Read OSS

統合階層: 1つのコードベース、25以上のAIエージェント

上級

前提知識

  • 記事1: アーキテクチャとプロジェクトナビゲーション
  • 記事2: Initコマンドの詳細解説
  • OOPデザインパターン(Template Method、Strategy)への理解
  • YAMLフロントマターの理解

統合階層: 1つのコードベース、25以上のAIエージェント

specify init --integration windsurf を実行すると、.windsurf/workflows/ にmarkdownファイルが生成されます。--integration gemini を使えば .gemini/commands/ にTOMLファイルが、--integration claude では .claude/skills/speckit-*/SKILL.md 配下にカスタムフロントマターを持つskillディレクトリが作られます。いずれも起点は同じ9つのコマンドテンプレートです。この仕組みを支えているのが統合クラス階層であり、Spec Kitのコードベースの中でもとりわけアーキテクチャ的に興味深い部分です。

4層クラス階層

統合システムは古典的なOOPをベースに構築されており、その核にはTemplate Methodパターンがあります。4つの基底クラスが段階的に具体的な振る舞いを提供します。

classDiagram
    class IntegrationBase {
        <<abstract>>
        +key: str
        +config: dict
        +registrar_config: dict
        +context_file: str
        +setup()*
        +teardown()
        +process_template()
        +copy_command_to_directory()
        +record_file_in_manifest()
        +write_file_and_record()
        +install_scripts()
    }

    class MarkdownIntegration {
        +setup()
        "~20 agents: Windsurf, Cursor, Roo, Amp..."
    }

    class TomlIntegration {
        +setup()
        +command_filename()
        -_extract_description()
        -_render_toml()
        "2 agents: Gemini, Tabnine"
    }

    class SkillsIntegration {
        +setup()
        +skills_dest()
        "3+ agents: Claude, Codex, Kimi"
    }

    class CopilotIntegration {
        +setup()
        +command_filename()
        "1 agent: fully custom"
    }

    IntegrationBase <|-- MarkdownIntegration
    IntegrationBase <|-- TomlIntegration
    IntegrationBase <|-- SkillsIntegration
    IntegrationBase <|-- CopilotIntegration

階層は integrations/base.py で定義されています。IntegrationBase はファイル操作、テンプレート処理、マニフェスト追跡といった細かいプリミティブを提供します。3つの具体的な基底クラスはそれぞれフォーマット固有のロジックで setup() を実装しています。MarkdownIntegrationTomlIntegrationSkillsIntegration です。CopilotIntegration は独自の要件から IntegrationBase を直接継承しています。

設計思想は明快です。ほとんどの統合はメソッドのオーバーライドを一切必要とすべきではない。3つのクラス属性を設定し、残りはすべて継承する。

IntegrationBase: 細かいプリミティブ

