【免费下载链接】gsd-core
Git. Ship. Done - 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 的复现描述):
- 路径限定只认识
*.test.*、*.spec.*和tests/:这些约定覆盖的是 JS/TS 及其同构生态。对于 Go 项目,一个提交如果只添加了foo_test.go,git pathspec 根本匹配不到,门控视为"没有 RED 提交",于是每个行为添加任务都会以TDD GATE TRIPPED: missing RED commit硬性中止。 **/前缀不匹配根目录路径:即使是在原本就支持的 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.go | Go | Go 标准测试命名,如foo_test.go、pkg/bar_test.go |
test_*.py/*_test.py | Python | 在tests/之外也识别 pytest 风格的前缀/后缀命名 |
*_test.exs | Elixir | ExUnit 约定 |
*_spec.rb/*_test.rb | Ruby | RSpec 与 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 仓库执行验证,四个用例分别钉住:
- 所有约定可见(第 191-201 行):种子仓库一次性提交
foo_test.go、pkg/bar_test.go、test_mod.py、mod_test.py、lib_spec.rb、x_test.exs,断言全部满足 RED 探测器。 - 根目录不再不可见(第 203-211 行):
root.test.js与根级foo_test.go必须命中——这正是旧 pathspec**/前缀的盲区。 - 既有 JS/TS 约定无回归(第 213-219 行):
src/a.test.ts、tests/c.py、__tests__/d.js在拓宽后依然命中。 - 实现文件永不匹配(第 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):
TDD_MODE解析为true(优先级:--tddCLI 标志 →workflow.tdd_mode配置);- 当前任务的
<task>frontmatter 中tdd="true"; - 任务的
<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 触发后的行为
门控触发时,执行器必须:
- 在运行任务的实现步骤之前中止(gsd-core/references/execute-mvp-tdd.md);
- 输出结构化中止报告:
### TDD GATE TRIPPED — Plan {plan_id}, Task {task_id}- 将
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_OK | target_test_failed | 目标测试以真实断言失败——唯一授权进入 GREEN 的判定 |
INVALID_RED | unexpected_green | RED 阶段退出码为 0:功能可能已存在或测试写错 |
INVALID_RED | zero_tests_discovered | 发现模式/固件未匹配到任何测试,运行未执行任何内容 |
INVALID_RED | nonzero_exit_without_test_failure | 非零退出但报告无失败测试(harness/解析器崩溃) |
INVALID_RED | fixture_or_load_failure | 所有失败条目都以目标文件命名(加载期崩溃,如 throw-on-require、语法错误、ENOENT) |
INVALID_RED | no_target_test_failure | 真实测试运行且失败,但失败的不是计划指定的目标测试 |
INVALID_RED | invalid_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 落地建议
- 多语言仓库优先使用本修复后的默认 pathspec:它比 gsd-core/references/tdd.md 的框架检测步骤枚举的项目类型更宽——识别一个 onboarding 流程尚未自动检测的约定成本为零,而使用了该约定的项目的 RED 提交不应被漏看;
- Rust 项目启用
workflow.tdd_mode前需预期门控触发:cargo test正常可用,但#[test]内嵌实现文件,路径门控无法区分;可将其视为记录在案的约束而非缺陷; - 理解
*.spec.*的误报面:api.spec.json之类的文件会让 RED 门通过纯规范文件提交,这是有意的取舍(与旧行为同类的既有误报的根目录延伸),不应在未评估行为变更的情况下擅自收窄; - 门控纪律不因 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
相关推荐
gsd-core 的 TDD Audit:用 gate-status 提交尾注把逐提交 TDD 门禁足迹带进 PR Body 与 squash-merge
gsd core 的 TDD Audit:用 gate status 提交尾注把逐提交 TDD 门禁足迹带进 PR Body 与 squash merge /g
gsd-core 修复:gap-analysis 现在正确识别 padded-prefix 约定的 CONTEXT.md 决策文件
gsd core 修复:gap analysis 现在正确识别 padded prefix 约定的 CONTEXT.md 决策文件 导读 本文围绕 gsd co
gsd-core 嵌套 Git 仓库检测修复解析:`/gsd-new-project` 与 `/gsd-ingest-docs` 如何通过 `git rev-parse` 语义避免误建 `.git`
gsd core 嵌套 Git 仓库检测修复解析: /gsd new project 与 /gsd ingest docs 如何通过 git rev parse
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考