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.
5.7 KiB
RFC: JSDoc completeness gate for the cordis surface
Status: implemented
Problem
The generated cordis catalog already walks every harness interface Events member and every ctx.<key> service class with the TypeScript compiler API, and already hard-errors on a missing @mode tag — a forcing function that made dispatch modes impossible to leave undocumented. Nothing equivalent guarded the rest of the JSDoc: a service method could ship with no doc at all, and no event or method documented its parameters or return value individually. A survey at adoption found 5 public service methods with no JSDoc and roughly 139 missing @param/@returns entries across 15 files — on the product API spine (ctx.bash, ctx.fs, ctx.sessions, …) and the cross-plugin event payload contracts, exactly the surface where "what does this argument mean" is the question a plugin author asks the IDE.
The AGENTS.md rule ("every export has a JSDoc explaining semantics") is prose-checkable only by review; the repo's stated preference is to encode invariants in mechanical gates. The scope "cordis service functions and events" has a precise machine definition that only the catalog generator knows: events are the interface Events members inside declare module 'cordis', and the service surface is the public methods of the class each interface Context key names. An ESLint rule cannot see that mapping; the generator computes it on every run.
Decision
Extend scripts/gen-cordis-catalog.ts — the same walk, the same @mode precedent — to enforce JSDoc COMPLETENESS on everything it catalogs. verify-cordis-catalog runs inside doc-sync, which both CI and the lefthook pre-push hook already execute, so the gate needs zero new wiring (quality-gates principle: one source of truth).
The contract:
- Events need description prose plus a non-empty
@paramfor every payload parameter. A payload parameter is a signature parameter that carries event data; thethisreceiver annotation and the trailing waterfallnextare exempt —nextis dispatch machinery whose semantics the@mode waterfalltag (and its structural cross-check) already owns, so restating it per event would be boilerplate. Documenting an exempt parameter anyway is allowed; only absence is checked. - Service classes need class-level JSDoc, and every public method needs description prose, a non-empty
@paramper parameter, and a non-empty@returnsunless the annotated return type isvoid/Promise<void>(where@returnsstays optional — resolution timing can be worth documenting — but is never required). - Stale tags error: an
@paramnaming no real parameter is a violation, mirroring the@mode-contradicts-signature check. Tag descriptions must be non-empty; their semantic quality beyond that is review's job. - Explicitness the walk can check: the gate is a pure-AST pass (no type checker), so a service method must annotate its return type (an inferred return cannot be classified) and surface parameters must be simple identifiers (a binding pattern has no name for
@paramto match). - Violations aggregate into one error listing every offender — a remediation pass sees the whole list at once. The previously fail-fast
@modechecks moved into the same aggregated report, with their message texts unchanged.
The tags are enforcement-only: parseJsDoc now ends description prose at the first block tag (standard JSDoc semantics, which also stops multi-line tag descriptions from leaking into the catalog as prose), so @param/@returns never change the rendered catalog.
Negative-path tests in packages/core/agent/tests/gen-cordis-catalog.spec.ts drive collectEvents/collectServices against synthetic fixtures to prove each guard fires and that the exemptions hold. The authoring rule lives in the root AGENTS.md conventions bullet alongside the @mode rule.
Alternatives considered
- An ESLint rule — cannot see the scope's machine definition (which
interface Eventsmembers and whichctx.<key>classes are the cordis surface); the catalog generator computes exactly that mapping on every run, so the gate lives there. - Rendering the tags into the catalog — restructuring the services section into per-method entries was considered and deliberately deferred: source JSDoc plus IDE hover is where method docs are consumed, and the catalog stays an index.
- An escape-hatch tag — none exists; the surface is small and curated (12 services, 57 methods, 27 events at adoption), and the point is that the check cannot be waved off.
Consequences
- A new event or service method cannot land with an undocumented parameter or result: the generator refuses to regenerate and
verify-cordis-catalogfails pre-push and in CI. The ~139 gaps found at adoption were filled in the same change, so the gate landed green. - The service surface must annotate return types explicitly and use identifier parameters. Neither constraint bound at adoption (every method already annotated; no destructured seam parameters existed); both are now load-bearing requirements a violating change will discover mechanically.
- The general AGENTS.md JSDoc rule ("one-liners when one line suffices") acquires a stricter carve-out on this surface: a one-line summary still suffices only when the method has no parameters and a void result.
@paramonnextorthisstays legal but unchecked — a deliberate asymmetry: the gate enforces the payload contract and refuses to demand boilerplate.- The rendered catalog is unchanged by the tags (prose stops at the first block tag). If method-level rendering is wanted later, that is a catalog-design decision to take separately, not a gap in this gate.