@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
213 lines (212 loc) • 7.55 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 { Command } from "commander";
import Database from "better-sqlite3";
import { join } from "path";
import { existsSync } from "fs";
import {
LinearTaskManager
} from "../../features/tasks/linear-task-manager.js";
function getTaskStore(projectRoot) {
const dbPath = join(projectRoot, ".stackmemory", "context.db");
if (!existsSync(dbPath)) {
console.log(
'\u274C StackMemory not initialized. Run "stackmemory init" first.'
);
return null;
}
const config = {
linearApiKey: process.env.STACKMEMORY_LINEAR_API_KEY || process.env.LINEAR_API_KEY,
autoSync: true,
syncInterval: 15
};
return new LinearTaskManager(config, void 0, projectRoot);
}
function createTaskCommands() {
const tasks = new Command("tasks").alias("task").description("Manage tasks from command line");
tasks.command("list").alias("ls").description("List tasks").option(
"-s, --status <status>",
"Filter by status (pending, in_progress, completed, blocked)"
).option(
"-p, --priority <priority>",
"Filter by priority (urgent, high, medium, low)"
).option("-q, --query <text>", "Search in title/description").option("-l, --limit <n>", "Limit results", "20").option("-a, --all", "Include completed tasks").action(async (options) => {
const projectRoot = process.cwd();
const taskStore = getTaskStore(projectRoot);
if (!taskStore) return;
try {
const db = new Database(
join(projectRoot, ".stackmemory", "context.db")
);
let query = "SELECT * FROM task_cache WHERE 1=1";
const params = [];
if (!options.all && !options.status) {
query += " AND status NOT IN ('completed', 'cancelled')";
}
if (options.status) {
query += " AND status = ?";
params.push(options.status);
}
if (options.priority) {
query += " AND priority = ?";
params.push(options.priority);
}
if (options.query) {
query += " AND (title LIKE ? OR description LIKE ?)";
params.push(`%${options.query}%`, `%${options.query}%`);
}
query += " ORDER BY priority ASC, created_at DESC LIMIT ?";
params.push(parseInt(options.limit));
const rows = db.prepare(query).all(...params);
db.close();
if (rows.length === 0) {
console.log("\u{1F4DD} No tasks found");
return;
}
console.log(`
\u{1F4CB} Tasks (${rows.length})
`);
const priorityIcon = {
urgent: "\u{1F534}",
high: "\u{1F7E0}",
medium: "\u{1F7E1}",
low: "\u{1F7E2}"
};
const statusIcon = {
pending: "\u23F3",
in_progress: "\u{1F504}",
completed: "\u2705",
blocked: "\u{1F6AB}",
cancelled: "\u274C"
};
rows.forEach((row) => {
const pIcon = priorityIcon[row.priority] || "\u26AA";
const sIcon = statusIcon[row.status] || "\u26AA";
const id = row.id.slice(0, 10);
console.log(`${sIcon} ${pIcon} [${id}] ${row.title}`);
if (row.description) {
const desc = row.description.split("\n")[0].slice(0, 60);
console.log(
` ${desc}${row.description.length > 60 ? "..." : ""}`
);
}
});
console.log("");
} catch (error) {
console.error("\u274C Failed to list tasks:", error.message);
}
});
tasks.command("add <title>").description("Add a new task").option("-d, --description <text>", "Task description").option(
"-p, --priority <priority>",
"Priority (urgent, high, medium, low)",
"medium"
).option("-t, --tags <tags>", "Comma-separated tags").action(async (title, options) => {
const projectRoot = process.cwd();
const taskStore = getTaskStore(projectRoot);
if (!taskStore) return;
try {
const taskId = taskStore.createTask({
title,
description: options.description,
priority: options.priority,
frameId: "cli",
tags: options.tags ? options.tags.split(",").map((t) => t.trim()) : []
});
console.log(`\u2705 Created task: ${taskId.slice(0, 10)}`);
console.log(` Title: ${title}`);
console.log(` Priority: ${options.priority}`);
} catch (error) {
console.error("\u274C Failed to add task:", error.message);
}
});
tasks.command("start <taskId>").description("Start working on a task").action(async (taskId) => {
const projectRoot = process.cwd();
const taskStore = getTaskStore(projectRoot);
if (!taskStore) return;
try {
const task = findTaskByPartialId(projectRoot, taskId);
if (!task) {
console.log(`\u274C Task not found: ${taskId}`);
return;
}
taskStore.updateTaskStatus(task.id, "in_progress", "Started from CLI");
console.log(`\u{1F504} Started: ${task.title}`);
} catch (error) {
console.error("\u274C Failed to start task:", error.message);
}
});
tasks.command("done <taskId>").alias("complete").description("Mark task as completed").action(async (taskId) => {
const projectRoot = process.cwd();
const taskStore = getTaskStore(projectRoot);
if (!taskStore) return;
try {
const task = findTaskByPartialId(projectRoot, taskId);
if (!task) {
console.log(`\u274C Task not found: ${taskId}`);
return;
}
taskStore.updateTaskStatus(task.id, "completed", "Completed from CLI");
console.log(`\u2705 Completed: ${task.title}`);
} catch (error) {
console.error("\u274C Failed to complete task:", error.message);
}
});
tasks.command("show <taskId>").description("Show task details").action(async (taskId) => {
const projectRoot = process.cwd();
try {
const task = findTaskByPartialId(projectRoot, taskId);
if (!task) {
console.log(`\u274C Task not found: ${taskId}`);
return;
}
console.log(`
\u{1F4CB} Task Details
`);
console.log(`ID: ${task.id}`);
console.log(`Title: ${task.title}`);
console.log(`Status: ${task.status}`);
console.log(`Priority: ${task.priority}`);
console.log(
`Created: ${new Date(task.created_at * 1e3).toLocaleString()}`
);
if (task.completed_at) {
console.log(
`Completed: ${new Date(task.completed_at * 1e3).toLocaleString()}`
);
}
if (task.description) {
console.log(`
Description:
${task.description}`);
}
const tags = JSON.parse(task.tags || "[]");
if (tags.length > 0) {
console.log(`
Tags: ${tags.join(", ")}`);
}
console.log("");
} catch (error) {
console.error("\u274C Failed to show task:", error.message);
}
});
return tasks;
}
function findTaskByPartialId(projectRoot, partialId) {
const dbPath = join(projectRoot, ".stackmemory", "context.db");
if (!existsSync(dbPath)) return null;
const db = new Database(dbPath);
let row = db.prepare("SELECT * FROM task_cache WHERE id = ?").get(partialId);
if (!row) {
row = db.prepare("SELECT * FROM task_cache WHERE id LIKE ?").get(`${partialId}%`);
}
if (!row && partialId.match(/^ENG-\d+$/i)) {
row = db.prepare("SELECT * FROM task_cache WHERE title LIKE ?").get(`%[${partialId.toUpperCase()}]%`);
}
db.close();
return row || null;
}
export {
createTaskCommands
};