Spec Kit Presets 体系详解:运行时模板解析、组合策略与目录管理的完整机制
Presets(预设)是 Spec Kit 中用于自定义 Spec-Driven Development(SDD)工作流的机制:它以"可叠加、按优先级排序"的模板与命令覆盖集合,让你在不 fork、不修改任何核心文件的前提下,定制 SDD 工作流产出的工件(spec、plan、tasks、checklist、constitution)以及引导 LLM 生成这些工件的命令本身。本文基于当前仓库的 presets/README.md 展开,并结合 presets/ARCHITECTURE.md 与 src/specify_cli/presets/ 下的实现源码,讲清模板解析栈、组合策略、命令注册、目录(catalog)管理与自建预设的完整流程。
Presets 解决什么问题
SDD 工作流的核心循环是:用模板生成规范文档,用命令(即写给 AI Agent 的分步提示词)驱动生成过程。默认模板对通用场景足够,但团队往往有特定诉求——合规检查必须出现在每份 spec 里、plan 必须先走安全评审、constitution 需要加入组织级治理条款等。
Presets 的定位就是这一层的定制点。它有两个关键设计前提:
- 不需要 fork 或修改核心文件:所有定制内容通过覆盖(override)而非改写实现,升级 Spec Kit 时预设不会被破坏;
- 可叠加(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 机制出现之前完全一致——这是它对存量项目零侵入的保证。
对应实现位于 PresetResolver(src/specify_cli/presets/__init__.py),并且为保持一致性,同一套解析逻辑在三种语言中各有一份实现:
- Python:
PresetResolver(src/specify_cli/presets/init.py); - Bash:
resolve_template()(scripts/bash/common.sh); - PowerShell:
Resolve-Template(scripts/powershell/common.ps1)。
其中组合内容(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"条目时,PresetManager(src/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}} |
两个重要的安全与清理语义:
- 扩展命令安全检查:命令名遵循
speckit.<ext-id>.<cmd-name>模式。当命令名有 3 段及以上点分段时,系统会提取扩展 ID 并检查.specify/extensions/<ext-id>/是否存在——扩展未安装时该命令会被跳过,避免产生引用不存在扩展的孤立文件;核心命令(如speckit.specify,仅 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-priority、preset enable、preset 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),其核心算法可以从源码结构看为:
collect_all_layers()收集该模板在整条优先级栈上的所有层,按"高优先级在前"排序;- 若最高优先级层的策略是
replace,直接返回该层内容——低层完全无关; - 否则从最高优先级向下扫描,找到最近的
replace层作为有效基底(effective base),只组合位于其上方的层; - 自基底向高优先级方向逐层应用策略:
prepend执行layer_content + "\n\n" + content,append执行content + "\n\n" + layer_content; wrap策略会校验占位符存在——缺失{CORE_TEMPLATE}(或脚本的$CORE_SCRIPT)会抛出PresetValidationError,明确指出 wrapper 必须包含该占位符(src/specify_cli/presets/init.py);- 对命令类型额外做 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 选择顺序:
- 设置了
SPECKIT_PRESET_CATALOG_URL环境变量 → 使用该单一自定义 catalog(替换所有默认值); - 存在项目级
.specify/preset-catalogs.yml→ 使用项目级 catalog 栈; - 存在用户级
~/.specify/preset-catalogs.yml→ 使用用户级 catalog 栈; - 都没有 → 使用内置默认:官方 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.com、github.com、api.github.com、codeload.github.com);非 GitHub 的 catalog URL 一律不带凭据抓取。
自建预设:从 scaffold 到发布
仓库在 presets/scaffold/ 提供了可直接复制的脚手架。presets/README.md 给出的五步流程:
- 复制
scaffold/到新目录; - 编辑
preset.yml填入预设元数据; - 在
templates/中新增或替换模板; - 用
specify preset add --dev .本地测试; - 用
specify preset resolve spec-template验证解析结果。
preset.yml 的完整字段结构可从 presets/scaffold/preset.yml 读到,核心包括:
schema_version: "1.0"与preset:块(id小写连字符、name、version语义化版本、description200 字符以内、author、repository、license);requires.speckit_version:声明最低 Spec Kit 版本约束(scaffold 示例为>=0.1.0,内置预设则分别要求>=0.6.0/>=0.14.4),安装时由兼容性检查强制校验;provides.templates:模板条目列表,每个条目含type、name、file、description,可选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.com、github.com、api.github.com、codeload.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用于发现组合冲突。
二者目前均为规划状态,当前仓库尚未提供这些命令。
进一步深入可查阅的仓库资源:
- presets/README.md — 本文的主体文档,用户指南;
- presets/ARCHITECTURE.md — 解析、命令注册、目录系统的内部架构与流程图;
- presets/PUBLISHING.md — 向 catalog 提交预设的指南;
- src/specify_cli/presets/init.py —
PresetCatalogEntry、PresetManifest、PresetRegistry、PresetManager、PresetCatalog、PresetResolver六大核心类; - src/specify_cli/presets/_commands.py —
specify preset ...全部子命令的 CLI 定义; - tests/contract/test_wheel_bundled_presets.py 与 tests/test_presets.py — 预设打包契约与行为测试。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00