首页
/ Spec Kit Presets 体系详解:运行时模板解析、组合策略与目录管理的完整机制

Spec Kit Presets 体系详解:运行时模板解析、组合策略与目录管理的完整机制

2026-09-06 23:14:11作者:董斯意

Presets(预设)是 Spec Kit 中用于自定义 Spec-Driven Development(SDD)工作流的机制:它以"可叠加、按优先级排序"的模板与命令覆盖集合,让你在不 fork、不修改任何核心文件的前提下,定制 SDD 工作流产出的工件(spec、plan、tasks、checklist、constitution)以及引导 LLM 生成这些工件的命令本身。本文基于当前仓库的 presets/README.md 展开,并结合 presets/ARCHITECTURE.mdsrc/specify_cli/presets/ 下的实现源码,讲清模板解析栈、组合策略、命令注册、目录(catalog)管理与自建预设的完整流程。

Presets 解决什么问题

SDD 工作流的核心循环是:用模板生成规范文档,用命令(即写给 AI Agent 的分步提示词)驱动生成过程。默认模板对通用场景足够,但团队往往有特定诉求——合规检查必须出现在每份 spec 里、plan 必须先走安全评审、constitution 需要加入组织级治理条款等。

Presets 的定位就是这一层的定制点。它有两个关键设计前提:

  1. 不需要 fork 或修改核心文件:所有定制内容通过覆盖(override)而非改写实现,升级 Spec Kit 时预设不会被破坏;
  2. 可叠加(stackable):多个预设可以同时安装,通过优先级数字决定同名模板谁生效,并通过组合策略(composition strategies)决定是"整体替换"还是"前后附加/包裹"。

模板解析栈:四层优先级与运行时解析

当 Spec Kit 需要某个模板(如 spec-template)时,它走一个四级解析栈(resolution stack):

优先级 来源 路径 用途
1(最高) Override .specify/templates/overrides/ 项目本地的一次性微调
2 Preset .specify/presets/<id>/templates/ 可分享、可叠加的定制
3 Extension .specify/extensions/<id>/templates/ 扩展提供的模板
4(最低) Core .specify/templates/ 随 Spec Kit 发行的默认模板

有两个值得注意的语义细节:

  • 解析发生在运行时。虽然预设文件在 specify preset add 时会被复制到 .specify/presets/<id>/,但 Spec Kit 不会把各层模板合并到单一位置——每次模板查找都会重新走一遍解析栈。这意味着安装、卸载、启用、禁用或调整预设优先级后,下一次命令运行即可看到新的解析结果,无需"重新构建"。
  • 未安装任何预设时行为不变。解析栈最终落到第 4 层核心模板,与 Presets 机制出现之前完全一致——这是它对存量项目零侵入的保证。

对应实现位于 PresetResolversrc/specify_cli/presets/__init__.py),并且为保持一致性,同一套解析逻辑在三种语言中各有一份实现:

其中组合内容(composition content)的解析在 Bash/PowerShell 侧只覆盖模板,命令与脚本的组合统一由 Python 解析器处理——这一点在 presets/ARCHITECTURE.md 中明确说明。

constitution 模板的生命周期

constitution-template 走同一套运行时模型,但有一个特殊的首次播种(seed)环节:

  • 项目初始化时会通过完整解析栈解析一次 constitution-template,并把结果写入 .specify/memory/constitution.md,保证下游命令始终有一份 constitution 可读;已存在的文件按字节原样保留。
  • 此后的每次 /constitution 运行都在运行时解析当前组合出的 constitution-template,再将其作为脚手架套用现有项目值与修订(amendments)。
  • 因此,安装、移除、启用、禁用或重新排序预设默认不会重写已存在的 constitution 文件

如果团队有意让预设栈的变更去刷新一份"未被人工修改过"的生成版 constitution,可以安装仓库内置的 constitution-sync 预设:它在命令时传播之外额外恢复了受保护的"安装时物化"行为,而人工编写(authored)的 constitution 始终受保护、绝不被覆盖。从 presets/catalog.json 可以看到它的官方定位描述为"Opt-in: restores guarded install-time constitution seeding and /constitution propagation for teams that treat materialized templates as reviewed artifacts"。

命令覆盖:与模板不同的"安装时"机制

模板定义的是"产出什么"(what),命令定义的是"LLM 如何一步步产出"(how)。两者在生效时机上有本质区别:

  • 模板覆盖在运行时解析——如上节所述,每次查找都走解析栈;
  • 命令覆盖在安装时注册——当预设声明了 type: "command" 条目时,PresetManagersrc/specify_cli/presets/init.py)会通过 src/specify_cli/agents.py 中的共享 CommandRegistrar,把命令注册到所有检测到的 Agent 目录.claude/commands/.gemini/commands/ 等),并按各 Agent 的格式要求写出文件。

CommandRegistrar 对不同类型 Agent 的渲染差异如下(来自 presets/ARCHITECTURE.md):

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

