news 2026/9/24 13:52:32

gsd-core 中 bracket 阶段 ID 约定的统一显示与配置校验解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
gsd-core 中 bracket 阶段 ID 约定的统一显示与配置校验解析

【免费下载链接】gsd-core

Git. Ship. Done - Core

项目地址:https://gitcode.com/gh_mirrors/ge/gsd-core
点击查看免费下载

本文围绕 gsd-core 的 ADR-612「bracket 阶段 ID 约定」显示面落地(issue #3638 / PR 4111)展开:当一个项目通过phase_id_convention: "bracket"显式 opt-in 后,progressstats、manager init 以及两种 statusline 格式会以统一的规范形态[CODE.MM] NN渲染阶段标识;而未 opt-in 的项目(nullsequentialmilestone-prefixed)则保留原有输出形状,字节级不变。同时config-set为该键新增了枚举校验,只接受三个受支持值。读完本文,你将掌握 bracket 约定的完整语法、各显示面的行为差异、底层规范解析/渲染对的实现原理,以及如何安全地配置与验证这一约定。

一、背景:ADR-612 bracket 约定到底是什么

gsd-core 的phase_id_convention配置键控制项目阶段 ID 的命名约定。在 docs/CONFIGURATION.md 中,它被定义为取值"sequential""milestone-prefixed""bracket"null的枚举,默认值为null

  • null/"sequential":沿用传统数字 ID(Phase 1Phase 2);
  • "milestone-prefixed":使用编码了所在 milestone 的全局唯一 ID(Phase 1-01Phase 1-02),并且是当前发布线唯一的roadmap upgrade迁移目标;
  • "bracket":把 milestone 前置到阶段编号之前——标题写作### [GSD.02] 05: Name,目录写作GSD.02-05-name

"bracket"是 opt-in 的:仓库必须显式把phase_id_convention设为"bracket",显示面才会切换到新的标识形态。这一点在 gsd-core/references/phase-id-convention.md 开头直接写明:"The bracket convention is opt-in throughphase_id_convention: "bracket"."

1.1 规范的 bracket 语法卡片

该约定的紧凑语法卡片由 src/phase-id-card.cts 单一来源生成(PHASE_ID_CARD常量),渲染站点通过phaseIdCard()引入,tests/phase-id-card.test.cjs 保证生成文档与其字节一致:

[GSD.02] 05.03-01 │ │ │ │ │ │ │ │ │ └── plan 01 │ │ │ └────── subphase 03 │ │ └───────── phase 05 │ └───────────── milestone 02 └───────────────── project GSD milestone = bracket integer; dots = phase-levels; one hyphen = plan; no 'Phase' word, no vX.Y

两种等价形态:

  • 显示形式[PROJECT.MM] PP[.SS][-LL]:方括号承载 project 与 milestone,点号连接阶段层级,单一连字符引入可选的 plan;
  • 目录形式PROJECT.MM-PP[.SS]-slug/:同一身份不加方括号编码到目录名中,末尾是 slug。

面向人的 bracket 显示面同时省略了字面的Phase标签和传统vX.Ymilestone 标记(参见 gsd-core/references/phase-id-convention.md)。

二、约定门控:只有精确的"bracket"才改变显示

#3638 的核心设计原则是精确门控(exact gating):显示面只在phase_id_convention精确等于"bracket"时才渲染规范的[CODE.MM] NN标识;其他约定(nullsequentialmilestone-prefixed,以及任何未识别值)保留原有的模式与输出形状。

这一点在 docs/CONFIGURATION.md 中有明确说明:"A project on any other value compiles the same heading patterns and retains the same output shape it did before."

从源码看,这一门控贯穿了所有相关模块。以 src/phase-id.cts 为例,多个核心辅助函数都以可选参数convention作为 ADR-2121 的「增量形态」约定——不传参会字节级等价于旧行为:

  • phaseHeadingPrefixSrcFor(src/phase-id.cts):if (convention !== 'bracket') return base;——非 bracket 约定编译的是站点原有的基准标题前缀源码(ANY_BRACKETLABEL_ONLY),而不是其超集;
  • getMilestoneFromPhaseId(src/phase-id.cts):bracket 分支从[PROJECT.MM]/{CODE}.{MM}-前缀读取 milestone(READING-B),非 bracket 路径保留传统前导整数规则(READING-A);
  • extractPhaseToken(src/phase-id.cts):bracket 目录{CODE}.{MM}-{PP}[.{SS}]-slug→ 阶段 tokenPP[.SS],同样被convention === 'bracket'门控。

门控而非「检测」的原因是字符串层面的不可区分性(ADR-2121):bracket 目录{CODE}.{MM}-{PP}与 legacy#1324字母前缀小数族(如P0.3-2P0.12-34)在 project code 以数字结尾时无法用纯字符串规则区分。自动检测会静默地把P0.3-2重新解释为2,造成关键辅助函数上的字节级读取回归——所以必须显式约定信号。

三、四个显示面的行为详解

3.1progress/stats:新增display_id字段

在 bracket 项目上,progressstats的 JSON 输出在两个层面发生变化(见 docs/CLI-TOOLS.md):

  • 每个阶段保持裸的 join key 在phases[].number,同时新增规范的人类可读标签phases[].display_id,例如{"number":"05.03","display_id":"[GSD.02] 05.03"}
  • milestone_version和表格标题使用[GSD.02],而不是 legacy 的v2.0标记。

对应的表格渲染从| 05.03 | display slice |变为| [GSD.02] 05.03 | display slice |,且不再出现v2.0

在非 bracket 项目上,行为由 tests/adr-612-bracket-display.property.test.cjs 钉死:number保持05.03,并且不出现display_id字段Object.hasOwn(output.phases[0], 'display_id') === false),表格保持| 05.03 | ... |原样。

