docs(i18n): refresh Agent Note translations after merge

This commit is contained in:
Tianyi Cui
2026-07-23 00:10:53 +08:00
parent cd17f8d23a
commit 744d65d63c
325 changed files with 1535 additions and 1321 deletions
@@ -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
2026-06-11-doc-sync-enforcement.md: cb238291f9ff5ba5d6333cdf2fe75a43fe3e8eea
2026-06-11-doc-sync-enforcement.zh.md: a443cfcd6f9bd17ae1512683ff8cd82411fb8590
2026-06-11-doc-sync-enforcement.md: 375059312c312dff7b5ddcb95ea5b82ac8cd4d06
2026-06-11-doc-sync-enforcement.zh.md: 7d0e812c7a4b0ec64113cd272b2fcbdfb6055f18
@@ -1,4 +1,4 @@
# RFC: Doc-sync 强制
# Agent Note: Doc-sync 强制
Status: implemented
@@ -13,20 +13,20 @@ AGENTS.md 承诺文档与代码严格同步,但这一承诺此前仅靠人眼
两道门禁,沿用既有的 `scripts/` 风格(tsx ESM,每个脚本一项职责):
1. **`doc-typecheck`** 从 `README.md``docs/**``packages/*/README.md` 中提取所有 ` ```ts ` 围栏代码块,写入一个继承根 `tsconfig.json` 的临时项目,然后用 `tsc -b` 编译。临时项目复用源码的 `paths` 映射和根 project references,因此文档示例能看到源码,而 vendor 代码仍在其自身的 tsconfig 设置下被检查。刻意作为草图的代码块可通过显式的 ` ```ts ignore-check ` 信息字符串来 opt-out;脚本会报告 opt-out 比例,超过一半即失败,防止该豁免机制悄然成为常态。
2. **`verify-event-taxonomy`** 从 `packages/*/src` 中的 `interface Events` 块和 `docs/architecture.md` 中的分类体系表分别提取事件名称,断言两个集合完全一致。只校验,不生成:表格保留手写的 Mode/Purpose 列,仅检查名称集合。(落地此门禁时发现了表格遗漏的三个事件:`tools/change``llm/adapter-change``system-prompt/change`。)**已被取代**:由[生成式 Cordis 目录](2026-06-20-generated-cordis-catalog.md)取代。此门禁及其 `architecture.md` 表格已退役,取而代之的是完全生成的 `docs/cordis-catalog/events.md` + `docs/cordis-catalog/services.md` 及其 `verify-cordis-catalog` 新鲜度门禁。本 RFC 中的其他门禁(`doc-typecheck` 以及下文修订中的 `verify-md-wrap`)不受影响。
2. **`verify-event-taxonomy`** 从 `packages/*/src` 中的 `interface Events` 块和 `docs/architecture.md` 中的分类体系表分别提取事件名称,断言两个集合完全一致。只校验,不生成:表格保留手写的 Mode/Purpose 列,仅检查名称集合。(落地此门禁时发现了表格遗漏的三个事件:`tools/change``llm/adapter-change``system-prompt/change`。)**已被取代**:由[生成式 Cordis 目录](2026-06-20-generated-cordis-catalog.md)取代。此门禁及其 `architecture.md` 表格已退役,取而代之的是完全生成的 `docs/cordis-catalog/events.md` + `docs/cordis-catalog/services.md` 及其 `verify-cordis-catalog` 新鲜度门禁。本 Agent Noteagent 决策记录)中的其他门禁(`doc-typecheck` 以及下文修订中的 `verify-md-wrap`)不受影响。
两者通过一个共享的 `doc-sync`(文档同步门禁)package.json 脚本运行,lefthook pre-push 钩子和 CI 都调用它([机械质量门禁](2026-06-11-quality-gates.md):钩子与 CI 调用相同脚本,因此门禁在推送前就在本地触发,而非仅在推送后)。它们在 `pnpm run typecheck` 之后运行,后者校验 doc-typecheck 所引用的 package/vendor 构建图
两者通过 package.json 中共享的 `doc-sync` 脚本运行;贡献者在相关文档变更中调用它,CI 则执行完整检查。[快速本地 Git 钩子](2026-07-22-fast-local-git-hooks.md)决策使这类按变更面选择的工作不进入 commit 和 push 钩子
**修订(2026-06-17):** 第三道门禁 **`verify-md-wrap`** 随后被纳入 `doc-sync`。它使用 `mdast-util-from-markdown` + GFM 解析范围内的每个 Markdown 文件(`README.md``docs/**``packages/*/README.md`,加上 `AGENTS.md` / `packages/AGENTS.md`),如果任何 `paragraph` 节点跨越多个源码行则失败,从而强制执行 docs/AGENTS.md 中「一个段落一个物理行」的写作规则。同样遵循只校验不生成的原则:它报告硬换行但从不重写,因此不会引入格式化噪音。`doc-sync` 现在包含三道门禁。
## 曾考虑的替代方案
- **API-extractor 金标报告**[已推迟的提案](../../proposed/process/2026-06-11-api-extractor-reports.md)):有意推迟。对于评审者已能直接看到源码 diff 的内部 monorepo 而言价值有限,且依赖重、配置繁琐。
- **API-extractor 基准报告**[已推迟的提案](../../proposed/process/2026-06-11-api-extractor-reports.md)):有意推迟。对于评审者已能直接看到源码 diff 的内部 monorepo 而言价值有限,且依赖重、配置繁琐。
- **从源码生成分类体系表**而非仅校验名称:否决,机制比问题本身更重;表格保留了手写的 Mode/Purpose 列,直到[生成式 Cordis 目录](2026-06-20-generated-cordis-catalog.md)完全取代了这项检查。
## 后果
- 可检查类别的文档漂移现在会让 pre-push 钩子和 CI 失败,而非等待评审发现。这是「机械门禁优于行文约定」原则的一个实例
- 可检查类别的文档漂移会直接使 `doc-sync` 和 CI 失败,而不是等评审发现。这是「机械门禁优于行文规范」原则的具体应用
- 让文档代码片段可编译需要少量 stub import/`declare``ignore-check` 比例必须保持低位,否则门禁形同虚设(比例守卫强制执行此约束)。
- 分类体系检查仅限名称——Mode 或 Purpose 列的错误仍需人工评审。
- 如果 package 未来对外发布,API 报告方案仍可重新考虑。
@@ -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
2026-06-11-quality-gates.md: 2d6b6815e80a728cb61a86e9f7a488340fe06fc9
2026-06-11-quality-gates.zh.md: f5088811f8562f5103740d39b48bd14c6d1da00c
2026-06-11-quality-gates.md: e1af110387936d644208dc1829fde4a4fdf8a3f9
2026-06-11-quality-gates.zh.md: eaae458e3eac6a45e0c16ec0b8fb950aa1b70619
@@ -2,6 +2,8 @@
Status: implemented
English | [中文](2026-06-11-quality-gates.zh.md)
The hook/CI symmetry in this record is superseded by [Fast local Git hooks](2026-07-22-fast-local-git-hooks.md); CI remains the exhaustive enforcement path.
## Problem
@@ -1,28 +1,30 @@
# RFC: 以机械质量门禁取代行文约定
# Agent Note: 以机械质量门禁取代行文约定
Status: implemented
[English](2026-06-11-quality-gates.md) | 中文
本记录中的钩子/CI 对称设计已由[快速本地 Git 钩子](2026-07-22-fast-local-git-hooks.md)取代;CI 仍是执行完整检查的路径。
## 问题
本代码库主要由 coding agent(智能体)开发。相比行文约定,agent 遵守强制门禁的可靠性远高得多;而当劳动由 agent 承担时,「工作量大」不构成成本论据。早期证据:未通过类型检查的测试被提交(vitest 不做类型检查),仅在评审中才被发现。
## 决策
AGENTS.md 中的每一条承诺都对应一个以非零退出码表示失败的命令,通过 git 钩子和 CI 调用同一套 package.json 脚本来执行
每条可机械检查的 AGENTS.md 承诺都一个以非零状态退出的命令。CI 执行完整集合,而 Git 钩子将延迟预算留给可低成本发现的本地缺陷
- 最严格的 TypeScript 配置(`noUncheckedIndexedAccess``exactOptionalPropertyTypes` 等);示例、测试和脚本通过根目录的 no-emit `tsconfig.json` 在 CI 中进行类型检查,而 package/vendor 代码保持在各自 project-reference 边界之后。
- ESLint strict-type-checked + @stylistic(作为强制执行的统一代码风格),包括文件内重复逻辑检查;vendor 代码排除在外。
- jscpd 检测 package 生产 TypeScript 与仓库脚本中的跨文件克隆;窄范围的源码区间例外用于记录有意为之的并行实现。
- `packages/*/*/src` 下按文件 100% 覆盖率(v8);不可达的防御性守卫使用 `/* v8 ignore */ ` 并注明理由,而非删除。
- knip(死代码/依赖)、publint(包(package)正确性)、workspace 约束(workspace 规则:private、cordis peer+dev、统一版本、ESM),以及对构建出的包声明文件进行 NodeNext 消费方类型检查。
- lefthook pre-commitlint 暂存文件、类型检查vendor manifest(元数据清单)守卫)和 pre-push(测试、hygiene);CI 在 Node 22.19/24/26 上运行完整矩阵,外加一个驱动 echo-agent 端到端的演示冒烟测试。
- lefthook pre-commit 修复已暂存文件的 lint 问题、拒绝已暂存的空白问题并检查 vendor manifest;pre-push 运行增量类型检查。CI 在 Node 22.19/24/26 上运行完整矩阵,并对 Headless、TUI、ACPAgent Client Protocol)、JSON-RPC、工作流和代码运行时入口路径执行已构建应用的冒烟测试。
## 后果
- 约定 agent 更替中得以存续;违规在本地快速失败。
- 约定不会因 agent 更替而失效;可低成本发现的 commit/push 缺陷在本地失败,其余完整规则违规在 CI 中失败。
- 门禁本身也是需要维护的代码;配置变更与其他变更一样需要评审。
- 100% 覆盖率的压力可能催生无断言的测试——变异测试是计划中的对策(见[变异测试提案](../../proposed/testing/2026-06-11-mutation-testing.md))。
<!-- rfc-format: alternatives-not-recorded (pre-format RFC) -->
<!-- agent-note-format: alternatives-not-recorded (pre-format Agent Note) -->
@@ -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
2026-06-11-tsdown-over-dumble.md: b1ce354b9b2baa042d8aa1be2a8de171ed4adfef
2026-06-11-tsdown-over-dumble.zh.md: 4bdd9939901ef376f2f6ccded609b644a4698495
2026-06-11-tsdown-over-dumble.md: 5d7593d689e2404bede0fa51b41c5d8eec397a03
2026-06-11-tsdown-over-dumble.zh.md: 080ee4c3fb9894049716fd5aec3ddd7b3d62d547
@@ -1,4 +1,4 @@
# RFC: 使用 tsdown 替代 dumble 进行 JS 打包
# Agent Note: 使用 tsdown 替代 dumble 进行 JS 打包
Status: implemented
@@ -15,7 +15,7 @@ Status: implemented
**tsdown**(基于 rolldown,每周约 250 万次下载,VoidZero 支持,活跃发布)替代 dumble:
- 根目录 `tsdown.config.ts`,配置 `workspace: ['vendor/*', 'packages/*/*']`(显式 glob 将打包范围限定在 vendor 的 Cordis 与 TypeScript 包目录树内;`workspace: true` 还会发现示例 manifest 和不需要打包的 workspace 成员)。
- 共享形:入口 `lib/types/index.js``outDir: 'lib'`ESM`platform: node``target: es2024``fixedExtension: false`(为 `"type": "module"` 包保 `.js` 扩展名),`dts: false`(声明文件由 tsc -b 负责),`clean: false`lib/ 同时存放 TSC 的 `lib/types` 中间产物树)。入口最初是 `src/index.ts`[TSC 优先构建 RFC](2026-06-17-ts-build-config.md) 后来将 tsdown 改为打包 TSC 输出的 JS,使 TypeScript 转换行为统一来自一个编译器。
- 共享形:入口 `lib/types/index.js``outDir: 'lib'`ESM`platform: node``target: es2024``fixedExtension: false`(为 `"type": "module"` 包保 `.js`),`dts: false`(声明 tsc -b 所有),`clean: false`lib/ 还保存 TSC 的 `lib/types` 中间树)。入口最初是 `src/index.ts`[TSC 优先构建 Agent Noteagent 决策记录)](2026-06-17-ts-build-config.md)随后将 tsdown 改为打包 TSC 输出的 JS,使 TypeScript 转换行为统一一个编译器提供
- vendor/ 中有两个按包覆盖的配置(属于我们自己的修改,与重新生成的 tsconfig 类似;记录在 vendor/README.md 中):schemastery(通过 `outExtensions` 输出双格式 `.mjs`/`.cjs`)、logger-console(两次单入口 pass,使共享基类被内联到每个入口而非生成哈希命名的 chunk,与上游发布形态一致)。
- `scripts/build.ts` 删除;`pnpm run build` = `tsc -b tsconfig.build.json && tsdown`
@@ -27,4 +27,4 @@ Status: implemented
## 后果
运行时打包产物仍遵循 dumble 时代的公开入口形`lib/index.js`,以及包特的变体,如 `schemastery``lib/index.mjs`/`lib/index.cjs` `logger-console``lib/browser.js`);声明文件现在位于 `lib/types` 下,见 [TSC 优先构建 RFC](2026-06-17-ts-build-config.md)。外部依赖仍来自各包的 dependencies/peerDependencies。我们放弃了 dumble 的 exports 字段推断功能:新增的非默认形态的包需要编写按包的 `tsdown.config.ts`不能仅靠 package.json 字段。未来可选方向:如果 `tsc -b` 成为瓶颈,tsdown 可以接管声明文件打包(isolatedDeclarations);那将是一个新的 RFC
运行时 bundle 输出仍沿用 dumble 时代的公开入口形`lib/index.js`,以及包特的变体,`schemastery``lib/index.mjs`/`lib/index.cjs` `logger-console``lib/browser.js`);根据 [TSC 优先构建 Agent Note](2026-06-17-ts-build-config.md),声明现位于 `lib/types` 下。External 仍来自各包的 dependencies/peerDependencies。我们放弃了 dumble 的 exports 字段推断:采用非默认形状的新包需要逐包提供 `tsdown.config.ts`,不能只依赖 package.json 字段。未来如果 `tsc -b` 成为瓶颈,tsdown 可以接管声明打包(isolatedDeclarations);这需要另写一份 Agent Note
@@ -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
2026-06-11-vendor-cordis-as-source.md: 6e1af616785411a20496334fb35e0c7a3bc41b43
2026-06-11-vendor-cordis-as-source.zh.md: bb9413fbba34e9a7040fd2e0a1bbcff1ba2345ff
2026-06-11-vendor-cordis-as-source.md: ae6f5438c5817c61a549d9edb2041d538fbcebe6
2026-06-11-vendor-cordis-as-source.zh.md: 8d6f0e39d53e1c85eaaa50c4c4bf1d9ef648d953
@@ -1,4 +1,4 @@
# RFC: 将 Cordis 以源码形式收录,而非作为 npm 依赖
# Agent Note: 将 Cordis 以源码形式收录,而非作为 npm 依赖
Status: implemented
@@ -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
2026-06-16-pnpm-over-yarn.md: f5787dc3019f2c474761972afb11deb42f3950e0
2026-06-16-pnpm-over-yarn.zh.md: 0f5acd13f6f113a569fbe4e7b92df62309c76537
2026-06-16-pnpm-over-yarn.md: 9dee405f509897e2a173399e466d574c518fa9ab
2026-06-16-pnpm-over-yarn.zh.md: ef34e6ba5d6e22037668c9aa3507dfd0f5438ad4
@@ -1,4 +1,4 @@
# RFC: 使用 pnpm 替代 Yarn 4 作为包管理器
# Agent Note: 使用 pnpm 替代 Yarn 4 作为包管理器
Status: implemented
@@ -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(智能体)构建、偶尔有人类贡献者阅读的仓库而言,「大多数工具和人所期望的包管理器」具有实际价值:更少的意外、更成熟的故障路径、更多可直接复用的解答。
切换成本目前处于最低点。本仓库尚无任何包(package)发布(每个包都是 `private: true`);开发/测试/演示全部通过 tsx **未构建**运行,因此包管理器只需做到:(a) 解析并链接 `node_modules`(b) 运行 workspace 脚本,(c) 强制执行 workspace 约束。唯一的 Yarn 特有资产是 `yarn.config.cjs``@yarnpkg/types` 约束引擎),体量小且可机械地重新表达。这与 [tsdown 决策](2026-06-11-tsdown-over-dumble.md)的逻辑一致:在爆炸半径尚小时,将承重工具换为生态更健康的选项。
切换成本目前处于最低点。本仓库尚无任何包(package)发布(每个包都是 `private: true`);开发/测试/演示全部通过 tsx **未构建**运行,因此包管理器只需做到:a解析并链接 `node_modules`b运行 workspace 脚本,c强制执行 workspace 约束。唯一的 Yarn 特有资产是 `yarn.config.cjs``@yarnpkg/types` 约束引擎),体量小且可机械地重新表达。这与 [tsdown 决策](2026-06-11-tsdown-over-dumble.md)的逻辑一致:在爆炸半径尚小时,将承重工具换为生态更健康的选项。
## 决策
@@ -40,4 +40,4 @@ Status: implemented
在快速本地磁盘上,pnpm 的内容寻址 store 通常在冷/热安装中胜出,尤其在多个检出之间的**磁盘占用**方面优势明显(一个全局 store 通过硬链接接入每个 `node_modules`,而 Yarn 每个 worktree 复制约 279 MB——部分开发者经常为本仓库保持约 10 个或更多 worktree)。该去重优势在上述迁移时数据中**未能**体现,因为测试 store 和 `node_modules` 位于不同文件系统,硬链接失效;在单文件系统的开发机或 CI 缓存上则适用。诚实的总结:在我们的 NFS 开发文件系统上,安装速度在噪声范围内不分伯仲;迁移的理由是生态对齐、幻影依赖安全性和跨检出磁盘去重,而非原始安装时间的胜出。
所有质量门禁(constraints、typecheck、lint、doc-sync、test:coverage 100%、build、knip、publint、echo-agent 演示冒烟测试)在 pnpm 上原样通过,这是链接器切换未引入幻影依赖破坏的正确性证明
所有质量门禁(constraints、类型检查、lint、doc-sync、达到 100% 的 test:coverage、构建、knip、publint 以及已构建应用的冒烟测试)在 pnpm 下通过,证明更换 linker 没有引入幽灵依赖故障
@@ -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
2026-06-17-ts-build-config.md: 9a75bcc3f043576f1cb793c38e0166aa0a010f68
2026-06-17-ts-build-config.zh.md: c0f6bd40f3f0b06e79b9dc9f9c7812481413374c
2026-06-17-ts-build-config.md: b82e5cae6dbfaae68d3a834162085e3f7b2c0c72
2026-06-17-ts-build-config.zh.md: e6cec899e521ee2c8325c7562329455141da8af5
@@ -1,4 +1,4 @@
# RFC: TSC 优先的构建与单一 tsconfig
# Agent Note: TSC 优先的构建与单一 tsconfig
Status: implemented
@@ -23,6 +23,7 @@ Status: implemented
- 在根目录严格配置下直接对 `vendor/*/src` 做类型检查,会触发大量不属于本项目所有权范围的类型错误。
- `packages/*/*``vendor` 的包依赖解析到 `vendor/*/lib`,以适应不同的 tsconfig 严格度。
## 决策
包内相对导入使用显式 `.ts` 说明符。
@@ -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
2026-06-18-markdown-cross-link-lint.md: f7ab6a4fd2b2c5cadaeabd5ef0c89f1097b5b44d
2026-06-18-markdown-cross-link-lint.zh.md: 61187042a3a0d3535479ffe3a796b6c1fdb82f5e
2026-06-18-markdown-cross-link-lint.md: 2e3b0f1fcd03f244756b0030f2da758c516a2bbb
2026-06-18-markdown-cross-link-lint.zh.md: cfe973ae939eceb50d6381ecf35c80df508907c3
@@ -1,4 +1,4 @@
# RFC: Markdown 交叉链接有效性检查
# Agent Note: Markdown 交叉链接有效性检查
Status: implemented
@@ -8,7 +8,7 @@ Status: implemented
本仓库的文档通过相对路径互相链接:`[topic](../implemented/2026-…-….md)``[the cookbook](adding-a-tool.md)``[architecture.md](../../architecture.md)`。此前没有任何机制验证这些目标是否存在。重命名或移动文件会静默破坏所有指向它的链接,且在读者点击之前不可见。[Doc-sync 强制](2026-06-11-doc-sync-enforcement.md)已经将两类文档漂移机械化(无法编译的代码块、陈旧的事件分类表),[verify-md-wrap](2026-06-11-doc-sync-enforcement.md) 覆盖了第三类(硬换行的段落),但死链是第四类同样可机械检查、却仍靠肉眼验证的问题。
触发本门禁的直接案例是引入它的那次 RFC 目录重组:将 `docs/adr/` + `docs/rfc/` 统一一个 `docs/rfc/`,下设 `proposed/`/`implemented/`/`rejected/` 子目录,手动改写了约四十条文档间链接。任何一个手误路径都会让一条死链随代码入库,而没有任何东西能拦住它
引入这道门禁的直接动因是 Agent Noteagent 决策记录)目录重组:将 `docs/adr/` `.agents/notes/` 统一到同一个 `.agents/notes/` 下,并设置 `proposed/``implemented/``rejected/` 子目录,需要手工重命名约 40 条文档间链接。只要有一处路径输入错误,就会在没有任何检查拦截的情况下交付断链
## 决策
@@ -18,7 +18,7 @@ Status: implemented
- 仅当目标是**相对路径**时才检查。跳过带协议的 URL(`https:``mailto:` 等)、协议相对路径(`//host`)、根绝对路径(`/path`,在检出目录中没有稳定基准)以及纯页内锚点(`#section`)。剥除 `#fragment`/`?query`,相对于链接所在文件的目录解析路径,并断言目标在磁盘上存在。
- 只报告、不改写;发现第一条死链即以非零状态退出。
范围与其他门禁一致,另外加上 AGENTS.md 对和 `.agents/skills/` 下仓库自有的 agent skill Markdown(这些 skill 文件交叉链接到 docs 目录,因此本次重组也改写了其中的链接):`README.md``docs/**/*.md``packages/*/README.md``AGENTS.md``packages/AGENTS.md``.agents/skills/**/*.md`按真实路径去重(`CLAUDE.md` 符号链接解析到 AGENTS.md 文件)。它接入 lefthook pre-push 钩子和 CI 都会运行的 `doc-sync` 脚本,因此死链在推送前就会在本地失败——与[机械化质量门禁](2026-06-11-quality-gates.md)一致
检查范围与其他门禁一致,并额外包含 AGENTS.md 文件对以及 `.agents/skills/` 下仓库自有的 agent-skill(技能)Markdown(这些 skill 文件交叉链接到 docs 目录,因此本次重组也改写了其中的链接):`README.md``docs/**/*.md``packages/*/README.md``AGENTS.md``packages/AGENTS.md``.agents/skills/**/*.md`。系统按真实路径去重(`CLAUDE.md` symlink 会解析到 AGENTS.md 文件)。该检查接入 `doc-sync`,因此相关文档变更与 CI 执行同一套断链检查
本门禁检查的是*文件存在性*,而非锚点有效性:指向一个真实文件但带有 `#wrong-heading` 片段的链接仍会通过(文件可解析;片段被剥除)。
@@ -28,6 +28,6 @@ Status: implemented
## 后果
- 重命名移动文件导致交叉链接悬空时,现在会在 pre-push 钩子和 CI 失败,而不是等读者点击死链才发现。这使得引入门禁的 RFC 重组具备自验能力:改写四十条链接的同一个 PR 也添加了证明无一悬空的检查。
- 造成交叉链接失效的重命名移动会直接使 `doc-sync` 和 CI 失败,而不是等读者点击死链才暴露。由此,引入门禁的 Agent Note 重组具备自验能力:同一个 PRPull Request)在改写 40 条链接的同时,也添加了证明这些链接均未悬空的检查。
- `doc-sync` 链中多了一个快速 tsx 脚本;无新增依赖(mdast/GFM 技术栈已作为 `verify-md-wrap` 的 devDependencies 存在)。
- 门禁强制的约定——通过可机械检查的相对链接引用文档,而非裸文本或编号——记录在 [docs/AGENTS.md](../../../AGENTS.md) 中,让作者知晓门禁的存在与原因
- 门禁强制执行的约定是:文档交叉引用必须使用可机械检查的相对链接,绝不能只写纯文本或编号。[docs/AGENTS.md](../../../../docs/AGENTS.md)记录了这项约定,使作者了解该门禁及其理由
@@ -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
2026-06-20-rfc-classification.md: 6975b74dbaa9ca9379f23b537133323cb694958a
2026-06-20-rfc-classification.zh.md: 423d5c87eb9e2cb07ed18834f4c15ffa1ac1983e
2026-06-20-agent-note-classification.md: 094233ed108e35e390cdc66b419179249c2d9173
2026-06-20-agent-note-classification.zh.md: b424b07e051507a894c8f5961feb5fe24a9da3c6
@@ -2,6 +2,8 @@
Status: implemented
English | [中文](2026-06-20-agent-note-classification.zh.md)
## Problem
A lifecycle-only Agent Note tree — `proposed/` / `implemented/` / `rejected/` — does not record what *kind* of decision each file contains. A reader browsing one lifecycle cannot distinguish a new capability from a removal or a tooling-policy change without opening each file.
@@ -1,48 +1,48 @@
# RFC: 通过路径编码的子目录对 RFC 进行分类
# Agent Note: 通过路径编码的子目录对 Agent Note 进行分类
Status: implemented
[English](2026-06-20-rfc-classification.md) | 中文
[English](2026-06-20-agent-note-classification.md) | 中文
## 问题
`docs/rfc/` 过去仅按**生命周期**分组 RFC:`proposed/``implemented/``rejected/`。没有任何机制记录每个 RFC 属于哪一*类*决策。索引在每个生命周期下只是一个扁平列表,无法按需筛选「所有简化类」或「所有测试策略类」决策。一批简化类 RFC 在同一天落地后,这个缺口变得具体:浏览 `proposed/` 的读者无法在不逐一打开文件的情况下区分新能、移除工具策略变更。
仅按生命周期组织的 Agent Note(agent 决策记录)目录树(`proposed/` / `implemented/` / `rejected/`)无法记录每个文件包含哪一*类*决策。读者浏览某个生命周期时,如果不逐一打开文件,就无法区分新能、移除项或工具策略变更。
本仓库一贯的倾向是[机械质量门禁优于行文规范](2026-06-11-quality-gates.md):不被机器检查的约定终将腐烂。因此这里的分类方案必须可强制执行,而非靠自觉的文件头。
## 决策
增加第二个维度——RFC 的**类别**——并将其编码在路径中:`{lifecycle}/{class}/yyyy-mm-dd-topic.md`。文件夹本身*就是*标签。文件位置声明其类别封闭集合「这些文件夹且仅限这些」,而既有的 [verify-md-links](2026-06-18-markdown-cross-link-lint.md) 门禁已经保护移动文件所需的路径写。
增加第二个维度,即 Agent Note 的**类别**并将其编码在路径中:`{lifecycle}/{class}/yyyy-mm-dd-topic.md`。文件夹*就是*标签。文件位置声明其类别封闭集合限定为「这些文件夹且仅限这些」既有的 [verify-md-links](2026-06-18-markdown-cross-link-lint.md) 门禁已经保护移动文件所需的路径写。
### 六个类别的封闭集合
| 类别 | 涵盖范围 |
|---|---|
| `feature` | 面向用户或模型的新能。 |
| `feature` | 面向用户或模型的新能。 |
| `bug-fix` | 修正缺陷或填补事后复盘暴露的空白。 |
| `simplification` | 移除代码、行为或对外表面积,不引入新能。 |
| `simplification` | 移除代码、行为或对外表面积,不引入新能。 |
| `architecture` | 关于**交付源码**的结构性决策——包(package)之间的关系、运行时词汇。 |
| `process` | **围绕**代码的工具、策略或工作流,而非运行时行为。 |
| `testing` | 测试基础设施与策略。 |
`architecture``process` 的分界线**architecture** 关乎我们交付的源码;**process** 关乎围绕源码的工具与工作流。本 RFC 本身是一个 `process` 决策——它改变的是仓库的组织方式门禁,而 harness 的运行时行为——因此位于 `implemented/process/` 下。
`architecture``process` 的分界**architecture** 关乎我们交付的源码;**process** 关乎源码周边的工具与工作流。本 Agent Note 本身属于 `process` 决策它改变仓库的组织方式门禁,而不是 harness 的运行时行为因此位于 `implemented/process/` 下。
### 两道门禁
两者都是 `doc-sync`(文档同步门禁)的成员,风格与 `verify-md-wrap` 一致(tsx ESM,只校验不生成,首个违规即以非零退出码退出):
- **`scripts/verify-rfc-classification.ts`**——封闭集合与索引新鲜度。它断言生命周期文件夹下的每个文件都位于规范集合中的某个类别文件夹内(生命周期根目录下散落 `.md` 或未知类别文件夹均判定失败),并断言生成的 [INDEX.md](../../INDEX.md) 与从目录树重新渲染的结果逐字节一致(见[生成 RFC 索引表](2026-07-04-generate-rfc-index-tables.md))。规范类别集合以 `const` 形式定义在 `scripts/rfc-index.ts` 中——这是与生成器共享的机器真源——而 [README](../../README.md) 以行文形式记录它;类别*描述*保持手写,索引由机器生成
- **`scripts/verify-doc-refs.ts`**——源码注释中的文档引用。RFC 路径不仅 Markdown 引用,也被 TypeScript 文档注释引用(以仓库根为起点的路径,如 `docs/rfc/implemented/testing/2026-06-19-acp-snapshot-tests.md`)。`verify-md-links` 从未扫描过这些引用,因此重组可能使它们静默失效。此门禁扫描 `packages/**` `examples/**` 下仓库自有的 `.ts` 文件(排除构建产物 `lib/` `vendor/`),查找 `docs/….md` 形式的 token每个以仓库根为起点的路径解析并断言其存在。它要求 `.md` 扩展名,因此无扩展名的行文引用(`docs/postmortem/0001``docs/architecture.md § Extending The Harness`)不受影响
- **`scripts/verify-agent-note-classification.ts`**:定义封闭的生命周期与类别集合。它断言生命周期文件夹下的每个文件都位于规范集合中的类别文件夹内(生命周期根目录下散落 `.md` 或未知类别文件夹都会失败),并拒绝集中式 `INDEX.md`。规范集合位于 `scripts/agent-note-tree.ts` 中,[README](../../README.md)以行文记录每个类别
- **`scripts/verify-doc-refs.ts`**:检查引用文档的源码注释。Agent Note 路径不仅出现在 Markdown 中,也出现在 TypeScript 文档注释中(例如以仓库根为起点的 `.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md`)。`verify-md-links` 看不到这些引用,因此目录重组可能静默留下失效引用。该门禁扫描 `packages/**` `examples/**` 下仓库自有的 `.ts` 文件(排除构建 `lib/` `vendor/`),查找 `docs/….md` `.agents/notes/….md` token解析每个以仓库根为起点的路径并断言其存在。它要求使用 `.md` 扩展名,因此不处理无扩展名的行文。
## 曾考虑的替代方案
- **在每个文件中添加 `Classification:` 行文行**(紧邻 `Status:`),由门禁解析。可行,但它将路径已能承载的事实重复到文件中,且行内容可能与所在文件夹不一致。路径编码使标签与其存储合二为一,没有需要保持同步的东西。
- **设立 `refactor` 类别。** 与 `simplification` 几乎完全重叠;唯一有人试图用来区分的标准是「可观察行为是否改变?」,而 `simplification` 已经编码了这一点(它不改变)。一个类别即可,无需两个。
- **从文件系统自动生成索引。** 此处最初否决,以保持索引手写;后被[生成 RFC 索引表](2026-07-04-generate-rfc-index-tables.md)取代——当堆叠的提案潮使手写表格成为仓库中冲突最频繁的文档区域后,列表改为完全生成的 [INDEX.md](../../INDEX.md),而 README 行文保持人工维护
- **生成或手工维护的语料索引。** 不予采纳:生命周期/类别目录树才是权威结构;集中式清单会制造合并热点,却没有提供目录树导航或仓库搜索无法实现的发现能力。单独的[索引提案](../../rejected/process/2026-07-04-generate-agent-note-index-tables.md)记录了被放弃的生成形状
## 后果
-个 RFC 现在都位于一个类别文件夹下,索引在每个生命周期内按类别分组。读者只需扫一个标题即可看到所有简化类或所有测试决策。
-份 Agent Note 都位于一个类别文件夹下。读者浏览单个文件夹,即可查看某个生命周期内的全部简化或测试决策。
- `doc-sync` 链中多了两个快速 tsx 脚本;无新依赖(mdast/GFM 栈已因 `verify-md-wrap`/`verify-md-links` 而存在)。
- 新增类别是一个刻意的动作:修改 `scripts/rfc-index.ts` 中的 `const` [Classification 章节](../../README.md#classification),而非仅仅 `mkdir` 一个文件夹。门禁拒绝未知文件夹,因此临时类别无法悄混入。
- 源码注释中的文档引用现在也受门禁保护——一个被移动或重命名的文档如果被 `.ts` 注释引用,pre-push 钩子就会失败,堵住 `verify-md-links` 在结构上无法看到的一类漂移。
- 新增类别必须是显式决策:修改 `scripts/agent-note-tree.ts` 中的 `const` [Classification 章节](../../README.md#classification),而不是只用 `mkdir` 创建文件夹。门禁拒绝未知文件夹,因此临时类别无法悄混入。
- 源码注释中的文档引用同样受门禁约束:被 `.ts` 注释引用的文档一旦移动或重命名,`doc-sync` 与 CI 中的 `verify-doc-refs` 就会失败,从而堵住 `verify-md-links` 在结构上无法发现的一类漂移。
@@ -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
2026-06-20-core-data-structures-catalog.md: 933844d0f442306af238bfc459372b1c97414f87
2026-06-20-core-data-structures-catalog.zh.md: 620457b28d6b4aa36ec139da7bcd892ed78e2673
2026-06-20-core-data-structures-catalog.md: a2f5e0e7b06e34cb361e4944bbfa3692355a2cbe
2026-06-20-core-data-structures-catalog.zh.md: ee740fd1fb7e5c335bf80e57e27d0a4651be49bd
@@ -1,4 +1,4 @@
# RFC: 核心数据结构目录与 `ts type-equiv` 漂移门禁
# Agent Note: 核心数据结构目录与 `ts type-equiv` 漂移门禁
Status: implemented
@@ -6,13 +6,13 @@ Status: implemented
## 问题
一位想要理解 harness 的读者可以在 [architecture.md](../../../architecture.md) 中找到它的*行为*(服务映射、session/turn/step 生命周期、事件分类体系),但没有一个集中的地方描述它的*词汇*——即行为所操作的数据结构。类型形状只存在于源码中,分散在各个 `packages/*/src/types.ts` ,因此要理解什么是 `Message``SessionEvent``StreamChunk`」就得直接阅读声明。一份行文目录会有帮助,但如果目录是对类型定义的转述或粘贴复制,那么字段一改它就会腐烂——而失去同步的类型文档比没有更糟,因为读者会信任它。
试图理解 harness 的读者可以在 [architecture.md](../../../../docs/architecture.md) 中找到它的*行为*(服务、session/turn/step 生命周期、事件分类),却找不到一个统一描述其*词汇*的地方,也就是这些行为所传递的数据结构。类型形状只存在于源码中,散落在 `packages/*/src/types.ts` 各处,因此要理解什么是 `Message``SessionEvent``StreamChunk`”,就必须直接阅读声明。文目录会有帮助,但复述或复制粘贴类型定义的目录会在字段发生变化时立即腐化,而不同步的类型文档比没有文档更糟,因为读者会信任它。
因此这项工作包含两个交织的问题:**这样的目录应当收录什么**(范围界定问题——一个 harness 有数十个跨包类型,全部堆上去对谁都没帮助),以及**如何防止粘贴的类型定义漂移**(持久性问题)。本 RFC 记录两项决策。它的姊妹篇 [生成式 Cordis 事件 + 服务目录](2026-06-20-generated-cordis-catalog.md)*接线*轴的补充:本篇编目数据结构,那篇编目传递数据结构的事件服务。
因此这项工作有两个相互交织的问题:**这样的目录应包含什么**(范围问题——harness 有数十个跨包类型,把它们全部倾倒进来对谁都没帮助),以及**如何避免粘贴的类型定义发生漂移**(持久性问题)。本 Agent Note(agent 决策记录)记下了这两项决策。与它配套的[生成式 Cordis 事件服务目录](2026-06-20-generated-cordis-catalog.md)*接线*维度形成补充:本文对数据结构编目,另一篇则对传递这些结构的事件服务编目
## 决策
`docs/core-data-structures/` 文件夹编目词汇,并新增 `verify-type-equiv` doc-sync(文档同步门禁)门禁,确保每处粘贴的类型定义与源码逐字节一致
增的 `docs/core-data-structures/` 目录对这些词汇编目,并配有新的 `verify-type-equiv` 文档同步门禁,使每个粘贴的类型声明及其 JSDoc 与源码保持同步
### 何为"核心"——主干与 seam 的分界线
@@ -29,12 +29,12 @@ Status: implemented
### `ts type-equiv` 机制——既逐字又防漂移
持久性求很具体:文档应当展示**当前类型定义的原文**(让读者看到真实形状,而非述),**并且**机械保证与源码一致。仓库已经编译围栏 ` ```ts ` 块(`doc-typecheck`),但一个真正可编译的块需要 import 噪音,且只证明*可赋值性*而非*字节相等*——一个类型相同但改了名的字段会通过。因此:
持久性求很具体:文档展示当前类型声明与原始 JSDoc 的**逐字**内容(让读者看到真实形状和源码契约,而非述),**并且**机械方式保证与源码匹配。仓库已经编译 ` ```ts ` 围栏块(`doc-typecheck`),但真正接受类型检查的块需要导入噪音,且只证明*可赋值性*——字段改名或 JSDoc 变化仍可能通过。因此:
- 类型定义逐字粘贴到专用的 ` ```ts type-equiv ` 围栏中。`doc-typecheck` 识别该围栏并跳过它(裸定义不能独立编译),且**将排除在 opt-out 比例之外**——它是一个独立检查的类别,而非未检查的草稿
- 新增的 `scripts/verify-type-equiv.ts` 通过 TypeScript 解析器提取每个块,并断言其与声明的符号**逐字节匹配源码**——之所以选择这种方式而非编译式 `_Check` 可赋值性断言,是因为我们需要的属性是字节相等,而非可赋值性。
- 完整的类型声明及其 JSDoc 会逐字粘贴到专用的 ` ```ts type-equiv ` 围栏中。简洁的 ` ```ts public-api ` 围栏承载与源码等价的类环境投影,用于实现体不应进入目录的类。`doc-typecheck` 识别并跳过这两种围栏(裸声明无法独立编译),且**将它们排除在退出检查比例之外**——它们是单独受检的类别,而不是未经检查的草
- 新增的 `scripts/verify-type-equiv.ts` 通过 TypeScript 解析器提取每个块,并断言其声明结构和每条 JSDoc 注释都与所声明的符号匹配,只忽略格式空白和非 JSDoc 注释。普通块保留完整声明。`public-api` 投影保留类的公共字段、构造函数、访问器和方法及其原始 JSDoc,同时移除实现体以及私有或受保护成员。之所以选择而非编译式 `_Check` 断言,是因为目录所保留的是源码名称与文档一致性,而不是可赋值性。
- 来源信息存放在集中的 `scripts/type-equiv.manifest.json``{ doc, symbol, source }` 条目)中,**而非**行文中的指令注释。脚本强制执行 **1:1 对应**:每个 type-equiv 块恰好有一条 manifest 条目,反之亦然;因此一个块永远不会被静默漏检,一条条目也永远不会腐烂。
- 接入 `doc-sync`,因此与其他文档门禁在同一条 lefthook pre-push 和 CI 路径中运行。
- 接入 `doc-sync`,因此相关文档变更会在本地运行它,CI 也会与其他文档检查一起运行
### 维护是作者的职责,门禁作为兜底
@@ -43,18 +43,18 @@ Status: implemented
## 曾考虑的替代方案
- **平铺罗列所有跨包词汇**`BashExecRequest` 测试案例否决了它。如果 seam 词汇算"核心",目录对谁都没帮助;分层的主干与 seam 结构胜出。
- **编译式 `_Check` 可赋值性断言**替代逐字节源码匹配:否决,因为我们需要的属性是字节相等而非可赋值性——一个类型相同但改了名的字段会通过可赋值性检查
- **编译式 `_Check` 可赋值性断言**替源码匹配:否决。可赋值性不会保留名称或 JSDoc;同类型字段改名或契约注释变化仍会通过
- **来源信息作为行文中的指令注释**:否决,改用集中 manifest;其强制的 1:1 对应确保一个块永远不会被静默漏检,一条条目也永远不会腐烂。
## 验证教训
主干与 seam 规则在采纳前经过了 `BashExecRequest`、工具 schema 与定义、schema DSL、展示类型以及 session/persistence 拆分的逐一测试。
`verify-type-equiv` 必须扫描完整的 Markdown 范围,而不仅仅是 manifest 中列出的文档。否则一个未登记`type-equiv` 块会逃脱所声称的一一检查。因此门禁将此类块报告为遗留块。本 RFC 将这条快速失败的扫描规则与主干-seam 分界线和逐字匹配决策一并记录;生成式 Cordis 目录在[RFC](2026-06-20-generated-cordis-catalog.md) 中有对称的设计记录。
`verify-type-equiv` 必须扫描完整的 Markdown 范围,而不仅是清单点名的文档。否则,未列入清单`type-equiv`会逃过所宣称的一一检查。因此门禁将此类块报告为孤儿。本 Agent Note 将这条失败关闭扫描规则与主干/接缝、逐字匹配决策一并记录;生成式 Cordis 目录在[Agent Note](2026-06-20-generated-cordis-catalog.md) 中有对称的设计记录。
## 后果
- 词汇现在有一个**不会静默漂移**的唯一归属:源码中的字段重命名会在 pre-push 钩子和 CI 中导致 `verify-type-equiv` 失败,直粘贴内容刷新。
- 这些词汇现在有一个**无法悄然漂移**的唯一归属:源码中的字段或公共类成员发生变化后,`doc-sync` 和 CI 中 `verify-type-equiv` 会持续失败,直粘贴内容刷新。Cordis 服务方法仍由生成式服务目录负责,而不会在此重复。
- 主干与 seam 分界线是一个可复用的范围界定工具,而非一次性的:同一条「你编写/持有/接收的东西是核心;为其提供类型推导/渲染/持久化的机制是细节」规则,后来也被用于界定事件/服务目录的 harness 层与继承层分层。
- `ts type-equiv` 围栏是继 ` ```ts `(编译)和 ` ```ts ignore-check `(草稿)之后的第三种文档块类别。后续的姊妹门禁又增加了第四种 ` ```ts cordis-catalog `(生成签名),复用了相同的跳过并排除处理。
- 添加或重塑核心类型现在附带一项文档义务,作者必须履行(门禁无法检测缺失的*新*类型),由 `dsh-code-review` 检查清单兜底。
@@ -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
2026-06-20-generated-cordis-catalog.md: 8b979764c1ae6693b77817fc84381e40e56f8d36
2026-06-20-generated-cordis-catalog.zh.md: 608ab3e0429fab43abec634ce0955f46306d8529
2026-06-20-generated-cordis-catalog.md: b5957cf06a9316447aae70183de462024bb24be3
2026-06-20-generated-cordis-catalog.zh.md: 33983d7693e1c67ac210b3ce23d28e7d46571ff9
@@ -1,4 +1,4 @@
# RFC: 生成式 Cordis 事件与服务目录
# Agent Note: 生成式 Cordis 事件与服务目录
Status: implemented
@@ -8,13 +8,13 @@ Status: implemented
插件作者需要两个参考面,而此前没有任何单一文档能提供:他们可以监听的每一个 Cordis **事件**(含精确签名与分发模式),以及他们可以调用的每一个 `ctx.<key>` **服务**(含精确接口)。相关信息虽然存在,但散落各处:`docs/architecture.md` 中一张手工维护的事件分类*表格*(名称 + 行文描述的 Mode/Purpose,由 `verify-event-taxonomy` 做名称集合校验)、一张服务映射表(8 行角色描述),以及 `interface Events` / `interface Context` 声明本身。分类表格还有一个盲区:它无法捕获全新的*未记录*事件——名称集合校验器只检查两侧已有的名称。
这是 [core-data-structures 目录](../../../core-data-structures/core.md)[RFC](2026-06-20-core-data-structures-catalog.md))在连线轴上的补充:后者编目的是 agent loop(智能体循环)流转的*数据结构*(经校验的手工粘贴);本 RFC 编目的是移动这些数据结构的*事件服务*。
这是[核心数据结构目录](../../../../docs/core-data-structures/core.md)[Agent Noteagent 决策记录)](2026-06-20-core-data-structures-catalog.md))在接线维度上的补充:前者对循环传递的*数据结构*编目(经验证的手工粘贴),本文则对传递它们的*事件服务*编目
## 决策
从源码生成目录,取代手工维护表格并校验子集的方式。
`scripts/gen-cordis-catalog.ts` 使用 TypeScript 编译器 API声明和源码 JSDoc 分别输出事件参考与服务参考。事件包含分模式;服务包含公签名。确定性的 `--write``--check` 模式使两个页面成为生成产物,新鲜度`doc-sync`(文档同步门禁)强制保障
`scripts/gen-cordis-catalog.ts` 使用 TypeScript 编译器 API根据声明和源码 JSDoc 分别生成事件与服务参考。事件包含分模式及其原始成员 JSDoc;服务包含公签名及各方法的原始 JSDoc。确定性的 `--write``--check` 模式使两个页面成为生成产物,`doc-sync` 强制检查新鲜度
纯生成在此处是正确的,因为代码库足够规范,AST 就是全部事实:每个事件/服务名称都是字符串字面量,可以往返映射到静态声明——不存在动态命名的事件,也不存在仅运行时的服务。因此生成的文档不可能出错,且从结构上消除了未记录事件的缺口(生成器枚举源码,而非校验手写子集)。
@@ -22,8 +22,8 @@ Status: implemented
- **`@mode` 标签,交叉校验。** 每个 harness 事件的 JSDoc 携带一个显式的 `@mode emit|waterfall|parallel|serial` 标签;缺少标签时生成器直接报错。当签名形状具有决定性时——尾部参数为 `next: () => …` 在结构上即为 waterfall(瀑布式事件)——生成器断言标签与之一致,矛盾时直接报错。emit/parallel/serial 的区别在结构上不可见(`session/flush` 返回 `Promise<void> | void` 且无 `next`,有序的 `agent/pre-step` 检查点亦然),因此信任标签。编写规则见 [AGENTS.md](../../../../AGENTS.md)。
- **分层范围。** harness 层(8 个 `@deepseek-ai/dsh-*` 服务及其事件)从源码完整渲染。继承层(cordis-core 的 `ctx.on/emit/effect/provide/…` + `internal/*` 事件 + loader/hmr/timer)是插件同样可见的固定 vendor 源码;它从生成器中一张人工维护的表格简洁渲染(名称 + 一行描述 + 源码指针),而非遍历 vendor AST。原因是 cordis-core 的 `Context` 混合了真正的 ctx 成员与非服务字段(`root``baseUrl``logger`),且 vendor 接口面仅在有意的 vendor 同步时才变化。
- **交叉链接到数据结构目录。** 签名中类型名(`GenerateOptions``StreamChunk``ToolDefinition` 等)链接到记录该类型的 core-data-structures 页面。映射是生成器中一个小型的人工维护常量,而非 `type-equiv.manifest.json`——后者记录的是 `…Map` 符号,而签名引用的是派生联合类型名,且有少数符号出现在两个页面上
- **专用围栏。** 签名块使用 ` ```ts cordis-catalog ` 信息字符串,`doc-typecheck` 识别跳过(裸签名片段不能独立编译),并排除在 opt-out 比例之外——与 `type-equiv`获得相同待遇
- **指向数据结构目录的交叉链接。** 签名中由仓库拥有的每个类型名(`GenerateOptions``StreamChunk``ToolDefinition`……)都会通过人工维护的映射链接到其主要核心数据结构页面。AST 遍历采用失败关闭策略:每个参数、泛型约束/默认值和返回类型引用都必须已映射、是签名自身的类型参数、是点名的 TypeScript/Cordis 基础类型,或带有点名的例外及其非目录文档归属。违规会连同源码位置汇总报告,并点明相应的归属列表。该映射不会复用 `type-equiv.manifest.json`,因为后者记录 `…Map` 符号,而签名引用派生联合类型名,并且会在多个页面列出某些符号
- **专用围栏。** 签名块使用 ` ```ts cordis-catalog ` 信息字符串,并把原始事件或公共方法 JSDoc 直接放在其声明之前。`doc-typecheck` 识别跳过这些裸片段,将其排除在退出检查比例之外——与 `type-equiv`的处理相同
本决策**取代** [doc-sync 强制](2026-06-11-doc-sync-enforcement.md)中事件分类的那一半:`verify-event-taxonomy` 及其 `docs/architecture.md` 表格退役(architecture.md 的标题保留,正文改为指向目录;服务映射的角色表格作为人工行文保留)。doc-typecheck、verify-md-wrap、verify-md-links 和 verify-type-equiv 不受影响。
@@ -31,11 +31,11 @@ Status: implemented
- **校验而非生成(退役的分类检查所做的事)**:*仅对本参考面*反转了这一策略。此处的数据可以机械地完整获取,因此生成严格强于对手工表格做名称集合校验(完整签名、不会漂移、能捕获未记录事件)。
- **遍历 vendor AST 以获取继承层**:否决,改用人工维护表格。cordis-core 的 `Context` 混合了真正的 ctx 成员与非服务字段,且固定的 vendor 接口面仅在有意同步时才变化。
- **复用 `type-equiv.manifest.json` 作为签名交叉链接映射**:否决,改用小型人工维护常量。manifest 记录的是 `…Map` 符号,而签名引用的是派生联合类型名,且有少数符号出现在两个页面上
- **复用 `type-equiv.manifest.json` 作为签名交叉链接映射**:否决,改用完整的人工维护常量和失败关闭覆盖。清单记录 `…Map` 符号,而签名引用派生联合类型名,并且会在多个页面列出某些符号。显式映射让每个渲染目标和每个非目录例外都成为可评审的决策
## 后果
- 目录不会漂移:源码变更而已提交文件未反映时,`verify-cordis-catalog` 在 pre-push 钩子和 CI 中失败。新事件缺少 `@mode` 标签,或标签与签名矛盾,生成器直接报错
- 事件的行文描述现在有了唯一归属——声明处的 JSDoc。JSDoc 写得单薄,目录条目就单薄,这迫使作者在源码处做好文档(生成器是 AGENTS.md「每个导出都有语义 JSDoc」规则的强制函数)
- 目录不会发生漂移:提交文件未反映的源码变化会使 `doc-sync` 和 CI 中的 `verify-cordis-catalog` 失败。新事件缺少 `@mode` 标签标签与签名冲突,或签名类型未分类,都会直接使生成器失败
- 事件与服务方法契约只有一个归属——声明处的 JSDoc。目录会在生成的签名块中重复该原始 JSDoc,并使用其描述部分作为条目正文,因此单薄的源码文档只会生成单薄的目录条目
- 继承层是手工摘要,因此 vendor 同步若新增或重命名了 cordis-core 事件或 `ctx` 成员,需要同步编辑 `gen-cordis-catalog.ts` 中的人工维护表格。这是不遍历固定 vendor 源码的有意代价;它很少变化,且在生成器中有明确标注。
- `verify-event-taxonomy.ts` 被删除,`docs/architecture.md` 的事件表格也已移除;之前链接到特定表格行的人现在会落在生成目录上。
@@ -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
2026-07-02-tool-schema-catalog.md: fe7ec47ac89c157482f612a374ca8773d7d0867f
2026-07-02-tool-schema-catalog.zh.md: 1567ff5dd5c53da501a86412d0e29127d33d29f6
2026-07-02-tool-schema-catalog.md: c8cc69df428f6eee0f66ed976865afe2a0702448
2026-07-02-tool-schema-catalog.zh.md: 7caa9e8747d8fd09806342035d185e664f7be7b7
@@ -1,4 +1,4 @@
# RFC: 生成式工具 schema 目录(启动并采集)
# Agent Note: 生成式工具 schema 目录(启动并采集)
Status: implemented
@@ -10,7 +10,7 @@ Status: implemented
## 决策
通过**启动每个工具插件并读取其注册 schema** 来生成目录,而非解析源码。`scripts/gen-tool-catalog.ts` 将每个已发布的工具包(package)挂载到一个新的 Cordis `Context`(带 `SystemPrompt` + `ToolRegistry` 以及插件 `apply` 所读取的注入 seam),调用 `ctx.tools.schemas()`(即发送给模型的 `ToolSchema[]`),dispose(资源释放)该 context,然后为每个包渲染一个 `## <package>` 节,每个工具一个 ` ```json ` `parameters` 块。它与 `gen-cordis-catalog` / `gen-module-graph` 的 CLI(命令行界面)形态一致:默认 `--write` 重新生成`--check` 在已提交副本陈旧时失败输出确定性(按 manifest(元数据清单排序,工具按名称排序)。`verify-tool-catalog`(即 `--check`)在 `doc-sync`(文档同步门禁)内运行,因此新鲜度门禁在 lefthook pre-push 和 CI 路径中与其他文档门禁一同触发
目录通过**启动每个工具插件并读取其注册 schema** 来生成,而不是解析源码。`scripts/gen-tool-catalog.ts` 在全新的 Cordis `Context` 上挂载每个已发布工具包(带 `SystemPrompt``ToolRegistry` 以及插件 `apply` 所读取的注入接缝),调用 `ctx.tools.schemas()`——也就是发送给模型的确切 `ToolSchema[]`——随后释放上下文,并为每个包渲染一个 `## <package>` 节,每个工具附带一个 ` ```json ` `parameters` 块。它与 `gen-cordis-catalog` / `gen-module-graph` 的 CLI 形状一致:默认 `--write` 重新生成;提交副本陈旧时 `--check` 失败输出具有确定性(按清单排序,工具按名称排序)。`verify-tool-catalog`(即 `--check`)在 `doc-sync` 内运行,因此相关文档变更和 CI 会执行同一项新鲜度检查
### 为何启动而非解析(核心要点)
@@ -21,7 +21,7 @@ Cordis 目录是纯 TypeScript AST 遍历,因为每个事件/服务名都是
- `tool-subagent` 的工具名是 `config.toolName ?? 'subagent'`——加载时选定,并非字面量。
- MCP 插件可以通过 `ctx.tools.register()` 直接注册**原始 JSON Schema**,完全不经过 `defineTool`,因此结构化枚举 `defineTool(` 调用点会遗漏。
唯一忠实的真源是插件加载后注册表实际持有的 schema。启动[测试策略](../../../testing.md)中验证世界,而非自我报告」这一原则在文档生成器上的应用:读取已发布产物,而非对它的再推导
唯一忠实的事实来源,是插件加载后注册表实际持有的 schema。启动插件是把[测试策略](../../../../docs/testing.md)中验证现实,而非自我报告”的准则应用到文档生成器:读取已发布产物,而非重新推导一份
### 恢复「不会静默遗漏」的保证
@@ -33,7 +33,7 @@ Cordis 目录是纯 TypeScript AST 遍历,因为每个事件/服务名都是
### 范围
`packages/*/tool-*` 下已发布的产品工具包,每个默认配置启动`dsh-tool-bash``bash``bash_output``bash_kill`)、`dsh-tool-todo``todo_write``dsh-tool-subagent``subagent`)。`examples/` 下的演示工具(`echo`)被排除,与 Cordis 目录仅覆盖 packages 的范围一致——演示工具不属于读者所查阅的产品接口
`packages/*/tool-*` 下已发布的产品工具包,每个都使用默认配置启动,包括 `dsh-tool-bash``bash``dsh-tool-tasks``task_output``task_list``task_kill``dsh-tool-subagent``subagent`)。仅供示例使用的工具不在范围内
目录的单位是包,而非每个配置化的工具实例。每个包以默认配置启动一次;加载时的别名(如 `subagent_fork`)会注明,但不枚举所有部署排列。部署清单是一个独立的、无界的接口。
@@ -49,7 +49,7 @@ schema 块使用 ` ```json `,而非自定义的 `ts` 系围栏。`doc-typechec
## 后果
- 目录不会漂移:工具 schema 变更而已提交文件未反映,`verify-tool-catalog` 会在 pre-push 钩子和 CI 中失败。新 `tool-*` 包未加入 manifest 则完整性守卫直接报错
- 目录不会发生漂移:提交文件未反映的工具 schema 变化会使 `doc-sync` 和 CI 中的 `verify-tool-catalog` 失败。新增的 `tool-*`未加入清单,会直接使完整性守卫失败
- 工具描述文本有唯一归属——源码中 `defineTool``description`——生成的条目质量取决于它,与 Cordis 目录对事件 JSDoc 施加的强制力相同。
- 生成器导入并执行工作区包(这是仓库中第一个这样做的脚本;其他脚本只读文本)。它通过根 `tsconfig``paths` 映射在 `tsx` 下运行,使用与演示和测试相同的未构建源码路径,因此不需要构建步骤。
- 未来某个工具背后新增一个能力 seam,意味着 manifest 中需要新增一条配方条目(声明要挂载哪些 seam)。这正是上文指出的有意为之的手写成本;仅在新增工具包时才需变更。
@@ -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
2026-07-03-documentation-graph-atlas.md: 3cb8e685452a5b013798d0c3f661fca218b3d69d
2026-07-03-documentation-graph-atlas.zh.md: f03268c63c6d834c59af6cf831a34e5a5eaf40d7
2026-07-03-documentation-graph-atlas.md: 9a30b13f9db6ceb2715517230e349cbe083850ec
2026-07-03-documentation-graph-atlas.zh.md: 438f1e6de696712d8dd2f1b503d15d1b42506cf9
@@ -1,4 +1,4 @@
# RFC: 面向维护者与 SDK 用户的文档关系图索引
# Agent Note: 面向维护者与 SDK 用户的文档关系图索引
Status: implemented
@@ -6,7 +6,7 @@ Status: implemented
## 问题
仓库已有若干高可信度的文档面,各自覆盖不同维度:[module-graph.md](../../../module-graph.md) 由包(package`peerDependencies` 生成;生成 [Cordis events](../../../cordis-catalog/events.md) 与 [services](../../../cordis-catalog/services.md) 目录 Cordis `Events``Context` 声明生成;[tool-catalog.md](../../../tool-catalog.md) 通过启动已发布的 tool 插件生成;[core-data-structures/](../../../core-data-structures/core.md) 使用 `ts type-equiv`保持粘贴的类型定义与源码同步。
仓库已有若干高可信文档面,各自覆盖不同维度:[module-graph.md](../../../../docs/module-graph.md) 根据包`peerDependencies` 生成;生成 [Cordis 事件](../../../../docs/cordis-catalog/events.md)和[服务](../../../../docs/cordis-catalog/services.md)目录根据 Cordis `Events``Context` 声明生成;[tool-catalog.md](../../../../docs/tool-catalog.md) 通过启动已发布工具插件生成;[core-data-structures/](../../../../docs/core-data-structures/core.md) 使用 `ts type-equiv`使粘贴的类型定义与源码保持同步。
这些参考文档是准确的,但大多是目录式的。维护者仍需自行综合关系:哪些包构成一个能力 seam、哪个应用组装了具体的主干、哪些事件是持久的而哪些是实时的、钩子或策略插件在哪里可以拦截工作、以及哪个面向模型的工具依赖哪个服务。SDK 用户从另一个角度面临同样的问题:「我想要某种行为,应该安装或加载哪个包?应该扩展哪个事件/服务/工具?」
@@ -14,7 +14,7 @@ Status: implemented
## 决策
新增生成关系图文档,索引位于 [docs/graph-atlas.md](../../../graph-atlas.md),由专用生成器产出,并通过 `pnpm run verify-doc-graphs` 及既有的目录新鲜度检查(作为 `doc-sync` 的一环)进行验证。
新增生成关系图文档,由聚焦的生成器产出并在 [docs/graph-atlas.md](../../../../docs/graph-atlas.md) 建立索引;作为 `doc-sync` 的一部分,通过 `pnpm run verify-doc-graphs` / 现有目录新鲜度检查进行验证。
该索引是既有目录之上的关系层。它不取代精确的参考文档,而是链接到它们并解释各部分如何组合在一起。
@@ -28,20 +28,21 @@ Status: implemented
### 首批发布的索引
首批索引链接十关系面。包拓扑工具-包能力映射位于已有的生成目录中(这些目录已拥有相应事实);其余聚焦图表由 `scripts/gen-doc-graphs.ts` 生成。
索引链接十一种关系面。包拓扑工具包所提供的功能位于已经拥有这些事实的现有生成目录中;其余聚焦图表由 `scripts/gen-doc-graphs.ts` 生成。
| 关系图 | 维护模式 | 真源 |
|---|---|---|
| [模块依赖图](../../../module-graph.md) | generated | `packages/*/*/package.json` 的 peer dependencies 加包分组路径 |
| [工具 schema 目录与包映射](../../../tool-catalog.md) | generated | 启动集的工具 schema 加工具-包的服务/副作用元数据 |
| [能力 seam 与核心服务](../../../capability-seams.md) | hybrid generated | Cordis 服务声明 `gen-doc-graphs.ts` 中的角色 manifest |
| [echo-agent 应用组合](../../../../examples/echo-agent/composition.md) | hybrid generated | `examples/echo-agent/cordis.yml` 插件列表加人工策划的应用/bundle 展开 |
| [coding-agent 应用组合](../../../../examples/coding-agent/composition.md) | hybrid generated | `examples/coding-agent/cordis.yml` 插件列表加人工策划的应用/bundle 展开 |
| [模块依赖图](../../../../docs/module-graph.md) | 生成式 | `packages/*/*/package.json` 的 peer dependency 与包分组路径 |
| [工具 schema 目录与包映射](../../../../docs/tool-catalog.md) | 生成式 | 启动后采集的工具 schema,以及工具包服务/效应元数据 |
| [能力接缝与核心服务](../../../../docs/capability-seams.md) | 混合生成式 | Cordis 服务声明,以及 `gen-doc-graphs.ts` 中的角色清单 |
| [tui-agent 应用组合](../../../../examples/tui-agent/composition.md) | 混合生成式 | `examples/tui-agent/cordis.yml` 插件列表,以及人工维护的应用/bundle 展开 |
| [headless-agent 应用组合](../../../../examples/headless-agent/composition.md) | 混合生成式 | `examples/headless-agent/cordis.yml` 插件列表,以及人工维护的应用/bundle 展开 |
| [cordis-agent 应用组合](../../../../examples/cordis-agent/composition.md) | 混合生成式 | `examples/cordis-agent/cordis.yml` 插件列表,以及人工维护的应用/bundle 展开 |
| [acp-agent 应用组合](../../../../examples/acp-agent/composition.md) | hybrid generated | `examples/acp-agent/cordis.yml` 插件列表加人工策划的应用/bundle 展开 |
| [事件生产者/消费者矩阵](../../../event-producer-consumer.md) | hybrid generated | Cordis 事件声明、AST 扫描的 `ctx.on/emit/parallel/serial/waterfall` 调用点,以及显式动态分覆盖 |
| [agent 轮次与步骤生命周期](../../../agent-lifecycle.md) | curated | architecture.md 循环生命周期、Cordis 目录链接与会话事件语义 |
| [工具执行流水线](../../../tool-execution-pipeline.md) | curated | 工具流水线语义与 `tools/execute` waterfall(瀑布式事件) |
| [ACP 快照回放](../../../../packages/ui/acp/snapshot-replay.md) | curated | 快照 harness 行为 |
| [事件生产者/消费者矩阵](../../../../docs/event-producer-consumer.md) | 混合生成式 | Cordis 事件声明、AST 扫描的 `ctx.on/emit/parallel/serial/waterfall` 位置,以及显式动态分覆盖 |
| [agent turn 与 step 生命周期](../../../../docs/agent-lifecycle.md) | 人工维护 | architecture.md 循环生命周期、Cordis 目录链接,以及 session 事件语义 |
| [工具执行线](../../../../docs/tool-execution-pipeline.md) | 人工维护 | 工具线语义与 `tools/execute` waterfall(瀑布式事件)|
| [ACPAgent Client Protocol快照回放](../../../../packages/ui/acp/snapshot-replay.md) | curated | 快照 harness 行为 |
### 为什么由生成器拥有文档
@@ -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
2026-07-04-cordis-jsdoc-completeness-gate.md: 8eaa997eb734b32e7e4a45d96def15dc541af1d5
2026-07-04-cordis-jsdoc-completeness-gate.zh.md: a49189b3162fbd0eba9e043c683f0fc56d0bf7d6
2026-07-04-cordis-jsdoc-completeness-gate.md: c7c39986414437ce4d0d4c64f9e25f47485fdf5c
2026-07-04-cordis-jsdoc-completeness-gate.zh.md: f1e7ec824ebae119c1cb27ca4dd9f0d8330dde1a
@@ -1,4 +1,4 @@
# RFC: 针对 Cordis 对外服务接口的 JSDoc 完整性门禁
# Agent Note: 针对 Cordis 对外服务接口的 JSDoc 完整性门禁
Status: implemented
@@ -12,7 +12,7 @@ AGENTS.md 中的规则(「每个导出都有解释语义的 JSDoc」)只能
## 决策
扩展 `scripts/gen-cordis-catalog.ts`同一次遍历同一 `@mode` 先例),对其编目的所有内容强制 JSDoc 完整性。`verify-cordis-catalog``doc-sync`(文档同步门禁)内运行,CI 和 lefthook pre-push 钩子都已执行该命令,因此门禁无需新增任何接线(质量门禁原则:单一真源)
扩展 `scripts/gen-cordis-catalog.ts`——复用同一次遍历同一 `@mode` 先例——对它编目的所有内容强制执行 JSDoc 完整性要求`verify-cordis-catalog``doc-sync` 内运行,因此相关文档变更和 CI 会执行同一门禁无需另行接线
契约如下:
@@ -22,20 +22,20 @@ AGENTS.md 中的规则(「每个导出都有解释语义的 JSDoc」)只能
- **遍历可检查的显式性**:门禁是纯 AST 遍历(不使用类型检查器),因此服务方法必须显式标注返回类型(推断的返回类型无法分类),接口参数必须是简单标识符(解构模式没有名称供 `@param` 匹配)。
- **违规聚合**为一条错误信息,列出所有违规项——修复时一次看到完整清单。此前快速失败的 `@mode` 检查也移入同一份聚合报告,消息文本不变。
这些标签**仅用于强制检查**`parseJsDoc` 现在在遇到第一个块标签时截止描述性文字(标准 JSDoc 语义,同时也防止多行标签描述泄漏到目录中充当正文),因此 `@param`/`@returns` 不会改变渲染出的目录
生成器保留同一源码注释的两种视图`parseJsDoc` 第一个块标签处结束条目正文,而 `ts cordis-catalog` 签名块包含原始 JSDoc,并完整保留 `@param``@returns` `@mode`。因此,读者可以看到完整的源码契约,而块标签文本不会泄漏到周围正文中
`packages/core/agent/tests/gen-cordis-catalog.spec.ts` 中的负路径测试对合成 fixture(测试前置数据)运行 `collectEvents`/`collectServices`,验证每条守卫都会触发且免检规则成立。撰写规则写在根 [AGENTS.md](../../../../AGENTS.md) 的约定条目中,与 `@mode` 规则并列。
## 曾考虑的替代方案
- **ESLint 规则**:无法看到该范围的机器定义(哪些 `interface Events` 成员、哪些 `ctx.<key>` 类构成 Cordis 对外服务接口);目录生成器在每次运行时恰好计算这层映射,因此门禁放在那里。
- **将标签渲染到目录中**:曾考虑将服务部分重构为逐方法条目,但有意推迟:方法文档的消费场景是源码 JSDoc 加 IDE 悬停,目录保持索引定位
- **将每个方法展开为单独的正文小节**:否决。目录保留一个服务章节和一个签名块,以维持可扫读性;附着于每个声明的 JSDoc 则在原处保留完整的方法契约
- **逃逸标签**:不设。该接口面小且经过策展(采纳时 12 个服务、57 个方法、27 个事件),要点在于检查不可豁免。
## 后果
-事件或服务方法时,若参数或返回值未写文档则无法落地:生成器拒绝重新生成,`verify-cordis-catalog` 在 pre-push 和 CI 失败。采纳时发现的约 139 处缺口在同一变更中补齐,门禁以绿色状态落地。
- 新事件或服务方法不能带着未记录的参数或结果落地:生成器拒绝重新生成,`verify-cordis-catalog` 也会使 `doc-sync` 和 CI 失败。采纳时发现的约 139 处缺口在同一变更中补齐,因此门禁以绿色状态落地。
- 服务接口必须显式标注返回类型并使用标识符参数。两项约束在采纳时均未构成限制(所有方法已有标注;不存在解构的 seam 参数);但二者现在是承重要求,违反时会被机械检测到。
- AGENTS.md 中通用的 JSDoc 规则(「一行能说清就用一行」)在此接口上获得了更严格的特例:仅当方法无参数且返回 void 时,一行摘要才足够。
-`next``this``@param` 合法但不检查——这是有意的不对称:门禁强制载荷契约,拒绝要求样板代码。
- 渲染出的目录不受这些标签影响(正文在第一个块标签处截止)。如果后续需要方法级渲染,那是一个独立的目录设计决策,而非本门禁的缺口
- 每个生成的事件或方法片段都带有其原始 JSDoc,而正文摘要不含标签。因此,源码编辑会同时刷新可读索引和签名旁展示的确切契约
@@ -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
2026-07-04-doc-tiers-and-budgets.md: ddbce540c68b9b35bb7e6a48b8db8abbc3a7a398
2026-07-04-doc-tiers-and-budgets.zh.md: d3f08d337af34b5def41792232330180123dd585
2026-07-04-doc-tiers-and-budgets.md: 9993eda6b0c7f1f8e5908dbd85fcfb4e5c6b3d1d
2026-07-04-doc-tiers-and-budgets.zh.md: d708919217a086928951c4acfed57985194301c4
@@ -1,4 +1,4 @@
# RFC: 文档分层、预算与上限门禁
# Agent Note: 文档分层、预算与上限门禁
Status: implemented
@@ -6,14 +6,14 @@ Status: implemented
## 问题
尽管已有写作指导,常设文档仍然积累了重复规则、述的事、重复的包package)映射和陈旧的 RFC 摘要。仅靠评审无法阻止这种膨胀,因此仓库需要在文档分类体系之外增加一道机械化的预算约束
尽管已有写作指导,常设文档仍不断累积重复规则、反复讲述的事、重复的包映射,以及陈旧的 Agent Note(agent 决策记录)摘要。仅靠评审无法阻止这种增长,因此仓库需要在文档分类之外再配一套机械预算
## 决策
- **分层分类体系,每条事实只有一个归属地。** [docs/AGENTS.md](../../../AGENTS.md) 是文档标准:它为每 Markdown 层级指定唯一职责(常设指令、系统图、类型目录、决策记录、事故叙事、实操手册、逐包契约、生成目录、工作流),禁止在归属层级之外重述事实(应以链接代替),并附带一份在撰写或评审任何文档时使用的余检查清单。
- **范围、硬约束的预算门禁。** [scripts/verify-doc-budgets.ts](../../../../scripts/verify-doc-budgets.ts) `doc-sync`(文档同步门禁)[scripts/doc-budgets.manifest.json](../../../../scripts/doc-budgets.manifest.json) 列出的每文档都必须低于其字数上限(`wc -w` 语义,整个文件),且受预算约束的文件如果缺失也会导致门禁失败,防止重命名后预算被静默遗留。范围刻意限定为容易膨胀的常设文档根目录和子树的 `AGENTS.md``architecture.md``packages/README.md`,以及它们将内容分流到的常设策略文档(`docs/testing.md``docs/defensive-patterns.md`)。参考文档、RFC 和 package README 不设预算:每一行都是事实,长度是合理的,由评审加冗余检查清单来管控
- **每项事实只归属一处的层级分类。**[docs/AGENTS.md](../../../../docs/AGENTS.md) 是文档标准:它为每 Markdown 层级分配单一职责(常设指令、系统图、类型目录、决策记录、事件故事、操作指南、各包契约、生成目录、工作流),禁止在事实归属层级之外重复陈述(应改为链接),并包含编写或评审任何文档时使用的余检查清单。
- **范围窄且严格的预算门禁。**[scripts/verify-doc-budgets.ts](../../../../scripts/verify-doc-budgets.ts) `doc-sync`[scripts/doc-budgets.manifest.json](../../../../scripts/doc-budgets.manifest.json) 列出的每文档都必须低于其字数上限(采用 `wc -w` 语义,统计整个文件);预算内文件缺失也会使门禁失败,使重命名无法悄然遗落其预算。范围刻意只涵盖容易膨胀的常设文档——根目录和子树`AGENTS.md` 文件`architecture.md``packages/README.md`,以及它们将内容移入的常设策略文档(`docs/testing.md``docs/defensive-patterns.md`)。参考文档、Agent Note 和包 README 不设预算:只要每一行都是事实,长度在这些位置就是合理的;评审和赘余检查清单负责约束它们
- **上限是只进不退的执行红线。** 上限设定为文档当前字数的至少 105%(留出工作余量,使日常措辞调整能通过,而真正的膨胀仍会触发门禁),并随着文档被精简到目标预算而同步下调、保持该余量(根 `AGENTS.md` ≤ 1,500 词;`architecture.md` ≤ 1,800;子树 `AGENTS.md` ≤ 600`packages/README.md` ≤ 600)。推进机制与[翻译配对的 `required` 清单](2026-07-02-bilingual-docs-and-pairing-gate.md)相同。门禁变红时,修复方式是按分类体系迁移或压缩内容;只有在 PR(Pull Request)描述中给出明确理由时才允许提高上限,manifest(元数据清单)的 diff 本身即为可评审的动作。
- **轻量工作流 skill(技能),契约文档。** [.agents/skills/dsh-doc-standards](../../../../.agents/skills/dsh-doc-standards/SKILL.md) 承载放置/审计/红灯修复工作流,并文档标准作为真源,与 [dsh-translate-docs](../../../../.agents/skills/dsh-translate-docs/SKILL.md) i18n 契约的分工方式一致
- **精简的工作流 skill(技能),契约文档。**[.agents/skills/dsh-doc-standards](../../../skills/dsh-doc-standards/SKILL.md) 承载放置/审计/红灯门禁工作流,并文档标准为事实来源,与 [dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md) i18n 契约之间的分工相同
## 曾考虑的替代方案
@@ -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
2026-07-04-persistence-log-catalog.md: d92666fc8bafcf42542940fe399d9a3ece20c173
2026-07-04-persistence-log-catalog.zh.md: a3524b251522050567be240f28944872e725a24e
2026-07-04-persistence-log-catalog.md: 1529f41b485c1bc8ca029c0a9264574fa7a886a0
2026-07-04-persistence-log-catalog.zh.md: 24ac6caf8331c579828e707e289f28827caa072f
@@ -1,4 +1,4 @@
# RFC: 生成式持久化日志事件目录
# Agent Note: 生成式持久化日志事件目录
Status: implemented
@@ -6,19 +6,19 @@ Status: implemented
## 问题
`SessionEventMap` 是磁盘的词汇,但其声明分散在拥有它的 session 包package)与声明合并中。生成式持久化目录是所有事件 payload 的唯一参考;手工维护的表格会漂移,被移除。这些记录不是 Cordis 事件观察者通过一的 `session/event` 总线事件接收它们,因此 Cordis 目录无法覆盖。生成器发现所有声明,doc-sync(文档同步门禁)的新鲜度门禁拒绝遗漏或陈旧输出。
`SessionEventMap` 是磁盘格式的词汇,但其声明分散在所属 session 包声明合并中。生成式持久化目录是所有事件、各自完整 payload 声明与源码 JSDoc,以及共享 `SessionEvent` 信封的唯一参考;手工维护的表格会发生漂移,因此被移除。这些记录不是 Cordis 事件——观察者通过一的 `session/event` 总线事件接收它们——所以 Cordis 目录无法覆盖。生成器发现所有声明,文档同步新鲜度门禁拒绝遗漏或陈旧输出。
## 决策
从源码生成 `docs/persistence-catalog.md`,配合新鲜度门禁,作为第四个参考面:持久化会话日志可以包含的*记录*,与 Cordis 目录(接线)、核心数据结构(词汇)和工具目录(工具)互补。
`gen-persistence-catalog.ts` 使用 TypeScript AST 扫描所有拥有方与声明合并的 `SessionEventMap`。它渲染源码 JSDoc、payload 类型、派生的 surface 徽章、参考链接源码位置。doc-sync 新鲜度检查会拒绝任何词汇变更后未重新生成目录的情况
`gen-persistence-catalog.ts` 使用 TypeScript AST 扫描每个所属及声明合并的 `SessionEventMap`。它从前置 JSDoc 开始渲染每个成员,直至完整的 payload 类型,保留嵌套属性注释且只移除其容器缩进;同时粘贴构成持久化信封的所属 `SessionEventType``SurfaceEventType``SurfaceOp``SessionEvent` 声明。派生的 surface 徽章、参考链接源码位置仍位于声明块之外。文档同步新鲜度检查会拒绝目录尚未重新生成的词汇或信封变更
具体选择:
- **JSDoc 完整性,强制执行。** 每个成员必须带有描述性文字——JSDoc 即为目录条目,与 Cordis 目录对总线事件施加的强制机制相同。成员上的 `@mode` 标签是硬错误:dispatch mode 属于 Cordis 总线事件,日志事件没有 mode,该标签会被误读为「此事件以模式 X 在总线上触发」。违规项聚合为一条错误,列出所有违规
- **强制保证 JSDoc 完整性。** 每个成员和渲染出的信封类型都必须带有描述正文,完整的源码 JSDoc 会在目录中保持附着于其声明。`@mode` 标签是硬错误:分派模式属于 Cordis 总线事件,持久化记录没有这种模式。所有违规会汇总为一条错误,列出每个违规
- **surface 徽章由派生得出,而非手工列举。** `SurfaceEventType`(产生 LLM(大语言模型)消息且可能携带 `surfaceOp` 的子集)从拥有方包中的 union 声明解析;如果 union 成员命名了一个未声明的事件,则为硬错误(否则陈旧的 union 成员会静默地不标注任何内容)。其余一律渲染为 **log-only**
- **专用围栏。** payload 块使用 ` ```ts persistence-catalog ` 信息字符串,`doc-typecheck` 识别并跳过它,不计入 opt-out 比例——`ts cordis-catalog` 的处理方式相同(裸 payload 片段无法独立编译)。
- **专用围栏。** 声明块使用 ` ```ts persistence-catalog ` 信息字符串,`doc-typecheck` 识别并跳过这些块,将其排除在退出检查比例之外——处理方式`ts cordis-catalog` 相同(这些声明引用所属模块中的类型,无法独立编译)。
- **仓库范围。** 目录枚举本仓库中的包,与兄弟文档的 packages-only 范围一致;下游插件可以合并更多事件类型,它们在设计上不在目录范围内。遍历过程用硬错误保护自身假设:拥有方的顶层 `interface SessionEventMap` 必须是 `@deepseek-ai/dsh-session` 中唯一的导出声明(无关的、局部的或同名重复的接口不能被当作磁盘词汇编入目录);任何声明不得携带 `extends`(继承的键会加入 `keyof SessionEventMap` 却没有对应的目录行);每个成员必须是带有显式 payload 类型的属性签名(方法形式的成员会加入 `keyof` 却在静默遍历中被漏过);跨声明的重复成员也会失败。
本方案取代了手工副本:session.md 的 `hook/*` 表格、精简版 README 的事件表格、hook-protocol README 的 payload 条目列表,以及 session README 的名称列表现在链接到目录,而不再重述 payload(周围的语义说明文字保留原位)。hook-protocol 合并成员上的两个误加的 `@mode emit` 标签已被移除——新门禁将它们作为类别错误拒绝。
@@ -30,7 +30,7 @@ Status: implemented
## 后果
- 目录不会漂移:词汇变更若未反映在已提交的文件中,`verify-persistence-catalog` 会在 pre-push 钩子和 CI 中失败;新合并的事件若缺少 JSDoc,生成器直接报错——插件不能添加未文档化的磁盘记录类型。
- 事件描述有唯一归属,即声明处的 JSDocJSDoc 写得单薄,目录条目就单薄,这迫使作者在源头做好文档
- 目录不会发生漂移:提交文件未反映的词汇或信封变化会使 `doc-sync` 和 CI 中的 `verify-persistence-catalog` 失败,而没有 JSDoc 的新增合并事件会直接使生成器失败——插件不能添加未记录的磁盘记录类型。
- 事件正文只有一个归属,即声明处的 JSDoc目录会保留该 JSDoc 和所有嵌套字段注释,不会将其扁平化或复述
- `SurfaceEventType` union 现在对文档具有结构性承载作用:重命名事件而不更新 union(或反过来)会导致生成器失败,而不仅仅是编译器失败。
- 徽章派生假设 union 始终是一组封闭的字符串字面量且只有一个拥有方;如果重构偏离了这一形状,必须在同一个变更中更新生成器。
@@ -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
2026-07-05-uniform-rfc-format.md: 9688ad6566e88372508bbfd8d032ab6d02568716
2026-07-05-uniform-rfc-format.zh.md: eab2505e64b813f698b4356bc52a796cbd66b948
2026-07-05-uniform-agent-note-format.md: 06082251c1b96c90ed470d84224662e00e29791b
2026-07-05-uniform-agent-note-format.zh.md: 61ade75f179d64fa73390dc85bcd47c30deb112a
@@ -2,6 +2,8 @@
Status: implemented
English | [中文](2026-07-05-uniform-agent-note-format.zh.md)
## Problem
Agent Note paths encoded lifecycle and class, but file contents still mixed headings, status formats, ADR and proposal templates, and proposal-era sections in implemented records. Authors copied whichever neighbor they found, and lifecycle moves could skip the required rewrite because no gate enforced an in-file contract.
@@ -1,30 +1,30 @@
# RFC: RFC 的统一受门禁约束的文件内格式
# Agent Note: Agent Note 的统一受门禁约束的文件内格式
Status: implemented
[English](2026-07-05-uniform-rfc-format.md) | 中文
[English](2026-07-05-uniform-agent-note-format.md) | 中文
## 问题
RFC 的路径已经编码了生命周期和类,但文件内容仍混杂着不同标题风格、状态格式、ADR 与 proposal 模板,以及已实现记录中残留的 proposal 时期的章节。作者随手复制找到的任何邻近文件,生命周期迁移时可以跳过必要的改写,因为没有门禁强制执行文件内契约。
Agent Noteagent 决策记录)的路径编码了生命周期和类,但文件内容仍混杂着不同标题、状态格式、ADR 与提案模板,以及已实现记录中的提案阶段章节。作者会复制随手找到的相邻文件,生命周期迁移可能跳过必要的改写,因为没有门禁强制执行文件内契约。
## 决策
[README.md § The file format](../../README.md#the-file-format)文件内契约头部块(`# RFC: <title>` 加上无日期、与所在文件夹一致的 `Status:` 枚举,唯一的正文内容是 rejection reason);按生命周期区分的正文骨架(所有阶段都`Problem``proposed/` 中为 `Proposal`/`Acceptance criteria`/`Risks``implemented/` 中为现在时`Decision`/`Consequences` 且禁止 proposal 时期的标题;`rejected/` 冻结 proposal 形态);必须包含 `Alternatives considered` 章节以及规范章节词汇表,其间的自定义技术章节保持自由形式。`pnpm run verify-rfc-format`[scripts/verify-rfc-format.ts](../../../../scripts/verify-rfc-format.ts))作为 `doc-sync`(文档同步门禁)的一环强制执行每机械化条款,因此生命周期迁移时跳过改写现在会 CI 失败,而不依赖评审者记忆。
[README.md § 文件格式](../../README.md#the-file-format)文件内契约——头部块(`# Agent Note: <title>`加上无日期且与文件夹一致的 `Status:` 枚举,其中只有拒绝原因可作为额外内容)、各生命周期的正文骨架(所有文件均`Problem``proposed/` 使用 `Proposal`/`Acceptance criteria`/`Risks``implemented/` 使用现在时的 `Decision`/`Consequences`,并禁止提案阶段标题;`rejected/` 冻结提案形状)、强制的 `Alternatives considered` 章节以及规范章节词汇;定制技术章节可在这些规范章节之间保持自由形式。`pnpm run verify-agent-note-format`[scripts/verify-agent-note-format.ts](../../../../scripts/verify-agent-note-format.ts))作为 `doc-sync` 的一部分强制执行每机械规则,因此跳过改写的生命周期迁移现在会使 CI 失败,而不依赖评审者记忆。
整个语料库在定义格式的同一变更中完成了规范化,这是预发布阶段的立场:没有过渡期,不容忍双格式并存。唯一的祖父条款针对内容而非格式:替代方案是记录下来的,不是凭空编造的;因此如果一篇格式定义前的 RFC 的替代方案无法从记录中重建,它会携带 `rfc-format: alternatives-not-recorded` 这条精确注释门禁对日期早于本 RFC 的文件接受该注释。
定义格式的同一变更规范化了整个语料库——遵循预发布立场:不设过渡期,不容忍双格式。唯一受既有条款豁免的是内容而非格式:替代方案只能记录、不能杜撰,因此若某份格式制定前的 Agent Note 无法从记录中还原替代方案,就会携带确切的 `agent-note-format: alternatives-not-recorded` 注释门禁对日期早于本的文件接受该注释。
## 曾考虑的替代方案
- **完刚性模板**(每个生命周期一个固定章节序列,所有 RFC 重构以适配):否决。大型设计 RFC 包含八到十五个自定义技术章节(包拓扑、协议格式契约、schema),这些是承重内容而非漂移;刚性序列会立即强制破坏性改写,并永远带来与模板的对抗
- **完整的刚性模板**(每个生命周期使用固定章节顺序,重构每份 Agent Note 以适配):否决。大型设计 Agent Note 包含八到十五个定制技术章节(包拓扑、线协议、schema),它们是承载设计的内容而非漂移;刚性顺序会迫使我们现在进行破坏性改写,并永远与模板较劲
- **仅规范化头部**(H1 和 Status,正文不动):否决。债务标记指出的是*正文*的体裁分裂,让 `Context`/`Decision``Problem`/`Proposal` 无限期并存什么也解决不了。
- **不设 Status 行**(文件夹本身就是状态;格式定前最新的三篇 RFC(以及其中一的中文对文件省略了该行):否决,保留自描述文件。省略 Status 行的动机是防止漂移,而将该行与文件夹做门禁校验即可消除漂移风险。
- **不设 Status 行**(文件夹已经表示状态;格式定前最新的三份 Agent Note 及其中一的中文对文件省略了该行):否决,保留文件的自描述性。通过门禁校验该行与文件夹一致,消除了原本促使我们删除它的漂移风险。
- **带日期的 Status**`Status: implemented (accepted YYYY-MM-DD)`):否决。接受日期属于叙述性历史,写作规则将其排除在文档之外;文件名承载首次提出日期,git 承载其余信息;门禁能检查日期格式,但永远无法检查其真实性。
- **裸 `# <title>` H1**:否决。`RFC: ` 前缀是语料库中的多数形式,且在文件脱离目录树阅读时能自描述体裁;索引生成器会剥离前缀,因此索引行无论哪种写法都一样
- **`## What we give up` 作为 implemented 的结尾章节**README 自身对 RFC 记录内容的措辞):否决。它只命名了代价,而诚实的后果章节同样记录这笔权衡换来了什么。
- **裸 `# <title>` H1**:否决。文件脱离目录树单独阅读时`Agent Note: ` 前缀能自描述体裁,而格式门禁可防止它漂移
- **`## What we give up` 作为已实现记录的结尾**README 对 Agent Note 所记录内容的原有表述):否决。它只点出成本,而诚实的后果章节也会记录取舍换来了什么。
- **只有约定没有门禁**(写下契约,靠评审强制执行):否决。slop checklist 已经通过约定禁止在 `implemented/` 中使用 spec 语气,而十九个文件展示了仅靠约定在此处能达到什么效果。
- **独立的 `FORMAT.md` 契约文件**最初的落地位置;在生成索引迁出到 [INDEX.md](../../INDEX.md) 之后折入 README.md:表格移走后 README 重新有了空间,一个前门同时承载布局、分类和格式,优于将契约拆分到两个文件
- **独立的 `FORMAT.md` 契约文件**否决。由一个入口同时承载布局、分类和格式,比维护两个契约文件更易发现和维护
## 后果
每篇 RFC 现在需要多一些结构,而必须包含 `Alternatives considered` 章节是刻意的摩擦:一个没有记录被否决方案的决策,会招致 RFC 本来就是为了防止的重新争论。格式定义前的 RFC 如果替代方案无法重建,则永久携带祖父条款注释这是记录上的诚实缺口,而非编造的理由。`doc-sync` 增加一道门禁,将 RFC 在生命周期文件夹之间迁移现在是迁移时的实际工作(迁移本就欠下的正文改写),而无人踪的延后清理。三十九个债务标记已全部消除,由它们等待的模板解决。
现在每份 Agent Note 都需要多一些结构,而强制的 `Alternatives considered` 章节是有意设置的阻力:记录决策却不记录它胜过什么,会招致 Agent Note 本应防止的重新争论。无法还原替代方案的格式制定前 Agent Note 会永久保留既有条款注释——这是记录诚实缺口,而不是杜撰的理由。`doc-sync` 增加一道门禁在生命周期文件夹之间移动 Agent Note 时,现在必须当场完成真正的工作(迁移本就应包含的正文改写),而不是推迟为无人踪的清理任务。三十九个债务标记已经消失,由它们一直等待的模板解决。
@@ -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
2026-07-06-export-surface-jsdoc-gate.md: c2543fe50320da70c9d75bb4a16df3b110b50c47
2026-07-06-export-surface-jsdoc-gate.zh.md: 98cb057cc1a661d2f51b8215609fc280d549c103
2026-07-06-export-surface-jsdoc-gate.md: 93d8a41fc2ffb235de5c56ffeb5569bc95249392
2026-07-06-export-surface-jsdoc-gate.zh.md: 64b4bcc620046f2c94a2ce3fbeb67e59ada888d4
@@ -1,4 +1,4 @@
# RFC: 导出表面 JSDoc 门禁
# Agent Note: 导出表面 JSDoc 门禁
Status: implemented
@@ -28,7 +28,7 @@ Status: implemented
- **插件协议槽位。** 顶层的 `name`/`inject`/`reusable`/`Config` 常量和 `apply` 入口,以及插件类上的同名静态成员,属于框架协议:其形状由 Cordis 固定,模块文档注释加 `interface Config` 承载插件的真实语义。
- **构造函数**,与 Cordis 门禁一致:插件类由框架构造,类文档承载全部说明。
`collectExportJsdocViolations()` 返回违规列表(CLI 在非空时以 1 退出),因此 `packages/core/agent/tests/verify-export-jsdoc.spec.ts` 中的负路径测试直接断言发现项,通过 fixture(测试前置数据)包驱动每一种拒绝和每一种豁免。
`collectExportJsdocViolations()` 返回违规列表(CLI(命令行界面)在非空时以 1 退出),因此 `packages/core/agent/tests/verify-export-jsdoc.spec.ts` 中的负路径测试直接断言发现项,通过 fixture(测试前置数据)包驱动每一种拒绝和每一种豁免。
## 曾考虑的替代方案
@@ -38,7 +38,7 @@ Status: implemented
## 后果
-导出不能在文档的情况下合入`verify-export-jsdoc` 使 `doc-sync` 失败,而 pre-push 和 CI 已运行该门禁。采纳时发现的 203 处缺口在同一变更中补齐,门禁以绿色状态落地。
- 新导出不能在缺少文档的情况下落地`verify-export-jsdoc` 使 `doc-sync` 和 CI 失败。采纳时发现的 203 处缺口在同一变更中补齐,因此门禁以绿色状态落地。
- 导出函数必须标注返回类型(采纳时已全面满足,现在成为门禁依赖),并在 `@param` 需要命名参数时使用标识符参数。
- seam 文档是权威的:实现从其继承链继承文档,值得保留在实现上的行为说明是补充,而非必需。
- 门禁构建一个 `ts.Program`(约 6 秒)——唯一需要类型解析的文档门禁;在已编译文档片段的 `doc-sync` 内可以接受。
@@ -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
2026-07-06-generated-config-catalog.md: 77c2007d4b5cd28efcfe391c2de712db3b5c107e
2026-07-06-generated-config-catalog.zh.md: 4d9b190b770432111287cedee5992d5822197feb
2026-07-06-generated-config-catalog.md: f39f5138526d3278e839ee0053d5051bb8bc1c36
2026-07-06-generated-config-catalog.zh.md: 8f08a7791916351df508c89f0c783dee2db17bae
@@ -1,4 +1,4 @@
# RFC: 生成式插件配置目录
# Agent Note: 生成式插件配置目录
Status: implemented
@@ -10,7 +10,7 @@ Status: implemented
## 决策
`scripts/gen-config-catalog.ts` 从每个插件声明的配置类型 JSDoc 生成 [docs/config-catalog.md](../../../config-catalog.md),包含注入要求、引用类型链接和源码指针。包内局部类型被传递性地包含workspace 和外部类型链接或名称形式引用。确定性的 `--write``--check` 模式使提交到仓库的页面成为一个生成产物。
`scripts/gen-config-catalog.ts` 根据各插件声明的 config 类型 JSDoc 生成 [docs/config-catalog.md](../../../../docs/config-catalog.md)包含注入要求、引用类型链接和源码位置。包内类型会以传递方式纳入workspace 类型和外部类型则会链接或名。确定性的 `--write``--check` 模式使提交页面成为生成产物。
此处采用纯 AST 生成是正确的,原因与 events/services catalog 相同,而与 tool catalog 不同:配置类型是静态声明,仓库中每个 schemastery schema 都是静态的 `z.object`/`z.intersect` 字面量,因此源码即全部真相——配置表面没有任何部分是运行时组合的。
@@ -34,7 +34,7 @@ Status: implemented
## 后果
- catalog 不会漂移:源码变更而提交文件未反映时,`verify-config-catalog` 在 pre-push 和 CI 中报错。未文档化的配置字段、无法解析的引用类型名、或 schema 键在配置类型中缺失,都会直接导致生成器报错
- 目录不会发生漂移:提交文件未反映的源码变化会使 `doc-sync` 和 CI 中的 `verify-config-catalog` 失败。config 字段未记录、被引用类型名无法解析,或 schema 键未出现在 config 类型中,都会直接使生成器失败
- 配置行文现在有了声明处的强制函数:编写新配置字段意味着编写其 JSDoc,而该 JSDoc 将逐字成为 catalog 条目。
- 生成器对无法静态遍历的形状直接报错——别名化的包内配置导入、非 `object`/`intersect` 组合构建的 schema、未列入的全局类型名。引入此类形状时必须同时教会生成器(否则该形状不能进入仓库),这正是设计意图:catalog 始终是全部真相。
- `gen-cordis-catalog.ts` 导出其 JSDoc/指针辅助函数与 `LINK_MAP` 供复用,因此两个 catalog 以相同方式交叉链接类型,新增一条 link-map 条目同时服务于两者。
@@ -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
2026-07-06-node-engine-floor.md: f7764c408374a329c9390635e1b92ae9a4fd9dee
2026-07-06-node-engine-floor.zh.md: c46bf5168007b97878319e5f333b5bb1ab1d8f2a
2026-07-06-node-engine-floor.md: f1754ea7ca32452a04c6cd8a0599568f602e47dd
2026-07-06-node-engine-floor.zh.md: 18e878bdfcc32938125ee46e742a42499e0b58ab
@@ -1,4 +1,4 @@
# RFC: 将 Node LTS 引擎下限提升至 22.19
# Agent Note: 将 Node LTS 引擎下限提升至 22.19
Status: implemented
@@ -15,7 +15,7 @@ Status: implemented
两个 Node 特性决定了源码运行时的门槛:
- **`node:sqlite`**`packages/session-persistence/session-persistence-sqlite` 在顶层执行 `import { DatabaseSync } from 'node:sqlite'`。该模块在 **22.13**LTS)和 **23.4**Current)取消了 `--experimental-sqlite` 标志要求;在此之前,导入它会在加载时抛出异常。
- **原生 TypeScript 类型剥离**`packages/examples/stdio-demo/tests/built-bin.e2e.ts` 冒烟测试`node`不用 tsx启动已发布的 `lib/bin.js`,并加载示例的 `.ts` 插件(`mock-llm.ts``echo-tool.ts`)。类型剥离从 **22.18**LTS)和 **23.6**Current)起成为默认行为;在此之前需要 `--experimental-strip-types`
- **原生 TypeScript 类型剥离**——构建模式的 `examples/headless-agent/tests/keyless-smoke.e2e.ts` 冒烟测试使用`node` tsx)启动 `dsh-cli-demo` 已发布的 `lib/bin.js`,并加载示例的 `.ts` 测试适配器(`cli-mock-llm.ts`)。类型剥离从 **22.18**LTS)和 **23.6**Current)起成为默认行为;更早版本需要 `--experimental-strip-types`
这些源码特性在 22.x 线上于 **22.18** 全部就绪,但已安装的 Pi 适配器依赖将宣传的 LTS 下限进一步提高。`@deepseek-ai/dsh-llm-pi-ai` 依赖 `@earendil-works/pi-ai@0.79.3`,后者的 package 声明 `engines.node >=22.19.0`,因此 LTS 下限为 **22.19**。24.x 分支保持 `>=24.0.0`。该不相交范围完全排除了 Node 23:Node 23.0–23.5 至少还有一个源码特性需要标志,而 23 线是非 LTS/已 EOL 的,宣传 `>=23.6` 会增加一条已终止的发布线和一条 CI 分支,而没有任何部署应当使用它。
@@ -26,7 +26,7 @@ Status: implemented
- 宣传的 LTS 分支不再低于 Pi 适配器依赖的下限。
- CI 通过 Node 22.19 直接验证 Node 22 LTS 下限,Node 24 分支保持 `node: 24`,Node 26 用于下一个偶数线;每条分支都对源码图执行类型检查,并实际启动未构建的工作流 worker。
- built-bin 冒烟测试无需版本条件标志:在 22.19 上类型剥离已是默认行为,因此测试保持其文档所述的纯 `node lib/bin.js` 路径。
- 未来如果有依赖或源码 API 提高运行时下限,必须在同一变更中同步修改 `engines.node`、兼容性矩阵和本 RFC
- 未来依赖或源码 API 提高运行时下限,必须在同一变更中同步调整 `engines.node`、兼容性矩阵和本 Agent Noteagent 决策记录)
## 曾考虑的替代方案
@@ -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
2026-07-06-parallel-github-ci-gates.md: 6a9bd339304c8efac7a29bdbe745270ab9661396
2026-07-06-parallel-github-ci-gates.zh.md: 24fdf27822aac7640fbeaf602fa8dd84277083fa
2026-07-06-parallel-github-ci-gates.md: 5c276f6a75936021369bc5ad9494c9aa6e4e3fc3
2026-07-06-parallel-github-ci-gates.zh.md: f96606ba2b58b856b3833e758853e9fa3a62bff3
@@ -2,6 +2,8 @@
Status: implemented
English | [中文](2026-07-06-parallel-github-ci-gates.zh.md)
## Problem
The keyless GitHub CI gates are mostly orthogonal: typecheck, lint, documentation freshness, coverage, snapshot replay, build, package-publication hygiene, demo smoke, and built-bin smoke fail for different reasons and do not need each other's runtime state. Running them as one ordered command chain makes the workflow wall clock equal the sum of those gates, while splitting every short leaf into its own GitHub job repeats checkout, Node setup, pnpm restore, and install work until orchestration overhead becomes the bottleneck.
@@ -1,4 +1,4 @@
# RFC: 并行 GitHub CI 门禁
# Agent Note: 并行 GitHub CI 门禁
Status: implemented
@@ -6,36 +6,45 @@ Status: implemented
## 问题
keyless GitHub CI 门禁大多相互正交:类型检查、lint、文档新鲜度、覆盖率、快照放、构建、包发布卫生检查、demo 冒烟测试与 built-bin 冒烟测试各自因不同原因失败,彼此不需要对方的运行时状态。将它们串成一条有序命令链工作流的挂钟时间等于所有门禁之和;而把每个叶子门禁拆成独立 GitHub job则会重复 checkout、Node 设置、pnpm restore 和 install 工作,直到编排开销本身成为瓶颈。
无密钥 GitHub CI 门禁大多相互正交:类型检查、lint、文档新鲜度、覆盖率、快照放、构建、包发布卫生、demo 冒烟和已构建二进制冒烟会因不同原因失败,不需要彼此的运行时状态。将它们作为一条有序命令链运行,会使工作流钟时间等于所有门禁耗时之和;而把每个短小叶子拆成独立 GitHub job又会反复执行 checkout、Node 设置、pnpm 恢复和安装,直到编排开销成为瓶颈。
难点在于产物边界。`publint``verify-node-next-types` 和 built-bin 冒烟测试需要构建出的 `lib/` 输出,而大多数门禁只需要源码和依赖。盲目扇出要么让这些产物消费方在 `pnpm run build` 输出声明文件和 bundle 之前就开始竞跑,要么在每个依赖产物 job 中重复构建
随着 workspace 增长,原有的宽车道拆分不再满足这一平衡。PR(Pull Request#404 合并时,Linux 的静态、覆盖率、快照和产物 job 分别耗时 148、195、94 和 230 秒;Windows 的静态和产物 job 分别耗时 251 和 482 秒。每个包都调用一次包管理器打包,主导了两个产物验证器的耗时;覆盖率在仅运行源码的套件前无谓地重建输出;CPU 密集型门禁则在静态与覆盖率车道内争用资源
产物边界仍然承载关键约束。`publint``verify-node-next-types`、已编译不变量加载和已构建二进制冒烟测试都需要生成的 `lib/` 输出。分片不能让这些消费者抢在构建前运行,也不能用源码执行取代它们对已发布产物的信号。
## 决策
[CI](../../../../.github/workflows/ci.yml) 将 keyless 检查分组为若干宽粒度的主运行时 lane,外加一个兼容性矩阵。工作流文件拥有当前 lane 和运行时清单的定义权
下述生产拓扑已经成为历史,并由[基于证据采用更大的托管 runner](2026-07-22-evidence-based-larger-hosted-runners.md) 取代。更大 runner 的决策移除了其分片选择器和工作流 job;本文保留早期拓扑为何被实现的记录
每个 lane 委托给 [scripts/run-gates.ts](../../../../scripts/run-gates.ts),该脚本以有界并发调度独立门禁,并为每个门禁打印一个可归因的结果块。产物消费方依赖其所在 lane 内的一次 build,而兼容性 job 将类型检查与一次真实的未构建 worker 启动结合,以覆盖运行时特定的 loader 行为
[CI](../../../../.github/workflows/ci.yml) 将非 Windows job 的一分钟和 Windows job 的三分钟视为观测所得的性能目标,而非取消截止时间。托管 runner 的波动应留下完整计时证据和有用的失败日志,而不是取消本来正确的门禁。[串行跨平台 CI 参考](2026-07-21-serial-cross-platform-ci-reference.md)会在 Linux、macOS 和 Windows 上独立运行完整、未分片的主 Node 聚合,使优化后的车道清单不会成为自身完整性的唯一判据
生成的 `.sessions/` 日志和 `.doc-typecheck-*` 临时目录被 lint 忽略。聚合的本地 CI 模式仍在 lint 之后运行 demo 冒烟测试,而拆分后的 GitHub static lane 可以直接运行 demo 冒烟测试,因为 lint 已隔离在自己的 lane 中
在该拓扑中,[scripts/run-gates.ts](../../../../scripts/run-gates.ts) 是通用的有界调度器,GitHub 则为昂贵的门禁族提供显式分片名称。`scripts/static-shards.ts` 将静态门禁划分为基础、文档类型、API 契约、目录、正文、文档投影和文档构建等归属,并拒绝缺失或重复的门禁分配。Linux lint 使用互不重叠的 A-C、D-M、N-S、T-Z 包源码和包测试车道,Windows 则使用完整的包源码与包测试车道;两者都包含从 `.` 开始的仓库补集,使新增顶层目标无法消失在分片之间,并负责唯一一次跨文件重复检查。`scripts/coverage-shards.ts` 把每个 workspace 包恰好分配给一个源码覆盖率车道。目录过滤器保留尾部分隔符,因为 Vitest 位置过滤器按子字符串匹配,否则会纳入具有同名前缀的相邻项。每个覆盖率车道只包含其拥有的源码文件,重复运行穷尽式伴随拓扑测试,并且不先执行构建,因为从删除了所有生成式 `lib/` 的树开始,完整覆盖率套件仍可通过
构建输出在 Node 24 的产物 lane 中只生成一次。产物消费方(`publint``verify-node-next-types` 和 built-bin 冒烟测试)声明对 `build` 的依赖,因此没有 upload/download 交接,消费方也不可能在声明文件或 bundle 就绪之前抢跑。CI 覆盖率报告仅输出文本,本地覆盖率则保留 HTML 报告
快照重放使用两个显式多文件车道,以及大型 ACP(Agent Client Protocol)文件的八个场景分区。`scripts/snapshot-shards.ts` 拥有该清单,其测试会发现快照配置允许的每个文件。每个快照 job 在其 Linux runner 准备 Bubblewrap 的同时安装依赖,随后构建已发布运行时,并且只运行分配给它的重放表面。该套件保留五个子进程的有界并发,因为重放的大部分时间都在等待子进程协议 I/O。fixture(测试前置数据)守卫仍会在每个分区中检查完整 ACP 场景表
两个工作流都缓存 pnpm store。真实 API 工作流使用共享的有界 Vitest 文件池,而非为每组测试单独开一个 job。
冷启动的独立文档类型检查会重建完整的项目引用图,因此专用文档类型车道只构建一次,再用这些声明检查 Markdown 块。Linux 文档车道使用 VitePress 的 MPA 构建,在观测所得的非 Windows 目标内保留页面渲染与死链接验证;单独的阻塞式 Windows 构建和生产站点车道保留已生成包与已发布站点检查,同时避免把两条关键路径放进同一个 job。
产物使用两个车道:一个元数据车道负责 `publint`、NodeNext 声明和已编译不变量加载,另一个负责已构建二进制冒烟。每个车道都会在其消费者之前自行构建。重复短时构建会消耗 runner 分钟数,但避免了上传/下载依赖,并使每个 job 的关键路径保持有界。
[scripts/publint-all.ts](../../../../scripts/publint-all.ts) 在进程内针对内存发布视图调用 publint 支持的 API;该视图由每份清单声明的文件和 npm 强制元数据文件构成。这样无需生成 103 次包管理器打包命令,也能保留 workspace 文件与已发布文件之间的区别。[scripts/verify-built-package-invariants.mjs](../../../../scripts/verify-built-package-invariants.mjs) 在真实包下暂存这些经过结构验证、由清单声明的 `lib/` 文件,再通过纯 Node 和 Cordis Loader 规范化导入已编译的自引用。若伴随项触及未声明的运行时 chunk,仍会失败。
兼容性车道会在每条声明支持的 Node 版本线上运行源码 worker 和 Zstandard 运行时冒烟。TypeScript 在专用的主 Node 24 车道中只检查一次源码图;在运行时兼容性 job 中重复同一编译器分析只会增加耗时,不会提供运行时特有信号。
工作流缓存 pnpm store,将每个不可变 ESLint 缓存的键绑定到其所属 lint 分片,为 Windows 测量保留原生 PowerShell,并保留一个聚合的 `all checks passed` 状态用于分支保护。Windows 复用三个穷尽式 lint 分区,并在共享 runner 设置后组合基础/目录/正文门禁与文档类型/API 契约门禁;只有调度方式与 Linux 分区不同。Windows 构建和生产站点验证继续阻塞,而更广泛的 Windows 静态、lint 和产物矩阵仍为观察性检查。
## 曾考虑的替代方案
- **在 Node 矩阵中保留完整串行链**:最容易推理,但会重复执行不产生 Node 版本特定信号的仓库级门禁,且让每个 PR 等待所有门禁的总和
- **每个门禁作为独立 GitHub job 运行**:最大化 GitHub 可见的扇出,但产生过多 check,且对运行时间短于 runner 准备时间的门禁而言,重复的 setup/install 开销得不偿失
- **将构建产物上传给依赖产物的 job**:在多 job 间保持正确性,但增加了 artifact upload/download 时间,且当产物消费方可以在主 job 内通过本地依赖排序运行时,工作流仍然过宽
- **并发运行 `typecheck``build`**:向调度器暴露更多工作,但两个命令都调用 `tsc -b`;在它们之间共享增量构建状态是一场不必要的竞争,换来的挂钟收益很小
- **使用无界的真实 API e2e 并行度**:否决。该套件包含大量真实模型/工具场景;worker 池需要一个显式的 `DSH_E2E_MAX_WORKERS` 上限,使 CI 和本地运行都能扇出,同时不会把配额或资源问题隐藏在不稳定的限流失败背后
- **保留宽车道**:最大限度减少工作流 YAML,但会保留观测到的数分钟反馈周期
- **每个叶子门禁分别成为 GitHub job**:最大化扇出,但短小的生成器和正文检查准备 runner 的时间会超过检查仓库的时间
- **向产物消费者上传一次构建**:避免重复编译,但上传/下载和依赖调度会延长墙钟时间;干净构建足够短,可以在有界车道内重复
- **在两个发布门禁中保留包管理器打包**:把清单选择委托给 pnpm,但会重复启动 200 多个包管理器进程。清单结构门禁加发布视图 fixture 使优化后的清单契约显式化,并会在存在磁盘上有但未发布的依赖时失败
- **在覆盖率前保留构建**:提供源码套件已不再消费的生成输出;干净树覆盖率证明表明这只是纯粹的延迟
- **在每个 Node 版本上执行类型检查**:重复编译器工作,而兼容性冒烟已经验证实际的 Node 特有加载与压缩行为。
## 后果
PR 反馈以少量 GitHub check 的形式呈现,每个宽粒度 job 内部包含结构化的逐门禁日志块。这将 runner 设置开销控制在有限范围内,并保持 Actions UI 紧凑,代价是失去了每个叶子门禁独立的状态标记
上述分片清单和矩阵 job 不属于当前仓库契约。取而代之的更大 runner 决策在单个进程中保留完整主清单,并以串行套件作为独立完整性判据
宽 lane 拆分比单一主 job 更频繁地重复 checkout、setup 和 install。这一设置开销是有意为之的:在 GitHub 托管 runner 上,将 lint、覆盖率和快照回放放在同一个进程池中运行会严重超额占用 CPU,以至于单 job 的关键路径比重复设置还要长
优化后的发布验证器依赖由 `verify-package-invariants` 强制执行的清单 `files` 契约。如果发布规则超出该契约,结构门禁和两个暂存视图必须一起变化
这种拆分引入了一项维护义务:当 `package.json` 新增或移除一个应纳入 CI 的门禁时,[scripts/run-gates.ts](../../../../scripts/run-gates.ts) 需要添加或删除对应的叶子。这一义务是有意为之的,因为该 runner 是同一套门禁词汇的并行执行计划,而非独立的质量策略
兼容性信号比主 Node 24 信号更窄。它证明源码图在每个声明支持的运行时上都能通过类型检查,且真实的未构建 workflow-worker 启动路径能够执行,而不必重复文档、覆盖率、发布、快照回放以及那些不因 Node 版本而异的无关冒烟测试。
兼容性 job 不再声称 TypeScript 本身已在每个 Node 运行时下执行。它们证明 Node 22、24 和 26 上对运行时敏感的源码加载,而主运行时负责唯一一次源码图类型检查
@@ -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
2026-07-06-parallel-pre-push-gates.md: cf032f8dfedd88a7d6be87999fd9786a9efbb2e8
2026-07-06-parallel-pre-push-gates.zh.md: 776faf1f46c6490f1935727612225414dd258aa3
2026-07-06-parallel-pre-push-gates.md: f2e8f0054e595be20a320ec7095f0fe674eb93c6
2026-07-06-parallel-pre-push-gates.zh.md: 06cd2fc41ca9a2cc2546fe85de82c8198f9573c3
@@ -2,6 +2,8 @@
Status: implemented
English | [中文](2026-07-06-parallel-pre-push-gates.zh.md)
The local-hook portion of this record is superseded by [Fast local Git hooks](2026-07-22-fast-local-git-hooks.md). The bounded gate scheduler and package-level `publint` parallelism remain in force for CI, `doc-sync`, and explicit local commands.
## Problem
@@ -1,42 +1,33 @@
# RFC: 并行 pre-push 门禁
# Agent Note: 并行 pre-push 门禁
Status: implemented
[English](2026-07-06-parallel-pre-push-gates.md) | 中文
本记录中的本地 hook 部分已由[快速本地 Git hook](2026-07-22-fast-local-git-hooks.md) 取代。有界门禁调度器和包级 `publint` 并行机制仍用于 CI、`doc-sync` 和显式本地命令。
## 问题
pre-push 钩子是分支离开本地机器前的最后一道检查点,因此它的挂钟时间直接影响贡献者是否愿意保持启用并信任其信号。Lefthook 已经能并行运行顶层 job,但 `pnpm run hygiene``pnpm run doc-sync` 等聚合 job 在单个 job 内部隐藏了长串的顺序执行链。钩子因此可能在配置上看似并行,实际仍在等待那些成员彼此独立却串行执行的子命令
将这些成员直接展平到 `lefthook.yml` 只能解决本地钩子的问题。CI 面临同样的调度问题,而在 YAML 中复制一长串叶子列表会让未来的脚本改动有两处可能漂移。
`publint` 在更低一层也有同样的形态。每个包(package)独立地根据自身 manifest(元数据清单)和构建产物做 lint,但 runner 按顺序遍历所有包。在本仓库中,这意味着一个包发布门禁的耗时与包的数量成正比,尽管各检查之间并不共享可变状态。
文档同步等聚合 job 隐藏了很长的串行链,其成员只读且相互独立。在工作流 YAML 中重复这些叶子清单,会使未来脚本变更有多个位置可以发生漂移;而串行运行包发布检查,会使一道门禁的耗时与包数量成正比
## 决策
[lefthook.yml](../../../../lefthook.yml) 保留一个名为 `full check` 的 pre-push job,运行 `pnpm run check:pre-push`。该 package 脚本委托给 [scripts/run-gates.ts](../../../../scripts/run-gates.ts),即 CI 使用的同一个有界调度器
[scripts/run-gates.ts](../../../../scripts/run-gates.ts) 拥有 CI、`doc-sync` 和选择启用的 `check:all` 命令所使用的有界调度器。它将具名模式展开为叶子门禁,遵守产物依赖,缓冲可归因的输出,并在调用方需要不同 worker 上限时接受 `DSH_GATE_CONCURRENCY`
`pre-push` 模式展开为以下叶子门禁:单元测试套件、快照测试套件、构建、`hygiene` 成员、`doc-sync` 成员,以及 module-graph 新鲜度。叶子列表保持与 package 脚本相同的门禁词汇,包括 RFC 分类和 RFC 格式,同时 runner 并发调度独立检查,并为每个门禁打印一个计时/输出块。
[scripts/publint-all.ts](../../../../scripts/publint-all.ts) 从 `packages/<group>/<pkg>` 发现包,并以根据 `availableParallelism()` 确定大小的 worker 池运行 `publint``DSH_PUBLINT_CONCURRENCY` 可以针对资源配置不同的本地机器和 CI runner 限制或提高 worker 数量。结果按包缓冲,并按确定性的包顺序打印,因此并行执行不会打乱各包的日志块。
构建门禁使钩子在干净的 worktree 上也能自足运行。`publint``verify-node-next-types` 等待构建产物就绪,而仅依赖源码的门禁继续并行执行
[scripts/publint-all.ts](../../../../scripts/publint-all.ts) 从 `packages/<group>/<pkg>` 发现包列表,并使用大小取自 `availableParallelism()` 的 worker 池运行 `publint``DSH_PUBLINT_CONCURRENCY` 可为资源配置不同的本地机器和 CI runner 设定或提高 worker 数量上限。结果按包缓冲,并以确定性的包顺序打印,因此并行执行不会打乱各包的日志块。
聚合 package 脚本仍然是临时本地运行的真源。调度器是对其成员门禁的并行执行计划,而非替代词汇。
各门禁的包脚本仍是临时本地运行所用的词汇。`hygiene` 继续作为聚合 `&&` 链,而 `doc-sync` 在调度器中拥有其成员列表([通过门禁调度器运行 doc-sync](2026-07-21-doc-sync-through-gate-scheduler.md)
## 曾考虑的替代方案
- **在钩子中保留聚合的 `hygiene``doc-sync` job**配置更简单,但 pre-push 的大部分挂钟时间仍然消耗在 lefthook 看不到也无法调度的串行命令链内部
- **每个叶子门禁声明一个 lefthook job**:通过 lefthook 原生 job 模型暴露并行,但会让钩子文件承载一长串成员列表,CI 无法复用
- **要求开发者在推送前手动构建**:可以省去一个钩子门禁,但会导致 `publint` 在干净 worktree 上失败,并把最后的本地检查点从可运行的检查降级为一种约定
- **在 shell 脚本中使用后台子命令**:能并行化工作,但会丢失 lefthook 的 job 名称、逐 job 计时和失败分组,且信号处理更难推理
- **为每个包声明一个 publint lefthook job**:暴露最大并行度,但会让钩子变成一份手动维护的包清单,恰好在新增包时漂移
- **以无界并发运行 publint**:仅在小型机器上以赌注方式最小化耗时,代价是进程数、内存压力、包 tarball 创建和日志可读性的风险。
- **保持聚合 job 串行**执行更简单,但墙钟时间等于各独立检查之和,并重复启动命令包装器
- **每个叶子门禁声明一个 CI job**:暴露最大工作流并行,但会重复 checkout、设置和安装开销,并在 YAML 中复制调度器清单
- **在 shell 脚本内后台运行子命令**:可以并行处理,但会失去各门禁计时、确定性的失败分组和直接的信号处理
- **每个包声明一个 `publint` job**:暴露最大包级并行度,但会创建手工维护的包清单,包发生变化时就会漂移
- **以无界并发运行 `publint`**:只有通过拿进程数、内存压力、包 tarball 创建和可读日志冒险,才能最大限度缩短小型仓库的耗时
## 后果
钩子的关键路径变为最慢的单个真实门禁,而非隐藏门禁链的总和。Lefthook 报告一个 `full check` jobrunner 在该 job 内部报告逐门禁计时,因此本地检查点偏慢时仍能指向主导耗时的那个门禁
由调度器支持的命令耗时取最慢依赖链,而非各独立门禁之和,并会报告主导耗时的门禁。代价是维护一个具有显式模式清单的定制调度器
钩子文件保持简短,重复的成员列表集中在 [scripts/run-gates.ts](../../../../scripts/run-gates.ts) 中,CI 和 pre-push 可以共享。代价是引入一个自定义调度器脚本(而非纯 lefthook 配置),外加本地 pre-push 路径中的一次构建
`publint-all.ts` 变为异步代码,缓冲命令输出而非实时继承 stdio。收益是包级别的并行性、稳定的输出顺序,以及一个用于资源调优的环境变量。
`publint-all.ts` 采用异步执行并缓冲命令输出,而不是实时继承 stdio。换来的是具有稳定输出顺序的包级并行,以及用于资源调节的单一环境变量
@@ -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
2026-07-10-readme-known-limitations-gate.md: 1aad1d40285f295833c2b1bd7d5c2e71bb780f34
2026-07-10-readme-known-limitations-gate.zh.md: 4b3904fac3f980f9d005907775618cd28778a6fe
2026-07-10-readme-known-limitations-gate.md: 2ca1168d795692730d17b6ab23dd113e8be277e5
2026-07-10-readme-known-limitations-gate.zh.md: 7968a30b996b931169bbe2678ec1500ff86ef577
@@ -1,4 +1,4 @@
# RFC: 在每个 package README 中设置受门禁保护的 Known Limitations 章节
# Agent Note: 在每个 package README 中设置受门禁保护的 Known Limitations 章节
Status: implemented
@@ -6,15 +6,15 @@ Status: implemented
## 问题
[文档标准](../../../AGENTS.md)将限制事项指定在 package README 中记录。如果没有统一的格式,缺失的章节无法区分经审计确认无此内容」与「忘了写文档」,而标题写法不一致也会妨碍全仓库搜索。
[文档标准](../../../../docs/AGENTS.md)规定限制项归属包 README。没有共享形状时,缺少章节无法区分经审计确认没有限制”与“忘记编写文档”,不同的标题还会妨碍全仓库搜索。
## 决策
`packages/<group>/<pkg>/package.json` 下的每个包(packagemanifest(元数据清单都有一个同目录的 README,其中包含规范的 `## Known Limitations and Deferred Work` 章节。该章节的条目记录该包拥有的持久消费缺口与非显而易见的维护者约束;常规清理工作仍留在源码 TODO 或所属 RFC 中。[`verify-package-readme-limitations` 门禁](../../../../scripts/verify-package-readme-limitations.ts)从 manifest 推导包集合,拒绝缺 README 的情况,并要求恰好一个规范 h2 标题且至少包含一个顶级条目。近似标题(如 "Limitations"、"Deferred"、"What is NOT here" 或 "Non-goals")会导致失败。
`packages/<group>/<pkg>/package.json` 下的每份包清单都有一个同 README,其中包含规范的 `## Known Limitations and Deferred Work` 章节。其项目符号记录该包拥有的持久消费缺口和不明显的维护者约束;普通清理仍留在源码 TODO 或所属 Agent Noteagent 决策记录)中。[`verify-package-readme-limitations` 门禁](../../../../scripts/verify-package-readme-limitations.ts)从清单推导包集合,拒绝缺 README,并要求恰好一个规范 h2 且至少包含一个顶层项目符号。“Limitations”“Deferred”“What is NOT here”或“Non-goals”等近似标题都会失败。
如果一个包确实没有需要声明的限制事项,则将其列入 `NO_LIMITATIONS` 并省略该章节。新增限制事项时须移除该条目;重命名或移除条目会失败,因为每个条目都必须对应一个被扫描的包。
门禁检查的是存在性、格式与白名单。覆盖面和准确性由文档标准与 [prose 标准](../../../../.agents/skills/dsh-prose-standard/SKILL.md)的评审负责。常设规则 [packages/AGENTS.md](../../../../packages/AGENTS.md)。
门禁检查存在性、形状和允许列表。按照文档与[正文](../../../skills/dsh-prose-standard/SKILL.md)标准进行的评审负责覆盖面和准确性。常设规则位于 [packages/AGENTS.md](../../../../packages/AGENTS.md)。
## 曾考虑的替代方案
@@ -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
2026-07-12-package-model-experience-contract.md: 28a1c75ca6f451b4ca2f6da6abf36f72c5d43b31
2026-07-12-package-model-experience-contract.zh.md: 77fa7a67847ed186b65db57b6dda378a93b94d92
2026-07-12-package-model-experience-contract.md: 92a8e5a1a81d00dae085e4af89456896373058e6
2026-07-12-package-model-experience-contract.zh.md: d81e87f1cb52cc77800ad5e6256796aa389f3215
@@ -2,6 +2,8 @@
Status: implemented
English | [中文](2026-07-12-package-model-experience-contract.zh.md)
## Problem
A package README can explain APIs and runtime mechanics without answering the questions that dominate an agent harness's behavior and cost: what from this package reaches a model request, under which conditions, how long those tokens remain, and whether later requests preserve a reusable KV-cache prefix. The omission is especially hard to audit in a plugin architecture. A consumer may turn a backend result into a tool message, a policy plugin may replace success with an error, compaction may remove old history, and an agent-scoped registration may change one agent's prompt or schemas while leaving every other agent unchanged. Reading only the nominally model-facing packages therefore misses real context effects, while reading source across every dependency is too expensive for routine review.
@@ -1,4 +1,4 @@
# RFC: Package Model Experience 契约
# Agent Note: Package Model Experience 契约
Status: implemented
@@ -6,28 +6,28 @@ Status: implemented
## 问题
一个 package(包)的 README 可以解释 API 和运行时机制,却不回答那个主导 agent harness(智能体框架)行为与成本的问题:本 package 中有什么内容会进入模型请求、在什么条件下进入、以及这些 token 会保留多久。在插件架构中,这一缺失尤其难以审计。消费可能把后端结果转为工具消息,策略插件可能把成功替换为错误,上下文压缩(context compaction可能移除旧历史,agent 作用域的注册可能改变某个 agent 的提示词或 schema其他 agent 不受影响。因此,只阅读名义上面向模型的 package 会遗漏真实的上下文影响,而跨所有依赖阅读源码对日常评审来说又太昂贵
README 可以解释 API 和运行时机制,却不回答主导 agent harness(智能体框架)行为与成本的问题:该包的哪些内容会进入模型请求、在什么条件下进入、这些 token 会保留多久,以及后续请求是否会保留可复用的 KV cache 前缀。在插件架构中,这种遗漏尤其难以审计。消费可能把后端结果转为工具消息,策略插件可能以错误取代成功结果,压缩可能移除旧历史,agent 范围的注册可能改变某个 agent 的 prompt 或 schema,却不影响其他 agent。因此,只阅读名义上面向模型的包会遗漏真实的上下文效应,而在每次常规评审中跨所有依赖阅读源码又成本过高
## 决策
每个具有面向模型或模型相邻契约的 workspace package README,在末尾、`## Known Limitations and Deferred Work` 之前放置规范的 [Model Experience 章节](../../../cookbook/adding-a-package.md#4-write-the-package-readme);位于 no-limitations 允许列表上的 package 以 Model Experience 本身作为末尾章节。经审计确认模型无关的通用 package 通过 `NO_MODEL_EXPERIENCE_SECTION` 省略该章节。
每个具有面向模型或邻近模型契约的 workspace 包 README 都以规范的[模型体验章节](../../../../docs/cookbook/adding-a-package.md#4-write-the-package-readme)收尾,位置紧邻 `## Known Limitations and Deferred Work` 之前;位于“无限制项”允许列表中的包则以模型体验本身结尾。经审计确认模型无关的通用通过 `NO_MODEL_EXPERIENCE_SECTION` 省略该章节。
具有直接、条件、有上限、生命周期、多表面或辅助模型效应的 package每个上下文表面使用一个 H3。每个 H3 说明相关模型接收到什么内容以及何时接收,然后对 token 效应进行分类。由 package 拥有的稳定文本逐字引用:系统提示词行文和其他长字面量使用嵌套 H4`markdown` 围栏,短字面量则以行内形式呈现并使用命名插值占位符。工具 schema 表面链接生成[工具目录](../../../tool-catalog.md)中对应的锚定章节,仅说明组合或配置差异;仅运行时定义的工具则解释为何目录中未收录。数据依赖和提供方拥有的文本以摘要形式描述。agent 作用域的可见性须显式说明;当作用域可以隐藏其中一个而不影响另一时,提示词表面与 schema 表面保持分
具有直接、条件、有上限、生命周期、多表面或辅助模型效应的包,为每个上下文表面使用一个 H3。每个表面包含三个有序 H4 字段——`What the model sees``Token effect``KV Cache effect`——每个字段都以一个正文段落开头。cache 字段区分仅追加增长、稳定重复前缀、替换先前 token,以及独立模型请求;它点明由包拥有、且能在新内容追加前改变请求的每项配置、范围、生命周期、压缩或路由变化。“Does not invalidate”表示该包保留一个已经可复用的前缀,并非承诺 provider 一定命中 cache 或保留某段时间。由包拥有的稳定文本按原文精确引用:system prompt 正文和其他长字面量在引入它们的字段下使用带标题的 H5`markdown` 围栏,通常位于 `What the model sees`;短字面量则以内联形式保留,并点名插值占位符。工具 schema 表面链接生成[工具目录](../../../../docs/tool-catalog.md)中带锚点的章节,并且只陈述组合或配置增量;仅运行时定义解释目录为何省略它们。依赖数据和由 provider 拥有的文本采用摘要。agent 范围的可见性须显式说明;当范围可隐藏 prompt 与 schema 中的一者而不影响另一时,两种表面保持分
没有模型上下文效应的 package,或其路径完全由另一个 package 渲染的 package,使用验证器审计过的单句形式:`None, as ``Indirectly, through `。纯传输和无密钥测试支持 package 在不创建模型绑定内容时使用 none 式。提供方后端即使对数据进行上限或过滤也使用 indirect 式;组装 bundle 在命名子 package 拥有全部效应时同样使用 indirect 形式。这些句子定位贡献所在,而不重述消费方的内容。结构化章节同样只记录 package 自身拥有的输入、变换和差异
没有模型上下文效应的包,或某条路径完全由另一个包渲染的包,使用验证器审计过的短格式:一句以 `None, as ``Indirectly, through ` 开头的句子,随后是一个 `KV Cache effect` H4 和一个正文段落。纯传输和无密钥测试支持包若不创建任何进入模型的内容,就使用 none 式。provider 后端即使会限制或过滤数据也使用 indirect 式;具名子项拥有全部效应时,接线 bundle 也使用该格式。这些章节定位贡献并声明不会直接使 cache 失效,同时不重复陈述消费者。结构化章节同样只记录由包拥有的输入、变换和增量
`verify-package-readme-model-experience` 发现 package manifest(元数据清单并验证三种分类、规范末尾章节顺序、必填字段、具体字面量证据、嵌套逐字块和锚定的工具目录链接。它在 `doc-sync`(文档同步门禁)和并行门禁运行器中执行。覆盖面、链接相关性和事实准确性仍由评审把关
`verify-package-readme-model-experience` 发现清单并验证三种分类、规范末尾章节顺序、确切字段标题深度与顺序、非空字段段落、逐字块的 H5 归属、具体字面量证据,以及带锚点的工具目录链接。它在 `doc-sync` 和并行门禁 runner 中运行。评审仍负责覆盖面、链接相关性和事实准确性。
## 曾考虑的替代方案
- **只记录注册提示词或工具的 package**:否决。后端、策略插件、适配器、持久化、作用域和压缩都会改变 token 的内容或生命周期,却不拥有面向模型的 schema。
- **从源码生成一份集中式上下文成本目录**:否决。AST 能找到注册点,但无法推断语义条件,如历史保留、输出截断、父子可见性或辅助模型边界。package README 是实现本地的契约;集中副本会增加又一个漂移面。
- **要求给出精确 token 数**:否决。精确数量取决于所选模型的 tokenizer、适配器序列化方式、配置和运行时数据。稳定的契约是增长形状:每请求固定、每调用条件性、保留、替换、有上限或零直接影响。
- **使用三列表格**:否决。精确源文本和条件结果形状使单元格密集难以扫读。重复的子章节为每个上下文表面提供读的纵向空间,同时保留相同的字段
- **使用表格**:否决。精确源文本和条件结果形状使单元格密集难以扫读。重复的小节在保留相同字段的同时,为每个上下文表面提供读的纵向空间。
- **允许所有零影响 package 省略该章节**:否决。无约束的缺失在「经审计的零影响」和「忘记写文档」之间有歧义。省略仅限于在验证器中以理由命名的模型无关通用 package;模型相邻的零影响 package 保留一句显式说明。
- **审计的零影响或简单间接 package 也要求完整结构化式**:否决。围绕一个事实重复标签没有意义。受门禁约束的句保留显式覆盖而无需繁文缛节
- **要求经审计的零效应包或简单间接包使用完整结构化式**:否决。它会围绕一个事实重复标签。受门禁约束的句子加 cache 字段既保留显式覆盖,又没有多余仪式
- **只有约定而无门禁**:否决。仓库级契约必须覆盖未来的每个 package;评审者的记忆无法可靠地检测到遗漏的 README 章节。
## 后果
评审者可以从任何面向模型或模型相邻的 package 出发,直接看到它对话模型、子模型和辅助调用的贡献,无需重建完整插件图。token 预算工作可以区分每次请求的重复开销与数据依赖的历史,agent 作用域的变更有了显式的文档检查点。package 作者在模型可见行为变更时维护一个或多个紧凑的上下文表面块或一句分类说明;经审计的通用 package 不承载无关的模型样板文字。结构化字段不承诺提供方精确的 token 数;测量仍然是模型和负载特定的,而文档化的增长可见性契约保持稳定。
评审者可以从任何面向模型或邻近模型的包开始,看到它对话模型、子模型和辅助调用的贡献,无需重建完整插件图。token 预算工作可以区分重复请求开销和依赖数据的历史,而 cache 敏感工作可以识别仅追加路径,以及最早由包引起的前缀变更。agent 范围变更有明确的文档检查点。每当模型可见行为发生变化时,包作者都要维护一个或多个紧凑的上下文表面块或一种已分类的短格式;经审计的通用包不携带无关的模型样板。结构化字段不承诺 provider 精确的 token 数或 cache 命中;测量仍取决于模型、provider 和工作负载,而所记录的增长可见性和前缀稳定性契约保持稳定。