ruvector 0.1.70 → 0.1.71

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/bin/cli.js CHANGED
@@ -4429,6 +4429,292 @@ hooksCmd.command('learn')
4429
4429
  console.log(JSON.stringify(result));
4430
4430
  });
4431
4431
 
4432
+ // Batch learn - process multiple experiences at once
4433
+ hooksCmd.command('batch-learn')
4434
+ .description('Record multiple learning experiences in batch for efficiency')
4435
+ .option('-f, --file <file>', 'JSON file with experiences array')
4436
+ .option('-d, --data <json>', 'Inline JSON array of experiences')
4437
+ .option('-t, --task <type>', 'Task type for all experiences', 'agent-routing')
4438
+ .action(async (opts) => {
4439
+ if (!loadLearningModules()) {
4440
+ console.log(JSON.stringify({ success: false, error: 'Learning modules not available' }));
4441
+ return;
4442
+ }
4443
+
4444
+ let experiences = [];
4445
+
4446
+ // Load from file or inline
4447
+ if (opts.file) {
4448
+ try {
4449
+ const content = fs.readFileSync(opts.file, 'utf-8');
4450
+ experiences = JSON.parse(content);
4451
+ } catch (e) {
4452
+ console.log(JSON.stringify({ success: false, error: `Failed to read file: ${e.message}` }));
4453
+ return;
4454
+ }
4455
+ } else if (opts.data) {
4456
+ try {
4457
+ experiences = JSON.parse(opts.data);
4458
+ } catch (e) {
4459
+ console.log(JSON.stringify({ success: false, error: `Invalid JSON: ${e.message}` }));
4460
+ return;
4461
+ }
4462
+ } else {
4463
+ console.log(JSON.stringify({ success: false, error: 'Provide --file or --data' }));
4464
+ return;
4465
+ }
4466
+
4467
+ if (!Array.isArray(experiences)) {
4468
+ experiences = [experiences];
4469
+ }
4470
+
4471
+ const dataPath = path.join(process.cwd(), '.ruvector', 'intelligence.json');
4472
+ let data = {};
4473
+ try {
4474
+ if (fs.existsSync(dataPath)) {
4475
+ data = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
4476
+ }
4477
+ } catch (e) {}
4478
+
4479
+ const engine = new LearningEngineClass();
4480
+ if (data.learning) {
4481
+ engine.import(data.learning);
4482
+ }
4483
+
4484
+ const results = [];
4485
+ let totalReward = 0;
4486
+
4487
+ for (const exp of experiences) {
4488
+ const experience = {
4489
+ state: exp.state,
4490
+ action: exp.action,
4491
+ reward: exp.reward ?? 0.5,
4492
+ nextState: exp.nextState ?? exp.state,
4493
+ done: exp.done ?? false,
4494
+ timestamp: exp.timestamp ?? Date.now()
4495
+ };
4496
+
4497
+ const delta = engine.update(opts.task, experience);
4498
+ totalReward += experience.reward;
4499
+ results.push({ state: exp.state, action: exp.action, delta });
4500
+ }
4501
+
4502
+ // Save
4503
+ data.learning = engine.export();
4504
+ fs.mkdirSync(path.dirname(dataPath), { recursive: true });
4505
+ fs.writeFileSync(dataPath, JSON.stringify(data, null, 2));
4506
+
4507
+ const stats = engine.getStatsSummary();
4508
+ console.log(JSON.stringify({
4509
+ success: true,
4510
+ processed: experiences.length,
4511
+ avgReward: totalReward / experiences.length,
4512
+ results,
4513
+ stats: {
4514
+ bestAlgorithm: stats.bestAlgorithm,
4515
+ totalUpdates: stats.totalUpdates,
4516
+ avgReward: stats.avgReward
4517
+ }
4518
+ }));
4519
+ });
4520
+
4521
+ // Subscribe to learning updates - stream real-time learning events
4522
+ hooksCmd.command('subscribe')
4523
+ .description('Subscribe to real-time learning updates (streaming)')
4524
+ .option('-e, --events <types>', 'Event types to subscribe to (learn,compress,route,memory)', 'learn,route')
4525
+ .option('-f, --format <fmt>', 'Output format (json, text)', 'json')
4526
+ .option('--poll <ms>', 'Poll interval in ms', parseInt, 1000)
4527
+ .action(async (opts) => {
4528
+ const events = opts.events.split(',').map(e => e.trim());
4529
+ const dataPath = path.join(process.cwd(), '.ruvector', 'intelligence.json');
4530
+
4531
+ let lastStats = { patterns: 0, memories: 0, trajectories: 0 };
4532
+ let lastLearning = { totalUpdates: 0 };
4533
+
4534
+ console.error(chalk.cyan('🔴 Subscribed to learning updates. Press Ctrl+C to stop.\n'));
4535
+ console.error(chalk.dim(` Events: ${events.join(', ')}`));
4536
+ console.error(chalk.dim(` Poll interval: ${opts.poll}ms\n`));
4537
+
4538
+ const emit = (type, data) => {
4539
+ const event = { type, timestamp: Date.now(), data };
4540
+ if (opts.format === 'json') {
4541
+ console.log(JSON.stringify(event));
4542
+ } else {
4543
+ const icon = { learn: '🧠', compress: '📦', route: '🎯', memory: '💾' }[type] || '📡';
4544
+ console.log(`${icon} [${type}] ${JSON.stringify(data)}`);
4545
+ }
4546
+ };
4547
+
4548
+ const check = () => {
4549
+ try {
4550
+ if (!fs.existsSync(dataPath)) return;
4551
+
4552
+ const data = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
4553
+ const stats = data.stats || {};
4554
+ const learning = data.learning?.stats || {};
4555
+
4556
+ // Check for new patterns (learn events)
4557
+ if (events.includes('learn')) {
4558
+ const currentPatterns = stats.total_patterns || 0;
4559
+ if (currentPatterns > lastStats.patterns) {
4560
+ emit('learn', {
4561
+ type: 'pattern',
4562
+ newPatterns: currentPatterns - lastStats.patterns,
4563
+ total: currentPatterns
4564
+ });
4565
+ lastStats.patterns = currentPatterns;
4566
+ }
4567
+
4568
+ // Check learning engine updates
4569
+ let totalUpdates = 0;
4570
+ Object.values(learning).forEach(algo => {
4571
+ if (algo.updates) totalUpdates += algo.updates;
4572
+ });
4573
+ if (totalUpdates > lastLearning.totalUpdates) {
4574
+ const bestAlgo = Object.entries(learning)
4575
+ .filter(([, v]) => v.updates > 0)
4576
+ .sort((a, b) => b[1].avgReward - a[1].avgReward)[0];
4577
+ emit('learn', {
4578
+ type: 'algorithm_update',
4579
+ newUpdates: totalUpdates - lastLearning.totalUpdates,
4580
+ totalUpdates,
4581
+ bestAlgorithm: bestAlgo?.[0] || 'none'
4582
+ });
4583
+ lastLearning.totalUpdates = totalUpdates;
4584
+ }
4585
+ }
4586
+
4587
+ // Check for new memories
4588
+ if (events.includes('memory')) {
4589
+ const currentMemories = stats.total_memories || 0;
4590
+ if (currentMemories > lastStats.memories) {
4591
+ emit('memory', {
4592
+ newMemories: currentMemories - lastStats.memories,
4593
+ total: currentMemories
4594
+ });
4595
+ lastStats.memories = currentMemories;
4596
+ }
4597
+ }
4598
+
4599
+ // Check for new trajectories (route events)
4600
+ if (events.includes('route')) {
4601
+ const currentTrajectories = stats.total_trajectories || 0;
4602
+ if (currentTrajectories > lastStats.trajectories) {
4603
+ emit('route', {
4604
+ newTrajectories: currentTrajectories - lastStats.trajectories,
4605
+ total: currentTrajectories
4606
+ });
4607
+ lastStats.trajectories = currentTrajectories;
4608
+ }
4609
+ }
4610
+
4611
+ } catch (e) {
4612
+ // Ignore read errors during updates
4613
+ }
4614
+ };
4615
+
4616
+ // Initial state
4617
+ check();
4618
+
4619
+ // Poll for updates
4620
+ const interval = setInterval(check, opts.poll);
4621
+
4622
+ // Handle graceful shutdown
4623
+ process.on('SIGINT', () => {
4624
+ clearInterval(interval);
4625
+ console.error(chalk.dim('\n\n👋 Subscription ended.'));
4626
+ process.exit(0);
4627
+ });
4628
+
4629
+ // Keep alive
4630
+ await new Promise(() => {});
4631
+ });
4632
+
4633
+ // Watch and learn - monitor file changes and auto-learn
4634
+ hooksCmd.command('watch')
4635
+ .description('Watch for changes and auto-learn patterns in real-time')
4636
+ .option('-p, --path <dir>', 'Directory to watch', '.')
4637
+ .option('-i, --ignore <patterns>', 'Patterns to ignore (comma-separated)', 'node_modules,dist,.git')
4638
+ .option('--dry-run', 'Show what would be learned without saving')
4639
+ .action(async (opts) => {
4640
+ const watchDir = path.resolve(opts.path);
4641
+ const ignorePatterns = opts.ignore.split(',').map(p => p.trim());
4642
+
4643
+ console.error(chalk.cyan(`👁️ Watching ${watchDir} for changes...\n`));
4644
+ console.error(chalk.dim(` Ignoring: ${ignorePatterns.join(', ')}`));
4645
+ console.error(chalk.dim(` Press Ctrl+C to stop.\n`));
4646
+
4647
+ const intel = new Intelligence({ skipEngine: true });
4648
+ let lastEdit = null;
4649
+ let editCount = 0;
4650
+
4651
+ const shouldIgnore = (filePath) => {
4652
+ return ignorePatterns.some(pattern => filePath.includes(pattern));
4653
+ };
4654
+
4655
+ const processChange = (eventType, filename) => {
4656
+ if (!filename || shouldIgnore(filename)) return;
4657
+
4658
+ const ext = path.extname(filename);
4659
+ const state = `edit:${ext || 'unknown'}`;
4660
+ const now = Date.now();
4661
+
4662
+ // Determine likely action based on file type
4663
+ const agentMapping = {
4664
+ '.ts': 'typescript-developer',
4665
+ '.js': 'coder',
4666
+ '.rs': 'rust-developer',
4667
+ '.py': 'python-developer',
4668
+ '.go': 'go-developer',
4669
+ '.md': 'documentation',
4670
+ '.json': 'config-manager',
4671
+ '.yaml': 'devops-engineer',
4672
+ '.yml': 'devops-engineer',
4673
+ };
4674
+ const agent = agentMapping[ext] || 'coder';
4675
+
4676
+ // Co-edit pattern detection
4677
+ if (lastEdit && lastEdit.file !== filename && (now - lastEdit.time) < 60000) {
4678
+ // Files edited within 1 minute are co-edits
4679
+ const coEditKey = [lastEdit.file, filename].sort().join('|');
4680
+ if (!opts.dryRun) {
4681
+ if (!intel.data.sequences) intel.data.sequences = {};
4682
+ if (!intel.data.sequences[lastEdit.file]) intel.data.sequences[lastEdit.file] = [];
4683
+ const existing = intel.data.sequences[lastEdit.file].find(s => s.file === filename);
4684
+ if (existing) {
4685
+ existing.score++;
4686
+ } else {
4687
+ intel.data.sequences[lastEdit.file].push({ file: filename, score: 1 });
4688
+ }
4689
+ }
4690
+ console.log(chalk.yellow(` 🔗 Co-edit: ${path.basename(lastEdit.file)} → ${path.basename(filename)}`));
4691
+ }
4692
+
4693
+ // Update Q-value for this file type
4694
+ if (!opts.dryRun) {
4695
+ intel.updateQ(state, agent, 0.5);
4696
+ intel.save();
4697
+ }
4698
+
4699
+ editCount++;
4700
+ console.log(chalk.green(` ✏️ [${editCount}] ${filename} → ${agent}`));
4701
+
4702
+ lastEdit = { file: filename, time: now };
4703
+ };
4704
+
4705
+ // Use fs.watch for real-time monitoring
4706
+ const watcher = fs.watch(watchDir, { recursive: true }, processChange);
4707
+
4708
+ process.on('SIGINT', () => {
4709
+ watcher.close();
4710
+ console.error(chalk.dim(`\n\n📊 Learned from ${editCount} file changes.`));
4711
+ process.exit(0);
4712
+ });
4713
+
4714
+ // Keep alive
4715
+ await new Promise(() => {});
4716
+ });
4717
+
4432
4718
  // ============================================
