arcvision 0.2.14 → 0.2.15

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.
Files changed (134) hide show
  1. package/ARCVISION_DIRECTORY_STRUCTURE.md +104 -0
  2. package/CLI_STRUCTURE.md +110 -0
  3. package/CONFIGURATION.md +119 -0
  4. package/IMPLEMENTATION_SUMMARY.md +99 -0
  5. package/README.md +149 -89
  6. package/architecture.authority.ledger.json +46 -0
  7. package/arcvision-0.2.3.tgz +0 -0
  8. package/arcvision-0.2.4.tgz +0 -0
  9. package/arcvision-0.2.5.tgz +0 -0
  10. package/arcvision.context.diff.json +2181 -0
  11. package/arcvision.context.json +1021 -0
  12. package/arcvision.context.v1.json +2163 -0
  13. package/arcvision.context.v2.json +2173 -0
  14. package/arcvision_context/README.md +93 -0
  15. package/arcvision_context/architecture.authority.ledger.json +83 -0
  16. package/arcvision_context/arcvision.context.json +6884 -0
  17. package/debug-cycle-detection.js +56 -0
  18. package/dist/index.js +1626 -25
  19. package/docs/ENHANCED_ACCURACY_SAFETY_PROTOCOL.md +172 -0
  20. package/docs/accuracy-enhancement-artifacts/enhanced-validation-config.json +98 -0
  21. package/docs/acig-robustness-guide.md +164 -0
  22. package/docs/authoritative-gate-implementation.md +168 -0
  23. package/docs/cli-strengthening-summary.md +232 -0
  24. package/docs/invariant-system-summary.md +100 -0
  25. package/docs/invariant-system.md +112 -0
  26. package/generate_large_test.js +42 -0
  27. package/large_test_repo.json +1 -0
  28. package/output1.json +2163 -0
  29. package/output2.json +2163 -0
  30. package/package.json +46 -36
  31. package/scan_calcom_report.txt +0 -0
  32. package/scan_leafmint_report.txt +0 -0
  33. package/scan_output.txt +0 -0
  34. package/scan_trigger_report.txt +0 -0
  35. package/schema/arcvision_context_schema_v1.json +136 -1
  36. package/src/arcvision-guard.js +433 -0
  37. package/src/core/authority-core-detector.js +382 -0
  38. package/src/core/authority-ledger.js +300 -0
  39. package/src/core/blastRadius.js +299 -0
  40. package/src/core/call-resolver.js +196 -0
  41. package/src/core/change-evaluator.js +509 -0
  42. package/src/core/change-evaluator.js.backup +424 -0
  43. package/src/core/change-evaluator.ts +285 -0
  44. package/src/core/chunked-uploader.js +180 -0
  45. package/src/core/circular-dependency-detector.js +404 -0
  46. package/src/core/cli-error-handler.js +458 -0
  47. package/src/core/cli-validator.js +458 -0
  48. package/src/core/compression.js +64 -0
  49. package/src/core/context_builder.js +741 -0
  50. package/src/core/dependency-manager.js +134 -0
  51. package/src/core/di-detector.js +202 -0
  52. package/src/core/diff-analyzer.js +76 -0
  53. package/src/core/example-invariants.js +135 -0
  54. package/src/core/failure-mode-synthesizer.js +341 -0
  55. package/src/core/invariant-analyzer.js +294 -0
  56. package/src/core/invariant-detector.js +548 -0
  57. package/src/core/invariant-enforcer.js +171 -0
  58. package/src/core/invariant-evaluation-utils.js +172 -0
  59. package/src/core/invariant-hooks.js +152 -0
  60. package/src/core/invariant-integration-example.js +186 -0
  61. package/src/core/invariant-registry.js +298 -0
  62. package/src/core/invariant-registry.ts +100 -0
  63. package/src/core/invariant-types.js +66 -0
  64. package/src/core/invariants-index.js +88 -0
  65. package/src/core/method-tracker.js +170 -0
  66. package/src/core/override-handler.js +304 -0
  67. package/src/core/ownership-resolver.js +227 -0
  68. package/src/core/parser-enhanced.js +80 -0
  69. package/src/core/parser.js +610 -0
  70. package/src/core/path-resolver.js +240 -0
  71. package/src/core/pattern-matcher.js +246 -0
  72. package/src/core/progress-tracker.js +71 -0
  73. package/src/core/react-nextjs-detector.js +245 -0
  74. package/src/core/readme-generator.js +167 -0
  75. package/src/core/retry-handler.js +57 -0
  76. package/src/core/scanner.js +289 -0
  77. package/src/core/semantic-analyzer.js +204 -0
  78. package/src/core/structural-context-owner.js +442 -0
  79. package/src/core/symbol-indexer.js +164 -0
  80. package/src/core/tsconfig-utils.js +73 -0
  81. package/src/core/type-analyzer.js +272 -0
  82. package/src/core/watcher.js +18 -0
  83. package/src/core/workspace-scanner.js +88 -0
  84. package/src/engine/context_builder.js +280 -0
  85. package/src/engine/context_sorter.js +59 -0
  86. package/src/engine/context_validator.js +200 -0
  87. package/src/engine/id-generator.js +16 -0
  88. package/src/engine/pass1_facts.js +260 -0
  89. package/src/engine/pass2_semantics.js +333 -0
  90. package/src/engine/pass3_lifter.js +99 -0
  91. package/src/engine/pass4_signals.js +201 -0
  92. package/src/index.js +830 -0
  93. package/src/plugins/express-plugin.js +48 -0
  94. package/src/plugins/plugin-manager.js +58 -0
  95. package/src/plugins/react-plugin.js +54 -0
  96. package/temp_original.js +0 -0
  97. package/test/determinism-test.js +83 -0
  98. package/test-authoritative-context.js +53 -0
  99. package/test-real-authoritative-context.js +118 -0
  100. package/test-upload-enhancements.js +111 -0
  101. package/test_repos/allowed-clean-architecture/.arcvision/invariants.json +57 -0
  102. package/test_repos/allowed-clean-architecture/adapters/controllers/UserController.js +95 -0
  103. package/test_repos/allowed-clean-architecture/adapters/http/HttpServer.js +78 -0
  104. package/test_repos/allowed-clean-architecture/application/dtos/CreateUserRequest.js +37 -0
  105. package/test_repos/allowed-clean-architecture/application/services/UserService.js +61 -0
  106. package/test_repos/allowed-clean-architecture/arcvision_context/README.md +93 -0
  107. package/test_repos/allowed-clean-architecture/arcvision_context/arcvision.context.json +2796 -0
  108. package/test_repos/allowed-clean-architecture/domain/interfaces/UserRepository.js +25 -0
  109. package/test_repos/allowed-clean-architecture/domain/models/User.js +39 -0
  110. package/test_repos/allowed-clean-architecture/index.js +45 -0
  111. package/test_repos/allowed-clean-architecture/infrastructure/database/DatabaseConnection.js +56 -0
  112. package/test_repos/allowed-clean-architecture/infrastructure/repositories/InMemoryUserRepository.js +61 -0
  113. package/test_repos/allowed-clean-architecture/package.json +15 -0
  114. package/test_repos/blocked-legacy-monolith/.arcvision/invariants.json +78 -0
  115. package/test_repos/blocked-legacy-monolith/arcvision_context/README.md +93 -0
  116. package/test_repos/blocked-legacy-monolith/arcvision_context/arcvision.context.json +2882 -0
  117. package/test_repos/blocked-legacy-monolith/database/dbConnection.js +35 -0
  118. package/test_repos/blocked-legacy-monolith/index.js +38 -0
  119. package/test_repos/blocked-legacy-monolith/modules/emailService.js +31 -0
  120. package/test_repos/blocked-legacy-monolith/modules/paymentProcessor.js +37 -0
  121. package/test_repos/blocked-legacy-monolith/package.json +15 -0
  122. package/test_repos/blocked-legacy-monolith/shared/utils.js +19 -0
  123. package/test_repos/blocked-legacy-monolith/utils/helpers.js +23 -0
  124. package/test_repos/risky-microservices-concerns/.arcvision/invariants.json +69 -0
  125. package/test_repos/risky-microservices-concerns/arcvision_context/README.md +93 -0
  126. package/test_repos/risky-microservices-concerns/arcvision_context/arcvision.context.json +3070 -0
  127. package/test_repos/risky-microservices-concerns/common/utils.js +77 -0
  128. package/test_repos/risky-microservices-concerns/gateways/apiGateway.js +84 -0
  129. package/test_repos/risky-microservices-concerns/index.js +20 -0
  130. package/test_repos/risky-microservices-concerns/libs/deprecatedHelper.js +36 -0
  131. package/test_repos/risky-microservices-concerns/package.json +15 -0
  132. package/test_repos/risky-microservices-concerns/services/orderService.js +42 -0
  133. package/test_repos/risky-microservices-concerns/services/userService.js +48 -0
  134. package/verify_engine.js +116 -0
