codemap-ai 3.2.0 → 3.5.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
@@ -2,22 +2,408 @@
2
2
 
3
3
  import {
4
4
  FlowStorage
5
- } from "./chunk-LXZ73T7X.js";
5
+ } from "./chunk-BRVRY5KT.js";
6
6
 
7
7
  // src/cli-flow.ts
8
8
  import { Command } from "commander";
9
9
  import { resolve as resolve3, join as join4 } from "path";
10
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
10
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync5, writeFileSync } from "fs";
11
11
  import { homedir } from "os";
12
12
  import chalk from "chalk";
13
13
  import ora from "ora";
14
14
 
15
15
  // src/flow/builder.ts
16
- import { readFileSync, statSync } from "fs";
16
+ import { readFileSync as readFileSync2, statSync } from "fs";
17
17
  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;
@@ -97,10 +483,13 @@ var TypeScriptParser = class extends BaseParser {
97
483
  language = "typescript";
98
484
  extensions = [".ts", ".tsx"];
99
485
  parser;
486
+ treeSitterParser;
487
+ // Expose for Phase 3
100
488
  constructor() {
101
489
  super();
102
490
  this.parser = new Parser();
103
491
  this.parser.setLanguage(TypeScript.typescript);
492
+ this.treeSitterParser = this.parser;
104
493
  }
105
494
  parse(filePath, content) {
106
495
  const analysis = this.createEmptyAnalysis(filePath);
@@ -272,31 +661,38 @@ var TypeScriptParser = class extends BaseParser {
272
661
  handleClass(node, filePath, analysis, parentId) {
273
662
  const nameNode = node.childForFieldName("name");
274
663
  if (!nameNode) return;
664
+ const extends_ = [];
665
+ for (const child of node.children) {
666
+ if (child.type === "class_heritage") {
667
+ const extendsClause = child.children.find((c) => c.type === "extends_clause");
668
+ if (extendsClause) {
669
+ const baseClass = extendsClause.children.find((c) => c.type === "identifier");
670
+ if (baseClass) {
671
+ extends_.push(baseClass.text);
672
+ }
673
+ }
674
+ }
675
+ }
275
676
  const classNode = this.createNode(
276
677
  "class",
277
678
  nameNode.text,
278
679
  filePath,
279
680
  node.startPosition.row + 1,
280
- node.endPosition.row + 1
681
+ node.endPosition.row + 1,
682
+ {
683
+ extends: extends_
684
+ }
281
685
  );
282
686
  analysis.nodes.push(classNode);
283
687
  analysis.edges.push(
284
688
  this.createEdge("contains", parentId, classNode.id)
285
689
  );
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
- }
690
+ for (const baseClass of extends_) {
691
+ analysis.edges.push(
692
+ this.createEdge("extends", classNode.id, `ref:${baseClass}`, {
693
+ unresolvedName: baseClass
694
+ })
695
+ );
300
696
  }
301
697
  const body = node.childForFieldName("body");
302
698
  if (body) {
@@ -310,6 +706,8 @@ var TypeScriptParser = class extends BaseParser {
310
706
  handleMethod(node, filePath, analysis, parentId) {
311
707
  const nameNode = node.childForFieldName("name");
312
708
  if (!nameNode) return;
709
+ const parentNode = analysis.nodes.find((n) => n.id === parentId);
710
+ const parentClass = parentNode?.type === "class" ? parentNode.name : void 0;
313
711
  const methodNode = this.createNode(
314
712
  "method",
315
713
  nameNode.text,
@@ -318,7 +716,8 @@ var TypeScriptParser = class extends BaseParser {
318
716
  node.endPosition.row + 1,
319
717
  {
320
718
  static: node.children.some((c) => c.type === "static"),
321
- async: node.children.some((c) => c.type === "async")
719
+ async: node.children.some((c) => c.type === "async"),
720
+ parentClass
322
721
  }
323
722
  );
324
723
  analysis.nodes.push(methodNode);
@@ -378,6 +777,7 @@ var JavaScriptParser = class extends TypeScriptParser {
378
777
  super();
379
778
  this.parser = new Parser();
380
779
  this.parser.setLanguage(JavaScript);
780
+ this.treeSitterParser = this.parser;
381
781
  }
382
782
  parse(filePath, content) {
383
783
  const sourceCode = typeof content === "string" ? content : String(content);
@@ -402,10 +802,13 @@ var PythonParser = class extends BaseParser {
402
802
  language = "python";
403
803
  extensions = [".py"];
404
804
  parser;
805
+ treeSitterParser;
806
+ // Expose for Phase 3
405
807
  constructor() {
406
808
  super();
407
809
  this.parser = new Parser2();
408
810
  this.parser.setLanguage(Python);
811
+ this.treeSitterParser = this.parser;
409
812
  }
410
813
  parse(filePath, content) {
411
814
  const analysis = this.createEmptyAnalysis(filePath);
@@ -753,6 +1156,8 @@ var PythonParser = class extends BaseParser {
753
1156
  node.endPosition.row + 1,
754
1157
  {
755
1158
  bases,
1159
+ extends: bases,
1160
+ // Add extends for consistency with TypeScript
756
1161
  isPrivate: name.startsWith("_")
757
1162
  }
758
1163
  );
@@ -790,6 +1195,8 @@ var PythonParser = class extends BaseParser {
790
1195
  const nameNode = node.childForFieldName("name");
791
1196
  if (!nameNode) return;
792
1197
  const name = nameNode.text;
1198
+ const parentNode = analysis.nodes.find((n) => n.id === classId);
1199
+ const parentClass = parentNode?.type === "class" ? parentNode.name : void 0;
793
1200
  const decorators = [];
794
1201
  if (node.parent?.type === "decorated_definition") {
795
1202
  for (const sibling of node.parent.children) {
@@ -818,7 +1225,8 @@ var PythonParser = class extends BaseParser {
818
1225
  classmethod: isClassMethod,
819
1226
  property: isProperty,
820
1227
  isPrivate: name.startsWith("_"),
821
- isDunder: name.startsWith("__") && name.endsWith("__")
1228
+ isDunder: name.startsWith("__") && name.endsWith("__"),
1229
+ parentClass
822
1230
  }
823
1231
  );
824
1232
  analysis.nodes.push(methodNode);
@@ -961,6 +1369,614 @@ function registerAllParsers() {
961
1369
  }
962
1370
  registerAllParsers();
963
1371
 
1372
+ // src/flow/phase3-analyzer.ts
1373
+ import { readFileSync } from "fs";
1374
+
1375
+ // src/analysis/property-tracker.ts
1376
+ function extractPropertyAccesses(functionNode, scopeId, scopeName, filePath) {
1377
+ const accesses = [];
1378
+ const isJS = /\.(js|ts|jsx|tsx)$/.test(filePath);
1379
+ const isPython = /\.py$/.test(filePath);
1380
+ if (isJS) {
1381
+ extractJavaScriptPropertyAccesses(functionNode, scopeId, scopeName, accesses);
1382
+ } else if (isPython) {
1383
+ extractPythonPropertyAccesses(functionNode, scopeId, scopeName, accesses);
1384
+ }
1385
+ return accesses;
1386
+ }
1387
+ function extractJavaScriptPropertyAccesses(node, scopeId, scopeName, accesses) {
1388
+ traverseNode(node, (current) => {
1389
+ if (current.type === "member_expression") {
1390
+ const access = parseJavaScriptMemberExpression(current, scopeId);
1391
+ if (access) {
1392
+ accesses.push(access);
1393
+ }
1394
+ }
1395
+ if (current.type === "optional_chain") {
1396
+ const memberExpr = current.children.find((c) => c.type === "member_expression");
1397
+ if (memberExpr) {
1398
+ const access = parseJavaScriptMemberExpression(memberExpr, scopeId);
1399
+ if (access) {
1400
+ access.accessType = "optional";
1401
+ accesses.push(access);
1402
+ }
1403
+ }
1404
+ }
1405
+ if (current.type === "subscript_expression") {
1406
+ const access = parseJavaScriptSubscript(current, scopeId);
1407
+ if (access) {
1408
+ accesses.push(access);
1409
+ }
1410
+ }
1411
+ });
1412
+ }
1413
+ function parseJavaScriptMemberExpression(node, scopeId) {
1414
+ const propertyPath = [];
1415
+ let objectName = "";
1416
+ let currentNode = node;
1417
+ while (currentNode && currentNode.type === "member_expression") {
1418
+ const propertyNode = currentNode.childForFieldName("property");
1419
+ if (propertyNode) {
1420
+ propertyPath.unshift(propertyNode.text);
1421
+ }
1422
+ const objectNode = currentNode.childForFieldName("object");
1423
+ if (!objectNode) break;
1424
+ if (objectNode.type === "identifier") {
1425
+ objectName = objectNode.text;
1426
+ break;
1427
+ } else if (objectNode.type === "member_expression") {
1428
+ currentNode = objectNode;
1429
+ } else {
1430
+ return null;
1431
+ }
1432
+ }
1433
+ if (!objectName || isCommonJavaScriptObject(objectName)) {
1434
+ return null;
1435
+ }
1436
+ if (propertyPath.length === 0) {
1437
+ return null;
1438
+ }
1439
+ return {
1440
+ objectName,
1441
+ propertyPath,
1442
+ fullPath: `${objectName}.${propertyPath.join(".")}`,
1443
+ line: node.startPosition.row + 1,
1444
+ scopeId,
1445
+ accessType: "dot"
1446
+ };
1447
+ }
1448
+ function parseJavaScriptSubscript(node, scopeId) {
1449
+ const objectNode = node.childForFieldName("object");
1450
+ const indexNode = node.childForFieldName("index");
1451
+ if (!objectNode || !indexNode) return null;
1452
+ if (indexNode.type !== "string") return null;
1453
+ const objectName = objectNode.text;
1454
+ const propertyName = indexNode.text.replace(/['"]/g, "");
1455
+ if (isCommonJavaScriptObject(objectName)) {
1456
+ return null;
1457
+ }
1458
+ return {
1459
+ objectName,
1460
+ propertyPath: [propertyName],
1461
+ fullPath: `${objectName}.${propertyName}`,
1462
+ line: node.startPosition.row + 1,
1463
+ scopeId,
1464
+ accessType: "bracket"
1465
+ };
1466
+ }
1467
+ function extractPythonPropertyAccesses(node, scopeId, scopeName, accesses) {
1468
+ traverseNode(node, (current) => {
1469
+ if (current.type === "attribute") {
1470
+ const access = parsePythonAttribute(current, scopeId);
1471
+ if (access) {
1472
+ accesses.push(access);
1473
+ }
1474
+ }
1475
+ if (current.type === "subscript") {
1476
+ const access = parsePythonSubscript(current, scopeId);
1477
+ if (access) {
1478
+ accesses.push(access);
1479
+ }
1480
+ }
1481
+ });
1482
+ }
1483
+ function parsePythonAttribute(node, scopeId) {
1484
+ const propertyPath = [];
1485
+ let objectName = "";
1486
+ let currentNode = node;
1487
+ while (currentNode && currentNode.type === "attribute") {
1488
+ const attrNode = currentNode.childForFieldName("attribute");
1489
+ if (attrNode) {
1490
+ propertyPath.unshift(attrNode.text);
1491
+ }
1492
+ const objectNode = currentNode.childForFieldName("object");
1493
+ if (!objectNode) break;
1494
+ if (objectNode.type === "identifier") {
1495
+ objectName = objectNode.text;
1496
+ break;
1497
+ } else if (objectNode.type === "attribute") {
1498
+ currentNode = objectNode;
1499
+ } else {
1500
+ return null;
1501
+ }
1502
+ }
1503
+ if (!objectName || isCommonPythonObject(objectName)) {
1504
+ return null;
1505
+ }
1506
+ if (propertyPath.length === 0) {
1507
+ return null;
1508
+ }
1509
+ return {
1510
+ objectName,
1511
+ propertyPath,
1512
+ fullPath: `${objectName}.${propertyPath.join(".")}`,
1513
+ line: node.startPosition.row + 1,
1514
+ scopeId,
1515
+ accessType: "dot"
1516
+ };
1517
+ }
1518
+ function parsePythonSubscript(node, scopeId) {
1519
+ const valueNode = node.childForFieldName("value");
1520
+ const subscriptChildren = node.children.filter(
1521
+ (c) => c.type === "string" || c.type === "identifier"
1522
+ );
1523
+ if (!valueNode || subscriptChildren.length === 0) return null;
1524
+ const keyNode = subscriptChildren.find((c) => c.type === "string");
1525
+ if (!keyNode) return null;
1526
+ const objectName = valueNode.text;
1527
+ const stringContent = keyNode.children.find((c) => c.type === "string_content");
1528
+ const propertyName = stringContent ? stringContent.text : keyNode.text.replace(/['"]/g, "");
1529
+ if (isCommonPythonObject(objectName)) {
1530
+ return null;
1531
+ }
1532
+ return {
1533
+ objectName,
1534
+ propertyPath: [propertyName],
1535
+ fullPath: `${objectName}.${propertyName}`,
1536
+ line: node.startPosition.row + 1,
1537
+ scopeId,
1538
+ accessType: "bracket"
1539
+ };
1540
+ }
1541
+ function traverseNode(node, visitor) {
1542
+ visitor(node);
1543
+ for (let i = 0; i < node.childCount; i++) {
1544
+ const child = node.child(i);
1545
+ if (child) {
1546
+ traverseNode(child, visitor);
1547
+ }
1548
+ }
1549
+ }
1550
+ function isCommonJavaScriptObject(name) {
1551
+ const commonObjects = /* @__PURE__ */ new Set([
1552
+ // Global objects
1553
+ "console",
1554
+ "window",
1555
+ "document",
1556
+ "process",
1557
+ "global",
1558
+ // Common libraries
1559
+ "Math",
1560
+ "JSON",
1561
+ "Date",
1562
+ "Array",
1563
+ "Object",
1564
+ "String",
1565
+ "Number",
1566
+ // Keywords
1567
+ "this",
1568
+ "super",
1569
+ // Common module patterns
1570
+ "exports",
1571
+ "module",
1572
+ "require"
1573
+ ]);
1574
+ return commonObjects.has(name);
1575
+ }
1576
+ function isCommonPythonObject(name) {
1577
+ const commonObjects = /* @__PURE__ */ new Set([
1578
+ // Keywords
1579
+ "self",
1580
+ "cls",
1581
+ // Built-in types
1582
+ "str",
1583
+ "int",
1584
+ "float",
1585
+ "dict",
1586
+ "list",
1587
+ "tuple",
1588
+ "set",
1589
+ // Common modules
1590
+ "os",
1591
+ "sys",
1592
+ "json",
1593
+ "math",
1594
+ "datetime"
1595
+ ]);
1596
+ return commonObjects.has(name);
1597
+ }
1598
+
1599
+ // src/analysis/return-analyzer.ts
1600
+ function analyzeReturnStatements(functionNode, functionId, functionName, filePath) {
1601
+ const returns = [];
1602
+ const isJS = /\.(js|ts|jsx|tsx)$/.test(filePath);
1603
+ const isPython = /\.py$/.test(filePath);
1604
+ if (isJS) {
1605
+ extractJavaScriptReturns(functionNode, functionId, functionName, returns);
1606
+ } else if (isPython) {
1607
+ extractPythonReturns(functionNode, functionId, functionName, returns);
1608
+ }
1609
+ return returns;
1610
+ }
1611
+ function extractJavaScriptReturns(node, functionId, functionName, returns) {
1612
+ traverseNode2(node, (current) => {
1613
+ if (current.type === "return_statement") {
1614
+ const returnNode = current.children.find(
1615
+ (c) => c.type !== "return" && c.type !== ";"
1616
+ );
1617
+ if (returnNode) {
1618
+ const returnInfo = parseJavaScriptReturnValue(
1619
+ returnNode,
1620
+ functionId,
1621
+ functionName
1622
+ );
1623
+ if (returnInfo) {
1624
+ returns.push(returnInfo);
1625
+ }
1626
+ }
1627
+ }
1628
+ });
1629
+ }
1630
+ function parseJavaScriptReturnValue(returnNode, functionId, functionName) {
1631
+ const line = returnNode.startPosition.row + 1;
1632
+ const returnExpression = returnNode.text;
1633
+ let returnType = null;
1634
+ let objectShape = null;
1635
+ if (returnNode.type === "object") {
1636
+ objectShape = extractJavaScriptObjectShape(returnNode);
1637
+ returnType = "object";
1638
+ } else if (returnNode.type === "array") {
1639
+ returnType = "array";
1640
+ objectShape = {
1641
+ properties: [],
1642
+ propertyTypes: /* @__PURE__ */ new Map(),
1643
+ nestedShapes: /* @__PURE__ */ new Map(),
1644
+ isArray: true,
1645
+ isDictionary: false,
1646
+ isOptional: false
1647
+ };
1648
+ } else if (returnNode.type === "new_expression") {
1649
+ const constructorNode = returnNode.childForFieldName("constructor");
1650
+ if (constructorNode) {
1651
+ returnType = constructorNode.text;
1652
+ }
1653
+ } else if (returnNode.type === "identifier") {
1654
+ returnType = `@var:${returnNode.text}`;
1655
+ } else if (returnNode.type === "call_expression") {
1656
+ const funcNode = returnNode.childForFieldName("function");
1657
+ if (funcNode) {
1658
+ returnType = `@call:${funcNode.text}`;
1659
+ }
1660
+ }
1661
+ return {
1662
+ functionId,
1663
+ functionName,
1664
+ returnType,
1665
+ objectShape,
1666
+ line,
1667
+ returnExpression
1668
+ };
1669
+ }
1670
+ function extractJavaScriptObjectShape(objectNode) {
1671
+ const properties = [];
1672
+ const propertyTypes = /* @__PURE__ */ new Map();
1673
+ const nestedShapes = /* @__PURE__ */ new Map();
1674
+ for (const child of objectNode.children) {
1675
+ if (child.type === "pair") {
1676
+ const keyNode = child.childForFieldName("key");
1677
+ const valueNode = child.childForFieldName("value");
1678
+ if (keyNode) {
1679
+ const propertyName = keyNode.text.replace(/['"]/g, "");
1680
+ properties.push(propertyName);
1681
+ if (valueNode) {
1682
+ const propertyType = inferJavaScriptType(valueNode);
1683
+ propertyTypes.set(propertyName, propertyType);
1684
+ if (valueNode.type === "object") {
1685
+ const nestedShape = extractJavaScriptObjectShape(valueNode);
1686
+ nestedShapes.set(propertyName, nestedShape);
1687
+ }
1688
+ }
1689
+ }
1690
+ }
1691
+ }
1692
+ return {
1693
+ properties,
1694
+ propertyTypes,
1695
+ nestedShapes,
1696
+ isArray: false,
1697
+ isDictionary: false,
1698
+ isOptional: false
1699
+ };
1700
+ }
1701
+ function inferJavaScriptType(valueNode) {
1702
+ switch (valueNode.type) {
1703
+ case "number":
1704
+ return "number";
1705
+ case "string":
1706
+ case "template_string":
1707
+ return "string";
1708
+ case "true":
1709
+ case "false":
1710
+ return "boolean";
1711
+ case "null":
1712
+ return "null";
1713
+ case "undefined":
1714
+ return "undefined";
1715
+ case "array":
1716
+ return "array";
1717
+ case "object":
1718
+ return "object";
1719
+ case "arrow_function":
1720
+ case "function_expression":
1721
+ return "function";
1722
+ case "identifier":
1723
+ return `@var:${valueNode.text}`;
1724
+ case "call_expression":
1725
+ return "@call";
1726
+ default:
1727
+ return "unknown";
1728
+ }
1729
+ }
1730
+ function extractPythonReturns(node, functionId, functionName, returns) {
1731
+ traverseNode2(node, (current) => {
1732
+ if (current.type === "return_statement") {
1733
+ const returnNode = current.children.find((c) => c.type !== "return");
1734
+ if (returnNode) {
1735
+ const returnInfo = parsePythonReturnValue(
1736
+ returnNode,
1737
+ functionId,
1738
+ functionName
1739
+ );
1740
+ if (returnInfo) {
1741
+ returns.push(returnInfo);
1742
+ }
1743
+ }
1744
+ }
1745
+ });
1746
+ }
1747
+ function parsePythonReturnValue(returnNode, functionId, functionName) {
1748
+ const line = returnNode.startPosition.row + 1;
1749
+ const returnExpression = returnNode.text;
1750
+ let returnType = null;
1751
+ let objectShape = null;
1752
+ if (returnNode.type === "dictionary") {
1753
+ objectShape = extractPythonDictionaryShape(returnNode);
1754
+ returnType = "dict";
1755
+ } else if (returnNode.type === "list") {
1756
+ returnType = "list";
1757
+ objectShape = {
1758
+ properties: [],
1759
+ propertyTypes: /* @__PURE__ */ new Map(),
1760
+ nestedShapes: /* @__PURE__ */ new Map(),
1761
+ isArray: true,
1762
+ isDictionary: false,
1763
+ isOptional: false
1764
+ };
1765
+ } else if (returnNode.type === "call") {
1766
+ const funcNode = returnNode.childForFieldName("function");
1767
+ if (funcNode) {
1768
+ const funcName = funcNode.text;
1769
+ if (funcName[0] === funcName[0].toUpperCase()) {
1770
+ returnType = funcName;
1771
+ } else {
1772
+ returnType = `@call:${funcName}`;
1773
+ }
1774
+ }
1775
+ } else if (returnNode.type === "identifier") {
1776
+ returnType = `@var:${returnNode.text}`;
1777
+ } else if (returnNode.type === "list_comprehension") {
1778
+ returnType = "list";
1779
+ objectShape = {
1780
+ properties: [],
1781
+ propertyTypes: /* @__PURE__ */ new Map(),
1782
+ nestedShapes: /* @__PURE__ */ new Map(),
1783
+ isArray: true,
1784
+ isDictionary: false,
1785
+ isOptional: false
1786
+ };
1787
+ }
1788
+ return {
1789
+ functionId,
1790
+ functionName,
1791
+ returnType,
1792
+ objectShape,
1793
+ line,
1794
+ returnExpression
1795
+ };
1796
+ }
1797
+ function extractPythonDictionaryShape(dictNode) {
1798
+ const properties = [];
1799
+ const propertyTypes = /* @__PURE__ */ new Map();
1800
+ const nestedShapes = /* @__PURE__ */ new Map();
1801
+ for (const child of dictNode.children) {
1802
+ if (child.type === "pair") {
1803
+ const keyNode = child.children.find((c) => c.type === "string");
1804
+ const valueNode = child.children.find(
1805
+ (c) => c.type !== "string" && c.type !== ":"
1806
+ );
1807
+ if (keyNode) {
1808
+ const stringContent = keyNode.children.find(
1809
+ (c) => c.type === "string_content"
1810
+ );
1811
+ const propertyName = stringContent ? stringContent.text : keyNode.text.replace(/['"]/g, "");
1812
+ properties.push(propertyName);
1813
+ if (valueNode) {
1814
+ const propertyType = inferPythonType(valueNode);
1815
+ propertyTypes.set(propertyName, propertyType);
1816
+ if (valueNode.type === "dictionary") {
1817
+ const nestedShape = extractPythonDictionaryShape(valueNode);
1818
+ nestedShapes.set(propertyName, nestedShape);
1819
+ }
1820
+ }
1821
+ }
1822
+ }
1823
+ }
1824
+ return {
1825
+ properties,
1826
+ propertyTypes,
1827
+ nestedShapes,
1828
+ isArray: false,
1829
+ isDictionary: true,
1830
+ isOptional: false
1831
+ };
1832
+ }
1833
+ function inferPythonType(valueNode) {
1834
+ switch (valueNode.type) {
1835
+ case "integer":
1836
+ case "float":
1837
+ return "number";
1838
+ case "string":
1839
+ return "string";
1840
+ case "true":
1841
+ case "false":
1842
+ return "boolean";
1843
+ case "none":
1844
+ return "None";
1845
+ case "list":
1846
+ return "list";
1847
+ case "dictionary":
1848
+ return "dict";
1849
+ case "lambda":
1850
+ return "function";
1851
+ case "identifier":
1852
+ return `@var:${valueNode.text}`;
1853
+ case "call":
1854
+ return "@call";
1855
+ default:
1856
+ return "unknown";
1857
+ }
1858
+ }
1859
+ function traverseNode2(node, visitor) {
1860
+ visitor(node);
1861
+ for (let i = 0; i < node.childCount; i++) {
1862
+ const child = node.child(i);
1863
+ if (child) {
1864
+ traverseNode2(child, visitor);
1865
+ }
1866
+ }
1867
+ }
1868
+
1869
+ // src/flow/storage-queries-phase3.ts
1870
+ import { randomUUID } from "crypto";
1871
+ function storePropertyAccesses(db, accesses) {
1872
+ const stmt = db.prepare(`
1873
+ INSERT OR REPLACE INTO property_accesses
1874
+ (id, scope_node_id, object_name, property_path, full_path, access_type, file_path, line)
1875
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1876
+ `);
1877
+ const insert = db.transaction((accesses2) => {
1878
+ for (const access of accesses2) {
1879
+ stmt.run(
1880
+ randomUUID(),
1881
+ access.scopeId,
1882
+ access.objectName,
1883
+ JSON.stringify(access.propertyPath),
1884
+ access.fullPath,
1885
+ access.accessType,
1886
+ access.filePath || "",
1887
+ access.line
1888
+ );
1889
+ }
1890
+ });
1891
+ insert(accesses);
1892
+ }
1893
+ function storeReturnInfo(db, returns) {
1894
+ const stmt = db.prepare(`
1895
+ INSERT OR REPLACE INTO return_shapes
1896
+ (id, function_id, return_type, object_shape, line, return_expression)
1897
+ VALUES (?, ?, ?, ?, ?, ?)
1898
+ `);
1899
+ const insert = db.transaction((returns2) => {
1900
+ for (const ret of returns2) {
1901
+ stmt.run(
1902
+ randomUUID(),
1903
+ ret.functionId,
1904
+ ret.returnType,
1905
+ ret.objectShape ? JSON.stringify(serializeObjectShape(ret.objectShape)) : null,
1906
+ ret.line,
1907
+ ret.returnExpression
1908
+ );
1909
+ }
1910
+ });
1911
+ insert(returns);
1912
+ }
1913
+ function serializeObjectShape(shape) {
1914
+ return {
1915
+ properties: shape.properties,
1916
+ propertyTypes: Array.from(shape.propertyTypes.entries()),
1917
+ nestedShapes: Array.from(shape.nestedShapes.entries()).map(([key, nested]) => [
1918
+ key,
1919
+ serializeObjectShape(nested)
1920
+ ]),
1921
+ isArray: shape.isArray,
1922
+ isDictionary: shape.isDictionary,
1923
+ isOptional: shape.isOptional
1924
+ };
1925
+ }
1926
+
1927
+ // src/flow/phase3-analyzer.ts
1928
+ function analyzeFileForPhase3(filePath, functionNodes, db) {
1929
+ try {
1930
+ const language = getLanguageFromPath(filePath);
1931
+ if (!language) return;
1932
+ const parserInstance = parserRegistry.getByLanguage(language);
1933
+ if (!parserInstance || !parserInstance.treeSitterParser) return;
1934
+ const content = readFileSync(filePath, "utf-8");
1935
+ const tree = parserInstance.treeSitterParser.parse(content);
1936
+ const rootNode = tree.rootNode;
1937
+ for (const func of functionNodes) {
1938
+ const funcNode = findNodeAtLine(rootNode, func.startLine, func.endLine);
1939
+ if (!funcNode) continue;
1940
+ const accesses = extractPropertyAccesses(funcNode, func.id, func.name, filePath);
1941
+ if (accesses.length > 0) {
1942
+ const accessesWithFile = accesses.map((a) => ({ ...a, filePath }));
1943
+ storePropertyAccesses(db, accessesWithFile);
1944
+ }
1945
+ const returns = analyzeReturnStatements(funcNode, func.id, func.name, filePath);
1946
+ if (returns.length > 0) {
1947
+ storeReturnInfo(db, returns);
1948
+ }
1949
+ }
1950
+ } catch (error) {
1951
+ }
1952
+ }
1953
+ function getLanguageFromPath(filePath) {
1954
+ if (filePath.endsWith(".py")) return "python";
1955
+ if (filePath.endsWith(".js") || filePath.endsWith(".jsx")) return "javascript";
1956
+ if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
1957
+ return null;
1958
+ }
1959
+ function findNodeAtLine(node, startLine, endLine) {
1960
+ const nodeStart = node.startPosition.row + 1;
1961
+ const nodeEnd = node.endPosition.row + 1;
1962
+ if (nodeStart === startLine && nodeEnd === endLine) {
1963
+ return node;
1964
+ }
1965
+ for (let i = 0; i < node.childCount; i++) {
1966
+ const child = node.child(i);
1967
+ if (child) {
1968
+ const result = findNodeAtLine(child, startLine, endLine);
1969
+ if (result) return result;
1970
+ }
1971
+ }
1972
+ if (nodeStart <= startLine && nodeEnd >= endLine) {
1973
+ if (node.type === "function_definition" || node.type === "function_declaration" || node.type === "method_definition" || node.type === "arrow_function" || node.type === "function_expression") {
1974
+ return node;
1975
+ }
1976
+ }
1977
+ return null;
1978
+ }
1979
+
964
1980
  // src/flow/builder.ts
965
1981
  var FlowBuilder = class {
966
1982
  storage;
@@ -988,7 +2004,7 @@ var FlowBuilder = class {
988
2004
  const filesToIndex = [];
989
2005
  let skipped = 0;
990
2006
  for (const filePath of allFiles) {
991
- const content = readFileSync(filePath, "utf-8");
2007
+ const content = readFileSync2(filePath, "utf-8");
992
2008
  const hash = this.hashContent(content);
993
2009
  if (!this.config.forceReindex) {
994
2010
  const existingHash = this.storage.getFileHash(filePath);
@@ -1021,6 +2037,12 @@ var FlowBuilder = class {
1021
2037
  currentFile: ""
1022
2038
  });
1023
2039
  this.resolveAllCalls();
2040
+ const enhancedResolver = new EnhancedResolver(this.storage);
2041
+ enhancedResolver.buildHierarchy();
2042
+ const enhancedResolved = enhancedResolver.resolveAllCalls();
2043
+ if (enhancedResolved.length > 0) {
2044
+ console.log(`\u2728 Enhanced resolver: resolved ${enhancedResolved.length} additional calls`);
2045
+ }
1024
2046
  const stats = this.storage.getStats();
1025
2047
  this.emitProgress({
1026
2048
  phase: "complete",
@@ -1066,7 +2088,7 @@ var FlowBuilder = class {
1066
2088
  }
1067
2089
  async parseAndStore(filePath, hash, rootPath) {
1068
2090
  const language = this.getLanguage(filePath);
1069
- const content = readFileSync(filePath, "utf-8");
2091
+ const content = readFileSync2(filePath, "utf-8");
1070
2092
  const parser = parserRegistry.getByLanguage(language);
1071
2093
  if (!parser) {
1072
2094
  throw new Error(`No parser found for language: ${language}`);
@@ -1103,6 +2125,15 @@ var FlowBuilder = class {
1103
2125
  );
1104
2126
  }
1105
2127
  }
2128
+ const functionNodes = analysis.nodes.filter((n) => n.type === "function" || n.type === "method").map((n) => ({
2129
+ id: n.id,
2130
+ name: n.name,
2131
+ startLine: n.startLine,
2132
+ endLine: n.endLine
2133
+ }));
2134
+ if (functionNodes.length > 0) {
2135
+ analyzeFileForPhase3(filePath, functionNodes, this.storage.db);
2136
+ }
1106
2137
  if (analysis.databaseOperations) {
1107
2138
  for (const dbOp of analysis.databaseOperations) {
1108
2139
  this.storage.insertDatabaseOperation({
@@ -1382,9 +2413,90 @@ function getGitStatus(rootPath) {
1382
2413
  }
1383
2414
 
1384
2415
  // src/analysis/imports.ts
1385
- import { readFileSync as readFileSync2 } from "fs";
2416
+ import { readFileSync as readFileSync3 } from "fs";
2417
+ function isJavaScriptBuiltin(identifier) {
2418
+ const builtins = /* @__PURE__ */ new Set([
2419
+ "Object",
2420
+ "Array",
2421
+ "String",
2422
+ "Number",
2423
+ "Boolean",
2424
+ "Date",
2425
+ "RegExp",
2426
+ "Error",
2427
+ "TypeError",
2428
+ "SyntaxError",
2429
+ "ReferenceError",
2430
+ "Promise",
2431
+ "Map",
2432
+ "Set",
2433
+ "WeakMap",
2434
+ "WeakSet",
2435
+ "JSON",
2436
+ "Math",
2437
+ "Infinity",
2438
+ "NaN",
2439
+ "undefined",
2440
+ "Function",
2441
+ "Symbol",
2442
+ "Proxy",
2443
+ "Reflect",
2444
+ "Int8Array",
2445
+ "Uint8Array",
2446
+ "Int16Array",
2447
+ "Uint16Array",
2448
+ "Int32Array",
2449
+ "Uint32Array",
2450
+ "Float32Array",
2451
+ "Float64Array",
2452
+ "BigInt",
2453
+ "ArrayBuffer",
2454
+ "DataView",
2455
+ "Intl",
2456
+ "WebAssembly",
2457
+ "Buffer",
2458
+ "URL",
2459
+ "URLSearchParams",
2460
+ "console",
2461
+ "process",
2462
+ "global",
2463
+ "window",
2464
+ "document",
2465
+ // Common keywords that look like types
2466
+ "TRUE",
2467
+ "FALSE",
2468
+ "NULL"
2469
+ ]);
2470
+ return builtins.has(identifier);
2471
+ }
2472
+ function isPythonBuiltin(identifier) {
2473
+ const builtins = /* @__PURE__ */ new Set([
2474
+ "True",
2475
+ "False",
2476
+ "None",
2477
+ "Exception",
2478
+ "BaseException",
2479
+ "ValueError",
2480
+ "TypeError",
2481
+ "KeyError",
2482
+ "AttributeError",
2483
+ "IndexError",
2484
+ "RuntimeError",
2485
+ "NotImplementedError",
2486
+ "Dict",
2487
+ "List",
2488
+ "Tuple",
2489
+ "Set",
2490
+ "FrozenSet",
2491
+ "Optional",
2492
+ "Union",
2493
+ "Any",
2494
+ "Callable"
2495
+ ]);
2496
+ return builtins.has(identifier);
2497
+ }
1386
2498
  function analyzeJavaScriptImports(filePath) {
1387
- const content = readFileSync2(filePath, "utf-8");
2499
+ const content = readFileSync3(filePath, "utf-8");
1388
2500
  const lines = content.split("\n");
1389
2501
  const imports = /* @__PURE__ */ new Map();
1390
2502
  for (let i = 0; i < lines.length; i++) {
@@ -1423,19 +2535,52 @@ function analyzeJavaScriptImports(filePath) {
1423
2535
  }
1424
2536
  }
1425
2537
  const missing = [];
1426
- const commonLibraries = ["express", "cors", "winston", "Redis", "MongoClient", "mongoose", "axios"];
1427
- for (const lib of commonLibraries) {
1428
- if (!imports.has(lib)) {
2538
+ const localDefinitions = /* @__PURE__ */ new Set();
2539
+ for (let i = 0; i < lines.length; i++) {
2540
+ const line = lines[i];
2541
+ const classMatch = line.match(/^(?:export\s+)?class\s+([A-Z][a-zA-Z0-9]*)/);
2542
+ if (classMatch) {
2543
+ localDefinitions.add(classMatch[1]);
2544
+ }
2545
+ const funcMatch = line.match(/^(?:export\s+)?(?:async\s+)?function\s+([A-Z][a-zA-Z0-9]*)/);
2546
+ if (funcMatch) {
2547
+ localDefinitions.add(funcMatch[1]);
2548
+ }
2549
+ const constMatch = line.match(/^(?:export\s+)?const\s+([A-Z][a-z][a-zA-Z0-9]*)\s*=/);
2550
+ if (constMatch) {
2551
+ localDefinitions.add(constMatch[1]);
2552
+ }
2553
+ }
2554
+ const potentialImports = /* @__PURE__ */ new Set();
2555
+ for (let i = 0; i < lines.length; i++) {
2556
+ let line = lines[i];
2557
+ if (line.trim().startsWith("import ") || line.trim().startsWith("//") || line.trim().startsWith("*") || line.trim().startsWith("#")) {
2558
+ continue;
2559
+ }
2560
+ line = line.replace(/\/\/.*$/g, "");
2561
+ line = line.replace(/\/\*.*?\*\//g, "");
2562
+ line = line.replace(/[`"'].*?[`"']/g, "");
2563
+ const matches = line.matchAll(/(?<!\.)([A-Z][a-z][a-zA-Z0-9]*)(?!\.)/g);
2564
+ for (const match of matches) {
2565
+ const identifier = match[1];
2566
+ if (!isJavaScriptBuiltin(identifier)) {
2567
+ potentialImports.add(identifier);
2568
+ }
2569
+ }
2570
+ }
2571
+ for (const identifier of potentialImports) {
2572
+ if (!imports.has(identifier) && !localDefinitions.has(identifier)) {
1429
2573
  const usageLines = [];
1430
2574
  for (let i = 0; i < lines.length; i++) {
1431
2575
  const line = lines[i];
1432
- if (line.includes(lib) && !line.trim().startsWith("//")) {
2576
+ const regex = new RegExp(`\\b${identifier}\\b`);
2577
+ if (regex.test(line) && !line.trim().startsWith("//")) {
1433
2578
  usageLines.push(i + 1);
1434
2579
  }
1435
2580
  }
1436
2581
  if (usageLines.length > 0) {
1437
2582
  missing.push({
1438
- identifier: lib,
2583
+ identifier,
1439
2584
  line: usageLines[0],
1440
2585
  type: "missing",
1441
2586
  usedAt: usageLines
@@ -1446,7 +2591,7 @@ function analyzeJavaScriptImports(filePath) {
1446
2591
  return { missing, unused };
1447
2592
  }
1448
2593
  function analyzePythonImports(filePath) {
1449
- const content = readFileSync2(filePath, "utf-8");
2594
+ const content = readFileSync3(filePath, "utf-8");
1450
2595
  const lines = content.split("\n");
1451
2596
  const imports = /* @__PURE__ */ new Map();
1452
2597
  for (let i = 0; i < lines.length; i++) {
@@ -1484,7 +2629,56 @@ function analyzePythonImports(filePath) {
1484
2629
  unused.push({ identifier, line, type: "unused" });
1485
2630
  }
1486
2631
  }
1487
- return { missing: [], unused };
2632
+ const missing = [];
2633
+ const localDefinitions = /* @__PURE__ */ new Set();
2634
+ for (let i = 0; i < lines.length; i++) {
2635
+ const line = lines[i];
2636
+ const classMatch = line.match(/^class\s+([A-Z][a-zA-Z0-9_]*)/);
2637
+ if (classMatch) {
2638
+ localDefinitions.add(classMatch[1]);
2639
+ }
2640
+ const funcMatch = line.match(/^(?:async\s+)?def\s+([A-Z][a-zA-Z0-9_]*)/);
2641
+ if (funcMatch) {
2642
+ localDefinitions.add(funcMatch[1]);
2643
+ }
2644
+ }
2645
+ const potentialImports = /* @__PURE__ */ new Set();
2646
+ for (let i = 0; i < lines.length; i++) {
2647
+ let line = lines[i];
2648
+ if (line.trim().startsWith("import ") || line.trim().startsWith("from ") || line.trim().startsWith("#")) {
2649
+ continue;
2650
+ }
2651
+ line = line.replace(/#.*$/g, "");
2652
+ line = line.replace(/[`"'f].*?[`"']/g, "");
2653
+ const matches = line.matchAll(/(?<!\.)([A-Z][a-zA-Z0-9_]*)(?!\.)/g);
2654
+ for (const match of matches) {
2655
+ const identifier = match[1];
2656
+ if (!isPythonBuiltin(identifier)) {
2657
+ potentialImports.add(identifier);
2658
+ }
2659
+ }
2660
+ }
2661
+ for (const identifier of potentialImports) {
2662
+ if (!imports.has(identifier) && !localDefinitions.has(identifier)) {
2663
+ const usageLines = [];
2664
+ for (let i = 0; i < lines.length; i++) {
2665
+ const line = lines[i];
2666
+ const regex = new RegExp(`\\b${identifier}\\b`);
2667
+ if (regex.test(line) && !line.trim().startsWith("#")) {
2668
+ usageLines.push(i + 1);
2669
+ }
2670
+ }
2671
+ if (usageLines.length > 0) {
2672
+ missing.push({
2673
+ identifier,
2674
+ line: usageLines[0],
2675
+ type: "missing",
2676
+ usedAt: usageLines
2677
+ });
2678
+ }
2679
+ }
2680
+ }
2681
+ return { missing, unused };
1488
2682
  }
1489
2683
  function analyzeImports(filePath) {
1490
2684
  if (filePath.endsWith(".py")) {
@@ -1496,14 +2690,14 @@ function analyzeImports(filePath) {
1496
2690
  }
1497
2691
 
1498
2692
  // src/analysis/docker.ts
1499
- import { readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
2693
+ import { readFileSync as readFileSync4, existsSync as existsSync2 } from "fs";
1500
2694
  import { load as parseYaml } from "js-yaml";
1501
2695
  import { join as join2 } from "path";
1502
2696
  function parseDockerfile(dockerfilePath) {
1503
2697
  if (!existsSync2(dockerfilePath)) {
1504
2698
  return null;
1505
2699
  }
1506
- const content = readFileSync3(dockerfilePath, "utf-8");
2700
+ const content = readFileSync4(dockerfilePath, "utf-8");
1507
2701
  const lines = content.split("\n");
1508
2702
  let inHealthCheck = false;
1509
2703
  for (const line of lines) {
@@ -1581,7 +2775,7 @@ function parseDockerCompose(projectRoot) {
1581
2775
  let composeData = null;
1582
2776
  for (const file of composeFiles) {
1583
2777
  if (existsSync2(file)) {
1584
- const content = readFileSync3(file, "utf-8");
2778
+ const content = readFileSync4(file, "utf-8");
1585
2779
  composeData = parseYaml(content);
1586
2780
  break;
1587
2781
  }
@@ -1650,6 +2844,549 @@ function calculateServiceBlastRadius(graph, serviceName) {
1650
2844
  return Array.from(affected);
1651
2845
  }
1652
2846
 
2847
+ // src/analysis/line-diff.ts
2848
+ import { execSync as execSync2 } from "child_process";
2849
+ function parseLineLevelDiff(projectRoot) {
2850
+ try {
2851
+ const diffOutput = execSync2("git diff HEAD", {
2852
+ cwd: projectRoot,
2853
+ encoding: "utf-8",
2854
+ maxBuffer: 10 * 1024 * 1024
2855
+ // 10MB buffer
2856
+ });
2857
+ if (!diffOutput.trim()) {
2858
+ return {
2859
+ removedLines: [],
2860
+ addedLines: [],
2861
+ modifiedLines: []
2862
+ };
2863
+ }
2864
+ return parseDiffOutput(diffOutput);
2865
+ } catch (error) {
2866
+ return {
2867
+ removedLines: [],
2868
+ addedLines: [],
2869
+ modifiedLines: []
2870
+ };
2871
+ }
2872
+ }
2873
+ function parseDiffOutput(diffOutput) {
2874
+ const removedLines = [];
2875
+ const addedLines = [];
2876
+ let currentFile = "";
2877
+ let currentFunction = "";
2878
+ let removedLineNumber = 0;
2879
+ let addedLineNumber = 0;
2880
+ const lines = diffOutput.split("\n");
2881
+ for (let i = 0; i < lines.length; i++) {
2882
+ const line = lines[i];
2883
+ if (line.startsWith("diff --git")) {
2884
+ const match = line.match(/b\/(.*?)$/);
2885
+ if (match) {
2886
+ currentFile = match[1].trim();
2887
+ }
2888
+ continue;
2889
+ }
2890
+ if (line.startsWith("@@")) {
2891
+ const match = line.match(/@@ -(\d+),\d+ \+(\d+),\d+ @@(.*)/);
2892
+ if (match) {
2893
+ removedLineNumber = parseInt(match[1]);
2894
+ addedLineNumber = parseInt(match[2]);
2895
+ const context = match[3].trim();
2896
+ if (context) {
2897
+ currentFunction = extractFunctionName(context);
2898
+ }
2899
+ }
2900
+ continue;
2901
+ }
2902
+ if (line.startsWith("---") || line.startsWith("+++") || line.startsWith("index ")) {
2903
+ continue;
2904
+ }
2905
+ if (line.startsWith("-") && !line.startsWith("---")) {
2906
+ const content = line.substring(1);
2907
+ if (content.trim()) {
2908
+ removedLines.push({
2909
+ file: currentFile,
2910
+ lineNumber: removedLineNumber,
2911
+ content,
2912
+ inFunction: currentFunction || void 0
2913
+ });
2914
+ }
2915
+ removedLineNumber++;
2916
+ continue;
2917
+ }
2918
+ if (line.startsWith("+") && !line.startsWith("+++")) {
2919
+ const content = line.substring(1);
2920
+ if (content.trim()) {
2921
+ addedLines.push({
2922
+ file: currentFile,
2923
+ lineNumber: addedLineNumber,
2924
+ content,
2925
+ inFunction: currentFunction || void 0
2926
+ });
2927
+ }
2928
+ addedLineNumber++;
2929
+ continue;
2930
+ }
2931
+ if (line.startsWith(" ") || !line.startsWith("-") && !line.startsWith("+")) {
2932
+ removedLineNumber++;
2933
+ addedLineNumber++;
2934
+ }
2935
+ }
2936
+ const modifiedLines = matchModifiedLines(removedLines, addedLines);
2937
+ return {
2938
+ removedLines,
2939
+ addedLines,
2940
+ modifiedLines
2941
+ };
2942
+ }
2943
+ function extractFunctionName(context) {
2944
+ context = context.replace(/^(function|def|class|const|let|var|async|export)\s+/, "");
2945
+ const patterns = [
2946
+ /^(\w+)\s*\(/,
2947
+ // functionName(
2948
+ /^(\w+)\s*=/,
2949
+ // functionName =
2950
+ /^(\w+)\s*{/,
2951
+ // ClassName {
2952
+ /^(\w+)\s*:/
2953
+ // functionName:
2954
+ ];
2955
+ for (const pattern of patterns) {
2956
+ const match = context.match(pattern);
2957
+ if (match) {
2958
+ return match[1];
2959
+ }
2960
+ }
2961
+ const firstWord = context.split(/\s+/)[0];
2962
+ return firstWord || "";
2963
+ }
2964
+ function matchModifiedLines(removedLines, addedLines) {
2965
+ const modifiedLines = [];
2966
+ const removedByLocation = /* @__PURE__ */ new Map();
2967
+ const addedByLocation = /* @__PURE__ */ new Map();
2968
+ for (const removed of removedLines) {
2969
+ const key = `${removed.file}:${removed.inFunction || "global"}`;
2970
+ if (!removedByLocation.has(key)) {
2971
+ removedByLocation.set(key, []);
2972
+ }
2973
+ removedByLocation.get(key).push(removed);
2974
+ }
2975
+ for (const added of addedLines) {
2976
+ const key = `${added.file}:${added.inFunction || "global"}`;
2977
+ if (!addedByLocation.has(key)) {
2978
+ addedByLocation.set(key, []);
2979
+ }
2980
+ addedByLocation.get(key).push(added);
2981
+ }
2982
+ for (const [location, removedLinesInLocation] of removedByLocation) {
2983
+ const addedLinesInLocation = addedByLocation.get(location) || [];
2984
+ for (const removed of removedLinesInLocation) {
2985
+ const matchingAdded = addedLinesInLocation.find(
2986
+ (added) => Math.abs(added.lineNumber - removed.lineNumber) <= 2
2987
+ );
2988
+ if (matchingAdded) {
2989
+ modifiedLines.push({
2990
+ before: removed,
2991
+ after: matchingAdded
2992
+ });
2993
+ }
2994
+ }
2995
+ }
2996
+ return modifiedLines;
2997
+ }
2998
+
2999
+ // src/analysis/removed-calls.ts
3000
+ function extractRemovedCalls(removedLines) {
3001
+ const removedCalls = [];
3002
+ for (const line of removedLines) {
3003
+ const calls = parseLineForCalls(line.content, line.file);
3004
+ for (const call of calls) {
3005
+ removedCalls.push({
3006
+ callee: call.name,
3007
+ removedFrom: line.inFunction || "unknown",
3008
+ file: line.file,
3009
+ lineNumber: line.lineNumber,
3010
+ rawContent: line.content.trim()
3011
+ });
3012
+ }
3013
+ }
3014
+ return removedCalls;
3015
+ }
3016
+ function parseLineForCalls(lineContent, filePath) {
3017
+ const calls = [];
3018
+ if (isCommentOrString(lineContent)) {
3019
+ return calls;
3020
+ }
3021
+ lineContent = lineContent.replace(/\/\/.*$/g, "");
3022
+ lineContent = lineContent.replace(/#.*$/g, "");
3023
+ lineContent = lineContent.replace(/\/\*.*?\*\//g, "");
3024
+ lineContent = lineContent.replace(/["'`].*?["'`]/g, "");
3025
+ const isJS = /\.(js|ts|jsx|tsx)$/.test(filePath);
3026
+ const isPython = /\.py$/.test(filePath);
3027
+ if (isJS) {
3028
+ calls.push(...extractJavaScriptCalls(lineContent));
3029
+ }
3030
+ if (isPython) {
3031
+ calls.push(...extractPythonCalls(lineContent));
3032
+ }
3033
+ return calls;
3034
+ }
3035
+ function isCommentOrString(line) {
3036
+ const trimmed = line.trim();
3037
+ if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*") || trimmed.startsWith("#")) {
3038
+ return true;
3039
+ }
3040
+ const stringPrefixes = ['"', "'", "`"];
3041
+ if (stringPrefixes.some((prefix) => trimmed.startsWith(prefix))) {
3042
+ return true;
3043
+ }
3044
+ return false;
3045
+ }
3046
+ function extractJavaScriptCalls(lineContent) {
3047
+ const calls = [];
3048
+ const seen = /* @__PURE__ */ new Set();
3049
+ const simplePattern = /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
3050
+ let match;
3051
+ while ((match = simplePattern.exec(lineContent)) !== null) {
3052
+ const functionName = match[1];
3053
+ if (!isJavaScriptKeyword(functionName) && !seen.has(functionName)) {
3054
+ calls.push({ name: functionName });
3055
+ seen.add(functionName);
3056
+ }
3057
+ }
3058
+ const methodPattern = /\.([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
3059
+ while ((match = methodPattern.exec(lineContent)) !== null) {
3060
+ const methodName = match[1];
3061
+ if (!isJavaScriptKeyword(methodName) && !seen.has(methodName)) {
3062
+ calls.push({ name: methodName });
3063
+ seen.add(methodName);
3064
+ }
3065
+ }
3066
+ const awaitPattern = /await\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
3067
+ while ((match = awaitPattern.exec(lineContent)) !== null) {
3068
+ const functionName = match[1];
3069
+ if (!isJavaScriptKeyword(functionName) && !seen.has(functionName)) {
3070
+ calls.push({ name: functionName });
3071
+ seen.add(functionName);
3072
+ }
3073
+ }
3074
+ return calls;
3075
+ }
3076
+ function extractPythonCalls(lineContent) {
3077
+ const calls = [];
3078
+ const seen = /* @__PURE__ */ new Set();
3079
+ const simplePattern = /\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g;
3080
+ let match;
3081
+ while ((match = simplePattern.exec(lineContent)) !== null) {
3082
+ const functionName = match[1];
3083
+ if (!isPythonKeyword(functionName) && !seen.has(functionName)) {
3084
+ calls.push({ name: functionName });
3085
+ seen.add(functionName);
3086
+ }
3087
+ }
3088
+ const methodPattern = /\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g;
3089
+ while ((match = methodPattern.exec(lineContent)) !== null) {
3090
+ const methodName = match[1];
3091
+ if (!isPythonKeyword(methodName) && !seen.has(methodName)) {
3092
+ calls.push({ name: methodName });
3093
+ seen.add(methodName);
3094
+ }
3095
+ }
3096
+ const awaitPattern = /await\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g;
3097
+ while ((match = awaitPattern.exec(lineContent)) !== null) {
3098
+ const functionName = match[1];
3099
+ if (!isPythonKeyword(functionName) && !seen.has(functionName)) {
3100
+ calls.push({ name: functionName });
3101
+ seen.add(functionName);
3102
+ }
3103
+ }
3104
+ return calls;
3105
+ }
3106
+ function isJavaScriptKeyword(identifier) {
3107
+ const keywords = /* @__PURE__ */ new Set([
3108
+ // Keywords
3109
+ "if",
3110
+ "else",
3111
+ "for",
3112
+ "while",
3113
+ "do",
3114
+ "switch",
3115
+ "case",
3116
+ "break",
3117
+ "continue",
3118
+ "return",
3119
+ "throw",
3120
+ "try",
3121
+ "catch",
3122
+ "finally",
3123
+ "new",
3124
+ "typeof",
3125
+ "instanceof",
3126
+ "delete",
3127
+ "void",
3128
+ "async",
3129
+ "await",
3130
+ "yield",
3131
+ "import",
3132
+ "export",
3133
+ "from",
3134
+ "class",
3135
+ "extends",
3136
+ "super",
3137
+ "this",
3138
+ "static",
3139
+ "const",
3140
+ "let",
3141
+ "var",
3142
+ "function",
3143
+ "constructor",
3144
+ "get",
3145
+ "set",
3146
+ // Common builtins to skip
3147
+ "console",
3148
+ "parseInt",
3149
+ "parseFloat",
3150
+ "isNaN",
3151
+ "isFinite",
3152
+ "setTimeout",
3153
+ "setInterval",
3154
+ "clearTimeout",
3155
+ "clearInterval",
3156
+ "require",
3157
+ "module",
3158
+ "exports",
3159
+ "__dirname",
3160
+ "__filename"
3161
+ ]);
3162
+ return keywords.has(identifier);
3163
+ }
3164
+ function isPythonKeyword(identifier) {
3165
+ const keywords = /* @__PURE__ */ new Set([
3166
+ // Keywords
3167
+ "if",
3168
+ "elif",
3169
+ "else",
3170
+ "for",
3171
+ "while",
3172
+ "break",
3173
+ "continue",
3174
+ "pass",
3175
+ "return",
3176
+ "raise",
3177
+ "try",
3178
+ "except",
3179
+ "finally",
3180
+ "with",
3181
+ "as",
3182
+ "yield",
3183
+ "import",
3184
+ "from",
3185
+ "class",
3186
+ "def",
3187
+ "lambda",
3188
+ "global",
3189
+ "nonlocal",
3190
+ "assert",
3191
+ "del",
3192
+ "in",
3193
+ "is",
3194
+ "not",
3195
+ "and",
3196
+ "or",
3197
+ "async",
3198
+ "await",
3199
+ // Common builtins to skip
3200
+ "print",
3201
+ "len",
3202
+ "range",
3203
+ "enumerate",
3204
+ "zip",
3205
+ "map",
3206
+ "filter",
3207
+ "sorted",
3208
+ "reversed",
3209
+ "sum",
3210
+ "min",
3211
+ "max",
3212
+ "abs",
3213
+ "round",
3214
+ "open",
3215
+ "input",
3216
+ "type",
3217
+ "isinstance",
3218
+ "hasattr",
3219
+ "getattr",
3220
+ "setattr"
3221
+ ]);
3222
+ return keywords.has(identifier);
3223
+ }
3224
+
3225
+ // src/analysis/removed-call-impact.ts
3226
+ var SECURITY_KEYWORDS = [
3227
+ // Authentication & Authorization
3228
+ "authenticate",
3229
+ "authorization",
3230
+ "authorize",
3231
+ "login",
3232
+ "logout",
3233
+ "signin",
3234
+ "signout",
3235
+ "verify",
3236
+ "check",
3237
+ "validate",
3238
+ "permission",
3239
+ "access",
3240
+ "role",
3241
+ "admin",
3242
+ "auth",
3243
+ // Input validation & sanitization
3244
+ "sanitize",
3245
+ "escape",
3246
+ "clean",
3247
+ "filter",
3248
+ "whitelist",
3249
+ "blacklist",
3250
+ "validate",
3251
+ "verification",
3252
+ "check",
3253
+ // Security tokens & sessions
3254
+ "token",
3255
+ "session",
3256
+ "cookie",
3257
+ "csrf",
3258
+ "xsrf",
3259
+ "jwt",
3260
+ "oauth",
3261
+ "api_key",
3262
+ "apikey",
3263
+ "secret",
3264
+ "password",
3265
+ "hash",
3266
+ "encrypt",
3267
+ // Injection prevention
3268
+ "xss",
3269
+ "sql",
3270
+ "injection",
3271
+ "query",
3272
+ "prepared",
3273
+ "statement",
3274
+ // Rate limiting & abuse prevention
3275
+ "ratelimit",
3276
+ "throttle",
3277
+ "captcha",
3278
+ "honeypot"
3279
+ ];
3280
+ function analyzeRemovedCallImpact(removedCalls, storage) {
3281
+ const impacts = [];
3282
+ for (const removedCall of removedCalls) {
3283
+ try {
3284
+ const impact = analyzeSingleRemovedCall(removedCall, storage);
3285
+ if (impact) {
3286
+ impacts.push(impact);
3287
+ }
3288
+ } catch (error) {
3289
+ console.error(`Error analyzing removed call ${removedCall.callee}:`, error);
3290
+ }
3291
+ }
3292
+ return impacts;
3293
+ }
3294
+ function analyzeSingleRemovedCall(removedCall, storage) {
3295
+ const nodes = storage.searchNodesByName(removedCall.callee);
3296
+ if (nodes.length === 0) {
3297
+ return null;
3298
+ }
3299
+ const functionNode = nodes[0];
3300
+ const allCallers = storage.getResolvedCallers(functionNode.id);
3301
+ const stillCalledBy = allCallers.filter((c) => c.name !== removedCall.removedFrom).map((c) => ({
3302
+ name: c.name,
3303
+ file: c.filePath,
3304
+ line: c.startLine
3305
+ }));
3306
+ const removedFromNodes = storage.searchNodesByName(removedCall.removedFrom);
3307
+ let impactedFunctions = [];
3308
+ if (removedFromNodes.length > 0) {
3309
+ const removedFromNode = removedFromNodes[0];
3310
+ const impactedNodes = storage.getImpactedNodes(removedFromNode.id, 5);
3311
+ impactedFunctions = impactedNodes.map((n) => ({
3312
+ name: n.name,
3313
+ file: n.filePath,
3314
+ line: n.startLine,
3315
+ reason: `Calls ${removedCall.removedFrom}() which no longer calls ${removedCall.callee}()`
3316
+ }));
3317
+ }
3318
+ const severity = calculateSeverity(
3319
+ removedCall,
3320
+ stillCalledBy,
3321
+ impactedFunctions
3322
+ );
3323
+ const description = buildImpactDescription(
3324
+ removedCall,
3325
+ stillCalledBy,
3326
+ impactedFunctions,
3327
+ severity
3328
+ );
3329
+ return {
3330
+ removedCall,
3331
+ functionNode: {
3332
+ id: functionNode.id,
3333
+ name: functionNode.name,
3334
+ file: functionNode.filePath,
3335
+ line: functionNode.startLine
3336
+ },
3337
+ stillCalledBy,
3338
+ impactedFunctions,
3339
+ severity,
3340
+ description
3341
+ };
3342
+ }
3343
+ function calculateSeverity(removedCall, stillCalledBy, impactedFunctions) {
3344
+ const isSecurityFunction = SECURITY_KEYWORDS.some(
3345
+ (keyword) => removedCall.callee.toLowerCase().includes(keyword)
3346
+ );
3347
+ if (isSecurityFunction && impactedFunctions.length > 5) {
3348
+ return "CRITICAL";
3349
+ }
3350
+ if (isSecurityFunction && stillCalledBy.length === 0) {
3351
+ return "CRITICAL";
3352
+ }
3353
+ if (stillCalledBy.length === 0) {
3354
+ return "HIGH";
3355
+ }
3356
+ if (impactedFunctions.length > 20) {
3357
+ return "HIGH";
3358
+ }
3359
+ if (impactedFunctions.length >= 10) {
3360
+ return "MEDIUM";
3361
+ }
3362
+ if (isSecurityFunction && impactedFunctions.length > 0) {
3363
+ return "MEDIUM";
3364
+ }
3365
+ return "LOW";
3366
+ }
3367
+ function buildImpactDescription(removedCall, stillCalledBy, impactedFunctions, severity) {
3368
+ const parts = [];
3369
+ parts.push(
3370
+ `${removedCall.callee}() was removed from ${removedCall.removedFrom}() at line ${removedCall.lineNumber}`
3371
+ );
3372
+ if (stillCalledBy.length === 0) {
3373
+ parts.push(`\u26A0\uFE0F ORPHANED: ${removedCall.callee}() is no longer called anywhere in the codebase`);
3374
+ } else {
3375
+ const callerNames = stillCalledBy.slice(0, 3).map((c) => c.name).join(", ");
3376
+ const moreCount = stillCalledBy.length > 3 ? ` and ${stillCalledBy.length - 3} more` : "";
3377
+ parts.push(`Still called by: ${callerNames}${moreCount}`);
3378
+ }
3379
+ if (impactedFunctions.length > 0) {
3380
+ parts.push(
3381
+ `Impact: ${impactedFunctions.length} function${impactedFunctions.length === 1 ? "" : "s"} depend on ${removedCall.removedFrom}()`
3382
+ );
3383
+ }
3384
+ if (severity === "CRITICAL") {
3385
+ parts.push("\u{1F534} CRITICAL: Security or validation function removed from execution path");
3386
+ }
3387
+ return parts.join("\n");
3388
+ }
3389
+
1653
3390
  // src/commands/impact.ts
1654
3391
  async function analyzeImpact(options = {}) {
1655
3392
  const rootPath = resolve2(options.path || ".");
@@ -1674,6 +3411,8 @@ async function analyzeImpactWithGit(storage, rootPath, options) {
1674
3411
  directCallers: /* @__PURE__ */ new Map(),
1675
3412
  affectedEndpoints: [],
1676
3413
  importIssues: /* @__PURE__ */ new Map(),
3414
+ removedCallImpacts: [],
3415
+ // NEW
1677
3416
  riskLevel: "LOW",
1678
3417
  totalImpact: 0
1679
3418
  };
@@ -1730,17 +3469,22 @@ async function analyzeImpactWithGit(storage, rootPath, options) {
1730
3469
  entryPointFiles
1731
3470
  };
1732
3471
  }
3472
+ const lineDiff = parseLineLevelDiff(rootPath);
3473
+ const removedCalls = extractRemovedCalls(lineDiff.removedLines);
3474
+ const removedCallImpacts = analyzeRemovedCallImpact(removedCalls, storage);
1733
3475
  const totalCallers = Array.from(directCallers.values()).reduce((sum, callers) => sum + callers.length, 0);
1734
3476
  const hasCriticalImportIssues = Array.from(importIssues.values()).some((i) => i.missing.length > 0);
1735
3477
  const hasDockerImpact = dockerImpact && dockerImpact.affectedServices.length > 0;
3478
+ const hasCriticalRemovedCalls = removedCallImpacts.some((i) => i.severity === "CRITICAL");
3479
+ const hasHighRiskRemovedCalls = removedCallImpacts.some((i) => i.severity === "HIGH");
1736
3480
  let riskLevel = "LOW";
1737
- if (hasCriticalImportIssues && hasDockerImpact) {
3481
+ if (hasCriticalRemovedCalls || hasCriticalImportIssues && hasDockerImpact) {
1738
3482
  riskLevel = "CRITICAL";
1739
- } else if (hasCriticalImportIssues || hasDockerImpact && dockerImpact.affectedServices.length > 2) {
3483
+ } else if (hasHighRiskRemovedCalls || hasCriticalImportIssues || hasDockerImpact && dockerImpact.affectedServices.length > 2) {
1740
3484
  riskLevel = "HIGH";
1741
3485
  } else if (totalCallers > 20 || affectedEndpoints.length > 5) {
1742
3486
  riskLevel = "HIGH";
1743
- } else if (totalCallers > 10 || affectedEndpoints.length > 2 || hasDockerImpact) {
3487
+ } else if (totalCallers > 10 || affectedEndpoints.length > 2 || hasDockerImpact || removedCallImpacts.length > 0) {
1744
3488
  riskLevel = "MEDIUM";
1745
3489
  }
1746
3490
  return {
@@ -1750,8 +3494,10 @@ async function analyzeImpactWithGit(storage, rootPath, options) {
1750
3494
  affectedEndpoints,
1751
3495
  importIssues,
1752
3496
  dockerImpact,
3497
+ removedCallImpacts,
3498
+ // NEW
1753
3499
  riskLevel,
1754
- totalImpact: totalCallers + (dockerImpact?.affectedServices.length || 0)
3500
+ totalImpact: totalCallers + (dockerImpact?.affectedServices.length || 0) + removedCallImpacts.length
1755
3501
  };
1756
3502
  }
1757
3503
  async function analyzeImpactWithHash(storage, rootPath, options) {
@@ -1786,6 +3532,8 @@ async function analyzeImpactWithHash(storage, rootPath, options) {
1786
3532
  directCallers: /* @__PURE__ */ new Map(),
1787
3533
  affectedEndpoints: [],
1788
3534
  importIssues,
3535
+ removedCallImpacts: [],
3536
+ // NEW - not available in hash mode
1789
3537
  riskLevel: hasCriticalImportIssues ? "CRITICAL" : changedFiles.length > 10 ? "HIGH" : changedFiles.length > 5 ? "MEDIUM" : "LOW",
1790
3538
  totalImpact: result.indexed
1791
3539
  };
@@ -1799,7 +3547,7 @@ function configureMCPServer(projectPath) {
1799
3547
  if (!existsSync3(claudeConfigPath)) {
1800
3548
  return false;
1801
3549
  }
1802
- const config = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
3550
+ const config = JSON.parse(readFileSync5(claudeConfigPath, "utf-8"));
1803
3551
  if (!config.mcpServers) {
1804
3552
  config.mcpServers = {};
1805
3553
  }
@@ -1813,7 +3561,7 @@ function configureMCPServer(projectPath) {
1813
3561
  return false;
1814
3562
  }
1815
3563
  }
1816
- program.name("codemap").description("CodeMap Flow - Call graph analyzer for understanding code impact").version("3.2.0");
3564
+ program.name("codemap").description("CodeMap Flow - Call graph analyzer for understanding code impact").version("3.5.0");
1817
3565
  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) => {
1818
3566
  const rootPath = resolve3(path);
1819
3567
  const outputDir = join4(rootPath, ".codemap");
@@ -2021,12 +3769,12 @@ program.command("impact").description("Analyze impact of code changes").argument
2021
3769
  for (const [file, issues] of result.importIssues.entries()) {
2022
3770
  if (issues.missing.length > 0) {
2023
3771
  console.log(chalk.red(` \u274C CRITICAL: ${file} - Missing imports still in use:`));
2024
- for (const missing of issues.missing.slice(0, 5)) {
3772
+ for (const missing of issues.missing.slice(0, 10)) {
2025
3773
  const usageInfo = missing.usedAt ? ` (used at lines: ${missing.usedAt.slice(0, 3).join(", ")}${missing.usedAt.length > 3 ? "..." : ""})` : "";
2026
3774
  console.log(chalk.gray(` \u2022 ${missing.identifier}${usageInfo}`));
2027
3775
  }
2028
- if (issues.missing.length > 5) {
2029
- console.log(chalk.gray(` ... and ${issues.missing.length - 5} more`));
3776
+ if (issues.missing.length > 10) {
3777
+ console.log(chalk.gray(` ... and ${issues.missing.length - 10} more`));
2030
3778
  }
2031
3779
  }
2032
3780
  if (issues.unused.length > 0 && options.verbose) {
@@ -2037,6 +3785,42 @@ program.command("impact").description("Analyze impact of code changes").argument
2037
3785
  }
2038
3786
  }
2039
3787
  }
3788
+ if (result.removedCallImpacts && result.removedCallImpacts.length > 0) {
3789
+ console.log(chalk.blue("\n \u{1F6A8} Removed Function Calls:\n"));
3790
+ for (const impact of result.removedCallImpacts) {
3791
+ const severityColors = {
3792
+ "CRITICAL": chalk.red,
3793
+ "HIGH": chalk.red,
3794
+ "MEDIUM": chalk.yellow,
3795
+ "LOW": chalk.gray
3796
+ };
3797
+ const severityColor = severityColors[impact.severity];
3798
+ console.log(severityColor(` ${impact.severity}: ${impact.removedCall.callee}()`));
3799
+ console.log(chalk.gray(` Removed from: ${impact.removedCall.removedFrom}() at line ${impact.removedCall.lineNumber}`));
3800
+ console.log(chalk.gray(` File: ${impact.removedCall.file}`));
3801
+ if (impact.stillCalledBy.length === 0) {
3802
+ console.log(chalk.red(` \u26A0\uFE0F ORPHANED: No longer called anywhere!`));
3803
+ } else {
3804
+ const callerNames = impact.stillCalledBy.slice(0, 3).map((c) => c.name).join(", ");
3805
+ const moreCount = impact.stillCalledBy.length > 3 ? ` and ${impact.stillCalledBy.length - 3} more` : "";
3806
+ console.log(chalk.gray(` Still called by: ${callerNames}${moreCount}`));
3807
+ }
3808
+ if (impact.impactedFunctions.length > 0) {
3809
+ console.log(chalk.gray(` Impact: ${impact.impactedFunctions.length} dependent function${impact.impactedFunctions.length === 1 ? "" : "s"}`));
3810
+ const topImpacted = impact.impactedFunctions.slice(0, 3);
3811
+ for (const func of topImpacted) {
3812
+ console.log(chalk.gray(` \u2022 ${func.name} (${func.file}:${func.line})`));
3813
+ }
3814
+ if (impact.impactedFunctions.length > 3) {
3815
+ console.log(chalk.gray(` ... and ${impact.impactedFunctions.length - 3} more`));
3816
+ }
3817
+ }
3818
+ if (impact.severity === "CRITICAL") {
3819
+ console.log(chalk.red(` \u{1F534} ${impact.description.split("\n").pop()}`));
3820
+ }
3821
+ console.log();
3822
+ }
3823
+ }
2040
3824
  if (result.dockerImpact) {
2041
3825
  console.log(chalk.blue("\n \u{1F4E6} Docker Service Impact:\n"));
2042
3826
  console.log(chalk.red(` \u26A0\uFE0F Entry point file(s) modified - service(s) will not start:`));
@@ -2076,7 +3860,7 @@ program.command("reindex").description("Force full re-index of codebase").argume
2076
3860
  program.command("mcp-server").description("Start MCP server for Claude Code integration").option("-p, --path <path>", "Project root path", ".").action(async (options) => {
2077
3861
  process.env.CODEMAP_PROJECT_ROOT = resolve3(options.path);
2078
3862
  process.env.CODEMAP_DB_PATH = join4(resolve3(options.path), ".codemap", "graph.db");
2079
- await import("./flow-server-UQEOUP2C.js");
3863
+ await import("./flow-server-FMPWJSLK.js");
2080
3864
  });
2081
3865
  program.parse();
2082
3866
  //# sourceMappingURL=cli.js.map