Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
7e9e85e
docs: plan itd-101 and write spc-17 citation-gate spec
REPPL Jul 27, 2026
aa4c491
feat: add the committed citation baseline schema
REPPL Jul 27, 2026
8e187c0
feat: add the zero-network citations lint family
REPPL Jul 27, 2026
a76f7e2
feat: arm the citation structure rules in this repo's docs-lint config
REPPL Jul 27, 2026
3120e60
fix: stop the DOI check claiming prose, and read wrapped citations
REPPL Jul 27, 2026
ffb83cf
docs: record the four design choices spc-17 left open
REPPL Jul 27, 2026
43f2060
refactor: extract the SSRF fetch guard into internal/urlguard
REPPL Jul 27, 2026
c505133
feat: add the citation refresh engine and the manual-queue receipt
REPPL Jul 27, 2026
e90a97c
feat: wire the citation baseline into the CLI, ahoy, and the release …
REPPL Jul 27, 2026
dfc5537
feat: arm the citation gate and commit this repo's citation baseline
REPPL Jul 27, 2026
39f6158
docs: describe the cite verbs on both documentation surfaces
REPPL Jul 27, 2026
6c5aad2
chore: record the receipt one flaky source did eventually answer
REPPL Jul 27, 2026
d7b25da
fix: close a path-traversal write and four record-integrity holes
REPPL Jul 27, 2026
bb1d168
fix: resolve containment through symlinks, contain roots, and fix the…
REPPL Jul 27, 2026
1dcb736
fix: report a refused redirect chain as answered
REPPL Jul 27, 2026
dfd172b
fix: contain every collected page, and stop the CHANGELOG overclaiming
REPPL Jul 27, 2026
e13d7c4
fix: bound the page read and stop the seam punishing a silent adapter
REPPL Jul 27, 2026
78cc4bc
docs: record manual verification receipt for the harness-engineering …
REPPL Jul 28, 2026
e195757
Merge remote-tracking branch 'origin/main' into feat/itd-101-citation…
REPPL Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
413 changes: 413 additions & 0 deletions .abcd/citations-baseline.json

Large diffs are not rendered by default.

55 changes: 41 additions & 14 deletions .abcd/development/brief/04-surfaces/10-docs.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
# `/abcd:docs` — Documentation-Currency Lint
# `/abcd:docs` — Documentation Currency and Citations

`/abcd:docs` runs abcd's documentation-currency checks over the current repo and
presents the result. It is **strictly read-only** — it performs zero writes, it
only reports. It is the deterministic half of the docs release gate (the semantic
half is the `docs-currency-reviewer` agent).
maintains the citation record those checks enforce. `lint` is **strictly
read-only** — it performs zero writes and touches no network — and it is the
deterministic half of the docs release gate (the semantic half is the
`docs-currency-reviewer` agent). The `cite` sub-tree is the writing half: it is
the only place abcd reaches the network on behalf of documentation, and it runs
when a maintainer asks.

## Sub-verbs

The shipped verb surface is one sub-verb, `lint`:

- **`/abcd:docs lint`** — lint this repo's documentation for currency and print
the findings. The plugin command (`commands/abcd/docs.md`) invokes
`abcd docs lint --json` and summarises the result.
- **`abcd docs cite refresh`** — fetch every cited URL once and rewrite the
committed citation baseline. Each URL gets exactly one bounded attempt with no
retries, and no response body is read: liveness is judged from the status line.
Sources that refuse automated fetchers are printed as a manual checklist rather
than recorded as broken.
- **`abcd docs cite confirm <url>… | --receipt <file>`** — record that a human
verified a citation the fetcher could not read. Both forms assemble one receipt
schema, so the generated checklist page that lands later is a different
producer of the same input rather than a second pathway. Only URLs the
documentation cites can be confirmed.

Bare `abcd docs` prints command usage (its one sub-verb, `lint`) — it does **not**
render a status board; the bare-status convention is scoped to `ahoy`/`capture`/
`memory`/`intent`/`spec` and bare `abcd`, not to `docs`. The global `--json` flag
emits the machine-readable finding list, and `docs lint` additionally accepts two
local flags: `--config` (path to the `docs-lint.json` it loads, default
`<root>/.abcd/docs-lint.json`) and `--root` (repo root to lint, default the
current working directory).
Bare `abcd docs` prints command usage — it does **not** render a status board;
the bare-status convention is scoped to `ahoy`/`capture`/`memory`/`intent`/`spec`
and bare `abcd`, not to `docs`. The global `--json` flag emits the
machine-readable payload. Every verb here accepts `--config` (path to the
`docs-lint.json` it loads, default `<root>/.abcd/docs-lint.json`) and `--root`
(repo root, default the current working directory), so the refresh fetches
exactly the set the gate demands receipts for. `docs lint` additionally accepts
`--release-gate`, which promotes an overdue citation from a warning to a blocker;
the flag is release machinery's, so an ordinary commit is never blocked by the
calendar.

