get-shit-done 技能安装中的命名空间归一化:拆解 SKILL.md 内 `/gsd:<cmd>` 残留引用修复(3583)
2026/9/8 22:29:07 网站建设 项目流程

get-shit-done 技能安装中的命名空间归一化:拆解 SKILL.md 内/gsd:<cmd>残留引用修复(#3583)

【免费下载链接】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

本篇技术文章围绕 get-shit-done(GSD,一个面向 Claude Code 的轻量级 meta-prompting / 上下文工程 / 规范驱动开发系统)的一则 bugfix changeset(.changeset/graceful-tigers-fly.md)展开:当仓库把 Claude 命令(command)安装成 Claude / Qwen / Hermes 的 SKILL.md 技能时,命令正文中遗留的旧式/gsd:<cmd>冒号引用如何被统一改写为规范化的gsd-<cmd>连字符形式。读完本文,你将理解 GSD 命令命名空间“存储方言”与“运行时方言”的差异、共享转换器fix-slash-commands.cjs的正则安全设计、安装器convertClaudeCommandToClaudeSkill的调用链,以及对应回归测试的防护策略,可直接迁移到任何需要跨运行时做“文本规范化”的 Agent 工具安装管线中。

一个 changeset 背后的问题:正文里的旧引用

graceful-tigers-fly.md以极简的 frontmatter(type: Fixedpr: 3583)记录了一次行为修复,核心陈述如下:

Claude skill install(convertClaudeCommandToClaudeSkill+copyCommandsAsClaudeSkills)现在会使用共享转换器fix-slash-commands.cjs中新增的transformContentToHyphen,把 SKILL.md 正文中退役的/gsd:<cmd>引用归一化为规范的gsd-<cmd>连字符形式。

这看起来只是一行改动,但它实际串联起了 GSD 系统内三个层面的问题:历史命名迁移安装期内容转换运行时命名空间一致性。下面分别从仓库源码逐层拆解。

问题根源:仓库里存在两种“命令引用方言”

方言一:冒号形式/gsd:<cmd>(仓库存储的规范形态)

GSD 的每个命令本体位于 commands/gsd/(如plan-phase.mdreview.md)。阅读 scripts/fix-slash-commands.cjs 的头部注释可以发现,仓库内部源码、文档与 workflow 的正文统一使用冒号形式/gsd:<cmd>)作为书写规范:

  • 默认方向的transformContent:把退役的/gsd-<cmd>/gsd:<cmd>,目的是“keep monorepo sources, docs, and workflows in the active colon form”(维持 monorepo 的活跃冒号形态);
  • 仅当把内容安装到特定运行时时,才走反向的transformContentToHyphen

也就是说,命令正文里如果提到兄弟命令(例如“运行/gsd:plan-phase后再执行/gsd:review”),写的是冒号形式。

方言二:连字符形式gsd-<cmd>(运行时注册的规范形态)

Claude Code(以及 Qwen、Hermes 的对应实现)以目录名 / frontmattername:注册技能。根据 tests/bug-2808-skill-hyphen-name.test.cjs 头部注释记录的完整历史:

  1. #2643 时代bin/install.js中的skillFrontmatterName()曾把连字符目录名(gsd-add-phase)转回冒号(gsd:add-phase),因为当时 workflow 里用Skill(skill="gsd:<cmd>")冒号形式调用;
  2. #2808 之后:所有 workflow 改为连字符形式调用,skillFrontmatterName()也改为直接返回连字符形式(见 bin/install.js 及其注释 “hyphen form as-is (gsd- ) — canonical since #2808”);
  3. #3583 的漏网点:frontmatter 的name:虽然在 #2808 已修正,但SKILL.md 的正文仍然原样照搬命令源文件的冒号式引用,导致安装出的技能在“以连字符注册技能”的运行时中出现失效链接——这正是 changeset 所说的 “body leakage is now eliminated”(正文泄漏被消除)。

一个值得注意的例外是 Codex:代码注释明确说明 “Codex must NOT use this helper”,因为 Codex 适配器以$gsd-<cmd>(shell 变量语法)调用技能,连字符形式天然正确。

核心修复:共享转换器 fix-slash-commands.cjs

修复的落点是 scripts/fix-slash-commands.cjs。它同时是一个“一键批处理脚本”和“可被安装器、测试复用的纯函数库”,实现双向的 GSD 斜杠命令命名空间归一化。

反向转换:transformContentToHyphen

安装期调用的是反向方向(fix-slash-commands.cjs):

function transformContentToHyphen(src, cmdNames) { const pattern = buildColonPattern(cmdNames); if (!pattern) return src; return src.replace(pattern, (_, cmd) => `gsd-${cmd}`); }

它把/gsd:<cmd>与不带斜杠的gsd:<cmd>一律改写为gsd-<cmd>

正则的安全设计:最长优先 + 双向词边界

buildColonPattern(fix-slash-commands.cjs)体现了这个转换器最值得借鉴的部分:

function buildColonPattern(cmdNames) { if (!Array.isArray(cmdNames) || cmdNames.length === 0) return null; const sorted = [...cmdNames].sort((a, b) => b.length - a.length); return new RegExp(`(?<![a-zA-Z0-9_-])gsd:(${sorted.join('|')})(?=[^a-zA-Z0-9_-]|$)`, 'g'); }
  • 最长优先排序:先匹配较长的命令名,避免plan-phase尚未匹配、plan先命中的“部分匹配”误伤;
  • 左侧负向后顾(?<![a-zA-Z0-9_-]):防止mygsd:plan-phaseprefix-gsd:review这类“嵌在大 token 内部”的伪引用被改写;
  • 右侧前瞻(?=[^a-zA-Z0-9_-]|$):防止把gsd:plan-phase-extra这类带后缀的词误截断;
  • 空列表短路:当命令注册表为空时直接返回null,调用方整体 no-op,绝不执行一次“宽泛的意外重写”。

