0075 — Upgrade the AI code-review pipeline: models, rendered evidence, merge discipline, and measurement

Context

The goal driving this ADR: increase shipped quality — fewer bugs (especially UI bugs), more component reuse and consistency, on-brand customer-facing copy, and testing depth (unit tests are never sufficient; prefer the E2E harnesses). The trigger question was "should we bump the review bot's model?", which turned into a full audit of the review pipeline (2026-07-01): CI/CD inspection, a 12-case study of bugs that shipped past review, a reviewer-prompt-vs-standards gap analysis, Trello bug-corpus mining (109 bug cards in 60 days), team-meeting transcript mining, and research on current claude-code-action capabilities.

Findings (each verified during the audit)

  1. The bot was effectively Sonnet-only and a generation behind. The size-tier table pinned claude-sonnet-4-6 for tiny/medium/large and claude-opus-4-7 for huge/mega — but every one of the last 30 PR reviews ran on Sonnet 4.6 (no PR reached an Opus tier). Turn budgets had large headroom (large tier: ~18–30 of 100 turns used).
  2. Most shipped bugs are invisible to a diff-only reviewer. Of 12 traced shipped bugs (fix-up PR → origin PR → what the bot said), 9 required a rendered page, a click-through, or realistic prod-shaped data to detect (stale statuses, blank sections, nav traps, wizard spinners, mislabeled channels). The dominant Trello/UI classes match: stale state after backend events, rendering regressions with real data, jargon/label wrongness leaking to customers, hidden/broken controls.
  3. The bot catches real bugs that get merged anyway. In 2 of the 12 cases the bot named the exact bug, file, line, and fix (PR #2572's ← Back to ProspectInquiry label; PR #2793's stale urgent flag) — and the PR merged with the finding open. Root cause: the review check was not in main's required status checks (verified: only Build / Type Check / Unit Tests / Vercel; the active ruleset requires 0 approvals). The entire verdict-encoder/override machinery was advisory.
  4. Lint was enforced nowhere. No CI lint job existed; the review bot's "mandatory lint verification" runs in a checkout without node_modules (env-only failure, waved through). Running lint for real surfaced 1,164 pre-existing errors and one genuine syntax error in scripts/ (outside tsconfig, so tsc never saw it).
  5. Verdict-marker drift. None of the last 30 review summaries carried the <!-- claude-review-summary --> marker (the bot rides the action's track_progress comment) — the verdict encoder has been living on its fragile last-##-comment fallback, the exact failure class of the #962/#967 incident.
  6. Reviewer prompt gaps vs. our own standards (from the gap analysis): stale cross-references ("universal lens 6" → actual Lens 8; arsenal registry path pointing at a nonexistent file), Lens 26 requiring true regression tests only for classification bugs, e2e/** matching no lens-router row (the harness-first lens never fired on the E2E dir), only 11 of 89 ADRs cited anywhere in the corpus (ADR-0047/0052/0071 missing despite incident-grade importance), no DetailPageShell enforcement, and severity inconsistency (the same raw-<select> violation was 🔴 in one PR and a nit in another).
  7. Platform facts (verified against docs, 2026-07-01): the bot runs on a dedicated Claude subscription (OAuth token + fallback token); Opus 4.8 is plan-included and selectable in CI; Sonnet 5 exists (claude-sonnet-5, the API's current sonnet alias); --effort {low..max} flows through claude_args; Fable 5 switches from plan-quota to paid usage credits after 2026-07-07 and runs cyber-domain safety classifiers that can refuse security-adjacent review content.

Decision

Seven decisions, first three already landed as PRs on 2026-07-01:

D1 — Model bump (PR #2832). Tier table: tiny → claude-sonnet-5; medium → claude-opus-4-8 (effort high); large/huge/mega → claude-opus-4-8 (effort xhigh). claude.yml (@claude re-review/work): claude-opus-4-8, effort xhigh. Turn budgets unchanged (headroom verified). Rationale: Opus 4.8 is specifically improved at code review / real-bug finding; the dedicated subscription makes per-token cost a non-issue.

D2 — Blocking lint via ratchet (PR #2836). New Lint job in ci.yml wired into the gate job (enforced through the existing required Build & Test check). Pre-existing violations captured in an ESLint 9 native bulk-suppressions baseline (eslint-suppressions.json); only NEW errors block. .next.backup/** added to eslint ignores (local OOM fix).

D3 — Reviewer-prompt hardening (PR #2837). Rendered-evidence policy (§J in the UI lens: UI-touching PRs must include screenshots/recording incl. one unhappy state; reviewer judges the pixels against brand/copy/loading rules); Lens 26 strengthened (every bugfix ships a regression test that fails pre-fix/passes post-fix; rendered-only failures may use a Playwright probe or documented preview repro); stale-reference fixes; e2e/** router row; ADR-0047/0052/0071 enforcement; DetailPageShell as canonical detail layout; marker-drift fix; environment-honesty rule for the bot's tsc/lint claims.

D4 — Make the bot's verdict a real merge gate. Add review to main's required status checks. The belt-and-suspenders infrastructure (fallback token, override workflow, admin merge) already mitigates bot-outage risk. This closes the "caught it, merged anyway" class — the single most fixable gap found.

D5 — Preview-based UI verification (follow-up workstream). The structural fix for finding #2: every PR already has a Vercel preview deploy; the bot reads diffs. Rollout: (a) deterministic screenshot step for UI-touching PRs — resolve preview URL (the await-preview job already does), sign in as the smoke user, screenshot routes mapped from changed files, capture console/hydration errors, post to the PR; (b) feed screenshots to the reviewer + allowlist a preview-shot script so it can adaptively capture suspicious states; (c) Playwright interaction probes against the preview for the top surfaces (renewal detail, prospect conversation, tours, work orders, turnovers) — grown by turning every shipped UI bug into a preview probe (the regression-test rule, extended to UI). Author-attached screenshots (D3's §J) are the interim layer, not the end state: self-reported evidence is happy-path-biased.

D6 — Measure review quality with a replay eval, not vibes. Before/after evidence for this (and any future) reviewer change:

D7 — ADR-sync cadence. The lens corpus drifts from the ADR corpus (~20 ADRs shipped in June alone; 11/89 cited). Monthly: list ADRs newer than the reviewer corpus's last touch and fold the enforceable ones into lenses (candidate for a small CI reminder script).

Consequences

Alternatives considered

References