@colbymchenry/codegraph 0.3.1 → 0.4.8

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.

Potentially problematic release.


This version of @colbymchenry/codegraph might be problematic. Click here for more details.

Files changed (113) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +647 -641
  3. package/dist/bin/codegraph.d.ts +7 -2
  4. package/dist/bin/codegraph.d.ts.map +1 -1
  5. package/dist/bin/codegraph.js +360 -140
  6. package/dist/bin/codegraph.js.map +1 -1
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +8 -1
  9. package/dist/config.js.map +1 -1
  10. package/dist/context/index.d.ts +17 -4
  11. package/dist/context/index.d.ts.map +1 -1
  12. package/dist/context/index.js +182 -15
  13. package/dist/context/index.js.map +1 -1
  14. package/dist/db/index.d.ts.map +1 -1
  15. package/dist/db/index.js +16 -0
  16. package/dist/db/index.js.map +1 -1
  17. package/dist/db/migrations.d.ts +1 -1
  18. package/dist/db/migrations.d.ts.map +1 -1
  19. package/dist/db/migrations.js +11 -12
  20. package/dist/db/migrations.js.map +1 -1
  21. package/dist/db/queries.d.ts +19 -0
  22. package/dist/db/queries.d.ts.map +1 -1
  23. package/dist/db/queries.js +221 -107
  24. package/dist/db/queries.js.map +1 -1
  25. package/dist/db/schema.sql +154 -149
  26. package/dist/directory.d.ts +13 -1
  27. package/dist/directory.d.ts.map +1 -1
  28. package/dist/directory.js +62 -19
  29. package/dist/directory.js.map +1 -1
  30. package/dist/errors.d.ts +1 -1
  31. package/dist/errors.d.ts.map +1 -1
  32. package/dist/errors.js +7 -1
  33. package/dist/errors.js.map +1 -1
  34. package/dist/extraction/grammars.d.ts +9 -4
  35. package/dist/extraction/grammars.d.ts.map +1 -1
  36. package/dist/extraction/grammars.js +133 -65
  37. package/dist/extraction/grammars.js.map +1 -1
  38. package/dist/extraction/index.d.ts.map +1 -1
  39. package/dist/extraction/index.js +119 -7
  40. package/dist/extraction/index.js.map +1 -1
  41. package/dist/extraction/tree-sitter.d.ts +62 -0
  42. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  43. package/dist/extraction/tree-sitter.js +937 -34
  44. package/dist/extraction/tree-sitter.js.map +1 -1
  45. package/dist/graph/traversal.d.ts.map +1 -1
  46. package/dist/graph/traversal.js +6 -2
  47. package/dist/graph/traversal.js.map +1 -1
  48. package/dist/index.d.ts +6 -38
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +85 -73
  51. package/dist/index.js.map +1 -1
  52. package/dist/installer/banner.js +7 -7
  53. package/dist/installer/claude-md-template.js +32 -32
  54. package/dist/installer/config-writer.d.ts +9 -0
  55. package/dist/installer/config-writer.d.ts.map +1 -1
  56. package/dist/installer/config-writer.js +78 -0
  57. package/dist/installer/config-writer.js.map +1 -1
  58. package/dist/installer/index.d.ts.map +1 -1
  59. package/dist/installer/index.js +11 -9
  60. package/dist/installer/index.js.map +1 -1
  61. package/dist/mcp/index.d.ts +14 -3
  62. package/dist/mcp/index.d.ts.map +1 -1
  63. package/dist/mcp/index.js +109 -29
  64. package/dist/mcp/index.js.map +1 -1
  65. package/dist/mcp/tools.d.ts +62 -1
  66. package/dist/mcp/tools.d.ts.map +1 -1
  67. package/dist/mcp/tools.js +414 -43
  68. package/dist/mcp/tools.js.map +1 -1
  69. package/dist/mcp/transport.d.ts.map +1 -1
  70. package/dist/mcp/transport.js +2 -0
  71. package/dist/mcp/transport.js.map +1 -1
  72. package/dist/resolution/frameworks/index.d.ts +1 -0
  73. package/dist/resolution/frameworks/index.d.ts.map +1 -1
  74. package/dist/resolution/frameworks/index.js +5 -1
  75. package/dist/resolution/frameworks/index.js.map +1 -1
  76. package/dist/resolution/frameworks/svelte.d.ts +9 -0
  77. package/dist/resolution/frameworks/svelte.d.ts.map +1 -0
  78. package/dist/resolution/frameworks/svelte.js +268 -0
  79. package/dist/resolution/frameworks/svelte.js.map +1 -0
  80. package/dist/resolution/index.d.ts +11 -2
  81. package/dist/resolution/index.d.ts.map +1 -1
  82. package/dist/resolution/index.js +94 -13
  83. package/dist/resolution/index.js.map +1 -1
  84. package/dist/sentry.d.ts +22 -0
  85. package/dist/sentry.d.ts.map +1 -0
  86. package/dist/sentry.js +159 -0
  87. package/dist/sentry.js.map +1 -0
  88. package/dist/sync/index.d.ts +4 -2
  89. package/dist/sync/index.d.ts.map +1 -1
  90. package/dist/sync/index.js +3 -5
  91. package/dist/sync/index.js.map +1 -1
  92. package/dist/types.d.ts +5 -1
  93. package/dist/types.d.ts.map +1 -1
  94. package/dist/types.js +11 -0
  95. package/dist/types.js.map +1 -1
  96. package/dist/utils.d.ts +45 -2
  97. package/dist/utils.d.ts.map +1 -1
  98. package/dist/utils.js +114 -3
  99. package/dist/utils.js.map +1 -1
  100. package/dist/vectors/embedder.d.ts +1 -1
  101. package/dist/vectors/embedder.d.ts.map +1 -1
  102. package/dist/vectors/embedder.js +2 -2
  103. package/dist/vectors/embedder.js.map +1 -1
  104. package/dist/vectors/search.d.ts.map +1 -1
  105. package/dist/vectors/search.js +33 -32
  106. package/dist/vectors/search.js.map +1 -1
  107. package/package.json +72 -67
  108. package/scripts/patch-tree-sitter-dart.js +112 -0
  109. package/scripts/postinstall.js +71 -68
  110. package/dist/sync/git-hooks.d.ts +0 -66
  111. package/dist/sync/git-hooks.d.ts.map +0 -1
  112. package/dist/sync/git-hooks.js +0 -281
  113. package/dist/sync/git-hooks.js.map +0 -1
