get-shit-done 的 gsd-tools --json-errors 如何输出稳定错误码供脚本断言
【免费下载链接】get-shit-doneA light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.项目地址: https://gitcode.com/GitHub_Trending/getshi/get-shit-done
当你在测试或 CI 脚本中调用 get-shit-done 的gsd-toolsCLI 时,错误默认以Error: <text>的自由文本写到 stderr。这类文本措辞可能随版本变化,对原始错误串做.includes()或正则匹配会让断言在无害的文案改动上误报、又对真正的错误漏报。gsd-tools内置的 JSON error mode 解决了这个问题:开启后,所有错误以一条结构化 JSON 写到 stderr,其中reason字段是冻结的常量错误码,脚本可以稳定地断言它。本文适用于本仓库的gsd-toolsCLI(入口文件位于 get-shit-done/bin/gsd-tools.cjs),运行环境要求 Node 22 及以上(CONTRIBUTING.md 明确 Node 22 为最低支持版本,Node 24 是主要 CI 目标)。
准备条件
- Node 22 或更高版本(Node 24 为主 CI 目标;不要使用 Node 22 之外的 API)。
- 仓库中的
gsd-tools入口文件是 get-shit-done/bin/gsd-tools.cjs。docs/json-errors.md 中的示例命令写作node gsd-tools.cjs ...,即在文件所在目录内运行;在仓库根目录执行时等价写法为node get-shit-done/bin/gsd-tools.cjs ...。 - 错误码定义在 get-shit-done/bin/lib/core.cjs 的
ERROR_REASON冻结枚举中,断言前先核对该文件确认当前代码集合。
开启 JSON error mode
两种方式任选其一,均为 opt-in,默认关闭,关闭时人类操作者看到的仍是Error: <message>纯文本:
# Flag(测试代码中推荐): node gsd-tools.cjs --json-errors <command> [args] # Env var(shell 包装器与 CI 推荐): GSD_JSON_ERRORS=1 node gsd-tools.cjs <command> [args]两个实现细节值得注意(见 get-shit-done/bin/gsd-tools.cjs 中的处理逻辑):
--json-errors在任何 flag 解析之前被检测并从 argv 中移除,因此它不会被路由器当作未知命令处理,且--cwd或 workstream 解析阶段的失败也会走结构化 stderr。- 该标志只改变错误输出形式,对成功命令没有任何影响:成功的命令照常以退出码 0 结束,stdout 内容不变。
错误输出的线格式
任何错误发生时,进程向stderr恰好写一行 JSON 并以退出码1退出:
{ "ok": false, "reason": "<error_code>", "message": "<human text>" }| 字段 | 类型 | 说明 |
|---|---|---|
ok | false | 错误对象恒为false。 |
reason | string | 下文分类表中的类型化原因码,稳定,应断言这个字段。 |
message | string | 人类可读描述,可能变化,不要断言它。 |
tests/feat-3255-json-errors-mode.test.cjs 锁定了三条可复用的形状契约:错误对象顶层恰好是{ok, reason, message}三个键(无额外键);每次调用 stderr 只有一行 JSON(进程在第一个错误时退出);成功命令不受--json-errors影响。实现位于 get-shit-done/bin/lib/core.cjs 的error()函数:JSON 模式下error()将{ ok: false, reason, message }序列化后写入 stderr 并process.exit(1)。
稳定错误码分类
reason的取值是get-shit-done/bin/lib/core.cjs中ERROR_REASON的冻结常量(snake_case、按子系统加前缀分组)。docs/json-errors.md 的分类表如下:
Dispatch 错误(gsd-tools 路由层)
| Code | 触发条件 |
|---|---|
sdk_unknown_command | 未知顶层命令(如gsd-tools bogus-cmd)、未知点分命令(gsd-tools foo.bar且foo不是已知命令)、域内未知子命令(如gsd-tools intel bogus-sub) |
sdk_missing_arg | SDK 层守卫判定必填参数缺失 |
sdk_fail_fast | 触发 SDK fail-fast 策略 |
用法 / flag 错误
| Code | 触发条件 |
|---|---|
usage | --pick后缺少值;gsd-tools 不接受的版本 flag(--version、-v);顶层无参数调用 |
Config 错误(config-get、config-set、config-ensure-section)
| Code | 触发条件 |
|---|---|
config_key_not_found | config-get的键不存在于配置文件 |
config_no_file | 配置操作时.planning/config.json不存在 |
config_parse_failed | 配置文件存在但不是合法 JSON |
config_invalid_key | config-set的键不在允许白名单内 |
Phase / workflow 错误
| Code | 触发条件 |
|---|---|
phase_not_found | Phase 目录查找无匹配 |
summary_no_planning | 无.planning/目录时执行 summary 操作 |
Graphify 错误
| Code | 触发条件 |
|---|---|
graphify_no_graph | 未构建 graph 时执行 graphify 查询或 diff |
graphify_invalid_query | graphify 查询串格式错误 |
Hook / 安全错误
| Code | 触发条件 |
|---|---|
hooks_opt_out | Hooks 被 opt-out 配置禁用 |
security_scan_failed | 安全扫描产出了阻断操作的发现 |
兜底
| Code | 触发条件 |
|---|---|
unknown | 所有未分配具体原因码的其他错误 |
SDK 路由层的结构化原因会透传到 CJS 层(gsd-tools.cjs将 SDK 返回的errorDetails.reason传给error()),因此经 SDK 路径失败的命令(如config-get)同样能拿到具体代码(如config_key_not_found)而不是笼统的unknown。
在脚本中写断言
docs/json-errors.md 的规则:始终用JSON.parse解析 stderr 并断言类型化字段,绝不对原始错误串使用.includes()、.match()或正则。CONTRIBUTING.md 的 "Prohibited: Raw Text Matching on Test Outputs" 一节将此列为仓库级禁令(由scripts/lint-no-source-grep.cjs策略强制执行)。
文档给出的正确写法(摘自 docs/json-errors.md):
// CORRECT: parse then assert on typed field const result = runGsdTools(['--json-errors', 'bogus-command'], tmpDir); assert.strictEqual(result.success, false); const err = JSON.parse(result.error); assert.strictEqual(err.ok, false); assert.strictEqual(err.reason, 'sdk_unknown_command'); // WRONG: text matching (banned by lint-no-source-grep policy) // assert.ok(result.error.includes('Unknown command'));其中runGsdTools是本仓库测试 helper(见 tests/helpers.cjs),返回包含success、output(stdout)、error(stderr)字段的运行结果。tests/feat-3255-json-errors-mode.test.cjs 中的runJsonErrorshelper 展示了更严格的封装:先断言命令必须失败,再JSON.parse(result.error),解析失败时把完整 stderr 抛进错误信息——这样脚本在 wire 格式被破坏时能给出可读的诊断。
对于 shell 包装器与 CI,用文档给出的环境变量方式开启即可,后续对 stderr 的解析逻辑与 JS 侧相同:
GSD_JSON_ERRORS=1 node gsd-tools.cjs <command> [args]验证方式:触发已知场景并核对 reason
以下"调用 → 预期 reason"组合均能在 tests/feat-3255-json-errors-mode.test.cjs 中找到对应测试用例,可直接作为你脚本中的断言目标:
调用(带--json-errors) | 预期退出码 | 预期reason |
|---|---|---|
totally-unknown-command-xyzzy(任意未知顶层命令) | 1 | sdk_unknown_command |
foo.bar(未知点分命令) | 1 | sdk_unknown_command |
intel bogus-subcommand-xyzzy(域内未知子命令) | 1 | sdk_unknown_command |
generate-slug test-text --pick(--pick缺值) | 1 | usage |
--version generate-slug x(gsd-tools 不接受--version) | 1 | usage |
config-get nonexistent_config_key_xyzzy(先执行过config-ensure-section建好.planning/config.json) | 1 | config_key_not_found |
对每次失败调用,完整断言链是:退出码为 1;stderr 能JSON.parse成功;ok === false;reason等于预期值;顶层键排序后恰好为['message', 'ok', 'reason'];非空 stderr 行恰好一条。对成功路径(如generate-slug hello-world),断言进程成功、stdout 非空,证明 flag 未污染正常输出。
扩展:新增一个错误码
docs/json-errors.md 的 "Adding a new error code" 给出固定四步流程(get-shit-done/bin/lib/core.cjs 中ERROR_REASON的注释与之一致):
- 在
get-shit-done/bin/lib/core.cjs的ERROR_REASON中加常量,取值用 snake_case 小写(即 JSON 线上的形式),按子系统加前缀分组(如CONFIG_*、SDK_*)。 - 在调用点把它作为
error()的第二个参数传入:error(msg, ERROR_REASON.NEW_CODE)。 - 在 docs/json-errors.md 的分类表中加一行。
- 加一个用
JSON.parse断言新reason代码的测试。
四步同步变更是刻意设计:枚举、调用点、文档、测试任何一处遗漏都会导致代码面与测试面漂移被测试发现。
限制
message字段不稳定,任何对它的断言都会随文案改动失效;只有reason是稳定契约。- 模式默认关闭,且只影响错误输出;成功路径的行为与输出完全不变。
- 错误码是冻结枚举,未分配具体码的错误落到
unknown;你的脚本断言unknown时要意识到这是兜底而非具体原因。 - 每次调用只输出第一处错误的 JSON 行,进程随即退出,stderr 不会有第二条错误。
【免费下载链接】get-shit-doneA light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.项目地址: https://gitcode.com/GitHub_Trending/getshi/get-shit-done
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考