token-pilot 0.45.0 → 0.46.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 (39) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +44 -3
  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/typo-guard.d.ts +1 -1
  30. package/dist/cli/typo-guard.js +2 -0
  31. package/dist/core/validation.d.ts +17 -0
  32. package/dist/core/validation.js +42 -0
  33. package/dist/hooks/installer.js +8 -0
  34. package/dist/hooks/user-prompt.d.ts +30 -0
  35. package/dist/hooks/user-prompt.js +44 -0
  36. package/dist/index.js +36 -2
  37. package/dist/server.js +4 -2
  38. package/hooks/hooks.json +10 -0
  39. 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.45.0"
9
+ "version": "0.46.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.45.0",
16
+ "version": "0.46.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.45.0",
3
+ "version": "0.46.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,10 +5,51 @@ 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
8
+ ## [0.46.0] - 2026-06-13
9
+
10
+ ### Added — UserPromptSubmit per-turn reinforcement (caveman-style awareness)
11
+
12
+ The `SessionStart` reminder injects the full mandatory-tool ruleset exactly
13
+ once (start / `/clear` / `/compact`). Over a long conversation that block decays
14
+ out of the model's attention and competing instructions crowd it out, so
15
+ sessions drift back to raw `Read` / `Grep` and stop using token-pilot — even
16
+ with hooks and CLAUDE.md rules in place. The caveman plugin solves the identical
17
+ problem with a `UserPromptSubmit` hook that re-injects a tiny anchor on every
18
+ user message; we now do the same.
19
+
20
+ New `hook-user-prompt` (`UserPromptSubmit`) emits a one-line **minimal anchor**
21
+ (~30 tokens) on every prompt — the floor that keeps token-pilot in the working
22
+ set. Deliberately a single short line: the heavy full ruleset stays in
23
+ `SessionStart`, so this per-turn channel never undercuts the tool's own token
24
+ budget (no event-log reads, no per-turn growth).
25
+
26
+ `additionalContext` only — never blocks the prompt. Safe-runner wrapped (always
27
+ exits 0). Respects `sessionStart.enabled`, `TOKEN_PILOT_BYPASS=1`, and a
28
+ dedicated `TOKEN_PILOT_PROMPT_REMINDER=0` opt-out. Wired into `hooks/hooks.json`,
29
+ the `install-hook` installer, and the typo-guard command allowlist.
30
+
31
+ ## [0.45.1] - 2026-06-11
32
+
33
+ ### Fixed — refuse a multi-repo workspace parent (cross-project index bleed)
34
+
35
+ `start.sh` always passes an explicit project root (`${CLAUDE_PROJECT_DIR:-$USER_CWD}`)
36
+ to the server, so `startServer`'s git-root **narrowing only runs in the
37
+ `!explicitRoot` branch — which is never taken**. When the session is launched
38
+ from a non-git workspace parent that nests several project repos (e.g.
39
+ `/work/loom` holding `token-pilot`, `loom-host`, `aimux`, …), the raw parent was
40
+ used verbatim and ast-index indexed **every** sibling into one index. Symbol
41
+ lookups then bled across projects — `find_usages` / `read_symbol` returning
42
+ matches from the wrong repo, or `symbol not found`. `isDangerousRoot` only
43
+ caught system/home dirs, so the parent slipped through.
44
+
45
+ New guard `isMultiRepoParent(root)` (in `core/validation.ts`) detects a non-git
46
+ directory with ≥2 immediate child git repos. When the resolved root matches,
47
+ ast-index is disabled (`skipAstIndex`) and a warning tells the user to set
48
+ `CLAUDE_PROJECT_DIR` to the specific project — fail safe instead of bleeding.
49
+ Wired into `startServer` and the `server.ts` MCP-roots auto-detect. Single-repo
50
+ roots, monorepos, and roots that are themselves a git repo are unaffected.
51
+
9
52
 
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
53
 
13
54
  ### Changed — default tool profile is now `full` (adoption fix)
14
55
 
@@ -9,7 +9,7 @@ tools:
9
9
  - mcp__token-pilot__read_symbol
10
10
  - Bash
11
11
  model: haiku
