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 8.4K tok).cq-patterns.md (
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. 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:
memory/backlog.mdunchecked 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~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 -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-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
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>) |
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 (git diff --name-only "${REVIEWED_FROM}..${REVIEWED_THROUGH}") BEFORE CQ scoring, and 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. And findings outside the fence that the diff did not cause are not silently dropped knowledge: they are pre-existing debt, so backlog them separately rather than scoring them against this diff.
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) |
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"
fi
Pass REVIEW_TREE to every dispatched agent as the root they analyze. The lead keeps using the live
checkout: CodeSift pre-compute, the fix loop, the artifact and the retro all belong there.
Teardown is mandatory and must survive a failed run — chmod -R a-w makes the directory
undeletable by the normal path, so a review that dies mid-flight leaves an unwritable worktree and a
registered entry that git worktree list will keep showing:
# The guard is not defensive padding. On the fallback path $REVIEW_TREE IS the live checkout, and
# an unguarded teardown would then `chmod -R u+w` the entire working repository — stripping the
# read-only bits off .git/objects, mounted secrets and locked configs — and try to `worktree
# remove` the main tree. A failed worktree creation must not damage the repo it failed to copy.
if [ -n "${REVIEW_TREE:-}" ] && [ "$REVIEW_TREE" != "$(git rev-parse --show-toplevel)" ]; then
chmod -R u+w "$REVIEW_TREE" 2>/dev/null
git worktree remove --force "$REVIEW_TREE" 2>/dev/null
fi
Run it at the end of Phase 3 and on every abort path. Record the outcome in the Validity Gate as
audit_tree: readonly(<sha7>) | live(<reason>). live is honest and allowed; silently claiming
readonly when the worktree was never created is not.
Do NOT use this for the fix loop. Phase 4 edits real files, runs the real suite and commits — it belongs in the live checkout, and pointing it at a frozen detached tree would produce commits on no branch. The split is the point: frozen tree for the eyes, live tree for the hands.
Stack Detection (TIER 2+)
Detect tech stack and load matching rules:
| Stack 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_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-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 | Up to 2 --multi passes, default timeout; stop early on a clean pass. |
| TIER 2–3 | larger / risk signals | Full sequential --multi (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--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
…(truncated)