token-pilot 0.16.2 → 0.17.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/CHANGELOG.md +16 -0
- package/dist/config/defaults.js +5 -0
- package/dist/core/context-registry.d.ts +4 -0
- package/dist/core/context-registry.js +13 -0
- package/dist/core/validation.d.ts +1 -0
- package/dist/core/validation.js +9 -0
- package/dist/formatters/structure.d.ts +1 -0
- package/dist/formatters/structure.js +48 -2
- package/dist/handlers/find-usages.js +65 -13
- package/dist/handlers/read-for-edit.d.ts +3 -1
- package/dist/handlers/read-for-edit.js +6 -1
- package/dist/handlers/read-symbol.d.ts +1 -0
- package/dist/handlers/read-symbol.js +20 -4
- package/dist/handlers/smart-read-many.js +9 -0
- package/dist/handlers/smart-read.d.ts +1 -0
- package/dist/handlers/smart-read.js +57 -0
- package/dist/handlers/symbol-display-constants.d.ts +10 -0
- package/dist/handlers/symbol-display-constants.js +10 -0
- package/dist/server/tool-definitions.d.ts +66 -17
- package/dist/server/tool-definitions.js +34 -16
- package/dist/server.js +3 -1
- package/dist/types.d.ts +6 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ All notable changes to Token Pilot will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.17.0] - 2026-04-02
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`smart_read` scope parameter** — `scope="nav"` returns names + line ranges only (2-3x smaller), `scope="exports"` shows only public API. Default `scope="full"` unchanged.
|
|
12
|
+
- **`smart_read` auto-delta** — when a file changed since last load (within 120s), shows ADDED/REMOVED/UNCHANGED symbols instead of full re-read. Config: `smartRead.autoDelta.enabled`.
|
|
13
|
+
- **`read_symbol` include_edit_context** — optional `include_edit_context=true` appends raw code block (max 60 lines) to save a separate `read_for_edit` call. Large symbols fall back to `read_for_edit`.
|
|
14
|
+
- **`find_usages` mode=list** — compact `file:line` output for initial discovery, 5-10x smaller than full mode.
|
|
15
|
+
- **`smart_read_many` per-file dedup** — skips files already in context and unchanged, returns compact reminder instead.
|
|
16
|
+
- **Actionable hints** — `read_for_edit` suggests `read_diff` after editing. Config: `display.actionableHints`.
|
|
17
|
+
- **`symbol-display-constants.ts`** — shared display constants for symbol rendering.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **441 tests** (was 427) — new tests for scope, list mode, include_edit_context, dedup.
|
|
21
|
+
- **MCP instructions** updated with scope/mode/include_edit_context guidance.
|
|
22
|
+
- **find_usages context rendering** — sequential instead of concurrent to prevent shared cache race condition.
|
|
23
|
+
|
|
8
24
|
## [0.16.1] - 2026-03-21
|
|
9
25
|
|
|
10
26
|
### Added
|
package/dist/config/defaults.js
CHANGED
|
@@ -12,6 +12,10 @@ export const DEFAULT_CONFIG = {
|
|
|
12
12
|
smallFileThreshold: 200,
|
|
13
13
|
showDependencyHints: true,
|
|
14
14
|
advisoryReminders: true,
|
|
15
|
+
autoDelta: {
|
|
16
|
+
enabled: true,
|
|
17
|
+
maxAgeSec: 120,
|
|
18
|
+
},
|
|
15
19
|
},
|
|
16
20
|
git: {
|
|
17
21
|
watchHead: true,
|
|
@@ -33,6 +37,7 @@ export const DEFAULT_CONFIG = {
|
|
|
33
37
|
showReferences: false,
|
|
34
38
|
maxDepth: 2,
|
|
35
39
|
showTokenSavings: true,
|
|
40
|
+
actionableHints: true,
|
|
36
41
|
},
|
|
37
42
|
contextMode: {
|
|
38
43
|
enabled: 'auto',
|
|
@@ -40,6 +40,10 @@ export declare class ContextRegistry {
|
|
|
40
40
|
entries: ContextEntry[];
|
|
41
41
|
};
|
|
42
42
|
estimateTokens(): number;
|
|
43
|
+
trackStructureSymbols(path: string, symbolNames: string[]): void;
|
|
44
|
+
getSymbolNames(path: string): string[] | undefined;
|
|
45
|
+
/** Get the timestamp when a file was last loaded into context. */
|
|
46
|
+
getLoadedAt(path: string): number | undefined;
|
|
43
47
|
invalidateByGitDiff(changedFiles: string[]): void;
|
|
44
48
|
}
|
|
45
49
|
//# sourceMappingURL=context-registry.d.ts.map
|
|
@@ -182,6 +182,19 @@ export class ContextRegistry {
|
|
|
182
182
|
}
|
|
183
183
|
return total;
|
|
184
184
|
}
|
|
185
|
+
trackStructureSymbols(path, symbolNames) {
|
|
186
|
+
const entry = this.entries.get(path);
|
|
187
|
+
if (entry) {
|
|
188
|
+
entry.symbolNames = symbolNames;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
getSymbolNames(path) {
|
|
192
|
+
return this.entries.get(path)?.symbolNames;
|
|
193
|
+
}
|
|
194
|
+
/** Get the timestamp when a file was last loaded into context. */
|
|
195
|
+
getLoadedAt(path) {
|
|
196
|
+
return this.entries.get(path)?.loadedAt;
|
|
197
|
+
}
|
|
185
198
|
invalidateByGitDiff(changedFiles) {
|
|
186
199
|
for (const file of changedFiles) {
|
|
187
200
|
this.entries.delete(file);
|
package/dist/core/validation.js
CHANGED
|
@@ -160,6 +160,14 @@ export function validateFindUsagesArgs(args) {
|
|
|
160
160
|
if (context_lines !== undefined && (context_lines < 0 || context_lines > 10)) {
|
|
161
161
|
throw new Error('"context_lines" must be between 0 and 10.');
|
|
162
162
|
}
|
|
163
|
+
let mode;
|
|
164
|
+
if (a.mode !== undefined && a.mode !== null) {
|
|
165
|
+
const validModes = ['full', 'list'];
|
|
166
|
+
if (typeof a.mode !== 'string' || !validModes.includes(a.mode)) {
|
|
167
|
+
throw new Error(`"mode" must be one of: ${validModes.join(', ')}`);
|
|
168
|
+
}
|
|
169
|
+
mode = a.mode;
|
|
170
|
+
}
|
|
163
171
|
return {
|
|
164
172
|
symbol: a.symbol,
|
|
165
173
|
scope: optionalString(a.scope, 'scope'),
|
|
@@ -167,6 +175,7 @@ export function validateFindUsagesArgs(args) {
|
|
|
167
175
|
limit,
|
|
168
176
|
lang: optionalString(a.lang, 'lang'),
|
|
169
177
|
context_lines,
|
|
178
|
+
mode,
|
|
170
179
|
};
|
|
171
180
|
}
|
|
172
181
|
/**
|
|
@@ -2,13 +2,48 @@
|
|
|
2
2
|
* Format a FileStructure as token-optimized text for LLMs.
|
|
3
3
|
*/
|
|
4
4
|
export function formatOutline(structure, options = {}) {
|
|
5
|
-
const { showImports = true, showDocs = true, showDependencyHints = true, maxDepth = 2, } = options;
|
|
5
|
+
const { showImports = true, showDocs = true, showDependencyHints = true, maxDepth = 2, scope = 'full', } = options;
|
|
6
6
|
const lines = [];
|
|
7
7
|
// Header
|
|
8
8
|
const sizeKB = (structure.meta.bytes / 1024).toFixed(1);
|
|
9
|
-
|
|
9
|
+
const scopeLabel = scope !== 'full' ? `, ${scope} mode` : '';
|
|
10
|
+
lines.push(`FILE: ${structure.path} (${structure.meta.lines} lines, ${sizeKB}KB${scopeLabel})`);
|
|
10
11
|
lines.push(`LANGUAGE: ${structure.language}`);
|
|
11
12
|
lines.push('');
|
|
13
|
+
// NAV scope: compact names + line ranges only
|
|
14
|
+
if (scope === 'nav') {
|
|
15
|
+
lines.push('SYMBOLS:');
|
|
16
|
+
for (const sym of structure.symbols) {
|
|
17
|
+
formatSymbolNav(sym, lines, 1, maxDepth);
|
|
18
|
+
}
|
|
19
|
+
lines.push('');
|
|
20
|
+
lines.push('HINT: Use scope="full" for type signatures and imports. Use read_symbol(path, symbol) to load code.');
|
|
21
|
+
return lines.join('\n');
|
|
22
|
+
}
|
|
23
|
+
// EXPORTS scope: only exported symbols
|
|
24
|
+
if (scope === 'exports') {
|
|
25
|
+
const exportNames = new Set(structure.exports.map(e => e.name));
|
|
26
|
+
const exportedSymbols = structure.symbols.filter(s => exportNames.has(s.name));
|
|
27
|
+
const hiddenCount = structure.symbols.length - exportedSymbols.length;
|
|
28
|
+
lines.push('EXPORTS:');
|
|
29
|
+
for (const exp of structure.exports) {
|
|
30
|
+
const defaultLabel = exp.isDefault ? ' (default)' : '';
|
|
31
|
+
lines.push(` ${exp.kind} ${exp.name}${defaultLabel}`);
|
|
32
|
+
}
|
|
33
|
+
lines.push('');
|
|
34
|
+
lines.push('STRUCTURE:');
|
|
35
|
+
for (const sym of exportedSymbols) {
|
|
36
|
+
formatSymbolTree(sym, lines, 1, maxDepth, showDocs, showDependencyHints);
|
|
37
|
+
}
|
|
38
|
+
lines.push('');
|
|
39
|
+
if (hiddenCount > 0) {
|
|
40
|
+
lines.push(`HINT: ${hiddenCount} non-exported symbol(s) hidden. Use scope="full" to see all symbols.`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
lines.push('HINT: Use read_symbol(path, symbol) to load a specific symbol\'s code.');
|
|
44
|
+
}
|
|
45
|
+
return lines.join('\n');
|
|
46
|
+
}
|
|
12
47
|
// Imports
|
|
13
48
|
if (showImports && structure.imports.length > 0) {
|
|
14
49
|
lines.push('IMPORTS:');
|
|
@@ -111,6 +146,17 @@ function formatSymbolTree(sym, lines, depth, maxDepth, showDocs, showDeps, paren
|
|
|
111
146
|
lines.push(`${indent} (${sym.children.length} members — increase depth to see)`);
|
|
112
147
|
}
|
|
113
148
|
}
|
|
149
|
+
function formatSymbolNav(sym, lines, depth, maxDepth) {
|
|
150
|
+
const indent = ' '.repeat(depth);
|
|
151
|
+
const loc = `[L${sym.location.startLine}-${sym.location.endLine}]`;
|
|
152
|
+
const callable = sym.kind === 'function' || sym.kind === 'method' ? '()' : '';
|
|
153
|
+
lines.push(`${indent}${sym.name}${callable} ${loc}`);
|
|
154
|
+
if (depth < maxDepth && sym.children.length > 0) {
|
|
155
|
+
for (const child of sym.children) {
|
|
156
|
+
formatSymbolNav(child, lines, depth + 1, maxDepth);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
114
160
|
/**
|
|
115
161
|
* Detect HTTP route decorators and format as "METHOD /path".
|
|
116
162
|
* Uses standard HTTP verbs — not framework-specific.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
1
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { assessConfidence, formatConfidence } from '../core/confidence.js';
|
|
4
4
|
/**
|
|
@@ -53,10 +53,15 @@ function renderSection(title, items) {
|
|
|
53
53
|
lines.push('');
|
|
54
54
|
return lines;
|
|
55
55
|
}
|
|
56
|
+
/** Max unique files to read for context (prevents unbounded I/O). */
|
|
57
|
+
const MAX_CONTEXT_FILES = 30;
|
|
58
|
+
/** Max file size (bytes) to read for context lines. */
|
|
59
|
+
const MAX_CONTEXT_FILE_SIZE = 500_000;
|
|
56
60
|
/**
|
|
57
61
|
* Render a section with surrounding source context lines.
|
|
62
|
+
* Uses shared fileCache to avoid re-reading the same file across sections.
|
|
58
63
|
*/
|
|
59
|
-
async function renderSectionWithContext(title, items, contextLines, projectRoot) {
|
|
64
|
+
async function renderSectionWithContext(title, items, contextLines, projectRoot, fileCache) {
|
|
60
65
|
if (items.length === 0)
|
|
61
66
|
return [];
|
|
62
67
|
const lines = [`${title}:`];
|
|
@@ -66,17 +71,30 @@ async function renderSectionWithContext(title, items, contextLines, projectRoot)
|
|
|
66
71
|
arr.push({ line: item.line, text: item.text });
|
|
67
72
|
byFile.set(item.file, arr);
|
|
68
73
|
}
|
|
74
|
+
let filesRead = 0;
|
|
69
75
|
for (const [file, matches] of byFile) {
|
|
70
76
|
matches.sort((a, b) => a.line - b.line);
|
|
71
77
|
lines.push(` ${file}:`);
|
|
72
|
-
// Read file for context
|
|
78
|
+
// Read file for context (with shared cache and limits)
|
|
73
79
|
let fileLines = null;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
fileLines = content.split('\n');
|
|
80
|
+
if (fileCache.has(file)) {
|
|
81
|
+
fileLines = fileCache.get(file);
|
|
77
82
|
}
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
else if (filesRead < MAX_CONTEXT_FILES) {
|
|
84
|
+
try {
|
|
85
|
+
const fileStat = await stat(resolve(projectRoot, file));
|
|
86
|
+
if (fileStat.size <= MAX_CONTEXT_FILE_SIZE) {
|
|
87
|
+
const content = await readFile(resolve(projectRoot, file), 'utf-8');
|
|
88
|
+
fileLines = content.split('\n');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// File unreadable
|
|
93
|
+
}
|
|
94
|
+
fileCache.set(file, fileLines);
|
|
95
|
+
filesRead++;
|
|
96
|
+
}
|
|
97
|
+
if (!fileLines) {
|
|
80
98
|
for (const m of matches) {
|
|
81
99
|
lines.push(` :${m.line} ${m.text}`);
|
|
82
100
|
}
|
|
@@ -218,6 +236,36 @@ export async function handleFindUsages(args, astIndex, projectRoot) {
|
|
|
218
236
|
meta: { files: [], definitions: 0, imports: 0, usages: 0, total: 0 },
|
|
219
237
|
};
|
|
220
238
|
}
|
|
239
|
+
// ─── List mode — compact file:line output ───
|
|
240
|
+
if (args.mode === 'list') {
|
|
241
|
+
const allItems = [...definitions, ...allImports, ...allUsages];
|
|
242
|
+
const byFile = new Map();
|
|
243
|
+
for (const item of allItems) {
|
|
244
|
+
const arr = byFile.get(item.file) ?? [];
|
|
245
|
+
arr.push(item.line);
|
|
246
|
+
byFile.set(item.file, arr);
|
|
247
|
+
}
|
|
248
|
+
const listLines = [
|
|
249
|
+
`USAGES OF "${args.symbol}" (${allItems.length} matches in ${byFile.size} files):`,
|
|
250
|
+
'',
|
|
251
|
+
];
|
|
252
|
+
for (const [file, fileLines] of byFile) {
|
|
253
|
+
const sorted = [...new Set(fileLines)].sort((a, b) => a - b);
|
|
254
|
+
listLines.push(` ${file}: L${sorted.join(', L')}`);
|
|
255
|
+
}
|
|
256
|
+
listLines.push('');
|
|
257
|
+
listLines.push(`HINT: Use find_usages("${args.symbol}", path="specific_dir/") to narrow, or read_symbol() on specific matches.`);
|
|
258
|
+
return {
|
|
259
|
+
content: [{ type: 'text', text: listLines.join('\n') }],
|
|
260
|
+
meta: {
|
|
261
|
+
files: Array.from(byFile.keys()),
|
|
262
|
+
definitions: definitions.length,
|
|
263
|
+
imports: allImports.length,
|
|
264
|
+
usages: allUsages.length,
|
|
265
|
+
total: allItems.length,
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
221
269
|
// Build header with active filters
|
|
222
270
|
const filterHints = [];
|
|
223
271
|
if (args.scope)
|
|
@@ -232,11 +280,11 @@ export async function handleFindUsages(args, astIndex, projectRoot) {
|
|
|
232
280
|
'',
|
|
233
281
|
];
|
|
234
282
|
if (args.context_lines !== undefined && args.context_lines > 0 && projectRoot) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
283
|
+
// Shared file cache across sections — sequential to avoid concurrent Map writes
|
|
284
|
+
const contextFileCache = new Map();
|
|
285
|
+
const defSection = await renderSectionWithContext('DEFINITIONS', definitions, args.context_lines, projectRoot, contextFileCache);
|
|
286
|
+
const impSection = await renderSectionWithContext('IMPORTS', allImports, args.context_lines, projectRoot, contextFileCache);
|
|
287
|
+
const useSection = await renderSectionWithContext('USAGES', allUsages, args.context_lines, projectRoot, contextFileCache);
|
|
240
288
|
lines.push(...defSection);
|
|
241
289
|
lines.push(...impSection);
|
|
242
290
|
lines.push(...useSection);
|
|
@@ -247,6 +295,10 @@ export async function handleFindUsages(args, astIndex, projectRoot) {
|
|
|
247
295
|
lines.push(...renderSection('USAGES', allUsages));
|
|
248
296
|
}
|
|
249
297
|
lines.push('HINT: Use read_symbol() or read_range() to load specific results.');
|
|
298
|
+
if (totalCount > 20) {
|
|
299
|
+
lines.push('');
|
|
300
|
+
lines.push(`NARROW: ${totalCount} matches found. Use find_usages("${args.symbol}", path="specific_dir/") to filter by location.`);
|
|
301
|
+
}
|
|
250
302
|
// Confidence metadata
|
|
251
303
|
const confidenceMeta = assessConfidence({
|
|
252
304
|
refsFound: totalCount > 0,
|
|
@@ -12,7 +12,9 @@ export interface ReadForEditArgs {
|
|
|
12
12
|
include_tests?: boolean;
|
|
13
13
|
include_changes?: boolean;
|
|
14
14
|
}
|
|
15
|
-
export declare function handleReadForEdit(args: ReadForEditArgs, projectRoot: string, symbolResolver: SymbolResolver, fileCache: FileCache, contextRegistry: ContextRegistry, astIndex: AstIndexClient
|
|
15
|
+
export declare function handleReadForEdit(args: ReadForEditArgs, projectRoot: string, symbolResolver: SymbolResolver, fileCache: FileCache, contextRegistry: ContextRegistry, astIndex: AstIndexClient, options?: {
|
|
16
|
+
actionableHints?: boolean;
|
|
17
|
+
}): Promise<{
|
|
16
18
|
content: Array<{
|
|
17
19
|
type: 'text';
|
|
18
20
|
text: string;
|
|
@@ -8,7 +8,7 @@ import { resolveSafePath } from '../core/validation.js';
|
|
|
8
8
|
import { assessConfidence, formatConfidence } from '../core/confidence.js';
|
|
9
9
|
const execFileAsync = promisify(execFile);
|
|
10
10
|
const DEFAULT_CONTEXT = 5;
|
|
11
|
-
export async function handleReadForEdit(args, projectRoot, symbolResolver, fileCache, contextRegistry, astIndex) {
|
|
11
|
+
export async function handleReadForEdit(args, projectRoot, symbolResolver, fileCache, contextRegistry, astIndex, options) {
|
|
12
12
|
const absPath = resolveSafePath(projectRoot, args.path);
|
|
13
13
|
const ctx = args.context ?? DEFAULT_CONTEXT;
|
|
14
14
|
// Get file content — also cache for read_diff baseline
|
|
@@ -220,6 +220,11 @@ export async function handleReadForEdit(args, projectRoot, symbolResolver, fileC
|
|
|
220
220
|
astAvailable: true,
|
|
221
221
|
});
|
|
222
222
|
outputLines.push(formatConfidence(confidenceMeta));
|
|
223
|
+
// Add post-edit hint (config-gated)
|
|
224
|
+
if (options?.actionableHints !== false) {
|
|
225
|
+
outputLines.push('');
|
|
226
|
+
outputLines.push(`AFTER EDIT: Use read_diff("${args.path}") to verify changes (90% cheaper than re-reading the file).`);
|
|
227
|
+
}
|
|
223
228
|
const output = outputLines.join('\n');
|
|
224
229
|
const tokens = estimateTokens(output);
|
|
225
230
|
// Track in context
|
|
@@ -8,6 +8,7 @@ export interface ReadSymbolArgs {
|
|
|
8
8
|
context_before?: number;
|
|
9
9
|
context_after?: number;
|
|
10
10
|
show?: 'full' | 'head' | 'tail' | 'outline';
|
|
11
|
+
include_edit_context?: boolean;
|
|
11
12
|
}
|
|
12
13
|
export declare function handleReadSymbol(args: ReadSymbolArgs, projectRoot: string, symbolResolver: SymbolResolver, fileCache: FileCache, contextRegistry: ContextRegistry, astIndex?: AstIndexClient, advisoryReminders?: boolean): Promise<{
|
|
13
14
|
content: Array<{
|
|
@@ -2,6 +2,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { estimateTokens } from '../core/token-estimator.js';
|
|
3
3
|
import { resolveSafePath } from '../core/validation.js';
|
|
4
4
|
import { assessConfidence, formatConfidence } from '../core/confidence.js';
|
|
5
|
+
import { MAX_SYMBOL_LINES, MAX_FULL_LINES, SYMBOL_HEAD_LINES, SYMBOL_TAIL_LINES } from './symbol-display-constants.js';
|
|
5
6
|
export async function handleReadSymbol(args, projectRoot, symbolResolver, fileCache, contextRegistry, astIndex, advisoryReminders = true) {
|
|
6
7
|
const absPath = resolveSafePath(projectRoot, args.path);
|
|
7
8
|
// Get file content
|
|
@@ -48,10 +49,9 @@ export async function handleReadSymbol(args, projectRoot, symbolResolver, fileCa
|
|
|
48
49
|
const loc = `[L${resolved.startLine}-${resolved.endLine}]`;
|
|
49
50
|
const lineCount = resolved.endLine - resolved.startLine + 1;
|
|
50
51
|
// Show mode: control how large symbols are displayed
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const TAIL = 30;
|
|
52
|
+
// Display constants from shared module (imported at top level)
|
|
53
|
+
const HEAD = SYMBOL_HEAD_LINES;
|
|
54
|
+
const TAIL = SYMBOL_TAIL_LINES;
|
|
55
55
|
let displaySource = source;
|
|
56
56
|
let truncated = false;
|
|
57
57
|
// Determine effective show mode
|
|
@@ -119,6 +119,22 @@ export async function handleReadSymbol(args, projectRoot, symbolResolver, fileCa
|
|
|
119
119
|
// Build full output including tracking message, THEN estimate tokens
|
|
120
120
|
outputLines.push('');
|
|
121
121
|
outputLines.push('CONTEXT TRACKED: This symbol is now in your context.');
|
|
122
|
+
// Optional: append raw edit context to save a separate read_for_edit call (small symbols only)
|
|
123
|
+
if (args.include_edit_context) {
|
|
124
|
+
const rawLines = lines.slice(resolved.startLine - 1, resolved.endLine);
|
|
125
|
+
const maxEditLines = 60;
|
|
126
|
+
if (rawLines.length <= maxEditLines) {
|
|
127
|
+
outputLines.push('');
|
|
128
|
+
outputLines.push('EDIT_CONTEXT (raw — copy directly as old_string):');
|
|
129
|
+
outputLines.push('```');
|
|
130
|
+
outputLines.push(rawLines.join('\n'));
|
|
131
|
+
outputLines.push('```');
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
outputLines.push('');
|
|
135
|
+
outputLines.push(`EDIT_CONTEXT: Symbol too large (${rawLines.length} lines). Use read_for_edit("${args.path}", symbol="${args.symbol}") instead.`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
122
138
|
const output = outputLines.join('\n');
|
|
123
139
|
const tokens = estimateTokens(output);
|
|
124
140
|
// Track
|
|
@@ -23,6 +23,15 @@ export async function handleSmartReadMany(args, projectRoot, astIndex, fileCache
|
|
|
23
23
|
for (let i = 0; i < uniquePaths.length; i += BATCH_CONCURRENCY) {
|
|
24
24
|
const batch = uniquePaths.slice(i, i + BATCH_CONCURRENCY);
|
|
25
25
|
const settled = await Promise.allSettled(batch.map(async (path) => {
|
|
26
|
+
// Per-file dedup: if file is in context and unchanged, return compact reminder
|
|
27
|
+
const absPath = resolveSafePath(projectRoot, path);
|
|
28
|
+
const cachedFile = fileCache.get(absPath);
|
|
29
|
+
if (cachedFile && contextRegistry.hasAnyLoaded(absPath) && !contextRegistry.isStale(absPath, cachedFile.hash)) {
|
|
30
|
+
const reminder = contextRegistry.compactReminder(absPath, cachedFile.structure?.symbols ?? []);
|
|
31
|
+
const reminderText = reminder || `FILE: ${path} (already in context, unchanged)`;
|
|
32
|
+
const fullTokens = await estimateFullFileTokens(projectRoot, path);
|
|
33
|
+
return { path, text: reminderText + `\nFor full re-read: smart_read("${path}")`, fullTokens };
|
|
34
|
+
}
|
|
26
35
|
const result = await handleSmartRead({ path }, projectRoot, astIndex, fileCache, contextRegistry, config);
|
|
27
36
|
const text = result.content[0]?.text ?? '';
|
|
28
37
|
const fullTokens = await estimateFullFileTokens(projectRoot, path);
|
|
@@ -8,6 +8,7 @@ export interface SmartReadArgs {
|
|
|
8
8
|
show_docs?: boolean;
|
|
9
9
|
show_references?: boolean;
|
|
10
10
|
depth?: number;
|
|
11
|
+
scope?: 'full' | 'nav' | 'exports';
|
|
11
12
|
}
|
|
12
13
|
export declare function handleSmartRead(args: SmartReadArgs, projectRoot: string, astIndex: AstIndexClient, fileCache: FileCache, contextRegistry: ContextRegistry, config: TokenPilotConfig): Promise<{
|
|
13
14
|
content: Array<{
|
|
@@ -6,6 +6,7 @@ import { resolveSafePath } from '../core/validation.js';
|
|
|
6
6
|
import { isNonCodeStructured, handleNonCodeRead } from './non-code.js';
|
|
7
7
|
import { parseTypeScriptRegex } from '../ast-index/regex-parser.js';
|
|
8
8
|
import { buildFileStructure } from '../ast-index/enricher.js';
|
|
9
|
+
import { formatDuration } from '../core/format-duration.js';
|
|
9
10
|
const TS_JS_EXTENSIONS = new Set(['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs']);
|
|
10
11
|
import { assessConfidence, formatConfidence } from '../core/confidence.js';
|
|
11
12
|
export async function handleSmartRead(args, projectRoot, astIndex, fileCache, contextRegistry, config) {
|
|
@@ -111,12 +112,64 @@ export async function handleSmartRead(args, projectRoot, astIndex, fileCache, co
|
|
|
111
112
|
return { content: [{ type: 'text', text: reminder }] };
|
|
112
113
|
}
|
|
113
114
|
}
|
|
115
|
+
// 5b. Auto-delta: file changed since last load, recently loaded
|
|
116
|
+
if (config.smartRead.autoDelta?.enabled &&
|
|
117
|
+
previouslyLoaded &&
|
|
118
|
+
contextRegistry.isStale(absPath, cached.hash)) {
|
|
119
|
+
const loadedAt = contextRegistry.getLoadedAt(absPath);
|
|
120
|
+
if (loadedAt !== undefined && (Date.now() - loadedAt) < (config.smartRead.autoDelta.maxAgeSec ?? 120) * 1000) {
|
|
121
|
+
const prevNames = contextRegistry.getSymbolNames(absPath) ?? [];
|
|
122
|
+
const prevSet = new Set(prevNames);
|
|
123
|
+
const currentNames = cached.structure.symbols.map(s => s.name);
|
|
124
|
+
const currentSet = new Set(currentNames);
|
|
125
|
+
const added = currentNames.filter(n => !prevSet.has(n));
|
|
126
|
+
const removed = prevNames.filter(n => !currentSet.has(n));
|
|
127
|
+
const unchanged = currentNames.filter(n => prevSet.has(n));
|
|
128
|
+
const elapsed = formatDuration(Date.now() - loadedAt);
|
|
129
|
+
const deltaLines = [
|
|
130
|
+
`FILE: ${args.path} (DELTA — changed since last read ${elapsed} ago)`,
|
|
131
|
+
'',
|
|
132
|
+
];
|
|
133
|
+
if (added.length > 0) {
|
|
134
|
+
deltaLines.push('ADDED:');
|
|
135
|
+
for (const name of added) {
|
|
136
|
+
const sym = cached.structure.symbols.find(s => s.name === name);
|
|
137
|
+
if (sym)
|
|
138
|
+
deltaLines.push(` ${sym.kind} ${sym.signature} [L${sym.location.startLine}-${sym.location.endLine}]`);
|
|
139
|
+
}
|
|
140
|
+
deltaLines.push('');
|
|
141
|
+
}
|
|
142
|
+
if (removed.length > 0) {
|
|
143
|
+
deltaLines.push(`REMOVED: ${removed.join(', ')}`);
|
|
144
|
+
deltaLines.push('');
|
|
145
|
+
}
|
|
146
|
+
if (unchanged.length > 0) {
|
|
147
|
+
deltaLines.push(`UNCHANGED (${unchanged.length} symbols):`);
|
|
148
|
+
for (const name of unchanged.slice(0, 15)) {
|
|
149
|
+
const sym = cached.structure.symbols.find(s => s.name === name);
|
|
150
|
+
if (sym)
|
|
151
|
+
deltaLines.push(` ${sym.name} [L${sym.location.startLine}-${sym.location.endLine}]`);
|
|
152
|
+
}
|
|
153
|
+
if (unchanged.length > 15)
|
|
154
|
+
deltaLines.push(` ... and ${unchanged.length - 15} more`);
|
|
155
|
+
deltaLines.push('');
|
|
156
|
+
}
|
|
157
|
+
deltaLines.push(`HINT: For full re-read: smart_read("${args.path}", scope="full")`);
|
|
158
|
+
const deltaText = deltaLines.join('\n');
|
|
159
|
+
const deltaTokens = estimateTokens(deltaText);
|
|
160
|
+
contextRegistry.trackLoad(absPath, { type: 'structure', startLine: 1, endLine: cached.structure.meta.lines, tokens: deltaTokens });
|
|
161
|
+
contextRegistry.setContentHash(absPath, cached.hash);
|
|
162
|
+
contextRegistry.trackStructureSymbols(absPath, currentNames);
|
|
163
|
+
return { content: [{ type: 'text', text: deltaText }] };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
114
166
|
// 6. Format output
|
|
115
167
|
const output = formatOutline(cached.structure, {
|
|
116
168
|
showImports: args.show_imports ?? config.display.showImports,
|
|
117
169
|
showDocs: args.show_docs ?? config.display.showDocs,
|
|
118
170
|
showDependencyHints: config.smartRead.showDependencyHints,
|
|
119
171
|
maxDepth: args.depth ?? config.display.maxDepth,
|
|
172
|
+
scope: args.scope ?? 'full',
|
|
120
173
|
});
|
|
121
174
|
// 6b. Adaptive fallback: if outline is not significantly smaller than raw, return raw
|
|
122
175
|
const structureTokens = estimateTokens(output);
|
|
@@ -129,6 +182,9 @@ export async function handleSmartRead(args, projectRoot, astIndex, fileCache, co
|
|
|
129
182
|
tokens: fullTokens,
|
|
130
183
|
});
|
|
131
184
|
contextRegistry.setContentHash(absPath, cached.hash);
|
|
185
|
+
if (cached.structure.symbols.length > 0) {
|
|
186
|
+
contextRegistry.trackStructureSymbols(absPath, cached.structure.symbols.map(s => s.name));
|
|
187
|
+
}
|
|
132
188
|
return {
|
|
133
189
|
content: [{
|
|
134
190
|
type: 'text',
|
|
@@ -148,6 +204,7 @@ export async function handleSmartRead(args, projectRoot, astIndex, fileCache, co
|
|
|
148
204
|
tokens: structureTokens,
|
|
149
205
|
});
|
|
150
206
|
contextRegistry.setContentHash(absPath, cached.hash);
|
|
207
|
+
contextRegistry.trackStructureSymbols(absPath, cached.structure.symbols.map(s => s.name));
|
|
151
208
|
// 9. Confidence metadata
|
|
152
209
|
const confidenceMeta = assessConfidence({
|
|
153
210
|
symbolResolved: (cached.structure.symbols?.length ?? 0) > 0,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Shared display constants for symbol rendering (read_symbol, read_symbols). */
|
|
2
|
+
/** Symbols larger than this get auto-truncated to outline mode. */
|
|
3
|
+
export declare const MAX_SYMBOL_LINES = 300;
|
|
4
|
+
/** Symbols larger than this are never shown in full, even with show="full". */
|
|
5
|
+
export declare const MAX_FULL_LINES = 500;
|
|
6
|
+
/** Lines shown from the start in head/outline mode. */
|
|
7
|
+
export declare const SYMBOL_HEAD_LINES = 50;
|
|
8
|
+
/** Lines shown from the end in tail/outline mode. */
|
|
9
|
+
export declare const SYMBOL_TAIL_LINES = 30;
|
|
10
|
+
//# sourceMappingURL=symbol-display-constants.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Shared display constants for symbol rendering (read_symbol, read_symbols). */
|
|
2
|
+
/** Symbols larger than this get auto-truncated to outline mode. */
|
|
3
|
+
export const MAX_SYMBOL_LINES = 300;
|
|
4
|
+
/** Symbols larger than this are never shown in full, even with show="full". */
|
|
5
|
+
export const MAX_FULL_LINES = 500;
|
|
6
|
+
/** Lines shown from the start in head/outline mode. */
|
|
7
|
+
export const SYMBOL_HEAD_LINES = 50;
|
|
8
|
+
/** Lines shown from the end in tail/outline mode. */
|
|
9
|
+
export const SYMBOL_TAIL_LINES = 30;
|
|
10
|
+
//# sourceMappingURL=symbol-display-constants.js.map
|
|
@@ -25,10 +25,16 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
25
25
|
type: string;
|
|
26
26
|
description: string;
|
|
27
27
|
};
|
|
28
|
+
scope: {
|
|
29
|
+
type: string;
|
|
30
|
+
enum: string[];
|
|
31
|
+
description: string;
|
|
32
|
+
};
|
|
28
33
|
symbol?: undefined;
|
|
29
34
|
context_before?: undefined;
|
|
30
35
|
context_after?: undefined;
|
|
31
36
|
show?: undefined;
|
|
37
|
+
include_edit_context?: undefined;
|
|
32
38
|
symbols?: undefined;
|
|
33
39
|
start_line?: undefined;
|
|
34
40
|
end_line?: undefined;
|
|
@@ -39,10 +45,10 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
39
45
|
include_tests?: undefined;
|
|
40
46
|
include_changes?: undefined;
|
|
41
47
|
paths?: undefined;
|
|
42
|
-
scope?: undefined;
|
|
43
48
|
kind?: undefined;
|
|
44
49
|
limit?: undefined;
|
|
45
50
|
lang?: undefined;
|
|
51
|
+
mode?: undefined;
|
|
46
52
|
include?: undefined;
|
|
47
53
|
recursive?: undefined;
|
|
48
54
|
max_depth?: undefined;
|
|
@@ -87,9 +93,14 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
87
93
|
enum: string[];
|
|
88
94
|
description: string;
|
|
89
95
|
};
|
|
96
|
+
include_edit_context: {
|
|
97
|
+
type: string;
|
|
98
|
+
description: string;
|
|
99
|
+
};
|
|
90
100
|
show_imports?: undefined;
|
|
91
101
|
show_docs?: undefined;
|
|
92
102
|
depth?: undefined;
|
|
103
|
+
scope?: undefined;
|
|
93
104
|
symbols?: undefined;
|
|
94
105
|
start_line?: undefined;
|
|
95
106
|
end_line?: undefined;
|
|
@@ -100,10 +111,10 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
100
111
|
include_tests?: undefined;
|
|
101
112
|
include_changes?: undefined;
|
|
102
113
|
paths?: undefined;
|
|
103
|
-
scope?: undefined;
|
|
104
114
|
kind?: undefined;
|
|
105
115
|
limit?: undefined;
|
|
106
116
|
lang?: undefined;
|
|
117
|
+
mode?: undefined;
|
|
107
118
|
include?: undefined;
|
|
108
119
|
recursive?: undefined;
|
|
109
120
|
max_depth?: undefined;
|
|
@@ -154,7 +165,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
154
165
|
show_imports?: undefined;
|
|
155
166
|
show_docs?: undefined;
|
|
156
167
|
depth?: undefined;
|
|
168
|
+
scope?: undefined;
|
|
157
169
|
symbol?: undefined;
|
|
170
|
+
include_edit_context?: undefined;
|
|
158
171
|
start_line?: undefined;
|
|
159
172
|
end_line?: undefined;
|
|
160
173
|
context_lines?: undefined;
|
|
@@ -164,10 +177,10 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
164
177
|
include_tests?: undefined;
|
|
165
178
|
include_changes?: undefined;
|
|
166
179
|
paths?: undefined;
|
|
167
|
-
scope?: undefined;
|
|
168
180
|
kind?: undefined;
|
|
169
181
|
limit?: undefined;
|
|
170
182
|
lang?: undefined;
|
|
183
|
+
mode?: undefined;
|
|
171
184
|
include?: undefined;
|
|
172
185
|
recursive?: undefined;
|
|
173
186
|
max_depth?: undefined;
|
|
@@ -206,10 +219,12 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
206
219
|
show_imports?: undefined;
|
|
207
220
|
show_docs?: undefined;
|
|
208
221
|
depth?: undefined;
|
|
222
|
+
scope?: undefined;
|
|
209
223
|
symbol?: undefined;
|
|
210
224
|
context_before?: undefined;
|
|
211
225
|
context_after?: undefined;
|
|
212
226
|
show?: undefined;
|
|
227
|
+
include_edit_context?: undefined;
|
|
213
228
|
symbols?: undefined;
|
|
214
229
|
context_lines?: undefined;
|
|
215
230
|
line?: undefined;
|
|
@@ -218,10 +233,10 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
218
233
|
include_tests?: undefined;
|
|
219
234
|
include_changes?: undefined;
|
|
220
235
|
paths?: undefined;
|
|
221
|
-
scope?: undefined;
|
|
222
236
|
kind?: undefined;
|
|
223
237
|
limit?: undefined;
|
|
224
238
|
lang?: undefined;
|
|
239
|
+
mode?: undefined;
|
|
225
240
|
include?: undefined;
|
|
226
241
|
recursive?: undefined;
|
|
227
242
|
max_depth?: undefined;
|
|
@@ -256,10 +271,12 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
256
271
|
show_imports?: undefined;
|
|
257
272
|
show_docs?: undefined;
|
|
258
273
|
depth?: undefined;
|
|
274
|
+
scope?: undefined;
|
|
259
275
|
symbol?: undefined;
|
|
260
276
|
context_before?: undefined;
|
|
261
277
|
context_after?: undefined;
|
|
262
278
|
show?: undefined;
|
|
279
|
+
include_edit_context?: undefined;
|
|
263
280
|
symbols?: undefined;
|
|
264
281
|
start_line?: undefined;
|
|
265
282
|
end_line?: undefined;
|
|
@@ -269,10 +286,10 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
269
286
|
include_tests?: undefined;
|
|
270
287
|
include_changes?: undefined;
|
|
271
288
|
paths?: undefined;
|
|
272
|
-
scope?: undefined;
|
|
273
289
|
kind?: undefined;
|
|
274
290
|
limit?: undefined;
|
|
275
291
|
lang?: undefined;
|
|
292
|
+
mode?: undefined;
|
|
276
293
|
include?: undefined;
|
|
277
294
|
recursive?: undefined;
|
|
278
295
|
max_depth?: undefined;
|
|
@@ -334,17 +351,19 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
334
351
|
show_imports?: undefined;
|
|
335
352
|
show_docs?: undefined;
|
|
336
353
|
depth?: undefined;
|
|
354
|
+
scope?: undefined;
|
|
337
355
|
context_before?: undefined;
|
|
338
356
|
context_after?: undefined;
|
|
339
357
|
show?: undefined;
|
|
358
|
+
include_edit_context?: undefined;
|
|
340
359
|
start_line?: undefined;
|
|
341
360
|
end_line?: undefined;
|
|
342
361
|
context_lines?: undefined;
|
|
343
362
|
paths?: undefined;
|
|
344
|
-
scope?: undefined;
|
|
345
363
|
kind?: undefined;
|
|
346
364
|
limit?: undefined;
|
|
347
365
|
lang?: undefined;
|
|
366
|
+
mode?: undefined;
|
|
348
367
|
include?: undefined;
|
|
349
368
|
recursive?: undefined;
|
|
350
369
|
max_depth?: undefined;
|
|
@@ -379,10 +398,12 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
379
398
|
show_imports?: undefined;
|
|
380
399
|
show_docs?: undefined;
|
|
381
400
|
depth?: undefined;
|
|
401
|
+
scope?: undefined;
|
|
382
402
|
symbol?: undefined;
|
|
383
403
|
context_before?: undefined;
|
|
384
404
|
context_after?: undefined;
|
|
385
405
|
show?: undefined;
|
|
406
|
+
include_edit_context?: undefined;
|
|
386
407
|
symbols?: undefined;
|
|
387
408
|
start_line?: undefined;
|
|
388
409
|
end_line?: undefined;
|
|
@@ -392,10 +413,10 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
392
413
|
include_callers?: undefined;
|
|
393
414
|
include_tests?: undefined;
|
|
394
415
|
include_changes?: undefined;
|
|
395
|
-
scope?: undefined;
|
|
396
416
|
kind?: undefined;
|
|
397
417
|
limit?: undefined;
|
|
398
418
|
lang?: undefined;
|
|
419
|
+
mode?: undefined;
|
|
399
420
|
include?: undefined;
|
|
400
421
|
recursive?: undefined;
|
|
401
422
|
max_depth?: undefined;
|
|
@@ -445,6 +466,11 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
445
466
|
type: string;
|
|
446
467
|
description: string;
|
|
447
468
|
};
|
|
469
|
+
mode: {
|
|
470
|
+
type: string;
|
|
471
|
+
enum: string[];
|
|
472
|
+
description: string;
|
|
473
|
+
};
|
|
448
474
|
path?: undefined;
|
|
449
475
|
show_imports?: undefined;
|
|
450
476
|
show_docs?: undefined;
|
|
@@ -452,6 +478,7 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
452
478
|
context_before?: undefined;
|
|
453
479
|
context_after?: undefined;
|
|
454
480
|
show?: undefined;
|
|
481
|
+
include_edit_context?: undefined;
|
|
455
482
|
symbols?: undefined;
|
|
456
483
|
start_line?: undefined;
|
|
457
484
|
end_line?: undefined;
|
|
@@ -496,10 +523,12 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
496
523
|
show_imports?: undefined;
|
|
497
524
|
show_docs?: undefined;
|
|
498
525
|
depth?: undefined;
|
|
526
|
+
scope?: undefined;
|
|
499
527
|
symbol?: undefined;
|
|
500
528
|
context_before?: undefined;
|
|
501
529
|
context_after?: undefined;
|
|
502
530
|
show?: undefined;
|
|
531
|
+
include_edit_context?: undefined;
|
|
503
532
|
symbols?: undefined;
|
|
504
533
|
start_line?: undefined;
|
|
505
534
|
end_line?: undefined;
|
|
@@ -510,10 +539,10 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
510
539
|
include_tests?: undefined;
|
|
511
540
|
include_changes?: undefined;
|
|
512
541
|
paths?: undefined;
|
|
513
|
-
scope?: undefined;
|
|
514
542
|
kind?: undefined;
|
|
515
543
|
limit?: undefined;
|
|
516
544
|
lang?: undefined;
|
|
545
|
+
mode?: undefined;
|
|
517
546
|
recursive?: undefined;
|
|
518
547
|
max_depth?: undefined;
|
|
519
548
|
verbose?: undefined;
|
|
@@ -543,10 +572,12 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
543
572
|
show_imports?: undefined;
|
|
544
573
|
show_docs?: undefined;
|
|
545
574
|
depth?: undefined;
|
|
575
|
+
scope?: undefined;
|
|
546
576
|
symbol?: undefined;
|
|
547
577
|
context_before?: undefined;
|
|
548
578
|
context_after?: undefined;
|
|
549
579
|
show?: undefined;
|
|
580
|
+
include_edit_context?: undefined;
|
|
550
581
|
symbols?: undefined;
|
|
551
582
|
start_line?: undefined;
|
|
552
583
|
end_line?: undefined;
|
|
@@ -557,10 +588,10 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
557
588
|
include_tests?: undefined;
|
|
558
589
|
include_changes?: undefined;
|
|
559
590
|
paths?: undefined;
|
|
560
|
-
scope?: undefined;
|
|
561
591
|
kind?: undefined;
|
|
562
592
|
limit?: undefined;
|
|
563
593
|
lang?: undefined;
|
|
594
|
+
mode?: undefined;
|
|
564
595
|
include?: undefined;
|
|
565
596
|
recursive?: undefined;
|
|
566
597
|
max_depth?: undefined;
|
|
@@ -599,10 +630,12 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
599
630
|
show_imports?: undefined;
|
|
600
631
|
show_docs?: undefined;
|
|
601
632
|
depth?: undefined;
|
|
633
|
+
scope?: undefined;
|
|
602
634
|
symbol?: undefined;
|
|
603
635
|
context_before?: undefined;
|
|
604
636
|
context_after?: undefined;
|
|
605
637
|
show?: undefined;
|
|
638
|
+
include_edit_context?: undefined;
|
|
606
639
|
symbols?: undefined;
|
|
607
640
|
start_line?: undefined;
|
|
608
641
|
end_line?: undefined;
|
|
@@ -613,10 +646,10 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
613
646
|
include_tests?: undefined;
|
|
614
647
|
include_changes?: undefined;
|
|
615
648
|
paths?: undefined;
|
|
616
|
-
scope?: undefined;
|
|
617
649
|
kind?: undefined;
|
|
618
650
|
limit?: undefined;
|
|
619
651
|
lang?: undefined;
|
|
652
|
+
mode?: undefined;
|
|
620
653
|
include?: undefined;
|
|
621
654
|
verbose?: undefined;
|
|
622
655
|
module?: undefined;
|
|
@@ -646,10 +679,12 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
646
679
|
show_imports?: undefined;
|
|
647
680
|
show_docs?: undefined;
|
|
648
681
|
depth?: undefined;
|
|
682
|
+
scope?: undefined;
|
|
649
683
|
symbol?: undefined;
|
|
650
684
|
context_before?: undefined;
|
|
651
685
|
context_after?: undefined;
|
|
652
686
|
show?: undefined;
|
|
687
|
+
include_edit_context?: undefined;
|
|
653
688
|
symbols?: undefined;
|
|
654
689
|
start_line?: undefined;
|
|
655
690
|
end_line?: undefined;
|
|
@@ -660,10 +695,10 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
660
695
|
include_tests?: undefined;
|
|
661
696
|
include_changes?: undefined;
|
|
662
697
|
paths?: undefined;
|
|
663
|
-
scope?: undefined;
|
|
664
698
|
kind?: undefined;
|
|
665
699
|
limit?: undefined;
|
|
666
700
|
lang?: undefined;
|
|
701
|
+
mode?: undefined;
|
|
667
702
|
include?: undefined;
|
|
668
703
|
recursive?: undefined;
|
|
669
704
|
max_depth?: undefined;
|
|
@@ -702,10 +737,12 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
702
737
|
show_imports?: undefined;
|
|
703
738
|
show_docs?: undefined;
|
|
704
739
|
depth?: undefined;
|
|
740
|
+
scope?: undefined;
|
|
705
741
|
symbol?: undefined;
|
|
706
742
|
context_before?: undefined;
|
|
707
743
|
context_after?: undefined;
|
|
708
744
|
show?: undefined;
|
|
745
|
+
include_edit_context?: undefined;
|
|
709
746
|
symbols?: undefined;
|
|
710
747
|
start_line?: undefined;
|
|
711
748
|
end_line?: undefined;
|
|
@@ -716,9 +753,9 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
716
753
|
include_tests?: undefined;
|
|
717
754
|
include_changes?: undefined;
|
|
718
755
|
paths?: undefined;
|
|
719
|
-
scope?: undefined;
|
|
720
756
|
kind?: undefined;
|
|
721
757
|
lang?: undefined;
|
|
758
|
+
mode?: undefined;
|
|
722
759
|
include?: undefined;
|
|
723
760
|
recursive?: undefined;
|
|
724
761
|
max_depth?: undefined;
|
|
@@ -765,10 +802,12 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
765
802
|
show_imports?: undefined;
|
|
766
803
|
show_docs?: undefined;
|
|
767
804
|
depth?: undefined;
|
|
805
|
+
scope?: undefined;
|
|
768
806
|
symbol?: undefined;
|
|
769
807
|
context_before?: undefined;
|
|
770
808
|
context_after?: undefined;
|
|
771
809
|
show?: undefined;
|
|
810
|
+
include_edit_context?: undefined;
|
|
772
811
|
symbols?: undefined;
|
|
773
812
|
start_line?: undefined;
|
|
774
813
|
end_line?: undefined;
|
|
@@ -779,8 +818,8 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
779
818
|
include_tests?: undefined;
|
|
780
819
|
include_changes?: undefined;
|
|
781
820
|
paths?: undefined;
|
|
782
|
-
scope?: undefined;
|
|
783
821
|
kind?: undefined;
|
|
822
|
+
mode?: undefined;
|
|
784
823
|
include?: undefined;
|
|
785
824
|
recursive?: undefined;
|
|
786
825
|
max_depth?: undefined;
|
|
@@ -814,10 +853,12 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
814
853
|
show_imports?: undefined;
|
|
815
854
|
show_docs?: undefined;
|
|
816
855
|
depth?: undefined;
|
|
856
|
+
scope?: undefined;
|
|
817
857
|
symbol?: undefined;
|
|
818
858
|
context_before?: undefined;
|
|
819
859
|
context_after?: undefined;
|
|
820
860
|
show?: undefined;
|
|
861
|
+
include_edit_context?: undefined;
|
|
821
862
|
symbols?: undefined;
|
|
822
863
|
start_line?: undefined;
|
|
823
864
|
end_line?: undefined;
|
|
@@ -828,10 +869,10 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
828
869
|
include_tests?: undefined;
|
|
829
870
|
include_changes?: undefined;
|
|
830
871
|
paths?: undefined;
|
|
831
|
-
scope?: undefined;
|
|
832
872
|
kind?: undefined;
|
|
833
873
|
limit?: undefined;
|
|
834
874
|
lang?: undefined;
|
|
875
|
+
mode?: undefined;
|
|
835
876
|
include?: undefined;
|
|
836
877
|
recursive?: undefined;
|
|
837
878
|
max_depth?: undefined;
|
|
@@ -873,6 +914,7 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
873
914
|
context_before?: undefined;
|
|
874
915
|
context_after?: undefined;
|
|
875
916
|
show?: undefined;
|
|
917
|
+
include_edit_context?: undefined;
|
|
876
918
|
symbols?: undefined;
|
|
877
919
|
start_line?: undefined;
|
|
878
920
|
end_line?: undefined;
|
|
@@ -886,6 +928,7 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
886
928
|
kind?: undefined;
|
|
887
929
|
limit?: undefined;
|
|
888
930
|
lang?: undefined;
|
|
931
|
+
mode?: undefined;
|
|
889
932
|
include?: undefined;
|
|
890
933
|
recursive?: undefined;
|
|
891
934
|
max_depth?: undefined;
|
|
@@ -923,10 +966,12 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
923
966
|
show_imports?: undefined;
|
|
924
967
|
show_docs?: undefined;
|
|
925
968
|
depth?: undefined;
|
|
969
|
+
scope?: undefined;
|
|
926
970
|
symbol?: undefined;
|
|
927
971
|
context_before?: undefined;
|
|
928
972
|
context_after?: undefined;
|
|
929
973
|
show?: undefined;
|
|
974
|
+
include_edit_context?: undefined;
|
|
930
975
|
symbols?: undefined;
|
|
931
976
|
start_line?: undefined;
|
|
932
977
|
end_line?: undefined;
|
|
@@ -937,10 +982,10 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
937
982
|
include_tests?: undefined;
|
|
938
983
|
include_changes?: undefined;
|
|
939
984
|
paths?: undefined;
|
|
940
|
-
scope?: undefined;
|
|
941
985
|
kind?: undefined;
|
|
942
986
|
limit?: undefined;
|
|
943
987
|
lang?: undefined;
|
|
988
|
+
mode?: undefined;
|
|
944
989
|
recursive?: undefined;
|
|
945
990
|
max_depth?: undefined;
|
|
946
991
|
verbose?: undefined;
|
|
@@ -978,10 +1023,12 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
978
1023
|
show_imports?: undefined;
|
|
979
1024
|
show_docs?: undefined;
|
|
980
1025
|
depth?: undefined;
|
|
1026
|
+
scope?: undefined;
|
|
981
1027
|
symbol?: undefined;
|
|
982
1028
|
context_before?: undefined;
|
|
983
1029
|
context_after?: undefined;
|
|
984
1030
|
show?: undefined;
|
|
1031
|
+
include_edit_context?: undefined;
|
|
985
1032
|
symbols?: undefined;
|
|
986
1033
|
start_line?: undefined;
|
|
987
1034
|
end_line?: undefined;
|
|
@@ -992,10 +1039,10 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
992
1039
|
include_tests?: undefined;
|
|
993
1040
|
include_changes?: undefined;
|
|
994
1041
|
paths?: undefined;
|
|
995
|
-
scope?: undefined;
|
|
996
1042
|
kind?: undefined;
|
|
997
1043
|
limit?: undefined;
|
|
998
1044
|
lang?: undefined;
|
|
1045
|
+
mode?: undefined;
|
|
999
1046
|
include?: undefined;
|
|
1000
1047
|
recursive?: undefined;
|
|
1001
1048
|
max_depth?: undefined;
|
|
@@ -1034,10 +1081,12 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
1034
1081
|
show_imports?: undefined;
|
|
1035
1082
|
show_docs?: undefined;
|
|
1036
1083
|
depth?: undefined;
|
|
1084
|
+
scope?: undefined;
|
|
1037
1085
|
symbol?: undefined;
|
|
1038
1086
|
context_before?: undefined;
|
|
1039
1087
|
context_after?: undefined;
|
|
1040
1088
|
show?: undefined;
|
|
1089
|
+
include_edit_context?: undefined;
|
|
1041
1090
|
symbols?: undefined;
|
|
1042
1091
|
start_line?: undefined;
|
|
1043
1092
|
end_line?: undefined;
|
|
@@ -1048,10 +1097,10 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
1048
1097
|
include_tests?: undefined;
|
|
1049
1098
|
include_changes?: undefined;
|
|
1050
1099
|
paths?: undefined;
|
|
1051
|
-
scope?: undefined;
|
|
1052
1100
|
kind?: undefined;
|
|
1053
1101
|
limit?: undefined;
|
|
1054
1102
|
lang?: undefined;
|
|
1103
|
+
mode?: undefined;
|
|
1055
1104
|
include?: undefined;
|
|
1056
1105
|
recursive?: undefined;
|
|
1057
1106
|
max_depth?: undefined;
|
|
@@ -9,26 +9,30 @@ export const MCP_INSTRUCTIONS = [
|
|
|
9
9
|
'1. New codebase / unfamiliar project → project_overview',
|
|
10
10
|
'2. Starting work on a directory → explore_area (outline + imports + tests + git log in one call)',
|
|
11
11
|
'3. Need to read a code file → smart_read (NOT Read/cat — returns structure, 60-80% fewer tokens)',
|
|
12
|
+
' - For navigation/browsing: smart_read(scope="nav") — names + lines only, 2-3x smaller',
|
|
13
|
+
' - For public API overview: smart_read(scope="exports")',
|
|
12
14
|
'4. Need one function/class body → read_symbol (loads only that symbol, NOT the whole file)',
|
|
13
|
-
'
|
|
14
|
-
'5.
|
|
15
|
-
'6.
|
|
16
|
-
'7.
|
|
17
|
-
'8.
|
|
18
|
-
'9.
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
15
|
+
' - Preparing edit? Add include_edit_context=true to skip separate read_for_edit call',
|
|
16
|
+
'5. Need MULTIPLE function/class bodies from same file → read_symbols (batch — one call instead of N)',
|
|
17
|
+
'6. Preparing an edit → read_for_edit (returns exact text for Edit old_string)',
|
|
18
|
+
'7. Verify edits after editing → read_diff (only changed hunks — REQUIRES smart_read BEFORE editing)',
|
|
19
|
+
'8. Multiple files at once → smart_read_many (batch up to 20 files)',
|
|
20
|
+
'9. Find where a symbol is used → find_usages (semantic: definitions + imports + usages)',
|
|
21
|
+
' - For initial discovery: find_usages(mode="list") — file:line only, 5-10x smaller',
|
|
22
|
+
'10. Understand file dependencies → related_files (imports, importers, tests — ranked by relevance)',
|
|
23
|
+
'11. List all symbols in a directory → outline (classes, functions, methods in one call)',
|
|
24
|
+
'12. Review git changes → smart_diff (NOT git diff — maps changes to functions/classes)',
|
|
25
|
+
'13. Commit history → smart_log (NOT git log — structured with categories)',
|
|
26
|
+
'14. Run tests → test_summary (NOT raw test output — structured pass/fail)',
|
|
27
|
+
'15. Code quality → code_audit (TODOs, deprecated, structural patterns)',
|
|
28
|
+
'16. Dead code → find_unused (unreferenced symbols across project)',
|
|
29
|
+
'17. Module architecture → module_info (deps, dependents, public API)',
|
|
26
30
|
'',
|
|
27
31
|
'USE DEFAULT TOOLS ONLY FOR: regex text search → Grep | exact raw content → Read | non-code configs → Read',
|
|
28
32
|
'',
|
|
29
33
|
'WORKFLOWS:',
|
|
30
34
|
'• Explore: project_overview → explore_area → smart_read → read_symbol',
|
|
31
|
-
'• Edit: smart_read →
|
|
35
|
+
'• Edit: smart_read → read_symbol(include_edit_context=true) → Edit → read_diff',
|
|
32
36
|
'• Refactor: find_usages → read_symbols → read_for_edit → Edit → test_summary',
|
|
33
37
|
'• Audit: code_audit + find_unused + Grep (for regex patterns)',
|
|
34
38
|
].join('\n');
|
|
@@ -44,6 +48,11 @@ export const TOOL_DEFINITIONS = [
|
|
|
44
48
|
show_imports: { type: 'boolean', description: 'Include import details (default: true)' },
|
|
45
49
|
show_docs: { type: 'boolean', description: 'Include doc comments (default: true)' },
|
|
46
50
|
depth: { type: 'number', description: 'Max depth for nested symbols (default: 2)' },
|
|
51
|
+
scope: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
enum: ['full', 'nav', 'exports'],
|
|
54
|
+
description: 'Output scope: full (default, all details), nav (names + lines only, 2-3x smaller), exports (public API only)',
|
|
55
|
+
},
|
|
47
56
|
},
|
|
48
57
|
required: ['path'],
|
|
49
58
|
},
|
|
@@ -59,13 +68,17 @@ export const TOOL_DEFINITIONS = [
|
|
|
59
68
|
context_before: { type: 'number', description: 'Lines of context before (default: 2)' },
|
|
60
69
|
context_after: { type: 'number', description: 'Lines of context after (default: 0)' },
|
|
61
70
|
show: { type: 'string', enum: ['full', 'head', 'tail', 'outline'], description: 'Display mode: full (all lines), head (first 50), tail (last 30), outline (head + methods + tail). Default: auto (full ≤300 lines, outline >300)' },
|
|
71
|
+
include_edit_context: {
|
|
72
|
+
type: 'boolean',
|
|
73
|
+
description: 'Append raw code block for Edit old_string (saves a read_for_edit call)',
|
|
74
|
+
},
|
|
62
75
|
},
|
|
63
76
|
required: ['path', 'symbol'],
|
|
64
77
|
},
|
|
65
78
|
},
|
|
66
79
|
{
|
|
67
80
|
name: 'read_symbols',
|
|
68
|
-
description: 'Batch read MULTIPLE symbols from ONE file
|
|
81
|
+
description: 'Batch read MULTIPLE symbols from ONE file for UNDERSTANDING code — saves N-1 round-trips vs calling read_symbol N times. Returns formatted symbol bodies with show modes (full/head/tail/outline). Use this when READING code, not editing. For edit preparation use read_for_edit instead.',
|
|
69
82
|
inputSchema: {
|
|
70
83
|
type: 'object',
|
|
71
84
|
properties: {
|
|
@@ -109,7 +122,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
109
122
|
},
|
|
110
123
|
{
|
|
111
124
|
name: 'read_for_edit',
|
|
112
|
-
description: 'Use INSTEAD OF Read when preparing an
|
|
125
|
+
description: 'Use INSTEAD OF Read when preparing an EDIT. Returns exact RAW code around a symbol or line — copy directly as old_string for Edit tool. Supports batch: pass "symbols" array to get multiple edit contexts in one call. Unlike read_symbols (for reading/understanding), this returns unformatted code optimized for copy-paste into Edit. Optional: include_callers, include_tests, include_changes for enriched context.',
|
|
113
126
|
inputSchema: {
|
|
114
127
|
type: 'object',
|
|
115
128
|
properties: {
|
|
@@ -157,6 +170,11 @@ export const TOOL_DEFINITIONS = [
|
|
|
157
170
|
limit: { type: 'number', description: 'Max results per category (default: 50, max: 500)' },
|
|
158
171
|
lang: { type: 'string', description: 'Filter by language/extension (e.g., "php", "typescript")' },
|
|
159
172
|
context_lines: { type: 'number', description: 'Lines of source context around each match (0-10). When set, shows surrounding code — saves follow-up read_symbol calls.' },
|
|
173
|
+
mode: {
|
|
174
|
+
type: 'string',
|
|
175
|
+
enum: ['full', 'list'],
|
|
176
|
+
description: 'Output mode: full (with context, default), list (file:line only, 5-10x smaller for initial discovery)',
|
|
177
|
+
},
|
|
160
178
|
},
|
|
161
179
|
required: ['symbol'],
|
|
162
180
|
},
|
package/dist/server.js
CHANGED
|
@@ -273,6 +273,7 @@ export async function createServer(projectRoot, options) {
|
|
|
273
273
|
return nonCodeResult;
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
|
+
// Dedup is handled inside handleSmartRead (step 5)
|
|
276
277
|
const result = await handleSmartRead(validArgs, projectRoot, astIndex, fileCache, contextRegistry, config);
|
|
277
278
|
const text = result.content[0]?.text ?? '';
|
|
278
279
|
const fullTokensSR = await fullFileTokens(validArgs.path);
|
|
@@ -283,6 +284,7 @@ export async function createServer(projectRoot, options) {
|
|
|
283
284
|
}
|
|
284
285
|
case 'read_symbol': {
|
|
285
286
|
const symArgs = validateReadSymbolArgs(args);
|
|
287
|
+
// Dedup is handled inside handleReadSymbol
|
|
286
288
|
const symResult = await handleReadSymbol(symArgs, projectRoot, symbolResolver, fileCache, contextRegistry, astIndex, config.smartRead.advisoryReminders);
|
|
287
289
|
const symText = symResult.content[0]?.text ?? '';
|
|
288
290
|
const symTokens = estimateTokens(symText);
|
|
@@ -319,7 +321,7 @@ export async function createServer(projectRoot, options) {
|
|
|
319
321
|
}
|
|
320
322
|
case 'read_for_edit': {
|
|
321
323
|
const editArgs = validateReadForEditArgs(args);
|
|
322
|
-
const editResult = await handleReadForEdit(editArgs, projectRoot, symbolResolver, fileCache, contextRegistry, astIndex);
|
|
324
|
+
const editResult = await handleReadForEdit(editArgs, projectRoot, symbolResolver, fileCache, contextRegistry, astIndex, { actionableHints: config.display.actionableHints });
|
|
323
325
|
const editText = editResult.content[0]?.text ?? '';
|
|
324
326
|
const editTokens = estimateTokens(editText);
|
|
325
327
|
const fullTokensEdit = await fullFileTokens(editArgs.path);
|
package/dist/types.d.ts
CHANGED
|
@@ -59,6 +59,7 @@ export interface ContextEntry {
|
|
|
59
59
|
contentHash: string;
|
|
60
60
|
tokenEstimate: number;
|
|
61
61
|
loadedAt: number;
|
|
62
|
+
symbolNames?: string[];
|
|
62
63
|
}
|
|
63
64
|
export interface LoadedRegion {
|
|
64
65
|
type: 'structure' | 'symbol' | 'range' | 'full';
|
|
@@ -89,6 +90,10 @@ export interface TokenPilotConfig {
|
|
|
89
90
|
smallFileThreshold: number;
|
|
90
91
|
showDependencyHints: boolean;
|
|
91
92
|
advisoryReminders: boolean;
|
|
93
|
+
autoDelta: {
|
|
94
|
+
enabled: boolean;
|
|
95
|
+
maxAgeSec: number;
|
|
96
|
+
};
|
|
92
97
|
};
|
|
93
98
|
git: {
|
|
94
99
|
watchHead: boolean;
|
|
@@ -110,6 +115,7 @@ export interface TokenPilotConfig {
|
|
|
110
115
|
showReferences: boolean;
|
|
111
116
|
maxDepth: number;
|
|
112
117
|
showTokenSavings: boolean;
|
|
118
|
+
actionableHints: boolean;
|
|
113
119
|
};
|
|
114
120
|
contextMode: {
|
|
115
121
|
enabled: boolean | 'auto';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "token-pilot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Save up to 80% tokens when AI reads code — MCP server for token-efficient code navigation, AST-aware structural reading instead of dumping full files into context window",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|