@colbymchenry/codegraph 0.2.9 → 0.5.0
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/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 +43 -10
- 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 +21 -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 +19 -12
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/queries.d.ts +32 -1
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +271 -118
- package/dist/db/queries.js.map +1 -1
- package/dist/db/schema.sql +163 -149
- package/dist/directory.d.ts +13 -1
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +85 -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 +6 -0
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +209 -44
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/tree-sitter.d.ts +67 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +980 -38
- 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 +91 -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 +126 -17
- 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 +66 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +442 -49
- 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/search/query-utils.d.ts +25 -0
- package/dist/search/query-utils.d.ts.map +1 -0
- package/dist/search/query-utils.js +124 -0
- package/dist/search/query-utils.js.map +1 -0
- 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 +7 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +89 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +252 -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 +74 -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,128 @@ 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
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Validate that a value is a non-empty string
|
|
328
|
+
*/
|
|
329
|
+
validateString(value, name) {
|
|
330
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
331
|
+
return this.errorResult(`${name} must be a non-empty string`);
|
|
332
|
+
}
|
|
333
|
+
return value;
|
|
334
|
+
}
|
|
157
335
|
/**
|
|
158
336
|
* Execute a tool by name
|
|
159
337
|
*/
|
|
@@ -173,12 +351,19 @@ class ToolHandler {
|
|
|
173
351
|
case 'codegraph_node':
|
|
174
352
|
return await this.handleNode(args);
|
|
175
353
|
case 'codegraph_status':
|
|
176
|
-
return await this.handleStatus();
|
|
354
|
+
return await this.handleStatus(args);
|
|
355
|
+
case 'codegraph_files':
|
|
356
|
+
return await this.handleFiles(args);
|
|
177
357
|
default:
|
|
178
358
|
return this.errorResult(`Unknown tool: ${toolName}`);
|
|
179
359
|
}
|
|
180
360
|
}
|
|
181
361
|
catch (err) {
|
|
362
|
+
try {
|
|
363
|
+
const { captureException } = require('../sentry');
|
|
364
|
+
captureException(err, { tool: toolName });
|
|
365
|
+
}
|
|
366
|
+
catch { }
|
|
182
367
|
return this.errorResult(`Tool execution failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
183
368
|
}
|
|
184
369
|
}
|
|
@@ -186,10 +371,14 @@ class ToolHandler {
|
|
|
186
371
|
* Handle codegraph_search
|
|
187
372
|
*/
|
|
188
373
|
async handleSearch(args) {
|
|
189
|
-
const query = args.query;
|
|
374
|
+
const query = this.validateString(args.query, 'query');
|
|
375
|
+
if (typeof query !== 'string')
|
|
376
|
+
return query;
|
|
377
|
+
const cg = this.getCodeGraph(args.projectPath);
|
|
190
378
|
const kind = args.kind;
|
|
191
|
-
const
|
|
192
|
-
const
|
|
379
|
+
const rawLimit = Number(args.limit) || 10;
|
|
380
|
+
const limit = (0, utils_1.clamp)(rawLimit, 1, 100);
|
|
381
|
+
const results = cg.searchNodes(query, {
|
|
193
382
|
limit,
|
|
194
383
|
kinds: kind ? [kind] : undefined,
|
|
195
384
|
});
|
|
@@ -197,16 +386,24 @@ class ToolHandler {
|
|
|
197
386
|
return this.textResult(`No results found for "${query}"`);
|
|
198
387
|
}
|
|
199
388
|
const formatted = this.formatSearchResults(results);
|
|
200
|
-
return this.textResult(formatted);
|
|
389
|
+
return this.textResult(this.truncateOutput(formatted));
|
|
201
390
|
}
|
|
202
391
|
/**
|
|
203
392
|
* Handle codegraph_context
|
|
204
393
|
*/
|
|
205
394
|
async handleContext(args) {
|
|
206
|
-
const task = args.task;
|
|
395
|
+
const task = this.validateString(args.task, 'task');
|
|
396
|
+
if (typeof task !== 'string')
|
|
397
|
+
return task;
|
|
398
|
+
// Mark session as consulted (enables Grep/Glob/Bash)
|
|
399
|
+
const sessionId = process.env.CLAUDE_SESSION_ID;
|
|
400
|
+
if (sessionId) {
|
|
401
|
+
markSessionConsulted(sessionId);
|
|
402
|
+
}
|
|
403
|
+
const cg = this.getCodeGraph(args.projectPath);
|
|
207
404
|
const maxNodes = args.maxNodes || 20;
|
|
208
405
|
const includeCode = args.includeCode !== false;
|
|
209
|
-
const context = await
|
|
406
|
+
const context = await cg.buildContext(task, {
|
|
210
407
|
maxNodes,
|
|
211
408
|
includeCode,
|
|
212
409
|
format: 'markdown',
|
|
@@ -253,85 +450,88 @@ class ToolHandler {
|
|
|
253
450
|
* Handle codegraph_callers
|
|
254
451
|
*/
|
|
255
452
|
async handleCallers(args) {
|
|
256
|
-
const symbol = args.symbol;
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const
|
|
260
|
-
|
|
453
|
+
const symbol = this.validateString(args.symbol, 'symbol');
|
|
454
|
+
if (typeof symbol !== 'string')
|
|
455
|
+
return symbol;
|
|
456
|
+
const cg = this.getCodeGraph(args.projectPath);
|
|
457
|
+
const limit = (0, utils_1.clamp)(args.limit || 20, 1, 100);
|
|
458
|
+
const match = this.findSymbol(cg, symbol);
|
|
459
|
+
if (!match) {
|
|
261
460
|
return this.textResult(`Symbol "${symbol}" not found in the codebase`);
|
|
262
461
|
}
|
|
263
|
-
const
|
|
264
|
-
const callers = this.cg.getCallers(node.id);
|
|
462
|
+
const callers = cg.getCallers(match.node.id);
|
|
265
463
|
if (callers.length === 0) {
|
|
266
|
-
return this.textResult(`No callers found for "${symbol}"`);
|
|
464
|
+
return this.textResult(`No callers found for "${symbol}"${match.note}`);
|
|
267
465
|
}
|
|
268
|
-
// Extract just the nodes from the { node, edge } tuples
|
|
269
466
|
const callerNodes = callers.slice(0, limit).map(c => c.node);
|
|
270
|
-
const formatted = this.formatNodeList(callerNodes, `Callers of ${symbol}`);
|
|
271
|
-
return this.textResult(formatted);
|
|
467
|
+
const formatted = this.formatNodeList(callerNodes, `Callers of ${symbol}`) + match.note;
|
|
468
|
+
return this.textResult(this.truncateOutput(formatted));
|
|
272
469
|
}
|
|
273
470
|
/**
|
|
274
471
|
* Handle codegraph_callees
|
|
275
472
|
*/
|
|
276
473
|
async handleCallees(args) {
|
|
277
|
-
const symbol = args.symbol;
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const
|
|
281
|
-
|
|
474
|
+
const symbol = this.validateString(args.symbol, 'symbol');
|
|
475
|
+
if (typeof symbol !== 'string')
|
|
476
|
+
return symbol;
|
|
477
|
+
const cg = this.getCodeGraph(args.projectPath);
|
|
478
|
+
const limit = (0, utils_1.clamp)(args.limit || 20, 1, 100);
|
|
479
|
+
const match = this.findSymbol(cg, symbol);
|
|
480
|
+
if (!match) {
|
|
282
481
|
return this.textResult(`Symbol "${symbol}" not found in the codebase`);
|
|
283
482
|
}
|
|
284
|
-
const
|
|
285
|
-
const callees = this.cg.getCallees(node.id);
|
|
483
|
+
const callees = cg.getCallees(match.node.id);
|
|
286
484
|
if (callees.length === 0) {
|
|
287
|
-
return this.textResult(`No callees found for "${symbol}"`);
|
|
485
|
+
return this.textResult(`No callees found for "${symbol}"${match.note}`);
|
|
288
486
|
}
|
|
289
|
-
// Extract just the nodes from the { node, edge } tuples
|
|
290
487
|
const calleeNodes = callees.slice(0, limit).map(c => c.node);
|
|
291
|
-
const formatted = this.formatNodeList(calleeNodes, `Callees of ${symbol}`);
|
|
292
|
-
return this.textResult(formatted);
|
|
488
|
+
const formatted = this.formatNodeList(calleeNodes, `Callees of ${symbol}`) + match.note;
|
|
489
|
+
return this.textResult(this.truncateOutput(formatted));
|
|
293
490
|
}
|
|
294
491
|
/**
|
|
295
492
|
* Handle codegraph_impact
|
|
296
493
|
*/
|
|
297
494
|
async handleImpact(args) {
|
|
298
|
-
const symbol = args.symbol;
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
495
|
+
const symbol = this.validateString(args.symbol, 'symbol');
|
|
496
|
+
if (typeof symbol !== 'string')
|
|
497
|
+
return symbol;
|
|
498
|
+
const cg = this.getCodeGraph(args.projectPath);
|
|
499
|
+
const depth = (0, utils_1.clamp)(args.depth || 2, 1, 10);
|
|
500
|
+
const match = this.findSymbol(cg, symbol);
|
|
501
|
+
if (!match) {
|
|
303
502
|
return this.textResult(`Symbol "${symbol}" not found in the codebase`);
|
|
304
503
|
}
|
|
305
|
-
const
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
return this.textResult(formatted);
|
|
504
|
+
const impact = cg.getImpactRadius(match.node.id, depth);
|
|
505
|
+
const formatted = this.formatImpact(symbol, impact) + match.note;
|
|
506
|
+
return this.textResult(this.truncateOutput(formatted));
|
|
309
507
|
}
|
|
310
508
|
/**
|
|
311
509
|
* Handle codegraph_node
|
|
312
510
|
*/
|
|
313
511
|
async handleNode(args) {
|
|
314
|
-
const symbol = args.symbol;
|
|
512
|
+
const symbol = this.validateString(args.symbol, 'symbol');
|
|
513
|
+
if (typeof symbol !== 'string')
|
|
514
|
+
return symbol;
|
|
515
|
+
const cg = this.getCodeGraph(args.projectPath);
|
|
315
516
|
// Default to false to minimize context usage
|
|
316
517
|
const includeCode = args.includeCode === true;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (results.length === 0 || !results[0]) {
|
|
518
|
+
const match = this.findSymbol(cg, symbol);
|
|
519
|
+
if (!match) {
|
|
320
520
|
return this.textResult(`Symbol "${symbol}" not found in the codebase`);
|
|
321
521
|
}
|
|
322
|
-
const node = results[0].node;
|
|
323
522
|
let code = null;
|
|
324
523
|
if (includeCode) {
|
|
325
|
-
code = await
|
|
524
|
+
code = await cg.getCode(match.node.id);
|
|
326
525
|
}
|
|
327
|
-
const formatted = this.formatNodeDetails(node, code);
|
|
328
|
-
return this.textResult(formatted);
|
|
526
|
+
const formatted = this.formatNodeDetails(match.node, code) + match.note;
|
|
527
|
+
return this.textResult(this.truncateOutput(formatted));
|
|
329
528
|
}
|
|
330
529
|
/**
|
|
331
530
|
* Handle codegraph_status
|
|
332
531
|
*/
|
|
333
|
-
async handleStatus() {
|
|
334
|
-
const
|
|
532
|
+
async handleStatus(args) {
|
|
533
|
+
const cg = this.getCodeGraph(args.projectPath);
|
|
534
|
+
const stats = cg.getStats();
|
|
335
535
|
const lines = [
|
|
336
536
|
'## CodeGraph Status',
|
|
337
537
|
'',
|
|
@@ -355,6 +555,199 @@ class ToolHandler {
|
|
|
355
555
|
}
|
|
356
556
|
return this.textResult(lines.join('\n'));
|
|
357
557
|
}
|
|
558
|
+
/**
|
|
559
|
+
* Handle codegraph_files - get project file structure from the index
|
|
560
|
+
*/
|
|
561
|
+
async handleFiles(args) {
|
|
562
|
+
const cg = this.getCodeGraph(args.projectPath);
|
|
563
|
+
const pathFilter = args.path;
|
|
564
|
+
const pattern = args.pattern;
|
|
565
|
+
const format = args.format || 'tree';
|
|
566
|
+
const includeMetadata = args.includeMetadata !== false;
|
|
567
|
+
const maxDepth = args.maxDepth != null ? (0, utils_1.clamp)(args.maxDepth, 1, 20) : undefined;
|
|
568
|
+
// Get all files from the index
|
|
569
|
+
const allFiles = cg.getFiles();
|
|
570
|
+
if (allFiles.length === 0) {
|
|
571
|
+
return this.textResult('No files indexed. Run `codegraph index` first.');
|
|
572
|
+
}
|
|
573
|
+
// Filter by path prefix
|
|
574
|
+
let files = pathFilter
|
|
575
|
+
? allFiles.filter(f => f.path.startsWith(pathFilter) || f.path.startsWith('./' + pathFilter))
|
|
576
|
+
: allFiles;
|
|
577
|
+
// Filter by glob pattern
|
|
578
|
+
if (pattern) {
|
|
579
|
+
const regex = this.globToRegex(pattern);
|
|
580
|
+
files = files.filter(f => regex.test(f.path));
|
|
581
|
+
}
|
|
582
|
+
if (files.length === 0) {
|
|
583
|
+
return this.textResult(`No files found matching the criteria.`);
|
|
584
|
+
}
|
|
585
|
+
// Format output
|
|
586
|
+
let output;
|
|
587
|
+
switch (format) {
|
|
588
|
+
case 'flat':
|
|
589
|
+
output = this.formatFilesFlat(files, includeMetadata);
|
|
590
|
+
break;
|
|
591
|
+
case 'grouped':
|
|
592
|
+
output = this.formatFilesGrouped(files, includeMetadata);
|
|
593
|
+
break;
|
|
594
|
+
case 'tree':
|
|
595
|
+
default:
|
|
596
|
+
output = this.formatFilesTree(files, includeMetadata, maxDepth);
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
return this.textResult(this.truncateOutput(output));
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Convert glob pattern to regex
|
|
603
|
+
*/
|
|
604
|
+
globToRegex(pattern) {
|
|
605
|
+
const escaped = pattern
|
|
606
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special regex chars except * and ?
|
|
607
|
+
.replace(/\*\*/g, '{{GLOBSTAR}}') // Temp placeholder for **
|
|
608
|
+
.replace(/\*/g, '[^/]*') // * matches anything except /
|
|
609
|
+
.replace(/\?/g, '[^/]') // ? matches single char except /
|
|
610
|
+
.replace(/\{\{GLOBSTAR\}\}/g, '.*'); // ** matches anything including /
|
|
611
|
+
return new RegExp(escaped);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Format files as a flat list
|
|
615
|
+
*/
|
|
616
|
+
formatFilesFlat(files, includeMetadata) {
|
|
617
|
+
const lines = [`## Files (${files.length})`, ''];
|
|
618
|
+
for (const file of files.sort((a, b) => a.path.localeCompare(b.path))) {
|
|
619
|
+
if (includeMetadata) {
|
|
620
|
+
lines.push(`- ${file.path} (${file.language}, ${file.nodeCount} symbols)`);
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
lines.push(`- ${file.path}`);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return lines.join('\n');
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Format files grouped by language
|
|
630
|
+
*/
|
|
631
|
+
formatFilesGrouped(files, includeMetadata) {
|
|
632
|
+
const byLang = new Map();
|
|
633
|
+
for (const file of files) {
|
|
634
|
+
const existing = byLang.get(file.language) || [];
|
|
635
|
+
existing.push(file);
|
|
636
|
+
byLang.set(file.language, existing);
|
|
637
|
+
}
|
|
638
|
+
const lines = [`## Files by Language (${files.length} total)`, ''];
|
|
639
|
+
// Sort languages by file count (descending)
|
|
640
|
+
const sortedLangs = [...byLang.entries()].sort((a, b) => b[1].length - a[1].length);
|
|
641
|
+
for (const [lang, langFiles] of sortedLangs) {
|
|
642
|
+
lines.push(`### ${lang} (${langFiles.length})`);
|
|
643
|
+
for (const file of langFiles.sort((a, b) => a.path.localeCompare(b.path))) {
|
|
644
|
+
if (includeMetadata) {
|
|
645
|
+
lines.push(`- ${file.path} (${file.nodeCount} symbols)`);
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
lines.push(`- ${file.path}`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
lines.push('');
|
|
652
|
+
}
|
|
653
|
+
return lines.join('\n');
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Format files as a tree structure
|
|
657
|
+
*/
|
|
658
|
+
formatFilesTree(files, includeMetadata, maxDepth) {
|
|
659
|
+
const root = { name: '', children: new Map() };
|
|
660
|
+
for (const file of files) {
|
|
661
|
+
const parts = file.path.split('/');
|
|
662
|
+
let current = root;
|
|
663
|
+
for (let i = 0; i < parts.length; i++) {
|
|
664
|
+
const part = parts[i];
|
|
665
|
+
if (!part)
|
|
666
|
+
continue;
|
|
667
|
+
if (!current.children.has(part)) {
|
|
668
|
+
current.children.set(part, { name: part, children: new Map() });
|
|
669
|
+
}
|
|
670
|
+
current = current.children.get(part);
|
|
671
|
+
// If this is the last part, it's a file
|
|
672
|
+
if (i === parts.length - 1) {
|
|
673
|
+
current.file = { language: file.language, nodeCount: file.nodeCount };
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
// Render tree
|
|
678
|
+
const lines = [`## Project Structure (${files.length} files)`, ''];
|
|
679
|
+
const renderNode = (node, prefix, isLast, depth) => {
|
|
680
|
+
if (maxDepth !== undefined && depth > maxDepth)
|
|
681
|
+
return;
|
|
682
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
683
|
+
const childPrefix = isLast ? ' ' : '│ ';
|
|
684
|
+
if (node.name) {
|
|
685
|
+
let line = prefix + connector + node.name;
|
|
686
|
+
if (node.file && includeMetadata) {
|
|
687
|
+
line += ` (${node.file.language}, ${node.file.nodeCount} symbols)`;
|
|
688
|
+
}
|
|
689
|
+
lines.push(line);
|
|
690
|
+
}
|
|
691
|
+
const children = [...node.children.values()];
|
|
692
|
+
// Sort: directories first, then files, both alphabetically
|
|
693
|
+
children.sort((a, b) => {
|
|
694
|
+
const aIsDir = a.children.size > 0 && !a.file;
|
|
695
|
+
const bIsDir = b.children.size > 0 && !b.file;
|
|
696
|
+
if (aIsDir !== bIsDir)
|
|
697
|
+
return aIsDir ? -1 : 1;
|
|
698
|
+
return a.name.localeCompare(b.name);
|
|
699
|
+
});
|
|
700
|
+
for (let i = 0; i < children.length; i++) {
|
|
701
|
+
const child = children[i];
|
|
702
|
+
const nextPrefix = node.name ? prefix + childPrefix : prefix;
|
|
703
|
+
renderNode(child, nextPrefix, i === children.length - 1, depth + 1);
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
renderNode(root, '', true, 0);
|
|
707
|
+
return lines.join('\n');
|
|
708
|
+
}
|
|
709
|
+
// =========================================================================
|
|
710
|
+
// Symbol resolution helpers
|
|
711
|
+
// =========================================================================
|
|
712
|
+
/**
|
|
713
|
+
* Find a symbol by name, handling disambiguation when multiple matches exist.
|
|
714
|
+
* Returns the best match and a note about alternatives if any.
|
|
715
|
+
*/
|
|
716
|
+
findSymbol(cg, symbol) {
|
|
717
|
+
const results = cg.searchNodes(symbol, { limit: 10 });
|
|
718
|
+
if (results.length === 0 || !results[0]) {
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
// If only one result, or first is an exact name match, use it directly
|
|
722
|
+
const exactMatches = results.filter(r => r.node.name === symbol);
|
|
723
|
+
if (exactMatches.length === 1) {
|
|
724
|
+
return { node: exactMatches[0].node, note: '' };
|
|
725
|
+
}
|
|
726
|
+
if (exactMatches.length > 1) {
|
|
727
|
+
// Multiple exact matches - pick first, note the others
|
|
728
|
+
const picked = exactMatches[0].node;
|
|
729
|
+
const others = exactMatches.slice(1).map(r => `${r.node.name} (${r.node.kind}) at ${r.node.filePath}:${r.node.startLine}`);
|
|
730
|
+
const note = `\n\n> **Note:** ${exactMatches.length} symbols named "${symbol}". Showing results for \`${picked.filePath}:${picked.startLine}\`. Others: ${others.join(', ')}`;
|
|
731
|
+
return { node: picked, note };
|
|
732
|
+
}
|
|
733
|
+
// No exact match, use best fuzzy match
|
|
734
|
+
return { node: results[0].node, note: '' };
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Maximum output length to prevent context bloat (characters)
|
|
738
|
+
*/
|
|
739
|
+
MAX_OUTPUT_LENGTH = 15000;
|
|
740
|
+
/**
|
|
741
|
+
* Truncate output if it exceeds the maximum length
|
|
742
|
+
*/
|
|
743
|
+
truncateOutput(text) {
|
|
744
|
+
if (text.length <= this.MAX_OUTPUT_LENGTH)
|
|
745
|
+
return text;
|
|
746
|
+
const truncated = text.slice(0, this.MAX_OUTPUT_LENGTH);
|
|
747
|
+
const lastNewline = truncated.lastIndexOf('\n');
|
|
748
|
+
const cutPoint = lastNewline > this.MAX_OUTPUT_LENGTH * 0.8 ? lastNewline : this.MAX_OUTPUT_LENGTH;
|
|
749
|
+
return truncated.slice(0, cutPoint) + '\n\n... (output truncated)';
|
|
750
|
+
}
|
|
358
751
|
// =========================================================================
|
|
359
752
|
// Formatting helpers (compact by default to reduce context usage)
|
|
360
753
|
// =========================================================================
|