token-pilot 0.13.0 → 0.14.1

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 +28 -7
  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 +61 -55
  58. package/dist/server.js +395 -30
  59. package/dist/types.d.ts +12 -0
  60. package/package.json +5 -3
  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
@@ -17,6 +17,8 @@ export class FileWatcher {
17
17
  watcher = null;
18
18
  watchedFiles = new Set();
19
19
  updateTimer = null;
20
+ fileChangeCallback = null;
21
+ astUpdateCallback = null;
20
22
  constructor(_projectRoot, fileCache, contextRegistry, _ignore, astIndex) {
21
23
  this.fileCache = fileCache;
22
24
  this.contextRegistry = contextRegistry;
@@ -37,6 +39,7 @@ export class FileWatcher {
37
39
  if (this.fileCache.get(absPath)) {
38
40
  this.fileCache.invalidate(absPath);
39
41
  }
42
+ this.fileChangeCallback?.(absPath);
40
43
  this.scheduleIndexUpdate();
41
44
  });
42
45
  this.watcher.on('unlink', (filePath) => {
@@ -44,6 +47,7 @@ export class FileWatcher {
44
47
  this.fileCache.invalidate(absPath);
45
48
  this.contextRegistry.forget(absPath);
46
49
  this.watchedFiles.delete(absPath);
50
+ this.fileChangeCallback?.(absPath);
47
51
  this.scheduleIndexUpdate();
48
52
  });
49
53
  }
@@ -53,10 +57,22 @@ export class FileWatcher {
53
57
  return;
54
58
  if (this.updateTimer)
55
59
  clearTimeout(this.updateTimer);
56
- this.updateTimer = setTimeout(() => {
57
- this.astIndex?.incrementalUpdate().catch(() => { });
60
+ this.updateTimer = setTimeout(async () => {
61
+ try {
62
+ await this.astIndex?.incrementalUpdate();
63
+ this.astUpdateCallback?.();
64
+ }
65
+ catch { /* ignore */ }
58
66
  }, FileWatcher.UPDATE_DEBOUNCE_MS);
59
67
  }
68
+ /** Register callback for file change/unlink events. */
69
+ onFileChange(callback) {
70
+ this.fileChangeCallback = callback;
71
+ }
72
+ /** Register callback for after AST index incremental update completes. */
73
+ onAstUpdate(callback) {
74
+ this.astUpdateCallback = callback;
75
+ }
60
76
  /** Add a specific file to watch. Called after smart_read/read_symbol loads a file. */
61
77
  watchFile(filePath) {
62
78
  const absPath = resolve(filePath);
@@ -7,6 +7,7 @@ export declare class GitWatcher {
7
7
  private watcher;
8
8
  private headRef;
9
9
  private enabled;
10
+ private branchSwitchCallback;
10
11
  constructor(projectRoot: string, fileCache: FileCache, contextRegistry: ContextRegistry, enabled: boolean);
11
12
  start(): Promise<void>;
12
13
  stop(): void;
@@ -19,6 +20,8 @@ export declare class GitWatcher {
19
20
  * Get files changed in the last N commits.
20
21
  */
21
22
  getRecentlyChangedFiles(commits?: number): Promise<string[]>;
23
+ /** Register callback for branch switch events. */
24
+ onBranchSwitchEvent(callback: (changedFiles: string[]) => void): void;
22
25
  private onBranchSwitch;
23
26
  private readHead;
24
27
  }
@@ -11,6 +11,7 @@ export class GitWatcher {
11
11
  watcher = null;
12
12
  headRef = '';
13
13
  enabled;
14
+ branchSwitchCallback = null;
14
15
  constructor(projectRoot, fileCache, contextRegistry, enabled) {
15
16
  this.projectRoot = projectRoot;
16
17
  this.fileCache = fileCache;
@@ -78,6 +79,10 @@ export class GitWatcher {
78
79
  return [];
79
80
  }
80
81
  }
82
+ /** Register callback for branch switch events. */
83
+ onBranchSwitchEvent(callback) {
84
+ this.branchSwitchCallback = callback;
85
+ }
81
86
  async onBranchSwitch() {
82
87
  // On branch switch, get files that differ between old and new branch
83
88
  // and selectively invalidate only those
@@ -85,6 +90,7 @@ export class GitWatcher {
85
90
  if (changed.length > 0) {
86
91
  await this.fileCache.invalidateByGitDiff(changed);
87
92
  this.contextRegistry.invalidateByGitDiff(changed);
93
+ this.branchSwitchCallback?.(changed);
88
94
  }
89
95
  }
90
96
  async readHead() {
@@ -1,9 +1,14 @@
1
1
  import type { AstIndexClient } from '../ast-index/client.js';
2
2
  import type { CodeAuditArgs } from '../core/validation.js';
3
- export declare function handleCodeAudit(args: CodeAuditArgs, projectRoot: string, astIndex: AstIndexClient): Promise<{
3
+ type AuditResult = {
4
4
  content: Array<{
5
5
  type: 'text';
6
6
  text: string;
7
7
  }>;
8
- }>;
8
+ meta: {
9
+ files: string[];
10
+ };
11
+ };
12
+ export declare function handleCodeAudit(args: CodeAuditArgs, projectRoot: string, astIndex: AstIndexClient): Promise<AuditResult>;
13
+ export {};
9
14
  //# sourceMappingURL=code-audit.d.ts.map
@@ -6,6 +6,7 @@ export async function handleCodeAudit(args, projectRoot, astIndex) {
6
6
  type: 'text',
7
7
  text: 'ast-index is not available (project root too broad or index oversized). Use Grep/ripgrep for pattern search.',
8
8
  }],
9
+ meta: { files: [] },
9
10
  };
10
11
  }
11
12
  const limit = args.limit ?? 50;
@@ -27,6 +28,7 @@ export async function handleCodeAudit(args, projectRoot, astIndex) {
27
28
  type: 'text',
28
29
  text: `Unknown check type: "${args.check}". Use: pattern, todo, deprecated, annotations, all`,
29
30
  }],
31
+ meta: { files: [] },
30
32
  };
31
33
  }
32
34
  }
@@ -42,6 +44,7 @@ async function handlePattern(pattern, lang, limit, projectRoot, astIndex) {
42
44
  type: 'text',
43
45
  text: `PATTERN SEARCH: "${pattern}"${lang ? ` (${lang})` : ''}\n\nNo matches found.\n\nHINT: Try Grep/ripgrep for text-based search if the pattern is not structural.`,
44
46
  }],