base.py#L54-L67 の抽象基底クラスは、4つの必須クラス属性を定義しています:

  • key — CLIツール名に対応する一意の識別子(例: "windsurf""kiro-cli"
  • confignamefoldercommands_subdirinstall_urlrequires_cli を持つメタデータdict
  • registrar_configdirformatargsextension を持つフォーマット仕様
  • context_file — エージェントのcontext/instructionsファイルへのオプションパス

base.py#L164-L362 にある構成要素メソッドはすべてstaticまたはインスタンスメソッドで、サブクラスが setup() の中で組み合わせて使います:

sequenceDiagram
    participant Setup as setup()
    participant List as list_command_templates()
    participant Proc as process_template()
    participant Write as write_file_and_record()
    participant Scripts as install_scripts()

    Setup->>List: Get sorted .md files from shared commands dir
    loop For each template
        Setup->>Proc: Transform raw markdown → agent-ready content
        Setup->>Write: Write to dest dir + record SHA-256 hash
    end
    Setup->>Scripts: Copy integration-specific scripts

write_file_and_record() メソッドは書き込み前に改行コードを \n に正規化し、直後にSHA-256ハッシュをマニフェストに記録します。これにより、マニフェストが常にディスク上の正確なバイト列を反映することが保証されます。後述するアンインストールの安全動作にとって欠かせない仕組みです。

Tip: shared_commands_dir() メソッドは、パート1で説明したデュアルパス解決パターンと同じ仕組みを使っています。まず core_pack/commands/(wheelインストール)を確認し、次に templates/commands/(ソースチェックアウト)を確認します。アセットにアクセスする基底クラスのメソッドはすべてこの規則に従っています。

process_template() の7ステップパイプライン

process_template() staticメソッドは、フォーマット変換の核心部分です。生のコマンドテンプレートをエージェント対応のコンテンツへ変換するため、7つの処理を順番に実行します:

flowchart TD
    S1["1. Extract scripts.{type} from YAML frontmatter"] --> S2["2. Replace {SCRIPT} with extracted command"]
    S2 --> S3["3. Extract agent_scripts.{type}, replace {AGENT_SCRIPT}"]
    S3 --> S4["4. Strip scripts: and agent_scripts: sections from frontmatter"]
    S4 --> S5["5. Replace {ARGS} with agent-specific placeholder"]
    S5 --> S6["6. Replace __AGENT__ with agent name"]
    S6 --> S7["7. Rewrite paths: scripts/ → .specify/scripts/"]

sh スクリプトを使ってWindsurf向けに plan.md テンプレートを処理する場合の流れを追ってみましょう:

入力フロントマター には両方のスクリプトバリアントが含まれています:

scripts:
  sh: scripts/bash/setup-plan.sh --json
  ps: scripts/powershell/setup-plan.ps1 -Json
agent_scripts:
  sh: scripts/bash/update-agent-context.sh __AGENT__
  ps: scripts/powershell/update-agent-context.ps1 -AgentType __AGENT__
  • ステップ1〜2後: {SCRIPT}scripts/bash/setup-plan.sh --json
  • ステップ3後: {AGENT_SCRIPT}scripts/bash/update-agent-context.sh __AGENT__
  • ステップ4後: scripts:agent_scripts: のYAMLブロックがフロントマターから削除される
  • ステップ5後: {ARGS}$ARGUMENTS(Windsurfのプレースホルダー)
  • ステップ6後: __AGENT__windsurf
  • ステップ7後: scripts/bash/setup-plan.sh.specify/scripts/bash/setup-plan.sh

ステップ7のパス書き換えは CommandRegistrar.rewrite_project_relative_paths() に委譲されます。テンプレート内で使われるリポジトリ相対パスから、スキャフォールド済み出力で使われるプロジェクト相対パスへの変換を担当しています。

ケーススタディ: Windsurf — 4属性の統合

Windsurfは、この階層の力を端的に示す例です。統合全体がわずか22行で完結しています — integrations/windsurf/__init__.py:

class WindsurfIntegration(MarkdownIntegration):
    key = "windsurf"
    config = {
        "name": "Windsurf",
        "folder": ".windsurf/",
        "commands_subdir": "workflows",
        "install_url": None,
        "requires_cli": False,
    }
    registrar_config = {
        "dir": ".windsurf/workflows",
        "format": "markdown",
        "args": "$ARGUMENTS",
        "extension": ".md",
    }
    context_file = ".windsurf/rules/specify-rules.md"

メソッドのオーバーライドはゼロです。base.py#L455-L508MarkdownIntegration.setup() がすべてを処理します。テンプレートの反復処理、process_template() の呼び出し、処理済みファイルの書き込み、スクリプトのインストールをまとめて担います。27個の統合のうち約20個がこの同じパターンに従っており、違いはクラス属性だけです。

ケーススタディ: Copilot — 完全カスタムの統合

Copilotはこの規則の例外です。どの基底クラスも対応できない3つの要件があるため、IntegrationBase を直接継承しています — integrations/copilot/__init__.py#L24-L38:

  1. .agent.md 拡張子 — 通常の .md の代わりに使用:

    def command_filename(self, template_name: str) -> str:
        return f"speckit.{template_name}.agent.md"
  2. .github/prompts/ 配下のコンパニオン .prompt.md ファイル — コマンドごとに1つずつ、エージェントコマンドを参照するYAMLヘッダーを含む:

    prompt_content = f"---\nagent: {cmd_name}\n---\n"
  3. VS Code settings.json のマージ — テンプレートのsettingsファイルを同梱し、既存のsettingsを上書きせずにマージする。copilot/__init__.py#L134-L186 のマージ処理は、コメント付きJSONCファイルにも対応している(json.loads でパースできない場合はマージをスキップして警告を出す)

flowchart LR
    A["Command templates"] --> B[".github/agents/<br/>speckit.plan.agent.md"]
    A --> C[".github/prompts/<br/>speckit.plan.prompt.md"]
    A --> D[".vscode/settings.json<br/>(merged)"]
    A --> E[".specify/integrations/<br/>copilot/scripts/"]

カスタムの setup() を持っていても、Copilotは同じ基底クラスのプリミティブを使い続けています: process_template()write_file_and_record()record_file_in_manifest()install_scripts()。階層は、すべてを書き直すことなく脱出できる抜け道を提供してくれます。

ケーススタディ: ClaudeとGemini — SkillsとTOML

ClaudeSkillsIntegration を使用し、agentskills.io 仕様に従ったコマンドごとのディレクトリ構造を生成します。ただしClaudeはポストプロセシングを追加しています — integrations/claude/__init__.py:

基底の SkillsIntegration.setup() がskillファイルを生成した後、Claudeのオーバーライドは生成されたファイルを反復して3つのフロントマターを注入します:

フロントマターキー 目的
user-invocable true Claude Codeで /command からskillにアクセス可能にする
disable-model-invocation true モデルがskillを自動実行するのを防ぐ
argument-hint "Describe the feature..." ユーザーがコマンドを呼び出す際のインラインヒントテキストを表示する

argument-hint の値は ARGUMENT_HINTS dict から取得されます。コマンドのstemと人間が読みやすいプロンプトのマッピングを定義しています。

Geminibase.py#L515-L684TomlIntegration を使い、同じ process_template() パイプラインを実行した後、結果をTOMLに変換します。変換処理ではフロントマターから description を抽出し(ブロックスカラーを処理するため yaml.safe_load を使用)、フロントマターを除去してから descriptionprompt キーを持つTOMLファイルを生成します。本文にトリプルクォートのデリミタが含まれる場合、リテラル文字列またはエスケープされた基本文字列へのフォールバックといったエッジケースにも対応しています。

IntegrationManifest: ハッシュ追跡による安全なアンインストール

セットアップ中に作成されるすべてのファイルは IntegrationManifest によって追跡されます。.specify/integrations/<key>.manifest.json に保存されるJSONファイルで、次の内容を持ちます:

{
  "integration": "claude",
  "version": "0.6.2.dev0",
  "installed_at": "2026-04-12T10:30:00+00:00",
  "files": {
    ".claude/skills/speckit-plan/SKILL.md": "a1b2c3d4e5f6...",
    ".claude/skills/speckit-specify/SKILL.md": "f6e5d4c3b2a1..."
  }
}

各値はインストール時のファイルコンテンツのSHA-256 hexダイジェストです。アンインストール時、manifest.uninstall() は現在のハッシュが記録済みのハッシュと一致するファイルのみを削除します。変更されたファイルはスキップされ、その旨が報告されます。ユーザーによるカスタマイズを誤って削除することを防ぐための仕組みです。

flowchart TD
    A["uninstall() called"] --> B["For each tracked file"]
    B --> C{"File exists?"}
    C -->|No| D["Skip (already gone)"]
    C -->|Yes| E{"SHA-256 matches?"}
    E -->|Yes| F["Delete file + clean empty parents"]
    E -->|No| G{"--force flag?"}
    G -->|Yes| F
    G -->|No| H["Skip (user modified)"]
    F --> I["Remove manifest JSON"]

Tip: マニフェストはファイル削除後に空になった親ディレクトリも削除します。アンインストール後に .claude/skills/speckit-plan/ のような空のディレクトリツリーが残るのを防ぐためです。

登録と新しい統合の追加

integrations/__init__.py_register_builtins() 関数は厳格な規約に従っています。importとregistrationはいずれもアルファベット順に並べます。各統合サブパッケージはPythonセーフなディレクトリ名を使います。キー内のハイフンはディレクトリ名ではアンダースコアになりますが(kiro-clikiro_cli/)、key 属性はハイフン形式を保ちます。

新しい統合を追加する手順は3つです:

  1. サブパッケージを作成するsrc/specify_cli/integrations/myagent/__init__.pyMarkdownIntegration(最も一般的なケース)を継承するクラスを定義する
  2. _register_builtins() に登録する — importと _register(MyAgentIntegration()) の呼び出しを追加する
  3. テストを追加するtests/integrations/test_integration_myagent.py で登録、config、プレースホルダー置換を検証する

ほとんどのエージェントでは、実装全体が25行以内に収まります。重労働はクラス階層が引き受けてくれます。

次のステップ

統合によってエージェント固有のコマンドファイルが生成されますが、それらのファイルの中身はどうなっているのでしょうか?パート4では、Spec Kitの宣言的ワークフローエンジンを形成する9つのスラッシュコマンドテンプレートを掘り下げます。YAMLフロントマターがワークフローDAGを構築する仕組み、シェルスクリプトが操作レイヤーを担う仕組み、そして構造化されたプロンプトによってAI出力の品質をどのように制約するかを解説します。