token-pilot 0.30.1 → 0.30.3

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 (39) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +13 -6
  4. package/agents/tp-api-surface-tracker.md +1 -1
  5. package/agents/tp-audit-scanner.md +1 -1
  6. package/agents/tp-commit-writer.md +1 -1
  7. package/agents/tp-context-engineer.md +1 -1
  8. package/agents/tp-dead-code-finder.md +1 -1
  9. package/agents/tp-debugger.md +1 -1
  10. package/agents/tp-dep-health.md +1 -1
  11. package/agents/tp-doc-writer.md +1 -1
  12. package/agents/tp-history-explorer.md +1 -1
  13. package/agents/tp-impact-analyzer.md +1 -1
  14. package/agents/tp-incident-timeline.md +1 -1
  15. package/agents/tp-incremental-builder.md +1 -1
  16. package/agents/tp-migration-scout.md +1 -1
  17. package/agents/tp-onboard.md +1 -1
  18. package/agents/tp-performance-profiler.md +1 -1
  19. package/agents/tp-pr-reviewer.md +1 -1
  20. package/agents/tp-refactor-planner.md +1 -1
  21. package/agents/tp-review-impact.md +1 -1
  22. package/agents/tp-run.md +1 -1
  23. package/agents/tp-session-restorer.md +1 -1
  24. package/agents/tp-ship-coordinator.md +1 -1
  25. package/agents/tp-spec-writer.md +1 -1
  26. package/agents/tp-test-coverage-gapper.md +1 -1
  27. package/agents/tp-test-triage.md +1 -1
  28. package/agents/tp-test-writer.md +1 -1
  29. package/dist/cli/ecosystem-check.d.ts +56 -0
  30. package/dist/cli/ecosystem-check.js +244 -0
  31. package/dist/cli/ecosystem-reminder.d.ts +35 -0
  32. package/dist/cli/ecosystem-reminder.js +59 -0
  33. package/dist/hooks/installer.js +0 -9
  34. package/dist/hooks/pre-edit.js +9 -5
  35. package/dist/index.js +34 -0
  36. package/docs/configuration.md +51 -0
  37. package/docs/ecosystem.md +83 -0
  38. package/hooks/hooks.json +0 -9
  39. package/package.json +1 -1
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Token Pilot \u2014 save 60-90% tokens when AI reads code",
9
- "version": "0.30.1"
9
+ "version": "0.30.3"
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. 22 MCP tools + 19 subagents + budget watchdog hooks.",
16
- "version": "0.30.1",
16
+ "version": "0.30.3",
17
17
  "author": {
18
18
  "name": "Digital-Threads"
19
19
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-pilot",
3
- "version": "0.30.1",
3
+ "version": "0.30.3",
4
4
  "description": "Saves 60-90% tokens when AI reads code. AST-aware lazy reading, symbol navigation, cross-session tool-usage analytics, 22 subagents (haiku/sonnet/opus-tiered) with budget watchdog.",
5
5
  "author": {
6
6
  "name": "Digital-Threads",
package/README.md CHANGED
@@ -79,13 +79,20 @@ TOKEN_PILOT_MODE=strict npx token-pilot
79
79
 
80
80
  ## Ecosystem
81
81
 
82
- | Tool | Role |
83
- |------|------|
84
- | **Token Pilot** | Enforcement layer — hooks, MCP structural reads, subagents |
85
- | **[ast-index](https://github.com/defendend/Claude-ast-index-search)** | Structural indexer. Auto-installed by Token Pilot; also a standalone CLI for bash-only agents |
86
- | **[context-mode](https://github.com/mksglu/claude-context-mode)** | Sandbox executor — runs shell/python/js, only stdout enters the context window |
82
+ Token Pilot owns **input** tokens — the stuff Claude reads from files, git, search. The other half of a session (what Claude *writes* back, how it executes code, how it remembers state across days) is owned by separate tools. They compose cleanly:
87
83
 
88
- Rules of thumb: read code `smart_read`/`read_symbol`; execute code with big output → context-mode `execute`; bash-only agent → `ast-index` CLI. Never copy all three into `CLAUDE.md` — Token Pilot's `doctor` warns when `CLAUDE.md` exceeds 60 lines.
84
+ | Tool | Owns | Typical savings |
85
+ |------|------|----------------:|
86
+ | **Token Pilot** | code reads, git, search | 60-90% input |
87
+ | **[caveman](https://github.com/JuliusBrussee/caveman)** | Claude's response prose (terse-speak skill) | ~75% output |
88
+ | **[ast-index](https://github.com/defendend/Claude-ast-index-search)** | the structural indexer Token Pilot rides on | foundation |
89
+ | **[context-mode](https://github.com/mksglu/claude-context-mode)** | sandboxed shell / python / js execution | 90%+ on big stdout |
90
+
91
+ A session that pairs `token-pilot` + `caveman` typically hits **~85-90% total reduction** — each cuts a different half, no overlap. Install what you need; none of them assume the others are present.
92
+
93
+ → [full ecosystem map](docs/ecosystem.md)
94
+
95
+ Rules of thumb: read code → `smart_read`/`read_symbol`; execute code with big output → context-mode `execute`; bash-only agent → `ast-index` CLI. Never copy the whole stack into `CLAUDE.md` — Token Pilot's `doctor` warns when `CLAUDE.md` exceeds 60 lines.
89
96
 
90
97
  ## Supported Languages
91
98
 
@@ -9,7 +9,7 @@ tools:
9
9
  - mcp__token-pilot__read_symbol
10
10
  - Bash
11
11
  model: haiku
12
- token_pilot_version: "0.30.1"
12
+ token_pilot_version: "0.30.3"
13
13
  token_pilot_body_hash: c9d33476fdf70c8a7a493ec8720f54792eda2f81585996246e94c130ff3ec356
14
14
  ---
15
15
 
@@ -11,7 +11,7 @@ tools:
11
11
  - Grep
12
12
  - Read
13
13
  model: sonnet
14
- token_pilot_version: "0.30.1"
14
+ token_pilot_version: "0.30.3"
15
15
  token_pilot_body_hash: 7095ffab66aca2e424f00875933e3f63bc10651eef2fde6a59f08bbbdbf86f7c
16
16
  ---
17
17
 
@@ -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.30.1"
11
+ token_pilot_version: "0.30.3"
12
12
  token_pilot_body_hash: b6831f11c61a9b255c2b6ffa04837130242fd02843463a7d30f109c1a06b3e3f
13
13
  ---
14
14
 
@@ -13,7 +13,7 @@ tools:
13
13
  - Edit
14
14
  - Glob
15
15
  model: sonnet
16
- token_pilot_version: "0.30.1"
16
+ token_pilot_version: "0.30.3"
17
17
  token_pilot_body_hash: 43f9364ce722ff76daf0f8720ddaf9f77e18d4c4ed8bee3e15f12d207798e778
18
18
  ---
19
19
 
@@ -11,7 +11,7 @@ tools:
11
11
  - Grep
12
12
  - Read
13
13
  model: sonnet
14
- token_pilot_version: "0.30.1"
14
+ token_pilot_version: "0.30.3"
15
15
  token_pilot_body_hash: 386760aed26df6c3595d3267954605565fad08afa8761e016079ae60c19887a8
16
16
  ---
17
17
 
@@ -12,7 +12,7 @@ tools:
12
12
  - Read
13
13
  - Bash
14
14
  model: sonnet
15
- token_pilot_version: "0.30.1"
15
+ token_pilot_version: "0.30.3"
16
16
  token_pilot_body_hash: 71738830d025e86c70988e046a2f7f30b4590f3d284291a18609ed5fdd732321
17
17
  ---
18
18
 
@@ -9,7 +9,7 @@ tools:
9
9
  - Bash
10
10
  - Read
11
11
  model: haiku
12
- token_pilot_version: "0.30.1"
12
+ token_pilot_version: "0.30.3"
13
13
  token_pilot_body_hash: 12634cd28889d0a0ef1b4a6b994ba978353e14f3cb349011c393076e7e2b5c96
14
14
  ---
15
15
 
@@ -13,7 +13,7 @@ tools:
13
13
  - Edit
14
14
  - Glob
15
15
  model: haiku
16
- token_pilot_version: "0.30.1"
16
+ token_pilot_version: "0.30.3"
17
17
  token_pilot_body_hash: 8e29d07dd8f58adeb9530ec477a59a6e42de6c624f322d2c6cfa8da66456b46a
18
18
  ---
19
19
 
@@ -10,7 +10,7 @@ tools:
10
10
  - Bash
11
11
  - Read
12
12
  model: haiku
13
- token_pilot_version: "0.30.1"
13
+ token_pilot_version: "0.30.3"
14
14
  token_pilot_body_hash: 260197bc31531352f5eda3b70cf114c7c57bb7e9373f68ca76161dd68a804b0d
15
15
  ---
16
16
 
@@ -12,7 +12,7 @@ tools:
12
12
  - mcp__token-pilot__read_symbols
13
13
  - Read
14
14
  model: sonnet
15
- token_pilot_version: "0.30.1"
15
+ token_pilot_version: "0.30.3"
16
16
  token_pilot_body_hash: 1da6936cc117a7627640fae3cc85bf13a17f0b0b0d0d533423dfb4b7c0b4b1c2
17
17
  ---
18
18
 
@@ -8,7 +8,7 @@ tools:
8
8
  - mcp__token-pilot__read_symbol
9
9
  - Bash
10
10
  model: inherit
11
- token_pilot_version: "0.30.1"
11
+ token_pilot_version: "0.30.3"
12
12
  token_pilot_body_hash: 213746bab7acb6730a6edb16e1ff7b2c56572c3adf4f94990799f1c168cfa2ad
13
13
  ---
14
14
 
@@ -13,7 +13,7 @@ tools:
13
13
  - Edit
14
14
  - Bash
15
15
  model: sonnet
16
- token_pilot_version: "0.30.1"
16
+ token_pilot_version: "0.30.3"
17
17
  token_pilot_body_hash: 14c9adcabfb772c77a467a5fbfa682abbd5adc87e22d7fbe5d1329ffd790dde5
18
18
  ---
19
19
 
@@ -11,7 +11,7 @@ tools:
11
11
  - Grep
12
12
  - Glob
13
13
  model: sonnet
14
- token_pilot_version: "0.30.1"
14
+ token_pilot_version: "0.30.3"
15
15
  token_pilot_body_hash: 62893e448e943d0e1b928a670823ec3e152de395e487564862f145bd82161fcb
16
16
  ---
17
17
 
@@ -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.30.1"
13
+ token_pilot_version: "0.30.3"
14
14
  token_pilot_body_hash: 4e82f7b3c6446663e958fb6bf5eb5348bbdf33389269c888ce0dab766e50561f
15
15
  ---
16
16
 
@@ -11,7 +11,7 @@ tools:
11
11
  - Bash
12
12
  - Read
13
13
  model: sonnet
14
- token_pilot_version: "0.30.1"
14
+ token_pilot_version: "0.30.3"
15
15
  token_pilot_body_hash: 8b9f454a47e57e3761668de788850ef97d5d6f127b059cf8e0cef03deaca3f98
16
16
  ---
17
17
 
@@ -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.30.1"
14
+ token_pilot_version: "0.30.3"
15
15
  token_pilot_body_hash: 91003b244472c4e65d840b55474a86ce04fba379859d588cc0fa54850b0e1e4f
16
16
  ---
17
17
 
@@ -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.30.1"
11
+ token_pilot_version: "0.30.3"
12
12
  token_pilot_body_hash: 45f972c6b36929491a529322bac3c34fd44872f7be4a974d25c7e27cb12e9dc3
13
13
  ---
14
14
 
@@ -9,7 +9,7 @@ tools:
9
9
  - mcp__token-pilot__module_info
10
10
  - Bash
11
11
  model: sonnet
12
- token_pilot_version: "0.30.1"
12
+ token_pilot_version: "0.30.3"
13
13
  token_pilot_body_hash: 3c1c66f952ac63a5936bec86fefda8c842fb9713bca81e48ca5bb568ccb5f367
14
14
  ---
15
15
 
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.30.1"
19
+ token_pilot_version: "0.30.3"
20
20
  token_pilot_body_hash: de342efe1e3ee265df1773ebde1241555750ab17de249190a5c1c200f1f8f51a
21
21
  ---
22
22
 
@@ -9,7 +9,7 @@ tools:
9
9
  - mcp__token-pilot__session_budget
10
10
  - Bash
11
11
  - Read
12
- token_pilot_version: "0.30.1"
12
+ token_pilot_version: "0.30.3"
13
13
  token_pilot_body_hash: d031f30e9cc4ea454aa256427659ed27249d820b75dc8b9b99c81ba7635230a7
14
14
  ---
15
15
 
@@ -11,7 +11,7 @@ tools:
11
11
  - Read
12
12
  - Grep
13
13
  model: sonnet
14
- token_pilot_version: "0.30.1"
14
+ token_pilot_version: "0.30.3"
15
15
  token_pilot_body_hash: 6b1c27b3dc4fad622cebff7c49e079fc764ca0ae57ef5bc4e61b563d8321092d
16
16
  ---
17
17
 
@@ -9,7 +9,7 @@ tools:
9
9
  - Read
10
10
  - Write
11
11
  model: sonnet
12
- token_pilot_version: "0.30.1"
12
+ token_pilot_version: "0.30.3"
13
13
  token_pilot_body_hash: 4ae44482db80a8a3a43794c6ecb665ec0b5385a274e1e5b2e3a404956075be88
14
14
  ---
15
15
 
@@ -10,7 +10,7 @@ tools:
10
10
  - mcp__token-pilot__test_summary
11
11
  - Glob
12
12
  - Grep
13
- token_pilot_version: "0.30.1"
13
+ token_pilot_version: "0.30.3"
14
14
  token_pilot_body_hash: 6d862d1bcaeda3fb13099f51e40faaaf45d16d7d41d1b938609500192aa606f2
15
15
  ---
16
16
 
@@ -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.30.1"
11
+ token_pilot_version: "0.30.3"
12
12
  token_pilot_body_hash: f4e0dcbd2b4e8648efcafc9d53101a66bf394d7c90e97df7581ac47fcfbff5cb
13
13
  ---
14
14
 
@@ -13,7 +13,7 @@ tools:
13
13
  - Edit
14
14
  - Bash
15
15
  model: sonnet
16
- token_pilot_version: "0.30.1"
16
+ token_pilot_version: "0.30.3"
17
17
  token_pilot_body_hash: 960fe9e907e9c7d13b14dcc22af99e8cc7e7335f99791fa808df76ac21e1f5e9
18
18
  ---
19
19
 
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Ecosystem coverage check — detects which complementary tools are
3
+ * installed alongside token-pilot.
4
+ *
5
+ * Scope: only tools whose author has a stable install story and whose
6
+ * problem-space is truly disjoint from ours (we don't recommend things
7
+ * that overlap our own tools/list).
8
+ *
9
+ * Status options:
10
+ * - "installed" — detected on disk, ready to use
11
+ * - "not-installed" — not detected in any known install location
12
+ * - "unknown" — couldn't check (permission denied, unusual OS, etc.)
13
+ *
14
+ * Pure read-only. No network calls. Fast enough to run from `doctor`
15
+ * on every invocation.
16
+ */
17
+ export type EcosystemToolId = "caveman" | "cavemem" | "context-mode";
18
+ export interface EcosystemToolStatus {
19
+ id: EcosystemToolId;
20
+ name: string;
21
+ role: string;
22
+ status: "installed" | "not-installed" | "unknown";
23
+ detectedAt: string | null;
24
+ installHint: string;
25
+ repo: string;
26
+ }
27
+ /**
28
+ * Run all ecosystem checks. Order is deterministic — the checks are cheap
29
+ * and we want the doctor output to be stable across runs.
30
+ */
31
+ export declare function checkEcosystem(): EcosystemToolStatus[];
32
+ /**
33
+ * Render a block suitable for appending to `token-pilot doctor` output.
34
+ * Returns null when every tool is installed — no point printing a
35
+ * noisy "all green" block when the user has nothing to act on.
36
+ */
37
+ export declare function formatEcosystemBlock(statuses: EcosystemToolStatus[]): string | null;
38
+ export type StatuslineStatus = "configured-chain" | "configured-tp-only" | "configured-caveman-only" | "configured-other" | "not-configured" | "unknown";
39
+ export interface StatuslineCheckResult {
40
+ status: StatuslineStatus;
41
+ configPath: string;
42
+ currentCommand: string | null;
43
+ }
44
+ /**
45
+ * Parse `~/.claude/settings.json` and report whether a statusline is
46
+ * wired to token-pilot's own script. Used by `doctor` to nudge toward
47
+ * the chain wrapper when appropriate. Never throws.
48
+ */
49
+ export declare function checkStatusline(): StatuslineCheckResult;
50
+ /**
51
+ * Render a doctor hint for the statusline badge. Returns null when the
52
+ * user either already has the best config (chain) or has a custom
53
+ * statusLine we don't want to touch.
54
+ */
55
+ export declare function formatStatuslineHint(result: StatuslineCheckResult, ecosystemStatuses: EcosystemToolStatus[]): string | null;
56
+ //# sourceMappingURL=ecosystem-check.d.ts.map
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Ecosystem coverage check — detects which complementary tools are
3
+ * installed alongside token-pilot.
4
+ *
5
+ * Scope: only tools whose author has a stable install story and whose
6
+ * problem-space is truly disjoint from ours (we don't recommend things
7
+ * that overlap our own tools/list).
8
+ *
9
+ * Status options:
10
+ * - "installed" — detected on disk, ready to use
11
+ * - "not-installed" — not detected in any known install location
12
+ * - "unknown" — couldn't check (permission denied, unusual OS, etc.)
13
+ *
14
+ * Pure read-only. No network calls. Fast enough to run from `doctor`
15
+ * on every invocation.
16
+ */
17
+ import { existsSync, readFileSync } from "node:fs";
18
+ import { homedir } from "node:os";
19
+ import { join } from "node:path";
20
+ /**
21
+ * Conventional Claude Code plugin cache for a given plugin name.
22
+ * Matches the pattern token-pilot itself lives under.
23
+ */
24
+ function claudePluginCacheDir(plugin) {
25
+ return join(homedir(), ".claude", "plugins", "cache", plugin);
26
+ }
27
+ /**
28
+ * Conventional Gemini CLI extensions dir for a given extension name.
29
+ */
30
+ function geminiExtensionDir(ext) {
31
+ return join(homedir(), ".gemini", "extensions", ext);
32
+ }
33
+ /**
34
+ * Probe a list of candidate paths; return the first that exists.
35
+ * Any FS error → return null (caller reports "unknown").
36
+ */
37
+ function firstExisting(paths) {
38
+ for (const p of paths) {
39
+ try {
40
+ if (existsSync(p))
41
+ return p;
42
+ }
43
+ catch {
44
+ // Permission denied or similar — move on, the caller decides how
45
+ // to report this. We don't want a doctor run to crash because one
46
+ // probe couldn't stat a path.
47
+ continue;
48
+ }
49
+ }
50
+ return null;
51
+ }
52
+ function checkCaveman() {
53
+ const candidates = [
54
+ claudePluginCacheDir("caveman"),
55
+ geminiExtensionDir("caveman"),
56
+ // Codex + Cursor install into project-local dirs — out of scope for a
57
+ // doctor run whose cwd is the user's code. If the user ran caveman
58
+ // through `npx skills`, the marker is usually `.claude/skills/caveman`
59
+ // or `.cursor/rules/caveman.mdc` — also project-local, same reason.
60
+ ];
61
+ const detected = firstExisting(candidates);
62
+ return {
63
+ id: "caveman",
64
+ name: "caveman",
65
+ role: "Output compression (terse-speak skill) — cuts ~75% of Claude's response prose",
66
+ status: detected ? "installed" : "not-installed",
67
+ detectedAt: detected,
68
+ installHint: "claude plugin marketplace add JuliusBrussee/caveman && claude plugin install caveman@caveman",
69
+ repo: "https://github.com/JuliusBrussee/caveman",
70
+ };
71
+ }
72
+ function checkCavemem() {
73
+ const candidates = [
74
+ claudePluginCacheDir("cavemem"),
75
+ geminiExtensionDir("cavemem"),
76
+ ];
77
+ const detected = firstExisting(candidates);
78
+ return {
79
+ id: "cavemem",
80
+ name: "cavemem",
81
+ role: "Cross-session memory — remember context across restarts",
82
+ status: detected ? "installed" : "not-installed",
83
+ detectedAt: detected,
84
+ installHint: "see https://github.com/JuliusBrussee/cavemem",
85
+ repo: "https://github.com/JuliusBrussee/cavemem",
86
+ };
87
+ }
88
+ function checkContextMode() {
89
+ const candidates = [
90
+ claudePluginCacheDir("context-mode"),
91
+ claudePluginCacheDir("claude-context-mode"),
92
+ ];
93
+ const detected = firstExisting(candidates);
94
+ return {
95
+ id: "context-mode",
96
+ name: "context-mode",
97
+ role: "Sandbox executor — runs shell/python/js, only stdout enters context",
98
+ status: detected ? "installed" : "not-installed",
99
+ detectedAt: detected,
100
+ installHint: "see https://github.com/mksglu/claude-context-mode",
101
+ repo: "https://github.com/mksglu/claude-context-mode",
102
+ };
103
+ }
104
+ /**
105
+ * Run all ecosystem checks. Order is deterministic — the checks are cheap
106
+ * and we want the doctor output to be stable across runs.
107
+ */
108
+ export function checkEcosystem() {
109
+ return [checkCaveman(), checkContextMode(), checkCavemem()];
110
+ }
111
+ /**
112
+ * Render a block suitable for appending to `token-pilot doctor` output.
113
+ * Returns null when every tool is installed — no point printing a
114
+ * noisy "all green" block when the user has nothing to act on.
115
+ */
116
+ export function formatEcosystemBlock(statuses) {
117
+ const missing = statuses.filter((s) => s.status === "not-installed");
118
+ const installed = statuses.filter((s) => s.status === "installed");
119
+ // When everything is green we stay silent — the doctor surface is busy
120
+ // enough. Users can still get the full map from `docs/ecosystem.md`.
121
+ if (missing.length === 0 && installed.length === 0)
122
+ return null;
123
+ const lines = ["── ecosystem coverage ──"];
124
+ if (installed.length > 0) {
125
+ for (const s of installed) {
126
+ lines.push(` ✓ ${s.name.padEnd(14)} ${s.role}`);
127
+ }
128
+ }
129
+ for (const s of missing) {
130
+ lines.push(` ○ ${s.name.padEnd(14)} missing — ${s.role}`);
131
+ lines.push(` install: ${s.installHint}`);
132
+ }
133
+ if (missing.length > 0) {
134
+ lines.push("");
135
+ lines.push(` token-pilot owns INPUT tokens. Each tool above owns a different`);
136
+ lines.push(` half of a session — they do not overlap. See docs/ecosystem.md.`);
137
+ }
138
+ return lines.join("\n");
139
+ }
140
+ /**
141
+ * Parse `~/.claude/settings.json` and report whether a statusline is
142
+ * wired to token-pilot's own script. Used by `doctor` to nudge toward
143
+ * the chain wrapper when appropriate. Never throws.
144
+ */
145
+ export function checkStatusline() {
146
+ const configPath = join(homedir(), ".claude", "settings.json");
147
+ const empty = {
148
+ status: "not-configured",
149
+ configPath,
150
+ currentCommand: null,
151
+ };
152
+ if (!existsSync(configPath))
153
+ return empty;
154
+ let text;
155
+ try {
156
+ text = readFileSync(configPath, "utf-8");
157
+ }
158
+ catch {
159
+ return { status: "unknown", configPath, currentCommand: null };
160
+ }
161
+ let parsed;
162
+ try {
163
+ parsed = JSON.parse(text);
164
+ }
165
+ catch {
166
+ return { status: "unknown", configPath, currentCommand: null };
167
+ }
168
+ const sl = parsed
169
+ ?.statusLine;
170
+ if (!sl || typeof sl.command !== "string")
171
+ return empty;
172
+ const cmd = sl.command;
173
+ if (cmd.includes("statusline-chain.sh")) {
174
+ return { status: "configured-chain", configPath, currentCommand: cmd };
175
+ }
176
+ if (cmd.includes("tp-statusline.sh")) {
177
+ return { status: "configured-tp-only", configPath, currentCommand: cmd };
178
+ }
179
+ if (cmd.includes("caveman-statusline.sh")) {
180
+ return {
181
+ status: "configured-caveman-only",
182
+ configPath,
183
+ currentCommand: cmd,
184
+ };
185
+ }
186
+ return { status: "configured-other", configPath, currentCommand: cmd };
187
+ }
188
+ /**
189
+ * Render a doctor hint for the statusline badge. Returns null when the
190
+ * user either already has the best config (chain) or has a custom
191
+ * statusLine we don't want to touch.
192
+ */
193
+ export function formatStatuslineHint(result, ecosystemStatuses) {
194
+ const lines = ["── statusline badge ──"];
195
+ const hasCaveman = ecosystemStatuses.some((s) => s.id === "caveman" && s.status === "installed");
196
+ const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
197
+ switch (result.status) {
198
+ case "configured-chain":
199
+ // User is already on the best config — stay silent.
200
+ return null;
201
+ case "configured-tp-only":
202
+ if (!hasCaveman)
203
+ return null;
204
+ lines.push(` ⚠ statusline points at tp-statusline.sh directly but caveman is`);
205
+ lines.push(` installed — switch to statusline-chain.sh so both badges show.`);
206
+ if (pluginRoot) {
207
+ lines.push(` command: bash "${pluginRoot}/hooks/statusline-chain.sh"`);
208
+ }
209
+ return lines.join("\n");
210
+ case "configured-caveman-only":
211
+ // Caveman's own statusline is already live. Suggest swapping to our
212
+ // chain wrapper so the `[TP]` badge joins in side-by-side.
213
+ lines.push(` ⚠ statusline renders caveman's badge only. Switch to token-pilot's`);
214
+ lines.push(` chain wrapper to also show [TP] with enforcement mode + saved tokens.`);
215
+ if (pluginRoot) {
216
+ lines.push(` command: bash "${pluginRoot}/hooks/statusline-chain.sh"`);
217
+ }
218
+ else {
219
+ lines.push(` command: bash "$(ls -t ~/.claude/plugins/cache/token-pilot/token-pilot/*/hooks/statusline-chain.sh 2>/dev/null | head -1)"`);
220
+ }
221
+ return lines.join("\n");
222
+ case "configured-other":
223
+ // Custom statusLine — respect it, never overwrite.
224
+ return null;
225
+ case "unknown":
226
+ return null;
227
+ case "not-configured": {
228
+ lines.push(` ○ no statusline badge configured — add one to see token-pilot`);
229
+ lines.push(` state (enforcement mode + cumulative saved tokens) in`);
230
+ lines.push(` Claude Code's status bar.`);
231
+ lines.push("");
232
+ const command = pluginRoot
233
+ ? `bash "${pluginRoot}/hooks/statusline-chain.sh"`
234
+ : `bash "$(ls -t ~/.claude/plugins/cache/token-pilot/token-pilot/*/hooks/statusline-chain.sh 2>/dev/null | head -1)"`;
235
+ lines.push(` Add to ${result.configPath}:`);
236
+ lines.push(` "statusLine": {`);
237
+ lines.push(` "type": "command",`);
238
+ lines.push(` "command": "${command.replace(/"/g, '\\"')}"`);
239
+ lines.push(` }`);
240
+ return lines.join("\n");
241
+ }
242
+ }
243
+ }
244
+ //# sourceMappingURL=ecosystem-check.js.map
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Ecosystem reminder — a one-shot nudge at MCP-server startup suggesting
3
+ * the user install caveman for output-side compression.
4
+ *
5
+ * Design lifted from `maybeEmitStartupReminder` (tp-* agents):
6
+ * - at most once per process (new Claude Code session = new process =
7
+ * new single reminder, not a per-session banner every turn);
8
+ * - silent when the user already installed caveman;
9
+ * - silenced completely by `TOKEN_PILOT_NO_ECOSYSTEM_TIPS=1`;
10
+ * - silenced inside spawned subagents (`TOKEN_PILOT_SUBAGENT=1`) so
11
+ * the banner never surfaces through Task-dispatched helpers.
12
+ *
13
+ * Three stderr lines is the max we allow — the whole point is to be
14
+ * helpful without being the thing that bloats the user's first impression.
15
+ */
16
+ export interface EcosystemReminderOptions {
17
+ env?: NodeJS.ProcessEnv;
18
+ }
19
+ /**
20
+ * Pure predicate — is a reminder currently warranted?
21
+ * Exposed separately from the stateful emitter so tests can exercise the
22
+ * decision matrix without touching stderr or the single-fire latch.
23
+ */
24
+ export declare function shouldEmitEcosystemReminder(opts?: EcosystemReminderOptions): boolean;
25
+ /**
26
+ * Emit the reminder to stderr if conditions warrant it. Single-fire per
27
+ * process. Returns `true` iff it actually wrote output, so callers can
28
+ * log telemetry without racing the latch.
29
+ */
30
+ export declare function maybeEmitEcosystemReminder(opts?: EcosystemReminderOptions): boolean;
31
+ /** Test-only: reset the single-fire guard between test cases. */
32
+ export declare function __resetEcosystemReminder(): void;
33
+ /** Test-only: expose the canonical message for assertion. */
34
+ export declare const __ECOSYSTEM_REMINDER_MESSAGE: string;
35
+ //# sourceMappingURL=ecosystem-reminder.d.ts.map
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Ecosystem reminder — a one-shot nudge at MCP-server startup suggesting
3
+ * the user install caveman for output-side compression.
4
+ *
5
+ * Design lifted from `maybeEmitStartupReminder` (tp-* agents):
6
+ * - at most once per process (new Claude Code session = new process =
7
+ * new single reminder, not a per-session banner every turn);
8
+ * - silent when the user already installed caveman;
9
+ * - silenced completely by `TOKEN_PILOT_NO_ECOSYSTEM_TIPS=1`;
10
+ * - silenced inside spawned subagents (`TOKEN_PILOT_SUBAGENT=1`) so
11
+ * the banner never surfaces through Task-dispatched helpers.
12
+ *
13
+ * Three stderr lines is the max we allow — the whole point is to be
14
+ * helpful without being the thing that bloats the user's first impression.
15
+ */
16
+ import { checkEcosystem } from "./ecosystem-check.js";
17
+ let emitted = false;
18
+ const MESSAGE = "[token-pilot] Tip: pair with caveman for ~75% output token savings\n" +
19
+ " install: claude plugin install caveman@caveman\n" +
20
+ " silence: set TOKEN_PILOT_NO_ECOSYSTEM_TIPS=1\n";
21
+ /**
22
+ * Pure predicate — is a reminder currently warranted?
23
+ * Exposed separately from the stateful emitter so tests can exercise the
24
+ * decision matrix without touching stderr or the single-fire latch.
25
+ */
26
+ export function shouldEmitEcosystemReminder(opts = {}) {
27
+ const env = opts.env ?? process.env;
28
+ if (env.TOKEN_PILOT_NO_ECOSYSTEM_TIPS === "1")
29
+ return false;
30
+ if (env.TOKEN_PILOT_SUBAGENT === "1")
31
+ return false;
32
+ const statuses = checkEcosystem();
33
+ const caveman = statuses.find((s) => s.id === "caveman");
34
+ // Only remind when we're actually sure caveman is missing. If detection
35
+ // is "unknown" (future value, permission-denied HOME, etc.) we stay
36
+ // silent — a wrong nudge is worse than no nudge.
37
+ return caveman?.status === "not-installed";
38
+ }
39
+ /**
40
+ * Emit the reminder to stderr if conditions warrant it. Single-fire per
41
+ * process. Returns `true` iff it actually wrote output, so callers can
42
+ * log telemetry without racing the latch.
43
+ */
44
+ export function maybeEmitEcosystemReminder(opts = {}) {
45
+ if (emitted)
46
+ return false;
47
+ if (!shouldEmitEcosystemReminder(opts))
48
+ return false;
49
+ process.stderr.write(MESSAGE);
50
+ emitted = true;
51
+ return true;
52
+ }
53
+ /** Test-only: reset the single-fire guard between test cases. */
54
+ export function __resetEcosystemReminder() {
55
+ emitted = false;
56
+ }
57
+ /** Test-only: expose the canonical message for assertion. */
58
+ export const __ECOSYSTEM_REMINDER_MESSAGE = MESSAGE;
59
+ //# sourceMappingURL=ecosystem-reminder.js.map
@@ -43,15 +43,6 @@ function createHookConfig(options) {
43
43
  },
44
44
  ],
45
45
  },
46
- {
47
- matcher: "Write",
48
- hooks: [
49
- {
50
- type: "command",
51
- command: buildHookCommand("hook-edit", options),
52
- },
53
- ],
54
- },
55
46
  {
56
47
  matcher: "Bash",
57
48
  hooks: [
@@ -30,7 +30,13 @@
30
30
  */
31
31
  export function decidePreEdit(input, ctx) {
32
32
  const toolName = input.tool_name ?? "";
33
- if (toolName !== "Edit" && toolName !== "MultiEdit" && toolName !== "Write") {
33
+ // Only Edit and MultiEdit touch a file partially with an old_string that
34
+ // MUST match disk byte-for-byte — those are the calls read_for_edit
35
+ // actually prepares. Write replaces the whole file (new content, no
36
+ // old_string) so enforcing prep on Write is overreach: blocks legit
37
+ // script regeneration / template overwrites that never needed prep.
38
+ // Pre-v0.30.3 we blocked Write too; that was wrong. Rolled back.
39
+ if (toolName !== "Edit" && toolName !== "MultiEdit") {
34
40
  return { kind: "allow" };
35
41
  }
36
42
  const filePath = input.tool_input?.file_path;
@@ -41,10 +47,8 @@ export function decidePreEdit(input, ctx) {
41
47
  // carry the same value, skip enforcement.
42
48
  if (!ctx.isCodeFile)
43
49
  return { kind: "allow" };
44
- // Non-existent files are out of scope for the enforcement:
45
- // - Write on a new file is legitimate new-file creation
46
- // - Edit / MultiEdit on a missing path will error downstream in
47
- // Claude Code itself — nothing for us to add there
50
+ // Non-existent files Edit/MultiEdit will error downstream in Claude Code
51
+ // itself. Nothing for us to add.
48
52
  if (!ctx.fileExists)
49
53
  return { kind: "allow" };
50
54
  // Explicit escape hatch. Documented as TOKEN_PILOT_BYPASS=1.
package/dist/index.js CHANGED
@@ -54,6 +54,7 @@ import { decidePreBash, renderPreBashOutput } from "./hooks/pre-bash.js";
54
54
  import { decidePreGrep, renderPreGrepOutput } from "./hooks/pre-grep.js";
55
55
  import { decidePreEdit, renderPreEditOutput, } from "./hooks/pre-edit.js";
56
56
  import { isEditPrepared as isEditPreparedFn } from "./core/edit-prep-state.js";
57
+ import { maybeEmitEcosystemReminder } from "./cli/ecosystem-reminder.js";
57
58
  import { parseEnforcementMode } from "./server/enforcement-mode.js";
58
59
  const execFileAsync = promisify(execFile);
59
60
  export const CODE_EXTENSIONS = new Set([
@@ -390,6 +391,16 @@ export async function startServer(cliArgs = process.argv.slice(2)) {
390
391
  }).catch(() => {
391
392
  /* ignore */
392
393
  });
394
+ // v0.30.x ecosystem nudge — one-time stderr tip suggesting caveman for
395
+ // output compression. Fires at most once per MCP process, stays silent
396
+ // when caveman is already detected or TOKEN_PILOT_NO_ECOSYSTEM_TIPS=1.
397
+ // Static import + synchronous call so nothing ever blocks startServer.
398
+ try {
399
+ maybeEmitEcosystemReminder();
400
+ }
401
+ catch {
402
+ /* ecosystem reminder must never block startup */
403
+ }
393
404
  // Phase 6 subtask 6.2 — age + size retention on hook-events archives.
394
405
  // Fire-and-forget: retention failures must never block startup.
395
406
  applyRetention(projectRoot).catch(() => {
@@ -864,6 +875,29 @@ export async function handleDoctor() {
864
875
  catch {
865
876
  /* ignore */
866
877
  }
878
+ // ── Ecosystem coverage ──
879
+ // Checks which complementary tools (caveman, context-mode, cavemem) are
880
+ // installed and prints gaps. Purely advisory — we never install anything.
881
+ // See docs/ecosystem.md for the rationale behind the whitelist.
882
+ try {
883
+ const { checkEcosystem, formatEcosystemBlock, checkStatusline, formatStatuslineHint, } = await import("./cli/ecosystem-check.js");
884
+ const ecosystemStatuses = checkEcosystem();
885
+ const block = formatEcosystemBlock(ecosystemStatuses);
886
+ if (block) {
887
+ console.log(block);
888
+ console.log("");
889
+ }
890
+ // Statusline hint — only prints when actionable (missing, or mid-upgrade
891
+ // when caveman is present but chain wrapper isn't used).
892
+ const statuslineHint = formatStatuslineHint(checkStatusline(), ecosystemStatuses);
893
+ if (statuslineHint) {
894
+ console.log(statuslineHint);
895
+ console.log("");
896
+ }
897
+ }
898
+ catch {
899
+ /* ecosystem check is best-effort; never break doctor */
900
+ }
867
901
  process.exit(0);
868
902
  }
869
903
  export async function handleInit(targetDir) {
@@ -62,6 +62,57 @@ Handlers remain active regardless of profile — a subagent that explicitly name
62
62
  TOKEN_PILOT_PROFILE=edit npx token-pilot
63
63
  ```
64
64
 
65
+ ## Startup reminders
66
+
67
+ Token Pilot prints at most one short tip to **stderr** per MCP process when it detects a gap worth your attention. Reminders are single-fire per session and each is silenced by a dedicated env var:
68
+
69
+ | Reminder | Condition | Silence with |
70
+ |---------|-----------|--------------|
71
+ | `tp-*` subagents not installed | `npx token-pilot install-agents` was never run | `TOKEN_PILOT_NO_AGENT_REMINDER=1` |
72
+ | caveman not installed | caveman skill missing from `~/.claude/plugins/cache/` and `~/.gemini/extensions/` | `TOKEN_PILOT_NO_ECOSYSTEM_TIPS=1` |
73
+
74
+ Both reminders stay silent inside spawned subagents (`TOKEN_PILOT_SUBAGENT=1`) — noise never leaks into Task-dispatched helpers.
75
+
76
+ Run `npx token-pilot doctor` any time to see the full ecosystem coverage table. The reminder is only a nudge; the doctor output is the canonical view.
77
+
78
+ ## Statusline badge
79
+
80
+ Claude Code supports a custom `statusLine` command that renders on every keystroke in the bottom bar. Token Pilot ships two scripts for this:
81
+
82
+ | Script | What it shows |
83
+ |--------|---------------|
84
+ | `hooks/tp-statusline.sh` | `[TP]` · `[TP:strict]` · `[TP deny 12k]` (with cumulative saved tokens for the current session) |
85
+ | `hooks/statusline-chain.sh` | Same, **plus** caveman's badge side-by-side if installed: `[CAVEMAN] [TP deny 12k]` |
86
+
87
+ Both scripts are hardened (bounded stdin read, whitelist sanitisation, no symlinks). Safe to keep enabled long-term.
88
+
89
+ ### Install (manual, one-time)
90
+
91
+ Add to `~/.claude/settings.json`:
92
+
93
+ ```json
94
+ "statusLine": {
95
+ "type": "command",
96
+ "command": "bash \"$(ls -t ~/.claude/plugins/cache/token-pilot/token-pilot/*/hooks/statusline-chain.sh 2>/dev/null | head -1)\""
97
+ }
98
+ ```
99
+
100
+ Restart Claude Code. `[TP]` appears in the status bar immediately. When caveman is also installed you'll see both badges.
101
+
102
+ ### Other machines
103
+
104
+ Same two-line recipe, or run the Python one-liner below if you're wary of editing JSON by hand:
105
+
106
+ ```bash
107
+ python3 -c "import json,os; p=os.path.expanduser('~/.claude/settings.json'); d=json.load(open(p)); d['statusLine']={'type':'command','command':'bash \"\$(ls -t ~/.claude/plugins/cache/token-pilot/token-pilot/*/hooks/statusline-chain.sh 2>/dev/null | head -1)\"'}; json.dump(d,open(p,'w'),indent=2)"
108
+ ```
109
+
110
+ ### Troubleshooting
111
+
112
+ - `npx token-pilot doctor` surfaces a dedicated **statusline badge** block when the config is missing or when it points at `tp-statusline.sh` directly (we nudge you to the chain wrapper once caveman is detected).
113
+ - If you have a custom `statusLine` already, token-pilot respects it — no override.
114
+ - Colours: `[TP]` is blue (`38;5;39`), caveman's `[CAVEMAN]` is orange (`38;5;172`) — deliberately distinct.
115
+
65
116
  ## CLI Reference
66
117
 
67
118
  ```bash
@@ -0,0 +1,83 @@
1
+ # Context-Efficient AI Coding — Ecosystem Map
2
+
3
+ Token Pilot solves **one half** of the context problem: the tokens that come *into* Claude's context from reading code. A full coding session has at least four independent places where tokens pile up — the tools below each own one of them.
4
+
5
+ Use them together. They compose cleanly and do not overlap.
6
+
7
+ ## Where your tokens actually go
8
+
9
+ ```
10
+ ┌──────────────────────────────────────────────────────────────────┐
11
+ │ A COMPLETE CODING SESSION │
12
+ │ │
13
+ │ INPUT side OUTPUT side │
14
+ │ ────────── ─────────── │
15
+ │ 1. Reading code files 4. Claude's response prose │
16
+ │ 2. git diff / log 5. Chain-of-thought noise │
17
+ │ 3. Running shell commands │
18
+ │ 4. Keeping context across sessions │
19
+ └──────────────────────────────────────────────────────────────────┘
20
+ ```
21
+
22
+ ## The stack
23
+
24
+ | Tool | Owns | Savings | Mechanism |
25
+ |------|------|--------:|-----------|
26
+ | **[token-pilot](https://github.com/Digital-Threads/token-pilot)** | code reads, git, search | **60-90% input** | MCP tools + PreToolUse hooks |
27
+ | **[caveman](https://github.com/JuliusBrussee/caveman)** | Claude's response prose | **~75% output** | System-prompt skill (terse style) |
28
+ | **[ast-index](https://github.com/defendend/Claude-ast-index-search)** | code indexing (underlying) | foundation for structural reads | Native Rust indexer |
29
+ | **[context-mode](https://github.com/mksglu/claude-context-mode)** | shell / python / js execution | **90%+ on big stdout** | Sandbox — only stdout enters context |
30
+ | **[cavemem](https://github.com/JuliusBrussee/cavemem)** | cross-session memory | context across restarts | Persistent structured recall |
31
+
32
+ **Combined footprint:** a session that spends ~70% on reading code, ~25% on shell output, and ~5% on remembered context could see ~85-90% total reduction when the right tool owns each segment.
33
+
34
+ ## What token-pilot does *not* do
35
+
36
+ To keep the boundaries clear:
37
+
38
+ - **token-pilot does not change Claude's response style.** If answers feel long, that's OUTPUT. Install `caveman` for terse-speak.
39
+ - **token-pilot does not execute code.** If `npm test` or long `python` output floods your context, install `context-mode`.
40
+ - **token-pilot does not remember across sessions.** If you're re-explaining context every morning, install `cavemem`.
41
+ - **token-pilot does not index source.** It *uses* ast-index under the hood — installed automatically, but also standalone.
42
+
43
+ ## Installing the full stack
44
+
45
+ Each tool is independent — install whatever you need.
46
+
47
+ ```bash
48
+ # Claude Code (plugin system)
49
+ claude plugin marketplace add Digital-Threads/token-pilot
50
+ claude plugin install token-pilot@token-pilot
51
+
52
+ claude plugin marketplace add JuliusBrussee/caveman
53
+ claude plugin install caveman@caveman
54
+
55
+ # token-pilot bootstraps ast-index automatically.
56
+ # context-mode can be installed via its own plugin route.
57
+ ```
58
+
59
+ For other clients (Cursor, Codex, Cline, Windsurf, …) each tool has its own install matrix — follow each project's README.
60
+
61
+ ## Why not one meta-plugin?
62
+
63
+ We considered shipping `ai-coding-savings-pack` that bundles all of them. Tradeoffs:
64
+
65
+ - **Pro:** one-command install.
66
+ - **Con:** blast radius. If any component ships a regression, the whole pack looks broken. Each tool has its own release cadence and support surface; coupling them hides those boundaries.
67
+
68
+ For now the recommendation is *install what you need, individually*. Revisit bundling after real combined-usage data shows it pays off.
69
+
70
+ ## Measuring the combined effect
71
+
72
+ Each tool ships its own telemetry, read as-is:
73
+
74
+ ```bash
75
+ npx token-pilot tool-audit # input savings (per tool, cumulative)
76
+ npx token-pilot doctor # ecosystem coverage check — see what's installed
77
+ ```
78
+
79
+ The `doctor` command checks which ecosystem tools are active in the current environment and prints gaps. That's the cheapest way to see "am I leaving savings on the table?"
80
+
81
+ ## Credits
82
+
83
+ Everything listed here is MIT or Apache-licensed open source maintained by small teams. If you use them, please star the repos — all of this is volunteer work.
package/hooks/hooks.json CHANGED
@@ -28,15 +28,6 @@
28
28
  }
29
29
  ]
30
30
  },
31
- {
32
- "matcher": "Write",
33
- "hooks": [
34
- {
35
- "type": "command",
36
- "command": "node ${CLAUDE_PLUGIN_ROOT}/dist/index.js hook-edit"
37
- }
38
- ]
39
- },
40
31
  {
41
32
  "matcher": "Bash",
42
33
  "hooks": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-pilot",
3
- "version": "0.30.1",
3
+ "version": "0.30.3",
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",