#!/usr/bin/env node // ═══════════════════════════════════════════════════════════════════════════ // SIL Observer - Kontinuerlig systemobservation // Skannar kodbas, databaser, och infrastruktur för att uppdatera grafen // ═══════════════════════════════════════════════════════════════════════════ import { execSync } from 'child_process'; import { readFileSync, existsSync } from 'fs'; import { globSync } from 'glob'; class SILObserver { constructor(graphPath) { this.graph = JSON.parse(readFileSync(graphPath, 'utf8')); this.changes = []; } // ── Git-observation ───────────────────────────────────────────────────── observeGit(repoPath) { console.log(`🔍 Skannar git-repo: ${repoPath}`); try { // Senaste commit const lastCommit = execSync('git log -1 --format="%H|%an|%ae|%at|%s"', { cwd: repoPath, encoding: 'utf8' }).trim(); const [hash, author, email, timestamp, message] = lastCommit.split('|'); // Ändrade filer const changedFiles = execSync('git diff-tree --no-commit-id --name-only -r HEAD', { cwd: repoPath, encoding: 'utf8' }).trim().split('\n').filter(f => f); // Nya endpoints från ändrade filer const newEndpoints = this.extractEndpointsFromFiles(changedFiles, repoPath); // Nya tabeller från migrationer const newTables = this.extractTablesFromMigrations(changedFiles, repoPath); this.changes.push({ type: 'git_commit', source: 'git', confidence: 0.99, data: { hash, author, timestamp: new Date(parseInt(timestamp) * 1000).toISOString(), message, changedFiles, newEndpoints, newTables } }); console.log(` ✅ Hittade ${changedFiles.length} ändrade filer`); console.log(` ✅ ${newEndpoints.length} nya endpoints`); console.log(` ✅ ${newTables.length} nya tabeller`); } catch (e) { console.error(` ❌ Git-fel: ${e.message}`); } } extractEndpointsFromFiles(files, repoPath) { const endpoints = []; for (const file of files) { if (!file.match(/\.(ts|js|tsx|jsx|rs)$/)) continue; const fullPath = `${repoPath}/${file}`; if (!existsSync(fullPath)) continue; const content = readFileSync(fullPath, 'utf8'); // Hitta API-endpoints (Express/Fastify/Axum) const endpointMatches = content.matchAll(/\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)/g); for (const match of endpointMatches) { endpoints.push({ method: match[1].toUpperCase(), path: match[2], file, confidence: 0.98 }); } // Hitta fetch/anrop till externa API:er const apiMatches = content.matchAll(/fetch\s*\(\s*['"`]([^'"`]+)/g); for (const match of apiMatches) { if (match[1].includes('api.quixzoom.com') || match[1].includes('http')) { endpoints.push({ type: 'external_call', url: match[1], file, confidence: 0.95 }); } } } return endpoints; } extractTablesFromMigrations(files, repoPath) { const tables = []; for (const file of files) { if (!file.includes('migration') && !file.includes('schema')) continue; const fullPath = `${repoPath}/${file}`; if (!existsSync(fullPath)) continue; const content = readFileSync(fullPath, 'utf8'); // Hitta CREATE TABLE const tableMatches = content.matchAll(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"']?(\w+)[`"']?/gi); for (const match of tableMatches) { tables.push({ name: match[1], operation: 'create', file, confidence: 0.97 }); } // Hitta ALTER TABLE const alterMatches = content.matchAll(/ALTER\s+TABLE\s+[`"']?(\w+)[`"']?/gi); for (const match of alterMatches) { tables.push({ name: match[1], operation: 'alter', file, confidence: 0.97 }); } } return tables; } // ── AST-observation ───────────────────────────────────────────────────── observeAST(repoPath) { console.log(`🔍 Skannar AST: ${repoPath}`); // Hitta alla TypeScript/JavaScript-filer const files = globSync('**/*.{ts,tsx,js,jsx}', { cwd: repoPath }); const imports = []; const functionCalls = []; for (const file of files.slice(0, 100)) { // Begränsa för prestanda const fullPath = `${repoPath}/${file}`; if (!existsSync(fullPath)) continue; const content = readFileSync(fullPath, 'utf8'); // Hitta imports const importMatches = content.matchAll(/import\s+.*?\s+from\s+['"`]([^'"`]+)['"`]/g); for (const match of importMatches) { imports.push({ source: match[1], file, confidence: 0.99 }); } // Hitta funktionsanrop till externa tjänster const callMatches = content.matchAll(/(\w+)\s*\(\s*['"`]([^'"`]*(?:api|http|stripe|ses)[^'"`]*)['"`]/gi); for (const match of callMatches) { functionCalls.push({ function: match[1], argument: match[2], file, confidence: 0.90 }); } } this.changes.push({ type: 'ast_analysis', source: 'ast', confidence: 0.98, data: { filesScanned: files.length, imports: imports.slice(0, 50), functionCalls: functionCalls.slice(0, 50) } }); console.log(` ✅ Skannade ${files.length} filer`); console.log(` ✅ Hittade ${imports.length} imports`); console.log(` ✅ Hittade ${functionCalls.length} funktionsanrop`); } // ── Miljövariabler ────────────────────────────────────────────────────── observeEnvironment() { console.log(`🔍 Skannar miljövariabler`); const envVars = {}; // Hitta .env-filer const envFiles = globSync('**/.env*', { cwd: '/home/bernt' }); for (const file of envFiles) { try { const content = readFileSync(`/home/bernt/${file}`, 'utf8'); const vars = content.match(/^\w+=[^\n]+/gm) || []; envVars[file] = vars.map(v => v.split('=')[0]); } catch (e) { // Ignorera filer vi inte kan läsa } } this.changes.push({ type: 'environment', source: 'env_files', confidence: 0.85, data: { files: envFiles, variables: envVars } }); console.log(` ✅ Hittade ${envFiles.length} .env-filer`); } // ── Sammanställning ───────────────────────────────────────────────────── generateReport() { console.log(`\n📊 OBSERVER-RAPPORT\n`); console.log(`Händelser: ${this.changes.length}`); console.log(`Källor: ${[...new Set(this.changes.map(c => c.source))].join(', ')}`); console.log(`Genomsnittlig confidence: ${(this.changes.reduce((a, c) => a + c.confidence, 0) / this.changes.length).toFixed(2)}`); // Föreslagna graf-uppdateringar console.log(`\n📝 Föreslagna uppdateringar av kunskapsgrafen:\n`); for (const change of this.changes) { switch (change.type) { case 'git_commit': if (change.data.newEndpoints.length > 0) { console.log(` ➕ Nya endpoints:`); change.data.newEndpoints.forEach(e => { console.log(` ${e.method} ${e.path} (${e.confidence})`); }); } if (change.data.newTables.length > 0) { console.log(` ➕ Nya/ändrade tabeller:`); change.data.newTables.forEach(t => { console.log(` ${t.operation.toUpperCase()} ${t.name} (${t.confidence})`); }); } break; case 'ast_analysis': console.log(` 🔍 AST-analys visar ${change.data.imports.length} imports`); break; case 'environment': console.log(` 🔧 Miljövariabler från ${Object.keys(change.data.variables).length} filer`); break; } } return this.changes; } } // ── Main ────────────────────────────────────────────────────────────────── const observer = new SILObserver('/home/bernt/.openclaw/workspace/SYSTEM_GRAPH.json'); console.log('🚀 SIL Observer startar...\n'); // Observera quiXzoom frontend if (existsSync('/home/bernt/repos/quixzoom.com')) { observer.observeGit('/home/bernt/repos/quixzoom.com'); observer.observeAST('/home/bernt/repos/quixzoom.com'); } // Observera ouroboros-identity if (existsSync('/home/bernt/rust/ouroboros-identity')) { observer.observeGit('/home/bernt/rust/ouroboros-identity'); } // Miljövariabler observer.observeEnvironment(); // Generera rapport const changes = observer.generateReport(); console.log(`\n✅ Observer klar. ${changes.length} händelser identifierade.`); console.log(` Kör graph-engine.mjs för att se påverkan.`);