claude-spotter 0.12.0 → 0.13.0

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,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.13.0
4
+
5
+ **Stop 判定軸を「要請充足チェック」から「ツール適用機会の監査」に転換**。v0.12.x 以前の `stage=turn_end` は `<user_input>` + `<used_tools>` + `<final_response>` を Haiku に渡し、「ユーザー要請されたツールが使われたか」を判定していた。この軸では Bell が Stop 到達後にすべき動作 — 事実断定の裏付け / 新知見の記録 / 既知情報の照会 — を拾えない。実セッションで Haiku が応答「判明: A モジュールは B に依存」に対し `caveat_record` を推奨する、といった本来期待される指摘が構造的に出ない状態だった (ユーザーが [この議論](https://github.com/kitepon-rgb/Spotter) で指摘)。
6
+
7
+ ### 新軸: ツール適用機会の監査
8
+
9
+ - **入力**: `<used_tools>` + `<final_response>` のみ (user_input は削除)
10
+ - **問い**: 「この応答で、カタログ上のツールが役立つ箇所ないか?」
11
+ - **3 カテゴリ**: 検証 (Read/Grep/Bash/WebFetch 等) / 登録 (memory/caveat 等) / 照会 (search/list 等)
12
+ - **非対称**: 指摘ゼロは歓迎、`used_tools` 既含は再指摘しない、迷ったら pass:true
13
+
14
+ 挨拶ターンの早期 pass は daemon 側の `state.lastUserInput === null` 分岐で従来通り機能する (user_input が来ていないターンは turn_end で `reason=no_user_input` で pass)。Stop hook の入力契約 (`final_response` のみを daemon に送信) は v0.4.4 時点で user_input を送っていないので hook 側の変更なし。
15
+
16
+ ### 変更点
17
+
18
+ - **編集 [src/daemon/haiku-caller.mjs](src/daemon/haiku-caller.mjs)**: `SHARED_HEADER` の stage=turn_end 説明を書き換え、few-shot を 4 件 (検証/登録/照会/pass) に拡張。`buildFinalStagePrompt` から `userInput` 引数を削除、`<user_input>` タグも削除
19
+ - **編集 [src/daemon/daemon.mjs](src/daemon/daemon.mjs)**: `handleTurnEnd` の `buildFinalStagePrompt` 呼び出しから `userInput` 引数削除、`savedUserInput` 変数削除。`state.lastUserInput` は `no_user_input` pass 分岐用に保持継続 (コメントで意図明記)
20
+ - **編集 [test/haiku-caller.test.mjs](test/haiku-caller.test.mjs)**: `buildFinalStagePrompt` の 3 テストから `userInput:` 引数削除、`<user_input>` タグ包含アサートを非包含アサートに反転
21
+ - **編集 [test/daemon.test.mjs](test/daemon.test.mjs)**: turn_end の per-turn prompt に `<user_input>` タグも user 発言原文も含まれないことを確認するテストを 1 件追加
22
+ - **編集 [CLAUDE.md](CLAUDE.md)**: Product Concept に「判定軸 (v0.13.0 で 2 軸化)」セクションを追加、user_input=要請充足チェック / turn_end=ツール適用機会の監査 を明記
23
+
24
+ ### 非互換
25
+
26
+ - **判定挙動の意味論変更**: v0.12.x までの「user_input 要請に対応するツール」しか指摘しなかった Stop hook が、v0.13.0 からは user_input 非依存で「応答に対する適用機会」を指摘する。false positive / false negative の方向性も変わるため、過検出率 / pass 率の再計測が必要 ([docs/open-issues.md](docs/open-issues.md) P0 に観測タスクを追加)
27
+ - **API 変更**: `buildFinalStagePrompt({ userInput, usedTools, finalResponse })` → `buildFinalStagePrompt({ usedTools, finalResponse })`。外部から直接呼ばれる API ではない (daemon 内部) ため影響範囲は Spotter 本体のみ
28
+
3
29
  ## 0.12.0
4
30
 
5
31
  **親 PID watch を heartbeat 方式に置換 + UserPromptSubmit auto-resurrect**。v0.6.2 で導入した `--parent-pid` watch (Claude Code 本体 PID を `process.kill(pid, 0)` で 5 秒間隔 ping) が VSCode native extension 環境で誤爆する問題を解消。`process.ppid` は extension host から spawn される短命ラッパーを指していて、5 秒で ESRCH → daemon 自死していた (`~/.spotter/logs/ppid-probe.log` の env dump で実測: hook の ppid が毎回 (55692, 46020 等) 変わるのに対し `VSCODE_PID=39964` は固定、CLAUDE_* 系には PID 系 env なし)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-spotter",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Audit agent running alongside Claude Code that catches missed tool calls — 気づく役と実行する役の分離",
5
5
  "type": "module",
6
6
  "bin": {
@@ -258,12 +258,13 @@ export async function startDaemon({
258
258
  return { pass: true, missing_tools: [], reason: 'no_user_input' };
259
259
  }
260
260
 
261
- const savedUserInput = state.lastUserInput;
261
+ // v0.13.0: state.lastUserInput は turn_end の Haiku 判定には渡さない (新軸は
262
+ // final_response + used_tools のみで判定)。ただし「挨拶ターン (user_input が来て
263
+ // いない) は早期 pass」の分岐は上で使うので保存は引き続き必要。
262
264
  const savedUsedTools = state.usedTools.slice();
263
265
  const { parsed, meta } = await runHaikuJudgment(
264
266
  'turn_end',
265
267
  buildFinalStagePrompt({
266
- userInput: savedUserInput,
267
268
  usedTools: savedUsedTools,
268
269
  finalResponse,
269
270
  })
@@ -38,6 +38,11 @@ export async function ensureWorkdir() {
38
38
 
39
39
  // Shared header covers BOTH stages — the preamble documents stage=user_input and
40
40
  // stage=turn_end so per-turn prompts only need to announce which stage they are.
41
+ //
42
+ // v0.13.0: stage=turn_end の判定軸を「要請充足チェック」から「ツール適用機会の監査」に転換。
43
+ // 旧軸は <user_input> に対し used_tools が足りているかをチェックしていた。新軸は
44
+ // <final_response> の内容 (事実断定 / 記録すべき新情報 / 既知情報の参照) に対し、カタログ
45
+ // 上のツール (検証 / 登録 / 照会) を差し込める余地を探す。非対称 (指摘ゼロ歓迎) 設計。
41
46
  const SHARED_HEADER = [
42
47
  'あなたは Spotter。Bell (主役の Claude) が呼び忘れるツールを検出する監査役です。',
43
48
  'ユーザーへの会話文は生成せず、必ず下記 JSON のみを返します。',
@@ -49,13 +54,32 @@ const SHARED_HEADER = [
49
54
  '',
50
55
  '## 判定対象',
51
56
  '各ターン、以下いずれかの stage で判定リクエストを受けます:',
52
- '- stage=user_input: <user_input> のみ届く。when_to_use に明確に該当するツールを列挙',
53
- '- stage=turn_end: <user_input> + <used_tools> + <final_response> が届く。既使用を除き Bell が呼び忘れたツールを列挙',
54
- 'どちらも推測禁止。該当なしなら pass:true。',
57
+ '',
58
+ '### stage=user_input',
59
+ '<user_input> のみ届く。when_to_use に明確に該当するツールを列挙。',
60
+ '推測禁止。該当なしなら pass:true。',
61
+ '',
62
+ '### stage=turn_end (ツール適用機会の監査)',
63
+ '<final_response> + <used_tools> が届く。',
64
+ 'Bell の応答に含まれる動作 — 事実の断定 / 記録すべき新情報 / 既知情報の参照 —',
65
+ 'それぞれについて、カタログに役立つツールがあれば提示する。',
66
+ '検証 (Read/Grep/Bash/WebFetch 等) / 登録 (memory/caveat 等) / 照会 (search/list 等) のいずれも対象。',
67
+ '<used_tools> に既に含まれるツールは再指摘しない。',
68
+ '指摘ゼロは歓迎。迷ったら pass:true。',
55
69
  '',
56
70
  '## 例',
57
- '- stage=user_input "今何時?" → {"pass":false,"missing_tools":[{"name":"current_time","reason":"時刻の直接質問"}]}',
58
- '- stage=user_input "ありがとう" → {"pass":true,"missing_tools":[]}',
71
+ '- stage=user_input "今何時?"',
72
+ ' → {"pass":false,"missing_tools":[{"name":"current_time","reason":"時刻の直接質問"}]}',
73
+ '- stage=user_input "ありがとう"',
74
+ ' → {"pass":true,"missing_tools":[]}',
75
+ '- stage=turn_end 応答「この関数は配列長を返します」(used:なし) ← 検証',
76
+ ' → {"pass":false,"missing_tools":[{"name":"Read","reason":"関数実装の断定は実ファイル読取で裏付けるべき"}]}',
77
+ '- stage=turn_end 応答「判明: A モジュールは B に依存」(used:Grep) ← 登録',
78
+ ' → {"pass":false,"missing_tools":[{"name":"mcp__caveat__caveat_record","reason":"新発見の依存関係は記録して次回参照可能にすべき"}]}',
79
+ '- stage=turn_end 応答「この話題は前にも議論したはず」(used:なし) ← 照会',
80
+ ' → {"pass":false,"missing_tools":[{"name":"mcp__caveat__caveat_search","reason":"過去の議論参照は検索して裏付けるべき"}]}',
81
+ '- stage=turn_end 応答「作業完了しました」(used:Read,Edit,Bash) ← pass',
82
+ ' → {"pass":true,"missing_tools":[]}',
59
83
  ].join('\n');
60
84
 
61
85
  // Preamble — sent exactly once per Haiku session (first call). Contains the role,
@@ -92,13 +116,13 @@ export function buildFirstStagePrompt({ userInput }) {
92
116
  }
93
117
 
94
118
  // Per-turn prompt — Stop hook stage.
95
- export function buildFinalStagePrompt({ userInput, usedTools, finalResponse }) {
119
+ // v0.13.0: user_input は渡さない。判定軸が「ユーザー要請の充足」から「final_response
120
+ // ツール適用機会があるか」に変わったため。挨拶ターンの早期 pass は daemon 側の
121
+ // state.lastUserInput === null 分岐 (handleTurnEnd) で処理する。
122
+ export function buildFinalStagePrompt({ usedTools, finalResponse }) {
96
123
  const usedList = usedTools.length > 0 ? usedTools.map((t) => `- ${t}`).join('\n') : '(なし)';
97
124
  return [
98
125
  'stage=turn_end',
99
- '<user_input>',
100
- userInput,
101
- '</user_input>',
102
126
  '<used_tools>',
103
127
  usedList,
104
128
  '</used_tools>',