首页
/ get-shit-done 3571 解析:配置清单的安装副本与双路径解析机制

get-shit-done 3571 解析:配置清单的安装副本与双路径解析机制

2026-09-04 16:14:34作者:庞眉杨Will

本文围绕 changeset 3571-configuration-manifest-install-layout.md(PR #3572)展开,讲解 get-shit-done 中"运行时安装后 gsd-tools.cjs 加载配置清单失败"这一缺陷的成因与修复方案:安装流程如何将 config-defaults.manifest.jsonconfig-schema.manifest.json 复制到 get-shit-done/bin/shared/,以及 configuration.generated.cjs 如何"先解析安装路径、再回退源码路径"。读完后你能理解该项目配置模块(Configuration Module)的单一数据源设计、清单文件的双布局解析逻辑,以及如何用回归测试验证安装布局下的模块可加载性。

问题背景:单一数据源与两种运行布局

该 changeset 的 frontmatter 声明了修复类型与关联 PR:

type: Fixed
pr: 3572

正文一句话概括了修复内容:运行时安装现在会把 config-defaults.manifest.jsonconfig-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.jsonconfig-schema.manifest.json 是配置默认值与合法键集合的唯一事实来源(single source of truth),而 config.cjscore.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');

这段实现有几个值得注意的设计点:

  1. 候选路径有序遍历:第一个候选 join(__dirname, '..', 'shared', fileName) 对应安装运行时布局 get-shit-done/bin/shared/(因为本文件位于 bin/lib/..bin/);第二个候选 join(__dirname, '..', '..', '..', 'sdk', 'shared', fileName) 对应源码检出布局。安装路径排在首位,保证安装环境优先命中同位清单。
  2. 区分"候选缺失"与"真实错误":只有 err.code === 'MODULE_NOT_FOUND' 且错误信息中包含该候选路径时,才继续尝试下一个候选;其他异常(如 JSON 语法错误)会原样抛出,不会被静默吞掉。
  3. 失败时给出可诊断的错误信息:两个候选都失败时,抛出的错误逐行列出尝试过的路径和最后一次错误原因,便于排查安装不完整的问题。

加载后的清单随即派生出配置模块的三个核心键集合(见 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_KEYSdynamicKeyPatterns 在运行时从 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_strategysub_repos)由消费方在边界处处理;安全键(security_enforcementsecurity_asvs_levelsecurity_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 中的 mergeDefaultsloadConfigloadConfig 读取 .planning/config.json(存在 workstream 时为 .planning/workstreams/<name>/config.json),文件缺失或为空时直接返回 mergeDefaults({})——即深拷贝默认值后叠加用户配置,对象按深合并(deepMergeConfig)处理。同一文件中还实现了遗留键规范化(normalizeLegacyKeys,如顶层 branching_strategygit.branching_strategy、顶层 depthgranularity)和磁盘迁移(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。键集合包含如 modegranularityparallelizationcommit_docsmodel_profileworkflow.researchworkflow.plan_checkworkflow.code_review_depth 等规范路径。

CJS 侧的消费入口是 config-schema.cjsisValidConfigKey:先查 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.mjsoutPath),并把双候选路径注释直接写进生成产物(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.cjsconfig.cjs 等基础模块间接依赖,修复前这类调用在安装布局下会整体失效;
  • 文档记录RELEASE-v1.42.3.md 的发布说明中记录了该条目,指出 "configuration.generated.cjs 会查找已安装的 [路径]"(对应 #3571 / PR #3572);
  • 可自查的安装布局:修复后,一次完整安装应在 get-shit-done/bin/shared/ 下看到 model-catalog.jsonconfig-defaults.manifest.jsonconfig-schema.manifest.json 三个文件(前两者即 #3571 新增复制项);源码检出布局则依赖仓库根目录的 sdk/shared/(当前仓库中该目录包含这三个文件);
  • 配套守卫测试:除本修复的回归测试外,config-schema-sdk-parity.test.cjs 保证 CJS 与 SDK 的键集合一致,config-schema-docs-parity.test.cjs 防止文档漂移,configuration-generator.test.cjsfeat-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() 后清单就位"两个用例锁定了这一行为。对维护者的启示是:当数据源集中在仓库某个子目录、而发行负载只复制另一个子目录时,要么在安装时补齐共享文件,要么在解析时提供可诊断的多候选回退——本案例两者兼用,并用端到端测试把"安装器行为"与"解析器逻辑"分别钉住。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384