# Agent Note: 并行 pre-push 门禁

Status: implemented

[English](2026-07-06-parallel-pre-push-gates.md) | 中文

本记录中的本地钩子部分已由[快速本地 Git 钩子](2026-07-22-fast-local-git-hooks.zh.md) 取代。有界门禁调度器和包级 `publint` 并行机制仍用于 CI、`doc-sync` 和显式本地命令。

## 问题

文档同步等聚合任务隐藏了很长的串行链，其中各项检查只读且相互独立。在工作流 YAML 中重复这些叶子清单，会使未来脚本变更有多个位置可以发生漂移；而串行运行包发布检查，会使一道门禁的耗时与包数量成正比。

## 决策

[scripts/run-gates.ts](../../../../scripts/run-gates.ts) 拥有 CI、`doc-sync` 和按需启用的 `check:all` 命令所使用的有界调度器。它将具名模式展开为叶子门禁，在启动子进程前拒绝空的或有歧义的依赖图，遵守产物依赖，默认缓冲可归因的输出，分别报告进程退出与信号终止结果，并在调用方需要不同 worker 上限时接受 `DSH_GATE_CONCURRENCY`。`needs` 边要求前置门禁通过，否则跳过依赖方；`after` 边只等待前置门禁以任意结果结算，随后仍允许后继门禁运行。标记为 `allowFailure` 的门禁仍会报告结果，但不会使聚合流程失败。

自身子进程能够保留有效归因的长时间协调门禁可以选择 `streamOutput`。其 stdout 与 stderr 会立即到达父进程，不会被缓冲，也不会在结束时重复打印。分区覆盖率与并行 Web 快照使用该模式，使运行中途的失败无需等待兄弟工作结束就能显示。

Node 24 消费方任务采用单个包含 10 道门禁的模式，而非由 shell 管理的进程池。其默认 worker 数等于门禁数，拉取请求 CI 则把活动门禁限制为 8 道，并由依赖关系控制就绪状态。构建与源码兼容性立即启动；构建完成后，`publint` 与已构建包不变式验证并行运行。lint、两套快照、文档类型检查、NodeNext 类型检查和 built-bin 冒烟测试等待不变式验证器清除临时包视图。

[scripts/publint-all.ts](../../../../scripts/publint-all.ts) 从 `packages/<group>/<pkg>` 发现包，并以根据 `availableParallelism()` 确定大小的 worker 池运行 `publint`。`DSH_PUBLINT_CONCURRENCY` 可以针对资源配置不同的本地机器和 CI runner 限制或提高 worker 数量。结果按包缓冲，并按确定性的包顺序打印，因此并行执行不会打乱各包的日志块。

各门禁的包脚本仍是临时本地运行所用的命令入口。`hygiene` 调用包含相同十三道检查且限制为本地四个 worker 的调度器模式，而 `doc-sync` 的成员列表由调度器管理（[通过门禁调度器运行 doc-sync](../../archived/process/2026-07-21-doc-sync-through-gate-scheduler.md)）。

## 验证

[scripts/run-gates.spec.ts](../../../../scripts/run-gates.spec.ts) 在执行器运行前拒绝无效图，锁定必须通过与只等结算两种顺序，锁定 hygiene、消费方与原生 Windows 清单及其失败语义，通过真实子进程验证信号终止，并证明流式输出会立即显示且不被缓冲。[scripts/publint-all.spec.ts](../../../../scripts/publint-all.spec.ts) 在下游产物消费方运行前拒绝缺失的公开导出。

## 曾考虑的替代方案

- **保持聚合 job 串行**：执行更简单，但墙钟时间等于各独立检查之和，并重复启动命令包装器。
- **每个叶子门禁声明一个 CI job**：暴露最大工作流并行度，但会重复 checkout、设置和安装开销，并在 YAML 中复制调度器清单。
- **在 shell 脚本内后台运行子命令**：可以并行处理，但会失去各门禁计时、确定性的失败分组和直接的信号处理。
- **让所有门禁继承 stdio**：可以立即显示进度，但会交错普通独立门禁的输出，并丢失调度器可归因的输出记录。流式输出仍是显式的门禁属性。
- **每个包声明一个 `publint` job**：暴露最大包级并行度，但会创建手工维护的包清单，包发生变化时就会漂移。
- **以无界并发运行 `publint`**：虽能最大限度缩短小型仓库的耗时，却会拿进程数量、内存压力、包 tarball 创建开销和日志可读性冒险。

## 后果

由调度器支持的命令耗时取决于最慢的依赖链，而非各独立门禁耗时之和，并会报告决定总耗时的门禁。无效图会直接失败，不会先执行其中一部分。代价是维护一个具有显式模式清单的定制调度器。

这条验证链会让使用已验证产物的下游消费方和 lint 延后启动，直至共享产物视图经确认有效且临时暂存已清除；这些下游门禁仍可彼此重叠运行。`publint` 需要构建，却不依赖暂存的验证视图，因此它会与验证器重叠，而不会延长这条依赖链。

大多数门禁仍保留确定性的输出块。少数长时间协调器用跨门禁输出顺序和缓冲日志换取即时诊断，而其最终状态仍可供聚合摘要使用。

`publint-all.ts` 采用异步执行并缓冲命令输出，而不是实时继承 stdio。换来的是具有稳定输出顺序的包级并行，以及用于资源调节的单一环境变量。
