- 人工智能
- AI 应用
- 提示工程
- 开发工具
- 工作流自动化
- AI Agent
【免费下载链接】get-shit-done
A light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.
导读
本文围绕 get-shit-done 仓库中一条type: Fixed的 changeset(.changeset/clever-wasps-parade.md,PR #3380)展开,深入剖析其描述的核心缺陷与修复:roadmap.update-plan-progress在收到零填充(padded)相位参数时,此前会静默跳过对未填充(unpadded)ROADMAP 正文中进度行与复选框的更新。读完本文,你将理解该命令的完整行为契约、零填充宽容正则(padding-tolerant regex)的实现原理、8 个调用点为何会集体“漂移”,以及仓库如何用奇偶一致性(parity)测试守住这条修复不倒退。
一、changeset 说了什么:一条短小但关键的修复记录
.changeset/clever-wasps-parade.md全文仅两段:
--- type: Fixed pr: 3380 --- roadmap.update-plan-progress now updates unpadded ROADMAP phase rows and checkboxes when called with zero-padded phase arguments.这条 changeset 属于仓库的标准发布说明体系(scripts/changeset/下存在完整的解析、渲染、lint 与 GitHub release notes 生成管线,配套测试见 tests/changeset-*.test.cjs)。它记录了一次“Fixed”级别的缺陷修复,关键词有三个:
- 命令:
roadmap.update-plan-progress,一个把磁盘上的 PLAN/SUMMARY 计数同步回ROADMAP.md的命令; - 缺陷场景:调用方传入了零填充相位参数(如
02.7); - 修复效果:现在能正确更新未填充的 ROADMAP 相位行(progress table row)与复选框(checkbox)。
要理解这次修复的份量,需要先弄清楚这个命令本身是做什么的、它更新哪些内容,以及“填充”不一致为什么会让更新静默失效。
二、命令定位:roadmap update-plan-progress是什么
在 get-shit-done 的规划体系中,.planning/ROADMAP.md是人与 Agent 共同维护的路标:它包含里程碑下的相位列表(- [ ] **Phase 2.7: ...**)、每个相位的详情小节(### Phase 2.7: ...、**Plans:** N plans、计划清单)以及进度总表(## Progress下的| Phase | Plans | Status | Completed |表格)。而真实的执行证据在磁盘上:phases/目录中每个相位的NN-01-PLAN.md与对应的NN-01-SUMMARY.md文件。
roadmap.update-plan-progress的作用就是以磁盘为事实源,回写 ROADMAP 的展示层。它有两种调用形式:
- CLI 形式(见 docs/CLI-TOOLS.md 的 Roadmap Commands 一节):
node gsd-tools.cjs roadmap update-plan-progress <N>- SDK query 形式(工作流内统一使用,见 get-shit-done/workflows/execute-plan.md 与 get-shit-done/workflows/execute-phase.md):
gsd-sdk query roadmap.update-plan-progress "${PHASE}"此外,命令还兼容--phase <N>旗标形式,以兼容 SDK 参数解析场景(对应 CHANGELOG 中 #2796 的修复:位置参数解析曾把字面量--phase当作相位值)。在查询注册表中,该命令被声明为mutation: true、outputMode: 'json'(见 sdk/src/query/command-manifest.roadmap.ts),路由经由 get-shit-done/bin/lib/roadmap-command-router.cjs 在 CJS 实现与 SDK 实现之间桥接(存在GSD_WORKSTREAM或 SDK 不可用时回退到 CJS)。
三、命令的完整行为契约:一次调用改四处
CJS 实现位于 get-shit-done/bin/lib/roadmap.cjs 的cmdRoadmapUpdatePlanProgress,SDK 移植版位于 sdk/src/query/roadmap-update-plan-progress.ts。两者逻辑一致,完整流程如下。
3.1 从磁盘统计相位
先通过findPhase定位相位目录并统计文件:plans为*-PLAN.md(或裸PLAN.md)文件列表,summaries为*-SUMMARY.md(或裸SUMMARY.md)列表(见 sdk/src/query/phase.ts 的getPhaseFileStats)。相位不存在时报错Phase ${phaseNum} not found;没有计划文件时直接返回:
{ "updated": false, "reason": "No plans found", "plan_count": 0, "summary_count": 0 }ROADMAP.md不存在时返回{ "updated": false, "reason": "ROADMAP.md not found", ... }。
3.2 判定状态并计算日期
const isComplete = summaryCount >= planCount; const status = isComplete ? 'Complete' : summaryCount > 0 ? 'In Progress' : 'Planned'; const today = new Date().toISOString().split('T')[0];状态机为:Complete(SUMMARY 数 ≥ PLAN 数)、In Progress(有部分 SUMMARY)、Planned(尚无 SUMMARY)。
3.3 四处 ROADMAP 突变
在 CJS 中以withPlanningLock包裹整个读-改-写(防并发写坏),SDK 中对应readModifyWriteRoadmapMd的原子写。共四处修改:
进度表行:匹配以相位号开头的表格行,支持 4 列(
Phase | Plans | Status | Completed)与 5 列(多出 Milestone 列)两种形态,分别更新 Plans 列(summaryCount/planCount)、Status 列(status.padEnd(11)保持对齐)与 Completed 列(完成时写入YYYY-MM-DD,否则留空)。详情小节
**Plans:**行:在### Phase N:小节内定位**Plans:**,替换为N/N plans complete(完成)或N/N plans executed(进行中)。注意这里使用了[ \t]*而非\s*——CHANGELOG 记录了 #2728 的教训:\s*会跨换行匹配,导致**Plans:**单独成行时误吞下一行计划复选框;同时用小节边界前瞻防止越界改到下一个相位的**Plans:**。相位总复选框:相位完成时,把总览清单里的
- [ ] **Phase N: ...**翻转为- [x] **Phase N: ...** (completed YYYY-MM-DD)。计划级复选框:遍历每个 SUMMARY 文件,把对应的
- [ ] 50-01-PLAN.md、- [ ] 50-01:、- [ ] **50-01**等形态翻转为- [x](正则(-\\s*\\[) (\\]\\s*(?:\\*\\*)?<planId>(?:\\*\\*)?))。
成功后返回:
{ "updated": true, "phase": "<phaseNum>", "plan_count": 3, "summary_count": 3, "status": "Complete", "complete": true }CLI 的非 raw 输出还会附带一行人类可读提示,如3/3 Complete。
3.4 工作流中的实际使用
该命令是执行流水线的“追踪写入点”:
- get-shit-done/workflows/execute-plan.md 的
update_roadmap步骤:每个计划完成后同步一次(仅非 worktree 模式;worktree 模式下各工作树各自持有 ROADMAP,由编排者统一合并,避免兄弟工作树写分叉——即 #2661 的单写者契约); - get-shit-done/workflows/execute-phase.md 的 5.7 节:每波 worktree 合并后,对每个完成计划调用
gsd-sdk query roadmap.update-plan-progress "${PHASE_NUMBER}" "${plan_id}" "complete",且仅当TEST_EXIT为 0(测试通过)才更新追踪文件——测试失败或超时(124)时保留计划为进行中状态,并把ROADMAP.md/STATE.md的变更用commit --files提交。
四、缺陷根源:padded 参数 vs unpadded 正文(#3537)
为什么“传入零填充参数”会导致更新失败?问题出在正则匹配上。get-shit-done 的技能层在解析出相位目录后,倾向于把填充后的形态(如02.7,因为磁盘目录统一零填充为01-name、02.7-...样式)传给命令;而人类手写的 ROADMAP 正文约定俗成使用未填充形态(### Phase 2.7:、- [ ] **Phase 2.7:**)。若正则片段是escapeRegex(phaseNum)(精确转义),那么02.7永远匹配不到2.7,命令“报告成功、文件却毫无变化”——静默空转(silent no-op)。
回归测试 tests/bug-3537-padded-id-against-unpadded-roadmap.test.cjs 的头部注释精确记录了漂移过程:
v1.42.1 added
phaseMarkdownRegexSource()which renders0*<integer><...>— padding-tolerant on both sides — but wired it into only 1 of 8 call sites. The other 7 used rawescapeRegex(phaseNum)or0*${escapeRegex(...)}(tolerated extra padding, not missing), so passing the padded form silently no-op'd and the verbs returned success while ROADMAP.md was unchanged.
即:0*${escapeRegex(...)}只能容忍“多出的填充”(2能匹配02),不能容忍“缺失的填充”(02匹配不到2)。修复只接进了 1 个调用点,其余 7 个继续漂移。本次 changeset(PR #3380)所记录的,正是把宽容两侧填充的正则源接入roadmap.update-plan-progress相关调用点的完整落地,使“padded 参数 → unpadded 正文”也能产生真实的突变。
五、修复机制:phaseMarkdownRegexSource的宽容匹配原理
核心工具函数位于 get-shit-done/bin/lib/core.cjs 的phaseMarkdownRegexSource:
function phaseMarkdownRegexSource(phaseNum) { const stripped = String(phaseNum).replace(/^[A-Z]{1,6}-(?=\d)/i, ''); const match = stripped.match(/^0*(\d+)([A-Z])?((?:\.\d+)*)$/i); if (!match) return escapeRegex(phaseNum); const integer = match[1].replace(/^0+/, '') || '0'; const letter = match[2] ? escapeRegex(match[2]) : ''; const decimal = match[3] ? escapeRegex(match[3]) : ''; return `0*${escapeRegex(integer)}${letter}${decimal}`; }原理三步:
- 剥离项目码前缀:
^[A-Z]{1,6}-(?=\d)去掉CK-这类前缀(目录CK-02.7-...→ 相位号02.7); - 解析数字结构:把
0*的整数部分、可选字母后缀(12A中的A)、可选的十进制段(.7、.1.2)拆开; - 重排为宽容片段:整数部分去掉前导零后,前面补
0*。于是02.7生成0*2\.7,既能匹配2.7也能匹配02.7甚至002.7。
对非数字的自定义 ID(如PROJ-42)则回退为精确转义,保证调用点可以无条件替换使用。配套函数phaseMarkdownRegexSourceExact(#3599)处理项目码前缀形态(PROJ-42需先精确匹配### Phase PROJ-42:,再回退到数字宽容形态),避免与恰好共享尾号的裸### Phase 42:交叉误匹配。
从当前 get-shit-done/bin/lib/roadmap.cjs 的调用点可见修复已全面落地:roadmap analyze的复选框检测(第 270 行)、update-plan-progress的表行/Plans/复选框正则(第 374 行)以及annotate-dependencies(第 535 行)均使用phaseMarkdownRegexSource;phase.cjs的精确形态调用点则使用phaseMarkdownRegexSourceExact(第 151 行)。测试文件注释中还披露了一个边界:同一份 ROADMAP 内混合填充是合法且真实存在的,因此“heading 用2.7、总结区复选框用02.7”也必须同时可匹配。
六、测试防线:奇偶一致性断言
这次修复最值得借鉴的是测试方法论。tests/bug-3537-padded-id-against-unpadded-roadmap.test.cjs构造了一个精确复刻 #3537 报告的 fixture:项目码CK、填充目录CK-02.7-meta-lead-ads/、未填充正文Phase 2.7,然后对同一 fixture 分别用 padded 与 unpadded 参数各跑一遍,断言两个结果 ROADMAP 的字节完全一致(expectParity)。其核心思想是:
- 非空洞通过(non-vacuous pass):除了断言两者相等,还断言
- [x] **Phase 2.7:确实发生了翻转——否则“两边都静默空转”也能通过等值断言,测试就失去了意义; - 覆盖所有受影响动词:
phase complete、roadmap get-phase(--raw)、phase next-decimal、phase insert、roadmap annotate-dependencies、roadmap update-plan-progress; - 反向回归:
phase next-decimal中额外断言“不能因为扫描失败就跳过已存在的2.7而错误提议02.1”。
SDK 侧的单测 sdk/src/query/roadmap-update-plan-progress.test.ts 则用更细粒度断言锁住四处突变:padded 参数03驱动 unpaddedPhase 3的复选框翻转、**Plans:** 1/1 plans complete、表格行| 3. build | 1/1 | Complete | 2026-..-.. |、计划复选框- [x] 03-01-PLAN.md;并回归 #2728 的两个坑:**Plans:**单独成行时不得覆盖其后的计划列表,且不得越界改写下一个相位(Phase 8/Phase 10)的**Plans:**行。
七、结论与使用建议
roadmap.update-plan-progress是 get-shit-done“磁盘即事实源、ROADMAP 即展示层”设计的关键同步器。本次 changeset 修复的实质,是把相位号匹配从“精确形态”升级为“任意填充形态”:无论调用方传2.7还是02.7,无论 ROADMAP 正文写作Phase 2.7还是Phase 02.7,命令都应产生字节一致的突变结果。
给使用者的建议:
- 调用时可放心传 padded 形态(如技能层解析出的
02.7),命令已能正确回写未填充正文,不必再手动归一化相位号; - 利用
--raw输出调试:CLI 的 raw 模式直接输出 JSON 载荷,便于确认updated、status、complete字段是否符合预期; - 留意失败语义:
No plans found/ROADMAP.md not found会返回updated: false而非报错,工作流编排时应检查该字段; - 保持 fixture 覆盖:若未来新增任何解析 ROADMAP 相位号的调用点,应复用
phaseMarkdownRegexSource,并补一条“padded 与 unpadded 奇偶一致”测试,防止下一个调用点重新漂移——这正是 #3537 用 8 个调用点漂移换来的教训。
相关参考:docs/CLI-TOOLS.md(命令速查)、CHANGELOG.md(#2728、#2796 等相邻修复)、sdk/src/query/QUERY-HANDLERS.md(query 注册表)。
- 人工智能
- AI 应用
- 提示工程
- 开发工具
- 工作流自动化
- AI Agent
【免费下载链接】get-shit-done
A light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.
相关推荐
gsd-core 修复详解:零填充 Phase 参数如何正确更新未填充的 ROADMAP 进度表
gsd core 修复详解:零填充 Phase 参数如何正确更新未填充的 ROADMAP 进度表 导读 本文剖析 gsd core(Git. Ship. Don
get-shit-done 修复 3599 深度解析:roadmap get-phase 如何正确命中 project-code 前缀阶段 ID
get shit done 修复 3599 深度解析:roadmap get phase 如何正确命中 project code 前缀阶段 ID 本文基于仓库中
人工智能AI 应用提示工程开发工具工作流自动化AI Agentget-shit-done 阶段正则扇出修复:让 02.7 与 2.7 双向匹配的 ROADMAP 解析机制
get shit done 阶段正则扇出修复:让 02.7 与 2.7 双向匹配的 ROADMAP 解析机制 本文基于仓库中 .changeset/3537 p
人工智能AI 应用提示工程开发工具工作流自动化AI Agent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考