claude-spotter 1.4.10 → 1.4.12

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,47 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.4.12
4
+
5
+ **macOS/Homebrew install verification docs**。npm registry からの clean global install 後に
6
+ `spotter install` と `spotter codex-hook install` が通ること、Codex hook が versioned
7
+ Homebrew Cellar Node path ではなく安定 symlink を使うことを README に明記した。
8
+
9
+ ### 変更点
10
+
11
+ - **編集 [README.md](README.md) / [README.ja.md](README.ja.md)**:
12
+ macOS Homebrew Node 環境では Codex hook command が `/opt/homebrew/bin/node` を使うこと、
13
+ release install smoke 手順 (`npm uninstall -g`, `npm install -g`, `spotter install -y`,
14
+ `spotter codex-hook install`) を追加。
15
+
16
+ ### 検証
17
+
18
+ - npm registry からの実インストールで確認:
19
+ `npm uninstall -g claude-spotter`, `npm install -g claude-spotter`,
20
+ `spotter --version`, `spotter install -y`, `spotter codex-hook install` が exit 0。
21
+
22
+ ## 1.4.11
23
+
24
+ **macOS Homebrew Node の versioned Cellar path を Codex hook に残さない修正**。
25
+ `spotter codex-hook install` が `/opt/homebrew/Cellar/node/<version>/bin/node` を hook command に
26
+ 書くと、Homebrew の Node 更新後に Codex hook が古い Node を指したまま壊れるため、現在の
27
+ Node 実体と一致する PATH 上の安定 symlink を優先するようにした。
28
+
29
+ ### 変更点
30
+
31
+ - **編集 [src/cli/codex-hook-cmd.mjs](src/cli/codex-hook-cmd.mjs)**:
32
+ `resolveCodexHookNodePath()` を追加。PATH 上の `node` が `process.execPath` と同じ realpath を
33
+ 指す場合は、その安定パスを hook command に使う。該当しない環境では従来通り
34
+ `process.execPath` に fallback するため、Windows / WSL2 の挙動は維持される。
35
+ - 既存の Spotter-owned Codex hook が古い Cellar Node path を指している場合、再実行で現在の
36
+ stable Node path と npm global package path に書き換える。Caveat など他 tool の hook は保持する。
37
+
38
+ ### 検証
39
+
40
+ - `node --test` 334 tests / 333 pass / 1 skip 緑。
41
+ - npm registry からの実インストールで確認:
42
+ `npm uninstall -g claude-spotter`, `npm install -g claude-spotter`,
43
+ `spotter --version`, `spotter install -y`, `spotter codex-hook install` が exit 0。
44
+
3
45
  ## 1.4.10
4
46
 
5
47
  **Claude host primary auditor: Codex CLI 検出で自動採用、なければ Haiku の 2 段選択へ**。
package/README.ja.md CHANGED
@@ -43,6 +43,11 @@ cd your-project
43
43
  spotter install
44
44
  ```
45
45
 
46
+ macOS の Homebrew Node 環境では、Codex hook command の Node パスに現在の実体と一致する
47
+ 安定 symlink (`/opt/homebrew/bin/node`) を使います。
48
+ `/opt/homebrew/Cellar/node/<version>/...` のような version 固定パスを書かないため、
49
+ Homebrew で Node が更新されても Codex hook が古い Node パスに取り残されません。
50
+
46
51
  `v0.3.0` 以降は**プロジェクト単位の明示的 install** を採用しています (v0.2 までの `postinstall` 自動登録はデーモン増殖の主因だったため撤回)。各プロジェクトの `.claude/settings.json` に hook を登録し、そのプロジェクトでの Claude Code セッションのみで有効になります。
47
52
  Codex CLI が使える環境では、同じ `spotter install` が user-level の Codex native hooks も登録します。実際に動くプロジェクトは `spotter install` が作る `.spotter/marker.json` で制限されるため、無関係な Codex セッションでは Spotter は起動しません。
48
53
  Codex 側では現行の `[features].hooks = true` を有効化し、互換のため旧 `codex_hooks` diagnostics output も認識します。
@@ -53,6 +58,16 @@ Spotter を upgrade した後、release note で hook 設定変更が案内さ
53
58
  spotter uninstall # このプロジェクトの hook 登録を解除
54
59
  ```
55
60
 
61
+ リリース時の npm install smoke:
62
+
63
+ ```bash
64
+ npm uninstall -g claude-spotter
65
+ npm install -g claude-spotter
66
+ spotter --version
67
+ spotter install -y
68
+ spotter codex-hook install
69
+ ```
70
+
56
71
  ## 動作要件
57
72
 
58
73
  - **Node.js 22.5 以上**
package/README.md CHANGED
@@ -43,6 +43,11 @@ cd your-project
43
43
  spotter install
