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,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sort the context object deterministically to ensure consistent output
|
|
3
|
+
* @param {Object} context - The context object to sort
|
|
4
|
+
* @returns {Object} - The sorted context object
|
|
5
|
+
*/
|
|
6
|
+
function sortContext(context) {
|
|
7
|
+
// Create a copy of the context to avoid modifying the original
|
|
8
|
+
const sortedContext = { ...context };
|
|
9
|
+
|
|
10
|
+
// Sort nodes by ID
|
|
11
|
+
if (Array.isArray(sortedContext.nodes)) {
|
|
12
|
+
sortedContext.nodes = [...sortedContext.nodes].sort((a, b) => a.id.localeCompare(b.id));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Sort edges by from, to, and relation
|
|
16
|
+
if (Array.isArray(sortedContext.edges)) {
|
|
17
|
+
sortedContext.edges = [...sortedContext.edges].sort((a, b) => {
|
|
18
|
+
if (a.from !== b.from) {
|
|
19
|
+
return a.from.localeCompare(b.from);
|
|
20
|
+
}
|
|
21
|
+
if (a.to !== b.to) {
|
|
22
|
+
return a.to.localeCompare(b.to);
|
|
23
|
+
}
|
|
24
|
+
return a.relation.localeCompare(b.relation);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Sort metrics alphabetically
|
|
29
|
+
if (sortedContext.metrics && typeof sortedContext.metrics === 'object') {
|
|
30
|
+
const sortedMetrics = {};
|
|
31
|
+
const keys = Object.keys(sortedContext.metrics).sort();
|
|
32
|
+
for (const key of keys) {
|
|
33
|
+
sortedMetrics[key] = sortedContext.metrics[key];
|
|
34
|
+
}
|
|
35
|
+
sortedContext.metrics = sortedMetrics;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Sort authority cores by authority score descending
|
|
39
|
+
if (Array.isArray(sortedContext.authority_cores)) {
|
|
40
|
+
sortedContext.authority_cores = [...sortedContext.authority_cores]
|
|
41
|
+
.sort((a, b) => (b.authorityScore || 0) - (a.authorityScore || 0));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Sort hidden coupling by count descending
|
|
45
|
+
if (Array.isArray(sortedContext.hidden_coupling)) {
|
|
46
|
+
sortedContext.hidden_coupling = [...sortedContext.hidden_coupling]
|
|
47
|
+
.sort((a, b) => (b.count || 0) - (a.count || 0));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Sort context surface by score descending
|
|
51
|
+
if (sortedContext.contextSurface && Array.isArray(sortedContext.contextSurface)) {
|
|
52
|
+
sortedContext.contextSurface = [...sortedContext.contextSurface]
|
|
53
|
+
.sort((a, b) => (b.score || 0) - (a.score || 0));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return sortedContext;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { sortContext };
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const Ajv = require('ajv');
|
|
4
|
+
|
|
5
|
+
// Create AJV instance with options for detailed error reporting
|
|
6
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validate the context object against the Arcvision schema and invariants using AJV
|
|
10
|
+
* @param {Object} context - The context object to validate
|
|
11
|
+
* @param {string} schemaPath - Path to the schema file (optional, defaults to standard schema)
|
|
12
|
+
* @returns {Object} - Validation result with valid flag and errors
|
|
13
|
+
*/
|
|
14
|
+
function validateContext(context, schemaPath = null) {
|
|
15
|
+
try {
|
|
16
|
+
// Use provided schema path or default to the standard schema
|
|
17
|
+
// When bundled, the schema file is located differently
|
|
18
|
+
let schemaFilePath;
|
|
19
|
+
if (schemaPath) {
|
|
20
|
+
schemaFilePath = schemaPath;
|
|
21
|
+
} else {
|
|
22
|
+
// Try multiple possible locations for the schema file
|
|
23
|
+
const possiblePaths = [
|
|
24
|
+
path.join(__dirname, '../../schema/arcvision_context_schema_v1.json'), // Standard path
|
|
25
|
+
path.join(__dirname, '../schema/arcvision_context_schema_v1.json'), // Bundled path
|
|
26
|
+
path.join(__dirname, 'schema/arcvision_context_schema_v1.json'), // Alternative bundled path
|
|
27
|
+
path.join(process.cwd(), 'schema/arcvision_context_schema_v1.json'), // Current working directory
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
for (const possiblePath of possiblePaths) {
|
|
31
|
+
if (fs.existsSync(possiblePath)) {
|
|
32
|
+
schemaFilePath = possiblePath;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!schemaFilePath) {
|
|
38
|
+
console.warn('⚠️ Schema file not found, using basic validation only');
|
|
39
|
+
// Return early with basic validation if schema not found
|
|
40
|
+
return { valid: true, warnings: ['Schema validation skipped - schema file not found'] };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!fs.existsSync(schemaFilePath)) {
|
|
45
|
+
console.warn(`⚠️ Schema file not found: ${schemaFilePath}, using basic validation only`);
|
|
46
|
+
// Return early with basic validation if schema not found
|
|
47
|
+
return { valid: true, warnings: ['Schema validation skipped - schema file not found'] };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let schema;
|
|
51
|
+
try {
|
|
52
|
+
schema = JSON.parse(fs.readFileSync(schemaFilePath, 'utf8'));
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.warn(`⚠️ Failed to parse schema file: ${error.message}, using basic validation only`);
|
|
55
|
+
return { valid: true, warnings: ['Schema validation skipped - failed to parse schema file'] };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Compile the schema with AJV
|
|
59
|
+
let validate;
|
|
60
|
+
try {
|
|
61
|
+
validate = ajv.compile(schema);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.warn(`⚠️ Failed to compile schema: ${error.message}, using basic validation only`);
|
|
64
|
+
return { valid: true, warnings: ['Schema validation skipped - failed to compile schema'] };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Validate the context against schema first
|
|
68
|
+
const schemaValid = validate(context);
|
|
69
|
+
|
|
70
|
+
if (!schemaValid) {
|
|
71
|
+
// Format errors for human readability
|
|
72
|
+
const errors = (validate.errors || []).map(err => {
|
|
73
|
+
const path = err.instancePath || "root";
|
|
74
|
+
const message = err.message || "schema violation";
|
|
75
|
+
return `${path}: ${message}`;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return { valid: false, errors, warnings: [] };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Now validate against system invariants
|
|
82
|
+
const invariantValidation = validateAgainstInvariants(context);
|
|
83
|
+
|
|
84
|
+
if (!invariantValidation.valid) {
|
|
85
|
+
return {
|
|
86
|
+
valid: false,
|
|
87
|
+
errors: invariantValidation.errors,
|
|
88
|
+
warnings: invariantValidation.warnings || []
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { valid: true, warnings: [] };
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return {
|
|
95
|
+
valid: false,
|
|
96
|
+
errors: [`Validation error: ${error.message}`],
|
|
97
|
+
warnings: [],
|
|
98
|
+
details: null
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Validate the context against its own invariants
|
|
105
|
+
* @param {Object} context - The context object to validate
|
|
106
|
+
* @returns {Object} - Validation result with valid flag and errors
|
|
107
|
+
*/
|
|
108
|
+
function validateAgainstInvariants(context) {
|
|
109
|
+
const errors = [];
|
|
110
|
+
|
|
111
|
+
// Check if invariants exist in the context
|
|
112
|
+
if (!context.invariants || !Array.isArray(context.invariants)) {
|
|
113
|
+
// If no invariants exist, we consider this valid (they may not have been generated yet)
|
|
114
|
+
return { valid: true };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Validate each invariant
|
|
118
|
+
for (const invariant of context.invariants) {
|
|
119
|
+
// Check if required fields exist
|
|
120
|
+
if (!invariant.id) {
|
|
121
|
+
errors.push(`Invariant missing required 'id' field: ${JSON.stringify(invariant)}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!invariant.statement) {
|
|
125
|
+
errors.push(`Invariant with id '${invariant.id}' missing required 'statement' field`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!invariant.scope) {
|
|
129
|
+
errors.push(`Invariant with id '${invariant.id}' missing required 'scope' field`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (typeof invariant.critical_path !== 'boolean' && invariant.critical_path !== undefined) {
|
|
133
|
+
errors.push(`Invariant with id '${invariant.id}' has invalid 'critical_path' field (must be boolean)`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!invariant.status) {
|
|
137
|
+
errors.push(`Invariant with id '${invariant.id}' missing required 'status' field`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Validate status enum
|
|
141
|
+
const validStatuses = ['active', 'disabled', 'pending', 'suspected'];
|
|
142
|
+
if (invariant.status && !validStatuses.includes(invariant.status)) {
|
|
143
|
+
errors.push(`Invariant with id '${invariant.id}' has invalid 'status' field: ${invariant.status}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Validate ownership section if present
|
|
148
|
+
if (context.ownership) {
|
|
149
|
+
for (const [concept, ownershipInfo] of Object.entries(context.ownership)) {
|
|
150
|
+
if (!ownershipInfo.source_of_truth) {
|
|
151
|
+
errors.push(`Ownership for concept '${concept}' missing required 'source_of_truth' field`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (ownershipInfo.derived_in && !Array.isArray(ownershipInfo.derived_in)) {
|
|
155
|
+
errors.push(`Ownership for concept '${concept}' has invalid 'derived_in' field (must be array)`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (ownershipInfo.consumers && !Array.isArray(ownershipInfo.consumers)) {
|
|
159
|
+
errors.push(`Ownership for concept '${concept}' has invalid 'consumers' field (must be array)`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (ownershipInfo.invalidators && !Array.isArray(ownershipInfo.invalidators)) {
|
|
163
|
+
errors.push(`Ownership for concept '${concept}' has invalid 'invalidators' field (must be array)`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Validate failure modes section if present
|
|
169
|
+
if (context.failure_modes && Array.isArray(context.failure_modes)) {
|
|
170
|
+
for (const failureMode of context.failure_modes) {
|
|
171
|
+
if (!failureMode.id) {
|
|
172
|
+
errors.push(`Failure mode missing required 'id' field: ${JSON.stringify(failureMode)}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!failureMode.trigger) {
|
|
176
|
+
errors.push(`Failure mode with id '${failureMode.id}' missing required 'trigger' field`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!failureMode.broken_invariant) {
|
|
180
|
+
errors.push(`Failure mode with id '${failureMode.id}' missing required 'broken_invariant' field`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!failureMode.observed_symptom) {
|
|
184
|
+
errors.push(`Failure mode with id '${failureMode.id}' missing required 'observed_symptom' field`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!failureMode.root_cause) {
|
|
188
|
+
errors.push(`Failure mode with id '${failureMode.id}' missing required 'root_cause' field`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
valid: errors.length === 0,
|
|
195
|
+
errors,
|
|
196
|
+
warnings: []
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
module.exports = { validateContext };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate a stable, deterministic ID based on the file path
|
|
5
|
+
* Same path will always produce the same ID
|
|
6
|
+
* @param {string} relativePath - The relative path to generate ID for
|
|
7
|
+
* @returns {string} - 16-character deterministic ID
|
|
8
|
+
*/
|
|
9
|
+
function stableId(relativePath) {
|
|
10
|
+
// Normalize the path by replacing backslashes with forward slashes and converting to lowercase
|
|
11
|
+
const normalized = relativePath.replace(/\\/g, '/').toLowerCase();
|
|
12
|
+
// Generate SHA-256 hash and take first 16 characters
|
|
13
|
+
return crypto.createHash('sha256').update(normalized).digest('hex').substring(0, 16);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = { stableId };
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass 1: Syntactic Fact Emission
|
|
3
|
+
*
|
|
4
|
+
* Objectives:
|
|
5
|
+
* - content-agnostic parsing (delegates to parser-enhanced)
|
|
6
|
+
* - emits raw indisputable facts: "File A imports string 'B'", "File A calls method 'foo'"
|
|
7
|
+
* - NO resolution of what 'B' is (that's Pass 2)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { glob } = require('glob');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const parser = require('../core/parser-enhanced');
|
|
14
|
+
const pluginManager = require('../plugins/plugin-manager');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Executes Pass 1: Scans directory and emits raw facts
|
|
18
|
+
* @param {string} directory - Root directory to scan
|
|
19
|
+
* @param {Object} options - Scan options
|
|
20
|
+
* @returns {Promise<Array>} List of file nodes with raw metadata (facts)
|
|
21
|
+
*/
|
|
22
|
+
async function executePass1(directory, options = {}) {
|
|
23
|
+
console.log('🧱 PASS 1: Syntactic Fact Extraction...');
|
|
24
|
+
|
|
25
|
+
// Normalize helper
|
|
26
|
+
const normalize = p => p.replace(/\\/g, '/');
|
|
27
|
+
|
|
28
|
+
const scanOptions = {
|
|
29
|
+
ignore: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**', ...options.ignore || []],
|
|
30
|
+
cwd: directory,
|
|
31
|
+
absolute: true
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Load plugins
|
|
35
|
+
const pluginDir = path.join(__dirname, '../plugins');
|
|
36
|
+
if (fs.existsSync(pluginDir)) {
|
|
37
|
+
pluginManager.loadPluginsFromDirectory(pluginDir);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Find all relevant code files for structural analysis
|
|
41
|
+
const files = await glob('**/*.{js,jsx,ts,tsx,cjs,mjs,json,lua}', {
|
|
42
|
+
...scanOptions,
|
|
43
|
+
ignore: [...scanOptions.ignore,
|
|
44
|
+
'**/*.d.ts',
|
|
45
|
+
'**/.next/**',
|
|
46
|
+
'**/coverage/**',
|
|
47
|
+
'**/arcvision.context.json',
|
|
48
|
+
'**/package-lock.json',
|
|
49
|
+
'**/yarn.lock',
|
|
50
|
+
// Additional non-code file types to exclude
|
|
51
|
+
'**/*.png', '**/*.jpg', '**/*.jpeg', '**/*.gif', '**/*.svg',
|
|
52
|
+
'**/*.pdf', '**/*.doc', '**/*.docx', '**/*.xls', '**/*.xlsx',
|
|
53
|
+
'**/*.zip', '**/*.tar', '**/*.gz', '**/*.exe', '**/*.dll',
|
|
54
|
+
'**/*.log', '**/*.tmp', '**/*.DS_Store', '**/Thumbs.db',
|
|
55
|
+
'**/*.lock', '**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**',
|
|
56
|
+
'**/target/**', '**/out/**', '**/vendor/**', '**/*.min.js', '**/*.bundle.js',
|
|
57
|
+
'**/public/**', '**/static/**', '**/assets/**', '**/images/**', '**/fonts/**',
|
|
58
|
+
'**/tests/**', '**/test/**', '**/__tests__/**', '**/__mocks__/**',
|
|
59
|
+
'**/*.test.js', '**/*.test.ts', '**/*.spec.js', '**/*.spec.ts',
|
|
60
|
+
'**/.*ignore', '**/.*config*', '**/.*rc', '**/.*lock', '**/.*cache',
|
|
61
|
+
'**/.*history', '**/.*backup', '**/.*temp', '**/.*tmp',
|
|
62
|
+
'**/README.*', '**/CHANGELOG.*', '**/TODO', '**/NOTES',
|
|
63
|
+
'**/Dockerfile', '**/docker-compose.yml', '**/*.dockerfile',
|
|
64
|
+
'**/Makefile', '**/*.mk', '**/*.pyc', '**/__pycache__/**',
|
|
65
|
+
'**/*.so', '**/*.dylib', '**/*.o', '**/*.obj', '**/*.jar', '**/*.war',
|
|
66
|
+
'**/*.lock', '**/*.pid', '**/*.seed', '**/*.pid', '**/*.sock'
|
|
67
|
+
]
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Use only code files for analysis
|
|
71
|
+
const allFiles = files;
|
|
72
|
+
|
|
73
|
+
console.log(` Identified ${allFiles.length} files for analysis`);
|
|
74
|
+
|
|
75
|
+
// Optimize concurrency based on file count to balance performance and memory usage
|
|
76
|
+
const BASE_CONCURRENCY = 25; // Reduced base concurrency for better resource management
|
|
77
|
+
const OPTIMAL_CONCURRENCY = Math.min(BASE_CONCURRENCY, Math.max(5, Math.floor(allFiles.length / 10))); // Adaptive concurrency
|
|
78
|
+
const CONCURRENCY = OPTIMAL_CONCURRENCY;
|
|
79
|
+
const rawNodes = [];
|
|
80
|
+
let totalFacts = 0;
|
|
81
|
+
|
|
82
|
+
// Process files in parallel batches
|
|
83
|
+
for (let i = 0; i < allFiles.length; i += CONCURRENCY) {
|
|
84
|
+
const batch = allFiles.slice(i, i + CONCURRENCY);
|
|
85
|
+
|
|
86
|
+
// Process the batch in parallel
|
|
87
|
+
const promises = batch.map(async (file) => {
|
|
88
|
+
try {
|
|
89
|
+
const relativePath = path.relative(directory, file);
|
|
90
|
+
const normalizedRelativePath = normalize(relativePath);
|
|
91
|
+
|
|
92
|
+
let metadata = {};
|
|
93
|
+
|
|
94
|
+
if (file.endsWith('.json')) {
|
|
95
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
96
|
+
metadata = {
|
|
97
|
+
id: file,
|
|
98
|
+
isJson: true,
|
|
99
|
+
raw: content
|
|
100
|
+
};
|
|
101
|
+
} else if (file.endsWith('.lua')) {
|
|
102
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
103
|
+
metadata = {
|
|
104
|
+
id: file,
|
|
105
|
+
isLua: true,
|
|
106
|
+
raw: content,
|
|
107
|
+
// Extract potential invocations from Lua content
|
|
108
|
+
potentialInvocations: extractLuaInvocations(content)
|
|
109
|
+
};
|
|
110
|
+
} else {
|
|
111
|
+
metadata = parser.parseFile(file);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
metadata = await pluginManager.processFile(file, metadata);
|
|
115
|
+
|
|
116
|
+
// Determine node type based on file extension
|
|
117
|
+
const isLuaFile = file.endsWith('.lua');
|
|
118
|
+
const nodeType = isLuaFile ? 'execution_script' : 'file';
|
|
119
|
+
const nodeRole = isLuaFile ? 'atomic_redis_operation' : 'Structure';
|
|
120
|
+
|
|
121
|
+
const node = {
|
|
122
|
+
id: normalizedRelativePath,
|
|
123
|
+
filePath: file,
|
|
124
|
+
type: nodeType,
|
|
125
|
+
role: nodeRole,
|
|
126
|
+
facts: {
|
|
127
|
+
imports: metadata.imports || [],
|
|
128
|
+
exports: metadata.exports || [],
|
|
129
|
+
functions: metadata.functions || [],
|
|
130
|
+
classes: metadata.classes || [],
|
|
131
|
+
types: metadata.types || [],
|
|
132
|
+
calls: {
|
|
133
|
+
functions: metadata.functionCalls || [],
|
|
134
|
+
methods: metadata.methodCalls || [],
|
|
135
|
+
constructors: metadata.constructorCalls || []
|
|
136
|
+
},
|
|
137
|
+
typeAnalysis: {
|
|
138
|
+
typeImports: metadata.typeImports || [],
|
|
139
|
+
interfaceDeps: metadata.interfaceDependencies || [],
|
|
140
|
+
genericDeps: metadata.genericDependencies || []
|
|
141
|
+
},
|
|
142
|
+
di: {
|
|
143
|
+
injections: metadata.constructorInjections || [],
|
|
144
|
+
hooks: metadata.hookDependencies || [],
|
|
145
|
+
context: metadata.contextUsages || []
|
|
146
|
+
},
|
|
147
|
+
react: metadata.componentUsage || [],
|
|
148
|
+
potentialInvocations: metadata.potentialInvocations || [],
|
|
149
|
+
invariantIndicators: extractInvariantIndicators(metadata.raw || '')
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Add language and execution boundary for Lua files
|
|
154
|
+
if (isLuaFile) {
|
|
155
|
+
node.language = 'lua';
|
|
156
|
+
node.execution_boundary = 'redis';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return { node, factCount: (metadata.imports?.length || 0) + (metadata.functionCalls?.length || 0) + (metadata.invariantIndicators?.length || 0) };
|
|
160
|
+
} catch (e) {
|
|
161
|
+
// Only warn if we're not in a high-performance mode
|
|
162
|
+
if (allFiles.length < 1000) { // Only show warnings for smaller projects
|
|
163
|
+
console.warn(`⚠️ Pass 1 failed for ${file}: ${e.message}`);
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const results = await Promise.all(promises);
|
|
170
|
+
|
|
171
|
+
results.filter(Boolean).forEach(res => {
|
|
172
|
+
rawNodes.push(res.node);
|
|
173
|
+
totalFacts += res.factCount;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
if (i % (CONCURRENCY * 4) === 0 && i > 0) {
|
|
177
|
+
console.log(` ... processed ${Math.min(i + CONCURRENCY, allFiles.length)} / ${allFiles.length} files`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
console.log(` ✓ Scanned ${rawNodes.length} files`);
|
|
182
|
+
console.log(` ✓ Extracted ${totalFacts} raw syntactic facts (including invariant indicators)`);
|
|
183
|
+
|
|
184
|
+
return rawNodes;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Extract potential Lua script invocations from content
|
|
189
|
+
* @param {string} content - Lua script content
|
|
190
|
+
* @returns {Array} Array of potential invocations
|
|
191
|
+
*/
|
|
192
|
+
function extractLuaInvocations(content) {
|
|
193
|
+
const invocations = [];
|
|
194
|
+
|
|
195
|
+
// Look for common BullMQ Lua script patterns
|
|
196
|
+
const patterns = [
|
|
197
|
+
/readFileSync\(['"].*\.lua['"].*\)/gi,
|
|
198
|
+
/defineCommand\(\s*\{\s*lua\s*:.*\}/gi,
|
|
199
|
+
/redis\.call\(['"].*['"].*\)/gi,
|
|
200
|
+
/redis\.pcall\(['"].*['"].*\)/gi
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
patterns.forEach(pattern => {
|
|
204
|
+
let match;
|
|
205
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
206
|
+
invocations.push(match[0]);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return invocations;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Extract potential invariant indicators from content
|
|
215
|
+
* @param {string} content - File content
|
|
216
|
+
* @returns {Array} Array of potential invariant indicators
|
|
217
|
+
*/
|
|
218
|
+
function extractInvariantIndicators(content) {
|
|
219
|
+
const indicators = [];
|
|
220
|
+
|
|
221
|
+
// Look for common invariant/pattern validation patterns
|
|
222
|
+
const patterns = [
|
|
223
|
+
/assert\(/gi, // assert() calls
|
|
224
|
+
/validate[A-Z]/gi, // validate functions
|
|
225
|
+
/check[A-Z]/gi, // check functions
|
|
226
|
+
/ensure[A-Z]/gi, // ensure functions
|
|
227
|
+
/guard[A-Z]/gi, // guard functions
|
|
228
|
+
/verify[A-Z]/gi, // verify functions
|
|
229
|
+
/Invariant/gi, // Invariant mentions
|
|
230
|
+
/constraint/gi, // constraint mentions
|
|
231
|
+
/must.*be/gi, // must-be patterns
|
|
232
|
+
/should.*be/gi, // should-be patterns
|
|
233
|
+
/expected.*to/gi, // expected-to patterns
|
|
234
|
+
/boundary.*check/gi, // boundary checks
|
|
235
|
+
/range.*check/gi, // range checks
|
|
236
|
+
/within.*bounds/gi, // bounds checks
|
|
237
|
+
/access.*control/gi, // access controls
|
|
238
|
+
/permission.*check/gi, // permission checks
|
|
239
|
+
/if.*!condition.*throw/gi, // guard clauses
|
|
240
|
+
/state.*transition/gi, // state transitions
|
|
241
|
+
/setState/gi, // state setters
|
|
242
|
+
/update.*state/gi, // state updaters
|
|
243
|
+
/transition/gi // transition patterns
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
patterns.forEach(pattern => {
|
|
247
|
+
let match;
|
|
248
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
249
|
+
indicators.push({
|
|
250
|
+
pattern: pattern.source,
|
|
251
|
+
match: match[0],
|
|
252
|
+
position: match.index
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
return indicators;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
module.exports = { executePass1 };
|