> ## Documentation Index
> Fetch the complete documentation index at: https://exosphere-auto-translate-docs-20260624-1149.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# アーキテクチャ

> フックハンドラー、設定の読み込み、ポリシー評価の内部動作について

このドキュメントでは、failproofai の内部動作を説明します。フックシステムがエージェントのツール呼び出しをどのようにインターセプトするか、設定がどのように読み込まれてマージされるか、ポリシーがどのように評価されるか、そしてダッシュボードがエージェントのアクティビティをどのように監視するかについて解説します。

***

## 概要

failproofai には独立した 2 つのサブシステムがあります。

1. **フックハンドラー** - Claude Code がエージェントのツール呼び出しごとに起動する高速な CLI サブプロセス。ポリシーを評価して判断を返します。
2. **エージェントモニター（ダッシュボード）** - エージェントセッションの監視とポリシー管理のための Next.js ウェブアプリケーション。

両サブシステムは `~/.failproofai/` とプロジェクトの `.failproofai/` ディレクトリにある設定ファイルを共有しますが、それぞれ独立したプロセスとして動作し、ファイルシステムを通じてのみ通信します。

***

## フックハンドラー

### Claude Code との連携

`failproofai policies --install` を実行すると、`~/.claude/settings.json` に以下のようなエントリが書き込まれます。

```json theme={null}
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "failproofai --hook PreToolUse"
          }
        ]
      }
    ],
    "PostToolUse": [ ... ]
  }
}
```

Claude Code はツール呼び出しごとに `failproofai --hook PreToolUse` をサブプロセスとして起動し、JSON ペイロードを stdin で渡します。

### ペイロードの形式

```json theme={null}
{
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/myproject/sessions/abc123.jsonl",
  "cwd": "/home/user/myproject",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "sudo apt install nodejs" }
}
```

`PostToolUse` イベントの場合、ペイロードにはツールの出力を含む `tool_result` も含まれます。

ハンドラーは stdin の上限を 1 MB に制限しています。この上限を超えるペイロードは破棄され、すべてのポリシーが暗黙的に allow となります。

### レスポンスの形式

**Deny（PreToolUse）：**

```json theme={null}
{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "Blocked by failproofai: sudo command blocked"
  }
}
```

**Deny（PostToolUse）：**

```json theme={null}
{
  "hookSpecificOutput": {
    "additionalContext": "Blocked by failproofai because: API key detected in output"
  }
}
```

**Instruct（Stop 以外のイベント）：**

```json theme={null}
{
  "hookSpecificOutput": {
    "additionalContext": "Instruction from failproofai: Verify tests pass before committing."
  }
}
```

**Stop イベントの instruct：**

* 終了コード：`2`
* 理由は stdout ではなく stderr に書き込まれます

**Allow：**

* 終了コード：`0`
* stdout は空

**メッセージ付き Allow：**

`allow(message)` を使うと、操作が許可された場合でもポリシーから Claude に情報コンテキストを返すことができます。フックハンドラーは以下の JSON を **stdout** に書き込みます（設定ファイルへの書き込みではなく、deny や instruct と同様に Claude Code へのハンドラーレスポンスです）。

```json theme={null}
// フックハンドラープロセスが stdout に書き込む内容
{
  "hookSpecificOutput": {
    "additionalContext": "All CI checks passed on branch 'feat/my-feature'."
  }
}
```

* 終了コード：`0`（操作は許可される）
* 複数のポリシーがメッセージ付きで `allow` を返した場合、それらのメッセージは改行で結合されて単一の `additionalContext` 文字列になります
* いずれのポリシーもメッセージを提供しない場合、stdout は空（従来と同じ動作）

### 処理パイプライン

`src/hooks/handler.ts` がパイプライン全体を実装しています。

```text theme={null}
stdin JSON
  → ペイロードのパース（最大 1 MB）
  → セッションメタデータの抽出（session_id、cwd、tool_name、tool_input など）
  → readMergedHooksConfig(cwd)    ← プロジェクト + ローカル + グローバル設定をマージ
  → 有効な組み込みポリシーをパラメーター解決後に登録
  → customPoliciesPath からカスタムポリシーを読み込み（設定されている場合）
  → カスタムポリシーをポリシーレジストリに登録
  → すべてのポリシーを評価（組み込みを先に、その後カスタムを）
      → 最初の deny で短絡
      → instruct の判断は蓄積
      → allow メッセージは蓄積
  → JSON 判断を stdout に書き込み
  → イベントを ~/.failproofai/hook-activity.jsonl に永続化
  → 終了
```

LLM 呼び出しなしで、典型的なペイロードの処理は 100ms 以内に完了します。

***

## 設定の読み込み