两个重要的安全与清理语义:

  1. 扩展命令安全检查:命令名遵循 speckit.<ext-id>.<cmd-name> 模式。当命令名有 3 段及以上点分段时,系统会提取扩展 ID 并检查 .specify/extensions/<ext-id>/ 是否存在——扩展未安装时该命令会被跳过,避免产生引用不存在扩展的孤立文件;核心命令(如 speckit.specify,仅 2 段)则无条件注册。
  2. 卸载即清理specify preset remove 会根据注册表元数据(registry metadata)删除各 Agent 目录中对应的命令文件,包括 Copilot 的伴随 .prompt.md 文件。

快速上手:specify preset 命令族

以下是 presets/README.md 给出的核心命令,覆盖搜索、安装、查询与卸载的完整生命周期:

# Search available presets
specify preset search

# Install a preset from the catalog
specify preset add healthcare-compliance

# Install from a local directory (for development)
specify preset add --dev ./my-preset

# Install with a specific priority (lower = higher precedence)
specify preset add healthcare-compliance --priority 5

# List installed presets
specify preset list

# See which template a name resolves to
specify preset resolve spec-template

# Get detailed info about a preset
specify preset info healthcare-compliance

# Remove a preset
specify preset remove healthcare-compliance

src/specify_cli/presets/_commands.py 的 CLI 定义可以补充几个实用细节:

  • --priority 默认值为 10("Resolution priority (lower = higher precedence, default 10)");
  • specify preset search 支持 --tag--author 过滤;
  • specify preset add 还支持 --from 参数,直接从 .zip.tar.gz.tgz 的 URL 安装;
  • 除 README 展示的六个命令外,CLI 还注册了 preset set-prioritypreset enablepreset disable 三个子命令,用于安装后调整优先级或临时停用某个预设(对应上文 constitution 生命周期中"不重写"的那组操作)。

其中 specify preset resolve <name> 是调试解析栈最直接的工具:它直接告诉你某个模板名当前解析到哪一层、哪个文件。

叠加多个预设:优先级与"默认覆盖"

多个预设可以同时安装。当两个预设提供同名模板时,--priority 数字越小者整体胜出(lower number = higher precedence):

specify preset add enterprise-safe --priority 10      # base layer
specify preset add healthcare-compliance --priority 5  # overrides enterprise-safe
specify preset add pm-workflow --priority 1           # overrides everything

关键语义:Presets 默认是覆盖(override),不是合并(merge)。若两个预设都以默认的 replace 策略提供 spec-template,优先级数字更小者完全胜出——不是逐段拼接。若希望"增强而非替换",则需要显式声明下文的组合策略。

组合策略(Composition Strategies)

预设可以在 preset.yml 中为每个模板条目声明 strategy,控制内容与低优先级层如何组合。两个字段分工明确:name 标识组合目标(在优先级栈中要叠加到哪个模板),file 指向实际内容文件(可以不同于约定的 templates/<name>.md 路径):

provides:
  templates:
    - type: "template"
      name: "spec-template"
      file: "templates/spec-addendum.md"
      strategy: "append"        # adds content after the core template

四种策略的定义:

策略 说明
replace(默认) 完全替换低优先级层的内容
prepend 内容置于低优先级层之前,以空行分隔
append 内容置于低优先级层之后,以空行分隔
wrap 内容中包含 {CORE_TEMPLATE} 占位符(脚本为 $CORE_SCRIPT),被低优先级层的内容替换

各类型支持情况:

类型 replace prepend append wrap
template ✓(默认)
command ✓(默认)
script ✓(默认)

组合是递归链式的:例如一个安全预设用 prepend、一个合规预设用 append,最终结果是"安全头部 + 核心内容 + 合规尾部"。

源码视角:resolve_content 如何组装

组合的 Python 实现是 PresetResolver.resolve_content()src/specify_cli/presets/init.py),其核心算法可以从源码结构看为:

  1. collect_all_layers() 收集该模板在整条优先级栈上的所有层,按"高优先级在前"排序;
  2. 若最高优先级层的策略是 replace,直接返回该层内容——低层完全无关;
  3. 否则从最高优先级向下扫描,找到最近的 replace 层作为有效基底(effective base),只组合位于其上方的层;
  4. 自基底向高优先级方向逐层应用策略:prepend 执行 layer_content + "\n\n" + contentappend 执行 content + "\n\n" + layer_content
  5. wrap 策略会校验占位符存在——缺失 {CORE_TEMPLATE}(或脚本的 $CORE_SCRIPT)会抛出 PresetValidationError,明确指出 wrapper 必须包含该占位符(src/specify_cli/presets/init.py);
  6. 对命令类型额外做 frontmatter 处理:剥离各层 YAML frontmatter 防止元数据泄漏进组合正文,最后把最高优先级层的 frontmatter 重新拼回(并从基底继承缺失的 scripts/agent_scripts/argument-hint 键,剥离内部用的 strategy 键)。

第 3 步的"最近 replace 层为基底"设计值得强调:它意味着一个高优先级的 append 预设不会"穿透"一个中优先级的 replace 预设去修改核心模板——replace 层是组合的硬性边界。

目录(Catalog)管理

