claude-spotter 1.4.20 → 1.4.22

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,11 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.4.22 — 2026-07-13
4
+
5
+ - **default-on裁定の文書修正。** v1.4.21の実装どおりdefault-onは確定事項であり、7日・30 fresh resultの測定は精度改善にだけ使う。ON/OFFの再審査やdefault-off rollbackを選択肢としていた誤記をREADME、正典、SLO、計画、課題台帳から削除した。
6
+
7
+ ## 1.4.21 — 2026-07-13
8
+
9
+ ### Changed
10
+
11
+ - **Throughline監査文脈をdefault-onへ変更。** `spotter install`がPATH上のThroughlineをabsolute pathへ解決できる場合、project markerへ既定設定する。marker v2の`origin:default|explicit`で旧既定disabledだけを移行し、明示OFFは再install後も維持する。POSIXはrealpath、Windows npm shimはabsolute `node.exe + throughline.mjs`を保存する。
12
+ - **不在時は明示disabled。** Throughlineが見つからない場合は固定理由付きdisabledとし、current-only監査や別backendへfallbackしない。installerとdoctorが状態・OFF手順を表示する。
13
+ - **実運用効果測定。** default-onはowner確定済み。7日以上・fresh 30件以上の過検出・見逃し・stale率・latencyは精度改善にだけ使い、ON/OFFを再審査しない。L2本文は評価ログへ保存しない。
14
+
15
+ ### 検証・公開
16
+
17
+ 478 tests(476 pass / 2 skip)、macOS/Linux/Windows × Node 22.5/22.xのCI 6/6、packの秘密・
18
+ 開発者固有path scan、隔離tarball installを通過。公開commitは`5026ace`。npm `claude-spotter@1.4.21`、
19
+ tag / GitHub Release `v1.4.21`、registry由来global installを同期した。registry版の空projectへ
20
+ `spotter install -y`を実行し、marker v2がThroughlineのglobal実体を`origin:default`で設定し、
21
+ doctorがconnector availableを返すことを確認した。
22
+
3
23
  ## 1.4.20 — 2026-07-13
4
24
 
5
25
  ### Added
6
26
 
7
27
  - **Throughline監査文脈のopt-in配布。** 新規projectは既定`disabled`のまま、ownerがabsoluteなThroughline commandとrepeatableな先頭引数で有効化できる。freshな完了L2 user/assistant pairだけを`N=2`、body 600文字、total 4,000文字に制限し、Codex CLIへstdinで渡す。Haikuはこのcontext経路では呼ばず、fresh以外でもAIを呼ばない。connector障害は固定警告に限定する。親出力は安全なcatalog tool ID由来の固定・非命令形助言だけで、L2、reason、provider rawを反射しない。7日・30 fresh resultのproduction default昇格gateは未完了のため、配布後もproject opt-inを維持する。
8
28
 
29
+ ### 検証・公開
30
+
31
+ Spotter 476 tests(474 pass / 2 skip)、Throughline 580 tests(全pass)、両CIのmacOS/Linux/Windows ×
32
+ Node matrix各6/6、packの秘密・開発者固有path scan、隔離tarball installを通過。公開commitは`7cbc3a1`、
33
+ npm `claude-spotter@1.4.20`、tag / GitHub Release `v1.4.20`、registry由来global installを同期した。
34
+ `spotter doctor`は0 warnings、監査文脈connectorはavailable。Codex hook trustだけは仕様上機械検証不能のため、
35
+ `/hooks`での人手確認を残す。
36
+
9
37
  ## 1.4.19
10
38
 
11
39
  親セッションの暴走を誘発できたHook出力の信頼境界を修正する。監査用AIは内部で構造化判定を返すだけとし、
package/README.ja.md CHANGED
@@ -146,19 +146,18 @@ flowchart LR
146
146
 
147
147
  両者に共通するのは **「主体に頼らない仕組み」**。併用できます。
148
148
 
149
- ### 任意の Throughline auditor context canary
149
+ ### Throughline auditor context(既定ON)
150
150
 
