codemap-ai 3.1.1 → 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 +1558 -43
- package/dist/cli.js.map +1 -1
- package/dist/{flow-server-B2SVQKDG.js → flow-server-4XSR5P4N.js} +2 -2
- package/dist/{flow-server-B2SVQKDG.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
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
|
|
7
7
|
// src/cli-flow.ts
|
|
8
8
|
import { Command } from "commander";
|
|
9
|
-
import { resolve as resolve3, join as
|
|
10
|
-
import { existsSync as
|
|
9
|
+
import { resolve as resolve3, join as join4 } from "path";
|
|
10
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
11
11
|
import { homedir } from "os";
|
|
12
12
|
import chalk from "chalk";
|
|
13
13
|
import ora from "ora";
|
|
@@ -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",
|
|
@@ -1279,7 +1686,7 @@ var DEFAULT_CONFIG = {
|
|
|
1279
1686
|
};
|
|
1280
1687
|
|
|
1281
1688
|
// src/commands/impact.ts
|
|
1282
|
-
import { resolve as resolve2, join as
|
|
1689
|
+
import { resolve as resolve2, join as join3, relative as relative2 } from "path";
|
|
1283
1690
|
|
|
1284
1691
|
// src/utils/git.ts
|
|
1285
1692
|
import { execSync } from "child_process";
|
|
@@ -1381,10 +1788,985 @@ function getGitStatus(rootPath) {
|
|
|
1381
1788
|
}
|
|
1382
1789
|
}
|
|
1383
1790
|
|
|
1791
|
+
// src/analysis/imports.ts
|
|
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
|
+
}
|
|
1874
|
+
function analyzeJavaScriptImports(filePath) {
|
|
1875
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
1876
|
+
const lines = content.split("\n");
|
|
1877
|
+
const imports = /* @__PURE__ */ new Map();
|
|
1878
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1879
|
+
const line = lines[i];
|
|
1880
|
+
const defaultMatch = line.match(/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/);
|
|
1881
|
+
if (defaultMatch) {
|
|
1882
|
+
imports.set(defaultMatch[1], i + 1);
|
|
1883
|
+
}
|
|
1884
|
+
const namedMatch = line.match(/import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/);
|
|
1885
|
+
if (namedMatch) {
|
|
1886
|
+
const names = namedMatch[1].split(",").map((n) => n.trim().split(" as ")[0].trim());
|
|
1887
|
+
names.forEach((name) => imports.set(name, i + 1));
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
const usedIdentifiers = /* @__PURE__ */ new Map();
|
|
1891
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1892
|
+
const line = lines[i];
|
|
1893
|
+
if (line.trim().startsWith("import ") || line.trim().startsWith("//") || line.trim().startsWith("*")) {
|
|
1894
|
+
continue;
|
|
1895
|
+
}
|
|
1896
|
+
for (const [identifier] of imports) {
|
|
1897
|
+
const regex = new RegExp(`\\b${identifier}\\b`, "g");
|
|
1898
|
+
if (regex.test(line)) {
|
|
1899
|
+
if (!usedIdentifiers.has(identifier)) {
|
|
1900
|
+
usedIdentifiers.set(identifier, []);
|
|
1901
|
+
}
|
|
1902
|
+
usedIdentifiers.get(identifier).push(i + 1);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
const unused = [];
|
|
1907
|
+
for (const [identifier, line] of imports) {
|
|
1908
|
+
const usage = usedIdentifiers.get(identifier);
|
|
1909
|
+
if (!usage || usage.length === 0) {
|
|
1910
|
+
unused.push({ identifier, line, type: "unused" });
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
const missing = [];
|
|
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)) {
|
|
1949
|
+
const usageLines = [];
|
|
1950
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1951
|
+
const line = lines[i];
|
|
1952
|
+
const regex = new RegExp(`\\b${identifier}\\b`);
|
|
1953
|
+
if (regex.test(line) && !line.trim().startsWith("//")) {
|
|
1954
|
+
usageLines.push(i + 1);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
if (usageLines.length > 0) {
|
|
1958
|
+
missing.push({
|
|
1959
|
+
identifier,
|
|
1960
|
+
line: usageLines[0],
|
|
1961
|
+
type: "missing",
|
|
1962
|
+
usedAt: usageLines
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
return { missing, unused };
|
|
1968
|
+
}
|
|
1969
|
+
function analyzePythonImports(filePath) {
|
|
1970
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
1971
|
+
const lines = content.split("\n");
|
|
1972
|
+
const imports = /* @__PURE__ */ new Map();
|
|
1973
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1974
|
+
const line = lines[i];
|
|
1975
|
+
const importMatch = line.match(/^import\s+(\w+)/);
|
|
1976
|
+
if (importMatch) {
|
|
1977
|
+
imports.set(importMatch[1], i + 1);
|
|
1978
|
+
}
|
|
1979
|
+
const fromMatch = line.match(/^from\s+[\w.]+\s+import\s+(.+)/);
|
|
1980
|
+
if (fromMatch) {
|
|
1981
|
+
const names = fromMatch[1].split(",").map((n) => n.trim().split(" as ")[0].trim());
|
|
1982
|
+
names.forEach((name) => imports.set(name, i + 1));
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
const usedIdentifiers = /* @__PURE__ */ new Map();
|
|
1986
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1987
|
+
const line = lines[i];
|
|
1988
|
+
if (line.trim().startsWith("import ") || line.trim().startsWith("from ") || line.trim().startsWith("#")) {
|
|
1989
|
+
continue;
|
|
1990
|
+
}
|
|
1991
|
+
for (const [identifier] of imports) {
|
|
1992
|
+
const regex = new RegExp(`\\b${identifier}\\b`, "g");
|
|
1993
|
+
if (regex.test(line)) {
|
|
1994
|
+
if (!usedIdentifiers.has(identifier)) {
|
|
1995
|
+
usedIdentifiers.set(identifier, []);
|
|
1996
|
+
}
|
|
1997
|
+
usedIdentifiers.get(identifier).push(i + 1);
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
const unused = [];
|
|
2002
|
+
for (const [identifier, line] of imports) {
|
|
2003
|
+
const usage = usedIdentifiers.get(identifier);
|
|
2004
|
+
if (!usage || usage.length === 0) {
|
|
2005
|
+
unused.push({ identifier, line, type: "unused" });
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
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 };
|
|
2058
|
+
}
|
|
2059
|
+
function analyzeImports(filePath) {
|
|
2060
|
+
if (filePath.endsWith(".py")) {
|
|
2061
|
+
return analyzePythonImports(filePath);
|
|
2062
|
+
} else if (filePath.match(/\.(js|jsx|ts|tsx)$/)) {
|
|
2063
|
+
return analyzeJavaScriptImports(filePath);
|
|
2064
|
+
}
|
|
2065
|
+
return { missing: [], unused: [] };
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
// src/analysis/docker.ts
|
|
2069
|
+
import { readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
|
|
2070
|
+
import { load as parseYaml } from "js-yaml";
|
|
2071
|
+
import { join as join2 } from "path";
|
|
2072
|
+
function parseDockerfile(dockerfilePath) {
|
|
2073
|
+
if (!existsSync2(dockerfilePath)) {
|
|
2074
|
+
return null;
|
|
2075
|
+
}
|
|
2076
|
+
const content = readFileSync3(dockerfilePath, "utf-8");
|
|
2077
|
+
const lines = content.split("\n");
|
|
2078
|
+
let inHealthCheck = false;
|
|
2079
|
+
for (const line of lines) {
|
|
2080
|
+
const trimmed = line.trim();
|
|
2081
|
+
if (line.includes("HEALTHCHECK")) {
|
|
2082
|
+
inHealthCheck = true;
|
|
2083
|
+
continue;
|
|
2084
|
+
}
|
|
2085
|
+
if (inHealthCheck && !line.startsWith(" ") && !line.startsWith(" ")) {
|
|
2086
|
+
inHealthCheck = false;
|
|
2087
|
+
}
|
|
2088
|
+
if (inHealthCheck) {
|
|
2089
|
+
continue;
|
|
2090
|
+
}
|
|
2091
|
+
if (trimmed.startsWith("CMD")) {
|
|
2092
|
+
const match = trimmed.match(/CMD\s+\[([^\]]+)\]/);
|
|
2093
|
+
if (match) {
|
|
2094
|
+
const parts = match[1].split(",").map((p) => p.trim().replace(/"/g, ""));
|
|
2095
|
+
const commandStr = parts.join(" ");
|
|
2096
|
+
return extractEntryPointFromCommand(commandStr);
|
|
2097
|
+
}
|
|
2098
|
+
const shellMatch = trimmed.match(/CMD\s+(.+)/);
|
|
2099
|
+
if (shellMatch) {
|
|
2100
|
+
return extractEntryPointFromCommand(shellMatch[1]);
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
if (trimmed.startsWith("ENTRYPOINT")) {
|
|
2104
|
+
const match = trimmed.match(/ENTRYPOINT\s+\[([^\]]+)\]/);
|
|
2105
|
+
if (match) {
|
|
2106
|
+
const parts = match[1].split(",").map((p) => p.trim().replace(/"/g, ""));
|
|
2107
|
+
const commandStr = parts.join(" ");
|
|
2108
|
+
return extractEntryPointFromCommand(commandStr);
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
return null;
|
|
2113
|
+
}
|
|
2114
|
+
function extractEntryPointFromCommand(command) {
|
|
2115
|
+
const commandStr = Array.isArray(command) ? command.join(" ") : command;
|
|
2116
|
+
const patterns = [
|
|
2117
|
+
/node\s+([^\s]+\.js)/,
|
|
2118
|
+
/python\s+-m\s+([\w.]+)/,
|
|
2119
|
+
/python\s+([^\s]+\.py)/,
|
|
2120
|
+
/uvicorn\s+([\w:]+)/,
|
|
2121
|
+
/npm\s+start/
|
|
2122
|
+
];
|
|
2123
|
+
for (const pattern of patterns) {
|
|
2124
|
+
const match = commandStr.match(pattern);
|
|
2125
|
+
if (match) {
|
|
2126
|
+
const entryFile = match[1];
|
|
2127
|
+
if (entryFile.endsWith(".js") || entryFile.endsWith(".py")) {
|
|
2128
|
+
return entryFile;
|
|
2129
|
+
} else if (entryFile.includes(":")) {
|
|
2130
|
+
const file = entryFile.split(":")[0];
|
|
2131
|
+
return `${file}.py`;
|
|
2132
|
+
} else {
|
|
2133
|
+
const file = entryFile.replace(/\./g, "/");
|
|
2134
|
+
return `${file}.py`;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
return null;
|
|
2139
|
+
}
|
|
2140
|
+
function parseDockerCompose(projectRoot) {
|
|
2141
|
+
const graph = {
|
|
2142
|
+
services: /* @__PURE__ */ new Map(),
|
|
2143
|
+
entryPoints: /* @__PURE__ */ new Map()
|
|
2144
|
+
};
|
|
2145
|
+
const composeFiles = [
|
|
2146
|
+
join2(projectRoot, "docker-compose.yaml"),
|
|
2147
|
+
join2(projectRoot, "docker-compose.yml"),
|
|
2148
|
+
join2(projectRoot, "compose.yaml"),
|
|
2149
|
+
join2(projectRoot, "compose.yml")
|
|
2150
|
+
];
|
|
2151
|
+
let composeData = null;
|
|
2152
|
+
for (const file of composeFiles) {
|
|
2153
|
+
if (existsSync2(file)) {
|
|
2154
|
+
const content = readFileSync3(file, "utf-8");
|
|
2155
|
+
composeData = parseYaml(content);
|
|
2156
|
+
break;
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
if (!composeData || !composeData.services) {
|
|
2160
|
+
return graph;
|
|
2161
|
+
}
|
|
2162
|
+
for (const [serviceName, serviceConfig] of Object.entries(composeData.services)) {
|
|
2163
|
+
const config = serviceConfig;
|
|
2164
|
+
const service = {
|
|
2165
|
+
name: serviceName,
|
|
2166
|
+
dependsOn: [],
|
|
2167
|
+
volumes: [],
|
|
2168
|
+
entryPoint: config.entrypoint,
|
|
2169
|
+
command: config.command
|
|
2170
|
+
};
|
|
2171
|
+
if (config.depends_on) {
|
|
2172
|
+
if (Array.isArray(config.depends_on)) {
|
|
2173
|
+
service.dependsOn = config.depends_on;
|
|
2174
|
+
} else if (typeof config.depends_on === "object") {
|
|
2175
|
+
service.dependsOn = Object.keys(config.depends_on);
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
if (config.volumes && Array.isArray(config.volumes)) {
|
|
2179
|
+
service.volumes = config.volumes.filter((v) => typeof v === "string").map((v) => v.split(":")[0]);
|
|
2180
|
+
}
|
|
2181
|
+
if (config.command) {
|
|
2182
|
+
const entryFile = extractEntryPointFromCommand(config.command);
|
|
2183
|
+
if (entryFile) {
|
|
2184
|
+
graph.entryPoints.set(serviceName, entryFile);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
if (!graph.entryPoints.has(serviceName) && config.build) {
|
|
2188
|
+
const buildContext = typeof config.build === "string" ? config.build : config.build.context;
|
|
2189
|
+
if (buildContext) {
|
|
2190
|
+
const dockerfilePath = join2(projectRoot, buildContext, "Dockerfile");
|
|
2191
|
+
const entryFile = parseDockerfile(dockerfilePath);
|
|
2192
|
+
if (entryFile) {
|
|
2193
|
+
graph.entryPoints.set(serviceName, entryFile);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
graph.services.set(serviceName, service);
|
|
2198
|
+
}
|
|
2199
|
+
return graph;
|
|
2200
|
+
}
|
|
2201
|
+
function findDependentServices(graph, serviceName) {
|
|
2202
|
+
const dependents = [];
|
|
2203
|
+
for (const [name, service] of graph.services) {
|
|
2204
|
+
if (service.dependsOn.includes(serviceName)) {
|
|
2205
|
+
dependents.push(name);
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
return dependents;
|
|
2209
|
+
}
|
|
2210
|
+
function calculateServiceBlastRadius(graph, serviceName) {
|
|
2211
|
+
const affected = /* @__PURE__ */ new Set();
|
|
2212
|
+
const queue = [serviceName];
|
|
2213
|
+
while (queue.length > 0) {
|
|
2214
|
+
const current = queue.shift();
|
|
2215
|
+
if (affected.has(current)) continue;
|
|
2216
|
+
affected.add(current);
|
|
2217
|
+
const dependents = findDependentServices(graph, current);
|
|
2218
|
+
queue.push(...dependents);
|
|
2219
|
+
}
|
|
2220
|
+
return Array.from(affected);
|
|
2221
|
+
}
|
|
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
|
+
|
|
1384
2766
|
// src/commands/impact.ts
|
|
1385
2767
|
async function analyzeImpact(options = {}) {
|
|
1386
2768
|
const rootPath = resolve2(options.path || ".");
|
|
1387
|
-
const dbPath =
|
|
2769
|
+
const dbPath = join3(rootPath, ".codemap", "graph.db");
|
|
1388
2770
|
const storage = new FlowStorage(dbPath);
|
|
1389
2771
|
try {
|
|
1390
2772
|
if (isGitRepository(rootPath)) {
|
|
@@ -1404,6 +2786,9 @@ async function analyzeImpactWithGit(storage, rootPath, options) {
|
|
|
1404
2786
|
changedFunctions: [],
|
|
1405
2787
|
directCallers: /* @__PURE__ */ new Map(),
|
|
1406
2788
|
affectedEndpoints: [],
|
|
2789
|
+
importIssues: /* @__PURE__ */ new Map(),
|
|
2790
|
+
removedCallImpacts: [],
|
|
2791
|
+
// NEW
|
|
1407
2792
|
riskLevel: "LOW",
|
|
1408
2793
|
totalImpact: 0
|
|
1409
2794
|
};
|
|
@@ -1421,7 +2806,7 @@ async function analyzeImpactWithGit(storage, rootPath, options) {
|
|
|
1421
2806
|
const directCallers = /* @__PURE__ */ new Map();
|
|
1422
2807
|
const affectedEndpoints = [];
|
|
1423
2808
|
for (const func of changedFunctions) {
|
|
1424
|
-
const nodes = storage.getNodesByFile(
|
|
2809
|
+
const nodes = storage.getNodesByFile(join3(rootPath, func.file)).filter((n) => n.name === func.functionName);
|
|
1425
2810
|
for (const node of nodes) {
|
|
1426
2811
|
const callers = storage.getResolvedCallersWithLocation(node.id);
|
|
1427
2812
|
if (callers.length > 0) {
|
|
@@ -1433,11 +2818,49 @@ async function analyzeImpactWithGit(storage, rootPath, options) {
|
|
|
1433
2818
|
}
|
|
1434
2819
|
}
|
|
1435
2820
|
}
|
|
2821
|
+
const importIssues = /* @__PURE__ */ new Map();
|
|
2822
|
+
for (const filePath of changedFilePaths) {
|
|
2823
|
+
const fullPath = join3(rootPath, filePath);
|
|
2824
|
+
const analysis = analyzeImports(fullPath);
|
|
2825
|
+
if (analysis.missing.length > 0 || analysis.unused.length > 0) {
|
|
2826
|
+
importIssues.set(relative2(rootPath, fullPath), analysis);
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
const dockerGraph = parseDockerCompose(rootPath);
|
|
2830
|
+
let dockerImpact;
|
|
2831
|
+
const entryPointFiles = [];
|
|
2832
|
+
const affectedServices = /* @__PURE__ */ new Set();
|
|
2833
|
+
for (const [serviceName, entryPoint] of dockerGraph.entryPoints) {
|
|
2834
|
+
for (const changedFile of changedFilePaths) {
|
|
2835
|
+
if (changedFile.includes(entryPoint) || entryPoint.includes(changedFile)) {
|
|
2836
|
+
entryPointFiles.push(entryPoint);
|
|
2837
|
+
const blastRadius = calculateServiceBlastRadius(dockerGraph, serviceName);
|
|
2838
|
+
blastRadius.forEach((s) => affectedServices.add(s));
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
if (affectedServices.size > 0) {
|
|
2843
|
+
dockerImpact = {
|
|
2844
|
+
affectedServices: Array.from(affectedServices),
|
|
2845
|
+
entryPointFiles
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2848
|
+
const lineDiff = parseLineLevelDiff(rootPath);
|
|
2849
|
+
const removedCalls = extractRemovedCalls(lineDiff.removedLines);
|
|
2850
|
+
const removedCallImpacts = analyzeRemovedCallImpact(removedCalls, storage);
|
|
1436
2851
|
const totalCallers = Array.from(directCallers.values()).reduce((sum, callers) => sum + callers.length, 0);
|
|
2852
|
+
const hasCriticalImportIssues = Array.from(importIssues.values()).some((i) => i.missing.length > 0);
|
|
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");
|
|
1437
2856
|
let riskLevel = "LOW";
|
|
1438
|
-
if (
|
|
2857
|
+
if (hasCriticalRemovedCalls || hasCriticalImportIssues && hasDockerImpact) {
|
|
2858
|
+
riskLevel = "CRITICAL";
|
|
2859
|
+
} else if (hasHighRiskRemovedCalls || hasCriticalImportIssues || hasDockerImpact && dockerImpact.affectedServices.length > 2) {
|
|
2860
|
+
riskLevel = "HIGH";
|
|
2861
|
+
} else if (totalCallers > 20 || affectedEndpoints.length > 5) {
|
|
1439
2862
|
riskLevel = "HIGH";
|
|
1440
|
-
} else if (totalCallers > 10 || affectedEndpoints.length > 2) {
|
|
2863
|
+
} else if (totalCallers > 10 || affectedEndpoints.length > 2 || hasDockerImpact || removedCallImpacts.length > 0) {
|
|
1441
2864
|
riskLevel = "MEDIUM";
|
|
1442
2865
|
}
|
|
1443
2866
|
return {
|
|
@@ -1445,8 +2868,12 @@ async function analyzeImpactWithGit(storage, rootPath, options) {
|
|
|
1445
2868
|
changedFunctions,
|
|
1446
2869
|
directCallers,
|
|
1447
2870
|
affectedEndpoints,
|
|
2871
|
+
importIssues,
|
|
2872
|
+
dockerImpact,
|
|
2873
|
+
removedCallImpacts,
|
|
2874
|
+
// NEW
|
|
1448
2875
|
riskLevel,
|
|
1449
|
-
totalImpact: totalCallers
|
|
2876
|
+
totalImpact: totalCallers + (dockerImpact?.affectedServices.length || 0) + removedCallImpacts.length
|
|
1450
2877
|
};
|
|
1451
2878
|
}
|
|
1452
2879
|
async function analyzeImpactWithHash(storage, rootPath, options) {
|
|
@@ -1467,12 +2894,23 @@ async function analyzeImpactWithHash(storage, rootPath, options) {
|
|
|
1467
2894
|
changedFiles.push(filePath);
|
|
1468
2895
|
}
|
|
1469
2896
|
}
|
|
2897
|
+
const importIssues = /* @__PURE__ */ new Map();
|
|
2898
|
+
for (const filePath of changedFiles) {
|
|
2899
|
+
const analysis = analyzeImports(filePath);
|
|
2900
|
+
if (analysis.missing.length > 0 || analysis.unused.length > 0) {
|
|
2901
|
+
importIssues.set(relative2(rootPath, filePath), analysis);
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
const hasCriticalImportIssues = Array.from(importIssues.values()).some((i) => i.missing.length > 0);
|
|
1470
2905
|
return {
|
|
1471
2906
|
changedFiles,
|
|
1472
2907
|
changedFunctions: [],
|
|
1473
2908
|
directCallers: /* @__PURE__ */ new Map(),
|
|
1474
2909
|
affectedEndpoints: [],
|
|
1475
|
-
|
|
2910
|
+
importIssues,
|
|
2911
|
+
removedCallImpacts: [],
|
|
2912
|
+
// NEW - not available in hash mode
|
|
2913
|
+
riskLevel: hasCriticalImportIssues ? "CRITICAL" : changedFiles.length > 10 ? "HIGH" : changedFiles.length > 5 ? "MEDIUM" : "LOW",
|
|
1476
2914
|
totalImpact: result.indexed
|
|
1477
2915
|
};
|
|
1478
2916
|
}
|
|
@@ -1481,11 +2919,11 @@ async function analyzeImpactWithHash(storage, rootPath, options) {
|
|
|
1481
2919
|
var program = new Command();
|
|
1482
2920
|
function configureMCPServer(projectPath) {
|
|
1483
2921
|
try {
|
|
1484
|
-
const claudeConfigPath =
|
|
1485
|
-
if (!
|
|
2922
|
+
const claudeConfigPath = join4(homedir(), ".claude", "config.json");
|
|
2923
|
+
if (!existsSync3(claudeConfigPath)) {
|
|
1486
2924
|
return false;
|
|
1487
2925
|
}
|
|
1488
|
-
const config = JSON.parse(
|
|
2926
|
+
const config = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
|
|
1489
2927
|
if (!config.mcpServers) {
|
|
1490
2928
|
config.mcpServers = {};
|
|
1491
2929
|
}
|
|
@@ -1499,17 +2937,17 @@ function configureMCPServer(projectPath) {
|
|
|
1499
2937
|
return false;
|
|
1500
2938
|
}
|
|
1501
2939
|
}
|
|
1502
|
-
program.name("codemap").description("CodeMap Flow - Call graph analyzer for understanding code impact").version("3.
|
|
2940
|
+
program.name("codemap").description("CodeMap Flow - Call graph analyzer for understanding code impact").version("3.2.0");
|
|
1503
2941
|
program.command("index").description("Build call graph for your codebase").argument("[path]", "Path to the project root", ".").option("-f, --force", "Force reindex all files (ignore cache)").option("--include <patterns...>", "Glob patterns to include").option("--exclude <patterns...>", "Glob patterns to exclude").action(async (path, options) => {
|
|
1504
2942
|
const rootPath = resolve3(path);
|
|
1505
|
-
const outputDir =
|
|
2943
|
+
const outputDir = join4(rootPath, ".codemap");
|
|
1506
2944
|
console.log(chalk.blue.bold("\n CodeMap Call Graph Indexer\n"));
|
|
1507
2945
|
console.log(chalk.gray(` Project: ${rootPath}
|
|
1508
2946
|
`));
|
|
1509
|
-
if (!
|
|
2947
|
+
if (!existsSync3(outputDir)) {
|
|
1510
2948
|
mkdirSync(outputDir, { recursive: true });
|
|
1511
2949
|
}
|
|
1512
|
-
const dbPath =
|
|
2950
|
+
const dbPath = join4(outputDir, "graph.db");
|
|
1513
2951
|
const storage = new FlowStorage(dbPath);
|
|
1514
2952
|
const builder = new FlowBuilder(storage, {
|
|
1515
2953
|
rootPath,
|
|
@@ -1580,18 +3018,18 @@ program.command("index").description("Build call graph for your codebase").argum
|
|
|
1580
3018
|
});
|
|
1581
3019
|
program.command("init").description("Initialize CodeMap (index + setup MCP)").argument("[path]", "Path to the project root", ".").option("--include <patterns...>", "Glob patterns to include").option("--exclude <patterns...>", "Glob patterns to exclude").option("--skip-mcp", "Skip MCP server configuration").action(async (path, options) => {
|
|
1582
3020
|
const rootPath = resolve3(path);
|
|
1583
|
-
const outputDir =
|
|
3021
|
+
const outputDir = join4(rootPath, ".codemap");
|
|
1584
3022
|
console.log(chalk.blue.bold("\n CodeMap Initialization\n"));
|
|
1585
3023
|
console.log(chalk.gray(` Project: ${rootPath}
|
|
1586
3024
|
`));
|
|
1587
|
-
if (
|
|
3025
|
+
if (existsSync3(join4(outputDir, "graph.db")) && !options.force) {
|
|
1588
3026
|
console.log(chalk.yellow(" Already initialized. Use 'codemap reindex' to rebuild.\n"));
|
|
1589
3027
|
return;
|
|
1590
3028
|
}
|
|
1591
|
-
if (!
|
|
3029
|
+
if (!existsSync3(outputDir)) {
|
|
1592
3030
|
mkdirSync(outputDir, { recursive: true });
|
|
1593
3031
|
}
|
|
1594
|
-
const dbPath =
|
|
3032
|
+
const dbPath = join4(outputDir, "graph.db");
|
|
1595
3033
|
const storage = new FlowStorage(dbPath);
|
|
1596
3034
|
const builder = new FlowBuilder(storage, {
|
|
1597
3035
|
rootPath,
|
|
@@ -1653,9 +3091,9 @@ program.command("init").description("Initialize CodeMap (index + setup MCP)").ar
|
|
|
1653
3091
|
});
|
|
1654
3092
|
program.command("impact").description("Analyze impact of code changes").argument("[path]", "Path to the project root", ".").option("-v, --verbose", "Show detailed output").action(async (path, options) => {
|
|
1655
3093
|
const rootPath = resolve3(path);
|
|
1656
|
-
const dbPath =
|
|
3094
|
+
const dbPath = join4(rootPath, ".codemap", "graph.db");
|
|
1657
3095
|
console.log(chalk.blue.bold("\n CodeMap Impact Analysis\n"));
|
|
1658
|
-
if (!
|
|
3096
|
+
if (!existsSync3(dbPath)) {
|
|
1659
3097
|
console.log(chalk.red(" Error: Not initialized. Run 'codemap init' first.\n"));
|
|
1660
3098
|
process.exit(1);
|
|
1661
3099
|
}
|
|
@@ -1702,9 +3140,86 @@ program.command("impact").description("Analyze impact of code changes").argument
|
|
|
1702
3140
|
console.log(chalk.gray(` \u2022 ${endpoint}`));
|
|
1703
3141
|
}
|
|
1704
3142
|
}
|
|
3143
|
+
if (result.importIssues.size > 0) {
|
|
3144
|
+
console.log(chalk.blue("\n \u{1F50D} Import Analysis:\n"));
|
|
3145
|
+
for (const [file, issues] of result.importIssues.entries()) {
|
|
3146
|
+
if (issues.missing.length > 0) {
|
|
3147
|
+
console.log(chalk.red(` \u274C CRITICAL: ${file} - Missing imports still in use:`));
|
|
3148
|
+
for (const missing of issues.missing.slice(0, 10)) {
|
|
3149
|
+
const usageInfo = missing.usedAt ? ` (used at lines: ${missing.usedAt.slice(0, 3).join(", ")}${missing.usedAt.length > 3 ? "..." : ""})` : "";
|
|
3150
|
+
console.log(chalk.gray(` \u2022 ${missing.identifier}${usageInfo}`));
|
|
3151
|
+
}
|
|
3152
|
+
if (issues.missing.length > 10) {
|
|
3153
|
+
console.log(chalk.gray(` ... and ${issues.missing.length - 10} more`));
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
if (issues.unused.length > 0 && options.verbose) {
|
|
3157
|
+
console.log(chalk.yellow(` \u26A0\uFE0F ${file} - Unused imports:`));
|
|
3158
|
+
for (const unused of issues.unused.slice(0, 3)) {
|
|
3159
|
+
console.log(chalk.gray(` \u2022 ${unused.identifier} (line ${unused.line})`));
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
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
|
+
}
|
|
3200
|
+
if (result.dockerImpact) {
|
|
3201
|
+
console.log(chalk.blue("\n \u{1F4E6} Docker Service Impact:\n"));
|
|
3202
|
+
console.log(chalk.red(` \u26A0\uFE0F Entry point file(s) modified - service(s) will not start:`));
|
|
3203
|
+
for (const file of result.dockerImpact.entryPointFiles) {
|
|
3204
|
+
console.log(chalk.gray(` \u2022 ${file}`));
|
|
3205
|
+
}
|
|
3206
|
+
console.log(chalk.red(`
|
|
3207
|
+
\u{1F4A5} Affected Services (${result.dockerImpact.affectedServices.length}):`));
|
|
3208
|
+
for (const service of result.dockerImpact.affectedServices) {
|
|
3209
|
+
console.log(chalk.gray(` \u2022 ${service}`));
|
|
3210
|
+
}
|
|
3211
|
+
console.log(chalk.yellow(`
|
|
3212
|
+
\u2192 If entry point crashes, these services WILL FAIL`));
|
|
3213
|
+
}
|
|
1705
3214
|
console.log(chalk.blue("\n Risk Assessment:\n"));
|
|
1706
|
-
const riskColor = result.riskLevel === "HIGH" ? chalk.red : result.riskLevel === "MEDIUM" ? chalk.yellow : chalk.green;
|
|
1707
|
-
console.log(` ${riskColor(result.riskLevel)} - ${result.totalImpact}
|
|
3215
|
+
const riskColor = result.riskLevel === "CRITICAL" ? chalk.red.bold : result.riskLevel === "HIGH" ? chalk.red : result.riskLevel === "MEDIUM" ? chalk.yellow : chalk.green;
|
|
3216
|
+
console.log(` ${riskColor(result.riskLevel)} - ${result.totalImpact} components affected`);
|
|
3217
|
+
if (result.riskLevel === "CRITICAL") {
|
|
3218
|
+
console.log(chalk.red.bold("\n \u26A0\uFE0F DO NOT DEPLOY - System-wide failure risk"));
|
|
3219
|
+
console.log(chalk.gray(" Fix critical issues before proceeding"));
|
|
3220
|
+
} else if (result.riskLevel === "HIGH") {
|
|
3221
|
+
console.log(chalk.yellow("\n \u26A0\uFE0F High risk - Extensive testing required"));
|
|
3222
|
+
}
|
|
1708
3223
|
console.log();
|
|
1709
3224
|
} catch (error) {
|
|
1710
3225
|
spinner.fail(chalk.red("Impact analysis failed"));
|
|
@@ -1720,8 +3235,8 @@ program.command("reindex").description("Force full re-index of codebase").argume
|
|
|
1720
3235
|
});
|
|
1721
3236
|
program.command("mcp-server").description("Start MCP server for Claude Code integration").option("-p, --path <path>", "Project root path", ".").action(async (options) => {
|
|
1722
3237
|
process.env.CODEMAP_PROJECT_ROOT = resolve3(options.path);
|
|
1723
|
-
process.env.CODEMAP_DB_PATH =
|
|
1724
|
-
await import("./flow-server-
|
|
3238
|
+
process.env.CODEMAP_DB_PATH = join4(resolve3(options.path), ".codemap", "graph.db");
|
|
3239
|
+
await import("./flow-server-4XSR5P4N.js");
|
|
1725
3240
|
});
|
|
1726
3241
|
program.parse();
|
|
1727
3242
|
//# sourceMappingURL=cli.js.map
|