token-pilot 0.24.1 → 0.26.5
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 +29 -12
- package/.claude-plugin/plugin.json +23 -5
- package/CHANGELOG.md +182 -0
- package/README.md +128 -15
- package/dist/agents/tp-api-surface-tracker.md +4 -3
- package/dist/agents/tp-audit-scanner.md +1 -1
- package/dist/agents/tp-commit-writer.md +1 -1
- package/dist/agents/tp-dead-code-finder.md +22 -7
- package/dist/agents/tp-debugger.md +1 -1
- package/dist/agents/tp-dep-health.md +3 -2
- package/dist/agents/tp-history-explorer.md +1 -1
- package/dist/agents/tp-impact-analyzer.md +1 -1
- package/dist/agents/tp-incident-timeline.md +1 -1
- package/dist/agents/tp-migration-scout.md +1 -1
- package/dist/agents/tp-onboard.md +1 -1
- package/dist/agents/tp-pr-reviewer.md +1 -1
- package/dist/agents/tp-refactor-planner.md +1 -1
- package/dist/agents/tp-review-impact.md +1 -1
- package/dist/agents/tp-run.md +1 -1
- package/dist/agents/tp-session-restorer.md +1 -1
- package/dist/agents/tp-test-coverage-gapper.md +1 -1
- package/dist/agents/tp-test-triage.md +1 -1
- package/dist/agents/tp-test-writer.md +1 -1
- package/dist/cli/detect-client.d.ts +39 -0
- package/dist/cli/detect-client.js +106 -0
- package/dist/cli/install-agents.d.ts +1 -0
- package/dist/cli/install-agents.js +31 -1
- package/dist/cli/tool-audit.d.ts +58 -0
- package/dist/cli/tool-audit.js +123 -0
- package/dist/cli/typo-guard.d.ts +1 -1
- package/dist/cli/typo-guard.js +1 -0
- package/dist/core/tool-call-log.d.ts +63 -0
- package/dist/core/tool-call-log.js +171 -0
- package/dist/handlers/read-symbols.js +23 -1
- package/dist/hooks/installer.js +27 -12
- package/dist/index.js +55 -0
- package/dist/server/profile-recommender.d.ts +48 -0
- package/dist/server/profile-recommender.js +102 -0
- package/dist/server/token-estimates.d.ts +17 -3
- package/dist/server/token-estimates.js +77 -45
- package/dist/server/tool-definitions.js +1 -1
- package/dist/server/tool-profiles.d.ts +46 -0
- package/dist/server/tool-profiles.js +81 -0
- package/dist/server.js +38 -1
- package/package.json +1 -1
- package/start.sh +0 -0
- package/.mcp.json +0 -8
|
@@ -2,9 +2,31 @@
|
|
|
2
2
|
* Token estimation functions for analytics.
|
|
3
3
|
* Used to calculate "tokens would be" for honest savings reporting.
|
|
4
4
|
*/
|
|
5
|
-
import { estimateTokens } from
|
|
6
|
-
import { resolveSafePath } from
|
|
7
|
-
import { CODE_EXTENSIONS } from
|
|
5
|
+
import { estimateTokens } from "../core/token-estimator.js";
|
|
6
|
+
import { resolveSafePath } from "../core/validation.js";
|
|
7
|
+
import { CODE_EXTENSIONS } from "../handlers/outline.js";
|
|
8
|
+
/**
|
|
9
|
+
* Honest savings classification for a tool's text output.
|
|
10
|
+
*
|
|
11
|
+
* Standalone (pure) so it can be unit-tested without spinning up the
|
|
12
|
+
* full token-estimates closure. Kept here because it's semantically
|
|
13
|
+
* tied to how this module reports wouldBe/returned pairs.
|
|
14
|
+
*
|
|
15
|
+
* v0.26.1 adds the 'none' branch: smart_read's small-file pass-through
|
|
16
|
+
* returns the file verbatim with a tiny header. Claiming wouldBe =
|
|
17
|
+
* fullFile for those calls was the root cause of the -2% "negative
|
|
18
|
+
* savings" line Opus 4.7 reported. With 'none', the recorder sets
|
|
19
|
+
* wouldBe = returned → 0% savings, no ghost overhead.
|
|
20
|
+
*/
|
|
21
|
+
export function detectSavingsCategoryPure(text) {
|
|
22
|
+
if (text.startsWith("REMINDER:") || text.startsWith("DEDUP:"))
|
|
23
|
+
return "dedup";
|
|
24
|
+
if (text.includes("returned in full, below threshold") ||
|
|
25
|
+
text.includes("returned in full, outline not smaller")) {
|
|
26
|
+
return "none";
|
|
27
|
+
}
|
|
28
|
+
return "compression";
|
|
29
|
+
}
|
|
8
30
|
/**
|
|
9
31
|
* Creates token estimation functions bound to a project context.
|
|
10
32
|
* Uses getter for projectRoot since it may change on auto-detect.
|
|
@@ -16,8 +38,8 @@ export function createTokenEstimates(getProjectRoot, fileCache) {
|
|
|
16
38
|
const cached = fileCache.get(absPath);
|
|
17
39
|
if (cached)
|
|
18
40
|
return estimateTokens(cached.content);
|
|
19
|
-
const { readFile: readFileAsync } = await import(
|
|
20
|
-
const content = await readFileAsync(absPath,
|
|
41
|
+
const { readFile: readFileAsync } = await import("node:fs/promises");
|
|
42
|
+
const content = await readFileAsync(absPath, "utf-8");
|
|
21
43
|
return estimateTokens(content);
|
|
22
44
|
}
|
|
23
45
|
catch {
|
|
@@ -26,34 +48,46 @@ export function createTokenEstimates(getProjectRoot, fileCache) {
|
|
|
26
48
|
}
|
|
27
49
|
async function estimateProjectOverviewWorkflowTokens(includeSections) {
|
|
28
50
|
const sectionFiles = {
|
|
29
|
-
stack: [
|
|
30
|
-
|
|
51
|
+
stack: [
|
|
52
|
+
"package.json",
|
|
53
|
+
"composer.json",
|
|
54
|
+
"Cargo.toml",
|
|
55
|
+
"pyproject.toml",
|
|
56
|
+
"go.mod",
|
|
57
|
+
],
|
|
58
|
+
ci: [
|
|
59
|
+
".gitlab-ci.yml",
|
|
60
|
+
"Jenkinsfile",
|
|
61
|
+
".circleci/config.yml",
|
|
62
|
+
"bitbucket-pipelines.yml",
|
|
63
|
+
".travis.yml",
|
|
64
|
+
],
|
|
31
65
|
quality: [
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
66
|
+
"tsconfig.json",
|
|
67
|
+
"vitest.config.ts",
|
|
68
|
+
"vitest.config.js",
|
|
69
|
+
"vitest.config.mts",
|
|
70
|
+
"jest.config.js",
|
|
71
|
+
"jest.config.ts",
|
|
72
|
+
"jest.config.mjs",
|
|
73
|
+
"eslint.config.js",
|
|
74
|
+
"eslint.config.mjs",
|
|
75
|
+
".eslintrc",
|
|
76
|
+
".eslintrc.js",
|
|
77
|
+
".eslintrc.json",
|
|
78
|
+
".eslintrc.yml",
|
|
79
|
+
"biome.json",
|
|
80
|
+
"biome.jsonc",
|
|
81
|
+
".prettierrc",
|
|
82
|
+
".prettierrc.js",
|
|
83
|
+
".prettierrc.json",
|
|
84
|
+
"prettier.config.js",
|
|
85
|
+
"phpunit.xml",
|
|
86
|
+
"phpunit.xml.dist",
|
|
87
|
+
"phpstan.neon",
|
|
88
|
+
"phpstan.neon.dist",
|
|
55
89
|
],
|
|
56
|
-
architecture: [
|
|
90
|
+
architecture: ["README.md"],
|
|
57
91
|
};
|
|
58
92
|
let total = 0;
|
|
59
93
|
const seen = new Set();
|
|
@@ -65,15 +99,17 @@ export function createTokenEstimates(getProjectRoot, fileCache) {
|
|
|
65
99
|
total += await fullFileTokens(file);
|
|
66
100
|
}
|
|
67
101
|
}
|
|
68
|
-
if (includeSections.includes(
|
|
102
|
+
if (includeSections.includes("ci")) {
|
|
69
103
|
try {
|
|
70
|
-
const { readdir: readDirAsync } = await import(
|
|
71
|
-
const workflowDir = resolveSafePath(getProjectRoot(),
|
|
72
|
-
const workflowFiles = await readDirAsync(workflowDir, {
|
|
104
|
+
const { readdir: readDirAsync } = await import("node:fs/promises");
|
|
105
|
+
const workflowDir = resolveSafePath(getProjectRoot(), ".github/workflows");
|
|
106
|
+
const workflowFiles = await readDirAsync(workflowDir, {
|
|
107
|
+
withFileTypes: true,
|
|
108
|
+
});
|
|
73
109
|
for (const file of workflowFiles) {
|
|
74
110
|
if (!file.isFile())
|
|
75
111
|
continue;
|
|
76
|
-
if (!file.name.endsWith(
|
|
112
|
+
if (!file.name.endsWith(".yml") && !file.name.endsWith(".yaml"))
|
|
77
113
|
continue;
|
|
78
114
|
total += await fullFileTokens(`.github/workflows/${file.name}`);
|
|
79
115
|
}
|
|
@@ -82,7 +118,7 @@ export function createTokenEstimates(getProjectRoot, fileCache) {
|
|
|
82
118
|
// ignore missing workflows dir
|
|
83
119
|
}
|
|
84
120
|
}
|
|
85
|
-
if (includeSections.includes(
|
|
121
|
+
if (includeSections.includes("architecture")) {
|
|
86
122
|
total += 200;
|
|
87
123
|
}
|
|
88
124
|
return total;
|
|
@@ -90,8 +126,8 @@ export function createTokenEstimates(getProjectRoot, fileCache) {
|
|
|
90
126
|
async function estimateOutlineWorkflowTokens(relativePath, recursive, maxDepth) {
|
|
91
127
|
const SAMPLE_LIMIT = 30;
|
|
92
128
|
try {
|
|
93
|
-
const { readdir: readDirAsync } = await import(
|
|
94
|
-
const { resolve: resolvePath } = await import(
|
|
129
|
+
const { readdir: readDirAsync } = await import("node:fs/promises");
|
|
130
|
+
const { resolve: resolvePath } = await import("node:path");
|
|
95
131
|
const absDir = resolveSafePath(getProjectRoot(), relativePath);
|
|
96
132
|
const sampledFiles = [];
|
|
97
133
|
let totalFiles = 0;
|
|
@@ -99,7 +135,7 @@ export function createTokenEstimates(getProjectRoot, fileCache) {
|
|
|
99
135
|
const entries = await readDirAsync(dirPath, { withFileTypes: true });
|
|
100
136
|
for (const entry of entries) {
|
|
101
137
|
if (entry.isFile()) {
|
|
102
|
-
const ext = entry.name.split(
|
|
138
|
+
const ext = entry.name.split(".").pop()?.toLowerCase() ?? "";
|
|
103
139
|
if (!CODE_EXTENSIONS.has(ext))
|
|
104
140
|
continue;
|
|
105
141
|
totalFiles++;
|
|
@@ -186,11 +222,7 @@ export function createTokenEstimates(getProjectRoot, fileCache) {
|
|
|
186
222
|
total += (meta.changeCount ?? 0) * 40;
|
|
187
223
|
return total;
|
|
188
224
|
}
|
|
189
|
-
|
|
190
|
-
if (text.startsWith('REMINDER:') || text.startsWith('DEDUP:'))
|
|
191
|
-
return 'dedup';
|
|
192
|
-
return 'compression';
|
|
193
|
-
}
|
|
225
|
+
const detectSavingsCategory = detectSavingsCategoryPure;
|
|
194
226
|
return {
|
|
195
227
|
fullFileTokens,
|
|
196
228
|
estimateProjectOverviewWorkflowTokens,
|
|
@@ -287,7 +287,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
287
287
|
// --- Search & navigation ---
|
|
288
288
|
{
|
|
289
289
|
name: "find_usages",
|
|
290
|
-
description: "Use INSTEAD OF Grep for finding symbol references. Semantic search — groups by: definitions, imports, usages. Supports scope, kind, limit, lang filters. Use context_lines to include surrounding code.",
|
|
290
|
+
description: "Use INSTEAD OF Grep for finding symbol references. Semantic search — groups by: definitions, imports, usages. Supports scope, kind, limit, lang filters. Use context_lines to include surrounding code. HINT: for very short / generic symbols (≤4 chars like `id`, `err`, `Cmd`, `db`) Grep is usually cheaper than find_usages — the semantic grouping doesn't pay off when the symbol resolves ambiguously across thousands of files.",
|
|
291
291
|
inputSchema: {
|
|
292
292
|
type: "object",
|
|
293
293
|
properties: {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.26.3 — tool profiles.
|
|
3
|
+
*
|
|
4
|
+
* Idea lifted honestly from Token Savior's TOKEN_SAVIOR_PROFILE. When an
|
|
5
|
+
* MCP server advertises 22 tools, every tools/list response costs the
|
|
6
|
+
* agent ~3 k tokens before it does anything. Most sessions don't need
|
|
7
|
+
* every tool — a code-review agent uses smart_read + find_usages +
|
|
8
|
+
* outline and nothing else. A profile lets the user ship a narrower
|
|
9
|
+
* tools/list while keeping the handlers live (so a subagent or another
|
|
10
|
+
* user in the same server can still reach the full set if they know
|
|
11
|
+
* the name).
|
|
12
|
+
*
|
|
13
|
+
* Three profiles:
|
|
14
|
+
* - full (default): everything, same as pre-v0.26.3.
|
|
15
|
+
* - nav : read-only exploration. smart_read, outline, find_usages,
|
|
16
|
+
* read_symbol, project_overview, module_info, related_files,
|
|
17
|
+
* explore_area, smart_log, smart_diff.
|
|
18
|
+
* - edit : nav + batch reads + everything Edit needs to hit a symbol
|
|
19
|
+
* precisely. Adds read_symbols, read_range, read_section,
|
|
20
|
+
* read_diff, read_for_edit, smart_read_many.
|
|
21
|
+
*
|
|
22
|
+
* Selection: TOKEN_PILOT_PROFILE=nav|edit|full env var. Unknown values
|
|
23
|
+
* fall back to full with a stderr warning. Silent on missing env.
|
|
24
|
+
*/
|
|
25
|
+
export type ToolProfile = "full" | "nav" | "edit";
|
|
26
|
+
export declare const PROFILE_NAMES: readonly ToolProfile[];
|
|
27
|
+
/** Minimum nav profile — exploration only, no editing support. */
|
|
28
|
+
export declare const NAV_TOOLS: ReadonlySet<string>;
|
|
29
|
+
/** Edit profile adds batch reads + edit-preparation tools. */
|
|
30
|
+
export declare const EDIT_EXTRAS: ReadonlySet<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Decide which tools the LLM sees in tools/list given a profile.
|
|
33
|
+
* Pure — safe to unit-test without spinning up the server.
|
|
34
|
+
*
|
|
35
|
+
* Tool names NOT matched by any profile rule (e.g. future additions)
|
|
36
|
+
* fall into 'full' only, to stay conservative by default.
|
|
37
|
+
*/
|
|
38
|
+
export declare function filterToolsByProfile<T extends {
|
|
39
|
+
name: string;
|
|
40
|
+
}>(tools: readonly T[], profile: ToolProfile): T[];
|
|
41
|
+
/**
|
|
42
|
+
* Parse the TOKEN_PILOT_PROFILE env value. Unknown values get a warning
|
|
43
|
+
* and fall back to full — we never silently apply a guess.
|
|
44
|
+
*/
|
|
45
|
+
export declare function parseProfileEnv(envValue: string | undefined, warn?: (msg: string) => void): ToolProfile;
|
|
46
|
+
//# sourceMappingURL=tool-profiles.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v0.26.3 — tool profiles.
|
|
3
|
+
*
|
|
4
|
+
* Idea lifted honestly from Token Savior's TOKEN_SAVIOR_PROFILE. When an
|
|
5
|
+
* MCP server advertises 22 tools, every tools/list response costs the
|
|
6
|
+
* agent ~3 k tokens before it does anything. Most sessions don't need
|
|
7
|
+
* every tool — a code-review agent uses smart_read + find_usages +
|
|
8
|
+
* outline and nothing else. A profile lets the user ship a narrower
|
|
9
|
+
* tools/list while keeping the handlers live (so a subagent or another
|
|
10
|
+
* user in the same server can still reach the full set if they know
|
|
11
|
+
* the name).
|
|
12
|
+
*
|
|
13
|
+
* Three profiles:
|
|
14
|
+
* - full (default): everything, same as pre-v0.26.3.
|
|
15
|
+
* - nav : read-only exploration. smart_read, outline, find_usages,
|
|
16
|
+
* read_symbol, project_overview, module_info, related_files,
|
|
17
|
+
* explore_area, smart_log, smart_diff.
|
|
18
|
+
* - edit : nav + batch reads + everything Edit needs to hit a symbol
|
|
19
|
+
* precisely. Adds read_symbols, read_range, read_section,
|
|
20
|
+
* read_diff, read_for_edit, smart_read_many.
|
|
21
|
+
*
|
|
22
|
+
* Selection: TOKEN_PILOT_PROFILE=nav|edit|full env var. Unknown values
|
|
23
|
+
* fall back to full with a stderr warning. Silent on missing env.
|
|
24
|
+
*/
|
|
25
|
+
export const PROFILE_NAMES = [
|
|
26
|
+
"full",
|
|
27
|
+
"nav",
|
|
28
|
+
"edit",
|
|
29
|
+
];
|
|
30
|
+
/** Minimum nav profile — exploration only, no editing support. */
|
|
31
|
+
export const NAV_TOOLS = new Set([
|
|
32
|
+
"smart_read",
|
|
33
|
+
"read_symbol",
|
|
34
|
+
"outline",
|
|
35
|
+
"find_usages",
|
|
36
|
+
"project_overview",
|
|
37
|
+
"module_info",
|
|
38
|
+
"related_files",
|
|
39
|
+
"explore_area",
|
|
40
|
+
"smart_log",
|
|
41
|
+
"smart_diff",
|
|
42
|
+
]);
|
|
43
|
+
/** Edit profile adds batch reads + edit-preparation tools. */
|
|
44
|
+
export const EDIT_EXTRAS = new Set([
|
|
45
|
+
"read_symbols",
|
|
46
|
+
"read_range",
|
|
47
|
+
"read_section",
|
|
48
|
+
"read_diff",
|
|
49
|
+
"read_for_edit",
|
|
50
|
+
"smart_read_many",
|
|
51
|
+
]);
|
|
52
|
+
/**
|
|
53
|
+
* Decide which tools the LLM sees in tools/list given a profile.
|
|
54
|
+
* Pure — safe to unit-test without spinning up the server.
|
|
55
|
+
*
|
|
56
|
+
* Tool names NOT matched by any profile rule (e.g. future additions)
|
|
57
|
+
* fall into 'full' only, to stay conservative by default.
|
|
58
|
+
*/
|
|
59
|
+
export function filterToolsByProfile(tools, profile) {
|
|
60
|
+
if (profile === "full")
|
|
61
|
+
return [...tools];
|
|
62
|
+
if (profile === "nav")
|
|
63
|
+
return tools.filter((t) => NAV_TOOLS.has(t.name));
|
|
64
|
+
// edit = nav + extras
|
|
65
|
+
return tools.filter((t) => NAV_TOOLS.has(t.name) || EDIT_EXTRAS.has(t.name));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Parse the TOKEN_PILOT_PROFILE env value. Unknown values get a warning
|
|
69
|
+
* and fall back to full — we never silently apply a guess.
|
|
70
|
+
*/
|
|
71
|
+
export function parseProfileEnv(envValue, warn = () => { }) {
|
|
72
|
+
if (!envValue)
|
|
73
|
+
return "full";
|
|
74
|
+
const lower = envValue.trim().toLowerCase();
|
|
75
|
+
if (lower === "full" || lower === "nav" || lower === "edit") {
|
|
76
|
+
return lower;
|
|
77
|
+
}
|
|
78
|
+
warn(`[token-pilot] Unknown TOKEN_PILOT_PROFILE="${envValue}". Expected full|nav|edit. Falling back to full.`);
|
|
79
|
+
return "full";
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=tool-profiles.js.map
|
package/dist/server.js
CHANGED
|
@@ -44,7 +44,9 @@ import { handleReadSection } from "./handlers/read-section.js";
|
|
|
44
44
|
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
|
+
import { appendToolCall } from "./core/tool-call-log.js";
|
|
47
48
|
import { MCP_INSTRUCTIONS, TOOL_DEFINITIONS, } from "./server/tool-definitions.js";
|
|
49
|
+
import { filterToolsByProfile, parseProfileEnv, } from "./server/tool-profiles.js";
|
|
48
50
|
import { createTokenEstimates } from "./server/token-estimates.js";
|
|
49
51
|
import { validateSmartReadArgs, validateReadSymbolArgs, validateReadSymbolsArgs, validateReadRangeArgs, validateReadDiffArgs, validateFindUsagesArgs, validateSmartReadManyArgs, validateReadForEditArgs, validateRelatedFilesArgs, validateOutlineArgs, validateFindUnusedArgs, validateCodeAuditArgs, validateProjectOverviewArgs, validateModuleInfoArgs, validateSmartDiffArgs, validateExploreAreaArgs, validateSmartLogArgs, validateTestSummaryArgs, validateReadSectionArgs, } from "./core/validation.js";
|
|
50
52
|
export async function createServer(projectRoot, options) {
|
|
@@ -240,14 +242,33 @@ export async function createServer(projectRoot, options) {
|
|
|
240
242
|
capabilities: { tools: {} },
|
|
241
243
|
instructions: MCP_INSTRUCTIONS,
|
|
242
244
|
});
|
|
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
|
+
const advertisedTools = filterToolsByProfile(TOOL_DEFINITIONS, activeProfile);
|
|
251
|
+
if (activeProfile !== "full") {
|
|
252
|
+
process.stderr.write(`[token-pilot] Profile: ${activeProfile} — advertising ${advertisedTools.length}/${TOOL_DEFINITIONS.length} tools. Unset TOKEN_PILOT_PROFILE for the full set.\n`);
|
|
253
|
+
}
|
|
243
254
|
server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
244
|
-
tools:
|
|
255
|
+
tools: advertisedTools,
|
|
245
256
|
}));
|
|
246
257
|
// Token estimation functions (extracted to server/token-estimates.ts)
|
|
247
258
|
const { fullFileTokens, estimateProjectOverviewWorkflowTokens, estimateOutlineWorkflowTokens, estimateRelatedFilesWorkflowTokens, estimateFindUsagesWorkflowTokens, estimateExploreAreaWorkflowTokens, detectSavingsCategory, } = createTokenEstimates(() => projectRoot, fileCache);
|
|
248
259
|
/** Record analytics with intent classification and decision trace. Returns policy advisory if any. */
|
|
249
260
|
function recordWithTrace(call) {
|
|
250
261
|
const { absPath, args, recentlyEdited, ...rest } = call;
|
|
262
|
+
// v0.26.1 — honest accounting. When a handler signals 'none' as
|
|
263
|
+
// the savings category (e.g. smart_read small-file pass-through),
|
|
264
|
+
// we weren't compressing anything — the caller got the file back
|
|
265
|
+
// verbatim plus a tiny header. Claiming wouldBe = fullFile here
|
|
266
|
+
// produced the -2% "negative savings" line on Opus 4.7's
|
|
267
|
+
// session_analytics. Zero the delta: 0% savings claimed, no ghost
|
|
268
|
+
// overhead.
|
|
269
|
+
if (rest.savingsCategory === "none") {
|
|
270
|
+
rest.tokensWouldBe = rest.tokensReturned;
|
|
271
|
+
}
|
|
251
272
|
analytics.record({
|
|
252
273
|
...rest,
|
|
253
274
|
intent: classifyIntent(rest.tool),
|
|
@@ -279,6 +300,22 @@ export async function createServer(projectRoot, options) {
|
|
|
279
300
|
totalCallCount,
|
|
280
301
|
totalTokensReturned,
|
|
281
302
|
});
|
|
303
|
+
// v0.26.2 — persist for cumulative tool-audit. Fire-and-forget;
|
|
304
|
+
// disk failures must not block the tool-response path. The audit
|
|
305
|
+
// CLI reads all archives + current to build a per-tool savings
|
|
306
|
+
// distribution across sessions, which is the foundation for any
|
|
307
|
+
// future prune/fix decision.
|
|
308
|
+
void appendToolCall(projectRoot, {
|
|
309
|
+
ts: rest.timestamp,
|
|
310
|
+
session_id: call.sessionId ?? "",
|
|
311
|
+
tool: rest.tool,
|
|
312
|
+
path: rest.path,
|
|
313
|
+
tokensReturned: rest.tokensReturned,
|
|
314
|
+
tokensWouldBe: rest.tokensWouldBe,
|
|
315
|
+
savingsCategory: rest.savingsCategory ?? "compression",
|
|
316
|
+
sessionCacheHit: rest.sessionCacheHit,
|
|
317
|
+
delegatedToContextMode: rest.delegatedToContextMode,
|
|
318
|
+
});
|
|
282
319
|
return advisory ? `\n${advisory.message}` : null;
|
|
283
320
|
}
|
|
284
321
|
// Handle tool calls with validated arguments
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "token-pilot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.5",
|
|
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",
|
package/start.sh
CHANGED
|
File without changes
|