ruvector 0.1.44 → 0.1.46

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 +523 -14
  2. package/package.json +1 -1
package/bin/cli.js CHANGED
@@ -2267,7 +2267,51 @@ 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
- hooksCmd.command('init').description('Initialize hooks in current project').option('--force', 'Force overwrite').option('--no-claude-md', 'Skip CLAUDE.md creation').action((opts) => {
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
+
2304
+ hooksCmd.command('init')
2305
+ .description('Initialize hooks in current project')
2306
+ .option('--force', 'Force overwrite existing settings')
2307
+ .option('--minimal', 'Only basic hooks (no env, permissions, or advanced hooks)')
2308
+ .option('--no-claude-md', 'Skip CLAUDE.md creation')
2309
+ .option('--no-permissions', 'Skip permissions configuration')
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')
2314
+ .action((opts) => {
2271
2315
  const settingsPath = path.join(process.cwd(), '.claude', 'settings.json');
2272
2316
  const settingsDir = path.dirname(settingsPath);
2273
2317
  if (!fs.existsSync(settingsDir)) fs.mkdirSync(settingsDir, { recursive: true });
@@ -2275,15 +2319,85 @@ hooksCmd.command('init').description('Initialize hooks in current project').opti
2275
2319
  if (fs.existsSync(settingsPath) && !opts.force) {
2276
2320
  try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); } catch {}
2277
2321
  }
2322
+
2278
2323
  // Fix schema if present
2279
2324
  if (settings.$schema) {
2280
2325
  settings.$schema = 'https://json.schemastore.org/claude-code-settings.json';
2281
2326
  }
2327
+
2282
2328
  // Clean up invalid hook names
2283
2329
  if (settings.hooks) {
2284
2330
  if (settings.hooks.Start) { delete settings.hooks.Start; }
2285
2331
  if (settings.hooks.End) { delete settings.hooks.End; }
2286
2332
  }
2333
+
2334
+ // Detect project type
2335
+ const projectTypes = detectProjectType();
2336
+ console.log(chalk.blue(` ✓ Detected project type(s): ${projectTypes.join(', ')}`));
2337
+
2338
+ // Environment variables for intelligence (unless --minimal or --no-env)
2339
+ if (!opts.minimal && opts.env !== false) {
2340
+ settings.env = settings.env || {};
2341
+ settings.env.RUVECTOR_INTELLIGENCE_ENABLED = settings.env.RUVECTOR_INTELLIGENCE_ENABLED || 'true';
2342
+ settings.env.RUVECTOR_LEARNING_RATE = settings.env.RUVECTOR_LEARNING_RATE || '0.1';
2343
+ settings.env.RUVECTOR_MEMORY_BACKEND = settings.env.RUVECTOR_MEMORY_BACKEND || 'rvlite';
2344
+ settings.env.INTELLIGENCE_MODE = settings.env.INTELLIGENCE_MODE || 'treatment';
2345
+ console.log(chalk.blue(' ✓ Environment variables configured'));
2346
+ }
2347
+
2348
+ // Permissions based on detected project type (unless --minimal or --no-permissions)
2349
+ if (!opts.minimal && opts.permissions !== false) {
2350
+ settings.permissions = settings.permissions || {};
2351
+ settings.permissions.allow = settings.permissions.allow || getPermissionsForProjectType(projectTypes);
2352
+ settings.permissions.deny = settings.permissions.deny || [
2353
+ 'Bash(rm -rf /)',
2354
+ 'Bash(sudo rm:*)',
2355
+ 'Bash(chmod 777:*)',
2356
+ 'Bash(:(){ :|:& };:)'
2357
+ ];
2358
+ console.log(chalk.blue(' ✓ Permissions configured (project-specific)'));
2359
+ }
2360
+
2361
+ // MCP server configuration (unless --minimal or --no-mcp)
2362
+ if (!opts.minimal && opts.mcp !== false) {
2363
+ settings.mcpServers = settings.mcpServers || {};
2364
+ // Only add if not already configured
2365
+ if (!settings.mcpServers['claude-flow'] && !settings.enabledMcpjsonServers?.includes('claude-flow')) {
2366
+ settings.enabledMcpjsonServers = settings.enabledMcpjsonServers || [];
2367
+ if (!settings.enabledMcpjsonServers.includes('claude-flow')) {
2368
+ settings.enabledMcpjsonServers.push('claude-flow');
2369
+ }
2370
+ }
2371
+ console.log(chalk.blue(' ✓ MCP servers configured'));
2372
+ }
2373
+
2374
+ // StatusLine configuration (unless --minimal or --no-statusline)
2375
+ if (!opts.minimal && opts.statusline !== false) {
2376
+ if (!settings.statusLine) {
2377
+ // Create a simple statusline script
2378
+ const statuslineScript = path.join(settingsDir, 'statusline.sh');
2379
+ const statuslineContent = `#!/bin/bash
2380
+ # RuVector Status Line - shows intelligence stats
2381
+ INTEL_FILE=".ruvector/intelligence.json"
2382
+ if [ -f "$INTEL_FILE" ]; then
2383
+ PATTERNS=$(jq -r '.patterns | length // 0' "$INTEL_FILE" 2>/dev/null || echo "0")
2384
+ MEMORIES=$(jq -r '.memories | length // 0' "$INTEL_FILE" 2>/dev/null || echo "0")
2385
+ echo "🧠 $PATTERNS patterns | 💾 $MEMORIES memories"
2386
+ else
2387
+ echo "🧠 RuVector"
2388
+ fi
2389
+ `;
2390
+ fs.writeFileSync(statuslineScript, statuslineContent);
2391
+ fs.chmodSync(statuslineScript, '755');
2392
+ settings.statusLine = {
2393
+ type: 'command',
2394
+ command: '.claude/statusline.sh'
2395
+ };
2396
+ console.log(chalk.blue(' ✓ StatusLine configured'));
2397
+ }
2398
+ }
2399
+
2400
+ // Core hooks (always included)
2287
2401
  settings.hooks = settings.hooks || {};
2288
2402
  settings.hooks.PreToolUse = [
2289
2403
  { matcher: 'Edit|Write|MultiEdit', hooks: [{ type: 'command', command: 'npx ruvector hooks pre-edit "$TOOL_INPUT_file_path"' }] },
@@ -2295,53 +2409,135 @@ hooksCmd.command('init').description('Initialize hooks in current project').opti
2295
2409
  ];
2296
2410
  settings.hooks.SessionStart = [{ hooks: [{ type: 'command', command: 'npx ruvector hooks session-start' }] }];
2297
2411
  settings.hooks.Stop = [{ hooks: [{ type: 'command', command: 'npx ruvector hooks session-end' }] }];
2412
+ console.log(chalk.blue(' ✓ Core hooks (PreToolUse, PostToolUse, SessionStart, Stop)'));
2413
+
2414
+ // Advanced hooks (unless --minimal)
2415
+ if (!opts.minimal) {
2416
+ // UserPromptSubmit - context suggestions on each prompt
2417
+ settings.hooks.UserPromptSubmit = [{
2418
+ hooks: [{
2419
+ type: 'command',
2420
+ timeout: 2000,
2421
+ command: 'npx ruvector hooks suggest-context'
2422
+ }]
2423
+ }];
2424
+
2425
+ // PreCompact - preserve important context before compaction
2426
+ settings.hooks.PreCompact = [
2427
+ {
2428
+ matcher: 'auto',
2429
+ hooks: [{
2430
+ type: 'command',
2431
+ timeout: 3000,
2432
+ command: 'npx ruvector hooks pre-compact --auto'
2433
+ }]
2434
+ },
2435
+ {
2436
+ matcher: 'manual',
2437
+ hooks: [{
2438
+ type: 'command',
2439
+ timeout: 3000,
2440
+ command: 'npx ruvector hooks pre-compact'
2441
+ }]
2442
+ }
2443
+ ];
2444
+
2445
+ // Notification - track all notifications for learning
2446
+ settings.hooks.Notification = [{
2447
+ matcher: '.*',
2448
+ hooks: [{
2449
+ type: 'command',
2450
+ timeout: 1000,
2451
+ command: 'npx ruvector hooks track-notification'
2452
+ }]
2453
+ }];
2454
+ console.log(chalk.blue(' ✓ Advanced hooks (UserPromptSubmit, PreCompact, Notification)'));
2455
+ }
2456
+
2298
2457
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
2299
- console.log(chalk.green('✅ Hooks initialized in .claude/settings.json'));
2458
+ console.log(chalk.green('\n✅ Hooks initialized in .claude/settings.json'));
2300
2459
 
2301
2460
  // Create CLAUDE.md if it doesn't exist (or force)
2302
2461
  const claudeMdPath = path.join(process.cwd(), 'CLAUDE.md');
2303
2462
  if (opts.claudeMd !== false && (!fs.existsSync(claudeMdPath) || opts.force)) {
2304
2463
  const claudeMdContent = `# Claude Code Project Configuration
2305
2464
 
2306
- ## RuVector Self-Learning Hooks
2465
+ ## RuVector Self-Learning Intelligence
2307
2466
 
2308
- This project uses RuVector's self-learning intelligence hooks for enhanced AI-assisted development.
2467
+ This project uses RuVector's self-learning intelligence hooks for enhanced AI-assisted development with Q-learning, vector memory, and automatic agent routing.
2309
2468
 
2310
2469
  ### Active Hooks
2311
2470
 
2312
2471
  | Hook | Trigger | Purpose |
2313
2472
  |------|---------|---------|
2314
- | PreToolUse | Before Edit/Write/Bash | Agent routing, command analysis |
2315
- | PostToolUse | After Edit/Write/Bash | Q-learning update, pattern recording |
2316
- | SessionStart | Conversation begins | Load intelligence, display stats |
2317
- | Stop | Conversation ends | Save learning data |
2473
+ | **PreToolUse** | Before Edit/Write/Bash | Agent routing, file analysis, command risk assessment |
2474
+ | **PostToolUse** | After Edit/Write/Bash | Q-learning update, pattern recording, outcome tracking |
2475
+ | **SessionStart** | Conversation begins | Load intelligence state, display learning stats |
2476
+ | **Stop** | Conversation ends | Save learning data, export metrics |
2477
+ | **UserPromptSubmit** | User sends message | Context suggestions, pattern recommendations |
2478
+ | **PreCompact** | Before context compaction | Preserve important context and memories |
2479
+ | **Notification** | Any notification | Track events for learning |
2480
+
2481
+ ### Environment Variables
2482
+
2483
+ | Variable | Default | Description |
2484
+ |----------|---------|-------------|
2485
+ | \`RUVECTOR_INTELLIGENCE_ENABLED\` | \`true\` | Enable/disable intelligence layer |
2486
+ | \`RUVECTOR_LEARNING_RATE\` | \`0.1\` | Q-learning rate (0.0-1.0) |
2487
+ | \`RUVECTOR_MEMORY_BACKEND\` | \`rvlite\` | Memory storage backend |
2488
+ | \`INTELLIGENCE_MODE\` | \`treatment\` | A/B testing mode (treatment/control) |
2318
2489
 
2319
2490
  ### Commands
2320
2491
 
2321
2492
  \`\`\`bash
2493
+ # Initialize hooks in a project
2494
+ npx ruvector hooks init
2495
+
2322
2496
  # View learning statistics
2323
2497
  npx ruvector hooks stats
2324
2498
 
2325
2499
  # Route a task to best agent
2326
2500
  npx ruvector hooks route "implement feature X"
2327
2501
 
2328
- # Store context in memory
2502
+ # Store context in vector memory
2329
2503
  npx ruvector hooks remember "important context" -t project
2330
2504
 
2331
- # Recall from memory
2505
+ # Recall from memory (semantic search)
2332
2506
  npx ruvector hooks recall "context query"
2507
+
2508
+ # Manual session management
2509
+ npx ruvector hooks session-start
2510
+ npx ruvector hooks session-end
2333
2511
  \`\`\`
2334
2512
 
2335
2513
  ### How It Works
2336
2514
 
2337
- 1. **Pre-edit hooks** analyze files and suggest the best agent for the task
2515
+ 1. **Pre-edit hooks** analyze files and suggest the best agent based on learned patterns
2338
2516
  2. **Post-edit hooks** record outcomes to improve future suggestions via Q-learning
2339
- 3. **Memory hooks** store and retrieve context using vector embeddings
2517
+ 3. **Memory hooks** store and retrieve context using vector embeddings (cosine similarity)
2340
2518
  4. **Session hooks** manage learning state across conversations
2519
+ 5. **UserPromptSubmit** provides context suggestions on each message
2520
+ 6. **PreCompact** preserves critical context before conversation compaction
2521
+ 7. **Notification** tracks all events for continuous learning
2522
+
2523
+ ### Learning Data
2524
+
2525
+ Stored in \`.ruvector/intelligence.json\`:
2526
+ - **Q-table patterns**: State-action values for agent routing
2527
+ - **Vector memories**: Embeddings for semantic recall
2528
+ - **Trajectories**: Learning history for improvement tracking
2529
+ - **Error patterns**: Known issues and suggested fixes
2341
2530
 
2342
- ### Configuration
2531
+ ### Init Options
2343
2532
 
2344
- Settings are stored in \`.claude/settings.json\`. Run \`npx ruvector hooks init\` to regenerate.
2533
+ \`\`\`bash
2534
+ npx ruvector hooks init # Full configuration
2535
+ npx ruvector hooks init --minimal # Basic hooks only
2536
+ npx ruvector hooks init --no-env # Skip environment variables
2537
+ npx ruvector hooks init --no-permissions # Skip permissions
2538
+ npx ruvector hooks init --no-claude-md # Skip this file
2539
+ npx ruvector hooks init --force # Overwrite existing
2540
+ \`\`\`
2345
2541
 
2346
2542
  ---
2347
2543
  *Powered by [RuVector](https://github.com/ruvnet/ruvector) self-learning intelligence*
@@ -2351,6 +2547,32 @@ Settings are stored in \`.claude/settings.json\`. Run \`npx ruvector hooks init\
2351
2547
  } else if (fs.existsSync(claudeMdPath) && !opts.force) {
2352
2548
  console.log(chalk.yellow('ℹ️ CLAUDE.md already exists (use --force to overwrite)'));
2353
2549
  }
2550
+
2551
+ // Update .gitignore (unless --no-gitignore)
2552
+ if (opts.gitignore !== false) {
2553
+ const gitignorePath = path.join(process.cwd(), '.gitignore');
2554
+ const entriesToAdd = ['.ruvector/', '.claude/statusline.sh'];
2555
+ let gitignoreContent = '';
2556
+ if (fs.existsSync(gitignorePath)) {
2557
+ gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
2558
+ }
2559
+ const linesToAdd = entriesToAdd.filter(entry => !gitignoreContent.includes(entry));
2560
+ if (linesToAdd.length > 0) {
2561
+ const newContent = gitignoreContent.trim() + '\n\n# RuVector intelligence data\n' + linesToAdd.join('\n') + '\n';
2562
+ fs.writeFileSync(gitignorePath, newContent);
2563
+ console.log(chalk.blue(' ✓ .gitignore updated'));
2564
+ }
2565
+ }
2566
+
2567
+ // Create .ruvector directory for intelligence data
2568
+ const ruvectorDir = path.join(process.cwd(), '.ruvector');
2569
+ if (!fs.existsSync(ruvectorDir)) {
2570
+ fs.mkdirSync(ruvectorDir, { recursive: true });
2571
+ console.log(chalk.blue(' ✓ .ruvector/ directory created'));
2572
+ }
2573
+
2574
+ console.log(chalk.green('\n✅ RuVector hooks initialization complete!'));
2575
+ console.log(chalk.dim(' Run `npx ruvector hooks verify` to test the setup'));
2354
2576
  });
2355
2577
 
2356
2578
  hooksCmd.command('stats').description('Show intelligence statistics').action(() => {
@@ -2487,4 +2709,291 @@ hooksCmd.command('track-notification').description('Track notification').action(
2487
2709
  console.log(JSON.stringify({ tracked: true }));
2488
2710
  });
2489
2711
 
2712
+ // Verify hooks are working
2713
+ hooksCmd.command('verify')
2714
+ .description('Verify hooks are working correctly')
2715
+ .option('--verbose', 'Show detailed output')
2716
+ .action((opts) => {
2717
+ console.log(chalk.bold.cyan('\n🔍 RuVector Hooks Verification\n'));
2718
+ const checks = [];
2719
+
2720
+ // Check 1: Settings file exists
2721
+ const settingsPath = path.join(process.cwd(), '.claude', 'settings.json');
2722
+ if (fs.existsSync(settingsPath)) {
2723
+ checks.push({ name: 'Settings file', status: 'pass', detail: '.claude/settings.json exists' });
2724
+ try {
2725
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
2726
+ // Check hooks
2727
+ const requiredHooks = ['PreToolUse', 'PostToolUse', 'SessionStart', 'Stop'];
2728
+ const missingHooks = requiredHooks.filter(h => !settings.hooks?.[h]);
2729
+ if (missingHooks.length === 0) {
2730
+ checks.push({ name: 'Required hooks', status: 'pass', detail: 'All core hooks configured' });
2731
+ } else {
2732
+ checks.push({ name: 'Required hooks', status: 'fail', detail: `Missing: ${missingHooks.join(', ')}` });
2733
+ }
2734
+ // Check advanced hooks
2735
+ const advancedHooks = ['UserPromptSubmit', 'PreCompact', 'Notification'];
2736
+ const hasAdvanced = advancedHooks.filter(h => settings.hooks?.[h]);
2737
+ if (hasAdvanced.length > 0) {
2738
+ checks.push({ name: 'Advanced hooks', status: 'pass', detail: `${hasAdvanced.length}/3 configured` });
2739
+ } else {
2740
+ checks.push({ name: 'Advanced hooks', status: 'warn', detail: 'None configured (optional)' });
2741
+ }
2742
+ // Check env
2743
+ if (settings.env?.RUVECTOR_INTELLIGENCE_ENABLED) {
2744
+ checks.push({ name: 'Environment vars', status: 'pass', detail: 'Intelligence enabled' });
2745
+ } else {
2746
+ checks.push({ name: 'Environment vars', status: 'warn', detail: 'Not configured' });
2747
+ }
2748
+ // Check permissions
2749
+ if (settings.permissions?.allow?.length > 0) {
2750
+ checks.push({ name: 'Permissions', status: 'pass', detail: `${settings.permissions.allow.length} allowed patterns` });
2751
+ } else {
2752
+ checks.push({ name: 'Permissions', status: 'warn', detail: 'Not configured' });
2753
+ }
2754
+ } catch (e) {
2755
+ checks.push({ name: 'Settings parse', status: 'fail', detail: 'Invalid JSON' });
2756
+ }
2757
+ } else {
2758
+ checks.push({ name: 'Settings file', status: 'fail', detail: 'Run `npx ruvector hooks init` first' });
2759
+ }
2760
+
2761
+ // Check 2: .ruvector directory
2762
+ const ruvectorDir = path.join(process.cwd(), '.ruvector');
2763
+ if (fs.existsSync(ruvectorDir)) {
2764
+ checks.push({ name: 'Data directory', status: 'pass', detail: '.ruvector/ exists' });
2765
+ const intelFile = path.join(ruvectorDir, 'intelligence.json');
2766
+ if (fs.existsSync(intelFile)) {
2767
+ const stats = fs.statSync(intelFile);
2768
+ checks.push({ name: 'Intelligence file', status: 'pass', detail: `${(stats.size / 1024).toFixed(1)}KB` });
2769
+ } else {
2770
+ checks.push({ name: 'Intelligence file', status: 'warn', detail: 'Will be created on first use' });
2771
+ }
2772
+ } else {
2773
+ checks.push({ name: 'Data directory', status: 'warn', detail: 'Will be created on first use' });
2774
+ }
2775
+
2776
+ // Check 3: Hook command execution
2777
+ try {
2778
+ const { execSync } = require('child_process');
2779
+ execSync('npx ruvector hooks stats', { stdio: 'pipe', timeout: 5000 });
2780
+ checks.push({ name: 'Command execution', status: 'pass', detail: 'Hooks commands work' });
2781
+ } catch (e) {
2782
+ checks.push({ name: 'Command execution', status: 'fail', detail: 'Commands failed to execute' });
2783
+ }
2784
+
2785
+ // Display results
2786
+ let passCount = 0, warnCount = 0, failCount = 0;
2787
+ checks.forEach(c => {
2788
+ const icon = c.status === 'pass' ? chalk.green('✓') : c.status === 'warn' ? chalk.yellow('⚠') : chalk.red('✗');
2789
+ const statusColor = c.status === 'pass' ? chalk.green : c.status === 'warn' ? chalk.yellow : chalk.red;
2790
+ console.log(` ${icon} ${c.name}: ${statusColor(c.detail)}`);
2791
+ if (c.status === 'pass') passCount++;
2792
+ else if (c.status === 'warn') warnCount++;
2793
+ else failCount++;
2794
+ });
2795
+
2796
+ console.log('');
2797
+ if (failCount === 0) {
2798
+ console.log(chalk.green(`✅ Verification passed! ${passCount} checks passed, ${warnCount} warnings`));
2799
+ } else {
2800
+ console.log(chalk.red(`❌ Verification failed: ${failCount} issues found`));
2801
+ console.log(chalk.dim(' Run `npx ruvector hooks doctor` for detailed diagnostics'));
2802
+ }
2803
+ });
2804
+
2805
+ // Doctor - diagnose setup issues
2806
+ hooksCmd.command('doctor')
2807
+ .description('Diagnose and fix setup issues')
2808
+ .option('--fix', 'Automatically fix issues')
2809
+ .action((opts) => {
2810
+ console.log(chalk.bold.cyan('\n🩺 RuVector Hooks Doctor\n'));
2811
+ const issues = [];
2812
+ const fixes = [];
2813
+
2814
+ // Check settings file
2815
+ const settingsPath = path.join(process.cwd(), '.claude', 'settings.json');
2816
+ if (!fs.existsSync(settingsPath)) {
2817
+ issues.push({ severity: 'error', message: 'No .claude/settings.json found', fix: 'Run `npx ruvector hooks init`' });
2818
+ } else {
2819
+ try {
2820
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
2821
+
2822
+ // Check for invalid schema
2823
+ if (settings.$schema && !settings.$schema.includes('schemastore.org')) {
2824
+ issues.push({ severity: 'warning', message: 'Invalid schema URL', fix: 'Will be corrected' });
2825
+ if (opts.fix) {
2826
+ settings.$schema = 'https://json.schemastore.org/claude-code-settings.json';
2827
+ fixes.push('Fixed schema URL');
2828
+ }
2829
+ }
2830
+
2831
+ // Check for old hook names
2832
+ if (settings.hooks?.Start || settings.hooks?.End) {
2833
+ issues.push({ severity: 'error', message: 'Invalid hook names (Start/End)', fix: 'Should be SessionStart/Stop' });
2834
+ if (opts.fix) {
2835
+ delete settings.hooks.Start;
2836
+ delete settings.hooks.End;
2837
+ fixes.push('Removed invalid hook names');
2838
+ }
2839
+ }
2840
+
2841
+ // Check hook format
2842
+ const hookNames = ['PreToolUse', 'PostToolUse'];
2843
+ hookNames.forEach(name => {
2844
+ if (settings.hooks?.[name]) {
2845
+ settings.hooks[name].forEach((hook, i) => {
2846
+ if (typeof hook.matcher === 'object') {
2847
+ issues.push({ severity: 'error', message: `${name}[${i}].matcher should be string, not object`, fix: 'Will be corrected' });
2848
+ }
2849
+ });
2850
+ }
2851
+ });
2852
+
2853
+ // Check for npx vs direct command
2854
+ const checkCommands = (hooks) => {
2855
+ if (!hooks) return;
2856
+ hooks.forEach(h => {
2857
+ h.hooks?.forEach(hh => {
2858
+ if (hh.command && hh.command.includes('ruvector') && !hh.command.startsWith('npx ') && !hh.command.includes('/bin/')) {
2859
+ issues.push({ severity: 'warning', message: `Command should use 'npx ruvector' for portability`, fix: 'Update to use npx' });
2860
+ }
2861
+ });
2862
+ });
2863
+ };
2864
+ Object.values(settings.hooks || {}).forEach(checkCommands);
2865
+
2866
+ // Save fixes
2867
+ if (opts.fix && fixes.length > 0) {
2868
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
2869
+ }
2870
+ } catch (e) {
2871
+ issues.push({ severity: 'error', message: 'Invalid JSON in settings file', fix: 'Re-run `npx ruvector hooks init --force`' });
2872
+ }
2873
+ }
2874
+
2875
+ // Check .gitignore
2876
+ const gitignorePath = path.join(process.cwd(), '.gitignore');
2877
+ if (fs.existsSync(gitignorePath)) {
2878
+ const content = fs.readFileSync(gitignorePath, 'utf-8');
2879
+ if (!content.includes('.ruvector/')) {
2880
+ issues.push({ severity: 'warning', message: '.ruvector/ not in .gitignore', fix: 'Add to prevent committing learning data' });
2881
+ if (opts.fix) {
2882
+ fs.appendFileSync(gitignorePath, '\n# RuVector intelligence data\n.ruvector/\n');
2883
+ fixes.push('Added .ruvector/ to .gitignore');
2884
+ }
2885
+ }
2886
+ }
2887
+
2888
+ // Display results
2889
+ if (issues.length === 0) {
2890
+ console.log(chalk.green(' ✓ No issues found! Your setup looks healthy.'));
2891
+ } else {
2892
+ issues.forEach(i => {
2893
+ const icon = i.severity === 'error' ? chalk.red('✗') : chalk.yellow('⚠');
2894
+ console.log(` ${icon} ${i.message}`);
2895
+ console.log(chalk.dim(` Fix: ${i.fix}`));
2896
+ });
2897
+
2898
+ if (opts.fix && fixes.length > 0) {
2899
+ console.log(chalk.green(`\n✅ Applied ${fixes.length} fix(es):`));
2900
+ fixes.forEach(f => console.log(chalk.green(` • ${f}`)));
2901
+ } else if (issues.some(i => i.severity === 'error')) {
2902
+ console.log(chalk.yellow('\n💡 Run with --fix to automatically fix issues'));
2903
+ }
2904
+ }
2905
+ });
2906
+
2907
+ // Export intelligence data
2908
+ hooksCmd.command('export')
2909
+ .description('Export intelligence data for backup')
2910
+ .option('-o, --output <file>', 'Output file path', 'ruvector-export.json')
2911
+ .option('--include-all', 'Include all data (patterns, memories, trajectories)')
2912
+ .action((opts) => {
2913
+ const intel = new Intelligence();
2914
+ const exportData = {
2915
+ version: '1.0',
2916
+ exported_at: new Date().toISOString(),
2917
+ patterns: intel.data?.patterns || {},
2918
+ memories: opts.includeAll ? (intel.data?.memories || []) : [],
2919
+ trajectories: opts.includeAll ? (intel.data?.trajectories || []) : [],
2920
+ errors: intel.data?.errors || {},
2921
+ stats: intel.stats()
2922
+ };
2923
+
2924
+ const outputPath = path.resolve(opts.output);
2925
+ fs.writeFileSync(outputPath, JSON.stringify(exportData, null, 2));
2926
+
2927
+ console.log(chalk.green(`✅ Exported intelligence data to ${outputPath}`));
2928
+ console.log(chalk.dim(` ${Object.keys(exportData.patterns).length} patterns`));
2929
+ console.log(chalk.dim(` ${exportData.memories.length} memories`));
2930
+ console.log(chalk.dim(` ${exportData.trajectories.length} trajectories`));
2931
+ });
2932
+
2933
+ // Import intelligence data
2934
+ hooksCmd.command('import')
2935
+ .description('Import intelligence data from backup')
2936
+ .argument('<file>', 'Import file path')
2937
+ .option('--merge', 'Merge with existing data (default: replace)')
2938
+ .option('--dry-run', 'Show what would be imported without making changes')
2939
+ .action((file, opts) => {
2940
+ const importPath = path.resolve(file);
2941
+ if (!fs.existsSync(importPath)) {
2942
+ console.error(chalk.red(`❌ File not found: ${importPath}`));
2943
+ process.exit(1);
2944
+ }
2945
+
2946
+ try {
2947
+ const importData = JSON.parse(fs.readFileSync(importPath, 'utf-8'));
2948
+
2949
+ if (!importData.version) {
2950
+ console.error(chalk.red('❌ Invalid export file (missing version)'));
2951
+ process.exit(1);
2952
+ }
2953
+
2954
+ console.log(chalk.cyan(`📦 Import file: ${file}`));
2955
+ console.log(chalk.dim(` Version: ${importData.version}`));
2956
+ console.log(chalk.dim(` Exported: ${importData.exported_at}`));
2957
+ console.log(chalk.dim(` Patterns: ${Object.keys(importData.patterns || {}).length}`));
2958
+ console.log(chalk.dim(` Memories: ${(importData.memories || []).length}`));
2959
+ console.log(chalk.dim(` Trajectories: ${(importData.trajectories || []).length}`));
2960
+
2961
+ if (opts.dryRun) {
2962
+ console.log(chalk.yellow('\n⚠️ Dry run - no changes made'));
2963
+ return;
2964
+ }
2965
+
2966
+ const intel = new Intelligence();
2967
+
2968
+ if (opts.merge) {
2969
+ // Merge patterns
2970
+ Object.assign(intel.data.patterns, importData.patterns || {});
2971
+ // Merge memories (deduplicate by content)
2972
+ const existingContent = new Set((intel.data.memories || []).map(m => m.content));
2973
+ (importData.memories || []).forEach(m => {
2974
+ if (!existingContent.has(m.content)) {
2975
+ intel.data.memories.push(m);
2976
+ }
2977
+ });
2978
+ // Merge trajectories
2979
+ intel.data.trajectories = (intel.data.trajectories || []).concat(importData.trajectories || []);
2980
+ // Merge errors
2981
+ Object.assign(intel.data.errors, importData.errors || {});
2982
+ console.log(chalk.green('✅ Merged intelligence data'));
2983
+ } else {
2984
+ intel.data.patterns = importData.patterns || {};
2985
+ intel.data.memories = importData.memories || [];
2986
+ intel.data.trajectories = importData.trajectories || [];
2987
+ intel.data.errors = importData.errors || {};
2988
+ console.log(chalk.green('✅ Replaced intelligence data'));
2989
+ }
2990
+
2991
+ intel.save();
2992
+ console.log(chalk.dim(' Data saved to .ruvector/intelligence.json'));
2993
+ } catch (e) {
2994
+ console.error(chalk.red(`❌ Failed to import: ${e.message}`));
2995
+ process.exit(1);
2996
+ }
2997
+ });
2998
+
2490
2999
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvector",
3
- "version": "0.1.44",
3
+ "version": "0.1.46",
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",