@optave/codegraph 2.2.3-dev.44e8146 → 2.3.1-dev.1aeea34
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 +55 -15
- package/package.json +7 -6
- package/src/builder.js +66 -0
- package/src/cli.js +116 -13
- package/src/cochange.js +498 -0
- package/src/config.js +6 -0
- package/src/db.js +40 -0
- package/src/embedder.js +61 -2
- package/src/export.js +158 -13
- package/src/extractors/helpers.js +2 -1
- package/src/extractors/javascript.js +294 -78
- package/src/index.js +13 -0
- package/src/mcp.js +62 -1
- package/src/parser.js +39 -2
- package/src/queries.js +158 -9
- package/src/registry.js +9 -1
- package/src/structure.js +94 -0
|
@@ -2,8 +2,224 @@ import { findChild, nodeEndLine } from './helpers.js';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Extract symbols from a JS/TS parsed AST.
|
|
5
|
+
* When a compiled tree-sitter Query is provided (from parser.js),
|
|
6
|
+
* uses the fast query-based path. Falls back to manual tree walk otherwise.
|
|
5
7
|
*/
|
|
6
|
-
export function extractSymbols(tree, _filePath) {
|
|
8
|
+
export function extractSymbols(tree, _filePath, query) {
|
|
9
|
+
if (query) return extractSymbolsQuery(tree, query);
|
|
10
|
+
return extractSymbolsWalk(tree);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ── Query-based extraction (fast path) ──────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
function extractSymbolsQuery(tree, query) {
|
|
16
|
+
const definitions = [];
|
|
17
|
+
const calls = [];
|
|
18
|
+
const imports = [];
|
|
19
|
+
const classes = [];
|
|
20
|
+
const exps = [];
|
|
21
|
+
|
|
22
|
+
const matches = query.matches(tree.rootNode);
|
|
23
|
+
|
|
24
|
+
for (const match of matches) {
|
|
25
|
+
// Build capture lookup for this match (1-3 captures each, very fast)
|
|
26
|
+
const c = Object.create(null);
|
|
27
|
+
for (const cap of match.captures) c[cap.name] = cap.node;
|
|
28
|
+
|
|
29
|
+
if (c.fn_node) {
|
|
30
|
+
// function_declaration
|
|
31
|
+
definitions.push({
|
|
32
|
+
name: c.fn_name.text,
|
|
33
|
+
kind: 'function',
|
|
34
|
+
line: c.fn_node.startPosition.row + 1,
|
|
35
|
+
endLine: nodeEndLine(c.fn_node),
|
|
36
|
+
});
|
|
37
|
+
} else if (c.varfn_name) {
|
|
38
|
+
// variable_declarator with arrow_function / function_expression
|
|
39
|
+
const declNode = c.varfn_name.parent?.parent;
|
|
40
|
+
const line = declNode ? declNode.startPosition.row + 1 : c.varfn_name.startPosition.row + 1;
|
|
41
|
+
definitions.push({
|
|
42
|
+
name: c.varfn_name.text,
|
|
43
|
+
kind: 'function',
|
|
44
|
+
line,
|
|
45
|
+
endLine: nodeEndLine(c.varfn_value),
|
|
46
|
+
});
|
|
47
|
+
} else if (c.cls_node) {
|
|
48
|
+
// class_declaration
|
|
49
|
+
const className = c.cls_name.text;
|
|
50
|
+
const startLine = c.cls_node.startPosition.row + 1;
|
|
51
|
+
definitions.push({
|
|
52
|
+
name: className,
|
|
53
|
+
kind: 'class',
|
|
54
|
+
line: startLine,
|
|
55
|
+
endLine: nodeEndLine(c.cls_node),
|
|
56
|
+
});
|
|
57
|
+
const heritage =
|
|
58
|
+
c.cls_node.childForFieldName('heritage') || findChild(c.cls_node, 'class_heritage');
|
|
59
|
+
if (heritage) {
|
|
60
|
+
const superName = extractSuperclass(heritage);
|
|
61
|
+
if (superName) classes.push({ name: className, extends: superName, line: startLine });
|
|
62
|
+
const implementsList = extractImplements(heritage);
|
|
63
|
+
for (const iface of implementsList) {
|
|
64
|
+
classes.push({ name: className, implements: iface, line: startLine });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} else if (c.meth_node) {
|
|
68
|
+
// method_definition
|
|
69
|
+
const methName = c.meth_name.text;
|
|
70
|
+
const parentClass = findParentClass(c.meth_node);
|
|
71
|
+
const fullName = parentClass ? `${parentClass}.${methName}` : methName;
|
|
72
|
+
definitions.push({
|
|
73
|
+
name: fullName,
|
|
74
|
+
kind: 'method',
|
|
75
|
+
line: c.meth_node.startPosition.row + 1,
|
|
76
|
+
endLine: nodeEndLine(c.meth_node),
|
|
77
|
+
});
|
|
78
|
+
} else if (c.iface_node) {
|
|
79
|
+
// interface_declaration (TS/TSX only)
|
|
80
|
+
const ifaceName = c.iface_name.text;
|
|
81
|
+
definitions.push({
|
|
82
|
+
name: ifaceName,
|
|
83
|
+
kind: 'interface',
|
|
84
|
+
line: c.iface_node.startPosition.row + 1,
|
|
85
|
+
endLine: nodeEndLine(c.iface_node),
|
|
86
|
+
});
|
|
87
|
+
const body =
|
|
88
|
+
c.iface_node.childForFieldName('body') ||
|
|
89
|
+
findChild(c.iface_node, 'interface_body') ||
|
|
90
|
+
findChild(c.iface_node, 'object_type');
|
|
91
|
+
if (body) extractInterfaceMethods(body, ifaceName, definitions);
|
|
92
|
+
} else if (c.type_node) {
|
|
93
|
+
// type_alias_declaration (TS/TSX only)
|
|
94
|
+
definitions.push({
|
|
95
|
+
name: c.type_name.text,
|
|
96
|
+
kind: 'type',
|
|
97
|
+
line: c.type_node.startPosition.row + 1,
|
|
98
|
+
endLine: nodeEndLine(c.type_node),
|
|
99
|
+
});
|
|
100
|
+
} else if (c.imp_node) {
|
|
101
|
+
// import_statement
|
|
102
|
+
const isTypeOnly = c.imp_node.text.startsWith('import type');
|
|
103
|
+
const modPath = c.imp_source.text.replace(/['"]/g, '');
|
|
104
|
+
const names = extractImportNames(c.imp_node);
|
|
105
|
+
imports.push({
|
|
106
|
+
source: modPath,
|
|
107
|
+
names,
|
|
108
|
+
line: c.imp_node.startPosition.row + 1,
|
|
109
|
+
typeOnly: isTypeOnly,
|
|
110
|
+
});
|
|
111
|
+
} else if (c.exp_node) {
|
|
112
|
+
// export_statement
|
|
113
|
+
const exportLine = c.exp_node.startPosition.row + 1;
|
|
114
|
+
const decl = c.exp_node.childForFieldName('declaration');
|
|
115
|
+
if (decl) {
|
|
116
|
+
const declType = decl.type;
|
|
117
|
+
const kindMap = {
|
|
118
|
+
function_declaration: 'function',
|
|
119
|
+
class_declaration: 'class',
|
|
120
|
+
interface_declaration: 'interface',
|
|
121
|
+
type_alias_declaration: 'type',
|
|
122
|
+
};
|
|
123
|
+
const kind = kindMap[declType];
|
|
124
|
+
if (kind) {
|
|
125
|
+
const n = decl.childForFieldName('name');
|
|
126
|
+
if (n) exps.push({ name: n.text, kind, line: exportLine });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const source = c.exp_node.childForFieldName('source') || findChild(c.exp_node, 'string');
|
|
130
|
+
if (source && !decl) {
|
|
131
|
+
const modPath = source.text.replace(/['"]/g, '');
|
|
132
|
+
const reexportNames = extractImportNames(c.exp_node);
|
|
133
|
+
const nodeText = c.exp_node.text;
|
|
134
|
+
const isWildcard = nodeText.includes('export *') || nodeText.includes('export*');
|
|
135
|
+
imports.push({
|
|
136
|
+
source: modPath,
|
|
137
|
+
names: reexportNames,
|
|
138
|
+
line: exportLine,
|
|
139
|
+
reexport: true,
|
|
140
|
+
wildcardReexport: isWildcard && reexportNames.length === 0,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
} else if (c.callfn_node) {
|
|
144
|
+
// call_expression with identifier function
|
|
145
|
+
calls.push({
|
|
146
|
+
name: c.callfn_name.text,
|
|
147
|
+
line: c.callfn_node.startPosition.row + 1,
|
|
148
|
+
});
|
|
149
|
+
} else if (c.callmem_node) {
|
|
150
|
+
// call_expression with member_expression function
|
|
151
|
+
const callInfo = extractCallInfo(c.callmem_fn, c.callmem_node);
|
|
152
|
+
if (callInfo) calls.push(callInfo);
|
|
153
|
+
const cbDef = extractCallbackDefinition(c.callmem_node, c.callmem_fn);
|
|
154
|
+
if (cbDef) definitions.push(cbDef);
|
|
155
|
+
} else if (c.callsub_node) {
|
|
156
|
+
// call_expression with subscript_expression function
|
|
157
|
+
const callInfo = extractCallInfo(c.callsub_fn, c.callsub_node);
|
|
158
|
+
if (callInfo) calls.push(callInfo);
|
|
159
|
+
} else if (c.assign_node) {
|
|
160
|
+
// CommonJS: module.exports = require(...) / module.exports = { ...require(...) }
|
|
161
|
+
handleCommonJSAssignment(c.assign_left, c.assign_right, c.assign_node, imports);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return { definitions, calls, imports, classes, exports: exps };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function handleCommonJSAssignment(left, right, node, imports) {
|
|
169
|
+
if (!left || !right) return;
|
|
170
|
+
const leftText = left.text;
|
|
171
|
+
if (!leftText.startsWith('module.exports') && leftText !== 'exports') return;
|
|
172
|
+
|
|
173
|
+
const rightType = right.type;
|
|
174
|
+
const assignLine = node.startPosition.row + 1;
|
|
175
|
+
|
|
176
|
+
if (rightType === 'call_expression') {
|
|
177
|
+
const fn = right.childForFieldName('function');
|
|
178
|
+
const args = right.childForFieldName('arguments') || findChild(right, 'arguments');
|
|
179
|
+
if (fn && fn.text === 'require' && args) {
|
|
180
|
+
const strArg = findChild(args, 'string');
|
|
181
|
+
if (strArg) {
|
|
182
|
+
imports.push({
|
|
183
|
+
source: strArg.text.replace(/['"]/g, ''),
|
|
184
|
+
names: [],
|
|
185
|
+
line: assignLine,
|
|
186
|
+
reexport: true,
|
|
187
|
+
wildcardReexport: true,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (rightType === 'object') {
|
|
194
|
+
for (let ci = 0; ci < right.childCount; ci++) {
|
|
195
|
+
const child = right.child(ci);
|
|
196
|
+
if (child && child.type === 'spread_element') {
|
|
197
|
+
const spreadExpr = child.child(1) || child.childForFieldName('value');
|
|
198
|
+
if (spreadExpr && spreadExpr.type === 'call_expression') {
|
|
199
|
+
const fn2 = spreadExpr.childForFieldName('function');
|
|
200
|
+
const args2 =
|
|
201
|
+
spreadExpr.childForFieldName('arguments') || findChild(spreadExpr, 'arguments');
|
|
202
|
+
if (fn2 && fn2.text === 'require' && args2) {
|
|
203
|
+
const strArg2 = findChild(args2, 'string');
|
|
204
|
+
if (strArg2) {
|
|
205
|
+
imports.push({
|
|
206
|
+
source: strArg2.text.replace(/['"]/g, ''),
|
|
207
|
+
names: [],
|
|
208
|
+
line: assignLine,
|
|
209
|
+
reexport: true,
|
|
210
|
+
wildcardReexport: true,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ── Manual tree walk (fallback when Query not available) ────────────────────
|
|
221
|
+
|
|
222
|
+
function extractSymbolsWalk(tree) {
|
|
7
223
|
const definitions = [];
|
|
8
224
|
const calls = [];
|
|
9
225
|
const imports = [];
|
|
@@ -28,30 +244,23 @@ export function extractSymbols(tree, _filePath) {
|
|
|
28
244
|
case 'class_declaration': {
|
|
29
245
|
const nameNode = node.childForFieldName('name');
|
|
30
246
|
if (nameNode) {
|
|
31
|
-
const
|
|
32
|
-
|
|
247
|
+
const className = nameNode.text;
|
|
248
|
+
const startLine = node.startPosition.row + 1;
|
|
249
|
+
definitions.push({
|
|
250
|
+
name: className,
|
|
33
251
|
kind: 'class',
|
|
34
|
-
line:
|
|
252
|
+
line: startLine,
|
|
35
253
|
endLine: nodeEndLine(node),
|
|
36
|
-
};
|
|
37
|
-
definitions.push(cls);
|
|
254
|
+
});
|
|
38
255
|
const heritage = node.childForFieldName('heritage') || findChild(node, 'class_heritage');
|
|
39
256
|
if (heritage) {
|
|
40
257
|
const superName = extractSuperclass(heritage);
|
|
41
258
|
if (superName) {
|
|
42
|
-
classes.push({
|
|
43
|
-
name: nameNode.text,
|
|
44
|
-
extends: superName,
|
|
45
|
-
line: node.startPosition.row + 1,
|
|
46
|
-
});
|
|
259
|
+
classes.push({ name: className, extends: superName, line: startLine });
|
|
47
260
|
}
|
|
48
261
|
const implementsList = extractImplements(heritage);
|
|
49
262
|
for (const iface of implementsList) {
|
|
50
|
-
classes.push({
|
|
51
|
-
name: nameNode.text,
|
|
52
|
-
implements: iface,
|
|
53
|
-
line: node.startPosition.row + 1,
|
|
54
|
-
});
|
|
263
|
+
classes.push({ name: className, implements: iface, line: startLine });
|
|
55
264
|
}
|
|
56
265
|
}
|
|
57
266
|
}
|
|
@@ -113,19 +322,20 @@ export function extractSymbols(tree, _filePath) {
|
|
|
113
322
|
if (declarator && declarator.type === 'variable_declarator') {
|
|
114
323
|
const nameN = declarator.childForFieldName('name');
|
|
115
324
|
const valueN = declarator.childForFieldName('value');
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
325
|
+
if (nameN && valueN) {
|
|
326
|
+
const valType = valueN.type;
|
|
327
|
+
if (
|
|
328
|
+
valType === 'arrow_function' ||
|
|
329
|
+
valType === 'function_expression' ||
|
|
330
|
+
valType === 'function'
|
|
331
|
+
) {
|
|
332
|
+
definitions.push({
|
|
333
|
+
name: nameN.text,
|
|
334
|
+
kind: 'function',
|
|
335
|
+
line: node.startPosition.row + 1,
|
|
336
|
+
endLine: nodeEndLine(valueN),
|
|
337
|
+
});
|
|
338
|
+
}
|
|
129
339
|
}
|
|
130
340
|
}
|
|
131
341
|
}
|
|
@@ -136,9 +346,7 @@ export function extractSymbols(tree, _filePath) {
|
|
|
136
346
|
const fn = node.childForFieldName('function');
|
|
137
347
|
if (fn) {
|
|
138
348
|
const callInfo = extractCallInfo(fn, node);
|
|
139
|
-
if (callInfo)
|
|
140
|
-
calls.push(callInfo);
|
|
141
|
-
}
|
|
349
|
+
if (callInfo) calls.push(callInfo);
|
|
142
350
|
if (fn.type === 'member_expression') {
|
|
143
351
|
const cbDef = extractCallbackDefinition(node, fn);
|
|
144
352
|
if (cbDef) definitions.push(cbDef);
|
|
@@ -164,33 +372,32 @@ export function extractSymbols(tree, _filePath) {
|
|
|
164
372
|
}
|
|
165
373
|
|
|
166
374
|
case 'export_statement': {
|
|
375
|
+
const exportLine = node.startPosition.row + 1;
|
|
167
376
|
const decl = node.childForFieldName('declaration');
|
|
168
377
|
if (decl) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (n)
|
|
179
|
-
exports.push({ name: n.text, kind: 'interface', line: node.startPosition.row + 1 });
|
|
180
|
-
} else if (decl.type === 'type_alias_declaration') {
|
|
378
|
+
const declType = decl.type;
|
|
379
|
+
const kindMap = {
|
|
380
|
+
function_declaration: 'function',
|
|
381
|
+
class_declaration: 'class',
|
|
382
|
+
interface_declaration: 'interface',
|
|
383
|
+
type_alias_declaration: 'type',
|
|
384
|
+
};
|
|
385
|
+
const kind = kindMap[declType];
|
|
386
|
+
if (kind) {
|
|
181
387
|
const n = decl.childForFieldName('name');
|
|
182
|
-
if (n) exports.push({ name: n.text, kind
|
|
388
|
+
if (n) exports.push({ name: n.text, kind, line: exportLine });
|
|
183
389
|
}
|
|
184
390
|
}
|
|
185
391
|
const source = node.childForFieldName('source') || findChild(node, 'string');
|
|
186
392
|
if (source && !decl) {
|
|
187
393
|
const modPath = source.text.replace(/['"]/g, '');
|
|
188
394
|
const reexportNames = extractImportNames(node);
|
|
189
|
-
const
|
|
395
|
+
const nodeText = node.text;
|
|
396
|
+
const isWildcard = nodeText.includes('export *') || nodeText.includes('export*');
|
|
190
397
|
imports.push({
|
|
191
398
|
source: modPath,
|
|
192
399
|
names: reexportNames,
|
|
193
|
-
line:
|
|
400
|
+
line: exportLine,
|
|
194
401
|
reexport: true,
|
|
195
402
|
wildcardReexport: isWildcard && reexportNames.length === 0,
|
|
196
403
|
});
|
|
@@ -212,9 +419,8 @@ export function extractSymbols(tree, _filePath) {
|
|
|
212
419
|
if (fn && fn.text === 'require' && args) {
|
|
213
420
|
const strArg = findChild(args, 'string');
|
|
214
421
|
if (strArg) {
|
|
215
|
-
const modPath = strArg.text.replace(/['"]/g, '');
|
|
216
422
|
imports.push({
|
|
217
|
-
source:
|
|
423
|
+
source: strArg.text.replace(/['"]/g, ''),
|
|
218
424
|
names: [],
|
|
219
425
|
line: node.startPosition.row + 1,
|
|
220
426
|
reexport: true,
|
|
@@ -236,9 +442,8 @@ export function extractSymbols(tree, _filePath) {
|
|
|
236
442
|
if (fn2 && fn2.text === 'require' && args2) {
|
|
237
443
|
const strArg2 = findChild(args2, 'string');
|
|
238
444
|
if (strArg2) {
|
|
239
|
-
const modPath2 = strArg2.text.replace(/['"]/g, '');
|
|
240
445
|
imports.push({
|
|
241
|
-
source:
|
|
446
|
+
source: strArg2.text.replace(/['"]/g, ''),
|
|
242
447
|
names: [],
|
|
243
448
|
line: node.startPosition.row + 1,
|
|
244
449
|
reexport: true,
|
|
@@ -266,6 +471,8 @@ export function extractSymbols(tree, _filePath) {
|
|
|
266
471
|
return { definitions, calls, imports, classes, exports };
|
|
267
472
|
}
|
|
268
473
|
|
|
474
|
+
// ── Shared helpers ──────────────────────────────────────────────────────────
|
|
475
|
+
|
|
269
476
|
function extractInterfaceMethods(bodyNode, interfaceName, definitions) {
|
|
270
477
|
for (let i = 0; i < bodyNode.childCount; i++) {
|
|
271
478
|
const child = bodyNode.child(i);
|
|
@@ -319,52 +526,63 @@ function extractImplementsFromNode(node) {
|
|
|
319
526
|
|
|
320
527
|
function extractReceiverName(objNode) {
|
|
321
528
|
if (!objNode) return undefined;
|
|
322
|
-
|
|
323
|
-
if (
|
|
324
|
-
if (objNode.type === 'super') return 'super';
|
|
529
|
+
const t = objNode.type;
|
|
530
|
+
if (t === 'identifier' || t === 'this' || t === 'super') return objNode.text;
|
|
325
531
|
return objNode.text;
|
|
326
532
|
}
|
|
327
533
|
|
|
328
534
|
function extractCallInfo(fn, callNode) {
|
|
329
|
-
|
|
535
|
+
const fnType = fn.type;
|
|
536
|
+
if (fnType === 'identifier') {
|
|
330
537
|
return { name: fn.text, line: callNode.startPosition.row + 1 };
|
|
331
538
|
}
|
|
332
539
|
|
|
333
|
-
if (
|
|
540
|
+
if (fnType === 'member_expression') {
|
|
334
541
|
const obj = fn.childForFieldName('object');
|
|
335
542
|
const prop = fn.childForFieldName('property');
|
|
336
543
|
if (!prop) return null;
|
|
337
544
|
|
|
338
|
-
|
|
545
|
+
const callLine = callNode.startPosition.row + 1;
|
|
546
|
+
const propText = prop.text;
|
|
547
|
+
|
|
548
|
+
if (propText === 'call' || propText === 'apply' || propText === 'bind') {
|
|
339
549
|
if (obj && obj.type === 'identifier')
|
|
340
|
-
return { name: obj.text, line:
|
|
550
|
+
return { name: obj.text, line: callLine, dynamic: true };
|
|
341
551
|
if (obj && obj.type === 'member_expression') {
|
|
342
552
|
const innerProp = obj.childForFieldName('property');
|
|
343
|
-
if (innerProp)
|
|
344
|
-
return { name: innerProp.text, line: callNode.startPosition.row + 1, dynamic: true };
|
|
553
|
+
if (innerProp) return { name: innerProp.text, line: callLine, dynamic: true };
|
|
345
554
|
}
|
|
346
555
|
}
|
|
347
556
|
|
|
348
|
-
|
|
349
|
-
|
|
557
|
+
const propType = prop.type;
|
|
558
|
+
if (propType === 'string' || propType === 'string_fragment') {
|
|
559
|
+
const methodName = propText.replace(/['"]/g, '');
|
|
350
560
|
if (methodName) {
|
|
351
561
|
const receiver = extractReceiverName(obj);
|
|
352
|
-
return { name: methodName, line:
|
|
562
|
+
return { name: methodName, line: callLine, dynamic: true, receiver };
|
|
353
563
|
}
|
|
354
564
|
}
|
|
355
565
|
|
|
356
566
|
const receiver = extractReceiverName(obj);
|
|
357
|
-
return { name:
|
|
567
|
+
return { name: propText, line: callLine, receiver };
|
|
358
568
|
}
|
|
359
569
|
|
|
360
|
-
if (
|
|
570
|
+
if (fnType === 'subscript_expression') {
|
|
361
571
|
const obj = fn.childForFieldName('object');
|
|
362
572
|
const index = fn.childForFieldName('index');
|
|
363
|
-
if (index
|
|
364
|
-
const
|
|
365
|
-
if (
|
|
366
|
-
const
|
|
367
|
-
|
|
573
|
+
if (index) {
|
|
574
|
+
const indexType = index.type;
|
|
575
|
+
if (indexType === 'string' || indexType === 'template_string') {
|
|
576
|
+
const methodName = index.text.replace(/['"`]/g, '');
|
|
577
|
+
if (methodName && !methodName.includes('$')) {
|
|
578
|
+
const receiver = extractReceiverName(obj);
|
|
579
|
+
return {
|
|
580
|
+
name: methodName,
|
|
581
|
+
line: callNode.startPosition.row + 1,
|
|
582
|
+
dynamic: true,
|
|
583
|
+
receiver,
|
|
584
|
+
};
|
|
585
|
+
}
|
|
368
586
|
}
|
|
369
587
|
}
|
|
370
588
|
}
|
|
@@ -395,7 +613,8 @@ function findFirstStringArg(argsNode) {
|
|
|
395
613
|
function walkCallChain(startNode, methodName) {
|
|
396
614
|
let current = startNode;
|
|
397
615
|
while (current) {
|
|
398
|
-
|
|
616
|
+
const curType = current.type;
|
|
617
|
+
if (curType === 'call_expression') {
|
|
399
618
|
const fn = current.childForFieldName('function');
|
|
400
619
|
if (fn && fn.type === 'member_expression') {
|
|
401
620
|
const prop = fn.childForFieldName('property');
|
|
@@ -403,13 +622,9 @@ function walkCallChain(startNode, methodName) {
|
|
|
403
622
|
return current;
|
|
404
623
|
}
|
|
405
624
|
}
|
|
406
|
-
}
|
|
407
|
-
if (current.type === 'member_expression') {
|
|
408
|
-
const obj = current.childForFieldName('object');
|
|
409
|
-
current = obj;
|
|
410
|
-
} else if (current.type === 'call_expression') {
|
|
411
|
-
const fn = current.childForFieldName('function');
|
|
412
625
|
current = fn;
|
|
626
|
+
} else if (curType === 'member_expression') {
|
|
627
|
+
current = current.childForFieldName('object');
|
|
413
628
|
} else {
|
|
414
629
|
break;
|
|
415
630
|
}
|
|
@@ -506,7 +721,8 @@ function extractSuperclass(heritage) {
|
|
|
506
721
|
function findParentClass(node) {
|
|
507
722
|
let current = node.parent;
|
|
508
723
|
while (current) {
|
|
509
|
-
|
|
724
|
+
const t = current.type;
|
|
725
|
+
if (t === 'class_declaration' || t === 'class') {
|
|
510
726
|
const nameNode = current.childForFieldName('name');
|
|
511
727
|
return nameNode ? nameNode.text : null;
|
|
512
728
|
}
|
package/src/index.js
CHANGED
|
@@ -7,6 +7,15 @@
|
|
|
7
7
|
|
|
8
8
|
// Graph building
|
|
9
9
|
export { buildGraph, collectFiles, loadPathAliases, resolveImportPath } from './builder.js';
|
|
10
|
+
// Co-change analysis
|
|
11
|
+
export {
|
|
12
|
+
analyzeCoChanges,
|
|
13
|
+
coChangeData,
|
|
14
|
+
coChangeForFiles,
|
|
15
|
+
coChangeTopData,
|
|
16
|
+
computeCoChanges,
|
|
17
|
+
scanGitHistory,
|
|
18
|
+
} from './cochange.js';
|
|
10
19
|
// Configuration
|
|
11
20
|
export { loadConfig } from './config.js';
|
|
12
21
|
// Shared constants
|
|
@@ -21,6 +30,7 @@ export {
|
|
|
21
30
|
buildEmbeddings,
|
|
22
31
|
cosineSim,
|
|
23
32
|
DEFAULT_MODEL,
|
|
33
|
+
disposeModel,
|
|
24
34
|
EMBEDDING_STRATEGIES,
|
|
25
35
|
embed,
|
|
26
36
|
estimateTokens,
|
|
@@ -53,7 +63,9 @@ export {
|
|
|
53
63
|
impactAnalysisData,
|
|
54
64
|
moduleMapData,
|
|
55
65
|
queryNameData,
|
|
66
|
+
rolesData,
|
|
56
67
|
statsData,
|
|
68
|
+
VALID_ROLES,
|
|
57
69
|
whereData,
|
|
58
70
|
} from './queries.js';
|
|
59
71
|
// Registry (multi-repo)
|
|
@@ -70,6 +82,7 @@ export {
|
|
|
70
82
|
// Structure analysis
|
|
71
83
|
export {
|
|
72
84
|
buildStructure,
|
|
85
|
+
classifyNodeRoles,
|
|
73
86
|
formatHotspots,
|
|
74
87
|
formatModuleBoundaries,
|
|
75
88
|
formatStructure,
|
package/src/mcp.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { createRequire } from 'node:module';
|
|
9
9
|
import { findCycles } from './cycles.js';
|
|
10
10
|
import { findDbPath } from './db.js';
|
|
11
|
-
import { ALL_SYMBOL_KINDS, diffImpactMermaid } from './queries.js';
|
|
11
|
+
import { ALL_SYMBOL_KINDS, diffImpactMermaid, VALID_ROLES } from './queries.js';
|
|
12
12
|
|
|
13
13
|
const REPO_PROP = {
|
|
14
14
|
repo: {
|
|
@@ -273,6 +273,23 @@ const BASE_TOOLS = [
|
|
|
273
273
|
},
|
|
274
274
|
},
|
|
275
275
|
},
|
|
276
|
+
{
|
|
277
|
+
name: 'node_roles',
|
|
278
|
+
description:
|
|
279
|
+
'Show node role classification (entry, core, utility, adapter, dead, leaf) based on connectivity patterns',
|
|
280
|
+
inputSchema: {
|
|
281
|
+
type: 'object',
|
|
282
|
+
properties: {
|
|
283
|
+
role: {
|
|
284
|
+
type: 'string',
|
|
285
|
+
enum: VALID_ROLES,
|
|
286
|
+
description: 'Filter to a specific role',
|
|
287
|
+
},
|
|
288
|
+
file: { type: 'string', description: 'Scope to a specific file (partial match)' },
|
|
289
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
},
|
|
276
293
|
{
|
|
277
294
|
name: 'hotspots',
|
|
278
295
|
description:
|
|
@@ -295,6 +312,27 @@ const BASE_TOOLS = [
|
|
|
295
312
|
},
|
|
296
313
|
},
|
|
297
314
|
},
|
|
315
|
+
{
|
|
316
|
+
name: 'co_changes',
|
|
317
|
+
description:
|
|
318
|
+
'Find files that historically change together based on git commit history. Requires prior `codegraph co-change --analyze`.',
|
|
319
|
+
inputSchema: {
|
|
320
|
+
type: 'object',
|
|
321
|
+
properties: {
|
|
322
|
+
file: {
|
|
323
|
+
type: 'string',
|
|
324
|
+
description: 'File path (partial match). Omit for top global pairs.',
|
|
325
|
+
},
|
|
326
|
+
limit: { type: 'number', description: 'Max results', default: 20 },
|
|
327
|
+
min_jaccard: {
|
|
328
|
+
type: 'number',
|
|
329
|
+
description: 'Minimum Jaccard similarity (0-1)',
|
|
330
|
+
default: 0.3,
|
|
331
|
+
},
|
|
332
|
+
no_tests: { type: 'boolean', description: 'Exclude test files', default: false },
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
},
|
|
298
336
|
];
|
|
299
337
|
|
|
300
338
|
const LIST_REPOS_TOOL = {
|
|
@@ -372,6 +410,7 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
372
410
|
whereData,
|
|
373
411
|
diffImpactData,
|
|
374
412
|
listFunctionsData,
|
|
413
|
+
rolesData,
|
|
375
414
|
} = await import('./queries.js');
|
|
376
415
|
|
|
377
416
|
const require = createRequire(import.meta.url);
|
|
@@ -540,6 +579,13 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
540
579
|
noTests: args.no_tests,
|
|
541
580
|
});
|
|
542
581
|
break;
|
|
582
|
+
case 'node_roles':
|
|
583
|
+
result = rolesData(dbPath, {
|
|
584
|
+
role: args.role,
|
|
585
|
+
file: args.file,
|
|
586
|
+
noTests: args.no_tests,
|
|
587
|
+
});
|
|
588
|
+
break;
|
|
543
589
|
case 'structure': {
|
|
544
590
|
const { structureData } = await import('./structure.js');
|
|
545
591
|
result = structureData(dbPath, {
|
|
@@ -559,6 +605,21 @@ export async function startMCPServer(customDbPath, options = {}) {
|
|
|
559
605
|
});
|
|
560
606
|
break;
|
|
561
607
|
}
|
|
608
|
+
case 'co_changes': {
|
|
609
|
+
const { coChangeData, coChangeTopData } = await import('./cochange.js');
|
|
610
|
+
result = args.file
|
|
611
|
+
? coChangeData(args.file, dbPath, {
|
|
612
|
+
limit: args.limit,
|
|
613
|
+
minJaccard: args.min_jaccard,
|
|
614
|
+
noTests: args.no_tests,
|
|
615
|
+
})
|
|
616
|
+
: coChangeTopData(dbPath, {
|
|
617
|
+
limit: args.limit,
|
|
618
|
+
minJaccard: args.min_jaccard,
|
|
619
|
+
noTests: args.no_tests,
|
|
620
|
+
});
|
|
621
|
+
break;
|
|
622
|
+
}
|
|
562
623
|
case 'list_repos': {
|
|
563
624
|
const { listRepos, pruneRegistry } = await import('./registry.js');
|
|
564
625
|
pruneRegistry();
|