Spec Kit 架构解析:一个 CLI 如何编排 AI 驱动的开发流程
前置知识
- ›Python 打包基础知识(pyproject.toml、wheel、entry points)
- ›熟悉 CLI 框架(typer 或 click)
- ›对 AI 编程助手有基本了解(Claude、Copilot、Gemini 等)
Spec Kit 架构解析:一个 CLI 如何编排 AI 驱动的开发流程
每款 AI 编程助手都有自己的配置目录、命令格式和接收指令的方式。如果你想同时为 Claude Code、Copilot、Gemini CLI、Cursor 以及二十多款其他 agent 提供结构化的工作流指引,就会面临格式各异、文件爆炸的问题。GitHub 的 Spec Kit 用一个 CLI 解决了这一难题——specify 命令将一套共享的工作流模板转换为面向 25 款以上助手的专属指令。本文将梳理这背后的架构设计。
什么是 Spec Kit 和规范驱动开发?
规范驱动开发(Spec-Driven Development,SDD)颠覆了规范与代码之间的传统关系。传统模式下,规范只是搭建实现的脚手架,用完即弃;而 SDD 将规范本身视为核心产出物,代码是规范的具体表达,而非相反。
Spec Kit 通过四个阶段的工作流将这一理念落地:
flowchart LR
A["/speckit.specify"] --> B["/speckit.plan"]
B --> C["/speckit.tasks"]
C --> D["/speckit.implement"]
A -.->|"clarify"| E["/speckit.clarify"]
E -.-> A
每个阶段都是一条由 AI agent 执行的斜杠命令。specify 命令将自然语言描述的功能需求转化为结构化规范;plan 命令将规范转化为技术实现方案;tasks 命令将方案拆解为有序的任务列表;implement 命令则逐一执行这些任务。AI agent 既是这些命令的读取者,也是执行者——markdown 文件本身就是程序,LLM 就是运行时。
specify CLI 工具本身并不执行这些工作流,它的职责是引导项目初始化——为开发者所使用的 AI 助手生成正确格式的配置文件。你可以把它理解为一个编译器,从同一份源码生成面向 25 款以上指令集架构的目标产物。
目录结构与模块职责
代码仓库在 Python CLI 包与其分发内容之间有清晰的职责划分:
| 目录 | 职责 |
|---|---|
src/specify_cli/ |
Python 包——CLI 命令、集成系统、extension/preset 管理器 |
templates/commands/ |
9 个斜杠命令模板(带 YAML frontmatter 的 markdown) |
templates/*.md |
文档模板(spec、plan、tasks、constitution、checklist) |
scripts/bash/ 和 scripts/powershell/ |
命令模板调用的 shell 脚本 |
extensions/git/ |
内置 git extension——5 个命令 + 18 个生命周期钩子 |
presets/lean/ |
内置 "lean" preset——轻量版工作流模板 |
tests/ |
pytest 测试套件,结构与 integration 子包保持镜像 |
docs/ |
DocFX 文档站点源文件 |
src/specify_cli/ 下的 Python 包模块结构简洁:
graph TD
INIT["__init__.py<br/>(4,143 lines — CLI + TUI + orchestration)"]
INT["integrations/<br/>__init__.py + base.py + 27 agent subpackages"]
MAN["integrations/manifest.py<br/>(SHA-256 file tracking)"]
EXT["extensions.py<br/>(ExtensionManifest, Registry, Manager)"]
PRE["presets.py<br/>(PresetManifest, Registry, Manager)"]
AGT["agents.py<br/>(CommandRegistrar — bridges extensions to agents)"]
INIT --> INT
INIT --> EXT
INIT --> PRE
EXT --> AGT
PRE --> AGT
INT --> MAN
整个项目以 __init__.py 为核心向外辐射。integrations 包负责 agent 抽象层;extensions 和 presets 是两套并行的插件系统,均通过 agents.py 作为桥接层,输出各 agent 专属的格式。
巨型 __init__.py——为什么这样设计
src/specify_cli/__init__.py 共 4,143 行,是整个项目的重力中心,包含:
main()入口和 Typer app 配置- 所有 CLI 命令:
init、check、version、extension、preset、integration - 进度显示的 TUI 组件
StepTracker - 交互选择组件
select_with_arrows() - 共享基础设施安装函数(
_install_shared_infra、_locate_core_pack) - 适配 wheel 安装和源码检出两种场景的资源路径解析逻辑
这种单文件的设计是一种有意为之的取舍。对于通过 uv tool install 安装的 CLI 工具而言,启动速度至关重要。单个模块意味着 import 时的文件系统查找次数更少。真正的拆分发生在这个文件的周围:integration 类层次结构位于 integrations/base.py,文件清单追踪位于 integrations/manifest.py,extension 和 preset 系统则各有独立模块。
入口本身非常简洁——__init__.py#L4139-L4143:
def main():
app()
if __name__ == "__main__":
main()
app 对象是一个 Typer 实例,使用了自定义的 BannerGroup,会在帮助信息输出前显示 ASCII banner——__init__.py#L301-L307:
app = typer.Typer(
name="specify",
help="Setup tool for Specify spec-driven development projects",
add_completion=False,
invoke_without_command=True,
cls=BannerGroup,
)
提示: 初次探索代码库时,在
__init__.py中搜索@app.command()装饰器,即可快速定位所有 CLI 命令。大约有十几个,每个对应一个独立的子命令,如init、extension add、preset list等。
模块依赖图与引导链
当用户执行 specify init 时,import 链会触发一系列级联操作,在第一行命令逻辑执行之前,25 款以上的 integration 已全部完成注册。流程如下:
flowchart TD
A["specify (entry point)"] --> B["specify_cli:main()"]
B --> C["__init__.py module loads"]
C --> D["_build_agent_config()"]
D --> E["from .integrations import INTEGRATION_REGISTRY"]
E --> F["integrations/__init__.py loads"]
F --> G["_register_builtins()"]
G --> H["Imports 27 agent subpackages"]
H --> I["_register() for each → populates INTEGRATION_REGISTRY"]
I --> J["AGENT_CONFIG dict built from registry"]
关键函数是 _build_agent_config(),在模块级别调用:
def _build_agent_config() -> dict[str, dict[str, Any]]:
"""Derive AGENT_CONFIG from INTEGRATION_REGISTRY."""
from .integrations import INTEGRATION_REGISTRY
config: dict[str, dict[str, Any]] = {}
for key, integration in INTEGRATION_REGISTRY.items():
if integration.config:
config[key] = dict(integration.config)
return config
AGENT_CONFIG = _build_agent_config()
这意味着 INTEGRATION_REGISTRY 必须在 __init__.py 加载完成之前全部填充完毕。这一工作由 _register_builtins() 完成——它按字母顺序 import 每个 integration 子包并逐一注册。integrations/__init__.py 的最后一行无条件触发它:
_register_builtins()
这种"import 时即注册"的模式确保了注册表始终完整一致,不存在部分初始化的风险。代价是:若新增的 integration 存在语法错误,整个 CLI 将无法启动。
agents.py 模块则采用了类似的懒加载初始化方式。其 CommandRegistrar 类在首次访问时从注册表构建 AGENT_CONFIGS,并通过 try/except 处理模块加载期间可能出现的循环导入问题——agents.py#L30-L57。
离线打包与分发
企业环境往往在安装阶段无法访问外网。Spec Kit 通过 Hatch 的 force-include 机制解决这一问题,将所有运行时资源直接打包进 Python wheel。
相关配置位于 pyproject.toml#L28-L45:
[tool.hatch.build.targets.wheel.force-include]
"templates/agent-file-template.md" = "specify_cli/core_pack/templates/agent-file-template.md"
"templates/commands" = "specify_cli/core_pack/commands"
"scripts/bash" = "specify_cli/core_pack/scripts/bash"
"scripts/powershell" = "specify_cli/core_pack/scripts/powershell"
"extensions/git" = "specify_cli/core_pack/extensions/git"
"presets/lean" = "specify_cli/core_pack/presets/lean"
构建时,Hatch 将仓库中的 templates/、scripts/、extensions/、presets/ 目录复制到 wheel 内的 specify_cli/core_pack/ 下。运行时,_locate_core_pack() 负责判断使用哪条路径:
def _locate_core_pack() -> Path | None:
candidate = Path(__file__).parent / "core_pack"
if candidate.is_dir():
return candidate
return None
flowchart TD
A["_locate_core_pack()"] --> B{"core_pack/ exists?"}
B -->|"Yes (wheel install)"| C["Use specify_cli/core_pack/"]
B -->|"No (source checkout)"| D["Fallback to repo root: templates/, scripts/"]
所有需要访问资源的函数——_install_shared_infra()、_locate_bundled_extension()、_locate_bundled_preset()——都遵循这种双路径模式。回退到仓库相对路径的机制意味着,从源码运行(uv run specify init)的开发者无需先构建 wheel,就能获得一致的行为体验。
提示: 遇到资源路径问题时,检查
specify_cli安装目录下是否存在core_pack/。若不存在,说明你在以源码模式运行,这在开发阶段完全正常,但资源路径的解析方式会有所不同。
下一步
至此,我们已梳理了整体架构——单体 CLI 核心、integration 注册表的引导机制,以及离线打包方案。接下来,我们将深入系统中最关键的命令。在第二篇中,我们将完整追踪 specify init 的执行路径:从 17 个 CLI 参数,经过交互式 TUI,到将空目录转化为完整 SDD 项目脚手架的 8 步编排流水线。