3.2 manager init

init manager在 bracket 项目上读取无标签 bracket 标题(如### [GSD.02] 05.03: Display Slice)并输出规范的display_id。测试(tests/adr-612-bracket-display.property.test.cjs)断言其输出:

{ "number": "05.03", "display_id": "[GSD.02] 05.03", "name": "Display Slice", "disk_status": "planned" }

3.3 两种 statusline 格式

hooks/gsd-statusline.js在两种 statusline 格式(full 与 compact)上都实现了 bracket 门控。其配置解析入口(hooks/gsd-statusline.js)把phase_id_convention === 'bracket'解析为convention: 'bracket'传入渲染函数;渲染时(hooks/gsd-statusline.js 与 hooks/gsd-statusline.js)通过opts.convention === 'bracket'决定:

  • milestone 显示用[GSD.02]替代v2.0
  • 阶段显示用 bracket 阶段标签(如[GSD.02] 05.03)替代P05/12之类的 legacy 形态。

测试(tests/adr-612-bracket-display.property.test.cjs)同时断言两种格式在 bracket 下匹配/\[GSD\.02\]/且不匹配/v2\.0/,并在sequential下保留精确的 legacy 字符串(见同文件 tests/adr-612-bracket-display.property.test.cjs)。

3.4 一个统一的语法决策:milestone 标签 = 阶段显示前缀

renderMilestoneId(id)返回[GSD.02]renderPhaseId(id)返回`${renderMilestoneId(id)} 05.03-01`——milestone 标签被钉死为所有 bracket 阶段显示共享的前缀(见 tests/adr-612-bracket-display.property.test.cjs)。显示适配层 src/phase-id-display.cts 中的renderBracketMilestoneDisplay('v2.0', 'GSD')能把 legacy 的v2.0元数据翻译成[GSD.02]

四、底层实现:规范解析/渲染对与显示适配层

4.1parsePhaseId/renderPhaseId/toDir:单一可信的 round-trip 模型

src/phase-id.cts 定义了唯一的可往返 bracket 模型(ADR-612 Decision 4)。PhaseId结构类型包含projectmilestone(零填充)、phase(零填充)、可选subphase与可选plan(仅文件名面)。

关键实现事实:

  • 规范一致性由构造保证parsePhaseId在解析显示形式[PROJECT.MM] PP[.SS][-LL]或目录形式{PROJECT}.{MM}-{PP}[.{SS}][-{plan|slug}]后,会重新渲染并逐字节比对输入,拒绝非填充数字([GSD.5] 5)、过度填充([GSD.005] 05)与多空格([GSD.02] 05),统一抛错而不是静默归一化;
  • render(parse(x)) === x契约由属性测试钉死:tests/adr-612-bracket-display.property.test.cjs 用 fast-check 对任意(project, milestone, phase, subphase)组合断言renderPhaseId(parsePhaseId(display)) === displaytoDir(parsePhaseId(display), 'display slice')精确命中手写目录;
  • toDir的写入侧校验:project 必须匹配[A-Z][A-Z0-9_]*,milestone/phase/subphase 必须匹配规范的数值宽度(恰好 2 位,或 3 位以上且无前导零——由BRACKET_CANONICAL_NUMERIC_SOURCE统一持有),slug 必须清洗为非空、非全数字的 token,防止路径穿越与磁盘↔身份双射破坏。

4.2 显示适配层:只做边界归一化

src/phase-id-display.cts 的定位是「纯适配器」:STATE.md和 milestone 元数据仍暴露 legacy 的vN.0标记,显示面需要把它翻译成 bracket 身份,但不重复实现另一套渲染器。renderBracketPhaseDisplayrenderBracketMilestoneDisplay只做数值边界归一化(剥离v前缀、拆分v2.0、规范数值宽度),随后委托给 src/phase-id.cts 的parsePhaseId/renderMilestoneId/renderPhaseId规范对;元数据不完整或无效时返回null,让装饰性调用方优雅降级而不是破坏命令/statusline 渲染。

4.3 约定的一次性解析与线程化

branch 文档 docs/adr/612-bracket-phase-id-convention.md 指出,消费方遵循「解析一次、显式线程化」的纪律:例如roadmap-command-router.cts在 src/roadmap-command-router.cts 中于消费方之前一次性解析phase_id_convention(含从 ROADMAP frontmatter 回退读取),再作为参数传入各消费方;src/roadmap.cts 同样把resolvePhaseIdConvention的结果线程化到阶段目录扫描(scopeToPhasematchPhaseDirs)等调用链,而不是在每个站点重新读取配置。

五、config-set的枚举校验:三个受支持值

#3638 的另一半是配置侧校验。此前phase_id_convention是一个未校验的魔法字面量;现在config-set只接受精确的三个值。

5.1 源码中的枚举定义

src/config.cts 定义了:

// ADR-612 PR-5: configuration accepts every convention the runtime can read. // Keep this distinct from roadmap-upgrade's supported target set: sequential // is valid project configuration but is not a migration destination. const VALID_PHASE_ID_CONVENTIONS: readonly string[] = Object.freeze([ 'sequential', 'milestone-prefixed', 'bracket', ]);

设置路径(src/config.cts)通过assertEnumValue(parsedValue, val, VALID_PHASE_ID_CONVENTIONS, 'phase_id_convention')校验;null用于取消该键(config-set phase_id_convention null会移除键本身,且保留兄弟配置项,见 tests/config.test.cjs)。

5.2 测试钉死的校验行为

tests/config.test.cjs 覆盖了完整行为矩阵:

  • 三个受支持值sequentialmilestone-prefixedbracket都能成功写入并被回读;
  • null取消键,且不影响model_profile等兄弟配置;
  • 不支持值(free-form)与大小写不匹配值(Bracket)都被拒绝,错误信息包含Invalid phase_id_convention与支持集合sequential, milestone-prefixed, bracket
  • sequential是合法项目配置,但不是roadmap upgrade --convention的迁移目标(迁移只接受milestone-prefixed)。

5.3 配置方式

# 在项目根目录设置 bracket 约定 gsd-tools config-set phase_id_convention bracket # 取消约定(键被移除,回到 null 行为) gsd-tools config-set phase_id_convention null

或直接在.planning/config.json中写入{"phase_id_convention": "bracket"}(测试夹具即以此方式构造 bracket 项目,见 tests/adr-612-bracket-display.property.test.cjs)。

六、兼容性保证与边界行为

6.1 非 bracket 项目:结构等同而非「论证等价」

这是本设计最值得注意的一点:未 opt-in 的仓库编译的标题模式恰好是基线拼写本身(BASE_ANY_BRACKET_HEADING_PREFIX_SRC/BASE_PHASE_LABEL_PREFIX_SRC),而不是其超集。原因是早期实现曾不加门控地放宽读取,并论证放宽后的形态「不可能出现在 legacy ROADMAP 中」——但### [RFC.2119] 5:### [v1.0] 2024:### [ADR.612] 3:都是合法 legacy 标题,放宽会把它们当作阶段,从而在没有 opt-in 的仓库上移动phase_counttotal_phases与 W006。构造时选择(construction-time selection)从根上消除了这一论证负担(见 src/phase-id.cts 的注释)。

6.2 malformed bracket 目录的恢复

当目录名不规范时(如小写gsd.02-05.03-recovered-display-name,tests/adr-612-bracket-display.property.test.cjs 的夹具),progressstats仍能识别出阶段并输出number: '05.03',但不产生display_idhasDisplayId: false)——优雅降级,不破坏命令输出。

