Context
CONTRIBUTING.md mandates YAML frontmatter on every .md in docs/, and docs/README.md says "Update this file when you add or supersede a document." Both rules are enforced by hope. docs/INTEGRITY-AUDIT.md already logs the drift as E-09, E-10, E-11 — access dates missing from evidence tables, pre-standard doc shapes in the Week-0 census, and root-level docs with no frontmatter at all.
For an agent picking this repo up cold, docs/README.md is the map. A silently stale map is worse than no map. Make it machine-checked.
What to build
Add scripts/lint-docs.mjs, wired as npm run lint:docs, failing with exit 1 on any of:
- A
.md under docs/ (recursive, excluding docs/README.md itself) missing YAML frontmatter, or missing any required key: title, doc_type, status, owner, created, updated, confidence, supersedes, sources_verified.
- A frontmatter value outside its allowed set:
doc_type: adr | research | spec | gate-result | pitch | brief | runbook
status: draft | review | accepted | superseded | killed
confidence: LOW | MED | HIGH
sources_verified: true | false
created / updated: YYYY-MM-DD
- A
.md under docs/ not linked from docs/README.md (match the relative path against any markdown link target in that file).
- A doc that does not end with an
## Open questions / what I could not verify section — CONTRIBUTING requires it on every doc.
- A relative markdown link inside
docs/ pointing at a path that does not exist.
Note: docs/gate/testbed.md currently uses doc_type: gate, which is not in CONTRIBUTING's list (gate-result is). Pick one, allow only that one, fix the outlier, and say which way you went in the PR description.
Also in scope: fix what the linter finds
The linter must be green on merge, so fixing the existing violations belongs in this PR — that is the issue, not scope creep. Expect to touch docs/research/census-week0/A5–A8 (pre-standard shape, E-10), any doc missing the Open-questions section, and docs/README.md rows for anything unlisted.
Do not rewrite a research finding, soften a FAIL, or change any number while fixing shape. Frontmatter and structure only.
Wiring
package.json: "lint:docs": "node scripts/lint-docs.mjs", added to the ci script after lint.
.github/workflows/ci.yml: a Lint docs step in the build job.
- No new dependencies. Frontmatter here is a flat
key: value list — hand-parse it; a YAML dependency is not warranted.
How to test
npm run lint:docs # exit 0 after your fixes
npm run ci
Prove each rule bites by breaking one thing at a time and confirming a clear, path-prefixed error:
- delete a frontmatter key from any
docs/*.md
- add
docs/scratch.md with valid frontmatter but no docs/README.md row
- point a link at
docs/does-not-exist.md
- strip the Open-questions heading from a doc
Revert every deliberate breakage before committing. Error messages must name the file, the line where available, and the rule violated — a future agent reads these, not the source.
Before you open the PR
Context
CONTRIBUTING.mdmandates YAML frontmatter on every.mdindocs/, anddocs/README.mdsays "Update this file when you add or supersede a document." Both rules are enforced by hope.docs/INTEGRITY-AUDIT.mdalready logs the drift as E-09, E-10, E-11 — access dates missing from evidence tables, pre-standard doc shapes in the Week-0 census, and root-level docs with no frontmatter at all.For an agent picking this repo up cold,
docs/README.mdis the map. A silently stale map is worse than no map. Make it machine-checked.What to build
Add
scripts/lint-docs.mjs, wired asnpm run lint:docs, failing with exit 1 on any of:.mdunderdocs/(recursive, excludingdocs/README.mditself) missing YAML frontmatter, or missing any required key:title,doc_type,status,owner,created,updated,confidence,supersedes,sources_verified.doc_type:adr | research | spec | gate-result | pitch | brief | runbookstatus:draft | review | accepted | superseded | killedconfidence:LOW | MED | HIGHsources_verified:true | falsecreated/updated:YYYY-MM-DD.mdunderdocs/not linked fromdocs/README.md(match the relative path against any markdown link target in that file).## Open questions / what I could not verifysection — CONTRIBUTING requires it on every doc.docs/pointing at a path that does not exist.Note:
docs/gate/testbed.mdcurrently usesdoc_type: gate, which is not in CONTRIBUTING's list (gate-resultis). Pick one, allow only that one, fix the outlier, and say which way you went in the PR description.Also in scope: fix what the linter finds
The linter must be green on merge, so fixing the existing violations belongs in this PR — that is the issue, not scope creep. Expect to touch
docs/research/census-week0/A5–A8(pre-standard shape, E-10), any doc missing the Open-questions section, anddocs/README.mdrows for anything unlisted.Do not rewrite a research finding, soften a FAIL, or change any number while fixing shape. Frontmatter and structure only.
Wiring
package.json:"lint:docs": "node scripts/lint-docs.mjs", added to theciscript afterlint..github/workflows/ci.yml: aLint docsstep in thebuildjob.key: valuelist — hand-parse it; a YAML dependency is not warranted.How to test
npm run lint:docs # exit 0 after your fixes npm run ciProve each rule bites by breaking one thing at a time and confirming a clear, path-prefixed error:
docs/*.mddocs/scratch.mdwith valid frontmatter but nodocs/README.mdrowdocs/does-not-exist.mdRevert every deliberate breakage before committing. Error messages must name the file, the line where available, and the rule violated — a future agent reads these, not the source.
Before you open the PR
npm run cigreen (includinglint:docs)npm run test:canarygreendocs/README.mdupdated; INTEGRITY-AUDIT E-09/E-10/E-11 rows updated to reflect what is now enforcedwave0/b0-docs-lint