【免费下载链接】gsd-core
Git. Ship. Done - Core
导读
本文基于 gsd-core 仓库中已归档的 changeset(.changeset/archived/witty-geese-purr.md),剖析一次针对"阶段目录命名两套体系并存"问题的修复:当项目在.planning/config.json中配置了project_code后,/gsd-discuss-phase与/gsd-plan-phase两条"首次触达"路径不再生成不带前缀的目录,而是与phase.add/phase.insert保持一致的{PROJECT_CODE}-{NN}-{slug}命名。读完本文,你将理解project_code前缀在整个阶段目录创建链路中的统一规则、共享 helper 的职责边界、对应的工作流改造与回归测试保障,并掌握排查与修复自身仓库中"双头目录"的方法。
一、背景:project_code与阶段目录命名规则
gsd-core 的规划工作区以.planning/phases/存放每个阶段的目录,目录名遵循{NN}-{slug}形态(如01-foundation/)。当一个仓库需要同时管理多个项目(或需要跨项目消歧)时,可以通过在.planning/config.json中设置project_code为阶段目录名增加前缀:
{ "project_code": "XR" }配置后,阶段目录名变为{PROJECT_CODE}-{NN}-{slug},例如XR-01-foundation/。这一能力在 docs/features/project-code-prefixing.md 中被定义为 v1.31 特性,并明确了三条需求约束:
| 需求 | 内容 |
|---|---|
| REQ-PREFIX-01 | 配置了project_code时,阶段目录必须以项目代码为前缀(如ABC-01-setup/) |
| REQ-PREFIX-02 | 未配置project_code时,必须保持标准命名(即无前缀的01-setup/) |
| REQ-PREFIX-03 | 前缀必须在所有阶段操作中一致应用 |
配置项的完整说明见 docs/CONFIGURATION.md:
| 设置 | 类型 | 默认值 | 说明 |
|---|---|---|---|
project_code | string | (无) | 阶段目录名前缀,如"ABC"生成ABC-01-setup/(v1.31 新增) |
从 src/phase-id.cts 的注释与实现可以看出,project_code遵循[A-Z][A-Z0-9_]*语法(允许数字与下划线,且不限于 6 位),这一形态与配置校验、以及解析路径上用于剥离前缀的stripProjectCodePrefix(见 docs/adr/2121-phase-identifier-parsing-consolidation.md)保持同一份语法来源。
二、问题:两条创建路径造成的"双头目录"
2.1 现象描述
changeset 原文给出了非常具体的故障画像:当project_code已配置时,同一仓库的.planning/phases/下会出现两种风格并存的目录名——
01-foundation/ # 无前缀(由 discuss-phase / plan-phase 首次触达创建) XR-02.1-spike/ # 带前缀(由 phase.add / phase.insert 创建)这种01-foundation/与XR-02.1-spike/混排的"两套命名体系"(two-headed naming convention)会造成阶段目录在解析、归档、状态统计等多个环节的歧义。
2.2 成因:首次触达路径漏掉前缀
/gsd-discuss-phase与/gsd-plan-phase属于"首次触达"路径——当一个阶段尚未建立目录时,由这两条流程负责把目录"从无到有"创建出来。修复前,这两条路径在计算目标目录名时没有套用project_code前缀,而phase.add/phase.insert在创建目录时却已经带上了前缀。于是同一条 ROADMAP 上后续追加的阶段带前缀、先前由讨论/规划流程首建的阶段不带前缀,目录树便出现了"双头"。
2.3 潜在危害
- 解析歧义:目录匹配逻辑(如 src/phase-locator.cts 中的
searchPhaseInDir/findPhaseInternal)依赖目录名的 token 匹配;同一裸阶段号可能命中多个目录,触发歧义告警(源码中会提示 "Set a distinct project_code in .planning/config.json to scope resolution",见 src/phase.cts)。 - 状态漂移:验证、UAT、归档等按目录扫描的功能会漏读或重复读到阶段产物。
- 跨项目串扰:多个项目共享一个
.planning/phases/树时,无前缀目录无法归属到正确的项目。
三、修复方案:把阶段目录创建收敛到共享 helper
changeset 的修复策略很清晰:将阶段目录命名与创建统一路由到单个共享 helper(当时命名为getPhaseDirName),从源头杜绝各调用方各自拼装目录名导致的漂移。
在当前仓库中,承担这一"规范化切点"职责的代码可以对应到两处:
- src/phase-id.cts 的
getPhaseDirFromPhaseId:纯函数式地根据phaseId、阶段名与projectCode生成规范化目录名——零填充的里程碑/阶段号 + 规范化 slug + 可选的项目代码前缀,并委托core-utils的共享 slug 生成逻辑,避免各调用方重复实现 slug 公式。 - src/init.cts 中
expected_phase_dir的推导:cmdInitPlanPhase(约 L1210-L1221)与cmdInitPhaseOp(约 L2304-L2315)在阶段目录尚不存在时,按同一公式计算出"应当存在的目录路径"并回传给工作流,工作流只需mkdir -p "${expected_phase_dir}"即可。
3.1 各创建路径的前缀统一现状
修复后,仓库内所有阶段目录创建入口都遵循{prefix}{NN}-{slug}或{prefix}{NN.MM}-{slug}的同一公式:
| 创建入口 | 位置 | 前缀应用 |
|---|---|---|
phase.add | src/phase.cts | const prefix = projectCode ? \${projectCode}-` : '',目录名拼入prefix` |
phase.add-batch | src/phase.cts | 同一prefix公式 |
phase.insert | src/phase.cts | _dirName = \${pfx}${_decimalPhase}-${slug}`(含子阶段号,如XR-02.1-spike`) |
scaffold phase-dir | src/commands.cts | 代码注释明确标注#3287:应用project_code前缀以与phase.add/phase.insert保持一致 |
init plan-phase/init phase-op的expected_phase_dir | src/init.cts | 推导时拼入前缀,供工作流首次触达使用 |
可见#3287这条修复不仅覆盖了 changeset 中提到的两条工作流命令,还顺带补齐了scaffold phase-dir这一手工脚手架入口,形成全链路一致。
四、工作流侧配套:从mkdir裸拼到expected_phase_dir
仅靠命令侧统一还不够——工作流文档中的"首次触达"步骤此前可能使用裸的mkdir -p .planning/phases/{NN}-{slug},一旦手工拼装就会再次漏掉前缀。修复后,工作流统一改为消费 init 返回的expected_phase_dir:
- gsd-core/workflows/discuss-phase.md(Find or create phase directory):读取 init 返回的
phase_dir、expected_phase_dir、phase_slug、padded_phase;当phase_dir为 null 时执行mkdir -p "${expected_phase_dir}",然后phase_dir="${expected_phase_dir}"。 - gsd-core/workflows/plan-phase.md:
phase_found为 false 时先校验 ROADMAP 中存在该阶段,再使用expected_phase_dir(注释明确说明"includesproject_codeprefix when set")创建目录。 - gsd-core/workflows/add-backlog.md:先读取
project_code,再拼接带前缀的 backlog 目录名,保持与所有其他阶段创建路径一致。
这种"让命令计算、让工作流消费"的设计,把命名规则收敛在单一职责的 helper 中,避免了工作流文档各自为政地手写目录名公式。
五、测试保障:前缀一致性回归套件
修复不是孤证,tests/phase.test.cjs 中沉淀了完整的回归测试:
- bug-3287 套件(tests/phase.test.cjs#L7088-L7252):构造
project_code: "XR"的临时项目(makeXRProjectfixture),断言:phase.add生成XR-02-auth-service而非裸02-auth-service;init phase-op 1在目录不存在时返回expected_phase_dir,且路径同时包含XR-前缀与阶段 slug;- 未设置
project_code时,expected_phase_dir不得匹配^[A-Z][A-Z0-9]*-前缀形态(即保持标准命名,对应 REQ-PREFIX-02); init plan-phase 1对expected_phase_dir的同一组断言。
- bug-3298 套件(tests/phase.test.cjs#L7255-L7304):直接扫描
gsd-core/workflows/import.md等工作流文件内容,用正则禁止出现裸mkdir .planning/phases/{NN}-{slug}模板与裸 shell 变量拼装模式,并要求必须引用expected_phase_dir——把"防漂移"推进到了文档内容层。
这两层测试分别从"命令输出正确"与"工作流文档不再手写裸目录名"两个维度锁死了回归。
六、实践指南:在自己的仓库中检查与落地
6.1 检查是否存在"双头目录"
在使用了project_code的 gsd-core 项目中,直接查看规划目录:
ls .planning/phases/如果同时出现01-foundation/(无前缀)与XR-02.1-spike/(有前缀)两种形态,即命中本文所述问题;可进一步通过gsd_run query config-get project_code --raw确认配置值。
6.2 如何让后续创建保持一致
- 确保
.planning/config.json中的project_code为期望的项目代码(如XR); - 阶段目录的创建一律交给命令侧完成:使用
phase add/phase insert/scaffold phase-dir,或在init plan-phase/init phase-op返回expected_phase_dir后由工作流mkdir -p,不要在脚本中手工拼装{NN}-{slug}; - 若已存在无前缀的历史目录,可将其重命名为带前缀的规范名(注意同步更新引用该目录路径的文档与状态),使命名收敛到单一体系。
6.3 与 bracket 约定的关系
project_code前缀是独立于phase_id_convention的维度。即便采用"bracket"约定(标题形如### [GSD.02] 05: Name、目录形如GSD.02-05-name),bracket 发射路径也只由config.phase_id_convention === 'bracket'门控,而非由project_code是否存在门控——详见 docs/adr/612-bracket-phase-id-convention.md。也就是说,配置了project_code但未启用 bracket 的仓库,同样必须遵守本文所述的前缀一致性规则。
七、相关证据与延伸阅读
- 原始变更记录:.changeset/archived/witty-geese-purr.md(
type: Fixed,关联 PR 3292,已归档) - 发布说明中的同源记录:docs/RELEASE-NOTES-LEGACY.md(标注为 #3287)
- 特性规格:docs/features/project-code-prefixing.md
- 配置参考:docs/CONFIGURATION.md
- 核心实现:src/phase.cts、src/commands.cts、src/init.cts、src/phase-id.cts
- 工作流:gsd-core/workflows/discuss-phase.md、gsd-core/workflows/plan-phase.md、gsd-core/workflows/add-backlog.md、gsd-core/workflows/import.md
- 回归测试:tests/phase.test.cjs
- 关联架构决策:docs/adr/612-bracket-phase-id-convention.md、docs/adr/2121-phase-identifier-parsing-consolidation.md
小结
witty-geese-purr这条 changeset 代表了一类典型的工程治理手段:当多个入口各自实现同一命名规则时,漂移只是时间问题。gsd-core 的解法——统一共享 helper、命令侧计算期望路径、工作流侧只消费不拼装、测试同时锁定命令行为与文档内容——为project_code前缀的一致性提供了从实现到文档再到回归的完整闭环,值得在阅读源码与二次开发时对照参考。
【免费下载链接】gsd-core
Git. Ship. Done - Core
相关推荐
gsd-core Progress 路由的规划前假设检查:统一规范化 /gsd-discuss-phase 替代路径
gsd core Progress 路由的规划前假设检查:统一规范化 /gsd discuss phase 替代路径 导读 :本篇文章以 gsd core 仓库
gsd-core 修复 Phase 目录 project_code 前缀漂移:3298 让 import 与 backlog 工作流统一遵守目录命名约定
gsd core 修复 Phase 目录 project_code 前缀漂移: 3298 让 import 与 backlog 工作流统一遵守目录命名约定 本篇
gsd-core `/gsd-discuss-phase` 模式路由修复:让 `workflow.discuss_mode: assumptions` 在 shim-only 安装下也能被正确遵守
gsd core /gsd discuss phase 模式路由修复:让 workflow.discuss_mode: assumptions 在 shim o
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考