Files
boc/SIL/observer.mjs
T

299 lines
9.8 KiB
JavaScript
Raw Normal View History

#!/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.`);