agentleFS
greglas75greglas75/zuvo/skills/review/SKILL.md

review

Skill7 starsChanged 27 days agoMITView on GitHub

Structured code review with parallel audit agents, confidence-scored triage, and optional auto-fix. Examines uncommitted changes, staged diffs, commit ranges, or specific paths. Produces a tiered report (MUST-FIX / RECOMMENDED / NIT) backed by evidence, then optionally applies fixes with verification.

192 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use close-spec, design-with-images 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.
  • dzhngSkill

    close-spec

    When a spec under specs/<feature>/ is done shipping (a write-spec build, or any planned task that produced a spec), archive it to specs/done/ and rewrite it from a build-plan into a durable rationale — the why, the principles, the invariants — pointing back to the real code for the how. Use when implementation has landed and the plan no longer matches what shipped, or the user says a feature/spec is finished. Pairs with [write-spec](../write-spec/SKILL.md) (the plan this closes) and [review](../review/SKILL.md) (run before closing).

    Also in 5 projects that use this

  • dzhngSkill

    design-with-images

    Explore UI and visual design with image generation, then iterate the real implementation against the user-selected concept. Use when brainstorming visual directions, offering mockup variants, or implementing an approved generated design; do not stop at the mockups or the first code approximation.

    Also in 5 projects that use this

  • LambenthanSkill

    daily-arxiv

    每日从 arXiv 拉取新论文,过滤相关性,auto-ingest 高优先级论文,检测 SOTA 更新

    Also in 5 projects that use this

  • LambenthanSkill

    exp-eval

    实验判决门:Review LLM 独立评判实验结果 → 4 种判决路径 → 自动更新 claims confidence、ideas status、graph edges

    Also in 5 projects that use this

  • +3 more from dzhng
  • +5 more from Lambenthan

The file

Show its rules and text

We split this skill into the rules it gives an agent

40 rules. 0 are common, 14 are distinctive, and 26 are very specific to this project.

  • 0

    Common

    Most of the 157 edited versions we read keep these.

  • 14

    Distinctive

    This author's own choices. Worth a look.

  • 26

    Very specific

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

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    After CodeSift setup, read the git diff.

    line 134
  2. Very specific:

    After deriving REVIEWED_FROM and REVIEWED_THROUGH for any commit-based scope (new, HEAD~N, abc123..def456, batch entry), validate the range …

    line 206
  3. Very specific:

    If this returns no commits, STOP and print: [RANGE-ERROR] Empty commit range.

    line 212
  4. Distinctive:

    Do NOT auto-swap the range.

    line 215
  5. Very specific:

    Before tier selection, compute PROD_LOGIC_LINES from changed non-test production hunks after stripping diff headers, blank lines, and …

    line 257
  6. Distinctive:

    Check the diff for these markers.

    line 298
  7. Distinctive:

    Every review MUST compute a deployment risk score.

    line 310
  8. Distinctive:

    Use the deterministic preload helper FIRST: Before issuing any ToolSearch, run:

    line 354
  9. Distinctive:

    After the review report is written, the review is NOT complete until:

    line 405
  10. Very specific:

    If you reach REVIEW COMPLETE and stop without calling append-runlog: the review is INVALID regardless of finding count.

    line 413
  11. Distinctive:

    Check whether CodeSift tools are available (the helper above already verified this)

    line 424
  12. Very specific:

    If unsure whether the repo is indexed: index_status()

    line 426
  13. Very specific:

    If not indexed: index_folder(path=<project_root>)

    line 427
  14. Distinctive:

    When the REVIEWED scope path resolves to a repo or worktree that is NOT the CWD, do NOT degrade CodeSift — re-point it at the target …

    line 431
  15. Very specific:

    Run it at the end of Phase 3 and on every abort path.

    line 497
  16. Very specific:

    If the diff adds/removes exports and knip is available: npx knip --reporter json 2>/dev/null.

    line 539
  17. Very specific:

    If any pre-compute call fails, set PRECOMPUTED_DATA=partial, log the failed operation in SKIPPED STEPS, and continue.

    line 549
  18. Very specific:

    If the repo uses both *.spec.* and *.test.*, run the test-reference step for both globs and merge the results.

    line 566
  19. Very specific:

    Do NOT use patterns, complexity, or file_outlines inside codebase_retrieval

    line 571
  20. Distinctive:

    TIER 0-1: No agents. Lead performs all analysis inline using CodeSift pre-computed data (Phase 0.5) if available.

    line 616
  21. Distinctive:

    Where the 40 gates go (TIER 3, many files): Every gate must be EVALUATED for every file — that is not negotiable and no summary form …

    line 654
  22. Very specific:

    When reviewing a commit range rather than the current working tree, verify that HEAD has not already changed a file after REVIEWED_THROUGH …

    line 679
  23. Distinctive:

    If the file changed after the reviewed range:

    line 685
  24. Distinctive:

    read the current file before reporting

    line 687
  25. Very specific:

    If adversarial-review not in PATH: ~/.zuvo/adversarial-review (stable.

    line 706
  26. Very specific:

    If it is NOT an ancestor: either merge/rebase first, or review $(git merge-base "$REVIEWED_FROM" "$REVIEWED_THROUGH")..$REVIEWED_THROUGH …

    line 715
  27. Very specific:

    Self-review escalation: If SELF-REVIEW marker set in 1.1, pass --multi flag.

    line 719
  28. Very specific:

    Status handling (D2+D3+D4, 2026-05-17): When the script exits non-zero or returns non-ok JSON status, branch:

    line 721
  29. Very specific:

    Record the verdicts: after triage: every finding is fixed, rejected (a false positive) or deferred — record each by its ID: in ONE call, …

    line 831
  30. Very specific:

    When deterministic facts are already known (for example: lockfile present in diff, package ships bundled types, file already patched at …

    line 852
  31. Very specific:

    If PROD_LOGIC_LINES = 0 and SELF-REVIEW is not set, skip adversarial and log: [CROSS-REVIEW] Skipped — no production logic changed.

    line 853
  32. Very specific:

    Write it only on success — a crashed/aborted review must leave no artifact, so a failed run never grants pipeline coverage.

    line 999
  33. Very specific:

    Run knowledge-curate.md (if loaded): WORK_TYPE="review", CALLER="zuvo:review", REFERENCE=<commit range or "staged">.

    line 1026
  34. Very specific:

    Follow ../../shared/includes/drive-by-findings.md with <skill> = review: backlog the security, bug, size, duplication, complexity, …

    line 1030
  35. Very specific:

    Follow ../../shared/includes/followup-ideas.md with <skill> = review: append genuine new ideas to memory/ideas.md at the MAIN checkout root …

    line 1038
  36. Very specific:

    Follow the retrospective protocol from retrospective.md.

    line 1046
  37. Distinctive:

    If the gate check skips, you MUST print one of:

    line 1049
  38. Very specific:

    Before printing REVIEW COMPLETE or the NEXT STEPS block, verify every item below.

    line 1057
  39. Very specific:

    If gate_status = FAIL, override the VERDICT to INCOMPLETE regardless of finding count, append [VALIDITY GATE FAIL] to the Run line NOTES …

    line 1200
  40. Distinctive:

    Do not wait indefinitely for it: Ask the outstanding agent for whatever it has, then finalize with the honest value.

    line 1235

“Common” is measured: kept by at least 6 in 10 of the 157 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

zuvo:review

Triage the diff, audit it through independent lenses, confidence-score every finding, run cross-model adversarial validation, and deliver a verdict. No separate "go" step required -- the review runs end to end.

Mandatory File Loading

PHASE 0 — Bootstrap (always, before reading any input)

  1. ../../shared/includes/codesift-setup.md      -- [READ | MISSING -> STOP]

This is the ONLY file loaded before reading the diff.

PHASE 0.5 — Classify (read diff, determine content type)

After CodeSift setup, read the git diff. Classify content type:

  • prod-only: diff touches production files only (no *.test.*, *.spec.*)
  • test-only: diff touches test files only
  • mixed: diff touches both production and test files

Print: [CLASSIFIED] Diff type: {prod-only|test-only|mixed}

PHASE 1 — Conditional Load (based on diff type)

Includeprod-onlytest-onlymixed
../../shared/includes/env-compat.mdFullFullFull
../../shared/includes/quality-gates.mdCQ1-CQ40 section only*Q1-Q25 section only**Full
../../shared/includes/cross-provider-review.mdFullFullFull
../../rules/cq-patterns.md or cq-patterns-core.mdPer code type***SKIPPer code type***
../../rules/cq-checklist.mdTIER 1+SKIPTIER 1+
../../rules/testing.mdSKIPFullFull
../../rules/security.mdIf security signalsSKIPIf security signals

* CQ section only: Read from start of file to the ## Q1-Q25 heading. Skip Q section. ** Q section only: Read from ## Q1-Q25: Test Quality Gates heading to end of file. Skip CQ section. *** cq-patterns loading rule: After Step 1 (classify code type), check the "High-Risk Gates by Code Type" table in cq-checklist.md. If the code type has <=10 relevant gates, load cq-patterns-core.md (~500 tok) instead of cq-patterns.md (~8.4K tok).

Print loaded files:

PHASE 1 — LOADED:
  [list with READ/SKIP status per file and section qualifiers]

Optional Files (loaded if available, degraded if missing)

  ../../shared/includes/knowledge-prime.md   -- [READ | MISSING -> degraded]
  ../../shared/includes/knowledge-curate.md  -- [READ | MISSING -> degraded]

DEFERRED — Load at completion

  ../../shared/includes/run-logger.md        -- [READ at final step]
  ../../shared/includes/retrospective.md     -- [READ at final step]
  ../../shared/includes/comment-pass.md      -- [READ at post-fix gate step 1b — FIX modes only; REPORT -> SKIP]

Argument Parsing

$ARGUMENTS controls both WHAT gets reviewed and WHAT to do with the findings.

Scope (what code to examine)

InputMeaningGit command
(empty)All uncommitted changesgit diff --stat HEAD
stagedOnly staged changesgit diff --stat --cached
newCommits since last reviewreviewed/* tag / merge-base resolution
HEAD~NLast N commitsgit diff --stat HEAD~N..HEAD
abc123..def456Specific commit rangegit diff --stat abc123..def456
commits A,B,CSpecific non-consecutive commit hashesUnion-diff via git show per hash, concatenated. Range-derived steps use the SPAN: REVIEWED_FROM=<oldest-hash>^, REVIEWED_THROUGH=<newest-hash>, and the artifact's files: lists ONLY the files from the named commits (never *) — the span may contain commits you did not review
src/services/Directory (uncommitted)git diff --stat HEAD -- src/services/

Tokens combine: HEAD~3 src/api/ reviews the last 3 commits scoped to src/api/.

new resolution order:

  1. The nearest reviewed/* tag reachable from HEAD, as a commit: git describe --tags --abbrev=0 --match 'reviewed/*' HEAD names the tag, and git rev-parse "<tag>^{commit}" is the start point. A non-zero exit of either command (no such tag, or a tag that does not resolve to a commit) falls through to steps 2 and 3, run in one shell call so step 3 sees DEFAULT_BRANCH. A start point equal to HEAD prints nothing new since <tag> and stops. (memory/backlog.md holds findings, not commits, so it is never read here.)
  2. Detect default branch: DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||'); DEFAULT_BRANCH=${DEFAULT_BRANCH:-main}
  3. Fallback: git merge-base HEAD "$DEFAULT_BRANCH" (step 1 found no tag; DEFAULT_BRANCH from step 2, in the same shell call)
  4. Final fallback: HEAD~5 with a warning

Range Validation

After deriving REVIEWED_FROM and REVIEWED_THROUGH for any commit-based scope (new, HEAD~N, abc123..def456, batch entry), validate the range before tier selection, CodeSift pre-compute, or adversarial review:

git log --oneline "${REVIEWED_FROM}..${REVIEWED_THROUGH}" | head -5

If this returns no commits, STOP and print: [RANGE-ERROR] Empty commit range. Verify base/tip order before running review.

Do NOT auto-swap the range.

Then print the validated diff stat:

git diff --shortstat "${REVIEWED_FROM}..${REVIEWED_THROUGH}"

Mode (what to do after the audit)

TokenModeBehavior
(none)FIX-AUTO (default)Audit, then automatically apply MUST-FIX + localized/high-confidence RECOMMENDED, verify, and run the post-fix adversarial gate — NO menu wait. NIT + structural-refactor RECOMMENDED → recorded through backlog-add (not force-applied).
--report-onlyREPORTAudit and present findings only. Do NOT touch code; print the menu and stop. Use when you want to read before acting.
fixFIX-ALLApply EVERY reported fix incl. NIT, then verify + gate.
blockingFIX-BLOCKINGApply only MUST-FIX findings, then verify + gate.
auto-fixAUTO-FIXDispatch zuvo:build to fix MUST-FIX issues (closed-loop).
tag <queue-file>UTILITYNo audit. Tick each batch queue entry whose commit SHA is an ancestor of HEAD (git merge-base --is-ancestor <sha> HEAD); with no argument, print usage and stop.
mark-reviewedUTILITYNo audit. Create reviewed/ git tags on commits.
statusUTILITYNo audit. Show unreviewed commit count and list.
batch <file>BATCHProcess a queue of commits: review, fix, tag per entry.
--thoroughFLAGActivate multi-pass review with majority voting.
--depth NFLAGFor status mode: how many commits to check (default 100).

Tier System

A quick git diff --stat determines how deep the review goes. Filter out noise files before counting (locks, dist, snapshots, generated code, binary assets).

Edge Cases (check before tier selection)

ConditionAction
0 files changed (empty diff)Print "No changes to review." -> STOP
All files are binaryPrint "Only binary files changed. Nothing to review." -> STOP
Binary files mixed with codeTier based on code lines only. Note binaries in report.
All changed files are noisePrint "Only noise files changed (locks, snapshots, dist). Nothing to review." -> STOP
Merge commit detectedInteractive: warn + offer --first-parent. Non-interactive: auto-apply --first-parent with [AUTO-DECISION].

Production Logic Line Count

Before tier selection, compute PROD_LOGIC_LINES from changed non-test production hunks after stripping diff headers, blank lines, and comment-only additions/deletions (//, #, /*, *, */).

If PROD_LOGIC_LINES = 0:

  • Force TIER 1 -- LIGHT
  • Skip TIER 2+ escalation driven only by risk signals on comment-only diffs
  • Skip heavy TIER 2-3 pre-compute and behavior-agent escalation
  • Print [AUTO-DECISION] No production logic lines changed -> TIER 1 override

Tier Selection

ConditionTier
PROD_LOGIC_LINES = 0TIER 1 -- LIGHT
<15 lines, no risk signalsTIER 0 -- NANO
15-100 lines, no risk signalsTIER 1 -- LIGHT
100-500 lines OR 5-15 files OR 1 risk signalTIER 2 -- STANDARD
>500 lines OR 15+ files OR 2+ risk signalsTIER 3 -- DEEP

Intent adjustments:

  • REFACTOR + <10 files + no DB/security/API/money signal: cap at TIER 2.
  • INFRA-only (config, CI, Dockerfile -- no production code): cap at TIER 1 unless >300 lines.

Tier Capabilities

CapabilityTIER 0TIER 1TIER 2TIER 3
Inline diff scanYesYesYesYes
CQ patterns loadedSkipCore (500 tok)Full (8.4K tok)Full (8.4K tok)
CQ1-CQ40 evaluationSkipYes (lead inline)Yes (CQ Auditor agent)Yes (CQ Auditor agent)
Q1-Q25 on test filesSkipIf present (lead)YesYes
Audit agentsNoneNoneBehavior + CQ (if new files)All 3 (Behavior + Structure + CQ)
Adversarial (bash script)Yes (all available)Yes (all available)Yes (all available)Yes (all available)
CodeSift pre-computeOptionalYes (light ops)Yes (core ops)Yes (core ops)
Confidence scoringLead inlineLead inlineRe-Scorer agentRe-Scorer agent
Hotspot detectionSkipSkipYesYes
Multi-pass (--thorough)RefusedOptionalOptionalAuto if >500L
Stack-specific rulesSkipSkipYesYes
Report persistenceSkipYesYesYes

Risk Signals

Check the diff for these markers. Each one counts toward tier escalation:

  • DB migration or schema changes
  • Security or authentication modifications
  • API contract changes (routes, request/response shapes)
  • Payment or money flow logic
  • More than 500 lines changed
  • New production files added (not test files)
  • AI-generated code patterns (hallucinated imports, generic names, overly verbose)

Deployment Risk Scoring

Every review MUST compute a deployment risk score.

FactorPointsHow to detect
Auth/authz changes+3Diff touches guards, middleware, JWT, session, role checks
Payment/money logic+3Diff touches payment, pricing, billing, subscription
DB migration or schema+2Migration files, schema changes, ALTER/CREATE TABLE
API contract changes+2New/modified routes, request/response shape changes
File in churn hotspot (top 10)+2Phase 0 hotspot detection. Score 0 at TIER 0-1.
>500 lines changed+1From diff stat
New production files added+1New .ts/.tsx/.py files (not tests)
Multi-service blast radius+1Changes affect 3+ modules/services
Reverts or rollback-sensitive+1State machine, data migration, irreversible ops
PointsLevelDeploy strategy
0-1LOWDirect merge -- standard CI
2-4MEDIUMMerge after review -- run full test suite
5-7HIGHCanary recommended -- deploy to subset first
8+CRITICALStaged rollout -- extra reviewer, canary mandatory

FIX-ALL Blockers

For high-risk changes (DB migrations, security/auth, API contracts, payment/money), apply fixes one at a time and run tests after each fix. If a fix breaks tests, revert it and report as [!].


Phase 0: Pre-Audit Setup

Backlog snapshot

Once, at the run's original start, before any backlog-add call: R='<absolute repo root>'; git -C "$R" status --porcelain -uall -- memory/, R being the root of the checkout under review (a linked worktree's own root), assigned in the same shell call. Review keeps its run state in the transcript, as it does COMMENT_BASE=<sha>: print the output under a backlog-snapshot: line (backlog-snapshot: (empty) when it printed nothing). A resumed run reuses the printed one and never takes it again (backlog-protocol.md "Filing from a skill", Committing what backlog-add wrote).

