首页
/ get-shit-done 多运行时安装命名空间归一化:彻底修复 Agent 正文中的 `/gsd:<cmd>` 冒号引用泄漏(3677)

get-shit-done 多运行时安装命名空间归一化:彻底修复 Agent 正文中的 `/gsd:<cmd>` 冒号引用泄漏(3677)

2026-09-07 11:20:52作者:宣海椒Queenly

get-shit-done(GSD)是一个面向 Claude Code、OpenCode、Gemini、Codex 等多种 AI 编程助手的 meta-prompting、上下文工程与规范驱动开发系统。其安装器 bin/install.js 需要把仓库里以"冒号形式"(/gsd:<cmd>)书写的斜杠命令引用,转换成各运行时实际可路由的形态。本篇文章围绕修复条目 .changeset/fix-3677-agent-colon-namespace-leak.mdtype: Fixed,PR #3680),完整解析该缺陷的成因、运行时分类策略、纯函数修复设计、底层转换器的实现细节与配套回归测试,帮助你理解并复现这套"多运行时正文归一化"机制。

背景:一套仓库源码、多套运行时命名约定的冲突

GSD 安装器负责把单份上游仓库,展开为面向十余种运行时的本地产物。不同运行时对"斜杠命令 / Skill / Agent 名称"的注册方式并不一致,这直接导致仓库源码中的正文引用与最终安装产物之间存在命名形态差异:

  • 仓库中的 Agent、Workflow、文档正文统一使用冒号形式书写,例如 agents/gsd-code-reviewer.md 中描述 "Spawned by /gsd:code-review",agents/gsd-code-fixer.md 中写有 "Spawned by /gsd:code-review --fix workflow";
  • 但自 #2808 起,以 name: 字段注册 Agent/Skill 的运行时(Claude Code、Qwen、Hermes)使用连字符形式 gsd-<cmd> 作为规范名称,使自动补全展示与 Skill(skill="gsd-<cmd>") 调用保持一致;
  • 连字符/冒号双向转换器集中在 scripts/fix-slash-commands.cjs,其文件头注释说明了方向约定:默认方向把废弃的 /gsd-<cmd> 还原为仓库正文使用的冒号形式;反向方向 transformContentToHyphen 在安装期把 /gsd:<cmd>/gsd:<cmd> 改写成 gsd-<cmd>

当正文中遗留的 /gsd:<cmd> 冒号引用被安装进一个只注册了 gsd-<cmd>(连字符)名称的运行时后,这些引用将指向不存在的斜杠命令——这就是命名空间泄漏的本质:文本看起来有效,运行时却无法路由

问题全貌:三个"泄漏面"都需要覆盖

GSD 的多运行时展开涉及三类正文载体,它们在安装管线中各自存在独立的转换路径,因此在 #3677 之前,冒号引用的泄漏需要分三个表面分别修复:

  1. SKILL.md 正文:由 #3583 提出、#3629 落地,在 convertClaudeCommandToClaudeSkill 内对 Skill 正文调用 transformContentToHyphen(见 bin/install.js),使安装后的 SKILL.md 正文与连字符 name: 匹配;
  2. 运行时发射(runtime emissions):由 #3584 提出、#3606 落地,覆盖运行时自身生成的引用文本;
  3. Agent 正文(本次 #3677 修复的目标):安装器把 agents/gsd-*.md 逐个拷贝为各运行时的 Agent 定义,此前缺少与上述两者一致的归一化步骤。

正如该 changeset 所述,三个修复共同构成"三面覆盖"(three-surface coverage),缺一不可。

根因拆解:为什么只有 Claude / Qwen / Hermes 会中招

要理解修复为什么采用"显式白名单",需要先弄清各运行时在安装管线中的不同行为。从 bin/install.js 顶部对 HYPHEN_NAME_AGENT_RUNTIMES 的解释看,运行时被划分为三类:

类别 运行时 行为
连字符 name: + 原样拷贝正文 claudeqwenhermes 注册 gsd-<cmd> 名称,但正文几乎原样拷贝:Claude 不做任何命名空间转换,Qwen/Hermes 只做品牌词替换(把 CLAUDE.mdQWEN.mdClaude CodeQwen Code.claude/.qwen/ 等),冒号引用因此泄漏
自我转换运行时 codexcopilotantigravitycursorwindsurfaugmenttraecodebuddyclineopencodekilo 各自的 convertClaudeAgentToXAgent() 转换器已经自行处理命名空间问题,归一化层不应重复改写
刻意使用冒号 gemini 有意的冒号命名空间,必须原样保留

代理安装循环中的实际代码印证了这一点:Qwen/Hermes 分支只做品牌替换(bin/install.js),随后立即进入 #3677 的归一化步骤,最后才写盘(bin/install.js)。

修复设计:一个纯函数谓词 + 一个门控辅助函数

#3677 的核心接缝由两个从 bin/install.js 导出的函数构成,二者都是纯函数,不依赖文件系统状态,因此可以被测试直接加载、无需真跑安装流程。

谓词:该运行时是否需要连字符归一化

const HYPHEN_NAME_AGENT_RUNTIMES = new Set(['claude', 'qwen', 'hermes']);

function shouldNormalizeHyphenNamespaceInAgentBody(runtime) {
  if (typeof runtime !== 'string' || runtime === '') return false;
  return HYPHEN_NAME_AGENT_RUNTIMES.has(runtime);
}

代码注释明确说明这里选择显式白名单而非黑名单:对未知/未来的运行时默认"不重写"——理由是宁可让旧引用泄漏,也不能把一个命名空间行为尚未验证的运行时正文改坏(better to leak than to mangle)。这体现了命名空间转换类逻辑必须"保守默认"的设计原则。

门控辅助函数:命中白名单才委托给底层转换器

function normalizeAgentBodyForRuntime(content, runtime, cmdNames) {
  if (!shouldNormalizeHyphenNamespaceInAgentBody(runtime)) return content;
  return transformContentToHyphen(content, cmdNames);
}

cmdNames 由调用方通过模块顶部的 readGsdCommandNames() 预读一次(bin/install.js),避免每个 Agent 重复执行 readdirSync 与正则构造。

底层转换器:脚本即库的双向归一化器

被委托的 transformContentToHyphen 位于 scripts/fix-slash-commands.cjs,这个文件既是可单独运行的修复脚本,也是安装器复用的库。它通过若干安全设计确保转换不会误伤正文:

  • 命令名册来自真实文件readCmdNames() 读取 commands/gsd/ 目录下的 .md 文件名作为已知命令集合(scripts/fix-slash-commands.cjs);
  • 最长优先匹配buildColonPattern 将命令按长度降序排序后拼进正则(scripts/fix-slash-commands.cjs),避免 plan-phase 抢先吃掉 plan-phase-x 之类的长命令;
  • 双向词边界保护:左侧使用负向环视 (?<![a-zA-Z0-9_-]),右侧使用 (?=[^a-zA-Z0-9_-]|$),保证 mygsd:foo 不被命中、/gsd:plan-phase-extra 不会误判为 plan-phase
  • 只改写已知命令gsd-sdkgsd-tools 这类 CLI 二进制标识符不属于斜杠命令名册,一律保持原样,防止把可执行文件引用改坏;
  • 空名册短路:当命令列表为空或非法时返回 null,调用方直接 no-op,避免退化成 (?=) 的宽泛匹配导致意外改写(scripts/fix-slash-commands.cjs)。

匹配结果同时支持 /gsd:execute-phase 与不带前导斜杠的裸写 gsd:execute-phase(后者同样会被重写为 gsd-execute-phase),因为冒号并不是正则匹配的一部分。

挂接点:为什么放在"所有运行时转换之后"

#3677 归一化调用在 Agent 安装循环中的位置经过精心设计——位于所有运行时专属转换完成之后、writeFileSync 之前bin/install.js)。以 Qwen/Hermes 为例,安装流程对每个 agents/gsd-*.md 依次执行:

  1. 读取源文件并替换 ~/.claude/ 等硬编码路径;
  2. 写入安装归属说明(attribution);
  3. 执行运行时专属转换:OpenCode/Kilo 改 frontmatter,Gemini 走 convertClaudeToGeminiAgent,Codex/Copilot/Cursor 等走各自的 agent 转换器,Qwen/Hermes 做品牌词替换;
  4. 调用 normalizeAgentBodyForRuntimebin/install.js);
  5. 写盘并进入 verifyInstalled 校验。

