首页
/ 在 spec-kit 中用 Preset 覆盖扩展命令:以 speckit.myext.myextcmd 为例的 Agent 命令定制指南

在 spec-kit 中用 Preset 覆盖扩展命令:以 speckit.myext.myextcmd 为例的 Agent 命令定制指南

2026-09-06 18:37:22作者:房伟宁

spec-kit 的 Preset(预设)系统允许开发者以"可堆叠、按优先级"的方式覆盖 Spec-Driven Development 工作流中的模板与命令。本文以官方脚手架 presets/scaffold/commands/speckit.myext.myextcmd.md 为骨架,讲解一个 Preset 如何覆盖扩展(extension)提供的命令、在何时生效、如何被注册进各 Agent 目录,并给出可复制、可验证的完整配置方法。读完本文,你将掌握命令覆盖文件的 YAML 头与正文结构、preset.yml 中的对应声明方式、覆盖背后的解析与注册机制,以及一套完整的本地开发与验证命令流。

一、覆盖文件本体:一个"扩展命令覆盖"长什么样

先看关联文档的完整内容(这是一个教学用 scaffold 示例,演示"myext 扩展的 myextcmd 命令"如何被 Preset 定制化):

---
description: "Override of the myext extension's myextcmd command"
---

<!-- Preset override for speckit.myext.myextcmd -->

You are following a customized version of the myext extension's myextcmd command.

When executing this command:

1. Read the user's input from $ARGUMENTS
2. Follow the standard myextcmd workflow
3. Additionally, apply the following customizations from this preset:
   - Add compliance checks before proceeding
   - Include audit trail entries in the output

> CUSTOMIZE: Replace the instructions above with your own.
> This file overrides the command that the "myext" extension provides.
> When this preset is installed, all agents (Claude, Gemini, Copilot, etc.)
> will use this version instead of the extension's original.

这份文件本身就构成了一篇"覆盖型命令"的最小完整示例,可以从三个层面拆解它的工作机制。

1.1 frontmatter:description 是命令的"自我介绍"

文件开头的 YAML frontmatter 只保留一个字段 description。它会被写入各 Agent 目录中注册后的命令文件,作为命令在 Agent 侧显示的名字/说明。在 tests/test_presets.py 中就有专门断言预设命令 description 传递行为的用例(例如 test_core_command_override_skill_uses_preset_command_description),说明 description 不仅是元数据,还会参与到 skill/command 解析和恢复逻辑中。

1.2 正文:给 LLM 的分步工作流指令

正文是真正会被 Agent 读取、执行的提示词。覆盖版的工作流是一个三段式的经典结构:

  1. 读取输入:从 $ARGUMENTS 获取用户传给该命令的参数。$ARGUMENTS 是 spec-kit 在 Agent 命令注册阶段统一使用的参数占位符——Markdown 类 Agent(Claude、Kilo Code、opencode 等)直接使用 $ARGUMENTS;Copilot 的 .agent.md/.prompt.md 也使用 $ARGUMENTS;而 Gemini、Qwen、Tabnine 的 TOML 格式则替换为 {{args}}(见 presets/ARCHITECTURE.md 中的 Agent 格式渲染对照表)。
  2. 走标准流程:先遵循扩展 myext 原始 myextcmd 命令既有的标准工作流,保证不破坏原命令的基础行为。
  3. 追加自定义:在标准流程之外叠加本 Preset 的自定义点——示例中是"执行前增加合规检查(compliance checks)"与"输出中纳入审计追踪条目(audit trail entries)"。

这正是"覆盖而非从零重写"的设计哲学:myextcmd 作为扩展命令在真实使用中通常有复杂的既定逻辑,Preset 覆盖时不需要把原逻辑全部抄写一遍——因为原扩展命令逻辑依然保留在扩展中,Preset 只需要在关键节点注入增量规则。

1.3 约定与约束:命令命名 speckit.<ext-id>.<cmd-name>

文件名 speckit.myext.myextcmd.md 遵循 spec-kit 的统一命令命名约定 speckit.<ext-id>.<cmd-name>

  • 前缀 speckit. 声明这是 spec-kit 托管命令;
  • 中段 myext 是该命令所属扩展(extension)的 ID
  • 末段 myextcmd 是扩展内的命令名。

