UNPKG

@calmhive/calmhive-cli

Version:

🐝 Intelligent Claude CLI wrapper with rule injection, AFk background processing, voice interface, and adaptive retry

789 lines (655 loc) 22.4 kB
/** * AFk Session Output Evaluator * * Uses claude -p to evaluate the quality of AFk session outputs * and provide scores/feedback on task completion */ const { spawn } = require('child_process'); const fs = require('fs').promises; const path = require('path'); class AFkEvaluator { constructor() { this.evaluationPrompts = { codeAnalysis: ` You are evaluating the output of an AFk session that was asked to analyze code and create a report. Rate the output on a scale of 0-10 for each criteria: **COMPLETENESS (0-10):** - Did it identify actual code issues? - Were specific files and functions mentioned? - Does it include actionable recommendations? **QUALITY (0-10):** - Are the suggestions technically sound? - Is the analysis detailed and specific? - Are code examples provided where helpful? **ACCURACY (0-10):** - Are the identified issues real problems? - Are the suggested solutions appropriate? - Is the technical information correct? **USEFULNESS (0-10):** - Would a developer find this helpful? - Are priorities clearly indicated? - Can the recommendations be acted upon? Format your response as: COMPLETENESS: X/10 - brief explanation QUALITY: X/10 - brief explanation ACCURACY: X/10 - brief explanation USEFULNESS: X/10 - brief explanation TOTAL: X/40 FEEDBACK: 2-3 sentences of constructive feedback Content to evaluate: `, fileCreation: ` You are evaluating whether an AFk session successfully completed a file creation task. Rate the output on a scale of 0-10 for each criteria: **TASK COMPLETION (0-10):** - Were the requested files actually created? - Do the files contain meaningful content? - Was the task completed as specified? **CONTENT QUALITY (0-10):** - Is the content well-structured and clear? - Does it serve the intended purpose? - Is the writing/code quality good? **ACCURACY (0-10):** - Is the information factually correct? - Are any code examples syntactically valid? - Is the content relevant to the request? **USEFULNESS (0-10):** - Would this output be helpful to a developer? - Does it provide actionable information? - Is it appropriately detailed for the task? Format your response as: TASK_COMPLETION: X/10 - brief explanation CONTENT_QUALITY: X/10 - brief explanation ACCURACY: X/10 - brief explanation USEFULNESS: X/10 - brief explanation TOTAL: X/40 FEEDBACK: 2-3 sentences of constructive feedback Task requested: {{TASK}} Files expected: {{EXPECTED_FILES}} Files actually created: {{ACTUAL_FILES}} Content to evaluate: `, bugFix: ` You are evaluating an AFk session that was asked to identify and fix bugs. Rate the output on a scale of 0-10 for each criteria: **BUG IDENTIFICATION (0-10):** - Were actual bugs correctly identified? - Are the problem descriptions accurate? - Were root causes analyzed properly? **SOLUTION QUALITY (0-10):** - Are the proposed fixes technically sound? - Do solutions address root causes? - Are fixes minimal and focused? **IMPLEMENTATION (0-10):** - Were actual code changes made? - Are the changes syntactically correct? - Do changes follow best practices? **TESTING (0-10):** - Were test cases considered or added? - Is there evidence of verification? - Are edge cases addressed? Format your response as: BUG_IDENTIFICATION: X/10 - brief explanation SOLUTION_QUALITY: X/10 - brief explanation IMPLEMENTATION: X/10 - brief explanation TESTING: X/10 - brief explanation TOTAL: X/40 FEEDBACK: 2-3 sentences of constructive feedback Content to evaluate: `, performance: ` You are evaluating an AFk session focused on performance optimization. Rate the output on a scale of 0-10 for each criteria: **ANALYSIS DEPTH (0-10):** - Were performance bottlenecks correctly identified? - Are metrics and benchmarks provided? - Is the analysis data-driven? **OPTIMIZATION QUALITY (0-10):** - Are the optimizations technically sound? - Do they address the actual bottlenecks? - Are trade-offs clearly explained? **IMPLEMENTATION (0-10):** - Were optimizations actually implemented? - Is the code clean and maintainable? - Are changes well-documented? **VERIFICATION (0-10):** - Are performance improvements measured? - Is there before/after comparison? - Are results reproducible? Format your response as: ANALYSIS_DEPTH: X/10 - brief explanation OPTIMIZATION_QUALITY: X/10 - brief explanation IMPLEMENTATION: X/10 - brief explanation VERIFICATION: X/10 - brief explanation TOTAL: X/40 FEEDBACK: 2-3 sentences of constructive feedback Content to evaluate: `, security: ` You are evaluating an AFk session focused on security analysis. Rate the output on a scale of 0-10 for each criteria: **VULNERABILITY IDENTIFICATION (0-10):** - Were real security issues found? - Are OWASP/CWE classifications used? - Is severity properly assessed? **RISK ASSESSMENT (0-10):** - Are attack vectors clearly explained? - Is business impact considered? - Are priorities well-established? **REMEDIATION (0-10):** - Are fixes comprehensive and correct? - Do they follow security best practices? - Are defense-in-depth principles applied? **VERIFICATION (0-10):** - Are fixes tested for effectiveness? - Is there evidence of security testing? - Are regression tests included? Format your response as: VULNERABILITY_IDENTIFICATION: X/10 - brief explanation RISK_ASSESSMENT: X/10 - brief explanation REMEDIATION: X/10 - brief explanation VERIFICATION: X/10 - brief explanation TOTAL: X/40 FEEDBACK: 2-3 sentences of constructive feedback Content to evaluate: `, documentation: ` You are evaluating an AFk session focused on documentation creation. Rate the output on a scale of 0-10 for each criteria: **COMPLETENESS (0-10):** - Are all requested topics covered? - Is necessary context provided? - Are examples included where helpful? **CLARITY (0-10):** - Is the writing clear and concise? - Is technical jargon explained? - Is the structure logical? **ACCURACY (0-10):** - Is information technically correct? - Are code examples valid? - Are references accurate? **USEFULNESS (0-10):** - Does it serve the intended audience? - Is it actionable and practical? - Will it improve understanding? Format your response as: COMPLETENESS: X/10 - brief explanation CLARITY: X/10 - brief explanation ACCURACY: X/10 - brief explanation USEFULNESS: X/10 - brief explanation TOTAL: X/40 FEEDBACK: 2-3 sentences of constructive feedback Content to evaluate: `, refactoring: ` You are evaluating an AFk session focused on code refactoring. Rate the output on a scale of 0-10 for each criteria: **CODE QUALITY (0-10):** - Is the refactored code cleaner? - Are design patterns properly applied? - Is coupling reduced? **MAINTAINABILITY (0-10):** - Is the code more readable? - Are abstractions appropriate? - Is complexity reduced? **SAFETY (0-10):** - Are behavior changes avoided? - Are tests maintained/updated? - Is backward compatibility preserved? **COMPLETENESS (0-10):** - Was the entire scope addressed? - Are all code smells resolved? - Is technical debt reduced? Format your response as: CODE_QUALITY: X/10 - brief explanation MAINTAINABILITY: X/10 - brief explanation SAFETY: X/10 - brief explanation COMPLETENESS: X/10 - brief explanation TOTAL: X/40 FEEDBACK: 2-3 sentences of constructive feedback Content to evaluate: ` }; } /** * Evaluate AFk session output using claude -p */ async evaluateSessionOutput(sessionId, taskType = 'codeAnalysis', options = {}) { try { console.log(`🧮 Evaluating AFk session: ${sessionId}`); // Gather session output content const sessionContent = await this.gatherSessionContent(sessionId, options); if (!sessionContent) { return { success: false, error: 'No session content found to evaluate' }; } // Select appropriate evaluation prompt const prompt = this.buildEvaluationPrompt(taskType, sessionContent, options); // Use claude -p to evaluate const evaluation = await this.runClaudeEvaluation(prompt); // Parse evaluation results const results = this.parseEvaluationResults(evaluation); console.log(`📊 Session evaluation complete: ${results.totalScore}/40`); return { success: true, sessionId, taskType, evaluation: results, rawOutput: evaluation }; } catch (error) { console.error(`❌ Evaluation error for ${sessionId}:`, error.message); return { success: false, sessionId, error: error.message }; } } /** * Gather all relevant content from an AFk session */ async gatherSessionContent(sessionId, options = {}) { const content = { logs: '', createdFiles: [], output: '', metadata: {} }; try { // Try multiple log locations const logPaths = [ path.join(process.env.HOME, '.claude', 'calmhive', 'v3', 'logs', `${sessionId}.log`), path.join(process.env.HOME, '.claude', 'afk_registry', sessionId, 'worker.log'), path.join(process.env.HOME, '.claude', 'afk_registry', sessionId, 'context-monitor.log') ]; for (const logPath of logPaths) { try { const logContent = await fs.readFile(logPath, 'utf8'); content.logs += logContent + '\\n\\n'; } catch (error) { // Log file doesn't exist at this path } } // Check for created files if specified if (options.expectedFiles) { for (const filePath of options.expectedFiles) { try { const fileContent = await fs.readFile(filePath, 'utf8'); content.createdFiles.push({ path: filePath, content: fileContent, size: fileContent.length }); } catch (error) { content.createdFiles.push({ path: filePath, error: error.message, size: 0 }); } } } // Get session database info if available try { const SessionDatabase = require('./session-database'); const sessionDb = new SessionDatabase(); const session = await sessionDb.getSession(sessionId); if (session) { content.metadata = { task: session.task, status: session.status, iterations: session.iterations, currentIteration: session.currentIteration, progress: session.progress, output: session.output || '', error: session.error }; content.output = session.output || ''; } } catch (error) { // Session database not available } return content; } catch (error) { console.error('Error gathering session content:', error); return null; } } /** * Build evaluation prompt based on task type and content */ buildEvaluationPrompt(taskType, sessionContent, options = {}) { let prompt = this.evaluationPrompts[taskType] || this.evaluationPrompts.codeAnalysis; // Replace template variables if (options.task) { prompt = prompt.replace('{{TASK}}', options.task); } if (options.expectedFiles) { prompt = prompt.replace('{{EXPECTED_FILES}}', options.expectedFiles.join(', ')); } if (sessionContent.createdFiles) { const actualFiles = sessionContent.createdFiles .filter(f => !f.error) .map(f => `${f.path} (${f.size} bytes)`) .join(', '); prompt = prompt.replace('{{ACTUAL_FILES}}', actualFiles || 'None'); } // Prepare content for evaluation const contentToEvaluate = this.prepareContentForEvaluation(sessionContent); return prompt + contentToEvaluate; } /** * Prepare session content for evaluation */ prepareContentForEvaluation(sessionContent) { let evaluation = ''; // Add session metadata if (sessionContent.metadata.task) { evaluation += `ORIGINAL TASK: ${sessionContent.metadata.task}\\n\\n`; } // Add session output if (sessionContent.output) { evaluation += `SESSION OUTPUT:\\n${sessionContent.output}\\n\\n`; } // Add created files content if (sessionContent.createdFiles.length > 0) { evaluation += 'CREATED FILES:\\n'; sessionContent.createdFiles.forEach(file => { if (file.error) { evaluation += `❌ ${file.path}: ${file.error}\\n`; } else { evaluation += `✅ ${file.path} (${file.size} bytes):\\n${file.content.substring(0, 2000)}${file.content.length > 2000 ? '...\\n[truncated]' : ''}\\n\\n`; } }); } // Add relevant log excerpts (last 1000 chars) if (sessionContent.logs) { const logExcerpt = sessionContent.logs.slice(-1000); evaluation += `LOG EXCERPT (last 1000 chars):\\n${logExcerpt}\\n`; } return evaluation; } /** * Run claude -p evaluation */ async runClaudeEvaluation(prompt) { return new Promise((resolve, reject) => { console.log('🤖 Running claude evaluation...'); const claudeProcess = spawn('claude', ['-p', prompt, '--output-format', 'json'], { stdio: ['pipe', 'pipe', 'pipe'] }); let stdout = ''; let stderr = ''; claudeProcess.stdout.on('data', (data) => { stdout += data.toString(); }); claudeProcess.stderr.on('data', (data) => { stderr += data.toString(); }); claudeProcess.on('exit', (code) => { if (code === 0 && stdout.trim()) { try { const jsonResponse = JSON.parse(stdout.trim()); if (jsonResponse.result) { resolve(jsonResponse.result); } else { reject(new Error('No result field in Claude JSON response')); } } catch (parseError) { reject(new Error(`Failed to parse Claude JSON response: ${parseError.message}`)); } } else { reject(new Error(`Claude evaluation failed: ${stderr || 'No output'}`)); } }); }); } /** * Parse evaluation results from claude output */ parseEvaluationResults(evaluationText) { const results = { scores: {}, totalScore: 0, maxScore: 40, feedback: '', rawText: evaluationText }; try { // Extract scores using regex patterns const scorePatterns = [ /(?:COMPLETENESS|TASK_COMPLETION|BUG_IDENTIFICATION):\s*(\d+)\/10/i, /(?:QUALITY|CONTENT_QUALITY|SOLUTION_QUALITY):\s*(\d+)\/10/i, /(?:ACCURACY):\s*(\d+)\/10/i, /(?:USEFULNESS|IMPLEMENTATION|TESTING):\s*(\d+)\/10/i ]; scorePatterns.forEach((pattern, index) => { const match = evaluationText.match(pattern); if (match) { const score = parseInt(match[1]); results.scores[`criteria${index + 1}`] = score; results.totalScore += score; } }); // Extract total score if explicitly mentioned const totalMatch = evaluationText.match(/TOTAL:\s*(\d+)\/40/i); if (totalMatch) { results.totalScore = parseInt(totalMatch[1]); } // Extract feedback const feedbackMatch = evaluationText.match(/FEEDBACK:\s*(.+?)(?:\n\n|$)/s); if (feedbackMatch) { results.feedback = feedbackMatch[1].trim(); } // Calculate percentage results.percentage = Math.round((results.totalScore / results.maxScore) * 100); } catch (error) { console.error('Error parsing evaluation results:', error); results.feedback = 'Failed to parse evaluation results'; } return results; } /** * Quick evaluation for specific file creation tasks */ async evaluateFileCreationTask(sessionId, expectedFiles, task) { return this.evaluateSessionOutput(sessionId, 'fileCreation', { expectedFiles, task }); } /** * Quick evaluation for code analysis tasks */ async evaluateCodeAnalysisTask(sessionId) { return this.evaluateSessionOutput(sessionId, 'codeAnalysis'); } /** * Quick evaluation for bug fix tasks */ async evaluateBugFixTask(sessionId) { return this.evaluateSessionOutput(sessionId, 'bugFix'); } /** * Quick evaluation for performance optimization tasks */ async evaluatePerformanceTask(sessionId) { return this.evaluateSessionOutput(sessionId, 'performance'); } /** * Quick evaluation for security analysis tasks */ async evaluateSecurityTask(sessionId) { return this.evaluateSessionOutput(sessionId, 'security'); } /** * Quick evaluation for documentation tasks */ async evaluateDocumentationTask(sessionId, options = {}) { return this.evaluateSessionOutput(sessionId, 'documentation', options); } /** * Quick evaluation for refactoring tasks */ async evaluateRefactoringTask(sessionId) { return this.evaluateSessionOutput(sessionId, 'refactoring'); } /** * Batch evaluate multiple sessions */ async batchEvaluate(sessionIds, taskType = 'codeAnalysis') { console.log(`🔄 Batch evaluating ${sessionIds.length} sessions...`); const results = []; for (const sessionId of sessionIds) { try { const result = await this.evaluateSessionOutput(sessionId, taskType); results.push(result); } catch (error) { results.push({ success: false, sessionId, error: error.message }); } } // Compute aggregate statistics const stats = this.computeAggregateStats(results); return { results, stats, timestamp: new Date().toISOString() }; } /** * Compute aggregate statistics from evaluation results */ computeAggregateStats(results) { const successful = results.filter(r => r.success); const failed = results.filter(r => !r.success); if (successful.length === 0) { return { totalSessions: results.length, successfulEvaluations: 0, failedEvaluations: failed.length, averageScore: 0, averagePercentage: 0 }; } const scores = successful.map(r => r.evaluation.totalScore); const percentages = successful.map(r => r.evaluation.percentage); return { totalSessions: results.length, successfulEvaluations: successful.length, failedEvaluations: failed.length, averageScore: scores.reduce((a, b) => a + b, 0) / scores.length, averagePercentage: percentages.reduce((a, b) => a + b, 0) / percentages.length, minScore: Math.min(...scores), maxScore: Math.max(...scores), distribution: this.getScoreDistribution(scores) }; } /** * Get score distribution for analytics */ getScoreDistribution(scores) { const distribution = { excellent: 0, // 36-40 good: 0, // 28-35 fair: 0, // 20-27 poor: 0 // 0-19 }; scores.forEach(score => { if (score >= 36) {distribution.excellent++;} else if (score >= 28) {distribution.good++;} else if (score >= 20) {distribution.fair++;} else {distribution.poor++;} }); return distribution; } /** * Generate evaluation report */ generateEvaluationReport(evaluationResult) { if (!evaluationResult.success) { return `# Evaluation Failed\n\nSession ID: ${evaluationResult.sessionId}\nError: ${evaluationResult.error}`; } const { evaluation } = evaluationResult; const report = []; report.push('# AFk Session Evaluation Report'); report.push(`\nSession ID: ${evaluationResult.sessionId}`); report.push(`Task Type: ${evaluationResult.taskType}`); report.push(`Timestamp: ${new Date().toISOString()}`); report.push(`\n## Overall Score: ${evaluation.totalScore}/${evaluation.maxScore} (${evaluation.percentage}%)`); report.push('\n## Detailed Scores'); Object.entries(evaluation.scores).forEach(([criteria, score]) => { report.push(`- ${criteria}: ${score}/10`); }); if (evaluation.feedback) { report.push('\n## Feedback'); report.push(evaluation.feedback); } report.push('\n## Score Interpretation'); if (evaluation.percentage >= 90) { report.push('✅ Excellent: The AFk session performed exceptionally well.'); } else if (evaluation.percentage >= 70) { report.push('👍 Good: The AFk session completed the task satisfactorily.'); } else if (evaluation.percentage >= 50) { report.push('⚠️ Fair: The AFk session partially completed the task but needs improvement.'); } else { report.push('❌ Poor: The AFk session did not adequately complete the task.'); } return report.join('\n'); } /** * Compare two AFk sessions */ async compareSessionPerformance(sessionId1, sessionId2, taskType = 'codeAnalysis') { console.log(`⚖️ Comparing sessions: ${sessionId1} vs ${sessionId2}`); const [result1, result2] = await Promise.all([ this.evaluateSessionOutput(sessionId1, taskType), this.evaluateSessionOutput(sessionId2, taskType) ]); if (!result1.success || !result2.success) { return { success: false, error: 'One or both evaluations failed' }; } const comparison = { session1: { id: sessionId1, totalScore: result1.evaluation.totalScore, percentage: result1.evaluation.percentage }, session2: { id: sessionId2, totalScore: result2.evaluation.totalScore, percentage: result2.evaluation.percentage }, winner: null, scoreDifference: 0, percentageDifference: 0 }; comparison.scoreDifference = Math.abs(result1.evaluation.totalScore - result2.evaluation.totalScore); comparison.percentageDifference = Math.abs(result1.evaluation.percentage - result2.evaluation.percentage); if (result1.evaluation.totalScore > result2.evaluation.totalScore) { comparison.winner = sessionId1; } else if (result2.evaluation.totalScore > result1.evaluation.totalScore) { comparison.winner = sessionId2; } else { comparison.winner = 'tie'; } return { success: true, comparison, details: { session1: result1, session2: result2 } }; } } module.exports = AFkEvaluator;