12
- token_pilot_version: "0.45.0"
12
+ token_pilot_version: "0.46.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.45.0"
14
+ token_pilot_version: "0.46.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.45.0"
11
+ token_pilot_version: "0.46.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.45.0"
16
+ token_pilot_version: "0.46.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.45.0"
14
+ token_pilot_version: "0.46.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.45.0"
15
+ token_pilot_version: "0.46.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.45.0"
12
+ token_pilot_version: "0.46.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.45.0"
16
+ token_pilot_version: "0.46.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.45.0"
13
+ token_pilot_version: "0.46.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.45.0"
15
+ token_pilot_version: "0.46.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.45.0"
11
+ token_pilot_version: "0.46.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.45.0"
16
+ token_pilot_version: "0.46.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.45.0"
14
+ token_pilot_version: "0.46.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.45.0"
13
+ token_pilot_version: "0.46.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.45.0"
14
+ token_pilot_version: "0.46.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.45.0"
14
+ token_pilot_version: "0.46.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.45.0"
11
+ token_pilot_version: "0.46.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.45.0"
12
+ token_pilot_version: "0.46.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.45.0"
19
+ token_pilot_version: "0.46.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.45.0"
12
+ token_pilot_version: "0.46.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.45.0"
14
+ token_pilot_version: "0.46.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.45.0"
12
+ token_pilot_version: "0.46.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.45.0"
13
+ token_pilot_version: "0.46.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.45.0"
11
+ token_pilot_version: "0.46.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.45.0"
16
+ token_pilot_version: "0.46.0"
17
17
  token_pilot_body_hash: 269f2fe22ff4517c277d3f56ca67d8a5527b93290ab21079a83ba7af22c1b5a9
18
18
  requiredMcpServers:
19
19
  - "token-pilot"
@@ -17,7 +17,7 @@
17
17
  * Everything else passes through untouched — a real project root like
18
18
  * `/home/user/my-project` or `./subdir` goes to startServer as before.
19
19
  */
