token-pilot 0.28.3 → 0.30.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 +75 -0
- package/README.md +39 -390
- package/agents/tp-api-surface-tracker.md +4 -2
- package/agents/tp-audit-scanner.md +4 -2
- package/agents/tp-commit-writer.md +4 -2
- package/agents/tp-context-engineer.md +4 -2
- package/agents/tp-dead-code-finder.md +4 -2
- package/agents/tp-debugger.md +4 -2
- package/agents/tp-dep-health.md +4 -2
- package/agents/tp-doc-writer.md +4 -2
- package/agents/tp-history-explorer.md +4 -2
- package/agents/tp-impact-analyzer.md +4 -2
- package/agents/tp-incident-timeline.md +4 -2
- package/agents/tp-incremental-builder.md +4 -2
- package/agents/tp-migration-scout.md +4 -2
- package/agents/tp-onboard.md +4 -2
- package/agents/tp-performance-profiler.md +4 -2
- package/agents/tp-pr-reviewer.md +4 -2
- package/agents/tp-refactor-planner.md +4 -2
- package/agents/tp-review-impact.md +4 -2
- package/agents/tp-run.md +4 -2
- package/agents/tp-session-restorer.md +4 -2
- package/agents/tp-ship-coordinator.md +4 -2
- package/agents/tp-spec-writer.md +4 -2
- package/agents/tp-test-coverage-gapper.md +4 -2
- package/agents/tp-test-triage.md +4 -2
- package/agents/tp-test-writer.md +4 -2
- package/dist/cli/tool-audit.d.ts +5 -0
- package/dist/cli/tool-audit.js +9 -1
- package/dist/core/policy-engine.d.ts +1 -5
- package/dist/core/policy-engine.js +9 -24
- package/dist/hooks/pre-bash.d.ts +13 -1
- package/dist/hooks/pre-bash.js +56 -1
- package/dist/hooks/pre-grep.d.ts +2 -1
- package/dist/hooks/pre-grep.js +3 -1
- package/dist/index.js +4 -2
- package/dist/server/enforcement-mode.d.ts +47 -0
- package/dist/server/enforcement-mode.js +59 -0
- package/dist/server/tool-definitions.d.ts +20 -0
- package/dist/server/tool-definitions.js +113 -10
- package/dist/server/tool-profiles.d.ts +19 -1
- package/dist/server/tool-profiles.js +38 -4
- package/dist/server.d.ts +2 -0
- package/dist/server.js +68 -16
- package/docs/agents.md +82 -0
- package/docs/configuration.md +117 -0
- package/docs/hooks.md +99 -0
- package/docs/installation.md +143 -0
- package/docs/tools.md +61 -0
- package/package.json +2 -2
|
@@ -25,6 +25,7 @@ export const PROFILE_NAMES = [
|
|
|
25
25
|
"full",
|
|
26
26
|
"nav",
|
|
27
27
|
"edit",
|
|
28
|
+
"minimal",
|
|
28
29
|
];
|
|
29
30
|
/**
|
|
30
31
|
* Meta-tools — diagnostic / self-observation tools that must be visible
|
|
@@ -37,6 +38,18 @@ export const META_TOOLS = new Set([
|
|
|
37
38
|
"session_budget",
|
|
38
39
|
"session_snapshot",
|
|
39
40
|
]);
|
|
41
|
+
/**
|
|
42
|
+
* Minimal profile — 5 core tools for emergency / context-constrained sessions.
|
|
43
|
+
* Token overhead: tools/list is tiny; instructions are ~80 tokens vs ~350 for full.
|
|
44
|
+
* Use TOKEN_PILOT_PROFILE=minimal when the agent's context budget is nearly full.
|
|
45
|
+
*/
|
|
46
|
+
export const MINIMAL_TOOLS = new Set([
|
|
47
|
+
"smart_read",
|
|
48
|
+
"read_symbol",
|
|
49
|
+
"find_usages",
|
|
50
|
+
"smart_diff",
|
|
51
|
+
"smart_log",
|
|
52
|
+
]);
|
|
40
53
|
/** Minimum nav profile — exploration only, no editing support. */
|
|
41
54
|
export const NAV_TOOLS = new Set([
|
|
42
55
|
"smart_read",
|
|
@@ -49,6 +62,7 @@ export const NAV_TOOLS = new Set([
|
|
|
49
62
|
"explore_area",
|
|
50
63
|
"smart_log",
|
|
51
64
|
"smart_diff",
|
|
65
|
+
"read_section", // v0.30.0: section reading is nav-class (read-only, no edit prep)
|
|
52
66
|
]);
|
|
53
67
|
/** Edit profile adds batch reads + edit-preparation tools. */
|
|
54
68
|
export const EDIT_EXTRAS = new Set([
|
|
@@ -73,6 +87,11 @@ export function filterToolsByProfile(tools, profile) {
|
|
|
73
87
|
// session_snapshot are the instruments for verifying the profile is
|
|
74
88
|
// doing its job. Hiding them would turn "did this save tokens?" into
|
|
75
89
|
// a guess.
|
|
90
|
+
if (profile === "minimal") {
|
|
91
|
+
// Minimal: 5 core tools only. META excluded — keep the footprint tiny.
|
|
92
|
+
// The agent can still call session_analytics by name if it knows it.
|
|
93
|
+
return tools.filter((t) => MINIMAL_TOOLS.has(t.name));
|
|
94
|
+
}
|
|
76
95
|
if (profile === "nav") {
|
|
77
96
|
return tools.filter((t) => NAV_TOOLS.has(t.name) || META_TOOLS.has(t.name));
|
|
78
97
|
}
|
|
@@ -85,14 +104,29 @@ export function filterToolsByProfile(tools, profile) {
|
|
|
85
104
|
* Parse the TOKEN_PILOT_PROFILE env value. Unknown values get a warning
|
|
86
105
|
* and fall back to full — we never silently apply a guess.
|
|
87
106
|
*/
|
|
107
|
+
/**
|
|
108
|
+
* Parse the TOKEN_PILOT_PROFILE env value.
|
|
109
|
+
*
|
|
110
|
+
* Default changed in v0.30.0: full → edit.
|
|
111
|
+
* Rationale: 'full' was exposing 22 tools + full instruction set on every
|
|
112
|
+
* session, burning ~3 k tokens before any work. 'edit' covers 99% of
|
|
113
|
+
* development workflows (reading + writing code). Switch to 'full' only
|
|
114
|
+
* when you need audit tools (code_audit, find_unused, test_summary).
|
|
115
|
+
*
|
|
116
|
+
* Unknown values fall back to 'edit' with a stderr warning — we never
|
|
117
|
+
* silently apply a guess.
|
|
118
|
+
*/
|
|
88
119
|
export function parseProfileEnv(envValue, warn = () => { }) {
|
|
89
120
|
if (!envValue)
|
|
90
|
-
return "
|
|
121
|
+
return "edit";
|
|
91
122
|
const lower = envValue.trim().toLowerCase();
|
|
92
|
-
if (lower === "full" ||
|
|
123
|
+
if (lower === "full" ||
|
|
124
|
+
lower === "nav" ||
|
|
125
|
+
lower === "edit" ||
|
|
126
|
+
lower === "minimal") {
|
|
93
127
|
return lower;
|
|
94
128
|
}
|
|
95
|
-
warn(`[token-pilot] Unknown TOKEN_PILOT_PROFILE="${envValue}". Expected full|nav|edit. Falling back to
|
|
96
|
-
return "
|
|
129
|
+
warn(`[token-pilot] Unknown TOKEN_PILOT_PROFILE="${envValue}". Expected full|nav|edit|minimal. Falling back to edit.`);
|
|
130
|
+
return "edit";
|
|
97
131
|
}
|
|
98
132
|
//# sourceMappingURL=tool-profiles.js.map
|
package/dist/server.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { type EnforcementMode } from "./server/enforcement-mode.js";
|
|
2
3
|
export declare function createServer(projectRoot: string, options?: {
|
|
3
4
|
skipAstIndex?: boolean;
|
|
5
|
+
enforcementMode?: EnforcementMode;
|
|
4
6
|
}): Promise<Server<{
|
|
5
7
|
method: string;
|
|
6
8
|
params?: {
|
package/dist/server.js
CHANGED
|
@@ -45,11 +45,13 @@ import { detectContextMode } from "./integration/context-mode-detector.js";
|
|
|
45
45
|
import { estimateTokens } from "./core/token-estimator.js";
|
|
46
46
|
import { checkPolicy, isFullReadTool } from "./core/policy-engine.js";
|
|
47
47
|
import { appendToolCall } from "./core/tool-call-log.js";
|
|
48
|
-
import {
|
|
48
|
+
import { getMcpInstructions, TOOL_DEFINITIONS, } from "./server/tool-definitions.js";
|
|
49
49
|
import { filterToolsByProfile, parseProfileEnv, } from "./server/tool-profiles.js";
|
|
50
|
+
import { STRICT_SMART_READ_MAX_TOKENS, STRICT_EXPLORE_AREA_INCLUDE, } from "./server/enforcement-mode.js";
|
|
50
51
|
import { createTokenEstimates } from "./server/token-estimates.js";
|
|
51
52
|
import { validateSmartReadArgs, validateReadSymbolArgs, validateReadSymbolsArgs, validateReadRangeArgs, validateReadDiffArgs, validateFindUsagesArgs, validateSmartReadManyArgs, validateReadForEditArgs, validateRelatedFilesArgs, validateOutlineArgs, validateFindUnusedArgs, validateCodeAuditArgs, validateProjectOverviewArgs, validateModuleInfoArgs, validateSmartDiffArgs, validateExploreAreaArgs, validateSmartLogArgs, validateTestSummaryArgs, validateReadSectionArgs, } from "./core/validation.js";
|
|
52
53
|
export async function createServer(projectRoot, options) {
|
|
54
|
+
const mode = options?.enforcementMode ?? "deny";
|
|
53
55
|
const config = await loadConfig(projectRoot);
|
|
54
56
|
const astIndex = new AstIndexClient(projectRoot, config.astIndex.timeout, {
|
|
55
57
|
binaryPath: config.astIndex.binaryPath,
|
|
@@ -193,7 +195,6 @@ export async function createServer(projectRoot, options) {
|
|
|
193
195
|
let fullFileReadsCount = 0;
|
|
194
196
|
let totalCallCount = 0;
|
|
195
197
|
let totalTokensReturned = 0;
|
|
196
|
-
const readForEditCalled = new Set();
|
|
197
198
|
// Detect context-mode companion
|
|
198
199
|
const cmEnabled = config.contextMode.enabled;
|
|
199
200
|
const contextModeStatus = await detectContextMode(projectRoot, cmEnabled === "auto" ? undefined : cmEnabled);
|
|
@@ -238,18 +239,20 @@ export async function createServer(projectRoot, options) {
|
|
|
238
239
|
catch {
|
|
239
240
|
/* fallback to hardcoded */
|
|
240
241
|
}
|
|
242
|
+
// v0.26.3 — tool profiles. TOKEN_PILOT_PROFILE=nav|edit|full|minimal
|
|
243
|
+
// (default: edit since v0.30.0) trims the advertised tools/list payload.
|
|
244
|
+
// Handlers stay live, so a subagent that explicitly names a filtered-out
|
|
245
|
+
// tool still gets a response — we just don't brag about every tool upfront.
|
|
246
|
+
// v0.30.0 — profile also selects matching MCP instructions so the agent
|
|
247
|
+
// doesn't see rules for tools that aren't in its tools/list.
|
|
248
|
+
const activeProfile = parseProfileEnv(process.env.TOKEN_PILOT_PROFILE, (m) => process.stderr.write(m + "\n"));
|
|
241
249
|
const server = new Server({ name: "token-pilot", version: pkgVersion }, {
|
|
242
250
|
capabilities: { tools: {} },
|
|
243
|
-
instructions:
|
|
251
|
+
instructions: getMcpInstructions(activeProfile),
|
|
244
252
|
});
|
|
245
|
-
// v0.26.3 — tool profiles. TOKEN_PILOT_PROFILE=nav|edit|full (default
|
|
246
|
-
// full) trims the advertised tools/list payload. Handlers stay live,
|
|
247
|
-
// so a subagent that explicitly names a filtered-out tool still gets
|
|
248
|
-
// a response — we just don't brag about every tool upfront.
|
|
249
|
-
const activeProfile = parseProfileEnv(process.env.TOKEN_PILOT_PROFILE, (m) => process.stderr.write(m + "\n"));
|
|
250
253
|
const advertisedTools = filterToolsByProfile(TOOL_DEFINITIONS, activeProfile);
|
|
251
|
-
if (activeProfile !== "
|
|
252
|
-
process.stderr.write(`[token-pilot] Profile: ${activeProfile} — advertising ${advertisedTools.length}/${TOOL_DEFINITIONS.length} tools.
|
|
254
|
+
if (activeProfile !== "edit") {
|
|
255
|
+
process.stderr.write(`[token-pilot] Profile: ${activeProfile} — advertising ${advertisedTools.length}/${TOOL_DEFINITIONS.length} tools. Set TOKEN_PILOT_PROFILE=edit for the default set.\n`);
|
|
253
256
|
}
|
|
254
257
|
server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
255
258
|
tools: advertisedTools,
|
|
@@ -289,14 +292,10 @@ export async function createServer(projectRoot, options) {
|
|
|
289
292
|
if (isFullReadTool(rest.tool)) {
|
|
290
293
|
fullFileReadsCount++;
|
|
291
294
|
}
|
|
292
|
-
if (rest.tool === "read_for_edit" && call.path) {
|
|
293
|
-
readForEditCalled.add(call.path);
|
|
294
|
-
}
|
|
295
295
|
// Policy check
|
|
296
296
|
const advisory = checkPolicy(config.policies, rest.tool, {
|
|
297
297
|
fullFileReadsCount,
|
|
298
298
|
tokensReturned: rest.tokensReturned,
|
|
299
|
-
readForEditCalled,
|
|
300
299
|
totalCallCount,
|
|
301
300
|
totalTokensReturned,
|
|
302
301
|
});
|
|
@@ -331,6 +330,14 @@ export async function createServer(projectRoot, options) {
|
|
|
331
330
|
switch (name) {
|
|
332
331
|
case "smart_read": {
|
|
333
332
|
const validArgs = validateSmartReadArgs(args);
|
|
333
|
+
// v0.30.0 strict mode: cap max_tokens when caller didn't set it.
|
|
334
|
+
let strictReadCapNote;
|
|
335
|
+
if (mode === "strict" && validArgs.max_tokens === undefined) {
|
|
336
|
+
validArgs.max_tokens = STRICT_SMART_READ_MAX_TOKENS;
|
|
337
|
+
strictReadCapNote =
|
|
338
|
+
`\n\n[token-pilot strict] Output capped at ${STRICT_SMART_READ_MAX_TOKENS} tokens ` +
|
|
339
|
+
`(TOKEN_PILOT_MODE=strict). Pass max_tokens explicitly to override.`;
|
|
340
|
+
}
|
|
334
341
|
const picked = pickRegistry(args);
|
|
335
342
|
// Try non-code handler for JSON/YAML/MD etc.
|
|
336
343
|
if (isNonCodeStructured(validArgs.path)) {
|
|
@@ -373,8 +380,9 @@ export async function createServer(projectRoot, options) {
|
|
|
373
380
|
absPath: resolve(projectRoot, validArgs.path),
|
|
374
381
|
args: validArgs,
|
|
375
382
|
});
|
|
376
|
-
|
|
377
|
-
|
|
383
|
+
const srSuffix = (policyAdv ?? "") + (strictReadCapNote ?? "");
|
|
384
|
+
if (srSuffix)
|
|
385
|
+
result.content[0] = { type: "text", text: text + srSuffix };
|
|
378
386
|
return result;
|
|
379
387
|
}
|
|
380
388
|
case "read_symbol": {
|
|
@@ -527,6 +535,15 @@ export async function createServer(projectRoot, options) {
|
|
|
527
535
|
}
|
|
528
536
|
case "find_usages": {
|
|
529
537
|
const usagesArgs = validateFindUsagesArgs(args);
|
|
538
|
+
// v0.30.0 strict mode: default mode to "list" when caller didn't set it.
|
|
539
|
+
// Injected before cache lookup so the key matches strict-mode cached results.
|
|
540
|
+
let strictFuNote;
|
|
541
|
+
if (mode === "strict" && usagesArgs.mode === undefined) {
|
|
542
|
+
usagesArgs.mode = "list";
|
|
543
|
+
strictFuNote =
|
|
544
|
+
`\n\n[token-pilot strict] find_usages mode defaulted to "list" ` +
|
|
545
|
+
`(TOKEN_PILOT_MODE=strict). Pass mode explicitly to override.`;
|
|
546
|
+
}
|
|
530
547
|
const cachedUsages = sessionCache?.get("find_usages", usagesArgs);
|
|
531
548
|
if (cachedUsages) {
|
|
532
549
|
recordWithTrace({
|
|
@@ -558,6 +575,12 @@ export async function createServer(projectRoot, options) {
|
|
|
558
575
|
savingsCategory: "compression",
|
|
559
576
|
args: usagesArgs,
|
|
560
577
|
});
|
|
578
|
+
if (strictFuNote && usagesResult.content[0]) {
|
|
579
|
+
usagesResult.content[0] = {
|
|
580
|
+
type: "text",
|
|
581
|
+
text: usagesText + strictFuNote,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
561
584
|
return usagesResult;
|
|
562
585
|
}
|
|
563
586
|
case "project_overview": {
|
|
@@ -801,6 +824,15 @@ export async function createServer(projectRoot, options) {
|
|
|
801
824
|
}
|
|
802
825
|
case "explore_area": {
|
|
803
826
|
const eaArgs = validateExploreAreaArgs(args);
|
|
827
|
+
// v0.30.0 strict mode: default include to outline-only when caller didn't set it.
|
|
828
|
+
// Injected before cache lookup so the key matches strict-mode cached results.
|
|
829
|
+
let strictEaCapNote;
|
|
830
|
+
if (mode === "strict" && eaArgs.include === undefined) {
|
|
831
|
+
eaArgs.include = STRICT_EXPLORE_AREA_INCLUDE;
|
|
832
|
+
strictEaCapNote =
|
|
833
|
+
`\n\n[token-pilot strict] include defaulted to ["outline"] ` +
|
|
834
|
+
`(TOKEN_PILOT_MODE=strict). Pass include explicitly to override.`;
|
|
835
|
+
}
|
|
804
836
|
const cachedEa = sessionCache?.get("explore_area", eaArgs);
|
|
805
837
|
if (cachedEa) {
|
|
806
838
|
recordWithTrace({
|
|
@@ -833,10 +865,24 @@ export async function createServer(projectRoot, options) {
|
|
|
833
865
|
savingsCategory: "compression",
|
|
834
866
|
args: eaArgs,
|
|
835
867
|
});
|
|
868
|
+
if (strictEaCapNote && eaResult.content[0]) {
|
|
869
|
+
eaResult.content[0] = {
|
|
870
|
+
type: "text",
|
|
871
|
+
text: eaText + strictEaCapNote,
|
|
872
|
+
};
|
|
873
|
+
}
|
|
836
874
|
return eaResult;
|
|
837
875
|
}
|
|
838
876
|
case "smart_log": {
|
|
839
877
|
const slArgs = validateSmartLogArgs(args);
|
|
878
|
+
// v0.30.0 strict mode: bound count to 20 when caller didn't set it.
|
|
879
|
+
let strictSlNote;
|
|
880
|
+
if (mode === "strict" && slArgs.count === undefined) {
|
|
881
|
+
slArgs.count = 20;
|
|
882
|
+
strictSlNote =
|
|
883
|
+
`\n\n[token-pilot strict] smart_log count defaulted to 20 ` +
|
|
884
|
+
`(TOKEN_PILOT_MODE=strict). Pass count explicitly to override.`;
|
|
885
|
+
}
|
|
840
886
|
const slResult = await handleSmartLog(slArgs, projectRoot);
|
|
841
887
|
const slText = slResult.content[0]?.text ?? "";
|
|
842
888
|
const slTokens = estimateTokens(slText);
|
|
@@ -849,6 +895,12 @@ export async function createServer(projectRoot, options) {
|
|
|
849
895
|
savingsCategory: "compression",
|
|
850
896
|
args: slArgs,
|
|
851
897
|
});
|
|
898
|
+
if (strictSlNote && slResult.content[0]) {
|
|
899
|
+
slResult.content[0] = {
|
|
900
|
+
type: "text",
|
|
901
|
+
text: slText + strictSlNote,
|
|
902
|
+
};
|
|
903
|
+
}
|
|
852
904
|
return { content: slResult.content };
|
|
853
905
|
}
|
|
854
906
|
case "test_summary": {
|
package/docs/agents.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# tp-* Subagents (Claude Code only)
|
|
2
|
+
|
|
3
|
+
`tp-*` subagents are a Claude Code feature. Other clients get the MCP tools + hooks but cannot invoke subagents. Each agent carries an explicit `model:` field in its frontmatter; the budget is enforced post-response — overshoots beyond 10% land in `.token-pilot/over-budget.log`.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx token-pilot install-agents --scope=user # all projects
|
|
9
|
+
npx token-pilot install-agents --scope=project # this repo only
|
|
10
|
+
npx token-pilot install-agents --scope=user --force # re-apply after an update
|
|
11
|
+
npx token-pilot uninstall-agents --scope=user|project
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`init` offers to install these; to add them to another project run `npx token-pilot install-agents`.
|
|
15
|
+
|
|
16
|
+
## Tier 1 — Workhorses (invoke proactively)
|
|
17
|
+
|
|
18
|
+
| Agent | When to invoke | Budget |
|
|
19
|
+
|-------|---------------|-------:|
|
|
20
|
+
| `tp-run` | General MCP-first workhorse; use when no specialised agent fits | 800 |
|
|
21
|
+
| `tp-onboard` | Orient to an unfamiliar repo (layout, entry points, modules) | 600 |
|
|
22
|
+
| `tp-pr-reviewer` | Review a diff / PR / changeset; verdict-first, Critical/Important tiers | 600 |
|
|
23
|
+
| `tp-impact-analyzer` | Trace blast-radius of a change (callers, transitive deps) | 400 |
|
|
24
|
+
| `tp-refactor-planner` | Plan a refactor with exact edit context per step | 500 |
|
|
25
|
+
| `tp-test-triage` | Investigate test failures → root cause → minimal fix | 500 |
|
|
26
|
+
|
|
27
|
+
## Tier 2 — Specialists
|
|
28
|
+
|
|
29
|
+
| Agent | When to invoke | Budget |
|
|
30
|
+
|-------|---------------|-------:|
|
|
31
|
+
| `tp-debugger` | Stack trace / error → root-cause line via call-tree traversal | 700 |
|
|
32
|
+
| `tp-migration-scout` | Pre-migration impact map grouped by effort class | 800 |
|
|
33
|
+
| `tp-test-writer` | Write tests for ONE symbol, mirrors project style, runs tests | 900 |
|
|
34
|
+
| `tp-dead-code-finder` | Cross-checked dead-code detection, output-only (never deletes) | 600 |
|
|
35
|
+
| `tp-commit-writer` | Draft Conventional-Commit from staged diff; refuses failing tests | 400 |
|
|
36
|
+
| `tp-history-explorer` | "Why is this like this?" — minimum commit chain explaining current state | 600 |
|
|
37
|
+
| `tp-audit-scanner` | Read-only security / quality audit; Critical / Important / Minor findings | 800 |
|
|
38
|
+
| `tp-session-restorer` | Rehydrate state after /clear or compaction from latest snapshot | 400 |
|
|
39
|
+
|
|
40
|
+
## Tier 3 — Combo / Workflow
|
|
41
|
+
|
|
42
|
+
| Agent | When to invoke | Budget |
|
|
43
|
+
|-------|---------------|-------:|
|
|
44
|
+
| `tp-review-impact` | Pre-merge blast-radius review (diff × dependents × API surface) | 700 |
|
|
45
|
+
| `tp-test-coverage-gapper` | Find symbols with zero test references, prioritised | 500 |
|
|
46
|
+
| `tp-api-surface-tracker` | Public API diff vs last release → MAJOR / MINOR / PATCH verdict | 600 |
|
|
47
|
+
| `tp-dep-health` | Dep audit: stale × heavily-used × removable | 600 |
|
|
48
|
+
| `tp-incident-timeline` | Correlate an incident window with commits, rank likely culprits | 700 |
|
|
49
|
+
|
|
50
|
+
## Tier 4 — Methodology
|
|
51
|
+
|
|
52
|
+
| Agent | When to invoke | Budget |
|
|
53
|
+
|-------|---------------|-------:|
|
|
54
|
+
| `tp-context-engineer` | Audit / write CLAUDE.md / AGENTS.md rules files per project | 800 |
|
|
55
|
+
| `tp-spec-writer` | Pre-code spec with gated workflow; surfaces assumptions before code | 900 |
|
|
56
|
+
| `tp-performance-profiler` | Measure → identify → fix → verify → guard; refuses to optimise without data | 800 |
|
|
57
|
+
| `tp-incremental-builder` | Multi-file feature work in thin vertical slices, test between each | 900 |
|
|
58
|
+
| `tp-doc-writer` | ADRs + READMEs + API docs; documents *why* not *what* | 700 |
|
|
59
|
+
| `tp-ship-coordinator` | 5-pillar pre-launch checklist (quality / security / observability / rollback / rollout) | 800 |
|
|
60
|
+
|
|
61
|
+
## Model Tiers
|
|
62
|
+
|
|
63
|
+
Every agent carries an explicit `model:` field:
|
|
64
|
+
|
|
65
|
+
| Model | Count | Used for |
|
|
66
|
+
|-------|------:|---------|
|
|
67
|
+
| `haiku` | 9 | Structured / format-bound output (commit messages, onboarding maps, ADRs, session briefings) |
|
|
68
|
+
| `sonnet` | 15 | Reasoning tasks (review, debug, test, plan, audit, spec, profile, ship) |
|
|
69
|
+
| `inherit` | 1 | Deep correlation needing the main thread's model (`tp-incident-timeline`) |
|
|
70
|
+
|
|
71
|
+
Under Opus 4.7's +35% tokenizer tax, keeping the majority of agent spawns on haiku/sonnet saves 5–10× model cost vs an all-Opus baseline.
|
|
72
|
+
|
|
73
|
+
## Third-party Agent Integration (bless-agents)
|
|
74
|
+
|
|
75
|
+
For third-party agents (e.g. `acc-*` plugins) whose tool allowlist excludes token-pilot MCP:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx token-pilot bless-agents # add token-pilot MCP to project-level overrides
|
|
79
|
+
npx token-pilot unbless-agents <name>... | --all
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`doctor` warns when the original agent has changed since blessing.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Configuration & Tool Profiles
|
|
2
|
+
|
|
3
|
+
## .token-pilot.json
|
|
4
|
+
|
|
5
|
+
Drop `.token-pilot.json` in your project root. All fields are optional.
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"hooks": { "mode": "deny-enhanced", "denyThreshold": 300 },
|
|
10
|
+
"sessionStart": { "enabled": true, "showStats": false, "maxReminderTokens": 250 },
|
|
11
|
+
"agents": { "scope": null, "reminder": true },
|
|
12
|
+
"smartRead": { "smallFileThreshold": 200 },
|
|
13
|
+
"cache": { "maxSizeMB": 100, "watchFiles": true },
|
|
14
|
+
"policies": { "maxFullFileReads": 10, "largeReadThreshold": 2000 },
|
|
15
|
+
"astIndex": { "binaryPath": null },
|
|
16
|
+
"updates": { "checkOnStartup": true, "autoUpdate": false },
|
|
17
|
+
"ignore": ["node_modules/**", "dist/**", ".git/**"]
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
| Option | Default | What it does |
|
|
22
|
+
|--------|---------|--------------|
|
|
23
|
+
| `hooks.mode` | `"deny-enhanced"` | Read hook mode: `off` / `advisory` / `deny-enhanced` |
|
|
24
|
+
| `hooks.denyThreshold` | `300` | Line count above which the hook intervenes on unbounded `Read` |
|
|
25
|
+
| `sessionStart.enabled` | `true` | Re-inject MCP-rules reminder at every new session / `/clear` / `/compact` |
|
|
26
|
+
| `agents.scope` | `null` | Persisted scope of last `install-agents` run; reused silently |
|
|
27
|
+
| `agents.reminder` | `true` | Show the "agents not installed" startup nudge |
|
|
28
|
+
| `smartRead.smallFileThreshold` | `200` | Files with fewer lines bypass AST overhead and are returned in full |
|
|
29
|
+
| `cache.maxSizeMB` | `100` | File cache ceiling (LRU eviction) |
|
|
30
|
+
| `policies.maxFullFileReads` | `10` | Warn after N full-file reads in session |
|
|
31
|
+
| `policies.largeReadThreshold` | `2000` | Token threshold above which a read is flagged as "large" in analytics |
|
|
32
|
+
|
|
33
|
+
## Tool Profiles
|
|
34
|
+
|
|
35
|
+
Trim the advertised `tools/list` to save ~2 k tokens per session. Set via `TOKEN_PILOT_PROFILE` in your MCP server env block.
|
|
36
|
+
|
|
37
|
+
| Profile | Tools | ~Tokens | Use when |
|
|
38
|
+
|---------|------:|--------:|----------|
|
|
39
|
+
| `full` *(default)* | 22 | ~4 150 | All capabilities |
|
|
40
|
+
| `edit` | 16 | ~3 120 | Code-change workflows (nav + batch reads + `read_for_edit`) |
|
|
41
|
+
| `nav` | 10 | ~1 910 | Read-only exploration / subagents that only navigate |
|
|
42
|
+
|
|
43
|
+
Handlers remain active regardless of profile — a subagent that explicitly names a filtered-out tool still gets served. The profile only controls what appears in `tools/list` at session start.
|
|
44
|
+
|
|
45
|
+
### Setting a profile
|
|
46
|
+
|
|
47
|
+
**In `.mcp.json`:**
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"mcpServers": {
|
|
51
|
+
"token-pilot": {
|
|
52
|
+
"command": "npx",
|
|
53
|
+
"args": ["-y", "token-pilot"],
|
|
54
|
+
"env": { "TOKEN_PILOT_PROFILE": "nav" }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Via shell:**
|
|
61
|
+
```bash
|
|
62
|
+
TOKEN_PILOT_PROFILE=edit npx token-pilot
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## CLI Reference
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
token-pilot # start MCP server
|
|
69
|
+
token-pilot init # create/merge .mcp.json; prompt about subagents
|
|
70
|
+
token-pilot install-agents [--scope=user|project] [--force]
|
|
71
|
+
token-pilot uninstall-agents --scope=user|project
|
|
72
|
+
token-pilot bless-agents # extend third-party agents with token-pilot MCP
|
|
73
|
+
token-pilot unbless-agents <name>... | --all
|
|
74
|
+
token-pilot install-hook # install PreToolUse hooks
|
|
75
|
+
token-pilot uninstall-hook
|
|
76
|
+
token-pilot stats # totals + top files from hook-events.jsonl
|
|
77
|
+
token-pilot stats --session[=<id>] | --by-agent
|
|
78
|
+
token-pilot tool-audit # per-tool savings distribution
|
|
79
|
+
token-pilot tool-audit --json
|
|
80
|
+
token-pilot doctor # diagnostics (ast-index, config, upstream drift)
|
|
81
|
+
token-pilot doctor --check=env # env var check only
|
|
82
|
+
token-pilot install-ast-index # download ast-index binary (auto on first run)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Architecture
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
src/
|
|
89
|
+
index.ts — CLI entry + MCP server bootstrap
|
|
90
|
+
server.ts — MCP server: 22 tool definitions + enforcement mode
|
|
91
|
+
server/
|
|
92
|
+
enforcement-mode.ts — TOKEN_PILOT_MODE parsing (advisory / deny / strict)
|
|
93
|
+
ast-index/ — ast-index binary client + auto-install
|
|
94
|
+
core/
|
|
95
|
+
event-log.ts — hook-events.jsonl + rotation + retention
|
|
96
|
+
session-analytics.ts, policy-engine.ts, intent-classifier.ts
|
|
97
|
+
hooks/
|
|
98
|
+
installer.ts — hook install/uninstall for Claude Code
|
|
99
|
+
pre-bash.ts — PreToolUse:Bash advisor (Bash/sh/eval/loop patterns)
|
|
100
|
+
pre-grep.ts — PreToolUse:Grep advisor (symbol-like pattern detection)
|
|
101
|
+
session-start.ts — SessionStart reminder handler
|
|
102
|
+
summary-pipeline.ts — ast-index → regex → head+tail → pass-through
|
|
103
|
+
cli/
|
|
104
|
+
install-agents.ts, uninstall-agents.ts
|
|
105
|
+
bless-agents.ts, unbless-agents.ts, doctor-drift.ts
|
|
106
|
+
stats.ts, tool-audit.ts
|
|
107
|
+
templates/agent-builder.ts
|
|
108
|
+
config/loader.ts, defaults.ts
|
|
109
|
+
handlers/ — 22 MCP tool handlers
|
|
110
|
+
git/ — HEAD + file watchers (cache invalidation)
|
|
111
|
+
|
|
112
|
+
scripts/
|
|
113
|
+
build-agents.mjs — render templates/ → dist/agents/
|
|
114
|
+
bench-hook.mjs — hook latency benchmark
|
|
115
|
+
|
|
116
|
+
templates/agents/ — source for tp-* family + shared preamble + contract
|
|
117
|
+
```
|
package/docs/hooks.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Hooks & Enforcement Modes
|
|
2
|
+
|
|
3
|
+
Token Pilot installs two categories of PreToolUse hooks in Claude Code:
|
|
4
|
+
|
|
5
|
+
1. **Read hook** — intercepts large `Read` calls (configurable threshold, default 300 lines) and returns a structural summary in the denial reason.
|
|
6
|
+
2. **Grep / Bash hooks** — block heavy recursive patterns (`grep -r`, `find /`, `cat <file.ts>`, unbounded `git log`, bare `git diff`) and redirect to token-pilot MCP equivalents.
|
|
7
|
+
|
|
8
|
+
## TOKEN_PILOT_MODE — Enforcement Mode
|
|
9
|
+
|
|
10
|
+
Controls how aggressively both hook categories behave:
|
|
11
|
+
|
|
12
|
+
| Value | Grep/Bash hooks | MCP output |
|
|
13
|
+
|-------|----------------|------------|
|
|
14
|
+
| `advisory` | Pass all through (no blocking) | No caps |
|
|
15
|
+
| `deny` *(default)* | Block heavy patterns, allow bounded variants | No caps |
|
|
16
|
+
| `strict` | Same as deny, plus auto-cap MCP output (see below) | Capped |
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Set in your MCP server env block or shell profile:
|
|
20
|
+
TOKEN_PILOT_MODE=strict npx token-pilot
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Strict-mode MCP output caps
|
|
24
|
+
|
|
25
|
+
When `TOKEN_PILOT_MODE=strict` and the caller has not set the parameter explicitly:
|
|
26
|
+
|
|
27
|
+
| Tool | Auto-injected default | Note appended |
|
|
28
|
+
|------|-----------------------|---------------|
|
|
29
|
+
| `smart_read` | `max_tokens: 2000` | Yes |
|
|
30
|
+
| `explore_area` | `include: ["outline"]` | Yes |
|
|
31
|
+
| `find_usages` | `mode: "list"` | Yes |
|
|
32
|
+
| `smart_log` | `count: 20` | Yes |
|
|
33
|
+
|
|
34
|
+
Pass the parameter explicitly to override the cap.
|
|
35
|
+
|
|
36
|
+
## Read Hook Modes
|
|
37
|
+
|
|
38
|
+
The PreToolUse:Read hook has its own mode (separate from enforcement mode). Set in `.token-pilot.json`:
|
|
39
|
+
|
|
40
|
+
| Mode | Behaviour |
|
|
41
|
+
|------|-----------|
|
|
42
|
+
| `off` | Hook is inert — all `Read` calls pass through |
|
|
43
|
+
| `advisory` | Denies unbounded Read with a short tip pointing at `smart_read` / `read_for_edit` |
|
|
44
|
+
| `deny-enhanced` *(default)* | Denies the Read and returns a full structural summary (imports, exports, declarations) **inside** the denial reason. Works for subagents that lack MCP access. |
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{ "hooks": { "mode": "deny-enhanced", "denyThreshold": 300 } }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Grep / Bash Hook Rules
|
|
51
|
+
|
|
52
|
+
The Grep hook redirects symbol-like patterns to `find_usages`. The Bash hook blocks:
|
|
53
|
+
|
|
54
|
+
| Pattern | Blocked when | Allowed when |
|
|
55
|
+
|---------|-------------|--------------|
|
|
56
|
+
| `grep -r`/`-R` | Always (unbounded) | Has `-m N` bound |
|
|
57
|
+
| `find /`, `find ~` | No `-maxdepth` | Has `-maxdepth N` |
|
|
58
|
+
| `cat <file.ts>` | Code file, no pipeline | In pipeline (`cat … \| head`) or non-code file |
|
|
59
|
+
| `git log` | No count limit | Has `-n N`, `--max-count`, or `\| head` |
|
|
60
|
+
| `git diff` | Bare (no path/flag) | Has path arg or `--stat` |
|
|
61
|
+
| `bash -c "…"`, `eval "…"` | Inner command is heavy | Inner command is benign |
|
|
62
|
+
|
|
63
|
+
## Installing / Removing Hooks
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx token-pilot install-hook # register PreToolUse hooks in Claude Code
|
|
67
|
+
npx token-pilot uninstall-hook # remove hooks
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Hooks are auto-installed on first server start inside Claude Code. The Claude Code plugin path installs hooks automatically:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
claude plugin marketplace add https://github.com/Digital-Threads/token-pilot
|
|
74
|
+
claude plugin install token-pilot@token-pilot
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Environment Variables
|
|
78
|
+
|
|
79
|
+
| Var | Effect |
|
|
80
|
+
|-----|--------|
|
|
81
|
+
| `TOKEN_PILOT_MODE` | `advisory` / `deny` (default) / `strict` — enforcement level for Grep/Bash hooks and MCP output caps |
|
|
82
|
+
| `TOKEN_PILOT_BYPASS=1` | Pass every Read through (Read hook only) |
|
|
83
|
+
| `TOKEN_PILOT_DENY_THRESHOLD=<n>` | Override `hooks.denyThreshold` (default 300) |
|
|
84
|
+
| `TOKEN_PILOT_ADAPTIVE_THRESHOLD=true` | Enable adaptive curve as session burns |
|
|
85
|
+
| `TOKEN_PILOT_DEBUG=1` | Verbose hook logging to stderr |
|
|
86
|
+
| `TOKEN_PILOT_NO_AGENT_REMINDER=1` | Suppress the "tp-* not installed" stderr nudge |
|
|
87
|
+
| `TOKEN_PILOT_SUBAGENT=1` | Mark the MCP server as running inside a subagent |
|
|
88
|
+
|
|
89
|
+
## Analytics & Audit
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
token-pilot stats # totals + top files from hook-events.jsonl
|
|
93
|
+
token-pilot stats --session[=<id>] # filter by session
|
|
94
|
+
token-pilot stats --by-agent # grouped by agent
|
|
95
|
+
token-pilot tool-audit # per-tool savings distribution (cumulative)
|
|
96
|
+
token-pilot tool-audit --json # machine-readable output
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Hook events accumulate in `.token-pilot/hook-events.jsonl`. The `session_analytics` MCP tool provides per-tool breakdown within the current session.
|