## What it checks

Expand All @@ -34,6 +48,13 @@ current working directory).
- **Stray root markdown** — no stray markdown at the repo root (it belongs under
`docs/`; the allowed root files are the fixed set — README, CHANGELOG,
CONTRIBUTING, etc.).
- **Citations** — where a repo arms the rules: footnote markers and definitions
in bijection, every crosswalk table row carrying a footnote, well-formed cited
URLs and DOIs, refused source domains, and the committed baseline at
`.abcd/citations-baseline.json` — no cited URL without a receipt, none recorded
broken, none whose recorded final address has drifted from what the page cites,
and a staleness warning past 180 days. All of it reads committed files; the
fetching lives in `cite refresh`.
- **Host-agnostic prose** — user-facing docs must not name a specific agent
harness or bundled tool. This repo's `.abcd/docs-lint.json` defines a family of
`harness/*` banned tokens (each a **blocker**) that catch such names, so the
Expand All @@ -50,12 +71,18 @@ release gate.

## Composition

`/abcd:docs` is the deterministic, fast, always-runnable currency check.
`/abcd:docs lint` is the deterministic, fast, always-runnable currency check.
The `docs-currency-reviewer` agent is its semantic complement — it verifies that
every user-facing claim still matches the code, which a structural lint cannot.
The release gate runs both: `docs lint` (deterministic) and the reviewer
(semantic) must each pass before a tag.

`cite refresh` composes with the gate by separation: the gate is deterministic
because the fetching happens elsewhere and arrives as a committed record a
reviewer reads in a diff. The baseline's age surfaces at `abcd ahoy` and in the
`abcd launch --dry-run` preflight, which names entries approaching the staleness
blocker while a release still cuts, and refuses on ones past it.

## References

