claude-spotter 1.5.8 → 1.5.10

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
@@ -3,6 +3,28 @@
3
3
  各節はそのversion公開時点の変更記録であり、後続versionにより置換された仕様を含む。
4
4
  現行runtime契約は[`docs/00_overview.md`](docs/00_overview.md)から辿る。
5
5
 
6
+ ## 1.5.10 — 2026-08-14
7
+
8
+ - **非ClaudeのHook入力を副作用前に無視する。** 非空の`sessionId`と`hookEventName`を持ち、
9
+ Claudeの`session_id`を持たないcamelCase envelopeだけをunsupportedとして識別する。
10
+ SessionStart、UserPromptSubmit、PreToolUse、Stop、SessionEndはproject探索、daemon、評価store、
11
+ tool記録、Stop監査、Hook eventより前に無出力・exit 0で終了する。
12
+ - **正式host範囲を広げない。** camelCase payloadをClaude形へ変換せず、Grok向けtool DB、auditor、
13
+ installer、diagnosticsを追加しない。Claude/Codexの既存入力と処理は変更しない。
14
+
15
+ ## 1.5.9 — 2026-08-14
16
+
17
+ - **Codex side conversationのStopを正常終了させる。** Codex公式Hook契約でnullableな
18
+ `transcript_path`が欠落または`null`の場合と、互換payloadが空文字の場合、未処理例外とexit 2を出さず、
19
+ `transcript_unavailable`の構造Hook eventを記録してexit 0で終了する。
20
+ - **不完全な利用観測を監査成功へ偽装しない。** transcriptが無いturnはauditorを呼ばず、
21
+ 対応する評価turnを`usageStatus: incomplete`で閉じる。非空pathのENOENT・transcript anomaly・
22
+ 通常taskの監査経路は変更せず、非string値はmalformed envelopeとしてexit 2を維持する。
23
+ - **4環境CIを工場共通workflowへ統合。** macOS native、Linux native、Windows native、WSL2の
24
+ reusable workflowへ移行し、依存導入、文書検証、full testの時間を分離する。
25
+ - **Windows CLI試験を端末PATHから分離。** Codex CLI検出・診断のWindows shim解決へ明示envを渡し、
26
+ 開発端末の実PATHに左右されない回帰testへ固定する。
27
+
6
28
  ## 1.5.8 — 2026-08-05
7
29
 
8
30
  - **コードから全ドキュメントを再照合。** 47 Markdownを現行contract、ADR、完了計画、
@@ -1,6 +1,6 @@
1
1
  # Spotter評価dashboard運用
2
2
 
3
- 現行npm配布版: **v1.5.8**(2026-08-05)。v1.5.8は文書整合releaseであり、
3
+ 現行npm配布版: **v1.5.10**(2026-08-14)。v1.5.10は非Claude Hook入力の副作用前no-op releaseであり、
4
4
  dashboard routing構成はv1.5.3から変更していない。
5
5
 
6
6
  この文書はservice設定の正本であり、各端末に現在installされているnpm versionの台帳ではない。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-spotter",
3
- "version": "1.5.8",
3
+ "version": "1.5.10",
4
4
  "description": "Audit agent running alongside Claude Code that catches missed tool calls — 気づく役と実行する役の分離",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,6 +15,7 @@ import {
15
15
  die,
16
16
  findSpotterMarker,
17
17
  isChildCall,
18
+ optionalString,
18
19
  readStdinJson,
19
20
  requireString,
20
21
  } from '../hooks/lib.mjs';
@@ -286,7 +287,27 @@ export async function runCodexStopHook({
286
287
  });
287
288
  return;
288
289
  }
289
- const transcriptPath = requireString(input, 'transcript_path');
290
+ const transcriptPath = optionalString(input, 'transcript_path');
291
+ if (!transcriptPath) {
292
+ await closeCodexEvaluationTurn({
293
+ createEvaluationStoreFn,
294
+ sessionId: codexSessionId(input),
295
+ usageStatus: 'incomplete',
296
+ completedAtMs: now(),
297
+ writeError: reportError,
298
+ });
299
+ await recordCodexHookEventSafe(recordHookEventFn, {
300
+ projectRoot,
301
+ event: {
302
+ hook: 'Stop',
303
+ status: 'skipped',
304
+ reason: 'transcript_unavailable',
305
+ usedToolCount: 0,
306
+ durationMs: Date.now() - startedAt,
307
+ },
308
+ }, reportError);
309
+ return;
310
+ }
290
311
  const finalResponse = codexLastAssistantMessage(input) ?? '(no final response available)';
291
312
  let toolUsage;
