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!
224 lines
6.7 KiB
JavaScript
224 lines
6.7 KiB
JavaScript
#!/usr/bin/env node
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// EOS Project Memory — Varför beslut fattades, vad som testades, etc.
|
|
// Erik-krav: "Inte chat history. Inte Markdown. Utan ett projektminne."
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
import { readFileSync, writeFileSync, existsSync, appendFileSync } from 'fs';
|
|
|
|
const MEMORY_DIR = '/home/bernt/.openclaw/workspace/EOS/memory';
|
|
const DECISIONS_LOG = `${MEMORY_DIR}/decisions.jsonl`;
|
|
const EXPERIMENTS_LOG = `${MEMORY_DIR}/experiments.jsonl`;
|
|
const BUGS_LOG = `${MEMORY_DIR}/bugs.jsonl`;
|
|
const ARCHITECTURE_LOG = `${MEMORY_DIR}/architecture.jsonl`;
|
|
|
|
class ProjectMemory {
|
|
constructor() {
|
|
if (!existsSync(MEMORY_DIR)) {
|
|
// Skapa med mkdirSync
|
|
const { mkdirSync } = require('fs');
|
|
mkdirSync(MEMORY_DIR, { recursive: true });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Logga ett beslut
|
|
*/
|
|
logDecision(decision) {
|
|
const entry = {
|
|
timestamp: new Date().toISOString(),
|
|
type: 'decision',
|
|
id: decision.id || `dec-${Date.now()}`,
|
|
question: decision.question,
|
|
answer: decision.answer,
|
|
// Varför
|
|
reasoning: decision.reasoning,
|
|
alternatives: decision.alternatives || [],
|
|
rejected: decision.rejected || [],
|
|
// Vem
|
|
decidedBy: decision.decidedBy,
|
|
// Konsekvenser
|
|
consequences: decision.consequences,
|
|
// Status
|
|
status: decision.status || 'active',
|
|
// Om beslutet senare ändrades
|
|
supersededBy: decision.supersededBy || null
|
|
};
|
|
|
|
appendFileSync(DECISIONS_LOG, JSON.stringify(entry) + '\n', 'utf8');
|
|
return entry;
|
|
}
|
|
|
|
/**
|
|
* Logga ett experiment
|
|
*/
|
|
logExperiment(experiment) {
|
|
const entry = {
|
|
timestamp: new Date().toISOString(),
|
|
type: 'experiment',
|
|
id: experiment.id || `exp-${Date.now()}`,
|
|
hypothesis: experiment.hypothesis,
|
|
method: experiment.method,
|
|
result: experiment.result,
|
|
conclusion: experiment.conclusion,
|
|
// Lärande
|
|
lesson: experiment.lesson,
|
|
// Om experimentet misslyckades
|
|
failure: experiment.failure || null
|
|
};
|
|
|
|
appendFileSync(EXPERIMENTS_LOG, JSON.stringify(entry) + '\n', 'utf8');
|
|
return entry;
|
|
}
|
|
|
|
/**
|
|
* Logga en bugg
|
|
*/
|
|
logBug(bug) {
|
|
const entry = {
|
|
timestamp: new Date().toISOString(),
|
|
type: 'bug',
|
|
id: bug.id || `bug-${Date.now()}`,
|
|
description: bug.description,
|
|
component: bug.component,
|
|
rootCause: bug.rootCause,
|
|
fix: bug.fix,
|
|
// Lärande
|
|
lesson: bug.lesson,
|
|
// Om buggar kunde ha förhindrats
|
|
preventable: bug.preventable || false,
|
|
prevention: bug.prevention || null
|
|
};
|
|
|
|
appendFileSync(BUGS_LOG, JSON.stringify(entry) + '\n', 'utf8');
|
|
return entry;
|
|
}
|
|
|
|
/**
|
|
* Logga arkitekturförändring
|
|
*/
|
|
logArchitecture(change) {
|
|
const entry = {
|
|
timestamp: new Date().toISOString(),
|
|
type: 'architecture',
|
|
id: change.id || `arch-${Date.now()}`,
|
|
change: change.change,
|
|
reason: change.reason,
|
|
// Före/efter
|
|
before: change.before,
|
|
after: change.after,
|
|
// Konsekvenser
|
|
impact: change.impact,
|
|
// Om ändringen ångrades
|
|
reverted: change.reverted || false,
|
|
revertReason: change.revertReason || null
|
|
};
|
|
|
|
appendFileSync(ARCHITECTURE_LOG, JSON.stringify(entry) + '\n', 'utf8');
|
|
return entry;
|
|
}
|
|
|
|
/**
|
|
* Sök i projektminnet
|
|
*/
|
|
query(query) {
|
|
const results = [];
|
|
|
|
// Sök i alla loggar
|
|
for (const log of [DECISIONS_LOG, EXPERIMENTS_LOG, BUGS_LOG, ARCHITECTURE_LOG]) {
|
|
if (!existsSync(log)) continue;
|
|
|
|
const entries = readFileSync(log, 'utf8')
|
|
.split('\n')
|
|
.filter(line => line.trim())
|
|
.map(line => {
|
|
try { return JSON.parse(line); } catch { return null; }
|
|
})
|
|
.filter(Boolean);
|
|
|
|
for (const entry of entries) {
|
|
const text = JSON.stringify(entry).toLowerCase();
|
|
if (text.includes(query.toLowerCase())) {
|
|
results.push(entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Besvara en fråga om projektets historia
|
|
*/
|
|
answerQuestion(question) {
|
|
const results = this.query(question);
|
|
|
|
console.log(`🔍 "${question}"\n`);
|
|
|
|
if (results.length === 0) {
|
|
console.log(' Ingen information hittad.\n');
|
|
return null;
|
|
}
|
|
|
|
for (const result of results.slice(0, 5)) {
|
|
console.log(` [${result.type.toUpperCase()}] ${result.id}`);
|
|
console.log(` ${result.question || result.hypothesis || result.description || result.change}`);
|
|
console.log(` ${result.answer || result.conclusion || result.fix || result.reason}`);
|
|
console.log();
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Visa projektminnets struktur
|
|
*/
|
|
showStructure() {
|
|
console.log('📚 PROJECT MEMORY\n');
|
|
|
|
const logs = [
|
|
{ name: 'Beslut', path: DECISIONS_LOG },
|
|
{ name: 'Experiment', path: EXPERIMENTS_LOG },
|
|
{ name: 'Buggar', path: BUGS_LOG },
|
|
{ name: 'Arkitektur', path: ARCHITECTURE_LOG }
|
|
];
|
|
|
|
for (const log of logs) {
|
|
const count = existsSync(log.path)
|
|
? readFileSync(log.path, 'utf8').split('\n').filter(l => l.trim()).length
|
|
: 0;
|
|
console.log(` ${log.name}: ${count} entries`);
|
|
}
|
|
console.log();
|
|
}
|
|
}
|
|
|
|
// ── Main ──────────────────────────────────────────────────────────────────
|
|
|
|
const memory = new ProjectMemory();
|
|
const command = process.argv[2] || '--structure';
|
|
|
|
if (command === '--structure') {
|
|
memory.showStructure();
|
|
} else if (command === '--query') {
|
|
const query = process.argv[3];
|
|
if (!query) {
|
|
console.log('Användning: node project-memory.mjs --query <fråga>');
|
|
process.exit(1);
|
|
}
|
|
memory.answerQuestion(query);
|
|
} else if (command === '--log-decision') {
|
|
memory.logDecision({
|
|
question: process.argv[3] || 'Varför valde vi X?',
|
|
answer: process.argv[4] || 'För att...',
|
|
reasoning: process.argv[5] || 'Eftersom...',
|
|
decidedBy: 'agent'
|
|
});
|
|
console.log('✅ Beslut loggat');
|
|
} else {
|
|
console.log('Användning:');
|
|
console.log(' node project-memory.mjs --structure # Visa struktur');
|
|
console.log(' node project-memory.mjs --query <fråga> # Sök i minnet');
|
|
console.log(' node project-memory.mjs --log-decision <q> <a> <r> # Logga beslut');
|
|
}
|