UNPKG

@invisiblecities/sidequest-cqo

Version:

Configuration-agnostic TypeScript and ESLint orchestrator with real-time watch mode, SQLite persistence, and intelligent terminal detection

376 lines 16.2 kB
/** * Analysis Service for Code Quality Orchestrator * Provides historical analysis and delta computation */ import { arrayAt } from "../utils/node-compatibility.js"; // import type { // ViolationHistory, // HistoryQueryParams // } from '../database/types.js'; // ============================================================================ // Analysis Service Implementation // ============================================================================ export class AnalysisService { storageService; constructor(storageService) { this.storageService = storageService; } // ======================================================================== // Delta Analysis // ======================================================================== computeViolationDeltas(previousViolations, currentViolations) { const previousSet = new Set(previousViolations); const currentSet = new Set(currentViolations); const added = currentViolations.filter((hash) => !previousSet.has(hash)); const removed = previousViolations.filter((hash) => !currentSet.has(hash)); const unchanged = currentViolations.filter((hash) => previousSet.has(hash)); return { added, removed, unchanged }; } // ======================================================================== // Historical Analysis // ======================================================================== async getViolationTrends(timeRange) { const violations = await this.storageService.getViolations({ since: timeRange.start.toISOString(), }); // Group violations by date, category, and severity const trendsMap = new Map(); for (const violation of violations) { const date = violation.last_seen_at?.split("T")?.[0] ?? new Date().toISOString().split("T")[0]; // Extract date part const key = `${date}:${violation.category}:${violation.severity}`; if (trendsMap.has(key)) { trendsMap.get(key).count++; } else { trendsMap.set(key, { date, count: 1, severity: violation.severity, category: violation.category, }); } } return [...trendsMap.values()].sort((a, b) => a.date.localeCompare(b.date)); } getRulePerformanceAnalysis(ruleId) { // const rulePerformance = await this.storageService.getRulePerformance(); const rulePerformance = []; // TODO: Implement getRulePerformance on IStorageService let filteredPerformance = rulePerformance; if (ruleId) { filteredPerformance = rulePerformance.filter((rule) => rule.rule_id === ruleId); } return filteredPerformance.map((rule) => { const ruleData = rule; return { rule: ruleData.rule_id, engine: ruleData.engine, avgExecutionTime: ruleData.avg_execution_time_ms || 0, avgViolationsFound: ruleData.avg_violations_found || 0, successRate: ruleData.total_runs > 0 ? ruleData.successful_runs / ruleData.total_runs : 0, lastRun: ruleData.last_run_at || "Never", trend: this.calculateTrend(ruleData.avg_violations_found ?? 0, ruleData.consecutive_zero_count ?? 0), }; }); } async getFileQualityTrends(filePath) { const violations = await this.storageService.getViolations(filePath ? { file_paths: [filePath] } : {}); // Group by file path const fileMap = new Map(); for (const violation of violations) { if (!fileMap.has(violation.file_path)) { fileMap.set(violation.file_path, { count: 0, categories: new Set() }); } const fileData = fileMap.get(violation.file_path); fileData.count++; fileData.categories.add(violation.category); } return [...fileMap.entries()].map(([path, data]) => ({ filePath: path, violationCount: data.count, trend: this.calculateFileTrend(data.count), // Simplified trend calculation categories: [...data.categories], })); } // ======================================================================== // Statistical Analysis // ======================================================================== async calculateViolationStats(_timeRange) { // For now, get all active violations to show meaningful stats // TODO: Fix timestamp filtering when database schema is properly updated const violations = await this.storageService.getViolations(); const byCategory = {}; const bySeverity = {}; const bySource = {}; const uniqueFiles = new Set(); for (const violation of violations) { // Count by category byCategory[violation.category] = (byCategory[violation.category] || 0) + 1; // Count by severity bySeverity[violation.severity] = (bySeverity[violation.severity] || 0) + 1; // Count by source bySource[violation.source] = (bySource[violation.source] || 0) + 1; // Track unique files uniqueFiles.add(violation.file_path); } return { total: violations.length, byCategory, bySeverity, bySource, avgPerFile: uniqueFiles.size > 0 ? violations.length / uniqueFiles.size : 0, filesAffected: uniqueFiles.size, }; } async identifyProblemFiles(threshold = 10) { const fileQualityTrends = await this.getFileQualityTrends(); return fileQualityTrends .filter((file) => file.violationCount >= threshold) .map((file) => ({ filePath: file.filePath, violationCount: file.violationCount, severityScore: this.calculateSeverityScore(file.violationCount), categories: file.categories, lastModified: new Date().toISOString(), // Simplified - would need file system access })) .sort((a, b) => b.severityScore - a.severityScore); } detectRuleFlakyness(minRuns = 10) { // const rulePerformance = await this.storageService.getRulePerformance(); const rulePerformance = []; // TODO: Implement getRulePerformance return rulePerformance .filter((rule) => rule.total_runs >= minRuns) .map((rule) => { const ruleData = rule; const variance = this.calculateVariance(ruleData.avg_violations_found ?? 0, ruleData.total_runs); const stdDeviation = Math.sqrt(variance); return { rule: ruleData.rule_id, engine: ruleData.engine, varianceScore: variance, runCount: rule.total_runs, avgViolations: rule.avg_violations_found || 0, stdDeviation, }; }) .filter((rule) => rule.stdDeviation > 2) // High variance threshold .sort((a, b) => b.varianceScore - a.varianceScore); } // ======================================================================== // Predictive Analysis // ======================================================================== async predictViolationGrowth(timeRange) { const trends = await this.getViolationTrends(timeRange); if (trends.length < 2) { return { projectedGrowth: 0, confidence: 0, timeframe: "30 days", factors: ["Insufficient historical data"], }; } // Simple linear regression for growth prediction const totalByDate = this.groupTrendsByDate(trends); const growthRate = this.calculateGrowthRate(totalByDate); return { projectedGrowth: growthRate * 30, // 30-day projection confidence: Math.min(trends.length / 30, 1), // Confidence based on data points timeframe: "30 days", factors: this.identifyGrowthFactors(trends), }; } recommendRuleFrequencies() { // const rulePerformance = await this.storageService.getRulePerformance(); const rulePerformance = []; // TODO: Implement getRulePerformance return rulePerformance.map((rule) => { const currentFrequency = 30_000; // Default 30 seconds let recommendedFrequency = currentFrequency; let reasoning = "No change recommended"; // Rules that consistently find violations should run more frequently if (rule.avg_violations_found && rule.avg_violations_found > 5) { recommendedFrequency = currentFrequency / 2; reasoning = "High violation rate - increase frequency"; } // Rules that rarely find violations can run less frequently else if ((rule.avg_violations_found ?? 0) < 1 && (rule.consecutive_zero_count ?? 0) > 5) { recommendedFrequency = currentFrequency * 2; reasoning = "Low violation rate - decrease frequency"; } // Rules with high execution time should run less frequently else if ((rule.avg_execution_time_ms ?? 0) > 1000) { recommendedFrequency = currentFrequency * 1.5; reasoning = "High execution time - decrease frequency"; } return { rule: rule.rule_id, engine: rule.engine, currentFrequency, recommendedFrequency: Math.max(5000, Math.min(300_000, recommendedFrequency)), // 5s to 5min bounds reasoning, }; }); } // ======================================================================== // Report Generation // ======================================================================== async generateQualityReport(timeRange) { const [summary, trends, problemFiles, rulePerformance] = await Promise.all([ this.calculateViolationStats(timeRange), this.getViolationTrends(timeRange), this.identifyProblemFiles(5), this.getRulePerformanceAnalysis(), ]); const recommendations = this.generateRecommendations(summary, problemFiles, rulePerformance); return { timeRange, summary, trends, problemFiles, rulePerformance, recommendations, }; } async generateRuleEfficiencyReport() { // const rulePerformance = await this.storageService.getRulePerformance(); const rulePerformance = []; // TODO: Implement getRulePerformance const recommendations = await this.recommendRuleFrequencies(); const totalRules = rulePerformance.length; const activeRules = rulePerformance.filter((rule) => rule.enabled).length; const avgExecutionTime = rulePerformance.reduce((sum, rule) => sum + (rule.avg_execution_time_ms || 0), 0) / totalRules; return { totalRules, activeRules, avgExecutionTime, resourceUtilization: this.calculateResourceUtilization(rulePerformance), recommendations, }; } // ======================================================================== // Private Helper Methods // ======================================================================== calculateTrend(avgViolations, consecutiveZeroCount) { if (consecutiveZeroCount > 3) { return "improving"; } if (avgViolations < 1) { return "stable"; } if (avgViolations > 10) { return "degrading"; } return "stable"; } calculateFileTrend(violationCount) { // Simplified trend calculation - in practice would compare historical data if (violationCount < 3) { return "improving"; } if (violationCount > 15) { return "degrading"; } return "stable"; } calculateSeverityScore(violationCount) { // Simple scoring algorithm - could be more sophisticated return violationCount * 1.5; } calculateVariance(avgValue, sampleSize) { // Simplified variance calculation - would need actual data points return avgValue * (1 / Math.sqrt(sampleSize)); } groupTrendsByDate(trends) { const dateMap = new Map(); for (const trend of trends) { const current = dateMap.get(trend.date) || 0; dateMap.set(trend.date, current + trend.count); } return dateMap; } calculateGrowthRate(totalByDate) { const values = [...totalByDate.values()]; if (values.length < 2) { return 0; } const first = values[0]; const last = arrayAt(values, -1); if (first === undefined || last === undefined) { return 0; } return (last - first) / values.length; } identifyGrowthFactors(trends) { const factors = []; // Analyze category distribution const categoryCount = new Map(); for (const trend of trends) { categoryCount.set(trend.category, (categoryCount.get(trend.category) || 0) + trend.count); } // Find dominant categories const sortedCategories = [...categoryCount.entries()] .sort(([, a], [, b]) => b - a) .slice(0, 3); for (const [category] of sortedCategories) { factors.push(`High activity in ${category} violations`); } return factors; } calculateResourceUtilization(rulePerformance) { // Simplified resource utilization calculation const totalExecutionTime = rulePerformance.reduce((sum, rule) => sum + (rule.avg_execution_time_ms || 0), 0); const maxPossibleTime = rulePerformance.length * 1000; // Assume 1s max per rule return Math.min(totalExecutionTime / maxPossibleTime, 1); } generateRecommendations(summary, problemFiles, rulePerformance) { const recommendations = []; // High violation count recommendations if (summary.total > 100) { recommendations.push("Consider implementing stricter code review processes"); } // Problem files recommendations if (problemFiles.length > 5) { recommendations.push(`Focus refactoring efforts on ${problemFiles.length} high-violation files`); } // Rule performance recommendations const slowRules = rulePerformance.filter((rule) => (rule.avgExecutionTime || 0) > 500); if (slowRules.length > 0) { recommendations.push(`Optimize ${slowRules.length} slow-performing rules`); } // Category-specific recommendations if ((summary.byCategory?.["record-type"] || 0) > summary.total * 0.3) { recommendations.push("High number of record-type violations - consider TypeScript configuration updates"); } return recommendations; } } // ============================================================================ // Service Factory // ============================================================================ let analysisServiceInstance; /** * Get or create analysis service instance */ export function getAnalysisService(storageService) { if (!analysisServiceInstance) { analysisServiceInstance = new AnalysisService(storageService); } return analysisServiceInstance; } /** * Reset analysis service instance (useful for testing) */ export function resetAnalysisService() { analysisServiceInstance = undefined; } //# sourceMappingURL=analysis-service.js.map