)
【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载导读本文围绕 gsd-core 中CommandRoutingHub命令路由枢纽的一次内部重构展开变更集 176-typed-result-discriminated-union 将 Hub 分发的错误结果从一个errorKind字符串加泛化message/details逃生舱重构为按错误种类携带各自类型化负载的判别联合。读完本文你将掌握四种错误变体的精确字段契约、四个工厂函数的正确用法、Hub 对非法返回的运行时校验机制以及下游命令路由器如 phase-command-router.cts如何通过kind判别做分支消费。一、背景Hub 从设计到类型化收缩的演进脉络CommandRoutingHub是 gsd-core 中所有命令族phase、state、verify、validate、init 等的统一分发接缝。它的诞生背景记录在 ADR-0012 中当时七个*-command-router.cjs文件各自重复检查模式 → 调用处理器 → 映射错误的三段式分发逻辑策略变更需要同时改多处。ADR-0012 因此引入 Hub将**无抛出契约no-throw contract与封闭错误分类closed error taxonomy**集中到一个模块。随后 ADR-0174 将 SDK 双运行时折叠为单一 TypeScript 源码树Hub 被简化删除mode、sdkLoader、cjsRegistry与两个 SDK 错误种类只保留四项跨切面关注点统一错误契约、manifest 解析、参数形状归一、可观测性。正是这次单运行时折叠让 Hub 的错误类型从 ADR-0012 时期的{ ok: false, errorKind, message, details? }平铺结构有了收紧为紧类型判别联合的前提。变更集 #176 正是这一演进的关键落地步骤它将errorKind字段重命名为kind并为每种错误变体定义专属的类型化负载移除 Hub 发错误结果中泛化的message/details逃生舱。从源码结构看这次重构属于SDK-internal内部契约调整见 变更集注释不触碰公开文档表面但它是所有命令路由器与调用方必须跟随迁移的硬契约变更。二、核心变更从errorKind平铺字段到kind判别联合2.1 变更前后对照重构前ADR-0012 时代的错误结果形态Result { ok: true, data } | { ok: false, errorKind, message, details? }重构后#176 落地于 src/command-routing-hub.cts的形态type ResultT | { ok: true; data: T } | { ok: false; kind: UnknownCommand; command: string } | { ok: false; kind: InvalidArgs; arg: string; reason: string; exitReason?: string } | { ok: false; kind: HandlerRefusal; reason: string } | { ok: false; kind: HandlerFailure; message: string; cause?: Error };两处关键语义变化判别字段重命名errorKind→kind。kind既是运行时判别属性也是 TypeScript 的字面量类型让switch/if分支在编译期就能获得类型收窄narrowing。按变体携带类型化负载每个ok: false变体只带自己真正需要的字段泛化的message与details逃生舱被移除——UnknownCommand不再有messageInvalidArgs不再有details。2.2 封闭错误种类枚举四个错误种类以冻结对象ERROR_KINDS导出src/command-routing-hub.cts#L61-L70const ERROR_KINDS Object.freeze({ UnknownCommand: UnknownCommand, // 请求的 family/subcommand 组合不在 manifest 中 InvalidArgs: InvalidArgs, // 处理器在执行前拒绝了传入参数 HandlerRefusal: HandlerRefusal, // CJS 处理器显式返回拒绝如不支持的子命令 HandlerFailure: HandlerFailure, // 处理器抛出了意外异常 } as const);测试 command-routing-hub.test.cjs#L511-L516 专门断言ERROR_KINDS的值是与其键名一致的稳定字符串常量保证调用方switch (result.kind)时与ERROR_KINDS.X一一对应不依赖裸字符串字面量。三、四种错误变体详解3.1UnknownCommand— 未知命令interface UnknownCommandResult { ok: false; kind: UnknownCommand; command: string; }语义请求的 family/subcommand 组合不在 manifest 或 CJS registry 中。负载仅command非空字符串例如phase或phase nonexistent。触发路径manifest 缺失 family、manifest 不含 subcommand、registry 缺 family、registry 缺 handler 四种情况都会返回该变体见 src/command-routing-hub.cts#L375-L401。严格键集测试断言其结果恰好为[command, kind, ok]三个键不得多出message/detailstests/command-routing-hub.test.cjs#L380-L395。3.2InvalidArgs— 参数校验失败interface InvalidArgsResult { ok: false; kind: InvalidArgs; arg: string; reason: string; exitReason?: string; // 可选见 3.5 }语义处理器在执行前拒绝了传入参数参数缺失、不支持、类型错误等。负载arg指明是哪个参数如--dry-run、--phasereason给出人类可读的解释如phase insert does not support --dry-run。严格键集二参形式的结果恰好为[arg, kind, ok, reason]tests/command-routing-hub.test.cjs#L423-L446。3.3HandlerRefusal— 处理器显式拒绝interface HandlerRefusalResult { ok: false; kind: HandlerRefusal; reason: string; }语义处理器有意识地拒绝执行如子命令在当前路由器语境下不被支持区别于抛异常的HandlerFailure。负载仅reason字符串。严格键集恰好[kind, ok, reason]tests/command-routing-hub.test.cjs#L449-L470。3.4HandlerFailure— 处理器异常interface HandlerFailureResult { ok: false; kind: HandlerFailure; message: string; cause?: Error; }语义处理器在分发过程中抛出意外异常Hub 捕获后转换为结构化错误。负载message为人类可读失败描述cause为原始抛出的 Error若存在。非 Error 抛出值处理若处理器抛出字符串或普通对象工厂会用new Error(non-Error cause: ...)包装并将原值挂在wrapper.thrown上保证下游.cause.stack不会静默返回undefined见 src/command-routing-hub.cts#L159-L177。严格键集有 cause 时恰好[cause, kind, message, ok]tests/command-routing-hub.test.cjs#L473-L491。3.5InvalidArgs的可选扩展exitReason?ADR-0174 修订 #1642 为InvalidArgs增加了可选字段exitReason它单独携带一个ERROR_REASON枚举值如ERROR_REASON.USAGE与reason人类可读文本分离。这样从error(msg, ERROR_REASON.USAGE)直调迁移到makeInvalidArgs(...)Result 的路由器能在GSD_JSON_ERRORS1的 JSON 错误信封中保留类型化 reason供 CLI 测试与集成 harness 消费。工厂在第三个参数为undefined或空字符串时不写入该键维持严格键集不变量src/command-routing-hub.cts#L139-L147。测试 command-routing-hub.test.cjs#L846-L940 覆盖了二参省略、三参携带、undefined/视为缺省、以及 Hub 透传不变等全部情形。四、工厂函数唯一合法的变体构造入口四个工厂函数随 Hub 一并导出src/command-routing-hub.cts#L435-L442供处理器与调用方构造错误结果工厂函数签名返回值makeUnknownCommand(command: string)ReadonlyUnknownCommandResultmakeInvalidArgs(arg: string, reason: string, exitReason?: string)ReadonlyInvalidArgsResultmakeHandlerRefusal(reason: string)ReadonlyHandlerRefusalResultmakeHandlerFailure(message: string, cause?: unknown)HandlerFailureResult三个设计要点全部返回Object.freeze冻结对象调用方无法在返回后向变体追加字段、破坏类型化负载不变量。测试 command-routing-hub.test.cjs#L767-L768 断言makeInvalidArgs返回冻结对象。ok固定为false as const工厂产物在 TypeScript 层面即被收窄为对应错误变体杜绝把true结果误传给错误分支。cause参数为unknown而非ErrormakeHandlerFailure内部处理三种情况——Error实例原样保存非 Error 值包装进带.thrown的 Errornull/undefined则不写入cause键。五、运行时守卫对处理器返回值的合法性校验类型系统只在编译期生效运行时处理器仍可能返回非法形状。因此 Hub 在dispatch内部对处理器返回的ok: false结果执行运行时校验src/command-routing-hub.cts#L408-L419依据是每个变体的字段模式_VARIANT_SCHEMAsrc/command-routing-hub.cts#L186-L204变体必填字段允许字段全集UnknownCommandcommandok, kind, commandInvalidArgsarg, reasonok, kind, arg, reason, exitReason?HandlerRefusalreasonok, kind, reasonHandlerFailuremessageok, kind, message, cause校验器_validateErrResultsrc/command-routing-hub.cts#L211-L244检出三类违规并返回契约违例描述未知kind不在封闭枚举中直接拒绝。缺失必填字段如InvalidArgs少了reason。多余字段如HandlerFailure变体携带了不允许的details键。任何违规结果都会被强制转换为HandlerFailure消息形如handler returned malformed Result variant: ...并以违规结果本身作为cause。测试 command-routing-hub.test.cjs#L559-L672 系统验证了这一行为用message冒充reason的InvalidArgs、缺reason的HandlerRefusal、缺message且带多余details的HandlerFailure、以及携带旧式errorKind字段的SomeLegacyKind全部被收编为HandlerFailure而形状合法的结果则原样透传不做任何改写。这一机制意味着#176 的判别联合不仅在编译期收紧类型还在运行时兜底旧式{ ok: false, errorKind: ... }返回即使漏网传入也会在 Hub 边界被识别并归一化不会带着非法形状污染下游。六、唯一的例外ExitError故意重抛Hub 的无抛出契约存在一个被源码注释明确标注的例外src/command-routing-hub.cts#L24-L32 与 L354-L356若处理器抛出的是ExitError来自 cli-exit.cts 的进程退出接缝例如io.cts的error()触发Hub故意重抛而非捕获转换。原因是抛出方已经自行写好了 stderr 并携带特定退出码终止进程若将其包装为HandlerFailure会从ExitError的泛化构造默认值重新推导消息、打印第二条错误的 stderr 行。重抛让它一路穿透到 CLI 入口的runMain()——这是唯一被设计为捕获ExitError的位置。七、下游消费命令路由器如何基于kind分支phase-command-router.cts是迁移到新契约的代表性消费者。它从 Hub 导入createHub, ERROR_KINDS, makeInvalidArgssrc/phase-command-router.cts#L23在参数校验处用工厂函数构造错误结果例如makeInvalidArgs(--id, --id requires a value)makeInvalidArgs(token,phase add does not support ${token})makeInvalidArgs(--descriptions, --descriptions must be a JSON array)makeInvalidArgs(phase-number, phase remove accepts exactly one phase number)见 src/phase-command-router.cts#L124-L270 的批量使用。在结果消费端路由器按kind判别分支src/phase-command-router.cts#L310-L315if (result.kind ERROR_KINDS.UnknownCommand) { // 未知命令分支 } if (result.kind ERROR_KINDS.InvalidArgs || result.kind ERROR_KINDS.HandlerRefusal) { // 参数/拒绝分支 }从源码结构看其余命令族路由器intel、quick-batch、graphify、refactor-trigger、mcp-server等见 src 目录 下各*-command-router.cts同样导入了这些工厂与常量说明判别联合契约已在全仓库命令族中推广。八、测试证据与不变量清单专项测试区块 command-routing-hub.test.cjs#L378-L516 为 #176 的判别联合提供了完整的回归防护核心不变量可归纳为严格键集每个错误变体恰好携带其类型化负载键不得有多余或缺失四个变体各有专属断言。kind值稳定ERROR_KINDS字符串常量与其键名一致可安全用于switch。工厂冻结工厂产物不可变更。非法形状归一畸形处理器返回在 Hub 边界被强制转换为HandlerFailure。exitReason可选语义二参省略、三参携带、undefined/视为缺省且 Hub 透传不变。九、迁移要点与结论对 Hub 的调用方与处理器作者#176 带来的迁移动作集中在三点字段重命名所有读取result.errorKind的地方改为result.kind。按变体消费负载不再依赖泛化message/details——UnknownCommand读command、InvalidArgs读arg/reason、HandlerRefusal读reason、HandlerFailure读message/cause。用工厂构造错误处理器返回错误结果一律通过四个工厂函数生成绕开工厂直接拼对象会在运行时被校验器拒绝并归一化。CommandRoutingHub的错误结果类型化改造本质上是把错误从模糊的键值包升级为可被编译器收窄、可被运行时校验、可被稳定枚举判别的领域模型。它让命令分发链路上的每个环节——处理器、Hub、路由器、CLI 适配层——对错误的形状拥有唯一共识这正是 gsd-core 单运行时架构下错误契约收敛的关键一步。若需进一步追溯设计依据可阅读 ADR-0174判别联合形状的决策与被其取代的 ADR-0012Hub 无抛出契约的起源。赞分享【免费下载链接】gsd-coreGit. Ship. Done - Core项目地址https://gitcode.com/gh_mirrors/ge/gsd-core点击查看免费下载相关推荐TypeSpec 判别类型完全指南discriminated 判别联合与 discriminator 继承多态TypeSpec 判别类型完全指南discriminated 判别联合与 discriminator 继承多态 TypeSpec 原生支持联合union编程语言编译器后端The Concise TypeScript Book 精读Discriminated Unions 判别联合类型完全指南The Concise TypeScript Book 精读Discriminated Unions 判别联合类型完全指南 判别联合Discriminate文档教程The Concise TypeScript Book 精讲判别联合Discriminated Unions的类型收窄实战The Concise TypeScript Book 精讲判别联合Discriminated Unions的类型收窄实战 本篇为开源仓库 The Con文档教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考