#!/usr/bin/env node // ═══════════════════════════════════════════════════════════════════════════ // EOS Readiness Gate — Ingen PR får mergeas om den sänker Engineering Readiness // Erik-krav: "Readiness före: Git 82, Efter: Git 76 → Merge blockeras" // ═══════════════════════════════════════════════════════════════════════════ import { readFileSync, existsSync } from 'fs'; const READINESS_PATH = '/home/bernt/.openclaw/workspace/EOS/readiness-report-v3.json'; const HISTORY_PATH = '/home/bernt/.openclaw/workspace/EOS/readiness-history.json'; class ReadinessGate { constructor() { this.currentReadiness = this.loadCurrentReadiness(); this.history = this.loadHistory(); } loadCurrentReadiness() { if (existsSync(READINESS_PATH)) { return JSON.parse(readFileSync(READINESS_PATH, 'utf8')); } return null; } loadHistory() { if (existsSync(HISTORY_PATH)) { return JSON.parse(readFileSync(HISTORY_PATH, 'utf8')); } return []; } /** * Kontrollera om en PR skulle sänka Readiness */ checkPR(pr) { console.log(`🔍 Readiness Gate: Kontrollerar PR "${pr.title}"\n`); if (!this.currentReadiness) { console.log('❌ Ingen Readiness-rapport hittad'); return { canMerge: false, reason: 'Ingen Readiness-rapport' }; } // Simulera hur PR påverkar Readiness const impact = this.simulateImpact(pr); console.log('📊 PÅVERKAN PÅ READINESS\n'); let blocked = false; const changes = []; for (const area of this.currentReadiness.areas) { const areaImpact = impact.areas.find(a => a.id === area.id); if (!areaImpact) continue; const before = area.score; const after = areaImpact.score; const delta = after - before; changes.push({ area: area.name, before, after, delta }); const icon = delta > 0 ? '📈' : delta < 0 ? '📉' : '➡️'; console.log(` ${icon} ${area.name}: ${before}% → ${after}% (${delta > 0 ? '+' : ''}${delta}%)`); // Blockera om Readiness sjunker under tröskel if (after < area.target && before >= area.target) { console.log(` 🔴 Sjunker under mål (${area.target}%)`); blocked = true; } // Blockera om Readiness sjunker överhuvudtaget för kritiska områden if (delta < 0 && ['git', 'cicd', 'backup', 'infrastructure'].includes(area.id)) { console.log(` 🔴 Kritiskt område försämras`); blocked = true; } } console.log(); const result = { canMerge: !blocked, changes, overallBefore: this.currentReadiness.overall, overallAfter: impact.overall, overallDelta: impact.overall - this.currentReadiness.overall }; if (blocked) { console.log('🔴 MERGE BLOCKERAD\n'); console.log(' PR:en sänker Engineering Readiness.'); console.log(' Åtgärda försämringarna innan merge.\n'); } else if (result.overallDelta < 0) { console.log('⚠️ MERGE VARNING\n'); console.log(' PR:en sänker Readiness men inte under tröskel.'); console.log(' Överväg att åtgärda ändå.\n'); } else { console.log('✅ MERGE GODKÄND\n'); console.log(' PR:en förbättrar eller behåller Readiness.\n'); } return result; } simulateImpact(pr) { // Simulera påverkan baserat på PR:ens filer const areas = this.currentReadiness.areas.map(area => ({ ...area, score: area.score })); let overallImpact = 0; for (const file of pr.files || []) { // Exempel: Om PR lägger till tester → öka Testing if (file.includes('test') || file.includes('spec')) { const testingArea = areas.find(a => a.id === 'testing'); if (testingArea) testingArea.score = Math.min(100, testingArea.score + 5); } // Om PR lägger till CI/CD → öka CI/CD if (file.includes('.github') || file.includes('.gitlab')) { const cicdArea = areas.find(a => a.id === 'cicd'); if (cicdArea) cicdArea.score = Math.min(100, cicdArea.score + 10); } // Om PR ändrar utan .gitignore → sänk Git if (file.includes('node_modules') || file.includes('dist')) { const gitArea = areas.find(a => a.id === 'git'); if (gitArea) gitArea.score = Math.max(0, gitArea.score - 10); } } // Beräkna ny overall let totalScore = 0; let totalWeight = 0; for (const area of areas) { totalScore += area.score * area.weight; totalWeight += area.weight; } return { overall: Math.round(totalScore / totalWeight), areas }; } } // ── Main ────────────────────────────────────────────────────────────────── const gate = new ReadinessGate(); const pr = { title: process.argv[2] || 'Lägg till tester för Mission Service', files: process.argv.slice(3).length > 0 ? process.argv.slice(3) : [ 'src/services/mission.test.ts', '.github/workflows/test.yml' ] }; gate.checkPR(pr);