回归测试 bug-2808-skill-hyphen-name.test.cjs 中专门有三条用例覆盖这些边界:gsd:plan-phase-extra不得被改写、mygsd:plan-phase不得被改写、混合输入中仅冒号形式被转换。

命令白名单与“非命令豁免”

命令名集合并非硬编码,而是运行时从 commands/gsd/ 目录实时读取(readCmdNames(),扫描*.md并去掉后缀)。两个方向都只改写已知命令,非命令标识符(如gsd-sdkgsd-tools)被刻意保留不动,这是前向转换器就已确立的安全契约(见 fix-slash-commands.cjs 注释)。

安装器调用链:convertClaudeCommandToClaudeSkill 里发生了什么

转换器真正的消费方在安装器 bin/install.js 中。

一次性预计算命令名

bin/install.js模块顶层require转换器(bin/install.js),并在注释中说明原因:每个技能都去fs.readdirSync加编译正则太浪费,模块加载时计算一次即可传给所有技能转换——这是对大规模技能安装的典型性能优化。

技能转换主函数

convertClaudeCommandToClaudeSkill(bin/install.js)的核心流程为:

  1. extractFrontmatterAndBody拆分源命令的 frontmatter 与正文;无 frontmatter 则原样返回;
  2. 取命令名列表(调用方未传cmdNames时回退到readGsdCommandNames());
  3. 正文执行transformContentToHyphen(body, names),得到normalizedBody
  4. 重建技能 frontmatter:name: gsd-<cmd>(连字符规范形态)、descriptionyamlQuote引号包裹(防止[BETA]…这类 YAML flow 指示符破坏解析,#2876 教训)、保留argument-hintagentallowed-tools保持 YAML 多行列表;
  5. 对 Hermes 额外写入version(其 SKILL.md 规范要求必填,用于skill_view()报告稳定标识)。

代码中#3583的注释点明了设计意图:让“安装出的 SKILL.md 正文”与“#2808 之后按连字符注册的name:”保持一致,即正文引用的命令也必须是同一命名空间下的连字符形式

哪些运行时适用:显式白名单而非黑名单

bin/install.js定义了一个针对 Agent 正文处理的运行时集合(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); } function normalizeAgentBodyForRuntime(content, runtime, cmdNames) { if (!shouldNormalizeHyphenNamespaceInAgentBody(runtime)) return content; return transformContentToHyphen(content, cmdNames); }

设计者选择显式允许列表而非拒绝列表,注释给出的理由值得记录:“better to leak than to mangle a runtime whose namespace behavior we haven't verified”——对于未知或未来的运行时,宁可保留原样,也不要在未经证实其命名空间行为的情况下贸然改写。这也解释了为什么 changeset 只声称覆盖 Claude、Qwen、Hermes 三个运行时。对应的迁移验证可参考 tests/claude-skills-migration.test.cjs、tests/qwen-skills-migration.test.cjs 与 tests/hermes-skills-migration.test.cjs。

回归护栏:bug-2808 测试如何守住这条不变量

changeset 明确提到 “Added regression guard in bug-2808 test”。tests/bug-2808-skill-hyphen-name.test.cjs 在原有的“#2808 技能名必须是连字符”断言之外,新增了正文层面的检查:

  • 生成产物断言:调用convertClaudeCommandToClaudeSkill得到 SKILL.md 后,扫描其正文中形如\bgsd:[a-z][a-z0-9-]*\b的残留,并显式放行gsd:sdkgsd:tools(它们本就不是斜杠命令),其余任何冒号命令引用都会让测试失败;
  • 源文件断言:扫描 get-shit-done/workflows 全部.md,确保不存在Skill(skill="gsd:<cmd>")冒号形式调用——正则Skill\(\s*skill\s*=\s*\\?['"]gsd:([^'"\s)]+)\\?['"]甚至覆盖了空格与转义引号的变体;
  • 转换器单元测试:直接对transformContentToHyphen验证正向改写、右侧边界(gsd:plan-phase-extra不误伤)、左侧边界(mygsd:plan-phase不误伤)与混合输入行为。

这套“生成产物 + 源内容 + 纯函数”三层测试,恰好分别守护了安装产物、仓库存储方言与转换器自身的正确性。

边界与适用前提

最后明确这次归一化的生效范围,避免误用:

  1. 仅作用于已识别命令:白名单来自 commands/gsd/ 下的*.md文件名;不在列表中的标识符一律不改;
  2. 安装期生效、存储期不变:monorepo 内命令源文件与文档仍保留冒号方言(由前向transformContent维护),只有安装到 Claude / Qwen / Hermes 的产物被改写为连字符——从源码结构可以推断,这是一套“存储规范与运行时规范解耦”的双方言策略;
  3. 测试文件豁免:批处理模式会跳过*.test.*文件,因为其中的夹具字符串本就是测试语义的一部分,改写会破坏断言;
  4. 未验证运行时默认不动HYPHEN_NAME_AGENT_RUNTIMES之外的运行时不触发正文归一化。

小结

graceful-tigers-fly.md记录的并非一次孤立的字符串替换,而是一整套可复用的工程实践:为“同一内容在不同运行时拥有不同规范形态”的问题提供双向纯函数转换器,用最长优先匹配与双向词边界保证重写安全,用显式运行时白名单控制影响面,再用三级回归测试守住产物与源码的双向一致。对于任何需要把 Markdown 类“命令/技能”产物安装进多种 AI 运行时(Claude Code、Qwen、Hermes、Codex 等)的工程,这套“共享转换器 + 安装期接入 + 边界测试”的模式都可以直接套用。

【免费下载链接】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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询