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!
320 lines
11 KiB
JavaScript
320 lines
11 KiB
JavaScript
#!/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();
|