#!/usr/bin/env node // ═══════════════════════════════════════════════════════════════════════════ // SIL Auto-Updater // Uppdaterar kunskapsgrafen från verifierbara källor // ═══════════════════════════════════════════════════════════════════════════ import { execSync } from 'child_process'; import { readFileSync, writeFileSync, existsSync } from 'fs'; import { globSync } from 'glob'; class AutoUpdater { constructor(graphPath) { this.graphPath = graphPath; this.graph = JSON.parse(readFileSync(graphPath, 'utf8')); this.updates = []; // Källklassificering this.sourceRules = { 'AST': { trust: 0.99, autoUpdate: true }, 'DB_MIGRATION': { trust: 0.97, autoUpdate: true }, 'OPENAPI': { trust: 0.96, autoUpdate: true }, 'K8S_MANIFEST': { trust: 0.95, autoUpdate: true }, 'TERRAFORM': { trust: 0.95, autoUpdate: true }, 'CI_CD': { trust: 0.94, autoUpdate: true }, 'README': { trust: 0.50, autoUpdate: false }, 'CODE_COMMENT': { trust: 0.40, autoUpdate: false }, 'AI_INFERENCE': { trust: 0.60, autoUpdate: false } }; } // ── Git-baserad uppdatering ───────────────────────────────────────────── updateFromGit(repoPath) { console.log(`🔄 Uppdaterar från git: ${repoPath}`); try { // Hämta senaste commits const commits = execSync( 'git log --since="7 days ago" --format="%H|%an|%ae|%at|%s"', { cwd: repoPath, encoding: 'utf8' } ).trim().split('\n').filter(c => c); for (const commit of commits) { const [hash, author, email, timestamp, message] = commit.split('|'); // Hämta ändrade filer const files = execSync( `git diff-tree --no-commit-id --name-only -r ${hash}`, { cwd: repoPath, encoding: 'utf8' } ).trim().split('\n').filter(f => f); // Analysera varje fil for (const file of files) { const update = this.analyzeFile(file, hash, message, repoPath); if (update) { this.updates.push(update); } } } console.log(` ✅ ${this.updates.length} uppdateringar identifierade`); } catch (e) { console.error(` ❌ Git-fel: ${e.message}`); } } analyzeFile(file, commitHash, commitMessage, repoPath) { // API-endpoints (hög confidence) if (file.match(/\.(ts|js|tsx|jsx|rs)$/)) { const endpoints = this.extractEndpoints(file, repoPath); if (endpoints.length > 0) { return { type: 'endpoint', source: 'AST', confidence: 0.99, file, commit: commitHash, commitMessage, data: endpoints, autoUpdate: true }; } } // Databasmigreringar (hög confidence) if (file.includes('migration') || file.includes('schema')) { const tables = this.extractTables(file, repoPath); if (tables.length > 0) { return { type: 'table', source: 'DB_MIGRATION', confidence: 0.97, file, commit: commitHash, commitMessage, data: tables, autoUpdate: true }; } } // OpenAPI-specifikationer (hög confidence) if (file.match(/openapi|swagger|\.yaml$|\.yml$/)) { return { type: 'api_contract', source: 'OPENAPI', confidence: 0.96, file, commit: commitHash, commitMessage, autoUpdate: true }; } // Kubernetes-manifest (hög confidence) if (file.match(/\.yaml$|\.yml$/) && file.includes('k8s')) { return { type: 'k8s_resource', source: 'K8S_MANIFEST', confidence: 0.95, file, commit: commitHash, commitMessage, autoUpdate: true }; } // Terraform (hög confidence) if (file.endsWith('.tf')) { return { type: 'infrastructure', source: 'TERRAFORM', confidence: 0.95, file, commit: commitHash, commitMessage, autoUpdate: true }; } // README (låg confidence, manuell review) if (file.includes('README')) { return { type: 'documentation', source: 'README', confidence: 0.50, file, commit: commitHash, commitMessage, autoUpdate: false }; } return null; } extractEndpoints(file, repoPath) { const endpoints = []; const fullPath = `${repoPath}/${file}`; if (!existsSync(fullPath)) return endpoints; const content = readFileSync(fullPath, 'utf8'); // Express/Fastify/Axum routes const matches = content.matchAll(/\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)/g); for (const match of matches) { endpoints.push({ method: match[1].toUpperCase(), path: match[2] }); } return endpoints; } extractTables(file, repoPath) { const tables = []; const fullPath = `${repoPath}/${file}`; if (!existsSync(fullPath)) return tables; const content = readFileSync(fullPath, 'utf8'); // CREATE TABLE const matches = content.matchAll(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"']?(\w+)[`"']?/gi); for (const match of matches) { tables.push({ name: match[1], operation: 'create' }); } // ALTER TABLE const alterMatches = content.matchAll(/ALTER\s+TABLE\s+[`"']?(\w+)[`"']?/gi); for (const match of alterMatches) { tables.push({ name: match[1], operation: 'alter' }); } return tables; } // ── Tillämpa uppdateringar ────────────────────────────────────────────── applyUpdates() { console.log('\n📝 Tillämpar uppdateringar\n'); let autoUpdated = 0; let manualReview = 0; for (const update of this.updates) { const rule = this.sourceRules[update.source]; if (rule && rule.autoUpdate && update.autoUpdate) { // Auto-uppdatera grafen this.applyToGraph(update); autoUpdated++; console.log(` ✅ AUTO: ${update.type} från ${update.source} (${update.file})`); } else { // Kräver manuell review manualReview++; console.log(` ⚠️ MANUELL: ${update.type} från ${update.source} (${update.file})`); } } console.log(`\n Sammanfattning:`); console.log(` Auto-uppdaterade: ${autoUpdated}`); console.log(` Kräver review: ${manualReview}`); } applyToGraph(update) { switch (update.type) { case 'endpoint': // Lägg till nya endpoints till befintlig service for (const endpoint of update.data) { const serviceNode = this.findServiceForFile(update.file); if (serviceNode) { if (!serviceNode.endpoints) serviceNode.endpoints = []; if (!serviceNode.endpoints.includes(`${endpoint.method} ${endpoint.path}`)) { serviceNode.endpoints.push(`${endpoint.method} ${endpoint.path}`); // Lägg till metadata om "Varför?" if (!serviceNode.endpointHistory) serviceNode.endpointHistory = []; serviceNode.endpointHistory.push({ endpoint: `${endpoint.method} ${endpoint.path}`, added: new Date().toISOString(), commit: update.commit, commitMessage: update.commitMessage, confidence: update.confidence, source: update.source }); } } } break; case 'table': // Uppdatera databasnoden const dbNode = this.graph.nodes.find(n => n.type === 'database'); if (dbNode) { for (const table of update.data) { if (!dbNode.tables.includes(table.name)) { dbNode.tables.push(table.name); // Lägg till "Varför?" if (!dbNode.tableHistory) dbNode.tableHistory = []; dbNode.tableHistory.push({ table: table.name, operation: table.operation, added: new Date().toISOString(), commit: update.commit, commitMessage: update.commitMessage, confidence: update.confidence, source: update.source }); } } } break; } } findServiceForFile(file) { // Mappa fil till service baserat på sökväg if (file.includes('auth')) return this.graph.nodes.find(n => n.id === 'auth'); if (file.includes('wallet')) return this.graph.nodes.find(n => n.id === 'wallet'); if (file.includes('mission')) return this.graph.nodes.find(n => n.id === 'mission'); if (file.includes('kyc')) return this.graph.nodes.find(n => n.id === 'kyc'); return null; } // ── Spara graf ────────────────────────────────────────────────────────── saveGraph() { this.graph.last_updated = new Date().toISOString(); this.graph.update_source = 'auto_updater'; writeFileSync(this.graphPath, JSON.stringify(this.graph, null, 2)); console.log(`\n💾 Graf sparad till ${this.graphPath}`); } // ── Reality Check ─────────────────────────────────────────────────────── runRealityCheck() { console.log('\n🔍 Reality Check\n'); const issues = []; // Kolla att alla services har endpoints const services = this.graph.nodes.filter(n => n.type === 'service'); for (const service of services) { if (!service.endpoints || service.endpoints.length === 0) { issues.push({ type: 'missing_endpoints', component: service.label, severity: 'medium', message: 'Service har inga dokumenterade endpoints' }); } } // Kolla att alla tabeller har en service const db = this.graph.nodes.find(n => n.type === 'database'); if (db && db.tables) { for (const table of db.tables) { const usedBy = this.graph.edges.filter(e => e.to === table || (e.relation && e.relation.includes(table)) ); if (usedBy.length === 0) { issues.push({ type: 'orphan_table', component: table, severity: 'low', message: 'Tabell verkar inte användas av någon service' }); } } } // Kolla konfidensnivåer const lowConfidence = this.graph.edges.filter(e => e.confidence && e.confidence < 0.70 ); if (lowConfidence.length > 0) { issues.push({ type: 'low_confidence', component: `${lowConfidence.length} relationer`, severity: 'info', message: 'Relationer med låg confidence behöver verifieras' }); } if (issues.length === 0) { console.log(' ✅ Inga avvikelser hittade'); } else { for (const issue of issues) { const icon = issue.severity === 'high' ? '🔴' : issue.severity === 'medium' ? '🟡' : '🟢'; console.log(` ${icon} ${issue.type}: ${issue.message}`); } } return issues; } } // ── Main ────────────────────────────────────────────────────────────────── const updater = new AutoUpdater('/home/bernt/.openclaw/workspace/SYSTEM_GRAPH.json'); console.log('🚀 SIL Auto-Updater\n'); // Uppdatera från quiXzoom if (existsSync('/home/bernt/repos/quixzoom.com')) { updater.updateFromGit('/home/bernt/repos/quixzoom.com'); } // Uppdatera från ouroboros-identity if (existsSync('/home/bernt/rust/ouroboros-identity')) { updater.updateFromGit('/home/bernt/rust/ouroboros-identity'); } // Tillämpa uppdateringar updater.applyUpdates(); // Kör reality check updater.runRealityCheck(); // Spara updater.saveGraph(); console.log('\n✅ Auto-updater klar');