docs: deduplicate paired code-block checks

This commit is contained in:
Tianyi Cui
2026-07-26 02:52:48 +08:00
parent 633ceaf41b
commit f2cf29bd04
18 changed files with 175 additions and 971 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # 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: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write # pnpm run verify-translation-pairing --write
2026-06-20-core-data-structures-catalog.md: d7e9d3d9b14fe723e3396c8167fe714b7613e2b5 2026-06-20-core-data-structures-catalog.md: ef100f96b06c454cfd1ec092cc7fd23e712bdf7a
2026-06-20-core-data-structures-catalog.zh.md: 8d2f16a46216cba8df0539be0abd2f1ad840eec3 2026-06-20-core-data-structures-catalog.zh.md: 4ace2b8c8a6b08e7721c1df8003ccfbdb128daf1

View File

@@ -33,7 +33,7 @@ The durability requirement was specific: the doc shows the **literal** current t
- 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. - 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. - 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. - 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** between each primary type-equiv block and one manifest entry, so a block can never be silently unchecked and an entry can never rot. A paired `.zh.md` block reuses the unsuffixed sibling's entry only when the complete tracked fence sequence matches in order, kind, and byte-exact body; otherwise the gate checks it independently, finds no manifest entry, and fails.
- Wired into `doc-sync`, so relevant documentation changes run it locally and CI runs it with the other documentation checks. - Wired into `doc-sync`, so relevant documentation changes run it locally and CI runs it with the other documentation checks.
### Maintenance is the author's job, with a gate backstop ### Maintenance is the author's job, with a gate backstop

View File

@@ -33,7 +33,7 @@ Status: implemented
- 完整的类型声明及其 JSDoc 会逐字粘贴到专用的 ` ```ts type-equiv ` 围栏中。简洁的 ` ```ts public-api ` 围栏承载与源码等价的类环境投影,用于实现体不应进入目录的类。`doc-typecheck` 会识别并跳过这两种围栏(裸声明无法独立编译),并且**将它们排除在 opt-out 比例之外**——它们是单独受检的类别,而不是未经检查的草图。 - 完整的类型声明及其 JSDoc 会逐字粘贴到专用的 ` ```ts type-equiv ` 围栏中。简洁的 ` ```ts public-api ` 围栏承载与源码等价的类环境投影,用于实现体不应进入目录的类。`doc-typecheck` 会识别并跳过这两种围栏(裸声明无法独立编译),并且**将它们排除在 opt-out 比例之外**——它们是单独受检的类别,而不是未经检查的草图。
- 新增的 `scripts/verify-type-equiv.ts` 通过 TypeScript 解析器提取每个块,并断言其声明结构和每条 JSDoc 注释都与所声明的符号匹配,只忽略格式空白和非 JSDoc 注释。普通块保留完整声明。`public-api` 投影保留类的公共字段、构造函数、访问器和方法及其原始 JSDoc同时移除实现体以及私有或受保护成员。之所以选择它而非编译式 `_Check` 断言,是因为目录所保留的是源码名称与文档一致性,而不是可赋值性。 - 新增的 `scripts/verify-type-equiv.ts` 通过 TypeScript 解析器提取每个块,并断言其声明结构和每条 JSDoc 注释都与所声明的符号匹配,只忽略格式空白和非 JSDoc 注释。普通块保留完整声明。`public-api` 投影保留类的公共字段、构造函数、访问器和方法及其原始 JSDoc同时移除实现体以及私有或受保护成员。之所以选择它而非编译式 `_Check` 断言,是因为目录所保留的是源码名称与文档一致性,而不是可赋值性。
- 来源信息存放在集中的 `scripts/type-equiv.manifest.json``{ doc, symbol, source }` 条目)中,**而非**行文中的指令注释。脚本强制执行 **1:1 对应**每个 type-equiv 块恰好有一条 manifest 条目,反之亦然;因此一个块永远不会被静默漏检,一条条目也永远不会腐烂。 - 来源信息存放在集中的 `scripts/type-equiv.manifest.json``{ doc, symbol, source }` 条目)中,**而非**行文中的指令注释。脚本每个 type-equiv 块一条 manifest 条目之间强制执行 **1:1 对应**,因此一个块永远不会被静默漏检,一条条目也永远不会腐烂。只有当配对 `.zh.md` 块的完整受跟踪围栏序列在顺序、类型和按字节精确的正文上均与无后缀兄弟文件匹配时,才会复用后者的条目;否则门禁会独立检查该块,发现没有 manifest 条目后失败。
- 接入 `doc-sync`因此相关文档变更会在本地运行它CI 也会与其他文档检查一起运行它。 - 接入 `doc-sync`因此相关文档变更会在本地运行它CI 也会与其他文档检查一起运行它。
### 维护是作者的职责,门禁作为兜底 ### 维护是作者的职责,门禁作为兜底

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # 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: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write # pnpm run verify-translation-pairing --write
2026-07-02-bilingual-docs-and-pairing-gate.md: 08e149ccc2342695d6dae4f1845896def6bf4388 2026-07-02-bilingual-docs-and-pairing-gate.md: ece3ccc183893a85335a36eb9b00cb42d32a1a37
2026-07-02-bilingual-docs-and-pairing-gate.zh.md: 91f25f33c20337ea688257829552795085e0a8c0 2026-07-02-bilingual-docs-and-pairing-gate.zh.md: dc8c648cc4ce9dd7739383c620ee2b1c3794f2ee

View File

