From e5721393969d581e3d7596544e99f94f4ee11798 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 19 Jul 2026 14:23:59 +0800 Subject: [PATCH] docs: shorten public API fence marker --- docs/AGENTS.md | 2 +- docs/core-data-structures/llm-streaming.md | 4 +-- docs/core-data-structures/session.md | 2 +- docs/development.i18n.yaml | 4 +-- docs/development.md | 2 +- docs/development.zh.md | 2 +- ...2026-06-20-core-data-structures-catalog.md | 2 +- scripts/doc-typecheck.ts | 4 +-- scripts/type-equiv.manifest.json | 2 +- scripts/verify-type-equiv.ts | 27 ++++++++++--------- 10 files changed, 27 insertions(+), 24 deletions(-) diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 27db1d88d7..f7635c659b 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -27,7 +27,7 @@ Placement: bugs → postmortems; rationale → RFCs; procedures → cookbooks; t - **Document current state, not change history.** Avoid "previously/now/no longer", PRs, commits, and stack positions in durable prose; name the live mechanism. Put change stories in commits, PRs, RFCs, or postmortems. - **Write an RFC in the same PR for decisions a maintainer may reasonably revisit.** Mechanical or self-evident changes need none ([when to write one](rfc/README.md)). - **One physical line per paragraph** (`verify-md-wrap`): use editor soft-wrap. Code blocks, tables, and list structure keep their formatting; code comments stay under the linter's column limit. -- **Fenced `ts` blocks must compile** (`doc-typecheck`); a pasted type declaration and its original JSDoc use ` ```ts type-equiv `, while a body-stripped public class declaration uses its `public-api` variant; register either in the manifest so neither can drift ([mechanics](development.md#documenting-types-verbatim-ts-type-equiv)). +- **Fenced `ts` blocks must compile** (`doc-typecheck`); a pasted type declaration and its original JSDoc use ` ```ts type-equiv `, while a body-stripped public class declaration uses ` ```ts public-api `; register either in the manifest so neither can drift ([mechanics](development.md#documenting-types-verbatim-ts-type-equiv)). - **The [core-data-structures catalog](core-data-structures/core.md) updates in the same change** that reshapes a documented type. `verify-type-equiv` catches drifted pastes, not never-documented new types ([what counts as core](core-data-structures/core.md#what-counts-as-core)). - **Bilingual pairs update together**: editing either side obligates the counterpart and a re-record in the same change ([i18n contract](i18n/README.md)). - **Comments and JSDoc state complete contracts, not reasoning transcripts.** Preserve behavior, conditions, timing, modality, exceptions, consequences, and non-obvious orientation; delete implementation narration, test walkthroughs, review analysis, and code restatement. Keep the local contract and link to its owning rationale. Use [dsh-prose-standard](../.agents/skills/dsh-prose-standard/SKILL.md) for required coverage, decision rules, and examples. diff --git a/docs/core-data-structures/llm-streaming.md b/docs/core-data-structures/llm-streaming.md index b27c21a69c..ce8c1bf074 100644 --- a/docs/core-data-structures/llm-streaming.md +++ b/docs/core-data-structures/llm-streaming.md @@ -91,7 +91,7 @@ interface TokenUsage { `BlockAssembler` ([`packages/llm/llm/src/assembler.ts`](../../packages/llm/llm/src/assembler.ts)) is the single shared implementation that folds a `StreamChunk` stream back into `ContentBlock`s, usage, finish reason, and replay state. The loop logs the raw chunks while feeding the same chunks through an assembler, then stores the assembled assistant content with its provider/model provenance. A consumer that needs the assembled result without re-implementing the fold uses this. -```ts type-equiv public-api +```ts public-api /** * Incrementally assembles raw {@link StreamChunk}s into complete * {@link ContentBlock}s and a final assistant {@link Message}. @@ -133,7 +133,7 @@ declare class BlockAssembler { `LlmAdapter` is the provider seam: subclass, implement `stream()`, and register one adapter instance with `ctx.llm.registerAdapter(providers, adapter)`. `GenerateOptions.provider` selects the registered adapter; `GenerateOptions.model` is passed to that adapter and need not be registered at lifecycle start. Duplicate provider routes fail atomically. Optional `providerInfo()` and asynchronous `listModels()` methods feed `LlmService.listProviders()` / `listModels()` with detached selector metadata. That catalog is advisory rather than a request whitelist: the adapter remains authoritative and may accept unlisted model ids. Adapter lookup happens at the terminal continuation of the `llm/stream` waterfall, so a listener may short-circuit the call or route a mutable one-shot request before lookup. The `block-start` / `block-end` `index` correlation and the assembler together mean an adapter only has to emit well-formed chunks — block reassembly is not each adapter's problem. The consumer surface (`ctx.llm.stream()`) and the `llm/stream` waterfall are described in [architecture.md § Content blocks and streaming](../architecture.md#content-blocks-and-streaming-dsh-llm). -```ts type-equiv public-api +```ts public-api /** * Provider-wire adapter for the harness message and stream vocabulary. Register implementations * with `ctx.llm.registerAdapter(providers, adapter)`. Every provider HTTP request must include diff --git a/docs/core-data-structures/session.md b/docs/core-data-structures/session.md index 1ece65c1c3..3668191fb2 100644 --- a/docs/core-data-structures/session.md +++ b/docs/core-data-structures/session.md @@ -309,7 +309,7 @@ interface SurfaceFoldResult { The body-stripped declaration keeps the plain class's public constructor, state accessors, append boundary, and history projections synchronized with source. Store operations remain in the generated [`ctx.sessions` service catalog](../cordis-catalog/services.md#ctxsessions--sessionstore). -```ts type-equiv public-api +```ts public-api /** * An event-sourced session: an append-only log of {@link SessionEvent}s. * diff --git a/docs/development.i18n.yaml b/docs/development.i18n.yaml index b0ce75ca4c..d7f82d7e92 100644 --- a/docs/development.i18n.yaml +++ b/docs/development.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -development.md: 6938e922c7937f0e6ef6c6e611752827e6a06f82 -development.zh.md: dfb302f23a3f09ea97684b75fcfa7c7dc2e9ade5 +development.md: a3268164cd06fb8bf66f52391bc42c2dc3ca9396 +development.zh.md: f8f29c64aa6e49e3ed6d7ef12df2a4911c9182b0 diff --git a/docs/development.md b/docs/development.md index 6938e922c7..a3268164cd 100644 --- a/docs/development.md +++ b/docs/development.md @@ -151,7 +151,7 @@ The [core data structures](core-data-structures/core.md) docs paste source-equiv { "doc": "docs/core-data-structures/session.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" } ``` -`pnpm run verify-type-equiv` (part of `doc-sync`) then extracts that symbol's declaration and attached JSDoc from source via the TypeScript parser and asserts the block matches both. For a class whose implementation bodies do not belong in the catalog, use ` ```ts type-equiv public-api ` and set `"projection": "public-api"`; the checked projection retains the public fields, constructor, accessors, methods, and original class/member JSDoc while omitting bodies and private or protected members. Comparison ignores whitespace and non-JSDoc comments but requires every original JSDoc comment, including member documentation, so readers see the source contract beside the exact shape. The gate also enforces a 1:1 correspondence by document, symbol, and projection, so a block can't go silently unchecked and a stale entry can't linger. `doc-typecheck` skips both variants (they aren't standalone-compilable) and excludes them from its opt-out ratio. When you change a documented declaration or its JSDoc, the gate fails until you update the paste; when you add or remove a block, update the manifest in the same change. +`pnpm run verify-type-equiv` (part of `doc-sync`) then extracts that symbol's declaration and attached JSDoc from source via the TypeScript parser and asserts the block matches both. For a class whose implementation bodies do not belong in the catalog, use ` ```ts public-api ` and set `"projection": "public-api"`; the checked projection retains the public fields, constructor, accessors, methods, and original class/member JSDoc while omitting bodies and private or protected members. Comparison ignores whitespace and non-JSDoc comments but requires every original JSDoc comment, including member documentation, so readers see the source contract beside the exact shape. The gate also enforces a 1:1 correspondence by document, symbol, and projection, so a block can't go silently unchecked and a stale entry can't linger. `doc-typecheck` skips both fence kinds (they aren't standalone-compilable) and excludes them from its opt-out ratio. When you change a documented declaration or its JSDoc, the gate fails until you update the paste; when you add or remove a block, update the manifest in the same change. ## Architecture context diff --git a/docs/development.zh.md b/docs/development.zh.md index dfb302f23a..f8f29c64aa 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -151,7 +151,7 @@ pnpm run demo:acp { "doc": "docs/core-data-structures/session.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" } ``` -`pnpm run verify-type-equiv`(`doc-sync` 的一环)随后通过 TypeScript 解析器从源码提取该符号的声明及其附带的 JSDoc,并断言代码块同时匹配两者。对于不应把实现体写进目录的类,请使用 ` ```ts type-equiv public-api ` 并设置 `"projection": "public-api"`;门禁检查的投影会保留公共字段、构造函数、访问器、方法以及类和成员的原始 JSDoc,同时省略实现体和私有或受保护成员。比对会忽略空白和非 JSDoc 注释,但要求保留每条原始 JSDoc(包括成员文档),让读者同时看到源码契约和确切形状。该门禁还按文档、符号和投影强制 1:1 对应,因此不会有块被静默漏检,也不会有陈旧条目滞留。`doc-typecheck` 跳过两种变体(它们不能独立编译),并将其排除在 opt-out 比例之外。当你改动一个已记录的类型声明或其 JSDoc 时,门禁会失败直到你更新粘贴内容;当你增删一个块时,请在同一个变更里更新 manifest。 +`pnpm run verify-type-equiv`(`doc-sync` 的一环)随后通过 TypeScript 解析器从源码提取该符号的声明及其附带的 JSDoc,并断言代码块同时匹配两者。对于不应把实现体写进目录的类,请使用 ` ```ts public-api ` 并设置 `"projection": "public-api"`;门禁检查的投影会保留公共字段、构造函数、访问器、方法以及类和成员的原始 JSDoc,同时省略实现体和私有或受保护成员。比对会忽略空白和非 JSDoc 注释,但要求保留每条原始 JSDoc(包括成员文档),让读者同时看到源码契约和确切形状。该门禁还按文档、符号和投影强制 1:1 对应,因此不会有块被静默漏检,也不会有陈旧条目滞留。`doc-typecheck` 跳过两种围栏(它们不能独立编译),并将其排除在 opt-out 比例之外。当你改动一个已记录的类型声明或其 JSDoc 时,门禁会失败直到你更新粘贴内容;当你增删一个块时,请在同一个变更里更新 manifest。 ## 架构上下文 diff --git a/docs/rfc/implemented/process/2026-06-20-core-data-structures-catalog.md b/docs/rfc/implemented/process/2026-06-20-core-data-structures-catalog.md index 974a19a621..47dc370d22 100644 --- a/docs/rfc/implemented/process/2026-06-20-core-data-structures-catalog.md +++ b/docs/rfc/implemented/process/2026-06-20-core-data-structures-catalog.md @@ -29,7 +29,7 @@ The rule that settled the remaining cases: ***the type you write, hold, or recei The durability requirement was specific: the doc shows the **literal** current type declaration and original JSDoc (so a reader sees the real shape and source contract, not a paraphrase) **and** is mechanically guaranteed to match source. The repo already compiles fenced ` ```ts ` blocks (`doc-typecheck`), but a real typechecked block needs import noise and proves only *assignability* — a renamed field or changed JSDoc can pass. So: -- Complete type declarations and their JSDoc are pasted verbatim into a dedicated ` ```ts type-equiv ` fence. A `public-api` variant carries the source-equivalent ambient projection for a class whose implementation bodies do not belong in the catalog. `doc-typecheck` recognizes both and skips them (the bare declarations are not standalone-compilable), and **excludes them from the opt-out ratio** — they are a separately-checked category, not unchecked sketches. +- Complete type declarations and their JSDoc are pasted verbatim into a dedicated ` ```ts type-equiv ` fence. A concise ` ```ts public-api ` fence carries the source-equivalent ambient projection for a class whose implementation bodies do not belong in the catalog. `doc-typecheck` recognizes both and skips them (the bare declarations are not standalone-compilable), and **excludes them from the opt-out ratio** — they are a separately-checked category, not unchecked sketches. - A new `scripts/verify-type-equiv.ts` extracts each block via the TypeScript parser and asserts that its declaration structure and every JSDoc comment match the declared symbol, ignoring only formatting whitespace and non-JSDoc comments. Ordinary blocks retain the complete declaration. A `public-api` projection retains a class's public fields, constructor, accessors, and methods with their original JSDoc while removing implementation bodies and private or protected members. This is chosen over a compiled `_Check` assertion because source names and documentation identity, not assignability, are the properties the catalog preserves. - Provenance lives in a central `scripts/type-equiv.manifest.json` (`{ doc, symbol, source }` entries), **not** in directive comments in the prose. The script enforces a **1:1 correspondence**: every type-equiv block has exactly one manifest entry and vice versa, so a block can never be silently unchecked and an entry can never rot. - Wired into `doc-sync`, so it runs in the same lefthook pre-push and CI paths as the other doc gates. diff --git a/scripts/doc-typecheck.ts b/scripts/doc-typecheck.ts index c4d6c04a35..34af82b48b 100644 --- a/scripts/doc-typecheck.ts +++ b/scripts/doc-typecheck.ts @@ -1,6 +1,6 @@ /** * Typecheck Markdown `ts` fences against the workspace API. `ignore-check` fences are reported as - * opt-outs; generated catalog fragments and `type-equiv` blocks are skipped here because their + * opt-outs; generated catalog fragments and source-equivalence blocks are skipped here because their * owning gates verify them. A build-coordinated mode consumes existing declarations without emit. */ @@ -33,7 +33,7 @@ const KIND_BY_INFO: Record = { 'ts': 'check', 'ts ignore-check': 'ignore', 'ts type-equiv': 'type-equiv', - 'ts type-equiv public-api': 'type-equiv', + 'ts public-api': 'type-equiv', 'ts cordis-catalog': 'cordis-catalog', 'ts persistence-catalog': 'persistence-catalog', 'ts config-catalog': 'config-catalog', diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index 09888916d0..831c45e610 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -1,5 +1,5 @@ { - "comment": "Maps each ` ```ts type-equiv ` block (by doc + declared symbol + projection) to the source declaration and original JSDoc it must match. Omit projection for the complete declaration; use public-api with a ` ```ts type-equiv public-api ` block for a body-stripped public class declaration. verify-type-equiv.ts enforces a 1:1 correspondence: every type-equiv block has exactly one entry here, and every entry resolves to exactly one block. Add an entry when you add a type-equiv block; remove it when you remove the block.", + "comment": "Maps each ` ```ts type-equiv ` or ` ```ts public-api ` block (by doc + declared symbol + projection) to the source declaration and original JSDoc it must match. Omit projection for the complete declaration; use public-api with a ` ```ts public-api ` block for a body-stripped public class declaration. verify-type-equiv.ts enforces a 1:1 correspondence: every source-equivalence block has exactly one entry here, and every entry resolves to exactly one block. Add an entry when you add a source-equivalence block; remove it when you remove the block.", "entries": [ { "doc": "docs/core-data-structures/core.md", "symbol": "Branded", "source": "packages/util/brand/src/index.ts" }, { "doc": "docs/core-data-structures/core.md", "symbol": "ContentBlockMap", "source": "packages/llm/llm/src/types.ts" }, diff --git a/scripts/verify-type-equiv.ts b/scripts/verify-type-equiv.ts index 58affaffca..d703536f17 100644 --- a/scripts/verify-type-equiv.ts +++ b/scripts/verify-type-equiv.ts @@ -1,10 +1,10 @@ /** - * Verify every `ts type-equiv` block against the source symbol named by the - * manifest. Ordinary entries preserve the complete declaration; `public-api` - * entries preserve a class's body-stripped public declaration. Blocks and - * entries have a one-to-one relationship; comparison ignores whitespace and - * non-JSDoc comments but preserves declaration structure and every original - * JSDoc comment. + * Verify every `ts type-equiv` and `ts public-api` block against the source + * symbol named by the manifest. Ordinary entries preserve the complete + * declaration; `public-api` entries preserve a class's body-stripped public + * declaration. Blocks and entries have a one-to-one relationship; comparison + * ignores whitespace and non-JSDoc comments but preserves declaration + * structure and every original JSDoc comment. */ import { globSync, readFileSync, existsSync } from 'node:fs' @@ -16,9 +16,9 @@ const root = resolve(import.meta.dirname, '..') /** Scan doc-typecheck's full Markdown scope so unmanifested blocks also fail. */ const MARKDOWN_GLOBS = ['README.md', 'docs/**/*.md', 'packages/*/*.md', 'packages/*/*/*.md', 'website/zh-CN/**/*.md'] -/** One manifest entry: a documented type-equiv block and its source symbol. */ +/** One manifest entry: a source-equivalence block and its source symbol. */ interface ManifestEntry { - /** Doc file (repo-relative) containing the ` ```ts type-equiv ` block. */ + /** Doc file (repo-relative) containing the source-equivalence block. */ doc: string /** The declared symbol the block must match (e.g. `SessionEvent`). */ symbol: string @@ -28,7 +28,7 @@ interface ManifestEntry { projection?: 'public-api' } -/** One extracted ` ```ts type-equiv ` block. */ +/** One extracted ` ```ts type-equiv ` or ` ```ts public-api ` block. */ interface EquivBlock { doc: string /** 1-based line of the opening fence (for diagnostics). */ @@ -64,7 +64,7 @@ function stripExport(code: string): string { return code.replace(/^export\s+(default\s+)?/, '') } -/** Parse the declared symbol name from a type-equiv block body. */ +/** Parse the declared symbol name from a source-equivalence block body. */ function blockSymbol(code: string): string | null { const sf = ts.createSourceFile('type-equiv.ts', code, ts.ScriptTarget.Latest, /* setParentNodes */ false, ts.ScriptKind.TS) for (const stmt of sf.statements) { @@ -76,7 +76,7 @@ function blockSymbol(code: string): string | null { return null } -/** Extract every ` ```ts type-equiv ` block from one Markdown file. */ +/** Extract every source-equivalence block from one Markdown file. */ function extractEquivBlocks(docRel: string): EquivBlock[] { const text = readFileSync(resolve(root, docRel), 'utf8') const lines = text.split('\n') @@ -107,8 +107,11 @@ function extractEquivBlocks(docRel: string): EquivBlock[] { continue } const info = (fence[2] ?? '').trim() + if (info === 'ts type-equiv public-api') { + throw new Error(`verify-type-equiv: ${docRel}:${i + 1} — use the concise \`ts public-api\` fence`) + } if (info === 'ts type-equiv') open = { line: i + 1, body: [] } - if (info === 'ts type-equiv public-api') open = { line: i + 1, body: [], projection: 'public-api' } + if (info === 'ts public-api') open = { line: i + 1, body: [], projection: 'public-api' } } if (open) throw new Error(`verify-type-equiv: ${docRel}:${open.line} — unterminated type-equiv block`) return blocks