Read OSS

Spec Kit の拡張機能: Extensions と Presets のプラグインアーキテクチャ

上級

前提知識

  • 第1回: アーキテクチャとプロジェクト構成
  • 第3回: インテグレーションシステム(CommandRegistrar の理解に必要)
  • 第4回: コマンドテンプレートとワークフローエンジン
  • YAML 構文と JSON スキーマの基礎知識

Spec Kit の拡張機能: Extensions と Presets のプラグインアーキテクチャ

9 つの組み込みコマンドは SDD の中核となるワークフローをカバーしていますが、実際のプロジェクトではそれだけでは足りないことがあります。たとえば、すべてのワークフローステップに git ブランチ管理を組み込みたいチームもあれば、スペックテンプレートに HIPAA の必須セクションを追加する医療コンプライアンス向けプリセットを使いたいケースもあるでしょう。Spec Kit はこうしたニーズに応えるため、2 つの並列した拡張メカニズムを提供しています。extensions は新しいコマンドとライフサイクルフックを追加し、presets は既存のテンプレートを上書きします。どちらも同じバリデーションパターン、カタログによる検出、そしてエージェント対応の出力フォーマットを共有しています。

Extension マニフェストのスキーマとバリデーション

すべての extension は extension.yml マニフェストで定義され、ExtensionManifest によってバリデーションされます。スキーマには4つのトップレベルフィールドが必須です。

schema_version: "1.0"
extension:
  id: my-ext        # ^[a-z0-9-]+$ の正規表現でバリデーション
  name: "My Extension"
  version: "1.0.0"  # packaging.version 経由で semver バリデーション
  description: "..."
requires:
  speckit_version: ">=0.2.0"  # PEP 440 バージョン指定子
provides:
  commands: [...]    # command か hook のどちらか一方は必須
hooks: {...}         # ライフサイクルフック(省略可)

コマンド名は EXTENSION_COMMAND_NAME_PATTERN の正規表現 speckit.{extension}.{command} に従う必要があります。この命名規則 — たとえば speckit.git.featurespeckit.git.commit — により、extension 間およびコアコマンドとの名前衝突を防ぐことができます。

extensions.py#L148-L231 のバリデーションは厳格です。

  • extension の ID は小文字の英数字とハイフンのみ
  • バージョンは packaging.version.Version によって有効な semver としてパースできること
  • speckit_version 指定子は実行中の CLI バージョンに対して検証される
  • すべてのコマンドに namefile の両フィールドが必要
  • すべてのフックに command フィールドが必要
  • extension はコマンドかフックを少なくとも 1 つ提供すること

ヒント: バリデーションエラーメッセージは具体的で対処しやすい内容になっています。たとえば「validation failed」という汎用的なメッセージではなく、「Invalid command name 'git.feature': must follow pattern 'speckit.{extension}.{command}'」のように詳細が示されます。extension を開発する際は、extension ディレクトリで specify extension add . を実行すると、その場でバリデーションフィードバックを得られます。

Git Extension: 実装の手本となる具体例

extensions/git/extension.yml に同梱されている git extension は、extension が何を実現できるかを示す最良のサンプルです。以下を提供しています。

5 つのコマンド:

コマンド 用途
speckit.git.feature 連番またはタイムスタンプ番号を使ったフィーチャーブランチの作成
speckit.git.validate ブランチが命名規則に従っているかの検証
speckit.git.remote GitHub 連携のための git リモート URL の検出
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.initialize を実行し(必須、optional: false)、constitution が作成される前に git リポジトリが存在することを保証します。before_specify フックは speckit.git.feature を実行し(こちらも必須)、フィーチャーブランチを作成します。すべての after_* フックは speckit.git.commitoptional: true で実行し、「Commit specification changes?」のようなユーザー向けプロンプトを表示します。

この extension には設定テンプレートと extensions/git/scripts/ 配下の bash・PowerShell 両対応のシェルスクリプトも含まれています。

フックシステムのアーキテクチャ

フックはマニフェストの 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

