import {MemoryCache} from './memory-cache.ts'; import {extractMetadata, Memory, MemoryNode} from './memory.ts'; export function buildMemoryGraph(memories: Memory[] | MemoryCache): MemoryNode[] { const mems = memories instanceof MemoryCache ? memories.memories : memories; const nameSet = new Set(mems.map(m => m.name)); const ghosts = new Set(); const nodes: MemoryNode[] = mems.map(m => { const {links, backlinks} = extractMetadata(m.content); return { name: m.name, missing: false, links, backlinks, }; }); for (const node of nodes) { for (const link of node.links) { if (!nameSet.has(link)) ghosts.add(link); } } return [ ...nodes, ...[...ghosts].map(name => ({ name, missing: true, links: [], backlinks: nodes .filter(n => n.links.includes(name)) .map(n => n.name), })) ]; } export function renderMemoryGraph(nodes) { if (!nodes.length) return 'No memories yet.'; const groups = new Map(); for (const node of nodes) { const [prefix, ...rest] = node.name.split('/'); const group = rest.length ? prefix : 'Root'; const label = rest.length ? rest.join('/') : node.name; if (!groups.has(group)) groups.set(group, []); groups.get(group).push({...node, label}); } const ghostCount = nodes.filter(n => n.missing).length; const lines = [`Memory Graph (${nodes.length} nodes, ${ghostCount} ghost${ghostCount === 1 ? '' : 's'})`, '']; for (const group of [...groups.keys()].sort()) { const items = groups.get(group).sort((a, b) => a.label.localeCompare(b.label)); lines.push(`${group}/`); items.forEach((n, i) => { const last = i === items.length - 1; const branch = last ? '└─' : '├─'; const pad = last ? ' ' : '│ '; const tag = n.missing ? ' (ghost)' : ''; lines.push(` ${branch} ${n.label}${tag}`); if (n.links.length) lines.push(` ${pad} → ${n.links.join(', ')}`); if (n.backlinks.length) lines.push(` ${pad} ← ${n.backlinks.join(', ')}`); }); lines.push(''); } return lines.join('\n').trimEnd(); }