adversarial-review
v2.10.0
Published
Adversarial, ship/no-ship code review of a git diff or branch — runs against any LLM (API or local CLI agent) and returns structured findings
Downloads
2,634
Maintainers
Readme
adversarial-review
Skeptical, ship/no-ship code review of a git diff or branch — run against any LLM.
The reviewer's only job is to break confidence in a change, not validate it. It hunts for the strongest reasons a change should not ship yet, prioritizing the failure classes that are expensive, dangerous, or hard to detect: auth and trust boundaries, injection, secrets, data loss, rollback safety, race conditions, schema drift, supply-chain and CI/CD changes, test weakening, and observability gaps. Output is structured JSON — a verdict, a terse summary, coverage, grounded findings (severity, category, file, line range, confidence, exploit scenario, quoted evidence, recommendation), and next steps.
It collects your git context, builds the prompt, calls a model (Anthropic / OpenAI /
Gemini API, or a local CLI agent like claude / codex / agy) using the provider's
native structured-output mode, validates the response against the JSON Schema, grounds
each finding against the actual change set, and prints a report. The exit code is
derived deterministically from the findings (severity + confidence thresholds), so it
drops straight into CI and pre-push hooks — and it fails closed: a git collection
failure exits 1, never a silent approve.
Beyond a single pass it can fan the same review out across multiple independent provider
families and gate on a quorum (--providers), run an
autonomous review → fix → re-review convergence loop (--loop), and
review artifacts — specs, tickets, rail-sets — instead of a diff
(--input).
The review prompt (
prompt-template.md) and output schema (schema.json) are derived from the OpenAI Codexadversarial-reviewskill (Copyright 2026 OpenAI). They have been generalized, extended, and stripped of the Codex-specific runtime so the tool works with any model. This project is licensed under the Apache License, Version 2.0; see NOTICE for attribution details.
Install
npm install -g adversarial-review
# or run ad hoc:
npx adversarial-review --helpInstalling as a Claude Code skill
The npm package ships a bundled Claude Code skill under skills/adversarial-review/.
Installing it lets Claude Code invoke the review automatically when you type phrases like
"review this branch" or "is this safe to ship", and gives it the full three-tier fallback
(Tier 3 works with no CLI and no API key).
Global skill (available in all projects):
# After npm install -g:
cp -r "$(npm root -g)/adversarial-review/skills/adversarial-review" ~/.claude/skills/
# Or from a cloned repo:
cp -r skills/adversarial-review ~/.claude/skills/Project skill (this repo only):
mkdir -p .claude/skills
cp -r "$(npm root -g)/adversarial-review/skills/adversarial-review" .claude/skills/Restart Claude Code after copying. Verify with /find-skills adversarial-review or by asking
Claude Code "what skills are available?"
Usage
# Review uncommitted working-tree changes
npx adversarial-review
# Review the current branch against main
npx adversarial-review --base main
# Add a focus area (weighted heavily by the reviewer)
npx adversarial-review "focus on the token refresh path"
# Higher recall and precision: sample the reviewer twice, then try to refute
# each finding and drop the ones that don't survive
npx adversarial-review --passes 2 --verify
# Give an API model more code to reason about than the diff hunks alone
npx adversarial-review --include-files --context-lines 20
# Just print the assembled prompt — no LLM call (pipe it anywhere you like)
npx adversarial-review --prompt-only > prompt.txt
# Machine-readable output for CI
npx adversarial-review --base main --json
# Diverse review: fan the same prompt out to two distinct provider families and
# gate on a quorum (diversity, not count — see "Multi-provider review")
npx adversarial-review --providers claude,gpt --quorum 1
# Autonomous convergence: review → fix → re-review until clean (working tree)
npx adversarial-review --loop --loop-unsafeExample output
NEEDS ATTENTION working tree on branch main
Summary
Two findings worth addressing before shipping: an unguarded secret assignment
and a missing rate-limit on the new endpoint.
Coverage
4 file(s) examined
Findings (2)
CRITICAL [secrets] Hardcoded API key in environment helper conf 0.95
src/env.js:12-12
The string literal assigned to `STRIPE_SECRET` is a live API key, not a
placeholder. It will be committed to version control and included in the
review payload sent to the model provider.
✗ failure: Any developer cloning the repo or any CI system gains full
Stripe API access.
→ fix: Remove the key, rotate it immediately, and load from an env var or
secret manager instead.
MEDIUM [resource-exhaustion] /api/events returns unbounded results conf 0.80
src/routes/events.js:34-34
The database query has no LIMIT clause. A single request can return every
row in the events table.
✗ failure: A large events table causes the response to time out or OOM the
process under normal traffic.
→ fix: Add pagination (LIMIT + OFFSET or cursor-based) and document the
page-size cap in the API contract.
Next steps
• Rotate the Stripe key immediately — treat it as compromised.
• Add a LIMIT clause and pagination to the /api/events query.Options
# Review target & output
--base <ref> Review the current branch against <ref> (merge-base...HEAD).
--scope <mode> auto (default) | working-tree | branch.
--input <file(s)> Review artifact files (specs, tickets, rail-sets) instead of
a git diff. Repeatable + comma-separated. Cannot combine with
--base, --scope working-tree|branch, or --loop. See below.
--prompt-only Print the assembled prompt to stdout and exit (no LLM call).
--json Print JSON (verdict matches the derived exit gate) instead of a rendered report.
# What gets sent
--max-files <n> Inline-diff cutoff by changed-file count (default 50).
--max-bytes <n> Inline-diff cutoff by diff size in bytes (default 262144).
--context-lines <n> Diff context lines passed to git diff -U<n> (default 10).
--include-files Also inline full post-change file contents (budgeted).
--allow-summary-review Allow API providers to review summary-only large diffs.
--allow-unsandboxed-cli Allow claude/agy/agent/copilot review without plan/read-only
mode. For opencode this DISABLES the generated read-only
config entirely — the reviewer gets your own agent's
write/bash access on an untrusted diff.
--allow-secrets Send the payload even if the secret scan finds likely
credentials in the diff (off by default).
# Gate
--fail-on <severity> Gate threshold: critical | high | medium (default) | low.
--min-confidence <x> Findings below this confidence don't gate (default 0.5).
--fail-on-empty Exit 1 (instead of 0) when there is nothing to review.
# Recall / precision
--verify Second pass: drop a finding only with contradictory evidence.
--passes <n> Run the review n times and merge findings (default 1).
--providers <list> Multi-provider mode: fan the same review out to each family
token (e.g. gpt,gemini,claude) and merge with cross-provider
corroboration. "auto" picks >=2 distinct families. Diversity,
not count — distinct from --passes. Cannot combine with --provider.
--quorum <n> needs-attention when >= n providers each flag a material
finding (default 1).
# Provider
--provider <name> anthropic | openai | gemini | vercel | gateway |
cursor | agent | copilot | opencode | <local-cli-cmd>.
cursor/agent → Cursor Agent CLI; vercel/gateway → AI Gateway;
copilot → GitHub Copilot CLI; opencode → opencode (incl.
opencode-go models). copilot and opencode are explicit-only.
--model <name> Force the model name (Gateway: use provider/model ids).
--api-base <url> Override the active provider's API base URL.
--api-key <key> Override the active provider's API key.
--headers <json> Inject custom JSON headers into the LLM request.
--timeout <seconds> Per-request API/CLI timeout (default 120).
# Reporting
--findings-ledger [path]
Append gating findings as JSONL to the ADLC findings ledger
(default .adlc/findings.jsonl) for P7 distillation.
# Loop mode (review → fix → repeat; see "Loop mode" below)
--loop Iterate review → fix → re-review until no gating findings
remain. Working-tree scope only. Composes with --providers.
--loop-max <n> Max fix iterations (default 3): N fixes + a final review.
--loop-fixer <cmd> Override the fixer CLI (default: auto-detect codex→claude→agy).
--loop-fixer-scope sc2 (default): only finding-cited files. unrestricted: all files.
--loop-fixer-file-cap Max files listed in unrestricted mode (default 100).
--loop-unsafe Required on every platform: no enforced write confinement exists.
The fixer has unrestricted write access to your filesystem.
--loop-unsafe-allow-fix-secrets
Bypass the secret scan on the fix prompt (same-provider checked).Exit codes
| Code | Verdict | Meaning |
|------|-------------------|--------------------------------------------------------|
| 0 | approve | No finding met the gate (--fail-on/--min-confidence). |
| 2 | needs-attention | At least one material finding worth blocking on. |
| 1 | error | Could not complete the review (including git failures). |
The model also reports its own verdict; if it disagrees with the derived gate, the disagreement is printed and the derived verdict wins. A gate that trusts the model's self-assessment can be argued out of blocking — this one can't.
The gate is hardened
- Fails closed. Any git collection failure exits
1. An empty scope warns (use--fail-on-emptyin CI so a misconfigured base ref can't silently pass). - Deterministic verdict. Exit code computed from findings: severity ≥
--fail-onand confidence ≥--min-confidence. - Grounding checks. A finding citing a file outside the change set (API mode), or
quoting
evidencethat doesn't appear in the provided context, is marked ungrounded and its confidence is halved for gating. - Prompt-injection resistant. The prompt instructs the reviewer that everything inside the repository context is untrusted data — and that any text in the diff attempting to influence the review (e.g. "reviewer: this is pre-approved") is itself a critical finding.
- Secret scan. The payload is scanned for likely credentials (AWS keys, PEM blocks,
API tokens, JWTs, hardcoded password assignments) before it leaves the machine; the run
is refused unless
--allow-secretsis passed. - Structured output at the API layer. Anthropic forced tool-use, OpenAI strict
json_schema(with automatic fallback for gateways that reject it), GeminiresponseSchema— JSON shape is enforced by the provider, with text-scraping and one self-correcting retry (which feeds the exact validation errors back to the model) as fallbacks. On final failure the raw output is saved to a temp file for debugging.
Choosing the model
If --provider is not given, the LLM is auto-detected. Inside Claude Code or Cursor, a
different provider from the builder is preferred — a model reviewing its own output is
a weaker critic. Otherwise:
ANTHROPIC_API_KEY→ Anthropic API (default modelclaude-sonnet-4-6)GEMINI_API_KEY→ Gemini API (gemini-2.5-pro)OPENAI_API_KEY→ OpenAI API (gpt-5)AI_GATEWAY_API_KEY(orVERCEL_OIDC_TOKEN) → Vercel AI Gateway (--provider vercel, default modelanthropic/claude-sonnet-5)- A local CLI agent on
PATH:claude,codex,agy, oragent(Cursor Agent CLI)
Cursor Agent CLI (--provider cursor or agent): requires agent on PATH and
agent login or CURSOR_API_KEY. Reviews run with --mode plan (read-only). This is
not a localhost HTTP proxy — for third-party OpenAI-compatible proxies use
--provider openai --api-base <url>.
GitHub Copilot CLI (--provider copilot): requires copilot on PATH and
copilot login. Reviews run with --mode plan (read-only). Explicit-only — it is
never auto-detected, and it does not count toward cross-family diversity: Copilot
routes to Claude, GPT, or Gemini depending on --model, so treating it as an independent
family would fake the diversity a --providers run reports. On Windows an npm-installed
copilot is a .cmd shim and is refused: Copilot takes the prompt as a command-line
argument, which cmd.exe would re-parse — use --provider claude or codex there.
opencode (--provider opencode): requires opencode on PATH and an authenticated
provider (opencode auth list). Its opencode-go provider reaches models no other
backend here serves — grok-4.5, kimi-k3, qwen3.7-max, glm-5.2, deepseek-v4-pro,
minimax-m3 — which makes it a genuinely independent critic of Big-3-authored code.
Default model opencode-go/grok-4.5; override with --model opencode-go/<name>.
Explicit-only and, like copilot, not a diversity family.
opencode has no read-only flag, so reviews run under a generated config (supplied via
OPENCODE_CONFIG) declaring a dedicated agent that can read the repository —
read, grep, glob, list allowed — with edit, bash, task, webfetch,
websearch, external_directory and the rest denied. Do not pass --agent plan
yourself — opencode treats plan as a subagent and silently falls back to your
default agent, which typically permits writes. --allow-unsandboxed-cli opts out and
runs under your own config, with a warning.
--stream is ignored for opencode: under --format json every tool result is an
event on stdout, and streaming mirrors those to stderr — which would put file
contents the reviewer opened straight into a CI log. The review still returns
normally; only the live progress output is suppressed.
Secrets are scanned on the way out as well as the way in. The pre-flight scan
covers the outbound payload (the diff and its context). It cannot cover what a
tool-using reviewer reads: with read/grep, a model can open a gitignored .env
that was never in the diff and quote it into a finding. So the review response is
scanned too, before it is rendered, printed as --json, or written to the findings
ledger. Matches are masked in place and the redaction is announced on stderr.
Redaction is surgical rather than dropping the field, because "you committed a live key" is one of the most valuable things this tool reports — the finding survives with the credential masked. The scan is heuristic, so treat review output from a tool-enabled provider as sensitive regardless.
The permission block enumerates every key in opencode's schema, which matters for
two non-obvious reasons: an unset key defaults to "ask", and an interactive prompt
in a headless run blocks forever; and neither "*" nor write means what it looks
like — "*" is not a wildcard, and modification is governed by edit, so denying
write denies nothing. tools: { write: false } is not a control either. Verified
against the real CLI: reads work, writes are blocked, shell commands are blocked.
Three further details are load-bearing, because OPENCODE_CONFIG merges with local
config rather than replacing it — and that merge includes project-local opencode.json
from the repository being reviewed:
- The agent name is random per run. A repo that ships an
opencode.jsonredefining the review agent by name wins the merge and re-enableswrite/bash. A fixed name is one the attacker knows; a per-run name cannot be written into a file in advance. --pureis always passed, so a reviewed repo cannot load a plugin — plugins run code, which would make tool permissions moot.- A preflight runs before the diff is sent.
opencode agent listmust report our agent asprimaryin the merged config. This is checked up front because under--format jsona silent downgrade to your default agent produces no output on any stream — there is nothing to detect afterwards.
Vercel AI Gateway (--provider vercel or gateway): one key, many provider/model
ids (e.g. openai/gpt-5.6-sol, anthropic/claude-sonnet-5, google/gemini-2.5-pro). With
only AI_GATEWAY_API_KEY set, --providers auto can fan across those families through
the Gateway (native vendor keys still win when present). Family token anthropic (not
the CLI-only token claude) selects the Anthropic family via Gateway/API.
Force any of them with --provider, and override the model with --model. Defaults are
the strong tier of each provider — gate quality tracks model tier; downgrade with
--model deliberately, not accidentally.
No key and no CLI agent? Use --prompt-only to emit the prompt and feed it to a model
yourself.
Global config & resolution caching
Running the review repeatedly (a batch, a CI matrix, a loop) shouldn't re-walk the
detection ladder — re-probing every CLI on PATH — each time. On the first auto-detect
the resolved provider is cached in a global config file and reused on later runs:
$ADVERSARIAL_REVIEW_CONFIG # explicit override, else…
$XDG_CONFIG_HOME/adversarial-review/config.json # else…
~/.config/adversarial-review/config.json{
"defaults": {
"models": { "gemini": "gemini-2.5-pro", "openai": "gpt-5", "cli:agy": "gemini-3.1-pro-high" }
},
"cache": {
"default": { "provider": "cli", "cliCmd": "agy", "family": "gemini", "model": null }
}
}This is strict JSON — no comments, no trailing commas (a syntax error makes the
whole file ignored, though never overwritten). defaults.models pins the model per
provider (precedence --model > pin > built-in default); a local CLI is keyed as
cli:<cmd>. cache is auto-written, keyed on the builder context
(claudecode/cursor/antigravity/default); its family is advisory — the diversity guard
recomputes it from provider/cliCmd, so editing it by hand can't weaken the review.
Reuse is safe by construction, not blind trust:
- Re-validated every run. A cached CLI must still be on
PATH; a cached API provider must still have its key. Any miss falls through to a full re-detection (onePATHstat / env read — far cheaper than the ladder). This is the "unless the right path doesn't work, reprobe" fallback. A cached CLI additionally defers to any API/gateway provider that outranks local CLIs in the current context, so a stale CLI name can't shadow a safe API key that became available since the resolution was cached. - Diversity-preserving. The cache is keyed on the builder context, so a resolution made inside Claude Code can never be served to a Cursor or plain-shell run, and a resolution is never reused if its family is the builder's own family — the review stays adversarial.
- No repository-local reviewer binary. A single trusted resolver is used by fresh
detection, cache reuse, and the spawn itself: it canonicalizes the executable (resolving
symlinks) and refuses any CLI that resolves inside the working tree — so a repo shipping a
node_modules/.bin/claudeshim (npm/npx put that dir onPATH) can be selected or executed by none of them. A cached CLI also records its canonical path and must still resolve to it, so a swapped/shadowed binary isn't reused. - Config can't come from the repo. The trust root is the whole git worktree (not
just the cwd, so a repo-root config is refused even from a nested package). A config path
is honored only when it is absolute and outside the worktree (symlinks resolved).
An explicit
ADVERSARIAL_REVIEW_CONFIGthat is relative or inside the tree disables config (no read, no write) rather than silently falling back to your personal~/.config; a relativeXDG_CONFIG_HOMEfalls back to the home default. A repository you review can't supply model pins or cache entries. - Cached only after success — everywhere. A resolution is persisted only after a review
completes successfully (in normal and
--loopmodes, per round); a cache-sourced provider that fails because its resolution is dead — a revoked/expired credential or a retired/invalid model — is invalidated and re-detected once with that provider excluded, so a stale entry can't stick. Transient failures (timeouts, 5xx) keep the cache. - Concurrency-safe. Cache writes reread-and-merge under a lock, so parallel batch/matrix runs sharing one config don't clobber each other's entries or a model-pin edit.
defaults.models is yours to author; cache is written automatically after the first
successful run (silently). Delete the file to force a fresh detection.
Multi-provider review (--providers)
--passes samples one model N times; --providers fans the same review out to
several independent providers and merges the results. The value is diversity, not
count — a second model from a different family catches failure modes the first is blind
to.
# Two distinct families, quorum 1 (any one provider's material finding gates)
npx adversarial-review --providers claude,gpt
# Let the tool pick >=2 distinct families for you (never the builder's own family)
npx adversarial-review --providers auto --quorum 2- Family tokens (
gpt,claude,gemini,openai,anthropic, …) each resolve to the best reachable provider — the API when its key is present, otherwise the local CLI. - Merge + corroboration. Findings are merged by
(file, category, overlapping lines); a finding raised by more than one provider is kept as one entry tagged withcorroborated_by. Distinct findings at the same location are preserved, never collapsed. - Quorum verdict. The result is
needs-attentionwhen the number of providers that each raised a gating finding is ≥--quorum(default1);approveonly when that count is0. - No silent downgrade. If fewer providers are reachable than requested, the run emits a loud under-satisfaction notice and proceeds with what is available.
--providerscannot be combined with--provider(or--model).
Loop mode (--loop)
--loop runs an autonomous review → fix → re-review cycle until no gating finding
remains (or a stop condition is hit). Each round reviews the working tree, hands the gating
findings to a fixer CLI that edits the files, then re-reviews. It composes with
--providers (each round is gated by the quorum verdict).
# Iterate until clean, capped at 3 fix rounds
npx adversarial-review --loop --loop-unsafe --loop-max 3- Working-tree scope only. The fixer writes to the working tree, so
--loopis incompatible with--scope branch/--base. (Branch-scoped convergence is not yet supported.) - Fixer. Auto-detected in order
codex → claude → agy; override with--loop-fixer. - Write Confinement. On Linux, kernel-level write confinement via bubblewrap (
bwrap) restricts fixer writes strictly to the target workspace (blocking~, credentials, parent directories, and other repositories). Whenbwrapis active,--loopruns securely without requiring--loop-unsafe. On macOS or Linux systems withoutbwrap/Landlock,--loop-unsafeis required to proceed. - Checkpoints. The working tree is stashed before each fix; on a fixer error or timeout the checkpoint is restored, and the recovery command is always printed.
- Stop conditions (exit
2):no-progress(the gating set repeats),ceiling(--loop-maxreached),no-diff(the fixer changed nothing), orfixer-error/fixer-timeout. Acleanexit is0.
Machine-readable loop output (--json)
With --json, the loop emits NDJSON events (loop_start, review, review_result,
stash_created, fix, loop_end). The terminal line is a single consolidated
loop_summary event carrying everything a run's evidence record needs:
{ "type": "loop_summary", "providers": ["claude", "gpt"], "iterations": 2,
"verdict": "needs-attention", "exitReason": "ceiling",
"survivingCount": 3, "acceptedCount": 0 }verdict is derived from exitReason (clean ⇒ approve); survivingCount is the
gating findings still unresolved at exit; acceptedCount is always 0 (accepting a
finding "with documented justification" is a human decision the loop leaves to you). It is
copy-pastable straight into an ADLC P6
gate-manifest evidence entry:
adversarial-review --loop --json ... | jq -c 'select(.type=="loop_summary")'Branch mode: pre-merge convergence (--loop --scope branch)
By default --loop reviews and fixes the working tree. With --scope branch
(or --base <ref>) it drives a pre-merge convergence loop: review the feature
branch against its base, commit each accepted fix onto the branch, and
re-review until clean — the loop ADR-0007's 18-round Cursor case study had to run
by hand.
# Converge the current branch against main, committing fixes as it goes
npx adversarial-review --loop --base main --loop-unsafeGit safety — this only ever touches your feature branch:
- The base ref (e.g.
main) is read-only — resolved to a sha at loop start and only ever diffed. Every write (commit,reset --hard,clean) targets the feature branch'sHEAD; the base is never a write target under any path. - A clean working tree is required at start: that guarantee is what makes the
loop's
git add -A/reset --hard/clean -fdsafe — any file appearing mid-loop is fixer-created, so discarding it can never touch your own work. - Rollback: a failed/partial fix is
reset --hardto the pre-fix commit; ano-progress/ceilingexit leaves the fix commits and prints agit reset --hard <original-HEAD>command to undo them all. SIGINT never auto-resets — it prints the recovery command and exits. - Fix commits use
--no-gpg-sign(disposable per-round commits you'll squash-merge; a signing prompt would otherwise hang the non-interactive loop). - A pushed branch is warned (a reset will diverge it from its remote — a
force-push would be needed) but not refused. A detached HEAD is refused, and
an explicit
--scope working-treealways wins over--base.
Recording findings for later distillation (--findings-ledger)
--findings-ledger [path] appends each gating finding as a JSONL line to an
ADLC findings ledger (default
.adlc/findings.jsonl), so repeated review findings can later be distilled (P7) into
permanent, deterministic defenses. A ledger write failure only warns — the verdict and
exit code are the product, the ledger is a side effect.
Reviewing artifacts, not diffs (--input)
Adversarial review isn't only for code. --input <file(s)> reviews arbitrary
artifacts — a spec, a ticket, a design doc, a declared set of rails/invariants
— with the same schema, grounding, --verify, --providers/--quorum,
--passes, --findings-ledger, --json, and --prompt-only machinery. Only the
review target and the charter change: instead of "find the strongest reasons this
diff shouldn't ship", the model hunts for ambiguity, missing or untestable
acceptance criteria, unhandled failure modes, unaddressed trust-boundary/security
concerns, internal contradictions, and unenforceable invariants.
# Review a spec before anyone writes code
npx adversarial-review --input docs/spec.md
# Review a ticket + its rail-set together, with two provider families
npx adversarial-review --input ticket.md,rails.md --providers claude,gpt
# Emit the artifact-review prompt to feed a model yourself (no LLM call)
npx adversarial-review --input spec.md --prompt-onlyThis makes design/spec review (and rail-set adequacy review) a first-class, schema-validated, recordable gate rather than an informal one-shot prompt.
- Fail-closed.
--inputreviews exactly the files you name — there is no summary fallback and no silent skip. A missing, binary, or oversized file, or an artifact with no reviewable text, is an error (exit 1), never a review that "approves" having read nothing. - Per-file size cap. In this mode
--max-bytesis the per-file cap (raise it to review a larger single artifact); there is no aggregate budget. - Mutually exclusive with the git-diff targets (
--base,--scope working-tree|branch) and with--loop(an artifact has no working tree to fix).
How it decides what to send
- Small change (≤
--max-filesfiles and ≤--max-bytesdiff bytes): the full diff (at-U<--context-lines>) is inlined and used as primary evidence. Add--include-filesto inline the full post-change contents of changed files too (budgeted at 4×--max-bytes), which materially improves API-model review quality. - Large change: only a summary (status, shortstat, file list) is inlined, and the model is told to inspect the diff itself with read-only git commands — useful when the model has shell/tool access.
For API providers, summary-only large-diff reviews fail closed by default because the model
cannot inspect your local repository. Use a local CLI provider, raise the inline limits,
narrow the scope, or pass --allow-summary-review if you intentionally want an API model
to review only the summary.
Privacy: whatever is collected is sent to the configured model provider. Treat the review payload as leaving your machine.
CI example
# .github/workflows/review.yml
- run: |
set -o pipefail
npx adversarial-review --base "origin/${{ github.base_ref }}" \
--fail-on high --fail-on-empty --json | tee review.json
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
# Exit code 2 fails the job when a finding meets the gate.
# set -o pipefail ensures the exit code from adversarial-review is not swallowed by tee.
# --fail-on-empty guards against a misconfigured base ref silently passing.
# Note: use actions/checkout with fetch-depth: 0 so merge-base resolution works.Project layout
| Path | Purpose |
|--------------------------|----------------------------------------------------------------|
| bin/cli.js | CLI entry point: secret gate, grounding, deterministic verdict. |
| src/git-context.js | Collects git status + diffs (fail-closed), inline/summary rule. |
| src/artifact-context.js| Collects --input artifact files (fail-closed) for artifact review. |
| src/review.js | Prompt build, run/verify/multi-pass, multi-provider merge, quorum verdict, rendering. |
| src/loop.js | --loop orchestration: fixer spawn, stash checkpoints, NDJSON events. |
| src/llm.js | Provider config + structured-output call wrapper (API and CLI). |
| src/schema-validate.js | Minimal JSON Schema walker + provider schema sanitizer. |
| src/secrets.js | Outbound payload secret scan. |
| src/findings-ledger.js | Appends gating findings to the ADLC findings ledger (--findings-ledger). |
| src/utils.js | Arg parsing, logging, help text. |
| prompt-template.md | The diff-review prompt (4 placeholders). |
| prompt-template-artifact.md | The --input artifact-review prompt (same placeholders/schema). |
| schema.json | JSON Schema the model output must conform to. |
prompt-template.md and schema.json are plain assets — edit them to tune the review or
the output contract without touching code. The runtime validates against schema.json
itself, so schema edits really do change the enforced contract. npm run sync-skill
copies the review prompts (prompt-template.md, prompt-template-artifact.md) and
schema.json into both skill trees (skills/ and .agents/skills/); a test fails
if they drift.
Changelog
See CHANGELOG.md for release history.
License
Apache License, Version 2.0 © Chris Williams (@voodootikigod). See LICENSE and NOTICE.