package/dist/mcp/tools.js CHANGED
@@ -4,13 +4,75 @@
4
4
  *
5
5
  * Defines the tools exposed by the CodeGraph MCP server.
6
6
  */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
7
40
  Object.defineProperty(exports, "__esModule", { value: true });
8
41
  exports.ToolHandler = exports.tools = void 0;
42
+ const index_1 = __importStar(require("../index"));
43
+ const crypto_1 = require("crypto");
44
+ const fs_1 = require("fs");
45
+ const utils_1 = require("../utils");
46
+ const os_1 = require("os");
47
+ const path_1 = require("path");
48
+ /**
49
+ * Mark a Claude session as having consulted MCP tools.
50
+ * This enables Grep/Glob/Bash commands that would otherwise be blocked.
51
+ */
52
+ function markSessionConsulted(sessionId) {
53
+ try {
54
+ const hash = (0, crypto_1.createHash)('md5').update(sessionId).digest('hex').slice(0, 16);
55
+ const markerPath = (0, path_1.join)((0, os_1.tmpdir)(), `codegraph-consulted-${hash}`);
56
+ (0, fs_1.writeFileSync)(markerPath, new Date().toISOString(), 'utf8');
57
+ }
58
+ catch {
59
+ // Silently fail - don't break MCP on marker write failure
60
+ }
61
+ }
62
+ /**
63
+ * Common projectPath property for cross-project queries
64
+ */
65
+ const projectPathProperty = {
66
+ type: 'string',
67
+ description: 'Path to a different project with .codegraph/ initialized. If omitted, uses current project. Use this to query other codebases.',
68
+ };
9
69
  /**
10
70
  * All CodeGraph MCP tools
11
71
  *
12
72
  * Designed for minimal context usage - use codegraph_context as the primary tool,
13
73
  * and only use other tools for targeted follow-up queries.
74
+ *
75
+ * All tools support cross-project queries via the optional `projectPath` parameter.
14
76
  */
