#!/usr/bin/env node // ═══════════════════════════════════════════════════════════════════════════ // SIL Historical Analysis — Analysera trender över tid // Erik-krav: "Vilka moduler orsakar flest regressionsfel? Vilka komponenter // förändras oftast? Vilka delar av systemet har lägst confidence?" // ═══════════════════════════════════════════════════════════════════════════ import { readFileSync, existsSync, writeFileSync } from 'fs'; const LOGS = { shadow: '/home/bernt/.openclaw/workspace/SIL/shadow-log.jsonl', gold: '/home/bernt/.openclaw/workspace/SIL/gold-set.jsonl', stability: '/home/bernt/.openclaw/workspace/SIL/stability-log.jsonl', versions: '/home/bernt/.openclaw/workspace/SIL/graph-version-log.jsonl', policy: '/home/bernt/.openclaw/workspace/SIL/policy-violations.jsonl', reasoning: '/home/bernt/.openclaw/workspace/SIL/reasoning-results.jsonl' }; class HistoricalAnalysis { constructor() { this.data = this.loadAllLogs(); } loadAllLogs() { const data = {}; for (const [key, path] of Object.entries(LOGS)) { data[key] = this.loadLog(path); } return data; } loadLog(path) { if (!existsSync(path)) return []; return readFileSync(path, 'utf8') .split('\n') .filter(line => line.trim()) .map(line => { try { return JSON.parse(line); } catch { return null; } }) .filter(Boolean); } // ── Trendanalyser ─────────────────────────────────────────────────────── /** * Vilka moduler orsakar flest regressionsfel? */ analyzeRegressionFailures() { const goldEntries = this.data.gold; const failures = {}; for (const entry of goldEntries) { const predictions = entry.predictions || []; for (const pred of predictions) { if (pred.verified === false) { failures[pred.component] = (failures[pred.component] || 0) + 1; } } } return Object.entries(failures) .sort((a, b) => b[1] - a[1]) .map(([component, count]) => ({ component, failures: count, percentage: Math.round(count / goldEntries.length * 100) + '%' })); } /** * Vilka komponenter förändras oftast? */ analyzeChangeFrequency() { const shadowEntries = this.data.shadow; const changes = {}; for (const entry of shadowEntries) { const components = entry.components || entry.changedComponents || []; if (!Array.isArray(components)) continue; for (const comp of components) { const compId = typeof comp === 'string' ? comp : comp.id; if (compId) { changes[compId] = (changes[compId] || 0) + 1; } } } return Object.entries(changes) .sort((a, b) => b[1] - a[1]) .map(([component, count]) => ({ component, changes: count, trend: this.calculateTrend(component) })); } /** * Vilka delar har lägst confidence? */ analyzeLowConfidence() { const shadowEntries = this.data.shadow; const confidenceByComponent = {}; for (const entry of shadowEntries) { const components = entry.components || entry.changedComponents || []; if (!Array.isArray(components)) continue; for (const comp of components) { const compId = typeof comp === 'string' ? comp : comp.id; if (!compId) continue; if (!confidenceByComponent[compId]) { confidenceByComponent[compId] = []; } confidenceByComponent[compId].push(entry.confidence || 0); } } return Object.entries(confidenceByComponent) .map(([component, confidences]) => ({ component, avgConfidence: Math.round( confidences.reduce((a, b) => a + b, 0) / confidences.length * 100 ) + '%', minConfidence: Math.round(Math.min(...confidences) * 100) + '%', samples: confidences.length })) .sort((a, b) => parseFloat(a.avgConfidence) - parseFloat(b.avgConfidence)); } /** * Vilka beroenden är mest instabila? */ analyzeUnstableDependencies() { const goldEntries = this.data.gold; const dependencyChanges = {}; for (const entry of goldEntries) { const predicted = entry.predicted || []; const actual = entry.actual || []; // Räkna felaktiga förutsägelser per komponent for (const pred of predicted) { if (!actual.includes(pred)) { dependencyChanges[pred] = (dependencyChanges[pred] || 0) + 1; } } } return Object.entries(dependencyChanges) .sort((a, b) => b[1] - a[1]) .map(([component, count]) => ({ component, falsePredictions: count, instability: count / goldEntries.length })); } /** * Var ökar den tekniska skulden snabbast? */ analyzeTechnicalDebt() { const policyEntries = this.data.policy; const violationsByTime = {}; for (const entry of policyEntries) { const date = entry.timestamp?.split('T')[0]; if (!date) continue; if (!violationsByTime[date]) { violationsByTime[date] = { count: 0, critical: 0 }; } violationsByTime[date].count++; if (entry.severity === 'CRITICAL') { violationsByTime[date].critical++; } } const sorted = Object.entries(violationsByTime) .sort((a, b) => new Date(a[0]) - new Date(b[0])); // Beräkna trend const trends = []; for (let i = 1; i < sorted.length; i++) { const prev = sorted[i - 1][1]; const curr = sorted[i][1]; trends.push({ date: sorted[i][0], change: curr.count - prev.count, acceleration: (curr.count - prev.count) - (prev.count - (sorted[i - 2]?.[1]?.count || 0)) }); } return { daily: sorted.map(([date, data]) => ({ date, ...data })), trends: trends.slice(-7), // senaste 7 dagarna increasing: trends.filter(t => t.change > 0).length, decreasing: trends.filter(t => t.change < 0).length }; } /** * Beräkna trend för en komponent */ calculateTrend(component) { const shadowEntries = this.data.shadow; const byWeek = {}; for (const entry of shadowEntries) { const components = entry.components || []; if (!components.includes(component)) continue; const date = entry.timestamp?.split('T')[0]; if (!date) continue; const week = this.getWeekKey(new Date(date)); byWeek[week] = (byWeek[week] || 0) + 1; } const weeks = Object.entries(byWeek).sort(); if (weeks.length < 2) return 'stable'; const first = weeks[0][1]; const last = weeks[weeks.length - 1][1]; if (last > first * 1.5) return 'increasing'; if (last < first * 0.5) return 'decreasing'; return 'stable'; } getWeekKey(date) { const d = new Date(date); d.setHours(0, 0, 0, 0); d.setDate(d.getDate() - d.getDay() + 1); return d.toISOString().split('T')[0]; } // ── Sammanfattning ────────────────────────────────────────────────────── generateReport() { const report = { timestamp: new Date().toISOString(), regressions: this.analyzeRegressionFailures(), changeFrequency: this.analyzeChangeFrequency(), lowConfidence: this.analyzeLowConfidence(), unstableDependencies: this.analyzeUnstableDependencies(), technicalDebt: this.analyzeTechnicalDebt() }; this.printReport(report); this.saveReport(report); return report; } printReport(report) { console.log('╔═══════════════════════════════════════════════════════════════╗'); console.log('║ SIL HISTORICAL ANALYSIS ║'); console.log('║ Trender och långsiktiga mönster ║'); console.log('╚═══════════════════════════════════════════════════════════════╝'); console.log(); // Regressionsfel console.log('🔴 MODULER MED FLEST REGRESSIONSFEL\n'); for (const r of report.regressions.slice(0, 10)) { console.log(` ${r.component}: ${r.failures} fel (${r.percentage})`); } console.log(); // Förändringsfrekvens console.log('📈 MEST FÖRÄNDRADE KOMPONENTER\n'); for (const c of report.changeFrequency.slice(0, 10)) { const icon = c.trend === 'increasing' ? '📈' : c.trend === 'decreasing' ? '📉' : '➡️'; console.log(` ${icon} ${c.component}: ${c.changes} ändringar (${c.trend})`); } console.log(); // Låg confidence console.log('⚠️ LÄGST CONFIDENCE\n'); for (const c of report.lowConfidence.slice(0, 10)) { console.log(` ${c.component}: ${c.avgConfidence} (min: ${c.minConfidence}, ${c.samples} samples)`); } console.log(); // Instabila beroenden console.log('🌊 INSTABILA BEROENDEN\n'); for (const d of report.unstableDependencies.slice(0, 10)) { console.log(` ${d.component}: ${d.falsePredictions} felaktiga förutsägelser`); } console.log(); // Teknisk skuld if (report.technicalDebt) { console.log('💰 TEKNISK SKULD\n'); console.log(` Trend: ${report.technicalDebt.increasing > report.technicalDebt.decreasing ? 'ÖKANDE' : 'MINSKANDE'}`); console.log(` Dagar med ökning: ${report.technicalDebt.increasing}`); console.log(` Dagar med minskning: ${report.technicalDebt.decreasing}`); if (report.technicalDebt.trends.length > 0) { console.log('\n Senaste 7 dagarna:'); for (const t of report.technicalDebt.trends) { const icon = t.change > 0 ? '📈' : t.change < 0 ? '📉' : '➡️'; console.log(` ${icon} ${t.date}: ${t.change > 0 ? '+' : ''}${t.change}`); } } console.log(); } console.log('═══════════════════════════════════════════════════════════════\n'); } saveReport(report) { const filename = `/home/bernt/.openclaw/workspace/SIL/historical-report-${new Date().toISOString().split('T')[0]}.json`; writeFileSync(filename, JSON.stringify(report, null, 2)); console.log(`💾 Rapport sparad: ${filename}\n`); } } // ── Main ────────────────────────────────────────────────────────────────── const analysis = new HistoricalAnalysis(); const command = process.argv[2] || '--report'; if (command === '--report') { analysis.generateReport(); } else if (command === '--regressions') { console.log(JSON.stringify(analysis.analyzeRegressionFailures(), null, 2)); } else if (command === '--changes') { console.log(JSON.stringify(analysis.analyzeChangeFrequency(), null, 2)); } else if (command === '--confidence') { console.log(JSON.stringify(analysis.analyzeLowConfidence(), null, 2)); } else if (command === '--debt') { console.log(JSON.stringify(analysis.analyzeTechnicalDebt(), null, 2)); } else { console.log('Användning:'); console.log(' node historical-analysis.mjs --report # Full rapport'); console.log(' node historical-analysis.mjs --regressions # Regressionsfel'); console.log(' node historical-analysis.mjs --changes # Förändringsfrekvens'); console.log(' node historical-analysis.mjs --confidence # Låg confidence'); console.log(' node historical-analysis.mjs --debt # Teknisk skuld'); }