@colbymchenry/codegraph 0.6.8 → 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 +179 -476
- package/dist/bin/codegraph.d.ts +0 -5
- package/dist/bin/codegraph.d.ts.map +1 -1
- package/dist/bin/codegraph.js +217 -237
- 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/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 -14
- package/dist/db/queries.js.map +1 -1
- package/dist/db/schema.sql +1 -16
- 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 -22
- 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 -1855
- 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 -114
- 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 +1 -10
- package/dist/installer/config-writer.d.ts.map +1 -1
- package/dist/installer/config-writer.js +0 -79
- 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 -116
- 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 -1
- 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 -21
- package/dist/mcp/tools.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 -141
- 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 -0
- package/dist/types.d.ts.map +1 -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/package.json +7 -12
- package/scripts/postinstall.js +0 -68
package/dist/mcp/tools.d.ts
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
* Defines the tools exposed by the CodeGraph MCP server.
|
|
5
5
|
*/
|
|
6
6
|
import CodeGraph from '../index';
|
|
7
|
+
/**
|
|
8
|
+
* Calculate the recommended number of codegraph_explore calls based on project size.
|
|
9
|
+
* Larger codebases need more exploration calls to cover their surface area,
|
|
10
|
+
* but smaller ones should use fewer to avoid unnecessary overhead.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getExploreBudget(fileCount: number): number;
|
|
7
13
|
/**
|
|
8
14
|
* MCP Tool definition
|
|
9
15
|
*/
|
|
@@ -59,6 +65,12 @@ export declare class ToolHandler {
|
|
|
59
65
|
* Whether a default CodeGraph instance is available
|
|
60
66
|
*/
|
|
61
67
|
hasDefaultCodeGraph(): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Get tool definitions with dynamic descriptions based on project size.
|
|
70
|
+
* The codegraph_explore tool description includes a budget recommendation
|
|
71
|
+
* scaled to the number of indexed files.
|
|
72
|
+
*/
|
|
73
|
+
getTools(): ToolDefinition[];
|
|
62
74
|
/**
|
|
63
75
|
* Get CodeGraph instance for a project
|
|
64
76
|
*
|
|
@@ -105,6 +117,16 @@ export declare class ToolHandler {
|
|
|
105
117
|
* Handle codegraph_impact
|
|
106
118
|
*/
|
|
107
119
|
private handleImpact;
|
|
120
|
+
/** Maximum output for explore tool — sized to stay under MCP client token limits (~10k tokens) */
|
|
121
|
+
private static readonly EXPLORE_MAX_OUTPUT;
|
|
122
|
+
/**
|
|
123
|
+
* Handle codegraph_explore — deep exploration in a single call
|
|
124
|
+
*
|
|
125
|
+
* Strategy: find relevant symbols via graph traversal, group by file,
|
|
126
|
+
* then read contiguous file sections covering all symbols per file.
|
|
127
|
+
* This replaces multiple codegraph_node + Read calls.
|
|
128
|
+
*/
|
|
129
|
+
private handleExplore;
|
|
108
130
|
/**
|
|
109
131
|
* Handle codegraph_node
|
|
110
132
|
*/
|
|
@@ -137,7 +159,18 @@ export declare class ToolHandler {
|
|
|
137
159
|
* Find a symbol by name, handling disambiguation when multiple matches exist.
|
|
138
160
|
* Returns the best match and a note about alternatives if any.
|
|
139
161
|
*/
|
|
162
|
+
/**
|
|
163
|
+
* Check if a node matches a symbol query, supporting both simple names and
|
|
164
|
+
* qualified "Parent.child" notation (e.g., "Session.request" matches a method
|
|
165
|
+
* named "request" inside a class named "Session").
|
|
166
|
+
*/
|
|
167
|
+
private matchesSymbol;
|
|
140
168
|
private findSymbol;
|
|
169
|
+
/**
|
|
170
|
+
* Find ALL symbols matching a name. Used by callers/callees/impact to aggregate
|
|
171
|
+
* results across all matching symbols (e.g., multiple classes with an `execute` method).
|
|
172
|
+
*/
|
|
173
|
+
private findAllSymbols;
|
|
141
174
|
/**
|
|
142
175
|
* Truncate output if it exceeds the maximum length
|
|
143
176
|
*/
|
package/dist/mcp/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,SAAuC,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,SAAuC,MAAM,UAAU,CAAC;AAW/D;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAM1D;AAgBD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;QAC3C,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAUD;;;;;;;GAOG;AACH,eAAO,MAAM,KAAK,EAAE,cAAc,EAkMjC,CAAC;AAEF;;;;;GAKG;AACH,qBAAa,WAAW;IAIV,OAAO,CAAC,EAAE;IAFtB,OAAO,CAAC,YAAY,CAAqC;gBAErC,EAAE,EAAE,SAAS,GAAG,IAAI;IAExC;;OAEG;IACH,mBAAmB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAIxC;;OAEG;IACH,mBAAmB,IAAI,OAAO;IAI9B;;;;OAIG;IACH,QAAQ,IAAI,cAAc,EAAE;IAqB5B;;;;;;;;OAQG;IACH,OAAO,CAAC,YAAY;IAqCpB;;OAEG;IACH,QAAQ,IAAI,IAAI;IAOhB;;OAEG;IACH,OAAO,CAAC,cAAc;IAOtB;;OAEG;IACG,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC;IA6BnF;;OAEG;YACW,YAAY;IAsB1B;;OAEG;YACW,aAAa;IAmC3B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAyB/B;;OAEG;YACW,aAAa;IAgC3B;;OAEG;YACW,aAAa;IAgC3B;;OAEG;YACW,YAAY;IAyC1B,kGAAkG;IAClG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAEnD;;;;;;OAMG;YACW,aAAa;IAgQ3B;;OAEG;YACW,UAAU;IAuBxB;;OAEG;YACW,YAAY;IA+B1B;;OAEG;YACW,WAAW;IAgDzB;;OAEG;IACH,OAAO,CAAC,WAAW;IAUnB;;OAEG;IACH,OAAO,CAAC,eAAe;IAcvB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA6B1B;;OAEG;IACH,OAAO,CAAC,eAAe;IA4EvB;;;OAGG;IACH;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,UAAU;IA8BlB;;;OAGG;IACH,OAAO,CAAC,cAAc;IAqBtB;;OAEG;IACH,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,YAAY;IA4BpB,OAAO,CAAC,iBAAiB;IAwBzB,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,WAAW;CAMpB"}
|
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':
|
|
@@ -452,16 +516,25 @@ class ToolHandler {
|
|
|
452
516
|
return symbol;
|
|
453
517
|
const cg = this.getCodeGraph(args.projectPath);
|
|
454
518
|
const limit = (0, utils_1.clamp)(args.limit || 20, 1, 100);
|
|
455
|
-
const
|
|
456
|
-
if (
|
|
519
|
+
const allMatches = this.findAllSymbols(cg, symbol);
|
|
520
|
+
if (allMatches.nodes.length === 0) {
|
|
457
521
|
return this.textResult(`Symbol "${symbol}" not found in the codebase`);
|
|
458
522
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (allCallers.length === 0) {
|
|
535
|
+
return this.textResult(`No callers found for "${symbol}"${allMatches.note}`);
|
|
462
536
|
}
|
|
463
|
-
const
|
|
464
|
-
const formatted = this.formatNodeList(callerNodes, `Callers of ${symbol}`) + match.note;
|
|
537
|
+
const formatted = this.formatNodeList(allCallers.slice(0, limit), `Callers of ${symbol}`) + allMatches.note;
|
|
465
538
|
return this.textResult(this.truncateOutput(formatted));
|
|
466
539
|
}
|
|
467
540
|
/**
|
|
@@ -473,16 +546,25 @@ class ToolHandler {
|
|
|
473
546
|
return symbol;
|
|
474
547
|
const cg = this.getCodeGraph(args.projectPath);
|
|
475
548
|
const limit = (0, utils_1.clamp)(args.limit || 20, 1, 100);
|
|
476
|
-
const
|
|
477
|
-
if (
|
|
549
|
+
const allMatches = this.findAllSymbols(cg, symbol);
|
|
550
|
+
if (allMatches.nodes.length === 0) {
|
|
478
551
|
return this.textResult(`Symbol "${symbol}" not found in the codebase`);
|
|
479
552
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (allCallees.length === 0) {
|
|
565
|
+
return this.textResult(`No callees found for "${symbol}"${allMatches.note}`);
|
|
483
566
|
}
|
|
484
|
-
const
|
|
485
|
-
const formatted = this.formatNodeList(calleeNodes, `Callees of ${symbol}`) + match.note;
|
|
567
|
+
const formatted = this.formatNodeList(allCallees.slice(0, limit), `Callees of ${symbol}`) + allMatches.note;
|
|
486
568
|
return this.textResult(this.truncateOutput(formatted));
|
|
487
569
|
}
|
|
488
570
|
/**
|
|
@@ -494,14 +576,275 @@ class ToolHandler {
|
|
|
494
576
|
return symbol;
|
|
495
577
|
const cg = this.getCodeGraph(args.projectPath);
|
|
496
578
|
const depth = (0, utils_1.clamp)(args.depth || 2, 1, 10);
|
|
497
|
-
const
|
|
498
|
-
if (
|
|
579
|
+
const allMatches = this.findAllSymbols(cg, symbol);
|
|
580
|
+
if (allMatches.nodes.length === 0) {
|
|
499
581
|
return this.textResult(`Symbol "${symbol}" not found in the codebase`);
|
|
500
582
|
}
|
|
501
|
-
|
|
502
|
-
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;
|
|
503
606
|
return this.textResult(this.truncateOutput(formatted));
|
|
504
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
|
+
}
|
|
505
848
|
/**
|
|
506
849
|
* Handle codegraph_node
|
|
507
850
|
*/
|
|
@@ -710,13 +1053,36 @@ class ToolHandler {
|
|
|
710
1053
|
* Find a symbol by name, handling disambiguation when multiple matches exist.
|
|
711
1054
|
* Returns the best match and a note about alternatives if any.
|
|
712
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
|
+
}
|
|
713
1077
|
findSymbol(cg, symbol) {
|
|
714
|
-
|
|
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 });
|
|
715
1082
|
if (results.length === 0 || !results[0]) {
|
|
716
1083
|
return null;
|
|
717
1084
|
}
|
|
718
|
-
|
|
719
|
-
const exactMatches = results.filter(r => r.node.name === symbol);
|
|
1085
|
+
const exactMatches = results.filter(r => this.matchesSymbol(r.node, symbol));
|
|
720
1086
|
if (exactMatches.length === 1) {
|
|
721
1087
|
return { node: exactMatches[0].node, note: '' };
|
|
722
1088
|
}
|
|
@@ -730,6 +1096,24 @@ class ToolHandler {
|
|
|
730
1096
|
// No exact match, use best fuzzy match
|
|
731
1097
|
return { node: results[0].node, note: '' };
|
|
732
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
|
+
}
|
|
733
1117
|
/**
|
|
734
1118
|
* Truncate output if it exceeds the maximum length
|
|
735
1119
|
*/
|