Review loop
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.From its SKILL.md
npx -y skills add BryceEWatson/claude-global-skills --skill review-loopAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
23.8 KB, ~5.8k tokens by cl100k_base, as published. Nobody here has run it
/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.)
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 (perTHRESHOLDS.reviewLoop.maxIterations)--cost-ceiling-tokens <n>— combined input+output token budget; default 300000--scope <git-diff|head-1>— defaultgit-diff(uncommitted changes)--auto— set when hook-invoked; suppresses preamble, writes structured exit--mode <code|plan|claim>— defaultcode. Controls which reviewer lens set fires in Step 4. The Stop hook auto-selects: 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. Otherwise →--mode code.--mode claimreviews 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. Manual invocation may override the mode.
Treat missing args defensively: any arg can be omitted, all have defaults.
Workflow
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— usefs.openSync(path, 'wx')(O_EXCL). - Per-repo lock
.local-state/repo-<sha1(toplevel)>.lock— same pattern. Hash ofgit 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-repoto.local-state/<session-id>.logand 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.
Run via Bash:
git status --porcelain— list of changed filesgit diff HEAD— full diff textgit 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.mdagents/design-coherence.mdagents/adversarial.mdagents/ship-readiness.mdagents/statistical-rigor.md(skip if project has.claude/review-loop.disabled-roleslistingstatistical-rigor)agents/execution-grounded.md(special — see below)
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.
Dispatch (all modes)
For each enabled agent 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)
- 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."
All LLM reviewers dispatch in parallel (one message, multiple Task calls). Code mode: 5 lenses. Plan mode: 4 lenses. Claim mode: 4 lenses.
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.
Step 6 — Aggregate + drift-guard + dedup
After falsifier:
- Drift-guard. Compute cosine similarity between each finding's
claimtext 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. - Cross-iteration dedup. For iteration ≥ 2: drop findings whose
claimis cosine-similar (≥0.85) to any finding already instate.findings_history[]that was addressed. - Confidence filter. For iteration ≥ 2: drop LLM findings with
confidence < 70(execution-grounded always kept). - Classify.
actionable= severity ≥ medium ANDload_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_iterationsAND cost-spent < ceiling AND ≥1 actionable):- Apply fixes for
actionablefindings usingEdit/Writetools (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 tostate.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--modevalue across iterations; the mode does not flip mid-loop.
- Apply fixes for
- Exhausted (iter cap reached OR cost ceiling crossed):
- Write
completion: 'review-exhausted'. - Emit
<promise>review-exhausted</promise>followed by an exit checklist listing unresolvedactionablefindings + the fullspeculativeset. - Release locks; exit.
- Write
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.
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.
- Detect the PR (read-only):
gh pr view --json number,headRefOid,headRefName,statefor the current branch. If it errors, returns no PR, orstate != "OPEN"→ skip silently (no PR = nothing to do). - 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. - Be honest about staleness: if the reviewed diff is uncommitted WIP, or local
HEADdiffers from the PR'sheadRefOid, 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. - Idempotent — never spam: if the PR's most recent comment already carries the
<!-- review-loop:<sha> -->marker, update it withgh pr comment <n> --edit-last --body-file <tmp>; otherwise add one withgh pr comment <n> --body-file <tmp>(temp file — the body has markdown/newlines). - 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.
**Verdict:** <review-clean ✅ | exhausted — N unresolved | stalled> <one line>
### 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._
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.
- 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.
- Detect the shape (read-only) from
git status --porcelain,git rev-parse --abbrev-ref HEAD,git log @{u}.. 2>/dev/null(unpushed commits), and thegh pr viewresult already gathered:- Uncommitted — the reviewed changes are still in the working tree (dirty
git status). - Committed, no PR —
HEADcarries the reviewed change butgh pr viewfound 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).
- Uncommitted — the reviewed changes are still in the working tree (dirty
- 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.
- 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 (<toplevel>/.claude/handoffs/<ts>_<slug>.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 mode, as claim mode skips the PR trail). Self-gating: most runs wrote no handoff and this no-ops.
- Attribute by POSITIVE authorship — never by recency. Anchor to
git rev-parse --show-toplevelof the reviewed tree and operate ONLY on<toplevel>/.claude/handoffs/. From filenames +git statusalone (no 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. - 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. - 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.
- 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 (
- 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.
- 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-exhaustedbranch 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.
Notes
- Per memory entry
feedback-confidence-per-step: each reviewer's prompt instructs it to labelload_bearinghonestly — 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=1is 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.logeither way. Per-project knobs under<repo>/.claude/: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).
What ships with it: 20 files
99.0 KB alongside SKILL.md, 6 of them executable
agents/
- adversarial.md4.0 KB
- architecture.md4.2 KB
- claim-calibration.md3.2 KB
- claim-coverage.md3.3 KB
- claim-falsification.md3.4 KB
- claim-method.md3.3 KB
- completeness.md3.7 KB
- design-coherence.md2.4 KB
- execution-grounded.md3.0 KB
- operator-empathy.md4.1 KB
- ship-readiness.md2.4 KB
- simplicity.md2.5 KB
- statistical-rigor.md2.7 KB
- vision-coherence.md3.6 KB
- install.cjsruns6.9 KB
- install.test.cjsruns4.3 KB
- stop-hook.cjsruns19.6 KB
- stop-hook.test.cjsruns13.8 KB
- uninstall.cjsruns5.6 KB
- uninstall.test.cjsruns3.3 KB