20
- export declare const KNOWN_COMMANDS: readonly ["hook-read", "hook-edit", "hook-pre-bash", "hook-pre-grep", "hook-pre-task", "hook-post-bash", "hook-post-task", "hook-session-start", "hook-bootstrap", "hook-subagent-stop", "install-statusline", "install-hook", "uninstall-hook", "install-ast-index", "doctor", "bless-agents", "unbless-agents", "install-agents", "uninstall-agents", "stats", "tool-audit", "save-doc", "list-docs", "init", "migrate-hooks", "errors", "workflow", "--version", "-v", "--help", "-h"];
20
+ export declare const KNOWN_COMMANDS: readonly ["hook-read", "hook-edit", "hook-pre-bash", "hook-pre-grep", "hook-pre-task", "hook-post-bash", "hook-post-task", "hook-session-start", "hook-bootstrap", "hook-subagent-stop", "hook-user-prompt", "install-statusline", "install-hook", "uninstall-hook", "install-ast-index", "doctor", "bless-agents", "unbless-agents", "install-agents", "uninstall-agents", "stats", "tool-audit", "save-doc", "list-docs", "init", "migrate-hooks", "errors", "workflow", "--version", "-v", "--help", "-h"];
21
21
  export interface TypoGuardResult {
22
22
  kind: "pass-through" | "typo";
23
23
  suggestion?: string;
@@ -37,6 +37,8 @@ export const KNOWN_COMMANDS = [
37
37
  "hook-bootstrap",
38
38
  // v0.40.0 — canonical subagent-completion capture
39
39
  "hook-subagent-stop",
40
+ // UserPromptSubmit per-turn reinforcement (caveman-style awareness)
41
+ "hook-user-prompt",
40
42
  // v0.42.0 — one-command statusline badge installer
41
43
  "install-statusline",
42
44
  "install-hook",
@@ -197,4 +197,21 @@ export declare function validateReadSectionArgs(args: unknown): {
197
197
  };
198
198
  /** Detect roots that would cause ast-index to scan the entire filesystem */
199
199
  export declare function isDangerousRoot(root: string): boolean;
200
+ /**
201
+ * Detect a non-git workspace parent that nests multiple sibling git
202
+ * repos. Handing such a directory to ast-index would index every
203
+ * sibling into one index, bleeding symbols across unrelated projects
204
+ * (find_usages / read_symbol returning matches from the wrong repo).
205
+ *
206
+ * Returns true only when BOTH hold:
207
+ * - `root` itself is NOT a git repo (no `.git` entry), AND
208
+ * - `root` has >= 2 immediate child directories that each contain a
209
+ * `.git` entry — a directory for a normal repo, a file for a
210
+ * submodule / worktree.
211
+ *
212
+ * Fail-open: a missing path, an unreadable directory, a single child
213
+ * repo, or a root that is itself a repo all return false, so legitimate
214
+ * single-project and monorepo layouts are never disabled.
215
+ */
216
+ export declare function isMultiRepoParent(root: string): boolean;
200
217
  //# sourceMappingURL=validation.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { resolve, relative } from "node:path";
2
+ import { existsSync, readdirSync } from "node:fs";
2
3
  /**
3
4
  * v0.33.0 (B9) — coerce an `unknown` argument value to an integer.
4
5
  *
@@ -615,4 +616,45 @@ export function isDangerousRoot(root) {
615
616
  return true;
616
617
  return false;
617
618
  }
619
+ /**
620
+ * Detect a non-git workspace parent that nests multiple sibling git
621
+ * repos. Handing such a directory to ast-index would index every
622
+ * sibling into one index, bleeding symbols across unrelated projects
623
+ * (find_usages / read_symbol returning matches from the wrong repo).
624
+ *
625
+ * Returns true only when BOTH hold:
626
+ * - `root` itself is NOT a git repo (no `.git` entry), AND
627
+ * - `root` has >= 2 immediate child directories that each contain a
628
+ * `.git` entry — a directory for a normal repo, a file for a
629
+ * submodule / worktree.
630
+ *
631
+ * Fail-open: a missing path, an unreadable directory, a single child
632
+ * repo, or a root that is itself a repo all return false, so legitimate
633
+ * single-project and monorepo layouts are never disabled.
634
+ */
635
+ export function isMultiRepoParent(root) {
636
+ if (!root)
637
+ return false;
638
+ let entries;
639
+ try {
640
+ // A root that is itself a git repo is a single project — vendored
641
+ // repos or submodules underneath are intentional, not a parent.
642
+ if (existsSync(resolve(root, ".git")))
643
+ return false;
644
+ entries = readdirSync(root, { withFileTypes: true });
645
+ }
646
+ catch {
647
+ return false;
648
+ }
649
+ let repoChildren = 0;
650
+ for (const entry of entries) {
651
+ if (!entry.isDirectory())
652
+ continue;
653
+ if (existsSync(resolve(root, entry.name, ".git"))) {
654
+ if (++repoChildren >= 2)
655
+ return true;
656
+ }
657
+ }
658
+ return false;
659
+ }
618
660
  //# sourceMappingURL=validation.js.map
@@ -127,6 +127,14 @@ function createHookConfig(options) {
127
127
  hooks: [hookEntry("hook-session-start", options)],
128
128
  },
129
129
  ],
130
+ // Per-turn reinforcement — re-injects a tiny anchor on every user
131
+ // message so the mandatory-tool rules don't decay out of attention
132
+ // over a long session (SessionStart fires only once).
133
+ UserPromptSubmit: [
134
+ {
135
+ hooks: [hookEntry("hook-user-prompt", options)],
136
+ },
137
+ ],
130
138
  PostToolUse: [
131
139
  {
132
140
  matcher: "Bash",
@@ -0,0 +1,30 @@
1
+ /**
2
+ * UserPromptSubmit reminder hook — per-turn reinforcement of the
3
+ * token-pilot mandatory-tool rules.
4
+ *
5
+ * Why this exists: `hook-session-start` injects the full ruleset exactly
6
+ * once (start / `/clear` / `/compact`). Over a long conversation that
7
+ * block decays out of the model's attention and competing instructions
8
+ * crowd it out, so sessions drift back to raw Read / Grep. The caveman
9
+ * plugin solves the identical problem by re-injecting a tiny anchor on
10
+ * EVERY user message; we do the same — one short line per prompt, the
11
+ * full heavy ruleset stays in SessionStart.
12
+ *
13
+ * Contract: emits `additionalContext` only — never blocks the prompt,
14
+ * never throws. Pure module: the builder takes (enabled, bypass) and
15
+ * returns a string or null; the thin `index.ts` case does the IO.
16
+ */
17
+ /**
18
+ * The per-turn anchor. Deliberately one line — re-sending the full
19
+ * mandatory block every turn would burn hundreds of tokens per message
20
+ * inside a tool whose entire point is saving tokens.
21
+ */
22
+ export declare const MINIMAL_ANCHOR: string;
23
+ /**
24
+ * Build the per-turn `additionalContext`, or null when disabled /
25
+ * bypassed (caller emits nothing).
26
+ */
27
+ export declare function buildPromptReminder(enabled: boolean, bypass: boolean): string | null;
28
+ /** Wrap the message in the UserPromptSubmit hook output envelope. */
29
+ export declare function formatPromptReminderOutput(message: string): string;
30
+ //# sourceMappingURL=user-prompt.d.ts.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * UserPromptSubmit reminder hook — per-turn reinforcement of the
3
+ * token-pilot mandatory-tool rules.
4
+ *
5
+ * Why this exists: `hook-session-start` injects the full ruleset exactly
6
+ * once (start / `/clear` / `/compact`). Over a long conversation that
7
+ * block decays out of the model's attention and competing instructions
8
+ * crowd it out, so sessions drift back to raw Read / Grep. The caveman
9
+ * plugin solves the identical problem by re-injecting a tiny anchor on
10
+ * EVERY user message; we do the same — one short line per prompt, the
11
+ * full heavy ruleset stays in SessionStart.
12
+ *
13
+ * Contract: emits `additionalContext` only — never blocks the prompt,
14
+ * never throws. Pure module: the builder takes (enabled, bypass) and
15
+ * returns a string or null; the thin `index.ts` case does the IO.
16
+ */
17
+ /**
18
+ * The per-turn anchor. Deliberately one line — re-sending the full
19
+ * mandatory block every turn would burn hundreds of tokens per message
20
+ * inside a tool whose entire point is saving tokens.
21
+ */
22
+ export const MINIMAL_ANCHOR = "[token-pilot] Before raw Read/Grep/git, use the token-pilot tools: " +
23
+ "smart_read · read_symbol · find_usages · smart_diff / smart_log. " +
24
+ "Delegate scoped work to tp-* specialists. " +
25
+ "Raw Read/Grep only with offset/limit or a narrow regex.";
26
+ /**
27
+ * Build the per-turn `additionalContext`, or null when disabled /
28
+ * bypassed (caller emits nothing).
29
+ */
30
+ export function buildPromptReminder(enabled, bypass) {
31
+ if (!enabled || bypass)
32
+ return null;
33
+ return MINIMAL_ANCHOR;
34
+ }
35
+ /** Wrap the message in the UserPromptSubmit hook output envelope. */
36
+ export function formatPromptReminderOutput(message) {
37
+ return JSON.stringify({
38
+ hookSpecificOutput: {
39
+ hookEventName: "UserPromptSubmit",
40
+ additionalContext: message,
41
+ },
42
+ });
43
+ }
44
+ //# sourceMappingURL=user-prompt.js.map
package/dist/index.js CHANGED
@@ -30,7 +30,7 @@ import { appendDiagnostic } from "./core/event-log.js";
30
30
  import { startWorkflow, endWorkflow, listWorkflows, workflowStatus, formatWorkflowStatus, formatWorkflowList, } from "./core/workflow.js";
31
31
  import { findBinary, installBinary, checkBinaryUpdate, isNewerVersion, } from "./ast-index/binary-manager.js";
32
32
  import { loadConfig } from "./config/loader.js";
33
- import { isDangerousRoot } from "./core/validation.js";
33
+ import { isDangerousRoot, isMultiRepoParent } from "./core/validation.js";
34
34
  import { runSummaryPipeline } from "./hooks/summary-pipeline.js";
35
35
  import { formatDenyMessage } from "./hooks/format-deny-message.js";
36
36
  import { isPathWithinProject } from "./hooks/path-safety.js";
@@ -47,6 +47,7 @@ import { detectDrift, formatDriftFinding } from "./cli/doctor-drift.js";
47
47
  import { handleInstallAgents, maybeEmitStartupReminder, } from "./cli/install-agents.js";
48
48
  import { handleUninstallAgents } from "./cli/uninstall-agents.js";
49
49
  import { appendEvent, applyRetention, } from "./core/event-log.js";
50
+ import { buildPromptReminder, formatPromptReminderOutput, } from "./hooks/user-prompt.js";
50
51
  import { handleStats } from "./cli/stats.js";
51
52
  import { handleToolAudit } from "./cli/tool-audit.js";
52
53
  import { promptYesNo } from "./cli/install-agents.js";
@@ -387,6 +388,27 @@ export async function main(cliArgs = process.argv.slice(2)) {
387
388
  });
388
389
  return;
389
390
  }
391
+ case "hook-user-prompt": {
392
+ // UserPromptSubmit per-turn reinforcement. SessionStart injects the
393
+ // full ruleset once; this re-injects a tiny anchor on every user
394
+ // message so token-pilot stays in the model's working set instead of
395
+ // decaying out of attention (the failure mode caveman fixes the same
396
+ // way). additionalContext only — never blocks the prompt.
397
+ await runHookEntryPoint({ hook: "hook-user-prompt" }, async () => {
398
+ const cfg = await loadConfig(process.cwd());
399
+ // Reuse the awareness toggle — disabling SessionStart reminders
400
+ // disables per-turn too. TOKEN_PILOT_PROMPT_REMINDER=0 turns off
401
+ // just the per-turn channel while keeping SessionStart.
402
+ const enabled = cfg.sessionStart.enabled &&
403
+ process.env.TOKEN_PILOT_PROMPT_REMINDER !== "0";
404
+ const bypass = process.env.TOKEN_PILOT_BYPASS === "1";
405
+ const message = buildPromptReminder(enabled, bypass);
406
+ if (message) {
407
+ process.stdout.write(formatPromptReminderOutput(message));
408
+ }
409
+ });
410
+ return;
411
+ }
390
412
  case "install-hook":
391
413
  await handleInstallHook(cliArgs[1] || process.cwd());
392
414
  return;
@@ -678,6 +700,18 @@ export async function startServer(cliArgs = process.argv.slice(2)) {
678
700
  ` Fix: pass project path explicitly — token-pilot /path/to/project\n` +
679
701
  ` Or configure mcpServers with "args": ["/path/to/project"]`);
680
702
  }
