Skip to content

主要な技術的決定

なぜ flock ではなく mkdir ベースのロックを使用するのか?

macOS には flock が標準搭載されていません。mkdir ベースのロックは mkdir をアトミック操作(POSIX 保証)として使用し、5秒のタイムアウトと古いロックの強制破棄を備えています。トラップにより、予期しない終了時のクリーンアップが保証されます。

なぜフェイルオープンフックなのか?

プラグインは jq が存在しない環境にインストールされる可能性があり、非パイプライン作業中は state.json が存在しない場合があります。フェイルクローズは正当な操作をブロックしてしまいます。各フックは command -v jq を確認し、存在しない場合は exit 0 します。

なぜエージェントはハードコードされた sonnet ではなくユーザーが設定したモデルを継承するのか?

柔軟性とユーザーコントロールのためです。以前はすべてのエージェントがフロントマターに model: sonnet を指定しており、ユーザーの設定に関わらずすべてのパイプライン実行を Sonnet に強制していました。エージェントフロントマターから model: フィールドを削除することで、Claude Code はユーザーのアクティブなモデル(/model で選択したモデルまたは Claude Code のデフォルト)を使用します。特定のモデルを固定したいユーザーは、個別のエージェントフロントマターファイルに model: <name> を追加できます(BACKLOG #21 のエージェントごとの選択メカニズム)。コスト管理には、ユーザー自身のモデル設定が適切な手段です。

なぜオーケストレーターはコードを読まないのか?

トークン節約のためです。オーケストレーターが実装ファイルを読み取ると、各フェーズでコンテキストが増大し、推論品質が低下します。すべてのフェーズを通じて小さなアーティファクトファイル(合計約 500 行)のみを読み取ることで、オーケストレーターは高速かつ集中した状態を保ちます。

このルールは diff 出力にも拡張されます:レビューエージェント(Phase 6 の impl-reviewer、Phase 7 の comprehensive-reviewer)は、オーケストレーターが事前に計算して diff を挿入するのではなく、自身のエージェントコンテキスト内で git diff main...HEAD を自己実行します。diff はエージェントのコンテキストで消費され、オーケストレーターのコンテキストでは消費されません — トークン節約ルールが満たされます。

なぜインラインプロンプトではなく別個のエージェントファイルを使用するのか?

  1. 各エージェントが永続的でバージョン管理可能なシステムプロンプトを持つ
  2. エージェントは他のスキルから再利用できる
  3. モデルはフロントマターでエージェントごとに設定できる
  4. オーケストレーターの SKILL.md が小さいまま保たれる(インラインプロンプトありだと約 900 行になるところを約 500 行)

ガード移行パターン(シェル → Go MCP ハンドラー)

pre-tool-hook.sh に新しいガードが追加された場合(例:ルール 3a–3j)、同じ不変条件を mcp-server/tools/ の対応する Go MCP ツールハンドラー内でも強制する必要があります。

パターン:

  1. シェルフックpre-tool-hook.sh): bash/edit ツール呼び出しで発火するガード。ディスクの state.json を使用し、jq で読み取ります。bash コマンドを exit 2 でブロックします。このレイヤーは常にアクティブです — MCP サーバーがインストールされていない場合でも。

  2. Go ハンドラーtools/guards.go): MCP ツールが呼び出されたときに発火するガード。state.ReadState() からすでに読み込まれた state.State を読み取ります。ブロッキングガードは IsError=true を返し、非ブロッキング警告は "warning" JSON キーの下に含まれます。

2つのレイヤーは独立していて補完的です。MCP サーバーが使用されている場合、Go ハンドラーが最初に発火します。シェルフックはすべての Bash ツール呼び出しでも独立して発火します。

新しいガードを追加する際の移行チェックリスト:

  • [ ] pre-tool-hook.sh に名前付きチェック関数を追加する(ディスパッチブロックにインラインで記述しない)
  • [ ] mcp-server/tools/guards.go に対応する関数を追加する(ブロッキング:error を返す;警告:string を返す)
  • [ ] mcp-server/tools/handlers.go の関連ハンドラーから呼び出す
  • [ ] シェルガード(test-hooks.sh)と Go ガード(tools/guards_test.go)の両方にテストを追加する
  • [ ] ガードカタログに新しいルールを文書化する

なぜ analysis.md と investigation.md が別個のファイルなのか?

Phase 1–2 の2つの出力ファイルは異なる役割を持ち、それらの間には厳格なデータ依存関係があります:

  • analysis.md(Phase 1 — situation-analyst): コードベースの現状をマッピングします — 関連ファイル、インターフェース、型、データフロー、既存のテスト。何を変更すべきかについての意見のない読み取り専用の調査です。
  • investigation.md(Phase 2 — investigator): analysis.md の上に構築します — investigator エージェントは明示的な入力として analysis.md を読み取ってから、根本原因分析、エッジケース、リスク、外部依存関係、先行技術、曖昧点、削除/リネームの影響を追加します。

これらを1つのファイルにマージすると、このシーケンシャルな依存関係が壊れます:investigator は同じファイルを読み取りと書き込みの両方に使用するか、Phase 1 のコンテンツをプロンプトに挿入する必要があり(ファイルが API の原則に違反する)、ディスクに存在しなくなります。

分割が重要な追加の理由が4つあります:

  1. 再開のセマンティクス — 各ファイルは独立したフェーズチェックポイントです。Phase 2 が失敗した場合、Phase 1 の analysis.md はすでにディスクにあり、investigator は situation analyst を再実行せずにリトライできます。
  2. コンシューマーの粒度task-decomposer(Phase 4)は investigation.md のみを読み取り;architectdesign-reviewer は両方を読み取ります。別個のファイルにより、各コンシューマーは正確に必要なものだけを読み込めます。
  3. アーティファクトガードpipeline_report_result は Phase 1 完了時に analysis.md を、Phase 2 完了時に investigation.md を独立して検証します。単一のマージされたファイルでは、1つのガードが2つの異なるセクションを検証する必要があり、ガードのロジックがコンテンツ構造に結合されます。
  4. 調査フロー — パイプラインが調査として実行される(実装フェーズなし)場合、Phase 2 の後で終了し、両方のファイルを最終的な成果物としてユーザーに提示します。別個のファイルにすることで、出力が2つの名前付きドキュメントとしてナビゲートしやすくなります。

2ファイルへの分割は工数レベルに関係なく維持されます。両方のファイルが同じパイプライン実行で生成されますが、それらは異なる役割を果たし、異なるダウンストリームエージェントによって消費されます。

なぜ SKILL.md のクロスリファレンスにインラインコメントアンカーを使用するのか?

SKILL.md はレンダラーではなく、生のマークダウンを読む LLM によって消費されます。HTML アンカー(<a id="...">)はレンダリングビューでは非表示ですが、生テキストでは LLM に見えてしまいます。採用された規約は、ターゲットの見出しに <!-- anchor: <token> --> を追加し、すべての散文参照でそのトークン(見出しテキストではなく)を使用します。トークンは短く、小文字で、ハイフン区切りであり、grep anchor: で検索できます。これが SKILL.md の安定ラベル規約です。新しいクロスリファレンスセクションを追加する際は、このパターンに従ってください。

Released under the MIT License.