@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
398 lines (397 loc) • 12.9 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 { randomUUID } from "crypto";
import { logger } from "../../../core/monitoring/logger.js";
class CordHandlers {
constructor(deps) {
this.deps = deps;
}
MAX_DEPTH = 10;
MAX_TASKS = 50;
/**
* cord_spawn — create a child task with clean context (only blocker results visible)
*/
async handleCordSpawn(args) {
return this.createTask(args, "spawn");
}
/**
* cord_fork — create a child task with full sibling context
*/
async handleCordFork(args) {
return this.createTask(args, "fork");
}
/**
* cord_complete — mark a task as completed and unblock dependents
*/
async handleCordComplete(args) {
try {
const { task_id, result } = args;
if (!task_id) throw new Error("task_id is required");
if (result === void 0 || result === null) {
throw new Error("result is required");
}
const db = this.deps.dbAdapter.getRawDatabase();
if (!db) throw new Error("Database not available");
const task = db.prepare("SELECT * FROM cord_tasks WHERE task_id = ?").get(task_id);
if (!task) throw new Error(`Task not found: ${task_id}`);
if (task.status === "completed") {
throw new Error(`Task already completed: ${task_id}`);
}
const now = Math.floor(Date.now() / 1e3);
db.prepare(
"UPDATE cord_tasks SET status = ?, result = ?, completed_at = ? WHERE task_id = ?"
).run("completed", String(result), now, task_id);
const unblocked = this.checkAndUnblockDependents(db, task_id);
logger.info("Cord task completed", { task_id, unblocked });
return {
content: [
{
type: "text",
text: `Task ${task_id} completed.${unblocked.length > 0 ? ` Unblocked: ${unblocked.join(", ")}` : ""}`
}
],
metadata: { task_id, status: "completed", unblocked }
};
} catch (error) {
logger.error(
"Error completing cord task",
error instanceof Error ? error : new Error(String(error))
);
throw error;
}
}
/**
* cord_ask — create an "ask" task (question with optional options)
*/
async handleCordAsk(args) {
try {
const { question, options, parent_id } = args;
if (!question) throw new Error("question is required");
const db = this.deps.dbAdapter.getRawDatabase();
if (!db) throw new Error("Database not available");
const projectId = this.getProjectId();
const runId = this.getRunId();
const taskId = randomUUID();
this.checkTaskLimit(db, projectId);
let depth = 0;
if (parent_id) {
depth = this.computeDepth(db, parent_id);
}
const prompt = options ? JSON.stringify({ question, options }) : JSON.stringify({ question });
db.prepare(
`INSERT INTO cord_tasks (task_id, parent_id, project_id, run_id, goal, prompt, status, context_mode, depth)
VALUES (?, ?, ?, ?, ?, ?, 'asked', 'ask', ?)`
).run(
taskId,
parent_id || null,
projectId,
runId,
question,
prompt,
depth
);
logger.info("Cord ask created", { task_id: taskId });
return {
content: [
{
type: "text",
text: `Ask created: ${taskId} \u2014 "${question}"`
}
],
metadata: {
task_id: taskId,
status: "asked",
context_mode: "ask",
question,
options: options || null
}
};
} catch (error) {
logger.error(
"Error creating cord ask",
error instanceof Error ? error : new Error(String(error))
);
throw error;
}
}
/**
* cord_tree — view the task tree with context scoping
*/
async handleCordTree(args) {
try {
const { task_id, include_results = false } = args;
const db = this.deps.dbAdapter.getRawDatabase();
if (!db) throw new Error("Database not available");
const projectId = this.getProjectId();
let tasks;
if (task_id) {
tasks = this.getSubtree(db, task_id);
} else {
tasks = db.prepare(
"SELECT * FROM cord_tasks WHERE project_id = ? ORDER BY depth ASC, created_at ASC"
).all(projectId);
}
if (tasks.length === 0) {
return {
content: [{ type: "text", text: "No cord tasks found." }],
metadata: { tasks: [] }
};
}
const taskMap = /* @__PURE__ */ new Map();
for (const t of tasks) taskMap.set(t.task_id, t);
const allTasks = db.prepare(
"SELECT * FROM cord_tasks WHERE project_id = ? ORDER BY depth ASC, created_at ASC"
).all(projectId);
const allTaskMap = /* @__PURE__ */ new Map();
for (const t of allTasks) allTaskMap.set(t.task_id, t);
const treeNodes = tasks.map((t) => {
const blockedBy = JSON.parse(t.blocked_by);
const node = {
task_id: t.task_id,
goal: t.goal,
status: t.status,
context_mode: t.context_mode,
depth: t.depth,
blocked_by: blockedBy,
parent_id: t.parent_id
};
if (include_results && t.result !== null) {
node.result = t.result;
}
node.visible_context = this.computeVisibleContext(
t,
allTaskMap,
include_results
);
return node;
});
const summary = tasks.map(
(t) => `${" ".repeat(t.depth)}[${t.status}] ${t.goal}${t.context_mode === "ask" ? " (ask)" : ""}`
).join("\n");
return {
content: [
{
type: "text",
text: `Cord Tree (${tasks.length} tasks):
${summary}`
}
],
metadata: { tasks: treeNodes }
};
} catch (error) {
logger.error(
"Error getting cord tree",
error instanceof Error ? error : new Error(String(error))
);
throw error;
}
}
// --- Private helpers ---
async createTask(args, contextMode) {
try {
const { goal, prompt = "", blocked_by = [], parent_id } = args;
if (!goal) throw new Error("goal is required");
const db = this.deps.dbAdapter.getRawDatabase();
if (!db) throw new Error("Database not available");
const projectId = this.getProjectId();
const runId = this.getRunId();
const taskId = randomUUID();
this.checkTaskLimit(db, projectId);
let depth = 0;
if (parent_id) {
depth = this.computeDepth(db, parent_id);
}
const blockerIds = Array.isArray(blocked_by) ? blocked_by : [];
if (blockerIds.length > 0) {
this.validateBlockers(db, blockerIds);
this.detectCircularDeps(db, taskId, blockerIds);
}
const status = this.initialStatus(db, blockerIds);
db.prepare(
`INSERT INTO cord_tasks (task_id, parent_id, project_id, run_id, goal, prompt, status, context_mode, blocked_by, depth)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
).run(
taskId,
parent_id || null,
projectId,
runId,
goal,
prompt,
status,
contextMode,
JSON.stringify(blockerIds),
depth
);
logger.info("Cord task created", {
task_id: taskId,
context_mode: contextMode,
status
});
return {
content: [
{
type: "text",
text: `Task ${taskId} created (${contextMode}, ${status}): ${goal}`
}
],
metadata: {
task_id: taskId,
status,
context_mode: contextMode,
depth,
blocked_by: blockerIds
}
};
} catch (error) {
logger.error(
"Error creating cord task",
error instanceof Error ? error : new Error(String(error))
);
throw error;
}
}
checkTaskLimit(db, projectId) {
const row = db.prepare("SELECT COUNT(*) as count FROM cord_tasks WHERE project_id = ?").get(projectId);
if (row.count >= this.MAX_TASKS) {
throw new Error(
`Task limit reached: ${this.MAX_TASKS} tasks per project`
);
}
}
computeDepth(db, parentId) {
const parent = db.prepare("SELECT depth FROM cord_tasks WHERE task_id = ?").get(parentId);
if (!parent) throw new Error(`Parent task not found: ${parentId}`);
const depth = parent.depth + 1;
if (depth >= this.MAX_DEPTH) {
throw new Error(`Max depth exceeded: ${this.MAX_DEPTH}`);
}
return depth;
}
validateBlockers(db, blockerIds) {
for (const id of blockerIds) {
const exists = db.prepare("SELECT 1 FROM cord_tasks WHERE task_id = ?").get(id);
if (!exists) throw new Error(`Blocker task not found: ${id}`);
}
}
detectCircularDeps(db, newTaskId, blockerIds) {
const visited = /* @__PURE__ */ new Set();
const queue = [...blockerIds];
while (queue.length > 0) {
const current = queue.shift();
if (current === newTaskId) {
throw new Error("Circular dependency detected");
}
if (visited.has(current)) continue;
visited.add(current);
const task = db.prepare("SELECT blocked_by FROM cord_tasks WHERE task_id = ?").get(current);
if (task) {
const deps = JSON.parse(task.blocked_by);
for (const dep of deps) {
if (!visited.has(dep)) queue.push(dep);
}
}
}
}
initialStatus(db, blockerIds) {
if (blockerIds.length === 0) return "active";
for (const id of blockerIds) {
const task = db.prepare("SELECT status FROM cord_tasks WHERE task_id = ?").get(id);
if (!task || task.status !== "completed") return "blocked";
}
return "active";
}
checkAndUnblockDependents(db, completedTaskId) {
const allBlocked = db.prepare("SELECT * FROM cord_tasks WHERE status = 'blocked'").all();
const unblocked = [];
for (const task of allBlocked) {
const blockers = JSON.parse(task.blocked_by);
if (!blockers.includes(completedTaskId)) continue;
const allDone = blockers.every((bid) => {
if (bid === completedTaskId) return true;
const blocker = db.prepare("SELECT status FROM cord_tasks WHERE task_id = ?").get(bid);
return blocker?.status === "completed";
});
if (allDone) {
db.prepare(
"UPDATE cord_tasks SET status = 'active' WHERE task_id = ?"
).run(task.task_id);
unblocked.push(task.task_id);
}
}
return unblocked;
}
getSubtree(db, rootId) {
const result = [];
const queue = [rootId];
while (queue.length > 0) {
const current = queue.shift();
const task = db.prepare("SELECT * FROM cord_tasks WHERE task_id = ?").get(current);
if (task) {
result.push(task);
const children = db.prepare(
"SELECT task_id FROM cord_tasks WHERE parent_id = ? ORDER BY created_at ASC"
).all(current);
for (const c of children) queue.push(c.task_id);
}
}
return result;
}
computeVisibleContext(task, allTaskMap, includeResults) {
const ctx = { prompt: task.prompt };
if (task.context_mode === "ask") {
try {
const parsed = JSON.parse(task.prompt);
ctx.question = parsed.question;
ctx.options = parsed.options || null;
} catch {
ctx.question = task.goal;
}
if (task.status === "completed" && task.result !== null) {
ctx.answer = task.result;
}
return ctx;
}
const blockerIds = JSON.parse(task.blocked_by);
const blockerResults = [];
for (const bid of blockerIds) {
const blocker = allTaskMap.get(bid);
if (blocker?.status === "completed" && blocker.result !== null) {
blockerResults.push({
task_id: bid,
goal: blocker.goal,
result: includeResults ? blocker.result : "[completed]"
});
}
}
if (blockerResults.length > 0) {
ctx.blocker_results = blockerResults;
}
if (task.context_mode === "fork" && task.parent_id) {
const siblingResults = [];
for (const [, t] of allTaskMap) {
if (t.parent_id === task.parent_id && t.task_id !== task.task_id && t.status === "completed" && t.result !== null) {
siblingResults.push({
task_id: t.task_id,
goal: t.goal,
result: includeResults ? t.result : "[completed]"
});
}
}
if (siblingResults.length > 0) {
ctx.sibling_results = siblingResults;
}
}
return ctx;
}
getProjectId() {
return this.deps.dbAdapter.projectId;
}
getRunId() {
return this.deps.frameManager.currentRunId;
}
}
export {
CordHandlers
};