Run the panel in five minutes
atcr is one Go binary with three faces — a CLI, an MCP server, and a companion Agent Skill — all running on the same engine. It is local-first and bring-your-own-keys: nothing leaves your machine except the model calls you configure and the telemetry surfaces described below.
Install
Requires Go 1.25 or newer.
# 1. install the binary go install github.com/samestrin/atcr/cmd/atcr@latest # 2. one-command onboarding — scaffold .atcr/ + set up the Synthetic provider atcr quickstart
atcr quickstart is the one-command default: it scaffolds
.atcr/, sets up the Synthetic flat-rate provider, walks
you through the single API-key environment variable (the key is never written to atcr's
config), and scaffolds a .github/workflows/atcr.yml — so you reach your
first review without hand-editing registry.yaml. Prefer to wire a provider
by hand? atcr init writes .atcr/config.yaml — the roster
(which agents run, in which lane) plus project defaults such as
payload_mode and fail_on — and drops the editable persona
files under .atcr/personas/; then follow "Configure providers" below.
Configure providers
Providers and agents are defined in ~/.config/atcr/registry.yaml. Each
provider is a base_url plus an api_key_env naming the
environment variable that holds the key — resolved at invoke time and never written to
disk:
# ~/.config/atcr/registry.yaml
providers:
openrouter:
base_url: https://openrouter.ai/api/v1
api_key_env: OPENROUTER_API_KEY
agents:
greta:
provider: openrouter
model: claude-sonnet-4.6
export OPENROUTER_API_KEY=sk-...
The roster in .atcr/config.yaml lists which of those agents actually run —
add or remove an agent name to change who reviews. Resolution runs
CLI flag > .atcr/config.yaml > registry.yaml > embedded default. A
repo can also ship its own providers in .atcr/registry.yaml; because a
project-defined provider could direct a key at an arbitrary endpoint, atcr gates them —
run atcr trust to authorize one before any review will use it.
Verify with doctor
Before spending a real review, self-test every configured endpoint:
$ atcr doctor AGENT PROVIDER MODEL STATUS SOURCE greta openrouter claude-sonnet-4.6 ok user bruce openrouter gpt-4o ok user kai local llama-3.3-70b ok project # exit 0 — every agent has a working invocation path
review
Resolve the git range, build the payload, and fan it out to the roster. Zero arguments
works on a feature branch — the range resolves against the default branch and the review
id is recorded in .atcr/latest.
$ atcr review → range main..HEAD (4 files, 211 lines) → fan-out 5 panelists · parallel ✓ greta 3 findings ✓ bruce 2 findings ✓ kai 1 finding ✓ mira 2 findings ✓ otto 1 finding → wrote .atcr/reviews/r-4f2a/sources/
Auto-fix (--auto-fix)
atcr review --auto-fix turns findings into fixes: an executor model
generates a patch per eligible finding, atcr applies it to the working tree, validates
it with your configured validation command (a build by default), auto-reverts on
failure, and opens a GitHub pull request. It
never auto-merges or commits autonomously — the PR is a proposal a
human reviews.
Because validation runs untrusted, model-authored code, it is
sandboxed by default: the validation command runs in an ephemeral,
network-isolated container against a writable copy of the patched tree — no host file is
ever mutated. The two danger flags are explicit opt-outs: --no-sandbox runs
validation directly on the host (warns on every run), and
--allow-config-edits lets patches touch CI workflows, .env*,
.atcr/, and other config paths that are blocked by default. See
docs/auto-fix.md
for the sandbox guarantees and the PR flow.
Baseline review (--all / --dir)
Review doesn't require a diff range: atcr review --all scans the whole
repository and --dir <path> scans a subtree — the first pass over a
legacy codebase or a periodic full audit. Baseline runs are incremental: files whose
content hash is unchanged since the last baseline are skipped, and
--fresh forces a full rescan.
$ atcr review --all # whole repo, incremental $ atcr review --dir internal/ # one subtree $ atcr review --all --fresh # ignore the hash cache, rescan everything
verify & debate
Two adversarial stages can run after reconcile. atcr verify sends skeptics
after each reconciled finding to confirm or rebut it and writes a confidence v2;
atcr debate cross-examines what survives — a proposer/challenger/judge
round that settles severity splits, gray-zone clusters, and verification disagreements.
Or chain the whole pipeline in one shot from review:
$ atcr review --verify # review → reconcile → verify $ atcr review --debate # review → reconcile → debate $ atcr review --thorough # the full gauntlet, one command
reconcile
Discover the per-agent sources, cluster by AST isomorphism (line proximity when no parser exists), dedupe by similarity, and score confidence by agreement:
$ atcr reconcile → 9 raw findings → 6 clusters → 3 reconciled ✓ wrote reconciled/{findings.txt, findings.json, report.md}
Reconciled findings also carry a stable symbol anchor — the enclosing block's name — so a finding can be relocated after line numbers drift.
report
Render the reconciled findings in whichever format you need. The same data, five views —
md and checklist for humans, json for tools,
sarif for code-scanning integrations, and axi for agent
callers:
$ atcr report --format md $ atcr report --format json $ atcr report --format checklist $ atcr report --format sarif $ atcr report --format axi
Whichever format you render, a finding's problem text leads with its
stable symbol anchor.
One-shot it. atcr review --fail-on high runs review,
reconcile, and a severity gate in a single command — drop it in CI to block a merge on
a confirmed high-severity finding.
Stable symbol anchors
A reconciled finding carries a stable symbol anchor whenever atcr can
resolve one safely: the name of the nearest enclosing named block — function, method,
class, or type — in parentheses at the head of the finding's problem text.
# the problem cell, as emitted by reconcile
(classifyHeader) Off-by-one when the header row is absent
The anchor exists because line numbers drift. Fixing one finding shifts every finding
below it in the same file, so a consumer that recorded file.go:210 now
points at the wrong line. A name survives that shift where a line number cannot, which
is what keeps a captured finding actionable a week later.
It is a findings-level contract, not a debt-only one. The anchor is stamped once on the shared finding before emit, so it renders the same way in the GitHub Action pull request comment, the check-run summary table, and the human-readable report — and travels unchanged into the technical-debt table a downstream consumer builds from it.
The prefix is optional and leading, and it is omitted entirely rather
than guessed — when the finding's language has no AST parser, when the finding is
file-level or its file is missing or unparseable, when no named block encloses the line,
when the resolved name would not survive the pipe-delimited table, or when AST grouping
is disabled via ATCR_DISABLE_AST_GROUPING. With the anchor omitted the cell
is byte-identical to the plain problem text, so a parser that knows nothing about
anchors keeps working. Note the anchor is a display and grep value, not a grouping key —
reconcile clusters findings on a separate structural key. See
docs/technical-debt-format.md
for the full contract.
AXI mode (--axi)
--axi (Agent eXperience Interface) makes atcr comfortable as a subprocess
for another agent: token-dense TOON output on stdout, diagnostics kept on stderr,
deterministic exit codes, and a line cap tunable via ATCR_AXI_MAX_LINES.
Available on atcr review, bare atcr, and as
atcr report --format axi. See
docs/agentic-consumption.md.
GitHub Action
The easiest CI path is the composite GitHub Action that ships in the repo
(action.yml): it runs review → reconcile → atcr github and
posts the result as a PR check with optional inline comments — no hand-rolled workflow
needed. The check-run summary — and the inline PR comments when
inline-comments: true — carry the same
stable symbol anchors as the local report. See
docs/github-action.md.
scorecard
Every atcr reconcile run writes a normalized per-reviewer evaluation record
as a byproduct — no flag needed (pass --no-scorecard to suppress it for one
run). Read one run's record with atcr scorecard, or aggregate across runs
with atcr leaderboard:
$ atcr scorecard REVIEWER MODEL RAISED CORROBORATED SURVIVED COST greta claude-sonnet-4.6 12 6 0.80 $0.04 bruce gpt-4o 9 5 0.71 $0.03 # which model finds the most real bugs, at what cost
MCP server
Run the same engine as an MCP stdio server so an agent host can call it as a tool:
$ atcr serve
Agent Skill
The companion Agent Skill contributes the host model's own review as the
+1 source — so even a single API key yields two or more independent sources
and a working confidence signal.
Technical debt (atcr debt)
Findings that survive reconciliation but do not get fixed are technical debt. An
atcr reconcile run that produces findings appends them to a local,
append-only store under .atcr/debt/ — month-sharded JSONL, created on first
write, so a repo that has never recorded a finding simply has no store and
list reports an empty backlog rather than an error. That write is the one
part of this you can opt out of: atcr reconcile --no-local-debt skips
persistence entirely, so a CI job that passes the flag reports its findings and still
leaves the backlog empty. All five subcommands operate on that one store, so an item
filed by add is visible to list and closeable by
resolve. Pass --dir to any of them to point at a store other
than the current repo's.
list — read the backlog
Renders the store as a table, one row per finding. The leading
ID column is the finding id you hand to resolve:
$ atcr debt list --severity HIGH --component internal/billing --sort est ID SEVERITY STATUS ORIGIN EST FILE CATEGORY PROBLEM 4a1c9f0e2b7d3856 HIGH open review 45 internal/billing/invoice.go:112 correctness (applyDiscount) Percentage discounts are rounded before cla… 9d2e7b41c05a6f38 HIGH open review 30 internal/billing/tax.go:64 edge-cases (rateFor) An unknown jurisdiction falls through to the defa…
Filter with --severity, --status, --category,
--component (a path prefix, matched on whole segments — so
internal/bill does not match internal/billing/invoice.go), or
--origin (review for findings atcr raised,
manual for ones you filed). Sort with
--sort severity|age|est|file. Add --json to get the selected
items as an array instead of a table. --status filters all four buckets
here — open, deferred, resolved, and
wontfix — which is a wider set than either add or
resolve accepts.
add — file an item by hand
Run it on a terminal with no flags for an interactive prompt, or pass every field for a
scriptable add. It echoes the new item's id, so the follow-up
resolve is a copy-paste away:
$ atcr debt add --severity MEDIUM \ --file internal/billing/refund.go:88 \ --problem "Partial refunds skip the audit log" \ --fix "Emit the same audit event the full-refund path emits" \ --category observability --est 30 Added MEDIUM item 7f3b8c15e9a24d60 to /home/you/acme/.atcr/debt.
--severity, --file, --problem,
--fix, and --category are required in flag mode;
--est (minutes) and --status are optional.
--status here accepts open, deferred, or
resolved — not wontfix, which is reserved for
resolve because dismissing a finding requires a written justification.
dashboard — aggregate rollup
Renders totals, a severity breakdown, a per-component breakdown, an age histogram, and a
top-priority list as Markdown. It writes to stdout by default; pass
--output <file> to write a file instead, the same way
atcr report does:
$ atcr debt dashboard --top 3 # Technical Debt Dashboard _Generated by `atcr debt dashboard` — do not edit by hand; regenerate to update._ **Total:** 38 | **Open:** 31 | **Deferred:** 4 | **Resolved:** 2 | **Wontfix:** 1 ## By Severity | Severity | Open | Deferred | Resolved | Wontfix | Total | |----------|------|----------|----------|---------|-------| | CRITICAL | 0 | 0 | 1 | 0 | 1 | | HIGH | 6 | 1 | 1 | 0 | 8 | | MEDIUM | 14 | 2 | 0 | 1 | 17 | | LOW | 11 | 1 | 0 | 0 | 12 |
--top <n> sizes the top-priority list and defaults to
10 (0 suppresses it). The render is deterministic, so
--check paired with --output fails on real content drift
rather than on clock movement — drop that pair in CI or a pre-commit hook to keep a
committed dashboard honest. Drift (or a missing target file) exits 4,
deliberately distinct from the generic failure code, so a hook can tell
“regenerate and stage” from a real failure and branch accordingly.
Secret-shaped tokens in finding text are scrubbed before rendering.
resolve — close an item
With no action flag it lists open items (--list, capped by
--max, filterable by --severity). --max defaults
to 10, so a bare atcr debt resolve shows ten rows out of a
backlog of any size and says nothing about the rest — pass --max 0 for no
cap. To close one, pass its id to --resolve — the id is a flag value, not a
positional argument. --status is narrower here than anywhere else in the
namespace: it accepts only resolved or wontfix, the two
closure outcomes. deferred is not a closure and is rejected here, so
--status deferred exits 2 even though add accepts it:
# fixed it $ atcr debt resolve --resolve 4a1c9f0e2b7d3856 --reason "clamp now runs before rounding" Marked 4a1c9f0e2b7d3856 resolved. # it was never a bug — dismiss it for good $ atcr debt resolve --resolve 9d2e7b41c05a6f38 --status wontfix --reason "the default rate is the documented fallback" Marked 9d2e7b41c05a6f38 wontfix.
Only wontfix is permanent. Closure is recorded, not
enforced: a finding id is derived from its file, line, and problem text, so a
resolved item re-opens the moment a later review detects the same thing
again, and deferred behaves the same way. That is the point — a
re-detection is a regression. wontfix is the one status that suppresses a
re-detection, which is why --status wontfix refuses to run without
--reason unless the item already carries a justification — one written by
reconcile enrichment, say. The rule is that a permanent dismissal must be
reviewable later, not that you have to retype a rationale the record already holds.
compact — fold the append-only store
The store never overwrites a record, so its size tracks total history rather than live findings. Compaction folds it by id and drops superseded records atomically:
$ atcr debt compact --dry-run Nothing to compact: 52 record(s), none superseded.
This is the one subcommand in the namespace you cannot undo — it rewrites the store in
place — so run --dry-run first. A reconcile that appends new
records compacts on its own once the store trips 100,000 records or 100 MiB
and has grown materially since the last compaction. That automatic path runs
only on the reconcile side, so a store you fill by hand with add never
compacts itself — run compact yourself there.
Whether the store is committed is your call, and your .gitignore is the
lever. The .atcr/.gitignore that atcr init writes excludes
cache/, reviews/, and history/ but not
debt/, so a repo that ignores .atcr/ wholesale keeps the
backlog local while one that does not will pick it up. Decide deliberately: the store is
written unredacted. The scrubbing described above happens when the dashboard renders,
not when a record is written, and a record carries the finding's problem and fix text
plus an evidence excerpt lifted from your source. Committing it publishes that text to
everyone who can read the repo. In a public repo that is also a quality decision: the
backlog is model-generated and unreviewed until someone triages it, so
list it and close the misfires with resolve --status wontfix
before the first commit rather than after. Full details, including the
--check pre-commit and CI recipes, in
docs/technical-debt.md.
Command reference
The full command surface, synced from the atcr repo's README:
| Command | Purpose |
|---|---|
atcr review |
Resolve the git range (or scan the whole repo with --all / a
subtree with --dir), build payloads, fan out to the reviewer pool,
write per-agent + merged findings; --auto-fix applies sandboxed
fixes and opens a PR
|
atcr reconcile |
Discover sources, cluster, dedupe, score confidence, write reconciled artifacts;
a trust-aware consensus filter uses scorecard history to keep high-trust
reviewers' singletons and demote low-trust ones, with
--consensus strict|lenient|off tuning how tolerant it is
|
atcr verify |
Run adversarial skeptics over reconciled findings; write verdicts and confidence v2 |
atcr verify diff |
Scan a unified diff for over-simplification ("reward-hack") fingerprints — a deleted or skipped test, a weakened assertion, a lint suppression. Deterministic and model-free (no provider, no API key, no network) — see docs/diff-smell.md |
atcr debate |
Cross-examine disputed findings (proposer/challenger/judge); settle severity splits, gray-zone clusters, and verification disagreements |
atcr report |
Render md / json / checklist / sarif / axi views over the reconciled findings |
atcr range |
Pre-flight base..head resolution only; prints resolution JSON |
atcr status |
Print a review's fan-out progress as JSON (roster + per-agent state) |
atcr init |
Write .atcr/config.yaml and the nine default personas (editable)
|
atcr quickstart |
Interactive onboarding: scaffold .atcr/ (reusing
init), set up the synthetic provider + API-key env var, and
scaffold a CI workflow (--open, --force,
--offline)
|
atcr config |
Update project configuration in .atcr/config.yaml via
atcr config set <telemetry|quality_signal> <true|false>
|
atcr serve |
Run the MCP stdio server over the same engine |
atcr doctor |
Self-test every configured endpoint (dedup'd by provider+model+base_url,
fallbacks included); per-agent table or --json, with a
SOURCE (user/project) provenance column
|
atcr history |
Query the per-package finding-history ledger in .atcr/history/:
trend counts by severity over a --since window and optional
--package prefix; --prune <horizon> deletes
whole monthly shards past a retention horizon — see
docs/history.md
|
atcr trust |
Authorize project-defined providers from .atcr/registry.yaml before
they can receive a key
|
atcr debt |
Query, capture, and report on technical debt in the
.atcr/debt/ store (list / add /
dashboard / resolve / compact, all over
that one store);
resolve --status wontfix --reason "<text>" dismisses a
false-positive finding so it stops resurfacing — see
docs/technical-debt.md
|
atcr audit-report |
Render a one-page markdown compliance report for a PR's review runs from the
append-only .atcr/audit.log.jsonl ledger (--pr <n>)
|
atcr github |
Post reconciled findings to a GitHub pull request as a check run |
atcr scorecard |
Display the per-reviewer scorecard for a single reconcile run |
atcr quality-report |
Render the aggregate persona+model dismissed/confirmed prompt-quality signal
(distinct from atcr report; content-free — persona, model, and
counts only)
|
atcr leaderboard |
Aggregate scorecard records across runs, ranked by corroboration rate |
atcr benchmark |
Standard benchmark-suite tooling for the public leaderboard |
atcr personas |
Manage community reviewer personas; submit contributes a
locally-tuned persona back upstream as a PR — see
docs/personas-authoring.md
|
atcr models |
Inspect model bindings, drift, and the catalog snapshot |
atcr skill |
Install the embedded Agent Skill;
atcr skill export [--harness <name>] [--user] [--dir <path>]
[--force]
writes it to your agent harness's skills directory
|
atcr version |
Print the atcr version |
Telemetry & cloud sync
An anonymous usage ping (four allowlisted fields: event, language, line count, status — no code, paths, or finding text) is on by default and currently wired but inactive. Opt out with one environment variable:
ATCR_TELEMETRY=0 atcr review
Note the inverse boolean — ATCR_TELEMETRY=0 (not =1) disables
it. Separately, --sync-cloud uploads a pseudonymous scorecard to a cloud
dashboard, strictly opt-in and gated on ATCR_API_KEY. Its per-reviewer rows
are keyed by a one-way, unsalted SHA-256 hash of the persona name — a stable pseudonym,
not a secret, because persona names come from a small, publicly-listed set and anyone
who pre-hashes that list can match a digest back to a name.
A third surface, the community prompt-quality signal, is off by default: opt in with
ATCR_QUALITY_SIGNAL=1 (or atcr config set quality_signal true)
and it sends an aggregate of the hashed persona name, model slug, and two dismissal
counts and nothing else — --preview prints the exact bytes before you ever
do. It carries the same persona digest as the scorecard, so it is pseudonymous on the
same terms. Full details in
docs/telemetry.md
and on the privacy page.
Exit codes
0 success · 1 failure (including a
--fail-on threshold violation) · 2 usage or configuration
error · 3 authentication failure (--sync-cloud missing or
rejected ATCR_API_KEY).
atcr debt dashboard --check adds 4 for drift or a missing
target file. Scripts and CI can branch on the code directly.