ruvector 0.2.18 → 0.2.20

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/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "ruvector",
3
- "version": "0.2.18",
4
- "description": "High-performance vector database for Node.js with automatic native/WASM fallback",
3
+ "version": "0.2.20",
4
+ "description": "Self-learning vector database for Node.js hybrid search, Graph RAG, FlashAttention-3, DiskANN, 50+ attention mechanisms",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "bin": {
8
8
  "ruvector": "./bin/cli.js"
9
9
  },
10
10
  "scripts": {
11
- "build": "tsc && cp -r src/core/onnx dist/core/",
11
+ "build": "tsc && cp src/core/onnx/pkg/package.json dist/core/onnx/pkg/",
12
12
  "prepublishOnly": "npm run build",
13
13
  "test": "node test/integration.js && node test/cli-commands.js"
14
14
  },
@@ -46,6 +46,12 @@
46
46
  "shared-intelligence",
47
47
  "mcp",
48
48
  "edge-computing",
49
+ "graph-rag",
50
+ "diskann",
51
+ "hybrid-search",
52
+ "colbert",
53
+ "turboquant",
54
+ "mamba",
49
55
  "pi-brain",
50
56
  "identity",
51
57
  "pi-key",
@@ -70,22 +76,32 @@
70
76
  "@ruvector/sona": "^0.1.4",
71
77
  "chalk": "^4.1.2",
72
78
  "commander": "^11.1.0",
73
- "glob": "^10.3.10",
79
+ "js-beautify": "^1.15.4",
74
80
  "ora": "^5.4.1"
75
81
  },
76
82
  "optionalDependencies": {
77
83
  "@ruvector/rvf": "^0.1.0"
78
84
  },
79
85
  "devDependencies": {
80
- "@types/glob": "^8.1.0",
81
86
  "@types/node": "^20.10.5",
82
87
  "typescript": "^5.3.3"
83
88
  },
89
+ "files": [
90
+ "bin/",
91
+ "dist/",
92
+ "src/decompiler/",
93
+ "README.md",
94
+ "LICENSE"
95
+ ],
84
96
  "peerDependencies": {
85
- "@ruvector/ruvllm": ">=2.0.0",
86
- "@ruvector/router": ">=0.1.0"
97
+ "@ruvector/pi-brain": ">=0.1.0",
98
+ "@ruvector/router": ">=0.1.0",
99
+ "@ruvector/ruvllm": ">=2.0.0"
87
100
  },
88
101
  "peerDependenciesMeta": {
102
+ "@ruvector/pi-brain": {
103
+ "optional": true
104
+ },
89
105
  "@ruvector/ruvllm": {
90
106
  "optional": true
91
107
  },
@@ -93,12 +109,6 @@
93
109
  "optional": true
94
110
  }
95
111
  },
96
- "files": [
97
- "bin/",
98
- "dist/",
99
- "README.md",
100
- "LICENSE"
101
- ],
102
112
  "engines": {
103
113
  "node": ">=18.0.0"
104
114
  }
