arcvision 0.2.27 → 0.2.29
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/package.json +2 -2
- package/src/arcvision_context/README.md +103 -0
- package/src/arcvision_context/architecture.authority.ledger.json +6 -0
- package/src/arcvision_context/arcvision.context.json +32907 -0
- package/src/core/artifact-manager.js +42 -13
- package/src/core/call-resolver.js +29 -24
- package/src/core/context_builder.js +52 -48
- package/src/core/failure-mode-synthesizer.js +80 -37
- package/src/core/invariant-detector.js +57 -12
- package/src/core/readme-generator.js +64 -11
- package/src/core/scanner.js +122 -25
- package/src/engine/context_builder.js +24 -15
- package/src/engine/pass5_intelligence.js +852 -0
- package/src/index.js +150 -57
|
@@ -33,9 +33,22 @@ class ArtifactManager {
|
|
|
33
33
|
createDirectories() {
|
|
34
34
|
this.requiredDirs.forEach(dir => {
|
|
35
35
|
const fullPath = path.join(this.projectRoot, dir);
|
|
36
|
-
|
|
37
|
-
fs.
|
|
38
|
-
|
|
36
|
+
try {
|
|
37
|
+
if (!fs.existsSync(fullPath)) {
|
|
38
|
+
fs.mkdirSync(fullPath, { recursive: true });
|
|
39
|
+
this.logCreation(dir);
|
|
40
|
+
// Verify directory was created
|
|
41
|
+
if (fs.existsSync(fullPath)) {
|
|
42
|
+
console.log(`✅ Verified directory creation: ${fullPath}`);
|
|
43
|
+
} else {
|
|
44
|
+
console.warn(`⚠️ Directory may not have been created: ${fullPath}`);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
console.log(`✅ Directory already exists: ${fullPath}`);
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(`❌ Failed to create directory ${fullPath}: ${error.message}`);
|
|
51
|
+
throw error;
|
|
39
52
|
}
|
|
40
53
|
});
|
|
41
54
|
}
|
|
@@ -46,16 +59,32 @@ class ArtifactManager {
|
|
|
46
59
|
createMissingFiles() {
|
|
47
60
|
// Create default ledger if missing
|
|
48
61
|
const ledgerPath = path.join(this.projectRoot, this.requiredFiles.ledger);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
try {
|
|
63
|
+
if (!fs.existsSync(ledgerPath)) {
|
|
64
|
+
console.log(`🔍 Creating ledger file: ${ledgerPath}`);
|
|
65
|
+
const defaultLedger = {
|
|
66
|
+
schema_version: '1.0',
|
|
67
|
+
system_id: path.basename(this.projectRoot),
|
|
68
|
+
created_at: new Date().toISOString(),
|
|
69
|
+
ledger: []
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
fs.writeFileSync(ledgerPath, JSON.stringify(defaultLedger, null, 2));
|
|
73
|
+
this.logCreation(this.requiredFiles.ledger);
|
|
74
|
+
|
|
75
|
+
// Verify file was created
|
|
76
|
+
if (fs.existsSync(ledgerPath)) {
|
|
77
|
+
const stats = fs.statSync(ledgerPath);
|
|
78
|
+
console.log(`✅ Verified ledger file creation: ${ledgerPath} (${stats.size} bytes)`);
|
|
79
|
+
} else {
|
|
80
|
+
console.warn(`⚠️ Ledger file may not have been created: ${ledgerPath}`);
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
console.log(`✅ Ledger file already exists: ${ledgerPath}`);
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error(`❌ Failed to create ledger file ${ledgerPath}: ${error.message}`);
|
|
87
|
+
throw error;
|
|
59
88
|
}
|
|
60
89
|
}
|
|
61
90
|
|
|
@@ -71,19 +71,21 @@ class CallResolver {
|
|
|
71
71
|
// Better: SymbolIndexer ensure 'default' is the name for default exports.
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
// Additional attempts for common patterns
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
74
|
+
// Additional attempts for common function-prefix patterns
|
|
75
|
+
// Guard: skip if targetSymbolName is falsy (e.g. undefined from a star import)
|
|
76
|
+
if (targetSymbolName && typeof targetSymbolName === 'string' && targetSymbolName.length > 0) {
|
|
77
|
+
const cap = targetSymbolName.charAt(0).toUpperCase() + targetSymbolName.slice(1);
|
|
78
|
+
const commonPatterns = [
|
|
79
|
+
`${targetFileId}::use${cap}`,
|
|
80
|
+
`${targetFileId}::get${cap}`,
|
|
81
|
+
`${targetFileId}::create${cap}`,
|
|
82
|
+
`${targetFileId}::_${targetSymbolName}`,
|
|
83
|
+
`${targetFileId}::${targetSymbolName}Impl`,
|
|
84
|
+
`${targetFileId}::${targetSymbolName}Handler`
|
|
85
|
+
];
|
|
86
|
+
for (const patternId of commonPatterns) {
|
|
87
|
+
if (this.symbolIndex.has(patternId)) return patternId;
|
|
88
|
+
}
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
91
|
}
|
|
@@ -97,17 +99,20 @@ class CallResolver {
|
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
// Additional local patterns
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
102
|
+
// Guard: skip if symbolName is falsy
|
|
103
|
+
if (symbolName && typeof symbolName === 'string' && symbolName.length > 0) {
|
|
104
|
+
const cap = symbolName.charAt(0).toUpperCase() + symbolName.slice(1);
|
|
105
|
+
const localPatterns = [
|
|
106
|
+
`${fileId}::use${cap}`,
|
|
107
|
+
`${fileId}::get${cap}`,
|
|
108
|
+
`${fileId}::create${cap}`,
|
|
109
|
+
`${fileId}::_${symbolName}`,
|
|
110
|
+
`${fileId}::${symbolName}Impl`,
|
|
111
|
+
`${fileId}::${symbolName}Handler`
|
|
112
|
+
];
|
|
113
|
+
for (const patternId of localPatterns) {
|
|
114
|
+
if (this.symbolIndex.has(patternId)) return patternId;
|
|
115
|
+
}
|
|
111
116
|
}
|
|
112
117
|
|
|
113
118
|
return null;
|
|
@@ -2,6 +2,7 @@ const path = require('path');
|
|
|
2
2
|
const { spawnSync } = require('child_process');
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const { stableId } = require('../engine/id-generator');
|
|
5
|
+
const { emptyIntelligenceBlock } = require('../engine/pass5_intelligence');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Build the context object that conforms to the Arcvision schema
|
|
@@ -40,7 +41,7 @@ function buildContext(fileNodes, edges, symbols, options = {}) {
|
|
|
40
41
|
if (file.intelligence && file.intelligence.connections) {
|
|
41
42
|
file.intelligence.connections.forEach(c => addDep(c.target));
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
+
|
|
44
45
|
// Add dependencies from metadata if intelligence is not available
|
|
45
46
|
if (!file.intelligence && file.metadata && file.metadata.imports) {
|
|
46
47
|
file.metadata.imports.forEach(imp => {
|
|
@@ -191,14 +192,17 @@ function buildContext(fileNodes, edges, symbols, options = {}) {
|
|
|
191
192
|
architectural_boundaries: options.architecturalBoundaries || {},
|
|
192
193
|
structural_invariants: options.structuralInvariants || [],
|
|
193
194
|
// Include detected invariants from the current scan - these should conform to the schema specification
|
|
194
|
-
invariants: Array.isArray(options.autoDetectedInvariants) && options.autoDetectedInvariants.length > 0 ? options.autoDetectedInvariants :
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
invariants: Array.isArray(options.autoDetectedInvariants) && options.autoDetectedInvariants.length > 0 ? options.autoDetectedInvariants :
|
|
196
|
+
Array.isArray(options.detectedInvariants) && options.detectedInvariants.length > 0 ? options.detectedInvariants :
|
|
197
|
+
[],
|
|
197
198
|
// Include invariant analysis results
|
|
198
199
|
invariant_analysis: options.invariantAnalysis || null,
|
|
199
200
|
// Include architectural health assessment
|
|
200
201
|
architectural_health: options.architecturalHealth || null,
|
|
201
|
-
authoritative_context: options.authoritativeContext || null
|
|
202
|
+
authoritative_context: options.authoritativeContext || null,
|
|
203
|
+
// Intelligence block — always populated, never null
|
|
204
|
+
// This is the canonical architecture intelligence layer from Pass 5.
|
|
205
|
+
intelligence: options.intelligence || emptyIntelligenceBlock()
|
|
202
206
|
};
|
|
203
207
|
|
|
204
208
|
// Phase 2 requirement: Artifact Hash (Trust Anchor)
|
|
@@ -206,7 +210,7 @@ function buildContext(fileNodes, edges, symbols, options = {}) {
|
|
|
206
210
|
const contextWithoutIntegrity = { ...context };
|
|
207
211
|
delete contextWithoutIntegrity.integrity;
|
|
208
212
|
const hash = crypto.createHash('sha256').update(JSON.stringify(contextWithoutIntegrity)).digest('hex');
|
|
209
|
-
|
|
213
|
+
|
|
210
214
|
// Now add the integrity field
|
|
211
215
|
context.integrity = {
|
|
212
216
|
sha256: hash
|
|
@@ -226,7 +230,7 @@ function getGitInfo() {
|
|
|
226
230
|
const originResult = spawnSync('git', ['remote', 'get-url', 'origin'], {
|
|
227
231
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
228
232
|
});
|
|
229
|
-
|
|
233
|
+
|
|
230
234
|
let repoUrl = 'unknown';
|
|
231
235
|
if (originResult.status === 0) {
|
|
232
236
|
let url = originResult.stdout.toString().trim();
|
|
@@ -239,17 +243,17 @@ function getGitInfo() {
|
|
|
239
243
|
}
|
|
240
244
|
repoUrl = url;
|
|
241
245
|
}
|
|
242
|
-
|
|
246
|
+
|
|
243
247
|
// Get the current commit hash
|
|
244
248
|
const commitResult = spawnSync('git', ['rev-parse', 'HEAD'], {
|
|
245
249
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
246
250
|
});
|
|
247
|
-
|
|
251
|
+
|
|
248
252
|
let commitHash = 'unknown';
|
|
249
253
|
if (commitResult.status === 0) {
|
|
250
254
|
commitHash = commitResult.stdout.toString().trim();
|
|
251
255
|
}
|
|
252
|
-
|
|
256
|
+
|
|
253
257
|
return {
|
|
254
258
|
repo: repoUrl,
|
|
255
259
|
commit: commitHash
|
|
@@ -274,7 +278,7 @@ function calculateIntegrityHash(obj) {
|
|
|
274
278
|
if (objCopy.integrity) {
|
|
275
279
|
delete objCopy.integrity;
|
|
276
280
|
}
|
|
277
|
-
|
|
281
|
+
|
|
278
282
|
const jsonString = JSON.stringify(objCopy);
|
|
279
283
|
return crypto.createHash('sha256').update(jsonString).digest('hex');
|
|
280
284
|
} catch (error) {
|
|
@@ -323,35 +327,35 @@ function generateStructuralLayers() {
|
|
|
323
327
|
function generateAdaptiveStructuralLayers(directory) {
|
|
324
328
|
const fs = require('fs');
|
|
325
329
|
const path = require('path');
|
|
326
|
-
|
|
330
|
+
|
|
327
331
|
// Analyze project characteristics to determine adaptive layers
|
|
328
332
|
const hasPackageJson = fs.existsSync(path.join(directory, 'package.json'));
|
|
329
|
-
const hasConfigFiles = fs.existsSync(path.join(directory, 'tsconfig.json')) ||
|
|
330
|
-
|
|
331
|
-
|
|
333
|
+
const hasConfigFiles = fs.existsSync(path.join(directory, 'tsconfig.json')) ||
|
|
334
|
+
fs.existsSync(path.join(directory, 'config')) ||
|
|
335
|
+
fs.existsSync(path.join(directory, 'webpack.config.js'));
|
|
332
336
|
const hasSrcDir = fs.existsSync(path.join(directory, 'src'));
|
|
333
337
|
const hasTestDir = fs.existsSync(path.join(directory, 'test')) || fs.existsSync(path.join(directory, 'tests'));
|
|
334
338
|
const hasDocsDir = fs.existsSync(path.join(directory, 'docs'));
|
|
335
|
-
|
|
339
|
+
|
|
336
340
|
// Count different file types to determine project size and type
|
|
337
341
|
let totalFiles = 0;
|
|
338
342
|
let jsFiles = 0;
|
|
339
343
|
let tsFiles = 0;
|
|
340
344
|
let luaFiles = 0;
|
|
341
345
|
let configFiles = 0;
|
|
342
|
-
|
|
346
|
+
|
|
343
347
|
function walk(dir, depth = 0) {
|
|
344
348
|
if (depth > 3) return; // Limit depth to avoid performance issues
|
|
345
|
-
|
|
349
|
+
|
|
346
350
|
if (!fs.existsSync(dir)) return;
|
|
347
|
-
|
|
351
|
+
|
|
348
352
|
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
349
|
-
|
|
353
|
+
|
|
350
354
|
for (const item of items) {
|
|
351
355
|
const fullPath = path.join(dir, item.name);
|
|
352
|
-
|
|
356
|
+
|
|
353
357
|
if (item.isDirectory()) {
|
|
354
|
-
if (!item.name.startsWith('.') && item.name !== 'node_modules' &&
|
|
358
|
+
if (!item.name.startsWith('.') && item.name !== 'node_modules' &&
|
|
355
359
|
item.name !== 'dist' && item.name !== 'build' && item.name !== '.git') {
|
|
356
360
|
walk(fullPath, depth + 1);
|
|
357
361
|
}
|
|
@@ -360,20 +364,20 @@ function generateAdaptiveStructuralLayers(directory) {
|
|
|
360
364
|
if (item.name.endsWith('.js')) jsFiles++;
|
|
361
365
|
if (item.name.endsWith('.ts')) tsFiles++;
|
|
362
366
|
if (item.name.endsWith('.lua')) luaFiles++;
|
|
363
|
-
if (['.json', '.yaml', '.yml', '.toml', '.config.js', '.config.ts', '.rc'].some(ext =>
|
|
367
|
+
if (['.json', '.yaml', '.yml', '.toml', '.config.js', '.config.ts', '.rc'].some(ext =>
|
|
364
368
|
item.name.endsWith(ext))) configFiles++;
|
|
365
369
|
}
|
|
366
370
|
}
|
|
367
371
|
}
|
|
368
|
-
|
|
372
|
+
|
|
369
373
|
walk(directory);
|
|
370
|
-
|
|
374
|
+
|
|
371
375
|
// Determine if this is a Lua-heavy project (like BullMQ)
|
|
372
376
|
const isLuaProject = luaFiles > 0 && (luaFiles / totalFiles) > 0.1; // More than 10% Lua files
|
|
373
|
-
|
|
377
|
+
|
|
374
378
|
// Determine project size
|
|
375
379
|
const isLargeProject = totalFiles > 1000;
|
|
376
|
-
|
|
380
|
+
|
|
377
381
|
// Adjust structural layers based on project characteristics
|
|
378
382
|
const layers = {
|
|
379
383
|
"runtime_code": {
|
|
@@ -413,7 +417,7 @@ function generateAdaptiveStructuralLayers(directory) {
|
|
|
413
417
|
"description": "Static assets with no effect on execution semantics"
|
|
414
418
|
}
|
|
415
419
|
};
|
|
416
|
-
|
|
420
|
+
|
|
417
421
|
// Add project-specific metadata
|
|
418
422
|
layers.project_metadata = {
|
|
419
423
|
"size_category": isLargeProject ? "large" : (totalFiles > 100 ? "medium" : "small"),
|
|
@@ -429,7 +433,7 @@ function generateAdaptiveStructuralLayers(directory) {
|
|
|
429
433
|
"has_tests": hasTestDir,
|
|
430
434
|
"is_lua_intensive": isLuaProject
|
|
431
435
|
};
|
|
432
|
-
|
|
436
|
+
|
|
433
437
|
return layers;
|
|
434
438
|
}
|
|
435
439
|
|
|
@@ -441,13 +445,13 @@ function generateAdaptiveStructuralLayers(directory) {
|
|
|
441
445
|
function generateProjectEnvelope(directory) {
|
|
442
446
|
const fs = require('fs');
|
|
443
447
|
const path = require('path');
|
|
444
|
-
|
|
448
|
+
|
|
445
449
|
const envelope = {
|
|
446
450
|
configuration_files: [],
|
|
447
451
|
documentation: [],
|
|
448
452
|
build_tools: []
|
|
449
453
|
};
|
|
450
|
-
|
|
454
|
+
|
|
451
455
|
// Common configuration files
|
|
452
456
|
const configFiles = [
|
|
453
457
|
'package.json',
|
|
@@ -497,7 +501,7 @@ function generateProjectEnvelope(directory) {
|
|
|
497
501
|
'remix.config.js',
|
|
498
502
|
'remix.config.ts'
|
|
499
503
|
];
|
|
500
|
-
|
|
504
|
+
|
|
501
505
|
// Common documentation files
|
|
502
506
|
const docFiles = [
|
|
503
507
|
'README.md',
|
|
@@ -521,7 +525,7 @@ function generateProjectEnvelope(directory) {
|
|
|
521
525
|
'CITATION.cff',
|
|
522
526
|
'CITATION.bib'
|
|
523
527
|
];
|
|
524
|
-
|
|
528
|
+
|
|
525
529
|
// Common build tool files
|
|
526
530
|
const buildToolFiles = [
|
|
527
531
|
'typedoc.config.cjs',
|
|
@@ -537,18 +541,18 @@ function generateProjectEnvelope(directory) {
|
|
|
537
541
|
'compodoc.json',
|
|
538
542
|
'documentation.yml'
|
|
539
543
|
];
|
|
540
|
-
|
|
544
|
+
|
|
541
545
|
// Function to find files recursively
|
|
542
546
|
function findFilesRecursively(dir, fileNames, results, maxDepth = 3, currentDepth = 0) {
|
|
543
547
|
if (currentDepth > maxDepth) return;
|
|
544
|
-
|
|
548
|
+
|
|
545
549
|
if (!fs.existsSync(dir)) return;
|
|
546
|
-
|
|
550
|
+
|
|
547
551
|
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
548
|
-
|
|
552
|
+
|
|
549
553
|
for (const item of items) {
|
|
550
554
|
const fullPath = path.join(dir, item.name);
|
|
551
|
-
|
|
555
|
+
|
|
552
556
|
if (item.isDirectory()) {
|
|
553
557
|
if (!item.name.startsWith('.') && item.name !== 'node_modules' && item.name !== 'dist' && item.name !== 'build') {
|
|
554
558
|
findFilesRecursively(fullPath, fileNames, results, maxDepth, currentDepth + 1);
|
|
@@ -564,53 +568,53 @@ function generateProjectEnvelope(directory) {
|
|
|
564
568
|
}
|
|
565
569
|
}
|
|
566
570
|
}
|
|
567
|
-
|
|
571
|
+
|
|
568
572
|
// Find all configuration files
|
|
569
573
|
const foundConfigFiles = [];
|
|
570
574
|
findFilesRecursively(directory, configFiles, foundConfigFiles);
|
|
571
|
-
|
|
575
|
+
|
|
572
576
|
foundConfigFiles.forEach(fileObj => {
|
|
573
577
|
envelope.configuration_files.push({
|
|
574
578
|
path: fileObj.path,
|
|
575
579
|
role: getRoleForConfigFile(fileObj.name)
|
|
576
580
|
});
|
|
577
581
|
});
|
|
578
|
-
|
|
582
|
+
|
|
579
583
|
// Find all documentation files
|
|
580
584
|
const foundDocFiles = [];
|
|
581
585
|
findFilesRecursively(directory, docFiles, foundDocFiles);
|
|
582
|
-
|
|
586
|
+
|
|
583
587
|
foundDocFiles.forEach(fileObj => {
|
|
584
588
|
envelope.documentation.push({
|
|
585
589
|
path: fileObj.path,
|
|
586
590
|
role: getRoleForDocFile(fileObj.name)
|
|
587
591
|
});
|
|
588
592
|
});
|
|
589
|
-
|
|
593
|
+
|
|
590
594
|
// Find all build tool files
|
|
591
595
|
const foundBuildToolFiles = [];
|
|
592
596
|
findFilesRecursively(directory, buildToolFiles, foundBuildToolFiles);
|
|
593
|
-
|
|
597
|
+
|
|
594
598
|
foundBuildToolFiles.forEach(fileObj => {
|
|
595
599
|
envelope.build_tools.push({
|
|
596
600
|
path: fileObj.path,
|
|
597
601
|
role: getRoleForBuildToolFile(fileObj.name)
|
|
598
602
|
});
|
|
599
603
|
});
|
|
600
|
-
|
|
604
|
+
|
|
601
605
|
// Remove duplicates based on path
|
|
602
606
|
envelope.configuration_files = envelope.configuration_files.filter((file, index, self) =>
|
|
603
607
|
index === self.findIndex(f => f.path === file.path)
|
|
604
608
|
);
|
|
605
|
-
|
|
609
|
+
|
|
606
610
|
envelope.documentation = envelope.documentation.filter((file, index, self) =>
|
|
607
611
|
index === self.findIndex(f => f.path === file.path)
|
|
608
612
|
);
|
|
609
|
-
|
|
613
|
+
|
|
610
614
|
envelope.build_tools = envelope.build_tools.filter((file, index, self) =>
|
|
611
615
|
index === self.findIndex(f => f.path === file.path)
|
|
612
616
|
);
|
|
613
|
-
|
|
617
|
+
|
|
614
618
|
return envelope;
|
|
615
619
|
}
|
|
616
620
|
|
|
@@ -68,21 +68,35 @@ class FailureModeSynthesizer {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
* Get default symptom for an invariant
|
|
71
|
+
* Get default symptom for an invariant based on its statement
|
|
72
72
|
*/
|
|
73
73
|
getDefaultSymptom(invariant) {
|
|
74
|
-
if (invariant
|
|
75
|
-
return '
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
if (!invariant) {
|
|
75
|
+
return 'System behavior deviates from expected requirements';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Handle cases where invariant might have description but no statement
|
|
79
|
+
const statementText = invariant.statement || invariant.description || invariant.id || '';
|
|
80
|
+
if (!statementText) {
|
|
81
|
+
return 'System behavior deviates from expected requirements';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Ensure we have a string before calling toLowerCase
|
|
85
|
+
const statementStr = String(statementText);
|
|
86
|
+
const statement = statementStr.toLowerCase();
|
|
87
|
+
|
|
88
|
+
if (statement.includes('availability')) {
|
|
89
|
+
return 'UI does not update or service unreachable';
|
|
90
|
+
} else if (statement.includes('state')) {
|
|
91
|
+
return 'Stale or inconsistent data presented to user';
|
|
92
|
+
} else if (statement.includes('sync') || statement.includes('synchronize')) {
|
|
80
93
|
return 'Data inconsistency between components';
|
|
81
|
-
} else if (
|
|
82
|
-
invariant.statement.toLowerCase().includes('auth')) {
|
|
94
|
+
} else if (statement.includes('permission') || statement.includes('auth')) {
|
|
83
95
|
return 'Unauthorized access or denied permissions';
|
|
96
|
+
} else if (statement.includes('performance') || statement.includes('latency')) {
|
|
97
|
+
return 'System response time exceeds threshold';
|
|
84
98
|
} else {
|
|
85
|
-
return 'Unexpected behavior or incorrect output';
|
|
99
|
+
return 'Unexpected behavior or incorrect logical output';
|
|
86
100
|
}
|
|
87
101
|
}
|
|
88
102
|
|
|
@@ -90,17 +104,34 @@ class FailureModeSynthesizer {
|
|
|
90
104
|
* Get blast radius for an invariant based on related nodes
|
|
91
105
|
*/
|
|
92
106
|
getInvariantBlastRadius(invariant, architectureMap) {
|
|
107
|
+
if (!invariant) {
|
|
108
|
+
return ['unknown'];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Handle cases where invariant might have description but no statement
|
|
112
|
+
const statementText = invariant.statement || invariant.description || invariant.id || '';
|
|
113
|
+
if (!statementText) {
|
|
114
|
+
return ['unknown'];
|
|
115
|
+
}
|
|
116
|
+
|
|
93
117
|
const { nodes } = architectureMap;
|
|
94
118
|
const blastRadius = [];
|
|
119
|
+
// Ensure we have a string before calling toLowerCase
|
|
120
|
+
const statementStr = String(statementText);
|
|
121
|
+
const statement = statementStr.toLowerCase();
|
|
95
122
|
|
|
96
123
|
// Look for nodes that are related to the invariant
|
|
97
124
|
for (const node of nodes) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
125
|
+
try {
|
|
126
|
+
if (node.path && statement.includes(node.path.toLowerCase())) {
|
|
127
|
+
blastRadius.push(node.path);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (node.id && statement.includes(node.id.toLowerCase())) {
|
|
131
|
+
blastRadius.push(node.id);
|
|
132
|
+
}
|
|
133
|
+
} catch (e) {
|
|
134
|
+
// Ignore errors
|
|
104
135
|
}
|
|
105
136
|
}
|
|
106
137
|
|
|
@@ -229,16 +260,19 @@ class FailureModeSynthesizer {
|
|
|
229
260
|
*/
|
|
230
261
|
isRelevantToInvariant(source, invariant) {
|
|
231
262
|
// Check if the source location is mentioned in the invariant
|
|
232
|
-
if (invariant.owner && source.location &&
|
|
233
|
-
|
|
263
|
+
if (invariant.owner && source.location &&
|
|
264
|
+
invariant.owner.toLowerCase().includes(source.location.toLowerCase())) {
|
|
234
265
|
return true;
|
|
235
266
|
}
|
|
236
267
|
|
|
237
|
-
// Check if the invariant statement mentions similar concepts
|
|
238
|
-
|
|
239
|
-
|
|
268
|
+
// Check if the invariant statement/description mentions similar concepts
|
|
269
|
+
const statementText = invariant.statement || invariant.description || invariant.id || '';
|
|
270
|
+
if (statementText && source.location) {
|
|
271
|
+
// Ensure we have a string before calling toLowerCase
|
|
272
|
+
const statementStr = String(statementText);
|
|
273
|
+
const invariantWords = statementStr.toLowerCase().split(/\W+/);
|
|
240
274
|
const locationWords = source.location.toLowerCase().split(/[\/\\.-_]+/);
|
|
241
|
-
|
|
275
|
+
|
|
242
276
|
// Check for overlapping terms
|
|
243
277
|
for (const word of invariantWords) {
|
|
244
278
|
if (word.length > 3 && locationWords.includes(word)) {
|
|
@@ -254,16 +288,25 @@ class FailureModeSynthesizer {
|
|
|
254
288
|
* Predict symptoms based on invariant and trigger
|
|
255
289
|
*/
|
|
256
290
|
predictSymptom(invariant, trigger) {
|
|
291
|
+
// Handle cases where invariant might have description but no statement
|
|
292
|
+
const statementText = invariant.statement || invariant.description || invariant.id || '';
|
|
293
|
+
if (!statementText) {
|
|
294
|
+
return 'Unexpected behavior or incorrect output';
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Ensure we have a string before calling toLowerCase
|
|
298
|
+
const statementStr = String(statementText);
|
|
299
|
+
|
|
257
300
|
// Default symptom prediction based on invariant type
|
|
258
|
-
if (
|
|
301
|
+
if (statementStr.toLowerCase().includes('availability')) {
|
|
259
302
|
return 'UI does not update';
|
|
260
|
-
} else if (
|
|
303
|
+
} else if (statementStr.toLowerCase().includes('state')) {
|
|
261
304
|
return 'Stale data presented to user';
|
|
262
|
-
} else if (
|
|
263
|
-
|
|
305
|
+
} else if (statementStr.toLowerCase().includes('sync') ||
|
|
306
|
+
statementStr.toLowerCase().includes('synchronize')) {
|
|
264
307
|
return 'Data inconsistency between components';
|
|
265
|
-
} else if (
|
|
266
|
-
|
|
308
|
+
} else if (statementStr.toLowerCase().includes('permission') ||
|
|
309
|
+
statementStr.toLowerCase().includes('auth')) {
|
|
267
310
|
return 'Unauthorized access or denied permissions';
|
|
268
311
|
} else {
|
|
269
312
|
return 'Unexpected behavior or incorrect output';
|
|
@@ -325,16 +368,16 @@ class FailureModeSynthesizer {
|
|
|
325
368
|
*/
|
|
326
369
|
containsMutationPattern(func) {
|
|
327
370
|
if (!func.name) return false;
|
|
328
|
-
|
|
371
|
+
|
|
329
372
|
const name = func.name.toLowerCase();
|
|
330
|
-
return name.includes('set') ||
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
373
|
+
return name.includes('set') ||
|
|
374
|
+
name.includes('update') ||
|
|
375
|
+
name.includes('modify') ||
|
|
376
|
+
name.includes('change') ||
|
|
377
|
+
name.includes('mutate') ||
|
|
378
|
+
name.includes('assign') ||
|
|
379
|
+
name.includes('put') ||
|
|
380
|
+
name.includes('post');
|
|
338
381
|
}
|
|
339
382
|
}
|
|
340
383
|
|