get-shit-done ADR-0002 深度解析:用 lint + 回归测试两层机制集中校验 commands/gsd 命令契约
get-shit-done ADR-0002 深度解析用 lint 回归测试两层机制集中校验 commands/gsd 命令契约【免费下载链接】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 仓库的 ADR-0002决策文档为主线完整讲解该项目如何为commands/gsd/*.md命令文件定义一份契约并通过 快速 lint 脚本、共享解析层 和 行为回归测试 三个源码件在 CI 中强制执行。读完本文你将掌握命令文件契约的五条规则、每个规则背后的实现逻辑含 frontmatter 解析与引用归一化的源码细节、如何在本地运行契约校验以及该决策为系统提示词节省 token 的具体收益。背景为什么需要集中式命令契约校验契约校验分散时的真实风险在 ADR-0002 做出决策之前命令文件的契约校验是零散的没有任何单一测试能够覆盖全部命令文件。根据 ADR 的 Context 一节当时的覆盖情况是tests/enh-2790-skill-consolidation.test.cjs 只检查特定技能整合后命令的存在性与 frontmattertests/bug-3135-capture-backlog-workflow.test.cjs 检查execution_context中-引用的可解析性该检查本身也是 2026-05-05 才补上的没有任何测试会同时检查所有命令的allowed-tools合法性、name:命名约定、description:非空性。这意味着任何触及某个命令文件的 PR 都可能打破契约而没有一个测试能捕获它。ADR 中给出了一个具体案例——add-backlog.md的缺口issue #3135一个 workflow 文件在整个整合周期里一直缺失直到后来专门为它写了定向回归测试才被发现。契约之外的第二个痛点上下文膨胀ADR 还记录了两个与契约直接相关、但属于成本性质的问题冗余的散文式-引用65 个命令文件中有 40 个同时包含两处指向同一路径的-引用——一处写在execution_context块内会被加载可执行另一处写在process正文里纯散文不生效。这给每次调用平白增加了约 900 token 的死重并且制造了一条漂移缝正文里的引用可能独立于可执行的execution_context引用而过期。超大命令内联实现当时最大的两个命令debug.md 与 thread.md没有像其他命令那样把实现委托给 workflow 文件而是把完整实现内联在命令文件里。这导致约 4400 token 的实现细节作为 skills 索引描述的一部分在每个会话中无条件加载——无论用户是否实际使用这两个命令。这两个问题共同构成了 ADR-0002 的动因不仅要守门契约校验还要瘦身让execution_context成为唯一权威声明。决策内容命令契约的五条规则ADR-0002 的核心决策Status: AcceptedDate: 2026-05-05是将commands/gsd/*.md的文件契约集中到单一校验缝validation seam并在两层强制执行——一个快速 lint 脚本 scripts/lint-command-contract.cjs作为测试前的 CI 步骤运行一个行为回归测试 tests/command-contract.test.cjs对照真实文件系统验证完整契约。契约本身定义了什么才算一个合法的commands/gsd/*.md共五条#规则说明1name:字段必须存在、非空且匹配gsd:*或gsd-*ns-开头的命名空间命令使用gsd-前缀2description:字段必须存在且非空3allowed-tools:块必须存在且非空且每一项都来自规范工具集canonical tool set4execution_context中的-引用每一条-引用必须解析到磁盘上真实存在的文件5execution_context中的-引用每条引用必须独占一行引用后不允许跟任何散文这五条规则可以直接在仓库里的真实命令文件上对照验证。例如 commands/gsd/quick.md--- name: gsd:quick description: Execute a quick task with GSD guarantees (atomic commits, state tracking) but skip optional agents argument-hint: [list | status slug | resume slug | --full] [--validate] [--discuss] [--research] [task description] allowed-tools: - Read - Write - Edit - Glob - Grep - Bash - Agent - AskUserQuestion requires: [phase] ---其正文中的execution_context块为execution_context ~/.claude/get-shit-done/workflows/quick.md /execution_context-引用独占一行、无尾随散文且指向的 workflow 文件真实存在——完全符合规则 4 和 5。再看命名约定的另一半ns-前缀文件使用gsd-命名。例如 commands/gsd/ns-workflow.md 的 frontmatter 是name: gsd-workflow而普通命令文件则使用gsd:前缀如name: gsd:debug见 commands/gsd/debug.md。两种写法都被正则/^gsd[:-]/接受。实现层一快速 lint 脚本scripts/lint-command-contract.cjs 是契约的第一道防线。它的运行方式非常直接——在仓库根目录执行node scripts/lint-command-contract.cjs脚本的工作流程从源码看是扫描 commands/gsd/ 目录下的全部.md文件ADR 声明时共 65 个命令对每个文件调用共享层 scripts/command-contract-helpers.cjs 中的parseFrontmatter与executionContextRefs两个解析器依次执行五条检查收集违规项按文件聚合输出诊断信息。其退出码语义在文件头注释中明确约定* Exit 0 clean. Exit 1 violations (with diagnostics).全部通过时输出类似ok lint-command-contract: 65 command files checked, 0 violations有违规时向 stderr 输出总违规数、按文件分组的具体原因例如name: must start with gsd: or gsd-, got ...、execution_context: -ref ... does not exist on disk并附上契约规范的出处——ADR 文档本身See docs/adr/0002-command-contract-validation-module.md for the contract spec.五个检查项在源码中的关键逻辑// 1. name: 存在 gsd: / gsd- 前缀 if (!fm.name || !fm.name.trim()) { violations.push(name: field missing or empty); } else if (!/^gsd[:-]/.test(fm.name.trim())) { violations.push(name: must start with gsd: or gsd-, got ${fm.name.trim()}); } // 3. allowed-tools: 存在 非空 全部来自规范工具集 const valid CANONICAL_TOOLS.has(tool) || (tool.startsWith(mcp__context7__) CANONICAL_TOOLS.has(mcp__context7__*)); if (!valid) violations.push(allowed-tools: unknown tool ${tool}); // 45. execution_context -refs 可解析 无尾随散文 const absPath path.join(GSD_ROOT, normalized); if (!fs.existsSync(absPath)) { /* violation */ } if (trailingProse) { /* violation */ }注意allowed-tools检查里的特例以mcp__context7__开头的工具名可以命中集合中的通配条目mcp__context7__*这是对 Context7 MCP 工具族开放的合法扩展位。共享解析层lint 与测试的单一事实来源ADR 强调两层强制执行必须对什么算合法工具、什么算合法-引用、什么算合法 frontmatter 结构达成一致。这个一致性由 scripts/command-contract-helpers.cjs 保证——它的文件头注释写明自己是被 lint 脚本与测试套件共同引用的单一事实来源Keeping these in one place ensures the lint script and the test suite always agree on what constitutes a valid tool, a valid -ref, and a valid frontmatter structure. A new canonical tool added here is automatically enforced by both consumers.也就是说往规范工具集里新增一个工具只需改这一处两层校验自动同步生效。规范工具集 CANONICAL_TOOLS当前完整取值如下源码 scripts/command-contract-helpers.cjs类别工具名文件操作Read、Write、Edit、Glob、Grep执行与委派Bash、Task、Agent、Skill、SlashCommand交互与网络AskUserQuestion、WebFetch、WebSearch状态TodoWriteMCPContext7 文档库mcp__context7__resolve-library-id、mcp__context7__query-docs、mcp__context7__*通配任何allowed-tools块中出现集合之外的工具名且不属于mcp__context7__通配前缀lint 与测试都会判定违规。frontmatter 解析对 Windows CRLF 的显式容错parseFrontmatter的实现有一个值得注意的工程细节——它对 CRLF 换行做了显式容错// CRLF-tolerant split: Windows checkouts (autocrlftrue) leave a trailing // \r on every line, making lines.indexOf(---, 1) return -1 (the value // would be ---\r, not ---) → returns {} → every field appears missing. const lines content.split(/\r?\n/);注释说明了一个真实故障模式Windows 检出autocrlftrue会在每行末尾留下\r如果按\n朴素切分闭合分隔符的值是---\r而非---查找失败返回空对象所有字段都会被误报为缺失。契约校验器自身不能对换行风格过敏否则 lint 会在 Windows 开发机上全线误报。解析器还支持简单的- item列表续行拼接这正是allowed-tools多行块列表的解析方式。-引用解析与路径归一化executionContextRefs是规则 4、5 的核心。它先用正则找出所有execution_context或execution_context_extended块再逐行提取以开头的引用const re /execution_context(?:_extended)?([\s\S]*?)\/execution_context(?:_extended)?/g;对每个引用源码做了两件事1尾随散文检测对应规则 5const token line.split(/\s/)[0]; const trailingProse line.length token.length;ref取行内第一个空白分隔的 token如果 trim 后整行比 token 长说明同一行还有别的文字即尾随散文违规。这正是 ADR 中引用必须独占一行规则的机器化表达。2路径归一化对应规则 4const normalized token .replace(/^(?:~|\$HOME)\//, ) .replace(/^(?:\.claude\/)?(?:get-shit-done\/)?/, );命令文件里的引用写的是安装后的用户路径例如~/.claude/get-shit-done/workflows/quick.md而校验发生在仓库里workflow 源文件位于仓库的get-shit-done/子树见 get-shit-done/workflows/。归一化逻辑先剥掉~/$HOME家目录前缀再剥掉可选的.claude/与get-shit-done/前缀最后由调用方拼回仓库内的根const GSD_ROOT path.join(ROOT, get-shit-done); // ... const absPath path.join(GSD_ROOT, normalized); if (!fs.existsSync(absPath)) { /* violation */ }这条安装路径 ↔ 仓库路径的双向映射是让契约检查能同时覆盖~/.claude/get-shit-done/...、$HOME/get-shit-done/...等多种书写形式的关键。实现层二行为回归测试tests/command-contract.test.cjs 是契约的第二道防线也是权威行为契约测试。它基于 Node 内置测试运行器node:testnode:assert/strict复用同一个共享层scripts/command-contract-helpers.cjs并针对commands/gsd/下每个文件动态生成五个 describe 块describe(command contract: name field (ADR-0002), () { ... }); describe(command contract: description field (ADR-0002), () { ... }); describe(command contract: allowed-tools (ADR-0002), () { ... }); describe(command contract: execution_context -refs resolve (ADR-0002), () { ... }); describe(command contract: execution_context -refs on own line (ADR-0002), () { ... });每个 describe 内部都遍历全部命令文件逐个断言。与 lint 脚本相比它的价值不在更快而在两点它是测试套件的一部分随npm test入口为 scripts/run-tests.cjs整体运行违规会以单个测试失败的形式出现在 CI 报告里并精确定位到哪个文件的哪条规则它是回归锚点ADR 明确指出该测试取代了enh-2790与bug-3135中零散的契约覆盖成为整个命令面的权威行为契约测试。文件头还有一段有信息量的注释解释了测试源码文本在本仓库中的合法性// allow-test-rule: source-text-is-the-product — commands/gsd/*.md files ARE the // deployed skill surface. Testing their contract tests the runtime behaviour.即commands/gsd/*.md本身就是部署出去的 skill 表面对它们做文本级契约测试等价于测试运行时行为——这为测试 Markdown 而非代码提供了明确的规则豁免依据。决策后果收益清单ADR 的 Consequences 一节记录了该决策落地后的六项结果全部可在当前仓库状态中印证单一 lint 脚本毫秒级全量检查lint-command-contract.cjs对全部 65 个命令做 frontmatter 不变量检查只需毫秒且在 CI 中先于测试套件运行——契约破坏在最快的反馈环上被拦截。测试覆盖收口tests/command-contract.test.cjs取代enh-2790、bug-3135中的零散检查成为整个命令面的权威行为契约测试。删除 40 个命令文件中的冗余散文式-引用每次调用回收约 900 token。debug.md 与 thread.md 重构为 workflow 委托模式从系统提示词的立即加载eager load中移除约 4400 token。当前 commands/gsd/debug.md 已经只剩一个指向 workflow 的-引用~/.claude/get-shit-done/workflows/debug.md加少量编排说明正文实现全部下沉。workflow 命名统一workflows/extract_learnings.md重命名为workflows/extract-learnings.md与其余所有 workflow 文件使用的连字符hyphen约定对齐——这也解释了为何契约校验对引用指向的文件不存在零容忍改名这类操作如果没有同步引用就会立刻被规则 4 捕获。execution_context块成为单一权威声明命令加载什么只由execution_context声明一次正文中不再重复——从制度上消灭了散文引用与可执行引用各自漂移的缝隙。动手验证在本地运行契约校验契约校验对开发者完全透明两个入口都可直接运行入口一独立 lint最快反馈# 仓库根目录 node scripts/lint-command-contract.cjs输出ok lint-command-contract: N command files checked, 0 violations即通过exit 0有违规时 exit 1stderr 按文件列出每条违规的具体原因可直接对照五条规则定位。入口二完整测试套件行为级验证npm test # 或聚焦单元套件 npm run test:unittests/command-contract.test.cjs随套件执行任何一条契约规则在任何命令文件上被打破都会以具名测试失败的形式暴露测试名形如quick.md: all execution_context -refs exist on disk。对命令作者的实际约束可以总结为一张最小 checklistname:以gsd:普通命令或gsd-ns-命名空间命令开头description:非空allowed-tools:块中的每一项都属于规范工具集或mcp__context7__前缀工具execution_context里的每条-引用指向真实文件且独占一行不在process等正文里重复书写已经由execution_context加载的路径。小结ADR-0002 的价值不在于多了一个 lint 脚本而在于它把命令文件长什么样才算对从散落各处的零散断言收敛为一份书面契约 一个共享解析层 两层强制执行的闭环lint 脚本在 CI 前置环节提供毫秒级快速拦截回归测试在测试套件中提供权威的行为级验证而command-contract-helpers.cjs保证两者永不分歧。配合删除冗余-引用与两大命令的 workflow 委托重构这个决策同时解决了契约守门与提示词瘦身约 900 4400 token/会话两个问题。对于任何以 Markdown 文件作为可部署 skill/prompt 表面的系统这套契约文档化 → 解析器单一事实来源 → lint 测试双层执行的模式都具备直接借鉴价值。【免费下载链接】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),仅供参考