#!/usr/bin/env node // ═══════════════════════════════════════════════════════════════════════════ // EOS Dependency Budget — Precis som prestandabudgetar // Erik-krav: "Om någon PR ökar komplexiteten bryter EOS budgeten" // ═══════════════════════════════════════════════════════════════════════════ import { readFileSync, existsSync } from 'fs'; import { execSync } from 'child_process'; const REGISTRY_PATH = '/home/bernt/.openclaw/workspace/EOS/capability-registry.json'; class DependencyBudget { constructor() { this.capabilities = this.loadCapabilities(); } loadCapabilities() { if (existsSync(REGISTRY_PATH)) { return JSON.parse(readFileSync(REGISTRY_PATH, 'utf8')); } return []; } /** * Analysera en PR mot dependency-budget */ analyzePR(pr) { console.log(`🔍 Analyserar PR: ${pr.title}\n`); const results = []; let totalViolations = 0; for (const file of pr.files) { const cap = this.findCapabilityForFile(file); if (!cap) continue; const analysis = this.analyzeFile(file, cap); results.push(analysis); if (!analysis.withinBudget) { totalViolations++; } } const report = { pr: pr.title, totalFiles: pr.files.length, analyzedFiles: results.length, violations: totalViolations, results }; this.printReport(report); return report; } findCapabilityForFile(file) { // Matcha fil mot capability baserat på sökväg for (const cap of this.capabilities) { for (const service of cap.services) { if (file.includes(service.toLowerCase().replace(' ', '-'))) { return cap; } } } return null; } analyzeFile(file, cap) { // Simulera analys av fil const currentDeps = cap.dependencies.length; const newDeps = this.countNewDependencies(file); const currentApis = cap.apis.filter(a => a.includes('stripe') || a.includes('external')).length; const newApis = this.countNewExternalApis(file); const totalDeps = currentDeps + newDeps; const totalApis = currentApis + newApis; const withinBudget = totalDeps <= cap.budget.maxDirectDeps && totalApis <= cap.budget.maxExternalApis; return { file, capability: cap.name, currentDeps, newDeps, totalDeps, maxDeps: cap.budget.maxDirectDeps, currentApis, newApis, totalApis, maxApis: cap.budget.maxExternalApis, withinBudget, issues: [] .concat(totalDeps > cap.budget.maxDirectDeps ? [`För många beroenden: ${totalDeps}/${cap.budget.maxDirectDeps}`] : []) .concat(totalApis > cap.budget.maxExternalApis ? [`För många externa API:er: ${totalApis}/${cap.budget.maxExternalApis}`] : []) }; } countNewDependencies(file) { // Simulering — i verkligheten skulle detta analysera import-satser return 0; } countNewExternalApis(file) { // Simulering — i verkligheten skulle detta analysera API-anrop return 0; } printReport(report) { console.log('╔═══════════════════════════════════════════════════════════════╗'); console.log('║ DEPENDENCY BUDGET ║'); console.log('║ PR-analys ║'); console.log('╚═══════════════════════════════════════════════════════════════╝'); console.log(); console.log(`📊 PR: ${report.pr}\n`); console.log(` Filer: ${report.totalFiles}`); console.log(` Analyserade: ${report.analyzedFiles}`); console.log(` Överträdelser: ${report.violations}\n`); for (const result of report.results) { const icon = result.withinBudget ? '✅' : '🔴'; console.log(`${icon} ${result.file}`); console.log(` Capability: ${result.capability}`); console.log(` Beroenden: ${result.totalDeps}/${result.maxDeps}`); console.log(` Externa API:er: ${result.totalApis}/${result.maxApis}`); if (!result.withinBudget) { console.log(' 🔴 Överträdelser:'); for (const issue of result.issues) { console.log(` • ${issue}`); } } console.log(); } if (report.violations > 0) { console.log('🔴 BUDGET ÖVERSKRIDEN\n'); console.log(' PR:en kan inte mergeas utan att budgeten justeras eller'); console.log(' komplexiteten minskas.\n'); } else { console.log('✅ BUDGET RESPEKTERAD\n'); console.log(' PR:en är inom budget.\n'); } console.log('═══════════════════════════════════════════════════════════════\n'); } /** * Visa budget-status för alla capabilities */ showBudgetStatus() { console.log('💰 DEPENDENCY BUDGET — Status\n'); for (const cap of this.capabilities) { const depCount = cap.dependencies.length; const apiCount = cap.apis.filter(a => a.includes('stripe') || a.includes('external')).length; const depStatus = depCount > cap.budget.maxDirectDeps ? '🔴' : depCount > cap.budget.maxDirectDeps * 0.8 ? '⚠️' : '✅'; const apiStatus = apiCount > cap.budget.maxExternalApis ? '🔴' : apiCount > cap.budget.maxExternalApis * 0.8 ? '⚠️' : '✅'; console.log(`${depStatus}${apiStatus} ${cap.name}`); console.log(` Beroenden: ${depCount}/${cap.budget.maxDirectDeps}`); console.log(` Externa API:er: ${apiCount}/${cap.budget.maxExternalApis}`); console.log(); } } } // ── Main ────────────────────────────────────────────────────────────────── const budget = new DependencyBudget(); const command = process.argv[2] || '--status'; if (command === '--status') { budget.showBudgetStatus(); } else if (command === '--analyze') { const pr = { title: process.argv[3] || 'Lägg till Stripe-integration', files: process.argv.slice(4).length > 0 ? process.argv.slice(4) : [ 'src/services/payment/stripe-adapter.ts', 'src/services/mission/mission-service.ts' ] }; budget.analyzePR(pr); } else { console.log('Användning:'); console.log(' node dependency-budget.mjs --status # Visa budget-status'); console.log(' node dependency-budget.mjs --analyze [files...] # Analysera PR'); }