claude-spotter 1.4.8 → 1.4.9

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,36 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.4.9
4
+
5
+ **Codex hooks feature 名の現行 CLI 追従**。現行 Codex CLI の `codex features list` は
6
+ hook 機能を `hooks stable true` と表示するが、Spotter の `codex-hook diagnostics` は旧名
7
+ `codex_hooks` だけを見ていたため、hooks 登録済みでも `availability:"unavailable"` と誤判定していた。
8
+
9
+ ### 変更点
10
+
11
+ - **編集 [src/cli/codex-hook-cmd.mjs](src/cli/codex-hook-cmd.mjs)**:
12
+ `codexHookDiagnostics` が現行 `hooks` と旧 `codex_hooks` の両方を enabled evidence として扱う。
13
+ `installCodexHooks` は現行 CLI に合わせて `[features].hooks = true` を書く。既に旧
14
+ `codex_hooks = true` がある環境では削除せず、`hooks = true` を追加して現行 CLI で確実に有効化する。
15
+ - **編集 [test/codex-hook-cmd.test.mjs](test/codex-hook-cmd.test.mjs)**:
16
+ 現行 `hooks stable true` と旧 `codex_hooks stable true` の diagnostics 回帰テスト、
17
+ 旧 feature key が残る config への install 回帰テストを追加。
18
+ - **編集 docs**:
19
+ `open-issues.md` の Codex pending / hook event path 記述を v1.4.8 以降の
20
+ host-neutral `.spotter/pending/` / `.spotter/hook-events.jsonl` に追従。
21
+
22
+ ### ユーザー側で必要な手順
23
+
24
+ 1. `npm install -g claude-spotter@1.4.9`
25
+ 2. Codex hooks を使う環境では、各 Spotter install 済み project で `spotter install` を再実行
26
+ (`~/.codex/config.toml` に `[features].hooks = true` を確実に反映するため)
27
+
28
+ ### 検証
29
+
30
+ - `node --test` 322 tests / 321 pass / 1 skip 緑
31
+ - `spotter codex-hook diagnostics --project /home/kite/projects/Spotter` が
32
+ `availability:"available"` / evidence=`hooks stable true` を返すことを実機確認
33
+
3
34
  ## 1.4.8
4
35
 
5
36
  **Hook 挙動 parity (Codex → Claude) 移植**。Codex 側で確定していた 3 つの hook 挙動 — Stop
package/README.ja.md CHANGED
@@ -45,6 +45,7 @@ spotter install
45
45
 
46
46
  `v0.3.0` 以降は**プロジェクト単位の明示的 install** を採用しています (v0.2 までの `postinstall` 自動登録はデーモン増殖の主因だったため撤回)。各プロジェクトの `.claude/settings.json` に hook を登録し、そのプロジェクトでの Claude Code セッションのみで有効になります。
47
47
  Codex CLI が使える環境では、同じ `spotter install` が user-level の Codex native hooks も登録します。実際に動くプロジェクトは `spotter install` が作る `.spotter/marker.json` で制限されるため、無関係な Codex セッションでは Spotter は起動しません。
48
+ Codex 側では現行の `[features].hooks = true` を有効化し、互換のため旧 `codex_hooks` diagnostics output も認識します。
48
49
 
49
50
  Spotter を upgrade した後、release note で hook 設定変更が案内されている場合は、各 install 済みプロジェクトで `spotter install` を再実行してください。global package update でコード経路は変わりますが、既存 `.claude/settings.json` の timeout 値は自動では書き換わりません。
50
51
 
package/README.md CHANGED
@@ -45,6 +45,7 @@ spotter install
45
45
 
46
46
  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.
47
47
  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.
48
+ For Codex, install enables the current `[features].hooks = true` flag and still recognizes older `codex_hooks` diagnostics output for compatibility.
48
49
 
49
50
  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.
50
51
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-spotter",
3
- "version": "1.4.8",
3
+ "version": "1.4.9",
4
4
  "description": "Audit agent running alongside Claude Code that catches missed tool calls — 気づく役と実行する役の分離",
5
5
  "type": "module",
