claude-spotter 1.4.28 → 1.5.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 +43 -0
- package/README.ja.md +31 -0
- package/README.md +32 -0
- package/bin/spotter.mjs +15 -0
- package/docs/11_dashboard-operations.md +118 -0
- package/ops/dashboard/hub-config.json +1 -0
- package/ops/dashboard/launchd/dev.kitepon.spotter-dashboard-device.plist +21 -0
- package/ops/dashboard/launchd/dev.kitepon.spotter-dashboard-tunnel.plist +23 -0
- package/ops/dashboard/systemd/spotter-dashboard-device.service +13 -0
- package/ops/dashboard/systemd/spotter-dashboard-hub.service +12 -0
- package/ops/dashboard/systemd/spotter-dashboard-tunnel.service +14 -0
- package/ops/dashboard/windows/install-dashboard-tasks.ps1 +16 -0
- package/ops/dashboard/windows/spotter-dashboard-device.ps1 +4 -0
- package/ops/dashboard/windows/spotter-dashboard-tunnel.ps1 +7 -0
- package/package.json +9 -2
- package/scripts/verify-release-commit.mjs +50 -0
- package/scripts/verify-release-commit.test.mjs +48 -0
- package/src/cli/codex-hook-cmd.mjs +180 -1
- package/src/cli/dashboard-cmd.mjs +129 -0
- package/src/cli/diagnostics-cmd.mjs +1 -1
- package/src/cli/evaluation-cmd.mjs +129 -0
- package/src/core/codex-transcript.mjs +13 -3
- package/src/core/evaluation-context.mjs +184 -0
- package/src/core/evaluation-report.mjs +84 -0
- package/src/core/evaluation-store.mjs +461 -0
- package/src/core/evaluation-tool-id.mjs +382 -0
- package/src/daemon/daemon.mjs +93 -4
- package/src/dashboard/device-server.mjs +216 -0
- package/src/dashboard/hub-server.mjs +214 -0
- package/src/dashboard/model.mjs +14 -0
- package/src/dashboard/render.mjs +225 -0
- package/src/hooks/pre-tool-use.mjs +57 -1
- package/src/hooks/user-prompt.mjs +83 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.0 — 2026-08-04
|
|
4
|
+
|
|
5
|
+
- **評価結果を端末別Web dashboardで表示。** 各端末の`~/.spotter/evaluation.db`をrequestごとに読み、
|
|
6
|
+
`S/P/I/C/A/M`、提案率`P/S`、採用率`A/C`、project/tool内訳、非採用caseと詳細を
|
|
7
|
+
dependency-freeのserver-side HTMLで表示する。request、auditorへ渡したcontext、Throughline
|
|
8
|
+
observer snapshotは混同せず別欄に置く。
|
|
9
|
+
- **端末選択hubを追加。** 静的な4端末mapから一覧を表示し、`/devices/<id>/`を各device serverへ
|
|
10
|
+
proxyする。healthは一覧request時に各端末1回だけ取得し、offlineやtimeoutは該当端末だけ502にする。
|
|
11
|
+
DB同期、background monitor、retry queue、reconcilerは追加しない。
|
|
12
|
+
- **運用面を同梱。** `spotter dashboard device` / `hub`、Mac LaunchAgent、Linux systemd user、
|
|
13
|
+
Windows Task Scheduler、SSH reverse tunnel、main-server hub config、Caddy/Cloudflare Accessの
|
|
14
|
+
配備手順を追加した。評価データは端末内に留め、公開面だけをowner emailのAccessで保護する。
|
|
15
|
+
- **検証。** dashboard focused test 16件、fixture SQLiteからdevice HTTP・hub proxyまでの経路、
|
|
16
|
+
full suite 584件(582 pass / 2 platform skip)を通過した。
|
|
17
|
+
|
|
18
|
+
## 1.4.29 — 2026-08-04
|
|
19
|
+
|
|
20
|
+
- **提案率とtool採用率の端末内評価を追加。** Claude / Codexの成功UserPromptSubmitを母数に、
|
|
21
|
+
safe projector後に実際に提示したtool itemと、同じturnでのcanonical tool利用を
|
|
22
|
+
`~/.spotter/evaluation.db`へ記録する。採用率の母数はoutcome確定itemだけとし、利用記録不全は
|
|
23
|
+
`outcome_missing`へ分離する。組込みtoolは評価対象外で、既存turn-end auditorのraw usedToolsは変更しない。
|
|
24
|
+
- **提案時の別文脈を保存。** proposal確定時刻を記録し、既存Throughline `observer-read`を一度だけ呼ぶ。
|
|
25
|
+
Spotterがauditorへ渡したcontextとは別fieldに保存し、失敗時はcontextだけunavailableとしてretryしない。
|
|
26
|
+
返却されたhost / thread hashを提案元sessionと照合し、同じprojectの並行sessionを取り違えた場合も
|
|
27
|
+
snapshotを保存しない。
|
|
28
|
+
- **欠測とtool identityを率から分離。** Claudeの利用記録失敗、Codex transcript不全、Stopが30分以上
|
|
29
|
+
欠落したopen proposalは`outcome_missing`として投影し、非採用へ混ぜない。Codex Skillは提案済みの
|
|
30
|
+
正規`SKILL.md`を実際にreadしたcallだけを採用として認識する。現行Codexのouter `exec`内で実行される
|
|
31
|
+
nested MCP callと、同じturn内の複数`exec`も個別の利用入力として保持する。
|
|
32
|
+
- **初回同時openを直列化。** 2 projectが未作成の評価DBを同時に開いてもWAL設定前に
|
|
33
|
+
`database is locked`とならないよう、bounded busy timeoutをschema初期化より先に有効化する。
|
|
34
|
+
retry、lockfile、background回収は追加しない。
|
|
35
|
+
- **評価CLIを追加。** `spotter evaluation report`で全project / project / tool / host別の
|
|
36
|
+
`S/P/I/C/A/M`、`P/S`、`A/C`を表示し、`cases` / `case`で非採用caseのrequest、2種類の文脈、
|
|
37
|
+
提案ID、利用ID、outcomeを確認できる。reportは保存済みSQLiteだけを読み、JSONはWindows
|
|
38
|
+
PowerShell 5.1でも壊れないASCII-safe形式で出力する。
|
|
39
|
+
- **検証・公開。** 固定fixture、Claude / Codex hook、Throughline束縛、2 process同時writeを含む
|
|
40
|
+
568 tests(macOS / Linuxは566 pass / 2 skip、Windowsは556 pass / 12 platform skip)、
|
|
41
|
+
macOS / Linux / Windows × Node 22.13 / 22.xのCI run `30913375991`、75-file packを通過した。
|
|
42
|
+
別projectの実Codex turnではThroughline文脈付きproposalから
|
|
43
|
+
`S=1 P=1 I=1 C=1 A=1 M=0`を記録し、Claude実turnのpass観測と両hostのproposal fixtureも確認した。
|
|
44
|
+
Windowsではouter `exec`内の絶対pathを壊さずSkill採用へ変換する回帰も固定した。
|
|
45
|
+
|
|
3
46
|
## 1.4.28 — 2026-07-21
|
|
4
47
|
|
|
5
48
|
- **Windows Codex SessionStartの誤timeoutを修正。** detached refreshを起動するSpotter所有hookの
|
package/README.ja.md
CHANGED
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
|
|
14
14
|
> **気づく役と実行する役を分離する。** Claude Code の横で並走し、主役の Claude が**ツールを呼び忘れたとき**だけ静かに指摘する監査役。
|
|
15
15
|
|
|
16
|
+
[kitepon.dev](https://kitepon.dev/)を運営する[クオ(@QLyun35332)](https://x.com/QLyun35332)が
|
|
17
|
+
開発・メンテナンスしています。
|
|
18
|
+
|
|
19
|
+
## 所有境界
|
|
20
|
+
|
|
21
|
+
本repositoryは監査挙動、project marker、installer、release、diagnosticsを所有します。
|
|
22
|
+
製品横断のcatalogとhost統合は、kitepon.devの製品開発を支える内部基盤
|
|
23
|
+
[dotagents](https://github.com/kitepon-rgb/dotagents)が担当します。
|
|
24
|
+
MarkItDownは別区分の第三者CLIです。
|
|
25
|
+
|
|
16
26
|
Claude には「使えるツールがあるのに、使うべきタイミングで使わない」という構造的な弱点があります。記録すべき決定を memory / caveat MCP に残さない、docs lookup MCP を呼ばずに古い知識で応答する、ブラウザ自動化 MCP で確認せず UI 状態を推測する — **「分からないと自覚できない」から、ツールを取りに行けない**。
|
|
17
27
|
|
|
18
28
|
Spotter はツールカタログを完全に把握した別の監査エージェントで、ユーザー入力と主役 AI の応答を並走監査します。自動選択では Claude host は Codex CLI があればそれを、なければ session-scoped Haiku を選び、Codex host は Codex CLI を既定にします。明示 backend override は host より優先しますが、runtime failure で別 backend へ黙って切り替えません。応答前は検証済みtool IDだけを固定・非命令形の助言へ変換でき、応答後のfindingは構造eventに留めて後続turnへ注入しません。監査用AIの自由文が親セッションへ入ることはありません。**主役 AI が自覚して自己監査する**設計は本プロダクトの存在意義を破壊するため、hook 経由でその意思と独立に検出します。
|
|
@@ -207,6 +217,16 @@ spotter db rebuild # Claude local + Claude global DB を両方消してか
|
|
|
207
217
|
spotter status # 稼働中の daemon 一覧
|
|
208
218
|
spotter doctor # 環境診断 (Node / claude CLI / Codex readiness / tool-db 整合性)
|
|
209
219
|
spotter diagnostics logs # daemon log から pass=false / backend latency / anomaly signal を集計
|
|
220
|
+
spotter evaluation report
|
|
221
|
+
# 端末内DBからproject横断の提案率・tool採用率を集計
|
|
222
|
+
spotter evaluation cases --outcome not-adopted
|
|
223
|
+
# 提案されたが同じturnで使われなかったtool itemを一覧
|
|
224
|
+
spotter evaluation case <observation-id>
|
|
225
|
+
# request、2種類の文脈、提案、利用、outcomeを確認
|
|
226
|
+
spotter dashboard device --id mac --name Mac
|
|
227
|
+
# この端末の評価DBを127.0.0.1:53940で配信
|
|
228
|
+
spotter dashboard hub --config dashboard-hub.json --host 172.18.0.1
|
|
229
|
+
# 端末一覧と/devices/<id>/の端末別proxyを配信
|
|
210
230
|
spotter codex risk-check --findings findings.json --host-agent claude
|
|
211
231
|
# Spotter finding を codex-sidecar に渡して read-only risk analysis
|
|
212
232
|
spotter codex review|explore|opinion --findings findings.json --host-agent claude
|
|
@@ -223,6 +243,17 @@ spotter auditor model-matrix --fixtures test/fixtures/auditor-model-matrix.v2.js
|
|
|
223
243
|
spotter uninstall # hook 登録を解除 (~/.spotter は残す)
|
|
224
244
|
```
|
|
225
245
|
|
|
246
|
+
### 端末別評価dashboard
|
|
247
|
+
|
|
248
|
+
dashboardはlocal-firstで動く。各端末が自身の`~/.spotter/evaluation.db`を読み、hubは固定の
|
|
249
|
+
端末・upstream対応だけを持つ。評価データをcloud DBへ複製しない。端末画面では
|
|
250
|
+
`S/P/I/C/A/M`、提案率、採用率、project/tool内訳、非採用case、別々に取得した2種類の文脈を
|
|
251
|
+
確認できる。health確認は端末一覧request時だけなので、端末がofflineでもbackground監視や
|
|
252
|
+
retry queueを作らず、その端末だけを切り離せる。
|
|
253
|
+
|
|
254
|
+
4端末のservice、reverse tunnel、Caddy/Cloudflare構成は
|
|
255
|
+
[docs/11_dashboard-operations.md](docs/11_dashboard-operations.md)を参照。
|
|
256
|
+
|
|
226
257
|
Codex risk dispatch を daemon から非同期に流す場合:
|
|
227
258
|
|
|
228
259
|
```bash
|
package/README.md
CHANGED
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
|
|
14
14
|
> **Separate the spotter from the doer.** Spotter runs alongside Claude Code and quietly flags the moments when your primary Claude **forgets to use a tool it has access to**.
|
|
15
15
|
|
|
16
|
+
Built and maintained by [Quo](https://x.com/QLyun35332) at [kitepon.dev](https://kitepon.dev/en).
|
|
17
|
+
|
|
18
|
+
## Ownership boundary
|
|
19
|
+
|
|
20
|
+
This repository owns auditor behavior, project markers, installers, release,
|
|
21
|
+
and diagnostics. The cross-product catalog and host integration are handled by
|
|
22
|
+
[dotagents](https://github.com/kitepon-rgb/dotagents), the internal development
|
|
23
|
+
toolchain behind kitepon.dev's products.
|
|
24
|
+
MarkItDown is managed separately as a third-party CLI.
|
|
25
|
+
|
|
16
26
|
Claude has a structural blind spot: **it can't reach for a tool it doesn't realize it needs**. It may skip a project memory MCP when a decision should be recorded, answer from stale memory instead of a docs-lookup MCP, or reason about UI state without a browser-automation MCP. The model can't always tell when it doesn't know — so the tool stays unused.
|
|
17
27
|
|
|
18
28
|
Spotter runs a separate auditor with the full tool catalog and checks both the user's prompt and the primary agent's reply. Automatic selection uses Codex CLI on a Claude host when available, otherwise the session-scoped Haiku path; on a Codex host it defaults to Codex CLI. An explicit backend override takes precedence, but a runtime failure never silently switches backend. Before the primary reply, validated tool IDs may become fixed, non-directive advice; after the reply, findings remain structured events and are not injected into a later turn. Auditor prose never enters the parent session. **The primary agent is never asked to self-audit** — that would defeat the premise. Detection happens through hooks, independent of the primary agent's intent.
|
|
@@ -216,6 +226,16 @@ spotter doctor # environment check (Node / claude CLI / Codex readines
|
|
|
216
226
|
spotter diagnostics logs # summarize daemon logs for pass=false / backend latency / anomaly signals
|
|
217
227
|
spotter diagnostics runtime-errors
|
|
218
228
|
# print the local allow-listed runtime-error aggregate snapshot (no network)
|
|
229
|
+
spotter evaluation report
|
|
230
|
+
# show cross-project proposal/adoption counts and rates from the local DB
|
|
231
|
+
spotter evaluation cases --outcome not-adopted
|
|
232
|
+
# list proposed tools that were not used in the same turn
|
|
233
|
+
spotter evaluation case <observation-id>
|
|
234
|
+
# inspect request, Spotter context, Throughline snapshot, proposal, usage, and outcome
|
|
235
|
+
spotter dashboard device --id mac --name Mac
|
|
236
|
+
# serve this terminal's local evaluation DB on 127.0.0.1:53940
|
|
237
|
+
spotter dashboard hub --config dashboard-hub.json --host 172.18.0.1
|
|
238
|
+
# list terminals and proxy /devices/<id>/ to their local servers
|
|
219
239
|
spotter codex risk-check --findings findings.json --host-agent claude
|
|
220
240
|
# run read-only codex-sidecar risk analysis for Spotter findings
|
|
221
241
|
spotter codex review|explore|opinion --findings findings.json --host-agent claude
|
|
@@ -232,6 +252,18 @@ spotter auditor model-matrix --fixtures test/fixtures/auditor-model-matrix.v2.js
|
|
|
232
252
|
spotter uninstall # remove hooks from this project (leaves ~/.spotter intact)
|
|
233
253
|
```
|
|
234
254
|
|
|
255
|
+
### Device-routed evaluation dashboard
|
|
256
|
+
|
|
257
|
+
The dashboard is local-first. Every terminal reads its own `~/.spotter/evaluation.db`; the hub
|
|
258
|
+
keeps only a static device-to-upstream map and does not copy evaluation data into a cloud database.
|
|
259
|
+
The device view shows `S/P/I/C/A/M`, proposal and adoption rates, project/tool breakdowns,
|
|
260
|
+
non-adopted cases, and the two separately captured context sources. The hub checks health only
|
|
261
|
+
when the device list is requested, so an offline terminal is isolated without a background monitor
|
|
262
|
+
or retry queue.
|
|
263
|
+
|
|
264
|
+
The reference four-terminal service, reverse-tunnel, and Caddy/Cloudflare layout is documented in
|
|
265
|
+
[docs/11_dashboard-operations.md](docs/11_dashboard-operations.md).
|
|
266
|
+
|
|
235
267
|
Optional async Codex risk dispatch:
|
|
236
268
|
|
|
237
269
|
```bash
|
package/bin/spotter.mjs
CHANGED
|
@@ -11,6 +11,8 @@ import { runCodexCommand } from '../src/cli/codex-cmd.mjs';
|
|
|
11
11
|
import { runCodexHookCommand } from '../src/cli/codex-hook-cmd.mjs';
|
|
12
12
|
import { runAuditorCommand } from '../src/cli/auditor-cmd.mjs';
|
|
13
13
|
import { runDiagnosticsCommand } from '../src/cli/diagnostics-cmd.mjs';
|
|
14
|
+
import { runEvaluationCommand } from '../src/cli/evaluation-cmd.mjs';
|
|
15
|
+
import { runDashboardCommand } from '../src/cli/dashboard-cmd.mjs';
|
|
14
16
|
import { runFactoryDiagnostics } from '../src/cli/factory-diagnostics.mjs';
|
|
15
17
|
import { runDaemonStart } from '../src/cli/daemon-cmd.mjs';
|
|
16
18
|
import { runSessionStart } from '../src/hooks/session-start.mjs';
|
|
@@ -45,6 +47,13 @@ Usage:
|
|
|
45
47
|
spotter diagnostics logs [--json] summarize daemon logs for precision diagnostics
|
|
46
48
|
spotter diagnostics runtime-errors [snapshot|ack|resolve|reopen|compact]
|
|
47
49
|
consume the local allow-listed aggregate store
|
|
50
|
+
spotter evaluation report [filters] [--json]
|
|
51
|
+
spotter evaluation cases --outcome OUTCOME [filters] [--json]
|
|
52
|
+
spotter evaluation case OBSERVATION_ID [--json]
|
|
53
|
+
read saved proposal-adoption observations
|
|
54
|
+
spotter dashboard device --id ID [--name NAME] [--host HOST] [--port PORT] [--db PATH]
|
|
55
|
+
spotter dashboard hub --config FILE [--host HOST] [--port PORT]
|
|
56
|
+
serve the local device-routed evaluation dashboard
|
|
48
57
|
spotter codex risk-check --findings FILE
|
|
49
58
|
run read-only codex-sidecar risk analysis
|
|
50
59
|
spotter codex review|explore|opinion --findings FILE
|
|
@@ -124,6 +133,12 @@ async function main() {
|
|
|
124
133
|
}
|
|
125
134
|
await runDiagnosticsCommand({ argv: rest });
|
|
126
135
|
return;
|
|
136
|
+
case 'evaluation':
|
|
137
|
+
await runEvaluationCommand({ argv: rest });
|
|
138
|
+
return;
|
|
139
|
+
case 'dashboard':
|
|
140
|
+
await runDashboardCommand({ argv: rest });
|
|
141
|
+
return;
|
|
127
142
|
case 'daemon': {
|
|
128
143
|
const sub = rest[0];
|
|
129
144
|
if (sub === 'start') { await runDaemonStart({ argv: rest.slice(1) }); return; }
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Spotter評価dashboard運用
|
|
2
|
+
|
|
3
|
+
## 固定構成
|
|
4
|
+
|
|
5
|
+
各端末のdevice serverは`127.0.0.1:53940`だけで待ち受ける。main-serverのhubは
|
|
6
|
+
Docker Caddyから到達できる`172.18.0.1:53940`で待ち受ける。評価DBは各端末の
|
|
7
|
+
`~/.spotter/evaluation.db`をその場で読み、端末外へ複製しない。
|
|
8
|
+
|
|
9
|
+
| 端末 | device ID | main-server側upstream |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| main-server Ubuntu | `main-server` | `127.0.0.1:53940` |
|
|
12
|
+
| Mac | `mac` | `127.0.0.1:53941` |
|
|
13
|
+
| FOX WSL2 | `fox-wsl` | `127.0.0.1:53942` |
|
|
14
|
+
| FOX Windows native | `fox-windows` | `127.0.0.1:53943` |
|
|
15
|
+
|
|
16
|
+
hub設定の正本は`ops/dashboard/hub-config.json`である。hubは一覧request時に各upstreamの
|
|
17
|
+
healthを1回だけ確認し、端末をonline/offline表示する。常時監視、再試行queue、DB同期は行わない。
|
|
18
|
+
|
|
19
|
+
## Mac
|
|
20
|
+
|
|
21
|
+
`ops/dashboard/launchd/`の2 plistを`~/Library/LaunchAgents/`へ配置する。既存labelを更新する時は
|
|
22
|
+
`launchctl bootout gui/$(id -u) <plist>`の後、`launchctl bootstrap gui/$(id -u) <plist>`を実行する。
|
|
23
|
+
deviceはMac実DB、tunnelはmain-serverの`53941`へ接続する。
|
|
24
|
+
|
|
25
|
+
確認:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
curl --fail http://127.0.0.1:53940/_spotter/health
|
|
29
|
+
ssh main-server curl --fail http://127.0.0.1:53941/_spotter/health
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## main-server Ubuntu
|
|
33
|
+
|
|
34
|
+
`ops/dashboard/systemd/spotter-dashboard-device.service`と
|
|
35
|
+
`spotter-dashboard-hub.service`を`~/.config/systemd/user/`へ配置する。
|
|
36
|
+
`~/.config/spotter/dashboard-device.env`は次の2行、hub configはrepo正本をコピーする。
|
|
37
|
+
|
|
38
|
+
```ini
|
|
39
|
+
SPOTTER_DEVICE_ID=main-server
|
|
40
|
+
SPOTTER_DEVICE_NAME=main-server
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
systemctl --user daemon-reload
|
|
45
|
+
systemctl --user enable --now spotter-dashboard-device.service spotter-dashboard-hub.service
|
|
46
|
+
curl --fail http://127.0.0.1:53940/_spotter/health
|
|
47
|
+
curl --fail http://172.18.0.1:53940/
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
systemd user managerの`PATH`から`spotter`が見えない場合だけ、ユーザー環境の実際のnpm binを
|
|
51
|
+
unitの`Environment=PATH=...`として追加する。別の起動経路へfallbackしない。
|
|
52
|
+
|
|
53
|
+
## FOX WSL2
|
|
54
|
+
|
|
55
|
+
device unitとtunnel unitを`~/.config/systemd/user/`へ配置する。device env:
|
|
56
|
+
|
|
57
|
+
```ini
|
|
58
|
+
SPOTTER_DEVICE_ID=fox-wsl
|
|
59
|
+
SPOTTER_DEVICE_NAME=FOX-WSL2
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
tunnel env:
|
|
63
|
+
|
|
64
|
+
```ini
|
|
65
|
+
SPOTTER_REMOTE_FORWARD=127.0.0.1:53942:127.0.0.1:53940
|
|
66
|
+
SPOTTER_TUNNEL_TARGET=main-server
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
systemctl --user daemon-reload
|
|
71
|
+
systemctl --user enable --now spotter-dashboard-device.service spotter-dashboard-tunnel.service
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## FOX Windows native
|
|
75
|
+
|
|
76
|
+
管理者権限は不要。`ops/dashboard/windows/`を固定pathへ配置し、Windows側から解決できる
|
|
77
|
+
main-serverのSSH host名またはIPを明示して次を1回実行する。
|
|
78
|
+
|
|
79
|
+
```powershell
|
|
80
|
+
.\install-dashboard-tasks.ps1 -MainServer '<main-server-host-or-ip>'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
登録される2 taskはログオン時にdevice serverとreverse tunnelを起動する。確認:
|
|
84
|
+
|
|
85
|
+
```powershell
|
|
86
|
+
Invoke-RestMethod http://127.0.0.1:53940/_spotter/health
|
|
87
|
+
Get-ScheduledTask -TaskName 'Spotter dashboard *' | Get-ScheduledTaskInfo
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 公開経路
|
|
91
|
+
|
|
92
|
+
Caddyへ次を追加する。`spotter.kitepon.dev`はcase詳細に会話文脈を含むため、Cloudflare側では
|
|
93
|
+
同hostname全体をAccess applicationの対象にし、owner emailだけをallowする。
|
|
94
|
+
|
|
95
|
+
```caddyfile
|
|
96
|
+
spotter.kitepon.dev {
|
|
97
|
+
import cf-origin-tls
|
|
98
|
+
import security-headers-base
|
|
99
|
+
reverse_proxy 172.18.0.1:53940
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
既存Cloudflare Tunnelのcatch-all 404より前に、hostname `spotter.kitepon.dev`、service
|
|
104
|
+
`https://caddy:443`、`noTLSVerify: true`、`matchSNItoHost: true`のingressを追加する。
|
|
105
|
+
DNSは同tunnel UUIDの`cfargotunnel.com`へCNAMEする。
|
|
106
|
+
|
|
107
|
+
## 受入と切り分け
|
|
108
|
+
|
|
109
|
+
確認順序はdevice loopback、main-serverの各upstream、hub、Caddy、Cloudflare Tunnel、Accessとする。
|
|
110
|
+
端末がofflineならhub一覧のその端末だけがofflineになる。選択時の502は該当upstreamのserviceまたは
|
|
111
|
+
reverse tunnelを確認する。hubや別端末を再起動する必要はない。
|
|
112
|
+
|
|
113
|
+
公開受入:
|
|
114
|
+
|
|
115
|
+
1. 未認証`https://spotter.kitepon.dev/`がCloudflare Accessへredirectされる。
|
|
116
|
+
2. 認証後の`/`が4端末を表示する。
|
|
117
|
+
3. online端末のoverview、project/tool内訳、非採用case、case詳細を表示できる。
|
|
118
|
+
4. 1端末を停止しても一覧と他端末が表示でき、停止端末だけoffline/502になる。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"devices":[{"id":"main-server","name":"main-server","upstream":"http://127.0.0.1:53940"},{"id":"mac","name":"Mac","upstream":"http://127.0.0.1:53941"},{"id":"fox-wsl","name":"FOX WSL2","upstream":"http://127.0.0.1:53942"},{"id":"fox-windows","name":"FOX Windows native","upstream":"http://127.0.0.1:53943"}]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>Label</key><string>dev.kitepon.spotter-dashboard-device</string>
|
|
6
|
+
<key>ProgramArguments</key>
|
|
7
|
+
<array>
|
|
8
|
+
<string>/opt/homebrew/bin/spotter</string>
|
|
9
|
+
<string>dashboard</string><string>device</string>
|
|
10
|
+
<string>--id</string><string>mac</string>
|
|
11
|
+
<string>--name</string><string>Mac</string>
|
|
12
|
+
<string>--host</string><string>127.0.0.1</string>
|
|
13
|
+
<string>--port</string><string>53940</string>
|
|
14
|
+
</array>
|
|
15
|
+
<key>RunAtLoad</key><true/>
|
|
16
|
+
<key>KeepAlive</key><true/>
|
|
17
|
+
<key>ThrottleInterval</key><integer>10</integer>
|
|
18
|
+
<key>StandardOutPath</key><string>/Users/kite/.spotter/dashboard-device.log</string>
|
|
19
|
+
<key>StandardErrorPath</key><string>/Users/kite/.spotter/dashboard-device.log</string>
|
|
20
|
+
</dict>
|
|
21
|
+
</plist>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>Label</key><string>dev.kitepon.spotter-dashboard-tunnel</string>
|
|
6
|
+
<key>ProgramArguments</key>
|
|
7
|
+
<array>
|
|
8
|
+
<string>/usr/bin/ssh</string><string>-N</string>
|
|
9
|
+
<string>-o</string><string>BatchMode=yes</string>
|
|
10
|
+
<string>-o</string><string>ExitOnForwardFailure=yes</string>
|
|
11
|
+
<string>-o</string><string>ServerAliveInterval=30</string>
|
|
12
|
+
<string>-o</string><string>ServerAliveCountMax=3</string>
|
|
13
|
+
<string>-o</string><string>TCPKeepAlive=yes</string>
|
|
14
|
+
<string>-R</string><string>127.0.0.1:53941:127.0.0.1:53940</string>
|
|
15
|
+
<string>main-server</string>
|
|
16
|
+
</array>
|
|
17
|
+
<key>RunAtLoad</key><true/>
|
|
18
|
+
<key>KeepAlive</key><true/>
|
|
19
|
+
<key>ThrottleInterval</key><integer>10</integer>
|
|
20
|
+
<key>StandardOutPath</key><string>/Users/kite/.spotter/dashboard-tunnel.log</string>
|
|
21
|
+
<key>StandardErrorPath</key><string>/Users/kite/.spotter/dashboard-tunnel.log</string>
|
|
22
|
+
</dict>
|
|
23
|
+
</plist>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Spotter evaluation dashboard device server
|
|
3
|
+
After=network.target
|
|
4
|
+
|
|
5
|
+
[Service]
|
|
6
|
+
Type=simple
|
|
7
|
+
EnvironmentFile=%h/.config/spotter/dashboard-device.env
|
|
8
|
+
ExecStart=/usr/bin/env spotter dashboard device --id ${SPOTTER_DEVICE_ID} --name ${SPOTTER_DEVICE_NAME} --host 127.0.0.1 --port 53940
|
|
9
|
+
Restart=on-failure
|
|
10
|
+
RestartSec=10
|
|
11
|
+
|
|
12
|
+
[Install]
|
|
13
|
+
WantedBy=default.target
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Spotter evaluation dashboard hub
|
|
3
|
+
After=network.target
|
|
4
|
+
|
|
5
|
+
[Service]
|
|
6
|
+
Type=simple
|
|
7
|
+
ExecStart=/usr/bin/env spotter dashboard hub --config %h/.config/spotter/dashboard-hub.json --host 172.18.0.1 --port 53940
|
|
8
|
+
Restart=on-failure
|
|
9
|
+
RestartSec=10
|
|
10
|
+
|
|
11
|
+
[Install]
|
|
12
|
+
WantedBy=default.target
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Spotter evaluation dashboard reverse tunnel
|
|
3
|
+
After=network-online.target
|
|
4
|
+
Wants=network-online.target
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
Type=simple
|
|
8
|
+
EnvironmentFile=%h/.config/spotter/dashboard-tunnel.env
|
|
9
|
+
ExecStart=/usr/bin/ssh -N -o BatchMode=yes -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o TCPKeepAlive=yes -R ${SPOTTER_REMOTE_FORWARD} ${SPOTTER_TUNNEL_TARGET}
|
|
10
|
+
Restart=on-failure
|
|
11
|
+
RestartSec=10
|
|
12
|
+
|
|
13
|
+
[Install]
|
|
14
|
+
WantedBy=default.target
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
param([Parameter(Mandatory = $true)][string]$MainServer)
|
|
2
|
+
$ErrorActionPreference = 'Stop'
|
|
3
|
+
$ops = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
4
|
+
$powershell = "$env:WINDIR\System32\WindowsPowerShell\v1.0\powershell.exe"
|
|
5
|
+
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Limited
|
|
6
|
+
$settings = New-ScheduledTaskSettingsSet -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero)
|
|
7
|
+
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
|
|
8
|
+
|
|
9
|
+
$deviceAction = New-ScheduledTaskAction -Execute $powershell -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$ops\spotter-dashboard-device.ps1`""
|
|
10
|
+
Register-ScheduledTask -TaskName 'Spotter dashboard device' -Action $deviceAction -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
|
|
11
|
+
|
|
12
|
+
$tunnelAction = New-ScheduledTaskAction -Execute $powershell -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$ops\spotter-dashboard-tunnel.ps1`" -MainServer `"$MainServer`""
|
|
13
|
+
Register-ScheduledTask -TaskName 'Spotter dashboard tunnel' -Action $tunnelAction -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
|
|
14
|
+
|
|
15
|
+
Start-ScheduledTask -TaskName 'Spotter dashboard device'
|
|
16
|
+
Start-ScheduledTask -TaskName 'Spotter dashboard tunnel'
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
param([Parameter(Mandatory = $true)][string]$MainServer)
|
|
2
|
+
$ErrorActionPreference = 'Stop'
|
|
3
|
+
& "$env:WINDIR\System32\OpenSSH\ssh.exe" -N `
|
|
4
|
+
-o BatchMode=yes -o ExitOnForwardFailure=yes `
|
|
5
|
+
-o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o TCPKeepAlive=yes `
|
|
6
|
+
-R '127.0.0.1:53943:127.0.0.1:53940' $MainServer
|
|
7
|
+
exit $LASTEXITCODE
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-spotter",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Audit agent running alongside Claude Code that catches missed tool calls — 気づく役と実行する役の分離",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"test": "node --test",
|
|
14
|
+
"verify:release-commit": "node scripts/verify-release-commit.mjs",
|
|
15
|
+
"prepublishOnly": "npm run verify:release-commit && npm test",
|
|
14
16
|
"postinstall": "node scripts/postinstall.mjs",
|
|
15
17
|
"preuninstall": "node scripts/preuninstall.mjs"
|
|
16
18
|
},
|
|
@@ -23,7 +25,10 @@
|
|
|
23
25
|
"audit",
|
|
24
26
|
"spotter"
|
|
25
27
|
],
|
|
26
|
-
"author":
|
|
28
|
+
"author": {
|
|
29
|
+
"name": "Quo / クオ at kitepon.dev",
|
|
30
|
+
"url": "https://kitepon.dev/"
|
|
31
|
+
},
|
|
27
32
|
"license": "MIT",
|
|
28
33
|
"repository": {
|
|
29
34
|
"type": "git",
|
|
@@ -43,6 +48,8 @@
|
|
|
43
48
|
"bin",
|
|
44
49
|
"src",
|
|
45
50
|
"scripts",
|
|
51
|
+
"ops/dashboard",
|
|
52
|
+
"docs/11_dashboard-operations.md",
|
|
46
53
|
"README.md",
|
|
47
54
|
"LICENSE",
|
|
48
55
|
"CHANGELOG.md"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// publishする成果が、既定ブランチへ着地済みのcommitから作られていることを保証する。
|
|
4
|
+
// 着地していないbranchからpublishすると、そのbranchが取り残された時点で公開物が
|
|
5
|
+
// 後続releaseから消え、統合契約だけが存在しない面を指し続ける(0.3.0の孤児releaseが実例)。
|
|
6
|
+
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
const scriptPath = fileURLToPath(import.meta.url);
|
|
13
|
+
const defaultProjectDirectory = path.dirname(path.dirname(scriptPath));
|
|
14
|
+
|
|
15
|
+
export function verifyReleaseCommit({
|
|
16
|
+
projectDirectory = defaultProjectDirectory,
|
|
17
|
+
spawn = spawnSync,
|
|
18
|
+
} = {}) {
|
|
19
|
+
const git = (...args) => {
|
|
20
|
+
const result = spawn("git", args, { cwd: projectDirectory, encoding: "utf8" });
|
|
21
|
+
return { ok: result.status === 0, stdout: (result.stdout ?? "").trim() };
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const head = git("rev-parse", "HEAD");
|
|
25
|
+
assert.ok(head.ok, "git HEAD を解決できません");
|
|
26
|
+
const originHead = git("symbolic-ref", "--quiet", "refs/remotes/origin/HEAD");
|
|
27
|
+
const defaultRef = originHead.ok && originHead.stdout
|
|
28
|
+
? originHead.stdout.replace("refs/remotes/", "")
|
|
29
|
+
: "origin/main";
|
|
30
|
+
const defaultResolved = git("rev-parse", "--verify", `${defaultRef}^{commit}`);
|
|
31
|
+
assert.ok(defaultResolved.ok, `既定ブランチ ${defaultRef} を解決できません。git fetch origin を先に実行してください。`);
|
|
32
|
+
const isAncestor = git("merge-base", "--is-ancestor", head.stdout, defaultRef);
|
|
33
|
+
assert.ok(
|
|
34
|
+
isAncestor.ok,
|
|
35
|
+
`publish対象 ${head.stdout.slice(0, 12)} が ${defaultRef} の祖先ではありません。`
|
|
36
|
+
+ " 先に既定ブランチへ着地させてpushしてから publish してください。"
|
|
37
|
+
);
|
|
38
|
+
const dirty = git("status", "--porcelain", "--untracked-files=normal");
|
|
39
|
+
assert.ok(dirty.ok, "working treeの状態を確認できません");
|
|
40
|
+
assert.equal(
|
|
41
|
+
dirty.stdout,
|
|
42
|
+
"",
|
|
43
|
+
`working treeに未commitの変更があります。publish対象commitとpayloadが一致しません:\n${dirty.stdout}`
|
|
44
|
+
);
|
|
45
|
+
return `release commit ${head.stdout.slice(0, 12)} is landed on ${defaultRef}.`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === scriptPath) {
|
|
49
|
+
console.log(verifyReleaseCommit());
|
|
50
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
|
|
8
|
+
import { verifyReleaseCommit } from "./verify-release-commit.mjs";
|
|
9
|
+
|
|
10
|
+
function git(cwd, ...args) {
|
|
11
|
+
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function createReleaseFixture(t) {
|
|
15
|
+
const root = await mkdtemp(path.join(tmpdir(), "spotter-release-gate-"));
|
|
16
|
+
const remote = path.join(root, "origin.git");
|
|
17
|
+
const work = path.join(root, "work");
|
|
18
|
+
t.after(async () => rm(root, { recursive: true, force: true }));
|
|
19
|
+
await mkdir(work);
|
|
20
|
+
git(root, "init", "--bare", remote);
|
|
21
|
+
git(work, "init", "--initial-branch=main");
|
|
22
|
+
git(work, "config", "user.name", "Spotter release gate test");
|
|
23
|
+
git(work, "config", "user.email", "spotter-release-gate@example.invalid");
|
|
24
|
+
await writeFile(path.join(work, ".gitignore"), "ignored.tmp\n", "utf8");
|
|
25
|
+
await writeFile(path.join(work, "tracked.txt"), "tracked\n", "utf8");
|
|
26
|
+
git(work, "add", ".gitignore", "tracked.txt");
|
|
27
|
+
git(work, "commit", "-m", "fixture");
|
|
28
|
+
git(work, "remote", "add", "origin", remote);
|
|
29
|
+
git(work, "push", "--set-upstream", "origin", "main");
|
|
30
|
+
return work;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test("release gate passes for a commit landed on main", async (t) => {
|
|
34
|
+
const work = await createReleaseFixture(t);
|
|
35
|
+
assert.match(verifyReleaseCommit({ projectDirectory: work }), /landed on origin\/main/);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("release gate rejects an untracked publish payload", async (t) => {
|
|
39
|
+
const work = await createReleaseFixture(t);
|
|
40
|
+
await writeFile(path.join(work, "untracked.txt"), "must block publish\n", "utf8");
|
|
41
|
+
assert.throws(() => verifyReleaseCommit({ projectDirectory: work }), /working treeに未commitの変更があります/);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("release gate permits ignored generated output", async (t) => {
|
|
45
|
+
const work = await createReleaseFixture(t);
|
|
46
|
+
await writeFile(path.join(work, "ignored.tmp"), "generated\n", "utf8");
|
|
47
|
+
assert.match(verifyReleaseCommit({ projectDirectory: work }), /landed on origin\/main/);
|
|
48
|
+
});
|