UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

1,199 lines (1,198 loc) 168 kB
/** * V3 CLI Hooks Command * Self-learning hooks system for intelligent workflow automation */ import { output } from '../output.js'; import { confirm } from '../prompt.js'; import { callMCPTool, MCPClientError } from '../mcp-client.js'; import { storeCommand } from './transfer-store.js'; // Hook types const HOOK_TYPES = [ { value: 'pre-edit', label: 'Pre-Edit', hint: 'Get context before editing files' }, { value: 'post-edit', label: 'Post-Edit', hint: 'Record editing outcomes' }, { value: 'pre-command', label: 'Pre-Command', hint: 'Assess risk before commands' }, { value: 'post-command', label: 'Post-Command', hint: 'Record command outcomes' }, { value: 'route', label: 'Route', hint: 'Route tasks to optimal agents' }, { value: 'explain', label: 'Explain', hint: 'Explain routing decisions' } ]; // Agent routing options const AGENT_TYPES = [ 'coder', 'researcher', 'tester', 'reviewer', 'architect', 'security-architect', 'security-auditor', 'memory-specialist', 'swarm-specialist', 'performance-engineer', 'core-architect', 'test-architect', 'coordinator', 'analyst', 'optimizer' ]; // Pre-edit subcommand const preEditCommand = { name: 'pre-edit', description: 'Get context and agent suggestions before editing a file', options: [ { name: 'file', short: 'f', description: 'File path to edit', type: 'string', required: false }, { name: 'operation', short: 'o', description: 'Type of edit operation (create, update, delete, refactor)', type: 'string', default: 'update' }, { name: 'context', short: 'c', description: 'Additional context about the edit', type: 'string' } ], examples: [ { command: 'claude-flow hooks pre-edit -f src/utils.ts', description: 'Get context before editing' }, { command: 'claude-flow hooks pre-edit -f src/api.ts -o refactor', description: 'Pre-edit with operation type' } ], action: async (ctx) => { // Default file to 'unknown' for backward compatibility (env var may be empty) const filePath = ctx.args[0] || ctx.flags.file || 'unknown'; const operation = ctx.flags.operation || 'update'; output.printInfo(`Analyzing context for: ${output.highlight(filePath)}`); try { // Call MCP tool for pre-edit hook const result = await callMCPTool('hooks_pre-edit', { filePath, operation, context: ctx.flags.context, includePatterns: true, includeRisks: true, }); if (ctx.flags.format === 'json') { output.printJson(result); return { success: true, data: result }; } output.writeln(); output.printBox([ `File: ${result.filePath}`, `Operation: ${result.operation}`, `Type: ${result.context.fileType}`, `Exists: ${result.context.fileExists ? 'Yes' : 'No'}` ].join('\n'), 'File Context'); if (result.context.suggestedAgents.length > 0) { output.writeln(); output.writeln(output.bold('Suggested Agents')); output.printList(result.context.suggestedAgents.map(a => output.highlight(a))); } if (result.context.relatedFiles.length > 0) { output.writeln(); output.writeln(output.bold('Related Files')); output.printList(result.context.relatedFiles.slice(0, 5).map(f => output.dim(f))); } if (result.context.patterns.length > 0) { output.writeln(); output.writeln(output.bold('Learned Patterns')); output.printTable({ columns: [ { key: 'pattern', header: 'Pattern', width: 40 }, { key: 'confidence', header: 'Confidence', width: 12, align: 'right', format: (v) => `${(Number(v) * 100).toFixed(1)}%` } ], data: result.context.patterns }); } if (result.context.risks.length > 0) { output.writeln(); output.writeln(output.bold(output.error('Potential Risks'))); output.printList(result.context.risks.map(r => output.warning(r))); } if (result.recommendations.length > 0) { output.writeln(); output.writeln(output.bold('Recommendations')); output.printList(result.recommendations.map(r => output.success(`• ${r}`))); } return { success: true, data: result }; } catch (error) { if (error instanceof MCPClientError) { output.printError(`Pre-edit hook failed: ${error.message}`); } else { output.printError(`Unexpected error: ${String(error)}`); } return { success: false, exitCode: 1 }; } } }; // Post-edit subcommand const postEditCommand = { name: 'post-edit', description: 'Record editing outcome for learning', options: [ { name: 'file', short: 'f', description: 'File path that was edited', type: 'string', required: false }, { name: 'success', short: 's', description: 'Whether the edit was successful', type: 'boolean', required: false }, { name: 'outcome', short: 'o', description: 'Outcome description', type: 'string' }, { name: 'metrics', short: 'm', description: 'Performance metrics (e.g., "time:500ms,quality:0.95")', type: 'string' } ], examples: [ { command: 'claude-flow hooks post-edit -f src/utils.ts --success true', description: 'Record successful edit' }, { command: 'claude-flow hooks post-edit -f src/api.ts --success false -o "Type error"', description: 'Record failed edit' } ], action: async (ctx) => { // Default file to 'unknown' for backward compatibility (env var may be empty) const filePath = ctx.args[0] || ctx.flags.file || 'unknown'; // Default success to true for backward compatibility (PostToolUse = success, PostToolUseFailure = failure) const success = ctx.flags.success !== undefined ? ctx.flags.success : true; output.printInfo(`Recording outcome for: ${output.highlight(filePath)}`); try { // Parse metrics if provided const metrics = {}; if (ctx.flags.metrics) { const metricsStr = ctx.flags.metrics; metricsStr.split(',').forEach(pair => { const [key, value] = pair.split(':'); if (key && value) { metrics[key.trim()] = parseFloat(value); } }); } // Call MCP tool for post-edit hook const result = await callMCPTool('hooks_post-edit', { filePath, success, outcome: ctx.flags.outcome, metrics, timestamp: Date.now(), }); if (ctx.flags.format === 'json') { output.printJson(result); return { success: true, data: result }; } output.writeln(); output.printSuccess(`Outcome recorded for ${filePath}`); if (result.learningUpdates) { output.writeln(); output.writeln(output.bold('Learning Updates')); output.printTable({ columns: [ { key: 'metric', header: 'Metric', width: 25 }, { key: 'value', header: 'Value', width: 15, align: 'right' } ], data: [ { metric: 'Patterns Updated', value: result.learningUpdates.patternsUpdated }, { metric: 'Confidence Adjusted', value: result.learningUpdates.confidenceAdjusted }, { metric: 'New Patterns', value: result.learningUpdates.newPatterns } ] }); } return { success: true, data: result }; } catch (error) { if (error instanceof MCPClientError) { output.printError(`Post-edit hook failed: ${error.message}`); } else { output.printError(`Unexpected error: ${String(error)}`); } return { success: false, exitCode: 1 }; } } }; // Pre-command subcommand const preCommandCommand = { name: 'pre-command', description: 'Assess risk before executing a command', options: [ { name: 'command', short: 'c', description: 'Command to execute', type: 'string', required: true }, { name: 'dry-run', short: 'd', description: 'Only analyze, do not execute', type: 'boolean', default: true } ], examples: [ { command: 'claude-flow hooks pre-command -c "rm -rf dist"', description: 'Assess command risk' }, { command: 'claude-flow hooks pre-command -c "npm install lodash"', description: 'Check package install' } ], action: async (ctx) => { const command = ctx.args[0] || ctx.flags.command; if (!command) { output.printError('Command is required. Use --command or -c flag.'); return { success: false, exitCode: 1 }; } output.printInfo(`Analyzing command: ${output.highlight(command)}`); try { // Call MCP tool for pre-command hook const result = await callMCPTool('hooks_pre-command', { command, includeAlternatives: true, }); if (ctx.flags.format === 'json') { output.printJson(result); return { success: true, data: result }; } output.writeln(); // Risk level indicator let riskIndicator; switch (result.riskLevel) { case 'critical': riskIndicator = output.error('CRITICAL'); break; case 'high': riskIndicator = output.error('HIGH'); break; case 'medium': riskIndicator = output.warning('MEDIUM'); break; default: riskIndicator = output.success('LOW'); } output.printBox([ `Risk Level: ${riskIndicator}`, `Should Proceed: ${result.shouldProceed ? output.success('Yes') : output.error('No')}` ].join('\n'), 'Risk Assessment'); if (result.risks.length > 0) { output.writeln(); output.writeln(output.bold('Identified Risks')); output.printTable({ columns: [ { key: 'type', header: 'Type', width: 15 }, { key: 'severity', header: 'Severity', width: 10 }, { key: 'description', header: 'Description', width: 40 } ], data: result.risks }); } if (result.safeAlternatives && result.safeAlternatives.length > 0) { output.writeln(); output.writeln(output.bold('Safe Alternatives')); output.printList(result.safeAlternatives.map(a => output.success(a))); } if (result.recommendations.length > 0) { output.writeln(); output.writeln(output.bold('Recommendations')); output.printList(result.recommendations); } return { success: true, data: result }; } catch (error) { if (error instanceof MCPClientError) { output.printError(`Pre-command hook failed: ${error.message}`); } else { output.printError(`Unexpected error: ${String(error)}`); } return { success: false, exitCode: 1 }; } } }; // Post-command subcommand const postCommandCommand = { name: 'post-command', description: 'Record command execution outcome', options: [ { name: 'command', short: 'c', description: 'Command that was executed', type: 'string', required: true }, { name: 'success', short: 's', description: 'Whether the command succeeded', type: 'boolean', required: false }, { name: 'exit-code', short: 'e', description: 'Command exit code', type: 'number', default: 0 }, { name: 'duration', short: 'd', description: 'Execution duration in milliseconds', type: 'number' } ], examples: [ { command: 'claude-flow hooks post-command -c "npm test" --success true', description: 'Record successful test run' }, { command: 'claude-flow hooks post-command -c "npm build" --success false -e 1', description: 'Record failed build' } ], action: async (ctx) => { const command = ctx.args[0] || ctx.flags.command; // Default success to true for backward compatibility const success = ctx.flags.success !== undefined ? ctx.flags.success : true; if (!command) { output.printError('Command is required. Use --command or -c flag.'); return { success: false, exitCode: 1 }; } output.printInfo(`Recording command outcome: ${output.highlight(command)}`); try { // Call MCP tool for post-command hook const result = await callMCPTool('hooks_post-command', { command, success, exitCode: ctx.flags.exitCode || 0, duration: ctx.flags.duration, timestamp: Date.now(), }); if (ctx.flags.format === 'json') { output.printJson(result); return { success: true, data: result }; } output.writeln(); output.printSuccess('Command outcome recorded'); if (result.learningUpdates) { output.writeln(); output.writeln(output.dim(`Patterns updated: ${result.learningUpdates.commandPatternsUpdated}`)); output.writeln(output.dim(`Risk assessment: ${result.learningUpdates.riskAssessmentUpdated ? 'Updated' : 'No change'}`)); } return { success: true, data: result }; } catch (error) { if (error instanceof MCPClientError) { output.printError(`Post-command hook failed: ${error.message}`); } else { output.printError(`Unexpected error: ${String(error)}`); } return { success: false, exitCode: 1 }; } } }; // Route subcommand const routeCommand = { name: 'route', description: 'Route task to optimal agent using learned patterns', options: [ { name: 'task', short: 't', description: 'Task description', type: 'string', required: true }, { name: 'context', short: 'c', description: 'Additional context', type: 'string' }, { name: 'top-k', short: 'K', description: 'Number of top agent suggestions', type: 'number', default: 3 } ], examples: [ { command: 'claude-flow hooks route -t "Fix authentication bug"', description: 'Route task to optimal agent' }, { command: 'claude-flow hooks route -t "Optimize database queries" -K 5', description: 'Get top 5 suggestions' } ], action: async (ctx) => { const task = ctx.args[0] || ctx.flags.task; const topK = ctx.flags.topK || 3; if (!task) { output.printError('Task description is required. Use --task or -t flag.'); return { success: false, exitCode: 1 }; } output.printInfo(`Routing task: ${output.highlight(task)}`); try { // Call MCP tool for routing const result = await callMCPTool('hooks_route', { task, context: ctx.flags.context, topK, includeEstimates: true, }); if (ctx.flags.format === 'json') { output.printJson(result); return { success: true, data: result }; } // Show routing method info if (result.routing) { output.writeln(); output.writeln(output.bold('Routing Method')); const methodDisplay = result.routing.method.startsWith('semantic') ? output.success(`${result.routing.method} (${result.routing.backend || 'semantic'})`) : 'keyword'; output.printList([ `Method: ${methodDisplay}`, result.routing.backend ? `Backend: ${result.routing.backend}` : null, `Latency: ${result.routing.latencyMs.toFixed(3)}ms`, result.matchedPattern ? `Matched Pattern: ${result.matchedPattern}` : null, ].filter(Boolean)); // Show semantic matches if available if (result.semanticMatches && result.semanticMatches.length > 0) { output.writeln(); output.writeln(output.dim('Semantic Matches:')); result.semanticMatches.forEach(m => { output.writeln(` ${m.pattern}: ${(m.score * 100).toFixed(1)}%`); }); } } output.writeln(); output.printBox([ `Agent: ${output.highlight(result.primaryAgent.type)}`, `Confidence: ${(result.primaryAgent.confidence * 100).toFixed(1)}%`, `Reason: ${result.primaryAgent.reason}` ].join('\n'), 'Primary Recommendation'); if (result.alternativeAgents.length > 0) { output.writeln(); output.writeln(output.bold('Alternative Agents')); output.printTable({ columns: [ { key: 'type', header: 'Agent Type', width: 20 }, { key: 'confidence', header: 'Confidence', width: 12, align: 'right', format: (v) => `${(Number(v) * 100).toFixed(1)}%` }, { key: 'reason', header: 'Reason', width: 35 } ], data: result.alternativeAgents }); } if (result.estimatedMetrics) { output.writeln(); output.writeln(output.bold('Estimated Metrics')); output.printList([ `Success Probability: ${(result.estimatedMetrics.successProbability * 100).toFixed(1)}%`, `Estimated Duration: ${result.estimatedMetrics.estimatedDuration}`, `Complexity: ${result.estimatedMetrics.complexity.toUpperCase()}` ]); } return { success: true, data: result }; } catch (error) { if (error instanceof MCPClientError) { output.printError(`Routing failed: ${error.message}`); } else { output.printError(`Unexpected error: ${String(error)}`); } return { success: false, exitCode: 1 }; } } }; // Explain subcommand const explainCommand = { name: 'explain', description: 'Explain routing decision with transparency', options: [ { name: 'task', short: 't', description: 'Task description', type: 'string', required: true }, { name: 'agent', short: 'a', description: 'Agent type to explain', type: 'string' }, { name: 'verbose', short: 'v', description: 'Verbose explanation', type: 'boolean', default: false } ], examples: [ { command: 'claude-flow hooks explain -t "Fix authentication bug"', description: 'Explain routing decision' }, { command: 'claude-flow hooks explain -t "Optimize queries" -a coder --verbose', description: 'Verbose explanation for specific agent' } ], action: async (ctx) => { const task = ctx.args[0] || ctx.flags.task; if (!task) { output.printError('Task description is required. Use --task or -t flag.'); return { success: false, exitCode: 1 }; } output.printInfo(`Explaining routing for: ${output.highlight(task)}`); try { // Call MCP tool for explanation const result = await callMCPTool('hooks_explain', { task, agent: ctx.flags.agent, verbose: ctx.flags.verbose || false, }); if (ctx.flags.format === 'json') { output.printJson(result); return { success: true, data: result }; } output.writeln(); output.writeln(output.bold('Decision Explanation')); output.writeln(); output.writeln(result.explanation); output.writeln(); output.printBox([ `Agent: ${output.highlight(result.decision.agent)}`, `Confidence: ${(result.decision.confidence * 100).toFixed(1)}%` ].join('\n'), 'Final Decision'); if (result.decision.reasoning.length > 0) { output.writeln(); output.writeln(output.bold('Reasoning Steps')); output.printList(result.decision.reasoning.map((r, i) => `${i + 1}. ${r}`)); } if (result.factors.length > 0) { output.writeln(); output.writeln(output.bold('Decision Factors')); output.printTable({ columns: [ { key: 'factor', header: 'Factor', width: 20 }, { key: 'weight', header: 'Weight', width: 10, align: 'right', format: (v) => `${(Number(v) * 100).toFixed(0)}%` }, { key: 'value', header: 'Value', width: 10, align: 'right', format: (v) => Number(v).toFixed(2) }, { key: 'impact', header: 'Impact', width: 25 } ], data: result.factors }); } if (result.patterns.length > 0 && ctx.flags.verbose) { output.writeln(); output.writeln(output.bold('Matched Patterns')); result.patterns.forEach((p, i) => { output.writeln(); output.writeln(`${i + 1}. ${output.highlight(p.pattern)} (${(p.matchScore * 100).toFixed(1)}% match)`); if (p.examples.length > 0) { output.printList(p.examples.slice(0, 3).map(e => output.dim(` ${e}`))); } }); } return { success: true, data: result }; } catch (error) { if (error instanceof MCPClientError) { output.printError(`Explanation failed: ${error.message}`); } else { output.printError(`Unexpected error: ${String(error)}`); } return { success: false, exitCode: 1 }; } } }; // Pretrain subcommand const pretrainCommand = { name: 'pretrain', description: 'Bootstrap intelligence from repository (4-step pipeline + embeddings)', options: [ { name: 'path', short: 'p', description: 'Repository path', type: 'string', default: '.' }, { name: 'depth', short: 'd', description: 'Analysis depth (shallow, medium, deep)', type: 'string', default: 'medium', choices: ['shallow', 'medium', 'deep'] }, { name: 'skip-cache', description: 'Skip cached analysis', type: 'boolean', default: false }, { name: 'with-embeddings', description: 'Index documents for semantic search during pretraining', type: 'boolean', default: true }, { name: 'embedding-model', description: 'ONNX embedding model', type: 'string', default: 'all-MiniLM-L6-v2', choices: ['all-MiniLM-L6-v2', 'all-mpnet-base-v2'] }, { name: 'file-types', description: 'File extensions to index (comma-separated)', type: 'string', default: 'ts,js,py,md,json' } ], examples: [ { command: 'claude-flow hooks pretrain', description: 'Pretrain with embeddings indexing' }, { command: 'claude-flow hooks pretrain -p ../my-project --depth deep', description: 'Deep analysis of specific project' }, { command: 'claude-flow hooks pretrain --no-with-embeddings', description: 'Skip embedding indexing' }, { command: 'claude-flow hooks pretrain --file-types ts,tsx,js', description: 'Index only TypeScript/JS files' } ], action: async (ctx) => { const repoPath = ctx.flags.path || '.'; const depth = ctx.flags.depth || 'medium'; const withEmbeddings = ctx.flags['with-embeddings'] !== false && ctx.flags.withEmbeddings !== false; const embeddingModel = (ctx.flags['embedding-model'] || ctx.flags.embeddingModel || 'all-MiniLM-L6-v2'); const fileTypes = (ctx.flags['file-types'] || ctx.flags.fileTypes || 'ts,js,py,md,json'); output.writeln(); output.writeln(output.bold('Pretraining Intelligence (4-Step Pipeline + Embeddings)')); output.writeln(); const steps = [ { name: 'RETRIEVE', desc: 'Top-k memory injection with MMR diversity' }, { name: 'JUDGE', desc: 'LLM-as-judge trajectory evaluation' }, { name: 'DISTILL', desc: 'Extract strategy memories from trajectories' }, { name: 'CONSOLIDATE', desc: 'Dedup, detect contradictions, prune old patterns' } ]; // Add embedding steps if enabled if (withEmbeddings) { steps.push({ name: 'EMBED', desc: `Index documents with ${embeddingModel} (ONNX)` }, { name: 'HYPERBOLIC', desc: 'Project to Poincaré ball for hierarchy preservation' }); } const spinner = output.createSpinner({ text: 'Starting pretraining...', spinner: 'dots' }); try { spinner.start(); // Display progress for each step for (const step of steps) { spinner.setText(`${step.name}: ${step.desc}`); await new Promise(resolve => setTimeout(resolve, 800)); } // Call MCP tool for pretraining const result = await callMCPTool('hooks_pretrain', { path: repoPath, depth, skipCache: ctx.flags.skipCache || false, withEmbeddings, embeddingModel, fileTypes: fileTypes.split(',').map((t) => t.trim()), }); spinner.succeed('Pretraining completed'); if (ctx.flags.format === 'json') { output.printJson(result); return { success: true, data: result }; } output.writeln(); // Base stats const tableData = [ { metric: 'Files Analyzed', value: result.stats.filesAnalyzed }, { metric: 'Patterns Extracted', value: result.stats.patternsExtracted }, { metric: 'Strategies Learned', value: result.stats.strategiesLearned }, { metric: 'Trajectories Evaluated', value: result.stats.trajectoriesEvaluated }, { metric: 'Contradictions Resolved', value: result.stats.contradictionsResolved }, ]; // Add embedding stats if available if (withEmbeddings && result.stats.documentsIndexed !== undefined) { tableData.push({ metric: 'Documents Indexed', value: result.stats.documentsIndexed }, { metric: 'Embeddings Generated', value: result.stats.embeddingsGenerated || 0 }, { metric: 'Hyperbolic Projections', value: result.stats.hyperbolicProjections || 0 }); } tableData.push({ metric: 'Duration', value: `${(result.duration / 1000).toFixed(1)}s` }); output.printTable({ columns: [ { key: 'metric', header: 'Metric', width: 30 }, { key: 'value', header: 'Value', width: 15, align: 'right' } ], data: tableData }); output.writeln(); output.printSuccess('Repository intelligence bootstrapped successfully'); if (withEmbeddings) { output.writeln(output.dim(' Semantic search enabled: Use "embeddings search -q <query>" to search')); } output.writeln(output.dim(' Next step: Run "claude-flow hooks build-agents" to generate optimized configs')); return { success: true, data: result }; } catch (error) { spinner.fail('Pretraining failed'); if (error instanceof MCPClientError) { output.printError(`Pretraining error: ${error.message}`); } else { output.printError(`Unexpected error: ${String(error)}`); } return { success: false, exitCode: 1 }; } } }; // Build agents subcommand const buildAgentsCommand = { name: 'build-agents', description: 'Generate optimized agent configs from pretrain data', options: [ { name: 'output', short: 'o', description: 'Output directory for agent configs', type: 'string', default: './agents' }, { name: 'focus', short: 'f', description: 'Focus area (v3-implementation, security, performance, all)', type: 'string', default: 'all' }, { name: 'config-format', description: 'Config format (yaml, json)', type: 'string', default: 'yaml', choices: ['yaml', 'json'] } ], examples: [ { command: 'claude-flow hooks build-agents', description: 'Build all agent configs' }, { command: 'claude-flow hooks build-agents --focus security -o ./config/agents', description: 'Build security-focused configs' } ], action: async (ctx) => { const output_dir = ctx.flags.output || './agents'; const focus = ctx.flags.focus || 'all'; const configFormat = ctx.flags.configFormat || 'yaml'; output.printInfo(`Building agent configs (focus: ${output.highlight(focus)})`); const spinner = output.createSpinner({ text: 'Generating configs...', spinner: 'dots' }); try { spinner.start(); // Call MCP tool for building agents const result = await callMCPTool('hooks_build-agents', { outputDir: output_dir, focus, format: configFormat, includePretrained: true, }); spinner.succeed(`Generated ${result.agents.length} agent configs`); if (ctx.flags.format === 'json') { output.printJson(result); return { success: true, data: result }; } output.writeln(); output.writeln(output.bold('Generated Agent Configs')); output.printTable({ columns: [ { key: 'type', header: 'Agent Type', width: 20 }, { key: 'configFile', header: 'Config File', width: 30 }, { key: 'capabilities', header: 'Capabilities', width: 10, align: 'right', format: (v) => String(Array.isArray(v) ? v.length : 0) } ], data: result.agents }); output.writeln(); output.printTable({ columns: [ { key: 'metric', header: 'Metric', width: 30 }, { key: 'value', header: 'Value', width: 15, align: 'right' } ], data: [ { metric: 'Configs Generated', value: result.stats.configsGenerated }, { metric: 'Patterns Applied', value: result.stats.patternsApplied }, { metric: 'Optimizations Included', value: result.stats.optimizationsIncluded } ] }); output.writeln(); output.printSuccess(`Agent configs saved to ${output_dir}`); return { success: true, data: result }; } catch (error) { spinner.fail('Agent config generation failed'); if (error instanceof MCPClientError) { output.printError(`Build agents error: ${error.message}`); } else { output.printError(`Unexpected error: ${String(error)}`); } return { success: false, exitCode: 1 }; } } }; // Metrics subcommand const metricsCommand = { name: 'metrics', description: 'View learning metrics dashboard', options: [ { name: 'period', short: 'p', description: 'Time period (1h, 24h, 7d, 30d, all)', type: 'string', default: '24h' }, { name: 'v3-dashboard', description: 'Show V3 performance dashboard', type: 'boolean', default: false }, { name: 'category', short: 'c', description: 'Metric category (patterns, agents, commands, performance)', type: 'string' } ], examples: [ { command: 'claude-flow hooks metrics', description: 'View 24h metrics' }, { command: 'claude-flow hooks metrics --period 7d --v3-dashboard', description: 'V3 metrics for 7 days' } ], action: async (ctx) => { const period = ctx.flags.period || '24h'; const v3Dashboard = ctx.flags.v3Dashboard; output.writeln(); output.writeln(output.bold(`Learning Metrics Dashboard (${period})`)); output.writeln(); try { // Call MCP tool for metrics const result = await callMCPTool('hooks_metrics', { period, includeV3: v3Dashboard, category: ctx.flags.category, }); if (ctx.flags.format === 'json') { output.printJson(result); return { success: true, data: result }; } // Patterns section output.writeln(output.bold('📊 Pattern Learning')); output.printTable({ columns: [ { key: 'metric', header: 'Metric', width: 25 }, { key: 'value', header: 'Value', width: 20, align: 'right' } ], data: [ { metric: 'Total Patterns', value: result.patterns.total }, { metric: 'Successful', value: output.success(String(result.patterns.successful)) }, { metric: 'Failed', value: output.error(String(result.patterns.failed)) }, { metric: 'Avg Confidence', value: `${(result.patterns.avgConfidence * 100).toFixed(1)}%` } ] }); output.writeln(); // Agent routing section output.writeln(output.bold('🤖 Agent Routing')); output.printTable({ columns: [ { key: 'metric', header: 'Metric', width: 25 }, { key: 'value', header: 'Value', width: 20, align: 'right' } ], data: [ { metric: 'Routing Accuracy', value: `${(result.agents.routingAccuracy * 100).toFixed(1)}%` }, { metric: 'Total Routes', value: result.agents.totalRoutes }, { metric: 'Top Agent', value: output.highlight(result.agents.topAgent) } ] }); output.writeln(); // Command execution section output.writeln(output.bold('⚡ Command Execution')); output.printTable({ columns: [ { key: 'metric', header: 'Metric', width: 25 }, { key: 'value', header: 'Value', width: 20, align: 'right' } ], data: [ { metric: 'Total Executed', value: result.commands.totalExecuted }, { metric: 'Success Rate', value: `${(result.commands.successRate * 100).toFixed(1)}%` }, { metric: 'Avg Risk Score', value: result.commands.avgRiskScore.toFixed(2) } ] }); if (v3Dashboard && result.performance) { const p = result.performance; output.writeln(); output.writeln(output.bold('🚀 V3 Performance Gains')); output.printList([ `Flash Attention: ${output.success(p.flashAttention ?? 'N/A')}`, `Memory Reduction: ${output.success(p.memoryReduction ?? 'N/A')}`, `Search Improvement: ${output.success(p.searchImprovement ?? 'N/A')}`, `Token Reduction: ${output.success(p.tokenReduction ?? 'N/A')}` ]); } return { success: true, data: result }; } catch (error) { if (error instanceof MCPClientError) { output.printError(`Metrics error: ${error.message}`); } else { output.printError(`Unexpected error: ${String(error)}`); } return { success: false, exitCode: 1 }; } } }; // Pattern Store command (imported from transfer-store.ts) // storeCommand is imported at the top // Transfer from project subcommand const transferFromProjectCommand = { name: 'from-project', aliases: ['project'], description: 'Transfer patterns from another project', options: [ { name: 'source', short: 's', description: 'Source project path', type: 'string', required: true }, { name: 'filter', short: 'f', description: 'Filter patterns by type', type: 'string' }, { name: 'min-confidence', short: 'm', description: 'Minimum confidence threshold (0-1)', type: 'number', default: 0.7 } ], examples: [ { command: 'claude-flow hooks transfer from-project -s ../old-project', description: 'Transfer all patterns' }, { command: 'claude-flow hooks transfer from-project -s ../prod --filter security -m 0.9', description: 'Transfer high-confidence security patterns' } ], action: async (ctx) => { const sourcePath = ctx.args[0] || ctx.flags.source; const minConfidence = ctx.flags.minConfidence || 0.7; if (!sourcePath) { output.printError('Source project path is required. Use --source or -s flag.'); return { success: false, exitCode: 1 }; } output.printInfo(`Transferring patterns from: ${output.highlight(sourcePath)}`); const spinner = output.createSpinner({ text: 'Analyzing source patterns...', spinner: 'dots' }); try { spinner.start(); // Call MCP tool for transfer const result = await callMCPTool('hooks_transfer', { sourcePath, filter: ctx.flags.filter, minConfidence, mergeStrategy: 'keep-highest-confidence', }); spinner.succeed(`Transferred ${result.transferred.total} patterns`); if (ctx.flags.format === 'json') { output.printJson(result); return { success: true, data: result }; } output.writeln(); output.writeln(output.bold('Transfer Summary')); output.printTable({ columns: [ { key: 'category', header: 'Category', width: 25 }, { key: 'count', header: 'Count', width: 15, align: 'right' } ], data: [ { category: 'Total Transferred', count: output.success(String(result.transferred.total)) }, { category: 'Skipped (Low Confidence)', count: result.skipped.lowConfidence }, { category: 'Skipped (Duplicates)', count: result.skipped.duplicates }, { category: 'Skipped (Conflicts)', count: result.skipped.conflicts } ] }); if (Object.keys(result.transferred.byType).length > 0) { output.writeln(); output.writeln(output.bold('By Pattern Type')); output.printTable({ columns: [ { key: 'type', header: 'Type', width: 20 }, { key: 'count', header: 'Count', width: 15, align: 'right' } ], data: Object.entries(result.transferred.byType).map(([type, count]) => ({ type, count })) }); } output.writeln(); output.printList([ `Avg Confidence: ${(result.stats.avgConfidence * 100).toFixed(1)}%`, `Avg Age: ${result.stats.avgAge}` ]); return { success: true, data: result }; } catch (error) { spinner.fail('Transfer failed'); if (error instanceof MCPClientError) { output.printError(`Transfer error: ${error.message}`); } else { output.printError(`Unexpected error: ${String(error)}`); } return { success: false, exitCode: 1 }; } } }; // Parent transfer command combining all transfer methods const transferCommand = { name: 'transfer', description: 'Transfer patterns and plugins via IPFS-based decentralized registry', subcommands: [storeCommand, transferFromProjectCommand], examples: [ { command: 'claude-flow hooks transfer store list', description: 'List patterns from registry' }, { command: 'claude-flow hooks transfer store search -q routing', description: 'Search patterns' }, { command: 'claude-flow hooks transfer store download -p seraphine-genesis', description: 'Download pattern' }, { command: 'claude-flow hooks transfer store publish', description: 'Publish pattern to registry' }, { command: 'claude-flow hooks transfer from-project -s ../other-project', description: 'Transfer from project' }, ], action: async () => { output.writeln(); output.writeln(output.bold('Pattern Transfer System')); output.writeln(output.dim('Decentralized pattern sharing via IPFS')); output.writeln(); output.writeln('Subcommands:'); output.printList([ `${output.highlight('store')} - Pattern marketplace (list, search, download, publish)`, `${output.highlight('from-project')} - Transfer patterns from another project`, ]); output.writeln(); output.writeln(output.bold('IPFS-Based Features:')); output.printList([ 'Decentralized registry via IPNS for discoverability', 'Content-addressed storage for integrity', 'Ed25519 signatures for verification', 'Anonymization levels: minimal, standard, strict, paranoid', 'Trust levels: unverified, community, verified, official', ]); output.writeln(); output.writeln('Run "claude-flow hooks transfer <subcommand> --help" for details'); return { success: true }; } }; // List subcommand const listCommand = { name: 'list', aliases: ['ls'], description: 'List all registered hooks', options: [ { name: 'enabled', short: 'e', description: 'Show only enabled hooks', type: 'boolean', default: false }, { name: 'type', short: 't', description: 'Filter by hook type', type: 'string' } ], action: async (ctx) => { try { // Call MCP tool for list const result = await callMCPTool('hooks_list', { enabled: ctx.flags.enabled || undefined, type: ctx.flags.type || undefined, }); if (ctx.flags.format === 'json') { output.printJson(result); return { success: true, data: result }; } output.writeln(); output.writeln(output.bold('Registered Hooks')); output.writeln(); if (result.hooks.length === 0) { output.printInfo('No hooks found matching criteria'); return { success: true, data: result }; } output.printTable({ columns: [ { key: 'name', header: 'Name', width: 20 }, { key: 'type', header: 'Type', width: 15 }, { key: 'enabled', header: 'Enabled', width: 10, format: (v) => v ? output.success('Yes') : output.dim('No') }, { key: 'priority', header: 'Priority', width: 10, align: 'right' }, { key: 'executionCount', header: 'Executions', width: 12, align: 'right' }, { key: 'lastExecuted', header: 'Last Executed', width: 20, format: (v) => v ? new Date(String(v)).toLocaleString() : 'Never' } ], data: result.hooks }); output.writeln(); output.printInfo(`Total: ${result.total} hooks`); return { success: true, data: result }; } catch (error) { if (error instanceof MCPClientError) { output.printError(`Failed to list hooks: ${error.message}`); } else { output.printError(`Unexpected error: ${String(error)}`); } return { success: false, exitCode: 1 }; } } }; // Pre-task subcommand const preTaskCommand = { name: 'pre-task', description: 'Record task start and get agent suggestions', options: [ { name: 'task-id', short: 'i', description: 'Unique task identifier', type: 'string', required: true }, { name: 'description', short: 'd', description: 'Task description', type: 'string', required: true }, { name: 'auto-spawn', short: 'a', description: 'Auto-spawn suggested agents', type: 'boolean', default: false } ], examples: [ { command: 'claude-flow hooks pre-task -i task-123 -d "Fix auth bug"', description: 'Record task start' }, { command: 'claude-flow hooks pre-task -i task-456 -d "Implement feature" --auto-spawn', description: 'With auto-spawn' } ], action: async (ctx) => { const taskId = ctx.flags.taskId; const description = ctx.args[0] || ctx.flags.description; if (!taskId || !description) { output.printError('Task ID and description are required.'); return { success: false, exitCode: 1 }; } output.printInfo(`Starting task: ${output.highlight(taskId)}`); try { const result = await callMCPTool('hooks_pre-task', { taskId, description, autoSpawn: ctx.flags.autoSpawn || false, timestamp: Date.now(), }); if (ctx.flags.format === 'json') { output.printJson(result); return { success: true, data: result }; } output.writeln(); output.printBox([ `Task ID: ${result.taskId}`, `Description: ${result.description}`, `Complexity: ${result.complexity.toUpperCase()}`, `Est. Duration: ${result.estimatedDuration}` ].join('\n'), 'Task Registered'); if (result.suggestedAgents.length > 0) {