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 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
@@ -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);
@@ -59,6 +59,7 @@ export interface FindUsagesArgs {
59
59
  limit?: number;
60
60
  lang?: string;
61
61
  context_lines?: number;
62
+ mode?: 'full' | 'list';
62
63
  }
63
64
  export declare function validateFindUsagesArgs(args: unknown): FindUsagesArgs;
64
65
  /**
@@ -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
  /**
@@ -5,6 +5,7 @@ export interface FormatOptions {
5
5
  showDependencyHints?: boolean;
6
6
  maxDepth?: number;
7
7
  showTokenSavings?: boolean;
8
+ scope?: 'full' | 'nav' | 'exports';
8
9
  }
9
10
  /**
10
11
  * Format a FileStructure as token-optimized text for LLMs.
@@ -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
- lines.push(`FILE: ${structure.path} (${structure.meta.lines} lines, ${sizeKB}KB)`);
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
- try {
75
- const content = await readFile(resolve(projectRoot, file), 'utf-8');
76
- fileLines = content.split('\n');
80
+ if (fileCache.has(file)) {
81
+ fileLines = fileCache.get(file);
77
82
  }
78
- catch {
79
- // File unreadable — fall back to text-only
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
- const [defSection, impSection, useSection] = await Promise.all([
236
- renderSectionWithContext('DEFINITIONS', definitions, args.context_lines, projectRoot),
237
- renderSectionWithContext('IMPORTS', allImports, args.context_lines, projectRoot),
238
- renderSectionWithContext('USAGES', allUsages, args.context_lines, projectRoot),
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): Promise<{
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
- const MAX_SYMBOL_LINES = 300;
52
- const MAX_FULL_LINES = 500;
53
- const HEAD = 50;
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
- '4b. Need MULTIPLE function/class bodies from same file read_symbols (batch — one call instead of N)',
14
- '5. Preparing an editread_for_edit (returns exact text for Edit old_string)',
15
- '6. Verify edits after editing read_diff (only changed hunks REQUIRES smart_read BEFORE editing)',
16
- '7. Multiple files at oncesmart_read_many (batch up to 20 files)',
17
- '8. Find where a symbol is used find_usages (semantic: definitions + imports + usages)',
18
- '9. Understand file dependenciesrelated_files (imports, importers, tests ranked by relevance)',
19
- '10. List all symbols in a directory → outline (classes, functions, methods in one call)',
20
- '11. Review git changessmart_diff (NOT git diffmaps changes to functions/classes)',
21
- '12. Commit historysmart_log (NOT git log structured with categories)',
22
- '13. Run teststest_summary (NOT raw test output structured pass/fail)',
23
- '14. Code qualitycode_audit (TODOs, deprecated, structural patterns)',
24
- '15. Dead codefind_unused (unreferenced symbols across project)',
25
- '16. Module architecturemodule_info (deps, dependents, public API)',
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 editread_for_edit (returns exact text for Edit old_string)',
18
+ '7. Verify edits after editingread_diff (only changed hunks REQUIRES smart_read BEFORE editing)',
19
+ '8. Multiple files at oncesmart_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 dependenciesrelated_files (imports, importers, testsranked 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 diffmaps changes to functions/classes)',
25
+ '13. Commit historysmart_log (NOT git log — structured with categories)',
26
+ '14. Run teststest_summary (NOT raw test output — structured pass/fail)',
27
+ '15. Code qualitycode_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 → read_for_edit → Edit → read_diff',
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 in a single call — saves N-1 round-trips vs calling read_symbol N times. Returns all requested symbol bodies.',
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 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. Optional: include_callers, include_tests, include_changes for enriched context.',
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.16.2",
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",