AI ワークフローをコードにしてみた
**2026年7月27日追記:**この記事を公開した翌日、BiliKitのM5.0でこのワークフロー最大の問題が表に出た。選んだ方針を厳密に実装する力はあっても、そもそもその問題をその方針で解くべきかは確認されないまま進んでしまう。元の記事はそのまま残し、続編として「AIワークフローをコードにした翌日、自分で壊した」を書いた。
BiliKitはまだV1すら完成していないのに、Swiftのコードはすでに2.1万行を超えている。
プロジェクトが小さかった頃は、AIに少し長めのプロンプトを渡し、出てきたdiffを自分で確認するだけでも何とかなっていた。間違いがあっても変更範囲が狭く、見つけるのはそれほど難しくなかった。
ところが、コードが増えるにつれてこのやり方では足りなくなった。
同じように「まずプロジェクトのルールを読んでから変更して」と頼んでも、セッションが変われば結果も変わる。テストは全部通っているのに、修正するレイヤーを間違えていることもあった。小さな不具合を直してほしかっただけなのに、新しい抽象化が一式増えて戻ってくることもある。
BiliKitには、テストが通っただけでは判断しづらい箇所も多い。Keychain、プレイヤーのライフサイクル、並行処理のキャンセル、弾幕レンダリング、ローカルサーバーなどだ。ビルドや単体テストが成功しても、署名済みのAppや実際の操作で問題がないとは限らない。
そのため、リポジトリには少しずつ次のような仕組みが増えていった。
AGENTS.md- リスク分類
- 品質ゲート
- 独立レビュー
- 複雑さの上限
- 作業内容に応じた検証方法
しばらく使ううちに、BiliKit内のワークフローはだいたい安定した。
そこで別のプロジェクトを始めて、また最初から作り直す必要があることに気づいた。
ルールの一部はAGENTS.mdにあり、一部は品質ゲートの文書やスクリプトにあり、残りは自分の習慣としてしか残っていない。記憶を頼りに書き直すか、BiliKitのファイルをそのままコピーするしかない。
前者は漏れが出るし、後者は余計なものまで持ち込んでしまう。
静的サイトにBiliKitのプレイヤー、Keychain、ローカルサーバー、メディアリダイレクト向けルールは必要ない。一方で、1,000行未満の小さなツールでも、認証情報を扱うなら雑には変更できない。
そこで、ルールそのものではなく、ルールを作る手順を再利用することにした。
codex-engineering-skillsというリポジトリを作り、まず2つのCodex Skillに分けた。
project-governance-bootstrapapple-dev-loop
前者はプロジェクトを読んで、そのプロジェクトに合った開発ルールを作る。後者はAppleプラットフォーム向けのビルド、テスト、検証を担当する。
BiliKitのAGENTS.mdをそのままテンプレートにはできなかった
最初はBiliKitのAGENTS.mdを少し一般化して、共通テンプレートにすればいいと思っていた。
実際に作り始めると、すぐに無理が出た。
BiliKitでは認証、リダイレクト、ローカルサーバー、再生処理、並行処理、レンダラー、破壊的なマイグレーションを高リスク領域として扱っている。BiliKitには実際にその失敗パターンがあるので、この分類で問題ない。
同じ一覧を静的サイトに置けば、ただの形式になってしまう。コマンドラインパーサーの変更に、署名済みAppでのKeychain検証まで要求するようになれば、さすがにおかしい。
そのためproject-governance-bootstrapは、まずリポジトリを読む。
- 既存の開発ルール
- アーキテクチャ上の決定
- manifestと依存関係
- テスト
- CI
- セキュリティ境界
- リリース方法
その後、必要なガバナンスを大まかに3段階から選ぶ。
- light:小規模で元に戻しやすく、主な検証方法が1つだけのもの
- standard:複数モジュール、公開境界、永続化、CI、複数のテスト層などがあるもの
- critical:認証情報、権限、破壊的な移行、信頼できない入力、ローカルサーバー、メディアのライフサイクル、本番インフラなどを扱うもの
コード行数は参考にしかならない。10万行の生成コードより、数十行のデータ削除処理のほうが危険なこともある。
生成されるのは、あくまでそのプロジェクト用のルールだ。Skillとして再利用するのは、そこに至るまでの判断手順になる。
テンプレートより小さなコンパイラに近い
今はproject-governance-bootstrapを、小さなコンパイラのようなものだと考えている。
入力はだいたい次のようになる。
コード+ manifest+ アーキテクチャ文書+ テスト+ CI+ セキュリティ境界+ リリースルール出力はこちらだ。
情報源の優先順位+ アーキテクチャ境界+ リスク領域+ 検証コマンド+ 権限の制限+ 必要に応じたレビュー担当リポジトリ内にはテンプレートも置いているが、出発点にすぎない。プロジェクトに関係のない節は、そのまま削除する。
テンプレートは、一度節ができるとなかなか消えない。「Security」という見出しがあれば、実際には特別なセキュリティ境界がなくても、人もAIも何かを書こうとする。見た目は立派でも、実際には誰も守らない文書ができあがる。
すでに十分なテストコマンドがあるなら、形式を揃えるためだけに別のゲートスクリプトを作る必要はない。どこに置いても通用する一般論を追加しても、あまり役には立たない。
必要な分だけあればいい。
2つのSkillに分けた理由
BiliKitで使っていたワークフローには、2つの問題が混ざっていた。
- このリポジトリでは何を守るべきか
- 今回のAppleプラットフォーム向け変更をどう検証するか
BiliKitはAppleプラットフォームのプロジェクトなので、普段は同時に必要になる。それでも、再利用する段階で2つに分けた。
project-governance-bootstrapはリポジトリのルールを担当する。署名済みApp、UIテスト、性能記録が必要だと判断することはあっても、Xcodeの操作手順までは抱え込まない。
apple-dev-loopは実際の検証を担当する。SwiftPM、xcodebuild、.xcresult、XCUI、署名済みApp、Computer Use、Instrumentsをどこで使うかは知っているが、アプリのアーキテクチャやリスク分類は決めない。
2つは組み合わせて使えるが、依存はしていない。
前者はRust、TypeScript、文書中心のリポジトリにも使える。後者は、すでに開発ルールが整っているSwiftプロジェクトでも単独で使える。
一緒に使う機会が多いからといって、1つにまとめる必要はなかった。
起動しない条件も必要だった
Skillを書いてみて、起動条件もかなり重要だと分かった。
apple-dev-loopが「Swift」という単語だけで起動したら、文法についての質問でもXcodeの確認、schemeの探索、ビルドまで始めてしまう。厳密ではあるが、明らかにやりすぎだ。
そこで、使う条件と使わない条件の両方を書いている。
Appleのツールチェーン、署名、実機、UI、性能検証が必要なタスクでは一連のループを使う。ソースだけで答えられるSwiftの質問や、実行環境を必要としない小さなPackage変更では使わない。
project-governance-bootstrapも同じだ。リポジトリにAGENTS.mdがあるという理由だけで、プロジェクト全体のルールを作り直してはいけない。
以前は説明を多めに渡しても特に問題ないと思っていた。実際には、余分な説明もコンテキストを使い、ツールの選び方に影響する。
単純なPackageテストにInstrumentsの詳しい手順まで読み込ませると、必要もないのに性能計測が候補に入ってくる。
情報が多いほど良いとは限らなかった。
SKILL.mdを大きくしすぎない
最初の案では、2つのSKILL.mdがかなり巨大になるところだった。
ガバナンス側にはすべてのリスク分類、テンプレート、レビュー担当、Apple向け例外を入れ、Apple側にはXcode、XCTest、署名、Simulator、UI、Instrumentsの手順を全部入れようとしていた。
今は短い基本手順だけを置き、必要な資料をその都度読むようにしている。
AppleプロジェクトでなければApple向けルールは読まない。独立した担当が不要ならAgent routingも読まない。PackageテストだけならInstrumentsの手順も読み込まない。
一般にはprogressive disclosureと呼ばれるやり方だが、コンテキストの依存関係を管理していると考えるほうが分かりやすい。
あるツールの説明が詳しいほど、そのツールは選ばれやすくなる。今回の判断に関係しないなら、最初から読み込まないほうがいい。
Appleの検証をどこまで行うか
apple-dev-loopには、次のような検証の段階がある。
- ソースと静的な制約の確認
- SwiftPM、単体テスト、結合テスト
xcodebuildと構造化された.xcresult- Xcode上の診断と操作
- 再現可能なXCUIテスト
- 署名済みAppと実際のUI操作
- 対象を絞った
xctraceまたはInstrumentsの記録 - CI、実機マトリクス、独立レビュー
毎回最後まで進むわけではない。今回の結論を証明できる段階まで来たら、そこで止める。
スクリーンショットは単体テストの代わりにならない。署名なしのビルドではKeychainへのアクセスを確認できない。Appが起動しただけではライフサイクルの問題が直ったとは言えない。1台のMacで取ったトレースだけで、すべての対応端末を確認したことにもならない。
Skillには小さな補助スクリプトも入れている。時間のかかる処理を始める前に、リポジトリ、workspace、scheme、Xcode、Developer Directoryを記録するものや、数千行のコンソール出力ではなく.xcresultの構造化データを読むものだ。
shellでXcodeを作り直すつもりはない。自動化して曖昧さを減らせる、範囲の狭い処理だけをスクリプトにしている。
インストールスクリプトでも事故は起きる
このリポジトリには、CodexからSkillを見つけられるようにシンボリックリンクを作るスクリプトがある。
リンク先がすでに正しければ何もしない。別のリンクや実体ディレクトリが置かれていれば、上書きを拒否する。
小さな処理だが、既存のSkillを黙って置き換えるインストーラーは普通にデータを消せる。
一度のビルドを通すためにグローバルなxcode-selectを変更するのも似ている。そのプロジェクトは直っても、次に別のプロジェクトを開いたとき、どのXcodeが選ばれているか分からなくなる。
そのため、設定はできるだけ現在のタスク内に閉じるようにした。状態が曖昧なら止まり、オプションのツールがないだけなら、今回の検証に必要になるまでblockerにはしない。
曖昧なルールをそのまま残せなくなった
BiliKitでは、次の一文でもだいたい意味が通じていた。
重要な変更には独立レビューが必要。
再利用するSkillにすると、すぐに疑問が出てくる。何を重要とするのか。複数ファイルを変更しただけでレビューが必要なのか。レビュー担当にはどこまで情報を渡すのか。結果が食い違ったらどうするのか。
結局、適用範囲を細かくした。
緑の作業では機械的にレビューを追加しない。単純作業ではない黄色の変更には、必要に応じて独立した読み取り専用レビューを付ける。赤の変更では失敗経路、キャンセル、所有権、セキュリティ、後片付け、ロールバックを重点的に見る。同時に複雑さにも上限を設け、厳密さを理由に手順が増え続けないようにする。
ほかのルールも同じだった。
最上位の決定的なテストが下位のテストを含んでいるなら、「すべてのテストを実行する」と言って同じ内容を何度も走らせる必要はない。
実機確認も、ローカルの決定的なテストでは今回の結論を証明できないときに使う。
Xcode MCPを使う場合も、接続先のXcodeプロセス、ウィンドウ、workspace、schemeを先に確認しないと、別のプロジェクトに対して正常に操作できてしまう。
プロジェクト内の文脈に頼っていたルールは、再利用する段階で条件を書き直す必要があった。
Skill自体もテストする
Skillsリポジトリにはvalidatorがあり、現在は次の項目を確認している。
- metadata
- 内部リンク
- 置換されていないプレースホルダー
- shell構文
- 空白とフォーマット
インストール先の衝突は別のスクリプトで確認する。
ただし、ここで分かるのはファイル構造が壊れていないことだけだ。
metadataもリンクもshellも正しくても、Skillの判断がおかしいことはある。起動範囲が広すぎたり、生成するルールが重すぎたり、既存の制約を見落としたり、今回の結論を証明できない検証方法を選んだりする可能性は残る。
次は別のプロジェクトで試す必要がある。
project-governance-bootstrapは、小さくて元に戻しやすいツール、普通のマルチモジュールアプリ、本当にセキュリティやライフサイクル上のリスクがあるプロジェクトで試したい。出てくるルールはそれぞれ違うはずだ。
apple-dev-loopは、Packageの変更ならPackageテスト、Xcodeプロジェクトならxcodebuild、Keychainなら署名済みApp、性能問題ならInstrumentsというところで正しく止まれるかを確認する。
結局、プロジェクト名だけを置き換えたBiliKitのルールが出てくるなら、また直せばいい。