预设的发现依赖 catalog 体系。默认启用官方与社区两个 catalog;社区预设由各自作者独立创建和维护,维护者只校验 catalog 条目的完整性与格式正确性,不审查、不审计、不背书、不支持预设代码本身——安装前应自行审阅预设源码。

管理命令:

# List active catalogs
specify preset catalog list

# Add a custom catalog
specify preset catalog add https://example.com/catalog.json --name my-org --install-allowed

# Remove a catalog
specify preset catalog remove my-org

PresetCatalog 的实现(src/specify_cli/presets/init.py)与 presets/ARCHITECTURE.md 可以确认其 catalog 选择顺序:

  1. 设置了 SPECKIT_PRESET_CATALOG_URL 环境变量 → 使用该单一自定义 catalog(替换所有默认值);
  2. 存在项目级 .specify/preset-catalogs.yml → 使用项目级 catalog 栈;
  3. 存在用户级 ~/.specify/preset-catalogs.yml → 使用用户级 catalog 栈;
  4. 都没有 → 使用内置默认:官方 catalog(允许安装)+ 社区 catalog(仅发现,discovery only)。

每个 catalog 条目带 priority(用于合并排序)与 install_allowed 标志;catalog 内容按 URL 做 SHA256 哈希缓存,缓存有效期 1 小时。当前仓库自带的 presets/catalog.json 即官方目录示例,收录了两个内置预设:

  • lean(Lean Workflow):"Minimal core workflow commands - just the prompt, just the artifact",提供 5 条命令覆盖,要求 speckit_version >= 0.6.0
  • constitution-sync(Constitution Template Sync):如前所述,要求 speckit_version >= 0.14.4

私有 GitHub 托管 catalog 的鉴权

当 catalog JSON 或预设 ZIP 托管在私有 GitHub 仓库时,需要 GitHub token:

# Authenticate with a token (gh CLI, PAT, or GITHUB_TOKEN in CI)
export GITHUB_TOKEN=$(gh auth token)

# Search a private catalog added via `specify preset catalog add`
specify preset search my-template

# Install from a private catalog
specify preset add my-template

token 只自动附加到目标是 GitHub 域名的请求(raw.githubusercontent.comgithub.comapi.github.comcodeload.github.com);非 GitHub 的 catalog URL 一律不带凭据抓取。

自建预设:从 scaffold 到发布

仓库在 presets/scaffold/ 提供了可直接复制的脚手架。presets/README.md 给出的五步流程:

  1. 复制 scaffold/ 到新目录;
  2. 编辑 preset.yml 填入预设元数据;
  3. templates/ 中新增或替换模板;
  4. specify preset add --dev . 本地测试;
  5. specify preset resolve spec-template 验证解析结果。

preset.yml 的完整字段结构可从 presets/scaffold/preset.yml 读到,核心包括:

  • schema_version: "1.0"preset: 块(id 小写连字符、nameversion 语义化版本、description 200 字符以内、authorrepositorylicense);
  • requires.speckit_version:声明最低 Spec Kit 版本约束(scaffold 示例为 >=0.1.0,内置预设则分别要求 >=0.6.0 / >=0.14.4),安装时由兼容性检查强制校验;
  • provides.templates:模板条目列表,每个条目含 typenamefiledescription,可选 replaces(被覆盖的核心模板名)与 strategy(四种组合策略);
  • 同一 provides 下可混排 type: "command" 条目(核心命令覆盖如 speckit.specify、扩展命令覆盖如 speckit.myext.myextcmd),以及预留的 type: "script" 条目;
  • tags:2~5 个用于目录发现的标签。

一个从源码结构看值得注意的点:预设位于解析栈的第 2 层,天然高于扩展(第 3 层),因此预设可以覆盖任意已安装扩展提供的模板,且无需任何额外声明——优先级自动生效。仓库中 presets/self-test/ 是一个完整覆盖所有核心模板(spec/plan/tasks/checklist/constitution)的自测预设,可作为条目写法的参考实例;发布流程见 presets/PUBLISHING.md

环境变量与配置文件汇总

环境变量 说明 默认值
SPECKIT_PRESET_CATALOG_URL 用单一 URL 覆盖整个 catalog 栈(替换所有默认值) 内置默认栈
GH_TOKEN / GITHUB_TOKEN 访问 GitHub 托管 URL(raw.githubusercontent.comgithub.comapi.github.comcodeload.github.com)的鉴权 token;私有仓库托管 catalog JSON 或预设 ZIP 时必需
配置文件 作用域 说明
.specify/preset-catalogs.yml 项目 本项目的自定义 catalog 栈
~/.specify/preset-catalogs.yml 用户 全部项目共享的自定义 catalog 栈

未来方向与相关资源

presets/README.md 明确列出两项考虑中的增强:

  • 结构化合并策略(Structural merge strategies):解析 Markdown 章节实现段级粒度(例如"只替换 ## Security 一节");
  • 冲突检测specify preset lint / specify preset doctor 用于发现组合冲突。

二者目前均为规划状态,当前仓库尚未提供这些命令。

进一步深入可查阅的仓库资源:

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