☰
gsd-core TDD RED 提交门控修复:跨语言测试约定识别与仓库根目录匹配解析
2026/9/28 7:21:15 网站建设 项目流程

【免费下载链接】gsd-core

Git. Ship. Done - Core

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

导读

本篇文章聚焦 gsd-core 中 TDD 模式下的RED 提交门控(RED-commit gate)修复——该修复(PR #4715,issue #4379)解决了 Go、Ruby、Elixir、Python 项目中每个行为添加任务都会误触发TDD GATE TRIPPED: missing RED commit的问题,同时补上了仓库根目录测试文件"不可见"的缺陷。读完本文,你将掌握:TDD 门控在 gsd-core 中的运行机制、RED 提交 pathspec 的精确构成与设计权衡、各语言测试约定如何被识别、以及 Rust 场景为何被明确记录为"已知缺口"。


一、问题背景:JS/TS 单一约定的 RED 门控如何拖垮多语言项目

在 gsd-core 的 TDD 流水线模式中,workflow.tdd_mode配置开启后(默认false,见 docs/CONFIGURATION.md),执行器会对所有type: tdd计划强制施加 RED → GREEN → REFACTOR 门序列。其中 RED 门要求:任何"行为添加"(behavior-adding)任务在实现代码提交之前,必须存在一条先行提交的失败测试提交。

修复前的 RED 门控存在两个致命缺陷(见 tests/safe-resume-gate-anchoring.test.cjs 的复现描述):

  1. 路径限定只认识*.test.*、*.spec.*和tests/:这些约定覆盖的是 JS/TS 及其同构生态。对于 Go 项目,一个提交如果只添加了foo_test.go,git pathspec 根本匹配不到,门控视为"没有 RED 提交",于是每个行为添加任务都会以TDD GATE TRIPPED: missing RED commit硬性中止。
  2. **/前缀不匹配根目录路径:即使是在原本就支持的 JS/TS 中,位于仓库根目录的foo.test.js也会因为**/通配符不匹配"无目录层级"的路径而不可见——这是 issue #4379 中未被报告的另一半问题(见测试第 203-211 行)。

而这一切发生的背景更令人困惑:gsd-core 的 TDD 参考文档 gsd-core/references/tdd.md 一直宣传go test ./...、pytest、cargo test等语言均受支持,门控却只能看见 JS/TS 约定——文档契约与运行时行为出现了严重漂移。

二、修复方案:#4379 的跨语言 pathspec 重构

修复的核心思路(实现在 gsd-core/workflows/execute-phase.md 的RED_COMMIT探测命令中)是:将 pathspec 从"窄到只认识 JS/TS"改为"宽到覆盖所有主流语言约定",同时绝不扩大到普通源码。

修复后的 pathspec 由下列裸 glob 组成(裸 glob 在 git 中匹配任意深度,天然覆盖根目录):

约定模式覆盖语言/生态说明
*.test.*JS/TS 及共享该约定的生态源码旁放置的测试文件,如foo.test.js
*.spec.*JS/TS 及共享该约定的生态如foo.spec.ts
tests/通用目录约定仓库根目录的tests/目录
__tests__/JS 生态(Jest 等)专门的测试目录
*_test.goGoGo 标准测试命名,如foo_test.go、pkg/bar_test.go
test_*.py/*_test.pyPython在tests/之外也识别 pytest 风格的前缀/后缀命名
*_test.exsElixirExUnit 约定
*_spec.rb/*_test.rbRubyRSpec 与 Minitest 约定

完整命令(保留注释中的设计决策):

RED_COMMIT=$(git log --oneline -E ${TDD_MILESTONE_BASE:+"$TDD_MILESTONE_BASE..HEAD"} \ --grep="${PLAN_SCOPE_RE}" \ -- "*.test.*" "*.spec.*" "tests/" "__tests__/" "*_test.go" "test_*.py" "*_test.py" "*_test.exs" "*_spec.rb" "*_test.rb" \ | head -1)

需要注意:pathspec 本身就是该门控对"什么是测试文件"的定义。测试 tests/safe-resume-gate-anchoring.test.cjs 特意从已发布的工作流文件中提取真实 pathspec 来驱动测试,而不是在测试里重写一份副本——确保被测试的是"真正上线的东西"而非与其平行的字符串拷贝。

三、源码级验证:测试如何证明修复有效

tests/safe-resume-gate-anchoring.test.cjs 的#4379 — the TDD RED pathspec is language-agnostic描述块(第 151-236 行)用真实的临时 git 仓库执行验证,四个用例分别钉住:

  1. 所有约定可见(第 191-201 行):种子仓库一次性提交foo_test.go、pkg/bar_test.go、test_mod.py、mod_test.py、lib_spec.rb、x_test.exs,断言全部满足 RED 探测器。
  2. 根目录不再不可见(第 203-211 行):root.test.js与根级foo_test.go必须命中——这正是旧 pathspec**/前缀的盲区。
  3. 既有 JS/TS 约定无回归(第 213-219 行):src/a.test.ts、tests/c.py、__tests__/d.js在拓宽后依然命中。
  4. 实现文件永不匹配(第 221-235 行):src/impl.go、src/lib.rs必须不命中——这是门控"仍然有能力触发"的前提。src/lib.rs不命中直接导向 Rust 缺口的必然性(见下文)。

四、设计权衡:为什么接受*.spec.*的误报而不收窄

拓宽 pathspec 并非没有代价。*.spec.*可能匹配到恰好含 "spec" 字样的非测试文件——例如api.spec.json、openapi.spec.yaml——这意味着一个只改动此类文件的提交也能让 RED 门通过。

gsd-core 在 gsd-core/references/tdd.md 明确记录了这一取舍:

  • 这不是新问题:旧的**/*.spec.*已经会在任意嵌套路径匹配这些文件,去掉**/前缀只是把同一类误报扩展到仓库根目录;
  • 选择接受而非收窄,因为收窄是对当前已支持用例的行为变更,且不属于"让其他语言可见"这个修复目标的一部分。

与之相对,Rust 是被有意排除的。#[test]在 Rust 中通常与实现同处src/*.rs文件内,一个 Rust RED 提交触碰的只有普通源码,任何基于路径的门控都无法将其与普通源码区分。如果为了覆盖 Rust 而把 pathspec 扩大到*.rs,就等价于匹配所有源码,门控将失去意义(参考 gsd-core/references/tdd.md 与测试第 221-235 行的关联论证)。因此在 Rust 项目中使用workflow.tdd_mode时,RED提交门控仍会触发——cargo test本身完全可用,受限于的只是基于路径的提交级门控,这是被记录在案而非试图"修复"的缺口。

五、门控运行机制:从 TDD_MODE 到 RED_COMMIT 的完整链路

5.1 门控的激活条件

门控内联于执行阶段的每个实现步骤之前(gsd-core/workflows/execute-phase.md)。自 #4011 起,门控仅以TDD_MODE为准,不再与产品级MVP_MODE耦合——旧逻辑把纪律门控绑在非 MVP 阶段上会导致其在所有非 MVP 阶段静默失效,违背了workflow.tdd_mode对所有type: tdd计划生效的契约。MVP 模式可以隐含 TDD,但 TDD 不再要求 MVP。

完整激活链(参考 gsd-core/references/execute-mvp-tdd.md):

  1. TDD_MODE解析为true(优先级:--tddCLI 标志 →workflow.tdd_mode配置);
  2. 当前任务的<task>frontmatter 中tdd="true";
  3. 任务的<behavior>块至少列出一条预期行为。

三者任一为假,门控即不激活,执行正常进行。

5.2 "行为添加任务"的精确定义

一个任务被视为行为添加,需要同时满足(gsd-core/references/execute-mvp-tdd.md):

  • frontmatter 中tdd="true";
  • <behavior>块至少命名一个用户可见的结果(排除纯配置/纯文档任务);
  • <files>列表至少包含一个源文件(排除仅含*.md、*.json、*.test.*、*.spec.*、*.yml、*.yaml、*.toml、*.ini、.env*等的任务)。

纯文档、纯配置或纯测试任务,即使两个模式都开启也会被门控跳过。

5.3 门控探测与触发

if [ "$TDD_MODE" = "true" ]; then IS_BEHAVIOR_ADDING=$(gsd_run query task.is-behavior-adding "$TASK_FILE" --pick is_behavior_adding) if [ "$IS_BEHAVIOR_ADDING" = "true" ]; then # #4003:同一锚定作用域与里程碑边界,零填充字面量 grep 会在正确的非填充 RED 提交上硬性中止 # #4619:PHASE_NUMBER 可为小数/N 段,仅对前导整数段去零 # #4748:PHASE_NUMBER 可带字母后缀(03A),在第一个非数字处切分 PHASE_INT=${PHASE_NUMBER%%[!0-9]*}; PHASE_REST=${PHASE_NUMBER#"$PHASE_INT"} PHASE_N="$((10#$PHASE_INT))${PHASE_REST//./\\.}" PLAN_N=$((10#${PLAN_ID})) PLAN_SCOPE_RE="^[a-z]+\((0*${PHASE_N})-(0*${PLAN_N})\):" TDD_MILESTONE_BASE=$(git describe --tags --abbrev=0 2>/dev/null || echo "") RED_COMMIT=$(git log --oneline -E ${TDD_MILESTONE_BASE:+"$TDD_MILESTONE_BASE..HEAD"} \ --grep="${PLAN_SCOPE_RE}" \ -- "*.test.*" "*.spec.*" "tests/" "__tests__/" "*_test.go" "test_*.py" "*_test.py" "*_test.exs" "*_spec.rb" "*_test.rb" \ | head -1) if [ -z "$RED_COMMIT" ]; then gsd_run query state.update last_gate_trip "${PLAN_ID}/${TASK_ID}" || true echo "TDD GATE TRIPPED: missing RED commit for ${PLAN_ID}/${TASK_ID}" exit 1 fi fi fi

门控的提交搜索遵循与safe_resume_gate相同的纪律:

  • 锚定作用域正则^[a-z]+\((0*${PHASE_N})-(0*${PLAN_N})\):只匹配提交作用域位置上的test(phase-plan):形式,正文中提及相同编号的提交不会被误命中(由 tests/safe-resume-gate-anchoring.test.cjs 的行为测试覆盖);
  • 里程碑边界:git describe --tags --abbrev=0得到的最近可达标签作为基线,防止更早里程碑中同作用域的旧提交"冒充"当前计划的 RED 提交;
  • 零填充容忍:提交协议承诺不填充零(#4003),因此PHASE_N/PLAN_N会先做零剥离,再以可容忍0*的 ERE 匹配——填充的字面量 grep 会在正确的非填充提交上硬性中止。

5.4 触发后的行为

门控触发时,执行器必须:

  1. 在运行任务的实现步骤之前中止(gsd-core/references/execute-mvp-tdd.md);
  2. 输出结构化中止报告:
### TDD GATE TRIPPED — Plan {plan_id}, Task {task_id}
  1. 将last_gate_trip状态写入state.update,供后续排障引用。

六、RED 证据校验:check tdd-red-evidence与 INVALID_RED

修复 pathspec 只是让 RED提交可见;而提交里的测试是否"真正地、故意地失败",则由 RED证据校验把关(issue #3770 引入)。执行器在 RED 阶段运行测试命令后,持久化证据记录(命令、退出码、失败测试、预期结果、实际结果)并校验:

gsd_run check tdd-red-evidence <record.json> --raw

核心实现位于 src/tdd-red-evidence.cts,分类逻辑(classifyRedEvidence,第 172-258 行)严格 fail-closed:

判定reason含义
RED_EVIDENCE_OKtarget_test_failed目标测试以真实断言失败——唯一授权进入 GREEN 的判定
INVALID_REDunexpected_greenRED 阶段退出码为 0:功能可能已存在或测试写错
INVALID_REDzero_tests_discovered发现模式/固件未匹配到任何测试,运行未执行任何内容
INVALID_REDnonzero_exit_without_test_failure非零退出但报告无失败测试(harness/解析器崩溃)
INVALID_REDfixture_or_load_failure所有失败条目都以目标文件命名(加载期崩溃,如 throw-on-require、语法错误、ENOENT)
INVALID_REDno_target_test_failure真实测试运行且失败,但失败的不是计划指定的目标测试
INVALID_REDinvalid_record/unreadable_record记录缺失或不可读

值得注意的是(#4724),check tdd-red-evidence还支持 Maven Surefire/Failsafe 的 XML 摘要:解析采用标签边界扫描而非惰性正则,避免"通过用例自闭合<testcase />标签吞掉中间所有用例"的经典陷阱,且 CDATA 内容(如System.out回显)不会被误扫为失败标签(src/tdd-red-evidence.cts)。

提交消息中的RED:前缀或(RED)标签本身不构成充分证据——只有check tdd-red-evidence返回RED_EVIDENCE_OK才能放行。

七、变更影响与使用建议

7.1 对现有工作流的影响

  • Go / Ruby / Elixir / Python 项目:行为添加任务不再因foo_test.go、*_spec.rb、*_test.exs、test_*.py不可见而误触发门控——这是本次修复的核心收益;
  • 根目录测试文件:root.test.js、根级*_test.go等位于仓库根的文件重新可见;
  • JS/TS 既有约定:*.test.*、*.spec.*、tests/、__tests__/全部保持命中,无回归;
  • Rust 项目:行为不变,RED 提交门控仍会触发(已知缺口,见下)。

7.2 落地建议

  1. 多语言仓库优先使用本修复后的默认 pathspec:它比 gsd-core/references/tdd.md 的框架检测步骤枚举的项目类型更宽——识别一个 onboarding 流程尚未自动检测的约定成本为零,而使用了该约定的项目的 RED 提交不应被漏看;
  2. Rust 项目启用workflow.tdd_mode前需预期门控触发:cargo test正常可用,但#[test]内嵌实现文件,路径门控无法区分;可将其视为记录在案的约束而非缺陷;
  3. 理解*.spec.*的误报面:api.spec.json之类的文件会让 RED 门通过纯规范文件提交,这是有意的取舍(与旧行为同类的既有误报的根目录延伸),不应在未评估行为变更的情况下擅自收窄;
  4. 门控纪律不因 pathspec 变宽而放松:pathspec 只负责"提交可见","测试是否真的失败"仍由check tdd-red-evidence的RED_EVIDENCE_OK判定把关。

八、延伸阅读

  • TDD 计划结构与执行周期:gsd-core/references/tdd.md
  • 门控运行时规范(中止报告格式):gsd-core/references/execute-mvp-tdd.md
  • 执行阶段工作流(门控内联位置):gsd-core/workflows/execute-phase.md
  • RED 证据分类实现:src/tdd-red-evidence.cts
  • 跨语言 pathspec 的回归测试:tests/safe-resume-gate-anchoring.test.cjs
  • TDD 流水线模式需求与配置:docs/features/tdd-pipeline-mode.md、docs/CONFIGURATION.md

【免费下载链接】gsd-core

Git. Ship. Done - Core

项目地址:https://gitcode.com/gh_mirrors/ge/gsd-core
点击查看免费下载
上一篇:Android列表动画进阶:UltimateRecyclerView自定义动画
下一篇:Maltrail学术论文引用:在网络安全研究中的应用案例

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询