Files
deepseek-harness/docs/rfc/implemented/process/2026-07-04-cordis-jsdoc-completeness-gate.md
T
Tianyi Cui e6fad266a6 docs(rfc): define and enforce a uniform RFC format; adopt it across the corpus
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.
2026-07-05 22:58:25 +08:00

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 @param for every payload parameter. A payload parameter is a signature parameter that carries event data; the this receiver annotation and the trailing waterfall next are exempt — next is dispatch machinery whose semantics the @mode waterfall tag (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 @param per parameter, and a non-empty @returns unless the annotated return type is void/Promise<void> (where @returns stays optional — resolution timing can be worth documenting — but is never required).
  • Stale tags error: an @param naming 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 @param to match).
  • Violations aggregate into one error listing every offender — a remediation pass sees the whole list at once. The previously fail-fast @mode checks 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 Events members and which ctx.<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-catalog fails 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.
  • @param on next or this stays 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.