6.3 opt-in 的代价(必须知晓的权衡)

文档在 docs/CONFIGURATION.md 中明确给出了 opt-in 的代价:在 bracket 仓库上,bracket 后直接跟数字的标题会被当作阶段标题,因此在其他约定下合法的小节标题形态——### [RFC.2119] 5:### [v1.0] 2024:### [ADR.612] 3:——都会被认作阶段,移动phase_counttotal_phases与 W006。bracket 仓库放弃了这一标题形态,这是 opt-in 换来的权衡;也是放宽读取必须在构造时从该配置值选择、而不是全局应用的原因。

6.4 范围限制

目前"bracket"只影响读取与显示路径,尚没有 bracket 迁移器和 bracket 写入(emit)。文档明确提示:"There is no bracket migrator and no bracket emit yet, so set it only on a project whose ROADMAP.md and phase directories already use that spelling."——只应在 ROADMAP.md 与阶段目录已使用该拼写的项目上开启。

七、验证与测试地图

围绕 #3638 的测试集中在两处:

测试文件覆盖点
tests/adr-612-bracket-display.property.test.cjsprogress/stats 的display_idmilestone_version;init manager 读取无标签 bracket 标题;legacy 项目保留旧对象/表格形状;malformed 目录恢复且无display_idrenderMilestoneId作为共享前缀;statusline full/compact 的约定门控;render(parse(x)) === xtoDir的 fast-check 属性测试
tests/config.test.cjsconfig-set phase_id_convention的枚举校验:三值接受、null取消、不支持/大小写不匹配拒绝、sequential非迁移目标