15
77
  exports.tools = [
16
78
  {
@@ -33,6 +95,7 @@ exports.tools = [
33
95
  description: 'Maximum results (default: 10)',
34
96
  default: 10,
35
97
  },
98
+ projectPath: projectPathProperty,
36
99
  },
37
100
  required: ['query'],
38
101
  },
@@ -57,6 +120,7 @@ exports.tools = [
57
120
  description: 'Include code snippets for key symbols (default: true)',
58
121
  default: true,
59
122
  },
123
+ projectPath: projectPathProperty,
60
124
  },
61
125
  required: ['task'],
62
126
  },
@@ -76,6 +140,7 @@ exports.tools = [
76
140
  description: 'Maximum number of callers to return (default: 20)',
77
141
  default: 20,
78
142
  },
143
+ projectPath: projectPathProperty,
79
144
  },
80
145
  required: ['symbol'],
81
146
  },
@@ -95,6 +160,7 @@ exports.tools = [
95
160
  description: 'Maximum number of callees to return (default: 20)',
96
161
  default: 20,
97
162
  },
163
+ projectPath: projectPathProperty,
98
164
  },
99
165
  required: ['symbol'],
100
166
  },
@@ -114,6 +180,7 @@ exports.tools = [
114
180
  description: 'How many levels of dependencies to traverse (default: 2)',
115
181
  default: 2,
116
182
  },
183
+ projectPath: projectPathProperty,
117
184
  },
118
185
  required: ['symbol'],
119
186
  },
@@ -133,6 +200,7 @@ exports.tools = [
133
200
  description: 'Include full source code (default: false to minimize context)',
134
201
  default: false,
135
202
  },
203
+ projectPath: projectPathProperty,
136
204
  },
137
205
  required: ['symbol'],
138
206
  },
@@ -142,18 +210,119 @@ exports.tools = [
142
210
  description: 'Get the status of the CodeGraph index, including statistics about indexed files, nodes, and edges.',
143
211
  inputSchema: {
144
212
  type: 'object',
145
- properties: {},
213
+ properties: {
214
+ projectPath: projectPathProperty,
215
+ },
216
+ },
217
+ },
218
+ {
219
+ name: 'codegraph_files',
220
+ description: 'REQUIRED for file/folder exploration. Get the project file structure from the CodeGraph index. Returns a tree view of all indexed files with metadata (language, symbol count). Much faster than Glob/filesystem scanning. Use this FIRST when exploring project structure, finding files, or understanding codebase organization.',
221
+ inputSchema: {
222
+ type: 'object',
223
+ properties: {
224
+ path: {
225
+ type: 'string',
226
+ description: 'Filter to files under this directory path (e.g., "src/components"). Returns all files if not specified.',
227
+ },
228
+ pattern: {
229
+ type: 'string',
230
+ description: 'Filter files matching this glob pattern (e.g., "*.tsx", "**/*.test.ts")',
231
+ },
232
+ format: {
233
+ type: 'string',
234
+ description: 'Output format: "tree" (hierarchical, default), "flat" (simple list), "grouped" (by language)',
235
+ enum: ['tree', 'flat', 'grouped'],
236
+ default: 'tree',
237
+ },
238
+ includeMetadata: {
239
+ type: 'boolean',
240
+ description: 'Include file metadata like language and symbol count (default: true)',
241
+ default: true,
242
+ },
243
+ maxDepth: {
244
+ type: 'number',
245
+ description: 'Maximum directory depth to show (default: unlimited)',
246
+ },
247
+ projectPath: projectPathProperty,
248
+ },
146
249
  },
147
250
  },
148
251
  ];