47
+ meta: { files: [] },
45
48
  };
46
49
  }
47
50
  // Group by file
@@ -64,7 +67,7 @@ async function handlePattern(pattern, lang, limit, projectRoot, astIndex) {
64
67
  lines.push('');
65
68
  }
66
69
  lines.push('HINT: Use read_symbol() to inspect specific matches, or Grep for text-based counting.');
67
- return { content: [{ type: 'text', text: lines.join('\n') }] };
70
+ return { content: [{ type: 'text', text: lines.join('\n') }], meta: { files: [...byFile.keys()] } };
68
71
  }
69
72
  catch (err) {
70
73
  // ast-grep not installed — return the error message
@@ -73,6 +76,7 @@ async function handlePattern(pattern, lang, limit, projectRoot, astIndex) {
73
76
  type: 'text',
74
77
  text: `PATTERN SEARCH ERROR:\n${err instanceof Error ? err.message : String(err)}\n\nFallback: Use Grep/ripgrep for text-based pattern search.`,
75
78
  }],
79
+ meta: { files: [] },
76
80
  };
77
81
  }
78
82
  }
@@ -85,6 +89,7 @@ async function handleTodo(limit, projectRoot, astIndex) {
85
89
  type: 'text',
86
90
  text: 'TODO/FIXME COMMENTS: none found.\n\nHINT: ast-index may not detect all comment formats. Try Grep: grep -rn "TODO\\|FIXME\\|HACK" --include="*.ts"',
87
91
  }],
92
+ meta: { files: [] },
88
93
  };
89
94
  }
90
95
  // Group by kind
@@ -106,7 +111,8 @@ async function handleTodo(limit, projectRoot, astIndex) {
106
111
  }
107
112
  lines.push('');
108
113
  }
