2026-06-20 19:47:09 +08:00
/ * *
2026-07-12 03:36:43 +08:00
* Generate the Cordis event and service catalogs from static declarations .
2026-07-19 14:57:52 +08:00
* The walk enforces event modes , JSDoc parameter / return completeness , and
* signature type - link coverage ; inherited Cordis services come from the
* curated table below . ` --check ` verifies both committed artifacts .
2026-06-20 19:47:09 +08:00
* /
import { globSync , readFileSync , writeFileSync } from 'node:fs'
2026-07-06 02:28:44 +08:00
import { resolve , sep } from 'node:path'
2026-06-20 19:47:09 +08:00
import ts from 'typescript'
Gate JSDoc completeness on every package export
New doc-sync gate verify-export-jsdoc walks every module-level exported
name under packages/*/*/src and requires description prose everywhere,
plus @param per parameter and @returns on non-void annotated returns for
function-like exports, public class methods, properties, and accessors.
The parsing + check helpers move out of gen-cordis-catalog.ts into a
shared scripts/jsdoc.ts so 'documented' means one thing on both gated
surfaces.
Deliberate exemptions (documented in the RFC): heritage-declared class
members (the seam declaration is the doc's one home — the one checker
query in an otherwise pure-AST walk), cordis plugin-protocol slots
(name/inject/reusable/Config/apply, top-level and static), constructors,
overload implementations, declare-module augmentation bodies, and
re-export statements (checked at the defining module).
The 203 under-documented exports the gate found at adoption are filled
in this change, so the gate lands green; generated catalogs/graphs are
regenerated for the shifted line pointers.
RFC: docs/rfc/implemented/process/2026-07-06-export-surface-jsdoc-gate.md
2026-07-06 22:09:30 +08:00
import { checkParams , checkReturns , parseJsDoc , parseTags , pointer , rawJsDoc , reportViolations , type Mode } from './jsdoc.ts'
2026-07-16 22:07:06 +08:00
import { cordisModuleBody , eventMembers , serviceClasses } from './cordis-walk.ts'
2026-06-20 19:47:09 +08:00
const root = resolve ( import . meta . dirname , '..' )
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
const OUT_EVENTS = 'docs/cordis-catalog/events.md'
const OUT_SERVICES = 'docs/cordis-catalog/services.md'
2026-06-20 19:47:09 +08:00
/ * * T h e f e n c e d - b l o c k i n f o s t r i n g f o r g e n e r a t e d s i g n a t u r e b l o c k s ( s k i p p e d b y
* doc - typecheck , since a bare signature fragment is not standalone - compilable ) . * /
const FENCE = 'ts cordis-catalog'
/ * *
2026-07-19 14:57:52 +08:00
* One primary core - data - structures page per project type used by a generated
* signature . This stays curated because union names intentionally do not
* reuse the type - equivalence manifest ' s map - symbol entries and some symbols
* appear on more than one page .
2026-06-20 19:47:09 +08:00
* /
feat: generated plugin config catalog (docs/config-catalog.md)
scripts/gen-config-catalog.ts walks every packages/<group>/<pkg> entry with
the TypeScript compiler API and emits docs/config-catalog.md: per loadable
plugin, the verbatim config declaration (JSDoc included) its apply/constructor
receives in a ts config-catalog fence, the inject requirements, resolved links
for every referenced type (package-local types pasted transitively, other
plugins' config types as intra-page anchors, LINK_MAP names to
core-data-structures, workspace types to source), and terse classification
lists for config-free plugins, abstract seams, and libraries — classification
is total, so a new package cannot go undocumented.
The walk enforces per-field JSDoc prose on every pasted declaration and
statically cross-checks the schemastery schema (z.object keys, z.intersect
composition across packages): every schema-validated key must be a declared
member of the config type. One violation existed repo-wide — the agents[].id
field in dsh-agent-loop — fixed by adding its JSDoc (which shifts the
cordis-catalog services page's source pointers; regenerated).
verify-config-catalog (--check) joins doc-sync; doc-typecheck learns the
ts config-catalog fence; gen-cordis-catalog exports its JSDoc/pointer helpers
and LINK_MAP for reuse. Negative-path spec in
packages/core/agent-core/tests/gen-config-catalog.spec.ts mirrors the
gen-cordis-catalog spec. Decision record:
docs/rfc/implemented/process/2026-07-06-generated-config-catalog.md (includes
the deliberate acceptance of README ## Config overlap).
2026-07-06 21:57:17 +08:00
export const LINK_MAP : Record < string , string > = {
2026-06-20 19:47:09 +08:00
Agent : 'core.md' ,
2026-07-19 14:57:52 +08:00
AgentOptions : 'core.md' ,
AgentStatus : 'core.md' ,
2026-06-20 19:47:09 +08:00
ContentBlock : 'core.md' ,
2026-07-19 14:57:52 +08:00
ContinuationDecision : 'core.md' ,
ContinuationStop : 'core.md' ,
2026-06-20 19:47:09 +08:00
GenerateOptions : 'core.md' ,
loop: every request is built from the log — boundary snapshot, header events, config-only waterfall
The loop is now transmission-stateless; a request is a pure function of
(session log, this step's rendered assembly, current AgentOptions):
- The reconstruction boundary is step/start: the messages snapshot is
taken in the same synchronous frame immediately before the step/start
append, so the request's messages are exactly the derivation over
events[0..stepStartSeq) — an inject() from an agent/request listener
(or any concurrent task) lands after the boundary and joins the NEXT
request. This changes behavior for a synchronous step/start
session/event listener that appends content (master derived after the
append, so such a listener could reach the current request):
agent/pre-step is the sanctioned seam for current-request content.
- agent/request is re-typed to config-only: (agent, turn, step,
config: LlmCallConfig, next) → LlmCallConfig. The frozen seed comes
from AgentOptions on a loop instance's first request (explicit options
beat the logged baseline — fork overrides and resume reconfiguration
stay correct) and from the log's folded header afterwards; listeners
return a replacement to switch. Content shaping through the request is
no longer expressible — model-visible content flows through the log
channels.
- recordRequestHeader appends whatever header event the request owes the
log before dispatch: an 'initial'/'resume' snapshot anchoring each
loop instance, a round-trip-verified delta on change, a 'fallback'
snapshot when the encoding cannot express it. Session.requestHeader()
is the log's incrementally-folded baseline.
- Requests are deep-frozen before dispatch (deepFreeze exempts the
AbortSignal — freezing one breaks AbortController.abort() outright);
frozen + sessionId is the loop-built marker the dev invariant keys on.
Ported from #162 and re-anchored on the log: the append-extension /
frozen-end-to-end / compaction-resend / prompt-change property tests,
plus new specs for the boundary semantics, resume anchoring, and the
end-to-end theorem (every recorded request rebuilds byte-equal from the
log alone). Live cache-hit e2e (request-cache.e2e.ts) verified against
the real DeepSeek API. Snapshot goldens intentionally stale until the
single re-record after the compact/summary envelope lands.
2026-07-06 03:07:34 +08:00
LlmCallConfig : 'core.md' ,
2026-07-19 14:57:52 +08:00
LlmModelInfo : 'core.md' ,
LlmProviderInfo : 'core.md' ,
Message : 'core.md' ,
MessageSource : 'core.md' ,
PromptDecision : 'core.md' ,
2026-07-19 16:41:51 +08:00
RequestError : 'core.md' ,
RequestErrorDecision : 'core.md' ,
2026-06-20 19:47:09 +08:00
SessionEvent : 'core.md' ,
2026-07-19 14:57:52 +08:00
SessionId : 'core.md' ,
2026-07-12 08:57:05 +08:00
SessionStartSource : 'core.md' ,
2026-07-11 21:37:38 +08:00
ApprovalOutcome : 'approval.md' ,
ApprovalPolicy : 'approval.md' ,
ApprovalRequest : 'approval.md' ,
2026-07-19 14:57:52 +08:00
ApprovalService : 'approval.md' ,
2026-06-20 19:47:09 +08:00
BashExecRequest : 'bash.md' ,
BashExecSpec : 'bash.md' ,
2026-07-19 14:57:52 +08:00
BashProcess : 'bash.md' ,
2026-06-20 19:47:09 +08:00
BashRunResult : 'bash.md' ,
2026-07-19 14:57:52 +08:00
DshEnvironment : 'bash.md' ,
2026-07-08 02:38:47 +08:00
CodeRunRequest : 'code-runtime.md' ,
CodeRunResult : 'code-runtime.md' ,
2026-07-19 14:57:52 +08:00
CompactionResult : 'compaction.md' ,
2026-07-19 16:41:51 +08:00
CompactionTrigger : 'compaction.md' ,
2026-07-19 14:57:52 +08:00
FileReadOutcome : 'filesystem.md' ,
FsDirEntry : 'filesystem.md' ,
2026-06-22 14:53:36 +08:00
FsEditOutcome : 'filesystem.md' ,
FsEditRequest : 'filesystem.md' ,
2026-06-26 18:14:30 +08:00
FsInfo : 'filesystem.md' ,
2026-07-19 14:57:52 +08:00
FsPathInfo : 'filesystem.md' ,
FsPolicyExec : 'filesystem.md' ,
2026-06-22 14:53:36 +08:00
FsTarget : 'filesystem.md' ,
FsVersion : 'filesystem.md' ,
fix(fs): address review — rename to dsh-fs-policy, fs/*-intent events, RFC currency, ENOTDIR
Rename per review naming decisions:
- package dsh-file-context → dsh-fs-policy (dir, package name, plugin name,
tsconfig refs, importers, type-equiv manifest, generated catalog + module-graph)
- events fs/write-expectation → fs/write-intent, fs/edit-expectation → fs/edit-intent
(fs/observed unchanged); type FsWriteExpectation → FsWriteIntent, "expectation"
wording → "intent" throughout
- exported FileContextExec → FsPolicyExec
Make the implemented RFCs describe what shipped, not the superseded designs:
the 2026-06-17 capability-seam + tool-schemas RFCs no longer place policy on
ctx.fs or use full/partial-view authorization, and the fsspec RFC's ctx.fileContext
service prose is rewritten to the fs/* event-gate reality (freshness-based auth).
Sharpen docs/rfc/implemented/AGENTS.md: a rename is a fact to fix IN PLACE — the
"new RFC" escape hatch is for macro decision reversals only, not renames.
Code fixes from review:
- fsio.ts resolveLocalTarget/probe translate ENOTDIR (a parent path segment is a
file) into the structured FsError taxonomy instead of leaking a raw Node error;
resolve reports FS_NOT_FOUND, probe reports absent. Regression tests proven to
fail on the unfixed code.
- tool-fs HMR test now asserts prompt sections (not just tool schemas) are
withdrawn on disposal.
- fs/observed is a plain (unguarded) ctx.emit: correct the fs-policy comment,
filesystem.md, and tool-fs module doc that wrongly claimed the tool "contains"
a throwing listener; a throw surfaces as the tool's isError result.
- drop the false "loaded by the default product config" claim (no config wires
the fs tools yet), the duplicate ctx.bash service-map row, the stale
FileReadRequest catalog link-map entry, and the fs/fs README EOF blank line;
correct the dsh-fs package.json description.
2026-07-02 03:12:38 +08:00
FsWriteIntent : 'filesystem.md' ,
2026-06-22 14:53:36 +08:00
FsWriteOutcome : 'filesystem.md' ,
2026-07-19 18:47:34 +08:00
CreateGoalRequest : 'goal.md' ,
CreateGoalSpec : 'goal.md' ,
EditGoalRequest : 'goal.md' ,
GoalChanged : 'goal.md' ,
GoalRef : 'goal.md' ,
GoalView : 'goal.md' ,
2026-07-19 14:57:52 +08:00
LlmAdapter : 'llm-streaming.md' ,
LlmService : 'llm-streaming.md' ,
StreamChunk : 'llm-streaming.md' ,
CreateSessionOptions : 'persistence.md' ,
SessionHeader : 'persistence.md' ,
SessionLocation : 'persistence.md' ,
ConfinedArgv : 'sandbox.md' ,
SandboxMode : 'sandbox.md' ,
SandboxPolicy : 'sandbox.md' ,
ScopeKey : 'scope.md' ,
Scoped : 'scope.md' ,
EpochHeader : 'session.md' ,
Session : 'session.md' ,
TurnEndReason : 'session.md' ,
SessionEventReadRequest : 'session-query.md' ,
SessionEventRecord : 'session-query.md' ,
SessionEventTrace : 'session-query.md' ,
SessionEventTraceRequest : 'session-query.md' ,
SessionEventWindow : 'session-query.md' ,
SessionLineageTrace : 'session-query.md' ,
SessionRecord : 'session-query.md' ,
SkillDefinition : 'skills.md' ,
SkillLookupOptions : 'skills.md' ,
SkillProvider : 'skills.md' ,
SkillRegistration : 'skills.md' ,
SkillSummary : 'skills.md' ,
SaveTextSpill : 'spill.md' ,
SpillRef : 'spill.md' ,
SubagentProvider : 'subagent.md' ,
SubagentRun : 'subagent.md' ,
SubagentService : 'subagent.md' ,
SubagentStartRequest : 'subagent.md' ,
AssembleContext : 'system-prompt.md' ,
PromptSection : 'system-prompt.md' ,
SystemPrompt : 'system-prompt.md' ,
ToolProviderResult : 'system-prompt.md' ,
TaskDoneListener : 'tasks.md' ,
TaskId : 'tasks.md' ,
TaskRead : 'tasks.md' ,
TaskSnapshot : 'tasks.md' ,
TaskStart : 'tasks.md' ,
TokenMeasurement : 'token-meter.md' ,
PostToolDecision : 'tools.md' ,
PreToolDecision : 'tools.md' ,
ToolDefinition : 'tools.md' ,
ToolExecution : 'tools.md' ,
ToolExecutionInput : 'tools.md' ,
ToolExecutionMode : 'tools.md' ,
ToolExecutionResult : 'tools.md' ,
ToolExecutionToken : 'tools.md' ,
ToolGuard : 'tools.md' ,
ToolRegistry : 'tools.md' ,
ToolRestriction : 'tools.md' ,
ToolSchema : 'tools.md' ,
AskUserQuestionAnswer : 'user-interaction.md' ,
AskUserQuestionRequest : 'user-interaction.md' ,
UserInteractionProvider : 'user-interaction.md' ,
WebFetchProvider : 'web.md' ,
WebFetchRequest : 'web.md' ,
WebFetchResult : 'web.md' ,
WebSearchProvider : 'web.md' ,
WebSearchRequest : 'web.md' ,
WebSearchResult : 'web.md' ,
WorkflowRun : 'workflow.md' ,
WorkflowRunInfo : 'workflow.md' ,
WorkflowStartRequest : 'workflow.md' ,
}
/** TypeScript lib and pinned framework types that have no repository-owned data page. */
const FOUNDATION_TYPE_NAMES = new Set ( [
'AbortSignal' ,
'AsyncIterable' ,
'Context' ,
'Error' ,
'Pick' ,
'Promise' ,
'Readonly' ,
] )
/** Project types deliberately documented outside the core-data catalog. */
const TYPE_LINK_EXEMPTIONS : Readonly < Record < string , string > > = {
AgentFactory : 'agent creation seam is owned by packages/core/agent/README.md' ,
AgentHandle : 'agent ownership handle is owned by packages/core/agent/README.md' ,
BashEnvContributor : 'service-local extension type is owned by packages/bash/tool-bash/src/index.ts' ,
BashEnvVariableInfo : 'service-local metadata type is owned by packages/bash/tool-bash/src/index.ts' ,
CompactAgentContext : 'compaction service input is owned by packages/compact/compact/src/index.ts' ,
CreateAgentOptions : 'agent creation contract is owned by packages/core/agent/README.md' ,
PresetOption : 'deployment menu metadata is owned by packages/ui/permission/README.md' ,
PresetSpec : 'deployment preset composition is owned by packages/ui/permission/README.md' ,
PromptAssembly : 'assembly result is owned by packages/core/system-prompt/README.md' ,
ResumeAgentOptions : 'agent resume contract is owned by packages/core/agent/README.md' ,
SessionForkSource : 'service-local fork input is owned by packages/core/session/src/index.ts' ,
SubagentRunEndInfo : 'event-local snapshot is owned by packages/subagent/subagent/src/index.ts' ,
SubagentRunInfo : 'event-local snapshot is owned by packages/subagent/subagent/src/index.ts' ,
WorkflowAgentEndInfo : 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts' ,
WorkflowAgentInfo : 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts' ,
WorkflowResultInfo : 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts' ,
}
/** Collect named references from parameter, generic-constraint/default, and return types. */
function signatureTypeNames ( member : ts.MethodSignature | ts . MethodDeclaration , sf : ts.SourceFile ) : string [ ] {
const declared = new Set ( member . typeParameters ? . map ( parameter = > parameter . name . text ) ? ? [ ] )
const referenced = new Set < string > ( )
const visit = ( node : ts.Node ) : void = > {
if ( ts . isTypeReferenceNode ( node ) ) referenced . add ( node . typeName . getText ( sf ) )
if ( ts . isTypeQueryNode ( node ) ) referenced . add ( node . exprName . getText ( sf ) )
ts . forEachChild ( node , visit )
}
for ( const parameter of member . typeParameters ? ? [ ] ) {
if ( parameter . constraint ) visit ( parameter . constraint )
if ( parameter . default ) visit ( parameter . default )
}
for ( const parameter of member . parameters ) {
if ( parameter . type ) visit ( parameter . type )
}
if ( member . type ) visit ( member . type )
return [ . . . referenced ] . filter ( name = > ! declared . has ( name ) ) . sort ( )
}
/** Append fail-closed signature type-link violations with actionable ownership choices. */
function checkTypeLinks (
where : string ,
member : ts.MethodSignature | ts . MethodDeclaration ,
sf : ts.SourceFile ,
violations : string [ ] ,
) : void {
for ( const name of signatureTypeNames ( member , sf ) ) {
if ( Object . hasOwn ( LINK_MAP , name )
|| FOUNDATION_TYPE_NAMES . has ( name )
|| Object . hasOwn ( TYPE_LINK_EXEMPTIONS , name ) ) continue
violations . push (
` ${ where } references unclassified type ' ${ name } '. Add it to LINK_MAP with its core-data-structures page, `
+ 'to FOUNDATION_TYPE_NAMES if TypeScript or Cordis owns it, or to TYPE_LINK_EXEMPTIONS with '
+ 'the non-catalog documentation owner.' ,
)
}
}
/** Throw one aggregated diagnostic for every unclassified signature type. */
function reportTypeLinkViolations ( gate : string , violations : string [ ] ) : void {
if ( violations . length === 0 ) return
throw new Error (
` ${ gate } : ${ violations . length } signature type-link coverage violation(s): \ n `
+ violations . map ( violation = > ` ${ violation } ` ) . join ( '\n' ) ,
)
2026-06-20 19:47:09 +08:00
}
/** One harness event, extracted from an `interface Events` block. */
interface EventEntry {
/** Scoped name, e.g. `agent/request`. */
name : string
/** The scope prefix, e.g. `agent` (everything before the first `/`). */
scope : string
/** Full signature text (the method-signature member, JSDoc stripped). */
signature : string
2026-07-19 12:52:34 +08:00
/** Original declaration JSDoc, dedented from its containing interface. */
jsDoc : string
2026-06-20 19:47:09 +08:00
/** Dispatch mode from the `@mode` tag. */
mode : Mode
/** Description prose (JSDoc minus the `@mode` tag), one line per paragraph. */
doc : string
/** Source pointer `packages/…/file.ts:line` of the declaration. */
source : string
}
2026-07-19 12:52:34 +08:00
/** One public service method and the source contract attached to it. */
interface ServiceMethodEntry {
/** Public method signature (body stripped). */
signature : string
/** Original method JSDoc, dedented from its containing class. */
jsDoc : string
}
2026-06-20 19:47:09 +08:00
/** One harness service, extracted from an `interface Context` block. */
interface ServiceEntry {
/** The `ctx.<key>` name, e.g. `llm`. */
key : string
/** The service class/interface name, e.g. `LlmService`. */
type : string
/** Whether the service class is abstract (a seam interface). */
abstract : boolean
/** Class-level JSDoc prose, one line per paragraph. */
doc : string
2026-07-19 12:52:34 +08:00
/** Public methods (bodies stripped), in source order. */
methods : ServiceMethodEntry [ ]
2026-06-20 19:47:09 +08:00
/** Source pointer of the class declaration. */
source : string
}
/** A terse inherited-tier entry (pinned vendor surface). */
interface InheritedEntry {
name : string
summary : string
/** Source pointer `vendor/…:line`. */
source : string
}
2026-07-16 22:07:06 +08:00
// cordisModuleBody / eventMembers / serviceClasses live in cordis-walk.ts,
// shared with gen-website-api.ts — one walk, two renderers.
2026-06-20 19:47:09 +08:00
/** The signature text of a method-signature member (everything but a body). */
function memberSignature ( member : ts.TypeElement | ts . ClassElement , sf : ts.SourceFile ) : string {
const full = member . getText ( sf )
const body = ( member as { body? : ts.Node } ) . body
const sig = body ? full . slice ( 0 , full . length - body . getText ( sf ) . length ) : full
return sig . replace ( /\s*;?\s*$/ , '' ) . replace ( /\s+/g , ' ' ) . trim ( )
}
2026-07-19 12:52:34 +08:00
/ * *
* Copy a node ' s original JSDoc while removing only the indentation imposed by
* its containing interface or class .
* /
function jsDocText ( text : string , sf : ts.SourceFile , node : ts.Node ) : string {
const raw = rawJsDoc ( text , node )
if ( ! raw ) return ''
const start = text . lastIndexOf ( raw , node . getStart ( sf ) )
const { line } = sf . getLineAndCharacterOfPosition ( start )
const lineStart = sf . getPositionOfLineAndCharacter ( line , 0 )
const indent = text . slice ( lineStart , start )
return raw . split ( '\n' )
. map ( ( lineText , index ) = > index > 0 && lineText . startsWith ( indent ) ? lineText . slice ( indent . length ) : lineText )
. join ( '\n' )
}
Add JSDoc completeness gate for the cordis surface
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.
2026-07-04 19:06:35 +08:00
/ * * W a l k e v e r y h a r n e s s ` i n t e r f a c e E v e n t s ` b l o c k a n d e x t r a c t i t s e v e n t s , h a r d -
* erroring ( aggregated ) on any JSDoc - completeness violation : a missing /
* contradicted ` @mode ` , missing description prose , or an undocumented payload
* parameter . ` scanRoot ` defaults to the repo root ; tests pass a fixture dir . * /
2026-06-20 19:47:09 +08:00
export function collectEvents ( scanRoot : string = root ) : EventEntry [ ] {
const entries : EventEntry [ ] = [ ]
Add JSDoc completeness gate for the cordis surface
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.
2026-07-04 19:06:35 +08:00
const violations : string [ ] = [ ]
2026-07-19 14:57:52 +08:00
const typeLinkViolations : string [ ] = [ ]
2026-07-06 02:28:44 +08:00
for ( const rel of globSync ( 'packages/*/*/src/*.ts' , { cwd : scanRoot } ) . map ( s = > s . split ( sep ) . join ( '/' ) ) . sort ( ) ) {
2026-06-20 19:47:09 +08:00
const abs = resolve ( scanRoot , rel )
const text = readFileSync ( abs , 'utf8' )
if ( ! text . includes ( 'interface Events' ) ) continue
const sf = ts . createSourceFile ( abs , text , ts . ScriptTarget . Latest , true )
const body = cordisModuleBody ( sf )
if ( ! body ) continue
2026-07-16 22:07:06 +08:00
for ( const { name , member } of eventMembers ( body , sf ) ) {
const signature = memberSignature ( member , sf )
const raw = rawJsDoc ( text , member )
const { doc , mode } = parseJsDoc ( raw )
const src = pointer ( rel , sf , member )
const where = ` event ' ${ name } ' ( ${ src } ) `
2026-07-19 14:57:52 +08:00
checkTypeLinks ( where , member , sf , typeLinkViolations )
2026-07-16 22:07:06 +08:00
if ( ! mode ) {
violations . push ( ` ${ where } is missing an @mode tag. Add '@mode emit|waterfall|parallel|serial' to its JSDoc (see AGENTS.md). ` )
2026-06-20 19:47:09 +08:00
}
2026-07-16 22:07:06 +08:00
// Conclusive structural check: a trailing `next: () => …` parameter is a
// waterfall. (emit vs parallel vs serial is not structurally
// distinguishable, so it is trusted from the tag.)
const last = member . parameters . at ( - 1 )
const hasNext = ! ! last && last . name . getText ( sf ) === 'next'
if ( mode && hasNext && mode !== 'waterfall' ) {
violations . push ( ` ${ where } has a trailing 'next' parameter (structurally a waterfall) but is tagged '@mode ${ mode } '. Fix the tag or the signature. ` )
2026-06-20 19:47:09 +08:00
}
2026-07-16 22:07:06 +08:00
if ( mode && ! hasNext && mode === 'waterfall' ) {
violations . push ( ` ${ where } is tagged '@mode waterfall' but has no trailing 'next' parameter. A waterfall delegates via next(). ` )
}
if ( ! doc ) violations . push ( ` ${ where } has no description prose. Say what happened / what a listener may do, above the block tags. ` )
// Payload parameters need a non-empty @param. The `this` receiver is not
// payload, and a waterfall's trailing `next` is covered by its mode.
const { params } = parseTags ( raw )
checkParams ( where , 'event' , member . parameters , params , sf ,
p = > ( ts . isIdentifier ( p . name ) && p . name . text === 'this' ) || ( hasNext && p === last ) , violations )
2026-07-19 12:52:34 +08:00
if ( mode ) entries . push ( { name , scope : name.split ( '/' ) [ 0 ] ? ? name , signature , jsDoc : jsDocText ( text , sf , member ) , mode , doc , source : src } )
2026-06-20 19:47:09 +08:00
}
}
Gate JSDoc completeness on every package export
New doc-sync gate verify-export-jsdoc walks every module-level exported
name under packages/*/*/src and requires description prose everywhere,
plus @param per parameter and @returns on non-void annotated returns for
function-like exports, public class methods, properties, and accessors.
The parsing + check helpers move out of gen-cordis-catalog.ts into a
shared scripts/jsdoc.ts so 'documented' means one thing on both gated
surfaces.
Deliberate exemptions (documented in the RFC): heritage-declared class
members (the seam declaration is the doc's one home — the one checker
query in an otherwise pure-AST walk), cordis plugin-protocol slots
(name/inject/reusable/Config/apply, top-level and static), constructors,
overload implementations, declare-module augmentation bodies, and
re-export statements (checked at the defining module).
The 203 under-documented exports the gate found at adoption are filled
in this change, so the gate lands green; generated catalogs/graphs are
regenerated for the shifted line pointers.
RFC: docs/rfc/implemented/process/2026-07-06-export-surface-jsdoc-gate.md
2026-07-06 22:09:30 +08:00
reportViolations ( 'gen-cordis-catalog' , violations )
2026-07-19 14:57:52 +08:00
reportTypeLinkViolations ( 'gen-cordis-catalog' , typeLinkViolations )
2026-06-20 19:47:09 +08:00
return entries
}
Add JSDoc completeness gate for the cordis surface
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.
2026-07-04 19:06:35 +08:00
/ * * W a l k e v e r y h a r n e s s ` i n t e r f a c e C o n t e x t ` b l o c k + i t s s e r v i c e c l a s s , h a r d -
* erroring ( aggregated ) on any JSDoc - completeness violation : a class or public
* method without JSDoc prose , an undocumented parameter , a stale ` @param ` , a
* missing ` @returns ` on a non - void method , or an inferred ( unannotated ) return
* type the pure - AST walk cannot classify .
2026-06-20 19:47:09 +08:00
* ` scanRoot ` defaults to the repo root ; tests pass a fixture dir . * /
export function collectServices ( scanRoot : string = root ) : ServiceEntry [ ] {
const entries : ServiceEntry [ ] = [ ]
Add JSDoc completeness gate for the cordis surface
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.
2026-07-04 19:06:35 +08:00
const violations : string [ ] = [ ]
2026-07-19 14:57:52 +08:00
const typeLinkViolations : string [ ] = [ ]
2026-07-06 02:28:44 +08:00
for ( const rel of globSync ( 'packages/*/*/src/index.ts' , { cwd : scanRoot } ) . map ( s = > s . split ( sep ) . join ( '/' ) ) . sort ( ) ) {
2026-06-20 19:47:09 +08:00
const abs = resolve ( scanRoot , rel )
const text = readFileSync ( abs , 'utf8' )
if ( ! text . includes ( 'interface Context' ) ) continue
const sf = ts . createSourceFile ( abs , text , ts . ScriptTarget . Latest , true )
const body = cordisModuleBody ( sf )
if ( ! body ) continue
2026-07-16 22:07:06 +08:00
// Resolve each ctx key to its service class (shared walk) and emit an entry.
for ( const { key , type , cls , abstract , doc : clsDoc } of serviceClasses ( body , sf , rel , violations ) ) {
2026-07-19 12:52:34 +08:00
const methods : ServiceMethodEntry [ ] = [ ]
2026-06-20 19:47:09 +08:00
for ( const member of cls . members ) {
if ( ! ts . isMethodDeclaration ( member ) ) continue
2026-07-13 23:27:00 +08:00
// Only instance methods callable through `ctx.<key>` are surface;
// private, protected, and static methods are not.
2026-06-20 20:04:39 +08:00
const nonPublic = member . modifiers ? . some ( m = >
m . kind === ts . SyntaxKind . PrivateKeyword
|| m . kind === ts . SyntaxKind . ProtectedKeyword
|| m . kind === ts . SyntaxKind . StaticKeyword )
2026-06-20 19:47:09 +08:00
|| ts . isPrivateIdentifier ( member . name )
2026-06-20 20:04:39 +08:00
if ( nonPublic ) continue
2026-06-20 19:47:09 +08:00
const memberName = member . name . getText ( sf )
if ( memberName . startsWith ( '[' ) ) continue // computed/symbol members
Add JSDoc completeness gate for the cordis surface
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.
2026-07-04 19:06:35 +08:00
const where = ` service method ctx. ${ key } . ${ memberName } ( ${ pointer ( rel , sf , member ) } ) `
2026-07-19 14:57:52 +08:00
checkTypeLinks ( where , member , sf , typeLinkViolations )
Add JSDoc completeness gate for the cordis surface
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.
2026-07-04 19:06:35 +08:00
const raw = rawJsDoc ( text , member )
2026-07-19 12:52:34 +08:00
methods . push ( { signature : memberSignature ( member , sf ) , jsDoc : jsDocText ( text , sf , member ) } )
Add JSDoc completeness gate for the cordis surface
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.
2026-07-04 19:06:35 +08:00
if ( ! raw ) { violations . push ( ` ${ where } has no JSDoc. ` ) ; continue }
if ( ! parseJsDoc ( raw ) . doc ) violations . push ( ` ${ where } has no description prose above its block tags. ` )
const { params , returns } = parseTags ( raw )
Gate JSDoc completeness on every package export
New doc-sync gate verify-export-jsdoc walks every module-level exported
name under packages/*/*/src and requires description prose everywhere,
plus @param per parameter and @returns on non-void annotated returns for
function-like exports, public class methods, properties, and accessors.
The parsing + check helpers move out of gen-cordis-catalog.ts into a
shared scripts/jsdoc.ts so 'documented' means one thing on both gated
surfaces.
Deliberate exemptions (documented in the RFC): heritage-declared class
members (the seam declaration is the doc's one home — the one checker
query in an otherwise pure-AST walk), cordis plugin-protocol slots
(name/inject/reusable/Config/apply, top-level and static), constructors,
overload implementations, declare-module augmentation bodies, and
re-export statements (checked at the defining module).
The 203 under-documented exports the gate found at adoption are filled
in this change, so the gate lands green; generated catalogs/graphs are
regenerated for the shifted line pointers.
RFC: docs/rfc/implemented/process/2026-07-06-export-surface-jsdoc-gate.md
2026-07-06 22:09:30 +08:00
// Every parameter needs a non-empty @param (`this` receiver exempt),
// and a non-void ANNOTATED result needs a non-empty @returns — the
// shared checkers carry the exact contract.
checkParams ( where , 'service' , member . parameters , params , sf ,
p = > ts . isIdentifier ( p . name ) && p . name . text === 'this' , violations )
checkReturns ( where , member . type , returns , sf , violations )
2026-06-20 19:47:09 +08:00
}
entries . push ( {
key ,
type ,
abstract ,
Add JSDoc completeness gate for the cordis surface
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.
2026-07-04 19:06:35 +08:00
doc : clsDoc ,
2026-06-20 19:47:09 +08:00
methods ,
source : pointer ( rel , sf , cls ) ,
} )
}
}
Gate JSDoc completeness on every package export
New doc-sync gate verify-export-jsdoc walks every module-level exported
name under packages/*/*/src and requires description prose everywhere,
plus @param per parameter and @returns on non-void annotated returns for
function-like exports, public class methods, properties, and accessors.
The parsing + check helpers move out of gen-cordis-catalog.ts into a
shared scripts/jsdoc.ts so 'documented' means one thing on both gated
surfaces.
Deliberate exemptions (documented in the RFC): heritage-declared class
members (the seam declaration is the doc's one home — the one checker
query in an otherwise pure-AST walk), cordis plugin-protocol slots
(name/inject/reusable/Config/apply, top-level and static), constructors,
overload implementations, declare-module augmentation bodies, and
re-export statements (checked at the defining module).
The 203 under-documented exports the gate found at adoption are filled
in this change, so the gate lands green; generated catalogs/graphs are
regenerated for the shifted line pointers.
RFC: docs/rfc/implemented/process/2026-07-06-export-surface-jsdoc-gate.md
2026-07-06 22:09:30 +08:00
reportViolations ( 'gen-cordis-catalog' , violations )
2026-07-19 14:57:52 +08:00
reportTypeLinkViolations ( 'gen-cordis-catalog' , typeLinkViolations )
2026-06-20 19:47:09 +08:00
return entries . sort ( ( a , b ) = > a . key . localeCompare ( b . key ) )
}
/ * *
* The inherited tier — cordis core + loader / hmr / timer . Curated , terse , and
* hand - summarized because ( a ) it is pinned vendor source that changes only on a
* deliberate vendor sync , ( b ) the cordis - core ` Context ` mixes true ctx members
* with non - service fields ( ` root ` , ` baseUrl ` , ` logger ` ) that a blind walk would
* wrongly surface as services , and ( c ) the internal / * events carry no JSDoc to
* render . Source pointers are verified against vendor by ` verify-md-links ` '
* sibling check is N / A ; keep them current on a vendor bump .
* /
const INHERITED_EVENTS : InheritedEntry [ ] = [
2026-07-16 18:12:36 +08:00
{ name : 'internal/plugin' , summary : 'A plugin fiber was created.' , source : 'vendor/cordis/src/events.ts:328' } ,
{ name : 'internal/status' , summary : 'A fiber changed lifecycle state.' , source : 'vendor/cordis/src/events.ts:330' } ,
{ name : 'internal/service' , summary : 'Interception hook for a service binding (no core producer).' , source : 'vendor/cordis/src/events.ts:332' } ,
{ name : 'internal/update' , summary : 'Waterfall: a fiber config update is being applied.' , source : 'vendor/cordis/src/events.ts:334' } ,
{ name : 'internal/get' , summary : 'Waterfall: a service is being read from the store.' , source : 'vendor/cordis/src/events.ts:336' } ,
{ name : 'internal/set' , summary : 'Waterfall: a service is being written to the store.' , source : 'vendor/cordis/src/events.ts:338' } ,
{ name : 'internal/listener' , summary : 'A listener was registered.' , source : 'vendor/cordis/src/events.ts:340' } ,
{ name : 'internal/dispatch' , summary : 'An event is being dispatched to listeners.' , source : 'vendor/cordis/src/events.ts:342' } ,
2026-06-20 19:47:09 +08:00
{ name : 'hmr/change' , summary : 'A watched source file changed on disk.' , source : 'vendor/hmr/src/index.ts:20' } ,
{ name : 'hmr/reload' , summary : 'Plugins are being reloaded after a change.' , source : 'vendor/hmr/src/index.ts:21' } ,
{ name : 'exit' , summary : 'The process is exiting on a signal.' , source : 'vendor/loader/src/index.ts:23' } ,
{ name : 'loader/config-update' , summary : 'The loader config tree changed.' , source : 'vendor/loader/src/index.ts:24' } ,
{ name : 'loader/entry-init' , summary : 'A config entry is being initialized.' , source : 'vendor/loader/src/index.ts:25' } ,
{ name : 'loader/partial-dispose' , summary : 'An entry is being partially disposed on reload.' , source : 'vendor/loader/src/index.ts:26' } ,
{ name : 'loader/patch-context' , summary : 'A context is being patched during a reload.' , source : 'vendor/loader/src/index.ts:27' } ,
]
2026-07-08 11:47:51 +08:00
export const INHERITED_SERVICES : InheritedEntry [ ] = [
2026-07-16 18:12:36 +08:00
{ name : 'ctx.on / ctx.once' , summary : 'Register an event listener (disposable).' , source : 'vendor/cordis/src/events.ts:34' } ,
{ name : 'ctx.emit / ctx.parallel / ctx.serial / ctx.bail / ctx.waterfall' , summary : 'Dispatch an event (sync / awaited / first-bail / veto-chain).' , source : 'vendor/cordis/src/events.ts:34' } ,
{ name : 'ctx.plugin / ctx.inject' , summary : 'Load a plugin / declare required services.' , source : 'vendor/cordis/src/registry.ts:164' } ,
2026-06-20 19:47:09 +08:00
{ name : 'ctx.effect' , summary : 'Register a disposable side effect tied to the fiber.' , source : 'vendor/cordis/src/fiber.ts:9' } ,
{ name : 'ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin' , summary : 'Low-level service-store access and binding.' , source : 'vendor/cordis/src/reflect.ts:7' } ,
2026-07-16 18:12:36 +08:00
{ name : 'ctx.extend / ctx.isolate / ctx.intercept' , summary : 'Derive a child context (scoped services / isolation / interception).' , source : 'vendor/cordis/src/context.ts:42' } ,
2026-06-20 19:47:09 +08:00
{ name : 'ctx.root / ctx.scope / ctx.fiber / ctx.registry / ctx.reflect / ctx.events / ctx.logger' , summary : 'Ambient handles onto the running context graph.' , source : 'vendor/cordis/src/context.ts:16' } ,
{ name : 'ctx.timer (+ interval / timeout / throttle / debounce / setTimeout / setInterval)' , summary : 'Disposable timer helpers. The `timer` key is provided at runtime; the six helpers are mixed onto ctx directly (declared via Pick).' , source : 'vendor/timer/src/index.ts:4' } ,
{ name : 'ctx.loader' , summary : 'The config Loader that booted the app (present under the loader).' , source : 'vendor/loader/src/index.ts:30' } ,
{ name : 'ctx.hmr' , summary : 'The hot-module-reload watcher (present under the hmr plugin).' , source : 'vendor/hmr/src/index.ts:15' } ,
]
/** Render the cross-link "Types:" line for a signature, or '' if none apply. */
function typeLinks ( signature : string ) : string {
const seen = new Set < string > ( )
for ( const name of Object . keys ( LINK_MAP ) ) {
if ( new RegExp ( ` \\ b ${ name } \\ b ` ) . test ( signature ) ) seen . add ( name )
}
if ( seen . size === 0 ) return ''
const links = [ . . . seen ] . sort ( ) . map ( n = > ` [ ${ n } ](../core-data-structures/ ${ LINK_MAP [ n ] } ) ` )
return ` Types: ${ links . join ( ' · ' ) } `
}
/** Render one harness event entry. */
function renderEvent ( e : EventEntry ) : string [ ] {
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
const out = [ ` ### \` ${ e . name } \` — ${ e . mode } ` , '' ]
2026-06-20 19:47:09 +08:00
if ( e . doc ) out . push ( e . doc , '' )
2026-07-19 12:52:34 +08:00
out . push ( '```' + FENCE , e . jsDoc , e . signature , '```' , '' )
2026-06-20 19:47:09 +08:00
const links = typeLinks ( e . signature )
if ( links ) out . push ( links , '' )
out . push ( ` Source: [ \` ${ e . source } \` ](../../ ${ e . source . split ( ':' ) [ 0 ] } ) ` , '' )
return out
}
/** Render one harness service entry. */
function renderService ( s : ServiceEntry ) : string [ ] {
const kind = s . abstract ? ' (abstract seam)' : ''
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
const out = [ ` ## \` ctx. ${ s . key } \` — \` ${ s . type } \` ${ kind } ` , '' ]
2026-06-20 19:47:09 +08:00
if ( s . doc ) out . push ( s . doc , '' )
if ( s . methods . length ) {
2026-07-19 12:52:34 +08:00
const declarations = s . methods . flatMap ( ( method , index ) = > [
. . . ( index > 0 ? [ '' ] : [ ] ) ,
method . jsDoc ,
method . signature ,
] )
out . push ( '```' + FENCE , . . . declarations , '```' , '' )
const links = typeLinks ( s . methods . map ( method = > method . signature ) . join ( '\n' ) )
2026-06-20 19:47:09 +08:00
if ( links ) out . push ( links , '' )
}
out . push ( ` Source: [ \` ${ s . source } \` ](../../ ${ s . source . split ( ':' ) [ 0 ] } ) ` , '' )
return out
}
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
/** The shared generated-file banner comment. */
const BANNER = [
'<!-- Generated by scripts/gen-cordis-catalog.ts — do not edit by hand.' ,
' Run `pnpm run gen-cordis-catalog` to regenerate. -->' ,
'' ,
]
/** The shared GENERATED + freshness-gate + fence notice paragraph. */
2026-07-19 12:52:34 +08:00
const GATE_NOTICE = 'This file is GENERATED from source (`scripts/gen-cordis-catalog.ts`) and verified fresh by `pnpm run verify-cordis-catalog` (part of `doc-sync`) — do not edit it by hand. Signature blocks use a `ts cordis-catalog` fence and include the original source JSDoc immediately before each event or service method. doc-typecheck skips these bare declaration fragments; type names in a signature link to the page that documents them.'
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
/** Render the events catalog (pure, deterministic given sorted inputs). */
2026-07-19 12:52:34 +08:00
export function renderEvents ( events : EventEntry [ ] ) : string {
2026-06-20 19:47:09 +08:00
const lines : string [ ] = [
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
. . . BANNER ,
'# Cordis Events Catalog' ,
2026-06-20 19:47:09 +08:00
'' ,
2026-07-19 12:52:34 +08:00
'Every cordis event a plugin can listen to: exact signature, dispatch mode, and original declaration JSDoc. This is one axis of the **wiring** reference a plugin author works against — the callable `ctx.<key>` surface is the sibling [services catalog](services.md), and [core-data-structures/](../core-data-structures/core.md) catalogs the *data structures* these signatures move around.' ,
2026-06-20 19:47:09 +08:00
'' ,
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
GATE_NOTICE ,
2026-06-20 19:47:09 +08:00
'' ,
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
'The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns, grouped by scope. The **inherited tier** at the end is the cordis-core + loader/hmr/timer event surface a plugin also sees — pinned vendor source, summarized tersely.' ,
2026-06-20 19:47:09 +08:00
'' ,
2026-07-05 19:07:34 +08:00
'Dispatch modes: **emit** (fire-and-forget), **waterfall** (each listener gets `next()` and may transform or veto — see [waterfall semantics](../cordis-primer.md#cordis-waterfall-semantics)), **parallel** (awaited fan-out; all listeners run), **serial** (awaited in registration order until one returns a bail value — anything other than `null`, `false`, or `undefined`).' ,
2026-06-20 19:47:09 +08:00
'' ,
]
const scopes = [ . . . new Set ( events . map ( e = > e . scope ) ) ] . sort ( )
for ( const scope of scopes ) {
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
lines . push ( ` ## \` ${ scope } /* \` ` , '' )
2026-06-20 19:47:09 +08:00
for ( const e of events . filter ( x = > x . scope === scope ) . sort ( ( a , b ) = > a . name . localeCompare ( b . name ) ) ) {
lines . push ( . . . renderEvent ( e ) )
}
}
lines . push (
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
'## Inherited events (cordis core + loader/hmr/timer)' ,
2026-06-20 19:47:09 +08:00
'' ,
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
'The framework events every plugin also sees, beyond the harness vocabulary above. This is pinned vendor source ([vendoring policy](../../vendor/README.md)); it is summarized here so the page is a complete picture of the event bus, without elevating framework internals to the harness tier\'s prominence.' ,
2026-06-20 19:47:09 +08:00
'' ,
)
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
for ( const e of INHERITED_EVENTS ) {
lines . push ( ` - \` ${ e . name } \` — ${ e . summary } ([ \` ${ e . source } \` ](../../ ${ e . source . split ( ':' ) [ 0 ] } )) ` )
}
lines . push ( '' )
return lines . join ( '\n' )
}
/** Render the services catalog (pure, deterministic given sorted inputs). */
2026-07-19 12:52:34 +08:00
export function renderServices ( services : ServiceEntry [ ] ) : string {
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
const lines : string [ ] = [
. . . BANNER ,
'# Cordis Services Catalog' ,
'' ,
2026-07-19 12:52:34 +08:00
'Every `ctx.<key>` service a plugin can call: the exact public interface with original method JSDoc, plus the class JSDoc. This is one axis of the **wiring** reference a plugin author works against — the events a plugin listens to are the sibling [events catalog](events.md), and [core-data-structures/](../core-data-structures/core.md) catalogs the *data structures* these signatures move around. An abstract seam (e.g. `ctx.bash`) is implemented by a separate package; the interface is what consumers code against.' ,
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
'' ,
GATE_NOTICE ,
'' ,
'The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns. The **inherited tier** at the end is the cordis-core + loader/hmr/timer `ctx` surface a plugin also sees — pinned vendor source, summarized tersely.' ,
'' ,
]
2026-06-20 19:47:09 +08:00
for ( const s of services ) lines . push ( . . . renderService ( s ) )
lines . push (
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
'## Inherited `ctx` members (cordis core + loader/hmr/timer)' ,
2026-06-20 19:47:09 +08:00
'' ,
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
'The framework `ctx` surface every plugin also sees, beyond the harness services above. This is pinned vendor source ([vendoring policy](../../vendor/README.md)); it is summarized here so the page is a complete picture of what `ctx` offers, without elevating framework internals to the harness tier\'s prominence.' ,
2026-06-20 19:47:09 +08:00
'' ,
)
for ( const s of INHERITED_SERVICES ) {
lines . push ( ` - \` ${ s . name } \` — ${ s . summary } ([ \` ${ s . source } \` ](../../ ${ s . source . split ( ':' ) [ 0 ] } )) ` )
}
lines . push ( '' )
return lines . join ( '\n' )
}
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
/ * * C L I e n t r y : ` - - w r i t e ` ( d e f a u l t ) w r i t e s b o t h c a t a l o g s , ` - - c h e c k ` f a i l s i f
* either is stale . Guarded behind an entry - point check so importing this module
* for tests neither regenerates the committed files nor calls process . exit . * /
2026-06-20 19:47:09 +08:00
function main ( ) : void {
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
const outputs : [ string , string ] [ ] = [
[ OUT_EVENTS , renderEvents ( collectEvents ( ) ) ] ,
[ OUT_SERVICES , renderServices ( collectServices ( ) ) ] ,
]
2026-06-20 19:47:09 +08:00
if ( process . argv . includes ( '--check' ) ) {
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
const stale : string [ ] = [ ]
for ( const [ out , content ] of outputs ) {
let committed : string | null = null
try {
committed = readFileSync ( resolve ( root , out ) , 'utf8' )
} catch {
// Only ENOENT (not yet generated) is expected; a present-but-unreadable
// file is not a state this repo produces. Either way the remedy is the
// same — regenerate — so treat a read failure as "stale".
committed = null
}
if ( committed !== content ) stale . push ( out )
2026-06-20 19:47:09 +08:00
}
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
if ( stale . length === 0 ) {
console . log ( ` gen-cordis-catalog: ${ OUT_EVENTS } and ${ OUT_SERVICES } are up to date. ` )
2026-06-20 19:47:09 +08:00
process . exit ( 0 )
}
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
console . error ( ` gen-cordis-catalog: ${ stale . join ( ' and ' ) } ${ stale . length === 1 ? 'is' : 'are' } stale. Run \` pnpm run gen-cordis-catalog \` and commit the result. ` )
2026-06-20 19:47:09 +08:00
process . exit ( 1 )
}
Split the cordis catalog into separate events and services documents
gen-cordis-catalog.ts now emits docs/cordis-catalog/events.md and
docs/cordis-catalog/services.md instead of the combined
events-and-services.md: a reader is either finding what to listen to or
what to call, and each axis now scans and deep-links as its own page.
Headings promote one level (scopes and ctx.<key> entries become H2), the
dispatch-mode legend lives on the events page, and the inherited tier
splits accordingly. --check verifies both files and names whichever is
stale.
Every reference updated in the same change (no compat redirects,
pre-release stance): architecture.md, AGENTS.md, docs/AGENTS.md tier row,
filesystem/subagent core-data-structures pages (the ctx.fs anchor
survives — slugs are heading-level-independent), fs README, four RFCs,
the tool-catalog and persistence-catalog generator intros (both
regenerated), and the bilingual development.md pair (re-recorded).
2026-07-05 00:45:06 +08:00
for ( const [ out , content ] of outputs ) writeFileSync ( resolve ( root , out ) , content )
console . log ( ` gen-cordis-catalog: wrote ${ OUT_EVENTS } and ${ OUT_SERVICES } . ` )
2026-06-20 19:47:09 +08:00
}
// Run only when invoked as a script, not when imported by a test.
if ( process . argv [ 1 ] && import . meta . filename === resolve ( process . argv [ 1 ] ) ) {
main ( )
}