arcvision 0.2.25 → 0.2.26

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.
@@ -21,17 +21,17 @@ class InvariantDetector {
21
21
  */
22
22
  async detectInvariants(scanResult, directory) {
23
23
  this.detectedInvariants = [];
24
-
24
+
25
25
  try {
26
26
  // Analyze the structural context for potential invariants
27
27
  await this.analyzeNodesForInvariants(scanResult.nodes, directory);
28
28
  await this.analyzeEdgesForInvariants(scanResult.edges);
29
29
  await this.analyzeCodeFilesForInvariants(directory);
30
30
  await this.analyzeStructuralPatterns(scanResult.nodes, scanResult.edges, directory);
31
-
31
+
32
32
  console.log(`\nšŸ›”ļø Invariant Detection Complete`);
33
33
  console.log(` Found ${this.detectedInvariants.length} potential system invariants`);
34
-
34
+
35
35
  return this.detectedInvariants;
36
36
  } catch (error) {
37
37
  console.error('Invariant detection failed:', error.message);
@@ -44,20 +44,24 @@ class InvariantDetector {
44
44
  */
45
45
  async analyzeNodesForInvariants(nodes, directory) {
46
46
  for (const node of nodes) {
47
- if (!node.path) continue;
48
-
47
+ const nodePath = node.path || node.id;
48
+ if (!nodePath) continue;
49
+
49
50
  try {
50
- const filePath = path.join(directory, node.path);
51
+ const filePath = path.join(directory, nodePath);
51
52
  if (await this.isFileAccessible(filePath)) {
52
53
  const content = await fs.readFile(filePath, 'utf8');
53
-
54
+
54
55
  // Look for validation/assertion patterns in the file
55
- const fileInvariants = this.extractInvariantsFromFile(content, node.path);
56
-
56
+ const fileInvariants = this.extractInvariantsFromFile(content, nodePath);
57
+
57
58
  for (const invariant of fileInvariants) {
58
- const nodeId = node.path || node.id || 'unknown';
59
+ const nodeId = nodePath || 'unknown';
60
+ // Use position in ID to avoid collisions for multiple instances in same file
61
+ const invariantId = this.generateInvariantId(invariant.pattern, nodeId, invariant.position);
62
+
59
63
  this.detectedInvariants.push({
60
- id: this.generateInvariantId(invariant.pattern, nodeId),
64
+ id: invariantId,
61
65
  system: 'automatic',
62
66
  statement: invariant.description,
63
67
  description: invariant.description,
@@ -74,6 +78,7 @@ class InvariantDetector {
74
78
  condition: { pattern: invariant.pattern }
75
79
  },
76
80
  source_file: nodeId,
81
+ position: invariant.position,
77
82
  detected_at: new Date().toISOString()
78
83
  });
79
84
  }
@@ -91,7 +96,7 @@ class InvariantDetector {
91
96
  async analyzeEdgesForInvariants(edges) {
92
97
  // Look for dependency patterns that suggest invariants
93
98
  const dependencyConstraints = this.identifyDependencyInvariants(edges);
94
-
99
+
95
100
  for (const constraint of dependencyConstraints) {
96
101
  this.detectedInvariants.push({
97
102
  id: this.generateInvariantId('dependency_constraint', constraint.from + '_' + constraint.to),
@@ -126,18 +131,19 @@ class InvariantDetector {
126
131
  */
127
132
  async analyzeCodeFilesForInvariants(directory) {
128
133
  const invariantFiles = await this.findInvariantRelatedFiles(directory);
129
-
134
+
130
135
  for (const filePath of invariantFiles) {
131
136
  try {
132
137
  const content = await fs.readFile(filePath, 'utf8');
133
138
  const relativePath = path.relative(directory, filePath);
134
-
139
+
135
140
  // Look for common invariant implementation patterns
136
141
  const patterns = this.searchForInvariantPatterns(content);
137
-
138
- for (const pattern of patterns) {
142
+
143
+ for (let i = 0; i < patterns.length; i++) {
144
+ const pattern = patterns[i];
139
145
  this.detectedInvariants.push({
140
- id: this.generateInvariantId(pattern.type, relativePath),
146
+ id: this.generateInvariantId(pattern.type, relativePath, i),
141
147
  system: 'automatic',
142
148
  statement: `System invariant implemented in ${relativePath}: ${pattern.description}`,
143
149
  description: `System invariant implemented in ${relativePath}: ${pattern.description}`,
@@ -170,7 +176,7 @@ class InvariantDetector {
170
176
  */
171
177
  extractInvariantsFromFile(content, filePath) {
172
178
  const invariants = [];
173
-
179
+
174
180
  // Check for validation function patterns
175
181
  const validationPatterns = [
176
182
  /function\s+validate\w+/gi,
@@ -181,7 +187,7 @@ class InvariantDetector {
181
187
  /function\s+assert\w+/gi,
182
188
  /function\s+enforce\w+/gi
183
189
  ];
184
-
190
+
185
191
  for (const pattern of validationPatterns) {
186
192
  const matches = content.match(pattern);
187
193
  if (matches) {
@@ -192,7 +198,7 @@ class InvariantDetector {
192
198
  });
193
199
  }
194
200
  }
195
-
201
+
196
202
  // Check for error handling patterns that suggest invariant enforcement
197
203
  const errorHandlingPatterns = [
198
204
  /try\s*\{/gi,
@@ -201,7 +207,7 @@ class InvariantDetector {
201
207
  /if\s*\([^)]+\)\s*throw/gi,
202
208
  /error\.message/gi
203
209
  ];
204
-
210
+
205
211
  for (const pattern of errorHandlingPatterns) {
206
212
  const match = content.match(pattern);
207
213
  if (match) {
@@ -212,7 +218,7 @@ class InvariantDetector {
212
218
  });
213
219
  }
214
220
  }
215
-
221
+
216
222
  // Check for state management patterns
217
223
  const stateManagementPatterns = [
218
224
  /setState\s*\(/gi,
@@ -223,7 +229,7 @@ class InvariantDetector {
223
229
  /state\./gi,
224
230
  /state\s*=\s*/gi
225
231
  ];
226
-
232
+
227
233
  for (const pattern of stateManagementPatterns) {
228
234
  const match = content.match(pattern);
229
235
  if (match) {
@@ -234,7 +240,7 @@ class InvariantDetector {
234
240
  });
235
241
  }
236
242
  }
237
-
243
+
238
244
  // Check for boundary enforcement patterns
239
245
  const boundaryCheckPatterns = [
240
246
  /boundary\s*check/gi,
@@ -246,7 +252,7 @@ class InvariantDetector {
246
252
  /validate\s*input/gi,
247
253
  /sanitiz/gi
248
254
  ];
249
-
255
+
250
256
  for (const pattern of boundaryCheckPatterns) {
251
257
  const match = content.match(pattern);
252
258
  if (match) {
@@ -257,22 +263,22 @@ class InvariantDetector {
257
263
  });
258
264
  }
259
265
  }
260
-
266
+
261
267
  // Check for invariant indicators from pass 1
262
268
  const invariantIndicators = this.extractDetailedInvariantIndicators(content, filePath);
263
269
  for (const indicator of invariantIndicators) {
264
270
  invariants.push(indicator);
265
271
  }
266
-
272
+
267
273
  return invariants;
268
274
  }
269
-
275
+
270
276
  /**
271
277
  * Extract detailed invariant indicators from content
272
278
  */
273
279
  extractDetailedInvariantIndicators(content, filePath) {
274
280
  const indicators = [];
275
-
281
+
276
282
  // Look for common invariant/pattern validation patterns
277
283
  const patterns = [
278
284
  // Assertion and validation patterns
@@ -284,13 +290,14 @@ class InvariantDetector {
284
290
  { pattern: /guard[A-Z]/gi, description: 'Guard function pattern', critical: true },
285
291
  { pattern: /verify[A-Z]/gi, description: 'Verify function pattern', critical: false },
286
292
  { pattern: /validate\(/gi, description: 'Validation function call', critical: false },
287
-
293
+
288
294
  // Constraint and invariant mentions
289
295
  { pattern: /Invariant/gi, description: 'Invariant mention', critical: true },
290
296
  { pattern: /constraint/gi, description: 'Constraint mention', critical: true },
291
- { pattern: /require\(/gi, description: 'Requirement assertion', critical: true },
297
+ { pattern: /VIOLATION/gi, description: 'Explicit violation marker', critical: true },
298
+ { pattern: /BLOCKED/gi, description: 'Blocking architectural pattern', critical: true },
292
299
  { pattern: /enforce/gi, description: 'Enforcement pattern', critical: true },
293
-
300
+
294
301
  // Conditional guard patterns
295
302
  { pattern: /must.*be/gi, description: 'Must-be pattern', critical: true },
296
303
  { pattern: /should.*be/gi, description: 'Should-be pattern', critical: false },
@@ -298,39 +305,39 @@ class InvariantDetector {
298
305
  { pattern: /if.*!\s*[^\s]+\s*throw/gi, description: 'Guard clause pattern', critical: true },
299
306
  { pattern: /if.*!\s*[^\s]+\s*reject/gi, description: 'Promise rejection guard', critical: true },
300
307
  { pattern: /throw.*Error/gi, description: 'Error throwing pattern', critical: true },
301
-
308
+
302
309
  // Boundary and validation patterns
303
310
  { pattern: /boundary.*check/gi, description: 'Boundary check', critical: true },
304
311
  { pattern: /range.*check/gi, description: 'Range check', critical: true },
305
312
  { pattern: /within.*bounds/gi, description: 'Bounds check', critical: true },
306
313
  { pattern: /validat(e|ion)/gi, description: 'Validation pattern', critical: false },
307
314
  { pattern: /guard/i, description: 'Guard pattern', critical: true },
308
-
315
+
309
316
  // Access control and security patterns
310
317
  { pattern: /access.*control/gi, description: 'Access control', critical: true },
311
318
  { pattern: /permission.*check/gi, description: 'Permission check', critical: true },
312
319
  { pattern: /auth(?:oriz|enticat)/gi, description: 'Authorization/authentication', critical: true },
313
320
  { pattern: /acl/gi, description: 'Access Control List', critical: true },
314
-
321
+
315
322
  // State management patterns
316
323
  { pattern: /state.*transition/gi, description: 'State transition', critical: true },
317
324
  { pattern: /setState/gi, description: 'State setter', critical: true },
318
325
  { pattern: /update.*state/gi, description: 'State updater', critical: true },
319
326
  { pattern: /transition/gi, description: 'Transition pattern', critical: true },
320
327
  { pattern: /immutable/gi, description: 'Immutability pattern', critical: true },
321
-
328
+
322
329
  // Type and schema validation patterns
323
330
  { pattern: /zod/gi, description: 'Zod validation schema', critical: false },
324
331
  { pattern: /joi/gi, description: 'Joi validation schema', critical: false },
325
332
  { pattern: /yup/gi, description: 'Yup validation schema', critical: false },
326
333
  { pattern: /ajv/gi, description: 'Ajv validation schema', critical: false },
327
334
  { pattern: /schema/gi, description: 'Schema definition', critical: false },
328
-
335
+
329
336
  // Configuration patterns
330
337
  { pattern: /config.*environ/gi, description: 'Environment configuration', critical: true },
331
338
  { pattern: /env.*validation/gi, description: 'Environment validation', critical: true }
332
339
  ];
333
-
340
+
334
341
  for (const item of patterns) {
335
342
  if (!item.pattern || !content) continue; // Skip if pattern or content is undefined
336
343
  let match;
@@ -338,13 +345,14 @@ class InvariantDetector {
338
345
  while ((match = item.pattern.exec(content)) !== null) {
339
346
  // Avoid duplicate matches by position
340
347
  const position = item.pattern.lastIndex;
341
- const prevMatch = indicators.find(ind => ind.pattern === item.pattern.source &&
342
- ind.description.includes(match[0]));
348
+ const prevMatch = indicators.find(ind => ind.pattern === item.pattern.source &&
349
+ ind.description.includes(match[0]));
343
350
  if (!prevMatch) {
344
351
  indicators.push({
345
352
  pattern: item.pattern.source,
346
353
  description: `${item.description} found in ${filePath}: '${match[0]}' at position ${position}`,
347
- isCritical: item.critical
354
+ isCritical: item.critical,
355
+ position: position
348
356
  });
349
357
  }
350
358
  }
@@ -353,7 +361,7 @@ class InvariantDetector {
353
361
  continue;
354
362
  }
355
363
  }
356
-
364
+
357
365
  return indicators;
358
366
  }
359
367
 
@@ -362,11 +370,11 @@ class InvariantDetector {
362
370
  */
363
371
  identifyDependencyInvariants(edges) {
364
372
  const constraints = [];
365
-
373
+
366
374
  // Look for patterns like core modules that should not depend on peripheral modules
367
375
  const coreModules = new Set();
368
376
  const peripheralModules = new Set();
369
-
377
+
370
378
  for (const edge of edges) {
371
379
  // Identify potential architectural layers based on file paths
372
380
  if (this.isCoreLayer(edge.from) && this.isPeripheralLayer(edge.to)) {
@@ -379,7 +387,7 @@ class InvariantDetector {
379
387
  });
380
388
  }
381
389
  }
382
-
390
+
383
391
  return constraints;
384
392
  }
385
393
 
@@ -388,19 +396,19 @@ class InvariantDetector {
388
396
  */
389
397
  async findInvariantRelatedFiles(directory) {
390
398
  const invariantFiles = [];
391
-
399
+
392
400
  const walk = async (dir) => {
393
401
  try {
394
402
  const entries = await fs.readdir(dir, { withFileTypes: true });
395
-
403
+
396
404
  for (const entry of entries) {
397
405
  const fullPath = path.join(dir, entry.name);
398
-
406
+
399
407
  if (entry.isDirectory()) {
400
408
  await walk(fullPath);
401
409
  } else if (entry.isFile()) {
402
410
  const fileName = entry.name.toLowerCase();
403
-
411
+
404
412
  // Look for files that commonly contain invariants
405
413
  if (
406
414
  fileName.includes('invariant') ||
@@ -420,7 +428,7 @@ class InvariantDetector {
420
428
  // Skip directories we can't access
421
429
  }
422
430
  };
423
-
431
+
424
432
  await walk(directory);
425
433
  return invariantFiles;
426
434
  }
@@ -430,7 +438,7 @@ class InvariantDetector {
430
438
  */
431
439
  searchForInvariantPatterns(content) {
432
440
  const patterns = [];
433
-
441
+
434
442
  // Look for assertion/verification patterns
435
443
  if (content.includes('assert(') || content.includes('AssertionError')) {
436
444
  patterns.push({
@@ -439,7 +447,7 @@ class InvariantDetector {
439
447
  assertion: 'Code contains assert() calls for invariant enforcement'
440
448
  });
441
449
  }
442
-
450
+
443
451
  // Look for validation patterns
444
452
  if (content.includes('validate(') || content.includes('isValid') || content.includes('checkValid')) {
445
453
  patterns.push({
@@ -448,7 +456,7 @@ class InvariantDetector {
448
456
  assertion: 'Code contains validation functions for invariant enforcement'
449
457
  });
450
458
  }
451
-
459
+
452
460
  // Look for guard clauses
453
461
  if (content.includes('if (!condition) throw') || content.includes('guard clause')) {
454
462
  patterns.push({
@@ -457,7 +465,7 @@ class InvariantDetector {
457
465
  assertion: 'Code contains early-exit guard clauses for invariant enforcement'
458
466
  });
459
467
  }
460
-
468
+
461
469
  // Look for state machine patterns
462
470
  if (content.includes('state') && (content.includes('transition') || content.includes('transitions'))) {
463
471
  patterns.push({
@@ -466,7 +474,7 @@ class InvariantDetector {
466
474
  assertion: 'Code implements state machine with transition constraints'
467
475
  });
468
476
  }
469
-
477
+
470
478
  return patterns;
471
479
  }
472
480
 
@@ -501,11 +509,11 @@ class InvariantDetector {
501
509
  */
502
510
  isCoreLayer(filePath) {
503
511
  if (!filePath) return false;
504
- return filePath.includes('/core/') ||
505
- filePath.includes('/lib/') ||
506
- filePath.includes('/shared/') ||
507
- filePath.includes('config') ||
508
- filePath.includes('auth');
512
+ return filePath.includes('/core/') ||
513
+ filePath.includes('/lib/') ||
514
+ filePath.includes('/shared/') ||
515
+ filePath.includes('config') ||
516
+ filePath.includes('auth');
509
517
  }
510
518
 
511
519
  /**
@@ -513,41 +521,41 @@ class InvariantDetector {
513
521
  */
514
522
  isPeripheralLayer(filePath) {
515
523
  if (!filePath) return false;
516
- return filePath.includes('/views/') ||
517
- filePath.includes('/templates/') ||
518
- filePath.includes('/ui/') ||
519
- filePath.includes('/tests/') ||
520
- filePath.includes('/mocks/');
524
+ return filePath.includes('/views/') ||
525
+ filePath.includes('/templates/') ||
526
+ filePath.includes('/ui/') ||
527
+ filePath.includes('/tests/') ||
528
+ filePath.includes('/mocks/');
521
529
  }
522
530
 
523
531
  /**
524
532
  * Generate unique invariant ID
525
533
  */
526
- generateInvariantId(pattern, context) {
527
- const baseId = `${pattern}_${context}`.replace(/[^a-zA-Z0-9_-]/g, '_');
534
+ generateInvariantId(pattern, context, discriminator = '') {
535
+ const baseId = `${pattern}_${context}_${discriminator}`.replace(/[^a-zA-Z0-9_-]/g, '_');
528
536
  return baseId.substring(0, 100); // Limit length
529
537
  }
530
-
538
+
531
539
  /**
532
540
  * Calculate confidence score for an invariant based on various factors
533
541
  */
534
542
  calculateConfidenceScore(pattern, context, content = '') {
535
543
  let score = 0.5; // Base confidence
536
-
544
+
537
545
  // Increase confidence based on pattern type
538
546
  if (pattern.includes('assert') || pattern.includes('validate') || pattern.includes('guard')) {
539
547
  score += 0.2;
540
548
  }
541
-
549
+
542
550
  if (pattern.includes('state') || pattern.includes('mutation') || pattern.includes('setState')) {
543
551
  score += 0.15;
544
552
  }
545
-
553
+
546
554
  // Increase confidence if context suggests critical file
547
555
  if (context.includes('core') || context.includes('lib') || context.includes('shared')) {
548
556
  score += 0.1;
549
557
  }
550
-
558
+
551
559
  // Increase confidence based on frequency of pattern in content
552
560
  if (content && typeof content === 'string') {
553
561
  const matches = content.match(new RegExp(pattern, 'gi'));
@@ -555,50 +563,50 @@ class InvariantDetector {
555
563
  score += Math.min(matches.length * 0.05, 0.2); // Up to 0.2 for multiple matches
556
564
  }
557
565
  }
558
-
566
+
559
567
  // Ensure score stays within bounds
560
568
  return Math.min(Math.max(score, 0.1), 1.0);
561
569
  }
562
-
570
+
563
571
  /**
564
572
  * Determine the owner of an invariant based on the source file
565
573
  */
566
574
  determineOwnerFromPath(filePath) {
567
575
  if (!filePath) return 'unknown';
568
-
576
+
569
577
  // Extract function or module name from file path
570
578
  const parts = filePath.split(/[\/]/);
571
579
  const fileName = parts[parts.length - 1];
572
580
  const baseName = fileName.replace(/\.[^/.]+$/, ''); // Remove extension
573
-
581
+
574
582
  return baseName;
575
583
  }
576
-
584
+
577
585
  /**
578
586
  * Analyze structural patterns in the codebase and generate architectural invariants
579
587
  */
580
588
  async analyzeStructuralPatterns(nodes, edges, directory) {
581
589
  // Generate layer-based architectural invariants
582
590
  await this.generateLayerInvariants(nodes, edges);
583
-
591
+
584
592
  // Generate dependency-based architectural invariants
585
593
  await this.generateDependencyInvariants(nodes, edges);
586
-
594
+
587
595
  // Generate coupling-based architectural invariants
588
596
  await this.generateCouplingInvariants(nodes, edges);
589
597
  }
590
-
598
+
591
599
  /**
592
600
  * Generate layer-based architectural invariants
593
601
  */
594
602
  async generateLayerInvariants(nodes, edges) {
595
603
  const layerMap = new Map();
596
-
604
+
597
605
  // Classify nodes by layer based on path patterns
598
606
  for (const node of nodes) {
599
607
  const filePath = node.path || node.id;
600
608
  if (!filePath) continue;
601
-
609
+
602
610
  let layer = 'general';
603
611
  if (filePath.includes('/controllers/') || filePath.includes('/handlers/')) {
604
612
  layer = 'controllers';
@@ -613,31 +621,31 @@ class InvariantDetector {
613
621
  } else if (filePath.includes('/views/') || filePath.includes('/components/') || filePath.includes('/ui/')) {
614
622
  layer = 'presentation';
615
623
  }
616
-
624
+
617
625
  if (!layerMap.has(layer)) {
618
626
  layerMap.set(layer, []);
619
627
  }
620
628
  layerMap.get(layer).push(filePath);
621
629
  }
622
-
630
+
623
631
  // Generate invariants based on layer dependencies (e.g., models shouldn't depend on controllers)
624
632
  const layerDependencies = new Map();
625
633
  for (const edge of edges) {
626
634
  const fromNode = nodes.find(n => n.id === edge.from || n.path === edge.from);
627
635
  const toNode = nodes.find(n => n.id === edge.to || n.path === edge.to);
628
-
636
+
629
637
  if (fromNode && toNode) {
630
638
  const fromPath = fromNode.path || fromNode.id;
631
639
  const toPath = toNode.path || toNode.id;
632
-
640
+
633
641
  let fromLayer = 'general';
634
642
  let toLayer = 'general';
635
-
643
+
636
644
  for (const [layer, paths] of layerMap.entries()) {
637
645
  if (paths.includes(fromPath)) fromLayer = layer;
638
646
  if (paths.includes(toPath)) toLayer = layer;
639
647
  }
640
-
648
+
641
649
  if (fromLayer !== toLayer) {
642
650
  const depKey = `${fromLayer}->${toLayer}`;
643
651
  if (!layerDependencies.has(depKey)) {
@@ -647,23 +655,23 @@ class InvariantDetector {
647
655
  }
648
656
  }
649
657
  }
650
-
658
+
651
659
  // Create invariants for problematic layer dependencies
652
660
  for (const [depKey, dependencies] of layerDependencies.entries()) {
653
661
  const [fromLayer, toLayer] = depKey.split('->');
654
-
662
+
655
663
  // Define architectural rules (e.g., models should not depend on controllers)
656
664
  const forbiddenLayerDeps = [
657
665
  { from: 'models', to: 'controllers' },
658
- { from: 'models', to: 'services' },
666
+ { from: 'models', to: 'services' },
659
667
  { from: 'controllers', to: 'models' },
660
668
  { from: 'presentation', to: 'models' }
661
669
  ];
662
-
663
- const isForbidden = forbiddenLayerDeps.some(rule =>
670
+
671
+ const isForbidden = forbiddenLayerDeps.some(rule =>
664
672
  rule.from === fromLayer && rule.to === toLayer
665
673
  );
666
-
674
+
667
675
  if (isForbidden) {
668
676
  this.detectedInvariants.push({
669
677
  id: this.generateInvariantId(`layer_${fromLayer}_not_depend_${toLayer}`, `${fromLayer}_${toLayer}`),
@@ -692,14 +700,14 @@ class InvariantDetector {
692
700
  }
693
701
  }
694
702
  }
695
-
703
+
696
704
  /**
697
705
  * Generate dependency-based architectural invariants
698
706
  */
699
707
  async generateDependencyInvariants(nodes, edges) {
700
708
  // Identify high-degree nodes that might indicate architectural hubs
701
709
  const nodeDegrees = new Map();
702
-
710
+
703
711
  for (const edge of edges) {
704
712
  // Count incoming edges (dependencies on this node)
705
713
  if (!nodeDegrees.has(edge.to)) {
@@ -708,11 +716,11 @@ class InvariantDetector {
708
716
  if (!nodeDegrees.has(edge.from)) {
709
717
  nodeDegrees.set(edge.from, { incoming: 0, outgoing: 0 });
710
718
  }
711
-
719
+
712
720
  nodeDegrees.get(edge.to).incoming++;
713
721
  nodeDegrees.get(edge.from).outgoing++;
714
722
  }
715
-
723
+
716
724
  // Identify potential architectural bottlenecks
717
725
  for (const [nodeId, degrees] of nodeDegrees.entries()) {
718
726
  // Make sure nodeId is valid before creating invariant
@@ -743,18 +751,18 @@ class InvariantDetector {
743
751
  }
744
752
  }
745
753
  }
746
-
754
+
747
755
  /**
748
756
  * Generate coupling-based architectural invariants
749
757
  */
750
758
  async generateCouplingInvariants(nodes, edges) {
751
759
  // Identify tightly coupled components
752
760
  const couplingMap = new Map();
753
-
761
+
754
762
  for (const edge of edges) {
755
763
  // Look for mutual dependencies indicating tight coupling
756
764
  const reverseEdge = edges.find(e => e.from === edge.to && e.to === edge.from);
757
-
765
+
758
766
  if (reverseEdge) {
759
767
  const coupleKey = [edge.from, edge.to].sort().join('|');
760
768
  if (!couplingMap.has(coupleKey)) {
@@ -763,11 +771,11 @@ class InvariantDetector {
763
771
  couplingMap.get(coupleKey).push(edge);
764
772
  }
765
773
  }
766
-
774
+
767
775
  // Create invariants for tightly coupled components
768
776
  for (const [coupleKey, edgesList] of couplingMap.entries()) {
769
777
  const [nodeA, nodeB] = coupleKey.split('|');
770
-
778
+
771
779
  // Only create the invariant if both node names are valid and non-empty
772
780
  if (nodeA && nodeA.trim() !== '' && nodeB && nodeB.trim() !== '') {
773
781
  this.detectedInvariants.push({
@@ -794,6 +802,18 @@ class InvariantDetector {
794
802
  }
795
803
  }
796
804
  }
805
+
806
+ /**
807
+ * Check if a file is accessible and readable
808
+ */
809
+ async isFileAccessible(filePath) {
810
+ try {
811
+ await fs.access(filePath, require('fs').constants.R_OK);
812
+ return true;
813
+ } catch (error) {
814
+ return false;
815
+ }
816
+ }
797
817
  }
798
818
 
799
819
  module.exports = { InvariantDetector };