Documentation

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.