codemap-ai 3.2.0 → 3.4.0

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