@@ -12,7 +12,7 @@ This repo's README and docs tree are read by people and agents inside and outsid
- **Paired sibling files with equal authority.** A documentation pair is three sibling files: English `foo.md`, Chinese `foo.zh.md`, and a consistency record `foo.i18n.yaml`. Neither language is canonical — a document may be authored and reviewed Chinese-first and translated to English afterwards, or the reverse; what binds the pair is that both sides must say the same thing, and pairs merge whole (both languages plus the record, never one alone). Policy: [docs/i18n/README.md](../../../../docs/i18n/README.md); translation rules: [docs/i18n/translation-rules.md](../../../../docs/i18n/translation-rules.md); terminology source of truth: [docs/i18n/terminology.md](../../../../docs/i18n/terminology.md). - **Paired sibling files with equal authority.** A documentation pair is three sibling files: English `foo.md`, Chinese `foo.zh.md`, and a consistency record `foo.i18n.yaml`. Neither language is canonical — a document may be authored and reviewed Chinese-first and translated to English afterwards, or the reverse; what binds the pair is that both sides must say the same thing, and pairs merge whole (both languages plus the record, never one alone). Policy: [docs/i18n/README.md](../../../../docs/i18n/README.md); translation rules: [docs/i18n/translation-rules.md](../../../../docs/i18n/translation-rules.md); terminology source of truth: [docs/i18n/terminology.md](../../../../docs/i18n/terminology.md).
- **A sidecar record of both blob hashes makes consistency checkable.** `foo.i18n.yaml` holds the full git blob hash of each side as of the last confirmed-consistent state. An edit to either side without re-confirming the pair is then mechanically detectable as a pure content comparison — no history lookup — and the hashes are computable for files edited in the same PR, which a commit-hash record is not. Re-recording (`verify-translation-pairing --write`) produces a reviewable yaml diff: confirming consistency is an explicit, visible act in the PR. - **A sidecar record of both blob hashes makes consistency checkable.** `foo.i18n.yaml` holds the full git blob hash of each side as of the last confirmed-consistent state. An edit to either side without re-confirming the pair is then mechanically detectable as a pure content comparison — no history lookup — and the hashes are computable for files edited in the same PR, which a commit-hash record is not. Re-recording (`verify-translation-pairing --write`) produces a reviewable yaml diff: confirming consistency is an explicit, visible act in the PR.
- **`verify-translation-pairing` joins `doc-sync`.** The gate ([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts)) enforces: sources selected explicitly, by document class, or by the manifest's `requiredSince` cutoff have complete pairs; every existing pair is complete (all three files) and consistent (both hashes match, switcher links both ways, structural signatures identical); and excluded generated, instruction, or bilingual-by-construction files stay unpaired. The `requiredClasses` set in [scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) closes a translated class for all present and future files instead of relying on an enumerated snapshot. - **`verify-translation-pairing` joins `doc-sync`.** The gate ([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts)) enforces: sources selected explicitly, by document class, or by the manifest's `requiredSince` cutoff have complete pairs; every existing pair is complete (all three files) and consistent (both hashes match, switcher links both ways, structural signatures identical); and excluded generated, instruction, or bilingual-by-construction files stay unpaired. The `requiredClasses` set in [scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) closes a translated class for all present and future files instead of relying on an enumerated snapshot. Source-oriented code gates consume a `.zh.md` fence sequence as a derivative only when its unsuffixed sibling has the same tracked fences in the same order with byte-identical bodies; an incomplete, reordered, reclassified, or changed sequence stays independent, so the owning code gate or pairing gate reports the mismatch.
- **The enforcement frontier advances in coherent review batches, then closes by class.** Explicit `required` entries admit related files while their back-catalog is still being reviewed; after that catalog is complete, its `non-readme` or `readme` class enters `requiredClasses` and can no longer grow a backlog. A site-published pair uses `pairedPages()` so the root locale projects `.zh.md` and `/en/` projects `.md`; creating a counterpart alone does not publish it. - **The enforcement frontier advances in coherent review batches, then closes by class.** Explicit `required` entries admit related files while their back-catalog is still being reviewed; after that catalog is complete, its `non-readme` or `readme` class enters `requiredClasses` and can no longer grow a backlog. A site-published pair uses `pairedPages()` so the root locale projects `.zh.md` and `/en/` projects `.md`; creating a counterpart alone does not publish it.
- **Pairing records are metadata, not Cordis Loader configuration.** Cordis configuration discovery accepts actual `.cordis.yml` and `.cordis.yaml` files while excluding `*.i18n.yaml`, even when the document name contains `cordis`. This preserves validation of executable Loader entries without parsing translation hashes as configuration. - **Pairing records are metadata, not Cordis Loader configuration.** Cordis configuration discovery accepts actual `.cordis.yml` and `.cordis.yaml` files while excluding `*.i18n.yaml`, even when the document name contains `cordis`. This preserves validation of executable Loader entries without parsing translation hashes as configuration.
- **Translation is agent work with human review.** The committed workflow is [.agents/skills/dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md), following the same pattern as [dsh-code-review](../../../skills/dsh-code-review/SKILL.md): the skill carries the workflow and defers to the docs as sources of truth. The skill directs the orchestrating agent to delegate translation writing to a subagent. - **Translation is agent work with human review.** The committed workflow is [.agents/skills/dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md), following the same pattern as [dsh-code-review](../../../skills/dsh-code-review/SKILL.md): the skill carries the workflow and defers to the docs as sources of truth. The skill directs the orchestrating agent to delegate translation writing to a subagent.

View File

@@ -12,7 +12,7 @@ Status: implemented
- **配对兄弟文件,两种语言同权。** 一对文档由三个兄弟文件组成:英文 `foo.md`、中文 `foo.zh.md`,以及一份一致性记录 `foo.i18n.yaml`。没有哪种语言是正典:一篇文档可以先用中文撰写和评审、之后再译成英文,反之亦可;约束配对的是:两侧必须表达相同的内容,且配对整体合并(两种语言加记录,绝不单独落一侧)。政策见 [docs/i18n/README.md](../../../../docs/i18n/README.md);翻译规则见 [docs/i18n/translation-rules.md](../../../../docs/i18n/translation-rules.md);术语真源见 [docs/i18n/terminology.md](../../../../docs/i18n/terminology.md)。 - **配对兄弟文件,两种语言同权。** 一对文档由三个兄弟文件组成:英文 `foo.md`、中文 `foo.zh.md`,以及一份一致性记录 `foo.i18n.yaml`。没有哪种语言是正典:一篇文档可以先用中文撰写和评审、之后再译成英文,反之亦可;约束配对的是:两侧必须表达相同的内容,且配对整体合并(两种语言加记录,绝不单独落一侧)。政策见 [docs/i18n/README.md](../../../../docs/i18n/README.md);翻译规则见 [docs/i18n/translation-rules.md](../../../../docs/i18n/translation-rules.md);术语真源见 [docs/i18n/terminology.md](../../../../docs/i18n/terminology.md)。
- **伴随记录保存两侧 blob hash使一致性可检查。** `foo.i18n.yaml` 保存两侧文件在上一次确认一致时各自的完整 git blob hash。此后修改了任一侧而未重新确认配对都能被机械检测出来纯内容比较无需查询历史而且同一个 PRPull Request内改动的文件也能计算出 hashcommit hash 式的记录做不到这一点。重新记录(`verify-translation-pairing --write`)会产生一份可评审的 yaml diff确认一致在 PR 中是一个显式、可见的动作。 - **伴随记录保存两侧 blob hash使一致性可检查。** `foo.i18n.yaml` 保存两侧文件在上一次确认一致时各自的完整 git blob hash。此后修改了任一侧而未重新确认配对都能被机械检测出来纯内容比较无需查询历史而且同一个 PRPull Request内改动的文件也能计算出 hashcommit hash 式的记录做不到这一点。重新记录(`verify-translation-pairing --write`)会产生一份可评审的 yaml diff确认一致在 PR 中是一个显式、可见的动作。
- **`verify-translation-pairing` 加入 `doc-sync`。** 门禁([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts))强制执行以下规则:通过显式指定、文档类别或 manifest元数据清单`requiredSince` 分界日期选中的源文档必须有完整配对;任何已存在的配对必须完整(三个文件齐全)且一致(两个 hash 匹配、切换行双向互链、结构签名一致);被排除的生成文档、指令文档或本身即双语的文档不得配对。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 中的 `requiredClasses` 集合会将已完成翻译的类别纳入强制范围,对其当前及今后所有文件强制执行契约,而不再依赖一份枚举式快照。 - **`verify-translation-pairing` 加入 `doc-sync`。** 门禁([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts))强制执行以下规则:通过显式指定、文档类别或 manifest元数据清单`requiredSince` 分界日期选中的源文档必须有完整配对;任何已存在的配对必须完整(三个文件齐全)且一致(两个 hash 匹配、切换行双向互链、结构签名一致);被排除的生成文档、指令文档或本身即双语的文档不得配对。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 中的 `requiredClasses` 集合会将已完成翻译的类别纳入强制范围,对其当前及今后所有文件强制执行契约,而不再依赖一份枚举式快照。只有当 `.zh.md` 围栏序列与其无后缀兄弟文件拥有顺序相同、正文按字节一致的同一组受跟踪围栏时,面向源码的代码门禁才会将其作为派生内容消费;不完整、顺序变更、重分类或已改动的序列仍会独立受检,因此由其所属的代码门禁或配对门禁报告不匹配。
- **执行红线按连贯的评审批次推进,再以类别为单位完成强制覆盖。** 在存量文档仍处于评审阶段时,显式 `required` 条目会纳入相关文件;存量文档全部完成后,其 `non-readme``readme` 类别进入 `requiredClasses`,不再产生新的 backlog。发布到文档站的配对使用 `pairedPages()`,由根 locale 投影 `.zh.md`,由 `/en/` 投影 `.md`;仅创建对侧文件并不会发布它。 - **执行红线按连贯的评审批次推进,再以类别为单位完成强制覆盖。** 在存量文档仍处于评审阶段时,显式 `required` 条目会纳入相关文件;存量文档全部完成后,其 `non-readme``readme` 类别进入 `requiredClasses`,不再产生新的 backlog。发布到文档站的配对使用 `pairedPages()`,由根 locale 投影 `.zh.md`,由 `/en/` 投影 `.md`;仅创建对侧文件并不会发布它。
- **配对记录是元数据,而不是 Cordis Loader 配置。** Cordis 配置发现会接受实际的 `.cordis.yml``.cordis.yaml` 文件,同时排除 `*.i18n.yaml`,即使文档名中包含 `cordis` 也不例外。这样既能继续校验可执行的 Loader 配置项,又不会把翻译 hash 当作配置来解析。 - **配对记录是元数据,而不是 Cordis Loader 配置。** Cordis 配置发现会接受实际的 `.cordis.yml``.cordis.yaml` 文件,同时排除 `*.i18n.yaml`,即使文档名中包含 `cordis` 也不例外。这样既能继续校验可执行的 Loader 配置项,又不会把翻译 hash 当作配置来解析。
- **翻译是 agent 的工作,由人评审。** 仓库内置的工作流是 [.agents/skills/dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md),与 [dsh-code-review](../../../skills/dsh-code-review/SKILL.md) 模式相同skill技能承载工作流并将文档作为真源。该 skill 要求编排 agent 把翻译写作委派给 subagent。 - **翻译是 agent 的工作,由人评审。** 仓库内置的工作流是 [.agents/skills/dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md),与 [dsh-code-review](../../../skills/dsh-code-review/SKILL.md) 模式相同skill技能承载工作流并将文档作为真源。该 skill 要求编排 agent 把翻译写作委派给 subagent。

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # 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: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write # pnpm run verify-translation-pairing --write
development.md: f706d54764bbf79d1f13ccb4c412e7b5717b1edb development.md: c46d84740e6f0a1f67158f39f9ea421cb57165d4
development.zh.md: 15f5ae0c514ac412c302a99cb0a662acce510c44 development.zh.md: 9e13ab258e1db5406f84ece61959a995110578ae

