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 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 join3 } from "path";
10
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
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 child of node.children) {
287
- if (child.type === "class_heritage") {
288
- const extendsClause = child.children.find((c) => c.type === "extends_clause");
289
- if (extendsClause) {
290
- const baseClass = extendsClause.children.find((c) => c.type === "identifier");
291
- if (baseClass) {
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 join2 } from "path";
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 = join2(rootPath, ".codemap", "graph.db");
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(join2(rootPath, func.file)).filter((n) => n.name === func.functionName);
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 (totalCallers > 20 || affectedEndpoints.length > 5) {
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
- riskLevel: changedFiles.length > 10 ? "HIGH" : changedFiles.length > 5 ? "MEDIUM" : "LOW",
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 = join3(homedir(), ".claude", "config.json");
1485
- if (!existsSync2(claudeConfigPath)) {
2922
+ const claudeConfigPath = join4(homedir(), ".claude", "config.json");
2923
+ if (!existsSync3(claudeConfigPath)) {
1486
2924
  return false;
1487
2925
  }
1488
- const config = JSON.parse(readFileSync2(claudeConfigPath, "utf-8"));
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.1.1");
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 = join3(rootPath, ".codemap");
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 (!existsSync2(outputDir)) {
2947
+ if (!existsSync3(outputDir)) {
1510
2948
  mkdirSync(outputDir, { recursive: true });
1511
2949
  }
1512
- const dbPath = join3(outputDir, "graph.db");
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 = join3(rootPath, ".codemap");
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 (existsSync2(join3(outputDir, "graph.db")) && !options.force) {
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 (!existsSync2(outputDir)) {
3029
+ if (!existsSync3(outputDir)) {
1592
3030
  mkdirSync(outputDir, { recursive: true });
1593
3031
  }
1594
- const dbPath = join3(outputDir, "graph.db");
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 = join3(rootPath, ".codemap", "graph.db");
3094
+ const dbPath = join4(rootPath, ".codemap", "graph.db");
1657
3095
  console.log(chalk.blue.bold("\n CodeMap Impact Analysis\n"));
1658
- if (!existsSync2(dbPath)) {
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} functions affected`);
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 = join3(resolve3(options.path), ".codemap", "graph.db");
1724
- await import("./flow-server-B2SVQKDG.js");
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