agentleFS
BryceEWatsonBryceEWatson/claude-global-skills/review-loop/SKILL.md

review-loop

Skill0 starsChanged 44 days agoMITView on GitHub

Dispatches a multi-agent review team over this session's work, runs an execution-grounded lint/test/build check, falsifies each finding against the diff, fixes load-bearing issues, and re-reviews until clean or the budget hits. Always records a commit-pinned verdict on the branch's open PR, so the review leaves a trail. Invoked manually or by the Stop hook after real work outside plan mode. `--mode claim` reviews an analytical conclusion against its primary sources instead of a diff; `--mode deliverable` reviews finished reader-facing work (a report, a content page, a decision briefing) for whether the human it is handed to can actually use it. Skip the next automatic run with `touch ~/.claude/skills/review-loop/.skip-next`; opt a repo out with `.claude/review-loop.disabled`.

54 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use motion-design, polish and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • soilmassSkill

    motion-design

    Create animations and motion graphics using industry standards. Use when asked to animate UI elements, create video content, implement transitions, or build motion systems. Implements W3C Design Tokens, Material Design 3 baseline, and Lottie specification compliance.

    Also in 5 projects that use this

  • HK-hubSkill

    polish

    Performs a final quality pass fixing alignment, spacing, consistency, and micro-detail issues before shipping. Use when the user mentions polish, finishing touches, pre-launch review, something looks off, or wants to go from good to great.

    Also in 3 projects that use this

  • GoogleSkill

    to-spec

    Turn the current conversation into a spec and publish it to the project issue tracker — no interview, just synthesis of what you've already discussed.

    Also in 4 projects that use this

  • GoogleSkill

    improve-codebase-architecture

    Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.

    Also in 5 projects that use this

  • AWSSkill

    ship

    Finalize dev work on an issue branch — update docs, push, and open a PR.

    Also in 4 projects that use this

  • MicrosoftSkill

    council

    Convene the persona panel — six orthogonal review lenses — on a target. Cold independent fan-out, debate-to-consensus, synthesized verdict with recorded dissent and a roster manifest.

    Also in 3 projects that use this

  • +4 more from google

The file

Show its rules and text

We split this skill into the rules it gives an agent