对照核心命令 speckit.specify(仅两段点分、属于 spec-kit 本体)可以看出,三段点分命名即"扩展命令"的标识。这一形状约束在 CLI 层也有落实:specify preset resolve 命令在解析带点名字时会用正则 [a-z0-9-]+(?:\.[a-z0-9-]+)+ 校验,要求每个点分段非空、且命令必须由至少两个点分段组成(见 src/specify_cli/presets/_commands.py)。

二、声明入口:preset.yml 里如何注册这个命令覆盖

文件本体只是内容,真正让 spec-kit 认识它的是 Preset 清单 preset.yml。在官方 scaffold 的 presets/scaffold/preset.yml 中,该覆盖对应如下条目:

# Override an extension command (e.g. from the "myext" extension):
- type: "command"
  name: "speckit.myext.myextcmd"
  file: "commands/speckit.myext.myextcmd.md"
  description: "Override myext's myextcmd command with custom workflow"
  replaces: "speckit.myext.myextcmd"

2.1 字段逐一解析

字段 取值示例 含义
type "command" 覆盖资源的类型。Preset 可提供 template(文档脚手架)、command(Agent 工作流提示词)、script(自定义脚本,预留)三类
name "speckit.myext.myextcmd" 在优先级解析栈中参与组合的标识名,必须是全小写字母数字 + 连字符 + 点的形式
file "commands/speckit.myext.myextcmd.md" 实际内容文件路径(相对 preset 根目录),可以与约定路径 commands/<name>.md 不一致
description 简短说明 该覆盖要做什么
replaces "speckit.myext.myextcmd" 指明覆盖的对象——这里是被覆盖的扩展命令名(可选字段)

scaffold 的 preset.yml 中同时展示了三种覆盖对象:核心模板 spec-templatetype: template + replaces: spec-template)、扩展模板 myext-templatereplaces: myext-template)、以及核心命令 speckit.specify。对比可见,同一套声明语法既适用于核心资源也适用于扩展资源——Preset 在解析栈中的位置高于扩展,因此任何已安装扩展提供的模板/命令都能被 Preset 覆盖(preset.yml 顶部注释原话:"Presets sit above extensions in the resolution stack, so you can override templates provided by any installed extension.")。

2.2 覆盖 vs 组合:strategy 决定是替换还是拼接

Preset 默认以 replace 策略整体替换下层内容,但每条模板/命令都可声明 strategy 改变组合方式(preset.ymlpresets/README.md 均给出了完整说明):

策略 行为 template command script
replace(默认) 完全替换低优先级内容
prepend 把本内容放到低优先级内容之前(空行分隔)
append 把本内容放到低优先级内容之后(空行分隔)
wrap 本内容含 {CORE_TEMPLATE}(模板/命令)或 $CORE_SCRIPT(脚本)占位符,用低优先级内容替换占位符

对命令而言,一个很实用的 wrap 场景是:为 speckit.specify 包一层"合规检查前导 + 收尾签名"——此时 name 仍写 speckit.specifystrategywrap,正文中放 {CORE_TEMPLATE} 作为原命令内容的插入点(scaffold 的 preset.yml 注释中给出了这段示例)。多个按 prepend/append 组合的 Preset 会递归串联:例如安全 preset 负责 prepend 头部、合规 preset 负责 append 尾部,最终输出 = 安全头 + 核心内容 + 合规尾。

三、机制内幕:命令覆盖"何时、如何"生效

要真正驾驭覆盖,必须区分 spec-kit 中两个关键时机的差异(presets/README.md 有明确说明):

  • 模板解析发生在运行时specify preset resolve spec-template 每次查找都重新走解析栈,Preset 内容彼此之间不预先合并;
  • 命令覆盖在安装时注册specify preset add 时,Preset 中所有 type: command 条目会被立即注册进所有已检测到的 Agent 目录.claude/commands/.gemini/commands/.github/agents/ 等,共覆盖 17+ 种 Agent)。

