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.
@@ -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
- if (!fs.existsSync(fullPath)) {
37
- fs.mkdirSync(fullPath, { recursive: true });
38
- this.logCreation(dir);
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
- if (!fs.existsSync(ledgerPath)) {
50
- const defaultLedger = {
51
- schema_version: '1.0',
52
- system_id: path.basename(this.projectRoot),
53
- created_at: new Date().toISOString(),
54
- ledger: []
55
- };
56
-
57
- fs.writeFileSync(ledgerPath, JSON.stringify(defaultLedger, null, 2));
58
- this.logCreation(this.requiredFiles.ledger);
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
- // Attempt 3: Try with common function prefixes
76
- const commonPatterns = [
77
- `${targetFileId}::use${targetSymbolName.charAt(0).toUpperCase() + targetSymbolName.slice(1)}`,
78
- `${targetFileId}::get${targetSymbolName.charAt(0).toUpperCase() + targetSymbolName.slice(1)}`,
79
- `${targetFileId}::create${targetSymbolName.charAt(0).toUpperCase() + targetSymbolName.slice(1)}`,
80
- `${targetFileId}::_${targetSymbolName}`, // underscore prefix
81
- `${targetFileId}::${targetSymbolName}Impl`, // implementation suffix
82
- `${targetFileId}::${targetSymbolName}Handler` // handler suffix
83
- ];
84
-
85
- for (const patternId of commonPatterns) {
86
- if (this.symbolIndex.has(patternId)) return patternId;
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
- const localPatterns = [
101
- `${fileId}::use${symbolName.charAt(0).toUpperCase() + symbolName.slice(1)}`,
102
- `${fileId}::get${symbolName.charAt(0).toUpperCase() + symbolName.slice(1)}`,
103
- `${fileId}::create${symbolName.charAt(0).toUpperCase() + symbolName.slice(1)}`,
104
- `${fileId}::_${symbolName}`, // underscore prefix
105
- `${fileId}::${symbolName}Impl`, // implementation suffix
106
- `${fileId}::${symbolName}Handler` // handler suffix
107
- ];
108
-
109
- for (const patternId of localPatterns) {
110
- if (this.symbolIndex.has(patternId)) return patternId;
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
- Array.isArray(options.detectedInvariants) && options.detectedInvariants.length > 0 ? options.detectedInvariants :
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
- fs.existsSync(path.join(directory, 'config')) ||
331
- fs.existsSync(path.join(directory, 'webpack.config.js'));
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.statement.toLowerCase().includes('availability')) {
75
- return 'UI does not update';
76
- } else if (invariant.statement.toLowerCase().includes('state')) {
77
- return 'Stale data presented to user';
78
- } else if (invariant.statement.toLowerCase().includes('sync') ||
79
- invariant.statement.toLowerCase().includes('synchronize')) {
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 (invariant.statement.toLowerCase().includes('permission') ||
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
- if (node.path && invariant.statement.toLowerCase().includes(node.path.toLowerCase())) {
99
- blastRadius.push(node.path);
100
- }
101
-
102
- if (node.id && invariant.statement.toLowerCase().includes(node.id.toLowerCase())) {
103
- blastRadius.push(node.id);
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
- invariant.owner.toLowerCase().includes(source.location.toLowerCase())) {
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
- if (invariant.statement && source.location) {
239
- const invariantWords = invariant.statement.toLowerCase().split(/\W+/);
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 (invariant.statement.toLowerCase().includes('availability')) {
301
+ if (statementStr.toLowerCase().includes('availability')) {
259
302
  return 'UI does not update';
260
- } else if (invariant.statement.toLowerCase().includes('state')) {
303
+ } else if (statementStr.toLowerCase().includes('state')) {
261
304
  return 'Stale data presented to user';
262
- } else if (invariant.statement.toLowerCase().includes('sync') ||
263
- invariant.statement.toLowerCase().includes('synchronize')) {
305
+ } else if (statementStr.toLowerCase().includes('sync') ||
306
+ statementStr.toLowerCase().includes('synchronize')) {
264
307
  return 'Data inconsistency between components';
265
- } else if (invariant.statement.toLowerCase().includes('permission') ||
266
- invariant.statement.toLowerCase().includes('auth')) {
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
- name.includes('update') ||
332
- name.includes('modify') ||
333
- name.includes('change') ||
334
- name.includes('mutate') ||
335
- name.includes('assign') ||
336
- name.includes('put') ||
337
- name.includes('post');
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