gen-cordis-catalog now hard-errors (aggregated, not fail-fast) when an event lacks description prose or a payload @param, or a public service method lacks JSDoc, a @param per parameter, a @returns on a non-void result, or an explicit return type annotation. The this receiver and the trailing waterfall next are exempt on events (mode machinery owned by @mode); a stale @param naming no real parameter errors, mirroring the @mode contradiction check. parseJsDoc now ends prose at the first block tag (standard JSDoc semantics), so the tags never change the rendered catalog — only Source: line pointers moved. Fills the ~139 gaps found across the 15 surface files, extends the spec with negative-path fixtures for every new guard plus the exemptions, records the decision as an implemented process RFC, and extends the AGENTS.md typed-events bullet with the authoring rule. Runs inside verify-cordis-catalog -> doc-sync, so CI and pre-push enforce it with zero new wiring.
5.4 KiB
RFC: JSDoc completeness gate for the cordis surface
Status: implemented (accepted 2026-07-04)
Context
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. Rendering them — 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. No escape-hatch tag 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.
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.
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.