feat(server): add institutional memory recall
- 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)
This commit is contained in:
44
apps/server/src/services/memory/recall.ts
Normal file
44
apps/server/src/services/memory/recall.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
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}`);
|
||||
}
|
||||
Reference in New Issue
Block a user