token-pilot 0.43.1 → 0.45.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 (44) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +93 -0
  4. package/README.md +2 -0
  5. package/agents/tp-api-surface-tracker.md +1 -1
  6. package/agents/tp-audit-scanner.md +1 -1
  7. package/agents/tp-commit-writer.md +1 -1
  8. package/agents/tp-context-engineer.md +1 -1
  9. package/agents/tp-dead-code-finder.md +1 -1
  10. package/agents/tp-debugger.md +1 -1
  11. package/agents/tp-dep-health.md +1 -1
  12. package/agents/tp-doc-writer.md +1 -1
  13. package/agents/tp-history-explorer.md +1 -1
  14. package/agents/tp-impact-analyzer.md +1 -1
  15. package/agents/tp-incident-timeline.md +1 -1
  16. package/agents/tp-incremental-builder.md +1 -1
  17. package/agents/tp-migration-scout.md +1 -1
  18. package/agents/tp-onboard.md +1 -1
  19. package/agents/tp-performance-profiler.md +1 -1
  20. package/agents/tp-pr-reviewer.md +1 -1
  21. package/agents/tp-refactor-planner.md +1 -1
  22. package/agents/tp-review-impact.md +1 -1
  23. package/agents/tp-run.md +1 -1
  24. package/agents/tp-session-restorer.md +1 -1
  25. package/agents/tp-ship-coordinator.md +1 -1
  26. package/agents/tp-spec-writer.md +1 -1
  27. package/agents/tp-test-coverage-gapper.md +1 -1
  28. package/agents/tp-test-triage.md +1 -1
  29. package/agents/tp-test-writer.md +1 -1
  30. package/dist/config/defaults.js +5 -1
  31. package/dist/core/event-log.d.ts +11 -0
  32. package/dist/hooks/pre-bash.js +36 -0
  33. package/dist/hooks/session-start.d.ts +9 -0
  34. package/dist/hooks/session-start.js +21 -1
  35. package/dist/hooks/subagent-stop.d.ts +7 -0
  36. package/dist/hooks/subagent-stop.js +3 -0
  37. package/dist/index.d.ts +15 -0
  38. package/dist/index.js +66 -11
  39. package/dist/server/profile-recommender.js +18 -15
  40. package/dist/server/tool-definitions.d.ts +2 -2
  41. package/dist/server/tool-definitions.js +3 -3
  42. package/dist/server/tool-profiles.js +11 -3
  43. package/dist/server.js +31 -2
  44. package/package.json +1 -1
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Token Pilot — save 60-90% tokens when AI reads code",
9
- "version": "0.43.1"
9
+ "version": "0.45.0"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "token-pilot",
14
14
  "source": "./",
15
15
  "description": "Reduces token consumption by 60-90% via AST-aware lazy file reading, structural symbol navigation, and cross-session tool-usage analytics. 23 MCP tools + 25 subagents + budget watchdog hooks.",