703
+ // Guard: refuse a non-git workspace parent that nests multiple sibling
704
+ // git repos. start.sh always passes an explicit root, so the git-detect
705
+ // narrowing above is skipped; without this guard a parent like
706
+ // `/work/loom` (holding several project repos) would be indexed whole,
707
+ // bleeding symbols across unrelated projects.
708
+ const multiRepoParent = !isDangerousRoot(projectRoot) && isMultiRepoParent(projectRoot);
709
+ if (multiRepoParent) {
710
+ console.error(`[token-pilot] WARNING: project root "${projectRoot}" contains multiple git repos.\n` +
711
+ ` ast-index will be disabled to avoid cross-project index bleed.\n` +
712
+ ` Fix: set CLAUDE_PROJECT_DIR to the specific project, or\n` +
713
+ ` configure mcpServers with "args": ["/path/to/project"].`);
714
+ }
681
715
  // Non-blocking update check for all components (logs to stderr, never blocks startup)
682
716
  const config = await loadConfig(projectRoot);
683
717
  const binaryStatus = await findBinary(config.astIndex.binaryPath);
@@ -731,7 +765,7 @@ export async function startServer(cliArgs = process.argv.slice(2)) {
731
765
  /* ignore — not Claude Code or no .claude dir */
732
766
  });
