@colbymchenry/codegraph 0.6.6 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +180 -502
- package/dist/bin/codegraph.d.ts +0 -5
- package/dist/bin/codegraph.d.ts.map +1 -1
- package/dist/bin/codegraph.js +217 -263
- package/dist/bin/codegraph.js.map +1 -1
- package/dist/bin/uninstall.d.ts +0 -1
- package/dist/bin/uninstall.d.ts.map +1 -1
- package/dist/bin/uninstall.js +3 -29
- package/dist/bin/uninstall.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -3
- package/dist/config.js.map +1 -1
- package/dist/context/index.d.ts +3 -5
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +497 -46
- package/dist/context/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 +10 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/queries.d.ts +53 -0
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +244 -24
- package/dist/db/queries.js.map +1 -1
- package/dist/db/schema.sql +1 -16
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -7
- package/dist/errors.js.map +1 -1
- package/dist/extraction/dfm-extractor.d.ts +31 -0
- package/dist/extraction/dfm-extractor.d.ts.map +1 -0
- package/dist/extraction/dfm-extractor.js +151 -0
- package/dist/extraction/dfm-extractor.js.map +1 -0
- package/dist/extraction/grammars.d.ts +9 -1
- package/dist/extraction/grammars.d.ts.map +1 -1
- package/dist/extraction/grammars.js +34 -2
- package/dist/extraction/grammars.js.map +1 -1
- package/dist/extraction/index.d.ts +7 -1
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +373 -29
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/languages/c-cpp.d.ts +4 -0
- package/dist/extraction/languages/c-cpp.d.ts.map +1 -0
- package/dist/extraction/languages/c-cpp.js +126 -0
- package/dist/extraction/languages/c-cpp.js.map +1 -0
- package/dist/extraction/languages/csharp.d.ts +3 -0
- package/dist/extraction/languages/csharp.d.ts.map +1 -0
- package/dist/extraction/languages/csharp.js +72 -0
- package/dist/extraction/languages/csharp.js.map +1 -0
- package/dist/extraction/languages/dart.d.ts +3 -0
- package/dist/extraction/languages/dart.d.ts.map +1 -0
- package/dist/extraction/languages/dart.js +192 -0
- package/dist/extraction/languages/dart.js.map +1 -0
- package/dist/extraction/languages/go.d.ts +3 -0
- package/dist/extraction/languages/go.d.ts.map +1 -0
- package/dist/extraction/languages/go.js +58 -0
- package/dist/extraction/languages/go.js.map +1 -0
- package/dist/extraction/languages/index.d.ts +10 -0
- package/dist/extraction/languages/index.d.ts.map +1 -0
- package/dist/extraction/languages/index.js +43 -0
- package/dist/extraction/languages/index.js.map +1 -0
- package/dist/extraction/languages/java.d.ts +3 -0
- package/dist/extraction/languages/java.d.ts.map +1 -0
- package/dist/extraction/languages/java.js +64 -0
- package/dist/extraction/languages/java.js.map +1 -0
- package/dist/extraction/languages/javascript.d.ts +3 -0
- package/dist/extraction/languages/javascript.d.ts.map +1 -0
- package/dist/extraction/languages/javascript.js +90 -0
- package/dist/extraction/languages/javascript.js.map +1 -0
- package/dist/extraction/languages/kotlin.d.ts +3 -0
- package/dist/extraction/languages/kotlin.d.ts.map +1 -0
- package/dist/extraction/languages/kotlin.js +253 -0
- package/dist/extraction/languages/kotlin.js.map +1 -0
- package/dist/extraction/languages/pascal.d.ts +3 -0
- package/dist/extraction/languages/pascal.d.ts.map +1 -0
- package/dist/extraction/languages/pascal.js +66 -0
- package/dist/extraction/languages/pascal.js.map +1 -0
- package/dist/extraction/languages/php.d.ts +3 -0
- package/dist/extraction/languages/php.d.ts.map +1 -0
- package/dist/extraction/languages/php.js +107 -0
- package/dist/extraction/languages/php.js.map +1 -0
- package/dist/extraction/languages/python.d.ts +3 -0
- package/dist/extraction/languages/python.d.ts.map +1 -0
- package/dist/extraction/languages/python.js +56 -0
- package/dist/extraction/languages/python.js.map +1 -0
- package/dist/extraction/languages/ruby.d.ts +3 -0
- package/dist/extraction/languages/ruby.d.ts.map +1 -0
- package/dist/extraction/languages/ruby.js +114 -0
- package/dist/extraction/languages/ruby.js.map +1 -0
- package/dist/extraction/languages/rust.d.ts +3 -0
- package/dist/extraction/languages/rust.d.ts.map +1 -0
- package/dist/extraction/languages/rust.js +109 -0
- package/dist/extraction/languages/rust.js.map +1 -0
- package/dist/extraction/languages/swift.d.ts +3 -0
- package/dist/extraction/languages/swift.d.ts.map +1 -0
- package/dist/extraction/languages/swift.js +91 -0
- package/dist/extraction/languages/swift.js.map +1 -0
- package/dist/extraction/languages/typescript.d.ts +3 -0
- package/dist/extraction/languages/typescript.d.ts.map +1 -0
- package/dist/extraction/languages/typescript.js +129 -0
- package/dist/extraction/languages/typescript.js.map +1 -0
- package/dist/extraction/liquid-extractor.d.ts +52 -0
- package/dist/extraction/liquid-extractor.d.ts.map +1 -0
- package/dist/extraction/liquid-extractor.js +313 -0
- package/dist/extraction/liquid-extractor.js.map +1 -0
- package/dist/extraction/parse-worker.d.ts +8 -0
- package/dist/extraction/parse-worker.d.ts.map +1 -0
- package/dist/extraction/parse-worker.js +57 -0
- package/dist/extraction/parse-worker.js.map +1 -0
- package/dist/extraction/svelte-extractor.d.ts +47 -0
- package/dist/extraction/svelte-extractor.d.ts.map +1 -0
- package/dist/extraction/svelte-extractor.js +230 -0
- package/dist/extraction/svelte-extractor.js.map +1 -0
- package/dist/extraction/tree-sitter-helpers.d.ts +28 -0
- package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -0
- package/dist/extraction/tree-sitter-helpers.js +103 -0
- package/dist/extraction/tree-sitter-helpers.js.map +1 -0
- package/dist/extraction/tree-sitter-types.d.ts +179 -0
- package/dist/extraction/tree-sitter-types.d.ts.map +1 -0
- package/dist/extraction/tree-sitter-types.js +10 -0
- package/dist/extraction/tree-sitter-types.js.map +1 -0
- package/dist/extraction/tree-sitter.d.ts +67 -125
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +1052 -1860
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/graph/traversal.d.ts.map +1 -1
- package/dist/graph/traversal.js +20 -2
- package/dist/graph/traversal.js.map +1 -1
- package/dist/index.d.ts +29 -53
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +88 -117
- package/dist/index.js.map +1 -1
- package/dist/installer/claude-md-template.d.ts +1 -1
- package/dist/installer/claude-md-template.d.ts.map +1 -1
- package/dist/installer/claude-md-template.js +15 -15
- package/dist/installer/config-writer.d.ts +2 -13
- package/dist/installer/config-writer.d.ts.map +1 -1
- package/dist/installer/config-writer.js +4 -87
- package/dist/installer/config-writer.js.map +1 -1
- package/dist/installer/index.d.ts +3 -4
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +118 -127
- package/dist/installer/index.js.map +1 -1
- package/dist/mcp/index.d.ts +5 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +25 -4
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/tools.d.ts +33 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +405 -26
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/transport.d.ts.map +1 -1
- package/dist/mcp/transport.js +0 -2
- package/dist/mcp/transport.js.map +1 -1
- package/dist/resolution/frameworks/csharp.js +29 -84
- package/dist/resolution/frameworks/csharp.js.map +1 -1
- package/dist/resolution/frameworks/express.js +44 -48
- package/dist/resolution/frameworks/express.js.map +1 -1
- package/dist/resolution/frameworks/go.js +34 -70
- package/dist/resolution/frameworks/go.js.map +1 -1
- package/dist/resolution/frameworks/java.js +29 -87
- package/dist/resolution/frameworks/java.js.map +1 -1
- package/dist/resolution/frameworks/laravel.js +6 -6
- package/dist/resolution/frameworks/laravel.js.map +1 -1
- package/dist/resolution/frameworks/python.js +33 -98
- package/dist/resolution/frameworks/python.js.map +1 -1
- package/dist/resolution/frameworks/react.js +53 -76
- package/dist/resolution/frameworks/react.js.map +1 -1
- package/dist/resolution/frameworks/ruby.js +12 -24
- package/dist/resolution/frameworks/ruby.js.map +1 -1
- package/dist/resolution/frameworks/rust.js +26 -66
- package/dist/resolution/frameworks/rust.js.map +1 -1
- package/dist/resolution/frameworks/svelte.js +11 -31
- package/dist/resolution/frameworks/svelte.js.map +1 -1
- package/dist/resolution/frameworks/swift.js +42 -160
- package/dist/resolution/frameworks/swift.js.map +1 -1
- package/dist/resolution/index.d.ts +19 -6
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +300 -144
- package/dist/resolution/index.js.map +1 -1
- package/dist/resolution/name-matcher.d.ts +5 -0
- package/dist/resolution/name-matcher.d.ts.map +1 -1
- package/dist/resolution/name-matcher.js +148 -8
- package/dist/resolution/name-matcher.js.map +1 -1
- package/dist/resolution/types.d.ts +1 -1
- package/dist/resolution/types.d.ts.map +1 -1
- package/dist/search/query-utils.d.ts +26 -1
- package/dist/search/query-utils.d.ts.map +1 -1
- package/dist/search/query-utils.js +209 -9
- package/dist/search/query-utils.js.map +1 -1
- package/dist/sync/index.d.ts +2 -4
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +4 -3
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/watcher.d.ts +81 -0
- package/dist/sync/watcher.d.ts.map +1 -0
- package/dist/sync/watcher.js +184 -0
- package/dist/sync/watcher.js.map +1 -0
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -1
- package/dist/types.js.map +1 -1
- package/dist/ui/shimmer-progress.d.ts +11 -0
- package/dist/ui/shimmer-progress.d.ts.map +1 -0
- package/dist/ui/shimmer-progress.js +90 -0
- package/dist/ui/shimmer-progress.js.map +1 -0
- package/dist/ui/shimmer-worker.d.ts +2 -0
- package/dist/ui/shimmer-worker.d.ts.map +1 -0
- package/dist/ui/shimmer-worker.js +112 -0
- package/dist/ui/shimmer-worker.js.map +1 -0
- package/dist/ui/types.d.ts +17 -0
- package/dist/ui/types.d.ts.map +1 -0
- package/dist/ui/types.js +3 -0
- package/dist/ui/types.js.map +1 -0
- package/dist/vectors/embedder.js +1 -1
- package/dist/vectors/embedder.js.map +1 -1
- package/dist/visualizer/server.d.ts.map +1 -1
- package/dist/visualizer/server.js +3 -11
- package/dist/visualizer/server.js.map +1 -1
- package/package.json +7 -12
- package/scripts/postinstall.js +0 -68
package/dist/mcp/tools.js
CHANGED
|
@@ -39,6 +39,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
})();
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
41
|
exports.ToolHandler = exports.tools = void 0;
|
|
42
|
+
exports.getExploreBudget = getExploreBudget;
|
|
42
43
|
const index_1 = __importStar(require("../index"));
|
|
43
44
|
const crypto_1 = require("crypto");
|
|
44
45
|
const fs_1 = require("fs");
|
|
@@ -47,6 +48,22 @@ const os_1 = require("os");
|
|
|
47
48
|
const path_1 = require("path");
|
|
48
49
|
/** Maximum output length to prevent context bloat (characters) */
|
|
49
50
|
const MAX_OUTPUT_LENGTH = 15000;
|
|
51
|
+
/**
|
|
52
|
+
* Calculate the recommended number of codegraph_explore calls based on project size.
|
|
53
|
+
* Larger codebases need more exploration calls to cover their surface area,
|
|
54
|
+
* but smaller ones should use fewer to avoid unnecessary overhead.
|
|
55
|
+
*/
|
|
56
|
+
function getExploreBudget(fileCount) {
|
|
57
|
+
if (fileCount < 500)
|
|
58
|
+
return 1;
|
|
59
|
+
if (fileCount < 5000)
|
|
60
|
+
return 2;
|
|
61
|
+
if (fileCount < 15000)
|
|
62
|
+
return 3;
|
|
63
|
+
if (fileCount < 25000)
|
|
64
|
+
return 4;
|
|
65
|
+
return 5;
|
|
66
|
+
}
|
|
50
67
|
/**
|
|
51
68
|
* Mark a Claude session as having consulted MCP tools.
|
|
52
69
|
* This enables Grep/Glob/Bash commands that would otherwise be blocked.
|
|
@@ -207,6 +224,26 @@ exports.tools = [
|
|
|
207
224
|
required: ['symbol'],
|
|
208
225
|
},
|
|
209
226
|
},
|
|
227
|
+
{
|
|
228
|
+
name: 'codegraph_explore',
|
|
229
|
+
description: 'Deep exploration tool — returns comprehensive context for a topic in a SINGLE call. Groups all relevant source code by file (contiguous sections, not snippets), includes a relationship map, and uses deeper graph traversal. Designed to replace multiple codegraph_node + file Read calls. Use this instead of codegraph_context when you need thorough understanding.',
|
|
230
|
+
inputSchema: {
|
|
231
|
+
type: 'object',
|
|
232
|
+
properties: {
|
|
233
|
+
query: {
|
|
234
|
+
type: 'string',
|
|
235
|
+
description: 'What you want to understand (e.g., "undo redo system", "authentication flow", "how routing works")',
|
|
236
|
+
},
|
|
237
|
+
maxFiles: {
|
|
238
|
+
type: 'number',
|
|
239
|
+
description: 'Maximum number of files to include source code from (default: 12)',
|
|
240
|
+
default: 12,
|
|
241
|
+
},
|
|
242
|
+
projectPath: projectPathProperty,
|
|
243
|
+
},
|
|
244
|
+
required: ['query'],
|
|
245
|
+
},
|
|
246
|
+
},
|
|
210
247
|
{
|
|
211
248
|
name: 'codegraph_status',
|
|
212
249
|
description: 'Get the status of the CodeGraph index, including statistics about indexed files, nodes, and edges.',
|
|
@@ -276,6 +313,31 @@ class ToolHandler {
|
|
|
276
313
|
hasDefaultCodeGraph() {
|
|
277
314
|
return this.cg !== null;
|
|
278
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* Get tool definitions with dynamic descriptions based on project size.
|
|
318
|
+
* The codegraph_explore tool description includes a budget recommendation
|
|
319
|
+
* scaled to the number of indexed files.
|
|
320
|
+
*/
|
|
321
|
+
getTools() {
|
|
322
|
+
if (!this.cg)
|
|
323
|
+
return exports.tools;
|
|
324
|
+
try {
|
|
325
|
+
const stats = this.cg.getStats();
|
|
326
|
+
const budget = getExploreBudget(stats.fileCount);
|
|
327
|
+
return exports.tools.map(tool => {
|
|
328
|
+
if (tool.name === 'codegraph_explore') {
|
|
329
|
+
return {
|
|
330
|
+
...tool,
|
|
331
|
+
description: `${tool.description} Budget: make at most ${budget} calls for this project (${stats.fileCount.toLocaleString()} files indexed).`,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
return tool;
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
return exports.tools;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
279
341
|
/**
|
|
280
342
|
* Get CodeGraph instance for a project
|
|
281
343
|
*
|
|
@@ -350,6 +412,8 @@ class ToolHandler {
|
|
|
350
412
|
return await this.handleCallees(args);
|
|
351
413
|
case 'codegraph_impact':
|
|
352
414
|
return await this.handleImpact(args);
|
|
415
|
+
case 'codegraph_explore':
|
|
416
|
+
return await this.handleExplore(args);
|
|
353
417
|
case 'codegraph_node':
|
|
354
418
|
return await this.handleNode(args);
|
|
355
419
|
case 'codegraph_status':
|
|
@@ -361,11 +425,6 @@ class ToolHandler {
|
|
|
361
425
|
}
|
|
362
426
|
}
|
|
363
427
|
catch (err) {
|
|
364
|
-
try {
|
|
365
|
-
const { captureException } = require('../sentry');
|
|
366
|
-
captureException(err, { tool: toolName });
|
|
367
|
-
}
|
|
368
|
-
catch { }
|
|
369
428
|
return this.errorResult(`Tool execution failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
370
429
|
}
|
|
371
430
|
}
|
|
@@ -457,16 +516,25 @@ class ToolHandler {
|
|
|
457
516
|
return symbol;
|
|
458
517
|
const cg = this.getCodeGraph(args.projectPath);
|
|
459
518
|
const limit = (0, utils_1.clamp)(args.limit || 20, 1, 100);
|
|
460
|
-
const
|
|
461
|
-
if (
|
|
519
|
+
const allMatches = this.findAllSymbols(cg, symbol);
|
|
520
|
+
if (allMatches.nodes.length === 0) {
|
|
462
521
|
return this.textResult(`Symbol "${symbol}" not found in the codebase`);
|
|
463
522
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
523
|
+
// Aggregate callers across all matching symbols
|
|
524
|
+
const seen = new Set();
|
|
525
|
+
const allCallers = [];
|
|
526
|
+
for (const node of allMatches.nodes) {
|
|
527
|
+
for (const c of cg.getCallers(node.id)) {
|
|
528
|
+
if (!seen.has(c.node.id)) {
|
|
529
|
+
seen.add(c.node.id);
|
|
530
|
+
allCallers.push(c.node);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
467
533
|
}
|
|
468
|
-
|
|
469
|
-
|
|
534
|
+
if (allCallers.length === 0) {
|
|
535
|
+
return this.textResult(`No callers found for "${symbol}"${allMatches.note}`);
|
|
536
|
+
}
|
|
537
|
+
const formatted = this.formatNodeList(allCallers.slice(0, limit), `Callers of ${symbol}`) + allMatches.note;
|
|
470
538
|
return this.textResult(this.truncateOutput(formatted));
|
|
471
539
|
}
|
|
472
540
|
/**
|
|
@@ -478,16 +546,25 @@ class ToolHandler {
|
|
|
478
546
|
return symbol;
|
|
479
547
|
const cg = this.getCodeGraph(args.projectPath);
|
|
480
548
|
const limit = (0, utils_1.clamp)(args.limit || 20, 1, 100);
|
|
481
|
-
const
|
|
482
|
-
if (
|
|
549
|
+
const allMatches = this.findAllSymbols(cg, symbol);
|
|
550
|
+
if (allMatches.nodes.length === 0) {
|
|
483
551
|
return this.textResult(`Symbol "${symbol}" not found in the codebase`);
|
|
484
552
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
553
|
+
// Aggregate callees across all matching symbols
|
|
554
|
+
const seen = new Set();
|
|
555
|
+
const allCallees = [];
|
|
556
|
+
for (const node of allMatches.nodes) {
|
|
557
|
+
for (const c of cg.getCallees(node.id)) {
|
|
558
|
+
if (!seen.has(c.node.id)) {
|
|
559
|
+
seen.add(c.node.id);
|
|
560
|
+
allCallees.push(c.node);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
488
563
|
}
|
|
489
|
-
|
|
490
|
-
|
|
564
|
+
if (allCallees.length === 0) {
|
|
565
|
+
return this.textResult(`No callees found for "${symbol}"${allMatches.note}`);
|
|
566
|
+
}
|
|
567
|
+
const formatted = this.formatNodeList(allCallees.slice(0, limit), `Callees of ${symbol}`) + allMatches.note;
|
|
491
568
|
return this.textResult(this.truncateOutput(formatted));
|
|
492
569
|
}
|
|
493
570
|
/**
|
|
@@ -499,14 +576,275 @@ class ToolHandler {
|
|
|
499
576
|
return symbol;
|
|
500
577
|
const cg = this.getCodeGraph(args.projectPath);
|
|
501
578
|
const depth = (0, utils_1.clamp)(args.depth || 2, 1, 10);
|
|
502
|
-
const
|
|
503
|
-
if (
|
|
579
|
+
const allMatches = this.findAllSymbols(cg, symbol);
|
|
580
|
+
if (allMatches.nodes.length === 0) {
|
|
504
581
|
return this.textResult(`Symbol "${symbol}" not found in the codebase`);
|
|
505
582
|
}
|
|
506
|
-
|
|
507
|
-
const
|
|
583
|
+
// Aggregate impact across all matching symbols
|
|
584
|
+
const mergedNodes = new Map();
|
|
585
|
+
const mergedEdges = [];
|
|
586
|
+
const seenEdges = new Set();
|
|
587
|
+
for (const node of allMatches.nodes) {
|
|
588
|
+
const impact = cg.getImpactRadius(node.id, depth);
|
|
589
|
+
for (const [id, n] of impact.nodes) {
|
|
590
|
+
mergedNodes.set(id, n);
|
|
591
|
+
}
|
|
592
|
+
for (const e of impact.edges) {
|
|
593
|
+
const key = `${e.source}->${e.target}:${e.kind}`;
|
|
594
|
+
if (!seenEdges.has(key)) {
|
|
595
|
+
seenEdges.add(key);
|
|
596
|
+
mergedEdges.push(e);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
const mergedImpact = {
|
|
601
|
+
nodes: mergedNodes,
|
|
602
|
+
edges: mergedEdges,
|
|
603
|
+
roots: allMatches.nodes.map(n => n.id),
|
|
604
|
+
};
|
|
605
|
+
const formatted = this.formatImpact(symbol, mergedImpact) + allMatches.note;
|
|
508
606
|
return this.textResult(this.truncateOutput(formatted));
|
|
509
607
|
}
|
|
608
|
+
/** Maximum output for explore tool — sized to stay under MCP client token limits (~10k tokens) */
|
|
609
|
+
static EXPLORE_MAX_OUTPUT = 35000;
|
|
610
|
+
/**
|
|
611
|
+
* Handle codegraph_explore — deep exploration in a single call
|
|
612
|
+
*
|
|
613
|
+
* Strategy: find relevant symbols via graph traversal, group by file,
|
|
614
|
+
* then read contiguous file sections covering all symbols per file.
|
|
615
|
+
* This replaces multiple codegraph_node + Read calls.
|
|
616
|
+
*/
|
|
617
|
+
async handleExplore(args) {
|
|
618
|
+
const query = this.validateString(args.query, 'query');
|
|
619
|
+
if (typeof query !== 'string')
|
|
620
|
+
return query;
|
|
621
|
+
const cg = this.getCodeGraph(args.projectPath);
|
|
622
|
+
const maxFiles = (0, utils_1.clamp)(args.maxFiles || 12, 1, 20);
|
|
623
|
+
const projectRoot = cg.getProjectRoot();
|
|
624
|
+
// Step 1: Find relevant context with generous parameters
|
|
625
|
+
const subgraph = await cg.findRelevantContext(query, {
|
|
626
|
+
searchLimit: 8,
|
|
627
|
+
traversalDepth: 3,
|
|
628
|
+
maxNodes: 80,
|
|
629
|
+
minScore: 0.2,
|
|
630
|
+
});
|
|
631
|
+
if (subgraph.nodes.size === 0) {
|
|
632
|
+
return this.textResult(`No relevant code found for "${query}"`);
|
|
633
|
+
}
|
|
634
|
+
// Step 2: Group nodes by file, score by relevance
|
|
635
|
+
const fileGroups = new Map();
|
|
636
|
+
const entryNodeIds = new Set(subgraph.roots);
|
|
637
|
+
// Build a set of nodes directly connected to entry points (depth 1)
|
|
638
|
+
const connectedToEntry = new Set();
|
|
639
|
+
for (const edge of subgraph.edges) {
|
|
640
|
+
if (entryNodeIds.has(edge.source))
|
|
641
|
+
connectedToEntry.add(edge.target);
|
|
642
|
+
if (entryNodeIds.has(edge.target))
|
|
643
|
+
connectedToEntry.add(edge.source);
|
|
644
|
+
}
|
|
645
|
+
for (const node of subgraph.nodes.values()) {
|
|
646
|
+
// Skip import/export nodes — they add noise without information
|
|
647
|
+
if (node.kind === 'import' || node.kind === 'export')
|
|
648
|
+
continue;
|
|
649
|
+
const group = fileGroups.get(node.filePath) || { nodes: [], score: 0 };
|
|
650
|
+
group.nodes.push(node);
|
|
651
|
+
// Score: entry point nodes worth 10, directly connected worth 3, others worth 1
|
|
652
|
+
if (entryNodeIds.has(node.id)) {
|
|
653
|
+
group.score += 10;
|
|
654
|
+
}
|
|
655
|
+
else if (connectedToEntry.has(node.id)) {
|
|
656
|
+
group.score += 3;
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
group.score += 1;
|
|
660
|
+
}
|
|
661
|
+
fileGroups.set(node.filePath, group);
|
|
662
|
+
}
|
|
663
|
+
// Only include files that have entry points or nodes directly connected to entry points
|
|
664
|
+
const relevantFiles = [...fileGroups.entries()].filter(([, group]) => group.score >= 3);
|
|
665
|
+
// Extract query terms for relevance checking
|
|
666
|
+
const queryTerms = query.toLowerCase().split(/\s+/).filter(t => t.length >= 3);
|
|
667
|
+
// Sort files: highest relevance first, deprioritize low-value files
|
|
668
|
+
const sortedFiles = relevantFiles.sort((a, b) => {
|
|
669
|
+
const aPath = a[0].toLowerCase();
|
|
670
|
+
const bPath = b[0].toLowerCase();
|
|
671
|
+
// Check if any node name or file path relates to query terms
|
|
672
|
+
const hasQueryRelevance = (filePath, nodes) => {
|
|
673
|
+
const fp = filePath.toLowerCase();
|
|
674
|
+
if (queryTerms.some(t => fp.includes(t)))
|
|
675
|
+
return true;
|
|
676
|
+
return nodes.some(n => queryTerms.some(t => n.name.toLowerCase().includes(t)));
|
|
677
|
+
};
|
|
678
|
+
const aRelevant = hasQueryRelevance(aPath, a[1].nodes);
|
|
679
|
+
const bRelevant = hasQueryRelevance(bPath, b[1].nodes);
|
|
680
|
+
if (aRelevant !== bRelevant)
|
|
681
|
+
return aRelevant ? -1 : 1;
|
|
682
|
+
// Deprioritize test files, icon files, and i18n files
|
|
683
|
+
const isLowValue = (p) => /\/(tests?|__tests?__|spec)\//i.test(p) ||
|
|
684
|
+
/\bicons?\b/i.test(p) ||
|
|
685
|
+
/\bi18n\b/i.test(p);
|
|
686
|
+
const aLow = isLowValue(aPath);
|
|
687
|
+
const bLow = isLowValue(bPath);
|
|
688
|
+
if (aLow !== bLow)
|
|
689
|
+
return aLow ? 1 : -1;
|
|
690
|
+
if (a[1].score !== b[1].score)
|
|
691
|
+
return b[1].score - a[1].score;
|
|
692
|
+
return b[1].nodes.length - a[1].nodes.length;
|
|
693
|
+
});
|
|
694
|
+
// Step 3: Build relationship map
|
|
695
|
+
const lines = [
|
|
696
|
+
`## Exploration: ${query}`,
|
|
697
|
+
'',
|
|
698
|
+
`Found ${subgraph.nodes.size} symbols across ${fileGroups.size} files.`,
|
|
699
|
+
'',
|
|
700
|
+
];
|
|
701
|
+
// Relationship map — show how symbols connect
|
|
702
|
+
const significantEdges = subgraph.edges.filter(e => e.kind !== 'contains' // skip contains — it's implied by file grouping
|
|
703
|
+
);
|
|
704
|
+
if (significantEdges.length > 0) {
|
|
705
|
+
lines.push('### Relationships');
|
|
706
|
+
lines.push('');
|
|
707
|
+
// Group edges by kind for readability
|
|
708
|
+
const byKind = new Map();
|
|
709
|
+
for (const edge of significantEdges) {
|
|
710
|
+
const sourceNode = subgraph.nodes.get(edge.source);
|
|
711
|
+
const targetNode = subgraph.nodes.get(edge.target);
|
|
712
|
+
if (!sourceNode || !targetNode)
|
|
713
|
+
continue;
|
|
714
|
+
const group = byKind.get(edge.kind) || [];
|
|
715
|
+
group.push({ source: sourceNode.name, target: targetNode.name });
|
|
716
|
+
byKind.set(edge.kind, group);
|
|
717
|
+
}
|
|
718
|
+
for (const [kind, edges] of byKind) {
|
|
719
|
+
// Show up to 15 relationships per kind
|
|
720
|
+
const shown = edges.slice(0, 15);
|
|
721
|
+
lines.push(`**${kind}:**`);
|
|
722
|
+
for (const e of shown) {
|
|
723
|
+
lines.push(`- ${e.source} → ${e.target}`);
|
|
724
|
+
}
|
|
725
|
+
if (edges.length > 15) {
|
|
726
|
+
lines.push(`- ... and ${edges.length - 15} more`);
|
|
727
|
+
}
|
|
728
|
+
lines.push('');
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
// Step 4: Read contiguous file sections
|
|
732
|
+
lines.push('### Source Code');
|
|
733
|
+
lines.push('');
|
|
734
|
+
let totalChars = lines.join('\n').length;
|
|
735
|
+
let filesIncluded = 0;
|
|
736
|
+
for (const [filePath, group] of sortedFiles) {
|
|
737
|
+
if (filesIncluded >= maxFiles)
|
|
738
|
+
break;
|
|
739
|
+
if (totalChars > ToolHandler.EXPLORE_MAX_OUTPUT * 0.9)
|
|
740
|
+
break;
|
|
741
|
+
const absPath = (0, utils_1.validatePathWithinRoot)(projectRoot, filePath);
|
|
742
|
+
if (!absPath || !(0, fs_1.existsSync)(absPath))
|
|
743
|
+
continue;
|
|
744
|
+
let fileContent;
|
|
745
|
+
try {
|
|
746
|
+
fileContent = (0, fs_1.readFileSync)(absPath, 'utf-8');
|
|
747
|
+
}
|
|
748
|
+
catch {
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
const fileLines = fileContent.split('\n');
|
|
752
|
+
const lang = group.nodes[0]?.language || '';
|
|
753
|
+
// Cluster nearby symbols to avoid reading huge gaps between distant symbols.
|
|
754
|
+
// Sort by start line, then merge overlapping/adjacent ranges (within 15 lines).
|
|
755
|
+
const ranges = group.nodes
|
|
756
|
+
.filter(n => n.startLine > 0 && n.endLine > 0)
|
|
757
|
+
.map(n => ({ start: n.startLine, end: n.endLine, name: n.name, kind: n.kind }))
|
|
758
|
+
.sort((a, b) => a.start - b.start);
|
|
759
|
+
if (ranges.length === 0)
|
|
760
|
+
continue;
|
|
761
|
+
const GAP_THRESHOLD = 15; // merge sections within 15 lines of each other
|
|
762
|
+
const clusters = [];
|
|
763
|
+
let current = { start: ranges[0].start, end: ranges[0].end, symbols: [`${ranges[0].name}(${ranges[0].kind})`] };
|
|
764
|
+
for (let i = 1; i < ranges.length; i++) {
|
|
765
|
+
const r = ranges[i];
|
|
766
|
+
if (r.start <= current.end + GAP_THRESHOLD) {
|
|
767
|
+
current.end = Math.max(current.end, r.end);
|
|
768
|
+
current.symbols.push(`${r.name}(${r.kind})`);
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
clusters.push(current);
|
|
772
|
+
current = { start: r.start, end: r.end, symbols: [`${r.name}(${r.kind})`] };
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
clusters.push(current);
|
|
776
|
+
// Build file section output from clusters
|
|
777
|
+
const contextPadding = 3;
|
|
778
|
+
let fileSection = '';
|
|
779
|
+
const allSymbols = [];
|
|
780
|
+
for (const cluster of clusters) {
|
|
781
|
+
const startIdx = Math.max(0, cluster.start - 1 - contextPadding);
|
|
782
|
+
const endIdx = Math.min(fileLines.length, cluster.end + contextPadding);
|
|
783
|
+
const section = fileLines.slice(startIdx, endIdx).join('\n');
|
|
784
|
+
if (fileSection.length > 0) {
|
|
785
|
+
fileSection += '\n\n// ... (gap) ...\n\n';
|
|
786
|
+
}
|
|
787
|
+
fileSection += section;
|
|
788
|
+
allSymbols.push(...cluster.symbols);
|
|
789
|
+
}
|
|
790
|
+
// Skip if this section would blow the output limit
|
|
791
|
+
if (totalChars + fileSection.length + 200 > ToolHandler.EXPLORE_MAX_OUTPUT) {
|
|
792
|
+
const budget = ToolHandler.EXPLORE_MAX_OUTPUT - totalChars - 200;
|
|
793
|
+
if (budget < 500)
|
|
794
|
+
break;
|
|
795
|
+
const trimmed = fileSection.slice(0, budget) + '\n// ... trimmed ...';
|
|
796
|
+
lines.push(`#### ${filePath} — ${allSymbols.join(', ')}`);
|
|
797
|
+
lines.push('');
|
|
798
|
+
lines.push('```' + lang);
|
|
799
|
+
lines.push(trimmed);
|
|
800
|
+
lines.push('```');
|
|
801
|
+
lines.push('');
|
|
802
|
+
totalChars += trimmed.length + 200;
|
|
803
|
+
filesIncluded++;
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
lines.push(`#### ${filePath} — ${allSymbols.join(', ')}`);
|
|
807
|
+
lines.push('');
|
|
808
|
+
lines.push('```' + lang);
|
|
809
|
+
lines.push(fileSection);
|
|
810
|
+
lines.push('```');
|
|
811
|
+
lines.push('');
|
|
812
|
+
totalChars += fileSection.length + 200;
|
|
813
|
+
filesIncluded++;
|
|
814
|
+
}
|
|
815
|
+
// Add remaining files as references (from both relevant and peripheral files)
|
|
816
|
+
const remainingRelevant = sortedFiles.slice(filesIncluded);
|
|
817
|
+
const peripheralFiles = [...fileGroups.entries()]
|
|
818
|
+
.filter(([, group]) => group.score < 3)
|
|
819
|
+
.sort((a, b) => b[1].score - a[1].score);
|
|
820
|
+
const remainingFiles = [...remainingRelevant, ...peripheralFiles];
|
|
821
|
+
if (remainingFiles.length > 0) {
|
|
822
|
+
lines.push('### Additional relevant files (not shown)');
|
|
823
|
+
lines.push('');
|
|
824
|
+
for (const [filePath, group] of remainingFiles.slice(0, 10)) {
|
|
825
|
+
const symbols = group.nodes.map(n => `${n.name}:${n.startLine}`).join(', ');
|
|
826
|
+
lines.push(`- ${filePath}: ${symbols}`);
|
|
827
|
+
}
|
|
828
|
+
if (remainingFiles.length > 10) {
|
|
829
|
+
lines.push(`- ... and ${remainingFiles.length - 10} more files`);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
// Add completeness signal so agents know they don't need to re-read these files
|
|
833
|
+
lines.push('');
|
|
834
|
+
lines.push('---');
|
|
835
|
+
lines.push(`> **Complete source code is included above for ${filesIncluded} files.** You do NOT need to re-read these files — the relevant sections are already shown in full. Only use Read/Grep for files listed under "Additional relevant files" if you need more detail.`);
|
|
836
|
+
// Add explore budget note based on project size
|
|
837
|
+
try {
|
|
838
|
+
const stats = cg.getStats();
|
|
839
|
+
const budget = getExploreBudget(stats.fileCount);
|
|
840
|
+
lines.push('');
|
|
841
|
+
lines.push(`> **Explore budget: ${budget} calls max for this project (${stats.fileCount.toLocaleString()} files indexed).** Stop exploring and synthesize your answer once you've used ${budget} calls — do NOT make additional explore calls beyond this budget.`);
|
|
842
|
+
}
|
|
843
|
+
catch {
|
|
844
|
+
// Stats unavailable — skip budget note
|
|
845
|
+
}
|
|
846
|
+
return this.textResult(lines.join('\n'));
|
|
847
|
+
}
|
|
510
848
|
/**
|
|
511
849
|
* Handle codegraph_node
|
|
512
850
|
*/
|
|
@@ -715,13 +1053,36 @@ class ToolHandler {
|
|
|
715
1053
|
* Find a symbol by name, handling disambiguation when multiple matches exist.
|
|
716
1054
|
* Returns the best match and a note about alternatives if any.
|
|
717
1055
|
*/
|
|
1056
|
+
/**
|
|
1057
|
+
* Check if a node matches a symbol query, supporting both simple names and
|
|
1058
|
+
* qualified "Parent.child" notation (e.g., "Session.request" matches a method
|
|
1059
|
+
* named "request" inside a class named "Session").
|
|
1060
|
+
*/
|
|
1061
|
+
matchesSymbol(node, symbol) {
|
|
1062
|
+
// Simple name match
|
|
1063
|
+
if (node.name === symbol)
|
|
1064
|
+
return true;
|
|
1065
|
+
// File basename match (e.g., "product-card" matches "product-card.liquid")
|
|
1066
|
+
if (node.kind === 'file' && node.name.replace(/\.[^.]+$/, '') === symbol)
|
|
1067
|
+
return true;
|
|
1068
|
+
// Qualified name match: "Parent.child" → look for "::Parent::child" in qualified_name
|
|
1069
|
+
if (symbol.includes('.')) {
|
|
1070
|
+
const parts = symbol.split('.');
|
|
1071
|
+
const qualifiedSuffix = parts.join('::');
|
|
1072
|
+
if (node.qualifiedName.includes(qualifiedSuffix))
|
|
1073
|
+
return true;
|
|
1074
|
+
}
|
|
1075
|
+
return false;
|
|
1076
|
+
}
|
|
718
1077
|
findSymbol(cg, symbol) {
|
|
719
|
-
|
|
1078
|
+
// Use higher limit for qualified lookups (e.g., "Session.request") since the
|
|
1079
|
+
// target may rank lower in FTS when there are many partial matches
|
|
1080
|
+
const limit = symbol.includes('.') ? 50 : 10;
|
|
1081
|
+
const results = cg.searchNodes(symbol, { limit });
|
|
720
1082
|
if (results.length === 0 || !results[0]) {
|
|
721
1083
|
return null;
|
|
722
1084
|
}
|
|
723
|
-
|
|
724
|
-
const exactMatches = results.filter(r => r.node.name === symbol);
|
|
1085
|
+
const exactMatches = results.filter(r => this.matchesSymbol(r.node, symbol));
|
|
725
1086
|
if (exactMatches.length === 1) {
|
|
726
1087
|
return { node: exactMatches[0].node, note: '' };
|
|
727
1088
|
}
|
|
@@ -735,6 +1096,24 @@ class ToolHandler {
|
|
|
735
1096
|
// No exact match, use best fuzzy match
|
|
736
1097
|
return { node: results[0].node, note: '' };
|
|
737
1098
|
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Find ALL symbols matching a name. Used by callers/callees/impact to aggregate
|
|
1101
|
+
* results across all matching symbols (e.g., multiple classes with an `execute` method).
|
|
1102
|
+
*/
|
|
1103
|
+
findAllSymbols(cg, symbol) {
|
|
1104
|
+
const results = cg.searchNodes(symbol, { limit: 50 });
|
|
1105
|
+
if (results.length === 0) {
|
|
1106
|
+
return { nodes: [], note: '' };
|
|
1107
|
+
}
|
|
1108
|
+
const exactMatches = results.filter(r => this.matchesSymbol(r.node, symbol));
|
|
1109
|
+
if (exactMatches.length <= 1) {
|
|
1110
|
+
const node = exactMatches[0]?.node ?? results[0].node;
|
|
1111
|
+
return { nodes: [node], note: '' };
|
|
1112
|
+
}
|
|
1113
|
+
const locations = exactMatches.map(r => `${r.node.kind} at ${r.node.filePath}:${r.node.startLine}`);
|
|
1114
|
+
const note = `\n\n> **Note:** Aggregated results across ${exactMatches.length} symbols named "${symbol}": ${locations.join(', ')}`;
|
|
1115
|
+
return { nodes: exactMatches.map(r => r.node), note };
|
|
1116
|
+
}
|
|
738
1117
|
/**
|
|
739
1118
|
* Truncate output if it exceeds the maximum length
|
|
740
1119
|
*/
|