refactor: replace overloaded surface terminology

This commit is contained in:
Turtle
2026-08-11 15:23:05 +08:00
parent c172faed37
commit 0c708cb10d
626 changed files with 1396 additions and 1397 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ export interface CordisCoreApiPage {
sections: CordisCoreApiSection[]
}
/** Explicit editorial grouping for the pinned Cordis core surface. */
/** Explicit editorial grouping for the pinned Cordis core API. */
export const CORDIS_CORE_API_PAGES: CordisCoreApiPage[] = [
{
out: 'docs/cordis-api/context.md',
+2 -2
View File
@@ -819,7 +819,7 @@ export function render(entries: CatalogEntry[]): string {
'',
'# Plugin Config Catalog',
'',
'Every `config:` block a `cordis.yml` entry can set: for each loadable harness package, the verbatim config declaration (JSDoc included) its `apply` function or service constructor receives, with every referenced type pasted alongside (package-local types) or linked (everything else). The paste is the plugin\'s full declared config type — a field the runtime schema deliberately excludes is a runtime-only seam (its own JSDoc says so) and is not settable from `cordis.yml`. This is the **deployment**-axis reference — the wiring a plugin author works against is the generated `cordis-surface` region on each [subsystem page](subsystems/core.md), the model-facing tool schemas are the [tool catalog](tool-catalog.md), and [subsystems/](subsystems/core.md) documents the types these declarations reference.',
'Every `config:` block a `cordis.yml` entry can set: for each loadable harness package, the verbatim config declaration (JSDoc included) its `apply` function or service constructor receives, with every referenced type pasted alongside (package-local types) or linked (everything else). The paste is the plugin\'s full declared config type — a field the runtime schema deliberately excludes is a runtime-only seam (its own JSDoc says so) and is not settable from `cordis.yml`. This is the **deployment**-axis reference — the wiring a plugin author works against is the generated Cordis API region on each [subsystem page](subsystems/core.md), the model-facing tool schemas are the [tool catalog](tool-catalog.md), and [subsystems/](subsystems/core.md) documents the types these declarations reference.',
'',
'This file is GENERATED from source (`scripts/gen-config-catalog.ts`) and verified fresh by `pnpm run verify-config-catalog` (part of `doc-sync`) — do not edit it by hand. Declaration blocks use a `ts config-catalog` fence (skipped by doc-typecheck, since a lone declaration referencing imports is not standalone-compilable). The generator also cross-checks the runtime schemastery schema against the pasted declaration — every schema-validated key, nested keys included, must be locatable on the declared config type — so the paste cannot hide a loader-accepted field.',
'',
@@ -832,7 +832,7 @@ export function render(entries: CatalogEntry[]): string {
lines.push(
'## Loadable plugins with no config',
'',
'These load from a `cordis.yml` entry with no `config:` block; they declare no config surface.',
'These load from a `cordis.yml` entry with no `config:` block; they declare no configuration API.',
'',
...entries.filter(e => e.kind === 'no-config').map(e => renderTerse(e, '')),
'',
+38 -39
View File
@@ -2,7 +2,7 @@
* Generate the per-subsystem Cordis service/event reference regions from the
* Typert catalog projection. Every harness `ctx.<key>` service and event scope
* maps to exactly one `docs/subsystems/` page through the curated tables below;
* the generator injects each page's surface between its GENERATED markers —
* the generator injects each page's Cordis API reference between its GENERATED markers —
* byte-identically into both language sides of the pair — and re-records a
* pair's `.i18n.yaml` only when nothing outside the region changed. The
* projection enforces event modes, JSDoc parameter/return completeness, and
@@ -40,7 +40,7 @@ export { REGION_BEGIN, REGION_END }
* The owning subsystems page for every harness `ctx.<key>` service the
* projection discovers. Fail-closed both ways: a discovered key absent here
* and an entry whose key the projection no longer discovers are both hard
* errors, so the partition can never silently drift from the service surface.
* errors, so the partition can never silently drift from the service API.
*/
export const SERVICE_PAGE: Record<string, string> = {
agentLoop: 'core.md',
@@ -105,7 +105,7 @@ export const SERVICE_PAGE: Record<string, string> = {
* `index.ts` files with a same-named service class — so a new service can
* never silently join this blind spot: it either enters {@link SERVICE_PAGE}
* or names itself here. Client-face keys (the projection analyzes the host
* face only) name the package README that owns their surface.
* face only) name the package README that owns their API.
* TODO(cordis-catalog-interface-services): the interface-typed and
* non-index-declared entries would all render once the projection resolves a
* Context key through its declaring file's imports to the class declaration.
@@ -119,24 +119,24 @@ export const SERVICE_WALK_EXEMPTIONS: Record<string, string> = {
dshHomePath: 'not a service: boot-provided root accessor function (typeof dshHomePath | undefined) for Loader !!js config expressions — packages/boot/app-boot/README.md owns the boot contract',
headlessIo: 'not a service: launcher-provided root accessor value (HeadlessIo | undefined) for the headless bundle runner — packages/bundle/headless/README.md owns this launcher contract',
launcherEnvironment: 'not a service: launcher-provided root accessor value (EnvironmentSnapshot | undefined) — packages/util/environment/README.md owns this launcher contract',
lsp: 'interface-typed (LspService); implementing class Lsp is not the declared type name — packages/lsp/lsp/README.md owns the surface',
apiProxy: 'interface-typed (ApiProxy) with the class in api-proxy.ts, not index.ts — packages/host/apiproxy/README.md owns the surface',
appShell: 'client-side interface-typed browser service — packages/client/web/README.md owns the surface',
connection: 'client-side interface-typed browser service — packages/client/connection/README.md owns the surface',
lsp: 'interface-typed (LspService); implementing class Lsp is not the declared type name — packages/lsp/lsp/README.md owns the API',
apiProxy: 'interface-typed (ApiProxy) with the class in api-proxy.ts, not index.ts — packages/host/apiproxy/README.md owns the API',
appShell: 'client-side interface-typed browser service — packages/client/web/README.md owns the API',
connection: 'client-side interface-typed browser service — packages/client/connection/README.md owns the API',
chatFileMentions: 'client-side slot-contract accessor (ChatFileMentions) — packages/client/ui-conversation/README.md owns the API',
command: 'client-side interface-typed browser service — packages/client/ui-command/README.md owns the surface',
conversation: 'client-side interface-typed browser service — packages/client/ui-conversation/README.md owns the surface',
conversationEvents: 'client-side interface-typed registry — packages/client/runtime/README.md owns the surface',
conversationViews: 'client-side interface-typed registry — packages/client/runtime/README.md owns the surface',
layout: 'client-side interface-typed browser service — packages/client/ui-layout/README.md owns the surface',
locale: 'client-side interface-typed browser service — packages/client/locale/README.md owns the surface',
models: 'client-side interface-typed browser service — packages/client/ui-model/README.md owns the surface',
modules: 'client-side interface-typed browser service — packages/client/modules/README.md owns the surface',
remote: 'client-side interface-typed gateway accessor (ClientRemote) — packages/api/gateway/README.md owns the surface',
slash: 'client-side interface-typed browser service — packages/client/ui-slash/README.md owns the surface',
slots: 'client-side interface-typed browser service — packages/client/runtime/README.md owns the surface',
theme: 'client-side interface-typed browser service — packages/client/ui-theme/README.md owns the surface',
workspaces: 'client-side interface-typed browser service — packages/client/runtime/README.md owns the surface',
command: 'client-side interface-typed browser service — packages/client/ui-command/README.md owns the API',
conversation: 'client-side interface-typed browser service — packages/client/ui-conversation/README.md owns the API',
conversationEvents: 'client-side interface-typed registry — packages/client/runtime/README.md owns the API',
conversationViews: 'client-side interface-typed registry — packages/client/runtime/README.md owns the API',
layout: 'client-side interface-typed browser service — packages/client/ui-layout/README.md owns the API',
locale: 'client-side interface-typed browser service — packages/client/locale/README.md owns the API',
models: 'client-side interface-typed browser service — packages/client/ui-model/README.md owns the API',
modules: 'client-side interface-typed browser service — packages/client/modules/README.md owns the API',
remote: 'client-side interface-typed gateway accessor (ClientRemote) — packages/api/gateway/README.md owns the API',
slash: 'client-side interface-typed browser service — packages/client/ui-slash/README.md owns the API',
slots: 'client-side interface-typed browser service — packages/client/runtime/README.md owns the API',
theme: 'client-side interface-typed browser service — packages/client/ui-theme/README.md owns the API',
workspaces: 'client-side interface-typed browser service — packages/client/runtime/README.md owns the API',
}
/**
@@ -178,19 +178,19 @@ export const EVENT_SCOPE_PAGE: Record<string, string> = {
* so a scope-level exemption would mask a host-face regression.
*/
export const EVENT_WALK_EXEMPTIONS: Record<string, string> = {
'commands/changed': 'client-face registry invalidation signal — packages/client/runtime/README.md owns the surface',
'connection/reset': 'client-face transport signal — packages/client/runtime/README.md owns the surface',
'credentials/changed': 'client-face registry invalidation signal — packages/client/runtime/README.md owns the surface',
'locale/change': 'client-face locale switch signal — packages/client/locale/README.md owns the surface',
'models/changed': 'client-face registry invalidation signal — packages/client/runtime/README.md owns the surface',
'session/preset-changed': 'client-face per-session catalog invalidation signal — packages/client/runtime/README.md owns the surface',
'settings/changed': 'client-face registry invalidation signal — packages/client/runtime/README.md owns the surface',
'slash/input-begin-command': 'client-face slash-input protocol — packages/client/ui-slash/README.md owns the surface',
'slash/input-consume-token': 'client-face slash-input protocol — packages/client/ui-slash/README.md owns the surface',
'slash/input-insert-reference': 'client-face slash-input protocol — packages/client/ui-slash/README.md owns the surface',
'slash/input-insert-text': 'client-face slash-input protocol — packages/client/ui-slash/README.md owns the surface',
'slots/changed': 'client-face slot invalidation signal — packages/client/runtime/README.md owns the surface',
'theme/change': 'client-face theme switch signal — packages/client/ui-theme/README.md owns the surface',
'commands/changed': 'client-face registry invalidation signal — packages/client/runtime/README.md owns the API',
'connection/reset': 'client-face transport signal — packages/client/runtime/README.md owns the API',
'credentials/changed': 'client-face registry invalidation signal — packages/client/runtime/README.md owns the API',
'locale/change': 'client-face locale switch signal — packages/client/locale/README.md owns the API',
'models/changed': 'client-face registry invalidation signal — packages/client/runtime/README.md owns the API',
'session/preset-changed': 'client-face per-session catalog invalidation signal — packages/client/runtime/README.md owns the API',
'settings/changed': 'client-face registry invalidation signal — packages/client/runtime/README.md owns the API',
'slash/input-begin-command': 'client-face slash-input protocol — packages/client/ui-slash/README.md owns the API',
'slash/input-consume-token': 'client-face slash-input protocol — packages/client/ui-slash/README.md owns the API',
'slash/input-insert-reference': 'client-face slash-input protocol — packages/client/ui-slash/README.md owns the API',
'slash/input-insert-text': 'client-face slash-input protocol — packages/client/ui-slash/README.md owns the API',
'slots/changed': 'client-face slot invalidation signal — packages/client/runtime/README.md owns the API',
'theme/change': 'client-face theme switch signal — packages/client/ui-theme/README.md owns the API',
}
/**
@@ -309,7 +309,6 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
CommandDescriptor: 'commands.md',
CommandId: 'commands.md',
CommandResult: 'commands.md',
CommandSurface: 'commands.md',
LlmAdapter: 'llm-streaming.md',
PreparedLlmCall: 'llm-streaming.md',
LlmService: 'llm-streaming.md',
@@ -563,8 +562,8 @@ export const CORDIS_CATALOG_POLICY: CordisCatalogPolicy = {
/**
* Splice a page's generated cordis-surface region into its Markdown content.
* The page must contain exactly one cordis-surface region (the markers are
* Splice a page's generated Cordis API region into its Markdown content.
* The page must contain exactly one `cordis-surface` marker region (the markers are
* part of the hand-owned page skeleton once, then owned by the generator);
* zero or several is a partition error the caller reports with the page path.
* The match is on THIS generator's exact markers, not the generic region
@@ -610,7 +609,7 @@ export interface WalkPartitionMaps {
}
/**
* Judge the rendered surface and the independent AST scan against the curated
* Judge the rendered API and the independent AST scan against the curated
* partition maps, fail-closed in both directions for services AND events: a
* rendered key/scope must be mapped to a page, a mapped key/scope must still
* render, and — the backstop — a DECLARED key/event the projection cannot see
@@ -618,7 +617,7 @@ export interface WalkPartitionMaps {
* direction guards the scan itself: everything rendered must also be declared
* to the scan, so a scan blind spot cannot decay silently. Pure so the
* acceptance paths are provable without running the projection.
* @param input - rendered surface plus the declared-key/event scans.
* @param input - rendered API plus the declared-key/event scans.
* @param maps - the curated page maps and walk exemptions.
* @returns one message per violation, empty when the partition holds.
*/
@@ -669,7 +668,7 @@ export function walkPartitionProblems(input: WalkPartitionInput, maps: WalkParti
// in a Context/Events merge the scan must also reach, so a rendered key or
// event the scan cannot see means the SCAN regressed (glob, prefilter, or
// block walk) — a partial blind spot that exemption staleness alone would
// never surface.
// never appear.
for (const key of input.renderedKeys.keys()) {
if (!input.declaredKeys.has(key)) problems.push(`ctx.${key} is rendered by the projection but the independent scan finds no Context merge declaring it; the scan has a blind spot (glob, prefilter, or module-block walk) — fix the scan, not the maps.`)
}
+5 -5
View File
@@ -395,7 +395,7 @@ const SERVICE_ROLES: ServiceRole[] = [
mode: 'seam',
implementations: ['pty-local'],
consumers: ['tool-pty'],
note: 'The registry owns exact-Agent session identity and cleanup; backends own terminal mechanics, while tool-pty exposes the owner-scoped model surface.',
note: 'The registry owns exact-Agent session identity and cleanup; backends own terminal mechanics, while tool-pty exposes the owner-scoped model tools.',
},
{
key: 'sandbox',
@@ -476,7 +476,7 @@ const SERVICE_ROLES: ServiceRole[] = [
mode: 'seam',
implementations: ['tasks-local'],
consumers: ['tool-bash', 'tool-pty', 'tool-subagent', 'tool-tasks'],
note: 'Producers (background bash, PTY sends, and subagent delegations) register running work; tool-tasks is the model-facing control surface that reads, lists, and kills it; tasks-local is the process-local registry.',
note: 'Producers (background bash, PTY sends, and subagent delegations) register running work; tool-tasks is the model-facing controller that reads, lists, and kills it; tasks-local is the process-local registry.',
},
{
key: 'web',
@@ -1271,7 +1271,7 @@ function renderLifecycle(): string {
'',
'The returned `agent/pre-step` decision is authoritative; listeners wrapping `next()` preserve downstream messages unless replacement is intentional. Steering and injected context pass through the same waterfall after a later claim operation takes their next-step batch.',
'',
'SDK users that need replayable transcript data should consume `session/event`; `agent/*` is the live coordination surface for queue/status, prompt interception, request construction, steering, continuation, and errors.',
'SDK users that need replayable transcript data should consume `session/event`; `agent/*` is the live coordination API for queue/status, prompt interception, request construction, steering, continuation, and errors.',
'',
...maintenanceFooter(maintenance),
].join('\n')
@@ -1335,7 +1335,7 @@ function renderToolPipeline(): string {
' allResults --> context',
'```',
'',
'Filesystem read-before-edit checks stay below `tool-fs` on `fs/*` events. Generic pre/post waterfalls host hooks and approval policy; `ctx.approval` resolves asks before monotonic guards, and owner policy that must not be reordered remains a registered guard. Around-dispatch concerns such as timeouts wrap `tools/execute`. The registry losslessly snapshots the candidate result and normalizes a snapshot failure before the visible definition\'s snapshotted `finalizeContent` callback enforces its synchronous content-only invariant. `tools/result` then observes the immutable, lossless-JSON outcome. This lets hooks span tool families without coupling the tools to one policy service. Code Mode sends both the reserved `run_code` transport and its serialized sub-calls through the pipeline; sub-calls carry the parent token, log `tool/code-dispatch`, surface denials as binding rejections, and omit `additionalContexts` to preserve call/result adjacency.',
'Filesystem read-before-edit checks stay below `tool-fs` on `fs/*` events. Generic pre/post waterfalls host hooks and approval policy; `ctx.approval` resolves asks before monotonic guards, and owner policy that must not be reordered remains a registered guard. Around-dispatch concerns such as timeouts wrap `tools/execute`. The registry losslessly snapshots the candidate result and normalizes a snapshot failure before the visible definition\'s snapshotted `finalizeContent` callback enforces its synchronous content-only invariant. `tools/result` then observes the immutable, lossless-JSON outcome. This lets hooks span tool families without coupling the tools to one policy service. Code Mode sends both the reserved `run_code` transport and its serialized sub-calls through the pipeline; sub-calls carry the parent token, log `tool/code-dispatch`, return denials as binding rejections, and omit `additionalContexts` to preserve call/result adjacency.',
'',
...maintenanceFooter(maintenance),
].join('\n')
@@ -1387,7 +1387,7 @@ function renderIndex(docs: GraphDoc[]): string {
const maintenance = 'mixed: each linked page declares generated, hybrid, or curated mode'
return [
...generatedHeader('Documentation Graph Index'),
'These diagrams show relationships that the generated catalogs do not. Use them to find package relationships, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type definitions still live in the [subsystem pages](subsystems/core.md) (types + the generated `cordis-surface` regions) and [tool-catalog.md](tool-catalog.md).',
'These diagrams show relationships that the generated catalogs do not. Use them to find package relationships, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type definitions still live in the [subsystem pages](subsystems/core.md) (types + the generated Cordis API regions) and [tool-catalog.md](tool-catalog.md).',
'',
'The process decision behind this index is recorded in [the documentation graph Agent Note](../.agents/notes/archived/process/2026-07-03-documentation-graph-atlas.md).',
'',
+8 -8
View File
@@ -142,7 +142,7 @@ interface ToolPackage {
* name to its own source.
*/
source: string | Readonly<Record<string, string>>
/** Services or owning runtime surfaces the package requires at execution time. */
/** Services or owning runtimes the package requires at execution time. */
requires: string[]
/** Session events or other visible state the tools write or affect. */
writes: string[]
@@ -156,14 +156,14 @@ interface ToolPackage {
/**
* Config for the caller's `ToolRegistry` mount. The registry itself ships a
* model-facing tool (`run_code`, registered under a non-native `mode`), so
* ITS catalog entry boots the registry in the mode that surfaces it;
* ITS catalog entry boots the registry in the mode that exposes it;
* every other entry uses the default (native) registry.
*/
toolsConfig?: ToolsConfig
/**
* A deployment note rendered after the package's tools, for a fact that
* booting the package alone cannot show. The registered tool NAME can be a
* load-time config (`tool-subagent`'s `toolName`), so one package may surface
* load-time config (`tool-subagent`'s `toolName`), so one package may appear
* under several names across deployments — the boot yields the package
* DEFAULT, and this note records the shipped alternatives the model sees.
*/
@@ -246,7 +246,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
await ctx.plugin(ToolPwsh)
},
note:
'The pwsh tool is the PowerShell-dialect consumer of the bash executor seam for Windows compositions (a PowerShell executor such as `@deepseek-ai/dsh-pwsh-local` backs `ctx.bash`); it mirrors the bash tool call-for-call minus the sandbox surface — `run_in_background` runs register with the generic `ctx.tasks` runtime and are collected/stopped through the `task_*` tools, and the managed `DSH_*` environment comes from `@deepseek-ai/dsh-bash-env`. Each call runs in a fresh process (no persistent PTY session), with native `C:\\...` paths and `$env:NAME` variables.',
'The pwsh tool is the PowerShell-dialect consumer of the bash executor seam for Windows compositions (a PowerShell executor such as `@deepseek-ai/dsh-pwsh-local` backs `ctx.bash`); it mirrors the bash tool call-for-call minus sandbox controls — `run_in_background` runs register with the generic `ctx.tasks` runtime and are collected/stopped through the `task_*` tools, and the managed `DSH_*` environment comes from `@deepseek-ai/dsh-bash-env`. Each call runs in a fresh process (no persistent PTY session), with native `C:\\...` paths and `$env:NAME` variables.',
},
{
pkg: '@deepseek-ai/dsh-tool-cordis',
@@ -284,7 +284,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
await ctx.plugin(ToolStrReplaceEditor)
},
note:
'Standalone view/create/unique literal replace/line insert tool over the filesystem seam; it composes with any shell or terminal surface.',
'Standalone view/create/unique literal replace/line insert tool over the filesystem seam; it composes with any shell or terminal API.',
},
{
pkg: '@deepseek-ai/dsh-tool-fs',
@@ -476,7 +476,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
await ctx.plugin(ToolTasks)
},
note:
'The kind-agnostic background-task control surface: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the control surface that arms producers\' `ctx.tasks.start()`.',
'The kind-agnostic background-task controller: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the controller that arms producers\' `ctx.tasks.start()`.',
},
{
pkg: '@deepseek-ai/dsh-tool-todo',
@@ -637,11 +637,11 @@ export function render(catalog: ToolCatalog): string {
'',
'# Tool Schema Catalog',
'',
'Every model-facing tool a shipped plugin contributes to `ctx.tools`: the `name`, `description`, and JSON-Schema `parameters` the model receives via the system-prompt assembly. It complements the [subsystem pages](subsystems/core.md) (the types plus each page\'s generated `cordis-surface` wiring region) — this page is the *tools* the agent is offered.',
'Every model-facing tool a shipped plugin contributes to `ctx.tools`: the `name`, `description`, and JSON-Schema `parameters` the model receives via the system-prompt assembly. It complements the [subsystem pages](subsystems/core.md) (the types plus each page\'s generated Cordis API region) — this page is the *tools* the agent is offered.',
'',
'This file is GENERATED and verified fresh by `pnpm run verify-tool-catalog` (part of `doc-sync`) — do not edit it by hand. Unlike the cordis catalog (a pure source-AST pass), this generator BOOTS each tool plugin on a real context and reads `ctx.tools.schemas()`, because a tool schema is not statically knowable (runtime-spread enums, concatenated descriptions, config-driven names, raw-JSON-Schema MCP tools). A completeness guard globs `packages/*/tool-*` and fails if any package is missing from the generator\'s boot manifest, so a new tool cannot be silently undocumented. See [the tool-schema-catalog Agent Note](../.agents/notes/implemented/process/2026-07-02-tool-schema-catalog.md).',
'',
'Scope: shipped product tools under `packages/*/tool-*`, each booted with its DEFAULT config, except where a Config field is REQUIRED with no default — there the generator must choose, and the per-package note records which branch this page shows. The registered tool NAME can be a load-time config (e.g. `tool-subagent`\'s `toolName`), so a deployment may surface a package under a different or additional name — a per-package note records those shipped aliases where they exist. The `examples/` demo tools (e.g. `echo`) are excluded, matching the cordis catalog\'s packages-only scope.',
'Scope: shipped product tools under `packages/*/tool-*`, each booted with its DEFAULT config, except where a Config field is REQUIRED with no default — there the generator must choose, and the per-package note records which branch this page shows. The registered tool NAME can be a load-time config (e.g. `tool-subagent`\'s `toolName`), so a deployment may expose a package under a different or additional name — a per-package note records those shipped aliases where they exist. The `examples/` demo tools (e.g. `echo`) are excluded, matching the cordis catalog\'s packages-only scope.',
'',
'## Tool Package Map',
'',
+4 -4
View File
@@ -1,6 +1,6 @@
/**
* Shared JSDoc parsing and completeness checks for the Cordis, persistence,
* and config catalogs and the export-surface gate.
* and config catalogs and the exported-API gate.
*/
import ts from 'typescript'
@@ -124,7 +124,7 @@ export function parseTags(raw: string): { params: Map<string, string>; returns:
* binding-pattern parameters, and reject stale tags. Exempt parameters may
* still be documented.
* @param where - the offender label violations open with, e.g. `event 'x' (file:1)`.
* @param surface - surface noun used in binding-pattern diagnostics.
* @param apiKind - API kind used in binding-pattern diagnostics.
* @param parameters - the declaration's parameter list.
* @param tags - the parsed `@param` name→description map from parseTags.
* @param sf - source file used to render binding patterns.
@@ -133,7 +133,7 @@ export function parseTags(raw: string): { params: Map<string, string>; returns:
*/
export function checkParams(
where: string,
surface: string,
apiKind: string,
parameters: readonly ts.ParameterDeclaration[],
tags: Map<string, string>,
sf: ts.SourceFile,
@@ -142,7 +142,7 @@ export function checkParams(
): void {
for (const p of parameters) {
if (!ts.isIdentifier(p.name)) {
violations.push(`${where}: parameter '${p.name.getText(sf)}' is a binding pattern; the ${surface} surface needs simple identifier parameters so @param can name them.`)
violations.push(`${where}: parameter '${p.name.getText(sf)}' is a binding pattern; the ${apiKind} API needs simple identifier parameters so @param can name them.`)
continue
}
if (isExempt(p)) continue
+1 -1
View File
@@ -281,7 +281,7 @@ export function parseTranslationPairingCliArgs(argv: string[]): TranslationPairi
}
}
/** The structural surface compared between the two sides of a pair. */
/** The structural signature compared between the two sides of a pair. */
export interface TranslationStructureSignature {
/** Heading depths in document order (h2 -> 2). */
headings: number[]
+2 -2
View File
@@ -6,7 +6,7 @@
* across domains.
*
* Layer model (lower may not import higher):
* 0 contract/ shared contract surface (types + slot declarations)
* 0 contract/ shared contract API (types + slot declarations)
* 1 <domain>/ + service domain implementations (skeleton/, chat/, ...)
* 2 apply.ts, index.ts assembly point and re-export shell
*
@@ -71,7 +71,7 @@ function checkPackage(pkgName: string, clientDir: string): Violation[] {
imported: spec,
reason: fromDomain === ''
? `top-level non-assembly file imports domain "${toDomain}" (only apply/index may assemble)`
: `domain "${fromDomain}" imports sibling domain "${toDomain}" (route shared surface through contract/)`,
: `domain "${fromDomain}" imports sibling domain "${toDomain}" (route shared API through contract/)`,
})
}
}
+16 -16
View File
@@ -58,7 +58,7 @@ function thisReceiver(p: ts.ParameterDeclaration): boolean {
}
/**
* Peel wrapper expressions that carry no surface of their own — parentheses,
* Peel wrapper expressions that define no API of their own — parentheses,
* `as` / `satisfies` / angle-bracket casts, non-null assertions — so a
* wrapped function expression is still classified as function-like.
* @param e - the expression to unwrap.
@@ -91,7 +91,7 @@ function callableAnnotation(type: ts.TypeNode): ts.SignatureDeclarationBase | 'r
}
/**
* Find inherited documentation for a class member without exempting newly public surface.
* Find inherited documentation for a class member without exempting a newly public API.
* @param cls - the class whose heritage to search.
* @param name - the member name to look up.
* @param staticSide - whether to search the constructor side instead of the instance side.
@@ -112,7 +112,7 @@ function heritageExemption(
const prop = type.getProperty(name)
if (prop === undefined) continue
const decls = prop.declarations ?? []
if (decls.length > 0 && decls.every(isProtected)) continue // public override of a protected base: new surface
if (decls.length > 0 && decls.every(isProtected)) continue // public override of a protected base: new API
let baseParams: Set<string> | null = null
let baseVoidReturn: boolean | null = null
for (const d of decls) {
@@ -192,7 +192,7 @@ function checkFunctionLike(
if (!raw) { w.violations.push(`${where} has no JSDoc.`); return }
if (!parseJsDoc(raw).doc) w.violations.push(`${where} has no description prose above its block tags.`)
const { params, returns } = parseTags(raw)
checkParams(where, 'export', parameters, params, w.sf, thisReceiver, w.violations)
checkParams(where, 'exported', parameters, params, w.sf, thisReceiver, w.violations)
if (!returnsWaived) checkReturns(where, returnType, returns, w.sf, w.violations)
}
@@ -205,7 +205,7 @@ function checkFunctionLike(
* statics are exempt; constructors are not checked (framework-constructed
* plugins, and the class doc owns the story).
* @param cls - the exported class declaration.
* @param name - the class's surface name (namespace-qualified).
* @param name - the class's exported name (namespace-qualified).
* @param w - the walk state violations append to.
*/
function checkClass(cls: ts.ClassDeclaration, name: string, w: Walk): void {
@@ -230,12 +230,12 @@ function checkClass(cls: ts.ClassDeclaration, name: string, w: Walk): void {
const raw = rawJsDoc(w.text, m)
// The heritage declaration owns the prose; parameters the base never
// names — including binding patterns, which no base declaration can
// name — are new surface and keep their @param duty.
// name — are new API and keep their @param duty.
const base = exemption.baseParams
const inBase = (p: ts.ParameterDeclaration): boolean =>
base !== null && ts.isIdentifier(p.name) && base.has(p.name.text.replace(/^_+/, ''))
if (base !== null && m.parameters.some(p => !thisReceiver(p) && !inBase(p))) {
checkParams(where, 'export', m.parameters, parseTags(raw).params, w.sf,
checkParams(where, 'exported', m.parameters, parseTags(raw).params, w.sf,
p => thisReceiver(p) || inBase(p), w.violations)
}
// A void base return carried no @returns duty, so an override growing a concrete result
@@ -258,7 +258,7 @@ function checkClass(cls: ts.ClassDeclaration, name: string, w: Walk): void {
} else if (ts.isSetAccessorDeclaration(m) && !documentedGetters.has(mname)) {
checkDescribed(`exported class accessor '${name}.${mname}' (${pointer(w.rel, w.sf, m)})`, rawJsDoc(w.text, m), w)
}
// index signatures / static blocks: not named surface
// index signatures / static blocks: no named API
}
}
@@ -310,7 +310,7 @@ function checkDecl(
const raw = rawJsDoc(w.text, stmt) // JSDoc sits on the statement, not the declarator
for (const d of stmt.declarationList.declarations) {
const name = ts.isIdentifier(d.name) ? d.name.text : d.name.getText(w.sf)
if (only !== null && !only.has(name)) continue // sibling declarator the export list never named: not surface
if (only !== null && !only.has(name)) continue // sibling declarator the export list never named: not exported API
if (prefix === '' && PROTOCOL_EXPORTS.has(name)) continue // cordis plugin-protocol slot
const where = `exported const '${prefix}${name}'${at(d)}`
const annotation = d.type !== undefined ? callableAnnotation(d.type) : null
@@ -321,7 +321,7 @@ function checkDecl(
// tags against — fail closed rather than silently narrow the check.
w.violations.push(`${where}: its callable type literal is not gate-classifiable; extract a named type and document it there.`)
} else if (annotation !== null) {
// An INLINE callable annotation is the surface signature itself: its
// An INLINE callable annotation is the exported signature itself: its
// parameters and result need docs right here. (A NAMED reference
// type carries its docs at the type's own declaration instead.)
checkFunctionLike(where, raw, annotation.parameters, annotation.type, false, w)
@@ -350,7 +350,7 @@ function checkDecl(
}
// In an ambient (`declare`) namespace body, members are implicitly
// exported — no `export` modifier required — so the recursion must treat
// every statement as surface.
// every statement as exported API.
const declared = ambient
|| ((ts.canHaveModifiers(stmt) ? ts.getModifiers(stmt) : undefined)?.some(m => m.kind === ts.SyntaxKind.DeclareKeyword) ?? false)
if (body !== undefined && ts.isModuleBlock(body)) checkScope(body.statements, nsPrefix, w, declared)
@@ -376,7 +376,7 @@ function checkDecl(
}
// Fail CLOSED: an exported statement kind this dispatch does not recognize
// must never pass silently — the gate's whole promise is that unchecked
// surface cannot exist. New TypeScript export forms extend the gate here.
// unchecked API cannot exist. New TypeScript export forms extend the gate here.
w.violations.push(`exported statement${at(stmt)} uses an export form verify-export-jsdoc does not handle; extend the gate.`)
}
@@ -385,7 +385,7 @@ function checkDecl(
* exported declaration, resolving `export { … }` lists (no module specifier)
* to their local declarations.
* @param statements - the scope's statements.
* @param prefix - the namespace qualification for surface names ('' at top level).
* @param prefix - the namespace qualification for exported names ('' at top level).
* @param w - the walk state violations append to.
* @param ambient - whether this scope is ambient (`declare` namespace or a declaration file), where members export implicitly.
*/
@@ -445,7 +445,7 @@ function checkScope(
}
if (ts.isExportAssignment(stmt)) {
if (stmt.isExportEquals) {
// `export =` has no ESM consumer surface in this repo and the walk
// `export =` has no ESM consumer API in this repo and the walk
// cannot classify its operand's type; refuse rather than fail open.
w.violations.push(`export-equals assignment (${pointer(w.rel, w.sf, stmt)}) is not a gate-supported export form; use ESM named exports.`)
continue
@@ -599,11 +599,11 @@ export function collectExportJsdocViolations(scanRoot: string = root): string[]
return violations
}
/** CLI entry: list every violation and exit 1, or confirm a clean surface. */
/** CLI entry: list every violation and exit 1, or confirm a documented API. */
function main(): void {
const violations = collectExportJsdocViolations()
if (violations.length === 0) {
console.log('verify-export-jsdoc: every exported name on the package surface is documented.')
console.log('verify-export-jsdoc: every exported name in each package API is documented.')
return
}
console.error(`verify-export-jsdoc: ${violations.length} JSDoc completeness violation(s) (see AGENTS.md):`)
+1 -1
View File
@@ -151,7 +151,7 @@ try {
cwd: root,
stdio: 'pipe',
})
console.log(`verify-node-next-types: ${packages.length} workspace package declaration surface(s) compile under NodeNext.`)
console.log(`verify-node-next-types: ${packages.length} workspace package declaration API(s) compile under NodeNext.`)
} catch (error: unknown) {
failed = true
const output = error as { stdout?: Buffer; stderr?: Buffer }
+111 -111
View File
@@ -38,14 +38,14 @@ const NO_MODEL_EXPERIENCE_SECTION: Readonly<Record<string, string>> = {
/**
* Packages whose Model Experience is simple enough for one gated sentence plus
* a KV-cache field. Every other package must carry canonical context-surface
* a KV-cache field. Every other package must carry canonical model-context
* blocks. A package moves on or off this list with its context behavior.
*/
const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/attachment/attachment': { kind: 'indirect', reason: 'The storage seam delegates model request rendering to provider adapters.' },
'packages/attachment/attachment-local': { kind: 'indirect', reason: 'The local backend delegates model request rendering to provider adapters.' },
'packages/bash/bash': { kind: 'indirect', reason: 'The service interface delegates all model rendering to dsh-tool-bash.' },
'packages/bash/bash-env': { kind: 'indirect', reason: 'The env service surfaces managed DSH_* facts through the shell tools (dsh-tool-bash/dsh-tool-pwsh); it registers no prompt or schema of its own.' },
'packages/bash/bash-env': { kind: 'indirect', reason: 'The env service exposes managed DSH_* facts through the shell tools (dsh-tool-bash/dsh-tool-pwsh); it registers no prompt or schema of its own.' },
'packages/bash/bash-local': { kind: 'indirect', reason: 'The executor backend delegates model rendering to dsh-tool-bash.' },
'packages/bash/pwsh-local': { kind: 'indirect', reason: 'The executor backend delegates model rendering to dsh-tool-pwsh.' },
'packages/code-runtime/code-runtime': { kind: 'indirect', reason: 'The service interface delegates model rendering to Code Mode in dsh-tools.' },
@@ -57,50 +57,50 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/typert/registry': { kind: 'none', reason: 'Runtime type registry; consumers (cordis_inspect, wire faces, gates) own any model-visible projection of registry contents.' },
'packages/typert/loader': { kind: 'none', reason: 'Loader integration only registers generated artifacts; consumers own any model-visible projection.' },
'packages/e2b/e2b': { kind: 'none', reason: 'The shared remote-runtime owner registers no model context; provider adapters and consumers own rendered effects.' },
'packages/client/hmr': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/modules': { kind: 'none', reason: 'Browser-side module-loading kernel machinery; registers no model surface.' },
'packages/client/test-runtime': { kind: 'none', reason: 'Browser-side test infrastructure (jsdom bench); registers no model surface.' },
'packages/client/ui-slots': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-primitives': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/web-react': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/schema-form': { kind: 'none', reason: 'Browser-side form-rendering library; registers no model surface.' },
'packages/client/connection': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/hmr': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/modules': { kind: 'none', reason: 'Browser-side module-loading kernel machinery; registers nothing model-facing.' },
'packages/client/test-runtime': { kind: 'none', reason: 'Browser-side test infrastructure (jsdom bench); registers nothing model-facing.' },
'packages/client/ui-slots': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-primitives': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/web-react': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/schema-form': { kind: 'none', reason: 'Browser-side form-rendering library; registers nothing model-facing.' },
'packages/client/connection': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/api/remotes': { kind: 'none', reason: 'The Remote BFF selects business methods and identity policy; selected services own any model-visible effect.' },
'packages/client/runtime': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-layout': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-sidebar': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-conversation': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/runtime': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-layout': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-sidebar': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-conversation': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-tool': { kind: 'none', reason: 'Browser-side Tool presentation layer; renders logged calls without changing model context.' },
'packages/client/ui-deliverables': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-task': { kind: 'none', reason: 'Browser-side read-only projection of ctx.tasks records; dsh-tool-tasks owns the model-facing surface.' },
'packages/client/ui-slash': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-deliverables': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-task': { kind: 'none', reason: 'Browser-side read-only projection of ctx.tasks records; dsh-tool-tasks owns the model-facing behavior.' },
'packages/client/ui-slash': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-command': { kind: 'indirect', reason: 'The dispatch paths trigger the host command.execute RPC; each command handler\'s host package owns any model-visible effect.' },
'packages/client/ui-model': { kind: 'indirect', reason: 'Selection routes session.selectModel; the Host snapshots the selection at the next prompt-assembly boundary and owns the model-visible effect.' },
'packages/client/ui-goal': { kind: 'indirect', reason: 'The strip verbs route goal.* mutations; the host GoalService owns the model-visible goal/change context message.' },
'packages/client/ui-permission': { kind: 'indirect', reason: 'The picker submits the host /permission command; the knob events it appends own the model-visible effect through the sandbox/approval consumers.' },
'packages/client/ui-plan': { kind: 'indirect', reason: 'The chip dispatches /plan off; dsh-plan-mode owns the model-visible policy, exit tool, and logged state.' },
'packages/client/ui-question': { kind: 'indirect', reason: 'The package mounts dsh-tool-ask-user; that tool owns the model-visible schema and answer rendering.' },
'packages/client/ui-trajectory': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-workspace': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-theme': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-settings': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-settings-general': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-models': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/locale': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/web': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' },
'packages/client/ui-trajectory': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-workspace': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-theme': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-settings': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-settings-general': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/ui-models': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/locale': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/client/web': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
'packages/examples/agent-spine-demo': { kind: 'indirect', reason: 'The bundle only mounts model-facing child plugins.' },
'packages/fs/fs': { kind: 'indirect', reason: 'The service interface delegates model rendering to dsh-tool-fs.' },
'packages/e2b/fs-e2b': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-fs.' },
'packages/fs/fs-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-fs.' },
'packages/hooks/hook-protocol': { kind: 'indirect', reason: 'Only the hook bridge plugins render decoded hook output to a model.' },
'packages/host/apiproxy': { kind: 'none', reason: 'The wire contract and fetch carriers move already-composed messages and register no model surface.' },
'packages/host/directory-picker': { kind: 'none', reason: 'The GUI-host picking seam registers no model surface.' },
'packages/host/directory-picker-auto': { kind: 'none', reason: 'The GUI-host picking chooser only mounts a backend row; registers no model surface.' },
'packages/host/directory-picker-browse': { kind: 'none', reason: 'The GUI-host picking backend registers no model surface.' },
'packages/host/directory-picker-native': { kind: 'none', reason: 'The GUI-host picking backend registers no model surface.' },
'packages/host/webserver': { kind: 'none', reason: 'The HTTP carrier bridges browser and API handler and registers no model surface.' },
'packages/host/frontend-static': { kind: 'none', reason: 'The SPA dist server answers browser asset requests and registers no model surface.' },
'packages/bundle/base': { kind: 'indirect', reason: 'The bundle is a patch-list carrier; each inserted row\'s package owns its model surface.' },
'packages/host/apiproxy': { kind: 'none', reason: 'The wire contract and fetch carriers move already-composed messages and register nothing model-facing.' },
'packages/host/directory-picker': { kind: 'none', reason: 'The GUI-host picking seam registers nothing model-facing.' },
'packages/host/directory-picker-auto': { kind: 'none', reason: 'The GUI-host picking chooser only mounts a backend row; it registers nothing model-facing.' },
'packages/host/directory-picker-browse': { kind: 'none', reason: 'The GUI-host picking backend registers nothing model-facing.' },
'packages/host/directory-picker-native': { kind: 'none', reason: 'The GUI-host picking backend registers nothing model-facing.' },
'packages/host/webserver': { kind: 'none', reason: 'The HTTP carrier bridges browser and API handler and registers nothing model-facing.' },
'packages/host/frontend-static': { kind: 'none', reason: 'The SPA dist server answers browser asset requests and registers nothing model-facing.' },
'packages/bundle/base': { kind: 'indirect', reason: 'The bundle is a patch-list carrier; each inserted row\'s package owns its model-facing behavior.' },
'packages/bundle/headless': { kind: 'none', reason: 'The one-shot runner submits the task as an ordinary user message; prompts and tools belong to the composed base and headless bundles.' },
'packages/llm/llm': { kind: 'none', reason: 'The adapter registry forwards already-assembled requests unchanged.' },
'packages/llm/token-meter': { kind: 'indirect', reason: 'The measurement service leaves model-visible changes to its consumers.' },
@@ -111,24 +111,24 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/subprocess/subprocess-local': { kind: 'indirect', reason: 'The spawn backend delegates model rendering to consumer seams such as the bash executor family.' },
'packages/sandbox/sandbox-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-bash-sandbox and dsh-tool-bash.' },
'packages/sandbox/sandbox-windows-acl': { kind: 'indirect', reason: 'The provider backend delegates model rendering to the bash/pwsh sandbox executors and their tools.' },
'packages/scaffold/create-sdk': { kind: 'indirect', reason: 'The initializer only writes project files; selected runtime plugins provide the generated project model surface.' },
'packages/scaffold/helper': { kind: 'none', reason: 'The project domain edits files and registers no live agent or model surface.' },
'packages/scaffold/create-sdk': { kind: 'indirect', reason: 'The initializer only writes project files; selected runtime plugins provide the generated project model behavior.' },
'packages/scaffold/helper': { kind: 'none', reason: 'The project domain edits files and registers neither a live agent nor model-facing content.' },
'packages/scaffold/scripts': { kind: 'indirect', reason: 'The launcher delegates model context to the loaded project plugin tree.' },
'packages/scaffold/client': { kind: 'none', reason: 'Client-process library; the model surface lives in the spawned runtime\'s composed plugins.' },
'packages/scaffold/protocol': { kind: 'none', reason: 'Client-facing wire library; the runtime plugins behind the serving entry own the model surface.' },
'packages/scaffold/telemetry': { kind: 'none', reason: 'The launcher-side reporter sends developer-cycle telemetry and registers no live agent or model surface.' },
'packages/session/session-projection': { kind: 'none', reason: 'The projection registry serves client-facing read models of already-logged session state and registers no model surface.' },
'packages/session/session-projection-cache': { kind: 'none', reason: 'The persisted cache accelerates host-side cold reads of projection state and registers no model surface.' },
'packages/session-query/session-query': { kind: 'none', reason: 'The trusted query service exposes cloned records only to callers and registers no model surface.' },
'packages/session-query/session-query-sqlite': { kind: 'none', reason: 'The search backend returns hits only to callers and registers no model surface.' },
'packages/settings/settings': { kind: 'indirect', reason: 'The seam stores and resolves user settings; consumer plugins own any model surface a value feeds.' },
'packages/settings/settings-local': { kind: 'indirect', reason: 'The file provider stores and publishes namespace sections; consumers of ctx.settings own any model surface.' },
'packages/credentials/credentials': { kind: 'indirect', reason: 'The seam resolves credential references; the consuming adapter owns every model surface a value authorizes.' },
'packages/credentials/credentials-local': { kind: 'indirect', reason: 'The file/environment provider stores credential values; consumers of ctx.credentials own any model surface.' },
'packages/util/atomic-write': { kind: 'none', reason: 'Pure filesystem write primitive; registers no model surface.' },
'packages/session/session-telemetry': { kind: 'none', reason: 'The seam observes the session stream and hands redacted copies outward; it registers no model surface.' },
'packages/session/session-telemetry-otel': { kind: 'none', reason: 'The backend forwards seam records into the OTel SDK pipeline and registers no model surface.' },
'packages/session/user-id': { kind: 'none', reason: 'The shared identifier appears only in telemetry metadata and a direct human command response; it registers no model surface.' },
'packages/scaffold/client': { kind: 'none', reason: 'Client-process library; model-facing behavior lives in the spawned runtime\'s composed plugins.' },
'packages/scaffold/protocol': { kind: 'none', reason: 'Client-facing wire library; the runtime plugins behind the serving entry own the model-facing behavior.' },
'packages/scaffold/telemetry': { kind: 'none', reason: 'The launcher-side reporter sends developer-cycle telemetry and registers neither a live agent nor model-facing content.' },
'packages/session/session-projection': { kind: 'none', reason: 'The projection registry serves client-facing read models of already-logged session state and registers nothing model-facing.' },
'packages/session/session-projection-cache': { kind: 'none', reason: 'The persisted cache accelerates host-side cold reads of projection state and registers nothing model-facing.' },
'packages/session-query/session-query': { kind: 'none', reason: 'The trusted query service exposes cloned records only to callers and registers nothing model-facing.' },
'packages/session-query/session-query-sqlite': { kind: 'none', reason: 'The search backend returns hits only to callers and registers nothing model-facing.' },
'packages/settings/settings': { kind: 'indirect', reason: 'The seam stores and resolves user settings; consumer plugins own any model-facing content fed by a value.' },
'packages/settings/settings-local': { kind: 'indirect', reason: 'The file provider stores and publishes namespace sections; consumers of ctx.settings own any model-facing behavior.' },
'packages/credentials/credentials': { kind: 'indirect', reason: 'The seam resolves credential references; the consuming adapter owns every model-facing use a value authorizes.' },
'packages/credentials/credentials-local': { kind: 'indirect', reason: 'The file/environment provider stores credential values; consumers of ctx.credentials own any model-facing behavior.' },
'packages/util/atomic-write': { kind: 'none', reason: 'Pure filesystem write primitive; registers nothing model-facing.' },
'packages/session/session-telemetry': { kind: 'none', reason: 'The seam observes the session stream and hands redacted copies outward; it registers nothing model-facing.' },
'packages/session/session-telemetry-otel': { kind: 'none', reason: 'The backend forwards seam records into the OTel SDK pipeline and registers nothing model-facing.' },
'packages/session/user-id': { kind: 'none', reason: 'The shared identifier appears only in telemetry metadata and a direct human command response; it registers nothing model-facing.' },
'packages/skill/skill': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-skill.' },
'packages/skill/skill-badge': { kind: 'indirect', reason: 'The bundled provider delegates model rendering to dsh-tool-skill.' },
'packages/skill/skill-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-skill.' },
@@ -141,9 +141,9 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/support/llm-mock-server': { kind: 'none', reason: 'The test server substitutes provider wire behavior without invoking a real model.' },
'packages/support/llm-replay': { kind: 'none', reason: 'The keyless adapter invokes no provider model.' },
'packages/api/gateway': { kind: 'none', reason: 'Remote dispatch infrastructure; invoked business methods own any model-visible effect.' },
'packages/typert/type-meta': { kind: 'none', reason: 'Compiler-independent Remote protocol declarations; registers no model surface.' },
'packages/typert/type-meta': { kind: 'none', reason: 'Compiler-independent Remote protocol declarations; registers nothing model-facing.' },
'packages/typert/generator': { kind: 'none', reason: 'The build-time generator runs outside any agent runtime and touches no model request.' },
'packages/tasks/tasks': { kind: 'indirect', reason: 'Producer and control-surface plugins own all model rendering over the task registry.' },
'packages/tasks/tasks': { kind: 'indirect', reason: 'Producer and controller plugins own all model rendering over the task registry.' },
'packages/tasks/tasks-local': { kind: 'indirect', reason: 'The registry backend delegates model rendering to producer plugins and dsh-tool-tasks.' },
'packages/examples/acp-demo': { kind: 'indirect', reason: 'The app bundle delegates request composition to dsh-agent-spine-demo and dsh-acp.' },
'packages/boot/app-boot': { kind: 'indirect', reason: 'Only the loaded plugin tree contributes model context.' },
@@ -153,7 +153,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/interaction/user-interaction': { kind: 'indirect', reason: 'Model-facing consumers render provider answers and seam errors.' },
'packages/util/timeout': { kind: 'indirect', reason: 'Only timeout consumers render timeout outcomes.' },
'packages/util/retention': { kind: 'indirect', reason: 'Only retention consumers render retained content and omission metadata.' },
'packages/util/native-command': { kind: 'none', reason: 'The host-side subprocess runner registers no model surface.' },
'packages/util/native-command': { kind: 'none', reason: 'The host-side subprocess runner registers nothing model-facing.' },
'packages/web/web': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-web.' },
'packages/web/web-fetch-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-web.' },
'packages/web/web-search-exa': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-web.' },
@@ -167,7 +167,7 @@ interface Failure {
type Line = MarkdownProseLine
interface ContextSurface {
interface ModelExperienceEntry {
heading: Line
modelView: Line
tokenEffect: Line
@@ -199,7 +199,7 @@ function validateNestedVerbatim(raw: readonly string[], fragments: Set<string>):
const fragment = headingFragment(title)
if (fragment.length === 0) return { blocks, error: 'verbatim H5 title must be non-empty' }
if (fragments.has(fragment)) {
return { blocks, error: `verbatim H5 title ${JSON.stringify(title)} is duplicated within its context surface` }
return { blocks, error: `verbatim H5 title ${JSON.stringify(title)} is duplicated within its model-context entry` }
}
fragments.add(fragment)
cursor += 1
@@ -224,7 +224,7 @@ function headingFragment(title: string): string {
}
/** A direct stable system-prompt contribution, as named by the README rules. */
function isDirectSystemPromptSurface(title: string): boolean {
function isDirectSystemPromptEntry(title: string): boolean {
return /\bsystem prompt\b/i.test(title)
}
@@ -244,13 +244,13 @@ const failures: Failure[] = []
const packageJsons = globSync('packages/*/*/package.json', { cwd: root }).map(path => path.split(sep).join('/')).sort()
const scannedPackages = new Set(packageJsons.map(path => path.slice(0, -'/package.json'.length)))
let structuredCount = 0
let contextSurfaceCount = 0
let modelContextEntryCount = 0
let omittedSectionCount = 0
let explainedNoneCount = 0
let indirectCount = 0
let verbatimBlockCount = 0
let systemPromptSurfaceCount = 0
let toolSchemaSurfaceCount = 0
let systemPromptEntryCount = 0
let toolSchemaEntryCount = 0
let kvCacheEffectCount = 0
for (const [pkg, reason] of Object.entries(NO_MODEL_EXPERIENCE_SECTION)) {
@@ -270,7 +270,7 @@ for (const [pkg, contract] of Object.entries(SENTENCE_MODEL_EXPERIENCE)) {
failures.push({ path: `${pkg}/README.md`, message: 'sentence allowlist entry does not name a scanned package' })
}
if (contract.reason.trim().length === 0) {
failures.push({ path: `${pkg}/README.md`, message: 'sentence allowlist entry must justify why structured context surfaces are unnecessary' })
failures.push({ path: `${pkg}/README.md`, message: 'sentence allowlist entry must justify why structured model-context entries are unnecessary' })
}
}
@@ -379,47 +379,47 @@ for (const packageJson of packageJsons) {
continue
}
const surfaceStarts = content
const entryStarts = content
.map((line, index) => ({ line, index }))
.filter(entry => /^### \S/.test(entry.line.raw))
if (surfaceStarts.length === 0 || surfaceStarts[0]?.index !== 0) {
failures.push({ path: readme, message: 'must contain one or more complete context-surface blocks' })
if (entryStarts.length === 0 || entryStarts[0]?.index !== 0) {
failures.push({ path: readme, message: 'must contain one or more complete model-context entries' })
continue
}
const surfaces: ContextSurface[] = []
const surfaceFragments = new Set<string>()
let surfaceError = false
for (let surfaceIndex = 0; surfaceIndex < surfaceStarts.length; surfaceIndex += 1) {
const start = surfaceStarts[surfaceIndex] as { line: Line; index: number }
const end = surfaceStarts[surfaceIndex + 1]?.index ?? content.length
const modelContextEntries: ModelExperienceEntry[] = []
const entryFragments = new Set<string>()
let entryError = false
for (let entryIndex = 0; entryIndex < entryStarts.length; entryIndex += 1) {
const start = entryStarts[entryIndex] as { line: Line; index: number }
const end = entryStarts[entryIndex + 1]?.index ?? content.length
const entries = content.slice(start.index, end)
const heading = entries[0] as Line
const title = heading.raw.slice('### '.length)
const fragment = headingFragment(title)
if (fragment.length === 0) {
failures.push({ path: readme, message: `line ${heading.index}: each context surface requires a non-empty H3 heading` })
surfaceError = true
failures.push({ path: readme, message: `line ${heading.index}: each model-context entry requires a non-empty H3 heading` })
entryError = true
break
}
if (surfaceFragments.has(fragment)) {
failures.push({ path: readme, message: `line ${heading.index}: duplicate context-surface link fragment ${JSON.stringify(fragment)}` })
surfaceError = true
if (entryFragments.has(fragment)) {
failures.push({ path: readme, message: `line ${heading.index}: duplicate model-context entry link fragment ${JSON.stringify(fragment)}` })
entryError = true
break
}
const fieldStarts = entries
.map((line, index) => ({ line, index }))
.filter(entry => /^#### \S/.test(entry.line.raw))
if (fieldStarts.length !== FIELD_HEADINGS.length || fieldStarts[0]?.index !== 1) {
failures.push({ path: readme, message: `line ${heading.index}: context surface requires exactly three ordered H4 fields: ${FIELD_HEADINGS.join(', ')}` })
surfaceError = true
failures.push({ path: readme, message: `line ${heading.index}: model-context entry requires exactly three ordered H4 fields: ${FIELD_HEADINGS.join(', ')}` })
entryError = true
break
}
if ((surfaceIndex === 0 && heading.index !== modelHeading.index + 2)
if ((entryIndex === 0 && heading.index !== modelHeading.index + 2)
|| rawLines[heading.index - 2]?.trim().length !== 0
|| fieldStarts[0].line.index !== heading.index + 2) {
failures.push({ path: readme, message: `line ${heading.index}: context-surface heading and first field require one blank line between them` })
surfaceError = true
failures.push({ path: readme, message: `line ${heading.index}: model-context entry heading and first field require one blank line between them` })
entryError = true
break
}
const parsedFields: ParsedField[] = []
@@ -429,7 +429,7 @@ for (const packageJson of packageJsons) {
const expectedHeading = FIELD_HEADINGS[fieldIndex] as string
if (fieldStart.line.raw !== expectedHeading) {
failures.push({ path: readme, message: `line ${fieldStart.line.index}: expected exact field heading ${JSON.stringify(expectedHeading)}, found ${JSON.stringify(fieldStart.line.raw)}` })
surfaceError = true
entryError = true
break
}
const fieldEnd = fieldStarts[fieldIndex + 1]?.index ?? entries.length
@@ -437,42 +437,42 @@ for (const packageJson of packageJsons) {
const value = fieldEntries[1]
if (value === undefined || /^#{1,6} /.test(value.raw) || value.raw.trim().length === 0) {
failures.push({ path: readme, message: `line ${fieldStart.line.index}: ${expectedHeading} requires one non-empty paragraph` })
surfaceError = true
entryError = true
break
}
if (value.index !== fieldStart.line.index + 2) {
failures.push({ path: readme, message: `line ${fieldStart.line.index}: ${expectedHeading} and its paragraph require one blank line between them` })
surfaceError = true
entryError = true
break
}
const unexpected = fieldEntries.slice(2).find(line => !/^##### \S/.test(line.raw))
if (unexpected !== undefined) {
failures.push({ path: readme, message: `line ${unexpected.index}: content after ${expectedHeading} paragraph must be a titled H5 plus \`markdown\` fence owned by that field` })
surfaceError = true
entryError = true
break
}
const nextHeadingLine = fieldStarts[fieldIndex + 1]?.line.index
?? surfaceStarts[surfaceIndex + 1]?.line.index
?? entryStarts[entryIndex + 1]?.line.index
?? nextH2Line
if (rawLines[nextHeadingLine - 2]?.trim().length !== 0) {
failures.push({ path: readme, message: `line ${nextHeadingLine}: Model Experience headings require a preceding blank line` })
surfaceError = true
entryError = true
break
}
const verbatim = validateNestedVerbatim(rawLines.slice(value.index, nextHeadingLine - 1), verbatimFragments)
if (verbatim.error !== undefined) {
failures.push({ path: readme, message: `line ${value.index}: ${verbatim.error}` })
surfaceError = true
entryError = true
break
}
if (fieldEntries.length - 2 !== verbatim.blocks) {
failures.push({ path: readme, message: `line ${value.index}: every nested H5 must own exactly one \`markdown\` fence` })
surfaceError = true
entryError = true
break
}
parsedFields.push({ value, verbatimBlocks: verbatim.blocks })
}
if (surfaceError) break
if (entryError) break
const modelViewField = parsedFields[0] as ParsedField
const tokenEffectField = parsedFields[1] as ParsedField
const kvCacheEffectField = parsedFields[2] as ParsedField
@@ -481,11 +481,11 @@ for (const packageJson of packageJsons) {
const kvCacheEffect = kvCacheEffectField.value
if (/\]\(#[^)]+\)/.test(modelView.raw) || /\]\(#[^)]+\)/.test(tokenEffect.raw) || /\]\(#[^)]+\)/.test(kvCacheEffect.raw)) {
failures.push({ path: readme, message: `line ${heading.index}: Model Experience fields must not link between local subsections; nest the H5 in its owning H4 field` })
surfaceError = true
entryError = true
break
}
surfaceFragments.add(fragment)
surfaces.push({
entryFragments.add(fragment)
modelContextEntries.push({
heading,
modelView,
tokenEffect,
@@ -495,49 +495,49 @@ for (const packageJson of packageJsons) {
verbatimBlocks: parsedFields.reduce((total, field) => total + field.verbatimBlocks, 0),
})
}
if (surfaceError) continue
if (entryError) continue
const promptWithoutVerbatim = surfaces.find(surface => isDirectSystemPromptSurface(surface.title)
&& surface.modelViewVerbatimBlocks === 0)
const promptWithoutVerbatim = modelContextEntries.find(entry => isDirectSystemPromptEntry(entry.title)
&& entry.modelViewVerbatimBlocks === 0)
if (promptWithoutVerbatim !== undefined) {
failures.push({ path: readme, message: `line ${promptWithoutVerbatim.heading.index}: system-prompt surface must contain a titled H5 plus verbatim \`markdown\` block under ${MODEL_VIEW_HEADING}` })
failures.push({ path: readme, message: `line ${promptWithoutVerbatim.heading.index}: system-prompt entry must contain a titled H5 plus verbatim \`markdown\` block under ${MODEL_VIEW_HEADING}` })
continue
}
const hasConcreteLiteral = surfaces.some(surface => surface.verbatimBlocks > 0
|| surface.modelView.raw.includes('`')
|| surface.tokenEffect.raw.includes('`')
|| toolCatalogLinkFragments(surface.modelView.raw).length > 0)
const hasConcreteLiteral = modelContextEntries.some(entry => entry.verbatimBlocks > 0
|| entry.modelView.raw.includes('`')
|| entry.tokenEffect.raw.includes('`')
|| toolCatalogLinkFragments(entry.modelView.raw).length > 0)
if (!hasConcreteLiteral) {
failures.push({ path: readme, message: 'structured Model Experience must ground at least one surface with inline code, a nested `markdown` block, or an anchored tool-catalog link' })
failures.push({ path: readme, message: 'structured Model Experience must ground at least one entry with inline code, a nested `markdown` block, or an anchored tool-catalog link' })
continue
}
let catalogError = false
for (const surface of surfaces) {
if (!/\bschemas?\b/i.test(surface.title)) continue
const fragments = toolCatalogLinkFragments(surface.modelView.raw)
for (const entry of modelContextEntries) {
if (!/\bschemas?\b/i.test(entry.title)) continue
const fragments = toolCatalogLinkFragments(entry.modelView.raw)
if (fragments.length === 0) {
failures.push({ path: readme, message: `line ${surface.heading.index}: tool-schema surface must link an anchored section of ../../../docs/tool-catalog.md` })
failures.push({ path: readme, message: `line ${entry.heading.index}: tool-schema entry must link an anchored section of ../../../docs/tool-catalog.md` })
catalogError = true
break
}
const invalid = fragments.find(fragment => !toolCatalogFragments.has(fragment))
if (invalid !== undefined) {
failures.push({ path: readme, message: `line ${surface.modelView.index}: tool-catalog link fragment ${JSON.stringify(invalid)} does not name an H2 section` })
failures.push({ path: readme, message: `line ${entry.modelView.index}: tool-catalog link fragment ${JSON.stringify(invalid)} does not name an H2 section` })
catalogError = true
break
}
}
if (catalogError) continue
verbatimBlockCount += surfaces.reduce((total, surface) => total + surface.verbatimBlocks, 0)
contextSurfaceCount += surfaces.length
systemPromptSurfaceCount += surfaces.filter(surface => isDirectSystemPromptSurface(surface.title)).length
toolSchemaSurfaceCount += surfaces.filter(surface => /\bschemas?\b/i.test(surface.title)).length
kvCacheEffectCount += surfaces.length
verbatimBlockCount += modelContextEntries.reduce((total, entry) => total + entry.verbatimBlocks, 0)
modelContextEntryCount += modelContextEntries.length
systemPromptEntryCount += modelContextEntries.filter(entry => isDirectSystemPromptEntry(entry.title)).length
toolSchemaEntryCount += modelContextEntries.filter(entry => /\bschemas?\b/i.test(entry.title)).length
kvCacheEffectCount += modelContextEntries.length
structuredCount += 1
}
if (failures.length === 0) {
console.log(`verify-package-readme-model-experience: ${packageJsons.length} README(s) checked (${omittedSectionCount} audited omissions, ${structuredCount} structured, ${contextSurfaceCount} context surfaces, ${kvCacheEffectCount} KV-cache fields, ${systemPromptSurfaceCount} fenced system-prompt surfaces, ${toolSchemaSurfaceCount} catalog-linked tool-schema surfaces, ${explainedNoneCount} explained none, ${indirectCount} indirect, ${verbatimBlockCount} verbatim markdown blocks), all conform.`)
console.log(`verify-package-readme-model-experience: ${packageJsons.length} README(s) checked (${omittedSectionCount} audited omissions, ${structuredCount} structured, ${modelContextEntryCount} model-context entries, ${kvCacheEffectCount} KV-cache fields, ${systemPromptEntryCount} fenced system-prompt entries, ${toolSchemaEntryCount} catalog-linked tool-schema entries, ${explainedNoneCount} explained none, ${indirectCount} indirect, ${verbatimBlockCount} verbatim markdown blocks), all conform.`)
process.exit(0)
}