@colbymchenry/codegraph 0.6.8 → 0.7.3
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 +531 -52
- 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 +56 -0
- package/dist/extraction/svelte-extractor.d.ts.map +1 -0
- package/dist/extraction/svelte-extractor.js +272 -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 +27 -3
- 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 +432 -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;IA2R3B;;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. IMPORTANT: Use specific symbol names, file names, or short code terms in your query — NOT natural language sentences. Before calling this, use codegraph_search to discover relevant symbol names, then include those names in your query. Bad: "how are agent prompts loaded and passed to the CLI". Good: "readAgentsFromDirectory createClaudeSession chat-manager agents.ts".',
|
|
230
|
+
inputSchema: {
|
|
231
|
+
type: 'object',
|
|
232
|
+
properties: {
|
|
233
|
+
query: {
|
|
234
|
+
type: 'string',
|
|
235
|
+
description: 'Symbol names, file names, or short code terms to explore (e.g., "AuthService loginUser session-manager", "GraphTraverser BFS impact traversal.ts"). Use codegraph_search first to find relevant names.',
|
|
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
|
+
}
|
|
462
533
|
}
|
|
463
|
-
|
|
464
|
-
|
|
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;
|
|
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
|
+
}
|
|
483
563
|
}
|
|
484
|
-
|
|
485
|
-
|
|
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;
|
|
486
568
|
return this.textResult(this.truncateOutput(formatted));
|
|
487
569
|
}
|
|
488
570
|
/**
|
|
@@ -494,14 +576,302 @@ 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
|
+
// Use a large maxNodes budget — explore has its own 35k char output limit
|
|
626
|
+
// that prevents context bloat, so more nodes just means better coverage
|
|
627
|
+
// across entry points (especially for large files like Svelte components).
|
|
628
|
+
const subgraph = await cg.findRelevantContext(query, {
|
|
629
|
+
searchLimit: 8,
|
|
630
|
+
traversalDepth: 3,
|
|
631
|
+
maxNodes: 200,
|
|
632
|
+
minScore: 0.2,
|
|
633
|
+
});
|
|
634
|
+
if (subgraph.nodes.size === 0) {
|
|
635
|
+
return this.textResult(`No relevant code found for "${query}"`);
|
|
636
|
+
}
|
|
637
|
+
// Step 2: Group nodes by file, score by relevance
|
|
638
|
+
const fileGroups = new Map();
|
|
639
|
+
const entryNodeIds = new Set(subgraph.roots);
|
|
640
|
+
// Build a set of nodes directly connected to entry points (depth 1)
|
|
641
|
+
const connectedToEntry = new Set();
|
|
642
|
+
for (const edge of subgraph.edges) {
|
|
643
|
+
if (entryNodeIds.has(edge.source))
|
|
644
|
+
connectedToEntry.add(edge.target);
|
|
645
|
+
if (entryNodeIds.has(edge.target))
|
|
646
|
+
connectedToEntry.add(edge.source);
|
|
647
|
+
}
|
|
648
|
+
for (const node of subgraph.nodes.values()) {
|
|
649
|
+
// Skip import/export nodes — they add noise without information
|
|
650
|
+
if (node.kind === 'import' || node.kind === 'export')
|
|
651
|
+
continue;
|
|
652
|
+
const group = fileGroups.get(node.filePath) || { nodes: [], score: 0 };
|
|
653
|
+
group.nodes.push(node);
|
|
654
|
+
// Score: entry point nodes worth 10, directly connected worth 3, others worth 1
|
|
655
|
+
if (entryNodeIds.has(node.id)) {
|
|
656
|
+
group.score += 10;
|
|
657
|
+
}
|
|
658
|
+
else if (connectedToEntry.has(node.id)) {
|
|
659
|
+
group.score += 3;
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
group.score += 1;
|
|
663
|
+
}
|
|
664
|
+
fileGroups.set(node.filePath, group);
|
|
665
|
+
}
|
|
666
|
+
// Only include files that have entry points or nodes directly connected to entry points
|
|
667
|
+
const relevantFiles = [...fileGroups.entries()].filter(([, group]) => group.score >= 3);
|
|
668
|
+
// Extract query terms for relevance checking
|
|
669
|
+
const queryTerms = query.toLowerCase().split(/\s+/).filter(t => t.length >= 3);
|
|
670
|
+
// Sort files: highest relevance first, deprioritize low-value files
|
|
671
|
+
const sortedFiles = relevantFiles.sort((a, b) => {
|
|
672
|
+
const aPath = a[0].toLowerCase();
|
|
673
|
+
const bPath = b[0].toLowerCase();
|
|
674
|
+
// Check if any node name or file path relates to query terms
|
|
675
|
+
const hasQueryRelevance = (filePath, nodes) => {
|
|
676
|
+
const fp = filePath.toLowerCase();
|
|
677
|
+
if (queryTerms.some(t => fp.includes(t)))
|
|
678
|
+
return true;
|
|
679
|
+
return nodes.some(n => queryTerms.some(t => n.name.toLowerCase().includes(t)));
|
|
680
|
+
};
|
|
681
|
+
const aRelevant = hasQueryRelevance(aPath, a[1].nodes);
|
|
682
|
+
const bRelevant = hasQueryRelevance(bPath, b[1].nodes);
|
|
683
|
+
if (aRelevant !== bRelevant)
|
|
684
|
+
return aRelevant ? -1 : 1;
|
|
685
|
+
// Deprioritize test files, icon files, and i18n files
|
|
686
|
+
const isLowValue = (p) => /\/(tests?|__tests?__|spec)\//i.test(p) ||
|
|
687
|
+
/\bicons?\b/i.test(p) ||
|
|
688
|
+
/\bi18n\b/i.test(p);
|
|
689
|
+
const aLow = isLowValue(aPath);
|
|
690
|
+
const bLow = isLowValue(bPath);
|
|
691
|
+
if (aLow !== bLow)
|
|
692
|
+
return aLow ? 1 : -1;
|
|
693
|
+
if (a[1].score !== b[1].score)
|
|
694
|
+
return b[1].score - a[1].score;
|
|
695
|
+
return b[1].nodes.length - a[1].nodes.length;
|
|
696
|
+
});
|
|
697
|
+
// Step 3: Build relationship map
|
|
698
|
+
const lines = [
|
|
699
|
+
`## Exploration: ${query}`,
|
|
700
|
+
'',
|
|
701
|
+
`Found ${subgraph.nodes.size} symbols across ${fileGroups.size} files.`,
|
|
702
|
+
'',
|
|
703
|
+
];
|
|
704
|
+
// Relationship map — show how symbols connect
|
|
705
|
+
const significantEdges = subgraph.edges.filter(e => e.kind !== 'contains' // skip contains — it's implied by file grouping
|
|
706
|
+
);
|
|
707
|
+
if (significantEdges.length > 0) {
|
|
708
|
+
lines.push('### Relationships');
|
|
709
|
+
lines.push('');
|
|
710
|
+
// Group edges by kind for readability
|
|
711
|
+
const byKind = new Map();
|
|
712
|
+
for (const edge of significantEdges) {
|
|
713
|
+
const sourceNode = subgraph.nodes.get(edge.source);
|
|
714
|
+
const targetNode = subgraph.nodes.get(edge.target);
|
|
715
|
+
if (!sourceNode || !targetNode)
|
|
716
|
+
continue;
|
|
717
|
+
const group = byKind.get(edge.kind) || [];
|
|
718
|
+
group.push({ source: sourceNode.name, target: targetNode.name });
|
|
719
|
+
byKind.set(edge.kind, group);
|
|
720
|
+
}
|
|
721
|
+
for (const [kind, edges] of byKind) {
|
|
722
|
+
// Show up to 15 relationships per kind
|
|
723
|
+
const shown = edges.slice(0, 15);
|
|
724
|
+
lines.push(`**${kind}:**`);
|
|
725
|
+
for (const e of shown) {
|
|
726
|
+
lines.push(`- ${e.source} → ${e.target}`);
|
|
727
|
+
}
|
|
728
|
+
if (edges.length > 15) {
|
|
729
|
+
lines.push(`- ... and ${edges.length - 15} more`);
|
|
730
|
+
}
|
|
731
|
+
lines.push('');
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
// Step 4: Read contiguous file sections
|
|
735
|
+
lines.push('### Source Code');
|
|
736
|
+
lines.push('');
|
|
737
|
+
let totalChars = lines.join('\n').length;
|
|
738
|
+
let filesIncluded = 0;
|
|
739
|
+
for (const [filePath, group] of sortedFiles) {
|
|
740
|
+
if (filesIncluded >= maxFiles)
|
|
741
|
+
break;
|
|
742
|
+
if (totalChars > ToolHandler.EXPLORE_MAX_OUTPUT * 0.9)
|
|
743
|
+
break;
|
|
744
|
+
const absPath = (0, utils_1.validatePathWithinRoot)(projectRoot, filePath);
|
|
745
|
+
if (!absPath || !(0, fs_1.existsSync)(absPath))
|
|
746
|
+
continue;
|
|
747
|
+
let fileContent;
|
|
748
|
+
try {
|
|
749
|
+
fileContent = (0, fs_1.readFileSync)(absPath, 'utf-8');
|
|
750
|
+
}
|
|
751
|
+
catch {
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
const fileLines = fileContent.split('\n');
|
|
755
|
+
const lang = group.nodes[0]?.language || '';
|
|
756
|
+
// Cluster nearby symbols to avoid reading huge gaps between distant symbols.
|
|
757
|
+
// Sort by start line, then merge overlapping/adjacent ranges (within 15 lines).
|
|
758
|
+
// Include both node ranges AND edge source locations so template sections
|
|
759
|
+
// with component usages/calls are covered (not just script block symbols).
|
|
760
|
+
const ranges = group.nodes
|
|
761
|
+
.filter(n => n.startLine > 0 && n.endLine > 0)
|
|
762
|
+
// Skip file/component nodes that span the entire file — they'd create one giant cluster
|
|
763
|
+
.filter(n => !(n.kind === 'component' && n.startLine === 1 && n.endLine >= fileLines.length - 1))
|
|
764
|
+
.map(n => ({ start: n.startLine, end: n.endLine, name: n.name, kind: n.kind }));
|
|
765
|
+
// Add edge source locations in this file — captures template references
|
|
766
|
+
// (component usages, event handlers) that aren't nodes themselves.
|
|
767
|
+
// Query edges directly from the DB (not just the subgraph) because BFS
|
|
768
|
+
// traversal may have pruned template reference targets due to node budget.
|
|
769
|
+
const edgeLines = new Set(); // dedup by "line:name"
|
|
770
|
+
for (const node of group.nodes) {
|
|
771
|
+
const outgoing = cg.getOutgoingEdges(node.id);
|
|
772
|
+
for (const edge of outgoing) {
|
|
773
|
+
if (!edge.line || edge.line <= 0 || edge.kind === 'contains')
|
|
774
|
+
continue;
|
|
775
|
+
const key = `${edge.line}:${edge.target}`;
|
|
776
|
+
if (edgeLines.has(key))
|
|
777
|
+
continue;
|
|
778
|
+
edgeLines.add(key);
|
|
779
|
+
// Look up target name from subgraph first, fall back to edge kind
|
|
780
|
+
const targetNode = subgraph.nodes.get(edge.target);
|
|
781
|
+
const targetName = targetNode?.name ?? edge.kind;
|
|
782
|
+
ranges.push({ start: edge.line, end: edge.line, name: targetName, kind: edge.kind });
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
ranges.sort((a, b) => a.start - b.start);
|
|
786
|
+
if (ranges.length === 0)
|
|
787
|
+
continue;
|
|
788
|
+
const GAP_THRESHOLD = 15; // merge sections within 15 lines of each other
|
|
789
|
+
const clusters = [];
|
|
790
|
+
let current = { start: ranges[0].start, end: ranges[0].end, symbols: [`${ranges[0].name}(${ranges[0].kind})`] };
|
|
791
|
+
for (let i = 1; i < ranges.length; i++) {
|
|
792
|
+
const r = ranges[i];
|
|
793
|
+
if (r.start <= current.end + GAP_THRESHOLD) {
|
|
794
|
+
current.end = Math.max(current.end, r.end);
|
|
795
|
+
current.symbols.push(`${r.name}(${r.kind})`);
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
clusters.push(current);
|
|
799
|
+
current = { start: r.start, end: r.end, symbols: [`${r.name}(${r.kind})`] };
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
clusters.push(current);
|
|
803
|
+
// Build file section output from clusters
|
|
804
|
+
const contextPadding = 3;
|
|
805
|
+
let fileSection = '';
|
|
806
|
+
const allSymbols = [];
|
|
807
|
+
for (const cluster of clusters) {
|
|
808
|
+
const startIdx = Math.max(0, cluster.start - 1 - contextPadding);
|
|
809
|
+
const endIdx = Math.min(fileLines.length, cluster.end + contextPadding);
|
|
810
|
+
const section = fileLines.slice(startIdx, endIdx).join('\n');
|
|
811
|
+
if (fileSection.length > 0) {
|
|
812
|
+
fileSection += '\n\n// ... (gap) ...\n\n';
|
|
813
|
+
}
|
|
814
|
+
fileSection += section;
|
|
815
|
+
allSymbols.push(...cluster.symbols);
|
|
816
|
+
}
|
|
817
|
+
// Skip if this section would blow the output limit
|
|
818
|
+
if (totalChars + fileSection.length + 200 > ToolHandler.EXPLORE_MAX_OUTPUT) {
|
|
819
|
+
const budget = ToolHandler.EXPLORE_MAX_OUTPUT - totalChars - 200;
|
|
820
|
+
if (budget < 500)
|
|
821
|
+
break;
|
|
822
|
+
const trimmed = fileSection.slice(0, budget) + '\n// ... trimmed ...';
|
|
823
|
+
lines.push(`#### ${filePath} — ${allSymbols.join(', ')}`);
|
|
824
|
+
lines.push('');
|
|
825
|
+
lines.push('```' + lang);
|
|
826
|
+
lines.push(trimmed);
|
|
827
|
+
lines.push('```');
|
|
828
|
+
lines.push('');
|
|
829
|
+
totalChars += trimmed.length + 200;
|
|
830
|
+
filesIncluded++;
|
|
831
|
+
break;
|
|
832
|
+
}
|
|
833
|
+
lines.push(`#### ${filePath} — ${allSymbols.join(', ')}`);
|
|
834
|
+
lines.push('');
|
|
835
|
+
lines.push('```' + lang);
|
|
836
|
+
lines.push(fileSection);
|
|
837
|
+
lines.push('```');
|
|
838
|
+
lines.push('');
|
|
839
|
+
totalChars += fileSection.length + 200;
|
|
840
|
+
filesIncluded++;
|
|
841
|
+
}
|
|
842
|
+
// Add remaining files as references (from both relevant and peripheral files)
|
|
843
|
+
const remainingRelevant = sortedFiles.slice(filesIncluded);
|
|
844
|
+
const peripheralFiles = [...fileGroups.entries()]
|
|
845
|
+
.filter(([, group]) => group.score < 3)
|
|
846
|
+
.sort((a, b) => b[1].score - a[1].score);
|
|
847
|
+
const remainingFiles = [...remainingRelevant, ...peripheralFiles];
|
|
848
|
+
if (remainingFiles.length > 0) {
|
|
849
|
+
lines.push('### Additional relevant files (not shown)');
|
|
850
|
+
lines.push('');
|
|
851
|
+
for (const [filePath, group] of remainingFiles.slice(0, 10)) {
|
|
852
|
+
const symbols = group.nodes.map(n => `${n.name}:${n.startLine}`).join(', ');
|
|
853
|
+
lines.push(`- ${filePath}: ${symbols}`);
|
|
854
|
+
}
|
|
855
|
+
if (remainingFiles.length > 10) {
|
|
856
|
+
lines.push(`- ... and ${remainingFiles.length - 10} more files`);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
// Add completeness signal so agents know they don't need to re-read these files
|
|
860
|
+
lines.push('');
|
|
861
|
+
lines.push('---');
|
|
862
|
+
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.`);
|
|
863
|
+
// Add explore budget note based on project size
|
|
864
|
+
try {
|
|
865
|
+
const stats = cg.getStats();
|
|
866
|
+
const budget = getExploreBudget(stats.fileCount);
|
|
867
|
+
lines.push('');
|
|
868
|
+
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.`);
|
|
869
|
+
}
|
|
870
|
+
catch {
|
|
871
|
+
// Stats unavailable — skip budget note
|
|
872
|
+
}
|
|
873
|
+
return this.textResult(lines.join('\n'));
|
|
874
|
+
}
|
|
505
875
|
/**
|
|
506
876
|
* Handle codegraph_node
|
|
507
877
|
*/
|
|
@@ -710,13 +1080,36 @@ class ToolHandler {
|
|
|
710
1080
|
* Find a symbol by name, handling disambiguation when multiple matches exist.
|
|
711
1081
|
* Returns the best match and a note about alternatives if any.
|
|
712
1082
|
*/
|
|
1083
|
+
/**
|
|
1084
|
+
* Check if a node matches a symbol query, supporting both simple names and
|
|
1085
|
+
* qualified "Parent.child" notation (e.g., "Session.request" matches a method
|
|
1086
|
+
* named "request" inside a class named "Session").
|
|
1087
|
+
*/
|
|
1088
|
+
matchesSymbol(node, symbol) {
|
|
1089
|
+
// Simple name match
|
|
1090
|
+
if (node.name === symbol)
|
|
1091
|
+
return true;
|
|
1092
|
+
// File basename match (e.g., "product-card" matches "product-card.liquid")
|
|
1093
|
+
if (node.kind === 'file' && node.name.replace(/\.[^.]+$/, '') === symbol)
|
|
1094
|
+
return true;
|
|
1095
|
+
// Qualified name match: "Parent.child" → look for "::Parent::child" in qualified_name
|
|
1096
|
+
if (symbol.includes('.')) {
|
|
1097
|
+
const parts = symbol.split('.');
|
|
1098
|
+
const qualifiedSuffix = parts.join('::');
|
|
1099
|
+
if (node.qualifiedName.includes(qualifiedSuffix))
|
|
1100
|
+
return true;
|
|
1101
|
+
}
|
|
1102
|
+
return false;
|
|
1103
|
+
}
|
|
713
1104
|
findSymbol(cg, symbol) {
|
|
714
|
-
|
|
1105
|
+
// Use higher limit for qualified lookups (e.g., "Session.request") since the
|
|
1106
|
+
// target may rank lower in FTS when there are many partial matches
|
|
1107
|
+
const limit = symbol.includes('.') ? 50 : 10;
|
|
1108
|
+
const results = cg.searchNodes(symbol, { limit });
|
|
715
1109
|
if (results.length === 0 || !results[0]) {
|
|
716
1110
|
return null;
|
|
717
1111
|
}
|
|
718
|
-
|
|
719
|
-
const exactMatches = results.filter(r => r.node.name === symbol);
|
|
1112
|
+
const exactMatches = results.filter(r => this.matchesSymbol(r.node, symbol));
|
|
720
1113
|
if (exactMatches.length === 1) {
|
|
721
1114
|
return { node: exactMatches[0].node, note: '' };
|
|
722
1115
|
}
|
|
@@ -730,6 +1123,24 @@ class ToolHandler {
|
|
|
730
1123
|
// No exact match, use best fuzzy match
|
|
731
1124
|
return { node: results[0].node, note: '' };
|
|
732
1125
|
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Find ALL symbols matching a name. Used by callers/callees/impact to aggregate
|
|
1128
|
+
* results across all matching symbols (e.g., multiple classes with an `execute` method).
|
|
1129
|
+
*/
|
|
1130
|
+
findAllSymbols(cg, symbol) {
|
|
1131
|
+
const results = cg.searchNodes(symbol, { limit: 50 });
|
|
1132
|
+
if (results.length === 0) {
|
|
1133
|
+
return { nodes: [], note: '' };
|
|
1134
|
+
}
|
|
1135
|
+
const exactMatches = results.filter(r => this.matchesSymbol(r.node, symbol));
|
|
1136
|
+
if (exactMatches.length <= 1) {
|
|
1137
|
+
const node = exactMatches[0]?.node ?? results[0].node;
|
|
1138
|
+
return { nodes: [node], note: '' };
|
|
1139
|
+
}
|
|
1140
|
+
const locations = exactMatches.map(r => `${r.node.kind} at ${r.node.filePath}:${r.node.startLine}`);
|
|
1141
|
+
const note = `\n\n> **Note:** Aggregated results across ${exactMatches.length} symbols named "${symbol}": ${locations.join(', ')}`;
|
|
1142
|
+
return { nodes: exactMatches.map(r => r.node), note };
|
|
1143
|
+
}
|
|
733
1144
|
/**
|
|
734
1145
|
* Truncate output if it exceeds the maximum length
|
|
735
1146
|
*/
|