#!/usr/bin/env node // ═══════════════════════════════════════════════════════════════════════════ // EOS Engineering Readiness — Mognadsindex per område // Erik-krav: "EOS borde inte bara säga STOP. Det borde också kunna ge ett mognadsindex." // ═══════════════════════════════════════════════════════════════════════════ import { execSync } from 'child_process'; import { readFileSync, existsSync, writeFileSync } from 'fs'; const READINESS_PATH = '/home/bernt/.openclaw/workspace/EOS/readiness-report.json'; /** * Områden och deras mätningar */ const READINESS_AREAS = [ { id: 'git-discipline', name: 'Git-disciplin', weight: 1.0, measures: [ { name: 'Ocommitade filer', check: () => { try { const status = execSync('git status --short', { cwd: '/home/bernt/repos/quixzoom.com', encoding: 'utf8' }); const uncommitted = status.trim().split('\n').filter(l => l.trim()).length; // 0 filer = 100%, 100+ filer = 0% const score = Math.max(0, 100 - uncommitted); return { score, evidence: `${uncommitted} ocommitade filer` }; } catch { return { score: 0, evidence: 'Kunde inte köra git status' }; } } }, { name: 'Git-konfiguration', check: () => { const hasGit = existsSync('/home/bernt/repos/quixzoom.com/.git'); return { score: hasGit ? 100 : 0, evidence: hasGit ? 'Git-repo hittat' : 'Inget Git-repo' }; } } ] }, { id: 'deployment', name: 'Deployment', weight: 1.0, measures: [ { name: 'CI/CD pipeline', check: () => { const hasCI = existsSync('/home/bernt/repos/quixzoom.com/.github/workflows') || existsSync('/home/bernt/repos/quixzoom.com/.gitlab-ci.yml'); return { score: hasCI ? 100 : 0, evidence: hasCI ? 'CI/CD hittad' : 'Ingen CI/CD' }; } }, { name: 'Deployment-script', check: () => { const hasDeploy = existsSync('/home/bernt/repos/quixzoom.com/deploy.sh') || existsSync('/home/bernt/repos/quixzoom.com/scripts/deploy.sh'); return { score: hasDeploy ? 100 : 0, evidence: hasDeploy ? 'Deploy-script hittat' : 'Inget deploy-script' }; } } ] }, { id: 'infrastructure', name: 'Infrastruktur', weight: 1.0, measures: [ { name: 'Infrastructure as Code', check: () => { const hasIaC = existsSync('/home/bernt/repos/quixzoom.com/terraform') || existsSync('/home/bernt/repos/quixzoom.com/cloudformation') || existsSync('/home/bernt/repos/quixzoom.com/cdk'); return { score: hasIaC ? 100 : 0, evidence: hasIaC ? 'IaC hittad' : 'Ingen IaC' }; } }, { name: 'Miljökonfiguration', check: () => { const hasEnv = existsSync('/home/bernt/repos/quixzoom.com/.env.example'); return { score: hasEnv ? 100 : 0, evidence: hasEnv ? '.env.example hittad' : 'Ingen .env.example' }; } } ] }, { id: 'api-contract', name: 'API-kontrakt', weight: 0.8, measures: [ { name: 'OpenAPI-spec', check: () => { const hasOpenAPI = existsSync('/home/bernt/repos/quixzoom.com/openapi.yaml') || existsSync('/home/bernt/repos/quixzoom.com/openapi.json'); return { score: hasOpenAPI ? 100 : 0, evidence: hasOpenAPI ? 'OpenAPI hittad' : 'Ingen OpenAPI' }; } }, { name: 'API-dokumentation', check: () => { const hasDocs = existsSync('/home/bernt/repos/quixzoom.com/API.md') || existsSync('/home/bernt/repos/quixzoom.com/docs/api.md'); return { score: hasDocs ? 100 : 0, evidence: hasDocs ? 'API-docs hittad' : 'Ingen API-dokumentation' }; } } ] }, { id: 'testability', name: 'Testbarhet', weight: 1.0, measures: [ { name: 'Tester', check: () => { const hasTests = existsSync('/home/bernt/repos/quixzoom.com/tests') || existsSync('/home/bernt/repos/quixzoom.com/__tests__'); return { score: hasTests ? 100 : 0, evidence: hasTests ? 'Tester hittade' : 'Inga tester' }; } }, { name: 'Test-konfiguration', check: () => { const hasConfig = existsSync('/home/bernt/repos/quixzoom.com/jest.config.js') || existsSync('/home/bernt/repos/quixzoom.com/vitest.config.ts'); return { score: hasConfig ? 100 : 0, evidence: hasConfig ? 'Test-config hittad' : 'Ingen test-config' }; } } ] }, { id: 'observability', name: 'Observerbarhet', weight: 0.8, measures: [ { name: 'Loggning', check: () => { const hasLogging = existsSync('/home/bernt/repos/quixzoom.com/src/lib/logger.ts') || existsSync('/home/bernt/repos/quixzoom.com/src/utils/logger.js'); return { score: hasLogging ? 100 : 0, evidence: hasLogging ? 'Logger hittad' : 'Ingen logger' }; } }, { name: 'Health checks', check: () => { const hasHealth = existsSync('/home/bernt/repos/quixzoom.com/src/routes/health.ts') || existsSync('/home/bernt/repos/quixzoom.com/src/api/health.js'); return { score: hasHealth ? 100 : 0, evidence: hasHealth ? 'Health endpoint hittad' : 'Ingen health endpoint' }; } } ] }, { id: 'agent-safety', name: 'Agent Safety', weight: 1.0, measures: [ { name: 'EOS-kontrakt', check: () => { const hasContract = existsSync('/home/bernt/.openclaw/workspace/EOS/engineering-contract.mjs'); return { score: hasContract ? 100 : 0, evidence: hasContract ? 'EOS-kontrakt hittat' : 'Inget EOS-kontrakt' }; } }, { name: 'Immutable Truth', check: () => { const hasTruth = existsSync('/home/bernt/.openclaw/workspace/EOS/immutable-truth.mjs'); return { score: hasTruth ? 100 : 0, evidence: hasTruth ? 'Immutable Truth hittad' : 'Ingen Immutable Truth' }; } } ] } ]; class EngineeringReadiness { constructor() { this.areas = READINESS_AREAS; } /** * Beräkna mognadsindex för alla områden */ calculate() { console.log('📊 Beräknar Engineering Readiness...\n'); const results = []; let totalScore = 0; let totalWeight = 0; for (const area of this.areas) { const areaResult = this.calculateArea(area); results.push(areaResult); totalScore += areaResult.score * area.weight; totalWeight += area.weight; } const overallScore = Math.round(totalScore / totalWeight); const report = { timestamp: new Date().toISOString(), overall: overallScore, areas: results, // Thresholds thresholds: { critical: 80, // Under 80% = STOP warning: 60 // Under 60% = ESCALATE }, // Status status: overallScore >= 80 ? 'READY' : overallScore >= 60 ? 'WARNING' : 'CRITICAL' }; this.saveReport(report); this.printReport(report); return report; } calculateArea(area) { let areaScore = 0; const measures = []; for (const measure of area.measures) { const result = measure.check(); measures.push({ name: measure.name, score: result.score, evidence: result.evidence }); areaScore += result.score; } const avgScore = Math.round(areaScore / area.measures.length); return { id: area.id, name: area.name, weight: area.weight, score: avgScore, measures, status: avgScore >= 80 ? 'READY' : avgScore >= 60 ? 'WARNING' : 'CRITICAL' }; } saveReport(report) { writeFileSync(READINESS_PATH, JSON.stringify(report, null, 2)); } printReport(report) { console.log('╔═══════════════════════════════════════════════════════════════╗'); console.log('║ ENGINEERING READINESS ║'); console.log('║ Mognadsindex per område ║'); console.log('╚═══════════════════════════════════════════════════════════════╝'); console.log(); // Overall const overallIcon = report.status === 'READY' ? '✅' : report.status === 'WARNING' ? '⚠️' : '🔴'; console.log(`${overallIcon} ÖVERGRIAPANDE: ${report.overall}%`); console.log(` Status: ${report.status}`); console.log(); // Per område console.log('📋 PER OMRÅDE\n'); for (const area of report.areas) { const icon = area.status === 'READY' ? '✅' : area.status === 'WARNING' ? '⚠️' : '🔴'; const bar = '█'.repeat(area.score / 5) + '░'.repeat(20 - area.score / 5); console.log(`${icon} ${area.name}`); console.log(` [${bar}] ${area.score}%`); console.log(` Vikt: ${area.weight}x`); for (const measure of area.measures) { const mIcon = measure.score >= 80 ? '✅' : measure.score >= 60 ? '⚠️' : '❌'; console.log(` ${mIcon} ${measure.name}: ${measure.score}% (${measure.evidence})`); } console.log(); } // Thresholds console.log('🎯 TRÖSKLAR\n'); console.log(` Critical: <${report.thresholds.critical}% (STOP)`); console.log(` Warning: <${report.thresholds.warning}% (ESCALATE)`); console.log(` Ready: ≥${report.thresholds.critical}%`); console.log(); // Rekommendation console.log('💡 REKOMMENDATION\n'); if (report.status === 'CRITICAL') { console.log(' 🔴 STOP — Agenter får INTE utföra ändringar'); console.log(' Åtgärda critical-områden innan fortsatt arbete'); } else if (report.status === 'WARNING') { console.log(' ⚠️ WARNING — Begränsad agent-aktivitet'); console.log(' Agenter får endast göra säkra ändringar'); } else { console.log(' ✅ READY — Agenter kan arbeta normalt'); } console.log(); console.log('═══════════════════════════════════════════════════════════════\n'); } } // ── Main ────────────────────────────────────────────────────────────────── const readiness = new EngineeringReadiness(); readiness.calculate();