@colbymchenry/codegraph 0.2.9 → 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.
- package/LICENSE +21 -21
- package/README.md +647 -641
- package/dist/bin/codegraph.d.ts +7 -2
- package/dist/bin/codegraph.d.ts.map +1 -1
- package/dist/bin/codegraph.js +360 -140
- package/dist/bin/codegraph.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -1
- package/dist/config.js.map +1 -1
- package/dist/context/index.d.ts +17 -4
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +182 -15
- package/dist/context/index.js.map +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +16 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +11 -12
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/queries.d.ts +19 -0
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +221 -107
- package/dist/db/queries.js.map +1 -1
- package/dist/db/schema.sql +154 -149
- package/dist/directory.d.ts +13 -1
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +62 -19
- package/dist/directory.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +7 -1
- package/dist/errors.js.map +1 -1
- package/dist/extraction/grammars.d.ts +9 -4
- package/dist/extraction/grammars.d.ts.map +1 -1
- package/dist/extraction/grammars.js +133 -65
- package/dist/extraction/grammars.js.map +1 -1
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +119 -7
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/tree-sitter.d.ts +62 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +937 -34
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/graph/traversal.d.ts.map +1 -1
- package/dist/graph/traversal.js +6 -2
- package/dist/graph/traversal.js.map +1 -1
- package/dist/index.d.ts +6 -38
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +85 -74
- package/dist/index.js.map +1 -1
- package/dist/installer/banner.js +7 -7
- package/dist/installer/claude-md-template.js +32 -32
- package/dist/installer/config-writer.d.ts +9 -0
- package/dist/installer/config-writer.d.ts.map +1 -1
- package/dist/installer/config-writer.js +78 -0
- package/dist/installer/config-writer.js.map +1 -1
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +28 -16
- package/dist/installer/index.js.map +1 -1
- package/dist/mcp/index.d.ts +14 -3
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +109 -29
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/tools.d.ts +62 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +414 -43
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/transport.d.ts.map +1 -1
- package/dist/mcp/transport.js +2 -0
- package/dist/mcp/transport.js.map +1 -1
- package/dist/resolution/frameworks/index.d.ts +1 -0
- package/dist/resolution/frameworks/index.d.ts.map +1 -1
- package/dist/resolution/frameworks/index.js +5 -1
- package/dist/resolution/frameworks/index.js.map +1 -1
- package/dist/resolution/frameworks/svelte.d.ts +9 -0
- package/dist/resolution/frameworks/svelte.d.ts.map +1 -0
- package/dist/resolution/frameworks/svelte.js +268 -0
- package/dist/resolution/frameworks/svelte.js.map +1 -0
- package/dist/resolution/index.d.ts +11 -2
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +94 -13
- package/dist/resolution/index.js.map +1 -1
- package/dist/sentry.d.ts +22 -0
- package/dist/sentry.d.ts.map +1 -0
- package/dist/sentry.js +159 -0
- package/dist/sentry.js.map +1 -0
- package/dist/sync/index.d.ts +4 -2
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +3 -5
- package/dist/sync/index.js.map +1 -1
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +45 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +114 -3
- package/dist/utils.js.map +1 -1
- package/dist/vectors/embedder.d.ts +1 -1
- package/dist/vectors/embedder.d.ts.map +1 -1
- package/dist/vectors/embedder.js +5 -2
- package/dist/vectors/embedder.js.map +1 -1
- package/dist/vectors/search.d.ts.map +1 -1
- package/dist/vectors/search.js +33 -32
- package/dist/vectors/search.js.map +1 -1
- package/package.json +72 -65
- package/scripts/patch-tree-sitter-dart.js +112 -0
- package/scripts/postinstall.js +71 -0
- package/dist/sync/git-hooks.d.ts +0 -66
- package/dist/sync/git-hooks.d.ts.map +0 -1
- package/dist/sync/git-hooks.js +0 -281
- 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 =
|
|
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
|
|
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
|
-
|
|
259
|
-
|
|
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
|
|
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
|
-
|
|
280
|
-
|
|
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
|
|
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
|
-
|
|
301
|
-
|
|
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
|
|
306
|
-
const
|
|
307
|
-
|
|
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
|
-
|
|
318
|
-
|
|
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
|
|
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
|
|
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
|
// =========================================================================
|