35 rules. 0 are common, 17 are distinctive, and 18 are very specific to this project.

  • 0

    Common

    Most of the 47 edited versions we read keep these.

  • 17

    Distinctive

    This author's own choices. Worth a look.

  • 18

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    When a session outside plan mode stops with reviewable work in the repo, the Stop hook invokes this skill automatically.

    line 27
  2. Distinctive:

    When someone asks how to pause or tune the automatic review, these are the switches:

    line 39
  3. Very specific:

    Disable per project: create .claude/review-loop.disabled in the directory the session runs from.

    line 42
  4. Distinctive:

    Treat missing args defensively: any arg can be omitted, all have defaults.

    line 106
  5. Distinctive:

    Run the guard — this is a command, not a judgement call:

    line 146
  6. Very specific:

    If --independence was already passed by the hook, trust it and skip re-running the guard.

    line 172
  7. Very specific:

    One review per session: Before starting, check .local-state/<session-id>.json for a prior completed run.

    line 174
  8. Distinctive:

    Read the file if it exists.

    line 201
  9. Very specific:

    Read the existing lock's PID.

    line 211
  10. Distinctive:

    If lock's mtime > 30 min ago AND PID is dead → remove stale lock, retry once.

    line 212
  11. Very specific:

    If state.last_diff_sha === current_diff_sha AND state.iteration > 0:

    line 245
  12. Distinctive:

    Read the 6 agent files:

    line 254
  13. Very specific:

    Do NOT load execution-grounded.md in plan mode — there's nothing to execute.

    line 271
  14. Very specific:

    Do NOT load ship-readiness.md, simplicity.md, or statistical-rigor.md in plan mode — they assume code-shape artifacts and produce …

    line 272
  15. Very specific:

    Do NOT load execution-grounded.md — there is nothing to run.

    line 295
  16. Distinctive:

    Do NOT load any code/plan lens — they category-error on an analytical claim.

    line 296
  17. Very specific:

    Do NOT load execution-grounded.md (nothing to run), ship-readiness.md, simplicity.md, or statistical-rigor.md (code-shape lenses on prose).

    line 317
  18. Very specific:

    Read its [] for exactly what it buys, and no more: the checklist is embedded in the lens, so an empty result means the checks were present …

    line 320
  19. Very specific:

    All LLM reviewers dispatch in parallel (one message, multiple Task calls).

    line 341
  20. Very specific:

    No enabled lens is not a clean review: If disabled-roles leaves the current mode with zero Task lenses (deliverable mode has one, so …

    line 343
  21. Very specific:

    Write completion: 'stalled' to state, emit <promise>review-stalled</promise> with one line naming the mode and the lenses …

    line 346
  22. Distinctive:

    Every run produces a final assistant text block ending with exactly one of:

    line 422
  23. Very specific:

    Be honest about staleness: if the reviewed diff is uncommitted WIP, or local HEAD differs from the PR's headRefOid, say so in the comment — …

    line 442
  24. Very specific:

    Idempotent — never spam: if the PR's most recent comment already carries the <!-- review-loop:<sha> --> marker, update it with gh pr …

    line 445
  25. Distinctive:

    Self-gate: If an open PR for the branch already exists (the trail step posted to it) → skip.

    line 496
  26. Very specific:

    Docs-staleness one-liner: If the reviewed diff changed behavior, a flag/CLI surface, an API, or a config field, add one line: "confirm the …

    line 510
  27. Distinctive:

    Keep it to a few lines. A reviewed-clean change that never ships is not "done" — the operator should leave the session knowing exactly what …

    line 515
  28. Distinctive:

    NOT anchor to git rev-parse --show-toplevel: in a linked worktree that is the worktree's own root, and this step would find zero handoffs …

    line 530
  29. Very specific:

    Idempotency: If the attributed handoff already carries <!-- review-loop-reconciled:<diff_sha> --> for the current reviewed state, skip …

    line 540
  30. Distinctive:

    Add ONE line to the continuation prompt's next-action or verification-debts, phrased as an OPEN risk, never as resolved: "precondition: …

    line 551
  31. Distinctive:

    Done-close shape: if the handoff has no continuation-prompt / next-action / verification-debts section, do NOT inject one — at most fix a …

    line 555
  32. Distinctive:

    Do not paraphrase, re-voice, or add review jargon to any other line.

    line 558
  33. Distinctive:

    Atomic write: write the full reconciled content to a temp file in the same dir and rename over the original (never a partial in-place …

    line 560
  34. Very specific:

    If at any point cost_spent_input + cost_spent_output > cost_ceiling_input_tokens:

    line 572
  35. Distinctive:

    Treat current accumulated findings as the iteration's output.

    line 574

“Common” is measured: kept by at least 6 in 10 of the 47 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

/review-loop

You are running the auto-review-loop skill. The Stop hook installed at ~/.claude/skills/review-loop/stop-hook.cjs invoked you after Claude finished a session with real code changes outside plan mode. (Or the user invoked you manually.)

When it runs, and the off-switches

When a session outside plan mode stops with reviewable work in the repo, the Stop hook invokes this skill automatically. The hook enforces that, so no session has to remember to start a review. This section is the user-facing documentation of the hook: keep it in step with stop-hook.cjs and install.cjs.

What counts as reviewable is measured on the working tree, not on which tools ran. The hook takes the tracked changes against HEAD plus untracked files Git doesn't ignore, keeps the ones that count as code, plan or deliverable files, and runs only when that set isn't empty. So a new untracked source file made with Write triggers it, and so does a dirty tracked diff with no Edit or Write at all. It skips when the transcript (its last 200 KB) shows no Edit or Write and git diff HEAD is empty, even if untracked files exist; when nothing reviewable changed (only docs, handoffs, lockfiles, scratch or generated files); and when it already dispatched a review for this exact diff.

When someone asks how to pause or tune the automatic review, these are the switches:

  • Skip once: touch ~/.claude/skills/review-loop/.skip-next. The hook consumes the marker at the next stop.
  • Disable per project: create .claude/review-loop.disabled in the directory the session runs from. The hook looks for it under the session's working directory, not the Git top level, so a marker at the repo root doesn't reach a session started in a subdirectory. Normally that's the same place; if you launch from a subdirectory, put the marker there too. Every per-project file the hook reads (this one and the plan-path, code-extension and deliverable-path overrides under Notes) resolves the same way.
  • Disable specific reviewer roles per project: .claude/review-loop.disabled-roles, one lens name per line (the agent file's name without .md). Step 4 skips each listed Task lens. It can't switch off the execution-grounded check, which runs in code mode regardless, and a run left with no enabled lens for its mode stops as not clean rather than reporting clean (see "Dispatch (all modes)" in Step 4).
  • State: ~/.claude/skills/review-loop/.local-state/<session-id>.json.
  • Install manifest: ~/.claude/skills/review-loop/.local-state/install-manifest.json.
  • Install / uninstall: node ~/.claude/skills/review-loop/install.cjs / node ~/.claude/skills/review-loop/uninstall.cjs.

The other per-project knobs (plan paths, code extensions, deliverable paths and the deliverable standard) are listed under Notes at the end.

Argument parsing

Parse from the slash-command arguments:

  • --session-id <id> — required; uniquely identifies this session
  • --iteration <n> — 0-indexed iteration count; defaults to 0
  • --max-iter <n> — defaults to 3 (per THRESHOLDS.reviewLoop.maxIterations)
  • --cost-ceiling-tokens <n> — combined input+output token budget; default 300000
  • --scope <git-diff|head-1> — default git-diff (uncommitted changes)
  • --auto — set when hook-invoked; suppresses preamble, writes structured exit
  • --independence <independent|self-authored|unverified> — set by the Stop hook from the measured authorship check. Absent on a manual invocation, where Step 0 measures it itself.
  • --self-review-ack "<reason>" — the documented escape hatch. Runs the review in the authoring session anyway, and permanently records WHY on the verdict. See Step 0.
  • --mode <code|plan|claim|deliverable> — default code. Controls which reviewer lens set fires in Step 4. The Stop hook auto-selects in this precedence order:

    1. any changed file matching the plan-artifact globs (**/*-plan.md, **/*-proposal.md, **/*-spec.md, **/*-retrospective.md, **/*-research/**/*.md, or a project override at .claude/review-loop.plan-paths) → --mode plan;
    2. else any reviewable source file → --mode code;
    3. else any changed file matching the deliverable globs (.md/.mdx under reports/, research/, content/ or src/content/, plus *-report.md, *-brief.md, *-summary.md at any depth, or a project override at .claude/review-loop.deliverable-paths) → --mode deliverable.

    Code outranks deliverable on purpose: the deliverable lens set does not review source, so letting a stray content file demote a code diff would silently drop the code review. The honest cost is that a diff mixing code and a deliverable gets code lenses only — invoke --mode deliverable by hand for those.

    --mode claim reviews an analytical conclusion / research finding (not a diff): the "artifact" is a set of load-bearing claims plus the primary sources they cite, and reviewers are sent back to those sources to try to break the conclusion. Manual-invocation only (the hook never auto-selects it); pass the claims under review — and where their evidence lives — as the slash-command arguments.

    --mode deliverable reviews finished reader-facing work: a report, a published content or project page, a briefing, a PR/issue body, a draft message, a generated artifact, a CLI's output. The hook auto-selects it for a deliverable-only diff, and it is also the right manual mode for a deliverable that never lands in a file — a decision presented in chat, a rendered page — in which case pass the artifact (or a path/URL to it) as the arguments.

    Manual invocation may override the mode.

Treat missing args defensively: any arg can be omitted, all have defaults.

How much review to run

Review is validation, so it follows the same proportionate-validation rule as ship-it Step 7:

  • Highest-value 20% first. One pass over the change, verified in one run, is the unit of work. Add review machinery (another round, a hook, an audit, a gate, a dashboard) only after a measured shortfall, such as a defect a pass actually missed.
  • Extra review is a cost you justify. Re-running a check that already passed on the same diff, or adding a round nobody asked for, needs a stated reason. The execution-grounded check still runs the project's lint, test and build as Step 4 says; any test you add beyond it is picked by its link to the change. Full CI runs on the PR head after each push (in ship-it, after its Step 8 push and again after each fix that reaches the PR), and a testable build never waits on CI or on another review round.
  • Thoroughness scales with users, blast radius and reversibility. Every thoroughness rule in this skill (the lens set, the falsifier stage, the iteration loop, the cost ceiling) applies in proportion to blast radius. None is dropped, only sized. On a manual run, a reversible change few people use gets a short pass: a lower --max-iter (2 still leaves one round to fix and re-check; 1 leaves none, so any actionable finding ends the run review-exhausted) and lenses that keep to what the change can break. A change many people depend on, one that deploys on merge, or one that's hard to undo (a schema, production data, a public page) gets the defaults and every lens at full depth. An automatic run doesn't size itself: the Stop hook passes no --max-iter or --cost-ceiling-tokens, so it keeps the defaults (--max-iter 3, the 300,000-token ceiling).
  • Hard walls never relax. However small the change, these hold at full strength: Step 0's independence gate, the execution-grounded check in code mode, the rule that a run with no enabled lens is never review-clean, never declaring clean at the budget wall, the commit-pinned verdict on the PR, and every safety or authority gate.
  • This trims mechanism, never scope (Build-Complete, Core Directive 2 of the global instructions). Proportion decides how many rounds, re-runs and added mechanisms a review gets. It never narrows the diff under review, and it never drops an actionable finding: each one is still fixed or listed on the exit checklist.

Workflow

Step 0 — Independence gate (BEFORE any review work)

A session cannot independently review work it wrote: it carries the authoring context, and therefore the same blind spots that produced the defect. This step runs first, so that nobody has spent reviewer effort by the time it stops them.

Run the guard — this is a command, not a judgement call:

node ~/.claude/skills/review-loop/authorship-guard.mjs --session-id <session-id> --repo "$(git rev-parse --show-toplevel)" --json

It reports which files in the diff this session (or a subagent it dispatched) edited, and exits 0 independent · 3 self-authored · 4 unverified.

Act on the exit code:

  • 0 independent → proceed to Step 1 normally. Nothing else changes.
  • 3 self-authored, invoked manually → STOP. Do no review work. Print the guard's message verbatim: it names the fix (dispatch a fresh session, gh pr checkout <n>, review there), not just the fault. Do not post a verdict. Do not fix anything. Exit.
  • 3 self-authored, with --self-review-ack "<reason>" → proceed, but the run is permanently marked: every verdict it produces carries the SELF-REVIEW banner below and withholds the review-clean token, so the auto-merge gate keeps holding and a human decides. The reason is quoted verbatim on the PR.
  • 3 self-authored, hook-invoked (--auto) → the Stop event fires in the authoring session, so this is the normal case there. Do not suppress the review: its findings still have value and an unattended run has nobody to dispatch a replacement. Run it, fix load-bearing findings as usual, and mark the verdict SELF-REVIEW as above.
  • 4 unverified → proceed, but the verdict says independence: unverified and names why. Could-not-determine is never upgraded to independent.

If --independence was already passed by the hook, trust it and skip re-running the guard.

One review per session. Before starting, check .local-state/<session-id>.json for a prior completed run. A second /review-loop in a session that already ran one shares the first review's context — the same contamination one step later. Warn plainly, name the earlier run, and prefer a fresh session.

Step 1 — State load

State file: ~/.claude/skills/review-loop/.local-state/<session-id>.json.

Shape:

{
  "session_id": "...",
  "started_at": "<unix-ms>",
  "iteration": 0,
  "max_iterations": 3,
  "cost_spent_input_tokens": 0,
  "cost_spent_output_tokens": 0,
  "cost_ceiling_input_tokens": 300000,
  "last_diff_sha": null,
  "findings_history": [],
  "completion": null,
  "scope": { "branch": "...", "files": [] }
}

Read the file if it exists; otherwise initialize.

Step 2 — Concurrent-run locks

Two locks needed:

  • Per-session lock .local-state/<session-id>.lock — use fs.openSync(path, 'wx') (O_EXCL).
  • Per-repo lock .local-state/repo-<sha1(toplevel)>.lock — same pattern. Hash of git rev-parse --show-toplevel.

Lock files store <pid> + <unix-ts>. If either fails to acquire:

  • Read the existing lock's PID; check liveness via process.kill(pid, 0).
  • If lock's mtime > 30 min ago AND PID is dead → remove stale lock, retry once.
  • Otherwise → log skip: concurrent-loop-in-repo to .local-state/<session-id>.log and exit cleanly.

Step 3 — Gather scope

Claim mode: skip the git commands below — there is no diff. Scope = the verbatim claims under review (from the invocation args, or a named artifact file) + the primary sources they cite. Use the claims' combined text as the diff_sha equivalent for the stall check and as the reference text for the Step 6 drift-guard. Skip the "Leave a trail on the PR" section unless the analysis lives in a file tied to an open PR.

Deliverable mode: the artifact is the whole finished deliverable, not its diff — a reader meets the entire page, not the lines that changed. Run the git commands to identify which deliverable files changed, then read each one in full and pass that full text to the reviewer. Read .claude/review-loop.deliverable-paths first if it exists: it REPLACES the default globs listed above, so in an override project the built-in list is wrong by construction and matching against it would find nothing. If the matched set comes out empty, say so explicitly — an empty artifact set is a routing fault to report, never a review-clean. If the deliverable was passed inline (no file — a decision presented in chat, a page reachable only by URL), skip the git commands as claim mode does and use the artifact text as the diff_sha equivalent and the Step 6 drift-guard reference. When the deliverable renders (an HTML page, a site route, a generated image, a CLI's output), open or run it and pass what you actually saw alongside the source.

Run via Bash:

  • git status --porcelain — list of changed files
  • git diff HEAD — full diff text
  • git rev-parse --abbrev-ref HEAD — current branch
  • SHA the diff via Bash git diff HEAD | sha256sum (or compute in Node).

If state.last_diff_sha === current_diff_sha AND state.iteration > 0:

  • Stall: same diff after attempted fixes means we're not making progress. Write completion: 'stalled' to state; emit <promise>review-stalled</promise> + unresolved checklist; exit.

Step 4 — Dispatch reviewers (parallel)

The reviewer lens set depends on --mode:

Mode code (default)

Read the 6 agent files:

  • agents/simplicity.md
  • agents/design-coherence.md
  • agents/adversarial.md
  • agents/ship-readiness.md
  • agents/statistical-rigor.md (skip if project has .claude/review-loop.disabled-roles listing statistical-rigor)
  • agents/execution-grounded.md (special — see below; disabled-roles does not apply to it)

Mode plan

The code-tuned lenses misfire on planning documents (ship-readiness has nothing to say about a markdown spec; execution-grounded has no pnpm lint to run on a .md file; simplicity flags prose redundancy as if it were duplicated code). Use this lens set instead:

  • agents/vision-coherence.md — does the plan realize each non-negotiable / falsifier / stated goal of the source-of-truth doc the plan derives from (VISION.md, CLAUDE.md, charter, RFC)?
  • agents/operator-empathy.md — will the human who acts on this plan actually be able to act on it under realistic constraints (touch-time, mobile parity, anxiety surfaces, decision fatigue)?
  • agents/architecture.md — does the plan respect declared layer separations, introduce silent state, bloat scope beyond a referenced scope-table, or imply Tier-1 file edits without surfacing them?
  • agents/completeness.md — cross-reference the plan against every spec section it claims to cover; flag missing surfaces, untestable done-criteria, dangling Tier-1 asks.

Do NOT load execution-grounded.md in plan mode — there's nothing to execute. Do NOT load ship-readiness.md, simplicity.md, or statistical-rigor.md in plan mode — they assume code-shape artifacts and produce category-error findings on prose.

Mode claim

The artifact is not a diff — it is one or more load-bearing analytical claims (a research finding, a comparison verdict, a recommendation) plus the primary sources they rest on. The failure modes are overstatement, confirmation bias, missed disconfirming evidence, and method holes — not bugs. Use this lens set:

  • agents/claim-falsification.md — go to the cited primary sources and try to DISPROVE each load-bearing claim; hunt specifically for disconfirming evidence the analysis omitted.
  • agents/claim-method.md — is the method sound enough to support the claim (do comparable data actually exist; is the sample non-degenerate; is attribution by an authoritative field, not inference; is an under-powered null being sold as a finding)?
  • agents/claim-calibration.md — do stated confidence and the headline match the evidence? Flag overstatement, proxy-as-fact, absence-of-evidence-vs- evidence-of-absence, and caveat-then-ignore.
  • agents/claim-coverage.md — did the analysis examine the RIGHT evidence? Selection/sampling bias, missed sources, over-aggressive filtering.

Do NOT load execution-grounded.md — there is nothing to run. Do NOT load any code/plan lens — they category-error on an analytical claim.

Mode deliverable

The artifact is finished reader-facing work. Every other mode checks an artifact against its specification; this one checks it against the person who has to read it. Correct, complete work still fails if the reader can't find the answer, can't act without opening three other things, or has to rebuild a structure the author already had. Use this lens set:

  • agents/operator-empathy.md — Scope B. Assign the deliverable a genre (report / reference / decision-ask / narrative / machine-output), then run its D1–D11 checklist: answer-first, inventories rendered as prose instead of tables, lists carrying reasoning, heading shape vs. genre, a decision without its evidence inline, a menu instead of a recommendation, terms used before they're introduced, code detail in the reading path, density, rigor apparatus in the reading path, edit-meta.

One lens, deliberately. completeness and the claim-* lenses would each have something to say about a report, but they answer "is it right / is it whole", which is already covered elsewhere and is not the gap this mode exists to close. Do NOT load execution-grounded.md (nothing to run), ship-readiness.md, simplicity.md, or statistical-rigor.md (code-shape lenses on prose).

Read its [] for exactly what it buys, and no more: the checklist is embedded in the lens, so an empty result means the checks were present and found nothing. It is not corroborated by a second lens, and the lens's false-negative rate is unmeasured — an over-tightened check can return [] on a deliverable that genuinely fails, which happened during this mode's own calibration. The one failure it does rule out is a declared project standard silently failing to load, which must surface as a checklist-unavailable finding instead.

Dispatch (all modes)

For each enabled agent file (a Task lens is disabled when its file name without .md is listed in the project's .claude/review-loop.disabled-roles; execution-grounded is not a Task lens and ignores that file), dispatch a Task (subagent_type: general-purpose) with:

  • The agent file's full body as the system instruction
  • The diff + file list + branch name (plan mode: include the full content of changed plan files, not just the diff — review needs the surrounding doc context; claim mode: include the verbatim claims under review + pointers to the primary sources/transcripts/data they cite, and instruct each reviewer to read those sources directly — its job is to break the claim against ground truth, not critique prose; deliverable mode: include the FULL text of each changed deliverable (never only the diff), the rendered form where one exists, and the contents of .claude/review-loop.deliverable-standard if present — a one-line pointer to the project's own standard — plus the document it points at, so the lens can load it as additional checks)
  • For iteration ≥ 2: the prior iteration's findings injected as a Reflexion-style verbal reflection (prepend: "In the prior iteration you flagged: [list]. The developer applied fixes. Your new findings should reflect what's now true rather than re-litigate prior decisions.")
  • An instruction to return JSON findings only, no preamble
  • Plan mode only — hard PLAN-VS-CODE DISCIPLINE block: "This document is a PLAN, not implementation. Do NOT emit findings of the form 'code at X does not implement Y' against a forward-looking spec. Valid findings: internal contradiction in the plan, missing required section, untestable done-criterion, constraint violation, Tier-1 ask missing for an implied file edit. Cap at 5 findings; quality over quantity."
  • Claim mode only — hard CLAIM DISCIPLINE block: "You are reviewing an analytical CONCLUSION, not code. Ground every finding in the cited primary source (read it; do not trust the analysis's own summary of it). Valid findings: a load-bearing claim contradicted or unsupported by the source, disconfirming evidence the analysis omitted, a method flaw that makes the claim unsupportable, overstatement vs. stated confidence. A failed falsification (you tried and could NOT break a claim) is a valid, useful result — report it. Cap at 6 findings; quality over quantity."
  • Deliverable mode only — hard DELIVERABLE DISCIPLINE block: "You are reviewing FINISHED work for whether its reader can use it — not whether it is correct, complete, or well-written. Assign the genre first; the checks are genre-dependent and mis-assigning it is how this lens produces noise. Every finding must carry a COUNT you measured (inventories, tables, list items, words per heading, sentence lengths) and must name the thing and the fix — which inventory becomes which table with which columns, which two sentences the answer moves into. 'Consider adding structure' is not a finding. Do not flag correctness, completeness, prose style, or word choice. A clean deliverable returns []. Cap at 5 findings. The single exception to the count rule is a checklist-unavailable finding, which reports that a declared project standard could not be read and needs no measurement."

All LLM reviewers dispatch in parallel (one message, multiple Task calls). Code mode: 5 lenses. Plan mode: 4 lenses. Claim mode: 4 lenses. Deliverable mode: 1 lens. Those counts are before disabled-roles; record each lens it removed, by name, on the PR verdict.

No enabled lens is not a clean review. If disabled-roles leaves the current mode with zero Task lenses (deliverable mode has one, so listing operator-empathy does it; code mode counts only its five LLM lenses, so the execution-grounded check alone doesn't save it), dispatch nothing and skip Steps 5 to 7. Write completion: 'stalled' to state, emit <promise>review-stalled</promise> with one line naming the mode and the lenses .claude/review-loop.disabled-roles removed, release locks, and post the PR verdict as stalled — no enabled lenses for <mode>. Never write review-clean for a run that reviewed nothing: an empty findings list from zero reviewers would otherwise read as a clean review, and that token is what lets a change land.

The execution-grounded reviewer (code mode only): not a Task dispatch. Invoke Bash directly to run the project's pnpm lint, pnpm test, pnpm build (or npm run lint/test/build if no pnpm-lock.yaml exists). Capture exit codes and stderr. Each non-zero exit → one finding with severity high, confidence 100, load_bearing: true, category execution-failure, claim describing the failure. Timeout: 180s.

Accumulate token usage from each Task's response into state.cost_spent_*_tokens.

Step 5 — Falsifier stage (NEW per industry research)

For each LLM-reviewer finding (NOT execution-grounded), dispatch a short-context falsifier Task:

"A reviewer flagged this issue: [finding.claim] at [finding.file]:[finding.line] (category: [finding.category], severity: [finding.severity]). The actual diff at that location is: [diff snippet ±10 lines around the cited line]. Try to disprove the claim. If the finding refers to nonexistent code, misreads the change, attacks a strawman, or relies on hallucinated context, return {falsified: true, evidence: '<short reason>'}. Otherwise return {falsified: false}."

Findings with falsified: true are dropped before aggregation. Token budget per falsifier: ~8000 tokens.

Claim mode: the falsifier receives the relevant primary-source excerpt (not a diff snippet) and tries to disprove the finding against it — i.e. confirm the finding is itself grounded in the source before it's used to overturn a conclusion. A finding that misreads or misquotes its source is dropped.

Deliverable mode: the falsifier receives the surrounding deliverable text (not a diff snippet) and re-counts what the finding claims to have measured. Drop the finding if the count is wrong (it says "zero tables" and there are four), if the cited passage doesn't exist, or if the check does not apply to the genre the deliverable actually is (a reference page flagged for list density). A miscounted finding is exactly the kind an author dismisses, so this stage is what keeps the lens credible. Never falsify a checklist-unavailable finding — it is a meta-signal about the run, not a claim about the artifact, and it cites a path that by definition could not be read, so the "cited passage doesn't exist" rule would silently delete the one signal that says the review was degraded.

Step 6 — Aggregate + drift-guard + dedup

After falsifier:

  1. Drift-guard. Compute cosine similarity between each finding's claim text and the diff text. Drop findings with similarity < 0.3 (off-topic / drift, per CodeAgent QA-Checker pattern). Use a simple TF-IDF approximation if no embedding model available. Deliverable mode: compare the claim against its cited passage plus that passage's section, NOT the concatenated document. The 0.3 cutoff was calibrated against a short focused diff, and cosine between a short claim and a long whole document is systematically lower (the document's larger vector norm shrinks each shared term's contribution), so reusing the number against full text would silently make this a stricter filter in the one mode that has a single lens and no cross-lens corroboration. Exempt checklist-unavailable entirely — its claim is about a standard that failed to load, shares almost no vocabulary with the deliverable, and would always drift-drop.
  2. Cross-iteration dedup. For iteration ≥ 2: drop findings whose claim is cosine-similar (≥0.85) to any finding already in state.findings_history[] that was addressed.
  3. Confidence filter. For iteration ≥ 2: drop LLM findings with confidence < 70 (execution-grounded always kept).
  4. Classify. actionable = severity ≥ medium AND load_bearing: true. speculative = the rest.

Step 7 — Decide

  • All clean (0 actionable findings): write completion: 'review-clean' to state; emit <promise>review-clean</promise> with a one-paragraph summary; release locks; exit.
  • More iterations available (iter + 1 < max_iterations AND cost-spent < ceiling AND ≥1 actionable):
    • Apply fixes for actionable findings using Edit / Write tools (one fix per actionable finding; if a fix is non-obvious, write a tracking-only comment in the affected file with a // REVIEW-LOOP-iter<N>: prefix).
    • Increment state.iteration; append the current iteration's full findings to state.findings_history.
    • Save state.
    • Release locks.
    • Exit with stdout JSON: {"decision": "block", "reason": "/review-loop --auto --session-id <id> --iteration <next> --max-iter <max> --mode <mode>", "systemMessage": "Review-loop iter <next>/<max>"} — preserve the --mode value across iterations; the mode does not flip mid-loop.
  • Exhausted (iter cap reached OR cost ceiling crossed):
    • Write completion: 'review-exhausted'.
    • Emit <promise>review-exhausted</promise> followed by an exit checklist listing unresolved actionable findings + the full speculative set.
    • Release locks; exit.

Claim mode: an actionable finding means a load-bearing claim is wrong or overstated. The "fix" is to correct the conclusion — if the analysis lives in a file, Edit it; if it lives only in the conversation, emit the corrected claim in your summary. Re-running reviewers on a purely deflationary correction (claiming less) is usually unnecessary — prefer a single pass unless a correction introduces a NEW load-bearing claim.

Deliverable mode: an actionable finding means the reader cannot use the deliverable as written. The fix is a restructure, never a rewrite — turn the inventory into the table, move the answer into the opening, paste the numbers in beside the link, add the gloss on first use. Do not change what the deliverable claims: its facts are another mode's business, and a usability pass that quietly edits substance is a worse defect than the one it fixed. If the deliverable lives only in the conversation, emit the restructured version in your summary rather than describing what should change.

Output format

Every run produces a final assistant text block ending with exactly one of:

  • <promise>review-clean</promise>
  • <promise>review-stalled</promise>
  • <promise>review-exhausted</promise>
  • (no promise marker if iteration continues — the hook re-invokes you)

The Stop hook reads stdout for the JSON {decision, reason, systemMessage} block; the user sees the assistant text.

Leave a trail on the PR (ALWAYS, when one exists)

On a terminal verdict (review-clean / review-exhausted / review-stalled) — i.e. the same point you'd archive state — record the verdict ON the open PR for this branch, so "was this reviewed?" is answerable from the PR itself, not from a chat transcript. This is not optional: a review without a trail is treated as incomplete. It is self-gating — most loop runs are on uncommitted WIP with no PR, and those post nothing.

  1. Detect the PR (read-only): gh pr view --json number,headRefOid,headRefName,state for the current branch. If it errors, returns no PR, or state != "OPEN" → skip silently (no PR = nothing to do).
  2. Compose a commit-pinned verdict from the template below. Pin to the PR's headRefOid. The verdict is valid only for the commit it ran against.
  3. Be honest about staleness: if the reviewed diff is uncommitted WIP, or local HEAD differs from the PR's headRefOid, say so in the comment — the verdict covers the reviewed state, not necessarily the PR head. Never let a review of one state read as covering another.
  4. Idempotent — never spam: if the PR's most recent comment already carries the <!-- review-loop:<sha> --> marker, update it with gh pr comment <n> --edit-last --body-file <tmp>; otherwise add one with gh pr comment <n> --body-file <tmp> (temp file — the body has markdown/newlines).
  5. Posting to GitHub is the loop's only outbound action. It writes a comment to the operator's own PR (reversible — editable/deletable) and is the standing instruction, so post it as part of completing the review; do not block the terminal exit waiting for a separate confirm.
## 🔬 review-loop verdict — pinned to `<headRefOid short>`

**Reviewed:** `<scope/range>` on `<headRefName>` · **iterations:** <n> · **method:** automated multi-agent
review-loop — <K> lens subagents (<names>) in independent contexts + an execution-grounded lint/test/build
check. Findings falsified against the diff; not a human review.
**Skipped by `.claude/review-loop.disabled-roles`:** <lens names> (omit this line when none were skipped)

**Verdict:** <review-clean ✅ | exhausted — N unresolved | stalled> <one line>
**Independence:** <independent — the reviewing session did not author this diff | ⚠️ SELF-REVIEW — NOT INDEPENDENT | unverified — could not be determined, see below>

### Fixed in-loop
- **[load-bearing]** `path:line` — <issue> → <fix>

### Surfaced, not fixed
- `path:line` — <issue> (<why deferred>)

<!-- review-loop:<headRefOid> --> _Re-run after new commits to refresh._

When independence is anything but independent, the verdict line MUST NOT contain the token review-clean — the auto-merge gate reads that token as "an independent review cleared this," and a self-review has not. Write self-review — no blocking findings instead, and open the comment with:

> ⚠️ **SELF-REVIEW — NOT INDEPENDENT.** This review ran in the session that wrote the code, so it
> carries the same blind spots that produced any defect in it. Discount it accordingly.
> Reason given: <verbatim --self-review-ack reason, or "hook-dispatched: the Stop event fires in the
> authoring session">. Files this session edited in this diff: <n> (<list>).
> A fresh session must still review this before it lands.

This is the whole enforcement: a self-review can still find things and still leaves a trail, but it cannot produce the token that lands the change. Withholding it needs no change to the merge gate.

Surface the shipping gap — no-PR nudge (ALWAYS, on a terminal verdict)

"Leave a trail on the PR" only fires when a PR already exists. The complementary risk — the one that bites silently — is a session that did real, reviewable work and then stopped without shipping it: the change is uncommitted, or committed but never PR'd, or parked on an unrelated branch. This is especially common for spawned background-task ("chip") sessions, which finish a self-contained unit of work in a worktree and have no memory telling them to open a PR. On a terminal verdict, after the PR-trail step, run this cheap read-only check and surface (never auto-act on) the gap.

  1. Self-gate. If an open PR for the branch already exists (the trail step posted to it) → skip. If there are no reviewable changes → skip. This no-op is the common case.
  2. Detect the shape (read-only) from git status --porcelain, git rev-parse --abbrev-ref HEAD, git log @{u}.. 2>/dev/null (unpushed commits), and the gh pr view result already gathered:
    • Uncommitted — the reviewed changes are still in the working tree (dirty git status).
    • Committed, no PR — HEAD carries the reviewed change but gh pr view found no open PR.
    • Unrelated branch — the current branch's name/topic looks unrelated to the reviewed files (a weak heuristic — surface it for the operator to judge, never assert it).
  3. Surface in the final summary (one short block, not a wall): name the shape and recommend the next shipping step — e.g. "recommend committing + opening a PR off the default branch", or "branch has commits but no PR — open one?" — and offer to do it, confirmation-gated. Creating a branch / commit / PR is an outbound, judgment-laden action: never auto-commit, auto-push, or auto-open a PR without an explicit per-instance confirm. Many sessions intentionally leave WIP — this is a nudge, not a gate, and it never blocks the terminal exit.
  4. Docs-staleness one-liner. If the reviewed diff changed behavior, a flag/CLI surface, an API, or a config field, add one line: "confirm the docs that describe this (README / skill / docs/) are updated — this loop reviewed the diff, not whether docs elsewhere went stale." Skip if the diff is doc-only or clearly doc-neutral.

Keep it to a few lines. A reviewed-clean change that never ships is not "done" — the operator should leave the session knowing exactly what remains to ship it.

Reconcile the session's handoff (code mode; only the handoff THIS session wrote)

session-end writes a handoff (<primary-checkout>/.claude/handoffs/<ts>_<slug>_<discriminator>.md) BEFORE this review runs, so its review-status can be stale the instant the verdict lands ("not reviewed yet"), or a load-bearing finding can change the risk of its stated next-action. On a terminal verdict, reconcile it — run this AFTER "Leave a trail on the PR" and BEFORE "State archival", on the still-on-disk state. Code mode only (skip in plan/claim/deliverable mode, as claim mode skips the PR trail). Self-gating: most runs wrote no handoff and this no-ops.

  1. Attribute by POSITIVE authorship — never by recency. Anchor to the primary checkout — git worktree list --porcelain | head -1 | sed 's/^worktree //', which is where session-end writes so the handoff survives worktree cleanup — and operate ONLY on <primary-checkout>/.claude/handoffs/. Do NOT anchor to git rev-parse --show-toplevel: in a linked worktree that is the worktree's own root, and this step would find zero handoffs and silently stop reconciling. Do not gate on git status either — the handoff lives in the primary checkout, so a git status in the reviewed worktree will not list it, and treating that as "no candidates" reproduces the same silent skip. List that directory directly. From filenames + first lines alone (no full body reads yet), find the handoff whose FIRST line is <!-- review-loop:session:<id> --> with <id> equal to THIS run's --session-id. If exactly zero match (incl. unattributed, or session-end stamped none) OR more than one matches → skip silently; this no-op is the expected outcome for most runs. NEVER fall back to "newest by mtime" — concurrent sibling sessions write into the same dir, and editing an unbound handoff corrupts another live session's continuation prompt.
  2. Idempotency. If the attributed handoff already carries <!-- review-loop-reconciled:<diff_sha> --> for the current reviewed state, skip (already done). Otherwise reconcile and write/replace that single marker in place — never append a second status block.
  3. Reconcile ONLY what the review changed — surgical, never a rewrite:
    • Correct a now-false review-status line — literal trigger only. Edit a line ONLY if it literally asserts the work is "unreviewed" / "not yet reviewed" / "untested" / "needs /review-loop" / "review pending" (case-insensitive; this enumerated set, no inference). Replace with per-verdict HONEST wording: clean → "review-loop: clean (<diff_sha>)"; exhausted → "review-loop: EXHAUSTED — N unresolved actionable findings (see PR trail)"; stalled → "review-loop: STALLED — no progress, not clean". NEVER write "clean"/"reviewed-OK" on a non-clean verdict. No such literal line → make NO status edit.
    • Elevate a load-bearing finding the handoff under-weights — only if it changes the next-action's risk. Add ONE line to the continuation prompt's next-action or verification-debts, phrased as an OPEN risk, never as resolved: "precondition: review-loop flagged <X> as unresolved (see PR trail) — confirm before <next-action>." Only for a finding already classified load-bearing; non-load-bearing findings never touch the handoff.
    • Done-close shape: if the handoff has no continuation-prompt / next-action / verification-debts section, do NOT inject one — at most fix a literal false status line, else skip. Never fabricate a section the author did not write.
    • Do not paraphrase, re-voice, or add review jargon to any other line. Cap: ≤2 status edits + ≤1 precondition. Quote each original line you changed in the final summary.
  4. Atomic write: write the full reconciled content to a temp file in the same dir and rename over the original (never a partial in-place stream), so a crash cannot truncate the irreplaceable handoff.
  5. A handoff-only edit is classified nothing-reviewable by the Stop hook's Gate A, so it will not re-arm the loop. (Edge: avoid handoff slugs ending in a plan-glob suffix like -retrospective / -plan / -spec, which would route to plan mode.)

State archival

On terminal exit (clean / stalled / exhausted), move state/<session-id>.json to state/archive/<session-id>-<unix-ts>.json and the .lock file is removed. The hook checks for terminal completion field and exits 0 if present (per its escape-hatch list).

Cost discipline

If at any point cost_spent_input + cost_spent_output > cost_ceiling_input_tokens:

  • Skip remaining reviewer dispatches in this iteration.
  • Treat current accumulated findings as the iteration's output.
  • Move to Step 6 / Step 7 (decide).
  • The review-exhausted branch will be taken in Step 7.

When invoked manually (no --auto flag)

Print a one-line preamble: "Running /review-loop on <branch>, iter <N>/<max>, scope=<scope>." before Step 4. Otherwise identical, except that you size the run yourself first, per "How much review to run".

Notes

  • Per memory entry feedback-confidence-per-step: each reviewer's prompt instructs it to label load_bearing honestly — in-iter fixes are limited to load-bearing items; speculative items surface as the exit checklist.
  • Per memory entry feedback-claude-code-not-api: when surfacing cost to the user, label as "plan usage (API-equivalent)" not "$X spent."
  • The Stop hook handles the infinite-loop guard (env var CLAUDE_REVIEW_LOOP_ACTIVE=1 is set before each iteration's re-invocation); the skill itself does not need to check this — if you're running, you've been invited.
  • The Stop hook is a cheap always-on gate, not an always-on review: after the safety exits it also skips when nothing reviewable changed (only docs / handoffs / lockfiles / scratch / generated files) or when the exact diff was already dispatched for review, logging the decision to .local-state/hook.log either way. Per-project knobs under .claude/ in the session's working directory (normally the repo root; the hook reads these from the working directory, not the Git top level, see "When it runs, and the off-switches"): review-loop.disabled (opt out entirely), review-loop.plan-paths (globs that count as plan artifacts), review-loop.code-exts (extensions that count as reviewable code; the file replaces the built-in default set), review-loop.deliverable-paths (globs that count as reader-facing deliverables; also replaces its default set — but anything under .claude/ stays excluded unconditionally and cannot be re-included by an override, since that path is session machinery), review-loop.deliverable-standard (a one-line pointer holding the path of the project's own deliverable/report standard, e.g. docs/REPORT-READABILITY-STANDARD.md — read by the lens, not by the hook, as additional checks; if it or its target can't be read the lens must say so rather than silently return []).

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.