Add scripts/gen-cordis-catalog.ts: a fully-generated docs/cordis-catalog/ events-and-services.md cataloging every cordis event (exact signature + @mode) and ctx.<key> service (exact interface), modeled on gen-module-graph's --write/--check freshness gate. The harness tier renders in full from the interface Events / interface Context declarations and their JSDoc; the inherited cordis-core/loader/hmr/timer surface renders tersely from a curated table. The generator hard-errors on a missing @mode tag and on a tag that contradicts a conclusive signature shape (a trailing next param is structurally a waterfall). Signature blocks use a ts cordis-catalog fence that doc-typecheck skips. Type tokens cross-link to the core-data-structures catalog. This supersedes the hand-maintained event-taxonomy table: verify-event-taxonomy is deleted and verify-cordis-catalog joins doc-sync. architecture.md keeps the Event taxonomy heading (TOC anchor) but points at the catalog; the Service-map role table stays. RFC, AGENTS.md @mode authoring rule, and dependent doc/skill references updated. Negative gate tests cover the missing-tag and tag/shape-contradiction paths.
5.9 KiB
RFC: Generated cordis events + services catalog
Status: implemented (accepted 2026-06-20)
Context
A plugin author needs two reference surfaces that no single document gave them: every cordis event they can listen to (with its exact signature and dispatch mode) and every ctx.<key> service they can call (with its exact interface). The pieces existed but were scattered — a hand-maintained event-taxonomy table in docs/architecture.md (names + prose Mode/Purpose, name-set-checked by verify-event-taxonomy), a Service-map table (8 rows of role prose), and the interface Events / interface Context declarations themselves. The taxonomy table also could not catch a brand-new undocumented event: a name-set verifier only checks the names that are already in the table on both sides.
This is the wiring-axis complement to the core-data-structures catalog: that one catalogs the data structures the loop moves around (verified hand-pastes); this one catalogs the events and services that move them.
Decision
Generate the catalog from source instead of hand-maintaining a table and verifying a subset.
scripts/gen-cordis-catalog.ts walks the interface Events and interface Context declarations (plus the service classes) with the TypeScript compiler API and emits docs/cordis-catalog/events-and-services.md — one ## Events section (grouped by scope, each event rendered as signature + mode badge + its source JSDoc) and one ## Services section (each ctx.<key> with its public method signatures + class JSDoc). It mirrors the gen-module-graph pattern exactly: --write regenerates, --check fails if the committed file is stale, output is deterministic (sorted), and the file is a build artifact that is never hand-edited. verify-cordis-catalog (the --check) runs inside doc-sync, so the freshness gate fires in the same lefthook pre-push and CI paths as every other doc gate.
Pure generation is correct here because the codebase is disciplined enough that the AST is the whole truth: every event/service name is a string literal that round-trips to a static declaration — there are no dynamically-named events and no runtime-only services. So a generated doc cannot be wrong, and it closes the undocumented-event gap structurally (generation enumerates source rather than checking a hand-written subset).
Specific choices:
@modetag, cross-checked. Each harness event's JSDoc carries an explicit@mode emit|waterfall|paralleltag; the generator hard-errors on a missing tag. Where the signature shape is conclusive — a trailingnext: () => …parameter is structurally a waterfall — it asserts the tag agrees and hard-errors on a contradiction. The emit-vs-parallel distinction is not structurally visible (session/flushreturnsPromise<void> | voidwith nonext), so it is trusted from the tag. The authoring rule lives in AGENTS.md.- Tiered scope. The harness tier (the 8
@deepseek-ai/dsh-*services + their events) is rendered in full from source. The inherited tier (cordis-corectx.on/emit/effect/provide/…+ theinternal/*events + loader/hmr/timer) is pinned vendor source a plugin also sees; it is rendered tersely (name + one-line + source pointer) from a curated table in the generator, NOT walked from the vendor AST — the cordis-coreContextmixes true ctx members with non-service fields (root,baseUrl,logger), and the vendor surface changes only on a deliberate vendor sync. - Cross-links to the data-structure catalog. A type name in a signature (
GenerateOptions,StreamChunk,ToolDefinition, …) links to the core-data-structures page that documents it. The map is a small hand-curated const in the generator — NOTtype-equiv.manifest.json, which documents the…Mapsymbols while signatures reference the derived union names, and lists a few symbols on two pages. - A dedicated fence. Signature blocks use a
```ts cordis-cataloginfo string thatdoc-typecheckrecognizes and skips (a bare signature fragment is not standalone-compilable), excluded from the opt-out ratio — the same treatmenttype-equivblocks get.
This supersedes the event-taxonomy half of doc-sync enforcement: verify-event-taxonomy and its docs/architecture.md table are retired (the architecture.md heading stays, its body now points at the catalog; the Service-map role table stays as curated prose). The verify-don't-generate principle that RFC chose for the taxonomy is reversed for this surface only — the data here is mechanically complete, so generation is strictly stronger (full signatures, cannot drift, catches undocumented events) than a name-set check of a hand-table. doc-typecheck, verify-md-wrap, verify-md-links, and verify-type-equiv are unchanged.
Consequences
- The catalog cannot drift: a source change that the committed file doesn't reflect fails
verify-cordis-catalogin the pre-push hook and CI. A new event with no@modetag, or a tag that contradicts its signature, fails the generator outright. - Event prose now has a single home — the JSDoc at the declaration. Thin JSDoc yields a thin catalog entry, which pressures authors to document at the source (the generator is a forcing function for the AGENTS.md "every export has a semantic JSDoc" rule).
- The inherited tier is hand-summarized, so a vendor sync that adds/renames a cordis-core event or
ctxmember needs a matching edit to the curated table ingen-cordis-catalog.ts. This is the deliberate cost of not walking pinned vendor source; it changes rarely and is called out in the generator. verify-event-taxonomy.tsis deleted and thedocs/architecture.mdevent table is gone; anyone who linked to a specific table row now lands on the generated catalog instead.