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

271 lines (270 loc) 7.42 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 "../../../core/monitoring/logger.js"; class ContextHandlers { constructor(deps) { this.deps = deps; } /** * Get current project context */ async handleGetContext(args) { try { const query = args.query || ""; const limit = args.limit || 5; logger.info("Getting context", { query, limit }); const hotStack = this.deps.frameManager.getHotStackContext(20); if (hotStack.length === 0) { return { content: [ { type: "text", text: "No active context frames found. Use start_frame to begin working on a task." } ] }; } const contextText = hotStack.map((frame, i) => { const depth = " ".repeat(i); const constraints = frame.header.constraints?.length ? ` ${depth} Constraints: ${frame.header.constraints.join(", ")}` : ""; const events = frame.recentEvents.length ? ` ${depth} Recent: ${frame.recentEvents.length} events` : ""; return `${depth}Frame ${i + 1}: ${frame.header.goal}${constraints}${events}`; }).join("\n"); return { content: [ { type: "text", text: `Current Context Stack: ${contextText}` } ] }; } catch (error) { logger.error( "Error getting context", error instanceof Error ? error : new Error(String(error)) ); throw error; } } /** * Record a decision or important information */ async handleAddDecision(args) { try { const { content, type } = args; if (!content) { throw new Error("Content is required"); } const currentFrame = this.deps.frameManager.getCurrentFrameId(); if (!currentFrame) { throw new Error("No active frame. Use start_frame first."); } this.deps.frameManager.addAnchor( type === "constraint" ? "CONSTRAINT" : "DECISION", content, type === "constraint" ? 9 : 7 ); this.deps.frameManager.addEvent("decision", { type, content, timestamp: Date.now() }); logger.info("Added decision/constraint", { type, content }); return { content: [ { type: "text", text: `Recorded ${type}: ${content}` } ] }; } catch (error) { logger.error( "Error adding decision", error instanceof Error ? error : new Error(String(error)) ); throw error; } } /** * Start a new frame (task/subtask) on the call stack */ async handleStartFrame(args) { try { const { name, type = "task", constraints = [], definitions = {} } = args; if (!name) { throw new Error("Frame name is required"); } const frameId = this.deps.frameManager.createFrame({ type, name, inputs: { constraints, definitions } }); logger.info("Started frame", { frameId, name, type }); return { content: [ { type: "text", text: `Started frame: ${name} (${frameId})` } ], metadata: { frameId, type, name } }; } catch (error) { logger.error( "Error starting frame", error instanceof Error ? error : new Error(String(error)) ); throw error; } } /** * Close current frame with summary */ async handleCloseFrame(args) { try { const { summary, frameId } = args; const targetFrameId = frameId || this.deps.frameManager.getCurrentFrameId(); if (!targetFrameId) { throw new Error("No active frame to close"); } const frame = this.deps.frameManager.getFrame(targetFrameId); if (!frame) { throw new Error(`Frame not found: ${targetFrameId}`); } if (summary) { this.deps.frameManager.addEvent("observation", { type: "completion_summary", content: summary, timestamp: Date.now() }); } this.deps.frameManager.closeFrame( targetFrameId, summary ? { summary } : {} ); logger.info("Closed frame", { frameId: targetFrameId, frameName: frame.name }); return { content: [ { type: "text", text: `Closed frame: ${frame.name}${summary ? ` with summary: ${summary}` : ""}` } ] }; } catch (error) { logger.error( "Error closing frame", error instanceof Error ? error : new Error(String(error)) ); throw error; } } /** * Add an anchor (important fact) to current frame */ async handleAddAnchor(args) { try { const { type, text, priority = 5 } = args; if (!text) { throw new Error("Anchor text is required"); } const currentFrame = this.deps.frameManager.getCurrentFrameId(); if (!currentFrame) { throw new Error("No active frame. Use start_frame first."); } const validTypes = [ "FACT", "DECISION", "CONSTRAINT", "INTERFACE_CONTRACT", "TODO", "RISK" ]; if (!validTypes.includes(type)) { throw new Error( `Invalid anchor type. Must be one of: ${validTypes.join(", ")}` ); } this.deps.frameManager.addAnchor(type, text, priority); logger.info("Added anchor", { type, text, priority }); return { content: [ { type: "text", text: `Added ${type.toLowerCase()}: ${text}` } ] }; } catch (error) { logger.error( "Error adding anchor", error instanceof Error ? error : new Error(String(error)) ); throw error; } } /** * Get current hot stack context */ async handleGetHotStack(args) { try { const maxEvents = args.max_events || 10; const hotStack = this.deps.frameManager.getHotStackContext(maxEvents); if (hotStack.length === 0) { return { content: [ { type: "text", text: "No active frames on the stack." } ] }; } const stackSummary = hotStack.map((frame, index) => ({ depth: index, frameId: frame.frameId, goal: frame.header.goal, constraints: frame.header.constraints || [], anchors: frame.anchors.length, recentEvents: frame.recentEvents.length, artifacts: frame.activeArtifacts.length })); return { content: [ { type: "text", text: `Hot Stack (${hotStack.length} frames): ` + stackSummary.map( (f) => ` ${f.depth}: ${f.goal} (${f.anchors} anchors, ${f.recentEvents} events)` ).join("\n") } ], metadata: { stack: stackSummary } }; } catch (error) { logger.error( "Error getting hot stack", error instanceof Error ? error : new Error(String(error)) ); throw error; } } } export { ContextHandlers };