@optave/codegraph 1.1.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -190
- package/README.md +498 -311
- package/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/grammars/tree-sitter-go.wasm +0 -0
- package/grammars/tree-sitter-hcl.wasm +0 -0
- package/grammars/tree-sitter-java.wasm +0 -0
- package/grammars/tree-sitter-javascript.wasm +0 -0
- package/grammars/tree-sitter-php.wasm +0 -0
- package/grammars/tree-sitter-python.wasm +0 -0
- package/grammars/tree-sitter-ruby.wasm +0 -0
- package/grammars/tree-sitter-rust.wasm +0 -0
- package/grammars/tree-sitter-tsx.wasm +0 -0
- package/grammars/tree-sitter-typescript.wasm +0 -0
- package/package.json +90 -69
- package/src/builder.js +161 -162
- package/src/cli.js +284 -224
- package/src/config.js +103 -55
- package/src/constants.js +41 -28
- package/src/cycles.js +125 -104
- package/src/db.js +129 -117
- package/src/embedder.js +253 -59
- package/src/export.js +150 -138
- package/src/index.js +50 -39
- package/src/logger.js +24 -20
- package/src/mcp.js +311 -139
- package/src/native.js +68 -0
- package/src/parser.js +2214 -573
- package/src/queries.js +334 -128
- package/src/resolve.js +171 -0
- package/src/watcher.js +81 -53
package/src/mcp.js
CHANGED
|
@@ -1,139 +1,311 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP (Model Context Protocol) server for codegraph.
|
|
3
|
-
* Exposes codegraph queries as tools that AI coding assistants can call.
|
|
4
|
-
*
|
|
5
|
-
* Requires: npm install @modelcontextprotocol/sdk
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { createRequire } from 'node:module';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
const TOOLS = [
|
|
13
|
-
{
|
|
14
|
-
name: 'query_function',
|
|
15
|
-
description: 'Find callers and callees of a function by name',
|
|
16
|
-
inputSchema: {
|
|
17
|
-
type: 'object',
|
|
18
|
-
properties: {
|
|
19
|
-
name: { type: 'string', description: 'Function name to query (supports partial match)' },
|
|
20
|
-
depth: {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* MCP (Model Context Protocol) server for codegraph.
|
|
3
|
+
* Exposes codegraph queries as tools that AI coding assistants can call.
|
|
4
|
+
*
|
|
5
|
+
* Requires: npm install @modelcontextprotocol/sdk
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import { findCycles } from './cycles.js';
|
|
10
|
+
import { findDbPath } from './db.js';
|
|
11
|
+
|
|
12
|
+
const TOOLS = [
|
|
13
|
+
{
|
|
14
|
+
name: 'query_function',
|
|
15
|
+
description: 'Find callers and callees of a function by name',
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
name: { type: 'string', description: 'Function name to query (supports partial match)' },
|
|
20
|
+
depth: {
|
|
21
|
+
type: 'number',
|
|
22
|
+
description: 'Traversal depth for transitive callers',
|
|
23
|
+
default: 2,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
required: ['name'],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'file_deps',
|
|
31
|
+
description: 'Show what a file imports and what imports it',
|
|
32
|
+
inputSchema: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
file: { type: 'string', description: 'File path (partial match supported)' },
|
|
36
|
+
},
|
|
37
|
+
required: ['file'],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'impact_analysis',
|
|
42
|
+
description: 'Show files affected by changes to a given file (transitive)',
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
file: { type: 'string', description: 'File path to analyze' },
|
|
47
|
+
},
|
|
48
|
+
required: ['file'],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'find_cycles',
|
|
53
|
+
description: 'Detect circular dependencies in the codebase',
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'module_map',
|
|
61
|
+
description: 'Get high-level overview of most-connected files',
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
limit: { type: 'number', description: 'Number of top files to show', default: 20 },
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'fn_deps',
|
|
71
|
+
description: 'Show function-level dependency chain: what a function calls and what calls it',
|
|
72
|
+
inputSchema: {
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
name: { type: 'string', description: 'Function/method/class name (partial match)' },
|
|
76
|
+
depth: { type: 'number', description: 'Transitive caller depth', default: 3 },
|
|
77
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
78
|
+
},
|
|
79
|
+
required: ['name'],
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'fn_impact',
|
|
84
|
+
description:
|
|
85
|
+
'Show function-level blast radius: all functions transitively affected by changes to a function',
|
|
86
|
+
inputSchema: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
properties: {
|
|
89
|
+
name: { type: 'string', description: 'Function/method/class name (partial match)' },
|
|
90
|
+
depth: { type: 'number', description: 'Max traversal depth', default: 5 },
|
|
91
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
92
|
+
},
|
|
93
|
+
required: ['name'],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'diff_impact',
|
|
98
|
+
description: 'Analyze git diff to find which functions changed and their transitive callers',
|
|
99
|
+
inputSchema: {
|
|
100
|
+
type: 'object',
|
|
101
|
+
properties: {
|
|
102
|
+
staged: { type: 'boolean', description: 'Analyze staged changes only', default: false },
|
|
103
|
+
ref: { type: 'string', description: 'Git ref to diff against (default: HEAD)' },
|
|
104
|
+
depth: { type: 'number', description: 'Transitive caller depth', default: 3 },
|
|
105
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'semantic_search',
|
|
111
|
+
description:
|
|
112
|
+
'Search code symbols by meaning using embeddings (requires prior `codegraph embed`)',
|
|
113
|
+
inputSchema: {
|
|
114
|
+
type: 'object',
|
|
115
|
+
properties: {
|
|
116
|
+
query: { type: 'string', description: 'Natural language search query' },
|
|
117
|
+
limit: { type: 'number', description: 'Max results to return', default: 15 },
|
|
118
|
+
min_score: { type: 'number', description: 'Minimum similarity score (0-1)', default: 0.2 },
|
|
119
|
+
},
|
|
120
|
+
required: ['query'],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'export_graph',
|
|
125
|
+
description: 'Export the dependency graph in DOT (Graphviz), Mermaid, or JSON format',
|
|
126
|
+
inputSchema: {
|
|
127
|
+
type: 'object',
|
|
128
|
+
properties: {
|
|
129
|
+
format: {
|
|
130
|
+
type: 'string',
|
|
131
|
+
enum: ['dot', 'mermaid', 'json'],
|
|
132
|
+
description: 'Export format',
|
|
133
|
+
},
|
|
134
|
+
file_level: {
|
|
135
|
+
type: 'boolean',
|
|
136
|
+
description: 'File-level graph (true) or function-level (false)',
|
|
137
|
+
default: true,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
required: ['format'],
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'list_functions',
|
|
145
|
+
description:
|
|
146
|
+
'List functions, methods, and classes in the codebase, optionally filtered by file or name pattern',
|
|
147
|
+
inputSchema: {
|
|
148
|
+
type: 'object',
|
|
149
|
+
properties: {
|
|
150
|
+
file: { type: 'string', description: 'Filter by file path (partial match)' },
|
|
151
|
+
pattern: { type: 'string', description: 'Filter by function name (partial match)' },
|
|
152
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
export { TOOLS };
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Start the MCP server.
|
|
162
|
+
* This function requires @modelcontextprotocol/sdk to be installed.
|
|
163
|
+
*/
|
|
164
|
+
export async function startMCPServer(customDbPath) {
|
|
165
|
+
let Server, StdioServerTransport;
|
|
166
|
+
try {
|
|
167
|
+
const sdk = await import('@modelcontextprotocol/sdk/server/index.js');
|
|
168
|
+
Server = sdk.Server;
|
|
169
|
+
const transport = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
|
170
|
+
StdioServerTransport = transport.StdioServerTransport;
|
|
171
|
+
} catch {
|
|
172
|
+
console.error(
|
|
173
|
+
'MCP server requires @modelcontextprotocol/sdk.\n' +
|
|
174
|
+
'Install it with: npm install @modelcontextprotocol/sdk',
|
|
175
|
+
);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Lazy import query functions to avoid circular deps at module load
|
|
180
|
+
const {
|
|
181
|
+
queryNameData,
|
|
182
|
+
impactAnalysisData,
|
|
183
|
+
moduleMapData,
|
|
184
|
+
fileDepsData,
|
|
185
|
+
fnDepsData,
|
|
186
|
+
fnImpactData,
|
|
187
|
+
diffImpactData,
|
|
188
|
+
listFunctionsData,
|
|
189
|
+
} = await import('./queries.js');
|
|
190
|
+
|
|
191
|
+
const require = createRequire(import.meta.url);
|
|
192
|
+
const Database = require('better-sqlite3');
|
|
193
|
+
|
|
194
|
+
const server = new Server(
|
|
195
|
+
{ name: 'codegraph', version: '1.0.0' },
|
|
196
|
+
{ capabilities: { tools: {} } },
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
server.setRequestHandler('tools/list', async () => ({ tools: TOOLS }));
|
|
200
|
+
|
|
201
|
+
server.setRequestHandler('tools/call', async (request) => {
|
|
202
|
+
const { name, arguments: args } = request.params;
|
|
203
|
+
const dbPath = customDbPath || undefined;
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
let result;
|
|
207
|
+
switch (name) {
|
|
208
|
+
case 'query_function':
|
|
209
|
+
result = queryNameData(args.name, dbPath);
|
|
210
|
+
break;
|
|
211
|
+
case 'file_deps':
|
|
212
|
+
result = fileDepsData(args.file, dbPath);
|
|
213
|
+
break;
|
|
214
|
+
case 'impact_analysis':
|
|
215
|
+
result = impactAnalysisData(args.file, dbPath);
|
|
216
|
+
break;
|
|
217
|
+
case 'find_cycles': {
|
|
218
|
+
const db = new Database(findDbPath(dbPath), { readonly: true });
|
|
219
|
+
const cycles = findCycles(db);
|
|
220
|
+
db.close();
|
|
221
|
+
result = { cycles, count: cycles.length };
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
case 'module_map':
|
|
225
|
+
result = moduleMapData(dbPath, args.limit || 20);
|
|
226
|
+
break;
|
|
227
|
+
case 'fn_deps':
|
|
228
|
+
result = fnDepsData(args.name, dbPath, {
|
|
229
|
+
depth: args.depth,
|
|
230
|
+
noTests: args.no_tests,
|
|
231
|
+
});
|
|
232
|
+
break;
|
|
233
|
+
case 'fn_impact':
|
|
234
|
+
result = fnImpactData(args.name, dbPath, {
|
|
235
|
+
depth: args.depth,
|
|
236
|
+
noTests: args.no_tests,
|
|
237
|
+
});
|
|
238
|
+
break;
|
|
239
|
+
case 'diff_impact':
|
|
240
|
+
result = diffImpactData(dbPath, {
|
|
241
|
+
staged: args.staged,
|
|
242
|
+
ref: args.ref,
|
|
243
|
+
depth: args.depth,
|
|
244
|
+
noTests: args.no_tests,
|
|
245
|
+
});
|
|
246
|
+
break;
|
|
247
|
+
case 'semantic_search': {
|
|
248
|
+
const { searchData } = await import('./embedder.js');
|
|
249
|
+
result = await searchData(args.query, dbPath, {
|
|
250
|
+
limit: args.limit,
|
|
251
|
+
minScore: args.min_score,
|
|
252
|
+
});
|
|
253
|
+
if (result === null) {
|
|
254
|
+
return {
|
|
255
|
+
content: [
|
|
256
|
+
{ type: 'text', text: 'Semantic search unavailable. Run `codegraph embed` first.' },
|
|
257
|
+
],
|
|
258
|
+
isError: true,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
case 'export_graph': {
|
|
264
|
+
const { exportDOT, exportMermaid, exportJSON } = await import('./export.js');
|
|
265
|
+
const db = new Database(findDbPath(dbPath), { readonly: true });
|
|
266
|
+
const fileLevel = args.file_level !== false;
|
|
267
|
+
switch (args.format) {
|
|
268
|
+
case 'dot':
|
|
269
|
+
result = exportDOT(db, { fileLevel });
|
|
270
|
+
break;
|
|
271
|
+
case 'mermaid':
|
|
272
|
+
result = exportMermaid(db, { fileLevel });
|
|
273
|
+
break;
|
|
274
|
+
case 'json':
|
|
275
|
+
result = exportJSON(db);
|
|
276
|
+
break;
|
|
277
|
+
default:
|
|
278
|
+
db.close();
|
|
279
|
+
return {
|
|
280
|
+
content: [
|
|
281
|
+
{
|
|
282
|
+
type: 'text',
|
|
283
|
+
text: `Unknown format: ${args.format}. Use dot, mermaid, or json.`,
|
|
284
|
+
},
|
|
285
|
+
],
|
|
286
|
+
isError: true,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
db.close();
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
case 'list_functions':
|
|
293
|
+
result = listFunctionsData(dbPath, {
|
|
294
|
+
file: args.file,
|
|
295
|
+
pattern: args.pattern,
|
|
296
|
+
noTests: args.no_tests,
|
|
297
|
+
});
|
|
298
|
+
break;
|
|
299
|
+
default:
|
|
300
|
+
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
304
|
+
} catch (err) {
|
|
305
|
+
return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const transport = new StdioServerTransport();
|
|
310
|
+
await server.connect(transport);
|
|
311
|
+
}
|
package/src/native.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native addon loader with graceful fallback to WASM.
|
|
3
|
+
*
|
|
4
|
+
* Tries to load the platform-specific napi-rs binary built from
|
|
5
|
+
* crates/codegraph-core. If unavailable the caller should fall back
|
|
6
|
+
* to the existing WASM pipeline.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
|
|
12
|
+
let _cached; // undefined = not yet tried, null = failed, object = module
|
|
13
|
+
let _loadError = null;
|
|
14
|
+
|
|
15
|
+
/** Map of (platform-arch) → npm package name. */
|
|
16
|
+
const PLATFORM_PACKAGES = {
|
|
17
|
+
'linux-x64': '@optave/codegraph-linux-x64-gnu',
|
|
18
|
+
'darwin-arm64': '@optave/codegraph-darwin-arm64',
|
|
19
|
+
'darwin-x64': '@optave/codegraph-darwin-x64',
|
|
20
|
+
'win32-x64': '@optave/codegraph-win32-x64-msvc',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Try to load the native napi addon.
|
|
25
|
+
* Returns the module on success, null on failure.
|
|
26
|
+
*/
|
|
27
|
+
export function loadNative() {
|
|
28
|
+
if (_cached !== undefined) return _cached;
|
|
29
|
+
|
|
30
|
+
const require = createRequire(import.meta.url);
|
|
31
|
+
|
|
32
|
+
const key = `${os.platform()}-${os.arch()}`;
|
|
33
|
+
const pkg = PLATFORM_PACKAGES[key];
|
|
34
|
+
if (pkg) {
|
|
35
|
+
try {
|
|
36
|
+
_cached = require(pkg);
|
|
37
|
+
return _cached;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
_loadError = err;
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
_loadError = new Error(`Unsupported platform: ${key}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_cached = null;
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check whether the native engine is available on this platform.
|
|
51
|
+
*/
|
|
52
|
+
export function isNativeAvailable() {
|
|
53
|
+
return loadNative() !== null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Return the native module or throw if not available.
|
|
58
|
+
*/
|
|
59
|
+
export function getNative() {
|
|
60
|
+
const mod = loadNative();
|
|
61
|
+
if (!mod) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Native codegraph-core not available: ${_loadError?.message || 'unknown error'}. ` +
|
|
64
|
+
'Install the platform package or use --engine wasm.',
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return mod;
|
|
68
|
+
}
|