# Review

> 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.

- Skill: `greglas75/review` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add greglas75/review`
- Raw SKILL.md: https://api.skillmd.com/api/skills/greglas75/review/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: greglas75 (https://skillmd.com/u/greglas75)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/greglas75/review

---


# zuvo:review

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

## Mandatory File Loading

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

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

This is the ONLY file loaded before reading the diff.

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

After CodeSift setup, read the git diff. Classify content type:
- **prod-only:** diff touches production files only (no `*.test.*`, `*.spec.*`)
- **test-only:** diff touches test files only
- **mixed:** diff touches both production and test files

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

### PHASE 1 — Conditional Load (based on diff type)

| Include | prod-only | test-only | mixed |
|---------|-----------|-----------|-------|
| `../../shared/includes/env-compat.md` | Full | Full | Full |
| `../../shared/includes/quality-gates.md` | CQ1-CQ40 section only* | Q1-Q25 section only** | Full |
| `../../shared/includes/cross-provider-review.md` | Full | Full | Full |
| `../../rules/cq-patterns.md` or `cq-patterns-core.md` | Per code type*** | **SKIP** | Per code type*** |
| `../../rules/cq-checklist.md` | TIER 1+ | **SKIP** | TIER 1+ |
| `../../rules/testing.md` | **SKIP** | Full | Full |
| `../../rules/security.md` | If security signals | **SKIP** | If security signals |

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

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

### Optional Files (loaded if available, degraded if missing)

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

### DEFERRED — Load at completion

```
  ../../shared/includes/run-logger.md        -- [READ at final step]
  ../../shared/includes/retrospective.md     -- [READ at final step]
```

---

## 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:**
1. `memory/backlog.md` unchecked entries -> oldest entry's parent hash as start point
2. Detect default branch: `DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||'); DEFAULT_BRANCH=${DEFAULT_BRANCH:-main}`
3. Fallback: `git merge-base HEAD "$DEFAULT_BRANCH"`
4. Final fallback: `HEAD~5` with a warning

### Range Validation

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

```bash
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:

```bash
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) | **Do NOT report the review complete.** Findings returned are real; the ABSENCE of findings says nothing about the omitted files. Re-run over the omitted set or split the input, then merge verdicts |

### Required POSTAMBLE — retrospective + verify-audit gates

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

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

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

### Mandatory acknowledgment (REQUIRED — print verbatim before Phase 0.5)

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

### Standard CodeSift checks (run AFTER the helper)

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

### Cross-checkout / worktree scope

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

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

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

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

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

```bash
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:

```bash
# 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):**

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

**TIER 2-3 (core ops):**

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

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

### Compatibility Notes

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

Pass results as `PRECOMPUTED_DATA` to each agent:

| 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)

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

### 1.4 CQ Self-Evaluation (TIER 1+)

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

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

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

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

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

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

### Pre-Existing Issues

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

### Working-Tree Staleness Check

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

```bash
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 `--multi` pass with `ZUVO_REVIEW_TIMEOUT=60`, not multi-pass. One cross-model look, bounded to ~60s.
- **A hung/timed-out provider is NEVER retried in a manual loop on a tiny diff.** If a provider times out at TIER 0/1, record `Adversarial: partial (<provider> only, others timed out)` and finalize — do NOT hand-retry the remaining providers (that hand-retry loop is exactly what turned 3 lines into 20 minutes). Chunking/retry is a TIER 2–3 concern for genuinely large diffs.
- **Always run in the background or with a long Bash `timeout`** per `adversarial-loop.md` — never let the 120s Bash-tool default kill the pass mid-flight.

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

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

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

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

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

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

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

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

**Provider accounting — a block is only "used" if it actually reviewed.** Count a provider toward `providers_used` only when its block contains at least one verdict or finding. A block that holds

…(truncated)
