arcvision 0.2.20 ā 0.2.21
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 +2 -0
- package/package.json +1 -1
- package/src/arcvision-guard.js +17 -17
- package/src/core/artifact-manager.js +4 -16
- package/src/core/config-validator.js +4 -11
- package/src/core/context_builder.js +3 -2
- package/src/core/feature-manager.js +19 -5
- package/src/core/invariant-detector.js +69 -51
- package/src/core/scanner.js +2 -1
- package/src/index.js +39 -9
|
@@ -3,3 +3,5 @@
|
|
|
3
3
|
{"timestamp":"2026-01-25T02:04:57.719Z","error":{"category":"UNKNOWN","message":"Invalid ledger structure","code":null,"type":"Error"},"context":{"operation":"evaluate","directory":"C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\test-dev-project"},"stack":"Error: Invalid ledger structure\n at AuthorityLedger.readLedger (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\core\\authority-ledger.js:203:15)\n at AuthorityLedger.appendEvent (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\core\\authority-ledger.js:176:27)\n at AuthorityLedger.recordBlocked (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\core\\authority-ledger.js:81:10)\n at Command.<anonymous> (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\index.js:662:35)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)"}
|
|
4
4
|
{"timestamp":"2026-01-25T02:05:38.566Z","error":{"category":"UNKNOWN","message":"Invalid ledger structure","code":null,"type":"Error"},"context":{"operation":"evaluate","directory":"C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\test-dev-project"},"stack":"Error: Invalid ledger structure\n at AuthorityLedger.readLedger (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\core\\authority-ledger.js:203:15)\n at AuthorityLedger.appendEvent (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\core\\authority-ledger.js:176:27)\n at AuthorityLedger.recordBlocked (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\core\\authority-ledger.js:81:10)\n at Command.<anonymous> (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\index.js:662:35)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)"}
|
|
5
5
|
{"timestamp":"2026-01-25T03:16:17.596Z","error":{"category":"CONFIGURATION","message":"Invalid or revoked token","code":null,"type":"Error"},"context":{"operation":"standard_upload"},"stack":"Error: Invalid or revoked token\n at C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\index.js:330:19\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async RetryHandler.executeWithRetry (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\core\\retry-handler.js:23:24)\n at async uploadToDatabase (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\index.js:294:24)\n at async Command.<anonymous> (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\index.js:462:9)"}
|
|
6
|
+
{"timestamp":"2026-01-25T10:28:14.750Z","error":{"category":"UNKNOWN","message":"Failed to initiate upload session","code":null,"type":"Error"},"context":{"operation":"chunked_upload"},"stack":"Error: Failed to initiate upload session\n at ChunkedUploader.uploadInChunks (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\core\\chunked-uploader.js:58:13)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\index.js:177:18\n at async RetryHandler.executeWithRetry (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\core\\retry-handler.js:23:24)\n at async uploadToDatabase (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\index.js:176:24)\n at async Command.<anonymous> (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\index.js:473:9)"}
|
|
7
|
+
{"timestamp":"2026-01-25T14:09:23.628Z","error":{"category":"CONFIGURATION","message":"Invalid or revoked token","code":null,"type":"Error"},"context":{"operation":"standard_upload"},"stack":"Error: Invalid or revoked token\n at C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\index.js:341:19\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async RetryHandler.executeWithRetry (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\core\\retry-handler.js:23:24)\n at async uploadToDatabase (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\index.js:294:24)\n at async Command.<anonymous> (C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\index.js:473:9)"}
|
package/package.json
CHANGED
package/src/arcvision-guard.js
CHANGED
|
@@ -206,11 +206,17 @@ program
|
|
|
206
206
|
fs.mkdirSync(tokensDir, { recursive: true });
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
//
|
|
210
|
-
const
|
|
211
|
-
if (fs.existsSync(
|
|
212
|
-
|
|
213
|
-
|
|
209
|
+
// Create default configuration (no longer using separate invariants file as they're stored in context)
|
|
210
|
+
const configPath = path.join(arcDir, 'config.json');
|
|
211
|
+
if (!fs.existsSync(configPath)) {
|
|
212
|
+
const defaultConfig = {
|
|
213
|
+
guard_rules: {
|
|
214
|
+
blast_radius: {
|
|
215
|
+
critical_threshold: 50
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
|
|
214
220
|
console.log(chalk.green('ā
Configuration files created'));
|
|
215
221
|
}
|
|
216
222
|
|
|
@@ -223,7 +229,7 @@ program
|
|
|
223
229
|
console.log(chalk.green('\nš ArcVision Impact Guard initialized successfully!'));
|
|
224
230
|
console.log(chalk.yellow('\nNext steps:'));
|
|
225
231
|
console.log(chalk.yellow('1. Review .arcvision/config.json for project settings'));
|
|
226
|
-
console.log(chalk.yellow('2.
|
|
232
|
+
console.log(chalk.yellow('2. Auto-detect architectural invariants by running: arcvision scan'));
|
|
227
233
|
console.log(chalk.yellow('3. Run: arcvision-guard check'));
|
|
228
234
|
console.log(chalk.yellow('4. For critical changes: arcvision-guard bypass-request --reason "your reason"'));
|
|
229
235
|
});
|
|
@@ -246,17 +252,11 @@ program
|
|
|
246
252
|
remediation: "Define appropriate remediation steps"
|
|
247
253
|
};
|
|
248
254
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const invariants = JSON.parse(fs.readFileSync(invariantsPath, 'utf8'));
|
|
256
|
-
invariants.project_specific_invariants.push(invariant);
|
|
257
|
-
|
|
258
|
-
fs.writeFileSync(invariantsPath, JSON.stringify(invariants, null, 2));
|
|
259
|
-
console.log(chalk.green(`ā
Added invariant: ${name}`));
|
|
255
|
+
// Note: Invariants are now automatically detected by scanning and stored in arcvision.context.json
|
|
256
|
+
console.log(chalk.red('ā Custom invariants are now managed differently.'));
|
|
257
|
+
console.log(chalk.yellow('š” Run "arcvision scan" to auto-detect invariants from your codebase.'));
|
|
258
|
+
console.log(chalk.yellow('š” Or manually edit the invariants section in arcvision_context/arcvision.context.json if needed.'));
|
|
259
|
+
process.exit(1);
|
|
260
260
|
});
|
|
261
261
|
|
|
262
262
|
// Helper functions
|
|
@@ -4,11 +4,12 @@ const path = require('path');
|
|
|
4
4
|
class ArtifactManager {
|
|
5
5
|
constructor(projectRoot) {
|
|
6
6
|
this.projectRoot = projectRoot;
|
|
7
|
-
|
|
7
|
+
// Remove .arcvision directory as invariants should be in main context file only
|
|
8
|
+
this.requiredDirs = ['arcvision_context'];
|
|
8
9
|
this.requiredFiles = {
|
|
9
10
|
context: 'arcvision_context/arcvision.context.json',
|
|
10
|
-
ledger: 'arcvision_context/architecture.authority.ledger.json'
|
|
11
|
-
invariants
|
|
11
|
+
ledger: 'arcvision_context/architecture.authority.ledger.json'
|
|
12
|
+
// Remove invariants from separate file as they should be in main context per schema
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -43,19 +44,6 @@ class ArtifactManager {
|
|
|
43
44
|
* Creates missing files with default content
|
|
44
45
|
*/
|
|
45
46
|
createMissingFiles() {
|
|
46
|
-
// Create default invariants file if missing
|
|
47
|
-
const invariantsPath = path.join(this.projectRoot, this.requiredFiles.invariants);
|
|
48
|
-
if (!fs.existsSync(invariantsPath)) {
|
|
49
|
-
const defaultInvariants = {
|
|
50
|
-
version: '1.0',
|
|
51
|
-
project_specific_invariants: [],
|
|
52
|
-
generated_at: new Date().toISOString()
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
fs.writeFileSync(invariantsPath, JSON.stringify(defaultInvariants, null, 2));
|
|
56
|
-
this.logCreation(this.requiredFiles.invariants);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
47
|
// Create default ledger if missing
|
|
60
48
|
const ledgerPath = path.join(this.projectRoot, this.requiredFiles.ledger);
|
|
61
49
|
if (!fs.existsSync(ledgerPath)) {
|
|
@@ -7,13 +7,13 @@ class ConfigValidator {
|
|
|
7
7
|
*/
|
|
8
8
|
static validateProjectStructure(projectPath) {
|
|
9
9
|
const requiredPaths = [
|
|
10
|
-
path.join(projectPath, 'arcvision_context')
|
|
11
|
-
|
|
10
|
+
path.join(projectPath, 'arcvision_context')
|
|
11
|
+
// Removed .arcvision as invariants are now stored in main context file
|
|
12
12
|
];
|
|
13
13
|
|
|
14
14
|
const optionalPaths = [
|
|
15
15
|
path.join(projectPath, 'arcvision_context', 'arcvision.context.json'),
|
|
16
|
-
|
|
16
|
+
// Removed .arcvision/invariants.json as invariants are now stored in main context file
|
|
17
17
|
path.join(projectPath, 'arcvision_context', 'architecture.authority.ledger.json')
|
|
18
18
|
];
|
|
19
19
|
|
|
@@ -174,14 +174,7 @@ class ConfigValidator {
|
|
|
174
174
|
isValid: projectValidation.isValid
|
|
175
175
|
};
|
|
176
176
|
|
|
177
|
-
//
|
|
178
|
-
const invariantsPath = path.join(projectPath, '.arcvision', 'invariants.json');
|
|
179
|
-
if (fs.existsSync(invariantsPath)) {
|
|
180
|
-
configValidation.invariantsFile = this.validateInvariantsFile(invariantsPath);
|
|
181
|
-
if (!configValidation.invariantsFile.isValid) {
|
|
182
|
-
configValidation.isValid = false;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
177
|
+
// No longer validating separate invariants file as they are stored in main context file
|
|
185
178
|
|
|
186
179
|
// Validate context file if it exists
|
|
187
180
|
const contextPath = path.join(projectPath, 'arcvision_context', 'arcvision.context.json');
|
|
@@ -190,8 +190,9 @@ function buildContext(fileNodes, edges, symbols, options = {}) {
|
|
|
190
190
|
structural_hubs: options.structuralHubs || [],
|
|
191
191
|
architectural_boundaries: options.architecturalBoundaries || {},
|
|
192
192
|
structural_invariants: options.structuralInvariants || [],
|
|
193
|
-
// Include detected invariants from the current scan
|
|
194
|
-
invariants: options.
|
|
193
|
+
// Include detected invariants from the current scan - these should conform to the schema specification
|
|
194
|
+
invariants: Array.isArray(options.autoDetectedInvariants) ? options.autoDetectedInvariants :
|
|
195
|
+
Array.isArray(options.detectedInvariants) ? options.detectedInvariants : [],
|
|
195
196
|
// Include invariant analysis results
|
|
196
197
|
invariant_analysis: options.invariantAnalysis || null,
|
|
197
198
|
// Include architectural health assessment
|
|
@@ -30,9 +30,23 @@ class FeatureManager {
|
|
|
30
30
|
* Checks for the presence of key artifacts
|
|
31
31
|
*/
|
|
32
32
|
static checkArtifacts(projectPath) {
|
|
33
|
+
// Read the context file to check if it contains invariants
|
|
34
|
+
const contextPath = path.join(projectPath, 'arcvision_context', 'arcvision.context.json');
|
|
35
|
+
let invariantsExists = false;
|
|
36
|
+
|
|
37
|
+
if (fs.existsSync(contextPath)) {
|
|
38
|
+
try {
|
|
39
|
+
const contextData = JSON.parse(fs.readFileSync(contextPath, 'utf8'));
|
|
40
|
+
invariantsExists = Array.isArray(contextData.invariants) && contextData.invariants.length > 0;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
// If there's an error parsing, assume no invariants
|
|
43
|
+
invariantsExists = false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
return {
|
|
34
|
-
context_exists: fs.existsSync(
|
|
35
|
-
invariants_exists:
|
|
48
|
+
context_exists: fs.existsSync(contextPath),
|
|
49
|
+
invariants_exists: invariantsExists,
|
|
36
50
|
ledger_exists: fs.existsSync(path.join(projectPath, 'arcvision_context', 'architecture.authority.ledger.json')),
|
|
37
51
|
readme_exists: fs.existsSync(path.join(projectPath, 'arcvision_context', 'README.md')),
|
|
38
52
|
arcvision_dir_exists: fs.existsSync(path.join(projectPath, '.arcvision')),
|
|
@@ -51,9 +65,9 @@ class FeatureManager {
|
|
|
51
65
|
recommendations.push({
|
|
52
66
|
type: 'SETUP',
|
|
53
67
|
priority: 'HIGH',
|
|
54
|
-
message: '
|
|
55
|
-
action: 'arcvision
|
|
56
|
-
description: '
|
|
68
|
+
message: 'Auto-detect architectural invariants for governance',
|
|
69
|
+
action: 'arcvision scan',
|
|
70
|
+
description: 'Re-scan your project to auto-detect architectural invariants'
|
|
57
71
|
});
|
|
58
72
|
}
|
|
59
73
|
|
|
@@ -61,16 +61,18 @@ class InvariantDetector {
|
|
|
61
61
|
system: 'automatic',
|
|
62
62
|
statement: invariant.description,
|
|
63
63
|
description: invariant.description,
|
|
64
|
-
severity: invariant.isCritical ? 'block' : 'risk',
|
|
65
64
|
scope: { files: [nodeId] },
|
|
66
65
|
critical_path: invariant.isCritical,
|
|
67
66
|
assertion: `Pattern: ${invariant.pattern}`,
|
|
67
|
+
status: 'suspected',
|
|
68
|
+
confidence: this.calculateConfidenceScore(invariant.pattern, nodeId, content),
|
|
69
|
+
owner: this.determineOwnerFromPath(nodeId),
|
|
70
|
+
violated_by: [],
|
|
71
|
+
severity: invariant.isCritical ? 'block' : 'risk',
|
|
68
72
|
rule: {
|
|
69
73
|
type: 'pattern',
|
|
70
74
|
condition: { pattern: invariant.pattern }
|
|
71
75
|
},
|
|
72
|
-
status: 'suspected',
|
|
73
|
-
confidence: this.calculateConfidenceScore(invariant.pattern, nodeId, content),
|
|
74
76
|
source_file: nodeId,
|
|
75
77
|
detected_at: new Date().toISOString()
|
|
76
78
|
});
|
|
@@ -96,10 +98,14 @@ class InvariantDetector {
|
|
|
96
98
|
system: 'automatic',
|
|
97
99
|
statement: constraint.description,
|
|
98
100
|
description: constraint.description,
|
|
99
|
-
severity: constraint.critical ? 'block' : 'risk',
|
|
100
101
|
scope: { files: [constraint.from, constraint.to] },
|
|
101
102
|
critical_path: constraint.critical,
|
|
102
103
|
assertion: `Dependency constraint: ${constraint.from} -> ${constraint.to}`,
|
|
104
|
+
status: 'suspected',
|
|
105
|
+
confidence: this.calculateConfidenceScore('dependency_constraint', constraint.from, ''),
|
|
106
|
+
owner: this.determineOwnerFromPath(constraint.from),
|
|
107
|
+
violated_by: [],
|
|
108
|
+
severity: constraint.critical ? 'block' : 'risk',
|
|
103
109
|
rule: {
|
|
104
110
|
type: 'dependency',
|
|
105
111
|
condition: {
|
|
@@ -109,8 +115,6 @@ class InvariantDetector {
|
|
|
109
115
|
}
|
|
110
116
|
}
|
|
111
117
|
},
|
|
112
|
-
status: 'suspected',
|
|
113
|
-
confidence: this.calculateConfidenceScore('dependency_constraint', constraint.from, ''),
|
|
114
118
|
source_relationship: `${constraint.from} -> ${constraint.to}`,
|
|
115
119
|
detected_at: new Date().toISOString()
|
|
116
120
|
});
|
|
@@ -137,16 +141,18 @@ class InvariantDetector {
|
|
|
137
141
|
system: 'automatic',
|
|
138
142
|
statement: `System invariant implemented in ${relativePath}: ${pattern.description}`,
|
|
139
143
|
description: `System invariant implemented in ${relativePath}: ${pattern.description}`,
|
|
140
|
-
severity: 'risk',
|
|
141
144
|
scope: { files: [relativePath] },
|
|
142
145
|
critical_path: true, // File specifically for invariants is likely critical
|
|
143
146
|
assertion: pattern.assertion,
|
|
147
|
+
status: 'suspected',
|
|
148
|
+
confidence: this.calculateConfidenceScore(pattern.type, relativePath, content),
|
|
149
|
+
owner: this.determineOwnerFromPath(relativePath),
|
|
150
|
+
violated_by: [],
|
|
151
|
+
severity: 'risk',
|
|
144
152
|
rule: {
|
|
145
153
|
type: 'pattern',
|
|
146
154
|
condition: { pattern: pattern.type }
|
|
147
155
|
},
|
|
148
|
-
status: 'suspected',
|
|
149
|
-
confidence: this.calculateConfidenceScore(pattern.type, relativePath, content),
|
|
150
156
|
source_file: relativePath,
|
|
151
157
|
implementation_type: pattern.type,
|
|
152
158
|
detected_at: new Date().toISOString()
|
|
@@ -664,10 +670,14 @@ class InvariantDetector {
|
|
|
664
670
|
system: 'architectural',
|
|
665
671
|
statement: `Layer dependency rule: ${fromLayer} components should not depend on ${toLayer} components`,
|
|
666
672
|
description: `Prevent ${fromLayer} layer from depending on ${toLayer} layer to maintain separation of concerns`,
|
|
667
|
-
severity: 'block',
|
|
668
673
|
scope: { files: dependencies.map(d => d.from) },
|
|
669
674
|
critical_path: true,
|
|
670
675
|
assertion: `Layer dependency rule: ${fromLayer} -> ${toLayer} is forbidden`,
|
|
676
|
+
status: 'detected',
|
|
677
|
+
confidence: 0.9,
|
|
678
|
+
owner: 'architectural-analyzer',
|
|
679
|
+
violated_by: [],
|
|
680
|
+
severity: 'block',
|
|
671
681
|
rule: {
|
|
672
682
|
type: 'dependency',
|
|
673
683
|
condition: {
|
|
@@ -677,8 +687,6 @@ class InvariantDetector {
|
|
|
677
687
|
}
|
|
678
688
|
}
|
|
679
689
|
},
|
|
680
|
-
status: 'detected',
|
|
681
|
-
confidence: 0.9,
|
|
682
690
|
detected_at: new Date().toISOString()
|
|
683
691
|
});
|
|
684
692
|
}
|
|
@@ -707,26 +715,31 @@ class InvariantDetector {
|
|
|
707
715
|
|
|
708
716
|
// Identify potential architectural bottlenecks
|
|
709
717
|
for (const [nodeId, degrees] of nodeDegrees.entries()) {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
718
|
+
// Make sure nodeId is valid before creating invariant
|
|
719
|
+
if (nodeId && nodeId !== 'undefined' && nodeId !== 'null' && nodeId !== 'unknown' && degrees.incoming > 0) {
|
|
720
|
+
if (degrees.incoming > 10) { // High incoming degree indicates hub
|
|
721
|
+
this.detectedInvariants.push({
|
|
722
|
+
id: this.generateInvariantId(`hub_protection_${nodeId}`, 'high_degree'),
|
|
723
|
+
system: 'architectural',
|
|
724
|
+
statement: `Protect high-degree node ${nodeId} from unauthorized modifications`,
|
|
725
|
+
description: `Node ${nodeId} has ${degrees.incoming} incoming dependencies, changes here affect many components`,
|
|
726
|
+
scope: { files: [nodeId] },
|
|
727
|
+
critical_path: true,
|
|
728
|
+
assertion: `High-degree node protection: ${nodeId} has ${degrees.incoming} dependencies`,
|
|
729
|
+
status: 'detected',
|
|
730
|
+
confidence: 0.8,
|
|
731
|
+
owner: this.determineOwnerFromPath(nodeId),
|
|
732
|
+
violated_by: [],
|
|
733
|
+
severity: 'risk',
|
|
734
|
+
rule: {
|
|
735
|
+
type: 'access_control',
|
|
736
|
+
condition: {
|
|
737
|
+
mustBeReviewed: true
|
|
738
|
+
}
|
|
739
|
+
},
|
|
740
|
+
detected_at: new Date().toISOString()
|
|
741
|
+
});
|
|
742
|
+
}
|
|
730
743
|
}
|
|
731
744
|
}
|
|
732
745
|
}
|
|
@@ -755,25 +768,30 @@ class InvariantDetector {
|
|
|
755
768
|
for (const [coupleKey, edgesList] of couplingMap.entries()) {
|
|
756
769
|
const [nodeA, nodeB] = coupleKey.split('|');
|
|
757
770
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
771
|
+
// Only create the invariant if both node names are valid and non-empty
|
|
772
|
+
if (nodeA && nodeA.trim() !== '' && nodeB && nodeB.trim() !== '') {
|
|
773
|
+
this.detectedInvariants.push({
|
|
774
|
+
id: this.generateInvariantId(`tight_coupling_${nodeA}_${nodeB}`, 'bidirectional'),
|
|
775
|
+
system: 'architectural',
|
|
776
|
+
statement: `Prevent tight coupling between ${nodeA} and ${nodeB}`,
|
|
777
|
+
description: `Bidirectional dependency detected between ${nodeA} and ${nodeB}, indicating tight coupling`,
|
|
778
|
+
scope: { files: [nodeA, nodeB] },
|
|
779
|
+
critical_path: false,
|
|
780
|
+
assertion: `Bidirectional dependency: ${nodeA} <-> ${nodeB}`,
|
|
781
|
+
status: 'detected',
|
|
782
|
+
confidence: 0.7,
|
|
783
|
+
owner: 'architectural-analyzer',
|
|
784
|
+
violated_by: [],
|
|
785
|
+
severity: 'risk',
|
|
786
|
+
rule: {
|
|
787
|
+
type: 'dependency',
|
|
788
|
+
condition: {
|
|
789
|
+
shouldAvoidBidirectional: true
|
|
790
|
+
}
|
|
791
|
+
},
|
|
792
|
+
detected_at: new Date().toISOString()
|
|
793
|
+
});
|
|
794
|
+
}
|
|
777
795
|
}
|
|
778
796
|
}
|
|
779
797
|
}
|
package/src/core/scanner.js
CHANGED
|
@@ -195,8 +195,9 @@ async function scan(directory) {
|
|
|
195
195
|
architecturalBoundaries,
|
|
196
196
|
structuralInvariants,
|
|
197
197
|
authoritativeContext,
|
|
198
|
-
// Include detected invariants
|
|
198
|
+
// Include detected invariants - this will be used in context_builder
|
|
199
199
|
autoDetectedInvariants: detectedInvariants,
|
|
200
|
+
detectedInvariants: detectedInvariants,
|
|
200
201
|
// Include invariant analysis results
|
|
201
202
|
invariantAnalysis,
|
|
202
203
|
// Include architectural health assessment
|
package/src/index.js
CHANGED
|
@@ -605,21 +605,48 @@ program
|
|
|
605
605
|
|
|
606
606
|
console.log(chalk.green(`ā
Loaded context from: ${contextFile}`));
|
|
607
607
|
|
|
608
|
-
// Load invariants
|
|
608
|
+
// Load invariants from both sources for backward compatibility
|
|
609
609
|
const invariantsFile = options.invariantsFile || path.join(targetDir, '.arcvision', 'invariants.json');
|
|
610
610
|
let loadSuccess = false;
|
|
611
611
|
|
|
612
|
+
// First, try to load auto-detected invariants from the main context file
|
|
613
|
+
const contextFilePath = options.contextFile || path.join(targetDir, 'arcvision_context', 'arcvision.context.json');
|
|
614
|
+
if (fs.existsSync(contextFilePath)) {
|
|
615
|
+
try {
|
|
616
|
+
const contextContent = fs.readFileSync(contextFilePath, 'utf8');
|
|
617
|
+
const contextData = JSON.parse(contextContent);
|
|
618
|
+
|
|
619
|
+
// Load auto-detected invariants from the context file if they exist
|
|
620
|
+
if (contextData.invariants && Array.isArray(contextData.invariants) && contextData.invariants.length > 0) {
|
|
621
|
+
console.log(chalk.blue(`Loading ${contextData.invariants.length} auto-detected invariants from context file...`));
|
|
622
|
+
|
|
623
|
+
// Load each invariant individually to the registry
|
|
624
|
+
for (const invariant of contextData.invariants) {
|
|
625
|
+
invariantRegistry.register(invariant);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
console.log(chalk.green(`ā
Loaded ${contextData.invariants.length} auto-detected invariants from context file`));
|
|
629
|
+
loadSuccess = true; // Mark as successful if we loaded from context
|
|
630
|
+
} else {
|
|
631
|
+
console.log(chalk.yellow('No auto-detected invariants found in context file'));
|
|
632
|
+
}
|
|
633
|
+
} catch (contextError) {
|
|
634
|
+
console.log(chalk.yellow(`ā ļø Could not load invariants from context file: ${contextError.message}`));
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Then, try to load project-specific invariants from the separate file (for backward compatibility)
|
|
612
639
|
if (fs.existsSync(invariantsFile)) {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
640
|
+
// We'll load the separate file to supplement the context file invariants
|
|
641
|
+
const fileLoadSuccess = invariantRegistry.loadFromFile(invariantsFile);
|
|
642
|
+
if (fileLoadSuccess) {
|
|
643
|
+
console.log(chalk.green(`ā
Loaded project-specific invariants from: ${invariantsFile}`));
|
|
644
|
+
loadSuccess = true;
|
|
616
645
|
} else {
|
|
617
|
-
console.log(chalk.yellow(`ā ļø Failed to load invariants from: ${invariantsFile}`));
|
|
618
|
-
console.log(chalk.yellow('Using default invariants...'));
|
|
646
|
+
console.log(chalk.yellow(`ā ļø Failed to load project-specific invariants from: ${invariantsFile}`));
|
|
619
647
|
}
|
|
620
648
|
} else {
|
|
621
|
-
console.log(chalk.yellow(`ā ļø
|
|
622
|
-
console.log(chalk.yellow('Using default invariants...'));
|
|
649
|
+
console.log(chalk.yellow(`ā ļø No project-specific invariants file found: ${invariantsFile}`));
|
|
623
650
|
}
|
|
624
651
|
|
|
625
652
|
// If invariants couldn't be loaded or file doesn't exist, use defaults
|
|
@@ -808,6 +835,9 @@ program
|
|
|
808
835
|
.option('--export <format>', 'Export ledger (json|csv)')
|
|
809
836
|
.action(async (options) => {
|
|
810
837
|
try {
|
|
838
|
+
// Initialize Authority Ledger with default path
|
|
839
|
+
const authorityLedger = new AuthorityLedger(path.join(process.cwd(), 'arcvision_context', 'architecture.authority.ledger.json'));
|
|
840
|
+
|
|
811
841
|
if (options.stats) {
|
|
812
842
|
const stats = authorityLedger.getStats();
|
|
813
843
|
console.log(chalk.blue('š Authority Ledger Statistics'));
|
|
@@ -845,7 +875,7 @@ program
|
|
|
845
875
|
}
|
|
846
876
|
|
|
847
877
|
// Default: show ledger info
|
|
848
|
-
console.log(chalk.blue('Authority Ledger Location: ./architecture.authority.ledger.json'));
|
|
878
|
+
console.log(chalk.blue('Authority Ledger Location: ./arcvision_context/architecture.authority.ledger.json'));
|
|
849
879
|
console.log(chalk.yellow('Use --stats, --recent-blocks, or --export for detailed information'));
|
|
850
880
|
process.exit(0);
|
|
851
881
|
|