> ## Documentation Index
> Fetch the complete documentation index at: https://docs.befailproof.ai/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` を返した場合、各メッセージは改行で結合されて 1 つの `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` はそれ以降のカスタムポリシーの評価を短絡しますが、組み込みポリシーはすでに実行済みです。

***

## アクティビティログ

各フックイベントの後、ハンドラーは `~/.failproofai/hook-activity.jsonl` に 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・React Server Components・Server Actions を使用した **Next.js 16** アプリケーションです。

```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 ルーター（hook / dashboard / install など）
├── 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 ダッシュボード（ページ + Server Actions）
├── lib/                          # 共有ユーティリティ
│   ├── projects.ts               # ファイルシステムから Claude プロジェクトを列挙
│   ├── log-entries.ts            # Claude トランスクリプト JSONL 形式のパース
│   ├── paths.ts                  # システムパスの解決
│   └── ...
├── components/                   # 共有 React UI コンポーネント
├── contexts/                     # React コンテキストプロバイダー（テーマ、自動更新、テレメトリー）
├── examples/                     # カスタムフックのサンプルファイル
└── __tests__/                    # ユニットテストおよび E2E テスト
```