- Plugin command: [`commands/abcd/docs.md`](../../../../commands/abcd/docs.md)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
id: itd-101
slug: every-citation-abcd-publishes-is-provably-alive-and-honestly
spec_id: null
kind: null
spec_id: spc-17
kind: standalone
suggested_kind: null
reclassification_history: []
builds_on: []
Expand Down
59 changes: 59 additions & 0 deletions .abcd/development/release/surface.json
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,58 @@
"hidden": false,
"flags": []
},
{
"path": "abcd docs cite",
"hidden": false,
"flags": []
},
{
"path": "abcd docs cite confirm",
"hidden": false,
"flags": [
{
"name": "config",
"shorthand": "",
"type": "string",
"required": false,
"hidden": false
},
{
"name": "receipt",
"shorthand": "",
"type": "string",
"required": false,
"hidden": false
},
{
"name": "root",
"shorthand": "",
"type": "string",
"required": false,
"hidden": false
}
]
},
{
"path": "abcd docs cite refresh",
"hidden": false,
"flags": [
{
"name": "config",
"shorthand": "",
"type": "string",
"required": false,
"hidden": false
},
{
"name": "root",
"shorthand": "",
"type": "string",
"required": false,
"hidden": false
}
]
},
{
"path": "abcd docs lint",
"hidden": false,
Expand All @@ -319,6 +371,13 @@
"required": false,
"hidden": false
},
{
"name": "release-gate",
"shorthand": "",
"type": "bool",
"required": false,
"hidden": false
},
{
"name": "root",
"shorthand": "",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
id: spc-17
slug: every-citation-abcd-publishes-is-provably-alive-and-honestly
intent: itd-101
---
# every-citation-abcd-publishes-is-provably-alive-and-honestly

## Summary

spc-17 delivers itd-101's citation gate: a deterministic, zero-network lint
family that runs in the commit gate, and an explicit on-demand refresh verb
(`abcd docs cite refresh`) that does the live fetching and writes a committed
baseline the lint then enforces offline. The design decisions below were
settled by the 2026-07-27 grill (recorded in the intent's Grill Settlements);
this spec records them together with the mechanism — it does not reopen them.

## Settled constraints (from the grill)

- **Zero network in the gate.** The citations lint family reads only committed
config and the committed baseline. All fetching lives in the refresh verb.
- **Refresh is manual now.** It is surfaced by ahoy status and
release-preflight nagging; a scheduled-CI wrapper is a later, separately
signed-off change and is out of this spec's scope.
- **Staleness policy.** A baseline entry older than 180 days makes docs lint
warn; one older than 365 days blocks at the release gate only — commits are
never calendar-blocked. Human-verified entries age on the same clock and
re-enter the manual queue when stale.
- **Honest labelling, never method.** Each baseline entry records whether
verification was automatic or manual, and when — never how a human verified.
- **The row-has-footnote rule is homed here.** The structural rule deferred
from spc-15 (every crosswalk table row carries at least one footnote) is
implemented in this intent or not at all.

## Mechanism

### Offline lint family (commit gate)

The docs-lint family gains citation rules, evaluated with zero network:

- **Structure**: footnote markers and definitions in bijection per page;
every crosswalk table row carries at least one footnote (the spc-15
deferral).
- **Syntax**: cited URLs and DOIs are well-formed.
- **Source policy**: aggregator domains are refused, from committed config.
- **Baseline enforcement**: no cited URL absent from the baseline, no entry
recorded as broken, no entry whose recorded final address has drifted from
the cited URL's current resolution record, and the staleness clock above
(180-day warn here; the 365-day blocker binds in the release gate, not the
commit gate).

Severities mirror the existing docs-lint family (blocker/warn), and findings
render with the same successor-carrying shape.

### Committed baseline

`abcd docs cite refresh` fetches every cited URL and writes the baseline, a
committed machine record under `.abcd/` (one file, schema-versioned). Per URL
it records: the final resolved address after redirects, when it was last
checked, the outcome, and the verification label — `automatic`, or `manual`
with its date. Nothing else: no fetch transcript, no method, no headers.

### Manual queue, one receipt schema

Sources that block automated fetchers join the manual queue:

- **Rung 1 (this spec's floor)**: the refresh verb prints the queue as a
checklist; the maintainer clears it link by link and a confirm verb writes
the dated receipt into the baseline (verification: manual + date).
- **Rung 2 (same schema, later rung)**: a generated, disposable checklist
page hands back a receipt file that the same confirm verb ingests. One
receipt schema serves both rungs — the page is a different producer of the
same input, not a second pathway.

### Engine and seam

The fetch engine is native and dependency-free (net/http, bounded redirects,
explicit timeouts). A specialist link checker can later slot in behind the
refresh seam as an adapter without changing gate semantics: the baseline
schema and the lint rules are the contract; the fetcher is a replaceable
producer.

### Surfacing

ahoy status reports the baseline's age summary (stale/overdue counts), and
the release preflight nags when entries approach or pass the 365-day blocker.
Both planes are wired at delivery: CLI verbs and the plugin markdown surface.

## Acceptance-criteria mapping

- AC 1 (zero-network gate: structure, syntax, policy from committed inputs)
→ Offline lint family.
- AC 2 (baseline records final address, checked-when, auto-vs-manual + date,
never how) → Committed baseline.
- AC 3 (180-day warn in docs lint; 365-day release-gate blocker; manual
verifications age the same) → Settled constraints + Offline lint family.
- AC 4 (printed checklist + confirm verb; generated page hands back a receipt
the same verb ingests; one schema) → Manual queue, one receipt schema.
- AC 5 (specialist checker slots behind the seam without gate changes) →
Engine and seam.

## Out of scope

- The scheduled-CI refresh wrapper (explicitly out of this intent, per the
grill; needs its own sign-off).
- The generated checklist page itself may land as the later rung; only the
receipt schema it must emit is fixed here.
- Any change to non-citation docs-lint rules.
7 changes: 6 additions & 1 deletion .abcd/docs-lint.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@
"rules": {
"links_resolve": {"enabled": true, "severity": "blocker"},
"stray_root_docs": {"enabled": true, "severity": "blocker",
"allowlist": ["README","AGENTS","CHANGELOG","CONTRIBUTING","SECURITY","LICENSE","ACKNOWLEDGEMENTS"]}
"allowlist": ["README","AGENTS","CHANGELOG","CONTRIBUTING","SECURITY","LICENSE","ACKNOWLEDGEMENTS"]},
"citation_footnotes": {"enabled": true, "severity": "blocker"},
"citation_crosswalk_rows": {"enabled": true, "severity": "blocker"},
"citation_url_syntax": {"enabled": true, "severity": "blocker"},
"citation_source_policy": {"enabled": true, "severity": "blocker", "refused_domains": []},
"citation_baseline": {"enabled": true, "severity": "blocker", "baseline": ".abcd/citations-baseline.json", "warn_after_days": 180, "block_after_days": 365}
},
"exempt_paths": [],
"exempt_if_status": []
Expand Down
Loading