#!/usr/bin/env node // ═══════════════════════════════════════════════════════════════════════════ // SIL Replay Mode — Spela upp historisk analys exakt som den såg ut då // Erik-krav: "Samma grafversion, samma regler, samma observer-version, samma reasoner-version" // ═══════════════════════════════════════════════════════════════════════════ import { readFileSync, existsSync, writeFileSync } from 'fs'; const SHADOW_LOG = '/home/bernt/.openclaw/workspace/SIL/shadow-log.jsonl'; const VERSION_DIR = '/home/bernt/.openclaw/workspace/SIL/graph-versions'; const PROVENANCE_LOG = '/home/bernt/.openclaw/workspace/SIL/provenance-log.jsonl'; class ReplayMode { constructor() { this.analyses = this.loadAnalyses(); } loadAnalyses() { if (!existsSync(SHADOW_LOG)) return []; return readFileSync(SHADOW_LOG, 'utf8') .split('\n') .filter(line => line.trim()) .map(line => { try { return JSON.parse(line); } catch { return null; } }) .filter(Boolean); } /** * Hitta en analys att spela upp */ findAnalysis(analysisId) { return this.analyses.find(a => a.analysisId === analysisId); } /** * Spela upp en historisk analys */ replay(analysisId) { const analysis = this.findAnalysis(analysisId); if (!analysis) { console.log(`❌ Analys ${analysisId} hittades inte\n`); return null; } console.log(`🔄 REPLAY: ${analysisId}\n`); // 1. Återskapa grafversion const graphVersion = analysis.provenance?.traceId; const graph = this.reconstructGraph(graphVersion); // 2. Återskapa kontext const context = this.reconstructContext(analysis); // 3. Återskapa beslutsunderlag const decision = this.reconstructDecision(analysis); // 4. Visa exakt hur analysen såg ut this.printReplay({ analysisId, timestamp: analysis.timestamp, graphVersion, context, decision, provenance: analysis.provenance }); return { analysisId, timestamp: analysis.timestamp, graphVersion, context, decision, provenance: analysis.provenance }; } /** * Återskapa graf från version */ reconstructGraph(versionId) { if (!versionId) { return { error: 'Ingen grafversion specificerad' }; } const path = `${VERSION_DIR}/graph-${versionId}.json`; if (!existsSync(path)) { return { error: `Grafversion ${versionId} hittades inte` }; } try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return { error: 'Kunde inte läsa grafversion' }; } } /** * Återskapa kontext */ reconstructContext(analysis) { return { repo: analysis.repo || 'unknown', baseBranch: analysis.baseBranch || 'main', headBranch: analysis.headBranch || 'HEAD', commit: analysis.commit || 'unknown', changedFiles: analysis.changedFiles || 0, // Versioner versions: { graph: analysis.provenance?.traceId, observer: analysis.observerVersion || 'unknown', reasoner: analysis.reasonerVersion || 'unknown', policy: analysis.policyVersion || 'unknown' } }; } /** * Återskapa beslutsunderlag */ reconstructDecision(analysis) { return { riskScore: analysis.decision?.summary?.riskScore, blastRadius: analysis.decision?.summary?.blastRadius, confidence: analysis.decision?.summary?.confidence, recommendation: analysis.decision?.summary?.recommendation, // Osäkerhet uncertainty: analysis.uncertaintySummary, // Tester requiredTests: analysis.requiredTests?.length || 0, // Risker risks: analysis.risks?.length || 0 }; } /** * Visa replay */ printReplay(replay) { console.log('╔═══════════════════════════════════════════════════════════════╗'); console.log('║ SIL REPLAY MODE ║'); console.log('║ Exakt återskapning av historisk analys ║'); console.log('╚═══════════════════════════════════════════════════════════════╝'); console.log(); console.log('📋 ANALYS\n'); console.log(` ID: ${replay.analysisId}`); console.log(` Tidpunkt: ${replay.timestamp}`); console.log(` Grafversion: ${replay.graphVersion || 'unknown'}`); console.log(); console.log('🌐 KONTEXT\n'); console.log(` Repo: ${replay.context.repo}`); console.log(` Branch: ${replay.context.headBranch} → ${replay.context.baseBranch}`); console.log(` Commit: ${replay.context.commit}`); console.log(` Ändrade filer: ${replay.context.changedFiles}`); console.log(); console.log('⚙️ VERSIONER\n'); console.log(` Graf: ${replay.context.versions.graph || 'unknown'}`); console.log(` Observer: ${replay.context.versions.observer}`); console.log(` Reasoner: ${replay.context.versions.reasoner}`); console.log(` Policy: ${replay.context.versions.policy}`); console.log(); if (replay.decision) { console.log('📊 BESLUTSUNDERLAG\n'); console.log(` Risk Score: ${replay.decision.riskScore || 'N/A'}`); console.log(` Blast Radius: ${replay.decision.blastRadius || 'N/A'}`); console.log(` Confidence: ${replay.decision.confidence || 'N/A'}`); console.log(` Rekommendation: ${replay.decision.recommendation || 'N/A'}`); console.log(` Tester: ${replay.decision.requiredTests}`); console.log(` Risker: ${replay.decision.risks}`); console.log(); } if (replay.provenance) { console.log('📋 PROVENANCE\n'); console.log(` Trace ID: ${replay.provenance.traceId}`); console.log(` Steg: ${replay.provenance.steps}`); console.log(` Slutsatser: ${replay.provenance.conclusions}`); console.log(); } console.log('═══════════════════════════════════════════════════════════════\n'); } /** * Lista alla tillgängliga analyser att spela upp */ listAvailableReplays() { console.log('📋 TILLGÄNGLIGA REPLAYS\n'); for (const analysis of this.analyses.slice(-20)) { const id = analysis.analysisId || analysis.timestamp; const date = analysis.timestamp?.split('T')[0] || 'unknown'; const components = analysis.summary?.changedComponents || 0; console.log(` ${id}`); console.log(` Datum: ${date}`); console.log(` Komponenter: ${components}`); console.log(` Grafversion: ${analysis.provenance?.traceId || 'unknown'}`); console.log(); } } /** * Jämför två replay:er */ compareReplays(analysisId1, analysisId2) { const replay1 = this.replay(analysisId1); const replay2 = this.replay(analysisId2); if (!replay1 || !replay2) return; console.log('╔═══════════════════════════════════════════════════════════════╗'); console.log('║ REPLAY COMPARISON ║'); console.log('╚═══════════════════════════════════════════════════════════════╝'); console.log(); console.log(`📊 ${analysisId1} vs ${analysisId2}\n`); // Jämför beslutsunderlag if (replay1.decision && replay2.decision) { console.log('BESLUTSUNDERLAG\n'); console.log(` Risk Score: ${replay1.decision.riskScore || 'N/A'} → ${replay2.decision.riskScore || 'N/A'}`); console.log(` Confidence: ${replay1.decision.confidence || 'N/A'} → ${replay2.decision.confidence || 'N/A'}`); console.log(` Rekommendation: ${replay1.decision.recommendation || 'N/A'} → ${replay2.decision.recommendation || 'N/A'}`); console.log(); } // Jämför versioner console.log('VERSIONER\n'); console.log(` Graf: ${replay1.context.versions.graph || 'N/A'} → ${replay2.context.versions.graph || 'N/A'}`); console.log(` Observer: ${replay1.context.versions.observer || 'N/A'} → ${replay2.context.versions.observer || 'N/A'}`); console.log(` Reasoner: ${replay1.context.versions.reasoner || 'N/A'} → ${replay2.context.versions.reasoner || 'N/A'}`); console.log(); console.log('═══════════════════════════════════════════════════════════════\n'); } } // ── Main ────────────────────────────────────────────────────────────────── const replay = new ReplayMode(); const command = process.argv[2] || '--list'; if (command === '--list') { replay.listAvailableReplays(); } else if (command === '--replay') { const analysisId = process.argv[3]; if (!analysisId) { console.log('Användning: node replay-mode.mjs --replay '); process.exit(1); } replay.replay(analysisId); } else if (command === '--compare') { const id1 = process.argv[3]; const id2 = process.argv[4]; if (!id1 || !id2) { console.log('Användning: node replay-mode.mjs --compare '); process.exit(1); } replay.compareReplays(id1, id2); } else { console.log('Användning:'); console.log(' node replay-mode.mjs --list # Lista tillgängliga replays'); console.log(' node replay-mode.mjs --replay # Spela upp analys'); console.log(' node replay-mode.mjs --compare # Jämför två analyser'); }