Primary, secondaries, human gate — everyone checks everyone.
An opt-in, human-gated code/design review where Claude (the primary) fans a doc or PR out
to N independent secondaries, adjudicates their findings, and converges — always stopping at a
human approval gate. fable is one secondary by default (Claude's guaranteed review voice),
and MULTI_REVIEW_FABLE=off drops it so a run spends no Claude tokens on review; add
cross-vendor reviewers (codex, gemini) for independent perspective.
One command, one model — star — for both local design docs and GitHub PRs.
flowchart TB
A["/multi-review <doc | PR> --reviewers codex,gemini"] --> P
subgraph R["each round · up to MULTI_REVIEW_MAX_ROUNDS (default 5)"]
direction TB
P["Claude · primary<br/>(neutral — adjudicates,<br/>never authors findings)"]
P -- blind copy --> F["fable · default"]
P -- blind copy --> C["codex"]
P -- blind copy --> G["gemini"]
F & C & G -- findings --> M["merge + verify-vendor<br/>(impostor → quarantine, not fail)"]
M --> J["primary agrees /<br/>disputes each"]
J -- "re-fan only on a<br/>high-severity fix" --> P
end
J -- "converge (1 round<br/>by default)" --> H["HUMAN GATE<br/>+ warn if no cross-vendor reviewer"]
H -- "PR only, on approval" --> Pub["one neutral gh pr review"]
/plugin install agrology/multi-review # direct
# or browse + get updates via the plugin UI:
/plugin marketplace add agrology/multi-review
/plugin install multi-review@agrology
Installs the /multi-review command + its scripts. The marketplace path also lists the plugin in
Claude Code's plugin UI with version tracking / update prompts. Reviewer setup depends on which
reviewers you add (below) — fable needs none.
If your design docs live somewhere other than the defaults below, set MULTI_REVIEW_DOC_DIRS
before the first run — the egress guard refuses to arm on a path outside it, so a repo with any
other layout is blocked until it is set:
Space-separated; individual dirs cannot contain spaces.
Migrating from the dual-review predecessor? It used DUAL_AGENT_DOC_DIRS. That variable is
gone and nothing inherits it — if your .claude/settings.json set it, replace it with
MULTI_REVIEW_DOC_DIRS, or drop it entirely if your docs are under either default layout.
Contributing to this repo? Run scripts/multi-review-install-hooks.sh once per clone. It
points core.hooksPath at the versioned .githooks/, whose pre-push refuses a push that has
not raised .claude-plugin/plugin.json's version — the comparison installed copies use to
decide whether to offer an update. git push --no-verify bypasses it for a WIP branch.
CI (.github/workflows/gate.yml) runs the full gate on every PR: the shell test suites on Linux
and macOS (the latter on /bin/bash 3.2, the version these scripts target), shellcheck, the
version-bump check, and a mutation sweep that deletes each security guard in turn and fails
unless a named test catches it — because a green suite is not by itself evidence that a guard bites.
/multi-review docs/specs/2026-01-01-my-design.md # local doc, fable only
/multi-review docs/specs/2026-01-01-my-design.md --reviewers codex # + codex (cross-vendor)
/multi-review https://github.com/owner/repo/pull/42 --reviewers codex,gemini- You don't have to type the path. Just say "multi-review the spec / the plan / this PR" —
it resolves to the doc in context, or the newest dated doc under
MULTI_REVIEW_DOC_DIRS. - Name reviewers in plain language. "multi-review the spec with codex and gemini" is
equivalent to
--reviewers codex,gemini. - The combo is remembered per repo. The last explicitly-named set is saved to
.multi-review/reviewers.pref; a later bare run reuses it (self-healing — a reviewer that isn't set up is dropped for that run with a notice, not an error). Say "forget the reviewers" to reset to fable-only (which, underMULTI_REVIEW_FABLE=off, leaves nothing — the next run refuses to arm rather than self-reviewing). - PR refs also accept
owner/repo#nand, in the current repo,#n— plus the forms people actually type: a bare123, orPR 123/pr#123(case-insensitive). Anything path-shaped still resolves as a local doc. - The set is
(--reviewers) ∪ {fable}by default.MULTI_REVIEW_FABLE=offdrops the∪ {fable}union, leaving just what you named; namingfableexplicitly still includes it. With the union off and nothing else usable, the run refuses to arm rather than self-reviewing. - Local docs are found under
MULTI_REVIEW_DOC_DIRS(defaultdocs/specs docs/plans docs/superpowers/specs docs/superpowers/plans— both the plain andsuperpowerslayouts); pass an explicit path otherwise. - On a PR,
ghingests the diff into a gitignored scratch file; on approval the primary posts one neutralgh pr review(anchored findings inline, the rest in the summary). Needsgh+jq.
| reviewer | vendor | setup |
|---|---|---|
fable (on by default) |
anthropic | none — runs in-harness |
codex |
openai | codex CLI authed — skill provisioned automatically per run (git-ignored) |
gemini |
gemini CLI authed + 3 settings (below) |
codex skill: provisioned automatically. On each run /multi-review materializes
this repo's .agents/skills/multi-review/ into the reviewed repo's root (git-ignored via
.git/info/exclude + an in-dir .gitignore); nothing to copy, nothing committed. A
.agents/skills/multi-review/ you commit yourself is respected and left untouched (and
flagged as possibly drifting from the installed plugin). An untracked copy (e.g. a stale
manual copy from before auto-provisioning existed) is never touched either — auto-provisioning
refuses to run until you remove it, and --check-reviewers/doctor will flag that too.
gemini prereqs (in order — the first is the blocker):
export GEMINI_CLI_TRUST_WORKSPACE=true(or trust the folder once). An untrusted workspace makes the CLI skip.env(so it can't authenticate — the error misleadingly says "set an Auth method") and disables file edits, so the reviewer can't write the doc.- An API key —
export GEMINI_API_KEY=…, or dropGEMINI_API_KEY=…in~/.gemini/.env(or your repo's.env). It auto-loads once the workspace is trusted. .gemini/settings.json→{"context":{"fileFiltering":{"respectGitIgnore":false}}}. Needed whenever the docs gemini must read are gitignored — your doc dirs, or PR mode's.multi-review/scratch — which is the common case.--check-reviewersnames the exact paths when this applies and stays silent when it does not, anddoctorreports it separately from auth (a reviewer that authenticates but cannot read the doc is not "ready"). The free tier's daily cap will exhaust a multi-round review.
Gemini does not need the protocol on disk: skill-less reviewers get the contract inlined in their dispatch prompt, so it reaches them regardless of workspace root or ignore rules.
Run /multi-review --check-reviewers to verify every reviewer's setup at a glance.
| env | default | meaning |
|---|---|---|
MULTI_REVIEW_REVIEWERS |
(empty) | comma set of extra secondaries, e.g. codex,gemini (per-run: --reviewers) |
MULTI_REVIEW_FABLE |
on |
off/0/false drops the default in-harness fable secondary, so a run spends no Claude tokens on review. Naming fable explicitly (--reviewers fable) still includes it. With no other secondary available the run refuses to arm rather than self-reviewing. An unrecognised value is a hard error. |
MULTI_REVIEW_MAX_ROUNDS |
5 |
round ceiling (each round costs N dispatches; convergence is adaptive) |
MULTI_REVIEW_REVIEWER_MODEL |
(provider default) | pin a provider's model (codex→gpt-5.6-terra, fable→fable, gemini→gemini-pro-latest) |
MULTI_REVIEW_DOC_DIRS |
docs/specs docs/plans docs/superpowers/specs docs/superpowers/plans |
where bare-name local docs are resolved. Covers the plain and superpowers layouts out of the box. Bare-name resolution warns when a newer dated doc sits in a directory it did not search — the egress guard cannot catch that, since the doc it picked is legitimately inside the configured dirs. |
MULTI_REVIEW_GEMINI_AUTOTRUST |
(off) | =1 scopes GEMINI_CLI_TRUST_WORKSPACE=true to the gemini dispatch (no profile edit needed). Security: trusting a workspace lets gemini honor its .env/settings and auto-edit — enable only for repos you trust, never a freshly-cloned one. |
The last explicitly-named reviewer combo is remembered per repo in .multi-review/reviewers.pref
(gitignored). It is written when reviewers are named (flag or prose) — except when
MULTI_REVIEW_REVIEWERS is set, which would shadow the pref anyway, so the write is skipped with a
notice. It sits below MULTI_REVIEW_REVIEWERS in precedence, and self-heals — a remembered
reviewer that isn't available in the repo is dropped on read (with a notice), never poisoning the
run. Reset it with a "forget the reviewers" request.
- Neutral primary. Claude only agrees/disputes secondaries' findings — it never authors its
own. That's the anti-rubber-stamp property.
fableis the default secondary so Claude's review voice is still heard, but adjudicated like any other. - Blind, independent copies. Each secondary reviews its own copy and never sees the others — uncorrelated perspective, not consensus.
- Identity-checked. After each turn,
verify-vendorconfirms the finding's> — viadisclosure maps to the selected provider's vendor. A mismatch or a no-show → that secondary is quarantined (set aside, surfaced), and the round proceeds on the rest. - Adaptive rounds. The primary re-fans-out while a round still surfaces new findings, up to
MAX; it converges the moment a round goes dry. Convergence is coverage — every finding has an agree/dispute — not agreement. - Independence, honestly. The gate warns when no cross-vendor secondary was admitted (a
fable-only run shares Claude's lineage — real value, weaker claim). Silence = a cross-vendor perspective was present. - Human gate. Nothing auto-merges or auto-posts; the run stops for you to decide.
> — vialines are self-claims, and models misreport their own identity (agemini-3.1-proturn has disclosed itself asgemini-2.5-pro). That's whyverify-vendorchecks the vendor, not the exact id — treat the line as who answered, not a version.
commands/multi-review.md— the/multi-reviewcommanddocs/multi-review.md— the star protocol contract (also vendored into the reviewer skill).agents/skills/multi-review/— the self-contained reviewer skill, auto-provisioned into consumer repos forcodex(git-ignored).claude-plugin/plugin.json— plugin manifestscripts/multi-review-star.sh— star grammar: mode / resolve-set / merge / open-findings / check-converged / gate-summary / blind-check / channel-check / composescripts/multi-review-reviewer.sh— provider registry: resolve / check / prompt / command / ensure-skill / doctor / verify-vendor / vendor-of-modelscripts/multi-review-pr.sh— PR ingest (viagh),refreshfor a later round, per-round head/merge-base records, content-based anchor remapping, a digest-verified diff window (the writer records the SHA-256 of the section body it composed; readers accept the one section that matches, so neither the PR description nor the review channel can move the window), + the one-neutral-review publishscripts/multi-review-scope.sh— diff-scoped copies for round N≥2:local-copy(docs — fence-aware region mapping) andpr-copy(PRs — what the author pushed since the last round as agit diff -W -U10delta with function context, guarded against rebases, forward merges, and a scoped copy that comes out no smaller than the full diff it replaces). Every path that cannot scope exits 3 with its reason and the round falls back to the full artifact, announced.scripts/multi-review-core.sh— marker state;-wait.sh— bounded per-copy wait;-egress-guard.sh— path validation;-build-reviewer-bundle.sh— regenerate the skill bundle;-history-check.sh— pre-publish sensitive-term gate (seePUBLISHING.md)scripts/*.test.sh— one suite per script (the gate below)CLAUDE.md/AGENTS.md— this repo's engineering agreement (§11 = multi-review specifics)
for t in scripts/*.test.sh; do bash "$t" || exit 1; done
Runs on macOS /bin/bash 3.2 and modern bash; keep shellcheck --severity=warning scripts/multi-review-*.sh clean.