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 c5eaeebcc6..07eb13b5cd 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 @@ -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-08-native-windows-pull-request-ci.md -2026-08-08-native-windows-pull-request-ci.md: 5a3cec04cc0a0869f0c7a9d7370a2ceb765ba4bd -2026-08-08-native-windows-pull-request-ci.zh.md: 18247369d6d48e402dcf6a1e2b9c5db1951a6a01 +2026-08-08-native-windows-pull-request-ci.md: 6a62fddb79670c3ab4cc0446796dbffd7130aed9 +2026-08-08-native-windows-pull-request-ci.zh.md: 990b8ed1434934337b8ff20c5f3be2c03cd9c61b diff --git a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md index 5a3cec04cc..6a62fddb79 100644 --- a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md +++ b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md @@ -24,7 +24,7 @@ The 16-core allocation is the measured capacity point for this inventory. Relati The first native run exposed two failures hidden by the compatibility lane. Documentation projection tests derived an image basename by splitting only on `/`; they now use Node's platform basename. Chokidar consumers received `%TEMP%` through the `C:\\Users\\RUNNER~1` 8.3 alias while libuv returned the long directory name, tripping its Windows event-path assertion. Shared settings and credentials watchers, plus Cordis module and exact-config HMR, now canonicalize the existing native watch base or deepest existing ancestor before opening the watcher and preserve a missing suffix, while file access and diagnostics retain the configured path. Module HMR attaches listeners and awaits the main watcher's ready event before plugin startup settles, so an immediate post-boot edit cannot race the initial scan. HMR acceptance derives expected identities through the same asynchronous native realpath operation, avoiding a synchronous Windows spelling that can retain the 8.3 alias. -Portable filesystem fixtures derive paths with `node:path`, compare native realpath identities, preserve file URLs at Node launcher boundaries, normalize only API-owned separators or line endings, and use filenames legal on every host. POSIX-only signal, mode-bit, unreadability, and writer-lock cases are platform-gated; portable failure contracts instead assert structured error codes, rollback, last-good state, atomic replacement, and absence of temporary residue through conflicts available on every host. Credentials permission validation uses an invalid-path fixture whose pre-lookup `ERR_INVALID_ARG_VALUE` is non-absence on every host, rather than depending on whether a file ancestor produces `ENOTDIR` or `ENOENT`. Stress and integration workloads keep their original assertions and receive explicit bounded time budgets where Windows instrumentation or process teardown can exceed Vitest's default ceiling. +Portable filesystem fixtures derive paths with `node:path`, compare native realpath identities, preserve file URLs at Node launcher boundaries, normalize only API-owned separators or line endings, and use filenames legal on every host. POSIX-only signal, mode-bit, unreadability, and writer-lock cases are platform-gated; portable failure contracts instead assert structured error codes, rollback, last-good state, atomic replacement, and absence of temporary residue through conflicts available on every host. Credentials permission validation uses an invalid-path fixture whose pre-lookup `ERR_INVALID_ARG_VALUE` is non-absence on every host, rather than depending on whether a file ancestor produces `ENOTDIR` or `ENOENT`. Worker-death fixtures drive real termination from the host after observing their protocol preconditions instead of calling `process.exit()` inside a nested Windows Worker; this preserves the worker-exit contract without exposing the enclosing Vitest fork to Node's process-wide native exit assertion. Stress and integration workloads keep their original assertions and receive explicit bounded time budgets where Windows instrumentation or process teardown can exceed Vitest's default ceiling. Native watchers use `canonicalizeWatchPath()` to realpath the deepest existing ancestor, prove it is an enumerable directory when a suffix is missing, and restore that suffix. This prevents Windows 8.3 aliases from being mixed with long-form libuv events and preserves `ENOTDIR` for a regular-file ancestor on every host. Settings, credentials, skill roots, and Cordis HMR retain configured paths for discovery and diagnostics; module HMR uses the canonical spelling for Node's load-cache identity, attaches listeners, and awaits its main watcher before plugin startup settles, so an immediate post-boot edit cannot race the initial scan. A skill root that is itself a symbolic link remains unexpanded when `watchFollowSymlinks: false`, allowing Chokidar to enforce that boundary. 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 18247369d6..990b8ed143 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 @@ -24,7 +24,7 @@ Status: implemented 首次原生运行暴露出两项被兼容性通道掩盖的故障。文档投影测试此前只按 `/` 拆分来派生图片 basename;现在改为使用 Node 根据平台计算的 basename。Chokidar 消费方收到的 `%TEMP%` 以 `C:\\Users\\RUNNER~1` 这个 8.3 别名表示,而 libuv 返回的是长目录名,导致其 Windows 事件路径断言失败。共享的设置 watcher 与凭据 watcher,以及 Cordis 的模块 HMR(热模块替换)与精确配置 HMR,现在都会在打开 watcher 前规范化现有的原生监听基准路径或层级最深的现有祖先路径,并保留尚不存在的后缀;文件访问和诊断仍使用配置路径。模块 HMR 会挂接监听器并等待主 watcher 的 ready 事件,之后插件启动才会完成,因此启动后立即发生的编辑无法与初始扫描形成竞态。HMR 验收通过相同的异步原生 realpath 操作派生预期身份,避免同步 Windows 路径写法仍保留 8.3 别名。 -可移植文件系统 fixture(测试前置数据)通过 `node:path` 派生路径、比较原生 realpath 标识、在 Node 启动器边界保留文件 URL,只规范化由 API 负责的分隔符或行尾,并使用每个宿主均允许的文件名。仅适用于 POSIX 的信号、模式位、不可读状态和 writer lock 场景按平台设门禁;可移植故障约定则通过每个宿主均可构造的冲突,断言结构化错误码、回滚、最后有效状态、原子替换及不存在临时残留。凭据权限验证采用无效路径 fixture;该路径在每个宿主上都会于系统查找前产生表示“非缺失”的 `ERR_INVALID_ARG_VALUE`,而不依赖文件祖先究竟产生 `ENOTDIR` 还是 `ENOENT`。压力与集成工作负载保留原有断言;如果 Windows 插桩或进程拆卸可能超过 Vitest 默认上限,就为其设置显式的有界时间预算。 +可移植文件系统 fixture(测试前置数据)通过 `node:path` 派生路径、比较原生 realpath 标识、在 Node 启动器边界保留文件 URL,只规范化由 API 负责的分隔符或行尾,并使用每个宿主均允许的文件名。仅适用于 POSIX 的信号、模式位、不可读状态和 writer lock 场景按平台设门禁;可移植故障约定则通过每个宿主均可构造的冲突,断言结构化错误码、回滚、最后有效状态、原子替换及不存在临时残留。凭据权限验证采用无效路径 fixture;该路径在每个宿主上都会于系统查找前产生表示“非缺失”的 `ERR_INVALID_ARG_VALUE`,而不依赖文件祖先究竟产生 `ENOTDIR` 还是 `ENOENT`。worker 死亡 fixture 会先观察其协议前置条件,再由宿主触发真实终止,而不在嵌套 Windows Worker 中调用 `process.exit()`;这样既保留了 worker 退出约定,也不会让外围 Vitest fork 暴露于 Node 进程级的原生退出断言。压力与集成工作负载保留原有断言;如果 Windows 插桩或进程拆卸可能超过 Vitest 默认上限,就为其设置显式的有界时间预算。 原生 watcher 使用 `canonicalizeWatchPath()` 对层级最深的现有祖先执行 realpath 解析;后缀缺失时,先证明该祖先是可枚举目录,再拼回后缀。这可避免 Windows 8.3 别名与长格式 libuv 事件混用,并让所有宿主在祖先为普通文件时都保留 `ENOTDIR`。设置、凭据、skill(技能)根与 Cordis HMR(热模块替换)在发现和诊断时保留配置路径;模块 HMR 则使用规范写法作为 Node 加载缓存标识、挂接监听器并在插件启动完成前等待主 watcher 就绪,因此启动后立即发生的编辑不会与初始扫描形成竞态。`watchFollowSymlinks: false` 时,若 skill 根本身是符号链接,系统不会展开最后这一级链接,从而让 Chokidar 强制执行该边界。 diff --git a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.i18n.yaml b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.i18n.yaml index 9c9cb9b476..04c3e6a7df 100644 --- a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.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-09-committed-artifact-citations.md -2026-08-09-committed-artifact-citations.md: 31b2d52b423a080579b3ca95cf077859d0bf4c91 -2026-08-09-committed-artifact-citations.zh.md: b69bb8743d10e2d1e194905d6d0de2d3d3667b1d +2026-08-09-committed-artifact-citations.md: 181378aab12a4423d9ac5216bac5304f3cfca11c +2026-08-09-committed-artifact-citations.zh.md: 36f32bb1fd0d6991919959e1ebe6482bf7b19b32 diff --git a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md index 31b2d52b42..181378aab1 100644 --- a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md +++ b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md @@ -18,7 +18,7 @@ Durable prose — comments, JSDoc, docs, notes, test comments and titles — cit - Implemented notes state shipped reality: a "deferred to a later PR" claim whose target shipped names the shipped note instead. - Recorded fixtures, snapshots, and archived notes are exempt: recorded model output and sealed history keep their original voice. Inside a note's change-story sections, a historical stage name ("the first cut shipped X") is current-state-safe; indexical stamps ("this cut") stay banned everywhere. -One repo-wide purge applied these rules across the prose surfaces, including the generator-owned templates (`scripts/gen-doc-graphs.ts`, `scripts/gen-tool-catalog.ts`, the typert generator's page notice) with regeneration, the type-equiv source JSDoc with page re-pastes, and the bilingual counterparts with pair re-records. +One repo-wide purge applied these rules across the prose surfaces, including the generator-owned templates (`scripts/gen-doc-graphs.ts`, `scripts/gen-tool-catalog.ts`, the typert generator's page notice) with regeneration, the type-equiv source JSDoc with page re-pastes, and the bilingual counterparts with pair re-records. The [dsh-trim-cot-leakage skill](../../../skills/dsh-trim-cot-leakage/SKILL.md) operationalizes these rules: the audit taxonomy, the committed recall batteries, and few-shot calibration for the keep/delete boundary. ## Alternatives considered @@ -28,7 +28,7 @@ One repo-wide purge applied these rules across the prose surfaces, including the ## Verification -The audit's grep batteries (English and Chinese, comments and prose, `--hidden` for `.agents/`) return no design-ordinal citations outside recorded fixtures and archived notes; `verify-type-equiv`, the `gen-*` freshness checks, and `verify-translation-pairing` pin the regenerated and re-recorded surfaces. Coverage gap: no gate rejects a new ordinal citation — review owns the rule. +The audit's grep batteries (English and Chinese, comments and prose, `--hidden` for `.agents/`) return no design-ordinal citations outside recorded fixtures, archived notes, and the trim skill's own calibration examples; `verify-type-equiv`, the `gen-*` freshness checks, and `verify-translation-pairing` pin the regenerated and re-recorded surfaces. Coverage gap: no gate rejects a new ordinal citation — review owns the rule. ## Consequences diff --git a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.zh.md b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.zh.md index b69bb8743d..36f32bb1fd 100644 --- a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.zh.md +++ b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.zh.md @@ -18,7 +18,7 @@ Status: implemented - 已实现的 Agent Note 陈述已交付的现实:「推迟到后续 PR」的说法若其目标已经交付,就改为点名那篇已交付的 note。 - 已录制的 fixture(测试前置数据)、快照与已归档的 Agent Note 不受此约束:已录制的模型输出与封存的历史保持原有行文。在 note 的变更故事段落内,历史阶段名称(「首版交付了 X」)属于安全的现状表述;指示性切次戳("this cut")在任何地方都仍被禁止。 -一次全仓库清理把这些规则应用到了各个行文表面,包括生成器持有的模板(`scripts/gen-doc-graphs.ts`、`scripts/gen-tool-catalog.ts`、typert 生成器的页面提示语,改后重新生成)、type-equiv 源码 JSDoc(改后把文档页重新粘贴)以及双语对侧文件(改后重新记录配对)。 +一次全仓库清理把这些规则应用到了各个行文表面,包括生成器持有的模板(`scripts/gen-doc-graphs.ts`、`scripts/gen-tool-catalog.ts`、typert 生成器的页面提示语,改后重新生成)、type-equiv 源码 JSDoc(改后把文档页重新粘贴)以及双语对侧文件(改后重新记录配对)。[dsh-trim-cot-leakage 技能](../../../skills/dsh-trim-cot-leakage/SKILL.md)把这些规则落地为可执行工作流:审计分类法、已提交的成批召回检索,以及校准保留/删除边界的少样本示例。 ## 曾考虑的替代方案 @@ -28,7 +28,7 @@ Status: implemented ## 验证 -审计的成批 grep 检索(英文与中文、注释与行文、对 `.agents/` 加 `--hidden`)在已录制 fixture 与已归档 Agent Note 之外没有命中任何设计序号引用;`verify-type-equiv`、各 `gen-*` 新鲜度检查与 `verify-translation-pairing` 把重新生成与重新记录的表面固定下来。覆盖缺口:没有门禁会拒绝新的序号引用,这条规则由评审把关。 +审计的成批 grep 检索(英文与中文、注释与行文、对 `.agents/` 加 `--hidden`)在已录制 fixture、已归档 Agent Note 与该清理技能自带的校准示例之外没有命中任何设计序号引用;`verify-type-equiv`、各 `gen-*` 新鲜度检查与 `verify-translation-pairing` 把重新生成与重新记录的表面固定下来。覆盖缺口:没有门禁会拒绝新的序号引用,这条规则由评审把关。 ## 后果 diff --git a/.agents/skills/dsh-doc-standards/SKILL.md b/.agents/skills/dsh-doc-standards/SKILL.md index 47b5725c18..dcbf2f2b21 100644 --- a/.agents/skills/dsh-doc-standards/SKILL.md +++ b/.agents/skills/dsh-doc-standards/SKILL.md @@ -37,12 +37,11 @@ Then check constraints that make placement expensive or wrong: After the structural pass, hunt the standard's slop checklist with the cheapest probes first. Verify and fetch the PR's live base, then run `pnpm --silent run change-scope --base ` to identify committed and dirty paths before applying semantic judgment. After a retarget or base merge, rerun the report and audit prose introduced by the new base. 1. Measure: `pnpm run verify-doc-budgets --list`, then `git ls-files '*.md' ':(exclude)vendor/**' | xargs wc -w | sort -rn | head -30` to spot unbudgeted outliers. -2. Hunt narrated history: `rg -n "no longer|used to|previously|was moved|renamed" --glob '*.md' --glob '*.ts' --glob '!vendor/**'` and keep only contrasts against a live alternative. Keep the vendor exclusion last so include globs cannot override it. -3. Inspect long comments for reasoning transcripts: control-flow narration, test walkthroughs, proof of obvious branches, review findings, rejected local alternatives, and the same rationale repeated beside sibling methods. Preserve only a non-obvious contract or durable rationale; otherwise delete the comment. -4. Hunt duplication by grepping distinctive phrases. Keep one home and replace other copies with links. -5. Replace hand-written catalogs, test/status inventories, and JSDoc restatements with the authoritative tree, script, or generated reference. -6. In `implemented/` Agent Notes, remove migration plans, acceptance-task checklists, and future-tense spec language. Keep concise verification contracts that identify the behaviors and tiers pinning the shipped decision, plus named coverage gaps. -7. If removing prose changes a promised behavior rather than its explanation, use a proposed Agent Note first (follow [dsh-find-simplifications](../dsh-find-simplifications/SKILL.md)). +2. Hunt reasoning-transcript leakage — narrated history, dead design-session citations, review choreography, control-flow narration, test walkthroughs — with [dsh-trim-cot-leakage](../dsh-trim-cot-leakage/SKILL.md), which owns the taxonomy, recall batteries, and the keep/delete boundary. Preserve only a non-obvious contract or durable rationale; the same rationale repeated beside sibling methods keeps one home. +3. Hunt duplication by grepping distinctive phrases. Keep one home and replace other copies with links. +4. Replace hand-written catalogs, test/status inventories, and JSDoc restatements with the authoritative tree, script, or generated reference. +5. In `implemented/` Agent Notes, remove migration plans, acceptance-task checklists, and future-tense spec language. Keep concise verification contracts that identify the behaviors and tiers pinning the shipped decision, plus named coverage gaps. +6. If removing prose changes a promised behavior rather than its explanation, use a proposed Agent Note first (follow [dsh-find-simplifications](../dsh-find-simplifications/SKILL.md)). Exclude `.agents/notes/archived/` from corpus audits and edits. Active prose may repair, redirect, or delete an inbound link, but never follow an archive-wide cleanup into the frozen target. diff --git a/.agents/skills/dsh-prose-standard/SKILL.md b/.agents/skills/dsh-prose-standard/SKILL.md index 26de553023..2381d459c2 100644 --- a/.agents/skills/dsh-prose-standard/SKILL.md +++ b/.agents/skills/dsh-prose-standard/SKILL.md @@ -5,7 +5,7 @@ description: Use when writing, reviewing, restoring, trimming, or auditing prose # DeepSeek Harness Prose Standard -Write enough to preserve the contract, then remove reasoning transcripts, repetition, and decoration. This skill owns editorial judgment and required prose coverage; use [dsh-doc-standards](../dsh-doc-standards/SKILL.md) for placement, budgets, bilingual pairs, and documentation gates. It is guidance, not a script. +Write enough to preserve the contract, then remove reasoning transcripts, repetition, and decoration. This skill owns editorial judgment and required prose coverage; use [dsh-doc-standards](../dsh-doc-standards/SKILL.md) for placement, budgets, bilingual pairs, and documentation gates, and [dsh-trim-cot-leakage](../dsh-trim-cot-leakage/SKILL.md) for hunting and fixing reasoning-transcript leakage. It is guidance, not a script. Comments describe non-obvious contracts or rationale that code cannot express; they do not restate what code already implies. diff --git a/.agents/skills/dsh-trim-cot-leakage/SKILL.md b/.agents/skills/dsh-trim-cot-leakage/SKILL.md new file mode 100644 index 0000000000..1ce776c28e --- /dev/null +++ b/.agents/skills/dsh-trim-cot-leakage/SKILL.md @@ -0,0 +1,45 @@ +--- +name: dsh-trim-cot-leakage +description: Use when auditing or fixing prose that reads like a leaked reasoning transcript — dead design-session citations such as (decision N), audit item codes, or §N of uncommitted drafts; change narration such as "used to", "no longer", "this cut"; stack or review vantage ("a later PR in this stack", "rejected in review"); reviewer-addressed justifications; control-flow narration; or hedged planning residue in comments, JSDoc, docs, or Agent Notes. +--- + +# Trimming Chain-of-Thought Leakage + +Chain-of-thought leakage is prose whose vantage is the authoring session rather than the repository: it cites artifacts only that session could see, narrates the change instead of the state, or argues with a reviewer who has left. The fix is never deletion alone — restate every factual clause so it stands at HEAD, then delete the transcript around it. **REQUIRED BACKGROUND:** [dsh-prose-standard](../dsh-prose-standard/SKILL.md) owns the complete-proposition rule this skill applies; the [committed-artifact-citations note](../../notes/implemented/process/2026-08-09-committed-artifact-citations.md) owns the citation rule's rationale. It is guidance, not a script. + +## The one test + +For every suspect passage ask: **could a reader at HEAD, with no access to any session transcript, PR thread, or uncommitted draft, resolve every reference and verify every claim?** If yes, it is not leakage, however historical it sounds. If no, restate the surviving facts from the repository's vantage and delete the rest. + +## Taxonomy + +1. **Dead design-session citations** — `(decision 7)`, `(audit C2)`, `design §4.7`, `plan §1.4`, phase labels (`T4`, `W3`, `P-I`), "the design ledger", "(B ruling)". If the decision has a committed owner, cite it by name and path; otherwise delete the citation and restate its factual clause to stand alone. +2. **Stack and PR vantage** — "a later PR in this stack", "this PR adds", "the previous commit". State the shipped mechanism or the extension point; deferred work moves to a `TODO` marker or an issue reference. +3. **Change narration and version stamps** — "used to", "no longer", "the old X", and indexical stamps ("v1", "this cut", "today", "now" contrasting with a past state). State the present behavior; a fixed regression becomes a present-tense counterfactual ("without X, Y happens"), never repo history ("used to Y"). +4. **Review choreography** — "Rejected in review:", "the reviewer confirmed", draft ordinals ("v5 of this note"), round attributions. Keep the surviving decision and rationale as plain fact; delete who said it when. +5. **Reviewer-addressed justification** — "the cast is safe — it simply…", "this is correct because…". A comment arguing its own correctness addresses a reviewer, not a maintainer. State the invariant that makes the code safe, or delete the comment if the code shows it. +6. **Restatement and derivation transcripts** — control-flow narration ("first we X, then we Y"), test walkthroughs, proofs of obvious branches. Delete; keep only a non-obvious contract or invariant. +7. **Hedges and planning residue** — "probably fine for now", "should be enough", deferrals with no marker. Promote to `TODO`/`FIXME` or restate as the actual bound; delete the hedge. +8. **Authoring-language slips** — untranslated working-language fragments (端, 设计稿, `---- 私有 ----` separators) in prose whose language is otherwise English, or the reverse in a zh counterpart. Translate or delete. + +## What is not leakage + +The citation boundary is where unaided passes fail in both directions — deleting durable references and keeping dead ones. Apply these keeps as written; [examples](references/examples.md) calibrates each: + +- **Issue references** — `#1470`, `TODO(name):`, "issue #N owns the follow-up" resolve at HEAD; keep them on any surface, including READMEs. Do not relocate them to Agent Notes. +- **Merged-PR and issue citations inside Agent Notes and postmortems** — sanctioned evidence per the [documentation standard](../../../docs/AGENTS.md)'s change-story routing. +- **Suppression justifications** — `oxlint-disable … -- reason`, coverage-ignore reasons, empty-catch explanations are required prose; fix a false reason, never delete it. +- **Counterfactual-present regression pins** — "without X, Y happens", "a naive X would…". +- **Measured bounds** — "(measured: 512 nests ≈ 0.15s)" calibrating a constant; the provenance word "measured" is load-bearing. +- **Runtime old/new states** — "the old connection drains before the new one accepts" is runtime lifecycle, not change history. +- **Historical stage names inside a note's change-story sections** — "the first cut shipped X" is current-state-safe there; indexical stamps ("this cut") stay banned everywhere. +- **External references that resolve outside the repo by design** — standards sections (RFC 9110 §10.1.5), Figma frame names; the §-ban covers uncommitted internal drafts, not external standards or committed docs that own their §-numbering. +- **Project voice and genre forms** — "we" as project voice; a note's Alternatives-considered section. + +## Workflow + +1. Scope and exclusions per [dsh-prose-standard](../dsh-prose-standard/SKILL.md): require an explicit scope; never touch `vendor/`, `.agents/notes/archived/`, or recorded fixtures and snapshots — recorded model output and sealed history keep their original voice. +2. Audit read-only first: run the [recall batteries](references/recall-batteries.md) (with `--hidden` so `.agents/` is searched), then judge every hit semantically. The batteries are probes, not the definition — each review round of the original purge surfaced shapes the batteries missed, so also read the densest prose in scope (module JSDoc, READMEs, Agent Notes) without a pattern in hand. +3. Fix owner-first per surface: generated catalogs → fix the source JSDoc or generator template, then regenerate; type-equivalence fences → fix the source JSDoc, then re-paste both bilingual pages (`verify-type-equiv` pins them); bilingual pairs → update the counterpart and re-record per [dsh-translate-docs](../dsh-translate-docs/SKILL.md); model-visible strings → wording is behavior, so flag for a snapshot-backed change instead of silently rewording. +4. Before deleting anything, enumerate the passage's propositions (prose-standard) and check the [overcorrection traps](references/examples.md#overcorrection-traps): trims that flip an obligation into an endorsement, promote a hypothetical to a shipped feature, delete a true fact, or drop provenance. +5. Verify: re-run the batteries expecting only sanctioned keeps and this skill's own references; confirm every remaining citation resolves at HEAD; run the gates for touched surfaces (`doc-sync` for docs, `verify-type-equiv`, `verify-translation-pairing`). diff --git a/.agents/skills/dsh-trim-cot-leakage/references/examples.md b/.agents/skills/dsh-trim-cot-leakage/references/examples.md new file mode 100644 index 0000000000..f478d90b13 --- /dev/null +++ b/.agents/skills/dsh-trim-cot-leakage/references/examples.md @@ -0,0 +1,253 @@ +# Few-shot leakage examples + +Distilled from the 2026-08 repo-wide purge and its review rounds. Use them to identify the governing principle, not as text templates. This file deliberately quotes leaked shapes as calibration material — the [recall batteries](recall-batteries.md) exclude this directory, and its wording is not a license elsewhere. + +## Dead citations + +### Decision ordinal with a committed owner + +**Leaked:** "Slash input resolves against the visible catalog (decision 21)." + +**Fixed:** "Slash input resolves against the visible catalog — the plain-text-reference decision, owned by the web input-machine note." + +The ordinal resolves nowhere at HEAD; the decision's name and owning note path do. Name the owning note's path at least once per file; later mentions may use the searchable name alone. + +### Decision ordinal without an owner + +**Leaked:** "The registry rejects duplicate names (decision 7: names are flat, no namespacing)." + +**Fixed:** "The registry rejects duplicate names; names are flat, with no namespacing." + +No committed artifact owns "decision 7", so the citation is deleted — but its factual clause (flat names) is restated to stand alone, not deleted with it. + +### Audit item codes + +**Leaked:** "Rendering is pure: same snapshot, same string (audit R3)." + +**Fixed:** "Rendering is pure: same snapshot, same string." + +There is no audit document in the repo; the code is pure session shorthand carrying zero propositions. + +### Section numbers of uncommitted drafts + +**Leaked:** "Layering follows the design (v2 §3.2): `src/core/` is the pure core." + +**Fixed:** "Layering: `src/core/` is the pure core." + +`§N` of a draft nobody committed is unresolvable. Contrast: "escapes per RFC 9110 §10.1.5" stays — an external standard resolves outside the repo by design, and a committed doc that owns its §-numbering may be cited by section. + +### Plan-phase labels + +**Leaked:** "`src/client/` is the shell (T4); the P-I migration owns the adapters." + +**Fixed:** "`src/client/` is the shell; the adapters live in `src/client/adapters/`." + +Phase labels index a plan that never landed. Replace the label with what the phase produced. + +## Stack and PR vantage + +### Stack position in durable prose + +**Leaked:** "A future remote backend implements this interface (the sandbox backend is a later PR in this stack)." + +**Fixed:** "A remote backend can implement this interface without changing the render layer." + +Durable prose cannot see the stack. Keep the extension-point contract; the pending work's home is the PR itself, a `TODO`, or an issue. + +### "This PR" in a README + +**Leaked:** "This PR adds cursor-based pagination to the session list." + +**Fixed:** "The session list paginates by cursor." + +A README outlives every PR; state the mechanism as current fact. + +## Change narration and version stamps + +### War story with a PR number + +**Leaked:** "Colors used to come from `--widget-*` tokens, which nothing defined, so it always rendered the fallbacks; the alias tokens fixed that (PR #88)." + +**Fixed:** "Colors come from the alias tokens; an undefined token renders the fallbacks." + +Both live facts survive — the current mechanism and the standing failure behavior — restated in the present. The bug's biography belongs to the PR and its Agent Note. + +### Removal narration + +**Leaked:** "The `probe` field is gone with the removal cut; badges ride the generic projection pair now." + +**Fixed:** "Badges use the generic projection pair." + +Readers who never saw `probe` learn nothing from its absence. "Now" contrasting with a deleted past is a version stamp. + +### Fixed regression → counterfactual present + +**Leaked:** "This used to double-encode multibyte labels." + +**Fixed:** "Without the byte-length guard, multibyte labels double-encode." + +The regression pin survives as a present-tense counterfactual that names the guard; "used to" pins it to repo archaeology instead. + +### Indexical version stamps + +**Leaked:** "Batch rendering is synchronous this cut; the async path is roadmap work." + +**Fixed:** "Batch rendering is synchronous." (The deferral lives in `TODO(widget-batch):` at the call site.) + +"This cut" / "v1" / "today" go stale the moment they merge. A historical stage name inside an Agent Note's change-story section ("the first cut shipped X") is current-state-safe; the indexical form never is. + +## Review choreography + +### Review verdicts as prose + +**Leaked:** "Rejected in review: caching the resolved spec. We keep resolution per-call." + +**Fixed (in an Agent Note's Alternatives considered):** "**Caching the resolved spec.** Rejected: the spec depends on per-call cwd, so a cache keyed by request would serve stale roots." + +The alternatives-considered genre is the sanctioned home; the reviewer and the round are not part of the rationale. + +### Draft ordinals + +**Leaked:** "As of v5 of this note, the loader also validates manifests." + +**Fixed:** "The loader validates manifests." + +An implemented note states shipped reality; its own revision history lives in git. + +## Reviewer-addressed justification + +### Arguing a cast + +**Leaked:** "The cast is safe — the SDK constructed the object, it simply doesn't declare the optionals strictly enough." + +**Fixed:** "The SDK constructs this object with every optional populated; the declared type is looser than the runtime guarantee." + +State the invariant a maintainer must not break. "It simply…" is a voice answering an objection nobody at HEAD raised. If the invariant is visible in the code, delete the comment instead. + +### Appeal to review authority + +**Leaked:** "This is correct because the reviewer confirmed the wrapping order." + +**Fixed:** (deleted; the wrapping order is stated in the function's `@returns`.) + +Correctness claims cite invariants or tests, never people. + +## Restatement and derivation + +### Control-flow narration + +**Leaked:** "First we normalize the label, then we truncate it, then we wrap it." + +**Fixed:** (deleted.) + +The three lines below the comment say the same thing in code. + +### Test walkthrough + +**Leaked:** "This test creates a session, sends two messages, waits for the second reply, and then asserts the log has four entries." + +**Fixed:** "Two round-trips must produce exactly four log entries — the projection dedupes the shared prefix." + +Keep only the non-obvious assertion rationale; the walkthrough restates the test body. + +## Hedges and planning residue + +### Unmarked deferral + +**Leaked:** "Probably fine to render eagerly for now." + +**Fixed:** (deleted; the deferral already has its `TODO(widget-batch):` marker.) + +A hedge without an owner is planning residue. If no marker exists, write one (`TODO(name): coalesce per animation frame`) instead of keeping the hedge. + +### Vague sizing + +**Leaked:** "A 64 KiB buffer should be enough for most cases." + +**Fixed:** "64 KiB holds the largest observed frame (48 KiB) with headroom; a larger frame fails loudly in `decode`." + +Replace the hedge with the actual bound and the failure behavior when it is exceeded. + +## Authoring-language slips + +**Leaked:** "The renderer runs on the client 端; see the 设计稿 for spacing. ---- 私有 ----" + +**Fixed:** "The renderer runs on the client side; spacing follows the Figma frame `widget-badges`." + +Working-language fragments and session separators are transcription residue. The Figma frame name stays: external provenance that resolves outside the repo by design. + +## Keeps + +### Issue references are durable on every surface + +**Keep:** "The cap applies to the complete rendered value, wrappers included (issue #1470 owns the follow-up)." + +An unaided pass deleted this, reasoning that issue citations belong in Agent Notes. Wrong direction: issues resolve at HEAD from any surface, and "#N owns the follow-up" is the sanctioned home for deferred work in a README. What Agent Notes and postmortems additionally sanction is citing *merged PRs* as evidence. + +### Dead name-drops are not "naming the owner" + +**Delete:** "Badge renderer over the widget seam (see the widget-rendering RFC)." + +An unaided pass kept this as "naming the owning document by topic". The test is resolvability, not form: no committed file answers to "the widget-rendering RFC", so the pointer is dead. Retarget it to the committed owner if one exists; otherwise delete it. + +### Suppression justifications + +**Keep (after fixing):** `// oxlint-disable-next-line no-non-null-assertion -- the one-element literal guarantees index 0.` + +The justification clause is required prose. When the stated reason is false (the original said "the loop guard above proves a frame exists" with no loop in sight), fix the reason; never delete it. + +### Measured bounds + +**Keep:** "Depth cap (measured: 512 nests ≈ 0.15s synchronous; 4096 blocks the loop)." + +The measurement pins the constant against uninformed retuning, and "measured" is the provenance that distinguishes data from a guess. + +### Runtime old/new is not change history + +**Keep:** "The old connection drains before the new one accepts." + +"Old" and "new" here name two live runtime objects during handover, not repository states. The change-narration ban is about repo history, not lifecycle vocabulary. + +## Overcorrection traps + +Every trap below shipped in the original purge and was caught in review. Enumerate a passage's propositions before trimming it. + +### Flipping an obligation into an endorsement + +**Original:** "These direct registrations are exceptions pending migration to slots." + +**Overcorrected:** "These direct registrations are sanctioned exceptions." + +**Right:** "These direct registrations are exceptions pending migration to slots." + +"Pending migration" is an obligation; "sanctioned" blesses the status quo. The trim inverted the sentence's modality while shortening it. + +### Promoting a hypothetical to a shipped feature + +**Original:** "A future IPC-based shell subclasses the executor and overrides `spawn`." + +**Overcorrected:** "An IPC-based shell subclasses the executor and overrides `spawn`." + +**Right:** "A hypothetical IPC-based shell — no such shell exists — would subclass the executor and override `spawn`." + +Deleting the future-marker alone turns a design illustration into a claim that the class ships. Mark the hypothetical explicitly instead of just unmarking the future. + +### Deleting a true fact with the transcript around it + +**Original:** "The gate notice narrates the check order; the notice text is also what `verify-doc-typecheck` compiles against." + +**Overcorrected:** "…" (whole sentence deleted as narration.) + +**Right:** "The notice text is what `verify-doc-typecheck` compiles against." + +Half the sentence was narration; the other half was a load-bearing coupling. Delete clauses, not sentences, when propositions share a line. + +### Dropping provenance while keeping the number + +**Original:** "The 4 MiB ceiling is measured: the largest generated `py-types` module is 3.1 MiB." + +**Overcorrected:** "The ceiling is 4 MiB; the largest generated `py-types` module is 3.1 MiB." + +**Right:** keep "measured". + +Without "measured" the 3.1 MiB reads as a definition rather than an observation, and nobody re-measures before raising the ceiling. diff --git a/.agents/skills/dsh-trim-cot-leakage/references/recall-batteries.md b/.agents/skills/dsh-trim-cot-leakage/references/recall-batteries.md new file mode 100644 index 0000000000..bacb434d16 --- /dev/null +++ b/.agents/skills/dsh-trim-cot-leakage/references/recall-batteries.md @@ -0,0 +1,42 @@ +# Recall batteries + +Probes for [the taxonomy](../SKILL.md#taxonomy), tuned during the 2026-08 purge. Every hit needs semantic judgment — the batteries over-match by design, and they under-match by nature: each review round of the purge found shapes no battery caught, so pair them with an unpatterned read of the densest prose in scope. + +## Invocation rules + +- Add `--hidden --glob '!.git/**'` so `.agents/` is searched; ripgrep skips dot-directories by default and the purge's biggest miss risk was Agent Notes. +- Exclusions go last so a later include cannot re-admit them: `--glob '!vendor/**' --glob '!node_modules/**' --glob '!.agents/notes/archived/**' --glob '!.agents/skills/dsh-trim-cot-leakage/references/**'` (this directory quotes leaked shapes as calibration), plus recorded fixture and snapshot directories in scope. +- A zero-hit pattern proves nothing until you have seen it match: test it against a known-positive string before trusting the negative. + +## English battery + +```sh +rg -n --hidden '\(decision \d|\(audit [A-Z]\d|design §|plan §|design ledger|\(B ruling|\bP-I\b|\bW\d\b|\bT\d\b' ... +rg -n --hidden 'this PR|this branch|this stack|later PR|previous commit|this commit' ... +rg -n --hidden 'used to |no longer|previously|the old |was renamed|was moved' ... +rg -n --hidden '\bv1\b|this cut|\bcut \d|\btoday\b|\bfor now\b|roadmap' ... +rg -n --hidden -i 'rejected in review|review round|reviewer|as of v\d' ... +rg -n --hidden 'probably |should be enough|should suffice|it simply|is safe —|is safe --' ... +rg -n --hidden '§\d' ... +``` + +## Chinese battery + +```sh +rg -n --hidden '设计稿|评审|上一?轮|旧版|老的|不再|以前|本版|遗留|私有' ... +rg -n --hidden '(^|[^a-zA-Z])端([^a-zA-Z]|$)' --glob '*.md' ... +``` + +## Known false-positive families + +Judged and kept during the purge; expect them again: + +- **Instrumental "used to"** — "the key used to sign requests" is instrumental, not temporal. The temporal form has a subject state before it ("colors used to come from…"). +- **Runtime old/new** — "the old connection drains before the new one accepts" names live objects during handover, not repo states. +- **"This PR" in process docs** — documentation *about* PR workflow ("the PR body should…", templates, this repo's process notes) legitimately says "PR"; the ban is on a doc adopting one PR's vantage about the code. +- **`v1` as protocol or path segment** — `/v1/chat` endpoints and wire-format names are identifiers, not version stamps. +- **`§N` with a committed owner** — external standards (RFC 9110 §10.1.5) and committed docs that own their §-numbering stay citable by section. +- **Contrastive "actually" and noun "wait"** — ordinary English, not hedging. +- **"Today" in generated timestamps and CLI output samples** — recorded output keeps its voice. +- **本版本 in zh prose** — a legitimate rendering of "this release" in versioned-artifact contexts; the banned indexical is 本版 as a bare stamp mirroring "this cut". +- **Alternatives-considered sections** — "rejected" inside an Agent Note's genre slot is the sanctioned home, not review choreography. diff --git a/packages/workflow/workflow-workerthread/tests/workflow-workerthread.spec.ts b/packages/workflow/workflow-workerthread/tests/workflow-workerthread.spec.ts index 79aad3af42..de190349a4 100644 --- a/packages/workflow/workflow-workerthread/tests/workflow-workerthread.spec.ts +++ b/packages/workflow/workflow-workerthread/tests/workflow-workerthread.spec.ts @@ -1239,22 +1239,18 @@ describe('dsh-workflow-workerthread', () => { await ctx.plugin(WorkerWorkflowEngine, { provider: 'doomed', maxConcurrentAgents: 2 }) const runEnds: WorkflowResultInfo[] = [] ctx.on('workflow/end', (_info, result) => { runEnds.push(result) }) + const childStarted = Promise.withResolvers() + ctx.on('workflow/agent-start', () => { childStarted.resolve(undefined) }) const handle = ctx.workflows.start({ - // The stray child's start RPC reaches the host, then the script kills - // its own worker through the documented vm escape — the host must - // settle `error` with the exit diagnostics and wind the child down. - ...scripted(` - agent('doomed') - const proc = ${ESCAPE} - const st = globalThis.constructor.constructor('return setTimeout')() - await new Promise(resolve => st(resolve, 200)) - proc.exit(7) - `), + ...scripted("return await agent('doomed')"), parent: fakeParent(), }) + const worker = (handle as unknown as { worker: Worker }).worker + await childStarted.promise + await worker.terminate() const result = await handle.result expect(result.stopReason).toBe('error') - expect(result.error).toContain('exit code 7') + expect(result.error).toContain('exit code 1') expect(result.agentsStarted).toBe(1) // A worker death is a stop reason like any other: workflow/end fires // with the error outcome — for a bus observer it is the only obituary. @@ -1306,26 +1302,22 @@ describe('dsh-workflow-workerthread', () => { }) ctx.on('workflow/end', () => { order.push('run-end') }) const handle = ctx.workflows.start({ - // Same choreography as the force-settle pairing test, but the worker - // DIES (the documented vm escape) instead of being terminated: the - // exit path must close slow's pair from the ledger too. The escaped - // setTimeout lets the already-posted messages flush before the kill. ...scripted(` const p = agent('slow') await agent('fast') - const proc = ${ESCAPE} - const st = globalThis.constructor.constructor('return setTimeout')() - await new Promise(resolve => st(resolve, 150)) - proc.exit(7) + await new Promise(() => {}) `), parent, }) + const worker = (handle as unknown as { worker: Worker }).worker await waitFor(() => { expect(order.filter(entry => entry.startsWith('start:')).length).toBe(2) }) const fast = provider.runs.find(run => (run.request.prompt[0] as { text?: string }).text === 'fast')! fast.settle(text('fast done')) + await waitFor(() => { expect(ends).toContainEqual({ seq: 2, outcome: 'completed' }) }) + await worker.terminate() const result = await handle.result expect(result.stopReason).toBe('error') - expect(result.error).toContain('exit code 7') + expect(result.error).toContain('exit code 1') expect(ends).toEqual([ { seq: 2, outcome: 'completed' }, { seq: 1, outcome: 'cancelled' }, @@ -1340,21 +1332,22 @@ describe('dsh-workflow-workerthread', () => { // guard in post()). const { ctx, parent, provider } = await setup({ disposeDelayMs: 300 }) const handle = ctx.workflows.start({ - // The STRAY child settles instantly, so its wrapper starts the slow - // host-side disposal concurrently while the script goes on to kill - // its own worker — the ack then resolves into a dead thread. ...scripted(` agent('stray, never awaited') - const proc = ${ESCAPE} - const st = globalThis.constructor.constructor('return setTimeout')() - await new Promise(resolve => st(resolve, 150)) - proc.exit(5) + await new Promise(() => {}) `), parent, }) + const worker = (handle as unknown as { worker: Worker }).worker + await waitFor(() => { + expect(provider.runs).toHaveLength(1) + expect(provider.runs[0]!.disposeCalls).toBe(1) + expect(provider.runs[0]!.disposed).toBe(false) + }) + await worker.terminate() const result = await handle.result expect(result.stopReason).toBe('error') - expect(result.error).toContain('exit code 5') + expect(result.error).toContain('exit code 1') // Result already settled — this is the reap's promptness (bounded // above the mock's fixed 300ms dispose delay, not a cold-start race); // tight explicit bound (see the helper's doc comment). @@ -1366,20 +1359,19 @@ describe('dsh-workflow-workerthread', () => { const { ctx, parent } = await setup({ config: { provider: 'stub', disposeGraceMs: 60_000 } }) const handle = ctx.workflows.start({ ...scripted(` - const proc = ${ESCAPE} - const st = globalThis.constructor.constructor('return setTimeout')() log('armed') - await new Promise(resolve => st(resolve, 400)) - proc.exit(3) + await new Promise(() => {}) `), parent, }) + const worker = (handle as unknown as { worker: Worker }).worker const logs: string[] = [] ctx.on('workflow/log', (_info, message) => { logs.push(message) }) await waitFor(() => { expect(logs).toContain('armed') }) handle.cancel('stop it') - // The grace is deliberately huge: only the worker's own death (exit 3, - // unreachable by the cancel — the script ignores hooks) settles this. + // The grace is deliberately huge: only the host-triggered worker death, + // not the cancellation timer, settles this. + await worker.terminate() const result = await handle.result expect(result.stopReason).toBe('cancelled') expect(result.error).toContain('stop it') diff --git a/scripts/project-doc-site.spec.ts b/scripts/project-doc-site.spec.ts index 29cef00630..a8f339cec7 100644 --- a/scripts/project-doc-site.spec.ts +++ b/scripts/project-doc-site.spec.ts @@ -251,7 +251,7 @@ describe('rewriteMarkdown', () => { }) describe('docsPages locale routes', () => { - it('publishes every route in both locales and selects paired sources', () => { + it('publishes every route in both locales and uses every available Chinese counterpart', () => { const byRoute = new Map(docsPages.map(page => [page.route, page])) for (const page of docsPages.filter(page => page.locale === 'root')) { const counterpart = byRoute.get(`en/${page.route}`) @@ -265,6 +265,11 @@ describe('docsPages locale routes', () => { } else { expect(counterpart?.source).toBe(page.source) expect(counterpart?.contentLocale).toBe(page.contentLocale) + const chineseSource = page.source.replace(/\.md$/, '.zh.md') + expect( + existsSync(resolve(repositoryRoot, chineseSource)), + `${page.route} has a Chinese counterpart but projects English`, + ).toBe(false) } } }) @@ -282,20 +287,16 @@ describe('docsPages locale routes', () => { } }) - it('projects translated subsystem pages while retaining explicit English fallbacks', () => { + it('projects every published subsystem page in Chinese', () => { const rootPages = docsPages.filter(page => ( page.locale === 'root' && page.route.startsWith('reference/subsystems/') )) const translated = rootPages.filter(page => page.contentLocale === 'zh-CN') const fallbacks = rootPages.filter(page => page.contentLocale === 'en-US') - expect(translated).toHaveLength(39) + expect(translated).toHaveLength(42) expect(translated.every(page => page.source.endsWith('.zh.md'))).toBe(true) - expect(fallbacks.map(page => page.source).sort()).toEqual([ - 'docs/subsystems/commands.md', - 'docs/subsystems/goal.md', - 'docs/subsystems/pty.md', - ]) + expect(fallbacks).toEqual([]) }) it('publishes the Cordis core API under matching locale structures', () => { diff --git a/website/docs.ts b/website/docs.ts index a8d07a59c2..9fcdc7c1a7 100644 --- a/website/docs.ts +++ b/website/docs.ts @@ -226,7 +226,7 @@ const develop = pairedPages([ }, ]) -const cordisTutorial = mirroredPages(([ +const cordisTutorial = pairedPages(([ ['index.md', 'Cordis 教程', 'Cordis tutorial'], ['01-first-plugin.md', '1. 第一个插件', '1. Your first plugin'], ['02-lifecycle-and-effects.md', '2. 生命周期与副作用', '2. Lifecycle and effects'], @@ -235,10 +235,9 @@ const cordisTutorial = mirroredPages(([ ['05-config.md', '5. 配置', '5. Configuration'], ['06-composition-and-hmr.md', '6. 组合与热重载', '6. Composition and HMR'], ['07-into-the-harness.md', '7. 进入 Harness', '7. Into the harness'], -] as const).map(([file, rootLabel, enLabel], order): MirroredPage => ({ +] as const).map(([file, rootLabel, enLabel], order): PairedPage => ({ source: `docs/cordis-tutorial/${file}`, route: `develop/cordis-tutorial/${file}`, - contentLocale: 'en-US', label: { root: rootLabel, en: enLabel }, sidebar: { root: 'zh-develop', en: 'en-develop' }, section: { root: 'Cordis 教程', en: 'Cordis tutorial' }, @@ -308,12 +307,11 @@ const subsystemsReference = pairedPages(([ }))) const reference = [ - ...mirroredPages(([ + ...pairedPages(([ ['docs/architecture.md', 'reference/index.md', '架构', 'Architecture', 0], - ] as const).map(([source, route, rootLabel, enLabel, order]): MirroredPage => ({ + ] as const).map(([source, route, rootLabel, enLabel, order]): PairedPage => ({ source, route, - contentLocale: 'en-US', label: { root: rootLabel, en: enLabel }, sidebar: { root: 'zh-reference', en: 'en-reference' }, section: { root: '概念', en: 'Concepts' }, @@ -369,28 +367,26 @@ const reference = [ section: { root: 'Cordis API', en: 'Cordis Core API' }, order: order + 5, }))), - ...mirroredPages(([ + ...pairedPages(([ ['goal.md', '目标', 'Goals', 14], ['pty.md', 'PTY 会话', 'PTY sessions', 26], ['commands.md', '命令', 'Human commands', 38], - ] as const).map(([file, rootLabel, enLabel, order]): MirroredPage => ({ + ] as const).map(([file, rootLabel, enLabel, order]): PairedPage => ({ source: `docs/subsystems/${file}`, route: `reference/subsystems/${file}`, - contentLocale: 'en-US', label: { root: rootLabel, en: enLabel }, sidebar: { root: 'zh-reference', en: 'en-reference' }, section: { root: '子系统', en: 'Subsystems' }, order, }))), - ...mirroredPages(([ + ...pairedPages(([ ['adding-a-package.md', '新增 Package', 'Adding a package'], ['adding-a-tool.md', '新增 Tool', 'Adding a tool'], ['adding-an-llm-adapter.md', '新增 LLM Adapter', 'Adding an LLM adapter'], ['extension-cookbook.md', '扩展模式', 'Extension patterns'], - ] as const).map(([file, rootLabel, enLabel], order): MirroredPage => ({ + ] as const).map(([file, rootLabel, enLabel], order): PairedPage => ({ source: `docs/cookbook/${file}`, route: `reference/cookbook/${file}`, - contentLocale: 'en-US', label: { root: rootLabel, en: enLabel }, sidebar: { root: 'zh-reference', en: 'en-reference' }, section: { root: '开发手册', en: 'Cookbook' },