mega-minds
Version:
Enhanced multi-agent workflow system for Claude Code projects with automated handoff management and Claude Code hooks integration
534 lines (460 loc) • 22.9 kB
JavaScript
// lib/intelligence/RequestAnalyzer.js
// Intelligent Agent Selection Engine - Core Feature #2 from PRD
// Analyzes user requests and determines appropriate agent assignments with 90%+ accuracy
const fs = require('fs-extra');
const path = require('path');
/**
* Analyzes user requests and determines appropriate agent assignments
* PRD Requirements: 90%+ agent selection accuracy, <2s response time, learning from feedback
*/
class RequestAnalyzer {
constructor(projectPath = process.cwd()) {
this.projectPath = projectPath;
this.feedbackFile = path.join(projectPath, '.mega-minds', 'intelligence', 'selection-feedback.json');
this.metricsFile = path.join(projectPath, '.mega-minds', 'intelligence', 'selection-metrics.json');
// Load agent capabilities from PRD specifications
this.agentCapabilities = this.loadAgentCapabilities();
this.selectionHistory = [];
this.feedbackData = [];
// Initialize directories
this.initializeIntelligenceSystem();
}
/**
* Initialize intelligence system directories and files
*/
async initializeIntelligenceSystem() {
const intelligenceDir = path.join(this.projectPath, '.mega-minds', 'intelligence');
await fs.ensureDir(intelligenceDir);
// Load existing feedback data if available
if (await fs.pathExists(this.feedbackFile)) {
try {
this.feedbackData = await fs.readJSON(this.feedbackFile);
} catch (error) {
console.warn('⚠️ Could not load selection feedback data:', error.message);
this.feedbackData = [];
}
}
}
/**
* Load agent capabilities based on PRD requirements and current codebase
* Maps agents to their expertise areas, keywords, and confidence thresholds
*/
loadAgentCapabilities() {
return {
'project-orchestrator-agent': {
keywords: ['project', 'coordinate', 'manage', 'plan', 'organize', 'workflow', 'oversee', 'lead'],
capabilities: ['project-management', 'coordination', 'planning', 'strategy', 'workflow-design'],
expertiseAreas: ['project-planning', 'team-coordination', 'workflow-management', 'strategic-planning'],
conflictPrevention: [], // Can work with all agents
priority: 1, // Highest priority for coordination tasks
description: 'Orchestrates project phases, coordinates between specialists, manages overall workflow'
},
'frontend-development-agent': {
keywords: ['ui', 'frontend', 'react', 'vue', 'angular', 'component', 'responsive', 'interface', 'user experience', 'css', 'html', 'javascript'],
capabilities: ['react', 'vue', 'angular', 'html', 'css', 'javascript', 'responsive-design', 'component-architecture'],
expertiseAreas: ['user-interface', 'component-development', 'frontend-architecture', 'user-experience'],
conflictPrevention: ['backend-development-agent'], // Should not handle backend tasks
priority: 2,
description: 'Creates user interfaces, components, and frontend application logic'
},
'backend-development-agent': {
keywords: ['api', 'backend', 'server', 'database', 'endpoint', 'microservice', 'rest', 'graphql', 'authentication', 'authorization'],
capabilities: ['nodejs', 'python', 'java', 'api-design', 'database-integration', 'server-architecture'],
expertiseAreas: ['server-logic', 'api-development', 'backend-architecture', 'database-design'],
conflictPrevention: ['frontend-development-agent'], // Should not handle UI tasks
priority: 2,
description: 'Builds server-side logic, APIs, database integrations, and backend services'
},
'testing-agent': {
keywords: ['test', 'testing', 'unit', 'integration', 'e2e', 'qa', 'quality', 'verify', 'validate', 'coverage'],
capabilities: ['unit-testing', 'integration-testing', 'e2e-testing', 'test-automation', 'quality-assurance'],
expertiseAreas: ['test-strategy', 'quality-assurance', 'test-automation', 'validation'],
conflictPrevention: [], // Can test any component
priority: 3,
description: 'Creates and executes tests, ensures code quality and application reliability'
},
'monitoring-agent': {
keywords: ['monitor', 'logging', 'metrics', 'alert', 'performance', 'observability', 'analytics', 'tracking'],
capabilities: ['monitoring', 'logging', 'alerting', 'performance-tracking', 'analytics'],
expertiseAreas: ['production-monitoring', 'observability', 'performance-analysis', 'system-health'],
conflictPrevention: ['testing-agent'], // Different from QA testing
priority: 4,
description: 'Sets up monitoring, logging, and observability for production systems'
},
'devops-agent': {
keywords: ['deploy', 'deployment', 'ci', 'cd', 'docker', 'kubernetes', 'infrastructure', 'pipeline', 'automation'],
capabilities: ['deployment', 'ci-cd', 'containerization', 'infrastructure', 'automation'],
expertiseAreas: ['deployment-automation', 'infrastructure-management', 'ci-cd-pipelines'],
conflictPrevention: [],
priority: 4,
description: 'Handles deployment, CI/CD, infrastructure, and DevOps automation'
},
'security-agent': {
keywords: ['security', 'vulnerability', 'authentication', 'authorization', 'encryption', 'secure', 'audit'],
capabilities: ['security-analysis', 'vulnerability-scanning', 'authentication', 'authorization', 'encryption'],
expertiseAreas: ['application-security', 'security-auditing', 'access-control'],
conflictPrevention: [],
priority: 3,
description: 'Ensures application security, performs security audits, implements security measures'
}
};
}
/**
* Analyze user request and return ranked agent recommendations
* PRD Requirement: <2 seconds response time, 90%+ accuracy
*/
async analyzeRequest(requestText, context = {}) {
const startTime = Date.now();
try {
// Tokenize and clean request
const tokens = this.tokenizeRequest(requestText);
const analysis = {
originalRequest: requestText,
tokens: tokens,
context: context,
timestamp: new Date().toISOString(),
recommendations: []
};
// Calculate agent scores with multiple algorithms
const agentScores = await this.calculateAgentScores(tokens, requestText, context);
// Apply conflict prevention and validation
const validatedScores = this.applyConflictPrevention(agentScores, requestText);
// Sort and format recommendations
analysis.recommendations = this.formatRecommendations(validatedScores, requestText);
// Apply learning from previous feedback
analysis.recommendations = await this.applyFeedbackLearning(analysis.recommendations, requestText);
// Record selection for metrics
await this.recordSelection(analysis);
const processingTime = Date.now() - startTime;
analysis.processingTimeMs = processingTime;
console.log(`🧠 Request analyzed in ${processingTime}ms: ${analysis.recommendations.length} agent recommendations`);
// PRD Requirement: <2 seconds response time
if (processingTime > 2000) {
console.warn(`⚠️ Analysis exceeded 2s target: ${processingTime}ms`);
}
return analysis;
} catch (error) {
console.error('❌ Error in request analysis:', error.message);
return {
originalRequest: requestText,
error: error.message,
recommendations: this.getFallbackRecommendations(),
processingTimeMs: Date.now() - startTime
};
}
}
/**
* Tokenize request text for analysis
* @private
*/
tokenizeRequest(requestText) {
return requestText
.toLowerCase()
.replace(/[^\w\s]/g, ' ') // Remove punctuation
.split(/\s+/)
.filter(token => token.length > 2) // Filter out short words
.filter(token => !['the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by'].includes(token)); // Remove stop words
}
/**
* Calculate confidence scores for each agent using multiple algorithms
* @private
*/
async calculateAgentScores(tokens, requestText, context) {
const scores = new Map();
for (const [agentName, agentData] of Object.entries(this.agentCapabilities)) {
let score = 0;
let matchDetails = {
keywordMatches: [],
capabilityMatches: [],
expertiseMatches: [],
contextBonus: 0
};
// Algorithm 1: Keyword matching (40% weight)
agentData.keywords.forEach(keyword => {
if (tokens.includes(keyword)) {
score += 3;
matchDetails.keywordMatches.push(keyword);
}
// Partial matching for compound keywords
if (requestText.toLowerCase().includes(keyword)) {
score += 1;
}
});
// Algorithm 2: Capability matching (35% weight)
agentData.capabilities.forEach(capability => {
const capabilityWords = capability.split('-');
if (capabilityWords.some(word => tokens.includes(word))) {
score += 2;
matchDetails.capabilityMatches.push(capability);
}
});
// Algorithm 3: Expertise area matching (25% weight)
agentData.expertiseAreas.forEach(expertise => {
const expertiseWords = expertise.split('-');
if (expertiseWords.some(word => tokens.includes(word))) {
score += 1.5;
matchDetails.expertiseMatches.push(expertise);
}
});
// Context bonuses
if (context.previousAgent && context.previousAgent !== agentName) {
// Bonus for different agent (avoid ping-pong)
score += 0.5;
}
if (context.projectType) {
// Bonus for matching project type
if (this.matchesProjectType(agentName, context.projectType)) {
score += 1;
matchDetails.contextBonus += 1;
}
}
// Priority weighting (higher priority = slight boost)
score += (6 - agentData.priority) * 0.2;
scores.set(agentName, {
score: Math.round(score * 100) / 100,
confidence: Math.min(score / 10, 1.0), // Normalize to 0-1
matchDetails: matchDetails,
description: agentData.description
});
}
return scores;
}
/**
* Apply conflict prevention rules to avoid incorrect agent selection
* PRD Requirement: Prevent monitoring-agent for development, etc.
* @private
*/
applyConflictPrevention(agentScores, requestText) {
const validatedScores = new Map(agentScores);
// Check for clear development vs monitoring conflicts
const isDevelopmentRequest = /create|build|develop|implement|code|write/.test(requestText.toLowerCase());
const isMonitoringRequest = /monitor|log|alert|track|observe|metrics/.test(requestText.toLowerCase());
if (isDevelopmentRequest && !isMonitoringRequest) {
// Reduce monitoring-agent score for development requests
if (validatedScores.has('monitoring-agent')) {
const monitoringScore = validatedScores.get('monitoring-agent');
monitoringScore.score *= 0.3; // Significant penalty
monitoringScore.confidence *= 0.3;
validatedScores.set('monitoring-agent', monitoringScore);
}
}
// Apply agent-specific conflict prevention
for (const [agentName, agentData] of Object.entries(this.agentCapabilities)) {
if (agentData.conflictPrevention.length > 0) {
agentData.conflictPrevention.forEach(conflictAgent => {
if (validatedScores.has(conflictAgent)) {
// Reduce conflicting agent scores
const conflictScore = validatedScores.get(conflictAgent);
conflictScore.score *= 0.7;
conflictScore.confidence *= 0.7;
validatedScores.set(conflictAgent, conflictScore);
}
});
}
}
return validatedScores;
}
/**
* Format recommendations with confidence thresholds
* @private
*/
formatRecommendations(agentScores, requestText) {
const sortedAgents = Array.from(agentScores.entries())
.filter(([, data]) => data.confidence > 0.1) // Minimum confidence threshold
.sort((a, b) => b[1].score - a[1].score)
.slice(0, 3); // Top 3 recommendations
return sortedAgents.map(([agentName, data], index) => ({
agent: agentName,
confidence: Math.round(data.confidence * 100),
score: data.score,
rank: index + 1,
reasoning: this.generateReasoning(data.matchDetails, requestText),
description: data.description,
recommended: index === 0 && data.confidence > 0.7 // High confidence for top recommendation
}));
}
/**
* Generate human-readable reasoning for agent selection
* @private
*/
generateReasoning(matchDetails, requestText) {
const reasons = [];
if (matchDetails.keywordMatches.length > 0) {
reasons.push(`Strong keyword matches: ${matchDetails.keywordMatches.slice(0, 3).join(', ')}`);
}
if (matchDetails.capabilityMatches.length > 0) {
reasons.push(`Relevant capabilities: ${matchDetails.capabilityMatches.slice(0, 2).join(', ')}`);
}
if (matchDetails.expertiseMatches.length > 0) {
reasons.push(`Expertise alignment: ${matchDetails.expertiseMatches.slice(0, 2).join(', ')}`);
}
if (matchDetails.contextBonus > 0) {
reasons.push(`Context bonus: +${matchDetails.contextBonus}`);
}
return reasons.length > 0 ? reasons.join('; ') : 'General capability match';
}
/**
* Apply learning from previous selection feedback
* PRD Requirement: Learning from selection feedback
* @private
*/
async applyFeedbackLearning(recommendations, requestText) {
if (this.feedbackData.length === 0) {
return recommendations;
}
// Find similar past requests
const similarRequests = this.feedbackData.filter(feedback =>
this.calculateTextSimilarity(feedback.originalRequest, requestText) > 0.6
);
if (similarRequests.length > 0) {
console.log(`📚 Found ${similarRequests.length} similar requests for learning`);
// Adjust recommendations based on feedback
for (const recommendation of recommendations) {
const relevantFeedback = similarRequests.filter(fb =>
fb.selectedAgent === recommendation.agent
);
if (relevantFeedback.length > 0) {
const avgAccuracy = relevantFeedback.reduce((sum, fb) => sum + fb.accuracy, 0) / relevantFeedback.length;
// Adjust confidence based on historical accuracy
recommendation.confidence = Math.round(recommendation.confidence * avgAccuracy);
recommendation.learningApplied = true;
recommendation.historicalAccuracy = Math.round(avgAccuracy * 100);
}
}
// Re-sort after learning adjustments
recommendations.sort((a, b) => b.confidence - a.confidence);
recommendations.forEach((rec, index) => {
rec.rank = index + 1;
rec.recommended = index === 0 && rec.confidence > 70;
});
}
return recommendations;
}
/**
* Calculate text similarity for learning
* @private
*/
calculateTextSimilarity(text1, text2) {
const tokens1 = new Set(this.tokenizeRequest(text1));
const tokens2 = new Set(this.tokenizeRequest(text2));
const intersection = new Set([...tokens1].filter(x => tokens2.has(x)));
const union = new Set([...tokens1, ...tokens2]);
return intersection.size / union.size; // Jaccard similarity
}
/**
* Check if agent matches project type context
* @private
*/
matchesProjectType(agentName, projectType) {
const projectMatches = {
'web-app': ['frontend-development-agent', 'backend-development-agent'],
'api': ['backend-development-agent'],
'mobile': ['frontend-development-agent'],
'cli': ['backend-development-agent'],
'library': ['backend-development-agent', 'testing-agent'],
'monitoring': ['monitoring-agent'],
'infrastructure': ['devops-agent']
};
return projectMatches[projectType]?.includes(agentName) || false;
}
/**
* Record selection for metrics and learning
* @private
*/
async recordSelection(analysis) {
this.selectionHistory.push({
timestamp: analysis.timestamp,
request: analysis.originalRequest,
recommendations: analysis.recommendations,
topAgent: analysis.recommendations[0]?.agent,
confidence: analysis.recommendations[0]?.confidence,
processingTime: analysis.processingTimeMs
});
// Keep only last 1000 selections for performance
if (this.selectionHistory.length > 1000) {
this.selectionHistory = this.selectionHistory.slice(-1000);
}
// Save metrics periodically
if (this.selectionHistory.length % 10 === 0) {
await this.saveMetrics();
}
}
/**
* Provide fallback recommendations when analysis fails
* @private
*/
getFallbackRecommendations() {
return [{
agent: 'project-orchestrator-agent',
confidence: 80,
score: 8.0,
rank: 1,
reasoning: 'Fallback to orchestrator for task coordination',
description: 'Orchestrates project phases and coordinates between specialists',
recommended: true,
fallback: true
}];
}
/**
* Record feedback on agent selection accuracy
* PRD Requirement: Learning from selection feedback
*/
async recordFeedback(originalRequest, selectedAgent, accuracy, notes = '') {
const feedback = {
timestamp: new Date().toISOString(),
originalRequest: originalRequest,
selectedAgent: selectedAgent,
accuracy: accuracy, // 0.0 to 1.0
notes: notes,
id: `feedback-${Date.now()}`
};
this.feedbackData.push(feedback);
// Keep only last 500 feedback entries
if (this.feedbackData.length > 500) {
this.feedbackData = this.feedbackData.slice(-500);
}
// Save feedback data
await fs.writeJSON(this.feedbackFile, this.feedbackData, { spaces: 2 });
console.log(`📝 Feedback recorded: ${selectedAgent} accuracy ${Math.round(accuracy * 100)}%`);
return feedback;
}
/**
* Get selection metrics for monitoring PRD requirements
*/
async getSelectionMetrics() {
const recentSelections = this.selectionHistory.slice(-100); // Last 100 selections
const recentFeedback = this.feedbackData.slice(-50); // Last 50 feedback entries
const metrics = {
totalSelections: this.selectionHistory.length,
recentSelections: recentSelections.length,
avgProcessingTime: recentSelections.length > 0 ?
recentSelections.reduce((sum, s) => sum + s.processingTime, 0) / recentSelections.length : 0,
avgConfidence: recentSelections.length > 0 ?
recentSelections.reduce((sum, s) => sum + (s.confidence || 0), 0) / recentSelections.length : 0,
// PRD Metrics
selectionAccuracy: recentFeedback.length > 0 ?
recentFeedback.reduce((sum, f) => sum + f.accuracy, 0) / recentFeedback.length : 0,
responseTimeTarget: '<2s',
accuracyTarget: '90%',
// Performance indicators
meetsResponseTime: recentSelections.every(s => s.processingTime < 2000),
meetsAccuracyTarget: recentFeedback.length > 0 ?
(recentFeedback.reduce((sum, f) => sum + f.accuracy, 0) / recentFeedback.length) >= 0.9 : false,
timestamp: new Date().toISOString()
};
return metrics;
}
/**
* Save metrics to file
* @private
*/
async saveMetrics() {
try {
const metrics = await this.getSelectionMetrics();
await fs.writeJSON(this.metricsFile, metrics, { spaces: 2 });
} catch (error) {
console.warn('⚠️ Could not save selection metrics:', error.message);
}
}
}
module.exports = RequestAnalyzer;