@aiready/ast-mcp-server 0.1.1 → 0.1.3

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/dist/index.js CHANGED
@@ -1,3 +1,8 @@
1
+ import {
2
+ searchCode,
3
+ validateWorkspacePath
4
+ } from "./chunk-PRWMQQYW.js";
5
+
1
6
  // src/index.ts
2
7
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -30,10 +35,11 @@ var GetFileStructureSchema = z.object({
30
35
  file: z.string().describe("Absolute path to the file to analyze")
31
36
  });
32
37
  var SearchCodeSchema = z.object({
33
- pattern: z.string().describe("Search pattern (regex)"),
38
+ pattern: z.string().describe("Search pattern (regex by default)"),
34
39
  path: z.string().describe("Directory to search in"),
35
40
  filePattern: z.string().optional().describe('Glob filter (e.g., "*.ts")'),
36
- limit: z.number().optional().default(50).describe("Max matches to return")
41
+ limit: z.number().optional().default(50).describe("Max matches to return"),
42
+ regex: z.boolean().optional().default(true).describe("Use regex mode (default true)")
37
43
  });
38
44
  var GetSymbolDocsSchema = z.object({
39
45
  symbol: z.string().describe("Symbol name to get documentation for"),
@@ -44,10 +50,8 @@ var BuildSymbolIndexSchema = z.object({
44
50
  });
45
51
 
46
52
  // src/adapters/typescript-adapter.ts
47
- import {
48
- Node,
49
- SyntaxKind
50
- } from "ts-morph";
53
+ import { Node as Node2 } from "ts-morph";
54
+ import fs3 from "fs";
51
55
 
52
56
  // src/project-manager.ts
53
57
  import { Project } from "ts-morph";
@@ -57,48 +61,80 @@ import { glob } from "glob";
57
61
  var ProjectManager = class {
58
62
  projects = /* @__PURE__ */ new Map();
59
63
  tsconfigCache = /* @__PURE__ */ new Map();
64
+ accessOrder = [];
65
+ // MRU at front
66
+ maxProjects = 4;
60
67
  /**
61
68
  * Find all tsconfig.json files in a directory (recursive)
62
69
  */
63
70
  async findTsConfigs(rootDir) {
64
- if (this.tsconfigCache.has(rootDir)) {
65
- return this.tsconfigCache.get(rootDir);
71
+ const safeRoot = validateWorkspacePath(rootDir);
72
+ if (this.tsconfigCache.has(safeRoot)) {
73
+ return this.tsconfigCache.get(safeRoot);
66
74
  }
67
75
  const configs = await glob("**/tsconfig.json", {
68
- cwd: rootDir,
76
+ cwd: safeRoot,
69
77
  absolute: true,
70
78
  ignore: ["**/node_modules/**", "**/dist/**"]
71
79
  });
72
- this.tsconfigCache.set(rootDir, configs);
80
+ this.tsconfigCache.set(safeRoot, configs);
73
81
  return configs;
74
82
  }
75
83
  /**
76
- * Get or create a Project for a specific file path
77
- */
78
- async getProjectForFile(filePath) {
79
- const tsconfigPath = await this.findNearestTsConfig(filePath);
80
- if (!tsconfigPath) return void 0;
81
- return this.getOrCreateProject(tsconfigPath);
82
- }
83
- /**
84
- * Get or create a Project for a tsconfig path
84
+ * Ensure a Project exists for a given tsconfig path, managing LRU cache
85
85
  */
86
- getOrCreateProject(tsconfigPath) {
86
+ ensureProject(tsconfigPath) {
87
+ this.checkMemoryPressure();
87
88
  if (this.projects.has(tsconfigPath)) {
89
+ this.moveToFront(tsconfigPath);
88
90
  return this.projects.get(tsconfigPath);
89
91
  }
92
+ while (this.projects.size >= this.maxProjects) {
93
+ const oldest = this.accessOrder.pop();
94
+ this.disposeProject(oldest);
95
+ }
90
96
  const project = new Project({
91
97
  tsConfigFilePath: tsconfigPath,
92
- skipAddingFilesFromTsConfig: false
98
+ skipAddingFilesFromTsConfig: true
99
+ // KEY: don't load all files
93
100
  });
94
101
  this.projects.set(tsconfigPath, project);
102
+ this.accessOrder.unshift(tsconfigPath);
95
103
  return project;
96
104
  }
105
+ /**
106
+ * Move a tsconfig path to the front of the access order (MRU)
107
+ */
108
+ moveToFront(tsconfigPath) {
109
+ const index = this.accessOrder.indexOf(tsconfigPath);
110
+ if (index > -1) {
111
+ this.accessOrder.splice(index, 1);
112
+ this.accessOrder.unshift(tsconfigPath);
113
+ }
114
+ }
115
+ checkMemoryPressure() {
116
+ const heapUsed = process.memoryUsage().heapUsed / 1024 / 1024;
117
+ const maxHeap = parseInt(process.env.AST_MAX_HEAP_MB || "1536", 10);
118
+ while (heapUsed > maxHeap && this.projects.size > 1) {
119
+ const oldest = this.accessOrder.pop();
120
+ this.disposeProject(oldest);
121
+ }
122
+ }
123
+ disposeProject(tsconfigPath) {
124
+ const project = this.projects.get(tsconfigPath);
125
+ if (project) {
126
+ for (const sourceFile of project.getSourceFiles()) {
127
+ project.removeSourceFile(sourceFile);
128
+ }
129
+ this.projects.delete(tsconfigPath);
130
+ }
131
+ }
97
132
  /**
98
133
  * Find the nearest tsconfig.json for a file
99
134
  */
100
135
  async findNearestTsConfig(filePath) {
101
- let currentDir = path.dirname(filePath);
136
+ const safePath = validateWorkspacePath(filePath);
137
+ let currentDir = path.dirname(safePath);
102
138
  const root = path.parse(currentDir).root;
103
139
  while (currentDir !== root) {
104
140
  const tsconfigPath = path.join(currentDir, "tsconfig.json");
@@ -110,125 +146,446 @@ var ProjectManager = class {
110
146
  return void 0;
111
147
  }
112
148
  /**
113
- * Get all projects that might contain a file or serve a path
149
+ * Get all tsconfigs for a path
114
150
  */
115
151
  async getProjectsForPath(rootDir) {
116
- const configs = await this.findTsConfigs(rootDir);
117
- return configs.map((config) => this.getOrCreateProject(config));
152
+ return this.findTsConfigs(rootDir);
118
153
  }
119
154
  /**
120
155
  * Dispose all projects to free memory
121
156
  */
122
157
  disposeAll() {
123
- for (const project of this.projects.values()) {
124
- project.getLanguageService().compilerObject.dispose();
158
+ for (const [key] of this.projects) {
159
+ this.disposeProject(key);
125
160
  }
126
- this.projects.clear();
161
+ this.accessOrder = [];
127
162
  }
128
163
  };
129
164
  var projectManager = new ProjectManager();
130
165
 
131
- // src/adapters/typescript-adapter.ts
132
- var TypeScriptAdapter = class {
166
+ // src/index/symbol-index.ts
167
+ import fs2 from "fs";
168
+ import crypto from "crypto";
169
+ import path2 from "path";
170
+ import { Node } from "ts-morph";
171
+ var SymbolIndex = class {
172
+ index = {};
173
+ getCachePath(rootDir) {
174
+ const hash = crypto.createHash("sha256").update(rootDir).digest("hex").slice(0, 12);
175
+ return path2.join(
176
+ process.platform === "win32" ? process.env.TEMP || "c:/temp" : "/tmp",
177
+ `ast-index-${hash}.json`
178
+ );
179
+ }
180
+ isCacheValid(cached, rootDir) {
181
+ if (cached.rootDir !== rootDir) return false;
182
+ for (const [file, cachedMtime] of Object.entries(cached.fileHashes)) {
183
+ if (!fs2.existsSync(file)) return false;
184
+ if (fs2.statSync(file).mtimeMs !== cachedMtime) return false;
185
+ }
186
+ return true;
187
+ }
188
+ computeStats(cache, duration_ms, memory_mb) {
189
+ const files = Object.keys(cache.fileHashes).length;
190
+ let functions = 0;
191
+ let classes = 0;
192
+ let interfaces = 0;
193
+ let types = 0;
194
+ for (const entries of Object.values(cache.symbols)) {
195
+ for (const entry of entries) {
196
+ if (entry.kind === "function" || entry.kind === "method") functions++;
197
+ if (entry.kind === "class") classes++;
198
+ if (entry.kind === "interface") interfaces++;
199
+ if (entry.kind === "type_alias") types++;
200
+ }
201
+ }
202
+ return {
203
+ indexed: {
204
+ files,
205
+ functions,
206
+ classes,
207
+ interfaces,
208
+ types
209
+ },
210
+ duration_ms: duration_ms || 0,
211
+ memory_mb: memory_mb || 0
212
+ };
213
+ }
214
+ mapKind(node) {
215
+ if (Node.isClassDeclaration(node)) return "class";
216
+ if (Node.isFunctionDeclaration(node)) return "function";
217
+ if (Node.isInterfaceDeclaration(node)) return "interface";
218
+ if (Node.isTypeAliasDeclaration(node)) return "type_alias";
219
+ if (Node.isEnumDeclaration(node)) return "enum";
220
+ if (Node.isVariableDeclaration(node)) return "variable";
221
+ if (Node.isMethodDeclaration(node)) return "method";
222
+ if (Node.isPropertyDeclaration(node)) return "property";
223
+ if (Node.isParameterDeclaration(node)) return "parameter";
224
+ return "variable";
225
+ }
133
226
  /**
134
- * Resolve definition of a symbol at a path
227
+ * Build/Warm the index for a given path
135
228
  */
136
- async resolveDefinition(symbolName, path2) {
137
- const projects = await projectManager.getProjectsForPath(path2);
138
- const results = [];
139
- for (const project of projects) {
140
- const sourceFiles = project.getSourceFiles();
141
- for (const sourceFile of sourceFiles) {
142
- const nodes = sourceFile.getDescendantsOfKind(SyntaxKind.Identifier).filter((id) => id.getText() === symbolName);
143
- for (const node of nodes) {
144
- const definitions = node.getDefinitionNodes();
145
- for (const defNode of definitions) {
146
- results.push(this.mapToDefinitionLocation(defNode));
229
+ async buildIndex(rootDir) {
230
+ const startTime = Date.now();
231
+ const safeRoot = validateWorkspacePath(rootDir);
232
+ const cachePath = this.getCachePath(safeRoot);
233
+ if (fs2.existsSync(cachePath)) {
234
+ try {
235
+ const cached = JSON.parse(
236
+ fs2.readFileSync(cachePath, "utf-8")
237
+ );
238
+ if (this.isCacheValid(cached, safeRoot)) {
239
+ this.index = cached.symbols;
240
+ return this.computeStats(
241
+ cached,
242
+ Date.now() - startTime,
243
+ process.memoryUsage().heapUsed / 1024 / 1024
244
+ );
245
+ }
246
+ } catch (e) {
247
+ }
248
+ }
249
+ const tsconfigs = await projectManager.getProjectsForPath(safeRoot);
250
+ const symbols = {};
251
+ const fileHashes = {};
252
+ for (const config of tsconfigs) {
253
+ const project = projectManager.ensureProject(config);
254
+ project.addSourceFilesFromTsConfig(config);
255
+ for (const sourceFile of project.getSourceFiles()) {
256
+ const filePath = sourceFile.getFilePath();
257
+ try {
258
+ fileHashes[filePath] = fs2.statSync(filePath).mtimeMs;
259
+ } catch {
260
+ continue;
261
+ }
262
+ for (const [name, decls] of sourceFile.getExportedDeclarations()) {
263
+ for (const decl of decls) {
264
+ const entry = {
265
+ name,
266
+ kind: this.mapKind(decl),
267
+ file: filePath,
268
+ line: decl.getStartLineNumber(),
269
+ column: decl.getStart() - decl.getStartLinePos(),
270
+ exported: true
271
+ };
272
+ (symbols[name] ||= []).push(entry);
273
+ }
274
+ }
275
+ for (const fn of sourceFile.getFunctions()) {
276
+ const name = fn.getName();
277
+ if (name && !symbols[name]?.some(
278
+ (s) => s.file === filePath && s.line === fn.getStartLineNumber()
279
+ )) {
280
+ const entry = {
281
+ name,
282
+ kind: "function",
283
+ file: filePath,
284
+ line: fn.getStartLineNumber(),
285
+ column: fn.getStart() - fn.getStartLinePos(),
286
+ exported: false
287
+ };
288
+ (symbols[name] ||= []).push(entry);
289
+ }
290
+ }
291
+ for (const cls of sourceFile.getClasses()) {
292
+ const name = cls.getName();
293
+ if (name && !symbols[name]?.some(
294
+ (s) => s.file === filePath && s.line === cls.getStartLineNumber()
295
+ )) {
296
+ const entry = {
297
+ name,
298
+ kind: "class",
299
+ file: filePath,
300
+ line: cls.getStartLineNumber(),
301
+ column: cls.getStart() - cls.getStartLinePos(),
302
+ exported: false
303
+ };
304
+ (symbols[name] ||= []).push(entry);
147
305
  }
148
306
  }
149
307
  }
150
308
  }
151
- return this.deduplicateLocations(results);
309
+ const cache = {
310
+ version: 1,
311
+ rootDir: safeRoot,
312
+ builtAt: (/* @__PURE__ */ new Date()).toISOString(),
313
+ fileHashes,
314
+ symbols
315
+ };
316
+ fs2.mkdirSync(path2.dirname(cachePath), { recursive: true });
317
+ fs2.writeFileSync(cachePath, JSON.stringify(cache));
318
+ this.index = symbols;
319
+ const duration = Date.now() - startTime;
320
+ const memoryUsage = process.memoryUsage().heapUsed / 1024 / 1024;
321
+ return this.computeStats(cache, duration, Math.round(memoryUsage));
152
322
  }
153
- /**
154
- * Find references to a symbol
155
- */
156
- async findReferences(symbolName, path2, limit = 50, offset = 0) {
157
- const projects = await projectManager.getProjectsForPath(path2);
158
- const results = [];
159
- for (const project of projects) {
160
- const sourceFiles = project.getSourceFiles();
161
- for (const sourceFile of sourceFiles) {
162
- const nodes = sourceFile.getDescendantsOfKind(SyntaxKind.Identifier).filter((id) => id.getText() === symbolName);
163
- for (const node of nodes) {
164
- const referencedSymbols = node.findReferences();
165
- for (const referencedSymbol of referencedSymbols) {
166
- const references = referencedSymbol.getReferences();
167
- for (const ref of references) {
168
- const sourceFile2 = ref.getSourceFile();
169
- const lineAndColumn = sourceFile2.getLineAndColumnAtPos(
170
- ref.getTextSpan().getStart()
171
- );
172
- results.push({
173
- file: sourceFile2.getFilePath(),
174
- line: lineAndColumn.line,
175
- column: lineAndColumn.column,
176
- text: ref.getNode().getParent()?.getText() || ref.getNode().getText()
177
- });
323
+ lookup(name) {
324
+ return this.index[name] || [];
325
+ }
326
+ lookupByFile(file) {
327
+ return Object.values(this.index).flat().filter((e) => e.file === file);
328
+ }
329
+ };
330
+ var symbolIndex = new SymbolIndex();
331
+
332
+ // src/worker/pool.ts
333
+ import { Worker } from "worker_threads";
334
+ import path3 from "path";
335
+ import { fileURLToPath } from "url";
336
+ var WorkerPool = class {
337
+ constructor(poolSize) {
338
+ this.poolSize = poolSize;
339
+ }
340
+ workers = [];
341
+ available = [];
342
+ queue = [];
343
+ activeJobs = /* @__PURE__ */ new Map();
344
+ taskId = 0;
345
+ async init() {
346
+ const __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
347
+ const workerPath = path3.join(__dirname2, "ast-worker.js");
348
+ for (let i = 0; i < this.poolSize; i++) {
349
+ const worker = new Worker(workerPath);
350
+ worker.on("message", (msg) => this.handleResult(msg));
351
+ worker.on("error", (err) => this.handleWorkerError(worker, err));
352
+ this.workers.push(worker);
353
+ this.available.push(worker);
354
+ }
355
+ }
356
+ async execute(type, payload) {
357
+ return new Promise((resolve, reject) => {
358
+ const id = String(++this.taskId);
359
+ const task = { id, type, payload, resolve, reject };
360
+ const worker = this.available.pop();
361
+ if (worker) {
362
+ this.dispatch(worker, task);
363
+ } else {
364
+ this.queue.push(task);
365
+ }
366
+ });
367
+ }
368
+ dispatch(worker, task) {
369
+ this.activeJobs.set(task.id, task);
370
+ worker.postMessage({ id: task.id, type: task.type, payload: task.payload });
371
+ }
372
+ handleResult(msg) {
373
+ const task = this.activeJobs.get(msg.id);
374
+ if (!task) return;
375
+ this.activeJobs.delete(msg.id);
376
+ if (msg.error) {
377
+ task.reject(new Error(msg.error));
378
+ } else {
379
+ task.resolve(msg.result);
380
+ }
381
+ const worker = this.workers.find(
382
+ (w) => !this.available.includes(w) && ![...this.activeJobs.values()].some((t) => t.id === msg.id)
383
+ // simplistic
384
+ );
385
+ }
386
+ handleWorkerError(worker, err) {
387
+ const idx = this.workers.indexOf(worker);
388
+ if (idx !== -1) {
389
+ worker.terminate();
390
+ const __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
391
+ const workerPath = path3.join(__dirname2, "ast-worker.js");
392
+ const newWorker = new Worker(workerPath);
393
+ newWorker.on("message", (msg) => this.handleResult(msg));
394
+ newWorker.on("error", (e) => this.handleWorkerError(newWorker, e));
395
+ this.workers[idx] = newWorker;
396
+ this.available.push(newWorker);
397
+ }
398
+ }
399
+ async terminate() {
400
+ await Promise.all(this.workers.map((w) => w.terminate()));
401
+ this.workers = [];
402
+ this.available = [];
403
+ }
404
+ };
405
+
406
+ // src/adapters/typescript-adapter.ts
407
+ var TypeScriptAdapter = class {
408
+ pool;
409
+ constructor() {
410
+ const poolSize = parseInt(process.env.AST_WORKER_POOL_SIZE || "2");
411
+ this.pool = new WorkerPool(poolSize);
412
+ }
413
+ async resolveDefinition(symbolName, path4) {
414
+ validateWorkspacePath(path4);
415
+ const indexHits = symbolIndex.lookup(symbolName);
416
+ if (indexHits.length > 0) {
417
+ const results = [];
418
+ for (const hit of indexHits) {
419
+ const tsconfig2 = await projectManager.findNearestTsConfig(hit.file);
420
+ if (tsconfig2) {
421
+ const project = projectManager.ensureProject(tsconfig2);
422
+ const sourceFile = project.addSourceFileAtPathIfExists(hit.file);
423
+ if (sourceFile) {
424
+ const exported = sourceFile.getExportedDeclarations().get(symbolName);
425
+ if (exported && exported.length > 0) {
426
+ results.push(this.mapToDefinitionLocation(exported[0]));
427
+ continue;
178
428
  }
179
429
  }
180
430
  }
431
+ results.push({
432
+ file: hit.file,
433
+ line: hit.line,
434
+ column: hit.column,
435
+ kind: hit.kind,
436
+ snippet: "",
437
+ documentation: void 0
438
+ });
181
439
  }
440
+ return results;
441
+ }
442
+ if (fs3.statSync(path4).isDirectory()) {
443
+ return [];
444
+ }
445
+ const tsconfig = await projectManager.findNearestTsConfig(path4);
446
+ if (!tsconfig) return [];
447
+ try {
448
+ const result = await this.pool.execute(
449
+ "resolve_definition",
450
+ {
451
+ tsconfig,
452
+ file: path4,
453
+ symbol: symbolName
454
+ }
455
+ );
456
+ return result;
457
+ } catch {
458
+ const project = projectManager.ensureProject(tsconfig);
459
+ const sourceFile = project.addSourceFileAtPathIfExists(path4);
460
+ if (!sourceFile) return [];
461
+ const exported = sourceFile.getExportedDeclarations().get(symbolName);
462
+ if (!exported) return [];
463
+ return exported.map((decl) => this.mapToDefinitionLocation(decl));
182
464
  }
183
- const uniqueResults = this.deduplicateLocations(results);
184
- return {
185
- references: uniqueResults.slice(offset, offset + limit),
186
- total_count: uniqueResults.length
187
- };
188
465
  }
189
- /**
190
- * Find implementations for a symbol
191
- */
192
- async findImplementations(symbolName, path2, limit = 50, offset = 0) {
193
- const projects = await projectManager.getProjectsForPath(path2);
466
+ async findReferences(symbolName, path4, limit = 50, offset = 0) {
467
+ validateWorkspacePath(path4);
468
+ const hits = symbolIndex.lookup(symbolName);
469
+ if (hits.length === 0) return { references: [], total_count: 0 };
470
+ const hit = hits[0];
471
+ const tsconfig = await projectManager.findNearestTsConfig(hit.file);
472
+ if (!tsconfig) return { references: [], total_count: 0 };
473
+ const project = projectManager.ensureProject(tsconfig);
474
+ const sourceFile = project.addSourceFileAtPathIfExists(hit.file);
475
+ if (!sourceFile) return { references: [], total_count: 0 };
476
+ const exported = sourceFile.getExportedDeclarations().get(symbolName);
477
+ if (!exported || exported.length === 0)
478
+ return { references: [], total_count: 0 };
479
+ const targetNode = exported[0];
480
+ try {
481
+ const { searchCode: searchCode2 } = await import("./search-code-V3LACKQ6.js");
482
+ const searchResults = await searchCode2(
483
+ symbolName,
484
+ path4,
485
+ "*.{ts,tsx,js,jsx}",
486
+ 1e3,
487
+ false
488
+ );
489
+ const filesToLoad = [...new Set(searchResults.map((r) => r.file))];
490
+ for (const file of filesToLoad) {
491
+ project.addSourceFileAtPathIfExists(file);
492
+ }
493
+ } catch (_e) {
494
+ }
495
+ const refSymbols = targetNode.findReferences?.();
496
+ if (!refSymbols) return { references: [], total_count: 0 };
194
497
  const results = [];
195
- for (const project of projects) {
196
- const sourceFiles = project.getSourceFiles();
197
- for (const sourceFile of sourceFiles) {
198
- const nodes = sourceFile.getDescendantsOfKind(SyntaxKind.Identifier).filter((id) => id.getText() === symbolName);
199
- for (const node of nodes) {
200
- const implementations = node.getImplementations();
201
- for (const impl of implementations) {
202
- const sourceFile2 = impl.getSourceFile();
203
- const lineAndColumn = sourceFile2.getLineAndColumnAtPos(
204
- impl.getTextSpan().getStart()
205
- );
206
- results.push({
207
- file: sourceFile2.getFilePath(),
208
- line: lineAndColumn.line,
209
- column: lineAndColumn.column,
210
- text: impl.getNode().getParent()?.getText() || impl.getNode().getText()
211
- });
212
- }
213
- }
498
+ for (const refSymbol of refSymbols) {
499
+ for (const ref of refSymbol.getReferences()) {
500
+ const sf = ref.getSourceFile();
501
+ const lc = sf.getLineAndColumnAtPos(ref.getTextSpan().getStart());
502
+ results.push({
503
+ file: sf.getFilePath(),
504
+ line: lc.line,
505
+ column: lc.column,
506
+ text: ref.getNode().getParent()?.getText() || ref.getNode().getText()
507
+ });
214
508
  }
215
509
  }
216
- const uniqueResults = this.deduplicateLocations(results);
510
+ const unique = this.deduplicateLocations(results);
217
511
  return {
218
- implementations: uniqueResults.slice(offset, offset + limit),
219
- total_count: uniqueResults.length
512
+ references: unique.slice(offset, offset + limit),
513
+ total_count: unique.length
220
514
  };
221
515
  }
222
- /**
223
- * Get file structure overview
224
- */
516
+ async findImplementations(symbolName, path4, limit = 50, offset = 0) {
517
+ validateWorkspacePath(path4);
518
+ const hits = symbolIndex.lookup(symbolName);
519
+ if (hits.length === 0) return { implementations: [], total_count: 0 };
520
+ const hit = hits[0];
521
+ const tsconfig = await projectManager.findNearestTsConfig(hit.file);
522
+ if (!tsconfig) return { implementations: [], total_count: 0 };
523
+ try {
524
+ const result = await this.pool.execute("find_implementations", {
525
+ tsconfig,
526
+ file: hit.file,
527
+ symbol: symbolName
528
+ });
529
+ return {
530
+ implementations: result.implementations.slice(offset, offset + limit),
531
+ total_count: result.total_count
532
+ };
533
+ } catch {
534
+ const project = projectManager.ensureProject(tsconfig);
535
+ const sourceFile = project.addSourceFileAtPathIfExists(hit.file);
536
+ if (!sourceFile) return { implementations: [], total_count: 0 };
537
+ const exported = sourceFile.getExportedDeclarations().get(symbolName);
538
+ if (!exported || exported.length === 0)
539
+ return { implementations: [], total_count: 0 };
540
+ const targetNode = exported[0];
541
+ if (!Node2.isClassDeclaration(targetNode) && !Node2.isInterfaceDeclaration(targetNode)) {
542
+ return { implementations: [], total_count: 0 };
543
+ }
544
+ try {
545
+ const { searchCode: searchCode2 } = await import("./search-code-V3LACKQ6.js");
546
+ const searchResults = await searchCode2(
547
+ symbolName,
548
+ path4,
549
+ "*.{ts,tsx,js,jsx}",
550
+ 1e3,
551
+ false
552
+ );
553
+ const filesToLoad = [...new Set(searchResults.map((r) => r.file))];
554
+ for (const file of filesToLoad) {
555
+ project.addSourceFileAtPathIfExists(file);
556
+ }
557
+ } catch (_e) {
558
+ }
559
+ const results = [];
560
+ const implementations = targetNode.getImplementations?.();
561
+ if (implementations) {
562
+ for (const impl of implementations) {
563
+ const sf = impl.getSourceFile();
564
+ const lc = sf.getLineAndColumnAtPos(impl.getTextSpan().getStart());
565
+ results.push({
566
+ file: sf.getFilePath(),
567
+ line: lc.line,
568
+ column: lc.column,
569
+ text: impl.getNode().getParent()?.getText() || impl.getNode().getText()
570
+ });
571
+ }
572
+ }
573
+ const unique = this.deduplicateLocations(results);
574
+ return {
575
+ implementations: unique.slice(offset, offset + limit),
576
+ total_count: unique.length
577
+ };
578
+ }
579
+ }
225
580
  async getFileStructure(filePath) {
226
- const project = await projectManager.getProjectForFile(filePath);
227
- if (!project) return void 0;
228
- const sourceFile = project.getSourceFile(filePath);
581
+ const safePath = validateWorkspacePath(filePath);
582
+ const tsconfig = await projectManager.findNearestTsConfig(safePath);
583
+ if (!tsconfig) return void 0;
584
+ const project = projectManager.ensureProject(tsconfig);
585
+ const sourceFile = project.addSourceFileAtPathIfExists(safePath);
229
586
  if (!sourceFile) return void 0;
230
587
  const structure = {
231
- file: filePath,
588
+ file: safePath,
232
589
  imports: sourceFile.getImportDeclarations().map((imp) => ({
233
590
  module: imp.getModuleSpecifierValue(),
234
591
  names: imp.getNamedImports().map((ni) => ni.getName())
@@ -245,9 +602,9 @@ var TypeScriptAdapter = class {
245
602
  };
246
603
  return structure;
247
604
  }
248
- /**
249
- * Helper: Map ts-morph Node (Declaration) to DefinitionLocation
250
- */
605
+ async shutdown() {
606
+ await this.pool.terminate();
607
+ }
251
608
  mapToDefinitionLocation(node) {
252
609
  const sourceFile = node.getSourceFile();
253
610
  const lineAndColumn = sourceFile.getLineAndColumnAtPos(node.getStart());
@@ -260,34 +617,25 @@ var TypeScriptAdapter = class {
260
617
  documentation: this.getJsDoc(node)
261
618
  };
262
619
  }
263
- /**
264
- * Helper: Map ts-morph Node to SymbolKind
265
- */
266
620
  mapNodeToSymbolKind(node) {
267
- if (Node.isClassDeclaration(node)) return "class";
268
- if (Node.isFunctionDeclaration(node)) return "function";
269
- if (Node.isInterfaceDeclaration(node)) return "interface";
270
- if (Node.isTypeAliasDeclaration(node)) return "type_alias";
271
- if (Node.isEnumDeclaration(node)) return "enum";
272
- if (Node.isVariableDeclaration(node)) return "variable";
273
- if (Node.isMethodDeclaration(node)) return "method";
274
- if (Node.isPropertyDeclaration(node)) return "property";
275
- if (Node.isParameterDeclaration(node)) return "parameter";
621
+ if (Node2.isClassDeclaration(node)) return "class";
622
+ if (Node2.isFunctionDeclaration(node)) return "function";
623
+ if (Node2.isInterfaceDeclaration(node)) return "interface";
624
+ if (Node2.isTypeAliasDeclaration(node)) return "type_alias";
625
+ if (Node2.isEnumDeclaration(node)) return "enum";
626
+ if (Node2.isVariableDeclaration(node)) return "variable";
627
+ if (Node2.isMethodDeclaration(node)) return "method";
628
+ if (Node2.isPropertyDeclaration(node)) return "property";
629
+ if (Node2.isParameterDeclaration(node)) return "parameter";
276
630
  return "variable";
277
631
  }
278
- /**
279
- * Helper: Map Symbol to SymbolKind
280
- */
281
632
  mapSymbolKind(symbol) {
282
633
  const decls = symbol.getDeclarations();
283
634
  if (decls.length > 0) return this.mapNodeToSymbolKind(decls[0]);
284
635
  return "variable";
285
636
  }
286
- /**
287
- * Helper: Get JSDoc from Node
288
- */
289
637
  getJsDoc(node) {
290
- if (Node.isJSDocable(node)) {
638
+ if (Node2.isJSDocable(node)) {
291
639
  const docs = node.getJsDocs();
292
640
  if (docs.length > 0) {
293
641
  return docs[0].getCommentText();
@@ -295,11 +643,8 @@ var TypeScriptAdapter = class {
295
643
  }
296
644
  return void 0;
297
645
  }
298
- /**
299
- * Helper: Get full JSDoc info (with tags)
300
- */
301
646
  getSymbolDocs(node) {
302
- if (Node.isJSDocable(node)) {
647
+ if (Node2.isJSDocable(node)) {
303
648
  const docs = node.getJsDocs();
304
649
  if (docs.length > 0) {
305
650
  const doc = docs[0];
@@ -362,9 +707,6 @@ var TypeScriptAdapter = class {
362
707
  members: enm.getMembers().map((m) => m.getName())
363
708
  };
364
709
  }
365
- /**
366
- * Helper: Deduplicate locations
367
- */
368
710
  deduplicateLocations(locations) {
369
711
  const seen = /* @__PURE__ */ new Set();
370
712
  return locations.filter((loc) => {
@@ -378,20 +720,20 @@ var TypeScriptAdapter = class {
378
720
  var typescriptAdapter = new TypeScriptAdapter();
379
721
 
380
722
  // src/tools/resolve-definition.ts
381
- async function resolveDefinition(symbol, path2) {
382
- return await typescriptAdapter.resolveDefinition(symbol, path2);
723
+ async function resolveDefinition(symbol, path4) {
724
+ return await typescriptAdapter.resolveDefinition(symbol, path4);
383
725
  }
384
726
 
385
727
  // src/tools/find-references.ts
386
- async function findReferences(symbol, path2, limit = 50, offset = 0) {
387
- return await typescriptAdapter.findReferences(symbol, path2, limit, offset);
728
+ async function findReferences(symbol, path4, limit = 50, offset = 0) {
729
+ return await typescriptAdapter.findReferences(symbol, path4, limit, offset);
388
730
  }
389
731
 
390
732
  // src/tools/find-implementations.ts
391
- async function findImplementations(symbol, path2, limit = 50, offset = 0) {
733
+ async function findImplementations(symbol, path4, limit = 50, offset = 0) {
392
734
  return await typescriptAdapter.findImplementations(
393
735
  symbol,
394
- path2,
736
+ path4,
395
737
  limit,
396
738
  offset
397
739
  );
@@ -402,76 +744,27 @@ async function getFileStructure(file) {
402
744
  return await typescriptAdapter.getFileStructure(file);
403
745
  }
404
746
 
405
- // src/tools/search-code.ts
406
- import { execFile } from "child_process";
407
- import { promisify } from "util";
408
- import { rgPath } from "@vscode/ripgrep";
409
- var execFileAsync = promisify(execFile);
410
- async function searchCode(pattern, searchPath, filePattern, limit = 50) {
411
- const args = [
412
- "--json",
413
- "--max-count",
414
- limit.toString(),
415
- "--fixed-strings",
416
- // Default to fixed strings unless we want regex
417
- pattern,
418
- searchPath
419
- ];
420
- if (filePattern) {
421
- args.push("--glob", filePattern);
422
- }
423
- args.push("--glob", "!**/node_modules/**");
424
- args.push("--glob", "!**/dist/**");
425
- args.push("--glob", "!**/.git/**");
426
- try {
427
- const { stdout } = await execFileAsync(rgPath, args);
428
- const lines = stdout.split("\n").filter(Boolean);
429
- const results = [];
430
- for (const line of lines) {
431
- const data = JSON.parse(line);
432
- if (data.type === "match") {
433
- const file = data.data.path.text;
434
- const lineNumber = data.data.line_number;
435
- const submatches = data.data.submatches;
436
- for (const submatch of submatches) {
437
- results.push({
438
- file,
439
- line: lineNumber,
440
- column: submatch.start,
441
- text: data.data.lines.text.trim()
442
- });
443
- }
444
- }
445
- }
446
- return results.slice(0, limit);
447
- } catch (error) {
448
- if (error.code === 1) {
449
- return [];
450
- }
451
- throw error;
452
- }
453
- }
454
-
455
747
  // src/tools/get-symbol-docs.ts
456
- import { SyntaxKind as SyntaxKind2 } from "ts-morph";
748
+ import { SyntaxKind } from "ts-morph";
457
749
  async function getSymbolDocs(symbol, filePath) {
458
- const projects = await projectManager.getProjectsForPath(filePath);
459
- for (const project of projects) {
460
- const sourceFile = project.getSourceFile(filePath);
461
- if (sourceFile) {
462
- const node = sourceFile.getDescendantsOfKind(SyntaxKind2.Identifier).find((id) => id.getText() === symbol);
463
- if (node) {
464
- const decls = node.getSymbol()?.getDeclarations();
465
- if (decls && decls.length > 0) {
466
- const docs = typescriptAdapter.getSymbolDocs(decls[0]);
467
- if (docs) {
468
- return {
469
- symbol,
470
- file: sourceFile.getFilePath(),
471
- line: sourceFile.getLineAndColumnAtPos(decls[0].getStart()).line,
472
- ...docs
473
- };
474
- }
750
+ const safePath = validateWorkspacePath(filePath);
751
+ const tsconfig = await projectManager.findNearestTsConfig(safePath);
752
+ if (!tsconfig) return void 0;
753
+ const project = projectManager.ensureProject(tsconfig);
754
+ const sourceFile = project.addSourceFileAtPathIfExists(safePath);
755
+ if (sourceFile) {
756
+ const node = sourceFile.getDescendantsOfKind(SyntaxKind.Identifier).find((id) => id.getText() === symbol);
757
+ if (node) {
758
+ const decls = node.getSymbol()?.getDeclarations();
759
+ if (decls && decls.length > 0) {
760
+ const docs = typescriptAdapter.getSymbolDocs(decls[0]);
761
+ if (docs) {
762
+ return {
763
+ symbol,
764
+ file: sourceFile.getFilePath(),
765
+ line: sourceFile.getLineAndColumnAtPos(decls[0].getStart()).line,
766
+ ...docs
767
+ };
475
768
  }
476
769
  }
477
770
  }
@@ -479,49 +772,9 @@ async function getSymbolDocs(symbol, filePath) {
479
772
  return void 0;
480
773
  }
481
774
 
482
- // src/index/symbol-index.ts
483
- var SymbolIndex = class {
484
- /**
485
- * Build/Warm the index for a given path
486
- */
487
- async buildIndex(rootDir) {
488
- const startTime = Date.now();
489
- const projects = await projectManager.getProjectsForPath(rootDir);
490
- let fileCount = 0;
491
- let functionCount = 0;
492
- let classCount = 0;
493
- let interfaceCount = 0;
494
- let typeCount = 0;
495
- for (const project of projects) {
496
- const sourceFiles = project.getSourceFiles();
497
- fileCount += sourceFiles.length;
498
- for (const sourceFile of sourceFiles) {
499
- functionCount += sourceFile.getFunctions().length;
500
- classCount += sourceFile.getClasses().length;
501
- interfaceCount += sourceFile.getInterfaces().length;
502
- typeCount += sourceFile.getTypeAliases().length;
503
- }
504
- }
505
- const duration = Date.now() - startTime;
506
- const memoryUsage = process.memoryUsage().heapUsed / 1024 / 1024;
507
- return {
508
- indexed: {
509
- files: fileCount,
510
- functions: functionCount,
511
- classes: classCount,
512
- interfaces: interfaceCount,
513
- types: typeCount
514
- },
515
- duration_ms: duration,
516
- memory_mb: Math.round(memoryUsage)
517
- };
518
- }
519
- };
520
- var symbolIndex = new SymbolIndex();
521
-
522
775
  // src/tools/build-symbol-index.ts
523
- async function buildSymbolIndex(path2) {
524
- return await symbolIndex.buildIndex(path2);
776
+ async function buildSymbolIndex(path4) {
777
+ return await symbolIndex.buildIndex(path4);
525
778
  }
526
779
 
527
780
  // src/index.ts
@@ -615,7 +868,8 @@ var ASTExplorerServer = class {
615
868
  pattern: { type: "string", description: "Search pattern" },
616
869
  path: { type: "string", description: "Directory to search" },
617
870
  filePattern: { type: "string", description: "Glob filter" },
618
- limit: { type: "number", default: 50 }
871
+ limit: { type: "number", default: 50 },
872
+ regex: { type: "boolean", default: true }
619
873
  },
620
874
  required: ["pattern", "path"]
621
875
  }
@@ -651,8 +905,8 @@ var ASTExplorerServer = class {
651
905
  try {
652
906
  switch (name) {
653
907
  case "resolve_definition": {
654
- const { symbol, path: path2 } = ResolveDefinitionSchema.parse(args);
655
- const results = await resolveDefinition(symbol, path2);
908
+ const { symbol, path: path4 } = ResolveDefinitionSchema.parse(args);
909
+ const results = await resolveDefinition(symbol, path4);
656
910
  return {
657
911
  content: [
658
912
  { type: "text", text: JSON.stringify(results, null, 2) }
@@ -660,8 +914,8 @@ var ASTExplorerServer = class {
660
914
  };
661
915
  }
662
916
  case "find_references": {
663
- const { symbol, path: path2, limit, offset } = FindReferencesSchema.parse(args);
664
- const results = await findReferences(symbol, path2, limit, offset);
917
+ const { symbol, path: path4, limit, offset } = FindReferencesSchema.parse(args);
918
+ const results = await findReferences(symbol, path4, limit, offset);
665
919
  return {
666
920
  content: [
667
921
  { type: "text", text: JSON.stringify(results, null, 2) }
@@ -669,10 +923,10 @@ var ASTExplorerServer = class {
669
923
  };
670
924
  }
671
925
  case "find_implementations": {
672
- const { symbol, path: path2, limit, offset } = FindImplementationsSchema.parse(args);
926
+ const { symbol, path: path4, limit, offset } = FindImplementationsSchema.parse(args);
673
927
  const results = await findImplementations(
674
928
  symbol,
675
- path2,
929
+ path4,
676
930
  limit,
677
931
  offset
678
932
  );
@@ -692,8 +946,14 @@ var ASTExplorerServer = class {
692
946
  };
693
947
  }
694
948
  case "search_code": {
695
- const { pattern, path: path2, filePattern, limit } = SearchCodeSchema.parse(args);
696
- const results = await searchCode(pattern, path2, filePattern, limit);
949
+ const { pattern, path: path4, filePattern, limit, regex } = SearchCodeSchema.parse(args);
950
+ const results = await searchCode(
951
+ pattern,
952
+ path4,
953
+ filePattern,
954
+ limit,
955
+ regex
956
+ );
697
957
  return {
698
958
  content: [
699
959
  { type: "text", text: JSON.stringify(results, null, 2) }
@@ -701,15 +961,15 @@ var ASTExplorerServer = class {
701
961
  };
702
962
  }
703
963
  case "get_symbol_docs": {
704
- const { symbol, path: path2 } = GetSymbolDocsSchema.parse(args);
705
- const docs = await getSymbolDocs(symbol, path2);
964
+ const { symbol, path: path4 } = GetSymbolDocsSchema.parse(args);
965
+ const docs = await getSymbolDocs(symbol, path4);
706
966
  return {
707
967
  content: [{ type: "text", text: JSON.stringify(docs, null, 2) }]
708
968
  };
709
969
  }
710
970
  case "build_symbol_index": {
711
- const { path: path2 } = BuildSymbolIndexSchema.parse(args);
712
- const stats = await buildSymbolIndex(path2);
971
+ const { path: path4 } = BuildSymbolIndexSchema.parse(args);
972
+ const stats = await buildSymbolIndex(path4);
713
973
  return {
714
974
  content: [{ type: "text", text: JSON.stringify(stats, null, 2) }]
715
975
  };