news 2026/9/25 14:36:51

gsd-core 测试治理实践:Phase Lifecycle 测试集群 20→4 合并与 lint-test-file-count 身份棘轮机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
gsd-core 测试治理实践:Phase Lifecycle 测试集群 20→4 合并与 lint-test-file-count 身份棘轮机制

【免费下载链接】gsd-core

Git. Ship. Done - Core

项目地址:https://gitcode.com/gh_mirrors/ge/gsd-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.

这段描述浓缩了整套治理规则的四个要点:

  1. 数量上限:默认情况下,仓库要求每个模块(以测试文件名的前缀归并,如phase、milestone、config)最多只有2 个测试文件;
  2. allowlist 是例外名单:历史上已经超出上限的模块被"锁定"进 allowlist,锁定的是当天的确切文件名集合,而不是单纯的文件数量;
  3. 身份棘轮(identity ratchet):向一个已被封顶(capped)的模块新增任何测试文件都会直接失败(标记为novel);反过来,如果文件被删除导致数量回落到 ≤2,该条目又必须整个从 allowlist 中移除(标记为stale,即 ratchet-down,只允许往下收紧、不允许往上放宽);
  4. 新增条目需要理由:想给某个模块新增 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-awaregsd-researcher-app-aware生产 seam 属于 researcher,而非 phase
phase-researcher-flow-diagramgsd-researcher-flow-diagram生产 seam 属于 researcher
feat-3023-phase-type-modelsfeat-3023-model-phase-types主题是 model 的类型模型,而非 phase
phase-6-cjs-sdk-seam-contractscjs-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 等。

可以看出,这是一轮有计划、成体系的测试治理专项:多个超限模块在同一时期被统一收敛。这套方法可以概括为三条可复用的操作准则:

  1. 合并优先于扩容:模块测试文件超限时,先按生产 seam 归类合并小文件(同 seam 的 bug-fix 测试可以安全并入主测试文件),而不是向 allowlist 申请更大额度;
  2. 重命名对齐归属:凡生产 seam 不属于该模块前缀的文件,一律重命名为其真实 seam 的命名(如phase-*→gsd-*),避免污染模块计数;
  3. 同步 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

项目地址:https://gitcode.com/gh_mirrors/ge/gsd-core
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 14:34:53

通信型CRM如何重塑客户跟进流程:坐席工作台与客户时间线实践复盘

做客服团队管理这几年&#xff0c;我一直有个执念&#xff1a;客户跟进的上下文绝对不能断。2024年下半年&#xff0c;我们整个客服和销售运营从“微信Excel传统呼叫平台”的混合方案&#xff0c;迁移到了DeskcommCRM&#xff0c;到现在跑了快九个月。整个过程从选型到落地&…

作者头像 李华
网站建设 2026/9/25 14:34:12

惠普光影暗影精灵通电自启与网络唤醒避坑指南

1. 惠普光影暗影精灵通电自启与网络唤醒的坑&#xff0c;我替你踩完了惠普光影精灵和暗影精灵这两个系列&#xff0c;在游戏本和台式机圈子里保有量极大&#xff0c;但有个问题几乎每隔一段时间就会被拎出来吐槽一轮&#xff1a;明明在BIOS里把通电自启和网络唤醒都开了&#x…

作者头像 李华
网站建设 2026/9/25 14:32:04

OpenCode与Harness组合:用Skill编排智能体数据分析全流程

先说一个我最近的真实感受&#xff1a;过去在终端里干数据分析&#xff0c;流程永远是“打开Jupyter → 手动导入CSV → 写清洗代码 → 画两张图 → 复制结果去拼报告”&#xff0c;每一步都要自己来&#xff0c;烦且容易断。直到我把工作流切到 OpenCode 智能体&#xff0c;配…

作者头像 李华
网站建设 2026/9/25 14:31:15

JSP+SSM第二课堂成绩单系统:从跑通到二次开发实战指南

简介&#xff1a;这份资源是面向高校计算机相关专业毕业设计场景的JSPSSM第二课堂成绩单管理系统完整源码包&#xff0c;适合正在准备毕设、需要可运行项目参考的学生及课程设计指导教师。系统采用SSM框架搭配JSP页面与MySQL数据库&#xff0c;基于JDK1.8开发&#xff0c;可在E…

作者头像 李华