放在最后一步之后的意义在于:先让各运行时的转换器完成自己的改写,再由这一层只对"未处理命名空间"的运行时兜底,避免与自我转换运行时产生双重改写。与之对应的,在通用负载拷贝函数 copyWithPathReplacement 中,同样存在对 .md 正文的统一归一化调用(源注释标注为 #3683),从而让 SKILL/Workflow 等负载与 Agent 正文保持同一套行为。

回归测试解剖:五组测试锁定行为契约

对应的回归测试位于 tests/bug-3677-agent-colon-namespace-leak.test.cjs,共分五组,覆盖从"函数存在性"到"真实源码效果"的完整契约:

  • A 组(导出接缝):断言 shouldNormalizeHyphenNamespaceInAgentBodynormalizeAgentBodyForRuntimebin/install.js 导出,保证测试能直接命中安装器内部逻辑;
  • B 组(谓词分桶):对白名单运行时 claude/qwen/hermes 断言返回 true,对 11 个自我转换运行时与 gemini 断言返回 false,并单独断言未知运行时 bogus-runtime-id 默认返回 false(保守策略)。测试注释同时给出约束:任何出现在 runtime-artifact-layout.cjs 的运行时必须恰好落入两个集合之一;
  • C 组(门控行为):用一段含 /gsd:execute-phase/gsd:verify-workgsd-sdk query commit 的样例正文,验证 claude/qwen/hermes 都被改写为连字符形式、gemini 的冒号被保留、copilot 在本层返回原样;
  • D 组(底层转换器健全性):确认只改写已注册命令,未知命令与 gsd-sdk 保持不动;
  • E 组(真实源码效能 + 幂等性):从 PR #3681 移植而来(源码注明感谢 johnzilla),逐个对 agents/gsd-*.md 真实文件执行转换并断言无任何冒号残留;同时验证幂等性——对已经是连字符形式的输入重复执行转换是 no-op,这一点对反复重装(re-install 会再次运行转换)至关重要,防止双重改写破坏正文。

给使用者的实际影响与验证方式

对最终用户而言,本修复的意义在于:完成一次 gsd update 全量重装后,Claude Code / Qwen / Hermes 安装目录下 Agent 正文中的 /gsd:<cmd> 都会被规范化为可路由的 /gsd-<cmd> 形式;而 Gemini 用户与 11 个自我转换运行时的安装产物则与修复前完全一致,不会出现任何意外的正文变化。

如果需要在当前仓库验证该修复的行为契约,可直接运行上述 tests/bug-3677-agent-colon-namespace-leak.test.cjs(基于 Node 内置 node:test,测试文件自身会设置 GSD_TEST_MODE=1 以跳过安装器主流程)。结合 scripts/fix-slash-commands.cjs 的通读,你可以把"命名空间形态变更"这类跨运行时风险,沉淀为"白名单谓词 + 纯函数门控 + 词边界正则 + 幂等性测试"这一套可复用的修复范式,应用到自己的多目标产物安装器设计中。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
924
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
599
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
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
394