【免费下载链接】gsd-core
Git. Ship. Done - Core
phase complete是 GSD(Git. Ship. Done)核心流程中负责推进阶段(Phase)的关键命令。当项目以乱序方式补完一个重新打开的阶段(即编号更靠后的阶段早已勾选 [x])时,旧逻辑会把数值上紧随其后的已完成阶段误判为下一个阶段,并持久化进STATE.md的current_phase字段,导致进度状态与 ROADMAP 实际勾选情况脱节。本文以 changeset 记录(.changeset/daring-finches-zip.md,type: Fixed,PR 4820,对应 issue #4699)为核心,结合 src/phase.cts、src/init.cts、src/roadmap.cts 的源码与 tests/phase.test.cjs 的回归测试,完整还原该缺陷的产生原因、修复方案、三层 next-phase 解析级联及其验证方式,帮助读者理解 GSD 中"ROADMAP 顺序即前沿"这一核心一致性规则。
一、缺陷背景:乱序完成(out-of-order completion)为何会污染 current_phase
在 GSD 的规划模型中,一个里程碑(Milestone)内的阶段由ROADMAP.md定义,每个阶段对应一个复选框条目(如- [ ] **Phase 2: Two**),phase complete完成某个阶段时会将该复选框改写为[x]。阶段目录(.planning/phases/)按NN-slug命名,但目录是惰性创建的——只有真正执行过的阶段才会有目录,因此"磁盘上有哪些目录"并不能可靠地代表"下一个未完成的阶段是谁"。
在修复前,phase complete的 next-phase 扫描存在两个未考虑完成状态的问题:
- 磁盘目录扫描(stage 1)和 ROADMAP 条目扫描(stage 2)都以"数值上严格大于 N 的最小阶段号"作为候选,从不检查该阶段的复选框是否已经是
[x]; - 一旦选中,该结果会通过
completePhaseCore状态转换(src/state-transition.cts)与syncAndPreserveStateMd写回STATE.md的current_phase/current_phase_name字段,造成持久化的错误状态。
典型受害场景:里程碑包含 Phase 1~4,Phase 3、4 已按序完成并勾选[x];随后 Phase 2 被重新打开并补完(此时 Phase 3、4 依旧保持[x])。旧逻辑在完成 Phase 2 后,数值上最小的"大于 2"的阶段是 Phase 3,于是把已完成的 Phase 3 写为current_phase——而roadmap analyze与init progress却正确地报告 Phase 4 才是下一个可执行阶段,三方输出互相矛盾。
这正是 changeset 所描述的回归现象:"picked the numerically-next phase even when its checkbox was already ticked, persisting it to STATE.md as current_phase"。
二、修复核心:以 ROADMAP 复选框[x]作为完成判据
2.1 完成的定义:复选框即完成
修复的根基是一条早已确立的规则:一个阶段是否完成,由 ROADMAP 中该阶段复选框是否为[x]决定(源码注释引用 #2028:"A phase is complete iff its roadmap checkbox is[x]",见 src/phase.cts)。phase complete在完成某阶段时正是通过改写该复选框来标记完成状态(src/phase.cts 附近的复选框改写逻辑,写入$1x$2 (completed ${today}))。
2.2 收集已完成阶段集合:roadmapCompleteNums
修复在src/phase.cts的 next-phase 解析入口(约 L4364-L4393)新增了"已完成阶段集合"的收集逻辑:
- 通过
extractCurrentMilestone(roadmapContent, cwd)提取当前里程碑文本; - 用正则
-\s*\[[xX]\]\s*(?:\*\*|__)?\s*Phase\s+(PHASE_NUMBER_TOKEN_SOURCE)匹配所有复选框已勾选(大小写不敏感,兼容[x]与[X])的阶段行; - 跳过哨兵阶段 ID(
isSentinelPhaseId,对应SENTINEL_RANGES = [0, 999]的 0.x 草稿与 999.x 积压阶段); - 通过
comparePhaseNum去重——这样02与2会被视为同一阶段,保证零填充(zero-padding)写法不会造成集合成员重复; - 该收集是 best-effort:若
ROADMAP.md不存在或无法解析,集合为空,扫描行为与修复前完全一致(fail-open)。
最终得到谓词isCompletePhaseNum(num),供两路扫描共用。
2.3 两路扫描同时加闸
修复对 stage 1 与 stage 2 做了对称的拦截:
- 磁盘目录扫描(src/phase.cts):遍历
listMilestonePhaseDirs返回的阶段目录时,若roadmapContent !== null && isCompletePhaseNum(dm[1])则continue; - ROADMAP 条目扫描(src/phase.cts):匹配到 heading 或 checkbox 形式的阶段行后,同样对已完成阶段
continue。
两路都保留"数值最小优先"(numeric MINIMUM above N)而非"首个命中"的选择语义,并用comparePhaseNum统一比较。由于该谓词基于comparePhaseNum去重,磁盘上的03-three目录与 ROADMAP 中的Phase 3会被视为同一个已完成阶段,不会漏网。
三、三层 next-phase 解析级联:修复在其中的位置
要理解本次修复的边界,需要先看清phase complete的 next-phase 解析是一个三级级联(源码注释将其称为 3-stage cascading fallback,见 src/phase.cts):
| 阶段 | 数据来源 | 规则 | 关联 issue |
|---|---|---|---|
| Stage 1 | .planning/phases/磁盘目录 | 数值上严格大于 N 的最小目录 | #2245、#3185、#3701 |
| Stage 2 | ROADMAP.md当前里程碑的 heading 与 checkbox 行 | 数值上严格大于 N 的最小阶段行 | #1591、#1729、#4078、#3701 |
| Stage 3 | ROADMAP.md的复选框状态(#2028 最低未完成覆盖) | 若存在编号更低且复选框未勾选[x]的阶段,则把它选为 next | #2028、#2949、#3350 |
本次 #4699 修复针对的是Stage 1 与 Stage 2 的共同盲区:它们只问"谁数值更大",不问"谁还没完成"。而 Stage 3 恰好相反——它专门寻找编号更低但未勾选的阶段,防止phase complete在乱序完成编号最高的阶段时错误地宣告里程碑结束。修复后,三个 stage 在"复选框[x]即完成"这一判据上达成一致:
- 若某阶段已
[x],Stage 1/2 不再选它; - Stage 3 的"最低未完成"覆盖逻辑保持原样,因为
[x]的完成判据本来就与它一致。
解析顺序与优先级保持roadmap wins on identity; the disk wins on spelling(src/phase.cts):两路都找到时以 ROADMAP 的身份为准,仅当二者指向同一阶段时才采纳磁盘的零填充拼写(如03或 slug 名称)。
四、与 roadmap analyze / init progress 的一致性
changeset 强调修复后next_phase与roadmap.analyze、init.progress保持"agreeing"。
- src/roadmap.cts:
cmdRoadmapAnalyze输出next_phase: nextPhase ? nextPhase.number : null,其 next-phase 推导同样基于未完成阶段; - src/init.cts:
init.progress明确注释了 "#3581: the frontier is ROADMAP ORDER, not artifact presence"——从排序后的阶段并集重新推导前沿:第一个状态为pending/not_started且roadmap_complete !== true的阶段即为 next_phase。也就是说,这两个命令从一开始就以"ROADMAP 未完成"为唯一判据,从未被磁盘目录误导;#4699 修复正是把phase complete拉回同一判据,从而消除三者间的分歧。
五、回归测试验证
tests/phase.test.cjs 为本次修复新增了完整的 describe 块phase complete skips already-complete phases as next_phase (#4699),共 5 个用例,覆盖了修复的全部行为面:
| 测试用例 | 场景 | 断言 |
|---|---|---|
| completing phase 2 out of order skips the already-complete phase 3 | Phase 1[x]、Phase 3[x],完成 Phase 2 | next_phase === '4'、is_last_phase === false,且STATE.md中current_phase不匹配3 |
| all later phases already [x] completes the milestone tail | Phase 3、4 均[x],完成 Phase 2 | is_last_phase === true、next_phase === null(里程碑尾部) |
| uppercase [X] checkboxes are recognized as complete | Phase 3 写作[X] | next_phase === '4'(大小写不敏感) |
| checkbox completion matches phase numbers across zero-padding | ROADMAP 写Phase 3、目录写03-three | next_phase === '4'(comparePhaseNum去重生效) |
| an outstanding phase 3 (unchecked) is still selected — negative control | Phase 3 为[ ] | next_phase === '03'(未完成阶段仍是合法候选,且磁盘拼写胜出) |
其中第一例直接断言了 #4699 的"实际危害"——持久化污染:STATE.md不得再携带已完成阶段作为current_phase。第五例作为阴性对照(negative control),确保修复没有过度收紧:只要阶段 3 的复选框未勾选,它依然是合法候选,且输出沿用磁盘的零填充拼写03。
六、适用前提与使用建议
- 修复生效的前提是项目存在可解析的
ROADMAP.md且阶段行使用复选框语法(- [x] **Phase N: Name**或 heading + 复选框组合)。若 ROADMAP 缺失、不可读或仅有 heading 而无复选框,roadmapCompleteNums集合为空,行为退化为修复前的纯数值扫描(fail-open 设计)。 - 若需在命令行查看修复后的行为,可运行
gsd phase complete <N>并检查 JSON 输出中的next_phase/next_phase_name/is_last_phase字段(相关输出字段定义见 src/phase.cts 附近的completed_phase/next_phase/next_phase_name组装);只读探查进度可运行gsd roadmap analyze或gsd init progress对比next_phase结果。 - 阶段复选框是全局完成判据的唯一权威来源,手工编辑 ROADMAP 勾选状态会影响
phase complete、roadmap analyze、init progress三者的一致性,请保持ROADMAP.md的复选框与阶段实际完成情况同步。
七、小结
#4699 是一次典型的"一致性修复":phase complete的 next-phase 选择长期只依赖数值次序,忽略复选框完成状态,导致乱序完成场景下STATE.md的current_phase被污染为已完成的阶段。修复在磁盘扫描与 ROADMAP 扫描两路同时引入roadmapCompleteNums完成集合过滤,使next_phase与roadmap.analyze、init.progress在"ROADMAP 顺序即前沿"的规则下完全对齐,并以 5 个回归测试锁定行为。这一修复同时印证了 GSD 的核心设计原则:阶段目录只是执行痕迹,ROADMAP 复选框才是进度真相。
【免费下载链接】gsd-core
Git. Ship. Done - Core
相关推荐
探索与研究阶段已完成
探索与研究阶段已完成 我已经完整阅读了关联文档 packages/mermaid/src/docs/syntax/gitgraph.md https://lin
图表库前端数据可视化gsd-core 修复解读:3381 修复 —— /gsd-verify-work --ws 通过 SDK 解析工作流阶段
gsd core 修复解读: 3381 修复 —— /gsd verify work ws 通过 SDK 解析工作流阶段 本篇文章基于 .changeset/a
gsd-core 验证门禁修复:human_needed 状态不再放行阶段完成与 Ship 预检
gsd core 验证门禁修复:human_needed 状态不再放行阶段完成与 Ship 预检 导读 :本文基于 gsd core 仓库的变更记录 fix 3
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考