首页
/ get-shit-done 查询分发架构升级:Command Topology 单一接缝如何统一命令解析、适配器绑定与无匹配诊断

get-shit-done 查询分发架构升级:Command Topology 单一接缝如何统一命令解析、适配器绑定与无匹配诊断

2026-09-07 15:52:20作者:翟江哲Frasier

本仓库(get-shit-done,下称 GSD)以 .changeset/ 目录下的变更片段(fragment)作为每次用户可见改动的发布说明载体。本文围绕 .changeset/blue-stones-topology.md 这条类型为 Changed 的片段展开,剖析它背后的真实源码改动:GSD SDK 的 query 命令分发被「Command Topology Module」加深重构。读完你将理解:gsd-sdk query 的输入是如何经过一个单一拓扑接缝(topology seam)完成命令 token 解析、原生 handler 适配器绑定与结构化无匹配诊断的,以及这套设计如何提升局部性(locality)并抑制分发接缝漂移(dispatch seam drift)。

一、变更片段到底说了什么

.changeset/blue-stones-topology.md 全文只有一段摘要性质的条目:

---
type: Changed
---

**Query command dispatch deepened with Command Topology Module** — query dispatch
now consumes a single topology seam that resolves command tokens, binds native
handler adapters, and returns structured no-match diagnosis, improving locality
and reducing dispatch seam drift.

逐词拆解这段工程语言,可以得到四个可验证的核心论断:

  1. 单一拓扑接缝(single topology seam):所有「token → 命令 → 处理器」的决策集中到一个名为 CommandTopology 的抽象上,而非散落在分发函数的多处分支中。
  2. 解析命令 token:拓扑的 resolve() 负责把 gsd-sdk query <tokens...> 的原始输入解析成规范命令名与剩余参数。
  3. 绑定原生 handler 适配器:解析成功后,拓扑直接返回可直接执行的原生适配器(adapter),并附带输出模式与是否变更型命令等元数据。
  4. 结构化无匹配诊断:解析失败时返回的是一份携带 normalizedattemptedhintsmessage 的结构化诊断对象,而不是一句干巴巴的报错字符串。

文章后续各节将以源码为准逐一印证并深化这四条,最后落到「locality 提升」与「dispatch seam drift 减少」这两个工程目标上。

二、前置背景:query 分发曾经存在的接缝漂移问题

要理解这次 Changed 的动机,先看改动前的接口形态。在 query-dispatch.ts 中,QueryDispatchDeps 携带了两种相互平行的原生分发方式:

export interface QueryDispatchDeps {
  registry: QueryRegistry;
  projectDir: string;
  ws?: string;
  cjsFallbackEnabled: boolean;
  resolveGsdToolsPath: (projectDir: string) => string;
  /** @deprecated use topology */
  dispatchNative?: (cmd: string, args: string[]) => Promise<QueryResult>;
  /** @deprecated use topology */
  nativeAdapter?: QueryNativeDispatchAdapter;
  topology: CommandTopology;
}

注意两处 /** @deprecated use topology */ 注释——这正是「接缝漂移」的历史痕迹:dispatchNative 回调与 nativeAdapter 对象曾经并存,调用方需要自行决定走哪条路,命令解析逻辑也可能在多处重复实现,任何一处漂移都会导致行为不一致。重构后的方向是:所有路径统一收敛到 topology 一个接缝上,旧的 dispatchNative / nativeAdapter 字段仅作为向后兼容的遗留接口保留。

从整体架构看,query-cli-adapter.ts 是 SDK 查询命令的 CLI 层入口,它演示了新的装配方式:

const registry = createRegistry();
const topology = createCommandTopology(registry);
const out = await runQueryDispatch({
  registry,
  projectDir: runtime.projectDir,
  ws: runtime.ws,
  cjsFallbackEnabled: queryFallbackToCjsEnabled(),
  resolveGsdToolsPath,
  topology,
}, input.queryArgv ?? []);

整个流程只有两个创建点:createRegistry() 提供命令注册表,createCommandTopology(registry) 把注册表包装成拓扑接缝。随后 runQueryDispatch 内部不再关心「怎么把一个命令解析出来」——那是 topology 的职责。

三、Command Topology 模块的公开契约

模块主体位于 sdk/src/query/command-topology.ts。先看类型层(对应「结构化」这一设计目标):

export type CommandTopologyOutputMode = 'json' | 'text' | 'raw';

export interface CommandTopologyMatch {
  kind: 'match';
  canonical: string;        // 规范化后的命令名,如 state.json
  args: string[];           // 剥离命令后的剩余参数
  output_mode: CommandTopologyOutputMode;
  mutation: boolean;        // 是否为变更型命令(写磁盘等副作用)
  adapter: QueryHandler;    // 绑定好的原生 handler
}