109
- return { content: [{ type: 'text', text: lines.join('\n') }] };
114
+ const todoFiles = [...new Set(limited.map(e => e.file))];
115
+ return { content: [{ type: 'text', text: lines.join('\n') }], meta: { files: todoFiles } };
110
116
  }
111
117
  async function handleDeprecated(limit, projectRoot, astIndex) {
112
118
  const entries = await astIndex.deprecated();
@@ -117,6 +123,7 @@ async function handleDeprecated(limit, projectRoot, astIndex) {
117
123
  type: 'text',
118
124
  text: 'DEPRECATED SYMBOLS: none found.\n\nHINT: ast-index detects @Deprecated annotations. Try Grep for other deprecation patterns.',
119
125
  }],
126
+ meta: { files: [] },
120
127
  };
121
128
  }
122
129
  const lines = [
@@ -129,7 +136,8 @@ async function handleDeprecated(limit, projectRoot, astIndex) {
129
136
  }
130
137
  lines.push('');
131
138
  lines.push('HINT: Use read_symbol() to inspect deprecated symbols before removing them.');
132
- return { content: [{ type: 'text', text: lines.join('\n') }] };
139
+ const depFiles = [...new Set(limited.map(e => e.file))];
140
+ return { content: [{ type: 'text', text: lines.join('\n') }], meta: { files: depFiles } };
133
141
  }
134
142
  async function handleAnnotations(name, limit, projectRoot, astIndex) {
135
143
  const entries = await astIndex.annotations(name);
@@ -140,6 +148,7 @@ async function handleAnnotations(name, limit, projectRoot, astIndex) {
140
148
  type: 'text',
141
149
  text: `ANNOTATIONS @${name}: none found.\n\nHINT: Try Grep for text-based search: grep -rn "@${name}" --include="*.ts"`,
142
150
  }],
151
+ meta: { files: [] },
143
152
  };
144
153
  }
145
154
  // Group by file
@@ -161,7 +170,8 @@ async function handleAnnotations(name, limit, projectRoot, astIndex) {
161
170
  }
162
171
  lines.push('');
163
172
  }
164
- return { content: [{ type: 'text', text: lines.join('\n') }] };
173
+ const annFiles = [...new Set(limited.map(e => e.file))];
174
+ return { content: [{ type: 'text', text: lines.join('\n') }], meta: { files: annFiles } };
165
175
  }
