@optave/codegraph 2.5.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +216 -89
- package/package.json +8 -7
- package/src/ast.js +392 -0
- package/src/audit.js +423 -0
- package/src/batch.js +180 -0
- package/src/boundaries.js +346 -0
- package/src/builder.js +375 -92
- package/src/cfg.js +1451 -0
- package/src/change-journal.js +130 -0
- package/src/check.js +432 -0
- package/src/cli.js +734 -107
- package/src/cochange.js +5 -2
- package/src/communities.js +7 -1
- package/src/complexity.js +124 -17
- package/src/config.js +10 -0
- package/src/dataflow.js +1187 -0
- package/src/db.js +96 -0
- package/src/embedder.js +359 -47
- package/src/export.js +305 -0
- package/src/extractors/csharp.js +64 -1
- package/src/extractors/go.js +66 -1
- package/src/extractors/hcl.js +22 -0
- package/src/extractors/java.js +61 -1
- package/src/extractors/javascript.js +142 -0
- package/src/extractors/php.js +79 -0
- package/src/extractors/python.js +134 -0
- package/src/extractors/ruby.js +89 -0
- package/src/extractors/rust.js +71 -1
- package/src/flow.js +4 -4
- package/src/index.js +78 -3
- package/src/manifesto.js +69 -1
- package/src/mcp.js +702 -193
- package/src/owners.js +359 -0
- package/src/paginate.js +37 -2
- package/src/parser.js +8 -0
- package/src/queries.js +590 -50
- package/src/snapshot.js +149 -0
- package/src/structure.js +9 -3
- package/src/triage.js +273 -0
- package/src/viewer.js +948 -0
- package/src/watcher.js +36 -1
package/src/mcp.js
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { createRequire } from 'node:module';
|
|
9
|
+
import { AST_NODE_KINDS } from './ast.js';
|
|
9
10
|
import { findCycles } from './cycles.js';
|
|
10
11
|
import { findDbPath } from './db.js';
|
|
11
12
|
import { MCP_DEFAULTS, MCP_MAX_LIMIT } from './paginate.js';
|
|
12
|
-
import {
|
|
13
|
+
import { diffImpactMermaid, EVERY_EDGE_KIND, EVERY_SYMBOL_KIND, VALID_ROLES } from './queries.js';
|
|
13
14
|
|
|
14
15
|
const REPO_PROP = {
|
|
15
16
|
repo: {
|
|
@@ -25,23 +26,71 @@ const PAGINATION_PROPS = {
|
|
|
25
26
|
|
|
26
27
|
const BASE_TOOLS = [
|
|
27
28
|
{
|
|
28
|
-
name: '
|
|
29
|
-
description:
|
|
29
|
+
name: 'query',
|
|
30
|
+
description:
|
|
31
|
+
'Query the call graph: find callers/callees with transitive chain, or find shortest path between two symbols',
|
|
30
32
|
inputSchema: {
|
|
31
33
|
type: 'object',
|
|
32
34
|
properties: {
|
|
33
|
-
name: { type: 'string', description: 'Function name
|
|
35
|
+
name: { type: 'string', description: 'Function/method/class name (partial match)' },
|
|
36
|
+
mode: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
enum: ['deps', 'path'],
|
|
39
|
+
description: 'deps (default): dependency chain. path: shortest path to target',
|
|
40
|
+
},
|
|
34
41
|
depth: {
|
|
35
42
|
type: 'number',
|
|
36
|
-
description: '
|
|
37
|
-
|
|
43
|
+
description: 'Transitive depth (deps default: 3, path default: 10)',
|
|
44
|
+
},
|
|
45
|
+
file: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
description: 'Scope search to functions in this file (partial match)',
|
|
48
|
+
},
|
|
49
|
+
kind: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
enum: EVERY_SYMBOL_KIND,
|
|
52
|
+
description: 'Filter by symbol kind',
|
|
53
|
+
},
|
|
54
|
+
to: { type: 'string', description: 'Target symbol for path mode (required in path mode)' },
|
|
55
|
+
edge_kinds: {
|
|
56
|
+
type: 'array',
|
|
57
|
+
items: { type: 'string', enum: EVERY_EDGE_KIND },
|
|
58
|
+
description: 'Edge kinds to follow in path mode (default: ["calls"])',
|
|
59
|
+
},
|
|
60
|
+
reverse: {
|
|
61
|
+
type: 'boolean',
|
|
62
|
+
description: 'Follow edges backward in path mode',
|
|
63
|
+
default: false,
|
|
38
64
|
},
|
|
65
|
+
from_file: { type: 'string', description: 'Disambiguate source by file in path mode' },
|
|
66
|
+
to_file: { type: 'string', description: 'Disambiguate target by file in path mode' },
|
|
39
67
|
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
40
68
|
...PAGINATION_PROPS,
|
|
41
69
|
},
|
|
42
70
|
required: ['name'],
|
|
43
71
|
},
|
|
44
72
|
},
|
|
73
|
+
{
|
|
74
|
+
name: 'path',
|
|
75
|
+
description: 'Find shortest path between two symbols in the dependency graph',
|
|
76
|
+
inputSchema: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: {
|
|
79
|
+
from: { type: 'string', description: 'Source symbol name' },
|
|
80
|
+
to: { type: 'string', description: 'Target symbol name' },
|
|
81
|
+
depth: { type: 'number', description: 'Max traversal depth (default: 10)' },
|
|
82
|
+
edge_kinds: {
|
|
83
|
+
type: 'array',
|
|
84
|
+
items: { type: 'string', enum: EVERY_EDGE_KIND },
|
|
85
|
+
description: 'Edge kinds to follow (default: ["calls"])',
|
|
86
|
+
},
|
|
87
|
+
from_file: { type: 'string', description: 'Disambiguate source by file' },
|
|
88
|
+
to_file: { type: 'string', description: 'Disambiguate target by file' },
|
|
89
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
90
|
+
},
|
|
91
|
+
required: ['from', 'to'],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
45
94
|
{
|
|
46
95
|
name: 'file_deps',
|
|
47
96
|
description: 'Show what a file imports and what imports it',
|
|
@@ -50,6 +99,21 @@ const BASE_TOOLS = [
|
|
|
50
99
|
properties: {
|
|
51
100
|
file: { type: 'string', description: 'File path (partial match supported)' },
|
|
52
101
|
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
102
|
+
...PAGINATION_PROPS,
|
|
103
|
+
},
|
|
104
|
+
required: ['file'],
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'file_exports',
|
|
109
|
+
description:
|
|
110
|
+
'Show exported symbols of a file with per-symbol consumers — who calls each export and from where',
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: 'object',
|
|
113
|
+
properties: {
|
|
114
|
+
file: { type: 'string', description: 'File path (partial match supported)' },
|
|
115
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
116
|
+
...PAGINATION_PROPS,
|
|
53
117
|
},
|
|
54
118
|
required: ['file'],
|
|
55
119
|
},
|
|
@@ -62,6 +126,7 @@ const BASE_TOOLS = [
|
|
|
62
126
|
properties: {
|
|
63
127
|
file: { type: 'string', description: 'File path to analyze' },
|
|
64
128
|
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
129
|
+
...PAGINATION_PROPS,
|
|
65
130
|
},
|
|
66
131
|
required: ['file'],
|
|
67
132
|
},
|
|
@@ -85,28 +150,6 @@ const BASE_TOOLS = [
|
|
|
85
150
|
},
|
|
86
151
|
},
|
|
87
152
|
},
|
|
88
|
-
{
|
|
89
|
-
name: 'fn_deps',
|
|
90
|
-
description: 'Show function-level dependency chain: what a function calls and what calls it',
|
|
91
|
-
inputSchema: {
|
|
92
|
-
type: 'object',
|
|
93
|
-
properties: {
|
|
94
|
-
name: { type: 'string', description: 'Function/method/class name (partial match)' },
|
|
95
|
-
depth: { type: 'number', description: 'Transitive caller depth', default: 3 },
|
|
96
|
-
file: {
|
|
97
|
-
type: 'string',
|
|
98
|
-
description: 'Scope search to functions in this file (partial match)',
|
|
99
|
-
},
|
|
100
|
-
kind: {
|
|
101
|
-
type: 'string',
|
|
102
|
-
enum: ALL_SYMBOL_KINDS,
|
|
103
|
-
description: 'Filter to a specific symbol kind',
|
|
104
|
-
},
|
|
105
|
-
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
106
|
-
},
|
|
107
|
-
required: ['name'],
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
153
|
{
|
|
111
154
|
name: 'fn_impact',
|
|
112
155
|
description:
|
|
@@ -122,41 +165,15 @@ const BASE_TOOLS = [
|
|
|
122
165
|
},
|
|
123
166
|
kind: {
|
|
124
167
|
type: 'string',
|
|
125
|
-
enum:
|
|
168
|
+
enum: EVERY_SYMBOL_KIND,
|
|
126
169
|
description: 'Filter to a specific symbol kind',
|
|
127
170
|
},
|
|
128
171
|
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
172
|
+
...PAGINATION_PROPS,
|
|
129
173
|
},
|
|
130
174
|
required: ['name'],
|
|
131
175
|
},
|
|
132
176
|
},
|
|
133
|
-
{
|
|
134
|
-
name: 'symbol_path',
|
|
135
|
-
description: 'Find the shortest path between two symbols in the call graph (A calls...calls B)',
|
|
136
|
-
inputSchema: {
|
|
137
|
-
type: 'object',
|
|
138
|
-
properties: {
|
|
139
|
-
from: { type: 'string', description: 'Source symbol name (partial match)' },
|
|
140
|
-
to: { type: 'string', description: 'Target symbol name (partial match)' },
|
|
141
|
-
max_depth: { type: 'number', description: 'Maximum BFS depth', default: 10 },
|
|
142
|
-
edge_kinds: {
|
|
143
|
-
type: 'array',
|
|
144
|
-
items: { type: 'string' },
|
|
145
|
-
description: 'Edge kinds to follow (default: ["calls"])',
|
|
146
|
-
},
|
|
147
|
-
reverse: { type: 'boolean', description: 'Follow edges backward', default: false },
|
|
148
|
-
from_file: { type: 'string', description: 'Disambiguate source by file (partial match)' },
|
|
149
|
-
to_file: { type: 'string', description: 'Disambiguate target by file (partial match)' },
|
|
150
|
-
kind: {
|
|
151
|
-
type: 'string',
|
|
152
|
-
enum: ALL_SYMBOL_KINDS,
|
|
153
|
-
description: 'Filter both symbols by kind',
|
|
154
|
-
},
|
|
155
|
-
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
156
|
-
},
|
|
157
|
-
required: ['from', 'to'],
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
177
|
{
|
|
161
178
|
name: 'context',
|
|
162
179
|
description:
|
|
@@ -176,7 +193,7 @@ const BASE_TOOLS = [
|
|
|
176
193
|
},
|
|
177
194
|
kind: {
|
|
178
195
|
type: 'string',
|
|
179
|
-
enum:
|
|
196
|
+
enum: EVERY_SYMBOL_KIND,
|
|
180
197
|
description: 'Filter to a specific symbol kind',
|
|
181
198
|
},
|
|
182
199
|
no_source: {
|
|
@@ -190,21 +207,25 @@ const BASE_TOOLS = [
|
|
|
190
207
|
description: 'Include test file source code',
|
|
191
208
|
default: false,
|
|
192
209
|
},
|
|
210
|
+
...PAGINATION_PROPS,
|
|
193
211
|
},
|
|
194
212
|
required: ['name'],
|
|
195
213
|
},
|
|
196
214
|
},
|
|
197
215
|
{
|
|
198
|
-
name: '
|
|
216
|
+
name: 'symbol_children',
|
|
199
217
|
description:
|
|
200
|
-
'
|
|
218
|
+
'List sub-declaration children of a symbol: parameters, properties, constants. Answers "what fields does this class have?" without reading source.',
|
|
201
219
|
inputSchema: {
|
|
202
220
|
type: 'object',
|
|
203
221
|
properties: {
|
|
204
|
-
|
|
222
|
+
name: { type: 'string', description: 'Function/method/class name (partial match)' },
|
|
223
|
+
file: { type: 'string', description: 'Scope to file (partial match)' },
|
|
224
|
+
kind: { type: 'string', enum: EVERY_SYMBOL_KIND, description: 'Filter by symbol kind' },
|
|
205
225
|
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
226
|
+
...PAGINATION_PROPS,
|
|
206
227
|
},
|
|
207
|
-
required: ['
|
|
228
|
+
required: ['name'],
|
|
208
229
|
},
|
|
209
230
|
},
|
|
210
231
|
{
|
|
@@ -241,32 +262,41 @@ const BASE_TOOLS = [
|
|
|
241
262
|
enum: ['json', 'mermaid'],
|
|
242
263
|
description: 'Output format (default: json)',
|
|
243
264
|
},
|
|
265
|
+
...PAGINATION_PROPS,
|
|
244
266
|
},
|
|
245
267
|
},
|
|
246
268
|
},
|
|
247
269
|
{
|
|
248
270
|
name: 'semantic_search',
|
|
249
271
|
description:
|
|
250
|
-
'Search code symbols by meaning using embeddings (requires prior `codegraph embed`)',
|
|
272
|
+
'Search code symbols by meaning using embeddings and/or keyword matching (requires prior `codegraph embed`). Default hybrid mode combines BM25 keyword + semantic search for best results.',
|
|
251
273
|
inputSchema: {
|
|
252
274
|
type: 'object',
|
|
253
275
|
properties: {
|
|
254
276
|
query: { type: 'string', description: 'Natural language search query' },
|
|
255
277
|
limit: { type: 'number', description: 'Max results to return', default: 15 },
|
|
256
278
|
min_score: { type: 'number', description: 'Minimum similarity score (0-1)', default: 0.2 },
|
|
279
|
+
mode: {
|
|
280
|
+
type: 'string',
|
|
281
|
+
enum: ['hybrid', 'semantic', 'keyword'],
|
|
282
|
+
description:
|
|
283
|
+
'Search mode: hybrid (BM25 + semantic, default), semantic (embeddings only), keyword (BM25 only)',
|
|
284
|
+
},
|
|
285
|
+
...PAGINATION_PROPS,
|
|
257
286
|
},
|
|
258
287
|
required: ['query'],
|
|
259
288
|
},
|
|
260
289
|
},
|
|
261
290
|
{
|
|
262
291
|
name: 'export_graph',
|
|
263
|
-
description:
|
|
292
|
+
description:
|
|
293
|
+
'Export the dependency graph in DOT, Mermaid, JSON, GraphML, GraphSON, or Neo4j CSV format',
|
|
264
294
|
inputSchema: {
|
|
265
295
|
type: 'object',
|
|
266
296
|
properties: {
|
|
267
297
|
format: {
|
|
268
298
|
type: 'string',
|
|
269
|
-
enum: ['dot', 'mermaid', 'json'],
|
|
299
|
+
enum: ['dot', 'mermaid', 'json', 'graphml', 'graphson', 'neo4j'],
|
|
270
300
|
description: 'Export format',
|
|
271
301
|
},
|
|
272
302
|
file_level: {
|
|
@@ -312,6 +342,7 @@ const BASE_TOOLS = [
|
|
|
312
342
|
description: 'Return all files without limit',
|
|
313
343
|
default: false,
|
|
314
344
|
},
|
|
345
|
+
...PAGINATION_PROPS,
|
|
315
346
|
},
|
|
316
347
|
},
|
|
317
348
|
},
|
|
@@ -333,28 +364,6 @@ const BASE_TOOLS = [
|
|
|
333
364
|
},
|
|
334
365
|
},
|
|
335
366
|
},
|
|
336
|
-
{
|
|
337
|
-
name: 'hotspots',
|
|
338
|
-
description:
|
|
339
|
-
'Find structural hotspots: files or directories with extreme fan-in, fan-out, or symbol density',
|
|
340
|
-
inputSchema: {
|
|
341
|
-
type: 'object',
|
|
342
|
-
properties: {
|
|
343
|
-
metric: {
|
|
344
|
-
type: 'string',
|
|
345
|
-
enum: ['fan-in', 'fan-out', 'density', 'coupling'],
|
|
346
|
-
description: 'Metric to rank by',
|
|
347
|
-
},
|
|
348
|
-
level: {
|
|
349
|
-
type: 'string',
|
|
350
|
-
enum: ['file', 'directory'],
|
|
351
|
-
description: 'Rank files or directories',
|
|
352
|
-
},
|
|
353
|
-
limit: { type: 'number', description: 'Number of results to return', default: 10 },
|
|
354
|
-
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
355
|
-
},
|
|
356
|
-
},
|
|
357
|
-
},
|
|
358
367
|
{
|
|
359
368
|
name: 'co_changes',
|
|
360
369
|
description:
|
|
@@ -373,20 +382,26 @@ const BASE_TOOLS = [
|
|
|
373
382
|
default: 0.3,
|
|
374
383
|
},
|
|
375
384
|
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
385
|
+
offset: { type: 'number', description: 'Skip this many results (pagination, default: 0)' },
|
|
376
386
|
},
|
|
377
387
|
},
|
|
378
388
|
},
|
|
379
389
|
{
|
|
380
390
|
name: 'execution_flow',
|
|
381
391
|
description:
|
|
382
|
-
'Trace execution flow forward from an entry point
|
|
392
|
+
'Trace execution flow forward from an entry point through callees to leaves, or list all entry points with list=true',
|
|
383
393
|
inputSchema: {
|
|
384
394
|
type: 'object',
|
|
385
395
|
properties: {
|
|
386
396
|
name: {
|
|
387
397
|
type: 'string',
|
|
388
398
|
description:
|
|
389
|
-
'Entry point or function name (
|
|
399
|
+
'Entry point or function name (required unless list=true). Supports prefix-stripped matching.',
|
|
400
|
+
},
|
|
401
|
+
list: {
|
|
402
|
+
type: 'boolean',
|
|
403
|
+
description: 'List all entry points grouped by type',
|
|
404
|
+
default: false,
|
|
390
405
|
},
|
|
391
406
|
depth: { type: 'number', description: 'Max forward traversal depth', default: 10 },
|
|
392
407
|
file: {
|
|
@@ -395,22 +410,10 @@ const BASE_TOOLS = [
|
|
|
395
410
|
},
|
|
396
411
|
kind: {
|
|
397
412
|
type: 'string',
|
|
398
|
-
enum:
|
|
413
|
+
enum: EVERY_SYMBOL_KIND,
|
|
399
414
|
description: 'Filter to a specific symbol kind',
|
|
400
415
|
},
|
|
401
416
|
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
402
|
-
},
|
|
403
|
-
required: ['name'],
|
|
404
|
-
},
|
|
405
|
-
},
|
|
406
|
-
{
|
|
407
|
-
name: 'list_entry_points',
|
|
408
|
-
description:
|
|
409
|
-
'List all framework entry points (routes, commands, events) in the codebase, grouped by type',
|
|
410
|
-
inputSchema: {
|
|
411
|
-
type: 'object',
|
|
412
|
-
properties: {
|
|
413
|
-
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
414
417
|
...PAGINATION_PROPS,
|
|
415
418
|
},
|
|
416
419
|
},
|
|
@@ -446,48 +449,284 @@ const BASE_TOOLS = [
|
|
|
446
449
|
type: 'string',
|
|
447
450
|
description: 'Filter by symbol kind (function, method, class, etc.)',
|
|
448
451
|
},
|
|
452
|
+
offset: { type: 'number', description: 'Skip this many results (pagination, default: 0)' },
|
|
449
453
|
},
|
|
450
454
|
},
|
|
451
455
|
},
|
|
452
456
|
{
|
|
453
|
-
name: '
|
|
457
|
+
name: 'communities',
|
|
454
458
|
description:
|
|
455
|
-
'
|
|
459
|
+
'Detect natural module boundaries using Louvain community detection. Compares discovered communities against directory structure and surfaces architectural drift.',
|
|
456
460
|
inputSchema: {
|
|
457
461
|
type: 'object',
|
|
458
462
|
properties: {
|
|
459
|
-
|
|
463
|
+
functions: {
|
|
464
|
+
type: 'boolean',
|
|
465
|
+
description: 'Function-level instead of file-level',
|
|
466
|
+
default: false,
|
|
467
|
+
},
|
|
468
|
+
resolution: {
|
|
469
|
+
type: 'number',
|
|
470
|
+
description: 'Louvain resolution parameter (higher = more communities)',
|
|
471
|
+
default: 1.0,
|
|
472
|
+
},
|
|
473
|
+
drift: {
|
|
474
|
+
type: 'boolean',
|
|
475
|
+
description: 'Show only drift analysis (omit community member lists)',
|
|
476
|
+
default: false,
|
|
477
|
+
},
|
|
460
478
|
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
479
|
+
...PAGINATION_PROPS,
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
name: 'code_owners',
|
|
485
|
+
description:
|
|
486
|
+
'Show CODEOWNERS mapping for files and functions. Shows ownership coverage, per-owner breakdown, and cross-owner boundary edges.',
|
|
487
|
+
inputSchema: {
|
|
488
|
+
type: 'object',
|
|
489
|
+
properties: {
|
|
490
|
+
file: { type: 'string', description: 'Scope to a specific file (partial match)' },
|
|
491
|
+
owner: { type: 'string', description: 'Filter to a specific owner (e.g. @team-name)' },
|
|
492
|
+
boundary: {
|
|
493
|
+
type: 'boolean',
|
|
494
|
+
description: 'Show cross-owner boundary edges',
|
|
495
|
+
default: false,
|
|
496
|
+
},
|
|
461
497
|
kind: {
|
|
462
498
|
type: 'string',
|
|
463
499
|
description: 'Filter by symbol kind (function, method, class, etc.)',
|
|
464
500
|
},
|
|
501
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
465
502
|
},
|
|
466
503
|
},
|
|
467
504
|
},
|
|
468
505
|
{
|
|
469
|
-
name: '
|
|
506
|
+
name: 'audit',
|
|
470
507
|
description:
|
|
471
|
-
'
|
|
508
|
+
'Composite report combining explain, fn-impact, and health metrics for a file or function. Returns structure, blast radius, complexity, and threshold breaches in one call.',
|
|
472
509
|
inputSchema: {
|
|
473
510
|
type: 'object',
|
|
474
511
|
properties: {
|
|
475
|
-
|
|
512
|
+
target: { type: 'string', description: 'File path or function name' },
|
|
513
|
+
quick: {
|
|
476
514
|
type: 'boolean',
|
|
477
|
-
description: '
|
|
515
|
+
description: 'Structural summary only (skip impact + health)',
|
|
478
516
|
default: false,
|
|
479
517
|
},
|
|
480
|
-
|
|
518
|
+
depth: { type: 'number', description: 'Impact analysis depth (default: 3)', default: 3 },
|
|
519
|
+
file: { type: 'string', description: 'Scope to file (partial match)' },
|
|
520
|
+
kind: {
|
|
521
|
+
type: 'string',
|
|
522
|
+
description: 'Filter by symbol kind (function, method, class, etc.)',
|
|
523
|
+
},
|
|
524
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
525
|
+
...PAGINATION_PROPS,
|
|
526
|
+
},
|
|
527
|
+
required: ['target'],
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
name: 'batch_query',
|
|
532
|
+
description:
|
|
533
|
+
'Run a query command against multiple targets in one call. Returns all results in a single JSON payload — ideal for multi-agent dispatch.',
|
|
534
|
+
inputSchema: {
|
|
535
|
+
type: 'object',
|
|
536
|
+
properties: {
|
|
537
|
+
command: {
|
|
538
|
+
type: 'string',
|
|
539
|
+
enum: [
|
|
540
|
+
'fn-impact',
|
|
541
|
+
'context',
|
|
542
|
+
'explain',
|
|
543
|
+
'where',
|
|
544
|
+
'query',
|
|
545
|
+
'impact',
|
|
546
|
+
'deps',
|
|
547
|
+
'flow',
|
|
548
|
+
'dataflow',
|
|
549
|
+
'complexity',
|
|
550
|
+
],
|
|
551
|
+
description: 'The query command to run for each target',
|
|
552
|
+
},
|
|
553
|
+
targets: {
|
|
554
|
+
type: 'array',
|
|
555
|
+
items: { type: 'string' },
|
|
556
|
+
description: 'List of target names (symbol names or file paths depending on command)',
|
|
557
|
+
},
|
|
558
|
+
depth: {
|
|
481
559
|
type: 'number',
|
|
482
|
-
description: '
|
|
483
|
-
default: 1.0,
|
|
560
|
+
description: 'Traversal depth (for fn-impact, context, fn, flow)',
|
|
484
561
|
},
|
|
485
|
-
|
|
562
|
+
file: {
|
|
563
|
+
type: 'string',
|
|
564
|
+
description: 'Scope to file (partial match)',
|
|
565
|
+
},
|
|
566
|
+
kind: {
|
|
567
|
+
type: 'string',
|
|
568
|
+
enum: EVERY_SYMBOL_KIND,
|
|
569
|
+
description: 'Filter symbol kind',
|
|
570
|
+
},
|
|
571
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
572
|
+
},
|
|
573
|
+
required: ['command', 'targets'],
|
|
574
|
+
},
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
name: 'triage',
|
|
578
|
+
description:
|
|
579
|
+
'Ranked audit queue by composite risk score. Merges connectivity (fan-in), complexity (cognitive), churn (commit count), role classification, and maintainability index into a single weighted score.',
|
|
580
|
+
inputSchema: {
|
|
581
|
+
type: 'object',
|
|
582
|
+
properties: {
|
|
583
|
+
level: {
|
|
584
|
+
type: 'string',
|
|
585
|
+
enum: ['function', 'file', 'directory'],
|
|
586
|
+
description:
|
|
587
|
+
'Granularity: function (default) | file | directory. File/directory shows hotspots',
|
|
588
|
+
},
|
|
589
|
+
sort: {
|
|
590
|
+
type: 'string',
|
|
591
|
+
enum: ['risk', 'complexity', 'churn', 'fan-in', 'mi'],
|
|
592
|
+
description: 'Sort metric (default: risk)',
|
|
593
|
+
},
|
|
594
|
+
min_score: {
|
|
595
|
+
type: 'number',
|
|
596
|
+
description: 'Only return symbols with risk score >= this threshold (0-1)',
|
|
597
|
+
},
|
|
598
|
+
role: {
|
|
599
|
+
type: 'string',
|
|
600
|
+
enum: VALID_ROLES,
|
|
601
|
+
description: 'Filter by role classification',
|
|
602
|
+
},
|
|
603
|
+
file: { type: 'string', description: 'Scope to file (partial match)' },
|
|
604
|
+
kind: {
|
|
605
|
+
type: 'string',
|
|
606
|
+
enum: ['function', 'method', 'class'],
|
|
607
|
+
description: 'Filter by symbol kind',
|
|
608
|
+
},
|
|
609
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
610
|
+
weights: {
|
|
611
|
+
type: 'object',
|
|
612
|
+
description:
|
|
613
|
+
'Custom scoring weights (e.g. {"fanIn":1,"complexity":0,"churn":0,"role":0,"mi":0})',
|
|
614
|
+
},
|
|
615
|
+
...PAGINATION_PROPS,
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
name: 'branch_compare',
|
|
621
|
+
description:
|
|
622
|
+
'Compare code structure between two git refs (branches, tags, commits). Shows added/removed/changed symbols and transitive caller impact using temporary git worktrees.',
|
|
623
|
+
inputSchema: {
|
|
624
|
+
type: 'object',
|
|
625
|
+
properties: {
|
|
626
|
+
base: { type: 'string', description: 'Base git ref (branch, tag, or commit SHA)' },
|
|
627
|
+
target: { type: 'string', description: 'Target git ref to compare against base' },
|
|
628
|
+
depth: { type: 'number', description: 'Max transitive caller depth', default: 3 },
|
|
629
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
630
|
+
format: {
|
|
631
|
+
type: 'string',
|
|
632
|
+
enum: ['json', 'mermaid'],
|
|
633
|
+
description: 'Output format (default: json)',
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
required: ['base', 'target'],
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
name: 'cfg',
|
|
641
|
+
description: 'Show intraprocedural control flow graph for a function. Requires build --cfg.',
|
|
642
|
+
inputSchema: {
|
|
643
|
+
type: 'object',
|
|
644
|
+
properties: {
|
|
645
|
+
name: { type: 'string', description: 'Function/method name (partial match)' },
|
|
646
|
+
format: {
|
|
647
|
+
type: 'string',
|
|
648
|
+
enum: ['json', 'dot', 'mermaid'],
|
|
649
|
+
description: 'Output format (default: json)',
|
|
650
|
+
},
|
|
651
|
+
file: { type: 'string', description: 'Scope to file (partial match)' },
|
|
652
|
+
kind: { type: 'string', enum: EVERY_SYMBOL_KIND, description: 'Filter by symbol kind' },
|
|
653
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
654
|
+
...PAGINATION_PROPS,
|
|
655
|
+
},
|
|
656
|
+
required: ['name'],
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
name: 'dataflow',
|
|
661
|
+
description: 'Show data flow edges or data-dependent blast radius. Requires build --dataflow.',
|
|
662
|
+
inputSchema: {
|
|
663
|
+
type: 'object',
|
|
664
|
+
properties: {
|
|
665
|
+
name: { type: 'string', description: 'Function/method name (partial match)' },
|
|
666
|
+
mode: {
|
|
667
|
+
type: 'string',
|
|
668
|
+
enum: ['edges', 'impact'],
|
|
669
|
+
description: 'edges (default) or impact',
|
|
670
|
+
},
|
|
671
|
+
depth: { type: 'number', description: 'Max depth for impact mode', default: 5 },
|
|
672
|
+
file: { type: 'string', description: 'Scope to file (partial match)' },
|
|
673
|
+
kind: { type: 'string', enum: EVERY_SYMBOL_KIND, description: 'Filter by symbol kind' },
|
|
674
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
675
|
+
...PAGINATION_PROPS,
|
|
676
|
+
},
|
|
677
|
+
required: ['name'],
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
name: 'check',
|
|
682
|
+
description:
|
|
683
|
+
'CI gate: run manifesto rules (no args), diff predicates (with ref/staged), or both (with rules flag). Returns pass/fail verdicts.',
|
|
684
|
+
inputSchema: {
|
|
685
|
+
type: 'object',
|
|
686
|
+
properties: {
|
|
687
|
+
ref: { type: 'string', description: 'Git ref to diff against (default: HEAD)' },
|
|
688
|
+
staged: { type: 'boolean', description: 'Analyze staged changes instead of unstaged' },
|
|
689
|
+
rules: {
|
|
486
690
|
type: 'boolean',
|
|
487
|
-
description: '
|
|
488
|
-
|
|
691
|
+
description: 'Also run manifesto rules alongside diff predicates',
|
|
692
|
+
},
|
|
693
|
+
cycles: { type: 'boolean', description: 'Enable cycles predicate (default: true)' },
|
|
694
|
+
blast_radius: {
|
|
695
|
+
type: 'number',
|
|
696
|
+
description: 'Max transitive callers threshold (null = disabled)',
|
|
697
|
+
},
|
|
698
|
+
signatures: { type: 'boolean', description: 'Enable signatures predicate (default: true)' },
|
|
699
|
+
boundaries: { type: 'boolean', description: 'Enable boundaries predicate (default: true)' },
|
|
700
|
+
depth: { type: 'number', description: 'Max BFS depth for blast radius (default: 3)' },
|
|
701
|
+
file: { type: 'string', description: 'Scope to file (partial match, manifesto mode)' },
|
|
702
|
+
kind: {
|
|
703
|
+
type: 'string',
|
|
704
|
+
description: 'Filter by symbol kind (manifesto mode)',
|
|
705
|
+
},
|
|
706
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
707
|
+
...PAGINATION_PROPS,
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
name: 'ast_query',
|
|
713
|
+
description:
|
|
714
|
+
'Search stored AST nodes (calls, literals, new, throw, await) by pattern. Requires a prior build.',
|
|
715
|
+
inputSchema: {
|
|
716
|
+
type: 'object',
|
|
717
|
+
properties: {
|
|
718
|
+
pattern: {
|
|
719
|
+
type: 'string',
|
|
720
|
+
description: 'GLOB pattern for node name (auto-wrapped in *..* for substring match)',
|
|
489
721
|
},
|
|
722
|
+
kind: {
|
|
723
|
+
type: 'string',
|
|
724
|
+
enum: AST_NODE_KINDS,
|
|
725
|
+
description: 'Filter by AST node kind',
|
|
726
|
+
},
|
|
727
|
+
file: { type: 'string', description: 'Scope to file (partial match)' },
|
|
490
728
|
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
729
|
+
...PAGINATION_PROPS,
|
|
491
730
|
},
|
|
492
731
|
},
|
|
493
732
|
},
|
|
@@ -557,14 +796,15 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
557
796
|
|
|
558
797
|
// Lazy import query functions to avoid circular deps at module load
|
|
559
798
|
const {
|
|
560
|
-
queryNameData,
|
|
561
799
|
impactAnalysisData,
|
|
562
800
|
moduleMapData,
|
|
563
801
|
fileDepsData,
|
|
802
|
+
exportsData,
|
|
564
803
|
fnDepsData,
|
|
565
804
|
fnImpactData,
|
|
566
805
|
pathData,
|
|
567
806
|
contextData,
|
|
807
|
+
childrenData,
|
|
568
808
|
explainData,
|
|
569
809
|
whereData,
|
|
570
810
|
diffImpactData,
|
|
@@ -615,18 +855,63 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
615
855
|
|
|
616
856
|
let result;
|
|
617
857
|
switch (name) {
|
|
618
|
-
case '
|
|
619
|
-
|
|
858
|
+
case 'query': {
|
|
859
|
+
const qMode = args.mode || 'deps';
|
|
860
|
+
if (qMode === 'path') {
|
|
861
|
+
if (!args.to) {
|
|
862
|
+
result = { error: 'path mode requires a "to" argument' };
|
|
863
|
+
break;
|
|
864
|
+
}
|
|
865
|
+
result = pathData(args.name, args.to, dbPath, {
|
|
866
|
+
maxDepth: args.depth ?? 10,
|
|
867
|
+
edgeKinds: args.edge_kinds,
|
|
868
|
+
reverse: args.reverse,
|
|
869
|
+
fromFile: args.from_file,
|
|
870
|
+
toFile: args.to_file,
|
|
871
|
+
kind: args.kind,
|
|
872
|
+
noTests: args.no_tests,
|
|
873
|
+
});
|
|
874
|
+
} else {
|
|
875
|
+
result = fnDepsData(args.name, dbPath, {
|
|
876
|
+
depth: args.depth,
|
|
877
|
+
file: args.file,
|
|
878
|
+
kind: args.kind,
|
|
879
|
+
noTests: args.no_tests,
|
|
880
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.query, MCP_MAX_LIMIT),
|
|
881
|
+
offset: args.offset ?? 0,
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
886
|
+
case 'path':
|
|
887
|
+
result = pathData(args.from, args.to, dbPath, {
|
|
888
|
+
maxDepth: args.depth ?? 10,
|
|
889
|
+
edgeKinds: args.edge_kinds,
|
|
890
|
+
fromFile: args.from_file,
|
|
891
|
+
toFile: args.to_file,
|
|
620
892
|
noTests: args.no_tests,
|
|
621
|
-
limit: Math.min(args.limit ?? MCP_DEFAULTS.query_function, MCP_MAX_LIMIT),
|
|
622
|
-
offset: args.offset ?? 0,
|
|
623
893
|
});
|
|
624
894
|
break;
|
|
625
895
|
case 'file_deps':
|
|
626
|
-
result = fileDepsData(args.file, dbPath, {
|
|
896
|
+
result = fileDepsData(args.file, dbPath, {
|
|
897
|
+
noTests: args.no_tests,
|
|
898
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.file_deps, MCP_MAX_LIMIT),
|
|
899
|
+
offset: args.offset ?? 0,
|
|
900
|
+
});
|
|
901
|
+
break;
|
|
902
|
+
case 'file_exports':
|
|
903
|
+
result = exportsData(args.file, dbPath, {
|
|
904
|
+
noTests: args.no_tests,
|
|
905
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.file_exports, MCP_MAX_LIMIT),
|
|
906
|
+
offset: args.offset ?? 0,
|
|
907
|
+
});
|
|
627
908
|
break;
|
|
628
909
|
case 'impact_analysis':
|
|
629
|
-
result = impactAnalysisData(args.file, dbPath, {
|
|
910
|
+
result = impactAnalysisData(args.file, dbPath, {
|
|
911
|
+
noTests: args.no_tests,
|
|
912
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.impact_analysis, MCP_MAX_LIMIT),
|
|
913
|
+
offset: args.offset ?? 0,
|
|
914
|
+
});
|
|
630
915
|
break;
|
|
631
916
|
case 'find_cycles': {
|
|
632
917
|
const db = new Database(findDbPath(dbPath), { readonly: true });
|
|
@@ -638,31 +923,14 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
638
923
|
case 'module_map':
|
|
639
924
|
result = moduleMapData(dbPath, args.limit || 20, { noTests: args.no_tests });
|
|
640
925
|
break;
|
|
641
|
-
case 'fn_deps':
|
|
642
|
-
result = fnDepsData(args.name, dbPath, {
|
|
643
|
-
depth: args.depth,
|
|
644
|
-
file: args.file,
|
|
645
|
-
kind: args.kind,
|
|
646
|
-
noTests: args.no_tests,
|
|
647
|
-
});
|
|
648
|
-
break;
|
|
649
926
|
case 'fn_impact':
|
|
650
927
|
result = fnImpactData(args.name, dbPath, {
|
|
651
928
|
depth: args.depth,
|
|
652
929
|
file: args.file,
|
|
653
930
|
kind: args.kind,
|
|
654
931
|
noTests: args.no_tests,
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
case 'symbol_path':
|
|
658
|
-
result = pathData(args.from, args.to, dbPath, {
|
|
659
|
-
maxDepth: args.max_depth,
|
|
660
|
-
edgeKinds: args.edge_kinds,
|
|
661
|
-
reverse: args.reverse,
|
|
662
|
-
fromFile: args.from_file,
|
|
663
|
-
toFile: args.to_file,
|
|
664
|
-
kind: args.kind,
|
|
665
|
-
noTests: args.no_tests,
|
|
932
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.fn_impact, MCP_MAX_LIMIT),
|
|
933
|
+
offset: args.offset ?? 0,
|
|
666
934
|
});
|
|
667
935
|
break;
|
|
668
936
|
case 'context':
|
|
@@ -673,10 +941,18 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
673
941
|
noSource: args.no_source,
|
|
674
942
|
noTests: args.no_tests,
|
|
675
943
|
includeTests: args.include_tests,
|
|
944
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.context, MCP_MAX_LIMIT),
|
|
945
|
+
offset: args.offset ?? 0,
|
|
676
946
|
});
|
|
677
947
|
break;
|
|
678
|
-
case '
|
|
679
|
-
result =
|
|
948
|
+
case 'symbol_children':
|
|
949
|
+
result = childrenData(args.name, dbPath, {
|
|
950
|
+
file: args.file,
|
|
951
|
+
kind: args.kind,
|
|
952
|
+
noTests: args.no_tests,
|
|
953
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.context, MCP_MAX_LIMIT),
|
|
954
|
+
offset: args.offset ?? 0,
|
|
955
|
+
});
|
|
680
956
|
break;
|
|
681
957
|
case 'where':
|
|
682
958
|
result = whereData(args.target, dbPath, {
|
|
@@ -700,27 +976,77 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
700
976
|
ref: args.ref,
|
|
701
977
|
depth: args.depth,
|
|
702
978
|
noTests: args.no_tests,
|
|
979
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.diff_impact, MCP_MAX_LIMIT),
|
|
980
|
+
offset: args.offset ?? 0,
|
|
703
981
|
});
|
|
704
982
|
}
|
|
705
983
|
break;
|
|
706
984
|
case 'semantic_search': {
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
limit: args.limit,
|
|
985
|
+
const mode = args.mode || 'hybrid';
|
|
986
|
+
const searchOpts = {
|
|
987
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.semantic_search, MCP_MAX_LIMIT),
|
|
988
|
+
offset: args.offset ?? 0,
|
|
710
989
|
minScore: args.min_score,
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
990
|
+
};
|
|
991
|
+
|
|
992
|
+
if (mode === 'keyword') {
|
|
993
|
+
const { ftsSearchData } = await import('./embedder.js');
|
|
994
|
+
result = ftsSearchData(args.query, dbPath, searchOpts);
|
|
995
|
+
if (result === null) {
|
|
996
|
+
return {
|
|
997
|
+
content: [
|
|
998
|
+
{
|
|
999
|
+
type: 'text',
|
|
1000
|
+
text: 'No FTS5 index found. Run `codegraph embed` to build the keyword index.',
|
|
1001
|
+
},
|
|
1002
|
+
],
|
|
1003
|
+
isError: true,
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
} else if (mode === 'semantic') {
|
|
1007
|
+
const { searchData } = await import('./embedder.js');
|
|
1008
|
+
result = await searchData(args.query, dbPath, searchOpts);
|
|
1009
|
+
if (result === null) {
|
|
1010
|
+
return {
|
|
1011
|
+
content: [
|
|
1012
|
+
{
|
|
1013
|
+
type: 'text',
|
|
1014
|
+
text: 'Semantic search unavailable. Run `codegraph embed` first.',
|
|
1015
|
+
},
|
|
1016
|
+
],
|
|
1017
|
+
isError: true,
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
} else {
|
|
1021
|
+
// hybrid (default) — falls back to semantic if no FTS5
|
|
1022
|
+
const { hybridSearchData, searchData } = await import('./embedder.js');
|
|
1023
|
+
result = await hybridSearchData(args.query, dbPath, searchOpts);
|
|
1024
|
+
if (result === null) {
|
|
1025
|
+
result = await searchData(args.query, dbPath, searchOpts);
|
|
1026
|
+
if (result === null) {
|
|
1027
|
+
return {
|
|
1028
|
+
content: [
|
|
1029
|
+
{
|
|
1030
|
+
type: 'text',
|
|
1031
|
+
text: 'Semantic search unavailable. Run `codegraph embed` first.',
|
|
1032
|
+
},
|
|
1033
|
+
],
|
|
1034
|
+
isError: true,
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
719
1038
|
}
|
|
720
1039
|
break;
|
|
721
1040
|
}
|
|
722
1041
|
case 'export_graph': {
|
|
723
|
-
const {
|
|
1042
|
+
const {
|
|
1043
|
+
exportDOT,
|
|
1044
|
+
exportGraphML,
|
|
1045
|
+
exportGraphSON,
|
|
1046
|
+
exportJSON,
|
|
1047
|
+
exportMermaid,
|
|
1048
|
+
exportNeo4jCSV,
|
|
1049
|
+
} = await import('./export.js');
|
|
724
1050
|
const db = new Database(findDbPath(dbPath), { readonly: true });
|
|
725
1051
|
const fileLevel = args.file_level !== false;
|
|
726
1052
|
const exportLimit = args.limit
|
|
@@ -739,13 +1065,26 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
739
1065
|
offset: args.offset ?? 0,
|
|
740
1066
|
});
|
|
741
1067
|
break;
|
|
1068
|
+
case 'graphml':
|
|
1069
|
+
result = exportGraphML(db, { fileLevel, limit: exportLimit });
|
|
1070
|
+
break;
|
|
1071
|
+
case 'graphson':
|
|
1072
|
+
result = exportGraphSON(db, {
|
|
1073
|
+
fileLevel,
|
|
1074
|
+
limit: exportLimit,
|
|
1075
|
+
offset: args.offset ?? 0,
|
|
1076
|
+
});
|
|
1077
|
+
break;
|
|
1078
|
+
case 'neo4j':
|
|
1079
|
+
result = exportNeo4jCSV(db, { fileLevel, limit: exportLimit });
|
|
1080
|
+
break;
|
|
742
1081
|
default:
|
|
743
1082
|
db.close();
|
|
744
1083
|
return {
|
|
745
1084
|
content: [
|
|
746
1085
|
{
|
|
747
1086
|
type: 'text',
|
|
748
|
-
text: `Unknown format: ${args.format}. Use dot, mermaid, or
|
|
1087
|
+
text: `Unknown format: ${args.format}. Use dot, mermaid, json, graphml, graphson, or neo4j.`,
|
|
749
1088
|
},
|
|
750
1089
|
],
|
|
751
1090
|
isError: true,
|
|
@@ -779,16 +1118,8 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
779
1118
|
depth: args.depth,
|
|
780
1119
|
sort: args.sort,
|
|
781
1120
|
full: args.full,
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
}
|
|
785
|
-
case 'hotspots': {
|
|
786
|
-
const { hotspotsData } = await import('./structure.js');
|
|
787
|
-
result = hotspotsData(dbPath, {
|
|
788
|
-
metric: args.metric,
|
|
789
|
-
level: args.level,
|
|
790
|
-
limit: args.limit,
|
|
791
|
-
noTests: args.no_tests,
|
|
1121
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.structure, MCP_MAX_LIMIT),
|
|
1122
|
+
offset: args.offset ?? 0,
|
|
792
1123
|
});
|
|
793
1124
|
break;
|
|
794
1125
|
}
|
|
@@ -796,34 +1127,42 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
796
1127
|
const { coChangeData, coChangeTopData } = await import('./cochange.js');
|
|
797
1128
|
result = args.file
|
|
798
1129
|
? coChangeData(args.file, dbPath, {
|
|
799
|
-
limit: args.limit,
|
|
1130
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.co_changes, MCP_MAX_LIMIT),
|
|
1131
|
+
offset: args.offset ?? 0,
|
|
800
1132
|
minJaccard: args.min_jaccard,
|
|
801
1133
|
noTests: args.no_tests,
|
|
802
1134
|
})
|
|
803
1135
|
: coChangeTopData(dbPath, {
|
|
804
|
-
limit: args.limit,
|
|
1136
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.co_changes, MCP_MAX_LIMIT),
|
|
1137
|
+
offset: args.offset ?? 0,
|
|
805
1138
|
minJaccard: args.min_jaccard,
|
|
806
1139
|
noTests: args.no_tests,
|
|
807
1140
|
});
|
|
808
1141
|
break;
|
|
809
1142
|
}
|
|
810
1143
|
case 'execution_flow': {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1144
|
+
if (args.list) {
|
|
1145
|
+
const { listEntryPointsData } = await import('./flow.js');
|
|
1146
|
+
result = listEntryPointsData(dbPath, {
|
|
1147
|
+
noTests: args.no_tests,
|
|
1148
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.execution_flow, MCP_MAX_LIMIT),
|
|
1149
|
+
offset: args.offset ?? 0,
|
|
1150
|
+
});
|
|
1151
|
+
} else {
|
|
1152
|
+
if (!args.name) {
|
|
1153
|
+
result = { error: 'Provide a name or set list=true' };
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
const { flowData } = await import('./flow.js');
|
|
1157
|
+
result = flowData(args.name, dbPath, {
|
|
1158
|
+
depth: args.depth,
|
|
1159
|
+
file: args.file,
|
|
1160
|
+
kind: args.kind,
|
|
1161
|
+
noTests: args.no_tests,
|
|
1162
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.execution_flow, MCP_MAX_LIMIT),
|
|
1163
|
+
offset: args.offset ?? 0,
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
827
1166
|
break;
|
|
828
1167
|
}
|
|
829
1168
|
case 'complexity': {
|
|
@@ -831,7 +1170,8 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
831
1170
|
result = complexityData(dbPath, {
|
|
832
1171
|
target: args.name,
|
|
833
1172
|
file: args.file,
|
|
834
|
-
limit: args.limit,
|
|
1173
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.complexity, MCP_MAX_LIMIT),
|
|
1174
|
+
offset: args.offset ?? 0,
|
|
835
1175
|
sort: args.sort,
|
|
836
1176
|
aboveThreshold: args.above_threshold,
|
|
837
1177
|
health: args.health,
|
|
@@ -840,15 +1180,6 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
840
1180
|
});
|
|
841
1181
|
break;
|
|
842
1182
|
}
|
|
843
|
-
case 'manifesto': {
|
|
844
|
-
const { manifestoData } = await import('./manifesto.js');
|
|
845
|
-
result = manifestoData(dbPath, {
|
|
846
|
-
file: args.file,
|
|
847
|
-
noTests: args.no_tests,
|
|
848
|
-
kind: args.kind,
|
|
849
|
-
});
|
|
850
|
-
break;
|
|
851
|
-
}
|
|
852
1183
|
case 'communities': {
|
|
853
1184
|
const { communitiesData } = await import('./communities.js');
|
|
854
1185
|
result = communitiesData(dbPath, {
|
|
@@ -856,6 +1187,184 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
856
1187
|
resolution: args.resolution,
|
|
857
1188
|
drift: args.drift,
|
|
858
1189
|
noTests: args.no_tests,
|
|
1190
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.communities, MCP_MAX_LIMIT),
|
|
1191
|
+
offset: args.offset ?? 0,
|
|
1192
|
+
});
|
|
1193
|
+
break;
|
|
1194
|
+
}
|
|
1195
|
+
case 'code_owners': {
|
|
1196
|
+
const { ownersData } = await import('./owners.js');
|
|
1197
|
+
result = ownersData(dbPath, {
|
|
1198
|
+
file: args.file,
|
|
1199
|
+
owner: args.owner,
|
|
1200
|
+
boundary: args.boundary,
|
|
1201
|
+
kind: args.kind,
|
|
1202
|
+
noTests: args.no_tests,
|
|
1203
|
+
});
|
|
1204
|
+
break;
|
|
1205
|
+
}
|
|
1206
|
+
case 'audit': {
|
|
1207
|
+
if (args.quick) {
|
|
1208
|
+
result = explainData(args.target, dbPath, {
|
|
1209
|
+
noTests: args.no_tests,
|
|
1210
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.explain, MCP_MAX_LIMIT),
|
|
1211
|
+
offset: args.offset ?? 0,
|
|
1212
|
+
});
|
|
1213
|
+
} else {
|
|
1214
|
+
const { auditData } = await import('./audit.js');
|
|
1215
|
+
result = auditData(args.target, dbPath, {
|
|
1216
|
+
depth: args.depth,
|
|
1217
|
+
file: args.file,
|
|
1218
|
+
kind: args.kind,
|
|
1219
|
+
noTests: args.no_tests,
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
break;
|
|
1223
|
+
}
|
|
1224
|
+
case 'batch_query': {
|
|
1225
|
+
const { batchData } = await import('./batch.js');
|
|
1226
|
+
result = batchData(args.command, args.targets, dbPath, {
|
|
1227
|
+
depth: args.depth,
|
|
1228
|
+
file: args.file,
|
|
1229
|
+
kind: args.kind,
|
|
1230
|
+
noTests: args.no_tests,
|
|
1231
|
+
});
|
|
1232
|
+
break;
|
|
1233
|
+
}
|
|
1234
|
+
case 'triage': {
|
|
1235
|
+
if (args.level === 'file' || args.level === 'directory') {
|
|
1236
|
+
const { hotspotsData } = await import('./structure.js');
|
|
1237
|
+
const TRIAGE_TO_HOTSPOT = {
|
|
1238
|
+
risk: 'fan-in',
|
|
1239
|
+
complexity: 'density',
|
|
1240
|
+
churn: 'coupling',
|
|
1241
|
+
mi: 'fan-in',
|
|
1242
|
+
};
|
|
1243
|
+
const metric = TRIAGE_TO_HOTSPOT[args.sort] ?? args.sort;
|
|
1244
|
+
result = hotspotsData(dbPath, {
|
|
1245
|
+
metric,
|
|
1246
|
+
level: args.level,
|
|
1247
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.hotspots, MCP_MAX_LIMIT),
|
|
1248
|
+
offset: args.offset ?? 0,
|
|
1249
|
+
noTests: args.no_tests,
|
|
1250
|
+
});
|
|
1251
|
+
} else {
|
|
1252
|
+
const { triageData } = await import('./triage.js');
|
|
1253
|
+
result = triageData(dbPath, {
|
|
1254
|
+
sort: args.sort,
|
|
1255
|
+
minScore: args.min_score,
|
|
1256
|
+
role: args.role,
|
|
1257
|
+
file: args.file,
|
|
1258
|
+
kind: args.kind,
|
|
1259
|
+
noTests: args.no_tests,
|
|
1260
|
+
weights: args.weights,
|
|
1261
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.triage, MCP_MAX_LIMIT),
|
|
1262
|
+
offset: args.offset ?? 0,
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
break;
|
|
1266
|
+
}
|
|
1267
|
+
case 'branch_compare': {
|
|
1268
|
+
const { branchCompareData, branchCompareMermaid } = await import('./branch-compare.js');
|
|
1269
|
+
const bcData = await branchCompareData(args.base, args.target, {
|
|
1270
|
+
depth: args.depth,
|
|
1271
|
+
noTests: args.no_tests,
|
|
1272
|
+
});
|
|
1273
|
+
result = args.format === 'mermaid' ? branchCompareMermaid(bcData) : bcData;
|
|
1274
|
+
break;
|
|
1275
|
+
}
|
|
1276
|
+
case 'cfg': {
|
|
1277
|
+
const { cfgData, cfgToDOT, cfgToMermaid } = await import('./cfg.js');
|
|
1278
|
+
const cfgResult = cfgData(args.name, dbPath, {
|
|
1279
|
+
file: args.file,
|
|
1280
|
+
kind: args.kind,
|
|
1281
|
+
noTests: args.no_tests,
|
|
1282
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.query, MCP_MAX_LIMIT),
|
|
1283
|
+
offset: args.offset ?? 0,
|
|
1284
|
+
});
|
|
1285
|
+
if (args.format === 'dot') {
|
|
1286
|
+
result = { text: cfgToDOT(cfgResult) };
|
|
1287
|
+
} else if (args.format === 'mermaid') {
|
|
1288
|
+
result = { text: cfgToMermaid(cfgResult) };
|
|
1289
|
+
} else {
|
|
1290
|
+
result = cfgResult;
|
|
1291
|
+
}
|
|
1292
|
+
break;
|
|
1293
|
+
}
|
|
1294
|
+
case 'dataflow': {
|
|
1295
|
+
const dfMode = args.mode || 'edges';
|
|
1296
|
+
if (dfMode === 'impact') {
|
|
1297
|
+
const { dataflowImpactData } = await import('./dataflow.js');
|
|
1298
|
+
result = dataflowImpactData(args.name, dbPath, {
|
|
1299
|
+
depth: args.depth,
|
|
1300
|
+
file: args.file,
|
|
1301
|
+
kind: args.kind,
|
|
1302
|
+
noTests: args.no_tests,
|
|
1303
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.fn_impact, MCP_MAX_LIMIT),
|
|
1304
|
+
offset: args.offset ?? 0,
|
|
1305
|
+
});
|
|
1306
|
+
} else {
|
|
1307
|
+
const { dataflowData } = await import('./dataflow.js');
|
|
1308
|
+
result = dataflowData(args.name, dbPath, {
|
|
1309
|
+
file: args.file,
|
|
1310
|
+
kind: args.kind,
|
|
1311
|
+
noTests: args.no_tests,
|
|
1312
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.query, MCP_MAX_LIMIT),
|
|
1313
|
+
offset: args.offset ?? 0,
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
break;
|
|
1317
|
+
}
|
|
1318
|
+
case 'check': {
|
|
1319
|
+
const isDiffMode = args.ref || args.staged;
|
|
1320
|
+
|
|
1321
|
+
if (!isDiffMode && !args.rules) {
|
|
1322
|
+
// No ref, no staged → run manifesto rules on whole codebase
|
|
1323
|
+
const { manifestoData } = await import('./manifesto.js');
|
|
1324
|
+
result = manifestoData(dbPath, {
|
|
1325
|
+
file: args.file,
|
|
1326
|
+
noTests: args.no_tests,
|
|
1327
|
+
kind: args.kind,
|
|
1328
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.manifesto, MCP_MAX_LIMIT),
|
|
1329
|
+
offset: args.offset ?? 0,
|
|
1330
|
+
});
|
|
1331
|
+
} else {
|
|
1332
|
+
const { checkData } = await import('./check.js');
|
|
1333
|
+
const checkResult = checkData(dbPath, {
|
|
1334
|
+
ref: args.ref,
|
|
1335
|
+
staged: args.staged,
|
|
1336
|
+
cycles: args.cycles,
|
|
1337
|
+
blastRadius: args.blast_radius,
|
|
1338
|
+
signatures: args.signatures,
|
|
1339
|
+
boundaries: args.boundaries,
|
|
1340
|
+
depth: args.depth,
|
|
1341
|
+
noTests: args.no_tests,
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
if (args.rules) {
|
|
1345
|
+
const { manifestoData } = await import('./manifesto.js');
|
|
1346
|
+
const manifestoResult = manifestoData(dbPath, {
|
|
1347
|
+
file: args.file,
|
|
1348
|
+
noTests: args.no_tests,
|
|
1349
|
+
kind: args.kind,
|
|
1350
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.manifesto, MCP_MAX_LIMIT),
|
|
1351
|
+
offset: args.offset ?? 0,
|
|
1352
|
+
});
|
|
1353
|
+
result = { check: checkResult, manifesto: manifestoResult };
|
|
1354
|
+
} else {
|
|
1355
|
+
result = checkResult;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
break;
|
|
1359
|
+
}
|
|
1360
|
+
case 'ast_query': {
|
|
1361
|
+
const { astQueryData } = await import('./ast.js');
|
|
1362
|
+
result = astQueryData(args.pattern, dbPath, {
|
|
1363
|
+
kind: args.kind,
|
|
1364
|
+
file: args.file,
|
|
1365
|
+
noTests: args.no_tests,
|
|
1366
|
+
limit: Math.min(args.limit ?? MCP_DEFAULTS.ast_query, MCP_MAX_LIMIT),
|
|
1367
|
+
offset: args.offset ?? 0,
|
|
859
1368
|
});
|
|
860
1369
|
break;
|
|
861
1370
|
}
|