From 5effc17c4369bb49a233323c0aa6e5358436d5fa Mon Sep 17 00:00:00 2001 From: rUv Date: Tue, 30 Dec 2025 19:22:00 +0000 Subject: [PATCH 01/10] fix(hooks): init command now fixes invalid schema and hook names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixes $schema to correct URL - Removes invalid Start/End hooks (renamed to SessionStart/Stop) - Preserves other existing settings when merging Bumps to v0.1.43 šŸ¤– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- npm/packages/ruvector/bin/cli.js | 9 +++++++++ npm/packages/ruvector/package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/npm/packages/ruvector/bin/cli.js b/npm/packages/ruvector/bin/cli.js index b49d4f331..c3b40e12a 100755 --- a/npm/packages/ruvector/bin/cli.js +++ b/npm/packages/ruvector/bin/cli.js @@ -2275,6 +2275,15 @@ hooksCmd.command('init').description('Initialize hooks in current project').opti if (fs.existsSync(settingsPath) && !opts.force) { try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); } catch {} } + // Fix schema if present + if (settings.$schema) { + settings.$schema = 'https://json.schemastore.org/claude-code-settings.json'; + } + // Clean up invalid hook names + if (settings.hooks) { + if (settings.hooks.Start) { delete settings.hooks.Start; } + if (settings.hooks.End) { delete settings.hooks.End; } + } settings.hooks = settings.hooks || {}; settings.hooks.PreToolUse = [ { matcher: 'Edit|Write|MultiEdit', hooks: [{ type: 'command', command: 'npx ruvector hooks pre-edit "$TOOL_INPUT_file_path"' }] }, diff --git a/npm/packages/ruvector/package.json b/npm/packages/ruvector/package.json index 81d1530b4..b52e236fb 100644 --- a/npm/packages/ruvector/package.json +++ b/npm/packages/ruvector/package.json @@ -1,6 +1,6 @@ { "name": "ruvector", - "version": "0.1.42", + "version": "0.1.43", "description": "High-performance vector database for Node.js with automatic native/WASM fallback", "main": "dist/index.js", "types": "dist/index.d.ts", From c961cbc50e57611d50ad9f641bfee16b6e4f0745 Mon Sep 17 00:00:00 2001 From: rUv Date: Tue, 30 Dec 2025 19:25:59 +0000 Subject: [PATCH 02/10] feat(hooks): init command now creates CLAUDE.md with RuVector documentation - Added CLAUDE.md creation to 'hooks init' command - Includes complete hooks documentation and CLI commands - Added --no-claude-md flag to skip CLAUDE.md creation - Respects existing CLAUDE.md unless --force is used --- npm/packages/ruvector/bin/cli.js | 56 +++++++++++++++++++++++++++++- npm/packages/ruvector/package.json | 2 +- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/npm/packages/ruvector/bin/cli.js b/npm/packages/ruvector/bin/cli.js index c3b40e12a..95346ec3b 100755 --- a/npm/packages/ruvector/bin/cli.js +++ b/npm/packages/ruvector/bin/cli.js @@ -2267,7 +2267,7 @@ class Intelligence { // Hooks command group const hooksCmd = program.command('hooks').description('Self-learning intelligence hooks for Claude Code'); -hooksCmd.command('init').description('Initialize hooks in current project').option('--force', 'Force overwrite').action((opts) => { +hooksCmd.command('init').description('Initialize hooks in current project').option('--force', 'Force overwrite').option('--no-claude-md', 'Skip CLAUDE.md creation').action((opts) => { const settingsPath = path.join(process.cwd(), '.claude', 'settings.json'); const settingsDir = path.dirname(settingsPath); if (!fs.existsSync(settingsDir)) fs.mkdirSync(settingsDir, { recursive: true }); @@ -2297,6 +2297,60 @@ hooksCmd.command('init').description('Initialize hooks in current project').opti settings.hooks.Stop = [{ hooks: [{ type: 'command', command: 'npx ruvector hooks session-end' }] }]; fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2)); console.log(chalk.green('āœ… Hooks initialized in .claude/settings.json')); + + // Create CLAUDE.md if it doesn't exist (or force) + const claudeMdPath = path.join(process.cwd(), 'CLAUDE.md'); + if (opts.claudeMd !== false && (!fs.existsSync(claudeMdPath) || opts.force)) { + const claudeMdContent = `# Claude Code Project Configuration + +## RuVector Self-Learning Hooks + +This project uses RuVector's self-learning intelligence hooks for enhanced AI-assisted development. + +### Active Hooks + +| Hook | Trigger | Purpose | +|------|---------|---------| +| PreToolUse | Before Edit/Write/Bash | Agent routing, command analysis | +| PostToolUse | After Edit/Write/Bash | Q-learning update, pattern recording | +| SessionStart | Conversation begins | Load intelligence, display stats | +| Stop | Conversation ends | Save learning data | + +### Commands + +\`\`\`bash +# View learning statistics +npx ruvector hooks stats + +# Route a task to best agent +npx ruvector hooks route "implement feature X" + +# Store context in memory +npx ruvector hooks remember "important context" -t project + +# Recall from memory +npx ruvector hooks recall "context query" +\`\`\` + +### How It Works + +1. **Pre-edit hooks** analyze files and suggest the best agent for the task +2. **Post-edit hooks** record outcomes to improve future suggestions via Q-learning +3. **Memory hooks** store and retrieve context using vector embeddings +4. **Session hooks** manage learning state across conversations + +### Configuration + +Settings are stored in \`.claude/settings.json\`. Run \`npx ruvector hooks init\` to regenerate. + +--- +*Powered by [RuVector](https://github.com/ruvnet/ruvector) self-learning intelligence* +`; + fs.writeFileSync(claudeMdPath, claudeMdContent); + console.log(chalk.green('āœ… CLAUDE.md created in project root')); + } else if (fs.existsSync(claudeMdPath) && !opts.force) { + console.log(chalk.yellow('ā„¹ļø CLAUDE.md already exists (use --force to overwrite)')); + } }); hooksCmd.command('stats').description('Show intelligence statistics').action(() => { diff --git a/npm/packages/ruvector/package.json b/npm/packages/ruvector/package.json index b52e236fb..a58c27570 100644 --- a/npm/packages/ruvector/package.json +++ b/npm/packages/ruvector/package.json @@ -1,6 +1,6 @@ { "name": "ruvector", - "version": "0.1.43", + "version": "0.1.44", "description": "High-performance vector database for Node.js with automatic native/WASM fallback", "main": "dist/index.js", "types": "dist/index.d.ts", From 345e8a45b95dcb22ee2ff59bfbedf57754617114 Mon Sep 17 00:00:00 2001 From: rUv Date: Tue, 30 Dec 2025 21:11:48 +0000 Subject: [PATCH 03/10] feat(hooks): enhanced init with full configuration - Add environment variables (RUVECTOR_INTELLIGENCE_ENABLED, LEARNING_RATE, etc.) - Add permissions configuration (allow/deny lists) - Add UserPromptSubmit hook for context suggestions - Add PreCompact hook for preserving context before compaction - Add Notification hook for event tracking - Add --minimal flag for basic hooks only - Add --no-env and --no-permissions flags - Updated CLAUDE.md template with complete documentation --- npm/packages/ruvector/bin/cli.js | 163 ++++++++++++++++++++++++++--- npm/packages/ruvector/package.json | 2 +- 2 files changed, 150 insertions(+), 15 deletions(-) diff --git a/npm/packages/ruvector/bin/cli.js b/npm/packages/ruvector/bin/cli.js index 95346ec3b..e67fac3fa 100755 --- a/npm/packages/ruvector/bin/cli.js +++ b/npm/packages/ruvector/bin/cli.js @@ -2267,7 +2267,14 @@ class Intelligence { // Hooks command group const hooksCmd = program.command('hooks').description('Self-learning intelligence hooks for Claude Code'); -hooksCmd.command('init').description('Initialize hooks in current project').option('--force', 'Force overwrite').option('--no-claude-md', 'Skip CLAUDE.md creation').action((opts) => { +hooksCmd.command('init') + .description('Initialize hooks in current project') + .option('--force', 'Force overwrite existing settings') + .option('--minimal', 'Only basic hooks (no env, permissions, or advanced hooks)') + .option('--no-claude-md', 'Skip CLAUDE.md creation') + .option('--no-permissions', 'Skip permissions configuration') + .option('--no-env', 'Skip environment variables') + .action((opts) => { const settingsPath = path.join(process.cwd(), '.claude', 'settings.json'); const settingsDir = path.dirname(settingsPath); if (!fs.existsSync(settingsDir)) fs.mkdirSync(settingsDir, { recursive: true }); @@ -2275,15 +2282,61 @@ hooksCmd.command('init').description('Initialize hooks in current project').opti if (fs.existsSync(settingsPath) && !opts.force) { try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); } catch {} } + // Fix schema if present if (settings.$schema) { settings.$schema = 'https://json.schemastore.org/claude-code-settings.json'; } + // Clean up invalid hook names if (settings.hooks) { if (settings.hooks.Start) { delete settings.hooks.Start; } if (settings.hooks.End) { delete settings.hooks.End; } } + + // Environment variables for intelligence (unless --minimal or --no-env) + if (!opts.minimal && opts.env !== false) { + settings.env = settings.env || {}; + settings.env.RUVECTOR_INTELLIGENCE_ENABLED = settings.env.RUVECTOR_INTELLIGENCE_ENABLED || 'true'; + settings.env.RUVECTOR_LEARNING_RATE = settings.env.RUVECTOR_LEARNING_RATE || '0.1'; + settings.env.RUVECTOR_MEMORY_BACKEND = settings.env.RUVECTOR_MEMORY_BACKEND || 'rvlite'; + settings.env.INTELLIGENCE_MODE = settings.env.INTELLIGENCE_MODE || 'treatment'; + console.log(chalk.blue(' āœ“ Environment variables configured')); + } + + // Permissions (unless --minimal or --no-permissions) + if (!opts.minimal && opts.permissions !== false) { + settings.permissions = settings.permissions || {}; + settings.permissions.allow = settings.permissions.allow || [ + 'Bash(npm run:*)', + 'Bash(npm test:*)', + 'Bash(npm install:*)', + 'Bash(npx:*)', + 'Bash(git status)', + 'Bash(git diff:*)', + 'Bash(git log:*)', + 'Bash(git add:*)', + 'Bash(git commit:*)', + 'Bash(git push)', + 'Bash(git branch:*)', + 'Bash(git checkout:*)', + 'Bash(ls:*)', + 'Bash(pwd)', + 'Bash(cat:*)', + 'Bash(mkdir:*)', + 'Bash(which:*)', + 'Bash(node:*)', + 'Bash(ruvector:*)' + ]; + settings.permissions.deny = settings.permissions.deny || [ + 'Bash(rm -rf /)', + 'Bash(sudo rm:*)', + 'Bash(chmod 777:*)' + ]; + console.log(chalk.blue(' āœ“ Permissions configured')); + } + + // Core hooks (always included) settings.hooks = settings.hooks || {}; settings.hooks.PreToolUse = [ { matcher: 'Edit|Write|MultiEdit', hooks: [{ type: 'command', command: 'npx ruvector hooks pre-edit "$TOOL_INPUT_file_path"' }] }, @@ -2295,53 +2348,135 @@ hooksCmd.command('init').description('Initialize hooks in current project').opti ]; settings.hooks.SessionStart = [{ hooks: [{ type: 'command', command: 'npx ruvector hooks session-start' }] }]; settings.hooks.Stop = [{ hooks: [{ type: 'command', command: 'npx ruvector hooks session-end' }] }]; + console.log(chalk.blue(' āœ“ Core hooks (PreToolUse, PostToolUse, SessionStart, Stop)')); + + // Advanced hooks (unless --minimal) + if (!opts.minimal) { + // UserPromptSubmit - context suggestions on each prompt + settings.hooks.UserPromptSubmit = [{ + hooks: [{ + type: 'command', + timeout: 2000, + command: 'npx ruvector hooks suggest-context' + }] + }]; + + // PreCompact - preserve important context before compaction + settings.hooks.PreCompact = [ + { + matcher: 'auto', + hooks: [{ + type: 'command', + timeout: 3000, + command: 'npx ruvector hooks pre-compact --auto' + }] + }, + { + matcher: 'manual', + hooks: [{ + type: 'command', + timeout: 3000, + command: 'npx ruvector hooks pre-compact' + }] + } + ]; + + // Notification - track all notifications for learning + settings.hooks.Notification = [{ + matcher: '.*', + hooks: [{ + type: 'command', + timeout: 1000, + command: 'npx ruvector hooks track-notification' + }] + }]; + console.log(chalk.blue(' āœ“ Advanced hooks (UserPromptSubmit, PreCompact, Notification)')); + } + fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2)); - console.log(chalk.green('āœ… Hooks initialized in .claude/settings.json')); + console.log(chalk.green('\nāœ… Hooks initialized in .claude/settings.json')); // Create CLAUDE.md if it doesn't exist (or force) const claudeMdPath = path.join(process.cwd(), 'CLAUDE.md'); if (opts.claudeMd !== false && (!fs.existsSync(claudeMdPath) || opts.force)) { const claudeMdContent = `# Claude Code Project Configuration -## RuVector Self-Learning Hooks +## RuVector Self-Learning Intelligence -This project uses RuVector's self-learning intelligence hooks for enhanced AI-assisted development. +This project uses RuVector's self-learning intelligence hooks for enhanced AI-assisted development with Q-learning, vector memory, and automatic agent routing. ### Active Hooks | Hook | Trigger | Purpose | |------|---------|---------| -| PreToolUse | Before Edit/Write/Bash | Agent routing, command analysis | -| PostToolUse | After Edit/Write/Bash | Q-learning update, pattern recording | -| SessionStart | Conversation begins | Load intelligence, display stats | -| Stop | Conversation ends | Save learning data | +| **PreToolUse** | Before Edit/Write/Bash | Agent routing, file analysis, command risk assessment | +| **PostToolUse** | After Edit/Write/Bash | Q-learning update, pattern recording, outcome tracking | +| **SessionStart** | Conversation begins | Load intelligence state, display learning stats | +| **Stop** | Conversation ends | Save learning data, export metrics | +| **UserPromptSubmit** | User sends message | Context suggestions, pattern recommendations | +| **PreCompact** | Before context compaction | Preserve important context and memories | +| **Notification** | Any notification | Track events for learning | + +### Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| \`RUVECTOR_INTELLIGENCE_ENABLED\` | \`true\` | Enable/disable intelligence layer | +| \`RUVECTOR_LEARNING_RATE\` | \`0.1\` | Q-learning rate (0.0-1.0) | +| \`RUVECTOR_MEMORY_BACKEND\` | \`rvlite\` | Memory storage backend | +| \`INTELLIGENCE_MODE\` | \`treatment\` | A/B testing mode (treatment/control) | ### Commands \`\`\`bash +# Initialize hooks in a project +npx ruvector hooks init + # View learning statistics npx ruvector hooks stats # Route a task to best agent npx ruvector hooks route "implement feature X" -# Store context in memory +# Store context in vector memory npx ruvector hooks remember "important context" -t project -# Recall from memory +# Recall from memory (semantic search) npx ruvector hooks recall "context query" + +# Manual session management +npx ruvector hooks session-start +npx ruvector hooks session-end \`\`\` ### How It Works -1. **Pre-edit hooks** analyze files and suggest the best agent for the task +1. **Pre-edit hooks** analyze files and suggest the best agent based on learned patterns 2. **Post-edit hooks** record outcomes to improve future suggestions via Q-learning -3. **Memory hooks** store and retrieve context using vector embeddings +3. **Memory hooks** store and retrieve context using vector embeddings (cosine similarity) 4. **Session hooks** manage learning state across conversations +5. **UserPromptSubmit** provides context suggestions on each message +6. **PreCompact** preserves critical context before conversation compaction +7. **Notification** tracks all events for continuous learning + +### Learning Data + +Stored in \`.ruvector/intelligence.json\`: +- **Q-table patterns**: State-action values for agent routing +- **Vector memories**: Embeddings for semantic recall +- **Trajectories**: Learning history for improvement tracking +- **Error patterns**: Known issues and suggested fixes -### Configuration +### Init Options -Settings are stored in \`.claude/settings.json\`. Run \`npx ruvector hooks init\` to regenerate. +\`\`\`bash +npx ruvector hooks init # Full configuration +npx ruvector hooks init --minimal # Basic hooks only +npx ruvector hooks init --no-env # Skip environment variables +npx ruvector hooks init --no-permissions # Skip permissions +npx ruvector hooks init --no-claude-md # Skip this file +npx ruvector hooks init --force # Overwrite existing +\`\`\` --- *Powered by [RuVector](https://github.com/ruvnet/ruvector) self-learning intelligence* diff --git a/npm/packages/ruvector/package.json b/npm/packages/ruvector/package.json index a58c27570..896ee2594 100644 --- a/npm/packages/ruvector/package.json +++ b/npm/packages/ruvector/package.json @@ -1,6 +1,6 @@ { "name": "ruvector", - "version": "0.1.44", + "version": "0.1.45", "description": "High-performance vector database for Node.js with automatic native/WASM fallback", "main": "dist/index.js", "types": "dist/index.d.ts", From 6246d5801e70b5699972f056dd214dcc3d772d91 Mon Sep 17 00:00:00 2001 From: rUv Date: Tue, 30 Dec 2025 21:18:29 +0000 Subject: [PATCH 04/10] feat(hooks): add verify, doctor, export, import commands and enhanced init New commands: - hooks verify: Check if hooks are working correctly - hooks doctor: Diagnose and fix setup issues (with --fix) - hooks export: Export intelligence data for backup - hooks import: Import intelligence data (with --merge, --dry-run) Enhanced init: - Auto-detect project type (Rust, Node, Python, Go, Ruby, Java) - Project-specific permissions (cargo for Rust, npm for Node, etc.) - StatusLine configuration with .claude/statusline.sh - MCP server configuration (claude-flow) - .gitignore update (adds .ruvector/) - Creates .ruvector/ directory New options for init: - --no-gitignore: Skip .gitignore update - --no-mcp: Skip MCP server configuration - --no-statusline: Skip statusLine configuration --- npm/packages/ruvector/bin/cli.js | 422 +++++++++++++++++++++++++++-- npm/packages/ruvector/package.json | 2 +- 2 files changed, 399 insertions(+), 25 deletions(-) diff --git a/npm/packages/ruvector/bin/cli.js b/npm/packages/ruvector/bin/cli.js index e67fac3fa..8909bc414 100755 --- a/npm/packages/ruvector/bin/cli.js +++ b/npm/packages/ruvector/bin/cli.js @@ -2267,6 +2267,40 @@ class Intelligence { // Hooks command group const hooksCmd = program.command('hooks').description('Self-learning intelligence hooks for Claude Code'); +// Helper: Detect project type +function detectProjectType() { + const cwd = process.cwd(); + const types = []; + if (fs.existsSync(path.join(cwd, 'Cargo.toml'))) types.push('rust'); + if (fs.existsSync(path.join(cwd, 'package.json'))) types.push('node'); + if (fs.existsSync(path.join(cwd, 'requirements.txt')) || fs.existsSync(path.join(cwd, 'pyproject.toml'))) types.push('python'); + if (fs.existsSync(path.join(cwd, 'go.mod'))) types.push('go'); + if (fs.existsSync(path.join(cwd, 'Gemfile'))) types.push('ruby'); + if (fs.existsSync(path.join(cwd, 'pom.xml')) || fs.existsSync(path.join(cwd, 'build.gradle'))) types.push('java'); + return types.length > 0 ? types : ['generic']; +} + +// Helper: Get permissions for project type +function getPermissionsForProjectType(types) { + const basePermissions = [ + 'Bash(git status)', 'Bash(git diff:*)', 'Bash(git log:*)', 'Bash(git add:*)', + 'Bash(git commit:*)', 'Bash(git push)', 'Bash(git branch:*)', 'Bash(git checkout:*)', + 'Bash(ls:*)', 'Bash(pwd)', 'Bash(cat:*)', 'Bash(mkdir:*)', 'Bash(which:*)', 'Bash(ruvector:*)' + ]; + const typePermissions = { + rust: ['Bash(cargo:*)', 'Bash(rustc:*)', 'Bash(rustfmt:*)', 'Bash(clippy:*)', 'Bash(wasm-pack:*)'], + node: ['Bash(npm:*)', 'Bash(npx:*)', 'Bash(node:*)', 'Bash(yarn:*)', 'Bash(pnpm:*)'], + python: ['Bash(python:*)', 'Bash(pip:*)', 'Bash(pytest:*)', 'Bash(poetry:*)', 'Bash(uv:*)'], + go: ['Bash(go:*)', 'Bash(gofmt:*)'], + ruby: ['Bash(ruby:*)', 'Bash(gem:*)', 'Bash(bundle:*)', 'Bash(rails:*)'], + java: ['Bash(mvn:*)', 'Bash(gradle:*)', 'Bash(java:*)', 'Bash(javac:*)'], + generic: ['Bash(make:*)'] + }; + let perms = [...basePermissions]; + types.forEach(t => { if (typePermissions[t]) perms = perms.concat(typePermissions[t]); }); + return [...new Set(perms)]; +} + hooksCmd.command('init') .description('Initialize hooks in current project') .option('--force', 'Force overwrite existing settings') @@ -2274,6 +2308,9 @@ hooksCmd.command('init') .option('--no-claude-md', 'Skip CLAUDE.md creation') .option('--no-permissions', 'Skip permissions configuration') .option('--no-env', 'Skip environment variables') + .option('--no-gitignore', 'Skip .gitignore update') + .option('--no-mcp', 'Skip MCP server configuration') + .option('--no-statusline', 'Skip statusLine configuration') .action((opts) => { const settingsPath = path.join(process.cwd(), '.claude', 'settings.json'); const settingsDir = path.dirname(settingsPath); @@ -2294,6 +2331,10 @@ hooksCmd.command('init') if (settings.hooks.End) { delete settings.hooks.End; } } + // Detect project type + const projectTypes = detectProjectType(); + console.log(chalk.blue(` āœ“ Detected project type(s): ${projectTypes.join(', ')}`)); + // Environment variables for intelligence (unless --minimal or --no-env) if (!opts.minimal && opts.env !== false) { settings.env = settings.env || {}; @@ -2304,36 +2345,56 @@ hooksCmd.command('init') console.log(chalk.blue(' āœ“ Environment variables configured')); } - // Permissions (unless --minimal or --no-permissions) + // Permissions based on detected project type (unless --minimal or --no-permissions) if (!opts.minimal && opts.permissions !== false) { settings.permissions = settings.permissions || {}; - settings.permissions.allow = settings.permissions.allow || [ - 'Bash(npm run:*)', - 'Bash(npm test:*)', - 'Bash(npm install:*)', - 'Bash(npx:*)', - 'Bash(git status)', - 'Bash(git diff:*)', - 'Bash(git log:*)', - 'Bash(git add:*)', - 'Bash(git commit:*)', - 'Bash(git push)', - 'Bash(git branch:*)', - 'Bash(git checkout:*)', - 'Bash(ls:*)', - 'Bash(pwd)', - 'Bash(cat:*)', - 'Bash(mkdir:*)', - 'Bash(which:*)', - 'Bash(node:*)', - 'Bash(ruvector:*)' - ]; + settings.permissions.allow = settings.permissions.allow || getPermissionsForProjectType(projectTypes); settings.permissions.deny = settings.permissions.deny || [ 'Bash(rm -rf /)', 'Bash(sudo rm:*)', - 'Bash(chmod 777:*)' + 'Bash(chmod 777:*)', + 'Bash(:(){ :|:& };:)' ]; - console.log(chalk.blue(' āœ“ Permissions configured')); + console.log(chalk.blue(' āœ“ Permissions configured (project-specific)')); + } + + // MCP server configuration (unless --minimal or --no-mcp) + if (!opts.minimal && opts.mcp !== false) { + settings.mcpServers = settings.mcpServers || {}; + // Only add if not already configured + if (!settings.mcpServers['claude-flow'] && !settings.enabledMcpjsonServers?.includes('claude-flow')) { + settings.enabledMcpjsonServers = settings.enabledMcpjsonServers || []; + if (!settings.enabledMcpjsonServers.includes('claude-flow')) { + settings.enabledMcpjsonServers.push('claude-flow'); + } + } + console.log(chalk.blue(' āœ“ MCP servers configured')); + } + + // StatusLine configuration (unless --minimal or --no-statusline) + if (!opts.minimal && opts.statusline !== false) { + if (!settings.statusLine) { + // Create a simple statusline script + const statuslineScript = path.join(settingsDir, 'statusline.sh'); + const statuslineContent = `#!/bin/bash +# RuVector Status Line - shows intelligence stats +INTEL_FILE=".ruvector/intelligence.json" +if [ -f "$INTEL_FILE" ]; then + PATTERNS=$(jq -r '.patterns | length // 0' "$INTEL_FILE" 2>/dev/null || echo "0") + MEMORIES=$(jq -r '.memories | length // 0' "$INTEL_FILE" 2>/dev/null || echo "0") + echo "🧠 $PATTERNS patterns | šŸ’¾ $MEMORIES memories" +else + echo "🧠 RuVector" +fi +`; + fs.writeFileSync(statuslineScript, statuslineContent); + fs.chmodSync(statuslineScript, '755'); + settings.statusLine = { + type: 'command', + command: '.claude/statusline.sh' + }; + console.log(chalk.blue(' āœ“ StatusLine configured')); + } } // Core hooks (always included) @@ -2486,6 +2547,32 @@ npx ruvector hooks init --force # Overwrite existing } else if (fs.existsSync(claudeMdPath) && !opts.force) { console.log(chalk.yellow('ā„¹ļø CLAUDE.md already exists (use --force to overwrite)')); } + + // Update .gitignore (unless --no-gitignore) + if (opts.gitignore !== false) { + const gitignorePath = path.join(process.cwd(), '.gitignore'); + const entriesToAdd = ['.ruvector/', '.claude/statusline.sh']; + let gitignoreContent = ''; + if (fs.existsSync(gitignorePath)) { + gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8'); + } + const linesToAdd = entriesToAdd.filter(entry => !gitignoreContent.includes(entry)); + if (linesToAdd.length > 0) { + const newContent = gitignoreContent.trim() + '\n\n# RuVector intelligence data\n' + linesToAdd.join('\n') + '\n'; + fs.writeFileSync(gitignorePath, newContent); + console.log(chalk.blue(' āœ“ .gitignore updated')); + } + } + + // Create .ruvector directory for intelligence data + const ruvectorDir = path.join(process.cwd(), '.ruvector'); + if (!fs.existsSync(ruvectorDir)) { + fs.mkdirSync(ruvectorDir, { recursive: true }); + console.log(chalk.blue(' āœ“ .ruvector/ directory created')); + } + + console.log(chalk.green('\nāœ… RuVector hooks initialization complete!')); + console.log(chalk.dim(' Run `npx ruvector hooks verify` to test the setup')); }); hooksCmd.command('stats').description('Show intelligence statistics').action(() => { @@ -2622,4 +2709,291 @@ hooksCmd.command('track-notification').description('Track notification').action( console.log(JSON.stringify({ tracked: true })); }); +// Verify hooks are working +hooksCmd.command('verify') + .description('Verify hooks are working correctly') + .option('--verbose', 'Show detailed output') + .action((opts) => { + console.log(chalk.bold.cyan('\nšŸ” RuVector Hooks Verification\n')); + const checks = []; + + // Check 1: Settings file exists + const settingsPath = path.join(process.cwd(), '.claude', 'settings.json'); + if (fs.existsSync(settingsPath)) { + checks.push({ name: 'Settings file', status: 'pass', detail: '.claude/settings.json exists' }); + try { + const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); + // Check hooks + const requiredHooks = ['PreToolUse', 'PostToolUse', 'SessionStart', 'Stop']; + const missingHooks = requiredHooks.filter(h => !settings.hooks?.[h]); + if (missingHooks.length === 0) { + checks.push({ name: 'Required hooks', status: 'pass', detail: 'All core hooks configured' }); + } else { + checks.push({ name: 'Required hooks', status: 'fail', detail: `Missing: ${missingHooks.join(', ')}` }); + } + // Check advanced hooks + const advancedHooks = ['UserPromptSubmit', 'PreCompact', 'Notification']; + const hasAdvanced = advancedHooks.filter(h => settings.hooks?.[h]); + if (hasAdvanced.length > 0) { + checks.push({ name: 'Advanced hooks', status: 'pass', detail: `${hasAdvanced.length}/3 configured` }); + } else { + checks.push({ name: 'Advanced hooks', status: 'warn', detail: 'None configured (optional)' }); + } + // Check env + if (settings.env?.RUVECTOR_INTELLIGENCE_ENABLED) { + checks.push({ name: 'Environment vars', status: 'pass', detail: 'Intelligence enabled' }); + } else { + checks.push({ name: 'Environment vars', status: 'warn', detail: 'Not configured' }); + } + // Check permissions + if (settings.permissions?.allow?.length > 0) { + checks.push({ name: 'Permissions', status: 'pass', detail: `${settings.permissions.allow.length} allowed patterns` }); + } else { + checks.push({ name: 'Permissions', status: 'warn', detail: 'Not configured' }); + } + } catch (e) { + checks.push({ name: 'Settings parse', status: 'fail', detail: 'Invalid JSON' }); + } + } else { + checks.push({ name: 'Settings file', status: 'fail', detail: 'Run `npx ruvector hooks init` first' }); + } + + // Check 2: .ruvector directory + const ruvectorDir = path.join(process.cwd(), '.ruvector'); + if (fs.existsSync(ruvectorDir)) { + checks.push({ name: 'Data directory', status: 'pass', detail: '.ruvector/ exists' }); + const intelFile = path.join(ruvectorDir, 'intelligence.json'); + if (fs.existsSync(intelFile)) { + const stats = fs.statSync(intelFile); + checks.push({ name: 'Intelligence file', status: 'pass', detail: `${(stats.size / 1024).toFixed(1)}KB` }); + } else { + checks.push({ name: 'Intelligence file', status: 'warn', detail: 'Will be created on first use' }); + } + } else { + checks.push({ name: 'Data directory', status: 'warn', detail: 'Will be created on first use' }); + } + + // Check 3: Hook command execution + try { + const { execSync } = require('child_process'); + execSync('npx ruvector hooks stats', { stdio: 'pipe', timeout: 5000 }); + checks.push({ name: 'Command execution', status: 'pass', detail: 'Hooks commands work' }); + } catch (e) { + checks.push({ name: 'Command execution', status: 'fail', detail: 'Commands failed to execute' }); + } + + // Display results + let passCount = 0, warnCount = 0, failCount = 0; + checks.forEach(c => { + const icon = c.status === 'pass' ? chalk.green('āœ“') : c.status === 'warn' ? chalk.yellow('⚠') : chalk.red('āœ—'); + const statusColor = c.status === 'pass' ? chalk.green : c.status === 'warn' ? chalk.yellow : chalk.red; + console.log(` ${icon} ${c.name}: ${statusColor(c.detail)}`); + if (c.status === 'pass') passCount++; + else if (c.status === 'warn') warnCount++; + else failCount++; + }); + + console.log(''); + if (failCount === 0) { + console.log(chalk.green(`āœ… Verification passed! ${passCount} checks passed, ${warnCount} warnings`)); + } else { + console.log(chalk.red(`āŒ Verification failed: ${failCount} issues found`)); + console.log(chalk.dim(' Run `npx ruvector hooks doctor` for detailed diagnostics')); + } + }); + +// Doctor - diagnose setup issues +hooksCmd.command('doctor') + .description('Diagnose and fix setup issues') + .option('--fix', 'Automatically fix issues') + .action((opts) => { + console.log(chalk.bold.cyan('\n🩺 RuVector Hooks Doctor\n')); + const issues = []; + const fixes = []; + + // Check settings file + const settingsPath = path.join(process.cwd(), '.claude', 'settings.json'); + if (!fs.existsSync(settingsPath)) { + issues.push({ severity: 'error', message: 'No .claude/settings.json found', fix: 'Run `npx ruvector hooks init`' }); + } else { + try { + const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); + + // Check for invalid schema + if (settings.$schema && !settings.$schema.includes('schemastore.org')) { + issues.push({ severity: 'warning', message: 'Invalid schema URL', fix: 'Will be corrected' }); + if (opts.fix) { + settings.$schema = 'https://json.schemastore.org/claude-code-settings.json'; + fixes.push('Fixed schema URL'); + } + } + + // Check for old hook names + if (settings.hooks?.Start || settings.hooks?.End) { + issues.push({ severity: 'error', message: 'Invalid hook names (Start/End)', fix: 'Should be SessionStart/Stop' }); + if (opts.fix) { + delete settings.hooks.Start; + delete settings.hooks.End; + fixes.push('Removed invalid hook names'); + } + } + + // Check hook format + const hookNames = ['PreToolUse', 'PostToolUse']; + hookNames.forEach(name => { + if (settings.hooks?.[name]) { + settings.hooks[name].forEach((hook, i) => { + if (typeof hook.matcher === 'object') { + issues.push({ severity: 'error', message: `${name}[${i}].matcher should be string, not object`, fix: 'Will be corrected' }); + } + }); + } + }); + + // Check for npx vs direct command + const checkCommands = (hooks) => { + if (!hooks) return; + hooks.forEach(h => { + h.hooks?.forEach(hh => { + if (hh.command && hh.command.includes('ruvector') && !hh.command.startsWith('npx ') && !hh.command.includes('/bin/')) { + issues.push({ severity: 'warning', message: `Command should use 'npx ruvector' for portability`, fix: 'Update to use npx' }); + } + }); + }); + }; + Object.values(settings.hooks || {}).forEach(checkCommands); + + // Save fixes + if (opts.fix && fixes.length > 0) { + fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2)); + } + } catch (e) { + issues.push({ severity: 'error', message: 'Invalid JSON in settings file', fix: 'Re-run `npx ruvector hooks init --force`' }); + } + } + + // Check .gitignore + const gitignorePath = path.join(process.cwd(), '.gitignore'); + if (fs.existsSync(gitignorePath)) { + const content = fs.readFileSync(gitignorePath, 'utf-8'); + if (!content.includes('.ruvector/')) { + issues.push({ severity: 'warning', message: '.ruvector/ not in .gitignore', fix: 'Add to prevent committing learning data' }); + if (opts.fix) { + fs.appendFileSync(gitignorePath, '\n# RuVector intelligence data\n.ruvector/\n'); + fixes.push('Added .ruvector/ to .gitignore'); + } + } + } + + // Display results + if (issues.length === 0) { + console.log(chalk.green(' āœ“ No issues found! Your setup looks healthy.')); + } else { + issues.forEach(i => { + const icon = i.severity === 'error' ? chalk.red('āœ—') : chalk.yellow('⚠'); + console.log(` ${icon} ${i.message}`); + console.log(chalk.dim(` Fix: ${i.fix}`)); + }); + + if (opts.fix && fixes.length > 0) { + console.log(chalk.green(`\nāœ… Applied ${fixes.length} fix(es):`)); + fixes.forEach(f => console.log(chalk.green(` • ${f}`))); + } else if (issues.some(i => i.severity === 'error')) { + console.log(chalk.yellow('\nšŸ’” Run with --fix to automatically fix issues')); + } + } + }); + +// Export intelligence data +hooksCmd.command('export') + .description('Export intelligence data for backup') + .option('-o, --output ', 'Output file path', 'ruvector-export.json') + .option('--include-all', 'Include all data (patterns, memories, trajectories)') + .action((opts) => { + const intel = new Intelligence(); + const exportData = { + version: '1.0', + exported_at: new Date().toISOString(), + patterns: intel.data?.patterns || {}, + memories: opts.includeAll ? (intel.data?.memories || []) : [], + trajectories: opts.includeAll ? (intel.data?.trajectories || []) : [], + errors: intel.data?.errors || {}, + stats: intel.stats() + }; + + const outputPath = path.resolve(opts.output); + fs.writeFileSync(outputPath, JSON.stringify(exportData, null, 2)); + + console.log(chalk.green(`āœ… Exported intelligence data to ${outputPath}`)); + console.log(chalk.dim(` ${Object.keys(exportData.patterns).length} patterns`)); + console.log(chalk.dim(` ${exportData.memories.length} memories`)); + console.log(chalk.dim(` ${exportData.trajectories.length} trajectories`)); + }); + +// Import intelligence data +hooksCmd.command('import') + .description('Import intelligence data from backup') + .argument('', 'Import file path') + .option('--merge', 'Merge with existing data (default: replace)') + .option('--dry-run', 'Show what would be imported without making changes') + .action((file, opts) => { + const importPath = path.resolve(file); + if (!fs.existsSync(importPath)) { + console.error(chalk.red(`āŒ File not found: ${importPath}`)); + process.exit(1); + } + + try { + const importData = JSON.parse(fs.readFileSync(importPath, 'utf-8')); + + if (!importData.version) { + console.error(chalk.red('āŒ Invalid export file (missing version)')); + process.exit(1); + } + + console.log(chalk.cyan(`šŸ“¦ Import file: ${file}`)); + console.log(chalk.dim(` Version: ${importData.version}`)); + console.log(chalk.dim(` Exported: ${importData.exported_at}`)); + console.log(chalk.dim(` Patterns: ${Object.keys(importData.patterns || {}).length}`)); + console.log(chalk.dim(` Memories: ${(importData.memories || []).length}`)); + console.log(chalk.dim(` Trajectories: ${(importData.trajectories || []).length}`)); + + if (opts.dryRun) { + console.log(chalk.yellow('\nāš ļø Dry run - no changes made')); + return; + } + + const intel = new Intelligence(); + + if (opts.merge) { + // Merge patterns + Object.assign(intel.data.patterns, importData.patterns || {}); + // Merge memories (deduplicate by content) + const existingContent = new Set((intel.data.memories || []).map(m => m.content)); + (importData.memories || []).forEach(m => { + if (!existingContent.has(m.content)) { + intel.data.memories.push(m); + } + }); + // Merge trajectories + intel.data.trajectories = (intel.data.trajectories || []).concat(importData.trajectories || []); + // Merge errors + Object.assign(intel.data.errors, importData.errors || {}); + console.log(chalk.green('āœ… Merged intelligence data')); + } else { + intel.data.patterns = importData.patterns || {}; + intel.data.memories = importData.memories || []; + intel.data.trajectories = importData.trajectories || []; + intel.data.errors = importData.errors || {}; + console.log(chalk.green('āœ… Replaced intelligence data')); + } + + intel.save(); + console.log(chalk.dim(' Data saved to .ruvector/intelligence.json')); + } catch (e) { + console.error(chalk.red(`āŒ Failed to import: ${e.message}`)); + process.exit(1); + } + }); + program.parse(); diff --git a/npm/packages/ruvector/package.json b/npm/packages/ruvector/package.json index 896ee2594..9afe2d9e7 100644 --- a/npm/packages/ruvector/package.json +++ b/npm/packages/ruvector/package.json @@ -1,6 +1,6 @@ { "name": "ruvector", - "version": "0.1.45", + "version": "0.1.46", "description": "High-performance vector database for Node.js with automatic native/WASM fallback", "main": "dist/index.js", "types": "dist/index.d.ts", From 2933eb2c6e83c5243ae9ccc141b4630aa6543f75 Mon Sep 17 00:00:00 2001 From: rUv Date: Tue, 30 Dec 2025 21:22:29 +0000 Subject: [PATCH 05/10] feat(hooks): add pretrain command + fix permissions deny pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New command: - hooks pretrain: Bootstrap intelligence by analyzing repository - Phase 1: Analyze file structure → agent routing patterns - Phase 2: Analyze git history → co-edit patterns - Phase 3: Create vector memories from key files - Phase 4: Build directory-agent mappings Options: --depth : Git history depth (default: 100) --workers : Parallel workers (default: 4) --skip-git: Skip git history analysis --skip-files: Skip file structure analysis --verbose: Show detailed progress Fix: - Removed fork bomb pattern from deny list (caused validation error) --- npm/packages/ruvector/bin/cli.js | 280 ++++++++++++++++++++++++++++- npm/packages/ruvector/package.json | 2 +- 2 files changed, 280 insertions(+), 2 deletions(-) diff --git a/npm/packages/ruvector/bin/cli.js b/npm/packages/ruvector/bin/cli.js index 8909bc414..e170451ea 100755 --- a/npm/packages/ruvector/bin/cli.js +++ b/npm/packages/ruvector/bin/cli.js @@ -2353,7 +2353,8 @@ hooksCmd.command('init') 'Bash(rm -rf /)', 'Bash(sudo rm:*)', 'Bash(chmod 777:*)', - 'Bash(:(){ :|:& };:)' + 'Bash(mkfs:*)', + 'Bash(dd if=/dev/zero:*)' ]; console.log(chalk.blue(' āœ“ Permissions configured (project-specific)')); } @@ -2996,4 +2997,281 @@ hooksCmd.command('import') } }); +// Pretrain - analyze repo and bootstrap learning with agent swarm +hooksCmd.command('pretrain') + .description('Pretrain intelligence by analyzing the repository with agent swarm') + .option('--depth ', 'Git history depth to analyze', '100') + .option('--workers ', 'Number of parallel analysis workers', '4') + .option('--skip-git', 'Skip git history analysis') + .option('--skip-files', 'Skip file structure analysis') + .option('--verbose', 'Show detailed progress') + .action(async (opts) => { + const { execSync, spawn } = require('child_process'); + console.log(chalk.bold.cyan('\n🧠 RuVector Pretrain - Repository Intelligence Bootstrap\n')); + + const intel = new Intelligence(); + const startTime = Date.now(); + const stats = { files: 0, patterns: 0, memories: 0, coedits: 0 }; + + // Agent types for different file patterns + const agentMapping = { + // Rust + '.rs': 'rust-developer', + 'Cargo.toml': 'rust-developer', + 'Cargo.lock': 'rust-developer', + // JavaScript/TypeScript + '.js': 'javascript-developer', + '.jsx': 'react-developer', + '.ts': 'typescript-developer', + '.tsx': 'react-developer', + '.mjs': 'javascript-developer', + '.cjs': 'javascript-developer', + 'package.json': 'node-developer', + // Python + '.py': 'python-developer', + 'requirements.txt': 'python-developer', + 'pyproject.toml': 'python-developer', + 'setup.py': 'python-developer', + // Go + '.go': 'go-developer', + 'go.mod': 'go-developer', + // Web + '.html': 'frontend-developer', + '.css': 'frontend-developer', + '.scss': 'frontend-developer', + '.vue': 'vue-developer', + '.svelte': 'svelte-developer', + // Config + '.json': 'config-specialist', + '.yaml': 'config-specialist', + '.yml': 'config-specialist', + '.toml': 'config-specialist', + // Docs + '.md': 'documentation-specialist', + '.mdx': 'documentation-specialist', + // Tests + '.test.js': 'test-engineer', + '.test.ts': 'test-engineer', + '.spec.js': 'test-engineer', + '.spec.ts': 'test-engineer', + '_test.go': 'test-engineer', + '_test.rs': 'test-engineer', + // DevOps + 'Dockerfile': 'devops-engineer', + 'docker-compose.yml': 'devops-engineer', + '.github/workflows': 'cicd-engineer', + 'Makefile': 'devops-engineer', + // SQL + '.sql': 'database-specialist', + }; + + // Phase 1: Analyze file structure + if (!opts.skipFiles) { + console.log(chalk.yellow('šŸ“ Phase 1: Analyzing file structure...\n')); + + try { + // Get all files in repo + const files = execSync('git ls-files 2>/dev/null || find . -type f -not -path "./.git/*" -not -path "./node_modules/*" -not -path "./target/*"', + { encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 }).trim().split('\n').filter(f => f); + + const filesByType = {}; + const dirPatterns = {}; + + files.forEach(file => { + stats.files++; + const ext = path.extname(file); + const basename = path.basename(file); + const dir = path.dirname(file); + + // Determine agent for this file + let agent = 'coder'; // default + if (agentMapping[basename]) { + agent = agentMapping[basename]; + } else if (agentMapping[ext]) { + agent = agentMapping[ext]; + } else if (file.includes('.test.') || file.includes('.spec.') || file.includes('_test.')) { + agent = 'test-engineer'; + } else if (file.includes('.github/workflows')) { + agent = 'cicd-engineer'; + } + + // Track file types + filesByType[ext] = (filesByType[ext] || 0) + 1; + + // Track directory patterns + const parts = dir.split('/'); + if (parts[0]) { + dirPatterns[parts[0]] = dirPatterns[parts[0]] || { count: 0, agents: {} }; + dirPatterns[parts[0]].count++; + dirPatterns[parts[0]].agents[agent] = (dirPatterns[parts[0]].agents[agent] || 0) + 1; + } + + // Create Q-learning pattern for this file type + const state = `edit:${ext || 'unknown'}`; + if (!intel.data.patterns[state]) { + intel.data.patterns[state] = {}; + } + intel.data.patterns[state][agent] = (intel.data.patterns[state][agent] || 0) + 0.5; + stats.patterns++; + }); + + // Log summary + if (opts.verbose) { + console.log(chalk.dim(' File types found:')); + Object.entries(filesByType).sort((a, b) => b[1] - a[1]).slice(0, 10).forEach(([ext, count]) => { + console.log(chalk.dim(` ${ext || '(no ext)'}: ${count} files`)); + }); + } + console.log(chalk.green(` āœ“ Analyzed ${stats.files} files`)); + console.log(chalk.green(` āœ“ Created ${Object.keys(intel.data.patterns).length} routing patterns`)); + + } catch (e) { + console.log(chalk.yellow(` ⚠ File analysis skipped: ${e.message}`)); + } + } + + // Phase 2: Analyze git history for co-edit patterns + if (!opts.skipGit) { + console.log(chalk.yellow('\nšŸ“œ Phase 2: Analyzing git history for co-edit patterns...\n')); + + try { + // Get commits with files changed + const gitLog = execSync( + `git log --name-only --pretty=format:"COMMIT:%H" -n ${opts.depth} 2>/dev/null`, + { encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 } + ); + + const commits = gitLog.split('COMMIT:').filter(c => c.trim()); + const coEditMap = {}; + + commits.forEach(commit => { + const lines = commit.trim().split('\n').filter(l => l && !l.startsWith('COMMIT:')); + const files = lines.slice(1).filter(f => f.trim()); // Skip the hash + + // Track which files are edited together + files.forEach(file1 => { + files.forEach(file2 => { + if (file1 !== file2) { + const key = [file1, file2].sort().join('|'); + coEditMap[key] = (coEditMap[key] || 0) + 1; + } + }); + }); + }); + + // Find strong co-edit patterns (files edited together 3+ times) + const strongPatterns = Object.entries(coEditMap) + .filter(([, count]) => count >= 3) + .sort((a, b) => b[1] - a[1]); + + // Store as sequence patterns + strongPatterns.slice(0, 100).forEach(([key, count]) => { + const [file1, file2] = key.split('|'); + if (!intel.data.sequences) intel.data.sequences = {}; + if (!intel.data.sequences[file1]) intel.data.sequences[file1] = []; + + const existing = intel.data.sequences[file1].find(s => s.file === file2); + if (existing) { + existing.score += count; + } else { + intel.data.sequences[file1].push({ file: file2, score: count }); + } + stats.coedits++; + }); + + console.log(chalk.green(` āœ“ Analyzed ${commits.length} commits`)); + console.log(chalk.green(` āœ“ Found ${strongPatterns.length} co-edit patterns`)); + + if (opts.verbose && strongPatterns.length > 0) { + console.log(chalk.dim(' Top co-edit patterns:')); + strongPatterns.slice(0, 5).forEach(([key, count]) => { + const [f1, f2] = key.split('|'); + console.log(chalk.dim(` ${path.basename(f1)} ↔ ${path.basename(f2)}: ${count} times`)); + }); + } + + } catch (e) { + console.log(chalk.yellow(` ⚠ Git analysis skipped: ${e.message}`)); + } + } + + // Phase 3: Create vector memories from important files + console.log(chalk.yellow('\nšŸ’¾ Phase 3: Creating vector memories from key files...\n')); + + try { + const importantFiles = [ + 'README.md', 'CLAUDE.md', 'package.json', 'Cargo.toml', + 'pyproject.toml', 'go.mod', '.claude/settings.json' + ]; + + for (const filename of importantFiles) { + const filePath = path.join(process.cwd(), filename); + if (fs.existsSync(filePath)) { + try { + const content = fs.readFileSync(filePath, 'utf-8').slice(0, 2000); // First 2KB + intel.data.memories = intel.data.memories || []; + intel.data.memories.push({ + content: `[${filename}] ${content.replace(/\n/g, ' ').slice(0, 500)}`, + type: 'project', + created: new Date().toISOString(), + embedding: intel.simpleEmbed ? intel.simpleEmbed(content) : null + }); + stats.memories++; + if (opts.verbose) console.log(chalk.dim(` āœ“ ${filename}`)); + } catch (e) { /* skip unreadable files */ } + } + } + + console.log(chalk.green(` āœ“ Created ${stats.memories} memory entries`)); + + } catch (e) { + console.log(chalk.yellow(` ⚠ Memory creation skipped: ${e.message}`)); + } + + // Phase 4: Analyze directory structure for agent recommendations + console.log(chalk.yellow('\nšŸ—‚ļø Phase 4: Building directory-agent mappings...\n')); + + try { + const dirs = execSync('find . -type d -maxdepth 2 -not -path "./.git*" -not -path "./node_modules*" -not -path "./target*" 2>/dev/null || echo "."', + { encoding: 'utf-8' }).trim().split('\n'); + + const dirAgentMap = {}; + dirs.forEach(dir => { + const name = path.basename(dir); + // Infer agent from directory name + if (['src', 'lib', 'core'].includes(name)) dirAgentMap[dir] = 'coder'; + else if (['test', 'tests', '__tests__', 'spec'].includes(name)) dirAgentMap[dir] = 'test-engineer'; + else if (['docs', 'documentation'].includes(name)) dirAgentMap[dir] = 'documentation-specialist'; + else if (['scripts', 'bin'].includes(name)) dirAgentMap[dir] = 'devops-engineer'; + else if (['components', 'views', 'pages'].includes(name)) dirAgentMap[dir] = 'frontend-developer'; + else if (['api', 'routes', 'handlers'].includes(name)) dirAgentMap[dir] = 'backend-developer'; + else if (['models', 'entities', 'schemas'].includes(name)) dirAgentMap[dir] = 'database-specialist'; + else if (['.github', '.gitlab', 'ci'].includes(name)) dirAgentMap[dir] = 'cicd-engineer'; + }); + + // Store directory patterns + intel.data.dirPatterns = dirAgentMap; + console.log(chalk.green(` āœ“ Mapped ${Object.keys(dirAgentMap).length} directories to agents`)); + + } catch (e) { + console.log(chalk.yellow(` ⚠ Directory analysis skipped: ${e.message}`)); + } + + // Save all learning data + intel.data.pretrained = { + date: new Date().toISOString(), + stats: stats + }; + intel.save(); + + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); + console.log(chalk.bold.green(`\nāœ… Pretrain complete in ${elapsed}s!\n`)); + console.log(chalk.cyan('Summary:')); + console.log(` šŸ“ ${stats.files} files analyzed`); + console.log(` 🧠 ${stats.patterns} agent routing patterns`); + console.log(` šŸ”— ${stats.coedits} co-edit patterns`); + console.log(` šŸ’¾ ${stats.memories} memory entries`); + console.log(chalk.dim('\nThe intelligence layer will now provide better recommendations.')); + }); + program.parse(); diff --git a/npm/packages/ruvector/package.json b/npm/packages/ruvector/package.json index 9afe2d9e7..c16bb1a15 100644 --- a/npm/packages/ruvector/package.json +++ b/npm/packages/ruvector/package.json @@ -1,6 +1,6 @@ { "name": "ruvector", - "version": "0.1.46", + "version": "0.1.47", "description": "High-performance vector database for Node.js with automatic native/WASM fallback", "main": "dist/index.js", "types": "dist/index.d.ts", From e6ef464df0223f626c550fd983ae113b6bc9d282 Mon Sep 17 00:00:00 2001 From: rUv Date: Tue, 30 Dec 2025 21:28:15 +0000 Subject: [PATCH 06/10] feat(hooks): add build-agents command and integrate pretrain/agents into init New command: - hooks build-agents: Generate optimized agent configs based on repo analysis - Focus modes: quality, speed, security, testing, fullstack - Detects languages (Rust, TypeScript, Python, Go) and frameworks (React, Vue) - Generates YAML/JSON/MD agent definitions with system prompts - Creates coordinator agent with routing rules - Outputs to .claude/agents/ Init enhancements: - --pretrain: Bootstrap intelligence after init - --build-agents [focus]: Generate agents after init Example: npx ruvector hooks init --pretrain --build-agents security --- npm/packages/ruvector/bin/cli.js | 338 ++++++++++++++++++++++++++++- npm/packages/ruvector/package.json | 2 +- 2 files changed, 337 insertions(+), 3 deletions(-) diff --git a/npm/packages/ruvector/bin/cli.js b/npm/packages/ruvector/bin/cli.js index e170451ea..a3a2cc0c8 100755 --- a/npm/packages/ruvector/bin/cli.js +++ b/npm/packages/ruvector/bin/cli.js @@ -2311,7 +2311,9 @@ hooksCmd.command('init') .option('--no-gitignore', 'Skip .gitignore update') .option('--no-mcp', 'Skip MCP server configuration') .option('--no-statusline', 'Skip statusLine configuration') - .action((opts) => { + .option('--pretrain', 'Run pretrain after init to bootstrap intelligence') + .option('--build-agents [focus]', 'Generate optimized agents (quality|speed|security|testing|fullstack)') + .action(async (opts) => { const settingsPath = path.join(process.cwd(), '.claude', 'settings.json'); const settingsDir = path.dirname(settingsPath); if (!fs.existsSync(settingsDir)) fs.mkdirSync(settingsDir, { recursive: true }); @@ -2573,7 +2575,35 @@ npx ruvector hooks init --force # Overwrite existing } console.log(chalk.green('\nāœ… RuVector hooks initialization complete!')); - console.log(chalk.dim(' Run `npx ruvector hooks verify` to test the setup')); + + // Run pretrain if requested + if (opts.pretrain) { + console.log(chalk.yellow('\nšŸ“š Running pretrain to bootstrap intelligence...\n')); + const { execSync } = require('child_process'); + try { + execSync('npx ruvector hooks pretrain', { stdio: 'inherit' }); + } catch (e) { + console.log(chalk.yellow('āš ļø Pretrain completed with warnings')); + } + } + + // Build agents if requested + if (opts.buildAgents) { + const focus = typeof opts.buildAgents === 'string' ? opts.buildAgents : 'quality'; + console.log(chalk.yellow(`\nšŸ—ļø Building optimized agents (focus: ${focus})...\n`)); + const { execSync } = require('child_process'); + try { + execSync(`npx ruvector hooks build-agents --focus ${focus} --include-prompts`, { stdio: 'inherit' }); + } catch (e) { + console.log(chalk.yellow('āš ļø Agent build completed with warnings')); + } + } + + if (!opts.pretrain && !opts.buildAgents) { + console.log(chalk.dim(' Run `npx ruvector hooks verify` to test the setup')); + console.log(chalk.dim(' Run `npx ruvector hooks pretrain` to bootstrap intelligence')); + console.log(chalk.dim(' Run `npx ruvector hooks build-agents` to generate optimized agents')); + } }); hooksCmd.command('stats').description('Show intelligence statistics').action(() => { @@ -3274,4 +3304,308 @@ hooksCmd.command('pretrain') console.log(chalk.dim('\nThe intelligence layer will now provide better recommendations.')); }); +// Agent Builder - generate optimized agent configs based on pretrain +hooksCmd.command('build-agents') + .description('Generate optimized agent configurations based on repository analysis') + .option('--focus ', 'Focus type: quality, speed, security, testing, fullstack', 'quality') + .option('--output ', 'Output directory', '.claude/agents') + .option('--format ', 'Format: yaml, json, md', 'yaml') + .option('--include-prompts', 'Include detailed system prompts') + .action((opts) => { + console.log(chalk.bold.cyan('\nšŸ—ļø RuVector Agent Builder\n')); + + const intel = new Intelligence(); + const outputDir = path.join(process.cwd(), opts.output); + + // Check if pretrained + if (!intel.data.pretrained && Object.keys(intel.data.patterns || {}).length === 0) { + console.log(chalk.yellow('āš ļø No pretrain data found. Running quick analysis...\n')); + // Quick file analysis + try { + const { execSync } = require('child_process'); + const files = execSync('git ls-files 2>/dev/null', { encoding: 'utf-8' }).trim().split('\n'); + files.forEach(f => { + const ext = path.extname(f); + intel.data.patterns = intel.data.patterns || {}; + intel.data.patterns[`edit:${ext}`] = intel.data.patterns[`edit:${ext}`] || {}; + }); + } catch (e) { /* continue without git */ } + } + + // Analyze patterns to determine relevant agents + const patterns = intel.data.patterns || {}; + const detectedLangs = new Set(); + const detectedFrameworks = new Set(); + + Object.keys(patterns).forEach(state => { + if (state.includes('.rs')) detectedLangs.add('rust'); + if (state.includes('.ts') || state.includes('.js')) detectedLangs.add('typescript'); + if (state.includes('.tsx') || state.includes('.jsx')) detectedFrameworks.add('react'); + if (state.includes('.py')) detectedLangs.add('python'); + if (state.includes('.go')) detectedLangs.add('go'); + if (state.includes('.vue')) detectedFrameworks.add('vue'); + if (state.includes('.sql')) detectedFrameworks.add('database'); + }); + + // Detect project type from files + const projectTypes = detectProjectType(); + + console.log(chalk.blue(` Detected languages: ${[...detectedLangs].join(', ') || 'generic'}`)); + console.log(chalk.blue(` Detected frameworks: ${[...detectedFrameworks].join(', ') || 'none'}`)); + console.log(chalk.blue(` Focus mode: ${opts.focus}\n`)); + + // Focus configurations + const focusConfigs = { + quality: { + description: 'Emphasizes code quality, best practices, and maintainability', + priorities: ['code-review', 'refactoring', 'documentation', 'testing'], + temperature: 0.3 + }, + speed: { + description: 'Optimized for rapid development and iteration', + priorities: ['implementation', 'prototyping', 'quick-fixes'], + temperature: 0.7 + }, + security: { + description: 'Security-first development with vulnerability awareness', + priorities: ['security-audit', 'input-validation', 'authentication', 'encryption'], + temperature: 0.2 + }, + testing: { + description: 'Test-driven development with comprehensive coverage', + priorities: ['unit-tests', 'integration-tests', 'e2e-tests', 'mocking'], + temperature: 0.4 + }, + fullstack: { + description: 'Balanced full-stack development capabilities', + priorities: ['frontend', 'backend', 'database', 'api-design'], + temperature: 0.5 + } + }; + + const focus = focusConfigs[opts.focus] || focusConfigs.quality; + + // Agent templates based on detected stack + const agents = []; + + // Core agents based on detected languages + if (detectedLangs.has('rust')) { + agents.push({ + name: 'rust-specialist', + type: 'rust-developer', + description: 'Rust development specialist for this codebase', + capabilities: ['cargo', 'unsafe-rust', 'async-rust', 'wasm', 'error-handling'], + focus: focus.priorities, + systemPrompt: opts.includePrompts ? `You are a Rust specialist for this project. +Focus on: memory safety, zero-cost abstractions, idiomatic Rust patterns. +Use cargo conventions, prefer Result over panic, leverage the type system. +${focus.description}` : null + }); + } + + if (detectedLangs.has('typescript')) { + agents.push({ + name: 'typescript-specialist', + type: 'typescript-developer', + description: 'TypeScript development specialist', + capabilities: ['types', 'generics', 'decorators', 'async-await', 'modules'], + focus: focus.priorities, + systemPrompt: opts.includePrompts ? `You are a TypeScript specialist for this project. +Focus on: strict typing, type inference, generic patterns, module organization. +Prefer type safety over any, use discriminated unions, leverage utility types. +${focus.description}` : null + }); + } + + if (detectedLangs.has('python')) { + agents.push({ + name: 'python-specialist', + type: 'python-developer', + description: 'Python development specialist', + capabilities: ['typing', 'async', 'testing', 'packaging', 'data-science'], + focus: focus.priorities, + systemPrompt: opts.includePrompts ? `You are a Python specialist for this project. +Focus on: type hints, PEP standards, pythonic idioms, virtual environments. +Use dataclasses, prefer pathlib, leverage context managers. +${focus.description}` : null + }); + } + + if (detectedLangs.has('go')) { + agents.push({ + name: 'go-specialist', + type: 'go-developer', + description: 'Go development specialist', + capabilities: ['goroutines', 'channels', 'interfaces', 'testing', 'modules'], + focus: focus.priorities, + systemPrompt: opts.includePrompts ? `You are a Go specialist for this project. +Focus on: simplicity, explicit error handling, goroutines, interface composition. +Follow Go conventions, use go fmt, prefer composition over inheritance. +${focus.description}` : null + }); + } + + // Framework-specific agents + if (detectedFrameworks.has('react')) { + agents.push({ + name: 'react-specialist', + type: 'react-developer', + description: 'React/Next.js development specialist', + capabilities: ['hooks', 'state-management', 'components', 'ssr', 'testing'], + focus: focus.priorities, + systemPrompt: opts.includePrompts ? `You are a React specialist for this project. +Focus on: functional components, hooks, state management, performance optimization. +Prefer composition, use memo wisely, follow React best practices. +${focus.description}` : null + }); + } + + if (detectedFrameworks.has('database')) { + agents.push({ + name: 'database-specialist', + type: 'database-specialist', + description: 'Database design and optimization specialist', + capabilities: ['schema-design', 'queries', 'indexing', 'migrations', 'orm'], + focus: focus.priorities, + systemPrompt: opts.includePrompts ? `You are a database specialist for this project. +Focus on: normalized schemas, efficient queries, proper indexing, data integrity. +Consider performance implications, use transactions appropriately. +${focus.description}` : null + }); + } + + // Focus-specific agents + if (opts.focus === 'testing' || opts.focus === 'quality') { + agents.push({ + name: 'test-architect', + type: 'test-engineer', + description: 'Testing and quality assurance specialist', + capabilities: ['unit-tests', 'integration-tests', 'mocking', 'coverage', 'tdd'], + focus: ['testing', 'quality', 'reliability'], + systemPrompt: opts.includePrompts ? `You are a testing specialist for this project. +Focus on: comprehensive test coverage, meaningful assertions, test isolation. +Write tests first when possible, mock external dependencies, aim for >80% coverage. +${focus.description}` : null + }); + } + + if (opts.focus === 'security') { + agents.push({ + name: 'security-auditor', + type: 'security-specialist', + description: 'Security audit and hardening specialist', + capabilities: ['vulnerability-scan', 'auth', 'encryption', 'input-validation', 'owasp'], + focus: ['security', 'compliance', 'hardening'], + systemPrompt: opts.includePrompts ? `You are a security specialist for this project. +Focus on: OWASP top 10, input validation, authentication, authorization, encryption. +Never trust user input, use parameterized queries, implement defense in depth. +${focus.description}` : null + }); + } + + // Add coordinator agent + agents.push({ + name: 'project-coordinator', + type: 'coordinator', + description: 'Coordinates multi-agent workflows for this project', + capabilities: ['task-decomposition', 'agent-routing', 'context-management'], + focus: focus.priorities, + routes: agents.filter(a => a.name !== 'project-coordinator').map(a => ({ + pattern: a.capabilities[0], + agent: a.name + })) + }); + + // Create output directory + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + // Generate agent files + agents.forEach(agent => { + let content; + const filename = `${agent.name}.${opts.format}`; + const filepath = path.join(outputDir, filename); + + if (opts.format === 'yaml') { + const yaml = [ + `# Auto-generated by RuVector Agent Builder`, + `# Focus: ${opts.focus}`, + `# Generated: ${new Date().toISOString()}`, + ``, + `name: ${agent.name}`, + `type: ${agent.type}`, + `description: ${agent.description}`, + ``, + `capabilities:`, + ...agent.capabilities.map(c => ` - ${c}`), + ``, + `focus:`, + ...agent.focus.map(f => ` - ${f}`), + ]; + if (agent.systemPrompt) { + yaml.push(``, `system_prompt: |`); + agent.systemPrompt.split('\n').forEach(line => yaml.push(` ${line}`)); + } + if (agent.routes) { + yaml.push(``, `routes:`); + agent.routes.forEach(r => yaml.push(` - pattern: "${r.pattern}"`, ` agent: ${r.agent}`)); + } + content = yaml.join('\n'); + } else if (opts.format === 'json') { + content = JSON.stringify(agent, null, 2); + } else { + // Markdown format + content = [ + `# ${agent.name}`, + ``, + `**Type:** ${agent.type}`, + `**Description:** ${agent.description}`, + ``, + `## Capabilities`, + ...agent.capabilities.map(c => `- ${c}`), + ``, + `## Focus Areas`, + ...agent.focus.map(f => `- ${f}`), + ].join('\n'); + if (agent.systemPrompt) { + content += `\n\n## System Prompt\n\n\`\`\`\n${agent.systemPrompt}\n\`\`\``; + } + } + + fs.writeFileSync(filepath, content); + console.log(chalk.green(` āœ“ Created ${filename}`)); + }); + + // Create index file + const indexContent = opts.format === 'yaml' + ? `# RuVector Agent Configuration\n# Focus: ${opts.focus}\n\nagents:\n${agents.map(a => ` - ${a.name}`).join('\n')}` + : JSON.stringify({ focus: opts.focus, agents: agents.map(a => a.name) }, null, 2); + + fs.writeFileSync(path.join(outputDir, `index.${opts.format === 'md' ? 'json' : opts.format}`), indexContent); + + // Update settings to reference agents + const settingsPath = path.join(process.cwd(), '.claude', 'settings.json'); + if (fs.existsSync(settingsPath)) { + try { + const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); + settings.agentConfig = { + directory: opts.output, + focus: opts.focus, + agents: agents.map(a => a.name), + generated: new Date().toISOString() + }; + fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2)); + console.log(chalk.blue('\n āœ“ Updated .claude/settings.json with agent config')); + } catch (e) { /* ignore settings errors */ } + } + + console.log(chalk.bold.green(`\nāœ… Generated ${agents.length} optimized agents in ${opts.output}/\n`)); + console.log(chalk.cyan('Agents created:')); + agents.forEach(a => { + console.log(` šŸ¤– ${chalk.bold(a.name)}: ${a.description}`); + }); + console.log(chalk.dim(`\nFocus mode "${opts.focus}": ${focus.description}`)); + }); + program.parse(); diff --git a/npm/packages/ruvector/package.json b/npm/packages/ruvector/package.json index c16bb1a15..33f63d803 100644 --- a/npm/packages/ruvector/package.json +++ b/npm/packages/ruvector/package.json @@ -1,6 +1,6 @@ { "name": "ruvector", - "version": "0.1.47", + "version": "0.1.48", "description": "High-performance vector database for Node.js with automatic native/WASM fallback", "main": "dist/index.js", "types": "dist/index.d.ts", From 1a5d62e20af63324229c8289b5d5206d927ddfa2 Mon Sep 17 00:00:00 2001 From: rUv Date: Tue, 30 Dec 2025 21:40:23 +0000 Subject: [PATCH 07/10] fix(hooks): use project-local storage for intelligence data - Intelligence now saves to .ruvector/intelligence.json in project dir - Falls back to ~/.ruvector/ only if no project context found - Prefers project-local when .ruvector/ or .claude/ exists - Fixes verify showing 'will be created' after pretrain --- npm/packages/ruvector/bin/cli.js | 32 ++++++++++++++++++++++++------ npm/packages/ruvector/package.json | 2 +- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/npm/packages/ruvector/bin/cli.js b/npm/packages/ruvector/bin/cli.js index a3a2cc0c8..c762cd977 100755 --- a/npm/packages/ruvector/bin/cli.js +++ b/npm/packages/ruvector/bin/cli.js @@ -2095,19 +2095,39 @@ program // Self-Learning Intelligence Hooks // ============================================ -const INTEL_PATH = path.join(require('os').homedir(), '.ruvector', 'intelligence.json'); - class Intelligence { constructor() { + this.intelPath = this.getIntelPath(); this.data = this.load(); this.alpha = 0.1; this.lastEditedFile = null; } + // Prefer project-local storage, fall back to home directory + getIntelPath() { + const projectPath = path.join(process.cwd(), '.ruvector', 'intelligence.json'); + const homePath = path.join(require('os').homedir(), '.ruvector', 'intelligence.json'); + + // If project .ruvector exists, use it + if (fs.existsSync(path.dirname(projectPath))) { + return projectPath; + } + // If project .claude exists (hooks initialized), prefer project-local + if (fs.existsSync(path.join(process.cwd(), '.claude'))) { + return projectPath; + } + // If home .ruvector exists with data, use it + if (fs.existsSync(homePath)) { + return homePath; + } + // Default to project-local for new setups + return projectPath; + } + load() { try { - if (fs.existsSync(INTEL_PATH)) { - return JSON.parse(fs.readFileSync(INTEL_PATH, 'utf-8')); + if (fs.existsSync(this.intelPath)) { + return JSON.parse(fs.readFileSync(this.intelPath, 'utf-8')); } } catch {} return { @@ -2123,9 +2143,9 @@ class Intelligence { } save() { - const dir = path.dirname(INTEL_PATH); + const dir = path.dirname(this.intelPath); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); - fs.writeFileSync(INTEL_PATH, JSON.stringify(this.data, null, 2)); + fs.writeFileSync(this.intelPath, JSON.stringify(this.data, null, 2)); } now() { return Math.floor(Date.now() / 1000); } diff --git a/npm/packages/ruvector/package.json b/npm/packages/ruvector/package.json index 33f63d803..d43eea7d7 100644 --- a/npm/packages/ruvector/package.json +++ b/npm/packages/ruvector/package.json @@ -1,6 +1,6 @@ { "name": "ruvector", - "version": "0.1.48", + "version": "0.1.49", "description": "High-performance vector database for Node.js with automatic native/WASM fallback", "main": "dist/index.js", "types": "dist/index.d.ts", From a761924bde7a96cd7c97d72a32f1f5c2237e6c4a Mon Sep 17 00:00:00 2001 From: rUv Date: Tue, 30 Dec 2025 21:51:37 +0000 Subject: [PATCH 08/10] docs: add Claude Code hooks section to README and create HOOKS.md - Added hooks feature summary near top of README.md - Created comprehensive HOOKS.md documentation - Links to detailed docs for pretrain, build-agents, verify, etc. --- npm/packages/ruvector/HOOKS.md | 221 +++++++++++++++++++++++++++++ npm/packages/ruvector/README.md | 20 +++ npm/packages/ruvector/package.json | 2 +- 3 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 npm/packages/ruvector/HOOKS.md diff --git a/npm/packages/ruvector/HOOKS.md b/npm/packages/ruvector/HOOKS.md new file mode 100644 index 000000000..7ad692031 --- /dev/null +++ b/npm/packages/ruvector/HOOKS.md @@ -0,0 +1,221 @@ +# RuVector Hooks for Claude Code + +Self-learning intelligence hooks that enhance Claude Code with Q-learning, vector memory, and automatic agent routing. + +## Quick Start + +```bash +# Full setup: hooks + pretrain + optimized agents +npx ruvector hooks init --pretrain --build-agents quality + +# Or step by step: +npx ruvector hooks init # Setup hooks +npx ruvector hooks pretrain # Analyze repository +npx ruvector hooks build-agents # Generate agent configs +``` + +## What It Does + +RuVector hooks integrate with Claude Code to provide: + +| Feature | Description | +|---------|-------------| +| **Agent Routing** | Suggests the best agent for each file type based on learned patterns | +| **Co-edit Patterns** | Predicts "likely next files" from git history | +| **Vector Memory** | Semantic recall of project context | +| **Command Analysis** | Risk assessment for bash commands | +| **Self-Learning** | Q-learning improves suggestions over time | + +## Commands + +### Initialization + +```bash +# Full configuration +npx ruvector hooks init + +# With pretrain and agent building +npx ruvector hooks init --pretrain --build-agents security + +# Minimal (basic hooks only) +npx ruvector hooks init --minimal + +# Options +--force # Overwrite existing settings +--minimal # Basic hooks only +--pretrain # Run pretrain after init +--build-agents # Generate optimized agents (quality|speed|security|testing|fullstack) +--no-claude-md # Skip CLAUDE.md creation +--no-permissions # Skip permissions config +--no-env # Skip environment variables +--no-gitignore # Skip .gitignore update +--no-mcp # Skip MCP server config +--no-statusline # Skip status line config +``` + +### Pretrain + +Analyze your repository to bootstrap intelligence: + +```bash +npx ruvector hooks pretrain + +# Options +--depth # Git history depth (default: 100) +--verbose # Show detailed progress +--skip-git # Skip git history analysis +--skip-files # Skip file structure analysis +``` + +**What it learns:** +- File type → Agent mapping (`.rs` → rust-developer) +- Co-edit patterns from git history +- Directory → Agent mapping +- Project context memories + +### Build Agents + +Generate optimized `.claude/agents/` configurations: + +```bash +npx ruvector hooks build-agents --focus quality + +# Focus modes +--focus quality # Code quality, best practices (default) +--focus speed # Rapid development, prototyping +--focus security # OWASP, input validation, encryption +--focus testing # TDD, comprehensive coverage +--focus fullstack # Balanced frontend/backend/database + +# Options +--output # Output directory (default: .claude/agents) +--format # yaml, json, or md (default: yaml) +--include-prompts # Include system prompts in agent configs +``` + +### Verification & Diagnostics + +```bash +# Check if hooks are working +npx ruvector hooks verify + +# Diagnose and fix issues +npx ruvector hooks doctor +npx ruvector hooks doctor --fix +``` + +### Data Management + +```bash +# View statistics +npx ruvector hooks stats + +# Export intelligence data +npx ruvector hooks export -o backup.json +npx ruvector hooks export --include-all + +# Import intelligence data +npx ruvector hooks import backup.json +npx ruvector hooks import backup.json --merge +``` + +### Memory Operations + +```bash +# Store context in vector memory +npx ruvector hooks remember "API uses JWT auth" -t project + +# Semantic search memory +npx ruvector hooks recall "authentication" + +# Route a task to best agent +npx ruvector hooks route "implement user login" +``` + +## Hook Events + +| Event | Trigger | RuVector Action | +|-------|---------|-----------------| +| **PreToolUse** | Before Edit/Write/Bash | Agent routing, file analysis, command risk | +| **PostToolUse** | After Edit/Write/Bash | Q-learning update, pattern recording | +| **SessionStart** | Conversation begins | Load intelligence, display stats | +| **Stop** | Conversation ends | Save learning data | +| **UserPromptSubmit** | User sends message | Context suggestions | +| **PreCompact** | Before context compaction | Preserve important context | +| **Notification** | Any notification | Track events for learning | + +## Generated Files + +After running `hooks init`: + +``` +your-project/ +ā”œā”€ā”€ .claude/ +│ ā”œā”€ā”€ settings.json # Hooks configuration +│ ā”œā”€ā”€ statusline.sh # Status bar script +│ └── agents/ # Generated agents (with --build-agents) +│ ā”œā”€ā”€ rust-specialist.yaml +│ ā”œā”€ā”€ typescript-specialist.yaml +│ ā”œā”€ā”€ test-architect.yaml +│ └── project-coordinator.yaml +ā”œā”€ā”€ .ruvector/ +│ └── intelligence.json # Learning data +ā”œā”€ā”€ CLAUDE.md # Project documentation +└── .gitignore # Updated with .ruvector/ +``` + +## Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `RUVECTOR_INTELLIGENCE_ENABLED` | `true` | Enable/disable intelligence | +| `RUVECTOR_LEARNING_RATE` | `0.1` | Q-learning rate (0.0-1.0) | +| `RUVECTOR_MEMORY_BACKEND` | `rvlite` | Memory storage backend | +| `INTELLIGENCE_MODE` | `treatment` | A/B testing mode | + +## Example Output + +### Agent Routing +``` +🧠 Intelligence Analysis: + šŸ“ src/api/routes.ts + šŸ¤– Recommended: typescript-developer (85% confidence) + → learned from 127 .ts files in repo + šŸ“Ž Likely next files: + - src/api/handlers.ts (12 co-edits) + - src/types/api.ts (8 co-edits) +``` + +### Command Analysis +``` +🧠 Command Analysis: + šŸ“¦ Category: rust + šŸ·ļø Type: test + āœ… Risk: LOW +``` + +## Best Practices + +1. **Run pretrain on existing repos** — Bootstrap intelligence before starting work +2. **Use focus modes** — Match agent generation to your current task +3. **Export before major changes** — Backup learning data +4. **Let it learn** — Intelligence improves with each edit + +## Troubleshooting + +```bash +# Check setup +npx ruvector hooks verify + +# Fix common issues +npx ruvector hooks doctor --fix + +# Reset and reinitialize +npx ruvector hooks init --force --pretrain +``` + +## Links + +- [RuVector GitHub](https://github.com/ruvnet/ruvector) +- [npm Package](https://www.npmjs.com/package/ruvector) +- [Claude Code Documentation](https://docs.anthropic.com/claude-code) diff --git a/npm/packages/ruvector/README.md b/npm/packages/ruvector/README.md index ef1adeb66..d715a0f8d 100644 --- a/npm/packages/ruvector/README.md +++ b/npm/packages/ruvector/README.md @@ -20,6 +20,26 @@ Built by [rUv](https://ruv.io) with production-grade Rust performance and intell --- +## 🧠 Claude Code Hooks (NEW) + +**Self-learning intelligence for Claude Code** — RuVector provides optimized hooks that learn from your development patterns. + +```bash +# One-command setup with pretrain and agent generation +npx ruvector hooks init --pretrain --build-agents quality +``` + +**Features:** +- šŸŽÆ **Smart Agent Routing** — Automatically suggests the best agent for each file type +- šŸ“š **Repository Pretrain** — Analyzes your codebase to bootstrap intelligence +- šŸ¤– **Agent Builder** — Generates optimized `.claude/agents/` configs for your stack +- šŸ”— **Co-edit Patterns** — Learns which files are edited together from git history +- šŸ’¾ **Vector Memory** — Semantic recall of project context + +šŸ“– **[Full Hooks Documentation →](https://github.com/ruvnet/ruvector/blob/main/npm/packages/ruvector/HOOKS.md)** + +--- + ## 🌟 Why Ruvector? ### The Problem with Existing Vector Databases diff --git a/npm/packages/ruvector/package.json b/npm/packages/ruvector/package.json index d43eea7d7..9e3232764 100644 --- a/npm/packages/ruvector/package.json +++ b/npm/packages/ruvector/package.json @@ -1,6 +1,6 @@ { "name": "ruvector", - "version": "0.1.49", + "version": "0.1.50", "description": "High-performance vector database for Node.js with automatic native/WASM fallback", "main": "dist/index.js", "types": "dist/index.d.ts", From 738571ff61d4b01d851d1490d5b288c8cba3d1a8 Mon Sep 17 00:00:00 2001 From: rUv Date: Tue, 30 Dec 2025 22:22:49 +0000 Subject: [PATCH 09/10] feat(mcp): add MCP server for Claude Code integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Model Context Protocol (MCP) server with stdio transport for seamless Claude Code integration. Provides 10 self-learning intelligence tools via JSON-RPC protocol. New commands: - `npx ruvector mcp start` - Start MCP server - `npx ruvector mcp info` - Show setup instructions MCP Tools: - hooks_stats - Get intelligence statistics - hooks_route - Route task to best agent - hooks_remember - Store context in vector memory - hooks_recall - Search vector memory semantically - hooks_init - Initialize hooks in project - hooks_pretrain - Pretrain from repository - hooks_build_agents - Generate agent configs - hooks_verify - Verify hooks configuration - hooks_doctor - Diagnose setup issues - hooks_export - Export intelligence data MCP Resources: - ruvector://intelligence/stats - ruvector://intelligence/patterns - ruvector://intelligence/memories Setup: claude mcp add ruvector npx ruvector mcp start šŸ¤– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- npm/package-lock.json | 103 +++-- npm/packages/ruvector/bin/cli.js | 55 +++ npm/packages/ruvector/bin/mcp-server.js | 581 ++++++++++++++++++++++++ npm/packages/ruvector/package.json | 3 +- 4 files changed, 693 insertions(+), 49 deletions(-) create mode 100644 npm/packages/ruvector/bin/mcp-server.js diff --git a/npm/package-lock.json b/npm/package-lock.json index bea6a57c6..5d4576f69 100644 --- a/npm/package-lock.json +++ b/npm/package-lock.json @@ -3897,6 +3897,10 @@ "resolved": "packages/router-win32-x64-msvc", "link": true }, + "node_modules/@ruvector/rudag": { + "resolved": "packages/rudag", + "link": true + }, "node_modules/@ruvector/ruvllm": { "resolved": "packages/ruvllm", "link": true @@ -10397,6 +10401,11 @@ "node": ">=0.10.0" } }, + "node_modules/idb": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.3.tgz", + "integrity": "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -14336,84 +14345,64 @@ "link": true }, "node_modules/ruvector-core-darwin-arm64": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/ruvector-core-darwin-arm64/-/ruvector-core-darwin-arm64-0.1.25.tgz", - "integrity": "sha512-5RmOAko4naiiL9TiVwfK2xH75qQk/FuknE3hcmKusZ0Z+xEEbF6NMLIQyZ6NfnhysQ4U8yfq8I8g9wPWz/Fbuw==", + "version": "0.1.29", + "resolved": "https://registry.npmjs.org/ruvector-core-darwin-arm64/-/ruvector-core-darwin-arm64-0.1.29.tgz", + "integrity": "sha512-gjZ1/J/0Nh9Mn74VdifIIkPLP/M4FqD/g+QVxWcfWcNFWhHVz+zHyxGjc6gJgrfYBquiMyP5jLfvyR3TffLanQ==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "darwin" - ], - "engines": { - "node": ">= 18" - } + ] }, "node_modules/ruvector-core-darwin-x64": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/ruvector-core-darwin-x64/-/ruvector-core-darwin-x64-0.1.25.tgz", - "integrity": "sha512-Op5KdgVlyN4WA9yqs+mVFovGiaG1rroeCMe/1nHxViQ2w77ELFuc/lQm//XbJY6YnBEsG6n+oi9NFc8AlpM9qg==", + "version": "0.1.29", + "resolved": "https://registry.npmjs.org/ruvector-core-darwin-x64/-/ruvector-core-darwin-x64-0.1.29.tgz", + "integrity": "sha512-SNq2DrIBWM53qG3YSYcNV/BnBbAoJouafAADOjG3PkM8+RPrIucTeUDBavf148DNo5ZI337IS8TK1/0HpJEwFg==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "darwin" - ], - "engines": { - "node": ">= 18" - } + ] }, "node_modules/ruvector-core-linux-arm64-gnu": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/ruvector-core-linux-arm64-gnu/-/ruvector-core-linux-arm64-gnu-0.1.25.tgz", - "integrity": "sha512-WvQ2hk/LVY1R8ZAdkt4FwW0mLlef2Vtdw7o5yPi9eHUfjv7uMCaXEpJgUvZHitb+CQhr7cvP6XDGFOu4x26eXA==", + "version": "0.1.29", + "resolved": "https://registry.npmjs.org/ruvector-core-linux-arm64-gnu/-/ruvector-core-linux-arm64-gnu-0.1.29.tgz", + "integrity": "sha512-gcA2qSQD9nEeHR8pIXr5SKpQAiHMxu4EyBUwUSG4UnWOxVQnnU0l2kA/Z2NZ6B+JWLrb5+nhkkv7AaSmb8YAsg==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 18" - } + ] }, "node_modules/ruvector-core-linux-x64-gnu": { - "version": "0.1.26", - "resolved": "https://registry.npmjs.org/ruvector-core-linux-x64-gnu/-/ruvector-core-linux-x64-gnu-0.1.26.tgz", - "integrity": "sha512-hiGlJwANFBUctqnausPivOaGUKQKr4xFwCWKYqBMh1B1CphFSpy8wm+06A3Yv0mWSGYkKs0hfo+lQNJXltOHjw==", + "version": "0.1.29", + "resolved": "https://registry.npmjs.org/ruvector-core-linux-x64-gnu/-/ruvector-core-linux-x64-gnu-0.1.29.tgz", + "integrity": "sha512-GcYCVNRbAmiXmEaMNvVnA78ZIM469H0VwP2JFGfibwiSASBgWGX3BT+mqflX+gtBynbtQqslj8XcqVdDxEkEBg==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">= 18" - } + ] }, "node_modules/ruvector-core-win32-x64-msvc": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/ruvector-core-win32-x64-msvc/-/ruvector-core-win32-x64-msvc-0.1.25.tgz", - "integrity": "sha512-Wm7M7Pcy/wfBisuRBwPlg0pbVzMXL9/aRfSc56/+hfauLSaI4nkiZ8MiqEbE/g1VM73BzDSm2+SxjM2omhuIyQ==", + "version": "0.1.29", + "resolved": "https://registry.npmjs.org/ruvector-core-win32-x64-msvc/-/ruvector-core-win32-x64-msvc-0.1.29.tgz", + "integrity": "sha512-nqHrlUAKpTreGO87jQLtFVrQUBT/7J/dBsPD5/mV9Fet/shncN0QMij7YqBTrlhR9qtQ2gAUGrG0zw69T60AiQ==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "win32" - ], - "engines": { - "node": ">= 18" - } + ] }, "node_modules/ruvector-extensions": { "resolved": "packages/ruvector-extensions", @@ -17589,7 +17578,7 @@ }, "packages/cli": { "name": "@ruvector/cli", - "version": "0.1.26", + "version": "0.1.28", "license": "MIT", "dependencies": { "commander": "^12.0.0" @@ -17617,7 +17606,7 @@ }, "packages/core": { "name": "@ruvector/core", - "version": "0.1.28", + "version": "0.1.29", "license": "MIT", "devDependencies": { "@napi-rs/cli": "^2.18.0" @@ -17626,11 +17615,11 @@ "node": ">=18.0.0" }, "optionalDependencies": { - "ruvector-core-darwin-arm64": "0.1.25", - "ruvector-core-darwin-x64": "0.1.25", - "ruvector-core-linux-arm64-gnu": "0.1.25", - "ruvector-core-linux-x64-gnu": "0.1.26", - "ruvector-core-win32-x64-msvc": "0.1.25" + "ruvector-core-darwin-arm64": "0.1.29", + "ruvector-core-darwin-x64": "0.1.29", + "ruvector-core-linux-arm64-gnu": "0.1.29", + "ruvector-core-linux-x64-gnu": "0.1.29", + "ruvector-core-win32-x64-msvc": "0.1.29" } }, "packages/graph-data-generator": { @@ -18242,10 +18231,28 @@ "node": ">=18.0.0" } }, + "packages/rudag": { + "version": "0.1.0", + "license": "MIT OR Apache-2.0", + "dependencies": { + "idb": "^8.0.0" + }, + "bin": { + "rudag": "bin/cli.js" + }, + "devDependencies": { + "@types/node": "^20.19.25", + "typescript": "^5.9.3" + }, + "engines": { + "node": ">= 16" + } + }, "packages/ruvector": { - "version": "0.1.35", + "version": "0.1.50", "license": "MIT", "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.0", "@ruvector/attention": "^0.1.3", "@ruvector/core": "^0.1.25", "@ruvector/gnn": "^0.1.22", diff --git a/npm/packages/ruvector/bin/cli.js b/npm/packages/ruvector/bin/cli.js index c762cd977..df07b1f0c 100755 --- a/npm/packages/ruvector/bin/cli.js +++ b/npm/packages/ruvector/bin/cli.js @@ -3628,4 +3628,59 @@ ${focus.description}` : null console.log(chalk.dim(`\nFocus mode "${opts.focus}": ${focus.description}`)); }); +// MCP Server command +const mcpCmd = program.command('mcp').description('MCP (Model Context Protocol) server for Claude Code integration'); + +mcpCmd.command('start') + .description('Start the RuVector MCP server') + .action(() => { + // Execute the mcp-server.js directly + const mcpServerPath = path.join(__dirname, 'mcp-server.js'); + if (!fs.existsSync(mcpServerPath)) { + console.error(chalk.red('Error: MCP server not found at'), mcpServerPath); + process.exit(1); + } + require(mcpServerPath); + }); + +mcpCmd.command('info') + .description('Show MCP server information and setup instructions') + .action(() => { + console.log(chalk.bold.cyan('\nšŸ”Œ RuVector MCP Server\n')); + console.log(chalk.white('The RuVector MCP server provides self-learning intelligence')); + console.log(chalk.white('tools to Claude Code via the Model Context Protocol.\n')); + + console.log(chalk.bold('Available Tools:')); + console.log(chalk.dim(' hooks_stats - Get intelligence statistics')); + console.log(chalk.dim(' hooks_route - Route task to best agent')); + console.log(chalk.dim(' hooks_remember - Store context in vector memory')); + console.log(chalk.dim(' hooks_recall - Search vector memory')); + console.log(chalk.dim(' hooks_init - Initialize hooks in project')); + console.log(chalk.dim(' hooks_pretrain - Pretrain from repository')); + console.log(chalk.dim(' hooks_build_agents - Generate agent configs')); + console.log(chalk.dim(' hooks_verify - Verify hooks configuration')); + console.log(chalk.dim(' hooks_doctor - Diagnose setup issues')); + console.log(chalk.dim(' hooks_export - Export intelligence data')); + + console.log(chalk.bold('\nšŸ“¦ Resources:')); + console.log(chalk.dim(' ruvector://intelligence/stats - Current statistics')); + console.log(chalk.dim(' ruvector://intelligence/patterns - Learned patterns')); + console.log(chalk.dim(' ruvector://intelligence/memories - Vector memories')); + + console.log(chalk.bold.yellow('\nāš™ļø Setup Instructions:\n')); + console.log(chalk.white('Add to Claude Code:')); + console.log(chalk.cyan(' claude mcp add ruvector npx ruvector mcp start\n')); + + console.log(chalk.white('Or add to .claude/settings.json:')); + console.log(chalk.dim(` { + "mcpServers": { + "ruvector": { + "command": "npx", + "args": ["ruvector", "mcp", "start"] + } + } + }`)); + console.log(); + }); + program.parse(); diff --git a/npm/packages/ruvector/bin/mcp-server.js b/npm/packages/ruvector/bin/mcp-server.js new file mode 100644 index 000000000..702555652 --- /dev/null +++ b/npm/packages/ruvector/bin/mcp-server.js @@ -0,0 +1,581 @@ +#!/usr/bin/env node + +/** + * RuVector MCP Server + * + * Model Context Protocol server for RuVector hooks + * Provides self-learning intelligence tools for Claude Code + * + * Usage: + * npx ruvector mcp start + * claude mcp add ruvector npx ruvector mcp start + */ + +const { Server } = require('@modelcontextprotocol/sdk/server/index.js'); +const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js'); +const { + CallToolRequestSchema, + ListToolsRequestSchema, + ListResourcesRequestSchema, + ReadResourceRequestSchema, +} = require('@modelcontextprotocol/sdk/types.js'); +const path = require('path'); +const fs = require('fs'); +const { execSync } = require('child_process'); + +// Intelligence class (simplified from cli.js) +class Intelligence { + constructor() { + this.intelPath = this.getIntelPath(); + this.data = this.load(); + } + + getIntelPath() { + const projectPath = path.join(process.cwd(), '.ruvector', 'intelligence.json'); + const homePath = path.join(require('os').homedir(), '.ruvector', 'intelligence.json'); + if (fs.existsSync(path.dirname(projectPath))) return projectPath; + if (fs.existsSync(path.join(process.cwd(), '.claude'))) return projectPath; + if (fs.existsSync(homePath)) return homePath; + return projectPath; + } + + load() { + try { + if (fs.existsSync(this.intelPath)) { + return JSON.parse(fs.readFileSync(this.intelPath, 'utf-8')); + } + } catch {} + return { patterns: {}, memories: [], trajectories: [], errors: {}, agents: {}, edges: [] }; + } + + save() { + const dir = path.dirname(this.intelPath); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(this.intelPath, JSON.stringify(this.data, null, 2)); + } + + stats() { + return { + total_patterns: Object.keys(this.data.patterns || {}).length, + total_memories: (this.data.memories || []).length, + total_trajectories: (this.data.trajectories || []).length, + total_errors: Object.keys(this.data.errors || {}).length + }; + } + + embed(text) { + const embedding = new Array(64).fill(0); + for (let i = 0; i < text.length; i++) { + const idx = (text.charCodeAt(i) + i * 7) % 64; + embedding[idx] += 1.0; + } + const norm = Math.sqrt(embedding.reduce((a, b) => a + b * b, 0)); + if (norm > 0) for (let i = 0; i < embedding.length; i++) embedding[i] /= norm; + return embedding; + } + + similarity(a, b) { + if (!a || !b || a.length !== b.length) return 0; + const dot = a.reduce((sum, v, i) => sum + v * b[i], 0); + const normA = Math.sqrt(a.reduce((sum, v) => sum + v * v, 0)); + const normB = Math.sqrt(b.reduce((sum, v) => sum + v * v, 0)); + return normA > 0 && normB > 0 ? dot / (normA * normB) : 0; + } + + remember(content, type = 'general') { + this.data.memories = this.data.memories || []; + this.data.memories.push({ + content, + type, + created: new Date().toISOString(), + embedding: this.embed(content) + }); + this.save(); + return { stored: true, total: this.data.memories.length }; + } + + recall(query, topK = 5) { + const queryEmbed = this.embed(query); + const scored = (this.data.memories || []).map((m, i) => ({ + ...m, + index: i, + score: this.similarity(queryEmbed, m.embedding) + })); + return scored.sort((a, b) => b.score - a.score).slice(0, topK); + } + + route(task, file = null) { + const ext = file ? path.extname(file) : ''; + const state = `edit:${ext || 'unknown'}`; + const actions = this.data.patterns[state] || {}; + + // Default agent mapping + const defaults = { + '.rs': 'rust-developer', + '.ts': 'typescript-developer', + '.tsx': 'react-developer', + '.js': 'javascript-developer', + '.py': 'python-developer', + '.go': 'go-developer', + '.sql': 'database-specialist' + }; + + let bestAgent = defaults[ext] || 'coder'; + let bestScore = 0.5; + + for (const [agent, score] of Object.entries(actions)) { + if (score > bestScore) { + bestAgent = agent; + bestScore = score; + } + } + + return { + agent: bestAgent, + confidence: Math.min(bestScore, 1.0), + reason: Object.keys(actions).length > 0 ? 'learned from patterns' : 'default mapping' + }; + } +} + +// Create MCP server +const server = new Server( + { + name: 'ruvector', + version: '0.1.51', + }, + { + capabilities: { + tools: {}, + resources: {}, + }, + } +); + +const intel = new Intelligence(); + +// Define tools +const TOOLS = [ + { + name: 'hooks_stats', + description: 'Get RuVector intelligence statistics including learned patterns, memories, and trajectories', + inputSchema: { + type: 'object', + properties: {}, + required: [] + } + }, + { + name: 'hooks_route', + description: 'Route a task to the best agent based on learned patterns', + inputSchema: { + type: 'object', + properties: { + task: { type: 'string', description: 'Task description' }, + file: { type: 'string', description: 'File path (optional)' } + }, + required: ['task'] + } + }, + { + name: 'hooks_remember', + description: 'Store context in vector memory for later recall', + inputSchema: { + type: 'object', + properties: { + content: { type: 'string', description: 'Content to remember' }, + type: { type: 'string', description: 'Memory type (project, code, decision, context)', default: 'general' } + }, + required: ['content'] + } + }, + { + name: 'hooks_recall', + description: 'Search vector memory for relevant context', + inputSchema: { + type: 'object', + properties: { + query: { type: 'string', description: 'Search query' }, + top_k: { type: 'number', description: 'Number of results', default: 5 } + }, + required: ['query'] + } + }, + { + name: 'hooks_init', + description: 'Initialize RuVector hooks in the current project', + inputSchema: { + type: 'object', + properties: { + pretrain: { type: 'boolean', description: 'Run pretrain after init', default: false }, + build_agents: { type: 'string', description: 'Focus for agent generation (quality, speed, security, testing, fullstack)' }, + force: { type: 'boolean', description: 'Force overwrite existing settings', default: false } + }, + required: [] + } + }, + { + name: 'hooks_pretrain', + description: 'Pretrain intelligence by analyzing the repository structure and git history', + inputSchema: { + type: 'object', + properties: { + depth: { type: 'number', description: 'Git history depth to analyze', default: 100 }, + skip_git: { type: 'boolean', description: 'Skip git history analysis', default: false }, + verbose: { type: 'boolean', description: 'Show detailed progress', default: false } + }, + required: [] + } + }, + { + name: 'hooks_build_agents', + description: 'Generate optimized agent configurations based on repository analysis', + inputSchema: { + type: 'object', + properties: { + focus: { + type: 'string', + description: 'Focus type for agent generation', + enum: ['quality', 'speed', 'security', 'testing', 'fullstack'], + default: 'quality' + }, + include_prompts: { type: 'boolean', description: 'Include system prompts in agent configs', default: true } + }, + required: [] + } + }, + { + name: 'hooks_verify', + description: 'Verify that hooks are configured correctly', + inputSchema: { + type: 'object', + properties: {}, + required: [] + } + }, + { + name: 'hooks_doctor', + description: 'Diagnose and optionally fix setup issues', + inputSchema: { + type: 'object', + properties: { + fix: { type: 'boolean', description: 'Automatically fix issues', default: false } + }, + required: [] + } + }, + { + name: 'hooks_export', + description: 'Export intelligence data for backup', + inputSchema: { + type: 'object', + properties: { + include_all: { type: 'boolean', description: 'Include all data (patterns, memories, trajectories)', default: false } + }, + required: [] + } + } +]; + +// List tools handler +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { tools: TOOLS }; +}); + +// Call tool handler +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + try { + switch (name) { + case 'hooks_stats': { + const stats = intel.stats(); + return { + content: [{ + type: 'text', + text: JSON.stringify({ + success: true, + stats, + intel_path: intel.intelPath + }, null, 2) + }] + }; + } + + case 'hooks_route': { + const result = intel.route(args.task, args.file); + return { + content: [{ + type: 'text', + text: JSON.stringify({ + success: true, + task: args.task, + file: args.file, + ...result + }, null, 2) + }] + }; + } + + case 'hooks_remember': { + const result = intel.remember(args.content, args.type || 'general'); + return { + content: [{ + type: 'text', + text: JSON.stringify({ + success: true, + ...result + }, null, 2) + }] + }; + } + + case 'hooks_recall': { + const results = intel.recall(args.query, args.top_k || 5); + return { + content: [{ + type: 'text', + text: JSON.stringify({ + success: true, + query: args.query, + results: results.map(r => ({ + content: r.content, + type: r.type, + score: r.score.toFixed(3), + created: r.created + })) + }, null, 2) + }] + }; + } + + case 'hooks_init': { + let cmd = 'npx ruvector hooks init'; + if (args.force) cmd += ' --force'; + if (args.pretrain) cmd += ' --pretrain'; + if (args.build_agents) cmd += ` --build-agents ${args.build_agents}`; + + try { + const output = execSync(cmd, { encoding: 'utf-8', timeout: 60000 }); + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: true, output }, null, 2) + }] + }; + } catch (e) { + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: false, error: e.message }, null, 2) + }] + }; + } + } + + case 'hooks_pretrain': { + let cmd = 'npx ruvector hooks pretrain'; + if (args.depth) cmd += ` --depth ${args.depth}`; + if (args.skip_git) cmd += ' --skip-git'; + if (args.verbose) cmd += ' --verbose'; + + try { + const output = execSync(cmd, { encoding: 'utf-8', timeout: 120000 }); + // Reload intelligence after pretrain + intel.data = intel.load(); + return { + content: [{ + type: 'text', + text: JSON.stringify({ + success: true, + output, + new_stats: intel.stats() + }, null, 2) + }] + }; + } catch (e) { + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: false, error: e.message }, null, 2) + }] + }; + } + } + + case 'hooks_build_agents': { + let cmd = 'npx ruvector hooks build-agents'; + if (args.focus) cmd += ` --focus ${args.focus}`; + if (args.include_prompts) cmd += ' --include-prompts'; + + try { + const output = execSync(cmd, { encoding: 'utf-8', timeout: 30000 }); + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: true, output }, null, 2) + }] + }; + } catch (e) { + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: false, error: e.message }, null, 2) + }] + }; + } + } + + case 'hooks_verify': { + try { + const output = execSync('npx ruvector hooks verify', { encoding: 'utf-8', timeout: 15000 }); + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: true, output }, null, 2) + }] + }; + } catch (e) { + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: false, error: e.message, output: e.stdout }, null, 2) + }] + }; + } + } + + case 'hooks_doctor': { + let cmd = 'npx ruvector hooks doctor'; + if (args.fix) cmd += ' --fix'; + + try { + const output = execSync(cmd, { encoding: 'utf-8', timeout: 15000 }); + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: true, output }, null, 2) + }] + }; + } catch (e) { + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: false, error: e.message }, null, 2) + }] + }; + } + } + + case 'hooks_export': { + const exportData = { + version: '1.0', + exported_at: new Date().toISOString(), + patterns: intel.data.patterns || {}, + memories: args.include_all ? (intel.data.memories || []) : [], + trajectories: args.include_all ? (intel.data.trajectories || []) : [], + errors: intel.data.errors || {}, + stats: intel.stats() + }; + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: true, data: exportData }, null, 2) + }] + }; + } + + default: + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: false, error: `Unknown tool: ${name}` }, null, 2) + }], + isError: true + }; + } + } catch (error) { + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: false, error: error.message }, null, 2) + }], + isError: true + }; + } +}); + +// Resources - expose intelligence data +server.setRequestHandler(ListResourcesRequestSchema, async () => { + return { + resources: [ + { + uri: 'ruvector://intelligence/stats', + name: 'Intelligence Stats', + description: 'Current RuVector intelligence statistics', + mimeType: 'application/json' + }, + { + uri: 'ruvector://intelligence/patterns', + name: 'Learned Patterns', + description: 'Q-learning patterns for agent routing', + mimeType: 'application/json' + }, + { + uri: 'ruvector://intelligence/memories', + name: 'Vector Memories', + description: 'Stored context memories', + mimeType: 'application/json' + } + ] + }; +}); + +server.setRequestHandler(ReadResourceRequestSchema, async (request) => { + const { uri } = request.params; + + switch (uri) { + case 'ruvector://intelligence/stats': + return { + contents: [{ + uri, + mimeType: 'application/json', + text: JSON.stringify(intel.stats(), null, 2) + }] + }; + + case 'ruvector://intelligence/patterns': + return { + contents: [{ + uri, + mimeType: 'application/json', + text: JSON.stringify(intel.data.patterns || {}, null, 2) + }] + }; + + case 'ruvector://intelligence/memories': + return { + contents: [{ + uri, + mimeType: 'application/json', + text: JSON.stringify((intel.data.memories || []).map(m => ({ + content: m.content, + type: m.type, + created: m.created + })), null, 2) + }] + }; + + default: + throw new Error(`Unknown resource: ${uri}`); + } +}); + +// Start server +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('RuVector MCP server running on stdio'); +} + +main().catch(console.error); diff --git a/npm/packages/ruvector/package.json b/npm/packages/ruvector/package.json index 9e3232764..4cccbba87 100644 --- a/npm/packages/ruvector/package.json +++ b/npm/packages/ruvector/package.json @@ -1,6 +1,6 @@ { "name": "ruvector", - "version": "0.1.50", + "version": "0.1.51", "description": "High-performance vector database for Node.js with automatic native/WASM fallback", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -52,6 +52,7 @@ "directory": "npm/packages/ruvector" }, "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.0", "@ruvector/core": "^0.1.25", "@ruvector/attention": "^0.1.3", "@ruvector/gnn": "^0.1.22", From e6ab31a38e72504bb0bc8fd4ee83222a21c9dcb0 Mon Sep 17 00:00:00 2001 From: rUv Date: Wed, 31 Dec 2025 00:28:19 +0000 Subject: [PATCH 10/10] feat(hooks): add full IntelligenceEngine with trajectory, co-edit, and attention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add IntelligenceEngine class integrating VectorDB, SONA, and Attention - Add 11 new MCP tools (22 total): trajectory tracking, co-edit patterns, error suggestions, swarm recommendations, force learning - Add 8 new CLI commands: trajectory-begin/step/end, coedit-record/suggest, error-record/suggest, force-learn - Integrate Flash/MultiHead attention for embeddings with fallbacks - Add Read/Glob/Task hooks to settings.json for pattern learning - Fix @ruvector/attention-linux-x64-gnu (publish v0.1.3) - Published ruvector@0.1.53 šŸ¤– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .claude/settings.json | 30 + npm/package-lock.json | 18 +- npm/packages/ruvector/bin/cli.js | 524 ++++++++- npm/packages/ruvector/bin/mcp-server.js | 722 +++++++++++- npm/packages/ruvector/package.json | 4 +- npm/packages/ruvector/src/core/index.ts | 2 + .../ruvector/src/core/intelligence-engine.ts | 1016 +++++++++++++++++ 7 files changed, 2266 insertions(+), 50 deletions(-) create mode 100644 npm/packages/ruvector/src/core/intelligence-engine.ts diff --git a/.claude/settings.json b/.claude/settings.json index e5704d718..bcf7ef767 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -63,6 +63,36 @@ "command": "ruvector hooks pre-command \"$TOOL_INPUT_command\"" } ] + }, + { + "matcher": "Read", + "hooks": [ + { + "type": "command", + "timeout": 1000, + "command": "ruvector hooks remember \"Reading: $TOOL_INPUT_file_path\" -t file_access --silent" + } + ] + }, + { + "matcher": "Glob|Grep", + "hooks": [ + { + "type": "command", + "timeout": 1000, + "command": "ruvector hooks remember \"Search: $TOOL_INPUT_pattern\" -t search_pattern --silent" + } + ] + }, + { + "matcher": "Task", + "hooks": [ + { + "type": "command", + "timeout": 1000, + "command": "ruvector hooks remember \"Agent: $TOOL_INPUT_subagent_type\" -t agent_spawn --silent" + } + ] } ], "PostToolUse": [ diff --git a/npm/package-lock.json b/npm/package-lock.json index 5d4576f69..ef038b4f1 100644 --- a/npm/package-lock.json +++ b/npm/package-lock.json @@ -3734,6 +3734,21 @@ "@ruvector/attention-win32-x64-msvc": "0.1.3" } }, + "node_modules/@ruvector/attention-linux-x64-gnu": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@ruvector/attention-linux-x64-gnu/-/attention-linux-x64-gnu-0.1.3.tgz", + "integrity": "sha512-79GWZmtsPhsRyXje8v5fLJr+AIpGVHu1OmsdsVHxKf15T5xupYtFcoZgNltSG0oX09KfZzhrDkfUY3TQnv/edA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@ruvector/burst-scaling": { "resolved": "packages/burst-scaling", "link": true @@ -18232,6 +18247,7 @@ } }, "packages/rudag": { + "name": "@ruvector/rudag", "version": "0.1.0", "license": "MIT OR Apache-2.0", "dependencies": { @@ -18249,7 +18265,7 @@ } }, "packages/ruvector": { - "version": "0.1.50", + "version": "0.1.52", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0", diff --git a/npm/packages/ruvector/bin/cli.js b/npm/packages/ruvector/bin/cli.js index df07b1f0c..4c4a2fa96 100755 --- a/npm/packages/ruvector/bin/cli.js +++ b/npm/packages/ruvector/bin/cli.js @@ -2093,14 +2093,98 @@ program // ============================================ // Self-Learning Intelligence Hooks +// Full RuVector Stack: VectorDB + SONA + Attention // ============================================ +// Try to load the full IntelligenceEngine, fallback to simple implementation +let IntelligenceEngine = null; +let engineAvailable = false; + +try { + const core = require('../dist/core/intelligence-engine.js'); + IntelligenceEngine = core.IntelligenceEngine || core.default; + engineAvailable = true; +} catch (e) { + // IntelligenceEngine not available, use fallback +} + class Intelligence { constructor() { this.intelPath = this.getIntelPath(); this.data = this.load(); this.alpha = 0.1; this.lastEditedFile = null; + this.sessionStartTime = null; + + // Initialize full RuVector engine if available + this.engine = null; + if (engineAvailable && IntelligenceEngine) { + try { + this.engine = new IntelligenceEngine({ + embeddingDim: 256, + maxMemories: 100000, + maxEpisodes: 50000, + enableSona: true, + enableAttention: true, + learningRate: this.alpha, + }); + // Import existing data into engine + if (this.data) { + this.engine.import(this.convertLegacyData(this.data), true); + } + } catch (e) { + // Engine initialization failed, use fallback + this.engine = null; + } + } + } + + // Convert legacy data format to new engine format + convertLegacyData(data) { + const converted = { + memories: [], + routingPatterns: {}, + errorPatterns: data.errors || {}, + coEditPatterns: {}, + agentMappings: {}, + }; + + // Convert memories + if (data.memories) { + converted.memories = data.memories.map(m => ({ + id: m.id, + content: m.content, + type: m.memory_type || 'general', + embedding: m.embedding || this.embed(m.content), + created: m.timestamp ? new Date(m.timestamp * 1000).toISOString() : new Date().toISOString(), + accessed: 0, + })); + } + + // Convert Q-learning patterns to routing patterns + if (data.patterns) { + for (const [key, value] of Object.entries(data.patterns)) { + const [state, action] = key.split('|'); + if (state && action) { + if (!converted.routingPatterns[state]) { + converted.routingPatterns[state] = {}; + } + converted.routingPatterns[state][action] = value.q_value || 0.5; + } + } + } + + // Convert file sequences to co-edit patterns + if (data.file_sequences) { + for (const seq of data.file_sequences) { + if (!converted.coEditPatterns[seq.from_file]) { + converted.coEditPatterns[seq.from_file] = {}; + } + converted.coEditPatterns[seq.from_file][seq.to_file] = seq.count; + } + } + + return converted; } // Prefer project-local storage, fall back to home directory @@ -2108,19 +2192,9 @@ class Intelligence { const projectPath = path.join(process.cwd(), '.ruvector', 'intelligence.json'); const homePath = path.join(require('os').homedir(), '.ruvector', 'intelligence.json'); - // If project .ruvector exists, use it - if (fs.existsSync(path.dirname(projectPath))) { - return projectPath; - } - // If project .claude exists (hooks initialized), prefer project-local - if (fs.existsSync(path.join(process.cwd(), '.claude'))) { - return projectPath; - } - // If home .ruvector exists with data, use it - if (fs.existsSync(homePath)) { - return homePath; - } - // Default to project-local for new setups + if (fs.existsSync(path.dirname(projectPath))) return projectPath; + if (fs.existsSync(path.join(process.cwd(), '.claude'))) return projectPath; + if (fs.existsSync(homePath)) return homePath; return projectPath; } @@ -2145,12 +2219,41 @@ class Intelligence { save() { const dir = path.dirname(this.intelPath); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + + // If engine is available, export its data + if (this.engine) { + try { + const engineData = this.engine.export(); + // Merge engine data with legacy format for compatibility + this.data.patterns = {}; + for (const [state, actions] of Object.entries(engineData.routingPatterns || {})) { + for (const [action, value] of Object.entries(actions)) { + this.data.patterns[`${state}|${action}`] = { state, action, q_value: value, visits: 1, last_update: this.now() }; + } + } + this.data.stats.total_patterns = Object.keys(this.data.patterns).length; + this.data.stats.total_memories = engineData.stats?.totalMemories || this.data.memories.length; + + // Add engine stats + this.data.engineStats = engineData.stats; + } catch (e) { + // Ignore engine export errors + } + } + fs.writeFileSync(this.intelPath, JSON.stringify(this.data, null, 2)); } now() { return Math.floor(Date.now() / 1000); } + // Use engine embedding if available (256-dim with attention), otherwise fallback (64-dim hash) embed(text) { + if (this.engine) { + try { + return this.engine.embed(text); + } catch {} + } + // Fallback: simple 64-dim hash embedding const embedding = new Array(64).fill(0); for (let i = 0; i < text.length; i++) { const idx = (text.charCodeAt(i) + i * 7) % 64; @@ -2162,21 +2265,68 @@ class Intelligence { } similarity(a, b) { - if (a.length !== b.length) return 0; + if (!a || !b || a.length !== b.length) return 0; const dot = a.reduce((sum, v, i) => sum + v * b[i], 0); const normA = Math.sqrt(a.reduce((sum, v) => sum + v * v, 0)); const normB = Math.sqrt(b.reduce((sum, v) => sum + v * v, 0)); return normA > 0 && normB > 0 ? dot / (normA * normB) : 0; } + // Memory operations - use engine's VectorDB for semantic search + async rememberAsync(memoryType, content, metadata = {}) { + if (this.engine) { + try { + const entry = await this.engine.remember(content, memoryType); + // Also store in legacy format for compatibility + this.data.memories.push({ + id: entry.id, + memory_type: memoryType, + content, + embedding: entry.embedding, + metadata, + timestamp: this.now() + }); + if (this.data.memories.length > 5000) this.data.memories.splice(0, 1000); + this.data.stats.total_memories = this.data.memories.length; + return entry.id; + } catch {} + } + return this.remember(memoryType, content, metadata); + } + remember(memoryType, content, metadata = {}) { const id = `mem_${this.now()}`; - this.data.memories.push({ id, memory_type: memoryType, content, embedding: this.embed(content), metadata, timestamp: this.now() }); + const embedding = this.embed(content); + this.data.memories.push({ id, memory_type: memoryType, content, embedding, metadata, timestamp: this.now() }); if (this.data.memories.length > 5000) this.data.memories.splice(0, 1000); this.data.stats.total_memories = this.data.memories.length; + + // Also store in engine if available + if (this.engine) { + this.engine.remember(content, memoryType).catch(() => {}); + } + return id; } + async recallAsync(query, topK = 5) { + if (this.engine) { + try { + const results = await this.engine.recall(query, topK); + return results.map(r => ({ + score: r.score || 0, + memory: { + id: r.id, + content: r.content, + memory_type: r.type, + timestamp: r.created + } + })); + } catch {} + } + return this.recall(query, topK); + } + recall(query, topK) { const queryEmbed = this.embed(query); return this.data.memories @@ -2184,6 +2334,7 @@ class Intelligence { .sort((a, b) => b.score - a.score).slice(0, topK).map(r => r.memory); } + // Q-learning operations - enhanced with SONA trajectory tracking getQ(state, action) { const key = `${state}|${action}`; return this.data.patterns[key]?.q_value ?? 0; @@ -2191,12 +2342,19 @@ class Intelligence { updateQ(state, action, reward) { const key = `${state}|${action}`; - if (!this.data.patterns[key]) this.data.patterns[key] = { state, action, q_value: 0, visits: 0, last_update: 0 }; + if (!this.data.patterns[key]) { + this.data.patterns[key] = { state, action, q_value: 0, visits: 0, last_update: 0 }; + } const p = this.data.patterns[key]; p.q_value = p.q_value + this.alpha * (reward - p.q_value); p.visits++; p.last_update = this.now(); this.data.stats.total_patterns = Object.keys(this.data.patterns).length; + + // Record episode in engine if available + if (this.engine) { + this.engine.recordEpisode(state, action, reward, state, false).catch(() => {}); + } } learn(state, action, outcome, reward) { @@ -2205,6 +2363,12 @@ class Intelligence { this.data.trajectories.push({ id, state, action, outcome, reward, timestamp: this.now() }); if (this.data.trajectories.length > 1000) this.data.trajectories.splice(0, 200); this.data.stats.total_trajectories = this.data.trajectories.length; + + // End trajectory in engine if available + if (this.engine) { + this.engine.endTrajectory(reward > 0.5, reward); + } + return id; } @@ -2218,20 +2382,53 @@ class Intelligence { return { action: bestAction, confidence: bestQ > 0 ? Math.min(bestQ, 1) : 0 }; } + // Agent routing - use engine's SONA-enhanced routing + async routeAsync(task, file, crateName, operation = 'edit') { + if (this.engine) { + try { + const result = await this.engine.route(task, file); + // Begin trajectory for learning + this.engine.beginTrajectory(task, file); + if (result.agent) { + this.engine.setTrajectoryRoute(result.agent); + } + return { + agent: result.agent, + confidence: result.confidence, + reason: result.reason + (result.patterns?.length ? ` (${result.patterns.length} SONA patterns)` : ''), + alternates: result.alternates, + patterns: result.patterns + }; + } catch {} + } + return this.route(task, file, crateName, operation); + } + route(task, file, crateName, operation = 'edit') { const fileType = file ? path.extname(file).slice(1) : 'unknown'; const state = `${operation}_${fileType}_in_${crateName ?? 'project'}`; const agentMap = { rs: ['rust-developer', 'coder', 'reviewer', 'tester'], ts: ['typescript-developer', 'coder', 'frontend-dev'], - tsx: ['typescript-developer', 'coder', 'frontend-dev'], - js: ['coder', 'frontend-dev'], + tsx: ['react-developer', 'typescript-developer', 'coder'], + js: ['javascript-developer', 'coder', 'frontend-dev'], + jsx: ['react-developer', 'coder'], py: ['python-developer', 'coder', 'ml-developer'], - md: ['docs-writer', 'coder'] + go: ['go-developer', 'coder'], + sql: ['database-specialist', 'coder'], + md: ['documentation-specialist', 'coder'], + yml: ['devops-engineer', 'coder'], + yaml: ['devops-engineer', 'coder'] }; const agents = agentMap[fileType] ?? ['coder', 'reviewer']; const { action, confidence } = this.suggest(state, agents); const reason = confidence > 0.5 ? 'learned from past success' : confidence > 0 ? 'based on patterns' : `default for ${fileType} files`; + + // Begin trajectory in engine + if (this.engine) { + this.engine.beginTrajectory(task || operation, file); + } + return { agent: action, confidence, reason }; } @@ -2244,27 +2441,77 @@ class Intelligence { } case 'ts': case 'tsx': case 'js': case 'jsx': return { suggest: true, command: 'npm test' }; case 'py': return { suggest: true, command: 'pytest' }; + case 'go': return { suggest: true, command: 'go test ./...' }; default: return { suggest: false, command: '' }; } } + // Co-edit pattern tracking - use engine's co-edit patterns recordFileSequence(fromFile, toFile) { const existing = this.data.file_sequences.find(s => s.from_file === fromFile && s.to_file === toFile); if (existing) existing.count++; else this.data.file_sequences.push({ from_file: fromFile, to_file: toFile, count: 1 }); this.lastEditedFile = toFile; + + // Record in engine + if (this.engine) { + this.engine.recordCoEdit(fromFile, toFile); + } } suggestNext(file, limit = 3) { - 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 })); + // Try engine first + if (this.engine) { + try { + const results = this.engine.getLikelyNextFiles(file, limit); + if (results.length > 0) { + return results.map(r => ({ file: r.file, score: r.count })); + } + } catch {} + } + 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 })); + } + + // Error pattern learning + recordErrorFix(errorPattern, fix) { + if (!this.data.errors[errorPattern]) { + this.data.errors[errorPattern] = []; + } + if (!this.data.errors[errorPattern].includes(fix)) { + this.data.errors[errorPattern].push(fix); + } + this.data.stats.total_errors = Object.keys(this.data.errors).length; + + if (this.engine) { + this.engine.recordErrorFix(errorPattern, fix); + } + } + + getSuggestedFixes(error) { + // Try engine first (uses embedding similarity) + if (this.engine) { + try { + const fixes = this.engine.getSuggestedFixes(error); + if (fixes.length > 0) return fixes; + } catch {} + } + return this.data.errors[error] || []; } classifyCommand(command) { const cmd = command.toLowerCase(); if (cmd.includes('cargo') || cmd.includes('rustc')) return { category: 'rust', subcategory: cmd.includes('test') ? 'test' : 'build', risk: 'low' }; - if (cmd.includes('npm') || cmd.includes('node')) return { category: 'javascript', subcategory: cmd.includes('test') ? 'test' : 'build', risk: 'low' }; - if (cmd.includes('git')) return { category: 'git', subcategory: 'vcs', risk: cmd.includes('push') ? 'medium' : 'low' }; - if (cmd.includes('rm') || cmd.includes('delete')) return { category: 'filesystem', subcategory: 'destructive', risk: 'high' }; + if (cmd.includes('npm') || cmd.includes('node') || cmd.includes('yarn') || cmd.includes('pnpm')) return { category: 'javascript', subcategory: cmd.includes('test') ? 'test' : 'build', risk: 'low' }; + if (cmd.includes('python') || cmd.includes('pip') || cmd.includes('pytest')) return { category: 'python', subcategory: cmd.includes('test') ? 'test' : 'run', risk: 'low' }; + if (cmd.includes('go ')) return { category: 'go', subcategory: cmd.includes('test') ? 'test' : 'build', risk: 'low' }; + if (cmd.includes('git')) return { category: 'git', subcategory: 'vcs', risk: cmd.includes('push') || cmd.includes('force') ? 'medium' : 'low' }; + if (cmd.includes('rm ') || cmd.includes('delete') || cmd.includes('rmdir')) return { category: 'filesystem', subcategory: 'destructive', risk: 'high' }; + if (cmd.includes('sudo') || cmd.includes('chmod') || cmd.includes('chown')) return { category: 'system', subcategory: 'privileged', risk: 'high' }; + if (cmd.includes('docker') || cmd.includes('kubectl')) return { category: 'container', subcategory: 'orchestration', risk: 'medium' }; return { category: 'shell', subcategory: 'general', risk: 'low' }; } @@ -2274,14 +2521,87 @@ class Intelligence { return { agents, edges }; } - stats() { return this.data.stats; } - sessionStart() { this.data.stats.session_count++; this.data.stats.last_session = this.now(); } + // Enhanced stats with engine metrics + stats() { + const baseStats = this.data.stats; + + if (this.engine) { + try { + const engineStats = this.engine.getStats(); + return { + ...baseStats, + // Engine stats + engineEnabled: true, + sonaEnabled: engineStats.sonaEnabled, + attentionEnabled: engineStats.attentionEnabled, + embeddingDim: engineStats.memoryDimensions, + totalMemories: engineStats.totalMemories, + totalEpisodes: engineStats.totalEpisodes, + trajectoriesRecorded: engineStats.trajectoriesRecorded, + patternsLearned: engineStats.patternsLearned, + microLoraUpdates: engineStats.microLoraUpdates, + baseLoraUpdates: engineStats.baseLoraUpdates, + ewcConsolidations: engineStats.ewcConsolidations, + }; + } catch {} + } + + return { ...baseStats, engineEnabled: false }; + } + + sessionStart() { + this.data.stats.session_count++; + this.data.stats.last_session = this.now(); + this.sessionStartTime = this.now(); + + // Tick engine for background learning + if (this.engine) { + this.engine.tick(); + } + } + sessionEnd() { - const duration = this.now() - this.data.stats.last_session; + const duration = this.now() - (this.sessionStartTime || this.data.stats.last_session); const actions = this.data.trajectories.filter(t => t.timestamp >= this.data.stats.last_session).length; + + // Force learning cycle + if (this.engine) { + this.engine.forceLearn(); + } + + // Save all data + this.save(); + return { duration, actions }; } + getLastEditedFile() { return this.lastEditedFile; } + + // New: Check if full engine is available + isEngineEnabled() { + return this.engine !== null; + } + + // New: Get engine capabilities + getCapabilities() { + if (!this.engine) { + return { + engine: false, + vectorDb: false, + sona: false, + attention: false, + embeddingDim: 64, + }; + } + const stats = this.engine.getStats(); + return { + engine: true, + vectorDb: true, + sona: stats.sonaEnabled, + attention: stats.attentionEnabled, + embeddingDim: stats.memoryDimensions, + }; + } } // Hooks command group @@ -2760,6 +3080,158 @@ hooksCmd.command('track-notification').description('Track notification').action( console.log(JSON.stringify({ tracked: true })); }); +// Trajectory tracking commands +hooksCmd.command('trajectory-begin') + .description('Begin tracking a new execution trajectory') + .requiredOption('-c, --context ', 'Task or operation context') + .option('-a, --agent ', 'Agent performing the task', 'unknown') + .action((opts) => { + const intel = new Intelligence(); + const trajId = `traj_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + if (!intel.data.activeTrajectories) intel.data.activeTrajectories = {}; + intel.data.activeTrajectories[trajId] = { + id: trajId, + context: opts.context, + agent: opts.agent, + steps: [], + startTime: Date.now() + }; + intel.save(); + console.log(JSON.stringify({ success: true, trajectory_id: trajId, context: opts.context, agent: opts.agent })); + }); + +hooksCmd.command('trajectory-step') + .description('Add a step to the current trajectory') + .requiredOption('-a, --action ', 'Action taken') + .option('-r, --result ', 'Result of action') + .option('--reward ', 'Reward signal (0-1)', '0.5') + .action((opts) => { + const intel = new Intelligence(); + const trajectories = intel.data.activeTrajectories || {}; + const trajIds = Object.keys(trajectories); + if (trajIds.length === 0) { + console.log(JSON.stringify({ success: false, error: 'No active trajectory' })); + return; + } + const latestTrajId = trajIds[trajIds.length - 1]; + trajectories[latestTrajId].steps.push({ + action: opts.action, + result: opts.result || '', + reward: parseFloat(opts.reward), + time: Date.now() + }); + intel.save(); + console.log(JSON.stringify({ success: true, trajectory_id: latestTrajId, step: trajectories[latestTrajId].steps.length })); + }); + +hooksCmd.command('trajectory-end') + .description('End the current trajectory with a quality score') + .option('--success', 'Task succeeded') + .option('--quality ', 'Quality score (0-1)', '0.5') + .action((opts) => { + const intel = new Intelligence(); + const trajectories = intel.data.activeTrajectories || {}; + const trajIds = Object.keys(trajectories); + if (trajIds.length === 0) { + console.log(JSON.stringify({ success: false, error: 'No active trajectory' })); + return; + } + const latestTrajId = trajIds[trajIds.length - 1]; + const traj = trajectories[latestTrajId]; + const quality = opts.success ? 0.8 : parseFloat(opts.quality); + traj.endTime = Date.now(); + traj.quality = quality; + traj.success = opts.success || false; + + if (!intel.data.trajectories) intel.data.trajectories = []; + intel.data.trajectories.push(traj); + delete trajectories[latestTrajId]; + intel.save(); + + console.log(JSON.stringify({ + success: true, + trajectory_id: latestTrajId, + steps: traj.steps.length, + duration_ms: traj.endTime - traj.startTime, + quality + })); + }); + +// Co-edit pattern commands +hooksCmd.command('coedit-record') + .description('Record co-edit pattern (files edited together)') + .requiredOption('-p, --primary ', 'Primary file being edited') + .requiredOption('-r, --related ', 'Related files edited together') + .action((opts) => { + const intel = new Intelligence(); + if (!intel.data.coEditPatterns) intel.data.coEditPatterns = {}; + if (!intel.data.coEditPatterns[opts.primary]) intel.data.coEditPatterns[opts.primary] = {}; + + for (const related of opts.related) { + intel.data.coEditPatterns[opts.primary][related] = (intel.data.coEditPatterns[opts.primary][related] || 0) + 1; + } + intel.save(); + console.log(JSON.stringify({ success: true, primary_file: opts.primary, related_count: opts.related.length })); + }); + +hooksCmd.command('coedit-suggest') + .description('Get suggested related files based on co-edit patterns') + .requiredOption('-f, --file ', 'Current file') + .option('-k, --top-k ', 'Number of suggestions', '5') + .action((opts) => { + const intel = new Intelligence(); + let suggestions = []; + + if (intel.data.coEditPatterns && intel.data.coEditPatterns[opts.file]) { + suggestions = Object.entries(intel.data.coEditPatterns[opts.file]) + .sort((a, b) => b[1] - a[1]) + .slice(0, parseInt(opts.topK)) + .map(([f, count]) => ({ file: f, count, confidence: Math.min(count / 10, 1) })); + } + console.log(JSON.stringify({ success: true, file: opts.file, suggestions })); + }); + +// Error pattern commands +hooksCmd.command('error-record') + .description('Record an error and its fix for learning') + .requiredOption('-e, --error ', 'Error message or code') + .requiredOption('-x, --fix ', 'Fix that resolved the error') + .option('-f, --file ', 'File where error occurred') + .action((opts) => { + const intel = new Intelligence(); + if (!intel.data.errors) intel.data.errors = {}; + if (!intel.data.errors[opts.error]) intel.data.errors[opts.error] = []; + intel.data.errors[opts.error].push({ fix: opts.fix, file: opts.file || '', recorded: Date.now() }); + intel.save(); + console.log(JSON.stringify({ success: true, error: opts.error.substring(0, 50), fixes_recorded: intel.data.errors[opts.error].length })); + }); + +hooksCmd.command('error-suggest') + .description('Get suggested fixes for an error based on learned patterns') + .requiredOption('-e, --error ', 'Error message or code') + .action((opts) => { + const intel = new Intelligence(); + let suggestions = []; + + if (intel.data.errors) { + for (const [errKey, fixes] of Object.entries(intel.data.errors)) { + if (opts.error.includes(errKey) || errKey.includes(opts.error)) { + suggestions.push(...fixes.map(f => f.fix)); + } + } + } + console.log(JSON.stringify({ success: true, error: opts.error.substring(0, 50), suggestions: [...new Set(suggestions)].slice(0, 5) })); + }); + +// Force learning command +hooksCmd.command('force-learn') + .description('Force an immediate learning cycle') + .action(() => { + const intel = new Intelligence(); + intel.tick(); + console.log(JSON.stringify({ success: true, result: 'Learning cycle triggered', stats: intel.stats() })); + }); + // Verify hooks are working hooksCmd.command('verify') .description('Verify hooks are working correctly') diff --git a/npm/packages/ruvector/bin/mcp-server.js b/npm/packages/ruvector/bin/mcp-server.js index 702555652..27d0251b5 100644 --- a/npm/packages/ruvector/bin/mcp-server.js +++ b/npm/packages/ruvector/bin/mcp-server.js @@ -23,11 +23,66 @@ const path = require('path'); const fs = require('fs'); const { execSync } = require('child_process'); -// Intelligence class (simplified from cli.js) +// Try to load the full IntelligenceEngine +let IntelligenceEngine = null; +let engineAvailable = false; + +try { + const core = require('../dist/core/intelligence-engine.js'); + IntelligenceEngine = core.IntelligenceEngine || core.default; + engineAvailable = true; +} catch (e) { + // IntelligenceEngine not available +} + +// Intelligence class with full RuVector stack support class Intelligence { constructor() { this.intelPath = this.getIntelPath(); this.data = this.load(); + this.engine = null; + + // Initialize full engine if available + if (engineAvailable && IntelligenceEngine) { + try { + this.engine = new IntelligenceEngine({ + embeddingDim: 256, + maxMemories: 100000, + enableSona: true, + enableAttention: true, + }); + // Import existing data + if (this.data) { + this.engine.import(this.convertLegacyData(this.data), true); + } + } catch (e) { + this.engine = null; + } + } + } + + convertLegacyData(data) { + const converted = { memories: [], routingPatterns: {}, errorPatterns: {}, coEditPatterns: {} }; + if (data.memories) { + converted.memories = data.memories.map(m => ({ + id: m.id || `mem-${Date.now()}`, + content: m.content, + type: m.type || 'general', + embedding: m.embedding || [], + created: m.created || new Date().toISOString(), + accessed: 0, + })); + } + if (data.patterns) { + for (const [key, value] of Object.entries(data.patterns)) { + const [state, action] = key.split('|'); + if (state && action) { + if (!converted.routingPatterns[state]) converted.routingPatterns[state] = {}; + converted.routingPatterns[state][action] = value.q_value || value || 0.5; + } + } + } + return converted; } getIntelPath() { @@ -51,19 +106,55 @@ class Intelligence { save() { const dir = path.dirname(this.intelPath); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + + // Export engine data if available + if (this.engine) { + try { + const engineData = this.engine.export(); + this.data.engineStats = engineData.stats; + } catch {} + } + fs.writeFileSync(this.intelPath, JSON.stringify(this.data, null, 2)); } stats() { - return { + const baseStats = { total_patterns: Object.keys(this.data.patterns || {}).length, total_memories: (this.data.memories || []).length, total_trajectories: (this.data.trajectories || []).length, total_errors: Object.keys(this.data.errors || {}).length }; + + if (this.engine) { + try { + const engineStats = this.engine.getStats(); + return { + ...baseStats, + engineEnabled: true, + sonaEnabled: engineStats.sonaEnabled, + attentionEnabled: engineStats.attentionEnabled, + embeddingDim: engineStats.memoryDimensions, + totalMemories: engineStats.totalMemories, + totalEpisodes: engineStats.totalEpisodes, + trajectoriesRecorded: engineStats.trajectoriesRecorded, + patternsLearned: engineStats.patternsLearned, + microLoraUpdates: engineStats.microLoraUpdates, + ewcConsolidations: engineStats.ewcConsolidations, + }; + } catch {} + } + + return { ...baseStats, engineEnabled: false }; } embed(text) { + if (this.engine) { + try { + return this.engine.embed(text); + } catch {} + } + // Fallback: 64-dim hash const embedding = new Array(64).fill(0); for (let i = 0; i < text.length; i++) { const idx = (text.charCodeAt(i) + i * 7) % 64; @@ -82,19 +173,42 @@ class Intelligence { return normA > 0 && normB > 0 ? dot / (normA * normB) : 0; } - remember(content, type = 'general') { + async remember(content, type = 'general') { + // Use engine if available (VectorDB storage) + if (this.engine) { + try { + const entry = await this.engine.remember(content, type); + // Also store in legacy format + this.data.memories = this.data.memories || []; + this.data.memories.push({ content, type, created: new Date().toISOString(), embedding: entry.embedding }); + this.save(); + return { stored: true, total: this.data.memories.length, engineStored: true }; + } catch {} + } + + // Fallback this.data.memories = this.data.memories || []; - this.data.memories.push({ - content, - type, - created: new Date().toISOString(), - embedding: this.embed(content) - }); + this.data.memories.push({ content, type, created: new Date().toISOString(), embedding: this.embed(content) }); this.save(); return { stored: true, total: this.data.memories.length }; } - recall(query, topK = 5) { + async recall(query, topK = 5) { + // Use engine if available (HNSW search - 150x faster) + if (this.engine) { + try { + const results = await this.engine.recall(query, topK); + return results.map(r => ({ + content: r.content, + type: r.type, + score: r.score || 0, + created: r.created, + engineResult: true + })); + } catch {} + } + + // Fallback: brute-force const queryEmbed = this.embed(query); const scored = (this.data.memories || []).map((m, i) => ({ ...m, @@ -104,20 +218,37 @@ class Intelligence { return scored.sort((a, b) => b.score - a.score).slice(0, topK); } - route(task, file = null) { + async route(task, file = null) { + // Use engine if available (SONA-enhanced routing) + if (this.engine) { + try { + const result = await this.engine.route(task, file); + return { + agent: result.agent, + confidence: result.confidence, + reason: result.reason, + alternates: result.alternates, + sonaPatterns: result.patterns?.length || 0, + engineRouted: true + }; + } catch {} + } + + // Fallback const ext = file ? path.extname(file) : ''; const state = `edit:${ext || 'unknown'}`; const actions = this.data.patterns[state] || {}; - // Default agent mapping const defaults = { '.rs': 'rust-developer', '.ts': 'typescript-developer', '.tsx': 'react-developer', '.js': 'javascript-developer', + '.jsx': 'react-developer', '.py': 'python-developer', '.go': 'go-developer', - '.sql': 'database-specialist' + '.sql': 'database-specialist', + '.md': 'documentation-specialist' }; let bestAgent = defaults[ext] || 'coder'; @@ -136,13 +267,31 @@ class Intelligence { reason: Object.keys(actions).length > 0 ? 'learned from patterns' : 'default mapping' }; } + + getCapabilities() { + if (!this.engine) { + return { engine: false, vectorDb: false, sona: false, attention: false, embeddingDim: 64 }; + } + try { + const stats = this.engine.getStats(); + return { + engine: true, + vectorDb: true, + sona: stats.sonaEnabled, + attention: stats.attentionEnabled, + embeddingDim: stats.memoryDimensions, + }; + } catch { + return { engine: true, vectorDb: false, sona: false, attention: false, embeddingDim: 256 }; + } + } } // Create MCP server const server = new Server( { name: 'ruvector', - version: '0.1.51', + version: '0.1.53', }, { capabilities: { @@ -274,6 +423,145 @@ const TOOLS = [ }, required: [] } + }, + { + name: 'hooks_capabilities', + description: 'Get RuVector engine capabilities (VectorDB, SONA, Attention)', + inputSchema: { + type: 'object', + properties: {}, + required: [] + } + }, + { + name: 'hooks_import', + description: 'Import intelligence data from backup file', + inputSchema: { + type: 'object', + properties: { + data: { type: 'object', description: 'Exported data object to import' }, + merge: { type: 'boolean', description: 'Merge with existing data', default: true } + }, + required: ['data'] + } + }, + { + name: 'hooks_swarm_recommend', + description: 'Get agent recommendation for a task type using learned patterns', + inputSchema: { + type: 'object', + properties: { + task_type: { type: 'string', description: 'Type of task (research, code, test, review, debug, etc.)' }, + file: { type: 'string', description: 'Optional file path for context' } + }, + required: ['task_type'] + } + }, + { + name: 'hooks_suggest_context', + description: 'Get relevant context suggestions for the current task', + inputSchema: { + type: 'object', + properties: { + query: { type: 'string', description: 'Current task or query' }, + top_k: { type: 'number', description: 'Number of suggestions', default: 5 } + }, + required: [] + } + }, + { + name: 'hooks_trajectory_begin', + description: 'Begin tracking a new execution trajectory', + inputSchema: { + type: 'object', + properties: { + context: { type: 'string', description: 'Task or operation context' }, + agent: { type: 'string', description: 'Agent performing the task' } + }, + required: ['context'] + } + }, + { + name: 'hooks_trajectory_step', + description: 'Add a step to the current trajectory', + inputSchema: { + type: 'object', + properties: { + action: { type: 'string', description: 'Action taken' }, + result: { type: 'string', description: 'Result of action' }, + reward: { type: 'number', description: 'Reward signal (0-1)', default: 0.5 } + }, + required: ['action'] + } + }, + { + name: 'hooks_trajectory_end', + description: 'End the current trajectory with a quality score', + inputSchema: { + type: 'object', + properties: { + success: { type: 'boolean', description: 'Whether the task succeeded' }, + quality: { type: 'number', description: 'Quality score (0-1)', default: 0.5 } + }, + required: [] + } + }, + { + name: 'hooks_coedit_record', + description: 'Record co-edit pattern (files edited together)', + inputSchema: { + type: 'object', + properties: { + primary_file: { type: 'string', description: 'Primary file being edited' }, + related_files: { type: 'array', items: { type: 'string' }, description: 'Related files edited together' } + }, + required: ['primary_file', 'related_files'] + } + }, + { + name: 'hooks_coedit_suggest', + description: 'Get suggested related files based on co-edit patterns', + inputSchema: { + type: 'object', + properties: { + file: { type: 'string', description: 'Current file' }, + top_k: { type: 'number', description: 'Number of suggestions', default: 5 } + }, + required: ['file'] + } + }, + { + name: 'hooks_error_record', + description: 'Record an error and its fix for learning', + inputSchema: { + type: 'object', + properties: { + error: { type: 'string', description: 'Error message or code' }, + fix: { type: 'string', description: 'Fix that resolved the error' }, + file: { type: 'string', description: 'File where error occurred' } + }, + required: ['error', 'fix'] + } + }, + { + name: 'hooks_error_suggest', + description: 'Get suggested fixes for an error based on learned patterns', + inputSchema: { + type: 'object', + properties: { + error: { type: 'string', description: 'Error message or code' } + }, + required: ['error'] + } + }, + { + name: 'hooks_force_learn', + description: 'Force an immediate learning cycle', + inputSchema: { + type: 'object', + properties: {}, + required: [] + } } ]; @@ -303,7 +591,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case 'hooks_route': { - const result = intel.route(args.task, args.file); + const result = await intel.route(args.task, args.file); return { content: [{ type: 'text', @@ -318,7 +606,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case 'hooks_remember': { - const result = intel.remember(args.content, args.type || 'general'); + const result = await intel.remember(args.content, args.type || 'general'); return { content: [{ type: 'text', @@ -331,7 +619,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } case 'hooks_recall': { - const results = intel.recall(args.query, args.top_k || 5); + const results = await intel.recall(args.query, args.top_k || 5); return { content: [{ type: 'text', @@ -341,8 +629,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { results: results.map(r => ({ content: r.content, type: r.type, - score: r.score.toFixed(3), - created: r.created + score: typeof r.score === 'number' ? r.score.toFixed(3) : r.score, + created: r.created, + engineResult: r.engineResult || false })) }, null, 2) }] @@ -469,13 +758,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { case 'hooks_export': { const exportData = { - version: '1.0', + version: '2.0', exported_at: new Date().toISOString(), patterns: intel.data.patterns || {}, memories: args.include_all ? (intel.data.memories || []) : [], trajectories: args.include_all ? (intel.data.trajectories || []) : [], errors: intel.data.errors || {}, - stats: intel.stats() + stats: intel.stats(), + capabilities: intel.getCapabilities() }; return { content: [{ @@ -485,6 +775,396 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { }; } + case 'hooks_capabilities': { + const capabilities = intel.getCapabilities(); + const stats = intel.stats(); + return { + content: [{ + type: 'text', + text: JSON.stringify({ + success: true, + capabilities, + features: { + vectorDb: capabilities.vectorDb ? 'HNSW indexing (150x faster search)' : 'Brute-force fallback', + sona: capabilities.sona ? 'Micro-LoRA + Base-LoRA + EWC++' : 'Q-learning fallback', + attention: capabilities.attention ? 'Self-attention embeddings' : 'Hash embeddings', + embeddingDim: capabilities.embeddingDim, + }, + stats: { + totalMemories: stats.totalMemories || stats.total_memories, + trajectoriesRecorded: stats.trajectoriesRecorded || 0, + patternsLearned: stats.patternsLearned || stats.total_patterns, + microLoraUpdates: stats.microLoraUpdates || 0, + ewcConsolidations: stats.ewcConsolidations || 0, + } + }, null, 2) + }] + }; + } + + case 'hooks_import': { + try { + const data = args.data; + const merge = args.merge !== false; + + if (data.patterns) { + if (merge) { + Object.assign(intel.data.patterns, data.patterns); + } else { + intel.data.patterns = data.patterns; + } + } + if (data.memories) { + if (merge) { + intel.data.memories = [...(intel.data.memories || []), ...data.memories]; + } else { + intel.data.memories = data.memories; + } + } + if (data.errors) { + if (merge) { + Object.assign(intel.data.errors, data.errors); + } else { + intel.data.errors = data.errors; + } + } + intel.save(); + + return { + content: [{ + type: 'text', + text: JSON.stringify({ + success: true, + message: `Imported ${Object.keys(data.patterns || {}).length} patterns, ${(data.memories || []).length} memories`, + merge + }, null, 2) + }] + }; + } catch (e) { + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: false, error: e.message }, null, 2) + }] + }; + } + } + + case 'hooks_swarm_recommend': { + const taskType = args.task_type || ''; + const file = args.file || ''; + + // Map task types to recommended agents + const taskAgentMap = { + research: ['researcher', 'analyst', 'explorer'], + code: ['coder', 'backend-dev', 'sparc-coder'], + test: ['tester', 'tdd-london-swarm', 'production-validator'], + review: ['reviewer', 'code-analyzer', 'analyst'], + debug: ['coder', 'tester', 'analyst'], + refactor: ['code-analyzer', 'reviewer', 'architect'], + document: ['documenter', 'api-docs', 'researcher'], + security: ['security-manager', 'reviewer', 'code-analyzer'], + performance: ['perf-analyzer', 'performance-benchmarker', 'optimizer'], + architecture: ['system-architect', 'architect', 'planner'] + }; + + // Get learned route if file provided + let learnedAgent = null; + if (file) { + const route = await intel.route({ task: taskType, file }); + learnedAgent = route?.agent; + } + + const recommendations = taskAgentMap[taskType.toLowerCase()] || ['coder', 'researcher', 'analyst']; + + return { + content: [{ + type: 'text', + text: JSON.stringify({ + success: true, + task_type: taskType, + recommendations, + learned_agent: learnedAgent, + suggested: learnedAgent || recommendations[0] + }, null, 2) + }] + }; + } + + case 'hooks_suggest_context': { + const query = args.query || ''; + const topK = args.top_k || 5; + + // Get relevant memories + const memories = await intel.recall(query, topK); + + // Get recent patterns + const recentPatterns = Object.entries(intel.data.patterns || {}) + .slice(0, topK) + .map(([state, actions]) => ({ state, topAction: Object.keys(actions)[0] })); + + return { + content: [{ + type: 'text', + text: JSON.stringify({ + success: true, + query, + memories: memories.map(m => ({ content: m.content, type: m.type, score: m.score })), + patterns: recentPatterns + }, null, 2) + }] + }; + } + + case 'hooks_trajectory_begin': { + const context = args.context; + const agent = args.agent || 'unknown'; + + // Store trajectory start in intel + if (!intel.data.activeTrajectories) intel.data.activeTrajectories = {}; + const trajId = `traj_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + intel.data.activeTrajectories[trajId] = { + id: trajId, + context, + agent, + steps: [], + startTime: Date.now() + }; + + // Also use engine if available + if (intel.engine) { + try { + intel.engine.beginTrajectory(context); + } catch (e) { /* fallback to manual */ } + } + + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: true, trajectory_id: trajId, context, agent }, null, 2) + }] + }; + } + + case 'hooks_trajectory_step': { + const action = args.action; + const result = args.result || ''; + const reward = args.reward || 0.5; + + // Add to most recent trajectory + const trajectories = intel.data.activeTrajectories || {}; + const trajIds = Object.keys(trajectories); + if (trajIds.length === 0) { + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: false, error: 'No active trajectory. Call hooks_trajectory_begin first.' }, null, 2) + }] + }; + } + + const latestTrajId = trajIds[trajIds.length - 1]; + trajectories[latestTrajId].steps.push({ action, result, reward, time: Date.now() }); + + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: true, trajectory_id: latestTrajId, step: trajectories[latestTrajId].steps.length }, null, 2) + }] + }; + } + + case 'hooks_trajectory_end': { + const success = args.success !== false; + const quality = args.quality || (success ? 0.8 : 0.2); + + const trajectories = intel.data.activeTrajectories || {}; + const trajIds = Object.keys(trajectories); + if (trajIds.length === 0) { + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: false, error: 'No active trajectory.' }, null, 2) + }] + }; + } + + const latestTrajId = trajIds[trajIds.length - 1]; + const traj = trajectories[latestTrajId]; + traj.endTime = Date.now(); + traj.quality = quality; + traj.success = success; + + // Move to completed trajectories + if (!intel.data.trajectories) intel.data.trajectories = []; + intel.data.trajectories.push(traj); + delete trajectories[latestTrajId]; + + // Learn from trajectory + if (intel.engine && traj.steps.length > 0) { + try { + intel.engine.endTrajectory(latestTrajId, quality); + } catch (e) { /* fallback */ } + } + + intel.save(); + + return { + content: [{ + type: 'text', + text: JSON.stringify({ + success: true, + trajectory_id: latestTrajId, + steps: traj.steps.length, + duration_ms: traj.endTime - traj.startTime, + quality + }, null, 2) + }] + }; + } + + case 'hooks_coedit_record': { + const primaryFile = args.primary_file; + const relatedFiles = args.related_files || []; + + if (!intel.data.coEditPatterns) intel.data.coEditPatterns = {}; + if (!intel.data.coEditPatterns[primaryFile]) intel.data.coEditPatterns[primaryFile] = {}; + + for (const related of relatedFiles) { + intel.data.coEditPatterns[primaryFile][related] = (intel.data.coEditPatterns[primaryFile][related] || 0) + 1; + } + + // Use engine if available + if (intel.engine) { + try { + for (const related of relatedFiles) { + intel.engine.recordCoEdit(primaryFile, related); + } + } catch (e) { /* fallback */ } + } + + intel.save(); + + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: true, primary_file: primaryFile, related_count: relatedFiles.length }, null, 2) + }] + }; + } + + case 'hooks_coedit_suggest': { + const file = args.file; + const topK = args.top_k || 5; + + let suggestions = []; + + // Try engine first + if (intel.engine) { + try { + suggestions = intel.engine.getLikelyNextFiles(file, topK); + } catch (e) { /* fallback */ } + } + + // Fallback to data + if (suggestions.length === 0 && intel.data.coEditPatterns && intel.data.coEditPatterns[file]) { + suggestions = Object.entries(intel.data.coEditPatterns[file]) + .sort((a, b) => b[1] - a[1]) + .slice(0, topK) + .map(([f, count]) => ({ file: f, count, confidence: count / 10 })); + } + + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: true, file, suggestions }, null, 2) + }] + }; + } + + case 'hooks_error_record': { + const error = args.error; + const fix = args.fix; + const file = args.file || ''; + + if (!intel.data.errors) intel.data.errors = {}; + if (!intel.data.errors[error]) intel.data.errors[error] = []; + intel.data.errors[error].push({ fix, file, recorded: Date.now() }); + + // Use engine if available + if (intel.engine) { + try { + intel.engine.recordErrorFix(error, fix); + } catch (e) { /* fallback */ } + } + + intel.save(); + + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: true, error: error.substring(0, 50), fixes_recorded: intel.data.errors[error].length }, null, 2) + }] + }; + } + + case 'hooks_error_suggest': { + const error = args.error; + + let suggestions = []; + + // Try engine first + if (intel.engine) { + try { + suggestions = intel.engine.getSuggestedFixes(error); + } catch (e) { /* fallback */ } + } + + // Fallback to data + if (suggestions.length === 0 && intel.data.errors) { + // Find similar errors + for (const [errKey, fixes] of Object.entries(intel.data.errors)) { + if (error.includes(errKey) || errKey.includes(error)) { + suggestions.push(...fixes.map(f => f.fix)); + } + } + } + + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: true, error: error.substring(0, 50), suggestions: [...new Set(suggestions)].slice(0, 5) }, null, 2) + }] + }; + } + + case 'hooks_force_learn': { + let result = 'Learning triggered'; + + if (intel.engine) { + try { + // Run forceLearn on engine + const learnResult = intel.engine.forceLearn(); + result = learnResult || 'Engine learning complete'; + + // Also tick for regular updates + intel.engine.tick(); + } catch (e) { + result = `Learning: ${e.message}`; + } + } + + // Save any updates + intel.save(); + + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: true, result, stats: intel.stats() }, null, 2) + }] + }; + } + default: return { content: [{ diff --git a/npm/packages/ruvector/package.json b/npm/packages/ruvector/package.json index 4cccbba87..0def7c1e0 100644 --- a/npm/packages/ruvector/package.json +++ b/npm/packages/ruvector/package.json @@ -1,6 +1,6 @@ { "name": "ruvector", - "version": "0.1.51", + "version": "0.1.53", "description": "High-performance vector database for Node.js with automatic native/WASM fallback", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -53,8 +53,8 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0", - "@ruvector/core": "^0.1.25", "@ruvector/attention": "^0.1.3", + "@ruvector/core": "^0.1.25", "@ruvector/gnn": "^0.1.22", "@ruvector/sona": "^0.1.4", "chalk": "^4.1.2", diff --git a/npm/packages/ruvector/src/core/index.ts b/npm/packages/ruvector/src/core/index.ts index e133b2f64..cb222bd91 100644 --- a/npm/packages/ruvector/src/core/index.ts +++ b/npm/packages/ruvector/src/core/index.ts @@ -9,9 +9,11 @@ export * from './gnn-wrapper'; export * from './attention-fallbacks'; export * from './agentdb-fast'; export * from './sona-wrapper'; +export * from './intelligence-engine'; // Re-export default objects for convenience export { default as gnnWrapper } from './gnn-wrapper'; export { default as attentionFallbacks } from './attention-fallbacks'; export { default as agentdbFast } from './agentdb-fast'; export { default as Sona } from './sona-wrapper'; +export { default as IntelligenceEngine } from './intelligence-engine'; diff --git a/npm/packages/ruvector/src/core/intelligence-engine.ts b/npm/packages/ruvector/src/core/intelligence-engine.ts new file mode 100644 index 000000000..ad65667c7 --- /dev/null +++ b/npm/packages/ruvector/src/core/intelligence-engine.ts @@ -0,0 +1,1016 @@ +/** + * IntelligenceEngine - Full RuVector Intelligence Stack + * + * Integrates all RuVector capabilities for self-learning hooks: + * - VectorDB with HNSW for semantic memory (150x faster) + * - SONA for continual learning (Micro-LoRA, EWC++) + * - FastAgentDB for episode/trajectory storage + * - Attention mechanisms for pattern recognition + * - ReasoningBank for pattern clustering + * + * Replaces the simple Q-learning approach with real ML-powered intelligence. + */ + +import { FastAgentDB, Episode, Trajectory, EpisodeSearchResult } from './agentdb-fast'; +import { SonaEngine, SonaConfig, LearnedPattern, SonaStats, isSonaAvailable } from './sona-wrapper'; + +// ============================================================================ +// Types +// ============================================================================ + +export interface MemoryEntry { + id: string; + content: string; + type: string; + embedding: number[]; + created: string; + accessed: number; + score?: number; +} + +export interface AgentRoute { + agent: string; + confidence: number; + reason: string; + patterns?: LearnedPattern[]; + alternates?: Array<{ agent: string; confidence: number }>; +} + +export interface LearningStats { + // Memory stats + totalMemories: number; + memoryDimensions: number; + + // Episode/trajectory stats + totalEpisodes: number; + totalTrajectories: number; + avgReward: number; + + // SONA stats (if available) + sonaEnabled: boolean; + trajectoriesRecorded: number; + patternsLearned: number; + microLoraUpdates: number; + baseLoraUpdates: number; + ewcConsolidations: number; + + // Pattern stats + routingPatterns: number; + errorPatterns: number; + coEditPatterns: number; + + // Attention stats + attentionEnabled: boolean; +} + +export interface IntelligenceConfig { + /** Embedding dimension for vectors (default: 256) */ + embeddingDim?: number; + /** Maximum memories to store (default: 100000) */ + maxMemories?: number; + /** Maximum episodes for trajectory storage (default: 50000) */ + maxEpisodes?: number; + /** Enable SONA continual learning (default: true if available) */ + enableSona?: boolean; + /** Enable attention mechanisms (default: true if available) */ + enableAttention?: boolean; + /** SONA configuration */ + sonaConfig?: Partial; + /** Storage path for persistence */ + storagePath?: string; + /** Learning rate for pattern updates (default: 0.1) */ + learningRate?: number; +} + +// ============================================================================ +// Lazy Loading +// ============================================================================ + +let VectorDB: any = null; +let vectorDbError: Error | null = null; + +function getVectorDB() { + if (VectorDB) return VectorDB; + if (vectorDbError) throw vectorDbError; + + try { + const core = require('@ruvector/core'); + VectorDB = core.VectorDb || core.VectorDB; + return VectorDB; + } catch { + try { + const pkg = require('ruvector'); + VectorDB = pkg.VectorDb || pkg.VectorDB; + return VectorDB; + } catch (e: any) { + vectorDbError = new Error(`VectorDB not available: ${e.message}`); + throw vectorDbError; + } + } +} + +let attentionModule: any = null; +let attentionError: Error | null = null; + +function getAttention() { + if (attentionModule) return attentionModule; + if (attentionError) return null; // Silently fail for optional module + + try { + attentionModule = require('@ruvector/attention'); + return attentionModule; + } catch (e: any) { + attentionError = e; + return null; + } +} + +// ============================================================================ +// Intelligence Engine +// ============================================================================ + +/** + * Full-stack intelligence engine using all RuVector capabilities + */ +export class IntelligenceEngine { + private config: Required; + private vectorDb: any = null; + private agentDb: FastAgentDB; + private sona: SonaEngine | null = null; + private attention: any = null; + + // In-memory data structures + private memories: Map = new Map(); + private routingPatterns: Map> = new Map(); // state -> action -> value + private errorPatterns: Map = new Map(); // error -> fixes + private coEditPatterns: Map> = new Map(); // file -> related files -> count + private agentMappings: Map = new Map(); // extension/dir -> agent + + // Runtime state + private currentTrajectoryId: number | null = null; + private sessionStart: number = Date.now(); + private learningEnabled: boolean = true; + + constructor(config: IntelligenceConfig = {}) { + this.config = { + embeddingDim: config.embeddingDim ?? 256, + maxMemories: config.maxMemories ?? 100000, + maxEpisodes: config.maxEpisodes ?? 50000, + enableSona: config.enableSona ?? true, + enableAttention: config.enableAttention ?? true, + sonaConfig: config.sonaConfig ?? {}, + storagePath: config.storagePath ?? '', + learningRate: config.learningRate ?? 0.1, + }; + + // Initialize FastAgentDB for episode storage + this.agentDb = new FastAgentDB(this.config.embeddingDim, this.config.maxEpisodes); + + // Initialize SONA if enabled and available + if (this.config.enableSona && isSonaAvailable()) { + try { + this.sona = SonaEngine.withConfig({ + hiddenDim: this.config.embeddingDim, + embeddingDim: this.config.embeddingDim, + microLoraRank: 2, // Fast adaptations + baseLoraRank: 8, + patternClusters: 100, + trajectoryCapacity: 10000, + ...this.config.sonaConfig, + }); + } catch (e) { + console.warn('SONA initialization failed, using fallback learning'); + } + } + + // Initialize attention if enabled + if (this.config.enableAttention) { + this.attention = getAttention(); + } + + // Initialize VectorDB for memory + this.initVectorDb(); + } + + private async initVectorDb(): Promise { + try { + const VDB = getVectorDB(); + this.vectorDb = new VDB({ + dimensions: this.config.embeddingDim, + distanceMetric: 'Cosine', + }); + } catch { + // VectorDB not available, use fallback + } + } + + // ========================================================================= + // Embedding Generation + // ========================================================================= + + /** + * Generate embedding using attention if available, otherwise use improved hash + */ + embed(text: string): number[] { + const dim = this.config.embeddingDim; + + // Try to use attention-based embedding + if (this.attention?.DotProductAttention) { + try { + return this.attentionEmbed(text, dim); + } catch { + // Fall through to hash embedding + } + } + + // Improved positional hash embedding + return this.hashEmbed(text, dim); + } + + /** + * Attention-based embedding using Flash or Multi-head attention + */ + private attentionEmbed(text: string, dim: number): number[] { + const tokens = this.tokenize(text); + const tokenEmbeddings = tokens.map(t => this.tokenEmbed(t, dim)); + + if (tokenEmbeddings.length === 0) { + return new Array(dim).fill(0); + } + + try { + // Try FlashAttention first (fastest) + if (this.attention?.FlashAttention) { + const flash = new this.attention.FlashAttention(dim); + const query = new Float32Array(this.meanPool(tokenEmbeddings)); + const keys = tokenEmbeddings.map(e => new Float32Array(e)); + const values = tokenEmbeddings.map(e => new Float32Array(e)); + const result = flash.forward(query, keys, values); + return Array.from(result); + } + + // Try MultiHeadAttention (better quality) + if (this.attention?.MultiHeadAttention) { + const numHeads = Math.min(8, Math.floor(dim / 32)); // 8 heads max + const mha = new this.attention.MultiHeadAttention(dim, numHeads); + const query = new Float32Array(this.meanPool(tokenEmbeddings)); + const keys = tokenEmbeddings.map(e => new Float32Array(e)); + const values = tokenEmbeddings.map(e => new Float32Array(e)); + const result = mha.forward(query, keys, values); + return Array.from(result); + } + + // Fall back to DotProductAttention + if (this.attention?.DotProductAttention) { + const attn = new this.attention.DotProductAttention(); + const query = this.meanPool(tokenEmbeddings); + const result = attn.forward( + new Float32Array(query), + tokenEmbeddings.map(e => new Float32Array(e)), + tokenEmbeddings.map(e => new Float32Array(e)) + ); + return Array.from(result); + } + } catch { + // Fall through to hash embedding + } + + // Ultimate fallback + return this.hashEmbed(text, dim); + } + + /** + * Improved hash-based embedding with positional encoding + */ + private hashEmbed(text: string, dim: number): number[] { + const embedding = new Array(dim).fill(0); + const tokens = this.tokenize(text); + + for (let t = 0; t < tokens.length; t++) { + const token = tokens[t]; + const posWeight = 1 / (1 + t * 0.1); // Positional decay + + for (let i = 0; i < token.length; i++) { + const charCode = token.charCodeAt(i); + // Multiple hash functions for better distribution + const h1 = (charCode * 31 + i * 17 + t * 7) % dim; + const h2 = (charCode * 37 + i * 23 + t * 11) % dim; + const h3 = (charCode * 41 + i * 29 + t * 13) % dim; + + embedding[h1] += posWeight; + embedding[h2] += posWeight * 0.5; + embedding[h3] += posWeight * 0.25; + } + } + + // L2 normalize + const norm = Math.sqrt(embedding.reduce((a, b) => a + b * b, 0)); + if (norm > 0) { + for (let i = 0; i < dim; i++) embedding[i] /= norm; + } + + return embedding; + } + + private tokenize(text: string): string[] { + return text.toLowerCase() + .replace(/[^\w\s]/g, ' ') + .split(/\s+/) + .filter(t => t.length > 0); + } + + private tokenEmbed(token: string, dim: number): number[] { + const embedding = new Array(dim).fill(0); + for (let i = 0; i < token.length; i++) { + const idx = (token.charCodeAt(i) * 31 + i * 17) % dim; + embedding[idx] += 1; + } + const norm = Math.sqrt(embedding.reduce((a, b) => a + b * b, 0)); + if (norm > 0) for (let i = 0; i < dim; i++) embedding[i] /= norm; + return embedding; + } + + private meanPool(embeddings: number[][]): number[] { + if (embeddings.length === 0) return []; + const dim = embeddings[0].length; + const result = new Array(dim).fill(0); + for (const emb of embeddings) { + for (let i = 0; i < dim; i++) result[i] += emb[i]; + } + for (let i = 0; i < dim; i++) result[i] /= embeddings.length; + return result; + } + + // ========================================================================= + // Memory Operations + // ========================================================================= + + /** + * Store content in vector memory + */ + async remember(content: string, type: string = 'general'): Promise { + const id = `mem-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + const embedding = this.embed(content); + + const entry: MemoryEntry = { + id, + content, + type, + embedding, + created: new Date().toISOString(), + accessed: 0, + }; + + this.memories.set(id, entry); + + // Index in VectorDB if available + if (this.vectorDb) { + try { + await this.vectorDb.insert({ + id, + vector: new Float32Array(embedding), + metadata: JSON.stringify({ content, type, created: entry.created }), + }); + } catch { + // Ignore indexing errors + } + } + + return entry; + } + + /** + * Semantic search of memories + */ + async recall(query: string, topK: number = 5): Promise { + const queryEmbed = this.embed(query); + + // Try VectorDB search first (HNSW - 150x faster) + if (this.vectorDb) { + try { + const results = await this.vectorDb.search({ + vector: new Float32Array(queryEmbed), + k: topK, + }); + + return results.map((r: any) => { + const entry = this.memories.get(r.id); + if (entry) { + entry.accessed++; + entry.score = 1 - r.score; // Convert distance to similarity + } + return entry; + }).filter((e: any): e is MemoryEntry => e !== null); + } catch { + // Fall through to brute force + } + } + + // Fallback: brute-force cosine similarity + const scored = Array.from(this.memories.values()).map(m => ({ + ...m, + score: this.cosineSimilarity(queryEmbed, m.embedding), + })); + + return scored + .sort((a, b) => (b.score || 0) - (a.score || 0)) + .slice(0, topK); + } + + private cosineSimilarity(a: number[], b: number[]): number { + if (a.length !== b.length) return 0; + let dot = 0, normA = 0, normB = 0; + for (let i = 0; i < a.length; i++) { + dot += a[i] * b[i]; + normA += a[i] * a[i]; + normB += b[i] * b[i]; + } + const denom = Math.sqrt(normA) * Math.sqrt(normB); + return denom > 0 ? dot / denom : 0; + } + + // ========================================================================= + // Agent Routing with SONA + // ========================================================================= + + /** + * Route a task to the best agent using learned patterns + */ + async route(task: string, file?: string): Promise { + const ext = file ? this.getExtension(file) : ''; + const state = this.getState(task, ext); + const taskEmbed = this.embed(task + ' ' + (file || '')); + + // Apply SONA micro-LoRA transformation if available + let adaptedEmbed = taskEmbed; + if (this.sona) { + try { + adaptedEmbed = this.sona.applyMicroLora(taskEmbed); + } catch { + // Use original embedding + } + } + + // Find similar patterns using ReasoningBank + let patterns: LearnedPattern[] = []; + if (this.sona) { + try { + patterns = this.sona.findPatterns(adaptedEmbed, 5); + } catch { + // No patterns + } + } + + // Default agent mappings + const defaults: Record = { + '.rs': 'rust-developer', + '.ts': 'typescript-developer', + '.tsx': 'react-developer', + '.js': 'javascript-developer', + '.jsx': 'react-developer', + '.py': 'python-developer', + '.go': 'go-developer', + '.sql': 'database-specialist', + '.md': 'documentation-specialist', + '.yml': 'devops-engineer', + '.yaml': 'devops-engineer', + '.json': 'coder', + '.toml': 'coder', + }; + + // Check learned patterns first + const statePatterns = this.routingPatterns.get(state); + let bestAgent = defaults[ext] || 'coder'; + let bestScore = 0.5; + let reason = 'default mapping'; + + if (statePatterns && statePatterns.size > 0) { + for (const [agent, score] of statePatterns) { + if (score > bestScore) { + bestAgent = agent; + bestScore = score; + reason = 'learned from patterns'; + } + } + } + + // Check custom agent mappings + if (this.agentMappings.has(ext)) { + const mapped = this.agentMappings.get(ext)!; + if (bestScore < 0.8) { + bestAgent = mapped; + bestScore = 0.8; + reason = 'custom mapping'; + } + } + + // Boost confidence if SONA patterns match + if (patterns.length > 0 && patterns[0].avgQuality > 0.7) { + bestScore = Math.min(1.0, bestScore + 0.1); + reason += ' + SONA pattern match'; + } + + return { + agent: bestAgent, + confidence: Math.min(1.0, bestScore), + reason, + patterns: patterns.length > 0 ? patterns : undefined, + alternates: this.getAlternates(statePatterns, bestAgent), + }; + } + + private getExtension(file: string): string { + const idx = file.lastIndexOf('.'); + return idx >= 0 ? file.slice(idx).toLowerCase() : ''; + } + + private getState(task: string, ext: string): string { + const taskType = task.includes('fix') ? 'fix' : + task.includes('test') ? 'test' : + task.includes('refactor') ? 'refactor' : + task.includes('document') ? 'docs' : 'edit'; + return `${taskType}:${ext || 'unknown'}`; + } + + private getAlternates(patterns: Map | undefined, exclude: string): Array<{ agent: string; confidence: number }> { + if (!patterns) return []; + return Array.from(patterns.entries()) + .filter(([a]) => a !== exclude) + .sort((a, b) => b[1] - a[1]) + .slice(0, 3) + .map(([agent, confidence]) => ({ agent, confidence: Math.min(1.0, confidence) })); + } + + // ========================================================================= + // Trajectory Learning + // ========================================================================= + + /** + * Begin recording a trajectory (before edit/command) + */ + beginTrajectory(context: string, file?: string): void { + const embed = this.embed(context + ' ' + (file || '')); + + if (this.sona) { + try { + this.currentTrajectoryId = this.sona.beginTrajectory(embed); + if (file) { + this.sona.addContext(this.currentTrajectoryId, file); + } + } catch { + this.currentTrajectoryId = null; + } + } + } + + /** + * Add a step to the current trajectory + */ + addTrajectoryStep(activations: number[], reward: number): void { + if (this.sona && this.currentTrajectoryId !== null) { + try { + const attentionWeights = new Array(activations.length).fill(1 / activations.length); + this.sona.addStep(this.currentTrajectoryId, activations, attentionWeights, reward); + } catch { + // Ignore step errors + } + } + } + + /** + * End the current trajectory with a quality score + */ + endTrajectory(success: boolean, quality?: number): void { + const q = quality ?? (success ? 0.9 : 0.3); + + if (this.sona && this.currentTrajectoryId !== null) { + try { + this.sona.endTrajectory(this.currentTrajectoryId, q); + } catch { + // Ignore end errors + } + } + + this.currentTrajectoryId = null; + } + + /** + * Set the agent route for current trajectory + */ + setTrajectoryRoute(agent: string): void { + if (this.sona && this.currentTrajectoryId !== null) { + try { + this.sona.setRoute(this.currentTrajectoryId, agent); + } catch { + // Ignore route errors + } + } + } + + // ========================================================================= + // Episode Learning (Q-learning compatible) + // ========================================================================= + + /** + * Record an episode for learning + */ + async recordEpisode( + state: string, + action: string, + reward: number, + nextState: string, + done: boolean, + metadata?: Record + ): Promise { + const stateEmbed = this.embed(state); + const nextStateEmbed = this.embed(nextState); + + // Store in FastAgentDB + await this.agentDb.storeEpisode({ + state: stateEmbed, + action, + reward, + nextState: nextStateEmbed, + done, + metadata, + }); + + // Update routing patterns (Q-learning style) + if (!this.routingPatterns.has(state)) { + this.routingPatterns.set(state, new Map()); + } + const patterns = this.routingPatterns.get(state)!; + const oldValue = patterns.get(action) || 0.5; + const newValue = oldValue + this.config.learningRate * (reward - oldValue); + patterns.set(action, newValue); + } + + /** + * Learn from similar past episodes + */ + async learnFromSimilar(state: string, k: number = 5): Promise { + const stateEmbed = this.embed(state); + return this.agentDb.searchByState(stateEmbed, k); + } + + // ========================================================================= + // Co-edit Pattern Learning + // ========================================================================= + + /** + * Record a co-edit pattern + */ + recordCoEdit(file1: string, file2: string): void { + if (!this.coEditPatterns.has(file1)) { + this.coEditPatterns.set(file1, new Map()); + } + if (!this.coEditPatterns.has(file2)) { + this.coEditPatterns.set(file2, new Map()); + } + + const count1 = this.coEditPatterns.get(file1)!.get(file2) || 0; + this.coEditPatterns.get(file1)!.set(file2, count1 + 1); + + const count2 = this.coEditPatterns.get(file2)!.get(file1) || 0; + this.coEditPatterns.get(file2)!.set(file1, count2 + 1); + } + + /** + * Get likely next files to edit + */ + getLikelyNextFiles(file: string, topK: number = 5): Array<{ file: string; count: number }> { + const related = this.coEditPatterns.get(file); + if (!related) return []; + + return Array.from(related.entries()) + .sort((a, b) => b[1] - a[1]) + .slice(0, topK) + .map(([f, count]) => ({ file: f, count })); + } + + // ========================================================================= + // Error Pattern Learning + // ========================================================================= + + /** + * Record an error pattern with fixes + */ + recordErrorFix(errorPattern: string, fix: string): void { + if (!this.errorPatterns.has(errorPattern)) { + this.errorPatterns.set(errorPattern, []); + } + const fixes = this.errorPatterns.get(errorPattern)!; + if (!fixes.includes(fix)) { + fixes.push(fix); + } + } + + /** + * Get suggested fixes for an error + */ + getSuggestedFixes(error: string): string[] { + // Exact match + if (this.errorPatterns.has(error)) { + return this.errorPatterns.get(error)!; + } + + // Fuzzy match by embedding similarity + const errorEmbed = this.embed(error); + const matches: Array<{ pattern: string; similarity: number; fixes: string[] }> = []; + + for (const [pattern, fixes] of this.errorPatterns) { + const patternEmbed = this.embed(pattern); + const similarity = this.cosineSimilarity(errorEmbed, patternEmbed); + if (similarity > 0.7) { + matches.push({ pattern, similarity, fixes }); + } + } + + if (matches.length === 0) return []; + + // Return fixes from most similar pattern + matches.sort((a, b) => b.similarity - a.similarity); + return matches[0].fixes; + } + + // ========================================================================= + // Tick / Background Learning + // ========================================================================= + + /** + * Run background learning cycle + */ + tick(): string | null { + if (this.sona) { + try { + return this.sona.tick(); + } catch { + return null; + } + } + return null; + } + + /** + * Force immediate learning + */ + forceLearn(): string | null { + if (this.sona) { + try { + return this.sona.forceLearn(); + } catch { + return null; + } + } + return null; + } + + // ========================================================================= + // Statistics + // ========================================================================= + + /** + * Get comprehensive learning statistics + */ + getStats(): LearningStats { + const agentDbStats = this.agentDb.getStats(); + + let sonaStats: SonaStats | null = null; + if (this.sona) { + try { + sonaStats = this.sona.getStats(); + } catch { + // No SONA stats + } + } + + // Calculate average reward from patterns + let totalReward = 0; + let rewardCount = 0; + for (const patterns of this.routingPatterns.values()) { + for (const reward of patterns.values()) { + totalReward += reward; + rewardCount++; + } + } + + return { + totalMemories: this.memories.size, + memoryDimensions: this.config.embeddingDim, + + totalEpisodes: agentDbStats.episodeCount, + totalTrajectories: agentDbStats.trajectoryCount, + avgReward: rewardCount > 0 ? totalReward / rewardCount : 0, + + sonaEnabled: this.sona !== null, + trajectoriesRecorded: sonaStats?.trajectoriesRecorded ?? 0, + patternsLearned: sonaStats?.patternsLearned ?? 0, + microLoraUpdates: sonaStats?.microLoraUpdates ?? 0, + baseLoraUpdates: sonaStats?.baseLoraUpdates ?? 0, + ewcConsolidations: sonaStats?.ewcConsolidations ?? 0, + + routingPatterns: this.routingPatterns.size, + errorPatterns: this.errorPatterns.size, + coEditPatterns: this.coEditPatterns.size, + + attentionEnabled: this.attention !== null, + }; + } + + // ========================================================================= + // Persistence + // ========================================================================= + + /** + * Export all data for persistence + */ + export(): Record { + return { + version: '2.0.0', + exported: new Date().toISOString(), + config: this.config, + + memories: Array.from(this.memories.values()), + + routingPatterns: Object.fromEntries( + Array.from(this.routingPatterns.entries()).map(([k, v]) => [ + k, + Object.fromEntries(v), + ]) + ), + + errorPatterns: Object.fromEntries(this.errorPatterns), + + coEditPatterns: Object.fromEntries( + Array.from(this.coEditPatterns.entries()).map(([k, v]) => [ + k, + Object.fromEntries(v), + ]) + ), + + agentMappings: Object.fromEntries(this.agentMappings), + + stats: this.getStats(), + }; + } + + /** + * Import data from persistence + */ + import(data: Record, merge: boolean = false): void { + if (!merge) { + this.memories.clear(); + this.routingPatterns.clear(); + this.errorPatterns.clear(); + this.coEditPatterns.clear(); + this.agentMappings.clear(); + } + + // Import memories + if (data.memories) { + for (const mem of data.memories) { + this.memories.set(mem.id, mem); + } + } + + // Import routing patterns + if (data.routingPatterns) { + for (const [state, actions] of Object.entries(data.routingPatterns)) { + const map = new Map(Object.entries(actions as Record)); + if (merge && this.routingPatterns.has(state)) { + const existing = this.routingPatterns.get(state)!; + for (const [action, value] of map) { + existing.set(action, Math.max(existing.get(action) || 0, value)); + } + } else { + this.routingPatterns.set(state, map); + } + } + } + + // Import error patterns + if (data.errorPatterns) { + for (const [pattern, fixes] of Object.entries(data.errorPatterns)) { + if (merge && this.errorPatterns.has(pattern)) { + const existing = this.errorPatterns.get(pattern)!; + for (const fix of fixes as string[]) { + if (!existing.includes(fix)) existing.push(fix); + } + } else { + this.errorPatterns.set(pattern, fixes as string[]); + } + } + } + + // Import co-edit patterns + if (data.coEditPatterns) { + for (const [file, related] of Object.entries(data.coEditPatterns)) { + const map = new Map(Object.entries(related as Record)); + if (merge && this.coEditPatterns.has(file)) { + const existing = this.coEditPatterns.get(file)!; + for (const [f, count] of map) { + existing.set(f, (existing.get(f) || 0) + count); + } + } else { + this.coEditPatterns.set(file, map); + } + } + } + + // Import agent mappings + if (data.agentMappings) { + for (const [ext, agent] of Object.entries(data.agentMappings)) { + this.agentMappings.set(ext, agent as string); + } + } + } + + /** + * Clear all data + */ + clear(): void { + this.memories.clear(); + this.routingPatterns.clear(); + this.errorPatterns.clear(); + this.coEditPatterns.clear(); + this.agentMappings.clear(); + this.agentDb.clear(); + } + + // ========================================================================= + // Compatibility with existing Intelligence class + // ========================================================================= + + /** Legacy: patterns object */ + get patterns(): Record> { + const result: Record> = {}; + for (const [state, actions] of this.routingPatterns) { + result[state] = Object.fromEntries(actions); + } + return result; + } + + /** Legacy: file_sequences array */ + get file_sequences(): string[][] { + const sequences: string[][] = []; + for (const [file, related] of this.coEditPatterns) { + const sorted = Array.from(related.entries()) + .sort((a, b) => b[1] - a[1]) + .map(([f]) => f); + if (sorted.length > 0) { + sequences.push([file, ...sorted.slice(0, 3)]); + } + } + return sequences; + } + + /** Legacy: errors object */ + get errors(): Record { + return Object.fromEntries(this.errorPatterns); + } +} + +// ============================================================================ +// Factory Functions +// ============================================================================ + +/** + * Create a new IntelligenceEngine with default settings + */ +export function createIntelligenceEngine(config?: IntelligenceConfig): IntelligenceEngine { + return new IntelligenceEngine(config); +} + +/** + * Create a high-performance engine with all features enabled + */ +export function createHighPerformanceEngine(): IntelligenceEngine { + return new IntelligenceEngine({ + embeddingDim: 512, + maxMemories: 200000, + maxEpisodes: 100000, + enableSona: true, + enableAttention: true, + sonaConfig: { + hiddenDim: 512, + microLoraRank: 2, + baseLoraRank: 16, + patternClusters: 200, + }, + }); +} + +/** + * Create a lightweight engine for fast startup + */ +export function createLightweightEngine(): IntelligenceEngine { + return new IntelligenceEngine({ + embeddingDim: 128, + maxMemories: 10000, + maxEpisodes: 5000, + enableSona: false, + enableAttention: false, + }); +} + +export default IntelligenceEngine;