☰
gsd-core 相位完成修复解析:next_phase 如何跳过已勾选 [x] 的已完成阶段
2026/9/28 3:25:30 网站建设 项目流程

【免费下载链接】gsd-core

Git. Ship. Done - Core

项目地址:https://gitcode.com/gh_mirrors/ge/gsd-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 扫描存在两个未考虑完成状态的问题:

  1. 磁盘目录扫描(stage 1)和 ROADMAP 条目扫描(stage 2)都以"数值上严格大于 N 的最小阶段号"作为候选,从不检查该阶段的复选框是否已经是[x];
  2. 一旦选中,该结果会通过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 2ROADMAP.md当前里程碑的 heading 与 checkbox 行数值上严格大于 N 的最小阶段行#1591、#1729、#4078、#3701
Stage 3ROADMAP.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 3Phase 1[x]、Phase 3[x],完成 Phase 2next_phase === '4'、is_last_phase === false,且STATE.md中current_phase不匹配3
all later phases already [x] completes the milestone tailPhase 3、4 均[x],完成 Phase 2is_last_phase === true、next_phase === null(里程碑尾部)
uppercase [X] checkboxes are recognized as completePhase 3 写作[X]next_phase === '4'(大小写不敏感)
checkbox completion matches phase numbers across zero-paddingROADMAP 写Phase 3、目录写03-threenext_phase === '4'(comparePhaseNum去重生效)
an outstanding phase 3 (unchecked) is still selected — negative controlPhase 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

项目地址:https://gitcode.com/gh_mirrors/ge/gsd-core
点击查看免费下载
上一篇:智慧树自动刷课神器:Autovisor完整使用指南
下一篇:如何让经典Flash内容在现代电脑上重获新生:CefFlashBrowser完全指南

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

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

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

立即咨询