【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载导读本篇技术指南围绕 gsd-core 一次真实的健壮性修复展开config-set key只传键、不传值曾以{ updated: true }加退出码 0 的方式成功却因为undefined值被JSON.stringify静默丢弃而悄悄破坏.planning/config.json。文章将以该修复为主线讲解 gsd-core 配置子系统config-set/config-get的完整参数校验链路、ERROR_REASON.USAGE类型化错误机制以及驱动这次修复的 CLI 对抗性输入矩阵negative matrix测试工具的设计。读完你将掌握 gsd-core 命令行参数校验的防守思路、类型化错误断言方法以及如何用无 shell 的 spawnSync 测试工具系统化地验证 CLI 的异常输入安全。一、问题背景一次成功的失败修复记录见仓库归档的 changeset 文档 .changeset/archived/3593-cli-negative-matrix-harness.md其描述非常直白修复前执行config-set model_profile只有 key、没有 value会返回{ updated: true }且退出码为 0——但 value 会以undefined传入随后被JSON.stringify在写盘时静默丢弃。这既不会报错也不会留下任何可追踪的失败痕迹属于典型的静默配置损坏silent config corruption。问题链条有三环参数缺口不设防config-set只校验了 key 的存在性未校验 value 是否存在值传递为undefined缺值调用时 value 参数为undefined而解析分支布尔/数字/JSON全部落空原值原样进入写盘流程JSON 序列化静默丢弃JSON.stringify对undefined属性会直接跳过导致设置成功但键实际上被删掉或从未写入。用一句话概括CLI 报告成功、磁盘悄然变化、调用方毫无察觉——这正是对抗性输入测试要消灭的失败模式。二、修复落地写盘之前的双重 Usage 守卫修复在配置命令的核心实现 src/config.cts 的cmdConfigSet中完成。该函数在解析与写盘之前先做两道参数守卫function cmdConfigSet(cwd: string, keyPath: string | undefined, value: string | undefined, raw: boolean, options: ConfigSetOptions {}): void { const dryRun options.dryRun true; if (!keyPath) { error(Usage: config-set key.path value, ERROR_REASON.USAGE); } // #3593: reject the key without value form (e.g. config-set // model_profile with args[2] undefined). Without this guard the // value passes through as undefined, the number/boolean/json branches // all fall through, and the write either silently strips the key // (JSON.stringify drops undefined values) or writes a corrupt entry. // Typed reason so the negative-matrix test can assert on it instead // of greppinng prose. if (value undefined) { error(Usage: config-set key.path value, ERROR_REASON.USAGE); } // ... }要点有三两道守卫缺一不可第一道拦截完全没传 keyconfig-set裸调用第二道拦截只传 key 没传 valueconfig-set model_profile——后者正是本次修复的靶点发生在任何写操作之前守卫在loadConfigJson、withPlanningLock、platformWriteSync之前执行确保失败路径不触碰磁盘携带类型化原因码error(..., ERROR_REASON.USAGE)不只是打印文案还会附带结构化的错误原因让测试可以断言result.reason usage而非靠正则匹配散文文本源码注释原文Typed reason so the negative-matrix test can assert on it instead of greppinng prose。类型化错误机制ERROR_REASON 冻结枚举ERROR_REASON.USAGE来自 I/O 原语模块 src/io.cts。该模块以Object.freeze定义了一组错误原因枚举其中USAGE: usage与CONFIG_INVALID_KEY、CONFIG_KEY_NOT_FOUND、CONFIG_NO_FILE、CONFIG_PARSE_FAILED等一起构成 CLI 可编程化的失败契约。配合--json-errors模式error()会在 stderr 输出单行 JSON 载荷形如{ ok: false, reason: usage, message: ... }随后抛出ExitError以非零码退出ADR-3889 起error()由直接process.exit改为抛错便于在进程内测试中捕获断言。从源码结构看这一机制的价值在于错误从给人看的话升级为给程序判的码测试与上层 Agent 都能用reason精确区分失败类别而不是解析文本。三、驱动修复的测试基建CLI 负向输入矩阵#3593这次修复并非偶然发现而是由新引入的CLI adversarial-input matrix对抗性输入矩阵#3593系统化地逼出来的。测试工具位于 tests/helpers/cli-negative.cjs其设计值得单独拆解。3.1 无 shell 的进程调用工具核心是spawnSync包装gsd-core/bin/gsd-tools.cjs关键约束是敌对值一律作为 argv 元素传递绝不拼进 shell 字符串源码注释Hostile values are passed as argv elements — never composed into a shell string。这样测试输入里的;、、$()、反引号、引号、换行、空字节等元字符到达 CLI 时只是不透明的数据而非 shell 语法——从源头杜绝了测试工具自身引入假阳性注入的可能。3.2 类型化中间表示IRparseSpawnResult把原始spawnSync结果整理成结构化 IRstatus/signal退出码与终止信号ok/reason/message从--json-errors的 stderr JSON 载荷解析而来hasStackTrace用正则/\n\s{2,}at\s/检测 stderr 是否泄漏了 V8 栈帧——只要出现at帧行就说明 CLI 在未包裹的代码路径上抛了异常测试即可据此失败jsonErrorsRequested标记本次是否走了 JSON 错误模式供测试区分缺 reason 是缺陷还是故意关闭 JSON 模式。工具文档明确写道The harness does NOT decide what the test asserts — it just shapes the data so the assertion is mechanical and prose-free即测试只断言reason码与退出状态永不断言散文文本。3.3 针对 config-set 缺值的回归测试该矩阵针对config命令族共枚举了 12 类对抗输入源自 CONTRIBUTING.md 的 QA Matrix Requirements / CLI and command routing并在 tests/config-get-default.test.cjs 中以折叠区块folded fromfeat-3593-cli-negative-config.test.cjs的形式落地。本次修复的核心回归测试如下test(config-set with key but no value fails with a typed reason, (t) { const projectDir createTempProject(cli-neg-config-3-); t.after(() cleanup(projectDir)); const result runCli([config-set, model_profile], { cwd: projectDir }); assertSafeFailure(result, config-set missing value); });assertSafeFailure是一组普适不变量status非 0必须失败signal为 null必须干净退出而非被信号杀死hasStackTrace为 false不得泄漏 V8 栈帧JSON 载荷ok false且reason是非空字符串类型化原因可精确断言。矩阵中的其他用例还覆盖了空串/纯空白 key、重复--cwd、未知全局 flag、未知子命令、以--开头的值应作为值而非 flag 处理等并配合snapshotInventory快照断言——失败的读操作不得改动文件系统。这些共同把config 命令族在任何畸形输入下都必须安全失败固化成了可回归的契约。四、纵深config-set 的完整入参校验体系cmdConfigSet远不止两道 Usage 守卫。从 src/config.cts 的实现看它在解析阶段之后、写盘之前还串联了一整套按 key 分派的类型化校验值得完整列出值解析规则true/false解析为布尔null解析为 null且语义化为清除该键见下数字走Number.isFinite(Number(val))而非!isNaN——Infinity/-Infinity不再被强制转成非有限数JSON.stringify会把非有限数渲染成null造成磁盘与回显不一致以[或{开头的字符串尝试JSON.parse为数组/对象失败则保留为字符串。清除语义#2046显式传入null等价于清除调用unsetConfigValue删除该键而非持久化 JSON null并支持--dry-run预览。源码注释指出持久化的 null 仍是存在且接近 truthy的值消费者必须特判对 secret 键尤其危险——遗留值可能被当作真实凭据使用。按 key 的强类型校验摘录Key 类别校验规则context/phase_id_convention/workflow.drift_action/workflow.human_verify_mode/workflow.context_guard_mode等枚举白名单assertEnumValue要求typeof parsedValue string且属于合法集合堵住String([val]) val的数组强转旁路context_window/workflow.smart_zone_tokens/workflow.drift_threshold有限正整数smart_zone_tokens额外要求Number.isSafeInteger读侧只接受安全整数接受与执行必须一致workflow.post_planning_gaps/workflow.compact_content/workflow.agent_hint_routing/planner.stall_detection_enabled/git.create_tag/statusline.*等严格布尔字符串false不能绕过#4570只有真布尔可改变默认开启的策略git.protected_branches非空字符串数组且逐元素校验用索引遍历而非.every()避免稀疏数组空洞被跳过hooks.context_warning_threshold/hooks.context_critical_threshold0–100 的百分比且拒绝警告0与临界100这类配对后永不可用的端点值ship.pr_body_sections段对象的字段白名单、source选择器正则、模板 token 白名单review.default_reviewers/review.reviewer_instances.name.field实例名正则、内建 slug 冲突、cli字段必须为已知适配器能力注册表键#1628按getCapabilityConfigSchema(cwd)声明的 typeenum/boolean/number/string动态校验写路径的通用防御_setNestedValue/_unsetNestedValue对每个路径段含中间段做内联字面量比较拦截__proto__/prototype/constructor防原型污染CodeQLjs/prototype-pollution-utility查询能识别的屏障形态secret 键isSecretKey在 CLI 输出前一律maskSecret掩码——明文只存在于磁盘config.jsonstdout/stderr 永不回显见 src/secrets.cts 的SECRET_CONFIG_KEYS与相关测试中对brave_search掩码的断言写入统一走withPlanningLock加锁 单次platformWriteSync原子落盘批写setConfigValues在单次锁内完成多键写入。此外config-get读侧也承担了对称职责config-get key [--default value]对缺失键依次回退 root 配置继承#2702、--default标志、schema 级默认值SCHEMA_DEFAULTS与能力注册表 configSchema见 src/config-loader.cts 的CONFIG_DEFAULTS清单且遍历全程用hasOwnProperty门控防止原型链解析——相关断言见 tests/config-get-default.test.cjs 中折叠的 #2256 区块与原型遍历属性测试。五、实操验证如何在本仓库复现修复前后行为以下操作均在当前仓库内可执行仓库为只读请勿修改任何文件1. 查看修复源码与守卫注释修复主体src/config.cts 中cmdConfigSet的#3593注释与两道 Usage 守卫约 824–838 行类型化错误枚举src/io.cts 中ERROR_REASONUSAGE: usage等与error()的 JSON 模式。2. 阅读并理解负向矩阵工具工具实现tests/helpers/cli-negative.cjs回归测试config-set with key but no value等 12 类用例tests/config-get-default.test.cjs 中folded:feat-3593-cli-negative-config区块。3. 运行相关测试验证契约node --test tests/config-get-default.test.cjs该文件同时覆盖config-get --default#1893、能力注册表 schema 默认值#2256、context_window合法键#2798与 schema 默认返回#2943是理解 config 命令族契约的最短路径。4. 手动观察当前行为修复后在一个临时项目目录中例如mkdir /tmp/gsd-demo cd /tmp/gsd-demo执行node /data/web/disk1/git_repo/gh_mirrors/ge/gsd-core/gsd-core/bin/gsd-tools.cjs config-set model_profile --json-errors修复后的预期结果非零退出stderr 输出单行 JSONok: falsereason: usage不产生任何栈帧且目录中不会出现被部分写入的config.json——这正是失败在写盘之前的直观体现。六、结语从一次 bug 到一类防御回看 #3593 的完整闭环可以看到 gsd-core 的一条质量主线问题暴露CLI 负向矩阵以系统化对抗输入缺参、空串、元字符、重复 flag、未知子命令等 12 类代替随机手测缺陷定位config-set缺值调用落入JSON.stringify静默丢弃undefined的陷阱修复加固写盘前增设类型化 Usage 守卫让失败可编程化、可断言回归锁定用reason usage 无栈帧 零文件突变的三重断言把必须安全失败固化为永久契约。对任何命令行工具的维护者而言这条链路的可迁移价值在于不要信任参数个数不要在写盘时依赖序列化器兜底永远给失败一个类型化原因。gsd-core 的 config 命令族还提供了完整的枚举/布尔/数字/原型污染/密钥掩码校验体系可以作为配置类 CLI 入参治理的参照实现。关联资源修复记录.changeset/archived/3593-cli-negative-matrix-harness.md核心实现src/config.cts、src/io.cts、src/config-loader.cts测试基建与用例tests/helpers/cli-negative.cjs、tests/config-get-default.test.cjs交互式配置命令文档commands/gsd/config.md、docs/CONFIGURATION.md赞分享【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载相关推荐gsd-core 配置写入链路修复剖析config-set 如何解锁 model_overrides.agent-id 动态键gsd core 配置写入链路修复剖析config set 如何解锁 model_overrides.agent id 动态键 本文围绕 gsd coregsd-core 代码审查自动修复调度修复/gsd-code-review --fix 标志从静默丢弃到完整链路gsd core 代码审查自动修复调度修复 /gsd code review fix 标志从静默丢弃到完整链路 本篇技术文章以 gsd core 仓库中的 cget-shit-done 缺陷修复实录3593 让 config-set key 缺值调用在写入前干净失败get shit done 缺陷修复实录 3593 让 config set key 缺值调用在写入前干净失败 导读 .planning/config.js人工智能AI 应用提示工程开发工具工作流自动化AI Agent上一篇minikube 从 Pod 访问宿主机资源host.minikube.internal 完整实战指南下一篇终极黑客松实战指南从创意到发布的完整成长路径创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