149
252
  /**
150
253
  * Tool handler that executes tools against a CodeGraph instance
254
+ *
255
+ * Supports cross-project queries via the projectPath parameter.
256
+ * Other projects are opened on-demand and cached for performance.
151
257
  */
152
258
  class ToolHandler {
153
259
  cg;
260
+ // Cache of opened CodeGraph instances for cross-project queries
261
+ projectCache = new Map();
154
262
  constructor(cg) {
155
263
  this.cg = cg;
156
264
  }
265
+ /**
266
+ * Update the default CodeGraph instance (e.g. after lazy initialization)
267
+ */
268
+ setDefaultCodeGraph(cg) {
269
+ this.cg = cg;
270
+ }
271
+ /**
272
+ * Whether a default CodeGraph instance is available
273
+ */
274
+ hasDefaultCodeGraph() {
275
+ return this.cg !== null;
276
+ }
277
+ /**
278
+ * Get CodeGraph instance for a project
279
+ *
280
+ * If projectPath is provided, opens that project's CodeGraph (cached).
281
+ * Otherwise returns the default CodeGraph instance.
282
+ *
283
+ * Walks up parent directories to find the nearest .codegraph/ folder,
284
+ * similar to how git finds .git/ directories.
285
+ */
286
+ getCodeGraph(projectPath) {
287
+ if (!projectPath) {
288
+ if (!this.cg) {
289
+ throw new Error('CodeGraph not initialized for this project. Run \'codegraph init\' first.');
290
+ }
291
+ return this.cg;
292
+ }
293
+ // Check cache first (using original path as key)
294
+ if (this.projectCache.has(projectPath)) {
295
+ return this.projectCache.get(projectPath);
296
+ }
297
+ // Walk up parent directories to find nearest .codegraph/
298
+ const resolvedRoot = (0, index_1.findNearestCodeGraphRoot)(projectPath);
299
+ if (!resolvedRoot) {
300
+ throw new Error(`CodeGraph not initialized in ${projectPath}. Run 'codegraph init' in that project first.`);
301
+ }
302
+ // Check if we already have this resolved root cached (different path, same project)
303
+ if (this.projectCache.has(resolvedRoot)) {
304
+ const cg = this.projectCache.get(resolvedRoot);
305
+ // Cache under original path too for faster future lookups
306
+ this.projectCache.set(projectPath, cg);
307
+ return cg;
308
+ }
309
+ // Open and cache under both paths
310
+ const cg = index_1.default.openSync(resolvedRoot);
311
+ this.projectCache.set(resolvedRoot, cg);
312
+ if (projectPath !== resolvedRoot) {
313
+ this.projectCache.set(projectPath, cg);
314
+ }
315
+ return cg;
316
+ }
317
+ /**
318
+ * Close all cached project connections
319
+ */
320
+ closeAll() {
321
+ for (const cg of this.projectCache.values()) {
322
+ cg.close();
323
+ }
324
+ this.projectCache.clear();
325
+ }
157
326
  /**
158
327
  * Execute a tool by name
159
328
  */
@@ -173,12 +342,19 @@ class ToolHandler {
173
342
  case 'codegraph_node':
174
343
  return await this.handleNode(args);
175
344
  case 'codegraph_status':
176
- return await this.handleStatus();
345
+ return await this.handleStatus(args);
346
+ case 'codegraph_files':
347
+ return await this.handleFiles(args);
177
348
  default:
178
349
  return this.errorResult(`Unknown tool: ${toolName}`);
179
350
  }
180
351
  }
181
352
  catch (err) {
353
+ try {
354
+ const { captureException } = require('../sentry');
355
+ captureException(err, { tool: toolName });
356
+ }
357
+ catch { }
182
358
  return this.errorResult(`Tool execution failed: ${err instanceof Error ? err.message : String(err)}`);
183
359
  }
184
360
  }
