Read OSS

扩展 Spec Kit:插件架构之扩展与预设

高级

前置知识

  • 第 1 篇:架构与项目导航
  • 第 3 篇:集成系统(了解 CommandRegistrar)
  • 第 4 篇:命令模板与工作流引擎
  • YAML 语法与 JSON Schema 基础概念

扩展 Spec Kit:插件架构之扩展与预设

9 个内置命令覆盖了 SDD 工作流的核心场景,但实际项目往往需要更多。团队可能希望在每个工作流步骤中集成 git 分支管理,或者需要一套医疗合规预设,在规格模板中强制添加 HIPAA 必填章节。Spec Kit 通过两套并行的扩展机制来满足这些需求:extensions 用于添加新命令和生命周期钩子,presets 用于覆盖现有模板。两套系统共享相同的校验模式、目录发现机制以及面向 AI agent 的输出格式化逻辑。

扩展清单 Schema 与校验

每个扩展都通过一个 extension.yml 清单文件来定义,并由 ExtensionManifest 进行校验。Schema 要求包含四个顶级字段:

schema_version: "1.0"
extension:
  id: my-ext        # ^[a-z0-9-]+$ regex validated
  name: "My Extension"
  version: "1.0.0"  # semver validated via packaging.version
  description: "..."
requires:
  speckit_version: ">=0.2.0"  # PEP 440 specifier
provides:
  commands: [...]    # At least one command OR hook required
hooks: {...}         # Optional lifecycle hooks

命令名称必须符合 EXTENSION_COMMAND_NAME_PATTERN 正则表达式:speckit.{extension}.{command}。这套命名空间约定——例如 speckit.git.featurespeckit.git.commit——有效避免了扩展之间以及扩展与核心命令之间的命名冲突。

extensions.py#L148-L231 中的校验逻辑相当严格:

  • 扩展 ID 只能包含小写字母、数字和连字符
  • 版本号必须能通过 packaging.version.Version 解析为合法的 semver
  • speckit_version 指定符会与当前运行的 CLI 版本进行校验
  • 每个命令必须同时包含 namefile 字段
  • 每个钩子必须包含 command 字段
  • 扩展至少需要提供一个命令或钩子

提示: 校验错误信息足够具体,可以直接指导修复——例如 "Invalid command name 'git.feature': must follow pattern 'speckit.{extension}.{command}'",而不是笼统的 "validation failed"。开发扩展时,在扩展目录下运行 specify extension add .,可以立即获得校验反馈。

Git 扩展:典型案例分析

内置的 git 扩展位于 extensions/git/extension.yml,是了解扩展能力的最佳参考。它提供了:

5 个命令:

命令 用途
speckit.git.feature 创建功能分支,支持顺序编号或时间戳编号
speckit.git.validate 校验分支是否符合命名规范
speckit.git.remote 检测 git 远程 URL,用于 GitHub 集成
speckit.git.initialize 初始化 git 仓库并创建初始提交
speckit.git.commit 在工作流步骤完成后自动提交变更

18 个生命周期钩子,覆盖每个工作流阶段:

flowchart LR
    subgraph "before_ hooks"
        BC["before_constitution<br/>(git.initialize)"]
        BS["before_specify<br/>(git.feature)"]
        BCl["before_clarify<br/>(git.commit)"]
        BP["before_plan<br/>(git.commit)"]
        BT["before_tasks<br/>(git.commit)"]
        BI["before_implement<br/>(git.commit)"]
        BCh["before_checklist<br/>(git.commit)"]
        BA["before_analyze<br/>(git.commit)"]
        BTI["before_taskstoissues<br/>(git.commit)"]
    end
    subgraph "after_ hooks"
        AC["after_constitution<br/>(git.commit)"]
        AS["after_specify<br/>(git.commit)"]
        ACl["after_clarify<br/>(git.commit)"]
        AP["after_plan<br/>(git.commit)"]
        AT["after_tasks<br/>(git.commit)"]
        AI["after_implement<br/>(git.commit)"]
        ACh["after_checklist<br/>(git.commit)"]
        AA["after_analyze<br/>(git.commit)"]
        ATI["after_taskstoissues<br/>(git.commit)"]
    end

before_constitution 钩子会执行 speckit.git.initializeoptional: false,强制执行),确保在创建 constitution 之前 git 仓库已经存在。before_specify 钩子会执行 speckit.git.feature(同样强制),用于创建功能分支。所有 after_* 钩子则以 optional: true 的方式执行 speckit.git.commit,并向用户展示类似 "Commit specification changes?" 的确认提示。

此外,该扩展还在 extensions/git/scripts/ 目录下包含了配置模板以及 bash 和 PowerShell 两种版本的 shell 脚本。

钩子系统架构

钩子在清单文件的 hooks 部分定义,每个钩子的结构如下:

hooks:
  before_specify:
    command: speckit.git.feature
    optional: false
    description: "Create feature branch before specification"
  after_specify:
    command: speckit.git.commit
    optional: true
    prompt: "Commit specification changes?"
    description: "Auto-commit after specification"
