agent-token-sentinel 0.0.0-stage → 0.1.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/LICENSE +21 -0
  3. package/README.md +275 -2
  4. package/README.zh-TW.md +281 -0
  5. package/SKILL.md +99 -0
  6. package/dist/circuit-breaker/breaker.d.ts +84 -0
  7. package/dist/circuit-breaker/breaker.js +321 -0
  8. package/dist/circuit-breaker/breaker.js.map +1 -0
  9. package/dist/circuit-breaker/checkpoint.d.ts +32 -0
  10. package/dist/circuit-breaker/checkpoint.js +118 -0
  11. package/dist/circuit-breaker/checkpoint.js.map +1 -0
  12. package/dist/circuit-breaker/detector.d.ts +58 -0
  13. package/dist/circuit-breaker/detector.js +296 -0
  14. package/dist/circuit-breaker/detector.js.map +1 -0
  15. package/dist/cli/hooks.d.ts +31 -0
  16. package/dist/cli/hooks.js +223 -0
  17. package/dist/cli/hooks.js.map +1 -0
  18. package/dist/cli/index.d.ts +10 -0
  19. package/dist/cli/index.js +272 -0
  20. package/dist/cli/index.js.map +1 -0
  21. package/dist/cli/install.d.ts +27 -0
  22. package/dist/cli/install.js +158 -0
  23. package/dist/cli/install.js.map +1 -0
  24. package/dist/config.d.ts +27 -0
  25. package/dist/config.js +63 -0
  26. package/dist/config.js.map +1 -0
  27. package/dist/index.d.ts +11 -0
  28. package/dist/index.js +12 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/mcp/server.d.ts +6 -0
  31. package/dist/mcp/server.js +203 -0
  32. package/dist/mcp/server.js.map +1 -0
  33. package/dist/sentinel.d.ts +107 -0
  34. package/dist/sentinel.js +259 -0
  35. package/dist/sentinel.js.map +1 -0
  36. package/dist/token-guard/compactor.d.ts +58 -0
  37. package/dist/token-guard/compactor.js +106 -0
  38. package/dist/token-guard/compactor.js.map +1 -0
  39. package/dist/token-guard/distiller.d.ts +29 -0
  40. package/dist/token-guard/distiller.js +228 -0
  41. package/dist/token-guard/distiller.js.map +1 -0
  42. package/dist/token-guard/pruner.d.ts +21 -0
  43. package/dist/token-guard/pruner.js +179 -0
  44. package/dist/token-guard/pruner.js.map +1 -0
  45. package/dist/types.d.ts +166 -0
  46. package/dist/types.js +7 -0
  47. package/dist/types.js.map +1 -0
  48. package/dist/utils/approach.d.ts +5 -0
  49. package/dist/utils/approach.js +39 -0
  50. package/dist/utils/approach.js.map +1 -0
  51. package/dist/utils/ast.d.ts +38 -0
  52. package/dist/utils/ast.js +212 -0
  53. package/dist/utils/ast.js.map +1 -0
  54. package/dist/utils/diff.d.ts +20 -0
  55. package/dist/utils/diff.js +110 -0
  56. package/dist/utils/diff.js.map +1 -0
  57. package/dist/utils/git.d.ts +9 -0
  58. package/dist/utils/git.js +22 -0
  59. package/dist/utils/git.js.map +1 -0
  60. package/dist/utils/hash.d.ts +3 -0
  61. package/dist/utils/hash.js +10 -0
  62. package/dist/utils/hash.js.map +1 -0
  63. package/dist/utils/normalize.d.ts +14 -0
  64. package/dist/utils/normalize.js +78 -0
  65. package/dist/utils/normalize.js.map +1 -0
  66. package/dist/utils/state-store.d.ts +32 -0
  67. package/dist/utils/state-store.js +139 -0
  68. package/dist/utils/state-store.js.map +1 -0
  69. package/dist/utils/tokens.d.ts +8 -0
  70. package/dist/utils/tokens.js +21 -0
  71. package/dist/utils/tokens.js.map +1 -0
  72. package/dist/version.d.ts +1 -0
  73. package/dist/version.js +4 -0
  74. package/dist/version.js.map +1 -0
  75. package/package.json +70 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.1.0] - Unreleased
