docs(i18n): refresh Agent Note translations after merge

This commit is contained in:
Tianyi Cui
2026-07-23 00:10:53 +08:00
parent cd17f8d23a
commit 744d65d63c
325 changed files with 1535 additions and 1321 deletions
@@ -1,42 +1,33 @@
# RFC: 并行 pre-push 门禁
# Agent Note: 并行 pre-push 门禁
Status: implemented
[English](2026-07-06-parallel-pre-push-gates.md) | 中文
本记录中的本地 hook 部分已由[快速本地 Git hook](2026-07-22-fast-local-git-hooks.md) 取代。有界门禁调度器和包级 `publint` 并行机制仍用于 CI、`doc-sync` 和显式本地命令。
## 问题
pre-push 钩子是分支离开本地机器前的最后一道检查点,因此它的挂钟时间直接影响贡献者是否愿意保持启用并信任其信号。Lefthook 已经能并行运行顶层 job,但 `pnpm run hygiene` 和 `pnpm run doc-sync` 等聚合 job 在单个 job 内部隐藏了长串的顺序执行链。钩子因此可能在配置上看似并行,实际仍在等待那些成员彼此独立却串行执行的子命令。
将这些成员直接展平到 `lefthook.yml` 只能解决本地钩子的问题。CI 面临同样的调度问题,而在 YAML 中复制一长串叶子列表会让未来的脚本改动有两处可能漂移。
`publint` 在更低一层也有同样的形态。每个包(package)独立地根据自身 manifest(元数据清单)和构建产物做 lint,但 runner 按顺序遍历所有包。在本仓库中,这意味着一个包发布门禁的耗时与包的数量成正比,尽管各检查之间并不共享可变状态。
文档同步等聚合 job 隐藏了很长的串行链,其成员只读且相互独立。在工作流 YAML 中重复这些叶子清单,会使未来脚本变更有多个位置可以发生漂移;而串行运行包发布检查,会使一道门禁的耗时与包数量成正比。
## 决策
[lefthook.yml](../../../../lefthook.yml) 保留一个名为 `full check` 的 pre-push job,运行 `pnpm run check:pre-push`。该 package 脚本委托给 [scripts/run-gates.ts](../../../../scripts/run-gates.ts),即 CI 使用的同一个有界调度器。
[scripts/run-gates.ts](../../../../scripts/run-gates.ts) 拥有 CI、`doc-sync` 和选择启用的 `check:all` 命令所使用的有界调度器。它将具名模式展开为叶子门禁,遵守产物依赖,缓冲可归因的输出,并在调用方需要不同 worker 上限时接受 `DSH_GATE_CONCURRENCY`。
`pre-push` 模式展开为以下叶子门禁:单元测试套件、快照测试套件、构建、`hygiene` 成员、`doc-sync` 成员,以及 module-graph 新鲜度。叶子列表保持与 package 脚本相同的门禁词汇,包括 RFC 分类和 RFC 格式,同时 runner 并发调度独立检查,并为每个门禁打印一个计时/输出块。
[scripts/publint-all.ts](../../../../scripts/publint-all.ts) 从 `packages/<group>/<pkg>` 发现包,并以根据 `availableParallelism()` 确定大小的 worker 池运行 `publint`。`DSH_PUBLINT_CONCURRENCY` 可以针对资源配置不同的本地机器和 CI runner 限制或提高 worker 数量。结果按包缓冲,并按确定性的包顺序打印,因此并行执行不会打乱各包的日志块。
构建门禁使钩子在干净的 worktree 上也能自足运行。`publint` 和 `verify-node-next-types` 等待构建产物就绪,而仅依赖源码的门禁继续并行执行。
[scripts/publint-all.ts](../../../../scripts/publint-all.ts) 从 `packages/<group>/<pkg>` 发现包列表,并使用大小取自 `availableParallelism()` 的 worker 池运行 `publint`。`DSH_PUBLINT_CONCURRENCY` 可为资源配置不同的本地机器和 CI runner 设定或提高 worker 数量上限。结果按包缓冲,并以确定性的包顺序打印,因此并行执行不会打乱各包的日志块。
聚合 package 脚本仍然是临时本地运行的真源。调度器是对其成员门禁的并行执行计划,而非替代词汇。
各门禁的包脚本仍是临时本地运行所用的词汇。`hygiene` 继续作为聚合 `&&` 链,而 `doc-sync` 在调度器中拥有其成员列表([通过门禁调度器运行 doc-sync](2026-07-21-doc-sync-through-gate-scheduler.md))。
## 曾考虑的替代方案
- **在钩子中保留聚合的 `hygiene` 和 `doc-sync` job**:配置更简单,但 pre-push 的大部分挂钟时间仍然消耗在 lefthook 看不到也无法调度的串行命令链内部。
- **为每个叶子门禁声明一个 lefthook job**:通过 lefthook 原生 job 模型暴露并行性,但会让钩子文件承载一长串成员列表,CI 无法复用。
- **要求开发者在推送前手动构建**:可以省去一个钩子门禁,但会导致 `publint` 在干净 worktree 上失败,并把最后的本地检查点从可运行的检查降级为一种约定。
- **在 shell 脚本中使用后台子命令**:能并行化工作,但会丢失 lefthook 的 job 名称、逐 job 计时和失败分组,且信号处理更难推理。
- **为每个包声明一个 publint lefthook job**:暴露最大并行度,但会让钩子变成一份手动维护的包清单,恰好在新增包时漂移。
- **以无界并发运行 publint**:仅在小型机器上以赌注方式最小化耗时,代价是进程数、内存压力、包 tarball 创建和日志可读性的风险。
- **保持聚合 job 串行**:执行更简单,但墙钟时间等于各独立检查之和,并重复启动命令包装器。
- **每个叶子门禁声明一个 CI job**:暴露最大工作流并行度,但会重复 checkout、设置和安装开销,并在 YAML 中复制调度器清单。
- **在 shell 脚本内后台运行子命令**:可以并行处理,但会失去各门禁计时、确定性的失败分组和直接的信号处理。
- **每个包声明一个 `publint` job**:暴露最大包级并行度,但会创建手工维护的包清单,包发生变化时就会漂移。
- **以无界并发运行 `publint`**:只有通过拿进程数、内存压力、包 tarball 创建和可读日志冒险,才能最大限度缩短小型仓库的耗时。
## 后果
钩子的关键路径变为最慢的单个真实门禁,而非隐藏门禁链的总和。Lefthook 报告一个 `full check` job,runner 在该 job 内部报告逐门禁计时,因此本地检查点偏慢时仍能指向主导耗时的那个门禁。
由调度器支持的命令耗时取最慢依赖链,而非各独立门禁之和,并会报告主导耗时的门禁。代价是维护一个具有显式模式清单的定制调度器。
钩子文件保持简短,重复的成员列表集中在 [scripts/run-gates.ts](../../../../scripts/run-gates.ts) 中,CI 和 pre-push 可以共享。代价是引入一个自定义调度器脚本(而非纯 lefthook 配置),外加本地 pre-push 路径中的一次构建。
`publint-all.ts` 变为异步代码,缓冲命令输出而非实时继承 stdio。收益是包级别的并行性、稳定的输出顺序,以及一个用于资源调优的环境变量。
`publint-all.ts` 采用异步执行并缓冲命令输出,而不是实时继承 stdio。换来的是具有稳定输出顺序的包级并行,以及用于资源调节的单一环境变量。