16
- "version": "0.43.1",
16
+ "version": "0.45.0",
17
17
  "author": {
18
18
  "name": "Digital-Threads"
19
19
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-pilot",
3
- "version": "0.43.1",
3
+ "version": "0.45.0",
4
4
  "description": "Saves 60-90% tokens on AI code reading. AST-aware lazy reads, symbol navigation, find_usages, structural git diff/log, edit-safety guard, Task-routing matcher, cross-session telemetry (errors + diagnostics), 25 tp-* subagents tiered to haiku/sonnet/opus with budget watchdog.",
5
5
  "author": {
6
6
  "name": "Digital-Threads",
package/CHANGELOG.md CHANGED
@@ -5,6 +5,99 @@ All notable changes to Token Pilot will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.45.0] - unreleased
9
+
10
+ Accumulating; not yet published. `0.44.0` is the published/consumed version —
11
+ the items below live only on `master` until `0.45.0` ships.
12
+
13
+ ### Changed — default tool profile is now `full` (adoption fix)
14
+
15
+ The advice surface (rules, SessionStart/PostToolUse banners, the pre-edit hook)
16
+ references `read_for_edit`, batch reads, `test_summary` etc. unconditionally,
17
+ but the old default (`edit`) and any trimmed profile hide some of those — so the
18
+ model calls a hidden tool, hits `No such tool available`, and falls back to raw
19
+ `Read`/`Bash`. Those dead round-trips cost far more than the ~2k tokens the trim
20
+ saved. Default is now `full` (advertise everything); trimmed profiles stay
21
+ opt-in via `TOKEN_PILOT_PROFILE=nav|edit|minimal`. When a trimmed profile is
22
+ active the SessionStart banner now prepends a caveat naming what's hidden.
23
+
24
+ The profile **recommender** no longer pushes a trim: it used to suggest
25
+ `TOKEN_PILOT_PROFILE=nav` for read-heavy usage and print an "apply to
26
+ `.mcp.json`" snippet — users applied it, then the next edit session hit the
27
+ trimmed-away `read_for_edit` / `read_range` / batch reads. It now always
28
+ recommends `full`. And `token-pilot doctor` loudly flags an explicit trimmed
29
+ profile, names the hidden tools, and tells you to remove it.
30
+
31
+ ### Added — tool failures are logged (no more silent breakage)
32
+
33
+ `createServer`'s tool dispatch now writes handler exceptions / validation errors
34
+ (and unknown-tool names that reach the server) to `~/.token-pilot/hook-errors.jsonl`,
35
+ visible via `token-pilot errors`. Previously tp breakage vanished while telemetry
36
+ reported "all ok". (`No such tool available` is rejected at the Claude Code layer
37
+ before reaching us and stays invisible by design — the full default removes its
38
+ main source.)
39
+
40
+ ### Fixed — `read_section` is docs-only
41
+
42
+ Clarified that `read_section` reads Markdown/YAML/JSON/CSV by heading/key/row —
43
+ **not** code by line/symbol (use `read_range` / `read_symbol`). Removed its
44
+ misleading placement under "Batch variants" in the SessionStart banner.
45
+
46
+ ### Added — bounded-read leak closed (gate on read span, not bound presence)
47
+
48
+ `PreToolUse:Read` passed *any* `offset`/`limit` Read straight through, so
49
+ `Read(file, limit=2000)` (Claude Code's default page) pulled a whole big file
50
+ hook-free **and** un-counted in the adaptive burn signal — the #1 invisible
51
+ leak. The hook now measures the span a Read actually pulls
52
+ (`effectiveReadSpanLines`) and applies the same deny threshold: a default-page
53
+ or offset-no-limit read of a big file denies with a structural summary, while a
54
+ genuinely narrow slice (`limit < threshold`) still passes. Cost estimates are
55
+ scaled by the span so bounded denies don't over-report savings.
56
+
57
+ ### Added — `parent_session_id` capture in SubagentStop (groundwork)
58
+
59
+ A subagent's MCP server runs with `CLAUDE_CODE_SESSION_ID` = the *agent*
60
+ session, so subagent savings get tagged with that id and the statusline's
61
+ main-session badge drops them (savings look flat when subagents do the reading).
62
+ SubagentStop now captures `parent_session_id` (which CC ships in the payload),
63
+ enabling a future child→parent rollup in the badge. Additive/no-op when absent.
64
+
65
+ ### Security — `vitest` 3.2.4 → 3.2.6
66
+
67
+ Patches GHSA-5xrq-8626-4rwp (Vitest UI arbitrary file read/exec, critical).
68
+ Dev-only dependency; shipped runtime deps unchanged. The other 32 Dependabot
69
+ alerts were already resolved (installed transitive versions at/above the
70
+ patched version) and auto-close on re-scan.
71
+
72
+ ### Docs
73
+
74
+ Fable-5 economic positioning in the README — savings are in tokens, value is in
75
+ tokens × price; keep the premium thread lean.
76
+
77
+ ## [0.44.0] - 2026-06-10
78
+
79
+ ### Changed — adaptive deny threshold ON by default
80
+
81
+ `hooks.adaptiveThreshold` now defaults to `true`. The curve is a no-op below
82
+ 30% session burn, so short / light sessions read exactly as before. Once an
83
+ agent has already pulled many large files — the long-session degradation users
84
+ actually report — the Read-hook deny threshold tightens (300 → 225 → 150 → 90,
85
+ floor 50 lines), pushing the agent back onto `smart_read` / `read_symbol` when
86
+ context is most precious. Opt out with `adaptiveThreshold: false`.
87
+
88
+ ### Added — pre-bash catches `sed` / `head` / `tail` raw-range dumps
89
+
90
+ The Bash pre-hook already blocked `cat <code-file>`; agents under pressure
91
+ worked around it with `sed -n '1,500p' file.ts` or `head -n 500 file.ts` to
92
+ pull a large slice straight to stdout. These now deny with a pointer to
93
+ `read_range` / `read_symbol` / `smart_read`. Exempt, as before: pipes
94
+ (processing), redirects (writing), `sed -i` (in-place edit), and small
95
+ `head` / `tail` counts (< 300 lines — the sanctioned bounded read).
96
+
97
+ ### Maintenance
98
+
99
+ `@ast-index/cli` lockfile tracks `3.47.0` (floor `^3.44.0` unchanged).
100
+
8
101
  ## [0.43.1] - 2026-06-06
9
102
 
10
103
  ### Fixed — `@ast-index/cli` floor raised to `^3.44.0`
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  **Token-efficient AI coding, enforced.** Cuts context consumption in AI coding assistants by up to **90%** without changing the way you work.
4
4
 
5
+ > **Why it matters more now:** as frontier models move up in price — Claude's Fable 5 is the most capable (and most expensive-per-token) tier yet — the tokens you *don't* spend reading code are worth more, not less. The savings are in tokens; the value is in tokens × price. Token Pilot keeps the expensive main thread lean so the premium model spends its budget on reasoning, not on re-reading files.
6
+
5
7
  Three layers, each useful on its own, stronger together:
6
8
 
7
9
  1. **MCP tools** — structural reads (`smart_read`, `read_symbol`, `read_for_edit`, …). Ask for an outline or load one function by name instead of the whole file.
@@ -9,7 +9,7 @@ tools:
9
9
  - mcp__token-pilot__read_symbol
10
10
  - Bash
11
11
  model: haiku
12
- token_pilot_version: "0.43.1"
12
+ token_pilot_version: "0.45.0"
13
13
  token_pilot_body_hash: dd184501203fa7f3c73f419c4ffbe33c4be75400cb64a7a51733a3fe23f6e085
14
14
  requiredMcpServers:
15
15
  - "token-pilot"
@@ -11,7 +11,7 @@ tools:
11
11
  - Grep
12
12
  - Read
13
13
  model: sonnet
14
- token_pilot_version: "0.43.1"
14
+ token_pilot_version: "0.45.0"
15
15
  token_pilot_body_hash: d172f600bf32277ea6eb4cbbee4542ddd698a986dcd96997d33930561964569b
16
16
  requiredMcpServers:
17
17
  - "token-pilot"
@@ -8,7 +8,7 @@ tools:
8
8
  - mcp__token-pilot__test_summary
9
9
  - mcp__token-pilot__outline
10
10
  - Bash
11
- token_pilot_version: "0.43.1"
11
+ token_pilot_version: "0.45.0"
12
12
  token_pilot_body_hash: de64a406b5176de19f7422619c7de7949b1f28865f225402c9cea9255f377428
13
13
  requiredMcpServers:
14
14
  - "token-pilot"
@@ -13,7 +13,7 @@ tools:
13
13
  - Edit
14
14
  - Glob
15
15
  model: sonnet
16
- token_pilot_version: "0.43.1"
16
+ token_pilot_version: "0.45.0"
17
17
  token_pilot_body_hash: 68b32af2dacd82ebe52c4eec93edb903d452688274c3065218270627c564d8b0
18
18
  requiredMcpServers:
19
19
  - "token-pilot"
@@ -11,7 +11,7 @@ tools:
11
11
  - Grep
12
12
  - Read
13
13
  model: sonnet
14
- token_pilot_version: "0.43.1"
14
+ token_pilot_version: "0.45.0"
15
15
  token_pilot_body_hash: d9b7f5b7ae6f4ae21305c775361bcab097cc774370a6d976c093571d46d55021
16
16
  requiredMcpServers:
17
17
  - "token-pilot"
@@ -12,7 +12,7 @@ tools:
12
12
  - Read
13
13
  - Bash
14
14
  model: sonnet
15
- token_pilot_version: "0.43.1"
15
+ token_pilot_version: "0.45.0"
16
16
  token_pilot_body_hash: 052413de8d92377edcde6ae5c823f5378db304baccfa29e8866467f42553a500
17
17
  requiredMcpServers:
18
18
  - "token-pilot"
@@ -9,7 +9,7 @@ tools:
9
9
  - Bash
10
10
  - Read
11
11
  model: haiku
12
- token_pilot_version: "0.43.1"
12
+ token_pilot_version: "0.45.0"
13
13
  token_pilot_body_hash: e14dc57493d816f8c2e017963e2ef5f66bea50fd0b805a80e8a0d97c968427e7
14
14
  requiredMcpServers:
15
15
  - "token-pilot"
@@ -13,7 +13,7 @@ tools:
13
13
  - Edit
14
14
  - Glob
15
15
  model: haiku
16
- token_pilot_version: "0.43.1"
16
+ token_pilot_version: "0.45.0"
17
17
  token_pilot_body_hash: 57d741794ab40e31a7ac49c68ea39a9088f5827cdef866ce81bfca1b7c9180cf
18
18
  requiredMcpServers:
19
19
  - "token-pilot"
@@ -10,7 +10,7 @@ tools:
10
10
  - Bash
11
11
  - Read
12
12
  model: haiku
13
- token_pilot_version: "0.43.1"
13
+ token_pilot_version: "0.45.0"
14
14
  token_pilot_body_hash: 7b70fa76a60e3c58a1de4f56c32c0f166424137e203a0cf1c8654e7c9235d904
15
15
  requiredMcpServers:
16
16
  - "token-pilot"
@@ -12,7 +12,7 @@ tools:
12
12
  - mcp__token-pilot__read_symbols
13
13
  - Read
14
14
  model: sonnet
15
- token_pilot_version: "0.43.1"
15
+ token_pilot_version: "0.45.0"
16
16
  token_pilot_body_hash: 351a987e11eba63852f5431a16d8eb53104f4f689f82fdcc5a2bf4db948ba92f
17
17
  requiredMcpServers:
18
18
  - "token-pilot"
@@ -8,7 +8,7 @@ tools:
8
8
  - mcp__token-pilot__read_symbol
9
9
  - Bash
10
10
  model: inherit
11
- token_pilot_version: "0.43.1"
11
+ token_pilot_version: "0.45.0"
12
12
  token_pilot_body_hash: de5722bfea374eaab096c1ae635c37879e7a91370ee3cd0532f4240be03c91eb
13
13
  requiredMcpServers:
14
14
  - "token-pilot"
@@ -13,7 +13,7 @@ tools:
13
13
  - Edit
14
14
  - Bash
15
15
  model: sonnet
16
- token_pilot_version: "0.43.1"
16
+ token_pilot_version: "0.45.0"
17
17
  token_pilot_body_hash: 375a824d0d847bb5453ec594c7a62ad566ee7e4d92717b0473f771f1a0477c60
18
18
  requiredMcpServers:
19
19
  - "token-pilot"
@@ -11,7 +11,7 @@ tools:
11
11
  - Grep
12
12
  - Glob
13
13
  model: sonnet
14
- token_pilot_version: "0.43.1"
14
+ token_pilot_version: "0.45.0"
15
15
  token_pilot_body_hash: 0334de1bf99b431b65359637d125cda7c44c6f780eb92c57cc538715b1939536
16
16
  requiredMcpServers:
17
17
  - "token-pilot"
@@ -10,7 +10,7 @@ tools:
10
10
  - mcp__token-pilot__smart_read
11
11
  - mcp__token-pilot__smart_read_many
12
12
  - mcp__token-pilot__read_section
13
- token_pilot_version: "0.43.1"
13
+ token_pilot_version: "0.45.0"
14
14
  token_pilot_body_hash: 832e95633fbc8e9b0c10f3e540a327d4be062fb4b3f17a6cce6be13f414e2927
15
15
  requiredMcpServers:
16
16
  - "token-pilot"
@@ -11,7 +11,7 @@ tools:
11
11
  - Bash
12
12
  - Read
13
13
  model: sonnet
14
- token_pilot_version: "0.43.1"
14
+ token_pilot_version: "0.45.0"
15
15
  token_pilot_body_hash: b61f06380d80798fa2e49d37bcba0653495bee04dd6bdbc1feff9a75607b0508
16
16
  requiredMcpServers:
17
17
  - "token-pilot"
@@ -11,7 +11,7 @@ tools:
11
11
  - mcp__token-pilot__read_for_edit
12
12
  - Read
13
13
  model: sonnet
14
- token_pilot_version: "0.43.1"
14
+ token_pilot_version: "0.45.0"
15
15
  token_pilot_body_hash: f83f50d05b4f70285ae7afed2b1a406fc436df56e61a0aedbfb31edc7f2b6e66
16
16
  requiredMcpServers:
17
17
  - "token-pilot"
@@ -8,7 +8,7 @@ tools:
8
8
  - mcp__token-pilot__outline
9
9
  - mcp__token-pilot__read_symbol
10
10
  model: sonnet
11
- token_pilot_version: "0.43.1"
11
+ token_pilot_version: "0.45.0"
12
12
  token_pilot_body_hash: c5f6fc122c89e16e5cf774045f92169ee3468555320b898171ba13eca5323550
13
13
  requiredMcpServers:
14
14
  - "token-pilot"
@@ -9,7 +9,7 @@ tools:
9
9
  - mcp__token-pilot__module_info
10
10
  - Bash
11
11
  model: sonnet
12
- token_pilot_version: "0.43.1"
12
+ token_pilot_version: "0.45.0"
13
13
  token_pilot_body_hash: 8ef3c3341cbfed4eb8dd130126a9683edc57e378c92ff0ca764d584fd941c55c
14
14
  requiredMcpServers:
15
15
  - "token-pilot"
package/agents/tp-run.md CHANGED
@@ -16,7 +16,7 @@ tools:
16
16
  - Glob
17
17
  - Bash
18
18
  model: haiku
19
- token_pilot_version: "0.43.1"
19
+ token_pilot_version: "0.45.0"
20
20
  token_pilot_body_hash: 2b08618d34a61f00aafccbda9fed6d83243296dedb83440edbd2d5c28bb6dbc4
21
21
  requiredMcpServers:
22
22
  - "token-pilot"
@@ -9,7 +9,7 @@ tools:
9
9
  - mcp__token-pilot__session_budget
10
10
  - Bash
11
11
  - Read
12
- token_pilot_version: "0.43.1"
12
+ token_pilot_version: "0.45.0"
13
13
  token_pilot_body_hash: 529374ed728f5eed5b758b3be3da65624783c0bf0c1a253d7d661a843eb5f767
14
14
  requiredMcpServers:
15
15
  - "token-pilot"
@@ -11,7 +11,7 @@ tools:
11
11
  - Read
12
12
  - Grep
13
13
  model: sonnet
14
- token_pilot_version: "0.43.1"
14
+ token_pilot_version: "0.45.0"
15
15
  token_pilot_body_hash: a60f6ae110eb3138064bce074e8ba26fa0ce5f4659df1624a9d9d3646803391b
16
16
  requiredMcpServers:
17
17
  - "token-pilot"
@@ -9,7 +9,7 @@ tools:
9
9
  - Read
10
10
  - Write
11
11
  model: sonnet
12
- token_pilot_version: "0.43.1"
12
+ token_pilot_version: "0.45.0"
13
13
  token_pilot_body_hash: c7a4e8b39228fd5158528f389c924c5ff2d98c4b9b05ee0106d54a26c5dc1350
14
14
  requiredMcpServers:
15
15
  - "token-pilot"
@@ -10,7 +10,7 @@ tools:
10
10
  - mcp__token-pilot__test_summary
11
11
  - Glob
12
12
  - Grep
13
- token_pilot_version: "0.43.1"
13
+ token_pilot_version: "0.45.0"
14
14
  token_pilot_body_hash: be81eed53a3720d146cf89e4a14a7a56577633f7c84c234c412ab70d64c05b11
15
15
  requiredMcpServers:
16
16
  - "token-pilot"
@@ -8,7 +8,7 @@ tools:
8
8
  - mcp__token-pilot__find_usages
9
9
  - mcp__token-pilot__read_symbol
10
10
  model: sonnet
11
- token_pilot_version: "0.43.1"
11
+ token_pilot_version: "0.45.0"
12
12
  token_pilot_body_hash: 362ecf4cb03b059421ea26933473700900073dc38b3a7fe271208dfb1ae14f90
13
13
  requiredMcpServers:
14
14
  - "token-pilot"
@@ -13,7 +13,7 @@ tools:
13
13
  - Edit
14
14
  - Bash
15
15
  model: sonnet
16
- token_pilot_version: "0.43.1"
16
+ token_pilot_version: "0.45.0"
17
17
  token_pilot_body_hash: 269f2fe22ff4517c277d3f56ca67d8a5527b93290ab21079a83ba7af22c1b5a9
18
18
  requiredMcpServers:
19
19
  - "token-pilot"
@@ -27,7 +27,11 @@ export const DEFAULT_CONFIG = {
27
27
  autoInstall: true,
28
28
  denyThreshold: 300,
29
29
  mode: "deny-enhanced",
30
- adaptiveThreshold: false,
30
+ // v0.44.0 — ON by default. The curve is a no-op below 30% session
31
+ // burn (short/light sessions read exactly as before), and only
32
+ // tightens the deny threshold once an agent has already pulled many
33
+ // large files — i.e. the long-session degradation users actually hit.
34
+ adaptiveThreshold: true,
31
35
  adaptiveBudgetTokens: 100_000,
32
36
  },
33
37
  context: {
@@ -38,6 +38,17 @@ export interface HookEvent {
38
38
  * never populate it; events stay shape-compatible.
39
39
  */
40
40
  parent_agent_id?: string | null;
41
+ /**
42
+ * v0.45.0 — root/parent SESSION id (distinct from agent_id). Claude Code
43
+ * exposes `parent_session_id` in subagent hook payloads (binary:
44
+ * `if(D.parentSessionId) X.parent_session_id = D.parentSessionId`). A
45
+ * subagent's MCP server is spawned with CLAUDE_CODE_SESSION_ID = the AGENT
46
+ * session, so its tool-call and denial savings get tagged with that id and
47
+ * the statusline's main-session filter drops them. Capturing the parent lets
48
+ * the badge roll subagent savings up to the conversation that spawned them.
49
+ * Optional — absent on the main thread and on older Claude Code.
50
+ */
51
+ parent_session_id?: string | null;
41
52
  /**
42
53
  * v0.38.0 — id of the token-pilot workflow this event belongs to,
43
54
  * when one is active (TOKEN_PILOT_WORKFLOW_ID set). Lets fleet-level
@@ -25,6 +25,11 @@
25
25
  * v0.28.0; tighten only if tool-audit shows repeated escapes.
26
26
  */
27
27
  const CODE_EXT_RE = /\.(ts|tsx|js|jsx|mjs|cjs|py|rb|go|rs|java|kt|swift|php|cs|cpp|c|h|hpp|scala|clj|ex|exs|elm|ml|fs|dart|lua|sh|bash|zsh)(\s|$|;|\||&|>|<)/;
28
+ // v0.44.0 — a `head -n N` / `tail -n N` slice this large on a code file is
29
+ // a whole-file dump in disguise. Mirrors the Read-hook denyThreshold (300)
30
+ // so the two layers agree on what counts as "too big". Smaller slices are
31
+ // the sanctioned bounded alternative and pass through.
32
+ const RAW_SLICE_DENY_LINES = 300;
28
33
  /** Check whether the command contains a specific utility invocation at
29
34
  * top level (not inside a quoted string). Cheap lexical match. */
30
35
  function invokes(command, utility) {
@@ -133,6 +138,37 @@ function detectHeavyPatternSingle(command) {
133
138
  "For head/tail access use `head -n N` or `tail -n N`.",
134
139
  };
135
140
  }
141
+ // 3b. sed / head / tail reading a code file as a raw range dump.
142
+ // v0.44.0 — closes the leak the cat rule left open: an agent under
143
+ // context pressure reaches for `sed -n '1,500p' file.ts` or
144
+ // `head -n 500 file.ts` to pull a big slice straight to stdout,
145
+ // sidestepping both the Read hook and the cat rule. Exempt the same
146
+ // shapes cat exempts — pipes (processing) and redirects (writing) —
147
+ // plus `sed -i` (in-place edit, not a read).
148
+ const dumpsCodeFile = CODE_EXT_RE.test(cmd) && !cmd.includes("|") && !/>/.test(cmd);
149
+ if (dumpsCodeFile && invokes(cmd, "sed") && !/\bsed\b[^|>]*\s-i\b/.test(cmd)) {
150
+ return {
151
+ kind: "deny",
152
+ reason: "`sed` on a code file dumps a raw range into context. " +
153
+ "Use mcp__token-pilot__read_range(path, start, end) for a bounded slice, " +
154
+ "read_symbol(path, name) for one function, or smart_read(path) for structure.",
155
+ };
156
+ }
157
+ if (dumpsCodeFile && (invokes(cmd, "head") || invokes(cmd, "tail"))) {
158
+ // Match an explicit line count: `-500`, `-n 500`, `-n500`. A `-c N`
159
+ // byte count or the default (no flag → 10 lines) never matches, so
160
+ // genuinely small slices pass through untouched.
161
+ const m = cmd.match(/(?:^|\s)-(?:n\s*)?(\d+)\b/);
162
+ const n = m ? parseInt(m[1], 10) : 0;
163
+ if (n >= RAW_SLICE_DENY_LINES) {
164
+ return {
165
+ kind: "deny",
166
+ reason: "`head`/`tail` with a large line count dumps a big slice into context. " +
167
+ "Use mcp__token-pilot__read_range(path, start, end) for a bounded slice, " +
168
+ "or smart_read(path) for a structural overview.",
169
+ };
170
+ }
171
+ }
136
172
  // 4. git log without -n / -N / -<N> (short-form max-count) / --max-count
137
173
  // v0.30.3: added -<N> support — `git log --oneline -5` is canonical
138
174
  // bounded syntax and must not trip the heuristic.
@@ -8,6 +8,7 @@
8
8
  * Output contract: one JSON line on stdout, or exit 0 silent.
9
9
  */
10
10
  import { type HookEvent } from "../core/event-log.js";
11
+ import { type ToolProfile } from "../server/tool-profiles.js";
11
12
  export declare function buildSubagentAdoptionNudge(events: HookEvent[], now: number, windowDays?: number, minSample?: number, threshold?: number): string | null;
12
13
  export interface AgentEntry {
13
14
  name: string;
@@ -43,5 +44,13 @@ export declare function buildReminderMessage(agents: AgentEntry[], maxReminderTo
43
44
  * Returns the JSON string to write to stdout, or null for silent exit.
44
45
  * Never throws — any error → null (fail-safe pass-through).
45
46
  */
47
+ /**
48
+ * v0.45.0 (token-pilot-2fd part 2) — when a trimmed TOOL profile is active,
49
+ * the banner still names tools that profile hides. The full default advertises
50
+ * everything, but an explicit nav/edit/minimal does not, so warn the agent
51
+ * before it calls a hidden tool, hits "No such tool available", and falls back
52
+ * to raw Read/Bash. Empty string for the default `full` profile.
53
+ */
54
+ export declare function profileBannerNote(profile: ToolProfile): string;
46
55
  export declare function handleSessionStart(opts: HandleSessionStartOptions): Promise<string | null>;
47
56
  //# sourceMappingURL=session-start.d.ts.map
@@ -11,6 +11,7 @@ import { readdir, readFile } from "node:fs/promises";
11
11
  import { join, basename } from "node:path";
12
12
  import { loadLatestSnapshot } from "./../handlers/session-snapshot-persist.js";
13
13
  import { loadEvents } from "../core/event-log.js";
14
+ import { parseProfileEnv } from "../server/tool-profiles.js";
14
15
  const SNAPSHOT_FRESH_MS = 2 * 3600 * 1000; // 2h — enough to cover compaction/restart, tight enough that a new day's unrelated work doesn't inherit yesterday's thread
15
16
  // ─── subagent adoption nudge (v0.32.0) ──────────────────────────────
16
17
  // Pure function: takes the event log + current time, returns either a
@@ -138,7 +139,8 @@ MANDATORY — use these BEFORE raw Read / Grep / git:
138
139
  smart_log(path?) — git log with symbol context (INSTEAD of raw git log)
139
140
  test_summary(command) — test runs without dumping full output
140
141
  project_overview — unfamiliar repo top-level map (first step)
141
- Batch variants (prefer over loops): read_symbols, smart_read_many, read_section.
142
+ Batch variants (prefer over loops): read_symbols, smart_read_many.
143
+ read_section — Markdown/YAML/JSON/CSV ONLY (by heading/key/row); for CODE use read_range / read_symbol.
142
144
  Also available: read_range, read_diff, module_info, related_files, explore_area,
143
145
  code_audit, find_unused, session_snapshot, session_budget, session_analytics.
144
146
  Raw Read/Grep allowed only with offset/limit / narrow regex / non-code files,
@@ -235,6 +237,20 @@ export function buildReminderMessage(agents, maxReminderTokens) {
235
237
  * Returns the JSON string to write to stdout, or null for silent exit.
236
238
  * Never throws — any error → null (fail-safe pass-through).
237
239
  */
240
+ /**
241
+ * v0.45.0 (token-pilot-2fd part 2) — when a trimmed TOOL profile is active,
242
+ * the banner still names tools that profile hides. The full default advertises
243
+ * everything, but an explicit nav/edit/minimal does not, so warn the agent
244
+ * before it calls a hidden tool, hits "No such tool available", and falls back
245
+ * to raw Read/Bash. Empty string for the default `full` profile.
246
+ */
247
+ export function profileBannerNote(profile) {
248
+ if (profile === "full")
249
+ return "";
250
+ return (`⚠ TOKEN_PILOT_PROFILE=${profile} — trimmed tool surface active. Some tools named below are NOT advertised this session ` +
251
+ `(test_summary / code_audit / find_unused always; read_for_edit / read_range / read_diff / batch reads on nav & minimal). ` +
252
+ `Calling them returns "No such tool available" — use the listed alternatives or unset TOKEN_PILOT_PROFILE to advertise all.\n\n`);
253
+ }
238
254
  export async function handleSessionStart(opts) {
239
255
  try {
240
256
  if (!opts.sessionStartConfig.enabled) {
@@ -242,6 +258,10 @@ export async function handleSessionStart(opts) {
242
258
  }
243
259
  const agents = await scanAgents(opts.projectRoot, opts.homeDir);
244
260
  let message = buildReminderMessage(agents, opts.sessionStartConfig.maxReminderTokens);
261
+ // Prepend a profile caveat when a trimmed surface hides referenced tools.
262
+ message =
263
+ profileBannerNote(parseProfileEnv(process.env.TOKEN_PILOT_PROFILE)) +
264
+ message;
245
265
  // TP-340: surface a fresh snapshot so the new session can resume.
246
266
  const snap = await loadLatestSnapshot(opts.projectRoot);
247
267
  if (snap && snap.ageMs < SNAPSHOT_FRESH_MS) {
@@ -38,6 +38,13 @@ export interface SubagentStopInput {
38
38
  last_assistant_message?: string;
39
39
  session_id?: string;
40
40
  parent_agent_id?: string;
41
+ /**
42
+ * v0.45.0 — root/parent session id. CC ships this in the SubagentStop
43
+ * payload (`X.parent_session_id`). Lets the statusline roll a subagent's
44
+ * savings (tagged with the agent's own session id) up to the parent
45
+ * conversation. Absent on older CC → field simply not written.
46
+ */
47
+ parent_session_id?: string;
41
48
  }
42
49
  /**
43
50
  * Best-effort token total from a subagent transcript (JSONL of CC
@@ -90,6 +90,9 @@ export function buildSubagentTaskEvent(input, now, tokensOverride) {
90
90
  agent_type: input.agent_type ?? null,
91
91
  agent_id: input.agent_id ?? null,
92
92
  ...(input.parent_agent_id ? { parent_agent_id: input.parent_agent_id } : {}),
93
+ ...(input.parent_session_id
94
+ ? { parent_session_id: input.parent_session_id }
95
+ : {}),
93
96
  event: "task",
94
97
  file: "",
95
98
  lines: 0,
package/dist/index.d.ts CHANGED
@@ -39,6 +39,21 @@ export declare function handleHookRead(filePathArg?: string, mode?: HookMode, de
39
39
  * wrapping.
40
40
  */
41
41
  export declare function runHookReadDispatch(filePathArg: string | undefined, mode: HookMode, denyThresholdArg?: number, projectRootArg?: string, adaptive?: HookReadAdaptiveOptions): Promise<string | null>;
42
+ /**
43
+ * v0.45.0 (token-pilot-xg9) — how many lines a Read actually pulls.
44
+ *
45
+ * An unbounded Read (no offset/limit) pulls the whole file. A bounded Read
46
+ * pulls `limit` lines starting at `offset` — but Claude Code's Read defaults
47
+ * to a 2000-line page, so `Read(file, limit=2000)` or an offset with no limit
48
+ * drags a whole big file through. The old hook passed ANY bounded Read
49
+ * straight through (`hasOffset || hasLimit → return null`), which is the leak:
50
+ * the model bounds with a large/default limit and reads everything hook-free
51
+ * AND un-counted in the adaptive burn signal. Comparing the *span* against the
52
+ * deny threshold closes that while still letting a genuinely narrow slice pass.
53
+ *
54
+ * `offset` / `limit` are null when the field is absent on the tool call.
55
+ */
56
+ export declare function effectiveReadSpanLines(totalLines: number, offset: number | null, limit: number | null): number;
42
57
  /**
43
58
  * PreToolUse:Edit / MultiEdit / Write enforcement.
44
59
  *
package/dist/index.js CHANGED
@@ -767,14 +767,36 @@ export async function runHookReadDispatch(filePathArg, mode, denyThresholdArg, p
767
767
  const projectRoot = projectRootArg ?? process.cwd();
768
768
  return runHookReadDispatchImpl(filePathArg, mode, denyThreshold, projectRoot, adaptive);
769
769
  }
770
+ /**
771
+ * v0.45.0 (token-pilot-xg9) — how many lines a Read actually pulls.
772
+ *
773
+ * An unbounded Read (no offset/limit) pulls the whole file. A bounded Read
774
+ * pulls `limit` lines starting at `offset` — but Claude Code's Read defaults
775
+ * to a 2000-line page, so `Read(file, limit=2000)` or an offset with no limit
776
+ * drags a whole big file through. The old hook passed ANY bounded Read
777
+ * straight through (`hasOffset || hasLimit → return null`), which is the leak:
778
+ * the model bounds with a large/default limit and reads everything hook-free
779
+ * AND un-counted in the adaptive burn signal. Comparing the *span* against the
780
+ * deny threshold closes that while still letting a genuinely narrow slice pass.
781
+ *
782
+ * `offset` / `limit` are null when the field is absent on the tool call.
783
+ */
784
+ export function effectiveReadSpanLines(totalLines, offset, limit) {
785
+ if (offset == null && limit == null)
786
+ return totalLines;
787
+ const DEFAULT_READ_PAGE = 2000;
788
+ const start = offset != null && offset > 0 ? offset : 0;
789
+ const page = limit != null && limit >= 0 ? limit : DEFAULT_READ_PAGE;
790
+ return Math.max(0, Math.min(page, totalLines - start));
791
+ }
770
792
  async function runHookReadDispatchImpl(filePathArg, mode, denyThreshold, projectRoot, adaptive = {}) {
771
793
  if (mode === "off")
772
794
  return null;
773
795
  // Parse stdin to get tool_input + session/agent metadata, unless a
774
796
  // filePath was supplied directly (tests, --filePath invocation).
775
797
  let filePath = filePathArg;
776
- let hasOffset = false;
777
- let hasLimit = false;
798
+ let offsetVal = null;
799
+ let limitVal = null;
778
800
  let sessionId = "";
779
801
  let agentType = null;
780
802
  let agentId = null;
@@ -783,8 +805,14 @@ async function runHookReadDispatchImpl(filePathArg, mode, denyThreshold, project
783
805
  const stdin = readFileSync(0, "utf-8");
784
806
  const input = JSON.parse(stdin);
785
807
  filePath = input?.tool_input?.file_path;
786
- hasOffset = input?.tool_input?.offset != null;
787
- hasLimit = input?.tool_input?.limit != null;
808
+ offsetVal =
809
+ typeof input?.tool_input?.offset === "number"
810
+ ? input.tool_input.offset
811
+ : null;
812
+ limitVal =
813
+ typeof input?.tool_input?.limit === "number"
814
+ ? input.tool_input.limit
815
+ : null;
788
816
  sessionId = typeof input?.session_id === "string" ? input.session_id : "";
789
817
  agentType =
790
818
  typeof input?.agent_type === "string" ? input.agent_type : null;
@@ -799,9 +827,6 @@ async function runHookReadDispatchImpl(filePathArg, mode, denyThreshold, project
799
827
  const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
800
828
  if (!CODE_EXTENSIONS.has(ext))
801
829
  return null;
802
- // Bounded Reads are always passed through — the agent already narrowed scope.
803
- if (hasOffset || hasLimit)
804
- return null;
805
830
  // Path safety: refuse to summarise any file outside the project root
806
831
  // (traversal, symlinks pointing outside). Pass-through on failure so the
807
832
  // agent is never blocked by a safety reject.
@@ -829,13 +854,20 @@ async function runHookReadDispatchImpl(filePathArg, mode, denyThreshold, project
829
854
  try {
830
855
  fileContent = readFileSync(filePath, "utf-8");
831
856
  lineCount = fileContent.split("\n").length;
832
- if (lineCount <= effectiveThreshold)
833
- return null;
834
857
  }
835
858
  catch {
836
859
  return null;
837
860
  }
838
- const charEst = Math.ceil(fileContent.length / 4);
861
+ // v0.45.0 (token-pilot-xg9) — measure the span the Read actually pulls, not
862
+ // just the file size. A narrow bounded slice (< threshold) still passes; a
863
+ // `limit=2000` / offset-no-limit read of a big file no longer slips through.
864
+ const spanLines = effectiveReadSpanLines(lineCount, offsetVal, limitVal);
865
+ if (spanLines <= effectiveThreshold)
866
+ return null;
867
+ // Cost estimate reflects what the read would pull (the span), so a bounded
868
+ // deny doesn't over-report savings vs the whole-file figure.
869
+ const spanRatio = lineCount > 0 ? Math.min(1, spanLines / lineCount) : 1;
870
+ const charEst = Math.ceil((fileContent.length * spanRatio) / 4);
839
871
  const wsRatio = (fileContent.match(/\s/g)?.length ?? 0) / fileContent.length;
840
872
  const estTokens = Math.ceil(charEst * (1 - wsRatio * 0.3));
841
873
  // Legacy telemetry (hook-denied.jsonl) — retained for backward compatibility
@@ -873,7 +905,7 @@ async function runHookReadDispatchImpl(filePathArg, mode, denyThreshold, project
873
905
  if (mode === "advisory") {
874
906
  const reason = `File "${filePath}" has ${lineCount} lines. Use mcp__token-pilot__smart_read("${filePath}") ` +
875
907
  `for a structural overview, or mcp__token-pilot__read_for_edit("${filePath}", symbol="<name>") ` +
876
- `for edit context. Bounded Read with offset/limit is still allowed.`;
908
+ `for edit context. A narrow bounded Read (small limit) is still allowed.`;
877
909
  await writeEvent("denied", Math.ceil(reason.length / 4));
878
910
  return JSON.stringify({
879
911
  hookSpecificOutput: {
@@ -1282,6 +1314,29 @@ export async function handleDoctor() {
1282
1314
  catch {
1283
1315
  /* ignore */
1284
1316
  }
1317
+ // ── explicit trimmed-profile warning (v0.45.0, token-pilot-26b) ──
1318
+ // An explicit TOKEN_PILOT_PROFILE=nav|edit|minimal hides tools the rules and
1319
+ // the pre-edit hook still reference (read_for_edit / read_range / batch),
1320
+ // trapping edit sessions on "No such tool available". This recurred for two
1321
+ // users. Surface it loudly so they can remove it.
1322
+ try {
1323
+ const raw = process.env.TOKEN_PILOT_PROFILE;
1324
+ if (raw && raw.trim()) {
1325
+ const { parseProfileEnv } = await import("./server/tool-profiles.js");
1326
+ if (parseProfileEnv(raw) !== "full") {
1327
+ console.log(`⚠ TOKEN_PILOT_PROFILE=${raw.trim()} is set — a TRIMMED tool surface.\n` +
1328
+ ` It hides read_for_edit / read_range / batch reads (and code_audit /\n` +
1329
+ ` find_unused / test_summary) that the rules + pre-edit hook still name →\n` +
1330
+ ` calls to them fail with "No such tool available" and the agent falls\n` +
1331
+ ` back to raw Read/Bash.\n` +
1332
+ ` Fix: remove "TOKEN_PILOT_PROFILE" from your .mcp.json env block (or set\n` +
1333
+ ` it to "full"), then restart. Full is the default since v0.45.0.\n`);
1334
+ }
1335
+ }
1336
+ }
1337
+ catch {
1338
+ /* doctor must never crash over an optional check */
1339
+ }
1285
1340
  // ── profile recommendation ──
1286
1341
  // v0.26.4 — data-driven. Reads cumulative tool-calls.jsonl and suggests
1287
1342
  // the narrowest TOKEN_PILOT_PROFILE that wouldn't hide any tool the
@@ -41,25 +41,29 @@ export function recommendProfile(events) {
41
41
  lowConfidence: true,
42
42
  };
43
43
  }
44
+ // v0.45.0 (token-pilot-26b) — we NO LONGER recommend trimming to nav/edit.
45
+ // Past tool usage doesn't predict future edits, and a trimmed profile hides
46
+ // read_for_edit / read_range / batch reads that the rules and the pre-edit
47
+ // hook still reference — so the agent calls them, hits "No such tool
48
+ // available", and falls back to raw Read/Bash. That recurring trap cost two
49
+ // users whole sessions. Full is the recommendation; minimal stays a
50
+ // self-serve, clearly-warned opt-in for context-critical work only.
44
51
  const allInNav = [...used].every((t) => NAV_TOOLS.has(t));
45
52
  if (allInNav) {
46
53
  return {
47
- recommended: "nav",
48
- reason: `Every tool you've used (${uniqueToolsSeen} distinct) is part of the nav subset. You're a read-only explorer.`,
54
+ recommended: "full",
55
+ reason: `You've used only nav-subset tools so far (${uniqueToolsSeen} distinct), but read_for_edit / read_range / batch reads — named by the rules and the pre-edit hook — live in edit/full. Stay on full so an edit doesn't hit "No such tool available". Set TOKEN_PILOT_PROFILE=minimal yourself ONLY if context is critically tight (it hides edit tools).`,
49
56
  uniqueToolsSeen,
50
57
  totalCalls,
51
- wouldHide: [
52
- ...[...EDIT_EXTRAS].filter((t) => !used.has(t)),
53
- /* full-only — we don't enumerate here, keep the list short */
54
- ],
58
+ wouldHide: [],
55
59
  lowConfidence: false,
56
60
  };
57
61
  }
58
62
  const allInEditOrBelow = [...used].every((t) => NAV_TOOLS.has(t) || EDIT_EXTRAS.has(t));
59
63
  if (allInEditOrBelow) {
60
64
  return {
61
- recommended: "edit",
62
- reason: `You use edit-preparation tools (read_for_edit, batch reads) but never reach for full-only tools like code_audit/test_summary/find_unused.`,
65
+ recommended: "full",
66
+ reason: `You use edit-prep tools but haven't reached for full-only ones (code_audit/test_summary/find_unused) yet. Stay on full — they cost ~1k tokens to advertise but trimming hides them the moment you need one, and dead calls cost more.`,
63
67
  uniqueToolsSeen,
64
68
  totalCalls,
65
69
  wouldHide: [],
@@ -87,15 +91,14 @@ export function formatRecommendation(rec) {
87
91
  lines.push(` data: ${rec.totalCalls} calls, ${rec.uniqueToolsSeen} distinct tools`);
88
92
  lines.push(` recommend: TOKEN_PILOT_PROFILE=${rec.recommended}`);
89
93
  lines.push(` why: ${rec.reason}`);
90
- if (rec.recommended !== "full") {
91
- lines.push(` savings: ~${rec.recommended === "nav" ? "2200 tokens (−54%)" : "1000 tokens (−25%)"} on every tools/list response`);
92
- lines.push(` apply: add "env": { "TOKEN_PILOT_PROFILE": "${rec.recommended}" } to your token-pilot entry in .mcp.json`);
93
- }
94
- else if (rec.lowConfidence) {
95
- lines.push(` action: keep default (full). Re-run \`token-pilot doctor\` after a few real sessions for a data-backed suggestion.`);
94
+ // v0.45.0 (26b) — we no longer print an "apply nav/edit to .mcp.json"
95
+ // snippet. recommendProfile always returns `full`; the old snippet trapped
96
+ // users into trimming, which hid edit tools the rules reference.
97
+ if (rec.lowConfidence) {
98
+ lines.push(` action: keep default (full). Re-run \`token-pilot doctor\` after a few real sessions for a data-backed view.`);
96
99
  }
97
100
  else {
98
- lines.push(` action: keep default (full). You're using what you have.`);
101
+ lines.push(` action: keep default (full). Trim to minimal yourself only for context-critical, read-only work.`);
99
102
  }
100
103
  return lines.join("\n");
101
104
  }
@@ -8,8 +8,8 @@
8
8
  *
9
9
  * minimal — 5 core tools, minimal context overhead
10
10
  * nav — 10 exploration tools, no editing
11
- * edit — nav + 6 edit-prep tools (DEFAULT)
12
- * full — everything including audit tools
11
+ * edit — nav + 6 edit-prep tools
12
+ * full — everything including audit tools (DEFAULT since v0.45.0)
13
13
  */
14
14
  import type { ToolProfile } from "./tool-profiles.js";
15
15
  /**
@@ -42,7 +42,7 @@ const MCP_INSTRUCTIONS_NAV = [
42
42
  "• Explore: project_overview → explore_area → smart_read → read_symbol",
43
43
  ].join("\n");
44
44
  // ---------------------------------------------------------------------------
45
- // Edit profile — nav + batch reads + edit-prep (DEFAULT)
45
+ // Edit profile — nav + batch reads + edit-prep
46
46
  // ---------------------------------------------------------------------------
47
47
  const MCP_INSTRUCTIONS_EDIT = [
48
48
  "Token Pilot — token-efficient code reading (saves 60-80% tokens). ALWAYS prefer these tools over Read/cat/grep.",
@@ -87,7 +87,7 @@ const MCP_INSTRUCTIONS_EDIT = [
87
87
  "• Long session: session_snapshot → compact context → continue with minimal state",
88
88
  ].join("\n");
89
89
  // ---------------------------------------------------------------------------
90
- // Full profile — all tools including audit (code_audit, find_unused, test_summary)
90
+ // Full profile — all tools including audit (code_audit, find_unused, test_summary) — DEFAULT since v0.45.0
91
91
  // ---------------------------------------------------------------------------
92
92
  const MCP_INSTRUCTIONS_FULL = [
93
93
  "Token Pilot — token-efficient code reading (saves 60-80% tokens). ALWAYS prefer these tools over Read/cat/grep.",
@@ -297,7 +297,7 @@ export const TOOL_DEFINITIONS = [
297
297
  },
298
298
  {
299
299
  name: "read_section",
300
- description: "Read a specific section from Markdown, YAML, JSON, or CSV files. Markdown: by heading name. YAML/JSON: by top-level key. CSV: by row range (rows:1-50). Much cheaper than reading the whole file.",
300
+ description: "Read a specific section from Markdown, YAML, JSON, or CSV files. Markdown: by heading name. YAML/JSON: by top-level key. CSV: by row range (rows:1-50). Much cheaper than reading the whole file. DOCS/DATA ONLY — `heading` is required; this does NOT read code by line/symbol. For source files use read_range (line range) or read_symbol (one symbol).",
301
301
  inputSchema: {
302
302
  type: "object",
303
303
  properties: {
@@ -117,8 +117,16 @@ export function filterToolsByProfile(tools, profile) {
117
117
  * silently apply a guess.
118
118
  */
119
119
  export function parseProfileEnv(envValue, warn = () => { }) {
120
+ // v0.45.0 — default is now `full` (was `edit`). Trimming the advertised
121
+ // tools/list saved ~2k tokens but created a mismatch: the rules, the
122
+ // SessionStart/PostToolUse banners and the pre-edit hook all reference tools
123
+ // (read_for_edit, test_summary, batch reads) that a trimmed profile hides —
124
+ // so the model calls them, hits "No such tool available", and falls back to
125
+ // raw Read/Bash. Those dead round-trips cost far more than the 2k saved.
126
+ // Advertise everything by default; users who truly need the smaller surface
127
+ // opt in with TOKEN_PILOT_PROFILE=nav|edit|minimal.
120
128
  if (!envValue)
121
- return "edit";
129
+ return "full";
122
130
  const lower = envValue.trim().toLowerCase();
123
131
  if (lower === "full" ||
124
132
  lower === "nav" ||
@@ -126,7 +134,7 @@ export function parseProfileEnv(envValue, warn = () => { }) {
126
134
  lower === "minimal") {
127
135
  return lower;
128
136
  }
129
- warn(`[token-pilot] Unknown TOKEN_PILOT_PROFILE="${envValue}". Expected full|nav|edit|minimal. Falling back to edit.`);
130
- return "edit";
137
+ warn(`[token-pilot] Unknown TOKEN_PILOT_PROFILE="${envValue}". Expected full|nav|edit|minimal. Falling back to full.`);
138
+ return "full";
131
139
  }
132
140
  //# sourceMappingURL=tool-profiles.js.map
package/dist/server.js CHANGED
@@ -4,6 +4,7 @@ import { AstIndexClient } from "./ast-index/client.js";
4
4
  import { FileCache } from "./core/file-cache.js";
5
5
  import { ContextRegistry } from "./core/context-registry.js";
6
6
  import { SessionRegistryManager } from "./core/session-registry.js";
7
+ import { appendError, classifyError } from "./core/error-log.js";
7
8
  import { SymbolResolver } from "./core/symbol-resolver.js";
8
9
  import { SessionAnalytics, } from "./core/session-analytics.js";
9
10
  import { classifyIntent } from "./core/intent-classifier.js";
@@ -277,8 +278,8 @@ export async function createServer(projectRoot, options) {
277
278
  instructions: getMcpInstructions(activeProfile),
278
279
  });
279
280
  const advertisedTools = filterToolsByProfile(TOOL_DEFINITIONS, activeProfile);
280
- if (activeProfile !== "edit") {
281
- process.stderr.write(`[token-pilot] Profile: ${activeProfile} — advertising ${advertisedTools.length}/${TOOL_DEFINITIONS.length} tools. Set TOKEN_PILOT_PROFILE=edit for the default set.\n`);
281
+ if (activeProfile !== "full") {
282
+ process.stderr.write(`[token-pilot] Profile: ${activeProfile} — advertising ${advertisedTools.length}/${TOOL_DEFINITIONS.length} tools (full is the default). A trimmed profile hides tools the rules/hooks still reference; unset TOKEN_PILOT_PROFILE to advertise all.\n`);
282
283
  }
283
284
  server.setRequestHandler(ListToolsRequestSchema, () => ({
284
285
  tools: advertisedTools,
@@ -1057,6 +1058,17 @@ export async function createServer(projectRoot, options) {
1057
1058
  return { content: budgetResult.content };
1058
1059
  }
1059
1060
  default:
1061
+ // token-pilot-m68 — an unknown tool NAME that still reached the
1062
+ // server (a forwarded call CC didn't reject). Log it so the gap is
1063
+ // visible via `token-pilot errors`.
1064
+ void appendError({
1065
+ ts: Date.now(),
1066
+ hook: `mcp-tool:${name}`,
1067
+ level: "warn",
1068
+ code: "unknown_tool",
1069
+ msg: `Unknown tool: ${name}`,
1070
+ input: { tool: name },
1071
+ });
1060
1072
  return {
1061
1073
  content: [{ type: "text", text: `Unknown tool: ${name}` }],
1062
1074
  isError: true,
@@ -1065,6 +1077,23 @@ export async function createServer(projectRoot, options) {
1065
1077
  }
1066
1078
  catch (err) {
1067
1079
  const message = err instanceof Error ? err.message : String(err);
1080
+ // v0.45.0 (token-pilot-m68) — surface tool failures that REACH the
1081
+ // server (validation errors, handler exceptions) in the same
1082
+ // ~/.token-pilot/hook-errors.jsonl the hooks use, so `token-pilot errors`
1083
+ // shows them. Previously these vanished — telemetry reported "saving
1084
+ // tokens, all ok" while broken tp calls were invisible. Best-effort;
1085
+ // appendError never throws. NOTE: "No such tool available" rejections
1086
+ // happen at the Claude Code layer BEFORE the call reaches us, so those
1087
+ // remain invisible by CC design (mitigated by the full-tool default).
1088
+ void appendError({
1089
+ ts: Date.now(),
1090
+ hook: `mcp-tool:${name}`,
1091
+ level: "error",
1092
+ code: classifyError(err),
1093
+ msg: message,
1094
+ stack: err instanceof Error ? err.stack : undefined,
1095
+ input: { tool: name },
1096
+ });
1068
1097
  return {
1069
1098
  content: [{ type: "text", text: `Error: ${message}` }],
1070
1099
  isError: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-pilot",
3
- "version": "0.43.1",
3
+ "version": "0.45.0",
4
4
  "description": "Save up to 80% tokens when AI reads code — MCP server for token-efficient code navigation, AST-aware structural reading instead of dumping full files into context window",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",