8
+
9
+ ### Added
10
+ - Circuit breaker: line-reciprocity flip-flop detection, SHA-256 error
11
+ fingerprints (A→A→A, A→B→A, A→B→A→B), hotspot warnings, CLOSED/OPEN/HALF_OPEN
12
+ state machine, Dead-End logs.
13
+ - Git checkpoints under `refs/sentinel/*` and rollback of agent-touched files
14
+ (branch, HEAD, index and stash are never modified; a backup ref is kept).
15
+ - Token guard: log distiller (~150 tokens), TypeScript-AST pruner, episodic
16
+ history compactor.
17
+ - MCP server (9 tools, 3 resources) and the `sentinel` CLI (`run`, `status`,
18
+ `reset`, `checkpoint`, `rollback`, `distill`, `prune`, `compact`, `install`, `hook`).
19
+ - Hard write blocking through Claude Code hooks; Cursor and Windsurf hooks (beta);
20
+ git pre-commit hook.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 T5A111
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,276 @@
1
- # Temporary Holding Version
1
+ # agent-token-sentinel
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **English** | [繁體中文](./README.zh-TW.md)
4
+
5
+ [![CI](https://github.com/T5A111/Token-Sentinel/actions/workflows/ci.yml/badge.svg)](https://github.com/T5A111/Token-Sentinel/actions/workflows/ci.yml)
6
+ [![npm](https://img.shields.io/npm/v/agent-token-sentinel.svg)](https://www.npmjs.com/package/agent-token-sentinel)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
8
+ ![Node](https://img.shields.io/badge/node-%E2%89%A520-339933)
9
+
10
+ > A local-first watchdog and context optimizer for coding agents (Claude Code, Cursor, Windsurf, any MCP client).
11
+ > It **breaks whack-a-mole debug loops** before they burn your token budget and wreck your code, and it **distills bloated context** so the agent stays sharp.
12
+
13
+ Everything runs on your machine: no external API calls, no telemetry. State lives in `.sentinel/` (git-ignored automatically).
14
+
15
+ > **Status: beta (v0.1).** The core engine, MCP server, CLI and the Claude Code integration are covered by the test suite on Linux, macOS and Windows.
16
+ > The **Cursor and Windsurf hook adapters are beta**: they are built against the published hook formats and tested with simulated payloads, but not yet verified end-to-end in those editors. Please [open an issue](https://github.com/T5A111/Token-Sentinel/issues) with your editor version if something doesn't trigger.
17
+
18
+ ---
19
+
20
+ ## Why
21
+
22
+ Coding agents fail in two expensive ways:
23
+
24
+ | Pain | What it looks like | What the sentinel does |
25
+ |---|---|---|
26
+ | **Whack-a-mole loop** | Fix A breaks B, fix B breaks A; the same null-check is added, removed, re-added; the same error comes back 5 times. | Detects code flip-flops, error-fingerprint cycles and failure streaks → **trips a circuit breaker**, blocks further writes, **rolls back** the dirty edits, and writes a **Dead-End log** that forces an architecture-level re-plan. |
27
+ | **Context bloat** | 3,000-token stack traces full of `node_modules`, whole-file rewrites, 20 rounds of stale tool output. | **Distills logs** to ~150 tokens, **prunes files** to the symbols you need (AST), and **compacts history** into one-line episode summaries. |
28
+
29
+ ## Highlights
30
+
31
+ - 🔁 **Code flip-flop detection**: line-reciprocity rate over the last 5 edits per file (the share of deleted lines that come back within 2 edits). Above 60% plus a failing run → trip.
32
+ - 🧬 **Error fingerprints**: SHA-256 of `ErrorName + normalized message + project stack + failing test`, after stripping timestamps, absolute paths, `line:col`, memory addresses, UUIDs, durations, ports and temp dirs. Detects `A→A→A`, `A→B→A` and `A→B→A→B`.
33
+ - ⚡ **Circuit breaker**: CLOSED → OPEN → HALF_OPEN, with manual reset (requires a new hypothesis) or a single probe after a cool-down.
34
+ - ⏪ **Safe rollback**: git snapshots via a *temporary index* under `refs/sentinel/*`. Your branch, HEAD, index and stash are never touched. Only agent-touched files are restored, and a backup ref is taken first, so the rollback is itself undoable.
35
+ - 🧪 **Log Distiller**: ANSI stripped; `node_modules`, `node:internal`, Vite/Rollup/Webpack/esbuild/Vitest/Jest frames dropped; keeps the error, the failing test, expected/received, and the **first project call site ±3 lines**. Typical reduction is 95%+.
36
+ - 🌳 **AST Pruner**: uses the TypeScript compiler API to keep target functions, classes or methods plus the same-file types and imports they reference; everything else becomes `// ... [omitted N lines of unrelated logic]`. Original line numbers are preserved. Other languages fall back to a brace/indent heuristic.
37
+ - 🗜️ **Episodic History Compactor**: every 3 debug rounds →
38
+ `[Compacted]: Rounds 1-3 attempted null-checks at src/auth.ts#login, failed with TypeError ×3. Reverted.`
39
+ - 🔌 **Hard enforcement**: pre-write hooks for Claude Code, Cursor and Windsurf exit with code 2 to block writes while the circuit is OPEN. A git pre-commit hook rejects commits.
40
+
41
+ ## Architecture
42
+
43
+ ```
44
+ ┌──────────── Coding agent (Claude Code · Cursor · Windsurf · any MCP client) ────────────┐
45
+ │ │
46
+ │ MCP tools ─────────────┐ pre/post hooks (stdin JSON) ──┐ sentinel run -- │
47
+ └──────────────────────────┼──────────────────────────────────────┼───────────┬───────────┘
48
+ ▼ ▼ ▼
49
+ src/mcp/server.ts src/cli/hooks.ts src/cli/index.ts
50
+ └───────────────┬──────────────────────┴───────────┘
51
+ ▼
52
+ src/sentinel.ts (facade · locked read-modify-write)
53
+ ┌──────────────────────────────┼─────────────────────────────────────┐
54
+ ▼ ▼ ▼
55
+ ┌── circuit-breaker/ ──────┐ ┌── token-guard/ ─────────┐ ┌── utils/ ─────────────┐
56
+ │ detector.ts reciprocity │ │ distiller.ts log → 150t │ │ state-store (atomic, │
57
+ │ fingerprints│ │ pruner.ts AST fold │ │ locked state.json) │
58
+ │ hotspot/risk│ │ compactor.ts episodes │ │ ast · diff · git │
59
+ │ breaker.ts state machine│ └──────────────────────────┘ │ normalize · tokens │
60
+ │ checkpoint.ts git snapshots│ └───────────────────────┘
61
+ └───────────────────────────┘
62
+ ▼
63
+ .sentinel/state.json · dead-ends/*.md · refs/sentinel/*
64
+ ```
65
+
66
+ ## Quick start
67
+
68
+ Requires **Node.js ≥ 20** and **git** (git is only needed for rollback).
69
+
70
+ **Option A: from npm (recommended)**
71
+
72
+ ```bash
73
+ cd ~/my-project
74
+ npx agent-token-sentinel install --all # Claude Code + Cursor + Windsurf + git hook
75
+ # or install globally to get a faster `sentinel` command:
76
+ npm install -g agent-token-sentinel && sentinel install --all
77
+ ```
78
+
79
+ **Option B: from source**
80
+
81
+ ```bash
82
+ git clone https://github.com/T5A111/Token-Sentinel.git && cd Token-Sentinel
83
+ npm install && npm run build && npm link # `npm link` puts `sentinel` on your PATH
84
+ cd ~/my-project && sentinel install --all
85
+ ```
86
+
87
+ `sentinel install` merges into existing configs (it never clobbers them) and is idempotent. Pass `--claude`, `--cursor`, `--windsurf` or `--git` to install selectively, or `--dry-run` to preview.
88
+ When run through `npx`, the generated configs call `npx -y agent-token-sentinel …`, so they never point into the temporary npx cache. With a global or source install they call the installed script directly, which is faster.
89
+
90
+ Then make the agent run tests through the wrapper (the installed `SKILL.md` and rules already tell it to):
91
+
92
+ ```bash
93
+ sentinel run -- npm test # or: npx agent-token-sentinel run -- npm test
94
+ ```
95
+
96
+ The exit code passes through unchanged, and a distilled failure is printed instead of the raw log.
97
+
98
+ ## Integration
99
+
100
+ ### Claude Code
101
+
102
+ `sentinel install --claude` writes:
103
+
104
+ **`.mcp.json`**
105
+ ```json
106
+ { "mcpServers": { "token-sentinel": { "command": "node", "args": ["/abs/path/dist/mcp/server.js"] } } }
107
+ ```
108
+
109
+ **`.claude/settings.json`** (hooks: PreToolUse blocks writes with exit 2 while OPEN; PostToolUse records edits and test runs)
110
+ ```json
111
+ {
112
+ "hooks": {
113
+ "PreToolUse": [{ "matcher": "Edit|MultiEdit|Write|NotebookEdit", "hooks": [{ "type": "command", "command": "node \"/abs/path/dist/cli/index.js\" hook claude", "timeout": 30 }] }],
114
+ "PostToolUse": [{ "matcher": "Edit|MultiEdit|Write|NotebookEdit|Bash", "hooks": [{ "type": "command", "command": "node \"/abs/path/dist/cli/index.js\" hook claude", "timeout": 30 }] }],
115
+ "PostToolUseFailure": [{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "node \"/abs/path/dist/cli/index.js\" hook claude", "timeout": 30 }] }]
116
+ }
117
+ }
118
+ ```
119
+
120
+ **`.claude/skills/token-sentinel/SKILL.md`**: the agent operating rules ([SKILL.md](./SKILL.md)).
121
+
122
+ ### Cursor *(beta)*
123
+
124
+ `sentinel install --cursor` writes `.cursor/mcp.json`, `.cursor/hooks.json` and `.cursor/rules/token-sentinel.mdc` (`alwaysApply: true`):
125
+
126
+ ```json
127
+ {
128
+ "version": 1,
129
+ "hooks": {
130
+ "preToolUse": [{ "command": "node \"/abs/path/dist/cli/index.js\" hook cursor --event preToolUse" }],
131
+ "afterFileEdit": [{ "command": "node \"/abs/path/dist/cli/index.js\" hook cursor --event afterFileEdit" }],
132
+ "afterShellExecution": [{ "command": "node \"/abs/path/dist/cli/index.js\" hook cursor --event afterShellExecution" }]
133
+ }
134
+ }
135
+ ```
136
+
137
+ When the circuit is OPEN, `preToolUse` answers `{"permission":"deny", ...}` and exits 2.
138
+
139
+ ### Windsurf (Cascade) *(beta)*
140
+
141
+ `sentinel install --windsurf` writes `.windsurf/hooks.json` and `.windsurf/rules/token-sentinel.md` (`trigger: always_on`):
142
+
143
+ ```json
144
+ {
145
+ "hooks": {
146
+ "pre_write_code": [{ "command": "node \"/abs/path/dist/cli/index.js\" hook windsurf --event pre_write_code", "show_output": true }],
147
+ "post_write_code": [{ "command": "node \"/abs/path/dist/cli/index.js\" hook windsurf --event post_write_code", "show_output": true }],
148
+ "post_run_command": [{ "command": "node \"/abs/path/dist/cli/index.js\" hook windsurf --event post_run_command", "show_output": true }]
149
+ }
150
+ }
151
+ ```
152
+
153
+ Windsurf reads MCP servers only from the user-global `~/.codeium/windsurf/mcp_config.json`. Add `--global` to have the installer merge it, or add it yourself:
154
+
155
+ ```json
156
+ { "mcpServers": { "token-sentinel": { "command": "node", "args": ["/abs/path/dist/mcp/server.js"], "env": { "SENTINEL_ROOT": "/abs/path/to/project" } } } }
157
+ ```
158
+
159
+ ### Git
160
+
161
+ `sentinel install --git` adds a `pre-commit` hook that rejects commits while the circuit is OPEN. An existing foreign hook is never overwritten; the installer prints the line to add instead.
162
+
163
+ ### Enforcement matrix
164
+
165
+ | Client | MCP tools | Hard write block | Auto-record edits | Auto-record test runs |
166
+ |---|---|---|---|---|
167
+ | Claude Code | ✅ | ✅ PreToolUse exit 2 | ✅ PostToolUse | ✅ (`sentinel run` for exact exit codes) |
168
+ | Cursor *(beta)* | ✅ | ✅ preToolUse exit 2 / `deny` | ✅ afterFileEdit | ✅ afterShellExecution |
169
+ | Windsurf *(beta)* | ✅ (global config) | ✅ pre_write_code exit 2 | ✅ post_write_code | ✅ post_run_command |
170
+ | Other MCP clients | ✅ | advisory (`sentinel_guard_edit`) | `sentinel_record_edit` | `sentinel_record_run` / `sentinel run` |
171
+ | git | — | ✅ pre-commit | — | — |
172
+
173
+ > Hook payload fields differ between clients and versions, so they are parsed tolerantly. When a client does not report a command's exit code, failure is inferred from the output (`FAIL`, `error TS…`, `Traceback`, …). For exact results, run tests through `sentinel run -- <cmd>`; those runs are recorded once and skipped by the post-command hooks.
174
+
175
+ ## MCP interface
176
+
177
+ | Tool | Purpose |
178
+ |---|---|
179
+ | `sentinel_guard_edit {file, intent?}` | Call before editing. Returns ALLOW / WARN / BLOCK and snapshots the file. |
180
+ | `sentinel_record_edit {file, diff? \| before?/after?}` | Record an edit when no hook is installed. |
181
+ | `sentinel_record_run {command, exitCode, log?}` | Record a build/test run; returns the distilled error and may trip the breaker. |
182
+ | `sentinel_check_circuit {}` | State, risk, reciprocity, fingerprint pattern, hotspot, checkpoint. |
183
+ | `sentinel_reset_circuit {hypothesis}` | Close the circuit; requires a new root-cause hypothesis (≥ 15 chars). |
184
+ | `sentinel_checkpoint {label?}` | Snapshot the working tree as a rollback target. |
185
+ | `sentinel_distill_log {log, tokenBudget?}` | Compress a log. |
186
+ | `sentinel_prune_file {file, targets[], lineNumbers?}` | Read only the needed symbols. |
187
+ | `sentinel_compact_history {}` | Fold old rounds into `[Compacted]` lines. |
188
+
189
+ Resources: `sentinel://state`, `sentinel://dead-ends`, `sentinel://history`.
190
+
191
+ ## CLI
192
+
193
+ ```
194
+ sentinel run [--raw] -- <cmd...> sentinel status [--json] sentinel reset [--hypothesis "..."]
195
+ sentinel checkpoint [label] sentinel rollback [id] sentinel distill [file|-] [--budget N]
196
+ sentinel prune <file> <symbol...> sentinel compact | history sentinel dead-ends
197
+ sentinel install [--claude|--cursor|--windsurf|--git|--all] [--global] [--command "..."] [--dry-run]
198
+ sentinel hook <claude|cursor|windsurf|git-pre-commit> [--event name]
199
+ sentinel mcp
200
+ ```
201
+
202
+ `sentinel status` exits with code 3 while the circuit is OPEN, so you can use it in scripts.
203
+
204
+ ## Configuration (`.sentinel/config.json`)
205
+
206
+ All fields are optional. An invalid file falls back to the defaults and shows a warning.
207
+
208
+ | Key | Default | Effect |
209
+ |---|---|---|
210
+ | `maxConsecutiveFailures` | `4` | Failing runs in a row before tripping. |
211
+ | `reciprocityThreshold` | `0.6` | Flip-flop threshold (share of deleted lines re-added within 2 edits). |
212
+ | `oscillationWindow` | `5` | Recent edits per file inspected. |
213
+ | `sameFingerprintLimit` | `3` | Identical error fingerprints in a row before tripping. |
214
+ | `hotspotRounds` | `3` | Consecutive failing rounds editing the same function → HIGH-risk warning. |
215
+ | `cooldownMs` | `300000` | Time OPEN before one HALF_OPEN probe is allowed. |
216
+ | `autoRollback` | `true` | Restore agent-touched files on trip (`false` = block only). |
217
+ | `distillTokenBudget` | `150` | Max tokens of a distilled log. |
218
+ | `compactEvery` | `3` | Debug rounds per compacted episode. |
219
+ | `contextLines` | `3` | Source lines shown around the call site. |
220
+ | `maxCheckpoints` | `20` | Checkpoint refs kept. |
221
+ | `runCommandPattern` | test/build regex | Which shell commands the post-command hooks treat as build/test runs. |
222
+
223
+ **Trip conditions** (any one): failure streak ≥ `maxConsecutiveFailures` · same fingerprint ≥ `sameFingerprintLimit` · error cycle `A→B→A` or ping-pong `A→B→A→B` · flip-flop > `reciprocityThreshold` with the last run failing · a failed HALF_OPEN probe.
224
+
225
+ ## Self-verification
226
+
227
+ ```bash
228
+ npm run typecheck && npm test # unit + integration suite
229
+ npm run test:pack # builds the npm tarball, installs it in a clean project, runs CLI + MCP handshake
230
+ ```
231
+
232
+ CI runs both on Linux, macOS and Windows with Node 20, 22 and 24.
233
+
234
+ The suite (`tests/self-test.ts`) covers the four required scenarios plus rollback, pruning, compaction, the MCP protocol and the CLI/installer:
235
+
236
+ | # | Scenario | Verified |
237
+ |---|---|---|
238
+ | 1 | add → remove → re-add a line | reciprocity 100% > 60% → loop risk; refinement edits score 0% |
239
+ | 2 | two Vitest logs, different time/path/line/address | identical SHA-256; streak counted; A→A→A trips |
240
+ | 3 | 105-line Vite/node_modules log | ≤ 10 lines, ≤ 150 tokens, error + call site ±3 lines kept (−96%) |
241
+ | 4 | 4 consecutive failures | OPEN, Dead-End JSON+MD, writes blocked (Claude/Cursor/Windsurf hooks exit 2), HALF_OPEN, reset |
242
+ | 5 | real git repo | agent files restored, new file removed, human edit untouched, HEAD/index/stash untouched, backup ref |
243
+ | 6 | 252-line TS file | target + transitively referenced types + used import kept; the rest folded |
244
+ | 7 | 6 debug rounds | auto `[Compacted]` after round 4; ≥ 70% of history tokens freed (82.6%) |
245
+ | 8 | MCP client ↔ server | all tools listed and callable; validation and path-traversal errors are tool errors |
246
+ | 9 | `sentinel run`, `install --all`, git pre-commit | exit code passthrough, distilled output, idempotent config merge, commit rejected while OPEN |
247
+
248
+ ## Project layout
249
+
250
+ ```
251
+ src/
252
+ ├── circuit-breaker/ detector.ts · breaker.ts · checkpoint.ts
253
+ ├── token-guard/ distiller.ts · pruner.ts · compactor.ts
254
+ ├── mcp/ server.ts
255
+ ├── cli/ index.ts · hooks.ts · install.ts
256
+ ├── utils/ ast.ts · diff.ts · git.ts · hash.ts · normalize.ts · state-store.ts · tokens.ts · approach.ts
257
+ ├── sentinel.ts facade (locking, persistence, wiring)
258
+ ├── config.ts · types.ts · index.ts
259
+ tests/self-test.ts self-verification suite
260
+ SKILL.md rules for the agent
261
+ ```
262
+
263
+ ## Limitations
264
+
265
+ - Token counts are estimated offline (~4 chars/token, CJK ≈ 1 token/char), with roughly ±15% error.
266
+ - Rollback needs a git repository. Without one, the sentinel runs in block-only mode.
267
+ - Precise AST pruning covers TS/JS (`.ts .tsx .js .jsx .mts .cts .mjs .cjs`). Other languages use a heuristic.
268
+ - An agent determined to bypass the guard (e.g. writing files through the shell) can still do so. The hooks gate the editor's write tools, and the git hook gates commits.
269
+
270
+ ## Contributing
271
+
272
+ Issues and PRs are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) for the development and release workflow, and [CHANGELOG.md](./CHANGELOG.md) for release notes.
273
+
274
+ ## License
275
+
276
+ MIT
@@ -0,0 +1,281 @@
1
+ # agent-token-sentinel
2
+
3
+ [English](./README.md) | **繁體中文**
4
+
5
+ [![CI](https://github.com/T5A111/Token-Sentinel/actions/workflows/ci.yml/badge.svg)](https://github.com/T5A111/Token-Sentinel/actions/workflows/ci.yml)
6
+ [![npm](https://img.shields.io/npm/v/agent-token-sentinel.svg)](https://www.npmjs.com/package/agent-token-sentinel)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
8
+ ![Node](https://img.shields.io/badge/node-%E2%89%A520-339933)
9
+
10
+ > 給 Coding Agent(Claude Code、Cursor、Windsurf 及任何 MCP 客戶端)用的本機優先看門狗與上下文優化引擎。
11
+ > 它會在 **打地鼠式除錯死循環** 燒光 Token、改壞程式碼之前**熔斷**,並**蒸餾膨脹的上下文**,讓 Agent 維持判斷精度。
12
+
13
+ 所有分析都在本機完成:不呼叫任何外部 API,也沒有遙測。狀態存放在 `.sentinel/`,並自動排除在 git 之外。
14
+
15
+ > **狀態:Beta(v0.1)。** 核心引擎、MCP Server、CLI 與 Claude Code 整合都有測試覆蓋,並在 Linux、macOS、Windows 上執行。
16
+ > **Cursor 與 Windsurf 的 Hook 整合為 Beta**:依照官方公布的 Hook 格式實作,並用模擬資料測試過,但還沒在這兩個編輯器中做過完整的實機驗證。如果 Hook 沒有觸發,歡迎[開 Issue](https://github.com/T5A111/Token-Sentinel/issues) 並附上編輯器版本。
17
+
18
+ ---
19
+
20
+ ## 為什麼需要它
21
+
22
+ Coding Agent 最常在兩件事上浪費最多:
23
+
24
+ | 痛點 | 症狀 | Sentinel 的作法 |
25
+ |---|---|---|
26
+ | **打地鼠死循環** | 修好 A 壞了 B,修 B 又壞了 A;同一行 null-check 加了又刪、刪了又加;同一個錯誤反覆出現 5 次。 | 偵測代碼振盪、報錯指紋循環與連續失敗 → **觸發熔斷**、阻擋寫入、**自動回滾**髒代碼,並產出 **Dead-End Log**,強制 Agent 回到架構層級重新規劃。 |
27
+ | **上下文膨脹** | 3,000 Token 的 stack trace 裡塞滿 `node_modules`、整檔覆寫、累積 20 輪過時的工具輸出。 | **蒸餾日誌**到約 150 Token、用 AST **只讀需要的函式**、把歷史**濃縮成單行摘要**。 |
28
+
29
+ ## 亮點
30
+
31
+ - 🔁 **代碼振盪偵測**:計算每個檔案最近 5 次修改的「行互逆率」,也就是被刪掉的行在之後 2 次修改內被加回來的比例。超過 60% 且最近一次執行失敗,就熔斷。
32
+ - 🧬 **報錯指紋**:先剝離時間戳、絕對路徑、`行:列`、記憶體位址、UUID、耗時、port、暫存目錄,再對 `ErrorName + 正規化訊息 + 專案內 stack + 失敗測試名` 計算 SHA-256。可偵測 `A→A→A`、`A→B→A`、`A→B→A→B` 三種模式。
33
+ - ⚡ **熔斷器**:CLOSED → OPEN → HALF_OPEN 三態。可手動重置(必須提出新的假設),或在冷卻後給一次試探機會。
34
+ - ⏪ **安全回滾**:用「暫存 index」建立 git 快照,存在 `refs/sentinel/*`。**絕不動**你的分支、HEAD、index 和 stash。只還原 Agent 動過的檔案,回滾前會先建立備份 ref,所以回滾本身也能復原。
35
+ - 🧪 **日誌蒸餾**:去除 ANSI 碼;剔除 `node_modules`、`node:internal`、Vite/Rollup/Webpack/esbuild/Vitest/Jest 內部 frame;保留錯誤訊息、失敗的測試、expected/received,以及**第一個專案呼叫點前後 3 行**。壓縮率通常在 95% 以上。
36
+ - 🌳 **AST 局部裁剪**:用 TypeScript Compiler API 只保留目標函式、類別或方法,加上它們引用到的同檔型別與 import。其餘程式碼摺疊為 `// ... [omitted N lines of unrelated logic]`,並保留原始行號。其他語言改用大括號或縮排的啟發式方法。
37
+ - 🗜️ **歷史斷代壓縮**:每 3 輪除錯自動產生一行摘要,例如:
38
+ `[Compacted]: Rounds 1-3 attempted null-checks at src/auth.ts#login, failed with TypeError ×3. Reverted.`
39
+ - 🔌 **硬性攔截**:Claude Code、Cursor、Windsurf 的寫入前 Hook 在熔斷 OPEN 時以 exit code 2 阻擋寫入;git pre-commit hook 會拒絕提交。
40
+
41
+ ## 架構
42
+
43
+ ```
44
+ ┌──────────── Coding Agent(Claude Code · Cursor · Windsurf · 任何 MCP 客戶端)────────────┐
45
+ │ │
46
+ │ MCP tools ─────────────┐ pre/post hooks(stdin JSON)──┐ sentinel run -- │
47
+ └──────────────────────────┼──────────────────────────────────────┼───────────┬────────────┘
48
+ ▼ ▼ ▼
49
+ src/mcp/server.ts src/cli/hooks.ts src/cli/index.ts
50
+ └───────────────┬──────────────────────┴───────────┘
51
+ ▼
52
+ src/sentinel.ts(Facade · 加鎖讀改寫)
53
+ ┌──────────────────────────────┼─────────────────────────────────────┐
54
+ ▼ ▼ ▼
55
+ ┌── circuit-breaker/ ──────┐ ┌── token-guard/ ─────────┐ ┌── utils/ ─────────────┐
56
+ │ detector.ts 行互逆率 │ │ distiller.ts 日誌→150t │ │ state-store(原子寫入 │
57
+ │ 報錯指紋 │ │ pruner.ts AST 摺疊 │ │ + 檔案鎖) │
58
+ │ 熱區/風險 │ │ compactor.ts 斷代壓縮 │ │ ast · diff · git │
59
+ │ breaker.ts 狀態機 │ └──────────────────────────┘ │ normalize · tokens │
60
+ │ checkpoint.ts git 快照 │ └───────────────────────┘
61
+ └───────────────────────────┘
62
+ ▼
63
+ .sentinel/state.json · dead-ends/*.md · refs/sentinel/*
64
+ ```
65
+
66
+ ## 快速開始
67
+
68
+ 需要 **Node.js ≥ 20** 與 **git**(git 只有回滾功能會用到)。
69
+
70
+ **方式 A:從 npm 安裝(推薦)**
71
+
72
+ ```bash
73
+ cd ~/my-project
74
+ npx agent-token-sentinel install --all # 一次設定 Claude Code + Cursor + Windsurf + git hook
75
+ # 或全域安裝,取得速度較快的 `sentinel` 指令:
76
+ npm install -g agent-token-sentinel && sentinel install --all
77
+ ```
78
+
79
+ **方式 B:從原始碼安裝**
80
+
81
+ ```bash
82
+ git clone https://github.com/T5A111/Token-Sentinel.git && cd Token-Sentinel
83
+ npm install && npm run build && npm link # `npm link` 會把 `sentinel` 指令加入 PATH
84
+ cd ~/my-project && sentinel install --all
85
+ ```
86
+
87
+ `sentinel install` 會**合併**進既有設定,不會覆蓋,重複執行也不會產生重複項目。可以用 `--claude`、`--cursor`、`--windsurf`、`--git` 個別安裝,或用 `--dry-run` 只預覽不寫入。
88
+ 透過 `npx` 執行時,產生的設定會呼叫 `npx -y agent-token-sentinel …`,不會指向 npx 的暫存快取目錄。全域安裝或從原始碼安裝時,會直接呼叫已安裝的腳本,速度較快。
89
+
90
+ 接著讓 Agent 透過包裝器執行測試(安裝時寫入的 `SKILL.md` 與規則檔已經會要求 Agent 這樣做):
91
+
92
+ ```bash
93
+ sentinel run -- npm test # 或:npx agent-token-sentinel run -- npm test
94
+ ```
95
+
96
+ exit code 會原樣傳回;失敗時印出蒸餾後的錯誤,而不是原始日誌。
97
+
98
+ ## 整合設定
99
+
100
+ ### Claude Code
101
+
102
+ `sentinel install --claude` 會寫入以下檔案:
103
+
104
+ **`.mcp.json`**
105
+ ```json
106
+ { "mcpServers": { "token-sentinel": { "command": "node", "args": ["/abs/path/dist/mcp/server.js"] } } }
107
+ ```
108
+
109
+ **`.claude/settings.json`**:PreToolUse 在 OPEN 時以 exit 2 阻擋寫入;PostToolUse 記錄每次修改與測試結果。
110
+ ```json
111
+ {
112
+ "hooks": {
113
+ "PreToolUse": [{ "matcher": "Edit|MultiEdit|Write|NotebookEdit", "hooks": [{ "type": "command", "command": "node \"/abs/path/dist/cli/index.js\" hook claude", "timeout": 30 }] }],
114
+ "PostToolUse": [{ "matcher": "Edit|MultiEdit|Write|NotebookEdit|Bash", "hooks": [{ "type": "command", "command": "node \"/abs/path/dist/cli/index.js\" hook claude", "timeout": 30 }] }],
115
+ "PostToolUseFailure": [{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "node \"/abs/path/dist/cli/index.js\" hook claude", "timeout": 30 }] }]
116
+ }
117
+ }
118
+ ```
119
+
120
+ **`.claude/skills/token-sentinel/SKILL.md`**:給 Agent 的操作規則(見 [SKILL.md](./SKILL.md))。
121
+
122
+ ### Cursor *(Beta)*
123
+
124
+ `sentinel install --cursor` 會寫入 `.cursor/mcp.json`、`.cursor/hooks.json`,以及 `.cursor/rules/token-sentinel.mdc`(設定為 `alwaysApply: true`):
125
+
126
+ ```json
127
+ {
128
+ "version": 1,
129
+ "hooks": {
130
+ "preToolUse": [{ "command": "node \"/abs/path/dist/cli/index.js\" hook cursor --event preToolUse" }],
131
+ "afterFileEdit": [{ "command": "node \"/abs/path/dist/cli/index.js\" hook cursor --event afterFileEdit" }],
132
+ "afterShellExecution": [{ "command": "node \"/abs/path/dist/cli/index.js\" hook cursor --event afterShellExecution" }]
133
+ }
134
+ }
135
+ ```
136
+
137
+ 熔斷 OPEN 時,`preToolUse` 會回傳 `{"permission":"deny", ...}`,並以 exit 2 結束。
138
+
139
+ ### Windsurf(Cascade)*(Beta)*
140
+
141
+ `sentinel install --windsurf` 會寫入 `.windsurf/hooks.json` 和 `.windsurf/rules/token-sentinel.md`(設定為 `trigger: always_on`):
142
+
143
+ ```json
144
+ {
145
+ "hooks": {
146
+ "pre_write_code": [{ "command": "node \"/abs/path/dist/cli/index.js\" hook windsurf --event pre_write_code", "show_output": true }],
147
+ "post_write_code": [{ "command": "node \"/abs/path/dist/cli/index.js\" hook windsurf --event post_write_code", "show_output": true }],
148
+ "post_run_command": [{ "command": "node \"/abs/path/dist/cli/index.js\" hook windsurf --event post_run_command", "show_output": true }]
149
+ }
150
+ }
151
+ ```
152
+
153
+ Windsurf 的 MCP 設定**只有全域檔** `~/.codeium/windsurf/mcp_config.json`。加上 `--global` 參數,安裝器就會自動合併;也可以手動加入:
154
+
155
+ ```json
156
+ { "mcpServers": { "token-sentinel": { "command": "node", "args": ["/abs/path/dist/mcp/server.js"], "env": { "SENTINEL_ROOT": "/abs/path/to/project" } } } }
157
+ ```
158
+
159
+ ### Git
160
+
161
+ `sentinel install --git` 會加入 `pre-commit` hook,在熔斷 OPEN 時拒絕提交。如果已經有其他人的 hook,安裝器不會覆蓋,而是印出需要手動加入的那一行。
162
+
163
+ ### 各客戶端支援程度
164
+
165
+ | 客戶端 | MCP 工具 | 硬性阻擋寫入 | 自動記錄修改 | 自動記錄測試 |
166
+ |---|---|---|---|---|
167
+ | Claude Code | ✅ | ✅ PreToolUse exit 2 | ✅ PostToolUse | ✅(要精確的 exit code,請用 `sentinel run`) |
168
+ | Cursor *(Beta)* | ✅ | ✅ preToolUse exit 2 / `deny` | ✅ afterFileEdit | ✅ afterShellExecution |
169
+ | Windsurf *(Beta)* | ✅(全域設定) | ✅ pre_write_code exit 2 | ✅ post_write_code | ✅ post_run_command |
170
+ | 其他 MCP 客戶端 | ✅ | 僅勸導(`sentinel_guard_edit`) | `sentinel_record_edit` | `sentinel_record_run` / `sentinel run` |
171
+ | git | — | ✅ pre-commit | — | — |
172
+
173
+ > 各客戶端、各版本送給 Hook 的 payload 欄位都不同,因此採用寬鬆解析。若客戶端沒有提供指令的 exit code,會從輸出內容(`FAIL`、`error TS…`、`Traceback` 等)推斷是否失敗。需要精確結果時,請用 `sentinel run -- <cmd>` 執行測試;這類執行只會記錄一次,post-command hook 會自動略過。
174
+
175
+ ## MCP 介面
176
+
177
+ | Tool | 用途 |
178
+ |---|---|
179
+ | `sentinel_guard_edit {file, intent?}` | 修改前呼叫,回傳 ALLOW / WARN / BLOCK,並為該檔建立快照。 |
180
+ | `sentinel_record_edit {file, diff? \| before?/after?}` | 沒有安裝 Hook 時,用來手動記錄修改。 |
181
+ | `sentinel_record_run {command, exitCode, log?}` | 記錄建置或測試結果,回傳蒸餾後的錯誤,必要時觸發熔斷。 |
182
+ | `sentinel_check_circuit {}` | 查看熔斷狀態、風險等級、互逆率、指紋模式、熱區與 checkpoint。 |
183
+ | `sentinel_reset_circuit {hypothesis}` | 關閉熔斷;必須提出新的根因假設(至少 15 字元)。 |
184
+ | `sentinel_checkpoint {label?}` | 建立工作區快照,作為回滾目標。 |
185
+ | `sentinel_distill_log {log, tokenBudget?}` | 壓縮日誌。 |
186
+ | `sentinel_prune_file {file, targets[], lineNumbers?}` | 只讀取需要的符號。 |
187
+ | `sentinel_compact_history {}` | 把舊回合濃縮成 `[Compacted]` 摘要。 |
188
+
189
+ Resources:`sentinel://state`、`sentinel://dead-ends`、`sentinel://history`。
190
+
191
+ ## CLI
192
+
193
+ ```
194
+ sentinel run [--raw] -- <cmd...> sentinel status [--json] sentinel reset [--hypothesis "..."]
195
+ sentinel checkpoint [label] sentinel rollback [id] sentinel distill [file|-] [--budget N]
196
+ sentinel prune <file> <symbol...> sentinel compact | history sentinel dead-ends
197
+ sentinel install [--claude|--cursor|--windsurf|--git|--all] [--global] [--command "..."] [--dry-run]
198
+ sentinel hook <claude|cursor|windsurf|git-pre-commit> [--event name]
199
+ sentinel mcp
200
+ ```
201
+
202
+ 熔斷 OPEN 時,`sentinel status` 會以 exit code 3 結束,方便在腳本中判斷。
203
+
204
+ ## 設定(`.sentinel/config.json`)
205
+
206
+ 所有欄位都是選填。設定檔格式錯誤時會改用預設值並顯示警告,不會讓工具崩潰。
207
+
208
+ | 欄位 | 預設值 | 作用 |
209
+ |---|---|---|
210
+ | `maxConsecutiveFailures` | `4` | 連續失敗幾次就熔斷。 |
211
+ | `reciprocityThreshold` | `0.6` | 振盪門檻:被刪掉的行在之後 2 次修改內被加回的比例。 |
212
+ | `oscillationWindow` | `5` | 每個檔案回看最近幾次修改。 |
213
+ | `sameFingerprintLimit` | `3` | 同一個報錯指紋連續出現幾次就熔斷。 |
214
+ | `hotspotRounds` | `3` | 同一函式連續幾輪被修改且失敗,就發出 HIGH 風險警告。 |
215
+ | `cooldownMs` | `300000` | OPEN 多久之後允許一次 HALF_OPEN 試探。 |
216
+ | `autoRollback` | `true` | 熔斷時是否還原 Agent 動過的檔案(設為 `false` 則只阻擋寫入)。 |
217
+ | `distillTokenBudget` | `150` | 蒸餾後日誌的 Token 上限。 |
218
+ | `compactEvery` | `3` | 每幾輪除錯壓縮成一段摘要。 |
219
+ | `contextLines` | `3` | 呼叫點前後顯示幾行原始碼。 |
220
+ | `maxCheckpoints` | `20` | 最多保留幾個 checkpoint ref。 |
221
+ | `runCommandPattern` | 測試/建置指令的正則 | post-command hook 用來判斷哪些 shell 指令算是建置或測試。 |
222
+
223
+ **熔斷條件**(任一成立即觸發):
224
+ - 連續失敗次數 ≥ `maxConsecutiveFailures`
225
+ - 同一指紋連續出現次數 ≥ `sameFingerprintLimit`
226
+ - 報錯循環 `A→B→A` 或乒乓 `A→B→A→B`
227
+ - 振盪率 > `reciprocityThreshold`,且最近一次執行失敗
228
+ - HALF_OPEN 試探失敗
229
+
230
+ ## 自我驗證
231
+
232
+ ```bash
233
+ npm run typecheck && npm test # 單元與整合測試
234
+ npm run test:pack # 打包 npm tarball,安裝到乾淨專案後執行 CLI 與 MCP 握手
235
+ ```
236
+
237
+ CI 會在 Linux、macOS、Windows 上,以 Node 20、22、24 執行以上兩組測試。
238
+
239
+ 測試套件 `tests/self-test.ts` 涵蓋規格要求的 4 個情境,另外加測回滾、AST 裁剪、歷史壓縮、MCP 協定和 CLI/安裝器:
240
+
241
+ | # | 情境 | 驗證內容 |
242
+ |---|---|---|
243
+ | 1 | 新增 → 刪除 → 加回同一行 | 互逆率 100% > 60%,標記為死循環風險;正常的漸進修改為 0% |
244
+ | 2 | 兩份時間、路徑、行號、位址都不同的 Vitest 日誌 | SHA-256 完全一致;連續失敗計數正確;A→A→A 觸發熔斷 |
245
+ | 3 | 105 行 Vite/node_modules 日誌 | 壓到 ≤ 10 行、≤ 150 Token,保留錯誤訊息與呼叫點前後 3 行(−96%) |
246
+ | 4 | 連續失敗 4 次 | 進入 OPEN、產出 Dead-End JSON+MD、寫入被阻擋(三家 Hook 皆 exit 2)、HALF_OPEN 試探、重置成功 |
247
+ | 5 | 真實 git repo | Agent 改的檔案被還原、新建檔案被移除、人工修改不受影響、HEAD/index/stash 未被動到、備份 ref 存在 |
248
+ | 6 | 252 行 TS 檔 | 保留目標函式、遞移引用的型別與用到的 import,其餘摺疊 |
249
+ | 7 | 6 輪除錯 | 第 4 輪後自動產生 `[Compacted]` 摘要;釋放 ≥ 70% 歷史 Token(實測 82.6%) |
250
+ | 8 | MCP client ↔ server | 所有工具都能列出並呼叫;參數驗證失敗與路徑穿越都以 tool error 回傳 |
251
+ | 9 | `sentinel run`、`install --all`、git pre-commit | exit code 原樣傳回、輸出已蒸餾、設定合併可重複執行、OPEN 時拒絕提交 |
252
+
253
+ ## 目錄結構
254
+
255
+ ```
256
+ src/
257
+ ├── circuit-breaker/ detector.ts · breaker.ts · checkpoint.ts
258
+ ├── token-guard/ distiller.ts · pruner.ts · compactor.ts
259
+ ├── mcp/ server.ts
260
+ ├── cli/ index.ts · hooks.ts · install.ts
261
+ ├── utils/ ast.ts · diff.ts · git.ts · hash.ts · normalize.ts · state-store.ts · tokens.ts · approach.ts
262
+ ├── sentinel.ts Facade(加鎖、持久化、模組串接)
263
+ ├── config.ts · types.ts · index.ts
264
+ tests/self-test.ts 自我驗證套件
265
+ SKILL.md 給 Agent 的規則
266
+ ```
267
+
268
+ ## 已知限制
269
+
270
+ - Token 數是離線估算(約 4 字元算 1 Token,中日韓文字約 1 字算 1 Token),誤差約 ±15%。
271
+ - 回滾需要 git repo;沒有 git 時只會阻擋寫入,不會回滾。
272
+ - 精確的 AST 裁剪只支援 TS/JS(`.ts .tsx .js .jsx .mts .cts .mjs .cjs`),其他語言改用啟發式方法。
273
+ - 刻意繞過的 Agent(例如直接用 shell 寫檔)仍然擋不住。Hook 只能管住編輯器的寫入工具,git hook 只能管住 commit。
274
+
275
+ ## 參與貢獻
276
+
277
+ 歡迎提交 Issue 和 PR。開發與發版流程請見 [CONTRIBUTING.md](./CONTRIBUTING.md),版本紀錄請見 [CHANGELOG.md](./CHANGELOG.md)。
278
+
279
+ ## 授權
280
+
281
+ MIT