@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
JavaScript
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
};