#!/usr/bin/env node // ═══════════════════════════════════════════════════════════════════════════ // System Graph Reasoning Engine // Traverserar kunskapsgrafen för att besvara frågor om systemet // ═══════════════════════════════════════════════════════════════════════════ import { readFileSync } from 'fs'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; const __dirname = dirname(fileURLToPath(import.meta.url)); const graphPath = join(__dirname, 'SYSTEM_GRAPH.json'); let graph; try { graph = JSON.parse(readFileSync(graphPath, 'utf8')); } catch (e) { console.error('❌ Kunde inte ladda SYSTEM_GRAPH.json:', e.message); process.exit(1); } // ── Hjälpfunktioner ─────────────────────────────────────────────────────── function getNode(id) { return graph.nodes.find(n => n.id === id); } function getEdgesFrom(nodeId) { return graph.edges.filter(e => e.from === nodeId); } function getEdgesTo(nodeId) { return graph.edges.filter(e => e.to === nodeId); } function getRelatedNodes(nodeId, direction = 'both') { const related = new Set(); if (direction === 'from' || direction === 'both') { getEdgesFrom(nodeId).forEach(e => related.add(e.to)); } if (direction === 'to' || direction === 'both') { getEdgesTo(nodeId).forEach(e => related.add(e.from)); } return Array.from(related).map(id => getNode(id)).filter(Boolean); } // ── Traversering ────────────────────────────────────────────────────────── function traverse(nodeId, visited = new Set(), depth = 0, maxDepth = 10) { if (depth > maxDepth || visited.has(nodeId)) return []; visited.add(nodeId); const node = getNode(nodeId); if (!node) return []; const edges = getEdgesFrom(nodeId); const results = [{ node, depth, edges }]; for (const edge of edges) { results.push(...traverse(edge.to, visited, depth + 1, maxDepth)); } return results; } function reverseTraverse(nodeId, visited = new Set(), depth = 0, maxDepth = 10) { if (depth > maxDepth || visited.has(nodeId)) return []; visited.add(nodeId); const node = getNode(nodeId); if (!node) return []; const edges = getEdgesTo(nodeId); const results = [{ node, depth, edges }]; for (const edge of edges) { results.push(...reverseTraverse(edge.from, visited, depth + 1, maxDepth)); } return results; } // ── Query-handlers ──────────────────────────────────────────────────────── function impactAnalysis(componentId) { console.log(`\n🔍 IMPACT ANALYSIS: ${componentId}\n`); const node = getNode(componentId); if (!node) { console.log(`❌ Komponent "${componentId}" hittades inte i grafen`); return; } console.log(`📍 Komponent: ${node.label} (${node.type})`); if (node.critical) console.log(`⚠️ KRITISK KOMPONENT`); console.log(''); const impacted = traverse(componentId); const byDepth = {}; impacted.forEach(({ node, depth }) => { if (depth === 0) return; // Skip self if (!byDepth[depth]) byDepth[depth] = []; byDepth[depth].push(node); }); console.log('📊 Påverkade komponenter:\n'); for (const [depth, nodes] of Object.entries(byDepth)) { console.log(` Nivå ${depth} (direkt${depth > 1 ? ' + indirekt' : ''}):`); nodes.forEach(n => { const icon = n.critical ? '🔴' : n.type === 'actor' ? '👤' : n.type === 'service' ? '⚙️' : n.type === 'database' ? '🗄️' : '📦'; console.log(` ${icon} ${n.label} (${n.type})${n.critical ? ' [KRITISK]' : ''}`); }); console.log(''); } // Affärsprocesser som påverkas const businessProcesses = impacted.filter(({ node }) => node.type === 'business_process'); if (businessProcesses.length > 0) { console.log('🏭 Affärsprocesser som påverkas:'); businessProcesses.forEach(({ node }) => { console.log(` • ${node.label}`); }); console.log(''); } // Tester som behövs const services = impacted.filter(({ node }) => node.type === 'service'); if (services.length > 0) { console.log('🧪 Tester som måste köras:'); services.forEach(({ node }) => { if (node.endpoints) { console.log(` • ${node.label}:`); node.endpoints.forEach(ep => console.log(` - ${ep}`)); } }); console.log(''); } } function failureAnalysis(componentId) { console.log(`\n💥 FAILURE ANALYSIS: ${componentId}\n`); const node = getNode(componentId); if (!node) { console.log(`❌ Komponent "${componentId}" hittades inte`); return; } console.log(`📍 Komponent som fallerar: ${node.label}\n`); // Hitta alla noder som ENDAST kan nås via denna komponent const allNodes = graph.nodes.filter(n => n.id !== componentId); const cutOff = []; for (const target of allNodes) { const paths = findAllPaths(target.id, componentId); if (paths.length === 0) continue; // Kolla om det finns vägar som INTE går via componentId const alternativePaths = paths.filter(p => !p.includes(componentId)); if (alternativePaths.length === 0) { cutOff.push(target); } } if (cutOff.length === 0) { console.log('✅ Ingen komponent är helt beroende av denna. Systemet har redundans.'); return; } console.log('❌ Komponenter som BLIR OTILLGÄNGLIGA:\n'); const byType = {}; cutOff.forEach(n => { if (!byType[n.type]) byType[n.type] = []; byType[n.type].push(n); }); for (const [type, nodes] of Object.entries(byType)) { const icon = type === 'actor' ? '👤' : type === 'service' ? '⚙️' : type === 'business_process' ? '🏭' : type === 'database' ? '🗄️' : '📦'; console.log(` ${icon} ${type.toUpperCase()}:`); nodes.forEach(n => { console.log(` • ${n.label}${n.critical ? ' [KRITISK]' : ''}`); }); console.log(''); } // Affärspåverkan const businessImpact = cutOff.filter(n => n.type === 'business_process'); if (businessImpact.length > 0) { console.log('💰 AFFÄRSPÅVERKAN:\n'); businessImpact.forEach(n => { console.log(` 🏭 ${n.label}`); console.log(` Steg: ${n.steps?.join(' → ') || 'okänt'}`); }); console.log(''); } } function findAllPaths(targetId, excludeId = null, currentPath = [], visited = new Set()) { if (currentPath.length > 10) return []; const paths = []; const edges = getEdgesTo(targetId); for (const edge of edges) { if (edge.from === excludeId) continue; if (visited.has(edge.from)) continue; const newPath = [edge.from, ...currentPath]; if (newPath.length > 1) { paths.push(newPath); } visited.add(edge.from); paths.push(...findAllPaths(edge.from, excludeId, newPath, visited)); } return paths; } function blastRadius(tableName) { console.log(`\n💣 BLAST RADIUS: ${tableName}\n`); const tableNode = graph.nodes.find(n => n.label === tableName && n.type === 'domain'); if (!tableNode) { console.log(`❌ Tabell "${tableName}" hittades inte`); return; } console.log(`📍 Tabell: ${tableNode.label}\n`); const impacted = reverseTraverse(tableNode.id); console.log('🔍 Komponenter som använder denna tabell:\n'); const services = []; const processes = []; impacted.forEach(({ node, depth }) => { if (depth === 0) return; if (node.type === 'service') services.push({ node, depth }); if (node.type === 'business_process') processes.push({ node, depth }); }); if (services.length > 0) { console.log(' ⚙️ TJÄNSTER:'); services.forEach(({ node, depth }) => { console.log(` ${' '.repeat(depth - 1)}• ${node.label} ${node.endpoints ? `(${node.endpoints.length} endpoints)` : ''}`); }); console.log(''); } if (processes.length > 0) { console.log(' 🏭 AFFÄRSPROCESSER:'); processes.forEach(({ node }) => { console.log(` • ${node.label}`); }); console.log(''); } // Förslag på tester console.log('🧪 FÖRESLAGNA TESTER:\n'); services.forEach(({ node }) => { if (node.endpoints) { console.log(` ${node.label}:`); node.endpoints.forEach(ep => { console.log(` - GET/POST ${ep} (med ${tableName} data)`); }); } }); } // ── Huvudmeny ───────────────────────────────────────────────────────────── function printHelp() { console.log(` 🧠 System Graph Reasoning Engine Användning: node graph-engine.mjs [argument] Kommandon: impact Visa vilka komponenter påverkas av en ändring failure Visa vad som går sönder om komponenten fallerar blast Visa vilka som använder en databastabell list Lista alla komponenter i grafen stats Visa statistik om grafen Exempel: node graph-engine.mjs impact wallet node graph-engine.mjs failure stripe node graph-engine.mjs blast missions `); } function listComponents() { console.log('\n📋 KOMPONENTER I SYSTEMET\n'); const byType = {}; graph.nodes.forEach(n => { if (!byType[n.type]) byType[n.type] = []; byType[n.type].push(n); }); for (const [type, nodes] of Object.entries(byType)) { const icon = type === 'actor' ? '👤' : type === 'service' ? '⚙️' : type === 'database' ? '🗄️' : type === 'frontend' ? '🖥️' : type === 'business_process' ? '🏭' : type === 'domain' ? '📊' : type === 'risk' ? '⚠️' : type === 'agent' ? '🤖' : '📦'; console.log(`${icon} ${type.toUpperCase()} (${nodes.length}):`); nodes.forEach(n => { const critical = n.critical ? ' 🔴' : ''; console.log(` • ${n.label}${critical}`); }); console.log(''); } } function stats() { console.log('\n📊 GRAF-STATISTIK\n'); console.log(` Noder: ${graph.nodes.length}`); console.log(` Kanter: ${graph.edges.length}`); console.log(` Affärsprocesser: ${graph.nodes.filter(n => n.type === 'business_process').length}`); console.log(` Risker: ${graph.nodes.filter(n => n.type === 'risk').length}`); console.log(` Kritiska komponenter: ${graph.nodes.filter(n => n.critical).length}`); console.log(` Senast uppdaterad: ${graph.last_updated}`); console.log(` Uppdateringskälla: ${graph.update_source}`); console.log(''); } // ── Main ────────────────────────────────────────────────────────────────── const [,, command, ...args] = process.argv; if (!command || command === 'help') { printHelp(); process.exit(0); } switch (command) { case 'impact': if (!args[0]) { console.log('❌ Ange komponent: node graph-engine.mjs impact '); process.exit(1); } impactAnalysis(args[0]); break; case 'failure': if (!args[0]) { console.log('❌ Ange komponent: node graph-engine.mjs failure '); process.exit(1); } failureAnalysis(args[0]); break; case 'blast': if (!args[0]) { console.log('❌ Ange tabell: node graph-engine.mjs blast '); process.exit(1); } blastRadius(args[0]); break; case 'list': listComponents(); break; case 'stats': stats(); break; default: console.log(`❌ Okänt kommando: ${command}`); printHelp(); process.exit(1); }