@@ -0,0 +1,407 @@
1
+ /**
2
+ * decompiler/index.js - High-level decompiler API.
3
+ *
4
+ * Exports three main entry points:
5
+ * - decompilePackage(name, version, options)
6
+ * - decompileFile(filePath, options)
7
+ * - decompileUrl(url, options)
8
+ *
9
+ * Each returns a standardized DecompileResult:
10
+ * { modules, metrics, witness, source, packageInfo? }
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const {
18
+ fetchPackageInfo,
19
+ fetchPackageFileList,
20
+ fetchFileContent,
21
+ findMainBundle,
22
+ parseTarget,
23
+ } = require('./npm-fetch');
24
+ const { splitModules } = require('./module-splitter');
25
+ const { buildWitnessChain, verifyWitnessChain } = require('./witness');
26
+ const { computeMetrics, computeModuleMetrics } = require('./metrics');
27
+ const { reconstructCode, reconstructRunnable } = require('./reconstructor');
28
+ const { validateReconstruction } = require('./validator');
29
+
30
+ /**
31
+ * Try to beautify source code using js-beautify (optional dep).
32
+ * Falls back to returning the source unchanged if not installed.
33
+ * @param {string} source
34
+ * @returns {string}
35
+ */
36
+ function beautify(source) {
37
+ try {
38
+ const jsBeautify = require('js-beautify');
39
+ const beautifyFn = jsBeautify.js || jsBeautify;
40
+ return beautifyFn(source, {
41
+ indent_size: 2,
42
+ space_in_empty_paren: false,
43
+ preserve_newlines: true,
44
+ max_preserve_newlines: 2,
45
+ end_with_newline: true,
46
+ });
47
+ } catch {
48
+ // js-beautify not installed; return source as-is
49
+ return source;
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Try to use the Rust decompiler for full Louvain graph partitioning (878+ modules).
55
+ * Falls back to Node.js keyword splitting if Rust binary not available.
56
+ *
57
+ * @param {string} filePath - path to JS file
58
+ * @param {string} outputDir - output directory
59
+ * @returns {{success: boolean, modules: number, outputDir: string}|null}
60
+ */
61
+ function tryRustDecompiler(filePath, outputDir) {
62
+ try {
63
+ const { execSync } = require('child_process');
64
+ // Try to find the Rust binary
65
+ const candidates = [
66
+ 'cargo run --release -p ruvector-decompiler --example run_on_cli --',
67
+ path.join(__dirname, '../../../../target/release/examples/run_on_cli'),
68
+ ];
69
+ for (const bin of candidates) {
70
+ try {
71
+ const cmd = bin.includes('cargo')
72
+ ? `${bin} "${filePath}" --output-dir "${outputDir}"`
73
+ : `"${bin}" "${filePath}" --output-dir "${outputDir}"`;
74
+ const result = execSync(cmd, {
75
+ timeout: 120000,
76
+ stdio: ['pipe', 'pipe', 'pipe'],
77
+ cwd: path.join(__dirname, '../../../..'),
78
+ });
79
+ const stderr = result.toString();
80
+ const match = stderr.match(/Wrote (\d+) modules/);
81
+ const moduleCount = match ? parseInt(match[1]) : 0;
82
+ return { success: true, modules: moduleCount, outputDir };
83
+ } catch { continue; }
84
+ }
85
+ } catch {}
86
+ return null;
87
+ }
88
+
89
+ /**
90
+ * Core decompilation pipeline: beautify -> split -> metrics -> witness -> reconstruct.
91
+ *
92
+ * If the Rust decompiler is available (cargo built), uses Louvain graph partitioning
93
+ * for 878+ modules with 100% parse rate. Falls back to Node.js keyword splitting.
94
+ *
95
+ * @param {string} source - raw JavaScript source
96
+ * @param {object} [options]
97
+ * @param {number} [options.minConfidence=0.3]
98
+ * @param {boolean} [options.witness=true]
99
+ * @param {boolean} [options.reconstruct=false] - apply readable reconstruction
100
+ * @param {boolean} [options.validate=false] - validate reconstruction preserves semantics
101
+ * @param {string} [options.patternPath] - path to training patterns JSON
102
+ * @param {boolean} [options.addComments=true] - add JSDoc comments during reconstruction
103
+ * @param {boolean} [options.improveStyle=true] - apply style improvements during reconstruction
104
+ * @param {boolean} [options.useRust=true] - try Rust Louvain partitioner first
105
+ * @param {string} [options.filePath] - original file path (needed for Rust pipeline)
106
+ * @returns {{modules: object[], metrics: object, witness: object|null, beautifiedSource: string, reconstruction?: object}}
107
+ */
108
+ function decompileSource(source, options = {}) {
109
+ const {
110
+ minConfidence = 0.3,
111
+ witness: generateWitness = true,
112
+ reconstruct = false,
113
+ validate = false,
114
+ patternPath,
115
+ addComments = true,
116
+ improveStyle = true,
117
+ useRust = true,
118
+ filePath,
119
+ } = options;
120
+
121
+ // Try Rust Louvain pipeline first (878+ modules, 100% parse rate)
122
+ if (useRust && filePath && source.length > 100000) {
123
+ const tmpDir = path.join(require('os').tmpdir(), 'ruvector-decompile-' + Date.now());
124
+ const rustResult = tryRustDecompiler(filePath, tmpDir);
125
+ if (rustResult && rustResult.success) {
126
+ // Load modules from Rust output
127
+ const sourceDir = path.join(tmpDir, 'source');
128
+ const rustModules = [];
129
+ try {
130
+ for (const f of fs.readdirSync(sourceDir).filter(f => f.endsWith('.js'))) {
131
+ const content = fs.readFileSync(path.join(sourceDir, f), 'utf8');
132
+ rustModules.push({
133
+ name: f.replace('.js', ''),
134
+ content,
135
+ fragments: 0,
136
+ confidence: 0.8,
137
+ });
138
+ }
139
+ } catch {}
140
+ if (rustModules.length > 0) {
141
+ const sourceMetrics = computeMetrics(source);
142
+ const witnessPath = path.join(tmpDir, 'witness.json');
143
+ let witnessChain = null;
144
+ try { witnessChain = JSON.parse(fs.readFileSync(witnessPath, 'utf8')); } catch {}
145
+ return {
146
+ modules: rustModules,
147
+ metrics: { source: sourceMetrics, modules: rustModules.length, engine: 'rust-louvain' },
148
+ witness: witnessChain,
149
+ beautifiedSource: source,
150
+ source,
151
+ };
152
+ }
153
+ }
154
+ }
155
+
156
+ // Fallback: Node.js keyword-based splitting
157
+ const beautified = beautify(source);
158
+ const { modules, unclassified } = splitModules(beautified, { minConfidence });
159
+ const sourceMetrics = computeMetrics(beautified);
160
+ const moduleMetrics = computeModuleMetrics(modules);
161
+ const witnessChain = generateWitness ? buildWitnessChain(source, modules) : null;
162
+
163
+ // Optional: apply readable reconstruction to each module
164
+ let reconstructionSummary = null;
165
+ if (reconstruct) {
166
+ let totalRenames = 0;
167
+ let totalComments = 0;
168
+ let totalConfidence = 0;
169
+ let validationResults = [];
170
+
171
+ for (const mod of modules) {
172
+ const result = reconstructCode(mod.content, {
173
+ patternPath,
174
+ propagateNames: true,
175
+ addComments,
176
+ improveStyle,
177
+ minConfidence,
178
+ });
179
+
180
+ const originalContent = mod.content;
181
+ mod.content = result.code;
182
+ mod.renames = result.renames;
183
+ mod.confidence = Math.max(mod.confidence, result.confidence);
184
+
185
+ totalRenames += result.renames.length;
186
+ totalComments += result.comments;
187
+ totalConfidence += result.confidence;
188
+
189
+ // Optional: validate the reconstruction
190
+ if (validate) {
191
+ const validation = validateReconstruction(originalContent, result.code);
192
+ validationResults.push({
193
+ module: mod.name,
194
+ ...validation,
195
+ });
196
+ }
197
+ }
198
+
199
+ reconstructionSummary = {
200
+ totalRenames,
201
+ totalComments,
202
+ averageConfidence: modules.length > 0
203
+ ? parseFloat((totalConfidence / modules.length).toFixed(3))
204
+ : 0,
205
+ modulesProcessed: modules.length,
206
+ };
207
+
208
+ if (validate) {
209
+ reconstructionSummary.validation = validationResults;
210
+ reconstructionSummary.allValid = validationResults.every((v) => v.syntaxValid);
211
+ reconstructionSummary.allEquivalent = validationResults.every((v) => v.functionallyEquivalent);
212
+ }
213
+ }
214
+
215
+ return {
216
+ modules,
217
+ metrics: {
218
+ source: sourceMetrics,
219
+ modules: moduleMetrics,
220
+ unclassifiedStatements: unclassified.length,
221
+ },
222
+ witness: witnessChain,
223
+ beautifiedSource: beautified,
224
+ ...(reconstructionSummary ? { reconstruction: reconstructionSummary } : {}),
225
+ };
226
+ }
227
+
228
+ /**
229
+ * Decompile an npm package.
230
+ *
231
+ * @param {string} packageName - e.g. 'express', '@anthropic-ai/claude-code'
232
+ * @param {string} [version] - defaults to 'latest'
233
+ * @param {object} [options]
234
+ * @param {number} [options.minConfidence=0.3]
235
+ * @param {boolean} [options.witness=true]
236
+ * @returns {Promise<{modules: object[], metrics: object, witness: object|null, packageInfo: object, bundlePath: string, source: string}>}
237
+ */
238
+ async function decompilePackage(packageName, version, options = {}) {
239
+ const info = await fetchPackageInfo(packageName);
240
+ const resolvedVersion = version || info.latest;
241
+
242
+ if (!info.versions.includes(resolvedVersion)) {
243
+ throw new Error(
244
+ `Version "${resolvedVersion}" not found for ${packageName}. ` +
245
+ `Available: ${info.versions.slice(0, 10).join(', ')}...`,
246
+ );
247
+ }
248
+
249
+ const files = await fetchPackageFileList(packageName, resolvedVersion);
250
+ const pkgJson = info.packageJson || {};
251
+ const bundlePath = findMainBundle(files, pkgJson);
252
+
253
+ if (!bundlePath) {
254
+ throw new Error(
255
+ `Could not find main bundle for ${packageName}@${resolvedVersion}. ` +
256
+ `Files: ${files.slice(0, 10).map((f) => f.name).join(', ')}`,
257
+ );
258
+ }
259
+
260
+ const source = await fetchFileContent(packageName, resolvedVersion, bundlePath);
261
+ const result = decompileSource(source, options);
262
+
263
+ return {
264
+ ...result,
265
+ packageInfo: {
266
+ name: info.name,
267
+ version: resolvedVersion,
268
+ description: info.description,
269
+ bundlePath,
270
+ bundleSize: source.length,
271
+ },
272
+ source,
273
+ };
274
+ }
275
+
276
+ /**
277
+ * Decompile a local JavaScript file.
278
+ *
279
+ * @param {string} filePath - path to a .js file
280
+ * @param {object} [options]
281
+ * @returns {{modules: object[], metrics: object, witness: object|null, filePath: string, source: string}}
282
+ */
283
+ function decompileFile(filePath, options = {}) {
284
+ const resolved = path.resolve(filePath);
285
+
286
+ if (!fs.existsSync(resolved)) {
287
+ throw new Error(`File not found: ${resolved}`);
288
+ }
289
+
290
+ const source = fs.readFileSync(resolved, 'utf-8');
291
+ const result = decompileSource(source, { ...options, filePath: resolved });
292
+
293
+ return {
294
+ ...result,
295
+ filePath: resolved,
296
+ source,
297
+ };
298
+ }
299
+
300
+ /**
301
+ * Decompile JavaScript from a URL.
302
+ *
303
+ * @param {string} url
304
+ * @param {object} [options]
305
+ * @returns {Promise<{modules: object[], metrics: object, witness: object|null, url: string, source: string}>}
306
+ */
307
+ async function decompileUrl(url, options = {}) {
308
+ const resp = await fetch(url, { redirect: 'follow' });
309
+ if (!resp.ok) {
310
+ throw new Error(`Failed to fetch ${url} (HTTP ${resp.status})`);
311
+ }
312
+
313
+ const source = await resp.text();
314
+ const result = decompileSource(source, options);
315
+
316
+ return {
317
+ ...result,
318
+ url,
319
+ source,
320
+ };
321
+ }
322
+
323
+ /**
324
+ * Write decompilation results to an output directory.
325
+ *
326
+ * @param {object} result - decompilation result from any of the decompile* functions
327
+ * @param {string} outputDir
328
+ * @param {string} [format='modules'] - 'modules', 'single', 'json'
329
+ */
330
+ function writeOutput(result, outputDir, format = 'modules') {
331
+ fs.mkdirSync(outputDir, { recursive: true });
332
+
333
+ if (format === 'json') {
334
+ const jsonResult = {
335
+ modules: result.modules.map((m) => ({
336
+ name: m.name,
337
+ fragments: m.fragments,
338
+ confidence: m.confidence,
339
+ content: m.content,
340
+ })),
341
+ metrics: result.metrics,
342
+ witness: result.witness,
343
+ packageInfo: result.packageInfo || null,
344
+ };
345
+ fs.writeFileSync(
346
+ path.join(outputDir, 'decompiled.json'),
347
+ JSON.stringify(jsonResult, null, 2),
348
+ );
349
+ return;
350
+ }
351
+
352
+ if (format === 'single') {
353
+ let output = '';
354
+ for (const mod of result.modules) {
355
+ output += `// ─── Module: ${mod.name} (confidence: ${mod.confidence}) ───\n\n`;
356
+ output += mod.content + '\n\n';
357
+ }
358
+ fs.writeFileSync(path.join(outputDir, 'decompiled.js'), output);
359
+ return;
360
+ }
361
+
362
+ // Default: 'modules' format — one file per module
363
+ // Supports hierarchical module names like 'tools/bash' -> tools/bash.js
364
+ for (let i = 0; i < result.modules.length; i++) {
365
+ const mod = result.modules[i];
366
+ const header = `// Module: ${mod.name}\n// Confidence: ${mod.confidence}\n// Fragments: ${mod.fragments}\n\n`;
367
+
368
+ if (mod.name.includes('/')) {
369
+ // Hierarchical: create subdirectories
370
+ const filePath = path.join(outputDir, mod.name + '.js');
371
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
372
+ fs.writeFileSync(filePath, header + mod.content);
373
+ } else {
374
+ const idx = String(i + 1).padStart(3, '0');
375
+ const fileName = `module-${idx}-${mod.name}.js`;
376
+ fs.writeFileSync(path.join(outputDir, fileName), header + mod.content);
377
+ }
378
+ }
379
+
380
+ // Metrics
381
+ fs.writeFileSync(
382
+ path.join(outputDir, 'metrics.json'),
383
+ JSON.stringify(result.metrics, null, 2),
384
+ );
385
+
386
+ // Witness chain
387
+ if (result.witness) {
388
+ fs.writeFileSync(
389
+ path.join(outputDir, 'witness.json'),
390
+ JSON.stringify(result.witness, null, 2),
391
+ );
392
+ }
393
+ }
394
+
395
+ module.exports = {
396
+ decompilePackage,
397
+ decompileFile,
398
+ decompileUrl,
399
+ decompileSource,
400
+ writeOutput,
401
+ beautify,
402
+ parseTarget,
403
+ verifyWitnessChain,
404
+ reconstructCode,
405
+ reconstructRunnable,
406
+ validateReconstruction,
407
+ };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * metrics.js - Code metrics extraction from JavaScript source.
3
+ *
4
+ * Computes structural metrics: function count, class count,
5
+ * declaration count, line count, async patterns, etc.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ /**
11
+ * Compute code metrics for a JavaScript source string.
12
+ *
13
+ * @param {string} source - JavaScript source code
14
+ * @returns {{
15
+ * lines: number,
16
+ * sizeBytes: number,
17
+ * functions: number,
18
+ * asyncFunctions: number,
19
+ * arrowFunctions: number,
20
+ * classes: number,
21
+ * classExtensions: number,
22
+ * constDeclarations: number,
23
+ * letDeclarations: number,
24
+ * varDeclarations: number,
25
+ * imports: number,
26
+ * exports: number,
27
+ * requires: number,
28
+ * awaitExpressions: number,
29
+ * promiseUsages: number,
30
+ * tryBlocks: number,
31
+ * throwStatements: number,
32
+ * regexLiterals: number
33
+ * }}
34
+ */
35
+ function computeMetrics(source) {
36
+ const count = (pattern) => (source.match(pattern) || []).length;
37
+
38
+ return {
39
+ lines: source.split('\n').length,
40
+ sizeBytes: Buffer.byteLength(source, 'utf-8'),
41
+ functions: count(/function\s*\w*\s*\(/g),
42
+ asyncFunctions: count(/async\s+function/g),
43
+ arrowFunctions: count(/=>/g),
44
+ classes: count(/class\s+\w+/g),
45
+ classExtensions: count(/extends\s+\w+/g),
46
+ constDeclarations: count(/\bconst\s+/g),
47
+ letDeclarations: count(/\blet\s+/g),
48
+ varDeclarations: count(/\bvar\s+/g),
49
+ imports: count(/\bimport\s+/g),
50
+ exports: count(/\bexport\s+/g),
51
+ requires: count(/\brequire\s*\(/g),
52
+ awaitExpressions: count(/\bawait\s+/g),
53
+ promiseUsages: count(/\bPromise\b/g),
54
+ tryBlocks: count(/\btry\s*\{/g),
55
+ throwStatements: count(/\bthrow\s+/g),
56
+ regexLiterals: count(/\/[^/\n]+\/[gimsuy]*/g),
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Compute a summary of metrics across multiple modules.
62
+ *
63
+ * @param {Array<{name: string, content: string}>} modules
64
+ * @returns {{moduleCount: number, totalLines: number, totalBytes: number, perModule: object[]}}
65
+ */
66
+ function computeModuleMetrics(modules) {
67
+ const perModule = modules.map((mod) => ({
68
+ name: mod.name,
69
+ ...computeMetrics(mod.content),
70
+ }));
71
+
72
+ const totalLines = perModule.reduce((sum, m) => sum + m.lines, 0);
73
+ const totalBytes = perModule.reduce((sum, m) => sum + m.sizeBytes, 0);
74
+
75
+ return {
76
+ moduleCount: modules.length,
77
+ totalLines,
78
+ totalBytes,
79
+ perModule,
80
+ };
81
+ }
82
+
83
+ module.exports = {
84
+ computeMetrics,
85
+ computeModuleMetrics,
86
+ };