get-shit-done 中 config-ensure-section 的双路径契约恢复:Phase 6 路由迁移下的 SDK 行为对齐实践
本文围绕 get-shit-done(GSD)项目 changeset 3577-config-ensure-section-parity 展开,讲解 Phase 6 SDK 路由迁移中 config-ensure-section 命令的回归事故是如何发生的、为什么采用“carve-out(保留 CJS 旧路径)”策略恢复旧契约,以及 config-defaults.manifest.json 作为默认值唯一事实来源、错误文案与 CJS 对齐这三项 parity 修复的具体实现。读完后,你将掌握该项目 CJS CLI 与 TS SDK 双实现共存时“行为契约”如何被维护,以及如何在源码层面验证这些契约。
背景:Phase 6 路由迁移引发的 config-ensure-section 回归
GSD 的工具链存在两套实现路径:一套是随 CLI 分发的 CJS 实现(get-shit-done/bin/lib/config.cjs),另一套是 TypeScript SDK 中的 Query Handler 实现(sdk/src/query/config-mutation.ts)。Phase 6 的“router migration”将部分 config-* 命令从 CJS 路径切到新的 SDK 处理器,但这一迁移打破了四个 CLI 回归测试,典型症状是测试中意外捕获到:
Usage: config-ensure-section <section>
问题根源在于两套实现对该命令的语义理解不一致:
- SDK 侧的
configEnsureSection处理器把args[0]视为必选的位置参数<section>,缺失时直接抛出上述 Usage 错误(见 sdk/src/query/config-mutation.ts); - 但 CLI 实际调用
config-ensure-section时从不传递 section 参数——对 CLI 而言,这个命令的语义是“确保完整配置文件存在”,而不是“确保某个 section 存在”。
因此,把无参调用路由到一个期待位置参数的新处理器上,必然触发 Usage 错误,tests/config.test.cjs、tests/agent-skills.test.cjs、tests/ai-evals.test.cjs 等测试随之失败。changeset 3577-config-ensure-section-parity.md 记录的修复策略是:将 config-ensure-section 作为 parity carve-out,不再经由新 SDK 的 configEnsureSection 处理器路由,恢复旧契约。
旧契约:CJS 路径 cmdConfigEnsureSection → ensureConfigFile → buildNewProjectConfig
被恢复的 CJS 调用链在 get-shit-done/bin/lib/config.cjs 中完整可见:
ensureConfigFile(cwd)(第 301 行起):确保.planning目录存在;若.planning/config.json已存在则直接返回{ created: false, reason: 'already_exists' };不存在则调用buildNewProjectConfig({})生成完整的默认配置,并以 2 空格缩进的 JSON 写入,返回{ created: true, path: '.planning/config.json' }。cmdConfigEnsureSection(cwd, raw)(第 333 行起):仅作为命令入口,调用上面的ensureConfigFile,并按created/exists两种状态输出结果。
从源码结构看,CJS 侧这个函数虽然命名为 “EnsureSection”,实际行为是“幂等地物化整个默认配置文件”,完全不涉及 section 参数——这正是 CLI 从未传参的原因,也解释了为什么新 SDK 处理器与 CLI 调用方式天然不兼容。
值得注意的是,SDK 仓库中 configEnsureSection 处理器依然保留(sdk/src/query/config-mutation.ts),其文档注释明确定义为“幂等地确保 config.json 中存在某个顶层 section:不存在则创建为空对象,存在则保留内容”,返回 { ensured: true, section }。它与 CJS 路径是两个不同粒度的操作(section 级 vs 文件级),carve-out 策略选择的是暂时保留文件级旧契约,而不是删除新处理器。命令在 SDK 命令目录中的注册信息(mutation: true、outputMode: 'json')见 sdk/src/query/command-manifest.non-family.ts。
默认值唯一事实来源:configNewProject 对齐 config-defaults.manifest.json
changeset 的第二项修复是:SDK 的 configNewProject 默认值现在与 sdk/shared/config-defaults.manifest.json 保持一致,并像 CJS 一样报告项目根相对路径 .planning/config.json。
manifest 的规范结构
该 manifest 文件头部注释声明了自己是“Configuration Module 的规范 CONFIG_DEFAULTS”,并明确了若干关键约定:
- 嵌套结构(
git.*、workflow.*、planning.*等)是规范形态;CJS 的扁平投影(如顶层的branching_strategy、sub_repos)由消费者在边界处处理; - 安全类键(
security_enforcement、security_asvs_level、security_block_on)与post_planning_gaps的规范位置在workflow.*下; brave_search、firecrawl、exa_search在 manifest 中默认为false,但运行时由buildNewProjectConfig检测 API key 决定实际值;plan_checker(CJS 扁平名)与workflow.plan_check(规范嵌套名)的命名分歧以规范名workflow.plan_check为准。
与 CJS 行为对齐的两个关键默认值在 manifest 第 4–5 行:commit_docs: true、parallelization: true。
configNewProject 的合并逻辑
SDK 处理器侧的实现(sdk/src/query/config-mutation.ts)展示了 manifest 如何被消费:
- manifest 净化:遍历净化后的 manifest,按
TOP_LEVEL_OMITTED_FROM_INIT、GIT_KEYS_OMITTED_FROM_INIT两个集合剔除不应出现在 init 配置中的键; - 运行时探测:
brave_search、firecrawl、exa_search用hasBraveSearch/hasFirecrawl/hasExaSearch的运行时检测结果覆盖 manifest 默认值,与 CJSbuildNewProjectConfig检测 API key 的行为一致;此外还硬编码了features: {}顶层槽位——源码注释说明这是为了在 manifest 未更新前维持与 CJS 的 parity; - 三层深度合并:
hardcoded defaults ← globalDefaults ← userChoices,对git、workflow、ship、hooks、agent_skills、features各分组逐层展开合并(源码中标注为 D11 决策); - 写盘与校验:对
ship.pr_body_sections调用validateShipPrBodySections,通过atomicWriteConfig原子写入; - 返回形态对齐:返回
{ data: { created: true, path: '.planning/config.json' } }——源码注释明确写道 “Match CJSensureConfigFileshape: report the relative project-rooted path so output stays workspace-portable”,即与 CJS 侧ensureConfigFile返回的相对路径形态完全一致,保证工作区可移植性。
错误词汇对齐:让遗留测试继续可匹配
changeset 的第三项修复是错误文案(error vocabulary)与 CJS 对齐,目的是让匹配错误字符串的遗留回归测试继续通过:
- 未知配置键:SDK 侧抛出
Unknown config key: <key>(键名不带引号),见 sdk/src/query/config-mutation.ts,其中<key>后可附带建议(${suggestion}拼接); - config-get 的坏 JSON 错误:以
Failed to read config.json:开头。对应实现见 sdk/src/query/config-query.ts,源码注释直接说明动机:“Lead the message withFailed to read config.json— matches the CJS”。
这两处看似琐碎的对齐,实质上是把错误字符串当作契约的一部分来管理:项目的回归测试按错误文案匹配来断言失败路径,SDK 迁移时任何措辞漂移都会造成测试回归。
修复的验证面:四个 CLI 回归测试
changeset 明确列出本次修复关闭的回归来自 tests/{config,agent-skills,ai-evals}.test.cjs,对应仓库中真实存在的 tests/config.test.cjs、tests/agent-skills.test.cjs、tests/ai-evals.test.cjs。这些测试在 CJS 路径上运行,断言 config-ensure-section 的输出形态(created / exists 状态、.planning/config.json 相对路径)与默认配置内容不被 SDK 迁移改变。
小结:路由迁移中的 parity 方法论
从 3577-config-ensure-section-parity.md 这次修复中可以提炼出 GSD 在 CJS→SDK 渐进迁移中复用的几条实践:
- 契约先行于实现:命令的可观测行为(参数要求、输出 JSON 形态、路径是绝对还是相对)才是契约;新处理器若改变了契约,即使“功能上等价”也算回归。
- carve-out 优于强行适配:当新处理器语义与既有调用方不匹配时,保留旧 CJS 路径(
cmdConfigEnsureSection → ensureConfigFile → buildNewProjectConfig)比修改所有调用方风险更低,新处理器可以留给未来语义收敛。 - 单一事实来源 + 边界投影:默认值集中在
sdk/shared/config-defaults.manifest.json,CJS 扁平形态由各实现自行投影;manifest 头部的_comment甚至把每一处已知的命名分歧都登记在案。 - 错误字符串是测试契约:
Unknown config key: <key>(无引号)、Failed to read config.json:前缀等细节都按“遗留测试可匹配”标准对齐,源码中以注释显式声明了这一点。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00