UNPKG

aios-core

Version:

Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework

776 lines (664 loc) 21.7 kB
const fs = require('fs').promises; const path = require('path'); const chalk = require('chalk'); /** * Tracks metrics for self-improvement operations */ class MetricsTracker { constructor(options = {}) { this.rootPath = options.rootPath || process.cwd(); this.metricsFile = path.join(this.rootPath, '.aios', 'improvement-metrics.json'); this.maxEntries = options.maxEntries || 1000; // Metric categories this.categories = { performance: ['execution_time', 'memory_usage', 'cpu_usage'], quality: ['test_coverage', 'code_complexity', 'error_rate'], impact: ['files_modified', 'functions_improved', 'bugs_fixed'], user: ['approval_rate', 'rollback_rate', 'satisfaction_score'] }; } /** * Initialize metrics system * @returns {Promise<void>} */ async initialize() { const metricsDir = path.dirname(this.metricsFile); try { await fs.mkdir(metricsDir, { recursive: true }); // Initialize file if doesn't exist try { await fs.access(this.metricsFile); } catch { await this.saveMetrics({ version: '1.0.0', created: new Date().toISOString(), improvements: [], aggregates: this.initializeAggregates(), trends: {} }); } } catch (error) { console.error(chalk.red(`Failed to initialize metrics: ${error.message}`)); } } /** * Record improvement metrics * @param {Object} improvement - Improvement data * @returns {Promise<void>} */ async recordImprovement(improvement) { await this.initialize(); const metrics = await this.loadMetrics(); const entry = { improvement_id: improvement.improvement_id, timestamp: new Date().toISOString(), metrics: improvement.metrics || {}, analysis: improvement.analysis || {}, plan: improvement.plan || {}, outcome: 'pending', measurements: await this.gatherMeasurements(improvement) }; metrics.improvements.push(entry); // Keep only recent entries if (metrics.improvements.length > this.maxEntries) { metrics.improvements = metrics.improvements.slice(-this.maxEntries); } // Update aggregates await this.updateAggregates(metrics, entry); // Calculate trends metrics.trends = await this.calculateTrends(metrics); await this.saveMetrics(metrics); console.log(chalk.green(`📊 Metrics recorded for improvement: ${improvement.improvement_id}`)); } /** * Update improvement outcome * @param {string} improvementId - Improvement ID * @param {Object} outcome - Outcome data * @returns {Promise<void>} */ async updateOutcome(improvementId, outcome) { const metrics = await this.loadMetrics(); const entry = metrics.improvements.find(i => i.improvement_id === improvementId); if (!entry) { throw new Error(`Improvement not found: ${improvementId}`); } entry.outcome = outcome.status; // 'success', 'failed', 'rolled_back' entry.outcome_details = outcome; entry.end_timestamp = new Date().toISOString(); // Calculate duration const start = new Date(entry.timestamp); const end = new Date(entry.end_timestamp); entry.duration_ms = end - start; // Update aggregates based on outcome await this.updateOutcomeAggregates(metrics, entry); await this.saveMetrics(metrics); } /** * Get improvement report * @param {string} improvementId - Improvement ID * @returns {Promise<Object>} Improvement report */ async getImprovementReport(improvementId) { const metrics = await this.loadMetrics(); const entry = metrics.improvements.find(i => i.improvement_id === improvementId); if (!entry) { throw new Error(`Improvement not found: ${improvementId}`); } const report = { improvement_id: improvementId, timestamp: entry.timestamp, outcome: entry.outcome, duration: entry.duration_ms ? `${(entry.duration_ms / 1000).toFixed(2)}s` : 'ongoing', metrics: entry.metrics, measurements: entry.measurements, impact_summary: this.generateImpactSummary(entry), recommendations: this.generateRecommendations(entry) }; return report; } /** * Get dashboard data * @param {Object} options - Dashboard options * @returns {Promise<Object>} Dashboard data */ async getDashboard(options = {}) { const { period = '7d' } = options; const metrics = await this.loadMetrics(); const cutoff = this.getPeriodCutoff(period); const recentImprovements = metrics.improvements.filter( i => new Date(i.timestamp) > cutoff ); const dashboard = { period, summary: { total_improvements: recentImprovements.length, successful: recentImprovements.filter(i => i.outcome === 'success').length, failed: recentImprovements.filter(i => i.outcome === 'failed').length, rolled_back: recentImprovements.filter(i => i.outcome === 'rolled_back').length, pending: recentImprovements.filter(i => i.outcome === 'pending').length }, performance: this.calculatePerformanceMetrics(recentImprovements), quality: this.calculateQualityMetrics(recentImprovements), trends: metrics.trends, top_improvements: this.getTopImprovements(recentImprovements, 5), recommendations: this.generateDashboardRecommendations(metrics) }; return dashboard; } /** * Generate analytics report * @param {Object} options - Report options * @returns {Promise<Object>} Analytics report */ async generateAnalytics(options = {}) { const metrics = await this.loadMetrics(); const analytics = { generated: new Date().toISOString(), period: options.period || 'all-time', improvements: { total: metrics.improvements.length, by_outcome: this.groupByOutcome(metrics.improvements), by_category: this.groupByCategory(metrics.improvements), by_month: this.groupByMonth(metrics.improvements) }, performance: { average_duration: this.calculateAverageDuration(metrics.improvements), success_rate: this.calculateSuccessRate(metrics.improvements), improvement_velocity: this.calculateVelocity(metrics.improvements) }, impact: { total_files_modified: metrics.aggregates.total_files_modified, total_functions_improved: metrics.aggregates.total_functions_improved, average_improvement_score: this.calculateAverageImprovementScore(metrics.improvements) }, patterns: this.identifyPatterns(metrics.improvements), insights: this.generateInsights(metrics) }; return analytics; } /** * Gather measurements for improvement * @private */ async gatherMeasurements(improvement) { const measurements = { baseline: {}, projected: {}, actual: {} }; // Baseline measurements from analysis if (improvement.analysis) { measurements.baseline = { overall_score: improvement.analysis.overall_score, category_scores: improvement.analysis.categories ? Object.entries(improvement.analysis.categories).reduce((acc, [cat, data]) => { acc[cat] = data.score; return acc; }, {}) : {} }; } // Projected improvements from plan if (improvement.plan) { measurements.projected = { impact: improvement.plan.estimatedImpact, effort: improvement.plan.estimatedEffort, risk: improvement.plan.riskLevel, files: improvement.plan.affectedFiles?.length || 0 }; } // Actual measurements will be filled later measurements.actual = { timestamp: new Date().toISOString() }; return measurements; } /** * Update aggregates * @private */ async updateAggregates(metrics, entry) { const agg = metrics.aggregates; agg.total_improvements++; if (entry.measurements.projected.files) { agg.total_files_modified += entry.measurements.projected.files; } // Update category counts if (entry.plan && entry.plan.target_areas) { entry.plan.target_areas.forEach(area => { agg.improvements_by_category[area] = (agg.improvements_by_category[area] || 0) + 1; }); } // Update hourly distribution const hour = new Date(entry.timestamp).getHours(); agg.improvements_by_hour[hour] = (agg.improvements_by_hour[hour] || 0) + 1; } /** * Update outcome aggregates * @private */ async updateOutcomeAggregates(metrics, entry) { const agg = metrics.aggregates; switch (entry.outcome) { case 'success': agg.successful_improvements++; if (entry.duration_ms) { agg.total_duration_ms += entry.duration_ms; } break; case 'failed': agg.failed_improvements++; break; case 'rolled_back': agg.rolled_back_improvements++; break; } // Update success rate const total = agg.successful_improvements + agg.failed_improvements + agg.rolled_back_improvements; agg.success_rate = total > 0 ? (agg.successful_improvements / total) * 100 : 0; } /** * Calculate trends * @private */ async calculateTrends(metrics) { const trends = {}; // Success rate trend (last 5 periods) const periods = 5; const periodLength = 7 * 24 * 60 * 60 * 1000; // 7 days trends.success_rate = []; for (let i = 0; i < periods; i++) { const end = Date.now() - (i * periodLength); const start = end - periodLength; const periodImprovements = metrics.improvements.filter(imp => { const timestamp = new Date(imp.timestamp).getTime(); return timestamp >= start && timestamp < end; }); const successRate = this.calculateSuccessRate(periodImprovements); trends.success_rate.unshift({ period: i, rate: successRate, count: periodImprovements.length }); } // Velocity trend trends.velocity = this.calculateVelocityTrend(metrics.improvements); // Category trends trends.categories = this.calculateCategoryTrends(metrics.improvements); return trends; } /** * Calculate success rate * @private */ calculateSuccessRate(_improvements) { const completed = improvements.filter(i => i.outcome !== 'pending'); if (completed.length === 0) return 0; const successful = completed.filter(i => i.outcome === 'success').length; return (successful / completed.length) * 100; } /** * Calculate velocity trend * @private */ calculateVelocityTrend(_improvements) { const last30Days = Date.now() - (30 * 24 * 60 * 60 * 1000); const last60Days = Date.now() - (60 * 24 * 60 * 60 * 1000); const recent = improvements.filter(i => new Date(i.timestamp) > last30Days).length; const previous = improvements.filter(i => { const timestamp = new Date(i.timestamp); return timestamp > last60Days && timestamp <= last30Days; }).length; const change = previous > 0 ? ((recent - previous) / previous) * 100 : 0; return { current: recent, previous, change: change.toFixed(1), direction: change > 0 ? 'up' : change < 0 ? 'down' : 'stable' }; } /** * Generate impact summary * @private */ generateImpactSummary(entry) { const summary = { scope: 'unknown', magnitude: 'unknown', areas_affected: [] }; if (entry.measurements.projected) { const files = entry.measurements.projected.files || 0; if (files === 0) summary.scope = 'none'; else if (files <= 3) summary.scope = 'small'; else if (files <= 10) summary.scope = 'medium'; else summary.scope = 'large'; summary.magnitude = entry.measurements.projected.impact || 'unknown'; } if (entry.plan && entry.plan.target_areas) { summary.areas_affected = entry.plan.target_areas; } return summary; } /** * Generate recommendations * @private */ generateRecommendations(entry) { const recommendations = []; if (entry.outcome === 'failed') { recommendations.push({ type: 'investigation', message: 'Investigate failure cause and adjust validation criteria' }); } if (entry.outcome === 'rolled_back') { recommendations.push({ type: 'review', message: 'Review rollback reasons and improve testing coverage' }); } if (entry.measurements.projected && entry.measurements.projected.risk === 'high') { recommendations.push({ type: 'caution', message: 'Consider breaking high-risk improvements into smaller changes' }); } return recommendations; } /** * Generate dashboard recommendations * @private */ generateDashboardRecommendations(metrics) { const recommendations = []; if (metrics.aggregates.success_rate < 70) { recommendations.push({ priority: 'high', message: 'Success rate below 70% - review validation and testing processes' }); } if (metrics.aggregates.rolled_back_improvements > metrics.aggregates.successful_improvements * 0.2) { recommendations.push({ priority: 'medium', message: 'High rollback rate detected - improve sandbox testing' }); } const recentTrend = metrics.trends.velocity; if (recentTrend && recentTrend.direction === 'down' && recentTrend.change < -50) { recommendations.push({ priority: 'low', message: 'Improvement velocity decreasing - consider process optimization' }); } return recommendations; } /** * Get top improvements * @private */ getTopImprovements(_improvements, limit) { return improvements .filter(i => i.outcome === 'success') .sort((a, b) => { const scoreA = a.measurements.projected?.impact || 0; const scoreB = b.measurements.projected?.impact || 0; return scoreB - scoreA; }) .slice(0, limit) .map(i => ({ id: i.improvement_id, timestamp: i.timestamp, impact: i.measurements.projected?.impact, areas: i.plan?.target_areas || [] })); } /** * Identify patterns * @private */ identifyPatterns(_improvements) { const patterns = { common_failures: {}, success_factors: [], time_patterns: {} }; // Analyze failures const failures = improvements.filter(i => i.outcome === 'failed'); failures.forEach(f => { if (f.plan && f.plan.target_areas) { f.plan.target_areas.forEach(area => { patterns.common_failures[area] = (patterns.common_failures[area] || 0) + 1; }); } }); // Success patterns const successes = improvements.filter(i => i.outcome === 'success'); if (successes.length > 0) { const avgFiles = successes.reduce((sum, s) => sum + (s.measurements.projected?.files || 0), 0) / successes.length; patterns.success_factors.push({ factor: 'optimal_file_count', value: Math.round(avgFiles), confidence: 0.7 }); } return patterns; } /** * Generate insights * @private */ generateInsights(metrics) { const insights = []; // Time-based insights const hourlyDist = metrics.aggregates.improvements_by_hour; const peakHour = Object.entries(hourlyDist) .sort(([,a], [,b]) => b - a)[0]; if (peakHour) { insights.push({ type: 'timing', message: `Most improvements occur at ${peakHour[0]}:00 hours`, data: { hour: peakHour[0], count: peakHour[1] } }); } // Category insights const categories = metrics.aggregates.improvements_by_category; const topCategory = Object.entries(categories) .sort(([,a], [,b]) => b - a)[0]; if (topCategory) { insights.push({ type: 'focus', message: `${topCategory[0]} improvements are most common (${topCategory[1]} times)`, data: { category: topCategory[0], count: topCategory[1] } }); } return insights; } /** * Initialize aggregates * @private */ initializeAggregates() { return { total_improvements: 0, successful_improvements: 0, failed_improvements: 0, rolled_back_improvements: 0, total_files_modified: 0, total_functions_improved: 0, total_duration_ms: 0, success_rate: 0, improvements_by_category: {}, improvements_by_hour: {} }; } /** * Get period cutoff date * @private */ getPeriodCutoff(period) { const now = new Date(); switch (period) { case '24h': return new Date(now - 24 * 60 * 60 * 1000); case '7d': return new Date(now - 7 * 24 * 60 * 60 * 1000); case '30d': return new Date(now - 30 * 24 * 60 * 60 * 1000); case '90d': return new Date(now - 90 * 24 * 60 * 60 * 1000); default: return new Date(0); // All time } } /** * Calculate average duration * @private */ calculateAverageDuration(_improvements) { const completed = improvements.filter(i => i.duration_ms); if (completed.length === 0) return 0; const total = completed.reduce((sum, i) => sum + i.duration_ms, 0); return Math.round(total / completed.length); } /** * Calculate improvement velocity * @private */ calculateVelocity(_improvements) { const last7Days = this.getPeriodCutoff('7d'); const recent = improvements.filter(i => new Date(i.timestamp) > last7Days); return recent.length / 7; // Per day } /** * Calculate average improvement score * @private */ calculateAverageImprovementScore(_improvements) { const withScores = improvements.filter(i => i.measurements?.baseline?.overall_score && i.outcome === 'success' ); if (withScores.length === 0) return 0; const totalImprovement = withScores.reduce((sum, i) => { const baseline = parseFloat(i.measurements.baseline.overall_score) || 0; const projected = baseline + (i.measurements.projected?.impact || 0); return sum + (projected - baseline); }, 0); return (totalImprovement / withScores.length).toFixed(2); } /** * Group improvements by outcome * @private */ groupByOutcome(_improvements) { return improvements.reduce((groups, imp) => { const outcome = imp.outcome || 'pending'; groups[outcome] = (groups[outcome] || 0) + 1; return groups; }, {}); } /** * Group improvements by category * @private */ groupByCategory(_improvements) { const groups = {}; improvements.forEach(imp => { if (imp.plan && imp.plan.target_areas) { imp.plan.target_areas.forEach(area => { groups[area] = (groups[area] || 0) + 1; }); } }); return groups; } /** * Group improvements by month * @private */ groupByMonth(_improvements) { return improvements.reduce((groups, imp) => { const month = new Date(imp.timestamp).toISOString().substring(0, 7); groups[month] = (groups[month] || 0) + 1; return groups; }, {}); } /** * Calculate performance metrics * @private */ calculatePerformanceMetrics(_improvements) { const successful = improvements.filter(i => i.outcome === 'success'); return { average_duration: this.calculateAverageDuration(successful), fastest_improvement: successful .filter(i => i.duration_ms) .sort((a, b) => a.duration_ms - b.duration_ms)[0]?.duration_ms || null, slowest_improvement: successful .filter(i => i.duration_ms) .sort((a, b) => b.duration_ms - a.duration_ms)[0]?.duration_ms || null }; } /** * Calculate quality metrics * @private */ calculateQualityMetrics(_improvements) { return { test_coverage_impact: 'N/A', // Would need actual test data complexity_reduction: 'N/A', // Would need complexity analysis error_rate_change: 'N/A' // Would need error tracking }; } /** * Calculate category trends * @private */ calculateCategoryTrends(_improvements) { const trends = {}; const categories = Object.keys(this.categories); categories.forEach(cat => { const catImprovements = improvements.filter(i => i.plan?.target_areas?.includes(cat) ); trends[cat] = { total: catImprovements.length, success_rate: this.calculateSuccessRate(catImprovements), recent_activity: catImprovements.filter(i => new Date(i.timestamp) > this.getPeriodCutoff('7d') ).length }; }); return trends; } /** * Load metrics * @private */ async loadMetrics() { try { const content = await fs.readFile(this.metricsFile, 'utf-8'); return JSON.parse(content); } catch { return { version: '1.0.0', improvements: [], aggregates: this.initializeAggregates(), trends: {} }; } } /** * Save metrics * @private */ async saveMetrics(metrics) { await fs.writeFile( this.metricsFile, JSON.stringify(metrics, null, 2) ); } } module.exports = MetricsTracker;