chore(docs): add lint:docs and fix the violations it found - #79
Conversation
CONTRIBUTING mandates YAML frontmatter on every doc and an Open-questions section at the end; docs/README.md says "update this file when you add or supersede a document". Both were enforced by hope, and INTEGRITY-AUDIT E-09/E-10/E-11 already logged the drift. For an agent picking the repo up cold, docs/README.md IS the map, and a silently stale map is worse than no map. scripts/lint-docs.mjs, wired as `npm run lint:docs` inside `npm run ci` and as a CI step, exits 1 on: frontmatter-missing no `---` block at the top frontmatter-missing-key a required key is absent frontmatter-bad-value value outside its allowed set, or a non-YYYY-MM-DD date not-in-index the doc is not linked from docs/README.md missing-open-questions no trailing Open-questions section broken-link a relative link that does not resolve No new dependencies — frontmatter is a flat key: value list and is hand-parsed. Errors are `path:line rule message`, since a future agent reads those and not this source. What it found, all fixed here, shape only — no finding, number, or verdict touched: - docs/gate/testbed.md carried `doc_type: gate`, which is not in CONTRIBUTING's list. Allowed set follows CONTRIBUTING (`gate-result`); the outlier is relabelled `spec`, matching its siblings compiler.md and runner.md. `gate-result` would have been a false claim — no gate result exists. - Six docs were absent from the index: the five-doc pitch pack (covered only by a `pitch/` directory link) and A2-grafana.md, hidden behind an `A1`-`A3` range link. Rows added. - Two research docs put an appendix after Open questions (vertical-search/adversary-report.md "Searches run", vertical-search/DECISION.md "Method notes"). Sections reordered above it; not a word changed. Each rule was demonstrated to fail on a broken input and every deliberate breakage reverted. INTEGRITY-AUDIT E-09/E-10/E-11 now state what the linter does and does not cover, so nothing there reads as closed when it is not: evidence table columns are unchecked, and `.md` outside docs/ is out of scope until CONTRIBUTING decides whether "living" is a status. Closes #53 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
OM152002
left a comment
There was a problem hiding this comment.
Approve.
Confirmed — I broke each rule deliberately and checked it fires: frontmatter-missing, -missing-key, -bad-value (bad enum and bad date), missing-open-questions (absent heading and a section after it), broken-link, not-in-index — all caught, and the tree returns clean (43 docs) afterwards. Wired into both npm run ci and a separate CI step.
The real risk in a "fix the violations" PR is a fix quietly editing a finding. It doesn't: comparing sorted line multisets before/after, adversary-report.md and DECISION.md are pure moves — identical content, only relocated above Open questions. No number or verdict touched.
The doc_type: gate → spec call is right, and refusing to label testbed.md gate-result when no gate number exists is the correct reading of rule 3. Scope is stated honestly too — E-09 "no", E-10 "partly", E-11 "out of scope" rather than marked done.
Two gaps worth a follow-up issue, not blockers
- Anchors are not validated. I appended
[bad anchor](./ROADMAP.md#this-anchor-does-not-exist)and it still reported clean. By #54's own standard — "a dead link here is worse than a stale sentence" — and with main's README now depending onREADME-narrative.md#6-where-evidence-stands-now, an anchor typo is a realistic break. Cheap fix: slug the target file's^#{1,6}headings and compare. - Six markdown files outside
docs/are unlinted, includingREADME.mdandCONTRIBUTING.md— the first files a cold reader opens. Acknowledged as deliberate; a link-only pass over them would still be cheap.
Closes #53.
What changed
scripts/lint-docs.mjs, wired asnpm run lint:docs— insidenpm run ci(afterlint) andas a
Lint docsstep in thebuildjob. No new dependencies: frontmatter here is a flatkey: valuelist, so it is hand-parsed.frontmatter-missing---block at the top of the filefrontmatter-missing-keytitle,doc_type,status,owner,created,updated,confidence,supersedes,sources_verifiedis absentfrontmatter-bad-valuecreated/updatednotYYYY-MM-DDnot-in-indexdocs/README.mdmissing-open-questions## Open questions / what I could not verify, or another##follows itbroken-linkdocs/resolves to nothingErrors are
path:line rule message— a future agent reads those, not the source. Linkchecking skips fenced code blocks so example markdown cannot trip it, and tolerates
#anchorand
?querysuffixes.doc_type: gate→specdocs/gate/testbed.mdwas the outlier. The allowed set follows CONTRIBUTING(
gate-result), sincedocs/README.mdcalls CONTRIBUTING the "doc-shape source of truth".I did not relabel testbed.md
gate-result. It is a spec for the test-bed — version matrix,what the harness seeds, CLI, Docker limitation — not the report of a measured gate. Calling it
gate-resultin a repo whose third hard rule is "never invent a metric", and which statesplainly that no gate number exists, would be a false claim in frontmatter.
specmatches itssiblings
compiler.mdandrunner.md.What the linter found, all fixed here
Shape only — no research finding, number, or verdict changed. The diff is verifiable on that
point.
docs/gate/testbed.md—doc_type: gate, above.pitch/directory link, and
A2-grafana.mdwas hidden behind an[A1](…)–[A3](…)range link thatreads as a range but only links two files. Rows added for all six.
vertical-search/adversary-report.md("Searches run") andvertical-search/DECISION.md("Method notes (integrity)" plus the adjudicator line). Both sections moved above Open
questions; not a word altered. CONTRIBUTING says every doc ends with Open questions, so
the rule checks position, not just presence.
Each rule demonstrated to fail
One breakage at a time, every one reverted before committing (
git statusclean afterwards):The "must be last" half of R4 is demonstrated by the linter's own first run, which is what
caught the two research docs above.
R5 also confirms the rules compose: breaking
./ROADMAP.mdin the index produced threebroken-linkerrors and anot-in-indexfordocs/ROADMAP.md, which the broken link nolonger covered. Links carrying an
#anchorstay clean.INTEGRITY-AUDIT: what is now enforced, and what is not
E-09/E-10/E-11 rows gained a "Machine-checked?" column rather than being marked done, because
most of them are not:
access_datecolumns) — no. The linter never inspects tablecolumns. Still review-enforced.
section are enforced and green; body shape beyond that is not.
design. The linter walks
docs/only, per the issue. Notedocs/README.mdindexes thosetwo as
status: living, which is not a valid frontmatter status — so widening the glob needsa CONTRIBUTING decision first, not just a bigger glob. Recorded as an open question.
docs/DEVELOPMENT.md's open question "whethernpm run lint:docsexists yet — issue #53" isnow answered in place, with the same caveats.
CONTRIBUTING.mdand the DEVELOPMENT commandtable list the new command.
Interaction with #78
#78 (still open) also edits five docs under
docs/. Test-merged the two branches locally:they merge clean and
lint:docsstays green on the combined tree, so landing them in eitherorder does not break
main.Tests
🤖 Generated with Claude Code