151
- 新規の project install では auditor context は **disabled** が既定です。これは project が所有する
152
- opt-in であり、Spotter 全体の production 既定ではありません。現在の候補は Spotter リポジトリだけの
153
- canary です。auditor-context オプションなしで `spotter install` を再実行した場合、既存プロジェクトの
154
- 設定は保持されます。無効化へ戻すには次を実行します。
151
+ `spotter install`はPATH上のThroughlineを絶対パスへ解決できる場合、auditor contextを既定で有効化します。
152
+ 旧版の既定`disabled` markerも通常の再installで有効化されます。過去に明示的に無効化したprojectは
153
+ `origin: explicit`として保持され、勝手に再有効化されません。無効化するには次を実行します。
155
154
 
156
155
  ```bash
157
156
  spotter install -y --auditor-context disabled
158
157
  ```
159
158
 
160
- project を opt-in するには、絶対パスの Throughline 実行ファイルと、必要なら繰り返し指定できる
161
- `--throughline-arg` を設定します。
159
+ 自動解決できない環境で手動設定する場合は、絶対パスのThroughline実行ファイルと、必要なら
160
+ 繰り返し指定できる`--throughline-arg`を設定します。
162
161
 
163
162
  ```bash
164
163
  spotter install -y --auditor-context throughline \
@@ -191,8 +190,7 @@ spotter auditor model-matrix --fixtures test/fixtures/auditor-model-matrix.v2.js
191
190
  --recent-turns 2 --body-cap 600
192
191
  ```
193
192
 
194
- 評価結論は現在 N=2 / 600 です。ただし rollout 承認ではありません。Spotter リポジトリ canary の
195
- 7 日・30 fresh result gate は未完了です。
193
+ 評価結論はN=2 / 600です。既定ONは確定済みで、7日・30 fresh resultの実運用測定は精度改善に使います。
196
194
 
197
195
  ## よく使うコマンド
198
196
 
package/README.md CHANGED
@@ -146,19 +146,19 @@ The audited catalog is host-local: Claude uses `<project>/.spotter/tool-db.json`
146
146
 
147
147
  Both share the principle of **"don't rely on the primary agent to do it itself."** They compose well — you can run them together.
148
148
 
149
- ### Optional Throughline auditor context canary
149
+ ### Throughline auditor context (default-on)
150
150
 
151
- Fresh project installs keep auditor context **disabled**. This is a project-owned
152
- opt-in, not a production-wide default: the current candidate is a canary in the
153
- Spotter repository only. Re-running `spotter install` without auditor-context
154
- options preserves the project's existing setting. Disable it again with:
151
+ When `spotter install` can resolve Throughline on PATH to an absolute executable,
152
+ auditor context is enabled by default. A normal reinstall migrates legacy markers
153
+ whose disabled state came from the old default. An explicit project opt-out is
154
+ stored with `origin: explicit` and is never silently re-enabled. Disable it with:
155
155
 
156
156
  ```bash
157
157
  spotter install -y --auditor-context disabled
158
158
  ```
159
159
 
160
- To opt a project in, configure a direct absolute Throughline executable and any
161
- leading arguments with repeatable `--throughline-arg`:
160
+ When automatic discovery is unavailable, configure a direct absolute Throughline
161
+ executable and any leading arguments with repeatable `--throughline-arg`:
162
162
 
163
163
  ```bash
164
164
  spotter install -y --auditor-context throughline \
@@ -195,8 +195,8 @@ spotter auditor model-matrix --fixtures test/fixtures/auditor-model-matrix.v2.js
195
195
  --recent-turns 2 --body-cap 600
196
196
  ```
197
197
 
198
- Current evaluation conclusion is N=2 / 600. It is not a rollout approval: the
199
- Spotter-repository canary still lacks its 7-day and 30-fresh-result gates.
198
+ The evaluated setting is N=2 / 600. Default-on is final; the 7-day,
199
+ 30-fresh-result sample is used only to identify precision improvements.
200
200
 
201
201
  ## Common commands
202
202
 
package/bin/spotter.mjs CHANGED
@@ -25,6 +25,8 @@ Usage:
25
25
  [--throughline-command ABS] [--throughline-arg VALUE]
26
26
  register hooks in <cwd>/.claude/settings.json
27
27
  and create <cwd>/.spotter/marker.json
28
+ (Throughline on PATH enables context by default;
29
+ pass disabled for a persistent project opt-out)
28
30
  (run inside each project you want audited)
29
31
  spotter install --user [-y] legacy: register globally in ~/.claude/settings.json
