@stackmemoryai/stackmemory
Version:
Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.
240 lines (237 loc) • 7.3 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 TaskHandlers {
constructor(deps) {
this.deps = deps;
}
/**
* Create a new task
*/
async handleCreateTask(args) {
try {
const { title, description, priority = "medium", tags = [], parent_id } = args;
if (!title) {
throw new Error("Task title is required");
}
const taskPriority = this.validatePriority(priority);
const taskId = await this.deps.taskStore.createTask({
title,
description: description || "",
priority: taskPriority,
tags: Array.isArray(tags) ? tags : [tags].filter(Boolean),
parentId: parent_id,
frameId: "default-frame"
});
logger.info("Created task", { taskId, title, priority });
return {
content: [
{
type: "text",
text: `Created task: ${title} (${taskId})`
}
],
metadata: {
taskId,
title,
priority: taskPriority
}
};
} catch (error) {
logger.error("Error creating task", error instanceof Error ? error : new Error(String(error)));
throw error;
}
}
/**
* Update task status
*/
async handleUpdateTaskStatus(args) {
try {
const { task_id, status, progress } = args;
if (!task_id) {
throw new Error("Task ID is required");
}
if (!status) {
throw new Error("Status is required");
}
const validStatus = this.validateStatus(status);
await this.deps.taskStore.updateTaskStatus(task_id, validStatus, progress);
const task = await this.deps.taskStore.getTask(task_id);
if (!task) {
throw new Error(`Task not found: ${task_id}`);
}
logger.info("Updated task status", { taskId: task_id, status: validStatus, progress });
return {
content: [
{
type: "text",
text: `Updated task ${task.title} to ${validStatus}${progress ? ` (${progress}% complete)` : ""}`
}
],
metadata: {
taskId: task_id,
status: validStatus,
progress
}
};
} catch (error) {
logger.error("Error updating task status", error instanceof Error ? error : new Error(String(error)));
throw error;
}
}
/**
* Get active tasks with filtering
*/
async handleGetActiveTasks(args) {
try {
const {
status,
priority,
limit = 20,
include_completed = false,
tags = [],
search
} = args;
const filters = {};
if (status) {
filters.status = this.validateStatus(status);
}
if (priority) {
filters.priority = this.validatePriority(priority);
}
if (!include_completed) {
filters.excludeCompleted = true;
}
if (Array.isArray(tags) && tags.length > 0) {
filters.tags = tags;
}
if (search) {
filters.search = search;
}
const tasks = await this.deps.taskStore.getActiveTasks();
const taskSummary = tasks.map((task) => ({
id: task.id,
title: task.title,
status: task.status,
priority: task.priority,
tags: task.tags || [],
created: new Date(task.created_at * 1e3).toLocaleDateString(),
progress: 0
}));
const summaryText = taskSummary.length > 0 ? taskSummary.map(
(t) => `${t.id}: ${t.title} [${t.status}] (${t.priority})`
).join("\n") : "No tasks found matching criteria";
return {
content: [
{
type: "text",
text: `Active Tasks (${tasks.length}):
${summaryText}`
}
],
metadata: {
tasks: taskSummary,
totalCount: tasks.length,
filters
}
};
} catch (error) {
logger.error("Error getting active tasks", error instanceof Error ? error : new Error(String(error)));
throw error;
}
}
/**
* Get task metrics and analytics
*/
async handleGetTaskMetrics(args) {
try {
const metrics = await this.deps.taskStore.getMetrics();
const metricsText = `
Task Metrics:
- Total: ${metrics.total_tasks}
- Blocked: ${metrics.blocked_tasks}
- Overdue: ${metrics.overdue_tasks}
- Completion Rate: ${(metrics.completion_rate * 100).toFixed(1)}%
- Effort Accuracy: ${(metrics.avg_effort_accuracy * 100).toFixed(1)}%
By Priority:
${Object.entries(metrics.by_priority || {}).map(([priority, count]) => `- ${priority}: ${count}`).join("\n")}
By Status:
${Object.entries(metrics.by_status || {}).map(([status, count]) => `- ${status}: ${count}`).join("\n")}
`.trim();
return {
content: [
{
type: "text",
text: metricsText
}
],
metadata: metrics
};
} catch (error) {
logger.error("Error getting task metrics", error instanceof Error ? error : new Error(String(error)));
throw error;
}
}
/**
* Add task dependency
*/
async handleAddTaskDependency(args) {
try {
const { task_id, depends_on, dependency_type = "blocks" } = args;
if (!task_id || !depends_on) {
throw new Error("Both task_id and depends_on are required");
}
await this.deps.taskStore.addDependency(task_id, depends_on);
const task = await this.deps.taskStore.getTask(task_id);
const dependencyTask = await this.deps.taskStore.getTask(depends_on);
if (!task || !dependencyTask) {
throw new Error("One or both tasks not found");
}
logger.info("Added task dependency", { taskId: task_id, dependsOn: depends_on, type: dependency_type });
return {
content: [
{
type: "text",
text: `Added dependency: ${task.title} depends on ${dependencyTask.title} (${dependency_type})`
}
],
metadata: {
taskId: task_id,
dependsOn: depends_on,
dependencyType: dependency_type
}
};
} catch (error) {
logger.error("Error adding task dependency", error instanceof Error ? error : new Error(String(error)));
throw error;
}
}
/**
* Validate task priority
*/
validatePriority(priority) {
const validPriorities = ["low", "medium", "high", "urgent"];
const normalizedPriority = priority.toLowerCase();
if (!validPriorities.includes(normalizedPriority)) {
throw new Error(`Invalid priority: ${priority}. Must be one of: ${validPriorities.join(", ")}`);
}
return normalizedPriority;
}
/**
* Validate task status
*/
validateStatus(status) {
const validStatuses = ["pending", "in_progress", "blocked", "completed", "cancelled"];
const normalizedStatus = status.toLowerCase().replace("_", "-");
if (!validStatuses.includes(normalizedStatus)) {
throw new Error(`Invalid status: ${status}. Must be one of: ${validStatuses.join(", ")}`);
}
return normalizedStatus;
}
}
export {
TaskHandlers
};
//# sourceMappingURL=task-handlers.js.map