44
44
  ```
45
45
 
46
+ On macOS with Homebrew Node, Codex hook commands use the stable
47
+ `/opt/homebrew/bin/node` symlink when it resolves to the current Node binary,
48
+ instead of a versioned `/opt/homebrew/Cellar/node/<version>/...` path. That keeps
49
+ Codex hooks working across Homebrew Node upgrades.
50
+
46
51
  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
52
  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
53
  For Codex, install enables the current `[features].hooks = true` flag and still recognizes older `codex_hooks` diagnostics output for compatibility.
@@ -53,6 +58,16 @@ After upgrading Spotter, re-run `spotter install` in each installed project when
53
58
  spotter uninstall # remove hooks from this project
54
59
  ```
55
60
 
61
+ Release install smoke:
62
+
63
+ ```bash
64
+ npm uninstall -g claude-spotter
65
+ npm install -g claude-spotter
66
+ spotter --version
67
+ spotter install -y
68
+ spotter codex-hook install
69
+ ```
70
+
56
71
  ## Requirements
57
72
 
58
73
  - **Node.js 22.5+**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-spotter",
3
- "version": "1.4.10",
3
+ "version": "1.4.12",
4
4
  "description": "Audit agent running alongside Claude Code that catches missed tool calls — 気づく役と実行する役の分離",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,8 +1,8 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { existsSync } from 'node:fs';
2
+ import { existsSync, realpathSync } from 'node:fs';
3
3
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
4
4
  import { homedir } from 'node:os';
5
- import { dirname, join, resolve } from 'node:path';
5
+ import { delimiter, dirname, join, resolve } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { createAuditorBackend } from '../core/auditor-backend.mjs';
8
8
  import { codexLastAssistantMessage, readCodexUsedTools } from '../core/codex-transcript.mjs';
@@ -315,7 +315,7 @@ export async function runCodexHookDiagnosticsCommand({
315
315
  writeOutput(JSON.stringify(result, null, 2) + '\n');
316
316
  }
317
317
 
318
- export async function installCodexHooks({ codexHome = defaultCodexHome(), nodePath = process.execPath, spotterBin = SPOTTER_BIN } = {}) {
318
+ export async function installCodexHooks({ codexHome = defaultCodexHome(), nodePath = resolveCodexHookNodePath(), spotterBin = SPOTTER_BIN } = {}) {
319
319
  const hooksPath = join(codexHome, 'hooks.json');
320
320
  const configPath = join(codexHome, 'config.toml');
321
321
  const current = await loadJson(hooksPath);
@@ -340,6 +340,41 @@ export async function installCodexHooks({ codexHome = defaultCodexHome(), nodePa
340
340
  };
341
341
  }
342
342
 
343
+ function safeRealpath(path, realpath = realpathSync.native) {
344
+ try {
345
+ return realpath(path);
346
+ } catch {
347
+ return null;
348
+ }
349
+ }
350
+
351
+ export function resolveCodexHookNodePath({
352
+ env = process.env,
353
+ execPath = process.execPath,
354
+ platform = process.platform,
355
+ exists = existsSync,
356
+ realpath = realpathSync.native,
357
+ } = {}) {
358
+ const execRealpath = safeRealpath(execPath, realpath);
359
+ const pathEnv = env.PATH || env.Path || '';
360
+ const names = platform === 'win32'
361
+ ? ['node.exe', 'node.cmd', 'node.bat', 'node']
362
+ : ['node'];
363
+
364
+ for (const dir of pathEnv.split(delimiter).filter(Boolean)) {
365
+ for (const name of names) {
366
+ const candidate = join(dir, name);
367
+ if (!exists(candidate)) continue;
368
+ const candidateRealpath = safeRealpath(candidate, realpath);
369
+ if (execRealpath && candidateRealpath && candidateRealpath === execRealpath) {
370
+ return candidate;
371
+ }
372
+ }
373
+ }
374
+
375
+ return execPath;
376
+ }
377
+
343
378
  export async function uninstallCodexHooks({ codexHome = defaultCodexHome() } = {}) {
344
379
  const hooksPath = join(codexHome, 'hooks.json');
345
380
  const current = await loadJson(hooksPath);
@@ -468,7 +468,7 @@ function execPortable(execFileFn, cmd, args, spawnOptions) {
468
468
  windowsHide: true,
469
469
  maxBuffer: 10 * 1024 * 1024,
470
470
  };
471
- if (process.platform === 'win32') {
471
+ if (process.platform === 'win32' && cmd !== process.execPath) {
472
472
  return execFileFn('cmd.exe', ['/c', cmd, ...args], options);
473
473
  }
474
474
  return execFileFn(cmd, args, options);
package/src/version.mjs CHANGED
@@ -1,2 +1,7 @@
1
- import pkg from '../package.json' with { type: 'json' };
1
+ import { readFileSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { dirname, resolve } from 'node:path';
4
+
5
+ const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
6
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
2
7
  export const version = pkg.version;