architecture-scaffold について
architecture-scaffoldは、高レベルのアーキテクチャ仕様を、実装ロジックを加える前にコンパイル可能な型の骨格へ変換するエージェントスキルです。モジュール構成、段階的なコンパイル、依存関係の検証、既存コードの対応付けを扱います。
主な機能
- 01モジュール、型、シグネチャの抽出
- 02レイヤーごとの依存ルール定義
- 03型スタブの段階的な構築
- 04コンパイルと依存方向の検証
- 05既存ロジックとスタブの対応付け
活用シーン
- 01アーキテクチャ仕様からリファクタリングの骨格を作成
- 02コンパイラーのフィードバックによる境界確認
- 03既存ロジックの移行計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ petekp/ claude-code-setup --skill architecture-scaffoldインストール後、Claude Code で/architecture-scaffoldを入力して読み込まれたか確認します。
同梱ファイルreferences/language-patterns.md
詳細
アーキテクチャの骨組み
高レベルのアーキテクチャ仕様をコンパイル可能な型の骨組みに変換し、誰かがロジックを1行でも書く前に、コンパイラーでその妥当性を証明します。
これが必要な理由
エージェントがいきなり実装に取りかかると、大規模なリファクタリングは失敗します。全体像を見失い、グローバルな設計と矛盾する局所的な判断を下した結果、出発点とは別の混乱を招いてしまいます。「人間がシェルを構築する」パラダイム(Mengdi Chen、2026年)は、構造とロジックを分離することで、これを解決します。
- 型は厳格な制約です。違反はビルド時にコンパイラーによって拒否されます
- テストは振る舞いの検証です。実行時にコードが何をするかを確認します
- 仕様は柔軟な指針です。判断の助けにはなりますが、強制はできません
このスキルが扱うのは第1層です。作業が終わるころには、すべてのモジュール、関数シグネチャ、プロトコル/トレイト、型の関係が実際のコードとして存在し、コンパイラーによる検証も済んでいます。まだロジックはありません。あるのはアーキテクチャの骨組みだけです。コンパイラーエラーを越えて、エージェントが何かを幻覚することは文字どおり不可能です。
3つのフェーズ
フェーズ1:目標とするアーキテクチャを抽出する
仕様を読みます。すべてのモジュール、型、シグネチャを構造化してまとめます。
フェーズ2:骨組みを構築する
実際の型とスタブ本体を含むソースファイルを書きます。すべてが通るまで、レイヤーごとにコンパイルします。
フェーズ3:既存のコードベースを対応付ける
各スタブについて、既存のロジックを移植できるか、書き直す必要があるかを判断します。
フェーズ1:目標とするアーキテクチャを抽出する
ユーザーから提供された評価文書または設計文書を読みます。次の点を確認します。
- モジュール/コンポーネント — 新しいアーキテクチャ単位は何か?
- 責務 — 各単位が担うものは何か?
- 型 — 境界を越えて受け渡されるデータ構造は何か?
- 依存関係 — どのモジュールがどのモジュールに依存するか?依存の方向は?
- 境界 — 明確な境界はどこか?(FFI、ネットワーク、永続化、フレームワークとの接点)
これらを捉えた構造化された概要であるモジュールマップを作成します。まだコードは書きません。モジュールマップは、文章で書かれた仕様とコードの骨組みをつなぐ中間表現です。
# Module Map
## Layer 1: Domain Types (innermost — no dependencies on other project modules)
### module: domain
Location: core/capacitor-core/src/domain/
Responsibility: Shared value types, identities, and enums used across all modules
Types:
- Project { id, name, path, ... }
- RuntimeSnapshot { active_project, hooks, timestamp, ... }
- HookEvent (enum: session_start, session_end, tool_use, ...)
Dependency rule: This module imports nothing from the project. Everything else may import this.
## Layer 2: Service Contracts (depend only on domain types)
### module: RuntimeEngine
Location: core/capacitor-core/src/runtime/
Responsibility: Core runtime lifecycle — start, stop, snapshot reads
Dependencies: domain (inward only)
Exposes:
- RuntimeEngine (protocol/trait)
- start(config: RuntimeConfig) -> Result<(), RuntimeError>
- stop() -> Result<(), RuntimeError>
- current_snapshot() -> RuntimeSnapshot
Types:
- RuntimeConfig { storage_path, poll_interval, ... }
- RuntimeError (enum: already_running, storage_unavailable, ...)
### module: SetupService
Location: core/capacitor-core/src/setup/
Responsibility: First-run and configuration workflows
Dependencies: domain (inward only)
Exposes:
- SetupService (protocol/trait)
- validate_setup() -> SetupStatus
- perform_setup(config: SetupConfig) -> Result<(), SetupError>
...
## Layer 3: FFI Boundary (translates between layers)
### module: ffi
Location: core/capacitor-core/src/ffi/
Responsibility: Expose Rust services to Swift via C-compatible interface
Dependencies: Layer 2 services, domain types
Boundary types (must be repr(C) or serializable):
- FFIRuntimeConfig, FFIRuntimeSnapshot, ...
Binding mechanism: [cbindgen / uniffi / manual C headers — match what the project uses]
## Layer 4: Swift Application Layer (outermost — depends on FFI)
### module: RuntimeSupervisor
Location: apps/swift/Sources/Capacitor/Services/RuntimeSupervisor.swift
...
依存関係のルール
モジュールマップでは、各レイヤーの依存関係のルールを明示する必要があります。これらは検証時に強制可能な制約になります。
Layer 1 (domain) → imports nothing from the project
Layer 2 (services) → imports only Layer 1
Layer 3 (FFI) → imports Layers 1 and 2
Layer 4 (Swift app) → imports only Layer 3's public interface
これらのルールによって、アーキテクチャが再びスパゲッティ化するのを防げます。明記してください。フェーズ2で機械的に検証します。
詳細度が重要です
各関数シグネチャには、パラメーター名、パラメーター型、戻り値の型を含めます。評価文書に指定がない場合は、説明された責務と既存のコードベースから推測します。不確かな点には印を付けてください。コードに進む前に、ユーザーに確認してもらう必要があります。
曖昧さへの対処
評価文書には、どうしても抜け漏れが生じます。よくある例を挙げます。
- エラー型 — 仕様には「SetupServiceを抽出する」とあるものの、発生しうるエラーが記載されていない場合。既存のコードを調べ、現在の実装で処理しているエラーを確認し、それをもとにエラーの列挙型を設計します。
- 共有型 — 2つのモジュールがどちらも
Projectにアクセスする必要がある場合、その型をどこに置くかを検討します。両方が依存するドメインレイヤーに置きます(依存の方向は内向きです)。 - 境界データ — FFIまたは永続化の境界を越えるものは何かを確認します。これらの型はシリアライズ可能である必要があります。該当する型には明示的に印を付けます。
迷ったら、ユーザーに確認してください。今の5秒の確認で、後から1時間のやり直しを防げます。
評価文書にある既存の参照を活用する
評価文書に特定のファイル、関数、行番号が記載されている場合(優れた文書なら記載されています)、シグネチャを推測する際の手がかりとして使います。評価文書がすでに lib.rs:276 の CoreRuntime.initialize() を示しているなら、コードベースを一から検索する必要はありません。該当箇所を読み、新しいシグネチャを設計します。
承認用のモジュールマップを提示する
コードを書く前に、モジュールマップをユーザーに提示します。次の点を確認してもらいます。
- モジュールが評価でのすべての推奨事項を網羅している
- 依存関係の方向が正しい
- ファイルやディレクトリの配置がプロジェクトに適している
- 重要なものが抜けていない
- 粒度が適切である(細かすぎるモジュールが多すぎず、肥大化したモジュールが少なすぎない)
フェーズ 2:骨組みを作成する
ここから実際のコードを書きます。各モジュール、各型、各関数シグネチャを用意しますが、実装ロジックは書きません。関数本体はスタブにします。
作業ブランチを作成する
git checkout -b architecture-scaffold
ファイルの配置
新しいファイルをどこに置くかは重要です。モジュールグラフが決まるためです。次の原則に従ってください。
- **新しいモジュールには、新しいファイルやディレクトリを用意します。**既存ファイルへの挿入はしません。
CoreRuntimeをRuntimeEngineとSetupServiceに分割する場合は、src/runtime/mod.rsとsrc/setup/mod.rsを作成します。lib.rsから切り出すのはまだ避けてください。 - **モジュールマップの「Location」フィールドを唯一の基準にします。**フェーズ 1 で配置を決めたら、ここで実行に移します。
- **古いファイルは、今の段階では変更しません。**骨組みは既存のコードと並行して配置します。古いコードの削除や変更はしません。これは移行フェーズで行います。
- モジュール宣言を更新します(Rust の
mod文や Swift のパッケージターゲットなど)。コンパイラーが新しいファイルを認識できるようにします。
レイヤーごとにスキャフォールドする
骨組みファイルを一度にすべて書いてからコンパイルするのは避けてください。内側から外側へと構築し、各レイヤーでコンパイルします。これにより、エラーの範囲を限定し、連鎖的な失敗を防げます。
ステップ 1:ドメイン型(レイヤー 1) 共有型(構造体、列挙型、型エイリアス)を記述して、コンパイルします。 依存関係がないため、コンパイルに失敗した場合は問題がこのレイヤー内に限定されます。
ステップ 2:サービス契約(レイヤー 2) プロトコルやトレイト、および関連する型を記述します。スタブ実装も記述して、コンパイルします。 失敗した場合、単純なインポートミスか、ドメイン層との型の不一致です(後者はアーキテクチャ上の問題です。下記を参照)。
ステップ 3:FFI 境界(レイヤー 3) FFI 型と、Rust サービスと Swift の間の変換レイヤーを記述します。このレイヤーでは、プロジェクトですでに使用しているバインディング機構(cbindgen、uniffi、手動の C ヘッダーなど)を使用する必要があります。Rust と Swift の両方をコンパイルし、生成されたバインディングが一致していることを確認します。
ステップ 4:Swift アプリケーション層(レイヤー 4) Swift のプロトコル、スタブ実装、および SwiftUI に公開する型を記述して、コンパイルします。
言語固有のパターン
Swift、Rust、TypeScript の詳しい例については、references/language-patterns.md を参照してください。どの言語でも基本となるパターンは同じです。契約(プロトコル/トレイト/インターフェイス)を定義し、すべてのフィールドを含む型を定義し、コンパイルは通るものの呼び出すとパニックになるスタブ実装を用意します(Swift では fatalError、Rust では todo!()、TypeScript では throw new Error)。
骨組みに含めるもの
含めるもの:
- 完全なメソッドシグネチャを備えたすべてのプロトコル/トレイト
- すべてのフィールド/バリアントを備えたすべての構造体、列挙型、型エイリアス
- コンパイラーが適合性を検証できるようにする、すべてのプロトコル/トレイトのスタブ実装
- モジュール宣言とインポート/エクスポート構造
- 各モジュールの公開 API
- FFI 境界の型とバインディング層(ヘッダー、uniffi 定義など)
含めないもの:
- 実際のビジネスロジック
- テストファイル(これらは後の移行フェーズで追加します)
- 新しいアーキテクチャで構造上の変更が必要な場合を除き、設定ファイル、ビルドスクリプト、CI
コンパイラーエラーへの対応
コンパイラーエラーはすべて同じではありません。次の2種類を区別してください。
偶発的なエラー — 誤字、インポートの不足、可視性修飾子の付け忘れ、宣言と使用箇所で型名が一致していない、といったものです。すぐに修正して再コンパイルしてください。これらは単なるノイズです。
アーキテクチャ上のシグナル — これらはモジュールマップが誤っていることを意味します。
- モジュール間の循環依存 → モジュールの境界の引き方が誤っています。作業を止め、モジュールマップに戻ってください。依存関係グラフがDAGになるまで再構成します。
- 境界での型の不一致 → 境界を挟んだ両側で、受け渡すデータについての認識が異なっています。これはコードの問題ではなく、設計上の欠陥です。どちら側が正しいかを決め、モジュールマップを更新してからコードを修正してください。
- 別のモジュールの内部に踏み込まないと実装できないプロトコル/トレイト → 抽象化の境界が適切な位置にありません。おそらく、責務を移す必要があります。
アーキテクチャ上のシグナルに遭遇したら、コンパイルを止め、モジュールマップを更新し、変更が重要な場合はユーザーの承認を得てから再開してください。コンパイラーを通すために問題を回避するような修正をしてはいけません。それが混乱の始まりです。
依存関係の方向を検証する
スケルトンがコンパイルできたら、宣言した依存ルールが守られていることを確認してください。各レイヤーで次のように確認します。
Rust: 各モジュールディレクトリ内のuse文をgrepします。すべてのuse crate::...パスは、同じレイヤーか下位レイヤーのモジュールのみを参照する必要があります。
# Example: verify Layer 2 modules only import from Layer 1
grep -rn "use crate::" core/capacitor-core/src/runtime/ | grep -v "domain"
# Should return nothing — any hit is a dependency violation
Swift: インポート文を確認します。Swiftモジュールは、宣言された依存関係のみを参照する必要があります。
違反が見つかった場合は、アーキテクチャ上の欠陥です。インポートではなく、モジュールマップを修正してください。
健全性の基準
次の条件を満たすと、スケルトンは「健全」です。
- すべてのレイヤーで、エラーなしでビルドが成功する
- すべてのモジュールの公開APIで型が完全に定義されている
- すべてのプロトコル/トレイトに、少なくとも1つのスタブ実装がある
- 依存関係の方向が機械的に検証されている(上記のgrepチェック)
- FFI境界の型が両側で定義され、バインディングレイヤーがコンパイルできる
検証結果を記録します。
## Skeleton Verification
- Rust (cargo check): PASS
- Swift (swift build): PASS
- FFI bindings: PASS (generated headers match Swift imports)
- Dependency direction: PASS (no violations found)
- Module count: 8 (4 Rust, 4 Swift)
- Protocol/trait count: 6
- Stub implementation count: 6
- FFI boundary types: 12 shared types verified
フェーズ3:旧コードベースをマッピングする
検証済みのスケルトンを用意したら、既存のコードに戻り、新しいアーキテクチャの各スタブについて、そのロジックがどこから来るのかを正確に対応付けます。
評価結果に記載された参照先から始める
評価結果に具体的なファイル、関数、行番号が記載されている場合は、それを使ってください。評価結果ですでに特定されているものを、コードベースで再検索してはいけません。たとえば、評価結果にlib.rs:276-340のCoreRuntime.initialize()が起動処理を担うと記載されているなら、RuntimeEngine.start()をマッピングする際の起点にします。
評価結果で具体的なコードが示されていないスタブについては、責務を基準に既存のコードベースを検索します。
移行マニフェスト
プロジェクトのルートにmigration-manifest.mdを作成します。
# Migration Manifest
Source assessment: [path to assessment]
Skeleton branch: architecture-scaffold
Generated: [date]
## RuntimeEngineImpl
### start(config:)
- **Source:** `CoreRuntime.initialize()` in `core/capacitor-core/src/lib.rs:276-340`
- **Action:** PORT
- **Confidence:** HIGH
- **Notes:** Currently also does setup validation; that moves to SetupService.
Signature change: takes RuntimeConfig instead of raw path + options.
### stop()
- **Source:** `CoreRuntime.shutdown()` in `core/capacitor-core/src/lib.rs:342-380`
- **Action:** PORT
- **Confidence:** HIGH
- **Notes:** Straightforward extraction, no entangled dependencies.
### currentSnapshot()
- **Source:** `CoreRuntime.get_snapshot()` in `core/capacitor-core/src/lib.rs:382-395`
- **Action:** ADAPT
- **Confidence:** HIGH
- **Notes:** Remove the global state bypass that check_hook_health uses.
The function is simple, but it currently reads from a global rather than
the instance's storage — that's the adaptation.
## SetupServiceImpl
### performSetup(config:)
- **Source:** `CoreRuntime.run_setup()` + `CoreRuntime.validate_config()`
- **Action:** REWRITE
- **Confidence:** MEDIUM
- **Notes:** Logic is scattered across the god object and interleaved with
runtime concerns. Gather requirements from both sources but write fresh.
Pay attention to the error handling in validate_config — it has edge cases
the rewrite needs to preserve.
アクションの分類
各スタブに、次のアクションを1つだけ割り当てます。
- PORT — 既存のロジックが新しいシグネチャで必要な処理を行います。ロジックを抽出し、シグネチャに合わせて調整して移動します。ロジック自体に問題はありません。
- ADAPT — 既存のロジックはおおむね適切ですが、新しいアーキテクチャに合わせるには重要な変更が必要です(例:グローバル状態への依存を取り除く、2つの処理を行う関数を分割する)。
- REWRITE — 既存のロジックは絡み合いすぎている、構成が大きく異なる、または単純に誤っています。旧コードを参考資料として用い、新たにロジックを書きます。移植元としては使いません。
- NEW — 既存のロジックでは対応できません。旧アーキテクチャにはなかった新機能です。
- DELETE — 旧コードには存在しますが、新しいアーキテクチャでは役割がありません。本当に不要なのか、ユーザーに確認します。
信頼度
各マッピングの信頼度を HIGH、MEDIUM、LOW で評価します。
- HIGH — ソースの場所が明確で、ロジックが自己完結しており、対応も単純です。
- MEDIUM — ソースは特定できますが、ロジックに依存関係や副作用があり、慎重な対応が必要です。
- LOW — 適切なソースか確信が持てないか、ロジックが複雑に絡み合っていて、対応の分類が誤っている可能性があります。
信頼度が LOW のマッピングには、ユーザーが確認できるように印を付けてください。これらは移行時に問題を引き起こす可能性が最も高い項目です。
マニフェストを提示して確認してもらう
移行マニフェストをユーザーに提示します。確認してもらう主な項目は次のとおりです。
- PORT と REWRITE の判断は正しいですか?(ユーザーは、コードベースのどの部分を信頼できるかを把握していることがよくあります)
- 信頼度が LOW のマッピングについて、調査が必要ですか?
- マッピングに抜けているものはありませんか?
- 「DELETE」の項目は本当に不要ですか?
出力
完了時点で、ユーザーは次の成果物を得ています。
- コンパイル可能な型スケルトン — ブランチ上にある、コードとして検証済みのターゲットアーキテクチャ
- モジュールマップ — 依存関係のルールを含む、アーキテクチャの人間が読める説明
- 移行マニフェスト — 旧コードから新しいスタブへの、アクションと信頼度を含む正確なマッピング
- コンパイラーと依存関係の検証 — 型が適切に組み合わさり、依存関係が正しい方向に流れることの機械的な証明
これらの成果物は、次のフェーズで実際に実装する際の入力となります。実装は architectural-refactor スキルを使う方法でも、手作業で開発する方法でも構いません。スケルトンのブランチがすべての実装作業の基盤となるターゲットになり、移行マニフェストによって、必要なロジックの場所をエージェントに正確に伝えられます。
architectural-refactor スキルへの橋渡し
実行に architectural-refactor スキルを使う予定であれば、移行マニフェストをそのスキルの形式に変換する必要があります。各スタブのマッピングを、リファクタリング計画のチャンクにします。
- PORT スタブは「抽出して移動」するチャンクになります(リスクが低いため、最初に実施します)
- ADAPT スタブは「抽出、変更して移動」するチャンクになります(リスクは中程度です)
- REWRITE スタブは「新しい実装を作成」するチャンクになります(リスクが最も高いため、最後に実施します)
- DELETE 項目は、最後に実施するクリーンアップのチャンクになります
- NEW 項目は、移植が完了してから実施する実装のチャンクになります
関連するスタブは、1 つのチャンクにまとめてください(例:1 つのモジュールにあるすべての PORT スタブを 1 つのチャンクにまとめる)。リスク順に並べます。PORT が最初、ADAPT が次、REWRITE が最後です。各チャンクの完了条件には、Phase 2 で使ったものと同じコンパイルチェックを含めてください。実際のロジックを実装した後もスケルトンがコンパイルできれば、アーキテクチャにずれは生じていません。
基本原則
- 判断基準はエージェントではなく、コンパイラーです。 エージェントがアーキテクチャに問題はないと言っても、コンパイラーが異なる判断を示したら、常にコンパイラーに従ってください。
- ロジックより型を先にします。 「この関数だけすぐに実装しよう」という衝動を抑えてください。振る舞いとは切り離して構造を検証することが、全体の目的です。
- アーキテクチャ上のエラーはコンパイラーのエラーではありません。 コンパイラーが構造上の問題(循環依存や境界の不一致)を明らかにしたら、モジュールマップに戻ってください。コンパイラーを黙らせるためにコードを修正してはいけません。
- 速さより正確さを優先します。 「ロジックは lib.rs のどこかにある」のような曖昧な移行マニフェストは、役に立たないどころか有害です。エージェントが推測する余地を与えてしまいます。具体的に記述してください。
- 早めに、何度でも質問してください。 コードを書く前に曖昧さを解消すれば、後のやり直しを何倍も減らせます。仕様が不明確な場合は、作業を止めて質問してください。
- パイプラインは一方向に進めます。 マッピング中にスケルトンの作成へ戻らないでください。マッピングの結果、スケルトンに不備が見つかった場合は、明示的に Phase 2 に戻り、スケルトンを修正して再コンパイルしてから、マッピングを再開してください。