05ed037fe8
- DNS: pilot.landvex.com -> 16.170.83.169 - TLS: Let's Encrypt certificate (expires 2026-09-30) - Nginx: reverse proxy with SSL termination - API: https://pilot.landvex.com/api/v1/missions - UI: https://pilot.landvex.com/ - Upload: POST /api/v1/missions/import (multipart/form-data) Verified: ✅ https://pilot.landvex.com/health ✅ https://pilot.landvex.com/version ✅ https://pilot.landvex.com/api/v1/missions (list) ✅ https://pilot.landvex.com/api/v1/missions/:id (get) ✅ POST /api/v1/missions/import (video upload) ✅ UI loads with title 'LandveX Intelligence Lab' Next: Pilot 001 — Break the system!
396 lines
12 KiB
JavaScript
396 lines
12 KiB
JavaScript
#!/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');
|