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
- microsoft/veronatheir own version, first seen Nov 8, 2026
- microsoft/FluidFrameworktheir own version, first seen Nov 8, 2026
- charliee1w/consolidation-memorytheir own version, first seen Nov 8, 2026
- jisuzzzz/haechitheir own version, first seen Nov 8, 2026
- apokamo/kajitheir own version, first seen Nov 8, 2026
- dirien/yet-another-agent-harnesstheir own version, first seen Nov 8, 2026
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.
dzhngSkillclose-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
dzhngSkilldesign-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
LambenthanSkilldaily-arxiv
每日从 arXiv 拉取新论文,过滤相关性,auto-ingest 高优先级论文,检测 SOTA 更新
Also in 5 projects that use this
LambenthanSkillexp-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
Other versions192 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 192
- garrytan/gstack★ 141ksame name
- ansible/ansible★ 71ksame name
- docling-project/docling★ 70ksame name
- traefik/traefik★ 65ksame name
- ClickHouse/ClickHouse★ 51ksame name
- temporalio/temporal★ 24ksame name
- tisfeng/Easydict★ 15ksame name
- juicedata/juicefs★ 15ksame name
- elie222/inbox-zero★ 13ksame name
- gitkraken/vscode-gitlens★ 10ksame name
182 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository58 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- a11y-auditSkill
- agent-benchmarkSkill
- api-auditSkill
- architectureSkill
- backlogSkill
- benchmarkSkill
- brainstormSkill
- buildSkill
- canarySkill
- ci-auditSkill
- code-auditSkill
- container-auditSkill
- content-auditSkill
- content-expandSkill
- content-fixSkill
- content-migrationSkill
- context-auditSkill
- db-auditSkill
- debugSkill
- dependency-auditSkill
- deploySkill
- design-reviewSkill
- designSkill
- docsSkill
- env-auditSkill
- executeSkill
- fix-testsSkill
- geo-auditSkill
- geo-fixSkill
- incidentSkill
- infra-auditSkill
- leadsSkill
- mutation-testSkill
- pentestSkill
- performance-auditSkill
- planSkill
- presentationSkill
- profile-sessionSkill
- receive-reviewSkill
- refactor-radarSkill
- refactorSkill
- release-docsSkill
- retroSkill
- security-auditSkill
- seo-auditSkill
- seo-fixSkill
- shipSkill
- skill-evalSkill
- structure-auditSkill
- test-auditSkill
- tests-performanceSkill
- ui-design-teamSkill
- using-zuvoSkill
- worktreeSkill
- write-articleSkill
- write-e2eSkill
- write-testsSkill
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
- Distinctive: line 134
After CodeSift setup, read the git diff.
- Very specific: line 206
After deriving REVIEWED_FROM and REVIEWED_THROUGH for any commit-based scope (new, HEAD~N, abc123..def456, batch entry), validate the range …
- Very specific: line 212
If this returns no commits, STOP and print: [RANGE-ERROR] Empty commit range.
- Distinctive: line 215
Do NOT auto-swap the range.
- Very specific: line 257
Before tier selection, compute PROD_LOGIC_LINES from changed non-test production hunks after stripping diff headers, blank lines, and …
- Distinctive: line 298
Check the diff for these markers.
- Distinctive: line 310
Every review MUST compute a deployment risk score.
- Distinctive: line 354
Use the deterministic preload helper FIRST: Before issuing any ToolSearch, run:
- Distinctive: line 405
After the review report is written, the review is NOT complete until:
- Very specific: line 413
If you reach REVIEW COMPLETE and stop without calling append-runlog: the review is INVALID regardless of finding count.
- Distinctive: line 424
Check whether CodeSift tools are available (the helper above already verified this)
- Very specific: line 426
If unsure whether the repo is indexed: index_status()
- Very specific: line 427
If not indexed: index_folder(path=<project_root>)
- Distinctive: line 431
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 …
- Very specific: line 497
Run it at the end of Phase 3 and on every abort path.
- Very specific: line 539
If the diff adds/removes exports and knip is available: npx knip --reporter json 2>/dev/null.
- Very specific: line 549
If any pre-compute call fails, set PRECOMPUTED_DATA=partial, log the failed operation in SKIPPED STEPS, and continue.
- Very specific: line 566
If the repo uses both *.spec.* and *.test.*, run the test-reference step for both globs and merge the results.
- Very specific: line 571
Do NOT use patterns, complexity, or file_outlines inside codebase_retrieval
- Distinctive: line 616
TIER 0-1: No agents. Lead performs all analysis inline using CodeSift pre-computed data (Phase 0.5) if available.
- Distinctive: line 654
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 …
- Very specific: line 679
When reviewing a commit range rather than the current working tree, verify that HEAD has not already changed a file after REVIEWED_THROUGH …
- Distinctive: line 685
If the file changed after the reviewed range:
- Distinctive: line 687
read the current file before reporting
- Very specific: line 706
If adversarial-review not in PATH: ~/.zuvo/adversarial-review (stable.
- Very specific: line 715
If it is NOT an ancestor: either merge/rebase first, or review $(git merge-base "$REVIEWED_FROM" "$REVIEWED_THROUGH")..$REVIEWED_THROUGH …
- Very specific: line 719
Self-review escalation: If SELF-REVIEW marker set in 1.1, pass --multi flag.
- Very specific: line 721
Status handling (D2+D3+D4, 2026-05-17): When the script exits non-zero or returns non-ok JSON status, branch:
- Very specific: line 831
Record the verdicts: after triage: every finding is fixed, rejected (a false positive) or deferred — record each by its ID: in ONE call, …
- Very specific: line 852
When deterministic facts are already known (for example: lockfile present in diff, package ships bundled types, file already patched at …
- Very specific: line 853
If PROD_LOGIC_LINES = 0 and SELF-REVIEW is not set, skip adversarial and log: [CROSS-REVIEW] Skipped — no production logic changed.
- Very specific: line 999
Write it only on success — a crashed/aborted review must leave no artifact, so a failed run never grants pipeline coverage.
- Very specific: line 1026
Run knowledge-curate.md (if loaded): WORK_TYPE="review", CALLER="zuvo:review", REFERENCE=<commit range or "staged">.
- Very specific: line 1030
Follow ../../shared/includes/drive-by-findings.md with <skill> = review: backlog the security, bug, size, duplication, complexity, …
- Very specific: line 1038
Follow ../../shared/includes/followup-ideas.md with <skill> = review: append genuine new ideas to memory/ideas.md at the MAIN checkout root …
- Very specific: line 1046
Follow the retrospective protocol from retrospective.md.
- Distinctive: line 1049
If the gate check skips, you MUST print one of:
- Very specific: line 1057
Before printing REVIEW COMPLETE or the NEXT STEPS block, verify every item below.
- Very specific: line 1200
If gate_status = FAIL, override the VERDICT to INCOMPLETE regardless of finding count, append [VALIDITY GATE FAIL] to the Run line NOTES …
- Distinctive: line 1235
Do not wait indefinitely for it: Ask the outstanding agent for whatever it has, then finalize with the honest value.
“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.
Files
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)
| Include | prod-only | test-only | mixed |
../../shared/includes/env-compat.md | Full | Full | Full |
../../shared/includes/quality-gates.md | CQ1-CQ40 section only* | Q1-Q25 section only** | Full |
../../shared/includes/cross-provider-review.md | Full | Full | Full |
../../rules/cq-patterns.md or cq-patterns-core.md | Per code type*** | SKIP | Per code type*** |
../../rules/cq-checklist.md | TIER 1+ | SKIP | TIER 1+ |
../../rules/testing.md | SKIP | Full | Full |
../../rules/security.md | If security signals | SKIP | If 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)
| Input | Meaning | Git command |
| (empty) | All uncommitted changes | git diff --stat HEAD |
staged | Only staged changes | git diff --stat --cached |
new | Commits since last review | reviewed/* tag / merge-base resolution |
HEAD~N | Last N commits | git diff --stat HEAD~N..HEAD |
abc123..def456 | Specific commit range | git diff --stat abc123..def456 |
commits A,B,C | Specific non-consecutive commit hashes | Union-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:
- The nearest
reviewed/*tag reachable from HEAD, as a commit:git describe --tags --abbrev=0 --match 'reviewed/*' HEADnames the tag, andgit 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 seesDEFAULT_BRANCH. A start point equal to HEAD printsnothing new since <tag>and stops. (memory/backlog.mdholds findings, not commits, so it is never read here.) - 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} - Fallback:
git merge-base HEAD "$DEFAULT_BRANCH"(step 1 found no tag;DEFAULT_BRANCHfrom step 2, in the same shell call) - Final fallback:
HEAD~5with 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 -5If 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)
| Token | Mode | Behavior |
| (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-only | REPORT | Audit and present findings only. Do NOT touch code; print the menu and stop. Use when you want to read before acting. |
fix | FIX-ALL | Apply EVERY reported fix incl. NIT, then verify + gate. |
blocking | FIX-BLOCKING | Apply only MUST-FIX findings, then verify + gate. |
auto-fix | AUTO-FIX | Dispatch zuvo:build to fix MUST-FIX issues (closed-loop). |
tag <queue-file> | UTILITY | No 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-reviewed | UTILITY | No audit. Create reviewed/ git tags on commits. |
status | UTILITY | No audit. Show unreviewed commit count and list. |
batch <file> | BATCH | Process a queue of commits: review, fix, tag per entry. |
--thorough | FLAG | Activate multi-pass review with majority voting. |
--depth N | FLAG | For 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)
| Condition | Action |
| 0 files changed (empty diff) | Print "No changes to review." -> STOP |
| All files are binary | Print "Only binary files changed. Nothing to review." -> STOP |
| Binary files mixed with code | Tier based on code lines only. Note binaries in report. |
| All changed files are noise | Print "Only noise files changed (locks, snapshots, dist). Nothing to review." -> STOP |
| Merge commit detected | Interactive: 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
| Condition | Tier |
PROD_LOGIC_LINES = 0 | TIER 1 -- LIGHT |
| <15 lines, no risk signals | TIER 0 -- NANO |
| 15-100 lines, no risk signals | TIER 1 -- LIGHT |
| 100-500 lines OR 5-15 files OR 1 risk signal | TIER 2 -- STANDARD |
| >500 lines OR 15+ files OR 2+ risk signals | TIER 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
| Capability | TIER 0 | TIER 1 | TIER 2 | TIER 3 |
| Inline diff scan | Yes | Yes | Yes | Yes |
| CQ patterns loaded | Skip | Core (500 tok) | Full (8.4K tok) | Full (8.4K tok) |
| CQ1-CQ40 evaluation | Skip | Yes (lead inline) | Yes (CQ Auditor agent) | Yes (CQ Auditor agent) |
| Q1-Q25 on test files | Skip | If present (lead) | Yes | Yes |
| Audit agents | None | None | Behavior + 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-compute | Optional | Yes (light ops) | Yes (core ops) | Yes (core ops) |
| Confidence scoring | Lead inline | Lead inline | Re-Scorer agent | Re-Scorer agent |
| Hotspot detection | Skip | Skip | Yes | Yes |
| Multi-pass (--thorough) | Refused | Optional | Optional | Auto if >500L |
| Stack-specific rules | Skip | Skip | Yes | Yes |
| Report persistence | Skip | Yes | Yes | Yes |
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.
| Factor | Points | How to detect |
| Auth/authz changes | +3 | Diff touches guards, middleware, JWT, session, role checks |
| Payment/money logic | +3 | Diff touches payment, pricing, billing, subscription |
| DB migration or schema | +2 | Migration files, schema changes, ALTER/CREATE TABLE |
| API contract changes | +2 | New/modified routes, request/response shape changes |
| File in churn hotspot (top 10) | +2 | Phase 0 hotspot detection. Score 0 at TIER 0-1. |
| >500 lines changed | +1 | From diff stat |
| New production files added | +1 | New .ts/.tsx/.py files (not tests) |
| Multi-service blast radius | +1 | Changes affect 3+ modules/services |
| Reverts or rollback-sensitive | +1 | State machine, data migration, irreversible ops |
| Points | Level | Deploy strategy |
| 0-1 | LOW | Direct merge -- standard CI |
| 2-4 | MEDIUM | Merge after review -- run full test suite |
| 5-7 | HIGH | Canary recommended -- deploy to subset first |
| 8+ | CRITICAL | Staged 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.
| Tool | Trigger | Reason | Skip allowed? |
review_diff | Always (any review with a diff) | KEY COMPOUND — 9 parallel checks (security, dead code, complexity, etc.) on the diff | NO |
changed_symbols | Always (any commit-range or staged review) | Which symbols added/modified/deleted in range — required for CQ scoring | NO |
diff_outline | Always | Structural diff per file (signatures only — no body churn noise) | NO |
impact_analysis | Always | Blast radius + affected_tests for the changed surface | NO |
find_references | Any finding cites a function/method | Regression risk verification | NO when condition holds |
scan_secrets | Always (any review touching code or config) | CAP5 hardcoded-secret pre-scan on the diff | NO |
search_patterns | Always | CQ8 empty-catch + CAP anti-patterns introduced in the diff | NO |
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 code | Framework-aware gates the diff inherits | NO 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 tool | Equivalent | Recorded as |
review_diff | audit_scan (compound 5-gate) | review_diff: absent-in-build (audit_scan: <findings>) |
changed_symbols + diff_outline | impact_analysis + get_file_outline | changed_symbols: absent-in-build (impact_analysis+get_file_outline: <result>) |
scan_secrets | grep 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
| Value | Forbidden when | Required 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_run | EVER | scan_secrets: <count> | |
changed_symbols: N/A (test-only diff) | EVER (test files have changed_symbols too) | changed_symbols: <count> | |
codesift: unavailable | mcp__codesift__* was in deferred-tools session-start banner | codesift: deferred-not-preloaded (FAILURE: skill required preload) | |
RETRO: skipped (nothing interesting) | EVER | One of: RETRO: skipped (trivial session, <3 findings and no fix-loop) OR full retro appended | |
Adversarial: skipped (context budget) / (tight context) | EVER | Chunk 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) | EVER | This 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) | EVER | Self-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) | EVER | Honesty 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 whitelist | EVER | Whitelist (from section 1.6): single_provider_only (exit 3), timeout (exit 124), BLOCKED_CONTEXT_BUDGET (after chunking attempt failed). Nothing else. | |
ok / any | 4 | Review 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:
memory/reviews/<date>-<scope>.md(TIER 1+) is on disk.~/.zuvo/append-runlogis called with the Run line — this triggers BOTH:- retro-gate: requires a matching
RETRO:entry in~/.zuvo/retros.logforskill=review project=<this>. If missing → exit 2, runs.log NOT appended. - audit-content gate: runs
~/.zuvo/verify-auditon the report. Every MUST-FIX and RECOMMENDED finding must contain at least onepath/to/file.ext:LINEcitation that resolves in the current tree. NIT findings without citations get rejected. If rejected → fix the report, re-runappend-runlog.
- retro-gate: requires a matching
- Print
RETRO_APPENDED: retros.log=YES retros.md=YES (verified)and confirm exit 0 fromappend-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:
- Check whether CodeSift tools are available (the helper above already verified this)
- Repo auto-resolves from CWD — do NOT call
list_repos()unless the review explicitly spans multiple repositories - If unsure whether the repo is indexed:
index_status() - 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:
- Resolve
TARGET_REPO.git -C <scope-path> rev-parse --show-toplevel. If it differs from CWD's toplevel, setTARGET_REPO=<that path>. - Pass
repo=/path=explicitly toreview_diff,changed_symbols,scan_secrets,find_references(andindex_folder) so they targetTARGET_REPO, not CWD. - 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 isindex_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-scopedgrepovergit diff "${REVIEWED_FROM}..${REVIEWED_THROUGH}", plus boundedReadon the changed files, recorded ascodesift: degraded (worktree not indexed)— only if thatindex_foldercall itself fails. This is the same rule as../../shared/includes/codesift-setup.md→ "Worktree path rejected byindex_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 anindex_foldercall would have made unnecessary.) - Keep
TARGET_REPOconsistent with the Phase 3 destructive-persistence precondition (the repoREVIEWED_FROM..REVIEWED_THROUGHis resolved against MUST be the sameTARGET_REPOanalysis 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"
fiPass 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
fiRun 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 indicator | Rules 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):
search_patterns(pattern="empty-catch", file_pattern="<changed-file-substring>", max_results=20)find_references(symbol_names=[<changed exports>], file_pattern="<active test glob>")analyze_complexity(file_pattern="<changed-file-substring>", top_n=10)
TIER 2-3 (core ops):
- For each changed production file:
get_file_outline(file_path="<relative path>") find_references(symbol_names=[<changed symbols>], file_pattern="<active test glob>")trace_call_chain(symbol_name="<key changed symbol>", direction="callers", depth=2)search_patterns(pattern="empty-catch", file_pattern="<changed-file-substring>", max_results=50)analyze_complexity(file_pattern="<changed-file-substring>", top_n=20)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_retrievalsub-query types:symbols,text,file_tree,outline,references,call_chain,impact,context,knowledge_map - Do NOT use
patterns,complexity, orfile_outlinesinsidecodebase_retrieval outlineuses singularfile_path- For direct
find_references, usesymbol_nameswhen checking multiple symbols - For
search_patternsandanalyze_complexity, use the standalone tools — there is no equivalent validcodebase_retrievalsub-query type
Pass results as PRECOMPUTED_DATA to each agent:
| Agent | Gets | Helps with |
| Behavior Auditor | Call chains, pattern matches, complexity | Focus on high-risk functions |
| Structure Auditor | File outlines, complexity, impact | SRP and limits pre-answered |
| CQ Auditor | Pattern matches, test refs, file outlines | ~40% of gates pre-evaluated |
| Confidence Re-Scorer | Reference counts, hotspot ranks, impact | Data-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_CONTEXTEach agent receives: diff, tech stack, change intent, PRECOMPUTED_DATA, PROJECT_CONTEXT (global error handlers, middleware, decorators).
Result Merging (after agents complete)
- Collect BEHAV-N, STRUCT-N, and CQ findings
- Deduplicate -- same file:line + same issue = keep the one with more evidence
- 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, anN/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:
| Tier | Diff | Adversarial shape |
| TIER 0 (NANO) | <15 prod-logic lines, 1 file, no risk signal | ONE --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 lines | ONE --multi pass; a second only on the fix delta, per adversarial-loop.md Step 5. |
| TIER 2–3 | larger / risk signals | ONE 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--multipass withZUVO_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
timeoutperadversarial-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—--multiwas 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 noteAdversarial: skipped (single_provider_only — install second provider for diversity)in the review output. - exit 124 /
status: "timeout"— ALL providers timed out. RecordAdversarial: skipped (timeout)and continue to next pass (or finalize if last pass). - exit 125 /
status: "suspended"— the HOST slept mid-run (suspended_secondssays 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 astimeout(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. Surfacetimeout_countin 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:
- 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. - Hunk-level chunking (if a single file's diff is still too large): split on
@@hunk boundaries with surrounding context. Run per-hunk, aggregate. - Drop --rotate to --single fastest provider. Reduces parallelism but keeps adversarial coverage. Note in header:
Adversarial: degraded (--single <provider>, context-budget chunked N files). - Last resort — exit BLOCKED: if even single-provider per-hunk does not fit, exit with status
BLOCKED_CONTEXT_BUDGETand surface to the user: "Diff is N chars across M files; adversarial cannot complete. Narrow the scope (e.g./zuvo:review path/to/subdir/orHEAD~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 concernRecord 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 = 0and 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 findingsDisposition
| Confidence | Action | Disposition |
| 0-25 | EXCLUDE from report; not passed to backlog-add | DISCARD |
| 26-50 | EXCLUDE from report; recorded through backlog-add (Backlog Persistence) | RECORD |
| 51-100 | KEEP in report | REPORT |
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 anyREVIEW COMPLETEtext 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-treetrue;REVIEWED_FROM..REVIEWED_THROUGHresolved against this repo's history).- For commit-range scopes (
new,HEAD~N, explicit hashes):REVIEWED_FROMandREVIEWED_THROUGHare both reachable from HEAD.- For
staged/ uncommitted scopes: skipreviewed/<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
| Tier | Meaning | Merge impact |
| MUST-FIX | Confirmed bug, security issue, data loss, critical CQ gate | Blocks merge |
| RECOMMENDED | Maintenance risk, degraded reliability | Merge discouraged |
| NIT | Style, readability, no functional impact | Merge OK as-is |
Report Sections
TIER 2-3 (full report, 14 sections):
- 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 clauseNIT 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:88Test 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.
- The caller is
zuvo:ship. Ship commits the version bump andCHANGELOG.mdin 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. WhenCALLER = 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. - 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_THROUGHonce, 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'sfiles: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
fiIf 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.logEnforcement: 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>" --strictOn 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 andused --multiwith 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 >=2REVIEW 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/Ajustification. 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:
- Set
gate_status = FAIL — adversarial skip outside whitelist (<reason>). - Override VERDICT to
INCOMPLETEregardless of finding count. - Append
[ESCAPE-HATCH-VIOLATION:adversarial:<reason>]to the Run line NOTES column. - 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 inzuvo/context/adversarial-task-*.txtreturning 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 toCONDITIONAL; 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 itemsubject:review-revalidate, Validity-gate process items below, + the NEXT STEPS "run/zuvo:review <range>to clear the CONDITIONAL");gate_statusstaysPASSwithdegraded=<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>):
- Set
gate_status = FAIL — tier2 sub-agent(s) not dispatched (<which>). - 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). - Append
[TIER2-SUBAGENT-SKIP:<which>]to the Run line NOTES. - 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:
- Set
gate_status = FAIL — localized RECOMMENDED deferred (<B-id or F-id>). - Override VERDICT to
INCOMPLETEand route the mis-deferred item(s) into Phase 4 NOW (do not emit REVIEW COMPLETE with them still unfixed). A fixed item gets nobacklog-addcall. Only if an open entry was recorded for it earlier (a--report-onlypass, an earlier run) is that entry closed, asbacklog-protocol.md"Filing from a skill", Closing, not removing says (tick it, append[FIXED <sha7>]; the archive helper moves it). - Append
[LOCALIZED-DEFER-DRIFT:<B-id or F-id>]to the Run line NOTES. A multi-file structural refactor stays deferred (recorded viabacklog-add) with its recipe — that is correct, not drift. The test is fix-scope (one file/symbol → fix; cross-cutting restructure → defer, recorded viabacklog-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 |
incomplete | review validity gate failed, verdict INCOMPLETE |
escape-hatch | adversarial pass skipped outside the whitelist |
revalidate | review coverage degraded, re-validate before merge |
tier2-skip | tier 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:refactorterritory 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-runlogThe wrapper:
- Verifies the matching
RETRO:entry inretros.log(skill+project). Missing → exit 2. - Runs
~/.zuvo/verify-auditon the report atmemory/reviews/<date>-<scope>.md. Findings withoutfile:linecitations → exit 2. - On both pass: appends to
runs.logand 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 tozuvo: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-reasonout-of-mode: blocking(the Backlog-deferral whitelist): assign"$E"the textdefer-reason: out-of-mode: blockingbefore the call, with"$R","$F"and"$T", as "Free text goes through a variable" in that section says. - AUTO-FIX: dispatch
zuvo:buildwith 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:
- 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.mdand run its whole sequence — mechanical scope,CHECKlines, ledger check, exit valve — withCOMMENT_SCOPE= every file the fix loop created or modified, production and test, plus every file a fix creates or edits, checked againstgit status --porcelain --untracked-files=all— a check on the record, never a source of paths (files other sessions changed stay out); andCOMMENT_BASE= theCOMMENT_BASE=<sha>printed at the start of Phase 4, passed as a literal — notHEAD, because in AUTO-FIXzuvo:build's 4.6 commit would hide the fix loop's lines from aHEADbase. 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; nevergit stash createon 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
CHECKclaim once by TEST-OR-GO and list it asCHECK 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'sawklookup: 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>]orN/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.
- 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:
- Adversarial re-validation — run one cross-provider adversarial pass on the FIX diff (
~/.zuvo/adversarial-review --mode codeon the applied changes). It must converge (no new CRITICAL): a new CRITICAL introduced by a fix is itself fixed (cap 3 passes peradversarial-loop.md), and each residual non-CRITICAL is recorded throughbacklog-add(Backlog Persistence, Generic write). Do NOT print the FIX-COMPLETE block while a fix-introduced CRITICAL is open. - 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 awrote=path: those go into the closing commit (Closing commit for backlog-add's writes, below). fix-loop.md's## Commithappens here, at step 3 — never before 1b and 2 have passed. - 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.jsonscripts,pyproject.toml, aMakefiletarget — the same detectionzuvo:mutation-testPhase 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:
- Git tag:
git tag review-YYYY-MM-DD-<short-slug>on the fix commit. This is distinct from the per-commitreviewed/<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. - 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]
===============================================================- 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
blockingmode also a localized one,out-of-mode: blocking): each recorded throughbacklog-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 commitchore(backlog): record findings of review runwith the run'swrote=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>, …(orBacklog 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 failsTreat 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:
- Collect MUST-FIX findings into a fix list
- Dispatch
zuvo:buildwith scope = affected files, task = fix descriptions, mode =--auto - After build completes, auto-run
zuvo:reviewon the fix diff (TIER 1 minimum) - If re-review finds new MUST-FIX: report (do NOT loop -- max 1 cycle)
- 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 coverageLines 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:
- Build set of reviewed hashes from
reviewed/*tags - Walk last N commits (default 100, configurable with
--depth N) - Print unreviewed:
Total: N | Reviewed: X | Unreviewed: Y-> STOP.
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
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.