Knowledge Prime

Check if knowledge base exists BEFORE loading the protocol — worktree-aware: resolve MAIN_ROOT=$(git worktree list --porcelain 2>/dev/null | head -1 | sed 's/^worktree //') (fallback --show-toplevel) and check Glob("$MAIN_ROOT/knowledge/*.jsonl") (knowledge lives at the MAIN checkout per backlog-protocol.md; a CWD-relative glob in a linked worktree finds nothing and silently skips priming — same bug class as the old memory/knowledge*.md pre-check that pointed at a path NO skill writes). If no files found, skip — do NOT load knowledge-prime.md (saves ~140L / ~1.6K tokens). If files exist, then load and run:

WORK_TYPE = "review"
WORK_KEYWORDS = <keywords from diff file paths and commit messages>
WORK_FILES = <changed files from the diff>

CodeSift Setup

Use the deterministic preload helper FIRST. Before issuing any ToolSearch, run:

~/.zuvo/compute-preload review "$PWD"

Copy the printed [CodeSift matching trace] block verbatim and issue the printed ToolSearch(query="select:...") line without modification. Math gate: [CodeSift loaded] tools=N must equal [Expected after load] tools=N from the helper. If they differ → [PRELOAD MATH MISMATCH] and abort before Phase 1.

MANDATORY TOOL CALLS — Review Validity Gate

This review is INVALID if any tool below is skipped when its trigger condition holds. "DEFERRED", "N/A", "TIER 0 minimal scope" are NOT valid reasons unless explicitly documented as such.

ToolTriggerReasonSkip allowed?
review_diffAlways (any review with a diff)KEY COMPOUND — 9 parallel checks (security, dead code, complexity, etc.) on the diffNO
changed_symbolsAlways (any commit-range or staged review)Which symbols added/modified/deleted in range — required for CQ scoringNO
diff_outlineAlwaysStructural diff per file (signatures only — no body churn noise)NO
impact_analysisAlwaysBlast radius + affected_tests for the changed surfaceNO
find_referencesAny finding cites a function/methodRegression risk verificationNO when condition holds
scan_secretsAlways (any review touching code or config)CAP5 hardcoded-secret pre-scan on the diffNO
search_patternsAlwaysCQ8 empty-catch + CAP anti-patterns introduced in the diffNO
codesift dup-check --base <REVIEWED_FROM> (Bash)Diff adds or changes production code AND the codesift CLI has dup-check (codesift dup-check --help exits 0)Each new/changed function compared with the whole index: copies of existing code. Exit 1 = findings → MUST-FIX (reuse/extend the existing symbol) unless the copy carries codesift-dup-ok: <reason> and the reason holds. Record dup-check: <N> duplicates (or dup-check: absent-in-build when the CLI lacks it)NO when condition holds
Stack-specific tools (nest_audit/framework_audit/python_audit/etc.)Framework/language detected AND diff touches framework codeFramework-aware gates the diff inheritsNO when conditions hold

Absent-in-build substitution (per-tool, NOT whole-server). If a required tool above is genuinely absent from THIS build's tool surface (verified absent, not merely deferred — see codesift-setup.md absent-in-build detection), run the documented equivalent and record <tool>: absent-in-build (<equivalent>: <result>), which SATISFIES the gate. This is distinct from whole-server absence (codesift-setup.md handles that). Substitution map:

Absent toolEquivalentRecorded as
review_diffaudit_scan (compound 5-gate)review_diff: absent-in-build (audit_scan: <findings>)
changed_symbols + diff_outlineimpact_analysis + get_file_outlinechanged_symbols: absent-in-build (impact_analysis+get_file_outline: <result>)
scan_secretsgrep secret-scan (high-entropy/key patterns on diff)scan_secrets: absent-in-build (grep secret-scan: <count>)

Fence the substitute's output to the reviewed file set. audit_scan (and the other compound substitutes) scan the repository, not your diff — a scoped review that scores their raw output drowns in repo-wide pre-existing findings that this change never touched, and the CQ score stops describing the diff. After each substitute call, discard findings whose file is outside the reviewed file set BEFORE CQ scoring. The reviewed file set depends on the scope: a commit range (new, HEAD~N, A..B) is git diff --name-only "${REVIEWED_FROM}..${REVIEWED_THROUGH}"; commits A,B,C is the union of git show --name-only --format= <hash> over the named commits; staged is git diff --name-only --cached; uncommitted (no scope token) is git diff --name-only HEAD plus git ls-files --others --exclude-standard; a directory token narrows any of them with -- <dir>. Record whether the tool honored the requested fence: audit_scan: fence-honored or audit_scan: fence-ignored (filtered <N>→<M>). Two exceptions to the fence, both about causality rather than file membership: a finding in an unchanged file that this diff caused — a caller broken by a changed signature, a consumer of a removed export, a now-unreachable branch — is IN scope and must be triaged normally, because the diff is what made it true (recorded with --source review, Backlog Persistence below). And findings outside the fence that the diff did not cause are not silently dropped knowledge: they are pre-existing debt, not scored against this diff. A pre-existing out-of-fence finding with a drive-by tag is recorded with --source drive-by:review; one with no tag is reported, not recorded (Backlog Persistence below; drive-by-findings.md).

Forbidden escape hatches

ValueForbidden whenRequired value instead
review_diff: skipped (TIER 0)EVER (TIER 0 still uses CodeSift pre-compute per Tier table)review_diff: <findings_per_check>
scan_secrets: not_runEVERscan_secrets: <count>
changed_symbols: N/A (test-only diff)EVER (test files have changed_symbols too)changed_symbols: <count>
codesift: unavailablemcp__codesift__* was in deferred-tools session-start bannercodesift: deferred-not-preloaded (FAILURE: skill required preload)
RETRO: skipped (nothing interesting)EVEROne of: RETRO: skipped (trivial session, <3 findings and no fix-loop) OR full retro appended
Adversarial: skipped (context budget) / (tight context)EVERChunk the diff (see section 1.6 CONTEXT BUDGET) and run adversarial per chunk, OR exit with BLOCKED_CONTEXT_BUDGET and ask the user to narrow scope. Skipping is never an option.
Adversarial: skipped (already mechanically detected) / (scanners covered it)EVERThis inverts the rationale. Adversarial's purpose is to find what mechanical scanners MISSED (CodeSift/audit_scan find patterns; adversarial finds semantics). Skipping because scanners ran is a category error. Run it.
Adversarial: skipped (self-review, low value) / (I wrote this code so adversarial adds little)EVERSelf-review REQUIRES MORE adversarial coverage, not less. Section 1.1 + 1.6 mandate --multi on SELF-REVIEW. Anchoring bias is exactly why adversarial exists here.
Adversarial: skipped (small diff) / (<N lines so not worth)EVER (Tier table line 284 mandates adversarial at TIER 0)Run it. Even <15 line diffs get the pass per the Tier table — a single-line semantic bug (e.g. inverted comparison, off-by-one, swapped args) is exactly the class adversarial catches that scanners cannot.
Adversarial: skipped (documented honestly) / (noting the skip transparently)EVERHonesty about a violation is still a violation. The Validity Gate evaluates whether the gate ran, not whether the skip was politely worded. Run it or exit BLOCKED.
Any Adversarial: skipped (<reason>) where <reason> is not on the whitelistEVERWhitelist (from section 1.6): single_provider_only (exit 3), timeout (exit 124), BLOCKED_CONTEXT_BUDGET (after chunking attempt failed). Nothing else.
ok / any4Review COMPLETED but the input was TRUNCATED — part of the change reached no provider (input_truncated=true in the artifact, which also lists the omitted files)Do NOT report the review complete. Findings returned are real; the ABSENCE of findings says nothing about the omitted files. Re-run over the omitted set or split the input, then merge verdicts

Required POSTAMBLE — retrospective + verify-audit gates

After the review report is written, the review is NOT complete until:

  1. memory/reviews/<date>-<scope>.md (TIER 1+) is on disk.
  2. ~/.zuvo/append-runlog is called with the Run line — this triggers BOTH:
    • retro-gate: requires a matching RETRO: entry in ~/.zuvo/retros.log for skill=review project=<this>. If missing → exit 2, runs.log NOT appended.
    • audit-content gate: runs ~/.zuvo/verify-audit on the report. Every MUST-FIX and RECOMMENDED finding must contain at least one path/to/file.ext:LINE citation that resolves in the current tree. NIT findings without citations get rejected. If rejected → fix the report, re-run append-runlog.
  3. Print RETRO_APPENDED: retros.log=YES retros.md=YES (verified) and confirm exit 0 from append-runlog.

If you reach REVIEW COMPLETE and stop without calling append-runlog: the review is INVALID regardless of finding count. The Validity Gate gate_status flips to FAIL — postamble incomplete and the verdict overrides to INCOMPLETE.

Mandatory acknowledgment (REQUIRED — print verbatim before Phase 0.5)

Mandatory-tools-acknowledgment: I will run review_diff + changed_symbols + diff_outline + impact_analysis + scan_secrets + search_patterns + find_references (on cited symbols) + stack-specific tools (nest_audit/framework_audit/python_audit/etc. when detected) for this review. Each MUST-FIX and RECOMMENDED finding will cite a `path/to/file.ext:LINE` resolving in the current tree.

Standard CodeSift checks (run AFTER the helper)

Follow codesift-setup.md:

  1. Check whether CodeSift tools are available (the helper above already verified this)
  2. Repo auto-resolves from CWD — do NOT call list_repos() unless the review explicitly spans multiple repositories
  3. If unsure whether the repo is indexed: index_status()
  4. If not indexed: index_folder(path=<project_root>)

Cross-checkout / worktree scope

When the REVIEWED scope path resolves to a repo or worktree that is NOT the CWD, do NOT degrade CodeSift — re-point it at the target instead:

  1. Resolve TARGET_REPO. git -C <scope-path> rev-parse --show-toplevel. If it differs from CWD's toplevel, set TARGET_REPO=<that path>.
  2. Pass repo=/path= explicitly to review_diff, changed_symbols, scan_secrets, find_references (and index_folder) so they target TARGET_REPO, not CWD.
  3. Fresh worktree staleness — index the worktree ONCE, then proceed. Run index_status(path=TARGET_REPO). If the branch's commits are not indexed, the semantic tools would silently answer from the stale main checkout, so they are not usable for this scope yet. The resolution is index_folder(path=TARGET_REPO) — auto-indexing refuses linked worktrees and defers to the parent, so a worktree typically has no index of its own and this is its FIRST index, not a re-index. Degrade to the authoritative local source — git-diff-scoped grep over git diff "${REVIEWED_FROM}..${REVIEWED_THROUGH}", plus bounded Read on the changed files, recorded as codesift: degraded (worktree not indexed) — only if that index_folder call itself fails. This is the same rule as ../../shared/includes/codesift-setup.md → "Worktree path rejected by index_file"; that include is the single source of truth — if this section and the include ever disagree, the include wins. It also covers the sibling-worktree case (a fresh index that is simply WIDER than your scope). Neither branch permits redirecting the check at the main checkout: that reports on code you are not reviewing, which is the stale-analysis trap this section exists to prevent. (This paragraph carried the pre-2026-08-11 text — "do NOT index the worktree" — for one commit after the include was corrected, which is exactly the drift the "include wins" clause above now makes cheap to resolve. The include's own measurement: 21.8M tokens, 13.2% of one run, spent on a grep fallback that an index_folder call would have made unnecessary.)
  4. Keep TARGET_REPO consistent with the Phase 3 destructive-persistence precondition (the repo REVIEWED_FROM..REVIEWED_THROUGH is resolved against MUST be the same TARGET_REPO analysis and tagging both reference).

