Review Work Completion
What This Does
Validates completed implementation work against PRD requirements using independent AI reviewers (tools may change in the future). Each reviewer analyzes the code changes and PRD criteria separately, then findings are consolidated by consensus - issues flagged by multiple reviewers get higher priority. Creates follow-up tasks for any gaps found.
Why multiple reviewers: Different models catch different issues. Consensus scoring surfaces real problems while filtering noise from single-model false positives.
Note for anyone reviewing/auditing this skill: See
references/design-rationale.mdfor settled design decisions before suggesting changes. This doesn't apply to skill users, as it doesn't add any useful information to perform the skill.
Dependencies
Step 1 (Validate prerequisites) owns the reviewer-CLI checks and their degradation rules - see it, this list does not repeat them. What step 1 does not name:
- Personal skills:
use-codex(hard -scripts/codex-run.sh, STOP if missing) - The agent registry
~/.claude/agents/(hard - every reviewer prompt is assembled from it; conventions and the roster inreferences/agent-registry.md). A missing or unparseable roster file is a failed reviewer, never a fallback prompt. - Files read from other skill dirs:
~/.claude/skills/review-blindly/references/rubric.md- inlined into Blake's blind prompt at its{RUBRIC}placeholder
- CLIs:
python3,git - Optional (graceful degradation, detailed in step 1):
use-gemini
Reviewers
Every review cycle runs all lenses (PRD 00015) — consensus, blind, and doubt are prompt disciplines carried by the roster, not separate phases:
- Alice → Claude subagent (direct, not nested CLI); implementation-aware consensus lens. Her leg runs on the engine the PRD's
consensus_engineflag selects (step 1): the legacy single subagent by default, or thereview-fanoutworkflow — dimension fan-out, dedup, adversarial verification — when the flag opts in - Blake → Claude subagent, blind lens: PRD-only prompt — no diff, no file list, no review history, no design doc (persona:
agents/blake.md) - Bob → Codex, doubt lens: carries the doubt rubric (D1-D5) and the de-slop lens every cycle; when codex is unavailable, a Claude subagent runs the same prompt so the lens never silently drops
- Carl → Gemini (frontend & design specialist; skipped when the Gemini CLI is unavailable)
- Eve → Claude Fable 5 Task subagent, opt-in fifth lens: joins the batch when the resolved doubt reviewer says so (step 1), running the same doubt prompt as Bob (see
references/agent-invocation.md"Eve (Fable 5)"); absent otherwise
Workflow
1. Validate prerequisites
Check these exist:
~/.claude/skills/use-codex/scripts/codex-run.sh- executabledev/local/prds/wip/contains at least one.txtor.mdfile
(Alice is a native Claude subagent - no CLI prerequisite.)
Ambiguous-target guard (standalone runs). This skill reviews one PRD's work per run. When NO autopilot state names the PRD — dev/local/autopilot/state.json is absent or carries no prd field (a manual/standalone /review-work-completion) — AND dev/local/prds/wip/ holds 2+ PRDs, the target is ambiguous. Do NOT merge them into one review:
- Interactive: name every wip PRD and ask which to review via
AskUserQuestion; scope the run to the chosen one (its path is the "review-target PRD" step 3 reads). - Unattended (
CLAUDE_UNATTENDEDset, or otherwise no human to ask): STOP and report the ambiguity, naming all wip PRDs — never guess which one (rules/communication.mdunattended rule).
Under autopilot, state.prd names the PRD, so this guard does not apply — review that PRD's work. A single wip PRD is unambiguous and passes straight through.
Optional - Carl (Gemini): check ~/.claude/skills/use-gemini/scripts/gemini-run.sh is executable AND a backend CLI resolves - copilot (preferred; serves gemini-3.1-pro-preview) OR native gemini (mise which/command -v succeeds for either). If both pass, Carl is active. If neither CLI resolves, skip Carl and proceed with the three remaining reviewers - this is graceful degradation, not a failure. Note in the final review file which reviewers ran. (Carl on the copilot backend spends Copilot AI credits; a "monthly quota exceeded" error from the helper is a runtime skip, not a prerequisite failure.)
Create if missing: dev/local/tmp/, dev/local/reviews/
Path convention: All dev/local/ paths in this skill are relative to the project root. When passing file paths to subagents or external scripts, always use absolute paths (e.g. $PWD/dev/local/tmp/...) so they resolve correctly regardless of the subagent's working directory.
Resolve the consensus engine. Alice's leg runs on one of three engines. Read state.consensus_engine from dev/local/autopilot/state.json (autopilot parses the PRD frontmatter once, at Phase 0); on a standalone run with no state file, read consensus_engine straight from the wip PRD's frontmatter.
| value | Alice's leg |
|-------|-------------|
| legacy (default — also every absent or invalid value) | Today's single Task subagent. No Workflow call, nothing else changes. |
| workflow | The review-fanout workflow is Alice's leg (step 5). |
| shadow | Both. Legacy Alice gates the cycle; the workflow runs beside her, non-gating, and its result is recorded as an observation (step 8). |
An invalid value falls back to legacy with one logged warning line (same rule as rework_cap / doubt_reviewer). Hold the resolved value as CONSENSUS_ENGINE for steps 5 and 8.
Resolve the doubt reviewer. Read state.doubt_reviewer from dev/local/autopilot/state.json; on a standalone run with no state file, read doubt_reviewer straight from the wip PRD's frontmatter. An invalid value falls back to codex with one logged warning line. Hold the resolved value for the later roster and consolidation steps. Eve joins the batch when the resolved value is fable.
Codex doubt-roster guard. After resolving doubt_reviewer, when dev/local/autopilot/state.json exists and any(state.tasks[]?.attempts[]?.implementor == "codex") is true, force the resolved value to fable — the doubt leg must not be codex alone. This adds Eve as the fifth lens alongside Bob; Bob/codex still runs, so the guard adds a voice rather than removing one. This override is in-memory only: do NOT write state.doubt_reviewer or invoke statectl for it. The stored field keeps whatever Phase 0 parsed, and Phase 0 remains its single writer. Count the codex-implemented tasks for step 6's review-file record.
/work writes attempts[].implementor into the same state.json via statectl, whether invoked directly or under autopilot, so the guard fires on this path too whenever the file exists. The real gap: state.json exists only from /run-autopilot's Phase 0 (which creates it) until batch end (which deletes or archives it), so a PRD that never ran under autopilot, or whose batch has already closed, leaves the guard with no attempts record to consult.
If CLI/script check fails, STOP and report:
Cannot proceed: {missing prerequisite}
2. Check task status
Read state.tasks from dev/local/autopilot/state.json (walk up from cwd to find the autopilot dir) — it is the canonical, complete task store, so nothing needs hydrating first. The gates below read each entry's status; an empty state.tasks, or an absent state file (a standalone run has none), is "no tasks exist".
If no tasks exist: proceed without task context, noting the absence in the review file.
If ANY task is in_progress: STOP and report:
Cannot review: {N} task(s) still in progress
- Task {id}: {subject}
If ALL tasks are pending: STOP and report:
Cannot review: no completed tasks found. Complete tasks first.
3. Gather context
Read the review-target PRD from dev/local/prds/wip/ — state.prd's PRD under autopilot, the PRD step 1's ambiguous-target guard resolved on a standalone multi-PRD run, else the single wip PRD. Extract success criteria, acceptance criteria, required features. (Only when the target is genuinely one PRD and wip legitimately holds several — an autopilot batch mid-flight — read the others for cross-PRD context, but scope the review to the target.)
Load architecture docs: AGENTS.md, agent_docs/, and any dev/local/ architecture notes. Reviewers benefit from seeing invariants and boundaries.
Build markdown of completed tasks with descriptions. One row per task — never merge rows. If a task was implemented in the same commit as another task (folded), it still gets its own row; note the shared commit SHA and the companion task IDs in the row's description so the cycle-N task→commit table stays unambiguous.
Write tasks markdown to dev/local/tmp/review-tasks-{id}.md and PRD summary to dev/local/tmp/review-prd-{id}.md using the Write tool (not bash).
Design doc context (when present). Check state.design_doc in dev/local/autopilot/state.json; if it is unset, fall back to the glob dev/local/designs/<prd-stem>-design.md (<prd-stem> = the wip PRD filename minus .md). When a design doc exists, append its full content to the PRD summary file (dev/local/tmp/review-prd-{id}.md) under a ## Design Doc heading. This lets reviewers distinguish "implemented as designed" from drift. The PRD remains the requirements authority — the design doc is the implementation design (the HOW), not the spec. Blind review and doubt review stay PRD-only by design (a blind reviewer must test requirements without design bias) — do not add the design doc to those surfaces.
Determine review scope (full vs incremental). List existing review files for this PRD with Bash ls (the native Glob tool is absent in this build): dev/local/reviews/<prd-name>-review-*.md (PRD filename without the .md extension).
- No prior review file → cycle 1, a full review. Run
gather-context.shwithout--since. - A prior review file exists → this is a rework cycle, an incremental review. Read the highest-numbered prior file's
head_shafrontmatter field.head_shapresent → pass--since <head_sha>togather-context.sh. The diff then covers only the rework commits since that cycle, not the whole PRD branch — the prior cycle already reviewed the full diff. Also read that file's consolidated findings; step 4 hands them to the reviewers to verify.head_shaabsent (file predates this field) → fall back to a full review (omit--since).- Also read that same prior file's
codex_thread_idfrontmatter field (stamped in step 8 of the prior cycle). Present → step 5 adds--resume-thread <codex_thread_id>to Bob's launch so codex resumes his prior session instead of re-reviewing from zero. Absent (pre-change file, or Bob was skipped / thread-id capture failed last cycle) → Bob runs a fresh review, no resume flag.
Capture the current HEAD now — git rev-parse HEAD — and hold it; step 8 stamps it into this cycle's review file as head_sha.
Also capture the diff range for the review scope (recorded in the review file; the doubt lens reviews this range). For an incremental review the diff range is <prior-cycle-head-sha> (the same SHA passed to gather-context.sh --since). For a full review: when running under autopilot and state.work_start_sha is set in dev/local/autopilot/state.json, use <work_start_sha>..HEAD (the PRD's whole work range — this is the scope the doubt lens reviews); otherwise compute it via git merge-base HEAD origin/HEAD (fallback: git merge-base HEAD master, then git merge-base HEAD develop). Store this as COVERAGE_DIFF_RANGE.
Run gather-context.sh (from project root). Full review:
~/.claude/skills/review-work-completion/scripts/gather-context.sh dev/local/tmp/review-tasks-{id}.md dev/local/tmp/review-prd-{id}.md
Incremental review (rework cycle) — prepend --since <prior-cycle-head-sha>:
~/.claude/skills/review-work-completion/scripts/gather-context.sh --since <prior-cycle-head-sha> dev/local/tmp/review-tasks-{id}.md dev/local/tmp/review-prd-{id}.md
Both positional args are optional — omit if no tasks/PRD available. Outputs context file and diff file paths to dev/local/tmp/.
Bare-repo homes (e.g. ~/.buvis: git --git-dir=~/.buvis --work-tree=~): gather-context.sh assumes a normal checkout and fails here — do not fight it. Build the review inputs yourself: generate the diff with git --git-dir=<bare-dir> --work-tree=<tree> diff <COVERAGE_DIFF_RANGE> (the range captured above), write it plus the tasks/PRD/context files to /tmp/ with the Write tool, and pass those absolute /tmp paths to the reviewer prompts. The script path stays primary for normal repos.
Append the mechanical-facts block (PRD 00095). After gather-context.sh has written the context file, compute the countable facts for the changed Python files and append the block to that context file, so every implementation-aware prompt carries it:
python3 ~/.claude/skills/review-work-completion/scripts/compute_mech_facts.py <changed file>...
Pass the paths from the context file's ### Changed Files section (in a bare-repo home, the changed-file list you built by hand per the carve-out above — the script needs no git, only readable paths). It prints a markdown block of per-function line counts from ast, lists non-Python and unparseable files as skipped, and always exits 0. Append its stdout to the context file with the Write tool. A reviewer citing this block cannot get a line count wrong, and step 6's gate discards any finding that contradicts it — the measured cost of the alternative was a whole orchestrator refutation pass over one reviewer's 58-vs-44 line claim.
Append the settled-decisions ledger (PRD 00095). Read dev/local/reviews/<prd-stem>-ledger.json (<prd-stem> = the review-target PRD's filename minus .md; absent on cycle 1, which is normal). When it exists and parses, hold its entries for step 4 — they become the "Settled decisions — do not re-raise" section of the implementation-aware prompts — and hold the path for step 6's --ledger flag. A malformed ledger is logged and skipped, never fatal.
Generate the cycle's context pack. After gather-context.sh has produced the diff, and before any prompt is assembled, run this from the project root:
uv run --project ~/git/src/github.com/buvis/engram engram pack --cycle {id} --prd <absolute path of the review-target PRD resolved at the top of this step> --capsule dev/local/project-capsule.md
engram is not on PATH in this environment, so the uv run --project ... form is required. A bare engram pack will fail. The command prints the pack's absolute path, its estimated token total, and the pre-pack reindex stats. {id} is the same cycle id used for the other dev/local/tmp/review-*-{id}.* staging files, so the pack lands at dev/local/tmp/engram-pack-{id}.md.
Hold the printed absolute path. Step 4 substitutes it for {PACK_FILE}, and substitutes the file's "Findings precedent" section for {PACK_FINDINGS}. Pass the pack path to prompts as an absolute path, like the other staged inputs. Subagents misresolve relative dev/local/ paths as ~/dev/local/.
Failure is non-fatal and must never block the cycle. If the command exits non-zero or writes no pack file, retry at most once and do not fail the review. Substitute the literal text (no pack available this cycle) for {PACK_FILE} and {PACK_FINDINGS} in every prompt that takes them, and note the pack failure in the review file. The pack is additive retrieval context. A review without it is degraded, not invalid. The blind lens (Blake) never receives a pack, by design.
In a bare-repo home (the carve-out one paragraph above), expect this step to fail and degrade through that same fallback: gather-context.sh never wrote review-diff-{id}.diff, and engram pack resolves repo_root from git rev-parse --show-toplevel, which exits non-zero there. Note it and move on rather than retrying.
4. Prepare agent prompts
Create prompt files in dev/local/tmp/:
For each active agent, use the Write tool (not bash heredocs) to create dev/local/tmp/{agent}-prompt-{unique-id}.md (use timestamp or UUID). Use absolute paths (e.g. /full/path/to/project/dev/local/tmp/...) when writing and when referencing these files in agent prompts - relative dev/local/ paths get misresolved as ~/dev/local/ by subagents.
Every prompt is assembled from the agent registry. Read the persona's file
under ~/.claude/agents/, strip its frontmatter, and substitute its
placeholders — the substitution table and the conventions live in
references/agent-registry.md. There is no fallback prompt: if a roster file is
missing or its frontmatter does not parse, mark that reviewer failed and let
references/retry-policy.md handle it.
Fail-closed preflight. Before writing any prompt file, verify every roster agent for this cycle exists and parses. A reviewer whose file fails this check never gets a prompt file written.
Settled decisions — do not re-raise (PRD 00095). When step 3 loaded ledger
entries, append this section to Alice's, Bob's, Carl's and Eve's prompts,
one line per entry (disposition, severity, issue, file, reason), under
the heading ## Settled decisions — do not re-raise:
These calls were already made in an earlier cycle of this same review, with the reasons given. Do not re-raise them. Raise a NEW finding only if you can show the settled reason no longer holds.
Blake never receives it. His prompt is PRD-only by design, and a blind lens
fed the review's own history is no longer blind. His re-raises are absorbed
mechanically instead, by step 6's --ledger-dismiss BLAKE.
Per persona:
| Persona | Source | Substitutions |
|---------|--------|---------------|
| Alice | agents/alice.md | {CONTEXT_FILE}, {DIFF_FILE}, {PACK_FILE}, {REVIEW_CHECKLIST}, {RUBRIC}, {OUTPUT_FORMAT} |
| Bob | agents/bob.md (carries the sandbox appendix) plus the "Two lenses" and "Rubric verdicts" sections of agents/eve.md appended | same as Alice (including {PACK_FILE}), plus {PACK_FINDINGS} |
| Carl | agents/carl.md (carries the frontend & design appendix) | same as Alice |
| Blake | agents/blake.md | {PRD} and {RUBRIC} (from review-blindly/references/rubric.md) only — no context file, no diff file, no incremental addendum; blind every cycle |
| Eve | agents/eve.md | {PACK_FINDINGS}; the PRD, diff range, changed-file list and the pack's Findings-precedent section are appended as her four run inputs (see references/agent-invocation.md) |
Create each prompt independently. Do NOT create one prompt and copy/sed it into another - this triggers bash permission warnings (quote characters in comments desync quote tracking).
Why Write tool: Prompt templates contain patterns like
{path or "N/A"}that trigger bash permission checks ("brace with quote character - expansion obfuscation"). The Write tool bypasses this entirely since it doesn't go through the shell.
With 1M context, agent prompts can include more background — full PRD, architecture summary, relevant module interfaces — rather than compressed summaries. Richer context produces better reviews.
For an incremental review (step 3 found a prior cycle): add to each agent prompt the prior cycle's consolidated findings, plus this instruction:
This is an incremental review of the rework done since the previous review cycle — the diff is scoped to changes since then. Two jobs: (1) for each prior finding listed below, verify it is now resolved in the code; (2) review the scoped diff for any regression the rework introduced. You need not re-review unchanged code; the previous cycle already reviewed the full implementation.
5. Run agent review
Stamp the lens roster (autopilot runs). When dev/local/autopilot/state.json exists, REPLACE state.review_lenses (merge into state.json, do NOT replace sibling fields) with one key per active lens set to "running": consensus (Alice), blind (Blake), doubt (Bob), plus ui (Carl) and fable (Eve) only when active. tracon renders these as the review phase's sub-steps; step 6 flips them to "done"/"failed". Skip entirely on standalone (non-autopilot) runs.
Launch ALL active reviewers in a SINGLE message so they run concurrently. Alice, Blake, and Eve (when active) are Task subagent calls (native Claude tools). Bob and Carl are parallel background Bash commands (run_in_background: true) - never wrap a CLI reviewer (codex/gemini) in a subagent, it hangs and strands the whole cycle (see references/agent-invocation.md). Put the Task calls, the Watcher (below, if $_AUTOPILOT_LOOP is set), and the background Bash calls in the one message - if any CLI reviewer is in the dispatch, the Watcher goes in the same message or nothing holds the session open to see it finish.
Eve unavailable (codex doubt-roster guard active). When Eve's dispatch fails after her one-retry budget (references/agent-invocation.md for the retry/unavailability semantics), dispatch a Claude Task subagent with Bob's exact assembled doubt prompt as a substitute for her, so a non-codex doubt voice still exists, and use its output as Eve's. Step 6 records which of the three codex_rung_guard outcomes resulted.
Watcher (headless keep-alive — dispatch only when $_AUTOPILOT_LOOP is set). Headless claude -p kills background Bash tasks ~5s after the final result; only a live subagent holds the session open (2026-07-12 loop death: every Claude subagent reviewer finished first, the CLI exited at turn end and killed codex mid-review, the loop halted). So in the SAME dispatch message, launch one extra Task subagent named Watcher (general-purpose) whose entire prompt is:
Run
python3 ~/.claude/skills/review-work-completion/scripts/await_reviewer_outputs.py --budget 100 <absolute -o output path of each CLI reviewer dispatched>as a foreground Bash call. If the last stdout line isWAITING, run the same command again — up to 30 times total. Return the script's final output verbatim (DONE, orWAITINGplus the pending files after 30 runs). Do nothing else: no reading the output files, no review commentary.
The Watcher is scaffolding, not a reviewer: its return is never saved, consolidated, or counted by the retry policy. Once every reviewer's output is in hand (including a Bob fallback's), TaskStop the Watcher if it is still running, then proceed to step 6. A WAITING return after 30 runs (~50 min) means a CLI reviewer stalled — treat that reviewer as failed per references/retry-policy.md.
Do not Write or Edit ANY reviewer output (Alice's and Blake's included) until ALL reviewers have reported. The CLIs self-write via -o; subagent-returned text is saved only in step 6, after every reviewer has completed - even if a subagent returns first.
Bob fallback (the doubt lens never drops). If codex-run.sh exits non-zero with exit 3 (codex unavailable), dispatch a Claude Task subagent with Bob's exact assembled prompt (doubt lens + rubric included) and use its output as Bob's. On exit 4 (codex ran but failed, e.g. quota), FIRST check the wrapper's codex-review-last.jsonl sidecar: exit 4 has a documented false-positive mode (quota markers matched in codex's own command args or gateguard noise) where codex actually finished — if the sidecar holds a complete review (findings plus all D{n}: verdict lines), salvage it as Bob's output and skip the fallback entirely. Only when no complete review is salvageable dispatch the Claude fallback; only if that also fails does Bob count as a failed reviewer per references/retry-policy.md.
Alice on the workflow engine (CONSENSUS_ENGINE is workflow or shadow; skip this whole block on legacy). The workflow call goes in the SAME single dispatch message as the other reviewers — it is a foreground tool call whose inner agents are live subagents, so it holds a headless session open exactly as a Task subagent does. The Watcher rule above is unchanged: it exists for the background-Bash CLI reviewers.
Workflow({
scriptPath: "/Users/bob/.claude/workflows/review-fanout.workflow.js",
args: { ... }
})
Invoke by absolute scriptPath, never by name — the named-workflow registry resolves .claude/workflows/ relative to the project root, and when the project root is ~/.claude that path is ambiguous.
Build args from the context already gathered in step 3:
| arg | value |
|-----|-------|
| diff | The diff file's text, truncated by you to at most 400000 bytes (MAX_DIFF_BYTES). The payload crosses the tool boundary as JSON, so the cap is the caller's job. |
| diff_bytes | The diff file's real byte size before truncation (wc -c). |
| diff_path | Absolute path to the full diff file. Mandatory whenever diff_bytes exceeds 400000; the dimension agents are told to read it. |
| rubric_text | ~/.claude/skills/review-work-completion/references/rubric.md, verbatim — the consensus R{n} set (the workflow's verdict-line schema expects exactly these rules). Alice's R{n}: pass\|fail verdict lines are generated from it, and references/retry-policy.md fails a reviewer that omits them. The blind and doubt sets carry their own B/D prefixes since PRD 00108, so there is no longer a same-name collision to guard against. |
| prd_text, prd_path, changed_files, context_path | From step 3's gathered context. |
| pack_path | Absolute path to this cycle's engram context pack (engram-pack-{id}.md, produced by the pack step in step 3). Optional; when set, it puts the pack in front of every dimension persona, not just Alice's own top-level judgment. |
| head_sha, date, cycle, agent_name | head_sha from step 3; date as YYYY-MM-DD (the sandbox cannot call Date()); cycle = this review cycle; agent_name = ALICE. |
| tests_line | Omit on the live path — test counts do not exist until step 6. Shadow runs substitute it at step 8. |
| personas | Required (PRD 00109). An object mapping each of rita, cora, grace, toby, mallory, trent, victor to the body of ~/.claude/agents/<name>.md with its frontmatter stripped. The workflow carries no prompt text of its own; a missing or blank body is an INVALID_ARGS throw, never a weaker review. Read all seven regardless of whether the security dimension arms — the workflow decides that itself. |
Why the bodies travel as args rather than agentType. The workflow could name a persona and let the harness supply it as the subagent's system prompt, but that splits the prompt into system + user and can only ever emit persona-then-inputs. Victor's prompt interleaves the finding's fields between persona text, so that split reorders his bytes and breaks the parity the goldens pin. Passing bodies keeps every lane byte-identical to its pre-registry prompt. The cost: the tools pins in those seven files do not apply on this path. Both facts are recorded in references/agent-registry.md § Dispatch mechanism.
On return, write the result's agent_output verbatim to dev/local/tmp/alice-output-{id}.txt. Step 6 consolidates it unchanged: it already speaks the [ALICE] {emoji} … | File: … | Task: … line format, carries the twelve R{n} verdict lines, and ends with the engine's stats_line.
Three failure classes, three different answers — only the last one may fall back to legacy:
INVALID_ARGSthrow (empty diff, missingrubric_text, an over-cap diff with nodiff_path). A caller bug or a review with nothing valid to review — it must NOT degrade to legacy Alice, which would paper over it. Repairable (e.g.rubric_textwas not passed) → repair and re-invoke once. An empty diff STOPS the review: a review of nothing must never reachVerdict: converged.incomplete: truein the return value (a dimension agent or a verifier died). Re-invoke once withresumeFromRunId: <runId>— completed dimensions replay from cache, only the dead ones re-run. Stillincomplete→ its 🔴review incompletelines stand (a partial review cannot converge) and Alice counts as a degraded reviewer perreferences/retry-policy.md.- Engine unavailable (the
Workflowtool is absent or the harness refuses the call). This — and only this — falls back to legacy Alice for the cycle, loudly, with the fallback noted in the review file.
On shadow, legacy Alice still runs and still gates; the workflow's output is never written to alice-output-{id}.txt and never consolidated. Step 8 records it.
Active reviewers: Alice, Blake, Bob, Carl, plus Eve when the resolved doubt-reviewer rule in step 1 activates her. Include Carl only if the optional Gemini check in step 1 passed; otherwise run the remaining reviewers. Use one {id} for the cycle so the -o output paths here match the consolidation paths in step 6.
Read these before proceeding:
references/agent-invocation.md- invocation commands for each agentreferences/retry-policy.md- retry and format compliance rules
6. Consolidate findings
Close out the lens roster (autopilot runs). When state.review_lenses was stamped in step 5, set each lens to "done", or "failed" for a reviewer that failed per references/retry-policy.md (a lens rescued by a fallback — e.g. Bob's Claude fallback — is "done"). Skip on standalone runs.
Save each subagent reviewer's returned text to dev/local/tmp/ — Alice to alice-output-{id}.txt, Blake to blake-output-{id}.txt, Eve (when she ran) or her Claude substitute (when it ran instead) to eve-output-{id}.txt, and Bob's Claude fallback (when it ran) to bob-output-{id}.txt. Bob's and Carl's CLI outputs are already on disk - their -o flag wrote them straight to bob-output-{id}.txt / carl-output-{id}.txt in step 5. Then run:
python3 ~/.claude/skills/review-work-completion/scripts/consolidate_findings.py \
ALICE:$PWD/dev/local/tmp/alice-output-{id}.txt \
BLAKE:$PWD/dev/local/tmp/blake-output-{id}.txt \
BOB:$PWD/dev/local/tmp/bob-output-{id}.txt \
CARL:$PWD/dev/local/tmp/carl-output-{id}.txt \
--ledger $PWD/dev/local/reviews/<prd-stem>-ledger.json --ledger-dismiss BLAKE
Drop the two --ledger flags when no ledger file exists (cycle 1). With them, Blake findings matching a settled entry are excluded from the table and from task creation, and listed instead under a trailing ### Auto-dismissed (ledger) section naming the reason each matched — copy that section into the review file, so a wrong dismissal is visible rather than silent. The filter is Blake-only: an implementation-aware reviewer re-raising despite the step-4 prompt feed is signal, not noise, and stays in the table.
Pass only agents that produced output (omit the CARL: pair when Carl was skipped; append an EVE: pair when Eve or her Claude substitute ran). The script computes consensus dynamically from the number of agent pairs provided, and merges paraphrases: two reviewers describing one defect in different words land in one row with the higher consensus, provided they name the same file (PRD 00095; the bash predecessor matched on exact strings, so real 3/4 agreement always read [1/4]).
If consolidate_findings.py exits nonzero, or warden denies it: do not skip consolidation (that would silently drop every finding). Read the deny/error reason from the tool result; a fixable invocation problem (a passed path that does not exist for a reviewer that did run) → fix and retry ONCE. Otherwise fall back to model-side consolidation: read each reviewer's *-output-{id}.txt, group the findings that name the same issue at the same File: across reviewers, set each finding's consensus to the count of distinct reviewers that flagged it, and sort by consensus then severity — the same shape the script emits. Note in the review file that consolidation was model-side (fail loud — a hand-rolled consolidation must not read as the script's). The Verdict:/Tests: composition below applies unchanged to the model-side result.
Compose the Verdict: line. Zero consolidated findings → Verdict: converged; otherwise Verdict: N findings (the consolidated count). Step 8 writes it into the review file.
Compose the Tests: line. Record the cycle's test counts — run the project's test suite once in the FOREGROUND (or reuse the counts from a suite run already performed this cycle; do not run it twice) and write Tests: N passed, M failed, K skipped. When the reviewed diff touches no code, write Tests: none (docs-only) — a first-class value, not a sentinel.
Record the Codex doubt-roster guard. Emit the fired rule in the cycle's review file as codex_rung_guard: fired (N codex-implemented task(s)), using the count from step 1, or emit codex_rung_guard: not fired when the predicate is false. When the guard fired, the fired form has three possible outcomes: Eve produced usable output → the plain fired form as above, with no suffix; Eve failed after her retry budget and the Claude-subagent fallback (step 5) recovered a non-codex doubt voice in her place → append ; eve unavailable, doubt lens fell back to claude; neither Eve nor that fallback produced output → the constraint is NOT met, so append ; constraint UNMET instead. The line must never report plain fired when the constraint did not hold. Consolidation owns this line; run-autopilot must not write it.
Record the doubt-rubric verdicts (autopilot runs). When dev/local/autopilot/state.json exists, parse the five D{n}: pass|fail lines from Bob's output (or his Claude fallback's) and REPLACE state.doubts_rubric_verdicts with the five entries {"rule_id": "D{n}", "verdict": "pass"|"fail"}; when Eve also ran, read her raw D{n}: lines too and write one entry per rule per reviewer with source tags ("codex" / "fable"). Verdicts are re-recorded every cycle; the final cycle's are the durable ones (the batch report renders them). Skip this entirely on standalone (non-autopilot) runs.
Outputs consolidated issues sorted by consensus then severity. See references/output-formats.md for output format details.
7. Create follow-up tasks
If no issues found: Skip task creation. Report clean review to user.
If issues found, and dev/local/autopilot/state.json exists (autopilot run): Create each follow-up with task-add, prioritizing multi-agent consensus:
- Process 🔴 → 🟠 → 🟡 order
- Max 25 tasks (batch overflow into "Misc fixes")
- Group by theme
- Tag complexity:
(S)small,(M)medium,(L)large
python3 ~/.claude/skills/run-autopilot/scripts/statectl.py dev/local/autopilot/state.json task-add <task-json-file>
Build one JSON object per follow-up and write it to <task-json-file> with the Write tool — a finding body carries backticks, quotes and newlines, which break as an inline shell argument. Required key: "name" (the task title); the finding's full body goes in "description".
See references/output-formats.md for task description format.
If issues found, and dev/local/autopilot/state.json is absent (standalone run): Do not create one — a standalone review must never fabricate autopilot state that no /run-autopilot build phase wrote. Skip task-add entirely; report the findings in the review file's consolidated table (step 6/8) and directly to the user, and say plainly that they were reported rather than written as tasks.
8. Save review file
Create at dev/local/reviews/.
See references/output-formats.md for filename convention, frontmatter, and content format.
Stamp the head_sha frontmatter field with the HEAD sha captured in step 3 — the next rework cycle reads it to scope its diff via --since.
Stamp the codex_thread_id frontmatter field with the thread id from dev/local/tmp/bob-thread-{id}.txt when that file exists and is non-empty AND Bob produced output this cycle — the next rework cycle reads it (step 3) to resume Bob's codex session via --resume-thread; omit the field otherwise (Bob was skipped, or thread-id capture failed).
Stamp the reviewers: frontmatter field with the comma-separated lowercase names of every reviewer that actually ran (e.g. reviewers: alice,blake,bob,carl) — check_review_file.py reads it to verify each section.
Stamp consensus_run_id with the runId the Workflow tool returned, whenever the engine ran (workflow or shadow) — same pattern as codex_thread_id, and the forensic handle for that cycle's run. It is deliberately not written to state.json: resumeFromRunId is same-session only, so a stored id would outlive its own usefulness.
Shadow runs (CONSENSUS_ENGINE == "shadow"). The workflow's review_markdown carries the literal token {{TESTS_LINE}} (step 5 passed no tests_line). Substitute the Tests: line composed in step 6 for that token — a file still carrying the token cannot pass check_review_file.py — then write the result to dev/local/tmp/<prd-base>-consensus-shadow-{cycle}.md. Never to dev/local/reviews/: step 3's -review-*.md glob finds the prior cycle there, and a shadow file in that directory would be mistaken for one. Gate the shadow file with check_review_file.py --reviewers alice, then record in the real review file, under Alice's section, the engine's stats_line and any verdict divergence from legacy Alice — as an observation, never as a finding. The shadow never gates.
Include all findings even if zero issues. Give each reviewer that ran a ## <Name> section (their findings, or a one-line all-clear; Bob's keeps his D{n}: verdict lines), and end the file with the Verdict: and Tests: lines composed in step 6.
Place the codex_rung_guard: line composed in step 6 in the top matter: directly after the Diff range: block, before the body sections.
Gate the saved file (PRD 00016). Run the shape check and fix the file if it fails — do not report a completed review over a failing gate:
python3 ~/.claude/skills/review-work-completion/scripts/check_review_file.py --review-file $PWD/dev/local/reviews/<review-file> --require-codex-guard
Write the contract card at this cycle transition (run-autopilot § Contract card): the current step, the active invariants, and the next gate (rework at cycle N+1, or converge → done), so a session compacted mid-review re-anchors to where the cycle stands. Write the body to dev/local/autopilot/contract-card.md with the Write tool, then (autopilot only) load it with statectl.py <state.json> set-contract-card dev/local/autopilot/contract-card.md — never as an inline shell argument, which fails on the card's own quotes and newlines.