token-pilot 0.13.0 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/.claude-plugin/hooks/hooks.json +9 -0
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/CHANGELOG.md +29 -0
  5. package/README.md +36 -15
  6. package/dist/config/defaults.js +12 -0
  7. package/dist/core/architecture-fingerprint.d.ts +34 -0
  8. package/dist/core/architecture-fingerprint.js +127 -0
  9. package/dist/core/budget-planner.d.ts +21 -0
  10. package/dist/core/budget-planner.js +68 -0
  11. package/dist/core/confidence.d.ts +31 -0
  12. package/dist/core/confidence.js +99 -0
  13. package/dist/core/context-registry.d.ts +14 -0
  14. package/dist/core/context-registry.js +55 -0
  15. package/dist/core/decision-trace.d.ts +31 -0
  16. package/dist/core/decision-trace.js +45 -0
  17. package/dist/core/intent-classifier.d.ts +13 -0
  18. package/dist/core/intent-classifier.js +44 -0
  19. package/dist/core/policy-engine.d.ts +41 -0
  20. package/dist/core/policy-engine.js +76 -0
  21. package/dist/core/session-analytics.d.ts +8 -0
  22. package/dist/core/session-analytics.js +86 -7
  23. package/dist/core/session-cache.d.ts +74 -0
  24. package/dist/core/session-cache.js +162 -0
  25. package/dist/core/validation.d.ts +3 -0
  26. package/dist/core/validation.js +3 -0
  27. package/dist/git/file-watcher.d.ts +6 -0
  28. package/dist/git/file-watcher.js +18 -2
  29. package/dist/git/watcher.d.ts +3 -0
  30. package/dist/git/watcher.js +6 -0
  31. package/dist/handlers/code-audit.d.ts +7 -2
  32. package/dist/handlers/code-audit.js +19 -5
  33. package/dist/handlers/explore-area.d.ts +10 -0
  34. package/dist/handlers/explore-area.js +39 -13
  35. package/dist/handlers/find-unused.d.ts +3 -0
  36. package/dist/handlers/find-unused.js +3 -2
  37. package/dist/handlers/find-usages.d.ts +7 -0
  38. package/dist/handlers/find-usages.js +36 -5
  39. package/dist/handlers/module-info.d.ts +3 -0
  40. package/dist/handlers/module-info.js +22 -2
  41. package/dist/handlers/project-overview.d.ts +1 -1
  42. package/dist/handlers/project-overview.js +18 -2
  43. package/dist/handlers/read-for-edit.d.ts +3 -0
  44. package/dist/handlers/read-for-edit.js +185 -3
  45. package/dist/handlers/read-range.d.ts +1 -1
  46. package/dist/handlers/read-range.js +16 -1
  47. package/dist/handlers/read-symbol.d.ts +1 -1
  48. package/dist/handlers/read-symbol.js +26 -2
  49. package/dist/handlers/related-files.d.ts +11 -0
  50. package/dist/handlers/related-files.js +178 -42
  51. package/dist/handlers/smart-read-many.js +70 -16
  52. package/dist/handlers/smart-read.js +10 -1
  53. package/dist/handlers/test-summary.js +26 -3
  54. package/dist/hooks/installer.d.ts +12 -8
  55. package/dist/hooks/installer.js +24 -8
  56. package/dist/index.d.ts +16 -1
  57. package/dist/index.js +62 -56
  58. package/dist/server.js +395 -30
  59. package/dist/types.d.ts +12 -0
  60. package/package.json +18 -14
  61. package/start.sh +28 -27
  62. package/dist/handlers/class-hierarchy.d.ts +0 -11
  63. package/dist/handlers/class-hierarchy.js +0 -28
  64. package/dist/handlers/export-ast-index.d.ts +0 -22
  65. package/dist/handlers/export-ast-index.js +0 -175
  66. package/dist/handlers/find-implementations.d.ts +0 -11
  67. package/dist/handlers/find-implementations.js +0 -27
  68. package/dist/handlers/search-code.d.ts +0 -14
  69. package/dist/handlers/search-code.js +0 -32
@@ -1,3 +1,4 @@
1
+ import { assessConfidence, formatConfidence } from '../core/confidence.js';
1
2
  /**
2
3
  * Escape special regex characters in a string.
3
4
  */