733
767
  const server = await createServer(projectRoot, {
734
- skipAstIndex: isDangerousRoot(projectRoot),
768
+ skipAstIndex: isDangerousRoot(projectRoot) || multiRepoParent,
735
769
  enforcementMode: parseEnforcementMode(process.env.TOKEN_PILOT_MODE),
736
770
  });
737
771
  const transport = new StdioServerTransport();
package/dist/server.js CHANGED
@@ -14,7 +14,7 @@ import { loadConfig } from "./config/loader.js";
14
14
  import { readFileSync } from "node:fs";
15
15
  import { dirname, resolve } from "node:path";
16
16
  import { execFile } from "node:child_process";
17
- import { isDangerousRoot } from "./core/validation.js";
17
+ import { isDangerousRoot, isMultiRepoParent } from "./core/validation.js";
18
18
  import { promisify } from "node:util";
19
19
  import { GitWatcher } from "./git/watcher.js";
20
20
  const execFilePromise = promisify(execFile);
@@ -151,7 +151,9 @@ export async function createServer(projectRoot, options) {
151
151
  for (const root of roots) {
152
152
  if (root.uri.startsWith("file://")) {
153
153
  const rootPath = decodeURIComponent(new URL(root.uri).pathname);
154
- if (rootPath && !isDangerousRoot(rootPath)) {
154
+ if (rootPath &&
155
+ !isDangerousRoot(rootPath) &&
156
+ !isMultiRepoParent(rootPath)) {
155
157
  await applyDetectedRoot(rootPath, "MCP roots");
156
158
  return;
157
159
  }
package/hooks/hooks.json CHANGED
@@ -76,6 +76,16 @@
76
76
  ]
77
77
  }
78
78
  ],
79
+ "UserPromptSubmit": [
80
+ {
81
+ "hooks": [
82
+ {
83
+ "type": "command",
84
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/dist/index.js hook-user-prompt"
85
+ }
86
+ ]
87
+ }
88
+ ],
79
89
  "PostToolUse": [
80
90
  {
81
91
  "matcher": "Bash",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-pilot",
3
- "version": "0.45.0",
3
+ "version": "0.46.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",