# Phase 4 Review  -  Multi-Repo Mode

> Loaded on demand from `phases/phase-4-review.md`. Conditional on
> `state.projects[].length > 1`; a single-repo run (the common case) never needs
> it and used to pay for it on every review.


Active when `state.projects[].length > 1`. The 3-reviewer/triage architecture is unchanged  -  what changes is the diff that reviewers see.

**Combined diff assembly**  -  reviewers get **one combined diff** covering all repos, with explicit per-repo headers so they can flag cross-repo impact:

```bash
COMBINED_DIFF=$(mktemp)
for proj in $(jq -r '.projects[] | "\(.name)\t\(.worktreePath)\t\(.baseBranch)"' "$STATE_FILE"); do
  IFS=$'\t' read -r name wt base <<< "$proj"
  ORIGIN=$(git -C "$wt" config --get remote.origin.url)
  printf '\n=== repo: %s (origin: %s) ===\n' "$name" "$ORIGIN" >> "$COMBINED_DIFF"
  printf 'Base: %s ... HEAD\n\n' "$base" >> "$COMBINED_DIFF"
  git -C "$wt" diff "origin/$base...HEAD" >> "$COMBINED_DIFF"
done
```

Same reviewer set (per host: Fable+Sonnet on Claude Code, Opus/GPT-5.4/Sonnet on Copilot CLI, gpt-5.6/gpt-5.4/gpt-5.6 on Codex CLI) receive `COMBINED_DIFF` with a multi-repo prefix in the system prompt:

```
This is a multi-repo task spanning {N} repos: {repo names}.
Each repo's diff is prefixed with `=== repo: <name> ===`.
A finding may span repos (e.g. "common adds API X, but uicomponents calls X with wrong type").
For cross-repo findings, set `crossRepo: true` and list all impacted repos in `affectedRepos: []`.
```

**Schema extension for findings** (additive, single-repo `crossRepo` defaults to false):

```json
{
  "severity": "blocking|important|suggestion",
  "file": "uicomponents/Sources/Foo.swift",
  "line": 42,
  "issue": "...",
  "fix": "...",
  "crossRepo": false,
  "affectedRepos": []
}
```

`file` paths are repo-name-prefixed (`<repo-name>/<path-relative-to-repo>`) so triage and rework can resolve them back to the right worktree. The triage prompt receives `state.projects[]` so it can validate `affectedRepos` entries.

**Triage cross-check**: Cross-repo findings are NEVER auto-rejected  -  the cross-cutting nature is exactly what reviewers are best at catching. Triage may still defer (out-of-scope) but rejection requires explicit reasoning.

**Token-budget guard** (per `token-budget.json` Phase 4 allowance): if `COMBINED_DIFF` exceeds 80% of the budget, truncate the largest repo's diff with a footer header:
```
[truncated  -  full diff in file://$WORKTREE/.review-diff.txt]
```
And log `review.diff_truncated repo=<name> bytes_dropped=<N>`. Triage receives the same truncated view + the truncation marker so it can flag suggestions to "review the full diff manually."

**Deterministic gates per repo**: Step 1 gates (build/lint/test/secrets) run **per repo**  -  failure in any one repo blocks AI review for the whole task. Build queue lock applies as in Phase 3 (multi-repo doesn't change Xcode serialization).

**Telemetry**: Per-repo build/test gate timings + a single combined review/triage call set:
```bash
$HOME/.claude/scripts/log-metric.sh "$TASK_ID" 4 gate.build repo=common status=pass duration_ms=$D
$HOME/.claude/scripts/log-metric.sh "$TASK_ID" 4 gate.build repo=uicomponents status=pass duration_ms=$D
$HOME/.claude/scripts/log-metric.sh "$TASK_ID" 4 review.combined_diff repos=2 bytes=$BYTES truncated=false
```

---