export interface CommandTopologyNoMatch {
  kind: 'no_match';
  attempted: string[];      // 实际尝试过的命令形态
  normalized?: string;      // 归一化后的输入
  hints: string[];          // 给用户的提示数组
  message: string;          // 组装好的完整错误消息
}

export type CommandTopologyResult = CommandTopologyMatch | CommandTopologyNoMatch;

export interface CommandTopology {
  resolve(tokens: string[], fallbackRestricted?: boolean): CommandTopologyResult;
}

值得注意的契约要点:

  • 结果用**可辨识联合(discriminated union)**表达,以 kind: 'match' | 'no_match' 区分成功与失败,消费方无需异常机制即可穷尽处理两种结局。
  • CommandTopology 接口只有一个方法 resolve,入参是原始 token 数组 + fallbackRestricted 布尔(用于告知 CJS 兜底是否被禁用,进而在诊断消息中提示用户),出参是联合结果。这一窄接口正是「单一接缝」的最小表面。
  • match 结果的 mutationoutput_mode 字段是把「命令能力元数据」与「命令解析」耦合在同一处返回,供上层在真正执行前做策略判断(见第五节的安全护栏)。

从文件行文看,resolve 的空命令处理、命令解析、handler 缺失三种情况都会返回 no_match,其中命令解析失败与 handler 缺失都复用同一个 diagnoseUnknownCommand 诊断器,保证错误口径统一。

四、深度解析:resolve 内部的三步流水线

createCommandTopology(registry) 返回的 resolve 实现(sdk/src/query/command-topology.ts)可以拆成清晰的四段处理逻辑:

4.1 空命令守卫

const command = tokens[0];
const args = tokens.slice(1);
if (!command) {
  return {
    kind: 'no_match',
    attempted: [],
    hints: [],
    message: 'Error: "gsd-sdk query" requires a command',
  };
}

首 token 缺失直接短路,给出与上层校验完全一致的提示文案(与 query-dispatch.tsvalidateQueryDispatchInputmissing_command 错误消息呼应),避免空输入继续走昂贵的注册表查询。

4.2 命令 token 解析与前缀匹配

核心解析委托给 query-command-resolution-strategy.tsresolveQueryCommand 先做 normalizeQueryCommand 归一化,再进入 resolveQueryTokens

export function resolveQueryCommand(command: string, args: string[], registry: QueryCommandRegistryLike): QueryCommandResolution | null {
  const [normCmd, normArgs] = normalizeQueryCommand(command, args);
  return resolveQueryTokens([normCmd, ...normArgs], registry);
}

归一化规则(sdk/src/query/query-command-resolution-strategy.ts)处理了大量「口语化子命令」到「点分规范命令」的映射,例如:

  • scaffoldphase.scaffold
  • 裸的 state(无参数)→ state.load
  • state <sub>verify <sub>init <sub>phase <sub>phases <sub>validate <sub>roadmap <sub> 各自查对应的 STATE_SUBCOMMANDS / VERIFY_SUBCOMMANDS / INIT_SUBCOMMANDS / PHASE_SUBCOMMANDS / PHASES_SUBCOMMANDS / VALIDATE_SUBCOMMANDS / ROADMAP_SUBCOMMANDS 集合(这些集合由 sdk/src/query/command-aliases.generated.js 生成);
  • 一组 MERGE_FIRST_WITH_SUBCOMMAND 命令(statetemplatefrontmattermilestoneworkstreaminteluattodocheck 等)在后续参数非 - 开头时合并为点分形式,如 milestone completemilestone.complete

真正的前缀匹配在 matchRegisteredPrefixsdk/src/query/query-command-resolution-strategy.ts):从最长前缀开始,逐段把 token 组拼成 dotted(点分)与 spaced(空格分)两种形态并查注册表,命中即返回命令与剩余参数;若直接匹配失败,resolveQueryTokens 还会尝试「展开首个点分 token」(state.jsonstate json)后再匹配,从而同时兼容 state jsonstate.json 两种书写习惯。解析结果的 matchedBydotted / spaced)与 expanded 字段保留了解析来源,供诊断与调试回溯。

4.3 适配器绑定与命令能力标注

解析命中后(sdk/src/query/command-topology.ts):

const adapter = registry.getHandler(matched.cmd);
if (!adapter) {
  // 走 diagnoseUnknownCommand,与解析失败同口径
}
return {
  kind: 'match',
  canonical: matched.cmd,
  args: matched.args,
  output_mode: supportsRawOutputCommand(matched.cmd) ? 'raw' : 'json',
  mutation: supportsMutationCommand(matched.cmd),
  adapter,
};

