squabble-mcp
Version:
Engineer-driven development with critical-thinking PM collaboration - MCP server for Claude
171 lines • 6.62 kB
JavaScript
export class TaskManager {
workspaceManager;
constructor(workspaceManager) {
this.workspaceManager = workspaceManager;
}
async getTasks() {
return await this.workspaceManager.getTasks();
}
async applyModifications(modifications) {
let tasks = await this.getTasks();
for (const mod of modifications) {
switch (mod.type) {
case 'ADD':
tasks = await this.addTask(tasks, mod);
break;
case 'DELETE':
tasks = await this.deleteTask(tasks, mod);
break;
case 'MODIFY':
tasks = await this.modifyTask(tasks, mod);
break;
case 'BLOCK':
tasks = await this.blockTask(tasks, mod);
break;
case 'SPLIT':
tasks = await this.splitTask(tasks, mod);
break;
case 'MERGE':
tasks = await this.mergeTasks(tasks, mod);
break;
}
}
await this.workspaceManager.saveTasks(tasks);
}
async addTask(tasks, mod) {
// Get the next sequential ID
const nextId = await this.getNextTaskId();
const newTask = {
id: nextId,
title: mod.details.title,
description: mod.details.description,
status: 'pending',
priority: mod.details.priority || 'medium',
dependencies: mod.details.dependencies || [],
requiresPlan: mod.details.requiresPlan !== undefined
? mod.details.requiresPlan
: this.shouldRequirePlan(mod.details.priority), // Default based on priority
branch: mod.details.branch, // PM-assigned git branch name
modificationHistory: [{
...mod,
timestamp: new Date()
}]
};
return [...tasks, newTask];
}
shouldRequirePlan(priority) {
// Simple heuristic for MVP: high and critical priority tasks require plans by default
return priority === 'high' || priority === 'critical';
}
async getNextTaskId() {
// Get current counter from workspace context
let counter = await this.workspaceManager.getContext('task-counter');
if (!counter || typeof counter !== 'number') {
counter = 0;
}
// Increment counter
counter++;
// Save updated counter
await this.workspaceManager.saveContext('task-counter', counter);
// Return formatted ID (e.g., SQBL-1, SQBL-2, etc.)
return `SQBL-${counter}`;
}
async deleteTask(tasks, mod) {
if (!mod.taskId)
return tasks;
return tasks.filter(task => {
if (task.id === mod.taskId) {
// Update tasks that depended on this one
tasks.forEach(t => {
if (t.dependencies.includes(mod.taskId)) {
t.modificationHistory.push({
...mod,
reason: `Dependency ${mod.taskId} was deleted`
});
t.dependencies = t.dependencies.filter(d => d !== mod.taskId);
}
});
return false;
}
return true;
});
}
async modifyTask(tasks, mod) {
if (!mod.taskId)
return tasks;
return tasks.map(task => {
if (task.id === mod.taskId) {
return {
...task,
description: mod.details.newDescription || task.description,
title: mod.details.newTitle || task.title,
priority: mod.details.newPriority || task.priority,
status: mod.details.status || task.status,
requiresPlan: mod.details.requiresPlan !== undefined
? mod.details.requiresPlan
: task.requiresPlan,
branch: mod.details.branch || task.branch, // Allow updating branch
modificationHistory: [...task.modificationHistory, mod]
};
}
return task;
});
}
async blockTask(tasks, mod) {
if (!mod.taskId)
return tasks;
return tasks.map(task => {
if (task.id === mod.taskId) {
return {
...task,
blockedBy: mod.details.blockedBy,
modificationHistory: [...task.modificationHistory, {
...mod,
timestamp: new Date()
}]
};
}
return task;
});
}
async splitTask(tasks, mod) {
if (!mod.taskId)
return tasks;
const originalTask = tasks.find(t => t.id === mod.taskId);
if (!originalTask)
return tasks;
// Remove original task
const filteredTasks = tasks.filter(t => t.id !== mod.taskId);
// Add subtasks
const subtasks = mod.details.subtasks.map((title, index) => ({
id: `${mod.taskId}.${index + 1}`, // Use dot notation for subtasks (e.g., SQBL-1.1, SQBL-1.2)
title,
status: 'pending',
priority: originalTask.priority,
dependencies: index === 0 ? originalTask.dependencies : [`${mod.taskId}-${index}`],
modificationHistory: [{
...mod,
reason: `Split from ${originalTask.title}`
}]
}));
return [...filteredTasks, ...subtasks];
}
async mergeTasks(tasks, mod) {
// Implementation for merging multiple tasks
// For now, return tasks unchanged
return tasks;
}
async getNextTasks(count = 3) {
const tasks = await this.getTasks();
// Get unblocked, pending tasks sorted by priority
const availableTasks = tasks
.filter(t => t.status === 'pending' && !t.blockedBy)
.filter(t => t.dependencies.every(dep => tasks.find(dt => dt.id === dep)?.status === 'done'))
.sort((a, b) => {
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
return priorityOrder[a.priority] - priorityOrder[b.priority];
});
return availableTasks.slice(0, count);
}
}
//# sourceMappingURL=task-manager.js.map