5.1 KiB
RFC: JSDoc completeness gate for the cordis surface
Status: implemented
Problem
The generated Cordis catalog enforced event dispatch modes but not complete service and event contracts. Methods could lack descriptions, and parameters or returns could be undocumented on the cross-plugin API surface where IDE guidance matters most.
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.