review-loop — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited review-loop (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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.)
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--mode <code|plan|claim> — default code. Controls which reviewer lens setfires 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 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. Manual invocation may override the mode.
Treat missing args defensively: any arg can be omitted, all have defaults.
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.
Two locks needed:
.local-state/<session-id>.lock — use fs.openSync(path, 'wx') (O_EXCL)..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:
process.kill(pid, 0).skip: concurrent-loop-in-repo to .local-state/<session-id>.log and exit cleanly.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 branchgit diff HEAD | sha256sum (or compute in Node).If state.last_diff_sha === current_diff_sha AND state.iteration > 0:
completion: 'stalled' to state; emit <promise>review-stalled</promise> + unresolved checklist; exit.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-roles listing statistical-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 toDISPROVE 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 matchthe 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:
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.
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.
After falsifier:
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.claim is cosine-similar (≥0.85) to any finding already in state.findings_history[] that was addressed.confidence < 70 (execution-grounded always kept).actionable = severity ≥ medium AND load_bearing: true. speculative = the rest.completion: 'review-clean' to state; emit <promise>review-clean</promise> with a one-paragraph summary; release locks; exit.iter + 1 < max_iterations AND cost-spent < ceiling AND ≥1 actionable):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).state.iteration; append the current iteration's full findings to state.findings_history.{"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.completion: 'review-exhausted'.<promise>review-exhausted</promise> followed by an exit checklist listing unresolved actionable findings + the full speculative set.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.
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>The Stop hook reads stdout for the JSON {decision, reason, systemMessage} block; the user sees the assistant text.
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.
gh pr view --json number,headRefOid,headRefName,state for the currentbranch. If it errors, returns no PR, or state != "OPEN" → skip silently (no PR = nothing to do).
headRefOid. The verdictis valid only for the commit it ran against.
HEAD differs from thePR'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.
<!-- 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).
(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._"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.
are no reviewable changes → skip. This no-op is the common case.
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:
git status).HEAD carries the reviewed change but gh pr view found no open PR.heuristic — surface it for the operator to judge, never assert it).
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.
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.
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.
git rev-parse --show-toplevel of thereviewed tree and operate ONLY on <toplevel>/.claude/handoffs/. From filenames + git status alone (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.
<!-- review-loop-reconciled:<diff_sha> --> forthe current reviewed state, skip (already done). Otherwise reconcile and write/replace that single marker in place — never append a second status block.
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.
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.
do NOT inject one — at most fix a literal false status line, else skip. Never fabricate a section the author did not write.
precondition. Quote each original line you changed in the final summary.
original (never a partial in-place stream), so a crash cannot truncate the irreplaceable handoff.
loop. (Edge: avoid handoff slugs ending in a plan-glob suffix like -retrospective / -plan / -spec, which would route to plan mode.)
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).
If at any point cost_spent_input + cost_spent_output > cost_ceiling_input_tokens:
review-exhausted branch will be taken in Step 7.--auto flag)Print a one-line preamble: "Running /review-loop on <branch>, iter <N>/<max>, scope=<scope>." before Step 4. Otherwise identical.
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.feedback-claude-code-not-api: when surfacing cost to the user, label as "plan usage (API-equivalent)" not "$X spent."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..local-state/hook.log either 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).~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.