05ed037fe8
- DNS: pilot.landvex.com -> 16.170.83.169 - TLS: Let's Encrypt certificate (expires 2026-09-30) - Nginx: reverse proxy with SSL termination - API: https://pilot.landvex.com/api/v1/missions - UI: https://pilot.landvex.com/ - Upload: POST /api/v1/missions/import (multipart/form-data) Verified: ✅ https://pilot.landvex.com/health ✅ https://pilot.landvex.com/version ✅ https://pilot.landvex.com/api/v1/missions (list) ✅ https://pilot.landvex.com/api/v1/missions/:id (get) ✅ POST /api/v1/missions/import (video upload) ✅ UI loads with title 'LandveX Intelligence Lab' Next: Pilot 001 — Break the system!
164 lines
5.4 KiB
JavaScript
164 lines
5.4 KiB
JavaScript
#!/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);
|