ruvector 0.2.28 → 0.2.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +2270 -2270
  3. package/bin/cli.js +9598 -9479
  4. package/bin/mcp-server.js +1 -1
  5. package/dist/core/intelligence-engine.d.ts +13 -0
  6. package/dist/core/intelligence-engine.d.ts.map +1 -1
  7. package/dist/core/intelligence-engine.js +38 -0
  8. package/dist/core/onnx/bundled-parallel.mjs +164 -164
  9. package/dist/core/onnx/embed-worker.mjs +67 -67
  10. package/dist/core/onnx/loader.js +434 -434
  11. package/dist/core/onnx/package.json +3 -3
  12. package/dist/core/onnx/pkg/LICENSE +21 -21
  13. package/dist/core/onnx/pkg/loader.js +348 -348
  14. package/dist/core/onnx/pkg/package.json +3 -3
  15. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm.d.ts +112 -112
  16. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm.js +5 -5
  17. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm_bg.js +638 -638
  18. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm_bg.wasm.d.ts +29 -29
  19. package/dist/core/parallel-workers.js +439 -439
  20. package/dist/workers/benchmark.js +15 -15
  21. package/package.json +122 -122
  22. package/src/decompiler/api-prober.js +302 -302
  23. package/src/decompiler/index.js +463 -463
  24. package/src/decompiler/metrics.js +86 -86
  25. package/src/decompiler/model-decompiler.js +423 -423
  26. package/src/decompiler/module-splitter.js +498 -498
  27. package/src/decompiler/module-tree.js +142 -142
  28. package/src/decompiler/name-predictor.js +400 -400
  29. package/src/decompiler/npm-fetch.js +176 -176
  30. package/src/decompiler/reconstructor.js +499 -499
  31. package/src/decompiler/reference-tracker.js +285 -285
  32. package/src/decompiler/statement-parser.js +285 -285
  33. package/src/decompiler/style-improver.js +438 -438
  34. package/src/decompiler/subcategories.js +339 -339
  35. package/src/decompiler/validator.js +379 -379
  36. package/src/decompiler/witness.js +140 -140
  37. package/wasm/package.json +26 -26
  38. package/wasm/ruvector_decompiler_wasm.d.ts +27 -27
  39. package/wasm/ruvector_decompiler_wasm.js +220 -220
  40. package/wasm/ruvector_decompiler_wasm_bg.wasm.d.ts +16 -16
  41. package/dist/core/onnx/pkg/ruvector.db +0 -0
