arcvision 0.2.28 → 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/call-resolver.js +29 -24
- package/src/core/context_builder.js +52 -48
- package/src/core/invariant-detector.js +15 -3
- package/src/core/readme-generator.js +64 -11
- package/src/core/scanner.js +12 -4
- package/src/engine/context_builder.js +24 -15
- package/src/engine/pass5_intelligence.js +852 -0
- package/src/index.js +150 -57
|
@@ -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
|
|
|
@@ -31,7 +31,7 @@ class InvariantDetector {
|
|
|
31
31
|
|
|
32
32
|
console.log('\n🔍 Starting Invariant Detection Analysis');
|
|
33
33
|
console.log(` Analyzing ${scanResult.nodes.length} nodes and ${scanResult.edges.length} relationships`);
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
// Analyze the structural context for potential invariants
|
|
36
36
|
await this.analyzeNodesForInvariants(scanResult.nodes, directory);
|
|
37
37
|
await this.analyzeEdgesForInvariants(scanResult.edges);
|
|
@@ -49,7 +49,7 @@ class InvariantDetector {
|
|
|
49
49
|
|
|
50
50
|
console.log(`\n🛡️ Invariant Detection Complete`);
|
|
51
51
|
console.log(` Found ${this.detectedInvariants.length} potential system invariants`);
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
// Log summary of invariant types
|
|
54
54
|
const typeCounts = {};
|
|
55
55
|
this.detectedInvariants.forEach(inv => {
|
|
@@ -344,7 +344,7 @@ class InvariantDetector {
|
|
|
344
344
|
{ pattern: /range.*check/gi, description: 'Range check', critical: true },
|
|
345
345
|
{ pattern: /within.*bounds/gi, description: 'Bounds check', critical: true },
|
|
346
346
|
{ pattern: /validat(e|ion)/gi, description: 'Validation pattern', critical: false },
|
|
347
|
-
{ pattern: /guard/
|
|
347
|
+
{ pattern: /guard/gi, description: 'Guard pattern', critical: true },
|
|
348
348
|
|
|
349
349
|
// Access control and security patterns
|
|
350
350
|
{ pattern: /access.*control/gi, description: 'Access control', critical: true },
|
|
@@ -429,15 +429,27 @@ class InvariantDetector {
|
|
|
429
429
|
*/
|
|
430
430
|
async findInvariantRelatedFiles(directory) {
|
|
431
431
|
const invariantFiles = [];
|
|
432
|
+
const MAX_FILES = 200; // Cap to avoid OOM on giant monorepos
|
|
433
|
+
|
|
434
|
+
// Directories to skip — mirrors the Pass 1 exclusion list
|
|
435
|
+
const SKIP_DIRS = new Set([
|
|
436
|
+
'node_modules', '.git', 'dist', 'build', '.next', '.turbo',
|
|
437
|
+
'coverage', '.cache', '__pycache__', 'vendor', 'out',
|
|
438
|
+
'target', '.yarn', '.pnp', 'arcvision_context'
|
|
439
|
+
]);
|
|
432
440
|
|
|
433
441
|
const walk = async (dir) => {
|
|
442
|
+
if (invariantFiles.length >= MAX_FILES) return; // Early exit if cap reached
|
|
434
443
|
try {
|
|
435
444
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
436
445
|
|
|
437
446
|
for (const entry of entries) {
|
|
447
|
+
if (invariantFiles.length >= MAX_FILES) return;
|
|
438
448
|
const fullPath = path.join(dir, entry.name);
|
|
439
449
|
|
|
440
450
|
if (entry.isDirectory()) {
|
|
451
|
+
// Skip heavy or irrelevant directories
|
|
452
|
+
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith('.')) continue;
|
|
441
453
|
await walk(fullPath);
|
|
442
454
|
} else if (entry.isFile()) {
|
|
443
455
|
const fileName = entry.name.toLowerCase();
|
|
@@ -11,7 +11,7 @@ function getCommitHash() {
|
|
|
11
11
|
const result = spawnSync('git', ['rev-parse', 'HEAD'], {
|
|
12
12
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
13
13
|
});
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
if (result.status === 0) {
|
|
16
16
|
return result.stdout.toString().trim();
|
|
17
17
|
}
|
|
@@ -27,9 +27,10 @@ function getCommitHash() {
|
|
|
27
27
|
* @param {string} timestamp - Generation timestamp
|
|
28
28
|
* @param {string} toolVersion - ArcVision tool version
|
|
29
29
|
* @param {Object} blastRadiusData - Blast radius analysis data
|
|
30
|
+
* @param {Object} intelligenceData - Architecture intelligence block from Pass 5
|
|
30
31
|
* @returns {string} Formatted README content
|
|
31
32
|
*/
|
|
32
|
-
function generateReadmeContent(commitHash, timestamp, toolVersion, blastRadiusData = null) {
|
|
33
|
+
function generateReadmeContent(commitHash, timestamp, toolVersion, blastRadiusData = null, intelligenceData = null) {
|
|
33
34
|
let content = `# ArcVision System Context Artifact
|
|
34
35
|
|
|
35
36
|
|
|
@@ -89,7 +90,7 @@ If this artifact conflicts with human memory, **trust the artifact**.`;
|
|
|
89
90
|
The following files have the highest blast radius and represent critical structural hubs in the system:
|
|
90
91
|
|
|
91
92
|
`;
|
|
92
|
-
|
|
93
|
+
|
|
93
94
|
blastRadiusData.topFiles.forEach((item, index) => {
|
|
94
95
|
let warningMessage = '';
|
|
95
96
|
if (index === 0) {
|
|
@@ -99,7 +100,7 @@ The following files have the highest blast radius and represent critical structu
|
|
|
99
100
|
} else {
|
|
100
101
|
warningMessage = 'Modifications can cause widespread inconsistencies.';
|
|
101
102
|
}
|
|
102
|
-
|
|
103
|
+
|
|
103
104
|
content += `- **${item.file}**
|
|
104
105
|
- Blast Radius: ${item.blastRadius} files (${item.percentOfGraph}% of codebase)
|
|
105
106
|
- Risk: ${warningMessage}
|
|
@@ -113,7 +114,7 @@ The following files have the highest blast radius and represent critical structu
|
|
|
113
114
|
|
|
114
115
|
No high-structure files detected based on import dependencies.`;
|
|
115
116
|
}
|
|
116
|
-
|
|
117
|
+
|
|
117
118
|
content += `
|
|
118
119
|
|
|
119
120
|
## How to Use With AI
|
|
@@ -141,7 +142,58 @@ All explanations, decisions, and AI reasoning should reference it.
|
|
|
141
142
|
|
|
142
143
|
Some execution script invocations are dynamically assembled at runtime and may not be statically traceable; such scripts are included
|
|
143
144
|
as execution boundaries without guaranteed call-site resolution`;
|
|
144
|
-
|
|
145
|
+
|
|
146
|
+
// Add architecture intelligence summary if available
|
|
147
|
+
if (intelligenceData) {
|
|
148
|
+
const intel = intelligenceData;
|
|
149
|
+
const centrality = intel.centrality || {};
|
|
150
|
+
const hotspots = intel.hotspots || {};
|
|
151
|
+
const risk = intel.risk_assessment || {};
|
|
152
|
+
const recs = intel.recommendations || [];
|
|
153
|
+
const fragility = intel.fragility || {};
|
|
154
|
+
const path = require('path');
|
|
155
|
+
|
|
156
|
+
content += `
|
|
157
|
+
|
|
158
|
+
## Architecture Intelligence Summary
|
|
159
|
+
|
|
160
|
+
_Generated by ArcVision — derived from structural graph analysis._
|
|
161
|
+
|
|
162
|
+
`;
|
|
163
|
+
if (centrality.most_central) {
|
|
164
|
+
const top = centrality.top_files && centrality.top_files[0];
|
|
165
|
+
const score = top ? ` (centrality score: ${top.score})` : '';
|
|
166
|
+
content += `- **Most Central Module**: \`${centrality.most_central}\`${score}\n`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const clusters = hotspots.clusters || [];
|
|
170
|
+
const topCluster = clusters.find(c => c.cycle_count > 0) || clusters[0];
|
|
171
|
+
if (topCluster && topCluster.files && topCluster.files.length > 0) {
|
|
172
|
+
const names = topCluster.files.slice(0, 3).map(f => path.basename(f)).join(' + ');
|
|
173
|
+
const cycleInfo = topCluster.cycle_count > 0 ? ` (${topCluster.cycle_count} cycle(s))` : '';
|
|
174
|
+
content += `- **Coupling Hotspot**: ${names}${cycleInfo}\n`;
|
|
175
|
+
} else {
|
|
176
|
+
content += `- **Coupling Hotspot**: None detected\n`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (risk.risk_score !== undefined) {
|
|
180
|
+
content += `- **Risk Score**: ${risk.risk_score.toFixed(1)} / 10 [${risk.risk_level}]\n`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (intel.entropy) {
|
|
184
|
+
content += `- **Architectural Entropy**: ${intel.entropy.score} [${intel.entropy.level.toUpperCase()}]\n`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (fragility.most_fragile) {
|
|
188
|
+
const frag = fragility.most_fragile;
|
|
189
|
+
content += `- **Most Fragile Module**: \`${path.basename(frag.file)}\` — fragility score: ${frag.score}\n`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (recs.length > 0) {
|
|
193
|
+
content += `- **Top Recommendation**: ${recs[0]}\n`;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
145
197
|
return content;
|
|
146
198
|
}
|
|
147
199
|
|
|
@@ -150,16 +202,17 @@ as execution boundaries without guaranteed call-site resolution`;
|
|
|
150
202
|
* @param {string} outputDir - Directory to save the README
|
|
151
203
|
* @param {string} toolVersion - ArcVision tool version
|
|
152
204
|
* @param {Object} blastRadiusData - Blast radius analysis data
|
|
205
|
+
* @param {Object} intelligenceData - Architecture intelligence block from Pass 5
|
|
153
206
|
*/
|
|
154
|
-
function generateReadme(outputDir, toolVersion, blastRadiusData = null) {
|
|
207
|
+
function generateReadme(outputDir, toolVersion, blastRadiusData = null, intelligenceData = null) {
|
|
155
208
|
const commitHash = getCommitHash();
|
|
156
209
|
const timestamp = new Date().toISOString();
|
|
157
|
-
const readmeContent = generateReadmeContent(commitHash, timestamp, toolVersion, blastRadiusData);
|
|
158
|
-
|
|
210
|
+
const readmeContent = generateReadmeContent(commitHash, timestamp, toolVersion, blastRadiusData, intelligenceData);
|
|
211
|
+
|
|
159
212
|
const readmePath = path.join(outputDir, 'README.md');
|
|
160
213
|
fs.writeFileSync(readmePath, readmeContent);
|
|
161
|
-
|
|
162
|
-
console.log(
|
|
214
|
+
|
|
215
|
+
console.log(`\u2705 README generated at ${readmePath}`);
|
|
163
216
|
}
|
|
164
217
|
|
|
165
218
|
module.exports = { generateReadme, generateReadmeContent, getCommitHash };
|
package/src/core/scanner.js
CHANGED
|
@@ -7,6 +7,7 @@ const { executePass1 } = require('../engine/pass1_facts');
|
|
|
7
7
|
const { executePass2 } = require('../engine/pass2_semantics');
|
|
8
8
|
const { executePass3 } = require('../engine/pass3_lifter');
|
|
9
9
|
const { executePass4 } = require('../engine/pass4_signals');
|
|
10
|
+
const { executePass5 } = require('../engine/pass5_intelligence');
|
|
10
11
|
|
|
11
12
|
// Import authority core detection
|
|
12
13
|
const { detectAuthorityCores, detectHiddenCoupling, detectArchitecturalArchetype } = require('./authority-core-detector');
|
|
@@ -64,6 +65,11 @@ async function scan(directory) {
|
|
|
64
65
|
|
|
65
66
|
const { nodes, edges, contextSurface, symbols } = structuralContext;
|
|
66
67
|
|
|
68
|
+
// --- PASS 5: INTELLIGENCE SYNTHESIS ---
|
|
69
|
+
// Interprets the graph already computed — no new file scanning.
|
|
70
|
+
// Computes centrality, hotspots, fragility, boundary violations, risk, and recommendations.
|
|
71
|
+
const { intelligence } = executePass5({ nodes, edges });
|
|
72
|
+
|
|
67
73
|
console.log(`\n🔍 Analysis Complete (${Date.now() - start}ms)`);
|
|
68
74
|
console.log(` Nodes: ${nodes.length}`);
|
|
69
75
|
console.log(` Edges: ${edges.length}`);
|
|
@@ -181,7 +187,7 @@ async function scan(directory) {
|
|
|
181
187
|
existingIds.add(existingInv.id);
|
|
182
188
|
}
|
|
183
189
|
}
|
|
184
|
-
|
|
190
|
+
|
|
185
191
|
// Ensure all invariants have statement fields as fallback
|
|
186
192
|
console.log(`🔍 Ensuring all ${allInvariants.length} invariants have statement fields...`);
|
|
187
193
|
for (let i = 0; i < allInvariants.length; i++) {
|
|
@@ -221,7 +227,7 @@ async function scan(directory) {
|
|
|
221
227
|
// Synthesize failure modes
|
|
222
228
|
let failure_modes = [];
|
|
223
229
|
try {
|
|
224
|
-
console.log(
|
|
230
|
+
console.log(`🔍 Synthesizing failure modes for ${allInvariants.length} invariants...`);
|
|
225
231
|
failure_modes = failureModeSynthesizer.synthesizeFailureModes(
|
|
226
232
|
{ nodes, edges },
|
|
227
233
|
allInvariants
|
|
@@ -359,7 +365,9 @@ async function scan(directory) {
|
|
|
359
365
|
invariants: allInvariants,
|
|
360
366
|
ownership,
|
|
361
367
|
failure_modes,
|
|
362
|
-
decision_guidance
|
|
368
|
+
decision_guidance,
|
|
369
|
+
// Intelligence block from Pass 5
|
|
370
|
+
intelligence
|
|
363
371
|
};
|
|
364
372
|
|
|
365
373
|
// Explicitly set archetype to ensure it's a proper object
|
|
@@ -423,7 +431,7 @@ async function scan(directory) {
|
|
|
423
431
|
try {
|
|
424
432
|
fs.writeFileSync(contextFilePath, JSON.stringify(sortedContext, null, 2));
|
|
425
433
|
console.log('✅ Context file written successfully');
|
|
426
|
-
|
|
434
|
+
|
|
427
435
|
// Verify file was written
|
|
428
436
|
if (fs.existsSync(contextFilePath)) {
|
|
429
437
|
const stats = fs.statSync(contextFilePath);
|