299 lines
9.8 KiB
JavaScript
299 lines
9.8 KiB
JavaScript
|
|
#!/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.`);
|