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.
- package/ARCVISION_DIRECTORY_STRUCTURE.md +104 -0
- package/CLI_STRUCTURE.md +110 -0
- package/CONFIGURATION.md +119 -0
- package/IMPLEMENTATION_SUMMARY.md +99 -0
- package/README.md +149 -89
- package/architecture.authority.ledger.json +46 -0
- package/arcvision-0.2.3.tgz +0 -0
- package/arcvision-0.2.4.tgz +0 -0
- package/arcvision-0.2.5.tgz +0 -0
- package/arcvision.context.diff.json +2181 -0
- package/arcvision.context.json +1021 -0
- package/arcvision.context.v1.json +2163 -0
- package/arcvision.context.v2.json +2173 -0
- package/arcvision_context/README.md +93 -0
- package/arcvision_context/architecture.authority.ledger.json +83 -0
- package/arcvision_context/arcvision.context.json +6884 -0
- package/debug-cycle-detection.js +56 -0
- package/dist/index.js +1626 -25
- package/docs/ENHANCED_ACCURACY_SAFETY_PROTOCOL.md +172 -0
- package/docs/accuracy-enhancement-artifacts/enhanced-validation-config.json +98 -0
- package/docs/acig-robustness-guide.md +164 -0
- package/docs/authoritative-gate-implementation.md +168 -0
- package/docs/cli-strengthening-summary.md +232 -0
- package/docs/invariant-system-summary.md +100 -0
- package/docs/invariant-system.md +112 -0
- package/generate_large_test.js +42 -0
- package/large_test_repo.json +1 -0
- package/output1.json +2163 -0
- package/output2.json +2163 -0
- package/package.json +46 -36
- package/scan_calcom_report.txt +0 -0
- package/scan_leafmint_report.txt +0 -0
- package/scan_output.txt +0 -0
- package/scan_trigger_report.txt +0 -0
- package/schema/arcvision_context_schema_v1.json +136 -1
- package/src/arcvision-guard.js +433 -0
- package/src/core/authority-core-detector.js +382 -0
- package/src/core/authority-ledger.js +300 -0
- package/src/core/blastRadius.js +299 -0
- package/src/core/call-resolver.js +196 -0
- package/src/core/change-evaluator.js +509 -0
- package/src/core/change-evaluator.js.backup +424 -0
- package/src/core/change-evaluator.ts +285 -0
- package/src/core/chunked-uploader.js +180 -0
- package/src/core/circular-dependency-detector.js +404 -0
- package/src/core/cli-error-handler.js +458 -0
- package/src/core/cli-validator.js +458 -0
- package/src/core/compression.js +64 -0
- package/src/core/context_builder.js +741 -0
- package/src/core/dependency-manager.js +134 -0
- package/src/core/di-detector.js +202 -0
- package/src/core/diff-analyzer.js +76 -0
- package/src/core/example-invariants.js +135 -0
- package/src/core/failure-mode-synthesizer.js +341 -0
- package/src/core/invariant-analyzer.js +294 -0
- package/src/core/invariant-detector.js +548 -0
- package/src/core/invariant-enforcer.js +171 -0
- package/src/core/invariant-evaluation-utils.js +172 -0
- package/src/core/invariant-hooks.js +152 -0
- package/src/core/invariant-integration-example.js +186 -0
- package/src/core/invariant-registry.js +298 -0
- package/src/core/invariant-registry.ts +100 -0
- package/src/core/invariant-types.js +66 -0
- package/src/core/invariants-index.js +88 -0
- package/src/core/method-tracker.js +170 -0
- package/src/core/override-handler.js +304 -0
- package/src/core/ownership-resolver.js +227 -0
- package/src/core/parser-enhanced.js +80 -0
- package/src/core/parser.js +610 -0
- package/src/core/path-resolver.js +240 -0
- package/src/core/pattern-matcher.js +246 -0
- package/src/core/progress-tracker.js +71 -0
- package/src/core/react-nextjs-detector.js +245 -0
- package/src/core/readme-generator.js +167 -0
- package/src/core/retry-handler.js +57 -0
- package/src/core/scanner.js +289 -0
- package/src/core/semantic-analyzer.js +204 -0
- package/src/core/structural-context-owner.js +442 -0
- package/src/core/symbol-indexer.js +164 -0
- package/src/core/tsconfig-utils.js +73 -0
- package/src/core/type-analyzer.js +272 -0
- package/src/core/watcher.js +18 -0
- package/src/core/workspace-scanner.js +88 -0
- package/src/engine/context_builder.js +280 -0
- package/src/engine/context_sorter.js +59 -0
- package/src/engine/context_validator.js +200 -0
- package/src/engine/id-generator.js +16 -0
- package/src/engine/pass1_facts.js +260 -0
- package/src/engine/pass2_semantics.js +333 -0
- package/src/engine/pass3_lifter.js +99 -0
- package/src/engine/pass4_signals.js +201 -0
- package/src/index.js +830 -0
- package/src/plugins/express-plugin.js +48 -0
- package/src/plugins/plugin-manager.js +58 -0
- package/src/plugins/react-plugin.js +54 -0
- package/temp_original.js +0 -0
- package/test/determinism-test.js +83 -0
- package/test-authoritative-context.js +53 -0
- package/test-real-authoritative-context.js +118 -0
- package/test-upload-enhancements.js +111 -0
- package/test_repos/allowed-clean-architecture/.arcvision/invariants.json +57 -0
- package/test_repos/allowed-clean-architecture/adapters/controllers/UserController.js +95 -0
- package/test_repos/allowed-clean-architecture/adapters/http/HttpServer.js +78 -0
- package/test_repos/allowed-clean-architecture/application/dtos/CreateUserRequest.js +37 -0
- package/test_repos/allowed-clean-architecture/application/services/UserService.js +61 -0
- package/test_repos/allowed-clean-architecture/arcvision_context/README.md +93 -0
- package/test_repos/allowed-clean-architecture/arcvision_context/arcvision.context.json +2796 -0
- package/test_repos/allowed-clean-architecture/domain/interfaces/UserRepository.js +25 -0
- package/test_repos/allowed-clean-architecture/domain/models/User.js +39 -0
- package/test_repos/allowed-clean-architecture/index.js +45 -0
- package/test_repos/allowed-clean-architecture/infrastructure/database/DatabaseConnection.js +56 -0
- package/test_repos/allowed-clean-architecture/infrastructure/repositories/InMemoryUserRepository.js +61 -0
- package/test_repos/allowed-clean-architecture/package.json +15 -0
- package/test_repos/blocked-legacy-monolith/.arcvision/invariants.json +78 -0
- package/test_repos/blocked-legacy-monolith/arcvision_context/README.md +93 -0
- package/test_repos/blocked-legacy-monolith/arcvision_context/arcvision.context.json +2882 -0
- package/test_repos/blocked-legacy-monolith/database/dbConnection.js +35 -0
- package/test_repos/blocked-legacy-monolith/index.js +38 -0
- package/test_repos/blocked-legacy-monolith/modules/emailService.js +31 -0
- package/test_repos/blocked-legacy-monolith/modules/paymentProcessor.js +37 -0
- package/test_repos/blocked-legacy-monolith/package.json +15 -0
- package/test_repos/blocked-legacy-monolith/shared/utils.js +19 -0
- package/test_repos/blocked-legacy-monolith/utils/helpers.js +23 -0
- package/test_repos/risky-microservices-concerns/.arcvision/invariants.json +69 -0
- package/test_repos/risky-microservices-concerns/arcvision_context/README.md +93 -0
- package/test_repos/risky-microservices-concerns/arcvision_context/arcvision.context.json +3070 -0
- package/test_repos/risky-microservices-concerns/common/utils.js +77 -0
- package/test_repos/risky-microservices-concerns/gateways/apiGateway.js +84 -0
- package/test_repos/risky-microservices-concerns/index.js +20 -0
- package/test_repos/risky-microservices-concerns/libs/deprecatedHelper.js +36 -0
- package/test_repos/risky-microservices-concerns/package.json +15 -0
- package/test_repos/risky-microservices-concerns/services/orderService.js +42 -0
- package/test_repos/risky-microservices-concerns/services/userService.js +48 -0
- 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 };
|