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,180 @@
1
+ const chalk = require('chalk');
2
+ const { Buffer } = require('buffer');
3
+
4
+ class ChunkedUploader {
5
+ constructor(chunkSize = 1024 * 1024 * 4) { // 4MB default chunk size
6
+ this.chunkSize = chunkSize;
7
+ }
8
+
9
+ /**
10
+ * Calculate approximate byte size of a JSON object
11
+ */
12
+ calculateByteSize(obj) {
13
+ return Buffer.byteLength(JSON.stringify(obj));
14
+ }
15
+
16
+ /**
17
+ * Split a large JSON object into chunks
18
+ */
19
+ splitIntoChunks(data, maxChunkSize = this.chunkSize) {
20
+ const jsonString = JSON.stringify(data);
21
+ const chunks = [];
22
+
23
+ for (let i = 0; i < jsonString.length; i += maxChunkSize) {
24
+ const chunk = jsonString.slice(i, i + maxChunkSize);
25
+ chunks.push({
26
+ data: chunk,
27
+ index: Math.floor(i / maxChunkSize),
28
+ totalChunks: Math.ceil(jsonString.length / maxChunkSize)
29
+ });
30
+ }
31
+
32
+ return chunks;
33
+ }
34
+
35
+ /**
36
+ * Reconstruct data from chunks
37
+ */
38
+ reconstructFromChunks(chunks) {
39
+ // Sort chunks by index to ensure correct order
40
+ chunks.sort((a, b) => a.index - b.index);
41
+ const reconstructedString = chunks.map(chunk => chunk.data).join('');
42
+ return JSON.parse(reconstructedString);
43
+ }
44
+
45
+ /**
46
+ * Upload data in chunks to the server
47
+ */
48
+ async uploadInChunks(data, token, apiUrl) {
49
+ const totalSize = this.calculateByteSize(data);
50
+ const chunks = this.splitIntoChunks(data);
51
+
52
+ console.log(chalk.blue(`Preparing to upload ${chunks.length} chunks (${Math.round(totalSize / 1024 / 1024)} MB total)`));
53
+
54
+ // First, initiate the chunked upload session
55
+ const sessionId = await this.initiateSession(chunks.length, token, apiUrl);
56
+
57
+ if (!sessionId) {
58
+ throw new Error('Failed to initiate upload session');
59
+ }
60
+
61
+ // Upload each chunk individually
62
+ const uploadPromises = chunks.map(async (chunk, index) => {
63
+ const progress = ((index + 1) / chunks.length) * 100;
64
+ console.log(chalk.yellow(`Uploading chunk ${index + 1}/${chunks.length} (${Math.round(progress)}%)`));
65
+
66
+ return this.uploadChunk(chunk, sessionId, token, apiUrl);
67
+ });
68
+
69
+ // Wait for all chunks to upload
70
+ const results = await Promise.all(uploadPromises);
71
+
72
+ // Verify all chunks were uploaded successfully
73
+ const allSuccessful = results.every(result => result.success);
74
+
75
+ if (!allSuccessful) {
76
+ throw new Error('Some chunks failed to upload');
77
+ }
78
+
79
+ // Finalize the upload
80
+ const finalResult = await this.finalizeUpload(sessionId, token, apiUrl);
81
+
82
+ return finalResult;
83
+ }
84
+
85
+ /**
86
+ * Initiate a chunked upload session
87
+ */
88
+ async initiateSession(totalChunks, token, apiUrl) {
89
+ try {
90
+ const response = await fetch(`${apiUrl}/api/upload/initiate`, {
91
+ method: 'POST',
92
+ headers: {
93
+ 'Content-Type': 'application/json',
94
+ 'Authorization': `Bearer ${token}`
95
+ },
96
+ body: JSON.stringify({
97
+ totalChunks,
98
+ timestamp: Date.now()
99
+ })
100
+ });
101
+
102
+ if (!response.ok) {
103
+ throw new Error(`Session initiation failed: ${response.status}`);
104
+ }
105
+
106
+ const result = await response.json();
107
+ return result.sessionId;
108
+ } catch (error) {
109
+ console.error(chalk.red(`Failed to initiate upload session: ${error.message}`));
110
+ return null;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Upload a single chunk
116
+ */
117
+ async uploadChunk(chunk, sessionId, token, apiUrl) {
118
+ try {
119
+ const response = await fetch(`${apiUrl}/api/upload/chunk`, {
120
+ method: 'POST',
121
+ headers: {
122
+ 'Content-Type': 'application/json',
123
+ 'Authorization': `Bearer ${token}`
124
+ },
125
+ body: JSON.stringify({
126
+ sessionId,
127
+ chunkIndex: chunk.index,
128
+ chunkData: chunk.data,
129
+ totalChunks: chunk.totalChunks
130
+ })
131
+ });
132
+
133
+ const result = await response.json();
134
+
135
+ if (!response.ok) {
136
+ console.error(chalk.red(`Chunk ${chunk.index} upload failed: ${response.status}`));
137
+ return { success: false, error: result.error || response.statusText };
138
+ }
139
+
140
+ return { success: true, chunkIndex: chunk.index };
141
+ } catch (error) {
142
+ console.error(chalk.red(`Chunk ${chunk.index} upload error: ${error.message}`));
143
+ return { success: false, error: error.message };
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Finalize the chunked upload
149
+ */
150
+ async finalizeUpload(sessionId, token, apiUrl) {
151
+ try {
152
+ console.log(chalk.yellow('Finalizing upload...'));
153
+
154
+ const response = await fetch(`${apiUrl}/api/upload/finalize`, {
155
+ method: 'POST',
156
+ headers: {
157
+ 'Content-Type': 'application/json',
158
+ 'Authorization': `Bearer ${token}`
159
+ },
160
+ body: JSON.stringify({
161
+ sessionId
162
+ })
163
+ });
164
+
165
+ if (!response.ok) {
166
+ throw new Error(`Finalization failed: ${response.status}`);
167
+ }
168
+
169
+ const result = await response.json();
170
+ return result;
171
+ } catch (error) {
172
+ console.error(chalk.red(`Upload finalization failed: ${error.message}`));
173
+ throw error;
174
+ }
175
+ }
176
+ }
177
+
178
+ module.exports = {
179
+ ChunkedUploader
180
+ };
@@ -0,0 +1,404 @@
1
+ /**
2
+ * Enhanced Circular Dependency Detector
3
+ * Detects bidirectional dependencies between modules
4
+ * Enhanced to catch all circular dependency patterns including transitive cycles
5
+ */
6
+
7
+ const { patternMatcher } = require('./pattern-matcher');
8
+
9
+ class CircularDependencyDetector {
10
+ constructor() {
11
+ this.detectedCycles = [];
12
+ }
13
+
14
+ /**
15
+ * Detect circular dependencies in the dependency graph
16
+ * Enhanced to detect all types of cycles including transitive dependencies
17
+ * @param {Array} nodes - Array of node objects
18
+ * @param {Array} edges - Array of edge objects
19
+ * @returns {Array} Array of detected circular dependencies
20
+ */
21
+ detectCircularDependencies(nodes, edges) {
22
+ const cycles = [];
23
+ const visited = new Set();
24
+ const recStack = new Set();
25
+ const adjList = new Map();
26
+
27
+ // Enhanced data structures for better cycle detection
28
+ const nodePaths = new Map();
29
+ const edgeWeights = new Map();
30
+
31
+ // Build adjacency list with enhanced metadata
32
+ for (const edge of edges) {
33
+ // Handle different edge structures (could be {from, to} or {source, target})
34
+ const fromNode = edge.from || edge.source;
35
+ const toNode = edge.to || edge.target;
36
+
37
+ if (!adjList.has(fromNode)) {
38
+ adjList.set(fromNode, []);
39
+ }
40
+ adjList.get(fromNode).push(toNode);
41
+
42
+ // Store edge weights for importance scoring
43
+ const edgeKey = `${fromNode}-${toNode}`;
44
+ edgeWeights.set(edgeKey, edge.weight || 1);
45
+ }
46
+
47
+ // Map node IDs to file paths
48
+ nodes.forEach(node => {
49
+ // Handle different node structures (could be {id, path} or other formats)
50
+ const nodeId = node.id || node.name || node.filePath;
51
+ const nodePath = node.path || node.filePath || node.name;
52
+ if (nodeId && nodePath) {
53
+ nodePaths.set(nodeId, nodePath);
54
+ }
55
+ });
56
+
57
+ // Enhanced DFS to detect cycles with comprehensive tracking
58
+ const dfs = (node, path) => {
59
+ if (recStack.has(node)) {
60
+ // Found cycle - get the cycle portion
61
+ const cycleStartIndex = path.indexOf(node);
62
+ if (cycleStartIndex !== -1) {
63
+ const cycleNodes = path.slice(cycleStartIndex);
64
+ const cycleEdges = [];
65
+
66
+ // Calculate cycle metrics
67
+ let totalWeight = 0;
68
+ let maxLength = 0;
69
+
70
+ for (let i = 0; i < cycleNodes.length; i++) {
71
+ const from = cycleNodes[i];
72
+ const to = cycleNodes[(i + 1) % cycleNodes.length];
73
+ const edgeKey = `${from}-${to}`;
74
+
75
+ cycleEdges.push({ from, to });
76
+ totalWeight += edgeWeights.get(edgeKey) || 1;
77
+ maxLength = Math.max(maxLength, nodePaths.get(from)?.length || 0);
78
+ }
79
+
80
+ cycles.push({
81
+ cycle: [...cycleNodes, node],
82
+ edges: cycleEdges,
83
+ length: cycleNodes.length + 1,
84
+ totalWeight,
85
+ maxLength,
86
+ severity: this.calculateCycleSeverity(totalWeight, cycleNodes.length)
87
+ });
88
+ }
89
+ return;
90
+ }
91
+
92
+ if (visited.has(node)) return;
93
+
94
+ visited.add(node);
95
+ recStack.add(node);
96
+ path.push(node);
97
+
98
+ const neighbors = adjList.get(node) || [];
99
+ for (const neighbor of neighbors) {
100
+ dfs(neighbor, [...path]);
101
+ }
102
+
103
+ recStack.delete(node);
104
+ };
105
+
106
+ // Run enhanced DFS from each unvisited node
107
+ for (const node of adjList.keys()) {
108
+ if (!visited.has(node)) {
109
+ dfs(node, []);
110
+ }
111
+ }
112
+
113
+ // Enhanced cycle reporting with detailed information
114
+ return cycles.map(cycle => ({
115
+ ...cycle,
116
+ fileCycle: cycle.cycle.map(nodeId => nodePaths.get(nodeId) || nodeId),
117
+ description: this.formatCycleDescription(cycle, nodePaths),
118
+ impactScore: this.calculateImpactScore(cycle, nodePaths)
119
+ })).sort((a, b) => b.impactScore - a.impactScore); // Sort by impact severity
120
+ }
121
+
122
+ /**
123
+ * Check if a specific forbidden dependency pattern creates a cycle
124
+ * @param {Object} forbiddenDep - Forbidden dependency rule
125
+ * @param {Array} nodes - Array of nodes
126
+ * @param {Array} edges - Array of edges
127
+ * @returns {boolean} True if circular dependency detected
128
+ */
129
+ checkForbiddenCreatesCycle(forbiddenDep, nodes, edges) {
130
+ // First check if the direct forbidden dependency exists
131
+ const directViolation = edges.some(edge => {
132
+ const sourcePath = this.getNodePath(edge.from, nodes);
133
+ const targetPath = this.getNodePath(edge.to, nodes);
134
+
135
+ const fromMatches = patternMatcher.match(sourcePath, forbiddenDep.from, { matchBase: true });
136
+ const toMatches = patternMatcher.match(targetPath, forbiddenDep.to, { matchBase: true });
137
+
138
+ return fromMatches && toMatches;
139
+ });
140
+
141
+ if (!directViolation) {
142
+ return false;
143
+ }
144
+
145
+ // Check if this creates a cycle
146
+ const cycles = this.detectCircularDependencies(nodes, edges);
147
+
148
+ // Check if any cycle involves the forbidden dependency pattern
149
+ return cycles.some(cycle => {
150
+ return cycle.fileCycle.some((filePath, index) => {
151
+ const nextIndex = (index + 1) % cycle.fileCycle.length;
152
+ const nextFilePath = cycle.fileCycle[nextIndex];
153
+
154
+ const fromMatches = patternMatcher.match(filePath, forbiddenDep.from, { matchBase: true });
155
+ const toMatches = patternMatcher.match(nextFilePath, forbiddenDep.to, { matchBase: true });
156
+
157
+ return fromMatches && toMatches;
158
+ });
159
+ });
160
+ }
161
+
162
+ /**
163
+ * Calculate cycle severity based on weight and length
164
+ */
165
+ calculateCycleSeverity(totalWeight, cycleLength) {
166
+ const baseSeverity = totalWeight / cycleLength;
167
+ if (baseSeverity >= 3) return 'CRITICAL';
168
+ if (baseSeverity >= 2) return 'HIGH';
169
+ if (baseSeverity >= 1) return 'MEDIUM';
170
+ return 'LOW';
171
+ }
172
+
173
+ /**
174
+ * Format detailed cycle description
175
+ */
176
+ formatCycleDescription(cycle, nodePaths) {
177
+ const fileCycle = cycle.cycle.map(nodeId => nodePaths.get(nodeId) || nodeId);
178
+ const cycleString = fileCycle.join(' → ');
179
+
180
+ let description = `Circular dependency detected (${cycle.severity}): ${cycleString}`;
181
+
182
+ if (cycle.totalWeight > cycle.length) {
183
+ description += ` [Weight: ${cycle.totalWeight}, Avg: ${(cycle.totalWeight/cycle.length).toFixed(2)}]`;
184
+ }
185
+
186
+ return description;
187
+ }
188
+
189
+ /**
190
+ * Calculate impact score for prioritization
191
+ */
192
+ calculateImpactScore(cycle, nodePaths) {
193
+ // Factors: cycle length, total weight, file path complexity, severity
194
+ const lengthFactor = Math.log(cycle.length + 1);
195
+ const weightFactor = cycle.totalWeight;
196
+ const severityMultiplier = {
197
+ 'CRITICAL': 4,
198
+ 'HIGH': 3,
199
+ 'MEDIUM': 2,
200
+ 'LOW': 1
201
+ }[cycle.severity] || 1;
202
+
203
+ // Path complexity factor (longer paths = higher complexity)
204
+ const pathComplexity = cycle.cycle.reduce((sum, nodeId) => {
205
+ const path = nodePaths.get(nodeId) || '';
206
+ return sum + (path.split('/').length + path.split('\\').length) / 2;
207
+ }, 0) / cycle.length;
208
+
209
+ return (lengthFactor * weightFactor * severityMultiplier * pathComplexity);
210
+ }
211
+
212
+ /**
213
+ * Get file path for a node ID
214
+ */
215
+ getNodePath(nodeId, nodes) {
216
+ const node = nodes.find(n => (n.id === nodeId) || (n.name === nodeId) || (n.filePath === nodeId));
217
+ return node ? (node.path || node.filePath || node.name) : nodeId;
218
+ }
219
+
220
+ /**
221
+ * Check for transitive forbidden dependencies
222
+ */
223
+ checkTransitiveForbiddenDependencies(forbiddenDep, nodes, edges) {
224
+ const violations = [];
225
+
226
+ // Build adjacency list for path finding
227
+ const adjList = new Map();
228
+ for (const edge of edges) {
229
+ if (!adjList.has(edge.from)) {
230
+ adjList.set(edge.from, []);
231
+ }
232
+ adjList.get(edge.from).push(edge.to);
233
+ }
234
+
235
+ // Check if there's a path from forbidden 'from' to forbidden 'to' through intermediate nodes
236
+ const nodePaths = new Map();
237
+ nodes.forEach(node => nodePaths.set(node.id, node.path));
238
+
239
+ // Find all nodes that match the 'from' pattern
240
+ const fromNodes = nodes.filter(node =>
241
+ patternMatcher.match(node.path, forbiddenDep.from, { matchBase: true })
242
+ );
243
+
244
+ // Find all nodes that match the 'to' pattern
245
+ const toNodes = nodes.filter(node =>
246
+ patternMatcher.match(node.path, forbiddenDep.to, { matchBase: true })
247
+ );
248
+
249
+ // Check for transitive paths
250
+ for (const fromNode of fromNodes) {
251
+ for (const toNode of toNodes) {
252
+ if (fromNode.id !== toNode.id) {
253
+ const hasPath = this.hasPath(adjList, fromNode.id, toNode.id);
254
+ if (hasPath) {
255
+ violations.push({
256
+ type: 'TRANSITIVE_FORBIDDEN',
257
+ from: nodePaths.get(fromNode.id),
258
+ to: nodePaths.get(toNode.id),
259
+ description: `Transitive forbidden dependency path: ${nodePaths.get(fromNode.id)} → ... → ${nodePaths.get(toNode.id)}`
260
+ });
261
+ }
262
+ }
263
+ }
264
+ }
265
+
266
+ return violations;
267
+ }
268
+
269
+ /**
270
+ * Check for reverse forbidden dependencies
271
+ */
272
+ checkReverseForbiddenDependencies(forbiddenDep, nodes, edges) {
273
+ const violations = [];
274
+
275
+ // Check if the reverse dependency exists (to → from)
276
+ for (const edge of edges) {
277
+ const sourcePath = this.getNodePath(edge.from, nodes);
278
+ const targetPath = this.getNodePath(edge.to, nodes);
279
+
280
+ const reverseFromMatches = patternMatcher.match(sourcePath, forbiddenDep.to, { matchBase: true });
281
+ const reverseToMatches = patternMatcher.match(targetPath, forbiddenDep.from, { matchBase: true });
282
+
283
+ if (reverseFromMatches && reverseToMatches) {
284
+ violations.push({
285
+ type: 'REVERSE_FORBIDDEN',
286
+ from: sourcePath,
287
+ to: targetPath,
288
+ description: `Reverse forbidden dependency: ${sourcePath} → ${targetPath} (opposite of allowed direction)`
289
+ });
290
+ }
291
+ }
292
+
293
+ return violations;
294
+ }
295
+
296
+ /**
297
+ * Check if there's a path between two nodes
298
+ */
299
+ hasPath(adjList, start, end) {
300
+ if (start === end) return false;
301
+
302
+ const visited = new Set();
303
+ const queue = [start];
304
+
305
+ while (queue.length > 0) {
306
+ const current = queue.shift();
307
+ if (current === end) return true;
308
+
309
+ if (visited.has(current)) continue;
310
+ visited.add(current);
311
+
312
+ const neighbors = adjList.get(current) || [];
313
+ for (const neighbor of neighbors) {
314
+ if (!visited.has(neighbor)) {
315
+ queue.push(neighbor);
316
+ }
317
+ }
318
+ }
319
+
320
+ return false;
321
+ }
322
+
323
+ /**
324
+ * Calculate overall severity from multiple violations
325
+ */
326
+ calculateOverallSeverity(violations) {
327
+ if (violations.length === 0) return 'NONE';
328
+
329
+ const severityMap = {
330
+ 'DIRECT_FORBIDDEN': 4,
331
+ 'CIRCULAR_DEPENDENCY': 3,
332
+ 'TRANSITIVE_FORBIDDEN': 2,
333
+ 'REVERSE_FORBIDDEN': 1
334
+ };
335
+
336
+ const maxSeverity = Math.max(...violations.map(v => severityMap[v.type] || 0));
337
+
338
+ if (maxSeverity >= 4) return 'CRITICAL';
339
+ if (maxSeverity >= 3) return 'HIGH';
340
+ if (maxSeverity >= 2) return 'MEDIUM';
341
+ return 'LOW';
342
+ }
343
+
344
+ /**
345
+ * Enhanced forbidden dependency checker that considers bidirectional relationships
346
+ * and transitive dependencies
347
+ */
348
+ checkEnhancedForbiddenDependency(forbiddenDep, nodes, edges) {
349
+ // Comprehensive check for all types of forbidden dependencies
350
+ const violations = [];
351
+
352
+ // Check 1: Direct forbidden dependency
353
+ const directForbidden = edges.some(edge => {
354
+ const sourcePath = this.getNodePath(edge.from, nodes);
355
+ const targetPath = this.getNodePath(edge.to, nodes);
356
+
357
+ const fromMatches = patternMatcher.match(sourcePath, forbiddenDep.from, { matchBase: true });
358
+ const toMatches = patternMatcher.match(targetPath, forbiddenDep.to, { matchBase: true });
359
+
360
+ if (fromMatches && toMatches) {
361
+ violations.push({
362
+ type: 'DIRECT_FORBIDDEN',
363
+ from: sourcePath,
364
+ to: targetPath,
365
+ description: `Direct forbidden dependency: ${sourcePath} → ${targetPath}`
366
+ });
367
+ return true;
368
+ }
369
+ return false;
370
+ });
371
+
372
+ // Check 2: Circular dependency creation
373
+ const createsCycle = this.checkForbiddenCreatesCycle(forbiddenDep, nodes, edges);
374
+ if (createsCycle) {
375
+ violations.push({
376
+ type: 'CIRCULAR_DEPENDENCY',
377
+ description: `Forbidden dependency creates circular dependency involving: ${forbiddenDep.from} ↔ ${forbiddenDep.to}`
378
+ });
379
+ }
380
+
381
+ // Check 3: Transitive forbidden dependencies
382
+ const transitiveViolations = this.checkTransitiveForbiddenDependencies(forbiddenDep, nodes, edges);
383
+ violations.push(...transitiveViolations);
384
+
385
+ // Check 4: Reverse dependency violations
386
+ const reverseViolations = this.checkReverseForbiddenDependencies(forbiddenDep, nodes, edges);
387
+ violations.push(...reverseViolations);
388
+
389
+ return {
390
+ directForbidden,
391
+ createsCycle,
392
+ transitiveViolations: transitiveViolations.length > 0,
393
+ reverseViolations: reverseViolations.length > 0,
394
+ violated: violations.length > 0,
395
+ violations,
396
+ description: violations.length > 0 ?
397
+ violations.map(v => v.description).join('; ') :
398
+ null,
399
+ severity: this.calculateOverallSeverity(violations)
400
+ };
401
+ }
402
+ }
403
+
404
+ module.exports = { CircularDependencyDetector };