Define the in-file RFC contract in docs/rfc/README.md § The file format: the header block (`# RFC: <title>` plus a dateless Status enum cross-checked against the lifecycle folder), the per-lifecycle body skeleton (a Problem opener everywhere; Proposal/Alternatives considered/ Acceptance criteria/Risks in proposed/; present-tense Decision/ Consequences with proposal-era headings banned in implemented/; the frozen proposal shape in rejected/), and a mandatory Alternatives considered section with a date-fenced grandfather comment for pre-format RFCs whose alternatives are not reconstructible from the record. Enforce it with a new doc-sync gate, scripts/verify-rfc-format.ts, and normalize all 112 RFCs to it: ~15 Status-line spellings collapse to the enum, 29 Context openers become Problem, the 39 legacy-format XXX debt markers are resolved and banned from reappearing, proposal-era sections in implemented RFCs are rewritten to shipped reality (including the web/fs/subagent seam RFCs' migration plans and test checklists, closing the doc-tiers deferred-work item on the web seam), every RFC gains an Alternatives considered section or the grandfather comment, and the bilingual pair is re-mirrored and re-recorded. Move the generated index tables out of README.md into a fully generated docs/rfc/INDEX.md — gen-rfc-index now writes the whole file, and verify-rfc-classification checks its freshness and rejects index-shaped rows in the curated README — which makes room for the format contract to live in the README front door instead of a separate FORMAT.md. The decision record, and the first RFC written in the new format, is docs/rfc/implemented/process/2026-07-05-uniform-rfc-format.md.
4.2 KiB
RFC: Doc-sync enforcement
Status: implemented
Problem
AGENTS.md promises that docs and code stay strictly in sync, but the promise was verified by eyeball. Review caught drift twice — a cookbook example contradicting the type policy, and a README citing the wrong registerAdapter call. Out-of-sync docs are worse than no docs, and this codebase is built primarily by agents that follow gates far more reliably than prose (mechanical quality gates). Two classes of doc drift are mechanically checkable: code blocks that no longer compile, and the event-taxonomy table that duplicates the interface Events declarations.
Decision
Two gates, mirroring the existing scripts/ style (tsx ESM, one job each):
doc-typecheckextracts every fenced```tsblock fromREADME.md,docs/**, andpackages/*/README.md, writes them to a temp project extending the roottsconfig.json, and compiles it withtsc -b. The temp project reuses the sourcepathsmap and the root project references, so documentation examples see source while vendored code remains checked under its own tsconfig settings. A block that is a deliberate sketch opts out with an explicit```ts ignore-checkinfo string; the script reports the opt-out ratio and fails if it exceeds half, so the escape hatch can't quietly become the norm.verify-event-taxonomyextracts the event names from theinterface Eventsblocks acrosspackages/*/srcand from the taxonomy table indocs/architecture.md, and asserts the two sets match exactly. Verify, don't generate: the table keeps its hand-written Mode/Purpose columns; only the set of names is checked. (Landing this surfaced three events the table had been missing —tools/change,llm/adapter-change,system-prompt/change.) Superseded by the generated cordis catalog: this gate and itsarchitecture.mdtable are retired in favor of the fully-generateddocs/cordis-catalog/events.md+docs/cordis-catalog/services.mdand theirverify-cordis-catalogfreshness gate. The other gates here (doc-typecheck, and theverify-md-wrapamendment below) are unaffected.
Both run via a shared doc-sync package.json script that the lefthook pre-push hook and CI both invoke (mechanical quality gates: hooks and CI call the same scripts, so the gate fires locally before a push — not only after it). They run after pnpm run typecheck, which validates the package/vendor build graph that doc-typecheck references.
Amendment (2026-06-17): a third gate, verify-md-wrap, was later folded into doc-sync. It parses each in-scope Markdown file (README.md, docs/**, packages/*/README.md, plus AGENTS.md / packages/AGENTS.md) with mdast-util-from-markdown + GFM and fails on any paragraph node spanning more than one source line, enforcing the docs/AGENTS.md "one physical line per paragraph" writing rule. Same verify-don't-generate principle: it reports hard-wraps and never rewrites, so it adds no formatting churn. doc-sync is now three gates.
Alternatives considered
- API-extractor golden reports (the deferred proposal) — deliberately deferred: low value for an internal monorepo where reviewers already see the source diff, and a heavy, finicky dependency.
- Generating the taxonomy table from source instead of verifying names — rejected as more machinery than the problem warranted; the table kept its hand-written Mode/Purpose columns until the generated cordis catalog superseded the check entirely.
Consequences
- Doc drift in the checkable classes now fails the pre-push hook and CI instead of waiting for a reviewer to notice. This is an instance of the "mechanical gates over prose" principle.
- Making doc snippets compile costs a few stub imports/
declares; theignore-checkratio must stay low or the gate is theater (the ratio guard enforces this). - The taxonomy check is name-only — a wrong Mode or Purpose column still needs human review.
- API reports remain available to revisit if the packages are ever published externally.