尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

gsd-sdk Query CLI Adapter 模块拆解:让 query 分发、错误映射与输出退出处理从 cli.ts 中独立出来

gsd-sdk Query CLI Adapter 模块拆解:让 query 分发、错误映射与输出退出处理从 cli.ts 中独立出来 gsd-sdk Query CLI Adapter 模块拆解让 query 分发、错误映射与输出退出处理从 cli.ts 中独立出来【免费下载链接】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本文以 changeset .changeset/cool-monkeys-smell.mdtype: Changed / PR #3074为核心骨架讲述 get-shit-done 的 SDKgsd-sdk如何把query相关的 CLI 逻辑抽取为独立的Query CLI Adapter Modulesdk/src/query/query-cli-adapter.ts。你会看到适配器与分发层的职责边界、错误→退出码的映射约定、CJS 回退策略以及抽取专有模块提升可测试性这一重构在源码与测试中是如何落地的。一、变更内容速览一次围绕职责单一的重构该 changeset 记录了一次结构性的内部重构无用户可见行为变化query CLI path extracted into a dedicated Query CLI Adapter Module—sdk/src/cli.tsnow delegates query-specific dispatch, error mapping, and output/exit handling tosdk/src/query/query-cli-adapter.tsfor better locality and testability.拆开来看这条 release note 包含四个要点抽取对象query 专属的分发dispatch、错误映射error mapping、输出与退出码处理output/exit handling抽取去处新增的独立模块 sdk/src/query/query-cli-adapter.ts宿主瘦身原来的总入口 sdk/src/cli.ts 不再直接承担这些逻辑只负责把 query 分支委托出去重构动机更好的代码局部性locality与可测试性testability。这是典型的 Seam Module 式演进——与仓库 docs/adr 中多次出现的模块化 seams 思路一脉相承把易变的、命令专属的适配逻辑收拢到贴近功能域的目录里这里是sdk/src/query/让总入口保持扁平。二、变更前的痛点cli.ts 身兼数职在抽取之前query 相关的逻辑分散在 sdk/src/cli.ts 的main()中。而cli.ts本身已经承担了大量职责参数解析parseCliArgs含run/auto/init/query四类命令读版本号、打印 USAGEworkstream 名校验、GSD_WORKSTREAM环境变量回退、多仓库 project root 查找init/auto/run三个命令的完整执行流程构造GSD实例、挂载 CLI/WebSocket transport、跑InitRunner等query 命令的分发、错误映射与输出。问题在于cli.ts是进程级入口天然难以单测依赖真实process、真实注册表、真实文件系统。把 query 分支留在入口文件里意味着每次调整 query 的分发或错误语义都要经过整条 CLI 链路验证。三、适配器模块的内部契约输入与输出抽取后的模块用一组清晰的 TypeScript 接口定义了CLI 世界与分发世界的边界。首先是输入// sdk/src/query/query-cli-adapter.ts export interface QueryCliAdapterInput { projectDir: string; ws?: string; // workstream 名路由 .planning/ 到 .planning/workstreams/name/ queryArgv?: string[]; // query 后的原始 argv去除了已知 SDK 全局 flag }其次是输出。这里有一个值得注意的设计适配器不直接写 stdout/stderr、不直接设process.exitCode而是返回一个结构化结果由调用方cli.ts负责落盘式输出// sdk/src/query/query-cli-output.ts export interface QueryCliAdapterOutput { exitCode: number; stdoutChunks: string[]; stderrLines: string[]; }正是这个只计算、不 IO的约定让测试成为可能——测试无需真实进程即可断言退出码与输出内容。cli.ts中的 query 分支因此变得极其单薄// sdk/src/cli.ts#L314-L324query 命令分支 if (args.command query) { const result await runQueryCliCommand({ projectDir: args.projectDir, ws: args.ws, queryArgv: args.queryArgv, }); for (const line of result.stderrLines) console.error(line); for (const chunk of result.stdoutChunks) process.stdout.write(chunk); process.exitCode result.exitCode; return; }可见cli.ts保留的是与终端进程相关的薄壳行为而 query 域内的语义全部下沉到适配器。四、runQueryCliCommand运行时上下文、注册表与分发编排query-cli-adapter.ts 中的runQueryCliCommand是模块的核心它把一次gsd-sdk query …调用编排为四个步骤export async function runQueryCliCommand(input: QueryCliAdapterInput): PromiseQueryCliAdapterOutput { try { // 1) 解析运行时上下文project root workstream const runtime resolveQueryRuntimeContext({ projectDir: input.projectDir, ws: input.ws }); // 2) 装配查询注册表与命令拓扑 const registry createRegistry(); const topology createCommandTopology(registry); // 3) 执行分发native / cjs / error 三种模式 const out await runQueryDispatch({ registry, projectDir: runtime.projectDir, ws: runtime.ws, cjsFallbackEnabled: queryFallbackToCjsEnabled(), resolveGsdToolsPath, topology, }, input.queryArgv ?? []); // 4) 把分发结果翻译成 CLI 输出 return buildQueryCliOutputFromDispatch(out); } catch (err) { return buildQueryCliOutputFromError(err); } }4.1 运行时上下文解析workstream 的四级优先级query-runtime-context.ts 负责确定一次查询作用在哪个.planning/树。其优先级源码注释中明确记录为--ws nameflag最高优先级若校验失败则视为未提供GSD_WORKSTREAM环境变量.planning/active-workstream文件由readActiveWorkstream读取兜底根目录.planning/无 workstream。同时resolveQueryRuntimeContext内部通过findProjectRoot处理多仓库场景的 project root 向上查找保证无论从哪个子目录发起查询语义都落在正确的工作区上。4.2 分发内部验证 → 拓扑匹配 → 原生/CJS 回退真正干活的是runQueryDispatchsdk/src/query/query-dispatch.ts其内部流程为输入验证空 argv、--pick缺字段等都会直接产出validation_error形态的失败结果命令归一化normalizeQueryCommand把state json、init.execute-phase、scaffold …等拼写映射到注册表的规范命令拓扑匹配createCommandTopology(registry).resolve(...)对归一化后的 argv 做最长前缀匹配先试a.b.c再试a b c空格形态越长者优先三种分发模式命中注册表走native未命中且 CJS 回退开启走cjsshell out 到gsd-tools.cjs两者皆不可走error携带 no-match 提示信息。分发结果统一为结构化联合类型见 query-dispatch-contract.ts成功{ ok: true, stdout, stderr, exit_code: 0 }失败{ ok: false, error: { kind, code, message, details }, stderr, exit_code }kind的取值集合固定为六种unknown_command、native_failure、native_timeout、fallback_failure、validation_error、internal_error。CLI 适配器作为薄壳直接消费这个契约里的exit_code与stdout/stderr。4.3 一个值得注意的安全细节mutating 命令的 --help 短路在runQueryDispatch中还有一道防御性保护若匹配到的原生 handler 在命令清单中标为mutation会写盘且 argv 里带--help/-h则直接短路返回一个非写盘的 usage stub避免milestone.complete --help这类调用意外触发落盘副作用见源码注释 #3259 的说明。这说明分发层不仅管把命令送到 handler还负责拦截危险的组合。五、错误到退出码的映射与输出约定适配器模块的第三项职责是错误映射实现在 query-cli-output.ts 的两个函数中。buildQueryCliOutputFromDispatch处理已结构化的失败把out.stderr与out.error.message拼进stderrLines并透传分发层算好的exit_code。buildQueryCliOutputFromError处理抛出型异常按异常类型分三层映射GSDError按err.classification通过exitCodeFor映射语义化退出码见 sdk/src/errors.tsvalidation参数缺失、schema 违规→10blocked依赖缺失、phase 不存在→11execution运行期失败、文件 I/O、解析错误→1interruption超时、信号、用户取消→1GSDToolsError优先把子进程原始 stderr按行透传err.stderr.split(/\r?\n/)让用户看到gsd-tools.cjs的真实诊断而非二次包装文本退出码取err.exitCode ?? 1。其他未知异常统一为Error: message退出码1。输出侧约定同样明确分发成功且结果为 JSON 时以JSON.stringify(data, null, 2)的两空格缩进写入 stdout纯文本格式则原样追加换行--pick field只对 JSON 输出生效若对 text 输出使用--pick会直接抛错。这样的分层让调用方与 handler 可以区分输入该修10、环境被阻塞11与运行失败1Agent 也便于据此决定下一步动作——这正是错误分类系统sdk/src/errors.ts设计时想要的可观测语义。六、兼容性开关GSD_QUERY_FALLBACK 与 CJS 回退适配器模块中还有一段容易被忽略、但对运行行为至关重要的逻辑——CJS 回退开关// sdk/src/query/query-cli-adapter.ts#L15-L19 function queryFallbackToCjsEnabled(): boolean { const v process.env.GSD_QUERY_FALLBACK?.toLowerCase(); if (v off || v never || v false || v 0) return false; return true; }也就是说默认开启CJS 回退当某个命令没有对应的原生注册 handler例如仍是 CLI-only 的graphify、from-gsd2时runQueryDispatch会把归一化后的 argv 交给resolveGsdToolsPath指向 query-gsd-tools-path.ts 背后的 SDK Package Compatibility 模块最终落到仓库的gsd-tools.cjs以子进程方式执行stderr 会附带一条简短的 bridge 警告。只有显式设置GSD_QUERY_FALLBACKoff/never/false/0时才进入严格模式未命中原生 handler 直接报unknown_command。这一点在官方文档中有对应说明sdk/README.md 的环境变量表也在 sdk/src/query/QUERY-HANDLERS.md 的gsd-sdk query路由一节得到确认。回退的粒度还延伸到策略层query-fallback-policy.ts中另有GSD_QUERY_FALLBACKregistered形态的受限策略对应describeFallbackDisabledPolicy供需要部分约束的调用方使用。对使用者而言最常见的几个实测命令形态是# 查询状态原生 handler gsd-sdk query state show gsd-sdk query state json # 取 JSON 结果中的单个字段 gsd-sdk query check auto-mode --pick active # 阶段操作空格或点号拼写均可 gsd-sdk query phase add 12 --help # 命中原生 handler输出该子命令的上下文帮助cli.ts的宽松解析器parseCliArgsQueryPermissivesdk/src/cli.ts只吞掉--project-dir、--ws、--ws-port、--model、--max-budget、-h/--help、-v/--version等已知 flag其余 token 一律原样进入queryArgv透传给注册表——这正是gsd-sdk query phase add --help能把--help送达到 handler 而不是被顶层 usage 截胡的原因注释中的 #3019 决策。七、可测试性红利依赖注入 seams 与单测覆盖changeset 把动机明确表述为 for better locality and testability。源码层面证据充分——适配器把变化点收敛为四个可 mock 的依赖 seamcreateRegistry()注册表createCommandTopology(registry)命令拓扑runQueryDispatch(...)分发器resolveGsdToolsPathCJS 路径解析resolveQueryRuntimeContext运行上下文于是单测 sdk/src/query/query-cli-adapter.test.ts 得以用vi.mockvi.hoisted构造一个纯净环境覆盖三条关键行为缺失命令的验证失败queryArgv: []时分发返回validation_errorexit_code 10断言输出 stderr 含 requires a commandws 与拓扑透传断言runQueryDispatch收到的ws、topology正确、且未注入已废弃的nativeAdapter路径 seam 装配断言传给分发器的resolveGsdToolsPath来自 query seam 模块且调用后把/tmp/project映射为/mock/gsd-tools.cjs。配合同目录的 query-cli-output.test.ts错误映射与输出构建也被独立锁定。仓库层面还有跨层集成回归tests/bug-2524-sdk-query-ws-flag.test.cjs文件名直指历史上 ws flag 的缺陷验证 SDK query 的 workstream flag 行为并在注释中回链到query-cli-adapter.test.ts。这形成了单测锁行为 集成防回归的双层保障——而这正是把逻辑从进程入口里挪出来之后才有的可测性。八、对外行为不变契约如何被完整继承抽取重构最大的风险是悄悄改变 CLI 语义。从现有代码可以确认迁移是按契约平移而非重写cli.ts的 query 分支只是把拿到结构化输出 → 逐行/逐块写出 → 设置退出码这段通用 shell 行为保留在入口其余全部委托Query CLI Adapter Module 对外暴露的仍是原来的 exit code、JSON stdout 与 stderr 约定。分发的规范化、最长前缀匹配、CJS 回退等语义定义在 sdk/src/query/query-dispatch.ts 与 QUERY-HANDLERS.md未被入口文件改动波及。gsd-sdk query的完整路由说明normalize → 最长前缀匹配 → dotted token 拆解 → CJS 回退 → JSON 输出与命令覆盖矩阵state.*、verify.*、phase.*、init.*等 manifest 家族都记录在 sdk/src/query/QUERY-HANDLERS.md可作为本次抽取后行为不变量的对照基准。九、小结这次重构留给架构的启发从一次小小的 changeset.changeset/cool-monkeys-smell.mdtype: Changed / PR #3074出发可以看到 get-shit-done 在 SDK 演进中反复使用的手法进程入口保持薄cli.ts只做参数解析与 IO 收尾业务语义全部下沉命令域自治sdk/src/query/目录既是 handler 的家也是 CLI 适配器的家改动不再跨层结构化契约从分发结果ok/exit_code/stderr到 CLI 输出exitCode/stdoutChunks/stderrLines再到错误分类validation10 / blocked11 / execution1层层都是可断言的数据结构可测优先依赖以 seam 形式注入让 query-cli-adapter.test.ts 这样的单测可以在无进程、无真实文件系统的情况下锁定行为。如果你正在维护一个被多个子命令撑大的 CLI 入口这条演进路径可以直接复用先定义输入结构 输出结构再把命令专属的分发、错误映射、退出处理抽进贴近功能域的适配器模块最后用依赖注入让它在测试里无痛重现。这既是 sdk/src/query/query-cli-adapter.ts 给出的答案也是 cli.ts 瘦身之后依然稳定运行的原因。【免费下载链接】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),仅供参考
返回列表