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
* /
2026-07-20 16:32:08 +08:00
import { globSync , mkdirSync , readFileSync , writeFileSync } from 'node:fs'
import { dirname , resolve , sep } from 'node:path'
2026-06-20 19:47:09 +08:00
import ts from 'typescript'
2026-07-20 16:32:08 +08:00
import { renderCordisCoreApiPages } from './cordis-core-api.ts'
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-21 12:48:46 +08:00
AgentCancelCause : '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' ,
2026-07-21 16:46:48 +08:00
HookContext : '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-20 15:34:00 +08:00
LlmModelContext : 'core.md' ,
2026-07-20 03:34:19 +08:00
LlmFailure : 'llm-streaming.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-07-21 16:46:48 +08:00
PreparedReferencedMessage : 'session-reference.md' ,
SessionReferenceCandidate : 'session-reference.md' ,
SessionReferenceInput : 'session-reference.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-24 12:31:26 +08:00
SessionLogSnapshot : 'session-query.md' ,
2026-07-21 16:46:48 +08:00
SessionSurfaceSnapshot : 'session-query.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 17:54:55 +08:00
PruneResult : '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' ,
EditGoalRequest : 'goal.md' ,
2026-07-20 16:39:40 +08:00
GoalBlockReason : 'goal.md' ,
2026-07-19 18:47:34 +08:00
GoalChanged : 'goal.md' ,
GoalRef : 'goal.md' ,
GoalView : 'goal.md' ,
2026-07-19 22:11:59 +08:00
CommandDefinition : 'commands.md' ,
CommandDescriptor : 'commands.md' ,
CommandResult : 'commands.md' ,
CommandSurface : 'commands.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' ,
2026-07-23 13:56:56 +08:00
SessionPersistenceSnapshot : 'persistence.md' ,
2026-07-19 14:57:52 +08:00
ConfinedArgv : 'sandbox.md' ,
2026-07-21 00:44:28 +08:00
SandboxExecutionPolicy : 'sandbox.md' ,
2026-07-19 14:57:52 +08:00
SandboxMode : 'sandbox.md' ,
SandboxPolicy : 'sandbox.md' ,
2026-07-21 16:01:00 +08:00
PtyBackend : 'pty.md' ,
PtyReadRequest : 'pty.md' ,
PtyReadResult : 'pty.md' ,
PtySendOperation : 'pty.md' ,
PtySendRequest : 'pty.md' ,
PtySessionId : 'pty.md' ,
PtySessionSnapshot : 'pty.md' ,
PtySignal : 'pty.md' ,
PtySignalResult : 'pty.md' ,
PtySpawnRequest : 'pty.md' ,
PtySpawnResult : 'pty.md' ,
2026-07-21 00:44:28 +08:00
SandboxPolicyRequest : 'sandbox.md' ,
2026-07-19 14:57:52 +08:00
ScopeKey : 'scope.md' ,
Scoped : 'scope.md' ,
EpochHeader : 'session.md' ,
2026-07-21 01:54:00 +08:00
OutOfBandSessionEventType : 'session.md' ,
2026-07-19 14:57:52 +08:00
Session : 'session.md' ,
2026-07-21 01:54:00 +08:00
SessionEventMap : 'session.md' ,
2026-07-19 14:57:52 +08:00
TurnEndReason : 'session.md' ,
2026-07-21 01:54:00 +08:00
TurnTrigger : 'session.md' ,
2026-07-19 14:57:52 +08:00
SessionEventReadRequest : 'session-query.md' ,
SessionEventRecord : 'session-query.md' ,
2026-07-23 13:56:56 +08:00
SessionEventResultFilter : 'session-query.md' ,
SessionEventSearchDocument : 'session-query.md' ,
SessionEventSearchHit : 'session-query.md' ,
2026-07-24 16:40:08 +08:00
SessionEventSearchPage : 'session-query.md' ,
2026-07-23 13:56:56 +08:00
SessionEventSearchRequest : 'session-query.md' ,
2026-07-19 14:57:52 +08:00
SessionEventTrace : 'session-query.md' ,
2026-07-24 16:40:08 +08:00
SessionEventTraceObservation : 'session-query.md' ,
2026-07-19 14:57:52 +08:00
SessionEventTraceRequest : 'session-query.md' ,
SessionEventWindow : 'session-query.md' ,
SessionLineageTrace : 'session-query.md' ,
SessionRecord : 'session-query.md' ,
2026-07-23 13:56:56 +08:00
SessionResultFilter : 'session-query.md' ,
SessionSearchExecContext : 'session-query.md' ,
SessionSearchHit : 'session-query.md' ,
SessionSearchPage : 'session-query.md' ,
SessionSearchRequest : 'session-query.md' ,
2026-07-24 16:40:08 +08:00
SessionTitleObservation : 'session-query.md' ,
2026-07-21 01:54:00 +08:00
SessionTitleProvider : 'session-title.md' ,
SessionTitleSnapshot : 'session-title.md' ,
2026-07-19 14:57:52 +08:00
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' ,
2026-07-19 23:38:54 +08:00
ToolDispatchExecution : 'tools.md' ,
2026-07-19 14:57:52 +08:00
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' ,
2026-07-19 19:19:57 +08:00
InvariantInstaller : 'service-local contribution contract is owned by packages/support/invariants/README.md' ,
feat(gui): step1 skeleton — dsc web serves built web UI over booted harness host
Five new modules: apps/dsc (bin: parseArgs + node:http static server +
signal shutdown), packages/host/apiproxy (programmatic harness core
composition, agents:[]), packages/client/web-runtime (React-free browser
runtime), packages/client/web-ui (React mount), apps/web (vite build
entry producing dist consumed by apps/dsc via package exports).
Root wiring: apps/* workspace glob, dsh-* paths for host/client groups,
demo:web script, apps/web/dist gitignore. No protocol/API routes yet —
contract lands in step2 (see missions/tasks/20260719-1902-apiproxy-api-design).
Includes the design + implementation archives (spec v2.1, deepseekchat
baseline and harness boot research, implementation run log).
Acceptance: 12/12 passed incl. real-key llm.stream smoke (51 chunks).
feat(gui): apiproxy — four-quadrant RPC contract + fetch carriers, live end to end
Contract layer (src/api/, 14 files): four named wire message types
(ClientRequest / ServerResponse / ServerRequest / ClientResponse) as a
discriminated union over strict bidirectional rpcId (initiator mints,
responder echoes; channel and message fully decoupled — HTTP is the
client->server pipe, SSE the reverse); narrow RpcRequest<P>/
RpcResponse<T> signature forms; RpcMethodMap with RequestPayload<K>/
ResponseValue<K> derivation; typed RpcError details map; approval/
question responses modeled as ClientResponse via a single /api/respond
endpoint (RpcReceipt carrier ack); zod schemas anchored per Wire<T>
against exactOptionalPropertyTypes.
impl/api-proxy.ts: describe/list/create, both SSE streams (frame queue
pump, subscribed baseline, lifecycle frames, signal cleanup); history
pages on message boundaries (tail-back scan, partial included in the
tail page); prompt dispatches queue->agent.send / steer->agent.steer
with rpcId carried through MessageSource; cancel for attached sessions;
cold-session resume deduped via a per-id promise map; host-level
provider/model defaults injected at create/resume.
fetch/: mechanical UNARY_ROUTES table, two-level parse with
path==method check, SSE frames completed to ServerRequest full form;
client mints -> narrows -> envelopes outbound, verifies rpcId echo
inbound, streams SSE frames, four-quadrant onEnvelope tap (debug panel
choke point). Real-browser fixes: URL base resolves to location.origin
(hardcoded internal base broke real pages), browser-safe export paths.
Design archives: contract design.md v2.0 with decision log,
core-coverage audit, comparative studies, step2 impl run log. Probed
end to end over real HTTP: prompt -> live model stream -> history
returns the finished reply.
feat(gui): RpcLog debug panel — fixture-driven milestone, playwright-verified 10/10
web-runtime: rpcLog + ui slices (zustand), four-quadrant RpcLogEntry
(client-request / server-response / server-request / client-response),
onEnvelope tap -> microtask-batched pump with 500-entry ring buffer,
ConnectionController (private state, backoff reconnect), fixture API
with fake envelopes (?fixture switch), bootWebRuntime; contract types
via temporary local copies (api-types.ts, swapped for real imports when
W3 client lands).
web-ui: components/panels/RpcLog five-piece set (badge with unread
count, floating panel, direction glyphs per quadrant, same-rpcId
pair highlighting in two families, JSON payload expand, follow/pause,
clear), App shell, utils/formatRelative, light-theme CSS variables with
dark placeholders.
dsc bin: mime lookup fixed to use the actually-served file (naked
'/?query' no longer falls through to octet-stream download); shutdown
closes SSE keep-alive connections so SIGTERM actually exits.
Acceptance: scripts/verify-rpclog-panel.mjs (chromium headless) ALL
PASS 10/10 over design.md §D 1-6.
pkg: add web scripts for building
feat(gui): session milestone — list + conversation over Session OOP, styled RpcLog v2.1
web-runtime: Session/SessionManager object layer (resident instances,
mux frame routing, lineage flattening), foldSurface adapter with padding
sentinels for paged windows, chunk accumulator for streaming partials,
batched change notification (useSyncExternalStore contract), connection
sinks + reconnect fix (the 300ms self-abort reconnect storm that made
the session list flap is gone), fixture rewritten as a scripted host
(60-turn history, typewriter replay, resident pending approval, child
session); temporary contract copies deleted in favor of real apiproxy
imports.
web-ui: sessions screen (list with lineage indent + selection as
container-local state), conversation view (turn grouping, reasoning
fold, tool cards, steering, pending interaction cards, upward paging
with scroll anchoring), input bar with queue/steer/stop; RpcLog panel
restyled per docs/web-styling.md (tokenized palette, quadrant badge
glyphs now vertical ↑↓⇟⇞, pair highlighting, floating shadow).
docs/web-styling.md: living style guide (tokens, visual baseline,
coding rules, evolution log).
Acceptance: verify-session.mjs 31/31, verify-session-real.mjs 5/5
(real model streaming), verify-rpclog-panel.mjs 10/10.
feat(gui): hostruntime split + repo-wide package prefix rename
Package split (design: 20260720-0101-hostruntime-split-design):
dsh-host-runtime carries bootHost + createApiProxy + startHost()
(RunningHost {api, handler, defaults, ctx, dispose} — the seam Electron
and any future shell reuses; ctx is the official front-door mount
point); dsh-host-webserver carries the node:http static+API bridge
(fixed: abort now keys on res 'close' + writableEnded — req 'close'
fires on body end since Node 16 and was killing every SSE stream
instantly, the reconnect-storm root cause); apps/dsc is now a thin
assembly with web/-p subcommands. dsc -p runs the full isomorphic
carrier chain in process (second real protocol consumer; probed
end-to-end against the live model).
Naming rule (user decree): packages under host/ and client/ carry the
directory prefix in their npm name — dsh-host-apiproxy,
dsh-client-web-runtime, dsh-client-web-ui renamed repo-wide in one
frozen batch; explicit tsconfig paths entries added where the wildcard
no longer matches.
Acceptance: verify-session 31/31, verify-rpclog-panel 10/10,
verify-session-real 7/7 (incl. new 12s connection-stability sentinels),
tsc green, dsc web + dsc -p smoke both pass.
refactor(gui): AbstractApiClient class hierarchy — OO client with inheritable seams
AbstractApiClient (apiproxy) carries every protocol invariant: rpcId
minting, four-quadrant envelope wrap/unwrap, zod parsing, SSE frame
parsing, the payload-direct IApiClient surface (callers no longer mint
rpcIds — the carrier does), and the instance-level envelope observation
pump (batched via microtask; moved off module-level globals in
rpc-log.ts, which is now a pure subscriber mapping envelopes into store
entries — the debug panel observes the connection, it is not part of
it).
Platform subclasses own two abstract seams (doFetch, onEnvelope) plus
three protocol-level virtuals for transportless overrides:
InProcessApiClient (apiproxy; dsc -p uses new InProcessApiClient(
host.handler)), WebApiClient (web-runtime), FixtureApiClient (fixture
now subclasses instead of wrapping). Naming per decree: AbstractApiClient
/ IApiClient; ApiProxy stays the impl-side narrow-form contract.
headless.ts call sites drop rpcRequest wrappers (payload-direct);
split-design archive updated with the naming-rule ledger.
tsc green; verify-session 31/31, verify-rpclog-panel 10/10,
verify-session-real 7/7 (12s connection sentinel count=4); dsc -p smoke
CALLER-OK.
feat(gui): InputBar final form — bug batch, deepseekchat layout, single primary button, running locks input
Squashes the whole InputBar iteration batch: IME/caret/auto-grow/focus/dedup
bug fixes, layout aligned to the deepseekchat baseline, single primary button
with hover flyout, finalized button semantics with the Codex-style icon
circle, and running-state locking where stop is the only mid-turn action.
The same batch carried the Chinese-to-English code comment sweep
(density pruned), folded in here.
docs(gui): purge work-log references from code comments
76 design-doc references cleared across the GUI packages: section
pointers inlined as self-contained constraint statements, pure pointer
comments dropped, milestone codenames and ruling tags out, and the 14
contract file headers switched to the formal RFC (the only sanctioned
external reference). web-styling.md now cites the styling RFC instead
of the disposable research archive. grep for work-log reference
variants is clean across the GUI packages.
docs(gui): file-header comments self-contained — drop RFC filename references
RFC renames/reorgs must not require a source sweep (the 2026-07-20
two-way merge proved it). 11 headers lose only the '(RFC …)' tail and
stay self-contained; api-proxy.ts keeps its minimal-first note.
fix(gui): session streaming — freeze interrupted partials, sweep stale running calls, send force-scrolls
Aborted turns never emit the finalizing assistant/message, so the
accumulated partial and its running tool cards kept rendering below
later messages — the "new message lands above the stopped reply"
illusion. turn/end side effects now freeze content-bearing partials
into interrupted terminal nodes (fractional seq keeps flow order; the
live freeze and history replay converge through applyEventSideEffects,
so a refresh reconstructs identical frozen nodes) and turn running tool
cards into interrupted terminal cards; only content-free partials are
swept outright. ConversationView gains the send-force-scroll rule (own
words must be visible) alongside the pre-update atBottom follow flag.
Regressions pinned as E2-4a–c (real host) and §E1-11h (fixture).
feat(gui): webserver hardening verify script
feat(gui): dark-mode toggle pinned to the sidebar bottom
Interim home before the Settings page exists (the button re-homes with
zero logic change — mechanics live in utils/theme.ts): html[data-theme]
flip + dsc.theme localStorage, stored choice wins over the OS
prefers-color-scheme default, applied in mount() before first paint so
a dark reload never flashes light. Moon/sun inline SVG icon button at
the sidebar's pinned bottom row. Pure front-end local concern: no RPC,
no Session/store involvement. Dark sweep of list/conversation/input
card/RPC panel found no unreadable pairs — no token changes needed.
docs(gui): GUI RFCs and web styling handbook
Layering+RPC protocol and web client architecture RFCs (post-reorg,
developer-facing polish folded in) plus the styling engineering
handbook. Mission work logs live in the commit above; PRs can be cut
from this commit to include formal docs only.
fix(gui): client object-layer hardening — audit timing/reference/resilience batches (S3-S5,C1-C3,C5-C8)
fix(gui): carrier error channel + webserver backpressure (audit A1-A5,A7-A10,R2,R5)
feat(gui): session persistence surface — cold list, project cwd, legacy no-cwd retirement
refactor: rename dsc CLI to dsh — apps/cli, bin name, package scope
Includes the root tsconfig project-references fix for host/* and
client/web-runtime (originally a separate build fix commit).
test(gui): three-tier suite — protocol/object/browser lanes, tier-a fill to per-file 100%
test(gui): jsdom lane for web-ui + web-runtime coverage gate entry
docs(gui): GUI testing system RFC (zh)
feat(gui): tool-card views — contract slot, host-computed delivery, three-level card fallback
fix(gui): lint clean across GUI packages — wrap long doc comments, drop dead type args, sync-return methods without awaits
docs(gui): doc-sync mechanical fixes — JSDoc on apiproxy/host exports, RFC sketch fences ignore-check, md-wrap paragraphs, drop missions links, web-ui plain-ts entry
chore(gui): module-graph regen + knip clean — drop dead re-exports, internalize createFixtureApi, scan web-ui tsx and verify mjs scripts
build(gui): wire client/host packages into the lib build shape — tsc references + tsdown (web-ui css-external), lib manifests, cordis peer, apiproxy typed subpaths, vite src aliases
test(gui): host-side per-file 100% coverage — apiproxy schema/carrier suites, webserver http-bridge suite, host-runtime composition suite; client/* coverage excluded pending the browser-side testing work item
docs(gui): package READMEs for the five GUI packages — model-experience audit entries, limitations sections
docs(gui): bilingual RFC pairs + client JSDoc completion — translate the three GUI RFCs to English with i18n records and manifest ratchet, Consequences sections both sides, full client/* export JSDoc, regen doc graphs and RFC index
fix(scripts): doc-typecheck built-declarations mode maps /src/* subpath wildcards (apiproxy browser-safe channels)
docs(gui): apply dsh rename across pr-gates docs — READMEs, layering RFC en, web-ui entry comment, i18n re-record
fix(gui): post-rebase lint reconciliation — wrap main-tree long doc comments, read-through narrowing guards, abortError Error normalization, handleUnary generic justification
fix(gui): post-rebase doc/test reconciliation — align host specs with evolved carrier contracts (sentinel rpcId, stream/error surfacing, url-path transport messages, defaults.cwd), Agent Note titles and relocated links, KV Cache effect sections, JSDoc on evolved exports
fix(gui): second-rebase reconciliation to 509db0cb3 — restore api panel exports the baseline suites consume, knip workspace entries for jsdom lane and apps/web smokes, hoist result narrowing, align testing.md to the narrowed web-ui exclusion
fix(test): vitest-scoped tsconfig maps bare imports for tsx specs — with GUI manifests now pointing at lib, an unmapped importer loaded a second copy of the web-runtime singletons
fix(gui): typecheck + lint clean over the tool-card batch — brand callIds and object-form turn/end reason in the view spec, narrow fixture arg stringification, wrap long v8-ignore comments
docs(gui): export JSDoc for tool-card surfaces + testing-note pairing header
docs: rfc for web testing
feat: add tools to host-runtime
fix(gui): dispatch agent/error via agentEvents in host-runtime spec — mounted invariants plugin rejects raw ctx.emit without the scope carrier
fix(gui): restore GUI knip workspaces + scripts/mjs entries and regenerate lockfile after master rebase
fix(gui): post-rebase gate repairs — drop context-node envelope (master unwrapped injected content envelopes), regen event matrix, condense testing.md web-ui exclusion within budget
fix(session): browser-safe deep-equal in surface — node:util import broke the vite bundle
ci(gates): frontend vite build joins pre-push — node: imports in the client closure pass tsc but break the browser bundle
test(tui): drop the checkout-dependent process.cwd() harness default — a long worktree path pushes the footer token counters past the 88-column fake terminal
test(gui): jsdom behavior E2E — conversation main path over fixture runtime, reconnect banner lifecycle
test(gui): jsdom RPC panel behavior — ledger rows, expand, pairing, pause/clear, follow-pause, payload truncation
test(gui): jsdom tier-2 — InputBar guards, reasoning fold, JSON blocks, message variants, theme, create-then-select; act-harden banner case
test(gui): jsdom tier-3 — ConversationView states/paging/force-bottom, ToolCallCard arms, PendingCard, list rows
test(gui): jsdom tails — view-card variants, LogRow directions, registry hygiene, badge overflow, hook ops, mount glue
test(gui): jsdom tails round 2 — call-ref blocks, resume follow, view precedence, failed create, empty-diff arm
test(gui): jsdom final arms — anchor compensation, follow-off, interval ticks, view halves, node-over-running precedence
test(gui): web-ui joins the per-file 100% coverage gate
Annotation-only src changes plus the config swap. The web-ui exclusion is
replaced by a single index.tsx entry (stale byte-identical duplicate of
mount.tsx, nothing imports it; same entry-glue treatment as bin.ts) and the
coverage include gains .tsx.
v8-ignore sites (each with its reason inline):
- ConversationView 3x ref-null guards; InputBar disabled-click guard
- ToolCallCard both-null arms + windowless-custom argsRaw arm
- LogRow css-module key fallbacks (start/stop block); RpcLogBody 3x ref-null guards
- web-runtime drift from the tool-card batch: fixture presenter catch/str
typo-guards, dense-array guards (fold-adapter reset, session rebuild,
fixture backscan), live view-present arm (fixture replays are text-only;
view vocabulary is covered by the history samples)
test(gui): close the PR #443 host-side coverage gaps — apiproxy client abort arms, api-proxy cold/view paths, webserver drain
- apiproxy fetch/client.ts: 3 new cases (pre-aborted signal short-circuits
before transport + string reason mapping, non-Error/string reason falls to
the default AbortError message, signal-less doFetch passthrough)
- runtime/api-proxy.ts: one v8-ignore (summarizeCold cwd arm — list()
filters cwd-less legacy metas) + api-proxy-cold.spec.ts (cold list merge:
mtime source, locate-undefined and vanished-log fallbacks, lineage;
no-persistence/no-factory resume → internal) + 2 view cases (history views
with meta passthrough and orphan/bad-args/presenterless soft-falls,
session/disposed open-call cleanup on the mux stream)
- webserver/index.ts: /api/big fixture drives both drain-wait legs (full
8MiB readback after drain, mid-chunk disconnect wakes via 'close')
feat: app shell
fix: rebase conflicts
fix: coverage
fix(gui): lint clean after rebase — wrap long v8-ignore comments, unconditional v1 detail-block claim
chore(gui): remove browser/probe verify scripts from scripts/
The six GUI acceptance/probe scripts (carrier-errors, rpclog-panel,
session, session-real, webserver-backpressure, webserver-hardening)
leave the repo's scripts/ tree; the three code comments that pointed at
them now describe the coverage lane without naming a script path.
fix(webserver): guard the request callback — one malformed request must not kill the process
The async handle() had no top-level catch, so any throw inside it (a bad
%-escape reaching decodeURIComponent, a client dropping mid-body, a
response stream erroring) became an unhandled rejection and took the whole
process down (audit R1 must-fix). The guard answers 400 when headers are
not out yet, destroys the socket when they are, and reports the failure to
onError (the package never prints). Spec covers all three legs: %-escape
barrage → 400 + server stays alive, non-Error throw wrapped for onError,
mid-stream explosion → socket teardown.
feat: client AGENTS.md
fix: client/AGENTS.md
fix: rebase
feat(gui): T0 cut 1 — 12 client package skeletons with contract stubs, dshClient declarations, tsdown client preset, theme token sheets
feat(gui): T0 cut 2 — pure git mv migration per v3 §11 (connection six, runtime sessions/kernel, ui-conversation chat, ui-primitives markdown family, web shell + e2e)
feat(gui): T0 cuts 3+4 — import rewiring to new package names, .legacy demotion of owner-rewrite files, legacy web-runtime/web-ui/apps-web retired to attic
feat(gui): connection 对账刀——index.ts 精确导出清单替换 export *,intents.legacy 溶解删除
feat(client/ui-slots): SlotCore real implementation — kind semantics, sync version + microtask-batched notify, onMutate bridge
feat(gui): web shell vite alias — retarget to new client packages, shell static surface only
feat(gui): host 侧刀属地半——HostWebPluginRegistry(entries 扫描+internal/plugin 去抖重扫+dshClient 校验+exports./client 解析)、GET /plugins/<id>/client.js 分发端点、GET / 与 SPA fallback 注入 __DSH_BOOT__(webPlugins 可选注入,不传行为不变)
feat(web-react): add use-sync-external-store dep + local shim typings
feat(web-react): bindSnapshotSelector via uSES with-selector shim
feat(gui): ui-layout concession-chain solver — pure computeColumns with contract geometry
feat(gui): ui-layout LayoutService — four persisted stores, clamped actions, list-driven prune
feat(gui): ui-layout AppFrame styles — grid columns, collapse-safe borders, edge drag handles
test(gui): 存量 spec 平移——connection 三件+runtime 六件自 attic 捞回改包名路径全绿;api-helpers 按归属拆分(wire 半留 connection、classifier 半随 conversation.ts 入 runtime);boot-intents/preinit/rpc-log 随 intents/rpc-log 退役不迁(记 v3 §3.2 溶解项)
feat(client/ui-primitives): StateDot/Button/Pill/Input/Menu atoms, ConnectionBanner de-legacied to pure props, JsonBlock CSS on --dsw tokens
feat(web-react): createSnapshotStore engine (rafFlush batch, persist opt-in, dev freeze) + spec
feat(gui): ui-layout AppFrame — grid tracks, pointer-capture drag handles with rAF throttle, frame ResizeObserver
feat(web-react): useInvoke (external pending store, stable invoke, concurrency count) + spec
test(web-react): bind spec — equality bail, custom eq, zero resubscribe, StrictMode, method sources
feat(gui): ui-layout index rewiring — real exports, client apply provides ctx.layout and defines three slots
feat(web-react): SessionProvider (renderBody deps) + RootBindingProvider + binding contexts + spec
feat(gui): web shell AppRoot boot-page styles — self-contained with neutral token fallbacks
feat(gui): web shell AppRoot — boot gate over loader status, fail-loud plugin failure list
fix(gui): AppRoot gates on explicit settled signal — status-derived readiness races the incrementally filled table
feat(client/ui-theme): ThemeService real implementation — registry with built-in light/dark, apply toggles body[data-ds-dark-theme], third-party token overrides as body inline vars
feat(web-react): scopedSlots outlet (kind matrix, inject WeakMap caches, per-entry error boundary) + spec
feat(gui): web shell module-table seed — pure-library entities for the loader require surface
feat(client/i18n): I18nService real implementation — ns×locale registry, stable bind(ns) reference, zh fallback chain, zh/en skeleton dictionaries
feat(gui): web shell assembly closure — layout exports via module table, SessionProvider + scopedSlots + RootBindingProvider
feat: client/ui-conversation
feat: code
codedoc
build(gui): root bundle green — web shell excluded from the lib workspace (vite app), ui-primitives lib externalizes css side-effect imports (web-ui precedent)
gates(gui): verify-cordis-config follows aggregate tsconfig references (root is a shell over host/client programs); module graph regenerated for the twelve client packages
chore(gui): retire legacy migration sources — every owner rewrite landed (t0-checklist §7 ledger honored); orphan css of retired components removed
gates(gui): knip green groundwork — e2e/tsx entries for the new packages, loader-runtime deps ignored where loading is by specifier string, fake plugin ids un-bare-named, dead test export dropped
chore(client): manifest shape batch A — ui-slots/web-react/ui-primitives invariant companions, files whitelist, cordis+invariants peer/dev, tsconfig refs
chore(client): manifest shape batch B — connection/runtime/ui-conversation/ui-trajectory files whitelist, cordis peer+dev, explicit invariant lib entries (clientBundle signature)
chore(client): manifest shape batch C — i18n/ui-layout/ui-sidebar/ui-theme invariant companions, files whitelist, invariants peer/dev, tsconfig refs
chore(client): manifest shape batch D — web shell gains node-half lib entry + invariant companion + uniform files whitelist
chore(client): drop verified-unused deps — dsh-tools from runtime/ui-conversation (types ride /presentation), ui-primitives+clsx from ui-layout
gates(gui): doc-gate fixes — theme JSDoc prose, three client type-link exemptions, agent-note paths follow the migration, config catalog regenerated
gates(gui): type-equiv manifest follows the types.ts extraction, approval JSDoc keeps its link form, persistence catalog regenerated
docs(gui): per-constant JSDoc on the contract geometry exports (export-jsdoc gate)
test(gates): loader-composition budget covers cold tsx resolution after the program split (was flaking at the default 5s)
docs(gui): README substantiation batch 1 — ui-slots/ui-primitives/web-react/connection: Model Experience short form, real deferred-work ledgers, description accuracy pass
fix(client): theme/i18n dual-entry split — service classes + cordis merges move to src/client (host catalog scanner no longer misclassifies client services), node halves keep types + empty apply; catalogs regenerated
docs(gui): README substantiation batch 2 — runtime/ui-layout/ui-sidebar/ui-conversation: Model Experience short form, package-owned deferred-work ledgers (unload stub, watch approximation, /client value-import rule, global details state, two-state dots, stats duration gap, single-bundle caches)
docs(gui): README substantiation batch 3 — ui-trajectory/ui-theme/i18n/web: Model Experience short form, deferred-work ledgers (placeholder charter, no theme toggle owner, empty locale dictionaries, one-shot rendering); both README gates green
test(scripts): purity spec adopts clientBundle two-arg signature (explicit libEntry, no default)
gates(gui): knip green — declaration-merge dep ignored, fake plugin id assembled at runtime, invariants dep de-duplicated to peer+dev, stale apps/web section dropped
feat(gui): 门禁波次 host 三包 invariant 形状——apiproxy explained-empty 伴生(wire 契约层零事件面)、webserver 真关系伴生(manifest 行必解析出 clientPath,防 __DSH_BOOT__ 广告 404 bundle;apps/cli 发布 webPlugins 键供审计)、runtime 补 files 白名单;三包 exports/files/peer+dev/tsconfig refs 齐 fw-react 形状;constraints+invariants 双 gate 零违规
build(client): ui-layout/ui-sidebar tsdown configs adopt the explicit two-arg clientBundle signature (orphaned follow-up of the manifest shape batch)
refactor(gui): shell boot becomes a library face — bootWebShell(el) exported for the apps/web entry; main.ts retired
refactor(gui): exports 纪律刀1——ui-theme/i18n node index 收敛为只空 apply(Translate/LocaleDict/ThemeTokens 类型下沉 src/client/),ui-conversation 的 I18nService import 改 /client 子路径
build(typecheck): converge to root host aggregate + tsconfig.client.json — delete tsconfig.host.json, verify-cordis-config seeds both aggregates
feat(gui): apps/web restored as the vite application — thin main over bootWebShell; dsh-client-web becomes a plain lib (index exports shell surface, vite files and e2e moved out)
chore(gates): knip.json rewritten on the master base — same semantics, minimal diff (formatting churn dropped)
docs(gui): 时效清扫②——testing.md 删 web-ui 覆盖豁免残句;web-styling.md 加 token 换代头注(--dsw-* 现行、工程约束条款仍有效并注明收编处)
docs(gui): 时效清扫③——四对 GUI Agent Note 加路径更新头注(web-runtime/web-ui/dsh-frontend→现行 12 包结构;设计结论存续声明;双语对同步)
docs(gui): 时效清扫③b——四对 note 头注的 i18n 配对哈希重录
build(typecheck): minimal-diff tsconfig shape — drop root files entry (purity spec + preset move to client program), compress comments, drop redundant util/home root ref
feat(gui): apps/web restoration follow-through — dsh-frontend package name, cli dist resolve, root build:web filter, tsdown exemption dropped, vitest web lane + knip + client aggregate retargeted, e2e paths rebased
refactor(gui): exports 纪律刀2——connection wire 六件 git mv 进 src/client/(wire 即该 dshClient 插件的 client 半),node index=只空 apply,/client 半边整面导出(v3 §3.2 清单原样),包内 tests 改 src/client 直取
refactor(gui): exports 纪律刀3——runtime 实现整体下沉 src/client/(sessions/slots/loader;契约类型与 cordis merge 随迁 client/index),node index=只空 apply;./loader exports 指 client/loader;全消费面(web 壳/ui-sidebar/ui-trajectory/tests)bare→/client 机械跟改;vitest.e2e 换 tsconfig.vitest paths(root tsconfig 排除 client 会把 /client import 掉到 exports 的浏览器 dist bundle)
refactor(gui): exports 纪律刀3 补遗——ui-layout 三处 bare runtime import 改 /client(刀3 消费面机械跟改漏提交件;跨属地机械一行×3 报备 ui-shell)
test(gui): drop the getSessionManager singleton case — the init/get pair is a dead legacy-boot surface with zero live consumers (SessionsService constructs and holds the manager under the plugin architecture); source removal tracked with rt-core
refactor(gui): 删 manager.ts 尾部 initSessionManager/getSessionManager 单例对——旧 boot 直连遗物,插件化下 SessionsService 构造持有 manager,全仓零活消费者(convo-b 测试清扫对表,其测试用例已先行退役 7e2c51898);头注释同步去单例措辞
code
refactor
2026-07-19 21:17:57 +08:00
LocaleDict : 'service-local dictionary shape is owned by packages/client/i18n/src/index.ts' ,
ThemeTokens : 'service-local token dictionary is owned by packages/client/ui-theme/src/index.ts' ,
Translate : 'service-local bound translator is owned by packages/client/i18n/src/index.ts' ,
2026-07-22 21:30:08 -07:00
TuiOverlayRequest : 'service-local extension contract is owned by packages/ui/tui/README.md' ,
TuiOverlaySession : 'service-local extension contract is owned by packages/ui/tui/README.md' ,
2026-07-19 19:19:57 +08:00
InvariantRegistration : 'service-local lifecycle handle is owned by packages/support/invariants/README.md' ,
2026-07-19 14:57:52 +08:00
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-18 21:35:06 +08:00
// cordisModuleBody / eventMembers / serviceClasses live in cordis-walk.ts.
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(). ` )
2026-06-20 19:47:09 +08:00
}
2026-07-16 22:07:06 +08:00
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
'' ,
2026-07-20 16:32:08 +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. The event-dispatch methods themselves are generated in the [Cordis core Events API](core/events.md).' ,
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 ,
'' ,
2026-07-20 16:32:08 +08:00
'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. Detailed Context, Fiber, Registry, and Service APIs are generated in the [Cordis core API](core/context.md).' ,
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
'' ,
]
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-07-20 16:32:08 +08:00
. . . renderCordisCoreApiPages ( ) ,
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
]
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 ) {
2026-07-20 16:32:08 +08:00
console . log ( ` gen-cordis-catalog: ${ outputs . length } generated file(s) 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 )
}
2026-07-20 16:32:08 +08:00
for ( const [ out , content ] of outputs ) {
const destination = resolve ( root , out )
mkdirSync ( dirname ( destination ) , { recursive : true } )
writeFileSync ( destination , content )
}
console . log ( ` gen-cordis-catalog: wrote ${ outputs . length } generated file(s). ` )
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 ( )
}