@aiready/ast-mcp-server 0.1.1 → 0.1.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.
package/dist/index.cjs CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,17 +30,118 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
33
+ // ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_tsx@4.21.0_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js
34
+ var init_cjs_shims = __esm({
35
+ "../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_tsx@4.21.0_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js"() {
36
+ "use strict";
37
+ }
38
+ });
39
+
40
+ // src/security.ts
41
+ function resolveWorkspaceRoot() {
42
+ return process.env.AST_WORKSPACE_ROOT || process.cwd();
43
+ }
44
+ function validateWorkspacePath(inputPath) {
45
+ const root = resolveWorkspaceRoot();
46
+ const resolved = import_path.default.resolve(root, inputPath);
47
+ const normalized = import_path.default.normalize(resolved);
48
+ if (!normalized.startsWith(root)) {
49
+ throw new Error(
50
+ `Path traversal detected: ${inputPath} escapes workspace root`
51
+ );
52
+ }
53
+ if (normalized.includes("\0")) {
54
+ throw new Error("Path contains null bytes");
55
+ }
56
+ return normalized;
57
+ }
58
+ var import_path;
59
+ var init_security = __esm({
60
+ "src/security.ts"() {
61
+ "use strict";
62
+ init_cjs_shims();
63
+ import_path = __toESM(require("path"), 1);
64
+ }
65
+ });
66
+
67
+ // src/tools/search-code.ts
68
+ var search_code_exports = {};
69
+ __export(search_code_exports, {
70
+ searchCode: () => searchCode
71
+ });
72
+ async function searchCode(pattern, searchPath, filePattern, limit = 50, regex = true) {
73
+ const safePath = validateWorkspacePath(searchPath);
74
+ const args = [
75
+ "--json",
76
+ "--max-count",
77
+ limit.toString(),
78
+ "--max-columns",
79
+ "500"
80
+ ];
81
+ if (!regex) {
82
+ args.push("--fixed-strings");
83
+ }
84
+ args.push(pattern, safePath);
85
+ if (filePattern) {
86
+ args.push("--glob", filePattern);
87
+ }
88
+ args.push("--glob", "!**/node_modules/**");
89
+ args.push("--glob", "!**/dist/**");
90
+ args.push("--glob", "!**/.git/**");
91
+ try {
92
+ const { stdout } = await execFileAsync(import_ripgrep.rgPath, args);
93
+ const lines = stdout.split("\n").filter(Boolean);
94
+ const results = [];
95
+ for (const line of lines) {
96
+ const data = JSON.parse(line);
97
+ if (data.type === "match") {
98
+ const file = data.data.path.text;
99
+ const lineNumber = data.data.line_number;
100
+ const submatches = data.data.submatches;
101
+ for (const submatch of submatches) {
102
+ results.push({
103
+ file,
104
+ line: lineNumber,
105
+ column: submatch.start,
106
+ text: data.data.lines.text.trim()
107
+ });
108
+ }
109
+ }
110
+ }
111
+ return results.slice(0, limit);
112
+ } catch (error) {
113
+ if (error.code === 1) {
114
+ return [];
115
+ }
116
+ throw error;
117
+ }
118
+ }
119
+ var import_child_process, import_util, import_ripgrep, execFileAsync;
120
+ var init_search_code = __esm({
121
+ "src/tools/search-code.ts"() {
122
+ "use strict";
123
+ init_cjs_shims();
124
+ import_child_process = require("child_process");
125
+ import_util = require("util");
126
+ import_ripgrep = require("@vscode/ripgrep");
127
+ init_security();
128
+ execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
129
+ }
130
+ });
131
+
30
132
  // src/index.ts
31
133
  var index_exports = {};
32
134
  __export(index_exports, {
33
135
  ASTExplorerServer: () => ASTExplorerServer
34
136
  });
35
137
  module.exports = __toCommonJS(index_exports);
138
+ init_cjs_shims();
36
139
  var import_server = require("@modelcontextprotocol/sdk/server/index.js");
37
140
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
38
141
  var import_types = require("@modelcontextprotocol/sdk/types.js");
39
142
 
40
143
  // src/schemas.ts
144
+ init_cjs_shims();
41
145
  var import_zod = require("zod");
