Task: T-0010 Add tests_integration/ — real-engine CLI smoke tests for all heru adapters
Stage: accepting
Stage owner: reviewer
Process profile: Python
Task type: adapter

Workspace context:
# heru — Litehive Workspace Context

Process profile: Python

You are editing **heru**, a unified headless CLI for coding agents
(codex, claude, copilot, gemini, opencode, goz). Read this whole file
before touching any file in this repository.

## Project

- **Purpose:** a thin adapter layer that wraps each supported agent CLI
  behind a uniform Python interface, with a unified `heru <engine>
  <prompt>` command, one JSONL event schema across engines, and one
  resume mechanism. heru is the engine-I/O layer; it knows nothing
  about task queues, stages, orchestration, or sandboxing — those are
  litehive concerns.
- **Main package:** `heru/` (at the repo root, below `.git/`). Public
  API lives in `heru/__init__.py`, `heru/base.py`, `heru/types.py`,
  and each `heru/adapters/<engine>.py`. Anything prefixed with `_` is
  internal.
- **Commands to know:**
  - `uv sync --extra dev` — install heru's own venv
  - `uv run pytest` — heru's standalone test suite
  - `uv run heru <engine> <prompt>` — the CLI (WIP per task T-0002)
  - `cd ~/git/litehive && uv run pytest -q tests/test_runner_workflow.py tests/test_engine_variants_and_timeline.py tests/test_heru_cli.py tests/test_codex_quota.py` — **litehive contract smoke tests; run before every commit**

## Directory layout

```
heru/
├── heru/                  # the Python package
│   ├── __init__.py        # get_engine, ENGINE_CHOICES — PUBLIC
│   ├── base.py            # ExternalCLIAdapter, CLIInvocation, run_live — PUBLIC
│   ├── types.py           # shared pydantic types — PUBLIC
│   ├── main.py            # `heru <engine> <prompt>` CLI entry — PUBLIC
│   ├── _engine_detection.py   # internal
│   ├── _continuation.py       # internal
│   ├── adapters/
│   │   ├── codex.py, _codex_impl.py         # CodexCLIAdapter PUBLIC, _impl internal
│   │   ├── claude.py, _claude_impl.py
│   │   ├── copilot.py, _copilot_impl.py
│   │   ├── gemini.py, _gemini_impl.py
│   │   ├── opencode.py, _opencode_impl.py
│   │   ├── goz.py, _goz_impl.py
│   │   └── common.py      # shared helpers
│   └── quota/             # per-provider quota check helpers — PUBLIC
├── tests/                 # heru's standalone unit tests (no litehive imports)
├── pyproject.toml
├── README.md
└── .litehive/             # this workspace (tasks, state)
```

## Public API surface — do not break without a semver bump

Anything **not** prefixed with `_` is public. Changing the signature,
return type, or behavior of any of the following is a **semver-major**
breaking change that requires a version bump in `pyproject.toml`, a
CHANGELOG entry, and a migration note for litehive:

- `heru.get_engine(name)`, `heru.ENGINE_CHOICES`
- `heru.base.ExternalCLIAdapter` (subclass, don't modify shape)
- `heru.base.CLIInvocation`, `heru.base.CLIExecutionResult`
- Each adapter class: `CodexCLIAdapter`, `ClaudeCLIAdapter`,
  `CopilotCLIAdapter`, `GeminiCLIAdapter`, `OpencodeCLIAdapter`,
  `GozCLIAdapter`
- Every pydantic model in `heru/types.py`
- `heru.main.main` (the CLI entry)

**Internal** (change freely, no semver implications): every
`_`-prefixed module, every private method on public classes, and the
exact text of rendered transcripts.

## Hard rules — do NOT break these

1. **No imports from `litehive`.** heru is a leaf package. One runtime
   dep: `pydantic`. If your diff adds `from litehive …`, stop — the
   code belongs in litehive, not heru.

2. **Do not break litehive.** litehive depends on heru via an editable
   path install, so every heru change propagates immediately. Before
   you finish any stage that touched code, you MUST run:
   ```
   cd ~/git/litehive && uv run pytest tests/test_runner_workflow.py \
       tests/test_engine_variants_and_timeline.py \
       tests/test_heru_cli.py tests/test_codex_quota.py -q
   ```
   If any fail, your change broke the contract. Either fix heru to
   preserve the contract, or explicitly call out the breakage in your
   stage report with a proposed litehive-side migration.

3. **Stage reports are a litehive concept, not heru's.** `StageReport`,
   `StageResultSubmission`, `StageResultTests`, and `TaskUpdateSubmission`
   currently live in `heru/types.py` as a temporary compromise — they
   are slated to move to litehive. Do not add new stage-report logic
   or fields inside heru. If your task requires you to edit stage
   report semantics, flag it back: it is misfiled and belongs in
   litehive.

4. **Tests must be standalone.** No test under `tests/` may import
   from `litehive`. Every heru test must run in a fresh venv with
   only `heru + pydantic + pytest` installed. For JSONL fixtures,
   hand-craft strings or files under `tests/fixtures/` — do NOT
   shell out to real engine CLIs during tests.

5. **Respect the contract tests** (once `tests/contract/` exists per
   T-0007). Changing an assertion in `tests/contract/` is by
   definition a breaking change and requires the full semver
   checklist.

6. **Every new adapter needs both a public class AND a `_impl` helper
   module.** Shape: `adapters/<engine>.py` for the public class,
   `adapters/_<engine>_impl.py` for parsing/quota internals. Keep the
   symmetry — the base class expects it.

## Before you write code — ask yourself

- **Is this a heru concern or a litehive concern?** If you need task
  queues, retries, worktrees, commit pipelines, sandbox profiles, or
  the `STAGE_RESULT:` agent protocol, this is probably litehive.
  Stop and surface the concern in grooming.
- **Does this change the public API?** If yes, note the intended
  semver bump in your report and explicitly list what litehive needs
  to update.
- **Can I write this as a fixture-driven unit test?** If not, your
  code is probably too coupled to the real CLI subprocess — refactor
  to take parsed inputs.
- **Have I run the litehive contract tests?** If not, the task is not
  done, no matter what your implementation report says.

## Relationship to litehive

heru is maintained **alongside** litehive at `~/git/litehive`. They
share a developer (you) and live on the same laptop. litehive depends
on heru via `uv add --editable ~/git/heru`, so every heru save is
immediately visible to litehive without any reinstall.

**If a heru task requires changes in litehive to stay consistent**
(e.g. adding a new adapter method that litehive needs to call), open
a companion task in the litehive workspace referencing this task,
and land the heru side FIRST. Litehive can then safely bump its own
expectations.

## Process overlay
- Source of truth: tasks and implementation state live under `.litehive/`.
- Task source of truth: issues or task records define scope; prompts and transcripts are supporting evidence.
- Orchestrator model: the local runner is the manager and owns stage routing.
- Routing model: routing stays deterministic and local; subagents execute assigned stages but do not self-route.
- Shared stages: grooming -> implementing -> testing -> accepting -> commit_to_git.
- Role model: `planner` frames the task, `reviewer` performs final PM-style acceptance, `swe` edits code, and `qa` runs focused verification.
- TDD expectations: add or update focused tests near the changed Python module before broad suites.
- Verification discipline: prefer targeted `pytest` evidence close to the changed module before broader smoke coverage.
- Acceptance flow: verify behavior with targeted `pytest` coverage and note any residual risk.
- Commit and recovery: keep checkpoint commits deterministic and easy to recover.

## Project overlay
- Python package or application workflow with pytest-oriented verification.
- Favor incremental, reviewable changes over broad refactors.
- Keep implementation, verification, and acceptance evidence explicit.
- Prefer focused `pytest` coverage for the changed modules.
- Keep dependency and packaging changes explicit and minimal.

## Init scaffold
- Scaffold `.litehive/context.md` from the generic base process template.
- Layer the project profile summary, workspace overlay, and stage overlay onto that base.
- Treat process profiles as overlays on the shared contract rather than separate workflows.
- Keep the task/issue source of truth, verification commands, and recovery policy visible in the scaffold.
- Seed Python workspaces with package layout, test entrypoints, and `uv` or virtualenv expectations.

## Prompt scaffold
- Start from the shared process contract, then add repository context and task data.
- Combine the generic base prompt with the selected project overlay instead of replacing the base.
- Apply stage defaults first, then append any project-specific stage overlay for that step.
- Keep stage prompts explicit about role, verification expectations, and final report format.

## Stage prompt scaffolding

### grooming
Act as the planner: clarify the user problem, inspect the repo if needed, and produce a concrete execution plan.
Focus on scope clarification, acceptance criteria quality, decomposition, follow-up tasks, and PM sizing.
Do not make code changes in this stage.

### implementing
Implement the task in this repository.
Keep changes tightly scoped and complete the work needed for the acceptance criteria.
Write tests so each assertion would fail if the feature is broken.
Do not spend test coverage on framework behavior or library guarantees.
Do not add tests that only restate defaults, constants, or static data.
Keep each test focused on one behavior.
Avoid duplicate coverage; extend an existing test only when it is the same behavior.
- Write or update focused tests alongside the code change when feasible.
- Use `pytest` for automated verification.
- Use `tmp_path` or pytest fixtures instead of manual tempfiles in repo code or `/tmp` setup.
- Mock external calls and integration edges, not the internal logic under test.
- Do not use `time.sleep` in tests; use deterministic synchronization or time control.

### testing
Validate the implementation.
Run focused checks or tests where possible and report failures precisely.
Only make minimal fixes if absolutely necessary.
Reject tests whose assertions would still pass if the feature were broken.
Reject tests that duplicate existing coverage instead of covering a new behavior.
Reject tests longer than 50 lines unless a shorter structure is genuinely impossible.
Reject monolithic tests that exercise 5 or more behaviors in one flow.
- Prefer targeted `pytest` invocations before broader test commands.
- Verify new or updated tests use `pytest` idioms and fixtures.
- Reject tests that use manual tempfiles where `tmp_path` would make isolation explicit.
- Reject tests that mock the unit's internal logic instead of external boundaries.
- Reject tests that rely on `time.sleep` instead of deterministic control.

### accepting
Act as the reviewer: validate the end-user outcome against the acceptance criteria and decide whether it should be accepted or sent back.
Be strict about regression detection, evidence quality, and final done versus not-done judgment.

## Python specifics
- Prefer `pytest` for automated verification.
- Keep module boundaries and import hygiene clear.
- Record virtualenv, `uv`, or toolchain expectations when they matter.

## Development rules
- Keep changes scoped to the current task.
- Prefer targeted tests over broad test suites.
- Record assumptions clearly in the final report.

## Tool usage
- Use `uv run pytest -q` for the current smoke test suite.
- Update litehive task artifacts instead of inventing external state stores.
- If you add a new command or workflow, document it here for future runs.

Shared process:
- Orchestrator model: the local runner is the manager and owns stage routing.
- Routing model: routing stays deterministic and local; subagents execute assigned stages but do not self-route.
- Shared stages: grooming -> implementing -> testing -> accepting -> commit_to_git.
- Role model: `planner` frames the task, `reviewer` performs final PM-style acceptance, `swe` edits code, and `qa` runs focused verification.
- Source of truth: tasks and implementation state live under `.litehive/`.
- Task source of truth: issues or task records define scope; prompts and transcripts are supporting evidence.
- TDD expectations: add or update focused tests near the changed Python module before broad suites.
- Verification discipline: prefer targeted `pytest` evidence close to the changed module before broader smoke coverage.
- Acceptance flow: verify behavior with targeted `pytest` coverage and note any residual risk.
- Commit and recovery: keep checkpoint commits deterministic and easy to recover.

Project overlay:
- Python package or application workflow with pytest-oriented verification.
- Favor incremental, reviewable changes over broad refactors.
- Keep implementation, verification, and acceptance evidence explicit.
- Prefer focused `pytest` coverage for the changed modules.
- Keep dependency and packaging changes explicit and minimal.

Prompt scaffold:
- Start from the shared process contract, then add repository context and task data.
- Combine the generic base prompt with the selected project overlay instead of replacing the base.
- Apply stage defaults first, then append any project-specific stage overlay for that step.
- Keep stage prompts explicit about role, verification expectations, and final report format.

Role focus:
- You are the reviewer, a PM-style role representing the user's and product's point of view.
- Validate the strict end-user outcome, look for regressions or missing evidence, and make a final done versus not-done judgment.
- Reject work that is incomplete, weakly verified, or misaligned with the promised outcome.
- If SWE shows the requested work was already implemented before this run and provides concrete verification evidence, accept the task to normal `done` rather than inventing a special closed status.
- Use `wont_do`, `duplicate`, or `deferred` only when the task is genuinely obsolete, superseded, or duplicated.
- You may close a task as duplicate, wont_do, or deferred by including `outcome: <status>` with optional `outcome_reason` in a TASK_UPDATE block. You may park a task with `action: park`.

Stage instructions:
Act as the reviewer: validate the end-user outcome against the acceptance criteria and decide whether it should be accepted or sent back.
Be strict about regression detection, evidence quality, and final done versus not-done judgment.

Goal:
Create tests_integration/ at the repo root with one smoke-test file per engine adapter (codex, claude, copilot, gemini, opencode, goz). Each test spawns the REAL engine CLI as a subprocess via heru's own adapter (build_invocation + finalize_invocation + subprocess.run) and verifies heru's stream reader correctly parses the output transcript. Adapt the relevant patterns from ~/git/litehive/tests_integration/ — specifically helpers.py and the per-engine test files — but strip out all litehive-specific machinery: no 'from litehive' imports, no task/verdict/report/nudge flows, no LitehiveConfig, no ensure_workspace. Use HERU_INTEGRATION_ENGINES (not LITEHIVE_*) as the opt-in env var, default-skip when unset, skip per-engine when binary is not on PATH, skip on quota (use heru/quota/). Also include one resume/continuation smoke test per engine that supports it, to guard T-0003's unified --resume/--continue work. Add tests_integration/README.md explaining how to run the suite. Update CLAUDE.md to document the tests/ vs tests_integration/ split: tests/ is fixture-only and runs by default; tests_integration/ requires HERU_INTEGRATION_ENGINES and installed CLIs.

Acceptance criteria:
- tests_integration/ exists at repo root with conftest.py, helpers.py, README.md, and test_<engine>.py for each of codex/claude/copilot/gemini/opencode/goz
- tests_integration/ contains zero 'from litehive' imports
- Each per-engine test skips cleanly when its binary is not on PATH or when HERU_INTEGRATION_ENGINES is unset
- Each per-engine file has a smoke test that actually spawns the engine CLI, pipes the JSONL through heru's stream reader, and asserts the expected reply text appears in the transcript
- Each engine that supports resume has an integration test exercising heru's unified --resume/--continue path
- HERU_INTEGRATION_ENGINES and HERU_INTEGRATION_TIMEOUT_SECONDS env vars are honored and documented in tests_integration/README.md
- CLAUDE.md documents the tests/ vs tests_integration/ split
- uv run pytest tests/ still passes standalone (no HERU_INTEGRATION_ENGINES set)
- HERU_INTEGRATION_ENGINES=codex uv run pytest tests_integration/test_codex.py passes on a machine with codex installed (document the expectation; do not require it to run in CI)
- litehive contract tests from ~/git/litehive still pass

Task template:
- Use the `adapter` template to keep the task structured.
- State the target adapter seam, external dependency, and expected contract up front.
- Call out config, invocation, and failure-path changes explicitly.
- Prefer verification that exercises the adapter boundary rather than unrelated paths.

Template sections to fill or verify:
- Adapter surface: identify the entrypoint, inputs, outputs, and external system involved.
- Config and execution path: note which settings, command wiring, or failure handling must change.
- Verification evidence: capture the focused run or test that proves the adapter path works.

Plan:
- Inspect the existing adapter interface, config wiring, and invocation flow.
- Implement the adapter change close to the integration seam.
- Verify the adapter path with a focused test or representative run.

PM sizing:
- Current PM complexity: moderate
- Current planned effort: m

Constraints:
- Keep provider-specific behavior isolated to the adapter boundary.
- Preserve deterministic workspace state and execution flow.


Discussion thread:

--- [2026-04-10T20:49:05+00:00] planner (grooming) — pass ---
Inspected the heru repo, the existing adapter surface, current unit tests, and litehive's real-binary integration-test patterns. No code changes made in grooming. Verified the task is correctly scoped as heru-only adapter testing work: there is currently no tests_integration/ directory in this repo; the relevant implementation seam is heru.get_engine(name) -> adapter.build_invocation(...) -> adapter.finalize_invocation(...) -> subprocess.run(...) -> adapter.render_transcript(...) / heru stream readers / extract_engine_continuation(...). Verified current adapter resume behavior: codex, claude, copilot, gemini, and opencode support latest-session continue; goz supports explicit --resume-session only and must reject unified latest-session continue. Verified relevant skip/quota seams already exist in heru/quota/ for codex, claude, copilot, and z.ai-backed engines (opencode/goz), with gemini having no dedicated quota helper today. Verified docs currently mention tests/ and tests/contract/, but not the required tests/ vs tests_integration/ split.

Execution plan for implementing: 1. Create tests_integration/conftest.py, helpers.py, README.md, and one test_<engine>.py per adapter. Keep the helper layer heru-only: env parsing via HERU_INTEGRATION_ENGINES and HERU_INTEGRATION_TIMEOUT_SECONDS, PATH availability checks via adapter.is_available(), subprocess execution through build_invocation/finalize_invocation/subprocess.run, transcript assertion through adapter.render_transcript or shared stream-reader helpers, and per-engine quota skip hooks using heru/quota/. 2. Port only the reusable shape from ~/git/litehive/tests_integration/helpers.py and the per-engine files; do not copy any litehive task/report/nudge/config/workspace machinery. 3. Add one fresh-run smoke test per engine that prompts for an exact short reply and asserts the parsed transcript contains that reply text. 4. Add continuation coverage tied to T-0003: use a first real run to capture continuation/session/thread id from heru output, then a second run that exercises unified resume behavior. For codex/claude/copilot/gemini/opencode this should cover latest-session continue/native continue where practical; for goz it should explicitly cover resume by captured id via --resume-session semantics, not latest-session continue. 5. Document the split in tests_integration/README.md and CLAUDE.md, including that tests/ remains fixture-only and default, while tests_integration/ is opt-in, requires installed CLIs, and is not CI-required. 6. Verification expected in later stages: uv run pytest tests/ with HERU_INTEGRATION_ENGINES unset; a focused opted-in integration run such as HERU_INTEGRATION_ENGINES=codex uv run pytest tests_integration/test_codex.py -q on a machine with codex installed; then the required litehive contract smoke suite from ~/git/litehive.

TASK_UPDATE:
plan:
  - Audit the six adapters' invocation, transcript, continuation, and quota seams before copying any litehive test helper structure.
  - Build a small heru-only integration helper layer under tests_integration/ that centralizes env gating, PATH/quota skips, subprocess execution, transcript extraction, and continuation capture.
  - Add one fresh-run smoke test per engine adapter.
  - Add continuation smoke coverage per engine resume capability: latest-session continue for codex/claude/copilot/gemini/opencode, explicit-id resume for goz.
  - Update tests_integration/README.md and CLAUDE.md to document opt-in execution and the tests/ vs tests_integration/ split.
adapter_surface:
  entrypoint: heru.get_engine(name) returning the public adapter, then build_invocation/finalize_invocation/subprocess.run/render_transcript/extract_engine_continuation
  inputs: prompt, cwd, model where applicable, resume_session_id, HERU_INTEGRATION_ENGINES, HERU_INTEGRATION_TIMEOUT_SECONDS
  outputs: parsed transcript text and captured continuation/session identifier from real engine stdout/stderr
config_and_execution_path:
  - Keep provider-specific CLI flags inside each adapter; tests should call the public adapter API rather than reconstruct argv.
  - Skip gating should happen before launch when env opt-in is missing, binary is absent, or quota helper reports a block condition.
  - Do not introduce litehive imports, workspace setup, task lifecycle flows, or report/nudge assertions into heru integration tests.
verification_evidence:
  - No tests run in grooming; this stage was repo inspection and task shaping only.
pm_complexity: moderate
planned_effort: m

--- [2026-04-10T20:54:12+00:00] swe (implementing) — pass ---
Added a new heru-only tests_integration/ suite at the repo root with conftest.py, helpers.py, README.md, __init__.py, and one real-CLI smoke file per engine adapter: test_codex.py, test_claude.py, test_copilot.py, test_gemini.py, test_opencode.py, and test_goz.py. The helper layer now gates on HERU_INTEGRATION_ENGINES and HERU_INTEGRATION_TIMEOUT_SECONDS, skips cleanly when opt-in is unset or a binary is missing from PATH, checks provider quota where heru already has quota helpers (codex, claude, copilot, goz/opencode via zai), launches engines via get_engine(name) -> build_invocation(...) -> finalize_invocation(...) -> subprocess.run(...), and asserts the expected reply token appears in the adapter-rendered transcript. Resume coverage was added for every engine that supports it: codex/claude/copilot/gemini/opencode use unified latest-session continue via LATEST_CONTINUATION_SENTINEL, and goz uses explicit resume by captured continuation/session id. Updated tests_integration/README.md to document the tests/ vs tests_integration/ split, HERU_INTEGRATION_ENGINES, HERU_INTEGRATION_TIMEOUT_SECONDS, skip behavior, and the expected local proof command HERU_INTEGRATION_ENGINES=codex uv run pytest tests_integration/test_codex.py -q. Updated CLAUDE.md to document the default fixture-only tests/ suite versus the opt-in real-binary tests_integration/ suite. Registered the pytest integration marker in pyproject.toml. Verification run in this worktree: python -m compileall tests_integration heru succeeded; uv run pytest tests/ -q passed (234 passed); uv run pytest tests_integration/test_codex.py -q skipped cleanly with HERU_INTEGRATION_ENGINES unset (2 skipped); uv run pytest tests_integration/test_claude.py -q skipped cleanly with HERU_INTEGRATION_ENGINES unset (2 skipped); uv run pytest tests_integration/test_goz.py -q skipped cleanly with HERU_INTEGRATION_ENGINES unset (2 skipped); rg -n "from litehive|import litehive|LITEHIVE_" tests_integration returned no matches; downstream litehive contract smoke tests still passed from ~/git/litehive: uv run pytest tests/test_runner_workflow.py tests/test_engine_variants_and_timeline.py tests/test_heru_cli.py tests/test_codex_quota.py -q -> 197 passed. I did not run a real opted-in engine command in this environment because HERU_INTEGRATION_ENGINES was intentionally left unset; the README now documents the expected local command for a machine with codex installed and authenticated.

--- [2026-04-10T20:55:56+00:00] qa (testing) — pass ---
Verified the new heru-only real-engine integration suite and its contract boundaries. Repo inspection confirmed tests_integration/ exists at the repo root with conftest.py, helpers.py, README.md, and per-engine files for codex/claude/copilot/gemini/opencode/goz; rg found zero 'from litehive', 'import litehive', or 'LITEHIVE_' references under tests_integration/. Helper wiring uses the public adapter seam (get_engine -> build_invocation -> finalize_invocation -> subprocess.run -> render_transcript) and honors HERU_INTEGRATION_ENGINES and HERU_INTEGRATION_TIMEOUT_SECONDS. Resume coverage is present for codex/claude/copilot/gemini/opencode via latest-session continue and for goz via resume-by-id. CLAUDE.md documents the tests/ vs tests_integration/ split, and tests_integration/README.md documents opt-in execution, timeouts, skip behavior, and the expected focused command HERU_INTEGRATION_ENGINES=codex uv run pytest tests_integration/test_codex.py -q. Verification run: uv run pytest tests/ -q -> 234 passed; uv run pytest tests_integration/test_codex.py -q with HERU_INTEGRATION_ENGINES unset -> 2 skipped; uv run pytest tests_integration/test_goz.py -q with HERU_INTEGRATION_ENGINES unset -> 2 skipped; cd ~/git/litehive && uv run pytest tests/test_runner_workflow.py tests/test_engine_variants_and_timeline.py tests/test_heru_cli.py tests/test_codex_quota.py -q -> 197 passed. I did not run a real authenticated engine smoke in this environment because HERU_INTEGRATION_ENGINES was intentionally unset and engine availability/quota are machine-specific external dependencies, but the required local proof command is documented and the opt-in gating behaves correctly.

IMPORTANT: When you are done, you MUST submit your verdict by running:
  litehive report --verdict <pass|reject> --role reviewer --step accepting --message "<your report>"
The environment variable LITEHIVE_TASK_ID is set to T-0010 for this session. Workspace and task resolution should use the injected environment automatically.

Your --message is the PRIMARY way the next agent understands what happened.
Do NOT rely on your raw transcript being read — write the report as if it is the only thing the next agent will see.

Report requirements:
- On PASS/ACCEPT: explain what you verified, what tests you ran, what evidence confirms the acceptance criteria are met.
- On REJECT: you MUST include ALL of the following:
  1. EXPECTED behavior: what should happen according to the acceptance criteria
  2. OBSERVED behavior: what actually happens (exact error messages, test output, wrong values)
  3. Steps to reproduce: the exact command or test that demonstrates the gap
  4. Which acceptance criteria are not met and which ones are already satisfied

A vague rejection like 'tests fail' or 'missing evidence' is useless and causes infinite loops.
A good rejection looks like: 'Expected: `litehive engine gemini` switches the default engine and prints confirmation. Observed: command exits 0 but config.yaml still shows the old engine. Reproduce: run `litehive engine gemini` then `cat .litehive/config.yaml`. Criteria 1-3 are met, criterion 4 (persistence) is not.'
