arcvision 0.2.25 → 0.2.27
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/README.md +59 -0
- package/package.json +1 -1
- package/src/core/authority-ledger.js +18 -20
- package/src/core/change-evaluator.js +56 -53
- package/src/core/cli-validator.js +59 -61
- package/src/core/invariant-detector.js +123 -103
- package/src/core/override-handler.js +28 -23
- package/src/core/scanner.js +114 -74
- package/src/engine/pass4_signals.js +12 -40
- package/src/index.js +38 -91
|
@@ -16,7 +16,7 @@ class CLIValidator {
|
|
|
16
16
|
if (!fs.existsSync(filePath)) return { valid: false, error: `File not found: ${filePath}` };
|
|
17
17
|
return { valid: true };
|
|
18
18
|
},
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
fileReadable: (filePath) => {
|
|
21
21
|
try {
|
|
22
22
|
fs.accessSync(filePath, fs.constants.R_OK);
|
|
@@ -25,7 +25,7 @@ class CLIValidator {
|
|
|
25
25
|
return { valid: false, error: `Permission denied: Cannot read ${filePath}` };
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
fileWritable: (filePath) => {
|
|
30
30
|
try {
|
|
31
31
|
const dir = path.dirname(filePath);
|
|
@@ -35,7 +35,7 @@ class CLIValidator {
|
|
|
35
35
|
return { valid: false, error: `Permission denied: Cannot write to ${filePath}` };
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
// JSON validations
|
|
40
40
|
validJSON: (content, source = 'input') => {
|
|
41
41
|
try {
|
|
@@ -45,7 +45,7 @@ class CLIValidator {
|
|
|
45
45
|
return { valid: false, error: `Invalid JSON in ${source}: ${error.message}` };
|
|
46
46
|
}
|
|
47
47
|
},
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
// Directory validations
|
|
50
50
|
directoryExists: (dirPath) => {
|
|
51
51
|
if (!dirPath) return { valid: false, error: 'Directory path is required' };
|
|
@@ -59,11 +59,11 @@ class CLIValidator {
|
|
|
59
59
|
return { valid: false, error: `Directory not accessible: ${dirPath}` };
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
// Context structure validations
|
|
64
64
|
validContextStructure: (context) => {
|
|
65
65
|
const errors = [];
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
// Required top-level fields
|
|
68
68
|
const requiredFields = ['nodes', 'edges', 'system'];
|
|
69
69
|
requiredFields.forEach(field => {
|
|
@@ -71,17 +71,17 @@ class CLIValidator {
|
|
|
71
71
|
errors.push(`Missing required field: ${field}`);
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
// Validate nodes array
|
|
76
76
|
if (context.nodes && !Array.isArray(context.nodes)) {
|
|
77
77
|
errors.push('nodes must be an array');
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
// Validate edges array
|
|
81
81
|
if (context.edges && !Array.isArray(context.edges)) {
|
|
82
82
|
errors.push('edges must be an array');
|
|
83
83
|
}
|
|
84
|
-
|
|
84
|
+
|
|
85
85
|
// Validate system object structure
|
|
86
86
|
if (context.system) {
|
|
87
87
|
if (typeof context.system !== 'object' || context.system === null) {
|
|
@@ -96,19 +96,19 @@ class CLIValidator {
|
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
-
|
|
99
|
+
|
|
100
100
|
return {
|
|
101
101
|
valid: errors.length === 0,
|
|
102
102
|
errors
|
|
103
103
|
};
|
|
104
104
|
},
|
|
105
|
-
|
|
105
|
+
|
|
106
106
|
// Invariant validations
|
|
107
107
|
validInvariants: (invariants) => {
|
|
108
108
|
if (!Array.isArray(invariants)) {
|
|
109
109
|
return { valid: false, error: 'Invariants must be an array' };
|
|
110
110
|
}
|
|
111
|
-
|
|
111
|
+
|
|
112
112
|
const errors = [];
|
|
113
113
|
invariants.forEach((invariant, index) => {
|
|
114
114
|
if (!invariant.id) {
|
|
@@ -130,7 +130,7 @@ class CLIValidator {
|
|
|
130
130
|
errors.push(`Invariant ${invariant.id || index}: Missing required 'rule' field`);
|
|
131
131
|
}
|
|
132
132
|
});
|
|
133
|
-
|
|
133
|
+
|
|
134
134
|
return {
|
|
135
135
|
valid: errors.length === 0,
|
|
136
136
|
errors
|
|
@@ -138,14 +138,14 @@ class CLIValidator {
|
|
|
138
138
|
}
|
|
139
139
|
};
|
|
140
140
|
}
|
|
141
|
-
|
|
141
|
+
|
|
142
142
|
/**
|
|
143
143
|
* Comprehensive validation for CLI operations
|
|
144
144
|
*/
|
|
145
145
|
validateOperation(operation, params) {
|
|
146
146
|
const errors = [];
|
|
147
147
|
const warnings = [];
|
|
148
|
-
|
|
148
|
+
|
|
149
149
|
switch (operation) {
|
|
150
150
|
case 'scan':
|
|
151
151
|
this.validateScan(params, errors, warnings);
|
|
@@ -162,7 +162,7 @@ class CLIValidator {
|
|
|
162
162
|
default:
|
|
163
163
|
errors.push(`Unknown operation: ${operation}`);
|
|
164
164
|
}
|
|
165
|
-
|
|
165
|
+
|
|
166
166
|
return {
|
|
167
167
|
valid: errors.length === 0,
|
|
168
168
|
errors,
|
|
@@ -170,33 +170,33 @@ class CLIValidator {
|
|
|
170
170
|
isValid: errors.length === 0
|
|
171
171
|
};
|
|
172
172
|
}
|
|
173
|
-
|
|
173
|
+
|
|
174
174
|
validateScan(params, errors, warnings) {
|
|
175
175
|
const { directory } = params;
|
|
176
|
-
|
|
176
|
+
|
|
177
177
|
// Validate directory
|
|
178
178
|
const dirValidation = this.validationRules.directoryExists(directory);
|
|
179
179
|
if (!dirValidation.valid) {
|
|
180
180
|
errors.push(dirValidation.error);
|
|
181
181
|
return; // No point continuing if directory doesn't exist
|
|
182
182
|
}
|
|
183
|
-
|
|
183
|
+
|
|
184
184
|
// Check for common issues
|
|
185
185
|
const commonIssues = this.checkCommonScanIssues(directory);
|
|
186
186
|
warnings.push(...commonIssues.warnings);
|
|
187
187
|
errors.push(...commonIssues.errors);
|
|
188
188
|
}
|
|
189
|
-
|
|
189
|
+
|
|
190
190
|
validateEvaluate(params, errors, warnings) {
|
|
191
191
|
const { contextFile, invariantsFile, directory } = params;
|
|
192
|
-
|
|
192
|
+
|
|
193
193
|
// Validate directory
|
|
194
194
|
const dirValidation = this.validationRules.directoryExists(directory);
|
|
195
195
|
if (!dirValidation.valid) {
|
|
196
196
|
errors.push(dirValidation.error);
|
|
197
197
|
return;
|
|
198
198
|
}
|
|
199
|
-
|
|
199
|
+
|
|
200
200
|
// Validate context file
|
|
201
201
|
if (contextFile) {
|
|
202
202
|
const ctxFileValidation = this.validationRules.fileExists(contextFile);
|
|
@@ -224,13 +224,11 @@ class CLIValidator {
|
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
|
-
|
|
227
|
+
|
|
228
228
|
// Validate invariants file if provided
|
|
229
229
|
if (invariantsFile) {
|
|
230
230
|
const invFileValidation = this.validationRules.fileExists(invariantsFile);
|
|
231
|
-
if (
|
|
232
|
-
warnings.push(`Invariants file not found: ${invariantsFile} (will use defaults)`);
|
|
233
|
-
} else {
|
|
231
|
+
if (invFileValidation.valid) {
|
|
234
232
|
const readable = this.validationRules.fileReadable(invariantsFile);
|
|
235
233
|
if (!readable.valid) {
|
|
236
234
|
warnings.push(`Cannot read invariants file: ${readable.error} (will use defaults)`);
|
|
@@ -254,17 +252,17 @@ class CLIValidator {
|
|
|
254
252
|
}
|
|
255
253
|
}
|
|
256
254
|
}
|
|
257
|
-
|
|
255
|
+
|
|
258
256
|
validateUpload(params, errors, warnings) {
|
|
259
257
|
const { contextData, token } = params;
|
|
260
|
-
|
|
258
|
+
|
|
261
259
|
// Validate token
|
|
262
260
|
if (!token) {
|
|
263
261
|
errors.push('Upload token is required. Run `arcvision link <TOKEN>` first.');
|
|
264
262
|
} else if (typeof token !== 'string' || token.length < 10) {
|
|
265
263
|
warnings.push('Upload token appears to be invalid or malformed.');
|
|
266
264
|
}
|
|
267
|
-
|
|
265
|
+
|
|
268
266
|
// Validate context data
|
|
269
267
|
if (!contextData) {
|
|
270
268
|
errors.push('Context data is required for upload');
|
|
@@ -273,7 +271,7 @@ class CLIValidator {
|
|
|
273
271
|
if (!structureValidation.valid) {
|
|
274
272
|
errors.push(...structureValidation.errors.map(e => `Context validation: ${e}`));
|
|
275
273
|
}
|
|
276
|
-
|
|
274
|
+
|
|
277
275
|
// Check data size
|
|
278
276
|
try {
|
|
279
277
|
const dataSize = Buffer.byteLength(JSON.stringify(contextData));
|
|
@@ -286,26 +284,26 @@ class CLIValidator {
|
|
|
286
284
|
}
|
|
287
285
|
}
|
|
288
286
|
}
|
|
289
|
-
|
|
287
|
+
|
|
290
288
|
validateDiff(params, errors, warnings) {
|
|
291
289
|
const { oldFile, newFile } = params;
|
|
292
|
-
|
|
290
|
+
|
|
293
291
|
// Validate both files exist and are readable
|
|
294
292
|
[oldFile, newFile].forEach((file, index) => {
|
|
295
293
|
const label = index === 0 ? 'Old file' : 'New file';
|
|
296
|
-
|
|
294
|
+
|
|
297
295
|
const fileValidation = this.validationRules.fileExists(file);
|
|
298
296
|
if (!fileValidation.valid) {
|
|
299
297
|
errors.push(`${label}: ${fileValidation.error}`);
|
|
300
298
|
return;
|
|
301
299
|
}
|
|
302
|
-
|
|
300
|
+
|
|
303
301
|
const readable = this.validationRules.fileReadable(file);
|
|
304
302
|
if (!readable.valid) {
|
|
305
303
|
errors.push(`${label}: ${readable.error}`);
|
|
306
304
|
return;
|
|
307
305
|
}
|
|
308
|
-
|
|
306
|
+
|
|
309
307
|
try {
|
|
310
308
|
const content = fs.readFileSync(file, 'utf8');
|
|
311
309
|
const jsonValidation = this.validationRules.validJSON(content, file);
|
|
@@ -321,13 +319,13 @@ class CLIValidator {
|
|
|
321
319
|
errors.push(`${label}: Failed to read file - ${error.message}`);
|
|
322
320
|
}
|
|
323
321
|
});
|
|
324
|
-
|
|
322
|
+
|
|
325
323
|
// Check if files are different
|
|
326
324
|
if (oldFile === newFile) {
|
|
327
325
|
warnings.push('Both files are the same. Diff will show no changes.');
|
|
328
326
|
}
|
|
329
327
|
}
|
|
330
|
-
|
|
328
|
+
|
|
331
329
|
/**
|
|
332
330
|
* Extract invariants from various JSON structures
|
|
333
331
|
*/
|
|
@@ -335,15 +333,15 @@ class CLIValidator {
|
|
|
335
333
|
if (Array.isArray(data)) {
|
|
336
334
|
return data;
|
|
337
335
|
}
|
|
338
|
-
|
|
336
|
+
|
|
339
337
|
if (data.project_specific_invariants && Array.isArray(data.project_specific_invariants)) {
|
|
340
338
|
return data.project_specific_invariants;
|
|
341
339
|
}
|
|
342
|
-
|
|
340
|
+
|
|
343
341
|
if (data.invariants && Array.isArray(data.invariants)) {
|
|
344
342
|
return data.invariants;
|
|
345
343
|
}
|
|
346
|
-
|
|
344
|
+
|
|
347
345
|
// Try to find invariants in any property
|
|
348
346
|
const keys = Object.keys(data);
|
|
349
347
|
for (const key of keys) {
|
|
@@ -351,54 +349,54 @@ class CLIValidator {
|
|
|
351
349
|
return data[key];
|
|
352
350
|
}
|
|
353
351
|
}
|
|
354
|
-
|
|
352
|
+
|
|
355
353
|
return [];
|
|
356
354
|
}
|
|
357
|
-
|
|
355
|
+
|
|
358
356
|
/**
|
|
359
357
|
* Check for common scan issues
|
|
360
358
|
*/
|
|
361
359
|
checkCommonScanIssues(directory) {
|
|
362
360
|
const warnings = [];
|
|
363
361
|
const errors = [];
|
|
364
|
-
|
|
362
|
+
|
|
365
363
|
try {
|
|
366
364
|
// Check for node_modules
|
|
367
365
|
const nodeModulesPath = path.join(directory, 'node_modules');
|
|
368
366
|
if (fs.existsSync(nodeModulesPath)) {
|
|
369
367
|
warnings.push('Found node_modules directory. Consider adding it to ignore patterns for better performance.');
|
|
370
368
|
}
|
|
371
|
-
|
|
369
|
+
|
|
372
370
|
// Check for large directories
|
|
373
371
|
const entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
374
372
|
const fileCount = entries.filter(entry => entry.isFile()).length;
|
|
375
373
|
const dirCount = entries.filter(entry => entry.isDirectory()).length;
|
|
376
|
-
|
|
374
|
+
|
|
377
375
|
if (fileCount > 1000) {
|
|
378
376
|
warnings.push(`Large number of files detected (${fileCount}). Scan may take longer than usual.`);
|
|
379
377
|
}
|
|
380
|
-
|
|
378
|
+
|
|
381
379
|
if (dirCount > 100) {
|
|
382
380
|
warnings.push(`Large number of directories detected (${dirCount}). Consider specifying a subdirectory.`);
|
|
383
381
|
}
|
|
384
|
-
|
|
382
|
+
|
|
385
383
|
// Check for common problematic files
|
|
386
384
|
const problematicExtensions = ['.log', '.tmp', '.cache'];
|
|
387
|
-
const hasProblematicFiles = entries.some(entry =>
|
|
385
|
+
const hasProblematicFiles = entries.some(entry =>
|
|
388
386
|
entry.isFile() && problematicExtensions.some(ext => entry.name.endsWith(ext))
|
|
389
387
|
);
|
|
390
|
-
|
|
388
|
+
|
|
391
389
|
if (hasProblematicFiles) {
|
|
392
390
|
warnings.push('Found potentially problematic files (.log, .tmp, .cache). These will be skipped during scan.');
|
|
393
391
|
}
|
|
394
|
-
|
|
392
|
+
|
|
395
393
|
} catch (error) {
|
|
396
394
|
errors.push(`Could not analyze directory structure: ${error.message}`);
|
|
397
395
|
}
|
|
398
|
-
|
|
396
|
+
|
|
399
397
|
return { warnings, errors };
|
|
400
398
|
}
|
|
401
|
-
|
|
399
|
+
|
|
402
400
|
/**
|
|
403
401
|
* Format validation results for display
|
|
404
402
|
*/
|
|
@@ -406,40 +404,40 @@ class CLIValidator {
|
|
|
406
404
|
if (results.isValid) {
|
|
407
405
|
return chalk.green('✅ All validations passed');
|
|
408
406
|
}
|
|
409
|
-
|
|
407
|
+
|
|
410
408
|
let output = '';
|
|
411
|
-
|
|
409
|
+
|
|
412
410
|
if (results.errors.length > 0) {
|
|
413
411
|
output += chalk.red('❌ Validation Errors:\n');
|
|
414
412
|
results.errors.forEach(error => {
|
|
415
413
|
output += ` • ${error}\n`;
|
|
416
414
|
});
|
|
417
415
|
}
|
|
418
|
-
|
|
416
|
+
|
|
419
417
|
if (results.warnings.length > 0) {
|
|
420
418
|
output += chalk.yellow('\n⚠️ Validation Warnings:\n');
|
|
421
419
|
results.warnings.forEach(warning => {
|
|
422
420
|
output += ` • ${warning}\n`;
|
|
423
421
|
});
|
|
424
422
|
}
|
|
425
|
-
|
|
423
|
+
|
|
426
424
|
return output;
|
|
427
425
|
}
|
|
428
|
-
|
|
426
|
+
|
|
429
427
|
/**
|
|
430
428
|
* Perform comprehensive pre-flight validation
|
|
431
429
|
*/
|
|
432
430
|
async preFlightValidation(operation, params) {
|
|
433
431
|
console.log(chalk.blue(`🔍 Pre-flight validation for ${operation} operation...`));
|
|
434
|
-
|
|
432
|
+
|
|
435
433
|
const validation = this.validateOperation(operation, params);
|
|
436
|
-
|
|
434
|
+
|
|
437
435
|
if (!validation.isValid) {
|
|
438
436
|
console.error(chalk.red('\n❌ Pre-flight validation failed:'));
|
|
439
437
|
console.error(this.formatValidationResults(validation));
|
|
440
438
|
process.exit(1);
|
|
441
439
|
}
|
|
442
|
-
|
|
440
|
+
|
|
443
441
|
if (validation.warnings.length > 0) {
|
|
444
442
|
console.warn(chalk.yellow('\n⚠️ Validation warnings:'));
|
|
445
443
|
validation.warnings.forEach(warning => {
|
|
@@ -447,7 +445,7 @@ class CLIValidator {
|
|
|
447
445
|
});
|
|
448
446
|
console.log(); // Add spacing
|
|
449
447
|
}
|
|
450
|
-
|
|
448
|
+
|
|
451
449
|
console.log(chalk.green('✅ Pre-flight validation passed\n'));
|
|
452
450
|
return true;
|
|
453
451
|
}
|