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

213 lines (212 loc) 7.55 kB
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 };