From 566d84fd7aa55807bd4dae2671239277a1c85293 Mon Sep 17 00:00:00 2001 From: ztimson Date: Tue, 4 Aug 2026 12:58:39 -0400 Subject: [PATCH] Added memory graph traversal helpers --- package.json | 2 +- src/helpers.ts | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 1 + 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 src/helpers.ts diff --git a/package.json b/package.json index 2b3055a..dd7dbfb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ztimson/ai-utils", - "version": "1.4.0", + "version": "1.4.1", "description": "AI Utility library", "author": "Zak Timson", "license": "MIT", diff --git a/src/helpers.ts b/src/helpers.ts new file mode 100644 index 0000000..29d128d --- /dev/null +++ b/src/helpers.ts @@ -0,0 +1,72 @@ +import {Memory, MemoryCache} from './memory.ts'; + +export type MemoryNode = { + name: string; + missing: boolean; + links: string[]; + backlinks: string[]; +} + +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 => ({ + name: m.name, + missing: false, + links: m.links, + backlinks: m.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(); +} diff --git a/src/index.ts b/src/index.ts index 16fbcbb..bdc7bf2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export * from './ai'; export * from './antrhopic'; export * from './audio'; +export * from './helpers'; export * from './llm'; export * from './memory'; export * from './open-ai';