4433
4719
  // END NEW CAPABILITY COMMANDS
4434
4720
  // ============================================
package/bin/mcp-server.js CHANGED
@@ -855,6 +855,67 @@ const TOOLS = [
855
855
  },
856
856
  required: ['key']
857
857
  }
858
+ },
859
+ {
860
+ name: 'hooks_batch_learn',
861
+ description: 'Record multiple learning experiences in batch for efficiency. Processes an array of experiences at once.',
862
+ inputSchema: {
863
+ type: 'object',
864
+ properties: {
865
+ experiences: {
866
+ type: 'array',
867
+ description: 'Array of experiences to learn from',
868
+ items: {
869
+ type: 'object',
870
+ properties: {
871
+ state: { type: 'string', description: 'State identifier' },
872
+ action: { type: 'string', description: 'Action taken' },
873
+ reward: { type: 'number', description: 'Reward (-1 to 1)' },
874
+ nextState: { type: 'string', description: 'Next state (optional)' },
875
+ done: { type: 'boolean', description: 'Episode ended' }
876
+ },
877
+ required: ['state', 'action', 'reward']
878
+ }
879
+ },
880
+ task: { type: 'string', description: 'Task type for all experiences', default: 'agent-routing' }
881
+ },
882
+ required: ['experiences']
883
+ }
884
+ },
885
+ {
886
+ name: 'hooks_subscribe_snapshot',
887
+ description: 'Get current state snapshot for subscription-style updates. Returns counts and deltas since last call.',
888
+ inputSchema: {
889
+ type: 'object',
890
+ properties: {
891
+ events: {
892
+ type: 'array',
893
+ description: 'Event types to check',
894
+ items: { type: 'string', enum: ['learn', 'compress', 'route', 'memory'] },
895
+ default: ['learn', 'route']
896
+ },
897
+ lastState: {
898
+ type: 'object',
899
+ description: 'Previous state for delta calculation',
900
+ properties: {
901
+ patterns: { type: 'number' },
902
+ memories: { type: 'number' },
903
+ trajectories: { type: 'number' },
904
+ updates: { type: 'number' }
905
+ }
906
+ }
907
+ },
908
+ required: []
909
+ }
910
+ },
911
+ {
912
+ name: 'hooks_watch_status',
913
+ description: 'Get file watching status and recent changes detected',
914
+ inputSchema: {
915
+ type: 'object',
916
+ properties: {},
917
+ required: []
918
+ }
858
919
  }