@@ -33,10 +34,16 @@ const LANG_EXT_MAP = {
33
34
  */
34
35
  export async function handleFindUsages(args, astIndex) {
35
36
  if (astIndex.isDisabled() || astIndex.isOversized()) {
36
- return { content: [{ type: 'text', text: 'find_usages is disabled: ' + (astIndex.isDisabled()
37
+ return {
38
+ content: [{
39
+ type: 'text',
40
+ text: 'find_usages is disabled: ' + (astIndex.isDisabled()
37
41
  ? 'project root not detected. Call smart_read() on any project file first — this auto-detects the project root and enables ast-index tools.'
38
- : 'ast-index built >50k files (likely includes node_modules). Ensure node_modules is in .gitignore.') +
39
- '\nAlternative: use grep_search to find symbol references.' }] };
42
+ : 'ast-index built >50k files (likely includes node_modules). Ensure node_modules is in .gitignore.')
43
+ + '\nAlternative: use grep_search to find symbol references.',
44
+ }],
45
+ meta: { files: [], definitions: 0, imports: 0, usages: 0, total: 0 },
46
+ };
40
47
  }
41
48
  // Run refs + search in parallel
42
49
  const [refs, searchResults] = await Promise.all([
@@ -129,7 +136,10 @@ export async function handleFindUsages(args, astIndex) {
129
136
  if (!astIndex.isAvailable()) {
130
137
  hints.push('WARNING: ast-index is not available.');
131
138
  }
132
- return { content: [{ type: 'text', text: hints.join('\n') }] };
139
+ return {
140
+ content: [{ type: 'text', text: hints.join('\n') }],
141
+ meta: { files: [], definitions: 0, imports: 0, usages: 0, total: 0 },
142
+ };
133
143
  }
134
144
  // Build header with active filters
135
145
  const filterHints = [];
@@ -169,6 +179,27 @@ export async function handleFindUsages(args, astIndex) {
169
179
  lines.push('');
170
180
  }
171
181
  lines.push('HINT: Use read_symbol() or read_range() to load specific results.');
172
- return { content: [{ type: 'text', text: lines.join('\n') }] };
182
+ // Confidence metadata
183
+ const confidenceMeta = assessConfidence({
184
+ refsFound: totalCount > 0,
185
+ astAvailable: astIndex.isAvailable(),
186
+ symbolResolved: definitions.length > 0,
187
+ });
188
+ lines.push(formatConfidence(confidenceMeta));
189
+ const files = Array.from(new Set([
190
+ ...definitions.map((d) => d.file),
191
+ ...allImports.map((i) => i.file),
192
+ ...allUsages.map((u) => u.file),
193
+ ])).sort();
194
+ return {
195
+ content: [{ type: 'text', text: lines.join('\n') }],
196
+ meta: {
197
+ files,
198
+ definitions: definitions.length,
199
+ imports: allImports.length,
200
+ usages: allUsages.length,
201
+ total: totalCount,
202
+ },
203
+ };
173
204
  }
174
205
  //# sourceMappingURL=find-usages.js.map
@@ -5,5 +5,8 @@ export declare function handleModuleInfo(args: ModuleInfoArgs, projectRoot: stri
5
5
  type: 'text';
6
6
  text: string;
7
7
  }>;
8
+ meta: {
9
+ files: string[];
10
+ };
8
11
  }>;
9
12
  //# sourceMappingURL=module-info.d.ts.map
@@ -8,6 +8,7 @@ export async function handleModuleInfo(args, projectRoot, astIndex) {
8
8
  text: '⚠ ast-index unavailable — module_info requires ast-index.\n' +
9
9
  'DEGRADED: Use find_usages() + related_files() as alternatives for dependency analysis.',
10
10
  }],
11
+ meta: { files: [] },
11
12
  };
12
13
  }
13
14
  const check = args.check ?? 'all';
@@ -36,7 +37,7 @@ export async function handleModuleInfo(args, projectRoot, astIndex) {
36
37
  sections.push('No modules detected. ast-index module analysis requires a modular project structure.');
37
38
  sections.push('HINT: Use find_usages() for cross-file symbol references, related_files() for import graphs.');
38
39
  }
