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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +44 -3
- package/agents/tp-api-surface-tracker.md +1 -1
- package/agents/tp-audit-scanner.md +1 -1
- package/agents/tp-commit-writer.md +1 -1
- package/agents/tp-context-engineer.md +1 -1
- package/agents/tp-dead-code-finder.md +1 -1
- package/agents/tp-debugger.md +1 -1
- package/agents/tp-dep-health.md +1 -1
- package/agents/tp-doc-writer.md +1 -1
- package/agents/tp-history-explorer.md +1 -1
- package/agents/tp-impact-analyzer.md +1 -1
- package/agents/tp-incident-timeline.md +1 -1
- package/agents/tp-incremental-builder.md +1 -1
- package/agents/tp-migration-scout.md +1 -1
- package/agents/tp-onboard.md +1 -1
- package/agents/tp-performance-profiler.md +1 -1
- package/agents/tp-pr-reviewer.md +1 -1
- package/agents/tp-refactor-planner.md +1 -1
- package/agents/tp-review-impact.md +1 -1
- package/agents/tp-run.md +1 -1
- package/agents/tp-session-restorer.md +1 -1
- package/agents/tp-ship-coordinator.md +1 -1
- package/agents/tp-spec-writer.md +1 -1
- package/agents/tp-test-coverage-gapper.md +1 -1
- package/agents/tp-test-triage.md +1 -1
- package/agents/tp-test-writer.md +1 -1
- package/dist/cli/typo-guard.d.ts +1 -1
- package/dist/cli/typo-guard.js +2 -0
- package/dist/core/validation.d.ts +17 -0
- package/dist/core/validation.js +42 -0
- package/dist/hooks/installer.js +8 -0
- package/dist/hooks/user-prompt.d.ts +30 -0
- package/dist/hooks/user-prompt.js +44 -0
- package/dist/index.js +36 -2
- package/dist/server.js +4 -2
- package/hooks/hooks.json +10 -0
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
12
|
+
token_pilot_version: "0.46.0"
|
|
13
13
|
token_pilot_body_hash: dd184501203fa7f3c73f419c4ffbe33c4be75400cb64a7a51733a3fe23f6e085
|
|
14
14
|
requiredMcpServers:
|
|
15
15
|
- "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.
|
|
11
|
+
token_pilot_version: "0.46.0"
|
|
12
12
|
token_pilot_body_hash: de64a406b5176de19f7422619c7de7949b1f28865f225402c9cea9255f377428
|
|
13
13
|
requiredMcpServers:
|
|
14
14
|
- "token-pilot"
|
package/agents/tp-debugger.md
CHANGED
package/agents/tp-dep-health.md
CHANGED
package/agents/tp-doc-writer.md
CHANGED
|
@@ -12,7 +12,7 @@ tools:
|
|
|
12
12
|
- mcp__token-pilot__read_symbols
|
|
13
13
|
- Read
|
|
14
14
|
model: sonnet
|
|
15
|
-
token_pilot_version: "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.
|
|
11
|
+
token_pilot_version: "0.46.0"
|
|
12
12
|
token_pilot_body_hash: de5722bfea374eaab096c1ae635c37879e7a91370ee3cd0532f4240be03c91eb
|
|
13
13
|
requiredMcpServers:
|
|
14
14
|
- "token-pilot"
|
package/agents/tp-onboard.md
CHANGED
|
@@ -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.
|
|
13
|
+
token_pilot_version: "0.46.0"
|
|
14
14
|
token_pilot_body_hash: 832e95633fbc8e9b0c10f3e540a327d4be062fb4b3f17a6cce6be13f414e2927
|
|
15
15
|
requiredMcpServers:
|
|
16
16
|
- "token-pilot"
|
package/agents/tp-pr-reviewer.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
package/agents/tp-spec-writer.md
CHANGED
|
@@ -10,7 +10,7 @@ tools:
|
|
|
10
10
|
- mcp__token-pilot__test_summary
|
|
11
11
|
- Glob
|
|
12
12
|
- Grep
|
|
13
|
-
token_pilot_version: "0.
|
|
13
|
+
token_pilot_version: "0.46.0"
|
|
14
14
|
token_pilot_body_hash: be81eed53a3720d146cf89e4a14a7a56577633f7c84c234c412ab70d64c05b11
|
|
15
15
|
requiredMcpServers:
|
|
16
16
|
- "token-pilot"
|
package/agents/tp-test-triage.md
CHANGED
|
@@ -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.
|
|
11
|
+
token_pilot_version: "0.46.0"
|
|
12
12
|
token_pilot_body_hash: 362ecf4cb03b059421ea26933473700900073dc38b3a7fe271208dfb1ae14f90
|
|
13
13
|
requiredMcpServers:
|
|
14
14
|
- "token-pilot"
|
package/agents/tp-test-writer.md
CHANGED
package/dist/cli/typo-guard.d.ts
CHANGED
|
@@ -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;
|
package/dist/cli/typo-guard.js
CHANGED
|
@@ -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
|
package/dist/core/validation.js
CHANGED
|
@@ -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
|
package/dist/hooks/installer.js
CHANGED
|
@@ -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 &&
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "token-pilot",
|
|
3
|
-
"version": "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",
|