codemap-ai 3.2.0 → 3.4.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/dist/cli.js +1191 -31
- package/dist/cli.js.map +1 -1
- package/dist/{flow-server-UQEOUP2C.js → flow-server-4XSR5P4N.js} +2 -2
- package/dist/{flow-server-UQEOUP2C.js.map → flow-server-4XSR5P4N.js.map} +1 -1
- package/dist/index.js +424 -17
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.js +1 -1
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -18,6 +18,392 @@ import { createHash } from "crypto";
|
|
|
18
18
|
import { glob } from "glob";
|
|
19
19
|
import { resolve, relative, extname } from "path";
|
|
20
20
|
|
|
21
|
+
// src/analysis/class-hierarchy.ts
|
|
22
|
+
function buildClassHierarchy(nodes) {
|
|
23
|
+
const classes = /* @__PURE__ */ new Map();
|
|
24
|
+
const children = /* @__PURE__ */ new Map();
|
|
25
|
+
const parents = /* @__PURE__ */ new Map();
|
|
26
|
+
for (const node of nodes) {
|
|
27
|
+
if (node.type === "class") {
|
|
28
|
+
const classNode = {
|
|
29
|
+
id: node.id,
|
|
30
|
+
name: node.name,
|
|
31
|
+
file: node.filePath,
|
|
32
|
+
line: node.startLine,
|
|
33
|
+
extends: node.extends || [],
|
|
34
|
+
methods: []
|
|
35
|
+
// Will populate in next step
|
|
36
|
+
};
|
|
37
|
+
classes.set(node.name, classNode);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for (const node of nodes) {
|
|
41
|
+
if (node.type === "method" && node.parentClass) {
|
|
42
|
+
const classNode = classes.get(node.parentClass);
|
|
43
|
+
if (classNode) {
|
|
44
|
+
classNode.methods.push(node.name);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
for (const [className, classNode] of classes) {
|
|
49
|
+
for (const parentName of classNode.extends) {
|
|
50
|
+
if (!children.has(parentName)) {
|
|
51
|
+
children.set(parentName, []);
|
|
52
|
+
}
|
|
53
|
+
children.get(parentName).push(className);
|
|
54
|
+
if (!parents.has(className)) {
|
|
55
|
+
parents.set(className, []);
|
|
56
|
+
}
|
|
57
|
+
parents.get(className).push(parentName);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
classes,
|
|
62
|
+
children,
|
|
63
|
+
parents
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function findMethodDefinition(className, methodName, hierarchy) {
|
|
67
|
+
const visited = /* @__PURE__ */ new Set();
|
|
68
|
+
const queue = [className];
|
|
69
|
+
while (queue.length > 0) {
|
|
70
|
+
const current = queue.shift();
|
|
71
|
+
if (visited.has(current)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
visited.add(current);
|
|
75
|
+
const classNode = hierarchy.classes.get(current);
|
|
76
|
+
if (classNode) {
|
|
77
|
+
if (classNode.methods.includes(methodName)) {
|
|
78
|
+
return classNode;
|
|
79
|
+
}
|
|
80
|
+
const parentNames = hierarchy.parents.get(current) || [];
|
|
81
|
+
for (const parent of parentNames) {
|
|
82
|
+
queue.push(parent);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
function resolveMethodCall(instanceType, methodName, hierarchy) {
|
|
89
|
+
return findMethodDefinition(instanceType, methodName, hierarchy);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/flow/enhanced-resolver.ts
|
|
93
|
+
var EnhancedResolver = class {
|
|
94
|
+
constructor(storage) {
|
|
95
|
+
this.storage = storage;
|
|
96
|
+
}
|
|
97
|
+
hierarchy = null;
|
|
98
|
+
variableTypes = /* @__PURE__ */ new Map();
|
|
99
|
+
/**
|
|
100
|
+
* Build class hierarchy from all nodes in storage
|
|
101
|
+
*/
|
|
102
|
+
buildHierarchy() {
|
|
103
|
+
const nodes = this.getAllNodes();
|
|
104
|
+
this.hierarchy = buildClassHierarchy(nodes);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Load variable type information from storage
|
|
108
|
+
*/
|
|
109
|
+
loadVariableTypes() {
|
|
110
|
+
const stmt = this.storage.db.prepare(`
|
|
111
|
+
SELECT * FROM variable_types
|
|
112
|
+
`);
|
|
113
|
+
const rows = stmt.all();
|
|
114
|
+
for (const row of rows) {
|
|
115
|
+
const scopeId = row.scope_node_id;
|
|
116
|
+
if (!this.variableTypes.has(scopeId)) {
|
|
117
|
+
this.variableTypes.set(scopeId, []);
|
|
118
|
+
}
|
|
119
|
+
this.variableTypes.get(scopeId).push({
|
|
120
|
+
name: row.variable_name,
|
|
121
|
+
type: row.type_name,
|
|
122
|
+
scope: scopeId,
|
|
123
|
+
line: row.line
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Resolve all unresolved edges using type information
|
|
129
|
+
*/
|
|
130
|
+
resolveAllCalls() {
|
|
131
|
+
if (!this.hierarchy) {
|
|
132
|
+
this.buildHierarchy();
|
|
133
|
+
}
|
|
134
|
+
this.loadVariableTypes();
|
|
135
|
+
const resolvedCalls = [];
|
|
136
|
+
const stmt = this.storage.db.prepare(`
|
|
137
|
+
SELECT * FROM edges
|
|
138
|
+
WHERE target_id LIKE 'ref:%'
|
|
139
|
+
AND type = 'calls'
|
|
140
|
+
`);
|
|
141
|
+
const edges = stmt.all();
|
|
142
|
+
for (const edgeRow of edges) {
|
|
143
|
+
const edge = {
|
|
144
|
+
id: edgeRow.id,
|
|
145
|
+
type: edgeRow.type,
|
|
146
|
+
sourceId: edgeRow.source_id,
|
|
147
|
+
targetId: edgeRow.target_id,
|
|
148
|
+
metadata: edgeRow.metadata ? JSON.parse(edgeRow.metadata) : void 0
|
|
149
|
+
};
|
|
150
|
+
const resolved = this.resolveCall(edge);
|
|
151
|
+
if (resolved.targetNode) {
|
|
152
|
+
resolvedCalls.push(resolved);
|
|
153
|
+
this.updateEdgeTarget(edge.id, resolved.targetNode.id);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return resolvedCalls;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Resolve a single call edge
|
|
160
|
+
*/
|
|
161
|
+
resolveCall(edge) {
|
|
162
|
+
const unresolvedName = edge.metadata?.unresolvedName;
|
|
163
|
+
if (!unresolvedName) {
|
|
164
|
+
return {
|
|
165
|
+
originalEdge: edge,
|
|
166
|
+
targetNode: null,
|
|
167
|
+
confidence: "LOW",
|
|
168
|
+
reason: "No unresolved name in metadata"
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const sourceNode = this.storage.getNode(edge.sourceId);
|
|
172
|
+
if (!sourceNode) {
|
|
173
|
+
return {
|
|
174
|
+
originalEdge: edge,
|
|
175
|
+
targetNode: null,
|
|
176
|
+
confidence: "LOW",
|
|
177
|
+
reason: "Source node not found"
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (!unresolvedName.includes(".")) {
|
|
181
|
+
return this.resolveSimpleCall(unresolvedName, sourceNode, edge);
|
|
182
|
+
}
|
|
183
|
+
return this.resolveMethodCall(unresolvedName, sourceNode, edge);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Resolve simple function call (no dot notation)
|
|
187
|
+
*/
|
|
188
|
+
resolveSimpleCall(functionName, sourceNode, edge) {
|
|
189
|
+
const sameFileNodes = this.storage.getNodesByFile(sourceNode.filePath);
|
|
190
|
+
const localMatch = sameFileNodes.find(
|
|
191
|
+
(n) => (n.type === "function" || n.type === "method") && n.name === functionName
|
|
192
|
+
);
|
|
193
|
+
if (localMatch) {
|
|
194
|
+
return {
|
|
195
|
+
originalEdge: edge,
|
|
196
|
+
targetNode: localMatch,
|
|
197
|
+
confidence: "HIGH",
|
|
198
|
+
reason: "Found in same file"
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
const globalMatches = this.storage.searchNodesByName(functionName);
|
|
202
|
+
if (globalMatches.length === 1) {
|
|
203
|
+
return {
|
|
204
|
+
originalEdge: edge,
|
|
205
|
+
targetNode: globalMatches[0],
|
|
206
|
+
confidence: "MEDIUM",
|
|
207
|
+
reason: "Single global match"
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
if (globalMatches.length > 1) {
|
|
211
|
+
const sourceDir = sourceNode.filePath.split("/").slice(0, -1).join("/");
|
|
212
|
+
const sameDirMatch = globalMatches.find((n) => n.filePath.startsWith(sourceDir));
|
|
213
|
+
if (sameDirMatch) {
|
|
214
|
+
return {
|
|
215
|
+
originalEdge: edge,
|
|
216
|
+
targetNode: sameDirMatch,
|
|
217
|
+
confidence: "MEDIUM",
|
|
218
|
+
reason: "Multiple matches, picked same directory"
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
originalEdge: edge,
|
|
223
|
+
targetNode: globalMatches[0],
|
|
224
|
+
confidence: "LOW",
|
|
225
|
+
reason: `Multiple matches (${globalMatches.length}), picked first`
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
originalEdge: edge,
|
|
230
|
+
targetNode: null,
|
|
231
|
+
confidence: "LOW",
|
|
232
|
+
reason: "No matches found"
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Resolve method call using type information
|
|
237
|
+
* Pattern: obj.method() or self.method()
|
|
238
|
+
*/
|
|
239
|
+
resolveMethodCall(fullName, sourceNode, edge) {
|
|
240
|
+
const parts = fullName.split(".");
|
|
241
|
+
const varName = parts[0];
|
|
242
|
+
const methodName = parts.slice(1).join(".");
|
|
243
|
+
if (varName === "self" || varName === "this") {
|
|
244
|
+
return this.resolveSelfMethodCall(methodName, sourceNode, edge);
|
|
245
|
+
}
|
|
246
|
+
const scopeTypes = this.variableTypes.get(sourceNode.id) || [];
|
|
247
|
+
const varType = scopeTypes.find((v) => v.name === varName);
|
|
248
|
+
if (!varType) {
|
|
249
|
+
return this.resolveSimpleCall(methodName, sourceNode, edge);
|
|
250
|
+
}
|
|
251
|
+
if (!this.hierarchy) {
|
|
252
|
+
return {
|
|
253
|
+
originalEdge: edge,
|
|
254
|
+
targetNode: null,
|
|
255
|
+
confidence: "LOW",
|
|
256
|
+
reason: "Class hierarchy not built"
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const classNode = resolveMethodCall(varType.type, methodName, this.hierarchy);
|
|
260
|
+
if (classNode) {
|
|
261
|
+
const methodNode = this.findMethodInClass(classNode.name, methodName);
|
|
262
|
+
if (methodNode) {
|
|
263
|
+
return {
|
|
264
|
+
originalEdge: edge,
|
|
265
|
+
targetNode: methodNode,
|
|
266
|
+
confidence: "HIGH",
|
|
267
|
+
reason: `Resolved via type tracking: ${varName}: ${varType.type}`
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
originalEdge: edge,
|
|
273
|
+
targetNode: null,
|
|
274
|
+
confidence: "LOW",
|
|
275
|
+
reason: `Type found (${varType.type}) but method not resolved`
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Resolve self.method() or this.method()
|
|
280
|
+
*/
|
|
281
|
+
resolveSelfMethodCall(methodName, sourceNode, edge) {
|
|
282
|
+
const parentClass = sourceNode.metadata?.parentClass;
|
|
283
|
+
if (!parentClass) {
|
|
284
|
+
return {
|
|
285
|
+
originalEdge: edge,
|
|
286
|
+
targetNode: null,
|
|
287
|
+
confidence: "LOW",
|
|
288
|
+
reason: "self/this call but no parent class"
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
if (!this.hierarchy) {
|
|
292
|
+
return {
|
|
293
|
+
originalEdge: edge,
|
|
294
|
+
targetNode: null,
|
|
295
|
+
confidence: "LOW",
|
|
296
|
+
reason: "Class hierarchy not built"
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const classNode = resolveMethodCall(parentClass, methodName, this.hierarchy);
|
|
300
|
+
if (classNode) {
|
|
301
|
+
const methodNode2 = this.findMethodInClass(classNode.name, methodName);
|
|
302
|
+
if (methodNode2) {
|
|
303
|
+
return {
|
|
304
|
+
originalEdge: edge,
|
|
305
|
+
targetNode: methodNode2,
|
|
306
|
+
confidence: "HIGH",
|
|
307
|
+
reason: `Resolved self.${methodName} in ${parentClass}`
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const sameFileNodes = this.storage.getNodesByFile(sourceNode.filePath);
|
|
312
|
+
const methodNode = sameFileNodes.find(
|
|
313
|
+
(n) => n.type === "method" && n.name === methodName
|
|
314
|
+
);
|
|
315
|
+
if (methodNode) {
|
|
316
|
+
return {
|
|
317
|
+
originalEdge: edge,
|
|
318
|
+
targetNode: methodNode,
|
|
319
|
+
confidence: "MEDIUM",
|
|
320
|
+
reason: "Found method in same file"
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
originalEdge: edge,
|
|
325
|
+
targetNode: null,
|
|
326
|
+
confidence: "LOW",
|
|
327
|
+
reason: `Method ${methodName} not found in ${parentClass}`
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Find method node in a class
|
|
332
|
+
*/
|
|
333
|
+
findMethodInClass(className, methodName) {
|
|
334
|
+
const stmt = this.storage.db.prepare(`
|
|
335
|
+
SELECT * FROM nodes
|
|
336
|
+
WHERE type = 'method'
|
|
337
|
+
AND name = ?
|
|
338
|
+
AND json_extract(metadata, '$.parentClass') = ?
|
|
339
|
+
`);
|
|
340
|
+
const row = stmt.get(methodName, className);
|
|
341
|
+
if (!row) return null;
|
|
342
|
+
return {
|
|
343
|
+
id: row.id,
|
|
344
|
+
type: row.type,
|
|
345
|
+
name: row.name,
|
|
346
|
+
filePath: row.file_path,
|
|
347
|
+
startLine: row.start_line,
|
|
348
|
+
endLine: row.end_line,
|
|
349
|
+
language: row.language,
|
|
350
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Get all nodes from storage
|
|
355
|
+
*/
|
|
356
|
+
getAllNodes() {
|
|
357
|
+
const stmt = this.storage.db.prepare("SELECT * FROM nodes");
|
|
358
|
+
const rows = stmt.all();
|
|
359
|
+
return rows.map((row) => ({
|
|
360
|
+
id: row.id,
|
|
361
|
+
type: row.type,
|
|
362
|
+
name: row.name,
|
|
363
|
+
filePath: row.file_path,
|
|
364
|
+
startLine: row.start_line,
|
|
365
|
+
endLine: row.end_line,
|
|
366
|
+
language: row.language,
|
|
367
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
368
|
+
}));
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Update edge target in database
|
|
372
|
+
*/
|
|
373
|
+
updateEdgeTarget(edgeId, newTargetId) {
|
|
374
|
+
const stmt = this.storage.db.prepare(`
|
|
375
|
+
UPDATE edges
|
|
376
|
+
SET target_id = ?
|
|
377
|
+
WHERE id = ?
|
|
378
|
+
`);
|
|
379
|
+
stmt.run(newTargetId, edgeId);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get resolution statistics
|
|
383
|
+
*/
|
|
384
|
+
getStats() {
|
|
385
|
+
const totalStmt = this.storage.db.prepare(`
|
|
386
|
+
SELECT COUNT(*) as count FROM edges WHERE type = 'calls'
|
|
387
|
+
`);
|
|
388
|
+
const totalRow = totalStmt.get();
|
|
389
|
+
const totalCalls = totalRow.count;
|
|
390
|
+
const resolvedStmt = this.storage.db.prepare(`
|
|
391
|
+
SELECT COUNT(*) as count FROM edges
|
|
392
|
+
WHERE type = 'calls' AND target_id NOT LIKE 'ref:%'
|
|
393
|
+
`);
|
|
394
|
+
const resolvedRow = resolvedStmt.get();
|
|
395
|
+
const resolvedCalls = resolvedRow.count;
|
|
396
|
+
const unresolvedCalls = totalCalls - resolvedCalls;
|
|
397
|
+
const resolutionRate = totalCalls > 0 ? resolvedCalls / totalCalls * 100 : 0;
|
|
398
|
+
return {
|
|
399
|
+
totalCalls,
|
|
400
|
+
resolvedCalls,
|
|
401
|
+
unresolvedCalls,
|
|
402
|
+
resolutionRate
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
21
407
|
// src/parsers/base.ts
|
|
22
408
|
var BaseParser = class {
|
|
23
409
|
nodeIdCounter = 0;
|
|
@@ -272,31 +658,38 @@ var TypeScriptParser = class extends BaseParser {
|
|
|
272
658
|
handleClass(node, filePath, analysis, parentId) {
|
|
273
659
|
const nameNode = node.childForFieldName("name");
|
|
274
660
|
if (!nameNode) return;
|
|
661
|
+
const extends_ = [];
|
|
662
|
+
for (const child of node.children) {
|
|
663
|
+
if (child.type === "class_heritage") {
|
|
664
|
+
const extendsClause = child.children.find((c) => c.type === "extends_clause");
|
|
665
|
+
if (extendsClause) {
|
|
666
|
+
const baseClass = extendsClause.children.find((c) => c.type === "identifier");
|
|
667
|
+
if (baseClass) {
|
|
668
|
+
extends_.push(baseClass.text);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
275
673
|
const classNode = this.createNode(
|
|
276
674
|
"class",
|
|
277
675
|
nameNode.text,
|
|
278
676
|
filePath,
|
|
279
677
|
node.startPosition.row + 1,
|
|
280
|
-
node.endPosition.row + 1
|
|
678
|
+
node.endPosition.row + 1,
|
|
679
|
+
{
|
|
680
|
+
extends: extends_
|
|
681
|
+
}
|
|
281
682
|
);
|
|
282
683
|
analysis.nodes.push(classNode);
|
|
283
684
|
analysis.edges.push(
|
|
284
685
|
this.createEdge("contains", parentId, classNode.id)
|
|
285
686
|
);
|
|
286
|
-
for (const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
analysis.edges.push(
|
|
293
|
-
this.createEdge("extends", classNode.id, `ref:${baseClass.text}`, {
|
|
294
|
-
unresolvedName: baseClass.text
|
|
295
|
-
})
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
687
|
+
for (const baseClass of extends_) {
|
|
688
|
+
analysis.edges.push(
|
|
689
|
+
this.createEdge("extends", classNode.id, `ref:${baseClass}`, {
|
|
690
|
+
unresolvedName: baseClass
|
|
691
|
+
})
|
|
692
|
+
);
|
|
300
693
|
}
|
|
301
694
|
const body = node.childForFieldName("body");
|
|
302
695
|
if (body) {
|
|
@@ -310,6 +703,8 @@ var TypeScriptParser = class extends BaseParser {
|
|
|
310
703
|
handleMethod(node, filePath, analysis, parentId) {
|
|
311
704
|
const nameNode = node.childForFieldName("name");
|
|
312
705
|
if (!nameNode) return;
|
|
706
|
+
const parentNode = analysis.nodes.find((n) => n.id === parentId);
|
|
707
|
+
const parentClass = parentNode?.type === "class" ? parentNode.name : void 0;
|
|
313
708
|
const methodNode = this.createNode(
|
|
314
709
|
"method",
|
|
315
710
|
nameNode.text,
|
|
@@ -318,7 +713,8 @@ var TypeScriptParser = class extends BaseParser {
|
|
|
318
713
|
node.endPosition.row + 1,
|
|
319
714
|
{
|
|
320
715
|
static: node.children.some((c) => c.type === "static"),
|
|
321
|
-
async: node.children.some((c) => c.type === "async")
|
|
716
|
+
async: node.children.some((c) => c.type === "async"),
|
|
717
|
+
parentClass
|
|
322
718
|
}
|
|
323
719
|
);
|
|
324
720
|
analysis.nodes.push(methodNode);
|
|
@@ -753,6 +1149,8 @@ var PythonParser = class extends BaseParser {
|
|
|
753
1149
|
node.endPosition.row + 1,
|
|
754
1150
|
{
|
|
755
1151
|
bases,
|
|
1152
|
+
extends: bases,
|
|
1153
|
+
// Add extends for consistency with TypeScript
|
|
756
1154
|
isPrivate: name.startsWith("_")
|
|
757
1155
|
}
|
|
758
1156
|
);
|
|
@@ -790,6 +1188,8 @@ var PythonParser = class extends BaseParser {
|
|
|
790
1188
|
const nameNode = node.childForFieldName("name");
|
|
791
1189
|
if (!nameNode) return;
|
|
792
1190
|
const name = nameNode.text;
|
|
1191
|
+
const parentNode = analysis.nodes.find((n) => n.id === classId);
|
|
1192
|
+
const parentClass = parentNode?.type === "class" ? parentNode.name : void 0;
|
|
793
1193
|
const decorators = [];
|
|
794
1194
|
if (node.parent?.type === "decorated_definition") {
|
|
795
1195
|
for (const sibling of node.parent.children) {
|
|
@@ -818,7 +1218,8 @@ var PythonParser = class extends BaseParser {
|
|
|
818
1218
|
classmethod: isClassMethod,
|
|
819
1219
|
property: isProperty,
|
|
820
1220
|
isPrivate: name.startsWith("_"),
|
|
821
|
-
isDunder: name.startsWith("__") && name.endsWith("__")
|
|
1221
|
+
isDunder: name.startsWith("__") && name.endsWith("__"),
|
|
1222
|
+
parentClass
|
|
822
1223
|
}
|
|
823
1224
|
);
|
|
824
1225
|
analysis.nodes.push(methodNode);
|
|
@@ -1021,6 +1422,12 @@ var FlowBuilder = class {
|
|
|
1021
1422
|
currentFile: ""
|
|
1022
1423
|
});
|
|
1023
1424
|
this.resolveAllCalls();
|
|
1425
|
+
const enhancedResolver = new EnhancedResolver(this.storage);
|
|
1426
|
+
enhancedResolver.buildHierarchy();
|
|
1427
|
+
const enhancedResolved = enhancedResolver.resolveAllCalls();
|
|
1428
|
+
if (enhancedResolved.length > 0) {
|
|
1429
|
+
console.log(`\u2728 Enhanced resolver: resolved ${enhancedResolved.length} additional calls`);
|
|
1430
|
+
}
|
|
1024
1431
|
const stats = this.storage.getStats();
|
|
1025
1432
|
this.emitProgress({
|
|
1026
1433
|
phase: "complete",
|
|
@@ -1383,6 +1790,87 @@ function getGitStatus(rootPath) {
|
|
|
1383
1790
|
|
|
1384
1791
|
// src/analysis/imports.ts
|
|
1385
1792
|
import { readFileSync as readFileSync2 } from "fs";
|
|
1793
|
+
function isJavaScriptBuiltin(identifier) {
|
|
1794
|
+
const builtins = /* @__PURE__ */ new Set([
|
|
1795
|
+
"Object",
|
|
1796
|
+
"Array",
|
|
1797
|
+
"String",
|
|
1798
|
+
"Number",
|
|
1799
|
+
"Boolean",
|
|
1800
|
+
"Date",
|
|
1801
|
+
"RegExp",
|
|
1802
|
+
"Error",
|
|
1803
|
+
"TypeError",
|
|
1804
|
+
"SyntaxError",
|
|
1805
|
+
"ReferenceError",
|
|
1806
|
+
"Promise",
|
|
1807
|
+
"Map",
|
|
1808
|
+
"Set",
|
|
1809
|
+
"WeakMap",
|
|
1810
|
+
"WeakSet",
|
|
1811
|
+
"JSON",
|
|
1812
|
+
"Math",
|
|
1813
|
+
"Infinity",
|
|
1814
|
+
"NaN",
|
|
1815
|
+
"undefined",
|
|
1816
|
+
"Function",
|
|
1817
|
+
"Symbol",
|
|
1818
|
+
"Proxy",
|
|
1819
|
+
"Reflect",
|
|
1820
|
+
"Int8Array",
|
|
1821
|
+
"Uint8Array",
|
|
1822
|
+
"Int16Array",
|
|
1823
|
+
"Uint16Array",
|
|
1824
|
+
"Int32Array",
|
|
1825
|
+
"Uint32Array",
|
|
1826
|
+
"Float32Array",
|
|
1827
|
+
"Float64Array",
|
|
1828
|
+
"BigInt",
|
|
1829
|
+
"ArrayBuffer",
|
|
1830
|
+
"DataView",
|
|
1831
|
+
"Intl",
|
|
1832
|
+
"WebAssembly",
|
|
1833
|
+
"Buffer",
|
|
1834
|
+
"URL",
|
|
1835
|
+
"URLSearchParams",
|
|
1836
|
+
"console",
|
|
1837
|
+
"process",
|
|
1838
|
+
"global",
|
|
1839
|
+
"window",
|
|
1840
|
+
"document",
|
|
1841
|
+
// Common keywords that look like types
|
|
1842
|
+
"TRUE",
|
|
1843
|
+
"FALSE",
|
|
1844
|
+
"NULL"
|
|
1845
|
+
]);
|
|
1846
|
+
return builtins.has(identifier);
|
|
1847
|
+
}
|
|
1848
|
+
function isPythonBuiltin(identifier) {
|
|
1849
|
+
const builtins = /* @__PURE__ */ new Set([
|
|
1850
|
+
"True",
|
|
1851
|
+
"False",
|
|
1852
|
+
"None",
|
|
1853
|
+
"Exception",
|
|
1854
|
+
"BaseException",
|
|
1855
|
+
"ValueError",
|
|
1856
|
+
"TypeError",
|
|
1857
|
+
"KeyError",
|
|
1858
|
+
"AttributeError",
|
|
1859
|
+
"IndexError",
|
|
1860
|
+
"RuntimeError",
|
|
1861
|
+
"NotImplementedError",
|
|
1862
|
+
"Dict",
|
|
1863
|
+
"List",
|
|
1864
|
+
"Tuple",
|
|
1865
|
+
"Set",
|
|
1866
|
+
"FrozenSet",
|
|
1867
|
+
"Optional",
|
|
1868
|
+
"Union",
|
|
1869
|
+
"Any",
|
|
1870
|
+
"Callable"
|
|
1871
|
+
]);
|
|
1872
|
+
return builtins.has(identifier);
|
|
1873
|
+
}
|
|
1386
1874
|
function analyzeJavaScriptImports(filePath) {
|
|
1387
1875
|
const content = readFileSync2(filePath, "utf-8");
|
|
1388
1876
|
const lines = content.split("\n");
|
|
@@ -1423,19 +1911,52 @@ function analyzeJavaScriptImports(filePath) {
|
|
|
1423
1911
|
}
|
|
1424
1912
|
}
|
|
1425
1913
|
const missing = [];
|
|
1426
|
-
const
|
|
1427
|
-
for (
|
|
1428
|
-
|
|
1914
|
+
const localDefinitions = /* @__PURE__ */ new Set();
|
|
1915
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1916
|
+
const line = lines[i];
|
|
1917
|
+
const classMatch = line.match(/^(?:export\s+)?class\s+([A-Z][a-zA-Z0-9]*)/);
|
|
1918
|
+
if (classMatch) {
|
|
1919
|
+
localDefinitions.add(classMatch[1]);
|
|
1920
|
+
}
|
|
1921
|
+
const funcMatch = line.match(/^(?:export\s+)?(?:async\s+)?function\s+([A-Z][a-zA-Z0-9]*)/);
|
|
1922
|
+
if (funcMatch) {
|
|
1923
|
+
localDefinitions.add(funcMatch[1]);
|
|
1924
|
+
}
|
|
1925
|
+
const constMatch = line.match(/^(?:export\s+)?const\s+([A-Z][a-z][a-zA-Z0-9]*)\s*=/);
|
|
1926
|
+
if (constMatch) {
|
|
1927
|
+
localDefinitions.add(constMatch[1]);
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
const potentialImports = /* @__PURE__ */ new Set();
|
|
1931
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1932
|
+
let line = lines[i];
|
|
1933
|
+
if (line.trim().startsWith("import ") || line.trim().startsWith("//") || line.trim().startsWith("*") || line.trim().startsWith("#")) {
|
|
1934
|
+
continue;
|
|
1935
|
+
}
|
|
1936
|
+
line = line.replace(/\/\/.*$/g, "");
|
|
1937
|
+
line = line.replace(/\/\*.*?\*\//g, "");
|
|
1938
|
+
line = line.replace(/[`"'].*?[`"']/g, "");
|
|
1939
|
+
const matches = line.matchAll(/(?<!\.)([A-Z][a-z][a-zA-Z0-9]*)(?!\.)/g);
|
|
1940
|
+
for (const match of matches) {
|
|
1941
|
+
const identifier = match[1];
|
|
1942
|
+
if (!isJavaScriptBuiltin(identifier)) {
|
|
1943
|
+
potentialImports.add(identifier);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
for (const identifier of potentialImports) {
|
|
1948
|
+
if (!imports.has(identifier) && !localDefinitions.has(identifier)) {
|
|
1429
1949
|
const usageLines = [];
|
|
1430
1950
|
for (let i = 0; i < lines.length; i++) {
|
|
1431
1951
|
const line = lines[i];
|
|
1432
|
-
|
|
1952
|
+
const regex = new RegExp(`\\b${identifier}\\b`);
|
|
1953
|
+
if (regex.test(line) && !line.trim().startsWith("//")) {
|
|
1433
1954
|
usageLines.push(i + 1);
|
|
1434
1955
|
}
|
|
1435
1956
|
}
|
|
1436
1957
|
if (usageLines.length > 0) {
|
|
1437
1958
|
missing.push({
|
|
1438
|
-
identifier
|
|
1959
|
+
identifier,
|
|
1439
1960
|
line: usageLines[0],
|
|
1440
1961
|
type: "missing",
|
|
1441
1962
|
usedAt: usageLines
|
|
@@ -1484,7 +2005,56 @@ function analyzePythonImports(filePath) {
|
|
|
1484
2005
|
unused.push({ identifier, line, type: "unused" });
|
|
1485
2006
|
}
|
|
1486
2007
|
}
|
|
1487
|
-
|
|
2008
|
+
const missing = [];
|
|
2009
|
+
const localDefinitions = /* @__PURE__ */ new Set();
|
|
2010
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2011
|
+
const line = lines[i];
|
|
2012
|
+
const classMatch = line.match(/^class\s+([A-Z][a-zA-Z0-9_]*)/);
|
|
2013
|
+
if (classMatch) {
|
|
2014
|
+
localDefinitions.add(classMatch[1]);
|
|
2015
|
+
}
|
|
2016
|
+
const funcMatch = line.match(/^(?:async\s+)?def\s+([A-Z][a-zA-Z0-9_]*)/);
|
|
2017
|
+
if (funcMatch) {
|
|
2018
|
+
localDefinitions.add(funcMatch[1]);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
const potentialImports = /* @__PURE__ */ new Set();
|
|
2022
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2023
|
+
let line = lines[i];
|
|
2024
|
+
if (line.trim().startsWith("import ") || line.trim().startsWith("from ") || line.trim().startsWith("#")) {
|
|
2025
|
+
continue;
|
|
2026
|
+
}
|
|
2027
|
+
line = line.replace(/#.*$/g, "");
|
|
2028
|
+
line = line.replace(/[`"'f].*?[`"']/g, "");
|
|
2029
|
+
const matches = line.matchAll(/(?<!\.)([A-Z][a-zA-Z0-9_]*)(?!\.)/g);
|
|
2030
|
+
for (const match of matches) {
|
|
2031
|
+
const identifier = match[1];
|
|
2032
|
+
if (!isPythonBuiltin(identifier)) {
|
|
2033
|
+
potentialImports.add(identifier);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
for (const identifier of potentialImports) {
|
|
2038
|
+
if (!imports.has(identifier) && !localDefinitions.has(identifier)) {
|
|
2039
|
+
const usageLines = [];
|
|
2040
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2041
|
+
const line = lines[i];
|
|
2042
|
+
const regex = new RegExp(`\\b${identifier}\\b`);
|
|
2043
|
+
if (regex.test(line) && !line.trim().startsWith("#")) {
|
|
2044
|
+
usageLines.push(i + 1);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
if (usageLines.length > 0) {
|
|
2048
|
+
missing.push({
|
|
2049
|
+
identifier,
|
|
2050
|
+
line: usageLines[0],
|
|
2051
|
+
type: "missing",
|
|
2052
|
+
usedAt: usageLines
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
return { missing, unused };
|
|
1488
2058
|
}
|
|
1489
2059
|
function analyzeImports(filePath) {
|
|
1490
2060
|
if (filePath.endsWith(".py")) {
|
|
@@ -1650,6 +2220,549 @@ function calculateServiceBlastRadius(graph, serviceName) {
|
|
|
1650
2220
|
return Array.from(affected);
|
|
1651
2221
|
}
|
|
1652
2222
|
|
|
2223
|
+
// src/analysis/line-diff.ts
|
|
2224
|
+
import { execSync as execSync2 } from "child_process";
|
|
2225
|
+
function parseLineLevelDiff(projectRoot) {
|
|
2226
|
+
try {
|
|
2227
|
+
const diffOutput = execSync2("git diff HEAD", {
|
|
2228
|
+
cwd: projectRoot,
|
|
2229
|
+
encoding: "utf-8",
|
|
2230
|
+
maxBuffer: 10 * 1024 * 1024
|
|
2231
|
+
// 10MB buffer
|
|
2232
|
+
});
|
|
2233
|
+
if (!diffOutput.trim()) {
|
|
2234
|
+
return {
|
|
2235
|
+
removedLines: [],
|
|
2236
|
+
addedLines: [],
|
|
2237
|
+
modifiedLines: []
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
return parseDiffOutput(diffOutput);
|
|
2241
|
+
} catch (error) {
|
|
2242
|
+
return {
|
|
2243
|
+
removedLines: [],
|
|
2244
|
+
addedLines: [],
|
|
2245
|
+
modifiedLines: []
|
|
2246
|
+
};
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
function parseDiffOutput(diffOutput) {
|
|
2250
|
+
const removedLines = [];
|
|
2251
|
+
const addedLines = [];
|
|
2252
|
+
let currentFile = "";
|
|
2253
|
+
let currentFunction = "";
|
|
2254
|
+
let removedLineNumber = 0;
|
|
2255
|
+
let addedLineNumber = 0;
|
|
2256
|
+
const lines = diffOutput.split("\n");
|
|
2257
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2258
|
+
const line = lines[i];
|
|
2259
|
+
if (line.startsWith("diff --git")) {
|
|
2260
|
+
const match = line.match(/b\/(.*?)$/);
|
|
2261
|
+
if (match) {
|
|
2262
|
+
currentFile = match[1].trim();
|
|
2263
|
+
}
|
|
2264
|
+
continue;
|
|
2265
|
+
}
|
|
2266
|
+
if (line.startsWith("@@")) {
|
|
2267
|
+
const match = line.match(/@@ -(\d+),\d+ \+(\d+),\d+ @@(.*)/);
|
|
2268
|
+
if (match) {
|
|
2269
|
+
removedLineNumber = parseInt(match[1]);
|
|
2270
|
+
addedLineNumber = parseInt(match[2]);
|
|
2271
|
+
const context = match[3].trim();
|
|
2272
|
+
if (context) {
|
|
2273
|
+
currentFunction = extractFunctionName(context);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
continue;
|
|
2277
|
+
}
|
|
2278
|
+
if (line.startsWith("---") || line.startsWith("+++") || line.startsWith("index ")) {
|
|
2279
|
+
continue;
|
|
2280
|
+
}
|
|
2281
|
+
if (line.startsWith("-") && !line.startsWith("---")) {
|
|
2282
|
+
const content = line.substring(1);
|
|
2283
|
+
if (content.trim()) {
|
|
2284
|
+
removedLines.push({
|
|
2285
|
+
file: currentFile,
|
|
2286
|
+
lineNumber: removedLineNumber,
|
|
2287
|
+
content,
|
|
2288
|
+
inFunction: currentFunction || void 0
|
|
2289
|
+
});
|
|
2290
|
+
}
|
|
2291
|
+
removedLineNumber++;
|
|
2292
|
+
continue;
|
|
2293
|
+
}
|
|
2294
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
2295
|
+
const content = line.substring(1);
|
|
2296
|
+
if (content.trim()) {
|
|
2297
|
+
addedLines.push({
|
|
2298
|
+
file: currentFile,
|
|
2299
|
+
lineNumber: addedLineNumber,
|
|
2300
|
+
content,
|
|
2301
|
+
inFunction: currentFunction || void 0
|
|
2302
|
+
});
|
|
2303
|
+
}
|
|
2304
|
+
addedLineNumber++;
|
|
2305
|
+
continue;
|
|
2306
|
+
}
|
|
2307
|
+
if (line.startsWith(" ") || !line.startsWith("-") && !line.startsWith("+")) {
|
|
2308
|
+
removedLineNumber++;
|
|
2309
|
+
addedLineNumber++;
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
const modifiedLines = matchModifiedLines(removedLines, addedLines);
|
|
2313
|
+
return {
|
|
2314
|
+
removedLines,
|
|
2315
|
+
addedLines,
|
|
2316
|
+
modifiedLines
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2319
|
+
function extractFunctionName(context) {
|
|
2320
|
+
context = context.replace(/^(function|def|class|const|let|var|async|export)\s+/, "");
|
|
2321
|
+
const patterns = [
|
|
2322
|
+
/^(\w+)\s*\(/,
|
|
2323
|
+
// functionName(
|
|
2324
|
+
/^(\w+)\s*=/,
|
|
2325
|
+
// functionName =
|
|
2326
|
+
/^(\w+)\s*{/,
|
|
2327
|
+
// ClassName {
|
|
2328
|
+
/^(\w+)\s*:/
|
|
2329
|
+
// functionName:
|
|
2330
|
+
];
|
|
2331
|
+
for (const pattern of patterns) {
|
|
2332
|
+
const match = context.match(pattern);
|
|
2333
|
+
if (match) {
|
|
2334
|
+
return match[1];
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
const firstWord = context.split(/\s+/)[0];
|
|
2338
|
+
return firstWord || "";
|
|
2339
|
+
}
|
|
2340
|
+
function matchModifiedLines(removedLines, addedLines) {
|
|
2341
|
+
const modifiedLines = [];
|
|
2342
|
+
const removedByLocation = /* @__PURE__ */ new Map();
|
|
2343
|
+
const addedByLocation = /* @__PURE__ */ new Map();
|
|
2344
|
+
for (const removed of removedLines) {
|
|
2345
|
+
const key = `${removed.file}:${removed.inFunction || "global"}`;
|
|
2346
|
+
if (!removedByLocation.has(key)) {
|
|
2347
|
+
removedByLocation.set(key, []);
|
|
2348
|
+
}
|
|
2349
|
+
removedByLocation.get(key).push(removed);
|
|
2350
|
+
}
|
|
2351
|
+
for (const added of addedLines) {
|
|
2352
|
+
const key = `${added.file}:${added.inFunction || "global"}`;
|
|
2353
|
+
if (!addedByLocation.has(key)) {
|
|
2354
|
+
addedByLocation.set(key, []);
|
|
2355
|
+
}
|
|
2356
|
+
addedByLocation.get(key).push(added);
|
|
2357
|
+
}
|
|
2358
|
+
for (const [location, removedLinesInLocation] of removedByLocation) {
|
|
2359
|
+
const addedLinesInLocation = addedByLocation.get(location) || [];
|
|
2360
|
+
for (const removed of removedLinesInLocation) {
|
|
2361
|
+
const matchingAdded = addedLinesInLocation.find(
|
|
2362
|
+
(added) => Math.abs(added.lineNumber - removed.lineNumber) <= 2
|
|
2363
|
+
);
|
|
2364
|
+
if (matchingAdded) {
|
|
2365
|
+
modifiedLines.push({
|
|
2366
|
+
before: removed,
|
|
2367
|
+
after: matchingAdded
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
return modifiedLines;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
// src/analysis/removed-calls.ts
|
|
2376
|
+
function extractRemovedCalls(removedLines) {
|
|
2377
|
+
const removedCalls = [];
|
|
2378
|
+
for (const line of removedLines) {
|
|
2379
|
+
const calls = parseLineForCalls(line.content, line.file);
|
|
2380
|
+
for (const call of calls) {
|
|
2381
|
+
removedCalls.push({
|
|
2382
|
+
callee: call.name,
|
|
2383
|
+
removedFrom: line.inFunction || "unknown",
|
|
2384
|
+
file: line.file,
|
|
2385
|
+
lineNumber: line.lineNumber,
|
|
2386
|
+
rawContent: line.content.trim()
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
return removedCalls;
|
|
2391
|
+
}
|
|
2392
|
+
function parseLineForCalls(lineContent, filePath) {
|
|
2393
|
+
const calls = [];
|
|
2394
|
+
if (isCommentOrString(lineContent)) {
|
|
2395
|
+
return calls;
|
|
2396
|
+
}
|
|
2397
|
+
lineContent = lineContent.replace(/\/\/.*$/g, "");
|
|
2398
|
+
lineContent = lineContent.replace(/#.*$/g, "");
|
|
2399
|
+
lineContent = lineContent.replace(/\/\*.*?\*\//g, "");
|
|
2400
|
+
lineContent = lineContent.replace(/["'`].*?["'`]/g, "");
|
|
2401
|
+
const isJS = /\.(js|ts|jsx|tsx)$/.test(filePath);
|
|
2402
|
+
const isPython = /\.py$/.test(filePath);
|
|
2403
|
+
if (isJS) {
|
|
2404
|
+
calls.push(...extractJavaScriptCalls(lineContent));
|
|
2405
|
+
}
|
|
2406
|
+
if (isPython) {
|
|
2407
|
+
calls.push(...extractPythonCalls(lineContent));
|
|
2408
|
+
}
|
|
2409
|
+
return calls;
|
|
2410
|
+
}
|
|
2411
|
+
function isCommentOrString(line) {
|
|
2412
|
+
const trimmed = line.trim();
|
|
2413
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*") || trimmed.startsWith("#")) {
|
|
2414
|
+
return true;
|
|
2415
|
+
}
|
|
2416
|
+
const stringPrefixes = ['"', "'", "`"];
|
|
2417
|
+
if (stringPrefixes.some((prefix) => trimmed.startsWith(prefix))) {
|
|
2418
|
+
return true;
|
|
2419
|
+
}
|
|
2420
|
+
return false;
|
|
2421
|
+
}
|
|
2422
|
+
function extractJavaScriptCalls(lineContent) {
|
|
2423
|
+
const calls = [];
|
|
2424
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2425
|
+
const simplePattern = /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
|
|
2426
|
+
let match;
|
|
2427
|
+
while ((match = simplePattern.exec(lineContent)) !== null) {
|
|
2428
|
+
const functionName = match[1];
|
|
2429
|
+
if (!isJavaScriptKeyword(functionName) && !seen.has(functionName)) {
|
|
2430
|
+
calls.push({ name: functionName });
|
|
2431
|
+
seen.add(functionName);
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
const methodPattern = /\.([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
|
|
2435
|
+
while ((match = methodPattern.exec(lineContent)) !== null) {
|
|
2436
|
+
const methodName = match[1];
|
|
2437
|
+
if (!isJavaScriptKeyword(methodName) && !seen.has(methodName)) {
|
|
2438
|
+
calls.push({ name: methodName });
|
|
2439
|
+
seen.add(methodName);
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
const awaitPattern = /await\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
|
|
2443
|
+
while ((match = awaitPattern.exec(lineContent)) !== null) {
|
|
2444
|
+
const functionName = match[1];
|
|
2445
|
+
if (!isJavaScriptKeyword(functionName) && !seen.has(functionName)) {
|
|
2446
|
+
calls.push({ name: functionName });
|
|
2447
|
+
seen.add(functionName);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
return calls;
|
|
2451
|
+
}
|
|
2452
|
+
function extractPythonCalls(lineContent) {
|
|
2453
|
+
const calls = [];
|
|
2454
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2455
|
+
const simplePattern = /\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g;
|
|
2456
|
+
let match;
|
|
2457
|
+
while ((match = simplePattern.exec(lineContent)) !== null) {
|
|
2458
|
+
const functionName = match[1];
|
|
2459
|
+
if (!isPythonKeyword(functionName) && !seen.has(functionName)) {
|
|
2460
|
+
calls.push({ name: functionName });
|
|
2461
|
+
seen.add(functionName);
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
const methodPattern = /\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g;
|
|
2465
|
+
while ((match = methodPattern.exec(lineContent)) !== null) {
|
|
2466
|
+
const methodName = match[1];
|
|
2467
|
+
if (!isPythonKeyword(methodName) && !seen.has(methodName)) {
|
|
2468
|
+
calls.push({ name: methodName });
|
|
2469
|
+
seen.add(methodName);
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
const awaitPattern = /await\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g;
|
|
2473
|
+
while ((match = awaitPattern.exec(lineContent)) !== null) {
|
|
2474
|
+
const functionName = match[1];
|
|
2475
|
+
if (!isPythonKeyword(functionName) && !seen.has(functionName)) {
|
|
2476
|
+
calls.push({ name: functionName });
|
|
2477
|
+
seen.add(functionName);
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
return calls;
|
|
2481
|
+
}
|
|
2482
|
+
function isJavaScriptKeyword(identifier) {
|
|
2483
|
+
const keywords = /* @__PURE__ */ new Set([
|
|
2484
|
+
// Keywords
|
|
2485
|
+
"if",
|
|
2486
|
+
"else",
|
|
2487
|
+
"for",
|
|
2488
|
+
"while",
|
|
2489
|
+
"do",
|
|
2490
|
+
"switch",
|
|
2491
|
+
"case",
|
|
2492
|
+
"break",
|
|
2493
|
+
"continue",
|
|
2494
|
+
"return",
|
|
2495
|
+
"throw",
|
|
2496
|
+
"try",
|
|
2497
|
+
"catch",
|
|
2498
|
+
"finally",
|
|
2499
|
+
"new",
|
|
2500
|
+
"typeof",
|
|
2501
|
+
"instanceof",
|
|
2502
|
+
"delete",
|
|
2503
|
+
"void",
|
|
2504
|
+
"async",
|
|
2505
|
+
"await",
|
|
2506
|
+
"yield",
|
|
2507
|
+
"import",
|
|
2508
|
+
"export",
|
|
2509
|
+
"from",
|
|
2510
|
+
"class",
|
|
2511
|
+
"extends",
|
|
2512
|
+
"super",
|
|
2513
|
+
"this",
|
|
2514
|
+
"static",
|
|
2515
|
+
"const",
|
|
2516
|
+
"let",
|
|
2517
|
+
"var",
|
|
2518
|
+
"function",
|
|
2519
|
+
"constructor",
|
|
2520
|
+
"get",
|
|
2521
|
+
"set",
|
|
2522
|
+
// Common builtins to skip
|
|
2523
|
+
"console",
|
|
2524
|
+
"parseInt",
|
|
2525
|
+
"parseFloat",
|
|
2526
|
+
"isNaN",
|
|
2527
|
+
"isFinite",
|
|
2528
|
+
"setTimeout",
|
|
2529
|
+
"setInterval",
|
|
2530
|
+
"clearTimeout",
|
|
2531
|
+
"clearInterval",
|
|
2532
|
+
"require",
|
|
2533
|
+
"module",
|
|
2534
|
+
"exports",
|
|
2535
|
+
"__dirname",
|
|
2536
|
+
"__filename"
|
|
2537
|
+
]);
|
|
2538
|
+
return keywords.has(identifier);
|
|
2539
|
+
}
|
|
2540
|
+
function isPythonKeyword(identifier) {
|
|
2541
|
+
const keywords = /* @__PURE__ */ new Set([
|
|
2542
|
+
// Keywords
|
|
2543
|
+
"if",
|
|
2544
|
+
"elif",
|
|
2545
|
+
"else",
|
|
2546
|
+
"for",
|
|
2547
|
+
"while",
|
|
2548
|
+
"break",
|
|
2549
|
+
"continue",
|
|
2550
|
+
"pass",
|
|
2551
|
+
"return",
|
|
2552
|
+
"raise",
|
|
2553
|
+
"try",
|
|
2554
|
+
"except",
|
|
2555
|
+
"finally",
|
|
2556
|
+
"with",
|
|
2557
|
+
"as",
|
|
2558
|
+
"yield",
|
|
2559
|
+
"import",
|
|
2560
|
+
"from",
|
|
2561
|
+
"class",
|
|
2562
|
+
"def",
|
|
2563
|
+
"lambda",
|
|
2564
|
+
"global",
|
|
2565
|
+
"nonlocal",
|
|
2566
|
+
"assert",
|
|
2567
|
+
"del",
|
|
2568
|
+
"in",
|
|
2569
|
+
"is",
|
|
2570
|
+
"not",
|
|
2571
|
+
"and",
|
|
2572
|
+
"or",
|
|
2573
|
+
"async",
|
|
2574
|
+
"await",
|
|
2575
|
+
// Common builtins to skip
|
|
2576
|
+
"print",
|
|
2577
|
+
"len",
|
|
2578
|
+
"range",
|
|
2579
|
+
"enumerate",
|
|
2580
|
+
"zip",
|
|
2581
|
+
"map",
|
|
2582
|
+
"filter",
|
|
2583
|
+
"sorted",
|
|
2584
|
+
"reversed",
|
|
2585
|
+
"sum",
|
|
2586
|
+
"min",
|
|
2587
|
+
"max",
|
|
2588
|
+
"abs",
|
|
2589
|
+
"round",
|
|
2590
|
+
"open",
|
|
2591
|
+
"input",
|
|
2592
|
+
"type",
|
|
2593
|
+
"isinstance",
|
|
2594
|
+
"hasattr",
|
|
2595
|
+
"getattr",
|
|
2596
|
+
"setattr"
|
|
2597
|
+
]);
|
|
2598
|
+
return keywords.has(identifier);
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
// src/analysis/removed-call-impact.ts
|
|
2602
|
+
var SECURITY_KEYWORDS = [
|
|
2603
|
+
// Authentication & Authorization
|
|
2604
|
+
"authenticate",
|
|
2605
|
+
"authorization",
|
|
2606
|
+
"authorize",
|
|
2607
|
+
"login",
|
|
2608
|
+
"logout",
|
|
2609
|
+
"signin",
|
|
2610
|
+
"signout",
|
|
2611
|
+
"verify",
|
|
2612
|
+
"check",
|
|
2613
|
+
"validate",
|
|
2614
|
+
"permission",
|
|
2615
|
+
"access",
|
|
2616
|
+
"role",
|
|
2617
|
+
"admin",
|
|
2618
|
+
"auth",
|
|
2619
|
+
// Input validation & sanitization
|
|
2620
|
+
"sanitize",
|
|
2621
|
+
"escape",
|
|
2622
|
+
"clean",
|
|
2623
|
+
"filter",
|
|
2624
|
+
"whitelist",
|
|
2625
|
+
"blacklist",
|
|
2626
|
+
"validate",
|
|
2627
|
+
"verification",
|
|
2628
|
+
"check",
|
|
2629
|
+
// Security tokens & sessions
|
|
2630
|
+
"token",
|
|
2631
|
+
"session",
|
|
2632
|
+
"cookie",
|
|
2633
|
+
"csrf",
|
|
2634
|
+
"xsrf",
|
|
2635
|
+
"jwt",
|
|
2636
|
+
"oauth",
|
|
2637
|
+
"api_key",
|
|
2638
|
+
"apikey",
|
|
2639
|
+
"secret",
|
|
2640
|
+
"password",
|
|
2641
|
+
"hash",
|
|
2642
|
+
"encrypt",
|
|
2643
|
+
// Injection prevention
|
|
2644
|
+
"xss",
|
|
2645
|
+
"sql",
|
|
2646
|
+
"injection",
|
|
2647
|
+
"query",
|
|
2648
|
+
"prepared",
|
|
2649
|
+
"statement",
|
|
2650
|
+
// Rate limiting & abuse prevention
|
|
2651
|
+
"ratelimit",
|
|
2652
|
+
"throttle",
|
|
2653
|
+
"captcha",
|
|
2654
|
+
"honeypot"
|
|
2655
|
+
];
|
|
2656
|
+
function analyzeRemovedCallImpact(removedCalls, storage) {
|
|
2657
|
+
const impacts = [];
|
|
2658
|
+
for (const removedCall of removedCalls) {
|
|
2659
|
+
try {
|
|
2660
|
+
const impact = analyzeSingleRemovedCall(removedCall, storage);
|
|
2661
|
+
if (impact) {
|
|
2662
|
+
impacts.push(impact);
|
|
2663
|
+
}
|
|
2664
|
+
} catch (error) {
|
|
2665
|
+
console.error(`Error analyzing removed call ${removedCall.callee}:`, error);
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
return impacts;
|
|
2669
|
+
}
|
|
2670
|
+
function analyzeSingleRemovedCall(removedCall, storage) {
|
|
2671
|
+
const nodes = storage.searchNodesByName(removedCall.callee);
|
|
2672
|
+
if (nodes.length === 0) {
|
|
2673
|
+
return null;
|
|
2674
|
+
}
|
|
2675
|
+
const functionNode = nodes[0];
|
|
2676
|
+
const allCallers = storage.getResolvedCallers(functionNode.id);
|
|
2677
|
+
const stillCalledBy = allCallers.filter((c) => c.name !== removedCall.removedFrom).map((c) => ({
|
|
2678
|
+
name: c.name,
|
|
2679
|
+
file: c.filePath,
|
|
2680
|
+
line: c.startLine
|
|
2681
|
+
}));
|
|
2682
|
+
const removedFromNodes = storage.searchNodesByName(removedCall.removedFrom);
|
|
2683
|
+
let impactedFunctions = [];
|
|
2684
|
+
if (removedFromNodes.length > 0) {
|
|
2685
|
+
const removedFromNode = removedFromNodes[0];
|
|
2686
|
+
const impactedNodes = storage.getImpactedNodes(removedFromNode.id, 5);
|
|
2687
|
+
impactedFunctions = impactedNodes.map((n) => ({
|
|
2688
|
+
name: n.name,
|
|
2689
|
+
file: n.filePath,
|
|
2690
|
+
line: n.startLine,
|
|
2691
|
+
reason: `Calls ${removedCall.removedFrom}() which no longer calls ${removedCall.callee}()`
|
|
2692
|
+
}));
|
|
2693
|
+
}
|
|
2694
|
+
const severity = calculateSeverity(
|
|
2695
|
+
removedCall,
|
|
2696
|
+
stillCalledBy,
|
|
2697
|
+
impactedFunctions
|
|
2698
|
+
);
|
|
2699
|
+
const description = buildImpactDescription(
|
|
2700
|
+
removedCall,
|
|
2701
|
+
stillCalledBy,
|
|
2702
|
+
impactedFunctions,
|
|
2703
|
+
severity
|
|
2704
|
+
);
|
|
2705
|
+
return {
|
|
2706
|
+
removedCall,
|
|
2707
|
+
functionNode: {
|
|
2708
|
+
id: functionNode.id,
|
|
2709
|
+
name: functionNode.name,
|
|
2710
|
+
file: functionNode.filePath,
|
|
2711
|
+
line: functionNode.startLine
|
|
2712
|
+
},
|
|
2713
|
+
stillCalledBy,
|
|
2714
|
+
impactedFunctions,
|
|
2715
|
+
severity,
|
|
2716
|
+
description
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2719
|
+
function calculateSeverity(removedCall, stillCalledBy, impactedFunctions) {
|
|
2720
|
+
const isSecurityFunction = SECURITY_KEYWORDS.some(
|
|
2721
|
+
(keyword) => removedCall.callee.toLowerCase().includes(keyword)
|
|
2722
|
+
);
|
|
2723
|
+
if (isSecurityFunction && impactedFunctions.length > 5) {
|
|
2724
|
+
return "CRITICAL";
|
|
2725
|
+
}
|
|
2726
|
+
if (isSecurityFunction && stillCalledBy.length === 0) {
|
|
2727
|
+
return "CRITICAL";
|
|
2728
|
+
}
|
|
2729
|
+
if (stillCalledBy.length === 0) {
|
|
2730
|
+
return "HIGH";
|
|
2731
|
+
}
|
|
2732
|
+
if (impactedFunctions.length > 20) {
|
|
2733
|
+
return "HIGH";
|
|
2734
|
+
}
|
|
2735
|
+
if (impactedFunctions.length >= 10) {
|
|
2736
|
+
return "MEDIUM";
|
|
2737
|
+
}
|
|
2738
|
+
if (isSecurityFunction && impactedFunctions.length > 0) {
|
|
2739
|
+
return "MEDIUM";
|
|
2740
|
+
}
|
|
2741
|
+
return "LOW";
|
|
2742
|
+
}
|
|
2743
|
+
function buildImpactDescription(removedCall, stillCalledBy, impactedFunctions, severity) {
|
|
2744
|
+
const parts = [];
|
|
2745
|
+
parts.push(
|
|
2746
|
+
`${removedCall.callee}() was removed from ${removedCall.removedFrom}() at line ${removedCall.lineNumber}`
|
|
2747
|
+
);
|
|
2748
|
+
if (stillCalledBy.length === 0) {
|
|
2749
|
+
parts.push(`\u26A0\uFE0F ORPHANED: ${removedCall.callee}() is no longer called anywhere in the codebase`);
|
|
2750
|
+
} else {
|
|
2751
|
+
const callerNames = stillCalledBy.slice(0, 3).map((c) => c.name).join(", ");
|
|
2752
|
+
const moreCount = stillCalledBy.length > 3 ? ` and ${stillCalledBy.length - 3} more` : "";
|
|
2753
|
+
parts.push(`Still called by: ${callerNames}${moreCount}`);
|
|
2754
|
+
}
|
|
2755
|
+
if (impactedFunctions.length > 0) {
|
|
2756
|
+
parts.push(
|
|
2757
|
+
`Impact: ${impactedFunctions.length} function${impactedFunctions.length === 1 ? "" : "s"} depend on ${removedCall.removedFrom}()`
|
|
2758
|
+
);
|
|
2759
|
+
}
|
|
2760
|
+
if (severity === "CRITICAL") {
|
|
2761
|
+
parts.push("\u{1F534} CRITICAL: Security or validation function removed from execution path");
|
|
2762
|
+
}
|
|
2763
|
+
return parts.join("\n");
|
|
2764
|
+
}
|
|
2765
|
+
|
|
1653
2766
|
// src/commands/impact.ts
|
|
1654
2767
|
async function analyzeImpact(options = {}) {
|
|
1655
2768
|
const rootPath = resolve2(options.path || ".");
|
|
@@ -1674,6 +2787,8 @@ async function analyzeImpactWithGit(storage, rootPath, options) {
|
|
|
1674
2787
|
directCallers: /* @__PURE__ */ new Map(),
|
|
1675
2788
|
affectedEndpoints: [],
|
|
1676
2789
|
importIssues: /* @__PURE__ */ new Map(),
|
|
2790
|
+
removedCallImpacts: [],
|
|
2791
|
+
// NEW
|
|
1677
2792
|
riskLevel: "LOW",
|
|
1678
2793
|
totalImpact: 0
|
|
1679
2794
|
};
|
|
@@ -1730,17 +2845,22 @@ async function analyzeImpactWithGit(storage, rootPath, options) {
|
|
|
1730
2845
|
entryPointFiles
|
|
1731
2846
|
};
|
|
1732
2847
|
}
|
|
2848
|
+
const lineDiff = parseLineLevelDiff(rootPath);
|
|
2849
|
+
const removedCalls = extractRemovedCalls(lineDiff.removedLines);
|
|
2850
|
+
const removedCallImpacts = analyzeRemovedCallImpact(removedCalls, storage);
|
|
1733
2851
|
const totalCallers = Array.from(directCallers.values()).reduce((sum, callers) => sum + callers.length, 0);
|
|
1734
2852
|
const hasCriticalImportIssues = Array.from(importIssues.values()).some((i) => i.missing.length > 0);
|
|
1735
2853
|
const hasDockerImpact = dockerImpact && dockerImpact.affectedServices.length > 0;
|
|
2854
|
+
const hasCriticalRemovedCalls = removedCallImpacts.some((i) => i.severity === "CRITICAL");
|
|
2855
|
+
const hasHighRiskRemovedCalls = removedCallImpacts.some((i) => i.severity === "HIGH");
|
|
1736
2856
|
let riskLevel = "LOW";
|
|
1737
|
-
if (hasCriticalImportIssues && hasDockerImpact) {
|
|
2857
|
+
if (hasCriticalRemovedCalls || hasCriticalImportIssues && hasDockerImpact) {
|
|
1738
2858
|
riskLevel = "CRITICAL";
|
|
1739
|
-
} else if (hasCriticalImportIssues || hasDockerImpact && dockerImpact.affectedServices.length > 2) {
|
|
2859
|
+
} else if (hasHighRiskRemovedCalls || hasCriticalImportIssues || hasDockerImpact && dockerImpact.affectedServices.length > 2) {
|
|
1740
2860
|
riskLevel = "HIGH";
|
|
1741
2861
|
} else if (totalCallers > 20 || affectedEndpoints.length > 5) {
|
|
1742
2862
|
riskLevel = "HIGH";
|
|
1743
|
-
} else if (totalCallers > 10 || affectedEndpoints.length > 2 || hasDockerImpact) {
|
|
2863
|
+
} else if (totalCallers > 10 || affectedEndpoints.length > 2 || hasDockerImpact || removedCallImpacts.length > 0) {
|
|
1744
2864
|
riskLevel = "MEDIUM";
|
|
1745
2865
|
}
|
|
1746
2866
|
return {
|
|
@@ -1750,8 +2870,10 @@ async function analyzeImpactWithGit(storage, rootPath, options) {
|
|
|
1750
2870
|
affectedEndpoints,
|
|
1751
2871
|
importIssues,
|
|
1752
2872
|
dockerImpact,
|
|
2873
|
+
removedCallImpacts,
|
|
2874
|
+
// NEW
|
|
1753
2875
|
riskLevel,
|
|
1754
|
-
totalImpact: totalCallers + (dockerImpact?.affectedServices.length || 0)
|
|
2876
|
+
totalImpact: totalCallers + (dockerImpact?.affectedServices.length || 0) + removedCallImpacts.length
|
|
1755
2877
|
};
|
|
1756
2878
|
}
|
|
1757
2879
|
async function analyzeImpactWithHash(storage, rootPath, options) {
|
|
@@ -1786,6 +2908,8 @@ async function analyzeImpactWithHash(storage, rootPath, options) {
|
|
|
1786
2908
|
directCallers: /* @__PURE__ */ new Map(),
|
|
1787
2909
|
affectedEndpoints: [],
|
|
1788
2910
|
importIssues,
|
|
2911
|
+
removedCallImpacts: [],
|
|
2912
|
+
// NEW - not available in hash mode
|
|
1789
2913
|
riskLevel: hasCriticalImportIssues ? "CRITICAL" : changedFiles.length > 10 ? "HIGH" : changedFiles.length > 5 ? "MEDIUM" : "LOW",
|
|
1790
2914
|
totalImpact: result.indexed
|
|
1791
2915
|
};
|
|
@@ -2021,12 +3145,12 @@ program.command("impact").description("Analyze impact of code changes").argument
|
|
|
2021
3145
|
for (const [file, issues] of result.importIssues.entries()) {
|
|
2022
3146
|
if (issues.missing.length > 0) {
|
|
2023
3147
|
console.log(chalk.red(` \u274C CRITICAL: ${file} - Missing imports still in use:`));
|
|
2024
|
-
for (const missing of issues.missing.slice(0,
|
|
3148
|
+
for (const missing of issues.missing.slice(0, 10)) {
|
|
2025
3149
|
const usageInfo = missing.usedAt ? ` (used at lines: ${missing.usedAt.slice(0, 3).join(", ")}${missing.usedAt.length > 3 ? "..." : ""})` : "";
|
|
2026
3150
|
console.log(chalk.gray(` \u2022 ${missing.identifier}${usageInfo}`));
|
|
2027
3151
|
}
|
|
2028
|
-
if (issues.missing.length >
|
|
2029
|
-
console.log(chalk.gray(` ... and ${issues.missing.length -
|
|
3152
|
+
if (issues.missing.length > 10) {
|
|
3153
|
+
console.log(chalk.gray(` ... and ${issues.missing.length - 10} more`));
|
|
2030
3154
|
}
|
|
2031
3155
|
}
|
|
2032
3156
|
if (issues.unused.length > 0 && options.verbose) {
|
|
@@ -2037,6 +3161,42 @@ program.command("impact").description("Analyze impact of code changes").argument
|
|
|
2037
3161
|
}
|
|
2038
3162
|
}
|
|
2039
3163
|
}
|
|
3164
|
+
if (result.removedCallImpacts && result.removedCallImpacts.length > 0) {
|
|
3165
|
+
console.log(chalk.blue("\n \u{1F6A8} Removed Function Calls:\n"));
|
|
3166
|
+
for (const impact of result.removedCallImpacts) {
|
|
3167
|
+
const severityColors = {
|
|
3168
|
+
"CRITICAL": chalk.red,
|
|
3169
|
+
"HIGH": chalk.red,
|
|
3170
|
+
"MEDIUM": chalk.yellow,
|
|
3171
|
+
"LOW": chalk.gray
|
|
3172
|
+
};
|
|
3173
|
+
const severityColor = severityColors[impact.severity];
|
|
3174
|
+
console.log(severityColor(` ${impact.severity}: ${impact.removedCall.callee}()`));
|
|
3175
|
+
console.log(chalk.gray(` Removed from: ${impact.removedCall.removedFrom}() at line ${impact.removedCall.lineNumber}`));
|
|
3176
|
+
console.log(chalk.gray(` File: ${impact.removedCall.file}`));
|
|
3177
|
+
if (impact.stillCalledBy.length === 0) {
|
|
3178
|
+
console.log(chalk.red(` \u26A0\uFE0F ORPHANED: No longer called anywhere!`));
|
|
3179
|
+
} else {
|
|
3180
|
+
const callerNames = impact.stillCalledBy.slice(0, 3).map((c) => c.name).join(", ");
|
|
3181
|
+
const moreCount = impact.stillCalledBy.length > 3 ? ` and ${impact.stillCalledBy.length - 3} more` : "";
|
|
3182
|
+
console.log(chalk.gray(` Still called by: ${callerNames}${moreCount}`));
|
|
3183
|
+
}
|
|
3184
|
+
if (impact.impactedFunctions.length > 0) {
|
|
3185
|
+
console.log(chalk.gray(` Impact: ${impact.impactedFunctions.length} dependent function${impact.impactedFunctions.length === 1 ? "" : "s"}`));
|
|
3186
|
+
const topImpacted = impact.impactedFunctions.slice(0, 3);
|
|
3187
|
+
for (const func of topImpacted) {
|
|
3188
|
+
console.log(chalk.gray(` \u2022 ${func.name} (${func.file}:${func.line})`));
|
|
3189
|
+
}
|
|
3190
|
+
if (impact.impactedFunctions.length > 3) {
|
|
3191
|
+
console.log(chalk.gray(` ... and ${impact.impactedFunctions.length - 3} more`));
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
if (impact.severity === "CRITICAL") {
|
|
3195
|
+
console.log(chalk.red(` \u{1F534} ${impact.description.split("\n").pop()}`));
|
|
3196
|
+
}
|
|
3197
|
+
console.log();
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
2040
3200
|
if (result.dockerImpact) {
|
|
2041
3201
|
console.log(chalk.blue("\n \u{1F4E6} Docker Service Impact:\n"));
|
|
2042
3202
|
console.log(chalk.red(` \u26A0\uFE0F Entry point file(s) modified - service(s) will not start:`));
|
|
@@ -2076,7 +3236,7 @@ program.command("reindex").description("Force full re-index of codebase").argume
|
|
|
2076
3236
|
program.command("mcp-server").description("Start MCP server for Claude Code integration").option("-p, --path <path>", "Project root path", ".").action(async (options) => {
|
|
2077
3237
|
process.env.CODEMAP_PROJECT_ROOT = resolve3(options.path);
|
|
2078
3238
|
process.env.CODEMAP_DB_PATH = join4(resolve3(options.path), ".codemap", "graph.db");
|
|
2079
|
-
await import("./flow-server-
|
|
3239
|
+
await import("./flow-server-4XSR5P4N.js");
|
|
2080
3240
|
});
|
|
2081
3241
|
program.parse();
|
|
2082
3242
|
//# sourceMappingURL=cli.js.map
|