39
- return { content: [{ type: 'text', text: sections.join('\n') }] };
40
+ return { content: [{ type: 'text', text: sections.join('\n') }], meta: { files: [] } };
40
41
  }
41
42
  sections.push('');
42
43
  // Run requested checks in parallel
@@ -115,7 +116,26 @@ export async function handleModuleInfo(args, projectRoot, astIndex) {
115
116
  }
116
117
  }
117
118
  sections.push('HINT: Use smart_read() on module files, find_usages() for cross-module references.');
118
- return { content: [{ type: 'text', text: sections.join('\n') }] };
119
+ // Collect files from results for analytics
120
+ const metaFiles = [];
121
+ for (const r of results) {
122
+ if (r.status !== 'fulfilled')
123
+ continue;
124
+ const { type, data } = r.value;
125
+ switch (type) {
126
+ case 'api':
127
+ for (const a of data)
128
+ metaFiles.push(a.file);
129
+ break;
130
+ case 'deps':
131
+ case 'dependents':
132
+ case 'unused-deps':
133
+ for (const d of data)
134
+ metaFiles.push(d.path);
135
+ break;
136
+ }
137
+ }
138
+ return { content: [{ type: 'text', text: sections.join('\n') }], meta: { files: [...new Set(metaFiles)] } };
119
139
  }