`src/hooks/hooks-config.ts` が 3 スコープの設定読み込みを実装しています。

```text theme={null}
[1] {cwd}/.failproofai/policies-config.json        ← プロジェクト（最高優先度）
[2] {cwd}/.failproofai/policies-config.local.json  ← ローカル
[3] ~/.failproofai/policies-config.json             ← グローバル（最低優先度）
```

マージのロジック：

* `enabledPolicies` - 3 つのファイルにわたって重複排除した和集合
* `policyParams` - ポリシーキーごとに、最初に定義したファイルが完全に優先
* `customPoliciesPath` - 最初に定義したファイルが優先
* `llm` - 最初に定義したファイルが優先

ウェブダッシュボードはプロジェクトの cwd を持たないため、読み取りと書き込みには `readHooksConfig()`（グローバルのみ）を使用します。

***

## ポリシーの評価

`src/hooks/policy-evaluator.ts` がポリシーを順番に実行します。

各ポリシーについて：

1. ポリシーの `params` スキーマを参照します（存在する場合）。
2. マージ済み設定から `policyParams[policy.name]` を読み取ります。
3. ユーザー指定の値をスキーマのデフォルト値にマージして `ctx.params` を生成します。
4. 解決されたコンテキストを使って `policy.fn(ctx)` を呼び出します。
5. 結果が `deny` の場合、即座に停止してその判断を返します。
6. 結果が `instruct` の場合、メッセージを蓄積して処理を続行します。
7. 結果が `allow` の場合、次のポリシーへ進みます。

すべてのポリシーの実行後：

* `deny` が返された場合、deny レスポンスを出力します。
* `instruct` が収集された場合、すべてのメッセージを結合した単一の instruct レスポンスを出力します。
* それ以外の場合、allow レスポンス（stdout 空、終了コード 0）を出力します。

***

## 組み込みポリシー

`src/hooks/builtin-policies.ts` が 39 個の組み込みポリシーを `BuiltinPolicyDefinition` オブジェクトとして定義しています。

```typescript theme={null}
interface BuiltinPolicyDefinition {
  name: string;
  description: string;
  fn: (ctx: PolicyContext) => PolicyResult;
  match: {
    events: HookEventType[];
    tools?: string[];
  };
  defaultEnabled: boolean;
  category: string;
  beta?: boolean;
  params?: PolicyParamsSchema;
}
```

`params` を受け取るポリシーは、各パラメーターの型とデフォルト値を持つ `PolicyParamsSchema` を宣言します。ポリシー評価器は `fn` を呼び出す前に解決済みの値を `ctx.params` に注入します。デフォルト値は常に先に適用されるため、ポリシー関数は null チェックなしで `ctx.params` を読み取れます。

ポリシー内のパターンマッチングは生の文字列マッチングではなく、解析済みのコマンドトークン（argv）を使用します。これにより、シェル演算子インジェクションによるバイパスを防ぎます（例：`sudo systemctl status *` というパターンは、コマンドに `; rm -rf /` を追加しても迂回できません）。

***

## カスタムポリシー

`src/hooks/custom-hooks-registry.ts` が `globalThis` を使ったレジストリを実装しています。

```typescript theme={null}
const REGISTRY_KEY = "__failproofai_custom_hooks__";

export const customPolicies = {
  add(hook: CustomHook): void { ... }
};

export function getCustomHooks(): CustomHook[] { ... }
export function clearCustomHooks(): void { ... }  // テスト用
```

`src/hooks/custom-hooks-loader.ts` がユーザーのポリシーファイルを読み込みます。

1. 設定から `customPoliciesPath` を読み取り、存在しない場合はスキップします。
2. 絶対パスに解決してファイルの存在を確認します。
3. すべての `from "failproofai"` インポートを実際の dist パスに書き換え、`customPolicies` が同じ `globalThis` レジストリに解決されるようにします。
4. ESM 互換性を確保するため、推移的なローカルインポートを再帰的に書き換えます。
5. 一時的な `.mjs` ファイルを書き出し、エントリファイルを `import()` します。
6. `getCustomHooks()` を呼び出して登録済みフックを取得します。
7. `finally` ブロックですべての一時ファイルを削除します。

エラーが発生した場合（ファイルが見つからない、構文エラー、インポート失敗など）、エラーは `~/.failproofai/hook.log` に記録され、ローダーは空の配列を返します。組み込みポリシーへの影響はありません。

カスタムポリシーはすべての組み込みポリシーの後に評価されます。カスタムポリシーの `deny` はそれ以降のカスタムポリシーを短絡させますが（その時点ではすべての組み込みポリシーはすでに実行済みです）。

***

## アクティビティのログ記録