Read-only audit checkout (TIER 2+, commit-range scopes)

The auditors read; the lead writes. Until that was a boundary in the filesystem it was only a convention, and two things went wrong because of it. A review of a commit range runs while the tree keeps moving — this skill already carries a "Working-Tree Staleness Check" for exactly that, i.e. it knows findings can be reported against a file HEAD has since changed. And in FIX modes the lead starts editing while auditors may still be reading, so a finding can be produced from a tree that no longer matches the range it claims to describe.

Give the audit agents a frozen, unwritable checkout of REVIEWED_THROUGH and keep the live tree for the fix loop:

REVIEW_TREE=$(mktemp -d)/audit-$(git rev-parse --short=7 "$REVIEWED_THROUGH")
if git worktree add --detach -q "$REVIEW_TREE" "$REVIEWED_THROUGH" 2>/dev/null; then
  chmod -R a-w "$REVIEW_TREE" 2>/dev/null          # enforcement, not etiquette
  echo "[REVIEW] audit tree: $REVIEW_TREE (read-only @ $(git rev-parse --short=7 "$REVIEWED_THROUGH"))"
else
  REVIEW_TREE="$(git rev-parse --show-toplevel)"
  echo "[REVIEW] audit tree: live checkout (read-only worktree unavailable) — findings may race the fix loop"
fi

Pass REVIEW_TREE to every dispatched agent as the root they analyze. The lead keeps using the live checkout: CodeSift pre-compute, the fix loop, the artifact and the retro all belong there.

Teardown is mandatory and must survive a failed run — chmod -R a-w makes the directory undeletable by the normal path, so a review that dies mid-flight leaves an unwritable worktree and a registered entry that git worktree list will keep showing:

# The guard is not defensive padding. On the fallback path $REVIEW_TREE IS the live checkout, and
# an unguarded teardown would then `chmod -R u+w` the entire working repository — stripping the
# read-only bits off .git/objects, mounted secrets and locked configs — and try to `worktree
# remove` the main tree. A failed worktree creation must not damage the repo it failed to copy.
if [ -n "${REVIEW_TREE:-}" ] && [ "$REVIEW_TREE" != "$(git rev-parse --show-toplevel)" ]; then
  chmod -R u+w "$REVIEW_TREE" 2>/dev/null
  git worktree remove --force "$REVIEW_TREE" 2>/dev/null
fi

Run it at the end of Phase 3 and on every abort path. Record the outcome in the Validity Gate as audit_tree: readonly(<sha7>) | live(<reason>). live is honest and allowed; silently claiming readonly when the worktree was never created is not.

Do NOT use this for the fix loop. Phase 4 edits real files, runs the real suite and commits — it belongs in the live checkout, and pointing it at a frozen detached tree would produce commits on no branch. The split is the point: frozen tree for the eyes, live tree for the hands.

Stack Detection (TIER 2+)

Detect tech stack and load matching rules:

Stack indicatorRules file
tsconfig.json../../rules/typescript.md
next.config.* or app/layout../../rules/react-nextjs.md
nest-cli.json or @nestjs/*../../rules/nestjs.md
requirements.txt / pyproject../../rules/python.md
composer.json../../rules/php.md
composer.json with yiisoft/yii2../../rules/yii2.md (with php.md — counts as ONE slot)
package.json with express (no Next/Nest)../../rules/express.md
astro.config.*../../rules/astro.md
go.mod../../rules/go.md
Cargo.toml../../rules/rust.md
*.csproj / *.sln../../rules/dotnet.md
Gemfile../../rules/ruby.md

Load at most 2 rules files. Pass to agents as STACK_RULES input.

Hotspot Detection (TIER 2+)

With CodeSift: analyze_hotspots(repo, since_days=90) -- if any diff file is in the top 10 hotspots, add a risk signal.

Without CodeSift: git log --format=format: --name-only --since="3 months ago" | sort | uniq -c | sort -rn | head -20

Blast Radius (TIER 2+)

With CodeSift: impact_analysis(repo, since=<REVIEWED_FROM>, depth=2) Without CodeSift: grep -r 'import.*[changed-module]' to find direct importers.

Dead Code Scan (optional, JS/TS only)

If the diff adds/removes exports and knip is available: npx knip --reporter json 2>/dev/null. Cross-reference flagged exports. If knip unavailable, skip silently.


Phase 0.5: CodeSift Pre-Compute

Runs only when CodeSift is available. When unavailable, agents fall back to their degraded modes (Read/Grep).

TIER 0 (optional): Skip unless CodeSift is already initialized. Minimal value for <=15 line diffs.

If any pre-compute call fails, set PRECOMPUTED_DATA=partial, log the failed operation in SKIPPED STEPS, and continue. Do NOT guess codebase_retrieval sub-query shapes.

TIER 1 (light ops):

  1. search_patterns(pattern="empty-catch", file_pattern="<changed-file-substring>", max_results=20)
  2. find_references(symbol_names=[<changed exports>], file_pattern="<active test glob>")
  3. analyze_complexity(file_pattern="<changed-file-substring>", top_n=10)

TIER 2-3 (core ops):

  1. For each changed production file: get_file_outline(file_path="<relative path>")
  2. find_references(symbol_names=[<changed symbols>], file_pattern="<active test glob>")
  3. trace_call_chain(symbol_name="<key changed symbol>", direction="callers", depth=2)
  4. search_patterns(pattern="empty-catch", file_pattern="<changed-file-substring>", max_results=50)
  5. analyze_complexity(file_pattern="<changed-file-substring>", top_n=20)
  6. impact_analysis(since=<REVIEWED_FROM>, until=<REVIEWED_THROUGH>, depth=2)

If the repo uses both *.spec.* and *.test.*, run the test-reference step for both globs and merge the results.

Compatibility Notes

  • Valid codebase_retrieval sub-query types: symbols, text, file_tree, outline, references, call_chain, impact, context, knowledge_map
  • Do NOT use patterns, complexity, or file_outlines inside codebase_retrieval
  • outline uses singular file_path
  • For direct find_references, use symbol_names when checking multiple symbols
  • For search_patterns and analyze_complexity, use the standalone tools — there is no equivalent valid codebase_retrieval sub-query type

Pass results as PRECOMPUTED_DATA to each agent:

AgentGetsHelps with
Behavior AuditorCall chains, pattern matches, complexityFocus on high-risk functions
Structure AuditorFile outlines, complexity, impactSRP and limits pre-answered
CQ AuditorPattern matches, test refs, file outlines~40% of gates pre-evaluated
Confidence Re-ScorerReference counts, hotspot ranks, impactData-driven confidence

Dispatch follows ../../shared/includes/execution-policy.md through env-compat. Reuse existing authorization within that policy; session restrictions take precedence. Run each required gate and report its actual independence or an unmet requirement.

Phase 1: Audit

Steps: 1.1 Self-Review Disclosure -> 1.2 Review Header -> 1.3 Agent Dispatch / Inline Audit -> 1.4 CQ (TIER 1+) -> 1.5 Q1-Q25 (if tests) -> 1.6 Adversarial (ALL tiers) -> 1.7 Result Merging

With --thorough: steps 1.3-1.5 become 3 independent passes in parallel, merged via majority voting, then adversarial runs after merge.

1.1 Self-Review Disclosure

Check whether you wrote any of the code being reviewed in this session. If yes, add a SELF-REVIEW marker to the header. Self-review detected -> pass --multi to the adversarial script (forces ALL available providers, not a rotating single). The flag is --multi — do NOT pass --all-providers (a phantom flag): the DISPATCH-SHAPE flags are limited to --multi/--single/--rotate/--exclude/--exclude-last (other flags such as --mode, --artifact, --append-artifact, --json are separate and valid); an unknown flag exits 2 and silently drops you to weaker coverage. Probe once if unsure: ~/.zuvo/adversarial-review --help | grep -- --multi. --multi exits 3 (single_provider_only) when <2 providers exist — only then fall back to --rotate/--single.

1.2 Review Header (merged banner -- single block replaces 4 separate blocks)

===============================================================
CODE REVIEW | TIER [0-3] ([NANO-DEEP])
SCOPE:  [N files, +X/-Y lines] | INTENT: [BUGFIX/REFACTOR/FEATURE/INFRA]
AUDIT:  [SOLO/TEAM (N)] | Adversarial: [providers] | RISK: [LOW-CRITICAL]
Risk signals: [x] API  [ ] DB  [ ] Auth  [ ] Money  [ ] 500+L
===============================================================

1.3 Agent Dispatch

Refer to env-compat.md for the correct dispatch pattern per environment.

TIER 0-1: No agents. Lead performs all analysis inline using CodeSift pre-computed data (Phase 0.5) if available.

TIER 2: Dispatch Behavior Auditor (agents/behavior-auditor.md) if new production files. Dispatch CQ Auditor (agents/cq-auditor.md) as background agent. Lead performs Structure analysis inline.

TIER 3: Dispatch all 3 audit agents in parallel:

Agent 1: Behavior Auditor
  model: "sonnet"
  type: "general-purpose"   # NOT Explore — Explore lacks mcp__codesift__* and CodeSift precheck hooks reject the dispatch
  instructions: read agents/behavior-auditor.md
  input: diff, tech stack, change intent, PRECOMPUTED_DATA, PROJECT_CONTEXT

Agent 2: Structure Auditor
  model: "sonnet"
  type: "general-purpose"
  instructions: read agents/structure-auditor.md
  input: diff, tech stack, change intent, PRECOMPUTED_DATA, PROJECT_CONTEXT

Agent 3: CQ Auditor
  model: "sonnet"
  type: "general-purpose"
  instructions: read agents/cq-auditor.md
  input: diff, tech stack, change intent, PRECOMPUTED_DATA, PROJECT_CONTEXT

Each agent receives: diff, tech stack, change intent, PRECOMPUTED_DATA, PROJECT_CONTEXT (global error handlers, middleware, decorators).

Result Merging (after agents complete)

  1. Collect BEHAV-N, STRUCT-N, and CQ findings
  2. Deduplicate -- same file:line + same issue = keep the one with more evidence
  3. Renumber sequentially as R-1, R-2, R-3...

1.4 CQ Self-Evaluation (TIER 1+)

For each changed production file, run CQ1-CQ40. Format: CQ EVAL: file.ts (NL) | CQ1=1 CQ2=0 ... | Score: X/Y -> PASS/FAIL | Critical gates: CQ4=0(no orgId:87). CQ critical gate failures (CQ3, CQ4, CQ5, CQ6, CQ8, CQ14) always produce MUST-FIX.

Where the 40 gates go (TIER 3, many files). Every gate must be EVALUATED for every file — that is not negotiable and no summary form relaxes it. What changes is where the evidence is printed: 40 gates × 15 files buries the findings the report exists to deliver. So:

  • The full per-file gate line always goes into the review artifact (memory/reviews/…), for every file. That is the auditable record, and it must be complete.
  • In the chat report, print the full line for any file with a 0, an N/A, or a critical-gate failure; for a fully clean file print one line: CQ EVAL: file.ts (NL) | 40/40 clean.
  • Never aggregate across files (cq=38/40 overall). Per-file scores are what caught Q7=0 and Q11=0 hiding under an aggregate; the compact form above is per-file, just shorter.

A clean-file summary line is only honest if the gates really ran. If some were not evaluated, that file is not clean — print the full line with the unevaluated gates marked, per ../../shared/includes/gate-registry.md.

1.5 Q1-Q25 Evaluation (if test files in diff)

For each test file, run Q1-Q25. Format: Q EVAL: file.spec.ts | Q1=1 Q2=1 ... | Score: X/Y -> PASS | Critical: Q7=1 Q11=1 Q13=1 Q15=1 Q17=1 -> PASS.

Pre-Existing Issues

Issues NOT introduced by the current diff: always report critical CQ gate violations (CQ3/4/5/6/8/14); briefly note CQ2, CQ10, CQ22; skip naming/magic numbers (code-audit territory). Cap at RECOMMENDED severity.

Working-Tree Staleness Check

When reviewing a commit range rather than the current working tree, verify that HEAD has not already changed a file after REVIEWED_THROUGH before reporting a finding against it:

git diff --quiet "{REVIEWED_THROUGH}..HEAD" -- <file>

If the file changed after the reviewed range:

  • mark it [ALREADY-PATCHED]
  • read the current file before reporting
  • drop stale findings that no longer exist at HEAD

1.6 Adversarial (ALL tiers — sequential)

Cross-model adversarial review using external providers. Runs sequentially via --multi — each pass fans out to every available provider, and different random provider. Text mode (no --json).

PROPORTIONALITY (HARD — a tiny diff gets a FAST pass, not a 20-minute grind). Adversarial still runs at every tier (a 3-line change CAN hide an inverted comparison), but the COST must match the diff. The 2026-07-10 pathology: a 3-line icon swap ran the full multi-pass rotate with each hung provider eating the 240s PROVIDER_TIMEOUT × several passes ≈ 20 minutes. That is a defect, not diligence. Scale the pass by tier:

TierDiffAdversarial shape
TIER 0 (NANO)<15 prod-logic lines, 1 file, no risk signalONE --single pass, ZUVO_REVIEW_TIMEOUT=60. No rotate, no second pass. git diff … | ZUVO_REVIEW_TIMEOUT=60 ~/.zuvo/adversarial-review --single --mode code. ~60s ceiling.
TIER 1 (LIGHT)15–100 linesONE --multi pass; a second only on the fix delta, per adversarial-loop.md Step 5.
TIER 2–3larger / risk signalsONE full --multi pass, then fix-delta passes only as Step 5 allows — 3 passes in all, never more.
  • Self-review overrides tier-down for correctness, but keep the timeout tight on tiny diffs: SELF-REVIEW still forces --multi (section 1.1) — but on a TIER 0 diff run it as ONE --multi pass with ZUVO_REVIEW_TIMEOUT=60, not multi-pass. One cross-model look, bounded to ~60s.
  • A hung/timed-out provider is NEVER retried in a manual loop on a tiny diff. If a provider times out at TIER 0/1, record Adversarial: partial (<provider> only, others timed out) and finalize — do NOT hand-retry the remaining providers (that hand-retry loop is exactly what turned 3 lines into 20 minutes). Chunking/retry is a TIER 2–3 concern for genuinely large diffs.
  • Always run in the background or with a long Bash timeout per adversarial-loop.md — never let the 120s Bash-tool default kill the pass mid-flight.

If adversarial-review not in PATH: ~/.zuvo/adversarial-review (stable; the versioned cache path breaks after any release)

BASE PREFLIGHT (run BEFORE piping any commit-range diff — a stale base wastes the whole pass). A two-dot <base>..<tip> diff computed against a base that is no longer an ancestor of the tip shows reverse hunks of other sessions' pushes — code being "deleted" that was actually added elsewhere. Every provider then reports a confident CRITICAL about a revert that does not exist (observed: one full multi-provider pass burned, 5/5 providers false-CRITICAL). Before the pipe:

git fetch -q origin                                    # the base may have moved since you resolved it
git merge-base --is-ancestor "$REVIEWED_FROM" "$REVIEWED_THROUGH" || echo "STALE BASE"

If it is NOT an ancestor: either merge/rebase first, or review $(git merge-base "$REVIEWED_FROM" "$REVIEWED_THROUGH")..$REVIEWED_THROUGH instead. Never send a known-stale range to the providers "to see what they say" — the findings are unfalsifiable noise you then have to disprove one by one.

TRUNCATED INPUT INVALIDATES THE PASS FOR THE OMITTED FILES. The staircase above is proactive; this is the reactive backstop for when it was not applied. If the wrapper prints input truncated (or the piped diff exceeds the provider cap), the pass did NOT review the files that fell off the end — and the highest-risk file is as likely to be dropped as any other. Do not accept that pass as coverage: re-run per production file (staircase step 1) for the omitted files before triage, and never let a nominal "pass 1 clean" stand for a file the provider never received.

Self-review escalation: If SELF-REVIEW marker set in 1.1, pass --multi flag.

Status handling (D2+D3+D4, 2026-05-17): When the script exits non-zero or returns non-ok JSON status, branch:

  • exit 3 / single_provider_only — --multi was requested but post-host-exclusion only 1 provider remains. Two options: re-invoke with --single (accept reduced consensus and note it in the review header) OR skip this pass and note Adversarial: skipped (single_provider_only — install second provider for diversity) in the review output.
  • exit 124 / status: "timeout" — ALL providers timed out. Record Adversarial: skipped (timeout) and continue to next pass (or finalize if last pass).
  • exit 125 / status: "suspended" — the HOST slept mid-run (suspended_seconds says how long); the providers were never given a chance. This is not reduced coverage and not a provider fault, so it is NOT a skip reason: re-invoke the same pass once. If the retry also returns 125, record it as timeout (same practical effect — no review — and the whitelist stays closed). Never report a suspended run as blocked provider infrastructure.
  • status: "partial" with exit 0 — some providers returned, others did not. Surface timeout_count in the review header (e.g. Adversarial pass 1: cursor-agent (1 of 2 providers; gemini timed out)) so the user sees coverage was reduced.

Provider accounting — a block is only "used" if it actually reviewed. Count a provider toward providers_used only when its block contains at least one verdict or finding. A block that holds nothing but an execution error, a usage/auth message, or an empty result is provider_failed, NOT a used provider. Otherwise the aggregate header claims cross-provider coverage that never happened — the same fake-coverage failure the Validity Gate exists to catch. If that drops the count below the tier's minimum, treat it as reduced coverage (Adversarial: partial (…)), never as a satisfied gate.

Unhealthy-provider short-circuit. Within one review run, after a provider returns two empty/error-only blocks, mark it unhealthy and stop dispatching to it for the remaining passes (including fix-delta passes) — repeated 5–8 minute waits buy nothing. Keep dispatching to the rest, and preserve the floor of two independent providers; if dropping the unhealthy one would take you below two, the run is reduced-coverage and must say so rather than silently continuing.

Cross-pass provider tracking: --multi already runs every available provider on each pass, so there is nothing left to rotate and --exclude-last is no longer threaded between calls — it existed to force variety when each pass got exactly one provider. Still capture providers_used_list (array field) from each pass's JSON: it is the record of who actually answered, which is what tells a later reader whether a pass was genuinely cross-model or quietly degraded to one provider because the others timed out. (The string field providers_used cannot be indexed in jq; use the array.)

CONTEXT BUDGET handling (the constructive escape valve — read this before invoking the "tight budget" rationalization):

Adversarial CLI providers have ~150K char input limits (varies: codex ~200K, gemini ~100K, cursor-agent ~150K). If git diff "${REVIEWED_FROM}..${REVIEWED_THROUGH}" | wc -c exceeds the smallest provider's limit OR the current session is genuinely close to its own ceiling, do NOT skip adversarial. Take the staircase in order:

  1. Per-file chunking. Split the diff per file (git diff --name-only "${REVIEWED_FROM}..${REVIEWED_THROUGH}") and run adversarial separately on each file's diff. Aggregate findings, dedupe by fingerprint. Per-file passes still satisfy the gate.
  2. Hunk-level chunking (if a single file's diff is still too large): split on @@ hunk boundaries with surrounding context. Run per-hunk, aggregate.
  3. Drop --rotate to --single fastest provider. Reduces parallelism but keeps adversarial coverage. Note in header: Adversarial: degraded (--single <provider>, context-budget chunked N files).
  4. Last resort — exit BLOCKED: if even single-provider per-hunk does not fit, exit with status BLOCKED_CONTEXT_BUDGET and surface to the user: "Diff is N chars across M files; adversarial cannot complete. Narrow the scope (e.g. /zuvo:review path/to/subdir/ or HEAD~3) and re-invoke." This blocks the review verdict — does NOT silently produce PASS/APPROVED.

What "context budget tight" is NOT a license to do: skip the pass, mark Adversarial: skipped (context budget), and proceed to a verdict. That value is on the forbidden escape hatches list (line 376+) and the Validity Gate will override the verdict to INCOMPLETE with suffix [ESCAPE-HATCH-VIOLATION:adversarial].

REPORT mode — sequential finding (no fixes)

Each pass uses --multi (every available provider runs in parallel). Prepend prior findings summary so each provider targets NEW issues.

# Proof file FIRST — the artifact you write in Phase 3 must cite it, and
# pipeline-gate-lib.sh::pg_artifact_proven REJECTS an artifact whose adversarial:
# path does not resolve or holds <2 "REVIEW BY:" lines. A review that skips this
# writes an artifact the push gate silently ignores.
# The filename must be unique per REVIEWED RANGE, not a fixed word. With a literal `review`
# default, two concurrent reviews (parallel agents on the same repo — routine here) both
# --append-artifact into ONE file, and pg_artifact_proven's ">=2 REVIEW BY: lines" check is then
# satisfied by lines belonging to the OTHER review. That defeats the proof gate with no error
# anywhere: the mechanical backstop passes on evidence about a different range.
RANGE_KEY=$(printf '%s' "${REVIEWED_FROM}..${REVIEWED_THROUGH}" | shasum | cut -c1-10)
ADV_PROOF="zuvo/proofs/${SLUG:+${SLUG}-}${RANGE_KEY}-adversarial.txt"   # range hash ALWAYS present

# The proof path is carried by --artifact on EVERY pass. --append-artifact is a boolean
# modifier that says "add to that file instead of overwriting it" — it is NOT where the path
# goes. Between 2026-08-07 and 2026-08-09 this block documented `--append-artifact "$ADV_PROOF"`;
# the parser took no value there, so the path fell through to `Unknown argument` and every
# copied pass exited 2 having written nothing. Six ship retros reported it before it was fixed.
# The one-arg form is accepted now as an alias, but write the canonical pair.

# Pass 1 (creates the proof — no --append-artifact, so an earlier run's file is replaced):
git diff "${REVIEWED_FROM}..${REVIEWED_THROUGH}" | ~/.zuvo/adversarial-review --multi --mode code --artifact "$ADV_PROOF"
# → Read output, extract ADV-1, ADV-2

# No pass 2 over the SAME diff. Re-reviewing an unchanged range "for new issues only" was the
# default until 2026-10-15 and it was the single largest cost in the fleet: every pass of a big
# branch is several chunks of 4-5 lanes, and the repeat passes mostly re-derived the false
# positives of the first (a lane-reported CRITICAL was real 21-22% of the time). Passes 2 and 3
# exist only for FIXES — FIX mode below, `adversarial-loop.md` Step 5. Report-only: one pass.

Check the exit code of every pass. 0 = reviewed, 3 = single_provider_only (honest degraded), 1/2 = nothing was reviewed, 5 = nothing was REVIEWABLE. A pass that exits non-zero writes no REVIEW BY: line, so an artifact citing this proof will fail pg_artifact_proven at push time — with the failure surfacing one phase later, in a place that says nothing about the pass that actually died.

Exit 5 is the fix-delta passes' foot-gun. Passes 2 and 3 in FIX mode pipe echo "PRIOR FINDINGS: …" followed by a git diff. If that diff resolves to nothing — the range already merged, the refs wrong, or the fixes applied in between — the payload is still a non-empty sentence of metadata. Before 2026-09-18 the reviewer took it, answered "0 findings", and wrote REVIEW BY: into the proof: a clean pass, a valid artifact, and not one line of code seen by any model. It now exits 5. Do not read an early exit as clean until you have checked which code produced it — 0 findings after exit 0 means reviewed-and-clean, after exit 5 it means nothing was there.

Early exit: 0 findings from a pass with exit 0 = stop (code is clean from that model's perspective). Exit 5 produces no findings report at all — the script exits before it prints anything to stdout — so read the exit code, not the empty output: re-derive the diff and run the pass again. An empty stdout is not a clean review.

FIX mode — sequential fix + validation

Same --multi pattern; pass 1 reviews the whole range, passes 2 and 3 review ONLY the fixes (adversarial-loop.md Step 5 — the triggers and the hard cap of 3 live there, and the driver refuses a fourth pass with exit 7).

# Pass 1: review post-primary-fix code (creates the proof — no --append-artifact)
git diff "${REVIEWED_FROM}..HEAD" | ~/.zuvo/adversarial-review --multi --mode code --artifact "$ADV_PROOF"
git rev-parse HEAD   # BASE1 — print it and paste the literal below: the next Bash call is a fresh shell
# → ADV-1 → fix only CONFIRMED CRITICALs and behaviour-changing WARNINGs → commit the fixes

# Pass 2 — only if pass 1 led to such a fix: everything since BASE1 — the fix commits, anything still
# uncommitted and NEW files (a fix's regression test). build-review-patch, not `git diff`, which drops
# untracked files. Exit 3 from the helper = the fixes changed nothing: do not spend the pass.
git rev-parse HEAD   # BASE2 — print it now, BEFORE any pass-2 fix lands
(echo "PRIOR FINDINGS: ADV-1 [desc] fixed in this delta — validate the fix, find what it broke";
 ~/.zuvo/build-review-patch --base "<BASE1>") | ~/.zuvo/adversarial-review --multi --mode code --artifact "$ADV_PROOF" --append-artifact
# → fix only a NEW CONFIRMED CRITICAL → commit

# Pass 3 — only for that fix: everything since BASE2.
(echo "PRIOR FINDINGS: ADV-n [desc] fixed in this delta — validate the fix only";
 ~/.zuvo/build-review-patch --base "<BASE2>") | ~/.zuvo/adversarial-review --multi --mode code --artifact "$ADV_PROOF" --append-artifact
# → there is no pass 4: everything still open is a known concern

Record the verdicts after triage: every finding is fixed, rejected (a false positive) or deferred — record each by its ID: in ONE call, ~/.zuvo/adversarial-review --record-disposition ID-1 fixed --record-disposition ID-2 rejected …, per ../../shared/includes/adversarial-loop.md Step 4.9 (the review lists its IDs on stderr).

In FIX mode the fixes land BETWEEN passes, so the last pass reviews content no earlier pass saw. The artifact you write in Phase 3 covers the FINAL blob of each file (the gate is content-keyed) — if you fix anything after the last adversarial pass, that pass no longer covers what you are pushing, and the honest move is one more append (a fix-delta pass), not a re-labelled old one — as long as the cap allows it. After pass 3 make no further edits except a CONFIRMED CRITICAL's fix, proven by a regression test that fails before it and passes after (adversarial-loop.md Step 5) — the one edit no pass covers, so name it in the review output.

Max 2 fix attempts per provider finding. Max 3 passes total — never more (driver exit 7).

Common rules

  • Use --multi — every available provider reviews the same diff in parallel. This used to say the opposite (--rotate, one random provider, explicitly warning off the all-provider run) as a cost measure. Measured 2026-09-02 on 20 real diffs with Opus-judged findings: 57% of each model's TRUE findings are unique to it and no model sees more than 28% of the 347 distinct defects — so one provider per review loses roughly two thirds of what was there. For the skill whose entire job is finding defects, that is not a cost saving, it is the product.
  • Strip lockfiles, snapshots, dist output, and other known noise files from the diff before piping it to adversarial-review.
  • When deterministic facts are already known (for example: lockfile present in diff, package ships bundled types, file already patched at HEAD), prepend a short FACTS: block before the diff so the adversarial provider does not rediscover settled facts.
  • If PROD_LOGIC_LINES = 0 and SELF-REVIEW is not set, skip adversarial and log: [CROSS-REVIEW] Skipped — no production logic changed.
  • Timeout: 60s per provider. Skip on timeout/malformed, continue with next.
  • All unavailable: [CROSS-REVIEW] No external provider available. in SKIPPED STEPS.
  • Severity: CRITICAL -> MUST-FIX (bypasses confidence gate). WARNING -> RECOMMENDED. INFO -> NIT.
  • Tag: each finding as [CROSS:<provider>]

Multi-Pass (--thorough variant)

3 audit passes in parallel: Pass 1 alphabetical, Pass 2 reverse dependency (leaf-first), Pass 3 risk-score descending. Majority voting: 3/3 -> KEEP + confidence +15. 2/3 -> KEEP. 1/3 -> DOWNGRADE one tier. Sequential adversarial runs AFTER multi-pass merge. Adversarial findings are NOT subject to voting — they go through confidence gate (WARNING/INFO) or bypass it (CRITICAL).


Phase 2: Confidence Gate

Structural-refactor findings → defer with a recipe, don't block. A RECOMMENDED finding whose fix is a structural refactor (extract a module, split a god-file, invert a dependency, restructure a layer) is real but does NOT belong in this review's fix loop — it is multi-file work that zuvo:refactor owns. Do not leave it as a vague "consider refactoring": record it through backlog-add (Backlog Persistence below, Generic write; --class CPLX, SIZE or DUP as the finding is) with a concrete resolution recipe (what to extract/split, the target shape, the 2-3 ordered steps, the rule it satisfies) in --evidence "$E", so it is actionable later, and keep it OUT of the MUST-FIX set so it cannot block the merge. A structural refactor surfaced as MUST-FIX on an unrelated diff is scope creep.

TIER 0-1: Lead scores each finding inline. Confidence: [X]/100 -- [reason].

TIER 2+: Dispatch Confidence Re-Scorer agent:

Agent: Confidence Re-Scorer
  model: "sonnet"
  type: "general-purpose"
  instructions: read agents/confidence-rescorer.md
  input: full candidate list, PRECOMPUTED_DATA, adversarial findings

Disposition

ConfidenceActionDisposition
0-25EXCLUDE from report; not passed to backlog-addDISCARD
26-50EXCLUDE from report; recorded through backlog-add (Backlog Persistence)RECORD
51-100KEEP in reportREPORT

Adversarial CRITICAL bypass: Findings from adversarial-review.sh tagged CRITICAL skip the confidence gate. Effective confidence = 100. No exceptions.

CQ/Q critical-gate bypass (same rule, added 2026-08-02): a finding sourced from a CRITICAL gate failure — CQ3, CQ4, CQ5, CQ6, CQ8, CQ14, or Q7, Q11, Q13, Q15, Q17 — also skips the confidence gate at effective confidence 100. Without this the skill contradicted itself: "critical gate failures ALWAYS produce MUST-FIX" and "MUST-FIX blocks merge", yet a CQ4 tenant-isolation finding scored 40 by the re-scorer was excluded from the report as below the threshold and never blocked anything.

When findings are recorded: every backlog-add call of Backlog Persistence happens AFTER Phase 4 Execute (or after Phase 3 if no execute), so a finding the fix loop resolved gets no call (backlog-protocol.md "Filing from a skill", Closing, not removing). The items FIX-AUTO defers (NIT, structural-refactor RECOMMENDED) are filed there too, once each, before the Validity Gate. The FIX-AUTO announcement block (AUTO-FIX: applying …) prints before Phase 4, so it names them by finding id (R-<n>) only; their B- or F-ids appear in the final report's recorded list.


Phase 3: Report

Phase 3 runs end-to-end with no approval pauses. Do not ask the user to confirm before persisting the report, tagging commits, recording findings through backlog-add, or running the retrospective. All subsections below (Backlog Persistence → Report Persistence → Tag Reviewed Commits → Knowledge Curation → Retrospective → Completion Gate → NEXT STEPS) execute in order before any REVIEW COMPLETE text is emitted. The only gate is the Completion Gate Check at the end.

Destructive-persistence preconditions (verify silently before tagging or writing to shared logs):

  • The CWD is a git repo and matches the scope being reviewed (git rev-parse --is-inside-work-tree true; REVIEWED_FROM..REVIEWED_THROUGH resolved against this repo's history).
  • For commit-range scopes (new, HEAD~N, explicit hashes): REVIEWED_FROM and REVIEWED_THROUGH are both reachable from HEAD.
  • For staged / uncommitted scopes: skip reviewed/<hash> tagging entirely (already documented below).
  • If any precondition fails: skip the destructive step (tag / log append) and report [skipped: precondition failed (<reason>)] in the gate check rather than silently writing into the wrong repo or logging spurious entries.

Severity Tiers

TierMeaningMerge impact
MUST-FIXConfirmed bug, security issue, data loss, critical CQ gateBlocks merge
RECOMMENDEDMaintenance risk, degraded reliabilityMerge discouraged
NITStyle, readability, no functional impactMerge OK as-is

Report Sections

TIER 2-3 (full report, 14 sections):

  1. META 2. SCOPE FENCE 3. VERDICT 4. QUESTIONS FOR AUTHOR (in FIX modes: pause, re-evaluate findings per answers; in REPORT: informational) 5. DEPLOYMENT RISK 6. SEVERITY SUMMARY 7. CHANGE SUMMARY 8. SKIPPED STEPS 9. VERIFICATION PASSED 10. BACKLOG IN SCOPE 11. DROPPED ISSUES (with their disposition) 12. FINDINGS (MUST-FIX -> RECOMMENDED -> NIT collapsed) 13. QUALITY WINS (max 3) 14. TEST ANALYSIS

TIER 0-1 (condensed report — merge sections to save ~1.5K output tokens): Combine META + SCOPE FENCE + VERDICT into the merged banner. Skip: DEPLOYMENT RISK (always LOW at TIER 0-1), SKIPPED STEPS (obvious), VERIFICATION PASSED (inline), BACKLOG IN SCOPE (check manually). Print only: banner, FINDINGS (if any), QUALITY WINS, NEXT STEPS. ~500 tok output vs ~3K for full report.

Each finding:

R-1 [MUST-FIX] Missing orgId filter in query -- returns all orgs' data
  File: src/order/order.service.ts:87
  Confidence: 92/100
  Evidence: findMany at :87 has no orgId in WHERE clause
  Fix: Add `organizationId: orgId` to the WHERE clause

NIT visual subordination:

NITs (3 items -- style/readability, no functional impact):
  R-12 unused import at auth.ts:3
  R-13 prefer ?? over || at config.ts:45
  R-14 collapsible if at user.service.ts:88

Test Coverage Delta (TIER 2+)

For each changed production file: check pre-computed test references (Phase 0.5). Symbols with 0 test refs -> RECOMMENDED finding at TIER 2+, observation in TEST ANALYSIS at TIER 1.

Backlog Persistence (after execute or after report if no execute)

Every finding this run does not fix goes through backlog-add, one call per finding (backlog-protocol.md "Filing from a skill"): the excluded findings scored 26-50 (RECORD), the reported findings left unfixed (51-100: a MUST-FIX left unfixed under --report-only or by a partial fix, every NIT above 25 (a NIT needs no defer-reason), a RECOMMENDED with a whitelist defer-reason, which goes in --evidence "$E" as defer-reason: <reason>, the post-fix gate's residual non-CRITICALs) and the pre-existing out-of-fence findings with a drive-by tag. Outside blocking mode a localized RECOMMENDED left unfixed is never recorded as a deferral: it fails the Localized-deferral drift gate. A finding scored 0-25 (DISCARD) is not passed, and a finding this run fixed gets no call (Closing, not removing). The helper's bars and dedupe replace any fingerprint of review's own.

  • A finding of the reviewed diff — in the reviewed file set, or in an unchanged file the diff caused (the fence's causality exception) — is a Generic write: ~/.zuvo/backlog-add --repo "$R" --class <C> --conf <N> --source review --file "$F" --line <n> [--anchor "$A"] --title "$T" [--evidence "$E"].
  • A pre-existing out-of-fence finding with a drive-by tag is recorded with --source drive-by:review; one with no tag is reported, not recorded. The call: ~/.zuvo/backlog-add --repo "$R" --class <C> --conf <N> --source drive-by:review --file "$F" --line <n> [--anchor "$A"] --title "$T" [--evidence "$E"] (drive-by-findings.md; its "Cap" limits only how many the summary lists by name, never how many are recorded).

For a finding of the reviewed diff, <C> is CQ, or BUG only when it names the input and the wrong output, or the class the finding is (CPLX, SIZE or DUP for a structural refactor, with its recipe in --evidence "$E"). For a pre-existing finding, <C> is its drive-by tag (SEC, BUG, SIZE, DUP, CPLX, PERF, DEAD or READ, drive-by-findings.md "Categories and what qualifies"). <N> is its effective confidence: the Phase 2 score, or 100 for a finding the adversarial-CRITICAL or CQ/Q critical-gate bypass sent past the confidence gate. Pass --anchor "$A" only when the class requires one, BUG, SEC or DATA, or the finding carries one, never an invented one; a finding about the whole file cites --line 1 with a "Whole file: …" title ("The location rule"). Assign "$R", "$F" and "$T" before the call, "$A" only when you pass --anchor, and "$E" only when you pass --evidence (a recipe or the evidence the finding carries), as "Free text goes through a variable" in that section says; an empty value you pass is a bug, and backlog-add refuses a missing required field (REFUSED missing-field:<f>). Report the B- or F-id it prints; it is the id the final report's recorded list and the drift gate cite. On a non-zero exit act on the protocol's exit table; a finding still not persisted is listed under NOT PERSISTED (<reason>), never appended by hand. The report states Recorded: N (backlog: a, ledger: b) ("The report line" in backlog-protocol.md), plus each NOT PERSISTED (…) by name.

Report Persistence (TIER 1+)

Save the full report to memory/reviews/YYYY-MM-DD-<scope>.md.

ORDERING (persistence artifacts are SHA-keyed — a later commit invalidates them). Both gates here key on the head SHA: verify-audit rejects a report whose Verified-against: stamp is not the current SHA, and the content-keyed artifact below encodes <base7>..<head7> in its own filename. So finish ALL commits first — including any late CQ/complexity cleanup — and only then write the report, the artifact, the tags, and the retro. The one exception is the closing commit for backlog-add's writes: it holds only memory/ paths, which the pipeline gate classes as non-production (pg_is_production: *.md, memory/findings/*.jsonl) and which verify-audit has already checked the stamp before (inside append-runlog), so it leaves the Verified-against: stamp and the <head7> artifact valid. If any other commit lands after persistence, the stamp and the <head7> are stale: re-stamp and rewrite the artifact for the new head rather than shipping a report the verifier will reject. Do the final complexity/structure checks before this section, not after it.

Two ways HEAD moves after a review, both observed, both handled here rather than discovered.

  1. The caller is zuvo:ship. Ship commits the version bump and CHANGELOG.md in its own Phase 3 — after it calls review. So a review invoked by ship that persists its artifact immediately produces a guaranteed-stale one: not a risk, a certainty, every single time the two compose. When CALLER = zuvo:ship, do the audit and the fix loop as usual but write the report, the artifact and the tags after ship's release commit, and key them on that commit. Ship's own artifact step is the synchronisation point; ask it for the post-commit SHA rather than reading HEAD at review time.
  2. Another session commits into the same branch while the review runs. A TIER-3 pass takes longer than a parallel agent's commit interval — measured 2026-09-22: HEAD advanced five times during one review, and the sub-agents had all been dispatched against the first of those. Freeze the range: record REVIEWED_THROUGH once, before the first gate, and review that SHA. When HEAD has moved by persistence time you have exactly two honest options — re-run the gates over the tail and widen the range, or write the artifact for the frozen range and state the uncovered commits explicitly in it. Do not silently widen the range to HEAD: the sub-agents never saw those commits, and the artifact's files: line would then claim coverage that only the adversarial pass (if that) actually has.

Whichever applies, the artifact must describe what was reviewed, not what happens to be at HEAD when it is written.

Content-keyed pipeline-entry artifact (REQUIRED — on successful completion only). In addition (or as the same file's first lines), write the content-keyed review artifact memory/reviews/<base7>..<head7>-<slug>.md carrying the machine-readable range: / files: header per ../../shared/includes/review-artifact.md. This is the signal the pipeline-entry gates read (pg_range_reviewed) — the path encodes the reviewed <base7>..<head7> range and the files: line records the reviewed production files (or *). Write it only on success — a crashed/aborted review must leave no artifact, so a failed run never grants pipeline coverage. Skip for staged/uncommitted scope (no committed range).

Tag Reviewed Commits (per-commit audit trail)

Naming convention: reviewed/<short-hash> tags the individual commits that were examined. This is distinct from the post-execute wrapper tag (review-YYYY-MM-DD-<slug>) that marks the fix commit produced by Phase 4.

# Namespace-collision guard: git refs are FILES, so a flat tag named `reviewed`
# and the directory `reviewed/<hash>` cannot coexist. NEVER delete the flat tag
# to make room — it is someone else's ref and deleting it is unrecoverable here.
if git rev-parse -q --verify refs/tags/reviewed >/dev/null; then
  echo "per-commit tags: skipped: namespace-collision(refs/tags/reviewed)"
else
  for H in $(git log --format='%H' "${REVIEWED_FROM}..${REVIEWED_THROUGH}"); do
    h=$(git log --format='%h' -1 "$H")
    git tag -f "reviewed/$h" "$H"
  done
fi

If the collision is hit, record per-commit tags: skipped: namespace-collision(<target>) in the report and still create the non-conflicting review-YYYY-MM-DD-<slug> wrapper tag — the audit trail degrades to the wrapper, it does not become a licence to rewrite existing tags. (git tag -f above only ever force-updates the same reviewed/<hash> name, which is the intended re-review behavior.)

Skip tagging when scope is staged or uncommitted.

Knowledge Curation

Run knowledge-curate.md (if loaded): WORK_TYPE="review", CALLER="zuvo:review", REFERENCE=<commit range or "staged">.

Drive-by findings (REQUIRED — record only, leaves a receipt)

Follow ../../shared/includes/drive-by-findings.md with <skill> = review: backlog the security, bug, size, duplication, complexity, performance, dead-code and readability problems noticed OUTSIDE this run's fence in files it already read (never fix them here), list every SEC one in the review report, then ALWAYS record the receipt ~/.zuvo/log-drive-by --skill review --sec <a> --bug <b> … (all zeros is an honest outcome).

Follow-up ideas (optional — ZERO ceremony, leaves a receipt)

Follow ../../shared/includes/followup-ideas.md with <skill> = review: append genuine new ideas to memory/ideas.md at the MAIN checkout root if any surfaced, then ALWAYS record the receipt ~/.zuvo/log-ideas --skill review --count <N> (N=0 is the normal, honest outcome — do not invent ideas to inflate it). The receipt makes the un-gated step's silence auditable in ~/.zuvo/ideas.log without forcing ideation.

Retrospective (REQUIRED)

Follow the retrospective protocol from retrospective.md. Gate check -> structured questions -> TSV emit -> markdown append.

If the gate check skips, you MUST print one of:

  • RETRO: skipped (trivial session, <3 findings and no fix-loop)
  • RETRO: skipped (<reason>) — reason must name a specific condition, not "nothing interesting"

Silently omitting the retro is a protocol violation. Track record shows ~90% of review runs skip this step without marking why, which produces no learning signal. If you are tempted to skip, print the reason explicitly so the pattern is visible in ~/.zuvo/retros.md.

Completion Gate Check (HARD GATE — blocks output)

Before printing REVIEW COMPLETE or the NEXT STEPS block, verify every item below. If any item is unchecked, execute the missing step now — do not emit the completion text with unfinished items.

COMPLETION GATE CHECK
[ ] Diff type classified and printed: [prod-only/test-only/mixed]
[ ] CQ self-eval printed for each changed production file
[ ] Q1-Q25 printed for each changed test file (if any)
[ ] TIER 2-3: Behavior Auditor (if new prod files) + CQ Auditor + Confidence Re-Scorer DISPATCHED; TIER 3 additionally Structure Auditor as sub-agents — NOT done inline as "lead" (or explicit [DEGRADED: ...] line, forbidden on self-review)
[ ] Every dispatched sub-agent RETURNED and its findings were read — count RETURNS, not dispatches. One still running bought no coverage: record `NO_RETURN(<elapsed>)`, which violates the tier exactly as `NOT_DISPATCHED` does. (2026-08-11: a TIER 3 self-review shipped a release on 2-of-3 auditors because nothing checked this; the third returned 12h later with a real finding.)
[ ] TIER 2-3 Next.js: framework_audit called (nextjs_route_map alone does NOT satisfy it)
[ ] Adversarial review ran — pass 1 over the whole range with findings printed, passes 2-3 only on fix deltas (max 3, adversarial-loop.md Step 5); SELF-REVIEW used --multi (not --rotate)
[ ] All findings confidence-scored
[ ] FIX modes: [GATE: comment-pass] PASS run=<id> (ledger-verified), N/A (no files written) or N/A (run=<id> no audited source) from post-fix step 1b, AUTO-FIX included; every CHECK claim listed as CHECK settled:; re-run before step 3's git add after any later write; its comment_pass: line pasted into the retro Telemetry block
[ ] FIX modes: zuvo:mutation-test chained on the tests covering the changed files (Phase 4 step 4), its gaps closed in-run, or the false trigger condition named
[ ] Backlog Persistence ran: every unfixed finding scored 26+ recorded through backlog-add, each B- or F-id reported or named under NOT PERSISTED (<reason>), or explicitly N/A
[ ] No localized RECOMMENDED silently deferred — every deferred RECOMMENDED, recorded through backlog-add, carries a defer-reason of [NIT] or [structural-refactor (multi-file)], or in blocking mode [out-of-mode: blocking]; any single-file fix deferred = drift, route it to Phase 4 instead
[ ] Closing commit for backlog-add's writes made as the run's last action (standalone FIX mode; or `none: nothing to stage`), or the `Backlog writes: <paths>` line printed (invoked by another skill, or --report-only)
[ ] Report saved to memory/reviews/YYYY-MM-DD-<scope>.md (TIER 1+)
[ ] Content-keyed artifact memory/reviews/<base7>..<head7>-<slug>.md written with range:/files:/adversarial: header — the adversarial: field points at the saved `--artifact` output (≥2 providers); an artifact without a real proof no longer grants coverage (on success; skip staged/uncommitted)
[ ] reviewed/<hash> tags created (skip for staged/uncommitted scope)
[ ] Knowledge curation ran (if knowledge-curate.md loaded)
[ ] Drive-by findings recorded per drive-by-findings.md: the `Drive-by: N recorded (…)` line and every SEC in the final summary, and the `~/.zuvo/log-drive-by` receipt written — zeros allowed
[ ] Retrospective ran OR explicit "RETRO: skipped (<reason>)" printed
[ ] Run: TSV line printed and appended to ~/.zuvo/runs.log

Enforcement: print the gate check as a checklist with actual [x] / [ ] marks so the user can audit. If any [ ] remains, loop back and complete it before emitting the NEXT STEPS block.

Validity Gate (REQUIRED — print BEFORE Run line, AFTER retro append + append-runlog)

What is machine-checked, and what is not. Three of these fields now have independent evidence behind them; the rest are self-attested and you should treat them that way.

RUN THIS after printing the gate (it is cheap and it is the point):

printf '%s' "$VALIDITY_GATE_BLOCK" | python3 scripts/verify-review-claims.py \
  --claims - --anchor "<the reviewed range or artifact slug>" --strict

On Codex, add --transcript "<actual current rollout JSONL>"; resolve the path from the current session's metadata. Default discovery supports Claude project logs only. Never synthesize a transcript or borrow another session's evidence to satisfy the gate. The verifier compares positive minimum-count claims and used --multi with recorded tool invocation evidence. Codex supports direct JSON calls and literal orchestration requests; unsupported evidence needed by a claim yields UNKNOWN (strict exit 2). A provable discrepancy still fails even when another claim is unknown. Invocation evidence does not prove successful execution, provider completion or review quality; those require the actual review artifacts.

Plus the two pre-existing mechanical backstops: the content-keyed artifact (pg_range_reviewed) and its adversarial proof file carrying >=2 REVIEW BY: lines (pg_artifact_proven).

Still self-attested (no verifier exists): the per-gate CQ/Q scores, the confidence numbers, the finding severities, and every not_required/N/A justification. Adding more forbidden phrasings does not change that — cite the transcript check and the artifact pair as evidence, never "the Validity Gate passed" on its own.

VALIDITY GATE
  triggers_held:
    diff_lines: <count>
    diff_type: [prod-only|test-only|mixed]
    language: <typescript|python|...>
    framework: <nextjs|nestjs|astro|hono|react|django|...|none>
    tier: <0|1|2|3>
  required_tool_calls:
    # VIOLATES_TRIGGER fires ONLY when the tool IS present in this build's surface
    # but was not called. `absent-in-build (<equivalent>: <result>)` = PASS (see
    # MANDATORY TOOL CALLS substitution map). NOT_CALLED on a present tool = violation.
    # A tool CALLED but erroring in-provider (`Transport closed`, provider crash) is
    # neither NOT_CALLED nor an infinite retry: per codesift-setup.md's transport
    # protocol, record `<tool>: call-failed(<error>) (<substitution-map fallback>:
    # <result>)` = PASS, and list the failure under SKIPPED STEPS.
    review_diff: [<N> findings across 9 checks | absent-in-build (audit_scan: <findings>) | NOT_CALLED — VIOLATES_TRIGGER]
    changed_symbols: [<N> symbols | absent-in-build (impact_analysis+get_file_outline: <result>) | NOT_CALLED — VIOLATES_TRIGGER]
    diff_outline: [<N> files outlined | absent-in-build (get_file_outline: <result>) | NOT_CALLED — VIOLATES_TRIGGER]
    impact_analysis: [<N> affected_tests / <N> blast | NOT_CALLED — VIOLATES_TRIGGER]
    scan_secrets: [<N> hits | absent-in-build (grep secret-scan: <count>) | NOT_CALLED — VIOLATES_TRIGGER]
    search_patterns: [<N> CQ8/CAP hits | NOT_CALLED — VIOLATES_TRIGGER]
    find_references: [<N> chains | not_required (no symbol cited) | NOT_CALLED — VIOLATES_TRIGGER]
    stack_specific (nest_audit/framework_audit/python_audit/etc.): [<result> | not_required | NOT_CALLED — VIOLATES_TRIGGER]
    framework_audit (Next.js ONLY): [<result> | not_nextjs | NOT_CALLED — VIOLATES_TRIGGER]
      # For a Next.js diff, framework_audit is the single-call-first requirement.
      # nextjs_route_map is a SUBSET (routes only) and does NOT satisfy it —
      # framework_audit also covers client-boundary, data-flow, and server-actions.
  audit_tree: [readonly(<sha7>) | live(<why the read-only worktree could not be created>)]
    # The auditors' root. `live` is an honest degraded value — the findings then race the fix loop
    # and the Working-Tree Staleness Check carries the weight alone. Claiming `readonly` without
    # having created the worktree is the same class as claiming a dispatch that never happened.
  tier2_subagents:   # TIER 2-3 only; for TIER 0-1 print "not_required (tier<2)"
    # Single-agent environments (Codex/Cursor/Antigravity — env-compat.md forbids
    # pipeline-stage thread dispatch there): SEQUENTIAL_CHECKPOINT(<role>, <evidence>)
    # is a PASS value for every role below and satisfies the Completion Gate
    # "DISPATCHED as sub-agents" item — the env-mandated checkpoint pass IS the
    # required result, not VIOLATES_TIER2. Adversarial coverage still required as usual.
    behavior_auditor: [DISPATCHED(<agent-return-marker>) | INLINE-SINGLE-AGENT-LOCK(<marker>) | not_required (no new prod files / tier<2) | NO_RETURN(<elapsed>) — VIOLATES_TIER2 | NOT_DISPATCHED — VIOLATES_TIER2]
    structure_auditor: [DISPATCHED(<agent-return-marker>) | INLINE-SINGLE-AGENT-LOCK(<marker>) | not_required (tier<3) | NO_RETURN(<elapsed>) — VIOLATES_TIER3 | NOT_DISPATCHED — VIOLATES_TIER3]
    cq_auditor: [DISPATCHED(<agent-return-marker>) | INLINE-SINGLE-AGENT-LOCK(<marker>) | NO_RETURN(<elapsed>) — VIOLATES_TIER2 | NOT_DISPATCHED — VIOLATES_TIER2]
    confidence_rescorer: [DISPATCHED(<agent-return-marker>) | INLINE-SINGLE-AGENT-LOCK(<marker>) | NO_RETURN(<elapsed>) — VIOLATES_TIER2 | NOT_DISPATCHED — VIOLATES_TIER2]
    # `DISPATCHED(<marker>)` requires a RETURNED result you actually used. A dispatch that
    # never came back is `NO_RETURN(<elapsed>)` and counts EXACTLY as NOT_DISPATCHED for every
    # rule below — it contributed no findings, so it bought no coverage, and the reason it is
    # missing does not change what the verdict may claim. This state existed in reality before
    # it existed in this table: on 2026-08-11 a TIER 3 SELF-REVIEW shipped a release with the
    # behavior auditor still running (it returned 12h later, with a real finding). Nothing
    # flagged it — the gate only recognised "not dispatched", so a dispatched-and-silent agent
    # fell through and the verdict read as full coverage. Before writing ANY value here, verify
    # each agent actually returned; "I sent three" is not "three reported".
    # DEGRADED is allowed ONLY when self_review_flag=no AND you print a one-line
    # [DEGRADED: <agent> skipped because <concrete reason>] — a DELIBERATE,
    # logged decision, never a silent omission. On SELF-REVIEW the sub-agents are
    # NON-NEGOTIABLE (author=reviewer bias is exactly what they mitigate): a
    # skipped sub-agent on self-review is NOT_DISPATCHED — VIOLATES_TIER2, no
    # degraded path. "adversarial covers it" is FALSE — sub-agents read the plan
    # + spec independently; adversarial sees only the diff.
  adversarial:
    passes_run: [<N> | 0 — VIOLATES_MANDATE]
    providers_used: [<provider1,provider2,...> | none]
    skip_reason: [n/a | single_provider_only | timeout | BLOCKED_CONTEXT_BUDGET | <other> — VIOLATES_MANDATE]
    # rate_limit is NOT a skip reason — per stall-recovery.md ("Rate-limit
    # is a RETRY condition, NEVER a quality lever"), a rate-limited adversarial pass
    # is RE-RUN across watchdog resumes until it completes, never recorded as a skip
    # or a degraded verdict. The only genuine no-retry unavailability reasons are
    # single_provider_only (only one model exists) and BLOCKED_CONTEXT_BUDGET (window
    # full) — those keep the degraded-coverage handling below; rate_limit does not.
    coverage_source: [fresh-this-run | same-session-same-commit(<artifact-paths>) | NONE — degraded]
    self_review_flag: [no | yes — used --multi | yes — DID_NOT_USE_--multi — VIOLATES_1.1]
  mutation_chain:   # Phase 4 step 4; FIX modes only — for --report-only print "not_required (report-only)"
    ran: [yes(<N> test files, <killed>/<total>) | not_required (<which trigger condition was false>) | NOT_RUN — VIOLATES_PHASE4]
    scope_source: [affected_tests | find_references | naming-convention | tests-touched-only]
    untested_files: [<count> — each one got a test written this run | 0]
    verdict_read_as: [clean | sampled(<N>) — total equalled a round plan, so the grade describes a budget | n/a]
    # `NOT_RUN` where the trigger held is the same class as a skipped adversarial pass: the one
    # gate that asks whether the tests would NOTICE a change did not ask. A bare `skipped` is not
    # a value here — name the false trigger condition or record NOT_RUN.
  backlog_deferral:
    recommended_applied: <count of localized RECOMMENDED fixed in Phase 4>
    recommended_deferred: [<count> all tagged NIT/structural-refactor(multi-file)/out-of-mode: blocking (blocking mode only) | <B-id or F-id> NO_VALID_DEFER_REASON — LOCALIZED-DEFER-DRIFT]
  postamble:
    retros_log_appended: [yes(bytes_added=N) | NOT_APPENDED — VIOLATES_REQUIRED_POSTAMBLE]
    retros_md_appended: [yes(entry_count=N) | NOT_APPENDED — VIOLATES_REQUIRED_POSTAMBLE]
    verify_audit_pass: [yes(<verified>/<total> findings) | NOT_RUN | REJECTED]
  gate_status: [PASS | FAIL — <which gates missing>]

If gate_status = FAIL, override the VERDICT to INCOMPLETE regardless of finding count, append [VALIDITY GATE FAIL] to the Run line NOTES column, and record the process item subject:review-incomplete (Validity-gate process items below).

Adversarial-skip violation handling (NEW — closes the 2026-05-28 escape-hatch loophole): If adversarial.skip_reason is set to anything OUTSIDE the whitelist {n/a, single_provider_only, timeout, BLOCKED_CONTEXT_BUDGET} — including but not limited to "context budget", "already mechanically detected", "self-review low value", "small diff", "documented honestly" — then:

  1. Set gate_status = FAIL — adversarial skip outside whitelist (<reason>).
  2. Override VERDICT to INCOMPLETE regardless of finding count.
  3. Append [ESCAPE-HATCH-VIOLATION:adversarial:<reason>] to the Run line NOTES column.
  4. Record the process item subject:review-escape-hatch (Validity-gate process items below) and put the verbatim quote of the skip rationale in the report beside its F-id (so the pattern is auditable).

Same handling if self_review_flag = yes — DID_NOT_USE_--multi (section 1.1 mandates --multi on self-review).

Rate-limit is NOT a degraded path — it is a RETRY (per stall-recovery.md). If a mandatory gate (fresh adversarial --multi, or any TIER-2 sub-agent fan-out) cannot complete this turn because of a rate-limit / API-error, do NOT record a skip, do NOT downgrade to CONDITIONAL, do NOT cite coverage. End the turn; the watchdog resumes; RE-RUN the exact gate. Repeat until it actually runs. A rate-limited review is still-running, not done-degraded. The verdict is computed only once every mandatory gate has truly run (or is proven by a same-commit artifact, below). There is no rate_limit skip-reason and no rate-limit CONDITIONAL — that path is removed precisely because it became the universal excuse.

Genuine capability-limit handling (NOT rate-limit — these do not clear by retrying). The only honest non-retry unavailability reasons are single_provider_only (only one model is configured, so cross-model --multi truly cannot run) and BLOCKED_CONTEXT_BUDGET (the context window is full). For these, check coverage_source:

  • same-session-same-commit(<artifacts>) — the dimension WAS covered by a real pass on the EXACT commits under review (e.g. adversarial artifacts in zuvo/context/adversarial-task-*.txt returning 0 open MUST-FIX). Verify the artifacts exist on disk AND reference these commit SHAs. If so, that dimension is legitimately covered → it does NOT degrade the verdict. (The ONE honest reuse — real coverage on the same code, not a lighter substitute.)
  • NONE — no fresh run and no same-commit artifact, and the limit genuinely cannot be retried away. Then: verdict downgrades to CONDITIONAL; print [DEGRADED-COVERAGE: <gate> not run — <single_provider_only|BLOCKED_CONTEXT_BUDGET>; re-validate before merge] + append [DEGRADED-COVERAGE:<gate>:<reason>] to the Run line NOTES; record a re-validation obligation (the process item subject:review-revalidate, Validity-gate process items below, + the NEXT STEPS "run /zuvo:review <range> to clear the CONDITIONAL"); gate_status stays PASS with degraded=<gate> noted.

The point: a clean APPROVE requires every mandatory gate to be either freshly run, proven by a same-commit artifact, or (only for a true capability limit) honestly degraded to CONDITIONAL. timeout (exit 124, ALL providers timed out) IS a legitimate recorded skip — it is a provider-side dead end, not a retryable rate-limit. Rate-limit is none of these — it is retried until the gate runs. n/a (no production logic to review) is the only no-coverage-needed path.

TIER 2 sub-agent skip handling (NEW — closes the silent-degradation / context-fatigue drift gap): This is the dominant real-world failure: on a long session the lead does CQ/behavior/confidence scoring inline as "lead" instead of dispatching the sub-agents, then rationalizes it after the fact ("adversarial covers it", "scoped check instead"). That is drift, not a decision. If any tier2_subagents.* reads NOT_DISPATCHED — VIOLATES_TIER2 or NO_RETURN(<elapsed>):

  1. Set gate_status = FAIL — tier2 sub-agent(s) not dispatched (<which>).
  2. Override VERDICT to INCOMPLETE (the inline "lead" scoring does NOT substitute — sub-agents read plan+spec independently and are the second pair of eyes; on self-review they are the ONLY independent eyes).
  3. Append [TIER2-SUBAGENT-SKIP:<which>] to the Run line NOTES.
  4. Record the process item subject:review-tier2-skip (Validity-gate process items below) and put the verbatim rationalization quote in the report beside its F-id. A DEGRADED line ([DEGRADED: <agent> skipped because <reason>]) is acceptable ONLY for non-self-review AND only as a printed, deliberate choice — never the default. On self-review there is NO degraded path.

NO_RETURN is the same failure wearing an innocent face, and it is the one that actually shipped. The drift above is a lead who chose not to dispatch; NO_RETURN is a lead who dispatched correctly, never noticed the agent did not come back, and wrote the verdict from whoever happened to reply. Same outcome — a tier claiming coverage it does not have — but no rationalization to catch it by, because nothing was rationalized. Use NO_RETURN(<elapsed>) and route it through steps 1-4 above (note [TIER2-SUBAGENT-NO-RETURN:<which>:<elapsed>] instead, and put the elapsed time in the report beside the item's F-id rather than a quote). Two practical rules:

  • Before writing the Validity Gate, count RETURNS against dispatches. If you dispatched 3 and can name findings from 2, the third is NO_RETURN — not "presumably fine".
  • Do not wait indefinitely for it. Ask the outstanding agent for whatever it has, then finalize with the honest value. A 12-hour agent that returns one real finding after the release is worth reading; it is not worth blocking on, and it is never worth assuming. (Measured 2026-08-11: exactly that, and the release went out on the overstated version.)

Harness single-agent lock is NOT a skip — run the roles INLINE. The rule above targets drift: a lead that COULD dispatch and chose not to, then rationalized it. It does not target a harness that forbids dispatch outright (Cursor, Antigravity — see env-compat.md; Codex is NOT one of them, it runs review roles inline because they are same-model, and dispatches mechanical workers). Treating those as the same thing made TIER 2+ unpassable on that harness: the skill mandated the one action the environment prohibits, so every run was INCOMPLETE no matter how well it was done. Three retros this week reported exactly that conflict.

Where dispatch is impossible, run each role's prompt INLINE as a distinct pass — read agents/<role>.md, execute it against the diff WITHOUT consulting your own earlier scoring, and record it as such:

behavior_auditor: INLINE-SINGLE-AGENT-LOCK(<marker>)
cq_auditor:       INLINE-SINGLE-AGENT-LOCK(<marker>)

INLINE-SINGLE-AGENT-LOCK satisfies the gate but caps the verdict at degraded:same-model — it is the same model doing both passes, so it is weaker than dispatch and must never be reported as strict. Cross-model independence still comes from the adversarial rotation, which is unaffected by the lock. This carve-out applies ONLY when the harness genuinely forbids dispatch. If dispatch is available and you scored inline anyway, that is the drift above and stays NOT_DISPATCHED — VIOLATES_TIER2.

Localized-deferral drift handling (NEW — closes the "RECOMMENDED → defer" reflex that defers things that should have been fixed in-loop): The recurring failure is the lead reflexively deferring a localized, single-file RECOMMENDED fix (add try/catch, add a guard, add a missing affordance) because it is "only RECOMMENDED" — conflating merge-severity with fix-scope. The backlog then accretes one-line fixes that the FIX-AUTO default already mandates applying. The gate applies in the FIX modes and to DEFERRED RECOMMENDED items only: a RECOMMENDED kept in the report (REPORT, 51-100) that the run leaves unfixed. A RECORD finding (26-50, excluded from the report) is recorded, is not a deferral and needs no defer-reason; under --report-only nothing is applied, every unfixed finding is recorded, and the gate does not apply. If any deferred RECOMMENDED item carries a defer-reason OUTSIDE the whitelist {NIT, structural-refactor (multi-file), out-of-mode: blocking} — or carries none, or carries out-of-mode: blocking in a run whose mode is not blocking — then:

  1. Set gate_status = FAIL — localized RECOMMENDED deferred (<B-id or F-id>).
  2. Override VERDICT to INCOMPLETE and route the mis-deferred item(s) into Phase 4 NOW (do not emit REVIEW COMPLETE with them still unfixed). A fixed item gets no backlog-add call. Only if an open entry was recorded for it earlier (a --report-only pass, an earlier run) is that entry closed, as backlog-protocol.md "Filing from a skill", Closing, not removing says (tick it, append [FIXED <sha7>]; the archive helper moves it).
  3. Append [LOCALIZED-DEFER-DRIFT:<B-id or F-id>] to the Run line NOTES. A multi-file structural refactor stays deferred (recorded via backlog-add) with its recipe — that is correct, not drift. The test is fix-scope (one file/symbol → fix; cross-cutting restructure → defer, recorded via backlog-add), never severity tier.

Validity-gate process items. Each gate above that says "record the process item subject:review-<gate>" makes one call: ~/.zuvo/backlog-add --repo "$R" --class PROCESS --conf 90 --source review --target subject:review-<gate> --title "$T" (backlog-protocol.md "Filing from a skill", Process item). T is the gate's fixed title, the same words on every run, so a repeat bumps the one record:

<gate>T
incompletereview validity gate failed, verdict INCOMPLETE
escape-hatchadversarial pass skipped outside the whitelist
revalidatereview coverage degraded, re-validate before merge
tier2-skiptier 2 sub-agent not dispatched or not returned

Assign "$R" and "$T" before the call, as "Free text goes through a variable" in that section says; an empty value you pass is a bug, and backlog-add refuses a missing required field (REFUSED missing-field:<f>). The item is kept in the findings ledger, not ticketed: name its F-id in the report with this run's reason (the quote, the elapsed time), which never goes in the title.

Print this Validity Gate AFTER the retro append and ~/.zuvo/append-runlog call (so postamble fields can be filled with yes(verified)).

NEXT STEPS Block

REVIEW COMPLETE -- <VERDICT>, <N> issues found.
DEPLOYMENT RISK: <RISK LEVEL> -- <deploy strategy>
Run: <ISO-8601-Z>	review	<project>	<CQ>	<Q>	<VERDICT>	<TASKS>	<DURATION>	<NOTES>	<BRANCH>	<SHA7>	<INCLUDES>	<TIER>

Default (FIX-AUTO) — do NOT stop here; auto-proceed to Phase 4. Unless --report-only (or an explicit fix/blocking/auto-fix/tag/status) was passed, the review does not present a menu and wait — it announces what it will apply and goes straight into the fix:

AUTO-FIX: applying <M> MUST-FIX + <R> RECOMMENDED (localized/high-confidence) → Phase 4.
DEFERRED, recorded via backlog-add: <K> NIT (every NIT above confidence 25) + <S> structural-refactor RECOMMENDED.
  <for each deferred RECOMMENDED> R-<n> (its defer-reason is printed in the FIX summary)

Backlog-deferral whitelist (HARD — closes the silent-deferral drift gap). The DEFAULT for a RECOMMENDED finding is fix it now in Phase 4, not a deferral. A RECOMMENDED finding may be deferred (recorded via backlog-add) ONLY if it matches one of exactly three categories, and you MUST print its defer-reason tag in ONE place, the FIX summary (Phase 4 step 3), in every FIX mode; the record carries it in --evidence "$E". The whitelist governs RECOMMENDED findings only: a NIT-tier finding needs no defer-reason, and every NIT above 25 is recorded (Backlog Persistence):

  • NIT — style/readability, zero functional impact (already its own tier; would only apply under explicit fix/FIX-ALL).
  • structural-refactor (multi-file) — extract a module, split a god-file, invert a dependency, restructure a layer. This is zuvo:refactor territory and gets a resolution recipe per Phase 2.
  • out-of-mode: blocking — ONLY when the run's mode is blocking (FIX-BLOCKING): a localized RECOMMENDED the mode leaves unfixed because the user chose MUST-FIX only. In FIX-ALL and FIX-AUTO it is never a valid reason.

Everything else is localized and gets fixed in-loop, full stop. A single-file, single-symbol reliability/correctness/affordance fix — add a missing try/catch around a transaction, add a null guard, tighten a WHERE clause, add a missing scope indicator on a button, reject invalid input — is localized + high-confidence by construction and is NEVER deferrable. "It's only RECOMMENDED so I'll backlog it" is the exact drift this gate forbids: severity tier (MUST-FIX vs RECOMMENDED) decides merge-blocking, it does NOT decide fix-now vs defer. The defer decision is made on the scope of the fix (localized → fix; multi-file refactor → defer, recorded via backlog-add), not on severity. If you cannot name the deferred item's category as NIT or structural-refactor (multi-file) (or, in blocking mode only, out-of-mode: blocking), it is NOT deferrable — route it to Phase 4. In FIX-ALL and FIX-AUTO a localized RECOMMENDED left unfixed is never recorded as a deferral; it fails the Localized-deferral drift gate, which routes it to Phase 4.

Then continue to Phase 4 immediately (no user turn). If there are ZERO MUST-FIX and ZERO applicable RECOMMENDED, print AUTO-FIX: nothing to apply (only NIT/structural — recorded) and finish. Only when --report-only was passed do you instead print the menu and stop (that pass has recorded its unfixed findings, so when the user then answers "fix", "blocking" or "auto-fix", Phase 4 closes the entries of the findings it fixes, as Closing, not removing says):

NEXT STEPS: "fix" (all) | "blocking" (MUST-FIX only) | "auto-fix" (zuvo:build) | "skip"

Append the Run line via the retro-gated wrapper (NOT direct >> runs.log):

printf '%b\n' "$RUN_LINE" | ~/.zuvo/append-runlog

The wrapper:

  • Verifies the matching RETRO: entry in retros.log (skill+project). Missing → exit 2.
  • Runs ~/.zuvo/verify-audit on the report at memory/reviews/<date>-<scope>.md. Findings without file:line citations → exit 2.
  • On both pass: appends to runs.log and prints confirmation.

If the wrapper exits non-zero: do NOT manually append to runs.log. Fix the cause and re-run.


Phase 4: Execute (FIX-AUTO / FIX-ALL / FIX-BLOCKING / AUTO-FIX)

Comment base, before the fix loop writes. Before the fix loop's first write, and before AUTO-FIX invokes zuvo:build, print COMMENT_BASE=<sha> into the transcript, resolved by git rev-parse -q --verify HEAD || git hash-object -t tree /dev/null; post-fix step 1b passes that printed literal. On a dirty tree — git status --porcelain --untracked-files=all prints anything at fix start — the base is a snapshot of the current tree instead: git stash create prints a commit of the tracked changes without touching the tree or the stash list, and the resolver above applies only when it prints nothing. Only lines the fix loop changes are then authored, not the developer's uncommitted WIP; untracked WIP files are not in the snapshot, so one the fix loop edits counts whole.

Read and follow the fix loop protocol from ../../shared/includes/fix-loop.md.

Input:
  FINDINGS: [R-N findings to fix, per mode]
  SCOPE_FENCE: [allowed files from triage]
  MODE: FIX-AUTO | FIX-ALL | FIX-BLOCKING | AUTO-FIX
  • FIX-AUTO (default): apply MUST-FIX + RECOMMENDED that are localized + high-confidence (confidence ≥ ~60 from Phase 2). Do NOT apply NIT or structural-refactor RECOMMENDED — those are recorded through backlog-add (Backlog Persistence, Generic write; the structural-refactor defer rule in Phase 2 already routes them there). This is the "I always click fix anyway" default — minus the low-value churn and the multi-file refactors that belong to zuvo:refactor.
  • FIX-ALL: apply MUST-FIX + RECOMMENDED + NIT (explicit fix — you want everything incl. nits).
  • FIX-BLOCKING: apply MUST-FIX only. A localized RECOMMENDED it leaves unfixed is a legitimate deferral: it is recorded through backlog-add (Backlog Persistence, Generic write) with the defer-reason out-of-mode: blocking (the Backlog-deferral whitelist): assign "$E" the text defer-reason: out-of-mode: blocking before the call, with "$R", "$F" and "$T", as "Free text goes through a variable" in that section says.
  • AUTO-FIX: dispatch zuvo:build with MUST-FIX findings as context (closed-loop, max 1 cycle).

Post-fix gate (MANDATORY — auto-applied fixes can over-correct)

Applying fixes without re-checking is how a "fix" silently becomes a regression (2026-05-30: an auto-applied resize fix removed the width floor → the dock could collapse to 0px and aria-valuemin > valuemax; caught only by the next adversarial pass). After applying ANY fixes, before declaring done:

  1. Verify — run the project's test/typecheck/build. A fix that leaves the suite red is reverted, not shipped.
    • Pre-existing failure in an UNTOUCHED workspace is not your regression (and reverting a good fix over it is the actual harm). If the full suite fails only outside the reviewed file set: re-run that workspace once to confirm it reproduces, then compare the failing files against the reviewed diff. A reproducible failure in files this review never touched does NOT invalidate a green targeted suite + typecheck — but the verdict MUST disclose it: verification debt: <workspace> <N> failing (pre-existing, out-of-scope), with the exact files/counts. Silence here is the escape hatch: an undisclosed "the suite was already broken" is indistinguishable from a fix that broke it. If the failure touches ANY reviewed file, it is yours — revert or fix, no debt option. 1b. Comment pass — read ../../shared/includes/comment-pass.md and run its whole sequence — mechanical scope, CHECK lines, ledger check, exit valve — with COMMENT_SCOPE = every file the fix loop created or modified, production and test, plus every file a fix creates or edits, checked against git status --porcelain --untracked-files=all — a check on the record, never a source of paths (files other sessions changed stay out); and COMMENT_BASE = the COMMENT_BASE=<sha> printed at the start of Phase 4, passed as a literal — not HEAD, because in AUTO-FIX zuvo:build's 4.6 commit would hide the fix loop's lines from a HEAD base. If it is missing (a resume): the same resolver (git rev-parse -q --verify HEAD || git hash-object -t tree /dev/null) only when the fix loop has committed nothing (AUTO-FIX: zuvo:build's 4.6 commit counts as the fix loop's), otherwise print [GATE: comment-pass] BLOCKED rc=2 base unknown; never git stash create on a resume, which would snapshot the fix loop's own edits. This step always runs, AUTO-FIX included, over every file the fix loop created or modified. One run, each path quoted, from the repository root:
    COMMENT_BASE=<sha printed at the start of Phase 4>
    rc=0; ~/.zuvo/comment-audit --base "$COMMENT_BASE" --files "<fixed-file-1>" "<fixed-file-2>" --skill review || rc=$?
    • rc 1 → fix every finding in-run and re-run until rc 0 — no cap, no backlog; history moves into the step 3 commit message. The one exception is the exit valve: findings that cannot be fixed without harming the code and exceed the justification cap → print [GATE: comment-pass] BLOCKED rc=1 ids=<id,…> <reason> and stop for a human.
    • rc 0 → settle each CHECK claim once by TEST-OR-GO and list it as CHECK settled: <file:line> test|removed|softened. A settling edit (a test added, a number removed) means one more helper run, and the marker takes that last clean run's id. Confirm the ledger with the include's awk lookup: a row for every path in scope (no row → [GATE: comment-pass] BLOCKED rc=0 no ledger row for run=<id>; a scoped path without a row → re-run over the full scope), then print [GATE: comment-pass] PASS run=<id> files=<n> justified=<k>[ ids=<id,…>][ env=<NAMES>] or N/A (run=<id> no audited source); N/A (no files written) is printed only when the scope is empty (include step 1), with no run. If the pass added a test or changed code, re-run step 1.
    • any other rc → fix the invocation; if it cannot be fixed, print [GATE: comment-pass] BLOCKED rc=<n> <reason> and do not commit or print the FIX-COMPLETE block.
    • Recheck: if any file in scope was changed or created after the last clean run (step 2's fixes, anything), re-run this step over the grown scope before step 3's git add, and before the commit of any file a later step writes (step 4's gap tests); the marker carries the new run id.
  2. Adversarial re-validation — run one cross-provider adversarial pass on the FIX diff (~/.zuvo/adversarial-review --mode code on the applied changes). It must converge (no new CRITICAL): a new CRITICAL introduced by a fix is itself fixed (cap 3 passes per adversarial-loop.md), and each residual non-CRITICAL is recorded through backlog-add (Backlog Persistence, Generic write). Do NOT print the FIX-COMPLETE block while a fix-introduced CRITICAL is open.
  3. Commit only after 1+1b+2 pass. Name the applied findings and the deferred ones in the FIX summary, by finding id (R-<n>), each deferred RECOMMENDED with its defer-reason; their B- or F-ids appear in the final report's recorded list (Backlog Persistence). This commit never stages a wrote= path: those go into the closing commit (Closing commit for backlog-add's writes, below). fix-loop.md's ## Commit happens here, at step 3 — never before 1b and 2 have passed.
  4. Mutation-test the tests that cover what these commits changed — see the next section. It runs AFTER the commit, so its scope is a committed fact rather than a moving tree.

This gate is what makes auto-fix safe to default: the user never has to eyeball each change, because the verify + adversarial pass catch an over-correction the way a human glance would.

Step 4 — chain into zuvo:mutation-test (MANDATORY when the trigger holds)

A green suite says the tests RAN. It does not say they would have NOTICED. That second question is the only one mutation testing answers, and nothing else in this pipeline asks it: the review's own gates score the diff, the adversarial pass reads it, and a test that asserts nothing passes all of them. Measured across 785 mutation-test runs on this machine: 180 reported concrete gaps closed against 9 that found nothing — when it is pointed at freshly written or freshly touched tests, it earns its cost.

This step used to be a thing the user remembered to type. Chaining it here is the same rule as ../../shared/includes/ → auto-chain-after-audit: a skill that has just changed code INVOKES the follow-up skill, it does not print a suggestion and stop.

Trigger (all three, else skip):

  • the fix loop committed at least one change (step 3 produced a commit), AND
  • that commit touched at least one production file or at least one test file, AND
  • the project has a test runner (package.json scripts, pyproject.toml, a Makefile target — the same detection zuvo:mutation-test Phase 0 does; if it finds none, skip and say so).

Scope — this is the part that goes wrong if you improvise it. The target is the tests, not the production files: every test file that was written or changed by this run, PLUS the existing test files that cover the production files created or changed by the commits under review. Derive it, do not guess:

# Production + test files from the commits this review covered, including the fix commit.
CHANGED=$(git diff --name-only "${REVIEWED_FROM}..HEAD")
TESTS_TOUCHED=$(printf '%s\n' "$CHANGED" | grep -E '\.(test|spec)\.[jt]sx?$|(^|/)(tests?|__tests__)/|_test\.py$|(^|/)test_[^/]+\.py$' || true)
PROD_CHANGED=$(printf '%s\n' "$CHANGED" | grep -vE '\.(test|spec)\.[jt]sx?$|(^|/)(tests?|__tests__)/|_test\.py$|(^|/)test_[^/]+\.py$' | grep -E '\.(ts|tsx|js|jsx|py|go|rs|rb|php)$' || true)

For each file in PROD_CHANGED, find the tests that cover it — impact_analysis(since=…)'s .affected_tests when CodeSift is available (it already ran in Phase 0.5, reuse that result rather than re-querying), otherwise find_references on the file's exported symbols filtered to the test glob, otherwise the project's own convention (foo.ts → foo.spec.ts, src/x/y.py → tests/x/test_y.py). Union that with TESTS_TOUCHED; pass the union as the scope.

A production file in PROD_CHANGED with no covering test is NOT a scope entry — it is a finding. Record it as untested: <file> and hand it to step 4's fix phase below, which writes the missing test. Silently dropping it turns "no tests to mutate" into a clean-looking result, which is the same class as a total=0 run reading as a perfect score.

Skill(skill="zuvo:mutation-test", args="<space-separated test files> --runner auto")

Pass the files, never a bare directory: a directory re-mutates everything the last twenty commits touched and the run stops being proportionate to the review.

Then close what it finds — in this run, not in the backlog. zuvo:mutation-test already fixes the tests whose gaps let a mutation survive, and that behaviour is the point of chaining it here. Two obligations this skill adds on top:

  • Every untested: file from the scope step gets a test written now (the mutation runner cannot find a gap in a test that does not exist).
  • A surviving mutant that reveals a production bug — not a test gap — is a MUST-FIX finding of THIS review: fix it, re-run the two gates above on the new diff, and stack it as its own commit.

Reading its verdict honestly. zuvo:mutation-test reports a score and a grade; a grade over a tiny denominator is a statement about a budget, not about the file. Measured on the same 785 runs: the mutant totals spike hard on round numbers — total=10 appears 57 times against 7 for total=9 and 1 for total=11, and the same spike sits on 20, 25, 30 and 50 — because --max bounds the LLM lane while the native runner mutates exhaustively and structurally cannot land on a round number. So: if the reported total equals the plan and the plan is round, say sampled(<N>), not clean. 129 of 712 passing runs (18%) are in that state.

Record the outcome in the Validity Gate (mutation_chain: field) and in the FIX summary. A skipped step must name which of the three trigger conditions was false — not_required (no commit from the fix loop) is a fact; a bare skipped is not.

Note: When FIX/BLOCKING/AUTO-FIX mode is active, Phase 1.6 adversarial runs in FIX variant (sequential providers validate and fix between passes). The fix-loop.md below handles primary audit findings. Adversarial findings discovered and fixed during Phase 1.6 do NOT appear in the fix-loop — they are already resolved.

Review-Specific Wrapper

After fix-loop.md completes:

  1. Git tag: git tag review-YYYY-MM-DD-<short-slug> on the fix commit. This is distinct from the per-commit reviewed/<hash> tags created in Phase 3 — that set marks what was audited, this one marks what was fixed. Both can coexist on the same repo.
  2. Post-Execute block:
===============================================================
EXECUTION COMPLETE
===============================================================
FILES MODIFIED: [list]
FIXED: [list of R-N items fixed]
TESTS WRITTEN: [list]
VERIFIED: Tests PASS, Types PASS
Commit: [hash] -- [message]
Tag: [tag name]
===============================================================
  1. Unfixed items from FIX-BLOCKING or a partial fix — a MUST-FIX a partial fix leaves open, every NIT above 25, and RECOMMENDED with a whitelist defer-reason only (structural-refactor (multi-file); in blocking mode also a localized one, out-of-mode: blocking): each recorded through backlog-add (Backlog Persistence, Generic write). In FIX-ALL and FIX-AUTO a localized RECOMMENDED left unfixed is not recorded: it fails the Localized-deferral drift gate.

Closing commit for backlog-add's writes

This is the run's last action: it comes after Phase 4 (in a FIX mode), after the Validity Gate and its process items, and so after every backlog-add call of the run (Backlog Persistence, the drive-by step, the validity-gate process items). It follows backlog-protocol.md "Filing from a skill", Committing what backlog-add wrote, against the backlog-snapshot: Phase 0 printed:

  • Standalone, in a FIX mode (fix, blocking, auto-fix, the FIX-AUTO default): make the one closing commit chore(backlog): record findings of review run with the run's wrote= paths, whether or not Phase 4 committed anything else. With no path left, there is no commit (none: nothing to stage).
  • Invoked by another skill (zuvo:ship, zuvo:execute, zuvo:refactor), or under --report-only, which makes no commits: stage nothing and print one line in the report, Backlog writes: <path>, … (or Backlog writes: none). The outer skill's closing commit carries them.

The closing commit holds only memory/ paths, never a reviewed file, so it is the one commit allowed after Report Persistence (the ORDERING paragraph there).

Staged Scope Stash Management (B5 fix)

When scope is staged:

1. git stash --keep-index        # save unstaged changes
2. Run fix-loop.md               # applies fixes, tests, commits
3. git stash pop                 # ALWAYS runs, even if fix-loop fails

Treat stash pop as a finally block. If fix-loop aborts, pop the stash and report the failure.

Closed-Loop Auto-Fix

When mode is AUTO-FIX:

  1. Collect MUST-FIX findings into a fix list
  2. Dispatch zuvo:build with scope = affected files, task = fix descriptions, mode = --auto
  3. After build completes, auto-run zuvo:review on the fix diff (TIER 1 minimum)
  4. If re-review finds new MUST-FIX: report (do NOT loop -- max 1 cycle)
  5. If clean: CLOSED-LOOP COMPLETE -- all MUST-FIX resolved

Batch Mode (batch <file>)

Process a queue of commits: review, fix, tag -- one at a time, zero interactive stops.

Input Format

One commit hash per line, optionally with description:

ecbf4351c | perf: memoize productById Map
57a26ea14 | test: broaden cross-app coverage

Lines starting with # are comments. Lines with - [x] or - [!] are skipped (resume mode).

Enrich Queue

Validate each hash (git cat-file -t). Rewrite file with - [ ] <hash> | <msg> | +X/-Y | N files.

Per-Commit Loop

For each [ ] entry: read diff -> triage -> audit at full depth -> fix (FIX-ALL) -> tag (reviewed/<hash>) -> update queue. TIER 3 in batch: run full review inline (sequential agents). Do NOT skip or redirect. If fix breaks tests, revert and mark [!]. Every [x] must include a code observation.

Resume: [x] skip, [!] skip, [ ] process. Completion: print totals and queue path.


Utility Modes

tag

No audit. tag <queue-file> ticks (- [x]) every queue entry (Batch Mode's format) whose commit SHA is an ancestor of HEAD (git merge-base --is-ancestor <sha> HEAD) and prints N ticked, M remaining -> STOP. Without a file it prints usage and stops. It never edits memory/backlog.md, which holds findings, not commits.

mark-reviewed

No audit. Create reviewed/ tags:

  • zuvo:review mark-reviewed -> all commits on branch (merge-base..HEAD)
  • zuvo:review mark-reviewed HEAD~3 -> last 3 commits
  • STOP after tagging.

status

No audit. Show unreviewed commits:

  1. Build set of reviewed hashes from reviewed/* tags
  2. Walk last N commits (default 100, configurable with --depth N)
  3. Print unreviewed: Total: N | Reviewed: X | Unreviewed: Y -> STOP.

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.