設計上の重要な判断として、フックの実行は CLI ではなく AI エージェントが行いますextensions.yml は宣言的な設定ファイルであり、AI がランタイムに読み込んで解釈します。フックをチェックするロジックはコマンドテンプレートの中に記述されており、「ランタイム」とは LLM がマークダウンの指示を読み解くプロセスそのものです。つまりフックの実行品質は AI エージェントが指示に従う能力に依存しますが、このトレードオフによってシステムをシンプルに保ちつつ、AI の自然言語理解を最大限に活かすことができています。

ExtensionRegistry とカタログスタック

インストール済みの extension は .specify/extensions/.registryExtensionRegistry が管理する 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
    }
  }
}

extension は削除しなくても有効・無効を切り替えられます。また、複数の extension が同じフックポイントを提供する場合、テンプレート解決の優先順位を決めるための priority 値が各 extension に設定されています。

extension の検出には、Spec Kit はマルチカタログシステムを採用しています。extensions/catalog.json の公式カタログには同梱の extension が一覧されています。

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

bundled: true フラグは、ダウンロードではなく core_pack/extensions/ から extension を探すよう CLI に指示します。コミュニティカタログ extensions/catalog.community.json や組織固有のカタログも、優先度ベースのマージ解決によって重ねて使用できます。

CommandRegistrar ブリッジ

extension コマンドをエージェント固有のディレクトリに書き込む際、extension システムはフォーマット変換を自前で行わず、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-L540register_commands_for_all_agents() メソッドは、プロジェクト内の既存エージェントディレクトリをスキャンします。.claude/skills/.github/agents/.gemini/commands/ などを検出し、各エージェントに適したフォーマットでextensionのコマンドを書き出します。つまりinit後にextensionをインストールしても、設定済みのエージェントすべてに自動でコマンドが展開されます。

CommandRegistrar 内の AGENT_CONFIGS dict は INTEGRATION_REGISTRY から遅延評価で生成されます。これは第1回で紹介した「単一の信頼できる情報源」パターンと同じ設計です。_ensure_configs() クラスメソッドはインポート失敗時にリトライし、モジュールロード中の循環インポートに対応しています。

Copilot 向けには、register_commands()write_copilot_prompt() も呼び出して、Copilot が必要とする .prompt.md ファイルを生成します。これによりフォーマット固有のロジックが一箇所に集約されています。

プリセットシステム: テンプレートのオーバーライドレイヤー

プリセットは、2 つ目の拡張メカニズムです。extension がコマンドとフックを追加するのに対し、プリセットは既存のテンプレートを置き換えますPresetManifest のバリデーションは ExtensionManifest とほぼ同じ構造で、スキーマバージョン、ID フォーマット、semver バリデーションのルールが共通しています。

presets/lean/preset.yml に同梱されている "lean" プリセットは、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 フィールドは、このテンプレートがどのコアコマンドを上書きするかを示します。解決の優先順位は次のとおりです。

ローカルプロジェクトファイル(最高優先度)
  ↓
プリセットテンプレート
  ↓
extension が提供するテンプレート
  ↓
コアテンプレート(最低優先度)

プリセットは extension と同じレジストリパターン(.specify/presets/.registry)、同じカタログ検出システム、そして同じ CommandRegistrar ブリッジを共有しています。

ヒント: プリセットと extension は共存できます。たとえば、ブランチ管理フックには git extension を使いながら、コマンドテンプレートをシンプルにするために lean プリセットを適用するといった組み合わせが可能です。同じテンプレートを複数のソースが提供する場合は、優先度システムによって確実に解決されます。

次回予告

アーキテクチャ、initパイプライン、インテグレーションシステム、コマンドテンプレート、拡張メカニズムと、主要な仕組みを一通り見てきました。残る最後のテーマは「これらが本当に動くことをどう保証するか」です。第6回では、25以上のエージェントを対象としたインテグレーションテストやextension・プリセットのバリデーションテストを取り上げます。加えてCIパイプラインと、新しいインテグレーションをコントリビュートする際の実践的なガイドも紹介します。