ruvector 0.1.45 → 0.1.47

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 (2) hide show
  1. package/bin/cli.js +676 -24
  2. package/package.json +1 -1
package/bin/cli.js CHANGED
@@ -2267,6 +2267,40 @@ class Intelligence {
2267
2267
  // Hooks command group
2268
2268
  const hooksCmd = program.command('hooks').description('Self-learning intelligence hooks for Claude Code');
2269
2269
 
2270
+ // Helper: Detect project type
2271
+ function detectProjectType() {
2272
+ const cwd = process.cwd();
2273
+ const types = [];
2274
+ if (fs.existsSync(path.join(cwd, 'Cargo.toml'))) types.push('rust');
2275
+ if (fs.existsSync(path.join(cwd, 'package.json'))) types.push('node');
2276
+ if (fs.existsSync(path.join(cwd, 'requirements.txt')) || fs.existsSync(path.join(cwd, 'pyproject.toml'))) types.push('python');
2277
+ if (fs.existsSync(path.join(cwd, 'go.mod'))) types.push('go');
2278
+ if (fs.existsSync(path.join(cwd, 'Gemfile'))) types.push('ruby');
2279
+ if (fs.existsSync(path.join(cwd, 'pom.xml')) || fs.existsSync(path.join(cwd, 'build.gradle'))) types.push('java');
2280
+ return types.length > 0 ? types : ['generic'];
2281
+ }
2282
+
2283
+ // Helper: Get permissions for project type
2284
+ function getPermissionsForProjectType(types) {
2285
+ const basePermissions = [
2286
+ 'Bash(git status)', 'Bash(git diff:*)', 'Bash(git log:*)', 'Bash(git add:*)',
2287
+ 'Bash(git commit:*)', 'Bash(git push)', 'Bash(git branch:*)', 'Bash(git checkout:*)',
2288
+ 'Bash(ls:*)', 'Bash(pwd)', 'Bash(cat:*)', 'Bash(mkdir:*)', 'Bash(which:*)', 'Bash(ruvector:*)'
2289
+ ];
2290
+ const typePermissions = {
2291
+ rust: ['Bash(cargo:*)', 'Bash(rustc:*)', 'Bash(rustfmt:*)', 'Bash(clippy:*)', 'Bash(wasm-pack:*)'],
2292
+ node: ['Bash(npm:*)', 'Bash(npx:*)', 'Bash(node:*)', 'Bash(yarn:*)', 'Bash(pnpm:*)'],
2293
+ python: ['Bash(python:*)', 'Bash(pip:*)', 'Bash(pytest:*)', 'Bash(poetry:*)', 'Bash(uv:*)'],
2294
+ go: ['Bash(go:*)', 'Bash(gofmt:*)'],
2295
+ ruby: ['Bash(ruby:*)', 'Bash(gem:*)', 'Bash(bundle:*)', 'Bash(rails:*)'],
2296
+ java: ['Bash(mvn:*)', 'Bash(gradle:*)', 'Bash(java:*)', 'Bash(javac:*)'],
2297
+ generic: ['Bash(make:*)']
2298
+ };
2299
+ let perms = [...basePermissions];
2300
+ types.forEach(t => { if (typePermissions[t]) perms = perms.concat(typePermissions[t]); });
2301
+ return [...new Set(perms)];
2302
+ }
2303
+
2270
2304
  hooksCmd.command('init')
2271
2305
  .description('Initialize hooks in current project')
2272
2306
  .option('--force', 'Force overwrite existing settings')
@@ -2274,6 +2308,9 @@ hooksCmd.command('init')
2274
2308
  .option('--no-claude-md', 'Skip CLAUDE.md creation')
2275
2309
  .option('--no-permissions', 'Skip permissions configuration')
2276
2310
  .option('--no-env', 'Skip environment variables')
2311
+ .option('--no-gitignore', 'Skip .gitignore update')
2312
+ .option('--no-mcp', 'Skip MCP server configuration')
2313
+ .option('--no-statusline', 'Skip statusLine configuration')
2277
2314
  .action((opts) => {
2278
2315
  const settingsPath = path.join(process.cwd(), '.claude', 'settings.json');
2279
2316
  const settingsDir = path.dirname(settingsPath);
@@ -2294,6 +2331,10 @@ hooksCmd.command('init')
2294
2331
  if (settings.hooks.End) { delete settings.hooks.End; }
2295
2332
  }
2296
2333
 
2334
+ // Detect project type
2335
+ const projectTypes = detectProjectType();
2336
+ console.log(chalk.blue(` ✓ Detected project type(s): ${projectTypes.join(', ')}`));
2337
+
2297
2338
  // Environment variables for intelligence (unless --minimal or --no-env)
2298
2339
  if (!opts.minimal && opts.env !== false) {
2299
2340
  settings.env = settings.env || {};
@@ -2304,36 +2345,57 @@ hooksCmd.command('init')
2304
2345
  console.log(chalk.blue(' ✓ Environment variables configured'));
2305
2346
  }
2306
2347
 
2307
- // Permissions (unless --minimal or --no-permissions)
2348
+ // Permissions based on detected project type (unless --minimal or --no-permissions)
2308
2349
  if (!opts.minimal && opts.permissions !== false) {
2309
2350
  settings.permissions = settings.permissions || {};
2310
- settings.permissions.allow = settings.permissions.allow || [
2311
- 'Bash(npm run:*)',
2312
- 'Bash(npm test:*)',
2313
- 'Bash(npm install:*)',
2314
- 'Bash(npx:*)',
2315
- 'Bash(git status)',
2316
- 'Bash(git diff:*)',
2317
- 'Bash(git log:*)',
2318
- 'Bash(git add:*)',
2319
- 'Bash(git commit:*)',
2320
- 'Bash(git push)',
2321
- 'Bash(git branch:*)',
2322
- 'Bash(git checkout:*)',
2323
- 'Bash(ls:*)',
2324
- 'Bash(pwd)',
2325
- 'Bash(cat:*)',
2326
- 'Bash(mkdir:*)',
2327
- 'Bash(which:*)',
2328
- 'Bash(node:*)',
2329
- 'Bash(ruvector:*)'
2330
- ];
2351
+ settings.permissions.allow = settings.permissions.allow || getPermissionsForProjectType(projectTypes);
2331
2352
  settings.permissions.deny = settings.permissions.deny || [
2332
2353
  'Bash(rm -rf /)',
2333
2354
  'Bash(sudo rm:*)',
2334
- 'Bash(chmod 777:*)'
2355
+ 'Bash(chmod 777:*)',
2356
+ 'Bash(mkfs:*)',
2357
+ 'Bash(dd if=/dev/zero:*)'
2335
2358
  ];
2336
- console.log(chalk.blue(' ✓ Permissions configured'));
2359
+ console.log(chalk.blue(' ✓ Permissions configured (project-specific)'));
2360
+ }
2361
+
2362
+ // MCP server configuration (unless --minimal or --no-mcp)
2363
+ if (!opts.minimal && opts.mcp !== false) {
2364
+ settings.mcpServers = settings.mcpServers || {};
2365
+ // Only add if not already configured
2366
+ if (!settings.mcpServers['claude-flow'] && !settings.enabledMcpjsonServers?.includes('claude-flow')) {
2367
+ settings.enabledMcpjsonServers = settings.enabledMcpjsonServers || [];
2368
+ if (!settings.enabledMcpjsonServers.includes('claude-flow')) {
2369
+ settings.enabledMcpjsonServers.push('claude-flow');
2370
+ }
2371
+ }
2372
+ console.log(chalk.blue(' ✓ MCP servers configured'));
2373
+ }
2374
+
2375
+ // StatusLine configuration (unless --minimal or --no-statusline)
2376
+ if (!opts.minimal && opts.statusline !== false) {
2377
+ if (!settings.statusLine) {
2378
+ // Create a simple statusline script
2379
+ const statuslineScript = path.join(settingsDir, 'statusline.sh');
2380
+ const statuslineContent = `#!/bin/bash
2381
+ # RuVector Status Line - shows intelligence stats
2382
+ INTEL_FILE=".ruvector/intelligence.json"
2383
+ if [ -f "$INTEL_FILE" ]; then
2384
+ PATTERNS=$(jq -r '.patterns | length // 0' "$INTEL_FILE" 2>/dev/null || echo "0")
2385
+ MEMORIES=$(jq -r '.memories | length // 0' "$INTEL_FILE" 2>/dev/null || echo "0")
2386
+ echo "🧠 $PATTERNS patterns | 💾 $MEMORIES memories"
2387
+ else
2388
+ echo "🧠 RuVector"
2389
+ fi
2390
+ `;
2391
+ fs.writeFileSync(statuslineScript, statuslineContent);
2392
+ fs.chmodSync(statuslineScript, '755');
2393
+ settings.statusLine = {
2394
+ type: 'command',
2395
+ command: '.claude/statusline.sh'
2396
+ };
2397
+ console.log(chalk.blue(' ✓ StatusLine configured'));
2398
+ }
2337
2399
  }
2338
2400
 
2339
2401
  // Core hooks (always included)
@@ -2486,6 +2548,32 @@ npx ruvector hooks init --force # Overwrite existing
2486
2548
  } else if (fs.existsSync(claudeMdPath) && !opts.force) {
2487
2549
  console.log(chalk.yellow('ℹ️ CLAUDE.md already exists (use --force to overwrite)'));
2488
2550
  }
2551
+
2552
+ // Update .gitignore (unless --no-gitignore)
2553
+ if (opts.gitignore !== false) {
2554
+ const gitignorePath = path.join(process.cwd(), '.gitignore');
2555
+ const entriesToAdd = ['.ruvector/', '.claude/statusline.sh'];
2556
+ let gitignoreContent = '';
2557
+ if (fs.existsSync(gitignorePath)) {
2558
+ gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
2559
+ }
2560
+ const linesToAdd = entriesToAdd.filter(entry => !gitignoreContent.includes(entry));
2561
+ if (linesToAdd.length > 0) {
2562
+ const newContent = gitignoreContent.trim() + '\n\n# RuVector intelligence data\n' + linesToAdd.join('\n') + '\n';
2563
+ fs.writeFileSync(gitignorePath, newContent);
2564
+ console.log(chalk.blue(' ✓ .gitignore updated'));
2565
+ }
2566
+ }
2567
+
2568
+ // Create .ruvector directory for intelligence data
2569
+ const ruvectorDir = path.join(process.cwd(), '.ruvector');
2570
+ if (!fs.existsSync(ruvectorDir)) {
2571
+ fs.mkdirSync(ruvectorDir, { recursive: true });
2572
+ console.log(chalk.blue(' ✓ .ruvector/ directory created'));
2573
+ }
2574
+
2575
+ console.log(chalk.green('\n✅ RuVector hooks initialization complete!'));
2576
+ console.log(chalk.dim(' Run `npx ruvector hooks verify` to test the setup'));
2489
2577
  });
