- File-based memory under .boocode/memory/ (project/user/reference topics) - Hierarchical 4-scope scan: global → home → project → session - Keyword/tag relevance matching for query-based recall - Injected as <boocode-memory> block in system prompt at assembly - v1 recall-only (extract/dream deferred to v2)
73 lines
2.2 KiB
TypeScript
73 lines
2.2 KiB
TypeScript
import { homedir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { readFile, readdir } from 'node:fs/promises';
|
|
import type { MemoryEntry } from './entries.js';
|
|
import { parseMemoryEntries } from './entries.js';
|
|
import { getMemoryRoot } from './paths.js';
|
|
|
|
export interface MemoryScope {
|
|
projectRoot: string;
|
|
sessionDir?: string;
|
|
homeDir?: string;
|
|
}
|
|
|
|
async function scanDirectory(dir: string): Promise<MemoryEntry[]> {
|
|
const entries: MemoryEntry[] = [];
|
|
try {
|
|
const files = await readdir(dir, { withFileTypes: true });
|
|
for (const file of files) {
|
|
if (file.isFile() && file.name.endsWith('.md')) {
|
|
const content = await readFile(join(dir, file.name), 'utf8');
|
|
entries.push(...parseMemoryEntries(file.name, content));
|
|
}
|
|
}
|
|
} catch {
|
|
// Directory doesn't exist
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
const MEMORY_TOPICS = ['project', 'user', 'reference'] as const;
|
|
|
|
async function scanTopicDirs(root: string): Promise<MemoryEntry[]> {
|
|
const entries: MemoryEntry[] = [];
|
|
for (const topic of MEMORY_TOPICS) {
|
|
entries.push(...(await scanDirectory(join(root, topic))));
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
export async function scanMemoryScopes(scope: MemoryScope): Promise<MemoryEntry[]> {
|
|
const allEntries: MemoryEntry[] = [];
|
|
|
|
// 1. Global (~/.boocode/memory/) - lowest priority
|
|
allEntries.push(...(await scanTopicDirs(getMemoryRoot(homedir()))));
|
|
|
|
// 2. Home ($HOME/.boocode/memory)
|
|
const homeDir = scope.homeDir ?? homedir();
|
|
const homeRoot = getMemoryRoot(homeDir);
|
|
if (homeRoot !== getMemoryRoot(homedir())) {
|
|
allEntries.push(...(await scanTopicDirs(homeRoot)));
|
|
}
|
|
|
|
// 3. Project (.boocode/memory/ under project root)
|
|
allEntries.push(...(await scanTopicDirs(getMemoryRoot(scope.projectRoot))));
|
|
|
|
// 4. Session (.boocode/sessions/<id>/memory.md) - highest priority
|
|
if (scope.sessionDir) {
|
|
try {
|
|
const sessionFile = join(scope.sessionDir, 'memory.md');
|
|
const content = await readFile(sessionFile, 'utf8');
|
|
allEntries.push(...parseMemoryEntries('session-memory', content));
|
|
} catch {
|
|
// No session memory file
|
|
}
|
|
}
|
|
|
|
return allEntries;
|
|
}
|
|
|
|
export async function scanProjectMemory(projectRoot: string): Promise<MemoryEntry[]> {
|
|
return scanMemoryScopes({ projectRoot });
|
|
}
|