| name | review |
| description | 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.
|
| codesift_tools | {"always":["analyze_project","index_status","index_folder","index_file","plan_turn","review_diff","changed_symbols","diff_outline","impact_analysis","get_symbol","get_symbols","get_file_outline","find_references","trace_call_chain","audit_scan","search_patterns","scan_secrets","search_text","search_symbols","get_file_tree"],"by_stack":{"typescript":["get_type_info","resolve_constant_value"],"javascript":[],"python":["python_audit","analyze_async_correctness","resolve_constant_value"],"php":["php_project_audit","php_security_scan","resolve_php_namespace"],"kotlin":["analyze_sealed_hierarchy","find_extension_functions","trace_flow_chain","trace_suspend_chain","trace_compose_tree","analyze_compose_recomposition","trace_hilt_graph","trace_room_schema","analyze_kmp_declarations","extract_kotlin_serialization_contract"],"nestjs":["nest_audit"],"nextjs":["framework_audit","nextjs_route_map"],"astro":["astro_audit","astro_actions_audit","astro_hydration_audit","astro_middleware","astro_sessions","astro_image_audit","astro_svg_components"],"hono":["analyze_hono_app","audit_hono_security"],"express":[],"fastify":[],"react":["react_quickstart","analyze_hooks","analyze_renders","analyze_context_graph","audit_compiler_readiness","trace_component_tree"],"django":["analyze_django_settings","effective_django_view_security","taint_trace"],"fastapi":["trace_fastapi_depends","get_pydantic_models"],"flask":["find_framework_wiring"],"jest":[],"yii":["resolve_php_service","trace_php_event","find_php_views"],"prisma":["analyze_prisma_schema"],"sql":["sql_audit"],"postgres":["migration_lint"]}} |
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-CQ29 section only* | Q1-Q19 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-Q19 heading. Skip Q section.
** Q section only: Read from ## Q1-Q19: 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]
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 | Backlog/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 |
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:
memory/backlog.md unchecked entries -> oldest entry's parent hash as start point
- 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"
- Final fallback:
HEAD~5 with a warning
Range Validation
After deriving REVIEWED_FROM and REVIEWED_THROUGH for any commit-based scope (new, HEAD~N, abc123..def456, batch entry), validate the range before tier selection, CodeSift pre-compute, or adversarial review:
git log --oneline "${REVIEWED_FROM}..${REVIEWED_THROUGH}" | head -5
If this returns no commits, STOP and print:
[RANGE-ERROR] Empty commit range. Verify base/tip order before running review.
Do NOT auto-swap the range.
Then print the validated diff stat:
git diff --shortstat "${REVIEWED_FROM}..${REVIEWED_THROUGH}"
Mode (what to do after the audit)
| 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 → backlog (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 | UTILITY | No audit. Remove reviewed commits from backlog. |
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-CQ29 evaluation | Skip | Yes (lead inline) | Yes (CQ Auditor agent) | Yes (CQ Auditor agent) |
| Q1-Q19 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
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 |
| 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>) |
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. |
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-runlog is called with the Run line — this triggers BOTH:
- retro-gate: requires a matching
RETRO: entry in ~/.zuvo/retros.log for skill=review project=<this>. If missing → exit 2, runs.log NOT appended.
- audit-content gate: runs
~/.zuvo/verify-audit on the report. Every MUST-FIX and RECOMMENDED finding must contain at least one path/to/file.ext:LINE citation that resolves in the current tree. NIT findings without citations get rejected. If rejected → fix the report, re-run append-runlog.
- Print
RETRO_APPENDED: retros.log=YES retros.md=YES (verified) and confirm exit 0 from append-runlog.
If you reach REVIEW COMPLETE and stop without calling append-runlog: the review is INVALID regardless of finding count. The Validity Gate gate_status flips to FAIL — postamble incomplete and the verdict overrides to INCOMPLETE.
Mandatory acknowledgment (REQUIRED — print verbatim before Phase 0.5)
Mandatory-tools-acknowledgment: I will run review_diff + changed_symbols + diff_outline + impact_analysis + scan_secrets + search_patterns + find_references (on cited symbols) + stack-specific tools (nest_audit/framework_audit/python_audit/etc. when detected) for this review. Each MUST-FIX and RECOMMENDED finding will cite a `path/to/file.ext:LINE` resolving in the current tree.
Standard CodeSift checks (run AFTER the helper)
Follow codesift-setup.md:
- 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, set TARGET_REPO=<that path>.
- Pass
repo=/path= explicitly to review_diff, changed_symbols, scan_secrets, find_references (and index_folder) so they target TARGET_REPO, not CWD.
- Fresh worktree staleness. Run
index_status(path=TARGET_REPO). If the worktree's branch commits are not yet indexed, run index_folder(path=TARGET_REPO) BEFORE any symbol/pattern tool — otherwise semantic tools silently scan the stale main checkout. If indexing the worktree is not possible, fall back to git-diff-scoped grep over git diff {REVIEWED_FROM}..{REVIEWED_THROUGH}.
- Keep
TARGET_REPO consistent with the Phase 3 destructive-persistence precondition (the repo REVIEWED_FROM..REVIEWED_THROUGH is resolved against MUST be the same TARGET_REPO analysis and tagging both reference).
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 |
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_retrieval sub-query types: symbols, text, file_tree, outline, references, call_chain, impact, context, knowledge_map
- Do NOT use
patterns, complexity, or file_outlines inside codebase_retrieval
outline uses singular file_path
- For direct
find_references, use symbol_names when checking multiple symbols
- For
search_patterns and analyze_complexity, use the standalone tools — there is no equivalent valid codebase_retrieval sub-query type
Pass results as PRECOMPUTED_DATA to each agent:
| 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 |
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-Q19 (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): adversarial-review.sh only accepts --multi/--single/--rotate/--exclude/--exclude-last/--artifact; an unknown flag exits 2 and silently drops you to weaker coverage. Probe once if unsure: adversarial-review --help | grep -- --multi. --multi exits 3 (single_provider_only) when <2 providers exist — only then fall back to --rotate/--single.
1.2 Review Header (merged banner -- single block replaces 4 separate blocks)
===============================================================
CODE REVIEW | TIER [0-3] ([NANO-DEEP])
SCOPE: [N files, +X/-Y lines] | INTENT: [BUGFIX/REFACTOR/FEATURE/INFRA]
AUDIT: [SOLO/TEAM (N)] | Adversarial: [providers] | RISK: [LOW-CRITICAL]
Risk signals: [x] API [ ] DB [ ] Auth [ ] Money [ ] 500+L
===============================================================
1.3 Agent Dispatch
Refer to env-compat.md for the correct dispatch pattern per environment.
TIER 0-1: No agents. Lead performs all analysis inline using CodeSift pre-computed data (Phase 0.5) if available.
TIER 2: Dispatch Behavior Auditor (agents/behavior-auditor.md) if new production files. Dispatch CQ Auditor (agents/cq-auditor.md) as background agent. Lead performs Structure analysis inline.
TIER 3: Dispatch all 3 audit agents in parallel:
Agent 1: Behavior Auditor
model: "sonnet"
type: "general-purpose" # NOT Explore — Explore lacks mcp__codesift__* and CodeSift precheck hooks reject the dispatch
instructions: read agents/behavior-auditor.md
input: diff, tech stack, change intent, PRECOMPUTED_DATA, PROJECT_CONTEXT
Agent 2: Structure Auditor
model: "sonnet"
type: "general-purpose"
instructions: read agents/structure-auditor.md
input: diff, tech stack, change intent, PRECOMPUTED_DATA, PROJECT_CONTEXT
Agent 3: CQ Auditor
model: "sonnet"
type: "general-purpose"
instructions: read agents/cq-auditor.md
input: diff, tech stack, change intent, PRECOMPUTED_DATA, PROJECT_CONTEXT
Each agent receives: diff, tech stack, change intent, PRECOMPUTED_DATA, PROJECT_CONTEXT (global error handlers, middleware, decorators).
Result Merging (after agents complete)
- 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-CQ29. Print all 29 gates. 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.
1.5 Q1-Q19 Evaluation (if test files in diff)
For each test file, run Q1-Q19. 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 --rotate — each pass uses a 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 adversarial-review --single --mode code. ~60s ceiling. |
| TIER 1 (LIGHT) | 15–100 lines | Up to 2 --rotate passes, default timeout; stop early on a clean pass. |
| TIER 2–3 | larger / risk signals | Full sequential --rotate (2–3 passes) as below. |
- Self-review overrides tier-down for correctness, but keep the timeout tight on tiny diffs: SELF-REVIEW still forces
--multi (section 1.1) — but on a TIER 0 diff run it as ONE --multi pass with ZUVO_REVIEW_TIMEOUT=60, not multi-pass. One cross-model look, bounded to ~60s.
- A hung/timed-out provider is NEVER retried in a manual loop on a tiny diff. If a provider times out at TIER 0/1, record
Adversarial: partial (<provider> only, others timed out) and finalize — do NOT hand-retry the remaining providers (that hand-retry loop is exactly what turned 3 lines into 20 minutes). Chunking/retry is a TIER 2–3 concern for genuinely large diffs.
- Always run in the background or with a long Bash
timeout per adversarial-loop.md — never let the 120s Bash-tool default kill the pass mid-flight.
If adversarial-review not in PATH: ~/.claude/plugins/cache/zuvo-marketplace/zuvo/*/scripts/adversarial-review.sh
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 — --rotate was requested but post-host-exclusion only 1 provider remains. Two options: re-invoke with --single (accept reduced consensus and note it in the review header) OR skip this pass and note Adversarial: skipped (single_provider_only — install second provider for diversity) in the review output.
- exit 124 /
status: "timeout" — ALL providers timed out. Record Adversarial: skipped (timeout) and continue to next pass (or finalize if last pass).
status: "partial" with exit 0 — some providers returned, others did not. Surface timeout_count in the review header (e.g. Adversarial pass 1: cursor-agent (1 of 2 providers; gemini timed out)) so the user sees coverage was reduced.
Cross-call rotation: between passes, capture providers_used_list[0] (array field) from each pass's JSON output and thread it into the next --rotate call via --exclude-last <name>. Forces a different provider on each successive pass even when host exclusion limits the pool. (The string field providers_used cannot be indexed with [0] in jq.)
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_BUDGET and surface to the user: "Diff is N chars across M files; adversarial cannot complete. Narrow the scope (e.g. /zuvo:review path/to/subdir/ or HEAD~3) and re-invoke." This blocks the review verdict — does NOT silently produce PASS/APPROVED.
What "context budget tight" is NOT a license to do: skip the pass, mark Adversarial: skipped (context budget), and proceed to a verdict. That value is on the forbidden escape hatches list (line 376+) and the Validity Gate will override the verdict to INCOMPLETE with suffix [ESCAPE-HATCH-VIOLATION:adversarial].
REPORT mode — sequential finding (no fixes)
Each pass uses --rotate (script picks a random unused provider). Prepend prior findings summary so each provider targets NEW issues.
git diff {REVIEWED_FROM}..{REVIEWED_THROUGH} | adversarial-review --rotate --mode code
(echo "PRIOR FINDINGS: ADV-1 [desc], ADV-2 [desc] — find NEW issues only";
git diff {REVIEWED_FROM}..{REVIEWED_THROUGH}) | adversarial-review --rotate --mode code
(echo "PRIOR FINDINGS: ADV-1..3 — final pass, find what everyone missed";
git diff {REVIEWED_FROM}..{REVIEWED_THROUGH}) | adversarial-review --rotate --mode code
Early exit: 0 findings from a pass = stop (code is clean from that model's perspective).
FIX mode — sequential fix + validation
Same --rotate pattern but each pass sees the IMPROVED diff after prior fixes.
git diff {REVIEWED_FROM}..HEAD | adversarial-review --rotate --mode code
git diff {REVIEWED_FROM}..HEAD | adversarial-review --rotate --mode code
git diff {REVIEWED_FROM}..HEAD | adversarial-review --rotate --mode code
Max 2 fix attempts per provider finding. Max 3 passes total.
Common rules
- Use
--rotate — script picks a random provider each call. Do NOT use bare --mode code (that runs all providers in parallel, defeating sequential).
- Strip lockfiles, snapshots, dist output, and other known noise files from the diff before piping it to
adversarial-review.
- When deterministic facts are already known (for example: lockfile present in diff, package ships bundled types, file already patched at HEAD), prepend a short
FACTS: block before the diff so the adversarial provider does not rediscover settled facts.
- If
PROD_LOGIC_LINES = 0 and SELF-REVIEW is not set, skip adversarial and log: [CROSS-REVIEW] Skipped — no production logic changed.
- Timeout: 60s per provider. Skip on timeout/malformed, continue with next.
- All unavailable:
[CROSS-REVIEW] No external provider available. in SKIPPED STEPS.
- Severity: CRITICAL -> MUST-FIX (bypasses confidence gate). WARNING -> RECOMMENDED. INFO -> NIT.
- Tag: each finding as
[CROSS:<provider>]
Multi-Pass (--thorough variant)
3 audit passes in parallel: Pass 1 alphabetical, Pass 2 reverse dependency (leaf-first), Pass 3 risk-score descending. Majority voting: 3/3 -> KEEP + confidence +15. 2/3 -> KEEP. 1/3 -> DOWNGRADE one tier. Sequential adversarial runs AFTER multi-pass merge. Adversarial findings are NOT subject to voting — they go through confidence gate (WARNING/INFO) or bypass it (CRITICAL).
Phase 2: Confidence Gate
Structural-refactor findings → defer with a recipe, don't block. A RECOMMENDED finding whose fix is a structural refactor (extract a module, split a god-file, invert a dependency, restructure a layer) is real but does NOT belong in this review's fix loop — it is multi-file work that zuvo:refactor owns. Do not leave it as a vague "consider refactoring": persist it to backlog with a concrete resolution recipe (what to extract/split, the target shape, the 2-3 ordered steps, the rule it satisfies) so it is actionable later, and keep it OUT of the MUST-FIX set so it cannot block the merge. A structural refactor surfaced as MUST-FIX on an unrelated diff is scope creep.
TIER 0-1: Lead scores each finding inline. Confidence: [X]/100 -- [reason].
TIER 2+: Dispatch Confidence Re-Scorer agent:
Agent: Confidence Re-Scorer
model: "sonnet"
type: "general-purpose"
instructions: read agents/confidence-rescorer.md
input: full candidate list, PRECOMPUTED_DATA, adversarial findings
Disposition
| Confidence | Action | Backlog Tag |
|---|
| 0-25 | EXCLUDE from report | [low-confidence] |
| 26-50 | EXCLUDE from report | [below-threshold] |
| 51-100 | KEEP in report | -- |
Adversarial CRITICAL bypass: Findings from adversarial-review.sh tagged CRITICAL skip the confidence gate. Effective confidence = 100. No exceptions.
Backlog write timing: All backlog writes happen AFTER Phase 4 Execute (or after Phase 3 if no execute). This prevents stale entries -- fixed findings are not written to backlog.
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, writing to backlog, or running the retrospective. All subsections below (Backlog Persistence → Report Persistence → Tag Reviewed Commits → Knowledge Curation → Retrospective → Completion Gate → NEXT STEPS) execute in order before any REVIEW COMPLETE text is emitted. The only gate is the Completion Gate Check at the end.
Destructive-persistence preconditions (verify silently before tagging or writing to shared logs):
- The CWD is a git repo and matches the scope being reviewed (
git rev-parse --is-inside-work-tree true; REVIEWED_FROM..REVIEWED_THROUGH resolved against this repo's history).
- For commit-range scopes (
new, HEAD~N, explicit hashes): REVIEWED_FROM and REVIEWED_THROUGH are both reachable from HEAD.
- For
staged / uncommitted scopes: skip reviewed/<hash> tagging entirely (already documented below).
- If any precondition fails: skip the destructive step (tag / log append) and report
[skipped: precondition failed (<reason>)] in the gate check rather than silently writing into the wrong repo or logging spurious entries.
Severity Tiers
| 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 tags) 12. FINDINGS (MUST-FIX -> RECOMMENDED -> NIT collapsed) 13. QUALITY WINS (max 3) 14. TEST ANALYSIS
TIER 0-1 (condensed report — merge sections to save ~1.5K output tokens):
Combine META + SCOPE FENCE + VERDICT into the merged banner. Skip: DEPLOYMENT RISK (always LOW at TIER 0-1), SKIPPED STEPS (obvious), VERIFICATION PASSED (inline), BACKLOG IN SCOPE (check manually). Print only: banner, FINDINGS (if any), QUALITY WINS, NEXT STEPS. ~500 tok output vs ~3K for full report.
Each finding:
R-1 [MUST-FIX] Missing orgId filter in query -- returns all orgs' data
File: src/order/order.service.ts:87
Confidence: 92/100
Evidence: findMany at :87 has no orgId in WHERE clause
Fix: Add `organizationId: orgId` to the WHERE clause
NIT visual subordination:
NITs (3 items -- style/readability, no functional impact):
R-12 unused import at auth.ts:3
R-13 prefer ?? over || at config.ts:45
R-14 collapsible if at user.service.ts:88
Test Coverage Delta (TIER 2+)
For each changed production file: check pre-computed test references (Phase 0.5). Symbols with 0 test refs -> RECOMMENDED finding at TIER 2+, observation in TEST ANALYSIS at TIER 1.
Backlog Persistence (after execute or after report if no execute)
Persist ALL findings to memory/backlog.md:
- Excluded findings (0-50 confidence): backlog with
[low-confidence] or [below-threshold] tag
- Unfixed reported findings (51-100): backlog
- Pre-existing issues: backlog
- Deduplicate by fingerprint:
file|rule-id|signature
Report Persistence (TIER 1+)
Save the full report to memory/reviews/YYYY-MM-DD-<scope>.md.
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.
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
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">.
Follow-up ideas (optional — ZERO ceremony)
If genuinely new IDEAS surfaced this session — feature possibilities, "we could also X", better
approaches for later — append ONE line each to memory/ideas.md at the MAIN checkout root (worktree-safe resolution per backlog-protocol.md; create if missing):
- [YYYY-MM-DD] [review] <idea> — <one-line context>. Ideas only: debt/findings go to backlog, not
here. If nothing surfaced, skip silently — no marker, no gate, no telemetry. This file is read
by knowledge-prime so future sessions see it.
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-Q19 printed for each changed test file (if any)
[ ] TIER 2-3: Behavior Auditor (if new prod files) + CQ Auditor + Confidence Re-Scorer DISPATCHED as sub-agents — NOT done inline as "lead" (or explicit [DEGRADED: ...] line, forbidden on self-review)
[ ] TIER 2-3 Next.js: framework_audit called (nextjs_route_map alone does NOT satisfy it)
[ ] Adversarial review ran — at least 2 sequential passes with findings printed; SELF-REVIEW used --multi (not --rotate)
[ ] All findings confidence-scored
[ ] Backlog persistence ran (memory/backlog.md updated or explicitly N/A)
[ ] No localized RECOMMENDED silently backlogged — every backlogged RECOMMENDED carries a defer-reason of [NIT] or [structural-refactor (multi-file)]; any single-file fix in backlog = drift, route it to Phase 4 instead
[ ] 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: header (on success; skip staged/uncommitted) — pipeline-entry signal
[ ] reviewed/<hash> tags created (skip for staged/uncommitted scope)
[ ] Knowledge curation ran (if knowledge-curate.md loaded)
[ ] Retrospective ran OR explicit "RETRO: skipped (<reason>)" printed
[ ] Run: TSV line printed and appended to ~/.zuvo/runs.log
Enforcement: print the gate check as a checklist with actual [x] / [ ] marks so the user can audit. If any [ ] remains, loop back and complete it before emitting the NEXT STEPS block.
Validity Gate (REQUIRED — print BEFORE Run line, AFTER retro append + append-runlog)
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.
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>) | not_required (no new prod files / tier<2) | NOT_DISPATCHED — VIOLATES_TIER2]
cq_auditor: [DISPATCHED(<agent-return-marker>) | NOT_DISPATCHED — VIOLATES_TIER2]
confidence_rescorer: [DISPATCHED(<agent-return-marker>) | NOT_DISPATCHED — VIOLATES_TIER2]
# 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 | BLOCKED_CONTEXT_BUDGET | <other> — VIOLATES_MANDATE]
# rate_limit / timeout are NOT skip reasons — 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]
backlog_deferral:
recommended_applied: <count of localized RECOMMENDED fixed in Phase 4>
recommended_deferred: [<count> all tagged NIT/structural-refactor(multi-file) | <B-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 add a backlog item B-review-incomplete-<date>.
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
INCOMPLETE regardless of finding count.
- Append
[ESCAPE-HATCH-VIOLATION:adversarial:<reason>] to the Run line NOTES column.
- Add backlog item
B-review-escape-hatch-<date> with the verbatim quote of the skip rationale (so the pattern is auditable).
Same handling if self_review_flag = yes — DID_NOT_USE_--multi (section 1.1 mandates --multi on self-review).
Rate-limit is NOT a degraded path — it is a RETRY (per stall-recovery.md). If a mandatory gate (fresh adversarial --multi, or any TIER-2 sub-agent fan-out) cannot complete this turn because of a rate-limit / API-error, do NOT record a skip, do NOT downgrade to CONDITIONAL, do NOT cite coverage. End the turn; the watchdog resumes; RE-RUN the exact gate. Repeat until it actually runs. A rate-limited review is still-running, not done-degraded. The verdict is computed only once every mandatory gate has truly run (or is proven by a same-commit artifact, below). There is no rate_limit skip-reason and no rate-limit CONDITIONAL — that path is removed precisely because it became the universal excuse.
Genuine capability-limit handling (NOT rate-limit — these do not clear by retrying). The only honest non-retry unavailability reasons are single_provider_only (only one model is configured, so cross-model --multi truly cannot run) and BLOCKED_CONTEXT_BUDGET (the context window is full). For these, check coverage_source:
same-session-same-commit(<artifacts>) — the dimension WAS covered by a real pass on the EXACT commits under review (e.g. adversarial artifacts in zuvo/context/adversarial-task-*.txt returning 0 open MUST-FIX). Verify the artifacts exist on disk AND reference these commit SHAs. If so, that dimension is legitimately covered → it does NOT degrade the verdict. (The ONE honest reuse — real coverage on the same code, not a lighter substitute.)
NONE — no fresh run and no same-commit artifact, and the limit genuinely cannot be retried away. Then: verdict downgrades to CONDITIONAL; print [DEGRADED-COVERAGE: <gate> not run — <single_provider_only|BLOCKED_CONTEXT_BUDGET>; re-validate before merge] + append [DEGRADED-COVERAGE:<gate>:<reason>] to the Run line NOTES; record a re-validation obligation (B-review-revalidate-<date> + the NEXT STEPS "run /zuvo:review <range> to clear the CONDITIONAL"); gate_status stays PASS with degraded=<gate> noted.
The point: a clean APPROVE requires every mandatory gate to be either freshly run, proven by a same-commit artifact, or (only for a true capability limit) honestly degraded to CONDITIONAL. 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:
- 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.
- Add backlog item
B-review-tier2-skip-<date> with the verbatim rationalization quote.
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.
Localized-deferral drift handling (NEW — closes the "RECOMMENDED → backlog" reflex that grows the backlog with things that should have been fixed in-loop): The recurring failure is the lead reflexively routing a localized, single-file RECOMMENDED fix (add try/catch, add a guard, add a missing affordance) to backlog because it is "only RECOMMENDED" — conflating merge-severity with fix-scope. The backlog then accretes one-line fixes that the AUTO-FIX default already mandates applying. If any backlogged RECOMMENDED item carries a defer-reason OUTSIDE the whitelist {NIT, structural-refactor (multi-file)} — or carries none — then:
- Set
gate_status = FAIL — localized RECOMMENDED deferred to backlog (<B-id>).
- Override VERDICT to
INCOMPLETE and route the mis-deferred item(s) into Phase 4 NOW (do not emit REVIEW COMPLETE with them still in backlog).
- Append
[LOCALIZED-DEFER-DRIFT:<B-id>] to the Run line NOTES.
A multi-file structural refactor stays in backlog with its recipe — that is correct, not drift. The test is fix-scope (one file/symbol → fix; cross-cutting restructure → backlog), never severity tier.
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 to backlog: <K> NIT + <S> structural-refactor RECOMMENDED.
<for each deferred item> B-<id> — defer-reason: [NIT | structural-refactor (multi-file)]
Backlog-deferral whitelist (HARD — closes the silent-deferral drift gap). The DEFAULT for a RECOMMENDED finding is fix it now in Phase 4, not backlog. A RECOMMENDED finding may be deferred to backlog ONLY if it matches one of exactly two categories, and you MUST print its defer-reason tag in the AUTO-FIX block above:
- NIT — style/readability, zero functional impact (already its own tier; would only apply under explicit
fix/FIX-ALL).
- structural-refactor (multi-file) — extract a module, split a god-file, invert a dependency, restructure a layer. This is
zuvo:refactor territory and gets a resolution recipe per Phase 2.
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 → backlog), not on severity. If you cannot name the deferred item's category as NIT or structural-refactor (multi-file), it is NOT deferrable — route 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 — backlogged) and finish. Only when --report-only was passed do you instead print the menu and stop:
NEXT STEPS: "fix" (all) | "blocking" (MUST-FIX only) | "auto-fix" (zuvo:build) | "skip"
Append the Run line via the retro-gated wrapper (NOT direct >> runs.log):
printf '%b\n' "$RUN_LINE" | ~/.zuvo/append-runlog
The wrapper:
- Verifies the matching
RETRO: entry in retros.log (skill+project). Missing → exit 2.
- Runs
~/.zuvo/verify-audit on the report at memory/reviews/<date>-<scope>.md. Findings without file:line citations → exit 2.
- On both pass: appends to
runs.log and prints confirmation.
If the wrapper exits non-zero: do NOT manually append to runs.log. Fix the cause and re-run.
Phase 4: Execute (FIX-AUTO / FIX-ALL / FIX-BLOCKING / AUTO-FIX)
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 go to backlog (the structural-refactor defer rule in Phase 2 already routes them there). This is the "I always click fix anyway" default — minus the low-value churn and the multi-file refactors that belong to
zuvo:refactor.
- FIX-ALL: apply MUST-FIX + RECOMMENDED + NIT (explicit
fix — you want everything incl. nits).
- FIX-BLOCKING: apply MUST-FIX only.
- AUTO-FIX: dispatch
zuvo:build with MUST-FIX findings as context (closed-loop, max 1 cycle).
Post-fix gate (MANDATORY — auto-applied fixes can over-correct)
Applying fixes without re-checking is how a "fix" silently becomes a regression (2026-05-30: an auto-applied resize fix removed the width floor → the dock could collapse to 0px and aria-valuemin > valuemax; caught only by the next adversarial pass). After applying ANY fixes, before declaring done:
- Verify — run the project's test/typecheck/build. A fix that leaves the suite red is reverted, not shipped.
- Adversarial re-validation — run one cross-provider adversarial pass on the FIX diff (
adversarial-review --mode code on the applied changes). It must converge (no new CRITICAL): a new CRITICAL introduced by a fix is itself fixed (cap 3 passes per adversarial-loop.md), residual non-CRITICAL → backlog. Do NOT print the FIX-COMPLETE block while a fix-introduced CRITICAL is open.
- Commit only after 1+2 pass. Record applied vs deferred (backlog IDs) in the FIX summary.
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.
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-commit reviewed/<hash> tags created in Phase 3 — that set marks what was audited, this one marks what was fixed. Both can coexist on the same repo.
- 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]
===============================================================
- Backlog persistence: unfixed items from FIX-BLOCKING (RECOMMENDED + NIT) or partial fix
Staged Scope Stash Management (B5 fix)
When scope is staged:
1. git stash --keep-index # save unstaged changes
2. Run fix-loop.md # applies fixes, tests, commits
3. git stash pop # ALWAYS runs, even if fix-loop fails
Treat stash pop as a finally block. If fix-loop aborts, pop the stash and report the failure.
Closed-Loop Auto-Fix
When mode is AUTO-FIX:
- Collect MUST-FIX findings into a fix list
- Dispatch
zuvo:build with scope = affected files, task = fix descriptions, mode = --auto
- After build completes, auto-run
zuvo:review on 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 )
Process a queue of commits: review, fix, tag -- one at a time, zero interactive stops.
Input Format
One commit hash per line, optionally with description:
ecbf4351c | perf: memoize productById Map
57a26ea14 | test: broaden cross-app coverage
Lines starting with # are comments. Lines with - [x] or - [!] are skipped (resume mode).
Enrich Queue
Validate each hash (git cat-file -t). Rewrite file with - [ ] <hash> | <msg> | +X/-Y | N files.
Per-Commit Loop
For each [ ] entry: read diff -> triage -> audit at full depth -> fix (FIX-ALL) -> tag (reviewed/<hash>) -> clean backlog -> 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. Clean review backlog:
- Read
memory/backlog.md
- For each unchecked hash:
git merge-base --is-ancestor <hash> HEAD
- If yes, remove the line
- Print: "Review backlog cleaned. N removed, M remaining." -> STOP.
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
- After tagging, clean
memory/backlog.md. STOP.
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.