【免费下载链接】gsd-core
Git. Ship. Done - Core
本篇以 gsd-core 仓库归档 changeset(.changeset/archived/3740-consolidate-phase-tests.md)为核心,深入剖析一次典型的"测试集群合并"工程实践:Phase Lifecycle Module 的测试文件如何从 20 个收敛为 4 个,同时满足lint-test-file-count的 allowlist ceiling 约束。读者将从中掌握该仓库测试文件数量门控的底层原理(身份棘轮 identity ratchet)、测试文件正确归并/重命名的判定准则,以及如何把这一套治理方法复用到其他模块的测试集群中。
一、背景:为什么 gsd-core 要限制"每个模块的测试文件数量"
gsd-core 的脚本目录中存在一个专门的 lint 门禁 scripts/lint-test-file-count.cjs,其 allowlist 基线文件 scripts/lint-test-file-count.allowlist.json 的_doc字段明确说明:
Baseline of modules currently exceeding the 2-test-file limit. Each entry locks in TODAY's exact test filenames as the allowlisted set (identity ratchet). Adding a NEW test file to a capped module fails (novel). Removing one requires pruning this list (stale, ratchet-down). When a cluster drops to ≤ 2, remove its entry entirely. New entries require justification in PR description.
这段描述浓缩了整套治理规则的四个要点:
- 数量上限:默认情况下,仓库要求每个模块(以测试文件名的前缀归并,如
phase、milestone、config)最多只有2 个测试文件; - allowlist 是例外名单:历史上已经超出上限的模块被"锁定"进 allowlist,锁定的是当天的确切文件名集合,而不是单纯的文件数量;
- 身份棘轮(identity ratchet):向一个已被封顶(capped)的模块新增任何测试文件都会直接失败(标记为
novel);反过来,如果文件被删除导致数量回落到 ≤2,该条目又必须整个从 allowlist 中移除(标记为stale,即 ratchet-down,只允许往下收紧、不允许往上放宽); - 新增条目需要理由:想给某个模块新增 allowlist 许可,必须在 PR 描述中给出 justification。
因此,当一个模块的测试文件数量持续膨胀时,正确做法不是"再申请一个更大的 allowlist",而是像 PR #3740 这样主动合并测试集群,在不改变上限的前提下把文件数量压下来。
二、合并清单解读:从 20 个文件到 4 个
该 changeset 的 Summary 第一句话点明了目标:将 Phase Lifecycle Module 的测试集群从 20 个文件合并为 4 个,从而满足lint-test-file-count的 allowlist ceiling。具体动作分为三类:
2.1 10 个小体积 CJS bug-fix 测试文件并入phase.test.cjs
合并的最大头是:将 10 个小型 CJS bug-fix 测试文件全部并入 tests/phase.test.cjs。
从当前仓库状态看,这个合并的产物是一个体量相当可观的测试文件——tests/phase.test.cjs 全文件约 1.6 万行,正是"多份 bug-fix 测试内容合并到单一文件"后的典型形态。这类合并之所以可行,是因为这 10 个文件全部指向同一个生产 seam(Phase Lifecycle 的 CJS 入口phase.cjs),合并后测试仍能在同一个describe/test命名空间下运行,且不改变任何被测行为。
2.2 SDK 测试文件的合并(历史记录)
changeset 还记录了 SDK 侧的合并动作:将sdk/src/phase-runner-types.test.ts和sdk/src/phase-prompt.test.ts合并进sdk/src/phase-runner.test.ts。
这里需要说明一个仓库现状:根据 CONTEXT.md 中 Phase Lifecycle Module 条目的记载,phase-runner.ts、phase-prompt.ts以及types.ts中的事件定义均已随 SDK 包按 ADR-0174 退休,当前仓库已不存在sdk/目录。因此这部分合并属于该 changeset 归档时的历史记录,反映了"SDK 尚未退休时代"的同类治理动作——即便在 SDK 侧,测试文件同样遵循数量收敛的纪律。
2.3 4 个"归属错误"测试文件的重命名
changeset 明确列出 4 个被重命名的测试文件,其共同特征是其生产 seam 并不是phase.cjs/phase.ts,此前以phase-前缀命名属于误归属:
| 原文件名 | 重命名后 | 说明 |
|---|---|---|
phase-researcher-app-aware | gsd-researcher-app-aware | 生产 seam 属于 researcher,而非 phase |
phase-researcher-flow-diagram | gsd-researcher-flow-diagram | 生产 seam 属于 researcher |
feat-3023-phase-type-models | feat-3023-model-phase-types | 主题是 model 的类型模型,而非 phase |
phase-6-cjs-sdk-seam-contracts | cjs-sdk-bridge-seam-contracts | 契约对象是 CJS/SDK bridge seam |
这一重命名动作的价值在于:lint-test-file-count按文件名前缀将测试文件归并到模块(如phase-*、milestone-*),一个实际测试 researcher 行为的文件若顶着phase-前缀,会被错误地计入 Phase 模块的测试数量,既虚增了该模块的 count,又掩盖了它真正归属模块的测试覆盖情况。重命名让"文件名 → 生产 seam → allowlist 归并"三者重新对齐。
三、顺带修复:phasePlanIndex诊断通道改为warnings[]数组(#3430)
合并测试的同时,该 changeset 还修复了一个诊断通道的缺陷(issue #3430):非规范 plan 文件名的告警原本通过一个独立的单数warning字段透出,现改为流经warnings[]数组,与其他诊断信息并列输出。
这一改动的工程意义在于:
- 单一数据通道:调用方不再需要同时检查
warning和warnings[]两处字段,只要统一消费warnings[]即可拿到全部诊断; - 一致性:其他诊断(如 phase-id 校验、STATE.md 陈旧检测等)都走
warnings[],phasePlanIndex此前是唯一的"例外通道",合并后消除了通道分裂。
从仓库测试可找到对应佐证:tests/planning-inspect.test.cjs 中存在phasePlanIndexAndPlanningInspectAgreeOnPlanObjectiveAndTaskCount与phasePlanIndexBehaviorUnchangedByPlanDocumentExtraction两个测试用例,前者验证phasePlanIndex与planning-inspect在计划目标与任务计数上保持一致,后者专门验证"提取 plan 文档后phasePlanIndex行为不变"——这正呼应了 changeset 中"合并/重构不得改变对外行为"的回归测试要求。
四、allowlist ceiling 更新:从 20 到 4,以及当前仓库状态
changeset 同步更新了 scripts/lint-test-file-count.allowlist.json,将phase条目的 ceiling 从20 下调到 4——这是"ratchet-down"(只降不升)精神的直接体现:测试集群收敛后,允许的上限必须同步收紧,而不是维持原状。
从当前仓库的 allowlist 状态看,phase条目现在允许的文件清单为 6 个文件(以 issue 3186 为依据,其中 tests/phase.test.cjs 正是本次合并的核心产物)。可以推断,归档后该模块的测试集群又经历了后续的少量增补,但整体上远低于合并前的 20 个文件量级——这从侧面说明本次合并对 Phase 测试集群的收敛是持久且显著的。
此外,该 changeset 头部带有<!-- docs-exempt: internal test refactor only — no user-facing surface changed -->注释,向仓库的文档守卫(docs-exempt 机制)声明:本次变更属于内部测试重构,未改变任何用户可见表面,因此不需要伴随文档更新。这同样是一个值得借鉴的仓库规范——测试重构与功能变更在文档义务上被明确区分。
五、CONTEXT.md 词汇表:Phase Lifecycle Module 条目
合并的同时,changeset 为 CONTEXT.md 新增了Phase Lifecycle Module的词汇表条目。该条目(位于 CONTEXT.md 的模块索引区)记载了该模块的完整职责边界:
- 职责范围:phase 的 create、rename、complete、remove、list 与 plan-index 操作,以及 phase-dir 前缀校验、STATE.md 陈旧检测与自动清理(auto-prune);
- 入口 seam:
gsd-core/bin/lib/phase.cjs(CJS 表面); - 类型化事件:
GSDPhaseStartEvent、GSDPhaseStepStartEvent、GSDPhaseStepCompleteEvent、GSDPhaseCompleteEvent; - 历史边界:SDK 原生查询表面、
types.ts事件定义、phase-runner.ts、phase-prompt.ts均已随 SDK 退休(ADR-0174)。
这解释了为何本次合并要同时处理 SDK 测试:模块的"测试集群"定义并不局限于单一运行时,只要共享同一个模块主题(Phase Lifecycle),无论其测试挂在 CJS seam 还是(当时的)SDK seam 下,都统一纳入集群数量核算。
六、同一治理模式的系列实践:不止 Phase 一个模块
3740并不是孤例。在 .changeset/archived/ 目录下可以找到同一批"测试集群合并"主题的姊妹 changeset:
- .changeset/archived/3742-consolidate-worktree-tests.md——worktree 测试集群合并;
- .changeset/archived/3753-consolidate-milestone-tests.md——milestone 测试集群合并(CONTEXT.md 中对应记载为 PR #3753,10 文件 → 4 文件);
- .changeset/archived/3755-consolidate-init-tests.md——init 测试集群合并;
- .changeset/archived/3757-consolidate-runtime-artifact-layout.md、.changeset/archived/3758-consolidate-installer-tests.md、.changeset/archived/3761-consolidate-graphify-tests.md 等。
可以看出,这是一轮有计划、成体系的测试治理专项:多个超限模块在同一时期被统一收敛。这套方法可以概括为三条可复用的操作准则:
- 合并优先于扩容:模块测试文件超限时,先按生产 seam 归类合并小文件(同 seam 的 bug-fix 测试可以安全并入主测试文件),而不是向 allowlist 申请更大额度;
- 重命名对齐归属:凡生产 seam 不属于该模块前缀的文件,一律重命名为其真实 seam 的命名(如
phase-*→gsd-*),避免污染模块计数; - 同步 ratchet-down:合并完成后,allowlist 的 ceiling 必须同步下调(20 → 4),并配套更新 CONTEXT.md 词汇表与回归测试,形成"代码收敛 + 门禁收紧 + 文档同步"的完整闭环。
七、实践启示:如何在自己的 PR 中安全地执行测试合并
结合 scripts/lint-test-file-count.cjs 与 tests/lint-test-file-count.test.cjs 中的行为验证,执行同类合并时需注意以下门禁行为:
- 新增文件即失败:只要模块仍在 allowlist 中,向该模块添加任何新测试文件都会被判定为
FAIL_NOVEL_FILES,即使总数量没变(测试用例明确覆盖了"删除一个旧文件、新增一个新文件、数量不变但身份变了"的场景——身份棘轮按文件名集合比对,而非数量); - 删到上限以下必须清空条目:若模块文件数回落到 ≤2 而 allowlist 条目仍在,会触发
FAIL_STALE_ALLOWLIST,整个条目必须被移除; - allowlist 判定状态:允许的模块返回
OK_IN_ALLOWLIST,未超限的普通模块正常通过,超限且不在 allowlist 的模块直接失败——因此在做合并时,务必保证最终文件集合与 allowlist 记录逐字节一致。
实操上,一个完整的合并 PR 应包含:合并后的主测试文件(行为不变的回归内容)、被重命名的文件、allowlist ceiling 的下调、CONTEXT.md 词汇表条目,以及(若涉及诊断字段调整)对应的契约测试,如 tests/planning-inspect.test.cjs 中验证phasePlanIndex行为不变的用例。这正是 changeset3740-consolidate-phase-tests所展示的完整样板:以一次内部重构,同时完成测试数量收敛、模块归属纠正、诊断通道统一与门禁收紧,且全程不触碰任何用户可见行为。
【免费下载链接】gsd-core
Git. Ship. Done - Core
相关推荐
Get Shit Done Worktree 测试集群合并实战:从 13 个文件收敛到 3 个,由 lint-test-file-count 棘轮治理驱动
Get Shit Done Worktree 测试集群合并实战:从 13 个文件收敛到 3 个,由 lint test file count 棘轮治理驱动 本篇
人工智能AI 应用提示工程开发工具工作流自动化AI Agentgsd-core Worktree 测试集群合并重构:从 13 个文件到 3 个文件的收敛实践
gsd core Worktree 测试集群合并重构:从 13 个文件到 3 个文件的收敛实践 本篇技术指南聚焦 gsd core 仓库中一次典型的测试维护重构
OmniRoute 测试覆盖率治理计划:从 56.95% 到 90% 的分阶段攀升与棘轮机制
OmniRoute 测试覆盖率治理计划:从 56.95% 到 90% 的分阶段攀升与棘轮机制 导读 本文基于 OmniRoute 仓库中的 Test Cover
后端API网关LLM 网关人工智能大模型MCP 服务桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考