@calmhive/calmhive-cli
Version:
🐝 Intelligent Claude CLI wrapper with rule injection, AFk background processing, voice interface, and adaptive retry
673 lines (582 loc) • 20.6 kB
JavaScript
/**
* Quality Dashboard and Metrics System
*
* Provides comprehensive quality monitoring and reporting across all Calmhive components.
* Aggregates Claude-powered quality evaluations into actionable insights and metrics.
*
* Features:
* - Real-time quality metrics collection
* - Historical quality trend analysis
* - Component-specific quality scoring
* - Quality regression detection
* - Actionable improvement recommendations
*/
const UniversalQualityEvaluator = require('./universal-quality-evaluator');
const fs = require('fs').promises;
const path = require('path');
class QualityDashboard {
constructor(options = {}) {
this.evaluator = new UniversalQualityEvaluator();
this.metricsDir = options.metricsDir || path.join(__dirname, '../metrics');
this.qualityThresholds = {
excellent: 85,
good: 70,
acceptable: 55,
poor: 40,
critical: 25
};
this.componentWeights = {
cliCommands: 0.3, // 30% - User-facing critical
integration: 0.25, // 25% - System reliability
workflows: 0.2, // 20% - User experience
templates: 0.15, // 15% - Configuration quality
performance: 0.1 // 10% - System efficiency
};
this.ensureMetricsDir();
}
/**
* Ensure metrics directory exists
*/
async ensureMetricsDir() {
try {
await fs.mkdir(this.metricsDir, { recursive: true });
} catch (error) {
// Directory exists or creation failed
}
}
/**
* Run comprehensive quality assessment across all components
*/
async runComprehensiveAssessment() {
console.log('📊 Starting comprehensive quality assessment...');
const assessmentResults = {
timestamp: new Date().toISOString(),
overallScore: 0,
componentScores: {},
qualityTrends: {},
regressionAlerts: [],
recommendations: [],
details: {}
};
try {
// Assess CLI Commands
console.log(' Assessing CLI commands...');
assessmentResults.componentScores.cliCommands = await this.assessCliCommands();
// Assess Integration Quality
console.log(' Assessing integration quality...');
assessmentResults.componentScores.integration = await this.assessIntegrationQuality();
// Assess Template Effectiveness
console.log(' Assessing template effectiveness...');
assessmentResults.componentScores.templates = await this.assessTemplateEffectiveness();
// Assess Workflow Quality
console.log(' Assessing workflow quality...');
assessmentResults.componentScores.workflows = await this.assessWorkflowQuality();
// Calculate overall score
assessmentResults.overallScore = this.calculateOverallScore(assessmentResults.componentScores);
// Generate quality trends
assessmentResults.qualityTrends = await this.analyzeQualityTrends();
// Detect regressions
assessmentResults.regressionAlerts = await this.detectQualityRegressions(assessmentResults);
// Generate recommendations
assessmentResults.recommendations = this.generateQualityRecommendations(assessmentResults);
// Save assessment results
await this.saveAssessmentResults(assessmentResults);
console.log(`📈 Comprehensive assessment complete. Overall score: ${assessmentResults.overallScore.toFixed(1)}%`);
return assessmentResults;
} catch (error) {
console.error('❌ Quality assessment error:', error.message);
return {
...assessmentResults,
error: error.message,
success: false
};
}
}
/**
* Assess CLI command quality
*/
async assessCliCommands() {
const commands = [
{ command: 'calmhive --version', expected: 'Display version information' },
{ command: 'calmhive --help', expected: 'Show comprehensive help' },
{ command: 'calmhive chat "test"', expected: 'Respond to simple query' },
{ command: 'calmhive config show', expected: 'Display configuration status' },
{ command: 'calmhive afk status', expected: 'Show session status' }
];
const evaluations = [];
for (const cmd of commands) {
try {
const result = await this.executeCommand(cmd.command);
const evaluation = await this.evaluator.evaluateCliCommand(
cmd.command,
result.stdout,
result.stderr,
cmd.expected
);
if (evaluation.success) {
evaluations.push({
command: cmd.command,
score: evaluation.evaluation.percentage,
confidence: evaluation.confidence.score,
feedback: evaluation.evaluation.feedback
});
}
} catch (error) {
evaluations.push({
command: cmd.command,
score: 0,
confidence: 0,
error: error.message
});
}
}
const averageScore = evaluations.length > 0
? evaluations.reduce((sum, e) => sum + e.score, 0) / evaluations.length
: 0;
return {
score: averageScore,
count: evaluations.length,
details: evaluations,
level: this.getQualityLevel(averageScore)
};
}
/**
* Assess integration quality through component interaction tests
*/
async assessIntegrationQuality() {
const integrationTests = [
{
name: 'ProcessManager + ProgressTracker',
test: () => this.testProcessManagerIntegration()
},
{
name: 'SessionDatabase + AFk Process',
test: () => this.testSessionDatabaseIntegration()
},
{
name: 'ConfigManager + Templates',
test: () => this.testConfigManagerIntegration()
}
];
const evaluations = [];
for (const test of integrationTests) {
try {
const result = await test.test();
evaluations.push({
integration: test.name,
score: result.score || 0,
confidence: result.confidence || 0,
details: result.details || 'No details available'
});
} catch (error) {
evaluations.push({
integration: test.name,
score: 0,
confidence: 0,
error: error.message
});
}
}
const averageScore = evaluations.length > 0
? evaluations.reduce((sum, e) => sum + e.score, 0) / evaluations.length
: 0;
return {
score: averageScore,
count: evaluations.length,
details: evaluations,
level: this.getQualityLevel(averageScore)
};
}
/**
* Assess template effectiveness
*/
async assessTemplateEffectiveness() {
const templates = [
{ type: 'CLAUDE.md', path: 'CLAUDE.md.example' },
{ type: 'CLAUDE-DESKTOP.md', path: 'CLAUDE-DESKTOP.md.example' },
{ type: 'Bug Hunting', path: 'commands/bug-hunting.md' },
{ type: 'Documentation', path: 'commands/documentation.md' }
];
const evaluations = [];
for (const template of templates) {
try {
const templatePath = path.join(__dirname, '..', template.path);
const content = await fs.readFile(templatePath, 'utf8');
const evaluation = await this.evaluator.evaluateTemplate(
template.type,
content.substring(0, 2000), // Truncate for evaluation
`Provide effective ${template.type} guidance`,
'Template quality assessment'
);
if (evaluation.success) {
evaluations.push({
template: template.type,
score: evaluation.evaluation.percentage,
confidence: evaluation.confidence.score,
feedback: evaluation.evaluation.feedback
});
}
} catch (error) {
evaluations.push({
template: template.type,
score: 0,
confidence: 0,
error: error.message
});
}
}
const averageScore = evaluations.length > 0
? evaluations.reduce((sum, e) => sum + e.score, 0) / evaluations.length
: 0;
return {
score: averageScore,
count: evaluations.length,
details: evaluations,
level: this.getQualityLevel(averageScore)
};
}
/**
* Assess workflow quality through simulated user scenarios
*/
async assessWorkflowQuality() {
const workflows = [
{
name: 'Quick Task Workflow',
description: 'Chat -> Run -> Result',
test: () => this.testQuickTaskWorkflow()
},
{
name: 'Configuration Workflow',
description: 'Config Show -> Install -> Verify',
test: () => this.testConfigurationWorkflow()
}
];
const evaluations = [];
for (const workflow of workflows) {
try {
const result = await workflow.test();
evaluations.push({
workflow: workflow.name,
score: result.score || 0,
confidence: result.confidence || 0,
description: workflow.description,
details: result.details || 'No details available'
});
} catch (error) {
evaluations.push({
workflow: workflow.name,
score: 0,
confidence: 0,
error: error.message
});
}
}
const averageScore = evaluations.length > 0
? evaluations.reduce((sum, e) => sum + e.score, 0) / evaluations.length
: 0;
return {
score: averageScore,
count: evaluations.length,
details: evaluations,
level: this.getQualityLevel(averageScore)
};
}
/**
* Calculate weighted overall score
*/
calculateOverallScore(componentScores) {
let weightedSum = 0;
let totalWeight = 0;
Object.entries(this.componentWeights).forEach(([component, weight]) => {
if (componentScores[component] && componentScores[component].score !== undefined) {
weightedSum += componentScores[component].score * weight;
totalWeight += weight;
}
});
return totalWeight > 0 ? weightedSum / totalWeight : 0;
}
/**
* Analyze quality trends over time
*/
async analyzeQualityTrends() {
try {
const historyFile = path.join(this.metricsDir, 'quality-history.json');
const history = await this.loadQualityHistory(historyFile);
if (history.length < 2) {
return { trend: 'insufficient_data', message: 'Need more data points for trend analysis' };
}
const recent = history.slice(-5); // Last 5 assessments
const scores = recent.map(h => h.overallScore);
// Simple trend calculation
const firstScore = scores[0];
const lastScore = scores[scores.length - 1];
const trendDirection = lastScore > firstScore ? 'improving' : 'declining';
const trendStrength = Math.abs(lastScore - firstScore);
return {
trend: trendDirection,
strength: trendStrength,
recentScores: scores,
analysis: `Quality has been ${trendDirection} by ${trendStrength.toFixed(1)} points over last ${recent.length} assessments`
};
} catch (error) {
return { trend: 'unknown', error: error.message };
}
}
/**
* Detect quality regressions
*/
async detectQualityRegressions(currentResults) {
const regressions = [];
try {
const historyFile = path.join(this.metricsDir, 'quality-history.json');
const history = await this.loadQualityHistory(historyFile);
if (history.length === 0) {
return regressions;
}
const lastAssessment = history[history.length - 1];
// Check each component for significant drops
Object.entries(currentResults.componentScores).forEach(([component, current]) => {
const previous = lastAssessment.componentScores[component];
if (previous && current.score < previous.score - 10) {
regressions.push({
component,
severity: current.score < previous.score - 20 ? 'critical' : 'warning',
previousScore: previous.score,
currentScore: current.score,
drop: previous.score - current.score,
message: `${component} quality dropped by ${(previous.score - current.score).toFixed(1)} points`
});
}
});
} catch (error) {
regressions.push({
component: 'regression_detection',
severity: 'warning',
message: `Regression detection failed: ${error.message}`
});
}
return regressions;
}
/**
* Generate actionable quality recommendations
*/
generateQualityRecommendations(assessmentResults) {
const recommendations = [];
// Overall score recommendations
if (assessmentResults.overallScore < this.qualityThresholds.acceptable) {
recommendations.push({
priority: 'high',
category: 'overall',
title: 'Critical Quality Issues',
description: `Overall quality score (${assessmentResults.overallScore.toFixed(1)}%) is below acceptable threshold`,
actions: ['Review failing components', 'Implement immediate fixes', 'Increase testing coverage']
});
}
// Component-specific recommendations
Object.entries(assessmentResults.componentScores).forEach(([component, data]) => {
if (data.score < this.qualityThresholds.good) {
let priority = 'medium';
if (data.score < this.qualityThresholds.poor) {priority = 'high';}
if (data.score < this.qualityThresholds.critical) {priority = 'critical';}
recommendations.push({
priority,
category: component,
title: `${component} Quality Below Standards`,
description: `${component} scored ${data.score.toFixed(1)}% (${data.level})`,
actions: this.getComponentSpecificActions(component, data.score)
});
}
});
// Regression recommendations
assessmentResults.regressionAlerts.forEach(regression => {
recommendations.push({
priority: regression.severity === 'critical' ? 'critical' : 'high',
category: 'regression',
title: `Quality Regression in ${regression.component}`,
description: regression.message,
actions: ['Investigate recent changes', 'Review failing tests', 'Consider rollback if critical']
});
});
return recommendations.sort((a, b) => {
const priorityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
return priorityOrder[b.priority] - priorityOrder[a.priority];
});
}
/**
* Get component-specific improvement actions
*/
getComponentSpecificActions(component, score) {
const actions = {
cliCommands: [
'Review command help text clarity',
'Improve error message usefulness',
'Add missing command options',
'Test command responsiveness'
],
integration: [
'Review component interfaces',
'Add integration testing',
'Check error propagation',
'Verify data consistency'
],
templates: [
'Review template clarity',
'Add more examples',
'Test template effectiveness',
'Update documentation'
],
workflows: [
'Streamline user workflows',
'Improve error recovery',
'Add workflow documentation',
'Test end-to-end scenarios'
]
};
return actions[component] || ['Review component implementation', 'Add comprehensive testing', 'Improve documentation'];
}
/**
* Get quality level description
*/
getQualityLevel(score) {
if (score >= this.qualityThresholds.excellent) {return 'excellent';}
if (score >= this.qualityThresholds.good) {return 'good';}
if (score >= this.qualityThresholds.acceptable) {return 'acceptable';}
if (score >= this.qualityThresholds.poor) {return 'poor';}
return 'critical';
}
/**
* Save assessment results to history
*/
async saveAssessmentResults(results) {
try {
const historyFile = path.join(this.metricsDir, 'quality-history.json');
const history = await this.loadQualityHistory(historyFile);
history.push(results);
// Keep only last 30 assessments
if (history.length > 30) {
history.splice(0, history.length - 30);
}
await fs.writeFile(historyFile, JSON.stringify(history, null, 2));
// Also save latest results
const latestFile = path.join(this.metricsDir, 'latest-assessment.json');
await fs.writeFile(latestFile, JSON.stringify(results, null, 2));
} catch (error) {
console.error('Failed to save assessment results:', error.message);
}
}
/**
* Load quality history
*/
async loadQualityHistory(historyFile) {
try {
const content = await fs.readFile(historyFile, 'utf8');
return JSON.parse(content);
} catch (error) {
return [];
}
}
/**
* Generate quality dashboard report
*/
generateDashboardReport(assessmentResults) {
const report = {
header: '📊 Calmhive Quality Dashboard',
timestamp: assessmentResults.timestamp,
summary: {
overallScore: `${assessmentResults.overallScore.toFixed(1)}%`,
level: this.getQualityLevel(assessmentResults.overallScore),
trend: assessmentResults.qualityTrends.trend || 'unknown'
},
components: Object.entries(assessmentResults.componentScores).map(([name, data]) => ({
name,
score: `${data.score.toFixed(1)}%`,
level: data.level,
count: data.count
})),
alerts: assessmentResults.regressionAlerts.map(alert => ({
severity: alert.severity,
message: alert.message
})),
topRecommendations: assessmentResults.recommendations.slice(0, 5)
};
return report;
}
// Helper test methods for integration assessment
async testProcessManagerIntegration() {
try {
const result = await this.executeCommand('node -e "const ProcessManager = require(\'./lib/process-manager\'); console.log(\'ProcessManager loaded\');"');
return { score: result.exitCode === 0 ? 80 : 20, confidence: 0.8, details: result.stdout };
} catch (error) {
return { score: 0, confidence: 0, details: error.message };
}
}
async testSessionDatabaseIntegration() {
try {
const result = await this.executeCommand('node -e "const SessionDatabase = require(\'./lib/session-database\'); console.log(\'SessionDatabase loaded\');"');
return { score: result.exitCode === 0 ? 80 : 20, confidence: 0.8, details: result.stdout };
} catch (error) {
return { score: 0, confidence: 0, details: error.message };
}
}
async testConfigManagerIntegration() {
try {
const result = await this.executeCommand('calmhive config show');
return { score: result.exitCode === 0 ? 75 : 25, confidence: 0.7, details: result.stdout };
} catch (error) {
return { score: 0, confidence: 0, details: error.message };
}
}
async testQuickTaskWorkflow() {
try {
const result = await this.executeCommand('calmhive chat "test" | head -5');
return { score: result.exitCode === 0 ? 70 : 30, confidence: 0.6, details: result.stdout };
} catch (error) {
return { score: 0, confidence: 0, details: error.message };
}
}
async testConfigurationWorkflow() {
try {
const result = await this.executeCommand('calmhive config show && calmhive config install --dry-run');
return { score: result.exitCode === 0 ? 75 : 25, confidence: 0.7, details: result.stdout };
} catch (error) {
return { score: 0, confidence: 0, details: error.message };
}
}
// Helper method to execute commands
async executeCommand(command) {
const { spawn } = require('child_process');
return new Promise((resolve) => {
const childProcess = spawn('bash', ['-c', command], {
stdio: ['pipe', 'pipe', 'pipe'],
cwd: path.join(__dirname, '..')
});
let stdout = '';
let stderr = '';
childProcess.stdout.on('data', (data) => {
stdout += data.toString();
});
childProcess.stderr.on('data', (data) => {
stderr += data.toString();
});
childProcess.on('exit', (code) => {
resolve({
exitCode: code,
stdout: stdout.trim(),
stderr: stderr.trim(),
command
});
});
// Timeout after 30 seconds
setTimeout(() => {
childProcess.kill('SIGKILL');
resolve({
exitCode: -1,
stdout: stdout.trim(),
stderr: 'Command timed out',
command
});
}, 30000);
});
}
}
module.exports = QualityDashboard;