这一步完成了「命令名 → 可执行函数」的绑定(registry.getHandler(matched.cmd)),并顺带通过 supportsRawOutputCommand / supportsMutationCommand(位于 sdk/src/query/query-policy-capability.ts)打上能力标签:默认输出为 json,若命令声明支持 raw 输出则切换为 rawadapter 的类型签名(见 sdk/src/query/utils.tsQueryHandler)说明它需要接收 (args, projectDir, ws),由上层统一注入项目上下文。

4.4 结构化无匹配诊断

当解析失败或 handler 缺失时,diagnoseUnknownCommandsdk/src/query/command-topology.ts)组装结构化诊断:

const noMatch = explainQueryCommandNoMatch(command, args, registry);
const normalized = [noMatch.normalized.command, ...noMatch.normalized.args].join(' ');
const attempted = noMatch.attempted.dotted.slice(0, 2);
const hints = [...UNKNOWN_COMMAND_HINTS];
const attemptedSuffix = attempted.length > 0 ? ` Attempted dotted: ${attempted.join(' | ')}.` : '';
const fallbackClause = fallbackRestricted ? `${describeFallbackDisabledPolicy()} ` : '';
const message = `Error: Unknown command: "${normalized}". ${hints[0]} ${hints[1]} ${fallbackClause}${hints[2]}${attemptedSuffix}`;

诊断数据的来源是 query-command-resolution-strategy.tsexplainQueryCommandNoMatch——它复用同一条匹配函数,把「实际上尝试过哪些 dotted / spaced 前缀」记录进 attempted 对象,真正做到「报错与解析共用一套逻辑,永不同步丢失」。

最终诊断消息的组成是模板化、可组合的:

  • hints 来自独立的 sdk/src/query/query-unknown-command-hints.tsUNKNOWN_COMMAND_HINTS),是可维护的提示文案常量;
  • fallbackRestricted 为真(即 CJS 兜底被关闭),会追加 describeFallbackDisabledPolicy()sdk/src/query/query-fallback-policy.ts)产出的策略说明;
  • 尝试过的点分前缀会以 Attempted dotted: a | b 的形式显式列出,帮助用户在输入错误时快速推理出正确命令形态。

五、上层编排:runQueryDispatch 如何消费这个接缝

拓扑接缝的真正消费者是 query-dispatch.tsplanQueryDispatch,它把分发决策收敛为一个显式的 DispatchPlan

const resolved = topology.resolve(queryArgv, !cjsFallbackEnabled);

if (resolved.kind === 'match') {
  return { mode: 'native', normalized: { ... }, matched: resolved };
}
if (cjsFallbackEnabled) {
  return { mode: 'cjs', normalized: { ... }, matched: null };
}
return {
  mode: 'error',
  normalized: { ... },
  matched: null,
  noMatchMessage: resolved.message,
  noMatchNormalized: resolved.normalized,
  noMatchAttempted: resolved.attempted,
  noMatchHints: resolved.hints,
};

这个三态机(DispatchMode = 'native' | 'cjs' | 'error')揭示了完整的分发优先级:

  1. topology 命中原生命令native 模式,直接使用 matched 中绑定的适配器或 dispatchNative / nativeAdapter 执行;
  2. topology 未命中但 CJS 兜底可用(由环境变量 GSD_QUERY_FALLBACK 控制,见 query-cli-adapter.ts,设为 off/never/false/0 即关闭)→ cjs 模式,回退到传统 CLI 调用;
  3. 两者都不可用error 模式,把拓扑产出的 messagenormalizedattemptedhints 原样透传给错误分类器 unknownCommandError,保证用户看到的诊断与拓扑内部分析完全一致。

runQueryDispatchsdk/src/query/query-dispatch.ts)在 native 分支执行前还有一个值得单独说明的防误写护栏(源码注释标记 #3259):当调用携带 --help/-h 且命中命令被拓扑标注为 mutation: true(变更型命令)时,会短路返回一个非变更的 usage 帮助 stub,而不是把 --help 透传给变更型 handler。注释明确指出,这防止了例如 milestone.complete --help 意外把里程碑产物写到磁盘。这正是第四节「能力标注」字段的实战价值——mutation 元数据在真正执行前启用了 fail-closed 的安全策略。

执行阶段的最终调用有两路(sdk/src/query/query-dispatch.ts):优先使用 nativeAdapter.dispatch(旧的接缝遗留形态),否则直接调用拓扑绑定的 matched.adapter(matched.args, deps.projectDir, deps.ws)。返回数据再经 formatSuccessjson/text 两种格式化与 --pick 字段提取)统一序列化为 CLI 输出。

六、测试如何锁定契约

模块级测试位于 sdk/src/query/command-topology.test.ts,用两条用例直接钉住上文所述契约:

