Skip to main content

Concepts

Git records what your codebase is. Nothing records what it refused to be. SCAR is version control for that negative space: the dead ends, the load-bearing weirdness, the invisible tripwires — captured as files, anchored to code, and surfaced at the exact moment someone (human or AI agent) is about to step on them.

The three primitives​

TypeMeaningExample
deadendWe tried X. It failed because Y. Don't retry unless Z changes."We tried Redis for session storage. Eviction under memory pressure logged users out mid-checkout. Don't retry unless sessions become re-derivable."
fenceThis code looks wrong. It is intentional. Here's why."Yes, this retry loop sleeps 7 seconds, not 5. The upstream vendor's rate limiter has a 6-second window they don't document."
landmineChanging A breaks B in a way nothing in the code tells you."The CSV export depends on the column order of this SELECT. Reorder it and the reconciliation pipeline silently corrupts."

Type semantics drive anchoring: a deadend protects against re-attempting an approach (primary anchor is usually a pattern), a fence protects existing code from change (path + symbol), a landmine encodes non-obvious coupling (anchor on the trigger site, body names the blast radius).

The scar file​

Scars are small structured Markdown files with YAML frontmatter, tracked in git, reviewed in PRs like code:

.scars/
├── 0001-redis-sessions.deadend.md
├── 0002-vendor-retry-window.fence.md
├── 0003-csv-column-order.landmine.md
├── candidates/ # drafts awaiting human promotion
└── archive/ # retired knowledge — kept forever

Naming is {seq}-{slug}.{type}.md, so a plain file listing already tells you the shape of a repo's pain.

Anchors​

Line numbers are dead on arrival; file paths die on renames. SCAR uses four anchor classes in combination, designed to degrade loudly, never silently:

  1. Path anchors — file or directory prefixes. A directory with a trailing slash or a file path both work; wildcards are not expanded. Cheap, survive content change, die on rename (mitigated by git rename tracking during re-anchor).
  2. Symbol anchors — function/class names resolved via tree-sitter. Survive moves within and across files. Primary class for fences. Measured survival across refactors: 94.6% / 92.5% on the shipped API. A symbol anchor keeps its scar out of orphan detection while the definition resolves: path::name in that file, a bare name in any tracked file. Without the [symbols] extra it cannot be checked, so it counts as live and scar lint says so once.
  3. Pattern anchors — regexes over new code (diff-scoped, not whole-repo). The only class that catches a dead end being re-attempted in a brand-new file.
  4. Command anchors — regexes over a shell command about to execute (- command: "uv sync(?!.* --all-extras)"). The only class with a firing surface for run-a-command mistakes, where there is no edit to anchor to. Never matched against paths or file content, so they cannot self-match; freshness comes from review_after, not content liveness.

A content fingerprint of the protected region powers drift detection: fingerprint drift is an advisory warning, and a scar whose location anchors all go dead becomes orphaned — loud in scar status and CI, never silently dropped.

Anchor breadth is a measurement concern, not just noise: a scar anchored on whole directories fires on nearly every edit and inflates any firing-derived metric. See Measurement methodology.

Violation tripwires​

A scar may declare a machine-checkable tripwire:

violation: "redis|aioredis"

The pre-edit hook injects the scar; the post-edit check runs the regex against the diff the agent just produced. Fired-then-violated is a logged, countable event — the basis of the compliance instrument. Two authoring rules matter:

  • Double-quote the regex. Single-quoted violation: values keep the quotes as literal regex characters and never match — a silently dead tripwire.
  • Only machine-checkable scars get violation:; prose dead ends stay advisory.

Injection and the fatigue budget​

Hard rule: max 3 scars injected per edit, max ~120 words each, ranked by severity × confidence × anchor specificity. Path-only matches render as one-line hints; the full body is injected only when the edit content trips a pattern or names an anchored symbol (matched_by: symbol; a file that merely defines the symbol is symbol_file, a one-line hint like a path match), and a body already shown for the same file within 4 hours collapses to a one-liner. When the cap cut scars, the injected header says so (3 of 5 matched shown) and points at scar why for the rest. A scar system that warns constantly is a scar system that gets uninstalled.

Lifecycle​

candidate ──promote──▶ active ──challenge──▶ challenged
│ │ └────────▶ archived (+tombstone note)
discard anchors drift
▼
orphaned ──re-anchor──▶ active
└──expire/review──▶ archived
  • Promotion is a human gate. Agents and scar harvest write only to candidates/; a human promotes.
  • Nothing expires automatically. review_after forces periodic freshness checks; challenge disputes with evidence; archive retires with a tombstone. The archive keeps everything — SCAR's own history is negative knowledge.
  • confidence is a static, human-authored ranking weight. Automatic decay was deliberately cut rather than shipping uncalibrated constants.

Harvest​

scar harvest mines git history for candidate scars: revert commits, dependencies added-then-removed, issues reopened repeatedly, churn-then-stability files, and comment archaeology (DO NOT, load-bearing). All output lands in candidates/ — precision over recall, because 50 junk candidates kill trust on day one. Field data so far: hand-authoring during sessions produces most active scars; harvest ranking is calibrated for code-heavy repos and is explicitly unreliable on flap-heavy GitOps repos.