arcvision 0.2.17 → 0.2.20
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/.arcvision/logs/errors.log +5 -0
- package/arcvision_context/architecture.authority.ledger.json +6 -63
- package/bin/arcvision.js +12 -0
- package/package.json +3 -2
- package/src/core/artifact-manager.js +143 -0
- package/src/core/command-base.js +107 -0
- package/src/core/config-validator.js +199 -0
- package/src/core/error-handler.js +106 -0
- package/src/core/feature-manager.js +218 -0
- package/src/core/feedback-generator.js +260 -0
- package/src/core/invariant-analyzer.js +22 -2
- package/src/core/invariant-detector.js +236 -3
- package/src/core/parser.js +85 -1
- package/src/core/scanner.js +18 -6
- package/src/engine/context_builder.js +21 -3
- package/src/engine/context_validator.js +7 -1
- package/src/engine/pass1_facts.js +2 -2
- package/src/index.js +41 -13
- package/test-block-functionality.js +40 -0
- package/test-dev-project/.arcvision/invariants.json +19 -0
- package/{arcvision_context → test-dev-project/arcvision_context}/README.md +9 -9
- package/test-dev-project/arcvision_context/architecture.authority.ledger.json +45 -0
- package/{arcvision.context.json → test-dev-project/arcvision_context/arcvision.context.json} +498 -496
- package/test-dev-project/src/core/data-service.js +0 -0
- package/test-dev-project/src/ui/user-profile.js +0 -0
- package/test-dev-project/src/utils/helpers.js +0 -0
- package/ARCVISION_DIRECTORY_STRUCTURE.md +0 -104
- package/CLI_STRUCTURE.md +0 -110
- package/CONFIGURATION.md +0 -119
- package/IMPLEMENTATION_SUMMARY.md +0 -99
- package/README.md +0 -149
- package/architecture.authority.ledger.json +0 -46
- package/arcvision-0.2.3.tgz +0 -0
- package/arcvision-0.2.4.tgz +0 -0
- package/arcvision-0.2.5.tgz +0 -0
- package/arcvision.context.diff.json +0 -2181
- package/arcvision.context.v1.json +0 -2163
- package/arcvision.context.v2.json +0 -2173
- package/arcvision_context/arcvision.context.json +0 -6884
- package/debug-cycle-detection.js +0 -56
- package/docs/ENHANCED_ACCURACY_SAFETY_PROTOCOL.md +0 -172
- package/docs/accuracy-enhancement-artifacts/enhanced-validation-config.json +0 -98
- package/docs/acig-robustness-guide.md +0 -164
- package/docs/authoritative-gate-implementation.md +0 -168
- package/docs/blast-radius-implementation.md +0 -76
- package/docs/blast-radius.md +0 -44
- package/docs/cli-strengthening-summary.md +0 -232
- package/docs/invariant-system-summary.md +0 -100
- package/docs/invariant-system.md +0 -112
- package/generate_large_test.js +0 -42
- package/large_test_repo.json +0 -1
- package/output1.json +0 -2163
- package/output2.json +0 -2163
- package/scan_calcom_report.txt +0 -0
- package/scan_leafmint_report.txt +0 -0
- package/scan_output.txt +0 -0
- package/scan_trigger_report.txt +0 -0
- package/temp_original.js +0 -0
- package/test/determinism-test.js +0 -83
- package/test-authoritative-context.js +0 -53
- package/test-real-authoritative-context.js +0 -118
- package/test-upload-enhancements.js +0 -111
- package/verify_engine.js +0 -116
|
@@ -27,6 +27,7 @@ class InvariantDetector {
|
|
|
27
27
|
await this.analyzeNodesForInvariants(scanResult.nodes, directory);
|
|
28
28
|
await this.analyzeEdgesForInvariants(scanResult.edges);
|
|
29
29
|
await this.analyzeCodeFilesForInvariants(directory);
|
|
30
|
+
await this.analyzeStructuralPatterns(scanResult.nodes, scanResult.edges, directory);
|
|
30
31
|
|
|
31
32
|
console.log(`\n🛡️ Invariant Detection Complete`);
|
|
32
33
|
console.log(` Found ${this.detectedInvariants.length} potential system invariants`);
|
|
@@ -57,11 +58,17 @@ class InvariantDetector {
|
|
|
57
58
|
const nodeId = node.path || node.id || 'unknown';
|
|
58
59
|
this.detectedInvariants.push({
|
|
59
60
|
id: this.generateInvariantId(invariant.pattern, nodeId),
|
|
61
|
+
system: 'automatic',
|
|
60
62
|
statement: invariant.description,
|
|
61
63
|
description: invariant.description,
|
|
62
|
-
|
|
64
|
+
severity: invariant.isCritical ? 'block' : 'risk',
|
|
65
|
+
scope: { files: [nodeId] },
|
|
63
66
|
critical_path: invariant.isCritical,
|
|
64
67
|
assertion: `Pattern: ${invariant.pattern}`,
|
|
68
|
+
rule: {
|
|
69
|
+
type: 'pattern',
|
|
70
|
+
condition: { pattern: invariant.pattern }
|
|
71
|
+
},
|
|
65
72
|
status: 'suspected',
|
|
66
73
|
confidence: this.calculateConfidenceScore(invariant.pattern, nodeId, content),
|
|
67
74
|
source_file: nodeId,
|
|
@@ -86,11 +93,22 @@ class InvariantDetector {
|
|
|
86
93
|
for (const constraint of dependencyConstraints) {
|
|
87
94
|
this.detectedInvariants.push({
|
|
88
95
|
id: this.generateInvariantId('dependency_constraint', constraint.from + '_' + constraint.to),
|
|
96
|
+
system: 'automatic',
|
|
89
97
|
statement: constraint.description,
|
|
90
98
|
description: constraint.description,
|
|
91
|
-
|
|
99
|
+
severity: constraint.critical ? 'block' : 'risk',
|
|
100
|
+
scope: { files: [constraint.from, constraint.to] },
|
|
92
101
|
critical_path: constraint.critical,
|
|
93
102
|
assertion: `Dependency constraint: ${constraint.from} -> ${constraint.to}`,
|
|
103
|
+
rule: {
|
|
104
|
+
type: 'dependency',
|
|
105
|
+
condition: {
|
|
106
|
+
forbiddenDependency: {
|
|
107
|
+
from: constraint.from,
|
|
108
|
+
to: constraint.to
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
94
112
|
status: 'suspected',
|
|
95
113
|
confidence: this.calculateConfidenceScore('dependency_constraint', constraint.from, ''),
|
|
96
114
|
source_relationship: `${constraint.from} -> ${constraint.to}`,
|
|
@@ -116,11 +134,17 @@ class InvariantDetector {
|
|
|
116
134
|
for (const pattern of patterns) {
|
|
117
135
|
this.detectedInvariants.push({
|
|
118
136
|
id: this.generateInvariantId(pattern.type, relativePath),
|
|
137
|
+
system: 'automatic',
|
|
119
138
|
statement: `System invariant implemented in ${relativePath}: ${pattern.description}`,
|
|
120
139
|
description: `System invariant implemented in ${relativePath}: ${pattern.description}`,
|
|
121
|
-
|
|
140
|
+
severity: 'risk',
|
|
141
|
+
scope: { files: [relativePath] },
|
|
122
142
|
critical_path: true, // File specifically for invariants is likely critical
|
|
123
143
|
assertion: pattern.assertion,
|
|
144
|
+
rule: {
|
|
145
|
+
type: 'pattern',
|
|
146
|
+
condition: { pattern: pattern.type }
|
|
147
|
+
},
|
|
124
148
|
status: 'suspected',
|
|
125
149
|
confidence: this.calculateConfidenceScore(pattern.type, relativePath, content),
|
|
126
150
|
source_file: relativePath,
|
|
@@ -543,6 +567,215 @@ class InvariantDetector {
|
|
|
543
567
|
|
|
544
568
|
return baseName;
|
|
545
569
|
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Analyze structural patterns in the codebase and generate architectural invariants
|
|
573
|
+
*/
|
|
574
|
+
async analyzeStructuralPatterns(nodes, edges, directory) {
|
|
575
|
+
// Generate layer-based architectural invariants
|
|
576
|
+
await this.generateLayerInvariants(nodes, edges);
|
|
577
|
+
|
|
578
|
+
// Generate dependency-based architectural invariants
|
|
579
|
+
await this.generateDependencyInvariants(nodes, edges);
|
|
580
|
+
|
|
581
|
+
// Generate coupling-based architectural invariants
|
|
582
|
+
await this.generateCouplingInvariants(nodes, edges);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Generate layer-based architectural invariants
|
|
587
|
+
*/
|
|
588
|
+
async generateLayerInvariants(nodes, edges) {
|
|
589
|
+
const layerMap = new Map();
|
|
590
|
+
|
|
591
|
+
// Classify nodes by layer based on path patterns
|
|
592
|
+
for (const node of nodes) {
|
|
593
|
+
const filePath = node.path || node.id;
|
|
594
|
+
if (!filePath) continue;
|
|
595
|
+
|
|
596
|
+
let layer = 'general';
|
|
597
|
+
if (filePath.includes('/controllers/') || filePath.includes('/handlers/')) {
|
|
598
|
+
layer = 'controllers';
|
|
599
|
+
} else if (filePath.includes('/services/') || filePath.includes('/business/')) {
|
|
600
|
+
layer = 'services';
|
|
601
|
+
} else if (filePath.includes('/models/') || filePath.includes('/entities/') || filePath.includes('/schemas/')) {
|
|
602
|
+
layer = 'models';
|
|
603
|
+
} else if (filePath.includes('/utils/') || filePath.includes('/helpers/') || filePath.includes('/lib/')) {
|
|
604
|
+
layer = 'utilities';
|
|
605
|
+
} else if (filePath.includes('/config/') || filePath.includes('/constants/')) {
|
|
606
|
+
layer = 'configuration';
|
|
607
|
+
} else if (filePath.includes('/views/') || filePath.includes('/components/') || filePath.includes('/ui/')) {
|
|
608
|
+
layer = 'presentation';
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (!layerMap.has(layer)) {
|
|
612
|
+
layerMap.set(layer, []);
|
|
613
|
+
}
|
|
614
|
+
layerMap.get(layer).push(filePath);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Generate invariants based on layer dependencies (e.g., models shouldn't depend on controllers)
|
|
618
|
+
const layerDependencies = new Map();
|
|
619
|
+
for (const edge of edges) {
|
|
620
|
+
const fromNode = nodes.find(n => n.id === edge.from || n.path === edge.from);
|
|
621
|
+
const toNode = nodes.find(n => n.id === edge.to || n.path === edge.to);
|
|
622
|
+
|
|
623
|
+
if (fromNode && toNode) {
|
|
624
|
+
const fromPath = fromNode.path || fromNode.id;
|
|
625
|
+
const toPath = toNode.path || toNode.id;
|
|
626
|
+
|
|
627
|
+
let fromLayer = 'general';
|
|
628
|
+
let toLayer = 'general';
|
|
629
|
+
|
|
630
|
+
for (const [layer, paths] of layerMap.entries()) {
|
|
631
|
+
if (paths.includes(fromPath)) fromLayer = layer;
|
|
632
|
+
if (paths.includes(toPath)) toLayer = layer;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (fromLayer !== toLayer) {
|
|
636
|
+
const depKey = `${fromLayer}->${toLayer}`;
|
|
637
|
+
if (!layerDependencies.has(depKey)) {
|
|
638
|
+
layerDependencies.set(depKey, []);
|
|
639
|
+
}
|
|
640
|
+
layerDependencies.get(depKey).push({ from: fromPath, to: toPath });
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Create invariants for problematic layer dependencies
|
|
646
|
+
for (const [depKey, dependencies] of layerDependencies.entries()) {
|
|
647
|
+
const [fromLayer, toLayer] = depKey.split('->');
|
|
648
|
+
|
|
649
|
+
// Define architectural rules (e.g., models should not depend on controllers)
|
|
650
|
+
const forbiddenLayerDeps = [
|
|
651
|
+
{ from: 'models', to: 'controllers' },
|
|
652
|
+
{ from: 'models', to: 'services' },
|
|
653
|
+
{ from: 'controllers', to: 'models' },
|
|
654
|
+
{ from: 'presentation', to: 'models' }
|
|
655
|
+
];
|
|
656
|
+
|
|
657
|
+
const isForbidden = forbiddenLayerDeps.some(rule =>
|
|
658
|
+
rule.from === fromLayer && rule.to === toLayer
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
if (isForbidden) {
|
|
662
|
+
this.detectedInvariants.push({
|
|
663
|
+
id: this.generateInvariantId(`layer_${fromLayer}_not_depend_${toLayer}`, `${fromLayer}_${toLayer}`),
|
|
664
|
+
system: 'architectural',
|
|
665
|
+
statement: `Layer dependency rule: ${fromLayer} components should not depend on ${toLayer} components`,
|
|
666
|
+
description: `Prevent ${fromLayer} layer from depending on ${toLayer} layer to maintain separation of concerns`,
|
|
667
|
+
severity: 'block',
|
|
668
|
+
scope: { files: dependencies.map(d => d.from) },
|
|
669
|
+
critical_path: true,
|
|
670
|
+
assertion: `Layer dependency rule: ${fromLayer} -> ${toLayer} is forbidden`,
|
|
671
|
+
rule: {
|
|
672
|
+
type: 'dependency',
|
|
673
|
+
condition: {
|
|
674
|
+
forbiddenDependency: {
|
|
675
|
+
from: `**/${fromLayer}/**`,
|
|
676
|
+
to: `**/${toLayer}/**`
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
},
|
|
680
|
+
status: 'detected',
|
|
681
|
+
confidence: 0.9,
|
|
682
|
+
detected_at: new Date().toISOString()
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Generate dependency-based architectural invariants
|
|
690
|
+
*/
|
|
691
|
+
async generateDependencyInvariants(nodes, edges) {
|
|
692
|
+
// Identify high-degree nodes that might indicate architectural hubs
|
|
693
|
+
const nodeDegrees = new Map();
|
|
694
|
+
|
|
695
|
+
for (const edge of edges) {
|
|
696
|
+
// Count incoming edges (dependencies on this node)
|
|
697
|
+
if (!nodeDegrees.has(edge.to)) {
|
|
698
|
+
nodeDegrees.set(edge.to, { incoming: 0, outgoing: 0 });
|
|
699
|
+
}
|
|
700
|
+
if (!nodeDegrees.has(edge.from)) {
|
|
701
|
+
nodeDegrees.set(edge.from, { incoming: 0, outgoing: 0 });
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
nodeDegrees.get(edge.to).incoming++;
|
|
705
|
+
nodeDegrees.get(edge.from).outgoing++;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Identify potential architectural bottlenecks
|
|
709
|
+
for (const [nodeId, degrees] of nodeDegrees.entries()) {
|
|
710
|
+
if (degrees.incoming > 10) { // High incoming degree indicates hub
|
|
711
|
+
this.detectedInvariants.push({
|
|
712
|
+
id: this.generateInvariantId(`hub_protection_${nodeId}`, 'high_degree'),
|
|
713
|
+
system: 'architectural',
|
|
714
|
+
statement: `Protect high-degree node ${nodeId} from unauthorized modifications`,
|
|
715
|
+
description: `Node ${nodeId} has ${degrees.incoming} incoming dependencies, changes here affect many components`,
|
|
716
|
+
severity: 'risk',
|
|
717
|
+
scope: { files: [nodeId] },
|
|
718
|
+
critical_path: true,
|
|
719
|
+
assertion: `High-degree node protection: ${nodeId} has ${degrees.incoming} dependencies`,
|
|
720
|
+
rule: {
|
|
721
|
+
type: 'access_control',
|
|
722
|
+
condition: {
|
|
723
|
+
mustBeReviewed: true
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
status: 'detected',
|
|
727
|
+
confidence: 0.8,
|
|
728
|
+
detected_at: new Date().toISOString()
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Generate coupling-based architectural invariants
|
|
736
|
+
*/
|
|
737
|
+
async generateCouplingInvariants(nodes, edges) {
|
|
738
|
+
// Identify tightly coupled components
|
|
739
|
+
const couplingMap = new Map();
|
|
740
|
+
|
|
741
|
+
for (const edge of edges) {
|
|
742
|
+
// Look for mutual dependencies indicating tight coupling
|
|
743
|
+
const reverseEdge = edges.find(e => e.from === edge.to && e.to === edge.from);
|
|
744
|
+
|
|
745
|
+
if (reverseEdge) {
|
|
746
|
+
const coupleKey = [edge.from, edge.to].sort().join('|');
|
|
747
|
+
if (!couplingMap.has(coupleKey)) {
|
|
748
|
+
couplingMap.set(coupleKey, []);
|
|
749
|
+
}
|
|
750
|
+
couplingMap.get(coupleKey).push(edge);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Create invariants for tightly coupled components
|
|
755
|
+
for (const [coupleKey, edgesList] of couplingMap.entries()) {
|
|
756
|
+
const [nodeA, nodeB] = coupleKey.split('|');
|
|
757
|
+
|
|
758
|
+
this.detectedInvariants.push({
|
|
759
|
+
id: this.generateInvariantId(`tight_coupling_${nodeA}_${nodeB}`, 'bidirectional'),
|
|
760
|
+
system: 'architectural',
|
|
761
|
+
statement: `Prevent tight coupling between ${nodeA} and ${nodeB}`,
|
|
762
|
+
description: `Bidirectional dependency detected between ${nodeA} and ${nodeB}, indicating tight coupling`,
|
|
763
|
+
severity: 'risk',
|
|
764
|
+
scope: { files: [nodeA, nodeB] },
|
|
765
|
+
critical_path: false,
|
|
766
|
+
assertion: `Bidirectional dependency: ${nodeA} <-> ${nodeB}`,
|
|
767
|
+
rule: {
|
|
768
|
+
type: 'dependency',
|
|
769
|
+
condition: {
|
|
770
|
+
shouldAvoidBidirectional: true
|
|
771
|
+
}
|
|
772
|
+
},
|
|
773
|
+
status: 'detected',
|
|
774
|
+
confidence: 0.7,
|
|
775
|
+
detected_at: new Date().toISOString()
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
}
|
|
546
779
|
}
|
|
547
780
|
|
|
548
781
|
module.exports = { InvariantDetector };
|
package/src/core/parser.js
CHANGED
|
@@ -220,7 +220,13 @@ function parseFile(filePath) {
|
|
|
220
220
|
classes: [],
|
|
221
221
|
variables: [],
|
|
222
222
|
types: [],
|
|
223
|
-
dependencies: []
|
|
223
|
+
dependencies: [],
|
|
224
|
+
functionCalls: [],
|
|
225
|
+
methodCalls: [],
|
|
226
|
+
constructorCalls: [],
|
|
227
|
+
typeImports: [],
|
|
228
|
+
interfaceDependencies: [],
|
|
229
|
+
genericDependencies: []
|
|
224
230
|
};
|
|
225
231
|
|
|
226
232
|
traverse(ast, {
|
|
@@ -421,6 +427,55 @@ function parseFile(filePath) {
|
|
|
421
427
|
loc: node.loc
|
|
422
428
|
});
|
|
423
429
|
}
|
|
430
|
+
|
|
431
|
+
// Track type imports/usage for dependency analysis
|
|
432
|
+
if (node.typeParameters && node.typeParameters.params) {
|
|
433
|
+
node.typeParameters.params.forEach(param => {
|
|
434
|
+
if (param.constraint) {
|
|
435
|
+
metadata.genericDependencies = metadata.genericDependencies || [];
|
|
436
|
+
metadata.genericDependencies.push({
|
|
437
|
+
name: param.name,
|
|
438
|
+
constraint: param.constraint,
|
|
439
|
+
loc: param.loc
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
TSInterfaceDeclaration({ node }) {
|
|
447
|
+
if (node.id && node.id.name) {
|
|
448
|
+
metadata.types.push({
|
|
449
|
+
name: node.id.name,
|
|
450
|
+
kind: 'interface',
|
|
451
|
+
loc: node.loc
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Track interface inheritance for dependency analysis
|
|
456
|
+
if (node.extends) {
|
|
457
|
+
metadata.interfaceDependencies = metadata.interfaceDependencies || [];
|
|
458
|
+
node.extends.forEach(extend => {
|
|
459
|
+
if (extend.expression && extend.expression.name) {
|
|
460
|
+
metadata.interfaceDependencies.push({
|
|
461
|
+
name: extend.expression.name,
|
|
462
|
+
type: 'extends',
|
|
463
|
+
loc: extend.loc
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
|
|
470
|
+
TSImportType({ node }) {
|
|
471
|
+
// Track TypeScript import types
|
|
472
|
+
metadata.typeImports = metadata.typeImports || [];
|
|
473
|
+
if (node.argument && node.argument.value) {
|
|
474
|
+
metadata.typeImports.push({
|
|
475
|
+
source: node.argument.value,
|
|
476
|
+
loc: node.loc
|
|
477
|
+
});
|
|
478
|
+
}
|
|
424
479
|
},
|
|
425
480
|
VariableDeclaration({ node }) {
|
|
426
481
|
node.declarations.forEach(decl => {
|
|
@@ -513,6 +568,35 @@ function parseFile(filePath) {
|
|
|
513
568
|
});
|
|
514
569
|
}
|
|
515
570
|
}
|
|
571
|
+
|
|
572
|
+
// Enhanced call detection for better structural relationship mapping
|
|
573
|
+
if (node.callee.type === 'Identifier') {
|
|
574
|
+
// Track function calls for dependency analysis
|
|
575
|
+
metadata.functionCalls = metadata.functionCalls || [];
|
|
576
|
+
metadata.functionCalls.push({
|
|
577
|
+
name: node.callee.name,
|
|
578
|
+
loc: node.loc
|
|
579
|
+
});
|
|
580
|
+
} else if (node.callee.type === 'MemberExpression') {
|
|
581
|
+
// Track method calls
|
|
582
|
+
metadata.methodCalls = metadata.methodCalls || [];
|
|
583
|
+
const objectName = node.callee.object.name || (node.callee.object.property ? node.callee.object.property.name : 'unknown');
|
|
584
|
+
const methodName = node.callee.property.name || 'unknown';
|
|
585
|
+
metadata.methodCalls.push({
|
|
586
|
+
object: objectName,
|
|
587
|
+
method: methodName,
|
|
588
|
+
loc: node.loc
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Track constructor calls
|
|
593
|
+
if (node.callee.type === 'Identifier' && node.callee.name && /^[A-Z]/.test(node.callee.name)) {
|
|
594
|
+
metadata.constructorCalls = metadata.constructorCalls || [];
|
|
595
|
+
metadata.constructorCalls.push({
|
|
596
|
+
constructor: node.callee.name,
|
|
597
|
+
loc: node.loc
|
|
598
|
+
});
|
|
599
|
+
}
|
|
516
600
|
},
|
|
517
601
|
|
|
518
602
|
VariableDeclarator({ node }) {
|
package/src/core/scanner.js
CHANGED
|
@@ -148,10 +148,20 @@ async function scan(directory) {
|
|
|
148
148
|
});
|
|
149
149
|
|
|
150
150
|
// Assess architectural health based on invariants
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
151
|
+
try {
|
|
152
|
+
architecturalHealth = invariantAnalyzer.assessArchitecturalHealth(detectedInvariants, {
|
|
153
|
+
directory,
|
|
154
|
+
projectName: path.basename(directory)
|
|
155
|
+
});
|
|
156
|
+
} catch (healthError) {
|
|
157
|
+
console.warn(`⚠️ Could not assess architectural health: ${healthError.message}`);
|
|
158
|
+
// Provide a default health assessment
|
|
159
|
+
architecturalHealth = {
|
|
160
|
+
score: 0,
|
|
161
|
+
level: 'unknown',
|
|
162
|
+
assessment: 'Could not assess architectural health due to analysis error'
|
|
163
|
+
};
|
|
164
|
+
}
|
|
155
165
|
} else {
|
|
156
166
|
// Even if no invariants are detected, create a baseline analysis
|
|
157
167
|
const invariantAnalyzer = new InvariantAnalyzer();
|
|
@@ -186,7 +196,7 @@ async function scan(directory) {
|
|
|
186
196
|
structuralInvariants,
|
|
187
197
|
authoritativeContext,
|
|
188
198
|
// Include detected invariants
|
|
189
|
-
detectedInvariants,
|
|
199
|
+
autoDetectedInvariants: detectedInvariants,
|
|
190
200
|
// Include invariant analysis results
|
|
191
201
|
invariantAnalysis,
|
|
192
202
|
// Include architectural health assessment
|
|
@@ -227,7 +237,7 @@ async function scan(directory) {
|
|
|
227
237
|
}
|
|
228
238
|
|
|
229
239
|
// Build context and ensure archetype is properly set
|
|
230
|
-
let context = buildContext(nodes, edges, finalContextOptions);
|
|
240
|
+
let context = buildContext(nodes, edges, symbols, finalContextOptions);
|
|
231
241
|
|
|
232
242
|
// Make sure archetype is set properly in the system section
|
|
233
243
|
if (!context.system.archetype || context.system.archetype === null) {
|
|
@@ -250,6 +260,8 @@ async function scan(directory) {
|
|
|
250
260
|
console.warn(' ⚠️ Proceeding despite validation issues to see results');
|
|
251
261
|
// Continue processing instead of throwing error
|
|
252
262
|
// throw new Error("Context validation failed");
|
|
263
|
+
} else {
|
|
264
|
+
console.log(' ✅ Context validation passed');
|
|
253
265
|
}
|
|
254
266
|
|
|
255
267
|
// Deterministic sort for consistent hashing/snapshots
|
|
@@ -257,9 +257,27 @@ function buildContext(files, edges, options = {}) {
|
|
|
257
257
|
context.metrics.files_with_high_blast_radius = nodes.filter(n => n.blast_radius > 5).length;
|
|
258
258
|
context.metrics.total_dependencies = schemaEdges.length;
|
|
259
259
|
|
|
260
|
-
//
|
|
261
|
-
if (options.
|
|
262
|
-
context.
|
|
260
|
+
// Include automatically detected invariants in the context first
|
|
261
|
+
if (options.autoDetectedInvariants !== undefined && Array.isArray(options.autoDetectedInvariants) && options.autoDetectedInvariants.length > 0) {
|
|
262
|
+
context.auto_detected_invariants = options.autoDetectedInvariants;
|
|
263
|
+
|
|
264
|
+
// Initialize context.invariants with auto-detected invariants if not already set
|
|
265
|
+
if (!context.invariants || !Array.isArray(context.invariants)) {
|
|
266
|
+
context.invariants = [...options.autoDetectedInvariants];
|
|
267
|
+
} else {
|
|
268
|
+
// Merge with existing invariants if they exist
|
|
269
|
+
context.invariants = [...context.invariants, ...options.autoDetectedInvariants];
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Add new canonical sections if provided in options - this will add Pass 4 invariants if they exist
|
|
274
|
+
if (options.invariants !== undefined && Array.isArray(options.invariants) && options.invariants.length > 0) {
|
|
275
|
+
if (!context.invariants) {
|
|
276
|
+
context.invariants = options.invariants;
|
|
277
|
+
} else if (Array.isArray(context.invariants)) {
|
|
278
|
+
// Merge with existing invariants (could be from autoDetectedInvariants)
|
|
279
|
+
context.invariants = [...context.invariants, ...options.invariants];
|
|
280
|
+
}
|
|
263
281
|
}
|
|
264
282
|
|
|
265
283
|
if (options.ownership !== undefined) {
|
|
@@ -2,8 +2,14 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const Ajv = require('ajv');
|
|
4
4
|
|
|
5
|
+
// Import format validators
|
|
6
|
+
const addFormats = require('ajv-formats');
|
|
7
|
+
|
|
5
8
|
// Create AJV instance with options for detailed error reporting
|
|
6
|
-
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
9
|
+
const ajv = new Ajv({ allErrors: true, strict: false, validateFormats: false, logger: false });
|
|
10
|
+
|
|
11
|
+
// Add standard formats including date-time
|
|
12
|
+
addFormats(ajv);
|
|
7
13
|
|
|
8
14
|
/**
|
|
9
15
|
* Validate the context object against the Arcvision schema and invariants using AJV
|
|
@@ -38,7 +38,7 @@ async function executePass1(directory, options = {}) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
// Find all relevant code files for structural analysis
|
|
41
|
-
const files =
|
|
41
|
+
const files = glob.sync('**/*.{js,jsx,ts,tsx,cjs,mjs,json,lua}', {
|
|
42
42
|
...scanOptions,
|
|
43
43
|
ignore: [...scanOptions.ignore,
|
|
44
44
|
'**/*.d.ts',
|
|
@@ -68,7 +68,7 @@ async function executePass1(directory, options = {}) {
|
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
// Use only code files for analysis
|
|
71
|
-
const allFiles = files;
|
|
71
|
+
const allFiles = Array.isArray(files) ? files : [];
|
|
72
72
|
|
|
73
73
|
console.log(` Identified ${allFiles.length} files for analysis`);
|
|
74
74
|
|
package/src/index.js
CHANGED
|
@@ -16,7 +16,7 @@ const RetryHandler = require('./core/retry-handler');
|
|
|
16
16
|
const { invariantRegistry } = require('./core/invariant-registry');
|
|
17
17
|
const { evaluateChange } = require('./core/change-evaluator');
|
|
18
18
|
// Import authority gate components
|
|
19
|
-
const {
|
|
19
|
+
const { AuthorityLedger } = require('./core/authority-ledger');
|
|
20
20
|
const { overrideHandler } = require('./core/override-handler');
|
|
21
21
|
// Import robust validation and error handling
|
|
22
22
|
const { cliValidator } = require('./core/cli-validator');
|
|
@@ -307,6 +307,9 @@ async function uploadToDatabase(jsonData) {
|
|
|
307
307
|
controller.abort();
|
|
308
308
|
clearTimeout(progress30s);
|
|
309
309
|
clearTimeout(progress60s);
|
|
310
|
+
console.error(chalk.red('\n❌ Upload timeout after 120 seconds'));
|
|
311
|
+
console.error(chalk.red('This may indicate network issues or server problems'));
|
|
312
|
+
console.error(chalk.yellow('Try again later or check your internet connection'));
|
|
310
313
|
}, 120000); // 120 second timeout
|
|
311
314
|
|
|
312
315
|
const response = await fetch(`${API_URL}/api/upload`, {
|
|
@@ -319,6 +322,14 @@ async function uploadToDatabase(jsonData) {
|
|
|
319
322
|
graph: jsonData
|
|
320
323
|
}),
|
|
321
324
|
signal: controller.signal
|
|
325
|
+
}).catch(error => {
|
|
326
|
+
if (error.name === 'AbortError') {
|
|
327
|
+
throw new Error('Upload cancelled due to timeout');
|
|
328
|
+
}
|
|
329
|
+
if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') {
|
|
330
|
+
throw new Error(`Cannot connect to ${API_URL} - check your internet connection and that the server is running`);
|
|
331
|
+
}
|
|
332
|
+
throw error;
|
|
322
333
|
});
|
|
323
334
|
|
|
324
335
|
// Clear all timeouts
|
|
@@ -405,6 +416,13 @@ program
|
|
|
405
416
|
directory: targetDir
|
|
406
417
|
});
|
|
407
418
|
|
|
419
|
+
// Initialize Artifact Manager
|
|
420
|
+
const { ArtifactManager } = require('./core/artifact-manager');
|
|
421
|
+
const artifactManager = new ArtifactManager(targetDir);
|
|
422
|
+
|
|
423
|
+
// Ensure all required artifacts exist
|
|
424
|
+
artifactManager.ensureArtifacts();
|
|
425
|
+
|
|
408
426
|
console.log(chalk.blue(`Scanning directory: ${targetDir}`));
|
|
409
427
|
|
|
410
428
|
const map = await scanner.scan(targetDir);
|
|
@@ -413,7 +431,7 @@ program
|
|
|
413
431
|
// Analyze and print blast radius insight
|
|
414
432
|
const blastRadiusAnalysis = analyzeBlastRadius(map);
|
|
415
433
|
if (blastRadiusAnalysis && blastRadiusAnalysis.topFiles && blastRadiusAnalysis.topFiles.length > 0) {
|
|
416
|
-
console.log('\n
|
|
434
|
+
console.log('\n🔍 STRUCTURAL ANALYSIS RESULTS:\n');
|
|
417
435
|
|
|
418
436
|
blastRadiusAnalysis.topFiles.forEach((item, index) => {
|
|
419
437
|
let warningMessage = '';
|
|
@@ -425,12 +443,12 @@ program
|
|
|
425
443
|
warningMessage = 'Modifications can cause widespread inconsistencies.';
|
|
426
444
|
}
|
|
427
445
|
|
|
428
|
-
console.log(`${index + 1}. ${item.file}`);
|
|
429
|
-
console.log(` Blast Radius: ${item.blastRadius} files (${item.percentOfGraph}%)`);
|
|
430
|
-
console.log(`
|
|
446
|
+
console.log(`${index + 1}. 📁 ${item.file}`);
|
|
447
|
+
console.log(` 🎯 Blast Radius: ${item.blastRadius} files (${item.percentOfGraph}%)`);
|
|
448
|
+
console.log(` ⚠️ Risk: ${warningMessage}\n`);
|
|
431
449
|
});
|
|
432
450
|
} else {
|
|
433
|
-
console.log('\
|
|
451
|
+
console.log('\n✅ No high-impact structural hubs detected in this codebase.');
|
|
434
452
|
}
|
|
435
453
|
|
|
436
454
|
// Validate the map before saving or uploading
|
|
@@ -554,18 +572,28 @@ program
|
|
|
554
572
|
contextFile: options.contextFile,
|
|
555
573
|
invariantsFile: options.invariantsFile
|
|
556
574
|
});
|
|
557
|
-
|
|
575
|
+
|
|
576
|
+
// Initialize Artifact Manager
|
|
577
|
+
const { ArtifactManager } = require('./core/artifact-manager');
|
|
578
|
+
const artifactManager = new ArtifactManager(targetDir);
|
|
579
|
+
|
|
580
|
+
// Ensure all required artifacts exist
|
|
581
|
+
artifactManager.ensureArtifacts();
|
|
582
|
+
|
|
583
|
+
// Initialize Authority Ledger with target directory
|
|
584
|
+
const authorityLedger = new AuthorityLedger(path.join(targetDir, 'arcvision_context', 'architecture.authority.ledger.json'));
|
|
585
|
+
|
|
558
586
|
console.log(chalk.blue('🔒 Authoritative Change Impact Gate (ACIG)'));
|
|
559
587
|
console.log(chalk.blue('Evaluating changes against canonical system invariants...\n'));
|
|
560
|
-
|
|
588
|
+
|
|
561
589
|
// Load current context from arcvision_context directory
|
|
562
590
|
const contextFile = options.contextFile || path.join(targetDir, 'arcvision_context', 'arcvision.context.json');
|
|
563
591
|
if (!fs.existsSync(contextFile)) {
|
|
564
592
|
console.error(chalk.red(`❌ Context file not found: ${contextFile}`));
|
|
565
|
-
console.error(chalk.yellow('Run
|
|
593
|
+
console.error(chalk.yellow('Run \`arcvision scan\` first to generate context file'));
|
|
566
594
|
process.exit(1);
|
|
567
595
|
}
|
|
568
|
-
|
|
596
|
+
|
|
569
597
|
let context;
|
|
570
598
|
try {
|
|
571
599
|
const contextContent = fs.readFileSync(contextFile, 'utf8');
|
|
@@ -574,13 +602,13 @@ program
|
|
|
574
602
|
console.error(chalk.red(`❌ Failed to parse context file: ${parseError.message}`));
|
|
575
603
|
process.exit(1);
|
|
576
604
|
}
|
|
577
|
-
|
|
605
|
+
|
|
578
606
|
console.log(chalk.green(`✅ Loaded context from: ${contextFile}`));
|
|
579
|
-
|
|
607
|
+
|
|
580
608
|
// Load invariants
|
|
581
609
|
const invariantsFile = options.invariantsFile || path.join(targetDir, '.arcvision', 'invariants.json');
|
|
582
610
|
let loadSuccess = false;
|
|
583
|
-
|
|
611
|
+
|
|
584
612
|
if (fs.existsSync(invariantsFile)) {
|
|
585
613
|
loadSuccess = invariantRegistry.loadFromFile(invariantsFile);
|
|
586
614
|
if (loadSuccess) {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const { evaluateChange } = require('./src/core/change-evaluator');
|
|
2
|
+
|
|
3
|
+
// Create a test that should definitely trigger a BLOCK
|
|
4
|
+
const mockContext = {
|
|
5
|
+
nodes: [
|
|
6
|
+
{
|
|
7
|
+
path: 'src/ui/user-profile.js',
|
|
8
|
+
content: 'import { fetchData } from "../core/data-service";'
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const testInvariant = {
|
|
14
|
+
id: 'block-all-ui-files',
|
|
15
|
+
system: 'test',
|
|
16
|
+
description: 'Block all UI files for testing',
|
|
17
|
+
severity: 'block',
|
|
18
|
+
scope: {
|
|
19
|
+
files: ['**/ui/**/*']
|
|
20
|
+
},
|
|
21
|
+
rule: {
|
|
22
|
+
type: 'existence',
|
|
23
|
+
condition: {
|
|
24
|
+
mustNotExist: 'src/ui/user-profile.js'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const result = evaluateChange({
|
|
30
|
+
changedFiles: ['src/ui/user-profile.js'],
|
|
31
|
+
dependencyGraph: mockContext,
|
|
32
|
+
invariants: [testInvariant],
|
|
33
|
+
context: {}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
console.log('Test result:', result.decision);
|
|
37
|
+
console.log('Violations:', result.violations.length);
|
|
38
|
+
if (result.violations.length > 0) {
|
|
39
|
+
console.log('Violation details:', result.violations[0].description);
|
|
40
|
+
}
|