另有 tests/phase-id-card.test.cjs 钉死语法卡片字节与 src/phase-id-card.cts 一致,以及同族测试 tests/adr-612-bracket-coherence.test.cjs、tests/adr-612-bracket-heading-selection.test.cjs 等覆盖读取侧一致性(均在 tests 目录下)。

八、总结

#3638(PR 4111)为 ADR-612 的 bracket 约定补齐了显示面与配置面的最后一块拼图,其核心贡献可以概括为三点:

  1. 统一的规范形态progressstats、manager init 与两种 statusline 在phase_id_convention: "bracket"下渲染同一套[CODE.MM] NN标识,progress/stats同时暴露numberdisplay_id双字段;
  2. 严格的约定门控:非 bracket 项目编译与渲染与其基线字节级等同,彻底消除「放宽读取被误用」的论证风险;
  3. 配置侧枚举校验config-set只接受sequentialmilestone-prefixedbracket三个精确值(或null取消),配合测试矩阵钉死行为。

如果你想在已有 ROADMAP.md 与阶段目录均使用 bracket 拼写的项目上启用它,只需gsd-tools config-set phase_id_convention bracket,并在progress/stats/init manager/statusline 输出中确认[CODE.MM]形态出现、legacyvN.0消失即可;若需深入语法细节,gsd-core/references/phase-id-convention.md 是权威的紧凑语法卡,src/phase-id.cts 与 src/phase-id-display.cts 则是规范的解析/渲染/适配实现。

【免费下载链接】gsd-core

Git. Ship. Done - Core

项目地址:https://gitcode.com/gh_mirrors/ge/gsd-core
点击查看免费下载

相关推荐

上一篇:Backdrop CMS:为非技术人员打造的全能内容管理系统
下一篇:Thelia:开源电商平台的强大选择

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 13:47:54

【Coze】【视频】踏马爽文工作流

今天给大家演示一个 踏马爽文视频 Coze 工作流。该工作流结合了大语言模型、批处理、语音合成和剪映小助手等功能节点,能够从输入的爽文主题出发,自动生成符合爽文文风的文案,再将文案转化为音频、字幕并组合到视频草稿中,最终实现一键生成爽文短视频的效果。通过这个工作流…

作者头像 李华
网站建设 2026/9/24 13:46:49

Flask 即插视图高级应用

在使用 Flask 框架构建 Web 应用时,即插视图(Pluggable Views)是一种结构化管理视图函数的重要方式。通过将视图逻辑封装进类中,不仅提升了代码的可读性和复用性,也更容易与大型项目架构兼容。尤其在构建 RESTful 接口、模块化开发等场景中,即插视图能极大简化开发流程,…

作者头像 李华
网站建设 2026/9/24 13:46:36

Flask 扩展 Moment 本地化日期和时间

Web 应用中时间显示是一个常见需求,而本地化展示时间更是提升用户体验的关键细节。不同地区的用户希望看到符合其文化习惯的时间格式,比如“2025年4月7日 上午10:30”这样的格式对中文用户更友好,而美国用户则更习惯“April 7, 2025, 10:30 AM”。 Flask-Moment 是 Flask 的…

作者头像 李华
网站建设 2026/9/24 13:46:31

Flask Cookies 本地数据

在Web开发中,Cookies 是实现用户会话管理、偏好设置保存以及简易身份识别的重要手段。Flask作为一个轻量级的Python Web框架,提供了简单直观的方式来处理Cookies。掌握Cookies的用法不仅有助于构建更智能的Web应用,也是在构建用户体验、处理用户状态以及提高系统安全性方面的…

作者头像 李华