292
313
  try {
@@ -561,6 +582,7 @@ export async function codexHookDiagnostics({
561
582
  command: 'codex',
562
583
  args: ['features', 'list'],
563
584
  platform,
585
+ env,
564
586
  });
565
587
  const features = spawnSyncFn(invocation.command, invocation.args, {
566
588
  encoding: 'utf8',
@@ -123,12 +123,14 @@ export function isSupportedNodeVersion(value) {
123
123
  export async function inspectCodexCliVersion({
124
124
  codexBin = 'codex',
125
125
  platform = process.platform,
126
+ env = process.env,
126
127
  execFileFn = execFileP,
127
128
  } = {}) {
128
129
  const invocation = buildWindowsCompatibleInvocation({
129
130
  command: codexBin,
130
131
  args: ['--version'],
131
132
  platform,
133
+ env,
132
134
  });
133
135
  const { stdout } = await execFileFn(invocation.command, invocation.args, {
134
136
  timeout: 5_000,
@@ -281,6 +281,7 @@ async function exists(path) {
281
281
 
282
282
  export function isCodexCliPresent({
283
283
  platform = process.platform,
284
+ env = process.env,
284
285
  spawnSyncFn = spawnSync,
285
286
  codexBin = 'codex',
286
287
  } = {}) {
@@ -288,6 +289,7 @@ export function isCodexCliPresent({
288
289
  command: codexBin,
289
290
  args: ['--version'],
290
291
  platform,
292
+ env,
291
293
  });
292
294
  const result = spawnSyncFn(invocation.command, invocation.args, {
293
295
  encoding: 'utf8',
package/src/hooks/lib.mjs CHANGED
@@ -46,6 +46,19 @@ export function isSubagentCall(input) {
46
46
  && input.agent_id.length > 0;
47
47
  }
48
48
 
49
+ // Grok invokes Claude-compatible hook commands with a camelCase wire envelope.
50
+ // Spotter does not support Grok as a host: ignore that envelope before any
51
+ // project lookup, daemon, evaluation, or hook-event side effect.
52
+ export function isUnsupportedNonClaudeEnvelope(input) {
53
+ return input !== null
54
+ && typeof input === 'object'
55
+ && typeof input.sessionId === 'string'
56
+ && input.sessionId.length > 0
57
+ && typeof input.hookEventName === 'string'
58
+ && input.hookEventName.length > 0
59
+ && !Object.hasOwn(input, 'session_id');
60
+ }
61
+
49
62
  // Walk up from startCwd looking for .spotter/marker.json. Returns the project
50
63
  // root path containing the marker, or null if none was found before reaching
51
64
  // the filesystem root.
@@ -10,6 +10,7 @@ import {
10
10
  die,
11
11
  isChildCall,
12
12
  isSubagentCall,
13
+ isUnsupportedNonClaudeEnvelope,
13
14
  isOutsideSpotterProject,
14
15
  findSpotterMarker,
15
16
  recordClaudeHookEvent,
@@ -29,6 +30,7 @@ export async function runPreToolUse({
29
30
  } = {}) {
30
31
  if (isChildCall()) return;
31
32
  const input = await readInput();
33
+ if (isUnsupportedNonClaudeEnvelope(input)) return;
32
34
  if (isSubagentCall(input)) return;
33
35
  if (isOutsideSpotterProject(input)) return;
34
36
 
@@ -8,6 +8,7 @@ import {
8
8
  requireString,
9
9
  isChildCall,
10
10
  isSubagentCall,
11
+ isUnsupportedNonClaudeEnvelope,
11
12
  isOutsideSpotterProject,
12
13
  findSpotterMarker,
13
14
  recordClaudeHookEvent,
@@ -24,6 +25,7 @@ export async function runSessionEnd({
24
25
  } = {}) {
25
26
  if (isChildCall()) return;
26
27
  const input = await readInput();
28
+ if (isUnsupportedNonClaudeEnvelope(input)) return;
27
29
  if (isSubagentCall(input)) return;
28
30
  if (isOutsideSpotterProject(input)) return;
29
31
 
@@ -22,6 +22,7 @@ import {
22
22
  die,
23
23
  isChildCall,
24
24
  isSubagentCall,
25
+ isUnsupportedNonClaudeEnvelope,
25
26
  isOutsideSpotterProject,
26
27
  findSpotterMarker,
27
28
  recordClaudeHookEvent,
@@ -39,6 +40,7 @@ export async function runSessionStart({
39
40
 
40
41
  const input = await readInput();
41
42
 
43
+ if (isUnsupportedNonClaudeEnvelope(input)) return;
42
44
  if (isSubagentCall(input)) return;
43
45
  if (input.source !== 'startup') return;
44
46
  if (isOutsideSpotterProject(input)) return;
@@ -8,6 +8,7 @@ import {
8
8
  findSpotterMarker,
9
9
  isChildCall,
10
10
  isSubagentCall,
11
+ isUnsupportedNonClaudeEnvelope,
11
12
  recordClaudeHookEvent,
12
13
  } from './lib.mjs';
13
14
  import { getLastAssistantText } from './transcript-reader.mjs';
@@ -26,6 +27,7 @@ export async function runStop({
26
27
  } = {}) {
27
28
  if (isChildCall()) return;
28
29
  const input = await readInput();
30
+ if (isUnsupportedNonClaudeEnvelope(input)) return;
29
31
  if (isSubagentCall(input)) return;
30
32
  // Resolve the installed project once. Keeping this proven root lets the failure event remain
31
33
  // attributable even if the marker disappears while the daemon request is in flight.
@@ -17,6 +17,7 @@ import {
17
17
  die,
18
18
  isChildCall,
19
19
  isSubagentCall,
20
+ isUnsupportedNonClaudeEnvelope,
20
21
  isOutsideSpotterProject,
21
22
  findSpotterMarker,
22
23
  recordClaudeHookEvent,
@@ -48,6 +49,7 @@ export async function runUserPrompt({
48
49
  } = {}) {
49
50
  if (isChildCall()) return;
50
51
  const input = await readInput();
52
+ if (isUnsupportedNonClaudeEnvelope(input)) return;
51
53
  if (isSubagentCall(input)) return;
52
54
  if (isOutsideSpotterProject(input)) return;
53
55