166
176
  async function handleAll(limit, projectRoot, astIndex) {
167
177
  // Run todo + deprecated in parallel
@@ -196,6 +206,10 @@ async function handleAll(limit, projectRoot, astIndex) {
196
206
  sections.push('');
197
207
  sections.push('HINT: Use code_audit(check="pattern", pattern="...") for structural pattern search (requires ast-grep).');
198
208
  sections.push(' Use Grep for text-based counting and regex search.');
199
- return { content: [{ type: 'text', text: sections.join('\n') }] };
209
+ const allFiles = [...new Set([
210
+ ...todos.slice(0, limit).map(e => e.file),
211
+ ...deprecated.slice(0, limit).map(e => e.file),
212
+ ])];
213
+ return { content: [{ type: 'text', text: sections.join('\n') }], meta: { files: allFiles } };
200
214
  }
201
215
  //# sourceMappingURL=code-audit.js.map
@@ -1,9 +1,19 @@
1
1
  import type { AstIndexClient } from '../ast-index/client.js';
2
2
  import type { ExploreAreaArgs } from '../core/validation.js';
3
+ export interface ExploreAreaMeta {
4
+ dir: string;
5
+ codeFiles: string[];
6
+ testFiles: string[];
7
+ internalDeps: string[];
8
+ importedBy: string[];
9
+ externalDeps: string[];
10
+ changeCount: number;
11
+ }
3
12
  export declare function handleExploreArea(args: ExploreAreaArgs, projectRoot: string, astIndex: AstIndexClient): Promise<{
4
13
  content: Array<{
5
14
  type: 'text';
6
15
  text: string;
7
16
  }>;
17
+ meta: ExploreAreaMeta;
8
18
  }>;
9
19
  //# sourceMappingURL=explore-area.d.ts.map
@@ -20,6 +20,15 @@ export async function handleExploreArea(args, projectRoot, astIndex) {
20
20
  if (!pathStat) {
21
21
  return {
22
22
  content: [{ type: 'text', text: `Path "${args.path}" not found.` }],
23
+ meta: {
24
+ dir: args.path,
25
+ codeFiles: [],
26
+ testFiles: [],
27
+ internalDeps: [],
28
+ importedBy: [],
29
+ externalDeps: [],
30
+ changeCount: 0,
31
+ },
23
32
  };
24
33
  }
25
34
  if (!pathStat.isDirectory()) {
@@ -49,17 +58,17 @@ export async function handleExploreArea(args, projectRoot, astIndex) {
49
58
  lines.push('');
50
59
  }
51
60
  // Imports
52
- const importLines = extractResult(importsSection);
61
+ const importLines = extractResult(importsSection)?.lines ?? null;
53
62
  if (importLines) {
54
63
  lines.push(...importLines);
55
64
  }
56
65
  // Tests
57
- const testLines = extractResult(testsSection);
66
+ const testLines = extractResult(testsSection)?.lines ?? null;
58
67
  if (testLines) {
59
68
  lines.push(...testLines);
60
69
  }
61
70
  // Changes
62
- const changeLines = extractResult(changesSection);
71
+ const changeLines = extractResult(changesSection)?.lines ?? null;
63
72
  if (changeLines) {
64
73
  lines.push(...changeLines);
65
74
  }
@@ -69,7 +78,18 @@ export async function handleExploreArea(args, projectRoot, astIndex) {
69
78
  lines.push('... truncated. Use outline() on specific subdirectories for details.');
70
79
  }
71
80
  lines.push('HINT: Use smart_read(file) for details, read_symbol(path, symbol) for source code, find_usages(symbol) for references.');
72
- return { content: [{ type: 'text', text: lines.join('\n') }] };
81
+ return {
82
+ content: [{ type: 'text', text: lines.join('\n') }],
83
+ meta: {
84
+ dir: relDir,
85
+ codeFiles: codeFiles.map((file) => relative(projectRoot, file)).sort(),
86
+ testFiles: extractResult(testsSection)?.testFiles ?? [],
87
+ internalDeps: extractResult(importsSection)?.internalDeps ?? [],
88
+ importedBy: extractResult(importsSection)?.importedBy ?? [],
89
+ externalDeps: extractResult(importsSection)?.externalDeps ?? [],
90
+ changeCount: extractResult(changesSection)?.count ?? 0,
91
+ },
92
+ };
73
93
  }
74
94
  // ──────────────────────────────────────────────
75
95
  // Outline section — reuses outlineDir from outline.ts
@@ -84,7 +104,7 @@ async function buildOutlineSection(absPath, projectRoot, astIndex) {
84
104
  // ──────────────────────────────────────────────
85
105
  async function buildImportsSection(codeFiles, absPath, projectRoot, astIndex) {
86
106
  if (!astIndex.isAvailable() || astIndex.isDisabled() || astIndex.isOversized()) {
87
- return [];
107
+ return { lines: [], internalDeps: [], importedBy: [], externalDeps: [] };
88
108
  }
89
109
  const filesToAnalyze = codeFiles.slice(0, MAX_IMPORT_FILES);
90
110
  const externalDeps = new Set();
@@ -150,7 +170,12 @@ async function buildImportsSection(codeFiles, absPath, projectRoot, astIndex) {
150
170
  }
151
171
  if (lines.length > 0)
152
172
  lines.push('');
153
- return lines;
173
+ return {
174
+ lines,
175
+ internalDeps: Array.from(internalDeps).sort(),
176
+ importedBy: Array.from(importedBy).sort(),
177
+ externalDeps: Array.from(externalDeps).sort(),
178
+ };
154
179
  }
155
180
  // ──────────────────────────────────────────────
156
181
  // Tests section — find test/spec files matching area files
@@ -215,11 +240,11 @@ async function buildTestsSection(codeFiles, absPath, projectRoot) {
215
240
  catch { /* skip unreadable dirs */ }
216
241
  }
217
242
  if (testFiles.length === 0)
218
- return [];
243
+ return { lines: [], testFiles: [] };
219
244
  const lines = [];
220
245
  lines.push(`TESTS: ${testFiles.join(', ')}`);
221
246
  lines.push('');
222
- return lines;
247
+ return { lines, testFiles: [...testFiles].sort() };
223
248
  }
224
249
  // ──────────────────────────────────────────────
225
250
  // Changes section — recent git log for this area
@@ -228,24 +253,25 @@ async function buildChangesSection(relDir, projectRoot) {
228
253
  try {
229
254
  const { stdout } = await execFileAsync('git', ['log', '--oneline', '-5', '--', relDir], { cwd: projectRoot, timeout: 5000 });
230
255
  if (!stdout.trim())
231
- return [];
256
+ return { lines: [], count: 0 };
232
257
  const lines = [];
258
+ const commits = stdout.trim().split('\n');
233
259
  lines.push('RECENT CHANGES:');
234
- for (const line of stdout.trim().split('\n')) {
260
+ for (const line of commits) {
235
261
  lines.push(` ${line}`);
236
262
  }
237
263
  lines.push('');
238
- return lines;
264
+ return { lines, count: commits.length };
239
265
  }
240
266
  catch {
241
- return [];
267
+ return { lines: [], count: 0 };
242
268
  }
243
269
  }
244
270
  // ──────────────────────────────────────────────
245
271
  // Helpers
246
272
  // ──────────────────────────────────────────────
247
273
  function extractResult(settled) {
248
- if (settled.status === 'fulfilled' && settled.value && settled.value.length > 0) {
274
+ if (settled.status === 'fulfilled' && settled.value) {
249
275
  return settled.value;
250
276
  }
251
277
  return null;
@@ -9,5 +9,8 @@ export declare function handleFindUnused(args: FindUnusedArgs, astIndex: AstInde
9
9
  type: 'text';
10
10
  text: string;
11
11
  }>;
12
+ meta: {
13
+ files: string[];
14
+ };
12
15
  }>;
13
16
  //# sourceMappingURL=find-unused.d.ts.map
@@ -18,7 +18,7 @@ export async function handleFindUnused(args, astIndex) {
18
18
  return { content: [{ type: 'text', text: 'find_unused is disabled: ' + (astIndex.isDisabled()
19
19
  ? 'project root not detected. Call smart_read() on any project file first — this auto-detects the project root and enables ast-index tools.'
20
20
  : 'ast-index built >50k files (likely includes node_modules). Ensure node_modules is in .gitignore.') +
21
- '\nAlternative: use grep_search to find unused exports manually.' }] };
21
+ '\nAlternative: use grep_search to find unused exports manually.' }], meta: { files: [] } };
22
22
  }
23
23
  const requestLimit = (args.limit ?? 30) + 20; // extra to compensate for filtering
24
24
  const unused = await astIndex.unusedSymbols({
@@ -67,6 +67,7 @@ export async function handleFindUnused(args, astIndex) {
67
67
  ? `No unused symbols found in module "${args.module}".${excluded > 0 ? ` (${excluded} constructors/protocol methods excluded)` : ''}`
68
68
  : `No unused symbols found in the project.${excluded > 0 ? ` (${excluded} constructors/protocol methods excluded)` : ''}`,
69
69
  }],
70
+ meta: { files: [] },
70
71
  };
71
72
  }
72
73
  const lines = [];
@@ -100,7 +101,7 @@ export async function handleFindUnused(args, astIndex) {
100
101
  lines.push(`(${langExcluded} constructors/protocol methods excluded)`);
101
102
  }
102
103
  lines.push('NOTE: Verify before removing — symbols may be used dynamically, in tests, or via framework conventions.');
103
- return { content: [{ type: 'text', text: lines.join('\n') }] };
104
+ return { content: [{ type: 'text', text: lines.join('\n') }], meta: { files: uniqueFiles } };
104
105
  }
105
106
  /**
106
107
  * Find decorators for a symbol by matching name + line in the outline tree.
@@ -15,5 +15,12 @@ export declare function handleFindUsages(args: FindUsagesArgs, astIndex: AstInde
15
15
  type: 'text';
16
16
  text: string;
17
17
  }>;
18
+ meta: {
19
+ files: string[];
20
+ definitions: number;
21
+ imports: number;
22
+ usages: number;
23
+ total: number;
24
+ };
18
25
  }>;
19
26
  //# sourceMappingURL=find-usages.d.ts.map
@@ -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<{