30
32
  (NOT RECOMMENDED — fires for every Claude Code session
@@ -175,8 +177,8 @@ function parseInstallArgs(argv) {
175
177
  if (mode === 'disabled' && (command !== null || args.length > 0)) throw invalidInstallArgs();
176
178
  if (mode === 'throughline' && (command === null || !isAbsoluteCommand(command) || isShellWrapper(command))) throw invalidInstallArgs();
177
179
  const auditorContext = mode === null ? undefined : mode === 'disabled'
178
- ? { mode: 'disabled' }
179
- : { mode: 'throughline', command, args };
180
+ ? { mode: 'disabled', origin: 'explicit' }
181
+ : { mode: 'throughline', command, args, origin: 'explicit' };
180
182
  return { target, autoYes, auditorContext };
181
183
  }
182
184
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-spotter",
3
- "version": "1.4.20",
3
+ "version": "1.4.22",
4
4
  "description": "Audit agent running alongside Claude Code that catches missed tool calls — 気づく役と実行する役の分離",
5
5
  "type": "module",
6
6
  "bin": {
@@ -152,7 +152,14 @@ export async function inspectAuditorContextConfiguration({
152
152
  }
153
153
 
154
154
  const config = marker?.auditorContext;
155
- if (config === undefined || config?.mode === 'disabled') return disabled;
155
+ if (config === undefined) return disabled;
156
+ if (config?.mode === 'disabled') {
157
+ if (config.origin === 'explicit') return { ok: true, mode: 'disabled', detail: 'explicit project opt-out' };
158
+ if (config.reason === 'throughline_unavailable') {
159
+ return { ok: true, mode: 'disabled', detail: 'default disabled: Throughline unavailable' };
160
+ }
161
+ return disabled;
162
+ }
156
163
  if (config?.mode !== 'throughline') {
157
164
  return { ok: false, mode: 'unknown', detail: 'invalid configuration' };
158
165
  }
@@ -12,10 +12,11 @@
12
12
  // Install seeds the DB automatically via `refresh` (project-mode only — user-mode
13
13
  // has no projectRoot so DB seeding is skipped there).
14
14
 
15
- import { mkdir, writeFile, readFile, access } from 'node:fs/promises';
15
+ import { mkdir, writeFile, readFile, access, realpath } from 'node:fs/promises';
16
+ import { constants as fsConstants } from 'node:fs';
16
17
  import { spawnSync } from 'node:child_process';
17
18
  import { homedir } from 'node:os';
18
- import { join, resolve, dirname } from 'node:path';
19
+ import { join, resolve, dirname, delimiter, extname } from 'node:path';
19
20
  import { fileURLToPath } from 'node:url';
20
21
  import { createInterface } from 'node:readline/promises';
21
22
  import { version as SPOTTER_VERSION } from '../version.mjs';
@@ -29,7 +30,7 @@ const SPOTTER_BIN = join(PACKAGE_ROOT, 'bin', 'spotter.mjs');
29
30
 
30
31
  const SPOTTER_HOME = join(homedir(), '.spotter');
31
32
 
32
- const MARKER_VERSION = '1';
33
+ const MARKER_VERSION = '2';
33
34
 
34
35
  // v1.3.0: UserPromptSubmit / Stop を 60s に統一。理由:
35
36
  // - daemon 側 Haiku timeout は 45s (DEFAULT_HAIKU_TIMEOUT_MS @ daemon.mjs)
@@ -58,6 +59,7 @@ export async function runInstall({
58
59
  codexCliPresentFn = isCodexCliPresent,
59
60
  installCodexHooksFn = installCodexHooks,
60
61
  auditorContext,
62
+ resolveDefaultAuditorContextFn = resolveDefaultAuditorContext,
61
63
  } = {}) {
62
64
  const settingsPath = target === 'user'
63
65
  ? join(homedir(), '.claude', 'settings.json')
@@ -86,8 +88,8 @@ export async function runInstall({
86
88
  const markerPath = join(markerDir, 'marker.json');
87
89
  await mkdir(markerDir, { recursive: true });
88
90
  const preservedAuditorContext = auditorContext === undefined
89
- ? await readExistingAuditorContext(markerPath)
90
- : auditorContext;
91
+ ? await readExistingAuditorContext(markerPath, resolveDefaultAuditorContextFn)
92
+ : { ...auditorContext, origin: 'explicit' };
91
93
  const marker = {
92
94
  markerVersion: MARKER_VERSION,
93
95
  spotterVersion: SPOTTER_VERSION,
@@ -96,6 +98,13 @@ export async function runInstall({
96
98
  };
97
99
  await writeFile(markerPath, JSON.stringify(marker, null, 2) + '\n', 'utf8');
98
100
  console.log(` wrote ${markerPath}`);
101
+ console.log(` auditor context: ${preservedAuditorContext.mode} (${preservedAuditorContext.origin})`);
102
+ if (preservedAuditorContext.mode === 'throughline') {
103
+ console.log(' sends bounded completed user/assistant text to the selected Codex auditor; disable with:');
104
+ console.log(' spotter install -y --auditor-context disabled');
105
+ } else if (preservedAuditorContext.reason === 'throughline_unavailable') {
106
+ console.log(' Throughline was not found; context auditing remains disabled (no current-only fallback)');
107
+ }
99
108
  }
100
109
 
101
110
  // 4. compute desired settings.json with hooks
@@ -176,16 +185,75 @@ export async function runInstall({
176
185
  }
177
186
  }
178
187
 
179
- async function readExistingAuditorContext(markerPath) {
188
+ async function readExistingAuditorContext(markerPath, resolveDefaultAuditorContextFn) {
180
189
  try {
181
190
  const marker = JSON.parse(await readFile(markerPath, 'utf8'));
182
- return marker?.auditorContext ?? { mode: 'disabled' };
191
+ const existing = marker?.auditorContext;
192
+ if (existing?.mode === 'throughline') {
193
+ return { ...existing, origin: existing.origin ?? 'explicit' };
194
+ }
195
+ if (existing?.mode === 'disabled' && existing.origin === 'explicit') {
196
+ return existing;
197
+ }
198
+ return resolveDefaultAuditorContextFn();
183
199
  } catch (err) {
184
- if (err?.code === 'ENOENT') return { mode: 'disabled' };
200
+ if (err?.code === 'ENOENT') return resolveDefaultAuditorContextFn();
185
201
  throw err;
186
202
  }
187
203
  }
188
204
 
205
+ export async function resolveDefaultAuditorContext({
206
+ env = process.env,
207
+ platform = process.platform,
208
+ nodePath = process.execPath,
209
+ accessFn = access,
210
+ realpathFn = realpath,
211
+ } = {}) {
212
+ const pathValue = platform === 'win32' ? (env.Path ?? env.PATH ?? '') : (env.PATH ?? '');
213
+ const pathSeparator = platform === 'win32' ? ';' : delimiter;
214
+ const names = platform === 'win32'
215
+ ? ['throughline.exe', 'throughline.cmd', 'throughline.bat', 'throughline']
216
+ : ['throughline'];
217
+
218
+ for (const directory of pathValue.split(pathSeparator).filter(Boolean)) {
219
+ for (const name of names) {
220
+ const candidate = join(directory, name);
221
+ if (!await canExecute(candidate, accessFn)) continue;
222
+ const extension = extname(candidate).toLowerCase();
223
+ if (extension === '.cmd' || extension === '.bat') {
224
+ const script = join(directory, 'node_modules', 'throughline', 'bin', 'throughline.mjs');
225
+ if (!await canRead(script, accessFn)) continue;
226
+ return { mode: 'throughline', command: nodePath, args: [script], origin: 'default' };
227
+ }
228
+ return {
229
+ mode: 'throughline',
230
+ command: await realpathFn(candidate),
231
+ args: [],
232
+ origin: 'default',
233
+ };
234
+ }
235
+ }
236
+ return { mode: 'disabled', origin: 'default', reason: 'throughline_unavailable' };
237
+ }
238
+
239
+ async function canExecute(path, accessFn) {
240
+ try {
241
+ await accessFn(path, fsConstants.X_OK);
242
+ return true;
243
+ } catch {
244
+ return false;
245
+ }
246
+ }
247
+
248
+ async function canRead(path, accessFn) {
249
+ try {
250
+ await accessFn(path, fsConstants.R_OK);
251
+ return true;
252
+ } catch {
253
+ return false;
254
+ }
255
+ }
256
+
189
257
  export function codexInstallNextSteps(projectRoot) {
190
258
  return [
191
259
  'review the three Spotter hooks with Codex /hooks',