@@ -1,379 +1,379 @@
1
- /**
2
- * validator.js - Operational validation for reconstructed code.
3
- *
4
- * Verifies that the reconstruction preserves semantics:
5
- * - Syntax validity (parseable without errors)
6
- * - String literal preservation (all strings intact)
7
- * - Class hierarchy preservation (same extends chains)
8
- * - Export preservation (same exports)
9
- * - Functional equivalence (same behavior for test inputs)
10
- */
11
-
12
- 'use strict';
13
-
14
- const vm = require('vm');
15
-
16
- /**
17
- * Validate that a reconstruction preserves the semantics of the original.
18
- *
19
- * @param {string} originalSource - the minified/beautified original
20
- * @param {string} reconstructedSource - the reconstructed version
21
- * @param {object} [options]
22
- * @param {boolean} [options.checkSyntax=true]
23
- * @param {boolean} [options.checkStrings=true]
24
- * @param {boolean} [options.checkClasses=true]
25
- * @param {boolean} [options.checkFunctions=true]
26
- * @param {number} [options.timeoutMs=1000] - sandbox execution timeout
27
- * @returns {{syntaxValid: boolean, exportsMatch: boolean, stringsPreserved: boolean, classesMatch: boolean, functionallyEquivalent: boolean, issues: string[]}}
28
- */
29
- function validateReconstruction(originalSource, reconstructedSource, options = {}) {
30
- const {
31
- checkSyntax = true,
32
- checkStrings = true,
33
- checkClasses = true,
34
- checkFunctions = true,
35
- timeoutMs = 1000,
36
- } = options;
37
-
38
- const issues = [];
39
- let syntaxValid = true;
40
- let stringsPreserved = true;
41
- let classesMatch = true;
42
- let exportsMatch = true;
43
- let functionallyEquivalent = true;
44
-
45
- // 1. Syntax check
46
- if (checkSyntax) {
47
- const syntaxResult = checkSyntaxValidity(reconstructedSource);
48
- syntaxValid = syntaxResult.valid;
49
- if (!syntaxValid) {
50
- issues.push(`Syntax error: ${syntaxResult.error}`);
51
- // If syntax is broken, further checks are unreliable
52
- return {
53
- syntaxValid,
54
- exportsMatch: false,
55
- stringsPreserved: false,
56
- classesMatch: false,
57
- functionallyEquivalent: false,
58
- issues,
59
- };
60
- }
61
- }
62
-
63
- // 2. String literal preservation
64
- if (checkStrings) {
65
- const result = checkStringPreservation(originalSource, reconstructedSource);
66
- stringsPreserved = result.preserved;
67
- for (const missing of result.missing) {
68
- issues.push(`Missing string literal: "${missing}"`);
69
- }
70
- }
71
-
72
- // 3. Class hierarchy preservation
73
- if (checkClasses) {
74
- const result = checkClassHierarchy(originalSource, reconstructedSource);
75
- classesMatch = result.match;
76
- for (const issue of result.issues) {
77
- issues.push(issue);
78
- }
79
- }
80
-
81
- // 4. Export/function count check
82
- if (checkFunctions) {
83
- const result = checkFunctionPreservation(originalSource, reconstructedSource);
84
- exportsMatch = result.match;
85
- for (const issue of result.issues) {
86
- issues.push(issue);
87
- }
88
- }
89
-
90
- // 5. Functional equivalence (best-effort, sandboxed)
91
- if (syntaxValid) {
92
- const result = checkFunctionalEquivalence(
93
- originalSource,
94
- reconstructedSource,
95
- timeoutMs,
96
- );
97
- functionallyEquivalent = result.equivalent;
98
- for (const issue of result.issues) {
99
- issues.push(issue);
100
- }
101
- }
102
-
103
- return {
104
- syntaxValid,
105
- exportsMatch,
106
- stringsPreserved,
107
- classesMatch,
108
- functionallyEquivalent,
109
- issues,
110
- };
111
- }
112
-
113
- /**
114
- * Check if source code is syntactically valid JavaScript.
115
- *
116
- * @param {string} source
117
- * @returns {{valid: boolean, error: string|null}}
118
- */
119
- function checkSyntaxValidity(source) {
120
- try {
121
- // Use Function constructor for syntax check (does not execute)
122
- new Function(source);
123
- return { valid: true, error: null };
124
- } catch (err) {
125
- return { valid: false, error: err.message };
126
- }
127
- }
128
-
129
- /**
130
- * Check that all string literals from the original appear in the reconstruction.
131
- * Identifiers may change, but string values must be preserved.
132
- *
133
- * @param {string} original
134
- * @param {string} reconstructed
135
- * @returns {{preserved: boolean, missing: string[], total: number}}
136
- */
137
- function checkStringPreservation(original, reconstructed) {
138
- const origStrings = extractStringLiterals(original);
139
- const reconStrings = new Set(extractStringLiterals(reconstructed));
140
-
141
- const missing = [];
142
- for (const s of origStrings) {
143
- // Skip very short strings and common noise
144
- if (s.length < 2) continue;
145
- if (!reconStrings.has(s)) {
146
- missing.push(s);
147
- }
148
- }
149
-
150
- return {
151
- preserved: missing.length === 0,
152
- missing: missing.slice(0, 20), // Cap at 20 for reporting
153
- total: origStrings.length,
154
- };
155
- }
156
-
157
- /**
158
- * Extract all string literals from source code.
159
- *
160
- * @param {string} source
161
- * @returns {string[]}
162
- */
163
- function extractStringLiterals(source) {
164
- const strings = [];
165
-
166
- // Match double-quoted strings
167
- const doubleQuoted = source.match(/"([^"\\]|\\.)*"/g) || [];
168
- for (const s of doubleQuoted) {
169
- strings.push(s.slice(1, -1));
170
- }
171
-
172
- // Match single-quoted strings
173
- const singleQuoted = source.match(/'([^'\\]|\\.)*'/g) || [];
174
- for (const s of singleQuoted) {
175
- strings.push(s.slice(1, -1));
176
- }
177
-
178
- return strings;
179
- }
180
-
181
- /**
182
- * Check that class hierarchies are preserved.
183
- * All "class X extends Y" pairs must appear in both versions.
184
- *
185
- * @param {string} original
186
- * @param {string} reconstructed
187
- * @returns {{match: boolean, issues: string[]}}
188
- */
189
- function checkClassHierarchy(original, reconstructed) {
190
- const origClasses = extractClassHierarchy(original);
191
- const reconClasses = extractClassHierarchy(reconstructed);
192
- const issues = [];
193
-
194
- // Check that base classes are preserved (names may have changed)
195
- const origBases = new Set(origClasses.map((c) => c.base).filter(Boolean));
196
- const reconBases = new Set(reconClasses.map((c) => c.base).filter(Boolean));
197
-
198
- // Base class names (Error, EventEmitter, etc.) should be preserved
199
- for (const base of origBases) {
200
- if (!reconBases.has(base)) {
201
- // Check if it is a built-in that was renamed
202
- const builtIns = ['Error', 'TypeError', 'RangeError', 'EventEmitter', 'Stream', 'Buffer'];
203
- if (builtIns.includes(base)) {
204
- issues.push(`Base class "${base}" missing from reconstruction`);
205
- }
206
- }
207
- }
208
-
209
- // Same number of class declarations
210
- if (origClasses.length !== reconClasses.length) {
211
- issues.push(
212
- `Class count mismatch: original has ${origClasses.length}, reconstructed has ${reconClasses.length}`,
213
- );
214
- }
215
-
216
- return { match: issues.length === 0, issues };
217
- }
218
-
219
- /**
220
- * Extract class declarations and their inheritance.
221
- *
222
- * @param {string} source
223
- * @returns {Array<{name: string, base: string|null}>}
224
- */
225
- function extractClassHierarchy(source) {
226
- const classes = [];
227
- const re = /class\s+(\w+)(?:\s+extends\s+(\w+))?/g;
228
- let match;
229
- while ((match = re.exec(source)) !== null) {
230
- classes.push({ name: match[1], base: match[2] || null });
231
- }
232
- return classes;
233
- }
234
-
235
- /**
236
- * Check that the number of functions/exports is preserved.
237
- *
238
- * @param {string} original
239
- * @param {string} reconstructed
240
- * @returns {{match: boolean, issues: string[]}}
241
- */
242
- function checkFunctionPreservation(original, reconstructed) {
243
- const issues = [];
244
-
245
- const origFuncCount = (original.match(/function\s*[\w$]*\s*\(/g) || []).length;
246
- const reconFuncCount = (reconstructed.match(/function\s*[\w$]*\s*\(/g) || []).length;
247
-
248
- if (origFuncCount !== reconFuncCount) {
249
- issues.push(
250
- `Function count mismatch: original has ${origFuncCount}, reconstructed has ${reconFuncCount}`,
251
- );
252
- }
253
-
254
- const origArrowCount = (original.match(/=>/g) || []).length;
255
- const reconArrowCount = (reconstructed.match(/=>/g) || []).length;
256
-
257
- if (origArrowCount !== reconArrowCount) {
258
- issues.push(
259
- `Arrow function count mismatch: original has ${origArrowCount}, reconstructed has ${reconArrowCount}`,
260
- );
261
- }
262
-
263
- // Check module.exports / export counts
264
- const origExports = (original.match(/module\.exports|export\s+(default\s+)?/g) || []).length;
265
- const reconExports = (reconstructed.match(/module\.exports|export\s+(default\s+)?/g) || []).length;
266
-
267
- if (origExports !== reconExports) {
268
- issues.push(
269
- `Export count mismatch: original has ${origExports}, reconstructed has ${reconExports}`,
270
- );
271
- }
272
-
273
- return { match: issues.length === 0, issues };
274
- }
275
-
276
- /**
277
- * Best-effort functional equivalence check.
278
- * Runs both versions in a sandboxed VM and compares outputs.
279
- *
280
- * This is a heuristic — it cannot prove full equivalence, but catches
281
- * obvious breakages (renamed exports, broken references, etc.).
282
- *
283
- * @param {string} original
284
- * @param {string} reconstructed
285
- * @param {number} timeoutMs
286
- * @returns {{equivalent: boolean, issues: string[]}}
287
- */
288
- function checkFunctionalEquivalence(original, reconstructed, timeoutMs) {
289
- const issues = [];
290
-
291
- // Compare the shape of what each version exports
292
- const origExports = safeEvalExports(original, timeoutMs);
293
- const reconExports = safeEvalExports(reconstructed, timeoutMs);
294
-
295
- if (origExports.error && !reconExports.error) {
296
- // Original errors but reconstructed does not — likely OK
297
- return { equivalent: true, issues };
298
- }
299
-
300
- if (!origExports.error && reconExports.error) {
301
- issues.push(`Reconstructed code fails to execute: ${reconExports.error}`);
302
- return { equivalent: false, issues };
303
- }
304
-
305
- if (origExports.error && reconExports.error) {
306
- // Both error — check if it is the same kind of error
307
- return { equivalent: true, issues };
308
- }
309
-
310
- // Compare export shapes (type and count of exported values)
311
- const origKeys = Object.keys(origExports.exports || {}).sort();
312
- const reconKeys = Object.keys(reconExports.exports || {}).sort();
313
-
314
- // Exports may have been renamed, so just compare counts and types
315
- if (origKeys.length !== reconKeys.length) {
316
- issues.push(
317
- `Exported key count differs: ${origKeys.length} vs ${reconKeys.length}`,
318
- );
319
- }
320
-
321
- // Compare types of exported values
322
- const origTypes = origKeys.map((k) => typeof origExports.exports[k]).sort();
323
- const reconTypes = reconKeys.map((k) => typeof reconExports.exports[k]).sort();
324
-
325
- for (let i = 0; i < Math.min(origTypes.length, reconTypes.length); i++) {
326
- if (origTypes[i] !== reconTypes[i]) {
327
- issues.push(
328
- `Export type mismatch at position ${i}: ${origTypes[i]} vs ${reconTypes[i]}`,
329
- );
330
- }
331
- }
332
-
333
- return { equivalent: issues.length === 0, issues };
334
- }
335
-
336
- /**
337
- * Safely execute code in a VM sandbox and extract module.exports.
338
- *
339
- * @param {string} source
340
- * @param {number} timeoutMs
341
- * @returns {{exports: object|null, error: string|null}}
342
- */
343
- function safeEvalExports(source, timeoutMs) {
344
- try {
345
- const sandbox = {
346
- module: { exports: {} },
347
- exports: {},
348
- require: () => ({}),
349
- console: { log() {}, error() {}, warn() {}, info() {} },
350
- process: { env: {}, argv: [], cwd: () => '/' },
351
- setTimeout: () => {},
352
- setInterval: () => {},
353
- clearTimeout: () => {},
354
- clearInterval: () => {},
355
- Buffer: { from: () => Buffer.alloc(0), alloc: () => Buffer.alloc(0) },
356
- global: {},
357
- __dirname: '/',
358
- __filename: '/test.js',
359
- };
360
-
361
- const context = vm.createContext(sandbox);
362
- const script = new vm.Script(source, { filename: 'reconstructed.js' });
363
- script.runInContext(context, { timeout: timeoutMs });
364
-
365
- return { exports: sandbox.module.exports, error: null };
366
- } catch (err) {
367
- return { exports: null, error: err.message };
368
- }
369
- }
370
-
371
- module.exports = {
372
- validateReconstruction,
373
- checkSyntaxValidity,
374
- checkStringPreservation,
375
- checkClassHierarchy,
376
- checkFunctionPreservation,
377
- checkFunctionalEquivalence,
378
- extractStringLiterals,
379
- };
1
+ /**
2
+ * validator.js - Operational validation for reconstructed code.
3
+ *
4
+ * Verifies that the reconstruction preserves semantics:
5
+ * - Syntax validity (parseable without errors)
6
+ * - String literal preservation (all strings intact)
7
+ * - Class hierarchy preservation (same extends chains)
8
+ * - Export preservation (same exports)
9
+ * - Functional equivalence (same behavior for test inputs)
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const vm = require('vm');
15
+
16
+ /**
17
+ * Validate that a reconstruction preserves the semantics of the original.
18
+ *
19
+ * @param {string} originalSource - the minified/beautified original
20
+ * @param {string} reconstructedSource - the reconstructed version
21
+ * @param {object} [options]
22
+ * @param {boolean} [options.checkSyntax=true]
23
+ * @param {boolean} [options.checkStrings=true]
24
+ * @param {boolean} [options.checkClasses=true]
25
+ * @param {boolean} [options.checkFunctions=true]
26
+ * @param {number} [options.timeoutMs=1000] - sandbox execution timeout
27
+ * @returns {{syntaxValid: boolean, exportsMatch: boolean, stringsPreserved: boolean, classesMatch: boolean, functionallyEquivalent: boolean, issues: string[]}}
28
+ */
29
+ function validateReconstruction(originalSource, reconstructedSource, options = {}) {
30
+ const {
31
+ checkSyntax = true,
32
+ checkStrings = true,
33
+ checkClasses = true,
34
+ checkFunctions = true,
35
+ timeoutMs = 1000,
36
+ } = options;
37
+
38
+ const issues = [];
39
+ let syntaxValid = true;
40
+ let stringsPreserved = true;
41
+ let classesMatch = true;
42
+ let exportsMatch = true;
43
+ let functionallyEquivalent = true;
44
+
45
+ // 1. Syntax check
46
+ if (checkSyntax) {
47
+ const syntaxResult = checkSyntaxValidity(reconstructedSource);
48
+ syntaxValid = syntaxResult.valid;
49
+ if (!syntaxValid) {
50
+ issues.push(`Syntax error: ${syntaxResult.error}`);
51
+ // If syntax is broken, further checks are unreliable
52
+ return {
53
+ syntaxValid,
54
+ exportsMatch: false,
55
+ stringsPreserved: false,
56
+ classesMatch: false,
57
+ functionallyEquivalent: false,
58
+ issues,
59
+ };
60
+ }
61
+ }
62
+
63
+ // 2. String literal preservation
64
+ if (checkStrings) {
65
+ const result = checkStringPreservation(originalSource, reconstructedSource);
66
+ stringsPreserved = result.preserved;
67
+ for (const missing of result.missing) {
68
+ issues.push(`Missing string literal: "${missing}"`);
69
+ }
70
+ }
71
+
72
+ // 3. Class hierarchy preservation
73
+ if (checkClasses) {
74
+ const result = checkClassHierarchy(originalSource, reconstructedSource);
75
+ classesMatch = result.match;
76
+ for (const issue of result.issues) {
77
+ issues.push(issue);
78
+ }
79
+ }
80
+
81
+ // 4. Export/function count check
82
+ if (checkFunctions) {
83
+ const result = checkFunctionPreservation(originalSource, reconstructedSource);
84
+ exportsMatch = result.match;
85
+ for (const issue of result.issues) {
86
+ issues.push(issue);
87
+ }
88
+ }
89
+
90
+ // 5. Functional equivalence (best-effort, sandboxed)
91
+ if (syntaxValid) {
92
+ const result = checkFunctionalEquivalence(
93
+ originalSource,
94
+ reconstructedSource,
95
+ timeoutMs,
96
+ );
97
+ functionallyEquivalent = result.equivalent;
98
+ for (const issue of result.issues) {
99
+ issues.push(issue);
100
+ }
101
+ }
102
+
103
+ return {
104
+ syntaxValid,
105
+ exportsMatch,
106
+ stringsPreserved,
107
+ classesMatch,
108
+ functionallyEquivalent,
109
+ issues,
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Check if source code is syntactically valid JavaScript.
115
+ *
116
+ * @param {string} source
117
+ * @returns {{valid: boolean, error: string|null}}
118
+ */
119
+ function checkSyntaxValidity(source) {
120
+ try {
121
+ // Use Function constructor for syntax check (does not execute)
122
+ new Function(source);
123
+ return { valid: true, error: null };
124
+ } catch (err) {
125
+ return { valid: false, error: err.message };
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Check that all string literals from the original appear in the reconstruction.
131
+ * Identifiers may change, but string values must be preserved.
132
+ *
133
+ * @param {string} original
134
+ * @param {string} reconstructed
135
+ * @returns {{preserved: boolean, missing: string[], total: number}}
136
+ */
137
+ function checkStringPreservation(original, reconstructed) {
138
+ const origStrings = extractStringLiterals(original);
139
+ const reconStrings = new Set(extractStringLiterals(reconstructed));
140
+
141
+ const missing = [];
142
+ for (const s of origStrings) {
143
+ // Skip very short strings and common noise
144
+ if (s.length < 2) continue;
145
+ if (!reconStrings.has(s)) {
146
+ missing.push(s);
147
+ }
148
+ }
149
+
150
+ return {
151
+ preserved: missing.length === 0,
152
+ missing: missing.slice(0, 20), // Cap at 20 for reporting
153
+ total: origStrings.length,
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Extract all string literals from source code.
159
+ *
160
+ * @param {string} source
161
+ * @returns {string[]}
162
+ */
163
+ function extractStringLiterals(source) {
164
+ const strings = [];
165
+
166
+ // Match double-quoted strings
167
+ const doubleQuoted = source.match(/"([^"\\]|\\.)*"/g) || [];
168
+ for (const s of doubleQuoted) {
169
+ strings.push(s.slice(1, -1));
170
+ }
171
+
172
+ // Match single-quoted strings
173
+ const singleQuoted = source.match(/'([^'\\]|\\.)*'/g) || [];
174
+ for (const s of singleQuoted) {
175
+ strings.push(s.slice(1, -1));
176
+ }
177
+
178
+ return strings;
179
+ }
180
+
181
+ /**
182
+ * Check that class hierarchies are preserved.
183
+ * All "class X extends Y" pairs must appear in both versions.
184
+ *
185
+ * @param {string} original
186
+ * @param {string} reconstructed
187
+ * @returns {{match: boolean, issues: string[]}}
188
+ */
189
+ function checkClassHierarchy(original, reconstructed) {
190
+ const origClasses = extractClassHierarchy(original);
191
+ const reconClasses = extractClassHierarchy(reconstructed);
192
+ const issues = [];
193
+
194
+ // Check that base classes are preserved (names may have changed)
195
+ const origBases = new Set(origClasses.map((c) => c.base).filter(Boolean));
196
+ const reconBases = new Set(reconClasses.map((c) => c.base).filter(Boolean));
197
+
198
+ // Base class names (Error, EventEmitter, etc.) should be preserved
199
+ for (const base of origBases) {
200
+ if (!reconBases.has(base)) {
201
+ // Check if it is a built-in that was renamed
202
+ const builtIns = ['Error', 'TypeError', 'RangeError', 'EventEmitter', 'Stream', 'Buffer'];
203
+ if (builtIns.includes(base)) {
204
+ issues.push(`Base class "${base}" missing from reconstruction`);
205
+ }
206
+ }
207
+ }
208
+
209
+ // Same number of class declarations
210
+ if (origClasses.length !== reconClasses.length) {
211
+ issues.push(
212
+ `Class count mismatch: original has ${origClasses.length}, reconstructed has ${reconClasses.length}`,
213
+ );
214
+ }
215
+
216
+ return { match: issues.length === 0, issues };
217
+ }
218
+
219
+ /**
220
+ * Extract class declarations and their inheritance.
221
+ *
222
+ * @param {string} source
223
+ * @returns {Array<{name: string, base: string|null}>}
224
+ */
225
+ function extractClassHierarchy(source) {
226
+ const classes = [];
227
+ const re = /class\s+(\w+)(?:\s+extends\s+(\w+))?/g;
228
+ let match;
229
+ while ((match = re.exec(source)) !== null) {
230
+ classes.push({ name: match[1], base: match[2] || null });
231
+ }
232
+ return classes;
233
+ }
234
+
235
+ /**
236
+ * Check that the number of functions/exports is preserved.
237
+ *
238
+ * @param {string} original
239
+ * @param {string} reconstructed
240
+ * @returns {{match: boolean, issues: string[]}}
241
+ */
242
+ function checkFunctionPreservation(original, reconstructed) {
243
+ const issues = [];
244
+
245
+ const origFuncCount = (original.match(/function\s*[\w$]*\s*\(/g) || []).length;
246
+ const reconFuncCount = (reconstructed.match(/function\s*[\w$]*\s*\(/g) || []).length;
247
+
248
+ if (origFuncCount !== reconFuncCount) {
249
+ issues.push(
250
+ `Function count mismatch: original has ${origFuncCount}, reconstructed has ${reconFuncCount}`,
251
+ );
252
+ }
253
+
254
+ const origArrowCount = (original.match(/=>/g) || []).length;
255
+ const reconArrowCount = (reconstructed.match(/=>/g) || []).length;
256
+
257
+ if (origArrowCount !== reconArrowCount) {
258
+ issues.push(
259
+ `Arrow function count mismatch: original has ${origArrowCount}, reconstructed has ${reconArrowCount}`,
260
+ );
261
+ }
262
+
263
+ // Check module.exports / export counts
264
+ const origExports = (original.match(/module\.exports|export\s+(default\s+)?/g) || []).length;
265
+ const reconExports = (reconstructed.match(/module\.exports|export\s+(default\s+)?/g) || []).length;
266
+
267
+ if (origExports !== reconExports) {
268
+ issues.push(
269
+ `Export count mismatch: original has ${origExports}, reconstructed has ${reconExports}`,
270
+ );
271
+ }
272
+
273
+ return { match: issues.length === 0, issues };
274
+ }
275
+
276
+ /**
277
+ * Best-effort functional equivalence check.
278
+ * Runs both versions in a sandboxed VM and compares outputs.
279
+ *
280
+ * This is a heuristic — it cannot prove full equivalence, but catches
281
+ * obvious breakages (renamed exports, broken references, etc.).
282
+ *
283
+ * @param {string} original
284
+ * @param {string} reconstructed
285
+ * @param {number} timeoutMs
286
+ * @returns {{equivalent: boolean, issues: string[]}}
287
+ */
288
+ function checkFunctionalEquivalence(original, reconstructed, timeoutMs) {
289
+ const issues = [];
290
+
291
+ // Compare the shape of what each version exports
292
+ const origExports = safeEvalExports(original, timeoutMs);
293
+ const reconExports = safeEvalExports(reconstructed, timeoutMs);
294
+
295
+ if (origExports.error && !reconExports.error) {
296
+ // Original errors but reconstructed does not — likely OK
297
+ return { equivalent: true, issues };
298
+ }
299
+
300
+ if (!origExports.error && reconExports.error) {
301
+ issues.push(`Reconstructed code fails to execute: ${reconExports.error}`);
302
+ return { equivalent: false, issues };
303
+ }
304
+
305
+ if (origExports.error && reconExports.error) {
306
+ // Both error — check if it is the same kind of error
307
+ return { equivalent: true, issues };
308
+ }
309
+
310
+ // Compare export shapes (type and count of exported values)
311
+ const origKeys = Object.keys(origExports.exports || {}).sort();
312
+ const reconKeys = Object.keys(reconExports.exports || {}).sort();
313
+
314
+ // Exports may have been renamed, so just compare counts and types
315
+ if (origKeys.length !== reconKeys.length) {
316
+ issues.push(
317
+ `Exported key count differs: ${origKeys.length} vs ${reconKeys.length}`,
318
+ );
319
+ }
320
+
321
+ // Compare types of exported values
322
+ const origTypes = origKeys.map((k) => typeof origExports.exports[k]).sort();
323
+ const reconTypes = reconKeys.map((k) => typeof reconExports.exports[k]).sort();
324
+
325
+ for (let i = 0; i < Math.min(origTypes.length, reconTypes.length); i++) {
326
+ if (origTypes[i] !== reconTypes[i]) {
327
+ issues.push(
328
+ `Export type mismatch at position ${i}: ${origTypes[i]} vs ${reconTypes[i]}`,
329
+ );
330
+ }
331
+ }
332
+
333
+ return { equivalent: issues.length === 0, issues };
334
+ }
335
+
336
+ /**
337
+ * Safely execute code in a VM sandbox and extract module.exports.
338
+ *
339
+ * @param {string} source
340
+ * @param {number} timeoutMs
341
+ * @returns {{exports: object|null, error: string|null}}
342
+ */
343
+ function safeEvalExports(source, timeoutMs) {
344
+ try {
345
+ const sandbox = {
346
+ module: { exports: {} },
347
+ exports: {},
348
+ require: () => ({}),
349
+ console: { log() {}, error() {}, warn() {}, info() {} },
350
+ process: { env: {}, argv: [], cwd: () => '/' },
351
+ setTimeout: () => {},
352
+ setInterval: () => {},
353
+ clearTimeout: () => {},
354
+ clearInterval: () => {},
355
+ Buffer: { from: () => Buffer.alloc(0), alloc: () => Buffer.alloc(0) },
356
+ global: {},
357
+ __dirname: '/',
358
+ __filename: '/test.js',
359
+ };
360
+
361
+ const context = vm.createContext(sandbox);
362
+ const script = new vm.Script(source, { filename: 'reconstructed.js' });
363
+ script.runInContext(context, { timeout: timeoutMs });
364
+
365
+ return { exports: sandbox.module.exports, error: null };
366
+ } catch (err) {
367
+ return { exports: null, error: err.message };
368
+ }
369
+ }
370
+
371
+ module.exports = {
372
+ validateReconstruction,
373
+ checkSyntaxValidity,
374
+ checkStringPreservation,
375
+ checkClassHierarchy,
376
+ checkFunctionPreservation,
377
+ checkFunctionalEquivalence,
378
+ extractStringLiterals,
379
+ };