859
920
  ];
860
921
 
@@ -1862,6 +1923,149 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1862
1923
  }, null, 2) }] };
1863
1924
  }
1864
1925
 
1926
+ case 'hooks_batch_learn': {
1927
+ let LearningEngine;
1928
+ try {
1929
+ LearningEngine = require('../dist/core/learning-engine').default;
1930
+ } catch (e) {
1931
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'LearningEngine not available' }) }] };
1932
+ }
1933
+
1934
+ const experiences = args.experiences || [];
1935
+ if (!Array.isArray(experiences) || experiences.length === 0) {
1936
+ return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'experiences must be a non-empty array' }) }] };
1937
+ }
1938
+
1939
+ const task = args.task || 'agent-routing';
1940
+ const engine = new LearningEngine();
1941
+
1942
+ // Import existing learning data
1943
+ if (intel.data.learning) {
1944
+ engine.import(intel.data.learning);
1945
+ }
1946
+
1947
+ const results = [];
1948
+ let totalReward = 0;
1949
+
1950
+ for (const exp of experiences) {
1951
+ const experience = {
1952
+ state: exp.state,
1953
+ action: exp.action,
1954
+ reward: exp.reward ?? 0.5,
1955
+ nextState: exp.nextState ?? exp.state,
1956
+ done: exp.done ?? false,
1957
+ timestamp: Date.now()
1958
+ };
1959
+
1960
+ const delta = engine.update(task, experience);
1961
+ totalReward += experience.reward;
1962
+ results.push({ state: exp.state, action: exp.action, reward: experience.reward, delta });
1963
+ }
1964
+
1965
+ // Save
1966
+ intel.data.learning = engine.export();
1967
+ intel.save();
1968
+
1969
+ const stats = engine.getStatsSummary();
1970
+ return { content: [{ type: 'text', text: JSON.stringify({
1971
+ success: true,
1972
+ processed: experiences.length,
1973
+ avgReward: totalReward / experiences.length,
1974
+ results,
1975
+ stats: {
1976
+ bestAlgorithm: stats.bestAlgorithm,
1977
+ totalUpdates: stats.totalUpdates,
1978
+ avgReward: stats.avgReward
1979
+ }
1980
+ }, null, 2) }] };
1981
+ }
1982
+
1983
+ case 'hooks_subscribe_snapshot': {
1984
+ const events = args.events || ['learn', 'route'];
1985
+ const lastState = args.lastState || { patterns: 0, memories: 0, trajectories: 0, updates: 0 };
1986
+
1987
+ const stats = intel.data.stats || {};
1988
+ const learning = intel.data.learning?.stats || {};
1989
+
1990
+ // Calculate current state
1991
+ let totalUpdates = 0;
1992
+ let bestAlgorithm = null;
1993
+ let bestAvgReward = -Infinity;
1994
+
1995
+ Object.entries(learning).forEach(([algo, data]) => {
1996
+ if (data.updates) {
1997
+ totalUpdates += data.updates;
1998
+ if (data.avgReward > bestAvgReward) {
1999
+ bestAvgReward = data.avgReward;
2000
+ bestAlgorithm = algo;
2001
+ }
2002
+ }
2003
+ });
2004
+
2005
+ const currentState = {
2006
+ patterns: stats.total_patterns || 0,
2007
+ memories: stats.total_memories || 0,
2008
+ trajectories: stats.total_trajectories || 0,
2009
+ updates: totalUpdates
2010
+ };
2011
+
2012
+ // Calculate deltas
2013
+ const deltas = {
2014
+ patterns: currentState.patterns - (lastState.patterns || 0),
2015
+ memories: currentState.memories - (lastState.memories || 0),
2016
+ trajectories: currentState.trajectories - (lastState.trajectories || 0),
2017
+ updates: currentState.updates - (lastState.updates || 0)
2018
+ };
2019
+
2020
+ const hasChanges = Object.values(deltas).some(d => d > 0);
2021
+
2022
+ // Build events array
2023
+ const eventsList = [];
2024
+ if (events.includes('learn') && deltas.patterns > 0) {
2025
+ eventsList.push({ type: 'learn', subtype: 'pattern', delta: deltas.patterns, total: currentState.patterns });
2026
+ }
2027
+ if (events.includes('learn') && deltas.updates > 0) {
2028
+ eventsList.push({ type: 'learn', subtype: 'algorithm', delta: deltas.updates, total: currentState.updates, bestAlgorithm });
2029
+ }
2030
+ if (events.includes('memory') && deltas.memories > 0) {
2031
+ eventsList.push({ type: 'memory', delta: deltas.memories, total: currentState.memories });
2032
+ }
2033
+ if (events.includes('route') && deltas.trajectories > 0) {
2034
+ eventsList.push({ type: 'route', delta: deltas.trajectories, total: currentState.trajectories });
2035
+ }
2036
+
2037
+ return { content: [{ type: 'text', text: JSON.stringify({
2038
+ success: true,
2039
+ hasChanges,
2040
+ currentState,
2041
+ deltas,
2042
+ events: eventsList,
2043
+ bestAlgorithm,
2044
+ timestamp: Date.now()
2045
+ }, null, 2) }] };
2046
+ }
2047
+
2048
+ case 'hooks_watch_status': {
2049
+ // Return current intelligence state as a "watch" status
2050
+ const stats = intel.data.stats || {};
2051
+ const patterns = Object.keys(intel.data.patterns || {});
2052
+ const recentPatterns = patterns.slice(-5);
2053
+
2054
+ return { content: [{ type: 'text', text: JSON.stringify({
2055
+ success: true,
2056
+ watching: true,
2057
+ stats: {
2058
+ totalPatterns: stats.total_patterns || 0,
2059
+ totalMemories: stats.total_memories || 0,
2060
+ totalTrajectories: stats.total_trajectories || 0,
2061
+ sessionCount: stats.session_count || 0
2062
+ },
2063
+ recentPatterns,
2064
+ lastUpdate: stats.last_session || Date.now(),
2065
+ tip: 'Use hooks_subscribe_snapshot with lastState for delta tracking'
2066
+ }, null, 2) }] };
2067
+ }
2068
+
1865
2069
  default:
1866
2070
  return {
1867
2071
  content: [{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvector",
3
- "version": "0.1.70",
3
+ "version": "0.1.71",
4
4
  "description": "High-performance vector database for Node.js with automatic native/WASM fallback",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/ruvector.db CHANGED
Binary file