ruvector 0.1.38 โ 0.1.40
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/.claude-flow/metrics/agent-metrics.json +1 -0
- package/.claude-flow/metrics/performance.json +87 -0
- package/.claude-flow/metrics/task-metrics.json +10 -0
- package/PACKAGE_SUMMARY.md +409 -0
- package/README.md +1679 -508
- package/bin/cli.js +2427 -0
- package/dist/core/agentdb-fast.d.ts +149 -0
- package/dist/core/agentdb-fast.d.ts.map +1 -0
- package/dist/core/agentdb-fast.js +301 -0
- package/dist/core/attention-fallbacks.d.ts +221 -0
- package/dist/core/attention-fallbacks.d.ts.map +1 -0
- package/dist/core/attention-fallbacks.js +361 -0
- package/dist/core/gnn-wrapper.d.ts +143 -0
- package/dist/core/gnn-wrapper.d.ts.map +1 -0
- package/dist/core/gnn-wrapper.js +213 -0
- package/dist/core/index.d.ts +15 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +39 -0
- package/dist/core/sona-wrapper.d.ts +215 -0
- package/dist/core/sona-wrapper.d.ts.map +1 -0
- package/dist/core/sona-wrapper.js +258 -0
- package/dist/index.d.ts +87 -82
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +169 -89
- package/dist/services/embedding-service.d.ts +136 -0
- package/dist/services/embedding-service.d.ts.map +1 -0
- package/dist/services/embedding-service.js +294 -0
- package/dist/services/index.d.ts +6 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +26 -0
- package/dist/types.d.ts +145 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/examples/api-usage.js +211 -0
- package/examples/cli-demo.sh +85 -0
- package/package.json +41 -93
- package/bin/ruvector.js +0 -1150
- package/dist/index.d.mts +0 -95
- package/dist/index.mjs +0 -5
package/bin/ruvector.js
DELETED
|
@@ -1,1150 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* rUvector CLI
|
|
5
|
-
*
|
|
6
|
-
* Beautiful command-line interface for vector database operations
|
|
7
|
-
* Includes: Vector Search, Graph/Cypher, GNN, Compression, and more
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const { Command } = require('commander');
|
|
11
|
-
const chalk = require('chalk');
|
|
12
|
-
const ora = require('ora');
|
|
13
|
-
const Table = require('cli-table3');
|
|
14
|
-
const fs = require('fs').promises;
|
|
15
|
-
const path = require('path');
|
|
16
|
-
|
|
17
|
-
// Lazy load backends to improve startup time
|
|
18
|
-
let vectorBackend = null;
|
|
19
|
-
let graphBackend = null;
|
|
20
|
-
let gnnBackend = null;
|
|
21
|
-
|
|
22
|
-
function getVectorBackend() {
|
|
23
|
-
if (!vectorBackend) {
|
|
24
|
-
try {
|
|
25
|
-
const { VectorIndex, getBackendInfo, Utils } = require('../dist/index.js');
|
|
26
|
-
vectorBackend = { VectorIndex, getBackendInfo, Utils };
|
|
27
|
-
} catch (e) {
|
|
28
|
-
console.error(chalk.red('Vector backend not available:', e.message));
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return vectorBackend;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function getGraphBackend() {
|
|
36
|
-
if (!graphBackend) {
|
|
37
|
-
try {
|
|
38
|
-
graphBackend = require('@ruvector/graph-node');
|
|
39
|
-
} catch (e) {
|
|
40
|
-
try {
|
|
41
|
-
graphBackend = require('@ruvector/graph-wasm');
|
|
42
|
-
} catch (e2) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return graphBackend;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function getGnnBackend() {
|
|
51
|
-
if (!gnnBackend) {
|
|
52
|
-
try {
|
|
53
|
-
gnnBackend = require('@ruvector/gnn-node');
|
|
54
|
-
} catch (e) {
|
|
55
|
-
try {
|
|
56
|
-
gnnBackend = require('@ruvector/gnn-wasm');
|
|
57
|
-
} catch (e2) {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return gnnBackend;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const program = new Command();
|
|
66
|
-
|
|
67
|
-
// Utility to format numbers
|
|
68
|
-
function formatNumber(num) {
|
|
69
|
-
if (num >= 1_000_000) {
|
|
70
|
-
return `${(num / 1_000_000).toFixed(2)}M`;
|
|
71
|
-
} else if (num >= 1_000) {
|
|
72
|
-
return `${(num / 1_000).toFixed(2)}K`;
|
|
73
|
-
}
|
|
74
|
-
return num.toString();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Utility to format bytes
|
|
78
|
-
function formatBytes(bytes) {
|
|
79
|
-
if (bytes >= 1_073_741_824) {
|
|
80
|
-
return `${(bytes / 1_073_741_824).toFixed(2)} GB`;
|
|
81
|
-
} else if (bytes >= 1_048_576) {
|
|
82
|
-
return `${(bytes / 1_048_576).toFixed(2)} MB`;
|
|
83
|
-
} else if (bytes >= 1_024) {
|
|
84
|
-
return `${(bytes / 1_024).toFixed(2)} KB`;
|
|
85
|
-
}
|
|
86
|
-
return `${bytes} B`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Utility to format duration
|
|
90
|
-
function formatDuration(ms) {
|
|
91
|
-
if (ms >= 1000) {
|
|
92
|
-
return `${(ms / 1000).toFixed(2)}s`;
|
|
93
|
-
}
|
|
94
|
-
return `${ms.toFixed(2)}ms`;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Doctor command - diagnose installation
|
|
98
|
-
program
|
|
99
|
-
.command('doctor')
|
|
100
|
-
.description('Diagnose installation and check all dependencies')
|
|
101
|
-
.action(async () => {
|
|
102
|
-
console.log(chalk.bold.cyan('\n๐ฉบ rUvector Doctor - Diagnosing Installation\n'));
|
|
103
|
-
|
|
104
|
-
const checks = [];
|
|
105
|
-
|
|
106
|
-
// Check Node.js version
|
|
107
|
-
const nodeVersion = process.version;
|
|
108
|
-
const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0]);
|
|
109
|
-
checks.push({
|
|
110
|
-
name: 'Node.js Version',
|
|
111
|
-
status: nodeMajor >= 16 ? 'pass' : 'fail',
|
|
112
|
-
message: nodeVersion,
|
|
113
|
-
hint: nodeMajor < 16 ? 'Requires Node.js >= 16.0.0' : null
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
// Check core package
|
|
117
|
-
let coreVersion = null;
|
|
118
|
-
try {
|
|
119
|
-
const core = require('@ruvector/core');
|
|
120
|
-
coreVersion = typeof core.version === 'function' ? core.version() : (core.version || 'installed');
|
|
121
|
-
checks.push({
|
|
122
|
-
name: '@ruvector/core',
|
|
123
|
-
status: 'pass',
|
|
124
|
-
message: coreVersion
|
|
125
|
-
});
|
|
126
|
-
} catch (e) {
|
|
127
|
-
checks.push({
|
|
128
|
-
name: '@ruvector/core',
|
|
129
|
-
status: 'warn',
|
|
130
|
-
message: 'Not installed (using fallback)',
|
|
131
|
-
hint: 'npm install @ruvector/core'
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Check graph package (Node.js native)
|
|
136
|
-
try {
|
|
137
|
-
const graph = require('@ruvector/graph-node');
|
|
138
|
-
checks.push({
|
|
139
|
-
name: '@ruvector/graph-node',
|
|
140
|
-
status: 'pass',
|
|
141
|
-
message: graph.version || 'installed'
|
|
142
|
-
});
|
|
143
|
-
} catch (e) {
|
|
144
|
-
// Check WASM fallback
|
|
145
|
-
try {
|
|
146
|
-
const graphWasm = require('@ruvector/graph-wasm');
|
|
147
|
-
checks.push({
|
|
148
|
-
name: '@ruvector/graph-wasm',
|
|
149
|
-
status: 'pass',
|
|
150
|
-
message: graphWasm.version || 'installed (WASM)'
|
|
151
|
-
});
|
|
152
|
-
} catch (e2) {
|
|
153
|
-
checks.push({
|
|
154
|
-
name: 'Graph Module',
|
|
155
|
-
status: 'warn',
|
|
156
|
-
message: 'Not installed',
|
|
157
|
-
hint: 'npm install @ruvector/graph-node'
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Check GNN package (Node.js native)
|
|
163
|
-
try {
|
|
164
|
-
const gnn = require('@ruvector/gnn-node');
|
|
165
|
-
checks.push({
|
|
166
|
-
name: '@ruvector/gnn-node',
|
|
167
|
-
status: 'pass',
|
|
168
|
-
message: gnn.version || 'installed'
|
|
169
|
-
});
|
|
170
|
-
} catch (e) {
|
|
171
|
-
// Check WASM fallback
|
|
172
|
-
try {
|
|
173
|
-
const gnnWasm = require('@ruvector/gnn-wasm');
|
|
174
|
-
checks.push({
|
|
175
|
-
name: '@ruvector/gnn-wasm',
|
|
176
|
-
status: 'pass',
|
|
177
|
-
message: gnnWasm.version || 'installed (WASM)'
|
|
178
|
-
});
|
|
179
|
-
} catch (e2) {
|
|
180
|
-
checks.push({
|
|
181
|
-
name: 'GNN Module',
|
|
182
|
-
status: 'warn',
|
|
183
|
-
message: 'Not installed',
|
|
184
|
-
hint: 'npm install @ruvector/gnn-node'
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Check dist files
|
|
190
|
-
const distPath = require('path').join(__dirname, '..', 'dist', 'index.js');
|
|
191
|
-
try {
|
|
192
|
-
require('fs').accessSync(distPath);
|
|
193
|
-
checks.push({
|
|
194
|
-
name: 'Built dist files',
|
|
195
|
-
status: 'pass',
|
|
196
|
-
message: 'Found'
|
|
197
|
-
});
|
|
198
|
-
} catch (e) {
|
|
199
|
-
checks.push({
|
|
200
|
-
name: 'Built dist files',
|
|
201
|
-
status: 'fail',
|
|
202
|
-
message: 'Not found',
|
|
203
|
-
hint: 'Run npm run build in the ruvector package'
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Display results
|
|
208
|
-
const table = new Table({
|
|
209
|
-
head: ['Check', 'Status', 'Details'],
|
|
210
|
-
colWidths: [25, 10, 40]
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
let hasErrors = false;
|
|
214
|
-
let hasWarnings = false;
|
|
215
|
-
|
|
216
|
-
checks.forEach(check => {
|
|
217
|
-
let statusIcon;
|
|
218
|
-
if (check.status === 'pass') {
|
|
219
|
-
statusIcon = chalk.green('โ Pass');
|
|
220
|
-
} else if (check.status === 'warn') {
|
|
221
|
-
statusIcon = chalk.yellow('โ Warn');
|
|
222
|
-
hasWarnings = true;
|
|
223
|
-
} else {
|
|
224
|
-
statusIcon = chalk.red('โ Fail');
|
|
225
|
-
hasErrors = true;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
table.push([
|
|
229
|
-
check.name,
|
|
230
|
-
statusIcon,
|
|
231
|
-
check.message + (check.hint ? chalk.gray(` (${check.hint})`) : '')
|
|
232
|
-
]);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
console.log(table.toString());
|
|
236
|
-
console.log();
|
|
237
|
-
|
|
238
|
-
// Summary
|
|
239
|
-
if (hasErrors) {
|
|
240
|
-
console.log(chalk.red('โ Some required checks failed. Please fix the issues above.'));
|
|
241
|
-
} else if (hasWarnings) {
|
|
242
|
-
console.log(chalk.yellow('โ All required checks passed, but some optional modules are missing.'));
|
|
243
|
-
console.log(chalk.cyan(' Install optional modules for full functionality.'));
|
|
244
|
-
} else {
|
|
245
|
-
console.log(chalk.green('โ All checks passed! rUvector is ready to use.'));
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Show available features
|
|
249
|
-
console.log(chalk.bold.cyan('\n๐ฆ Available Commands:\n'));
|
|
250
|
-
console.log(chalk.white(' Core: ') + chalk.green('info, init, stats, insert, search, benchmark'));
|
|
251
|
-
if (getGraphBackend()) {
|
|
252
|
-
console.log(chalk.white(' Graph: ') + chalk.green('graph query, graph create-node'));
|
|
253
|
-
} else {
|
|
254
|
-
console.log(chalk.white(' Graph: ') + chalk.gray('(install @ruvector/graph-node)'));
|
|
255
|
-
}
|
|
256
|
-
if (getGnnBackend()) {
|
|
257
|
-
console.log(chalk.white(' GNN: ') + chalk.green('gnn layer, gnn compress'));
|
|
258
|
-
} else {
|
|
259
|
-
console.log(chalk.white(' GNN: ') + chalk.gray('(install @ruvector/gnn-node)'));
|
|
260
|
-
}
|
|
261
|
-
console.log();
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
// Info command
|
|
265
|
-
program
|
|
266
|
-
.command('info')
|
|
267
|
-
.description('Show backend information and available modules')
|
|
268
|
-
.action(() => {
|
|
269
|
-
const { getBackendInfo } = getVectorBackend();
|
|
270
|
-
const info = getBackendInfo();
|
|
271
|
-
|
|
272
|
-
console.log(chalk.bold.cyan('\n๐ rUvector - All-in-One Vector Database\n'));
|
|
273
|
-
|
|
274
|
-
const table = new Table({
|
|
275
|
-
chars: { 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
table.push(
|
|
279
|
-
['Backend Type', chalk.green(info.type === 'native' ? 'โก Native' : '๐ WASM')],
|
|
280
|
-
['Version', info.version],
|
|
281
|
-
['Features', info.features.join(', ')]
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
console.log(table.toString());
|
|
285
|
-
console.log();
|
|
286
|
-
|
|
287
|
-
// Show available modules
|
|
288
|
-
console.log(chalk.bold.cyan('๐ฆ Available Modules:\n'));
|
|
289
|
-
const modulesTable = new Table({
|
|
290
|
-
head: ['Module', 'Status', 'Description'],
|
|
291
|
-
colWidths: [20, 12, 45]
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
// Check vector
|
|
295
|
-
modulesTable.push(['Vector Search', chalk.green('โ Ready'), 'HNSW index, similarity search']);
|
|
296
|
-
|
|
297
|
-
// Check graph
|
|
298
|
-
const graphAvailable = getGraphBackend() !== null;
|
|
299
|
-
modulesTable.push([
|
|
300
|
-
'Graph/Cypher',
|
|
301
|
-
graphAvailable ? chalk.green('โ Ready') : chalk.yellow('โ Optional'),
|
|
302
|
-
'Neo4j-compatible queries, hyperedges'
|
|
303
|
-
]);
|
|
304
|
-
|
|
305
|
-
// Check GNN
|
|
306
|
-
const gnnAvailable = getGnnBackend() !== null;
|
|
307
|
-
modulesTable.push([
|
|
308
|
-
'GNN Layers',
|
|
309
|
-
gnnAvailable ? chalk.green('โ Ready') : chalk.yellow('โ Optional'),
|
|
310
|
-
'Neural network on graph topology'
|
|
311
|
-
]);
|
|
312
|
-
|
|
313
|
-
// Built-in features
|
|
314
|
-
modulesTable.push(['Compression', chalk.green('โ Built-in'), 'f32โf16โPQ8โPQ4โBinary (2-32x)']);
|
|
315
|
-
modulesTable.push(['WASM/Browser', chalk.green('โ Built-in'), 'Client-side vector search']);
|
|
316
|
-
|
|
317
|
-
console.log(modulesTable.toString());
|
|
318
|
-
console.log();
|
|
319
|
-
|
|
320
|
-
if (!graphAvailable || !gnnAvailable) {
|
|
321
|
-
console.log(chalk.cyan('๐ก Install optional modules:'));
|
|
322
|
-
if (!graphAvailable) {
|
|
323
|
-
console.log(chalk.white(' npm install @ruvector/graph-node'));
|
|
324
|
-
}
|
|
325
|
-
if (!gnnAvailable) {
|
|
326
|
-
console.log(chalk.white(' npm install @ruvector/gnn-node'));
|
|
327
|
-
}
|
|
328
|
-
console.log();
|
|
329
|
-
}
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
// Init command
|
|
333
|
-
program
|
|
334
|
-
.command('init <path>')
|
|
335
|
-
.description('Initialize a new vector index')
|
|
336
|
-
.option('-d, --dimension <number>', 'Vector dimension', '384')
|
|
337
|
-
.option('-m, --metric <type>', 'Distance metric (cosine|euclidean|dot)', 'cosine')
|
|
338
|
-
.option('-t, --type <type>', 'Index type (flat|hnsw)', 'hnsw')
|
|
339
|
-
.option('--hnsw-m <number>', 'HNSW M parameter', '16')
|
|
340
|
-
.option('--hnsw-ef <number>', 'HNSW ef_construction parameter', '200')
|
|
341
|
-
.action(async (indexPath, options) => {
|
|
342
|
-
const spinner = ora('Initializing vector index...').start();
|
|
343
|
-
|
|
344
|
-
try {
|
|
345
|
-
const { VectorIndex } = getVectorBackend();
|
|
346
|
-
const index = new VectorIndex({
|
|
347
|
-
dimension: parseInt(options.dimension),
|
|
348
|
-
metric: options.metric,
|
|
349
|
-
indexType: options.type,
|
|
350
|
-
hnswConfig: options.type === 'hnsw' ? {
|
|
351
|
-
m: parseInt(options.hnswM),
|
|
352
|
-
efConstruction: parseInt(options.hnswEf)
|
|
353
|
-
} : undefined
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
await index.save(indexPath);
|
|
357
|
-
|
|
358
|
-
spinner.succeed(chalk.green('Index initialized successfully!'));
|
|
359
|
-
|
|
360
|
-
console.log(chalk.cyan('\nConfiguration:'));
|
|
361
|
-
console.log(` Path: ${chalk.white(indexPath)}`);
|
|
362
|
-
console.log(` Dimension: ${chalk.white(options.dimension)}`);
|
|
363
|
-
console.log(` Metric: ${chalk.white(options.metric)}`);
|
|
364
|
-
console.log(` Type: ${chalk.white(options.type)}`);
|
|
365
|
-
|
|
366
|
-
if (options.type === 'hnsw') {
|
|
367
|
-
console.log(chalk.cyan('\nHNSW Parameters:'));
|
|
368
|
-
console.log(` M: ${chalk.white(options.hnswM)}`);
|
|
369
|
-
console.log(` ef_construction: ${chalk.white(options.hnswEf)}`);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
console.log();
|
|
373
|
-
} catch (error) {
|
|
374
|
-
spinner.fail(chalk.red('Failed to initialize index'));
|
|
375
|
-
console.error(chalk.red(error.message));
|
|
376
|
-
process.exit(1);
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
// Stats command
|
|
381
|
-
program
|
|
382
|
-
.command('stats <path>')
|
|
383
|
-
.description('Show index statistics')
|
|
384
|
-
.action(async (indexPath) => {
|
|
385
|
-
const spinner = ora('Loading index...').start();
|
|
386
|
-
|
|
387
|
-
try {
|
|
388
|
-
const { VectorIndex } = getVectorBackend();
|
|
389
|
-
const index = await VectorIndex.load(indexPath);
|
|
390
|
-
const stats = await index.stats();
|
|
391
|
-
|
|
392
|
-
spinner.succeed(chalk.green('Index loaded'));
|
|
393
|
-
|
|
394
|
-
console.log(chalk.bold.cyan('\n๐ Index Statistics\n'));
|
|
395
|
-
|
|
396
|
-
const table = new Table({
|
|
397
|
-
chars: { 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
table.push(
|
|
401
|
-
['Vectors', chalk.white(formatNumber(stats.vectorCount))],
|
|
402
|
-
['Dimension', chalk.white(stats.dimension)],
|
|
403
|
-
['Index Type', chalk.white(stats.indexType)],
|
|
404
|
-
['Memory Usage', chalk.white(stats.memoryUsage ? formatBytes(stats.memoryUsage) : 'N/A')]
|
|
405
|
-
);
|
|
406
|
-
|
|
407
|
-
console.log(table.toString());
|
|
408
|
-
console.log();
|
|
409
|
-
} catch (error) {
|
|
410
|
-
spinner.fail(chalk.red('Failed to load index'));
|
|
411
|
-
console.error(chalk.red(error.message));
|
|
412
|
-
process.exit(1);
|
|
413
|
-
}
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
// Insert command
|
|
417
|
-
program
|
|
418
|
-
.command('insert <path> <vectors-file>')
|
|
419
|
-
.description('Insert vectors from JSON file')
|
|
420
|
-
.option('-b, --batch-size <number>', 'Batch size', '1000')
|
|
421
|
-
.action(async (indexPath, vectorsFile, options) => {
|
|
422
|
-
let spinner = ora('Loading index...').start();
|
|
423
|
-
|
|
424
|
-
try {
|
|
425
|
-
const { VectorIndex } = getVectorBackend();
|
|
426
|
-
const index = await VectorIndex.load(indexPath);
|
|
427
|
-
spinner.succeed();
|
|
428
|
-
|
|
429
|
-
spinner = ora('Loading vectors...').start();
|
|
430
|
-
const data = await fs.readFile(vectorsFile, 'utf-8');
|
|
431
|
-
const vectors = JSON.parse(data);
|
|
432
|
-
spinner.succeed(chalk.green(`Loaded ${vectors.length} vectors`));
|
|
433
|
-
|
|
434
|
-
const startTime = Date.now();
|
|
435
|
-
spinner = ora('Inserting vectors...').start();
|
|
436
|
-
|
|
437
|
-
let lastProgress = 0;
|
|
438
|
-
await index.insertBatch(vectors, {
|
|
439
|
-
batchSize: parseInt(options.batchSize),
|
|
440
|
-
progressCallback: (progress) => {
|
|
441
|
-
const percent = Math.floor(progress * 100);
|
|
442
|
-
if (percent > lastProgress) {
|
|
443
|
-
spinner.text = `Inserting vectors... ${percent}%`;
|
|
444
|
-
lastProgress = percent;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
const duration = Date.now() - startTime;
|
|
450
|
-
const throughput = vectors.length / (duration / 1000);
|
|
451
|
-
|
|
452
|
-
spinner.succeed(chalk.green('Vectors inserted!'));
|
|
453
|
-
|
|
454
|
-
console.log(chalk.cyan('\nPerformance:'));
|
|
455
|
-
console.log(` Duration: ${chalk.white(formatDuration(duration))}`);
|
|
456
|
-
console.log(` Throughput: ${chalk.white(formatNumber(throughput))} vectors/sec`);
|
|
457
|
-
|
|
458
|
-
spinner = ora('Saving index...').start();
|
|
459
|
-
await index.save(indexPath);
|
|
460
|
-
spinner.succeed(chalk.green('Index saved'));
|
|
461
|
-
|
|
462
|
-
console.log();
|
|
463
|
-
} catch (error) {
|
|
464
|
-
spinner.fail(chalk.red('Operation failed'));
|
|
465
|
-
console.error(chalk.red(error.message));
|
|
466
|
-
process.exit(1);
|
|
467
|
-
}
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
// Search command
|
|
471
|
-
program
|
|
472
|
-
.command('search <path>')
|
|
473
|
-
.description('Search for similar vectors')
|
|
474
|
-
.requiredOption('-q, --query <vector>', 'Query vector as JSON array')
|
|
475
|
-
.option('-k, --top-k <number>', 'Number of results', '10')
|
|
476
|
-
.option('--ef <number>', 'HNSW ef parameter')
|
|
477
|
-
.action(async (indexPath, options) => {
|
|
478
|
-
const spinner = ora('Loading index...').start();
|
|
479
|
-
|
|
480
|
-
try {
|
|
481
|
-
const { VectorIndex } = getVectorBackend();
|
|
482
|
-
const index = await VectorIndex.load(indexPath);
|
|
483
|
-
spinner.succeed();
|
|
484
|
-
|
|
485
|
-
const query = JSON.parse(options.query);
|
|
486
|
-
|
|
487
|
-
spinner.text = 'Searching...';
|
|
488
|
-
spinner.start();
|
|
489
|
-
|
|
490
|
-
const startTime = Date.now();
|
|
491
|
-
const results = await index.search(query, {
|
|
492
|
-
k: parseInt(options.topK),
|
|
493
|
-
ef: options.ef ? parseInt(options.ef) : undefined
|
|
494
|
-
});
|
|
495
|
-
const duration = Date.now() - startTime;
|
|
496
|
-
|
|
497
|
-
spinner.succeed(chalk.green(`Found ${results.length} results in ${formatDuration(duration)}`));
|
|
498
|
-
|
|
499
|
-
console.log(chalk.bold.cyan('\n๐ Search Results\n'));
|
|
500
|
-
|
|
501
|
-
const table = new Table({
|
|
502
|
-
head: ['Rank', 'ID', 'Score', 'Metadata'],
|
|
503
|
-
colWidths: [6, 20, 12, 40]
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
results.forEach((result, i) => {
|
|
507
|
-
table.push([
|
|
508
|
-
chalk.yellow(`#${i + 1}`),
|
|
509
|
-
result.id,
|
|
510
|
-
chalk.green(result.score.toFixed(4)),
|
|
511
|
-
result.metadata ? JSON.stringify(result.metadata).substring(0, 37) + '...' : ''
|
|
512
|
-
]);
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
console.log(table.toString());
|
|
516
|
-
console.log();
|
|
517
|
-
} catch (error) {
|
|
518
|
-
spinner.fail(chalk.red('Search failed'));
|
|
519
|
-
console.error(chalk.red(error.message));
|
|
520
|
-
process.exit(1);
|
|
521
|
-
}
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
// Benchmark command
|
|
525
|
-
program
|
|
526
|
-
.command('benchmark')
|
|
527
|
-
.description('Run performance benchmarks')
|
|
528
|
-
.option('-d, --dimension <number>', 'Vector dimension', '384')
|
|
529
|
-
.option('-n, --num-vectors <number>', 'Number of vectors', '10000')
|
|
530
|
-
.option('-q, --num-queries <number>', 'Number of queries', '100')
|
|
531
|
-
.action(async (options) => {
|
|
532
|
-
const { VectorIndex, Utils } = getVectorBackend();
|
|
533
|
-
const dimension = parseInt(options.dimension);
|
|
534
|
-
const numVectors = parseInt(options.numVectors);
|
|
535
|
-
const numQueries = parseInt(options.numQueries);
|
|
536
|
-
|
|
537
|
-
console.log(chalk.bold.cyan('\nโก Performance Benchmark\n'));
|
|
538
|
-
console.log(chalk.cyan('Configuration:'));
|
|
539
|
-
console.log(` Dimension: ${chalk.white(dimension)}`);
|
|
540
|
-
console.log(` Vectors: ${chalk.white(formatNumber(numVectors))}`);
|
|
541
|
-
console.log(` Queries: ${chalk.white(formatNumber(numQueries))}`);
|
|
542
|
-
console.log();
|
|
543
|
-
|
|
544
|
-
const results = [];
|
|
545
|
-
|
|
546
|
-
try {
|
|
547
|
-
// Create index
|
|
548
|
-
let spinner = ora('Creating index...').start();
|
|
549
|
-
const index = new VectorIndex({
|
|
550
|
-
dimension,
|
|
551
|
-
metric: 'cosine',
|
|
552
|
-
indexType: 'hnsw'
|
|
553
|
-
});
|
|
554
|
-
spinner.succeed();
|
|
555
|
-
|
|
556
|
-
// Generate vectors
|
|
557
|
-
spinner = ora('Generating vectors...').start();
|
|
558
|
-
const vectors = [];
|
|
559
|
-
for (let i = 0; i < numVectors; i++) {
|
|
560
|
-
vectors.push({
|
|
561
|
-
id: `vec_${i}`,
|
|
562
|
-
values: Utils.randomVector(dimension)
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
spinner.succeed();
|
|
566
|
-
|
|
567
|
-
// Insert benchmark
|
|
568
|
-
spinner = ora('Benchmarking inserts...').start();
|
|
569
|
-
const insertStart = Date.now();
|
|
570
|
-
await index.insertBatch(vectors, { batchSize: 1000 });
|
|
571
|
-
const insertDuration = Date.now() - insertStart;
|
|
572
|
-
const insertThroughput = numVectors / (insertDuration / 1000);
|
|
573
|
-
spinner.succeed();
|
|
574
|
-
|
|
575
|
-
results.push({
|
|
576
|
-
operation: 'Insert',
|
|
577
|
-
duration: insertDuration,
|
|
578
|
-
throughput: insertThroughput
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
// Search benchmark
|
|
582
|
-
spinner = ora('Benchmarking searches...').start();
|
|
583
|
-
const queries = [];
|
|
584
|
-
for (let i = 0; i < numQueries; i++) {
|
|
585
|
-
queries.push(Utils.randomVector(dimension));
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
const searchStart = Date.now();
|
|
589
|
-
for (const query of queries) {
|
|
590
|
-
await index.search(query, { k: 10 });
|
|
591
|
-
}
|
|
592
|
-
const searchDuration = Date.now() - searchStart;
|
|
593
|
-
const searchThroughput = numQueries / (searchDuration / 1000);
|
|
594
|
-
spinner.succeed();
|
|
595
|
-
|
|
596
|
-
results.push({
|
|
597
|
-
operation: 'Search',
|
|
598
|
-
duration: searchDuration,
|
|
599
|
-
throughput: searchThroughput
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
// Display results
|
|
603
|
-
console.log(chalk.bold.cyan('\n๐ Results\n'));
|
|
604
|
-
|
|
605
|
-
const table = new Table({
|
|
606
|
-
head: ['Operation', 'Total Time', 'Throughput'],
|
|
607
|
-
colWidths: [15, 20, 25]
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
results.forEach(result => {
|
|
611
|
-
table.push([
|
|
612
|
-
chalk.white(result.operation),
|
|
613
|
-
chalk.yellow(formatDuration(result.duration)),
|
|
614
|
-
chalk.green(`${formatNumber(result.throughput)} ops/sec`)
|
|
615
|
-
]);
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
console.log(table.toString());
|
|
619
|
-
console.log();
|
|
620
|
-
|
|
621
|
-
// Backend info
|
|
622
|
-
const { getBackendInfo } = getVectorBackend();
|
|
623
|
-
const info = getBackendInfo();
|
|
624
|
-
console.log(chalk.cyan(`Backend: ${chalk.white(info.type)}`));
|
|
625
|
-
console.log();
|
|
626
|
-
|
|
627
|
-
} catch (error) {
|
|
628
|
-
console.error(chalk.red('Benchmark failed:'), error.message);
|
|
629
|
-
process.exit(1);
|
|
630
|
-
}
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
// ============================================================================
|
|
634
|
-
// GRAPH COMMANDS
|
|
635
|
-
// ============================================================================
|
|
636
|
-
|
|
637
|
-
const graphCmd = program
|
|
638
|
-
.command('graph')
|
|
639
|
-
.description('Graph database commands (Cypher queries, nodes, edges)');
|
|
640
|
-
|
|
641
|
-
graphCmd
|
|
642
|
-
.command('query <cypher>')
|
|
643
|
-
.description('Execute a Cypher query')
|
|
644
|
-
.option('-f, --format <type>', 'Output format (table|json)', 'table')
|
|
645
|
-
.action(async (cypher, options) => {
|
|
646
|
-
const graph = getGraphBackend();
|
|
647
|
-
if (!graph) {
|
|
648
|
-
console.error(chalk.red('Graph module not installed. Run: npm install @ruvector/graph-node'));
|
|
649
|
-
process.exit(1);
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
const spinner = ora('Executing Cypher query...').start();
|
|
653
|
-
try {
|
|
654
|
-
const db = new graph.GraphDB();
|
|
655
|
-
const results = await db.query(cypher);
|
|
656
|
-
spinner.succeed(chalk.green(`Query returned ${results.length} results`));
|
|
657
|
-
|
|
658
|
-
if (options.format === 'json') {
|
|
659
|
-
console.log(JSON.stringify(results, null, 2));
|
|
660
|
-
} else {
|
|
661
|
-
if (results.length > 0) {
|
|
662
|
-
const table = new Table({
|
|
663
|
-
head: Object.keys(results[0]).map(k => chalk.cyan(k))
|
|
664
|
-
});
|
|
665
|
-
results.forEach(row => {
|
|
666
|
-
table.push(Object.values(row).map(v =>
|
|
667
|
-
typeof v === 'object' ? JSON.stringify(v) : String(v)
|
|
668
|
-
));
|
|
669
|
-
});
|
|
670
|
-
console.log(table.toString());
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
} catch (error) {
|
|
674
|
-
spinner.fail(chalk.red('Query failed'));
|
|
675
|
-
console.error(chalk.red(error.message));
|
|
676
|
-
process.exit(1);
|
|
677
|
-
}
|
|
678
|
-
});
|
|
679
|
-
|
|
680
|
-
graphCmd
|
|
681
|
-
.command('create-node')
|
|
682
|
-
.description('Create a new node')
|
|
683
|
-
.requiredOption('-l, --label <label>', 'Node label')
|
|
684
|
-
.requiredOption('-p, --properties <json>', 'Node properties as JSON')
|
|
685
|
-
.action(async (options) => {
|
|
686
|
-
const graph = getGraphBackend();
|
|
687
|
-
if (!graph) {
|
|
688
|
-
console.error(chalk.red('Graph module not installed. Run: npm install @ruvector/graph-node'));
|
|
689
|
-
process.exit(1);
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
try {
|
|
693
|
-
const db = new graph.GraphDB();
|
|
694
|
-
const props = JSON.parse(options.properties);
|
|
695
|
-
const nodeId = await db.createNode(options.label, props);
|
|
696
|
-
console.log(chalk.green(`โ Created node: ${nodeId}`));
|
|
697
|
-
} catch (error) {
|
|
698
|
-
console.error(chalk.red('Failed to create node:', error.message));
|
|
699
|
-
process.exit(1);
|
|
700
|
-
}
|
|
701
|
-
});
|
|
702
|
-
|
|
703
|
-
// ============================================================================
|
|
704
|
-
// GNN COMMANDS
|
|
705
|
-
// ============================================================================
|
|
706
|
-
|
|
707
|
-
const gnnCmd = program
|
|
708
|
-
.command('gnn')
|
|
709
|
-
.description('Graph Neural Network commands');
|
|
710
|
-
|
|
711
|
-
gnnCmd
|
|
712
|
-
.command('layer')
|
|
713
|
-
.description('Create and test a GNN layer')
|
|
714
|
-
.option('-i, --input-dim <number>', 'Input dimension', '128')
|
|
715
|
-
.option('-h, --hidden-dim <number>', 'Hidden dimension', '256')
|
|
716
|
-
.option('--heads <number>', 'Attention heads', '4')
|
|
717
|
-
.action(async (options) => {
|
|
718
|
-
const gnn = getGnnBackend();
|
|
719
|
-
if (!gnn) {
|
|
720
|
-
console.error(chalk.red('GNN module not installed. Run: npm install @ruvector/gnn-node'));
|
|
721
|
-
process.exit(1);
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
try {
|
|
725
|
-
const layer = new gnn.RuvectorLayer(
|
|
726
|
-
parseInt(options.inputDim),
|
|
727
|
-
parseInt(options.hiddenDim),
|
|
728
|
-
parseInt(options.heads),
|
|
729
|
-
0.1 // dropout
|
|
730
|
-
);
|
|
731
|
-
console.log(chalk.green('โ GNN Layer created'));
|
|
732
|
-
console.log(chalk.cyan('Configuration:'));
|
|
733
|
-
console.log(` Input dim: ${options.inputDim}`);
|
|
734
|
-
console.log(` Hidden dim: ${options.hiddenDim}`);
|
|
735
|
-
console.log(` Attention heads: ${options.heads}`);
|
|
736
|
-
} catch (error) {
|
|
737
|
-
console.error(chalk.red('Failed to create GNN layer:', error.message));
|
|
738
|
-
process.exit(1);
|
|
739
|
-
}
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
gnnCmd
|
|
743
|
-
.command('compress')
|
|
744
|
-
.description('Compress a vector using adaptive compression')
|
|
745
|
-
.requiredOption('-v, --vector <json>', 'Vector as JSON array')
|
|
746
|
-
.option('-f, --frequency <number>', 'Access frequency (0-1)', '0.5')
|
|
747
|
-
.action(async (options) => {
|
|
748
|
-
const gnn = getGnnBackend();
|
|
749
|
-
if (!gnn) {
|
|
750
|
-
console.error(chalk.red('GNN module not installed. Run: npm install @ruvector/gnn-node'));
|
|
751
|
-
process.exit(1);
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
try {
|
|
755
|
-
const vector = JSON.parse(options.vector);
|
|
756
|
-
const freq = parseFloat(options.frequency);
|
|
757
|
-
const compressor = new gnn.TensorCompress();
|
|
758
|
-
const compressed = compressor.compress(vector, freq);
|
|
759
|
-
|
|
760
|
-
console.log(chalk.green('โ Vector compressed'));
|
|
761
|
-
console.log(chalk.cyan('Compression info:'));
|
|
762
|
-
console.log(` Original size: ${vector.length * 4} bytes`);
|
|
763
|
-
console.log(` Compressed: ${JSON.stringify(compressed).length} bytes`);
|
|
764
|
-
console.log(` Access frequency: ${freq}`);
|
|
765
|
-
console.log(` Level: ${gnn.getCompressionLevel(freq)}`);
|
|
766
|
-
} catch (error) {
|
|
767
|
-
console.error(chalk.red('Compression failed:', error.message));
|
|
768
|
-
process.exit(1);
|
|
769
|
-
}
|
|
770
|
-
});
|
|
771
|
-
|
|
772
|
-
// Version
|
|
773
|
-
program.version(require('../package.json').version, '-v, --version', 'Show version');
|
|
774
|
-
|
|
775
|
-
// Help customization
|
|
776
|
-
program.on('--help', () => {
|
|
777
|
-
console.log('');
|
|
778
|
-
console.log(chalk.bold.cyan('Diagnostics:'));
|
|
779
|
-
console.log(' $ ruvector doctor Diagnose installation');
|
|
780
|
-
console.log(' $ ruvector info Show backend info');
|
|
781
|
-
console.log('');
|
|
782
|
-
console.log(chalk.bold.cyan('Vector Commands:'));
|
|
783
|
-
console.log(' $ ruvector init my-index.bin -d 384 Initialize index');
|
|
784
|
-
console.log(' $ ruvector stats my-index.bin Show index statistics');
|
|
785
|
-
console.log(' $ ruvector insert my-index.bin vectors.json Insert vectors');
|
|
786
|
-
console.log(' $ ruvector search my-index.bin -q "[0.1,...]" -k 10');
|
|
787
|
-
console.log(' $ ruvector benchmark -d 384 -n 10000 Run benchmarks');
|
|
788
|
-
console.log('');
|
|
789
|
-
console.log(chalk.bold.cyan('Graph Commands (requires @ruvector/graph-node):'));
|
|
790
|
-
console.log(' $ ruvector graph query "MATCH (n) RETURN n" Execute Cypher');
|
|
791
|
-
console.log(' $ ruvector graph create-node -l Person -p \'{"name":"Alice"}\'');
|
|
792
|
-
console.log('');
|
|
793
|
-
console.log(chalk.bold.cyan('GNN Commands (requires @ruvector/gnn-node):'));
|
|
794
|
-
console.log(' $ ruvector gnn layer -i 128 -h 256 --heads 4 Create GNN layer');
|
|
795
|
-
console.log(' $ ruvector gnn compress -v "[0.1,...]" -f 0.5 Compress vector');
|
|
796
|
-
console.log('');
|
|
797
|
-
console.log(chalk.bold.cyan('Hooks Commands (Claude Code integration):'));
|
|
798
|
-
console.log(' $ ruvector hooks init Initialize hooks');
|
|
799
|
-
console.log(' $ ruvector hooks stats Show intelligence stats');
|
|
800
|
-
console.log(' $ ruvector hooks session-start Start session');
|
|
801
|
-
console.log(' $ ruvector hooks pre-edit <file> Pre-edit intelligence');
|
|
802
|
-
console.log(' $ ruvector hooks post-edit <file> Post-edit learning');
|
|
803
|
-
console.log('');
|
|
804
|
-
console.log(chalk.cyan('For more info: https://github.com/ruvnet/ruvector'));
|
|
805
|
-
console.log('');
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
// ============================================================================
|
|
809
|
-
// HOOKS COMMANDS - Self-learning intelligence for Claude Code
|
|
810
|
-
// ============================================================================
|
|
811
|
-
|
|
812
|
-
const INTEL_PATH = path.join(require('os').homedir(), '.ruvector', 'intelligence.json');
|
|
813
|
-
|
|
814
|
-
class Intelligence {
|
|
815
|
-
constructor() {
|
|
816
|
-
this.data = this.load();
|
|
817
|
-
this.alpha = 0.1;
|
|
818
|
-
this.lastEditedFile = null;
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
load() {
|
|
822
|
-
try {
|
|
823
|
-
const fss = require('fs');
|
|
824
|
-
if (fss.existsSync(INTEL_PATH)) {
|
|
825
|
-
return JSON.parse(fss.readFileSync(INTEL_PATH, 'utf-8'));
|
|
826
|
-
}
|
|
827
|
-
} catch {}
|
|
828
|
-
return {
|
|
829
|
-
patterns: {},
|
|
830
|
-
memories: [],
|
|
831
|
-
trajectories: [],
|
|
832
|
-
errors: {},
|
|
833
|
-
file_sequences: [],
|
|
834
|
-
agents: {},
|
|
835
|
-
edges: [],
|
|
836
|
-
stats: { total_patterns: 0, total_memories: 0, total_trajectories: 0, total_errors: 0, session_count: 0, last_session: 0 }
|
|
837
|
-
};
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
save() {
|
|
841
|
-
const fss = require('fs');
|
|
842
|
-
const dir = path.dirname(INTEL_PATH);
|
|
843
|
-
if (!fss.existsSync(dir)) fss.mkdirSync(dir, { recursive: true });
|
|
844
|
-
fss.writeFileSync(INTEL_PATH, JSON.stringify(this.data, null, 2));
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
now() { return Math.floor(Date.now() / 1000); }
|
|
848
|
-
|
|
849
|
-
embed(text) {
|
|
850
|
-
const embedding = new Array(64).fill(0);
|
|
851
|
-
for (let i = 0; i < text.length; i++) {
|
|
852
|
-
const idx = (text.charCodeAt(i) + i * 7) % 64;
|
|
853
|
-
embedding[idx] += 1.0;
|
|
854
|
-
}
|
|
855
|
-
const norm = Math.sqrt(embedding.reduce((a, b) => a + b * b, 0));
|
|
856
|
-
if (norm > 0) for (let i = 0; i < embedding.length; i++) embedding[i] /= norm;
|
|
857
|
-
return embedding;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
similarity(a, b) {
|
|
861
|
-
if (a.length !== b.length) return 0;
|
|
862
|
-
const dot = a.reduce((sum, v, i) => sum + v * b[i], 0);
|
|
863
|
-
const normA = Math.sqrt(a.reduce((sum, v) => sum + v * v, 0));
|
|
864
|
-
const normB = Math.sqrt(b.reduce((sum, v) => sum + v * v, 0));
|
|
865
|
-
return normA > 0 && normB > 0 ? dot / (normA * normB) : 0;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
remember(memoryType, content, metadata = {}) {
|
|
869
|
-
const id = `mem_${this.now()}`;
|
|
870
|
-
this.data.memories.push({ id, memory_type: memoryType, content, embedding: this.embed(content), metadata, timestamp: this.now() });
|
|
871
|
-
if (this.data.memories.length > 5000) this.data.memories.splice(0, 1000);
|
|
872
|
-
this.data.stats.total_memories = this.data.memories.length;
|
|
873
|
-
return id;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
recall(query, topK) {
|
|
877
|
-
const queryEmbed = this.embed(query);
|
|
878
|
-
return this.data.memories
|
|
879
|
-
.map(m => ({ score: this.similarity(queryEmbed, m.embedding), memory: m }))
|
|
880
|
-
.sort((a, b) => b.score - a.score).slice(0, topK).map(r => r.memory);
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
getQ(state, action) {
|
|
884
|
-
const key = `${state}|${action}`;
|
|
885
|
-
return this.data.patterns[key]?.q_value ?? 0;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
updateQ(state, action, reward) {
|
|
889
|
-
const key = `${state}|${action}`;
|
|
890
|
-
if (!this.data.patterns[key]) this.data.patterns[key] = { state, action, q_value: 0, visits: 0, last_update: 0 };
|
|
891
|
-
const p = this.data.patterns[key];
|
|
892
|
-
p.q_value = p.q_value + this.alpha * (reward - p.q_value);
|
|
893
|
-
p.visits++;
|
|
894
|
-
p.last_update = this.now();
|
|
895
|
-
this.data.stats.total_patterns = Object.keys(this.data.patterns).length;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
learn(state, action, outcome, reward) {
|
|
899
|
-
const id = `traj_${this.now()}`;
|
|
900
|
-
this.updateQ(state, action, reward);
|
|
901
|
-
this.data.trajectories.push({ id, state, action, outcome, reward, timestamp: this.now() });
|
|
902
|
-
if (this.data.trajectories.length > 1000) this.data.trajectories.splice(0, 200);
|
|
903
|
-
this.data.stats.total_trajectories = this.data.trajectories.length;
|
|
904
|
-
return id;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
suggest(state, actions) {
|
|
908
|
-
let bestAction = actions[0] ?? '';
|
|
909
|
-
let bestQ = -Infinity;
|
|
910
|
-
for (const action of actions) {
|
|
911
|
-
const q = this.getQ(state, action);
|
|
912
|
-
if (q > bestQ) { bestQ = q; bestAction = action; }
|
|
913
|
-
}
|
|
914
|
-
return { action: bestAction, confidence: bestQ > 0 ? Math.min(bestQ, 1) : 0 };
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
route(task, file, crateName, operation = 'edit') {
|
|
918
|
-
const fileType = file ? path.extname(file).slice(1) : 'unknown';
|
|
919
|
-
const state = `${operation}_${fileType}_in_${crateName ?? 'project'}`;
|
|
920
|
-
const agentMap = {
|
|
921
|
-
rs: ['rust-developer', 'coder', 'reviewer', 'tester'],
|
|
922
|
-
ts: ['typescript-developer', 'coder', 'frontend-dev'],
|
|
923
|
-
tsx: ['typescript-developer', 'coder', 'frontend-dev'],
|
|
924
|
-
js: ['coder', 'frontend-dev'],
|
|
925
|
-
py: ['python-developer', 'coder', 'ml-developer'],
|
|
926
|
-
md: ['docs-writer', 'coder']
|
|
927
|
-
};
|
|
928
|
-
const agents = agentMap[fileType] ?? ['coder', 'reviewer'];
|
|
929
|
-
const { action, confidence } = this.suggest(state, agents);
|
|
930
|
-
const reason = confidence > 0.5 ? 'learned from past success' : confidence > 0 ? 'based on patterns' : `default for ${fileType} files`;
|
|
931
|
-
return { agent: action, confidence, reason };
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
shouldTest(file) {
|
|
935
|
-
const ext = path.extname(file).slice(1);
|
|
936
|
-
switch (ext) {
|
|
937
|
-
case 'rs': {
|
|
938
|
-
const crateMatch = file.match(/crates\/([^/]+)/);
|
|
939
|
-
return crateMatch ? { suggest: true, command: `cargo test -p ${crateMatch[1]}` } : { suggest: true, command: 'cargo test' };
|
|
940
|
-
}
|
|
941
|
-
case 'ts': case 'tsx': case 'js': case 'jsx': return { suggest: true, command: 'npm test' };
|
|
942
|
-
case 'py': return { suggest: true, command: 'pytest' };
|
|
943
|
-
default: return { suggest: false, command: '' };
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
recordFileSequence(fromFile, toFile) {
|
|
948
|
-
const existing = this.data.file_sequences.find(s => s.from_file === fromFile && s.to_file === toFile);
|
|
949
|
-
if (existing) existing.count++;
|
|
950
|
-
else this.data.file_sequences.push({ from_file: fromFile, to_file: toFile, count: 1 });
|
|
951
|
-
this.lastEditedFile = toFile;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
suggestNext(file, limit = 3) {
|
|
955
|
-
return this.data.file_sequences.filter(s => s.from_file === file).sort((a, b) => b.count - a.count).slice(0, limit).map(s => ({ file: s.to_file, score: s.count }));
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
recordError(command, message) {
|
|
959
|
-
const codeMatch = message.match(/error\[([A-Z]\d+)\]/i) || message.match(/([A-Z]\d{4})/);
|
|
960
|
-
const codes = [];
|
|
961
|
-
if (codeMatch) {
|
|
962
|
-
const code = codeMatch[1];
|
|
963
|
-
codes.push(code);
|
|
964
|
-
if (!this.data.errors[code]) this.data.errors[code] = { code, error_type: 'unknown', message: message.slice(0, 500), fixes: [], occurrences: 0 };
|
|
965
|
-
this.data.errors[code].occurrences++;
|
|
966
|
-
this.data.stats.total_errors = Object.keys(this.data.errors).length;
|
|
967
|
-
}
|
|
968
|
-
return codes;
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
classifyCommand(command) {
|
|
972
|
-
const cmd = command.toLowerCase();
|
|
973
|
-
if (cmd.includes('cargo') || cmd.includes('rustc')) return { category: 'rust', subcategory: cmd.includes('test') ? 'test' : 'build', risk: 'low' };
|
|
974
|
-
if (cmd.includes('npm') || cmd.includes('node')) return { category: 'javascript', subcategory: cmd.includes('test') ? 'test' : 'build', risk: 'low' };
|
|
975
|
-
if (cmd.includes('git')) return { category: 'git', subcategory: 'vcs', risk: cmd.includes('push') ? 'medium' : 'low' };
|
|
976
|
-
if (cmd.includes('rm') || cmd.includes('delete')) return { category: 'filesystem', subcategory: 'destructive', risk: 'high' };
|
|
977
|
-
return { category: 'shell', subcategory: 'general', risk: 'low' };
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
swarmStats() {
|
|
981
|
-
const agents = Object.keys(this.data.agents).length;
|
|
982
|
-
const edges = this.data.edges.length;
|
|
983
|
-
const activeAgents = Object.values(this.data.agents).filter(a => a.status === 'active');
|
|
984
|
-
const avgSuccess = activeAgents.length > 0 ? activeAgents.reduce((sum, a) => sum + a.success_rate, 0) / activeAgents.length : 0;
|
|
985
|
-
return { agents, edges, avgSuccess };
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
stats() { return this.data.stats; }
|
|
989
|
-
sessionStart() { this.data.stats.session_count++; this.data.stats.last_session = this.now(); }
|
|
990
|
-
sessionEnd() {
|
|
991
|
-
const duration = this.now() - this.data.stats.last_session;
|
|
992
|
-
const actions = this.data.trajectories.filter(t => t.timestamp >= this.data.stats.last_session).length;
|
|
993
|
-
return { duration, actions };
|
|
994
|
-
}
|
|
995
|
-
getLastEditedFile() { return this.lastEditedFile; }
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
const hooksCmd = program.command('hooks').description('Self-learning intelligence hooks for Claude Code');
|
|
999
|
-
|
|
1000
|
-
hooksCmd.command('init').description('Initialize hooks in current project').option('--force', 'Force overwrite').action((opts) => {
|
|
1001
|
-
const fss = require('fs');
|
|
1002
|
-
const settingsPath = path.join(process.cwd(), '.claude', 'settings.json');
|
|
1003
|
-
const settingsDir = path.dirname(settingsPath);
|
|
1004
|
-
if (!fss.existsSync(settingsDir)) fss.mkdirSync(settingsDir, { recursive: true });
|
|
1005
|
-
let settings = {};
|
|
1006
|
-
if (fss.existsSync(settingsPath)) try { settings = JSON.parse(fss.readFileSync(settingsPath, 'utf-8')); } catch {}
|
|
1007
|
-
settings.hooks = settings.hooks || {};
|
|
1008
|
-
settings.hooks.PreToolUse = [{ matcher: 'Edit|Write|MultiEdit', hooks: ['ruvector hooks pre-edit "$TOOL_INPUT_file_path"'] }, { matcher: 'Bash', hooks: ['ruvector hooks pre-command "$TOOL_INPUT_command"'] }];
|
|
1009
|
-
settings.hooks.PostToolUse = [{ matcher: 'Edit|Write|MultiEdit', hooks: ['ruvector hooks post-edit "$TOOL_INPUT_file_path"'] }, { matcher: 'Bash', hooks: ['ruvector hooks post-command "$TOOL_INPUT_command"'] }];
|
|
1010
|
-
settings.hooks.SessionStart = ['ruvector hooks session-start'];
|
|
1011
|
-
settings.hooks.Stop = ['ruvector hooks session-end'];
|
|
1012
|
-
fss.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
1013
|
-
console.log(chalk.green('โ
Hooks initialized in .claude/settings.json'));
|
|
1014
|
-
});
|
|
1015
|
-
|
|
1016
|
-
hooksCmd.command('stats').description('Show intelligence statistics').action(() => {
|
|
1017
|
-
const intel = new Intelligence();
|
|
1018
|
-
const stats = intel.stats();
|
|
1019
|
-
const swarm = intel.swarmStats();
|
|
1020
|
-
console.log(chalk.bold.cyan('\n๐ง RuVector Intelligence Stats\n'));
|
|
1021
|
-
console.log(` ${chalk.green(stats.total_patterns)} Q-learning patterns`);
|
|
1022
|
-
console.log(` ${chalk.green(stats.total_memories)} vector memories`);
|
|
1023
|
-
console.log(` ${chalk.green(stats.total_trajectories)} learning trajectories`);
|
|
1024
|
-
console.log(` ${chalk.green(stats.total_errors)} error patterns\n`);
|
|
1025
|
-
console.log(chalk.bold('Swarm Status:'));
|
|
1026
|
-
console.log(` ${chalk.cyan(swarm.agents)} agents registered`);
|
|
1027
|
-
console.log(` ${chalk.cyan(swarm.edges)} coordination edges`);
|
|
1028
|
-
});
|
|
1029
|
-
|
|
1030
|
-
hooksCmd.command('session-start').description('Session start hook').option('--resume', 'Resume previous session').action(() => {
|
|
1031
|
-
const intel = new Intelligence();
|
|
1032
|
-
intel.sessionStart();
|
|
1033
|
-
intel.save();
|
|
1034
|
-
console.log(chalk.bold.cyan('๐ง RuVector Intelligence Layer Active'));
|
|
1035
|
-
console.log('โก Intelligence guides: agent routing, error fixes, file sequences');
|
|
1036
|
-
});
|
|
1037
|
-
|
|
1038
|
-
hooksCmd.command('session-end').description('Session end hook').option('--export-metrics', 'Export metrics').action((opts) => {
|
|
1039
|
-
const intel = new Intelligence();
|
|
1040
|
-
const sessionInfo = intel.sessionEnd();
|
|
1041
|
-
intel.save();
|
|
1042
|
-
console.log('๐ Session ended. Learning data saved.');
|
|
1043
|
-
if (opts.exportMetrics) console.log(JSON.stringify({ duration_seconds: sessionInfo.duration, actions_recorded: sessionInfo.actions }));
|
|
1044
|
-
});
|
|
1045
|
-
|
|
1046
|
-
hooksCmd.command('pre-edit').description('Pre-edit intelligence').argument('<file>', 'File path').action((file) => {
|
|
1047
|
-
const intel = new Intelligence();
|
|
1048
|
-
const fileName = path.basename(file);
|
|
1049
|
-
const crateMatch = file.match(/crates\/([^/]+)/);
|
|
1050
|
-
const crate = crateMatch?.[1];
|
|
1051
|
-
const { agent, confidence, reason } = intel.route(`edit ${fileName}`, file, crate, 'edit');
|
|
1052
|
-
console.log(chalk.bold('๐ง Intelligence Analysis:'));
|
|
1053
|
-
console.log(` ๐ ${chalk.cyan(crate ?? 'project')}/${fileName}`);
|
|
1054
|
-
console.log(` ๐ค Recommended: ${chalk.green.bold(agent)} (${(confidence * 100).toFixed(0)}% confidence)`);
|
|
1055
|
-
if (reason) console.log(` โ ${chalk.dim(reason)}`);
|
|
1056
|
-
const nextFiles = intel.suggestNext(file, 3);
|
|
1057
|
-
if (nextFiles.length > 0) { console.log(' ๐ Likely next files:'); nextFiles.forEach(n => console.log(` - ${n.file} (${n.score} edits)`)); }
|
|
1058
|
-
});
|
|
1059
|
-
|
|
1060
|
-
hooksCmd.command('post-edit').description('Post-edit learning').argument('<file>', 'File path').option('--success', 'Edit succeeded').action((file, opts) => {
|
|
1061
|
-
const intel = new Intelligence();
|
|
1062
|
-
const success = opts.success ?? true;
|
|
1063
|
-
const ext = path.extname(file).slice(1);
|
|
1064
|
-
const crateMatch = file.match(/crates\/([^/]+)/);
|
|
1065
|
-
const crate = crateMatch?.[1] ?? 'project';
|
|
1066
|
-
const state = `edit_${ext}_in_${crate}`;
|
|
1067
|
-
const lastFile = intel.getLastEditedFile();
|
|
1068
|
-
if (lastFile && lastFile !== file) intel.recordFileSequence(lastFile, file);
|
|
1069
|
-
intel.learn(state, success ? 'successful-edit' : 'failed-edit', success ? 'completed' : 'failed', success ? 1.0 : -0.5);
|
|
1070
|
-
intel.remember('edit', `${success ? 'successful' : 'failed'} edit of ${ext} in ${crate}`);
|
|
1071
|
-
intel.save();
|
|
1072
|
-
console.log(`๐ Learning recorded: ${success ? 'โ
' : 'โ'} ${path.basename(file)}`);
|
|
1073
|
-
const test = intel.shouldTest(file);
|
|
1074
|
-
if (test.suggest) console.log(` ๐งช Consider: ${chalk.cyan(test.command)}`);
|
|
1075
|
-
});
|
|
1076
|
-
|
|
1077
|
-
hooksCmd.command('pre-command').description('Pre-command intelligence').argument('<command...>', 'Command').action((command) => {
|
|
1078
|
-
const intel = new Intelligence();
|
|
1079
|
-
const cmd = command.join(' ');
|
|
1080
|
-
const classification = intel.classifyCommand(cmd);
|
|
1081
|
-
console.log(chalk.bold('๐ง Command Analysis:'));
|
|
1082
|
-
console.log(` ๐ฆ Category: ${chalk.cyan(classification.category)}`);
|
|
1083
|
-
console.log(` ๐ท๏ธ Type: ${classification.subcategory}`);
|
|
1084
|
-
if (classification.risk === 'high') console.log(` โ ๏ธ Risk: ${chalk.red('HIGH')} - Review carefully`);
|
|
1085
|
-
else if (classification.risk === 'medium') console.log(` โก Risk: ${chalk.yellow('MEDIUM')}`);
|
|
1086
|
-
else console.log(` โ
Risk: ${chalk.green('LOW')}`);
|
|
1087
|
-
});
|
|
1088
|
-
|
|
1089
|
-
hooksCmd.command('post-command').description('Post-command learning').argument('<command...>', 'Command').option('--success', 'Success').action((command, opts) => {
|
|
1090
|
-
const intel = new Intelligence();
|
|
1091
|
-
const cmd = command.join(' ');
|
|
1092
|
-
const success = opts.success ?? true;
|
|
1093
|
-
const classification = intel.classifyCommand(cmd);
|
|
1094
|
-
intel.learn(`cmd_${classification.category}_${classification.subcategory}`, success ? 'success' : 'failure', success ? 'completed' : 'failed', success ? 0.8 : -0.3);
|
|
1095
|
-
intel.remember('command', `${cmd} ${success ? 'succeeded' : 'failed'}`);
|
|
1096
|
-
intel.save();
|
|
1097
|
-
console.log(`๐ Command ${success ? 'โ
' : 'โ'} recorded`);
|
|
1098
|
-
});
|
|
1099
|
-
|
|
1100
|
-
hooksCmd.command('route').description('Route task to agent').argument('<task...>', 'Task').option('--file <file>', 'File').option('--crate <crate>', 'Crate').action((task, opts) => {
|
|
1101
|
-
const intel = new Intelligence();
|
|
1102
|
-
const result = intel.route(task.join(' '), opts.file, opts.crate);
|
|
1103
|
-
console.log(JSON.stringify({ task: task.join(' '), recommended: result.agent, confidence: result.confidence, reasoning: result.reason }, null, 2));
|
|
1104
|
-
});
|
|
1105
|
-
|
|
1106
|
-
hooksCmd.command('suggest-context').description('Suggest relevant context').action(() => {
|
|
1107
|
-
const intel = new Intelligence();
|
|
1108
|
-
const stats = intel.stats();
|
|
1109
|
-
console.log(`RuVector Intelligence: ${stats.total_patterns} learned patterns, ${stats.total_errors} error fixes available. Use 'ruvector hooks route' for agent suggestions.`);
|
|
1110
|
-
});
|
|
1111
|
-
|
|
1112
|
-
hooksCmd.command('remember').description('Store in memory').requiredOption('-t, --type <type>', 'Memory type').option('--memory-type <type>', 'Memory type alias').argument('<content...>', 'Content').action((content, opts) => {
|
|
1113
|
-
const intel = new Intelligence();
|
|
1114
|
-
const memType = opts.type || opts.memoryType || 'general';
|
|
1115
|
-
const id = intel.remember(memType, content.join(' '));
|
|
1116
|
-
intel.save();
|
|
1117
|
-
console.log(JSON.stringify({ success: true, id }));
|
|
1118
|
-
});
|
|
1119
|
-
|
|
1120
|
-
hooksCmd.command('recall').description('Search memory').argument('<query...>', 'Query').option('-k, --top-k <n>', 'Results', '5').action((query, opts) => {
|
|
1121
|
-
const intel = new Intelligence();
|
|
1122
|
-
const results = intel.recall(query.join(' '), parseInt(opts.topK));
|
|
1123
|
-
console.log(JSON.stringify({ query: query.join(' '), results: results.map(r => ({ type: r.memory_type, content: r.content.slice(0, 200), timestamp: r.timestamp })) }, null, 2));
|
|
1124
|
-
});
|
|
1125
|
-
|
|
1126
|
-
hooksCmd.command('pre-compact').description('Pre-compact hook').option('--auto', 'Auto mode').action(() => {
|
|
1127
|
-
const intel = new Intelligence();
|
|
1128
|
-
intel.save();
|
|
1129
|
-
console.log('๐๏ธ Pre-compact: State saved');
|
|
1130
|
-
});
|
|
1131
|
-
|
|
1132
|
-
hooksCmd.command('swarm-recommend').description('Recommend agent for task').argument('<task-type>', 'Task type').action((taskType) => {
|
|
1133
|
-
console.log(JSON.stringify({ task_type: taskType, recommended: 'coder', type: 'default', score: 0.8 }));
|
|
1134
|
-
});
|
|
1135
|
-
|
|
1136
|
-
hooksCmd.command('async-agent').description('Async agent hook').option('--action <action>', 'Action').option('--agent-id <id>', 'Agent ID').option('--task <task>', 'Task').action((opts) => {
|
|
1137
|
-
console.log(JSON.stringify({ action: opts.action, agent_id: opts.agentId, status: 'ok' }));
|
|
1138
|
-
});
|
|
1139
|
-
|
|
1140
|
-
hooksCmd.command('lsp-diagnostic').description('LSP diagnostic hook').option('--file <file>', 'File').option('--severity <sev>', 'Severity').option('--message <msg>', 'Message').action((opts) => {
|
|
1141
|
-
console.log(JSON.stringify({ file: opts.file, severity: opts.severity, action: 'logged' }));
|
|
1142
|
-
});
|
|
1143
|
-
|
|
1144
|
-
hooksCmd.command('track-notification').description('Track notification').action(() => { console.log(JSON.stringify({ tracked: true })); });
|
|
1145
|
-
|
|
1146
|
-
program.parse(process.argv);
|
|
1147
|
-
|
|
1148
|
-
if (!process.argv.slice(2).length) {
|
|
1149
|
-
program.outputHelp();
|
|
1150
|
-
}
|