#!/usr/bin/env node // ═══════════════════════════════════════════════════════════════════════════ // SIL Failure Review — Klassificera varje fel // Erik-krav: "Varje gång SIL har fel ska ni inte bara logga det. Ni ska klassificera felet." // ═══════════════════════════════════════════════════════════════════════════ import { readFileSync, writeFileSync, existsSync, appendFileSync } from 'fs'; const FAILURE_LOG = '/home/bernt/.openclaw/workspace/SIL/validation/failure-log.jsonl'; /** * Feltyper som SIL kan göra */ const FAILURE_TYPES = { KNOWLEDGE_ERROR: { description: 'Grafen saknade en relation', example: 'SIL visste inte att Wallet påverkar Payout', action: 'Uppdatera kunskapsgrafen' }, PARSING_ERROR: { description: 'AST missade en import', example: 'SIL såg inte att filen importerade Stripe', action: 'Förbättra AST-parser' }, RUNTIME_DRIFT: { description: 'Driftmiljön skiljde sig från modellen', example: 'Produktion hade en feature flag som grafen inte kände till', action: 'Uppdatera observer' }, POLICY_ERROR: { description: 'Arkitekturregel felaktigt formulerad', example: 'Regeln var för strikt och flaggade giltig kod', action: 'Justera arkitekturpolicy' }, LLM_ERROR: { description: 'Felaktig inferens', example: 'SIL drog fel slutsats från korrekt data', action: 'Förbättra resonemangsmodell' }, CONFIDENCE_ERROR: { description: 'För hög confidence trots svag evidens', example: 'SIL sa 95% confidence men hade fel', action: 'Kalibrera om confidence-modell' }, OBSERVATION_ERROR: { description: 'Felaktig eller ofullständig observation', example: 'Git diff missade en fil', action: 'Förbättra observer' }, REASONING_ERROR: { description: 'Felaktigt resonemang från korrekt data', example: 'Korrekt data men fel slutsats', action: 'Granska resonemangsregler' } }; class FailureReview { constructor() { this.failures = this.loadFailures(); } loadFailures() { if (!existsSync(FAILURE_LOG)) return []; return readFileSync(FAILURE_LOG, 'utf8') .split('\n') .filter(line => line.trim()) .map(line => { try { return JSON.parse(line); } catch { return null; } }) .filter(Boolean); } /** * Logga ett nytt fel med klassificering */ logFailure(failure) { const entry = { timestamp: new Date().toISOString(), analysisId: failure.analysisId, pr: failure.pr, // Vad SIL sa silPrediction: failure.silPrediction, silConfidence: failure.silConfidence, // Vad som var sant actualOutcome: failure.actualOutcome, // Klassificering failureType: failure.failureType, failureTypeDescription: FAILURE_TYPES[failure.failureType]?.description, // Kontext component: failure.component, evidence: failure.evidence, // Root cause rootCause: failure.rootCause, // Åtgärd action: failure.action || FAILURE_TYPES[failure.failureType]?.action, // Allvarlighet severity: failure.severity || 'MEDIUM', // Lärande lesson: failure.lesson }; appendFileSync(FAILURE_LOG, JSON.stringify(entry) + '\n', 'utf8'); console.log(`✅ Fel loggat: ${failure.failureType}`); console.log(` ${FAILURE_TYPES[failure.failureType]?.description}`); console.log(` Åtgärd: ${entry.action}\n`); return entry; } /** * Analysera felmönster */ analyzePatterns() { const byType = {}; const byComponent = {}; const bySeverity = {}; for (const failure of this.failures) { // Per typ byType[failure.failureType] = (byType[failure.failureType] || 0) + 1; // Per komponent if (failure.component) { byComponent[failure.component] = (byComponent[failure.component] || 0) + 1; } // Per allvarlighet bySeverity[failure.severity] = (bySeverity[failure.severity] || 0) + 1; } return { total: this.failures.length, byType: Object.entries(byType) .sort((a, b) => b[1] - a[1]) .map(([type, count]) => ({ type, count, percentage: Math.round(count / this.failures.length * 100) + '%', description: FAILURE_TYPES[type]?.description, action: FAILURE_TYPES[type]?.action })), byComponent: Object.entries(byComponent) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .map(([component, count]) => ({ component, count })), bySeverity }; } /** * Generera rapport */ generateReport() { const patterns = this.analyzePatterns(); const report = { timestamp: new Date().toISOString(), totalFailures: patterns.total, patterns, // Dominant feltyp dominantFailure: patterns.byType[0], // Rekommendationer recommendations: this.generateRecommendations(patterns) }; this.printReport(report); return report; } generateRecommendations(patterns) { const recs = []; // Om en feltyp dominerar → prioritera den if (patterns.byType.length > 0) { const dominant = patterns.byType[0]; if (parseFloat(dominant.percentage) > 30) { recs.push({ priority: 'HIGH', issue: `${dominant.type} dominerar (${dominant.percentage})`, action: dominant.action }); } } // Om vissa komponenter har många fel → granska dem for (const comp of patterns.byComponent.slice(0, 3)) { if (comp.count > 3) { recs.push({ priority: 'MEDIUM', issue: `Komponent ${comp.component} har ${comp.count} fel`, action: `Granska och uppdatera graf för ${comp.component}` }); } } return recs; } printReport(report) { console.log('╔═══════════════════════════════════════════════════════════════╗'); console.log('║ FAILURE REVIEW ║'); console.log('║ Klassificering av SIL:s fel ║'); console.log('╚═══════════════════════════════════════════════════════════════╝'); console.log(); console.log(`📊 TOTALT: ${report.totalFailures} fel\n`); console.log('📋 PER TYP\n'); for (const type of report.patterns.byType) { console.log(` ${type.count}x ${type.type}`); console.log(` ${type.description}`); console.log(` ${type.percentage} av alla fel`); console.log(` Åtgärd: ${type.action}`); console.log(); } console.log('📋 PER KOMPONENT (top 10)\n'); for (const comp of report.patterns.byComponent) { console.log(` ${comp.component}: ${comp.count} fel`); } console.log(); console.log('📋 PER ALLVARLIGHET\n'); for (const [sev, count] of Object.entries(report.patterns.bySeverity)) { console.log(` ${sev}: ${count}`); } console.log(); if (report.recommendations.length > 0) { console.log('💡 REKOMMENDATIONER\n'); for (const rec of report.recommendations) { const icon = rec.priority === 'HIGH' ? '🔴' : '🟡'; console.log(` ${icon} [${rec.priority}] ${rec.issue}`); console.log(` → ${rec.action}`); } console.log(); } console.log('═══════════════════════════════════════════════════════════════\n'); } /** * Lista alla feltyper */ listTypes() { console.log('📋 FELTYPER\n'); for (const [type, info] of Object.entries(FAILURE_TYPES)) { console.log(` ${type}:`); console.log(` ${info.description}`); console.log(` Exempel: ${info.example}`); console.log(` Åtgärd: ${info.action}`); console.log(); } } } // ── Main ────────────────────────────────────────────────────────────────── const review = new FailureReview(); const command = process.argv[2] || '--report'; if (command === '--report') { review.generateReport(); } else if (command === '--log') { // Exempel på att logga ett fel const failure = { analysisId: process.argv[3] || 'unknown', pr: process.argv[4] || 'PR-123', silPrediction: process.argv[5] || 'Wallet påverkar inte Payout', silConfidence: 0.85, actualOutcome: 'Wallet påverkar Payout', failureType: process.argv[6] || 'KNOWLEDGE_ERROR', component: process.argv[7] || 'wallet', evidence: 'Git diff visade ändring i payout.ts', rootCause: 'Grafen saknade kant Wallet → Payout', severity: 'HIGH', lesson: 'Måste uppdatera graf när nya beroenden läggs till' }; review.logFailure(failure); } else if (command === '--types') { review.listTypes(); } else { console.log('Användning:'); console.log(' node failure-review.mjs --report # Generera rapport'); console.log(' node failure-review.mjs --log # Logga fel'); console.log(' node failure-review.mjs --types # Lista feltyper'); }