claude-spotter 1.4.15 → 1.4.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,115 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.4.18
4
+
5
+ auditor model の更新を model 名の場当たり的な置換から切り離し、versioned policy と再現可能な比較 eval を
6
+ 導入する。反復評価24/24 exactの `gpt-5.6-terra × medium` をowner裁定でproductionへ昇格した。
7
+ `latest` aliasやCodex CLIの暗黙既定、失敗時fallback、eval artifactからの自動昇格は使わない。
8
+
9
+ ### 変更点
10
+
11
+ - **model policy**: 意味論的な auditor role、production selection、`gpt-5.6-luna × low` /
12
+ `gpt-5.6-terra × low` / `gpt-5.6-terra × medium` の評価 profile、policy version、検証状態を
13
+ 単一 module に集約した。medium追加時にpolicy versionを2、production昇格時に3へ上げた。
14
+ - **backend / diagnostics**: model と reasoning effort を backend 生成時に一度だけ解決し、成功・失敗の
15
+ structured result と diagnostics に effective selection、選択元、policy version、検証状態を残す。
16
+ model invocation が失敗しても別 model へ retry しない。
17
+ - **比較 eval**: `spotter auditor model-matrix` を追加した。同じ versioned fixture を固定順序で実行し、
18
+ fixture hash、Codex CLI version、schema / exact match、false positive / negative、p50 / p95、timeout、
19
+ catalog 外 name と anomaly を bounded artifact に記録する。Codex JSONL `turn.completed.usage` から
20
+ token数だけを抽出し、raw event本文は保存しない。ChatGPTプランの金額costはAPI価格で代用せず
21
+ `not-available-chatgpt-plan` とし、artifact自身がmodel昇格を許可することはない。
22
+ - **usage-limit diagnostics**: Codex CLIが実測済みの利用上限文言で非ゼロ終了した場合をgenericな
23
+ `E_CODEX_CLI_EXIT` から `E_CODEX_CLI_USAGE_LIMIT` へ分離した。認証失効を先に判定し、一般的な429は
24
+ 誤分類しない。Hookはリセット時刻まで待つかCodexプランを確認する復旧案内を表示し、fallbackは行わない。
25
+ - **model unavailable diagnostics**: Codex CLIが指定modelをChatGPTアカウントで利用できないと返した場合を
26
+ `E_CODEX_CLI_MODEL_UNAVAILABLE`へ分離した。認証・利用上限を優先し、providerのstdout/stderrはredact、
27
+ effective selectionとexit codeは診断用に保持する。別modelへのfallbackは行わない。
28
+ - **公式model更新監視**: OpenAI公式のlatest-model / ChatGPT pricing Markdownの両方に揃った完全3種familyだけを
29
+ 数値版比較し、週次workflowが評価提案Issueを重複なく作る。policy書換え・model呼出・自動昇格は行わない。
30
+ - **運用SLO / Stop実測**: UserPromptSubmit / Stopのp50・p95・timeout率と品質gateを日本語で正本化した。
31
+ CLIのStop continuationはmax-1を確認した一方、App background/app-server taskはStop非発火だったため、
32
+ active Appのblock挙動が未確認のまま既存契約を変えず、pending deliveryを維持する。
33
+
34
+ ### 検証
35
+
36
+ `auditor-model-matrix-cmd` / `auditor-cmd` / `cli` の targeted test 29 / 29 pass、全体
37
+ 439 / 437 pass / 0 fail / 2 skip。
38
+ 循環・非文字列 backend 応答、model selection 不一致、version 出力の秘密混入、dirty fixture、filtered
39
+ hallucination を敵対的に再検証し、blocker 0 を確認した。代表 fixture の repeat=1 operational smoke は
40
+ baseline / Luna / Terra の全12件が Codex CLI usage limit で `E_CODEX_CLI_EXIT` となったため、model 品質・
41
+ latency・availability の比較には使わなかった。Pro20回復後のrepeat=3を2回実行し、Terra lowは合算
42
+ 23/24 exactでbaseline 18/24、Luna low 17/24より最良。usage対応runではtoken usageを36/36取得した。
43
+ Terra lowにも見逃しが1件出たためmediumを追加評価した。Terra mediumは2回のrepeat=3で合計24/24 exact、
44
+ FP/FN 0、timeout 0となり、owner裁定でproductionへ採用した。ChatGPTプランの金額costは取得不能として
45
+ 明示した。採用runは全体p95 4.361秒で、stage別SLOとtimeout/workload感度も
46
+ [`docs/04_operational-slo.md`](https://github.com/kitepon-rgb/Spotter/blob/main/docs/04_operational-slo.md)へ固定した。
47
+
48
+ ## 1.4.17
49
+
50
+ v1.4.16 で実装済みだった stale Unix socket recovery を、v1.4.16 tag を改変せずこの patch の実配布候補に含める。
51
+ daemon 異常死後の orphan socket を次回起動前に安全に除去する回復経路が、初めて配布物へ入る。
52
+
53
+ ### 変更点
54
+
55
+ - **Codex SessionStart hook**: unsupported な `async:true` を除去し、現行 schema
56
+ `{type, command, timeout}` に合わせた。upgrade 時は旧 field を正規化し、他製品・他 hook の定義を保持する。
57
+ - **diagnostics / guidance**: readiness、registered、compatible、canonical、observed を分けて表示する。
58
+ 信頼状態が未知なら `/hooks` での確認を案内し、doctor / install の次手も明示する。
59
+ - **Claude Stop の失敗通知**: backend / transport failure を stage-aware な warning として same-session
60
+ pending に永続化し、次の prompt で配信する。finding / warning の永続化に失敗した場合は stderr と
61
+ `degraded` event を記録して non-blocking を維持する。最終 turn で次 prompt がない場合には配信できない
62
+ 限界は残る。
63
+ - **Codex used-tools**: 現行 `custom_tool_call`、legacy `function_call`、MCP、agents を current turn の
64
+ bounded tail から認識する。schema / missing / oversize の anomaly を記録して false skip を防ぎ、既存の
65
+ public API は維持する。
66
+ - **auditor model**: この release の production default は引き続き `gpt-5.4-mini × low`。GPT-5.6 の
67
+ policy / evaluation は次 patch で扱い、この patch での自動昇格はしない。
68
+
69
+ ### 検証
70
+
71
+ `1c67698` の clean worktree から npm pack / temp prefix install / CLI version / Hook install・reinstall を
72
+ smoke し、`node --test` は 383 / 381 pass / 0 fail / 2 skip。targeted test と adversarial review も
73
+ blocker 0。v1.4.17-only README / CHANGELOG を載せた local release candidate は `6ea6a2b`。
74
+ CLI help、58-entry pack、同じ full suite を再確認した。OS CI matrix はmacOS / Linux / Windows ×
75
+ Node 22.5.0 / 22.xの全6件がgreen。最終 SHA `7987f2a`をtag / npm / GitHub Releaseへ公開し、
76
+ npm `latest`とfresh global installの三者一致を確認した。
77
+
78
+ ## 1.4.16
79
+
80
+ **daemon が異常死した後、残った Unix socket で二度と起動できなくなり、そのセッションが永久に未監査になる
81
+ 回復経路バグを根治**。daemon が graceful shutdown を経ずに死ぬ (SIGKILL / crash / マシンスリープで
82
+ SessionEnd 未発火) と、`stop()` の socket unlink が走らず `~/.spotter/runtime/session-<id>.sock` が orphan
83
+ として残る。以後の auto-resurrect (v0.12.0) / SessionStart は `assertNoLiveDaemon` (PID 死亡を確認) を通過
84
+ して `server.listen(path)` に進むが、stale socket が残っているため `EADDRINUSE` で reject され、
85
+ `daemon listening` ログに到達する前に die。auto-resurrect は同じ socket パスへの再 bind を試みて毎回同じ
86
+ `EADDRINUSE` で crash-loop するため、そのセッションは会話を続けても永久に復活せず「Spotter 監査は一時無効
87
+ のまま」になっていた。実セッション (Kikoeru `83d7aa04`) の daemon ログで根本原因を確定: 16:26 正常起動 →
88
+ graceful shutdown ログなしの異常死 → 18:43–19:14 の 5 回 restart が全部 `tool-db loaded` /
89
+ `auditor backend selected` 直後で停止 (listen 未到達)、hook-events は毎ターン `E_UNREACHABLE` /
90
+ `E_RESURRECT_FAILED` の degraded。
91
+
92
+ ### 変更点
93
+
94
+ - **編集 [src/daemon/transport.mjs](src/daemon/transport.mjs)**: `removeStaleSocketFile(path)` を新設・
95
+ export。Unix domain socket の orphan ファイルを unlink する。ENOENT (stale ファイルなし = 通常の初回起動)
96
+ は no-op、それ以外のエラーは rethrow (§0: silent swallow 禁止)。Windows は Named Pipe が owner プロセス
97
+ 終了で自動消滅するため no-op。
98
+ - **編集 [src/daemon/daemon.mjs](src/daemon/daemon.mjs)**: `startDaemon` が `assertNoLiveDaemon`
99
+ (no-live 確認) 通過後・`server.listen(path)` 前に `removeStaleSocketFile(path)` を呼ぶ。liveness 確認後
100
+ なので socket は確実に orphan であり安全に消せる。
101
+ - **テスト 3 件追加 [test/transport.test.mjs](test/transport.test.mjs)**: (1) stale socket で `listen` が
102
+ `EADDRINUSE` で落ちる挙動を実再現 → `removeStaleSocketFile` 後に fresh daemon が bind & round-trip 成功、
103
+ (2) ENOENT は throw せず no-op、(3) Windows Named Pipe path は no-op (CI Windows matrix で実行)。
104
+ - **追記 [docs/open-issues.md](docs/open-issues.md)**: 解決済みテーブルに本バグを記録 + P0「daemon 突然死」
105
+ 節に回復経路修正の相互参照を追加。
106
+ - **付随**: `package-lock.json` の version が 1.4.14 のまま drift していたのを 1.4.16 に同期。
107
+ - `node --test` 348 / 346 pass / 0 fail / 2 skip 緑。
108
+
109
+ 設計の主旨: 異常死 (スリープ / 強制終了) は構造的に避けられない前提で、「死なせない」ではなく「死んでも次の
110
+ resurrect で確実に復活する」ことを保証する。これで auto-resurrect (v0.12.0) が stale socket に対しても
111
+ 初めて機能する。突然死の根因 (なぜ死ぬか) の観測は open-issues.md P0 で引き続き継続。
112
+
3
113
  ## 1.4.15
4
114
 
5
115
  **codex ログイン失効でサイレントに死に、host の Claude が無反応になる実害バグを根治**。codex auditor の
@@ -31,7 +141,7 @@
31
141
  - **編集 [src/hooks/pre-tool-use.mjs](src/hooks/pre-tool-use.mjs)**: daemon/transport エラーで
32
142
  `die(exit 2 = ツール拒否)` をやめ、`status:"degraded"` 記録 + exit 0 (ツール許可)。記録は best-effort
33
143
  telemetry でありツールを止める理由にならない。
34
- - **更新 [docs/SPOTTER_CLAUDE_CONTRACT.md](docs/SPOTTER_CLAUDE_CONTRACT.md)**: `UserPromptSubmit` /
144
+ - **更新 [docs/02_spotter-claude-contract.md](docs/02_spotter-claude-contract.md)**: `UserPromptSubmit` /
35
145
  `PreToolUse` / `Stop` の失敗時 exit-code 契約を新挙動に追従。
36
146
  - **追記 [docs/open-issues.md](docs/open-issues.md)**: auth-freeze バグの解決を記録。`Stop` 失敗が
37
147
  セッション最終ターンだと deferred-delivery の性質上サイレントになる残課題を P2 に追記。
@@ -320,7 +430,7 @@ v1.4.6 までは `SPOTTER_AUDITOR_BACKEND_POLICY=next` を Claude host で立て
320
430
  test、Claude+`next` で `SPOTTER_AUDITOR_BACKEND=haiku` 明示が依然として Haiku を選ぶ
321
431
  互換 test、`createAuditorBackend` factory が `auto` + Claude + `next` で Codex CLI backend を
322
432
  返す factory-level test を追加。
323
- - **編集 [docs/SPOTTER_CLAUDE_CONTRACT.md](docs/SPOTTER_CLAUDE_CONTRACT.md)** /
433
+ - **編集 [docs/02_spotter-claude-contract.md](docs/02_spotter-claude-contract.md)** /
324
434
  [docs/archive/SPOTTER_PRIMARY_BACKEND_TODO.md](docs/archive/SPOTTER_PRIMARY_BACKEND_TODO.md) /
325
435
  [docs/open-issues.md](docs/open-issues.md):
326
436
  Claude host の `current` / `next` policy 表と Phase 5 ゲート、Haiku compatibility が
@@ -840,11 +950,11 @@ v0.8.0 で claude.ai OAuth 系 MCP (Gmail / Calendar / Drive) を手書き basel
840
950
  - コマンド表の `spotter db refresh` コメント更新 (「組込み 遅延ツール」削除、v1.1.0 以降は通常不要な旨追記)、`spotter db rebuild` の挙動を local+global wipe に訂正
841
951
  - 設計ドキュメント節を 4 本立て (catalog-design / open-issues / CLAUDE.md §0 / spotter-plan 歴史記録) に再編
842
952
  - Haiku timeout 表記を v0.5.0 (30s) → v0.13.1 (45s) に訂正
843
- - **編集 [docs/catalog-design.md](docs/catalog-design.md)**:
953
+ - **編集 [docs/01_catalog-design.md](docs/01_catalog-design.md)**:
844
954
  - 新節「収集タイミング (v1.1.0 以降)」追加 — install 同期 seed / SessionStart bg refresh / db refresh / db rebuild の 4 経路を整理
845
955
  - 歴史節に v1.1.x の「収集タイミング自動化」を追記
846
956
  - **編集 [docs/archive/spotter-plan.md](docs/archive/spotter-plan.md)**:
847
- - 冒頭に「v0.1 時点の設計議事録」である旨のブリッジ追加、現行設計の真実源 (catalog-design.md / open-issues.md / CLAUDE.md) へのリンクを明示
957
+ - 冒頭に「v0.1 時点の設計議事録」である旨のブリッジ追加、現行設計の真実源 (01_catalog-design.md / open-issues.md / CLAUDE.md) へのリンクを明示
848
958
 
849
959
  ## 1.1.2
850
960
 
@@ -915,14 +1025,14 @@ Spotter の役割は「Bell にとって**言われないと思い出さない**
915
1025
 
916
1026
  ### 変更点
917
1027
 
918
- - **削除 [src/tool-db/deferred-baseline.mjs](src/tool-db/deferred-baseline.mjs)**: 手書き 17 件の baseline を撤去。`DEFERRED_TOOL_BASELINE` / `getDeferredDescription` / `listDeferredNames` の export も削除 (破壊変更)
1028
+ - **削除 `src/tool-db/deferred-baseline.mjs`**: 手書き 17 件の baseline を撤去。`DEFERRED_TOOL_BASELINE` / `getDeferredDescription` / `listDeferredNames` の export も削除 (破壊変更)
919
1029
  - **新規 [src/tool-db/frontmatter.mjs](src/tool-db/frontmatter.mjs)**: SKILL.md / agent .md の YAML frontmatter から `name` + `description` を抽出する最小パーサー (ゼロ依存)
920
1030
  - **新規 [src/tool-db/investigate-skills.mjs](src/tool-db/investigate-skills.mjs)**: スキルを 3 scope (user / project / 有効化プラグイン) から収集。プラグイン由来は `<plugin>:<skill>` に名前空間化、ユーザー / プロジェクト由来は素の名前。`enabledPlugins` の有効化判定は user scope + project scope の union、`~/.claude/plugins/installed_plugins.json` の `installPath` から実体にアクセス
921
1031
  - **新規 [src/tool-db/investigate-agents.mjs](src/tool-db/investigate-agents.mjs)**: サブエージェントを同じく 3 scope から収集。名前は素の名前、衝突は project > user > plugin の優先順で解決
922
1032
  - **編集 [src/tool-db/refresh.mjs](src/tool-db/refresh.mjs)**: `buildInvestigationSnapshot` から deferred 経路を削除、スキル / サブエージェント経路を追加。MCP live fetch + `claude.ai` baseline (Gmail / Calendar / Drive) は維持
923
1033
  - **編集 [src/cli/db-cmd.mjs](src/cli/db-cmd.mjs)**: `spotter db rebuild` が local DB に加えて **global DB も wipe** するように仕様変更。旧バージョンから上がってきたユーザーが古い deferred エントリを抱えたままにならないため
924
1034
  - **編集 [src/index.mjs](src/index.mjs)**: `listSkillsAll` / `listActivePlugins` / `listAgentsAll` の export 追加
925
- - **リネーム [docs/catalog-design-deferred-mcp.md](docs/catalog-design.md) → [docs/catalog-design.md](docs/catalog-design.md)**: 大幅書き直し。対象範囲・分類軸・収集経路を v1.0.0 仕様で更新
1035
+ - **リネーム [docs/catalog-design-deferred-mcp.md](docs/01_catalog-design.md) → [docs/01_catalog-design.md](docs/01_catalog-design.md)**: 大幅書き直し。対象範囲・分類軸・収集経路を v1.0.0 仕様で更新
926
1036
  - **テスト更新 [test/tool-db.test.mjs](test/tool-db.test.mjs)**: deferred baseline テスト 3 件削除、frontmatter テスト 3 件 + skill テスト 2 件 + agent テスト 2 件を追加。計 32 件全通過
927
1037
 
928
1038
  ### 結果
@@ -950,7 +1060,7 @@ preamble 初回送信サイズ推定 15-25K tokens (Haiku 4.5 の 200K コンテ
950
1060
 
951
1061
  ## 0.13.3
952
1062
 
953
- **カタログ外ツール名の推奨を遮断 (prompt 明示 + 事後 filter の二重防御)**。v0.13.2 リリース直後の実セッション ([daemon-f047521c.log](../../.spotter/logs/daemon-f047521c-9cce-4822-9555-90b206b8341e.log) line 9) で `turn_end: pass=false, missing=Skill(tl)` を観測。**`Skill(tl)` はカタログ (tool-db.json 57 件) に存在しない**。Haiku が training 記憶 or few-shot の `current_time` / `Skill` 表記から cargo-cult してカタログ外名を提案していた。これが恒常化するとユーザーが無効な推奨に混乱する + /tl など description を直しても Haiku は参照していないため修正が届かない、という構造問題になる。
1063
+ **カタログ外ツール名の推奨を遮断 (prompt 明示 + 事後 filter の二重防御)**。v0.13.2 リリース直後の実セッション (`daemon-f047521c.log` line 9) で `turn_end: pass=false, missing=Skill(tl)` を観測。**`Skill(tl)` はカタログ (tool-db.json 57 件) に存在しない**。Haiku が training 記憶 or few-shot の `current_time` / `Skill` 表記から cargo-cult してカタログ外名を提案していた。これが恒常化するとユーザーが無効な推奨に混乱する + /tl など description を直しても Haiku は参照していないため修正が届かない、という構造問題になる。
954
1064
 
955
1065
  ### 変更点
956
1066
 
@@ -972,7 +1082,7 @@ preamble 初回送信サイズ推定 15-25K tokens (Haiku 4.5 の 200K コンテ
972
1082
 
973
1083
  ## 0.13.2
974
1084
 
975
- **Daemon の死因を必ずログに残す診断インフラ + Haiku 子プロセス stdio の防御的 error listener**。v0.13.1 までは daemon が `uncaughtException` / `unhandledRejection` で死ぬと痕跡ゼロで消えていた ([daemon-80b5c0af.log](../../.spotter/logs/daemon-80b5c0af-700f-47af-a3ac-796144823a7d.log) line 15 → line 16 で shutdown ログなしに再起動)。次に同じことが起きた時に真因を必ず捕まえられるよう、診断 handler を導入。
1085
+ **Daemon の死因を必ずログに残す診断インフラ + Haiku 子プロセス stdio の防御的 error listener**。v0.13.1 までは daemon が `uncaughtException` / `unhandledRejection` で死ぬと痕跡ゼロで消えていた (`daemon-80b5c0af.log` line 15 → line 16 で shutdown ログなしに再起動)。次に同じことが起きた時に真因を必ず捕まえられるよう、診断 handler を導入。
976
1086
 
977
1087
  ### 監査の経過と結論
978
1088
 
@@ -988,11 +1098,11 @@ preamble 初回送信サイズ推定 15-25K tokens (Haiku 4.5 の 200K コンテ
988
1098
  ### 残課題
989
1099
 
990
1100
  - daemon 突然死の真因特定: **次回再現を待つ**。診断 handler が入ったので、次に死亡した時はログに必ず痕跡が残る。それを見て対処する
991
- - v0.13.1 で 30s → 45s 緩和した Haiku timeout の効果観測は継続: 現セッション [daemon-69bd2b93.log](../../.spotter/logs/daemon-69bd2b93-ffbe-43bc-94e7-1d0ba2bd9e74.log) line 5 で `mode=first, duration_ms=32703` を観測 (30s 設定なら timeout していた値が 45s で生存)。サンプル 1 件で結論はまだ早い
1101
+ - v0.13.1 で 30s → 45s 緩和した Haiku timeout の効果観測は継続: 現セッション `daemon-69bd2b93.log` line 5 で `mode=first, duration_ms=32703` を観測 (30s 設定なら timeout していた値が 45s で生存)。サンプル 1 件で結論はまだ早い
992
1102
 
993
1103
  ## 0.13.1
994
1104
 
995
- **Haiku timeout 30s → 45s 緩和 + hook 側 IPC timeout を整合**。v0.13.0 以前の実セッション ([daemon-80b5c0af.log](../../.spotter/logs/daemon-80b5c0af-700f-47af-a3ac-796144823a7d.log) line 15) で `E_HAIKU_TIMEOUT: haiku did not respond within 30000ms` を観測。同ログ line 20 でも `mode=first, duration_ms=20948` と 30s の 70% 域まで達しており、timeout が実測レイテンシに対して狭すぎた。合わせて [src/hooks/stop.mjs](src/hooks/stop.mjs) の IPC timeout が元々 15s で Haiku 側 30s と整合していなかった既存バグ (turn_end で Haiku が 16s 超かかると hook 側が先に諦めていた) も同時解消。
1105
+ **Haiku timeout 30s → 45s 緩和 + hook 側 IPC timeout を整合**。v0.13.0 以前の実セッション (`daemon-80b5c0af.log` line 15) で `E_HAIKU_TIMEOUT: haiku did not respond within 30000ms` を観測。同ログ line 20 でも `mode=first, duration_ms=20948` と 30s の 70% 域まで達しており、timeout が実測レイテンシに対して狭すぎた。合わせて [src/hooks/stop.mjs](src/hooks/stop.mjs) の IPC timeout が元々 15s で Haiku 側 30s と整合していなかった既存バグ (turn_end で Haiku が 16s 超かかると hook 側が先に諦めていた) も同時解消。
996
1106
 
997
1107
  ### 調査で判明した Haiku 4.5 の高速化ダイヤル不在
998
1108
 
@@ -1181,7 +1291,7 @@ v0.7.0 リリース直後、新規セッションで `spotter db refresh` を打
1181
1291
 
1182
1292
  Haiku が「Bell が呼び忘れているツール」を判定するには、Bell が今のセッションで実際に呼べるツールを知っている必要がある。v0.6.x までのカタログは `current_time` / `web_search` / `read_file` のような **抽象的な汎用ツール 5 件** を手書きしていただけで、Caveat や Gmail のような MCP ツール、TodoWrite や WebSearch のような Claude Code 組込みの遅延ツールは Haiku の視野に入っていなかった。結果、ユーザーが「過去に解決したナレッジを残したい」と言っても Spotter は Caveat を推奨できないという的外れな状態だった。
1183
1293
 
1184
- 設計思想は [docs/catalog-design-deferred-mcp.md](docs/catalog-design-deferred-mcp.md) に集約。要点:
1294
+ 設計思想は現行の [docs/01_catalog-design.md](docs/01_catalog-design.md) に引き継ぎ。要点:
1185
1295
 
1186
1296
  - **Haiku に渡すのは name + description のペアだけ**。schema は不要 — どう呼ぶかは Bell が ToolSearch で解決する責任 (役割分業)
1187
1297
  - **MCP ツールの description は MCP サーバーから直接取得**。Spotter は中継者に徹し、手書きで言い換えない (single source of truth = MCP server)
@@ -1194,7 +1304,7 @@ Haiku が「Bell が呼び忘れているツール」を判定するには、Bel
1194
1304
  - **新規 [src/tool-db/loader.mjs](src/tool-db/loader.mjs)**: JSON DB の atomic 読み書き、`{version, tools: {name → description}}` スキーマ検証
1195
1305
  - **新規 [src/tool-db/lookup.mjs](src/tool-db/lookup.mjs)**: 3 段階 lookup + write-through + drift 補正
1196
1306
  - **新規 [src/tool-db/investigate-mcp.mjs](src/tool-db/investigate-mcp.mjs)**: `claude mcp list` / `claude mcp get` で MCP サーバー列挙、stdio サーバーに JSON-RPC で `initialize` + `tools/list` を実行して description 取得
1197
- - **新規 [src/tool-db/deferred-baseline.mjs](src/tool-db/deferred-baseline.mjs)**: Claude Code 組込み 遅延ツール (WebSearch / TodoWrite / 等 17 件) の手書き description ベースライン (Claude Code 自体は MCP 経由で query できないため)
1307
+ - **新規 `src/tool-db/deferred-baseline.mjs`**: Claude Code 組込み 遅延ツール (WebSearch / TodoWrite / 等 17 件) の手書き description ベースライン (Claude Code 自体は MCP 経由で query できないため)
1198
1308
  - **新規 [src/tool-db/refresh.mjs](src/tool-db/refresh.mjs)**: 投資 = 利用可能ツール一覧取得 + 各ツールを 3 段階解決 + DB 書き戻し
1199
1309
  - **新規 [src/cli/db-cmd.mjs](src/cli/db-cmd.mjs)**: `spotter db list` / `refresh` / `rebuild`
1200
1310
  - **編集 [src/daemon/daemon.mjs](src/daemon/daemon.mjs)**: `loadCatalog` 廃止、`startDaemon({ projectRoot })` で tool-db を読み込み (テスト用に `tools` 直接指定も可)
@@ -1203,7 +1313,7 @@ Haiku が「Bell が呼び忘れているツール」を判定するには、Bel
1203
1313
  - **編集 [src/cli/install.mjs](src/cli/install.mjs)**: `tool-catalog/` 作成と template コピー削除、install 完了時に `spotter db refresh` 実行を案内
1204
1314
  - **編集 [src/cli/doctor.mjs](src/cli/doctor.mjs)**: catalog チェック → tool-db (global + local) のチェック
1205
1315
  - **編集 [bin/spotter.mjs](bin/spotter.mjs)**: `spotter catalog edit/lint` を `spotter db list/refresh/rebuild` に置換
1206
- - **削除**: [src/catalog/](src/catalog/), [src/cli/catalog.mjs](src/cli/catalog.mjs), `templates/tools.yaml`, `test/catalog.test.mjs`, `test/loader.test.mjs`
1316
+ - **削除**: `src/catalog/`, `src/cli/catalog.mjs`, `templates/tools.yaml`, `test/catalog.test.mjs`, `test/loader.test.mjs`
1207
1317
  - **新規 [test/tool-db.test.mjs](test/tool-db.test.mjs)**: 21 件 (loader/lookup/investigate-mcp/deferred-baseline)
1208
1318
 
1209
1319
  ### Breaking
package/README.ja.md CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  Claude には「使えるツールがあるのに、使うべきタイミングで使わない」という構造的な弱点があります。記録すべき決定を memory / caveat MCP に残さない、docs lookup MCP を呼ばずに古い知識で応答する、ブラウザ自動化 MCP で確認せず UI 状態を推測する — **「分からないと自覚できない」から、ツールを取りに行けない**。
17
17
 
18
- Spotter はツールカタログを完全に把握した別エージェント (Claude Haiku 4.5) をセッション毎に常駐させ、Claude の発話予定と応答を並走監査します。見落としを検出すると透明化された指摘として Claude に届け、補正応答を促します。**Claude が自覚して呼ぶ**設計は本プロダクトの存在意義を破壊するため、Claude から呼ぶのではなく hook 経由で Claude の意思と独立に検出する構造を取っています。
18
+ Spotter はツールカタログを完全に把握した別の監査エージェントで、ユーザー入力と主役 AI の応答を並走監査します。自動選択では Claude host は Codex CLI があればそれを、なければ session-scoped Haiku を選び、Codex host は Codex CLI を既定にします。明示 backend override は host より優先しますが、runtime failure で別 backend へ黙って切り替えません。見落としは透明化された指摘として次に利用できる文脈へ届けます。**主役 AI が自覚して自己監査する**設計は本プロダクトの存在意義を破壊するため、hook 経由でその意思と独立に検出します。
19
19
 
20
20
  <p align="center">
21
21
  <img src=".github/concept.svg" alt="Claude が答え、Spotter が見ている" width="80%">
@@ -59,6 +59,7 @@ Homebrew で Node が更新されても Codex hook が古い Node パスに取
59
59
  `v0.3.0` 以降は**プロジェクト単位の明示的 install** を採用しています (v0.2 までの `postinstall` 自動登録はデーモン増殖の主因だったため撤回)。各プロジェクトの `.claude/settings.json` に hook を登録し、そのプロジェクトでの Claude Code セッションのみで有効になります。
60
60
  Codex CLI が使える環境では、同じ `spotter install` が user-level の Codex native hooks も登録します。実際に動くプロジェクトは `spotter install` が作る `.spotter/marker.json` で制限されるため、無関係な Codex セッションでは Spotter は起動しません。
61
61
  Codex 側では現行の `[features].hooks = true` を有効化し、互換のため旧 `codex_hooks` diagnostics output も認識します。
62
+ Spotter が所有する Codex handler は現行の同期 command schema で生成します。install / upgrade 後は `/hooks` で review して新しい Codex session を開いてください。`spotter codex-hook diagnostics` は登録と readiness を診断しますが、trust を内部状態から推測しません。
62
63
 
63
64
  Spotter を upgrade した後、release note で hook 設定変更が案内されている場合は、各 install 済みプロジェクトで `spotter install` を再実行してください。global package update でコード経路は変わりますが、既存 `.claude/settings.json` の timeout 値は自動では書き換わりません。
64
65
 
@@ -80,8 +81,8 @@ spotter codex-hook install
80
81
 
81
82
  - **Node.js 22.5 以上**
82
83
  - **Claude Code 2.0 以上**
83
- - **現行 Claude-backed auditor path では Claude Max プラン** (`claude -p` で Haiku を起動するため)
84
- - **Codex native hooks では Codex CLI**。Codex host の監査は既定で `codex exec` を使い、Haiku へ fallback しません
84
+ - **Codex CLI**。Codex native hooks の既定 backend と Claude host の優先 auditor path で使います。自動選択後の runtime failure で Haiku へ fallback しません
85
+ - **Claude Max プラン**は Claude host が Haiku path を選ぶ場合だけ必要です(Codex CLI 不在、または `SPOTTER_AUDITOR_BACKEND=haiku` 明示時)
85
86
 
86
87
  ## アーキテクチャ
87
88
 
@@ -126,7 +127,7 @@ flowchart LR
126
127
  SK --> DB
127
128
  AG --> DB
128
129
  BL --> DB
129
- DB --> H[Haiku 監査<br/>session-scoped, preamble-once]
130
+ DB --> H[独立 auditor<br/>Codex CLI があれば優先<br/>なければ session-scoped Haiku]
130
131
  ```
131
132
 
132
133
  監査対象のツール (name + description) は host-local に分離されます。Claude は `<project>/.spotter/tool-db.json`、Codex は `<project>/.spotter/tool-db.codex.json` を使います。**daemon が監査に使うのは Claude local DB のみ**で、Codex native hooks は Codex local DB を読みます。グローバル description cache も host ごとに分離され、Claude は `~/.spotter/tool-db.json`、Codex は `~/.spotter/tool-db.codex.json` を使います。これらは同じ host の他プロジェクト間でだけ再利用され、監査入力には混ぜません。各 host-local DB は **その host の現時点の discovery 結果と一致** (refresh 時に prune される) するため、別プロジェクトや別 host のツールリストで上書きされることはありません。
@@ -170,7 +171,9 @@ spotter codex work --findings findings.json --instruction "docs 更新" --approv
170
171
  spotter codex-hook install
171
172
  # Codex native hooks の修復 / 明示登録 (通常は spotter install が実行)
172
173
  spotter codex-hook diagnostics
173
- # Codex hooks feature と Spotter hook 登録を診断
174
+ # Codex hook の登録/readiness を診断。trust は /hooks で review
175
+ spotter auditor model-matrix --fixtures test/fixtures/auditor-model-matrix.v1.json
176
+ # pinned auditor model profile を再現可能に比較する experimental eval
174
177
  spotter uninstall # hook 登録を解除 (~/.spotter は残す)
175
178
  ```
176
179
 
@@ -184,31 +187,37 @@ SPOTTER_CODEX_RISK_CHECK=1 spotter daemon start --session-id ... --project-root
184
187
  `spotter codex risk-check` に渡します。hook 応答は Codex を待ちません。
185
188
  配線だけ確認する場合は `SPOTTER_CODEX_RISK_CHECK_DRY_RUN=1` を併用します。
186
189
 
187
- Primary auditor backend policy: Claude hooks は現行の Haiku compatibility path を既定のまま維持します。
188
- Codex native hooks は Codex CLI を既定 backend とし、Haiku へ fallback しません。
190
+ Primary auditor backend policy: Claude hooks の auto selection は PATH に Codex CLI があれば Codex CLI、
191
+ なければ Haiku compatibility path。Codex native hooks の auto selection は Codex CLI です。
192
+ `SPOTTER_AUDITOR_BACKEND` の明示 override はどちらの host でも優先し、runtime failure では別 backend へ
193
+ hidden fallback しません。
189
194
  Codex 側の SessionStart hook は `.spotter/tool-db.codex.json` を bg refresh し、Claude DB には触れません。
190
- Codex CLI auditor の子プロセスは、hook 判定を安く速く保つため既定で `gpt-5.4-mini` と
191
- `model_reasoning_effort="low"` を明示指定します。実測や制御された実験では
192
- `SPOTTER_CODEX_CLI_MODEL` / `SPOTTER_CODEX_CLI_REASONING_EFFORT` で上書きできます。
195
+ Codex CLI auditor は versioned product policy を使い、production は反復 fixture 評価を通過した
196
+ `gpt-5.6-terra × medium`。`gpt-5.6-luna × low` / `gpt-5.6-terra × low` は比較 profile として残し、
197
+ profile から production へ自動昇格しません。`latest` alias や
198
+ 親 Codex の default を暗黙継承せず、失敗時に別 model へ retry しません。制御された実験では
199
+ `SPOTTER_CODEX_CLI_MODEL` / `SPOTTER_CODEX_CLI_REASONING_EFFORT` で上書きでき、diagnostics は unverified と表示します。
193
200
  明示 smoke には `SPOTTER_AUDITOR_BACKEND=codex-sidecar` も使えます。
194
201
 
195
202
  ## 設計ドキュメント
196
203
 
197
- - **現行設計 (カタログ / 収集経路 / 分類軸)**: [docs/catalog-design.md](docs/catalog-design.md) — v1.0.0 以降の真実源
204
+ - **現行設計 (カタログ / 収集経路 / 分類軸)**: [docs/01_catalog-design.md](docs/01_catalog-design.md) — v1.0.0 以降の真実源
198
205
  - **現時点で塞がっていない穴 + 実測未検証の懸念**: [docs/open-issues.md](docs/open-issues.md) — 新規作業に入る前に必読
199
- - **Runtime contract**: [docs/SPOTTER_CLAUDE_CONTRACT.md](docs/SPOTTER_CLAUDE_CONTRACT.md) — Claude hook / daemon / Haiku 契約と Codex native hook policy
206
+ - **Runtime contract**: [docs/02_spotter-claude-contract.md](docs/02_spotter-claude-contract.md) — Claude hook / daemon / Haiku 契約と Codex native hook policy
200
207
  - **実装規範と不変条件 (§0)**: [CLAUDE.md](CLAUDE.md) — フォールバック禁止 / silent fallback 禁止 / 暫定コード禁止
201
208
  - **Archive**: [docs/archive/](docs/archive/) — 完了済み Codex rollout 計画、primary backend smoke log、v0.1 設計議事録
202
209
 
203
210
  ## 既知の制約
204
211
 
205
- - v1.4.8 以降、Claude / Codex 両 host で `Stop` hook は **遅延配送 (deferred delivery)** に統一されました。`Stop` で見落としツールを検出した場合、Spotter は `<projectRoot>/.spotter/pending/<sessionId>.json` に指摘を積み、次の same-session `UserPromptSubmit` で `additionalContext` として配信します。当ターンの最初の応答は transcript にそのまま残るため、`decision:"block"` で補正サイクルを回す方式の「最終応答が補正中心になって元の文脈が迷子」問題が解消します (Codex 側は `Stop Blocked` / exit code 1 回避も兼ねる)
212
+ - v1.4.8 以降、Claude / Codex 両 host で `Stop` hook は **遅延配送 (deferred delivery)** に統一されています。`Stop` で見落としツールを検出した場合、Spotter は `<projectRoot>/.spotter/pending/<sessionId>.json` に指摘を積み、次の same-session `UserPromptSubmit` で `additionalContext` として配信します。当ターンの最初の応答は transcript にそのまま残ります
206
213
  - pending ファイルは Claude / Codex が同じパス (`.spotter/pending/`) を共有します。host-neutral 設計です
207
- - **JSON スキーマ違反は v0.5.0 以降「想定済み異常」として silent pass + session renew で回復**します (role collapse 検知パス、daemon ログに `role_collapse_reset` を残す)。一方 **Haiku timeout は引き続き throw** され、UserPromptSubmit がブロックされてユーザー入力が Claude に届かない症状として顕在化します (timeout は v0.5.0 で 30s、v0.13.1 で 45s に拡張)。timeout の fail-open 化 (pass 扱い) は §0 改訂とセットで今後検討
214
+ - **Haiku の JSON スキーマ違反は v0.5.0 以降「想定済み異常」として session renew + `role_collapse_reset` で回復**します。**v1.4.15 以降、auditor/daemon の失敗はプロンプトをブロックしません**: `UserPromptSubmit` は `[Spotter からの警告]` を出して exit 0。`Stop` 失敗も warning pending に積み、次の same-session prompt で1回配信します。直後に session が終わる場合だけ、配送先となる次 prompt がありません
208
215
 
209
216
  <details>
210
217
  <summary><strong>📋 最近のハイライト</strong></summary>
211
218
 
219
+ - **daemon は異常死しても復活する** (v1.4.16) — daemon が graceful shutdown を経ず死んでも (マシンスリープ / 強制終了 / `SessionEnd` 前の crash)、残った Unix socket が以後の起動を塞がなくなった。`startDaemon` が bind 前に orphan socket を除去するので、次の `UserPromptSubmit` の auto-resurrect が `EADDRINUSE` で crash-loop せずに成功し、「そのセッションが永久に未監査」になる事態を防ぐ
220
+ - **失敗は声に出して縮退、host を固めない** (v1.4.15) — auditor backend が失敗したとき (例: codex のログイン失効) も、`UserPromptSubmit` hook はプロンプトを黙って消さずに `[Spotter からの警告]` を出して通す。codex ログイン失効時は直し方 (`codex login`) を明示する
212
221
  - **プラグイン形式の MCP サーバー対応** — `plugin:everything-claude-code:context7` のように名前に内部コロンを含むサーバーを正しくパースし、配下のツールをカタログに取り込めるようになった (旧版はこの形式のサーバーをすべて単一の `"plugin"` に潰して、Claude の監査から silent に脱落させていた)
213
222
  - **プロジェクト単位の監査隔離** — daemon が監査に使うのはローカル DB のみ。グローバル DB は description 再利用キャッシュに役割限定。**他プロジェクト**でインストールしたツールが現プロジェクトの監査に混入することはない
214
223
  - **手放しでカタログ維持** — `spotter install` が Claude DB を自動 seed、Claude / Codex それぞれの SessionStart が host-local DB を bg refresh する。手書き管理は一切不要
package/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  Claude has a structural blind spot: **it can't reach for a tool it doesn't realize it needs**. It may skip a project memory MCP when a decision should be recorded, answer from stale memory instead of a docs-lookup MCP, or reason about UI state without a browser-automation MCP. The model can't always tell when it doesn't know — so the tool stays unused.
17
17
 
18
- Spotter pins a second agent (Claude Haiku 4.5) next to Claude. The second agent has the full tool catalog memorized and audits both the user's prompt and Claude's reply in parallel. When it spots a missed tool, it injects a transparent recommendation into Claude's context and, if needed, asks Claude to amend its answer. **Claude is never asked to self-audit** — that would defeat the entire premise. Detection happens through hooks, independent of Claude's intent.
18
+ Spotter runs a separate auditor with the full tool catalog and checks both the user's prompt and the primary agent's reply. Automatic selection uses Codex CLI on a Claude host when available, otherwise the session-scoped Haiku path; on a Codex host it defaults to Codex CLI. An explicit backend override takes precedence, but a runtime failure never silently switches backend. When Spotter finds a missed tool, it injects a transparent recommendation into the next available context. **The primary agent is never asked to self-audit** — that would defeat the premise. Detection happens through hooks, independent of the primary agent's intent.
19
19
 
20
20
  <p align="center">
21
21
  <img src=".github/concept.svg" alt="Claude answers · Spotter watches" width="80%">
@@ -59,6 +59,7 @@ Codex hooks working across Homebrew Node upgrades.
59
59
  Since `v0.3.0`, Spotter requires **explicit per-project install** (the earlier `postinstall` auto-registration was the leading cause of orphan daemons). `spotter install` writes hooks into the project's `.claude/settings.json`; the audit is then active only in Claude Code sessions for that project.
60
60
  When the Codex CLI is available, the same `spotter install` also registers user-level Codex native hooks. Project activation still depends on the same per-project `.spotter/marker.json`, so unrelated Codex sessions do not trigger Spotter.
61
61
  For Codex, install enables the current `[features].hooks = true` flag and still recognizes older `codex_hooks` diagnostics output for compatibility.
62
+ Installer-owned Codex handlers use the current synchronous command schema. After install or upgrade, review them with `/hooks`, then open a fresh Codex session; `spotter codex-hook diagnostics` reports registration/readiness but does not guess hook trust.
62
63
 
63
64
  After upgrading Spotter, re-run `spotter install` in each installed project when release notes mention hook setting changes. The global package update changes the code path, but existing `.claude/settings.json` timeout values are not rewritten automatically.
64
65
 
@@ -80,8 +81,8 @@ spotter codex-hook install
80
81
 
81
82
  - **Node.js 22.5+**
82
83
  - **Claude Code 2.0+**
83
- - **Claude Max plan** for the current Claude-backed auditor path (Spotter spawns Haiku via `claude -p`)
84
- - **Codex CLI** for Codex native hooks. Codex host auditing uses `codex exec` by default and does not fall back to Haiku
84
+ - **Codex CLI** for the default Codex-native backend and the preferred Claude-host auditor path. The auto-selected Codex backend does not fall back to Haiku after a runtime failure
85
+ - **Claude Max plan** only when a Claude host selects the Haiku path (Codex CLI is absent or `SPOTTER_AUDITOR_BACKEND=haiku` is explicit)
85
86
 
86
87
  ## Architecture
87
88
 
@@ -126,7 +127,7 @@ flowchart LR
126
127
  SK --> DB
127
128
  AG --> DB
128
129
  BL --> DB
129
- DB --> H[Haiku audit<br/>session-scoped, preamble-once]
130
+ DB --> H[Independent auditor<br/>Codex CLI when available<br/>otherwise session-scoped Haiku]
130
131
  ```
131
132
 
132
133
  The audited catalog is host-local: Claude uses `<project>/.spotter/tool-db.json`, while Codex uses `<project>/.spotter/tool-db.codex.json`. **The daemon audits against the Claude local DB only**, and Codex native hooks read the Codex local DB. Global description caches are host-specific too: Claude uses `~/.spotter/tool-db.json`, while Codex uses `~/.spotter/tool-db.codex.json`. They are shared only across projects for the same host and are never audit sources. Each host-local DB matches that host's **current** discovery snapshot for the project (stale entries are pruned on refresh), so tools from another project or another host cannot overwrite this session's audit catalog.
@@ -171,7 +172,9 @@ spotter codex work --findings findings.json --instruction "Update docs" --approv
171
172
  spotter codex-hook install
172
173
  # repair / explicitly register Codex native hooks (normally handled by spotter install)
173
174
  spotter codex-hook diagnostics
174
- # check Codex hooks feature and Spotter hook entries
175
+ # check Codex hook registration/readiness; trust is reviewed with /hooks
176
+ spotter auditor model-matrix --fixtures test/fixtures/auditor-model-matrix.v1.json
177
+ # experimental reproducible comparison of pinned auditor model profiles
175
178
  spotter uninstall # remove hooks from this project (leaves ~/.spotter intact)
176
179
  ```
177
180
 
@@ -185,33 +188,38 @@ When enabled, the daemon dispatches `pass:false` findings to `spotter codex risk
185
188
  in a detached process. Hook responses do not wait for Codex. Add
186
189
  `SPOTTER_CODEX_RISK_CHECK_DRY_RUN=1` to exercise the wiring without calling Codex.
187
190
 
188
- Primary auditor backend policy: Claude hooks keep the current Haiku-compatible path by
189
- default. Codex native hooks use Codex CLI by default and do not fall back to Haiku;
190
- their SessionStart hook refreshes `.spotter/tool-db.codex.json` in the background
191
+ Primary auditor backend policy: Claude hooks automatically select Codex CLI when it is available on PATH,
192
+ otherwise the Haiku-compatible path. Codex native hooks automatically select Codex CLI. An explicit
193
+ `SPOTTER_AUDITOR_BACKEND` override wins on either host; runtime failure never triggers a hidden fallback.
194
+ The Codex SessionStart hook refreshes `.spotter/tool-db.codex.json` in the background
191
195
  without touching the Claude DB.
192
- Codex CLI auditor child processes explicitly use `gpt-5.4-mini` with
193
- `model_reasoning_effort="low"` by default so hook checks stay cheap and fast;
196
+ Codex CLI auditor child processes use a versioned product policy. The production selection is
197
+ `gpt-5.6-terra × medium`, promoted after repeated fixture evaluation. `gpt-5.6-luna × low` and
198
+ `gpt-5.6-terra × low` remain comparison profiles; profiles never trigger automatic upgrades.
199
+ Spotter does not inherit a `latest` alias or the parent Codex default, and an invocation failure never retries another model.
194
200
  `SPOTTER_CODEX_CLI_MODEL` and `SPOTTER_CODEX_CLI_REASONING_EFFORT` can override
195
- those values for smoke tests or controlled experiments.
201
+ the production values for controlled experiments; diagnostics mark overrides as unverified.
196
202
  `SPOTTER_AUDITOR_BACKEND=codex-sidecar` is available for explicit sidecar auditor smoke.
197
203
 
198
204
  ## Design docs
199
205
 
200
- - **Current design** (catalog, discovery, classification axes): [docs/catalog-design.md](docs/catalog-design.md) — source of truth from v1.0.0
206
+ - **Current design** (catalog, discovery, classification axes): [docs/01_catalog-design.md](docs/01_catalog-design.md) — source of truth from v1.0.0
201
207
  - **Open issues + unverified concerns**: [docs/open-issues.md](docs/open-issues.md) — read this before starting new work
202
- - **Runtime contract**: [docs/SPOTTER_CLAUDE_CONTRACT.md](docs/SPOTTER_CLAUDE_CONTRACT.md) — Claude hook / daemon / Haiku contract plus Codex native hook policy
208
+ - **Runtime contract**: [docs/02_spotter-claude-contract.md](docs/02_spotter-claude-contract.md) — Claude hook / daemon / Haiku contract plus Codex native hook policy
203
209
  - **Implementation invariants (§0)**: [CLAUDE.md](CLAUDE.md) — no fallbacks, no silent failures, no provisional code
204
210
  - **Archived plans and history**: [docs/archive/](docs/archive/) — completed Codex rollout plans, primary backend smoke logs, and the frozen v0.1 design discussion
205
211
 
206
212
  ## Known limitations
207
213
 
208
- - The `Stop` hook fires **after** Claude's first answer has already been streamed to the user. When Spotter sends Claude back, the user sees both the original answer and the corrected one. Detection accuracy in `UserPromptSubmit` (the *pre-response* stage) is therefore Spotter's primary axis of quality
214
+ - The `Stop` hook fires **after** the first answer has already been streamed. Spotter therefore queues a finding for the next same-session prompt instead of rewriting that answer. Detection accuracy in `UserPromptSubmit` (the *pre-response* stage) remains the primary quality axis
209
215
  - `Stop` hook is **deferred** for both Claude and Codex hosts as of v1.4.8. When Spotter finds a missed tool at `Stop`, it appends the finding to `<projectRoot>/.spotter/pending/<sessionId>.json` and surfaces it on the next same-session `UserPromptSubmit` as `additionalContext`. The original assistant message stays as the turn's final transcript entry — no `decision:"block"` re-generation cycle. The same pending file is shared by Claude and Codex (host-neutral path)
210
- - **Since v0.5.0, JSON schema violations from Haiku are treated as expected-anomalies** (silent pass + session renew, logged as `role_collapse_reset`) — this is the role-collapse recovery path. **Haiku timeouts still throw**, which surfaces as `UserPromptSubmit` blocking the user's prompt from reaching Claude. Timeouts have been raised twice (30s in v0.5.0, 45s in v0.13.1); making timeouts fail-open is deferred until §0 is revisited
216
+ - **Since v0.5.0, JSON schema violations from Haiku are treated as expected anomalies** (session renew + `role_collapse_reset`). **Since v1.4.15, an auditor/daemon failure no longer blocks the prompt**: `UserPromptSubmit` emits a loud `[Spotter からの警告]` and exits 0. A `Stop` failure is queued as the same kind of warning and delivered once on the next same-session prompt. If the session ends immediately, no later prompt exists and that final warning cannot be surfaced
211
217
 
212
218
  <details>
213
219
  <summary><strong>📋 Recent highlights</strong></summary>
214
220
 
221
+ - **Daemon recovers after an ungraceful death** (v1.4.16) — if the daemon dies without graceful shutdown (machine sleep, force-quit, crash before `SessionEnd`), the Unix socket it leaves behind no longer bricks every restart. `startDaemon` removes the orphaned socket before binding, so the next `UserPromptSubmit` auto-resurrect succeeds instead of crash-looping on `EADDRINUSE` and leaving the session permanently unaudited
222
+ - **Failures degrade loudly, never freeze the host** (v1.4.15) — when the auditor backend fails (e.g. codex login expired), the `UserPromptSubmit` hook surfaces a `[Spotter からの警告]` and lets your prompt through instead of silently erasing it. codex login expiry names the one-line fix (`codex login`)
215
223
  - **Plugin-scoped MCP servers** — names like `plugin:everything-claude-code:context7` (with internal colons) are now parsed correctly and their tools enter the catalog. Earlier versions silently collapsed all plugin MCP servers into a single literal `"plugin"`, dropping their tools from Claude's audit
216
224
  - **Per-project / per-host audit isolation** — the daemon audits against the local DB only; global DBs are host-specific description caches. Tools discovered in *other* projects or another host can never bleed into this project's audit set
217
225
  - **Zero-touch catalog** — `spotter install` seeds the Claude DB automatically; Claude and Codex SessionStart hooks keep their host-local DBs fresh in the background. You never have to maintain the tool list by hand
package/bin/spotter.mjs CHANGED
@@ -50,6 +50,8 @@ Usage:
50
50
  (experimental) run primary auditor backend once
51
51
  spotter auditor matrix --stage STAGE --input FILE
52
52
  (experimental) compare primary auditor backend matrix
53
+ spotter auditor model-matrix --fixtures FILE
54
+ (experimental) evaluate pinned Codex auditor profiles
53
55
  spotter daemon start --session-id ID (internal) run session daemon
54
56
  spotter hook <event> (internal) hook dispatch
55
57
  events: session-start | user-prompt |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-spotter",
3
- "version": "1.4.15",
3
+ "version": "1.4.18",
4
4
  "description": "Audit agent running alongside Claude Code that catches missed tool calls — 気づく役と実行する役の分離",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,151 @@
1
+ import { CODEX_AUDITOR_MODEL_POLICY } from '../src/core/codex-auditor-model-policy.mjs';
2
+
3
+ export const LATEST_MODELS_URL = 'https://developers.openai.com/api/docs/guides/latest-model.md';
4
+ export const PRICING_URL = 'https://learn.chatgpt.com/docs/pricing.md';
5
+ export const MAX_BODY_BYTES = 1024 * 1024;
6
+ export const FETCH_TIMEOUT_MS = 15_000;
7
+
8
+ const ROLES = ['sol', 'terra', 'luna'];
9
+ const PROPOSAL = '同じfixtureでLuna low/Terra lowを比較し、品質不足時のみTerra mediumを評価する。productionの自動昇格・書換えは行わない。';
10
+
11
+ export class ModelPolicyCheckError extends Error {
12
+ constructor(code) {
13
+ super(code);
14
+ this.name = 'ModelPolicyCheckError';
15
+ this.code = code;
16
+ }
17
+ }
18
+
19
+ function fail(code) { throw new ModelPolicyCheckError(code); }
20
+
21
+ function versionKey(major, minor) { return `${Number(major)}.${Number(minor)}`; }
22
+
23
+ export function compareVersions(left, right) {
24
+ const [leftMajor, leftMinor] = left.split('.').map(Number);
25
+ const [rightMajor, rightMinor] = right.split('.').map(Number);
26
+ return leftMajor - rightMajor || leftMinor - rightMinor;
27
+ }
28
+
29
+ export function extractCompleteFamilies(text, source) {
30
+ const regex = source === 'latest'
31
+ ? /\bgpt-(\d+)\.(\d+)-(sol|terra|luna)\b/gi
32
+ : /\bGPT-(\d+)\.(\d+)\s+(Sol|Terra|Luna)\b/g;
33
+ const byVersion = new Map();
34
+ for (const match of text.matchAll(regex)) {
35
+ const version = versionKey(match[1], match[2]);
36
+ const roles = byVersion.get(version) ?? new Set();
37
+ roles.add(match[3].toLowerCase());
38
+ byVersion.set(version, roles);
39
+ }
40
+ return [...byVersion.entries()]
41
+ .filter(([, roles]) => ROLES.every((role) => roles.has(role)))
42
+ .map(([version]) => ({ version, models: ROLES.map((role) => `gpt-${version}-${role}`) }))
43
+ .sort((a, b) => compareVersions(a.version, b.version));
44
+ }
45
+
46
+ async function readBoundedBody(response) {
47
+ const declaredLength = Number(response.headers?.get?.('content-length'));
48
+ if (Number.isFinite(declaredLength) && declaredLength > MAX_BODY_BYTES) fail('E_MODEL_POLICY_CHECK_BODY_TOO_LARGE');
49
+ if (!response.body?.getReader) {
50
+ const text = await response.text();
51
+ if (Buffer.byteLength(text) > MAX_BODY_BYTES) fail('E_MODEL_POLICY_CHECK_BODY_TOO_LARGE');
52
+ return text;
53
+ }
54
+ const reader = response.body.getReader();
55
+ const chunks = [];
56
+ let size = 0;
57
+ try {
58
+ for (;;) {
59
+ const { done, value } = await reader.read();
60
+ if (done) break;
61
+ size += value.byteLength;
62
+ if (size > MAX_BODY_BYTES) {
63
+ await reader.cancel();
64
+ fail('E_MODEL_POLICY_CHECK_BODY_TOO_LARGE');
65
+ }
66
+ chunks.push(value);
67
+ }
68
+ } finally {
69
+ reader.releaseLock?.();
70
+ }
71
+ return new TextDecoder().decode(Buffer.concat(chunks));
72
+ }
73
+
74
+ export async function fetchMarkdown(url, { fetchFn = fetch, timeoutMs = FETCH_TIMEOUT_MS } = {}) {
75
+ const controller = new AbortController();
76
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
77
+ try {
78
+ let response;
79
+ try {
80
+ response = await fetchFn(url, { signal: controller.signal, headers: { accept: 'text/markdown,text/plain;q=0.9' } });
81
+ } catch {
82
+ fail('E_MODEL_POLICY_CHECK_FETCH_FAILED');
83
+ }
84
+ if (!response || response.status !== 200) fail('E_MODEL_POLICY_CHECK_HTTP_STATUS');
85
+ return await readBoundedBody(response);
86
+ } finally {
87
+ clearTimeout(timeout);
88
+ }
89
+ }
90
+
91
+ function policyProduction(policy) {
92
+ const model = policy?.production?.model;
93
+ const match = typeof model === 'string' && /^gpt-(\d+)\.(\d+)-(sol|terra|luna)$/.exec(model);
94
+ if (!match) fail('E_MODEL_POLICY_CHECK_INVALID_POLICY');
95
+ return { model, version: versionKey(match[1], match[2]) };
96
+ }
97
+
98
+ export async function runModelPolicyCheck({
99
+ fetchFn = fetch,
100
+ now = () => new Date(),
101
+ policy = CODEX_AUDITOR_MODEL_POLICY,
102
+ timeoutMs = FETCH_TIMEOUT_MS,
103
+ } = {}) {
104
+ const production = policyProduction(policy);
105
+ const [latestMarkdown, pricingMarkdown] = await Promise.all([
106
+ fetchMarkdown(LATEST_MODELS_URL, { fetchFn, timeoutMs }),
107
+ fetchMarkdown(PRICING_URL, { fetchFn, timeoutMs }),
108
+ ]);
109
+ const latestFamilies = extractCompleteFamilies(latestMarkdown, 'latest');
110
+ const pricingFamilies = extractCompleteFamilies(pricingMarkdown, 'pricing');
111
+ if (latestFamilies.length === 0 || pricingFamilies.length === 0) fail('E_MODEL_POLICY_CHECK_REQUIRED_FAMILY_MISSING');
112
+ const latestDetected = latestFamilies.at(-1);
113
+ const pricingDetected = pricingFamilies.at(-1);
114
+ if (latestDetected.version !== pricingDetected.version) fail('E_MODEL_POLICY_CHECK_SOURCE_MISMATCH');
115
+ const pricingVersionSet = new Set(pricingFamilies.map(({ version }) => version));
116
+ const commonFamilies = latestFamilies.filter(({ version }) => pricingVersionSet.has(version));
117
+ const detectedFamily = latestDetected;
118
+ const status = compareVersions(detectedFamily.version, production.version) > 0 ? 'update-available' : 'current';
119
+ const diagnostics = status === 'current'
120
+ && commonFamilies.some(({ version }) => version !== production.version)
121
+ ? ['E_MODEL_POLICY_CHECK_NON_PRODUCTION_CANDIDATE_PRESENT']
122
+ : [];
123
+ return {
124
+ schema: 'spotter.codex_model_update_check.v1',
125
+ checkedAt: now().toISOString(),
126
+ policy: { version: policy.policyVersion, production: policy.production.model },
127
+ sources: [LATEST_MODELS_URL, PRICING_URL],
128
+ detectedFamily,
129
+ candidates: commonFamilies,
130
+ status,
131
+ proposal: status === 'update-available' ? PROPOSAL : null,
132
+ diagnostics,
133
+ };
134
+ }
135
+
136
+ export async function main({ stdout = process.stdout, stderr = process.stderr, run = runModelPolicyCheck } = {}) {
137
+ try {
138
+ const artifact = await run();
139
+ stdout.write(`${JSON.stringify(artifact)}\n`);
140
+ return 0;
141
+ } catch (error) {
142
+ const code = error instanceof ModelPolicyCheckError ? error.code : 'E_MODEL_POLICY_CHECK_UNEXPECTED';
143
+ stderr.write(`${code}\n`);
144
+ return 1;
145
+ }
146
+ }
147
+
148
+ if (import.meta.url === new URL(process.argv[1], 'file:').href) {
149
+ const exitCode = await main();
150
+ process.exitCode = exitCode;
151
+ }