it('resolves native command with adapter', () => {
  const registry = createRegistry();
  const topology = createCommandTopology(registry);
  const out = topology.resolve(['state', 'json']);
  expect(out.kind).toBe('match');
  expect(out.canonical).toBe('state.json');   // 归一化:state json → state.json
  expect(out.args).toEqual([]);
  expect(typeof out.adapter).toBe('function'); // 适配器已绑定
});

it('returns no_match with diagnosis', () => {
  const topology = createCommandTopology(createRegistry());
  const out = topology.resolve(['unknown-cmd'], true);
  expect(out.kind).toBe('no_match');
  expect(out.message).toContain('Unknown command');
  expect(out.attempted.length).toBeGreaterThanOrEqual(0);
});

第一条用例验证「token 解析 + 适配器绑定」双职责(state json 被归一化为点分命令 state.json 且拿到可调用的 adapter);第二条验证 fallbackRestricted = true 时未知命令仍返回结构化 no_match 与诊断消息。此外,sdk/src/query/query-dispatch.test.ts 覆盖了从 runQueryDispatch 到 CLI 输出的端到端行为,确保拓扑的结果在计划阶段(planQueryDispatch)与执行阶段(runQueryDispatch)间稳定传递。

七、这套改造如何服务「locality」与「seam drift」两个目标

回到变更片段摘要里的两个价值主张,结合源码可以得出清晰结论:

提升局部性(improving locality)。命令解析、能力标注、错误诊断、适配器绑定这些原本可能散落在分发函数、CLI 适配器与错误分类器中的关注点,现在全部内聚到 sdk/src/query/command-topology.ts 及它直接依赖的解析策略文件中。新增一个查询命令时,开发者只需要关心注册表(registry)与能力声明文件,分发管线无需改动——这是典型的高内聚低耦合重构。

减少分发接缝漂移(reducing dispatch seam drift)。改动前 dispatchNativenativeAdapter 两个平行的「接缝」并存且都被标记为 @deprecated,容易造成调用方对路径选择的分叉。引入 CommandTopology 后,planQueryDispatch 的三种模式(native / cjs / error)共享同一次 topology.resolve 结果,诊断消息与解析逻辑复用同一匹配函数(explainQueryCommandNoMatchresolveQueryCommand 底层共用 matchRegisteredPrefix),从结构上杜绝了解析口径与报错口径不一致的漂移。

从变更管理角度,这条 Changed 级别的片段遵循仓库的 .changeset/README.md 约定:每个 PR 向 .changeset/ 投放一个随机命名的 fragment,发布时由 scripts/changeset/cli.cjsrender 命令按 typeAdded / Changed / Deprecated / Removed / Fixed / Security,见 scripts/changeset/parse.cjs)分组合并进 CHANGELOG.md。因此 blue-stones-topology 这条片段不仅是本模块的权威变更记录,也是读者追踪该重构从「发布说明」回溯到「实现源码」的最佳入口。

八、如何在当前仓库中复现与验证

若想亲身体验上述行为,可在本仓库 sdk/ 目录下运行模块测试:

# 在 sdk/ 子项目内执行(依赖该目录下已配置的 vitest)
npm test -- query-dispatch command-topology

其中 sdk/src/query/command-topology.test.ts 会直接验证 resolve 的 match/no_match 契约,sdk/src/query/query-dispatch.test.ts 验证完整分发链路。阅读代码时建议按以下顺序建立心智模型:

  1. sdk/src/query/command-topology.ts — 拓扑接缝的类型与实现(本改动的核心交付物);
  2. sdk/src/query/query-command-resolution-strategy.ts — 归一化、前缀匹配、no-match 轨迹采集;
  3. sdk/src/query/query-dispatch.ts — 三态分发计划与执行编排;
  4. sdk/src/query/query-cli-adapter.ts — CLI 入口处的装配示范;
  5. sdk/src/query/query-dispatch.test.tssdk/src/query/command-topology.test.ts — 行为契约的回归守护。

小结

.changeset/blue-stones-topology.md 用一句话概括了一次并不简单的架构收敛:gsd-sdk query 的分发从「多接缝并存、解析与报错各写一套」演化为「单一 Command Topology 接缝」。在该接缝内,token 解析遵循一套可追溯的归一化与前后缀匹配策略,成功路径返回已绑定 adapter 并标注 output_modemutation 能力的结果,失败路径返回携带 normalized / attempted / hints / message 的结构化诊断。理解这条变更,等于同时掌握了这个仓库查询子系统的核心架构与它的变更管理方法——这也是开发者在类似规模项目中降低接缝漂移、提升模块局部性时可以借鉴的工程样本。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
900
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
927
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.94 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
603
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
396
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
527