2490
2578
 
2491
2579
  hooksCmd.command('stats').description('Show intelligence statistics').action(() => {
@@ -2622,4 +2710,568 @@ hooksCmd.command('track-notification').description('Track notification').action(
2622
2710
  console.log(JSON.stringify({ tracked: true }));
2623
2711
  });
2624
2712
 
2713
+ // Verify hooks are working
2714
+ hooksCmd.command('verify')
2715
+ .description('Verify hooks are working correctly')
2716
+ .option('--verbose', 'Show detailed output')
2717
+ .action((opts) => {
2718
+ console.log(chalk.bold.cyan('\n🔍 RuVector Hooks Verification\n'));
2719
+ const checks = [];
2720
+
2721
+ // Check 1: Settings file exists
2722
+ const settingsPath = path.join(process.cwd(), '.claude', 'settings.json');
2723
+ if (fs.existsSync(settingsPath)) {
2724
+ checks.push({ name: 'Settings file', status: 'pass', detail: '.claude/settings.json exists' });
2725
+ try {
2726
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
2727
+ // Check hooks
2728
+ const requiredHooks = ['PreToolUse', 'PostToolUse', 'SessionStart', 'Stop'];
2729
+ const missingHooks = requiredHooks.filter(h => !settings.hooks?.[h]);
2730
+ if (missingHooks.length === 0) {
2731
+ checks.push({ name: 'Required hooks', status: 'pass', detail: 'All core hooks configured' });
2732
+ } else {
2733
+ checks.push({ name: 'Required hooks', status: 'fail', detail: `Missing: ${missingHooks.join(', ')}` });
2734
+ }
2735
+ // Check advanced hooks
2736
+ const advancedHooks = ['UserPromptSubmit', 'PreCompact', 'Notification'];
2737
+ const hasAdvanced = advancedHooks.filter(h => settings.hooks?.[h]);
2738
+ if (hasAdvanced.length > 0) {
2739
+ checks.push({ name: 'Advanced hooks', status: 'pass', detail: `${hasAdvanced.length}/3 configured` });
2740
+ } else {
2741
+ checks.push({ name: 'Advanced hooks', status: 'warn', detail: 'None configured (optional)' });
2742
+ }
2743
+ // Check env
2744
+ if (settings.env?.RUVECTOR_INTELLIGENCE_ENABLED) {
2745
+ checks.push({ name: 'Environment vars', status: 'pass', detail: 'Intelligence enabled' });
2746
+ } else {
2747
+ checks.push({ name: 'Environment vars', status: 'warn', detail: 'Not configured' });
2748
+ }
2749
+ // Check permissions
2750
+ if (settings.permissions?.allow?.length > 0) {
2751
+ checks.push({ name: 'Permissions', status: 'pass', detail: `${settings.permissions.allow.length} allowed patterns` });
2752
+ } else {
2753
+ checks.push({ name: 'Permissions', status: 'warn', detail: 'Not configured' });
2754
+ }
2755
+ } catch (e) {
2756
+ checks.push({ name: 'Settings parse', status: 'fail', detail: 'Invalid JSON' });
2757
+ }
2758
+ } else {
2759
+ checks.push({ name: 'Settings file', status: 'fail', detail: 'Run `npx ruvector hooks init` first' });
2760
+ }
2761
+
2762
+ // Check 2: .ruvector directory
2763
+ const ruvectorDir = path.join(process.cwd(), '.ruvector');
2764
+ if (fs.existsSync(ruvectorDir)) {
2765
+ checks.push({ name: 'Data directory', status: 'pass', detail: '.ruvector/ exists' });
2766
+ const intelFile = path.join(ruvectorDir, 'intelligence.json');
2767
+ if (fs.existsSync(intelFile)) {
2768
+ const stats = fs.statSync(intelFile);
2769
+ checks.push({ name: 'Intelligence file', status: 'pass', detail: `${(stats.size / 1024).toFixed(1)}KB` });
2770
+ } else {
2771
+ checks.push({ name: 'Intelligence file', status: 'warn', detail: 'Will be created on first use' });
2772
+ }
2773
+ } else {
2774
+ checks.push({ name: 'Data directory', status: 'warn', detail: 'Will be created on first use' });
2775
+ }
2776
+
2777
+ // Check 3: Hook command execution
2778
+ try {
2779
+ const { execSync } = require('child_process');
2780
+ execSync('npx ruvector hooks stats', { stdio: 'pipe', timeout: 5000 });
2781
+ checks.push({ name: 'Command execution', status: 'pass', detail: 'Hooks commands work' });
2782
+ } catch (e) {
2783
+ checks.push({ name: 'Command execution', status: 'fail', detail: 'Commands failed to execute' });
2784
+ }
2785
+
2786
+ // Display results
2787
+ let passCount = 0, warnCount = 0, failCount = 0;
2788
+ checks.forEach(c => {
2789
+ const icon = c.status === 'pass' ? chalk.green('✓') : c.status === 'warn' ? chalk.yellow('⚠') : chalk.red('✗');
2790
+ const statusColor = c.status === 'pass' ? chalk.green : c.status === 'warn' ? chalk.yellow : chalk.red;
2791
+ console.log(` ${icon} ${c.name}: ${statusColor(c.detail)}`);
2792
+ if (c.status === 'pass') passCount++;
2793
+ else if (c.status === 'warn') warnCount++;
2794
+ else failCount++;
2795
+ });
2796
+
2797
+ console.log('');
2798
+ if (failCount === 0) {
2799
+ console.log(chalk.green(`✅ Verification passed! ${passCount} checks passed, ${warnCount} warnings`));
2800
+ } else {
2801
+ console.log(chalk.red(`❌ Verification failed: ${failCount} issues found`));
2802
+ console.log(chalk.dim(' Run `npx ruvector hooks doctor` for detailed diagnostics'));
2803
+ }
2804
+ });
2805
+
2806
+ // Doctor - diagnose setup issues
2807
+ hooksCmd.command('doctor')
2808
+ .description('Diagnose and fix setup issues')
2809
+ .option('--fix', 'Automatically fix issues')
2810
+ .action((opts) => {
2811
+ console.log(chalk.bold.cyan('\n🩺 RuVector Hooks Doctor\n'));
2812
+ const issues = [];
2813
+ const fixes = [];
2814
+
2815
+ // Check settings file
2816
+ const settingsPath = path.join(process.cwd(), '.claude', 'settings.json');
2817
+ if (!fs.existsSync(settingsPath)) {
2818
+ issues.push({ severity: 'error', message: 'No .claude/settings.json found', fix: 'Run `npx ruvector hooks init`' });
2819
+ } else {
2820
+ try {
2821
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
2822
+
2823
+ // Check for invalid schema
2824
+ if (settings.$schema && !settings.$schema.includes('schemastore.org')) {
2825
+ issues.push({ severity: 'warning', message: 'Invalid schema URL', fix: 'Will be corrected' });
2826
+ if (opts.fix) {
2827
+ settings.$schema = 'https://json.schemastore.org/claude-code-settings.json';
2828
+ fixes.push('Fixed schema URL');
2829
+ }
2830
+ }
2831
+
2832
+ // Check for old hook names
2833
+ if (settings.hooks?.Start || settings.hooks?.End) {
2834
+ issues.push({ severity: 'error', message: 'Invalid hook names (Start/End)', fix: 'Should be SessionStart/Stop' });
2835
+ if (opts.fix) {
2836
+ delete settings.hooks.Start;
2837
+ delete settings.hooks.End;
2838
+ fixes.push('Removed invalid hook names');
2839
+ }
2840
+ }
2841
+
2842
+ // Check hook format
2843
+ const hookNames = ['PreToolUse', 'PostToolUse'];
2844
+ hookNames.forEach(name => {
2845
+ if (settings.hooks?.[name]) {
2846
+ settings.hooks[name].forEach((hook, i) => {
2847
+ if (typeof hook.matcher === 'object') {
2848
+ issues.push({ severity: 'error', message: `${name}[${i}].matcher should be string, not object`, fix: 'Will be corrected' });
2849
+ }
2850
+ });
2851
+ }
2852
+ });
2853
+
2854
+ // Check for npx vs direct command
2855
+ const checkCommands = (hooks) => {
2856
+ if (!hooks) return;
2857
+ hooks.forEach(h => {
2858
+ h.hooks?.forEach(hh => {
2859
+ if (hh.command && hh.command.includes('ruvector') && !hh.command.startsWith('npx ') && !hh.command.includes('/bin/')) {
2860
+ issues.push({ severity: 'warning', message: `Command should use 'npx ruvector' for portability`, fix: 'Update to use npx' });
2861
+ }
2862
+ });
2863
+ });
2864
+ };
2865
+ Object.values(settings.hooks || {}).forEach(checkCommands);
2866
+
2867
+ // Save fixes
2868
+ if (opts.fix && fixes.length > 0) {
2869
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
2870
+ }
2871
+ } catch (e) {
2872
+ issues.push({ severity: 'error', message: 'Invalid JSON in settings file', fix: 'Re-run `npx ruvector hooks init --force`' });
2873
+ }
2874
+ }
2875
+
2876
+ // Check .gitignore
2877
+ const gitignorePath = path.join(process.cwd(), '.gitignore');
2878
+ if (fs.existsSync(gitignorePath)) {
2879
+ const content = fs.readFileSync(gitignorePath, 'utf-8');
2880
+ if (!content.includes('.ruvector/')) {
2881
+ issues.push({ severity: 'warning', message: '.ruvector/ not in .gitignore', fix: 'Add to prevent committing learning data' });
2882
+ if (opts.fix) {
2883
+ fs.appendFileSync(gitignorePath, '\n# RuVector intelligence data\n.ruvector/\n');
2884
+ fixes.push('Added .ruvector/ to .gitignore');
2885
+ }
2886
+ }
2887
+ }
2888
+
2889
+ // Display results
2890
+ if (issues.length === 0) {
2891
+ console.log(chalk.green(' ✓ No issues found! Your setup looks healthy.'));
2892
+ } else {
2893
+ issues.forEach(i => {
2894
+ const icon = i.severity === 'error' ? chalk.red('✗') : chalk.yellow('⚠');
2895
+ console.log(` ${icon} ${i.message}`);
2896
+ console.log(chalk.dim(` Fix: ${i.fix}`));
2897
+ });
2898
+
2899
+ if (opts.fix && fixes.length > 0) {
2900
+ console.log(chalk.green(`\n✅ Applied ${fixes.length} fix(es):`));
2901
+ fixes.forEach(f => console.log(chalk.green(` • ${f}`)));
2902
+ } else if (issues.some(i => i.severity === 'error')) {
2903
+ console.log(chalk.yellow('\n💡 Run with --fix to automatically fix issues'));
2904
+ }
2905
+ }
2906
+ });
2907
+
2908
+ // Export intelligence data
2909
+ hooksCmd.command('export')
2910
+ .description('Export intelligence data for backup')
2911
+ .option('-o, --output <file>', 'Output file path', 'ruvector-export.json')
2912
+ .option('--include-all', 'Include all data (patterns, memories, trajectories)')
2913
+ .action((opts) => {
2914
+ const intel = new Intelligence();
2915
+ const exportData = {
2916
+ version: '1.0',
2917
+ exported_at: new Date().toISOString(),
2918
+ patterns: intel.data?.patterns || {},
2919
+ memories: opts.includeAll ? (intel.data?.memories || []) : [],
2920
+ trajectories: opts.includeAll ? (intel.data?.trajectories || []) : [],
2921
+ errors: intel.data?.errors || {},
2922
+ stats: intel.stats()
2923
+ };
2924
+
2925
+ const outputPath = path.resolve(opts.output);
2926
+ fs.writeFileSync(outputPath, JSON.stringify(exportData, null, 2));
2927
+
2928
+ console.log(chalk.green(`✅ Exported intelligence data to ${outputPath}`));
2929
+ console.log(chalk.dim(` ${Object.keys(exportData.patterns).length} patterns`));
2930
+ console.log(chalk.dim(` ${exportData.memories.length} memories`));
2931
+ console.log(chalk.dim(` ${exportData.trajectories.length} trajectories`));
2932
+ });
2933
+
2934
+ // Import intelligence data
2935
+ hooksCmd.command('import')
2936
+ .description('Import intelligence data from backup')
2937
+ .argument('<file>', 'Import file path')
2938
+ .option('--merge', 'Merge with existing data (default: replace)')
2939
+ .option('--dry-run', 'Show what would be imported without making changes')
2940
+ .action((file, opts) => {
2941
+ const importPath = path.resolve(file);
2942
+ if (!fs.existsSync(importPath)) {
2943
+ console.error(chalk.red(`❌ File not found: ${importPath}`));
2944
+ process.exit(1);
2945
+ }
2946
+
2947
+ try {
2948
+ const importData = JSON.parse(fs.readFileSync(importPath, 'utf-8'));
2949
+
2950
+ if (!importData.version) {
2951
+ console.error(chalk.red('❌ Invalid export file (missing version)'));
2952
+ process.exit(1);
2953
+ }
2954
+
2955
+ console.log(chalk.cyan(`📦 Import file: ${file}`));
2956
+ console.log(chalk.dim(` Version: ${importData.version}`));
2957
+ console.log(chalk.dim(` Exported: ${importData.exported_at}`));
2958
+ console.log(chalk.dim(` Patterns: ${Object.keys(importData.patterns || {}).length}`));
2959
+ console.log(chalk.dim(` Memories: ${(importData.memories || []).length}`));
2960
+ console.log(chalk.dim(` Trajectories: ${(importData.trajectories || []).length}`));
2961
+
2962
+ if (opts.dryRun) {
2963
+ console.log(chalk.yellow('\n⚠️ Dry run - no changes made'));
2964
+ return;
2965
+ }
2966
+
2967
+ const intel = new Intelligence();
2968
+
2969
+ if (opts.merge) {
2970
+ // Merge patterns
2971
+ Object.assign(intel.data.patterns, importData.patterns || {});
2972
+ // Merge memories (deduplicate by content)
2973
+ const existingContent = new Set((intel.data.memories || []).map(m => m.content));
2974
+ (importData.memories || []).forEach(m => {
2975
+ if (!existingContent.has(m.content)) {
2976
+ intel.data.memories.push(m);
2977
+ }
2978
+ });
2979
+ // Merge trajectories
2980
+ intel.data.trajectories = (intel.data.trajectories || []).concat(importData.trajectories || []);
2981
+ // Merge errors
2982
+ Object.assign(intel.data.errors, importData.errors || {});
2983
+ console.log(chalk.green('✅ Merged intelligence data'));
2984
+ } else {
2985
+ intel.data.patterns = importData.patterns || {};
2986
+ intel.data.memories = importData.memories || [];
2987
+ intel.data.trajectories = importData.trajectories || [];
2988
+ intel.data.errors = importData.errors || {};
2989
+ console.log(chalk.green('✅ Replaced intelligence data'));
2990
+ }
2991
+
2992
+ intel.save();
2993
+ console.log(chalk.dim(' Data saved to .ruvector/intelligence.json'));
2994
+ } catch (e) {
2995
+ console.error(chalk.red(`❌ Failed to import: ${e.message}`));
2996
+ process.exit(1);
2997
+ }
2998
+ });
2999
+
3000
+ // Pretrain - analyze repo and bootstrap learning with agent swarm
3001
+ hooksCmd.command('pretrain')
3002
+ .description('Pretrain intelligence by analyzing the repository with agent swarm')
3003
+ .option('--depth <n>', 'Git history depth to analyze', '100')
3004
+ .option('--workers <n>', 'Number of parallel analysis workers', '4')
3005
+ .option('--skip-git', 'Skip git history analysis')
3006
+ .option('--skip-files', 'Skip file structure analysis')
3007
+ .option('--verbose', 'Show detailed progress')
3008
+ .action(async (opts) => {
3009
+ const { execSync, spawn } = require('child_process');
3010
+ console.log(chalk.bold.cyan('\n🧠 RuVector Pretrain - Repository Intelligence Bootstrap\n'));
3011
+
3012
+ const intel = new Intelligence();
3013
+ const startTime = Date.now();
3014
+ const stats = { files: 0, patterns: 0, memories: 0, coedits: 0 };
3015
+
3016
+ // Agent types for different file patterns
3017
+ const agentMapping = {
3018
+ // Rust
3019
+ '.rs': 'rust-developer',
3020
+ 'Cargo.toml': 'rust-developer',
3021
+ 'Cargo.lock': 'rust-developer',
3022
+ // JavaScript/TypeScript
3023
+ '.js': 'javascript-developer',
3024
+ '.jsx': 'react-developer',
3025
+ '.ts': 'typescript-developer',
3026
+ '.tsx': 'react-developer',
3027
+ '.mjs': 'javascript-developer',
3028
+ '.cjs': 'javascript-developer',
3029
+ 'package.json': 'node-developer',
3030
+ // Python
3031
+ '.py': 'python-developer',
3032
+ 'requirements.txt': 'python-developer',
3033
+ 'pyproject.toml': 'python-developer',
3034
+ 'setup.py': 'python-developer',
3035
+ // Go
3036
+ '.go': 'go-developer',
3037
+ 'go.mod': 'go-developer',
3038
+ // Web
3039
+ '.html': 'frontend-developer',
3040
+ '.css': 'frontend-developer',
3041
+ '.scss': 'frontend-developer',
3042
+ '.vue': 'vue-developer',
3043
+ '.svelte': 'svelte-developer',
3044
+ // Config
3045
+ '.json': 'config-specialist',
3046
+ '.yaml': 'config-specialist',
3047
+ '.yml': 'config-specialist',
3048
+ '.toml': 'config-specialist',
3049
+ // Docs
3050
+ '.md': 'documentation-specialist',
3051
+ '.mdx': 'documentation-specialist',
3052
+ // Tests
3053
+ '.test.js': 'test-engineer',
3054
+ '.test.ts': 'test-engineer',
3055
+ '.spec.js': 'test-engineer',
3056
+ '.spec.ts': 'test-engineer',
3057
+ '_test.go': 'test-engineer',
3058
+ '_test.rs': 'test-engineer',
3059
+ // DevOps
3060
+ 'Dockerfile': 'devops-engineer',
3061
+ 'docker-compose.yml': 'devops-engineer',
3062
+ '.github/workflows': 'cicd-engineer',
3063
+ 'Makefile': 'devops-engineer',
3064
+ // SQL
3065
+ '.sql': 'database-specialist',
3066
+ };
3067
+
3068
+ // Phase 1: Analyze file structure
3069
+ if (!opts.skipFiles) {
3070
+ console.log(chalk.yellow('📁 Phase 1: Analyzing file structure...\n'));
3071
+
3072
+ try {
3073
+ // Get all files in repo
3074
+ const files = execSync('git ls-files 2>/dev/null || find . -type f -not -path "./.git/*" -not -path "./node_modules/*" -not -path "./target/*"',
3075
+ { encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 }).trim().split('\n').filter(f => f);
3076
+
3077
+ const filesByType = {};
3078
+ const dirPatterns = {};
3079
+
3080
+ files.forEach(file => {
3081
+ stats.files++;
3082
+ const ext = path.extname(file);
3083
+ const basename = path.basename(file);
3084
+ const dir = path.dirname(file);
3085
+
3086
+ // Determine agent for this file
3087
+ let agent = 'coder'; // default
3088
+ if (agentMapping[basename]) {
3089
+ agent = agentMapping[basename];
3090
+ } else if (agentMapping[ext]) {
3091
+ agent = agentMapping[ext];
3092
+ } else if (file.includes('.test.') || file.includes('.spec.') || file.includes('_test.')) {
3093
+ agent = 'test-engineer';
3094
+ } else if (file.includes('.github/workflows')) {
3095
+ agent = 'cicd-engineer';
3096
+ }
3097
+
3098
+ // Track file types
3099
+ filesByType[ext] = (filesByType[ext] || 0) + 1;
3100
+
3101
+ // Track directory patterns
3102
+ const parts = dir.split('/');
3103
+ if (parts[0]) {
3104
+ dirPatterns[parts[0]] = dirPatterns[parts[0]] || { count: 0, agents: {} };
3105
+ dirPatterns[parts[0]].count++;
3106
+ dirPatterns[parts[0]].agents[agent] = (dirPatterns[parts[0]].agents[agent] || 0) + 1;
3107
+ }
3108
+
3109
+ // Create Q-learning pattern for this file type
3110
+ const state = `edit:${ext || 'unknown'}`;
3111
+ if (!intel.data.patterns[state]) {
3112
+ intel.data.patterns[state] = {};
3113
+ }
3114
+ intel.data.patterns[state][agent] = (intel.data.patterns[state][agent] || 0) + 0.5;
3115
+ stats.patterns++;
3116
+ });
3117
+
3118
+ // Log summary
3119
+ if (opts.verbose) {
3120
+ console.log(chalk.dim(' File types found:'));
3121
+ Object.entries(filesByType).sort((a, b) => b[1] - a[1]).slice(0, 10).forEach(([ext, count]) => {
3122
+ console.log(chalk.dim(` ${ext || '(no ext)'}: ${count} files`));
3123
+ });
3124
+ }
3125
+ console.log(chalk.green(` ✓ Analyzed ${stats.files} files`));
3126
+ console.log(chalk.green(` ✓ Created ${Object.keys(intel.data.patterns).length} routing patterns`));
3127
+
3128
+ } catch (e) {
3129
+ console.log(chalk.yellow(` ⚠ File analysis skipped: ${e.message}`));
3130
+ }
3131
+ }
3132
+
3133
+ // Phase 2: Analyze git history for co-edit patterns
3134
+ if (!opts.skipGit) {
3135
+ console.log(chalk.yellow('\n📜 Phase 2: Analyzing git history for co-edit patterns...\n'));
3136
+
3137
+ try {
3138
+ // Get commits with files changed
3139
+ const gitLog = execSync(
3140
+ `git log --name-only --pretty=format:"COMMIT:%H" -n ${opts.depth} 2>/dev/null`,
3141
+ { encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 }
3142
+ );
3143
+
3144
+ const commits = gitLog.split('COMMIT:').filter(c => c.trim());
3145
+ const coEditMap = {};
3146
+
3147
+ commits.forEach(commit => {
3148
+ const lines = commit.trim().split('\n').filter(l => l && !l.startsWith('COMMIT:'));
3149
+ const files = lines.slice(1).filter(f => f.trim()); // Skip the hash
3150
+
3151
+ // Track which files are edited together
3152
+ files.forEach(file1 => {
3153
+ files.forEach(file2 => {
3154
+ if (file1 !== file2) {
3155
+ const key = [file1, file2].sort().join('|');
3156
+ coEditMap[key] = (coEditMap[key] || 0) + 1;
3157
+ }
3158
+ });
3159
+ });
3160
+ });
3161
+
3162
+ // Find strong co-edit patterns (files edited together 3+ times)
3163
+ const strongPatterns = Object.entries(coEditMap)
3164
+ .filter(([, count]) => count >= 3)
3165
+ .sort((a, b) => b[1] - a[1]);
3166
+
3167
+ // Store as sequence patterns
3168
+ strongPatterns.slice(0, 100).forEach(([key, count]) => {
3169
+ const [file1, file2] = key.split('|');
3170
+ if (!intel.data.sequences) intel.data.sequences = {};
3171
+ if (!intel.data.sequences[file1]) intel.data.sequences[file1] = [];
3172
+
3173
+ const existing = intel.data.sequences[file1].find(s => s.file === file2);
3174
+ if (existing) {
3175
+ existing.score += count;
3176
+ } else {
3177
+ intel.data.sequences[file1].push({ file: file2, score: count });
3178
+ }
3179
+ stats.coedits++;
3180
+ });
3181
+
3182
+ console.log(chalk.green(` ✓ Analyzed ${commits.length} commits`));
3183
+ console.log(chalk.green(` ✓ Found ${strongPatterns.length} co-edit patterns`));
3184
+
3185
+ if (opts.verbose && strongPatterns.length > 0) {
3186
+ console.log(chalk.dim(' Top co-edit patterns:'));
3187
+ strongPatterns.slice(0, 5).forEach(([key, count]) => {
3188
+ const [f1, f2] = key.split('|');
3189
+ console.log(chalk.dim(` ${path.basename(f1)} ↔ ${path.basename(f2)}: ${count} times`));
3190
+ });
3191
+ }
3192
+
3193
+ } catch (e) {
3194
+ console.log(chalk.yellow(` ⚠ Git analysis skipped: ${e.message}`));
3195
+ }
3196
+ }
3197
+
3198
+ // Phase 3: Create vector memories from important files
3199
+ console.log(chalk.yellow('\n💾 Phase 3: Creating vector memories from key files...\n'));
3200
+
3201
+ try {
3202
+ const importantFiles = [
3203
+ 'README.md', 'CLAUDE.md', 'package.json', 'Cargo.toml',
3204
+ 'pyproject.toml', 'go.mod', '.claude/settings.json'
3205
+ ];
3206
+
3207
+ for (const filename of importantFiles) {
3208
+ const filePath = path.join(process.cwd(), filename);
3209
+ if (fs.existsSync(filePath)) {
3210
+ try {
3211
+ const content = fs.readFileSync(filePath, 'utf-8').slice(0, 2000); // First 2KB
3212
+ intel.data.memories = intel.data.memories || [];
3213
+ intel.data.memories.push({
3214
+ content: `[${filename}] ${content.replace(/\n/g, ' ').slice(0, 500)}`,
3215
+ type: 'project',
3216
+ created: new Date().toISOString(),
3217
+ embedding: intel.simpleEmbed ? intel.simpleEmbed(content) : null
3218
+ });
3219
+ stats.memories++;
3220
+ if (opts.verbose) console.log(chalk.dim(` ✓ ${filename}`));
3221
+ } catch (e) { /* skip unreadable files */ }
3222
+ }
3223
+ }
3224
+
3225
+ console.log(chalk.green(` ✓ Created ${stats.memories} memory entries`));
3226
+
3227
+ } catch (e) {
3228
+ console.log(chalk.yellow(` ⚠ Memory creation skipped: ${e.message}`));
3229
+ }
3230
+
3231
+ // Phase 4: Analyze directory structure for agent recommendations
3232
+ console.log(chalk.yellow('\n🗂️ Phase 4: Building directory-agent mappings...\n'));
3233
+
3234
+ try {
3235
+ const dirs = execSync('find . -type d -maxdepth 2 -not -path "./.git*" -not -path "./node_modules*" -not -path "./target*" 2>/dev/null || echo "."',
3236
+ { encoding: 'utf-8' }).trim().split('\n');
3237
+
3238
+ const dirAgentMap = {};
3239
+ dirs.forEach(dir => {
3240
+ const name = path.basename(dir);
3241
+ // Infer agent from directory name
3242
+ if (['src', 'lib', 'core'].includes(name)) dirAgentMap[dir] = 'coder';
3243
+ else if (['test', 'tests', '__tests__', 'spec'].includes(name)) dirAgentMap[dir] = 'test-engineer';
3244
+ else if (['docs', 'documentation'].includes(name)) dirAgentMap[dir] = 'documentation-specialist';
3245
+ else if (['scripts', 'bin'].includes(name)) dirAgentMap[dir] = 'devops-engineer';
3246
+ else if (['components', 'views', 'pages'].includes(name)) dirAgentMap[dir] = 'frontend-developer';
3247
+ else if (['api', 'routes', 'handlers'].includes(name)) dirAgentMap[dir] = 'backend-developer';
3248
+ else if (['models', 'entities', 'schemas'].includes(name)) dirAgentMap[dir] = 'database-specialist';
3249
+ else if (['.github', '.gitlab', 'ci'].includes(name)) dirAgentMap[dir] = 'cicd-engineer';
3250
+ });
3251
+
3252
+ // Store directory patterns
3253
+ intel.data.dirPatterns = dirAgentMap;
3254
+ console.log(chalk.green(` ✓ Mapped ${Object.keys(dirAgentMap).length} directories to agents`));
3255
+
3256
+ } catch (e) {
3257
+ console.log(chalk.yellow(` ⚠ Directory analysis skipped: ${e.message}`));
3258
+ }
3259
+
3260
+ // Save all learning data
3261
+ intel.data.pretrained = {
3262
+ date: new Date().toISOString(),
3263
+ stats: stats
3264
+ };
3265
+ intel.save();
3266
+
3267
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
3268
+ console.log(chalk.bold.green(`\n✅ Pretrain complete in ${elapsed}s!\n`));
3269
+ console.log(chalk.cyan('Summary:'));
3270
+ console.log(` 📁 ${stats.files} files analyzed`);
3271
+ console.log(` 🧠 ${stats.patterns} agent routing patterns`);
3272
+ console.log(` 🔗 ${stats.coedits} co-edit patterns`);
3273
+ console.log(` 💾 ${stats.memories} memory entries`);
3274
+ console.log(chalk.dim('\nThe intelligence layer will now provide better recommendations.'));
3275
+ });
3276
+
2625
3277
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvector",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
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",