这正是关联文档末尾那段注释的落点:"When this preset is installed, all agents (Claude, Gemini, Copilot, etc.) will use this version instead of the extension's original."——安装即全局生效,所有 Agent 拿到的都是覆盖版命令。

3.1 注册流程与"扩展安全检查"

presets/ARCHITECTURE.md 给出了完整的命令注册决策流程,其中最关键的一步是扩展安全检查(extension safety check)

  1. specify preset add my-preset 检测到 type: command 条目;
  2. 依据命令名的点分段数量分流:
    • 命名空间命令(3 段以上,如 speckit.myext.myextcmd)→ 提取扩展 ID(myext),检查 .specify/extensions/<ext-id>/ 是否存在;
    • 扩展未安装 → 跳过该命令,避免生成指向不存在扩展的孤儿文件;
    • 扩展已安装 → 注册命令;
  3. 核心命令(如 speckit.specify,只有 2 段)→ 无条件注册。

tests/test_presets.py 中可找到对应行为的两类用例:test_extension_command_registered_when_extension_present 验证"扩展在场则注册",test_selfcontained_namespaced_command_scaffolds_without_extension 则覆盖"无扩展依赖、自带命令脚手架的命名空间命令"这一变体,印证了该规则的边界处理。

实操含义:如果你在 preset.yml 中覆盖一个扩展命令,必须先把对应扩展安装好specify extension add myext),否则注册会被静默跳过。测试覆盖版命令时,先 specify preset list / 查看对应 Agent 目录确认文件确实生成。

3.2 按 Agent 渲染不同格式

注册命令时,CommandRegistrar(位于 src/specify_cli/agents.py)会根据目标 Agent 的格式规范渲染同一份内容:

Agent 类型 格式 文件扩展名 参数占位符
Claude、Kilo Code、opencode 等 Markdown .md $ARGUMENTS
Copilot Markdown(含配套) .agent.md + .prompt.md $ARGUMENTS
Gemini、Qwen、Tabnine TOML .toml {{args}}

也就是说,你在 speckit.myext.myextcmd.md 里写的正文会被原样注册成各 Agent 能识别的命令文件,description$ARGUMENTS 则被转换成对应平台的约定形态。测试套件中 test_argument_hint_preserved_for_preset_commandtest_argument_hint_not_added_for_non_claude_preset_command 等用例,专门守护了这种跨 Agent 渲染时参数占位符不被破坏的细节。

3.3 卸载与启停的清理语义

  • specify preset remove my-preset:根据 registry 元数据,从所有 Agent 目录删除对应命令文件(Copilot 的 .prompt.md 配套文件一并清理),恢复到扩展原始命令;
  • specify preset disable / enable不会反注册已注册的命令——CLI 输出明确提示 "Previously registered commands/skills remain active until preset removal"。需要临时退出覆盖时,理解这一点能避免误判(禁用 ≠ 命令立即消失)。

这套"可回滚、可清理"的设计意味着:覆盖扩展命令是非破坏性的,myext 扩展的原版 myextcmd 始终保留在扩展包中,只要卸载 Preset 即可还原。

四、把示例扩展成你自己的覆盖命令

把脚手架示例改造成可用的真实覆盖,只需四步:

步骤 1:复制 scaffold 作为起点

cp -r presets/scaffold my-preset

步骤 2:编辑 preset.yml 元数据与条目

preset:
  id: "my-preset"          # 小写字母/数字/连字符
  name: "My Preset"
  version: "1.0.0"         # 语义化版本
  description: "Brief description of what your preset provides"
requires:
  speckit_version: ">=0.1.0"   # 最低 spec-kit 版本约束
provides:
  templates:
    - type: "command"
      name: "speckit.myext.myextcmd"
      file: "commands/speckit.myext.myextcmd.md"
      description: "Override myext's myextcmd command with custom workflow"
      replaces: "speckit.myext.myextcmd"

清单必需字段还包括 schema_version: "1.0"authorrepositorylicense(MIT 为推荐开源许可)、可选 tags(2~5 个便于目录检索)。

步骤 3:重写命令覆盖正文