@@ -186,10 +362,11 @@ class ToolHandler {
186
362
  * Handle codegraph_search
187
363
  */
188
364
  async handleSearch(args) {
365
+ const cg = this.getCodeGraph(args.projectPath);
189
366
  const query = args.query;
190
367
  const kind = args.kind;
191
- const limit = args.limit || 10;
192
- const results = this.cg.searchNodes(query, {
368
+ const limit = (0, utils_1.clamp)(args.limit || 10, 1, 100);
369
+ const results = cg.searchNodes(query, {
193
370
  limit,
194
371
  kinds: kind ? [kind] : undefined,
195
372
  });
@@ -197,16 +374,22 @@ class ToolHandler {
197
374
  return this.textResult(`No results found for "${query}"`);
198
375
  }
199
376
  const formatted = this.formatSearchResults(results);
200
- return this.textResult(formatted);
377
+ return this.textResult(this.truncateOutput(formatted));
201
378
  }
202
379
  /**
203
380
  * Handle codegraph_context
204
381
  */
205
382
  async handleContext(args) {
383
+ // Mark session as consulted (enables Grep/Glob/Bash)
384
+ const sessionId = process.env.CLAUDE_SESSION_ID;
385
+ if (sessionId) {
386
+ markSessionConsulted(sessionId);
387
+ }
388
+ const cg = this.getCodeGraph(args.projectPath);
206
389
  const task = args.task;
207
390
  const maxNodes = args.maxNodes || 20;
208
391
  const includeCode = args.includeCode !== false;
209
- const context = await this.cg.buildContext(task, {
392
+ const context = await cg.buildContext(task, {
210
393
  maxNodes,
211
394
  includeCode,
212
395
  format: 'markdown',
@@ -253,85 +436,80 @@ class ToolHandler {
253
436
  * Handle codegraph_callers
254
437
  */
255
438
  async handleCallers(args) {
439
+ const cg = this.getCodeGraph(args.projectPath);
256
440
  const symbol = args.symbol;
257
- const limit = args.limit || 20;
258
- // First find the node by name
259
- const results = this.cg.searchNodes(symbol, { limit: 1 });
260
- if (results.length === 0 || !results[0]) {
441
+ const limit = (0, utils_1.clamp)(args.limit || 20, 1, 100);
442
+ const match = this.findSymbol(cg, symbol);
443
+ if (!match) {
261
444
  return this.textResult(`Symbol "${symbol}" not found in the codebase`);
262
445
  }
263
- const node = results[0].node;
264
- const callers = this.cg.getCallers(node.id);
446
+ const callers = cg.getCallers(match.node.id);
265
447
  if (callers.length === 0) {
266
- return this.textResult(`No callers found for "${symbol}"`);
448
+ return this.textResult(`No callers found for "${symbol}"${match.note}`);
267
449
  }
268
- // Extract just the nodes from the { node, edge } tuples
269
450
  const callerNodes = callers.slice(0, limit).map(c => c.node);
270
- const formatted = this.formatNodeList(callerNodes, `Callers of ${symbol}`);
271
- return this.textResult(formatted);
451
+ const formatted = this.formatNodeList(callerNodes, `Callers of ${symbol}`) + match.note;
452
+ return this.textResult(this.truncateOutput(formatted));
272
453
  }
273
454
  /**
274
455
  * Handle codegraph_callees
275
456
  */
276
457
  async handleCallees(args) {
458
+ const cg = this.getCodeGraph(args.projectPath);
277
459
  const symbol = args.symbol;
278
- const limit = args.limit || 20;
279
- // First find the node by name
280
- const results = this.cg.searchNodes(symbol, { limit: 1 });
281
- if (results.length === 0 || !results[0]) {
460
+ const limit = (0, utils_1.clamp)(args.limit || 20, 1, 100);
461
+ const match = this.findSymbol(cg, symbol);
462
+ if (!match) {
282
463
  return this.textResult(`Symbol "${symbol}" not found in the codebase`);
283
464
  }
284
- const node = results[0].node;
285
- const callees = this.cg.getCallees(node.id);
465
+ const callees = cg.getCallees(match.node.id);
286
466
  if (callees.length === 0) {
287
- return this.textResult(`No callees found for "${symbol}"`);
467
+ return this.textResult(`No callees found for "${symbol}"${match.note}`);
288
468
  }
289
- // Extract just the nodes from the { node, edge } tuples
290
469
  const calleeNodes = callees.slice(0, limit).map(c => c.node);
291
- const formatted = this.formatNodeList(calleeNodes, `Callees of ${symbol}`);
292
- return this.textResult(formatted);
470
+ const formatted = this.formatNodeList(calleeNodes, `Callees of ${symbol}`) + match.note;
471
+ return this.textResult(this.truncateOutput(formatted));
293
472
  }
294
473
  /**
295
474
  * Handle codegraph_impact
296
475
  */
297
476
  async handleImpact(args) {
477
+ const cg = this.getCodeGraph(args.projectPath);
298
478
  const symbol = args.symbol;
299
- const depth = args.depth || 2;
300
- // First find the node by name
301
- const results = this.cg.searchNodes(symbol, { limit: 1 });
302
- if (results.length === 0 || !results[0]) {
479
+ const depth = (0, utils_1.clamp)(args.depth || 2, 1, 10);
480
+ const match = this.findSymbol(cg, symbol);
481
+ if (!match) {
303
482
  return this.textResult(`Symbol "${symbol}" not found in the codebase`);
304
483
  }
305
- const node = results[0].node;
306
- const impact = this.cg.getImpactRadius(node.id, depth);
307
- const formatted = this.formatImpact(symbol, impact);
308
- return this.textResult(formatted);
484
+ const impact = cg.getImpactRadius(match.node.id, depth);
485
+ const formatted = this.formatImpact(symbol, impact) + match.note;
486
+ return this.textResult(this.truncateOutput(formatted));
309
487
  }
310
488
  /**
311
489
  * Handle codegraph_node
312
490
  */
313
491
  async handleNode(args) {
492
+ const cg = this.getCodeGraph(args.projectPath);
314
493
  const symbol = args.symbol;
315
494
  // Default to false to minimize context usage
316
495
  const includeCode = args.includeCode === true;
317
- // Find the node by name
318
- const results = this.cg.searchNodes(symbol, { limit: 1 });
319
- if (results.length === 0 || !results[0]) {
496
+ const match = this.findSymbol(cg, symbol);
497
+ if (!match) {
320
498
  return this.textResult(`Symbol "${symbol}" not found in the codebase`);
321
499
  }
322
- const node = results[0].node;
323
500
  let code = null;
324
501
  if (includeCode) {
325
- code = await this.cg.getCode(node.id);
502
+ code = await cg.getCode(match.node.id);
326
503
  }
327
- const formatted = this.formatNodeDetails(node, code);
328
- return this.textResult(formatted);
504
+ const formatted = this.formatNodeDetails(match.node, code) + match.note;
505
+ return this.textResult(this.truncateOutput(formatted));
329
506
  }
330
507
  /**
331
508
  * Handle codegraph_status
332
509
  */
333
- async handleStatus() {
334
- const stats = this.cg.getStats();
510
+ async handleStatus(args) {
511
+ const cg = this.getCodeGraph(args.projectPath);
512
+ const stats = cg.getStats();
335
513
  const lines = [
336
514
  '## CodeGraph Status',
337
515
  '',
@@ -355,6 +533,199 @@ class ToolHandler {
355
533
  }
356
534
  return this.textResult(lines.join('\n'));
357
535
  }
536
+ /**
537
+ * Handle codegraph_files - get project file structure from the index
538
+ */
539
+ async handleFiles(args) {
540
+ const cg = this.getCodeGraph(args.projectPath);
541
+ const pathFilter = args.path;
542
+ const pattern = args.pattern;
543
+ const format = args.format || 'tree';
544
+ const includeMetadata = args.includeMetadata !== false;
545
+ const maxDepth = args.maxDepth != null ? (0, utils_1.clamp)(args.maxDepth, 1, 20) : undefined;
546
+ // Get all files from the index
547
+ const allFiles = cg.getFiles();
548
+ if (allFiles.length === 0) {
549
+ return this.textResult('No files indexed. Run `codegraph index` first.');
550
+ }
551
+ // Filter by path prefix
552
+ let files = pathFilter
553
+ ? allFiles.filter(f => f.path.startsWith(pathFilter) || f.path.startsWith('./' + pathFilter))
554
+ : allFiles;
555
+ // Filter by glob pattern
556
+ if (pattern) {
557
+ const regex = this.globToRegex(pattern);
558
+ files = files.filter(f => regex.test(f.path));
559
+ }
560
+ if (files.length === 0) {
561
+ return this.textResult(`No files found matching the criteria.`);
562
+ }
563
+ // Format output
564
+ let output;
565
+ switch (format) {
566
+ case 'flat':
567
+ output = this.formatFilesFlat(files, includeMetadata);
568
+ break;
569
+ case 'grouped':
570
+ output = this.formatFilesGrouped(files, includeMetadata);
571
+ break;
572
+ case 'tree':
573
+ default:
574
+ output = this.formatFilesTree(files, includeMetadata, maxDepth);
575
+ break;
576
+ }
577
+ return this.textResult(this.truncateOutput(output));
578
+ }
579
+ /**
580
+ * Convert glob pattern to regex
581
+ */
582
+ globToRegex(pattern) {
583
+ const escaped = pattern
584
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special regex chars except * and ?
585
+ .replace(/\*\*/g, '{{GLOBSTAR}}') // Temp placeholder for **
586
+ .replace(/\*/g, '[^/]*') // * matches anything except /
587
+ .replace(/\?/g, '[^/]') // ? matches single char except /
588
+ .replace(/\{\{GLOBSTAR\}\}/g, '.*'); // ** matches anything including /
589
+ return new RegExp(escaped);
590
+ }
591
+ /**
592
+ * Format files as a flat list
593
+ */
594
+ formatFilesFlat(files, includeMetadata) {
595
+ const lines = [`## Files (${files.length})`, ''];
596
+ for (const file of files.sort((a, b) => a.path.localeCompare(b.path))) {
597
+ if (includeMetadata) {
598
+ lines.push(`- ${file.path} (${file.language}, ${file.nodeCount} symbols)`);
599
+ }
600
+ else {
601
+ lines.push(`- ${file.path}`);
602
+ }
603
+ }
604
+ return lines.join('\n');
605
+ }
606
+ /**
607
+ * Format files grouped by language
608
+ */
609
+ formatFilesGrouped(files, includeMetadata) {
610
+ const byLang = new Map();
611
+ for (const file of files) {
612
+ const existing = byLang.get(file.language) || [];
613
+ existing.push(file);
614
+ byLang.set(file.language, existing);
615
+ }
616
+ const lines = [`## Files by Language (${files.length} total)`, ''];
617
+ // Sort languages by file count (descending)
618
+ const sortedLangs = [...byLang.entries()].sort((a, b) => b[1].length - a[1].length);
619
+ for (const [lang, langFiles] of sortedLangs) {
620
+ lines.push(`### ${lang} (${langFiles.length})`);
621
+ for (const file of langFiles.sort((a, b) => a.path.localeCompare(b.path))) {
622
+ if (includeMetadata) {
623
+ lines.push(`- ${file.path} (${file.nodeCount} symbols)`);
624
+ }
625
+ else {
626
+ lines.push(`- ${file.path}`);
627
+ }
628
+ }
629
+ lines.push('');
630
+ }
631
+ return lines.join('\n');
632
+ }
633
+ /**
634
+ * Format files as a tree structure
635
+ */
636
+ formatFilesTree(files, includeMetadata, maxDepth) {
637
+ const root = { name: '', children: new Map() };
638
+ for (const file of files) {
639
+ const parts = file.path.split('/');
640
+ let current = root;
641
+ for (let i = 0; i < parts.length; i++) {
642
+ const part = parts[i];
643
+ if (!part)
644
+ continue;
645
+ if (!current.children.has(part)) {
646
+ current.children.set(part, { name: part, children: new Map() });
647
+ }
648
+ current = current.children.get(part);
649
+ // If this is the last part, it's a file
650
+ if (i === parts.length - 1) {
651
+ current.file = { language: file.language, nodeCount: file.nodeCount };
652
+ }
653
+ }
654
+ }
655
+ // Render tree
656
+ const lines = [`## Project Structure (${files.length} files)`, ''];
657
+ const renderNode = (node, prefix, isLast, depth) => {
658
+ if (maxDepth !== undefined && depth > maxDepth)
659
+ return;
660
+ const connector = isLast ? '└── ' : '├── ';
661
+ const childPrefix = isLast ? ' ' : '│ ';
662
+ if (node.name) {
663
+ let line = prefix + connector + node.name;
664
+ if (node.file && includeMetadata) {
665
+ line += ` (${node.file.language}, ${node.file.nodeCount} symbols)`;
666
+ }
667
+ lines.push(line);
668
+ }
669
+ const children = [...node.children.values()];
670
+ // Sort: directories first, then files, both alphabetically
671
+ children.sort((a, b) => {
672
+ const aIsDir = a.children.size > 0 && !a.file;
673
+ const bIsDir = b.children.size > 0 && !b.file;
674
+ if (aIsDir !== bIsDir)
675
+ return aIsDir ? -1 : 1;
676
+ return a.name.localeCompare(b.name);
677
+ });
678
+ for (let i = 0; i < children.length; i++) {
679
+ const child = children[i];
680
+ const nextPrefix = node.name ? prefix + childPrefix : prefix;
681
+ renderNode(child, nextPrefix, i === children.length - 1, depth + 1);
682
+ }
683
+ };
684
+ renderNode(root, '', true, 0);
685
+ return lines.join('\n');
686
+ }
687
+ // =========================================================================
688
+ // Symbol resolution helpers
689
+ // =========================================================================
690
+ /**
691
+ * Find a symbol by name, handling disambiguation when multiple matches exist.
692
+ * Returns the best match and a note about alternatives if any.
693
+ */
694
+ findSymbol(cg, symbol) {
695
+ const results = cg.searchNodes(symbol, { limit: 10 });
696
+ if (results.length === 0 || !results[0]) {
697
+ return null;
698
+ }
699
+ // If only one result, or first is an exact name match, use it directly
700
+ const exactMatches = results.filter(r => r.node.name === symbol);
701
+ if (exactMatches.length === 1) {
702
+ return { node: exactMatches[0].node, note: '' };
703
+ }
704
+ if (exactMatches.length > 1) {
705
+ // Multiple exact matches - pick first, note the others
706
+ const picked = exactMatches[0].node;
707
+ const others = exactMatches.slice(1).map(r => `${r.node.name} (${r.node.kind}) at ${r.node.filePath}:${r.node.startLine}`);
708
+ const note = `\n\n> **Note:** ${exactMatches.length} symbols named "${symbol}". Showing results for \`${picked.filePath}:${picked.startLine}\`. Others: ${others.join(', ')}`;
709
+ return { node: picked, note };
710
+ }
711
+ // No exact match, use best fuzzy match
712
+ return { node: results[0].node, note: '' };
713
+ }
714
+ /**
715
+ * Maximum output length to prevent context bloat (characters)
716
+ */
717
+ MAX_OUTPUT_LENGTH = 15000;
718
+ /**
719
+ * Truncate output if it exceeds the maximum length
720
+ */
721
+ truncateOutput(text) {
722
+ if (text.length <= this.MAX_OUTPUT_LENGTH)
723
+ return text;
724
+ const truncated = text.slice(0, this.MAX_OUTPUT_LENGTH);
725
+ const lastNewline = truncated.lastIndexOf('\n');
726
+ const cutPoint = lastNewline > this.MAX_OUTPUT_LENGTH * 0.8 ? lastNewline : this.MAX_OUTPUT_LENGTH;
727
+ return truncated.slice(0, cutPoint) + '\n\n... (output truncated)';
728
+ }
358
729
  // =========================================================================
359
730
  // Formatting helpers (compact by default to reduce context usage)
360
731
  // =========================================================================