各フックイベントの後、ハンドラーは JSONL の 1 行を `~/.failproofai/hook-activity.jsonl` に追記します。

```json theme={null}
{
  "timestamp": "2026-04-06T12:34:56.789Z",
  "sessionId": "abc123",
  "eventType": "PreToolUse",
  "toolName": "Bash",
  "policyName": "block-sudo",
  "decision": "deny",
  "reason": "sudo command blocked by failproofai",
  "durationMs": 12
}
```

非 allow の判断をしたポリシーごとに 1 行記録されます。allow の判断はファイルサイズを抑えるため記録されません。

***

## ダッシュボードのアーキテクチャ

ダッシュボードは App Router を使用した **Next.js 16** アプリケーションで、React Server Components と Server Actions を採用しています。

```text theme={null}
app/
  layout.tsx                  ← ルートレイアウト（テーマ、テレメトリー、ナビゲーション）
  projects/page.tsx           ← サーバーコンポーネント：Claude プロジェクトの一覧
  project/[name]/page.tsx     ← サーバーコンポーネント：プロジェクト内のセッション一覧
  project/[name]/session/
    [sessionId]/page.tsx      ← サーバーコンポーネント：セッションビューアーの表示
  policies/page.tsx           ← クライアントコンポーネント：ポリシー管理 + アクティビティログ
  actions/
    get-hooks-config.ts       ← 設定とポリシー一覧の読み取り
    update-hooks-config.ts    ← ポリシーの有効/無効の切り替え
    update-policy-params.ts   ← ポリシーパラメーターの更新
    get-hook-activity.ts      ← アクティビティログのページネーション/検索
    install-hooks-web.ts      ← ブラウザからのフックのインストール/削除
  api/
    download/[project]/[session]/route.ts   ← CLI セッションごとのエクスポート（JSONL または JSON）
```

**データフロー：**

* ページコンポーネントは `lib/projects.ts` と `lib/log-entries.ts` を呼び出して、ファイルシステムから直接プロジェクト/セッションデータを読み取ります（読み取りに API レイヤーなし）。
* Policies ページはすべての変更操作（切り替え、パラメーター更新、インストール/削除）に Server Actions を使用します。
* セッションビューアーは Claude の JSONL トランスクリプト形式をパースし、メッセージとツール呼び出しのタイムラインを描画します。

**主要な設計方針：**

* データベースなし - すべての永続状態はプレーンファイル（`~/.failproofai/`、`~/.claude/projects/`）に保存
* 変更操作に Server Actions を使用 - CRUD 操作に REST API が不要
* 読み取りページに React Server Components を使用 - 初期表示が高速でデータフェッチのクライアントバンドルが不要
* インタラクティビティが必要な箇所にのみクライアントコンポーネントを使用（ポリシーの切り替え、アクティビティ検索、ログビューアー）

***

## ファイル構成

```text theme={null}
failproofai/
├── bin/
│   └── failproofai.mjs           # CLI ルーター（フック / ダッシュボード / インストール など）
├── src/hooks/
│   ├── handler.ts                # フックイベントパイプライン
│   ├── builtin-policies.ts       # 39 個のポリシー定義
│   ├── policy-evaluator.ts       # ポリシー実行エンジン
│   ├── policy-registry.ts        # ポリシーの登録と参照
│   ├── policy-types.ts           # TypeScript インターフェース
│   ├── hooks-config.ts           # マルチスコープ設定の読み込み
│   ├── custom-hooks-registry.ts  # globalThis を使ったフックレジストリ
│   ├── custom-hooks-loader.ts    # ユーザー JS フック用の ESM ローダー
│   ├── manager.ts                # インストール / 削除 / 一覧操作
│   ├── install-prompt.ts         # インタラクティブなポリシー選択プロンプト
│   ├── hook-logger.ts            # hook.log へのログ記録
│   ├── hook-activity-store.ts    # hook-activity.jsonl へのアクティビティ永続化
│   └── llm-client.ts             # LLM API クライアント（AI 駆動ポリシー用）
├── app/                          # Next.js ダッシュボード（ページ + サーバーアクション）
├── lib/                          # 共有ユーティリティ
│   ├── projects.ts               # ファイルシステムから Claude プロジェクトを列挙
│   ├── log-entries.ts            # Claude トランスクリプト JSONL 形式のパース
│   ├── paths.ts                  # システムパスの解決
│   └── ...
├── components/                   # 共有 React UI コンポーネント
├── contexts/                     # React コンテキストプロバイダー（テーマ、自動更新、テレメトリー）
├── examples/                     # カスタムフックのサンプルファイル
└── __tests__/                    # ユニットテストと E2E テスト
```
