--- name: interlinked-spec-audit description: "Keep prose specs and design docs honest against the code using Interlinked's spec-audit system. Load this when extracting a doc's invariants or facts (`interlinked spec invariants`, `spec agenda`), when you see `[interlinked:spec-drift]`, `[interlinked:spec-marker]`, `[interlinked:spec-xref]`, or `[interlinked:disputed-ground]` warnings after editing markdown, when ingesting/triaging an external review report (`interlinked findings ingest / status / verify / ack`), or when running executable doc examples (`interlinked doctest`). The system detects and tracks — it never auto-fixes; you write the fix. Covers the fact ledger, findings reconciliation, invariants/agenda, and current-state caveats." --- # interlinked-spec-audit — keep prose specs honest against code The spec-audit system is three cooperating substrates plus doctest. Its one policy: **detect, never autofix** — findings carry evidence and candidate resolutions, but the agent always writes the fix. - **Fact ledger** (`interlinked spec`) — deterministically extracts the *enumerable facts* that drift during revision (ID registries like `FG-INV-07`/`B7`, count/range claims, headings/ `§`-refs, backticked paths, `` markers), merges them cross-file, and flags disagreements ("drift") at edit time. - **Findings reconciliation** (`interlinked findings`) — ingest an external reviewer's numbered report into a durable corpus, then track each finding to closure (*touched* by an overlapping edit, or *acked* with a reason). - **Invariants + review agenda** — extract a doc's invariants into a taxonomy, and generate a standing discovery agenda for the next reviewer. ## Load this when - Extracting invariants/facts from a design doc, or building a review agenda. - You see a `[interlinked:spec-*]` or `[interlinked:disputed-ground]` warning after a markdown edit. - Ingesting or triaging an external code-review report. - Running executable examples embedded in docs. ## `interlinked spec` Two subcommands, no flags: ```bash interlinked spec agenda # → .interlinked/review-agenda.md interlinked spec invariants # → .interlinked/policies/.invariants.md ``` - **`spec agenda`** — walks cwd, writes a review agenda with three sections: **compose-checks** (an ID namespace/declared fact constrained from ≥2 files — "read them together, confirm no contradiction"), **coverage gaps** (a section whose heading matches a *kind* — format / protocol / crypto-keys / … — but whose body omits that kind's standard concerns), and **outstanding deterministic drift**, then an **open review-findings** section appended last. It asks questions, never renders verdicts. - **`spec invariants `** — for markdown: numbered registry rows + MUST/never/"sole truth" doctrine sentences (fenced code/blockquotes skipped). For code: `// INVARIANT:` / `// SAFETY:` comments + `assert(`/`assert!` calls. Output is a labeled taxonomy with verbatim quotes + `file:line` provenance (review context / Tier-2 classification input). ## `interlinked findings` Review-report ingestion + reconciliation. The CLI operates on `review_`-prefixed findings. ```bash interlinked findings ingest [--reviewer ] # parse a numbered report into the corpus interlinked findings status [--all] # "N total — X open, Y touched, Z acked" interlinked findings verify [--write] # re-anchor findings whose lines moved interlinked findings ack --reason [--by ] ``` - **ingest** parses findings that start at **column 0** as `N. [severity: high] statement…` (severity optional; first repo-path`:line` token = the anchor; `Evidence:` line = the quote). `bug_class` is derived from the statement's first 6 words, so **re-ingesting a reworded finding creates a new row** (same text merges provenance). - **status** folds the reconciliation sidecar; open findings are what you must close. - **verify** re-checks each finding's anchor against the tree (live / moved / drifted / gone / unverified); `--write` re-anchors *moved* rows. **verify never changes reconciliation state** — it keeps the ledger true, it doesn't close anything. - **ack** appends an `acked` txn (a later touch never downgrades an ack). - An edit that overlaps a finding's cited span (±3 lines) **auto-marks it *touched***. **Storage:** corpus `.interlinked/findings/corpus.jsonl` (append-only, last-write-wins per `id`) + global cache `~/.interlinked/findings-corpus.jsonl`; reconciliation sidecar `.interlinked/findings/reconciliation.jsonl` (states: open/touched/acked). Two-axis model: corpus `status` (candidate/approved/…) vs. the reconciliation state the CLI reports. ## `interlinked doctest` For expected output as well as successful execution, use `tests contracts import ` on explicit `json interlinked-contract` fences. Ordinary code fences remain illustrative. Import creates proposed cases with the original document hash and quoted example; it never executes them or authorizes policy. Exact observation bindings detect altered expectations; general requirement interpretation stays advisory. Run selected cases explicitly with `tests contracts run`; use **interlinked-verify** for the manifest and bounded runners. Existing `doctest` behavior remains exit-status based. Executes markdown code-fences that opted in with a `doctest` info-string token, asserting each exits 0. Only tagged fences run (untagged `rm -rf` examples are never executed). ```` ```bash doctest interlinked findings status ``` ```` Flags: `--path ` (default cwd), `--json`. Each block is an independent `bash -c` (60s timeout); output `doctest: P/T block(s) passed`, exit 1 on any failure. **Caveats (verified):** it runs in the CLI's `process.cwd()`, **not** `--path`'s directory (run it from the repo root); a **missing/typo'd `--path` silently reports `0/0` and exits 0** (check the total is nonzero); no `cd`/env/var state carries across blocks; the fence language is ignored (everything runs as bash). ## Checks you encounter at edit time All gated by config `spec_checks` (default **on**). The repository ledger skips Git-ignored files and directory trees, including local archive copies. Tracked files matched by ignore patterns still participate. Incremental refreshes and previews preserve the scope captured when the ledger was built; rebuild after changing ignore or tracking rules. Every build takes a fresh Git ignore snapshot. If Git is unavailable, the bounded filesystem walk still runs with its standard exclusions. Do not rewrite historical snapshots to satisfy a live-repository link warning. After updating the daemon, the next markdown observation refreshes the derived Stop stash; evidence logs stay intact. A fresh `spec agenda` run can confirm repository scope independently of the daemon's ledger. **PostToolUse single-file** (8 checks, `severity: warning`): `spec_dangling_anchor` (proven — a `[x](#slug)` or `§N.N` ref with no target), `spec_numbering`, `spec_count_claim` ("six bets" above a B1..B7 census), `spec_pitfall` (curated spec falsehoods), `spec_claim_untagged`, `spec_capacity_claim`, `spec_table_sum` (proven — a Total row that doesn't recompute), `spec_stage_order`. **PostToolUse cross-file drift** — on a markdown edit, `[interlinked:spec-drift]` for count/range/declared-fact/xref drift *involving the edited file* (≤5/edit; the rest deferred to the evidence log and review agenda). Quoted code examples, quoted phrases and explicit `Evidence:`/`Example:`/input/output fields do not assert live numeric facts or contribute example IDs to the census. Individually formatted identifiers and count tokens remain supported. These rules depend on content, not repository names or review-directory paths. Declared `fact:` markers must be live markup: markers inside inline code, fenced code or blockquotes are examples, not declarations. Generic count claims use a scoped identifier census. A README may describe its directory; other documents must mention the namespace or link to its home near the claim before sibling files enter the census. Nearby explicit links can select another document set. A shared noun such as “gates” does not join an unrelated repository-wide identifier namespace. Stop reports a repository snapshot; it does not claim that the current session introduced every finding. Full comparisons, related files, stable finding IDs and observation provenance are retained in `spec-drift.jsonl` and can be searched with `interlinked data search --source spec-drift`. The prior session stash is bounded, so first observation is not evidence of when a defect was introduced. **PreToolUse spec pre-gates** (arm after the session's first markdown edit builds the ledger): - **Introduced declared-marker drift → decision `ask`** (`[interlinked:spec-marker]`, exact-match, zero-FP): a write setting `` to a value conflicting with other files prompts the **human** — it's an *ask*, not a hard block. Response: update every site of the fact (or fix the source of truth first), then retry. - **Removing a heading other files link to → warning** (`[interlinked:spec-xref]`). - **Introducing new cross-file drift → warning** (`[interlinked:spec-drift]`, introduced-only). **Disputed-ground** — reading or editing a file that carries **open review findings** emits `[interlinked:disputed-ground] carries N open review finding(s)… you are building on disputed ground`. **Stop nudges** (stderr, never block): retained structural marker/link findings, and ingested review findings with neither a touching edit nor an ack. Inferred count/range comparisons are heuristic evidence and never become spec Stop nudges or automatic sibling-edit obligations. Unclassified legacy stash entries also do not qualify. Structural findings take priority before the bounded Stop stash is capped, so advisory volume cannot crowd them out. Old inferred completion entries remain preserved but do not trigger completion reminders or code-signature Stop warnings. All observed drift remains append-only in `spec-drift.jsonl`, including heuristic findings; `stop_eligible` records whether its kind qualifies for a Stop nudge. Search with `interlinked data search --source spec-drift --json`, or inspect the live tail with `interlinked query spec-drift`. A quiet Stop does not certify that all prose agrees. After upgrading Interlinked CLI, use the normal build/update and `reload` workflow in each running project so its daemon loads the new parser and Stop policy. Existing log history and review documents are preserved; the next markdown observation refreshes the derived stash. **How to respond:** for inferred count/range drift, first check whether the text is an example, historical quotation, sub-range or a live assertion about the same registry. Preserve quoted review evidence. Only after establishing a real contradiction decide which side is stale, fix it, recount, then check sibling docs stating the same fact. For dangling anchors — fix or qualify the ref. For a marker `ask` — update all sites, retry. For disputed-ground/open findings — resolve with an edit, or `interlinked findings ack --reason`. ## Config & files (`.interlinked/`) `findings/corpus.jsonl`, `findings/reconciliation.jsonl`, `review-agenda.md`, `policies/.invariants.md`. Disable the checks with `{"spec_checks":{"enabled":false}}` in guard-rules. ## Common workflows ```bash interlinked spec invariants docs/design/big-plan.md # taxonomy of the doc's invariants interlinked spec agenda # repo-wide review agenda interlinked findings ingest audit.md --reviewer sol # triage an external review interlinked findings status # open/touched/acked # …edit to address findings (overlaps auto-mark "touched")… interlinked findings ack --reason "deferred to v2; tracked in #412" interlinked findings verify --write # re-anchor moved findings ``` ## Current-state notes (honest — verified 2026-07) - **The findings corpus is local-only despite a "COMMITTED" code comment.** The blanket `.interlinked/*` gitignore has no `!` carve-out for `findings/`, so the corpus, reconciliation log, and agenda **do not travel in PR diffs or to teammates**. (To actually commit it you'd add `!.interlinked/findings/corpus.jsonl` to `.gitignore`.) - **Cross-file ledger drift is not surfaced in `interlinked verify`** yet — only in the PostToolUse ledger phase, Stop, and `spec agenda`. Don't expect `verify` to report it. - **Statusline spec counters are unpopulated** (no live spec heartbeat on the statusline yet). - **Marker-drift is an `ask`, not a hard block**, and only arms after the session's first markdown edit — the very first markdown write of a fresh daemon is ungated. - **`findings ingest` is format-specific** (numbered `N. [severity] …` at column 0); reworded re-ingests create distinct rows. ## Related skills - **interlinked-verify** — the general check catalog and how findings/suppressions work. - **interlinked-harness** — how `ask`/warning decisions reach you; suppression grammar. - **interlinked-observability** — `interlinked collect` (backfill Codex sessions into the timeline).