get-shit-done 3571 解析:配置清单的安装副本与双路径解析机制
本文围绕 changeset 3571-configuration-manifest-install-layout.md(PR #3572)展开,讲解 get-shit-done 中"运行时安装后 gsd-tools.cjs 加载配置清单失败"这一缺陷的成因与修复方案:安装流程如何将 config-defaults.manifest.json 与 config-schema.manifest.json 复制到 get-shit-done/bin/shared/,以及 configuration.generated.cjs 如何"先解析安装路径、再回退源码路径"。读完后你能理解该项目配置模块(Configuration Module)的单一数据源设计、清单文件的双布局解析逻辑,以及如何用回归测试验证安装布局下的模块可加载性。
问题背景:单一数据源与两种运行布局
该 changeset 的 frontmatter 声明了修复类型与关联 PR:
type: Fixed
pr: 3572
正文一句话概括了修复内容:运行时安装现在会把 config-defaults.manifest.json 和 config-schema.manifest.json 复制进 get-shit-done/bin/shared/,configuration.generated.cjs 会优先解析该"同位(co-located)"安装路径,再回退到源码检出(source checkout)的 sdk/shared/ 路径,从而防止在没有兄弟目录 sdk/ 检出时,直接调用已安装的 CJS 抛出 MODULE_NOT_FOUND。
要理解这个问题的严重性,需要先看配置模块的数据流。get-shit-done 的 CJS 侧不再内联任何配置字面量,而是全部来自清单。见 config-schema.cjs 头部注释:
/**
* Thin adapter — sources schema data from the manifest via the generated
* Configuration Module. All inline literals have been removed; the manifest
* at sdk/shared/config-schema.manifest.json is the single source of truth.
*
* Imported by:
* - config.cjs (isValidConfigKey validator)
* - tests/config-schema-docs-parity.test.cjs (CI drift guard)
* - tests/config-schema-sdk-parity.test.cjs (CJS↔SDK parity guard)
*/
也就是说,config-defaults.manifest.json 和 config-schema.manifest.json 是配置默认值与合法键集合的唯一事实来源(single source of truth),而 config.cjs、core.cjs 等运行时模块都依赖 configuration.generated.cjs 间接加载它们。
但仓库存在两种布局:
- 源码检出布局:
get-shit-done/bin/lib/configuration.generated.cjs向上三级可到达sdk/shared/; - 安装后布局:安装器只复制
get-shit-done/目录到运行时目录(如~/.codex/get-shit-done/),并不复制sdk/目录。此时向上三级的sdk/shared/路径指向一个不存在的位置,require()直接抛MODULE_NOT_FOUND。
修复一:loadConfigurationManifest 的双候选路径解析
修复后的核心实现位于 configuration.generated.cjs:
// ─── Manifest requires ───────────────────────────────────────────────────────
function loadConfigurationManifest(fileName) {
const candidates = [
// Installed runtime layout: get-shit-done/bin/shared/*.manifest.json
join(__dirname, '..', 'shared', fileName),
// Source-repo dev layout: sdk/shared/*.manifest.json
join(__dirname, '..', '..', '..', 'sdk', 'shared', fileName),
];
let lastErr = null;
for (const candidate of candidates) {
try {
return require(candidate);
} catch (err) {
const isMissingCandidate =
err && err.code === 'MODULE_NOT_FOUND' && String(err.message || '').includes(candidate);
if (!isMissingCandidate) throw err;
lastErr = err;
}
}
throw new Error(
`${fileName} not found. Tried:\n${candidates.map((p) => ` ${p}`).join('\n')}\nLast error: ${lastErr?.message}`
);
}
const CONFIG_DEFAULTS = loadConfigurationManifest('config-defaults.manifest.json');
const SCHEMA_MANIFEST = loadConfigurationManifest('config-schema.manifest.json');
这段实现有几个值得注意的设计点:
- 候选路径有序遍历:第一个候选
join(__dirname, '..', 'shared', fileName)对应安装运行时布局get-shit-done/bin/shared/(因为本文件位于bin/lib/,..即bin/);第二个候选join(__dirname, '..', '..', '..', 'sdk', 'shared', fileName)对应源码检出布局。安装路径排在首位,保证安装环境优先命中同位清单。 - 区分"候选缺失"与"真实错误":只有
err.code === 'MODULE_NOT_FOUND'且错误信息中包含该候选路径时,才继续尝试下一个候选;其他异常(如 JSON 语法错误)会原样抛出,不会被静默吞掉。 - 失败时给出可诊断的错误信息:两个候选都失败时,抛出的错误逐行列出尝试过的路径和最后一次错误原因,便于排查安装不完整的问题。
加载后的清单随即派生出配置模块的三个核心键集合(见 configuration.generated.cjs):
const VALID_CONFIG_KEYS = new Set(SCHEMA_MANIFEST.validKeys);
const RUNTIME_STATE_KEYS = new Set(SCHEMA_MANIFEST.runtimeStateKeys);
const DYNAMIC_KEY_PATTERNS = SCHEMA_MANIFEST.dynamicKeyPatterns.map((p) => {
const pattern = new RegExp(p.source);
return { ...p, test: (key) => { pattern.lastIndex = 0; return pattern.test(key); } };
});
清单中还保存了正则的 source 字符串,CJS 侧在运行时用 new RegExp(source) 重建 .test 函数——这与清单注释中"runtimeStateKeys mirrors RUNTIME_STATE_KEYS、dynamicKeyPatterns 在运行时从 source 重建 .test"的描述一致。
修复二:安装流程把清单复制进安装负载
仅有"回退路径"还不够——安装布局下回退路径(sdk/shared/)根本不存在。因此修复的另一半在 install.js 中,安装器在复制 get-shit-done/ 技能目录之后,显式把 sdk/shared/ 下的共享文件复制到 CJS 模块首先解析的同位路径:
// #3288 / #3571 — Copy sdk/shared manifests into the get-shit-done payload
// at the co-located path that CJS modules resolve first:
// get-shit-done/bin/shared/*.json
//
// The install copies get-shit-done/ but NOT sdk/ — CJS modules' legacy
// source-repo paths (3 levels up → sdk/shared/) therefore resolve to a
// non-existent location in every post-install layout. Copying these shared
// files alongside the CJS files ensures require() succeeds without needing
// sdk/ to exist.
const sharedPayloadFiles = [
'model-catalog.json',
'config-defaults.manifest.json',
'config-schema.manifest.json',
];
for (const fileName of sharedPayloadFiles) {
const sharedSrc = path.join(src, 'sdk', 'shared', fileName);
const sharedDest = path.join(skillDest, 'bin', 'shared', fileName);
const displayPath = `get-shit-done/bin/shared/${fileName}`;
if (fs.existsSync(sharedSrc)) {
fs.mkdirSync(path.dirname(sharedDest), { recursive: true });
fs.copyFileSync(sharedSrc, sharedDest);
if (verifyFileInstalled(sharedDest, displayPath)) {
console.log(` ${green}✓${reset} Installed ${displayPath}`);
} else {
failures.push(displayPath);
}
} else {
failures.push(`sdk/shared/${fileName} (source missing)`);
}
}
注意三点:
- 复制清单是
model-catalog.json(#3288 已加入)与本修复新增的两个配置清单,统一落到skillDest/bin/shared/,即安装目录下的get-shit-done/bin/shared/; - 每个文件复制后调用
verifyFileInstalled校验,失败会记入failures数组,使安装报告能如实反映缺失; - 若源端
sdk/shared/<file>在发行包中缺失,则显式记为sdk/shared/<file> (source missing),而不是静默跳过。
由此形成"两侧互补"的完整闭环:安装器保证安装布局下 bin/shared/ 清单存在,运行时保证源码布局下 sdk/shared/ 清单可用,无论哪种布局 require() 都能成功。
两个清单的内容:默认值与合法键集合
修复对象是清单文件本身,这里简要说明它们的结构与消费方式。
config-defaults.manifest.json:规范化默认值
sdk/shared/config-defaults.manifest.json 是"配置模块的规范化 CONFIG_DEFAULTS",其头部注释即说明:嵌套形状为规范形态,CJS 侧的扁平键(如 branching_strategy、sub_repos)由消费方在边界处处理;安全键(security_enforcement、security_asvs_level、security_block_on)与 post_planning_gaps 的规范位置在 workflow.* 下。主要默认值摘录如下:
| 配置键 | 默认值 | 说明 |
|---|---|---|
model_profile |
"balanced" |
模型档位 |
commit_docs |
true |
文档是否随代码提交 |
parallelization |
true |
计划并行执行 |
search_gitignored |
false |
搜索是否包含被 git 忽略的内容 |
brave_search / firecrawl / exa_search |
均为 false |
外部搜索 API 默认关闭,运行时检测到 API 键时才启用(见 _comment) |
context_window |
200000 |
上下文窗口 token 数 |
phase_naming |
"sequential" |
阶段命名策略 |
mode |
"interactive" |
运行模式 |
claude_md_path |
"./CLAUDE.md" |
CLAUDE.md 路径 |
git.branching_strategy |
"none" |
分支策略(规范嵌套位置) |
git.create_tag |
true |
里程碑打 tag |
git.phase_branch_template |
"gsd/phase-{phase}-{slug}" |
阶段分支模板 |
git.milestone_branch_template |
"gsd/{milestone}-{slug}" |
里程碑分支模板 |
workflow.research / plan_check / verifier / nyquist_validation |
true |
计划/校验类开关 |
workflow.tdd_mode |
false |
TDD 模式 |
workflow.human_verify_mode |
"end-of-phase" |
人工验证时机 |
workflow.auto_advance |
false |
阶段自动推进 |
workflow.node_repair / node_repair_budget |
true / 2 |
节点修复及其预算 |
workflow.ui_safety_gate |
true |
UI 安全门禁 |
workflow.discuss_mode / skip_discuss / max_discuss_passes |
"discuss" / false / 3 |
讨论模式相关 |
workflow.subagent_timeout |
300000 |
子代理超时(毫秒) |
workflow.code_review / code_review_depth |
true / "standard" |
代码评审及深度 |
workflow.security_enforcement / security_asvs_level / security_block_on |
true / 1 / "high" |
安全强制与 ASVS 等级 |
planning.granularity |
"standard" |
规划粒度 |
hooks.context_warnings / workflow_guard |
true / false |
Hook 开关 |
graphify.auto_update |
false |
依赖图自动更新 |
这些默认值在加载时如何生效,可见 configuration.generated.cjs 中的 mergeDefaults 与 loadConfig:loadConfig 读取 .planning/config.json(存在 workstream 时为 .planning/workstreams/<name>/config.json),文件缺失或为空时直接返回 mergeDefaults({})——即深拷贝默认值后叠加用户配置,对象按深合并(deepMergeConfig)处理。同一文件中还实现了遗留键规范化(normalizeLegacyKeys,如顶层 branching_strategy → git.branching_strategy、顶层 depth → granularity)和磁盘迁移(migrateOnDisk),这些逻辑都依赖清单提供的 CONFIG_DEFAULTS。
config-schema.manifest.json:合法键集合
sdk/shared/config-schema.manifest.json 的头部注释说明其来源约束:validKeys 是 CJS 侧 config-schema.cjs 与 SDK 侧 query/config-schema.ts 键集合的并集,两者由 config-schema-sdk-parity.test.cjs 强制集合相等;dynamicKeyPatterns 保存 SDK 中规范的正则 source 字符串,运行时重建 .test。键集合包含如 mode、granularity、parallelization、commit_docs、model_profile、workflow.research、workflow.plan_check、workflow.code_review_depth 等规范路径。
CJS 侧的消费入口是 config-schema.cjs 的 isValidConfigKey:先查 VALID_CONFIG_KEYS 精确集合,再查 RUNTIME_STATE_KEYS(运行时状态键,如 workflow._auto_chain_active),最后逐一匹配 DYNAMIC_KEY_PATTERNS 动态模式。这套校验被 config.cjs 用于 config set 等命令的键合法性检查,并有 config-schema-docs-parity.test.cjs 作为文档漂移守卫。
生成管线:configuration.generated.cjs 从哪来
configuration.generated.cjs 头部明确标注为生成文件:
* GENERATED FILE — DO NOT EDIT.
* Source: sdk/src/configuration/index.ts
* Regenerate: cd sdk && npm run gen:configuration
对应生成器 sdk/scripts/gen-configuration.mjs:它"要求"(require)sdk/shared/ 下的两个 JSON 清单,输出到 get-shit-done/bin/lib/configuration.generated.cjs(见 gen-configuration.mjs 的 outPath),并把双候选路径注释直接写进生成产物(gen-configuration.mjs 中的两处布局注释)。因此 #3571 的路径解析修复是在生成器层面落地的——重新生成后所有发行版本都会携带同位路径优先的解析逻辑,而不是在某个已安装副本上打补丁。清单新鲜度另有 sdk/scripts/check-configuration-fresh.mjs 在 CI 中把关,防止手写清单与生成产物漂移。
回归测试:验证安装布局下的可加载性
本修复的回归测试为 tests/bug-3571-configuration-manifest-install-path.test.cjs,文件头注释直接复述了缺陷:"configuration.generated.cjs 之前只用源码检出的 sdk/shared 路径,导致已安装的 gsd-tools.cjs 损坏,因为运行时安装复制 get-shit-done/ 但不复制 sdk/。"测试包含两个用例:
用例一:同位清单让模块无需 sdk/shared 即可加载(L71-L96)。在临时目录构造安装布局 ~/.codex/get-shit-done/bin/{lib,shared},把仓库中的 configuration.generated.cjs 与两个清单复制进去,然后 require 该安装副本,断言不抛异常,并验证键集合内容:
assert.doesNotThrow(() => {
mod = require(installedCjs);
}, 'installed configuration.generated.cjs must not require ~/.codex/sdk/shared');
assert.ok(mod.VALID_CONFIG_KEYS.has('workflow.plan_review_convergence'));
用例二:完整安装后清单落在同位 bin/shared/(L98-L128)。将 HOME 指向临时目录后调用真实的 install(true, 'codex')(来自 bin/install.js),随后断言 ~/.codex/get-shit-done/bin/shared/ 下两个清单都存在、是合法 JSON,且安装后的 configuration.generated.cjs 能直接从同位清单加载:
const sharedDir = path.join(tmpRoot, '.codex', 'get-shit-done', 'bin', 'shared');
for (const fileName of ['config-defaults.manifest.json', 'config-schema.manifest.json']) {
const installedManifest = path.join(sharedDir, fileName);
assert.ok(fs.existsSync(installedManifest), `${fileName} must be copied to ${sharedDir}`);
assert.doesNotThrow(() => {
JSON.parse(fs.readFileSync(installedManifest, 'utf8'));
}, `${fileName} must be valid JSON`);
}
这两个用例分别覆盖"解析器逻辑"与"安装器行为",正好对应 #3571 修复的两个组成部分,形成端到端验证。
影响范围与验证方式
从仓库证据看,该修复的影响面与验证方式如下:
- 修复范围:直接受益方是安装布局下直接调用 CJS 工具链的场景(如
gsd-tools.cjs的子命令)。清单加载被 core.cjs、config.cjs 等基础模块间接依赖,修复前这类调用在安装布局下会整体失效; - 文档记录:RELEASE-v1.42.3.md 的发布说明中记录了该条目,指出 "
configuration.generated.cjs会查找已安装的 [路径]"(对应 #3571 / PR #3572); - 可自查的安装布局:修复后,一次完整安装应在
get-shit-done/bin/shared/下看到model-catalog.json、config-defaults.manifest.json、config-schema.manifest.json三个文件(前两者即 #3571 新增复制项);源码检出布局则依赖仓库根目录的sdk/shared/(当前仓库中该目录包含这三个文件); - 配套守卫测试:除本修复的回归测试外,config-schema-sdk-parity.test.cjs 保证 CJS 与 SDK 的键集合一致,config-schema-docs-parity.test.cjs 防止文档漂移,configuration-generator.test.cjs 与 feat-3598-generator-correctness.test.cjs 覆盖生成器正确性,feat-3210-fallow-integration.test.cjs 等也引用了清单路径——改动清单或生成逻辑时,这批测试构成回归边界。
小结
#3571 是一个典型的"发布布局与开发布局不一致"引发的运行时缺陷:配置数据被集中到 sdk/shared/ 的 JSON 清单作为单一事实源,但安装器只分发 get-shit-done/ 而不分发 sdk/,导致已安装 CJS 的 require() 落空。修复由两半构成——安装器把清单复制到 CJS 优先解析的同位目录 get-shit-done/bin/shared/(bin/install.js),生成器在产物中写入"安装路径优先、源码路径回退"的 loadConfigurationManifest 解析逻辑(get-shit-done/bin/lib/configuration.generated.cjs)。tests/bug-3571-configuration-manifest-install-path.test.cjs 以"模拟安装布局 require 不抛错"和"真实 install() 后清单就位"两个用例锁定了这一行为。对维护者的启示是:当数据源集中在仓库某个子目录、而发行负载只复制另一个子目录时,要么在安装时补齐共享文件,要么在解析时提供可诊断的多候选回退——本案例两者兼用,并用端到端测试把"安装器行为"与"解析器逻辑"分别钉住。
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