View File

@@ -166,7 +166,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" } { "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 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. `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 enforces a 1:1 correspondence by document, symbol, and projection between primary blocks and manifest entries; a paired `.zh.md` block reuses its unsuffixed sibling's entry only when the whole tracked fence sequence is byte-identical and ordered identically. `doc-typecheck` applies the same derivative rule to compilable fences, while skipping both source-equivalence fence kinds from compilation and 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 primary block, update the manifest in the same change.
## Architecture context ## Architecture context

View File

@@ -166,7 +166,7 @@ pnpm run demo:acp
{ "doc": "docs/core-data-structures/session.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" } { "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 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包括成员文档让读者同时看到源码契约和确切形状。该门禁按文档、符号和投影,在主块与 manifest 条目之间强制 1:1 对应;只有当配对 `.zh.md` 块的完整受跟踪围栏序列与其无后缀兄弟文件按字节一致且顺序相同时,才会复用后者的条目。`doc-typecheck` 对可编译围栏应用同一派生规则,同时跳过两种源码等价围栏的编译,并将其排除在 opt-out 比例之外。当你改动一个已记录的类型声明或其 JSDoc 时,门禁会失败直到你更新粘贴内容;当你增删一个块时,请在同一个变更里更新 manifest。
## 架构上下文 ## 架构上下文

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # 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: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write # pnpm run verify-translation-pairing --write
README.md: a60572b0691702c949b44b82a7b1d732a888ed93 README.md: 053453bc622f58083a5e0e2992f1b8a820e3f3f9
README.zh.md: 04ae032233dfa01c145b5d6bdbe7353a366e11f1 README.zh.md: 7f6242e8ff2a3ec69e2b84402dab447297047991

View File

@@ -28,6 +28,8 @@ This repo's documentation is read by people and agents both inside and outside t
3. Files listed as `excluded` have no `.zh.md` and no `.i18n.yaml` at all. 3. Files listed as `excluded` have no `.zh.md` and no `.i18n.yaml` at all.
4. Every date-named document (`yyyy-mm-dd-*.md`) dated on or after the manifest's `requiredSince` cutoff has a complete pair — new date-named Agent Notes merge bilingual from birth. 4. Every date-named document (`yyyy-mm-dd-*.md`) dated on or after the manifest's `requiredSince` cutoff has a complete pair — new date-named Agent Notes merge bilingual from birth.
Source-oriented code gates consume an exact `.zh.md` fence sequence as a derivative of its unsuffixed sibling instead of compiling or manifesting the same code twice. The sequence must match in length, order, fence kind, and byte-exact body; otherwise both copies remain independently checked and the pairing gate reports the structural mismatch.
`pnpm run verify-translation-pairing --list` prints the current pairing state of every document in scope — missing, out-of-sync, or ok — and is the work list for translation batches. It never fails; it reports. `pnpm run verify-translation-pairing --list` prints the current pairing state of every document in scope — missing, out-of-sync, or ok — and is the work list for translation batches. It never fails; it reports.
The practical rule this gate creates: **when a PR edits either side of a paired document, the same PR updates the counterpart and re-records the pair** (run the [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) skill, then `--write`), exactly like the repo's existing doc-sync rule for code and READMEs. A PR that leaves a pair out of sync goes red in CI. The practical rule this gate creates: **when a PR edits either side of a paired document, the same PR updates the counterpart and re-records the pair** (run the [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) skill, then `--write`), exactly like the repo's existing doc-sync rule for code and READMEs. A PR that leaves a pair out of sync goes red in CI.

View File

@@ -28,6 +28,8 @@
3. 列为 `excluded` 的文件完全没有 `.zh.md`,也没有 `.i18n.yaml`。 3. 列为 `excluded` 的文件完全没有 `.zh.md`,也没有 `.i18n.yaml`。
4. 凡文件名符合 `yyyy-mm-dd-*.md` 且日期不早于 manifest元数据清单中 `requiredSince` 分界日期的文档,都必须有完整配对;新建的日期命名 Agent Note 从创建起便须配齐中英文。 4. 凡文件名符合 `yyyy-mm-dd-*.md` 且日期不早于 manifest元数据清单中 `requiredSince` 分界日期的文档,都必须有完整配对;新建的日期命名 Agent Note 从创建起便须配齐中英文。
面向源码的代码门禁会把精确的 `.zh.md` 围栏序列视为其无后缀兄弟文件的派生内容,而不会再次编译相同代码或在 manifest 中重复登记。该序列必须在长度、顺序、围栏类型和按字节精确的正文上一致;否则两份副本仍会独立受检,配对门禁也会报告结构不匹配。
`pnpm run verify-translation-pairing --list` 打印范围内每篇文档的当前配对状态missing、out-of-sync 或 ok是翻译批次的工作清单。它从不失败它只报告。 `pnpm run verify-translation-pairing --list` 打印范围内每篇文档的当前配对状态missing、out-of-sync 或 ok是翻译批次的工作清单。它从不失败它只报告。
这个门禁带来的实际规则是:**当一个 PR 修改了已配对文档的任一侧时,同一个 PR 更新另一侧并重新记录配对**(运行 [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) skill技能再 `--write`),与本仓库既有的代码与 README 的 doc-sync 规则完全一致。留下失去同步的配对的 PR 会在 CI 变红。 这个门禁带来的实际规则是:**当一个 PR 修改了已配对文档的任一侧时,同一个 PR 更新另一侧并重新记录配对**(运行 [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) skill技能再 `--write`),与本仓库既有的代码与 README 的 doc-sync 规则完全一致。留下失去同步的配对的 PR 会在 CI 变红。

View File

@@ -1,7 +1,8 @@
/** /**
* Typecheck Markdown `ts` fences against the workspace API. `ignore-check` fences are reported as * Typecheck Markdown `ts` fences against the workspace API. `ignore-check` fences are reported as
* opt-outs; generated catalog fragments and source-equivalence 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. * owning gates verify them. Byte-identical `.zh.md` copies reuse their unsuffixed sibling's check. A
* build-coordinated mode consumes existing declarations without emit.
*/ */
import { execFileSync } from 'node:child_process' import { execFileSync } from 'node:child_process'
@@ -10,6 +11,7 @@ import { join, relative, resolve } from 'node:path'
import ts from 'typescript' import ts from 'typescript'
import { builtDeclarationPath } from './doc-typecheck-paths.ts' import { builtDeclarationPath } from './doc-typecheck-paths.ts'
import { extractFences } from './md-fences.ts' import { extractFences } from './md-fences.ts'
import { partitionPairedMarkdownDerivatives } from './paired-markdown-derivatives.ts'
const root = resolve(import.meta.dirname, '..') const root = resolve(import.meta.dirname, '..')
@@ -206,7 +208,12 @@ for (const pattern of markdownGlobs) {
} }
files.sort() files.sort()
const all = files.flatMap(extractBlocks) const extracted = files.flatMap(extractBlocks)
const { primary: all, derivatives } = partitionPairedMarkdownDerivatives(
extracted,
block => block.file,
block => `${block.kind}\0${block.code}`,
)
const checked = all.filter(b => b.kind === 'check') const checked = all.filter(b => b.kind === 'check')
const ignored = all.filter(b => b.kind === 'ignore') const ignored = all.filter(b => b.kind === 'ignore')
// Only compile-eligible fences belong in the opt-out ratio; every other skipped // Only compile-eligible fences belong in the opt-out ratio; every other skipped
@@ -233,7 +240,7 @@ if (compilationError !== undefined) {
const ratio = ignored.length / ratioDenominator const ratio = ignored.length / ratioDenominator
const skipped = all.length - ratioDenominator const skipped = all.length - ratioDenominator
console.log(`doc-typecheck: ${checked.length} block(s) compiled, ${ignored.length} ignored (${(ratio * 100).toFixed(0)}% opt-out), ${skipped} type-equiv/catalog (checked elsewhere).`) console.log(`doc-typecheck: ${checked.length} block(s) compiled, ${ignored.length} ignored (${(ratio * 100).toFixed(0)}% opt-out), ${skipped} type-equiv/catalog (checked elsewhere), ${derivatives.length} paired derivative(s).`)
// Guard against the escape hatch becoming the norm. // Guard against the escape hatch becoming the norm.
if (ratioDenominator >= 4 && ratio > 0.5) { if (ratioDenominator >= 4 && ratio > 0.5) {
console.error(`doc-typecheck: too many blocks opt out of checking (${ignored.length}/${ratioDenominator}). Make them compile or delete them.`) console.error(`doc-typecheck: too many blocks opt out of checking (${ignored.length}/${ratioDenominator}). Make them compile or delete them.`)

View File

@@ -0,0 +1,66 @@
import { describe, expect, it } from 'vitest'
import { partitionPairedMarkdownDerivatives } from './paired-markdown-derivatives.ts'
interface Block {
doc: string
kind: string
code: string
}
const partition = (blocks: Block[]) => partitionPairedMarkdownDerivatives(
blocks,
block => block.doc,
block => `${block.kind}\0${block.code}`,
)
describe('partitionPairedMarkdownDerivatives', () => {
it('treats a complete byte-identical Chinese sequence as derivative', () => {
const english = [
{ doc: 'docs/example.md', kind: 'ts', code: 'const one = 1' },
{ doc: 'docs/example.md', kind: 'type-equiv', code: 'interface Example {}' },
]
const chinese = english.map(block => ({ ...block, doc: 'docs/example.zh.md' }))
const unrelated = { doc: 'docs/other.md', kind: 'ts', code: 'const other = 2' }
expect(partition([...english, ...chinese, unrelated])).toEqual({
primary: [...english, unrelated],
derivatives: chinese,
})
})
it('keeps reordered, changed, partial, and orphan Chinese sequences primary', () => {
const sequence = (doc: string) => [
{ doc, kind: 'ts', code: 'const one = 1' },
{ doc, kind: 'ts', code: 'const two = 2' },
]
const english = sequence('docs/example.md')
const changed = english.map((block, index) => ({
...block,
doc: 'docs/example.zh.md',
code: index === 0 ? 'const one = 0' : block.code,
}))
const reorderedEnglish = sequence('docs/reordered.md')
const reordered = [...reorderedEnglish].reverse().map(block => ({ ...block, doc: 'docs/reordered.zh.md' }))
const partialEnglish = sequence('docs/partial.md')
const partial = [{ ...partialEnglish[0]!, doc: 'docs/partial.zh.md' }]
const orphan = [{ doc: 'docs/orphan.zh.md', kind: 'ts', code: 'const orphan = true' }]
const blocks = [
...english,
...changed,
...reorderedEnglish,
...reordered,
...partialEnglish,
...partial,
...orphan,
]
expect(partition(blocks)).toEqual({ primary: blocks, derivatives: [] })
})
it('requires the fence kind to match as well as the body', () => {
const english = { doc: 'docs/example.md', kind: 'type-equiv', code: 'interface Example {}' }
const chinese = { ...english, doc: 'docs/example.zh.md', kind: 'public-api' }
expect(partition([english, chinese])).toEqual({ primary: [english, chinese], derivatives: [] })
})
})

View File

@@ -0,0 +1,63 @@
/**
* Separate byte-identical Chinese Markdown code blocks from the primary checks
* performed on their unsuffixed English siblings. The bilingual pairing gate
* owns cross-language identity; source-oriented gates consume one copy.
*/
/** The result of separating canonical blocks from paired Chinese derivatives. */
export interface MarkdownDerivativePartition<T> {
/** Blocks that still require the caller's owning check. */
primary: T[]
/** Chinese blocks covered by the byte-identical unsuffixed sequence. */
derivatives: T[]
}
/** Return the unsuffixed sibling of a Chinese Markdown path. */
function unsuffixedSibling(doc: string): string | null {
return doc.endsWith('.zh.md') ? `${doc.slice(0, -'.zh.md'.length)}.md` : null
}
/**
* Partition complete byte-identical `.zh.md` block sequences from primary
* blocks. A partial or reordered match stays primary so the caller fails
* closed; the translation-pairing gate reports the cross-language mismatch.
*
* @param blocks - Blocks in repository scan order.
* @param docOf - Repository-relative Markdown path owning a block.
* @param fingerprintOf - Block kind/info string plus byte-exact body.
* @returns Primary blocks and paired Chinese derivatives, preserving order.
*/
export function partitionPairedMarkdownDerivatives<T>(
blocks: readonly T[],
docOf: (block: T) => string,
fingerprintOf: (block: T) => string,
): MarkdownDerivativePartition<T> {
const byDoc = new Map<string, T[]>()
for (const block of blocks) {
const doc = docOf(block)
const group = byDoc.get(doc)
if (group) group.push(block)
else byDoc.set(doc, [block])
}
const derivativeDocs = new Set<string>()
for (const [doc, candidates] of byDoc) {
const sibling = unsuffixedSibling(doc)
if (sibling === null) continue
const originals = byDoc.get(sibling)
if (originals === undefined || originals.length !== candidates.length) continue
if (candidates.every((candidate, index) => {
const original = originals[index]
return original !== undefined && fingerprintOf(candidate) === fingerprintOf(original)
})) {
derivativeDocs.add(doc)
}
}
const primary: T[] = []
const derivatives: T[] = []
for (const block of blocks) {
(derivativeDocs.has(docOf(block)) ? derivatives : primary).push(block)
}
return { primary, derivatives }
}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
{ {
"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.", "comment": "Maps each primary ` ```ts type-equiv ` or ` ```ts public-api ` block (by doc + declared symbol + projection) to the source declaration and original JSDoc it must match. Paired `.zh.md` blocks are byte-identical derivatives checked through their unsuffixed sibling and have no duplicate entry. 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 between primary blocks and entries. Add an entry when you add a primary source-equivalence block; remove it when you remove the block.",
"entries": [ "entries": [
{ {
"doc": "docs/core-data-structures/core.md", "doc": "docs/core-data-structures/core.md",
@@ -1248,949 +1248,6 @@
"doc": "docs/core-data-structures/session-query.md", "doc": "docs/core-data-structures/session-query.md",
"symbol": "SessionSearchHit", "symbol": "SessionSearchHit",
"source": "packages/session-query/session-query/src/types.ts" "source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "Branded",
"source": "packages/util/brand/src/index.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "ContentBlockMap",
"source": "packages/llm/llm/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "AssistantProvenance",
"source": "packages/llm/llm/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "Message",
"source": "packages/llm/llm/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "MessageSourceMap",
"source": "packages/llm/llm/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "FinishReasonMap",
"source": "packages/llm/llm/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "LlmProviderInfo",
"source": "packages/llm/llm/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "LlmModelInfo",
"source": "packages/llm/llm/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "LlmModelContext",
"source": "packages/llm/llm/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "GenerateOptions",
"source": "packages/llm/llm/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "ToolSchema",
"source": "packages/llm/llm/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "LlmCallConfig",
"source": "packages/llm/llm/src/call-config.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "SessionEvent",
"source": "packages/core/session/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "SendOptions",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "AgentCancelCause",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "InjectOptions",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "ResolvedAgentInput",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "AgentMessageId",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "AgentMessage",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "CancelOptions",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "Agent",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "HookContext",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "PromptDecision",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "ContinuationDecision",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "RequestError",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "RequestErrorDecision",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "ContinuationStop",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/core.zh.md",
"symbol": "SessionStartSource",
"source": "packages/core/agent/src/types.ts"
},
{
"doc": "docs/core-data-structures/scope.zh.md",
"symbol": "ScopeKey",
"source": "packages/core/scope/src/index.ts"
},
{
"doc": "docs/core-data-structures/scope.zh.md",
"symbol": "Scoped",
"source": "packages/core/scope/src/index.ts"
},
{
"doc": "docs/core-data-structures/scope.zh.md",
"symbol": "Scope",
"source": "packages/core/scope/src/index.ts"
},
{
"doc": "docs/core-data-structures/scope.zh.md",
"symbol": "ScopeLayer",
"source": "packages/core/scope/src/store.ts"
},
{
"doc": "docs/core-data-structures/system-prompt.zh.md",
"symbol": "AssembleContext",
"source": "packages/core/system-prompt/src/index.ts"
},
{
"doc": "docs/core-data-structures/system-prompt.zh.md",
"symbol": "PromptSection",
"source": "packages/core/system-prompt/src/index.ts"
},
{
"doc": "docs/core-data-structures/system-prompt.zh.md",
"symbol": "ToolProviderResult",
"source": "packages/core/system-prompt/src/index.ts"
},
{
"doc": "docs/core-data-structures/llm-streaming.zh.md",
"symbol": "StreamChunk",
"source": "packages/llm/llm/src/types.ts"
},
{
"doc": "docs/core-data-structures/llm-streaming.zh.md",
"symbol": "LlmFailure",
"source": "packages/llm/llm/src/types.ts"
},
{
"doc": "docs/core-data-structures/llm-streaming.zh.md",
"symbol": "TokenUsage",
"source": "packages/llm/llm/src/types.ts"
},
{
"doc": "docs/core-data-structures/llm-streaming.zh.md",
"symbol": "ContentBlockMap",
"source": "packages/llm/llm/src/types.ts"
},
{
"doc": "docs/core-data-structures/llm-streaming.zh.md",
"symbol": "AppIdentity",
"source": "packages/llm/llm/src/attribution.ts"
},
{
"doc": "docs/core-data-structures/llm-streaming.zh.md",
"symbol": "BlockAssembler",
"source": "packages/llm/llm/src/assembler.ts",
"projection": "public-api"
},
{
"doc": "docs/core-data-structures/llm-streaming.zh.md",
"symbol": "LlmAdapter",
"source": "packages/llm/llm/src/index.ts",
"projection": "public-api"
},
{
"doc": "docs/core-data-structures/session.zh.md",
"symbol": "PromptMessageData",
"source": "packages/core/session/src/types.ts"
},
{
"doc": "docs/core-data-structures/session.zh.md",
"symbol": "SessionEventMap",
"source": "packages/core/session/src/types.ts"
},
{
"doc": "docs/core-data-structures/session.zh.md",
"symbol": "OutOfBandSessionEventMap",
"source": "packages/core/session/src/types.ts"
},
{
"doc": "docs/core-data-structures/session.zh.md",
"symbol": "EpochHeader",
"source": "packages/core/session/src/types.ts"
},
{
"doc": "docs/core-data-structures/session.zh.md",
"symbol": "TodoItem",
"source": "packages/core/session/src/types.ts"
},
{
"doc": "docs/core-data-structures/session.zh.md",
"symbol": "SessionEvent",
"source": "packages/core/session/src/types.ts"
},
{
"doc": "docs/core-data-structures/session.zh.md",
"symbol": "TurnTriggerMap",
"source": "packages/core/session/src/types.ts"
},
{
"doc": "docs/core-data-structures/session.zh.md",
"symbol": "TurnEndReasonMap",
"source": "packages/core/session/src/types.ts"
},
{
"doc": "docs/core-data-structures/session.zh.md",
"symbol": "SurfaceEventType",
"source": "packages/core/session/src/types.ts"
},
{
"doc": "docs/core-data-structures/session.zh.md",
"symbol": "SurfaceOp",
"source": "packages/core/session/src/types.ts"
},
{
"doc": "docs/core-data-structures/session.zh.md",
"symbol": "SurfaceIntent",
"source": "packages/core/session/src/types.ts"
},
{
"doc": "docs/core-data-structures/session.zh.md",
"symbol": "SessionSurface",
"source": "packages/core/session/src/surface.ts"
},
{
"doc": "docs/core-data-structures/session.zh.md",
"symbol": "SurfaceFoldReplacement",
"source": "packages/core/session/src/surface.ts"
},
{
"doc": "docs/core-data-structures/session.zh.md",
"symbol": "SurfaceFoldResult",
"source": "packages/core/session/src/surface.ts"
},
{
"doc": "docs/core-data-structures/session.zh.md",
"symbol": "Session",
"source": "packages/core/session/src/index.ts",
"projection": "public-api"
},
{
"doc": "docs/core-data-structures/persistence.zh.md",
"symbol": "SessionHeader",
"source": "packages/core/session/src/types.ts"
},
{
"doc": "docs/core-data-structures/persistence.zh.md",
"symbol": "CreateSessionOptions",
"source": "packages/core/session/src/types.ts"
},
{
"doc": "docs/core-data-structures/persistence.zh.md",
"symbol": "SessionLocation",
"source": "packages/session-persistence/session-persistence/src/index.ts"
},
{
"doc": "docs/core-data-structures/persistence.zh.md",
"symbol": "SessionPersistenceRevision",
"source": "packages/session-persistence/session-persistence/src/revision.ts"
},
{
"doc": "docs/core-data-structures/persistence.zh.md",
"symbol": "SessionPersistenceSnapshot",
"source": "packages/session-persistence/session-persistence/src/index.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionEventSurface",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionRecord",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionLogSnapshot",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionSurfaceSnapshot",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionTitleObservation",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionTitleObservationResult",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionEventRecord",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionResultFilter",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionEventResultFilter",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionEventSearchDocument",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionSearchCursor",
"source": "packages/session-query/session-query/src/cursor.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionSearchRequest",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionEventSearchRequest",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionSearchPage",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionEventSearchPage",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionEventSearchHit",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionSearchHit",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionLineageNode",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionLineageTrace",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionQueryErrorCode",
"source": "packages/session-query/session-query/src/config.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionEventReadRequest",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionEventWindow",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionEventTraceRequest",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionEventTrace",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/session-query.zh.md",
"symbol": "SessionEventTraceObservation",
"source": "packages/session-query/session-query/src/types.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ToolOutputDefinition",
"source": "packages/core/tools/src/index.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ToolDefinition",
"source": "packages/core/tools/src/index.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ValueSchemaSpec",
"source": "packages/core/tools/src/schema.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ParameterPropertySpec",
"source": "packages/core/tools/src/schema.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ParameterSchemaSpec",
"source": "packages/core/tools/src/schema.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "InferValue",
"source": "packages/core/tools/src/schema.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "InferArgs",
"source": "packages/core/tools/src/schema.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ToolExecutionToken",
"source": "packages/core/tools/src/index.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ToolExecutionInput",
"source": "packages/core/tools/src/index.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ToolExecution",
"source": "packages/core/tools/src/index.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ToolDispatchExecution",
"source": "packages/core/tools/src/index.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ToolExecutionMode",
"source": "packages/core/tools/src/index.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ToolRunContext",
"source": "packages/core/tools/src/index.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ToolGuard",
"source": "packages/core/tools/src/index.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ToolRestriction",
"source": "packages/core/tools/src/index.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ToolFailure",
"source": "packages/core/tools/src/index.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ToolExecutionSuccess",
"source": "packages/core/tools/src/index.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ToolExecutionFailure",
"source": "packages/core/tools/src/index.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ToolExecutionResult",
"source": "packages/core/tools/src/index.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "PreToolDecision",
"source": "packages/core/tools/src/index.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "PostToolDecision",
"source": "packages/core/tools/src/index.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "JsonSchemaScalar",
"source": "packages/core/tools/src/json-schema.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "JsonSchemaType",
"source": "packages/core/tools/src/json-schema.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "JsonSchemaNode",
"source": "packages/core/tools/src/json-schema.ts"
},
{
"doc": "docs/core-data-structures/tools.zh.md",
"symbol": "ObjectJsonSchema",
"source": "packages/core/tools/src/json-schema.ts"
},
{
"doc": "docs/core-data-structures/user-interaction.zh.md",
"symbol": "AskUserQuestionOption",
"source": "packages/ui/user-interaction/src/types.ts"
},
{
"doc": "docs/core-data-structures/user-interaction.zh.md",
"symbol": "AskUserQuestionItem",
"source": "packages/ui/user-interaction/src/types.ts"
},
{
"doc": "docs/core-data-structures/user-interaction.zh.md",
"symbol": "AskUserQuestionRequest",
"source": "packages/ui/user-interaction/src/index.ts"
},
{
"doc": "docs/core-data-structures/user-interaction.zh.md",
"symbol": "AskUserQuestionAnswerItem",
"source": "packages/ui/user-interaction/src/types.ts"
},
{
"doc": "docs/core-data-structures/user-interaction.zh.md",
"symbol": "AskUserQuestionAnswer",
"source": "packages/ui/user-interaction/src/types.ts"
},
{
"doc": "docs/core-data-structures/user-interaction.zh.md",
"symbol": "UserInteractionProvider",
"source": "packages/ui/user-interaction/src/index.ts"
},
{
"doc": "docs/core-data-structures/user-interaction.zh.md",
"symbol": "UserInteractionError",
"source": "packages/ui/user-interaction/src/index.ts"
},
{
"doc": "docs/core-data-structures/approval.zh.md",
"symbol": "ApprovalRequestId",
"source": "packages/ui/user-approval/src/types.ts"
},
{
"doc": "docs/core-data-structures/approval.zh.md",
"symbol": "ApprovalOutcome",
"source": "packages/ui/user-approval/src/types.ts"
},
{
"doc": "docs/core-data-structures/approval.zh.md",
"symbol": "ApprovalPolicy",
"source": "packages/ui/user-approval/src/index.ts"
},
{
"doc": "docs/core-data-structures/approval.zh.md",
"symbol": "ApprovalRequest",
"source": "packages/ui/user-approval/src/index.ts"
},
{
"doc": "docs/core-data-structures/bash.zh.md",
"symbol": "DshEnvironmentKey",
"source": "packages/bash/bash/src/types.ts"
},
{
"doc": "docs/core-data-structures/bash.zh.md",
"symbol": "DshEnvironment",
"source": "packages/bash/bash/src/types.ts"
},
{
"doc": "docs/core-data-structures/bash.zh.md",
"symbol": "BashExecRequest",
"source": "packages/bash/bash/src/types.ts"
},
{
"doc": "docs/core-data-structures/bash.zh.md",
"symbol": "BashExecSpec",
"source": "packages/bash/bash/src/types.ts"
},
{
"doc": "docs/core-data-structures/bash.zh.md",
"symbol": "BashRunResult",
"source": "packages/bash/bash/src/types.ts"
},
{
"doc": "docs/core-data-structures/bash.zh.md",
"symbol": "BashSandboxInfo",
"source": "packages/bash/bash/src/types.ts"
},
{
"doc": "docs/core-data-structures/bash.zh.md",
"symbol": "CollectedOutput",
"source": "packages/bash/bash/src/types.ts"
},
{
"doc": "docs/core-data-structures/bash.zh.md",
"symbol": "BashProcess",
"source": "packages/bash/bash/src/types.ts"
},
{
"doc": "docs/core-data-structures/bash.zh.md",
"symbol": "BashProcessRead",
"source": "packages/bash/bash/src/types.ts"
},
{
"doc": "docs/core-data-structures/sandbox.zh.md",
"symbol": "SandboxMode",
"source": "packages/sandbox/sandbox/src/index.ts"
},
{
"doc": "docs/core-data-structures/sandbox.zh.md",
"symbol": "ConfinedSandboxMode",
"source": "packages/sandbox/sandbox/src/index.ts"
},
{
"doc": "docs/core-data-structures/sandbox.zh.md",
"symbol": "SandboxExecutionPolicy",
"source": "packages/sandbox/sandbox/src/index.ts"
},
{
"doc": "docs/core-data-structures/sandbox.zh.md",
"symbol": "SandboxEnforcement",
"source": "packages/sandbox/sandbox/src/index.ts"
},
{
"doc": "docs/core-data-structures/sandbox.zh.md",
"symbol": "SandboxPolicy",
"source": "packages/sandbox/sandbox/src/index.ts"
},
{
"doc": "docs/core-data-structures/sandbox.zh.md",
"symbol": "SandboxPolicyRequest",
"source": "packages/sandbox/sandbox-policy/src/index.ts"
},
{
"doc": "docs/core-data-structures/sandbox.zh.md",
"symbol": "ConfinedArgv",
"source": "packages/sandbox/sandbox/src/index.ts"
},
{
"doc": "docs/core-data-structures/code-runtime.zh.md",
"symbol": "CodeJsonValue",
"source": "packages/code-runtime/code-runtime/src/types.ts"
},
{
"doc": "docs/core-data-structures/code-runtime.zh.md",
"symbol": "CodeRunRequest",
"source": "packages/code-runtime/code-runtime/src/types.ts"
},
{
"doc": "docs/core-data-structures/code-runtime.zh.md",
"symbol": "CodeRunResult",
"source": "packages/code-runtime/code-runtime/src/types.ts"
},
{
"doc": "docs/core-data-structures/code-runtime.zh.md",
"symbol": "CodeBindingNamespace",
"source": "packages/code-runtime/code-runtime/src/types.ts"
},
{
"doc": "docs/core-data-structures/code-runtime.zh.md",
"symbol": "CodeBindingErrorClass",
"source": "packages/code-runtime/code-runtime/src/types.ts"
},
{
"doc": "docs/core-data-structures/code-runtime.zh.md",
"symbol": "CodeBindingFunction",
"source": "packages/code-runtime/code-runtime/src/types.ts"
},
{
"doc": "docs/core-data-structures/code-runtime.zh.md",
"symbol": "CodeRunFailure",
"source": "packages/code-runtime/code-runtime/src/types.ts"
},
{
"doc": "docs/core-data-structures/filesystem.zh.md",
"symbol": "FsTarget",
"source": "packages/fs/fs/src/types.ts"
},
{
"doc": "docs/core-data-structures/filesystem.zh.md",
"symbol": "FsTargetKey",
"source": "packages/fs/fs/src/types.ts"
},
{
"doc": "docs/core-data-structures/filesystem.zh.md",
"symbol": "FsVersion",
"source": "packages/fs/fs/src/types.ts"
},
{
"doc": "docs/core-data-structures/filesystem.zh.md",
"symbol": "FsInfo",
"source": "packages/fs/fs/src/types.ts"
},
{
"doc": "docs/core-data-structures/filesystem.zh.md",
"symbol": "FsPathInfo",
"source": "packages/fs/fs/src/types.ts"
},
{
"doc": "docs/core-data-structures/filesystem.zh.md",
"symbol": "FsDirEntry",
"source": "packages/fs/fs/src/types.ts"
},
{
"doc": "docs/core-data-structures/filesystem.zh.md",
"symbol": "FsWriteIntent",
"source": "packages/fs/fs/src/types.ts"
},
{
"doc": "docs/core-data-structures/filesystem.zh.md",
"symbol": "FsWriteOutcome",
"source": "packages/fs/fs/src/types.ts"
},
{
"doc": "docs/core-data-structures/filesystem.zh.md",
"symbol": "FsEditRequest",
"source": "packages/fs/fs/src/types.ts"
},
{
"doc": "docs/core-data-structures/filesystem.zh.md",
"symbol": "FsEditOutcome",
"source": "packages/fs/fs/src/types.ts"
},
{
"doc": "docs/core-data-structures/filesystem.zh.md",
"symbol": "FsErrorCode",
"source": "packages/fs/fs/src/types.ts"
},
{
"doc": "docs/core-data-structures/filesystem.zh.md",
"symbol": "FsPolicyExec",
"source": "packages/fs/fs-policy/src/types.ts"
},
{
"doc": "docs/core-data-structures/filesystem.zh.md",
"symbol": "FileReadOutcome",
"source": "packages/fs/tool-fs/src/read-render.ts"
},
{
"doc": "docs/core-data-structures/skills.zh.md",
"symbol": "SkillSource",
"source": "packages/skill/skill/src/index.ts"
},
{
"doc": "docs/core-data-structures/skills.zh.md",
"symbol": "SkillResourceBase",
"source": "packages/skill/skill/src/index.ts"
},
{
"doc": "docs/core-data-structures/skills.zh.md",
"symbol": "SkillSummary",
"source": "packages/skill/skill/src/index.ts"
},
{
"doc": "docs/core-data-structures/skills.zh.md",
"symbol": "SkillCandidate",
"source": "packages/skill/skill/src/index.ts"
},
{
"doc": "docs/core-data-structures/skills.zh.md",
"symbol": "SkillDefinition",
"source": "packages/skill/skill/src/index.ts"
},
{
"doc": "docs/core-data-structures/skills.zh.md",
"symbol": "SkillRegistration",
"source": "packages/skill/skill/src/index.ts"
},
{
"doc": "docs/core-data-structures/skills.zh.md",
"symbol": "SkillLookupOptions",
"source": "packages/skill/skill/src/index.ts"
},
{
"doc": "docs/core-data-structures/skills.zh.md",
"symbol": "SkillProvider",
"source": "packages/skill/skill/src/index.ts"
},
{
"doc": "docs/core-data-structures/skills.zh.md",
"symbol": "Config",
"source": "packages/skill/skill/src/index.ts"
},
{
"doc": "docs/core-data-structures/compaction.zh.md",
"symbol": "CompactionResult",
"source": "packages/compact/compact/src/types.ts"
},
{
"doc": "docs/core-data-structures/compaction.zh.md",
"symbol": "CompactionTrigger",
"source": "packages/compact/compact/src/index.ts"
},
{
"doc": "docs/core-data-structures/compaction.zh.md",
"symbol": "PrunedEntry",
"source": "packages/compact/compact-tool-result-prune/src/types.ts"
},
{
"doc": "docs/core-data-structures/compaction.zh.md",
"symbol": "PruneResult",
"source": "packages/compact/compact-tool-result-prune/src/types.ts"
},
{
"doc": "docs/core-data-structures/subagent.zh.md",
"symbol": "SubagentCapabilities",
"source": "packages/subagent/subagent/src/types.ts"
},
{
"doc": "docs/core-data-structures/subagent.zh.md",
"symbol": "SubagentStartRequest",
"source": "packages/subagent/subagent/src/types.ts"
},
{
"doc": "docs/core-data-structures/subagent.zh.md",
"symbol": "SubagentResult",
"source": "packages/subagent/subagent/src/types.ts"
},
{
"doc": "docs/core-data-structures/subagent.zh.md",
"symbol": "SubagentStopReasonMap",
"source": "packages/subagent/subagent/src/types.ts"
},
{
"doc": "docs/core-data-structures/subagent.zh.md",
"symbol": "SubagentRun",
"source": "packages/subagent/subagent/src/types.ts"
},
{
"doc": "docs/core-data-structures/subagent.zh.md",
"symbol": "SubagentProvider",
"source": "packages/subagent/subagent/src/types.ts"
},
{
"doc": "docs/core-data-structures/web.zh.md",
"symbol": "WebSearchRequest",
"source": "packages/web/web/src/types.ts"
},
{
"doc": "docs/core-data-structures/web.zh.md",
"symbol": "WebSearchResult",
"source": "packages/web/web/src/types.ts"
},
{
"doc": "docs/core-data-structures/web.zh.md",
"symbol": "WebSearchSource",
"source": "packages/web/web/src/types.ts"
},
{
"doc": "docs/core-data-structures/web.zh.md",
"symbol": "WebFetchRequest",
"source": "packages/web/web/src/types.ts"
},
{
"doc": "docs/core-data-structures/web.zh.md",
"symbol": "WebFetchResult",
"source": "packages/web/web/src/types.ts"
},
{
"doc": "docs/core-data-structures/web.zh.md",
"symbol": "WebFetchBody",
"source": "packages/web/web/src/types.ts"
},
{
"doc": "docs/core-data-structures/workflow.zh.md",
"symbol": "WorkflowStartRequest",
"source": "packages/workflow/workflow/src/types.ts"
},
{
"doc": "docs/core-data-structures/workflow.zh.md",
"symbol": "WorkflowMeta",
"source": "packages/workflow/workflow/src/types.ts"
},
{
"doc": "docs/core-data-structures/workflow.zh.md",
"symbol": "WorkflowResult",
"source": "packages/workflow/workflow/src/types.ts"
},
{
"doc": "docs/core-data-structures/workflow.zh.md",
"symbol": "WorkflowRun",
"source": "packages/workflow/workflow/src/types.ts"
} }
] ]
} }

View File

@@ -4,12 +4,14 @@
* declaration; `public-api` entries preserve a class's body-stripped public * declaration; `public-api` entries preserve a class's body-stripped public
* declaration. Blocks and entries have a one-to-one relationship; comparison * declaration. Blocks and entries have a one-to-one relationship; comparison
* ignores whitespace and non-JSDoc comments but preserves declaration * ignores whitespace and non-JSDoc comments but preserves declaration
* structure and every original JSDoc comment. * structure and every original JSDoc comment. Byte-identical `.zh.md` blocks
* reuse the manifest-backed check of their unsuffixed sibling.
*/ */
import { globSync, readFileSync, existsSync } from 'node:fs' import { globSync, readFileSync, existsSync } from 'node:fs'
import { resolve, sep } from 'node:path' import { resolve, sep } from 'node:path'
import ts from 'typescript' import ts from 'typescript'
import { partitionPairedMarkdownDerivatives } from './paired-markdown-derivatives.ts'
const root = resolve(import.meta.dirname, '..') const root = resolve(import.meta.dirname, '..')
@@ -223,7 +225,12 @@ const docSet = new Set<string>()
for (const pattern of MARKDOWN_GLOBS) { for (const pattern of MARKDOWN_GLOBS) {
for (const match of globSync(pattern, { cwd: root })) docSet.add(match.split(sep).join('/')) for (const match of globSync(pattern, { cwd: root })) docSet.add(match.split(sep).join('/'))
} }
const blocks: EquivBlock[] = [...docSet].sort().flatMap(extractEquivBlocks) const extractedBlocks: EquivBlock[] = [...docSet].sort().flatMap(extractEquivBlocks)
const { primary: blocks, derivatives } = partitionPairedMarkdownDerivatives(
extractedBlocks,
block => block.doc,
block => `${block.projection ?? 'declaration'}\0${block.code}`,
)
const errors: string[] = [] const errors: string[] = []
// A manifest entry naming a doc that does not exist (or is outside the scanned // A manifest entry naming a doc that does not exist (or is outside the scanned
@@ -299,11 +306,11 @@ for (const e of entries) {
} }
if (errors.length === 0) { if (errors.length === 0) {
console.log(`verify-type-equiv: ${verified} type-equiv block(s) match source structure and JSDoc (1:1 with manifest).`) console.log(`verify-type-equiv: ${verified} type-equiv block(s) match source structure and JSDoc (1:1 with manifest); ${derivatives.length} paired derivative(s).`)
process.exit(0) process.exit(0)
} }
console.error('verify-type-equiv: type-equiv verification failed:') console.error('verify-type-equiv: type-equiv verification failed:')
for (const e of errors) console.error(` ${e}`) for (const e of errors) console.error(` ${e}`)
console.error(`\n(checked ${blocks.length} block(s) across ${new Set(blocks.map(b => b.doc)).size} doc(s); manifest at scripts/type-equiv.manifest.json)`) console.error(`\n(checked ${blocks.length} primary block(s) across ${new Set(blocks.map(b => b.doc)).size} doc(s), ${derivatives.length} paired derivative(s); manifest at scripts/type-equiv.manifest.json)`)
process.exit(1) process.exit(1)