get-shit-done 配置键白名单机制解析:workflow._auto_chain_active 为何不再被 config-set 拒绝
本文以 get-shit-done(GSD)的一个 changeset 修复(PR #3197)为主体,讲解 gsd-tools config-set workflow._auto_chain_active 从被拒绝到被接受的完整过程:一个内部运行时状态键(RUNTIME_STATE_KEYS)因 SDK 与 CJS 双端 schema 未同步而报出 "Unknown config key",以及修复如何通过三层校验、manifest 单一事实源和 CI 一致性断言从根本上杜绝此类漂移。读完后你能定位 GSD 配置键校验的调用链、理解双端 schema 的同步机制,并复现/验证这一回归测试。
问题背景:一个内部运行时状态键被 config-set 拒绝
workflow._auto_chain_active 是 GSD 的内部运行时状态键(runtime-state key),用于追踪「自主链式执行(autonomous chaining)」是否处于激活状态。它在 GSD 的多个工作流执行链中被反复读写:
-
在
get-shit-done/references/planning-config.md(第 268 行)中,它被登记为:键 类型 默认值 取值 说明 workflow._auto_chain_activeboolean falsetrue,falseInternal: tracks whether autonomous chaining is active -
写入方来自多个 workflow。例如
get-shit-done/workflows/discuss-phase/modes/chain.md中的链式推进步骤会执行gsd-sdk query config-set workflow._auto_chain_active true/... false;执行器 agent 也会在 执行器定义 中读取它:AUTO_CHAIN=$(gsd-sdk query config-get workflow._auto_chain_active 2>/dev/null || echo "false")。 -
它参与检查点(checkpoint)的自动放行逻辑:如
get-shit-done/references/checkpoints.md(第 11 行)所述,当workflow._auto_chain_active或workflow.auto_advance为true时,human-verify 会自动批准、decision 自动选中第一个选项,而 human-action 仍会停止(认证门控无法自动化)。
关键问题在于:这类键以下划线前缀(_auto_chain_active)标记为「内部状态」,用户并不期望手动设置它,但它必须能被 config-set 合法写入——否则工作流自身执行到该步骤时就会失败。这正是 PR #3197 要修复的场景。
changeset(.changeset/fix-3197-gsd-tools-config-whitelist.md)记录了缺陷:
gsd-tools config-set workflow._auto_chain_activeno longer rejected —workflow._auto_chain_activeis an internal runtime-state key written by plan-phase, execute-phase, discuss-phase, and transition workflows. PR #3162 added it toRUNTIME_STATE_KEYSin the SDK'sconfig-schema.tsbut did not mirror the change to the CJSconfig-schema.cjsused bygsd-tools.cjs. Users routed throughgsd-tools.cjscontinued to see "Unknown config key" (#3033).
也就是说:#3162 在 SDK 侧把该键加入了 RUNTIME_STATE_KEYS,但没有同步到 CJS 侧的 config-schema.cjs,于是走 gsd-tools.cjs 入口的用户仍然撞上 "Unknown config key"(原始缺陷报告为 #3033)。
配置键校验的三道关卡
GSD 对 config-set <key.path> <value> 的校验并非「非黑即白」,而是由三个集合依次判定。以最典型的 CJS 校验器为例(get-shit-done/bin/lib/config-schema.cjs):
function isValidConfigKey(keyPath) {
if (VALID_CONFIG_KEYS.has(keyPath)) return true; // 1. 静态合法键(精确匹配)
if (RUNTIME_STATE_KEYS.has(keyPath)) return true; // 2. 运行时状态键(#3197 新增的判断)
return DYNAMIC_KEY_PATTERNS.some((p) => p.test(keyPath)); // 3. 动态键(正则模式)
}
三层语义分别是:
VALID_CONFIG_KEYS— 用户可配置的静态键,精确字符串匹配(如workflow.auto_advance、git.create_tag、model_profile)。RUNTIME_STATE_KEYS— 内部运行时状态键,通常不由用户手设,但工作流需要config-set写入。当前集合只有workflow._auto_chain_active一项。DYNAMIC_KEY_PATTERNS— 带命名空间的动态键,用正则匹配,例如agent_skills.<agent-type>、review.models.<cli-name>、features.<feature_name>等。
这三个集合的真实取值都来自单一 manifest sdk/shared/config-schema.manifest.json。其中 runtimeStateKeys 段(第 101–103 行)为:
"runtimeStateKeys": [
"workflow._auto_chain_active"
]
dynamicKeyPatterns 段则列出了 agent_skills、review.models、features、claude_md_assembly.blocks、model_profile_overrides、models、dynamic_routing、model_overrides、review.max_prompt_tokens_per_reviewer 等正则模式。
当 isValidConfigKey 全部落空时,CJS/SDK 侧会抛出 Unknown config key: <key>,并附带基于「最长公共前缀」的键名纠错建议(见 sdk/src/query/config-mutation.ts 的 isValidConfigKey,其中 CONFIG_KEY_SUGGESTIONS 先于 LCP 兜底提供更精确的提示)。缺陷的本质就是:workflow._auto_chain_active 落在第二关(RUNTIME_STATE_KEYS)应当命中,但 CJS 侧的 RUNTIME_STATE_KEYS 是空的,于是掉进了 Unknown config key 分支。
根因:SDK 与 CJS 双端 schema 漂移
GSD 存在两条 config-set 的执行路径,历史上各自维护一份 schema 字面量:
| 路径 | 入口 | 校验器 | schema 来源(#3197 时期) |
|---|---|---|---|
| CJS | get-shit-done/bin/gsd-tools.cjs | get-shit-done/bin/lib/config.cjs 调 isValidConfigKey |
config-schema.cjs 内联 |
| SDK | gsd-sdk query config-set |
sdk/src/query/config-mutation.ts 的 configSet |
config-schema.ts 内联 |
- CJS 路径:
gsd-tools.cjs→config.cjs的cmdConfigSet(第 410 行if (!isValidConfigKey(keyPath)))→config-schema.cjs的isValidConfigKey。 - SDK 路径:
config-mutation.ts的configSet(第 281 行const validation = isValidConfigKey(keyPath))→config-schema.ts的isValidConfigKeyPath。
PR #3162 只改了 SDK 侧 config-schema.ts,把 workflow._auto_chain_active 加进了 RUNTIME_STATE_KEYS;CJS 侧的 config-schema.cjs 没有跟随。结果就是:同一句 config-set workflow._auto_chain_active true,走 SDK 入口能成功,走 gsd-tools.cjs 入口却报 "Unknown config key"——这就是 #3033 的现象,也是双端 schema 字面量「漂移」的典型后果。
修复方案:把 RUNTIME_STATE_KEYS 纳入 CJS 校验
changeset 明确列出了 #3197 的四处改动,均可在仓库源码中一一对应:
-
给
config-schema.cjs增加RUNTIME_STATE_KEYS,并与VALID_CONFIG_KEYS一同导出(get-shit-done/bin/lib/config-schema.cjs):module.exports = { VALID_CONFIG_KEYS, RUNTIME_STATE_KEYS, DYNAMIC_KEY_PATTERNS, isValidConfigKey }; -
更新
isValidConfigKey()接受运行时状态键——即在VALID_CONFIG_KEYS命中之后、DYNAMIC_KEY_PATTERNS之前,插入if (RUNTIME_STATE_KEYS.has(keyPath)) return true;(第 27 行)。 -
SDK 的
config-mutation.ts改为导入并校验同一集合(sdk/src/query/config-mutation.ts 从./config-schema.js导入RUNTIME_STATE_KEYS,并在isValidConfigKey第 165 行做RUNTIME_STATE_KEYS.has(keyPath)判断),使双端判定逻辑对齐。 -
新增 CI 一致性断言,确保两侧的
RUNTIME_STATE_KEYS集合保持同步(见下节)。
修复后,config-set 成功写入的返回值形如(config-mutation.ts 第 446–454 行):
{ data: { updated: true, key: 'workflow._auto_chain_active', value: true } }
值得强调的是取值语义:true/false 会被 parseConfigValue(第 208–216 行)强制转换为原生布尔,避免把字符串 "true" 写进 config.json。
纵深:manifest 单一事实源与结构性防漂移(#3536)
changeset 描述的 #3197 是「立即修复」,而当前仓库的源码结构显示,随后一次重构(Phase 2 Cycle 5,#3536)把这种漂移从「需要 CI 拦截」升级成了「结构上不可能发生」。从当前源码结构看:
-
manifest 成为唯一事实源。sdk/shared/config-schema.manifest.json 同时承载
validKeys、runtimeStateKeys、dynamicKeyPatterns三份数据,其_comment字段说明:validKeys是 CJS 与 SDK 两侧并集,二者由tests/config-schema-sdk-parity.test.cjs强制集合相等。 -
SDK 侧:sdk/src/configuration/index.ts 直接从 manifest 读出
VALID_CONFIG_KEYS与RUNTIME_STATE_KEYS:export const VALID_CONFIG_KEYS: ReadonlySet<string> = new Set(_schemaManifest.validKeys); export const RUNTIME_STATE_KEYS: ReadonlySet<string> = new Set(_schemaManifest.runtimeStateKeys);而 sdk/src/query/config-schema.ts 已变成一个「薄重导出适配器」,不再含任何内联键字面量。
-
CJS 侧:get-shit-done/bin/lib/configuration.generated.cjs 是「GENERATED FILE — DO NOT EDIT」,同样从 manifest 装载:
const SCHEMA_MANIFEST = loadConfigurationManifest('config-schema.manifest.json'); const VALID_CONFIG_KEYS = new Set(SCHEMA_MANIFEST.validKeys); const RUNTIME_STATE_KEYS = new Set(SCHEMA_MANIFEST.runtimeStateKeys);get-shit-done/bin/lib/config-schema.cjs 则只是
require这个生成文件,把三个集合透传出去。
这样一来,#3197 要防的「SDK 加了、CJS 没加」在结构上被消除——两侧都从同一份 manifest 派生,不存在两份可独立漂移的字面量。CI 断言(tests/config-schema-sdk-parity.test.cjs 的「CJS RUNTIME_STATE_KEYS matches manifest runtimeStateKeys exactly」)退化为「确保没人和 manifest 脱钩」的守卫。这也是从源码结构可以推断出的设计意图:与其让每个修复去追平两端,不如把两端收敛到一个数据源。
回归测试与验证
针对本次修复的回归测试是 tests/bug-3197-gsd-tools-config-whitelist.test.cjs,共三条用例,走的是真实的 gsd-tools.cjs(CJS 路径):
// 用例 1:通过 CJS 路径设置 workflow._auto_chain_active=true 成功
const result = runGsdTools(['config-set', 'workflow._auto_chain_active', 'true'], tmpDir);
assert.ok(result.success, `config-set workflow._auto_chain_active true should succeed, got:...`);
// 用例 2/3:设置 true / false 后,断言 .planning/config.json 中
// config.workflow._auto_chain_active 的确切布尔值
三条用例分别验证:(1) 不再被拒绝(result.success 为真);(2) 设置 true 后磁盘 config.json 中 workflow._auto_chain_active === true;(3) 设置 false 后为 false。测试注释直接点明根因——「RUNTIME_STATE_KEYS was added to sdk/…/config-schema.ts in #3162 but not to get-shit-done/bin/lib/config-schema.cjs」,与上文根因分析一致。
配套的一致性守卫 tests/config-schema-sdk-parity.test.cjs 则从 manifest 出发断言:CJS 的 VALID_CONFIG_KEYS、RUNTIME_STATE_KEYS、DYNAMIC_KEY_PATTERNS 与 manifest 完全相等,且 SDK 的 config-schema.ts 是「重导出壳」而非「重新声明的 Set」。
相关代码路径速查
| 关注点 | 路径 | 说明 |
|---|---|---|
| 本次修复的 changeset | .changeset/fix-3197-gsd-tools-config-whitelist.md | 主体文档,PR #3197 |
| CJS 入口 | get-shit-done/bin/gsd-tools.cjs | config-set 命令入口 |
| CJS 校验器 | get-shit-done/bin/lib/config.cjs / config-schema.cjs | cmdConfigSet 调 isValidConfigKey |
| CJS 生成源 | get-shit-done/bin/lib/configuration.generated.cjs | 从 manifest 装载三个集合 |
| SDK 校验器 | sdk/src/query/config-mutation.ts | configSet 与 isValidConfigKey |
| SDK schema 适配器 | sdk/src/query/config-schema.ts | 重导出 + isValidConfigKeyPath |
| 单一事实源 manifest | sdk/shared/config-schema.manifest.json | runtimeStateKeys 在此 |
| 键的登记与语义 | get-shit-done/references/planning-config.md / checkpoints.md | 类型/默认值/自动放行逻辑 |
| 写入方示例 | agents/gsd-executor.md / workflows/discuss-phase/modes/chain.md | 谁在读写该键 |
| 回归测试 | tests/bug-3197-gsd-tools-config-whitelist.test.cjs | 三条用例走 CJS 路径 |
| 一致性守卫 | tests/config-schema-sdk-parity.test.cjs | 双端集合与 manifest 相等 |
| 版本记录 | CHANGELOG.md / docs/RELEASE-v1.41.0.md | #3197 于 v1.41.0 随版发布 |
小结
- 缺陷:
workflow._auto_chain_active(内部运行时状态键)在 #3162 只被加入 SDK 侧RUNTIME_STATE_KEYS,未同步到 CJS 侧,导致走gsd-tools.cjs的config-set报 "Unknown config key"(#3033)。 - 修复(#3197):CJS 侧
config-schema.cjs增加并导出RUNTIME_STATE_KEYS,isValidConfigKey()新增运行时状态键判定;SDKconfig-mutation.ts对齐校验;新增 CI 一致性断言。 - 结构演进(#3536):
sdk/shared/config-schema.manifest.json成为 SDK 与 CJS 双端共同的单一事实源,双端集合由其派生,从结构上消除了这类「加一端忘另一端」的漂移。 - 验证:
tests/bug-3197-gsd-tools-config-whitelist.test.cjs(功能回归)与tests/config-schema-sdk-parity.test.cjs(集合一致性)共同保证修复不被回退。
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 StartedRust0627
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