arcvision 0.2.14 → 0.2.16
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,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type Dependency Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Tracks TypeScript type-only dependencies including:
|
|
5
|
+
* - Type-only imports
|
|
6
|
+
* - Interface extensions
|
|
7
|
+
* - Interface implementations
|
|
8
|
+
* - Generic type parameters
|
|
9
|
+
* - Return type dependencies
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const traverse = require('@babel/traverse').default;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Extract type-only imports from AST
|
|
16
|
+
* @param {Object} ast - Babel AST
|
|
17
|
+
* @returns {Array} Array of type imports
|
|
18
|
+
*/
|
|
19
|
+
function extractTypeImports(ast) {
|
|
20
|
+
const typeImports = [];
|
|
21
|
+
|
|
22
|
+
if (!ast) return typeImports;
|
|
23
|
+
|
|
24
|
+
traverse(ast, {
|
|
25
|
+
ImportDeclaration(path) {
|
|
26
|
+
const node = path.node;
|
|
27
|
+
|
|
28
|
+
// Check if it's a type-only import: import type { ... }
|
|
29
|
+
if (node.importKind === 'type') {
|
|
30
|
+
const specifiers = [];
|
|
31
|
+
node.specifiers.forEach(spec => {
|
|
32
|
+
if (spec.imported) {
|
|
33
|
+
specifiers.push({
|
|
34
|
+
imported: spec.imported.name || spec.imported.value,
|
|
35
|
+
local: spec.local.name
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
typeImports.push({
|
|
41
|
+
source: node.source.value,
|
|
42
|
+
specifiers,
|
|
43
|
+
importKind: 'type',
|
|
44
|
+
loc: node.loc
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check for individual type specifiers: import { type User } from '...'
|
|
49
|
+
else {
|
|
50
|
+
const typeSpecifiers = [];
|
|
51
|
+
node.specifiers.forEach(spec => {
|
|
52
|
+
if (spec.importKind === 'type' && spec.imported) {
|
|
53
|
+
typeSpecifiers.push({
|
|
54
|
+
imported: spec.imported.name || spec.imported.value,
|
|
55
|
+
local: spec.local.name
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (typeSpecifiers.length > 0) {
|
|
61
|
+
typeImports.push({
|
|
62
|
+
source: node.source.value,
|
|
63
|
+
specifiers: typeSpecifiers,
|
|
64
|
+
importKind: 'mixed',
|
|
65
|
+
loc: node.loc
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return typeImports;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Extract interface dependencies (extends, implements)
|
|
77
|
+
* @param {Object} ast - Babel AST
|
|
78
|
+
* @returns {Array} Array of interface dependencies
|
|
79
|
+
*/
|
|
80
|
+
function extractInterfaceDependencies(ast) {
|
|
81
|
+
const interfaceDeps = [];
|
|
82
|
+
|
|
83
|
+
if (!ast) return interfaceDeps;
|
|
84
|
+
|
|
85
|
+
traverse(ast, {
|
|
86
|
+
// Interface extends
|
|
87
|
+
TSInterfaceDeclaration(path) {
|
|
88
|
+
const node = path.node;
|
|
89
|
+
const interfaceName = node.id.name;
|
|
90
|
+
|
|
91
|
+
if (node.extends && node.extends.length > 0) {
|
|
92
|
+
node.extends.forEach(ext => {
|
|
93
|
+
const extendedInterface = extractTypeName(ext.expression);
|
|
94
|
+
if (extendedInterface) {
|
|
95
|
+
interfaceDeps.push({
|
|
96
|
+
interface: interfaceName,
|
|
97
|
+
extends: extendedInterface,
|
|
98
|
+
type: 'interface_extends',
|
|
99
|
+
loc: node.loc
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
// Class implements
|
|
107
|
+
ClassDeclaration(path) {
|
|
108
|
+
const node = path.node;
|
|
109
|
+
const className = node.id ? node.id.name : 'Anonymous';
|
|
110
|
+
|
|
111
|
+
if (node.implements && node.implements.length > 0) {
|
|
112
|
+
node.implements.forEach(impl => {
|
|
113
|
+
const implementedInterface = extractTypeName(impl.expression);
|
|
114
|
+
if (implementedInterface) {
|
|
115
|
+
interfaceDeps.push({
|
|
116
|
+
class: className,
|
|
117
|
+
implements: implementedInterface,
|
|
118
|
+
type: 'class_implements',
|
|
119
|
+
loc: node.loc
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return interfaceDeps;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Extract generic type dependencies
|
|
132
|
+
* @param {Object} ast - Babel AST
|
|
133
|
+
* @returns {Array} Array of generic type usages
|
|
134
|
+
*/
|
|
135
|
+
function extractGenericDependencies(ast) {
|
|
136
|
+
const genericDeps = [];
|
|
137
|
+
|
|
138
|
+
if (!ast) return genericDeps;
|
|
139
|
+
|
|
140
|
+
traverse(ast, {
|
|
141
|
+
// Function return types
|
|
142
|
+
FunctionDeclaration(path) {
|
|
143
|
+
const node = path.node;
|
|
144
|
+
const functionName = node.id ? node.id.name : 'anonymous';
|
|
145
|
+
|
|
146
|
+
if (node.returnType) {
|
|
147
|
+
const returnTypeName = extractComplexTypeName(node.returnType.typeAnnotation);
|
|
148
|
+
if (returnTypeName) {
|
|
149
|
+
genericDeps.push({
|
|
150
|
+
function: functionName,
|
|
151
|
+
returnType: returnTypeName,
|
|
152
|
+
type: 'return_type',
|
|
153
|
+
loc: node.loc
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
// Arrow function return types
|
|
160
|
+
ArrowFunctionExpression(path) {
|
|
161
|
+
const node = path.node;
|
|
162
|
+
|
|
163
|
+
if (node.returnType) {
|
|
164
|
+
const returnTypeName = extractComplexTypeName(node.returnType.typeAnnotation);
|
|
165
|
+
if (returnTypeName) {
|
|
166
|
+
genericDeps.push({
|
|
167
|
+
function: 'arrow_function',
|
|
168
|
+
returnType: returnTypeName,
|
|
169
|
+
type: 'return_type',
|
|
170
|
+
loc: node.loc
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
// Variable type annotations
|
|
177
|
+
VariableDeclarator(path) {
|
|
178
|
+
const node = path.node;
|
|
179
|
+
const varName = node.id.name;
|
|
180
|
+
|
|
181
|
+
if (node.id.typeAnnotation) {
|
|
182
|
+
const typeName = extractComplexTypeName(node.id.typeAnnotation.typeAnnotation);
|
|
183
|
+
if (typeName) {
|
|
184
|
+
genericDeps.push({
|
|
185
|
+
variable: varName,
|
|
186
|
+
type: typeName,
|
|
187
|
+
kind: 'variable_type',
|
|
188
|
+
loc: node.loc
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return genericDeps;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Analyze all type dependencies in a file
|
|
200
|
+
* @param {Object} ast - Babel AST
|
|
201
|
+
* @returns {Object} All type dependencies
|
|
202
|
+
*/
|
|
203
|
+
function analyzeTypeDependencies(ast) {
|
|
204
|
+
if (!ast) {
|
|
205
|
+
return {
|
|
206
|
+
typeImports: [],
|
|
207
|
+
interfaceDependencies: [],
|
|
208
|
+
genericDependencies: []
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
typeImports: extractTypeImports(ast),
|
|
214
|
+
interfaceDependencies: extractInterfaceDependencies(ast),
|
|
215
|
+
genericDependencies: extractGenericDependencies(ast)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Helper: Extract type name from TypeScript type node
|
|
221
|
+
*/
|
|
222
|
+
function extractTypeName(node) {
|
|
223
|
+
if (!node) return null;
|
|
224
|
+
|
|
225
|
+
if (node.type === 'Identifier') {
|
|
226
|
+
return node.name;
|
|
227
|
+
} else if (node.type === 'TSTypeReference' && node.typeName) {
|
|
228
|
+
if (node.typeName.type === 'Identifier') {
|
|
229
|
+
return node.typeName.name;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Helper: Extract complex type names (including generics)
|
|
238
|
+
*/
|
|
239
|
+
function extractComplexTypeName(node) {
|
|
240
|
+
if (!node) return null;
|
|
241
|
+
|
|
242
|
+
if (node.type === 'TSTypeReference' && node.typeName) {
|
|
243
|
+
let baseName = extractTypeName(node);
|
|
244
|
+
|
|
245
|
+
// Handle generic parameters
|
|
246
|
+
if (node.typeParameters && node.typeParameters.params) {
|
|
247
|
+
const params = node.typeParameters.params
|
|
248
|
+
.map(p => extractComplexTypeName(p))
|
|
249
|
+
.filter(Boolean)
|
|
250
|
+
.join(', ');
|
|
251
|
+
if (params) {
|
|
252
|
+
baseName += `<${params}>`;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return baseName;
|
|
257
|
+
} else if (node.type === 'Identifier') {
|
|
258
|
+
return node.name;
|
|
259
|
+
} else if (node.type === 'TSArrayType') {
|
|
260
|
+
const elementType = extractComplexTypeName(node.elementType);
|
|
261
|
+
return elementType ? `${elementType}[]` : null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
module.exports = {
|
|
268
|
+
extractTypeImports,
|
|
269
|
+
extractInterfaceDependencies,
|
|
270
|
+
extractGenericDependencies,
|
|
271
|
+
analyzeTypeDependencies
|
|
272
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const chokidar = require('chokidar');
|
|
2
|
+
|
|
3
|
+
function watch(directory, callback) {
|
|
4
|
+
const watcher = chokidar.watch(directory, {
|
|
5
|
+
ignored: [/(^|[\/\\])\../, '**/node_modules/**'], // ignore dotfiles
|
|
6
|
+
persistent: true,
|
|
7
|
+
ignoreInitial: true
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
watcher
|
|
11
|
+
.on('add', path => callback('add', path))
|
|
12
|
+
.on('change', path => callback('change', path))
|
|
13
|
+
.on('unlink', path => callback('unlink', path));
|
|
14
|
+
|
|
15
|
+
return watcher;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = { watch };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { loadTSConfig } = require('./tsconfig-utils');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Scans the workspace to build a map of package names to their locations
|
|
7
|
+
* and loads tsconfig configurations.
|
|
8
|
+
*/
|
|
9
|
+
class WorkspaceScanner {
|
|
10
|
+
constructor(rootPath) {
|
|
11
|
+
this.rootPath = path.resolve(rootPath);
|
|
12
|
+
this.workspaceMap = new Map(); // packageName -> absolutePath
|
|
13
|
+
this.tsconfigMap = new Map(); // workspaceRoot -> tsconfig object
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Performs the scan and returns the context.
|
|
18
|
+
* @returns {Object} { workspaceMap, tsconfigMap }
|
|
19
|
+
*/
|
|
20
|
+
scan() {
|
|
21
|
+
console.log(`Starting workspace scan at ${this.rootPath}`);
|
|
22
|
+
this._scanRecursively(this.rootPath);
|
|
23
|
+
|
|
24
|
+
// Sort maps for determinism in output/debugging
|
|
25
|
+
const sortedWorkspaceMap = new Map([...this.workspaceMap.entries()].sort());
|
|
26
|
+
const sortedTsconfigMap = new Map([...this.tsconfigMap.entries()].sort());
|
|
27
|
+
|
|
28
|
+
console.log(`Workspace scan complete. Found ${sortedWorkspaceMap.size} packages.`);
|
|
29
|
+
return {
|
|
30
|
+
workspaceMap: sortedWorkspaceMap,
|
|
31
|
+
tsconfigMap: sortedTsconfigMap
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_scanRecursively(currentPath) {
|
|
36
|
+
let entries;
|
|
37
|
+
try {
|
|
38
|
+
entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.error(`Failed to read directory ${currentPath}: ${e.message}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Sort entries for deterministic scanning order
|
|
45
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
46
|
+
|
|
47
|
+
// Check for tsconfig in this directory
|
|
48
|
+
// We check this for every directory we visit.
|
|
49
|
+
const tsConfig = loadTSConfig(currentPath);
|
|
50
|
+
if (tsConfig) {
|
|
51
|
+
this.tsconfigMap.set(currentPath, tsConfig);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
56
|
+
|
|
57
|
+
if (entry.isDirectory()) {
|
|
58
|
+
// Skip node_modules and hidden directories (starting with .)
|
|
59
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
this._scanRecursively(fullPath);
|
|
63
|
+
} else if (entry.name === 'package.json') {
|
|
64
|
+
this._processPackageJson(fullPath);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
_processPackageJson(packageJsonPath) {
|
|
70
|
+
try {
|
|
71
|
+
const content = fs.readFileSync(packageJsonPath, 'utf-8');
|
|
72
|
+
const pkg = JSON.parse(content);
|
|
73
|
+
|
|
74
|
+
if (pkg.name) {
|
|
75
|
+
if (this.workspaceMap.has(pkg.name)) {
|
|
76
|
+
// console.warn(`Duplicate package name found: ${pkg.name} at ${packageJsonPath}. Keeping ${this.workspaceMap.get(pkg.name)}`);
|
|
77
|
+
} else {
|
|
78
|
+
this.workspaceMap.set(pkg.name, path.dirname(packageJsonPath));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch (e) {
|
|
82
|
+
// Failures logged, never fatal
|
|
83
|
+
console.warn(`Failed to parse package.json at ${packageJsonPath}: ${e.message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { WorkspaceScanner };
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const crypto = require('crypto');
|
|
3
|
+
const { stableId } = require('./id-generator');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generate integrity hash for the context
|
|
7
|
+
* @param {Array} nodes - List of nodes
|
|
8
|
+
* @param {Array} edges - List of edges
|
|
9
|
+
* @returns {string} SHA256 hash
|
|
10
|
+
*/
|
|
11
|
+
function generateIntegrityHash(nodes, edges) {
|
|
12
|
+
const content = JSON.stringify({
|
|
13
|
+
nodes: nodes.map(n => ({
|
|
14
|
+
id: n.id,
|
|
15
|
+
path: n.path,
|
|
16
|
+
role: n.role
|
|
17
|
+
})).sort((a, b) => a.id.localeCompare(b.id)),
|
|
18
|
+
edges: edges.map(e => ({
|
|
19
|
+
from: e.from,
|
|
20
|
+
to: e.to,
|
|
21
|
+
relation: e.relation
|
|
22
|
+
})).sort((a, b) => {
|
|
23
|
+
if (a.from !== b.from) return a.from.localeCompare(b.from);
|
|
24
|
+
return a.to.localeCompare(b.to);
|
|
25
|
+
})
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build the context object that conforms to the Arcvision schema
|
|
33
|
+
* @param {Array} files - List of files from scanner
|
|
34
|
+
* @param {Array} edges - List of edges from scanner
|
|
35
|
+
* @param {Object} options - Options including directory path and project name
|
|
36
|
+
* @returns {Object} - Context object conforming to schema
|
|
37
|
+
*/
|
|
38
|
+
function buildContext(files, edges, options = {}) {
|
|
39
|
+
const {
|
|
40
|
+
directory = '.',
|
|
41
|
+
projectName = 'arcvision',
|
|
42
|
+
language = 'javascript'
|
|
43
|
+
} = options;
|
|
44
|
+
|
|
45
|
+
// Build nodes from files
|
|
46
|
+
const nodes = files.map(file => {
|
|
47
|
+
// Use the node's id field which is already the normalized relative path
|
|
48
|
+
const normalizedPath = file.id;
|
|
49
|
+
|
|
50
|
+
// Determine role based on multiple factors for better accuracy
|
|
51
|
+
const hasFunctions = file.metadata.functions && file.metadata.functions.length > 0;
|
|
52
|
+
const hasClasses = file.metadata.classes && file.metadata.classes.length > 0;
|
|
53
|
+
const hasExports = file.metadata.exports && file.metadata.exports.length > 0;
|
|
54
|
+
const hasApiCalls = file.metadata.apiCalls && file.metadata.apiCalls.length > 0;
|
|
55
|
+
|
|
56
|
+
let role = 'Structure';
|
|
57
|
+
if (hasFunctions || hasClasses || hasApiCalls) {
|
|
58
|
+
role = 'Implementation';
|
|
59
|
+
} else if (hasExports) {
|
|
60
|
+
role = 'Interface';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Extract dependencies more accurately and remove duplicates
|
|
64
|
+
const dependencies = [];
|
|
65
|
+
const uniqueDeps = new Set();
|
|
66
|
+
if (file.metadata.imports && Array.isArray(file.metadata.imports)) {
|
|
67
|
+
file.metadata.imports.forEach(imp => {
|
|
68
|
+
if (imp.source && typeof imp.source === 'string') {
|
|
69
|
+
// Only add unique dependencies
|
|
70
|
+
if (!uniqueDeps.has(imp.source)) {
|
|
71
|
+
uniqueDeps.add(imp.source);
|
|
72
|
+
dependencies.push(imp.source);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
id: stableId(normalizedPath),
|
|
80
|
+
type: 'file',
|
|
81
|
+
path: normalizedPath,
|
|
82
|
+
role: role,
|
|
83
|
+
dependencies: dependencies,
|
|
84
|
+
blast_radius: file.metadata.blast_radius || 0
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Build edges from dependencies
|
|
89
|
+
const schemaEdges = [];
|
|
90
|
+
for (const edge of edges) {
|
|
91
|
+
// Find source and target nodes to get their stable IDs
|
|
92
|
+
const sourceNode = nodes.find(n => n.path === edge.source);
|
|
93
|
+
const targetNode = nodes.find(n => n.path === edge.target);
|
|
94
|
+
|
|
95
|
+
// Include edges where source exists (even if target doesn't exist as a node)
|
|
96
|
+
if (sourceNode) {
|
|
97
|
+
// Normalize the edge type to fit the schema
|
|
98
|
+
let relationType = 'imports';
|
|
99
|
+
|
|
100
|
+
// Handle import-related types
|
|
101
|
+
if (edge.type === 'imports' || edge.type === 'require' || edge.type === 'export-from' ||
|
|
102
|
+
edge.type === 'export-all' || edge.type === 'dynamic-import' || edge.type === 'require-assignment') {
|
|
103
|
+
relationType = 'imports';
|
|
104
|
+
}
|
|
105
|
+
// Handle call-related types (NEW)
|
|
106
|
+
else if (edge.type === 'function_call' || edge.type === 'method_call') {
|
|
107
|
+
relationType = 'calls';
|
|
108
|
+
}
|
|
109
|
+
// Handle constructor calls (NEW) - map to calls for schema compliance
|
|
110
|
+
else if (edge.type === 'constructor_call') {
|
|
111
|
+
relationType = 'calls';
|
|
112
|
+
}
|
|
113
|
+
// Handle component usage (NEW) - map to imports for schema compliance
|
|
114
|
+
else if (edge.type === 'component_usage' || edge.type === 'jsx_component' || edge.type === 'jsx_member_component') {
|
|
115
|
+
relationType = 'imports';
|
|
116
|
+
}
|
|
117
|
+
// Handle existing types
|
|
118
|
+
else if (edge.type === 'calls') {
|
|
119
|
+
relationType = 'calls';
|
|
120
|
+
} else if (edge.type === 'owns') {
|
|
121
|
+
relationType = 'owns';
|
|
122
|
+
} else if (edge.type === 'depends_on') {
|
|
123
|
+
relationType = 'depends_on';
|
|
124
|
+
} else {
|
|
125
|
+
// Default to 'imports' for any unknown edge types to ensure schema compliance
|
|
126
|
+
relationType = 'imports';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (targetNode) {
|
|
130
|
+
// Both source and target nodes exist
|
|
131
|
+
schemaEdges.push({
|
|
132
|
+
from: sourceNode.id,
|
|
133
|
+
to: targetNode.id,
|
|
134
|
+
relation: relationType
|
|
135
|
+
});
|
|
136
|
+
} else {
|
|
137
|
+
// Source exists but target doesn't (external dependency like node_modules)
|
|
138
|
+
// We'll still include the edge to preserve the relationship
|
|
139
|
+
// However, since the schema requires valid node IDs, we'll only add if the target is a known node
|
|
140
|
+
// For now, we'll skip edges to non-existent targets to maintain schema compliance
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Calculate metrics
|
|
146
|
+
const metrics = {
|
|
147
|
+
total_files: nodes.length,
|
|
148
|
+
total_nodes: nodes.length,
|
|
149
|
+
total_edges: schemaEdges.length,
|
|
150
|
+
total_imports: schemaEdges.filter(edge => edge.relation === 'imports').length,
|
|
151
|
+
total_calls: schemaEdges.filter(edge => edge.relation === 'calls').length,
|
|
152
|
+
total_owns: schemaEdges.filter(edge => edge.relation === 'owns').length,
|
|
153
|
+
total_depends_on: schemaEdges.filter(edge => edge.relation === 'depends_on').length,
|
|
154
|
+
files_with_functions: nodes.filter(n => n.role === 'Implementation').length
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Build and return the context object
|
|
158
|
+
const context = {
|
|
159
|
+
schema_version: '1.0.0',
|
|
160
|
+
generated_at: new Date().toISOString(),
|
|
161
|
+
system: {
|
|
162
|
+
name: projectName,
|
|
163
|
+
root_path: path.resolve(directory),
|
|
164
|
+
language: language,
|
|
165
|
+
archetype: options.archetype || {
|
|
166
|
+
type: 'general_purpose',
|
|
167
|
+
confidence: 'low',
|
|
168
|
+
description: 'General purpose application',
|
|
169
|
+
authorityPattern: 'distributed_authority'
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
nodes,
|
|
173
|
+
edges: schemaEdges,
|
|
174
|
+
symbols: options.symbols || [],
|
|
175
|
+
metrics,
|
|
176
|
+
contextSurface: options.contextSurface || {},
|
|
177
|
+
|
|
178
|
+
// Authority core detection results
|
|
179
|
+
authority_cores: options.authorityCores || [],
|
|
180
|
+
hidden_coupling: options.hiddenCoupling || [],
|
|
181
|
+
|
|
182
|
+
// Completeness metrics
|
|
183
|
+
completeness_metrics: options.completenessMetrics || {
|
|
184
|
+
totalFilesFound: nodes.length,
|
|
185
|
+
filesAnalyzed: nodes.length,
|
|
186
|
+
coveragePercentage: 100,
|
|
187
|
+
missingCriticalFiles: []
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
// Assumptions about analysis limitations
|
|
191
|
+
assumptions: [
|
|
192
|
+
'Analysis excludes node_modules and build artifacts',
|
|
193
|
+
'TypeScript declaration files (.d.ts) are excluded from analysis',
|
|
194
|
+
'Circular dependencies may not be fully detected',
|
|
195
|
+
'Dynamic imports may not be fully resolved'
|
|
196
|
+
],
|
|
197
|
+
|
|
198
|
+
// Source repository information
|
|
199
|
+
source: {
|
|
200
|
+
repo: options.rootPath || path.resolve(directory),
|
|
201
|
+
commit: options.commitHash || 'unknown',
|
|
202
|
+
generated_at: new Date().toISOString(),
|
|
203
|
+
arcvision_version: options.arcvisionVersion || '1.0.0'
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
// Integrity hash
|
|
207
|
+
integrity: {
|
|
208
|
+
sha256: generateIntegrityHash(nodes, edges)
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
// Structural layers analysis
|
|
212
|
+
structural_layers: {
|
|
213
|
+
runtime_code: {
|
|
214
|
+
status: 'included',
|
|
215
|
+
description: 'JavaScript/TypeScript source files'
|
|
216
|
+
},
|
|
217
|
+
execution_scripts: {
|
|
218
|
+
status: 'included',
|
|
219
|
+
description: 'Lua execution scripts'
|
|
220
|
+
},
|
|
221
|
+
infrastructure_scripts: {
|
|
222
|
+
status: 'excluded',
|
|
223
|
+
description: 'Infrastructure scripts (bash, shell, etc.)'
|
|
224
|
+
},
|
|
225
|
+
configuration: {
|
|
226
|
+
status: 'included',
|
|
227
|
+
description: 'Configuration files (JSON, YAML, etc.)'
|
|
228
|
+
},
|
|
229
|
+
documentation: {
|
|
230
|
+
status: 'excluded',
|
|
231
|
+
description: 'Documentation files'
|
|
232
|
+
},
|
|
233
|
+
assets: {
|
|
234
|
+
status: 'excluded',
|
|
235
|
+
description: 'Asset files (images, fonts, etc.)'
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
// Project envelope
|
|
240
|
+
project_envelope: {
|
|
241
|
+
configuration_files: nodes
|
|
242
|
+
.filter(n => n.path && (n.path.endsWith('.json') || n.path.endsWith('.yaml') || n.path.endsWith('.yml') || n.path.includes('config')))
|
|
243
|
+
.map(n => ({
|
|
244
|
+
path: n.path,
|
|
245
|
+
role: 'configuration'
|
|
246
|
+
})),
|
|
247
|
+
documentation: [],
|
|
248
|
+
build_tools: []
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
// Diff summary placeholder
|
|
252
|
+
diff_summary: null
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// Add additional computed metrics
|
|
256
|
+
context.metrics.files_with_functions = nodes.filter(n => n.role === 'Implementation').length;
|
|
257
|
+
context.metrics.files_with_high_blast_radius = nodes.filter(n => n.blast_radius > 5).length;
|
|
258
|
+
context.metrics.total_dependencies = schemaEdges.length;
|
|
259
|
+
|
|
260
|
+
// Add new canonical sections if provided in options
|
|
261
|
+
if (options.invariants !== undefined) {
|
|
262
|
+
context.invariants = options.invariants;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (options.ownership !== undefined) {
|
|
266
|
+
context.ownership = options.ownership;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (options.failure_modes !== undefined) {
|
|
270
|
+
context.failure_modes = options.failure_modes;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (options.decision_guidance !== undefined) {
|
|
274
|
+
context.decision_guidance = options.decision_guidance;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return context;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
module.exports = { buildContext };
|