Merge retargeted parent into Claude Code subagent provider
This commit is contained in:
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-06-11-quality-gates.md
|
||||
2026-06-11-quality-gates.md: 60db7ba5cfa8184c0fcce764aa027f32a9b721ab
|
||||
2026-06-11-quality-gates.zh.md: a5ac7cd831255d479f7a1d75586785e546877fba
|
||||
2026-06-11-quality-gates.zh.md: 10585977820ba8e4f4bc6e251d484dc657d0558c
|
||||
|
||||
@@ -14,16 +14,16 @@ Status: implemented
|
||||
|
||||
每条可机械检查的 AGENTS.md 承诺都有一个以非零状态退出的命令。CI 执行完整集合,而 Git 钩子将延迟预算留给可低成本发现的本地缺陷:
|
||||
|
||||
- 最严格的 TypeScript 配置(`noUncheckedIndexedAccess`、`exactOptionalPropertyTypes` 等);示例、测试和脚本通过根目录的 no-emit `tsconfig.json` 在 CI 中进行类型检查,而包(package)/vendor 代码保持在各自 project-reference 边界之后。
|
||||
- 最严格的 TypeScript 配置(`noUncheckedIndexedAccess`、`exactOptionalPropertyTypes` 等);示例、测试和脚本通过根目录的 no-emit `tsconfig.json` 在 CI 中进行类型检查,而包/vendor 代码保持在各自 project-reference 边界之后。
|
||||
- [Oxlint](2026-07-29-oxlint-linter.md) 配合类型感知的 TypeScript 规则以及 @stylistic 和 SonarJS 兼容插件,强制执行统一代码风格和文件内重复逻辑检查;vendor 代码排除在外。
|
||||
- jscpd 检测包的生产 TypeScript 代码与仓库脚本中的跨文件克隆;窄范围的源码区间例外用于记录有意为之的并行实现。
|
||||
- `packages/*/*/src` 下按文件 100% 覆盖率(v8);不可达的防御性守卫使用 `/* v8 ignore */ ` 并注明理由,而非删除。
|
||||
- knip(死代码/依赖)、publint(包的正确性)、workspace 约束(workspace 规则:private、cordis peer+dev、统一版本、ESM),以及对构建出的包声明文件进行 NodeNext 消费方类型检查。
|
||||
- lefthook pre-commit 先应用仅用于格式化的 ESLint 修复,再执行 Oxlint 验证和原生修复,拒绝已暂存的空白问题并检查 vendor manifest;pre-push 运行增量类型检查。CI 在 Node 22.19/24/26 上运行完整矩阵,并对 Headless、TUI、ACP(Agent Client Protocol)、JSON-RPC、工作流和代码运行时入口路径执行已构建应用的冒烟测试。
|
||||
- lefthook pre-commit 先应用仅用于格式化的 ESLint 修复,再执行 Oxlint 验证和原生修复,拒绝已暂存的空白问题并检查 vendor manifest(元数据清单);pre-push 运行增量类型检查。CI 在 Node 22.19/24/26 上运行完整矩阵,并对 Headless、TUI、ACP(Agent Client Protocol)、JSON-RPC、工作流和代码运行时入口路径执行已构建应用的冒烟测试。
|
||||
|
||||
## 后果
|
||||
|
||||
- 约定不会因 agent 更替而失效;可低成本发现的 commit/push 缺陷在本地失败,其余完整规则违规在 CI 中失败。
|
||||
- 约定不会因 agent 更替而失效;可低成本发现的 commit/push 缺陷会在本地触发失败,其余违规会在 CI 的完整检查中触发失败。
|
||||
- 门禁本身也是需要维护的代码;配置变更与其他变更一样需要评审。
|
||||
- 100% 覆盖率的压力可能催生无断言的测试——变异测试是计划中的对策(见[变异测试提案](../../proposed/testing/2026-06-11-mutation-testing.md))。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.md
|
||||
2026-06-16-pnpm-over-yarn.md: 30b34c65fdea94b20dec4d627a0fca40de760fd1
|
||||
2026-06-16-pnpm-over-yarn.zh.md: eb13890b7e7051301874b9273966771328b065a3
|
||||
2026-06-16-pnpm-over-yarn.zh.md: 7560b748654cf79409e16b6c2b1c701b367f01d4
|
||||
|
||||
@@ -6,18 +6,18 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
本仓库最初使用 **Yarn 4** 搭配 `node-modules` 链接器启动。这是一个刻意保守的选择:行为类似 npm 的扁平布局,同时享有 Yarn 的 workspaces 和 `yarn constraints`。它能正常工作。但 Yarn 4 源自 Plug'n'Play 的血统,使得 `node-modules` 链接器成为非主流模式;而更广泛的 JS 生态——工具默认值、CI action、Corepack 示例、贡献者的熟悉度——正日益以 pnpm 为中心。对于一个主要由 agent(智能体)构建、偶尔有人类贡献者阅读的仓库而言,「大多数工具和人所期望的包管理器」具有实际价值:更少的意外、更成熟的故障路径、更多可直接复用的解答。
|
||||
本仓库最初使用 **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`);开发流程、测试和源码模式 demo 都通过各自声明的 TypeScript 启动器运行,产物检查则会显式构建。因此,包管理器只需做到:(a)解析并链接 `node_modules`,(b)运行 workspace 脚本,(c)强制执行 workspace 约束。唯一的 Yarn 特有资产是 `yarn.config.cjs`(`@yarnpkg/types` 约束引擎),体量小且可机械地重新表达。这与 [tsdown 决策](../../archived/process/2026-06-11-tsdown-over-dumble.md)的逻辑一致:在爆炸半径尚小时,将承重工具换为生态更健康的选项。
|
||||
切换成本目前处于最低点。本仓库尚无任何包发布(每个包都是 `private: true`);开发流程、测试和源码模式 demo 都通过各自声明的 TypeScript 启动器运行,产物检查则会显式构建。因此,包管理器只需做到:(a)解析并链接 `node_modules`,(b)运行 workspace 脚本,(c)强制执行 workspace 约束。唯一的 Yarn 特有资产是 `yarn.config.cjs`(`@yarnpkg/types` 约束引擎),体量小且可机械地重新表达。这与 [tsdown 决策](../../archived/process/2026-06-11-tsdown-over-dumble.md)的逻辑一致:在爆炸半径尚小时,将承重工具换为生态更健康的选项。
|
||||
|
||||
## 决策
|
||||
|
||||
采用 **pnpm 11.7.0**,通过 `packageManager` 字段固定版本,经 Corepack 安装(与 Yarn 使用的机制相同):
|
||||
|
||||
- **Workspaces** 从 `package.json` 的 `workspaces` 数组 + `.yarnrc.yml` 迁移到 `pnpm-workspace.yaml`(`vendor/*`、`packages/*`——同样的 glob;`examples/*` 保持非 workspace,与先前设置及 tsdown 的显式 glob 一致)。
|
||||
- **严格符号链接链接器**(pnpm 默认)取代 Yarn 的提升式 `node-modules` 链接器。我们刻意**不**添加 `node-linker=hoisted` / `shamefully-hoist` 逃生口:pnpm 的非扁平 `node_modules` 会让幻影依赖(引用未声明的传递依赖)大声失败,这对于一个以机械门禁为核心质量保障的仓库(见[机械质量门禁](2026-06-11-quality-gates.md))是一项*优势*。门禁套件(类型检查、lint、test、build、knip)是证明不存在此类幻影导入的安全网。
|
||||
- **严格符号链接链接器**(pnpm 默认)取代 Yarn 的提升式 `node-modules` 链接器。我们刻意**不**添加 `node-linker=hoisted` / `shamefully-hoist` 逃生口:pnpm 的非扁平 `node_modules` 会使幻影依赖(引用未声明的传递依赖)明确报错,这对于一个以机械门禁为核心质量保障的仓库(见[机械质量门禁](2026-06-11-quality-gates.md))是一项*优势*。门禁套件(类型检查、lint、test、build、knip)是证明不存在此类幻影导入的安全网。
|
||||
- **构建脚本白名单。** pnpm 10+ 不运行依赖的生命周期脚本,除非将其加入白名单。`pnpm-workspace.yaml` 携带一份显式的 `allowBuilds` 映射(`esbuild`、`lefthook`、`@google/genai`、`protobufjs`)——与本仓库对模型/工具输出已有的供应链加固姿态一致,现在也应用于安装时的代码执行。`peerDependencyRules.allowedVersions.typescript: '>=5 <7'` 消除仓库内 TypeScript 的良性 peer 范围警告。
|
||||
- **约束变为包管理器无关。** `yarn.config.cjs`(导入 `@yarnpkg/types`,使用 `Yarn.workspaces()` / `workspace.set()`)被 `scripts/check-workspace-constraints.ts` 取代——一个纯 tsx 脚本,通过 `pnpm run constraints` 运行。它在相同的 `vendor` + `packages` 范围上强制执行完全相同的不变式:每个包 `private: true`;`@deepseek-ai/dsh-*` 包将 `cordis` 同时声明为对等依赖(peer dependency)和 dev 依赖且范围一致、使用根 `package.json` 的版本、设置 `type: module`;vendor 包仅检查 privacy。
|
||||
- **约束变为包管理器无关。** `yarn.config.cjs`(导入 `@yarnpkg/types`,使用 `Yarn.workspaces()` / `workspace.set()`)被 `scripts/check-workspace-constraints.ts` 取代——一个纯 tsx 脚本,通过 `pnpm run constraints` 运行。它在相同的 `vendor` + `packages` 范围上强制执行完全相同的不变式:每个包 `private: true`;`@deepseek-ai/dsh-*` 包将 `cordis` 同时声明为对等依赖(peer dependency)和 dev 依赖且范围一致、使用根 `package.json` 的版本、设置 `type: module`;vendor 包仅检查是否为私有。
|
||||
- 所有 CI、lefthook 钩子、`package.json` 脚本和文档中的 `yarn …` 动词变为 `pnpm …` / `pnpm run …`。`yarn.lock` → `pnpm-lock.yaml`(lockfile v9)。`.gitignore` 将 `.yarn/` 换为 `.pnpm-store/`。vendor README(如 `vendor/cordis/README.md`)按 Vendoring Policy 保持其上游 `yarn` 示例不变。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
@@ -30,14 +30,14 @@ Status: implemented
|
||||
|
||||
约束检查失去了 Yarn 的自动**修复**能力(`workspace.set()` 能原地改写 manifest);tsx 脚本仅做检查,不通过时以非零退出码和消息退出。这是可接受的:CI 从未运行过 `--fix`,且需要手动编辑的情况很少。贡献者现在为 pnpm 而非 Yarn 运行 `corepack enable`;`pnpm exec lefthook install` 取代 `yarn lefthook install`(`postinstall` 钩子仍会运行 `lefthook install`)。
|
||||
|
||||
性能(迁移时在开发 NFS 文件系统上测量;单次运行样本,方差大——仅供方向性参考,非基准测试套件):
|
||||
性能(迁移时在开发 NFS 文件系统上测量;运行次数为个位数的样本,方差大——仅供方向性参考,非基准测试套件):
|
||||
|
||||
| 场景 | Yarn 4 | pnpm 11 |
|
||||
|---|---|---|
|
||||
| 冷启动(空缓存/store,无 `node_modules`) | ~14 s | ~16 s |
|
||||
| 热重链接(缓存/store 已热,`node_modules` 已删除) | ~12–14 s | ~15–22 s |
|
||||
| 冻结,`node_modules` 存在(无操作重验证) | ~2–8 s | ~0.5–7 s |
|
||||
| 冻结安装,`node_modules` 存在(无操作重验证) | ~2–8 s | ~0.5–7 s |
|
||||
|
||||
在快速本地磁盘上,pnpm 的内容寻址 store 通常在冷/热安装中胜出,尤其在多个检出之间的**磁盘占用**方面优势明显(一个全局 store 通过硬链接接入每个 `node_modules`,而 Yarn 每个 worktree 复制约 279 MB——部分开发者经常为本仓库保持约 10 个或更多 worktree)。该去重优势在上述迁移时数据中**未能**体现,因为测试 store 和 `node_modules` 位于不同文件系统,硬链接失效;在单文件系统的开发机或 CI 缓存上则适用。诚实的总结:在我们的 NFS 开发文件系统上,安装速度在噪声范围内不分伯仲;迁移的理由是生态对齐、幻影依赖安全性和跨检出磁盘去重,而非原始安装时间的胜出。
|
||||
|
||||
所有质量门禁(constraints、类型检查、lint、doc-sync、达到 100% 的 test:coverage、构建、knip、publint 以及已构建应用的冒烟测试)均在 pnpm 下通过,证明更换 linker 没有引入幽灵依赖故障。
|
||||
所有质量门禁(constraints、类型检查、lint、doc-sync、达到 100% 的 test:coverage、构建、knip、publint 以及已构建应用的冒烟测试)均在 pnpm 下通过,证明更换链接器没有引入幻影依赖故障。
|
||||
|
||||
@@ -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-06-17-ts-build-config.md
|
||||
2026-06-17-ts-build-config.md: 20dd3d8d0a11e01397c36388903cd112a170f0bf
|
||||
2026-06-17-ts-build-config.zh.md: 196ed3f2921a4be6f65cd56fc496993b7121f484
|
||||
2026-06-17-ts-build-config.md: ec4c6a4aeb1074a69d45b2cf4b4c733b410ccceb
|
||||
2026-06-17-ts-build-config.zh.md: 8115bb2557eea967d57eb6693e2bb288188792a8
|
||||
|
||||
@@ -32,7 +32,7 @@ In-package relative imports use explicit `.ts` specifiers.
|
||||
|
||||
`pnpm run build` is a two-stage build:
|
||||
|
||||
- Stage 1: `tsc -b` over the root solution emits per-module `.js`, declarations `.d.ts`, JS sourcemaps `.js.map`, and declaration sourcemaps `.d.ts.map` into each package's `lib/types`. This is the authoritative TypeScript compilation result. For publish we keep `.d.ts` / `.d.ts.map` and ignore `.js` / `.js.map`.
|
||||
- Stage 1: `tsc -b` over the root solution emits per-module `.js`, declarations `.d.ts`, JS sourcemaps `.js.map`, and declaration sourcemaps `.d.ts.map` into each package's `lib/types`. This is the authoritative TypeScript compilation result. Publication keeps `.d.ts`; packages whose runtime exports explicitly point into the emitted tree also keep its `.js` files. `.js.map` and `.d.ts.map` remain in the local build tree.
|
||||
- The graph is the project-reference graph reachable from the root solution `tsconfig.json` through the two aggregates ([topology](2026-07-22-tsconfig-solution-root-two-aggregates.md)). It validates and emits package/vendor build results.
|
||||
- Stage 2: a bundler reads the emitted JS under `lib/types` and writes the bundled runtime entry as `lib/index.js` or `lib/index.mjs` (follow current behavior). This stage is bundling only. It must not read TypeScript source or emit declarations.
|
||||
|
||||
@@ -77,9 +77,9 @@ Build responsibilities are clearer:
|
||||
|
||||
- Each module under `packages/<group>/<pkg>` and `vendor/*` has one local tsconfig for build, typecheck, and tools that run source directly, such as the `dsh` source loader, `tsx`, and `vitest`.
|
||||
- The `build` command drives the root solution graph. `tsc -b` owns the publishable per-module `.js` and `.d.ts` output, and the bundler owns only `lib/index.*`.
|
||||
- `lib/types/*.d.ts` and `.d.ts.map` are the publish declaration output.
|
||||
- `lib/types/*.d.ts` is the publish declaration output; `.d.ts.map` remains only as a local compilation artifact.
|
||||
- `lib/types/*.d.ts` uses explicit `.ts` relative specifiers, which TypeScript's NodeNext/Node16 resolver maps to sibling `.d.ts` files.
|
||||
- `lib/types/*.js` is only a bundler input and must not be used as a runtime entry or public import target.
|
||||
- `lib/types/*.js` is normally only a bundler input. It is published only when an explicit runtime export points into the emitted tree.
|
||||
- `lib/index.*` is the publish runtime output and is generated by the bundler, currently `tsdown`.
|
||||
- `pnpm run verify-node-next-types` scans built declarations for relative specifiers without file extensions, then typechecks a temporary external ESM consumer with `moduleResolution: "NodeNext"` against the built `types`/`exports` surface, so declaration specifier regressions fail before publish.
|
||||
- The `typecheck` command uses `tsconfig.json`. Examples, tests, and scripts are checked by the root no-emit project, while packages and vendor modules keep the same emit behavior as `build`. Package and vendor source stays behind project-reference boundaries.
|
||||
|
||||
@@ -32,7 +32,7 @@ Status: implemented
|
||||
|
||||
`pnpm run build` 是两阶段构建:
|
||||
|
||||
- 阶段 1:在根 solution 上执行 `tsc -b`,将逐模块的 `.js`、声明文件 `.d.ts`、JS sourcemap `.js.map` 和声明 sourcemap `.d.ts.map` 输出到各包的 `lib/types`。这是权威的 TypeScript 编译结果。发布时保留 `.d.ts` / `.d.ts.map`,忽略 `.js` / `.js.map`。
|
||||
- 阶段 1:在根 solution 上执行 `tsc -b`,将逐模块的 `.js`、声明文件 `.d.ts`、JS sourcemap `.js.map` 和声明 sourcemap `.d.ts.map` 输出到各包的 `lib/types`。这是权威的 TypeScript 编译结果。发布时保留 `.d.ts`;如果包的运行时 export 显式指向该输出树,也会保留其中的 `.js` 文件。`.js.map` 和 `.d.ts.map` 留在本地构建树中。
|
||||
- 该图是从根 solution `tsconfig.json` 经两个聚合可达的 project-reference 图([拓扑](2026-07-22-tsconfig-solution-root-two-aggregates.md)),用于校验并输出包/vendor 的构建结果。
|
||||
- 阶段 2:打包器读取 `lib/types` 下输出的 JS,将打包后的运行时入口写为 `lib/index.js` 或 `lib/index.mjs`(沿用当前行为)。此阶段仅做打包,禁止读取 TypeScript 源码或输出声明文件。
|
||||
|
||||
@@ -77,9 +77,9 @@ tsx scripts/clean.ts
|
||||
|
||||
- `packages/<group>/<pkg>` 和 `vendor/*` 下的每个模块有一份本地 tsconfig,同时服务于构建、类型检查和直接运行源码的工具(如 `dsh` 源码 loader、`tsx` 和 `vitest`)。
|
||||
- `build` 命令驱动根 solution 图。`tsc -b` 负责可发布的逐模块 `.js` 和 `.d.ts` 输出,打包器仅负责 `lib/index.*`。
|
||||
- `lib/types/*.d.ts` 和 `.d.ts.map` 是发布用的声明输出。
|
||||
- `lib/types/*.d.ts` 是发布用的声明输出;`.d.ts.map` 只作为本地编译产物保留。
|
||||
- `lib/types/*.d.ts` 使用显式 `.ts` 相对说明符,TypeScript 的 NodeNext/Node16 解析器会将其映射到同级的 `.d.ts` 文件。
|
||||
- `lib/types/*.js` 仅作为打包器输入,禁止用作运行时入口或公开导入目标。
|
||||
- `lib/types/*.js` 通常仅作为打包器输入。只有显式运行时 export 指向该输出树时,才会发布这些文件。
|
||||
- `lib/index.*` 是发布用的运行时输出,由打包器(当前为 `tsdown`)生成。
|
||||
- `pnpm run verify-node-next-types` 扫描构建出的声明文件,检查是否存在缺少文件扩展名的相对说明符,然后以 `moduleResolution: "NodeNext"` 对构建出的 `types`/`exports` 接口进行临时外部 ESM 消费方的类型检查,确保声明说明符的回归在发布前被捕获。
|
||||
- `typecheck` 命令使用 `tsconfig.json`。示例、测试和脚本由根 no-emit 项目检查,包和 vendor 模块保持与 `build` 相同的输出行为。包和 vendor 源码始终处于 project-reference 边界之后。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.md
|
||||
2026-06-18-markdown-cross-link-lint.md: b8b1337e9d758da6a4cc0bb46a6b37906357f877
|
||||
2026-06-18-markdown-cross-link-lint.zh.md: 823af80950127a0bf0b76da7769611d0d3a6c09b
|
||||
2026-06-18-markdown-cross-link-lint.zh.md: 9b627ebb17a0567424ca0caaeac8edd9b36917f2
|
||||
|
||||
@@ -6,9 +6,9 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
本仓库的文档通过相对路径互相链接:`[topic](../implemented/2026-…-….md)`、`[the cookbook](adding-a-tool.md)`、`[architecture.md](../../architecture.md)`。此前没有任何机制验证这些目标是否存在。重命名或移动文件会静默破坏所有指向它的链接,且在读者点击之前不可见。[Doc-sync 强制](../../archived/process/2026-06-11-doc-sync-enforcement.md)已经将两类文档漂移机械化(无法编译的代码块、陈旧的事件分类表),[verify-md-wrap](../../archived/process/2026-06-11-doc-sync-enforcement.md) 覆盖了第三类(硬换行的段落),但死链是第四类同样可机械检查、却仍靠肉眼验证的问题。
|
||||
本仓库的文档通过相对路径互相链接:`[topic](../implemented/2026-…-….md)`、`[the cookbook](adding-a-tool.md)`、`[architecture.md](../../architecture.md)`。此前没有任何机制验证这些目标是否存在。重命名或移动文件会静默破坏所有指向它的链接,且在读者点击之前不可见。[doc-sync(文档同步门禁)强制执行](../../archived/process/2026-06-11-doc-sync-enforcement.md)已经将两类文档漂移的检查自动化(无法编译的代码块、陈旧的事件分类表),[verify-md-wrap](../../archived/process/2026-06-11-doc-sync-enforcement.md) 覆盖了第三类(硬换行的段落),但死链是第四类同样可机械检查、却仍靠肉眼验证的问题。
|
||||
|
||||
引入这道门禁的直接动因是 Agent Note(agent 决策记录)目录树重组:将 `docs/adr/` 与 `.agents/notes/` 统一到同一个 `.agents/notes/` 下,并设置 `proposed/`、`implemented/`、`rejected/` 子目录,需要手工重命名约 40 条文档间链接。只要有一处路径输入错误,就会在没有任何检查拦截的情况下交付断链。
|
||||
引入这道门禁的直接动因是 Agent Note 目录树重组:将 `docs/adr/` 与 `.agents/notes/` 统一到同一个 `.agents/notes/` 下,并设置 `proposed/`、`implemented/`、`rejected/` 子目录,需要手工重命名约 40 条文档间链接。只要有一处路径输入错误,就会在没有任何检查拦截的情况下交付断链。
|
||||
|
||||
## 决策
|
||||
|
||||
@@ -18,9 +18,9 @@ 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` symlink 会解析到 AGENTS.md 文件)。该检查接入 `doc-sync`,因此相关文档变更与 CI 执行同一套断链检查。
|
||||
检查范围与其他门禁一致,并额外包含 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` 片段的链接仍会通过(文件可解析;片段被剥除)。
|
||||
本门禁检查的是*文件存在性*,而非锚点有效性:指向一个真实文件但带有 `#wrong-heading` 片段的链接仍会通过(文件路径可解析;片段被剥除)。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-06-20-agent-note-classification.md
|
||||
2026-06-20-agent-note-classification.md: edb65a772c81b818bf3811c9f3f64ed1a6497647
|
||||
2026-06-20-agent-note-classification.zh.md: eff333b52309fbfc7706fbf15a26b07c761f5b0e
|
||||
2026-06-20-agent-note-classification.zh.md: e2a21fc4ca6579720ed25aae09ef439f63a2de8c
|
||||
|
||||
@@ -6,7 +6,7 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
仅按生命周期组织的 Agent Note(agent 决策记录)目录树(`proposed/` / `implemented/` / `rejected/`)无法记录每个文件包含哪一*类*决策。读者浏览某个生命周期时,如果不逐一打开文件,就无法区分新功能、移除项或工具策略变更。
|
||||
仅按生命周期组织的 Agent Note 目录树(`proposed/` / `implemented/` / `rejected/`)无法记录每个文件包含哪一*类*决策。读者浏览某个生命周期时,如果不逐一打开文件,就无法区分新功能、移除项或工具策略变更。
|
||||
|
||||
本仓库一贯的倾向是[机械质量门禁优于行文规范](2026-06-11-quality-gates.md):不被机器检查的约定终将腐烂。因此这里的分类方案必须可强制执行,而非靠自觉的文件头。
|
||||
|
||||
@@ -19,9 +19,9 @@ Status: implemented
|
||||
| 类别 | 涵盖范围 |
|
||||
|---|---|
|
||||
| `feature` | 面向用户或模型的新功能。 |
|
||||
| `bug-fix` | 修正缺陷或填补事后复盘暴露的空白。 |
|
||||
| `simplification` | 移除代码、行为或对外表面积,不引入新功能。 |
|
||||
| `architecture` | 关于**交付源码**的结构性决策——包(package)之间的关系、运行时词汇。 |
|
||||
| `bug-fix` | 修正缺陷或填补事故复盘(postmortem)暴露的空白。 |
|
||||
| `simplification` | 移除代码、行为或对外接口范围,不引入新功能。 |
|
||||
| `architecture` | 关于**交付源码**的结构性决策——包之间的关系、运行时词汇。 |
|
||||
| `process` | **围绕**代码的工具、策略或工作流,而非运行时行为。 |
|
||||
| `testing` | 测试基础设施与策略。 |
|
||||
|
||||
@@ -31,14 +31,14 @@ Status: implemented
|
||||
|
||||
两者都是 `doc-sync`(文档同步门禁)的成员,风格与 `verify-md-wrap` 一致(tsx ESM,只校验不生成,首个违规即以非零退出码退出):
|
||||
|
||||
- **`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` 扩展名,因此不处理无扩展名的行文。
|
||||
- **`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:`),由门禁解析。可行,但它将路径已能承载的事实重复到文件中,且行内容可能与所在文件夹不一致。路径编码使标签与其存储合二为一,没有需要保持同步的东西。
|
||||
- **在每个文件中添加 `Classification:` 文本行**(紧邻 `Status:`),由门禁解析。可行,但它将路径已能承载的事实重复到文件中,且行内容可能与所在文件夹不一致。路径编码使标签与其存储合二为一,没有需要保持同步的东西。
|
||||
- **设立 `refactor` 类别。** 与 `simplification` 几乎完全重叠;唯一有人试图用来区分的标准是「可观察行为是否改变?」,而 `simplification` 已经编码了这一点(它不改变)。一个类别即可,无需两个。
|
||||
- **生成或手工维护的语料索引。** 不予采纳:生命周期/类别目录树才是权威结构;集中式清单会制造合并热点,却没有提供目录树导航或仓库搜索无法实现的发现能力。
|
||||
- **生成或手工维护的文档集索引。** 不予采纳:生命周期/类别目录树才是权威结构;集中式清单会制造合并热点,却没有提供目录树导航或仓库搜索无法实现的发现能力。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md
|
||||
2026-06-20-core-data-structures-catalog.md: ef100f96b06c454cfd1ec092cc7fd23e712bdf7a
|
||||
2026-06-20-core-data-structures-catalog.zh.md: 4ace2b8c8a6b08e7721c1df8003ccfbdb128daf1
|
||||
2026-06-20-core-data-structures-catalog.zh.md: 0545235f96341a638c43805de1b47a400d69e618
|
||||
|
||||
@@ -6,22 +6,22 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
试图理解 harness 的读者可以在 [architecture.md](../../../../docs/architecture.md) 中找到它的*行为*(服务图、会话/轮次/步骤生命周期、事件分类),却找不到一个统一描述其*词汇*的地方,也就是这些行为所传递的数据结构。类型形状只存在于源码中,散落在 `packages/*/src/types.ts` 各处,因此要理解“什么是 `Message`、`SessionEvent`、`StreamChunk`”,就必须直接阅读声明。文字目录会有所帮助,但复述或复制粘贴类型定义的目录会在字段发生变化时立即腐化,而不同步的类型文档比没有文档更糟,因为读者会信任它。
|
||||
试图理解 harness 的读者可以在 [architecture.md](../../../../docs/architecture.md) 中找到它的*行为*(服务图、会话/轮次/步骤生命周期、事件分类),却找不到一个统一描述其*词汇*的地方,也就是这些行为所传递的数据结构。类型形状只存在于源码中,散落在 `packages/*/src/types.ts` 各处,因此要理解「什么是 `Message`、`SessionEvent`、`StreamChunk`」,就必须直接阅读声明。文字目录会有所帮助,但复述或复制粘贴类型定义的目录会在字段发生变化时立即腐化,而不同步的类型文档比没有文档更糟,因为读者会信任它。
|
||||
|
||||
因此,这项工作有两个相互交织的问题:**这样的目录应包含什么**(范围问题——harness 有数十种跨包(package)边界的类型,把它们全部倾倒进来对谁都没有帮助),以及**如何避免粘贴的类型定义发生漂移**(持久性问题)。本 Agent Note(agent 决策记录)记下了这两项决策。与它配套的[生成式 Cordis 事件与服务目录](2026-06-20-generated-cordis-catalog.md)从*接线*维度形成补充:本文对数据结构编目,另一篇则对传递这些结构的事件和服务编目。
|
||||
因此,这项工作有两个相互交织的问题:**这样的目录应包含什么**(范围问题——harness 有数十种跨包边界的类型,把它们全部倾倒进来对谁都没有帮助),以及**如何避免粘贴的类型定义发生漂移**(持久性问题)。本 Agent Note 记下了这两项决策。与它配套的[生成的 Cordis 事件与服务目录](2026-06-20-generated-cordis-catalog.md)从*接线*维度形成补充:本文对数据结构编目,另一篇则对传递这些结构的事件和服务编目。
|
||||
|
||||
## 决策
|
||||
|
||||
新增的 `docs/core-data-structures/` 目录对这些词汇编目,并配有新的 `verify-type-equiv` 文档同步门禁,使每个粘贴的类型声明及其 JSDoc 与源码保持同步。
|
||||
新增的 `docs/core-data-structures/` 目录对这些词汇编目,并配有新的 `verify-type-equiv` doc-sync(文档同步门禁),使每个粘贴的类型声明及其 JSDoc 与源码保持同步。
|
||||
|
||||
### 何为"核心"——主干与 seam 的分界线
|
||||
### 何为「核心」——主干与 seam 的分界线
|
||||
|
||||
范围界定并非自上而下拍定,而是将候选定义逐一对照具体的边界类型反复测试,直到一条规则在所有案例中都成立。决定性的测试是 `BashExecRequest`/`BashExecSpec`/`BashRunResult`:bash 是一个能力 *seam*,不属于 agent loop(智能体循环)主干;如果这些算"核心",那么"核心"就意味着*所有跨包词汇*,目录沦为平铺罗列;如果不算,"核心"就意味着*中央主干*,bash 词汇归入子页面。后者胜出,由此确定了整体结构:一个**分层文件夹**,而非一份平铺文档。
|
||||
范围界定并非自上而下拍定,而是将候选定义逐一对照具体的边界类型反复测试,直到一条规则在所有案例中都成立。决定性的测试是 `BashExecRequest`/`BashExecSpec`/`BashRunResult`:bash 是一个能力 *seam*,不属于 agent loop(智能体循环)主干;如果这些算「核心」,那么「核心」就意味着*所有跨包词汇*,目录沦为平铺罗列;如果不算,「核心」就意味着*中央主干*,bash 词汇归入子页面。后者胜出,由此确定了整体结构:一个**分层文件夹**,而非一份平铺文档。
|
||||
|
||||
确定其余案例的规则是:***你编写、持有或接收的类型是核心;为其提供类型推导、渲染或持久化的机制是子页面细节。*** 逐一验证如下:
|
||||
|
||||
- 一个数据结构是**核心**的,如果它流经 agent loop 主干——无论加载了哪些插件,循环在每个轮次都会持有、派生、流式输出或记录它(`Message`、`StreamChunk`、`SessionEvent`、`Agent` 句柄)——**或者**它是插件作者面对某条流水线时编写的唯一标志性类型(`ToolDefinition`)。
|
||||
- `ToolDefinition` 是核心(它是每个工具作者编写的东西),**即使循环从不持有它**——对于这一个标志性类型,撰写重要性压过了严格的"流经主干"规则。但它的类型推导机制——`ValueSchemaSpec`、`ParameterSchemaSpec`、`InferValue` 与 `InferArgs`——是子页面细节。这就是主干与 seam 分界线的精确表述。
|
||||
- `ToolDefinition` 是核心(它是每个工具作者编写的东西),**即使循环从不持有它**——对于这一个标志性类型,编写层面的重要性压过了严格的「流经主干」规则。但它的类型推导机制——`ValueSchemaSpec`、`ParameterSchemaSpec`、`InferValue` 与 `InferArgs`——是子页面细节。这就是主干与 seam 分界线的精确表述。
|
||||
- `ToolSchema` 是核心(它是流经每个步骤的模型请求 `GenerateOptions` 的一个字段),即使它在概念上属于工具流水线——当*流经主干*与*概念归属*冲突时,前者胜出。
|
||||
- 工具展示词汇(`ToolCallView`/`ToolResultView` 等)、`SessionPersistence` 持久性 seam 以及 bash 词汇是子页面。
|
||||
|
||||
@@ -31,18 +31,18 @@ Status: implemented
|
||||
|
||||
持久性要求很具体:文档展示当前类型声明与原始 JSDoc 的**逐字**内容(让读者看到真实形状和源码契约,而非复述),**并且**以机械方式保证其与源码匹配。仓库已经会编译 ` ```ts ` 围栏块(`doc-typecheck`),但真正接受类型检查的块需要导入噪音,而且只能证明*可赋值性*——字段改名或 JSDoc 变化仍可能通过。因此:
|
||||
|
||||
- 完整的类型声明及其 JSDoc 会逐字粘贴到专用的 ` ```ts type-equiv ` 围栏中。简洁的 ` ```ts public-api ` 围栏承载与源码等价的类环境投影,用于实现体不应进入目录的类。`doc-typecheck` 会识别并跳过这两种围栏(裸声明无法独立编译),并且**将它们排除在 opt-out 比例之外**——它们是单独受检的类别,而不是未经检查的草图。
|
||||
- 完整的类型声明及其 JSDoc 会逐字粘贴到专用的 ` ```ts type-equiv ` 围栏中。简洁的 ` ```ts public-api ` 围栏承载类的源码等价环境声明投影,用于实现体不应进入目录的类。`doc-typecheck` 会识别并跳过这两种围栏(裸声明无法独立编译),并且**将它们排除在 opt-out 比例之外**——它们是单独受检的类别,而不是未经检查的草图。
|
||||
- 新增的 `scripts/verify-type-equiv.ts` 通过 TypeScript 解析器提取每个块,并断言其声明结构和每条 JSDoc 注释都与所声明的符号匹配,只忽略格式空白和非 JSDoc 注释。普通块保留完整声明。`public-api` 投影保留类的公共字段、构造函数、访问器和方法及其原始 JSDoc,同时移除实现体以及私有或受保护成员。之所以选择它而非编译式 `_Check` 断言,是因为目录所保留的是源码名称与文档一致性,而不是可赋值性。
|
||||
- 来源信息存放在集中的 `scripts/type-equiv.manifest.json`(`{ doc, symbol, source }` 条目)中,**而非**行文中的指令注释。脚本在每个主 type-equiv 块与一条 manifest 条目之间强制执行 **1:1 对应**,因此一个块永远不会被静默漏检,一条条目也永远不会腐烂。只有当配对 `.zh.md` 块的完整受跟踪围栏序列在顺序、类型和按字节精确的正文上均与无后缀兄弟文件匹配时,才会复用后者的条目;否则门禁会独立检查该块,发现没有 manifest 条目后失败。
|
||||
- 来源信息存放在集中的 `scripts/type-equiv.manifest.json`(`{ doc, symbol, source }` 条目)中,**而非**行文中的指令注释。脚本在每个主 type-equiv 块与一条 manifest(元数据清单)条目之间强制执行 **1:1 对应**,因此一个块永远不会被静默漏检,一条条目也永远不会腐烂。只有当配对 `.zh.md` 块的完整受跟踪围栏序列在顺序、类型和按字节精确的正文上均与无后缀兄弟文件匹配时,才会复用后者的条目;否则门禁会独立检查该块,发现没有 manifest 条目后失败。
|
||||
- 接入 `doc-sync`,因此相关文档变更会在本地运行它,CI 也会与其他文档检查一起运行它。
|
||||
|
||||
### 维护是作者的职责,门禁作为兜底
|
||||
|
||||
`verify-type-equiv` 能捕获已记录类型的*粘贴漂移*,但无法告诉你一个全新的核心类型没有被记录。因此 AGENTS.md 和 `dsh-code-review` skill(技能)已更新,要求在变更添加或重塑已记录类型时同步更新目录——门禁处理漂移,人处理新增表面。
|
||||
`verify-type-equiv` 能捕获已记录类型的*粘贴漂移*,但无法告诉你一个全新的核心类型没有被记录。因此 AGENTS.md 和 `dsh-code-review` skill(技能)已更新,要求在变更添加或重塑已记录类型时同步更新目录——门禁处理漂移,人处理新增类型。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
- **平铺罗列所有跨包词汇**:`BashExecRequest` 测试案例否决了它。如果 seam 词汇算"核心",目录对谁都没帮助;分层的主干与 seam 结构胜出。
|
||||
- **平铺罗列所有跨包词汇**:`BashExecRequest` 测试案例否决了它。如果 seam 词汇算「核心」,目录对谁都没帮助;分层的主干与 seam 结构胜出。
|
||||
- **用编译式 `_Check` 可赋值性断言**代替源码匹配:否决。可赋值性不会保留名称或 JSDoc;同类型字段改名或契约注释变化仍会通过。
|
||||
- **来源信息作为行文中的指令注释**:否决,改用集中 manifest;其强制的 1:1 对应确保一个块永远不会被静默漏检,一条条目也永远不会腐烂。
|
||||
|
||||
@@ -50,11 +50,11 @@ Status: implemented
|
||||
|
||||
主干与 seam 规则在采纳前经过了 `BashExecRequest`、工具 schema 与定义、schema DSL、展示类型以及会话/持久化拆分的逐一测试。
|
||||
|
||||
`verify-type-equiv` 必须扫描完整的 Markdown 范围,而不仅是清单点名的文档。否则,未列入清单的 `type-equiv` 块就会逃过所宣称的一一检查。因此,门禁会将此类块报告为孤儿。本 Agent Note 将这条失败关闭扫描规则,连同主干与 seam 的分界决策及逐字匹配决策一并记录;生成式 Cordis 目录在[其 Agent Note](2026-06-20-generated-cordis-catalog.md) 中有对称的设计记录。
|
||||
`verify-type-equiv` 必须扫描完整的 Markdown 范围,而不仅是 manifest 点名的文档。否则,未列入清单的 `type-equiv` 块就会逃过所宣称的一一检查。因此,门禁会将此类块报告为未列入清单的块。本 Agent Note 将这条默认拒绝放行的扫描规则,连同主干与 seam 的分界决策及逐字匹配决策一并记录;生成的 Cordis 目录在[其 Agent Note](2026-06-20-generated-cordis-catalog.md) 中有对称的设计记录。
|
||||
|
||||
## 后果
|
||||
|
||||
- 这些词汇现在有一个**无法悄然漂移**的唯一归属:源码中的字段或公共类成员发生变化后,`doc-sync` 和 CI 中的 `verify-type-equiv` 会持续失败,直至粘贴内容刷新。Cordis 服务方法仍由生成式服务目录负责,而不会在此重复。
|
||||
- 这些词汇现在有一个**无法悄然漂移**的唯一归属:源码中的字段或公共类成员发生变化后,`doc-sync` 和 CI 中的 `verify-type-equiv` 会持续失败,直至粘贴内容刷新。Cordis 服务方法仍由生成的服务目录负责,而不会在此重复。
|
||||
- 主干与 seam 分界线是一个可复用的范围界定工具,而非一次性的:同一条「你编写/持有/接收的东西是核心;为其提供类型推导/渲染/持久化的机制是细节」规则,后来也被用于界定事件/服务目录的 harness 层与继承层分层。
|
||||
- `ts type-equiv` 围栏是继 ` ```ts `(编译)和 ` ```ts ignore-check `(草稿)之后的第三种文档块类别。后续的姊妹门禁又增加了第四种 ` ```ts cordis-catalog `(生成签名),复用了相同的跳过并排除处理。
|
||||
- 添加或重塑核心类型现在附带一项文档义务,作者必须履行(门禁无法检测缺失的*新*类型),由 `dsh-code-review` 检查清单兜底。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.md
|
||||
2026-06-20-generated-cordis-catalog.md: 5005e50a2e23c8286a8057dc57f365554bde5056
|
||||
2026-06-20-generated-cordis-catalog.zh.md: 0f8f20673b01ef1218a7d2dfa47c189862803775
|
||||
2026-06-20-generated-cordis-catalog.zh.md: 384e00d23aafeec7c7bed9bf572a628f77150993
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note: 生成式 Cordis 事件与服务目录
|
||||
# Agent Note: 生成的 Cordis 事件与服务目录
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,9 +6,9 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
插件作者需要两个参考面,而此前没有任何单一文档能提供:他们可以监听的每一个 Cordis **事件**(含精确签名与分发模式),以及他们可以调用的每一个 `ctx.<key>` **服务**(含精确接口)。相关信息虽然存在,但散落各处:`docs/architecture.md` 中一张手工维护的事件分类*表格*(名称 + 行文描述的 Mode/Purpose,由 `verify-event-taxonomy` 做名称集合校验)、一张服务映射表(8 行角色描述),以及 `interface Events` / `interface Context` 声明本身。分类表格还有一个盲区:它无法捕获全新的*未记录*事件——名称集合校验器只检查两侧已有的名称。
|
||||
插件作者需要两类此前没有任何单一文档能同时提供的参考信息:他们可以监听的每一个 Cordis **事件**(含精确签名与分发模式),以及他们可以调用的每一个 `ctx.<key>` **服务**(含精确接口)。相关信息虽然存在,但散落各处:`docs/architecture.md` 中一张手工维护的事件分类体系*表格*(名称 + 行文描述的 Mode/Purpose,由 `verify-event-taxonomy` 做名称集合校验)、一张服务映射表(8 行角色描述),以及 `interface Events` / `interface Context` 声明本身。分类体系表格还有一个盲区:它无法捕获全新的*未记录*事件——名称集合校验器只检查两侧已有的名称。
|
||||
|
||||
这是对[核心数据结构目录](../../../../docs/core-data-structures/core.md)([其 Agent Note(agent 决策记录)](2026-06-20-core-data-structures-catalog.md))在接线维度上的补充:前者对循环传递的*数据结构*编目(经验证的手工粘贴),本文则对传递它们的*事件和服务*编目。
|
||||
这是对[核心数据结构目录](../../../../docs/core-data-structures/core.md)([其 Agent Note](2026-06-20-core-data-structures-catalog.md))在接线维度上的补充:前者对循环传递的*数据结构*编目(经验证的手工粘贴),本文则对传递它们的*事件和服务*编目。
|
||||
|
||||
## 决策
|
||||
|
||||
@@ -16,26 +16,26 @@ Status: implemented
|
||||
|
||||
`scripts/gen-cordis-catalog.ts` 使用 TypeScript 编译器 API,根据声明和源码 JSDoc 分别生成事件与服务参考。事件包含分派模式及其原始成员 JSDoc;服务包含公共签名及各方法的原始 JSDoc。确定性的 `--write` 和 `--check` 模式使两个页面成为生成产物,并由 `doc-sync` 强制检查新鲜度。
|
||||
|
||||
纯生成在此处是正确的,因为代码库足够规范,AST 就是全部事实:每个事件/服务名称都是字符串字面量,可以往返映射到静态声明——不存在动态命名的事件,也不存在仅运行时的服务。因此生成的文档不可能出错,且从结构上消除了未记录事件的缺口(生成器枚举源码,而非校验手写子集)。
|
||||
完全通过生成来构建目录在此处是正确的,因为代码库足够规范,AST 包含全部事实:每个事件/服务名称都是字符串字面量,可以往返映射到静态声明——不存在动态命名的事件,也不存在仅运行时的服务。因此生成的文档不可能出错,且从结构上消除了未记录事件的缺口(生成器枚举源码,而非校验手写子集)。
|
||||
|
||||
具体选择:
|
||||
|
||||
- **`@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`……)都会通过人工维护的映射链接到其主要核心数据结构页面。AST 遍历采用失败关闭策略:每个参数、泛型约束/默认值和返回类型引用都必须已映射、是签名自身的类型参数、是点名的 TypeScript/Cordis 基础类型,或带有点名的例外及其非目录文档归属。违规会连同源码位置汇总报告,并点明相应的归属列表。该映射不会复用 `type-equiv.manifest.json`,因为后者记录 `…Map` 符号,而签名引用派生的联合类型名,并且会在多个页面列出某些符号。
|
||||
- **分层范围。** 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`……)都会通过人工维护的映射链接到其主要核心数据结构页面。AST 遍历采用默认拒绝放行的策略:每个参数、泛型约束/默认值和返回类型引用都必须已映射、是签名自身的类型参数、是点名的 TypeScript/Cordis 基础类型,或带有点名的例外及其非目录文档归属。违规会连同源码位置汇总报告,并点明相应的归属列表。该映射不会复用 `type-equiv.manifest.json`,因为后者记录 `…Map` 符号,而签名引用派生的联合类型名,并且会在多个页面列出某些符号。
|
||||
- **专用围栏。** 签名块使用 ` ```ts cordis-catalog ` 信息字符串,并把原始事件或公共方法 JSDoc 直接放在其声明之前。`doc-typecheck` 会识别并跳过这些裸片段,将其排除在 opt-out 比例之外——与 `type-equiv` 块的处理相同。
|
||||
|
||||
本决策**取代** [doc-sync 强制](../../archived/process/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 不受影响。
|
||||
本决策**取代** [doc-sync 强制](../../archived/process/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 不受影响。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
- **校验而非生成(退役的分类检查所做的事)**:*仅对本参考面*反转了这一策略。此处的数据可以机械地完整获取,因此生成严格强于对手工表格做名称集合校验(完整签名、不会漂移、能捕获未记录事件)。
|
||||
- **遍历 vendor AST 以获取继承层**:否决,改用人工维护表格。cordis-core 的 `Context` 混合了真正的 ctx 成员与非服务字段,且固定的 vendor 接口面仅在有意同步时才变化。
|
||||
- **复用 `type-equiv.manifest.json` 作为签名交叉链接映射**:否决,改用完整的人工维护常量和失败关闭覆盖。清单记录 `…Map` 符号,而签名引用派生的联合类型名,并且会在多个页面列出某些符号。显式映射让每个渲染目标和每个非目录例外都成为可评审的决策。
|
||||
- **校验而非生成(退役的分类体系检查所做的事)**:*仅对本参考面*反转了这一策略。此处的数据可以机械地完整获取,因此生成严格强于对手工表格做名称集合校验(完整签名、不会漂移、能捕获未记录事件)。
|
||||
- **遍历 vendor AST 以获取继承层**:否决,改用人工维护表格。cordis-core 的 `Context` 混合了真正的 ctx 成员与非服务字段,且固定的 vendor 接口仅在有意同步时才变化。
|
||||
- **复用 `type-equiv.manifest.json` 作为签名交叉链接映射**:否决,改用完整的人工维护常量和默认拒绝放行的覆盖检查。manifest 记录 `…Map` 符号,而签名引用派生的联合类型名,并且会在多个页面列出某些符号。显式映射让每个渲染目标和每个非目录例外都成为可评审的决策。
|
||||
|
||||
## 后果
|
||||
|
||||
- 目录不会发生漂移:提交文件未反映的源码变化会使 `doc-sync` 和 CI 中的 `verify-cordis-catalog` 失败。新事件缺少 `@mode` 标签、标签与其签名冲突,或签名类型未分类,都会直接使生成器失败。
|
||||
- 事件与服务方法契约只有一个归属——声明处的 JSDoc。目录会在生成的签名块中重复该原始 JSDoc,并使用其描述部分作为条目正文,因此单薄的源码文档只会生成单薄的目录条目。
|
||||
- 继承层是手工摘要,因此 vendor 同步若新增或重命名了 cordis-core 事件或 `ctx` 成员,需要同步编辑 `gen-cordis-catalog.ts` 中的人工维护表格。这是不遍历固定 vendor 源码的有意代价;它很少变化,且在生成器中有明确标注。
|
||||
- 继承层是手工摘要,因此 vendor 同步若新增或重命名了 cordis-core 事件或 `ctx` 成员,需要同步编辑 `gen-cordis-catalog.ts` 中的人工维护表格。这是不遍历固定版本的 vendor 源码的有意代价;它很少变化,且在生成器中有明确标注。
|
||||
- `verify-event-taxonomy.ts` 被删除,`docs/architecture.md` 的事件表格也已移除;之前链接到特定表格行的人现在会落在生成目录上。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md
|
||||
2026-07-02-bilingual-docs-and-pairing-gate.md: 8e9d0ab8653528517346b198c97814d8d8979440
|
||||
2026-07-02-bilingual-docs-and-pairing-gate.zh.md: cf37d56be0180cc742b0dabefeb1f8f3b54ba557
|
||||
2026-07-02-bilingual-docs-and-pairing-gate.zh.md: e8f18ebb0d1620830116f6d5d9ffef29664a578e
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note:通过配对兄弟文件与配对门禁实现双语文档
|
||||
# Agent Note: 通过配对兄弟文件与配对门禁实现双语文档
|
||||
|
||||
Status: implemented
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-02-tool-schema-catalog.md
|
||||
2026-07-02-tool-schema-catalog.md: c8cc69df428f6eee0f66ed976865afe2a0702448
|
||||
2026-07-02-tool-schema-catalog.zh.md: f08cb5b5312dd07f91037a4382bf5e416cae552d
|
||||
2026-07-02-tool-schema-catalog.zh.md: a1af928cc799974b93d2c8e29792790bc1f4e9e6
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note: 生成式工具 schema 目录(启动并采集)
|
||||
# Agent Note: 生成的工具 schema 目录(启动并采集)
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -10,7 +10,7 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
目录通过**启动每个工具插件并读取其已注册 schema** 来生成,而不是解析源码。`scripts/gen-tool-catalog.ts` 在全新的 Cordis `Context` 上挂载每个已发布工具包(package);该上下文还提供 `SystemPrompt`、`ToolRegistry` 以及插件 `apply` 所读取的注入 seam。生成器调用 `ctx.tools.schemas()`——也就是发送给模型的确切 `ToolSchema[]`——随后释放上下文,并为每个包渲染一个 `## <package>` 章节,每个工具附带一个 ` ```json ` `parameters` 块。它与 `gen-cordis-catalog` / `gen-module-graph` 的 CLI 形状一致:默认 `--write` 重新生成;提交副本陈旧时 `--check` 失败;输出具有确定性(按清单排序,工具按名称排序)。`verify-tool-catalog`(即 `--check`)在 `doc-sync` 内运行,因此相关文档变更和 CI 会执行同一项新鲜度检查。
|
||||
目录通过**启动每个工具插件并读取其已注册 schema** 来生成,而不是解析源码。`scripts/gen-tool-catalog.ts` 在全新的 Cordis `Context` 上挂载每个已发布工具包;该上下文还提供 `SystemPrompt`、`ToolRegistry` 以及插件 `apply` 所读取的注入 seam。生成器调用 `ctx.tools.schemas()`——也就是发送给模型的确切 `ToolSchema[]`——随后 dispose(资源释放)上下文,并为每个包渲染一个 `## <package>` 章节,每个工具附带一个 ` ```json ` `parameters` 块。它与 `gen-cordis-catalog` / `gen-module-graph` 的 CLI 形状一致:默认 `--write` 重新生成;提交副本陈旧时 `--check` 失败;输出具有确定性(按清单排序,工具按名称排序)。`verify-tool-catalog`(即 `--check`)在 `doc-sync` 内运行,因此相关文档变更和 CI 会执行同一项新鲜度检查。
|
||||
|
||||
### 为何启动而非解析(核心要点)
|
||||
|
||||
@@ -35,7 +35,7 @@ Cordis 目录是纯 TypeScript AST 遍历,因为每个事件/服务名都是
|
||||
|
||||
`packages/*/tool-*` 下已发布的产品工具包,每个都使用默认配置启动,包括 `dsh-tool-bash`(`bash`)、`dsh-tool-tasks`(`task_output`、`task_list`、`task_kill`)和 `dsh-tool-subagent`(`subagent`)。仅供示例使用的工具不在范围内。
|
||||
|
||||
目录的单位是包,而非每个配置化的工具实例。每个包以默认配置启动一次;加载时的别名(如 `subagent_fork`)会注明,但不枚举所有部署排列。部署清单是一个独立的、无界的接口。
|
||||
目录的单位是包,而非经过配置的每个工具实例。每个包以默认配置启动一次;加载时的别名(如 `subagent_fork`)会注明,但不枚举所有部署配置组合。部署清单覆盖的是一个独立且无界的范围。
|
||||
|
||||
### 使用普通 `json` 围栏
|
||||
|
||||
@@ -49,7 +49,7 @@ schema 块使用 ` ```json `,而非自定义的 `ts` 系围栏。`doc-typechec
|
||||
|
||||
## 后果
|
||||
|
||||
- 目录不会发生漂移:提交文件未反映的工具 schema 变化会使 `doc-sync` 和 CI 中的 `verify-tool-catalog` 失败。新增的 `tool-*` 包若未加入清单,会直接使完整性守卫失败。
|
||||
- 目录不会发生漂移:提交文件未反映的工具 schema 变化会使 `doc-sync` 和 CI 中的 `verify-tool-catalog` 失败。新增的 `tool-*` 包若未加入 manifest,会直接使完整性守卫失败。
|
||||
- 工具描述文本有唯一归属——源码中 `defineTool` 的 `description`——生成的条目质量取决于它,与 Cordis 目录对事件 JSDoc 施加的强制力相同。
|
||||
- 生成器导入并执行工作区包(这是仓库中第一个这样做的脚本;其他脚本只读文本)。它通过根 `tsconfig` 的 `paths` 映射在 `tsx` 下运行,使用与演示和测试相同的未构建源码路径,因此不需要构建步骤。
|
||||
- 未来某个工具背后新增一个能力 seam,意味着 manifest 中需要新增一条配方条目(声明要挂载哪些 seam)。这正是上文指出的有意为之的手写成本;仅在新增工具包时才需变更。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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: a52c40a9a147fd39fdec4c61079822f1b1115227
|
||||
2026-07-04-doc-tiers-and-budgets.zh.md: d03c2046c9963e4d62d2a7221d4563f60d3f4953
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-04-doc-tiers-and-budgets.md
|
||||
2026-07-04-doc-tiers-and-budgets.md: e7b3421d09a1ae5ab9a9373e8040832c1b0d4b97
|
||||
2026-07-04-doc-tiers-and-budgets.zh.md: 18bf98777c87512e6161a4529db66030ced47bfd
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note: Documentation tiers, budgets, and the ceiling gate
|
||||
# Agent Note: Documentation structure, tiers, and budgets
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,13 +6,14 @@ English | [中文](2026-07-04-doc-tiers-and-budgets.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Standing docs accumulated repeated rules, retold incidents, duplicated package maps, and stale Agent Note summaries despite existing writing guidance. Because review alone did not prevent that growth, the repository needed a mechanical budget alongside its documentation taxonomy.
|
||||
Standing docs accumulated repeated rules, retold incidents, duplicated package maps, and stale Agent Note summaries despite existing writing guidance. That guidance also did not define how a document's place in the hierarchy limits its scope or how ordered teaching differs from lookup-oriented material. Because review alone did not prevent that growth, the repository needed a mechanical budget alongside its documentation taxonomy.
|
||||
|
||||
## Decision
|
||||
|
||||
- **A tier taxonomy with one home per fact.** [docs/AGENTS.md](../../../../docs/AGENTS.md) is the documentation standard: it assigns every Markdown tier a single job (standing orders, system map, type catalog, decision records, incident stories, how-tos, per-package contracts, generated catalogs, workflows), forbids restating a fact outside its home tier (link instead), and carries the slop checklist used when writing or reviewing any doc.
|
||||
- **Structure follows the documentation tree.** [docs/AGENTS.md](../../../../docs/AGENTS.md) is the documentation standard: a document owns detail about its subject, summarizes only the purpose, responsibility, and high-level behavior of direct children, and links to deeper owners. [Agent Notes](../../README.md) remain outside this structural contract. Every human-facing document is a tutorial with an ordered outcome or a reference with an explicit lookup scope; a [postmortem](../../../../docs/postmortem/README.md) is an incident-scoped reference whose chronology records evidence. Tutorials introduce concepts in prerequisite order for the reader's starting knowledge.
|
||||
- **A tier taxonomy with one home per fact.** The standard assigns every Markdown tier one job, forbids restating a fact outside its home tier, and carries the slop checklist used when writing or reviewing any doc.
|
||||
- **A narrow, hard budget gate.** [scripts/verify-doc-budgets.ts](../../../../scripts/verify-doc-budgets.ts) joins `doc-sync`: every doc listed in [scripts/doc-budgets.manifest.json](../../../../scripts/doc-budgets.manifest.json) must stay under its word ceiling (`wc -w` semantics, whole file), and a budgeted file that is missing fails the gate so a rename cannot silently orphan its budget. Scope is deliberately only the accretion-prone standing docs — the root and subtree `AGENTS.md` files, `architecture.md`, `packages/README.md`, and the standing policy docs they evict content into (`docs/testing.md`, `docs/defensive-patterns.md`). Reference docs, Agent Notes, and package READMEs are unbudgeted: length is legitimate there when every row is a fact, and review plus the slop checklist govern them.
|
||||
- **Ceilings are an enforcement frontier that ratchets.** A ceiling sits at least 5% above the doc's current size — working headroom, so routine wording edits pass while real growth still trips the gate — and ratchets down, keeping that margin, as the doc is brought to its target budget (root `AGENTS.md` ≤ 1,500 words; `architecture.md` ≤ 1,800; subtree `AGENTS.md` ≤ 600; `packages/README.md` ≤ 600). When the gate goes red the fix is to relocate or condense per the taxonomy; raising a ceiling is permitted only with explicit justification in the PR description, the manifest diff being the reviewable act.
|
||||
- **Ceilings are an enforcement frontier that ratchets.** A doc at or below its target keeps at least 5% headroom as its ceiling ratchets down; a doc above target keeps a frozen ceiling that prevents growth until it reaches the target (root `AGENTS.md` ≤ 1,600 words; `architecture.md` ≤ 1,800; subtree `AGENTS.md` ≤ 600 except `packages/AGENTS.md` ≤ 650 and `docs/AGENTS.md` ≤ 1,250; `packages/README.md` ≤ 600). When the gate goes red, relocate or condense; raise a ceiling only with explicit PR justification.
|
||||
- **A thin workflow skill, contracts in docs.** [.agents/skills/dsh-doc-standards](../../../skills/dsh-doc-standards/SKILL.md) carries the placement/audit/red-gate workflow and defers to the standard as its source of truth, the same split as [dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md) over the i18n contract.
|
||||
|
||||
## Alternatives considered
|
||||
@@ -23,6 +24,7 @@ Standing docs accumulated repeated rules, retold incidents, duplicated package m
|
||||
|
||||
## Consequences
|
||||
|
||||
- Adding to a budgeted doc now requires displacement: relocate the addition to its taxonomy home with a pointer, or condense existing prose to pay for it. Growth without pruning fails CI.
|
||||
- The bring-under-target rewrites land as stacked follow-ups that ratchet the manifest down as they merge; until each lands, its doc's frozen ceiling only prevents further growth.
|
||||
- Adding to a budgeted doc requires displacement: relocate the addition to its taxonomy home with a pointer, or condense existing prose to pay for it. Growth without pruning fails CI.
|
||||
- Structural review starts with ownership and document form before sentence-level editing, so lower-level detail moves to its owner instead of being polished in the wrong place.
|
||||
- Budgeted docs that remain above target cannot grow; reaching the target restores the 5% working headroom.
|
||||
- Word count is a crude proxy accepted deliberately: it cannot judge quality, but it forces the relocation decision at exactly the moment content is being added, which is when the author has the context to place it correctly.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note: 文档分层、预算与上限门禁
|
||||
# Agent Note: 文档结构、层级与预算
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,23 +6,25 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
尽管已有写作指导,常设文档仍不断累积重复规则、反复讲述的事件、重复的包(package)映射,以及陈旧的 Agent Note(agent 决策记录)摘要。仅靠评审无法阻止这种增长,因此仓库需要在文档分类之外再配一套机械预算。
|
||||
尽管已有写作指导,常设文档仍不断累积重复规则、反复讲述的事件、重复的包(package)映射,以及陈旧的 Agent Note(agent 决策记录)摘要。该指导也未明确文档在层级中的位置如何限定其内容范围,以及按顺序引导读者学习的内容与面向查阅的材料有何不同。仅靠评审无法阻止这种增长,因此仓库需要在文档分类之外再配一套机械预算。
|
||||
|
||||
## 决策
|
||||
|
||||
- **每项事实只归属一处的层级分类。**[docs/AGENTS.md](../../../../docs/AGENTS.md) 是文档标准:它为每种 Markdown 层级分配单一职责(常设指令、系统图、类型目录、决策记录、事件故事、操作指南、各包契约、生成式目录、工作流),禁止在事实归属层级之外重复陈述(应改为链接),并包含编写或评审任何文档时使用的赘余检查清单。
|
||||
- **结构遵循文档树。**[docs/AGENTS.md](../../../../docs/AGENTS.md) 是文档标准:文档负责承载其主题的详细内容,仅概述直接子项的目的、职责和高层行为,并链接到更深层内容的归属文档。[Agent Note](../../README.md) 仍不受这一结构契约约束。每份面向人的文档要么是按顺序引导读者达成结果的教程(tutorial),要么是查阅范围明确的参考文档(reference);[事故复盘(postmortem)](../../../../docs/postmortem/README.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)。门禁变红时,修复方式是按分类体系迁移或压缩内容;只有在 PR(Pull Request)描述中给出明确理由时才允许提高上限,manifest(元数据清单)的 diff 本身即为可评审的动作。
|
||||
- **上限是只进不退的执行红线。** 达到或低于目标的文档在上限逐步下调时保留至少 5% 的余量;高于目标的文档则维持冻结的上限,在达到目标之前不得增长(根 `AGENTS.md` ≤ 1,600 词;`architecture.md` ≤ 1,800;子树 `AGENTS.md` ≤ 600,但 `packages/AGENTS.md` ≤ 650、`docs/AGENTS.md` ≤ 1,250;`packages/README.md` ≤ 600)。门禁变红时,迁移或压缩内容;只有在 PR(Pull Request)描述中给出明确理由时才提高上限。
|
||||
- **精简的工作流 skill(技能),契约归文档。**[.agents/skills/dsh-doc-standards](../../../skills/dsh-doc-standards/SKILL.md) 承载放置/审计/红灯门禁工作流,并以文档标准为真源,与 [dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md) 和 i18n 契约之间的分工相同。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
- **仅靠 skill 和评审纪律,不设门禁**:否决。上述膨胀正是在现行规则和评审注意力已经存在的情况下发生的;一条没有机械后盾的行文规则在此处已被证明无法维持,而本仓库自身的[质量门禁立场](2026-06-11-quality-gates.md)认为值得保持的不变式就值得编码。
|
||||
- **对所有文档层级全面设限**:否决。一刀切的上限恰好惩罚了那些正当的长文档(如特性矩阵或类型目录,每一行都是事实),并产生逐文件的例外变更,训练贡献者机械地批准提限。
|
||||
- **仅靠 skill 和评审纪律,不设门禁**:否决。上述膨胀正是在现行规则和评审注意力已经存在的情况下发生的;一条没有自动化保障的行文规则在此处已被证明无法维持,而本仓库自身的[质量门禁立场](2026-06-11-quality-gates.md)认为值得保持的不变式就值得编码。
|
||||
- **对所有文档层级全面设限**:否决。一刀切的上限恰好惩罚了那些正当的长文档(如功能矩阵或类型目录,每一行都是事实),并产生逐文件的例外变更,训练贡献者机械地批准提限。
|
||||
- **将标准放在 skill 内部**:否决。契约归文档,工作流归 skill;如果标准被塞进 SKILL.md,那些不调用该 skill 而直接编辑文档的 agent(智能体)就看不到它,而 `docs/AGENTS.md` 已经作为子树指令被任何在 `docs/` 下工作的人加载。
|
||||
|
||||
## 后果
|
||||
|
||||
- 向受预算约束的文档添加内容现在需要置换:将新增内容迁移到其分类体系归属地并留下指针,或压缩现有行文来腾出空间。只增不减会导致 CI 失败。
|
||||
- 精简到目标预算的重写以堆叠的后续 PR 落地,每次合并时同步下调 manifest 中的上限;在各自落地之前,文档冻结的上限仅阻止进一步膨胀。
|
||||
- 向受预算约束的文档添加内容需要置换:将新增内容迁移到其分类体系归属地并留下指针,或压缩现有行文来腾出空间。只增不减会导致 CI 失败。
|
||||
- 结构评审先检查归属关系和文档形式,再进行句子层面的编辑,使较低层级的细节迁移到其归属文档,而不是在错误的位置加以润色。
|
||||
- 仍高于目标的受预算约束文档不得增长;达到目标后,将恢复 5% 的工作余量。
|
||||
- 字数是一个粗糙的代理指标,这是有意接受的:它无法判断质量,但它在内容被添加的那一刻强制触发迁移决策,而那正是作者拥有足够上下文来正确放置内容的时刻。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-05-uniform-agent-note-format.md
|
||||
2026-07-05-uniform-agent-note-format.md: 06082251c1b96c90ed470d84224662e00e29791b
|
||||
2026-07-05-uniform-agent-note-format.zh.md: df6b0f4dfacf122f452807491680091827f69c25
|
||||
2026-07-05-uniform-agent-note-format.zh.md: 8f874f26c43a88ec3d2f1185c7b4d6f2f39ba1bb
|
||||
|
||||
@@ -6,17 +6,17 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
Agent Note(agent 决策记录)的路径编码了生命周期和类别,但文件内容仍混杂着不同标题、状态格式、ADR 与提案模板,以及已实现记录中的提案阶段章节。作者会复制随手找到的相邻文件,而生命周期迁移可能跳过必要的改写,因为没有门禁强制执行文件内契约。
|
||||
Agent Note 的路径编码了生命周期和类别,但文件内容仍混杂着不同标题、状态格式、ADR 与提案模板,以及已实现记录中的提案阶段章节。作者会复制随手找到的相邻文件,而生命周期迁移可能跳过必要的改写,因为没有门禁强制执行文件内契约。
|
||||
|
||||
## 决策
|
||||
|
||||
[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 失败,而不再依赖评审者记忆。
|
||||
[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 失败,而不再依赖评审者记忆。
|
||||
|
||||
定义该格式的同一变更规范化了整个语料库——遵循预发布立场:不设过渡期,不容忍双格式。唯一受既有条款豁免的是内容,而非格式:替代方案只能记录、不能杜撰,因此若某份格式制定前的 Agent Note 无法从记录中还原替代方案,就会携带确切的 `agent-note-format: alternatives-not-recorded` 注释;门禁只对日期早于本文的文件接受该注释。
|
||||
定义该格式的同一变更规范化了整个语料库——遵循预发布立场:不设过渡期,不容忍双格式。唯一适用既有内容豁免的是内容,而非格式:替代方案只能记录、不能杜撰,因此若某份格式制定前的 Agent Note 无法从记录中还原替代方案,就会带有内容完全匹配 `agent-note-format: alternatives-not-recorded` 的注释;门禁只对日期早于本文的文件接受该注释。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
- **完整的刚性模板**(每个生命周期使用固定章节顺序,重构每份 Agent Note 以适配):否决。大型设计 Agent Note 包含八到十五个定制技术章节(包(package)拓扑、线协议、schema),它们是承载设计的内容,而非漂移;刚性顺序会迫使我们现在进行破坏性改写,并永远与模板较劲。
|
||||
- **完整的刚性模板**(每个生命周期使用固定章节顺序,重构每份 Agent Note 以适配):否决。大型设计 Agent Note 包含八到十五个定制技术章节(包拓扑、协议契约、schema),它们是承载设计的内容,而非漂移;刚性顺序会迫使我们现在进行破坏性改写,并永远与模板较劲。
|
||||
- **仅规范化头部**(H1 和 Status,正文不动):否决。债务标记指出的是*正文*的体裁分裂,让 `Context`/`Decision` 与 `Problem`/`Proposal` 无限期并存什么也解决不了。
|
||||
- **不设 Status 行**(文件夹已经表示状态;格式制定前最新的三份 Agent Note 及其中一份的中文对应文件省略了该行):否决,保留文件的自描述性。通过门禁校验该行与文件夹一致,消除了原本促使我们删除它的漂移风险。
|
||||
- **带日期的 Status**(`Status: implemented (accepted YYYY-MM-DD)`):否决。接受日期属于叙述性历史,写作规则将其排除在文档之外;文件名承载首次提出日期,git 承载其余信息;门禁能检查日期格式,但永远无法检查其真实性。
|
||||
@@ -27,4 +27,4 @@ Agent Note(agent 决策记录)的路径编码了生命周期和类别,但
|
||||
|
||||
## 后果
|
||||
|
||||
现在每份 Agent Note 都需要稍多一些结构,而强制的 `Alternatives considered` 章节是有意设置的阻力:记录决策却不记录它胜过什么,会招致 Agent Note 本应防止的重新争论。无法还原替代方案的格式制定前 Agent Note 会永久保留既有条款注释——这是记录中诚实的缺口,而不是杜撰的理由。`doc-sync` 增加一道门禁;在生命周期文件夹之间移动 Agent Note 时,现在必须当场完成真正的工作(迁移本就应包含的正文改写),而不是推迟为无人跟踪的清理任务。三十九个债务标记已经消失,由它们一直等待的模板解决。
|
||||
现在每份 Agent Note 都需要稍多一些结构,而强制的 `Alternatives considered` 章节是有意设置的阻力:记录决策却不记录它胜过什么,会招致 Agent Note 本应防止的重新争论。无法还原替代方案的格式制定前 Agent Note 会永久保留既有内容豁免注释——这是记录中诚实的缺口,而不是杜撰的理由。`doc-sync` 增加一道门禁;在生命周期文件夹之间移动 Agent Note 时,现在必须当场完成真正的工作(迁移本就应包含的正文改写),而不是推迟为无人跟踪的清理任务。三十九个债务标记已经消失,由它们一直等待的模板解决。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-06-export-surface-jsdoc-gate.md
|
||||
2026-07-06-export-surface-jsdoc-gate.md: 821f68ebe60eba44cade223b3bc8286e09740956
|
||||
2026-07-06-export-surface-jsdoc-gate.zh.md: 6cfa28dc43ae2bdf46ec5d997a1a96d3e308559b
|
||||
2026-07-06-export-surface-jsdoc-gate.zh.md: 5f5f7b70a486b0aa5e8e7fd07810dfbcfb719a50
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note: 导出表面 JSDoc 门禁
|
||||
# Agent Note: 导出接口 JSDoc 门禁
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,25 +6,25 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
[Cordis JSDoc 完整性门禁](../../archived/process/2026-07-04-cordis-jsdoc-completeness-gate.md)使得 Cordis 表面上的参数和返回值不可能缺少文档——`interface Events` 成员和 `ctx.<key>` 服务类——但这只是插件作者所导入内容的一小部分。AGENTS.md 中的规则「每个导出(以及非显而易见的方法)都必须有解释语义的 JSDoc」在其他地方只能靠评审以行文方式检查,而且没有任何机制要求普通导出函数带 `@param`/`@returns`。采纳时的一次调查发现 34 个包(package)中有 203 个文档不完整的模块级导出:seam 相关辅助函数(`runBash`、`readForEdit`、`htmlToMarkdown`)、格式编解码器、完全无文档的接口和类型别名——恰恰是 IDE 消费方悬停查看的那些名称。
|
||||
[Cordis JSDoc 完整性门禁](../../archived/process/2026-07-04-cordis-jsdoc-completeness-gate.md)使得 Cordis 接口上的参数和返回值不可能缺少文档——`interface Events` 成员和 `ctx.<key>` 服务类——但这只覆盖插件作者可导入接口的一小部分。AGENTS.md 中的规则「每个导出(以及非显而易见的方法)都必须有解释语义的 JSDoc」在其他地方仍只是由评审检查的文字约定,而且没有任何机制要求普通导出函数带 `@param`/`@returns`。采纳时的一次调查发现 34 个包中有 203 个文档不完整的模块级导出:seam 相关辅助函数(`runBash`、`readForEdit`、`htmlToMarkdown`)、格式编解码器、完全无文档的接口和类型别名——恰恰是 IDE 消费方悬停查看的那些名称。
|
||||
|
||||
## 决策
|
||||
|
||||
新增门禁 `scripts/verify-export-jsdoc.ts`(`pnpm run verify-export-jsdoc`,接入 `doc-sync`(文档同步门禁),与 `verify-cordis-catalog` 并列),遍历每个 `packages/<group>/<pkg>/src/` 目录树下的所有模块级导出名称。解析与检查辅助函数从 `gen-cordis-catalog.ts` 移入共享的 `scripts/jsdoc.ts`,使得「已文档化」在两个表面上含义一致:描述性文字在第一个块标签处截止、每个可检查参数需要非空 `@param`、非 void 且有显式标注的返回值需要非空 `@returns`、过时的 `@param` 报错,违规项汇总为一份报告。
|
||||
新增门禁 `scripts/verify-export-jsdoc.ts`(`pnpm run verify-export-jsdoc`,接入 `doc-sync`(文档同步门禁),与 `verify-cordis-catalog` 并列),遍历每个 `packages/<group>/<pkg>/src/` 目录树下的所有模块级导出名称。解析与检查辅助函数从 `gen-cordis-catalog.ts` 移入共享的 `scripts/jsdoc.ts`,使得「已文档化」在两类接口上含义一致:描述性文字在第一个块标签处截止、每个可检查参数需要非空 `@param`、非 void 且有显式标注的返回值需要非空 `@returns`、过时的 `@param` 报错,违规项汇总为一份报告。
|
||||
|
||||
按声明类型划分的契约:
|
||||
|
||||
- 每个导出名称都需要带有非空描述文字的 JSDoc。
|
||||
- 函数类导出(函数声明;初始化器为函数或带有内联可调用标注的 const;非标识符的函数默认导出)遵循完整的函数契约,分类前会剥离包装表达式(括号、`as`/`satisfies` 类型断言、非空断言)。如果 const 声明器标注了一个具名类型(`export const f: Handler = …`),签名契约推迟到该类型自身的声明处,`@returns` 保持可选;内联的 `(x: T) => U` 标注或单调用签名字面量本身就是表面签名,适用完整契约;而混合了调用/构造签名与其他成员的字面量则直接拒绝(没有单一签名可供标签对照——请提取具名类型)。
|
||||
- 函数类导出(函数声明;初始化器为函数或带有内联可调用标注的 const;非标识符的函数默认导出)遵循完整的函数契约,分类前会剥离包装表达式(括号、`as`/`satisfies` 类型断言、非空断言)。如果 const 声明器标注了一个具名类型(`export const f: Handler = …`),签名契约推迟到该类型自身的声明处,`@returns` 保持可选;内联的 `(x: T) => U` 标注或单调用签名字面量本身就是对外签名,适用完整契约;而混合了调用/构造签名与其他成员的字面量则直接拒绝(没有单一签名可供标签对照——请提取具名类型)。
|
||||
- 导出类需要类级别的描述文字;公开方法(包括静态方法——可通过导出名称访问)遵循函数契约;公开属性和访问器需要描述文字(get/set 对由 getter 覆盖)。重载实现体免检——签名承载文档。
|
||||
- 导出接口、类型别名和枚举需要声明级别的描述文字;成员级别的强制有意推迟(承载关键成员契约的 seam 服务类已在 Cordis 门禁之下)。
|
||||
- 导出命名空间递归检查(在 ambient `declare` 命名空间内,每个成员隐式导出);命名空间本身仅在不与同名的已文档化声明合并时才需要描述文字(Config-namespace 惯用法只需文档化插件一次)。
|
||||
- `declare module`/`declare global` 体和 `export … from` 重导出语句被跳过:augmentation 不是包的导出,重导出的定义在其定义处检查。`export import X = N.member` 别名需要文档化自身——其目标可能是遍历不会访问的非导出命名空间成员——且门禁仅支持纯描述文字的目标类型:可调用、类或命名空间目标携带别名描述文字无法承载的签名/成员契约,门禁会拒绝并要求直接导出该声明。
|
||||
- 其余情况按封闭原则失败:`export =` 直接拒绝;基类从未命名的参数即使作为绑定模式仍需 `@param`;dispatch 不识别的导出语句类型本身就是违规——没有任何导出形式能因遗漏而免检。
|
||||
- 其余情况一律默认拒绝:`export =` 直接拒绝;即使参数使用绑定模式,只要基类未为其命名,仍需 `@param`;dispatch 不识别的导出语句类型本身就是违规——没有任何导出形式能因遗漏而免检。
|
||||
|
||||
三类豁免避免门禁要求样板代码,精神与 Cordis 门禁的 `this`/`next` 豁免一致(为已豁免的名称编写文档是允许的;只有缺失才不被检查):
|
||||
|
||||
- **继承成员。** 重写从其基类声明继承文档。新增的公开表面仍需文档:新增参数、将 protected 成员公开重写、或在 void 基类之上返回具体类型。继承查找和推断返回值分类是门禁唯一需要类型检查器的工作;其他检查使用 AST。
|
||||
- **继承成员。** 重写从其基类声明继承文档。新增的公开接口仍需文档:新增参数、将 protected 成员公开重写、或将基类的 void 返回改为具体类型。继承查找和推断返回值分类是门禁唯一需要类型检查器的工作;其他检查使用 AST。
|
||||
- **插件协议槽位。** 顶层的 `name`/`inject`/`reusable`/`Config` 常量和 `apply` 入口,以及插件类上的同名静态成员,属于框架协议:其形状由 Cordis 固定,模块文档注释加 `interface Config` 承载插件的真实语义。
|
||||
- **构造函数**,与 Cordis 门禁一致:插件类由框架构造,类文档承载全部说明。
|
||||
|
||||
@@ -34,11 +34,11 @@ Status: implemented
|
||||
|
||||
- **eslint-plugin-jsdoc**(`require-jsdoc`/`require-param`/`require-returns`):覆盖了机械核心,但无法表达本仓库的契约。继承成员豁免需要跨包的类型解析,协议槽位和命名空间合并惯用法是 Cordis 特有的,而完整性语义(标签前描述文字、过时标签报错、汇总报告)已在 `scripts/jsdoc.ts` 中与 catalog 生成器共享。两套微妙不同的「已文档化」定义,正是本仓库「单一归属」规则所要防止的失败模式。
|
||||
- **扩展 `gen-cordis-catalog.ts`**:catalog 生成器渲染一个精选表面并守卫其新鲜度;仓库级遍历没有 catalog 可渲染。共享辅助函数、保持遍历独立,使每个门禁的职责清晰可读。
|
||||
- **强制接口/类型别名的成员文档**:推迟。这会使检查表面成倍增长,而这些成员大多是自描述的字段;承载关键成员契约的 seam 服务类已有门禁。如果评审中出现成员文档漂移再重新考虑。
|
||||
- **强制接口/类型别名的成员文档**:推迟。这会使检查表面成倍增长,而检查对象大多只是含义直观的字段;承载关键成员契约的 seam 服务类已有门禁。如果评审中出现成员文档漂移再重新考虑。
|
||||
|
||||
## 后果
|
||||
|
||||
- 新导出不能在缺少文档的情况下落地:`verify-export-jsdoc` 会使 `doc-sync` 和 CI 失败。采纳时发现的 203 处缺口已在同一变更中补齐,因此门禁以绿色状态落地。
|
||||
- 新导出不能在缺少文档的情况下落地:`verify-export-jsdoc` 会使 `doc-sync` 和 CI 失败。采纳时发现的 203 处缺口已在同一变更中补齐,因此门禁落地时所有检查均已通过。
|
||||
- 导出函数必须标注返回类型(采纳时已全面满足,现在成为门禁依赖),并在 `@param` 需要命名参数时使用标识符参数。
|
||||
- seam 文档是权威的:实现从其继承链继承文档,值得保留在实现上的行为说明是补充,而非必需。
|
||||
- 门禁构建一个 `ts.Program`(约 6 秒)——唯一需要类型解析的文档门禁;在已编译文档片段的 `doc-sync` 内可以接受。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-06-node-engine-floor.md
|
||||
2026-07-06-node-engine-floor.md: ef047d885a442106a35922f4716d2996d8a98ca7
|
||||
2026-07-06-node-engine-floor.zh.md: a0281addf7d4327d7f6ea30e3a3f0f40d6782bd0
|
||||
2026-07-06-node-engine-floor.zh.md: c409b006baa3451eb5fc5260be4e14ffe88776c8
|
||||
|
||||
@@ -6,7 +6,7 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
根 `engines.node` 范围中的 Node 22 分支是对已安装工作区的契约,而不仅仅是 harness 源码直接调用的运行时 API 的契约。它不得低于工作区在该分支上安装的依赖包(package)所声明的 `engines.node`;否则 `pnpm install --engine-strict` 会在一个已宣传的 LTS 版本上失败,而非严格模式的安装则会在依赖所支持的运行时范围之外运行。
|
||||
根 `engines.node` 范围中的 Node 22 分支是对安装后工作区的契约,而不仅仅是 harness 源码直接调用的运行时 API 的契约。它不得低于工作区在该分支上安装的依赖包所声明的 `engines.node`;否则 `pnpm install --engine-strict` 会在一个已宣传的 LTS 版本上失败,而非严格模式下的安装结果则会在依赖所支持的运行时范围之外运行。
|
||||
|
||||
## 决策
|
||||
|
||||
@@ -19,21 +19,21 @@ Status: implemented
|
||||
|
||||
这些源码特性在 22.x 线上于 **22.18** 全部就绪,但已安装的 Pi 适配器依赖将宣传的 LTS 下限进一步提高。`@deepseek-ai/dsh-llm-pi-ai` 依赖 `@earendil-works/pi-ai@0.79.3`,后者的包声明 `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 分支,而没有任何部署应当使用它。
|
||||
|
||||
`@types/node` 继续固定在 22.x 线(`^22.20.0`),以匹配 LTS 支持线:使用 Node 23+/24+/25+ 的 API 会在所有机器和类型检查门禁中导致 `tsc` 失败,而不是编译通过、直到仅下限矩阵分支才能捕获的运行时错误才暴露。目前整个代码树在 Node 22 类型表面上类型检查全部通过,因此这一固定没有任何代价。
|
||||
`@types/node` 继续固定在 22.x 线(`^22.20.0`),以匹配 LTS 支持线:使用 Node 23+/24+/25+ 的 API 会在所有机器和类型检查门禁中导致 `tsc` 失败,而不是先编译通过,直到下限矩阵分支运行时才暴露错误。目前整个代码树针对 Node 22 类型接口的类型检查全部通过,因此固定该版本不产生任何代价。
|
||||
|
||||
## 后果
|
||||
|
||||
- 宣传的 LTS 分支不再低于 Pi 适配器依赖的下限。
|
||||
- CI 通过 Node 22.19 直接验证 Node 22 LTS 下限,将主要覆盖率任务保留在 `node: 24`,并用 Node 26 验证下一个偶数线;三个版本均运行聚焦的兼容性冒烟测试。
|
||||
- built-bin 冒烟测试无需版本条件标志:在 22.19 上类型剥离已是默认行为,因此测试保持其文档所述的纯 `node lib/bin.js` 路径。
|
||||
- 未来若依赖或源码 API 提高运行时下限,必须在同一变更中同步调整 `engines.node`、兼容性矩阵和本 Agent Note(agent 决策记录)。
|
||||
- 未来若依赖或源码 API 提高运行时下限,必须在同一变更中同步调整 `engines.node`、兼容性矩阵和本 Agent Note。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
- **保持 `^22.18.0 || >=24.0.0`。** 否决:它宣传的 LTS 版本低于 Pi 适配器依赖的下限。`@earendil-works/pi-ai@0.79.3` 要求 `>=22.19.0`。
|
||||
- **降级或固定 `@earendil-works/pi-ai` 以保留 22.18 的宣传范围。** 否决:当前 Pi 适配器依赖是预期工作区的一部分,且 22.19 仍在 Node 22 LTS 线内。
|
||||
- **下限 `>=22.13`(`node:sqlite` 边界)加上在 22.13–22.17 的 built-bin 冒烟测试中使用 `--experimental-strip-types`。** 否决:它为一个狭窄范围增加了版本条件测试标志,并将实验性标志依赖包装为正式支持。Pi 适配器依赖已经要求更高的 LTS 下限。
|
||||
- **开放式 `>=22.19`。** 否决:它宣传支持 Node 23.0–23.5,而在这些版本上 `node:sqlite`(直到 23.4)或类型剥离(直到 23.6)仍需标志。
|
||||
- **包含 Node 23.6+(`^22.19.0 || >=23.6.0`)。** 否决:23.6+ 确实能无标志运行两个源码特性,但 Node 23 已 end-of-life;宣传一条已终止的发布线会增加一个范围项和一条 CI 分支,而没有任何部署应当使用该运行时。
|
||||
- **使用无上限的 `>=22.19`。** 否决:它宣传支持 Node 23.0–23.5,而在这些版本上 `node:sqlite`(直到 23.4)或类型剥离(直到 23.6)仍需标志。
|
||||
- **包含 Node 23.6+(`^22.19.0 || >=23.6.0`)。** 否决:23.6+ 确实能无标志运行两个源码特性,但 Node 23 已结束生命周期(EOL);宣传一条已终止的发布线会增加一个范围项和一条 CI 分支,而没有任何部署应当使用该运行时。
|
||||
- **矩阵 `[22, 24, 26]` 而非固定 `22.19`。** 否决:浮动的主版本号条目会随时间上漂,悄然不再验证所声明的 LTS 下限。
|
||||
- **保持 `@types/node` 超前于运行时下限(`^25`)。** 否决:类型定义超前于运行时下限会让仅 Node 24/25 才有的 API 编译通过,仅在 22.x 上运行时才失败。将 `@types/node` 固定在 22.x 线上可将此类问题转化为所有环境下的编译错误。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md
|
||||
2026-07-06-parallel-pre-push-gates.md: 538e52c5318fb6d4eab2e8786513a08c1ff0ec55
|
||||
2026-07-06-parallel-pre-push-gates.zh.md: e93eec8757c20c8154703d9bdfd2f0c805e6a26c
|
||||
2026-07-06-parallel-pre-push-gates.zh.md: 0830e99484ebad40aa28ba6d2cfed1f09cfbee42
|
||||
|
||||
@@ -4,21 +4,21 @@ Status: implemented
|
||||
|
||||
[English](2026-07-06-parallel-pre-push-gates.md) | 中文
|
||||
|
||||
本记录中的本地 hook 部分已由[快速本地 Git hook](2026-07-22-fast-local-git-hooks.md) 取代。有界门禁调度器和包(package)级 `publint` 并行机制仍用于 CI、`doc-sync` 和显式本地命令。
|
||||
本记录中的本地钩子部分已由[快速本地 Git 钩子](2026-07-22-fast-local-git-hooks.md) 取代。有界门禁调度器和包级 `publint` 并行机制仍用于 CI、`doc-sync` 和显式本地命令。
|
||||
|
||||
## 问题
|
||||
|
||||
文档同步等聚合 job 隐藏了很长的串行链,其成员只读且相互独立。在工作流 YAML 中重复这些叶子清单,会使未来脚本变更有多个位置可以发生漂移;而串行运行包发布检查,会使一道门禁的耗时与包数量成正比。
|
||||
文档同步等聚合任务隐藏了很长的串行链,其中各项检查只读且相互独立。在工作流 YAML 中重复这些叶子清单,会使未来脚本变更有多个位置可以发生漂移;而串行运行包发布检查,会使一道门禁的耗时与包数量成正比。
|
||||
|
||||
## 决策
|
||||
|
||||
[scripts/run-gates.ts](../../../../scripts/run-gates.ts) 拥有 CI、`doc-sync` 和选择启用的 `check:all` 命令所使用的有界调度器。它将具名模式展开为叶子门禁,在启动子进程前拒绝空的或有歧义的依赖图,遵守产物依赖,缓冲可归因的输出,分别报告退出结果与信号结果,并在调用方需要不同 worker 上限时接受 `DSH_GATE_CONCURRENCY`。
|
||||
[scripts/run-gates.ts](../../../../scripts/run-gates.ts) 拥有 CI、`doc-sync` 和按需启用的 `check:all` 命令所使用的有界调度器。它将具名模式展开为叶子门禁,在启动子进程前拒绝空的或有歧义的依赖图,遵守产物依赖,缓冲可归因的输出,分别报告进程退出与信号终止结果,并在调用方需要不同 worker 上限时接受 `DSH_GATE_CONCURRENCY`。
|
||||
|
||||
Node 24 消费方 job 采用单个包含七道门禁的模式,而非由 shell 管理的进程池。其默认 worker 数等于门禁数,但门禁是否就绪由依赖关系控制:`publint` 先于已构建包不变式验证运行,快照回放、NodeNext 类型检查、built-bin 冒烟测试和 lint 则等待该验证完成。lint 之所以等待,是因为不变式验证器会临时暂存包视图,而 linter 不得遍历这些视图;源码兼容性检查可以与这条验证链重叠运行。
|
||||
Node 24 消费方任务采用单个包含七道门禁的模式,而非由 shell 管理的进程池。其默认 worker 数等于门禁数,但门禁是否就绪由依赖关系控制:`publint` 先于已构建包不变式验证运行,快照回放、NodeNext 类型检查、built-bin 冒烟测试和 lint 则等待该验证完成。lint 之所以等待,是因为不变式验证器会临时暂存包视图,而 linter 不得遍历这些视图;源码兼容性检查可以与这条验证链重叠运行。
|
||||
|
||||
[scripts/publint-all.ts](../../../../scripts/publint-all.ts) 从 `packages/<group>/<pkg>` 发现包,并以根据 `availableParallelism()` 确定大小的 worker 池运行 `publint`。`DSH_PUBLINT_CONCURRENCY` 可以针对资源配置不同的本地机器和 CI runner 限制或提高 worker 数量。结果按包缓冲,并按确定性的包顺序打印,因此并行执行不会打乱各包的日志块。
|
||||
|
||||
各门禁的包脚本仍是临时本地运行所用的词汇。`hygiene` 继续作为聚合 `&&` 链,而 `doc-sync` 在调度器中拥有其成员列表([通过门禁调度器运行 doc-sync](../../archived/process/2026-07-21-doc-sync-through-gate-scheduler.md))。
|
||||
各门禁的包脚本仍是临时本地运行所用的命令入口。`hygiene` 继续作为聚合 `&&` 链,而 `doc-sync` 的成员列表由调度器管理([通过门禁调度器运行 doc-sync](../../archived/process/2026-07-21-doc-sync-through-gate-scheduler.md))。
|
||||
|
||||
## 验证
|
||||
|
||||
@@ -30,12 +30,12 @@ Node 24 消费方 job 采用单个包含七道门禁的模式,而非由 shell
|
||||
- **每个叶子门禁声明一个 CI job**:暴露最大工作流并行度,但会重复 checkout、设置和安装开销,并在 YAML 中复制调度器清单。
|
||||
- **在 shell 脚本内后台运行子命令**:可以并行处理,但会失去各门禁计时、确定性的失败分组和直接的信号处理。
|
||||
- **每个包声明一个 `publint` job**:暴露最大包级并行度,但会创建手工维护的包清单,包发生变化时就会漂移。
|
||||
- **以无界并发运行 `publint`**:只有通过拿进程数、内存压力、包 tarball 创建和可读日志冒险,才能最大限度缩短小型仓库的耗时。
|
||||
- **以无界并发运行 `publint`**:虽能最大限度缩短小型仓库的耗时,却会拿进程数量、内存压力、包 tarball 创建开销和日志可读性冒险。
|
||||
|
||||
## 后果
|
||||
|
||||
由调度器支持的命令耗时取最慢依赖链,而非各独立门禁之和,并会报告主导耗时的门禁。无效图会直接失败,不会先执行其中一部分。代价是维护一个具有显式模式清单的定制调度器。
|
||||
由调度器支持的命令耗时取决于最慢的依赖链,而非各独立门禁耗时之和,并会报告决定总耗时的门禁。无效图会直接失败,不会先执行其中一部分。代价是维护一个具有显式模式清单的定制调度器。
|
||||
|
||||
这条验证链会让已恢复产物的下游消费方和 lint 延后启动,直至共享产物视图经确认有效且临时暂存已清除;这些下游门禁仍可彼此重叠运行。
|
||||
这条验证链会让使用已恢复产物的下游消费方和 lint 延后启动,直至共享产物视图经确认有效且临时暂存已清除;这些下游门禁仍可彼此重叠运行。
|
||||
|
||||
`publint-all.ts` 采用异步执行并缓冲命令输出,而不是实时继承 stdio。换来的是具有稳定输出顺序的包级并行,以及用于资源调节的单一环境变量。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-10-readme-known-limitations-gate.md
|
||||
2026-07-10-readme-known-limitations-gate.md: 2ca1168d795692730d17b6ab23dd113e8be277e5
|
||||
2026-07-10-readme-known-limitations-gate.zh.md: 4e42492f501cca1a45a90694acea4ca78e920780
|
||||
2026-07-10-readme-known-limitations-gate.zh.md: cabb368af5b3e35ffcb8713b2c96fdb767f6237d
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note: 每个包(package)README 中受门禁保护的 Known Limitations 章节
|
||||
# Agent Note: 每个包 README 中受门禁保护的「已知限制」章节
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,21 +6,21 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
[文档标准](../../../../docs/AGENTS.md)规定限制项归属包 README。没有共享形状时,缺少章节无法区分“经审计确认没有限制”与“忘记编写文档”,不同的标题还会妨碍全仓库搜索。
|
||||
[文档标准](../../../../docs/AGENTS.md)规定限制项归属包 README。没有统一结构时,章节缺失便无法区分“经审计确认没有限制”与“忘记编写文档”,不同的标题还会妨碍全仓库搜索。
|
||||
|
||||
## 决策
|
||||
|
||||
`packages/<group>/<pkg>/package.json` 下的每份包清单都有一个同级 README,其中包含规范的 `## Known Limitations and Deferred Work` 章节。其项目符号记录由该包拥有的持久消费方缺口和不明显的维护者约束;普通清理仍留在源码 TODO 或所属 Agent Note(agent 决策记录)中。[`verify-package-readme-limitations` 门禁](../../../../scripts/verify-package-readme-limitations.ts)从清单推导包集合,拒绝缺失 README,并要求恰好一个规范 h2 且至少包含一个顶层项目符号。“Limitations”“Deferred”“What is NOT here”或“Non-goals”等近似标题都会失败。
|
||||
每份位于 `packages/<group>/<pkg>/package.json` 的 manifest(元数据清单)都有一个同级 README,其中包含规范的 `## Known Limitations and Deferred Work` 章节。其中的项目符号记录由该包负责的长期消费方缺口和不明显的维护者约束;一般清理事项仍留在源码 TODO 或所属 Agent Note 中。[`verify-package-readme-limitations` 门禁](../../../../scripts/verify-package-readme-limitations.ts)从 manifest 推导包集合,拒绝缺失 README,并要求恰好一个规范的 H2 标题,且至少包含一个顶层项目符号。“Limitations”“Deferred”“What is NOT here”或“Non-goals”等近似标题都会失败。
|
||||
|
||||
如果一个包确实没有需要声明的限制事项,则将其列入 `NO_LIMITATIONS` 并省略该章节。新增限制事项时须移除该条目;重命名或移除条目会失败,因为每个条目都必须对应一个被扫描的包。
|
||||
如果一个包确实没有需要声明的限制事项,则将其列入 `NO_LIMITATIONS` 并省略该章节。新增限制事项时须移除该条目;包重命名或移除后,陈旧条目会使门禁失败,因为每个条目都必须对应一个被扫描的包。
|
||||
|
||||
门禁检查存在性、形状和允许列表。按照文档与[正文](../../../skills/dsh-prose-standard/SKILL.md)标准进行的评审负责覆盖面和准确性。常设规则位于 [packages/AGENTS.md](../../../../packages/AGENTS.md)。
|
||||
门禁检查存在性、形状和允许列表。评审依据文档与[行文](../../../skills/dsh-prose-standard/SKILL.md)标准检查覆盖面和准确性。常设规则位于 [packages/AGENTS.md](../../../../packages/AGENTS.md)。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
- **自由格式标题**:无法统一搜索,仍需近似标题检测。
|
||||
- **要求空章节或写 "None."**:样板文字可能在包新增限制事项后仍然残留;白名单使「确无限制」这一状态显式且可评审。
|
||||
- **设置字数上限**:合理的限制事项数量因包而异,因此由评审管控这一不设预算的 README 层级。
|
||||
- **要求空章节或写 "None."**:样板文字可能在包新增限制事项后仍然残留;允许列表使「确实没有限制事项」这一状态显式且可评审。
|
||||
- **设置词数上限**:合理的限制事项数量因包而异,因此由评审管控这一不设词数预算的 README 层级。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-12-package-model-experience-contract.md
|
||||
2026-07-12-package-model-experience-contract.md: 92a8e5a1a81d00dae085e4af89456896373058e6
|
||||
2026-07-12-package-model-experience-contract.zh.md: 54b181738b8276c634f777ad3424191c8652baec
|
||||
2026-07-12-package-model-experience-contract.zh.md: 34dd909adc6a3a5f4c9113df675d31012b836370
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note: 包(package)的模型体验契约
|
||||
# Agent Note: 包的模型体验契约
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,17 +6,17 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
包 README 可以解释 API 和运行时机制,却不回答主导 agent harness(智能体框架)行为与成本的问题:该包的哪些内容会进入模型请求、在什么条件下进入、这些 token 会保留多久,以及后续请求是否会保留可复用的 KV cache 前缀。在插件架构中,这种遗漏尤其难以审计。消费方可能把后端结果转为工具消息,策略插件可能以错误取代成功结果,压缩可能移除旧历史,而 agent 范围的注册可能改变某个 agent 的提示词或 schema,却不影响其他 agent。因此,只阅读名义上面向模型的包会遗漏真实的上下文效应,而在每次常规评审中跨所有依赖阅读源码又成本过高。
|
||||
包 README 可以解释 API 和运行时机制,却不回答主导 agent harness(智能体框架)行为与成本的问题:该包的哪些内容会进入模型请求、在什么条件下进入、这些 token 会保留多久,以及后续请求是否会保留可复用的 KV Cache 前缀。在插件架构中,这种遗漏尤其难以审计。消费方可能把后端结果转为工具消息,策略插件可能以错误取代成功结果,压缩可能移除旧历史,而 agent 范围的注册可能改变某个 agent 的提示词或 schema,却不影响其他 agent。因此,只阅读名义上面向模型的包会遗漏真实的上下文效应,而在每次常规评审中跨所有依赖阅读源码又成本过高。
|
||||
|
||||
## 决策
|
||||
|
||||
每个具有面向模型或邻近模型契约的 workspace 包 README 都以规范的[模型体验章节](../../../../docs/cookbook/adding-a-package.md#4-write-the-package-readme)收尾,位置紧邻 `## Known Limitations and Deferred Work` 之前;位于“无限制项”允许列表中的包则以模型体验本身结尾。经审计确认与模型无关的通用包通过 `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` 省略该章节。
|
||||
|
||||
具有直接、条件式、有上限、全生命周期、多表面或辅助模型效应的包,为每个上下文表面使用一个 H3。每个表面包含三个有序 H4 字段——`What the model sees`、`Token effect` 和 `KV Cache effect`——每个字段都以一个正文段落开头。cache 字段区分仅追加增长、稳定重复前缀、替换先前 token,以及独立模型请求;它点明由包拥有、且能在新内容追加前改变请求的每项配置、范围、生命周期、压缩或路由变化。“Does not invalidate”表示该包保留一个已经可复用的前缀,并非承诺提供方一定命中 cache 或保留某段时间。由包拥有的稳定文本按原文精确引用:系统提示词正文和其他长字面量在引入它们的字段下使用带标题的 H5 加 `markdown` 围栏,通常位于 `What the model sees`;短字面量则以内联形式保留,并点名插值占位符。工具 schema 表面链接生成式[工具目录](../../../../docs/tool-catalog.md)中带锚点的章节,并且只陈述组合或配置增量;仅运行时定义解释目录为何省略它们。依赖数据和由提供方拥有的文本采用摘要。agent 范围的可见性须显式说明;当范围可隐藏提示词与 schema 中的一者而不影响另一者时,两种表面保持分离。
|
||||
具有直接、条件式、有上限、生命周期、多表面或辅助模型效应的包,为每个上下文表面使用一个 H3。每个表面包含三个有序 H4 字段——`What the model sees`、`Token effect` 和 `KV Cache effect`——每个字段都以一个正文段落开头。缓存字段区分仅追加增长、稳定重复前缀、替换先前 token,以及独立模型请求;它点明由包拥有、且能在新内容追加前改变请求的每项配置、作用域、生命周期、压缩或路由变化。“Does not invalidate”表示该包保留一个已经可复用的前缀,并非承诺提供方一定能命中缓存或将其保留特定时长。由包拥有的稳定文本按原文精确引用:系统提示词正文和其他长字面量在引入它们的字段下使用带标题的 H5 加 `markdown` 围栏,通常位于 `What the model sees`;短字面量则以内联形式保留,并点名插值占位符。工具 schema 表面链接生成的[工具目录](../../../../docs/tool-catalog.md)中带锚点的章节,并且只陈述组合或配置增量;仅运行时定义解释目录为何省略它们。依赖数据和由提供方拥有的文本采用摘要。agent 范围的可见性须显式说明;当范围可隐藏提示词与 schema 中的一者而不影响另一者时,两种表面保持分离。
|
||||
|
||||
没有模型上下文效应的包,或某条路径完全由另一个包渲染的包,使用验证器审计过的短格式:一句以 `None, as ` 或 `Indirectly, through ` 开头的句子,随后是一个 `KV Cache effect` H4 和一个正文段落。纯传输包和无密钥测试支持包若不创建任何进入模型的内容,就使用 none 格式。提供方后端即使会限制或过滤数据也使用 indirect 格式;具名子项拥有全部效应时,接线 bundle 也使用该格式。这些章节定位贡献并声明不会直接使 cache 失效,同时不重复陈述消费方。结构化章节同样只记录由包拥有的输入、变换和增量。
|
||||
没有模型上下文效应的包,或某条路径完全由另一个包渲染的包,使用验证器审计过的短格式:一句以 `None, as ` 或 `Indirectly, through ` 开头的句子,随后是一个 `KV Cache effect` H4 和一个正文段落。纯传输包和无密钥测试支持包若不创建任何进入模型的内容,就使用 none 格式。提供方后端即使会限制或过滤数据,也使用间接格式;具名子项拥有全部效应时,接线 bundle 也使用该格式。这些章节会指出贡献所在,并声明不会直接导致 KV Cache 失效,同时不重复陈述消费方。结构化章节同样只记录由包拥有的输入、变换和增量。
|
||||
|
||||
`verify-package-readme-model-experience` 发现包清单,并验证三种分类、规范末尾章节顺序、确切字段标题深度与顺序、非空字段段落、逐字块的 H5 归属、具体字面量证据,以及带锚点的工具目录链接。它在 `doc-sync` 和并行门禁 runner 中运行。评审仍负责覆盖面、链接相关性和事实准确性。
|
||||
`verify-package-readme-model-experience` 发现各包的 manifest(元数据清单),并验证三种分类、规范末尾章节顺序、确切字段标题深度与顺序、非空字段段落、逐字块的 H5 归属、具体字面量证据,以及带锚点的工具目录链接。它在 `doc-sync` 和并行门禁 runner 中运行。评审仍负责覆盖面、链接相关性和事实准确性。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
@@ -30,4 +30,4 @@ Status: implemented
|
||||
|
||||
## 后果
|
||||
|
||||
评审者可以从任何面向模型或邻近模型的包开始,看到它对对话模型、子模型和辅助调用的贡献,无需重建完整插件图。token 预算工作可以区分重复请求开销和依赖数据的历史,而 cache 敏感工作可以识别仅追加路径,以及最早由包引起的前缀变更。agent 范围变更有明确的文档检查点。每当模型可见行为发生变化时,包作者都要维护一个或多个紧凑的上下文表面块,或一种已分类的短格式;经审计的通用包不携带无关的模型样板。结构化字段不承诺由提供方给出的精确 token 数或 cache 命中;测量仍取决于具体模型、提供方和工作负载,而所记录的增长、可见性和前缀稳定性契约保持稳定。
|
||||
评审者可以从任何面向模型或邻近模型的包开始,看到它对对话模型、子模型和辅助调用的贡献,无需重建完整插件图。token 预算工作可以区分重复请求开销和依赖数据的历史,而 cache 敏感工作可以识别仅追加路径,以及最早由包引起请求前缀变化的位置。agent 范围变更有明确的文档检查点。每当模型可见行为发生变化时,包作者都要维护一个或多个紧凑的上下文表面块,或一种已分类的短格式;经审计的通用包不携带无关的模型样板。结构化字段不承诺由提供方给出的精确 token 数或 cache 命中;测量仍取决于具体模型、提供方和工作负载,而所记录的增长、可见性和前缀稳定性契约保持稳定。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-13-documentation-site-projection.md
|
||||
2026-07-13-documentation-site-projection.md: 2452c9dfa53e05061446df2fe650f3b4d6428c01
|
||||
2026-07-13-documentation-site-projection.zh.md: 9df230ea8adeb8744387a5f7efdf288d6a1f6eaa
|
||||
2026-07-13-documentation-site-projection.zh.md: 6f1c79ac502a04714cd77f680108dbff035b048c
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note: 将规范文档投影到网站
|
||||
# Agent Note: 将权威文档投影到网站
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,31 +6,31 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
仓库需要一个可导航的文档网站,但不能让网站目录成为第二个文档源。把包(package)指南、架构页面或生成目录复制到网站专用目录树,会使两份副本发生漂移;让 VitePress 直接指向仓库根目录,又会把公开 URL 和导航与内部文件布局耦合。仓库相对链接在网站上也需要指向不同位置:已发布页面应留在站内,源文件和未发布的贡献者文档则应指向 GitHub。
|
||||
仓库需要一个可导航的文档网站,但不能让网站目录成为第二个文档源。把包指南、架构页面或生成目录复制到网站专用目录树,会使两份副本发生漂移;让 VitePress 直接指向仓库根目录,又会把公开 URL 和导航与内部文件布局耦合。仓库相对链接在网站上也需要指向不同位置:已发布页面应留在站内,源文件和未发布的贡献者文档则应指向 GitHub。
|
||||
|
||||
## 决策
|
||||
|
||||
规范 Markdown 保留在拥有它的仓库层级中。面向产品的指南位于 `docs/user/`,生成的参考资料保留在现有生成目录中,架构页面和实操手册(cookbook)页面也保留在现有的 `docs/` 路径。
|
||||
权威 Markdown 保留在其所属的仓库层级中。面向产品的指南位于 `docs/user/`,生成的参考资料保留在现有生成目录中,架构页面和实操手册(cookbook)页面也保留在现有的 `docs/` 路径。
|
||||
|
||||
`website/docs.ts` 是一份显式的发布 manifest(元数据清单)。每个条目将一个规范源文件映射到稳定的公开路由、侧边栏、分区和顺序。因此,新增或移除已发布页面是一项可评审的 manifest 变更,而不是隐式目录扫描的结果。
|
||||
`website/docs.ts` 是一份显式的发布 manifest(元数据清单)。每个条目将一个权威源文件映射到稳定的公开路由、侧边栏、分区和顺序。因此,新增或移除已发布页面是一项可评审的 manifest 变更,而不是隐式目录扫描的结果。
|
||||
|
||||
在 VitePress 启动或构建之前,`scripts/project-doc-site.ts` 会把 manifest 投影到被忽略的 `website/.generated/` 目录。生成目录树遵循公开路由,使 VitePress 导航、locale 检测和本地搜索使用同一套路由词汇。每个页面都会获得一个指向其规范仓库文件的 `editSource` frontmatter 字段;编辑链接回调只读取该页面的数据,因此公开 URL 与源文件布局彼此独立。
|
||||
在 VitePress 启动或构建之前,`scripts/project-doc-site.ts` 会把 manifest 投影到被忽略的 `website/.generated/` 目录。生成目录树遵循公开路由,使 VitePress 导航、locale 检测和本地搜索使用同一套路由命名。每个页面都会获得一个指向其权威仓库文件的 `editSource` frontmatter 字段;编辑链接回调只读取该页面的数据,因此公开 URL 与源文件布局彼此独立。
|
||||
|
||||
各 locale 的首页投影只保留规范 YAML frontmatter。面向仓库的正文可以保留其 H1 和双语源文件链接,而 VitePress 首页主题负责渲染 hero 与功能区,网站导航负责切换 locale。
|
||||
各 locale 的首页投影只保留权威 YAML frontmatter。面向仓库的正文可以保留其 H1 和双语源文件链接,而 VitePress 首页主题负责渲染 hero 与功能区,网站导航负责切换 locale。
|
||||
|
||||
投影器解析 Markdown 链接,但不会重新序列化文档。指向另一个已发布源文件的链接会变成站内相对路由;指向未发布仓库文件的链接会变成 GitHub 源文件链接;仓库图片会变成 GitHub raw URL。相对目标不存在时,投影快速失败。单元测试固定这些转换,`docs:check` 则运行投影器测试和 VitePress 生产构建,并将二者纳入 `doc-sync` 和并行文档门禁。
|
||||
投影器解析 Markdown 链接,但不会重新序列化文档。指向另一个已发布源文件的链接会变成站内相对路由;指向未发布仓库文件的链接会变成 GitHub 源文件链接;仓库图片会变成 GitHub raw URL。相对目标不存在时,投影会失败。单元测试会锁定这些转换行为,`docs:check` 则运行投影器测试和 VitePress 生产构建,并将二者纳入 `doc-sync` 和并行文档门禁。
|
||||
|
||||
`website/AGENTS.md` 是网站子树中唯一维护的 Markdown 文件。投影器测试会枚举已跟踪且未被忽略的文件,并拒绝网站中的任何其他 Markdown,因此网站专用的 locale、路由、API 或生成源文件副本无法绕过发布 manifest。
|
||||
`website/AGENTS.md` 是网站子树中唯一维护的 Markdown 文件。投影器测试会枚举所有已跟踪文件和未被忽略的未跟踪文件,并拒绝网站中的任何其他 Markdown,因此网站专用的 locale、路由、API 或生成源文件副本无法绕过发布 manifest。
|
||||
|
||||
Mermaid 渲染规范图表。网站工作区显式声明 `vitepress-plugin-mermaid` 要求 Vite 预打包的 5 个包,因为 pnpm 的严格依赖隔离会使本地开发服务器无法使用这些传递依赖;Knip 将这种仅运行时使用记录为有意的依赖例外。
|
||||
Mermaid 渲染权威图表。网站工作区显式声明 `vitepress-plugin-mermaid` 要求 Vite 预打包的 5 个包,因为 pnpm 的严格依赖隔离会使本地开发服务器无法使用这些传递依赖;Knip 将这种仅运行时使用记录为有意的依赖例外。
|
||||
|
||||
网站发布与网站构建保持分离。专用 GitHub Actions 工作流运行现有文档门禁,将 `website/.dist` 作为 Pages 产物上传,并只在构建成功后部署。`actions/configure-pages` 在构建时向 VitePress 提供目标位置的 base path,因此私有 Pages 源站、未来的公开项目路径和自定义域名不需要各自的检入配置。Pages 可见性仍是仓库托管设置,而不是工作流权限。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**在 `website/` 下提交复制的 Markdown。** 这种方式让 VitePress 设置更直接,但每份复制的指南或 API 表格都会多出一个所有者,并且需要一套无法识别权威副本的同步约定。
|
||||
**在 `website/` 下提交复制的 Markdown。** 这种方式让 VitePress 设置更直接,但每份复制的指南或 API 表格都会多出一个归属方,并且需要一套无法识别权威副本的同步约定。
|
||||
|
||||
**让 `website/` 成为每个已发布页面的规范归属。** 这种方式仍只有一份副本,却只是为了满足渲染器,就把架构、生成的参考资料和面向贡献者的材料移出了各自的仓库归属层级。
|
||||
**让 `website/` 成为每个已发布页面的权威归属。** 这种方式仍只有一份副本,却只是为了满足渲染器,就把架构、生成的参考资料和面向贡献者的材料移出了各自的仓库归属层级。
|
||||
|
||||
**自动发现所有 Markdown 文件。** 这种方式最大限度减少 manifest 维护,却会意外发布内部文档、把源文件移动暴露为 URL 变更,并根据偶然的目录顺序生成导航。
|
||||
|
||||
@@ -42,6 +42,6 @@ Mermaid 渲染规范图表。网站工作区显式声明 `vitepress-plugin-merma
|
||||
|
||||
## 后果
|
||||
|
||||
文档事实只有一个可编辑归属,公开路由在源文件移动后仍保持稳定,网站也能纳入生成的参考资料而无需提交另一份生成副本。本地开发会监视规范输入并重新生成一次性投影。布局门禁会把陈旧的网站专用 Markdown 目录树变成合并失败,而不是被忽略的构建输入。影响文档网站的合并会把检查过的结果部署到 Pages,手动触发则提供恢复与验证入口。
|
||||
文档事实只有一个可编辑归属,公开路由在源文件移动后仍保持稳定,网站也能纳入生成的参考资料而无需提交另一份生成副本。本地开发会监视权威输入并重新生成一次性投影。布局门禁会把陈旧的网站专用 Markdown 目录树变成合并失败,而不是被忽略的构建输入。影响文档网站的合并会把检查过的结果部署到 Pages,手动触发则提供恢复和验证的入口。
|
||||
|
||||
发布 manifest 是一份需要维护的 allowlist,链接投影也引入了一层仓库专用的构建适配器。新增一种 Markdown 链接行为时,需要增加投影器测试。Mermaid 支持也会增大客户端 bundle,但能保留规范文档中已经使用的图表。
|
||||
发布 manifest 是一份需要维护的 allowlist,链接投影也引入了一层仓库专用的构建适配器。新增一种 Markdown 链接行为时,需要增加投影器测试。Mermaid 支持也会增大客户端 bundle,但能保留权威文档中已经使用的图表。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-14-typescript-program-backed-semantic-gates.md
|
||||
2026-07-14-typescript-program-backed-semantic-gates.md: 91639d53b660c68ae52c7ddcef6b3f594e82273e
|
||||
2026-07-14-typescript-program-backed-semantic-gates.zh.md: 2270408564f0fc90241255dfb86a65c30362d651
|
||||
2026-07-14-typescript-program-backed-semantic-gates.zh.md: 65e19ba2bd34c2411a2e5ca64dfc85f9bf18cfb3
|
||||
|
||||
@@ -10,17 +10,17 @@ Status: implemented
|
||||
|
||||
当前的门禁基于 TypeScript 单文件语法解析能力,使用命名约定、手写的表格、JSDoc 等方式来维护这类信息。
|
||||
|
||||
仓库需要一个语义真源,同时不能引入运行时包(package)之间的循环依赖、宽泛的兜底启发式逻辑,或重复描述 TypeScript 已有信息的机器可读标注。
|
||||
仓库需要一个语义真源,同时不能引入运行时包之间的循环依赖、宽泛的兜底启发式逻辑,或重复描述 TypeScript 已有信息的机器可读标注。
|
||||
|
||||
## 决策
|
||||
|
||||
仓库可以通过项目级类型信息 `ts.Program` 进行跨文件项目类型联合计算,并通过 `TypeChecker` 来提取 **强类型** 信息,用以缓解原有命名约定、手写表格、JSDoc 标注等形式。
|
||||
仓库门禁可以通过 `ts.Program` 汇集项目级类型信息,并使用 `TypeChecker` 提取**强类型**事实,从而减少对命名约定、手写表格和 JSDoc 元数据的依赖。
|
||||
|
||||
当前已完成 A / B 两个门禁的语义化改造。
|
||||
仓库将这一模型应用于以下两个门禁。
|
||||
|
||||
### 一个项目模型展开根项目配置
|
||||
|
||||
[`TypeScriptProject`](../../../../scripts/ts-project.ts) 解析根 `tsconfig.json`,递归展开每个项目引用,并将各引用项目的源码根合并为一个不输出文件的语义 Program。直接从根项目配置创建普通 Program 时,TypeScript 可能将引用项目重定向到构建后的声明文件;显式展开可以让门禁继续遍历各包的 `src` 文件,并使用真实符号标识。
|
||||
[`TypeScriptProject`](../../../../scripts/ts-project.ts) 解析根 `tsconfig.json`,递归展开每个项目引用,并将各引用项目的源码根合并为一个不输出文件的语义 Program。直接从根项目配置创建普通 Program 时,TypeScript 可能将引用项目重定向到构建后的声明文件;显式展开可以让门禁继续遍历各包的 `src` 文件,并保留符号同一性。
|
||||
|
||||
该封装统一负责配置诊断、语义编译选项、仓库相对路径、源码查找和共享 TypeChecker。各门禁不再自行按文件通配模式扫描包源码,也不再分别构建不完整的 Program。
|
||||
|
||||
@@ -28,19 +28,19 @@ Status: implemented
|
||||
|
||||
[`gen-doc-graphs`](../../../../scripts/gen-doc-graphs.ts) 根据调用接收者与仓库中真实 `Context`、`AgentEventDispatch` 和 Cordis `EventsService` 类型之间的可赋值关系进行分类。变量名和属性拼写不再决定某次调用是否属于事件操作。
|
||||
|
||||
Context 与 AgentEventDispatch 调用只贡献有限的字符串字面量事件集合。对于直接调用 `EventsService.dispatch()` 的路径,生成器会沿数组字面量、常量别名、条件分支和未导出本地辅助函数的已解析调用点恢复事件槽位。泛型转发参数不算作具体生产方:事件仍归属于传入封闭事件值的调用点。
|
||||
Context 与 AgentEventDispatch 调用只贡献由字符串字面量构成的有限事件集合。对于直接调用 `EventsService.dispatch()` 的路径,生成器会沿数组字面量、常量别名、条件分支和未导出本地辅助函数的已解析调用点恢复事件槽位。泛型转发参数不算作具体生产方:事件仍归属于传入封闭事件值的调用点。
|
||||
|
||||
语义查询只在存在消费分支的位置运行:调用先经过封闭的事件 API 方法名集合预过滤,再做接收者分类;辅助函数调用点索引按需构建,而不是预先对全部包源码的每个调用求解签名。需求式索引对每个辅助函数逐一证明局部性——未导出、位于真正的 ES 模块文件中、且同文件所有引用都是直接调用位的辅助函数,按模块作用域规则其全部调用必在本文件内,此时只索引该文件。任一前提无法证明(带导出修饰符、位于全局 script 文件、存在别名化或无法归类的引用)即回退到原全部包源码索引,回退路径就是原语义本身:证明只影响开销,不影响结果。惰性单一全局索引方案被否决,因为当前源码树确实会走到辅助函数参数路径,该方案仍需支付几乎全额的 `getResolvedSignature` 扫描成本。
|
||||
|
||||
每个已声明的 harness 事件都必须存在扫描得到的生产方。找不到生产方时,生成过程会将其视为无调用方的事件词汇或尚不支持的语义 dispatch 形态并明确失败;没有监听方的扩展点仍然合法。`internal/dispatch` 插桩不会被当作它所观察的每个事件的订阅,因此关系矩阵只记录直接的产品监听方,不再手工补充间接关系。
|
||||
每个已声明的 harness 事件都必须存在扫描得到的生产方。找不到生产方时,生成过程会将其视为没有生产方的事件词汇或尚不支持的语义 dispatch 形态,并明确失败;没有监听方的扩展点仍然合法。`internal/dispatch` 插桩不会被当作它所观察的每个事件的订阅,因此关系矩阵只记录直接的产品监听方,不再手工补充间接关系。
|
||||
|
||||
### B. 带作用域的事件路由生成一份强类型解析函数表
|
||||
|
||||
[`gen-scoped-events`](../../../../scripts/gen-scoped-events.ts) 扫描真实的 `scopeTarget(base, key)` 调用,为每种 scoped 基础对象确定路由键类型。随后,它查找带有 `this: Scoped<Base>` 的 Cordis `Events` 成员,并在每个事件参数及其一层公开属性中搜索类型;移除 `null` 和 `undefined` 后,候选类型必须与路由键类型完全相同。
|
||||
[`gen-scoped-events`](../../../../scripts/gen-scoped-events.ts) 扫描真实的 `scopeTarget(base, key)` 调用,为每种 scoped 基础对象确定路由键类型。随后,它查找带有 `this: Scoped<Base>` 的 Cordis `Events` 成员,并在每个事件参数及其第一层公开属性中搜索与该键匹配的类型;移除 `null` 和 `undefined` 后,候选类型必须与路由键类型完全相同。
|
||||
|
||||
恰好一个匹配项会生成解析函数。存在多个匹配项时,含义不明确,生成器会失败。没有匹配项时,事件必须标记 `@dshScopeScan unsupported`;该标记只用于路由键有意留在事件参数之外的情况,例如按所属 agent(智能体)路由的会话事件和按父 agent 路由的 subagent 生命周期事件。此标记只表示扫描不受支持,不编码事件名、参数下标、属性路径或替代类型。
|
||||
|
||||
仓库提交的 [`scoped-events.generated.ts`](../../../../packages/core/scope/src/scoped-events.generated.ts) 是位于 scoped dispatch 所属包中的纯运行时映射,不导入任何事件声明方包。语义完整性由生成器自身保证:根 Program 枚举所有 scoped `Events` 声明与真实 `scopeTarget` 契约,通过 checker 解析唯一的 payload 路径,并在渲染 `unknown[]` 运行时边界前拒绝缺失、陈旧或含义不明确的条目。
|
||||
提交到仓库的 [`scoped-events.generated.ts`](../../../../packages/core/scope/src/scoped-events.generated.ts) 是位于 scoped dispatch 所属包中的纯运行时映射,不导入任何事件声明方包。语义完整性由生成器自身保证:根 Program 枚举所有 scoped `Events` 声明与真实 `scopeTarget` 契约,通过 checker 解析唯一的 payload 路径,并在渲染 `unknown[]` 运行时边界前拒绝缺失、陈旧或含义不明确的条目。
|
||||
|
||||
`dsh-scope/invariant` companion 消费这份映射,不再维护手写事件表。Program 分析发生在仓库门禁内,而不是依赖生成的类型导入,因此 `dsh-scope` 和 `dsh-invariants` 都不需要依赖所有事件声明方。
|
||||
|
||||
@@ -50,7 +50,7 @@ Context 与 AgentEventDispatch 调用只贡献有限的字符串字面量事件
|
||||
|
||||
## 验证
|
||||
|
||||
`verify-doc-graphs` 对语义生产方/监听方扫描执行新鲜度检查;`verify-scoped-events` 会重新运行 Program 分析,并检查生成映射的新鲜度。根 TypeScript 构建编译其运行时适配器;workspace 约束与运行时依赖闭包检查确保事件声明方聚合不会进入部署依赖。
|
||||
`verify-doc-graphs` 对语义生产方/监听方扫描执行新鲜度检查;`verify-scoped-events` 会重新运行 Program 分析,并检查生成映射的新鲜度。根 TypeScript 构建会编译该运行时适配器;workspace 约束与运行时依赖闭包检查确保事件声明方聚合不会进入部署依赖。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.md
|
||||
2026-07-19-remove-generated-agent-note-index.md: ee85ec0757d5924f5784c43a50003eb96e0a9531
|
||||
2026-07-19-remove-generated-agent-note-index.zh.md: 23e6d3b0b9aaaa02f53e72789f409c0050112193
|
||||
2026-07-19-remove-generated-agent-note-index.zh.md: 7ace1c4f03d5ff3d1137fe611a5ec0ec2ae21b2f
|
||||
|
||||
@@ -12,7 +12,7 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
生命周期/类别文件系统目录树就是 Agent Note 清单。[README.md](../../README.md) 继续作为人工维护的入口和契约,普通的目录树浏览与仓库搜索负责内容发现。
|
||||
按生命周期/类别组织的文件系统目录树就是 Agent Note 清单。[README.md](../../README.md) 继续作为人工维护的入口和契约,普通的目录树浏览与仓库搜索负责内容发现。
|
||||
|
||||
`scripts/agent-note-tree.ts` 持有封闭的生命周期/类别集合与结构遍历器。`verify-agent-note-classification` 校验该目录树,并拒绝旧目录和根目录中的 `INDEX.md`,但不会渲染集中式清单或检查其新鲜度。
|
||||
|
||||
@@ -22,10 +22,10 @@ Status: implemented
|
||||
|
||||
**提供不提交到仓库的按需索引命令。** 这可以避免已提交文件的冲突,但仍需维护渲染器和命令,而目录树浏览与仓库搜索已经覆盖该发现路径。
|
||||
|
||||
**恢复人工维护的索引。** 它具有相同的共享文件争用问题,还会重新引入生成机制已经避免的完整性和排序错误。
|
||||
**恢复人工维护的索引。** 它同样会造成共享文件争用,还会重新引入生成机制已经避免的完整性和排序错误。
|
||||
|
||||
## 影响
|
||||
|
||||
- 添加、移动或重命名 Agent Note 时,不再改动覆盖整个语料库的生成文件。
|
||||
- 分类门禁执行的工作更少,文档门禁拓扑也不会增加进程或阶段。
|
||||
- 分类门禁执行的工作更少,文档门禁拓扑也无需增加进程或阶段。
|
||||
- 读者不再获得单一的时间顺序页面,改用生命周期/类别目录树或仓库搜索。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-19-require-agent-notes-for-non-trivial-changes.md
|
||||
2026-07-19-require-agent-notes-for-non-trivial-changes.md: 162ae61affb4c1b0ad526fa0da41f84ebbb02089
|
||||
2026-07-19-require-agent-notes-for-non-trivial-changes.zh.md: cd015ba62f1f2b1e9e5e6c36d1cde5bd35cba84c
|
||||
2026-07-19-require-agent-notes-for-non-trivial-changes.zh.md: 1578312ba43bfc79fe50841aaf651da6f0771fb9
|
||||
|
||||
@@ -10,11 +10,11 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
每项实质性变更都在同一个 PR 中新增或更新至少一份 Agent Note。实质性变更包括行为、架构、跨文件或跨包契约、流程或工具、测试策略、磁盘格式、线协议或配置格式,以及维护者可能合理重审的其他决策。
|
||||
每项实质性变更都在同一个 PR(Pull Request)中新增或更新至少一份 Agent Note。实质性变更包括行为、架构、跨文件或跨包契约、流程或工具、测试策略、磁盘格式、协议格式(wire format)或配置格式,以及维护者可能合理重审的其他决策。
|
||||
|
||||
更新已经持有该决策的 Agent Note 即满足规则;仅当没有 Agent Note 持有该决策时才新增记录。完全机械或局部、且不改变行为、契约、结构、流程或决策依据的编辑可豁免。[Agent Notes README](../../README.md#when-to-write-one) 持有这条边界,根目录 `AGENTS.md` 则携带常驻指令。
|
||||
|
||||
只有在当前持有该决策的记录保存了所有独有的决策依据、备选方案、影响、验证契约和明确指出的覆盖缺口后,才可将被完全取代的 implemented Agent Note 合并到该记录中并删除。同一变更还要修复入站链接,并删除中文对侧文件和一致性记录。仅部分被取代时,两个记录仍需互相链接并保持与现状一致;合并既不将旧决策改写成与其相反的决策,也不让 git 历史成为决策依据的唯一副本。
|
||||
只有在当前持有该决策的记录保存了所有独有的决策依据、备选方案、影响、验证契约和明确指出的覆盖缺口后,才可将被完全取代的 implemented Agent Note 合并到该记录中并删除。同一变更还要修复入站链接,并删除中文对侧文件和伴随记录。仅部分被取代时,两个记录仍需互相链接并保持与现状一致;合并既不将旧决策改写成与其相反的决策,也不让 git 历史成为决策依据的唯一副本。
|
||||
|
||||
后续决策完全移除较早的功能时,只有该功能已从生产代码、配置、schema、持久化格式或协议格式、迁移和兼容行为中消失,当前文档不再将其描述为可用,且没有测试把它作为受支持行为来执行,移除记录才会成为当前持有记录。移除决策的依据和验证该功能已不存在的测试可以保留。它必须保留该功能的最初动机、为什么该动机已不足以证明继续保留该功能、完全移除之外的备选方案、放弃的能力、重新引入的条件,以及证明已彻底移除的验证。只描述已删除行为的实现清单和测试已经过时,不属于当前验证契约。仅移除一种传输、默认值、实现或展示仍属于部分取代。
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-19-web-styling-system.md
|
||||
2026-07-19-web-styling-system.md: b4d647924ab6ab172cd7a7e2531a10a2a7e62981
|
||||
2026-07-19-web-styling-system.zh.md: 01064d4d52b3ed2b179a4795f5113b94480945bd
|
||||
2026-07-19-web-styling-system.zh.md: a22443aef8a4b72404b55bfce9cee60feb0b0364
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# RFC: Web 样式体系——token 框架与工程约束
|
||||
# Agent Note: Web 样式体系——token 框架与工程约束
|
||||
|
||||
Status: implemented
|
||||
|
||||
> token 体系更新(2026-07-22):本文框架裁决(CSS Modules + clsx、无组件库、无 tailwind、组件只用 token)仍然生效,但两层 `--bg-*`/`--text-*` token 表及其宿主 `web-ui/src/style/global.css` 已被 `packages/client/ui-theme/src/styles/` 的 `--dsw-*` static+alias 双层表取代(暗色=`body[data-ds-dark-theme]` 覆写)——样式表本身即 token 权威。
|
||||
> token 体系更新(2026-07-22):本文框架裁决(CSS Modules + clsx、无组件库、无 tailwind、颜色只用 token)仍然生效,但两层 `--bg-*`/`--text-*` token 表及其宿主 `web-ui/src/style/global.css` 已被 `packages/client/ui-theme/src/styles/` 的 `--dsw-*` static+alias 双层表取代(暗色=`body[data-ds-dark-theme]` 覆写)——样式表本身即 token 权威。
|
||||
|
||||
[English](2026-07-19-web-styling-system.md) | 中文
|
||||
|
||||
@@ -10,16 +10,16 @@ Status: implemented
|
||||
|
||||
## Problem
|
||||
|
||||
GUI 无设计师供给,样式由 agent 编写并 review;没有一套机器可对照的 token 体系与编码规范,颜色/圆角/动效会在组件间字面量漂移,暗色主题会长成组件内散落的条件分支。
|
||||
GUI 无设计师供给,样式由 agent 编写并 review;没有一套机器可检查的 token 体系与编码规范,颜色/圆角/动效会在组件间字面量漂移,暗色主题会长成组件内散落的条件分支。
|
||||
|
||||
## Decision(框架五条)
|
||||
|
||||
| # | 决策 | 内容 |
|
||||
|---|---|---|
|
||||
| 1 | **视觉基线 = Chat 对齐** | 取值全部来自对 Chat 前端调研(品牌蓝 `--accent: #3964fe`、灰阶、气泡/侧边栏几何、阴影分级……);允许偏离但须在 web-styling.md 偏离表记录 |
|
||||
| 1 | **视觉基线 = Chat 对齐** | 取值全部来自对 Chat 前端的调研(品牌蓝 `--accent: #3964fe`、灰阶、气泡/侧边栏几何、阴影分级……);允许偏离但须在 web-styling.md 偏离表记录 |
|
||||
| 2 | **token 两层不三层** | 基线仓是 static→alias→specific 三层;我们体量下压成「语义层直接持实值(注释标 base 色板出处)+ 极少数组件专属槽位(`--bg-sidebar`/`--bubble-bg`)」两层,全部住 `web-ui/src/style/global.css` |
|
||||
| 3 | **字号/间距不 token 化** | 基线仓同款决策:字号在组件里写 px 且**成对写行高**(16/24、14/22、12/18),间距用 4 的倍数;token 化只覆盖颜色/圆角/动效/字体栈/阴影 |
|
||||
| 4 | **边框与交互态用透明度制** | 边框 `rgba(0,0,0,.04/.1)`、hover/active `rgba(38,49,72,.06/.1)`——叠加在任意海拔底色上都成立,不新造实色灰 |
|
||||
| 4 | **边框与交互态用透明度制** | 边框 `rgba(0,0,0,.04/.1)`、hover/active `rgba(38,49,72,.06/.1)`——叠加在任意层级的背景色上都成立,不新造实色灰 |
|
||||
| 5 | **暗色只在 token 表做** | `:root` 亮色实值 + `[data-theme='dark']` 覆盖同名变量;**组件 CSS 零主题选择器**;确需按主题换非 token 值时用「CSS 变量桥」(组件定义局部变量、主题块只覆写变量) |
|
||||
|
||||
## 工程约束
|
||||
@@ -45,13 +45,13 @@ GUI 无设计师供给,样式由 agent 编写并 review;没有一套机器
|
||||
|
||||
| 内容 | 归属 |
|
||||
|---|---|
|
||||
| 框架五条、工程约束、为何两层/为何不 token 化字号 | 本 RFC(改=新 RFC 供替) |
|
||||
| token 逐项权威值(含暗色)、视觉基线常数(侧边栏/气泡/会话列/输入卡片几何)、RPC 四象限方向符视觉词汇、编码规范 12 条、偏离记录 | web-styling.md(活文档,随实现演进) |
|
||||
| 框架五条、工程约束、为何两层/为何不 token 化字号 | 本 RFC(修改框架须由新 RFC 取代本文) |
|
||||
| token 逐项权威值(含暗色)、视觉基线常数(侧边栏/气泡/会话行/输入卡片几何)、RPC 四象限方向符视觉词汇、编码规范 12 条、偏离记录 | web-styling.md(活文档,随实现演进) |
|
||||
| 取值证据(deepseekchat file:line) | 调研归档已完成使命,git 历史留档 |
|
||||
|
||||
## Consequences
|
||||
|
||||
样式收敛到机器可对照:颜色/圆角/动效/阴影只引 web-styling.md §1 token,暗色是单一属性选择器覆盖表,review 与自查共用同一张 12 条清单。接受的代价:字号/间距靠成对行高与 4 倍数纪律而非 token;动框架本身须新 RFC 供替。
|
||||
样式收敛到机器可检查:颜色/圆角/动效/阴影只引 web-styling.md §1 token,暗色是单一属性选择器覆盖表,review 与自查共用同一张 12 条清单。接受的代价:字号/间距靠成对行高与 4 倍数纪律而非 token;动框架本身须由新 RFC 取代本文。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-20-gui-testing-system.md
|
||||
2026-07-20-gui-testing-system.md: 4a1600bbef7ef795677a446228fcc279a4b53f39
|
||||
2026-07-20-gui-testing-system.zh.md: 2aa5d7f66783c69964cabf7eb18a018b54528a33
|
||||
2026-07-20-gui-testing-system.zh.md: af4736ced6195dfc8d40ab276149490e8af94975
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# RFC: GUI 测试体系——三层结构
|
||||
# Agent Note: GUI 测试体系——三层结构
|
||||
|
||||
Status: implemented
|
||||
|
||||
> 路径更新(2026-07-22,插件体系重构):本文三层理念与金路径方法仍为现行;家搬了——对象层 spec 现居 `packages/client/runtime/tests/`(原 web-runtime)、wire spec 现居 `packages/client/connection/tests/`,`web-ui` 覆盖豁免随包消亡(组件 spec 为各 `packages/client/*/tests/` 的 jsdom 套件)。组件 spec 形态遵循 [slot 体系标准](../architecture/2026-07-22-slot-type-chain-implementation.md):props 直喂——store 份额来自 `createXXXStore().create()`(真引擎,获认可的零机械路径),框架 hook 用普通桩;无渲染机械、不挂 provider。坑位归属/注册表语义归 2 层地界(`runtime` + `ui-slots` 套件),不归组件 spec。
|
||||
> 路径更新(2026-07-22,插件体系重构):本文三层理念与金路径方法仍为现行;家搬了——对象层 spec 现居 `packages/client/runtime/tests/`(原 web-runtime)、wire spec 现居 `packages/client/connection/tests/`,`web-ui` 覆盖豁免随包消亡(组件 spec 为各 `packages/client/*/tests/` 的 jsdom 套件)。组件 spec 形态遵循 [slot 体系标准](../architecture/2026-07-22-slot-type-chain-implementation.md):props 直喂——store 份额来自 `createXXXStore().create()`(真引擎,获认可的零机械路径),框架 hook 用普通桩;无渲染机械、不挂 provider。slot 归属/注册表语义归 2 层地界(`runtime` + `ui-slots` 套件),不归组件 spec。
|
||||
|
||||
[English](2026-07-20-gui-testing-system.md) | 中文
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.md
|
||||
2026-07-21-serial-cross-platform-ci-reference.md: 71b364f72a094f7899a0929eec29f3a16ccc8a93
|
||||
2026-07-21-serial-cross-platform-ci-reference.zh.md: 0f324a5c235a2b495186caf0d6cc0490cc5876e3
|
||||
2026-07-21-serial-cross-platform-ci-reference.zh.md: 4f5920ffc150ce16d5567ca05128f7524c2a2b21
|
||||
|
||||
@@ -18,9 +18,9 @@ Status: implemented
|
||||
|
||||
[CI](../../../../.github/workflows/ci.yml) 为拉取请求事件与 master 推送事件赋予互补的职责。拉取请求在 GitHub 标准托管容量上运行合并后的 Linux 和 Windows 作业,以及 Node 兼容性与 Python 契约。向 `master` 推送时会跳过这些作业,改为运行四个显式参考作业:在标准托管运行器上的 `serial / linux`、`serial / macos` 和 `serial / windows`,以及在公司自有 `vm-backup` 池上的 `serial / linux (self-hosted standby)`——后者是热备演练,持续验证[故障切换手册](2026-07-26-ci-failover-runbook.md)所描述的切换目标。这些作业有意分别重复简短的代码检出、运行时设置和依赖锁定的安装步骤,不用矩阵或可复用工作流把操作系统差异隐藏起来。`workflow_dispatch` 仅用于运行器基准测试。
|
||||
|
||||
每个参考作业均在不设置任何分片选择器的情况下运行 `pnpm run check:ci`。`DSH_GATE_CONCURRENCY=1` 使顶层聚合每次只执行一个已经就绪的门禁;覆盖率、快照回放、built-bin 冒烟测试和发布验证的并发数也设为 1。各参考作业可以彼此并行,但每台主机上的仓库门禁都串行运行且完整执行。Linux 在回放快照前安装 bubblewrap,Windows 则在安装采用符号链接的工作区前启用开发人员模式。
|
||||
每个参考作业均在不设置任何分片选择器的情况下运行 `pnpm run check:ci`。`DSH_GATE_CONCURRENCY=1` 使顶层聚合每次只执行一个已经就绪的门禁;覆盖率、快照回放、built-bin 冒烟测试和发布验证的 worker 数量也设为 1。各参考作业可以彼此并行,但每台主机上的仓库门禁都串行运行且完整执行。Linux 在回放快照前安装 bubblewrap,Windows 则在安装采用符号链接的工作区前启用开发人员模式。
|
||||
|
||||
该完整聚合流程仍明确划分平台归属。`pty-local` 支持 Linux 与 macOS,因此其单元测试和逐文件覆盖率契约由 POSIX 平台负责,而不会在 Windows 上加载一个明确拒绝 `win32` 的后端;Windows 仍会执行所有可移植包(package)。可移植 fixture(测试前置数据)通过 `node:path` 派生原生路径,使用与生产代码相同的原生 realpath 实现比较规范化后的路径标识,并采用所有宿主机均允许的文件名。ACP(Agent Client Protocol)快照运行还会把生成的 cwd 分别通过 realpath 的 JavaScript 实现与原生实现得到的两种表示一并传给规范化器;规范化器按长度从长到短替换这些别名,避免 Windows 的短路径与长路径表示差异导致共享 fixture 反复变化。
|
||||
该完整聚合流程仍明确划分平台归属。`pty-local` 支持 Linux 与 macOS,因此其单元测试和逐文件覆盖率契约由 POSIX 平台负责,而不会在 Windows 上加载一个明确拒绝 `win32` 的后端;Windows 仍会执行所有可移植包。可移植 fixture(测试前置数据)通过 `node:path` 派生原生路径,使用与生产代码相同的原生 realpath 实现比较规范化后的路径标识,并采用所有宿主机均允许的文件名。ACP(Agent Client Protocol)快照运行还会把生成的 cwd 分别通过 realpath 的 JavaScript 实现与原生实现得到的两种表示一并传给规范化器;规范化器按长度从长到短替换这些别名,避免 Windows 的短路径与长路径表示差异导致共享 fixture 反复变化。
|
||||
|
||||
macOS 参考流程使用 fork 进程运行常规 Vitest 项目。macOS arm64 上的 Node 24 曾在工作线程中执行 CJS 词法分析器时异常终止;进程边界能够隔离这一外部运行时故障,且无需从聚合流程中删除任何测试,而 Linux 与 Windows 仍使用开销更低的线程池。仓库自身引入的竞态均在相应的观测边界修复:开发构建产物的轮询逻辑每次发布重新扫描结果前,都会先暂存候选表、候选图和候选监视基线映射;构建产物缺失后会一直保持脏状态,直到成功计算内容哈希。PTY 就绪检测会在轮询检查前台进程组归属期间保留提示符候选项;常规静默时限也适用于交互式子进程继承提示符标记的情况。真实 PTY fixture 会在运行时拼接同步标记,使就绪等待逻辑不会把交互式 shell 的输入回显误判为子进程已就绪。实时链接场景下的包管理器 e2e 会保留由工作流预先准备的 Corepack 主目录、pnpm 元数据缓存和 store 缓存,同时隔离其他包管理器的可变缓存,因此不会在安装前丢弃可复用的包管理器状态。
|
||||
|
||||
@@ -47,6 +47,6 @@ master 分支的参考作业仅用于诊断,不参与拉取请求所要求的
|
||||
|
||||
仅在真实宿主内核或打包后的 Landlock 安装中可见的沙箱回归,可能在 master 上的运行报告前已经合并。我们接受这个合并后检测窗口,以换取从每个拉取请求中移除四个非阻塞作业;默认分支仍保留完整信号。
|
||||
|
||||
明确的 `pty-local` 归属边界意味着 Windows 不会声称覆盖一个无法加载的后端,而 macOS 采用 fork 的单元测试工作进程会增加进程启动开销。这些代价换来的是:支持范围内的每项功能都有能够如实反映对应平台行为的判据,原生运行时异常终止不会抹掉其余单元测试结果,各项对时序敏感的观测逻辑也都会以调用方有机会修改状态前已建立的状态作为起点。
|
||||
明确的 `pty-local` 归属边界意味着 Windows 不会声称覆盖一个无法加载的后端,而 macOS 采用 fork 的单元测试工作进程会增加进程启动开销。这些代价换来的是:支持范围内的每个方面都有能够如实反映对应平台行为的判据,原生运行时异常终止不会抹掉其余单元测试结果,各项对时序敏感的观测逻辑也都会以调用方有机会修改状态前已建立的状态作为起点。
|
||||
|
||||
移除严格的时长超时后,系统会观测到延迟回归,而不是在发生回归时自动取消运行。因此,性能改动必须附带托管环境测量结果,已完成的日志则保留优化最慢通道所需的信息。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-22-evidence-based-larger-hosted-runners.md
|
||||
2026-07-22-evidence-based-larger-hosted-runners.md: d46b8291ec05e997728da76354354f9e36bd2fb4
|
||||
2026-07-22-evidence-based-larger-hosted-runners.zh.md: e05ad30a713258ed7bc3d8099f8d6fab3d7c0c5d
|
||||
2026-07-22-evidence-based-larger-hosted-runners.zh.md: f43712859d8351ffff45c7b6d5eb2b65015ee4c3
|
||||
|
||||
@@ -22,7 +22,7 @@ Linux 主流程使用 3 个相互独立的 32 核作业。覆盖率单独运行
|
||||
|
||||
门禁依赖关系保持显式。覆盖率消费源码,不等待构建。文档类型检查以消费方通道的完整 project-reference 输出为输入。快照回放和发布消费方等待生成的输出,而 Node 版本兼容性作业会验证对运行时敏感的源码加载,且不重复主源码项目图的类型检查。PTY 和子进程套件继续使用自身有界的内部并发,不继承运行器的核心数。
|
||||
|
||||
产物边界保持显式。`scripts/publint-all.ts` 对内存中的发布视图调用 publint 支持的 API;该视图由每个 manifest(元数据清单)声明的文件和 npm 强制要求的元数据组成,从而避免为每个包(package)启动一次包管理器 pack 进程。`scripts/verify-built-package-invariants.mjs` 将已声明的 `lib/` 文件暂存到真实包下,并通过普通 Node 和 Cordis Loader 规范化导入其已编译的自身引用;发布契约只要遗漏一个运行时分片,检查仍会失败。
|
||||
产物边界保持显式。`scripts/publint-all.ts` 对内存中的发布视图调用 publint 支持的 API;该视图由每个 manifest(元数据清单)声明的文件和 npm 强制要求的元数据组成,从而避免为每个包启动一次包管理器 pack 进程。`scripts/verify-built-package-invariants.mjs` 将已声明的 `lib/` 文件暂存到真实包下,并通过普通 Node 和 Cordis Loader 规范化导入其已编译的自身引用;发布契约只要遗漏一个运行时分片,检查仍会失败。
|
||||
|
||||
Windows 以一次 32 核环境设置同时承载阻塞性构建、生产网站和观测性的构建产物契约。重复的 lint、覆盖率和快照清单由 Linux 承担,因为在 Windows 上运行这些观测性副本会延长付费关键路径,却不会新增任何阻塞性平台契约。
|
||||
|
||||
@@ -44,7 +44,7 @@ Windows 仓库工作在超过 16 核后收益很小,但 32 核池可以让完
|
||||
|
||||
客户端包依赖图增大后,缓存机制和调度器压力也成为实测工作负载的一部分。在[一次分支头精确的候选运行](https://github.com/deepseek-harness/deepseek-harness/actions/runs/29912577681)中,Linux 的仓库门禁耗时 39 秒,完整作业耗时 69 秒;Windows 的仓库门禁耗时 117 秒,完整作业耗时 228 秒。Windows pnpm 缓存的 154 MB 归档下载耗时约 2 秒,但解压耗时 27 秒,随后安装耗时 23 秒,作业结束后的保存又耗时 14 秒。一次[无缓存的全规格运行轨迹](https://github.com/deepseek-harness/deepseek-harness/actions/runs/29913033155)在 27 秒内完成了同一台 32 核 Windows 运行器上的安装。因此,未来若要启用大型运行器,需要测量完整作业,而不能只测门禁耗时。
|
||||
|
||||
任何比较都必须计入主机设置。一个标准 Node 26 作业曾在总共 67 秒的耗时中,把 36 秒用在 `Set up job` 上;`actions/setup-node` 从托管 toolcache 找到 Node 后,仍花费 46.56 秒输出缓存的 Windows 环境详情。一个 Linux 候选作业还在注册 50 KB 的 Bubblewrap 包时耗时 18 秒,因为托管映像扫描了 202,507 个包数据库文件。[`scripts/prepare-ci-bubblewrap.sh`](../../../../scripts/prepare-ci-bubblewrap.sh) 改为验证固定包内容并将其解压到临时运行器目录,执行功能性隔离探针,并让这项准备工作与依赖安装重叠执行。
|
||||
任何比较都必须计入主机设置。一个标准 Node 26 作业曾在总共 67 秒的耗时中,把 36 秒用在 `Set up job` 上;`actions/setup-node` 从托管 toolcache 找到 Node 后,仍花费 46.56 秒输出缓存的 Windows 环境详情。一个 Linux 候选作业还在注册 50 KB 的 Bubblewrap 包时耗时 18 秒,因为托管映像扫描了 202,507 个包数据库文件。[`scripts/prepare-ci-bubblewrap.sh`](../../../../scripts/prepare-ci-bubblewrap.sh) 改为验证固定版本的 payload 并将其解压到临时运行器目录,执行功能性隔离探针,并让这项准备工作与依赖安装重叠执行。
|
||||
|
||||
内层与外层工作线程上限是相互独立的控制机制。一次[分支头精确、使用 32 个工作线程的 ESLint 实验](https://github.com/deepseek-harness/deepseek-harness/actions/runs/29918329463)使 lint 耗时增至 52.28 秒、覆盖率耗时增至 42.71 秒;同一次运行中,一项适配器空闲超时测试失败。后来一次同时运行 8 项门禁的运行轨迹将覆盖率耗时降至 35.17 秒,但生产网站构建被延后,直到聚合流程耗时达到 41.06 秒时才完成。因此,不能仅凭核心数照搬同等规模的工作线程上限。
|
||||
|
||||
@@ -52,7 +52,7 @@ Windows 仓库工作在超过 16 核后收益很小,但 32 核池可以让完
|
||||
|
||||
只有在 `master` 移动时,才运行完整的 Linux、macOS 和 Windows 串行参考。拉取请求使用企业级运行器必需路径和标准托管兼容性作业,其他大型运行器规格仅通过手动触发运行。
|
||||
|
||||
另有一条串行 Linux 参考在每次 `master` 推送时运行于公司自有的自托管池(`vm-backup` 标签:一台 64 核虚拟机,运行 6 个常驻的 systemd 管理运行器实例)。它是热备演练而非必需检查:每次运行都重新证明这台持久化虚拟机能够执行完整的未分片聚合流程。实际切换机制已预先布线:三个必需 Linux 作业通过写者可管理的仓库变量 `DSH_CI_FAILOVER` 解析运行器池,因此故障响应就是设置一个变量并重跑——无需合并(合并本身会被正在失败的检查死锁)([切换手册](2026-07-26-ci-failover-runbook.md))。该热备通道由 push 触发,执行的始终是基线分支自身的工作流定义。但需要注意:故障切换期间,`pull_request` 作业确实会带着 PR merge 引用自带的工作流定义到达这些运行器——信任边界是仓库成员资格(仓库为私有且禁用 fork,选择器排除 Dependabot),详见[故障切换手册](2026-07-26-ci-failover-runbook.md)的记录。
|
||||
另有一条串行 Linux 参考在每次 `master` 推送时运行于公司自有的自托管池(`vm-backup` 标签:一台 64 核虚拟机,运行 6 个常驻的 systemd 管理运行器实例)。它是热备演练而非必需检查:每次运行都重新证明这台持久化虚拟机能够执行完整的未分片聚合流程。实际切换机制已预先布线:三个必需 Linux 作业通过写入权限持有者可管理的仓库变量 `DSH_CI_FAILOVER` 解析运行器池,因此故障响应就是设置一个变量并重跑——无需合并(合并本身会被正在失败的检查阻塞,形成死锁)([切换手册](2026-07-26-ci-failover-runbook.md))。该热备通道由 push 触发,执行的始终是基础分支自身的工作流定义。但需要注意:故障切换期间,`pull_request` 作业确实会带着 PR merge 引用自带的工作流定义到达这些运行器——信任边界是仓库成员资格(仓库为私有且禁用 fork,选择器排除 Dependabot),详见[故障切换手册](2026-07-26-ci-failover-runbook.md)的记录。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-22-fast-local-git-hooks.md
|
||||
2026-07-22-fast-local-git-hooks.md: 838024c4293372b1430d357774feb06cd9742b9b
|
||||
2026-07-22-fast-local-git-hooks.zh.md: 460acf5270c075a808c6a4dc42635a808c7cd192
|
||||
2026-07-22-fast-local-git-hooks.zh.md: 26968dd19ffb42f4a618dc760b2a6edeb4900393
|
||||
|
||||
@@ -8,7 +8,7 @@ Status: implemented
|
||||
|
||||
agent(智能体)已经会运行能够覆盖自身改动的测试和检查,而提交、推送与 CI 可能分别重复其中范围越来越广的子集。因此,全量 pre-push 套件会拖慢每次推送,放大与当前改动无关的本地偶发失败,而且 CI 紧接着再次运行完整矩阵时不会提供新信号。
|
||||
|
||||
快速钩子仍需在工作离开本机之前拦下检查成本低且把握高的缺陷。暂存文件格式问题、空白错误、vendor 源码元数据缺失与仓库类型错误符合这条边界;单元测试套件、快照、文档检查、构建与包(package)的 `hygiene` 检查则随改动范围而异,不符合这条边界。
|
||||
快速钩子仍需在工作离开本机之前拦下检查成本低且把握高的缺陷。暂存文件格式问题、空白错误、vendor 源码元数据缺失与仓库类型错误符合这条边界;单元测试套件、快照、文档检查、构建与包的 `hygiene` 检查则随改动范围而异,不符合这条边界。
|
||||
|
||||
## 决策
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-22-product-first-root-readme.md
|
||||
2026-07-22-product-first-root-readme.md: 32542a45019d64ed1826d4eb21e68c67c3c3d52e
|
||||
2026-07-22-product-first-root-readme.zh.md: 1c4d5fa53854bfcade9742da1fb74d9636909f84
|
||||
2026-07-22-product-first-root-readme.zh.md: 994228d89154a041757685f526cad63c9455cfc8
|
||||
|
||||
@@ -16,7 +16,7 @@ Status: implemented
|
||||
|
||||
用户入口章节在已有的 Web、TUI 和 Headless 入口旁补充 ACP(Agent Client Protocol)自动化服务器和 Python/JSON-RPC SDK。安装后的 TUI 仍只需执行一条 `dsh` 命令;Web 说明要求先构建当前检出,再运行 `dsh web`,并明确处理自定义或复用的检出路径。这两条启动路径必须分别能在真实 PTY 与生产构建/HTTP 冒烟中原样执行。能力段落沿用简洁清单的写法,补充已经交付的 PTY、LSP、Web、目标、规划、任务、沙箱、审批、设置、凭据、会话查询和遥测等能力类别,并说明不同组合只选用其中一部分。相邻的一条列表项说明权威会话日志规则,因为持久化、回放、查询、遥测和各类接口都依赖它。
|
||||
|
||||
包(package)与服务的完整清单仍由各自的归属文档维护。中英文 README 采用相同的技术结构,但社区章节仍分别指向各自语言受众的主要交流渠道。文档网站继续使用独立的用户指南首页。
|
||||
包与服务的完整清单仍由各自的归属文档维护。中英文 README 采用相同的技术结构,但社区章节仍分别指向各自语言受众的主要交流渠道。文档网站继续使用独立的用户指南首页。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-23-personal-staging-maintenance-skills.md
|
||||
2026-07-23-personal-staging-maintenance-skills.md: a7ccc5b1e0f13e880c58a93d2e4c2cd4f06e2a93
|
||||
2026-07-23-personal-staging-maintenance-skills.zh.md: db1595c83da0ad93e9ba9055b5a3d7fe7cfe1706
|
||||
2026-07-23-personal-staging-maintenance-skills.zh.md: d988eae0b4746f14b893b8f1f32f8396e6548232
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Agent Note: 个人集成分支维护 skill(技能)
|
||||
# Agent Note: 个人集成分支维护 skill
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -10,13 +10,13 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
仓库从其根 `skills/` 目录分发 [`dsh-customize`](../../../../skills/dsh-customize/SKILL.md)、[`dsh-upgrade`](../../../../skills/dsh-upgrade/SKILL.md) 和 [`dsh-upstream-customization`](../../../../skills/dsh-upstream-customization/SKILL.md)。它们的描述同时说明操作内容和选择该 skill 的用户请求。分发的 TUI 在启动时将该目录提供给本地 skill 提供方,在发现优先级上位于项目根目录和用户根目录之后。这些 skill 根据已安装的启动器而非个人路径或分支名称定位当前生效的检出和集成分支,遵从仓库内指令,要求使用任务 worktree,并利用集成分支所在 worktree 的既有 `.agents/merge.lock`,串行执行每一次个人集成分支修改。
|
||||
仓库从其根 `skills/` 目录分发 [`dsh-customize`](../../../../skills/dsh-customize/SKILL.md)、[`dsh-upgrade`](../../../../skills/dsh-upgrade/SKILL.md) 和 [`dsh-upstream-customization`](../../../../skills/dsh-upstream-customization/SKILL.md)。它们的描述同时说明操作内容和选择该 skill(技能)的用户请求。分发的 TUI 在启动时将该目录提供给本地 skill 提供方,在发现优先级上位于项目根目录和用户根目录之后。这些 skill 根据已安装的启动器而非个人路径或分支名称定位当前生效的检出和集成分支,遵从仓库内指令,要求使用任务 worktree,并利用集成分支所在 worktree 的既有 `.agents/merge.lock`,串行执行每一次个人集成分支修改。
|
||||
|
||||
升级流程在变基前检查 Git 日志和提交范围,以识别将进入升级的上游变更、个人提交、重复内容和可能发生冲突的区域。它会丢弃上游已经提供的定制;如果这类定制在本地只剩说明性差异,也会一并丢弃,除非该说明包含上游缺失且可独立使用的当前契约。每次升级尝试使用同一个 UTC 基本格式时间戳,用于其独立的 `dsh-staging-<timestamp>` 同级克隆、本地 `dsh-upgrade/prepare-<timestamp>` 分支、新的 `dsh-staging/<timestamp>` 分支、私有的上游引用与恢复引用,以及启动器备份。同级克隆的名称不派生自当前目录名,名称冲突会直接失败,而不是追加临时后缀。流程根据进程命令和运行时环境而非 shell 工作目录推导当前 DSH 进程的源码位置,随后将已安装启动器所指向的仓库和检出视为不可变,唯一例外是持有其既有合并锁。
|
||||
升级流程在变基前检查 Git 日志和提交范围,以识别将进入升级的上游变更、个人提交、重复内容和可能发生冲突的区域。它会丢弃上游已经提供的定制;如果这类定制在本地只剩文档差异,也会一并丢弃,除非该说明包含上游缺失且可独立使用的当前契约。每次升级尝试使用同一个 UTC 基本格式时间戳,用于其独立的 `dsh-staging-<timestamp>` 同级克隆、本地 `dsh-upgrade/prepare-<timestamp>` 分支、新的 `dsh-staging/<timestamp>` 分支、私有的上游引用与恢复引用,以及启动器备份。同级克隆的名称不派生自当前目录名,名称冲突会直接失败,而不是追加临时后缀。流程根据进程命令和运行时环境而非 shell 工作目录推导当前 DSH 进程的源码位置,随后将已安装启动器所指向的仓库和检出视为不可变,唯一例外是持有其既有合并锁。
|
||||
|
||||
在独立克隆中验证通过后,工作流会创建并验证带时间戳的集成分支,然后以原子方式将启动器从保持不变的旧集成分支检出一次性切换到新集成分支检出。启动器绝不会指向准备、功能、评审、发布或处于分离状态的检出。切换前的失败会让已安装的检出和启动器保持不变;切换后的失败则恢复并验证启动器备份。旧的集成分支检出、其分支、恢复引用和启动器备份会一直保留,直到重启后的进程证明 DSH 运行于新的集成分支,且用户明确批准回滚清理为止。
|
||||
|
||||
`dsh-upstream-customization` 独立于本地维护和升级,负责向上游发布。它推荐 bug 修复、附加式且不冲突的插件功能,以及视觉改进;侵入式变更需先取得维护者批准。在升级结束时,agent 会对剩余定制进行分类、说明其上游价值、建议是否提交,并询问用户希望向上游贡献哪个具名候选项。只有用户做出选择后才会加载发布工作流;每项功能在推送或创建草稿 PR(Pull Request)前仍必须得到明确批准。获批的变更均以当前上游 `master` 为起点,不带入无关的个人提交。TUI 功能的草稿 PR 建议在移除凭证与个人数据后,附上完整应用的截图。`dsh-customize` 要求在集成前于专用 tmux 会话中检验交互式 TUI 行为。
|
||||
`dsh-upstream-customization` 独立于本地维护和升级,负责向上游发布。它推荐 bug 修复、附加式且不冲突的插件功能,以及视觉改进;侵入式变更需先取得维护者批准。在升级结束时,agent 会对剩余定制进行分类、说明其上游价值、建议是否提交,并询问用户希望向上游贡献哪个具名候选项。只有用户做出选择后才会加载发布工作流;每项功能在推送或创建草稿 PR(Pull Request)前仍必须得到明确批准。获批的变更均以当前上游 `master` 为起点,不带入无关的个人提交。TUI 功能的草稿 PR 建议在移除凭证与个人数据后,附上组装后应用的截图。`dsh-customize` 要求在集成前于专用 tmux 会话中检验交互式 TUI 行为。
|
||||
|
||||
## 备选方案
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-23-portable-required-pull-request-ci.md
|
||||
2026-07-23-portable-required-pull-request-ci.md: 1a6939e8386e381cba114a7be71993a644457a45
|
||||
2026-07-23-portable-required-pull-request-ci.zh.md: cf0af769f9e740a2c9285caf4be05023371578d9
|
||||
2026-07-23-portable-required-pull-request-ci.zh.md: f7a95ced2353ca7aaee9a586cc4f0bb297cbf7d4
|
||||
|
||||
@@ -12,7 +12,7 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
[CI](../../../../.github/workflows/ci.yml) 在仅限本仓库使用的企业级 32 核运行器池上运行必需的主 Node 24 作业,以及稳定的 `all checks passed` 聚合流程。该聚合流程不执行代码检出或仓库门禁;但让它与所依赖的实质性作业共用企业级运行器池,可以避免这些作业已经成功后,必需判定结果又引入一项单独的标准托管计费依赖。必需的 Windows 作业在标准 `ubuntu-latest` 上通过 Wine 运行 Windows Node 以覆盖阻断表面([Wine 通道决策](2026-07-27-wine-windows-gates-experiment.md)),使拉取请求的 Windows 契约不依赖任何 Windows 运行器分配;完整的原生内核 Windows 清单归 master 串行参考流程所有。标准 `ubuntu-latest` 作业保留 Node 22.19、Node 26 和 Python SDK 兼容性,`master` 则运行完整的 Linux、macOS 和 Windows 串行参考流程。这些标准托管作业让可移植执行边界保持可观测,而不必在每个拉取请求中重复主清单。
|
||||
[CI](../../../../.github/workflows/ci.yml) 在仅限本仓库使用的企业级 32 核运行器池上运行必需的主 Node 24 作业,以及稳定的 `all checks passed` 聚合流程。该聚合流程不执行代码检出或仓库门禁;但让它与所依赖的实质性作业共用企业级运行器池,可以避免这些作业已经成功后,必需判定结果又引入一项单独的标准托管计费依赖。必需的 Windows 作业在标准 `ubuntu-latest` 上通过 Wine 运行 Windows Node 以覆盖阻断性检查范围([Wine 通道决策](2026-07-27-wine-windows-gates-experiment.md)),使拉取请求的 Windows 契约不依赖任何 Windows 运行器分配;完整的原生内核 Windows 清单位于 master 串行参考流程中。标准 `ubuntu-latest` 作业保留 Node 22.19、Node 26 和 Python SDK 兼容性,`master` 则运行完整的 Linux、macOS 和 Windows 串行参考流程。这些标准托管作业让可移植执行边界保持可观测,而不必在每个拉取请求中重复主清单。
|
||||
|
||||
两项 Linux 主作业、Node 兼容性、Python SDK 和 `windows node 24 / wine blocking` 继续作为 `all checks passed` 的依赖项;分支保护继续要求 `e2e` 和 `all checks passed`。剩余的企业级 Linux 运行器标签无法分配运行器时没有自动后备机制:标准作业会继续报告各自的契约,但无法产出缺失的必需结果。
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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-23-translation-prompt-v4-contract.md: 3e1e51797aa3463c8db24d8657120434e6822789
|
||||
2026-07-23-translation-prompt-v4-contract.zh.md: 161d2b6cf3bd3499e3c505a178da40ce577ca797
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-23-translation-prompt-v4-contract.md
|
||||
2026-07-23-translation-prompt-v4-contract.md: adeb63e0f4bf03ec0b94a371f596bb84138d81ed
|
||||
2026-07-23-translation-prompt-v4-contract.zh.md: 5bd7bbf0d2d02222ca8038afb0f5a156fffea1b0
|
||||
|
||||
@@ -12,6 +12,8 @@ Automated counterpart generation needs a stable prompt that reproduces the regis
|
||||
|
||||
The committed [translation prompt](../../../../docs/i18n/translation-prompt.md) is the calibrated pipeline asset. Its renderer injects only the source language, target language, and current [terminology table](../../../../docs/i18n/terminology.md), and rejects unknown, missing, or malformed placeholder syntax before assembling a request. The request assembler retains the source basename outside the model-visible prompt and places each reviewed whole-document pair into one bare-text user/assistant example turn before the real source document. The template may carry model-specific calibration rules, but those rules remain subordinate to the repository's binding pairing, terminology, structure, and emphasis contracts.
|
||||
|
||||
The v7 calibration retains that v4 protocol and makes the instruction priority explicit: source meaning and protected structure, then the terminology table, then whole-document gold-pair voice, then general guidance and embedded examples. It directs the model to draft as a native technical author and then compare clause by clause, preserving actors, conditions, negation, modality, lifecycle conditions, direction, result channels, ownership, and quantities. Style guidance cannot invent an actor or vary a terminology-table form, defined concept, or contract verb merely for variety. Unresolved terminology stays unchanged in the translation and is reported only as pending review.
|
||||
|
||||
The response has three ordered top-level sections: `translation`, `review`, and `final`. The response consumer derives the target basename from the retained source context, preserves optional leading YAML frontmatter, and mechanically inserts or corrects the language switcher after the first H1 in `final`. The parser requires each section exactly once, rejects content outside the envelope, and tolerates one outer `xml` Markdown fence because models sometimes echo the prompt's example fence.
|
||||
|
||||
## Response framing
|
||||
@@ -28,6 +30,10 @@ The executable contract lives in [the renderer, request assembler, parser, and r
|
||||
|
||||
**Return only the final translation.** A single body is simpler to parse but discards the explicit correction pass used to catch tone, structure, terminology, and punctuation defects before publication.
|
||||
|
||||
**Replace the calibrated v4 asset wholesale with a later experimental prompt.** Later experiments clarified useful general rules but did not pass strict whole-document evaluation as complete replacements. The production asset adopts only the improvements that preserve the established examples and executable protocol.
|
||||
|
||||
**Require a strict draft-to-final change ledger.** Requiring every final edit to appear in a free-text review ledger adds output burden without proving that the review catches local semantic drift. The review records actual corrections, while the final translation remains subject to deterministic structure checks and human review.
|
||||
|
||||
## Consequences
|
||||
|
||||
Prompt wording is executable behavior and receives code review, a translation-prompt verifier, and a runnable request/response snapshot. The calibrated asset and the general translation rules can evolve for their different audiences, but review must reject contradictions with binding repository contracts. The line escape is visible only when source documentation contains a wrapper tag on its own line, and parser tests pin its lossless behavior.
|
||||
Prompt wording is executable behavior and receives code review, a translation-prompt verifier, and a runnable request/response snapshot. Focused tests pin the retained v4 examples and selected v7 safeguards. The `translation-prompt-v4` snapshot directory names the stable renderer/parser protocol lineage rather than the current calibration revision. The calibrated asset and the general translation rules can evolve for their different audiences, but review must reject contradictions with binding repository contracts. The line escape is visible only when source documentation contains a wrapper tag on its own line, and parser tests pin its lossless behavior.
|
||||
|
||||
@@ -12,6 +12,8 @@ Status: implemented
|
||||
|
||||
提交入库的[翻译提示词](../../../../docs/i18n/translation-prompt.md)是经过校准的流水线资源。其渲染器仅注入源语言、目标语言和当前[术语表](../../../../docs/i18n/terminology.md),并在组装请求前拒绝未知、缺失或语法格式错误的占位符。请求组装器在模型可见的提示词之外保留源文件基本名,并在真正的源文档之前,将每组经评审的整篇文档对编排为一个纯文本 user/assistant 示例轮次。模板可以包含针对特定模型的校准规则,但这些规则必须服从仓库中具约束力的配对、术语、结构与强调格式契约。
|
||||
|
||||
v7 校准保留这套 v4 协议,并明确指令优先级:先保持源文含义与受保护结构,再遵循术语表,然后以整篇金标校准语体,最后应用一般指导与内嵌示例。模型先以母语技术作者的方式起草,再逐项对照源文,保留执行主体、条件、否定、情态、生命周期条件、方向、结果通道、所有权和数量。文体指导不得虚构执行主体,也不得仅为丰富措辞而改换术语表词形、已定义概念或契约动词。无法裁定的术语在译文中保持不变,只在评审段标为待人工确认。
|
||||
|
||||
响应包含三个有序的顶层分段:`translation`、`review` 和 `final`。响应消费方根据保留的源文件上下文推导目标文件基本名,保留文件开头可选的 YAML frontmatter,并以机械方式在 `final` 中第一个 H1 之后插入或校正语言切换行。解析器要求每个分段恰好出现一次,拒绝封套之外的内容,并允许响应最外层有一层 `xml` Markdown 围栏,因为模型有时会照抄提示词中的示例围栏。
|
||||
|
||||
## 响应封装格式
|
||||
@@ -28,6 +30,10 @@ Status: implemented
|
||||
|
||||
**只返回最终译文。** 单一正文更易解析,却会丢弃显式修正步骤;这个步骤用于在发布前发现语气、结构、术语和标点缺陷。
|
||||
|
||||
**用较新的实验提示词整体替换经校准的 v4 资源。** 后续实验澄清了一些有价值的通用规则,但作为完整替代方案未通过严格的整篇文档评估。生产资源只吸收能够保留既有示例与可执行协议的改进。
|
||||
|
||||
**要求严格的草稿到定稿变更账本。** 要求定稿中的每项修改都出现在自由文本评审账本中,会增加输出负担,却不能证明评审能够发现局部语义漂移。评审段记录实际修正,定稿仍需接受确定性结构检查与人工评审。
|
||||
|
||||
## 影响
|
||||
|
||||
提示词措辞属于可执行行为,因此需要经过代码评审、翻译提示词校验器校验及可运行的请求/响应快照验证。经校准的资源与通用翻译规则可以针对各自的受众分别演进,但评审必须拒绝任何与仓库约束性契约冲突的改动。只有当源文档中的封装标签独占一行时,行转义才会显现;解析器测试锁定这一无损行为。
|
||||
提示词措辞属于可执行行为,因此需要经过代码评审、翻译提示词校验器校验及可运行的请求/响应快照验证。聚焦测试固定保留的 v4 示例与选定的 v7 保护规则。`translation-prompt-v4` 快照目录命名的是稳定的渲染器/解析器协议谱系,而不是当前校准修订号。经校准的资源与通用翻译规则可以针对各自的受众分别演进,但评审必须拒绝任何与仓库约束性契约冲突的改动。只有当源文档中的封装标签独占一行时,行转义才会显现;解析器测试锁定这一无损行为。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-25-semantic-pr-label-taxonomy.md
|
||||
2026-07-25-semantic-pr-label-taxonomy.md: 3217b405e968d4d2c1eba1f1a5a08008b18ba514
|
||||
2026-07-25-semantic-pr-label-taxonomy.zh.md: 4cc603daa52bc9e6b0a85e33086a559a21dcc621
|
||||
2026-07-25-semantic-pr-label-taxonomy.zh.md: 978f11af9402f248679e2087ff7fb513321b69b9
|
||||
|
||||
@@ -44,7 +44,7 @@ PR(Pull Request)需要传达两个不同的信号:它带来哪一类变更
|
||||
|
||||
`tasks` 负责与运行中进程绑定的后台工作,`schedule` 则负责持久化的定时作业。`tools` 负责通用的注册表契约、schema 契约和执行契约;具体能力只有在修改其中一项契约时才带有 `tools`。`attachment` 负责持久化的媒体引用和多模态输入传递,`artifact` 则负责模型声明的交付物标识和预览生命周期;二者都不会因实现包含工具或界面部分而借用 `tools` 或 `ui`。
|
||||
|
||||
标签名称以语义归属为准,而不是词面相似性。`hooks` 指 Claude Code 和 Codex 的 agent 桥接,而不是本地 Git 钩子;`platform` 指产品可移植性,而不是 CI 运行器选择;`build` 指编译、打包和已构建的包(package)产物,而不是文档生成器。
|
||||
标签名称以语义归属为准,而不是词面相似性。`hooks` 指 Claude Code 和 Codex 的 agent 桥接,而不是本地 Git 钩子;`platform` 指产品可移植性,而不是 CI 运行器选择;`build` 指编译、打包和已构建的包产物,而不是文档生成器。
|
||||
|
||||
### 可扩展性
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.md
|
||||
2026-07-26-briefed-minimal-translation-updates.md: a47251376771165d0eb229aaa0fb7f63589d7d77
|
||||
2026-07-26-briefed-minimal-translation-updates.zh.md: 5a6787b9bdb3286b71842abec075c5a0ebfc7991
|
||||
2026-07-26-briefed-minimal-translation-updates.zh.md: c3c1b4e845b5a45acde55884b883b1dd1e570d77
|
||||
|
||||
@@ -18,7 +18,7 @@ Status: implemented
|
||||
|
||||
## 基准测试
|
||||
|
||||
该决策来自对本仓库历史上十次真实配对更新的受控回放(2026 年 7 月;每例改动 1 到 64 行英文,涵盖 README、RFC、Agent Note(agent 决策记录)与用户文档)。每个样例都在临时仓库中重建到其真实的上次确认状态,英文改动保持未提交,再用全新的 subagent 分别跑过相互竞争的各条工作流:维持现状的语料加载路径、简报路径、无指导对照组、整篇重译、小模型上的简报路径,以及每个 agent(智能体)一次处理三对文档的批量方案。产出先经机械门禁把关,再由评委盲评打分;评委还同时收到真实的历史更新与原样未动的陈旧对侧文件作为对照。
|
||||
该决策来自对本仓库历史上十次真实配对更新的受控回放(2026 年 7 月;每例改动 1 到 64 行英文,涵盖 README、RFC、Agent Note 与用户文档)。每个样例都在临时仓库中重建到其真实的上次确认状态,英文改动保持未提交,再用全新的 subagent 分别跑过相互竞争的各条工作流:维持现状的语料加载路径、简报路径、无指导对照组、整篇重译、小模型上的简报路径,以及每个 agent(智能体)一次处理三对文档的批量方案。产出先经机械门禁把关,再由评委盲评打分;评委还同时收到真实的历史更新与原样未动的陈旧对侧文件作为对照。
|
||||
|
||||
- 简报路径在盲评的忠实性、保留度与流畅度上与现状路径打平(两者都达到或超过真实历史更新的水平),而在未发生停滞的样例上只花费约三分之一的 token 用量与墙钟时间(全部十例的中位数:相对 token 成本单位 276k 对 595k,轮次数 14 对 32)。
|
||||
- 整篇重译被证实有害,而不只是浪费:它丢弃经评审的措辞,盲评保留度因此崩塌(4.4/10 对 9.8);它还使各更新组保持住的既定术语发生漂移(既定译法本就写在对侧文件自身的正文里);而且它是成本最高的一组。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md
|
||||
2026-07-26-ci-failover-runbook.md: 72261f95ea74b61e3915a1a6419b2c2e616efbd9
|
||||
2026-07-26-ci-failover-runbook.zh.md: fdce40ffac5036cb4caf8eb86d20b7bb5bae4fc8
|
||||
2026-07-26-ci-failover-runbook.zh.md: 1ee679eabc296ab31d71b87945409788539425bd
|
||||
|
||||
@@ -10,7 +10,7 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
三个必需的 Linux 工作作业——以及 `all checks passed` 判定作业(若不随切换,即使全部工作作业通过,它仍会滞留在故障池的队列中)——各自通过仓库变量 `DSH_CI_FAILOVER` 解析运行器池。变量不存在(正常)时它们运行在托管企业池上;由任何具备写权限的协作者设为 `selfhosted` 时,四者全部切换到公司自有的自托管 `vm-backup` 池,coverage 与 snapshot 的并发降到共享虚拟机上限,并跳过托管路径的 pnpm 缓存恢复。这个开关是写者可管理的仓库状态而非一次合并,因此在所有检查都是红色时仍然有效。自有池的就绪状态由 `serial / linux (self-hosted standby)` 通道持续验证——每次 master 推送都在其上运行完整的未分片聚合流程。
|
||||
三个必需的 Linux 工作作业——以及 `all checks passed` 判定作业(若不随切换,即使全部工作作业通过,它仍会滞留在故障池的队列中)——各自通过仓库变量 `DSH_CI_FAILOVER` 解析运行器池。变量不存在(正常)时它们运行在托管企业池上;由任何具备写权限的协作者设为 `selfhosted` 时,四者全部切换到公司自有的自托管 `vm-backup` 池,覆盖率与快照的并发降到共享虚拟机上限,并跳过托管路径的 pnpm 缓存恢复。这个开关是写者可管理的仓库状态而非一次合并,因此在所有检查都是红色时仍然有效。自有池的就绪状态由 `serial / linux (self-hosted standby)` 通道持续验证——每次 master 推送都在其上运行完整的未分片聚合流程。
|
||||
|
||||
### 自有池是什么
|
||||
|
||||
@@ -20,11 +20,11 @@ Status: implemented
|
||||
|
||||
1. 仓库 **Settings → Secrets and variables → Actions → Variables → New repository variable**:名称 `DSH_CI_FAILOVER`,值 `selfhosted`。
|
||||
2. 重新触发必需作业,使其重新解析运行器池。已经为托管标签**排队**的作业不会重定向,也无法原地 re-run,因此对于本手册所述的无限排队故障,应取消卡住的运行并 re-run all jobs,或推送一个新提交;“Re-run failed jobs”只有在作业真正失败(而非仍在排队)时才有用。
|
||||
3. 切换到此完成。故障切换状态下工作流还会自动:把 `DSH_COVERAGE_MAX_WORKERS` 降为 8、`DSH_SNAPSHOT_MAX_CONCURRENCY` 降为 12(按 6 个常驻实例定容:最坏 6 × 8 = 48 个覆盖率工作进程对 64 核)(共享虚拟机的争抢上限),并跳过托管路径的 pnpm 缓存恢复(虚拟机的持久 store 直接提供热安装)。
|
||||
3. 切换到此完成。故障切换状态下工作流还会自动:把 `DSH_COVERAGE_MAX_WORKERS` 降为 8、`DSH_SNAPSHOT_MAX_CONCURRENCY` 降为 12(按 6 个常驻实例定容:最坏情况下,6 × 8 = 48 个覆盖率工作进程运行在 64 核虚拟机上)(共享虚拟机的争抢上限),并跳过托管路径的 pnpm 缓存恢复(虚拟机的持久 store 直接提供热安装)。
|
||||
|
||||
#**Dependabot 例外。**四个选择器都刻意排除了 `dependabot[bot]`:故障切换期间,Dependabot 拉取请求继续在托管池排队,而不是把依赖方提供的代码放到持久化虚拟机上执行。故障期间 Dependabot PR 持续排队是预期行为而非切换失败;托管池恢复后它会自行完成。
|
||||
#**Dependabot 例外。**四个选择器都刻意排除了 `dependabot[bot]`:故障切换期间,Dependabot 拉取请求继续在托管池排队,而不是把依赖项提供的代码放到持久化虚拟机上执行。故障期间 Dependabot PR 持续排队是预期行为而非切换失败;托管池恢复后它会自行完成。
|
||||
|
||||
**谁能扳动这个变量。**GitHub 的 API 允许任何具有写权限的协作者管理仓库变量,因此该开关实际是写者级而非严格的管理员级。在本仓库的信任模型下这并不构成越权:runner group 接纳本私有、禁 fork 仓库的全部工作流(这是让 PR 引用的故障切换得以成立的刻意取舍),因此任何写者本就可以通过推送分支工作流触达这台虚拟机。抵御不可信代码的边界是仓库成员资格;变量只是为成员路由工作。
|
||||
**谁能扳动这个变量。**GitHub 的 API 允许任何具有写权限的协作者管理仓库变量,因此该开关实际是写者级而非严格的管理员级。在本仓库的信任模型下这并不构成升权:runner group 接纳本私有、禁 fork 仓库的全部工作流(这是让 PR 引用的故障切换得以成立的刻意取舍),因此任何写者本就可以通过推送分支工作流触达这台虚拟机。抵御不可信代码的边界是仓库成员资格;变量只是为成员路由工作。
|
||||
|
||||
## 切换期间的容量
|
||||
|
||||
@@ -47,4 +47,4 @@ Status: implemented
|
||||
|
||||
## 后果
|
||||
|
||||
从托管池故障中恢复只需一个变量(任何写者可设)加一次重跑,关键路径上没有合并。代价是要维护第二套运行器拓扑:热备通道在每次 master 推送时都运行它,使故障切换目标永不失效;而 `ci.yml` 中的并发与缓存恢复分支带有一条 `selfhosted` 支路,必须与托管支路保持同步。
|
||||
从托管池故障中恢复只需一个变量(任何写者可设)加一次重跑,关键路径上没有合并。代价是要维护第二套运行器拓扑:热备通道在每次 master 推送时都运行它,避免故障切换目标变得陈旧;而 `ci.yml` 中的并发与缓存恢复分支带有一条 `selfhosted` 支路,必须与托管支路保持同步。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-26-dependencies-over-hand-rolling.md
|
||||
2026-07-26-dependencies-over-hand-rolling.md: 22720c483c1c9e8145497b3e83cbc9f17570b161
|
||||
2026-07-26-dependencies-over-hand-rolling.zh.md: ac988eb4b3af9ba18ee2150bab93f01f0e36003e
|
||||
2026-07-26-dependencies-over-hand-rolling.zh.md: 4c3aee489cd8a8269b2b083ea2fb7978d01f4e26
|
||||
|
||||
@@ -6,18 +6,18 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
harness 手写了大量基础设施,而成熟的外部包(package)早已提供同等能力。其中一部分是有意为之——以源码形式收录的 Cordis([引入 vendor 的决策](2026-06-11-vendor-cordis-as-source.md))、[孪生 LLM(大语言模型)适配器](../architecture/2026-06-13-twin-llm-adapters.md)、作为配置 schema 标准的 schemastery——但相当大一部分源自一条未经言明的「避免新依赖」反射,逐渐累积而成:仓库级的外部依赖清单始终很小,各包却各自长出了自己的 SSE(Server-Sent Events)解析器、协议分帧器、重试循环和 glob 匹配器。`AGENTS.md` 其实从未写下任何依赖政策,agent(智能体)只能从既有模式中自行推断出一条,而这条推断出的规则(「不要加依赖」)比任何人实际决定过的都更严格。这正是 Not Invented Here(非我发明)谬误在默认状态下运作:每一个对维护良好的库的手写克隆,都是要由我们自己测试、撰写文档、评审和调试的代码,却享受不到生态累积下来的边界情况修复。
|
||||
harness 手写了大量基础设施,而成熟的外部包早已提供同等能力。其中一部分是有意为之——以源码形式收录的 Cordis([引入 vendor 的决策](2026-06-11-vendor-cordis-as-source.md))、[孪生 LLM(大语言模型)适配器](../architecture/2026-06-13-twin-llm-adapters.md)、作为配置 schema 标准的 schemastery——但相当大一部分是在一种未经言明的「避免新依赖」下意识作用下逐渐累积而成的:仓库级的外部依赖清单始终很小,各包却各自长出了自己的 SSE(Server-Sent Events)解析器、协议分帧器、重试循环和 glob 匹配器。`AGENTS.md` 其实从未写下任何依赖政策,agent(智能体)只能从既有模式中自行推断出一条,而这条推断出的规则(「不要加依赖」)比任何人实际决定过的都更严格。这正是 Not Invented Here(非我发明)谬误在默认状态下运作:每一个对维护良好的库的手写克隆,都是要由我们自己测试、撰写文档、评审和调试的代码,却享受不到生态累积下来的边界情况修复。
|
||||
|
||||
## 决策
|
||||
|
||||
引入外部依赖是一种正当的简化,而不是政策特例。当一个维护良好的包(或我们引擎下限即已提供的 Node 内置能力)覆盖了某块手写接口面时,替换手写代码就是优先方向,并遵循与其他任何简化相同的证据标准:这次替换必须切实缩减我们持有的东西(代码、测试和契约面),而不是仅仅把复杂度挪到一个包装层后面。
|
||||
引入外部依赖是一种正当的简化,而不是政策特例。当一个维护良好的包(或我们引擎下限即已提供的 Node 内置能力)覆盖了某块手写接口面时,替换手写代码就是优先方向,并遵循与其他任何简化相同的证据标准:这次替换必须切实缩减我们负责维护的内容(代码、测试和契约面),而不是仅仅把复杂度挪到一个包装层后面。
|
||||
|
||||
新依赖的准入门槛:
|
||||
|
||||
- **净删除。** 该依赖替换的是真实持有的代码(实现 + 专属测试 + 文档),而不是假想中的未来代码。只增加能力的依赖属于功能决策,不属于简化。
|
||||
- **净删除。** 该依赖替换的是实际由我们维护的代码(实现 + 专属测试 + 文档),而不是假想中的未来代码。只增加能力的依赖属于功能决策,不属于简化。
|
||||
- **健康度。** 持续维护、广泛使用、传递依赖足迹合理。一个无人维护的小包,只是拿我们的代码换来别人废弃的代码。
|
||||
- **边界契合。** 该包的语义要覆盖我们的实际契约;仍需围绕它手写补齐的残留语义,要计入这次替换的减分项。
|
||||
- **不触碰已定案的 seam。** schemastery(配置 schema)、源码收录的 Cordis、`@earendil-works` 孪生适配器,以及其他记录在已实现 Agent Note(agent 决策记录)中的决策,不因本政策而重开;一次会瓦解已记录设计的替换,必须胜过所记录的论证理由,而不能只援引本 Agent Note。
|
||||
- **不触碰已定案的 seam。** schemastery(配置 schema)、源码收录的 Cordis、`@earendil-works` 孪生适配器,以及其他记录在已实现 Agent Note 中的决策,不因本政策而重开;一次会瓦解已记录设计的替换,必须胜过所记录的论证理由,而不能只援引本 Agent Note。
|
||||
|
||||
`packages/util/` 的「零依赖」章程描述的是该分组的*导出*纪律(util 包不携带 harness 依赖,从而任何分组都能依赖它们),并不禁止在能带来简化时使用外部包;如果一个 util 包的全部职责有维护良好的外部包做得更好,就应当用该依赖替换它,而不是为了章程而保留它。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-26-frozen-agent-note-archive.md
|
||||
2026-07-26-frozen-agent-note-archive.md: 52b43088b276c0c8e263fc8a81a2df1408cc8059
|
||||
2026-07-26-frozen-agent-note-archive.zh.md: 9362fbcb268045f0cb6762bf6804a54eed34caee
|
||||
2026-07-26-frozen-agent-note-archive.zh.md: ac87935fff1b3942d0bc8eade49237aa3c90d21e
|
||||
|
||||
@@ -6,17 +6,17 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
implemented Agent Note(agent 决策记录)作为当前决策记录持续维护,因此活跃记录集合中的每个路径、符号、默认值、译文、代码围栏、包(package)引用和出站链接都会形成维护义务。当决策依据可以指导未来工作时,这项成本合理;但对于已经收尾的 UI 细节、小型修复、已被取代的实现机制,或当前权威依据已转移到别处的流程历史,这项成本并不值得。删除所有低价值的已实施记录会抹去有用的历史证据,而保留每一项被否决的提案,又会留下既无采纳可能也无启发意义的想法。这套记录集合需要一道留存边界,在区分活跃指导与冻结历史的同时,避免让归档成为另一个维护层级。
|
||||
implemented Agent Note 作为当前决策记录持续维护,因此活跃记录集合中的每个路径、符号、默认值、译文、代码围栏、包引用和出站链接都会形成维护义务。当决策依据可以指导未来工作时,这项成本合理;但对于已经收尾的 UI 细节、小型修复、已被取代的实现机制,或当前权威依据已转移到别处的流程历史,这项成本并不值得。删除所有低价值的已实施记录会抹去有用的历史证据,而保留每一项被否决的提案,又会留下既无采纳可能也无启发意义的想法。这套记录集合需要一道留存边界,在区分活跃指导与冻结历史的同时,避免让归档成为另一个维护层级。
|
||||
|
||||
## 决策
|
||||
|
||||
只有 implemented Agent Note 可以归档。当一份已实施记录的交付决策已经完整落地,且其决策依据、备选方案、后果、否定性保证和重新引入条件不太可能再指导未来工作时,将其移入归档。基础性边界、持久化语义与协议语义、安全规则、反复出现且看似诱人的设计选择和尚未解决的重新引入条件,无论记录的存续时间或字数如何,都继续作为活跃记录保留。proposed Agent Note 绝不进入归档;过时的提案应转为 rejected。仅当 rejected Agent Note 仍能避免一种诱人且影响重大的错误时保留,否则将其三个配对文件完整删除。
|
||||
只有 implemented Agent Note 可以归档。当一份已实施记录的交付决策已经完整落地,且其决策依据、备选方案、后果、否定性保证和重新引入条件不太可能再指导未来工作时,将其移入归档。基础性边界、持久语义与协议语义、安全规则、反复出现且看似诱人的设计选择和尚未解决的重新引入条件,无论记录的存续时间或字数如何,都继续作为活跃记录保留。proposed Agent Note 绝不进入归档;过时的提案应转为 rejected。仅当 rejected Agent Note 仍能避免一种诱人且影响重大的错误时保留,否则将其三个配对文件完整删除。
|
||||
|
||||
归档路径为 `.agents/notes/archived/{kind}/yyyy-mm-dd-topic.md`,其中省略了冗余的 `implemented` 层级。归档变更会移动完整的英文、中文和一致性伴随记录三个文件,保留 `Status: implemented`,并在两种语言的文件中紧接该状态行插入 `Archived: YYYY-MM-DD`。归档时只允许做文件迁移、添加该元数据行、相应地重新记录伴随文件,以及机械修复入站链接。
|
||||
归档路径为 `.agents/notes/archived/{kind}/yyyy-mm-dd-topic.md`,其中省略了冗余的 `implemented` 层级。归档变更会移动完整的英文、中文和一致性伴随记录三个文件,保留 `Status: implemented`,并在两种语言的文件中紧接该状态行插入 `Archived: YYYY-MM-DD`。归档时只允许做文件迁移、添加该元数据行、相应地重新记录伴随记录,以及机械修复入站链接。
|
||||
|
||||
归档后,这三个文件永久冻结,只作为历史背景,不再是当前权威依据。不得因为包重命名、行为变化、翻译标准、格式规则、出站链接失效或后续文档契约而更新归档文件。活跃文档可以有意链接到归档 Agent Note,也可以把该链接重定向到当前权威依据,或直接删除。仓库门禁因此会校验指向归档文件的链接,但绝不把归档文件作为链接源来校验。
|
||||
|
||||
[`verify-archived-agent-notes`](../../../../scripts/verify-archived-agent-notes.ts) 负责维护冻结边界。它只接受封闭集合中的 Agent Note 类别,要求三个配对文件完整、状态为 implemented,且归档日期有效并互相匹配;它还会用双方当前的 Git blob hash 校验伴随记录,并在仅追加的 manifest 中按路径和 SHA-256 内容 hash 封存每项产物。其 `--write` 模式会先证明每条现有封存记录对应的内容都未改变,再仅追加新归档的产物。拉取请求 CI 会提供可信的基准 SHA,并在运行校验器前检出完整历史,因此复用运行器上的浅克隆检出无法漏掉基线 manifest。普通的 Agent Note 格式、翻译配对、换行、Markdown 链接、包路径、Mermaid、文档 TypeScript 和类型等价门禁都排除归档源文件,因此这些门禁持续演进的标准不会产生修改历史记录的压力。
|
||||
[`verify-archived-agent-notes`](../../../../scripts/verify-archived-agent-notes.ts) 负责维护冻结边界。它只接受封闭集合中的 Agent Note 类别,要求三个配对文件完整、状态为 implemented,且归档日期有效并互相匹配;它还会用双方当前的 Git blob hash 校验伴随记录,并在仅追加的 manifest(元数据清单) 中按路径和 SHA-256 内容 hash 封存每项产物。其 `--write` 模式会先证明每条现有封存记录对应的内容都未改变,再仅追加新归档的产物。拉取请求 CI 会提供可信的基准 SHA,并在运行校验器前检出完整历史,因此复用运行器上的浅克隆检出无法漏掉基线 manifest。普通的 Agent Note 格式、翻译配对、换行、Markdown 链接、包路径、Mermaid、文档 TypeScript 和类型等价门禁都排除归档源文件,因此这些门禁持续演进的标准不会产生修改历史记录的压力。
|
||||
|
||||
[`dsh-archive-agent-notes`](../../../skills/dsh-archive-agent-notes/SKILL.md) 工作流负责分类判断。它要求逐份 Agent Note 做语义审计,使用代码和当前文档识别现行权威依据,仅把字数作为初步筛选手段,收录经过校准的保留、归档和删除示例,并报告真正处于边界的结果,以供评审。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md
|
||||
2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.md: 006360cedfd4d1e2c2b67ede98062a375e316f47
|
||||
2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md: d511e7a2b89788fe8addd2cc47634742c7a87fe4
|
||||
2026-07-26-pnpm-action-setup-for-symmetric-ci-caching.zh.md: 2258a1fe5f91b27b8a3b636c7f3ad26f502a98df
|
||||
|
||||
@@ -13,22 +13,22 @@ Status: implemented
|
||||
`pnpm/action-setup@v4` 是 CI 中提供 pnpm 的唯一机制:没有任何工作流运行 `corepack enable`。根目录的 `@yarnpkg/cli-dist` 开发依赖另行提供 generated-project e2e 所运行的现代 Yarn CLI(命令行界面);因此,用于包管理器覆盖率的 Yarn 不会沿用 runner 镜像里的 Yarn Classic。缓存仍是叠加在 pnpm 提供机制上的按作业政策,保持三种刻意的形态:
|
||||
|
||||
- **对称缓存**(既恢复也保存):带 `cache: pnpm` 的 `actions/setup-node`——`e2e.yml`、`docs-pages.yml`、`pi-ai-provider-e2e.yml`、`build-exe-for-python-sdk.yml`,以及 `ci.yml` 的 node-compat 与两个 benchmark 作业。larger-runner benchmark 通过条件化的 `cache:` 输入让 store 缓存仅限 Linux;consolidated benchmark 在两个平台上都启用缓存。
|
||||
- **只恢复不上传/生产者配对**(手写的 `actions/cache` 步骤):企业 runner 上的三个 PR(Pull Request)作业与基于 Wine 的拉取请求 Windows 作业只恢复不保存,把缓存压缩/上传挡在它们的延迟敏感路径之外——这种不对称是 `setup-node` 的缓存无法表达的。每个作业都在 action 可替换的安装目录之外配置 store,并解析该路径,从而与 master 推送触发的 serial-linux 生产者所用的路径和精确键匹配;企业作业在自托管故障切换期间跳过恢复,因为该 VM 的持久 store 已能直接提供热安装。
|
||||
- **只恢复不上传/生产者配对**(手写的 `actions/cache` 步骤):企业 runner 上的三个 PR(Pull Request)作业与基于 Wine 的拉取请求 Windows 作业只恢复不保存,把缓存压缩/上传挡在它们的延迟敏感路径之外——这种不对称是 `setup-node` 的缓存无法表达的。每个作业都在 action 可替换的安装目录之外配置 store,并解析该路径,从而与 master 推送触发的 serial-linux 生产者所用的路径和精确键匹配;企业作业在自托管故障切换期间跳过恢复,因为该 VM 的持久 store 已经预热。
|
||||
- **无缓存或持久化**(不使用 store 缓存 action):原生 serial-windows 和 serial-macos 加上 `sandbox.yml` 从冷 store 或 runner 本地 store 安装。自托管热备与故障切换作业复用其 VM 的持久 pnpm store,不传输托管缓存归档。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
- **保留手写步骤。** 它们能用,但那是会各自漂移的设置样板副本,而且对 corepack 的依赖是已知的未来失效点。
|
||||
- **把企业作业的缓存也转换成 `cache: pnpm`。** 否决:只恢复不上传的不对称是 `ci.yml` 注释中有记录的延迟决策;为统一工具而抹掉它,属于颠倒优先级。
|
||||
- **转换 serial-linux 的 store 缓存。** 实现期间否决:原提案曾把 serial-linux 计入对称设置,但其缓存步骤是企业作业只恢复不上传配对中的生产者一半——把它改成 `setup-node` 的键格式,等于换条路径做了企业作业的转换。
|
||||
- **只转换带缓存的工作流,留下其余 `corepack enable` 站点。** 评审跟进时否决:提供 pnpm 与缓存是可分离的关注点,在无缓存作业里留下 corepack 只会保留未来失效点和两套并存的提供方式,毫无收益。
|
||||
- **转换 serial-linux 的 store 缓存。** 实现期间否决:原提案曾把 serial-linux 计入对称设置,但其缓存步骤是企业作业只恢复不上传配对中的生产者一端——把它改成 `setup-node` 的键格式,等于换条路径做了企业作业的转换。
|
||||
- **只转换带缓存的工作流,留下其余出现 `corepack enable` 的位置。** 评审跟进时否决:提供 pnpm 与缓存是可分离的关注点,在无缓存作业里留下 corepack 只会保留未来失效点和两套并存的提供方式,毫无收益。
|
||||
- **依赖 runner 镜像自带的 Yarn。** 否决:Corepack 移除后,托管镜像提供的是 Yarn 1.22,而 generated-project e2e 要求 Yarn 2 或更高版本。锁定版本的根开发依赖让该项覆盖率不再受 runner 镜像内容影响。
|
||||
- **用一个组合 action 包装 action-setup + setup-node。** 暂不采纳:剩余的按作业差异(node 版本矩阵、按平台的条件缓存、只恢复不上传配对)是刻意的政策而非样板——包装层要么长出镜像这些差异的输入,要么抹平一处真实的不对称,而两行的组合已接近下限。
|
||||
- **用一个组合 action 包装 action-setup + setup-node。** 暂不采纳:剩余的按作业差异(node 版本矩阵、按平台的条件缓存、只恢复不上传配对)是刻意的政策而非样板——包装层要么不得不增加与这些差异一一对应的输入,要么抹平一处真实的不对称,而两行的组合已接近下限。
|
||||
|
||||
## 后果
|
||||
|
||||
- corepack 依赖已从 CI 中彻底消失;pnpm 在所有工作流中都经由 pnpm 团队的官方 action 提供,版本锁定继续单一来源于 `package.json` 的 `packageManager` 字段。
|
||||
- generated-project e2e 运行根目录锁定的 Yarn 4 CLI,既不再沿用 runner 镜像中的 Yarn 版本,也不会因此悄然跳过。
|
||||
- 已转换泳道的缓存键格式变更了一次;各跑一次冷运行重建缓存后,命中率与旧步骤持平。内建缓存键涵盖平台、架构与锁文件哈希,但不含 Node 版本,因此 node-compat 矩阵的各条腿共享同一条 store 缓存记录——这是安全的,因为 pnpm store 与 Node 版本无关。
|
||||
- `setup-node` 内建的 pnpm 缓存只按精确键恢复,没有 `restore-keys` 前缀回退:`pnpm-lock.yaml` 一旦变更,已转换泳道会从冷 store 起步,而不是从上一条缓存记录播种。
|
||||
- `setup-node` 内建的 pnpm 缓存只按精确键恢复,没有 `restore-keys` 前缀回退:`pnpm-lock.yaml` 一旦变更,已转换泳道会从冷 store 起步,而不是利用上一条缓存记录预填充。
|
||||
- `pnpm/action-setup` 每次运行都会删除其安装目录,并把默认 store 放在由此产生的 `PNPM_HOME` 下。因此,需要缓存配对或自托管持久化的 Linux 作业会把 `PNPM_CONFIG_STORE_DIR` 设为 `$HOME/.local/share/pnpm/store`,置于 action 目录之外;只恢复不上传的作业与 serial-linux 会解析并共享这一稳定路径及精确键。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-26-web-syntax-highlighting-shiki.md
|
||||
2026-07-26-web-syntax-highlighting-shiki.md: 48a1e4c43f19693f90906f210f0ed85db3f31687
|
||||
2026-07-26-web-syntax-highlighting-shiki.zh.md: 780b66a309c841c542f873f376226b8da454e050
|
||||
2026-07-26-web-syntax-highlighting-shiki.zh.md: 31fb3d916c7cb1e928bc37eca86fb11e21374b16
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Agent Note:web client 的语法高亮——同步细粒度的 shiki
|
||||
# Agent Note: web client 的语法高亮——同步细粒度的 shiki
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-07-26-web-syntax-highlighting-shiki.md) | 中文
|
||||
|
||||
> 范围:web client 唯一的一套语法高亮体系——依赖裁决、单例形态、token 表契约与各消费表面。本篇是 Code Mode UI 堆叠 PR(Pull Request)链的第五个 PR;[chat 子调用行 Agent Note](../feature/2026-07-26-code-mode-chat-subcall-rows.md)交付了 `run_code` 程序正文,而本体系存在的意义正是让它可读。样式的基本规则归 [Web 样式体系裁决](2026-07-19-web-styling-system.md)所有。
|
||||
> 范围:web client 唯一的一套语法高亮体系——依赖裁决、单例形态、token 表契约与各消费表面。本篇是 Code Mode UI 堆叠 PR(Pull Request)链的第五个 PR;[chat 子调用行 Agent Note](../feature/2026-07-26-code-mode-chat-subcall-rows.md)交付了 `run_code` 程序正文,而本体系存在的意义正是让它可读。样式的基本规则由 [Web 样式体系裁决](2026-07-19-web-styling-system.md)规定。
|
||||
|
||||
## 问题
|
||||
|
||||
@@ -15,8 +15,8 @@ client 过去把每一处代码表面——assistant 正文里的 markdown 围
|
||||
**采用同步细粒度形态的 shiki,作为 `ui-primitives` 里的一个单例,主题化完全经由 CSS 自定义属性完成。**
|
||||
|
||||
- **依赖**:`shiki/core` + `@shikijs/langs`,经 `createHighlighterCoreSync` 搭配 `createJavaScriptRegexEngine({ forgiving: true })` 组装——不带 oniguruma WASM、没有异步初始化、对 bundle 友好。语法(grammar)白名单:`typescript`(内嵌 JS)、`shellscript`、`json`——即 harness 实际会渲染的那几种语言;其余一律回退到几何完全一致的纯文本块,绝不报错。先例:VitePress 站点已经通过 shiki 渲染全部文档代码;而在 TypeScript(正是此处要紧的载荷)上,TextMate 语法实质性优于正则高亮器。
|
||||
- **单例**:`ui-primitives/src/markdown/highlight.ts` 为每个 document 创建一个 `HighlighterCore`,并公开 `highlightToHtml(code, lang)`(undefined 即渲染为纯文本)。引擎加语法的构建是一次约 120-175ms 的长任务,因此模块在插件启动时用延迟任务预热单例(惰性路径保留为正确性兜底),把这笔开销挪出渲染路径——否则流式 finalize 交换的那一刻会卡顿。别名表用 `Map` 而非对象:fence 信息串由 assistant 撰写,诸如 `constructor` 这样的标签必须落空,而不是解析到继承属性并让 shiki 崩溃。共享的 `CodeBlock` 组件同时拥有两条分支;其 shiki 分支经 `dangerouslySetInnerHTML` 注入生成的 span 树——此用法获准,因为 shiki 输出的是从代码文本计算出的静态 span 树(不流经任何用户 HTML,没有脚本或事件处理器),这正是 shiki 自身文档载明的消费路径。
|
||||
- **主题化**:shiki 的 `createCssVariablesTheme` 让每一种 token 颜色都经由 `--shiki-*` 自定义属性路由;取值本身住在新增的 `ui-theme/styles/shiki.css` token 表里(亮色在 `:root`、暗色在 `body[data-ds-dark-theme]`——层叠方式与其余每张样式表相同),由壳的 `base.css` 导入链引入。组件 CSS 保持只用 token;任何字面颜色都不进入 JS 或组件样式表。背景/前景以别名指向既有的 markdown 代码块 token,使高亮块与纯文本块彼此一致。
|
||||
- **单例**:`ui-primitives/src/markdown/highlight.ts` 为每个 document 创建一个 `HighlighterCore`,并公开 `highlightToHtml(code, lang)`(undefined 即渲染为纯文本)。引擎加语法的构建是一次约 120-175ms 的长任务,因此模块在插件启动时用延迟任务预热单例(惰性路径保留为正确性兜底),把这笔开销挪出渲染路径——否则流式 finalize 交换的那一刻会卡顿。别名表用 `Map` 而非对象:fence 信息字符串由 assistant 撰写,诸如 `constructor` 这样的标签必须落空,而不是解析到继承属性并让 shiki 崩溃。共享的 `CodeBlock` 组件同时拥有两条分支;其 shiki 分支经 `dangerouslySetInnerHTML` 注入生成的 span 树——此用法获准,因为 shiki 输出的是从代码文本计算出的静态 span 树(不流经任何用户 HTML,没有脚本或事件处理器),这正是 shiki 自身文档载明的消费路径。
|
||||
- **主题化**:shiki 的 `createCssVariablesTheme` 让每一种 token 颜色都经由 `--shiki-*` 自定义属性路由;取值本身住在新增的 `ui-theme/styles/shiki.css` token 表里(亮色在 `:root`、暗色在 `body[data-ds-dark-theme]`——层叠方式与其余每张样式表相同),由壳的 `base.css` 导入链引入。组件 CSS 保持只用 token;任何颜色字面量都不进入 JS 或组件样式表。背景/前景以别名指向既有的 markdown 代码块 token,使高亮块与纯文本块彼此一致。
|
||||
- **表面**:markdown 围栏代码块(`MarkdownText` 的 `pre` 组件把单字符串围栏路由到 `CodeBlock`)、`run_code` 展开后的程序正文(ToolRow 的 code 变体,`lang="typescript"`),以及 details 面板的 Input 参数(`lang="json"`)。工具输出从不做语法高亮——它是任意文本,硬猜一种语法,带来的误高亮会多于帮助;bash 卡片的输出只承载其自身 ANSI 序列声明的颜色,经由[终端卡片](../feature/2026-07-28-web-terminal-card.md)渲染。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-27-dependabot-version-updates.md
|
||||
2026-07-27-dependabot-version-updates.md: 725649652c5b91ba4897d03b548b9aa5c3694c21
|
||||
2026-07-27-dependabot-version-updates.zh.md: 1ab34e76b84c7423be76fa79ae6bb3705a07a76c
|
||||
2026-07-27-dependabot-version-updates.zh.md: 6400ba8ed94bf138fcece90e5d7ff82886d33ed1
|
||||
|
||||
@@ -6,7 +6,7 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
来自包注册表的依赖与 GitHub Actions 依赖都需要定期更新机制。每个新版本一经发布便立即采用,会增加受到遭入侵的版本和早期回归影响的风险;但完全依靠手动更新,又会导致依赖版本差距持续扩大。以源码形式纳入仓库的 Cordis 与各自维护独立锁文件的工作区,也不能不加区分地视为同一棵包(package)树。
|
||||
来自包注册表的依赖与 GitHub Actions 依赖都需要定期更新机制。每个新版本一经发布便立即采用,会增加受到遭入侵的版本和早期回归影响的风险;但完全依靠手动更新,又会导致依赖版本差距持续扩大。以源码形式纳入仓库的 Cordis 与各自维护独立锁文件的工作区,也不能不加区分地视为同一棵包树。
|
||||
|
||||
## 决策
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-27-explicit-change-scope-report.md
|
||||
2026-07-27-explicit-change-scope-report.md: ed09ffc44252e1e571d50537b3471cad8d68d8f9
|
||||
2026-07-27-explicit-change-scope-report.zh.md: e13092395d6708b1ec030d13365853a5eaecba55
|
||||
2026-07-27-explicit-change-scope-report.zh.md: 783f89b9b42f1706da218997a06bfe7c28a0936b
|
||||
|
||||
@@ -32,10 +32,10 @@ Status: implemented
|
||||
|
||||
**报告当前分支与上游,并维护并行的人类可读渲染器。** 调用方在调用前已经验证分支和基准状态,没有消费方使用这些字段,而格式化文字只会重复 JSON schema,并不能提高路径完整性。
|
||||
|
||||
## 结果
|
||||
## 后果
|
||||
|
||||
显式输入仍可能指定错误的基准,但这种错误是可见的:报告中会显示输入引用与解析出的三个 commit ID。调用方需要付出少量成本,在运行该命令前验证实时基准并从远端获取它。
|
||||
|
||||
字符串 schema 有意不表示非 UTF-8 路径字节。含有这类路径的仓库必须先重命名这些路径才能生成报告,以此保持范围精确,而非返回有损结果。
|
||||
|
||||
仓库需要维护一个 Git 拓扑辅助工具及相应的聚焦测试。由此,pre-push 证据选择、代码评审与文档审计可以共享一份确定且只读的已提交及本地变更说明,而不必混入代码托管平台或策略职责。
|
||||
仓库需要维护一个 Git 拓扑辅助工具及相应的聚焦测试。由此,pre-push 证据选择、代码评审与文档审计可以共享一份关于已提交变更和本地变更的确定性只读说明,而不必混入代码托管平台或策略职责。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-27-wine-windows-gates-experiment.md
|
||||
2026-07-27-wine-windows-gates-experiment.md: 640c8e455b1a35ea4ac83454227147b9979316dc
|
||||
2026-07-27-wine-windows-gates-experiment.zh.md: f30e09ca7411ef83d02faf012ce54d6c6c65dff1
|
||||
2026-07-27-wine-windows-gates-experiment.zh.md: 67f59a93d1b1fad98e36e6f9c51bc77abb9e3d07
|
||||
|
||||
@@ -6,42 +6,42 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
Pull request 的 Windows 通道存在的意义是证明两个阻断性 win32 表面——workspace 构建与生产站点——它此前运行在托管 `windows-2025` 上,是必需矩阵中最慢的作业:7–9 分钟,对照 Linux 作业的 1.5–2.5 分钟,因此 Windows VM 的启动、准备与文件系统开销主导了每个 pull request 的关键路径。
|
||||
Pull request 的 Windows 通道旨在验证两个阻断性 win32 表面,即 workspace 构建与生产站点。该通道此前运行在托管 `windows-2025` 上,是必需矩阵中最慢的作业:耗时 7–9 分钟,而 Linux 作业耗时 1.5–2.5 分钟,因此 Windows VM 的启动、准备与文件系统开销主导了每个 pull request 的关键路径。
|
||||
|
||||
实验回答的问题是:一台普通 Linux runner 能否以 Linux 墙钟为阻断表面产出等效的 win32 信号,让 pull request 路径上完全没有 Windows VM?
|
||||
实验回答的问题是:一台普通 Linux runner 能否以 Linux 作业的墙钟时间为这些阻断表面产出等效的 win32 信号,让 pull request 路径上完全没有 Windows VM?
|
||||
|
||||
## 决策
|
||||
|
||||
[ci.yml](../../../../.github/workflows/ci.yml) 中必需的 pull request `windows` 作业(`windows node 24 / wine blocking`)在 `ubuntu-latest` 上通过 Wine 用真实 Windows 二进制运行阻断门禁命令:校验和验证过的 win-x64 Node.js 执行 `tsc -b`、`tsdown` 与 VitePress 生产构建,因此工具链的 win32 分支——反斜杠路径处理、`CreateProcess` 派生语义、`@esbuild/win32-x64` 的 PE 加载、以及 rolldown/rollup 的 MSVC `.node` 插件——都真正执行。master 的 `serial-windows` 作业原封不动:完整的原生内核清单,包括本通道不运行的观察性可移植性门禁,仍在每次 master push 时于真实 `windows-2025` 上执行。
|
||||
|
||||
依赖在 Linux 上原生安装,`supportedArchitectures` 扩展到 win32-x64,使 Windows 平台包物化进同一个 store;通过直接调用各工具的 JavaScript 入口绕开 cmd-shim 层,这正是 `run-gates` 最终派生的那些进程。`nodeLinker: hoisted` 是承重的,不是风格问题:[PR #689](https://github.com/deepseek-harness/deepseek-harness/pull/689) 的独立原型保留了 pnpm 默认的 isolated 布局——包括在 Linux 预取的 store 上忠实地用 Windows pnpm 离线重装——而 Wine 下的 Windows Node 依然无法穿过 isolated 符号链接链解析 `@esbuild/win32-x64` 或加载 koffi 预编译产物,在任何仓库门禁运行前就失败了。扁平的真实文件布局才让门禁变得可达;本通道采纳了 #689 的校验和固定,同时明确放弃其"Windows pnpm 安装依赖树"的目标(安装契约在此仍由 Linux 侧验证)。
|
||||
依赖在 Linux 上原生安装,`supportedArchitectures` 扩展到 win32-x64,使 Windows 平台包物化进同一个 store;通过直接调用各工具的 JavaScript 入口绕开 cmd-shim 层,这正是 `run-gates` 最终派生的那些进程。`nodeLinker: hoisted` 是承重的,不是风格问题:[PR #689](https://github.com/deepseek-harness/deepseek-harness/pull/689) 的独立原型保留了 pnpm 默认的 isolated 布局——包括在 Linux 预取的 store 上忠实地用 Windows pnpm 离线重装——而 Wine 下的 Windows Node 依然无法穿过 isolated 符号链接链解析 `@esbuild/win32-x64` 或加载 koffi 预编译产物,在任何仓库门禁运行前就失败了。扁平的真实文件布局才让门禁变得可达;本通道采纳了 #689 的校验和固定,同时明确放弃其「Windows pnpm 安装依赖树」的目标(安装契约在此仍由 Linux 侧验证)。
|
||||
|
||||
该通道靠四个杠杆保持 Linux CI 作业的墙钟:master 刷新的 pnpm store 缓存(只恢复,与 Linux 作业同键)、Wine 供给(apt 安装、Windows Node 下载、`wineboot`)与 `pnpm install` 并发运行、两个阻断表面并发运行——与 `run-gates` 在原生 Windows 上给它们的形状相同——以及按 runner 镜像为键的 apt 归档缓存,由 master 的 `wine apt cache` 作业播种,使每个 pull request 都能从默认分支作用域恢复。
|
||||
该通道靠四个杠杆把墙钟时间保持在与 Linux CI 作业相当的水平:master 刷新的 pnpm store 缓存(只恢复,与 Linux 作业同键)、Wine 供给(apt 安装、Windows Node 下载、`wineboot`)与 `pnpm install` 并发运行、两个阻断表面并发运行——与 `run-gates` 在原生 Windows 上给它们的形状相同——以及按 runner 镜像为键的 apt 归档缓存,由 master 的 `wine apt cache` 作业播种,使每个 pull request 都能从默认分支作用域恢复。
|
||||
|
||||
门禁逻辑集中在一个脚本里,[scripts/wine-windows-gates.sh](../../../../scripts/wine-windows-gates.sh):ci.yml 作业只供给 runner 状态(缓存、apt Wine)然后调用它,可选的本地门禁 `pnpm run check:windows-wine` 在装有 Wine 的开发机上运行同一个脚本——单一实现,因此本地复现红色 CI 通道不需要在环境之间做任何转译。该本地门禁是诊断工具而非例行检查:仅在排查已知的 Windows 相关失败时运行;日常 win32 信号归 CI 所有,[dsh-pre-push-checks](../../../skills/dsh-pre-push-checks/SKILL.md) 也从不选择它。脚本从不改动工作树:把被跟踪加未跟踪未忽略的文件快照进一个临时目录,只对快照施加 Wine 特有的 pnpm 覆盖,并在那里对着共享 store 安装;Wine prefix 与校验和验证过的 Windows Node zip 持久存放在 `.cache/wine-windows/` 下,本地重跑跳过供给,nodejs.org 不可达时回退到最新的已缓存 zip。
|
||||
门禁逻辑集中在一个脚本里,[scripts/wine-windows-gates.sh](../../../../scripts/wine-windows-gates.sh):ci.yml 作业只供给 runner 状态(缓存、apt Wine)然后调用它,可选的本地门禁 `pnpm run check:windows-wine` 在装有 Wine 的开发机上运行同一个脚本——单一实现,因此本地复现红色 CI 通道不需要在环境之间做任何转译。该本地门禁是诊断工具而非例行检查:仅在排查已知的 Windows 相关失败时运行;日常 win32 信号归 CI 所有,[dsh-pre-push-checks](../../../skills/dsh-pre-push-checks/SKILL.md) 也从不选择它。脚本从不改动工作树:把已跟踪文件和未跟踪但未忽略的文件快照进一个临时目录,只对快照施加 Wine 特有的 pnpm 覆盖,并在那里对着共享 store 安装;Wine prefix 与校验和验证过的 Windows Node zip 持久存放在 `.cache/wine-windows/` 下,本地重跑跳过供给,nodejs.org 不可达时回退到最新的已缓存 zip。
|
||||
|
||||
五条环境约束塑造了 CI 与本地执行,每条都以一次红色运行被发现:Ubuntu 的 `wine64` 包本身不往 PATH 放任何东西(要装 `wine` 调度器);Wine 下的 Node 无法把 stdio 接到调用方的管道上(引导期 `Socket open EBADF`——所有调用都经文件中转 stdio);Wine 不对 pnpm isolated 布局的 Unix 符号链接做 realpath(即上文的 hoisted 布局);macOS Wine 也会把 hoisted workspace 链接暴露为普通目录,因此 client 测试聚合会纳入每个包自己的 CSS 模块声明,而不依赖 project-reference realpath;Wine 无法创建 Windows 符号链接(VitePress 的 `linkVue` 报 `ENOTSUP`——`vue` 链接在门禁前由宿主侧铺好)。
|
||||
|
||||
## 实测结果
|
||||
|
||||
2026-07-27 实测,热缓存,pull request 触发,标准 2 核 `ubuntu-latest`:端到端 2 分 46 秒——准备与缓存恢复约 17 秒,并发安装+供给 33 秒,并发门禁 110 秒——对照 Linux CI 作业的 1.5–2.5 分钟与被替换的 `windows-2025` 作业的 7–9 分钟。冷缓存约多付一分钟。实验期间定义过 8 核基准腿,但它从未离开受限 `dsh-ubuntu-*` 池的队列;标准 runner 的数字已达标,故不使用更大的机器。
|
||||
2026-07-27 实测,热缓存,pull request 触发,标准 2 核 `ubuntu-latest`:端到端 2 分 46 秒——准备与缓存恢复约 17 秒,并发安装+供给 33 秒,并发门禁 110 秒——对照 Linux CI 作业的 1.5–2.5 分钟与被替换的 `windows-2025` 作业的 7–9 分钟。冷缓存约多付一分钟。实验期间定义过 8 核基准测试作业,但它从未离开受限 `dsh-ubuntu-*` 池的队列;标准 runner 的数字已达标,故不使用更大的机器。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**保留托管 `windows-2025` 的 pull request 作业(现状)。** 其信号没有问题,问题只在延迟:为两条构建命令花 7–9 分钟,是必需矩阵中最慢的作业。它作为 master 串行参照存续——在那里完整性比延迟更重要。
|
||||
|
||||
**在 Linux runner 内用 QEMU/KVM 跑完整 Windows 客户机。** 真实 NT 内核,保真度完整,包括大小写不敏感的 NTFS 与 ConPTY——但首个门禁运行前要花数十分钟下载镜像并做无人值守安装(兄弟实验分支 `exp/kvm-windows-ci` 实测端到端 40 分 19 秒)。只有配上会挤压 Actions 缓存预算的磁盘镜像缓存才可晋升。
|
||||
**在 Linux runner 内用 QEMU/KVM 跑完整 Windows 客户机。** 真实 NT 内核,保真度完整,包括大小写不敏感的 NTFS 与 ConPTY——但首个门禁运行前要花数十分钟下载镜像并做无人值守安装(兄弟实验分支 `exp/kvm-windows-ci` 实测端到端 40 分 19 秒)。只有配上会挤压 Actions 缓存预算的磁盘镜像缓存才可投入使用。
|
||||
|
||||
**在 Wine 下由 Windows pnpm 执行安装([PR #689](https://github.com/deepseek-harness/deepseek-harness/pull/689))。** 同一想法的更高保真度变体:把 MinGit 与 pnpm 放进 prefix,用 Linux 预取填充 store,再由 Windows Node 运行 `pnpm install --offline`,让安装契约本身以 win32 身份执行。它到达了安装但没到达门禁——Wine 的网络无法直接访问 registry,且 isolated 的 `node_modules` 布局即便在干净的离线安装后也挫败了 Windows 平台包的解析。本通道用掉这份保真度(hoisted 布局、Linux 侧安装)来换取门禁可达;两份记录是同一裁决互补的两半。
|
||||
**在 Wine 下由 Windows pnpm 执行安装([PR #689](https://github.com/deepseek-harness/deepseek-harness/pull/689))。** 同一想法的更高保真度变体:把 MinGit 与 pnpm 放进 prefix,用 Linux 预取填充 store,再由 Windows Node 运行 `pnpm install --offline`,让安装契约本身以 win32 身份执行。它到达了安装但没到达门禁——Wine 的网络无法直接访问 registry,且 isolated 的 `node_modules` 布局即便在干净的离线安装后也挫败了 Windows 平台包的解析。本通道牺牲这份保真度(hoisted 布局、Linux 侧安装)来换取门禁可达;两份记录是同一裁决互补的两半。
|
||||
|
||||
**Linux 上的文件系统语义通道(casefold ext4、文件名 lint)。** 以近零成本捕获最高频的 Windows 破坏类别,但对 win32 二进制什么也证明不了。作为兄弟实验分支 `exp/casefold-windows-ci` 探索;与本通道互补而非竞争。
|
||||
**Linux 上的文件系统语义通道(casefold ext4、文件名 lint)。** 以近零成本捕获最高频的 Windows 故障类别,但对 win32 二进制什么也证明不了。作为兄弟实验分支 `exp/casefold-windows-ci` 探索;与本通道互补而非竞争。
|
||||
|
||||
**Windows 容器。** 不可行:Windows 容器要求 Windows 宿主内核;托管 Linux runner 无法运行。
|
||||
|
||||
**砍掉 Windows 通道。** 已否决——win32 是一等产品目标:基于 koffi 的 DACL 与持久命名空间模块、基于 ConPTY 的 PTY 会话、以及 Windows 路径策略都随 `packages/` 交付。
|
||||
|
||||
## 结果
|
||||
## 后果
|
||||
|
||||
每个 pull request 的 Windows 裁决现在以 Linux 作业的时间在免费标准容量上到达,pull request 关键路径上不再有任何 Windows VM 分配;`all checks passed` 消费的仍是原来的 `windows` 作业 id。
|
||||
每个 pull request 的 Windows 裁决现在都能在 Linux 作业的耗时范围内、利用免费的标准 runner 容量得出,pull request 关键路径上不再有任何 Windows VM 分配;`all checks passed` 消费的仍是原来的 `windows` 作业 id。
|
||||
|
||||
这笔交易的代价:Wine 在大小写敏感的 ext4 之上重实现 Win32——NTFS 大小写不敏感、真实 DACL、ConPTY 与崩溃持久性语义在此都未被证明,且观察性可移植性清单(duplication、publint、node-next 类型、win32 上的构建包不变量)完全不再于 pull request 上运行。master 的 `serial-windows` 参照拥有这一切:Wine 绿灯的 pull request 仍可能在原生内核的 master 运行上失败,该失败模式被接受为合并后处理。该通道还把 Wine 特有的分歧继承为永久的作业结构——文件中转的 stdio、宿主侧的 `vue` 链接、hoisted 布局——因此未来依赖 isolated 布局语义或进程内符号链接创建的工具链变更会先在这里以 Wine 失败而非产品失败的形式浮现,分诊必须如此归类。若 Wine 红灯在无产品原因的情况下反复出现,记录在案的退路是把 `windows` 作业还原为 git 历史中保存的 Wine 之前的 `windows-2025` 定义。
|
||||
这笔交易的代价:Wine 在大小写敏感的 ext4 之上重实现 Win32——NTFS 大小写不敏感、真实 DACL、ConPTY 与崩溃持久性语义在此都未被证明,且观察性可移植性清单(duplication、publint、node-next 类型、win32 上已构建包的不变量)完全不再于 pull request 上运行。这一切均由 master 的 `serial-windows` 参照负责验证:Wine 绿灯的 pull request 仍可能在原生内核的 master 运行上失败,项目接受这种失败可能在合并后才出现。该通道还将 Wine 特有的差异固化为永久的作业结构——文件中转的 stdio、宿主侧的 `vue` 链接、hoisted 布局——因此未来依赖 isolated 布局语义或进程内符号链接创建的工具链变更会先在这里以 Wine 失败而非产品失败的形式浮现,分诊必须如此归类。若 Wine 红灯在无产品原因的情况下反复出现,记录在案的退路是把 `windows` 作业还原为 git 历史中保存的 Wine 之前的 `windows-2025` 定义。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md
|
||||
2026-07-27-worktree-local-lefthook.md: 75dfd47087356c34005ec4673e174e451a72c660
|
||||
2026-07-27-worktree-local-lefthook.zh.md: bc4902769561c3d33d2101de55e28e70d114f39b
|
||||
2026-07-27-worktree-local-lefthook.zh.md: 42d4d89e375dfd7b28298d89e82c5e9e74908062
|
||||
|
||||
@@ -28,7 +28,7 @@ Lefthook 生成的钩子会优先使用安装时从对应 worktree 记录的绝
|
||||
|
||||
**构建通用的钩子管理器串联层。** 执行顺序、参数转发、失败语义和升级都会成为仓库自行负责的行为,却与 Lefthook 隔离无关。因此,安装程序会拒绝 worktree 专属的自定义路径,只将范围更窄的继承路径覆盖设为显式操作。
|
||||
|
||||
**将特定 CI 提供商的凭据 include 路径加入白名单。** CI 不使用贡献者钩子,因此路径豁免会使安装程序的安全性耦合于提供商的检出目录内部结构,并削弱贡献者安装时的严格验证。CI 无操作方案无需任何豁免即可避免修改仓库。
|
||||
**将特定 CI 提供商的凭据 include 路径加入白名单。**CI 不使用贡献者钩子,因此路径豁免会使安装程序的安全性耦合于 CI 提供商检出流程的内部实现,并削弱贡献者安装时的严格验证。在 CI 中直接跳过操作,无需任何豁免即可避免修改仓库。
|
||||
|
||||
**停止自动安装钩子。** 手动设置可以避免共享写入,却会使仓库中低成本的提交与推送检查意外变成可选项,短期存在、由 agent(智能体)使用的 worktree 尤其容易受到影响。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-29-oxlint-linter.md
|
||||
2026-07-29-oxlint-linter.md: 41a50a9d08819809f954aa99007081f270692f38
|
||||
2026-07-29-oxlint-linter.zh.md: 1ad72a00cb921ed688363583d56634f52b355b4e
|
||||
2026-07-29-oxlint-linter.zh.md: 85d053af867353ff1b4c53822013f55660c89cca
|
||||
|
||||
@@ -12,7 +12,7 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
根目录的 [`.oxlintrc.json`](../../../../.oxlintrc.json) 是仓库 lint 配置的权威来源。`lint` 包(package)脚本、门禁调度器、CI 和 lefthook 通过 [`scripts/run-oxlint.ts`](../../../../scripts/run-oxlint.ts) 调用 Oxlint,进行全仓库、类型感知或暂存验证。`lint:fix` 脚本和 lefthook 先调用仅用于格式化的 [`eslint.format.config.mjs`](../../../../eslint.format.config.mjs),再运行 Oxlint。直接的 `eslint` 和 `@typescript-eslint/parser` 开发依赖仅用于这次不加载项目的格式化流程;其精确版本锁定经过测试的解析器与修复器配对,该配置不包含正确性规则或类型感知规则。
|
||||
根目录的 [`.oxlintrc.json`](../../../../.oxlintrc.json) 是仓库 lint 配置的权威来源。`lint` 包脚本、门禁调度器、CI 和 lefthook 通过 [`scripts/run-oxlint.ts`](../../../../scripts/run-oxlint.ts) 调用 Oxlint,进行全仓库、类型感知或暂存验证。`lint:fix` 脚本和 lefthook 先调用仅用于格式化的 [`eslint.format.config.mjs`](../../../../eslint.format.config.mjs),再运行 Oxlint。直接的 `eslint` 和 `@typescript-eslint/parser` 开发依赖仅用于这次不加载项目的格式化流程;其精确版本锁定经过测试的解析器与修复器配对,该配置不包含正确性规则或类型感知规则。
|
||||
|
||||
`options.typeAware` 启用 `oxlint-tsgolint`。其后端按文件发现 TypeScript 项目:包源码使用各自的包项目,host 测试、示例和网站使用 `tsconfig.host.json`,client 测试及 `scripts/client-bundle-purity.spec.ts` 使用 `tsconfig.client.json`。不含程序的根解决方案绝不会被扁平化。Oxlint 的 `--tsconfig` 覆盖项会影响导入解析,但类型感知 lint 会忽略它,因此本仓库不设置该选项。该配置显式载入迁移后的严格类型检查规则和仓库覆盖配置,而不启用内容可能发生变化的 Oxlint 宽泛类别。`typescript/no-unnecessary-condition` 仍从 Oxlint 的 nursery 规则集中启用,因为它在迁移前就是仓库强制执行的规则。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-30-cordis-config-source-plane-resolution-gate.md
|
||||
2026-07-30-cordis-config-source-plane-resolution-gate.md: f9070d39559948ef27f96df5afccd7c4e076f131
|
||||
2026-07-30-cordis-config-source-plane-resolution-gate.zh.md: fac6c4047d334d7dd0685aa270234fee3d15dba8
|
||||
2026-07-30-cordis-config-source-plane-resolution-gate.zh.md: eb8fa6fa77f2b363214c5a67af0cd9ce3409b721
|
||||
|
||||
@@ -6,7 +6,7 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
`apps/cli/config/tui.cordis.yml` 新增了 `@deepseek-ai/dsh-tui/prompt` 配置项,却没有对应的 tsconfig `paths` 映射。通用的 `@deepseek-ai/dsh-*` 通配符会把 `tui/prompt` 整体代入其 `<group>/*/src` 候选路径,而这些路径全都不存在,因此 [tsx 源码启动](../architecture/2026-07-29-dsh-source-launch-tsx-esm.md)会回退到包(package)的 `exports`,解析出产物面文件 `lib/prompt.js`。任何带有已构建 `lib/` 的环境(开发者目录树运行 `pnpm build` 后)都能正常启动,而 e2e 工作流以 `lib` 模式(`DSH_EXAMPLE_MODE=lib`,构建产物 bin 在普通 Node 下运行)执行无密钥 TUI PTY 冒烟测试,因此 CI 根本不会经过源码启动向量——与此同时,所有干净检出环境中的 `pnpm dsh` 都会在启动时失败,并报错 `plugin(s) failed to load: @deepseek-ai/dsh-tui/prompt`。当时没有门禁检查源码面,因此该故障未被发现便进入发布版本,仅在新的 worktree 中暴露。
|
||||
`apps/cli/config/tui.cordis.yml` 新增了 `@deepseek-ai/dsh-tui/prompt` 配置项,却没有对应的 tsconfig `paths` 映射。通用的 `@deepseek-ai/dsh-*` 通配符会把 `tui/prompt` 整体代入其 `<group>/*/src` 候选路径,而这些路径全都不存在,因此 [tsx 源码启动](../architecture/2026-07-29-dsh-source-launch-tsx-esm.md)会回退到包的 `exports`,解析出产物面文件 `lib/prompt.js`。任何带有已构建 `lib/` 的环境(开发者目录树运行 `pnpm build` 后)都能正常启动,而 e2e 工作流以 `lib` 模式(`DSH_EXAMPLE_MODE=lib`,构建产物 bin 在普通 Node 下运行)执行无密钥 TUI PTY 冒烟测试,因此 CI 根本不会经过源码启动向量——与此同时,所有干净检出环境中的 `pnpm dsh` 都会在启动时失败,并报错 `plugin(s) failed to load: @deepseek-ai/dsh-tui/prompt`。当时没有门禁检查源码面,因此该故障未被发现便进入发布版本,仅在新的 worktree 中暴露。
|
||||
|
||||
## 决策
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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-06-coverage-uncovered-locations.md
|
||||
2026-08-06-coverage-uncovered-locations.md: 27c466e7ae505a2d318f8b21d442c31619610f59
|
||||
2026-08-06-coverage-uncovered-locations.zh.md: 93eeb863e51679c60d9e0ccfc742156c098aeca4
|
||||
@@ -0,0 +1,42 @@
|
||||
# Agent Note: Exact uncovered locations on coverage failure
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-08-06-coverage-uncovered-locations.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
When the per-file 100% coverage gate fails, vitest emits only file-level error lines (`ERROR: Coverage for lines (…) does not meet global threshold (100%) for <file>`) — you learn which file fell short, not which lines. The built-in `text` report does have an Uncovered Line #s column, but it is one giant table over hundreds of files repo-wide: the column truncates at the table width, carries line numbers but no column numbers, does not distinguish statements from branches from functions, and passing files occupy rows all the same. The net effect is that a red coverage run on CI is not directly actionable; the only way to locate the specific gap is to rerun the html report locally.
|
||||
|
||||
## Decision
|
||||
|
||||
`scripts/coverage-uncovered-locations.cjs` is a custom istanbul reporter (a `ReportBase` subclass): for every file below 100%, it emits one self-contained single-line record per uncovered statement, untaken branch path, and uncalled function — `<path>:<line>:<col> uncovered <kind> …` — directly clickable in terminals and CI logs, and easy to grep. When every file passes, it prints nothing. istanbul report generation runs before threshold validation, so the records land exactly above the existing ERROR lines.
|
||||
|
||||
The wiring is a single point: the coverage block in the root `vitest.config.ts` is the repo's only coverage configuration, shared by the CI lane (`run-gates ci-coverage`), local `test:coverage`, and focused runs (`--coverage.include`). The reporter joins both the CI and local reporter arrays by absolute path (`fileURLToPath`) — istanbul-reports' `create()` falls back to a bare `require(name)` for non-built-in names, and a relative path would resolve against istanbul's own package directory.
|
||||
|
||||
Output conventions:
|
||||
|
||||
- istanbul's 0-based column numbers are converted to 1-based (the convention editors and terminal links expect).
|
||||
- v8 reports `end.column = Infinity` for whole-line statements: a span crossing lines degrades to a `(to <line>)` suffix carrying only the line number, and a single-line span omits the suffix.
|
||||
- An implicit branch arm (such as a missing else) may carry no location; the reporter falls back to the branch's own span so the record stays clickable; branch records are annotated with the branch type and `path k/n`.
|
||||
- Records within a file are sorted by line, then column; there is no cap on the count.
|
||||
|
||||
Two companion changes: the root `package.json` adds `istanbul-lib-report` as a devDependency (under pnpm's strict layout, `scripts/` cannot reach nested dependencies); the root workspace's entry/project globs in `knip.json` gain `scripts/**/*.cjs`, making the file and its dependencies visible to the hygiene gate.
|
||||
|
||||
CJS is a forced shape, and a justified exception to the ESM-everywhere discipline: istanbul loads custom reporters via a bare `require()` outside the tsx/Vite pipeline, where TypeScript cannot participate; the namespace object `require(esm)` returns also fails its `new Cons(cfg)` construction — CommonJS is the only reliable shape.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Rely on the built-in `text` report's Uncovered Line #s column.** This is precisely the problem as found: one repo-wide table, column-width truncation, line numbers only, no kind distinction, passing files in the same column — not actionable in CI logs.
|
||||
- **Add a `json` reporter plus a separate wrapper script that reads `coverage-final.json` for post-processing after a failure.** Feasible in pure ESM/TS, but the wrapper would have to wrap both entry points — `package.json`'s `test:coverage` and the run-gates gate — changing their command shapes; the custom-reporter route touches one piece of configuration and takes effect at both entry points automatically.
|
||||
- **Write the reporter in TypeScript/ESM.** istanbul's loading mechanism (a bare `require` outside the pipeline) rules this out, as above; swapping out the loading mechanism for the sake of one report file is out of proportion.
|
||||
|
||||
## Verification
|
||||
|
||||
Local matrix: with a deliberately induced failure, all three record kinds appear and their locations match the planted gaps; a mixed run emits records only for the failing files (files at 100% within the same run stay silent); an all-green run produces zero output and exit code 0. CI evidence: after temporarily planting one unreachable statement/branch/function in `clampTimeout`, the coverage lane — under the isolated condition of all tests passing (632 files / 10326 cases) with only the threshold failing — printed the 4 records above the ERROR lines (evidence links in the PR #1716 description); the demonstration commit was removed from the branch before merge.
|
||||
|
||||
## Consequences
|
||||
|
||||
- A red coverage run is self-sufficient: the log gives exact line and column numbers plus the kind of each gap, and rerunning the html report locally to pinpoint it is no longer needed.
|
||||
- The cost is one CJS-file discipline exception and one root devDependency; all-green runs produce zero output and add no log noise.
|
||||
- A file with zero coverage yields output on the order of its statement count (deliberately uncapped): the gate demands zero gaps, so the full listing is the action list, and vitest's own ERROR lines already provide the per-file summary as a backstop.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Agent Note: 覆盖率未达标时输出精确未覆盖位置
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-08-06-coverage-uncovered-locations.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
per-file 100% 覆盖率门禁失败时,vitest 只输出文件级错误行(`ERROR: Coverage for lines (…) does not meet global threshold (100%) for <file>`)——知道哪个文件没达标,不知道差在哪几行。内置 `text` 报表虽有 Uncovered Line #s 列,但它是全仓几百个文件的大表:该列按表宽截断、只有行号没有列号、不区分语句/分支/函数,且达标文件同样占行。结果是 CI 上的覆盖率红报不可直接行动,定位具体缺口只能本地重跑一遍 html 报表。
|
||||
|
||||
## Decision
|
||||
|
||||
`scripts/coverage-uncovered-locations.cjs` 是一个自定义 istanbul reporter(`ReportBase` 子类):对每个低于 100% 的文件,按未覆盖语句、未走的分支路径、未调用函数各输出一条自含的单行记录 `<path>:<line>:<col> uncovered <kind> …`——terminal 与 CI 日志中可直接点击跳转,也便于 grep。全部文件达标时零输出。istanbul 报表生成先于 threshold 校验,因此记录恰好落在既有 ERROR 行上方。
|
||||
|
||||
接线是单点的:根 `vitest.config.ts` 的 coverage 块是全仓唯一覆盖率配置,CI lane(`run-gates ci-coverage`)、本地 `test:coverage` 与聚焦跑(`--coverage.include`)共用它。该 reporter 以绝对路径(`fileURLToPath`)加入 CI 与本地两个 reporter 数组——istanbul-reports 的 `create()` 对非内置名回退为裸 `require(name)`,相对路径会按 istanbul 自己的包目录解析。
|
||||
|
||||
输出约定:
|
||||
|
||||
- istanbul 的 0 基列号转为 1 基(编辑器与终端链接的约定)。
|
||||
- v8 对整行语句给出 `end.column = Infinity`:跨行时降级为只带行号的 `(to <line>)` 后缀,单行时省略后缀。
|
||||
- 隐式分支臂(如缺省 else)可能不带位置,回退到分支自身的 span,保证记录仍可点击;分支记录标注类型与 `path k/n`。
|
||||
- 同文件内记录按行、列排序;不设条数上限。
|
||||
|
||||
配套两处:根 `package.json` 增补 devDependency `istanbul-lib-report`(pnpm 严格布局下 `scripts/` 摸不到嵌套依赖);`knip.json` 根 workspace 的 entry/project 通配增加 `scripts/**/*.cjs`,使该文件及其依赖对 hygiene 门禁可见。
|
||||
|
||||
CJS 是被迫的形态,也是 ESM-everywhere 纪律的一个有据例外:istanbul 在 tsx/Vite 管线之外用裸 `require()` 装载自定义 reporter,TypeScript 无法参与;`require(esm)` 返回的命名空间对象也过不了它的 `new Cons(cfg)` 构造,CommonJS 是唯一可靠形态。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **依赖内置 `text` 报表的 Uncovered Line #s 列。** 正是问题现状:全仓大表、列宽截断、只有行号、不分种类、达标文件同列——CI 日志里不可行动。
|
||||
- **加 `json` reporter,另写 wrapper 脚本失败后读 `coverage-final.json` 后处理。** 纯 ESM/TS 可行,但 wrapper 必须同时包住 `package.json` 的 `test:coverage` 与 run-gates 的 gate 两个入口,命令形状随之改变;自定义 reporter 路线只动一处配置,两个入口自动生效。
|
||||
- **用 TypeScript/ESM 写 reporter。** istanbul 的装载机制(管线外裸 `require`)决定了不可行,见上;为一个报表文件把装载机制换掉不成比例。
|
||||
|
||||
## Verification
|
||||
|
||||
本地矩阵:故意制造未达标时三类记录齐全、位置与埋点一致;混合现场只输出未达标文件(同跑内 100% 的文件静默);全绿跑零输出、退出码 0。CI 实证:临时在 `clampTimeout` 埋入一处不可达语句/分支/函数后,coverage lane 在全部测试通过(632 文件 / 10326 用例)、仅 threshold 失败的隔离条件下,把 4 条记录打印在 ERROR 行上方(证据链接见 PR #1716 描述);演示提交在合并前已从分支撤除。
|
||||
|
||||
## Consequences
|
||||
|
||||
- 覆盖率红报自足:日志直接给出精确行列号与缺口种类,不再需要本地重跑 html 报表定位。
|
||||
- 代价是一个 CJS 文件的纪律例外与一个根 devDependency;全绿运行零输出,不增加日志噪音。
|
||||
- 整文件零覆盖时输出条数与该文件语句数同阶(刻意不设上限):门禁要求零缺口,全量列出即是行动清单,vitest 自身的 ERROR 行已按文件汇总兜底。
|
||||
Reference in New Issue
Block a user