- 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)
45 lines
1.4 KiB
TypeScript
45 lines
1.4 KiB
TypeScript
import type { MemoryEntry } from './entries.js';
|
|
import { scanProjectMemory } from './scan.js';
|
|
|
|
function extractKeywords(query: string): string[] {
|
|
return query
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9\s]/g, '')
|
|
.split(/\s+/)
|
|
.filter((w) => w.length > 2);
|
|
}
|
|
|
|
export function rankByRelevance(query: string, entries: MemoryEntry[]): MemoryEntry[] {
|
|
const keywords = extractKeywords(query);
|
|
if (keywords.length === 0) return entries.slice(0, 5);
|
|
|
|
const scored = entries.map((entry) => {
|
|
let score = 0;
|
|
const searchText = `${entry.title} ${entry.content} ${entry.tags.join(' ')}`.toLowerCase();
|
|
for (const kw of keywords) {
|
|
if (entry.title.toLowerCase().includes(kw)) score += 3;
|
|
if (entry.tags.some((t) => t.toLowerCase().includes(kw))) score += 2;
|
|
if (entry.content.toLowerCase().includes(kw)) score += 1;
|
|
}
|
|
return { entry, score };
|
|
});
|
|
|
|
return scored
|
|
.filter((s) => s.score > 0)
|
|
.sort((a, b) => b.score - a.score)
|
|
.slice(0, 10)
|
|
.map((s) => s.entry);
|
|
}
|
|
|
|
export async function loadMemoryForSession(
|
|
projectRoot: string,
|
|
_sessionId?: string,
|
|
query?: string,
|
|
): Promise<string[]> {
|
|
const entries = await scanProjectMemory(projectRoot);
|
|
if (entries.length === 0) return [];
|
|
|
|
const relevant = query ? rankByRelevance(query, entries) : entries.slice(0, 5);
|
|
return relevant.map((e) => `[${e.topic}] ${e.title}: ${e.content}`);
|
|
}
|