42
146
  var ResolveDefinitionSchema = import_zod.z.object({
43
147
  symbol: import_zod.z.string().describe(
@@ -61,10 +165,11 @@ var GetFileStructureSchema = import_zod.z.object({
61
165
  file: import_zod.z.string().describe("Absolute path to the file to analyze")
62
166
  });
63
167
  var SearchCodeSchema = import_zod.z.object({
64
- pattern: import_zod.z.string().describe("Search pattern (regex)"),
168
+ pattern: import_zod.z.string().describe("Search pattern (regex by default)"),
65
169
  path: import_zod.z.string().describe("Directory to search in"),
66
170
  filePattern: import_zod.z.string().optional().describe('Glob filter (e.g., "*.ts")'),
67
- limit: import_zod.z.number().optional().default(50).describe("Max matches to return")
171
+ limit: import_zod.z.number().optional().default(50).describe("Max matches to return"),
172
+ regex: import_zod.z.boolean().optional().default(true).describe("Use regex mode (default true)")
68
173
  });
69
174
  var GetSymbolDocsSchema = import_zod.z.object({
70
175
  symbol: import_zod.z.string().describe("Symbol name to get documentation for"),
@@ -74,189 +179,450 @@ var BuildSymbolIndexSchema = import_zod.z.object({
74
179
  path: import_zod.z.string().describe("Project root directory to index")
75
180
  });
76
181
 
182
+ // src/tools/resolve-definition.ts
183
+ init_cjs_shims();
184
+
77
185
  // src/adapters/typescript-adapter.ts
78
- var import_ts_morph2 = require("ts-morph");
186
+ init_cjs_shims();
187
+ var import_ts_morph3 = require("ts-morph");
188
+ var import_fs3 = __toESM(require("fs"), 1);
79
189
 
80
190
  // src/project-manager.ts
191
+ init_cjs_shims();
81
192
  var import_ts_morph = require("ts-morph");
82
- var import_path = __toESM(require("path"), 1);
193
+ var import_path2 = __toESM(require("path"), 1);
83
194
  var import_fs = __toESM(require("fs"), 1);
84
195
  var import_glob = require("glob");
196
+ init_security();
85
197
  var ProjectManager = class {
86
198
  projects = /* @__PURE__ */ new Map();
87
199
  tsconfigCache = /* @__PURE__ */ new Map();
200
+ accessOrder = [];
201
+ // MRU at front
202
+ maxProjects = 4;
88
203
  /**
89
204
  * Find all tsconfig.json files in a directory (recursive)
90
205
  */
91
206
  async findTsConfigs(rootDir) {
92
- if (this.tsconfigCache.has(rootDir)) {
93
- return this.tsconfigCache.get(rootDir);
207
+ const safeRoot = validateWorkspacePath(rootDir);
208
+ if (this.tsconfigCache.has(safeRoot)) {
209
+ return this.tsconfigCache.get(safeRoot);
94
210
  }
95
211
  const configs = await (0, import_glob.glob)("**/tsconfig.json", {
96
- cwd: rootDir,
212
+ cwd: safeRoot,
97
213
  absolute: true,
98
214
  ignore: ["**/node_modules/**", "**/dist/**"]
99
215
  });
100
- this.tsconfigCache.set(rootDir, configs);
216
+ this.tsconfigCache.set(safeRoot, configs);
101
217
  return configs;
102
218
  }
103
219
  /**
104
- * Get or create a Project for a specific file path
105
- */
106
- async getProjectForFile(filePath) {
107
- const tsconfigPath = await this.findNearestTsConfig(filePath);
108
- if (!tsconfigPath) return void 0;
109
- return this.getOrCreateProject(tsconfigPath);
110
- }
111
- /**
112
- * Get or create a Project for a tsconfig path
220
+ * Ensure a Project exists for a given tsconfig path, managing LRU cache
113
221
  */
114
- getOrCreateProject(tsconfigPath) {
222
+ ensureProject(tsconfigPath) {
223
+ this.checkMemoryPressure();
115
224
  if (this.projects.has(tsconfigPath)) {
225
+ this.moveToFront(tsconfigPath);
116
226
  return this.projects.get(tsconfigPath);
117
227
  }
228
+ while (this.projects.size >= this.maxProjects) {
229
+ const oldest = this.accessOrder.pop();
230
+ this.disposeProject(oldest);
231
+ }
118
232
  const project = new import_ts_morph.Project({
119
233
  tsConfigFilePath: tsconfigPath,
120
- skipAddingFilesFromTsConfig: false
234
+ skipAddingFilesFromTsConfig: true
235
+ // KEY: don't load all files
121
236
  });
122
237
  this.projects.set(tsconfigPath, project);
238
+ this.accessOrder.unshift(tsconfigPath);
123
239
  return project;
124
240
  }
241
+ /**
242
+ * Move a tsconfig path to the front of the access order (MRU)
243
+ */
244
+ moveToFront(tsconfigPath) {
245
+ const index = this.accessOrder.indexOf(tsconfigPath);
246
+ if (index > -1) {
247
+ this.accessOrder.splice(index, 1);
248
+ this.accessOrder.unshift(tsconfigPath);
249
+ }
250
+ }
251
+ checkMemoryPressure() {
252
+ const heapUsed = process.memoryUsage().heapUsed / 1024 / 1024;
253
+ const maxHeap = parseInt(process.env.AST_MAX_HEAP_MB || "1536", 10);
254
+ while (heapUsed > maxHeap && this.projects.size > 1) {
255
+ const oldest = this.accessOrder.pop();
256
+ this.disposeProject(oldest);
257
+ }
258
+ }
259
+ disposeProject(tsconfigPath) {
260
+ const project = this.projects.get(tsconfigPath);
261
+ if (project) {
262
+ for (const sourceFile of project.getSourceFiles()) {
263
+ project.removeSourceFile(sourceFile);
264
+ }
265
+ this.projects.delete(tsconfigPath);
266
+ }
267
+ }
125
268
  /**
126
269
  * Find the nearest tsconfig.json for a file
127
270
  */
128
271
  async findNearestTsConfig(filePath) {
129
- let currentDir = import_path.default.dirname(filePath);
130
- const root = import_path.default.parse(currentDir).root;
272
+ const safePath = validateWorkspacePath(filePath);
273
+ let currentDir = import_path2.default.dirname(safePath);
274
+ const root = import_path2.default.parse(currentDir).root;
131
275
  while (currentDir !== root) {
132
- const tsconfigPath = import_path.default.join(currentDir, "tsconfig.json");
276
+ const tsconfigPath = import_path2.default.join(currentDir, "tsconfig.json");
133
277
  if (import_fs.default.existsSync(tsconfigPath)) {
134
278
  return tsconfigPath;
135
279
  }
136
- currentDir = import_path.default.dirname(currentDir);
280
+ currentDir = import_path2.default.dirname(currentDir);
137
281
  }
138
282
  return void 0;
139
283
  }
140
284
  /**
141
- * Get all projects that might contain a file or serve a path
285
+ * Get all tsconfigs for a path
142
286
  */
143
287
  async getProjectsForPath(rootDir) {
144
- const configs = await this.findTsConfigs(rootDir);
145
- return configs.map((config) => this.getOrCreateProject(config));
288
+ return this.findTsConfigs(rootDir);
146
289
  }
147
290
  /**
148
291
  * Dispose all projects to free memory
149
292
  */
150
293
  disposeAll() {
151
- for (const project of this.projects.values()) {
152
- project.getLanguageService().compilerObject.dispose();
294
+ for (const [key] of this.projects) {
295
+ this.disposeProject(key);
153
296
  }
154
- this.projects.clear();
297
+ this.accessOrder = [];
155
298
  }
156
299
  };
157
300
  var projectManager = new ProjectManager();
158
301
 
159
- // src/adapters/typescript-adapter.ts
160
- var TypeScriptAdapter = class {
302
+ // src/index/symbol-index.ts
303
+ init_cjs_shims();
304
+ init_security();
305
+ var import_fs2 = __toESM(require("fs"), 1);
306
+ var import_crypto = __toESM(require("crypto"), 1);
307
+ var import_path3 = __toESM(require("path"), 1);
308
+ var import_ts_morph2 = require("ts-morph");
309
+ var SymbolIndex = class {
310
+ index = {};
311
+ getCachePath(rootDir) {
312
+ const hash = import_crypto.default.createHash("sha256").update(rootDir).digest("hex").slice(0, 12);
313
+ return import_path3.default.join(
314
+ process.platform === "win32" ? process.env.TEMP || "c:/temp" : "/tmp",
315
+ `ast-index-${hash}.json`
316
+ );
317
+ }
318
+ isCacheValid(cached, rootDir) {
319
+ if (cached.rootDir !== rootDir) return false;
320
+ for (const [file, cachedMtime] of Object.entries(cached.fileHashes)) {
321
+ if (!import_fs2.default.existsSync(file)) return false;
322
+ if (import_fs2.default.statSync(file).mtimeMs !== cachedMtime) return false;
323
+ }
324
+ return true;
325
+ }
326
+ computeStats(cache, duration_ms, memory_mb) {
327
+ const files = Object.keys(cache.fileHashes).length;
328
+ let functions = 0;
329
+ let classes = 0;
330
+ let interfaces = 0;
331
+ let types = 0;
332
+ for (const entries of Object.values(cache.symbols)) {
333
+ for (const entry of entries) {
334
+ if (entry.kind === "function" || entry.kind === "method") functions++;
335
+ if (entry.kind === "class") classes++;
336
+ if (entry.kind === "interface") interfaces++;
337
+ if (entry.kind === "type_alias") types++;
338
+ }
339
+ }
340
+ return {
341
+ indexed: {
342
+ files,
343
+ functions,
344
+ classes,
345
+ interfaces,
346
+ types
347
+ },
348
+ duration_ms: duration_ms || 0,
349
+ memory_mb: memory_mb || 0
350
+ };
351
+ }
352
+ mapKind(node) {
353
+ if (import_ts_morph2.Node.isClassDeclaration(node)) return "class";
354
+ if (import_ts_morph2.Node.isFunctionDeclaration(node)) return "function";
355
+ if (import_ts_morph2.Node.isInterfaceDeclaration(node)) return "interface";
356
+ if (import_ts_morph2.Node.isTypeAliasDeclaration(node)) return "type_alias";
357
+ if (import_ts_morph2.Node.isEnumDeclaration(node)) return "enum";
358
+ if (import_ts_morph2.Node.isVariableDeclaration(node)) return "variable";
359
+ if (import_ts_morph2.Node.isMethodDeclaration(node)) return "method";
360
+ if (import_ts_morph2.Node.isPropertyDeclaration(node)) return "property";
361
+ if (import_ts_morph2.Node.isParameterDeclaration(node)) return "parameter";
362
+ return "variable";
363
+ }
161
364
  /**
162
- * Resolve definition of a symbol at a path
365
+ * Build/Warm the index for a given path
163
366
  */
164
- async resolveDefinition(symbolName, path2) {
165
- const projects = await projectManager.getProjectsForPath(path2);
166
- const results = [];
167
- for (const project of projects) {
168
- const sourceFiles = project.getSourceFiles();
169
- for (const sourceFile of sourceFiles) {
170
- const nodes = sourceFile.getDescendantsOfKind(import_ts_morph2.SyntaxKind.Identifier).filter((id) => id.getText() === symbolName);
171
- for (const node of nodes) {
172
- const definitions = node.getDefinitionNodes();
173
- for (const defNode of definitions) {
174
- results.push(this.mapToDefinitionLocation(defNode));
367
+ async buildIndex(rootDir) {
368
+ const startTime = Date.now();
369
+ const safeRoot = validateWorkspacePath(rootDir);
370
+ const cachePath = this.getCachePath(safeRoot);
371
+ if (import_fs2.default.existsSync(cachePath)) {
372
+ try {
373
+ const cached = JSON.parse(
374
+ import_fs2.default.readFileSync(cachePath, "utf-8")
375
+ );
376
+ if (this.isCacheValid(cached, safeRoot)) {
377
+ this.index = cached.symbols;
378
+ return this.computeStats(
379
+ cached,
380
+ Date.now() - startTime,
381
+ process.memoryUsage().heapUsed / 1024 / 1024
382
+ );
383
+ }
384
+ } catch (e) {
385
+ }
386
+ }
387
+ const tsconfigs = await projectManager.getProjectsForPath(safeRoot);
388
+ const symbols = {};
389
+ const fileHashes = {};
390
+ for (const config of tsconfigs) {
391
+ const project = projectManager.ensureProject(config);
392
+ project.addSourceFilesFromTsConfig(config);
393
+ for (const sourceFile of project.getSourceFiles()) {
394
+ const filePath = sourceFile.getFilePath();
395
+ try {
396
+ fileHashes[filePath] = import_fs2.default.statSync(filePath).mtimeMs;
397
+ } catch {
398
+ continue;
399
+ }
400
+ for (const [name, decls] of sourceFile.getExportedDeclarations()) {
401
+ for (const decl of decls) {
402
+ const entry = {
403
+ name,
404
+ kind: this.mapKind(decl),
405
+ file: filePath,
406
+ line: decl.getStartLineNumber(),
407
+ column: decl.getStart() - decl.getStartLinePos(),
408
+ exported: true
409
+ };
410
+ (symbols[name] ||= []).push(entry);
411
+ }
412
+ }
413
+ for (const fn of sourceFile.getFunctions()) {
414
+ const name = fn.getName();
415
+ if (name && !symbols[name]?.some(
416
+ (s) => s.file === filePath && s.line === fn.getStartLineNumber()
417
+ )) {
418
+ const entry = {
419
+ name,
420
+ kind: "function",
421
+ file: filePath,
422
+ line: fn.getStartLineNumber(),
423
+ column: fn.getStart() - fn.getStartLinePos(),
424
+ exported: false
425
+ };
426
+ (symbols[name] ||= []).push(entry);
427
+ }
428
+ }
429
+ for (const cls of sourceFile.getClasses()) {
430
+ const name = cls.getName();
431
+ if (name && !symbols[name]?.some(
432
+ (s) => s.file === filePath && s.line === cls.getStartLineNumber()
433
+ )) {
434
+ const entry = {
435
+ name,
436
+ kind: "class",
437
+ file: filePath,
438
+ line: cls.getStartLineNumber(),
439
+ column: cls.getStart() - cls.getStartLinePos(),
440
+ exported: false
441
+ };
442
+ (symbols[name] ||= []).push(entry);
175
443
  }
176
444
  }
177
445
  }
178
446
  }
179
- return this.deduplicateLocations(results);
447
+ const cache = {
448
+ version: 1,
449
+ rootDir: safeRoot,
450
+ builtAt: (/* @__PURE__ */ new Date()).toISOString(),
451
+ fileHashes,
452
+ symbols
453
+ };
454
+ import_fs2.default.mkdirSync(import_path3.default.dirname(cachePath), { recursive: true });
455
+ import_fs2.default.writeFileSync(cachePath, JSON.stringify(cache));
456
+ this.index = symbols;
457
+ const duration = Date.now() - startTime;
458
+ const memoryUsage = process.memoryUsage().heapUsed / 1024 / 1024;
459
+ return this.computeStats(cache, duration, Math.round(memoryUsage));
180
460
  }
181
- /**
182
- * Find references to a symbol
183
- */
184
- async findReferences(symbolName, path2, limit = 50, offset = 0) {
185
- const projects = await projectManager.getProjectsForPath(path2);
186
- const results = [];
187
- for (const project of projects) {
188
- const sourceFiles = project.getSourceFiles();
189
- for (const sourceFile of sourceFiles) {
190
- const nodes = sourceFile.getDescendantsOfKind(import_ts_morph2.SyntaxKind.Identifier).filter((id) => id.getText() === symbolName);
191
- for (const node of nodes) {
192
- const referencedSymbols = node.findReferences();
193
- for (const referencedSymbol of referencedSymbols) {
194
- const references = referencedSymbol.getReferences();
195
- for (const ref of references) {
196
- const sourceFile2 = ref.getSourceFile();
197
- const lineAndColumn = sourceFile2.getLineAndColumnAtPos(
198
- ref.getTextSpan().getStart()
199
- );
200
- results.push({
201
- file: sourceFile2.getFilePath(),
202
- line: lineAndColumn.line,
203
- column: lineAndColumn.column,
204
- text: ref.getNode().getParent()?.getText() || ref.getNode().getText()
205
- });
461
+ lookup(name) {
462
+ return this.index[name] || [];
463
+ }
464
+ lookupByFile(file) {
465
+ return Object.values(this.index).flat().filter((e) => e.file === file);
466
+ }
467
+ };
468
+ var symbolIndex = new SymbolIndex();
469
+
470
+ // src/adapters/typescript-adapter.ts
471
+ init_security();
472
+ var TypeScriptAdapter = class {
473
+ async resolveDefinition(symbolName, path4) {
474
+ validateWorkspacePath(path4);
475
+ const indexHits = symbolIndex.lookup(symbolName);
476
+ if (indexHits.length > 0) {
477
+ const results = [];
478
+ for (const hit of indexHits) {
479
+ const tsconfig2 = await projectManager.findNearestTsConfig(hit.file);
480
+ if (tsconfig2) {
481
+ const project2 = projectManager.ensureProject(tsconfig2);
482
+ const sourceFile2 = project2.addSourceFileAtPathIfExists(hit.file);
483
+ if (sourceFile2) {
484
+ const exported2 = sourceFile2.getExportedDeclarations().get(symbolName);
485
+ if (exported2 && exported2.length > 0) {
486
+ results.push(this.mapToDefinitionLocation(exported2[0]));
487
+ continue;
206
488
  }
207
489
  }
208
490
  }
491
+ results.push({
492
+ file: hit.file,
493
+ line: hit.line,
494
+ column: hit.column,
495
+ kind: hit.kind,
496
+ snippet: "",
497
+ documentation: void 0
498
+ });
499
+ }
500
+ return results;
501
+ }
502
+ if (import_fs3.default.statSync(path4).isDirectory()) {
503
+ return [];
504
+ }
505
+ const tsconfig = await projectManager.findNearestTsConfig(path4);
506
+ if (!tsconfig) return [];
507
+ const project = projectManager.ensureProject(tsconfig);
508
+ const sourceFile = project.addSourceFileAtPathIfExists(path4);
509
+ if (!sourceFile) return [];
510
+ const exported = sourceFile.getExportedDeclarations().get(symbolName);
511
+ if (!exported) return [];
512
+ return exported.map((decl) => this.mapToDefinitionLocation(decl));
513
+ }
514
+ async findReferences(symbolName, path4, limit = 50, offset = 0) {
515
+ validateWorkspacePath(path4);
516
+ const hits = symbolIndex.lookup(symbolName);
517
+ if (hits.length === 0) return { references: [], total_count: 0 };
518
+ const hit = hits[0];
519
+ const tsconfig = await projectManager.findNearestTsConfig(hit.file);
520
+ if (!tsconfig) return { references: [], total_count: 0 };
521
+ const project = projectManager.ensureProject(tsconfig);
522
+ const sourceFile = project.addSourceFileAtPathIfExists(hit.file);
523
+ if (!sourceFile) return { references: [], total_count: 0 };
524
+ const exported = sourceFile.getExportedDeclarations().get(symbolName);
525
+ if (!exported || exported.length === 0)
526
+ return { references: [], total_count: 0 };
527
+ const targetNode = exported[0];
528
+ try {
529
+ const { searchCode: searchCode2 } = await Promise.resolve().then(() => (init_search_code(), search_code_exports));
530
+ const searchResults = await searchCode2(
531
+ symbolName,
532
+ path4,
533
+ "*.{ts,tsx,js,jsx}",
534
+ 1e3,
535
+ false
536
+ );
537
+ const filesToLoad = [...new Set(searchResults.map((r) => r.file))];
538
+ console.log("Search code files to load:", filesToLoad);
539
+ for (const file of filesToLoad) {
540
+ project.addSourceFileAtPathIfExists(file);
541
+ }
542
+ } catch (_e) {
543
+ }
544
+ const refSymbols = targetNode.findReferences?.();
545
+ if (!refSymbols) return { references: [], total_count: 0 };
546
+ const results = [];
547
+ for (const refSymbol of refSymbols) {
548
+ for (const ref of refSymbol.getReferences()) {
549
+ const sf = ref.getSourceFile();
550
+ const lc = sf.getLineAndColumnAtPos(ref.getTextSpan().getStart());
551
+ results.push({
552
+ file: sf.getFilePath(),
553
+ line: lc.line,
554
+ column: lc.column,
555
+ text: ref.getNode().getParent()?.getText() || ref.getNode().getText()
556
+ });
209
557
  }
210
558
  }
211
- const uniqueResults = this.deduplicateLocations(results);
559
+ const unique = this.deduplicateLocations(results);
212
560
  return {
213
- references: uniqueResults.slice(offset, offset + limit),
214
- total_count: uniqueResults.length
561
+ references: unique.slice(offset, offset + limit),
562
+ total_count: unique.length
215
563
  };
216
564
  }
217
- /**
218
- * Find implementations for a symbol
219
- */
220
- async findImplementations(symbolName, path2, limit = 50, offset = 0) {
221
- const projects = await projectManager.getProjectsForPath(path2);
565
+ async findImplementations(symbolName, path4, limit = 50, offset = 0) {
566
+ validateWorkspacePath(path4);
567
+ const hits = symbolIndex.lookup(symbolName);
568
+ if (hits.length === 0) return { implementations: [], total_count: 0 };
569
+ const hit = hits[0];
570
+ const tsconfig = await projectManager.findNearestTsConfig(hit.file);
571
+ if (!tsconfig) return { implementations: [], total_count: 0 };
572
+ const project = projectManager.ensureProject(tsconfig);
573
+ const sourceFile = project.addSourceFileAtPathIfExists(hit.file);
574
+ if (!sourceFile) return { implementations: [], total_count: 0 };
575
+ const exported = sourceFile.getExportedDeclarations().get(symbolName);
576
+ if (!exported || exported.length === 0)
577
+ return { implementations: [], total_count: 0 };
578
+ const targetNode = exported[0];
579
+ if (!import_ts_morph3.Node.isClassDeclaration(targetNode) && !import_ts_morph3.Node.isInterfaceDeclaration(targetNode)) {
580
+ return { implementations: [], total_count: 0 };
581
+ }
582
+ try {
583
+ const { searchCode: searchCode2 } = await Promise.resolve().then(() => (init_search_code(), search_code_exports));
584
+ const searchResults = await searchCode2(
585
+ symbolName,
586
+ path4,
587
+ "*.{ts,tsx,js,jsx}",
588
+ 1e3,
589
+ false
590
+ );
591
+ const filesToLoad = [...new Set(searchResults.map((r) => r.file))];
592
+ for (const file of filesToLoad) {
593
+ project.addSourceFileAtPathIfExists(file);
594
+ }
595
+ } catch (_e) {
596
+ }
222
597
  const results = [];
223
- for (const project of projects) {
224
- const sourceFiles = project.getSourceFiles();
225
- for (const sourceFile of sourceFiles) {
226
- const nodes = sourceFile.getDescendantsOfKind(import_ts_morph2.SyntaxKind.Identifier).filter((id) => id.getText() === symbolName);
227
- for (const node of nodes) {
228
- const implementations = node.getImplementations();
229
- for (const impl of implementations) {
230
- const sourceFile2 = impl.getSourceFile();
231
- const lineAndColumn = sourceFile2.getLineAndColumnAtPos(
232
- impl.getTextSpan().getStart()
233
- );
234
- results.push({
235
- file: sourceFile2.getFilePath(),
236
- line: lineAndColumn.line,
237
- column: lineAndColumn.column,
238
- text: impl.getNode().getParent()?.getText() || impl.getNode().getText()
239
- });
240
- }
241
- }
598
+ const implementations = targetNode.getImplementations?.();
599
+ if (implementations) {
600
+ for (const impl of implementations) {
601
+ const sf = impl.getSourceFile();
602
+ const lc = sf.getLineAndColumnAtPos(impl.getTextSpan().getStart());
603
+ results.push({
604
+ file: sf.getFilePath(),
605
+ line: lc.line,
606
+ column: lc.column,
607
+ text: impl.getNode().getParent()?.getText() || impl.getNode().getText()
608
+ });
242
609
  }
243
610
  }
244
- const uniqueResults = this.deduplicateLocations(results);
611
+ const unique = this.deduplicateLocations(results);
245
612
  return {
246
- implementations: uniqueResults.slice(offset, offset + limit),
247
- total_count: uniqueResults.length
613
+ implementations: unique.slice(offset, offset + limit),
614
+ total_count: unique.length
248
615
  };
249
616
  }
250
- /**
251
- * Get file structure overview
252
- */
253
617
  async getFileStructure(filePath) {
254
- const project = await projectManager.getProjectForFile(filePath);
255
- if (!project) return void 0;
256
- const sourceFile = project.getSourceFile(filePath);
618
+ const safePath = validateWorkspacePath(filePath);
619
+ const tsconfig = await projectManager.findNearestTsConfig(safePath);
620
+ if (!tsconfig) return void 0;
621
+ const project = projectManager.ensureProject(tsconfig);
622
+ const sourceFile = project.addSourceFileAtPathIfExists(safePath);
257
623
  if (!sourceFile) return void 0;
258
624
  const structure = {
259
- file: filePath,
625
+ file: safePath,
260
626
  imports: sourceFile.getImportDeclarations().map((imp) => ({
261
627
  module: imp.getModuleSpecifierValue(),
262
628
  names: imp.getNamedImports().map((ni) => ni.getName())
@@ -273,9 +639,6 @@ var TypeScriptAdapter = class {
273
639
  };
274
640
  return structure;
275
641
  }
276
- /**
277
- * Helper: Map ts-morph Node (Declaration) to DefinitionLocation
278
- */
279
642
  mapToDefinitionLocation(node) {
280
643
  const sourceFile = node.getSourceFile();
281
644
  const lineAndColumn = sourceFile.getLineAndColumnAtPos(node.getStart());
@@ -288,34 +651,25 @@ var TypeScriptAdapter = class {
288
651
  documentation: this.getJsDoc(node)
289
652
  };
290
653
  }
291
- /**
292
- * Helper: Map ts-morph Node to SymbolKind
293
- */
294
654
  mapNodeToSymbolKind(node) {
295
- if (import_ts_morph2.Node.isClassDeclaration(node)) return "class";
296
- if (import_ts_morph2.Node.isFunctionDeclaration(node)) return "function";
297
- if (import_ts_morph2.Node.isInterfaceDeclaration(node)) return "interface";
298
- if (import_ts_morph2.Node.isTypeAliasDeclaration(node)) return "type_alias";
299
- if (import_ts_morph2.Node.isEnumDeclaration(node)) return "enum";
300
- if (import_ts_morph2.Node.isVariableDeclaration(node)) return "variable";
301
- if (import_ts_morph2.Node.isMethodDeclaration(node)) return "method";
302
- if (import_ts_morph2.Node.isPropertyDeclaration(node)) return "property";
303
- if (import_ts_morph2.Node.isParameterDeclaration(node)) return "parameter";
655
+ if (import_ts_morph3.Node.isClassDeclaration(node)) return "class";
656
+ if (import_ts_morph3.Node.isFunctionDeclaration(node)) return "function";
657
+ if (import_ts_morph3.Node.isInterfaceDeclaration(node)) return "interface";
658
+ if (import_ts_morph3.Node.isTypeAliasDeclaration(node)) return "type_alias";
659
+ if (import_ts_morph3.Node.isEnumDeclaration(node)) return "enum";
660
+ if (import_ts_morph3.Node.isVariableDeclaration(node)) return "variable";
661
+ if (import_ts_morph3.Node.isMethodDeclaration(node)) return "method";
662
+ if (import_ts_morph3.Node.isPropertyDeclaration(node)) return "property";
663
+ if (import_ts_morph3.Node.isParameterDeclaration(node)) return "parameter";
304
664
  return "variable";
305
665
  }
306
- /**
307
- * Helper: Map Symbol to SymbolKind
308
- */
309
666
  mapSymbolKind(symbol) {
310
667
  const decls = symbol.getDeclarations();
311
668
  if (decls.length > 0) return this.mapNodeToSymbolKind(decls[0]);
312
669
  return "variable";
313
670
  }
314
- /**
315
- * Helper: Get JSDoc from Node
316
- */
317
671
  getJsDoc(node) {
318
- if (import_ts_morph2.Node.isJSDocable(node)) {
672
+ if (import_ts_morph3.Node.isJSDocable(node)) {
319
673
  const docs = node.getJsDocs();
320
674
  if (docs.length > 0) {
321
675
  return docs[0].getCommentText();
@@ -323,11 +677,8 @@ var TypeScriptAdapter = class {
323
677
  }
324
678
  return void 0;
325
679
  }
326
- /**
327
- * Helper: Get full JSDoc info (with tags)
328
- */
329
680
  getSymbolDocs(node) {
330
- if (import_ts_morph2.Node.isJSDocable(node)) {
681
+ if (import_ts_morph3.Node.isJSDocable(node)) {
331
682
  const docs = node.getJsDocs();
332
683
  if (docs.length > 0) {
333
684
  const doc = docs[0];
@@ -390,9 +741,6 @@ var TypeScriptAdapter = class {
390
741
  members: enm.getMembers().map((m) => m.getName())
391
742
  };
392
743
  }
393
- /**
394
- * Helper: Deduplicate locations
395
- */
396
744
  deduplicateLocations(locations) {
397
745
  const seen = /* @__PURE__ */ new Set();
398
746
  return locations.filter((loc) => {
@@ -406,100 +754,59 @@ var TypeScriptAdapter = class {
406
754
  var typescriptAdapter = new TypeScriptAdapter();
407
755
 
408
756
  // src/tools/resolve-definition.ts
409
- async function resolveDefinition(symbol, path2) {
410
- return await typescriptAdapter.resolveDefinition(symbol, path2);
757
+ async function resolveDefinition(symbol, path4) {
758
+ return await typescriptAdapter.resolveDefinition(symbol, path4);
411
759
  }
412
760
 
413
761
  // src/tools/find-references.ts
414
- async function findReferences(symbol, path2, limit = 50, offset = 0) {
415
- return await typescriptAdapter.findReferences(symbol, path2, limit, offset);
762
+ init_cjs_shims();
763
+ async function findReferences(symbol, path4, limit = 50, offset = 0) {
764
+ return await typescriptAdapter.findReferences(symbol, path4, limit, offset);
416
765
  }
417
766
 
418
767
  // src/tools/find-implementations.ts
419
- async function findImplementations(symbol, path2, limit = 50, offset = 0) {
768
+ init_cjs_shims();
769
+ async function findImplementations(symbol, path4, limit = 50, offset = 0) {
420
770
  return await typescriptAdapter.findImplementations(
421
771
  symbol,
422
- path2,
772
+ path4,
423
773
  limit,
424
774
  offset
425
775
  );
426
776
  }
427
777
 
428
778
  // src/tools/get-file-structure.ts
779
+ init_cjs_shims();
429
780
  async function getFileStructure(file) {
430
781
  return await typescriptAdapter.getFileStructure(file);
431
782
  }
432
783
 
433
- // src/tools/search-code.ts
434
- var import_child_process = require("child_process");
435
- var import_util = require("util");
436
- var import_ripgrep = require("@vscode/ripgrep");
437
- var execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
438
- async function searchCode(pattern, searchPath, filePattern, limit = 50) {
439
- const args = [
440
- "--json",
441
- "--max-count",
442
- limit.toString(),
443
- "--fixed-strings",
444
- // Default to fixed strings unless we want regex
445
- pattern,
446
- searchPath
447
- ];
448
- if (filePattern) {
449
- args.push("--glob", filePattern);
450
- }
451
- args.push("--glob", "!**/node_modules/**");
452
- args.push("--glob", "!**/dist/**");
453
- args.push("--glob", "!**/.git/**");
454
- try {
455
- const { stdout } = await execFileAsync(import_ripgrep.rgPath, args);
456
- const lines = stdout.split("\n").filter(Boolean);
457
- const results = [];
458
- for (const line of lines) {
459
- const data = JSON.parse(line);
460
- if (data.type === "match") {
461
- const file = data.data.path.text;
462
- const lineNumber = data.data.line_number;
463
- const submatches = data.data.submatches;
464
- for (const submatch of submatches) {
465
- results.push({
466
- file,
467
- line: lineNumber,
468
- column: submatch.start,
469
- text: data.data.lines.text.trim()
470
- });
471
- }
472
- }
473
- }
474
- return results.slice(0, limit);
475
- } catch (error) {
476
- if (error.code === 1) {
477
- return [];
478
- }
479
- throw error;
480
- }
481
- }
784
+ // src/index.ts
785
+ init_search_code();
482
786
 
483
787
  // src/tools/get-symbol-docs.ts
484
- var import_ts_morph3 = require("ts-morph");
788
+ init_cjs_shims();
789
+ var import_ts_morph4 = require("ts-morph");
790
+ init_security();
485
791
  async function getSymbolDocs(symbol, filePath) {
486
- const projects = await projectManager.getProjectsForPath(filePath);
487
- for (const project of projects) {
488
- const sourceFile = project.getSourceFile(filePath);
489
- if (sourceFile) {
490
- const node = sourceFile.getDescendantsOfKind(import_ts_morph3.SyntaxKind.Identifier).find((id) => id.getText() === symbol);
491
- if (node) {
492
- const decls = node.getSymbol()?.getDeclarations();
493
- if (decls && decls.length > 0) {
494
- const docs = typescriptAdapter.getSymbolDocs(decls[0]);
495
- if (docs) {
496
- return {
497
- symbol,
498
- file: sourceFile.getFilePath(),
499
- line: sourceFile.getLineAndColumnAtPos(decls[0].getStart()).line,
500
- ...docs
501
- };
502
- }
792
+ const safePath = validateWorkspacePath(filePath);
793
+ const tsconfig = await projectManager.findNearestTsConfig(safePath);
794
+ if (!tsconfig) return void 0;
795
+ const project = projectManager.ensureProject(tsconfig);
796
+ const sourceFile = project.addSourceFileAtPathIfExists(safePath);
797
+ if (sourceFile) {
798
+ const node = sourceFile.getDescendantsOfKind(import_ts_morph4.SyntaxKind.Identifier).find((id) => id.getText() === symbol);
799
+ if (node) {
800
+ const decls = node.getSymbol()?.getDeclarations();
801
+ if (decls && decls.length > 0) {
802
+ const docs = typescriptAdapter.getSymbolDocs(decls[0]);
803
+ if (docs) {
804
+ return {
805
+ symbol,
806
+ file: sourceFile.getFilePath(),
807
+ line: sourceFile.getLineAndColumnAtPos(decls[0].getStart()).line,
808
+ ...docs
809
+ };
503
810
  }
504
811
  }
505
812
  }
@@ -507,49 +814,10 @@ async function getSymbolDocs(symbol, filePath) {
507
814
  return void 0;
508
815
  }
509
816
 
510
- // src/index/symbol-index.ts
511
- var SymbolIndex = class {
512
- /**
513
- * Build/Warm the index for a given path
514
- */
515
- async buildIndex(rootDir) {
516
- const startTime = Date.now();
517
- const projects = await projectManager.getProjectsForPath(rootDir);
518
- let fileCount = 0;
519
- let functionCount = 0;
520
- let classCount = 0;
521
- let interfaceCount = 0;
522
- let typeCount = 0;
523
- for (const project of projects) {
524
- const sourceFiles = project.getSourceFiles();
525
- fileCount += sourceFiles.length;
526
- for (const sourceFile of sourceFiles) {
527
- functionCount += sourceFile.getFunctions().length;
528
- classCount += sourceFile.getClasses().length;
529
- interfaceCount += sourceFile.getInterfaces().length;
530
- typeCount += sourceFile.getTypeAliases().length;
531
- }
532
- }
533
- const duration = Date.now() - startTime;
534
- const memoryUsage = process.memoryUsage().heapUsed / 1024 / 1024;
535
- return {
536
- indexed: {
537
- files: fileCount,
538
- functions: functionCount,
539
- classes: classCount,
540
- interfaces: interfaceCount,
541
- types: typeCount
542
- },
543
- duration_ms: duration,
544
- memory_mb: Math.round(memoryUsage)
545
- };
546
- }
547
- };
548
- var symbolIndex = new SymbolIndex();
549
-
550
817
  // src/tools/build-symbol-index.ts
551
- async function buildSymbolIndex(path2) {
552
- return await symbolIndex.buildIndex(path2);
818
+ init_cjs_shims();
819
+ async function buildSymbolIndex(path4) {
820
+ return await symbolIndex.buildIndex(path4);
553
821
  }
554
822
 
555
823
  // src/index.ts
@@ -643,7 +911,8 @@ var ASTExplorerServer = class {
643
911
  pattern: { type: "string", description: "Search pattern" },
644
912
  path: { type: "string", description: "Directory to search" },
645
913
  filePattern: { type: "string", description: "Glob filter" },
646
- limit: { type: "number", default: 50 }
914
+ limit: { type: "number", default: 50 },
915
+ regex: { type: "boolean", default: true }
647
916
  },
648
917
  required: ["pattern", "path"]
649
918
  }
@@ -679,8 +948,8 @@ var ASTExplorerServer = class {
679
948
  try {
680
949
  switch (name) {
681
950
  case "resolve_definition": {
682
- const { symbol, path: path2 } = ResolveDefinitionSchema.parse(args);
683
- const results = await resolveDefinition(symbol, path2);
951
+ const { symbol, path: path4 } = ResolveDefinitionSchema.parse(args);
952
+ const results = await resolveDefinition(symbol, path4);
684
953
  return {
685
954
  content: [
686
955
  { type: "text", text: JSON.stringify(results, null, 2) }
@@ -688,8 +957,8 @@ var ASTExplorerServer = class {
688
957
  };
689
958
  }
690
959
  case "find_references": {
691
- const { symbol, path: path2, limit, offset } = FindReferencesSchema.parse(args);
692
- const results = await findReferences(symbol, path2, limit, offset);
960
+ const { symbol, path: path4, limit, offset } = FindReferencesSchema.parse(args);
961
+ const results = await findReferences(symbol, path4, limit, offset);
693
962
  return {
694
963
  content: [
695
964
  { type: "text", text: JSON.stringify(results, null, 2) }
@@ -697,10 +966,10 @@ var ASTExplorerServer = class {
697
966
  };
698
967
  }
699
968
  case "find_implementations": {
700
- const { symbol, path: path2, limit, offset } = FindImplementationsSchema.parse(args);
969
+ const { symbol, path: path4, limit, offset } = FindImplementationsSchema.parse(args);
701
970
  const results = await findImplementations(
702
971
  symbol,
703
- path2,
972
+ path4,
704
973
  limit,
705
974
  offset
706
975
  );
@@ -720,8 +989,14 @@ var ASTExplorerServer = class {
720
989
  };
721
990
  }
722
991
  case "search_code": {
723
- const { pattern, path: path2, filePattern, limit } = SearchCodeSchema.parse(args);
724
- const results = await searchCode(pattern, path2, filePattern, limit);
992
+ const { pattern, path: path4, filePattern, limit, regex } = SearchCodeSchema.parse(args);
993
+ const results = await searchCode(
994
+ pattern,
995
+ path4,
996
+ filePattern,
997
+ limit,
998
+ regex
999
+ );
725
1000
  return {
726
1001
  content: [
727
1002
  { type: "text", text: JSON.stringify(results, null, 2) }
@@ -729,15 +1004,15 @@ var ASTExplorerServer = class {
729
1004
  };
730
1005
  }
731
1006
  case "get_symbol_docs": {
732
- const { symbol, path: path2 } = GetSymbolDocsSchema.parse(args);
733
- const docs = await getSymbolDocs(symbol, path2);
1007
+ const { symbol, path: path4 } = GetSymbolDocsSchema.parse(args);
1008
+ const docs = await getSymbolDocs(symbol, path4);
734
1009
  return {
735
1010
  content: [{ type: "text", text: JSON.stringify(docs, null, 2) }]
736
1011
  };
737
1012
  }
738
1013
  case "build_symbol_index": {
739
- const { path: path2 } = BuildSymbolIndexSchema.parse(args);
740
- const stats = await buildSymbolIndex(path2);
1014
+ const { path: path4 } = BuildSymbolIndexSchema.parse(args);
1015
+ const stats = await buildSymbolIndex(path4);
741
1016
  return {
742
1017
  content: [{ type: "text", text: JSON.stringify(stats, null, 2) }]
743
1018
  };