6
6
  "bin": {
@@ -35,6 +35,7 @@ const CODEX_HOOK_TIMEOUT_SEC = 60;
35
35
  const DEFAULT_CODEX_HOOK_AUDITOR_TIMEOUT_MS = 20_000;
36
36
  const SHORT_PROMPT_MAX_CHARS = 10;
37
37
  const DEFAULT_CODEX_STOP_SHORT_FINAL_MAX_CHARS = 120;
38
+ const CODEX_HOOK_FEATURE_NAMES = ['hooks', 'codex_hooks'];
38
39
 
39
40
  const CODEX_HOOK_USAGE = `spotter codex-hook — Codex native hook adapter
40
41
 
@@ -364,7 +365,8 @@ export async function codexHookDiagnostics({ codexHome = defaultCodexHome(), pro
364
365
  const features = spawnSyncFn('codex', ['features', 'list'], { encoding: 'utf8', maxBuffer: 1024 * 1024 });
365
366
  const featureOutput = [features.stdout, features.stderr].filter(Boolean).join('\n');
366
367
  const hooks = await loadJson(join(codexHome, 'hooks.json'));
367
- const codexHooksFeature = /^codex_hooks\s+\S+\s+true\b/m.test(featureOutput) ? 'enabled' : 'not-enabled';
368
+ const evidence = featureOutput.split('\n').find((line) => isEnabledCodexHookFeatureLine(line)) ?? null;
369
+ const codexHooksFeature = evidence ? 'enabled' : 'not-enabled';
368
370
  const installed = {
369
371
  sessionStart: hookState(hooks, 'SessionStart', 'codex-hook session-start'),
370
372
  userPromptSubmit: hookState(hooks, 'UserPromptSubmit', 'codex-hook user-prompt-submit'),
@@ -382,7 +384,7 @@ export async function codexHookDiagnostics({ codexHome = defaultCodexHome(), pro
382
384
  codexHome,
383
385
  hooksPath: join(codexHome, 'hooks.json'),
384
386
  installedHooks: installed,
385
- evidence: featureOutput.split('\n').find((line) => line.trim().startsWith('codex_hooks')) ?? null,
387
+ evidence,
386
388
  runtime: runtimeProjectRoot
387
389
  ? await summarizeCodexHookEvents({ projectRoot: runtimeProjectRoot })
388
390
  : null,
@@ -653,7 +655,7 @@ function isSpotterCodexHook(hook) {
653
655
  async function ensureCodexHooksFeature(configPath) {
654
656
  let raw = '';
655
657
  if (existsSync(configPath)) raw = await readFile(configPath, 'utf8');
656
- if (/^\s*codex_hooks\s*=\s*true\s*$/m.test(raw)) return 'already-enabled';
658
+ if (/^\s*hooks\s*=\s*true\s*$/m.test(raw)) return 'already-enabled';
657
659
  const next = enableCodexHooksFeature(raw);
658
660
  await mkdir(dirname(configPath), { recursive: true });
659
661
  await writeFile(configPath, next, 'utf8');
@@ -663,7 +665,7 @@ async function ensureCodexHooksFeature(configPath) {
663
665
  function enableCodexHooksFeature(raw) {
664
666
  const text = raw.trimEnd();
665
667
  if (!/^\s*\[features\]\s*$/m.test(text)) {
666
- return `${text}${text ? '\n\n' : ''}[features]\ncodex_hooks = true\n`;
668
+ return `${text}${text ? '\n\n' : ''}[features]\nhooks = true\n`;
667
669
  }
668
670
  const lines = text.split('\n');
669
671
  let inFeatures = false;
@@ -678,15 +680,23 @@ function enableCodexHooksFeature(raw) {
678
680
  inFeatures = /^\s*\[features\]\s*$/.test(line);
679
681
  continue;
680
682
  }
681
- if (inFeatures && /^\s*codex_hooks\s*=/.test(line)) {
682
- lines[index] = 'codex_hooks = true';
683
+ if (inFeatures && /^\s*hooks\s*=/.test(line)) {
684
+ lines[index] = 'hooks = true';
683
685
  return `${lines.join('\n')}\n`;
684
686
  }
685
687
  }
686
- lines.splice(insertAt, 0, 'codex_hooks = true');
688
+ lines.splice(insertAt, 0, 'hooks = true');
687
689
  return `${lines.join('\n')}\n`;
688
690
  }
689
691
 
692
+ function isEnabledCodexHookFeatureLine(line) {
693
+ const trimmed = String(line ?? '').trim();
694
+ return CODEX_HOOK_FEATURE_NAMES.some((name) => {
695
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
696
+ return new RegExp(`^${escaped}\\s+\\S+\\s+true\\b`).test(trimmed);
697
+ });
698
+ }
699
+
690
700
  function hookState(settings, event, commandFragment) {
691
701
  const groups = settings?.hooks?.[event];
692
702
  if (!Array.isArray(groups)) return 'not-installed';