@@ -0,0 +1,333 @@
1
+ /**
2
+ * Pass 2: Semantic Resolution
3
+ *
4
+ * Objectives:
5
+ * - Resolve import strings to actual file IDs (using enhanced resolution logic)
6
+ * - Index Global Symbols (Functions, Classes, Exports)
7
+ * - Resolve Call Sites to Symbols (Typed Edges)
8
+ * - Resolve Dependency Injection relationships
9
+ */
10
+
11
+ const path = require('path');
12
+ const { loadTSConfig } = require('../core/tsconfig-utils');
13
+ const { resolveImport } = require('../core/path-resolver');
14
+ const { SymbolIndexer } = require('../core/symbol-indexer');
15
+ const { CallResolver } = require('../core/call-resolver');
16
+
17
+ /**
18
+ * Executes Pass 2: Resolves meanings and connections
19
+ * @param {Array} rawNodes - List of nodes from Pass 1
20
+ * @param {string} rootDir - Root directory
21
+ * @param {Object} workspaceContext - { workspaceMap, tsconfigMap }
22
+ * @returns {Promise<Object>} Object containing enriched nodes and resolved edges
23
+ */
24
+ async function executePass2(rawNodes, rootDir, workspaceContext) {
25
+ console.log('🧠 PASS 2: Semantic Resolution...');
26
+
27
+ // 1. Setup Resolution Context
28
+ const config = loadTSConfig(rootDir);
29
+ const tsconfig = config.options;
30
+ const configRoot = config.configDir;
31
+ const normalize = p => p.replace(/\\/g, '/');
32
+ const workspaceMap = workspaceContext ? workspaceContext.workspaceMap : null;
33
+
34
+ // Create quick lookup for file existence
35
+ const fileSet = new Set(rawNodes.map(n => n.id));
36
+ const absoluteFileSet = new Set(rawNodes.map(n => n.filePath));
37
+ const edges = [];
38
+
39
+ // Helper to add unique edges
40
+ const addEdge = (source, target, type, confidence = 1.0, metadata = {}) => {
41
+ if (!source || !target) return;
42
+ if (source === target) return;
43
+
44
+ edges.push({
45
+ source,
46
+ target,
47
+ type,
48
+ confidence,
49
+ metadata
50
+ });
51
+ };
52
+
53
+ // --- PASS 2A: IMPORT RESOLUTION ---
54
+ let resolvedImports = 0;
55
+
56
+ for (const node of rawNodes) {
57
+ if (!node.facts.imports) continue;
58
+
59
+ // Resolve config for THIS file (Truth-level accuracy for monorepos)
60
+ const nodeConfig = loadTSConfig(path.dirname(node.filePath));
61
+ const nodeTsconfig = nodeConfig.options;
62
+ const nodeConfigRoot = nodeConfig.configDir;
63
+
64
+ for (const imp of node.facts.imports) {
65
+ const importString = imp.source;
66
+ let targetId = null;
67
+ let resolvedPath = null;
68
+
69
+ // Resolve using path-resolver with the cached fileSet AND the correct config for THIS file
70
+ const resolvedAbs = resolveImport(importString, node.filePath, nodeConfigRoot, nodeTsconfig, workspaceMap, absoluteFileSet);
71
+
72
+ if (resolvedAbs) {
73
+ targetId = normalize(path.relative(rootDir, resolvedAbs));
74
+ resolvedPath = targetId;
75
+
76
+ // Annotate import with resolved path for CallResolver usage
77
+ imp.resolvedPath = targetId;
78
+ } else {
79
+ // Fallback for relative paths in some edge cases
80
+ if (importString.startsWith('.')) {
81
+ try {
82
+ const nodeDir = path.dirname(node.filePath);
83
+ const simpleAbs = path.resolve(nodeDir, importString);
84
+ const exts = ['.ts', '.tsx', '.js', '.jsx', '.json', '/index.ts', '/index.js'];
85
+ for (const ext of [''].concat(exts)) {
86
+ const candidate = normalize(path.relative(rootDir, simpleAbs + ext));
87
+ if (fileSet.has(candidate)) {
88
+ targetId = candidate;
89
+ imp.resolvedPath = candidate;
90
+ break;
91
+ }
92
+ }
93
+ } catch (e) { }
94
+ }
95
+ }
96
+
97
+ // If we found a target within our scanned files, create an edge
98
+ if (targetId && fileSet.has(targetId)) {
99
+ // Determine edge type
100
+ let type = 'imports';
101
+ if (workspaceContext && workspaceContext.workspaceMap) {
102
+ // check if importString matches a workspace package
103
+ // (This check is simplified, real check is if resolvedAbs is in another package root)
104
+ }
105
+
106
+ addEdge(node.id, targetId, type, 1.0, { specifiers: imp.specifiers });
107
+ resolvedImports++;
108
+ }
109
+ }
110
+ }
111
+ console.log(` ✓ Resolved ${resolvedImports} import dependencies`);
112
+
113
+
114
+ // --- PASS 2B: SYMBOL INDEXING ---
115
+ // Extract symbols from raw nodes
116
+ const symbolIndexer = new SymbolIndexer();
117
+ const nodesForIndexing = rawNodes.map(n => ({
118
+ id: n.id,
119
+ metadata: n.facts // Indexer expects metadata.exports, etc. (Pass 1 facts structure matches enough)
120
+ }));
121
+
122
+ // Index builds the global symbol table
123
+ const { symbols, fileToSymbols } = symbolIndexer.index(nodesForIndexing);
124
+
125
+
126
+ // --- PASS 2C: CALL RESOLUTION ---
127
+ // Resolve call sites to symbol IDs
128
+ const callResolver = new CallResolver(symbols, null); // fileMap not strictly needed if we annotated imports
129
+
130
+ const nodesForResolution = rawNodes.map(n => {
131
+ // Merge JSX components into function calls for resolution
132
+ const combinedFunctions = [
133
+ ...(n.facts.calls.functions || []),
134
+ ...(n.facts.react || []).map(r => ({
135
+ name: r.component,
136
+ type: 'jsx_component',
137
+ loc: r.loc
138
+ }))
139
+ ];
140
+
141
+ return {
142
+ id: n.id,
143
+ metadata: n.facts,
144
+ methodCalls: {
145
+ functionCalls: combinedFunctions,
146
+ methodCalls: n.facts.calls.methods,
147
+ constructorCalls: n.facts.calls.constructors
148
+ }
149
+ };
150
+ });
151
+
152
+ const callEdges = callResolver.resolve(nodesForResolution);
153
+
154
+ // Add Resolved Call Edges
155
+ let resolvedCalls = 0;
156
+ for (const edge of callEdges) {
157
+ // edge: { sourceFileId, targetSymbolId, type, confidence }
158
+ // We need an edge between FILES for the graph structure.
159
+ // We also want to record the specific symbol connection.
160
+
161
+ const symbol = symbols.get(edge.targetSymbolId);
162
+ if (symbol) {
163
+ const targetFileId = symbol.fileId;
164
+ if (fileSet.has(targetFileId)) {
165
+ addEdge(edge.sourceFileId, targetFileId, edge.type, edge.confidence, {
166
+ symbol: symbol.name,
167
+ symbolId: edge.targetSymbolId,
168
+ line: edge.line
169
+ });
170
+ resolvedCalls++;
171
+ }
172
+ }
173
+ }
174
+ console.log(` ✓ Resolved ${resolvedCalls} semantic call edges`);
175
+
176
+
177
+ // --- PASS 2D: LUA SCRIPT INVOCATION DETECTION ---
178
+ let luaInvocationCount = 0;
179
+ for (const node of rawNodes) {
180
+ if (!node.facts.potentialInvocations || node.facts.potentialInvocations.length === 0) continue;
181
+
182
+ // Look for Lua script invocations in TypeScript files
183
+ const content = node.filePath && require('fs').readFileSync(node.filePath, 'utf-8');
184
+ if (!content) continue;
185
+
186
+ // Check for common patterns that invoke Lua scripts
187
+ const luaInvocationPatterns = [
188
+ /defineCommand\(\s*['"].*?['"]\s*,\s*\{[^}]*lua\s*:[^}]*\}/gi, // Matches defineCommand("commandName", { ... lua: ... })
189
+ /defineCommand\(\s*[^,)]+\s*,\s*\{[^}]*lua\s*:[^}]*\}/gi, // Matches defineCommand(commandName, { ... lua: ... })
190
+ /defineCommand\(\s*\{[^}]*lua\s*:[^}]*\}/gi, // Original pattern
191
+ /readFileSync\(['"].*\.lua['"].*\)/gi,
192
+ /require\(['"].*\.lua['"].*\)/gi,
193
+ /fs\.readFileSync\(['"].*\.lua['"].*\)/gi,
194
+ /import\s+.*?from\s+['"].*\.lua['"].*\)?/gi,
195
+ /import\(['"].*\.lua['"].*\)/gi,
196
+ /loadScript\(['"].*\.lua['"].*\)/gi,
197
+ /loadLua\(['"].*\.lua['"].*\)/gi,
198
+ /load\(['"].*\.lua['"].*\)/gi
199
+ ];
200
+
201
+ for (const pattern of luaInvocationPatterns) {
202
+ let match;
203
+ while ((match = pattern.exec(content)) !== null) {
204
+ // Extract the Lua file path from the match
205
+ const luaFileMatch = match[0].match(/['"].*?\.lua['"]/);
206
+ if (luaFileMatch) {
207
+ let luaFilePath = luaFileMatch[0].slice(1, -1); // Remove quotes
208
+
209
+ // Handle different path formats
210
+ if (luaFilePath.startsWith('./') || luaFilePath.startsWith('../')) {
211
+ // Relative path
212
+ luaFilePath = path.resolve(path.dirname(node.filePath), luaFilePath);
213
+ luaFilePath = normalize(path.relative(rootDir, luaFilePath));
214
+ } else if (luaFilePath.startsWith('/')) {
215
+ // Absolute path within project
216
+ luaFilePath = normalize(path.relative(rootDir, path.join(rootDir, luaFilePath)));
217
+ } else {
218
+ // Just filename or relative path - look for it in common directories
219
+ const commonLuaDirs = ['src', 'scripts', 'commands', 'lua', 'lib'];
220
+ let foundPath = null;
221
+
222
+ for (const dir of commonLuaDirs) {
223
+ const candidatePath = normalize(path.join(dir, luaFilePath));
224
+ if (fileSet.has(candidatePath)) {
225
+ foundPath = candidatePath;
226
+ break;
227
+ }
228
+ }
229
+
230
+ if (foundPath) {
231
+ luaFilePath = foundPath;
232
+ } else {
233
+ // Fallback to same directory as the current file
234
+ const nodeDir = path.dirname(node.id);
235
+ luaFilePath = normalize(path.join(nodeDir, luaFilePath));
236
+ }
237
+ }
238
+
239
+ // Check if the Lua file exists in our fileSet
240
+ if (fileSet.has(luaFilePath)) {
241
+ addEdge(node.id, luaFilePath, 'invokes_execution_script', 1.0, {
242
+ type: 'lua_invocation',
243
+ invocation_pattern: match[0],
244
+ original_match: match[0],
245
+ resolved_path: luaFilePath
246
+ });
247
+ luaInvocationCount++;
248
+ }
249
+ }
250
+ }
251
+ }
252
+ }
253
+ console.log(` ✓ Detected ${luaInvocationCount} Lua script invocations`);
254
+
255
+ // --- PASS 2E: TYPE RESOLUTION ---
256
+ let typeEdgesCount = 0;
257
+ for (const node of rawNodes) {
258
+ if (!node.facts.typeAnalysis) continue;
259
+
260
+ const nodeConfig = loadTSConfig(path.dirname(node.filePath));
261
+ const nodeTsconfig = nodeConfig.options;
262
+ const nodeConfigRoot = nodeConfig.configDir;
263
+
264
+ const { typeImports, interfaceDeps, genericDeps } = node.facts.typeAnalysis;
265
+
266
+ // 1. Resolve Type Imports (matches both 'import type' and 'import { type X }')
267
+ for (const imp of typeImports) {
268
+ const resolvedAbs = resolveImport(imp.source, node.filePath, nodeConfigRoot, nodeTsconfig, workspaceMap, absoluteFileSet);
269
+ if (resolvedAbs) {
270
+ const targetId = normalize(path.relative(rootDir, resolvedAbs));
271
+ if (fileSet.has(targetId)) {
272
+ addEdge(node.id, targetId, 'depends_on', 0.9, { type: 'type_import' });
273
+ typeEdgesCount++;
274
+ }
275
+ }
276
+ }
277
+
278
+ // 2. Resolve Interface Extensions & Class Implementations
279
+ // (This uses the local symbols or imported symbols to find the target file)
280
+ const allTypeDeps = [...interfaceDeps, ...genericDeps];
281
+ for (const dep of allTypeDeps) {
282
+ const typeName = dep.extends || dep.implements || dep.returnType || dep.type;
283
+ if (!typeName) continue;
284
+
285
+ // Simple resolution: find if typeName exists as an exported symbol in any imported file
286
+ // (We reuse resolveImportedSymbol logic but simplified here)
287
+ for (const imp of (node.facts.imports || [])) {
288
+ if (imp.resolvedPath) {
289
+ const targetFileId = imp.resolvedPath;
290
+ const candidateId = `${targetFileId}::${typeName}`;
291
+ if (symbols.has(candidateId)) {
292
+ addEdge(node.id, targetFileId, 'depends_on', 1.0, { type: 'type_reference', symbol: typeName });
293
+ typeEdgesCount++;
294
+ break;
295
+ }
296
+ }
297
+ }
298
+ }
299
+ }
300
+ console.log(` ✓ Resolved ${typeEdgesCount} TypeScript type connections`);
301
+
302
+ // Enhance nodes with invariant indicators from pass 1
303
+ const enhancedNodes = rawNodes.map(node => {
304
+ // Carry forward invariant indicators from pass 1
305
+ if (node.facts && node.facts.invariantIndicators) {
306
+ return {
307
+ ...node,
308
+ invariantIndicators: node.facts.invariantIndicators
309
+ };
310
+ }
311
+ return node;
312
+ });
313
+
314
+ return {
315
+ nodes: enhancedNodes,
316
+ edges: edges,
317
+ symbols: Array.from(symbols.values()) // Return symbols list for ContextBuilder
318
+ };
319
+ }
320
+
321
+ /**
322
+ * Helper: Find valid provider file for a class/type name
323
+ */
324
+ /*
325
+ function findProviderForType(typeName, symbolTable) {
326
+ if (!symbolTable.has(typeName)) return null;
327
+ const defs = symbolTable.get(typeName);
328
+ const bestDef = defs.find(d => d.type === 'class') || defs[0];
329
+ return bestDef ? bestDef.file : null;
330
+ }
331
+ */
332
+
333
+ module.exports = { executePass2 };
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Pass 3: Structural Lifting
3
+ *
4
+ * Objectives:
5
+ * - Lift low-level nodes into "Structural Roles" (Store, Service, Component, Utility)
6
+ * - This uses inference based on naming, content, and connectivity.
7
+ */
8
+
9
+ /**
10
+ * Executes Pass 3: Infers roles and structure
11
+ * @param {Object} semanticData - Output from Pass 2 { nodes, edges }
12
+ * @returns {Promise<Object>} Enhanced nodes with roles and layers
13
+ */
14
+ async function executePass3(semanticData) {
15
+ console.log('🧬 PASS 3: Structural Lifting...');
16
+
17
+ const { nodes, edges } = semanticData;
18
+ const liftedNodes = [];
19
+
20
+ let rolesFound = {};
21
+
22
+ for (const node of nodes) {
23
+ // Start with default
24
+ let role = 'module';
25
+ let layer = 'generic';
26
+
27
+ const pathLower = node.id.toLowerCase();
28
+ const filename = node.id.split('/').pop();
29
+
30
+ // 1. Pattern-based Role Inference
31
+ if (pathLower.includes('service') || filename.endsWith('.service.ts')) {
32
+ role = 'service';
33
+ layer = 'logic';
34
+ } else if (pathLower.includes('controller') || filename.endsWith('.controller.ts')) {
35
+ role = 'controller';
36
+ layer = 'api';
37
+ } else if (pathLower.includes('component') || pathLower.includes('.tsx') || pathLower.includes('.jsx')) {
38
+ role = 'component';
39
+ layer = 'ui';
40
+ } else if (pathLower.includes('store') || pathLower.includes('redux') || pathLower.includes('context')) {
41
+ role = 'store';
42
+ layer = 'state';
43
+ } else if (pathLower.includes('util') || pathLower.includes('helper')) {
44
+ role = 'utility';
45
+ layer = 'shared';
46
+ } else if (pathLower.includes('model') || pathLower.includes('entity') || pathLower.includes('dto')) {
47
+ role = 'model';
48
+ layer = 'data';
49
+ } else if (pathLower.includes('config') || pathLower.includes('env')) {
50
+ role = 'config';
51
+ } else if (pathLower.includes('hook') || filename.startsWith('use')) {
52
+ role = 'hook';
53
+ layer = 'ui-logic';
54
+ }
55
+
56
+ // 2. Content-based Role Inference (Refining based on captured facts)
57
+ if (node.facts.di && (node.facts.di.injections.length > 0 || node.facts.di.context.length > 0)) {
58
+ // Heavily dependent file
59
+ if (role === 'module') role = 'consumer';
60
+ }
61
+
62
+ if (node.facts.exports.some(e => e.type === 'class')) {
63
+ // Could be a core definition
64
+ if (role === 'module') role = 'class_def';
65
+ }
66
+
67
+ // 3. Connectivity-based Inference (is it a Hub?)
68
+ // (We can refine this in Pass 4, but simple tagging happens here)
69
+
70
+ // Add to stats
71
+ rolesFound[role] = (rolesFound[role] || 0) + 1;
72
+
73
+ // Create lifted node with invariant indicators preserved
74
+ const liftedNode = {
75
+ ...node,
76
+ structure: {
77
+ role,
78
+ layer
79
+ }
80
+ };
81
+
82
+ // Carry forward invariant indicators if they exist
83
+ if (node.invariantIndicators) {
84
+ liftedNode.invariantIndicators = node.invariantIndicators;
85
+ }
86
+
87
+ liftedNodes.push(liftedNode);
88
+ }
89
+
90
+ console.log(` ✓ Inferred structural roles: ${JSON.stringify(rolesFound)}`);
91
+
92
+ return {
93
+ nodes: liftedNodes,
94
+ edges: edges, // Edges passed through
95
+ symbols: semanticData.symbols // Propagate symbols
96
+ };
97
+ }
98
+
99
+ module.exports = { executePass3 };
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Pass 4: Signal Extraction
3
+ *
4
+ * Objectives:
5
+ * - Calculate graph theory metrics: Blast Radius, Centrality
6
+ * - Identify "High Value" nodes
7
+ * - Compute volatility/coupling signals
8
+ */
9
+
10
+ const { buildReverseDependencyGraph, computeBlastRadius, analyzeCriticality } = require('../core/blastRadius');
11
+ const { InvariantDetector } = require('../core/invariant-detector');
12
+ const { OwnershipResolver } = require('../core/ownership-resolver');
13
+ const { FailureModeSynthesizer } = require('../core/failure-mode-synthesizer');
14
+ const path = require('path');
15
+
16
+ /**
17
+ * Executes Pass 4: Computes signals and metrics
18
+ * @param {Object} liftedData - Output from Pass 3 { nodes, edges }
19
+ * @returns {Promise<Object>} Final structural context ready for JSON
20
+ */
21
+ async function executePass4(liftedData) {
22
+ console.log('🧠 PASS 4: Signal Extraction & Metrics...');
23
+
24
+ const { nodes, edges } = liftedData;
25
+
26
+ // 1. Prepare Data for Existing BlastRadius Logic
27
+ // Existing logic expects { nodes: [{id: ...}], edges: [{source, target}] }
28
+ // Our nodes have 'id' matching the edges, so it's compatible.
29
+
30
+ // Create a temporary compliant object for the legacy algo
31
+ const architectureMap = { nodes, edges };
32
+
33
+ // 2. Compute Blast Radius
34
+ const reverseGraph = buildReverseDependencyGraph(architectureMap);
35
+ const blastRadiusMap = computeBlastRadius(reverseGraph);
36
+
37
+ // 3. Compute Criticality (includes dependency strength)
38
+ // Note: analyzeCriticality expects nodes to have specific metadata structure
39
+ // We might need to ensure compatibility, but let's assume 'id' is enough for basic graph stuff
40
+ // and we inject the score back.
41
+
42
+ // We need to inject .metadata for analyzeCriticality to work fully if it checks for functions
43
+ // Our nodes have .facts, so let's temporarily alias it if needed, or just rely on graph structure.
44
+ nodes.forEach(n => {
45
+ n.metadata = n.facts; // Map facts to metadata for compatibility with legacy helpers
46
+ n.metadata.blast_radius = blastRadiusMap[n.id] || 0;
47
+ });
48
+
49
+ const criticalityList = analyzeCriticality(blastRadiusMap, reverseGraph, nodes);
50
+ const criticalityMap = new Map(criticalityList.map(c => [c.file, c]));
51
+
52
+ // Pre-calculate degrees to avoid O(N*E) nested loops
53
+ const outDegreeMap = new Map();
54
+ const inDegreeMap = new Map();
55
+ edges.forEach(e => {
56
+ outDegreeMap.set(e.source, (outDegreeMap.get(e.source) || 0) + 1);
57
+ inDegreeMap.set(e.target, (inDegreeMap.get(e.target) || 0) + 1);
58
+ });
59
+
60
+ // 4. Enrich Nodes with Signals
61
+ const finalNodes = nodes.map(node => {
62
+ const signals = {
63
+ blast_radius: blastRadiusMap[node.id] || 0,
64
+ criticality: criticalityMap.get(node.id)?.criticalityScore || 0,
65
+ is_hub: criticalityMap.get(node.id)?.isHub || false,
66
+ outgoing_deps: outDegreeMap.get(node.id) || 0,
67
+ incoming_deps: inDegreeMap.get(node.id) || 0
68
+ };
69
+
70
+ // Return node with clear separation of concerns
71
+ const finalNode = {
72
+ id: node.id,
73
+ type: node.type,
74
+ filePath: node.filePath, // Internal absolute path
75
+
76
+ // The 3 Pillars of Arcvision
77
+ role: node.structure.role, // From Pass 3
78
+ layer: node.structure.layer, // From Pass 3
79
+ signals: signals, // From Pass 4
80
+
81
+ // Deep Code Intelligence (from Pass 1 & 2)
82
+ intelligence: {
83
+ ...node.facts,
84
+ // Add resolved outbound connections for easy traversal
85
+ connections: edges.filter(e => e.source === node.id).map(e => ({
86
+ target: e.target,
87
+ type: e.type,
88
+ confidence: e.confidence
89
+ }))
90
+ }
91
+ };
92
+
93
+ // Carry forward invariant indicators if they exist
94
+ if (node.invariantIndicators) {
95
+ finalNode.invariantIndicators = node.invariantIndicators;
96
+ }
97
+
98
+ return finalNode;
99
+ });
100
+
101
+ // Calculate additional metrics for authority core detection
102
+ const enhancedCriticalityList = finalNodes.map(node => ({
103
+ file: node.id,
104
+ criticalityScore: node.signals.criticality,
105
+ blastRadius: node.signals.blast_radius,
106
+ dependencies: node.signals.incoming_deps,
107
+ isHub: node.signals.is_hub
108
+ }));
109
+
110
+ // 5. Final Stats
111
+ const totalFiles = finalNodes.length;
112
+ const maxBlast = Math.max(...finalNodes.map(n => n.signals.blast_radius));
113
+ console.log(` ✓ Signals computed. Max Blast Radius: ${maxBlast}`);
114
+
115
+ // Context Surface (Top critical files)
116
+ const contextSurface = enhancedCriticalityList.slice(0, 5).map(c => ({
117
+ file: c.file,
118
+ score: c.criticalityScore,
119
+ reason: `Blast: ${c.blastRadius}, Deps: ${c.dependencies}`
120
+ }));
121
+
122
+ // Initialize components for new features
123
+ const invariantDetector = new InvariantDetector();
124
+ const ownershipResolver = new OwnershipResolver();
125
+ const failureModeSynthesizer = new FailureModeSynthesizer();
126
+
127
+ // Generate invariants
128
+ const invariants = await invariantDetector.detectInvariants(
129
+ { nodes: finalNodes, edges: edges },
130
+ process.cwd()
131
+ );
132
+
133
+ // Resolve ownership
134
+ const ownership = ownershipResolver.resolveOwnership(
135
+ { nodes: finalNodes, edges: edges }
136
+ );
137
+
138
+ // Synthesize failure modes
139
+ const failureModes = failureModeSynthesizer.synthesizeFailureModes(
140
+ { nodes: finalNodes, edges: edges },
141
+ invariants
142
+ );
143
+
144
+ // Generate decision guidance
145
+ const decisionGuidance = generateDecisionGuidance(invariants, failureModes);
146
+
147
+ return {
148
+ nodes: finalNodes,
149
+ edges: edges,
150
+ symbols: liftedData.symbols, // Propagate symbols
151
+ contextSurface,
152
+ invariants,
153
+ ownership,
154
+ failure_modes: failureModes,
155
+ decision_guidance: decisionGuidance
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Generate decision guidance based on invariants and failure modes
161
+ */
162
+ function generateDecisionGuidance(invariants, failureModes) {
163
+ if (!invariants || invariants.length === 0) {
164
+ return {};
165
+ }
166
+
167
+ // Find the most critical invariant that has been violated
168
+ const mostCriticalInvariant = invariants
169
+ .filter(inv => inv.confidence > 0.7)
170
+ .sort((a, b) => (b.confidence || 0) - (a.confidence || 0))[0];
171
+
172
+ if (!mostCriticalInvariant) {
173
+ return {};
174
+ }
175
+
176
+ // Find related failure modes
177
+ const relatedFailures = failureModes.filter(fm =>
178
+ fm.broken_invariant === mostCriticalInvariant.id
179
+ );
180
+
181
+ // Generate recommended action
182
+ const recommendedAction = {
183
+ fix_type: 'invariant_restoration',
184
+ location: mostCriticalInvariant.owner || 'unknown',
185
+ action: `Address invariant violation: ${mostCriticalInvariant.statement}`,
186
+ risk_if_ignored: 'System instability, unpredictable behavior, or cascading failures'
187
+ };
188
+
189
+ // If there are specific failure modes, add more specific guidance
190
+ if (relatedFailures.length > 0) {
191
+ const mostCriticalFailure = relatedFailures[0];
192
+ recommendedAction.action = `Fix ${mostCriticalFailure.trigger} to restore invariant: ${mostCriticalInvariant.statement}`;
193
+ recommendedAction.location = mostCriticalFailure.root_cause || recommendedAction.location;
194
+ }
195
+
196
+ return {
197
+ recommended_action: recommendedAction
198
+ };
199
+ }
200
+
201
+ module.exports = { executePass4 };