commands/speckit.myext.myextcmd.md 中替换 > CUSTOMIZE 标记之下的全部指令,写清你的增量规则。例如把示例的"合规检查 + 审计追踪"落地为:

You are following a customized version of the myext extension's myextcmd command.

When executing this command:

1. Read the user's input from $ARGUMENTS
2. Follow the standard myextcmd workflow
3. Additionally, apply the following customizations from this preset:
   - Add compliance checks before proceeding
     - Verify the requested change maps to an open requirement
     - Refuse execution when any acceptance criterion is missing
   - Include audit trail entries in the output
     - Record command name, timestamp, agent identity, and resolved decision
     - Append the trail to .specify/memory/audit.log

提示:你还可以在正文中像 extensions/template/commands/example.md 那样使用 tools: frontmatter 声明 MCP 工具依赖,或在正文中引用 {CORE_TEMPLATE}(wrap 策略时),请按你的扩展实际能力组织内容。

步骤 4:本地安装、解析验证与清理

# 先确保被覆盖的扩展已安装(否则命令注册会被安全机制跳过)
specify extension add myext

# 从本地目录开发模式安装(--dev)
specify preset add --dev ./my-preset

# 查看命令解析结果
specify preset resolve speckit.myext.myextcmd

# 确认 Agent 目录中已生成覆盖版命令文件
ls .claude/commands/

# 查看 Preset 信息与已安装列表
specify preset info my-preset
specify preset list

# 验证完成后移除
specify preset remove my-preset

若需与团队共享,可随后参照 presets/PUBLISHING.md 提交到目录(catalog)。scaffold 的完整开发循环见其 presets/scaffold/README.md

五、CLI 全命令速查与源码落点

覆盖扩展命令涉及的 specify preset 子命令均由 src/specify_cli/presets/_commands.py 中 Typer 定义的 preset_app 提供,核心实现类(PresetManagerPresetResolverPresetRegistryPresetCatalog)在 src/specify_cli/presets/init.py,跨 Agent 注册由 src/specify_cli/agents.pyCommandRegistrar 完成:

命令 作用
specify preset search [query] [--tag] [--author] 在目录中搜索 Preset
specify preset add <id> [--priority N] 从目录/内置安装 Preset
specify preset add --from <zip/tar.gz/tgz URL> 从归档 URL 安装
specify preset add --dev <本地目录> 开发模式安装(覆盖调试首选)
specify preset list 列出已安装 Preset(按实际解析优先级排序显示)
specify preset resolve <name> 查看某模板/命令名当前解析到哪个文件及来源
specify preset info <id> 查看已安装或目录内 Preset 详情
specify preset set-priority <id> <priority> 调整解析优先级(数字小者优先)
specify preset enable/disable <id> 启用/禁用(不反注册已注册命令)
specify preset remove <id> 卸载并从 Agent 目录清理注册的命令

值得注意的实现细节:preset add--from 下载有完整的安全校验(URL 必须 HTTPS 或 localhost,重定向目标受限、内容限长读取),priority 必须为正整数(命令行直接校验 priority < 1 即报错)。多 Preset 同时提供同名模板时按 (priority, preset-id) 排序,数值小者优先、同值按 ID 字母序,preset list 的输出顺序与真实解析顺序保持一致。

结语

speckit.myext.myextcmd.md 这一份 20 行的覆盖示例出发,可以看清 spec-kit Preset 系统完整的设计链路:模板按运行时解析栈逐层解析,命令则在安装时按"扩展安全检查 → 按 Agent 格式渲染 → 写入各 Agent 目录"的流程全局注册。覆盖扩展命令因此成了一种低成本、可回滚、跨 Agent 统一生效的定制手段——团队可以在不 fork、不修改 spec-kit 核心代码的前提下,为既有扩展命令叠加合规检查、审计追踪、专属文案等团队规则。若要继续深入,可顺序阅读 presets/README.md(用户指南)、presets/ARCHITECTURE.md(内部架构与决策流程)以及 src/specify_cli/presets/init.py(核心实现),并结合 tests/test_presets.py 中的命令注册用例验证自己的理解。

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