UNPKG

@stackmemoryai/stackmemory

Version:

Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a

94 lines (91 loc) 3.08 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import { logger } from "../monitoring/logger.js"; import { frameLifecycleHooks } from "./frame-lifecycle-hooks.js"; const ENRICHMENT_PROMPT = `You are a context enrichment engine. Given a frame digest and its parent context, do two things: 1. Rewrite the digest to be fully self-contained \u2014 resolve all pronouns ("it", "that", "the project") using parent context. 2. Extract entity-relation-value triples from the content. Respond in JSON only: {"enrichedDigest":"...","entities":[{"name":"...","relation":"...","value":"...","context":"..."}]}`; function buildEnrichmentInput(frame, parents) { const parentCtx = parents.map((p) => `[${p.name}]: ${p.digest_text ?? ""}`).join("\n"); return `Parent context: ${parentCtx} Current frame "${frame.name}": ${frame.digest_text ?? ""}`; } async function enrichFrame(frame, parents, apiKey) { try { const Anthropic = (await import("@anthropic-ai/sdk")).default; const client = new Anthropic({ apiKey }); const input = buildEnrichmentInput(frame, parents); const response = await client.messages.create({ model: "claude-3-5-haiku-latest", max_tokens: 1024, system: ENRICHMENT_PROMPT, messages: [{ role: "user", content: input }] }); const text = response.content[0].type === "text" ? response.content[0].text : ""; return JSON.parse(text); } catch (err) { logger.warn("Frame enrichment LLM call failed", { error: err instanceof Error ? err.message : String(err), frameId: frame.frame_id }); return void 0; } } let unregister = null; function registerEnrichmentHook(config, deps) { if (unregister) unregister(); if (!config.enabled) return () => { }; const apiKey = process.env.ANTHROPIC_API_KEY; if (!apiKey) { logger.warn("Enrichment enabled but ANTHROPIC_API_KEY not set"); return () => { }; } unregister = frameLifecycleHooks.onFrameClosed( "frame-enrichment", async (data) => { if (!data.frame.digest_text) return; const parents = await deps.getParentFrames( data.frame.frame_id, config.lookbackDepth ); const result = await enrichFrame(data.frame, parents, apiKey); if (!result) return; await deps.updateDigest(data.frame.frame_id, result.enrichedDigest); if (config.extractEntities) { for (const entity of result.entities) { try { deps.recordEntity( data.frame.project_id, entity.name, entity.relation, entity.value, entity.context, data.frame.frame_id ); } catch { } } } }, -10 // low priority — runs after other hooks ); return () => { if (unregister) { unregister(); unregister = null; } }; } export { enrichFrame, registerEnrichmentHook };