#!/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 '); 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 # Sök i minnet'); console.log(' node project-memory.mjs --log-decision # Logga beslut'); }