From 8a334c4d496e4585867c5452821635e847bf7ac6 Mon Sep 17 00:00:00 2001 From: pku-xht Date: Wed, 19 Aug 2026 02:26:57 +0800 Subject: [PATCH] fix(i18n): bind localized links to active pairs --- ...6-06-11-content-block-vocabulary.i18n.yaml | 2 +- .../2026-06-11-content-block-vocabulary.zh.md | 2 +- .../2026-06-13-twin-llm-adapters.i18n.yaml | 2 +- .../2026-06-13-twin-llm-adapters.zh.md | 2 +- ...06-17-filesystem-capability-seam.i18n.yaml | 2 +- ...026-06-17-filesystem-capability-seam.zh.md | 4 +- .../2026-06-24-web-capability-seam.i18n.yaml | 2 +- .../2026-06-24-web-capability-seam.zh.md | 2 +- ...026-06-30-event-domain-semantics.i18n.yaml | 2 +- .../2026-06-30-event-domain-semantics.zh.md | 2 +- ...6-07-02-tool-render-intent-union.i18n.yaml | 2 +- .../2026-07-02-tool-render-intent-union.zh.md | 2 +- ...send-and-coalesced-user-messages.i18n.yaml | 2 +- ...ied-send-and-coalesced-user-messages.zh.md | 2 +- ...-07-29-dsh-source-launch-tsx-esm.i18n.yaml | 2 +- ...2026-07-29-dsh-source-launch-tsx-esm.zh.md | 4 +- .../2026-07-29-package-regrouping.i18n.yaml | 2 +- .../2026-07-29-package-regrouping.zh.md | 2 +- ...claimed-pre-step-inbox-lifecycle.i18n.yaml | 2 +- ...-31-claimed-pre-step-inbox-lifecycle.zh.md | 2 +- ...26-08-01-packaged-ripgrep-search.i18n.yaml | 2 +- .../2026-08-01-packaged-ripgrep-search.zh.md | 4 +- ...headless-direct-core-entry-point.i18n.yaml | 2 +- ...-09-headless-direct-core-entry-point.zh.md | 2 +- ...9-human-transcript-append-origin.i18n.yaml | 2 +- ...07-29-human-transcript-append-origin.zh.md | 2 +- ...29-web-details-session-lifecycle.i18n.yaml | 2 +- ...-07-29-web-details-session-lifecycle.zh.md | 2 +- ...-30-bounded-overwrite-diff-basis.i18n.yaml | 2 +- ...6-07-30-bounded-overwrite-diff-basis.zh.md | 2 +- ...rce-checkout-workdir-distinction.i18n.yaml | 2 +- ...-source-checkout-workdir-distinction.zh.md | 2 +- ...ranscript-log-ordered-projection.i18n.yaml | 2 +- ...eb-transcript-log-ordered-projection.zh.md | 2 +- ...actions-require-a-completed-turn.i18n.yaml | 2 +- ...ail-actions-require-a-completed-turn.zh.md | 2 +- .../feature/2026-06-15-code-mode.i18n.yaml | 2 +- .../feature/2026-06-15-code-mode.zh.md | 2 +- .../feature/2026-07-05-skill-system.i18n.yaml | 2 +- .../feature/2026-07-05-skill-system.zh.md | 6 +- ...10-sqlite-session-query-provider.i18n.yaml | 2 +- ...-07-10-sqlite-session-query-provider.zh.md | 2 +- ...26-07-28-skill-invocation-policy.i18n.yaml | 2 +- .../2026-07-28-skill-invocation-policy.zh.md | 2 +- ...b-context-source-and-steer-marks.i18n.yaml | 2 +- ...4-web-context-source-and-steer-marks.zh.md | 4 +- .../2026-06-16-pnpm-over-yarn.i18n.yaml | 2 +- .../process/2026-06-16-pnpm-over-yarn.zh.md | 2 +- ...6-06-18-markdown-cross-link-lint.i18n.yaml | 2 +- .../2026-06-18-markdown-cross-link-lint.zh.md | 2 +- ...-20-core-data-structures-catalog.i18n.yaml | 2 +- ...6-06-20-core-data-structures-catalog.zh.md | 4 +- ...-bilingual-docs-and-pairing-gate.i18n.yaml | 4 +- ...6-07-02-bilingual-docs-and-pairing-gate.md | 2 +- ...7-02-bilingual-docs-and-pairing-gate.zh.md | 4 +- .../2026-07-06-export-jsdoc-gate.i18n.yaml | 2 +- .../2026-07-06-export-jsdoc-gate.zh.md | 2 +- ...26-07-06-parallel-pre-push-gates.i18n.yaml | 2 +- .../2026-07-06-parallel-pre-push-gates.zh.md | 2 +- ...subsystem-cordis-surface-regions.i18n.yaml | 2 +- ...per-subsystem-cordis-surface-regions.zh.md | 2 +- ...8-native-windows-pull-request-ci.i18n.yaml | 2 +- ...08-08-native-windows-pull-request-ci.zh.md | 2 +- ...-08-18-localized-bilingual-links.i18n.yaml | 4 +- .../2026-08-18-localized-bilingual-links.md | 12 +-- ...2026-08-18-localized-bilingual-links.zh.md | 12 +-- ...ove-agent-boundary-mirror-events.i18n.yaml | 2 +- ...-remove-agent-boundary-mirror-events.zh.md | 6 +- .../2026-06-26-fsspec-style-fs-seam.i18n.yaml | 2 +- .../2026-06-26-fsspec-style-fs-seam.zh.md | 2 +- ...4-tighten-hook-protocol-contract.i18n.yaml | 2 +- ...07-04-tighten-hook-protocol-contract.zh.md | 2 +- ...-remove-synthetic-log-only-turns.i18n.yaml | 2 +- ...7-28-remove-synthetic-log-only-turns.zh.md | 2 +- .../2026-08-04-remove-tui-package.i18n.yaml | 2 +- .../2026-08-04-remove-tui-package.zh.md | 2 +- ...ve-steering-interjection-caption.i18n.yaml | 2 +- ...remove-steering-interjection-caption.zh.md | 2 +- .../2026-06-19-acp-snapshot-tests.i18n.yaml | 2 +- .../2026-06-19-acp-snapshot-tests.zh.md | 4 +- ...-fork-child-replay-seed-boundary.i18n.yaml | 2 +- ...6-22-fork-child-replay-seed-boundary.zh.md | 2 +- ...6-06-22-subagent-snapshot-replay.i18n.yaml | 2 +- .../2026-06-22-subagent-snapshot-replay.zh.md | 2 +- ...2026-06-11-api-extractor-reports.i18n.yaml | 2 +- .../2026-06-11-api-extractor-reports.zh.md | 2 +- ...06-20-discover-package-inventory.i18n.yaml | 2 +- ...026-06-20-discover-package-inventory.zh.md | 2 +- ...06-20-truncate-interrupted-turns.i18n.yaml | 2 +- ...026-06-20-truncate-interrupted-turns.zh.md | 2 +- ...ency-swaps-rejected-by-nih-audit.i18n.yaml | 2 +- ...pendency-swaps-rejected-by-nih-audit.zh.md | 8 +- .agents/skills/dsh-translate-docs/SKILL.md | 2 +- docs/graph-atlas.i18n.yaml | 2 +- docs/graph-atlas.zh.md | 2 +- docs/i18n/README.i18n.yaml | 4 +- docs/i18n/README.md | 2 +- docs/i18n/README.zh.md | 2 +- docs/i18n/translation-prompt.md | 2 +- docs/i18n/translation-rules.i18n.yaml | 4 +- docs/i18n/translation-rules.md | 2 +- docs/i18n/translation-rules.zh.md | 2 +- docs/persistence-catalog.i18n.yaml | 2 +- docs/persistence-catalog.zh.md | 2 +- docs/testing.i18n.yaml | 2 +- docs/testing.zh.md | 2 +- .../client/ui-conversation/README.i18n.yaml | 2 +- packages/client/ui-conversation/README.zh.md | 2 +- .../client/ui-primitives/README.i18n.yaml | 2 +- packages/client/ui-primitives/README.zh.md | 2 +- packages/fs/fs/README.i18n.yaml | 2 +- packages/fs/fs/README.zh.md | 2 +- .../repeat-tool-reminder/README.i18n.yaml | 2 +- .../guard/repeat-tool-reminder/README.zh.md | 2 +- packages/llm/llm/README.i18n.yaml | 2 +- packages/llm/llm/README.zh.md | 4 +- .../acp-snapshot/README.i18n.yaml | 2 +- .../test-support/acp-snapshot/README.zh.md | 4 +- packages/web/tool-web/README.i18n.yaml | 2 +- packages/web/tool-web/README.zh.md | 2 +- packages/web/web/README.i18n.yaml | 2 +- packages/web/web/README.zh.md | 2 +- scripts/gen-cordis-catalog-record.spec.ts | 13 ++- scripts/gen-cordis-catalog.ts | 14 ++- scripts/gen-translation-brief.ts | 9 +- scripts/merge-translation-pairing.ts | 4 +- .../request-response.expected.json | 14 +-- scripts/translation-links.spec.ts | 86 ++++++++++++------- scripts/translation-links.ts | 5 +- scripts/translation-pairing-merge.spec.ts | 38 +++++++- scripts/translation-pairing-merge.ts | 36 +++++++- scripts/translation-pairing.spec.ts | 40 +++++++-- scripts/translation-pairing.ts | 16 ++++ scripts/translation-prompt.spec.ts | 2 + scripts/verify-translation-pairing.ts | 11 ++- 135 files changed, 379 insertions(+), 213 deletions(-) diff --git a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml index f31dee1c9d..a8dd8129ce 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md 2026-06-11-content-block-vocabulary.md: 5228724bb9101307db9929aaf7831b477c2a6022 -2026-06-11-content-block-vocabulary.zh.md: b88fa60f585c5e479f76c27dd561374b7c2a3197 +2026-06-11-content-block-vocabulary.zh.md: 63c50ea6dfae79557cb33b9952eb581c90efdcd4 diff --git a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md index b88fa60f58..63c50ea6df 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md @@ -23,6 +23,6 @@ harness 需要一套统一的内部消息语言,供 agent loop(智能体循 - 推理(reasoning)在核心层有了归属,无需依赖提供方特有的结构。 - 多模态块只有在适配器、UI 和上下文压缩(context compaction)三方协同支持后才会回归;见 [drop-image Agent Note](../simplification/2026-07-04-drop-image-content-block.zh.md)。 -- 缓存提示与 assistant prefill 在有实际适配器能兑现之前保持缺席;见[无生产者的词汇变体](../../archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.zh.md)与[无端到端可用路径的请求旋钮](../../archived/simplification/2026-07-04-drop-inert-request-knobs.zh.md) Agent Note。 +- 缓存提示与 assistant prefill 在有实际适配器能兑现之前保持缺席;见[无生产者的词汇变体](../../archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.md)与[无端到端可用路径的请求旋钮](../../archived/simplification/2026-07-04-drop-inert-request-knobs.md) Agent Note。 - 每个适配器都需承担翻译成本;首批真实适配器已验证了流式输出协议,新适配器应继续在适配器本地测试中验证其提供方特有的映射。 - 跨包边界的 ID 使用品牌类型(`CallId`、agent 与会话共享的 `SessionId`)——零运行时开销的名义类型。 diff --git a/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.i18n.yaml index 96f8e6834f..3af4789414 100644 --- a/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md 2026-06-13-twin-llm-adapters.md: a4c87325a0b0d1ebe6cf8f95672e5de74ef37d57 -2026-06-13-twin-llm-adapters.zh.md: 087f300f871b8b3821925daf25bfbf968062b047 +2026-06-13-twin-llm-adapters.zh.md: 36996750a16cc95393d727cffee3bcc53573eaf2 diff --git a/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.zh.md b/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.zh.md index 087f300f87..36996750a1 100644 --- a/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.zh.md @@ -12,7 +12,7 @@ Status: implemented 从一开始就针对同一份约定交付**两个**适配器,刻意基于不同的内部实现构建: -- `dsh-llm-deepseek`:直接 `fetch` + 仓库内翻译逻辑对接 DeepSeek API;SSE(Server-Sent Events)分帧委托给 `eventsource-parser`([已归档的 SSE 解析器替换](../../archived/simplification/2026-07-26-eventsource-parser-for-deepseek-sse.zh.md))。孪生身份在于自行持有 fetch/translate 内部实现而非委托给完整的提供方 SDK,不在于手写传输层管道。 +- `dsh-llm-deepseek`:直接 `fetch` + 仓库内翻译逻辑对接 DeepSeek API;SSE(Server-Sent Events)分帧委托给 `eventsource-parser`([已归档的 SSE 解析器替换](../../archived/simplification/2026-07-26-eventsource-parser-for-deepseek-sse.md))。孪生身份在于自行持有 fetch/translate 内部实现而非委托给完整的提供方 SDK,不在于手写传输层管道。 - `dsh-llm-pi-ai`:通过 `@earendil-works/pi-ai` 库访问同一端点(该库有自己的事件词汇)。 二者共同执行的规则是:**凡 StreamChunk 词汇无法为两个实现同时表达的内容,都是核心词汇的缺陷**——立即暴露,而非等到下一个提供方接入时才发现。这对孪生适配器确立了现已记录在 `dsh-llm/src/types.ts` 中 `StreamChunk` 上的约定:usage 在 finish 之前发出、finish 之后不再有任何事件、工具调用的 `arguments` 全程以原始 JSON 字符串传递,以及消费方必须在两侧都处理的两条合法错误路径(`stream()` 抛异常,*或者*以 `finish {kind:'error'|'aborted'}` 结束)。这一分歧正是由基于库的适配器暴露出来的,单一直接 fetch 适配器会将其隐藏。 diff --git a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.i18n.yaml index 8bcd0c506e..c3984a9fa9 100644 --- a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.md 2026-06-17-filesystem-capability-seam.md: 6265aebf5e7ffdd4ec4dc0083aa55adb34ee78a1 -2026-06-17-filesystem-capability-seam.zh.md: c1f500641b7c9888bae5f16b904bf7f6d1501331 +2026-06-17-filesystem-capability-seam.zh.md: f0f1cb132fe06ebf669f0310f0066f4fdd36ad0b diff --git a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md index c1f500641b..f0f1cb132f 100644 --- a/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md @@ -32,7 +32,7 @@ Consumer 包仅依赖 Service Definition 包,从不依赖 `dsh-fs-local`。需 第一个后端有意仅限本地:`dsh-fs-local` 基于宿主文件系统实现 `ctx.fs`。未来的兄弟后端可在同一接口之后提供沙箱、远程、虚拟或项目作用域的文件系统。 -第一个消费方有意仅限文本文件:`dsh-tool-fs` 暴露面向模型的 `read`、`write` 和 `edit` 工具,处理 UTF-8 文本文件。未来的消费方可以添加目录列表、搜索/glob、二进制安全操作、文件监视或更高层的项目操作,只要 `ctx.fs` 上存在所需能力,就无需改动本地后端包。直接目录列表后来由[为文件系统 seam 添加直接目录列举能力](../../archived/architecture/2026-07-03-filesystem-directory-listing-seam.zh.md)添加。 +第一个消费方有意仅限文本文件:`dsh-tool-fs` 暴露面向模型的 `read`、`write` 和 `edit` 工具,处理 UTF-8 文本文件。未来的消费方可以添加目录列表、搜索/glob、二进制安全操作、文件监视或更高层的项目操作,只要 `ctx.fs` 上存在所需能力,就无需改动本地后端包。直接目录列表后来由[为文件系统 seam 添加直接目录列举能力](../../archived/architecture/2026-07-03-filesystem-directory-listing-seam.md)添加。 文件系统权限和沙箱并非此拆分所隐含。本地后端从其配置的基目录解析相对路径,但路径包含约束策略是独立的决策:要么由更严格的 `ctx.fs` 实现强制执行,要么由权限/沙箱插件包装 `tools/execute` 并在调用到达消费方之前否决。 @@ -98,7 +98,7 @@ Consumer 包仅依赖 Service Definition 包,从不依赖 `dsh-fs-local`。需 策略插件(而非 `ctx.fs`)对先前观测进行门控:`edit` 要求 owner 有先前观测(否则报 `FS_NOT_OBSERVED`),记录的版本作为 CAS 基础传给 `editText`。在策略插件缺席时,`ctx.fs` 本身是一个完整的无约束 seam(无条件写入/编辑);工具从不与策略方法耦合。 -文件系统约定失败以 `FsError extends HarnessError` 抛出,工具注册表将其转换为带结构化 `{ name, code }` 元数据的 `isError` 工具结果。`dsh-fs` 拥有此词汇,而非由每个工具各自发明消息。错误码包括 `FS_NOT_FOUND`、`FS_NOT_TEXT`、`FS_STALE_VERSION`、`FS_NOT_OBSERVED`、`FS_NOT_REGULAR_FILE`、`FS_AMBIGUOUS_EDIT`、`FS_EDIT_NOT_FOUND` 和 `FS_ABORTED`。(早期草案包含 `FS_PARTIAL_OBSERVATION`;基于新鲜度的授权没有 partial/full 区分,因此已删除。目录列表相关的错误码后来由[为文件系统 seam 添加直接目录列举能力](../../archived/architecture/2026-07-03-filesystem-directory-listing-seam.zh.md)添加。) +文件系统约定失败以 `FsError extends HarnessError` 抛出,工具注册表将其转换为带结构化 `{ name, code }` 元数据的 `isError` 工具结果。`dsh-fs` 拥有此词汇,而非由每个工具各自发明消息。错误码包括 `FS_NOT_FOUND`、`FS_NOT_TEXT`、`FS_STALE_VERSION`、`FS_NOT_OBSERVED`、`FS_NOT_REGULAR_FILE`、`FS_AMBIGUOUS_EDIT`、`FS_EDIT_NOT_FOUND` 和 `FS_ABORTED`。(早期草案包含 `FS_PARTIAL_OBSERVATION`;基于新鲜度的授权没有 partial/full 区分,因此已删除。目录列表相关的错误码后来由[为文件系统 seam 添加直接目录列举能力](../../archived/architecture/2026-07-03-filesystem-directory-listing-seam.md)添加。) ## 工具消费方行为 diff --git a/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.i18n.yaml index 1a7908de99..f0e60bbc20 100644 --- a/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md 2026-06-24-web-capability-seam.md: 7e7b09f19864bd2ad8ad9d69579c1d5c79600cde -2026-06-24-web-capability-seam.zh.md: 88bd10705837496e7f162c992717131618b57a46 +2026-06-24-web-capability-seam.zh.md: dbb41ee42d2c7503955ead2df32abe80b3a4f641 diff --git a/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.zh.md b/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.zh.md index 88bd107058..dbb41ee42d 100644 --- a/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.zh.md @@ -34,7 +34,7 @@ Web 访问是一个一等能力 seam,遵循[能力 seam Agent Note](2026-06-13 这使模型 schema 保持稳定,而不将插件加载顺序、凭证状态或 HMR(热模块替换)时序纳入面向模型的约定。如果 web 搜索已启用但不存在可用的搜索提供方,`web_search` 仍然可见,执行时以结构化的 `WebError`(如 `WEB_PROVIDER_UNAVAILABLE` 或 `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`)失败。如果某个提供方在 `dsh-tool-web` 之后出现,下一次执行即可使用它而无需更改 schema。如果某个提供方在调用过程中消失,执行以结构化的 `WebError` 失败,而不是静默选择另一个提供方或回退到 `UNKNOWN_TOOL`。 -该 seam 刻意不暴露任何观察面——没有注册表变更事件,也没有聚合的能力状态查询。不可用性是调用方通过执行观察到的事实:`search()`/`fetch()` 在调用时解析提供方,并抛出命名了失败原因的结构化 `WebError`。[观察面 Agent Note](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.zh.md) 记录了这一判断:基于调用的派生选择与基于启用的注册使得没有消费方需要变更信号或独立于执行和错误路由的可用性探测;未来的提供方状态面板会重新引入它实际消费的最小信号或查询。 +该 seam 刻意不暴露任何观察面——没有注册表变更事件,也没有聚合的能力状态查询。不可用性是调用方通过执行观察到的事实:`search()`/`fetch()` 在调用时解析提供方,并抛出命名了失败原因的结构化 `WebError`。[观察面 Agent Note](../../archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md) 记录了这一判断:基于调用的派生选择与基于启用的注册使得没有消费方需要变更信号或独立于执行和错误路由的可用性探测;未来的提供方状态面板会重新引入它实际消费的最小信号或查询。 ## 包拓扑 diff --git a/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.i18n.yaml index 2b51b21c05..feb512101f 100644 --- a/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.md 2026-06-30-event-domain-semantics.md: 70da718b5471ce309a090c8aade3e7290cc949dc -2026-06-30-event-domain-semantics.zh.md: f5d61da445f90514bd9b536bbed48734f03b8008 +2026-06-30-event-domain-semantics.zh.md: c3b12a167da0a41b792914d82a675a98b3a0b860 diff --git a/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.zh.md b/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.zh.md index f5d61da445..c3b12a167d 100644 --- a/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.zh.md @@ -33,7 +33,7 @@ harness 通过 Cordis 事件分类体系扩展 agent loop(智能体循环) - 循环不再 emit 任何边界镜像;`closeStep` 仅追加 `step/end`,`closeTurn` 仅追加 `turn/end`。`Session.append` 负责 post-commit observer 隔离,因此抛出异常的边界 observer 无法改变轮次结果或饿死后续消费方;事件接纳失败或内部校验失败仍会在边界进入日志之前向外抛出。 - 之前通过已移除 emit 观察边界的测试,现在观察持久的 `turn/start`/`turn/end`/`step/start`/`step/end` 会话事件——它们所锁定的行为(边界顺序、步骤计数)不变;只是读取的源移到了规范源。那些测试*抛出异常的轮次边界 emit 监听器*的用例被删除,因为该代码路径不再存在(没有 emit 可供抛出)。按照 [AGENTS.md「测试记录行为,而非黄金真相」](../../../../AGENTS.md),行为与其测试一同迁移(或一同消亡)。 - 循环仅在 `append('step/start')` 返回后才标记步骤已打开(`stepOpen = true`)。内部分发校验在日志推入之前运行,可能在不打开步骤的情况下拒绝;post-commit `session/event` observer 的失败被隔离在 `Session.append` 内部。因此该标记精确表示已提交的、欠一个后续 `step/end` 的边界。 -- 完整实现见[简化 Agent Note「停止将持久边界镜像为 agent 事件」](../simplification/2026-06-20-remove-agent-boundary-mirror-events.zh.md):全部四个边界镜像被移除,所有消费方从 `session/event` 读取边界。`agent/steering`(不是边界镜像)不在该 Agent Note 范围内,由其后续 Agent Note [移除 `agent/steering` 镜像 emit](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.zh.md) 单独移除——它镜像的是持久的中途 steering `user/message`。 +- 完整实现见[简化 Agent Note「停止将持久边界镜像为 agent 事件」](../simplification/2026-06-20-remove-agent-boundary-mirror-events.zh.md):全部四个边界镜像被移除,所有消费方从 `session/event` 读取边界。`agent/steering`(不是边界镜像)不在该 Agent Note 范围内,由其后续 Agent Note [移除 `agent/steering` 镜像 emit](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.md) 单独移除——它镜像的是持久的中途 steering `user/message`。 - 生成的 Cordis 事件表面(`docs/subsystems/` 各页)不再列出镜像事件。 diff --git a/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.i18n.yaml index 4888175ce2..933b3d2826 100644 --- a/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md 2026-07-02-tool-render-intent-union.md: 52626d3d9200146df95aee7836d37bbaf7e6a9ec -2026-07-02-tool-render-intent-union.zh.md: 544a8b14e66a93c717e156152817b473ba70c8ab +2026-07-02-tool-render-intent-union.zh.md: bc91b89c53c3c43b6c483dc0d4876a00f5a936b7 diff --git a/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.zh.md b/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.zh.md index 544a8b14e6..bc91b89c53 100644 --- a/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.zh.md @@ -78,5 +78,5 @@ terminal 意图只用于展示。harness 仍通过自身的 bash 服务执行命 ## 相关 - 取代早先被否决的折叠工具自有呈现提案(已否决——「等两个真实工具和两个真实消费方,然后做带标签 render-intent 联合类型」)中的推迟决定。该条件现已满足;本 Agent Note 即为那个联合类型。 -- 被[结果时已应用 hunk 差异](../../archived/architecture/2026-07-02-result-time-applied-hunk-diffs.zh.md)(已归档)扩展:后者添加了一个持久化的 `meta` 通道,使 write/edit 在结果时输出 `DiffResultView`(应用后的变更:带上下文行的 contextual hunk / 每个 `replace_all` 位点一个,或创建时的整文件 diff)——值/呈现拆分与持久化的 `presentationMeta` 通道现由[规范工具输出约定](2026-07-20-canonical-tool-output-contract.zh.md)拥有。 +- 被[结果时已应用 hunk 差异](../../archived/architecture/2026-07-02-result-time-applied-hunk-diffs.md)(已归档)扩展:后者添加了一个持久化的 `meta` 通道,使 write/edit 在结果时输出 `DiffResultView`(应用后的变更:带上下文行的 contextual hunk / 每个 `replace_all` 位点一个,或创建时的整文件 diff)——值/呈现拆分与持久化的 `presentationMeta` 通道现由[规范工具输出约定](2026-07-20-canonical-tool-output-contract.zh.md)拥有。 - 将 `ToolTerminal` 折入当前 UI 传输层使用的带标签 `terminal` 视图。 diff --git a/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.i18n.yaml index 6ae03ebc10..4bb9345820 100644 --- a/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.md 2026-07-22-unified-send-and-coalesced-user-messages.md: bc195e77c71b554d60c03c91134bbcef95678fff -2026-07-22-unified-send-and-coalesced-user-messages.zh.md: 367a20c528b52f71ded9cfc324f3c3b4bbec8dc0 +2026-07-22-unified-send-and-coalesced-user-messages.zh.md: 414b5542aae2972b22ba015ba9397ec44921a6fa diff --git a/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md b/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md index 367a20c528..414b5542aa 100644 --- a/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md @@ -48,6 +48,6 @@ agent(智能体)的对外驱动接口逐渐长出三个近乎平行的动词 ## 相关 - [one-send-one-turn](../simplification/2026-07-17-one-send-one-turn.zh.md)——本决策所依托的「每轮次只认领一条消息」规则。 -- [remove-agent-steering-mirror](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.zh.md)——折叠镜像实时事件的先例。 +- [remove-agent-steering-mirror](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.md)——折叠镜像实时事件的先例。 - [explicit-turn-cancellation](2026-07-16-explicit-turn-cancellation.zh.md)——`keepInbox` 所扩展的取消原因信号。 - [带标识的不可变消息值](2026-07-28-identified-immutable-message-values.zh.md)——本路由决策现在所依托的消息标识与表示约定。 diff --git a/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.i18n.yaml index 86c7b4ee82..4ec48834d9 100644 --- a/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.md 2026-07-29-dsh-source-launch-tsx-esm.md: 425cf46ca802e8d3ac1d49a1955c5e7ea208bd20 -2026-07-29-dsh-source-launch-tsx-esm.zh.md: 3c538680614035b3bc14e2c758fabaeab1bc41bc +2026-07-29-dsh-source-launch-tsx-esm.zh.md: 87b323c82f28135396d1d16991e2365c691fcc6a diff --git a/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.zh.md b/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.zh.md index 3c53868061..87b323c82f 100644 --- a/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.zh.md @@ -4,11 +4,11 @@ Status: implemented [English](2026-07-29-dsh-source-launch-tsx-esm.md) | 中文 -> 取代[原生 TypeScript 源码启动](../../archived/architecture/2026-07-28-dsh-native-typescript-source-launch.zh.md):Node 移除了该决策所依赖的能力。 +> 取代[原生 TypeScript 源码启动](../../archived/architecture/2026-07-28-dsh-native-typescript-source-launch.md):Node 移除了该决策所依赖的能力。 ## 问题 -[已归档的原生源码启动决策](../../archived/architecture/2026-07-28-dsh-native-typescript-source-launch.zh.md)让 `apps/cli/src/bin.ts` 在 `node --experimental-transform-types` 下运行,配合一个只做解析的 paths loader,由 Node 负责 TypeScript 转换。Node 26.0.0 移除了 `--experimental-transform-types`(进程以 `bad option` 拒绝该 flag),只保留 strip 模式,而 strip 模式无法接受这个源码图必需的语法:vendor Cordis 中的参数属性(`constructor(private ctx: Context)`)、`vendor/hmr` 中的 `@Inject` 装饰器,以及遍布 `vendor/` 与 `packages/workflow` 的运行时 enum/namespace。仓库的 engines 范围(`^22.19.0 || >=24.0.0`)包含 Node 26,因此原生启动链在其上完全无法启动——且没有任何 CI 任务执行过真实启动向量,这一不兼容悄然发布。 +[已归档的原生源码启动决策](../../archived/architecture/2026-07-28-dsh-native-typescript-source-launch.md)让 `apps/cli/src/bin.ts` 在 `node --experimental-transform-types` 下运行,配合一个只做解析的 paths loader,由 Node 负责 TypeScript 转换。Node 26.0.0 移除了 `--experimental-transform-types`(进程以 `bad option` 拒绝该 flag),只保留 strip 模式,而 strip 模式无法接受这个源码图必需的语法:vendor Cordis 中的参数属性(`constructor(private ctx: Context)`)、`vendor/hmr` 中的 `@Inject` 装饰器,以及遍布 `vendor/` 与 `packages/workflow` 的运行时 enum/namespace。仓库的 engines 范围(`^22.19.0 || >=24.0.0`)包含 Node 26,因此原生启动链在其上完全无法启动——且没有任何 CI 任务执行过真实启动向量,这一不兼容悄然发布。 启动延迟同样是问题:off-thread 的 `module.register()` 钩子工作线程把每次解析都跨线程序列化(TUI 启动期间约 440ms 的 `makeSyncRequest` 等待),而完整的 tsx 默认形态(`--import tsx`)会因其 CJS 钩子放大解析开销而多花约 0.4s。 diff --git a/.agents/notes/implemented/architecture/2026-07-29-package-regrouping.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-29-package-regrouping.i18n.yaml index 6980d6ba8f..134bbf1d6b 100644 --- a/.agents/notes/implemented/architecture/2026-07-29-package-regrouping.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-29-package-regrouping.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-29-package-regrouping.md 2026-07-29-package-regrouping.md: 52a1fa28e4826daa7b0bb84a37be3c094fddb68c -2026-07-29-package-regrouping.zh.md: adcbec13575cf904d4c41a12f128723d49904b99 +2026-07-29-package-regrouping.zh.md: bfba4c6bfc6190956b40b295b650d0bed78eb3f4 diff --git a/.agents/notes/implemented/architecture/2026-07-29-package-regrouping.zh.md b/.agents/notes/implemented/architecture/2026-07-29-package-regrouping.zh.md index adcbec1357..bfba4c6bfc 100644 --- a/.agents/notes/implemented/architecture/2026-07-29-package-regrouping.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-29-package-regrouping.zh.md @@ -6,7 +6,7 @@ Status: implemented ## 问题 -两级 `packages//` 层级结构([原始决策](../../archived/architecture/2026-06-20-package-hierarchy.zh.md))自 6 月以来已经漂移:167 个包彼时坐落在 42 个组里,若干组边界已经对不上这些包的实际聚类。 +两级 `packages//` 层级结构([原始决策](../../archived/architecture/2026-06-20-package-hierarchy.md))自 6 月以来已经漂移:167 个包彼时坐落在 42 个组里,若干组边界已经对不上这些包的实际聚类。 - `ui/` 混杂了四个互不相关的平面:人类终端通道(`tui`)、SDK 的 JSON-RPC 服务端一半(`jsonrpc`,它对 `dsh-sdk-protocol` 的对等依赖(peer dependency)把它绑在 SDK 通信栈上)、人机交互 seam(`user-questions`、`user-approval`、`permission`、`tool-ask-user`、`commands`),以及与通道无关的 boot 胶水(`app-boot`)。它自己的 README 只能逐一叙述这堆混杂,说不出一个统一职责。 - 会话家族被割裂在五个组里——`session-persistence/`、`session-projection/`、`session-query/`、`session-title/` 与 `telemetry/`——而实测依赖边明明把它们连成一体(query → persistence、title → projection、projection → persistence;见 [docs/module-graph.md](../../../../docs/module-graph.zh.md))。 diff --git a/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.i18n.yaml index 32a3fda889..9b41e2e0cf 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.md 2026-07-31-claimed-pre-step-inbox-lifecycle.md: 73768e1eee8957f8976d40812b0a31a2961f0825 -2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md: 98007b342fd7e8f80419417047f29146a9c19057 +2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md: 343816abaf394b8f64924cf36b753c6b1b2e34ca diff --git a/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md b/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md index 98007b342f..343816abaf 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md @@ -22,7 +22,7 @@ Status: implemented 必须对当前步骤进行原子改写的插件从 `agent/pre-step` 返回消息。只需要稍后上下文的插件可以直接修改 `agent.inbox`。Workspace context 同时使用两条路径:异步文件系统投影会暂存一条可替换的 `next-step` 消息,而下一次进入步骤的 pre-step 会把该消息或新组合的基线折入最终批次,并移除仍待处理的副本。reject 会让该条目继续排队。 -已归档的[可寻址队列项决策](../../archived/feature/2026-07-29-addressable-queue-operations.zh.md)描述了已被取代的单次出现包装层设计。现在由 `MessageId` 负责寻址,而保留的 Host 队列镜像根据持久 splice 投影派生快照。 +已归档的[可寻址队列项决策](../../archived/feature/2026-07-29-addressable-queue-operations.md)描述了已被取代的单次出现包装层设计。现在由 `MessageId` 负责寻址,而保留的 Host 队列镜像根据持久 splice 投影派生快照。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/architecture/2026-08-01-packaged-ripgrep-search.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-01-packaged-ripgrep-search.i18n.yaml index 6103cbb0ba..870bf8ec05 100644 --- a/.agents/notes/implemented/architecture/2026-08-01-packaged-ripgrep-search.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-01-packaged-ripgrep-search.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-01-packaged-ripgrep-search.md 2026-08-01-packaged-ripgrep-search.md: c401bc1e30df0c3443ad58b37f64523b1c10cb31 -2026-08-01-packaged-ripgrep-search.zh.md: 58465242acc398c970435dc05de49c10ea8ad454 +2026-08-01-packaged-ripgrep-search.zh.md: 6a195e59f77aa73b9d0918461cef45360e617098 diff --git a/.agents/notes/implemented/architecture/2026-08-01-packaged-ripgrep-search.zh.md b/.agents/notes/implemented/architecture/2026-08-01-packaged-ripgrep-search.zh.md index 58465242ac..6a195e59f7 100644 --- a/.agents/notes/implemented/architecture/2026-08-01-packaged-ripgrep-search.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-01-packaged-ripgrep-search.zh.md @@ -4,11 +4,11 @@ Status: implemented [English](2026-08-01-packaged-ripgrep-search.md) | 中文 -> 取代 [bash 承载的 grep/glob 发现工具](../../archived/feature/2026-07-09-bash-backed-grep-glob-discovery.zh.md):v1 决策中明确延期的方案——直接 spawn ripgrep——现在成为实际交付的实现。 +> 取代 [bash 承载的 grep/glob 发现工具](../../archived/feature/2026-07-09-bash-backed-grep-glob-discovery.md):v1 决策中明确延期的方案——直接 spawn ripgrep——现在成为实际交付的实现。 ## 问题 -`glob`/`grep` 工具经由 bash 执行器 seam 运行,这使系统 `rg` 安装成为宿主依赖。Windows 和容器镜像的 `PATH` 默认没有 `rg`,工具在那里会静默消失;部署方只能从加载期探针警告里发现这一点。bash seam 还迫使整个模型可见参数面经过一个 shell 引号工具,因为工具与 ripgrep 之间隔着一层 shell——[bash 承载决策](../../archived/feature/2026-07-09-bash-backed-grep-glob-discovery.zh.md) 把这种耦合记为 v1 的取舍,并把直接 spawn 列为 shell 字符串域一旦被证明过于敏感时的合理后续。它确实被证明了:每个模型值都要经受 POSIX 单引号转义,探针要在测试里脚本化,执行器自身的超时分类还与协作式工具超时策略已有的职责重复。 +`glob`/`grep` 工具经由 bash 执行器 seam 运行,这使系统 `rg` 安装成为宿主依赖。Windows 和容器镜像的 `PATH` 默认没有 `rg`,工具在那里会静默消失;部署方只能从加载期探针警告里发现这一点。bash seam 还迫使整个模型可见参数面经过一个 shell 引号工具,因为工具与 ripgrep 之间隔着一层 shell——[bash 承载决策](../../archived/feature/2026-07-09-bash-backed-grep-glob-discovery.md) 把这种耦合记为 v1 的取舍,并把直接 spawn 列为 shell 字符串域一旦被证明过于敏感时的合理后续。它确实被证明了:每个模型值都要经受 POSIX 单引号转义,探针要在测试里脚本化,执行器自身的超时分类还与协作式工具超时策略已有的职责重复。 ## 决策 diff --git a/.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.i18n.yaml index b8d7abff97..d9ad41a205 100644 --- a/.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.md 2026-08-09-headless-direct-core-entry-point.md: cf6b4a92a6e9b390c7fcaca17f56b4c652cc9319 -2026-08-09-headless-direct-core-entry-point.zh.md: fdd3eca5f7ea0ab976102853bcf9f6e814ab4f92 +2026-08-09-headless-direct-core-entry-point.zh.md: 9f45fcdaf87ddcccbd331eeacce0dc7035c61f19 diff --git a/.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.zh.md b/.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.zh.md index fdd3eca5f7..9f45fcdaf8 100644 --- a/.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-09-headless-direct-core-entry-point.zh.md @@ -20,7 +20,7 @@ Status: implemented `loadProfile` 识别安装过程拥有的精确 headless 元组(`dsh-base`、`dsh-web-app`、`dsh-headless`),将其规范化为随附的 headless 模板,并保留 manifest(元数据清单)的其他所有字段。带额外项、缺少项或顺序不同的组合包列表归用户所有,保持不变。 -本 Agent Note 负责 headless 的传输与完成约定。[应用持有自己的命令行](2026-08-06-app-owned-command-line.zh.md)负责当前的 `dsh --profile headless` 语法;原 [`dsh run` 决策](../../archived/feature/2026-08-08-dsh-run-headless-command.zh.md)记录已被取代的启动器持有语法,[GUI 分层与 RPC 协议](2026-07-19-gui-layering-and-rpc-protocol.zh.md)负责浏览器网关边界,[Web 配置树启动与传输分层](2026-07-24-web-config-tree-boot-and-transport-layering.zh.md)负责 Web 插件树,[默认模型跟随选择器](../feature/2026-08-07-default-model-follows-the-picker.zh.md)负责共享 Agent 默认值的持久化。 +本 Agent Note 负责 headless 的传输与完成约定。[应用持有自己的命令行](2026-08-06-app-owned-command-line.zh.md)负责当前的 `dsh --profile headless` 语法;原 [`dsh run` 决策](../../archived/feature/2026-08-08-dsh-run-headless-command.md)记录已被取代的启动器持有语法,[GUI 分层与 RPC 协议](2026-07-19-gui-layering-and-rpc-protocol.zh.md)负责浏览器网关边界,[Web 配置树启动与传输分层](2026-07-24-web-config-tree-boot-and-transport-layering.zh.md)负责 Web 插件树,[默认模型跟随选择器](../feature/2026-08-07-default-model-follows-the-picker.zh.md)负责共享 Agent 默认值的持久化。 ## 验证 diff --git a/.agents/notes/implemented/bug-fix/2026-07-29-human-transcript-append-origin.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-29-human-transcript-append-origin.i18n.yaml index 3a1046a2a6..cc523de6ac 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-29-human-transcript-append-origin.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-07-29-human-transcript-append-origin.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-29-human-transcript-append-origin.md 2026-07-29-human-transcript-append-origin.md: 5e9b5c254d54a7eb189f353f44cd6eb18c1bc2df -2026-07-29-human-transcript-append-origin.zh.md: 2c9d0ed484a586b26bad3ed8dcf271332eebd28b +2026-07-29-human-transcript-append-origin.zh.md: 51cf6e2f122f8f452213ce1d5ed02bb1a7eeba70 diff --git a/.agents/notes/implemented/bug-fix/2026-07-29-human-transcript-append-origin.zh.md b/.agents/notes/implemented/bug-fix/2026-07-29-human-transcript-append-origin.zh.md index 2c9d0ed484..51cf6e2f12 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-29-human-transcript-append-origin.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-07-29-human-transcript-append-origin.zh.md @@ -26,7 +26,7 @@ Status: implemented 浏览器客户端在[Web transcript 投影笔记](2026-07-30-web-transcript-log-ordered-projection.zh.md)中单独修复:它按日志顺序投影同一份追加来源 transcript 并渲染一个标记组件,同时闭合本次变更打开的分页缺口——因为 `session.history` 不再为检查点消耗额度,它永远不会在检查点与检查点引用的来源事件这个整体内切分,于是一页可以携带一个引用了窗口之外 `surfaceOp.start` 的检查点,而浏览器的 surface fold 会拒绝该范围。这个缺口早于本次变更(此前计数就可能越过检查点进入它所遮蔽的范围),但当检查点是最旧的被计数消息时,旧分页规则会把整段被遮蔽的范围放在同一页。 -终端的[已归档实时压缩进度决策](../../archived/feature/2026-07-30-compaction-progress-visibility.zh.md)使用独立标记对中的事件驱动现有的单格指示器。它既不改变本文所负责的完成标记,也不添加规模信息:检查点的 `sourceEventSeqs` 仍可供经另行论证的计数或区间使用。因此,进度显示既不需要修改标记内容,也不以提取 `renderReplacement(event)` 为前置条件。 +终端的[已归档实时压缩进度决策](../../archived/feature/2026-07-30-compaction-progress-visibility.md)使用独立标记对中的事件驱动现有的单格指示器。它既不改变本文所负责的完成标记,也不添加规模信息:检查点的 `sourceEventSeqs` 仍可供经另行论证的计数或区间使用。因此,进度显示既不需要修改标记内容,也不以提取 `renderReplacement(event)` 为前置条件。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/bug-fix/2026-07-29-web-details-session-lifecycle.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-29-web-details-session-lifecycle.i18n.yaml index a7da9315c3..89116403f6 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-29-web-details-session-lifecycle.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-07-29-web-details-session-lifecycle.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-29-web-details-session-lifecycle.md 2026-07-29-web-details-session-lifecycle.md: 41b89fc059a02e56f53b27e5a5b48fb9488b93d7 -2026-07-29-web-details-session-lifecycle.zh.md: 3d631a93d6bc8f2e292142b0337cb73869ea494e +2026-07-29-web-details-session-lifecycle.zh.md: af0565bf78b17ab91861b260537f4c70ef43f4f9 diff --git a/.agents/notes/implemented/bug-fix/2026-07-29-web-details-session-lifecycle.zh.md b/.agents/notes/implemented/bug-fix/2026-07-29-web-details-session-lifecycle.zh.md index 3d631a93d6..af0565bf78 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-29-web-details-session-lifecycle.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-07-29-web-details-session-lifecycle.zh.md @@ -10,7 +10,7 @@ Status: implemented ## 决策 -`AppFrame` 从权威会话投影读取当前会话 id 及其摘要中的 `blank` 标志。它只在该会话能够拥有详情时记录最后一个选中的非 blank 会话 id,因此 hero 和其他未选中状态既不会触发关闭,也不会替换最后一个会话 owner;这些状态下,详情栏轨道的渲染宽度派生为零,但存储的首选宽度不变。首个会话保留布局 store 的初始首选值,其[已归档的可见性默认值决策](../../archived/bug-fix/2026-07-30-web-details-default-closed.zh.md)选择关闭;返回同一会话时恢复其当前宽度;选择不同会话时,系统会先通过布局 store 关闭根作用域存储的详情栏首选宽度,再进行绘制。逐会话的聊天选中项继续由 [slot 体系标准](../architecture/2026-07-22-slot-type-chain-implementation.zh.md)所述的会话作用域 store 拥有。 +`AppFrame` 从权威会话投影读取当前会话 id 及其摘要中的 `blank` 标志。它只在该会话能够拥有详情时记录最后一个选中的非 blank 会话 id,因此 hero 和其他未选中状态既不会触发关闭,也不会替换最后一个会话 owner;这些状态下,详情栏轨道的渲染宽度派生为零,但存储的首选宽度不变。首个会话保留布局 store 的初始首选值,其[已归档的可见性默认值决策](../../archived/bug-fix/2026-07-30-web-details-default-closed.md)选择关闭;返回同一会话时恢复其当前宽度;选择不同会话时,系统会先通过布局 store 关闭根作用域存储的详情栏首选宽度,再进行绘制。逐会话的聊天选中项继续由 [slot 体系标准](../architecture/2026-07-22-slot-type-chain-implementation.zh.md)所述的会话作用域 store 拥有。 布局 store 是瞬时状态,详情栏在启动时保持关闭。它既不读取也不写入 `localStorage`,因此重新加载会恢复侧边栏默认值,并使详情栏保持关闭,无需会话基线例外。在同一个未变化的会话内手动关闭和重新打开详情栏,仍保持原有行为。该生命周期 effect 不改变 [Workspace 拥有的 New Session 动线](../feature/2026-07-25-workspace-ui-product-flow.zh.md)、composer 草稿、会话导航或让步链缩放。 diff --git a/.agents/notes/implemented/bug-fix/2026-07-30-bounded-overwrite-diff-basis.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-30-bounded-overwrite-diff-basis.i18n.yaml index f6f1febbac..ae909cb72b 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-30-bounded-overwrite-diff-basis.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-07-30-bounded-overwrite-diff-basis.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-30-bounded-overwrite-diff-basis.md 2026-07-30-bounded-overwrite-diff-basis.md: 7a09934bd1798059de43a092f338d37aa9ccbd9a -2026-07-30-bounded-overwrite-diff-basis.zh.md: 456d312d98c85988901e4d198308e664eb411808 +2026-07-30-bounded-overwrite-diff-basis.zh.md: 761c0e6e5591ba8d31f7a43ac555d4b10b97659a diff --git a/.agents/notes/implemented/bug-fix/2026-07-30-bounded-overwrite-diff-basis.zh.md b/.agents/notes/implemented/bug-fix/2026-07-30-bounded-overwrite-diff-basis.zh.md index 456d312d98..761c0e6e55 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-30-bounded-overwrite-diff-basis.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-07-30-bounded-overwrite-diff-basis.zh.md @@ -6,7 +6,7 @@ Status: implemented ## 问题 -`dsh-fs-local` 会在 `FsWriteOutcome.before` 中返回完整旧文件,供消费方生成覆写上下文 diff。这个仅用于展示的预读没有上限:大文件覆写可能分配整个旧文件;而仅检查较早的路径 stat 也无法真正实施上限,因为外部进程可以在 stat 与读取之间替换文件或扩大文件。即使旧文件很小,大替换内容也会使上下文 hunk 接近替换内容本身的大小。本改动关闭了 [result-time applied-hunk diff](../../archived/architecture/2026-07-02-result-time-applied-hunk-diffs.zh.md) 中记录的暂缓上限事项。 +`dsh-fs-local` 会在 `FsWriteOutcome.before` 中返回完整旧文件,供消费方生成覆写上下文 diff。这个仅用于展示的预读没有上限:大文件覆写可能分配整个旧文件;而仅检查较早的路径 stat 也无法真正实施上限,因为外部进程可以在 stat 与读取之间替换文件或扩大文件。即使旧文件很小,大替换内容也会使上下文 hunk 接近替换内容本身的大小。本改动关闭了 [result-time applied-hunk diff](../../archived/architecture/2026-07-02-result-time-applied-hunk-diffs.md) 中记录的暂缓上限事项。 ## 决策 diff --git a/.agents/notes/implemented/bug-fix/2026-07-30-source-checkout-workdir-distinction.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-30-source-checkout-workdir-distinction.i18n.yaml index 0dc62d1951..527471cd2e 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-30-source-checkout-workdir-distinction.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-07-30-source-checkout-workdir-distinction.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-30-source-checkout-workdir-distinction.md 2026-07-30-source-checkout-workdir-distinction.md: ba6d9dd12b55a54d4ae8d2e91ad83ac3c1dc47fd -2026-07-30-source-checkout-workdir-distinction.zh.md: 858119374f74c97e1381ac9c09250c5e7c29cf3e +2026-07-30-source-checkout-workdir-distinction.zh.md: 06b09f82f294a70795090a9aec1b2220bd6e7512 diff --git a/.agents/notes/implemented/bug-fix/2026-07-30-source-checkout-workdir-distinction.zh.md b/.agents/notes/implemented/bug-fix/2026-07-30-source-checkout-workdir-distinction.zh.md index 858119374f..06b09f82f2 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-30-source-checkout-workdir-distinction.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-07-30-source-checkout-workdir-distinction.zh.md @@ -6,7 +6,7 @@ Status: implemented ## 问题 -`harness:source` 提示词段遵循[源码位置决策](../../archived/feature/2026-07-21-dsh-system-prompt-source-path.zh.md),但原有措辞把 checkout 称为「你自己的源代码」,却没有区分该路径与会话 workspace。在 persona 不声明 `{{cwd}}` 的普通 TUI 配置中,这可能是系统提示词开头附近唯一固定的绝对路径。因此,DeepSeek V4 可能会直接用 harness checkout 回答「what's the workdir?」,而不是确定会话的当前工作目录。 +`harness:source` 提示词段遵循[源码位置决策](../../archived/feature/2026-07-21-dsh-system-prompt-source-path.md),但原有措辞把 checkout 称为「你自己的源代码」,却没有区分该路径与会话 workspace。在 persona 不声明 `{{cwd}}` 的普通 TUI 配置中,这可能是系统提示词开头附近唯一固定的绝对路径。因此,DeepSeek V4 可能会直接用 harness checkout 回答「what's the workdir?」,而不是确定会话的当前工作目录。 直接断言 checkout 不是工作目录同样不准确。`dsh meta` 会有意让源码 checkout 同时充当这两个值。 diff --git a/.agents/notes/implemented/bug-fix/2026-07-30-web-transcript-log-ordered-projection.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-30-web-transcript-log-ordered-projection.i18n.yaml index 74dc10a926..a33ceea5e1 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-30-web-transcript-log-ordered-projection.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-07-30-web-transcript-log-ordered-projection.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-30-web-transcript-log-ordered-projection.md 2026-07-30-web-transcript-log-ordered-projection.md: 102bdab27eddab8f3f61390b0025c0d6ce0c3edc -2026-07-30-web-transcript-log-ordered-projection.zh.md: 8b25d79e36cbed2e0af914d8aa7874fdcf7af087 +2026-07-30-web-transcript-log-ordered-projection.zh.md: d7f6c980db1eeb58fa1315b50ecbd2a62e81e86c diff --git a/.agents/notes/implemented/bug-fix/2026-07-30-web-transcript-log-ordered-projection.zh.md b/.agents/notes/implemented/bug-fix/2026-07-30-web-transcript-log-ordered-projection.zh.md index 8b25d79e36..d7f6c980db 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-30-web-transcript-log-ordered-projection.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-07-30-web-transcript-log-ordered-projection.zh.md @@ -75,4 +75,4 @@ Web e2e 场景现在围绕它录制的那一轮上的压缩事务播种一次真 ## Deferred -终端的[已归档压缩进度决策](../../archived/feature/2026-07-30-compaction-progress-visibility.zh.md)使用实时独立标记对驱动单格指示器,并不改变此浏览器投影。 +终端的[已归档压缩进度决策](../../archived/feature/2026-07-30-compaction-progress-visibility.md)使用实时独立标记对驱动单格指示器,并不改变此浏览器投影。 diff --git a/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.i18n.yaml index 643e598729..f4a1549d87 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.md 2026-08-05-turn-tail-actions-require-a-completed-turn.md: 44a890c955089096204a5b2a2833905c9ef9f7ed -2026-08-05-turn-tail-actions-require-a-completed-turn.zh.md: 8826bcd0c73832d0915703fc1ab3b01466071b30 +2026-08-05-turn-tail-actions-require-a-completed-turn.zh.md: 71eb7acc42e3c03065891a92c2ce82ce12e2cda0 diff --git a/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.zh.md b/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.zh.md index 8826bcd0c7..71eb7acc42 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-08-05-turn-tail-actions-require-a-completed-turn.zh.md @@ -8,7 +8,7 @@ Status: implemented assistant IconActions 此前只从已定稿的 transcript(文本记录)推导:每个轮次中最后一条含内容文本的 assistant 拥有该行。这个量只有在轮次关闭后才稳定。轮次仍在产出步骤时,模型在工具调用前写下的叙述就是当时该轮次的最后一条内容 assistant,于是它在工具执行期间取得该行,等下一步的文本落定又把它交出去。读者会看到复制、分支和时钟出现在一句中间叙述下方,把流程推开一行 28px,然后消失。该行在这个状态下本身也是残缺的:分支控件已经通过 `turnEnds` 判定为禁用,`Ran for` 标签已经通过 `turnTimings` 判定为不显示,只有复制可用。 -[已归档的消息 chrome 决策](../../archived/feature/2026-07-29-web-message-icon-actions-and-clock.zh.md)一直声称轮次中间的叙述不带 chrome,但推导过程从未拿到能让这句话成立的完成信号。 +[已归档的消息 chrome 决策](../../archived/feature/2026-07-29-web-message-icon-actions-and-clock.md)一直声称轮次中间的叙述不带 chrome,但推导过程从未拿到能让这句话成立的完成信号。 ## 决策 diff --git a/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml b/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml index fed77ab036..13987435b3 100644 --- a/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-06-15-code-mode.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-06-15-code-mode.md 2026-06-15-code-mode.md: dbf77557c9f1aa59deed443b74ca3ef83137d773 -2026-06-15-code-mode.zh.md: dd182888c2f8980c5344853865a7fdfe5d58b641 +2026-06-15-code-mode.zh.md: 091623adcf1b543b104a20482f7a0106a6437f1d diff --git a/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md b/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md index dd182888c2..091623adcf 100644 --- a/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md +++ b/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md @@ -50,7 +50,7 @@ Cloudflare 的 [Code Mode](https://blog.cloudflare.com/code-mode/) 提出了一 **并发是有界的,而非被序列化。** 每次 run 拥有一个分发队列,严格按提交顺序启动调用,并通过 `registry.executionMode` 对每个调用分类——与原生循环所用的 fail-closed `isConcurrencySafe` 约定相同。连续的 parallel 类调用最多重叠 `maxParallelSubCalls` 个(默认 10;设为 `1` 恢复串行分发);exclusive 类调用会排空池并单独运行。结算时放弃尚未开始的排队调用。本 note 交付的是被序列化的占位实现;取代它的调度器由[实时并行 Agent Note](2026-07-26-code-mode-live-parallel-dispatch.zh.md) 负责。 -**呈现。** `run_code` 的 render intent 按[呈现意图 Agent Note](../architecture/2026-07-02-tool-render-intent-union.zh.md)在此决定:`presentCall` 创建一个 `generic` 卡片,`kind: 'execute'`,以程序文本作为标题,并将同一程序文本作为 `rawInput`;`run_code` 有意不声明 `presentResult`,因此 TUI 和宿主/客户端运行时(Web)会通过通用原始内容回退机制,使用最终持久化的 `tool/result.content` 补全该卡片,其中包括捕获的日志,以及返回值、失败信息或 post-policy spill 预览。这不是 `terminal` 卡片:该卡片的语义是「工作目录中的 shell 命令」,程序不是。参见[结果卡片完整性说明](../../archived/bug-fix/2026-07-20-code-mode-result-card-completeness.zh.md)。 +**呈现。** `run_code` 的 render intent 按[呈现意图 Agent Note](../architecture/2026-07-02-tool-render-intent-union.zh.md)在此决定:`presentCall` 创建一个 `generic` 卡片,`kind: 'execute'`,以程序文本作为标题,并将同一程序文本作为 `rawInput`;`run_code` 有意不声明 `presentResult`,因此 TUI 和宿主/客户端运行时(Web)会通过通用原始内容回退机制,使用最终持久化的 `tool/result.content` 补全该卡片,其中包括捕获的日志,以及返回值、失败信息或 post-policy spill 预览。这不是 `terminal` 卡片:该卡片的语义是「工作目录中的 shell 命令」,程序不是。参见[结果卡片完整性说明](../../archived/bug-fix/2026-07-20-code-mode-result-card-completeness.md)。 ### 可观测性:`tool/code-dispatch` diff --git a/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml b/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml index c123e2ff7e..4b4a5e85c6 100644 --- a/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-05-skill-system.md 2026-07-05-skill-system.md: 481cdecbb4acaf5a0c38106ab47c6c489aca507c -2026-07-05-skill-system.zh.md: 7ae3bc293263a135a4b84fbcc6f646f9cbbc2097 +2026-07-05-skill-system.zh.md: a0d7dd953337b0e9e764738d6d4cd7ef825999f5 diff --git a/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md b/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md index 7ae3bc2932..a0d7dd9533 100644 --- a/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md +++ b/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md @@ -24,7 +24,7 @@ DeepSeek Harness 使用同一原语,使项目特定的评审、插件编写和 本地 skill 的文件系统 I/O 在加载了文件系统服务时通过 `ctx.fs` 进行:项目根目录查找使用 `resolve` 和 `stat` 探测 `.git`,根目录发现使用 `listDir`,skill 读取使用 `readText`。Node 文件系统作为后备,供在不挂载 fs seam 的最小上下文中加载 `dsh-skill-filesystem` 时使用。缺失的根目录、不可读或格式错误的 skill 文件、以及提供方 `list()` 的瞬态失败均降级为警告并跳过,使一个坏源不会导致所有 agent 请求失败;格式错误的候选项仍然快速失败,因为它们违反了提供方约定。 -`dsh-tool-skill` 在会话的第一个 `agent/pre-step` 注入一个持久化的 user-role `` 目录,作为带来源的 `user/message`,且仅当该 agent 的工具视图解析到本插件精确的 `skill` 注册时才注入。该目录仅包含排序后的 skill 名称与描述;不包含正文、路径、来源、提供方和路由提示。描述经过空白规范化、XML 转义,并受 `catalogDescriptionMaxLength` 上限约束,其默认值为 `500`,最小值为 `3`。完整的 skill 正文从不包含在目录中。(目录最初通过仅请求的[会话前缀扩展点](../../archived/feature/2026-07-07-session-prefix.zh.md)(已归档)传递;[统一带来源消息的决策](../architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md)将其移入持久化历史。) +`dsh-tool-skill` 在会话的第一个 `agent/pre-step` 注入一个持久化的 user-role `` 目录,作为带来源的 `user/message`,且仅当该 agent 的工具视图解析到本插件精确的 `skill` 注册时才注入。该目录仅包含排序后的 skill 名称与描述;不包含正文、路径、来源、提供方和路由提示。描述经过空白规范化、XML 转义,并受 `catalogDescriptionMaxLength` 上限约束,其默认值为 `500`,最小值为 `3`。完整的 skill 正文从不包含在目录中。(目录最初通过仅请求的[会话前缀扩展点](../../archived/feature/2026-07-07-session-prefix.md)(已归档)传递;[统一带来源消息的决策](../architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md)将其移入持久化历史。) 注册表的 `list()` 返回全部胜出摘要,而模型与用户消费方应用[独立调用策略决策](2026-07-28-skill-invocation-policy.zh.md)定义的调用判定。`skill({ name })` 工具为当前 agent cwd 加载一个模型可调用的 skill,返回包含 ``、`` 和 `` 的工具结果。`resourceBase` 提供一个目录、URL 或不透明的提供方管理的基路径,用于显式引用的脚本、参考资料和资产;资源仅按需加载,不进行目录枚举。无法解析的名称报告该 skill 未知或不再可用;无效名称和 `invocation.modelInvocable` 为 `false` 的 skill 保留不同的工具错误。工具结果是面向模型的可见披露路径。 @@ -38,7 +38,7 @@ DeepSeek Harness 使用同一原语,使项目特定的评审、插件编写和 **将本地文件系统扫描直接放入 `ctx.skills`。** 否决,因为编码 agent、Web agent 和未来的插件生态需要不同的 skill 来源。提供方注册表与 subagent seam 镜像:注册表拥有冲突解决和消费方,实现负责加载。 -**使用系统提示词段落。** 否决,因为渲染后的系统提示词是单一字符串,而目录是一条 user-role `` 消息。[仅请求的会话前缀扩展点](../../archived/feature/2026-07-07-session-prefix.zh.md)(已归档)是最初的机制;统一带来源消息的决策移除该扩展点后,目录改为具有相同消息形状的持久化带来源注入。 +**使用系统提示词段落。** 否决,因为渲染后的系统提示词是单一字符串,而目录是一条 user-role `` 消息。[仅请求的会话前缀扩展点](../../archived/feature/2026-07-07-session-prefix.md)(已归档)是最初的机制;统一带来源消息的决策移除该扩展点后,目录改为具有相同消息形状的持久化带来源注入。 **在 `~/.dsh/skills/.system` 下物化内置 DSH 编写 skill。** 否决,因为打包的 skill 不会在启动时写入用户主目录,嵌入式或远程提供方在配置后提供 skill。 @@ -54,4 +54,4 @@ agent-core 主干包含一个目录贡献者、一个本地提供方和一个面 ## 延后 -fork 的 skill 上下文(`context: fork`)、参数声明与提示(`arguments` 和 `argument-hint`)、以及逐 skill 的工具约束(`allowed-tools` 和 `disallowed-tools`)不在已交付的约定范围内。注册表、本地提供方和面向模型的工具不解析、不广播、也不强制执行这些字段。直接用户调用已作为 TUI 功能交付,基于共享调用策略和受信的 `get()` 原语;见[已归档的 TUI skill 斜杠命令](../../archived/feature/2026-07-21-tui-skill-slash-command.zh.md)。 +fork 的 skill 上下文(`context: fork`)、参数声明与提示(`arguments` 和 `argument-hint`)、以及逐 skill 的工具约束(`allowed-tools` 和 `disallowed-tools`)不在已交付的约定范围内。注册表、本地提供方和面向模型的工具不解析、不广播、也不强制执行这些字段。直接用户调用已作为 TUI 功能交付,基于共享调用策略和受信的 `get()` 原语;见[已归档的 TUI skill 斜杠命令](../../archived/feature/2026-07-21-tui-skill-slash-command.md)。 diff --git a/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.i18n.yaml b/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.i18n.yaml index b235b96bc9..16d4768b03 100644 --- a/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.md 2026-07-10-sqlite-session-query-provider.md: 0e15ea15f516091825276c4a9bcde2184c653123 -2026-07-10-sqlite-session-query-provider.zh.md: 5770f2598f48067cda694b14fb319ac98fa005df +2026-07-10-sqlite-session-query-provider.zh.md: f18d394733de29ba297adfc2602efabe33b9b75f diff --git a/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.zh.md b/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.zh.md index 5770f2598f..f18d394733 100644 --- a/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.zh.md +++ b/.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.zh.md @@ -12,7 +12,7 @@ Status: implemented ## 决策 -`@deepseek-ai/dsh-session-query` 声明一个抽象的 `ctx.sessionQuery` 服务,其精确读取、过滤与追踪均有具体实现,仅有两项全文方法为抽象方法。`searchSessions(request, exec?)` 返回按游标分页的 `SessionSearchHit`,每个会话以其中匹配度最高的事件作为命中结果;`searchEvents(request, exec?)` 返回一个逻辑会话内的 `SessionEventSearchHit`。两种请求都必须提供 `query`,可以接受 `limit` 和由服务拥有的品牌化 `SessionSearchCursor`,并支持可选的中止信号。会话搜索接受 `sessionFilters` 与事件元数据过滤器,事件搜索接受事件元数据过滤器。结果会公开有界的纯文本摘要片段,但不公开提供方标识符或数值相关性分数。单一键拓扑由[统一服务决策](../../archived/architecture/2026-07-23-unified-session-query-service.zh.md)定义。 +`@deepseek-ai/dsh-session-query` 声明一个抽象的 `ctx.sessionQuery` 服务,其精确读取、过滤与追踪均有具体实现,仅有两项全文方法为抽象方法。`searchSessions(request, exec?)` 返回按游标分页的 `SessionSearchHit`,每个会话以其中匹配度最高的事件作为命中结果;`searchEvents(request, exec?)` 返回一个逻辑会话内的 `SessionEventSearchHit`。两种请求都必须提供 `query`,可以接受 `limit` 和由服务拥有的品牌化 `SessionSearchCursor`,并支持可选的中止信号。会话搜索接受 `sessionFilters` 与事件元数据过滤器,事件搜索接受事件元数据过滤器。结果会公开有界的纯文本摘要片段,但不公开提供方标识符或数值相关性分数。单一键拓扑由[统一服务决策](../../archived/architecture/2026-07-23-unified-session-query-service.md)定义。 `@deepseek-ai/dsh-session-query-sqlite` 扩展接口服务,并且是 `ctx.sessionQuery` 唯一的具体所有者。它依赖实时的 `ctx.sessions`,动态观察可选的 `ctx.sessionPersistence`,并拥有一个专用的派生 SQLite 数据库。系统没有搜索提供方注册表、协调器、持久化事件或 agent loop(智能体循环)集成。 diff --git a/.agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.i18n.yaml b/.agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.i18n.yaml index 7ea54b9593..f8d99db60e 100644 --- a/.agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.md 2026-07-28-skill-invocation-policy.md: 7a4f83ecae3dea82ab6864e748c9d8b8599f6bfc -2026-07-28-skill-invocation-policy.zh.md: 918382753ba22f5340543c807981b9805a910d93 +2026-07-28-skill-invocation-policy.zh.md: 8d66af69b7f45c5d76d18963df6921eaec2e1e8c diff --git a/.agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.zh.md b/.agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.zh.md index 918382753b..8d66af69b7 100644 --- a/.agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.zh.md +++ b/.agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.zh.md @@ -29,7 +29,7 @@ skill 注册表最初将发现操作视为模型目录:`ctx.skills.list()` 会 | `{ modelInvocable: false, userInvocable: true }` | 排除 | 包含 | | `{ modelInvocable: false, userInvocable: false }` | 排除 | 排除 | -该决策扩展了 [skill 系统](2026-07-05-skill-system.zh.md),并取代[已归档的 TUI skill 斜杠命令](../../archived/feature/2026-07-21-tui-skill-slash-command.zh.md)中记录的调用策略限制。 +该决策扩展了 [skill 系统](2026-07-05-skill-system.zh.md),并取代[已归档的 TUI skill 斜杠命令](../../archived/feature/2026-07-21-tui-skill-slash-command.md)中记录的调用策略限制。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.i18n.yaml b/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.i18n.yaml index 5b7258ac26..a56f2fed91 100644 --- a/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.md 2026-08-04-web-context-source-and-steer-marks.md: 06f6b7b4bd12e2dccb62a38a5b57b153a6263901 -2026-08-04-web-context-source-and-steer-marks.zh.md: 3b3a329a5434e344a5894b46a8402465a6a370d2 +2026-08-04-web-context-source-and-steer-marks.zh.md: d8109d832fdf6974a89427a6296b24054c6eac36 diff --git a/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.zh.md b/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.zh.md index 3b3a329a54..d8109d832f 100644 --- a/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.zh.md +++ b/.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.zh.md @@ -14,13 +14,13 @@ Status: implemented transcript 为非提示消息可能承担的三种角色分别命名:注入上下文、召回会话、steering。 -Chat Message Definition 为每个 `ContextMessageNode` 附加一份包含生产者角色和名称的 `provenance` 视图;`contextProvenance()` 仅依据持久来源计算该视图。它返回 `role`(`inject`,跨会话快照则为 `recall`)与命名生产者的 `label`。`ContextInjectionRow` 以角色作为标题,并按 `ToolRow` 摘要的几何在标题旁展示该名称,因此折叠态就已经回答了「注入了什么、由谁注入」;141px 滚动视口与截断上限沿用[已归档的展开项决策](../../archived/feature/2026-07-30-web-context-injection-disclosure.zh.md),未作改动。视口里渲染什么,则由[上下文形态决策](2026-08-05-context-form-vocabulary.zh.md)引入的、相互独立的形态轴决定。 +Chat Message Definition 为每个 `ContextMessageNode` 附加一份包含生产者角色和名称的 `provenance` 视图;`contextProvenance()` 仅依据持久来源计算该视图。它返回 `role`(`inject`,跨会话快照则为 `recall`)与命名生产者的 `label`。`ContextInjectionRow` 以角色作为标题,并按 `ToolRow` 摘要的几何在标题旁展示该名称,因此折叠态就已经回答了「注入了什么、由谁注入」;141px 滚动视口与截断上限沿用[已归档的展开项决策](../../archived/feature/2026-07-30-web-context-injection-disclosure.md),未作改动。视口里渲染什么,则由[上下文形态决策](2026-08-05-context-form-vocabulary.zh.md)引入的、相互独立的形态轴决定。 **名称从日志中读出,绝不来自客户端维护的生产者名称表。** `agent-instructions` 以它对账过的去重指令文件路径命名,`session-reference` 以它读取的会话标题命名,插件来源以其记录的插件 id 命名,其余来源则以自身的 `kind` 命名——这正是可合并扩展联合类型有文档记载的默认分支。没有可读 kind 的来源降级为无名注入。于是新增或重命名的生产者无需客户端发版即可辨识,任何名称都不会相对代码失准,恢复、fork 或来自外部的日志与实时会话的投影结果完全一致。 `recall` 覆盖 `session-reference`,因为它是当前唯一会把另一个会话的材料搬进本会话的已发布来源。今天没有任何 Web 叶子挂载 `dsh-session-reference`——它此前只有终端宿主——因此该分支的存在是为了日志可移植性,而不是为了某个已打包的生产方,其覆盖来自单元测试而非组装后的 Web 场景。 -Chat Inbox 与 Message Definition 会重放持久 `agent/inbox/spliced` 事件;如果一条用户来源的消息以相同身份从 `next-step` 被领取,后续 `user/message` 就投影为 `SteeringMessageNode`。`MessageItem` 为这种持久消息与待处理 steering 气泡加上 `插话` 标注。从排队轮次领取的消息仍是 `UserMessageNode`,非用户来源的 next-step 消息仍是上下文。这推翻了[已归档的取消 steer 入口与插话装饰决策](../../archived/simplification/2026-07-31-web-ui-no-steer-entry-or-interjection-chrome.zh.md)中的一条结论。当时移除徽章,是因为 composer 无法 steer,标签指向了用户做不到的动作。此后 composer 获得了 Steer 手势,却没有同步修订那份 note;本决策提供了它在「重新引入」条款中要求的产品决策,并订正了其中留下的过时事实。标注是这里唯一的 steering 装饰:composer 模式、Queue dock 的严格 steer 操作、待处理 steering 的生命周期仍归各自的所有者。 +Chat Inbox 与 Message Definition 会重放持久 `agent/inbox/spliced` 事件;如果一条用户来源的消息以相同身份从 `next-step` 被领取,后续 `user/message` 就投影为 `SteeringMessageNode`。`MessageItem` 为这种持久消息与待处理 steering 气泡加上 `插话` 标注。从排队轮次领取的消息仍是 `UserMessageNode`,非用户来源的 next-step 消息仍是上下文。这推翻了[已归档的取消 steer 入口与插话装饰决策](../../archived/simplification/2026-07-31-web-ui-no-steer-entry-or-interjection-chrome.md)中的一条结论。当时移除徽章,是因为 composer 无法 steer,标签指向了用户做不到的动作。此后 composer 获得了 Steer 手势,却没有同步修订那份 note;本决策提供了它在「重新引入」条款中要求的产品决策,并订正了其中留下的过时事实。标注是这里唯一的 steering 装饰:composer 模式、Queue dock 的严格 steer 操作、待处理 steering 的生命周期仍归各自的所有者。 ## 考虑过的替代方案 diff --git a/.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.i18n.yaml b/.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.i18n.yaml index f3455cfaa9..6122ba2771 100644 --- a/.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.i18n.yaml +++ b/.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.md 2026-06-16-pnpm-over-yarn.md: 30b34c65fdea94b20dec4d627a0fca40de760fd1 -2026-06-16-pnpm-over-yarn.zh.md: 26c6dc4ae4ac232ee0a43d563938dc3c57e9362b +2026-06-16-pnpm-over-yarn.zh.md: c2c289ad2e43b6bdcb0997e9a4e18421d6705c6a diff --git a/.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.zh.md b/.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.zh.md index 26c6dc4ae4..c2c289ad2e 100644 --- a/.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.zh.md +++ b/.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.zh.md @@ -8,7 +8,7 @@ Status: implemented 本仓库最初使用 **Yarn 4** 搭配 `node-modules` 链接器。这是一个刻意保守的选择:行为类似 npm 的扁平布局,同时享有 Yarn 的 workspaces 和 `yarn constraints`。它能正常工作。但 Yarn 4 源自 Plug'n'Play 的血统,使得 `node-modules` 链接器成为非主流模式;而更广泛的 JS 生态——工具默认值、CI action、Corepack 示例、贡献者的熟悉度——正日益以 pnpm 为中心。对于一个主要由 agent(智能体)构建、偶尔有人类贡献者阅读的仓库而言,「大多数工具和人所期望的包管理器」具有实际价值:更少的意外、更成熟的故障路径、更多可直接复用的解答。 -切换成本目前处于最低点。本仓库尚无任何包发布(每个包都是 `private: true`);开发流程、测试和源码模式 demo 都通过各自声明的 TypeScript 启动器运行,产物检查则会显式构建。因此,包管理器只需做到:(a)解析并链接 `node_modules`,(b)运行 workspace 脚本,(c)强制执行 workspace 约束。唯一的 Yarn 特有资产是 `yarn.config.cjs`(`@yarnpkg/types` 约束引擎),体量小且可机械地重新表达。这与 [tsdown 决策](../../archived/process/2026-06-11-tsdown-over-dumble.zh.md)的逻辑一致:在爆炸半径尚小时,将承重工具换为生态更健康的选项。 +切换成本目前处于最低点。本仓库尚无任何包发布(每个包都是 `private: true`);开发流程、测试和源码模式 demo 都通过各自声明的 TypeScript 启动器运行,产物检查则会显式构建。因此,包管理器只需做到:(a)解析并链接 `node_modules`,(b)运行 workspace 脚本,(c)强制执行 workspace 约束。唯一的 Yarn 特有资产是 `yarn.config.cjs`(`@yarnpkg/types` 约束引擎),体量小且可机械地重新表达。这与 [tsdown 决策](../../archived/process/2026-06-11-tsdown-over-dumble.md)的逻辑一致:在爆炸半径尚小时,将承重工具换为生态更健康的选项。 ## 决策 diff --git a/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.i18n.yaml b/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.i18n.yaml index a6919bb98d..bf3b882085 100644 --- a/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.i18n.yaml +++ b/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.md 2026-06-18-markdown-cross-link-lint.md: 37e0a41c08e068d6b3128300f078c8e417f1f337 -2026-06-18-markdown-cross-link-lint.zh.md: ba83106dcd57ced1ae006b8c94105b8f1f5e2332 +2026-06-18-markdown-cross-link-lint.zh.md: 56a1c9e90debe06d313fd5aaf03f771b60327dcb diff --git a/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.zh.md b/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.zh.md index ba83106dcd..56a1c9e90d 100644 --- a/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.zh.md +++ b/.agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.zh.md @@ -6,7 +6,7 @@ Status: implemented ## 问题 -本仓库的文档通过相对路径互相链接:`[topic](../implemented/2026-…-….md)`、`[the cookbook](adding-a-tool.md)`、`[architecture.md](../../architecture.md)`。此前没有任何机制验证这些目标是否存在。重命名或移动文件会静默破坏所有指向它的链接,且在读者点击之前不可见。[doc-sync(文档同步门禁)强制执行](../../archived/process/2026-06-11-doc-sync-enforcement.zh.md)已经将两类文档漂移的检查自动化(无法编译的代码块、陈旧的事件分类体系表),[verify-md-wrap](../../archived/process/2026-06-11-doc-sync-enforcement.zh.md) 覆盖了第三类(硬换行的段落),但死链是第四类同样可机械检查、却仍靠肉眼验证的问题。 +本仓库的文档通过相对路径互相链接:`[topic](../implemented/2026-…-….md)`、`[the cookbook](adding-a-tool.md)`、`[architecture.md](../../architecture.md)`。此前没有任何机制验证这些目标是否存在。重命名或移动文件会静默破坏所有指向它的链接,且在读者点击之前不可见。[doc-sync(文档同步门禁)强制执行](../../archived/process/2026-06-11-doc-sync-enforcement.md)已经将两类文档漂移的检查自动化(无法编译的代码块、陈旧的事件分类体系表),[verify-md-wrap](../../archived/process/2026-06-11-doc-sync-enforcement.md) 覆盖了第三类(硬换行的段落),但死链是第四类同样可机械检查、却仍靠肉眼验证的问题。 引入这道门禁的直接动因是 Agent Note 目录树重组:将 `docs/adr/` 与 `.agents/notes/` 统一到同一个 `.agents/notes/` 下,并设置 `proposed/`、`implemented/`、`rejected/` 子目录,需要手工重命名约 40 条文档间链接。只要有一处路径输入错误,就会在没有任何检查拦截的情况下交付断链。 diff --git a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml index 4e9ee5ffa3..01b73a2317 100644 --- a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml +++ b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md 2026-06-20-core-data-structures-catalog.md: 489dc488b82883dc1ab23fb0685c9467651cb60f -2026-06-20-core-data-structures-catalog.zh.md: 255d97aa8d62f7288476d55dc8bfcbb2f964a19c +2026-06-20-core-data-structures-catalog.zh.md: 2742603cd6f83d114c00e9b50977cedc32f90b60 diff --git a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md index 255d97aa8d..2742603cd6 100644 --- a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md +++ b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md @@ -8,7 +8,7 @@ Status: implemented 试图理解 harness 的读者可以在 [architecture.md](../../../../docs/architecture.zh.md) 中找到它的*行为*(服务图、会话/轮次/步骤生命周期、事件分类体系),却找不到一个统一描述其*词汇*的地方,也就是这些行为所传递的数据结构。类型定义只存在于源码中,散落在 `packages/*/src/types.ts` 各处,因此要理解「什么是 `Message`、`SessionEvent`、`StreamChunk`」,就必须直接阅读声明。文字目录会有所帮助,但复述或复制粘贴类型定义的目录会在字段发生变化时立即腐化,而不同步的类型文档比没有文档更糟,因为读者会信任它。 -因此,这项工作有两个相互交织的问题:**这样的目录应包含什么**(范围问题——harness 有数十种跨包边界的类型,把它们全部罗列出来对谁都没有帮助),以及**如何避免粘贴的类型定义发生漂移**(持久性问题)。本 Agent Note 记下了这两项决策。与它历史上配套的[已归档的 Cordis 事件与服务目录自动生成决策](../../archived/process/2026-06-20-generated-cordis-catalog.zh.md)从*接线*维度形成补充:本文对数据结构编目,另一篇则对传递这些结构的事件和服务编目。 +因此,这项工作有两个相互交织的问题:**这样的目录应包含什么**(范围问题——harness 有数十种跨包边界的类型,把它们全部罗列出来对谁都没有帮助),以及**如何避免粘贴的类型定义发生漂移**(持久性问题)。本 Agent Note 记下了这两项决策。与它历史上配套的[已归档的 Cordis 事件与服务目录自动生成决策](../../archived/process/2026-06-20-generated-cordis-catalog.md)从*接线*维度形成补充:本文对数据结构编目,另一篇则对传递这些结构的事件和服务编目。 ## 决策 @@ -50,7 +50,7 @@ Status: implemented ## 验证教训 -`verify-type-equiv` 必须扫描完整的 Markdown 范围,而不仅是 manifest 点名的文档。否则,未列入清单的 `type-equiv` 块就会逃过所宣称的一一检查。因此,门禁会将此类块报告为未列入清单的块。本 Agent Note 将这条默认拒绝放行的扫描规则,连同主干与子系统的分界决策及逐字匹配决策一并记录;生成的 Cordis 目录在[其已归档的 Agent Note](../../archived/process/2026-06-20-generated-cordis-catalog.zh.md) 中有对称的设计记录。 +`verify-type-equiv` 必须扫描完整的 Markdown 范围,而不仅是 manifest 点名的文档。否则,未列入清单的 `type-equiv` 块就会逃过所宣称的一一检查。因此,门禁会将此类块报告为未列入清单的块。本 Agent Note 将这条默认拒绝放行的扫描规则,连同主干与子系统的分界决策及逐字匹配决策一并记录;生成的 Cordis 目录在[其已归档的 Agent Note](../../archived/process/2026-06-20-generated-cordis-catalog.md) 中有对称的设计记录。 ## 后果 diff --git a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml index b0f4292c00..afd9d3c5e7 100644 --- a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.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 .agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md -2026-07-02-bilingual-docs-and-pairing-gate.md: cfcb7cc53119dbfa05cf9bcade54b2137edb22ce -2026-07-02-bilingual-docs-and-pairing-gate.zh.md: b5d266216aa36bf64f2e1472a903e2f412cd1fdc +2026-07-02-bilingual-docs-and-pairing-gate.md: 3a93b4aa68e6f6092abf42a40ec148a205a78691 +2026-07-02-bilingual-docs-and-pairing-gate.zh.md: e145afe7cff8d0d0632541110d1c0f2bf70d11f2 diff --git a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md index cfcb7cc531..3a93b4aa68 100644 --- a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md +++ b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md @@ -12,7 +12,7 @@ This repo's documentation corpus is read by people and agents inside and outside - **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 `, which requires naming the confirmed pairs — bulk re-record is an explicit `--write --all`) 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: every discovered, non-excluded source has a complete pair; every existing pair is complete (all three files) and consistent (both hashes match, the Chinese side and every authored English source carry their switchers while listed generated English sources are exempt, structural signatures identical); and excluded generated, instruction, or bilingual-by-construction files stay unpaired. Relative document links use the target sibling matching the source locale, while the structure signature normalizes `.md` and `.zh.md` siblings to one semantic target and retains the exact query/fragment suffix; the [localized bilingual links decision](2026-08-18-localized-bilingual-links.md) owns that refinement. [scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) contains only explicit exclusions, so no requirement can bypass discovery and receive a weaker check. 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. +- **`verify-translation-pairing` joins `doc-sync`.** The gate ([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts)) enforces: every discovered, non-excluded source has a complete pair; every existing pair is complete (all three files) and consistent (both hashes match, the Chinese side and every authored English source carry their switchers while listed generated English sources are exempt, structural signatures identical); and excluded generated, instruction, or bilingual-by-construction files stay unpaired. Relative document links whose targets belong to that active corpus use the target sibling matching the source locale, while the structure signature normalizes `.md` and `.zh.md` siblings to one semantic target and retains the exact query/fragment suffix; the [localized bilingual links decision](2026-08-18-localized-bilingual-links.md) owns that refinement. [scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) contains only explicit exclusions, so no requirement can bypass discovery and receive a weaker check. 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. - **One corpus-wide requirement.** Every document in scope requires a complete pair from creation; the policy has no per-file rollout state, date cutoff, or README-specific class. README discovery covers every case-insensitive README basename outside vendored, dependency, and ignored build-output trees, including future top-level directories. 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. - **Translation is agent work with human review.** Routine changes use the direct one-pass path owned by the [lightweight-translation decision](2026-08-08-lightweight-routine-documentation-translation.md). The [extended translation skill](../../../skills/dsh-translate-docs/SKILL.md) retains delegated translation and the other heavier mechanisms for explicit user invocation; both paths defer to the documentation contracts as their sources of truth. diff --git a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md index b5d266216a..e145afe7cf 100644 --- a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md +++ b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md @@ -6,13 +6,13 @@ Status: implemented ## 问题 -本仓库的文档语料会被公司内外的人和 agent(智能体)以中英两种语言阅读。在没有机制的情况下纯靠手工维护第二语言,正是译文腐烂的根源:一侧持续演进,另一侧默默失实,而没有门禁能够发现。对于这类不变式,本仓库一贯的做法是将其编码为机械检查(见[质量门禁](2026-06-11-quality-gates.zh.md)与 [doc-sync(文档同步门禁)强制](../../archived/process/2026-06-11-doc-sync-enforcement.zh.md)),因此双语政策随附一道门禁一起交付。 +本仓库的文档语料会被公司内外的人和 agent(智能体)以中英两种语言阅读。在没有机制的情况下纯靠手工维护第二语言,正是译文腐烂的根源:一侧持续演进,另一侧默默失实,而没有门禁能够发现。对于这类不变式,本仓库一贯的做法是将其编码为机械检查(见[质量门禁](2026-06-11-quality-gates.zh.md)与 [doc-sync(文档同步门禁)强制](../../archived/process/2026-06-11-doc-sync-enforcement.md)),因此双语政策随附一道门禁一起交付。 ## 决策 - **配对兄弟文件,两种语言同权。** 一对文档由三个兄弟文件组成:英文 `foo.md`、中文 `foo.zh.md`,以及一份一致性记录 `foo.i18n.yaml`。没有哪种语言是正典:一篇文档可以先用中文撰写和评审、之后再译成英文,反之亦可;约束配对的是:两侧必须表达相同的内容,且配对整体合并(两种语言加记录,绝不单独落一侧)。政策见 [docs/i18n/README.md](../../../../docs/i18n/README.zh.md);翻译规则见 [docs/i18n/translation-rules.md](../../../../docs/i18n/translation-rules.zh.md);术语真源见 [docs/i18n/terminology.md](../../../../docs/i18n/terminology.md)。 - **伴随记录保存两侧 blob hash,使一致性可检查。** `foo.i18n.yaml` 保存两侧文件在上一次确认一致时各自的完整 Git blob hash。此后修改了任一侧而未重新确认配对,都能被机械检测出来(纯内容比较,无需查询历史),而且同一个 PR(Pull Request)内改动的文件也能计算出 hash,commit hash 式的记录做不到这一点。重新记录(`verify-translation-pairing --write `,要求点名所确认的配对;批量重新记录是显式的 `--write --all`)会产生一份可评审的 YAML diff:确认一致在 PR 中是一个显式、可见的动作。 -- **`verify-translation-pairing` 加入 `doc-sync`。** 门禁([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts))强制执行以下规则:每个已发现且未排除的源文档都有完整配对;每个现有配对都完整(三个文件齐全)且一致(两侧的 hash 均与记录匹配、中文侧和所有人工撰写的英文源都带语言切换行而清单内的生成英文源除外、结构签名一致);被排除的生成文档、指令文档或本身即双语的文档不得配对。相对文档链接使用与源文件 locale 相同的目标兄弟文件;结构签名则把 `.md` 与 `.zh.md` 兄弟文件规范化为同一个语义目标,并保留完全相同的 query/fragment 后缀;该细化规则由[双语文档链接本地化决策](2026-08-18-localized-bilingual-links.zh.md)负责。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 只包含显式排除项,因此任何要求都无法绕过发现流程而接受较弱的检查。只有当 `.zh.md` 围栏序列与其无后缀兄弟文件拥有顺序相同、正文按字节一致的同一组受跟踪围栏时,面向源码的代码门禁才会将其作为派生内容消费;不完整、顺序变更、重分类或已改动的序列仍会独立受检,因此由其所属的代码门禁或配对门禁报告不匹配。 +- **`verify-translation-pairing` 加入 `doc-sync`。** 门禁([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts))强制执行以下规则:每个已发现且未排除的源文档都有完整配对;每个现有配对都完整(三个文件齐全)且一致(两侧的 hash 均与记录匹配、中文侧和所有人工撰写的英文源都带语言切换行而清单内的生成英文源除外、结构签名一致);被排除的生成文档、指令文档或本身即双语的文档不得配对。目标属于该活跃语料的相对文档链接使用与源文件 locale 相同的目标兄弟文件;结构签名则把 `.md` 与 `.zh.md` 兄弟文件规范化为同一个语义目标,并保留完全相同的 query/fragment 后缀;该细化规则由[双语文档链接本地化决策](2026-08-18-localized-bilingual-links.zh.md)负责。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 只包含显式排除项,因此任何要求都无法绕过发现流程而接受较弱的检查。只有当 `.zh.md` 围栏序列与其无后缀兄弟文件拥有顺序相同、正文按字节一致的同一组受跟踪围栏时,面向源码的代码门禁才会将其作为派生内容消费;不完整、顺序变更、重分类或已改动的序列仍会独立受检,因此由其所属的代码门禁或配对门禁报告不匹配。 - **全语料统一要求。** 范围内的每篇文档从创建起就必须有完整配对;政策没有逐文件推进状态、日期分界或 README 专用类别。README 发现会覆盖 vendor 源码、依赖目录与被忽略的构建产物目录之外所有文件名不区分大小写匹配 README 的文件,包括今后新增的顶层目录。发布到文档站的配对使用 `pairedPages()`,由根 locale 投影 `.zh.md`,由 `/en/` 投影 `.md`;仅创建对侧文件并不会发布它。 - **配对记录是元数据,而不是 Cordis Loader 配置。** Cordis 配置发现会接受实际的 `.cordis.yml` 和 `.cordis.yaml` 文件,同时排除 `*.i18n.yaml`,即使文档名中包含 `cordis` 也不例外。这样既能继续校验可执行的 Loader 配置项,又不会把翻译 hash 当作配置来解析。 - **翻译是 agent 的工作,由人评审。** 常规改动采用由[轻量翻译决策](2026-08-08-lightweight-routine-documentation-translation.zh.md)确立的直接单遍路径。[扩展翻译 skill(技能)](../../../skills/dsh-translate-docs/SKILL.md)保留委派翻译和其他较重机制,供用户显式调用;两条路径均以文档契约为真源。 diff --git a/.agents/notes/implemented/process/2026-07-06-export-jsdoc-gate.i18n.yaml b/.agents/notes/implemented/process/2026-07-06-export-jsdoc-gate.i18n.yaml index d05f070a57..efb05ed5e6 100644 --- a/.agents/notes/implemented/process/2026-07-06-export-jsdoc-gate.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-06-export-jsdoc-gate.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-06-export-jsdoc-gate.md 2026-07-06-export-jsdoc-gate.md: 5f594cf279c07c41a96a124f1d25dbf880543718 -2026-07-06-export-jsdoc-gate.zh.md: 9024f83cc1be3af589da54eaf5e0342a6b013a7d +2026-07-06-export-jsdoc-gate.zh.md: a45c7909008a50764e5d73f6ae5943a7850e0573 diff --git a/.agents/notes/implemented/process/2026-07-06-export-jsdoc-gate.zh.md b/.agents/notes/implemented/process/2026-07-06-export-jsdoc-gate.zh.md index 9024f83cc1..a45c790900 100644 --- a/.agents/notes/implemented/process/2026-07-06-export-jsdoc-gate.zh.md +++ b/.agents/notes/implemented/process/2026-07-06-export-jsdoc-gate.zh.md @@ -6,7 +6,7 @@ Status: implemented ## 问题 -[Cordis JSDoc 完整性门禁](../../archived/process/2026-07-04-cordis-jsdoc-completeness-gate.zh.md)使得 Cordis 接口上的参数和返回值不可能缺少文档——`interface Events` 成员和 `ctx.` 服务类——但这只覆盖插件作者可导入接口的一小部分。AGENTS.md 中的规则「每个导出(以及非显而易见的方法)都必须有解释语义的 JSDoc」在其他地方仍只是由评审检查的文字约定,而且没有任何机制要求普通导出函数带 `@param`/`@returns`。采纳时的一次调查发现 34 个包中有 203 个文档不完整的模块级导出:seam 相关辅助函数(`runBash`、`readForEdit`、`htmlToMarkdown`)、格式编解码器、完全无文档的接口和类型别名——恰恰是 IDE 消费方悬停查看的那些名称。 +[Cordis JSDoc 完整性门禁](../../archived/process/2026-07-04-cordis-jsdoc-completeness-gate.md)使得 Cordis 接口上的参数和返回值不可能缺少文档——`interface Events` 成员和 `ctx.` 服务类——但这只覆盖插件作者可导入接口的一小部分。AGENTS.md 中的规则「每个导出(以及非显而易见的方法)都必须有解释语义的 JSDoc」在其他地方仍只是由评审检查的文字约定,而且没有任何机制要求普通导出函数带 `@param`/`@returns`。采纳时的一次调查发现 34 个包中有 203 个文档不完整的模块级导出:seam 相关辅助函数(`runBash`、`readForEdit`、`htmlToMarkdown`)、格式编解码器、完全无文档的接口和类型别名——恰恰是 IDE 消费方悬停查看的那些名称。 ## 决策 diff --git a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.i18n.yaml b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.i18n.yaml index 6c88e2520a..ddb7f26b1e 100644 --- a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md 2026-07-06-parallel-pre-push-gates.md: 2ae08b8c87939085a0f8c7e0cb3ac69fb3ab8e91 -2026-07-06-parallel-pre-push-gates.zh.md: 0b59eccf9490992705e9a795c863f6a5f04aefe5 +2026-07-06-parallel-pre-push-gates.zh.md: 0abe9af4113b5be36bebc1bc2e421be6c805f851 diff --git a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.zh.md b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.zh.md index 0b59eccf94..0abe9af411 100644 --- a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.zh.md +++ b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.zh.md @@ -20,7 +20,7 @@ Node 24 消费方任务采用单个包含 10 道门禁的模式,而非由 shel [scripts/publint-all.ts](../../../../scripts/publint-all.ts) 从 `packages//` 发现包,并以根据 `availableParallelism()` 确定大小的 worker 池运行 `publint`。`DSH_PUBLINT_CONCURRENCY` 可以针对资源配置不同的本地机器和 CI runner 限制或提高 worker 数量。结果按包缓冲,并按确定性的包顺序打印,因此并行执行不会打乱各包的日志块。 -各门禁的包脚本仍是临时本地运行所用的命令入口。`hygiene` 继续作为聚合 `&&` 链,而 `doc-sync` 的成员列表由调度器管理([通过门禁调度器运行 doc-sync](../../archived/process/2026-07-21-doc-sync-through-gate-scheduler.zh.md))。 +各门禁的包脚本仍是临时本地运行所用的命令入口。`hygiene` 继续作为聚合 `&&` 链,而 `doc-sync` 的成员列表由调度器管理([通过门禁调度器运行 doc-sync](../../archived/process/2026-07-21-doc-sync-through-gate-scheduler.md))。 ## 验证 diff --git a/.agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.i18n.yaml b/.agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.i18n.yaml index 9bfa9459a5..a69f51c47e 100644 --- a/.agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.md 2026-07-28-per-subsystem-cordis-surface-regions.md: 66973223ec443fdd2a150fdd32d722ae3cad261b -2026-07-28-per-subsystem-cordis-surface-regions.zh.md: b152dd7837aaab28a1d2f7bcd762a64005df52a5 +2026-07-28-per-subsystem-cordis-surface-regions.zh.md: de000402bd20018c423a391dfefca4a3a9d65bdf diff --git a/.agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.zh.md b/.agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.zh.md index b152dd7837..de000402bd 100644 --- a/.agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.zh.md +++ b/.agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.zh.md @@ -8,7 +8,7 @@ Status: implemented 一个子系统的文档过去分散在三个归属:手写的 subsystems 页面(介绍、数据结构、动词)、平铺生成的 `docs/cordis-catalog/services.md` 中属于它的 `ctx.` 切片,以及平铺的 `docs/cordis-catalog/events.md` 中属于其事件作用域的切片。shell.md 的读者必须再打开两份文档,才能看到该页面正在描述的服务接口与事件;除了手工维护的链接,没有任何机制把这三个视图联系在一起。平铺目录还游离在双语语料之外(生成输出只有英文,故被排除在配对之外),因此,这套参考内容完全没有中文入口。 -[生成式目录决策](../../archived/process/2026-06-20-generated-cordis-catalog.zh.md)本身(从源码生成、`@mode` 标签交叉校验、失败关闭的类型链接覆盖、`ts cordis-catalog` 围栏)不在质疑之列;改变的只是生成输出「落在哪里」。 +[生成式目录决策](../../archived/process/2026-06-20-generated-cordis-catalog.md)本身(从源码生成、`@mode` 标签交叉校验、失败关闭的类型链接覆盖、`ts cordis-catalog` 围栏)不在质疑之列;改变的只是生成输出「落在哪里」。 ## 决策 diff --git a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.i18n.yaml b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.i18n.yaml index 812676ee75..5fb38dbf24 100644 --- a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md 2026-08-08-native-windows-pull-request-ci.md: d4883cf1363a33a444f1172829149c0c41f21c10 -2026-08-08-native-windows-pull-request-ci.zh.md: 557478888890a33a44984f2dcd1c3c69324e8a5e +2026-08-08-native-windows-pull-request-ci.zh.md: 0ba9629e873d82aba33a2cbad9e46e264ffe4312 diff --git a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md index 5574788888..0ba9629e87 100644 --- a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md +++ b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md @@ -12,7 +12,7 @@ Status: implemented ## 决策 -[ci.yml](../../../../.github/workflows/ci.yml) 中必需的 `windows` 作业仍是在 `ubuntu-latest` 上运行的 `windows node 24 / wine blocking`。它保留经过校验和验证的 Windows Node、Wine apt 与 pnpm 缓存、仅限工作区快照的 hoisted 安装,以及运行工作区构建与生产网站的[共享 Wine 门禁脚本](../../../../scripts/wine-windows-gates.sh)。Node 分发文件传输采用有界重试;nodejs.org 的大文件传输停滞时,由支持范围请求的传输镜像续传相同字节,但版本和 SHA-256 权威仍属于 nodejs.org,归档通过该校验前绝不会投入使用。稳定的 `windows` 作业 ID 仍是 `all checks passed` 的依赖项。[已归档的 Wine 实验](../../archived/process/2026-07-27-wine-windows-gates-experiment.zh.md)保留其实测取舍,而本文负责当前双通道拓扑。 +[ci.yml](../../../../.github/workflows/ci.yml) 中必需的 `windows` 作业仍是在 `ubuntu-latest` 上运行的 `windows node 24 / wine blocking`。它保留经过校验和验证的 Windows Node、Wine apt 与 pnpm 缓存、仅限工作区快照的 hoisted 安装,以及运行工作区构建与生产网站的[共享 Wine 门禁脚本](../../../../scripts/wine-windows-gates.sh)。Node 分发文件传输采用有界重试;nodejs.org 的大文件传输停滞时,由支持范围请求的传输镜像续传相同字节,但版本和 SHA-256 权威仍属于 nodejs.org,归档通过该校验前绝不会投入使用。稳定的 `windows` 作业 ID 仍是 `all checks passed` 的依赖项。[已归档的 Wine 实验](../../archived/process/2026-07-27-wine-windows-gates-experiment.md)保留其实测取舍,而本文负责当前双通道拓扑。 每个拉取请求还会在组织自有的 `dsh-windows-2025-16core` 运行器上启动一个常规且独立的 `windows-native` 作业,名称为 `windows node 24 / native complete`。该作业为工作区符号链接启用开发人员模式,通过 `pnpm/action-setup` 提供仓库固定版本的 pnpm,在不传输 store 归档的情况下执行不可变安装,并在原生 PowerShell 下运行 `pnpm run check:ci:windows-complete`。门禁卡住时,120 分钟超时会为其设定上限,同时不把实测性能目标当作正确性截止时间。 diff --git a/.agents/notes/implemented/process/2026-08-18-localized-bilingual-links.i18n.yaml b/.agents/notes/implemented/process/2026-08-18-localized-bilingual-links.i18n.yaml index ac3b86c8b6..4e2a2c5555 100644 --- a/.agents/notes/implemented/process/2026-08-18-localized-bilingual-links.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-18-localized-bilingual-links.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 .agents/notes/implemented/process/2026-08-18-localized-bilingual-links.md -2026-08-18-localized-bilingual-links.md: b970571c14df78028193474850a96a019cef2a23 -2026-08-18-localized-bilingual-links.zh.md: 15e37467c56201d97e69d4c4115700bde67c8b89 +2026-08-18-localized-bilingual-links.md: fab52cb70e611c25842883513bb89e1e1861a935 +2026-08-18-localized-bilingual-links.zh.md: a86be2800adaa7113b8da78ac4420ae48fde1663 diff --git a/.agents/notes/implemented/process/2026-08-18-localized-bilingual-links.md b/.agents/notes/implemented/process/2026-08-18-localized-bilingual-links.md index b970571c14..fab52cb70e 100644 --- a/.agents/notes/implemented/process/2026-08-18-localized-bilingual-links.md +++ b/.agents/notes/implemented/process/2026-08-18-localized-bilingual-links.md @@ -10,9 +10,11 @@ GitHub resolves repository Markdown links directly, without the documentation we ## Decision -A repository-relative document link follows the source file's locale when the target has an English/Chinese sibling pair: English sources use the target `.md`, and Chinese sources use its `.zh.md`. Both sides retain the same semantic target and exact query/fragment suffix. External URLs, images, pure in-page fragments, and targets without a Chinese sibling remain unchanged. The language switcher is the explicit cross-locale exception. +A repository-relative document link follows the source file's locale when the target belongs to the active bilingual corpus: English sources use the target `.md`, and Chinese sources use its `.zh.md`. Both sides retain the same semantic target and exact query/fragment suffix. A missing counterpart in that corpus is a pair-completeness error rather than a fallback; external URLs, images, pure in-page fragments, and targets outside the corpus remain unchanged. The language switcher is the explicit cross-locale exception. -The pairing core resolves relative paths against the repository tree, including extensionless and directory-index aliases, and normalizes paired locale paths to one English-sibling identity for structural comparison. `verify-translation-pairing` separately rejects a wrong-locale target with the source file, line, actual URL, and expected URL. The merge driver and mechanical translation briefing use the same semantic comparison, so they accept locale-correct path differences without weakening any other structural requirement. +Page-navigation links whose intended target is a tutorial landing page name `index.md` or `index.zh.md` explicitly, so GitHub opens the page rather than a directory listing. The audited basic, framework, and practice entries use those concrete targets; ordinary package, source, example, and manifest directory references remain valid, and no generic directory-link prohibition exists. + +The pairing core resolves relative paths against the repository tree, applies the active-scope and manifest-exclusion predicate, and normalizes paired locale paths to one English-sibling identity for structural comparison. `verify-translation-pairing` separately rejects a wrong-locale target with the source file, line, actual URL, and expected URL. The merge driver, mechanical translation briefing, and Cordis generator use the same scope predicate and semantic comparison, so they accept locale-correct path differences without weakening any other structural requirement. The Cordis subsystem-region generator renders one catalog model, then projects paired document destinations for the Chinese output. Generated-region comparison normalizes only these paired locale paths; markers, prose, ordering, code, non-document URLs, and query/fragment suffixes remain byte-equal. @@ -20,7 +22,7 @@ Existing active bilingual sources use the locale-correct target. A Chinese targe ## Verification -Pairing tests cover English and Chinese locale selection, targets without a sibling, switcher exclusion, exact query/fragment retention, directory-index resolution, definitions, rewrites, and diagnostics. Merge-driver, translation-brief, Cordis generator, and documentation-site tests cover their respective consumers. Corpus checks require zero wrong-locale links, resolvable fragments, fresh generated regions, current pair records, and a successful documentation-site build. +Pairing tests cover English and Chinese locale selection, out-of-scope targets with siblings, in-scope targets missing a counterpart, switcher exclusion, exact query/fragment retention, directory-index resolution, definitions, rewrites, and diagnostics. Documentation-site tests also pin the audited basic, framework, and practice entry links to explicit index pages in both locales. Merge-driver, translation-brief, and Cordis generator tests cover their respective consumers. Corpus checks require zero wrong-locale links, resolvable fragments, fresh generated regions, current pair records, and a successful documentation-site build. ## Alternatives considered @@ -28,10 +30,10 @@ Pairing tests cover English and Chinese locale selection, targets without a sibl **Use translated heading fragments.** Locale-specific fragments require each link producer to know a translated heading and create a second mapping whose lifecycle can drift. One shared suffix plus an explicit target alias keeps the stable identifier with the target document. -**Maintain a locale-link manifest.** The file naming convention and repository tree already determine the target sibling. A second registry would duplicate identity and require updates for every move or new pair. +**Maintain a locale-link manifest.** Pairing discovery, the exclusions-only manifest, and the sibling naming convention already determine whether a target belongs to the corpus and which paths form its pair. A second registry would duplicate identity and require updates for every move or new pair. **Rewrite links only during publication.** The website already does this, but GitHub and other repository renderers consume the source files directly. Correct source paths are the user-visible behavior. ## Consequences -GitHub readers remain in the language they selected when a translated target exists, and future regressions fail in the same corpus-wide pairing check that owns bilingual structure. Pair sides intentionally differ in paired document path spelling, while every other link property stays aligned. Stable English fragment aliases add small permanent identifiers to translated targets, and generators that own both outputs must project locale paths before recording the pair. +GitHub readers remain in the language they selected when a target belongs to the active bilingual corpus, and future regressions fail in the same corpus-wide pairing check that owns bilingual structure. Pair sides intentionally differ in paired document path spelling, while every other link property stays aligned. Stable English fragment aliases add small permanent identifiers to translated targets, and generators that own both outputs must project locale paths before recording the pair. diff --git a/.agents/notes/implemented/process/2026-08-18-localized-bilingual-links.zh.md b/.agents/notes/implemented/process/2026-08-18-localized-bilingual-links.zh.md index 15e37467c5..a86be2800a 100644 --- a/.agents/notes/implemented/process/2026-08-18-localized-bilingual-links.zh.md +++ b/.agents/notes/implemented/process/2026-08-18-localized-bilingual-links.zh.md @@ -10,9 +10,11 @@ GitHub 会直接解析仓库 Markdown 链接,不经过文档网站的 locale p ## Decision -目标存在中英文兄弟文件配对时,仓库相对文档链接跟随源文件 locale:英文源使用目标 `.md`,中文源使用其 `.zh.md`。两侧保持相同的语义目标以及完全相同的 query/fragment 后缀。外部 URL、图片、纯页内 fragment 与没有中文兄弟文件的目标保持不变。语言切换行是显式跨 locale 例外。 +目标属于活跃双语语料时,仓库相对文档链接跟随源文件 locale:英文源使用目标 `.md`,中文源使用其 `.zh.md`。两侧保持相同的语义目标以及完全相同的 query/fragment 后缀。该范围内缺少对侧属于配对完整性错误,不得回退;外部 URL、图片、纯页内 fragment 与范围外目标保持不变。语言切换行是显式跨 locale 例外。 -配对核心会依据仓库文件树解析相对路径,包括无扩展名路径与目录索引别名,并在结构比较时把配对的 locale 路径规范化为同一个英文兄弟文件身份。`verify-translation-pairing` 会另外拒绝 locale 错误的目标,并报告源文件、行号、实际 URL 与期望 URL。合并驱动与机械翻译简报使用同一套语义比较,因此既能接受正确的 locale 路径差异,又不会削弱其他结构要求。 +意图进入教程首页的页面导航链接会显式写出 `index.md` 或 `index.zh.md`,使 GitHub 直接打开页面,而不是目录列表。已审计的 basic、framework 与 practice 入口均使用这些具体目标;普通 package、源码、示例与 manifest 目录引用仍然有效,不存在通用的目录链接禁止规则。 + +配对核心会依据仓库文件树解析相对路径,再应用活跃范围与 manifest 排除项谓词,并在结构比较时把配对的 locale 路径规范化为同一个英文兄弟文件身份。`verify-translation-pairing` 会另外拒绝 locale 错误的目标,并报告源文件、行号、实际 URL 与期望 URL。合并驱动、机械翻译简报与 Cordis 生成器使用同一套范围谓词和语义比较,因此既能接受正确的 locale 路径差异,又不会削弱其他结构要求。 Cordis subsystem 区块生成器先渲染同一个 catalog model,再为中文输出投影配对文档目标。生成区块比较只规范化这些配对 locale 路径;marker、正文、顺序、代码、非文档 URL 与 query/fragment 后缀仍须逐字节一致。 @@ -20,7 +22,7 @@ Cordis subsystem 区块生成器先渲染同一个 catalog model,再为中文 ## Verification -配对测试覆盖中英文 locale 选择、没有对侧的目标、语言切换行排除、query/fragment 原样保留、目录索引解析、定义式链接、重写与诊断。合并驱动、翻译简报、Cordis 生成器和文档网站测试分别覆盖各自消费路径。全语料检查要求 locale 错误链接为零、fragment 可解析、生成区块新鲜、配对记录为当前内容,并要求文档网站成功构建。 +配对测试覆盖中英文 locale 选择、存在兄弟文件的范围外目标、范围内缺少对侧的目标、语言切换行排除、query/fragment 原样保留、目录索引解析、定义式链接、重写与诊断。文档网站测试还会固定中英文两侧已审计的 basic、framework 与 practice 入口,要求它们指向具体索引页。合并驱动、翻译简报与 Cordis 生成器测试分别覆盖各自消费路径。全语料检查要求 locale 错误链接为零、fragment 可解析、生成区块新鲜、配对记录为当前内容,并要求文档网站成功构建。 ## Alternatives considered @@ -28,10 +30,10 @@ Cordis subsystem 区块生成器先渲染同一个 catalog model,再为中文 **使用翻译后的标题 fragment。** 各 locale 专属 fragment 会要求每个链接生成方了解译文标题,还会引入一份可能发生漂移的映射。共享同一个后缀,并由目标文档提供显式别名,能让稳定标识归目标文档所有。 -**维护 locale 链接 manifest。** 文件命名约定与仓库文件树已经能够确定目标兄弟文件。第二份注册表会重复身份事实,并要求每次移动文件或新增配对时同步更新。 +**维护 locale 链接 manifest。** 配对发现、只含排除项的 manifest 与兄弟命名约定已经能够确定目标是否属于语料以及配对路径。第二份注册表会重复身份事实,并要求每次移动文件或新增配对时同步更新。 **只在发布时重写链接。** 文档网站已经这样处理,但 GitHub 与其他仓库渲染器会直接消费源文件。正确的源码路径本身就是用户可见行为。 ## Consequences -存在翻译目标时,GitHub 读者会留在自己选择的语言中;未来回退会由拥有双语结构的同一道全语料配对检查拒绝。配对两侧会有意使用不同的配对文档路径拼写,而链接的其他属性仍保持一致。稳定的英文 fragment 别名会为翻译目标增加少量永久标识;同时拥有两侧输出的生成器必须先投影 locale 路径,再记录配对。 +目标属于活跃双语语料时,GitHub 读者会留在自己选择的语言中;未来回退会由拥有双语结构的同一道全语料配对检查拒绝。配对两侧会有意使用不同的配对文档路径拼写,而链接的其他属性仍保持一致。稳定的英文 fragment 别名会为翻译目标增加少量永久标识;同时拥有两侧输出的生成器必须先投影 locale 路径,再记录配对。 diff --git a/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.i18n.yaml b/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.i18n.yaml index 7a85aa4897..52d81ec626 100644 --- a/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.md 2026-06-20-remove-agent-boundary-mirror-events.md: cb3cb83e7e37fae52b59bb8566284aa80708e23a -2026-06-20-remove-agent-boundary-mirror-events.zh.md: ccdf24c33672fc239c8f938db17ef98b5944d09f +2026-06-20-remove-agent-boundary-mirror-events.zh.md: 85572deb662b544b1de635a063e3a19d2388fda4 diff --git a/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.zh.md b/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.zh.md index ccdf24c336..85572deb66 100644 --- a/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.zh.md +++ b/.agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.zh.md @@ -24,13 +24,13 @@ Status: implemented 保留——不是持久边界镜像,因此不在本决策范围内: -- `agent/steering`——不是边界,因此不在本决策范围内。它镜像持久的 `steering/message` 控制记录,而非边界,后来由自己的后续决策移除:[移除 `agent/steering` 镜像 emit](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.zh.md)。 -- `agent/stream-chunk`——实时 token 流。不在本决策范围内(它镜像持久的 `assistant/chunk`,而非边界),后来由自己的后续决策移除:[停止将 token 流镜像为 agent 事件](../../archived/simplification/2026-07-02-remove-stream-chunk-mirror.zh.md)。 +- `agent/steering`——不是边界,因此不在本决策范围内。它镜像持久的 `steering/message` 控制记录,而非边界,后来由自己的后续决策移除:[移除 `agent/steering` 镜像 emit](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.md)。 +- `agent/stream-chunk`——实时 token 流。不在本决策范围内(它镜像持久的 `assistant/chunk`,而非边界),后来由自己的后续决策移除:[停止将 token 流镜像为 agent 事件](../../archived/simplification/2026-07-02-remove-stream-chunk-mirror.md)。 - `agent/created`、`agent/disposed`、`agent/status`、`agent/error`、`agent/queued`——不属于 transcript 数据的生命周期/控制事件。尤其是 `agent/queued`,它是在任何持久事件存在之前触发的收件箱确认(取消的排队工作可能永远不会进入日志),所以有意只保留为实时事件。 ## 曾考虑的替代方案 -- **将 `agent/steering` 一并移除**——原始提案的范围;因超出范围而被排除:它镜像持久的 `steering/message` 控制记录,而非边界,后来由[自己的决策](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.zh.md)移除(`agent/stream-chunk` 也由[流分片镜像 Agent Note](../../archived/simplification/2026-07-02-remove-stream-chunk-mirror.zh.md) 移除)。 +- **将 `agent/steering` 一并移除**——原始提案的范围;因超出范围而被排除:它镜像持久的 `steering/message` 控制记录,而非边界,后来由[自己的决策](../../archived/simplification/2026-07-04-remove-agent-steering-mirror.md)移除(`agent/stream-chunk` 也由[流分片镜像 Agent Note](../../archived/simplification/2026-07-02-remove-stream-chunk-mirror.md) 移除)。 - **为 stdio UI 保留轮次镜像**——[事件域语义 Agent Note](../architecture/2026-06-30-event-domain-semantics.zh.md) 的原始立场;在此否决,因为 `dsh-ui-stdio` 是可随时丢弃的测试 REPL,而非承载关键约束的消费方,并且它改为根据 `session/event` 加自己的实时目标对象渲染边界。 ## 后果 diff --git a/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.i18n.yaml b/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.i18n.yaml index 72097b65d3..75c3259498 100644 --- a/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.md 2026-06-26-fsspec-style-fs-seam.md: 927220a4042a47aa1a76926161427613f3a2b809 -2026-06-26-fsspec-style-fs-seam.zh.md: fe8bbae0324196dd143563b77dd24d3cf38d5459 +2026-06-26-fsspec-style-fs-seam.zh.md: 8b9f36f884955aa23ba82bfe7798db6c1c8a6cb4 diff --git a/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.zh.md b/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.zh.md index fe8bbae032..8b9f36f884 100644 --- a/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.zh.md +++ b/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.zh.md @@ -113,7 +113,7 @@ type FsWriteIntent = ## 后续扩展 -后来,[为文件系统 seam 添加直接目录列表](../../archived/architecture/2026-07-03-filesystem-directory-listing-seam.zh.md)进一步扩展了该 seam。该后续工作单独记录,使本文继续描述最初落地的 fsspec 风格改造。 +后来,[为文件系统 seam 添加直接目录列表](../../archived/architecture/2026-07-03-filesystem-directory-listing-seam.md)进一步扩展了该 seam。该后续工作单独记录,使本文继续描述最初落地的 fsspec 风格改造。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.i18n.yaml index 2f009523d0..e74da894f9 100644 --- a/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.md 2026-07-04-tighten-hook-protocol-contract.md: 70cf7a15fe0ec0c43c0ef768667ef4741aee27d2 -2026-07-04-tighten-hook-protocol-contract.zh.md: 0a32c2d38b3ea39b89a4edcf40b88b7ebe41871a +2026-07-04-tighten-hook-protocol-contract.zh.md: 3be16b4b8cdbe210b0f2e2dc1dc315dc0d3a2bd6 diff --git a/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.zh.md b/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.zh.md index 0a32c2d38b..3be16b4b8c 100644 --- a/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.zh.md @@ -6,7 +6,7 @@ Status: implemented ## 问题 -`dsh-hook-protocol`/bridge 约定中有四部分没有遵守 [subagent observe/enrich Agent Note](../../archived/feature/2026-06-30-subagent-observe-enrich.zh.md) 记下的准则——后者因缺少消费方而删除 `agentType` 生命周期字段,以下各项没有通过同一检验: +`dsh-hook-protocol`/bridge 约定中有四部分没有遵守 [subagent observe/enrich Agent Note](../../archived/feature/2026-06-30-subagent-observe-enrich.md) 记下的准则——后者因缺少消费方而删除 `agentType` 生命周期字段,以下各项没有通过同一检验: 1. **`HookDialect` 的 `'native'` 变体**(`packages/hooks/hook-protocol/src/types.ts`)没有生产者——bridge 会标记 `'claude'` 和 `'codex'`;所有位置中唯一构造 `'native'` 的是该库自己的单元测试。字段自身的 JSDoc 将 `dialect` 定义为「运行它的 bridge」,而 native 不是 bridge:[拦截扩展点 Agent Note](../feature/2026-06-30-interception-extension-points.zh.md) 记载 native 钩子不是一个包,并且「native 插件无需持久钩子日志即可使用类型化 Decision」;旗舰 native 插件实践示例恰好断言了这一点(完全没有 `hook/*` 事件)。 2. **`HookOutput.suppressOutput`**(同一文件)被 codec 解析后在所有路径上均被丢弃:没有 bridge 分支处理它、没有合并 fold、没有 warn、没有 deferred-list 行——在所有「被解析但未兑现」的同类字段中它是唯一没有明确延期声明的(`updatedInput` → 一条 warn 日志加 [pre-tool-input-rewrite 提案](../../proposed/feature/2026-06-30-pre-tool-input-rewrite.zh.md);`systemMessage` → 一条 warn 日志加 README deferred 行;`continue`/`stopReason` → 一个 `TODO(hook-continue-false)` 锚点加 `'stop'` decision 记录)。从结构上看根本无物可抑制:钩子 stdout 从不进入任何 transcript(文本记录);上下文仅通过 `additionalContext` 流入,日志也只记录 `decision`/`stderrSummary`。因此,钩子作者设置 `suppressOutput: true` 得到的是无声的空操作,且无任何警告。 diff --git a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml index 9604b13b95..a6ce4ad6b0 100644 --- a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md 2026-07-28-remove-synthetic-log-only-turns.md: 2b0add1a916021cfc1790c8f4fd5684305d81be8 -2026-07-28-remove-synthetic-log-only-turns.zh.md: c1ceed0a58f79b1c854b8ee1636a805b0c14117d +2026-07-28-remove-synthetic-log-only-turns.zh.md: 59fa8e4fcc6301cf6d04cab0d572e0c4ff15731b diff --git a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md index c1ceed0a58..59fa8e4fcc 100644 --- a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md @@ -22,7 +22,7 @@ Status: implemented 会话 fork 可以结束于开放轮次之外的任意稳定事件位置,而不限于 `turn/end`。这样,默认 fork 会保留独立标题和其他插件所属的纯日志记录,同时仍拒绝在活跃执行过程中截断前缀。 -历史上的[通用轮次封闭决策](../../archived/architecture/2026-06-15-turn-enclosure-invariant.zh.md)如今只适合用于解释为何曾引入合成机制。[上下文注入决策](../architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md)确立了当前语义:一个轮次表示一次模型循环执行。[排队手动压缩决策](../feature/2026-07-30-queued-manual-compaction.zh.md)将该规则应用于持久多事件标记对,并拥有其标记与接纳语义。 +历史上的[通用轮次封闭决策](../../archived/architecture/2026-06-15-turn-enclosure-invariant.md)如今只适合用于解释为何曾引入合成机制。[上下文注入决策](../architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md)确立了当前语义:一个轮次表示一次模型循环执行。[排队手动压缩决策](../feature/2026-07-30-queued-manual-compaction.zh.md)将该规则应用于持久多事件标记对,并拥有其标记与接纳语义。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/simplification/2026-08-04-remove-tui-package.i18n.yaml b/.agents/notes/implemented/simplification/2026-08-04-remove-tui-package.i18n.yaml index 4a7869532e..a4e350da2a 100644 --- a/.agents/notes/implemented/simplification/2026-08-04-remove-tui-package.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-08-04-remove-tui-package.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-08-04-remove-tui-package.md 2026-08-04-remove-tui-package.md: 2834ec10aa86a3dcf612a1fe9067732ca93d921f -2026-08-04-remove-tui-package.zh.md: 7cd602cc02c784756001b620f18d5288f657e2c7 +2026-08-04-remove-tui-package.zh.md: 6532bb690a14e4d0e877a6e1cd049800369aaf6c diff --git a/.agents/notes/implemented/simplification/2026-08-04-remove-tui-package.zh.md b/.agents/notes/implemented/simplification/2026-08-04-remove-tui-package.zh.md index 7cd602cc02..6532bb690a 100644 --- a/.agents/notes/implemented/simplification/2026-08-04-remove-tui-package.zh.md +++ b/.agents/notes/implemented/simplification/2026-08-04-remove-tui-package.zh.md @@ -16,7 +16,7 @@ Status: implemented 作为 TUI 包最后消费方的 SDK 项目工具链已由[工具链移除决策](2026-08-11-remove-sdk-project-toolchain.zh.md)删除。宿主应用仍可直接挂载提供方无关的 `dsh-user-questions`、`dsh-commands` 和呈现服务。 -本决策取代[显式配置 `dsh` 入口决策](../../archived/simplification/2026-08-03-explicit-config-dsh-entrypoint.zh.md)中保留可复用包的决定,也使已归档 TUI 实现记录不再适用于当前状态。这些历史记录继续保持冻结,但不再作为受支持包或应用清单的依据。 +本决策取代[显式配置 `dsh` 入口决策](../../archived/simplification/2026-08-03-explicit-config-dsh-entrypoint.md)中保留可复用包的决定,也使已归档 TUI 实现记录不再适用于当前状态。这些历史记录继续保持冻结,但不再作为受支持包或应用清单的依据。 本记录汇总了因移除该包而无法继续保持现行有效、现已删除的仅涉及该包的记录。终端 UI 曾在长对话期间保持会话身份可见、移除重复模型标签、为消息附加耗时与阶段状态、在提示词旁显示 workspace 与分支上下文,并保守地解析完整 XML 包装层,以生成人类可读的回退输出。这些选择改善了一个终端前端,但在没有部署的情况下,并不足以证明应保留它。未来的 XML 回退仍必须使用真正的解析器而非正则表达式。 diff --git a/.agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.i18n.yaml b/.agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.i18n.yaml index 6b5c7ad489..b940675bf2 100644 --- a/.agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.md 2026-08-10-web-remove-steering-interjection-caption.md: 2c396f54945fb1c3626f2e5fcc849e81893c23f0 -2026-08-10-web-remove-steering-interjection-caption.zh.md: d20d0ea741305ade3ad624ef5ef132520d241b26 +2026-08-10-web-remove-steering-interjection-caption.zh.md: 10a3f66d161f56111b0d1de207c05e6469a3e5bc diff --git a/.agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.zh.md b/.agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.zh.md index d20d0ea741..10a3f66d16 100644 --- a/.agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.zh.md +++ b/.agents/notes/implemented/simplification/2026-08-10-web-remove-steering-interjection-caption.zh.md @@ -14,7 +14,7 @@ steering 完全按用户气泡渲染。`UserStyleBubble` 不再有 steering 标 运行时的区分保持不变。从持久 `agent/inbox/spliced` 历史投影 `SteeringMessageNode`、`data-pending-steering` 属性、待处理到持久的交接全部保留:待处理生命周期无论呈现如何都需要节点身份,测试也仍通过该属性定位待处理气泡。 -本决策部分取代[上下文来源与 steer 标识决策](../feature/2026-08-04-web-context-source-and-steer-marks.zh.md)中的 steering 条款;其上下文来源与召回命名仍然有效。这个标注此前已经翻转过一次:[已归档的取消 steer 装饰决策](../../archived/simplification/2026-07-31-web-ui-no-steer-entry-or-interjection-chrome.zh.md)在 composer 无法 steer 时移除了它,2026-08-04 的决策在 composer 获得 Steer 手势后把它加了回来。本次移除不重议手势本身——steering 入口、Queue dock 的插话发送操作、待处理生命周期各归其主——只判定 transcript 不需要为其结果命名。 +本决策部分取代[上下文来源与 steer 标识决策](../feature/2026-08-04-web-context-source-and-steer-marks.zh.md)中的 steering 条款;其上下文来源与召回命名仍然有效。这个标注此前已经翻转过一次:[已归档的取消 steer 装饰决策](../../archived/simplification/2026-07-31-web-ui-no-steer-entry-or-interjection-chrome.md)在 composer 无法 steer 时移除了它,2026-08-04 的决策在 composer 获得 Steer 手势后把它加了回来。本次移除不重议手势本身——steering 入口、Queue dock 的插话发送操作、待处理生命周期各归其主——只判定 transcript 不需要为其结果命名。 ## 考虑过的替代方案 diff --git a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.i18n.yaml b/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.i18n.yaml index 382692bc44..d8d6915f7b 100644 --- a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md 2026-06-19-acp-snapshot-tests.md: bee59901eed7bb068dc93699aa2bb5e55216b8ff -2026-06-19-acp-snapshot-tests.zh.md: 98bcf90275e920493ae549290f657ff2695baca1 +2026-06-19-acp-snapshot-tests.zh.md: 032c681d3db464d6a5254f67a5eee1551256df66 diff --git a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.zh.md b/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.zh.md index 98bcf90275..032c681d3d 100644 --- a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.zh.md +++ b/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.zh.md @@ -44,14 +44,14 @@ Status: implemented 记录模式使用真实 `llm-deepseek` 适配器和配置为 `persistenceCompression: 'none'` 的 JSONL 持久化后端运行场景,再把生成的 `.jsonl` 复制到场景目录。显式 raw 模式让已提交回放 fixture 保持逐行可读,而普通部署使用后端的压缩默认值;符合条件的分片连续段仍使用默认的打包存储行。逐事件追加具有持久性,但 harness 会在采集前优雅关闭子进程(关闭 stdin → `await ctx.dispose()`),以确保最终事件已刷出。`llm-replay` 本身不执行记录——它只负责回放。 -回放使用 `cordis.snapshot.yml` overlay,以 `llm-replay` 替换真实适配器,同时保留实际组合。记录使用普通配置和由 harness 提供的持久化根目录。回放模式跳过 `.env` 加载,因此意外存在的 API 密钥不会触发真实调用。参见[单一来源配置 Agent Note](../../archived/testing/2026-07-04-single-source-acp-replay-config.zh.md)。 +回放使用 `cordis.snapshot.yml` overlay,以 `llm-replay` 替换真实适配器,同时保留实际组合。记录使用普通配置和由 harness 提供的持久化根目录。回放模式跳过 `.env` 加载,因此意外存在的 API 密钥不会触发真实调用。参见[单一来源配置 Agent Note](../../archived/testing/2026-07-04-single-source-acp-replay-config.md)。 ### 两个表面:归一化后比对 快照运行断言**两个**归一化后的表面,因为 harness 的外部表面是不同的: 1. **stdout transcript**——自动化客户端收到的、分帧后的 ACP JSON-RPC 响应与已提交的消息更新。它捕获传输约定的回归,与已提交的 `stdout.expected.jsonl` 比较。 -2. **重新持久化的会话 JSONL**,经过规范化后与 `session.jsonl` 比较。同一 fixture 同时作为回放来源和预期日志。提示词与工具的主体内容会被清理;每种请求头类别由一个场景固定余下的请求头序列。该 pin 默认拥有可读的提示词与工具 schema 伴随文件;当完整的对应序列相同时,也可将另一个 pin 指定为其中任一来源,因此每个不同的伴随文件版本只提交一次。fixture 守卫会拒绝重复的伴随文件内容,录制/刷新会拒绝生成不同字节的共享引用方。最初的请求头固定理由保留在[请求头固定 Agent Note](../../archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.zh.md)中。Override 场景仅从其伴随文件派生模型行为。 +2. **重新持久化的会话 JSONL**,经过规范化后与 `session.jsonl` 比较。同一 fixture 同时作为回放来源和预期日志。提示词与工具的主体内容会被清理;每种请求头类别由一个场景固定余下的请求头序列。该 pin 默认拥有可读的提示词与工具 schema 伴随文件;当完整的对应序列相同时,也可将另一个 pin 指定为其中任一来源,因此每个不同的伴随文件版本只提交一次。fixture 守卫会拒绝重复的伴随文件内容,录制/刷新会拒绝生成不同字节的共享引用方。最初的请求头固定理由保留在[请求头固定 Agent Note](../../archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.md)中。Override 场景仅从其伴随文件派生模型行为。 两个表面互补:stdout 覆盖精简的自动化协议格式,JSONL 覆盖协议格式有意省略的循环、工具和边界结构。 diff --git a/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.i18n.yaml b/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.i18n.yaml index 1e9ffc68df..20b2d178bd 100644 --- a/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.md 2026-06-22-fork-child-replay-seed-boundary.md: 2768f32adff2badf2d4b18d14ce7eebcb87fda09 -2026-06-22-fork-child-replay-seed-boundary.zh.md: 459c50ee6fa49220e89838376ac38198a5403bce +2026-06-22-fork-child-replay-seed-boundary.zh.md: d7a4468e5304a17b9b52bbc9669f6637e5207b15 diff --git a/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.zh.md b/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.zh.md index 459c50ee6f..d7a4468e53 100644 --- a/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.zh.md +++ b/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.zh.md @@ -35,7 +35,7 @@ subagent 脚本由 [`deriveReplayScript`](../../../../packages/test-support/llm- `dsh-llm-replay` 的 `parseSessionHeader` 现在也读取 `seedLength`(缺失则为 0),`loadSessionScripts` 从 `parseSessionLog(text).slice(seedLength)` 推导子会话条目——即边界及之后的事件,也就是子会话自身的模型调用。对 spawn 子会话而言 `seedLength` 为 0,此操作是空操作,spawn 场景逐字节不变。 -这弥补了路由正确性的缺口,两个已录制的 fork 场景对其进行端到端验证——见[记录 fork 与混合 spawn+fork 快照场景](../../archived/testing/2026-06-22-fork-snapshot-scenarios.zh.md)。 +这弥补了路由正确性的缺口,两个已录制的 fork 场景对其进行端到端验证——见[记录 fork 与混合 spawn+fork 快照场景](../../archived/testing/2026-06-22-fork-snapshot-scenarios.md)。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.i18n.yaml b/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.i18n.yaml index 1dca253009..5041596972 100644 --- a/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.md 2026-06-22-subagent-snapshot-replay.md: bdd4e93ea185c5483c2ff81eba617baaf8cc40b2 -2026-06-22-subagent-snapshot-replay.zh.md: 3dff0f5cb8dd0bffaf6c1368c5d6a114b9041fd9 +2026-06-22-subagent-snapshot-replay.zh.md: 48691fe4db94febba8d041cf750b69aa9849dd52 diff --git a/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.zh.md b/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.zh.md index 3dff0f5cb8..48691fe4db 100644 --- a/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.zh.md +++ b/.agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.zh.md @@ -54,5 +54,5 @@ Status: implemented - `TODO(subagent-snapshots)` 延期项已解决:嵌套 agent 的 transcript 现在是快照层的一等形态。 - `GenerateOptions.sessionId` 是一个小而诚实的 core API 新增,在回放之外同样有用(遥测、请求路由)。 -- `subagent` 工具绑定到单一提供方,因此 `subagent-multi` 中的两个子 agent 都是 spawn(全新创建)。键控按会话路由而非按后端路由,因此对 fork 同样正确。但脚本*派生*逻辑此前不正确:fork 子会话的日志以种子化的父前缀(父会话的 `assistant/chunk` 事件)开头,如果从完整日志派生脚本,就会把父 agent 的响应当作子 agent 的来回放。这一正确性缺口通过持久化种子边界来弥合——见[持久化 seed 边界以确保 fork 子会话回放正确路由](2026-06-22-fork-child-replay-seed-boundary.zh.md)——录制的 fork 与混合 spawn+fork 场景现在通过一份 transcript 同时验证两种传输方式(见[记录 fork 与混合 spawn+fork 快照场景](../../archived/testing/2026-06-22-fork-snapshot-scenarios.zh.md))。 +- `subagent` 工具绑定到单一提供方,因此 `subagent-multi` 中的两个子 agent 都是 spawn(全新创建)。键控按会话路由而非按后端路由,因此对 fork 同样正确。但脚本*派生*逻辑此前不正确:fork 子会话的日志以种子化的父前缀(父会话的 `assistant/chunk` 事件)开头,如果从完整日志派生脚本,就会把父 agent 的响应当作子 agent 的来回放。这一正确性缺口通过持久化种子边界来弥合——见[持久化 seed 边界以确保 fork 子会话回放正确路由](2026-06-22-fork-child-replay-seed-boundary.zh.md)——录制的 fork 与混合 spawn+fork 场景现在通过一份 transcript 同时验证两种传输方式(见[记录 fork 与混合 spawn+fork 快照场景](../../archived/testing/2026-06-22-fork-snapshot-scenarios.md))。 - 进程外(ACP(Agent Client Protocol))subagent 是完全不同的回放形态(每个子 agent 是自己的进程、有自己的回放),作为 `TODO(acp-subagent-replay)` 记录在 `subagent-acp` 中。 diff --git a/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.i18n.yaml b/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.i18n.yaml index bab9d4f580..834ddaf3ac 100644 --- a/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.i18n.yaml +++ b/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/proposed/process/2026-06-11-api-extractor-reports.md 2026-06-11-api-extractor-reports.md: 2aee524596a5b798591fecded539a8d2236a3d96 -2026-06-11-api-extractor-reports.zh.md: b90c4670fc603d69efc1e3fc36dd363c67ddb621 +2026-06-11-api-extractor-reports.zh.md: e62b6168efac5c8296b55ff8ee0c7861d1849d31 diff --git a/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.zh.md b/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.zh.md index b90c4670fc..e62b6168ef 100644 --- a/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.zh.md +++ b/.agents/notes/proposed/process/2026-06-11-api-extractor-reports.zh.md @@ -4,7 +4,7 @@ Status: proposed [English](2026-06-11-api-extractor-reports.md) | 中文 -> 文档块类型检查与事件分类体系两部分已交付([doc-sync(文档同步门禁)强制](../../archived/process/2026-06-11-doc-sync-enforcement.zh.md));剩余的 API 报告部分作为独立提案被推迟。 +> 文档块类型检查与事件分类体系两部分已交付([doc-sync(文档同步门禁)强制](../../archived/process/2026-06-11-doc-sync-enforcement.md));剩余的 API 报告部分作为独立提案被推迟。 ## 问题 diff --git a/.agents/notes/proposed/process/2026-06-20-discover-package-inventory.i18n.yaml b/.agents/notes/proposed/process/2026-06-20-discover-package-inventory.i18n.yaml index 9fa2322318..98087b0c9b 100644 --- a/.agents/notes/proposed/process/2026-06-20-discover-package-inventory.i18n.yaml +++ b/.agents/notes/proposed/process/2026-06-20-discover-package-inventory.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/proposed/process/2026-06-20-discover-package-inventory.md 2026-06-20-discover-package-inventory.md: 7de865e43f87f0e41fad43b3786b9825509e9d50 -2026-06-20-discover-package-inventory.zh.md: 06b75bcf28b934e6a03106130d18b4072d759838 +2026-06-20-discover-package-inventory.zh.md: fed3d9dd8740b2dc22434f8af7e1e8c025502f61 diff --git a/.agents/notes/proposed/process/2026-06-20-discover-package-inventory.zh.md b/.agents/notes/proposed/process/2026-06-20-discover-package-inventory.zh.md index 06b75bcf28..fed3d9dd87 100644 --- a/.agents/notes/proposed/process/2026-06-20-discover-package-inventory.zh.md +++ b/.agents/notes/proposed/process/2026-06-20-discover-package-inventory.zh.md @@ -8,7 +8,7 @@ Status: proposed 包与门禁清单在 TypeScript project references、包文档、CI 描述和 Knip 覆盖项中反复出现。大多数只是重述包布局、manifest(元数据清单)数据或聚合命令内容。因此每新增一个包都会产生本可避免的同步点。 -[包层级结构](../../archived/architecture/2026-06-20-package-hierarchy.zh.md)已经手动消除了其中若干:`scripts/publint-all.ts` 现在从 `packages//` 布局推导列表,两份 `tsconfig` 的 `paths` 映射也合并为一个 `@deepseek-ai/dsh-*` 通配符。剩下的是无法用 glob 消除的清单,主要是聚合配置(`tsconfig.host.json`、`tsconfig.client.json`)中的项目引用(`references`)——TypeScript 要求它们是显式数组(没有通配符形式)。 +[包层级结构](../../archived/architecture/2026-06-20-package-hierarchy.md)已经手动消除了其中若干:`scripts/publint-all.ts` 现在从 `packages//` 布局推导列表,两份 `tsconfig` 的 `paths` 映射也合并为一个 `@deepseek-ai/dsh-*` 通配符。剩下的是无法用 glob 消除的清单,主要是聚合配置(`tsconfig.host.json`、`tsconfig.client.json`)中的项目引用(`references`)——TypeScript 要求它们是显式数组(没有通配符形式)。 当静态列表编码的是策略时,它们是合理的;当它们只是重复 `package.json`、workspace glob 或包层级结构中已有的 manifest 数据或布局事实时,就是不必要的摩擦。 diff --git a/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.i18n.yaml b/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.i18n.yaml index 2e18c1c2f5..d460dbd142 100644 --- a/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.i18n.yaml +++ b/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.md 2026-06-20-truncate-interrupted-turns.md: c4b0084de537927a27829eb78990c6559bd1df7d -2026-06-20-truncate-interrupted-turns.zh.md: f4461242d23de137c351c39d4a08e0efda6881b4 +2026-06-20-truncate-interrupted-turns.zh.md: ad6c85fc62077be45b1f3bb55c31cf4b4a7fd1d1 diff --git a/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.zh.md b/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.zh.md index f4461242d2..ad6c85fc62 100644 --- a/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.zh.md +++ b/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.zh.md @@ -31,6 +31,6 @@ Status: rejected — 单个轮次可以包含大量真实工作,包括多个 ## 相关 -本提案是对[会话持久化](../../implemented/architecture/2026-06-14-session-persistence.zh.md)与历史上的[通用轮次封闭规则](../../archived/architecture/2026-06-15-turn-enclosure-invariant.zh.md)的直接简化。它还移除了持久化步骤边界事件的大部分动机,使[移除持久化步骤边界事件](2026-06-20-drop-durable-step-boundaries.zh.md)的改动更小。 +本提案是对[会话持久化](../../implemented/architecture/2026-06-14-session-persistence.zh.md)与历史上的[通用轮次封闭规则](../../archived/architecture/2026-06-15-turn-enclosure-invariant.md)的直接简化。它还移除了持久化步骤边界事件的大部分动机,使[移除持久化步骤边界事件](2026-06-20-drop-durable-step-boundaries.zh.md)的改动更小。 diff --git a/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.i18n.yaml b/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.i18n.yaml index 9dea9a958c..3fc0ba2154 100644 --- a/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.i18n.yaml +++ b/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.md 2026-07-26-dependency-swaps-rejected-by-nih-audit.md: c834142bccaeca2f7407c984767f5aa0af88bc22 -2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md: 17c59f460436671d42cd9b2bc5a07c449199f49b +2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md: 043f87809cba0ad5bdae18eaef345d55ad3c41ec diff --git a/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md b/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md index 17c59f4604..043f87809c 100644 --- a/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md +++ b/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md @@ -18,7 +18,7 @@ Status: rejected — 下列每一项替换在证据上都未达到净简化门 - **以 `vscode-languageserver-types` 承担 lsp-stdio 的协议类型子集**:约 80 行类型加约 45 行守卫,但上游守卫在两个方向上都与本仓库不一致(接受本仓库必须拒绝的 `uri: undefined`;强制要求本仓库容忍缺失的 `targetRange`),而且 initialize 结果的形状住在 `vscode-languageserver-protocol` 里,会把 `vscode-jsonrpc` 拖成运行时依赖——为 80 行严格贴合规范的代码付出约 1 MB。 - **以 `json-rpc-2.0` 替换 `dsh-sdk-jsonrpc-server`**:可删除的关联/分发代码确实存在(约 100–130 行),但 NDJSON 协议格式(wire format)必须与手写的 Python SDK 客户端逐位一致,该包只有单一维护者,且 [GUI RPC 决策](../../implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.zh.md)已把这个包当作冻结的窄接口面对待。`vscode-jsonrpc` 更不合适(Content-Length 分帧、该协议并不具备的取消词汇)。 - **以 `jsonrpcclient` 承担 Python SDK 客户端**:v4 只做消息的构造/解析——约 20 行——而真正要紧的 500 行(子进程生命周期、线程化读取器、id 关联、双向的服务端角色应答)全都保留;该库处于低维护模式。 -- **以 `eventsource-parser` 替换 apiproxy 的 `readSse`**:可删除的分帧只有约 15 行,线路两端都在仓库内,规范符合性无关紧要,而且这会给一个浏览器安全的包添加依赖。(对比[已归档的 llm-deepseek 依赖决策](../../archived/simplification/2026-07-26-eventsource-parser-for-deepseek-sse.zh.md):那里线路对面是真实的提供方。) +- **以 `eventsource-parser` 替换 apiproxy 的 `readSse`**:可删除的分帧只有约 15 行,线路两端都在仓库内,规范符合性无关紧要,而且这会给一个浏览器安全的包添加依赖。(对比[已归档的 llm-deepseek 依赖决策](../../archived/simplification/2026-07-26-eventsource-parser-for-deepseek-sse.md):那里线路对面是真实的提供方。) **重试、定时器与异步:** @@ -41,14 +41,14 @@ Status: rejected — 下列每一项替换在证据上都未达到净简化门 - **以 `write-file-atomic` 承担 fs-local/storage-json 的原子写**:这些包缺少私有 0700 暂存目录、Win32 DACL 复制/`ReplaceFileW`、AbortSignal 支持和父目录 fsync——每一项都正是手写实现的意义所在。koffi Win32 绑定本身由 [Windows 持久发布决策](../../implemented/architecture/2026-07-05-windows-jsonl-durable-publish.zh.md)提供依据。 - **以 `fzstd`/原生 zstd 包承担 JSONL 帧扫描**:`node:zlib` 内置的 zstd 已经负责压缩([zstd 决策](../../implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.zh.md),其中明确否决了外部原生依赖);剩下的 `scanZstdFrames` 为撕裂尾部修复*不做解压*地定位 RFC 8878 帧边界,没有任何包公开这项能力。 -- **以 `picomatch`/`tinyglobby`/`ignore` 承担 fs 搜索**:根本不存在 glob 引擎——依照 [bash 承载的发现工具决策](../../archived/feature/2026-07-09-bash-backed-grep-glob-discovery.zh.md),两个发现类工具都通过 shell 调用 ripgrep。 +- **以 `picomatch`/`tinyglobby`/`ignore` 承担 fs 搜索**:根本不存在 glob 引擎——依照 [bash 承载的发现工具决策](../../archived/feature/2026-07-09-bash-backed-grep-glob-discovery.md),两个发现类工具都通过 shell 调用 ripgrep。 - **以 `istextorbinary`/`chardet` 承担文本检测**:手写实现是约 15 行的 NUL 采样加 fatal 模式的 `TextDecoder`;启发式包体量更大,还会改变模型能读到哪些文件(模型可见的 `FS_NOT_TEXT` 漂移)。 - **以 `shell-quote` 承担 POSIX 单引号包裹**:两个各 1 行、测试详尽的引号辅助函数,对上一个处于维护模式、有 CVE 历史、转义输出还不一样的包——安全边界不是省一行代码的地方。 - **以 `strip-ansi` 承担 pty 净化**:pty 净化器是一台流式状态机,带跨分片的断裂序列续接和 OSC `133;D` 提示符标记提取(shell 就绪信号);无状态的剥离器只能替掉约 20 行内层代码,全部状态机构件原样保留。`stripVTControlCharacters` 还被实证会泄漏未终止的 OSC 载荷,会话标题归一化器必须剥除它们(反欺骗)。 - **以 `pidtree`/`ps-tree` 承担 pty 进程巡检器**:它们只给裸 PID 树;这段代码需要对抗 PID 复用的启动时间身份校验,加上 `/proc` stdin 等待检测,没有包做这些。 -- **以 `execa` 承担 subagent-subprocess 的 dispose(资源释放)阶梯**:`forceKillAfterDelay` 覆盖 SIGTERM→SIGKILL,但覆盖不了先发 stdin EOF 的协作层级,也覆盖不了「无退出沿即 reject」约定;在这里采用它意味着重写各 spawn 调用点、同时阶梯照旧保留。(测试基础设施的 spawn 管线是另一回事——见[已归档的 execa 测试基础设施决策](../../archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.zh.md)。) +- **以 `execa` 承担 subagent-subprocess 的 dispose(资源释放)阶梯**:`forceKillAfterDelay` 覆盖 SIGTERM→SIGKILL,但覆盖不了先发 stdin EOF 的协作层级,也覆盖不了「无退出沿即 reject」约定;在这里采用它意味着重写各 spawn 调用点、同时阶梯照旧保留。(测试基础设施的 spawn 管线是另一回事——见[已归档的 execa 测试基础设施决策](../../archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.md)。) - **以 `tree-kill` 承担 acp-snapshot 拆除与 lsp 进程终止**:那些代码行做的是排空顺序与错误传播,不是进程树遍历;lsp/bash 已经使用分离的进程组加 taskkill。 -- **在 TUI 测试驱动器上到处使用 node-pty**:已归档的 [Windows TUI 决策](../../archived/feature/2026-07-20-windows-tui-support.zh.md)明确否决了在每个宿主上都使用 node-pty;它当时已经是 Windows 那一条腿。 +- **在 TUI 测试驱动器上到处使用 node-pty**:已归档的 [Windows TUI 决策](../../archived/feature/2026-07-20-windows-tui-support.md)明确否决了在每个宿主上都使用 node-pty;它当时已经是 Windows 那一条腿。 **服务器与 HTTP:** diff --git a/.agents/skills/dsh-translate-docs/SKILL.md b/.agents/skills/dsh-translate-docs/SKILL.md index 429505ac9a..ae778a0c52 100644 --- a/.agents/skills/dsh-translate-docs/SKILL.md +++ b/.agents/skills/dsh-translate-docs/SKILL.md @@ -52,7 +52,7 @@ When translations need to be written from scratch, the orchestrating agent does - **Read the completed counterpart alone.** After the source comparison, read the translated file without the source beside it and rewrite phrasing whose awkwardness only becomes visible in isolation. - Write only the final text to the file, never drafts or notes. - Every term in [terminology.md](../../../docs/i18n/terminology.md) renders exactly as specified. For a Chinese target, use the Chinese and first-occurrence columns; an unlisted term needs a citable Chinese OSS/vendor precedent or stays English under 「待定术语」. For an English target, use the English column and an established English technical term; preserve an ambiguous source term with a short gloss and list it as pending. Never invent a rendering inline. -- Code blocks are byte-identical across the pair, comments included. Repository-relative document links keep the same semantic target and exact query/fragment suffix, using `.md` on the English side and an existing `.zh.md` sibling on the Chinese side; links without a paired target stay unchanged, and the switcher remains the cross-locale exception. +- Code blocks are byte-identical across the pair, comments included. Repository-relative document links keep the same semantic target and exact query/fragment suffix: targets in the active bilingual corpus use `.md` on the English side and `.zh.md` on the Chinese side, a missing in-scope counterpart is an error, targets outside the corpus keep their authored path, and the switcher remains the cross-locale exception. - The pairing gate checks heading depths, fenced blocks, table row and column counts, list kinds, ordered-list starts, list item counts, link locale, and semantic targets. In Pass 2, manually verify list and table order, noncanonical list numbering, inline code, emphasis, meaning, terminology, and tone. ## Find the work diff --git a/docs/graph-atlas.i18n.yaml b/docs/graph-atlas.i18n.yaml index 9222671551..ab27d9794c 100644 --- a/docs/graph-atlas.i18n.yaml +++ b/docs/graph-atlas.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/graph-atlas.md graph-atlas.md: 1da995e8ff6b47e0a83f0308342c5f45ccbd3835 -graph-atlas.zh.md: 5660b803eb42cd98eeac8d3f1df98d50bc1204ce +graph-atlas.zh.md: cbd32efeb9d0b9435a086ca857f2691b4e9d655d diff --git a/docs/graph-atlas.zh.md b/docs/graph-atlas.zh.md index 5660b803eb..cbd32efeb9 100644 --- a/docs/graph-atlas.zh.md +++ b/docs/graph-atlas.zh.md @@ -7,7 +7,7 @@ 这些图展示生成目录未包含的关系。可以用它们查找包之间的关系、能力 seam、事件流、面向模型的工具、应用组合和运行时生命周期路径。精确签名和类型定义仍以[子系统页面](subsystems/core.zh.md)(类型和生成的 `cordis-surface` 区域)及[工具目录](tool-catalog.zh.md)为准。 -本索引背后的流程决策记录在[文档图 Agent Note](../.agents/notes/archived/process/2026-07-03-documentation-graph-atlas.zh.md)中。 +本索引背后的流程决策记录在[文档图 Agent Note](../.agents/notes/archived/process/2026-07-03-documentation-graph-atlas.md)中。 | 图 | 模式 | | --- | --- | diff --git a/docs/i18n/README.i18n.yaml b/docs/i18n/README.i18n.yaml index d948a79788..47d6f06149 100644 --- a/docs/i18n/README.i18n.yaml +++ b/docs/i18n/README.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 docs/i18n/README.md -README.md: 3213541457458eea60910da5daf629579afeb57d -README.zh.md: 2d98f628e1070f7f5ddb8df0de70e66f232d43af +README.md: c4c3be407576164be198b8a6f06b73f4b8cf8d5b +README.zh.md: 5efe580286c8d2f6c0814d4f5ab564b4b96034c6 diff --git a/docs/i18n/README.md b/docs/i18n/README.md index 3213541457..c4c3be4075 100644 --- a/docs/i18n/README.md +++ b/docs/i18n/README.md @@ -19,7 +19,7 @@ This repo's documentation is read by people and agents both inside and outside t When two branches contain valid confirmations of the same pair, the installed `dsh-translation-pairing` Git merge driver composes a new record only if Git's default text merge succeeds for both recorded owner-blob triplets and the merged pair retains its required switchers and structural signature. The Chinese file must retain its English backlink; an authored English source must retain its Chinese link, while a listed generated English source is exempt. Any structure the driver cannot verify remains an ordinary conflict; `pnpm run resolve-translation-pairing-conflicts` applies the same fail-closed operation to a merge that has already stopped, stages every safe pairing record, and exits unsuccessfully when other pairing conflicts remain. The [automatic pairing merges Agent Note](../../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) owns the mechanism and alternatives. - **Language switcher.** The Chinese file always links back immediately after its H1 heading with `[English](foo.md) | 中文`. An authored English file reciprocates there with `English | [中文](foo.zh.md)`; a listed generated English source omits that line so it remains byte-identical to generator output. A README published outside GitHub, such as PyPI project metadata, may use the canonical `https://github.com/deepseek-ai/deepseek-harness/blob/master/` URL to the same counterpart so the switcher still resolves there. -- **Structure mirrors the counterpart.** Heading depths and order, list kinds, ordered-list starts, list item counts, table row and column counts, semantic link targets with exact query/fragment suffixes, and verbatim code blocks match one to one across the pair. A relative document link uses the target's English `.md` path on the English side and its `.zh.md` sibling on the Chinese side when that sibling exists; links without a paired target stay unchanged. See [translation-rules.md](translation-rules.md) for the full preservation rules. Existing Markdown gates apply to `.zh.md` files unchanged (`verify-md-wrap`, `verify-md-links`). +- **Structure mirrors the counterpart.** Heading depths and order, list kinds, ordered-list starts, list item counts, table row and column counts, semantic link targets with exact query/fragment suffixes, and verbatim code blocks match one to one across the pair. When a relative document link targets the active bilingual corpus, the English side uses its `.md` path and the Chinese side uses its `.zh.md` path. A missing counterpart in that corpus is a pair-completeness error rather than a fallback; targets outside the active corpus keep the authored path. See [translation-rules.md](translation-rules.md) for the full preservation rules. Existing Markdown gates apply to `.zh.md` files unchanged (`verify-md-wrap`, `verify-md-links`). ## The gate: verify-translation-pairing diff --git a/docs/i18n/README.zh.md b/docs/i18n/README.zh.md index 2d98f628e1..5efe580286 100644 --- a/docs/i18n/README.zh.md +++ b/docs/i18n/README.zh.md @@ -21,7 +21,7 @@ 当两个分支都包含同一配对的有效确认时,已安装的 `dsh-translation-pairing` Git 合并驱动只会在 Git 默认文本合并能分别干净合并记录所指向的英文三方 blob 与中文三方 blob,且合并后的配对仍保留必需的语言切换行和结构签名时,组合出一份新记录。中文文件必须保留指向英文的反向链接;普通撰写的英文源必须保留指向中文的链接,而清单内的生成英文源不作此要求。任何合并驱动无法验证的结构都保留为普通冲突;`pnpm run resolve-translation-pairing-conflicts` 会对已经停止的合并执行同一套遇错即保留冲突的操作,暂存每份可安全生成的配对记录,并在还有其他配对冲突时以非零状态退出。[自动配对合并 Agent Note](../../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.zh.md) 负责记录该机制与备选方案。 - **语言切换行。** 中文文件一律在 H1 标题后立即以 `[English](foo.md) | 中文` 链回英文。普通撰写的英文文件在同一位置以 `English | [中文](foo.zh.md)` 互链;清单内的生成英文源省略此行,以便与生成器输出逐字节一致。发布到 GitHub 以外位置的 README(例如 PyPI 项目元数据)可以改用指向同一对侧文件的规范 `https://github.com/deepseek-ai/deepseek-harness/blob/master/` URL,使切换行在该位置仍可访问。 -- **结构与另一侧一一对应。** 标题深度与顺序、列表类型、有序列表起始编号、列表项数量、表格行列数、保留原样 query/fragment 后缀的语义链接目标,以及逐字节一致的代码块在配对两侧一一对应。相对文档链接在英文侧使用目标的英文 `.md` 路径;目标存在 `.zh.md` 兄弟文件时,中文侧使用该中文路径;目标没有对侧时保持原链接。完整保持规则见 [translation-rules.md](translation-rules.zh.md)。既有 Markdown 门禁对 `.zh.md` 文件原样生效(`verify-md-wrap`、`verify-md-links`)。 +- **结构与另一侧一一对应。** 标题深度与顺序、列表类型、有序列表起始编号、列表项数量、表格行列数、保留原样 query/fragment 后缀的语义链接目标,以及逐字节一致的代码块在配对两侧一一对应。相对文档链接的目标属于活跃双语语料时,英文侧使用其 `.md` 路径,中文侧使用其 `.zh.md` 路径。该范围内缺少对侧属于配对完整性错误,不得回退;范围外的目标保留原路径。完整保持规则见 [translation-rules.md](translation-rules.zh.md)。既有 Markdown 门禁对 `.zh.md` 文件原样生效(`verify-md-wrap`、`verify-md-links`)。 ## 门禁:verify-translation-pairing diff --git a/docs/i18n/translation-prompt.md b/docs/i18n/translation-prompt.md index 61cffbfc5c..903a3bfc59 100644 --- a/docs/i18n/translation-prompt.md +++ b/docs/i18n/translation-prompt.md @@ -56,7 +56,7 @@ A lower-priority rule may refine but never override a higher-priority requiremen - Keep each prose paragraph on one physical line. Use paragraph breaks, not hard-wrapped lines inside a paragraph. - Fenced code blocks must be byte-identical to the source, including info strings, whitespace, and ALL comments inside them. Do NOT translate or reformat any content inside code blocks. This is a hard rule with no exceptions. - Inline code spans must be kept verbatim. This includes commands, flags, paths, identifiers, API and event names, config keys, protocol values, version numbers, and other machine-readable tokens. Never translate or reformat them. -- Every repository-relative document link must keep the source link's semantic target and exact query/fragment suffix. When the target has a target-language sibling, English output uses its `.md` path and Chinese output uses its `.zh.md` path; without a sibling, keep the original target. External URLs, images, and pure in-page fragments stay unchanged. Translate link text. +- Every repository-relative document link must keep the source link's semantic target and exact query/fragment suffix. When the target belongs to the active bilingual corpus, English output uses its `.md` path and Chinese output uses its `.zh.md` path; a missing counterpart in that corpus is an error, while targets outside it keep the original path. External URLs, images, and pure in-page fragments stay unchanged. Translate link text. - Language switcher line: when an English source contains `English | [中文](source-filename.zh.md)`, write `[English](source-filename.md) | 中文`. When a Chinese source contains `[English](source-filename.md) | 中文`, write `English | [中文](source-filename.zh.md)`. Do NOT copy the source switcher unchanged. If the source has no switcher, do not invent a filename or switcher; the pipeline inserts the canonical target switcher after parsing ``. - Preserve emphasis marker types and the semantic spans they cover. Do not add, remove, move, or change bold and italic markers. diff --git a/docs/i18n/translation-rules.i18n.yaml b/docs/i18n/translation-rules.i18n.yaml index 6c2fe130c1..358bd24fa3 100644 --- a/docs/i18n/translation-rules.i18n.yaml +++ b/docs/i18n/translation-rules.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 docs/i18n/translation-rules.md -translation-rules.md: df70b03cb566ade63a8a0b1b256e5a0d8116185c -translation-rules.zh.md: aecfa9a6d237785e0e5bb2b0d6a2c7e1a026a45f +translation-rules.md: f268a09cf02e1a079065441271ffb304eb81d3ee +translation-rules.zh.md: 7daa639ce390d109649f16edf6935d2d80edfbb4 diff --git a/docs/i18n/translation-rules.md b/docs/i18n/translation-rules.md index df70b03cb5..f268a09cf0 100644 --- a/docs/i18n/translation-rules.md +++ b/docs/i18n/translation-rules.md @@ -28,7 +28,7 @@ The pairing gate checks heading depths, fenced code blocks, table row and column - tables (same columns, same row order; header cells translated per terminology), - fenced code blocks — **byte-identical, including comments**; the pairing signature compares their info strings and contents, and ` ```ts ` blocks compile under `doc-typecheck`, - inline code spans (commands, flags, config keys, file paths, event names, API names, version numbers) — verbatim, never translated or reformatted, -- links and anchors: every relative document link MUST keep the same semantic target and exact query/fragment suffix. When that target has a paired sibling, the English side uses its `.md` path and the Chinese side uses its `.zh.md` path; without a sibling, both sides keep the original target. External URLs, images, and pure in-page fragments stay unchanged. The language switcher remains the explicit cross-locale exception, and a README rendered outside GitHub MAY use the canonical public repository URL to its exact counterpart as documented in [README.md](README.md). Link TEXT is translated. +- links and anchors: every relative document link MUST keep the same semantic target and exact query/fragment suffix. When the target belongs to the active bilingual corpus, the English side uses its `.md` path and the Chinese side uses its `.zh.md` path; a missing counterpart in that corpus is an error, while targets outside it keep the original path. External URLs, images, and pure in-page fragments stay unchanged. The language switcher remains the explicit cross-locale exception, and a README rendered outside GitHub MAY use the canonical public repository URL to its exact counterpart as documented in [README.md](README.md). Link TEXT is translated. The repo's Markdown conventions apply to `.zh.md` files unchanged: one physical line per paragraph (`verify-md-wrap`), resolving relative links (`verify-md-links`), exactly one trailing newline. diff --git a/docs/i18n/translation-rules.zh.md b/docs/i18n/translation-rules.zh.md index aecfa9a6d2..7daa639ce3 100644 --- a/docs/i18n/translation-rules.zh.md +++ b/docs/i18n/translation-rules.zh.md @@ -28,7 +28,7 @@ - 表格(相同的列、相同的行序;表头单元格按术语表翻译); - 围栏代码块:**逐字节一致,包括注释**。配对签名比对信息字符串与内容,` ```ts ` 块还要通过 `doc-typecheck` 编译; - 行内代码(命令、flag、配置键、文件路径、事件名、API 名、版本号):原样保留,从不翻译或重排; -- 链接与锚点:每个相对文档链接必须保持相同的语义目标和完全相同的 query/fragment 后缀。目标存在配对兄弟文件时,英文侧使用其 `.md` 路径,中文侧使用其 `.zh.md` 路径;没有对侧时,两边都保留原目标。外部 URL、图片与纯页内 fragment 保持不变。语言切换行仍是显式跨 locale 例外;在 GitHub 以外位置渲染的 README 可以按 [README.md](README.zh.md) 的规定,使用指向确切对侧文件的规范公开仓库 URL。链接**文字**翻译。 +- 链接与锚点:每个相对文档链接必须保持相同的语义目标和完全相同的 query/fragment 后缀。目标属于活跃双语语料时,英文侧使用其 `.md` 路径,中文侧使用其 `.zh.md` 路径;该范围内缺少对侧属于错误,范围外的目标保留原路径。外部 URL、图片与纯页内 fragment 保持不变。语言切换行仍是显式跨 locale 例外;在 GitHub 以外位置渲染的 README 可以按 [README.md](README.zh.md) 的规定,使用指向确切对侧文件的规范公开仓库 URL。链接**文字**翻译。 本仓库的 Markdown 约定对 `.zh.md` 文件原样生效:一个段落一个物理行(`verify-md-wrap`)、相对链接必须可解析(`verify-md-links`)、文件末尾恰好一个换行。 diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml index 541ec5d59b..d42cae0a03 100644 --- a/docs/persistence-catalog.i18n.yaml +++ b/docs/persistence-catalog.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/persistence-catalog.md persistence-catalog.md: cf796ca322027886b1a0b78d69ac1d3a98d9459f -persistence-catalog.zh.md: 40f3480fdffd57133e94287517cb3f8859349108 +persistence-catalog.zh.md: 49dd7771e6df13576ae21d22b7eb18fb8d82faaf diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index 40f3480fdf..49dd7771e6 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -7,7 +7,7 @@ 会话持久事件日志中可能出现的所有事件类型:完整持久化的 `SessionEvent` 信封,以及可通过合并扩展的 `SessionEventMap` 中的每个成员,包括 `@deepseek-ai/dsh-session` 所属的词汇和本仓库中每个插件对 `@deepseek-ai/dsh-session/types` 的声明合并,并附有源 JSDoc、完整 payload 声明、surface 标记和声明位置。本文档是 [session.md](subsystems/session.zh.md)(surface 排序与 `deriveMessages()` 投影)、[persistence.md](subsystems/persistence.zh.md)(如何让日志持久化)和 [session.md](subsystems/session.zh.md#cordis-surface) 中生成区域(实时总线接线;日志事件**不是** cordis 事件,它通过唯一的 `session/event` emit 到达监听器)的补充。 -英文源文件根据源码生成(`scripts/gen-persistence-catalog.ts`),并由 `pnpm run verify-persistence-catalog`(`doc-sync`(文档同步门禁)的一部分)验证新鲜度;本中文文件作为经评审对侧通过双语配对维护。声明块保留源码声明和嵌套属性的 JSDoc,只移除其所在接口/模块带来的缩进,并使用 `ts persistence-catalog` 围栏(doc-typecheck 会跳过这些围栏,因为声明引用了其所属模块中的类型)。payload 中的类型名称会链接到记录该类型的页面。参见 [persistence-log-catalog Agent Note](../.agents/notes/archived/process/2026-07-04-persistence-log-catalog.zh.md)。 +英文源文件根据源码生成(`scripts/gen-persistence-catalog.ts`),并由 `pnpm run verify-persistence-catalog`(`doc-sync`(文档同步门禁)的一部分)验证新鲜度;本中文文件作为经评审对侧通过双语配对维护。声明块保留源码声明和嵌套属性的 JSDoc,只移除其所在接口/模块带来的缩进,并使用 `ts persistence-catalog` 围栏(doc-typecheck 会跳过这些围栏,因为声明引用了其所属模块中的类型)。payload 中的类型名称会链接到记录该类型的页面。参见 [persistence-log-catalog Agent Note](../.agents/notes/archived/process/2026-07-04-persistence-log-catalog.md)。 以下信封声明组合了每个事件的 `type`、单调递增的 `seq`、以 epoch 毫秒表示的 `time`、`data`、可选的未知类型跳过标记 `ignorable`,以及条件字段 `surfaceOp`/`sourceEventSeqs`。**surface** 表示 `SurfaceEventType` 成员:它会生成一条 LLM(大语言模型)消息,并声明该事件如何加入 surface 列表。**log-only** 表示其他所有事件:这类记录可持久化、可回放,但不参与派生历史。每个 payload 均可进行 JSON 序列化(在 `Session.append` 处强制执行),整个格式固定为 `SESSION_FORMAT_VERSION = 0`:这是预发布格式,不暗示任何兼容性(参见[版本立场](subsystems/persistence.zh.md))。范围仅限本仓库中的包;下游插件可以继续合并其他事件类型,而这些类型按设计不属于本目录。 diff --git a/docs/testing.i18n.yaml b/docs/testing.i18n.yaml index fa76052790..dd1abc7cba 100644 --- a/docs/testing.i18n.yaml +++ b/docs/testing.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/testing.md testing.md: bef73983e48365f6655e4d4242f4f73223d971ed -testing.zh.md: 7b49aa3698649f69caac91a4846bc1de64a75796 +testing.zh.md: 8afec35a510c5bbae7edcca0a407735a81432399 diff --git a/docs/testing.zh.md b/docs/testing.zh.md index 7b49aa3698..8afec35a51 100644 --- a/docs/testing.zh.md +++ b/docs/testing.zh.md @@ -9,7 +9,7 @@ - **单元测试**(`pnpm run test`):vitest 运行包和示例各自的 `tests/**` 目录下的测试,以及匹配 `scripts/**/*.spec.ts` 的仓库脚本测试;测试文件与其所覆盖的代码区域放在一起。每个注册表都有一个 HMR(热模块替换)安全测试(对向该注册表贡献内容的 fiber 执行 dispose(资源释放),并断言清理完成)。优先覆盖边界情况、错误路径、事件顺序、并发竞态,以及针对约定回归的永久测试(见 `packages/core/agent-loop/tests/contract-regressions.spec.ts`)。 - **覆盖率门禁**(`pnpm run test:coverage`):门禁级运行,对 `packages/*/*/src` 按文件 100% 覆盖。未覆盖的行往往是门禁正确标记出的死代码(应删除),而非需要补写的测试。行覆盖率是必要条件,但永远不是充分条件:它证明行被执行过,不证明功能按交付预期工作。`packages/shell/pwsh-local/src` 的按文件 100% 覆盖需要真实的 `pwsh`:缺少它时其执行器套件会自动跳过,`vitest.config.ts` 会豁免该文件以使无 pwsh 的主机保持绿色,而 CI runner 自带 pwsh,仍按完整标准执行门禁。 - **真实 API e2e**(`pnpm run test:e2e`):带密钥测试调用真实提供方 API,包括 DeepSeek 模型以及各提供方特有的冒烟测试;这些测试各自由自己的密钥控制(`EXA_API_KEY`、`PERPLEXITY_API_KEY` 等),缺少密钥时套件会自动跳过,使 keyless CI 保持绿色([真实 API e2e Agent Note](../.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.zh.md))。 -- **快照**(`pnpm run test:snapshot`):无密钥预期输出覆盖对外行为(传输约定与呈现),持久化日志则固定组装后的后端行为。ACP 启动真实的自动化服务器示例、回放录制会话,并对归一化 JSON-RPC 与重新持久化的日志执行 diff([ACP 快照 Agent Note](../.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.zh.md));headless 后端场景通过未导出的 JSONL 测试 driver 启动各自显式的示例组装,而 `apps/cli` 则单独负责产品 CLI(命令行界面)`dsh --profile headless` 的验收。当模型 transcript(文本记录)发生变化时使用 `pnpm run test:snapshot:record`,回放输入仍然有效时使用 `pnpm run test:snapshot:refresh`;请审查每一处 JSONL 与预期输出差异。一个 ACP 场景(`text-turn`)固定完整的系统提示词与工具 schema 内容;其他 fixture(测试前置数据)将其 token 化,因此修改只会扰动一行([pinned-header Agent Note](../.agents/notes/archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.zh.md))。 +- **快照**(`pnpm run test:snapshot`):无密钥预期输出覆盖对外行为(传输约定与呈现),持久化日志则固定组装后的后端行为。ACP 启动真实的自动化服务器示例、回放录制会话,并对归一化 JSON-RPC 与重新持久化的日志执行 diff([ACP 快照 Agent Note](../.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.zh.md));headless 后端场景通过未导出的 JSONL 测试 driver 启动各自显式的示例组装,而 `apps/cli` 则单独负责产品 CLI(命令行界面)`dsh --profile headless` 的验收。当模型 transcript(文本记录)发生变化时使用 `pnpm run test:snapshot:record`,回放输入仍然有效时使用 `pnpm run test:snapshot:refresh`;请审查每一处 JSONL 与预期输出差异。一个 ACP 场景(`text-turn`)固定完整的系统提示词与工具 schema 内容;其他 fixture(测试前置数据)将其 token 化,因此修改只会扰动一行([pinned-header Agent Note](../.agents/notes/archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.md))。 - **Web 浏览器快照**(`pnpm run test:web`;必需的 Linux PR(Pull Request)门禁):Chromium 将回放后的浏览器输出与 `apps/web/tests/snapshots/` 比较。CI 强制只读的 `DSH_SNAPSHOT=replay`,绝不写入预期输出;record/refresh 留在本地,每处 diff 都须评审([web e2e 车道](../.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md)、[CI 门禁决策](../.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md))。`test:web` 会[先构建](../.agents/notes/implemented/bug-fix/2026-07-28-themed-scrollbars-and-reserved-gutter.zh.md)以交付插件 CSS。 签入仓库的会话格式 JSONL 使用规范打包行布局,无密钥快照门禁会通过 `session` header 发现每一份此类 fixture;[临时迁移器](../scripts/migrate-packed-session-fixtures.ts)会改写旧版 fixture 布局。 diff --git a/packages/client/ui-conversation/README.i18n.yaml b/packages/client/ui-conversation/README.i18n.yaml index 22b70945f1..96e11177c4 100644 --- a/packages/client/ui-conversation/README.i18n.yaml +++ b/packages/client/ui-conversation/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/ui-conversation/README.md README.md: d1a265b5789d9f1d9b5e630e0548ae5f619eebbf -README.zh.md: 81a34fdfaa66d13505cc53e1bd0a80bfa74761bf +README.zh.md: b1c0a502ce8e5985851afff16717e4b0c4c36986 diff --git a/packages/client/ui-conversation/README.zh.md b/packages/client/ui-conversation/README.zh.md index 81a34fdfaa..b1c0a502ce 100644 --- a/packages/client/ui-conversation/README.zh.md +++ b/packages/client/ui-conversation/README.zh.md @@ -16,7 +16,7 @@ Chat 业务行是彼此独立的注册表贡献,不是封闭的内建联合。 会话页头会在标题旁渲染会话作用域的 `'conversation.session.header.actions'` 列表,并在最右侧渲染独立的 `'conversation.session.header.utilities'` 列表。会话上下文和谱系控件保留在 `actions` 中;可选的会话工具不会改变它们的顺序或位置。编辑器链的 currency 包含当前对话 `session`;ui-subagent 会选取 one-shot 或 parent 不可用的已寻址会话,并按原因显示只读文案,而普通 InputBar 会让所有已寻址 child 仅保留 Send,因为继续执行服务不公开逐 Activation 取消操作,`session.cancel` 也会绕过其所有权。 -已记录的非用户消息渲染为默认折叠的展开项,标题栏先给出运行时为该消息投影出的角色——注入为 `上下文注入`,召回为 `跨会话召回`——其后是该投影从持久来源读出的生产者名称,因此读者无需展开即可区分 skill(技能)目录、工作区指令文件与被召回的会话。来源未提供生产者名称时只显示角色。共享的 `DisclosureRow` 原子组件让该上下文界面与消息流中的其他紧凑行保持相同几何,同时保留上下文语义:展开内容区的高度会随内容自适应,最大为 141px,超出后滚动,且不会合成工具状态或摘要([历史展开项决策](../../../.agents/notes/archived/feature/2026-07-30-web-context-injection-disclosure.zh.md)、[生产者标签决策](../../../.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.zh.md))。该内容区按生产方在持久来源上声明的形态渲染:`instructions` 在正文之上列出它对账过的文件,`catalog` 列出来源记录的条目而非面向模型的正文,其余取值——未声明、本版本不认识、或字段不可用——一律渲染 opaque 内容区,即按真实换行展示面向模型的文本,并把剩余来源字段列出。opaque 不是兜底剩余物而是有文档的默认:恢复的、fork 的、外部写入的日志,无论其生产方是否挂载在此处,都必须渲染得出来。持久或待处理的 steering(中途引导)气泡沿用用户气泡的呈现,不加任何装饰;transcript 中唯一的 steering 信号是它出现在轮次中途的位置。 +已记录的非用户消息渲染为默认折叠的展开项,标题栏先给出运行时为该消息投影出的角色——注入为 `上下文注入`,召回为 `跨会话召回`——其后是该投影从持久来源读出的生产者名称,因此读者无需展开即可区分 skill(技能)目录、工作区指令文件与被召回的会话。来源未提供生产者名称时只显示角色。共享的 `DisclosureRow` 原子组件让该上下文界面与消息流中的其他紧凑行保持相同几何,同时保留上下文语义:展开内容区的高度会随内容自适应,最大为 141px,超出后滚动,且不会合成工具状态或摘要([历史展开项决策](../../../.agents/notes/archived/feature/2026-07-30-web-context-injection-disclosure.md)、[生产者标签决策](../../../.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.zh.md))。该内容区按生产方在持久来源上声明的形态渲染:`instructions` 在正文之上列出它对账过的文件,`catalog` 列出来源记录的条目而非面向模型的正文,其余取值——未声明、本版本不认识、或字段不可用——一律渲染 opaque 内容区,即按真实换行展示面向模型的文本,并把剩余来源字段列出。opaque 不是兜底剩余物而是有文档的默认:恢复的、fork 的、外部写入的日志,无论其生产方是否挂载在此处,都必须渲染得出来。持久或待处理的 steering(中途引导)气泡沿用用户气泡的呈现,不加任何装饰;transcript 中唯一的 steering 信号是它出现在轮次中途的位置。 Think 行默认保持折叠,并在不展开思维链的情况下暴露实时推理(reasoning)吞吐:当推理块是流式输出尾部时,摘要从结算后的首行切换到最新的非空行,其单行滚动区会随每个 delta 追到行内末端。展开该行会移除移动摘要,让完整推理进入普通页面流,因此页面阅读不会与内部跟随器争夺滚动;结算后恢复左对齐的稳定首行摘要([决策](../../../.agents/notes/implemented/feature/2026-08-02-web-thinking-tail-scroll.zh.md))。 diff --git a/packages/client/ui-primitives/README.i18n.yaml b/packages/client/ui-primitives/README.i18n.yaml index 98fe9da683..2968cbdf9a 100644 --- a/packages/client/ui-primitives/README.i18n.yaml +++ b/packages/client/ui-primitives/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/ui-primitives/README.md README.md: 9a5b33e6b2ecae0652d640d2a6927e3f1d05b1a1 -README.zh.md: 3665606ae6a32e7e2eaf7acd6b48308a84dce14d +README.zh.md: e60317496ccce7241b004cb59d8af3c840f27a27 diff --git a/packages/client/ui-primitives/README.zh.md b/packages/client/ui-primitives/README.zh.md index 3665606ae6..e60317496c 100644 --- a/packages/client/ui-primitives/README.zh.md +++ b/packages/client/ui-primitives/README.zh.md @@ -6,7 +6,7 @@ ## 悬浮卡片 -`HoverCard` 通过指针离开宽限期,使采用 portal 渲染的预览在跨过与锚点之间的间隙时仍可触及。消费方还可传入 `copyText`:此时卡片为指针与键盘激活提供按钮语义,其无障碍名称会在 `copyLabel` 前缀后包含该值,通过包内剪贴板辅助函数原样写入该值,并且只有宿主接受写入后,才会临时将内容替换为 `copiedLabel`。与卡片相交的非折叠文本选区会阻止指针点击激活;成功反馈保持卡片原有高度,并随卡片关闭或在一秒后清除。`copyLabel` 和 `copiedLabel` 采用 label prop,是因为这个 zero-cordis 原子组件无法读取应用 locale;省略 `copyText` 时,卡片维持只读且可选择文本的行为。历史依据见[已归档的悬浮卡片复制 Agent Note](../../../.agents/notes/archived/feature/2026-07-31-hover-card-click-copy.zh.md)。 +`HoverCard` 通过指针离开宽限期,使采用 portal 渲染的预览在跨过与锚点之间的间隙时仍可触及。消费方还可传入 `copyText`:此时卡片为指针与键盘激活提供按钮语义,其无障碍名称会在 `copyLabel` 前缀后包含该值,通过包内剪贴板辅助函数原样写入该值,并且只有宿主接受写入后,才会临时将内容替换为 `copiedLabel`。与卡片相交的非折叠文本选区会阻止指针点击激活;成功反馈保持卡片原有高度,并随卡片关闭或在一秒后清除。`copyLabel` 和 `copiedLabel` 采用 label prop,是因为这个 zero-cordis 原子组件无法读取应用 locale;省略 `copyText` 时,卡片维持只读且可选择文本的行为。历史依据见[已归档的悬浮卡片复制 Agent Note](../../../.agents/notes/archived/feature/2026-07-31-hover-card-click-copy.md)。 ## Toast diff --git a/packages/fs/fs/README.i18n.yaml b/packages/fs/fs/README.i18n.yaml index f6f9ca4202..5736cd6c81 100644 --- a/packages/fs/fs/README.i18n.yaml +++ b/packages/fs/fs/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/fs/fs/README.md README.md: 7bde7d4d64005a6bdd0f0ac974bf45e5450e4207 -README.zh.md: 2c35bf06ed576df6c0e58ea605eab19ada1f2fb4 +README.zh.md: c4366dc1805938a7020310f4cf627b9072a75cd5 diff --git a/packages/fs/fs/README.zh.md b/packages/fs/fs/README.zh.md index 2c35bf06ed..c4366dc180 100644 --- a/packages/fs/fs/README.zh.md +++ b/packages/fs/fs/README.zh.md @@ -61,6 +61,6 @@ ## 已知限制与延期工作 - **变更操作约定只支持文本**:文本读取和两个变更操作都以 `FS_NOT_TEXT` 拒绝二进制/非 UTF-8 内容;`readBytes` 是唯一的原始字节原语,二进制安全的变更操作仍是[工具 schema Agent Note](../../../.agents/notes/implemented/feature/2026-06-17-filesystem-tool-schemas.zh.md)有意延期的工作。 -- **只有十二个原语**:没有删除、重命名/移动、复制或监视;`listDir` 只支持一层,递归、glob、分页和搜索不在范围内,见[目录列出 Agent Note](../../../.agents/notes/archived/architecture/2026-07-03-filesystem-directory-listing-seam.zh.md)。 +- **只有十二个原语**:没有删除、重命名/移动、复制或监视;`listDir` 只支持一层,递归、glob、分页和搜索不在范围内,见[目录列出 Agent Note](../../../.agents/notes/archived/architecture/2026-07-03-filesystem-directory-listing-seam.md)。 - **没有 I/O deadline**:该 seam 不启动超时;取消只是每个原语上尽力而为的可选 `AbortSignal`(见有意采用的 [fs 能力族立场](../README.zh.md))。 - **先解析后操作使远程后端每次工具调用需要两次往返**:折叠或缓存解析由这种后端自行决定。 diff --git a/packages/guard/repeat-tool-reminder/README.i18n.yaml b/packages/guard/repeat-tool-reminder/README.i18n.yaml index 729870daba..1f7952d22f 100644 --- a/packages/guard/repeat-tool-reminder/README.i18n.yaml +++ b/packages/guard/repeat-tool-reminder/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/guard/repeat-tool-reminder/README.md README.md: cfece8150dce8e2fe45b4e942bf7188f46836551 -README.zh.md: 620f69e29fa8419fdf8cff68a62224b698f139a9 +README.zh.md: 439480d876ad07d8bc095b2534a5a2ce732bfc9b diff --git a/packages/guard/repeat-tool-reminder/README.zh.md b/packages/guard/repeat-tool-reminder/README.zh.md index 620f69e29f..439480d876 100644 --- a/packages/guard/repeat-tool-reminder/README.zh.md +++ b/packages/guard/repeat-tool-reminder/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -这是一个仅提供建议的循环中断器,而非面向模型的工具:它不会出现在工具列表中,不会否决或改写调用,只增加一种行为。它监视每个 agent(智能体)的工具调用流,统计以完全相同的规范化参数连续调用同一工具的次数;达到所配置的连续次数时,它会注入逐级增强的提示,要求模型停止重复、重新阅读上一次结果,并改用其他方案或结束任务。究竟是换一种方式重试、收集更多证据还是完成任务,仍完全由模型决定:合理的重复调用既不会延迟,也不会受阻。决策记录见 [repeat-tool-reminder Agent Note](../../../.agents/notes/archived/feature/2026-07-08-repeat-tool-guard.zh.md)。 +这是一个仅提供建议的循环中断器,而非面向模型的工具:它不会出现在工具列表中,不会否决或改写调用,只增加一种行为。它监视每个 agent(智能体)的工具调用流,统计以完全相同的规范化参数连续调用同一工具的次数;达到所配置的连续次数时,它会注入逐级增强的提示,要求模型停止重复、重新阅读上一次结果,并改用其他方案或结束任务。究竟是换一种方式重试、收集更多证据还是完成任务,仍完全由模型决定:合理的重复调用既不会延迟,也不会受阻。决策记录见 [repeat-tool-reminder Agent Note](../../../.agents/notes/archived/feature/2026-07-08-repeat-tool-guard.md)。 ## 配置 diff --git a/packages/llm/llm/README.i18n.yaml b/packages/llm/llm/README.i18n.yaml index 2f82773f81..2621de89af 100644 --- a/packages/llm/llm/README.i18n.yaml +++ b/packages/llm/llm/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/llm/llm/README.md README.md: e6b3c4924ad4e7cf115abdbfb38d22d0f524e377 -README.zh.md: a9a2f07cb536ba7f0b99481fe027247e8d6b147b +README.zh.md: 91f1c6ede2b800671b35a90d02a23a6d79e96e0e diff --git a/packages/llm/llm/README.zh.md b/packages/llm/llm/README.zh.md index a9a2f07cb5..91f1c6ede2 100644 --- a/packages/llm/llm/README.zh.md +++ b/packages/llm/llm/README.zh.md @@ -102,8 +102,8 @@ ## 已知限制与暂缓事项 - **本服务不执行重试、缓存或速率限制**:提供方注册会存储重试策略,但 `llm/stream` 仍是单次尝试调用包装层。agent loop 会将已验证模型请求失败单独提供给 `agent/request-error`,其默认行为是保留原始失败;`@deepseek-ai/dsh-llm-retry` 是共享示例主干加载的可选执行器。 -- **`GenerateOptions` 采样只包含 `temperature`/`maxTokens`/`stop`**:没有 `tool_choice`、`top_p` 或 penalty 字段;有产生方落地时词汇才会增长(见 [已删除惰性旋钮](../../../.agents/notes/archived/simplification/2026-07-04-drop-inert-request-knobs.zh.md))。 -- **只有出现实际产生方后,相应变体才会加入**:`prefill`、逐工具 `strict`、内容块 `cache` 提示和 `agent` 消息来源变体,都因当前没有产生方而被移除(见 [Agent Note](../../../.agents/notes/archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.zh.md))。 +- **`GenerateOptions` 采样只包含 `temperature`/`maxTokens`/`stop`**:没有 `tool_choice`、`top_p` 或 penalty 字段;有产生方落地时词汇才会增长(见 [已删除惰性旋钮](../../../.agents/notes/archived/simplification/2026-07-04-drop-inert-request-knobs.md))。 +- **只有出现实际产生方后,相应变体才会加入**:`prefill`、逐工具 `strict`、内容块 `cache` 提示和 `agent` 消息来源变体,都因当前没有产生方而被移除(见 [Agent Note](../../../.agents/notes/archived/simplification/2026-07-04-prune-producerless-vocabulary-variants.md))。 - **`BlockAssembler` 只处理核心块类型**:如果插件添加块类型的流从未由 `block-end` 关闭,`blocks()` 会抛出异常。 - **`APP_IDENTITY.url` 指向一个尚不存在的仓库**:该公开主页必须在发布前可访问。 - **`GenerateOptions.sessionId` 是本地声明的品牌类型**:导入 dsh-session 的 `SessionId` 会产生循环;未来拥有 id 的包可以消除该权宜之计。 diff --git a/packages/test-support/acp-snapshot/README.i18n.yaml b/packages/test-support/acp-snapshot/README.i18n.yaml index 840cafcfb7..6a414b9c77 100644 --- a/packages/test-support/acp-snapshot/README.i18n.yaml +++ b/packages/test-support/acp-snapshot/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/test-support/acp-snapshot/README.md README.md: 31f4ec0caeb995a10202d4a452ee7e433749762f -README.zh.md: b193366e066a52376b63cf69f4e32f23292207f2 +README.zh.md: 4e9a8236827002a55faf19e4af646ef72b0c725e diff --git a/packages/test-support/acp-snapshot/README.zh.md b/packages/test-support/acp-snapshot/README.zh.md index b193366e06..4e9a823682 100644 --- a/packages/test-support/acp-snapshot/README.zh.md +++ b/packages/test-support/acp-snapshot/README.zh.md @@ -8,7 +8,7 @@ ACP(Agent Client Protocol)快照套件工具包:无密钥快照层(`pnpm - **`launchAcpTestAgent`(启动器)**:从指定 cwd 在 tsx 下启动源 agent(智能体),或在普通 Node 下启动已构建 `lib` agent;通过原始字节 stdout tee 连接 SDK 客户端,收集会话更新和 stderr,在启动阶段报告异步 spawn 失败,默认拒绝未处理的权限请求,并负责优雅或带信号关闭。关闭会等待进程退出、继承 stdio 关闭和 ACP parser 耗尽,然后才完成关闭或传播子级错误,使捕获内容完整,且调用方可在任一结果后移除自有路径。当 Windows 接受强制终止但异步发布退出标记时,关闭会给该标记有界宽限,然后才将回退拒绝视为第二次失败。快照和普通 e2e 套件共享该进程边界;测试只需提供 agent 路径、cwd、环境覆盖和任何权限策略。 - **`runScenario`(harness)**:通过启动器从确定性 `input.json` 脚本驱动 ACP JSON-RPC stdio,将原始 stdout tee 给预期输出和纯度检查,并在优雅 stdin EOF 后收集每个持久化原始 JSONL 会话日志(父会话和 subagent 子会话,主会话优先)。`AgentUnderTest` 提供绝对 `binScript`、可选 `libBinScript`、`configPath` 和 `tsconfigPath` 路径,因为子进程 cwd 位于仓库外。当生成子级 cwd 的授权本身是测试对象时,`workspaceParent` 可以将它从平台临时目录移出。启动失败会在拒绝诊断中保留已捕获 agent stderr。 -- **规范化器**:将已捕获内容转换为稳定文本或可移植 fixture 的纯函数:`normalizeStdout`(JSON-RPC id → 首次出现序列;UUID 以及生成 cwd 的每种原生/JavaScript 文件系统写法 → token,按最长优先;根据 cwd 的分隔符选择规范 `/` 或宿主原生形式;同时作为 stdout 纯度检查)、`normalizeSessionLog`(时间归零、保留 `seq`、使用同一 cwd 路径策略)、`tokenizeSessionFixtureCwd`(生成的 workspace 及其文件系统别名,包括已进行 token 化的 macOS `/private` 别名 → 单一规范 `{{cwd}}`;手工编写的临时路径保持不变)、`scrubSystemPrompts`(提示词文本 → `{{system}}`)、`scrubToolSchemas`(schema bulk → `{{tools}}`)、`scrubRequestHeaders`(每个 pin 之外的所有 header bulk → `{{system}}`/`{{tools}}`/`{{messagePrefix}}`,保留结构;见[header 固定 Agent Note](../../../.agents/notes/archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.zh.md))和 `stabilizeFixtureMessageIds`(针对任意录制器已准备写入 fixture 的父级/子级日志,通过结构化方式仅改写 surface 和持久 inbox 中完整消息的 ID 字段,将已提交 UUID 带入未变化且双向唯一匹配的消息)。 +- **规范化器**:将已捕获内容转换为稳定文本或可移植 fixture 的纯函数:`normalizeStdout`(JSON-RPC id → 首次出现序列;UUID 以及生成 cwd 的每种原生/JavaScript 文件系统写法 → token,按最长优先;根据 cwd 的分隔符选择规范 `/` 或宿主原生形式;同时作为 stdout 纯度检查)、`normalizeSessionLog`(时间归零、保留 `seq`、使用同一 cwd 路径策略)、`tokenizeSessionFixtureCwd`(生成的 workspace 及其文件系统别名,包括已进行 token 化的 macOS `/private` 别名 → 单一规范 `{{cwd}}`;手工编写的临时路径保持不变)、`scrubSystemPrompts`(提示词文本 → `{{system}}`)、`scrubToolSchemas`(schema bulk → `{{tools}}`)、`scrubRequestHeaders`(每个 pin 之外的所有 header bulk → `{{system}}`/`{{tools}}`/`{{messagePrefix}}`,保留结构;见[header 固定 Agent Note](../../../.agents/notes/archived/testing/2026-07-06-pin-request-header-content-in-one-scenario.md))和 `stabilizeFixtureMessageIds`(针对任意录制器已准备写入 fixture 的父级/子级日志,通过结构化方式仅改写 surface 和持久 inbox 中完整消息的 ID 字段,将已提交 UUID 带入未变化且双向唯一匹配的消息)。 - **`defineAcpSnapshotSuite`(工厂)**:为场景表注册完整 describe/it 树:每场景预期输出与重新持久化日志比较、录制/刷新 fixture 回写、拒绝结构化 `UNKNOWN_TOOL` 结果、每个 header 类别一个 token 化 pin(由可独立共享的 `system-prompt.expected.md` 和 `tool-schemas.expected.json` 伴随文件组合而成),以及实时一致性保护。其 fixture 保护会拒绝遗留场景目录、缺失文件、一个类别包含多个 pin、重复的伴随文件内容、带非规范 macOS 前缀的 cwd token、未擦除的 JSONL header,以及格式错误的 pin header。在录制或刷新模式写入 fixture 前,仅当一条未变化完整消息的 ID 及其去除身份后的指纹在场景可写入 fixture 的父级/子级日志中均唯一时,该消息才会保留已提交的 UUID;会话包的权威 surface 类型谓词负责选择 surface 载体,与其关联的 `agent/inbox/spliced` 副本也纳入同一映射,且仅改写这些载体中通过验证的 `id` 字段。新增、发生变化、格式错误以及图关系存在歧义的消息保留本次生成的 UUID。刷新会使用收集所得本次运行的 id、cwd 及全部 cwd 别名评估本次生成的叶值;只有完整逻辑记录布局对齐且易变字符串替换形成双射时,才会复用归一化后等价的叶值;surface 或 inbox 载体中的完整消息 ID 不参与此路径,因为后续结构化处理负责这些 ID;有歧义的日志保留本次生成的字符串,而本次生成的语义值仍为权威数据。它还会在对齐事件时间前展开打包时序 envelope,因此切换打包/非打包布局无法移动后续记录。新插入的 `session/title` 使用前一个事件的时间,因此功能驱动的插入不会扰动 fixture 余下部分。每个场景目录的 `session.jsonl` 和连续 `session..jsonl` 同级文件构成有序的主会话/子会话清单;场景表不重复其数量。必须在 vitest 收集时调用。 签入仓库的会话 fixture 使用规范打包行;[临时仓库迁移器](../../../scripts/migrate-packed-session-fixtures.ts)(`pnpm run migrate:packed-session-fixtures`)会改写较旧的 fixture 布局,由其[移除提案](../../../.agents/notes/proposed/process/2026-07-26-remove-packed-session-fixture-migrator.zh.md)负责删除该迁移器。 @@ -59,7 +59,7 @@ defineAcpSnapshotSuite({ 每个场景都比较 `stdout.expected.jsonl`,其中以 cwd 为根的分隔符规范化为 `/`。在 Windows 上,`pinsNativeWindowsStdout` 还会在共享预期输出之后比较完整 `stdout.expected.windows.jsonl`,并且仅在启用时要求存在该伴随文件。需要非 Windows 主机的场景声明 `posixOnly`,在 Windows 上跳过运行测试,但 fixture 保护仍在所有平台覆盖其已提交文件;示例包括 POSIX 进程语义(例如取消正在运行的 bash 调用会终止一个已脱离的进程组)和 Windows 无法表示的生成路径。组合需要可用 `pwsh` 的场景声明 `pwshOnly`;调用方提供的 `hasPwsh` 探测(随附的 acp-agent 套件遵循执行器自身的解析,因此 Program Files 安装也计入)在解析不到可用 `pwsh` 时跳过运行测试,而 fixture 保护仍处处覆盖其已提交文件。 -示例还发布 `cordis.snapshot.yml` 回放 overlay,位于 `cordis.yml` 旁边(bin 在 `DSH_SNAPSHOT=replay` 下交换它们,见[单源回放配置 Agent Note](../../../.agents/notes/archived/testing/2026-07-04-single-source-acp-replay-config.zh.md));回放 fixture 由 [`dsh-llm-replay`](../llm-replay/README.zh.md) 提供,本包通过为子进程设置的 `DSH_SNAPSHOT_*` env var 指向它。`pnpm run test:snapshot:record` 调用在线 LLM(大语言模型),并重写已记录场景的模型 fixture;`pnpm run test:snapshot:refresh` 保持无密钥,运行回放 overlay,并从已提交模型脚本重写 stdout、可比较会话日志预期输出,以及各 pin 自有的提示词与工具 schema 伴随文件。Fixture 角色、录制/回放/刷新语义和场景表字段记录在 `Scenario` 以及[快照 Agent Note](../../../.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.zh.md) 中。 +示例还发布 `cordis.snapshot.yml` 回放 overlay,位于 `cordis.yml` 旁边(bin 在 `DSH_SNAPSHOT=replay` 下交换它们,见[单源回放配置 Agent Note](../../../.agents/notes/archived/testing/2026-07-04-single-source-acp-replay-config.md));回放 fixture 由 [`dsh-llm-replay`](../llm-replay/README.zh.md) 提供,本包通过为子进程设置的 `DSH_SNAPSHOT_*` env var 指向它。`pnpm run test:snapshot:record` 调用在线 LLM(大语言模型),并重写已记录场景的模型 fixture;`pnpm run test:snapshot:refresh` 保持无密钥,运行回放 overlay,并从已提交模型脚本重写 stdout、可比较会话日志预期输出,以及各 pin 自有的提示词与工具 schema 伴随文件。Fixture 角色、录制/回放/刷新语义和场景表字段记录在 `Scenario` 以及[快照 Agent Note](../../../.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.zh.md) 中。 约束:`suite.ts` 与 `harness.ts` 导入 vitest(harness 通过 `vi.waitFor` 轮询其持久边界等待),因此包入口只能在 vitest 运行中导入(启动器和规范化器没有此依赖,但从同一入口发布)。启动器和套件工厂按设计专用于 ACP,启动器使用 SDK 的 `ClientSideConnection`;规范化器是与传输无关的会话日志/文本辅助工具,还由 JSON-RPC 和 Web 快照录制器消费。输入脚本覆盖初始化、新建会话、文本提示简写、精确结构化 ACP 提示词块、取消、预期 RPC 失败和持久轮次边界等待。权限往返是选项类别选择(`allow_once`、`reject_once` 等)的 FIFO 队列,映射到 agent 发出的 `optionId`;缺少或耗尽的队列回答 `cancelled`,未提供类别会拒绝运行。 diff --git a/packages/web/tool-web/README.i18n.yaml b/packages/web/tool-web/README.i18n.yaml index 244581a1b4..f0c67b542a 100644 --- a/packages/web/tool-web/README.i18n.yaml +++ b/packages/web/tool-web/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/web/tool-web/README.md README.md: 81dc4d3f3dbf9788e68b86513bfa09271fb99c84 -README.zh.md: 85efad1db97e50404a624b74c1425a919fa9cabc +README.zh.md: 5f65a4596118c9d8fb855aa422b2f0700a36d601 diff --git a/packages/web/tool-web/README.zh.md b/packages/web/tool-web/README.zh.md index 85efad1db9..5f65a45961 100644 --- a/packages/web/tool-web/README.zh.md +++ b/packages/web/tool-web/README.zh.md @@ -133,6 +133,6 @@ Use the web_fetch tool to retrieve the content of a specific HTTP(S) URL (for ex ## 已知限制与暂缓事项 -- **HTML→markdown 转换会在 GFM 无法安全表示的输入上降级**:[turndown](https://github.com/mixmark-io/turndown)(带 GFM 表格/删除线)通过真实 DOM 转换至多 `fetchMaxOutputChars` 个源字符。保守的 512 层词法守卫会将深层或嵌套有歧义的主体作为原始 HTML 直接透传,转换异常也会如此处理;表格的 `colspan` 会被忽略,因为 GFM 无法表示跨列单元格。这些限制可避免阻塞事件循环,也避免不受信任的数值属性使输出膨胀([已归档的依赖决策](../../../.agents/notes/archived/simplification/2026-07-26-turndown-for-tool-web-html-markdown.zh.md))。 +- **HTML→markdown 转换会在 GFM 无法安全表示的输入上降级**:[turndown](https://github.com/mixmark-io/turndown)(带 GFM 表格/删除线)通过真实 DOM 转换至多 `fetchMaxOutputChars` 个源字符。保守的 512 层词法守卫会将深层或嵌套有歧义的主体作为原始 HTML 直接透传,转换异常也会如此处理;表格的 `colspan` 会被忽略,因为 GFM 无法表示跨列单元格。这些限制可避免阻塞事件循环,也避免不受信任的数值属性使输出膨胀([已归档的依赖决策](../../../.agents/notes/archived/simplification/2026-07-26-turndown-for-tool-web-html-markdown.md))。 - **面向模型的接口有意保持精简,后续扩展暂缓**:`max_results` 保持为配置上限(不是模型参数),`web_fetch` 只接受 `url`(没有 `format`/`prompt`/LLM(大语言模型)摘要模式);两项都列为 [seam Agent Note](../../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.zh.md) 中的后续步骤。 - **没有 web 专用权限策略**:两个工具都不会请求 `ctx.approval` 就直接执行;需要确认的部署必须添加 `tools/pre-execute` 策略,该包不定义持久化的 URL/域名授权。 diff --git a/packages/web/web/README.i18n.yaml b/packages/web/web/README.i18n.yaml index dc7f60d4af..0b8f351822 100644 --- a/packages/web/web/README.i18n.yaml +++ b/packages/web/web/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/web/web/README.md README.md: 8dfc7f032e25e40207bb3880777b814e67175a20 -README.zh.md: 1520f4d6d0a8c686bc1e8bd53963c26bd4a12006 +README.zh.md: 98922ad83fedb7ded07b2e180f8a6e4261032112 diff --git a/packages/web/web/README.zh.md b/packages/web/web/README.zh.md index 1520f4d6d0..98922ad83f 100644 --- a/packages/web/web/README.zh.md +++ b/packages/web/web/README.zh.md @@ -55,7 +55,7 @@ ## 已知限制与暂缓事项 -- **没有观测接口**:没有提供方变更事件或能力状态查询;可用性只能通过执行 `search()`/`fetch()` 并按抛出的 `WebError` code 路由来观测,无提供方失败是通用的 `WEB_PROVIDER_UNAVAILABLE`,不会枚举逐提供方原因(见 [Agent Note](../../../.agents/notes/archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.zh.md))。 +- **没有观测接口**:没有提供方变更事件或能力状态查询;可用性只能通过执行 `search()`/`fetch()` 并按抛出的 `WebError` code 路由来观测,无提供方失败是通用的 `WEB_PROVIDER_UNAVAILABLE`,不会枚举逐提供方原因(见 [Agent Note](../../../.agents/notes/archived/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md))。 - **`WebSearchRequest` 只携带 `query` + `maxResults`**:提供方无关的控制项(新近程度、域名过滤条件、区域提示、搜索深度)暂缓至 Exa 与 Perplexity 都能诚实支持时(见 [seam Agent Note](../../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.zh.md))。 - **`WebFetchBody` 没有 `pdf` 分支**:可提取文本的 PDF 支持属于明确的暂缓工作;封闭联合会使新增该分支成为三个 web 包中由编译强制执行的变更。 - **提供方支持的页面提取不属于 `fetch()` 范围**:Firecrawl/Tavily 风格的 `web_extract` 能力暂缓,而不会扩展抓取操作。 diff --git a/scripts/gen-cordis-catalog-record.spec.ts b/scripts/gen-cordis-catalog-record.spec.ts index 470efb4c24..3b51562768 100644 --- a/scripts/gen-cordis-catalog-record.spec.ts +++ b/scripts/gen-cordis-catalog-record.spec.ts @@ -154,13 +154,22 @@ describe('localizePageRegion', () => { const root = mkdtempSync(join(tmpdir(), 'cordis-region-locale-')) roots.push(root) mkdirSync(join(root, 'docs/subsystems'), { recursive: true }) + mkdirSync(join(root, 'packages'), { recursive: true }) + mkdirSync(join(root, 'scripts'), { recursive: true }) writeFileSync(join(root, 'docs/subsystems/target.md'), '# Target\n') writeFileSync(join(root, 'docs/subsystems/target.zh.md'), '# 目标\n') - const region = `${REGION_BEGIN}\n[Target](target.md#api) [Source](../../packages/x.ts)\n${REGION_END}` + writeFileSync(join(root, 'docs/subsystems/excluded.md'), '# Excluded\n') + writeFileSync(join(root, 'docs/subsystems/excluded.zh.md'), '# 排除\n') + writeFileSync(join(root, 'packages/outside.md'), '# Outside\n') + writeFileSync(join(root, 'packages/outside.zh.md'), '# 范围外\n') + writeFileSync(join(root, 'scripts/translation-pairing.manifest.json'), JSON.stringify({ + excluded: ['docs/subsystems/excluded.md'], + })) + const region = `${REGION_BEGIN}\n[Target](target.md#api) [Excluded](excluded.md) [Outside](../../packages/outside.md)\n${REGION_END}` expect(localizePageRegion(region, 'docs/subsystems/page.md', root)).toBe(region) expect(localizePageRegion(region, 'docs/subsystems/page.zh.md', root)).toBe( - `${REGION_BEGIN}\n[Target](target.zh.md#api) [Source](../../packages/x.ts)\n${REGION_END}`, + `${REGION_BEGIN}\n[Target](target.zh.md#api) [Excluded](excluded.md) [Outside](../../packages/outside.md)\n${REGION_END}`, ) }) }) diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index 2961417b8f..fb0cc0ce6c 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -33,8 +33,10 @@ import { contextKeyMap, contextMergeFiles, eventNameList } from './cordis-walk.t import { blobHash, parsePairMeta, + parseTranslationPairingManifest, partitionGeneratedRegions, renderPairMeta, + translationPairSourcePredicate, } from './translation-pairing.ts' import { rewriteTranslationLinkLocales } from './translation-links.ts' @@ -716,9 +718,15 @@ export interface WalkPartitionMaps { /** Project paired Markdown destinations in one generated region to the page's locale. */ export function localizePageRegion(region: string, pageRel: string, scanRoot: string = root): string { - return pageRel.endsWith('.zh.md') - ? rewriteTranslationLinkLocales(region, { repoRoot: scanRoot, sourcePath: pageRel }).content - : region + if (!pageRel.endsWith('.zh.md')) return region + const manifest = parseTranslationPairingManifest( + readFileSync(resolve(scanRoot, 'scripts/translation-pairing.manifest.json'), 'utf8'), + ) + return rewriteTranslationLinkLocales(region, { + repoRoot: scanRoot, + sourcePath: pageRel, + isTranslationPairSource: translationPairSourcePredicate(manifest), + }).content } /** diff --git a/scripts/gen-translation-brief.ts b/scripts/gen-translation-brief.ts index 6e5f5183a8..fac9e9bcb4 100644 --- a/scripts/gen-translation-brief.ts +++ b/scripts/gen-translation-brief.ts @@ -16,11 +16,13 @@ import { existsSync, globSync, mkdtempSync, readFileSync, rmSync, writeFileSync import { tmpdir } from 'node:os' import { basename, join, resolve, sep } from 'node:path' import { + isTranslationPairingManifestExcluded, isTranslationScopeFile, pairAnchorOfArgument, parseTranslationMarkdown, parseTranslationPairingManifest, TRANSLATION_SCOPE_GLOB_EXCLUDES, + translationPairSourcePredicate, translationStructureDiff, translationStructureSignature, } from './translation-pairing.ts' @@ -41,10 +43,11 @@ import { const root = resolve(import.meta.dirname, '..') const manifest = parseTranslationPairingManifest(readFileSync(join(root, 'scripts/translation-pairing.manifest.json'), 'utf8')) +const isTranslationPairSource = translationPairSourcePredicate(manifest) const terminology = readFileSync(join(root, 'docs/i18n/terminology.md'), 'utf8') function isExcluded(file: string): boolean { - return manifest.excluded.some(entry => (entry.endsWith('/') ? file.startsWith(entry) : file === entry)) + return isTranslationPairingManifestExcluded(file, manifest) } /** Recorded hashes of one consistency record: basename → blob hash. */ @@ -225,12 +228,12 @@ function applyMechanical(counterpartPath: string, sourceCurrent: string, result: translationStructureSignature( parseTranslationMarkdown(sourceCurrent), counterpartBase, - { repoRoot: root, sourcePath, markdown: sourceCurrent }, + { repoRoot: root, sourcePath, isTranslationPairSource, markdown: sourceCurrent }, ), translationStructureSignature( parseTranslationMarkdown(result), sourceBase, - { repoRoot: root, sourcePath: counterpartPath, markdown: result }, + { repoRoot: root, sourcePath: counterpartPath, isTranslationPairSource, markdown: result }, ), ) if (errors.length > 0) { diff --git a/scripts/merge-translation-pairing.ts b/scripts/merge-translation-pairing.ts index dda2621a36..b14305bddb 100644 --- a/scripts/merge-translation-pairing.ts +++ b/scripts/merge-translation-pairing.ts @@ -4,6 +4,7 @@ import { execFileSync } from 'node:child_process' import { readFileSync, writeFileSync } from 'node:fs' import { mergeTranslationPairingRecords, + repositoryTranslationPairSource, resolveTranslationPairingConflicts, } from './translation-pairing-merge.ts' @@ -16,7 +17,7 @@ try { const root = execFileSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).trim() if (args[0] === '--resolve') { if (args.length !== 1) throw new Error('--resolve takes no paths; it inspects the unmerged index') - const resolved = resolveTranslationPairingConflicts(root) + const resolved = resolveTranslationPairingConflicts(root, repositoryTranslationPairSource(root)) if (resolved.length === 0) { console.log('merge-translation-pairing: no unresolved pairing records') } else { @@ -36,6 +37,7 @@ try { readFileSync(ancestorPath, 'utf8'), readFileSync(currentPath, 'utf8'), readFileSync(otherPath, 'utf8'), + repositoryTranslationPairSource(root), ) writeFileSync(currentPath, result.record) } diff --git a/scripts/snapshots/translation-prompt-v4/request-response.expected.json b/scripts/snapshots/translation-prompt-v4/request-response.expected.json index e0d1096f8a..28b4584fff 100644 --- a/scripts/snapshots/translation-prompt-v4/request-response.expected.json +++ b/scripts/snapshots/translation-prompt-v4/request-response.expected.json @@ -4,7 +4,7 @@ "messages": [ { "role": "system", - "content": "# Translation Prompt\n\nYou are a senior technical translator specializing in LLM and agent development documentation. Your task is to translate the complete source document from English to Chinese, producing natural, professional technical prose.\n\nRead each complete semantic unit, understand it, and restate it as a native technical author would write it in the target language. Do not mechanically preserve source-language syntax. Then verify the translation against the source clause by clause: preserve every proposition and add none. Fluency never justifies losing or altering meaning, and completeness never justifies unnatural word-for-word prose.\n\n## Priority\n\nApply these authorities in order:\n\n1. Preserve the source meaning and the required document structure, protected content, and formatting.\n2. Follow the injected terminology table exactly.\n3. Use the injected whole-document gold pairs to calibrate target-language voice and phrasing.\n4. Apply the general writing guidance and illustrative examples in this prompt.\n\nA lower-priority rule may refine but never override a higher-priority requirement. Gold pairs calibrate voice; they are not a translation memory. No style preference, gold-pair phrasing, or embedded example may override source meaning, required structure, protected content, or the terminology table.\n\n## Quality Requirements\n\n### Structure and Format Preservation\n- Output a complete translated document that maintains the same document frame as the source: heading hierarchy and order, list kinds and item counts, ordered-list starts, table rows and columns, link order and semantic targets, and code blocks.\n- Paragraph boundaries may change within the same structural unit when the target language needs different semantic grouping. Do not merge or move content across headings, list items, table cells, or other independent structural units.\n- Keep each prose paragraph on one physical line. Use paragraph breaks, not hard-wrapped lines inside a paragraph.\n- Fenced code blocks must be byte-identical to the source, including info strings, whitespace, and ALL comments inside them. Do NOT translate or reformat any content inside code blocks. This is a hard rule with no exceptions.\n- Inline code spans must be kept verbatim. This includes commands, flags, paths, identifiers, API and event names, config keys, protocol values, version numbers, and other machine-readable tokens. Never translate or reformat them.\n- Every repository-relative document link must keep the source link's semantic target and exact query/fragment suffix. When the target has a target-language sibling, English output uses its `.md` path and Chinese output uses its `.zh.md` path; without a sibling, keep the original target. External URLs, images, and pure in-page fragments stay unchanged. Translate link text.\n- Language switcher line: when an English source contains `English | [中文](source-filename.zh.md)`, write `[English](source-filename.md) | 中文`. When a Chinese source contains `[English](source-filename.md) | 中文`, write `English | [中文](source-filename.zh.md)`. Do NOT copy the source switcher unchanged. If the source has no switcher, do not invent a filename or switcher; the pipeline inserts the canonical target switcher after parsing ``.\n- Preserve emphasis marker types and the semantic spans they cover. Do not add, remove, move, or change bold and italic markers.\n\n### Faithfulness\n- Preserve every proposition in the source and add none. Every sentence, list item, note, FIXME, warning, example, caveat, prerequisite, and guarantee must have an equivalent in the translation. Count list items on both sides.\n- Preserve actors, objects, conditions, exceptions, negation, modality, causal relationships, and distinctions between concepts.\n- Preserve the exact strength and orientation of contracts. Completion and lifecycle conditions, failure behavior, directions and data flow, normal and exceptional result channels, ownership changes, and quantitative bounds must not be weakened, strengthened, reversed, or merged.\n- Translate ideas rather than source-language idioms, but never use fluency as a reason to omit or alter meaning.\n\n### Tone and Style\n- The translation must read as if originally written in the target language by a native technical author. If an expression sounds like a word-for-word rendering from the source language, rephrase it.\n- Write in a professional, formal tone appropriate for developer documentation. Never use colloquial or casual expressions.\n- Name an actor when the target language would otherwise obscure an actor that the source states or unambiguously implies. Never invent responsibility merely to avoid a passive construction.\n- Prefer established target-language engineering terms over literal renderings. Replace metaphors with direct descriptions that preserve the source meaning.\n- Use polite imperative forms where the text instructs the reader to do something. In Chinese, address the reader as `你`, not `您`.\n- Keep the author's register: concise stays concise, detailed stays detailed.\n\n### Sentence Structure\n- Break long sentences where the target language needs a pause. Avoid run-on sentences.\n- Use active voice when it improves clarity without changing or inventing the actor. Retain passive voice when the actor is unknown, irrelevant, or intentionally omitted.\n- Restructure source-language syntax into clear target-language syntax. Preserve the logical scope of conditions, concessions, negation, coordination, and modifiers.\n- Split or combine clauses when needed for readability, provided every source relationship remains explicit.\n- Translate meaning, not words. Do not invent words or expressions that a native technical author would not use.\n\n### Word Choice\n- Prefer precise, formal vocabulary over casual or colloquial alternatives.\n- When multiple synonyms exist, choose the one most commonly used in professional technical documentation of the target language.\n- Translate ordinary prose when an established target-language expression is clear. Preserve proper nouns, canonical product names, code identifiers, APIs, paths, package names, and terms that the terminology table requires to remain in the source language.\n- Use context to resolve polysemous words. A familiar word does not have one fixed rendering in every technical domain.\n- Avoid slang, internal jargon, or overly literal translations that would not be recognized by the general developer audience.\n- Do not use the same word to translate distinct source-language concepts when their distinction matters.\n- Avoid repeating the same ordinary verb in close proximity when a natural equivalent preserves the exact meaning. Never vary a terminology-table form, defined concept, or contract verb merely for stylistic variety.\n\n#### When translating into Chinese\n- When a number modifies a noun, include a natural Chinese classifier or measure word when Chinese grammar requires one. For example: \"three-role capability seam\" → \"包含三种角色的能力 seam\", not \"三角色 seam\". Do not add classifiers to code, identifiers, versions, units, or fixed names.\n\n### Punctuation\n\n#### When translating into Chinese\n- Use full-width Chinese punctuation in Chinese prose: `,。:;?!()「」`. Keep half-width punctuation inside code spans, numbers, and complete verbatim English text.\n- Prefer colons, periods, commas, or parentheses over em dashes when they make the sentence clearer or more natural. Keep an em dash when it is the clearest natural punctuation.\n- Use enumeration commas (、) between parallel Chinese items, not regular commas.\n- Keep list-item endings consistent with their grammar. Complete sentences may end with periods or other grammatically required punctuation; do not end list items with commas.\n- Put one half-width space between Chinese text and Latin words or numerals. Do not add a space next to full-width punctuation, and do not leave a meaningless half-width space between two Chinese characters.\n- Markdown emphasis markers do not create a word boundary. Determine spacing from the rendered adjacent characters: Chinese next to Chinese takes no space, while Chinese next to a Latin word or numeral takes one half-width space.\n- Use half-width digits and Latin letters, never full-width forms.\n- For RFC 2119 keywords (MUST, MUST NOT, SHOULD, MAY), translate to the corresponding Chinese term (必须、禁止、应当、可以), preserve the SOURCE emphasis span exactly, and do not weaken its normative strength: plain source stays plain (必须), italic source stays italic (*必须*), and bold source stays bold (**必须**).\n\n#### When translating into English\n- Use half-width English punctuation and standard English spacing. Preserve full-width punctuation only in verbatim Chinese text.\n- Convert enumeration commas (、) to English commas and Chinese prose quotation marks to English double quotes.\n- Convert Chinese topic-comment sentences and omitted-subject constructions into clear English subjects when the actor is stated or unambiguously implied. Do not invent an actor.\n- Use concise professional developer prose and established English technical terms. Do not transliterate Chinese engineering idioms literally.\n- Use the terminology table's English column exactly and do not carry Chinese first-occurrence glosses into English prose.\n\n## Terminology\n\nA terminology table is provided below. Follow it strictly:\n- Render every listed term exactly as specified.\n- When the target language is Chinese, use the \"中文\" column. On the document's first prose occurrence, write the \"首次出现\" value when one is specified; on later occurrences, write only the part before the parenthetical gloss.\n- When the target language is English, use the \"English\" column without a Chinese gloss; do not copy the \"中文\" or \"首次出现\" value into English prose.\n- If a term has already been glossed as part of a compound term, do not gloss it again when it appears alone later.\n- NEVER use translations listed in the \"不要译作\" column.\n- Code spans and other protected tokens remain verbatim even when their text resembles a listed term.\n- For an unlisted technical term, use an established target-language technical term when its meaning is unambiguous in context. For a Chinese target, use an established Chinese rendering from a major Chinese-language OSS or vendor source; if you cannot reliably determine such a rendering, preserve the source term and record `[Terminology: pending]` in `` with a tentative rendering for human review. For an English target, use the established English technical term; if the source term has no unambiguous established equivalent, preserve it with the shortest English gloss needed to make it intelligible and record `[Terminology: pending]` in ``. A tentative rendering may appear in `` but must not be silently adopted in `` or ``, and you must not invent or claim a specific external precedent. This rule applies to terminology only; for general prose, freely restructure and paraphrase for natural expression.\n\n# Terminology\n\n本表约定本仓库的中英术语统一译法。\n\n**通用规则:**\n- \"中文\"列为中文译文的正文默认用词。若该列为英文,则中文译文的正文中保留英文不翻译。\n- 首次出现按\"首次出现\"列书写(带括号注释);后续出现只写括号前的部分(可能为中文,也可能为英文),不出现括号内的注释。\n- \"不要译作\"列为严格禁止的译法。\n- 如果某术语已经作为另一个术语的组成部分被括注过(如 `agent loop(智能体循环)` 中已包含 `agent` 的括注),则该术语后续单独出现时无需再次括注。\n\n## 缩写类(中英文文本中均使用缩写)\n\n| English | 中文 | 首次出现 | 不要译作 | 备注 |\n|---|---|---|---|---|\n| ACP | ACP | ACP(Agent Client Protocol) | | |\n| AI | AI | AI(人工智能) | | |\n| API | API | | | |\n| CI | CI | | | |\n| CLI | CLI | CLI(命令行界面) | | |\n| e2e | e2e | | | |\n| HMR | HMR | HMR(热模块替换) | | |\n| JSON Schema | JSON Schema | | | |\n| JSONL | JSONL | | | |\n| LLM | LLM | LLM(大语言模型) | | |\n| MCP | MCP | | | |\n| PR | PR | PR(Pull Request) | | |\n| RAG | RAG | RAG(检索增强生成) | | |\n| SDK | SDK | | | 只指受支持的 Python 与 TypeScript SDK 所使用的 JSON-RPC 客户端/服务器协议;DeepSeek Harness 项目本身不是 SDK |\n| SSE | SSE | SSE(Server-Sent Events) | | |\n\n## 英文类(中英文文本中均使用英文)\n\n| English | 中文 | 首次出现 | 不要译作 | 备注 |\n|---|---|---|---|---|\n| agent | agent | agent(智能体) | | |\n| Agent Note | Agent Note | | 智能体注记、智能体笔记 | 仓库定义的文档类型,涵盖提案、已实现决策和被否决提案;中文对侧 H1 保持固定前缀 `# Agent Note: `,标题中不加术语括注 |\n| agent harness | agent harness | agent harness(智能体框架) | | agent 组合词(agent harness/workflow/loop/skill 等)整体保留英文;未括注过 agent 时首现按对应组合词或 agent 行处理 |\n| agent loop | agent loop | agent loop(智能体循环) | | |\n| blob hash | blob hash | | | `git hash-object` 的结果 |\n| coding agent | coding agent | coding agent(编程智能体) | | agent 组合词,正文保留英文 |\n| Cordis | Cordis | | | |\n| dispose | dispose | dispose(资源释放) | | |\n| doc-sync | doc-sync | doc-sync(文档同步门禁) | | |\n| fiber | fiber | | | |\n| fixture | fixture | fixture(测试前置数据) | | |\n| fork | fork | | | |\n| Function Calling | Function Calling | Function Calling(函数调用) | | |\n| harness | harness | | | |\n| harness engineering | harness engineering | | | |\n| KV Cache | KV Cache | | | 专有技术名称,保持大小写与空格 |\n| lint | lint | | | |\n| mock | mock | | | 保留英文;指测试替身 |\n| loader | loader | | | |\n| manifest | manifest | manifest(元数据清单) | | |\n| monorepo | monorepo | | | |\n| Round | Round | | 回合、目标回合、Ralph 回合 | 外层策略使用 Round 时,领域层级为 Session > Round > Turn(轮次) > Step(步骤);Round 是可选的外层策略迭代,并非每个会话轮次都具有的通用层级。Goal Round 与 Ralph Round 均保留英文。一个 Round 承载一个轮次,步骤隶属于该轮次;明确的零步骤轮次仍保持原义。 |\n| schema | schema | | | |\n| schema DSL | schema DSL | | | |\n| seam | seam | | 接缝 | 一个可替换能力的整体,包含 Service Definition / Service Provider / Consumer 三种角色;角色需要独立演化时才拆包,也可由同一包承担多个角色。以 `packages/shell` 为范例;Service Definition 是 Cordis `Service`(抽象类或具体 registry 服务),不是 TypeScript interface。任何单一角色、普通边界或扩展点都不能称为 seam。本仓库正文保留英文;与 `extension point` 是不同概念 |\n| Service Provider | Service Provider | | Service provider | 能力 seam 的命名角色;单数固定写作 Service Provider,复数写作 Service Providers。泛指提供服务的 provider 不适用本词条 |\n| skill | skill | skill(技能) | | |\n| slot | slot | | 坑位、孔位 | 客户端架构中的具名可注册位置,保留英文 |\n| spill | spill | | | 工具输出超限落盘机制;组合词写 `spill 文件`、`spill 路径` |\n| spawn | spawn | | | |\n| steering | steering | steering(中途引导) | | |\n| job id | job id | | 任务 id | 保留英文 |\n| subagent | subagent | | | |\n| transcript | transcript | transcript(文本记录) | | 指会话渲染给用户或编辑器的完整文本,区别于事件日志 |\n| Typert | Typert | | TypeRT、typeRT、Type RT | DeepSeek Harness 类型图、生成器、loader 与运行时 registry 的产品拼写 |\n| waterfall | waterfall | waterfall(瀑布式事件) | | |\n| wheel | wheel 包 | | | Python 打包格式 |\n| worktree | worktree | | | git 工作区概念 |\n| Zstandard | Zstandard | | | RFC 8878 compression format; `zstd` remains a code value. |\n\n## 双语类(中英文文本各自使用中英文)\n\n| English | 中文 | 首次出现 | 不要译作 | 备注 |\n|---|---|---|---|---|\n| adapter | 适配器 | | | |\n| adapter contract | 适配器约定 | 适配器约定(adapter contract) | | |\n| append-only | 仅追加 | | | |\n| artifact | 产物 | | 制品 | |\n| backend | 后端 | | | |\n| binder | 绑定器 | | | 命名角色:把已声明接口绑定到调用方 context 或生命周期 |\n| config | 配置 | | | 命名角色:一个已解析配置值或边界严格的配置记录 |\n| controller | 控制器 | | | 命名角色:接受意图并改变一项既有领域或展示状态 |\n| directory | 目录 | | | 命名角色:暴露供发现或选择的条目及元数据 |\n| engine | 引擎 | | | 命名角色:实现领域算法或有状态执行模型 |\n| gateway | 网关 | | | 命名角色:适配进程、网络、RPC 或 API 边界 |\n| handle | 句柄 | | | 命名角色:引用并控制或观察一个实时资源 |\n| policy | 策略 | | | 命名角色:决定允许、选择、限制或观察什么 |\n| presenter | 展示转换器 | | | 命名角色:把领域值纯转换为渲染意图 |\n| resolver | 解析器 | | | 命名角色:根据输入计算或定位一个答案 |\n| store | 存储 | | | 命名角色:拥有一组数据并主要提供数据操作 |\n| background job | 后台任务 | | | |\n| block | 块 | | | |\n| build target | 构建目标 | | | |\n| cancel | 取消 | | | |\n| canary test | canary 测试 | | 金丝雀测试 | 本仓库保留 `canary` |\n| capability | 能力 | | | 必须与 `feature` → `功能` 区分 |\n| capability seam | 能力 seam | | 功能 seam、能力接缝 | 本仓库 Service Definition、Service Provider 与 Consumer 三种角色组成完整可替换能力的命名架构概念;普通 `seam` 仍按其词条处理 |\n| feature | 功能 | | 能力 | SDK 产品与工程模型中的可管理产品单元 |\n| feature option | 功能选项 | | variant | 一项 SDK 功能内有限、可选择的实现或配置 |\n| checkpoint | 检查点 | | | |\n| chunk | 分片 | | | |\n| compaction | 压缩 | 压缩(compaction) | | |\n| companion tool | 配套工具 | | | |\n| composition bundle | 组合包 | | | 只约束应用或插件的组合语境,不约束所有 `bundle` |\n| Cordis plugin config | Cordis 插件配置 | | | Cordis 插件公开的 `Config` 对象或配置结构 |\n| config key | 配置键 | | | Cordis 插件配置中的单个字段 |\n| consumer | 消费方 | | 消费者 | |\n| content block | 内容块 | | | |\n| Cookbook | 实操手册 | | | 文档标题用语 |\n| context | 上下文 | | | |\n| counterpart | 对侧文件 | | 对应物、配对物 | 双语配对语境;泛指\"另一侧\"时可写「另一侧」 |\n| configurable-provider directory | 可配置提供方目录 | | | llm seam 中 `registerConfigurableProviders()` 维护的目录;沿用 Service Catalog →「服务目录」先例 |\n| context compaction | 上下文压缩 | 上下文压缩(context compaction) | | |\n| contract | 约定 | | | 如:`pairing contract` →`配对约定` |\n| Cordis config entry | Cordis 配置项 | | | 指 `cordis.yml` 插件列表中的一项;插件实现本身写`Cordis 插件` |\n| Cordis plugin | Cordis 插件 | | | Cordis 加载的插件实现,不指 `cordis.yml` 中的一项配置 |\n| crash recovery | 崩溃恢复 | | | |\n| deploy root | 部署根目录 | | | |\n| dormant | 休眠 | | 睡眠、蛰伏 | 指已声明可配置但当前未注册路由的提供方 |\n| durability | 持久性 | | | |\n| feature requirement | 功能依赖 | | | 功能或功能选项通过 `requires` 声明的关系 |\n| event | 事件 | | | |\n| event log | 事件日志 | | | |\n| event stream | 事件流 | | | |\n| event-sourced | 事件溯源 | | | 沿用 DDD 社区通行译法 |\n| Executive summary | 摘要 | | | 事故复盘标题用语 |\n| executor | 执行器 | | | |\n| expected output | 预期输出 | | 金标 | 指 snapshot 比较产物;翻译语料的人工校准样例不在此列 |\n| extension | 扩展 | | | |\n| extension point | 扩展点 | | | 注意与 `seam` 区分 |\n| fail-fast | 快速失败 | | | |\n| fenced code block | 围栏代码块 | | | 沿用 MDN 中文翻译 |\n| fingerprint | 指纹 | | | 通用内容指纹;双语配对机制使用 sidecar record 记录两侧 blob hash |\n| finish reason | 结束原因 | | | |\n| fold | 折叠区 | | | 配置界面语境:默认收起的字段分区(collapsed →「收起」)|\n| foreground run | 前台运行 | | | |\n| freshness | 新鲜度 | | | 沿用 MDN 中文翻译;在本项目中指译文相对源文的同步状态 |\n| hook | 钩子 | | | |\n| implementation | 实现 | | | |\n| inference | 推理 | 推理(inference) | | 需要和 `reasoning` 区分时保留英文括注 |\n| info string | 信息字符串 | | | 沿用 CommonMark 中文翻译;指代码围栏 ``` 之后的语言标注 |\n| injection | 注入 | | | |\n| integration | 集成 | | | |\n| interface | 接口 | | | |\n| language switcher | 语言切换行 | | | i18n 配对机制用语:双语配对文件顶部的互链行 |\n| merge | 合并 | | | |\n| message | 消息 | | | |\n| mod | 模组 | | | |\n| model provider | 模型提供方 | | | |\n| model selection | 模型选择 | | 模型目标 | 面向 Agent 的提供方、模型和可选推理强度选择。 |\n| module | 模块 | | | |\n| non-escalation | 非升权 | | 非升级、不可升级 | 仅用于安全与权限语境,指主体不得获得超出既有授权的权限;普通升级不适用此行 |\n| npm dependency | NPM 依赖 | | | `package.json` 中的包关系;`dependencies`、`devDependencies` 等字段保持原样 |\n| opt-out ratio | opt-out 比例 | | 退出检查比例 | |\n| orphan | 遗留 | | 孤儿、孤立 | 指英文源已不存在的 `.zh.md`(如「遗留译文」);进程语境按 OS 惯用语译「孤儿进程」 |\n| orphan branch | 孤立分支 | | 孤儿分支 | 沿用 git 官方中文翻译 |\n| package | 包 | | | 指 npm 包(`@deepseek-ai/dsh-*`);`package.json` 等代码标识保持原样 |\n| pairing | 配对 | | | |\n| parent-subset grants | 父级子集授权 | | 父集合授权 | 指授权范围仅限于父级所持授权的子集 |\n| peer dependency | 对等依赖 | 对等依赖(peer dependency) | | |\n| permission | 权限 | | | |\n| persistence | 持久化 | | | |\n| pipeline | 流水线 | | | |\n| plugin | 插件 | | | |\n| postmortem | 事故复盘 | 事故复盘(postmortem) | 事后分析、事故记录 | 事故记录与分析文档;目录或路径中的 `postmortem` 保持代码形式 |\n| prompt | 提示词 | | | |\n| provider | 提供方 | | | |\n| provider-neutral | 提供方无关 | | 提供方中立 | |\n| quality gate | 质量门禁 | | | |\n| quiescence | 完全停稳 | | 静默、静止状态 | 指生命周期工作全部结算后的状态 |\n| reasoning | 推理 | 推理(reasoning) | | 需要和 `inference` 区分时保留英文括注 |\n| reasoning_content | 思考内容 | | | |\n| registry | 注册表 | | | |\n| replay | 回放 | | | |\n| resume | 恢复 | | | |\n| runtime | 运行时 | | | |\n| same-world subprocess | 与宿主共享文件系统和内核的子进程 | | 同世界子进程 | |\n| sandbox | 沙箱 | | | |\n| service | 服务 | | | |\n| serving interface | 对外服务接口 | | | |\n| session | 会话 | | | |\n| session event | 会话事件 | | | |\n| setup card | 设置卡片 | | | 首次运行时代替行卡直接展开的配置卡 |\n| sidecar file | 伴随文件 | | | 指与文档同目录的普通伴随文件 |\n| sidecar record | 伴随记录 | | 旁挂记录 | 指与文档同目录的伴随记录文件 |\n| smoke test | 冒烟测试 | | | |\n| snapshot | 快照 | | | |\n| source of truth | 真源 | | 事实来源、唯一来源 | |\n| spine | 主干 | | | |\n| stale | 陈旧 | | 过期 | 与 `fresh`(`新鲜`)成对;门禁输出中保留英文 `stale` 不翻译;`expired` 才译为`过期` |\n| step | 步骤 | | | |\n| stream | 流 | | | |\n| structural signature | 结构签名 | | | i18n 配对机制用语:门禁比对两侧文件时提取的有序结构序列(标题层级、代码块、列表等) |\n| Summary | 概述 | | | 事故复盘标题用语 |\n| system prompt | 系统提示词 | | | |\n| taxonomy | 分类体系 | | | |\n| token usage | token 用量 | | | |\n| tool | 工具 | | | |\n| tool call | 工具调用 | | | |\n| tool result | 工具结果 | | | |\n| tool schema | 工具 schema | | | |\n| toolkit | 工具包 | | | |\n| turn | 轮次 | | | |\n| VFS | VFS | 虚拟文件系统(VFS) | | |\n| typecheck | 类型检查 | | | |\n| vocabulary | 词汇 | | | |\n| wire format | 协议格式 | 协议格式(wire format) | | |\n| workflow | 工作流 | | | |\n| wrapper | 包装层 | | | 软件层或 SDK 包装层 |\n| wrapper script | 包装脚本 | | | 可执行脚本包装层 |\n\n\n## Output Format\n\nReturn exactly three raw XML sections in the order shown below. Do not wrap the response in a Markdown code fence and do not add analysis or text before, between, or after the sections. The fence below only displays the required format; do not reproduce the fence.\n\nThe outer section tags are framing. If Markdown inside any section body contains a line consisting only of ``, ``, ``, ``, ``, or ``, prefix that line with `\\`. If the original line already has one or more backslashes immediately before the tag, add one more. The parser removes exactly one framing escape; tags mentioned inline need no escaping.\n\n```xml\n\n(First pass: the complete translation, written as natural target-language technical prose)\n\n\n\n(Second pass: actual corrections only, one correction per line with a category tag, e.g.)\n- [Tone] \"旁挂记录\" → \"伴随记录\"(生造词)\n- [Sentence] 第 3 段补充逗号断句\n- [Punctuation] 两处破折号替换为冒号\n- [Terminology: pending] source term → tentative rendering\n- 无修正\n\n\n\n(Complete final translation after corrections)\n\n```\n\n## Self-Review Instructions\n\nAfter writing ``, verify it in two directions. First re-read it in the target language only without comparing it with the source; this makes awkward phrasing easier to notice. Then compare it against the source clause by clause for completeness and exact meaning. Resolve doubts before writing ``; do not include reasoning transcripts, checks that passed, tentative suggestions, retractions, or no-op corrections.\n\n**Structure**\n- Are the heading hierarchy and order, list kind and item count, ordered-list start, table dimensions, and code block content identical to the source?\n- Are ALL comments and info strings inside code blocks left untranslated and byte-identical to the source?\n- Are inline code spans and machine-readable tokens verbatim?\n- Is an existing language switcher correctly flipped, and is no switcher or filename invented when the source lacks one?\n- Do links preserve their semantic targets and exact query/fragment suffixes while using target-locale paths, and are emphasis spans preserved?\n- Does spacing across emphasis boundaries follow the same Chinese/Latin/numeral rule as ordinary prose?\n- Are wrapper-tag lines inside section bodies escaped with one additional backslash?\n\n**Faithfulness**\n- Clause by clause, is anything added, dropped, weakened, strengthened, reversed, merged, or re-bounded? Are list item counts identical on both sides?\n- Do actors, objects, conditions, exceptions, negation, modality, causal relationships, guarantees, contract directions, result channels, ownership changes, and quantities survive exactly?\n\n**Tone & Style**\n- Does every sentence read as if originally written by a native technical author?\n- Is there any colloquial, casual, overly informal, promotional, or metaphorical phrasing?\n- Are actors explicit where the target language needs them, without inventing responsibility?\n\n**Sentence Structure**\n- Are there run-on sentences that need breaking?\n- Are there stiff passive constructions that can safely become active, or active constructions that invent an actor?\n- Are conditions, concessions, negation, coordination, and modifiers scoped clearly?\n\n**Word Choice**\n- Are there overly literal translations that sound unnatural?\n- Are ordinary prose words left untranslated despite an established target-language expression?\n- Does each polysemous word fit its local context?\n- Is the same target-language word used for distinct source concepts, or is a defined term varied merely to avoid repetition?\n- Is any slang or internal jargon present?\n\n**Terminology**\n- For a Chinese target, are first-occurrence glosses correctly applied to the true first prose occurrence, neither missing nor repeated? For an English target, are Chinese glosses absent?\n- Are any \"不要译作\" forbidden translations present?\n- Do protected tokens remain untouched even when they resemble terminology entries?\n- For an unlisted term, does a Chinese target use an established Chinese rendering or preserve the source term as pending when no reliable rendering is known, and does an English target use the established English technical term or preserve only an ambiguous source term with the shortest necessary gloss and a pending notice?\n\n**Punctuation** (when target is Chinese)\n- Are punctuation, mixed-script spacing, quotation marks, Latin letters, and digits in their required forms?\n- Are there em dashes that make the sentence less clear and should be replaced, while natural em dashes remain intact?\n- Are list-item endings grammatically consistent, with none ending in commas?\n- Do RFC 2119 keywords preserve the source emphasis span and normative strength exactly?\n\nRecord actual corrections in ``, then output the corrected complete document in ``. If no correction or pending terminology notice is needed, write exactly `- 无修正` in `` and copy `` unchanged into ``. If `` contains only pending terminology notices, copy `` unchanged into ``.\n\n## Examples\n\nBelow are representative examples of common problems and their corrections. Follow the \"Good\" versions within the rule each example illustrates; examples do not override source context or higher-priority requirements.\n\n### Colloquial verb → Professional verb\n- Source: `The repo pins pnpm@11.7.0 in package.json`\n- Bad: `仓库在 package.json 中钉住 pnpm@11.7.0`\n- Good: `该仓库在 package.json 中固定使用 pnpm@11.7.0`\n\n### Run-on sentence → Natural phrasing with pause\n- Source: `Read docs/architecture.md before changing anything under packages/.`\n- Bad: `改动 packages/ 下的任何东西之前先读 docs/architecture.md。`\n- Good: `在修改 packages/ 目录下的任何内容之前,请先阅读 docs/architecture.md。`\n\n### Stiff passive voice → Active and natural\n- Source: `a green gate means the pair was confirmed consistent at these exact contents, not that the confirmation was sound.`\n- Bad: `门禁绿意味着这对文档曾在当前内容上被确认一致,不意味着这次确认本身是对的。`\n- Good: `门禁通过意味着这组文档在当前内容上的一致性得到了确认,不代表确认本身正确可靠。`\n\n### Invented word → Natural expression\n- Source: `A sidecar record of both blob hashes makes consistency checkable`\n- Bad: `旁挂记录两侧 blob hash,使一致性可检查`\n- Good: `伴随记录保存两侧 blob hash,使一致性可检查`\n\n### Em-dash → Colon/period\n- Source: `FIXME — an issue that should block a new release. A release should not ship with an open FIXME unless reviewers explicitly agree the change can be merged anyway.`\n- Bad: `FIXME——应当阻塞新版本发布的问题。除非评审者明确同意可以照常合入,发布不应带着未解决的 FIXME 出门。`\n- Good: `FIXME:应当阻塞新版本发布的问题。除非评审者明确同意该更改可以合并,否则发布版本不应包含未解决的 FIXME。`\n\n### Overly literal → Meaningful rendering\n- Source: `awkward phrasing is easier to notice when you read the translation without comparing it with the source`\n- Bad: `不把译文和原文比较时,尴尬的措辞更容易被注意`\n- Good: `不对照原文阅读译文时,更容易察觉别扭的表达`\n\n### Terminology — do not translate what should be kept in English\n- Source: `typed service seams, and explicit extension points`\n- Bad: `类型化的服务 seam(扩展点)与显式扩展点`\n- Good: `类型化的服务 seam 与显式扩展点`\n\n### Slang/jargon → Professional phrasing\n- Source: `The committed agent workflow lives in .agents/skills/dsh-translate-docs`\n- Bad: `进仓的 agent 工作流见 .agents/skills/dsh-translate-docs`\n- Good: `仓库内置的 agent 工作流见 .agents/skills/dsh-translate-docs`\n\n### \"For humans\" — translate the intent, not the word\n- Source: `For humans, start with the development guide`\n- Bad: `对于人工读者,请先从开发指南开始`(\"人工读者\"生硬)\n- Good: `面向开发者:请先阅读开发指南`(\"开发者\"自然,且中文里冒号在此处更自然)\n\n### Code block comments — NEVER translate\n- Source code block contains: `# full-screen TUI coding agent (needs DEEPSEEK_API_KEY)`\n- Bad: `# 全屏 TUI coding agent(需要 DEEPSEEK_API_KEY)`\n- Good: `# full-screen TUI coding agent (needs DEEPSEEK_API_KEY)` (keep exactly as-is, byte-for-byte)\n\n### Language switcher — flip direction\n- Source file (English) has: `English | [中文](README.zh.md)`\n- Bad (copying source unchanged): `English | [中文](README.zh.md)`\n- Good (flipped for Chinese file): `[English](README.md) | 中文`\n\n---\n\nNow translate the following document:" + "content": "# Translation Prompt\n\nYou are a senior technical translator specializing in LLM and agent development documentation. Your task is to translate the complete source document from English to Chinese, producing natural, professional technical prose.\n\nRead each complete semantic unit, understand it, and restate it as a native technical author would write it in the target language. Do not mechanically preserve source-language syntax. Then verify the translation against the source clause by clause: preserve every proposition and add none. Fluency never justifies losing or altering meaning, and completeness never justifies unnatural word-for-word prose.\n\n## Priority\n\nApply these authorities in order:\n\n1. Preserve the source meaning and the required document structure, protected content, and formatting.\n2. Follow the injected terminology table exactly.\n3. Use the injected whole-document gold pairs to calibrate target-language voice and phrasing.\n4. Apply the general writing guidance and illustrative examples in this prompt.\n\nA lower-priority rule may refine but never override a higher-priority requirement. Gold pairs calibrate voice; they are not a translation memory. No style preference, gold-pair phrasing, or embedded example may override source meaning, required structure, protected content, or the terminology table.\n\n## Quality Requirements\n\n### Structure and Format Preservation\n- Output a complete translated document that maintains the same document frame as the source: heading hierarchy and order, list kinds and item counts, ordered-list starts, table rows and columns, link order and semantic targets, and code blocks.\n- Paragraph boundaries may change within the same structural unit when the target language needs different semantic grouping. Do not merge or move content across headings, list items, table cells, or other independent structural units.\n- Keep each prose paragraph on one physical line. Use paragraph breaks, not hard-wrapped lines inside a paragraph.\n- Fenced code blocks must be byte-identical to the source, including info strings, whitespace, and ALL comments inside them. Do NOT translate or reformat any content inside code blocks. This is a hard rule with no exceptions.\n- Inline code spans must be kept verbatim. This includes commands, flags, paths, identifiers, API and event names, config keys, protocol values, version numbers, and other machine-readable tokens. Never translate or reformat them.\n- Every repository-relative document link must keep the source link's semantic target and exact query/fragment suffix. When the target belongs to the active bilingual corpus, English output uses its `.md` path and Chinese output uses its `.zh.md` path; a missing counterpart in that corpus is an error, while targets outside it keep the original path. External URLs, images, and pure in-page fragments stay unchanged. Translate link text.\n- Language switcher line: when an English source contains `English | [中文](source-filename.zh.md)`, write `[English](source-filename.md) | 中文`. When a Chinese source contains `[English](source-filename.md) | 中文`, write `English | [中文](source-filename.zh.md)`. Do NOT copy the source switcher unchanged. If the source has no switcher, do not invent a filename or switcher; the pipeline inserts the canonical target switcher after parsing ``.\n- Preserve emphasis marker types and the semantic spans they cover. Do not add, remove, move, or change bold and italic markers.\n\n### Faithfulness\n- Preserve every proposition in the source and add none. Every sentence, list item, note, FIXME, warning, example, caveat, prerequisite, and guarantee must have an equivalent in the translation. Count list items on both sides.\n- Preserve actors, objects, conditions, exceptions, negation, modality, causal relationships, and distinctions between concepts.\n- Preserve the exact strength and orientation of contracts. Completion and lifecycle conditions, failure behavior, directions and data flow, normal and exceptional result channels, ownership changes, and quantitative bounds must not be weakened, strengthened, reversed, or merged.\n- Translate ideas rather than source-language idioms, but never use fluency as a reason to omit or alter meaning.\n\n### Tone and Style\n- The translation must read as if originally written in the target language by a native technical author. If an expression sounds like a word-for-word rendering from the source language, rephrase it.\n- Write in a professional, formal tone appropriate for developer documentation. Never use colloquial or casual expressions.\n- Name an actor when the target language would otherwise obscure an actor that the source states or unambiguously implies. Never invent responsibility merely to avoid a passive construction.\n- Prefer established target-language engineering terms over literal renderings. Replace metaphors with direct descriptions that preserve the source meaning.\n- Use polite imperative forms where the text instructs the reader to do something. In Chinese, address the reader as `你`, not `您`.\n- Keep the author's register: concise stays concise, detailed stays detailed.\n\n### Sentence Structure\n- Break long sentences where the target language needs a pause. Avoid run-on sentences.\n- Use active voice when it improves clarity without changing or inventing the actor. Retain passive voice when the actor is unknown, irrelevant, or intentionally omitted.\n- Restructure source-language syntax into clear target-language syntax. Preserve the logical scope of conditions, concessions, negation, coordination, and modifiers.\n- Split or combine clauses when needed for readability, provided every source relationship remains explicit.\n- Translate meaning, not words. Do not invent words or expressions that a native technical author would not use.\n\n### Word Choice\n- Prefer precise, formal vocabulary over casual or colloquial alternatives.\n- When multiple synonyms exist, choose the one most commonly used in professional technical documentation of the target language.\n- Translate ordinary prose when an established target-language expression is clear. Preserve proper nouns, canonical product names, code identifiers, APIs, paths, package names, and terms that the terminology table requires to remain in the source language.\n- Use context to resolve polysemous words. A familiar word does not have one fixed rendering in every technical domain.\n- Avoid slang, internal jargon, or overly literal translations that would not be recognized by the general developer audience.\n- Do not use the same word to translate distinct source-language concepts when their distinction matters.\n- Avoid repeating the same ordinary verb in close proximity when a natural equivalent preserves the exact meaning. Never vary a terminology-table form, defined concept, or contract verb merely for stylistic variety.\n\n#### When translating into Chinese\n- When a number modifies a noun, include a natural Chinese classifier or measure word when Chinese grammar requires one. For example: \"three-role capability seam\" → \"包含三种角色的能力 seam\", not \"三角色 seam\". Do not add classifiers to code, identifiers, versions, units, or fixed names.\n\n### Punctuation\n\n#### When translating into Chinese\n- Use full-width Chinese punctuation in Chinese prose: `,。:;?!()「」`. Keep half-width punctuation inside code spans, numbers, and complete verbatim English text.\n- Prefer colons, periods, commas, or parentheses over em dashes when they make the sentence clearer or more natural. Keep an em dash when it is the clearest natural punctuation.\n- Use enumeration commas (、) between parallel Chinese items, not regular commas.\n- Keep list-item endings consistent with their grammar. Complete sentences may end with periods or other grammatically required punctuation; do not end list items with commas.\n- Put one half-width space between Chinese text and Latin words or numerals. Do not add a space next to full-width punctuation, and do not leave a meaningless half-width space between two Chinese characters.\n- Markdown emphasis markers do not create a word boundary. Determine spacing from the rendered adjacent characters: Chinese next to Chinese takes no space, while Chinese next to a Latin word or numeral takes one half-width space.\n- Use half-width digits and Latin letters, never full-width forms.\n- For RFC 2119 keywords (MUST, MUST NOT, SHOULD, MAY), translate to the corresponding Chinese term (必须、禁止、应当、可以), preserve the SOURCE emphasis span exactly, and do not weaken its normative strength: plain source stays plain (必须), italic source stays italic (*必须*), and bold source stays bold (**必须**).\n\n#### When translating into English\n- Use half-width English punctuation and standard English spacing. Preserve full-width punctuation only in verbatim Chinese text.\n- Convert enumeration commas (、) to English commas and Chinese prose quotation marks to English double quotes.\n- Convert Chinese topic-comment sentences and omitted-subject constructions into clear English subjects when the actor is stated or unambiguously implied. Do not invent an actor.\n- Use concise professional developer prose and established English technical terms. Do not transliterate Chinese engineering idioms literally.\n- Use the terminology table's English column exactly and do not carry Chinese first-occurrence glosses into English prose.\n\n## Terminology\n\nA terminology table is provided below. Follow it strictly:\n- Render every listed term exactly as specified.\n- When the target language is Chinese, use the \"中文\" column. On the document's first prose occurrence, write the \"首次出现\" value when one is specified; on later occurrences, write only the part before the parenthetical gloss.\n- When the target language is English, use the \"English\" column without a Chinese gloss; do not copy the \"中文\" or \"首次出现\" value into English prose.\n- If a term has already been glossed as part of a compound term, do not gloss it again when it appears alone later.\n- NEVER use translations listed in the \"不要译作\" column.\n- Code spans and other protected tokens remain verbatim even when their text resembles a listed term.\n- For an unlisted technical term, use an established target-language technical term when its meaning is unambiguous in context. For a Chinese target, use an established Chinese rendering from a major Chinese-language OSS or vendor source; if you cannot reliably determine such a rendering, preserve the source term and record `[Terminology: pending]` in `` with a tentative rendering for human review. For an English target, use the established English technical term; if the source term has no unambiguous established equivalent, preserve it with the shortest English gloss needed to make it intelligible and record `[Terminology: pending]` in ``. A tentative rendering may appear in `` but must not be silently adopted in `` or ``, and you must not invent or claim a specific external precedent. This rule applies to terminology only; for general prose, freely restructure and paraphrase for natural expression.\n\n# Terminology\n\n本表约定本仓库的中英术语统一译法。\n\n**通用规则:**\n- \"中文\"列为中文译文的正文默认用词。若该列为英文,则中文译文的正文中保留英文不翻译。\n- 首次出现按\"首次出现\"列书写(带括号注释);后续出现只写括号前的部分(可能为中文,也可能为英文),不出现括号内的注释。\n- \"不要译作\"列为严格禁止的译法。\n- 如果某术语已经作为另一个术语的组成部分被括注过(如 `agent loop(智能体循环)` 中已包含 `agent` 的括注),则该术语后续单独出现时无需再次括注。\n\n## 缩写类(中英文文本中均使用缩写)\n\n| English | 中文 | 首次出现 | 不要译作 | 备注 |\n|---|---|---|---|---|\n| ACP | ACP | ACP(Agent Client Protocol) | | |\n| AI | AI | AI(人工智能) | | |\n| API | API | | | |\n| CI | CI | | | |\n| CLI | CLI | CLI(命令行界面) | | |\n| e2e | e2e | | | |\n| HMR | HMR | HMR(热模块替换) | | |\n| JSON Schema | JSON Schema | | | |\n| JSONL | JSONL | | | |\n| LLM | LLM | LLM(大语言模型) | | |\n| MCP | MCP | | | |\n| PR | PR | PR(Pull Request) | | |\n| RAG | RAG | RAG(检索增强生成) | | |\n| SDK | SDK | | | 只指受支持的 Python 与 TypeScript SDK 所使用的 JSON-RPC 客户端/服务器协议;DeepSeek Harness 项目本身不是 SDK |\n| SSE | SSE | SSE(Server-Sent Events) | | |\n\n## 英文类(中英文文本中均使用英文)\n\n| English | 中文 | 首次出现 | 不要译作 | 备注 |\n|---|---|---|---|---|\n| agent | agent | agent(智能体) | | |\n| Agent Note | Agent Note | | 智能体注记、智能体笔记 | 仓库定义的文档类型,涵盖提案、已实现决策和被否决提案;中文对侧 H1 保持固定前缀 `# Agent Note: `,标题中不加术语括注 |\n| agent harness | agent harness | agent harness(智能体框架) | | agent 组合词(agent harness/workflow/loop/skill 等)整体保留英文;未括注过 agent 时首现按对应组合词或 agent 行处理 |\n| agent loop | agent loop | agent loop(智能体循环) | | |\n| blob hash | blob hash | | | `git hash-object` 的结果 |\n| coding agent | coding agent | coding agent(编程智能体) | | agent 组合词,正文保留英文 |\n| Cordis | Cordis | | | |\n| dispose | dispose | dispose(资源释放) | | |\n| doc-sync | doc-sync | doc-sync(文档同步门禁) | | |\n| fiber | fiber | | | |\n| fixture | fixture | fixture(测试前置数据) | | |\n| fork | fork | | | |\n| Function Calling | Function Calling | Function Calling(函数调用) | | |\n| harness | harness | | | |\n| harness engineering | harness engineering | | | |\n| KV Cache | KV Cache | | | 专有技术名称,保持大小写与空格 |\n| lint | lint | | | |\n| mock | mock | | | 保留英文;指测试替身 |\n| loader | loader | | | |\n| manifest | manifest | manifest(元数据清单) | | |\n| monorepo | monorepo | | | |\n| Round | Round | | 回合、目标回合、Ralph 回合 | 外层策略使用 Round 时,领域层级为 Session > Round > Turn(轮次) > Step(步骤);Round 是可选的外层策略迭代,并非每个会话轮次都具有的通用层级。Goal Round 与 Ralph Round 均保留英文。一个 Round 承载一个轮次,步骤隶属于该轮次;明确的零步骤轮次仍保持原义。 |\n| schema | schema | | | |\n| schema DSL | schema DSL | | | |\n| seam | seam | | 接缝 | 一个可替换能力的整体,包含 Service Definition / Service Provider / Consumer 三种角色;角色需要独立演化时才拆包,也可由同一包承担多个角色。以 `packages/shell` 为范例;Service Definition 是 Cordis `Service`(抽象类或具体 registry 服务),不是 TypeScript interface。任何单一角色、普通边界或扩展点都不能称为 seam。本仓库正文保留英文;与 `extension point` 是不同概念 |\n| Service Provider | Service Provider | | Service provider | 能力 seam 的命名角色;单数固定写作 Service Provider,复数写作 Service Providers。泛指提供服务的 provider 不适用本词条 |\n| skill | skill | skill(技能) | | |\n| slot | slot | | 坑位、孔位 | 客户端架构中的具名可注册位置,保留英文 |\n| spill | spill | | | 工具输出超限落盘机制;组合词写 `spill 文件`、`spill 路径` |\n| spawn | spawn | | | |\n| steering | steering | steering(中途引导) | | |\n| job id | job id | | 任务 id | 保留英文 |\n| subagent | subagent | | | |\n| transcript | transcript | transcript(文本记录) | | 指会话渲染给用户或编辑器的完整文本,区别于事件日志 |\n| Typert | Typert | | TypeRT、typeRT、Type RT | DeepSeek Harness 类型图、生成器、loader 与运行时 registry 的产品拼写 |\n| waterfall | waterfall | waterfall(瀑布式事件) | | |\n| wheel | wheel 包 | | | Python 打包格式 |\n| worktree | worktree | | | git 工作区概念 |\n| Zstandard | Zstandard | | | RFC 8878 compression format; `zstd` remains a code value. |\n\n## 双语类(中英文文本各自使用中英文)\n\n| English | 中文 | 首次出现 | 不要译作 | 备注 |\n|---|---|---|---|---|\n| adapter | 适配器 | | | |\n| adapter contract | 适配器约定 | 适配器约定(adapter contract) | | |\n| append-only | 仅追加 | | | |\n| artifact | 产物 | | 制品 | |\n| backend | 后端 | | | |\n| binder | 绑定器 | | | 命名角色:把已声明接口绑定到调用方 context 或生命周期 |\n| config | 配置 | | | 命名角色:一个已解析配置值或边界严格的配置记录 |\n| controller | 控制器 | | | 命名角色:接受意图并改变一项既有领域或展示状态 |\n| directory | 目录 | | | 命名角色:暴露供发现或选择的条目及元数据 |\n| engine | 引擎 | | | 命名角色:实现领域算法或有状态执行模型 |\n| gateway | 网关 | | | 命名角色:适配进程、网络、RPC 或 API 边界 |\n| handle | 句柄 | | | 命名角色:引用并控制或观察一个实时资源 |\n| policy | 策略 | | | 命名角色:决定允许、选择、限制或观察什么 |\n| presenter | 展示转换器 | | | 命名角色:把领域值纯转换为渲染意图 |\n| resolver | 解析器 | | | 命名角色:根据输入计算或定位一个答案 |\n| store | 存储 | | | 命名角色:拥有一组数据并主要提供数据操作 |\n| background job | 后台任务 | | | |\n| block | 块 | | | |\n| build target | 构建目标 | | | |\n| cancel | 取消 | | | |\n| canary test | canary 测试 | | 金丝雀测试 | 本仓库保留 `canary` |\n| capability | 能力 | | | 必须与 `feature` → `功能` 区分 |\n| capability seam | 能力 seam | | 功能 seam、能力接缝 | 本仓库 Service Definition、Service Provider 与 Consumer 三种角色组成完整可替换能力的命名架构概念;普通 `seam` 仍按其词条处理 |\n| feature | 功能 | | 能力 | SDK 产品与工程模型中的可管理产品单元 |\n| feature option | 功能选项 | | variant | 一项 SDK 功能内有限、可选择的实现或配置 |\n| checkpoint | 检查点 | | | |\n| chunk | 分片 | | | |\n| compaction | 压缩 | 压缩(compaction) | | |\n| companion tool | 配套工具 | | | |\n| composition bundle | 组合包 | | | 只约束应用或插件的组合语境,不约束所有 `bundle` |\n| Cordis plugin config | Cordis 插件配置 | | | Cordis 插件公开的 `Config` 对象或配置结构 |\n| config key | 配置键 | | | Cordis 插件配置中的单个字段 |\n| consumer | 消费方 | | 消费者 | |\n| content block | 内容块 | | | |\n| Cookbook | 实操手册 | | | 文档标题用语 |\n| context | 上下文 | | | |\n| counterpart | 对侧文件 | | 对应物、配对物 | 双语配对语境;泛指\"另一侧\"时可写「另一侧」 |\n| configurable-provider directory | 可配置提供方目录 | | | llm seam 中 `registerConfigurableProviders()` 维护的目录;沿用 Service Catalog →「服务目录」先例 |\n| context compaction | 上下文压缩 | 上下文压缩(context compaction) | | |\n| contract | 约定 | | | 如:`pairing contract` →`配对约定` |\n| Cordis config entry | Cordis 配置项 | | | 指 `cordis.yml` 插件列表中的一项;插件实现本身写`Cordis 插件` |\n| Cordis plugin | Cordis 插件 | | | Cordis 加载的插件实现,不指 `cordis.yml` 中的一项配置 |\n| crash recovery | 崩溃恢复 | | | |\n| deploy root | 部署根目录 | | | |\n| dormant | 休眠 | | 睡眠、蛰伏 | 指已声明可配置但当前未注册路由的提供方 |\n| durability | 持久性 | | | |\n| feature requirement | 功能依赖 | | | 功能或功能选项通过 `requires` 声明的关系 |\n| event | 事件 | | | |\n| event log | 事件日志 | | | |\n| event stream | 事件流 | | | |\n| event-sourced | 事件溯源 | | | 沿用 DDD 社区通行译法 |\n| Executive summary | 摘要 | | | 事故复盘标题用语 |\n| executor | 执行器 | | | |\n| expected output | 预期输出 | | 金标 | 指 snapshot 比较产物;翻译语料的人工校准样例不在此列 |\n| extension | 扩展 | | | |\n| extension point | 扩展点 | | | 注意与 `seam` 区分 |\n| fail-fast | 快速失败 | | | |\n| fenced code block | 围栏代码块 | | | 沿用 MDN 中文翻译 |\n| fingerprint | 指纹 | | | 通用内容指纹;双语配对机制使用 sidecar record 记录两侧 blob hash |\n| finish reason | 结束原因 | | | |\n| fold | 折叠区 | | | 配置界面语境:默认收起的字段分区(collapsed →「收起」)|\n| foreground run | 前台运行 | | | |\n| freshness | 新鲜度 | | | 沿用 MDN 中文翻译;在本项目中指译文相对源文的同步状态 |\n| hook | 钩子 | | | |\n| implementation | 实现 | | | |\n| inference | 推理 | 推理(inference) | | 需要和 `reasoning` 区分时保留英文括注 |\n| info string | 信息字符串 | | | 沿用 CommonMark 中文翻译;指代码围栏 ``` 之后的语言标注 |\n| injection | 注入 | | | |\n| integration | 集成 | | | |\n| interface | 接口 | | | |\n| language switcher | 语言切换行 | | | i18n 配对机制用语:双语配对文件顶部的互链行 |\n| merge | 合并 | | | |\n| message | 消息 | | | |\n| mod | 模组 | | | |\n| model provider | 模型提供方 | | | |\n| model selection | 模型选择 | | 模型目标 | 面向 Agent 的提供方、模型和可选推理强度选择。 |\n| module | 模块 | | | |\n| non-escalation | 非升权 | | 非升级、不可升级 | 仅用于安全与权限语境,指主体不得获得超出既有授权的权限;普通升级不适用此行 |\n| npm dependency | NPM 依赖 | | | `package.json` 中的包关系;`dependencies`、`devDependencies` 等字段保持原样 |\n| opt-out ratio | opt-out 比例 | | 退出检查比例 | |\n| orphan | 遗留 | | 孤儿、孤立 | 指英文源已不存在的 `.zh.md`(如「遗留译文」);进程语境按 OS 惯用语译「孤儿进程」 |\n| orphan branch | 孤立分支 | | 孤儿分支 | 沿用 git 官方中文翻译 |\n| package | 包 | | | 指 npm 包(`@deepseek-ai/dsh-*`);`package.json` 等代码标识保持原样 |\n| pairing | 配对 | | | |\n| parent-subset grants | 父级子集授权 | | 父集合授权 | 指授权范围仅限于父级所持授权的子集 |\n| peer dependency | 对等依赖 | 对等依赖(peer dependency) | | |\n| permission | 权限 | | | |\n| persistence | 持久化 | | | |\n| pipeline | 流水线 | | | |\n| plugin | 插件 | | | |\n| postmortem | 事故复盘 | 事故复盘(postmortem) | 事后分析、事故记录 | 事故记录与分析文档;目录或路径中的 `postmortem` 保持代码形式 |\n| prompt | 提示词 | | | |\n| provider | 提供方 | | | |\n| provider-neutral | 提供方无关 | | 提供方中立 | |\n| quality gate | 质量门禁 | | | |\n| quiescence | 完全停稳 | | 静默、静止状态 | 指生命周期工作全部结算后的状态 |\n| reasoning | 推理 | 推理(reasoning) | | 需要和 `inference` 区分时保留英文括注 |\n| reasoning_content | 思考内容 | | | |\n| registry | 注册表 | | | |\n| replay | 回放 | | | |\n| resume | 恢复 | | | |\n| runtime | 运行时 | | | |\n| same-world subprocess | 与宿主共享文件系统和内核的子进程 | | 同世界子进程 | |\n| sandbox | 沙箱 | | | |\n| service | 服务 | | | |\n| serving interface | 对外服务接口 | | | |\n| session | 会话 | | | |\n| session event | 会话事件 | | | |\n| setup card | 设置卡片 | | | 首次运行时代替行卡直接展开的配置卡 |\n| sidecar file | 伴随文件 | | | 指与文档同目录的普通伴随文件 |\n| sidecar record | 伴随记录 | | 旁挂记录 | 指与文档同目录的伴随记录文件 |\n| smoke test | 冒烟测试 | | | |\n| snapshot | 快照 | | | |\n| source of truth | 真源 | | 事实来源、唯一来源 | |\n| spine | 主干 | | | |\n| stale | 陈旧 | | 过期 | 与 `fresh`(`新鲜`)成对;门禁输出中保留英文 `stale` 不翻译;`expired` 才译为`过期` |\n| step | 步骤 | | | |\n| stream | 流 | | | |\n| structural signature | 结构签名 | | | i18n 配对机制用语:门禁比对两侧文件时提取的有序结构序列(标题层级、代码块、列表等) |\n| Summary | 概述 | | | 事故复盘标题用语 |\n| system prompt | 系统提示词 | | | |\n| taxonomy | 分类体系 | | | |\n| token usage | token 用量 | | | |\n| tool | 工具 | | | |\n| tool call | 工具调用 | | | |\n| tool result | 工具结果 | | | |\n| tool schema | 工具 schema | | | |\n| toolkit | 工具包 | | | |\n| turn | 轮次 | | | |\n| VFS | VFS | 虚拟文件系统(VFS) | | |\n| typecheck | 类型检查 | | | |\n| vocabulary | 词汇 | | | |\n| wire format | 协议格式 | 协议格式(wire format) | | |\n| workflow | 工作流 | | | |\n| wrapper | 包装层 | | | 软件层或 SDK 包装层 |\n| wrapper script | 包装脚本 | | | 可执行脚本包装层 |\n\n\n## Output Format\n\nReturn exactly three raw XML sections in the order shown below. Do not wrap the response in a Markdown code fence and do not add analysis or text before, between, or after the sections. The fence below only displays the required format; do not reproduce the fence.\n\nThe outer section tags are framing. If Markdown inside any section body contains a line consisting only of ``, ``, ``, ``, ``, or ``, prefix that line with `\\`. If the original line already has one or more backslashes immediately before the tag, add one more. The parser removes exactly one framing escape; tags mentioned inline need no escaping.\n\n```xml\n\n(First pass: the complete translation, written as natural target-language technical prose)\n\n\n\n(Second pass: actual corrections only, one correction per line with a category tag, e.g.)\n- [Tone] \"旁挂记录\" → \"伴随记录\"(生造词)\n- [Sentence] 第 3 段补充逗号断句\n- [Punctuation] 两处破折号替换为冒号\n- [Terminology: pending] source term → tentative rendering\n- 无修正\n\n\n\n(Complete final translation after corrections)\n\n```\n\n## Self-Review Instructions\n\nAfter writing ``, verify it in two directions. First re-read it in the target language only without comparing it with the source; this makes awkward phrasing easier to notice. Then compare it against the source clause by clause for completeness and exact meaning. Resolve doubts before writing ``; do not include reasoning transcripts, checks that passed, tentative suggestions, retractions, or no-op corrections.\n\n**Structure**\n- Are the heading hierarchy and order, list kind and item count, ordered-list start, table dimensions, and code block content identical to the source?\n- Are ALL comments and info strings inside code blocks left untranslated and byte-identical to the source?\n- Are inline code spans and machine-readable tokens verbatim?\n- Is an existing language switcher correctly flipped, and is no switcher or filename invented when the source lacks one?\n- Do links preserve their semantic targets and exact query/fragment suffixes while using target-locale paths, and are emphasis spans preserved?\n- Does spacing across emphasis boundaries follow the same Chinese/Latin/numeral rule as ordinary prose?\n- Are wrapper-tag lines inside section bodies escaped with one additional backslash?\n\n**Faithfulness**\n- Clause by clause, is anything added, dropped, weakened, strengthened, reversed, merged, or re-bounded? Are list item counts identical on both sides?\n- Do actors, objects, conditions, exceptions, negation, modality, causal relationships, guarantees, contract directions, result channels, ownership changes, and quantities survive exactly?\n\n**Tone & Style**\n- Does every sentence read as if originally written by a native technical author?\n- Is there any colloquial, casual, overly informal, promotional, or metaphorical phrasing?\n- Are actors explicit where the target language needs them, without inventing responsibility?\n\n**Sentence Structure**\n- Are there run-on sentences that need breaking?\n- Are there stiff passive constructions that can safely become active, or active constructions that invent an actor?\n- Are conditions, concessions, negation, coordination, and modifiers scoped clearly?\n\n**Word Choice**\n- Are there overly literal translations that sound unnatural?\n- Are ordinary prose words left untranslated despite an established target-language expression?\n- Does each polysemous word fit its local context?\n- Is the same target-language word used for distinct source concepts, or is a defined term varied merely to avoid repetition?\n- Is any slang or internal jargon present?\n\n**Terminology**\n- For a Chinese target, are first-occurrence glosses correctly applied to the true first prose occurrence, neither missing nor repeated? For an English target, are Chinese glosses absent?\n- Are any \"不要译作\" forbidden translations present?\n- Do protected tokens remain untouched even when they resemble terminology entries?\n- For an unlisted term, does a Chinese target use an established Chinese rendering or preserve the source term as pending when no reliable rendering is known, and does an English target use the established English technical term or preserve only an ambiguous source term with the shortest necessary gloss and a pending notice?\n\n**Punctuation** (when target is Chinese)\n- Are punctuation, mixed-script spacing, quotation marks, Latin letters, and digits in their required forms?\n- Are there em dashes that make the sentence less clear and should be replaced, while natural em dashes remain intact?\n- Are list-item endings grammatically consistent, with none ending in commas?\n- Do RFC 2119 keywords preserve the source emphasis span and normative strength exactly?\n\nRecord actual corrections in ``, then output the corrected complete document in ``. If no correction or pending terminology notice is needed, write exactly `- 无修正` in `` and copy `` unchanged into ``. If `` contains only pending terminology notices, copy `` unchanged into ``.\n\n## Examples\n\nBelow are representative examples of common problems and their corrections. Follow the \"Good\" versions within the rule each example illustrates; examples do not override source context or higher-priority requirements.\n\n### Colloquial verb → Professional verb\n- Source: `The repo pins pnpm@11.7.0 in package.json`\n- Bad: `仓库在 package.json 中钉住 pnpm@11.7.0`\n- Good: `该仓库在 package.json 中固定使用 pnpm@11.7.0`\n\n### Run-on sentence → Natural phrasing with pause\n- Source: `Read docs/architecture.md before changing anything under packages/.`\n- Bad: `改动 packages/ 下的任何东西之前先读 docs/architecture.md。`\n- Good: `在修改 packages/ 目录下的任何内容之前,请先阅读 docs/architecture.md。`\n\n### Stiff passive voice → Active and natural\n- Source: `a green gate means the pair was confirmed consistent at these exact contents, not that the confirmation was sound.`\n- Bad: `门禁绿意味着这对文档曾在当前内容上被确认一致,不意味着这次确认本身是对的。`\n- Good: `门禁通过意味着这组文档在当前内容上的一致性得到了确认,不代表确认本身正确可靠。`\n\n### Invented word → Natural expression\n- Source: `A sidecar record of both blob hashes makes consistency checkable`\n- Bad: `旁挂记录两侧 blob hash,使一致性可检查`\n- Good: `伴随记录保存两侧 blob hash,使一致性可检查`\n\n### Em-dash → Colon/period\n- Source: `FIXME — an issue that should block a new release. A release should not ship with an open FIXME unless reviewers explicitly agree the change can be merged anyway.`\n- Bad: `FIXME——应当阻塞新版本发布的问题。除非评审者明确同意可以照常合入,发布不应带着未解决的 FIXME 出门。`\n- Good: `FIXME:应当阻塞新版本发布的问题。除非评审者明确同意该更改可以合并,否则发布版本不应包含未解决的 FIXME。`\n\n### Overly literal → Meaningful rendering\n- Source: `awkward phrasing is easier to notice when you read the translation without comparing it with the source`\n- Bad: `不把译文和原文比较时,尴尬的措辞更容易被注意`\n- Good: `不对照原文阅读译文时,更容易察觉别扭的表达`\n\n### Terminology — do not translate what should be kept in English\n- Source: `typed service seams, and explicit extension points`\n- Bad: `类型化的服务 seam(扩展点)与显式扩展点`\n- Good: `类型化的服务 seam 与显式扩展点`\n\n### Slang/jargon → Professional phrasing\n- Source: `The committed agent workflow lives in .agents/skills/dsh-translate-docs`\n- Bad: `进仓的 agent 工作流见 .agents/skills/dsh-translate-docs`\n- Good: `仓库内置的 agent 工作流见 .agents/skills/dsh-translate-docs`\n\n### \"For humans\" — translate the intent, not the word\n- Source: `For humans, start with the development guide`\n- Bad: `对于人工读者,请先从开发指南开始`(\"人工读者\"生硬)\n- Good: `面向开发者:请先阅读开发指南`(\"开发者\"自然,且中文里冒号在此处更自然)\n\n### Code block comments — NEVER translate\n- Source code block contains: `# full-screen TUI coding agent (needs DEEPSEEK_API_KEY)`\n- Bad: `# 全屏 TUI coding agent(需要 DEEPSEEK_API_KEY)`\n- Good: `# full-screen TUI coding agent (needs DEEPSEEK_API_KEY)` (keep exactly as-is, byte-for-byte)\n\n### Language switcher — flip direction\n- Source file (English) has: `English | [中文](README.zh.md)`\n- Bad (copying source unchanged): `English | [中文](README.zh.md)`\n- Good (flipped for Chinese file): `[English](README.md) | 中文`\n\n---\n\nNow translate the following document:" }, { "role": "user", @@ -24,27 +24,27 @@ }, { "role": "user", - "content": "# Bilingual documentation\n\nEnglish | [中文](README.zh.md)\n\nThis repo's documentation is read by people and agents both inside and outside the company, so every document in scope is maintained in English and Simplified Chinese. This page defines the pairing contract, checks, scope, and exclusions; [translation-rules.md](translation-rules.md) defines how to translate; [terminology.md](terminology.md) is the terminology source of truth. Routine agent work follows the lightweight path in [docs/AGENTS.md](../AGENTS.md); the extended [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) workflow is available only through explicit user invocation.\n\n## The pairing contract\n\n- **Both languages carry equal authority.** A document may be authored and reviewed in either language first — a Chinese-first Agent Note is as legitimate as an English-first one — and the counterpart is translated from it. Neither file outranks the other; what binds them is that they must say the same thing.\n- **A pair is three sibling files.** The English `foo.md`, the Chinese `foo.zh.md`, and a consistency record `foo.i18n.yaml`, all in the same directory. No locale directories, no separate translation repo, no interleaved bilingual files. Pairs merge whole: a PR never lands one language without the other two files.\n- **The consistency record.** `foo.i18n.yaml` holds the full git blob hash of each side as of the last time the two were confirmed to say the same thing:\n\n ```yaml\n foo.md: 3f786850e387550fdab836ed7e6dc881de23001b\n foo.zh.md: 89e6c98d92887913cadf06b2adb97f26cde4849b\n ```\n\n Blob hashes, not commit hashes, so the record is computable for files edited in the same PR (`git hash-object foo.md`) and consistency is a pure content comparison. `--write` stores those snapshots in the local Git object database before recording them, including uncommitted working-tree contents, and pins every distinct stored blob under a content-addressed `refs/dsh/translation-pairing/snapshots/` ref so garbage collection cannot invalidate a recorded recovery pointer. The recorded hashes therefore recover the exact last-confirmed text of either side, so an out-of-sync pair is updated by patching the counterpart minimally against the edited side's diff — never by re-translating whole files. Routine work makes that patch directly; when the user explicitly invokes the extended workflow, `pnpm run gen-translation-brief ` can instead assemble the update at the narrowest safely aligned granularity and `--apply` can splice a code-fence-only change after structural validation ([briefed-updates Agent Note](../../.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.md)). After bringing the pair back in line, `pnpm run verify-translation-pairing --write ` re-records both hashes; that yaml diff is the reviewable act of confirming consistency, which is why `--write` requires naming the pairs you confirmed (`--write --all` is the explicit corpus-wide form).\n\n When two branches contain valid confirmations of the same pair, the installed `dsh-translation-pairing` Git merge driver composes a new record only if Git's default text merge succeeds for both recorded owner-blob triplets and the merged pair retains its required switchers and structural signature. The Chinese file must retain its English backlink; an authored English source must retain its Chinese link, while a listed generated English source is exempt. Any structure the driver cannot verify remains an ordinary conflict; `pnpm run resolve-translation-pairing-conflicts` applies the same fail-closed operation to a merge that has already stopped, stages every safe pairing record, and exits unsuccessfully when other pairing conflicts remain. The [automatic pairing merges Agent Note](../../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) owns the mechanism and alternatives.\n- **Language switcher.** The Chinese file always links back immediately after its H1 heading with `[English](foo.md) | 中文`. An authored English file reciprocates there with `English | [中文](foo.zh.md)`; a listed generated English source omits that line so it remains byte-identical to generator output. A README published outside GitHub, such as PyPI project metadata, may use the canonical `https://github.com/deepseek-ai/deepseek-harness/blob/master/` URL to the same counterpart so the switcher still resolves there.\n- **Structure mirrors the counterpart.** Heading depths and order, list kinds, ordered-list starts, list item counts, table row and column counts, semantic link targets with exact query/fragment suffixes, and verbatim code blocks match one to one across the pair. A relative document link uses the target's English `.md` path on the English side and its `.zh.md` sibling on the Chinese side when that sibling exists; links without a paired target stay unchanged. See [translation-rules.md](translation-rules.md) for the full preservation rules. Existing Markdown gates apply to `.zh.md` files unchanged (`verify-md-wrap`, `verify-md-links`).\n\n## The gate: verify-translation-pairing\n\n`pnpm run verify-translation-pairing` (part of `doc-sync`, which contributors run locally for documentation changes and CI runs exhaustively) enforces the contract mechanically:\n\n1. Every document in scope has a complete pair. README discovery is case-insensitive on the basename, so `missions/readme.md` is in scope alongside the other documentation roots.\n2. Every pair artifact that exists at all is complete and consistent: all three files present, each side's current blob hash equals the recorded one (editing either side without re-confirming the pair goes red), the Chinese side and every authored English source carry their language switchers (listed generated English sources are exempt), every ordinary relative document link uses its source side's target locale, and the structural signatures match in order — heading depths, verbatim code blocks (info string and content), table row and column counts, list kinds, ordered-list starts, item counts, and semantic link targets with exact query/fragment suffixes apart from the switcher.\n3. Files listed as `excluded` have no `.zh.md` and no `.i18n.yaml` at all. Frozen Agent Notes under `.agents/notes/archived/` are outside this evolving gate; their dedicated verifier requires and seals the complete existing triplet instead.\n\nSource-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.\n\n`pnpm run verify-translation-pairing --list` prints the current pairing state of every document in scope — missing, out-of-sync, or ok. It never fails; `missing` and `out-of-sync` rows identify violations that the normal check rejects.\n\n`pnpm run verify-translation-pairing ` checks just the named pairs — any of a pair's three files (or its bare stem) names it — so an update loop verifies its own pair in seconds instead of re-scanning the corpus. The no-argument corpus-wide form is what `doc-sync` and CI run; a scoped green never substitutes for it at PR level.\n\nThe practical rule this gate creates: **when a PR edits either side of a paired document, the same PR updates the counterpart directly in one terminology-guided pass and re-records the pair with `--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.\n\nThe gate's limit, stated plainly: **a green gate means the pair was confirmed consistent at these exact contents, not that the confirmation was sound.** It checks hashes and Markdown structure; it cannot judge whether the two sides actually say the same thing, or whether the wording is accurate, well-termed, and natural — that is the reviewer's half of the contract, per [translation-rules.md](translation-rules.md). A re-recorded pair with a sloppy counterpart passes the gate; it must not pass review.\n\n## Scope and exclusions\n\n**Scope**: the root CONTRIBUTING document, every non-vendor README, and every active document under `.agents/notes/**`, `docs/**`, and `python/**`. README matching is case-insensitive on the basename and covers future directories without another manifest edit. Dependency and ignored build-output trees and the frozen `.agents/notes/archived/` tree are discovery exclusions, not evolving translation source.\n\nGenerated English references and graphs participate in pairing when a reviewed Chinese counterpart is available. Their generators remain the English source of truth, and freshness and pairing gates enforce their respective invariants independently; regeneration that changes English leaves the pair out of sync until the reviewed Chinese counterpart is updated and re-recorded. A generator that owns both sides, such as the Cordis subsystem-region generator, projects paired document paths to each output locale while keeping every other generated byte equal. Generated English sources omit the language switcher that ordinary authored sources carry, because adding it would make the generator stale; their Chinese counterparts still link back to the English source. A generated page's Chinese counterpart may rewrite only self-referential generation and maintenance statements that would otherwise be false for the reviewed translation; all technical content remains subject to the ordinary faithfulness rules.\n\n**Excluded** (never paired, and the gate rejects a `.zh.md` or `.i18n.yaml` for them):\n\n- [cordis-api/inherited.md](../cordis-api/inherited.md) — generated without a reviewed Chinese counterpart, so both website locales project the English source.\n- `docs/AGENTS.md`, `.agents/notes/**/AGENTS.md`, and their `CLAUDE.md` instruction symlinks — agent instructions, maintained in English only like the root `AGENTS.md`.\n- `docs/i18n/terminology.md` and [style-samples.md](style-samples.md) — both are bilingual by construction.\n- [translation-prompt.md](translation-prompt.md) — the automated pipeline's prompt template; its body is machine-consumed verbatim, so a paired translation would change pipeline behavior.\n- `.agents/notes/archived/` — frozen historical triplets. [`verify-archived-agent-notes`](../../scripts/verify-archived-agent-notes.ts) validates their completeness and content seals; translation maintenance must never rewrite them.\n\n**Universal requirement**: every current or future document in scope must merge as a complete bilingual pair. [scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) contains only explicit exclusions; there is no per-file rollout list, date cutoff, or README-specific policy class.\n\n## Division of labor\n\nRoutine counterparts are updated directly by the working agent in one shot and one pass after it loads [terminology.md](terminology.md); it does not invoke a translation skill, generate a briefing, run a separate translation-review pass, or delegate to a subagent. The extended [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) workflow retains those heavier mechanisms for explicit user invocation. The gate checks pair completeness, recorded hashes, the Chinese backlink and authored-source switcher (with the documented generated-source exception), and its documented structural signature. Review still owns translation quality, terminology, and structural requirements that the signature does not encode. The prompt contract is executable: [scripts/translation-prompt.ts](../../scripts/translation-prompt.ts) renders the committed template (terminology injected; the template carries its own calibrated rules) into either direction and parses the three-section response, while `verify-translation-prompt` exercises both render directions and the checked-in example in `doc-sync`.\n" + "content": "# Bilingual documentation\n\nEnglish | [中文](README.zh.md)\n\nThis repo's documentation is read by people and agents both inside and outside the company, so every document in scope is maintained in English and Simplified Chinese. This page defines the pairing contract, checks, scope, and exclusions; [translation-rules.md](translation-rules.md) defines how to translate; [terminology.md](terminology.md) is the terminology source of truth. Routine agent work follows the lightweight path in [docs/AGENTS.md](../AGENTS.md); the extended [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) workflow is available only through explicit user invocation.\n\n## The pairing contract\n\n- **Both languages carry equal authority.** A document may be authored and reviewed in either language first — a Chinese-first Agent Note is as legitimate as an English-first one — and the counterpart is translated from it. Neither file outranks the other; what binds them is that they must say the same thing.\n- **A pair is three sibling files.** The English `foo.md`, the Chinese `foo.zh.md`, and a consistency record `foo.i18n.yaml`, all in the same directory. No locale directories, no separate translation repo, no interleaved bilingual files. Pairs merge whole: a PR never lands one language without the other two files.\n- **The consistency record.** `foo.i18n.yaml` holds the full git blob hash of each side as of the last time the two were confirmed to say the same thing:\n\n ```yaml\n foo.md: 3f786850e387550fdab836ed7e6dc881de23001b\n foo.zh.md: 89e6c98d92887913cadf06b2adb97f26cde4849b\n ```\n\n Blob hashes, not commit hashes, so the record is computable for files edited in the same PR (`git hash-object foo.md`) and consistency is a pure content comparison. `--write` stores those snapshots in the local Git object database before recording them, including uncommitted working-tree contents, and pins every distinct stored blob under a content-addressed `refs/dsh/translation-pairing/snapshots/` ref so garbage collection cannot invalidate a recorded recovery pointer. The recorded hashes therefore recover the exact last-confirmed text of either side, so an out-of-sync pair is updated by patching the counterpart minimally against the edited side's diff — never by re-translating whole files. Routine work makes that patch directly; when the user explicitly invokes the extended workflow, `pnpm run gen-translation-brief ` can instead assemble the update at the narrowest safely aligned granularity and `--apply` can splice a code-fence-only change after structural validation ([briefed-updates Agent Note](../../.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.md)). After bringing the pair back in line, `pnpm run verify-translation-pairing --write ` re-records both hashes; that yaml diff is the reviewable act of confirming consistency, which is why `--write` requires naming the pairs you confirmed (`--write --all` is the explicit corpus-wide form).\n\n When two branches contain valid confirmations of the same pair, the installed `dsh-translation-pairing` Git merge driver composes a new record only if Git's default text merge succeeds for both recorded owner-blob triplets and the merged pair retains its required switchers and structural signature. The Chinese file must retain its English backlink; an authored English source must retain its Chinese link, while a listed generated English source is exempt. Any structure the driver cannot verify remains an ordinary conflict; `pnpm run resolve-translation-pairing-conflicts` applies the same fail-closed operation to a merge that has already stopped, stages every safe pairing record, and exits unsuccessfully when other pairing conflicts remain. The [automatic pairing merges Agent Note](../../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) owns the mechanism and alternatives.\n- **Language switcher.** The Chinese file always links back immediately after its H1 heading with `[English](foo.md) | 中文`. An authored English file reciprocates there with `English | [中文](foo.zh.md)`; a listed generated English source omits that line so it remains byte-identical to generator output. A README published outside GitHub, such as PyPI project metadata, may use the canonical `https://github.com/deepseek-ai/deepseek-harness/blob/master/` URL to the same counterpart so the switcher still resolves there.\n- **Structure mirrors the counterpart.** Heading depths and order, list kinds, ordered-list starts, list item counts, table row and column counts, semantic link targets with exact query/fragment suffixes, and verbatim code blocks match one to one across the pair. When a relative document link targets the active bilingual corpus, the English side uses its `.md` path and the Chinese side uses its `.zh.md` path. A missing counterpart in that corpus is a pair-completeness error rather than a fallback; targets outside the active corpus keep the authored path. See [translation-rules.md](translation-rules.md) for the full preservation rules. Existing Markdown gates apply to `.zh.md` files unchanged (`verify-md-wrap`, `verify-md-links`).\n\n## The gate: verify-translation-pairing\n\n`pnpm run verify-translation-pairing` (part of `doc-sync`, which contributors run locally for documentation changes and CI runs exhaustively) enforces the contract mechanically:\n\n1. Every document in scope has a complete pair. README discovery is case-insensitive on the basename, so `missions/readme.md` is in scope alongside the other documentation roots.\n2. Every pair artifact that exists at all is complete and consistent: all three files present, each side's current blob hash equals the recorded one (editing either side without re-confirming the pair goes red), the Chinese side and every authored English source carry their language switchers (listed generated English sources are exempt), every ordinary relative document link uses its source side's target locale, and the structural signatures match in order — heading depths, verbatim code blocks (info string and content), table row and column counts, list kinds, ordered-list starts, item counts, and semantic link targets with exact query/fragment suffixes apart from the switcher.\n3. Files listed as `excluded` have no `.zh.md` and no `.i18n.yaml` at all. Frozen Agent Notes under `.agents/notes/archived/` are outside this evolving gate; their dedicated verifier requires and seals the complete existing triplet instead.\n\nSource-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.\n\n`pnpm run verify-translation-pairing --list` prints the current pairing state of every document in scope — missing, out-of-sync, or ok. It never fails; `missing` and `out-of-sync` rows identify violations that the normal check rejects.\n\n`pnpm run verify-translation-pairing ` checks just the named pairs — any of a pair's three files (or its bare stem) names it — so an update loop verifies its own pair in seconds instead of re-scanning the corpus. The no-argument corpus-wide form is what `doc-sync` and CI run; a scoped green never substitutes for it at PR level.\n\nThe practical rule this gate creates: **when a PR edits either side of a paired document, the same PR updates the counterpart directly in one terminology-guided pass and re-records the pair with `--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.\n\nThe gate's limit, stated plainly: **a green gate means the pair was confirmed consistent at these exact contents, not that the confirmation was sound.** It checks hashes and Markdown structure; it cannot judge whether the two sides actually say the same thing, or whether the wording is accurate, well-termed, and natural — that is the reviewer's half of the contract, per [translation-rules.md](translation-rules.md). A re-recorded pair with a sloppy counterpart passes the gate; it must not pass review.\n\n## Scope and exclusions\n\n**Scope**: the root CONTRIBUTING document, every non-vendor README, and every active document under `.agents/notes/**`, `docs/**`, and `python/**`. README matching is case-insensitive on the basename and covers future directories without another manifest edit. Dependency and ignored build-output trees and the frozen `.agents/notes/archived/` tree are discovery exclusions, not evolving translation source.\n\nGenerated English references and graphs participate in pairing when a reviewed Chinese counterpart is available. Their generators remain the English source of truth, and freshness and pairing gates enforce their respective invariants independently; regeneration that changes English leaves the pair out of sync until the reviewed Chinese counterpart is updated and re-recorded. A generator that owns both sides, such as the Cordis subsystem-region generator, projects paired document paths to each output locale while keeping every other generated byte equal. Generated English sources omit the language switcher that ordinary authored sources carry, because adding it would make the generator stale; their Chinese counterparts still link back to the English source. A generated page's Chinese counterpart may rewrite only self-referential generation and maintenance statements that would otherwise be false for the reviewed translation; all technical content remains subject to the ordinary faithfulness rules.\n\n**Excluded** (never paired, and the gate rejects a `.zh.md` or `.i18n.yaml` for them):\n\n- [cordis-api/inherited.md](../cordis-api/inherited.md) — generated without a reviewed Chinese counterpart, so both website locales project the English source.\n- `docs/AGENTS.md`, `.agents/notes/**/AGENTS.md`, and their `CLAUDE.md` instruction symlinks — agent instructions, maintained in English only like the root `AGENTS.md`.\n- `docs/i18n/terminology.md` and [style-samples.md](style-samples.md) — both are bilingual by construction.\n- [translation-prompt.md](translation-prompt.md) — the automated pipeline's prompt template; its body is machine-consumed verbatim, so a paired translation would change pipeline behavior.\n- `.agents/notes/archived/` — frozen historical triplets. [`verify-archived-agent-notes`](../../scripts/verify-archived-agent-notes.ts) validates their completeness and content seals; translation maintenance must never rewrite them.\n\n**Universal requirement**: every current or future document in scope must merge as a complete bilingual pair. [scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) contains only explicit exclusions; there is no per-file rollout list, date cutoff, or README-specific policy class.\n\n## Division of labor\n\nRoutine counterparts are updated directly by the working agent in one shot and one pass after it loads [terminology.md](terminology.md); it does not invoke a translation skill, generate a briefing, run a separate translation-review pass, or delegate to a subagent. The extended [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) workflow retains those heavier mechanisms for explicit user invocation. The gate checks pair completeness, recorded hashes, the Chinese backlink and authored-source switcher (with the documented generated-source exception), and its documented structural signature. Review still owns translation quality, terminology, and structural requirements that the signature does not encode. The prompt contract is executable: [scripts/translation-prompt.ts](../../scripts/translation-prompt.ts) renders the committed template (terminology injected; the template carries its own calibrated rules) into either direction and parses the three-section response, while `verify-translation-prompt` exercises both render directions and the checked-in example in `doc-sync`.\n" }, { "role": "assistant", - "content": "# 双语文档\n\n[English](README.md) | 中文\n\n本仓库的文档会被公司内外的人和 agent(智能体)阅读,因此范围内的每篇文档都以英文和简体中文维护。本页定义配对约定、检查、范围与排除规则;[translation-rules.md](translation-rules.zh.md) 定义如何翻译;[terminology.md](terminology.md) 是术语真源。agent 的日常工作遵循 [docs/AGENTS.md](../AGENTS.md) 中的轻量路径;扩展版 [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) 工作流仅在用户显式调用时可用。\n\n\n\n## 配对约定\n\n- **两种语言同权。** 一篇文档可以先用任一语言撰写和评审(先写中文的 Agent Note 与先写英文的一样正当),另一侧由它翻译而来。两个文件谁也不高于谁;约束它们的是二者必须说同样的话。\n- **一对文档是三个同目录文件。** 英文 `foo.md`、中文 `foo.zh.md`,加一份一致性记录 `foo.i18n.yaml`,都在同一目录。不用语言目录,不用独立翻译仓库,不用中英混排的单文件。配对必须整体合并:PR(Pull Request)永远不会只带一种语言而缺其余两个文件。\n- **一致性记录。**`foo.i18n.yaml` 保存两侧文件在上一次被确认「说同样的话」时各自的完整 Git blob hash:\n\n ```yaml\n foo.md: 3f786850e387550fdab836ed7e6dc881de23001b\n foo.zh.md: 89e6c98d92887913cadf06b2adb97f26cde4849b\n ```\n\n 用 blob hash 而不是 commit hash,这样同一个 PR 里改动的文件也能算出记录(`git hash-object foo.md`),一致性是纯内容比较。`--write` 会先把这些快照存入本地 Git 对象库再写下记录,未提交的 worktree 内容也不例外;它还会在内容寻址的 `refs/dsh/translation-pairing/snapshots/` ref 下固定每个不同的已存 blob,使垃圾回收无法让已记录的恢复指针失效。因此记录的 hash 能还原任一侧上次确认时的确切文本,所以失去同步的配对是「按被改一侧的 diff 最小化地修补另一侧」,从不整篇重译。日常工作会直接完成这份修补;用户显式调用扩展工作流时,可改由 `pnpm run gen-translation-brief ` 以能安全对齐的最窄粒度汇集这次更新,并由 `--apply` 在结构校验后拼接仅涉及围栏代码块的改动([briefed-updates Agent Note](../../.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.zh.md))。两侧对齐后,`pnpm run verify-translation-pairing --write ` 重新记录两个 hash;那份 YAML diff 就是「确认一致」这个动作本身,可以被评审,也正因如此,`--write` 要求点名你确认过的配对(`--write --all` 是显式的全语料形式)。\n\n 当两个分支都包含同一配对的有效确认时,已安装的 `dsh-translation-pairing` Git 合并驱动只会在 Git 默认文本合并能分别干净合并记录所指向的英文三方 blob 与中文三方 blob,且合并后的配对仍保留必需的语言切换行和结构签名时,组合出一份新记录。中文文件必须保留指向英文的反向链接;普通撰写的英文源必须保留指向中文的链接,而清单内的生成英文源不作此要求。任何合并驱动无法验证的结构都保留为普通冲突;`pnpm run resolve-translation-pairing-conflicts` 会对已经停止的合并执行同一套遇错即保留冲突的操作,暂存每份可安全生成的配对记录,并在还有其他配对冲突时以非零状态退出。[自动配对合并 Agent Note](../../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.zh.md) 负责记录该机制与备选方案。\n- **语言切换行。** 中文文件一律在 H1 标题后立即以 `[English](foo.md) | 中文` 链回英文。普通撰写的英文文件在同一位置以 `English | [中文](foo.zh.md)` 互链;清单内的生成英文源省略此行,以便与生成器输出逐字节一致。发布到 GitHub 以外位置的 README(例如 PyPI 项目元数据)可以改用指向同一对侧文件的规范 `https://github.com/deepseek-ai/deepseek-harness/blob/master/` URL,使切换行在该位置仍可访问。\n- **结构与另一侧一一对应。** 标题深度与顺序、列表类型、有序列表起始编号、列表项数量、表格行列数、保留原样 query/fragment 后缀的语义链接目标,以及逐字节一致的代码块在配对两侧一一对应。相对文档链接在英文侧使用目标的英文 `.md` 路径;目标存在 `.zh.md` 兄弟文件时,中文侧使用该中文路径;目标没有对侧时保持原链接。完整保持规则见 [translation-rules.md](translation-rules.zh.md)。既有 Markdown 门禁对 `.zh.md` 文件原样生效(`verify-md-wrap`、`verify-md-links`)。\n\n## 门禁:verify-translation-pairing\n\n`pnpm run verify-translation-pairing`(`doc-sync`(文档同步门禁)的一环,贡献者会针对文档变更在本地运行,CI 则会完整运行)机械地强制执行这份约定:\n\n1. 范围内的每篇文档都有完整配对。发现 README 时,basename 不区分大小写,因此 `missions/readme.md` 与其他文档根一样属于范围。\n2. 任何已存在的配对产物都完整且一致:三个文件齐全、每一侧的当前 blob hash 等于记录值(改了任一侧而没重新确认配对就变红)、中文侧和所有普通撰写的英文源都带语言切换行(清单内的生成英文源除外)、每条普通相对文档链接都使用源文件一侧对应的目标 locale,且结构签名按序一致:标题深度、逐字节一致的代码块(信息字符串与内容)、表格行列数、列表类型、有序列表起始编号、列表项数量,以及除切换行之外保留原样 query/fragment 后缀的语义链接目标。\n3. 列为 `excluded` 的文件完全没有 `.zh.md`,也没有 `.i18n.yaml`。`.agents/notes/archived/` 下冻结的 Agent Note 不受这个持续演进的门禁约束;专用校验器会要求其现有的三个配对文件完整,并将其封存。\n\n面向源码的代码门禁会把精确的 `.zh.md` 围栏序列视为其无后缀兄弟文件的派生内容,而不会再次编译相同代码或在 manifest(元数据清单)中重复登记。该序列必须在长度、顺序、围栏类型和按字节精确的正文上一致;否则两份副本仍会独立受检,配对门禁也会报告结构不匹配。\n\n`pnpm run verify-translation-pairing --list` 打印范围内每篇文档的当前配对状态(missing、out-of-sync 或 ok)。它从不失败;其中 missing 与 out-of-sync 行指出普通检查会拒绝的违规。\n\n`pnpm run verify-translation-pairing ` 只检查被点名的配对——配对的三个文件中的任意一个(或其裸词干)都能点名它——因此更新循环几秒内就能验证自己的配对,而不必重新扫描全语料。`doc-sync` 与 CI 运行的是无参数的全语料形式;限定范围的绿灯在 PR 层面永远不能替代它。\n\n这个门禁带来的实际规则是:**当一个 PR 修改了已配对文档的任一侧时,同一个 PR 在术语指导下直接一次完成对侧文件的更新,并用 `--write ` 重新记录配对**,与本仓库既有的代码与 README 的 doc-sync 规则完全一致。留下失去同步的配对的 PR 会在 CI 变红。\n\n门禁的限制很明确:**门禁通过意味着这组文档在当前内容上的一致性得到了确认,不代表确认本身正确可靠。** 它检查记录的 hash 与 Markdown 结构;它无法判断两侧是否真的在说同样的话,也无法判断措辞是否准确、术语是否得当、行文是否自然;这部分约定由评审者把关,见 [translation-rules.md](translation-rules.zh.md)。重新记录了 hash 但另一侧翻得潦草的配对能通过门禁;它不得通过评审。\n\n## 范围与排除\n\n**范围**:根目录 CONTRIBUTING 文档、除 vendor 源码外的全部 README,以及 `.agents/notes/**`、`docs/**` 与 `python/**` 下的全部活跃文档。匹配 README 时只看文件名且不区分大小写,因此今后新增的目录无需再修改 manifest。依赖目录、被忽略的构建产物目录以及冻结的 `.agents/notes/archived/` 目录树只在发现阶段排除,不属于持续演进的翻译源文档。\n\n有经评审的中文对侧的生成英文参考文档和图文档遵循配对规则。生成器仍是英文真源,新鲜度门禁与配对门禁各自独立强制其约束;重新生成导致英文变化后,配对会保持失去同步状态,直至经评审的中文对侧完成更新并重新记录。Cordis subsystem 区块生成器等同时拥有两侧输出的生成器,会把配对文档路径投影到各自 locale,同时保持其余生成字节一致。生成的英文源文件不含普通撰写文档所带的语言切换行,因为添加该行会使生成器新鲜度检查失败;中文对侧仍链接回英文源。生成页的中文对侧只能改写若直译便不再符合经评审译文事实的自指生成与维护说明;所有技术内容仍受普通忠实性规则约束。\n\n**排除**(永不配对,门禁拒绝为它们建 `.zh.md` 或 `.i18n.yaml`):\n\n- [cordis-api/inherited.md](../cordis-api/inherited.md):该生成文档没有经评审的中文对侧,因此网站的两个 locale 都投影英文源文件。\n- `docs/AGENTS.md`、`.agents/notes/**/AGENTS.md` 以及指向它们的 `CLAUDE.md` 指令符号链接:agent 指令,与根 `AGENTS.md` 一样只以英文维护。\n- `docs/i18n/terminology.md` 与 [style-samples.md](style-samples.md):二者本身即为中英对照文档。\n- [translation-prompt.md](translation-prompt.md):自动翻译流水线的提示词模板;正文逐字进入模型请求,配对翻译会改变流水线行为。\n- `.agents/notes/archived/`:冻结的历史三文件配对。[`verify-archived-agent-notes`](../../scripts/verify-archived-agent-notes.ts) 校验其完整性和内容封存记录;翻译维护绝不能重写这些文件。\n\n**统一要求**:当前及今后纳入范围的每篇文档,合并时都必须构成完整的双语配对。[scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) 只包含显式排除项;不存在逐文件推进清单、日期分界或 README 专用政策类别。\n\n## 分工\n\n日常更新对侧文件时,负责处理的 agent 会先加载 [terminology.md](terminology.md),再直接一次性更新且只处理一遍;它不会调用翻译 skill(技能)、生成简报、执行单独的翻译评审轮次,也不会委派给 subagent。扩展版 [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) 工作流保留这些较重的机制,仅供用户显式调用。门禁负责检查配对是否完整、记录的 hash、中文反向链接和普通撰写源的切换行(生成源按本文规则例外),以及本文列出的结构签名;翻译质量、术语和签名未涵盖的结构要求仍由评审把关。提示词约定也有可执行实现:[scripts/translation-prompt.ts](../../scripts/translation-prompt.ts) 会把仓库内置的模板(注入术语表;模板自带经人工校准的规则)渲染为英译中或中译英两个方向的提示词,并解析三段式响应;`doc-sync` 中的 `verify-translation-prompt` 会检查两个渲染方向与仓库内示例。\n" + "content": "# 双语文档\n\n[English](README.md) | 中文\n\n本仓库的文档会被公司内外的人和 agent(智能体)阅读,因此范围内的每篇文档都以英文和简体中文维护。本页定义配对约定、检查、范围与排除规则;[translation-rules.md](translation-rules.zh.md) 定义如何翻译;[terminology.md](terminology.md) 是术语真源。agent 的日常工作遵循 [docs/AGENTS.md](../AGENTS.md) 中的轻量路径;扩展版 [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) 工作流仅在用户显式调用时可用。\n\n\n\n## 配对约定\n\n- **两种语言同权。** 一篇文档可以先用任一语言撰写和评审(先写中文的 Agent Note 与先写英文的一样正当),另一侧由它翻译而来。两个文件谁也不高于谁;约束它们的是二者必须说同样的话。\n- **一对文档是三个同目录文件。** 英文 `foo.md`、中文 `foo.zh.md`,加一份一致性记录 `foo.i18n.yaml`,都在同一目录。不用语言目录,不用独立翻译仓库,不用中英混排的单文件。配对必须整体合并:PR(Pull Request)永远不会只带一种语言而缺其余两个文件。\n- **一致性记录。**`foo.i18n.yaml` 保存两侧文件在上一次被确认「说同样的话」时各自的完整 Git blob hash:\n\n ```yaml\n foo.md: 3f786850e387550fdab836ed7e6dc881de23001b\n foo.zh.md: 89e6c98d92887913cadf06b2adb97f26cde4849b\n ```\n\n 用 blob hash 而不是 commit hash,这样同一个 PR 里改动的文件也能算出记录(`git hash-object foo.md`),一致性是纯内容比较。`--write` 会先把这些快照存入本地 Git 对象库再写下记录,未提交的 worktree 内容也不例外;它还会在内容寻址的 `refs/dsh/translation-pairing/snapshots/` ref 下固定每个不同的已存 blob,使垃圾回收无法让已记录的恢复指针失效。因此记录的 hash 能还原任一侧上次确认时的确切文本,所以失去同步的配对是「按被改一侧的 diff 最小化地修补另一侧」,从不整篇重译。日常工作会直接完成这份修补;用户显式调用扩展工作流时,可改由 `pnpm run gen-translation-brief ` 以能安全对齐的最窄粒度汇集这次更新,并由 `--apply` 在结构校验后拼接仅涉及围栏代码块的改动([briefed-updates Agent Note](../../.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.zh.md))。两侧对齐后,`pnpm run verify-translation-pairing --write ` 重新记录两个 hash;那份 YAML diff 就是「确认一致」这个动作本身,可以被评审,也正因如此,`--write` 要求点名你确认过的配对(`--write --all` 是显式的全语料形式)。\n\n 当两个分支都包含同一配对的有效确认时,已安装的 `dsh-translation-pairing` Git 合并驱动只会在 Git 默认文本合并能分别干净合并记录所指向的英文三方 blob 与中文三方 blob,且合并后的配对仍保留必需的语言切换行和结构签名时,组合出一份新记录。中文文件必须保留指向英文的反向链接;普通撰写的英文源必须保留指向中文的链接,而清单内的生成英文源不作此要求。任何合并驱动无法验证的结构都保留为普通冲突;`pnpm run resolve-translation-pairing-conflicts` 会对已经停止的合并执行同一套遇错即保留冲突的操作,暂存每份可安全生成的配对记录,并在还有其他配对冲突时以非零状态退出。[自动配对合并 Agent Note](../../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.zh.md) 负责记录该机制与备选方案。\n- **语言切换行。** 中文文件一律在 H1 标题后立即以 `[English](foo.md) | 中文` 链回英文。普通撰写的英文文件在同一位置以 `English | [中文](foo.zh.md)` 互链;清单内的生成英文源省略此行,以便与生成器输出逐字节一致。发布到 GitHub 以外位置的 README(例如 PyPI 项目元数据)可以改用指向同一对侧文件的规范 `https://github.com/deepseek-ai/deepseek-harness/blob/master/` URL,使切换行在该位置仍可访问。\n- **结构与另一侧一一对应。** 标题深度与顺序、列表类型、有序列表起始编号、列表项数量、表格行列数、保留原样 query/fragment 后缀的语义链接目标,以及逐字节一致的代码块在配对两侧一一对应。相对文档链接的目标属于活跃双语语料时,英文侧使用其 `.md` 路径,中文侧使用其 `.zh.md` 路径。该范围内缺少对侧属于配对完整性错误,不得回退;范围外的目标保留原路径。完整保持规则见 [translation-rules.md](translation-rules.zh.md)。既有 Markdown 门禁对 `.zh.md` 文件原样生效(`verify-md-wrap`、`verify-md-links`)。\n\n## 门禁:verify-translation-pairing\n\n`pnpm run verify-translation-pairing`(`doc-sync`(文档同步门禁)的一环,贡献者会针对文档变更在本地运行,CI 则会完整运行)机械地强制执行这份约定:\n\n1. 范围内的每篇文档都有完整配对。发现 README 时,basename 不区分大小写,因此 `missions/readme.md` 与其他文档根一样属于范围。\n2. 任何已存在的配对产物都完整且一致:三个文件齐全、每一侧的当前 blob hash 等于记录值(改了任一侧而没重新确认配对就变红)、中文侧和所有普通撰写的英文源都带语言切换行(清单内的生成英文源除外)、每条普通相对文档链接都使用源文件一侧对应的目标 locale,且结构签名按序一致:标题深度、逐字节一致的代码块(信息字符串与内容)、表格行列数、列表类型、有序列表起始编号、列表项数量,以及除切换行之外保留原样 query/fragment 后缀的语义链接目标。\n3. 列为 `excluded` 的文件完全没有 `.zh.md`,也没有 `.i18n.yaml`。`.agents/notes/archived/` 下冻结的 Agent Note 不受这个持续演进的门禁约束;专用校验器会要求其现有的三个配对文件完整,并将其封存。\n\n面向源码的代码门禁会把精确的 `.zh.md` 围栏序列视为其无后缀兄弟文件的派生内容,而不会再次编译相同代码或在 manifest(元数据清单)中重复登记。该序列必须在长度、顺序、围栏类型和按字节精确的正文上一致;否则两份副本仍会独立受检,配对门禁也会报告结构不匹配。\n\n`pnpm run verify-translation-pairing --list` 打印范围内每篇文档的当前配对状态(missing、out-of-sync 或 ok)。它从不失败;其中 missing 与 out-of-sync 行指出普通检查会拒绝的违规。\n\n`pnpm run verify-translation-pairing ` 只检查被点名的配对——配对的三个文件中的任意一个(或其裸词干)都能点名它——因此更新循环几秒内就能验证自己的配对,而不必重新扫描全语料。`doc-sync` 与 CI 运行的是无参数的全语料形式;限定范围的绿灯在 PR 层面永远不能替代它。\n\n这个门禁带来的实际规则是:**当一个 PR 修改了已配对文档的任一侧时,同一个 PR 在术语指导下直接一次完成对侧文件的更新,并用 `--write ` 重新记录配对**,与本仓库既有的代码与 README 的 doc-sync 规则完全一致。留下失去同步的配对的 PR 会在 CI 变红。\n\n门禁的限制很明确:**门禁通过意味着这组文档在当前内容上的一致性得到了确认,不代表确认本身正确可靠。** 它检查记录的 hash 与 Markdown 结构;它无法判断两侧是否真的在说同样的话,也无法判断措辞是否准确、术语是否得当、行文是否自然;这部分约定由评审者把关,见 [translation-rules.md](translation-rules.zh.md)。重新记录了 hash 但另一侧翻得潦草的配对能通过门禁;它不得通过评审。\n\n## 范围与排除\n\n**范围**:根目录 CONTRIBUTING 文档、除 vendor 源码外的全部 README,以及 `.agents/notes/**`、`docs/**` 与 `python/**` 下的全部活跃文档。匹配 README 时只看文件名且不区分大小写,因此今后新增的目录无需再修改 manifest。依赖目录、被忽略的构建产物目录以及冻结的 `.agents/notes/archived/` 目录树只在发现阶段排除,不属于持续演进的翻译源文档。\n\n有经评审的中文对侧的生成英文参考文档和图文档遵循配对规则。生成器仍是英文真源,新鲜度门禁与配对门禁各自独立强制其约束;重新生成导致英文变化后,配对会保持失去同步状态,直至经评审的中文对侧完成更新并重新记录。Cordis subsystem 区块生成器等同时拥有两侧输出的生成器,会把配对文档路径投影到各自 locale,同时保持其余生成字节一致。生成的英文源文件不含普通撰写文档所带的语言切换行,因为添加该行会使生成器新鲜度检查失败;中文对侧仍链接回英文源。生成页的中文对侧只能改写若直译便不再符合经评审译文事实的自指生成与维护说明;所有技术内容仍受普通忠实性规则约束。\n\n**排除**(永不配对,门禁拒绝为它们建 `.zh.md` 或 `.i18n.yaml`):\n\n- [cordis-api/inherited.md](../cordis-api/inherited.md):该生成文档没有经评审的中文对侧,因此网站的两个 locale 都投影英文源文件。\n- `docs/AGENTS.md`、`.agents/notes/**/AGENTS.md` 以及指向它们的 `CLAUDE.md` 指令符号链接:agent 指令,与根 `AGENTS.md` 一样只以英文维护。\n- `docs/i18n/terminology.md` 与 [style-samples.md](style-samples.md):二者本身即为中英对照文档。\n- [translation-prompt.md](translation-prompt.md):自动翻译流水线的提示词模板;正文逐字进入模型请求,配对翻译会改变流水线行为。\n- `.agents/notes/archived/`:冻结的历史三文件配对。[`verify-archived-agent-notes`](../../scripts/verify-archived-agent-notes.ts) 校验其完整性和内容封存记录;翻译维护绝不能重写这些文件。\n\n**统一要求**:当前及今后纳入范围的每篇文档,合并时都必须构成完整的双语配对。[scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) 只包含显式排除项;不存在逐文件推进清单、日期分界或 README 专用政策类别。\n\n## 分工\n\n日常更新对侧文件时,负责处理的 agent 会先加载 [terminology.md](terminology.md),再直接一次性更新且只处理一遍;它不会调用翻译 skill(技能)、生成简报、执行单独的翻译评审轮次,也不会委派给 subagent。扩展版 [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) 工作流保留这些较重的机制,仅供用户显式调用。门禁负责检查配对是否完整、记录的 hash、中文反向链接和普通撰写源的切换行(生成源按本文规则例外),以及本文列出的结构签名;翻译质量、术语和签名未涵盖的结构要求仍由评审把关。提示词约定也有可执行实现:[scripts/translation-prompt.ts](../../scripts/translation-prompt.ts) 会把仓库内置的模板(注入术语表;模板自带经人工校准的规则)渲染为英译中或中译英两个方向的提示词,并解析三段式响应;`doc-sync` 中的 `verify-translation-prompt` 会检查两个渲染方向与仓库内示例。\n" }, { "role": "user", - "content": "# Translation rules\n\nEnglish | [中文](translation-rules.zh.md)\n\nHow to translate between the two sides of a documentation pair in this repo. Both languages carry equal authority ([README.md](README.md)): a change is authored in either language, and that side is the source for that update — these rules govern producing or updating the counterpart. They bind humans and agents equally. Routine agent work translates the changed content directly in one terminology-guided pass; the extended [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) workflow runs only when the user explicitly invokes it. Rule levels follow RFC 2119 usage: **MUST** / **MUST NOT** are gate- or review-blocking; **SHOULD** needs a stated reason to deviate; **MAY** is discretionary.\n\n## Faithfulness\n\n- The counterpart *MUST* say what the authored side says — no added behavior, prerequisites, warnings, version claims, or examples, and no dropped ones. If the pair disagrees on substance, neither language wins by default: fix the side that is wrong, then bring the other along in the same change.\n- The counterpart *SHOULD* read as natural technical writing in its own language, not word-by-word gloss. Translate meaning, restructure sentences where the target grammar wants it, and keep the author's register — terse stays terse.\n- Do not translate the untranslatable: if a sentence resists natural rendering because it leans on an idiom of the source language, translate the idea, not the idiom.\n\n## Voice\n\n- The register is calibrated by [style-samples.md](style-samples.md) — human-approved gold pairs, one per document genre. The counterpart MUST match the target-language side of the nearest sample; where its voice and a prose voice rule disagree, the sample wins. Chinese targets use institutional technical Chinese; English targets use concise professional developer prose.\n- Write as a native technical author restating the content, not as a translator transposing sentences, while preserving every source clause: nothing added, nothing dropped — fluency never justifies losing a clause.\n- Give sentences an explicit actor when the target language would otherwise obscure it; for Chinese, replace vague passives or abstract subjects with the actual actor (系统、门禁、评审人).\n- Prefer established target-language engineering idiom over calques (误报/漏检 for false positive/negative, 执行红线 for enforcement frontier); localize metaphors instead of transplanting them, and unpack noun chains where the target language requires it.\n- Split long paragraphs by semantic unit — one idea per paragraph. Paragraph boundaries MAY differ from the source; the structural signature does not count paragraphs.\n- When translating into Chinese, category nouns use Chinese with a first-mention English annotation (实操手册(cookbook)); when translating into English, use the conventional English category name. Literal directory or file references stay code-formatted English.\n\n## Structure preservation\n\nThe pairing gate checks heading depths, fenced code blocks, table row and column counts, list kinds, ordered-list starts, list item counts, link locale, and semantic targets. Preserve the rest of the frame manually; the paired files MUST match one to one in:\n\n- heading hierarchy (same levels, same order — heading TEXT is translated),\n- list shape and numbering,\n- tables (same columns, same row order; header cells translated per terminology),\n- fenced code blocks — **byte-identical, including comments**; the pairing signature compares their info strings and contents, and ` ```ts ` blocks compile under `doc-typecheck`,\n- inline code spans (commands, flags, config keys, file paths, event names, API names, version numbers) — verbatim, never translated or reformatted,\n- links and anchors: every relative document link MUST keep the same semantic target and exact query/fragment suffix. When that target has a paired sibling, the English side uses its `.md` path and the Chinese side uses its `.zh.md` path; without a sibling, both sides keep the original target. External URLs, images, and pure in-page fragments stay unchanged. The language switcher remains the explicit cross-locale exception, and a README rendered outside GitHub MAY use the canonical public repository URL to its exact counterpart as documented in [README.md](README.md). Link TEXT is translated.\n\nThe repo's Markdown conventions apply to `.zh.md` files unchanged: one physical line per paragraph (`verify-md-wrap`), resolving relative links (`verify-md-links`), exactly one trailing newline.\n\n## Terminology\n\n- [terminology.md](terminology.md) is the source of truth in both directions. Before translating, load it; every listed term MUST follow its row and its \"不要译作\" prohibitions. A Chinese target uses the \"中文\" column and its \"首次出现\" annotation; an English target uses the \"English\" column without adding a Chinese gloss.\n- For a Chinese target, an unlisted technical term MAY use an established rendering from a major Chinese-language OSS or vendor source (K8s/Vue/MDN Chinese docs, 微软简中风格指南, big-tech project docs), cited in the PR. Without such precedent it MUST stay in English and be listed under 「待定术语」(pending terms) with a suggested rendering.\n- For an English target, use the established English technical term. If the source term has no unambiguous established equivalent, preserve it with a short explanatory gloss and list it under pending terms. Neither direction may invent a rendering inline; a decided term enters [terminology.md](terminology.md) in the same PR or a follow-up.\n\n## Typography\n\nThese rules govern the Chinese side; the English side follows the repo's normal Markdown conventions (root `AGENTS.md`). The mixed-script rules below follow the cross-project consensus of the [MDN Simplified Chinese translation guide](https://github.com/mdn/translated-content/blob/main/docs/zh-cn/translation-guide.md), the [Kubernetes zh-cn localization guide](https://kubernetes.io/zh-cn/docs/contribute/localization_zh/), the [Vue.js Chinese translation conventions](https://github.com/vuejs-translations/docs-zh-cn/wiki/%E7%BF%BB%E8%AF%91%E9%A1%BB%E7%9F%A5), and [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines), which in turn ground in [W3C clreq](https://www.w3.org/TR/clreq/) and GB/T 15834—2011:\n\n- MUST put one half-width space between Chinese text and Latin words, and between Chinese text and numerals: `每个 plugin 注册 3 个 tool`。No space between a full-width punctuation mark and anything.\n- MUST use full-width (Chinese) punctuation in Chinese prose: `,。:;?!()「」`. Half-width punctuation stays inside code spans, inside complete English sentences quoted as-is, and in numbers (`3.5`, `1,024`).\n- Chinese prose *SHOULD* prefer colons, periods, commas, or parentheses over em dashes. Keep an em dash only when no other punctuation preserves the sentence naturally.\n- Enumeration commas: a Chinese list of parallel items uses 顿号(、), not commas.\n- MUST NOT use full-width digits or full-width Latin letters — `123` never, `123` always.\n- Proper nouns keep their canonical casing: GitHub, TypeScript, DeepSeek — never `github`/`Github` unless quoting code.\n- Second person is 你, not 您 (matches the Vue and Kubernetes Chinese conventions and this repo's direct voice).\n- Emphasis markers (`**bold**`, `*italic*`) stay on the same spans as the source; Chinese has no italics, so the rendered emphasis may look identical — do not substitute quotation marks or other decoration.\n\n## Quality bar\n\n- A pair is done when a bilingual engineer reading either file alone gets everything a reader of the other gets — same facts, same caveats, same tone — and nothing extra.\n- Run `pnpm run verify-translation-pairing` and the rest of `doc-sync` for records, switchers, heading depths, code blocks, table row and column counts, list kinds, ordered-list starts, list item counts, links, and repository Markdown rules. Human review owns list and table order, noncanonical list numbering, inline code, emphasis, meaning, terminology, and tone.\n\n## References\n\nAuthorities cited by these rules, for humans and agents who want the underlying reasoning:\n\n- [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines) — the de-facto community standard for mixed CJK/Latin spacing and punctuation.\n- [MDN zh-CN translation guide](https://github.com/mdn/translated-content/blob/main/docs/zh-cn/translation-guide.md) — an in-repo translation-rules file of the same shape as this one; spacing, punctuation, and glossary practice.\n- [Kubernetes zh-cn localization guide](https://kubernetes.io/zh-cn/docs/contribute/localization_zh/) — terminology-first-occurrence and punctuation practice from the largest zh localization team.\n- [Vue.js docs-zh-cn 翻译须知](https://github.com/vuejs-translations/docs-zh-cn/wiki/%E7%BF%BB%E8%AF%91%E9%A1%BB%E7%9F%A5) — per-term translate/keep decisions and tone.\n- [zh-style-guide](https://zh-style-guide.readthedocs.io) — a community Chinese technical-writing style guide whose rule-level taxonomy (and RFC 2119 keyword levels) this file borrows; aggregates GB/T 15834/15835, clreq, and vendor guides.\n- [W3C clreq](https://www.w3.org/TR/clreq/) and the [Microsoft Simplified Chinese style guide](https://learn.microsoft.com/en-us/globalization/reference/microsoft-style-guides) — the formal typographic and vendor-localization baselines.\n- GB/T 19682-2005《翻译服务译文质量要求》 — the national standard whose three base requirements (忠实原文、术语统一、行文通顺) this file's Faithfulness and Terminology sections operationalize.\n" + "content": "# Translation rules\n\nEnglish | [中文](translation-rules.zh.md)\n\nHow to translate between the two sides of a documentation pair in this repo. Both languages carry equal authority ([README.md](README.md)): a change is authored in either language, and that side is the source for that update — these rules govern producing or updating the counterpart. They bind humans and agents equally. Routine agent work translates the changed content directly in one terminology-guided pass; the extended [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) workflow runs only when the user explicitly invokes it. Rule levels follow RFC 2119 usage: **MUST** / **MUST NOT** are gate- or review-blocking; **SHOULD** needs a stated reason to deviate; **MAY** is discretionary.\n\n## Faithfulness\n\n- The counterpart *MUST* say what the authored side says — no added behavior, prerequisites, warnings, version claims, or examples, and no dropped ones. If the pair disagrees on substance, neither language wins by default: fix the side that is wrong, then bring the other along in the same change.\n- The counterpart *SHOULD* read as natural technical writing in its own language, not word-by-word gloss. Translate meaning, restructure sentences where the target grammar wants it, and keep the author's register — terse stays terse.\n- Do not translate the untranslatable: if a sentence resists natural rendering because it leans on an idiom of the source language, translate the idea, not the idiom.\n\n## Voice\n\n- The register is calibrated by [style-samples.md](style-samples.md) — human-approved gold pairs, one per document genre. The counterpart MUST match the target-language side of the nearest sample; where its voice and a prose voice rule disagree, the sample wins. Chinese targets use institutional technical Chinese; English targets use concise professional developer prose.\n- Write as a native technical author restating the content, not as a translator transposing sentences, while preserving every source clause: nothing added, nothing dropped — fluency never justifies losing a clause.\n- Give sentences an explicit actor when the target language would otherwise obscure it; for Chinese, replace vague passives or abstract subjects with the actual actor (系统、门禁、评审人).\n- Prefer established target-language engineering idiom over calques (误报/漏检 for false positive/negative, 执行红线 for enforcement frontier); localize metaphors instead of transplanting them, and unpack noun chains where the target language requires it.\n- Split long paragraphs by semantic unit — one idea per paragraph. Paragraph boundaries MAY differ from the source; the structural signature does not count paragraphs.\n- When translating into Chinese, category nouns use Chinese with a first-mention English annotation (实操手册(cookbook)); when translating into English, use the conventional English category name. Literal directory or file references stay code-formatted English.\n\n## Structure preservation\n\nThe pairing gate checks heading depths, fenced code blocks, table row and column counts, list kinds, ordered-list starts, list item counts, link locale, and semantic targets. Preserve the rest of the frame manually; the paired files MUST match one to one in:\n\n- heading hierarchy (same levels, same order — heading TEXT is translated),\n- list shape and numbering,\n- tables (same columns, same row order; header cells translated per terminology),\n- fenced code blocks — **byte-identical, including comments**; the pairing signature compares their info strings and contents, and ` ```ts ` blocks compile under `doc-typecheck`,\n- inline code spans (commands, flags, config keys, file paths, event names, API names, version numbers) — verbatim, never translated or reformatted,\n- links and anchors: every relative document link MUST keep the same semantic target and exact query/fragment suffix. When the target belongs to the active bilingual corpus, the English side uses its `.md` path and the Chinese side uses its `.zh.md` path; a missing counterpart in that corpus is an error, while targets outside it keep the original path. External URLs, images, and pure in-page fragments stay unchanged. The language switcher remains the explicit cross-locale exception, and a README rendered outside GitHub MAY use the canonical public repository URL to its exact counterpart as documented in [README.md](README.md). Link TEXT is translated.\n\nThe repo's Markdown conventions apply to `.zh.md` files unchanged: one physical line per paragraph (`verify-md-wrap`), resolving relative links (`verify-md-links`), exactly one trailing newline.\n\n## Terminology\n\n- [terminology.md](terminology.md) is the source of truth in both directions. Before translating, load it; every listed term MUST follow its row and its \"不要译作\" prohibitions. A Chinese target uses the \"中文\" column and its \"首次出现\" annotation; an English target uses the \"English\" column without adding a Chinese gloss.\n- For a Chinese target, an unlisted technical term MAY use an established rendering from a major Chinese-language OSS or vendor source (K8s/Vue/MDN Chinese docs, 微软简中风格指南, big-tech project docs), cited in the PR. Without such precedent it MUST stay in English and be listed under 「待定术语」(pending terms) with a suggested rendering.\n- For an English target, use the established English technical term. If the source term has no unambiguous established equivalent, preserve it with a short explanatory gloss and list it under pending terms. Neither direction may invent a rendering inline; a decided term enters [terminology.md](terminology.md) in the same PR or a follow-up.\n\n## Typography\n\nThese rules govern the Chinese side; the English side follows the repo's normal Markdown conventions (root `AGENTS.md`). The mixed-script rules below follow the cross-project consensus of the [MDN Simplified Chinese translation guide](https://github.com/mdn/translated-content/blob/main/docs/zh-cn/translation-guide.md), the [Kubernetes zh-cn localization guide](https://kubernetes.io/zh-cn/docs/contribute/localization_zh/), the [Vue.js Chinese translation conventions](https://github.com/vuejs-translations/docs-zh-cn/wiki/%E7%BF%BB%E8%AF%91%E9%A1%BB%E7%9F%A5), and [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines), which in turn ground in [W3C clreq](https://www.w3.org/TR/clreq/) and GB/T 15834—2011:\n\n- MUST put one half-width space between Chinese text and Latin words, and between Chinese text and numerals: `每个 plugin 注册 3 个 tool`。No space between a full-width punctuation mark and anything.\n- MUST use full-width (Chinese) punctuation in Chinese prose: `,。:;?!()「」`. Half-width punctuation stays inside code spans, inside complete English sentences quoted as-is, and in numbers (`3.5`, `1,024`).\n- Chinese prose *SHOULD* prefer colons, periods, commas, or parentheses over em dashes. Keep an em dash only when no other punctuation preserves the sentence naturally.\n- Enumeration commas: a Chinese list of parallel items uses 顿号(、), not commas.\n- MUST NOT use full-width digits or full-width Latin letters — `123` never, `123` always.\n- Proper nouns keep their canonical casing: GitHub, TypeScript, DeepSeek — never `github`/`Github` unless quoting code.\n- Second person is 你, not 您 (matches the Vue and Kubernetes Chinese conventions and this repo's direct voice).\n- Emphasis markers (`**bold**`, `*italic*`) stay on the same spans as the source; Chinese has no italics, so the rendered emphasis may look identical — do not substitute quotation marks or other decoration.\n\n## Quality bar\n\n- A pair is done when a bilingual engineer reading either file alone gets everything a reader of the other gets — same facts, same caveats, same tone — and nothing extra.\n- Run `pnpm run verify-translation-pairing` and the rest of `doc-sync` for records, switchers, heading depths, code blocks, table row and column counts, list kinds, ordered-list starts, list item counts, links, and repository Markdown rules. Human review owns list and table order, noncanonical list numbering, inline code, emphasis, meaning, terminology, and tone.\n\n## References\n\nAuthorities cited by these rules, for humans and agents who want the underlying reasoning:\n\n- [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines) — the de-facto community standard for mixed CJK/Latin spacing and punctuation.\n- [MDN zh-CN translation guide](https://github.com/mdn/translated-content/blob/main/docs/zh-cn/translation-guide.md) — an in-repo translation-rules file of the same shape as this one; spacing, punctuation, and glossary practice.\n- [Kubernetes zh-cn localization guide](https://kubernetes.io/zh-cn/docs/contribute/localization_zh/) — terminology-first-occurrence and punctuation practice from the largest zh localization team.\n- [Vue.js docs-zh-cn 翻译须知](https://github.com/vuejs-translations/docs-zh-cn/wiki/%E7%BF%BB%E8%AF%91%E9%A1%BB%E7%9F%A5) — per-term translate/keep decisions and tone.\n- [zh-style-guide](https://zh-style-guide.readthedocs.io) — a community Chinese technical-writing style guide whose rule-level taxonomy (and RFC 2119 keyword levels) this file borrows; aggregates GB/T 15834/15835, clreq, and vendor guides.\n- [W3C clreq](https://www.w3.org/TR/clreq/) and the [Microsoft Simplified Chinese style guide](https://learn.microsoft.com/en-us/globalization/reference/microsoft-style-guides) — the formal typographic and vendor-localization baselines.\n- GB/T 19682-2005《翻译服务译文质量要求》 — the national standard whose three base requirements (忠实原文、术语统一、行文通顺) this file's Faithfulness and Terminology sections operationalize.\n" }, { "role": "assistant", - "content": "# 翻译规则\n\n[English](translation-rules.md) | 中文\n\n本文规定:如何在本仓库文档配对的中英文两种语言之间进行翻译。两种语言同权(见 [README.md](README.zh.md)):每次变更可以用任一语言撰写,被编辑的一侧即为本次更新的源;本文的规则约束如何产出或更新对侧文件。这些规则对人类和 agent(智能体)同等生效。日常工作中,agent 会在术语指导下直接一次完成有改动内容的翻译;扩展版 [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) 工作流仅在用户显式调用时运行。规则级别沿用 RFC 2119 的用法:**必须(MUST)** / **禁止(MUST NOT)** 会卡门禁或评审;**应当(SHOULD)** 偏离时要说明理由;**可以(MAY)** 自行裁量。\n\n## 忠实性\n\n- 对侧文件*必须*传达与撰写侧相同的内容:不添加行为、前置条件、警告、版本声明或示例,也不漏掉任何一项。如果两侧在实质内容上不一致,没有哪种语言默认获胜;请修正错误的一侧,并在同一个变更里同步更新另一侧。\n- 对侧文件读起来*应当*是其语言自然的技术文字,而非逐词对照的译文。请根据语义翻译,在目标语言语法需要时重组句子,并保持原作者的语域(比如:简练的保持简练)。\n- 不要翻译不可译的内容:如果一句话依赖源语言的习语、无法自然转换,请翻译它的意思,而非习语本身。\n\n## 行文\n\n- 语体以 [style-samples.md](style-samples.md) 为校准锚点。人工定稿的金标样例按文体各一组,译文必须参照文体最接近的样例,采用其中目标语言一侧的语体;如果样例与本文的行文规则冲突,以样例为准。译成中文时,采用规范的技术制度文;译成英文时,采用简洁、专业的开发者文档语体。\n- 以母语技术作者的身份重述内容,而不是以译者身份逐句转写,同时保留原文的每个语义成分:不添加、不遗漏——流畅永远不是丢掉语义成分的理由。\n- 如果直译会让执行主体含糊,请明确写出实际执行者;译成中文时,应由「系统、门禁、评审人」等实际执行者作主语,避免含糊的被动句或抽象主语。\n- 优先采用目标语言中通行的工程表达,避免生硬直译(false positive/negative→误报/漏检、enforcement frontier→执行红线);隐喻应自然改写,名词链则按目标语言的习惯拆开。\n- 长段按语义单元拆分,一段一件事。段落边界可以与原文不同;结构签名不比对段落数。\n- 翻译为中文时,类别名词使用中文并在首现括注英文(实操手册(cookbook));翻译为英文时,使用通行的英文类别名。指目录或文件本身时保留代码体英文。\n\n## 结构保持\n\n配对门禁会检查标题深度、围栏代码块、表格行列数、列表类型、有序列表起始编号、列表项数量、链接 locale 与语义目标;门禁未覆盖的结构仍需人工核对。两个配对文件必须在以下方面一一对应:\n\n- 标题层级(相同级别、相同顺序;标题的**文字**要翻译);\n- 列表形态与编号;\n- 表格(相同的列、相同的行序;表头单元格按术语表翻译);\n- 围栏代码块:**逐字节一致,包括注释**。配对签名比对信息字符串与内容,` ```ts ` 块还要通过 `doc-typecheck` 编译;\n- 行内代码(命令、flag、配置键、文件路径、事件名、API 名、版本号):原样保留,从不翻译或重排;\n- 链接与锚点:每个相对文档链接必须保持相同的语义目标和完全相同的 query/fragment 后缀。目标存在配对兄弟文件时,英文侧使用其 `.md` 路径,中文侧使用其 `.zh.md` 路径;没有对侧时,两边都保留原目标。外部 URL、图片与纯页内 fragment 保持不变。语言切换行仍是显式跨 locale 例外;在 GitHub 以外位置渲染的 README 可以按 [README.md](README.zh.md) 的规定,使用指向确切对侧文件的规范公开仓库 URL。链接**文字**翻译。\n\n本仓库的 Markdown 约定对 `.zh.md` 文件原样生效:一个段落一个物理行(`verify-md-wrap`)、相对链接必须可解析(`verify-md-links`)、文件末尾恰好一个换行。\n\n## 术语\n\n- [terminology.md](terminology.md) 是双向的术语真源。翻译前请先加载它;表内术语必须遵守对应行与「不要译作」禁项。译成中文时,采用「中文」列,并按「首次出现」列括注;译成英文时,采用「English」列,不加中文括注。\n- 译成中文时,术语表未收录的技术术语只有在主流中文 OSS 文档或厂商资料中已有通行译法时才可以翻译(K8s/Vue/MDN 中文文档、微软简中风格指南、大厂项目文档),并须在 PR 中注明出处;否则必须保留英文,并在 PR 描述的「待定术语」中给出建议译法。\n- 译成英文时,采用通行的英文技术术语。如果源术语没有明确的通行对应词,则保留原词、附上简短说明,并列入「待定术语」。两个方向都不得自行创造译法;确定后的术语须在同一个 PR 或后续 PR 中加入 [terminology.md](terminology.md)。\n\n## 排版\n\n本节规则约束中文一侧;英文一侧遵循仓库常规的 Markdown 约定(根 `AGENTS.md`)。以下中西文混排规则遵循 [MDN 简体中文翻译指南](https://github.com/mdn/translated-content/blob/main/docs/zh-cn/translation-guide.md)、[Kubernetes 中文本地化指南](https://kubernetes.io/zh-cn/docs/contribute/localization_zh/)、[Vue.js 中文翻译须知](https://github.com/vuejs-translations/docs-zh-cn/wiki/%E7%BF%BB%E8%AF%91%E9%A1%BB%E7%9F%A5) 与[中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines)的跨项目共识,其根据是 [W3C clreq](https://www.w3.org/TR/clreq/) 与 GB/T 15834—2011:\n\n- 必须在中文与拉丁词之间、中文与数字之间各留一个半角空格:`每个 plugin 注册 3 个 tool`。全角标点与任何字符之间不加空格。\n- 中文行文必须使用全角(中文)标点:`,。:;?!()「」`。半角标点保留在代码内、按原样引用的完整英文句子内、以及数字内(`3.5`、`1,024`)。\n- 中文行文*应当*优先使用冒号、句号、逗号或括号,尽量不用破折号;只有其他标点都无法自然表达时才保留破折号。\n- 顿号:中文的并列项之间使用顿号(、),而非逗号。\n- 禁止使用全角数字或全角拉丁字母:永远不写 `123`,永远写 `123`。\n- 专有名词保持规范大小写:GitHub、TypeScript、DeepSeek。除非引用代码,否则绝不写 `github`/`Github`。\n- 第二人称用「你」,不用「您」(与 Vue、Kubernetes 中文约定及本仓库的直接语气一致)。\n- 强调标记(`**加粗**`、`*斜体*`)落在与对侧相同的文字段上。中文没有斜体,渲染效果可能看不出差别,不要用引号或其他装饰替代。\n\n## 质量标准\n\n- 一对文档的完成标准:一位双语工程师只读其中任一文件,能获得与另一文件读者完全相同的信息(相同的事实、相同的告诫、相同的语气),并且没有任何多余的内容。\n- 请运行 `pnpm run verify-translation-pairing` 与 `doc-sync` 的其余门禁。这些门禁会检查一致性记录、切换行、标题深度、代码块、表格行列数、列表类型、有序列表起始编号、列表项数量、链接及仓库 Markdown 规则;列表与表格的顺序、非常规列表编号、行内代码、强调标记、语义、术语和语体则由人工评审负责。\n\n## 参考资料\n\n本文各规则引用的权威出处,供想了解底层依据的人和 agent 查阅:\n\n- [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines):中西文混排空格与标点的社区事实标准。\n- [MDN 简体中文翻译指南](https://github.com/mdn/translated-content/blob/main/docs/zh-cn/translation-guide.md):与本文同形态的仓库内置翻译规则文件;空格、标点与术语表实践。\n- [Kubernetes 中文本地化指南](https://kubernetes.io/zh-cn/docs/contribute/localization_zh/):最大的中文本地化团队的术语首现与标点实践。\n- [Vue.js docs-zh-cn 翻译须知](https://github.com/vuejs-translations/docs-zh-cn/wiki/%E7%BF%BB%E8%AF%91%E9%A1%BB%E7%9F%A5):逐术语的译/留决策与语气。\n- [zh-style-guide](https://zh-style-guide.readthedocs.io):社区中文技术文档写作规范,本文借用了它的规则级别分类体系(与 RFC 2119 关键词分级);它聚合了 GB/T 15834/15835、clreq 与各厂商指南。\n- [W3C clreq](https://www.w3.org/TR/clreq/) 与[微软简体中文风格指南](https://learn.microsoft.com/en-us/globalization/reference/microsoft-style-guides):排版学与厂商本地化的正式基线。\n- GB/T 19682-2005《翻译服务译文质量要求》:国家标准;本文「忠实性」与「术语」两节将其三项基本要求(忠实原文、术语统一、行文通顺)落实为可操作的规则。\n" + "content": "# 翻译规则\n\n[English](translation-rules.md) | 中文\n\n本文规定:如何在本仓库文档配对的中英文两种语言之间进行翻译。两种语言同权(见 [README.md](README.zh.md)):每次变更可以用任一语言撰写,被编辑的一侧即为本次更新的源;本文的规则约束如何产出或更新对侧文件。这些规则对人类和 agent(智能体)同等生效。日常工作中,agent 会在术语指导下直接一次完成有改动内容的翻译;扩展版 [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) 工作流仅在用户显式调用时运行。规则级别沿用 RFC 2119 的用法:**必须(MUST)** / **禁止(MUST NOT)** 会卡门禁或评审;**应当(SHOULD)** 偏离时要说明理由;**可以(MAY)** 自行裁量。\n\n## 忠实性\n\n- 对侧文件*必须*传达与撰写侧相同的内容:不添加行为、前置条件、警告、版本声明或示例,也不漏掉任何一项。如果两侧在实质内容上不一致,没有哪种语言默认获胜;请修正错误的一侧,并在同一个变更里同步更新另一侧。\n- 对侧文件读起来*应当*是其语言自然的技术文字,而非逐词对照的译文。请根据语义翻译,在目标语言语法需要时重组句子,并保持原作者的语域(比如:简练的保持简练)。\n- 不要翻译不可译的内容:如果一句话依赖源语言的习语、无法自然转换,请翻译它的意思,而非习语本身。\n\n## 行文\n\n- 语体以 [style-samples.md](style-samples.md) 为校准锚点。人工定稿的金标样例按文体各一组,译文必须参照文体最接近的样例,采用其中目标语言一侧的语体;如果样例与本文的行文规则冲突,以样例为准。译成中文时,采用规范的技术制度文;译成英文时,采用简洁、专业的开发者文档语体。\n- 以母语技术作者的身份重述内容,而不是以译者身份逐句转写,同时保留原文的每个语义成分:不添加、不遗漏——流畅永远不是丢掉语义成分的理由。\n- 如果直译会让执行主体含糊,请明确写出实际执行者;译成中文时,应由「系统、门禁、评审人」等实际执行者作主语,避免含糊的被动句或抽象主语。\n- 优先采用目标语言中通行的工程表达,避免生硬直译(false positive/negative→误报/漏检、enforcement frontier→执行红线);隐喻应自然改写,名词链则按目标语言的习惯拆开。\n- 长段按语义单元拆分,一段一件事。段落边界可以与原文不同;结构签名不比对段落数。\n- 翻译为中文时,类别名词使用中文并在首现括注英文(实操手册(cookbook));翻译为英文时,使用通行的英文类别名。指目录或文件本身时保留代码体英文。\n\n## 结构保持\n\n配对门禁会检查标题深度、围栏代码块、表格行列数、列表类型、有序列表起始编号、列表项数量、链接 locale 与语义目标;门禁未覆盖的结构仍需人工核对。两个配对文件必须在以下方面一一对应:\n\n- 标题层级(相同级别、相同顺序;标题的**文字**要翻译);\n- 列表形态与编号;\n- 表格(相同的列、相同的行序;表头单元格按术语表翻译);\n- 围栏代码块:**逐字节一致,包括注释**。配对签名比对信息字符串与内容,` ```ts ` 块还要通过 `doc-typecheck` 编译;\n- 行内代码(命令、flag、配置键、文件路径、事件名、API 名、版本号):原样保留,从不翻译或重排;\n- 链接与锚点:每个相对文档链接必须保持相同的语义目标和完全相同的 query/fragment 后缀。目标属于活跃双语语料时,英文侧使用其 `.md` 路径,中文侧使用其 `.zh.md` 路径;该范围内缺少对侧属于错误,范围外的目标保留原路径。外部 URL、图片与纯页内 fragment 保持不变。语言切换行仍是显式跨 locale 例外;在 GitHub 以外位置渲染的 README 可以按 [README.md](README.zh.md) 的规定,使用指向确切对侧文件的规范公开仓库 URL。链接**文字**翻译。\n\n本仓库的 Markdown 约定对 `.zh.md` 文件原样生效:一个段落一个物理行(`verify-md-wrap`)、相对链接必须可解析(`verify-md-links`)、文件末尾恰好一个换行。\n\n## 术语\n\n- [terminology.md](terminology.md) 是双向的术语真源。翻译前请先加载它;表内术语必须遵守对应行与「不要译作」禁项。译成中文时,采用「中文」列,并按「首次出现」列括注;译成英文时,采用「English」列,不加中文括注。\n- 译成中文时,术语表未收录的技术术语只有在主流中文 OSS 文档或厂商资料中已有通行译法时才可以翻译(K8s/Vue/MDN 中文文档、微软简中风格指南、大厂项目文档),并须在 PR 中注明出处;否则必须保留英文,并在 PR 描述的「待定术语」中给出建议译法。\n- 译成英文时,采用通行的英文技术术语。如果源术语没有明确的通行对应词,则保留原词、附上简短说明,并列入「待定术语」。两个方向都不得自行创造译法;确定后的术语须在同一个 PR 或后续 PR 中加入 [terminology.md](terminology.md)。\n\n## 排版\n\n本节规则约束中文一侧;英文一侧遵循仓库常规的 Markdown 约定(根 `AGENTS.md`)。以下中西文混排规则遵循 [MDN 简体中文翻译指南](https://github.com/mdn/translated-content/blob/main/docs/zh-cn/translation-guide.md)、[Kubernetes 中文本地化指南](https://kubernetes.io/zh-cn/docs/contribute/localization_zh/)、[Vue.js 中文翻译须知](https://github.com/vuejs-translations/docs-zh-cn/wiki/%E7%BF%BB%E8%AF%91%E9%A1%BB%E7%9F%A5) 与[中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines)的跨项目共识,其根据是 [W3C clreq](https://www.w3.org/TR/clreq/) 与 GB/T 15834—2011:\n\n- 必须在中文与拉丁词之间、中文与数字之间各留一个半角空格:`每个 plugin 注册 3 个 tool`。全角标点与任何字符之间不加空格。\n- 中文行文必须使用全角(中文)标点:`,。:;?!()「」`。半角标点保留在代码内、按原样引用的完整英文句子内、以及数字内(`3.5`、`1,024`)。\n- 中文行文*应当*优先使用冒号、句号、逗号或括号,尽量不用破折号;只有其他标点都无法自然表达时才保留破折号。\n- 顿号:中文的并列项之间使用顿号(、),而非逗号。\n- 禁止使用全角数字或全角拉丁字母:永远不写 `123`,永远写 `123`。\n- 专有名词保持规范大小写:GitHub、TypeScript、DeepSeek。除非引用代码,否则绝不写 `github`/`Github`。\n- 第二人称用「你」,不用「您」(与 Vue、Kubernetes 中文约定及本仓库的直接语气一致)。\n- 强调标记(`**加粗**`、`*斜体*`)落在与对侧相同的文字段上。中文没有斜体,渲染效果可能看不出差别,不要用引号或其他装饰替代。\n\n## 质量标准\n\n- 一对文档的完成标准:一位双语工程师只读其中任一文件,能获得与另一文件读者完全相同的信息(相同的事实、相同的告诫、相同的语气),并且没有任何多余的内容。\n- 请运行 `pnpm run verify-translation-pairing` 与 `doc-sync` 的其余门禁。这些门禁会检查一致性记录、切换行、标题深度、代码块、表格行列数、列表类型、有序列表起始编号、列表项数量、链接及仓库 Markdown 规则;列表与表格的顺序、非常规列表编号、行内代码、强调标记、语义、术语和语体则由人工评审负责。\n\n## 参考资料\n\n本文各规则引用的权威出处,供想了解底层依据的人和 agent 查阅:\n\n- [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines):中西文混排空格与标点的社区事实标准。\n- [MDN 简体中文翻译指南](https://github.com/mdn/translated-content/blob/main/docs/zh-cn/translation-guide.md):与本文同形态的仓库内置翻译规则文件;空格、标点与术语表实践。\n- [Kubernetes 中文本地化指南](https://kubernetes.io/zh-cn/docs/contribute/localization_zh/):最大的中文本地化团队的术语首现与标点实践。\n- [Vue.js docs-zh-cn 翻译须知](https://github.com/vuejs-translations/docs-zh-cn/wiki/%E7%BF%BB%E8%AF%91%E9%A1%BB%E7%9F%A5):逐术语的译/留决策与语气。\n- [zh-style-guide](https://zh-style-guide.readthedocs.io):社区中文技术文档写作规范,本文借用了它的规则级别分类体系(与 RFC 2119 关键词分级);它聚合了 GB/T 15834/15835、clreq 与各厂商指南。\n- [W3C clreq](https://www.w3.org/TR/clreq/) 与[微软简体中文风格指南](https://learn.microsoft.com/en-us/globalization/reference/microsoft-style-guides):排版学与厂商本地化的正式基线。\n- GB/T 19682-2005《翻译服务译文质量要求》:国家标准;本文「忠实性」与「术语」两节将其三项基本要求(忠实原文、术语统一、行文通顺)落实为可操作的规则。\n" }, { "role": "user", - "content": "# Agent Note: Bilingual documentation via paired sibling files and a pairing gate\n\nStatus: implemented\n\nEnglish | [中文](2026-07-02-bilingual-docs-and-pairing-gate.zh.md)\n\n## Problem\n\nThis repo's documentation corpus is read by people and agents inside and outside the company, in both English and Chinese. Maintaining a second language by hand, with no mechanism, is how translations rot: one side moves on, the other silently lies, and no gate notices. The repo's standing answer to invariants of this kind is to encode them as a mechanical check (see [quality gates](2026-06-11-quality-gates.md) and [doc-sync enforcement](../../archived/process/2026-06-11-doc-sync-enforcement.md)), so the bilingual policy ships with one.\n\n## Decision\n\n- **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).\n- **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 `, which requires naming the confirmed pairs — bulk re-record is an explicit `--write --all`) produces a reviewable yaml diff: confirming consistency is an explicit, visible act in the PR.\n- **`verify-translation-pairing` joins `doc-sync`.** The gate ([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts)) enforces: every discovered, non-excluded source has a complete pair; every existing pair is complete (all three files) and consistent (both hashes match, the Chinese side and every authored English source carry their switchers while listed generated English sources are exempt, structural signatures identical); and excluded generated, instruction, or bilingual-by-construction files stay unpaired. Relative document links use the target sibling matching the source locale, while the structure signature normalizes `.md` and `.zh.md` siblings to one semantic target and retains the exact query/fragment suffix; the [localized bilingual links decision](2026-08-18-localized-bilingual-links.md) owns that refinement. [scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) contains only explicit exclusions, so no requirement can bypass discovery and receive a weaker check. 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.\n- **One corpus-wide requirement.** Every document in scope requires a complete pair from creation; the policy has no per-file rollout state, date cutoff, or README-specific class. README discovery covers every case-insensitive README basename outside vendored, dependency, and ignored build-output trees, including future top-level directories. 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.\n- **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.\n- **Translation is agent work with human review.** Routine changes use the direct one-pass path owned by the [lightweight-translation decision](2026-08-08-lightweight-routine-documentation-translation.md). The [extended translation skill](../../../skills/dsh-translate-docs/SKILL.md) retains delegated translation and the other heavier mechanisms for explicit user invocation; both paths defer to the documentation contracts as their sources of truth.\n\n## Verification\n\nThe verification contract covers each boundary independently. `verify-translation-pairing` pins pair completeness, hashes, switchers, and structure; [`project-doc-site.spec.ts`](../../../../scripts/project-doc-site.spec.ts) pins locale-specific source selection for published pairs; [`cordis-config-files.spec.ts`](../../../../scripts/cordis-config-files.spec.ts) pins discovery of Loader YAML and exclusion of translation records; and the [translation-prompt runnable snapshot](../../../../scripts/translation-prompt.snapshot.ts) pins the rendered system message, five reviewed example pairs, source request, and consumed response. Together these checks make pair drift, publication drift, configuration misclassification, and model-visible prompt drift review-visible.\n\n## Alternatives considered\n\n- **English as the canonical source with a fingerprint inside the translation** — `.zh.md` files would carry an HTML comment recording the English source's blob hash, and translation would flow EN → ZH only. Rejected: the team wants Chinese-first authoring (write and review a Chinese Agent Note, then translate to English) with the two languages holding equal authority, which a one-directional canonical model cannot express. The sidecar record covering BOTH sides replaced the in-file one-directional fingerprint; the blob-hash mechanics survived unchanged.\n- **Locale directories (`docs/en/` + `docs/zh/`, the Kubernetes/ECharts model)** — rejected: this repo has no docs-site framework to map locales to routes, moving every English file would churn every existing cross-reference, and `verify-md-links`/`verify-doc-refs` would need path-mapping logic instead of working unchanged.\n- **A separate translation repo (the PingCAP `docs`/`docs-cn` model)** — rejected: right for a docs product with independent release trains, overkill for a monorepo's own documentation; it also puts the translation outside the reach of this repo's gates.\n- **Interleaved bilingual files (single file, both languages)** — rejected: doubles every diff, breaks the one-line-per-paragraph convention's diff ergonomics, and makes partial inconsistency invisible.\n- **Commit-hash records (the MDN `l10n.sourceCommit` model)** — rejected in favor of blob hashes: a same-PR edit has no commit hash yet, so the MDN model cannot express \"consistent as of the state this PR introduces\", and verifying it requires git history instead of file content.\n- **Comparing git timestamps of the pair (no record)** — rejected: formatting-only edits would false-positive, and a counterpart committed after an unrelated edit would false-negative; content identity is the only signal that means what the gate claims.\n\n## Industry precedent\n\nPaired sibling files with locale suffixes are the dominant Chinese big-tech convention (ant-design `index.zh-CN.md`/`index.en-US.md`; arco-design `README.zh-CN.md` with a top-of-file switcher; Apache ShardingSphere's 387 `.cn.md`/`.en.md` pairs) — but none of those repos *enforce* pairing or consistency in CI; the convention holds by review alone. Consistency automation exists outside China: MDN's `l10n.sourceCommit` front-matter fingerprint, Vue's Ryu-Cho action (upstream-commit watcher that opens issues/PRs for stale translations), Kubernetes' localization drift scripts, and Microsoft's Azure co-op-translator (source-hash-driven LLM re-translation in CI). This design combines the two: the Chinese-ecosystem file layout with a hash-pair gate, plus an agent-run workflow in place of a bot service.\n\n## Consequences\n\n- Editing either side of a paired document obligates the same PR to update the counterpart and re-record the pair — the gate makes the doc-sync rule bilingual, and CI (not reviewer memory) carries the invariant.\n- Every pair adds a third file to the tree. The record is machine-written (`--write`), so the cost is directory noise, not maintenance effort; in exchange, \"who confirmed these consistent, and when\" is answerable from git blame on the yaml.\n- When the two sides disagree, no mechanical rule picks a winner — the PR review does. That is the price of equal authority, accepted deliberately: the alternative (a canonical language) forbids Chinese-first authoring.\n- Generated English documents remain derived from source and freshness-gated by their owning generators. A generated page with a reviewed Chinese counterpart participates in the three-file pairing workflow, with one structural exception: the generated English source has no language switcher because adding one would make the generator stale, while the Chinese counterpart links back to it. Generated pages without a reviewed counterpart remain explicit exclusions and use an English website projection.\n- The exclusions-only manifest makes every current and future in-scope document mandatory through the same path. There is no explicit requirement, cutoff, or class entry that can fall outside discovery while appearing enforced.\n- The recorded hashes double as the update tool: [gen-translation-brief](2026-07-26-briefed-minimal-translation-updates.md) recovers either side's last-confirmed text from them and assembles the minimal-update briefing, so re-translation of whole files is never forced by the mechanism.\n" + "content": "# Agent Note: Bilingual documentation via paired sibling files and a pairing gate\n\nStatus: implemented\n\nEnglish | [中文](2026-07-02-bilingual-docs-and-pairing-gate.zh.md)\n\n## Problem\n\nThis repo's documentation corpus is read by people and agents inside and outside the company, in both English and Chinese. Maintaining a second language by hand, with no mechanism, is how translations rot: one side moves on, the other silently lies, and no gate notices. The repo's standing answer to invariants of this kind is to encode them as a mechanical check (see [quality gates](2026-06-11-quality-gates.md) and [doc-sync enforcement](../../archived/process/2026-06-11-doc-sync-enforcement.md)), so the bilingual policy ships with one.\n\n## Decision\n\n- **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).\n- **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 `, which requires naming the confirmed pairs — bulk re-record is an explicit `--write --all`) produces a reviewable yaml diff: confirming consistency is an explicit, visible act in the PR.\n- **`verify-translation-pairing` joins `doc-sync`.** The gate ([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts)) enforces: every discovered, non-excluded source has a complete pair; every existing pair is complete (all three files) and consistent (both hashes match, the Chinese side and every authored English source carry their switchers while listed generated English sources are exempt, structural signatures identical); and excluded generated, instruction, or bilingual-by-construction files stay unpaired. Relative document links whose targets belong to that active corpus use the target sibling matching the source locale, while the structure signature normalizes `.md` and `.zh.md` siblings to one semantic target and retains the exact query/fragment suffix; the [localized bilingual links decision](2026-08-18-localized-bilingual-links.md) owns that refinement. [scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) contains only explicit exclusions, so no requirement can bypass discovery and receive a weaker check. 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.\n- **One corpus-wide requirement.** Every document in scope requires a complete pair from creation; the policy has no per-file rollout state, date cutoff, or README-specific class. README discovery covers every case-insensitive README basename outside vendored, dependency, and ignored build-output trees, including future top-level directories. 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.\n- **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.\n- **Translation is agent work with human review.** Routine changes use the direct one-pass path owned by the [lightweight-translation decision](2026-08-08-lightweight-routine-documentation-translation.md). The [extended translation skill](../../../skills/dsh-translate-docs/SKILL.md) retains delegated translation and the other heavier mechanisms for explicit user invocation; both paths defer to the documentation contracts as their sources of truth.\n\n## Verification\n\nThe verification contract covers each boundary independently. `verify-translation-pairing` pins pair completeness, hashes, switchers, and structure; [`project-doc-site.spec.ts`](../../../../scripts/project-doc-site.spec.ts) pins locale-specific source selection for published pairs; [`cordis-config-files.spec.ts`](../../../../scripts/cordis-config-files.spec.ts) pins discovery of Loader YAML and exclusion of translation records; and the [translation-prompt runnable snapshot](../../../../scripts/translation-prompt.snapshot.ts) pins the rendered system message, five reviewed example pairs, source request, and consumed response. Together these checks make pair drift, publication drift, configuration misclassification, and model-visible prompt drift review-visible.\n\n## Alternatives considered\n\n- **English as the canonical source with a fingerprint inside the translation** — `.zh.md` files would carry an HTML comment recording the English source's blob hash, and translation would flow EN → ZH only. Rejected: the team wants Chinese-first authoring (write and review a Chinese Agent Note, then translate to English) with the two languages holding equal authority, which a one-directional canonical model cannot express. The sidecar record covering BOTH sides replaced the in-file one-directional fingerprint; the blob-hash mechanics survived unchanged.\n- **Locale directories (`docs/en/` + `docs/zh/`, the Kubernetes/ECharts model)** — rejected: this repo has no docs-site framework to map locales to routes, moving every English file would churn every existing cross-reference, and `verify-md-links`/`verify-doc-refs` would need path-mapping logic instead of working unchanged.\n- **A separate translation repo (the PingCAP `docs`/`docs-cn` model)** — rejected: right for a docs product with independent release trains, overkill for a monorepo's own documentation; it also puts the translation outside the reach of this repo's gates.\n- **Interleaved bilingual files (single file, both languages)** — rejected: doubles every diff, breaks the one-line-per-paragraph convention's diff ergonomics, and makes partial inconsistency invisible.\n- **Commit-hash records (the MDN `l10n.sourceCommit` model)** — rejected in favor of blob hashes: a same-PR edit has no commit hash yet, so the MDN model cannot express \"consistent as of the state this PR introduces\", and verifying it requires git history instead of file content.\n- **Comparing git timestamps of the pair (no record)** — rejected: formatting-only edits would false-positive, and a counterpart committed after an unrelated edit would false-negative; content identity is the only signal that means what the gate claims.\n\n## Industry precedent\n\nPaired sibling files with locale suffixes are the dominant Chinese big-tech convention (ant-design `index.zh-CN.md`/`index.en-US.md`; arco-design `README.zh-CN.md` with a top-of-file switcher; Apache ShardingSphere's 387 `.cn.md`/`.en.md` pairs) — but none of those repos *enforce* pairing or consistency in CI; the convention holds by review alone. Consistency automation exists outside China: MDN's `l10n.sourceCommit` front-matter fingerprint, Vue's Ryu-Cho action (upstream-commit watcher that opens issues/PRs for stale translations), Kubernetes' localization drift scripts, and Microsoft's Azure co-op-translator (source-hash-driven LLM re-translation in CI). This design combines the two: the Chinese-ecosystem file layout with a hash-pair gate, plus an agent-run workflow in place of a bot service.\n\n## Consequences\n\n- Editing either side of a paired document obligates the same PR to update the counterpart and re-record the pair — the gate makes the doc-sync rule bilingual, and CI (not reviewer memory) carries the invariant.\n- Every pair adds a third file to the tree. The record is machine-written (`--write`), so the cost is directory noise, not maintenance effort; in exchange, \"who confirmed these consistent, and when\" is answerable from git blame on the yaml.\n- When the two sides disagree, no mechanical rule picks a winner — the PR review does. That is the price of equal authority, accepted deliberately: the alternative (a canonical language) forbids Chinese-first authoring.\n- Generated English documents remain derived from source and freshness-gated by their owning generators. A generated page with a reviewed Chinese counterpart participates in the three-file pairing workflow, with one structural exception: the generated English source has no language switcher because adding one would make the generator stale, while the Chinese counterpart links back to it. Generated pages without a reviewed counterpart remain explicit exclusions and use an English website projection.\n- The exclusions-only manifest makes every current and future in-scope document mandatory through the same path. There is no explicit requirement, cutoff, or class entry that can fall outside discovery while appearing enforced.\n- The recorded hashes double as the update tool: [gen-translation-brief](2026-07-26-briefed-minimal-translation-updates.md) recovers either side's last-confirmed text from them and assembles the minimal-update briefing, so re-translation of whole files is never forced by the mechanism.\n" }, { "role": "assistant", - "content": "# Agent Note: 通过配对兄弟文件与配对门禁实现双语文档\n\nStatus: implemented\n\n[English](2026-07-02-bilingual-docs-and-pairing-gate.md) | 中文\n\n## 问题\n\n本仓库的文档语料会被公司内外的人和 agent(智能体)以中英两种语言阅读。在没有机制的情况下纯靠手工维护第二语言,正是译文腐烂的根源:一侧持续演进,另一侧默默失实,而没有门禁能够发现。对于这类不变式,本仓库一贯的做法是将其编码为机械检查(见[质量门禁](2026-06-11-quality-gates.zh.md)与 [doc-sync(文档同步门禁)强制](../../archived/process/2026-06-11-doc-sync-enforcement.zh.md)),因此双语政策随附一道门禁一起交付。\n\n## 决策\n\n- **配对兄弟文件,两种语言同权。** 一对文档由三个兄弟文件组成:英文 `foo.md`、中文 `foo.zh.md`,以及一份一致性记录 `foo.i18n.yaml`。没有哪种语言是正典:一篇文档可以先用中文撰写和评审、之后再译成英文,反之亦可;约束配对的是:两侧必须表达相同的内容,且配对整体合并(两种语言加记录,绝不单独落一侧)。政策见 [docs/i18n/README.md](../../../../docs/i18n/README.zh.md);翻译规则见 [docs/i18n/translation-rules.md](../../../../docs/i18n/translation-rules.zh.md);术语真源见 [docs/i18n/terminology.md](../../../../docs/i18n/terminology.md)。\n- **伴随记录保存两侧 blob hash,使一致性可检查。** `foo.i18n.yaml` 保存两侧文件在上一次确认一致时各自的完整 Git blob hash。此后修改了任一侧而未重新确认配对,都能被机械检测出来(纯内容比较,无需查询历史),而且同一个 PR(Pull Request)内改动的文件也能计算出 hash,commit hash 式的记录做不到这一点。重新记录(`verify-translation-pairing --write `,要求点名所确认的配对;批量重新记录是显式的 `--write --all`)会产生一份可评审的 YAML diff:确认一致在 PR 中是一个显式、可见的动作。\n- **`verify-translation-pairing` 加入 `doc-sync`。** 门禁([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts))强制执行以下规则:每个已发现且未排除的源文档都有完整配对;每个现有配对都完整(三个文件齐全)且一致(两侧的 hash 均与记录匹配、中文侧和所有人工撰写的英文源都带语言切换行而清单内的生成英文源除外、结构签名一致);被排除的生成文档、指令文档或本身即双语的文档不得配对。相对文档链接使用与源文件 locale 相同的目标兄弟文件;结构签名则把 `.md` 与 `.zh.md` 兄弟文件规范化为同一个语义目标,并保留完全相同的 query/fragment 后缀;该细化规则由[双语文档链接本地化决策](2026-08-18-localized-bilingual-links.zh.md)负责。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 只包含显式排除项,因此任何要求都无法绕过发现流程而接受较弱的检查。只有当 `.zh.md` 围栏序列与其无后缀兄弟文件拥有顺序相同、正文按字节一致的同一组受跟踪围栏时,面向源码的代码门禁才会将其作为派生内容消费;不完整、顺序变更、重分类或已改动的序列仍会独立受检,因此由其所属的代码门禁或配对门禁报告不匹配。\n- **全语料统一要求。** 范围内的每篇文档从创建起就必须有完整配对;政策没有逐文件推进状态、日期分界或 README 专用类别。README 发现会覆盖 vendor 源码、依赖目录与被忽略的构建产物目录之外所有文件名不区分大小写匹配 README 的文件,包括今后新增的顶层目录。发布到文档站的配对使用 `pairedPages()`,由根 locale 投影 `.zh.md`,由 `/en/` 投影 `.md`;仅创建对侧文件并不会发布它。\n- **配对记录是元数据,而不是 Cordis Loader 配置。** Cordis 配置发现会接受实际的 `.cordis.yml` 和 `.cordis.yaml` 文件,同时排除 `*.i18n.yaml`,即使文档名中包含 `cordis` 也不例外。这样既能继续校验可执行的 Loader 配置项,又不会把翻译 hash 当作配置来解析。\n- **翻译是 agent 的工作,由人评审。** 常规改动采用由[轻量翻译决策](2026-08-08-lightweight-routine-documentation-translation.zh.md)确立的直接单遍路径。[扩展翻译 skill(技能)](../../../skills/dsh-translate-docs/SKILL.md)保留委派翻译和其他较重机制,供用户显式调用;两条路径均以文档契约为真源。\n\n## 验证\n\n验证约定分别覆盖每个边界。`verify-translation-pairing` 固定配对完整性、hash、语言切换行和结构;[`project-doc-site.spec.ts`](../../../../scripts/project-doc-site.spec.ts) 固定已发布配对按 locale 选择对应源文件;[`cordis-config-files.spec.ts`](../../../../scripts/cordis-config-files.spec.ts) 固定 Loader YAML 的发现以及翻译记录的排除;[翻译提示词可运行快照](../../../../scripts/translation-prompt.snapshot.ts)则固定渲染后的系统消息、五对经评审的示例、源请求和所消费的响应。这些检查共同使配对漂移、发布漂移、配置误分类和模型可见提示词漂移都可在评审中看见。\n\n## 曾考虑的替代方案\n\n- **英文为正典源、指纹放在译文内**:`.zh.md` 文件携带一条 HTML 注释记录英文源的 blob hash,翻译只沿 EN → ZH 单向流动。否决:团队需要中文先行的撰写方式(先写、先审中文 Agent Note,再译英文),两种语言同权,而单向正典模型无法表达这一点。覆盖**两侧**的伴随记录取代了文件内的单向指纹;blob hash 的机制本身保持不变。\n- **语言目录(`docs/en/` + `docs/zh/`,Kubernetes/ECharts 模式)**:否决。本仓库没有将 locale 映射到路由的文档站框架;如果移动所有英文文件,所有既有交叉引用都要随之修改;且 `verify-md-links`/`verify-doc-refs` 将需要路径映射逻辑,而非原样工作。\n- **独立翻译仓库(PingCAP `docs`/`docs-cn` 模式)**:否决。适合有独立发布节奏的文档产品,对 monorepo 自身的文档而言过重;还会把译文置于本仓库门禁触及不到的地方。\n- **中英混排单文件(一个文件、两种语言)**:否决。每个 diff 都翻倍,破坏一段一行约定的 diff 易读性,且局部不一致不可见。\n- **Commit hash 式记录(MDN `l10n.sourceCommit` 模式)**:否决,改用 blob hash。同一个 PR 内的改动还没有 commit hash,MDN 模式无法表达「与本 PR 引入的状态一致」,且校验它需要 git 历史而非文件内容。\n- **比较配对两侧的 git 时间戳(无记录)**:否决。纯格式化的改动会误报,一次无关改动之后提交的对侧文件会漏报;只有内容同一性这个信号才与门禁的承诺名实相符。\n\n## 业界先例\n\n带语言后缀的配对兄弟文件是中国大厂的主流约定(ant-design 的 `index.zh-CN.md`/`index.en-US.md`;arco-design 的 `README.zh-CN.md` 加顶部切换行;Apache ShardingSphere 的 387 对 `.cn.md`/`.en.md`),但这些仓库都没有在 CI 中**强制**配对或一致性检查;约定纯靠评审维系。一致性自动化存在于中国以外:MDN 的 `l10n.sourceCommit` front-matter 指纹、Vue 的 Ryu-Cho action(监视上游 commit,为陈旧译文自动开 issue/PR)、Kubernetes 的本地化漂移脚本、微软 Azure co-op-translator(CI 中由源 hash 驱动的 LLM 重译)。本设计将两者结合:中文生态的文件布局,加上 hash 配对门禁,再加一个由 agent 运行的工作流替代 bot 服务。\n\n## 后果\n\n- 修改已配对文档的任一侧,同一个 PR 就有义务更新对侧并重新记录配对。门禁将 doc-sync 规则双语化,不变式由 CI(而非评审者的记忆)承载。\n- 每个配对给目录树多添一个文件。记录由机器写入(`--write`),代价是目录噪音而非维护负担;换来的是「谁在何时确认过这对文档一致」可以从 yaml 的 git blame 直接回答。\n- 两侧说法冲突时,没有机械规则裁决谁赢,由 PR 评审裁决。这是同权的代价,且是有意接受的:另一个选项(正典语言)会禁止中文先行撰写。\n- 生成的英文文档仍由源码派生,并由各自的生成器实施新鲜度门禁。有经评审中文对侧的生成页面遵循三文件配对工作流,但有一项结构例外:生成的英文源文件不含语言切换行,因为添加该行会使生成器新鲜度检查失败;中文对侧仍链接回英文源。没有经评审对侧的生成页面保留为显式排除项,并在网站上投影英文。\n- 只含排除项的 manifest(元数据清单)通过同一路径,要求当前及今后纳入范围的每篇文档都必须配对。不存在显式要求、分界或类别条目可以落在发现范围之外,却看似已经强制执行。\n- 记录的 hash 兼作更新工具:[gen-translation-brief](2026-07-26-briefed-minimal-translation-updates.zh.md) 会从中还原任一侧上次确认的文本并组装最小更新简报,因此这套机制从不强迫整篇重译。\n" + "content": "# Agent Note: 通过配对兄弟文件与配对门禁实现双语文档\n\nStatus: implemented\n\n[English](2026-07-02-bilingual-docs-and-pairing-gate.md) | 中文\n\n## 问题\n\n本仓库的文档语料会被公司内外的人和 agent(智能体)以中英两种语言阅读。在没有机制的情况下纯靠手工维护第二语言,正是译文腐烂的根源:一侧持续演进,另一侧默默失实,而没有门禁能够发现。对于这类不变式,本仓库一贯的做法是将其编码为机械检查(见[质量门禁](2026-06-11-quality-gates.zh.md)与 [doc-sync(文档同步门禁)强制](../../archived/process/2026-06-11-doc-sync-enforcement.md)),因此双语政策随附一道门禁一起交付。\n\n## 决策\n\n- **配对兄弟文件,两种语言同权。** 一对文档由三个兄弟文件组成:英文 `foo.md`、中文 `foo.zh.md`,以及一份一致性记录 `foo.i18n.yaml`。没有哪种语言是正典:一篇文档可以先用中文撰写和评审、之后再译成英文,反之亦可;约束配对的是:两侧必须表达相同的内容,且配对整体合并(两种语言加记录,绝不单独落一侧)。政策见 [docs/i18n/README.md](../../../../docs/i18n/README.zh.md);翻译规则见 [docs/i18n/translation-rules.md](../../../../docs/i18n/translation-rules.zh.md);术语真源见 [docs/i18n/terminology.md](../../../../docs/i18n/terminology.md)。\n- **伴随记录保存两侧 blob hash,使一致性可检查。** `foo.i18n.yaml` 保存两侧文件在上一次确认一致时各自的完整 Git blob hash。此后修改了任一侧而未重新确认配对,都能被机械检测出来(纯内容比较,无需查询历史),而且同一个 PR(Pull Request)内改动的文件也能计算出 hash,commit hash 式的记录做不到这一点。重新记录(`verify-translation-pairing --write `,要求点名所确认的配对;批量重新记录是显式的 `--write --all`)会产生一份可评审的 YAML diff:确认一致在 PR 中是一个显式、可见的动作。\n- **`verify-translation-pairing` 加入 `doc-sync`。** 门禁([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts))强制执行以下规则:每个已发现且未排除的源文档都有完整配对;每个现有配对都完整(三个文件齐全)且一致(两侧的 hash 均与记录匹配、中文侧和所有人工撰写的英文源都带语言切换行而清单内的生成英文源除外、结构签名一致);被排除的生成文档、指令文档或本身即双语的文档不得配对。目标属于该活跃语料的相对文档链接使用与源文件 locale 相同的目标兄弟文件;结构签名则把 `.md` 与 `.zh.md` 兄弟文件规范化为同一个语义目标,并保留完全相同的 query/fragment 后缀;该细化规则由[双语文档链接本地化决策](2026-08-18-localized-bilingual-links.zh.md)负责。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 只包含显式排除项,因此任何要求都无法绕过发现流程而接受较弱的检查。只有当 `.zh.md` 围栏序列与其无后缀兄弟文件拥有顺序相同、正文按字节一致的同一组受跟踪围栏时,面向源码的代码门禁才会将其作为派生内容消费;不完整、顺序变更、重分类或已改动的序列仍会独立受检,因此由其所属的代码门禁或配对门禁报告不匹配。\n- **全语料统一要求。** 范围内的每篇文档从创建起就必须有完整配对;政策没有逐文件推进状态、日期分界或 README 专用类别。README 发现会覆盖 vendor 源码、依赖目录与被忽略的构建产物目录之外所有文件名不区分大小写匹配 README 的文件,包括今后新增的顶层目录。发布到文档站的配对使用 `pairedPages()`,由根 locale 投影 `.zh.md`,由 `/en/` 投影 `.md`;仅创建对侧文件并不会发布它。\n- **配对记录是元数据,而不是 Cordis Loader 配置。** Cordis 配置发现会接受实际的 `.cordis.yml` 和 `.cordis.yaml` 文件,同时排除 `*.i18n.yaml`,即使文档名中包含 `cordis` 也不例外。这样既能继续校验可执行的 Loader 配置项,又不会把翻译 hash 当作配置来解析。\n- **翻译是 agent 的工作,由人评审。** 常规改动采用由[轻量翻译决策](2026-08-08-lightweight-routine-documentation-translation.zh.md)确立的直接单遍路径。[扩展翻译 skill(技能)](../../../skills/dsh-translate-docs/SKILL.md)保留委派翻译和其他较重机制,供用户显式调用;两条路径均以文档契约为真源。\n\n## 验证\n\n验证约定分别覆盖每个边界。`verify-translation-pairing` 固定配对完整性、hash、语言切换行和结构;[`project-doc-site.spec.ts`](../../../../scripts/project-doc-site.spec.ts) 固定已发布配对按 locale 选择对应源文件;[`cordis-config-files.spec.ts`](../../../../scripts/cordis-config-files.spec.ts) 固定 Loader YAML 的发现以及翻译记录的排除;[翻译提示词可运行快照](../../../../scripts/translation-prompt.snapshot.ts)则固定渲染后的系统消息、五对经评审的示例、源请求和所消费的响应。这些检查共同使配对漂移、发布漂移、配置误分类和模型可见提示词漂移都可在评审中看见。\n\n## 曾考虑的替代方案\n\n- **英文为正典源、指纹放在译文内**:`.zh.md` 文件携带一条 HTML 注释记录英文源的 blob hash,翻译只沿 EN → ZH 单向流动。否决:团队需要中文先行的撰写方式(先写、先审中文 Agent Note,再译英文),两种语言同权,而单向正典模型无法表达这一点。覆盖**两侧**的伴随记录取代了文件内的单向指纹;blob hash 的机制本身保持不变。\n- **语言目录(`docs/en/` + `docs/zh/`,Kubernetes/ECharts 模式)**:否决。本仓库没有将 locale 映射到路由的文档站框架;如果移动所有英文文件,所有既有交叉引用都要随之修改;且 `verify-md-links`/`verify-doc-refs` 将需要路径映射逻辑,而非原样工作。\n- **独立翻译仓库(PingCAP `docs`/`docs-cn` 模式)**:否决。适合有独立发布节奏的文档产品,对 monorepo 自身的文档而言过重;还会把译文置于本仓库门禁触及不到的地方。\n- **中英混排单文件(一个文件、两种语言)**:否决。每个 diff 都翻倍,破坏一段一行约定的 diff 易读性,且局部不一致不可见。\n- **Commit hash 式记录(MDN `l10n.sourceCommit` 模式)**:否决,改用 blob hash。同一个 PR 内的改动还没有 commit hash,MDN 模式无法表达「与本 PR 引入的状态一致」,且校验它需要 git 历史而非文件内容。\n- **比较配对两侧的 git 时间戳(无记录)**:否决。纯格式化的改动会误报,一次无关改动之后提交的对侧文件会漏报;只有内容同一性这个信号才与门禁的承诺名实相符。\n\n## 业界先例\n\n带语言后缀的配对兄弟文件是中国大厂的主流约定(ant-design 的 `index.zh-CN.md`/`index.en-US.md`;arco-design 的 `README.zh-CN.md` 加顶部切换行;Apache ShardingSphere 的 387 对 `.cn.md`/`.en.md`),但这些仓库都没有在 CI 中**强制**配对或一致性检查;约定纯靠评审维系。一致性自动化存在于中国以外:MDN 的 `l10n.sourceCommit` front-matter 指纹、Vue 的 Ryu-Cho action(监视上游 commit,为陈旧译文自动开 issue/PR)、Kubernetes 的本地化漂移脚本、微软 Azure co-op-translator(CI 中由源 hash 驱动的 LLM 重译)。本设计将两者结合:中文生态的文件布局,加上 hash 配对门禁,再加一个由 agent 运行的工作流替代 bot 服务。\n\n## 后果\n\n- 修改已配对文档的任一侧,同一个 PR 就有义务更新对侧并重新记录配对。门禁将 doc-sync 规则双语化,不变式由 CI(而非评审者的记忆)承载。\n- 每个配对给目录树多添一个文件。记录由机器写入(`--write`),代价是目录噪音而非维护负担;换来的是「谁在何时确认过这对文档一致」可以从 yaml 的 git blame 直接回答。\n- 两侧说法冲突时,没有机械规则裁决谁赢,由 PR 评审裁决。这是同权的代价,且是有意接受的:另一个选项(正典语言)会禁止中文先行撰写。\n- 生成的英文文档仍由源码派生,并由各自的生成器实施新鲜度门禁。有经评审中文对侧的生成页面遵循三文件配对工作流,但有一项结构例外:生成的英文源文件不含语言切换行,因为添加该行会使生成器新鲜度检查失败;中文对侧仍链接回英文源。没有经评审对侧的生成页面保留为显式排除项,并在网站上投影英文。\n- 只含排除项的 manifest(元数据清单)通过同一路径,要求当前及今后纳入范围的每篇文档都必须配对。不存在显式要求、分界或类别条目可以落在发现范围之外,却看似已经强制执行。\n- 记录的 hash 兼作更新工具:[gen-translation-brief](2026-07-26-briefed-minimal-translation-updates.zh.md) 会从中还原任一侧上次确认的文本并组装最小更新简报,因此这套机制从不强迫整篇重译。\n" }, { "role": "user", diff --git a/scripts/translation-links.spec.ts b/scripts/translation-links.spec.ts index d941e42106..43b23dc100 100644 --- a/scripts/translation-links.spec.ts +++ b/scripts/translation-links.spec.ts @@ -8,6 +8,7 @@ import { normalizeTranslationMarkdownLinks, rewriteTranslationLinkLocales, translationLinkLocaleViolations, + type TranslationLinkContext, } from './translation-links.ts' import { removeFixtureSafely } from './test-fixture-cleanup.ts' @@ -21,6 +22,7 @@ function fixture(): string { const root = mkdtempSync(join(tmpdir(), 'dsh-translation-links-')) roots.push(root) mkdirSync(join(root, 'docs/section'), { recursive: true }) + mkdirSync(join(root, 'packages'), { recursive: true }) writeFileSync(join(root, 'docs/guide.md'), '# Guide\n') writeFileSync(join(root, 'docs/guide.zh.md'), '# 指南\n') writeFileSync(join(root, 'docs/reference.md'), '# Overview\n') @@ -28,11 +30,26 @@ function fixture(): string { writeFileSync(join(root, 'docs/unpaired.md'), '# Only\n') writeFileSync(join(root, 'docs/section/index.md'), '# Section\n') writeFileSync(join(root, 'docs/section/index.zh.md'), '# 章节\n') + writeFileSync(join(root, 'packages/outside.md'), '# Outside\n') + writeFileSync(join(root, 'packages/outside.zh.md'), '# 范围外\n') return root } +function linkContext( + root: string, + sourcePath: string, + repositoryFileExists?: (repoPath: string) => boolean, +): TranslationLinkContext { + return { + repoRoot: root, + sourcePath, + isTranslationPairSource: path => path.startsWith('docs/'), + ...(repositoryFileExists === undefined ? {} : { repositoryFileExists }), + } +} + function expectUnchangedLinkInput(root: string, input: string): void { - const context = { repoRoot: root, sourcePath: 'docs/guide.md' } + const context = linkContext(root, 'docs/guide.md') expect(translationLinkLocaleViolations(input, context)).toEqual([]) expect(rewriteTranslationLinkLocales(input, context)).toEqual({ content: input, rewritten: 0 }) expect(normalizeTranslationMarkdownLinks(input, context)).toBe(input) @@ -43,7 +60,7 @@ describe('translation link locale validation', () => { const root = fixture() expect(translationLinkLocaleViolations( '# 指南\n\n正文。\n\n[概览](reference.md?view=full#overview)\n', - { repoRoot: root, sourcePath: 'docs/guide.zh.md' }, + linkContext(root, 'docs/guide.zh.md'), )).toEqual([{ sourcePath: 'docs/guide.zh.md', line: 5, @@ -52,19 +69,27 @@ describe('translation link locale validation', () => { }]) }) - it('accepts the target-locale sibling and an unpaired target', () => { + it('accepts the target-locale sibling and an out-of-scope target with its own sibling', () => { const root = fixture() expect(translationLinkLocaleViolations( - '[paired](reference.zh.md) [unpaired](unpaired.md)\n', - { repoRoot: root, sourcePath: 'docs/guide.zh.md' }, + '[paired](reference.zh.md) [outside](../packages/outside.md)\n', + linkContext(root, 'docs/guide.zh.md'), )).toEqual([]) }) + it('does not fall back when an active target is missing its locale sibling', () => { + const root = fixture() + expect(translationLinkLocaleViolations( + '[missing](unpaired.md)\n', + linkContext(root, 'docs/guide.zh.md'), + )[0]).toMatchObject({ expectedUrl: 'unpaired.zh.md' }) + }) + it('requires English sources to use the English sibling', () => { const root = fixture() expect(translationLinkLocaleViolations( '[Reference](reference.zh.md)\n', - { repoRoot: root, sourcePath: 'docs/guide.md' }, + linkContext(root, 'docs/guide.md'), )[0]).toMatchObject({ url: 'reference.zh.md', expectedUrl: 'reference.md', @@ -75,7 +100,7 @@ describe('translation link locale validation', () => { const root = fixture() expect(translationLinkLocaleViolations( '[Section](section/)\n', - { repoRoot: root, sourcePath: 'docs/guide.md' }, + linkContext(root, 'docs/guide.md'), )).toEqual([]) }) @@ -83,7 +108,7 @@ describe('translation link locale validation', () => { const root = fixture() expect(translationLinkLocaleViolations( '# 指南\n\n[English](guide.md) | 中文\n', - { repoRoot: root, sourcePath: 'docs/guide.zh.md' }, + linkContext(root, 'docs/guide.zh.md'), ['guide.md'], )).toEqual([]) }) @@ -93,7 +118,7 @@ describe('translation link locale validation', () => { const markdown = '# 指南\n\n[English](guide.md) | 中文\n\n[正文](guide.md)\n' expect(translationLinkLocaleViolations( markdown, - { repoRoot: root, sourcePath: 'docs/guide.zh.md' }, + linkContext(root, 'docs/guide.zh.md'), ['guide.md'], )).toEqual([{ sourcePath: 'docs/guide.zh.md', @@ -103,7 +128,7 @@ describe('translation link locale validation', () => { }]) expect(rewriteTranslationLinkLocales( markdown, - { repoRoot: root, sourcePath: 'docs/guide.zh.md' }, + linkContext(root, 'docs/guide.zh.md'), ['guide.md'], ).content).toBe('# 指南\n\n[English](guide.md) | 中文\n\n[正文](guide.zh.md)\n') }) @@ -112,36 +137,33 @@ describe('translation link locale validation', () => { const root = fixture() expect(translationLinkLocaleViolations( '[章节](section/)\n', - { repoRoot: root, sourcePath: 'docs/guide.zh.md' }, + linkContext(root, 'docs/guide.zh.md'), )[0]).toMatchObject({ expectedUrl: 'section/index.zh.md' }) expect(normalizeTranslationMarkdownLinks( '[Section](section/)\n', - { repoRoot: root, sourcePath: 'docs/guide.md' }, + linkContext(root, 'docs/guide.md'), )).toBe(normalizeTranslationMarkdownLinks( '[Section](section/index.zh.md)\n', - { repoRoot: root, sourcePath: 'docs/guide.zh.md' }, + linkContext(root, 'docs/guide.zh.md'), )) }) - it('uses the selected repository content plane for sibling discovery', () => { + it('uses the selected content plane for target existence without deriving scope from siblings', () => { const root = fixture() const staged = new Set(['docs/reference.md', 'docs/reference.zh.md']) expect(translationLinkLocaleViolations( '[概览](reference.md)\n', - { - repoRoot: root, - sourcePath: 'docs/guide.zh.md', - repositoryFileExists: path => staged.has(path), - }, + linkContext(root, 'docs/guide.zh.md', path => staged.has(path)), )).toHaveLength(1) staged.delete('docs/reference.zh.md') expect(translationLinkLocaleViolations( '[概览](reference.md)\n', - { - repoRoot: root, - sourcePath: 'docs/guide.zh.md', - repositoryFileExists: path => staged.has(path), - }, + linkContext(root, 'docs/guide.zh.md', path => staged.has(path)), + )).toHaveLength(1) + staged.delete('docs/reference.md') + expect(translationLinkLocaleViolations( + '[概览](reference.md)\n', + linkContext(root, 'docs/guide.zh.md', path => staged.has(path)), )).toEqual([]) }) }) @@ -152,7 +174,7 @@ describe('translation link rewriting and normalization', () => { const input = '[概览](reference.md?view=full&mode=all#overview "reference.md title")\n' expect(rewriteTranslationLinkLocales( input, - { repoRoot: root, sourcePath: 'docs/guide.zh.md' }, + linkContext(root, 'docs/guide.zh.md'), )).toEqual({ content: '[概览](reference.zh.md?view=full&mode=all#overview "reference.md title")\n', rewritten: 1, @@ -163,7 +185,7 @@ describe('translation link rewriting and normalization', () => { const root = fixture() expect(rewriteTranslationLinkLocales( '[概览][ref]\n\n[ref]: "title"\n', - { repoRoot: root, sourcePath: 'docs/guide.zh.md' }, + linkContext(root, 'docs/guide.zh.md'), ).content).toBe('[概览][ref]\n\n[ref]: "title"\n') }) @@ -183,14 +205,14 @@ describe('translation link rewriting and normalization', () => { it('normalizes only paired locale paths and retains other bytes', () => { const root = fixture() - const english = '[Reference](reference.md#overview) [Only](unpaired.md)\n' - const chinese = '[Reference](reference.zh.md#overview) [Only](unpaired.md)\n' + const english = '[Reference](reference.md#overview) [Outside](../packages/outside.md)\n' + const chinese = '[Reference](reference.zh.md#overview) [Outside](../packages/outside.md)\n' expect(normalizeTranslationMarkdownLinks( english, - { repoRoot: root, sourcePath: 'docs/guide.md' }, + linkContext(root, 'docs/guide.md'), )).toBe(normalizeTranslationMarkdownLinks( chinese, - { repoRoot: root, sourcePath: 'docs/guide.zh.md' }, + linkContext(root, 'docs/guide.zh.md'), )) }) @@ -200,10 +222,10 @@ describe('translation link rewriting and normalization', () => { const literal = '[Reference](reference.zh.md?x=1&y=2#overview)\n' expect(normalizeTranslationMarkdownLinks( escaped, - { repoRoot: root, sourcePath: 'docs/guide.md' }, + linkContext(root, 'docs/guide.md'), )).not.toBe(normalizeTranslationMarkdownLinks( literal, - { repoRoot: root, sourcePath: 'docs/guide.zh.md' }, + linkContext(root, 'docs/guide.zh.md'), )) }) }) diff --git a/scripts/translation-links.ts b/scripts/translation-links.ts index 698c814d42..8208307583 100644 --- a/scripts/translation-links.ts +++ b/scripts/translation-links.ts @@ -18,6 +18,8 @@ export interface TranslationLinkContext { repoRoot: string /** Repository-relative Markdown source path. */ sourcePath: string + /** Whether an English Markdown path belongs to the active bilingual corpus. */ + isTranslationPairSource: (sourcePath: string) => boolean /** Selected content plane; defaults to regular files in the working tree. */ repositoryFileExists?: (repoPath: string) => boolean } @@ -146,9 +148,8 @@ function translationPairTarget(targetPath: string, context: TranslationLinkConte const source = targetPath.endsWith('.zh.md') ? targetPath.replace(/\.zh\.md$/, '.md') : targetPath.endsWith('.md') ? targetPath : undefined - if (source === undefined) return undefined + if (source === undefined || !context.isTranslationPairSource(source)) return undefined const zh = source.replace(/\.md$/, '.zh.md') - if (!repositoryFileExists(context, source) || !repositoryFileExists(context, zh)) return undefined return { source, zh } } diff --git a/scripts/translation-pairing-merge.spec.ts b/scripts/translation-pairing-merge.spec.ts index 8eed9a9dfc..19ad75af7c 100644 --- a/scripts/translation-pairing-merge.spec.ts +++ b/scripts/translation-pairing-merge.spec.ts @@ -16,8 +16,8 @@ import { fileURLToPath } from 'node:url' import { afterEach, describe, expect, it } from 'vitest' import { gitBlobHash, storeGitBlob } from './translation-pairing-git.ts' import { - mergeTranslationPairingRecords, - resolveTranslationPairingConflicts, + mergeTranslationPairingRecords as mergeTranslationPairingRecordsWithScope, + resolveTranslationPairingConflicts as resolveTranslationPairingConflictsWithScope, } from './translation-pairing-merge.ts' import { renderTranslationPairingRecord, @@ -36,6 +36,27 @@ interface Fixture { root: string } +function mergeTranslationPairingRecords( + root: string, + metaPath: string, + ancestorRecord: string, + currentRecord: string, + otherRecord: string, +) { + return mergeTranslationPairingRecordsWithScope( + root, + metaPath, + ancestorRecord, + currentRecord, + otherRecord, + () => true, + ) +} + +function resolveTranslationPairingConflicts(root: string): string[] { + return resolveTranslationPairingConflictsWithScope(root, () => true) +} + afterEach(() => { for (const fixture of fixtures.splice(0)) removeFixtureSafely(fixture) }) @@ -253,6 +274,19 @@ describe('translation pairing merge composition', { timeout: 15_000 }, () => { )).toThrow('pairing record escapes the repository') }) + it('rejects a pairing record excluded from the active corpus', () => { + const fixture = createFixture(false) + + expect(() => mergeTranslationPairingRecordsWithScope( + fixture.root, + 'docs/guide.i18n.yaml', + '', + '', + '', + () => false, + )).toThrow('docs/guide.i18n.yaml is excluded from the active bilingual documentation corpus') + }) + it('merges the owner blobs named by three valid records', () => { const fixture = createFixture(false) git(fixture, ['config', 'merge.default', 'text']) diff --git a/scripts/translation-pairing-merge.ts b/scripts/translation-pairing-merge.ts index 243c76ec9d..dbf1141763 100644 --- a/scripts/translation-pairing-merge.ts +++ b/scripts/translation-pairing-merge.ts @@ -16,7 +16,9 @@ import { isTranslationScopeFile, languageSwitcherTargets, parseTranslationMarkdown, + parseTranslationPairingManifest, requiresSourceLanguageSwitcher, + translationPairSourcePredicate, translationStructureDiff, translationStructureSignature, } from './translation-pairing.ts' @@ -166,7 +168,13 @@ function loadRecordOwners( } } -function assertMergedPairStructure(root: string, paths: TranslationPairPaths, source: Buffer, zh: Buffer): void { +function assertMergedPairStructure( + root: string, + paths: TranslationPairPaths, + source: Buffer, + zh: Buffer, + isTranslationPairSource: (sourcePath: string) => boolean, +): void { const sourceText = source.toString('utf8') const zhText = zh.toString('utf8') const sourceTree = parseTranslationMarkdown(sourceText) @@ -186,11 +194,13 @@ function assertMergedPairStructure(root: string, paths: TranslationPairPaths, so ...translationLinkLocaleViolations(sourceText, { repoRoot: root, sourcePath: paths.source, + isTranslationPairSource, repositoryFileExists, }, zhSwitcherTargets), ...translationLinkLocaleViolations(zhText, { repoRoot: root, sourcePath: paths.zh, + isTranslationPairSource, repositoryFileExists, }, sourceSwitcherTargets), ] @@ -203,12 +213,14 @@ function assertMergedPairStructure(root: string, paths: TranslationPairPaths, so translationStructureSignature(sourceTree, zhSwitcherTargets, { repoRoot: root, sourcePath: paths.source, + isTranslationPairSource, repositoryFileExists, markdown: sourceText, }), translationStructureSignature(zhTree, sourceSwitcherTargets, { repoRoot: root, sourcePath: paths.zh, + isTranslationPairSource, repositoryFileExists, markdown: zhText, }), @@ -248,19 +260,23 @@ export function mergeTranslationPairingRecords( ancestorRecord: string, currentRecord: string, otherRecord: string, + isTranslationPairSource: (sourcePath: string) => boolean, ): TranslationPairingMergeResult { const normalizedMeta = normalizeMetaPath(root, metaPath) if (!isTranslationScopeFile(normalizedMeta)) { throw new Error(`${normalizedMeta} is outside the active bilingual documentation corpus`) } const paths = translationPairPathsFromMeta(normalizedMeta) + if (!isTranslationPairSource(paths.source)) { + throw new Error(`${normalizedMeta} is excluded from the active bilingual documentation corpus`) + } assertDefaultTextMerge(root, paths) const ancestor = loadRecordOwners(root, 'ancestor', ancestorRecord, paths) const current = loadRecordOwners(root, 'current', currentRecord, paths) const other = loadRecordOwners(root, 'other', otherRecord, paths) const sourceContent = mergeBlobTriplet(root, paths.source, ancestor.source, current.source, other.source) const zhContent = mergeBlobTriplet(root, paths.zh, ancestor.zh, current.zh, other.zh) - assertMergedPairStructure(root, paths, sourceContent, zhContent) + assertMergedPairStructure(root, paths, sourceContent, zhContent, isTranslationPairSource) const sourceHash = storeGitBlob(root, sourceContent) const zhHash = storeGitBlob(root, zhContent) return { @@ -272,6 +288,16 @@ export function mergeTranslationPairingRecords( } } +/** Read the repository manifest and return its active bilingual-source predicate. */ +export function repositoryTranslationPairSource(root: string): (sourcePath: string) => boolean { + const path = 'scripts/translation-pairing.manifest.json' + const content = readGitIndexBlob(root, path)?.content ?? readFileSync(join(root, path)) + const manifest = parseTranslationPairingManifest( + content.toString('utf8'), + ) + return translationPairSourcePredicate(manifest) +} + function unmergedSidecars(root: string): Map { const output = runGit(root, ['ls-files', '--unmerged', '-z'], 'listing unresolved merge entries').toString('utf8') const records = new Map() @@ -325,7 +351,10 @@ function assertUneditedSidecar( * @param root - Repository root with an in-progress merge-like operation. * @returns Repository-relative sidecar paths resolved and staged. */ -export function resolveTranslationPairingConflicts(root: string): string[] { +export function resolveTranslationPairingConflicts( + root: string, + isTranslationPairSource: (sourcePath: string) => boolean, +): string[] { const resolutions: { path: string; record: string }[] = [] const failures: { path: string; reason: string }[] = [] for (const [metaPath, stages] of [...unmergedSidecars(root)].sort(([left], [right]) => left.localeCompare(right))) { @@ -343,6 +372,7 @@ export function resolveTranslationPairingConflicts(root: string): string[] { ancestorRecord, currentRecord, otherRecord, + isTranslationPairSource, ) const paths = translationPairPathsFromMeta(metaPath) if (readGitIndexBlob(root, paths.source)?.objectId !== result.sourceHash) { diff --git a/scripts/translation-pairing.spec.ts b/scripts/translation-pairing.spec.ts index f7e9a279a1..587b71e9b8 100644 --- a/scripts/translation-pairing.spec.ts +++ b/scripts/translation-pairing.spec.ts @@ -18,6 +18,7 @@ import { } from './translation-pairing-record.ts' import { blobHash, + isTranslationPairingManifestExcluded, isTranslationScopeFile, languageSwitcherTargets, pairAnchorOfArgument, @@ -26,15 +27,21 @@ import { parseTranslationPairingManifest, partitionGeneratedRegions, requiresSourceLanguageSwitcher, + translationPairSourcePredicate, translationStructureDiff, translationStructureSignature, } from './translation-pairing.ts' +const fixturePairSource = (): boolean => true + function signature(markdown: string) { return translationStructureSignature( parseTranslationMarkdown(markdown), 'counterpart.zh.md', - { repoRoot: process.cwd(), sourcePath: 'counterpart.md', markdown }, + { + repoRoot: process.cwd(), sourcePath: 'counterpart.md', + isTranslationPairSource: fixturePairSource, markdown, + }, ) } @@ -147,11 +154,16 @@ describe('translation pairing snapshots', () => { describe('translation pairing manifest', () => { it('accepts an exclusions-only manifest', () => { - expect(parseTranslationPairingManifest(JSON.stringify({ + const manifest = parseTranslationPairingManifest(JSON.stringify({ excluded: ['docs/generated/'], - }))).toEqual({ + })) + expect(manifest).toEqual({ excluded: ['docs/generated/'], }) + expect(isTranslationPairingManifestExcluded('docs/generated/page.md', manifest)).toBe(true) + expect(translationPairSourcePredicate(manifest)('docs/generated/page.md')).toBe(false) + expect(translationPairSourcePredicate(manifest)('docs/guide.md')).toBe(true) + expect(translationPairSourcePredicate(manifest)('packages/example/guide.md')).toBe(false) }) it.each([ @@ -192,11 +204,13 @@ describe('translation pairing switchers', () => { expect(translationStructureSignature(canonical, targets, { repoRoot: process.cwd(), sourcePath: 'python/sdk/README.md', + isTranslationPairSource: fixturePairSource, markdown: canonicalMarkdown, }).links).toEqual([]) expect(translationStructureSignature(wrongPath, targets, { repoRoot: process.cwd(), sourcePath: 'python/sdk/README.md', + isTranslationPairSource: fixturePairSource, markdown: wrongMarkdown, }).links).toEqual([ 'https://github.com/deepseek-ai/deepseek-harness/blob/master/other/README.zh.md', @@ -212,7 +226,10 @@ describe('translation pairing switchers', () => { expect(translationStructureSignature( parseTranslationMarkdown(markdown), languageSwitcherTargets('guide.md'), - { repoRoot: root, sourcePath: 'guide.zh.md', markdown }, + { + repoRoot: root, sourcePath: 'guide.zh.md', + isTranslationPairSource: fixturePairSource, markdown, + }, ).links).toEqual(['dsh-translation-target:guide.md']) } finally { rmSync(root, { recursive: true, force: true }) @@ -305,12 +322,18 @@ describe('translation structural signature', () => { const source = translationStructureSignature( parseTranslationMarkdown(sourceMarkdown), 'guide.zh.md', - { repoRoot: root, sourcePath: 'guide.md', markdown: sourceMarkdown }, + { + repoRoot: root, sourcePath: 'guide.md', + isTranslationPairSource: fixturePairSource, markdown: sourceMarkdown, + }, ) const counterpart = translationStructureSignature( parseTranslationMarkdown(counterpartMarkdown), 'guide.md', - { repoRoot: root, sourcePath: 'guide.zh.md', markdown: counterpartMarkdown }, + { + repoRoot: root, sourcePath: 'guide.zh.md', + isTranslationPairSource: fixturePairSource, markdown: counterpartMarkdown, + }, ) expect(translationStructureDiff(source, counterpart)).toEqual([]) } finally { @@ -335,7 +358,10 @@ describe('translation structural signature', () => { expect(translationStructureSignature( parseTranslationMarkdown(markdown), 'guide.zh.md', - { repoRoot: root, sourcePath: 'guide.md', markdown }, + { + repoRoot: root, sourcePath: 'guide.md', + isTranslationPairSource: fixturePairSource, markdown, + }, ).links).toEqual(['dsh-translation-target:reference.md']) } finally { rmSync(root, { recursive: true, force: true }) diff --git a/scripts/translation-pairing.ts b/scripts/translation-pairing.ts index 35e7155fa8..3f30ac4c1c 100644 --- a/scripts/translation-pairing.ts +++ b/scripts/translation-pairing.ts @@ -218,6 +218,22 @@ export function parseTranslationPairingManifest(content: string): TranslationPai return { excluded: excludedField(record) } } +/** Whether a manifest entry excludes one exact file or a directory subtree. */ +export function isTranslationPairingManifestExcluded( + file: string, + manifest: TranslationPairingManifest, +): boolean { + return manifest.excluded.some(entry => (entry.endsWith('/') ? file.startsWith(entry) : file === entry)) +} + +/** Build the active bilingual-source predicate shared by every link consumer. */ +export function translationPairSourcePredicate( + manifest: TranslationPairingManifest, +): (sourcePath: string) => boolean { + return sourcePath => isTranslationScopeFile(sourcePath) + && !isTranslationPairingManifestExcluded(sourcePath, manifest) +} + /** * Normalize one CLI pair argument to its English anchor path: any of the * pair's three files (`foo.md`, `foo.zh.md`, `foo.i18n.yaml`) or the bare diff --git a/scripts/translation-prompt.spec.ts b/scripts/translation-prompt.spec.ts index acf35ae90c..96ddc98991 100644 --- a/scripts/translation-prompt.spec.ts +++ b/scripts/translation-prompt.spec.ts @@ -59,6 +59,8 @@ describe('translation prompt rendering', () => { expect(rendered).toContain('Never invent responsibility merely to avoid a passive construction') expect(rendered).toContain('Never vary a terminology-table form, defined concept, or contract verb merely for stylistic variety') expect(rendered).toContain('Chinese output uses its `.zh.md` path') + expect(rendered).toContain('belongs to the active bilingual corpus') + expect(rendered).toContain('a missing counterpart in that corpus is an error') expect(rendered).toContain('exact query/fragment suffix') expect(rendered).toContain('Return exactly three raw XML sections') }) diff --git a/scripts/verify-translation-pairing.ts b/scripts/verify-translation-pairing.ts index ac9a0aea23..5b2b4052a4 100644 --- a/scripts/verify-translation-pairing.ts +++ b/scripts/verify-translation-pairing.ts @@ -30,8 +30,10 @@ import { parseTranslationPairingManifest, partitionGeneratedRegions, requiresSourceLanguageSwitcher, + isTranslationPairingManifestExcluded, isTranslationScopeFile, TRANSLATION_SCOPE_GLOB_EXCLUDES, + translationPairSourcePredicate, translationStructureDiff, translationStructureSignature, } from './translation-pairing.ts' @@ -86,6 +88,7 @@ if (manifestContent === undefined) { throw new Error('scripts/translation-pairing.manifest.json is missing from the selected content plane') } const manifest = parseTranslationPairingManifest(manifestContent.toString('utf8')) +const isTranslationPairSource = translationPairSourcePredicate(manifest) /** * An excluded entry ending in `/` excludes the whole directory. The trailing @@ -94,7 +97,7 @@ const manifest = parseTranslationPairingManifest(manifestContent.toString('utf8' * manifest must keep their trailing slash. */ function isExcluded(file: string): boolean { - return manifest.excluded.some(entry => (entry.endsWith('/') ? file.startsWith(entry) : file === entry)) + return isTranslationPairingManifestExcluded(file, manifest) } // Enumerate the scope once: the whole corpus, or exactly the named pairs' @@ -250,11 +253,13 @@ for (const source of [...pairAnchors].sort()) { ...translationLinkLocaleViolations(sourceText, { repoRoot: root, sourcePath: source, + isTranslationPairSource, repositoryFileExists, }, zhSwitcherTargets), ...translationLinkLocaleViolations(zhText, { repoRoot: root, sourcePath: zh, + isTranslationPairSource, repositoryFileExists, }, sourceSwitcherTargets), ]) { @@ -279,11 +284,13 @@ for (const source of [...pairAnchors].sort()) { const normalizedSourceRegions = sourceRegions.regions.map(region => normalizeTranslationMarkdownLinks(region, { repoRoot: root, sourcePath: source, + isTranslationPairSource, repositoryFileExists, })) const normalizedZhRegions = zhRegions.regions.map(region => normalizeTranslationMarkdownLinks(region, { repoRoot: root, sourcePath: zh, + isTranslationPairSource, repositoryFileExists, })) if (normalizedSourceRegions.length !== normalizedZhRegions.length @@ -304,12 +311,14 @@ for (const source of [...pairAnchors].sort()) { translationStructureSignature(sourceTree, zhSwitcherTargets, { repoRoot: root, sourcePath: source, + isTranslationPairSource, repositoryFileExists, markdown: sourceText, }), translationStructureSignature(zhTree, sourceSwitcherTargets, { repoRoot: root, sourcePath: zh, + isTranslationPairSource, repositoryFileExists, markdown: zhText, }),