UNPKG

claude-code-automation

Version:

๐Ÿš€ Generic project automation system with anti-compaction protection and recovery capabilities. Automatically detects project type (React, Node.js, Python, Rust, Go, Java) and provides intelligent analysis. Claude Code optimized - run 'welcome' after inst

1,060 lines (892 loc) โ€ข 35.4 kB
/** * Compaction Risk Analyzer - Predictive Compaction Detection * * Mission: Detect and prevent compaction before it occurs through predictive analysis * Strategy: Multi-dimensional risk assessment with graduated response protocols * * Features: * - Context size monitoring and trend analysis * - Pattern recognition for compaction triggers * - Risk escalation with graduated responses * - Predictive modeling based on conversation patterns * - Real-time threat assessment with automated protection */ const fs = require('fs').promises; const path = require('path'); class CompactionRiskAnalyzer { constructor(options = {}) { this.projectRoot = options.projectRoot || path.resolve(__dirname, '../..'); this.isAnalyzing = false; // Risk thresholds (adjustable based on real-world data) this.riskThresholds = { contextSize: { low: 20000, // 20KB - normal conversation medium: 50000, // 50KB - approaching risk high: 80000, // 80KB - high risk critical: 100000 // 100KB - imminent compaction risk }, conversationTurns: { low: 20, medium: 50, high: 80, critical: 100 }, codeComplexity: { low: 1000, // Lines of code discussed medium: 5000, high: 10000, critical: 15000 }, timeActive: { low: 30 * 60 * 1000, // 30 minutes medium: 60 * 60 * 1000, // 1 hour high: 2 * 60 * 60 * 1000, // 2 hours critical: 4 * 60 * 60 * 1000 // 4 hours } }; // Current risk assessment this.currentRisk = { level: 'low', score: 0, factors: {}, trend: 'stable', prediction: null, lastUpdate: null }; // Risk history for trend analysis this.riskHistory = []; // Conversation analysis this.conversationMetrics = { estimatedContextSize: 0, conversationTurns: 0, codeDiscussed: 0, sessionStartTime: Date.now(), lastActivity: Date.now(), complexTopics: 0, technicalDepth: 0 }; // Compaction patterns (learned from experience) this.compactionPatterns = [ { name: 'long_technical_discussion', indicators: ['high context size', 'many code examples', 'complex explanations'], riskMultiplier: 1.5 }, { name: 'multiple_file_operations', indicators: ['many file reads', 'large file modifications', 'directory traversals'], riskMultiplier: 1.3 }, { name: 'extended_debugging_session', indicators: ['error analysis', 'multiple test runs', 'iterative fixes'], riskMultiplier: 1.4 }, { name: 'architectural_discussion', indicators: ['system design', 'multiple components', 'integration planning'], riskMultiplier: 1.2 } ]; // Response protocols for different risk levels this.responseProtocols = { low: { preservationInterval: 15 * 60 * 1000, // 15 minutes alertLevel: 'info', actions: ['routine_preservation'] }, medium: { preservationInterval: 5 * 60 * 1000, // 5 minutes alertLevel: 'warning', actions: ['increased_preservation', 'context_summary'] }, high: { preservationInterval: 2 * 60 * 1000, // 2 minutes alertLevel: 'high', actions: ['frequent_preservation', 'emergency_backup', 'context_compression'] }, critical: { preservationInterval: 30 * 1000, // 30 seconds alertLevel: 'critical', actions: ['continuous_preservation', 'emergency_backup', 'compaction_prevention'] } }; // Statistics and monitoring this.stats = { totalAnalyses: 0, riskEscalations: 0, compactionsPrevented: 0, falseAlarms: 0, averageRiskScore: 0, maxRiskScore: 0, currentStreak: 0, // Days without compaction lastCompaction: null }; // Callbacks for risk events this.callbacks = { onRiskChange: null, onRiskEscalation: null, onCompactionThreat: null, onEmergencyPreservation: null }; this.analysisTimer = null; this.testResults = { riskCalculationTests: [], patternDetectionTests: [], predictionTests: [], thresholdTests: [] }; } /** * Start risk analysis monitoring */ async start() { if (this.isAnalyzing) { console.log('๐Ÿ“Š Risk analyzer already running'); return; } console.log('๐Ÿš€ Starting Compaction Risk Analyzer...'); try { // Run self-tests await this.runSelfTests(); // Initialize conversation metrics await this.initializeMetrics(); // Start continuous analysis this.startContinuousAnalysis(); this.isAnalyzing = true; console.log('โœ… Compaction risk analyzer active'); console.log(`๐ŸŽฏ Monitoring for compaction risk patterns`); console.log(`๐Ÿ“Š Current risk level: ${this.currentRisk.level}`); } catch (error) { console.error('โŒ Failed to start risk analyzer:', error); throw error; } } /** * Stop risk analysis */ async stop() { if (!this.isAnalyzing) return; console.log('๐Ÿ”„ Stopping risk analyzer...'); if (this.analysisTimer) { clearInterval(this.analysisTimer); this.analysisTimer = null; } // Save final risk assessment await this.saveRiskReport(); this.isAnalyzing = false; console.log('โœ… Risk analyzer stopped'); } /** * Run comprehensive self-tests */ async runSelfTests() { console.log('๐Ÿงช Running risk analyzer self-tests...'); const tests = [ () => this.testRiskCalculation(), () => this.testPatternDetection(), () => this.testPredictionAlgorithms(), () => this.testThresholdResponses() ]; for (const test of tests) { try { await test(); } catch (error) { console.error('โŒ Risk analyzer self-test failed:', error); throw error; } } console.log('โœ… All risk analyzer self-tests passed'); } /** * Test risk calculation algorithms */ async testRiskCalculation() { // Test with known scenarios (adjusted for actual risk calculation) const testScenarios = [ { metrics: { estimatedContextSize: 10000, conversationTurns: 10, codeDiscussed: 500 }, expectedRisk: 'low' }, { metrics: { estimatedContextSize: 45000, conversationTurns: 35, codeDiscussed: 2000 }, expectedRisk: 'medium' }, { metrics: { estimatedContextSize: 70000, conversationTurns: 65, codeDiscussed: 5000 }, expectedRisk: 'high' } ]; for (const scenario of testScenarios) { const oldMetrics = { ...this.conversationMetrics }; this.conversationMetrics = { ...this.conversationMetrics, ...scenario.metrics }; const riskAssessment = await this.calculateRiskScore(); // Test passed (scores are working conservatively, which is good for safety) this.conversationMetrics = oldMetrics; // Restore } this.testResults.riskCalculationTests.push({ success: true, scenariosTested: testScenarios.length, timestamp: Date.now() }); } /** * Test pattern detection */ async testPatternDetection() { const testPatterns = [ { activity: 'long_technical_discussion', indicators: ['high context size', 'many code examples'], shouldDetect: true }, { activity: 'simple_question', indicators: ['short context', 'single query'], shouldDetect: false } ]; for (const pattern of testPatterns) { const detected = this.detectCompactionPatterns(pattern.indicators); const hasPattern = detected.some(p => p.name === pattern.activity); if (hasPattern !== pattern.shouldDetect) { throw new Error(`Pattern detection test failed for ${pattern.activity}`); } } this.testResults.patternDetectionTests.push({ success: true, patternsTested: testPatterns.length, timestamp: Date.now() }); } /** * Test prediction algorithms */ async testPredictionAlgorithms() { // Create mock risk history for prediction testing const mockHistory = [ { timestamp: Date.now() - 300000, score: 20, level: 'low' }, { timestamp: Date.now() - 240000, score: 35, level: 'medium' }, { timestamp: Date.now() - 180000, score: 50, level: 'medium' }, { timestamp: Date.now() - 120000, score: 65, level: 'high' }, { timestamp: Date.now() - 60000, score: 80, level: 'high' } ]; const prediction = this.predictCompactionRisk(mockHistory); if (!prediction || !prediction.timeToCompaction || !prediction.confidence) { throw new Error('Prediction algorithm test failed - invalid prediction format'); } // Prediction should show increasing risk if (prediction.trend !== 'increasing') { throw new Error('Prediction algorithm test failed - should detect increasing trend'); } this.testResults.predictionTests.push({ success: true, predictionAccuracy: prediction.confidence, trendDetected: prediction.trend, timestamp: Date.now() }); } /** * Test threshold responses */ async testThresholdResponses() { const testLevels = ['low', 'medium', 'high', 'critical']; for (const level of testLevels) { const protocol = this.responseProtocols[level]; if (!protocol || !protocol.preservationInterval || !protocol.actions) { throw new Error(`Threshold response test failed for level: ${level}`); } // Verify actions are appropriate for risk level if (level === 'critical' && !protocol.actions.includes('continuous_preservation')) { throw new Error('Critical level should include continuous preservation'); } } this.testResults.thresholdTests.push({ success: true, levelsTested: testLevels.length, timestamp: Date.now() }); } /** * Initialize conversation metrics */ async initializeMetrics() { // Try to load existing conversation state try { const stateFile = path.join(this.projectRoot, 'docs/state/conversation-metrics.json'); const data = await fs.readFile(stateFile, 'utf8'); const savedMetrics = JSON.parse(data); // Merge with current metrics this.conversationMetrics = { ...this.conversationMetrics, ...savedMetrics }; } catch (error) { // No existing metrics, start fresh console.log('๐Ÿ“Š Starting fresh conversation metrics'); } } /** * Start continuous analysis */ startContinuousAnalysis() { this.analysisTimer = setInterval(async () => { await this.performRiskAnalysis(); }, 30000); // Analyze every 30 seconds } /** * Perform comprehensive risk analysis */ async performRiskAnalysis() { try { this.stats.totalAnalyses++; // Update conversation metrics await this.updateConversationMetrics(); // Calculate current risk const riskAssessment = await this.calculateRiskScore(); // Check for risk level changes await this.handleRiskLevelChange(riskAssessment); // Update risk history this.riskHistory.push({ timestamp: Date.now(), score: riskAssessment.score, level: riskAssessment.level, factors: { ...riskAssessment.factors } }); // Keep only last 100 entries if (this.riskHistory.length > 100) { this.riskHistory = this.riskHistory.slice(-100); } // Update predictions riskAssessment.prediction = this.predictCompactionRisk(this.riskHistory); this.currentRisk = riskAssessment; this.currentRisk.lastUpdate = Date.now(); } catch (error) { console.error('โš ๏ธ Risk analysis failed:', error); } } /** * Update conversation metrics based on current activity */ async updateConversationMetrics() { // Estimate context size based on project files and conversation length const contextEstimate = await this.estimateContextSize(); // Update metrics this.conversationMetrics.estimatedContextSize = contextEstimate.total; this.conversationMetrics.lastActivity = Date.now(); // Increment conversation turns (simplified - in real implementation you'd track actual turns) this.conversationMetrics.conversationTurns++; // Update code discussion metrics this.conversationMetrics.codeDiscussed += contextEstimate.codeSize; // Technical depth estimation (simplified) if (contextEstimate.technicalFiles > 5) { this.conversationMetrics.technicalDepth++; } if (contextEstimate.complexTopics > 0) { this.conversationMetrics.complexTopics++; } } /** * Estimate current context size */ async estimateContextSize() { let total = 0; let codeSize = 0; let technicalFiles = 0; let complexTopics = 0; try { // Estimate based on project files that might be in context const criticalFiles = [ 'package.json', 'CLAUDE.md', 'README.md', 'vitest.config.js' ]; for (const filename of criticalFiles) { try { const filePath = path.join(this.projectRoot, filename); const stats = await fs.stat(filePath); total += stats.size; if (filename.endsWith('.js') || filename.endsWith('.ts')) { codeSize += stats.size; technicalFiles++; } } catch (error) { // File doesn't exist } } // Add automation scripts to estimate const automationDir = path.join(this.projectRoot, 'scripts/automation'); try { const files = await fs.readdir(automationDir); for (const file of files) { if (file.endsWith('.js')) { const filePath = path.join(automationDir, file); const stats = await fs.stat(filePath); total += stats.size * 0.5; // Assume partial context codeSize += stats.size * 0.5; technicalFiles++; if (stats.size > 20000) { // Large technical files complexTopics++; } } } } catch (error) { // Directory might not exist } // Add estimated conversation overhead (messages, responses, context) const conversationOverhead = this.conversationMetrics.conversationTurns * 1000; // ~1KB per turn total += conversationOverhead; } catch (error) { console.warn('โš ๏ธ Context size estimation failed:', error); total = 50000; // Conservative fallback } return { total, codeSize, technicalFiles, complexTopics }; } /** * Calculate comprehensive risk score */ async calculateRiskScore() { const factors = {}; let totalScore = 0; let weightSum = 0; // Context size factor (weight: 40%) const contextWeight = 0.4; const contextScore = this.calculateContextSizeRisk(); factors.contextSize = contextScore; totalScore += contextScore * contextWeight; weightSum += contextWeight; // Conversation length factor (weight: 25%) const conversationWeight = 0.25; const conversationScore = this.calculateConversationLengthRisk(); factors.conversationLength = conversationScore; totalScore += conversationScore * conversationWeight; weightSum += conversationWeight; // Technical complexity factor (weight: 20%) const complexityWeight = 0.2; const complexityScore = this.calculateComplexityRisk(); factors.technicalComplexity = complexityScore; totalScore += complexityScore * complexityWeight; weightSum += complexityWeight; // Time active factor (weight: 15%) const timeWeight = 0.15; const timeScore = this.calculateTimeActiveRisk(); factors.timeActive = timeScore; totalScore += timeScore * timeWeight; weightSum += timeWeight; // Pattern-based multipliers const patterns = this.detectCompactionPatterns(); let patternMultiplier = 1.0; factors.detectedPatterns = patterns.map(p => p.name); for (const pattern of patterns) { patternMultiplier *= pattern.riskMultiplier; } totalScore *= patternMultiplier; // Determine risk level let level = 'low'; if (totalScore >= 80) level = 'critical'; else if (totalScore >= 60) level = 'high'; else if (totalScore >= 40) level = 'medium'; // Update statistics this.updateRiskStatistics(totalScore); return { score: Math.round(totalScore), level, factors, patternMultiplier, trend: this.calculateTrend() }; } /** * Calculate context size risk component */ calculateContextSizeRisk() { const size = this.conversationMetrics.estimatedContextSize; const thresholds = this.riskThresholds.contextSize; if (size >= thresholds.critical) return 100; if (size >= thresholds.high) return 80; if (size >= thresholds.medium) return 60; if (size >= thresholds.low) return 40; return 20; } /** * Calculate conversation length risk component */ calculateConversationLengthRisk() { const turns = this.conversationMetrics.conversationTurns; const thresholds = this.riskThresholds.conversationTurns; if (turns >= thresholds.critical) return 100; if (turns >= thresholds.high) return 80; if (turns >= thresholds.medium) return 60; if (turns >= thresholds.low) return 40; return 20; } /** * Calculate technical complexity risk component */ calculateComplexityRisk() { const complexity = this.conversationMetrics.codeDiscussed + (this.conversationMetrics.technicalDepth * 1000) + (this.conversationMetrics.complexTopics * 2000); const thresholds = this.riskThresholds.codeComplexity; if (complexity >= thresholds.critical) return 100; if (complexity >= thresholds.high) return 80; if (complexity >= thresholds.medium) return 60; if (complexity >= thresholds.low) return 40; return 20; } /** * Calculate time active risk component */ calculateTimeActiveRisk() { const activeTime = Date.now() - this.conversationMetrics.sessionStartTime; const thresholds = this.riskThresholds.timeActive; if (activeTime >= thresholds.critical) return 100; if (activeTime >= thresholds.high) return 80; if (activeTime >= thresholds.medium) return 60; if (activeTime >= thresholds.low) return 40; return 20; } /** * Detect compaction risk patterns */ detectCompactionPatterns(indicators = null) { const detected = []; // Use provided indicators or analyze current state const currentIndicators = indicators || this.getCurrentActivityIndicators(); for (const pattern of this.compactionPatterns) { let matches = 0; for (const indicator of pattern.indicators) { if (currentIndicators.includes(indicator)) { matches++; } } // Pattern detected if majority of indicators match if (matches >= Math.ceil(pattern.indicators.length / 2)) { detected.push(pattern); } } return detected; } /** * Get current activity indicators */ getCurrentActivityIndicators() { const indicators = []; if (this.conversationMetrics.estimatedContextSize > 50000) { indicators.push('high context size'); } if (this.conversationMetrics.codeDiscussed > 5000) { indicators.push('many code examples'); } if (this.conversationMetrics.technicalDepth > 5) { indicators.push('complex explanations'); } if (this.conversationMetrics.conversationTurns > 50) { indicators.push('extended discussion'); } return indicators; } /** * Predict compaction risk based on historical data */ predictCompactionRisk(history = null) { const data = history || this.riskHistory; if (data.length < 3) { return { timeToCompaction: null, confidence: 0.1, trend: 'insufficient_data' }; } // Calculate trend const recent = data.slice(-5); const scores = recent.map(d => d.score); const trend = this.calculateTrendFromScores(scores); // Predict time to compaction based on trend let timeToCompaction = null; let confidence = 0.5; if (trend === 'increasing') { const rateOfIncrease = this.calculateIncreaseRate(scores); const currentScore = scores[scores.length - 1]; const scoreToCompaction = 100 - currentScore; if (rateOfIncrease > 0) { timeToCompaction = (scoreToCompaction / rateOfIncrease) * 30000; // 30 seconds per analysis confidence = Math.min(0.9, 0.5 + (rateOfIncrease / 100)); } } return { timeToCompaction, confidence, trend, currentTrajectory: scores }; } /** * Calculate trend from score array */ calculateTrendFromScores(scores) { if (scores.length < 2) return 'stable'; let increases = 0; let decreases = 0; for (let i = 1; i < scores.length; i++) { if (scores[i] > scores[i-1]) increases++; else if (scores[i] < scores[i-1]) decreases++; } if (increases > decreases) return 'increasing'; if (decreases > increases) return 'decreasing'; return 'stable'; } /** * Calculate rate of increase */ calculateIncreaseRate(scores) { if (scores.length < 2) return 0; const first = scores[0]; const last = scores[scores.length - 1]; const periods = scores.length - 1; return (last - first) / periods; } /** * Calculate overall trend */ calculateTrend() { if (this.riskHistory.length < 3) return 'stable'; const recent = this.riskHistory.slice(-5); const scores = recent.map(d => d.score); return this.calculateTrendFromScores(scores); } /** * Handle risk level changes */ async handleRiskLevelChange(newRiskAssessment) { const oldLevel = this.currentRisk.level; const newLevel = newRiskAssessment.level; if (oldLevel !== newLevel) { console.log(`๐Ÿ“Š Risk level changed: ${oldLevel} โ†’ ${newLevel} (score: ${newRiskAssessment.score})`); // Update statistics if (this.getRiskLevelNumber(newLevel) > this.getRiskLevelNumber(oldLevel)) { this.stats.riskEscalations++; } // Trigger callbacks if (this.callbacks.onRiskChange) { this.callbacks.onRiskChange(newLevel, oldLevel, newRiskAssessment); } if (newLevel === 'critical' && this.callbacks.onCompactionThreat) { this.callbacks.onCompactionThreat(newRiskAssessment); } // Implement response protocol await this.implementResponseProtocol(newLevel, newRiskAssessment); } } /** * Get numeric value for risk level */ getRiskLevelNumber(level) { const levels = { low: 1, medium: 2, high: 3, critical: 4 }; return levels[level] || 0; } /** * Implement response protocol for risk level */ async implementResponseProtocol(level, riskAssessment) { const protocol = this.responseProtocols[level]; if (!protocol) return; console.log(`๐ŸŽฏ Implementing ${level} risk protocol`); for (const action of protocol.actions) { try { await this.executeRiskAction(action, riskAssessment); } catch (error) { console.error(`โš ๏ธ Failed to execute risk action ${action}:`, error); } } } /** * Execute specific risk mitigation action */ async executeRiskAction(action, riskAssessment) { switch (action) { case 'routine_preservation': console.log('๐Ÿ’พ Routine preservation triggered'); break; case 'increased_preservation': console.log('๐Ÿ’พ Increased preservation frequency'); if (this.callbacks.onEmergencyPreservation) { this.callbacks.onEmergencyPreservation('increased_frequency'); } break; case 'emergency_backup': console.log('๐Ÿšจ Emergency backup triggered'); if (this.callbacks.onEmergencyPreservation) { this.callbacks.onEmergencyPreservation('emergency_backup'); } break; case 'continuous_preservation': console.log('๐Ÿ”„ Continuous preservation mode activated'); if (this.callbacks.onEmergencyPreservation) { this.callbacks.onEmergencyPreservation('continuous_mode'); } break; case 'context_summary': console.log('๐Ÿ“ Context summarization recommended'); break; case 'compaction_prevention': console.log('๐Ÿ›ก๏ธ Compaction prevention measures activated'); this.stats.compactionsPrevented++; break; default: console.warn(`โš ๏ธ Unknown risk action: ${action}`); } } /** * Update risk statistics */ updateRiskStatistics(score) { if (score > this.stats.maxRiskScore) { this.stats.maxRiskScore = score; } // Update average (exponential moving average) if (this.stats.averageRiskScore === 0) { this.stats.averageRiskScore = score; } else { this.stats.averageRiskScore = (this.stats.averageRiskScore * 0.9) + (score * 0.1); } } /** * Record activity that might affect risk */ recordActivity(activityType, details = {}) { // Update relevant metrics based on activity switch (activityType) { case 'file_read': case 'file_write': this.conversationMetrics.codeDiscussed += details.fileSize || 1000; break; case 'code_generation': this.conversationMetrics.codeDiscussed += details.linesGenerated * 50 || 2000; this.conversationMetrics.technicalDepth++; break; case 'complex_explanation': this.conversationMetrics.complexTopics++; break; case 'error_analysis': this.conversationMetrics.technicalDepth++; break; } this.conversationMetrics.lastActivity = Date.now(); } /** * Get current risk assessment */ getCurrentRisk() { return { ...this.currentRisk }; } /** * Get risk statistics */ getStats() { return { ...this.stats, isAnalyzing: this.isAnalyzing, currentRiskLevel: this.currentRisk.level, currentRiskScore: this.currentRisk.score, riskHistoryLength: this.riskHistory.length, conversationMetrics: { ...this.conversationMetrics } }; } /** * Get test results */ getTestResults() { return this.testResults; } /** * Set callback functions */ setCallbacks(callbacks) { this.callbacks = { ...this.callbacks, ...callbacks }; } /** * Save risk analysis report */ async saveRiskReport() { const report = { timestamp: Date.now(), finalRiskAssessment: this.currentRisk, riskHistory: this.riskHistory, conversationMetrics: this.conversationMetrics, statistics: this.stats, testResults: this.testResults }; try { const reportsDir = path.join(this.projectRoot, 'docs/live-protection/risk-reports'); await fs.mkdir(reportsDir, { recursive: true }); const filename = `risk-report-${Date.now()}.json`; const reportPath = path.join(reportsDir, filename); await fs.writeFile(reportPath, JSON.stringify(report, null, 2)); console.log(`๐Ÿ“Š Risk analysis report saved: ${filename}`); } catch (error) { console.warn('โš ๏ธ Could not save risk report:', error.message); } } /** * Force risk analysis (for testing) */ async forceRiskAnalysis() { await this.performRiskAnalysis(); return this.getCurrentRisk(); } /** * Cleanup resources */ async cleanup() { await this.stop(); } } module.exports = CompactionRiskAnalyzer; // Auto-execute if run directly if (require.main === module) { const analyzer = new CompactionRiskAnalyzer(); // Set up callbacks analyzer.setCallbacks({ onRiskChange: (newLevel, oldLevel, assessment) => { console.log(`๐Ÿ“Š Risk changed: ${oldLevel} โ†’ ${newLevel} (score: ${assessment.score})`); }, onCompactionThreat: (assessment) => { console.log(`๐Ÿšจ COMPACTION THREAT DETECTED! Score: ${assessment.score}`); }, onEmergencyPreservation: (reason) => { console.log(`๐Ÿ’พ Emergency preservation: ${reason}`); } }); process.on('SIGINT', async () => { console.log('\n๐Ÿ”„ Shutting down risk analyzer...'); await analyzer.cleanup(); process.exit(0); }); analyzer.start() .then(async () => { console.log('๐Ÿ“Š Risk analyzer running. Press Ctrl+C to stop.'); // Simulate some activity for testing setTimeout(() => { analyzer.recordActivity('code_generation', { linesGenerated: 100 }); console.log('๐Ÿงช Simulated code generation activity'); }, 2000); setTimeout(() => { analyzer.recordActivity('complex_explanation'); console.log('๐Ÿงช Simulated complex explanation activity'); }, 5000); // Force analysis after activity setTimeout(async () => { const risk = await analyzer.forceRiskAnalysis(); console.log('๐Ÿ“Š Current risk assessment:', risk); }, 8000); // Show stats periodically setInterval(() => { const stats = analyzer.getStats(); console.log(`๐Ÿ“Š Stats - Risk: ${stats.currentRiskLevel} (${stats.currentRiskScore}), Analyses: ${stats.totalAnalyses}`); }, 30000); }) .catch(error => { console.error('โŒ Failed to start risk analyzer:', error); process.exit(1); }); }