arcvision 0.2.12 → 0.2.15

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.
Files changed (134) hide show
  1. package/ARCVISION_DIRECTORY_STRUCTURE.md +104 -0
  2. package/CLI_STRUCTURE.md +110 -0
  3. package/CONFIGURATION.md +119 -0
  4. package/IMPLEMENTATION_SUMMARY.md +99 -0
  5. package/README.md +149 -89
  6. package/architecture.authority.ledger.json +46 -0
  7. package/arcvision-0.2.3.tgz +0 -0
  8. package/arcvision-0.2.4.tgz +0 -0
  9. package/arcvision-0.2.5.tgz +0 -0
  10. package/arcvision.context.diff.json +2181 -0
  11. package/arcvision.context.json +1021 -0
  12. package/arcvision.context.v1.json +2163 -0
  13. package/arcvision.context.v2.json +2173 -0
  14. package/arcvision_context/README.md +93 -0
  15. package/arcvision_context/architecture.authority.ledger.json +83 -0
  16. package/arcvision_context/arcvision.context.json +6884 -0
  17. package/debug-cycle-detection.js +56 -0
  18. package/dist/index.js +1626 -25
  19. package/docs/ENHANCED_ACCURACY_SAFETY_PROTOCOL.md +172 -0
  20. package/docs/accuracy-enhancement-artifacts/enhanced-validation-config.json +98 -0
  21. package/docs/acig-robustness-guide.md +164 -0
  22. package/docs/authoritative-gate-implementation.md +168 -0
  23. package/docs/cli-strengthening-summary.md +232 -0
  24. package/docs/invariant-system-summary.md +100 -0
  25. package/docs/invariant-system.md +112 -0
  26. package/generate_large_test.js +42 -0
  27. package/large_test_repo.json +1 -0
  28. package/output1.json +2163 -0
  29. package/output2.json +2163 -0
  30. package/package.json +46 -36
  31. package/scan_calcom_report.txt +0 -0
  32. package/scan_leafmint_report.txt +0 -0
  33. package/scan_output.txt +0 -0
  34. package/scan_trigger_report.txt +0 -0
  35. package/schema/arcvision_context_schema_v1.json +136 -1
  36. package/src/arcvision-guard.js +433 -0
  37. package/src/core/authority-core-detector.js +382 -0
  38. package/src/core/authority-ledger.js +300 -0
  39. package/src/core/blastRadius.js +299 -0
  40. package/src/core/call-resolver.js +196 -0
  41. package/src/core/change-evaluator.js +509 -0
  42. package/src/core/change-evaluator.js.backup +424 -0
  43. package/src/core/change-evaluator.ts +285 -0
  44. package/src/core/chunked-uploader.js +180 -0
  45. package/src/core/circular-dependency-detector.js +404 -0
  46. package/src/core/cli-error-handler.js +458 -0
  47. package/src/core/cli-validator.js +458 -0
  48. package/src/core/compression.js +64 -0
  49. package/src/core/context_builder.js +741 -0
  50. package/src/core/dependency-manager.js +134 -0
  51. package/src/core/di-detector.js +202 -0
  52. package/src/core/diff-analyzer.js +76 -0
  53. package/src/core/example-invariants.js +135 -0
  54. package/src/core/failure-mode-synthesizer.js +341 -0
  55. package/src/core/invariant-analyzer.js +294 -0
  56. package/src/core/invariant-detector.js +548 -0
  57. package/src/core/invariant-enforcer.js +171 -0
  58. package/src/core/invariant-evaluation-utils.js +172 -0
  59. package/src/core/invariant-hooks.js +152 -0
  60. package/src/core/invariant-integration-example.js +186 -0
  61. package/src/core/invariant-registry.js +298 -0
  62. package/src/core/invariant-registry.ts +100 -0
  63. package/src/core/invariant-types.js +66 -0
  64. package/src/core/invariants-index.js +88 -0
  65. package/src/core/method-tracker.js +170 -0
  66. package/src/core/override-handler.js +304 -0
  67. package/src/core/ownership-resolver.js +227 -0
  68. package/src/core/parser-enhanced.js +80 -0
  69. package/src/core/parser.js +610 -0
  70. package/src/core/path-resolver.js +240 -0
  71. package/src/core/pattern-matcher.js +246 -0
  72. package/src/core/progress-tracker.js +71 -0
  73. package/src/core/react-nextjs-detector.js +245 -0
  74. package/src/core/readme-generator.js +167 -0
  75. package/src/core/retry-handler.js +57 -0
  76. package/src/core/scanner.js +289 -0
  77. package/src/core/semantic-analyzer.js +204 -0
  78. package/src/core/structural-context-owner.js +442 -0
  79. package/src/core/symbol-indexer.js +164 -0
  80. package/src/core/tsconfig-utils.js +73 -0
  81. package/src/core/type-analyzer.js +272 -0
  82. package/src/core/watcher.js +18 -0
  83. package/src/core/workspace-scanner.js +88 -0
  84. package/src/engine/context_builder.js +280 -0
  85. package/src/engine/context_sorter.js +59 -0
  86. package/src/engine/context_validator.js +200 -0
  87. package/src/engine/id-generator.js +16 -0
  88. package/src/engine/pass1_facts.js +260 -0
  89. package/src/engine/pass2_semantics.js +333 -0
  90. package/src/engine/pass3_lifter.js +99 -0
  91. package/src/engine/pass4_signals.js +201 -0
  92. package/src/index.js +830 -0
  93. package/src/plugins/express-plugin.js +48 -0
  94. package/src/plugins/plugin-manager.js +58 -0
  95. package/src/plugins/react-plugin.js +54 -0
  96. package/temp_original.js +0 -0
  97. package/test/determinism-test.js +83 -0
  98. package/test-authoritative-context.js +53 -0
  99. package/test-real-authoritative-context.js +118 -0
  100. package/test-upload-enhancements.js +111 -0
  101. package/test_repos/allowed-clean-architecture/.arcvision/invariants.json +57 -0
  102. package/test_repos/allowed-clean-architecture/adapters/controllers/UserController.js +95 -0
  103. package/test_repos/allowed-clean-architecture/adapters/http/HttpServer.js +78 -0
  104. package/test_repos/allowed-clean-architecture/application/dtos/CreateUserRequest.js +37 -0
  105. package/test_repos/allowed-clean-architecture/application/services/UserService.js +61 -0
  106. package/test_repos/allowed-clean-architecture/arcvision_context/README.md +93 -0
  107. package/test_repos/allowed-clean-architecture/arcvision_context/arcvision.context.json +2796 -0
  108. package/test_repos/allowed-clean-architecture/domain/interfaces/UserRepository.js +25 -0
  109. package/test_repos/allowed-clean-architecture/domain/models/User.js +39 -0
  110. package/test_repos/allowed-clean-architecture/index.js +45 -0
  111. package/test_repos/allowed-clean-architecture/infrastructure/database/DatabaseConnection.js +56 -0
  112. package/test_repos/allowed-clean-architecture/infrastructure/repositories/InMemoryUserRepository.js +61 -0
  113. package/test_repos/allowed-clean-architecture/package.json +15 -0
  114. package/test_repos/blocked-legacy-monolith/.arcvision/invariants.json +78 -0
  115. package/test_repos/blocked-legacy-monolith/arcvision_context/README.md +93 -0
  116. package/test_repos/blocked-legacy-monolith/arcvision_context/arcvision.context.json +2882 -0
  117. package/test_repos/blocked-legacy-monolith/database/dbConnection.js +35 -0
  118. package/test_repos/blocked-legacy-monolith/index.js +38 -0
  119. package/test_repos/blocked-legacy-monolith/modules/emailService.js +31 -0
  120. package/test_repos/blocked-legacy-monolith/modules/paymentProcessor.js +37 -0
  121. package/test_repos/blocked-legacy-monolith/package.json +15 -0
  122. package/test_repos/blocked-legacy-monolith/shared/utils.js +19 -0
  123. package/test_repos/blocked-legacy-monolith/utils/helpers.js +23 -0
  124. package/test_repos/risky-microservices-concerns/.arcvision/invariants.json +69 -0
  125. package/test_repos/risky-microservices-concerns/arcvision_context/README.md +93 -0
  126. package/test_repos/risky-microservices-concerns/arcvision_context/arcvision.context.json +3070 -0
  127. package/test_repos/risky-microservices-concerns/common/utils.js +77 -0
  128. package/test_repos/risky-microservices-concerns/gateways/apiGateway.js +84 -0
  129. package/test_repos/risky-microservices-concerns/index.js +20 -0
  130. package/test_repos/risky-microservices-concerns/libs/deprecatedHelper.js +36 -0
  131. package/test_repos/risky-microservices-concerns/package.json +15 -0
  132. package/test_repos/risky-microservices-concerns/services/orderService.js +42 -0
  133. package/test_repos/risky-microservices-concerns/services/userService.js +48 -0
  134. package/verify_engine.js +116 -0
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Blast Radius Analysis
3
+ *
4
+ * This module computes the blast radius for each file in the architecture map.
5
+ * Blast radius is defined as the number of files that import a given file.
6
+ * It also includes transitive dependencies analysis for more comprehensive impact assessment.
7
+ */
8
+
9
+ /**
10
+ * Builds a reverse dependency graph from the architecture map
11
+ * @param {Object} architectureMap - The architecture map with nodes and edges
12
+ * @returns {Object} Reverse dependency map where key is imported file and value is array of importing files
13
+ */
14
+ function buildReverseDependencyGraph(architectureMap) {
15
+ const reverseGraph = {};
16
+
17
+ // Initialize all files in the reverse graph
18
+ if (architectureMap.nodes && Array.isArray(architectureMap.nodes)) {
19
+ architectureMap.nodes.forEach(node => {
20
+ const filePath = node.id;
21
+ if (filePath && !reverseGraph[filePath]) {
22
+ reverseGraph[filePath] = [];
23
+ }
24
+ });
25
+ }
26
+
27
+ // Build reverse dependencies from edges
28
+ if (architectureMap.edges && Array.isArray(architectureMap.edges)) {
29
+ architectureMap.edges.forEach(edge => {
30
+ if (edge.source && edge.target) {
31
+ const importingFile = edge.source; // file that imports
32
+ const importedFile = edge.target; // file that is imported
33
+
34
+ if (!reverseGraph[importedFile]) {
35
+ reverseGraph[importedFile] = [];
36
+ }
37
+
38
+ // Add the importing file to the list of files that depend on importedFile
39
+ if (!reverseGraph[importedFile].includes(importingFile)) {
40
+ reverseGraph[importedFile].push(importingFile);
41
+ }
42
+ }
43
+ });
44
+ }
45
+
46
+ return reverseGraph;
47
+ }
48
+
49
+ /**
50
+ * Computes blast radius for each file
51
+ * @param {Object} reverseGraph - The reverse dependency graph
52
+ * @returns {Object} Map of file paths to their blast radius (number of files that import them)
53
+ */
54
+ function computeBlastRadius(reverseGraph) {
55
+ const blastRadiusMap = {};
56
+
57
+ for (const [filePath, importingFiles] of Object.entries(reverseGraph)) {
58
+ blastRadiusMap[filePath] = Array.isArray(importingFiles) ? importingFiles.length : 0;
59
+ }
60
+
61
+ return blastRadiusMap;
62
+ }
63
+
64
+ /**
65
+ * Computes transitive blast radius (indirect dependencies)
66
+ * @param {Object} reverseGraph - The reverse dependency graph
67
+ * @param {Object} directBlastRadius - Direct blast radius map
68
+ * @returns {Object} Map of file paths to their total blast radius (direct + transitive)
69
+ */
70
+ function computeTransitiveBlastRadius(reverseGraph, directBlastRadius) {
71
+ const transitiveBlastRadius = {};
72
+
73
+ for (const [filePath] of Object.entries(reverseGraph)) {
74
+ // Calculate transitive dependencies using a breadth-first search
75
+ const visited = new Set();
76
+ const queue = [...(reverseGraph[filePath] || [])];
77
+ let transitiveCount = 0;
78
+
79
+ while (queue.length > 0) {
80
+ const currentFile = queue.shift();
81
+
82
+ if (!visited.has(currentFile)) {
83
+ visited.add(currentFile);
84
+ transitiveCount++;
85
+
86
+ // Add dependencies of this file to the queue
87
+ if (reverseGraph[currentFile] && Array.isArray(reverseGraph[currentFile])) {
88
+ queue.push(...reverseGraph[currentFile]);
89
+ }
90
+ }
91
+ }
92
+
93
+ // Total blast radius = direct + transitive
94
+ transitiveBlastRadius[filePath] = (directBlastRadius[filePath] || 0) + transitiveCount;
95
+ }
96
+
97
+ return transitiveBlastRadius;
98
+ }
99
+
100
+ /**
101
+ * Finds the file with the highest blast radius
102
+ * @param {Object} blastRadiusMap - Map of file paths to their blast radius
103
+ * @returns {Object|null} Object containing the file path and blast radius, or null if no files
104
+ */
105
+ function findHighestBlastRadius(blastRadiusMap) {
106
+ if (Object.keys(blastRadiusMap).length === 0) {
107
+ return null;
108
+ }
109
+
110
+ let maxFile = null;
111
+ let maxRadius = -1;
112
+
113
+ for (const [filePath, radius] of Object.entries(blastRadiusMap)) {
114
+ if (radius > maxRadius) {
115
+ maxRadius = radius;
116
+ maxFile = { file: filePath, blast_radius: radius };
117
+ }
118
+ }
119
+
120
+ return maxFile;
121
+ }
122
+
123
+ /**
124
+ * Gets the top N files by blast radius
125
+ * @param {Object} blastRadiusMap - Map of file paths to their blast radius
126
+ * @param {number} limit - Number of top files to return (default: 3)
127
+ * @returns {Array} Array of top N files with their blast radius
128
+ */
129
+ function getTopBlastRadiusFiles(blastRadiusMap, limit = 3) {
130
+ if (Object.keys(blastRadiusMap).length === 0) {
131
+ return [];
132
+ }
133
+
134
+ // Convert to array and sort by blast radius descending
135
+ const sortedFiles = Object.entries(blastRadiusMap)
136
+ .map(([file, blast_radius]) => ({ file, blast_radius }))
137
+ .sort((a, b) => {
138
+ // Sort by blast radius first, then by filename for consistency
139
+ if (b.blast_radius !== a.blast_radius) {
140
+ return b.blast_radius - a.blast_radius;
141
+ }
142
+ return a.file.localeCompare(b.file);
143
+ });
144
+
145
+ return sortedFiles.slice(0, limit);
146
+ }
147
+
148
+ /**
149
+ * Computes blast radius results with percentage of total files
150
+ * @param {Object} blastRadiusMap - Map of file paths to their blast radius
151
+ * @param {number} totalFiles - Total number of files in the project
152
+ * @returns {Array} Array of files with blast radius and percentage
153
+ */
154
+ function computeBlastRadiusWithPercentage(blastRadiusMap, totalFiles) {
155
+ if (Object.keys(blastRadiusMap).length === 0 || totalFiles === 0) {
156
+ return [];
157
+ }
158
+
159
+ return Object.entries(blastRadiusMap)
160
+ .map(([file, blastRadius]) => {
161
+ const percentOfGraph = Number(((blastRadius / totalFiles) * 100).toFixed(2));
162
+ return {
163
+ file,
164
+ blastRadius,
165
+ percentOfGraph
166
+ };
167
+ })
168
+ .sort((a, b) => {
169
+ // Sort by blast radius first, then by filename for consistency
170
+ if (b.blastRadius !== a.blastRadius) {
171
+ return b.blastRadius - a.blastRadius;
172
+ }
173
+ return a.file.localeCompare(b.file);
174
+ });
175
+ }
176
+
177
+ /**
178
+ * Analyzes the criticality of files based on multiple factors
179
+ * @param {Object} blastRadiusMap - Direct blast radius map
180
+ * @param {Object} reverseGraph - The reverse dependency graph
181
+ * @param {Array} nodes - The list of nodes in the architecture map
182
+ * @returns {Array} Array of files with criticality scores
183
+ */
184
+ function analyzeCriticality(blastRadiusMap, reverseGraph, nodes) {
185
+ const criticalityAnalysis = [];
186
+
187
+ for (const [file, blastRadius] of Object.entries(blastRadiusMap)) {
188
+ const node = nodes.find(n => n.id === file);
189
+
190
+ // Calculate various metrics for criticality
191
+ const dependencies = reverseGraph[file] ? reverseGraph[file].length : 0; // How many files depend on this file
192
+ const dependencyStrength = calculateDependencyStrength(reverseGraph, file);
193
+
194
+ // Determine criticality score based on multiple factors
195
+ let criticalityScore = blastRadius; // Start with direct blast radius
196
+
197
+ // Add weight for dependency strength
198
+ criticalityScore += dependencyStrength * 0.5;
199
+
200
+ // Add weight if the file has special characteristics (has functions, exports, etc.)
201
+ if (node && node.metadata) {
202
+ if (node.metadata.functions && node.metadata.functions.length > 0) {
203
+ criticalityScore += 1; // Files with functions are more critical
204
+ }
205
+ if (node.metadata.classes && node.metadata.classes.length > 0) {
206
+ criticalityScore += 1.5; // Classes tend to be more central
207
+ }
208
+ if (node.metadata.types && node.metadata.types.length > 0) {
209
+ criticalityScore += 0.5; // Types are foundational
210
+ }
211
+ if (node.metadata.exports && node.metadata.exports.length > 0) {
212
+ criticalityScore += 0.7; // Exporting files are more critical
213
+ }
214
+ if (node.metadata.apiCalls && node.metadata.apiCalls.length > 0) {
215
+ criticalityScore += 1.2; // API calls connect to external systems
216
+ }
217
+ }
218
+
219
+ // Add weight based on file path patterns (e.g., auth, core, utils)
220
+ if (file.toLowerCase().includes('auth')) {
221
+ criticalityScore += 2;
222
+ } else if (file.toLowerCase().includes('core') || file.toLowerCase().includes('util') || file.toLowerCase().includes('utils')) {
223
+ criticalityScore += 1.5;
224
+ } else if (file.toLowerCase().includes('config') || file.toLowerCase().includes('index')) {
225
+ criticalityScore += 0.8;
226
+ }
227
+
228
+ // Boost score for files with higher blast radius
229
+ if (blastRadius > 10) {
230
+ criticalityScore *= 1.8; // Very high impact files get more boost
231
+ } else if (blastRadius > 5) {
232
+ criticalityScore *= 1.5; // Higher impact files get a boost
233
+ } else if (blastRadius > 0) {
234
+ criticalityScore *= 1.1; // Any connected file gets slight boost
235
+ }
236
+
237
+ // Additional factors for criticality
238
+ // Files with many outgoing dependencies are also critical
239
+ if (node && node.dependencies && node.dependencies.length > 5) {
240
+ criticalityScore += 2; // Highly coupled files
241
+ } else if (node && node.dependencies && node.dependencies.length > 10) {
242
+ criticalityScore += 5; // Extremely coupled files
243
+ }
244
+
245
+ // Files that appear in multiple contexts (e.g. shared folders)
246
+ if (file.includes('/shared/') || file.includes('/common/') || file.includes('/utils/') || file.includes('/lib/')) {
247
+ criticalityScore += 1.5; // Shared components are typically more critical
248
+ }
249
+
250
+ criticalityAnalysis.push({
251
+ file,
252
+ blastRadius,
253
+ dependencies,
254
+ dependencyStrength,
255
+ criticalityScore: Number(criticalityScore.toFixed(2)),
256
+ isHub: blastRadius > 5 // Consider files with blast radius > 5 as hubs
257
+ });
258
+ }
259
+
260
+ // Sort by criticality score descending, then by file name ascending
261
+ return criticalityAnalysis.sort((a, b) => {
262
+ if (b.criticalityScore !== a.criticalityScore) {
263
+ return b.criticalityScore - a.criticalityScore;
264
+ }
265
+ return a.file.localeCompare(b.file);
266
+ });
267
+ }
268
+
269
+ /**
270
+ * Calculates the strength of dependencies for a file (how many dependencies it has)
271
+ * @param {Object} reverseGraph - The reverse dependency graph
272
+ * @param {string} file - The file to analyze
273
+ * @returns {number} The dependency strength score
274
+ */
275
+ function calculateDependencyStrength(reverseGraph, file) {
276
+ if (!reverseGraph[file] || !Array.isArray(reverseGraph[file])) {
277
+ return 0;
278
+ }
279
+
280
+ // Calculate the cumulative strength of dependencies
281
+ let strength = 0;
282
+ for (const dep of reverseGraph[file]) {
283
+ if (reverseGraph[dep]) {
284
+ strength += reverseGraph[dep].length; // How many files the dependency affects
285
+ }
286
+ }
287
+
288
+ return strength;
289
+ }
290
+
291
+ module.exports = {
292
+ buildReverseDependencyGraph,
293
+ computeBlastRadius,
294
+ computeTransitiveBlastRadius,
295
+ findHighestBlastRadius,
296
+ getTopBlastRadiusFiles,
297
+ computeBlastRadiusWithPercentage,
298
+ analyzeCriticality
299
+ };
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Resolves CallExpressions and NewExpressions to symbols using the import graph.
3
+ * Emits typed edges: CALLS, INSTANTIATES
4
+ */
5
+ class CallResolver {
6
+ constructor(symbolIndex, fileMap) {
7
+ this.symbolIndex = symbolIndex; // Map<symbolId, Symbol>
8
+ this.fileMap = fileMap; // Map<fileId, FileObject> (to access imports)
9
+ }
10
+
11
+ /**
12
+ * Resolve calls in all files
13
+ * @returns {Array<Object>} List of edges
14
+ */
15
+ resolve(rawFiles) {
16
+ const edges = [];
17
+ console.log(`Resolving calls for ${rawFiles.length} files...`);
18
+
19
+ for (const file of rawFiles) {
20
+ const fileEdges = this._resolveFile(file);
21
+ edges.push(...fileEdges);
22
+ }
23
+
24
+ console.log(`Resolved ${edges.length} call/instantiate edges.`);
25
+ return edges;
26
+ }
27
+
28
+ _resolveFile(file) {
29
+ const edges = [];
30
+ if (!file.methodCalls) return edges; // generated by method-tracker.js
31
+
32
+ const fileId = file.id;
33
+ const { functionCalls, constructorCalls, methodCalls } = file.methodCalls;
34
+ const imports = file.metadata.imports || [];
35
+
36
+ // Helper to find imported symbol source
37
+ const resolveImportedSymbol = (symbolName) => {
38
+ // Check imports
39
+ for (const imp of imports) {
40
+ // imp.specifiers: [{local, imported}]
41
+ if (imp.specifiers) {
42
+ const spec = imp.specifiers.find(s => s.local === symbolName);
43
+ if (spec) {
44
+ // Found import. Now we need the resolved file path of this import.
45
+ // This requires the 'resolvedPath' to be present on the import object.
46
+ // This implies PathResolver must run BEFORE CallResolver and annotate imports.
47
+ if (imp.resolvedPath) {
48
+ const targetFileId = imp.resolvedPath;
49
+ const targetSymbolName = spec.imported === 'default' ? 'default' : spec.imported; // Or default export name?
50
+
51
+ // Construct potential symbol ID
52
+ // Note: If target is a file, we look for 'targetFileId::targetSymbolName'
53
+ // Special handling for default exports might be needed if they are named differently in target.
54
+ // For now assuming named exports match or 'default'.
55
+
56
+ // Retry finding the exact symbol ID in the target file
57
+ // This usually requires a lookup in fileToSymbols or just constructing ID if deterministic
58
+
59
+ // Attempt 1: Named export
60
+ let candidateId = `${targetFileId}::${targetSymbolName}`;
61
+ if (this.symbolIndex.has(candidateId)) return candidateId;
62
+
63
+ // Attempt 2: If default import, we need to find what is exported as default
64
+ if (spec.imported === 'default') {
65
+ // Find the symbol in target file that has type 'default' or name 'default'
66
+ // The SymbolIndexer indexes 'default' if it's named 'default' or if we handled it.
67
+ candidateId = `${targetFileId}::default`;
68
+ if (this.symbolIndex.has(candidateId)) return candidateId;
69
+
70
+ // fallback: look for ANY default export in target file via SymbolIndex iteration (expensive but necessary?)
71
+ // Better: SymbolIndexer ensure 'default' is the name for default exports.
72
+ }
73
+
74
+ // Additional attempts for common patterns
75
+ // Attempt 3: Try with common function prefixes
76
+ const commonPatterns = [
77
+ `${targetFileId}::use${targetSymbolName.charAt(0).toUpperCase() + targetSymbolName.slice(1)}`,
78
+ `${targetFileId}::get${targetSymbolName.charAt(0).toUpperCase() + targetSymbolName.slice(1)}`,
79
+ `${targetFileId}::create${targetSymbolName.charAt(0).toUpperCase() + targetSymbolName.slice(1)}`,
80
+ `${targetFileId}::_${targetSymbolName}`, // underscore prefix
81
+ `${targetFileId}::${targetSymbolName}Impl`, // implementation suffix
82
+ `${targetFileId}::${targetSymbolName}Handler` // handler suffix
83
+ ];
84
+
85
+ for (const patternId of commonPatterns) {
86
+ if (this.symbolIndex.has(patternId)) return patternId;
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+
93
+ // If not imported, it might be local
94
+ const localId = `${fileId}::${symbolName}`;
95
+ if (this.symbolIndex.has(localId)) {
96
+ return localId;
97
+ }
98
+
99
+ // Additional local patterns
100
+ const localPatterns = [
101
+ `${fileId}::use${symbolName.charAt(0).toUpperCase() + symbolName.slice(1)}`,
102
+ `${fileId}::get${symbolName.charAt(0).toUpperCase() + symbolName.slice(1)}`,
103
+ `${fileId}::create${symbolName.charAt(0).toUpperCase() + symbolName.slice(1)}`,
104
+ `${fileId}::_${symbolName}`, // underscore prefix
105
+ `${fileId}::${symbolName}Impl`, // implementation suffix
106
+ `${fileId}::${symbolName}Handler` // handler suffix
107
+ ];
108
+
109
+ for (const patternId of localPatterns) {
110
+ if (this.symbolIndex.has(patternId)) return patternId;
111
+ }
112
+
113
+ return null;
114
+ };
115
+
116
+ // 1. Resolve Function Calls
117
+ if (functionCalls) {
118
+ for (const call of functionCalls) {
119
+ const targetSymbolId = resolveImportedSymbol(call.name);
120
+ if (targetSymbolId) {
121
+ edges.push({
122
+ sourceFileId: fileId,
123
+ targetSymbolId: targetSymbolId,
124
+ type: 'CALLS',
125
+ line: call.loc ? call.loc.start.line : 0,
126
+ confidence: 1.0
127
+ });
128
+ }
129
+ }
130
+ }
131
+
132
+ // 2. Resolve Constructor Calls
133
+ if (constructorCalls) {
134
+ for (const call of constructorCalls) {
135
+ const targetSymbolId = resolveImportedSymbol(call.className);
136
+ if (targetSymbolId) {
137
+ edges.push({
138
+ sourceFileId: fileId,
139
+ targetSymbolId: targetSymbolId,
140
+ type: 'INSTANTIATES',
141
+ line: call.loc ? call.loc.start.line : 0,
142
+ confidence: 1.0
143
+ });
144
+ }
145
+ }
146
+ }
147
+
148
+ // 3. Resolve Method Calls (Namespace & Object Property Resolution)
149
+ // e.g., import * as api from './api'; api.fetchData();
150
+ // OR import { actions } from './actions'; actions.login();
151
+ if (methodCalls) {
152
+ for (const call of methodCalls) {
153
+ const imp = imports.find(i => i.specifiers?.some(s => s.local === call.object));
154
+ if (imp && imp.resolvedPath) {
155
+ const targetFileId = imp.resolvedPath;
156
+ const spec = imp.specifiers.find(s => s.local === call.object);
157
+ const importedAs = spec ? spec.imported : null;
158
+
159
+ // Strategy A: Namespace Resolution
160
+ let candidateId = `${targetFileId}::${call.method}`;
161
+ if (this.symbolIndex.has(candidateId)) {
162
+ edges.push({
163
+ sourceFileId: fileId, targetSymbolId: candidateId,
164
+ type: 'CALLS', line: call.loc ? call.loc.start.line : 0, confidence: 1.0
165
+ });
166
+ continue;
167
+ }
168
+
169
+ // Strategy B: Object Property Resolution
170
+ if (importedAs) {
171
+ const targetSymbolName = importedAs === 'default' ? 'default' : importedAs;
172
+ candidateId = `${targetFileId}::${targetSymbolName}.${call.method}`;
173
+ if (this.symbolIndex.has(candidateId)) {
174
+ edges.push({
175
+ sourceFileId: fileId, targetSymbolId: candidateId,
176
+ type: 'CALLS', line: call.loc ? call.loc.start.line : 0, confidence: 1.0
177
+ });
178
+ continue;
179
+ }
180
+ }
181
+ }
182
+ const localObjectId = `${fileId}::${call.object}.${call.method}`;
183
+ if (this.symbolIndex.has(localObjectId)) {
184
+ edges.push({
185
+ sourceFileId: fileId, targetSymbolId: localObjectId,
186
+ type: 'CALLS', line: call.loc ? call.loc.start.line : 0, confidence: 1.0
187
+ });
188
+ }
189
+ }
190
+ }
191
+
192
+ return edges;
193
+ }
194
+ }
195
+
196
+ module.exports = { CallResolver };