120
140
  function rel(projectRoot, absPath) {
121
141
  return relative(projectRoot, absPath) || absPath;
@@ -1,6 +1,6 @@
1
1
  import type { AstIndexClient } from '../ast-index/client.js';
2
2
  import type { ProjectOverviewArgs } from '../core/validation.js';
3
- export declare function handleProjectOverview(args: ProjectOverviewArgs, projectRoot: string, astIndex: AstIndexClient): Promise<{
3
+ export declare function handleProjectOverview(args: ProjectOverviewArgs, projectRoot: string, astIndex: AstIndexClient, appVersion?: string): Promise<{
4
4
  content: Array<{
5
5
  type: 'text';
6
6
  text: string;
@@ -1,6 +1,13 @@
1
1
  import { detectProject } from '../core/project-detector.js';
2
- export async function handleProjectOverview(args, projectRoot, astIndex) {
2
+ import { loadFingerprint, saveFingerprint, buildFingerprint, formatCachedFingerprint, } from '../core/architecture-fingerprint.js';
3
+ export async function handleProjectOverview(args, projectRoot, astIndex, appVersion) {
3
4
  const lines = [];
5
+ // Check for cached fingerprint
6
+ const cachedFp = await loadFingerprint(projectRoot);
7
+ if (cachedFp) {
8
+ lines.push(formatCachedFingerprint(cachedFp));
9
+ lines.push('');
10
+ }
4
11
  // 1. Dual detection: ast-index + config scanner
5
12
  let astIndexType;
6
13
  let mapData = null;
@@ -117,7 +124,16 @@ export async function handleProjectOverview(args, projectRoot, astIndex) {
117
124
  lines.push('');
118
125
  }
119
126
  lines.push('HINT: Use smart_read() on files, find_usages() for symbol references, outline() for directory overview.');
120
- return { content: [{ type: 'text', text: lines.join('\n') }] };
127
+ // Save fingerprint for next session
128
+ const outputText = lines.join('\n');
129
+ try {
130
+ const fp = buildFingerprint(outputText, appVersion ?? 'unknown');
131
+ await saveFingerprint(projectRoot, fp);
132
+ }
133
+ catch {
134
+ // Non-critical — don't fail the handler
135
+ }
136
+ return { content: [{ type: 'text', text: outputText }] };
121
137
  }
122
138
  // ──────────────────────────────────────────────
123
139
  // Formatters
@@ -7,6 +7,9 @@ export interface ReadForEditArgs {
7
7
  symbol?: string;
8
8
  line?: number;
9
9
  context?: number;
10
+ include_callers?: boolean;
11
+ include_tests?: boolean;
12
+ include_changes?: boolean;
10
13
  }
11
14
  export declare function handleReadForEdit(args: ReadForEditArgs, projectRoot: string, symbolResolver: SymbolResolver, fileCache: FileCache, contextRegistry: ContextRegistry, astIndex: AstIndexClient): Promise<{
12
15
  content: Array<{
@@ -1,7 +1,12 @@
1
- import { readFile, stat } from 'node:fs/promises';
1
+ import { readFile, stat, access } from 'node:fs/promises';
2
+ import { execFile } from 'node:child_process';
3
+ import { promisify } from 'node:util';
2
4
  import { createHash } from 'node:crypto';
5
+ import { relative, join } from 'node:path';
3
6
  import { estimateTokens } from '../core/token-estimator.js';
4
7
  import { resolveSafePath } from '../core/validation.js';
8
+ import { assessConfidence, formatConfidence } from '../core/confidence.js';
9
+ const execFileAsync = promisify(execFile);
5
10
  const DEFAULT_CONTEXT = 5;
6
11
  export async function handleReadForEdit(args, projectRoot, symbolResolver, fileCache, contextRegistry, astIndex) {
7
12
  const absPath = resolveSafePath(projectRoot, args.path);
@@ -91,7 +96,7 @@ export async function handleReadForEdit(args, projectRoot, symbolResolver, fileC
91
96
  const rangeCount = rangeEnd - rangeStart + 1;
92
97
  // Extract RAW code (no line number prefixes — ready for Edit old_string)
93
98
  const rawCode = lines.slice(rangeStart - 1, rangeEnd).join('\n');
94
- const output = [
99
+ const outputLines = [
95
100
  `--- EDIT CONTEXT ---`,
96
101
  `FILE: ${args.path}`,
97
102
  `TARGET: ${targetLabel}`,
@@ -103,7 +108,54 @@ export async function handleReadForEdit(args, projectRoot, symbolResolver, fileC
103
108
  '',
104
109
  `To edit: use exact text above as old_string in Edit tool.`,
105
110
  `For Read requirement: Read("${args.path}", offset=${rangeStart}, limit=${rangeCount})`,
106
- ].join('\n');
111
+ ];
112
+ // --- Optional enrichment sections ---
113
+ // include_callers: compact caller list via ast-index refs
114
+ if (args.include_callers && args.symbol && !astIndex.isDisabled()) {
115
+ try {
116
+ const refs = await astIndex.refs(args.symbol, 10);
117
+ const callers = refs.usages.slice(0, 5);
118
+ if (callers.length > 0) {
119
+ outputLines.push('');
120
+ outputLines.push(`CALLERS (${callers.length}):`);
121
+ for (const c of callers) {
122
+ const relPath = relative(projectRoot, c.path);
123
+ const ctx = c.context ? ` — ${c.context.trim().slice(0, 80)}` : '';
124
+ outputLines.push(` ${relPath}:${c.line}${ctx}`);
125
+ }
126
+ }
127
+ else {
128
+ outputLines.push('');
129
+ outputLines.push('CALLERS: none found');
130
+ }
131
+ }
132
+ catch {
133
+ // ast-index unavailable — skip silently
134
+ }
135
+ }
136
+ // include_tests: find related test file and list test names
137
+ if (args.include_tests) {
138
+ const testSection = await findTestSection(absPath, args.path, projectRoot, astIndex);
139
+ outputLines.push('');
140
+ outputLines.push(...testSection);
141
+ }
142
+ // include_changes: git diff filtered to target region
143
+ if (args.include_changes) {
144
+ const diffSection = await findChangesSection(absPath, projectRoot, rangeStart, rangeEnd);
145
+ outputLines.push('');
146
+ outputLines.push(...diffSection);
147
+ }
148
+ // Confidence metadata
149
+ const confidenceMeta = assessConfidence({
150
+ symbolResolved: !!args.symbol && startLine > 0,
151
+ fullFile: false,
152
+ truncated: false,
153
+ hasCallers: args.include_callers ?? false,
154
+ hasTests: args.include_tests ?? false,
155
+ astAvailable: true,
156
+ });
157
+ outputLines.push(formatConfidence(confidenceMeta));
158
+ const output = outputLines.join('\n');
107
159
  const tokens = estimateTokens(output);
108
160
  // Track in context
109
161
  contextRegistry.trackLoad(absPath, {
@@ -115,4 +167,134 @@ export async function handleReadForEdit(args, projectRoot, symbolResolver, fileC
115
167
  });
116
168
  return { content: [{ type: 'text', text: output }] };
117
169
  }
170
+ // --- Helper: find related test file and extract test names ---
171
+ async function findTestSection(absPath, relPath, projectRoot, astIndex) {
172
+ // Derive test file path from source path using common conventions
173
+ // src/handlers/foo.ts → tests/handlers/foo.test.ts
174
+ // src/core/bar.ts → tests/core/bar.test.ts
175
+ const srcPrefix = 'src/';
176
+ let testRelPath;
177
+ if (relPath.startsWith(srcPrefix)) {
178
+ const rest = relPath.slice(srcPrefix.length);
179
+ const ext = rest.match(/\.[^.]+$/)?.[0] ?? '.ts';
180
+ const base = rest.replace(/\.[^.]+$/, '');
181
+ testRelPath = `tests/${base}.test${ext}`;
182
+ }
183
+ else {
184
+ const ext = relPath.match(/\.[^.]+$/)?.[0] ?? '.ts';
185
+ const base = relPath.replace(/\.[^.]+$/, '');
186
+ testRelPath = `${base}.test${ext}`;
187
+ }
188
+ const testAbsPath = join(projectRoot, testRelPath);
189
+ try {
190
+ await access(testAbsPath);
191
+ }
192
+ catch {
193
+ return [`TESTS: none found (expected at ${testRelPath})`];
194
+ }
195
+ // Test file exists — try to get outline for test names
196
+ const lines = [`TESTS: ${testRelPath}`];
197
+ if (!astIndex.isDisabled()) {
198
+ try {
199
+ const outline = await astIndex.outline(testAbsPath);
200
+ if (outline?.symbols && outline.symbols.length > 0) {
201
+ for (const sym of outline.symbols) {
202
+ lines.push(` ${sym.kind} ${sym.name}`);
203
+ if (sym.children) {
204
+ for (const child of sym.children) {
205
+ lines.push(` ${child.kind} ${child.name}`);
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }
211
+ catch {
212
+ // outline failed — just show file path
213
+ }
214
+ }
215
+ return lines;
216
+ }
217
+ // --- Helper: git diff filtered to target region ---
218
+ async function findChangesSection(absPath, projectRoot, rangeStart, rangeEnd) {
219
+ const MAX_DIFF_LINES = 30;
220
+ try {
221
+ // Try unstaged changes first
222
+ let diffOutput = '';
223
+ let diffLabel = 'unstaged';
224
+ try {
225
+ const { stdout } = await execFileAsync('git', ['diff', 'HEAD', '--', absPath], {
226
+ cwd: projectRoot,
227
+ timeout: 5000,
228
+ });
229
+ diffOutput = stdout;
230
+ }
231
+ catch {
232
+ // git not available or not a repo
233
+ return ['RECENT CHANGES: unavailable (not a git repo)'];
234
+ }
235
+ // If no unstaged changes, try last commit
236
+ if (!diffOutput.trim()) {
237
+ try {
238
+ const { stdout } = await execFileAsync('git', ['diff', 'HEAD~1', '--', absPath], {
239
+ cwd: projectRoot,
240
+ timeout: 5000,
241
+ });
242
+ diffOutput = stdout;
243
+ diffLabel = 'last commit';
244
+ }
245
+ catch {
246
+ // no previous commit
247
+ }
248
+ }
249
+ if (!diffOutput.trim()) {
250
+ return ['RECENT CHANGES: none (file unchanged)'];
251
+ }
252
+ // Filter hunks to those overlapping with target range
253
+ const relevantLines = filterDiffHunks(diffOutput, rangeStart, rangeEnd);
254
+ if (relevantLines.length === 0) {
255
+ return ['RECENT CHANGES: none in target region'];
256
+ }
257
+ const lines = [`RECENT CHANGES (${diffLabel}):`];
258
+ const trimmed = relevantLines.slice(0, MAX_DIFF_LINES);
259
+ for (const line of trimmed) {
260
+ lines.push(` ${line}`);
261
+ }
262
+ if (relevantLines.length > MAX_DIFF_LINES) {
263
+ lines.push(` ... ${relevantLines.length - MAX_DIFF_LINES} more lines`);
264
+ }
265
+ return lines;
266
+ }
267
+ catch {
268
+ return ['RECENT CHANGES: unavailable'];
269
+ }
270
+ }
271
+ /** Filter diff output to only hunks overlapping [rangeStart, rangeEnd]. */
272
+ function filterDiffHunks(diff, rangeStart, rangeEnd) {
273
+ const allLines = diff.split('\n');
274
+ const result = [];
275
+ let inRelevantHunk = false;
276
+ for (const line of allLines) {
277
+ // Hunk header: @@ -a,b +c,d @@
278
+ const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
279
+ if (hunkMatch) {
280
+ const hunkStart = parseInt(hunkMatch[1], 10);
281
+ const hunkLen = parseInt(hunkMatch[2] ?? '1', 10);
282
+ const hunkEnd = hunkStart + hunkLen - 1;
283
+ // Check overlap with target range
284
+ inRelevantHunk = hunkStart <= rangeEnd && hunkEnd >= rangeStart;
285
+ if (inRelevantHunk) {
286
+ result.push(line);
287
+ }
288
+ continue;
289
+ }
290
+ // Skip diff metadata lines (diff --git, index, ---, +++)
291
+ if (line.startsWith('diff ') || line.startsWith('index ') || line.startsWith('--- ') || line.startsWith('+++ ')) {
292
+ continue;
293
+ }
294
+ if (inRelevantHunk && (line.startsWith('+') || line.startsWith('-') || line.startsWith(' '))) {
295
+ result.push(line);
296
+ }
297
+ }
298
+ return result;
299
+ }
118
300
  //# sourceMappingURL=read-for-edit.js.map
@@ -5,7 +5,7 @@ export interface ReadRangeArgs {
5
5
  start_line: number;
6
6
  end_line: number;
7
7
  }
8
- export declare function handleReadRange(args: ReadRangeArgs, projectRoot: string, fileCache: FileCache, contextRegistry: ContextRegistry): Promise<{
8
+ export declare function handleReadRange(args: ReadRangeArgs, projectRoot: string, fileCache: FileCache, contextRegistry: ContextRegistry, advisoryReminders?: boolean): Promise<{
9
9
  content: Array<{
10
10
  type: 'text';
11
11
  text: string;
@@ -1,7 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { estimateTokens } from '../core/token-estimator.js';
3
3
  import { resolveSafePath } from '../core/validation.js';
4
- export async function handleReadRange(args, projectRoot, fileCache, contextRegistry) {
4
+ export async function handleReadRange(args, projectRoot, fileCache, contextRegistry, advisoryReminders = true) {
5
5
  const absPath = resolveSafePath(projectRoot, args.path);
6
6
  // Get lines
7
7
  const cached = fileCache.get(absPath);
@@ -13,6 +13,18 @@ export async function handleReadRange(args, projectRoot, fileCache, contextRegis
13
13
  const content = await readFile(absPath, 'utf-8');
14
14
  lines = content.split('\n');
15
15
  }
16
+ // Dedup: check if full file is already in context and unchanged
17
+ if (advisoryReminders) {
18
+ const hash = cached?.hash;
19
+ if (hash && !contextRegistry.isStale(absPath, hash)) {
20
+ if (contextRegistry.isFullyLoaded(absPath)) {
21
+ const reminder = contextRegistry.rangeReminder(absPath, args.start_line, args.end_line);
22
+ if (reminder) {
23
+ return { content: [{ type: 'text', text: reminder }] };
24
+ }
25
+ }
26
+ }
27
+ }
16
28
  const start = Math.max(0, args.start_line - 1);
17
29
  const end = Math.min(lines.length, args.end_line);
18
30
  if (start >= lines.length || start >= end) {
@@ -39,6 +51,9 @@ export async function handleReadRange(args, projectRoot, fileCache, contextRegis
39
51
  endLine: args.end_line,
40
52
  tokens,
41
53
  });
54
+ if (cached?.hash) {
55
+ contextRegistry.setContentHash(absPath, cached.hash);
56
+ }
42
57
  return { content: [{ type: 'text', text: output }] };
43
58
  }
44
59
  //# sourceMappingURL=read-range.js.map
@@ -9,7 +9,7 @@ export interface ReadSymbolArgs {
9
9
  context_after?: number;
10
10
  show?: 'full' | 'head' | 'tail' | 'outline';
11
11
  }
12
- export declare function handleReadSymbol(args: ReadSymbolArgs, projectRoot: string, symbolResolver: SymbolResolver, fileCache: FileCache, contextRegistry: ContextRegistry, astIndex?: AstIndexClient): Promise<{
12
+ export declare function handleReadSymbol(args: ReadSymbolArgs, projectRoot: string, symbolResolver: SymbolResolver, fileCache: FileCache, contextRegistry: ContextRegistry, astIndex?: AstIndexClient, advisoryReminders?: boolean): Promise<{
13
13
  content: Array<{
14
14
  type: 'text';
15
15
  text: string;
@@ -1,7 +1,8 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { estimateTokens } from '../core/token-estimator.js';
3
3
  import { resolveSafePath } from '../core/validation.js';
4
- export async function handleReadSymbol(args, projectRoot, symbolResolver, fileCache, contextRegistry, astIndex) {
4
+ import { assessConfidence, formatConfidence } from '../core/confidence.js';
5
+ export async function handleReadSymbol(args, projectRoot, symbolResolver, fileCache, contextRegistry, astIndex, advisoryReminders = true) {
5
6
  const absPath = resolveSafePath(projectRoot, args.path);
6
7
  // Get file content
7
8
  const cached = fileCache.get(absPath);
@@ -13,6 +14,18 @@ export async function handleReadSymbol(args, projectRoot, symbolResolver, fileCa
13
14
  const content = await readFile(absPath, 'utf-8');
14
15
  lines = content.split('\n');
15
16
  }
17
+ // Dedup: check if content already in context and unchanged
18
+ if (advisoryReminders) {
19
+ const hash = cached?.hash;
20
+ if (hash && !contextRegistry.isStale(absPath, hash)) {
21
+ if (contextRegistry.isFullyLoaded(absPath) || contextRegistry.isSymbolLoaded(absPath, args.symbol)) {
22
+ const reminder = contextRegistry.symbolReminder(absPath, args.symbol);
23
+ if (reminder) {
24
+ return { content: [{ type: 'text', text: reminder }] };
25
+ }
26
+ }
27
+ }
28
+ }
16
29
  // Resolve symbol — auto-fetch structure if not cached
17
30
  let structure = cached?.structure;
18
31
  if (!structure && astIndex) {
@@ -116,6 +129,17 @@ export async function handleReadSymbol(args, projectRoot, symbolResolver, fileCa
116
129
  endLine: resolved.endLine,
117
130
  tokens,
118
131
  });
119
- return { content: [{ type: 'text', text: output }] };
132
+ if (cached?.hash) {
133
+ contextRegistry.setContentHash(absPath, cached.hash);
134
+ }
135
+ // Confidence metadata
136
+ const confidenceMeta = assessConfidence({
137
+ symbolResolved: true,
138
+ truncated,
139
+ fullFile: false,
140
+ hasCallers: resolved.symbol.references.length > 0,
141
+ astAvailable: !!structure,
142
+ });
143
+ return { content: [{ type: 'text', text: output + formatConfidence(confidenceMeta) }] };
120
144
  }
121
145
  //# sourceMappingURL=read-symbol.js.map
@@ -2,10 +2,21 @@ import type { AstIndexClient } from '../ast-index/client.js';
2
2
  export interface RelatedFilesArgs {
3
3
  path: string;
4
4
  }
5
+ export interface RelatedFilesMeta {
6
+ imports: string[];
7
+ importedBy: string[];
8
+ tests: string[];
9
+ ranked: {
10
+ high: string[];
11
+ medium: string[];
12
+ low: string[];
13
+ };
14
+ }
5
15
  export declare function handleRelatedFiles(args: RelatedFilesArgs, projectRoot: string, astIndex: AstIndexClient): Promise<{
6
16
  content: Array<{
7
17
  type: 'text';
8
18
  text: string;
9
19
  }>;
20
+ meta: RelatedFilesMeta;
10
21
  }>;
11
22
  //# sourceMappingURL=related-files.d.ts.map