flowchart TD
    A["Extension installed"] --> B["Hooks merged into<br/>.specify/extensions.yml"]
    B --> C["AI reads command template"]
    C --> D["Template says: check hooks.before_specify"]
    D --> E["AI reads .specify/extensions.yml"]
    E --> F{"Hook found?"}
    F -->|No| G["Continue normally"]
    F -->|Yes| H{"optional?"}
    H -->|false| I["AI executes /speckit.git.feature"]
    H -->|true| J["AI presents prompt to user"]
    J --> K{"User approves?"}
    K -->|Yes| I
    K -->|No| G
    I --> L["Hook result"] --> G

这里有一个关键的设计决策:钩子由 AI agent 执行,而非 CLIextensions.yml 是一份声明式配置文件,AI 在运行时读取并解释其内容。命令模板中包含了检查钩子的逻辑——而所谓的"运行时",其实是 LLM 在阅读 Markdown 指令。这意味着钩子的执行可靠性取决于 AI agent 对指令的理解能力。这是一种权衡:以此换来系统的简洁性,同时充分发挥 AI 对自然语言的理解优势。

ExtensionRegistry 与目录栈

已安装的扩展记录在 .specify/extensions/.registry 中,这是一个由 ExtensionRegistry 管理的 JSON 文件:

{
  "schema_version": "1.0",
  "extensions": {
    "git": {
      "id": "git",
      "version": "1.0.0",
      "enabled": true,
      "installed_at": "2026-04-12T10:30:00+00:00",
      "priority": 10
    }
  }
}

扩展可以在不卸载的情况下启用或禁用,每个扩展还有一个优先级值,当多个扩展提供同一钩子点时,用于决定模板解析的顺序。

在扩展发现方面,Spec Kit 采用多目录系统。官方目录位于 extensions/catalog.json,列出了所有内置扩展:

{
  "schema_version": "1.0",
  "extensions": {
    "git": {
      "name": "Git Branching Workflow",
      "bundled": true,
      "tags": ["git", "branching", "workflow", "core"]
    }
  }
}

bundled: true 标志告诉 CLI 从 core_pack/extensions/ 目录加载该扩展,而不是从网络下载。社区目录 extensions/catalog.community.json 以及组织级别的私有目录可以叠加使用,通过基于优先级的合并策略进行解析。

CommandRegistrar 桥接层

当扩展命令需要写入各 agent 对应的目录时,扩展系统本身不处理格式转换,而是委托给 CommandRegistrar

sequenceDiagram
    participant Ext as ExtensionManager
    participant Reg as CommandRegistrar
    participant Dir as Agent Directories

    Ext->>Reg: register_commands_for_all_agents(commands, source_id, ...)
    Reg->>Reg: _ensure_configs() — load from INTEGRATION_REGISTRY
    loop For each detected agent
        Reg->>Reg: Check if agent_dir exists in project
        alt Markdown agent
            Reg->>Dir: Write speckit.git.feature.md
        else TOML agent
            Reg->>Dir: Write speckit.git.feature.toml
        else Skills agent
            Reg->>Dir: Write speckit-git-feature/SKILL.md
        end
    end

agents.py#L503-L540 中的 register_commands_for_all_agents() 方法会扫描项目中已有的 agent 目录(.claude/skills/.github/agents/.gemini/commands/ 等),并以每个 agent 对应的格式写入扩展命令。这意味着在 init 完成后再安装扩展,其命令会自动同步到所有已配置的 agent。

CommandRegistrar 内部的 AGENT_CONFIGS 字典采用懒加载方式,从 INTEGRATION_REGISTRY 填充——这与第 1 篇介绍的单一数据源模式一脉相承。_ensure_configs() 类方法在导入失败时会自动重试,以应对模块加载时的循环导入问题。

针对 Copilot,register_commands() 还会额外调用 write_copilot_prompt(),生成 Copilot 所需的配套 .prompt.md 文件,将格式专属逻辑集中管理。

预设系统:模板覆盖层

预设是第二种扩展机制。extensions 负责添加命令和钩子,而 presets 则负责替换现有模板。PresetManifest 的校验逻辑与 ExtensionManifest 高度相似——相同的 schema 版本、相同的 ID 格式规则、相同的 semver 校验。

内置的 "lean" 预设位于 presets/lean/preset.yml,用精简版本替换了 5 个核心命令:

provides:
  templates:
    - type: "command"
      name: "speckit.specify"
      file: "commands/speckit.specify.md"
      description: "Lean specify - create spec.md from a feature description"
      replaces: "speckit.specify"

replaces 字段指明该模板要覆盖哪个核心命令。模板解析的优先级顺序如下:

本地项目文件(最高优先级)
  ↓
预设模板
  ↓
扩展提供的模板
  ↓
核心模板(最低优先级)

预设与扩展共享相同的注册表模式(.specify/presets/.registry)、相同的目录发现系统,以及相同的 CommandRegistrar 桥接层来处理各 agent 的输出格式。

提示: 预设和扩展可以同时使用。你可以在使用 git 扩展管理分支钩子的同时,应用 lean 预设来简化命令模板。当多个来源提供同一模板时,优先级系统能确保解析结果可预期。

下一步

至此,我们已经介绍了架构、初始化流程、集成系统、命令模板以及扩展机制。还有最后一块拼图:Spec Kit 如何确保这一切真正可靠地运转?第 6 篇将深入测试套件——涵盖 25+ 个 agent 的集成测试、扩展与预设的校验测试、CI 流水线,以及贡献新集成的实用指南。