token-pilot 0.19.2 → 0.22.2
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/hooks/hooks.json +21 -0
- package/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +129 -0
- package/README.md +172 -315
- package/dist/agents/tp-commit-writer.md +41 -0
- package/dist/agents/tp-dead-code-finder.md +43 -0
- package/dist/agents/tp-debugger.md +45 -0
- package/dist/agents/tp-impact-analyzer.md +44 -0
- package/dist/agents/tp-migration-scout.md +43 -0
- package/dist/agents/tp-onboard.md +40 -0
- package/dist/agents/tp-pr-reviewer.md +41 -0
- package/dist/agents/tp-refactor-planner.md +42 -0
- package/dist/agents/tp-run.md +48 -0
- package/dist/agents/tp-test-triage.md +40 -0
- package/dist/agents/tp-test-writer.md +46 -0
- package/dist/cli/agent-frontmatter.d.ts +48 -0
- package/dist/cli/agent-frontmatter.js +189 -0
- package/dist/cli/bless-agents.d.ts +65 -0
- package/dist/cli/bless-agents.js +307 -0
- package/dist/cli/claudeignore.d.ts +33 -0
- package/dist/cli/claudeignore.js +88 -0
- package/dist/cli/claudemd-hygiene.d.ts +26 -0
- package/dist/cli/claudemd-hygiene.js +43 -0
- package/dist/cli/doctor-drift.d.ts +31 -0
- package/dist/cli/doctor-drift.js +130 -0
- package/dist/cli/doctor-env-check.d.ts +25 -0
- package/dist/cli/doctor-env-check.js +91 -0
- package/dist/cli/install-agents.d.ts +108 -0
- package/dist/cli/install-agents.js +402 -0
- package/dist/cli/save-doc.d.ts +42 -0
- package/dist/cli/save-doc.js +145 -0
- package/dist/cli/scan-agents.d.ts +46 -0
- package/dist/cli/scan-agents.js +227 -0
- package/dist/cli/stats.d.ts +36 -0
- package/dist/cli/stats.js +131 -0
- package/dist/cli/unbless-agents.d.ts +33 -0
- package/dist/cli/unbless-agents.js +85 -0
- package/dist/cli/uninstall-agents.d.ts +36 -0
- package/dist/cli/uninstall-agents.js +117 -0
- package/dist/config/defaults.d.ts +1 -1
- package/dist/config/defaults.js +14 -8
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +105 -11
- package/dist/core/context-registry.d.ts +16 -1
- package/dist/core/context-registry.js +60 -28
- package/dist/core/event-log.d.ts +79 -0
- package/dist/core/event-log.js +190 -0
- package/dist/core/session-registry.d.ts +43 -0
- package/dist/core/session-registry.js +113 -0
- package/dist/core/session-savings.d.ts +19 -0
- package/dist/core/session-savings.js +60 -0
- package/dist/handlers/session-budget.d.ts +32 -0
- package/dist/handlers/session-budget.js +61 -0
- package/dist/handlers/session-snapshot-persist.d.ts +22 -0
- package/dist/handlers/session-snapshot-persist.js +76 -0
- package/dist/hooks/adaptive-threshold.d.ts +27 -0
- package/dist/hooks/adaptive-threshold.js +46 -0
- package/dist/hooks/format-deny-message.d.ts +21 -0
- package/dist/hooks/format-deny-message.js +147 -0
- package/dist/hooks/installer.js +121 -31
- package/dist/hooks/path-safety.d.ts +16 -0
- package/dist/hooks/path-safety.js +34 -0
- package/dist/hooks/post-bash.d.ts +46 -0
- package/dist/hooks/post-bash.js +77 -0
- package/dist/hooks/session-start.d.ts +45 -0
- package/dist/hooks/session-start.js +179 -0
- package/dist/hooks/summary-ast-index.d.ts +28 -0
- package/dist/hooks/summary-ast-index.js +122 -0
- package/dist/hooks/summary-head-tail.d.ts +15 -0
- package/dist/hooks/summary-head-tail.js +78 -0
- package/dist/hooks/summary-pipeline.d.ts +35 -0
- package/dist/hooks/summary-pipeline.js +63 -0
- package/dist/hooks/summary-regex.d.ts +14 -0
- package/dist/hooks/summary-regex.js +130 -0
- package/dist/hooks/summary-types.d.ts +29 -0
- package/dist/hooks/summary-types.js +9 -0
- package/dist/index.d.ts +15 -3
- package/dist/index.js +509 -149
- package/dist/integration/context-mode-detector.d.ts +7 -1
- package/dist/integration/context-mode-detector.js +51 -15
- package/dist/server/tool-definitions.d.ts +149 -0
- package/dist/server/tool-definitions.js +424 -202
- package/dist/server.d.ts +1 -1
- package/dist/server.js +456 -179
- package/dist/templates/agent-builder.d.ts +49 -0
- package/dist/templates/agent-builder.js +104 -0
- package/dist/types.d.ts +38 -4
- package/package.json +4 -2
- package/skills/stats/SKILL.md +13 -2
package/dist/server.js
CHANGED
|
@@ -1,49 +1,52 @@
|
|
|
1
|
-
import { Server } from
|
|
2
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, } from
|
|
3
|
-
import { AstIndexClient } from
|
|
4
|
-
import { FileCache } from
|
|
5
|
-
import { ContextRegistry } from
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { AstIndexClient } from "./ast-index/client.js";
|
|
4
|
+
import { FileCache } from "./core/file-cache.js";
|
|
5
|
+
import { ContextRegistry } from "./core/context-registry.js";
|
|
6
|
+
import { SessionRegistryManager } from "./core/session-registry.js";
|
|
7
|
+
import { SymbolResolver } from "./core/symbol-resolver.js";
|
|
8
|
+
import { SessionAnalytics, } from "./core/session-analytics.js";
|
|
9
|
+
import { classifyIntent } from "./core/intent-classifier.js";
|
|
10
|
+
import { buildDecisionTrace } from "./core/decision-trace.js";
|
|
11
|
+
import { SessionCache } from "./core/session-cache.js";
|
|
12
|
+
import { loadConfig } from "./config/loader.js";
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { dirname, resolve } from "node:path";
|
|
15
|
+
import { execFile } from "node:child_process";
|
|
16
|
+
import { isDangerousRoot } from "./core/validation.js";
|
|
17
|
+
import { promisify } from "node:util";
|
|
18
|
+
import { GitWatcher } from "./git/watcher.js";
|
|
18
19
|
const execFilePromise = promisify(execFile);
|
|
19
|
-
import { FileWatcher } from
|
|
20
|
-
import { handleSmartRead } from
|
|
21
|
-
import { handleReadSymbol } from
|
|
22
|
-
import { handleReadSymbols } from
|
|
23
|
-
import { handleReadRange } from
|
|
24
|
-
import { handleReadDiff } from
|
|
25
|
-
import { handleFindUsages } from
|
|
26
|
-
import { handleSmartReadMany } from
|
|
27
|
-
import { handleProjectOverview } from
|
|
28
|
-
import { handleNonCodeRead, isNonCodeStructured } from
|
|
29
|
-
import { handleFindUnused } from
|
|
30
|
-
import { handleReadForEdit } from
|
|
31
|
-
import { handleRelatedFiles } from
|
|
32
|
-
import { handleOutline } from
|
|
33
|
-
import { handleCodeAudit } from
|
|
34
|
-
import { handleModuleInfo } from
|
|
35
|
-
import { handleSmartDiff } from
|
|
36
|
-
import { handleExploreArea } from
|
|
37
|
-
import { handleSmartLog } from
|
|
38
|
-
import { handleTestSummary } from
|
|
39
|
-
import { handleSessionSnapshot } from
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
20
|
+
import { FileWatcher } from "./git/file-watcher.js";
|
|
21
|
+
import { handleSmartRead } from "./handlers/smart-read.js";
|
|
22
|
+
import { handleReadSymbol } from "./handlers/read-symbol.js";
|
|
23
|
+
import { handleReadSymbols } from "./handlers/read-symbols.js";
|
|
24
|
+
import { handleReadRange } from "./handlers/read-range.js";
|
|
25
|
+
import { handleReadDiff } from "./handlers/read-diff.js";
|
|
26
|
+
import { handleFindUsages } from "./handlers/find-usages.js";
|
|
27
|
+
import { handleSmartReadMany } from "./handlers/smart-read-many.js";
|
|
28
|
+
import { handleProjectOverview } from "./handlers/project-overview.js";
|
|
29
|
+
import { handleNonCodeRead, isNonCodeStructured } from "./handlers/non-code.js";
|
|
30
|
+
import { handleFindUnused } from "./handlers/find-unused.js";
|
|
31
|
+
import { handleReadForEdit } from "./handlers/read-for-edit.js";
|
|
32
|
+
import { handleRelatedFiles } from "./handlers/related-files.js";
|
|
33
|
+
import { handleOutline } from "./handlers/outline.js";
|
|
34
|
+
import { handleCodeAudit } from "./handlers/code-audit.js";
|
|
35
|
+
import { handleModuleInfo } from "./handlers/module-info.js";
|
|
36
|
+
import { handleSmartDiff } from "./handlers/smart-diff.js";
|
|
37
|
+
import { handleExploreArea } from "./handlers/explore-area.js";
|
|
38
|
+
import { handleSmartLog } from "./handlers/smart-log.js";
|
|
39
|
+
import { handleTestSummary } from "./handlers/test-summary.js";
|
|
40
|
+
import { handleSessionSnapshot } from "./handlers/session-snapshot.js";
|
|
41
|
+
import { persistSnapshot } from "./handlers/session-snapshot-persist.js";
|
|
42
|
+
import { handleSessionBudget } from "./handlers/session-budget.js";
|
|
43
|
+
import { handleReadSection } from "./handlers/read-section.js";
|
|
44
|
+
import { detectContextMode } from "./integration/context-mode-detector.js";
|
|
45
|
+
import { estimateTokens } from "./core/token-estimator.js";
|
|
46
|
+
import { checkPolicy, isFullReadTool } from "./core/policy-engine.js";
|
|
47
|
+
import { MCP_INSTRUCTIONS, TOOL_DEFINITIONS, } from "./server/tool-definitions.js";
|
|
48
|
+
import { createTokenEstimates } from "./server/token-estimates.js";
|
|
49
|
+
import { validateSmartReadArgs, validateReadSymbolArgs, validateReadSymbolsArgs, validateReadRangeArgs, validateReadDiffArgs, validateFindUsagesArgs, validateSmartReadManyArgs, validateReadForEditArgs, validateRelatedFilesArgs, validateOutlineArgs, validateFindUnusedArgs, validateCodeAuditArgs, validateProjectOverviewArgs, validateModuleInfoArgs, validateSmartDiffArgs, validateExploreAreaArgs, validateSmartLogArgs, validateTestSummaryArgs, validateReadSectionArgs, } from "./core/validation.js";
|
|
47
50
|
export async function createServer(projectRoot, options) {
|
|
48
51
|
const config = await loadConfig(projectRoot);
|
|
49
52
|
const astIndex = new AstIndexClient(projectRoot, config.astIndex.timeout, {
|
|
@@ -52,7 +55,38 @@ export async function createServer(projectRoot, options) {
|
|
|
52
55
|
});
|
|
53
56
|
const fileCache = new FileCache(config.cache.maxSizeMB, config.smartRead.smallFileThreshold);
|
|
54
57
|
const contextRegistry = new ContextRegistry();
|
|
58
|
+
const sessionRegistries = new SessionRegistryManager(projectRoot);
|
|
59
|
+
// Flush persisted session registries on shutdown (best-effort; every hot
|
|
60
|
+
// tool-call path also flushes immediately, so this is only for registries
|
|
61
|
+
// whose last access never got a post-call flush). `beforeExit` doesn't
|
|
62
|
+
// fire on signal-based termination (SIGINT / SIGTERM), so we hook those
|
|
63
|
+
// too — Node runs every signal listener before the default action, giving
|
|
64
|
+
// flushAll a fair chance to complete. `process.exit()` bypasses listeners
|
|
65
|
+
// entirely; callers that care about durability should not use it.
|
|
66
|
+
const shutdownFlush = () => {
|
|
67
|
+
void sessionRegistries.flushAll();
|
|
68
|
+
};
|
|
69
|
+
process.once("beforeExit", shutdownFlush);
|
|
70
|
+
process.once("SIGINT", shutdownFlush);
|
|
71
|
+
process.once("SIGTERM", shutdownFlush);
|
|
55
72
|
const symbolResolver = new SymbolResolver(astIndex);
|
|
73
|
+
/**
|
|
74
|
+
* TP-69m — pick the right ContextRegistry for this tool call.
|
|
75
|
+
* - force:true → empty registry (agent wants to bypass dedup)
|
|
76
|
+
* - session_id present → per-session, disk-backed registry
|
|
77
|
+
* - neither → process-default (legacy behaviour for callers that
|
|
78
|
+
* don't yet know their session_id)
|
|
79
|
+
*/
|
|
80
|
+
function pickRegistry(rawArgs) {
|
|
81
|
+
const a = (rawArgs ?? {});
|
|
82
|
+
const force = a.force === true;
|
|
83
|
+
const sessionId = typeof a.session_id === "string" ? a.session_id : "";
|
|
84
|
+
if (force)
|
|
85
|
+
return { reg: new ContextRegistry(), sessionId, force: true };
|
|
86
|
+
if (sessionId)
|
|
87
|
+
return { reg: sessionRegistries.getFor(sessionId), sessionId, force };
|
|
88
|
+
return { reg: contextRegistry, sessionId, force };
|
|
89
|
+
}
|
|
56
90
|
// Try to init ast-index (non-fatal if not available)
|
|
57
91
|
const needsAutoDetect = !!options?.skipAstIndex;
|
|
58
92
|
try {
|
|
@@ -61,7 +95,7 @@ export async function createServer(projectRoot, options) {
|
|
|
61
95
|
// Dangerous root (/, home dir) — don't build index yet
|
|
62
96
|
// Will auto-detect real project root from first file path
|
|
63
97
|
astIndex.disableIndex();
|
|
64
|
-
console.error(
|
|
98
|
+
console.error("[token-pilot] ast-index: waiting for first file path to auto-detect project root");
|
|
65
99
|
}
|
|
66
100
|
else if (config.astIndex.buildOnStart) {
|
|
67
101
|
await astIndex.ensureIndex();
|
|
@@ -96,10 +130,10 @@ export async function createServer(projectRoot, options) {
|
|
|
96
130
|
if (caps?.roots) {
|
|
97
131
|
const { roots } = await server.listRoots();
|
|
98
132
|
for (const root of roots) {
|
|
99
|
-
if (root.uri.startsWith(
|
|
133
|
+
if (root.uri.startsWith("file://")) {
|
|
100
134
|
const rootPath = decodeURIComponent(new URL(root.uri).pathname);
|
|
101
135
|
if (rootPath && !isDangerousRoot(rootPath)) {
|
|
102
|
-
await applyDetectedRoot(rootPath,
|
|
136
|
+
await applyDetectedRoot(rootPath, "MCP roots");
|
|
103
137
|
return;
|
|
104
138
|
}
|
|
105
139
|
}
|
|
@@ -113,7 +147,7 @@ export async function createServer(projectRoot, options) {
|
|
|
113
147
|
if (filePath) {
|
|
114
148
|
const dir = dirname(filePath);
|
|
115
149
|
try {
|
|
116
|
-
const { stdout } = await execFilePromise(
|
|
150
|
+
const { stdout } = await execFilePromise("git", ["rev-parse", "--show-toplevel"], {
|
|
117
151
|
cwd: dir,
|
|
118
152
|
timeout: 3000,
|
|
119
153
|
});
|
|
@@ -133,16 +167,16 @@ export async function createServer(projectRoot, options) {
|
|
|
133
167
|
*/
|
|
134
168
|
function extractFilePath(toolArgs) {
|
|
135
169
|
const path = toolArgs?.path;
|
|
136
|
-
if (path && typeof path ===
|
|
170
|
+
if (path && typeof path === "string" && path.startsWith("/"))
|
|
137
171
|
return path;
|
|
138
172
|
const paths = toolArgs?.paths;
|
|
139
|
-
if (paths?.[0] && typeof paths[0] ===
|
|
173
|
+
if (paths?.[0] && typeof paths[0] === "string" && paths[0].startsWith("/"))
|
|
140
174
|
return paths[0];
|
|
141
175
|
const file = toolArgs?.file;
|
|
142
|
-
if (file && typeof file ===
|
|
176
|
+
if (file && typeof file === "string" && file.startsWith("/"))
|
|
143
177
|
return file;
|
|
144
178
|
const mod = toolArgs?.module;
|
|
145
|
-
if (mod && typeof mod ===
|
|
179
|
+
if (mod && typeof mod === "string" && mod.startsWith("/"))
|
|
146
180
|
return mod;
|
|
147
181
|
return undefined;
|
|
148
182
|
}
|
|
@@ -160,7 +194,7 @@ export async function createServer(projectRoot, options) {
|
|
|
160
194
|
const readForEditCalled = new Set();
|
|
161
195
|
// Detect context-mode companion
|
|
162
196
|
const cmEnabled = config.contextMode.enabled;
|
|
163
|
-
const contextModeStatus = await detectContextMode(projectRoot, cmEnabled ===
|
|
197
|
+
const contextModeStatus = await detectContextMode(projectRoot, cmEnabled === "auto" ? undefined : cmEnabled);
|
|
164
198
|
if (contextModeStatus.detected) {
|
|
165
199
|
console.error(`[token-pilot] context-mode detected (source: ${contextModeStatus.source})`);
|
|
166
200
|
}
|
|
@@ -193,14 +227,16 @@ export async function createServer(projectRoot, options) {
|
|
|
193
227
|
});
|
|
194
228
|
}
|
|
195
229
|
// Read version from package.json
|
|
196
|
-
let pkgVersion =
|
|
230
|
+
let pkgVersion = "0.1.1";
|
|
197
231
|
try {
|
|
198
|
-
const pkgPath = new URL(
|
|
199
|
-
const pkg = JSON.parse(readFileSync(pkgPath,
|
|
232
|
+
const pkgPath = new URL("../package.json", import.meta.url).pathname;
|
|
233
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
200
234
|
pkgVersion = pkg.version;
|
|
201
235
|
}
|
|
202
|
-
catch {
|
|
203
|
-
|
|
236
|
+
catch {
|
|
237
|
+
/* fallback to hardcoded */
|
|
238
|
+
}
|
|
239
|
+
const server = new Server({ name: "token-pilot", version: pkgVersion }, {
|
|
204
240
|
capabilities: { tools: {} },
|
|
205
241
|
instructions: MCP_INSTRUCTIONS,
|
|
206
242
|
});
|
|
@@ -232,7 +268,7 @@ export async function createServer(projectRoot, options) {
|
|
|
232
268
|
if (isFullReadTool(rest.tool)) {
|
|
233
269
|
fullFileReadsCount++;
|
|
234
270
|
}
|
|
235
|
-
if (rest.tool ===
|
|
271
|
+
if (rest.tool === "read_for_edit" && call.path) {
|
|
236
272
|
readForEditCalled.add(call.path);
|
|
237
273
|
}
|
|
238
274
|
// Policy check
|
|
@@ -251,30 +287,32 @@ export async function createServer(projectRoot, options) {
|
|
|
251
287
|
// Auto-detect project root on first tool call (when startup root was /)
|
|
252
288
|
// Tries: MCP roots → git detect from file path in args
|
|
253
289
|
if (needsAutoDetect && !autoDetectDone) {
|
|
254
|
-
const detectedPath = extractFilePath(
|
|
290
|
+
const detectedPath = extractFilePath(args ?? {});
|
|
255
291
|
await tryAutoDetectRoot(detectedPath);
|
|
256
292
|
}
|
|
257
293
|
try {
|
|
258
294
|
switch (name) {
|
|
259
|
-
case
|
|
295
|
+
case "smart_read": {
|
|
260
296
|
const validArgs = validateSmartReadArgs(args);
|
|
297
|
+
const picked = pickRegistry(args);
|
|
261
298
|
// Try non-code handler for JSON/YAML/MD etc.
|
|
262
299
|
if (isNonCodeStructured(validArgs.path)) {
|
|
263
|
-
const nonCodeResult = await handleNonCodeRead(validArgs.path, projectRoot,
|
|
300
|
+
const nonCodeResult = await handleNonCodeRead(validArgs.path, projectRoot, picked.reg, {
|
|
264
301
|
contextModeStatus,
|
|
265
302
|
largeNonCodeThreshold: config.contextMode.largeNonCodeThreshold,
|
|
266
303
|
adviseDelegation: config.contextMode.adviseDelegation,
|
|
267
304
|
});
|
|
268
305
|
if (nonCodeResult) {
|
|
269
|
-
const text = nonCodeResult.content[0]?.text ??
|
|
306
|
+
const text = nonCodeResult.content[0]?.text ?? "";
|
|
270
307
|
recordWithTrace({
|
|
271
|
-
tool:
|
|
308
|
+
tool: "smart_read",
|
|
272
309
|
path: validArgs.path,
|
|
273
310
|
tokensReturned: estimateTokens(text),
|
|
274
|
-
tokensWouldBe: await fullFileTokens(validArgs.path) ||
|
|
311
|
+
tokensWouldBe: (await fullFileTokens(validArgs.path)) ||
|
|
312
|
+
estimateTokens(text),
|
|
275
313
|
timestamp: Date.now(),
|
|
276
|
-
delegatedToContextMode: text.includes(
|
|
277
|
-
savingsCategory:
|
|
314
|
+
delegatedToContextMode: text.includes("ADVISORY:") && text.includes("context-mode"),
|
|
315
|
+
savingsCategory: "compression",
|
|
278
316
|
absPath: resolve(projectRoot, validArgs.path),
|
|
279
317
|
args: validArgs,
|
|
280
318
|
});
|
|
@@ -282,78 +320,151 @@ export async function createServer(projectRoot, options) {
|
|
|
282
320
|
}
|
|
283
321
|
}
|
|
284
322
|
// Dedup is handled inside handleSmartRead (step 5)
|
|
285
|
-
const result = await handleSmartRead(validArgs, projectRoot, astIndex, fileCache,
|
|
286
|
-
|
|
323
|
+
const result = await handleSmartRead(validArgs, projectRoot, astIndex, fileCache, picked.reg, config);
|
|
324
|
+
if (picked.sessionId && !picked.force) {
|
|
325
|
+
void sessionRegistries.flush(picked.sessionId);
|
|
326
|
+
}
|
|
327
|
+
const text = result.content[0]?.text ?? "";
|
|
287
328
|
const fullTokensSR = await fullFileTokens(validArgs.path);
|
|
288
|
-
const policyAdv = recordWithTrace({
|
|
329
|
+
const policyAdv = recordWithTrace({
|
|
330
|
+
tool: "smart_read",
|
|
331
|
+
path: validArgs.path,
|
|
332
|
+
tokensReturned: estimateTokens(text),
|
|
333
|
+
tokensWouldBe: fullTokensSR || estimateTokens(text),
|
|
334
|
+
timestamp: Date.now(),
|
|
335
|
+
savingsCategory: detectSavingsCategory(text),
|
|
336
|
+
absPath: resolve(projectRoot, validArgs.path),
|
|
337
|
+
args: validArgs,
|
|
338
|
+
});
|
|
289
339
|
if (policyAdv)
|
|
290
|
-
result.content[0] = { type:
|
|
340
|
+
result.content[0] = { type: "text", text: text + policyAdv };
|
|
291
341
|
return result;
|
|
292
342
|
}
|
|
293
|
-
case
|
|
343
|
+
case "read_symbol": {
|
|
294
344
|
const symArgs = validateReadSymbolArgs(args);
|
|
345
|
+
const pickedSym = pickRegistry(args);
|
|
295
346
|
// Dedup is handled inside handleReadSymbol
|
|
296
|
-
const symResult = await handleReadSymbol(symArgs, projectRoot, symbolResolver, fileCache,
|
|
297
|
-
|
|
347
|
+
const symResult = await handleReadSymbol(symArgs, projectRoot, symbolResolver, fileCache, pickedSym.reg, astIndex, config.smartRead.advisoryReminders);
|
|
348
|
+
if (pickedSym.sessionId && !pickedSym.force) {
|
|
349
|
+
void sessionRegistries.flush(pickedSym.sessionId);
|
|
350
|
+
}
|
|
351
|
+
const symText = symResult.content[0]?.text ?? "";
|
|
298
352
|
const symTokens = estimateTokens(symText);
|
|
299
353
|
const fullTokensSym = await fullFileTokens(symArgs.path);
|
|
300
|
-
recordWithTrace({
|
|
354
|
+
recordWithTrace({
|
|
355
|
+
tool: "read_symbol",
|
|
356
|
+
path: symArgs.path,
|
|
357
|
+
tokensReturned: symTokens,
|
|
358
|
+
tokensWouldBe: fullTokensSym || symTokens,
|
|
359
|
+
timestamp: Date.now(),
|
|
360
|
+
savingsCategory: detectSavingsCategory(symText),
|
|
361
|
+
absPath: resolve(projectRoot, symArgs.path),
|
|
362
|
+
args: symArgs,
|
|
363
|
+
});
|
|
301
364
|
return symResult;
|
|
302
365
|
}
|
|
303
|
-
case
|
|
366
|
+
case "read_symbols": {
|
|
304
367
|
const rsArgs = validateReadSymbolsArgs(args);
|
|
305
368
|
const rsResult = await handleReadSymbols(rsArgs, projectRoot, symbolResolver, fileCache, contextRegistry, astIndex, config.smartRead.advisoryReminders);
|
|
306
|
-
const rsText = rsResult.content[0]?.text ??
|
|
369
|
+
const rsText = rsResult.content[0]?.text ?? "";
|
|
307
370
|
const rsTokens = estimateTokens(rsText);
|
|
308
371
|
const fullTokensRs = await fullFileTokens(rsArgs.path);
|
|
309
|
-
recordWithTrace({
|
|
372
|
+
recordWithTrace({
|
|
373
|
+
tool: "read_symbols",
|
|
374
|
+
path: rsArgs.path,
|
|
375
|
+
tokensReturned: rsTokens,
|
|
376
|
+
tokensWouldBe: fullTokensRs || rsTokens,
|
|
377
|
+
timestamp: Date.now(),
|
|
378
|
+
savingsCategory: "compression",
|
|
379
|
+
absPath: resolve(projectRoot, rsArgs.path),
|
|
380
|
+
args: rsArgs,
|
|
381
|
+
});
|
|
310
382
|
return rsResult;
|
|
311
383
|
}
|
|
312
|
-
case
|
|
384
|
+
case "read_range": {
|
|
313
385
|
const rangeArgs = validateReadRangeArgs(args);
|
|
314
|
-
const
|
|
315
|
-
const
|
|
386
|
+
const pickedRange = pickRegistry(args);
|
|
387
|
+
const rangeResult = await handleReadRange(rangeArgs, projectRoot, fileCache, pickedRange.reg, config.smartRead.advisoryReminders);
|
|
388
|
+
if (pickedRange.sessionId && !pickedRange.force) {
|
|
389
|
+
void sessionRegistries.flush(pickedRange.sessionId);
|
|
390
|
+
}
|
|
391
|
+
const rangeText = rangeResult.content[0]?.text ?? "";
|
|
316
392
|
const rangeTokens = estimateTokens(rangeText);
|
|
317
393
|
const fullTokensRange = await fullFileTokens(rangeArgs.path);
|
|
318
|
-
recordWithTrace({
|
|
394
|
+
recordWithTrace({
|
|
395
|
+
tool: "read_range",
|
|
396
|
+
path: rangeArgs.path,
|
|
397
|
+
tokensReturned: rangeTokens,
|
|
398
|
+
tokensWouldBe: fullTokensRange || rangeTokens,
|
|
399
|
+
timestamp: Date.now(),
|
|
400
|
+
savingsCategory: detectSavingsCategory(rangeText),
|
|
401
|
+
absPath: resolve(projectRoot, rangeArgs.path),
|
|
402
|
+
args: rangeArgs,
|
|
403
|
+
});
|
|
319
404
|
return rangeResult;
|
|
320
405
|
}
|
|
321
|
-
case
|
|
406
|
+
case "read_section": {
|
|
322
407
|
const secArgs = validateReadSectionArgs(args);
|
|
323
408
|
const secResult = await handleReadSection(secArgs, projectRoot, contextRegistry);
|
|
324
|
-
const secText = secResult.content[0]?.text ??
|
|
409
|
+
const secText = secResult.content[0]?.text ?? "";
|
|
325
410
|
const secTokens = estimateTokens(secText);
|
|
326
411
|
const fullTokensSec = await fullFileTokens(secArgs.path);
|
|
327
412
|
recordWithTrace({
|
|
328
|
-
tool:
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
413
|
+
tool: "read_section",
|
|
414
|
+
path: secArgs.path,
|
|
415
|
+
tokensReturned: secTokens,
|
|
416
|
+
tokensWouldBe: fullTokensSec || secTokens,
|
|
417
|
+
timestamp: Date.now(),
|
|
418
|
+
savingsCategory: "compression",
|
|
419
|
+
absPath: resolve(projectRoot, secArgs.path),
|
|
420
|
+
args: secArgs,
|
|
332
421
|
});
|
|
333
422
|
return secResult;
|
|
334
423
|
}
|
|
335
|
-
case
|
|
424
|
+
case "read_diff": {
|
|
336
425
|
const diffArgs = validateReadDiffArgs(args);
|
|
337
426
|
const diffResult = await handleReadDiff(diffArgs, projectRoot, fileCache, contextRegistry);
|
|
338
|
-
const diffText = diffResult.content[0]?.text ??
|
|
427
|
+
const diffText = diffResult.content[0]?.text ?? "";
|
|
339
428
|
const diffTokens = estimateTokens(diffText);
|
|
340
429
|
const fullTokensDiff = await fullFileTokens(diffArgs.path);
|
|
341
|
-
recordWithTrace({
|
|
430
|
+
recordWithTrace({
|
|
431
|
+
tool: "read_diff",
|
|
432
|
+
path: diffArgs.path,
|
|
433
|
+
tokensReturned: diffTokens,
|
|
434
|
+
tokensWouldBe: fullTokensDiff || diffTokens,
|
|
435
|
+
timestamp: Date.now(),
|
|
436
|
+
savingsCategory: "compression",
|
|
437
|
+
absPath: resolve(projectRoot, diffArgs.path),
|
|
438
|
+
args: diffArgs,
|
|
439
|
+
});
|
|
342
440
|
return diffResult;
|
|
343
441
|
}
|
|
344
|
-
case
|
|
442
|
+
case "read_for_edit": {
|
|
345
443
|
const editArgs = validateReadForEditArgs(args);
|
|
346
444
|
const editResult = await handleReadForEdit(editArgs, projectRoot, symbolResolver, fileCache, contextRegistry, astIndex, { actionableHints: config.display.actionableHints });
|
|
347
|
-
const editText = editResult.content[0]?.text ??
|
|
445
|
+
const editText = editResult.content[0]?.text ?? "";
|
|
348
446
|
const editTokens = estimateTokens(editText);
|
|
349
447
|
const fullTokensEdit = await fullFileTokens(editArgs.path);
|
|
350
|
-
recordWithTrace({
|
|
448
|
+
recordWithTrace({
|
|
449
|
+
tool: "read_for_edit",
|
|
450
|
+
path: editArgs.path,
|
|
451
|
+
tokensReturned: editTokens,
|
|
452
|
+
tokensWouldBe: fullTokensEdit || editTokens,
|
|
453
|
+
timestamp: Date.now(),
|
|
454
|
+
savingsCategory: "compression",
|
|
455
|
+
absPath: resolve(projectRoot, editArgs.path),
|
|
456
|
+
args: editArgs,
|
|
457
|
+
});
|
|
351
458
|
return editResult;
|
|
352
459
|
}
|
|
353
|
-
case
|
|
460
|
+
case "smart_read_many": {
|
|
354
461
|
const manyArgs = validateSmartReadManyArgs(args);
|
|
355
|
-
const
|
|
356
|
-
const
|
|
462
|
+
const pickedMany = pickRegistry(args);
|
|
463
|
+
const manyResult = await handleSmartReadMany(manyArgs, projectRoot, astIndex, fileCache, pickedMany.reg, config);
|
|
464
|
+
if (pickedMany.sessionId && !pickedMany.force) {
|
|
465
|
+
void sessionRegistries.flush(pickedMany.sessionId);
|
|
466
|
+
}
|
|
467
|
+
const manyText = manyResult.content[0]?.text ?? "";
|
|
357
468
|
const manyTokens = estimateTokens(manyText);
|
|
358
469
|
const uniqueManyPaths = Array.from(new Set(manyArgs.paths));
|
|
359
470
|
let fullTokensMany = 0;
|
|
@@ -361,241 +472,407 @@ export async function createServer(projectRoot, options) {
|
|
|
361
472
|
fullTokensMany += await fullFileTokens(p);
|
|
362
473
|
}
|
|
363
474
|
recordWithTrace({
|
|
364
|
-
tool:
|
|
365
|
-
path: uniqueManyPaths.join(
|
|
475
|
+
tool: "smart_read_many",
|
|
476
|
+
path: uniqueManyPaths.join(", "),
|
|
366
477
|
tokensReturned: manyTokens,
|
|
367
478
|
tokensWouldBe: fullTokensMany || manyTokens,
|
|
368
479
|
timestamp: Date.now(),
|
|
369
|
-
savingsCategory:
|
|
480
|
+
savingsCategory: "compression",
|
|
370
481
|
args: manyArgs,
|
|
371
482
|
});
|
|
372
483
|
return manyResult;
|
|
373
484
|
}
|
|
374
|
-
case
|
|
485
|
+
case "find_usages": {
|
|
375
486
|
const usagesArgs = validateFindUsagesArgs(args);
|
|
376
|
-
const cachedUsages = sessionCache?.get(
|
|
487
|
+
const cachedUsages = sessionCache?.get("find_usages", usagesArgs);
|
|
377
488
|
if (cachedUsages) {
|
|
378
|
-
recordWithTrace({
|
|
489
|
+
recordWithTrace({
|
|
490
|
+
tool: "find_usages",
|
|
491
|
+
path: usagesArgs.symbol,
|
|
492
|
+
tokensReturned: cachedUsages.tokenEstimate,
|
|
493
|
+
tokensWouldBe: cachedUsages.tokensWouldBe ?? cachedUsages.tokenEstimate,
|
|
494
|
+
timestamp: Date.now(),
|
|
495
|
+
sessionCacheHit: true,
|
|
496
|
+
savingsCategory: "cache",
|
|
497
|
+
args: usagesArgs,
|
|
498
|
+
});
|
|
379
499
|
return cachedUsages.result;
|
|
380
500
|
}
|
|
381
501
|
const usagesResult = await handleFindUsages(usagesArgs, astIndex, projectRoot);
|
|
382
|
-
const usagesText = usagesResult.content[0]?.text ??
|
|
502
|
+
const usagesText = usagesResult.content[0]?.text ?? "";
|
|
383
503
|
const usagesTokens = estimateTokens(usagesText);
|
|
384
504
|
const usagesWouldBe = await estimateFindUsagesWorkflowTokens(usagesResult.meta.files);
|
|
385
|
-
sessionCache?.set(
|
|
386
|
-
files: usagesResult.meta.files.map(f => resolve(projectRoot, f)),
|
|
505
|
+
sessionCache?.set("find_usages", usagesArgs, usagesResult, {
|
|
506
|
+
files: usagesResult.meta.files.map((f) => resolve(projectRoot, f)),
|
|
387
507
|
dependsOnAst: true,
|
|
388
508
|
}, usagesTokens, usagesWouldBe || usagesTokens);
|
|
389
509
|
recordWithTrace({
|
|
390
|
-
tool:
|
|
510
|
+
tool: "find_usages",
|
|
391
511
|
path: usagesArgs.symbol,
|
|
392
512
|
tokensReturned: usagesTokens,
|
|
393
513
|
tokensWouldBe: usagesWouldBe || usagesTokens,
|
|
394
514
|
timestamp: Date.now(),
|
|
395
|
-
savingsCategory:
|
|
515
|
+
savingsCategory: "compression",
|
|
396
516
|
args: usagesArgs,
|
|
397
517
|
});
|
|
398
518
|
return usagesResult;
|
|
399
519
|
}
|
|
400
|
-
case
|
|
520
|
+
case "project_overview": {
|
|
401
521
|
const overviewArgs = validateProjectOverviewArgs(args);
|
|
402
|
-
const cachedOverview = sessionCache?.get(
|
|
522
|
+
const cachedOverview = sessionCache?.get("project_overview", overviewArgs);
|
|
403
523
|
if (cachedOverview) {
|
|
404
|
-
recordWithTrace({
|
|
524
|
+
recordWithTrace({
|
|
525
|
+
tool: "project_overview",
|
|
526
|
+
path: projectRoot,
|
|
527
|
+
tokensReturned: cachedOverview.tokenEstimate,
|
|
528
|
+
tokensWouldBe: cachedOverview.tokensWouldBe ?? cachedOverview.tokenEstimate,
|
|
529
|
+
timestamp: Date.now(),
|
|
530
|
+
sessionCacheHit: true,
|
|
531
|
+
savingsCategory: "cache",
|
|
532
|
+
args: overviewArgs,
|
|
533
|
+
});
|
|
405
534
|
return cachedOverview.result;
|
|
406
535
|
}
|
|
407
536
|
const overviewResult = await handleProjectOverview(overviewArgs, projectRoot, astIndex, pkgVersion);
|
|
408
|
-
const overviewText = overviewResult.content[0]?.text ??
|
|
409
|
-
overviewResult.content[0] = {
|
|
537
|
+
const overviewText = overviewResult.content[0]?.text ?? "";
|
|
538
|
+
overviewResult.content[0] = {
|
|
539
|
+
type: "text",
|
|
540
|
+
text: `TOKEN PILOT v${pkgVersion}\n\n${overviewText}`,
|
|
541
|
+
};
|
|
410
542
|
const ovTokens = estimateTokens(overviewResult.content[0].text);
|
|
411
|
-
const overviewWouldBe = await estimateProjectOverviewWorkflowTokens(overviewArgs.include ?? [
|
|
412
|
-
sessionCache?.set(
|
|
543
|
+
const overviewWouldBe = await estimateProjectOverviewWorkflowTokens(overviewArgs.include ?? ["stack", "ci", "quality", "architecture"]);
|
|
544
|
+
sessionCache?.set("project_overview", overviewArgs, overviewResult, {
|
|
413
545
|
dependsOnAst: true,
|
|
414
546
|
}, ovTokens, overviewWouldBe || ovTokens);
|
|
415
547
|
recordWithTrace({
|
|
416
|
-
tool:
|
|
548
|
+
tool: "project_overview",
|
|
417
549
|
path: projectRoot,
|
|
418
550
|
tokensReturned: ovTokens,
|
|
419
551
|
tokensWouldBe: overviewWouldBe || ovTokens,
|
|
420
552
|
timestamp: Date.now(),
|
|
421
|
-
savingsCategory:
|
|
553
|
+
savingsCategory: "compression",
|
|
422
554
|
args: overviewArgs,
|
|
423
555
|
});
|
|
424
556
|
return overviewResult;
|
|
425
557
|
}
|
|
426
|
-
case
|
|
558
|
+
case "related_files": {
|
|
427
559
|
const relArgs = validateRelatedFilesArgs(args);
|
|
428
|
-
const cachedRel = sessionCache?.get(
|
|
560
|
+
const cachedRel = sessionCache?.get("related_files", relArgs);
|
|
429
561
|
if (cachedRel) {
|
|
430
|
-
recordWithTrace({
|
|
562
|
+
recordWithTrace({
|
|
563
|
+
tool: "related_files",
|
|
564
|
+
path: relArgs.path,
|
|
565
|
+
tokensReturned: cachedRel.tokenEstimate,
|
|
566
|
+
tokensWouldBe: cachedRel.tokensWouldBe ?? cachedRel.tokenEstimate,
|
|
567
|
+
timestamp: Date.now(),
|
|
568
|
+
sessionCacheHit: true,
|
|
569
|
+
savingsCategory: "cache",
|
|
570
|
+
absPath: resolve(projectRoot, relArgs.path),
|
|
571
|
+
args: relArgs,
|
|
572
|
+
});
|
|
431
573
|
return cachedRel.result;
|
|
432
574
|
}
|
|
433
575
|
const relResult = await handleRelatedFiles(relArgs, projectRoot, astIndex);
|
|
434
|
-
const relText = relResult.content[0]?.text ??
|
|
576
|
+
const relText = relResult.content[0]?.text ?? "";
|
|
435
577
|
const relTokens = estimateTokens(relText);
|
|
436
578
|
const relWouldBe = await estimateRelatedFilesWorkflowTokens(relArgs.path, relResult.meta);
|
|
437
579
|
const relDeps = [
|
|
438
580
|
resolve(projectRoot, relArgs.path),
|
|
439
|
-
...relResult.meta.imports.map(f => resolve(projectRoot, f)),
|
|
440
|
-
...relResult.meta.importedBy.map(f => resolve(projectRoot, f)),
|
|
441
|
-
...relResult.meta.tests.map(f => resolve(projectRoot, f)),
|
|
581
|
+
...relResult.meta.imports.map((f) => resolve(projectRoot, f)),
|
|
582
|
+
...relResult.meta.importedBy.map((f) => resolve(projectRoot, f)),
|
|
583
|
+
...relResult.meta.tests.map((f) => resolve(projectRoot, f)),
|
|
442
584
|
];
|
|
443
|
-
sessionCache?.set(
|
|
585
|
+
sessionCache?.set("related_files", relArgs, relResult, {
|
|
444
586
|
files: relDeps,
|
|
445
587
|
dependsOnAst: true,
|
|
446
588
|
}, relTokens, relWouldBe || relTokens);
|
|
447
589
|
recordWithTrace({
|
|
448
|
-
tool:
|
|
590
|
+
tool: "related_files",
|
|
449
591
|
path: relArgs.path,
|
|
450
592
|
tokensReturned: relTokens,
|
|
451
593
|
tokensWouldBe: relWouldBe || relTokens,
|
|
452
594
|
timestamp: Date.now(),
|
|
453
|
-
savingsCategory:
|
|
595
|
+
savingsCategory: "compression",
|
|
454
596
|
absPath: resolve(projectRoot, relArgs.path),
|
|
455
597
|
args: relArgs,
|
|
456
598
|
});
|
|
457
599
|
return relResult;
|
|
458
600
|
}
|
|
459
|
-
case
|
|
601
|
+
case "outline": {
|
|
460
602
|
const outlineArgs = validateOutlineArgs(args);
|
|
461
|
-
const cachedOutline = sessionCache?.get(
|
|
603
|
+
const cachedOutline = sessionCache?.get("outline", outlineArgs);
|
|
462
604
|
if (cachedOutline) {
|
|
463
|
-
recordWithTrace({
|
|
605
|
+
recordWithTrace({
|
|
606
|
+
tool: "outline",
|
|
607
|
+
path: outlineArgs.path,
|
|
608
|
+
tokensReturned: cachedOutline.tokenEstimate,
|
|
609
|
+
tokensWouldBe: cachedOutline.tokensWouldBe ?? cachedOutline.tokenEstimate,
|
|
610
|
+
timestamp: Date.now(),
|
|
611
|
+
sessionCacheHit: true,
|
|
612
|
+
savingsCategory: "cache",
|
|
613
|
+
args: outlineArgs,
|
|
614
|
+
});
|
|
464
615
|
return cachedOutline.result;
|
|
465
616
|
}
|
|
466
617
|
const outlineResult = await handleOutline(outlineArgs, projectRoot, astIndex);
|
|
467
|
-
const outlineText = outlineResult.content[0]?.text ??
|
|
618
|
+
const outlineText = outlineResult.content[0]?.text ?? "";
|
|
468
619
|
const outlineTokens = estimateTokens(outlineText);
|
|
469
620
|
const outlineWouldBe = await estimateOutlineWorkflowTokens(outlineArgs.path, outlineArgs.recursive ?? false, outlineArgs.max_depth ?? 2);
|
|
470
|
-
sessionCache?.set(
|
|
471
|
-
files: [resolve(projectRoot, outlineArgs.path) +
|
|
621
|
+
sessionCache?.set("outline", outlineArgs, outlineResult, {
|
|
622
|
+
files: [resolve(projectRoot, outlineArgs.path) + "/"],
|
|
472
623
|
dependsOnAst: true,
|
|
473
624
|
}, outlineTokens, outlineWouldBe || outlineTokens);
|
|
474
625
|
recordWithTrace({
|
|
475
|
-
tool:
|
|
626
|
+
tool: "outline",
|
|
476
627
|
path: outlineArgs.path,
|
|
477
628
|
tokensReturned: outlineTokens,
|
|
478
629
|
tokensWouldBe: outlineWouldBe || outlineTokens,
|
|
479
630
|
timestamp: Date.now(),
|
|
480
|
-
savingsCategory:
|
|
631
|
+
savingsCategory: "compression",
|
|
481
632
|
args: outlineArgs,
|
|
482
633
|
});
|
|
483
634
|
return outlineResult;
|
|
484
635
|
}
|
|
485
|
-
case
|
|
636
|
+
case "session_analytics": {
|
|
486
637
|
const verbose = args?.verbose === true;
|
|
487
|
-
return {
|
|
638
|
+
return {
|
|
639
|
+
content: [
|
|
640
|
+
{
|
|
641
|
+
type: "text",
|
|
642
|
+
text: `TOKEN PILOT v${pkgVersion}\n\n${analytics.report(verbose)}`,
|
|
643
|
+
},
|
|
644
|
+
],
|
|
645
|
+
};
|
|
488
646
|
}
|
|
489
|
-
case
|
|
647
|
+
case "find_unused": {
|
|
490
648
|
const unusedArgs = validateFindUnusedArgs(args);
|
|
491
|
-
const cachedUnused = sessionCache?.get(
|
|
649
|
+
const cachedUnused = sessionCache?.get("find_unused", unusedArgs);
|
|
492
650
|
if (cachedUnused) {
|
|
493
|
-
recordWithTrace({
|
|
651
|
+
recordWithTrace({
|
|
652
|
+
tool: "find_unused",
|
|
653
|
+
path: unusedArgs.module ?? "all",
|
|
654
|
+
tokensReturned: cachedUnused.tokenEstimate,
|
|
655
|
+
tokensWouldBe: cachedUnused.tokensWouldBe ?? cachedUnused.tokenEstimate,
|
|
656
|
+
timestamp: Date.now(),
|
|
657
|
+
sessionCacheHit: true,
|
|
658
|
+
savingsCategory: "cache",
|
|
659
|
+
args: unusedArgs,
|
|
660
|
+
});
|
|
494
661
|
return cachedUnused.result;
|
|
495
662
|
}
|
|
496
663
|
const unusedResult = await handleFindUnused(unusedArgs, astIndex);
|
|
497
|
-
const unusedText = unusedResult.content[0]?.text ??
|
|
664
|
+
const unusedText = unusedResult.content[0]?.text ?? "";
|
|
498
665
|
const unusedTokens = estimateTokens(unusedText);
|
|
499
666
|
const unusedWouldBe = await estimateFindUsagesWorkflowTokens(unusedResult.meta.files);
|
|
500
|
-
sessionCache?.set(
|
|
501
|
-
recordWithTrace({
|
|
667
|
+
sessionCache?.set("find_unused", unusedArgs, unusedResult, { dependsOnAst: true }, unusedTokens, unusedWouldBe || unusedTokens);
|
|
668
|
+
recordWithTrace({
|
|
669
|
+
tool: "find_unused",
|
|
670
|
+
path: unusedArgs.module ?? "all",
|
|
671
|
+
tokensReturned: unusedTokens,
|
|
672
|
+
tokensWouldBe: unusedWouldBe || unusedTokens,
|
|
673
|
+
timestamp: Date.now(),
|
|
674
|
+
savingsCategory: "compression",
|
|
675
|
+
args: unusedArgs,
|
|
676
|
+
});
|
|
502
677
|
return unusedResult;
|
|
503
678
|
}
|
|
504
|
-
case
|
|
679
|
+
case "code_audit": {
|
|
505
680
|
const auditArgs = validateCodeAuditArgs(args);
|
|
506
|
-
const cachedAudit = sessionCache?.get(
|
|
681
|
+
const cachedAudit = sessionCache?.get("code_audit", auditArgs);
|
|
507
682
|
if (cachedAudit) {
|
|
508
|
-
recordWithTrace({
|
|
683
|
+
recordWithTrace({
|
|
684
|
+
tool: "code_audit",
|
|
685
|
+
path: auditArgs.check,
|
|
686
|
+
tokensReturned: cachedAudit.tokenEstimate,
|
|
687
|
+
tokensWouldBe: cachedAudit.tokensWouldBe ?? cachedAudit.tokenEstimate,
|
|
688
|
+
timestamp: Date.now(),
|
|
689
|
+
sessionCacheHit: true,
|
|
690
|
+
savingsCategory: "cache",
|
|
691
|
+
args: auditArgs,
|
|
692
|
+
});
|
|
509
693
|
return cachedAudit.result;
|
|
510
694
|
}
|
|
511
695
|
const auditResult = await handleCodeAudit(auditArgs, projectRoot, astIndex);
|
|
512
|
-
const auditText = auditResult.content[0]?.text ??
|
|
696
|
+
const auditText = auditResult.content[0]?.text ?? "";
|
|
513
697
|
const auditTokens = estimateTokens(auditText);
|
|
514
698
|
const auditWouldBe = await estimateFindUsagesWorkflowTokens(auditResult.meta.files);
|
|
515
|
-
sessionCache?.set(
|
|
516
|
-
recordWithTrace({
|
|
699
|
+
sessionCache?.set("code_audit", auditArgs, auditResult, { dependsOnAst: true }, auditTokens, auditWouldBe || auditTokens);
|
|
700
|
+
recordWithTrace({
|
|
701
|
+
tool: "code_audit",
|
|
702
|
+
path: auditArgs.check,
|
|
703
|
+
tokensReturned: auditTokens,
|
|
704
|
+
tokensWouldBe: auditWouldBe || auditTokens,
|
|
705
|
+
timestamp: Date.now(),
|
|
706
|
+
savingsCategory: "compression",
|
|
707
|
+
args: auditArgs,
|
|
708
|
+
});
|
|
517
709
|
return auditResult;
|
|
518
710
|
}
|
|
519
|
-
case
|
|
711
|
+
case "module_info": {
|
|
520
712
|
const moduleArgs = validateModuleInfoArgs(args);
|
|
521
|
-
const cachedModule = sessionCache?.get(
|
|
713
|
+
const cachedModule = sessionCache?.get("module_info", moduleArgs);
|
|
522
714
|
if (cachedModule) {
|
|
523
|
-
recordWithTrace({
|
|
715
|
+
recordWithTrace({
|
|
716
|
+
tool: "module_info",
|
|
717
|
+
path: moduleArgs.module,
|
|
718
|
+
tokensReturned: cachedModule.tokenEstimate,
|
|
719
|
+
tokensWouldBe: cachedModule.tokensWouldBe ?? cachedModule.tokenEstimate,
|
|
720
|
+
timestamp: Date.now(),
|
|
721
|
+
sessionCacheHit: true,
|
|
722
|
+
savingsCategory: "cache",
|
|
723
|
+
args: moduleArgs,
|
|
724
|
+
});
|
|
524
725
|
return cachedModule.result;
|
|
525
726
|
}
|
|
526
727
|
const moduleResult = await handleModuleInfo(moduleArgs, projectRoot, astIndex);
|
|
527
|
-
const moduleText = moduleResult.content[0]?.text ??
|
|
728
|
+
const moduleText = moduleResult.content[0]?.text ?? "";
|
|
528
729
|
const moduleTokens = estimateTokens(moduleText);
|
|
529
730
|
const moduleWouldBe = await estimateFindUsagesWorkflowTokens(moduleResult.meta.files);
|
|
530
|
-
sessionCache?.set(
|
|
531
|
-
recordWithTrace({
|
|
731
|
+
sessionCache?.set("module_info", moduleArgs, moduleResult, { dependsOnAst: true }, moduleTokens, moduleWouldBe || moduleTokens);
|
|
732
|
+
recordWithTrace({
|
|
733
|
+
tool: "module_info",
|
|
734
|
+
path: moduleArgs.module,
|
|
735
|
+
tokensReturned: moduleTokens,
|
|
736
|
+
tokensWouldBe: moduleWouldBe || moduleTokens,
|
|
737
|
+
timestamp: Date.now(),
|
|
738
|
+
savingsCategory: "compression",
|
|
739
|
+
args: moduleArgs,
|
|
740
|
+
});
|
|
532
741
|
return moduleResult;
|
|
533
742
|
}
|
|
534
|
-
case
|
|
743
|
+
case "smart_diff": {
|
|
535
744
|
const sdArgs = validateSmartDiffArgs(args);
|
|
536
745
|
const sdResult = await handleSmartDiff(sdArgs, projectRoot, astIndex);
|
|
537
|
-
const sdText = sdResult.content[0]?.text ??
|
|
746
|
+
const sdText = sdResult.content[0]?.text ?? "";
|
|
538
747
|
const sdTokens = estimateTokens(sdText);
|
|
539
|
-
recordWithTrace({
|
|
748
|
+
recordWithTrace({
|
|
749
|
+
tool: "smart_diff",
|
|
750
|
+
path: sdArgs.path ?? sdArgs.scope ?? "unstaged",
|
|
751
|
+
tokensReturned: sdTokens,
|
|
752
|
+
tokensWouldBe: sdResult.rawTokens || sdTokens,
|
|
753
|
+
timestamp: Date.now(),
|
|
754
|
+
savingsCategory: "compression",
|
|
755
|
+
args: sdArgs,
|
|
756
|
+
});
|
|
540
757
|
return { content: sdResult.content };
|
|
541
758
|
}
|
|
542
|
-
case
|
|
759
|
+
case "explore_area": {
|
|
543
760
|
const eaArgs = validateExploreAreaArgs(args);
|
|
544
|
-
const cachedEa = sessionCache?.get(
|
|
761
|
+
const cachedEa = sessionCache?.get("explore_area", eaArgs);
|
|
545
762
|
if (cachedEa) {
|
|
546
|
-
recordWithTrace({
|
|
763
|
+
recordWithTrace({
|
|
764
|
+
tool: "explore_area",
|
|
765
|
+
path: eaArgs.path,
|
|
766
|
+
tokensReturned: cachedEa.tokenEstimate,
|
|
767
|
+
tokensWouldBe: cachedEa.tokensWouldBe ?? cachedEa.tokenEstimate,
|
|
768
|
+
timestamp: Date.now(),
|
|
769
|
+
sessionCacheHit: true,
|
|
770
|
+
savingsCategory: "cache",
|
|
771
|
+
args: eaArgs,
|
|
772
|
+
});
|
|
547
773
|
return cachedEa.result;
|
|
548
774
|
}
|
|
549
775
|
const eaResult = await handleExploreArea(eaArgs, projectRoot, astIndex);
|
|
550
|
-
const eaText = eaResult.content[0]?.text ??
|
|
776
|
+
const eaText = eaResult.content[0]?.text ?? "";
|
|
551
777
|
const eaTokens = estimateTokens(eaText);
|
|
552
778
|
const eaWouldBe = await estimateExploreAreaWorkflowTokens(eaResult.meta);
|
|
553
|
-
sessionCache?.set(
|
|
554
|
-
files: [resolve(projectRoot, eaArgs.path) +
|
|
779
|
+
sessionCache?.set("explore_area", eaArgs, eaResult, {
|
|
780
|
+
files: [resolve(projectRoot, eaArgs.path) + "/"],
|
|
555
781
|
dependsOnAst: true,
|
|
556
782
|
dependsOnGit: true,
|
|
557
783
|
}, eaTokens, eaWouldBe || eaTokens);
|
|
558
784
|
recordWithTrace({
|
|
559
|
-
tool:
|
|
785
|
+
tool: "explore_area",
|
|
560
786
|
path: eaArgs.path,
|
|
561
787
|
tokensReturned: eaTokens,
|
|
562
788
|
tokensWouldBe: eaWouldBe || eaTokens,
|
|
563
789
|
timestamp: Date.now(),
|
|
564
|
-
savingsCategory:
|
|
790
|
+
savingsCategory: "compression",
|
|
565
791
|
args: eaArgs,
|
|
566
792
|
});
|
|
567
793
|
return eaResult;
|
|
568
794
|
}
|
|
569
|
-
case
|
|
795
|
+
case "smart_log": {
|
|
570
796
|
const slArgs = validateSmartLogArgs(args);
|
|
571
797
|
const slResult = await handleSmartLog(slArgs, projectRoot);
|
|
572
|
-
const slText = slResult.content[0]?.text ??
|
|
798
|
+
const slText = slResult.content[0]?.text ?? "";
|
|
573
799
|
const slTokens = estimateTokens(slText);
|
|
574
|
-
recordWithTrace({
|
|
800
|
+
recordWithTrace({
|
|
801
|
+
tool: "smart_log",
|
|
802
|
+
path: slArgs.path ?? "all",
|
|
803
|
+
tokensReturned: slTokens,
|
|
804
|
+
tokensWouldBe: slResult.rawTokens || slTokens,
|
|
805
|
+
timestamp: Date.now(),
|
|
806
|
+
savingsCategory: "compression",
|
|
807
|
+
args: slArgs,
|
|
808
|
+
});
|
|
575
809
|
return { content: slResult.content };
|
|
576
810
|
}
|
|
577
|
-
case
|
|
811
|
+
case "test_summary": {
|
|
578
812
|
const tsArgs = validateTestSummaryArgs(args);
|
|
579
813
|
const tsResult = await handleTestSummary(tsArgs, projectRoot);
|
|
580
|
-
const tsText = tsResult.content[0]?.text ??
|
|
814
|
+
const tsText = tsResult.content[0]?.text ?? "";
|
|
581
815
|
const tsTokens = estimateTokens(tsText);
|
|
582
|
-
recordWithTrace({
|
|
816
|
+
recordWithTrace({
|
|
817
|
+
tool: "test_summary",
|
|
818
|
+
path: tsArgs.command,
|
|
819
|
+
tokensReturned: tsTokens,
|
|
820
|
+
tokensWouldBe: tsResult.rawTokens || tsTokens,
|
|
821
|
+
timestamp: Date.now(),
|
|
822
|
+
savingsCategory: "compression",
|
|
823
|
+
args: tsArgs,
|
|
824
|
+
});
|
|
583
825
|
return { content: tsResult.content };
|
|
584
826
|
}
|
|
585
|
-
case
|
|
827
|
+
case "session_snapshot": {
|
|
586
828
|
const snapshotArgs = args;
|
|
587
829
|
if (!snapshotArgs.goal) {
|
|
588
|
-
return {
|
|
830
|
+
return {
|
|
831
|
+
content: [{ type: "text", text: "Error: goal is required" }],
|
|
832
|
+
isError: true,
|
|
833
|
+
};
|
|
589
834
|
}
|
|
590
835
|
const snapshotResult = handleSessionSnapshot(snapshotArgs);
|
|
591
|
-
const snapshotText = snapshotResult.content[0]?.text ??
|
|
836
|
+
const snapshotText = snapshotResult.content[0]?.text ?? "";
|
|
592
837
|
const snapshotTokens = estimateTokens(snapshotText);
|
|
593
|
-
|
|
838
|
+
// TP-340: persist to .token-pilot/snapshots/ unless caller opts out.
|
|
839
|
+
if (snapshotArgs.persist !== false) {
|
|
840
|
+
try {
|
|
841
|
+
await persistSnapshot({ projectRoot, body: snapshotText });
|
|
842
|
+
}
|
|
843
|
+
catch {
|
|
844
|
+
/* best-effort — never fail the tool call */
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
recordWithTrace({
|
|
848
|
+
tool: "session_snapshot",
|
|
849
|
+
tokensReturned: snapshotTokens,
|
|
850
|
+
tokensWouldBe: snapshotTokens,
|
|
851
|
+
timestamp: Date.now(),
|
|
852
|
+
savingsCategory: "compression",
|
|
853
|
+
});
|
|
594
854
|
return { content: snapshotResult.content };
|
|
595
855
|
}
|
|
856
|
+
case "session_budget": {
|
|
857
|
+
const budgetArgs = args;
|
|
858
|
+
const budgetResult = await handleSessionBudget({ sessionId: budgetArgs.sessionId ?? "" }, projectRoot, {
|
|
859
|
+
baseThreshold: config.hooks.denyThreshold,
|
|
860
|
+
adaptiveThreshold: config.hooks.adaptiveThreshold,
|
|
861
|
+
adaptiveBudgetTokens: config.hooks.adaptiveBudgetTokens,
|
|
862
|
+
});
|
|
863
|
+
const budgetTokens = estimateTokens(budgetResult.content[0]?.text ?? "");
|
|
864
|
+
recordWithTrace({
|
|
865
|
+
tool: "session_budget",
|
|
866
|
+
tokensReturned: budgetTokens,
|
|
867
|
+
tokensWouldBe: budgetTokens,
|
|
868
|
+
timestamp: Date.now(),
|
|
869
|
+
savingsCategory: "compression",
|
|
870
|
+
});
|
|
871
|
+
return { content: budgetResult.content };
|
|
872
|
+
}
|
|
596
873
|
default:
|
|
597
874
|
return {
|
|
598
|
-
content: [{ type:
|
|
875
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
599
876
|
isError: true,
|
|
600
877
|
};
|
|
601
878
|
}
|
|
@@ -603,7 +880,7 @@ export async function createServer(projectRoot, options) {
|
|
|
603
880
|
catch (err) {
|
|
604
881
|
const message = err instanceof Error ? err.message : String(err);
|
|
605
882
|
return {
|
|
606
|
-
content: [{ type:
|
|
883
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
607
884
|
isError: true,
|
|
608
885
|
};
|
|
609
886
|
}
|