Skip to main content
Glama
jakedx6
by jakedx6
tasks.js37.3 kB
import { supabaseService } from '../lib/api-client.js'; import { requireAuth } from '../lib/auth.js'; import { logger } from '../lib/logger.js'; import { z } from 'zod'; // Input schemas for task tools const ListTasksSchema = z.object({ project_id: z.string().uuid().optional(), initiative_id: z.string().uuid().optional(), status: z.enum(['todo', 'in_progress', 'done']).optional(), assignee_id: z.string().uuid().optional(), search: z.string().optional(), limit: z.number().int().positive().max(100).default(20) }); const GetTaskSchema = z.object({ task_id: z.string().uuid() }); const CreateTaskSchema = z.object({ project_id: z.string().uuid(), initiative_id: z.string().uuid().optional(), title: z.string().min(1).max(500), description: z.string().optional(), priority: z.enum(['low', 'medium', 'high']).default('medium'), due_date: z.string().datetime().optional(), assignee_id: z.string().uuid().optional() // Removed metadata as it doesn't exist in the database schema }); const UpdateTaskSchema = z.object({ task_id: z.string().uuid(), title: z.string().min(1).max(500).optional(), description: z.string().optional(), status: z.enum(['todo', 'in_progress', 'done']).optional(), priority: z.enum(['low', 'medium', 'high']).optional(), due_date: z.string().datetime().optional(), assignee_id: z.string().uuid().optional(), initiative_id: z.string().uuid().optional() // Removed metadata as it doesn't exist in the database schema }); const GetTaskContextSchema = z.object({ task_id: z.string().uuid() }); /** * List tasks with filtering */ export const listTasksTool = { name: 'list_tasks', description: 'List tasks with optional filtering by project, initiative, status, or assignee', inputSchema: { type: 'object', properties: { project_id: { type: 'string', format: 'uuid', description: 'Filter tasks by project ID' }, initiative_id: { type: 'string', format: 'uuid', description: 'Filter tasks by initiative ID' }, status: { type: 'string', enum: ['todo', 'in_progress', 'done'], description: 'Filter tasks by status' }, assignee_id: { type: 'string', format: 'uuid', description: 'Filter tasks by assignee' }, search: { type: 'string', description: 'Search tasks by title or description' }, limit: { type: 'number', minimum: 1, maximum: 100, default: 20, description: 'Maximum number of tasks to return' } } } }; export const listTasks = requireAuth(async (args) => { const { project_id, initiative_id, status, assignee_id, search, limit } = ListTasksSchema.parse(args); logger.info('Listing tasks', { project_id, initiative_id, status, assignee_id, search, limit }); let tasks = await supabaseService.getTasks({ project_id, status, assignee_id, search }, { limit }, { field: 'updated_at', order: 'desc' }); // Filter by initiative_id if provided (since API doesn't support it directly yet) if (initiative_id) { tasks = tasks.filter(task => task.initiative_id === initiative_id); } return { tasks, total: tasks.length, filters_applied: { project_id, initiative_id, status, assignee_id, search } }; }); /** * Create new task */ export const createTaskTool = { name: 'create_task', description: 'Create a new task in a project', inputSchema: { type: 'object', properties: { project_id: { type: 'string', format: 'uuid', description: 'The project ID where the task will be created' }, initiative_id: { type: 'string', format: 'uuid', description: 'Optional initiative ID to associate the task with' }, title: { type: 'string', minLength: 1, maxLength: 500, description: 'The title of the task' }, description: { type: 'string', description: 'Optional detailed description of the task' }, priority: { type: 'string', enum: ['low', 'medium', 'high'], default: 'medium', description: 'Priority level of the task' }, due_date: { type: 'string', format: 'date-time', description: 'Optional due date for the task (ISO 8601 format)' }, assignee_id: { type: 'string', format: 'uuid', description: 'Optional user ID to assign the task to' }, // Removed metadata as it doesn't exist in the database schema }, required: ['project_id', 'title'] } }; export const createTask = requireAuth(async (args) => { const taskData = CreateTaskSchema.parse(args); logger.info('Creating new task', { project_id: taskData.project_id, initiative_id: taskData.initiative_id, title: taskData.title }); const task = await supabaseService.createTask({ project_id: taskData.project_id, initiative_id: taskData.initiative_id || null, title: taskData.title, description: taskData.description || null, priority: taskData.priority, due_date: taskData.due_date || null, assignee_id: taskData.assignee_id || null, status: 'todo' // Removed metadata as it doesn't exist in the database schema }); logger.info('Task created successfully', { task_id: task.id, title: task.title }); return { task, message: `Task "${task.title}" created successfully` }; }); /** * Update existing task */ export const updateTaskTool = { name: 'update_task', description: 'Update an existing task with new information', inputSchema: { type: 'object', properties: { task_id: { type: 'string', format: 'uuid', description: 'The unique identifier of the task to update' }, title: { type: 'string', minLength: 1, maxLength: 500, description: 'New title for the task' }, description: { type: 'string', description: 'New description for the task' }, status: { type: 'string', enum: ['todo', 'in_progress', 'done'], description: 'New status for the task' }, priority: { type: 'string', enum: ['low', 'medium', 'high'], description: 'New priority for the task' }, due_date: { type: 'string', format: 'date-time', description: 'New due date for the task (ISO 8601 format)' }, assignee_id: { type: 'string', format: 'uuid', description: 'New assignee for the task' }, initiative_id: { type: 'string', format: 'uuid', description: 'New initiative ID to associate the task with' }, // Removed metadata as it doesn't exist in the database schema }, required: ['task_id'] } }; export const updateTask = requireAuth(async (args) => { const { task_id, ...updates } = UpdateTaskSchema.parse(args); logger.info('Updating task', { task_id, updates }); // Handle status change logic // completed_at property removed as it doesn't exist in the database schema const task = await supabaseService.updateTask(task_id, updates); logger.info('Task updated successfully', { task_id: task.id }); return { task, message: `Task "${task.title}" updated successfully` }; }); /** * Get task context including related documents and conversations */ export const getTaskContextTool = { name: 'get_task_context', description: 'Get comprehensive task context including related documents and AI conversations', inputSchema: { type: 'object', properties: { task_id: { type: 'string', format: 'uuid', description: 'The unique identifier of the task' } }, required: ['task_id'] } }; export const getTaskContext = requireAuth(async (args) => { const { task_id } = GetTaskContextSchema.parse(args); logger.info('Getting task context', { task_id }); // Get the task first to get project info const task = await supabaseService.getTask(task_id); // Get related documents from the same project const relatedDocuments = await supabaseService.getDocuments({ project_id: task.project_id }, { limit: 10 }, { field: 'updated_at', order: 'desc' }); // Get AI-friendly task analysis const taskAnalysis = { completion_status: calculateCompletionStatus(task), time_estimates: estimateTimeRequirements(task), dependencies: findTaskDependencies(task), suggested_actions: generateTaskSuggestions(task), related_documentation: relatedDocuments.filter(doc => doc.content.toLowerCase().includes(task.title.toLowerCase()) || (task.description && doc.content.toLowerCase().includes(task.description.toLowerCase()))) }; return { task, related_documents: relatedDocuments, task_analysis: taskAnalysis, ai_suggestions: generateTaskAISuggestions(task, taskAnalysis) }; }); // Helper functions for task analysis function calculateCompletionStatus(task) { if (task.status === 'done') return 'done'; if (task.status === 'todo') return 'todo'; // blocked status doesn't exist in schema if (task.due_date && new Date(task.due_date) < new Date()) return 'overdue'; if (task.status === 'in_progress') return 'active'; return 'todo'; } function estimateTimeRequirements(task) { // Simple estimation based on description length and complexity const descriptionLength = task.description?.length || 0; const priority = task.priority; let estimatedHours = 2; // Base estimate if (descriptionLength > 500) estimatedHours += 2; if (priority === 'high' || priority === 'urgent') estimatedHours += 1; return { estimated_hours: estimatedHours, confidence: 'low', // Would be higher with historical data factors_considered: ['description_length', 'priority'] }; } function findTaskDependencies(task) { // In a real implementation, you'd analyze task relationships // For now, return empty array return []; } function generateTaskSuggestions(task) { const suggestions = []; if (!task.description) { suggestions.push('Add a detailed description to help with task execution'); } if (!task.due_date) { suggestions.push('Set a due date to improve planning and prioritization'); } if (!task.assignee_id) { suggestions.push('Assign the task to a team member for accountability'); } if (task.status === 'todo' && task.priority === 'high') { suggestions.push('Consider moving this high-priority task to in_progress'); } return suggestions; } function generateTaskAISuggestions(task, analysis) { const suggestions = []; if (analysis.completion_status === 'overdue') { suggestions.push('This task is overdue. Consider reassessing the scope or extending the deadline.'); } if (analysis.estimated_hours > 8) { suggestions.push('This task seems complex. Consider breaking it into smaller subtasks.'); } if (analysis.related_documentation.length === 0) { suggestions.push('No related documentation found. Consider creating supporting documents.'); } return suggestions; } function identifyBottlenecks(taskBoard) { const bottlenecks = []; if (taskBoard.in_progress.length > taskBoard.todo.length * 2) { bottlenecks.push('Too many tasks in progress - consider focusing on completion'); } if (taskBoard.blocked.length > 0) { bottlenecks.push(`${taskBoard.blocked.length} blocked task(s) need attention`); } return bottlenecks; } function calculateFlowMetrics(taskBoard) { const total = Object.values(taskBoard).flat().length; return { wip_limit_suggestion: Math.max(3, Math.floor(total * 0.3)), throughput_potential: taskBoard.done.length > taskBoard.in_progress.length ? 'good' : 'concerning', cycle_time_estimate: 'unavailable' // Would calculate with historical data }; } // Removed task tools export from here - will be at end of file /** * Get task by ID */ export const getTaskTool = { name: 'get_task', description: 'Get a task by ID with full details', inputSchema: { type: 'object', properties: { task_id: { type: 'string', format: 'uuid', description: 'The unique identifier of the task' } }, required: ['task_id'] } }; export const getTask = requireAuth(async (args) => { const { task_id } = GetTaskSchema.parse(args); logger.info('Getting task', { task_id }); try { // Use the direct getTask method instead of searching const task = await supabaseService.getTask(task_id); logger.info('Task retrieved successfully', { task_id, title: task.title }); return { task, message: `Task "${task.title}" retrieved successfully` }; } catch (error) { logger.error('Failed to get task', { task_id, error: error.message, errorCode: error.code, statusCode: error.statusCode, fullError: error }); // Handle NotFoundError specifically if (error.code === 'NOT_FOUND' || error.statusCode === 404) { throw new Error(`Task with ID ${task_id} not found`); } // Re-throw other errors throw error; } }); /** * Add task dependency */ export const addTaskDependencyTool = { name: 'add_task_dependency', description: 'Add a dependency relationship between tasks', inputSchema: { type: 'object', properties: { task_id: { type: 'string', description: 'ID of the dependent task' }, depends_on_task_id: { type: 'string', description: 'ID of the task this task depends on' }, dependency_type: { type: 'string', enum: ['blocks', 'subtask', 'related'], default: 'blocks', description: 'Type of dependency relationship' } }, required: ['task_id', 'depends_on_task_id'] } }; const AddTaskDependencySchema = z.object({ task_id: z.string().min(1), depends_on_task_id: z.string().min(1), dependency_type: z.enum(['blocks', 'subtask', 'related']).default('blocks') }); export const addTaskDependency = requireAuth(async (args) => { const { task_id, depends_on_task_id, dependency_type } = AddTaskDependencySchema.parse(args); logger.info('Adding task dependency', { task_id, depends_on_task_id, dependency_type }); // Validate tasks exist const [task, dependsOnTask] = await Promise.all([ supabaseService.getTask(task_id), supabaseService.getTask(depends_on_task_id) ]); if (!task || !dependsOnTask) { throw new Error('One or both tasks not found'); } // Check for circular dependencies const hasCircularDependency = await checkCircularDependency(task_id, depends_on_task_id); if (hasCircularDependency) { throw new Error('Cannot create dependency: would create circular dependency'); } // Create dependency record const dependency = await supabaseService.createTaskDependency({ task_id, depends_on_task_id, dependency_type, created_at: new Date().toISOString() }); // Note: blocked status doesn't exist in database schema // In a real implementation, you might use metadata or a separate blocking system return { dependency, task: await supabaseService.getTask(task_id), depends_on_task: dependsOnTask, message: `Dependency created: "${task.title}" ${dependency_type} "${dependsOnTask.title}"` }; }); /** * Get task dependencies */ export const getTaskDependenciesTool = { name: 'get_task_dependencies', description: 'Get all dependencies for a task or project', inputSchema: { type: 'object', properties: { task_id: { type: 'string', description: 'ID of the task (optional if project_id provided)' }, project_id: { type: 'string', description: 'ID of the project (optional if task_id provided)' }, include_transitive: { type: 'boolean', default: false, description: 'Include transitive dependencies' } } } }; const GetTaskDependenciesSchema = z.object({ task_id: z.string().optional(), project_id: z.string().optional(), include_transitive: z.boolean().default(false) }).refine(data => data.task_id || data.project_id, { message: "Either task_id or project_id must be provided" }); export const getTaskDependencies = requireAuth(async (args) => { const { task_id, project_id, include_transitive } = GetTaskDependenciesSchema.parse(args); logger.info('Getting task dependencies', { task_id, project_id, include_transitive }); let dependencies; if (task_id) { dependencies = await supabaseService.getTaskDependencies(task_id); } else { dependencies = await supabaseService.getProjectDependencies(project_id); } // Build dependency graph const dependencyGraph = buildDependencyGraph(dependencies); // Calculate critical path if project-level let criticalPath = null; if (project_id) { const projectTasks = await supabaseService.getTasks({ project_id }); criticalPath = calculateCriticalPath(projectTasks, dependencies); } return { dependencies, dependency_graph: dependencyGraph, critical_path: criticalPath, blocked_tasks: dependencies.filter(d => d.dependency_type === 'blocks' && d.depends_on_task?.status !== 'done'), analysis: analyzeDependencies(dependencies) }; }); /** * Create task workflow */ export const createTaskWorkflowTool = { name: 'create_task_workflow', description: 'Create a workflow with multiple connected tasks', inputSchema: { type: 'object', properties: { project_id: { type: 'string', description: 'ID of the project' }, workflow_name: { type: 'string', description: 'Name of the workflow' }, tasks: { type: 'array', items: { type: 'object', properties: { title: { type: 'string' }, description: { type: 'string' }, assignee_id: { type: 'string' }, priority: { type: 'string', enum: ['low', 'medium', 'high'] }, estimated_hours: { type: 'number' }, depends_on: { type: 'array', items: { type: 'number' }, description: 'Array of task indices this task depends on' } }, required: ['title'] }, description: 'Array of tasks to create in the workflow' }, auto_start: { type: 'boolean', default: false, description: 'Whether to automatically start the first tasks' } }, required: ['project_id', 'workflow_name', 'tasks'] } }; const CreateTaskWorkflowSchema = z.object({ project_id: z.string().min(1), workflow_name: z.string().min(1), tasks: z.array(z.object({ title: z.string().min(1), description: z.string().optional(), assignee_id: z.string().optional(), priority: z.enum(['low', 'medium', 'high']).default('medium'), estimated_hours: z.number().positive().optional(), depends_on: z.array(z.number()).default([]) })).min(1), auto_start: z.boolean().default(false) }); export const createTaskWorkflow = requireAuth(async (args) => { const { project_id, workflow_name, tasks, auto_start } = CreateTaskWorkflowSchema.parse(args); logger.info('Creating task workflow', { project_id, workflow_name, task_count: tasks.length }); const createdTasks = []; const taskMap = new Map(); // Map task index to created task ID // Create all tasks first for (let i = 0; i < tasks.length; i++) { const taskData = tasks[i]; const task = await supabaseService.createTask({ title: taskData.title, description: taskData.description || null, project_id, initiative_id: null, status: 'todo', priority: taskData.priority, assignee_id: taskData.assignee_id || null, due_date: null // Removed metadata as it doesn't exist in the database schema }); createdTasks.push(task); taskMap.set(i, task.id); } // Create dependencies const dependencies = []; for (let i = 0; i < tasks.length; i++) { const taskData = tasks[i]; const currentTaskId = taskMap.get(i); for (const dependsOnIndex of taskData.depends_on) { if (dependsOnIndex < i) { // Only allow dependencies on previous tasks const dependsOnTaskId = taskMap.get(dependsOnIndex); const dependency = await supabaseService.createTaskDependency({ task_id: currentTaskId, depends_on_task_id: dependsOnTaskId, dependency_type: 'blocks' }); dependencies.push(dependency); } } } // Auto-start first tasks if requested if (auto_start) { const firstTasks = createdTasks.filter((_, index) => tasks[index].depends_on.length === 0); for (const task of firstTasks) { await supabaseService.updateTask(task.id, { status: 'todo', updated_at: new Date().toISOString() }); } } return { workflow_name, tasks_created: createdTasks.length, dependencies_created: dependencies.length, tasks: createdTasks, dependencies, ready_tasks: auto_start ? createdTasks.filter((_, i) => tasks[i].depends_on.length === 0).length : 0 }; }); /** * Bulk update tasks */ export const bulkUpdateTasksTool = { name: 'bulk_update_tasks', description: 'Update multiple tasks at once', inputSchema: { type: 'object', properties: { task_ids: { type: 'array', items: { type: 'string' }, description: 'Array of task IDs to update' }, updates: { type: 'object', properties: { status: { type: 'string', enum: ['todo', 'in_progress', 'done'] }, priority: { type: 'string', enum: ['low', 'medium', 'high'] }, assignee_id: { type: 'string' }, due_date: { type: 'string', format: 'date-time' } }, description: 'Updates to apply to all tasks' }, cascade_dependencies: { type: 'boolean', default: false, description: 'Whether to update dependent tasks automatically' } }, required: ['task_ids', 'updates'] } }; const BulkUpdateTasksSchema = z.object({ task_ids: z.array(z.string().min(1)).min(1), updates: z.object({ status: z.enum(['todo', 'in_progress', 'done']).optional(), priority: z.enum(['low', 'medium', 'high']).optional(), assignee_id: z.string().optional(), due_date: z.string().datetime().optional() }), cascade_dependencies: z.boolean().default(false) }); export const bulkUpdateTasks = requireAuth(async (args) => { const { task_ids, updates, cascade_dependencies } = BulkUpdateTasksSchema.parse(args); logger.info('Bulk updating tasks', { task_count: task_ids.length, updates, cascade_dependencies }); const results = []; const now = new Date().toISOString(); for (const task_id of task_ids) { try { const updateData = { ...updates, updated_at: now }; // Handle status change cascading if (updates.status === 'done' && cascade_dependencies) { await handleTaskCompletion(task_id); } const result = await supabaseService.updateTask(task_id, updateData); results.push({ task_id, success: true, task: result }); } catch (error) { logger.error(`Failed to update task ${task_id}:`, error); results.push({ task_id, success: false, error: error instanceof Error ? error.message : 'Unknown error' }); } } return { summary: { total_tasks: task_ids.length, successful_updates: results.filter(r => r.success).length, failed_updates: results.filter(r => !r.success).length }, results, applied_updates: updates }; }); /** * Get task workflow status */ export const getTaskWorkflowStatusTool = { name: 'get_task_workflow_status', description: 'Get status and progress of a task workflow', inputSchema: { type: 'object', properties: { project_id: { type: 'string', description: 'ID of the project' }, workflow_name: { type: 'string', description: 'Name of the workflow' } }, required: ['project_id', 'workflow_name'] } }; const GetTaskWorkflowStatusSchema = z.object({ project_id: z.string().min(1), workflow_name: z.string().min(1) }); export const getTaskWorkflowStatus = requireAuth(async (args) => { const { project_id, workflow_name } = GetTaskWorkflowStatusSchema.parse(args); logger.info('Getting workflow status', { project_id, workflow_name }); // Get workflow tasks (metadata_filter not available) const tasks = await supabaseService.getTasks({ project_id // metadata_filter not available as metadata doesn't exist in database schema }); if (tasks.length === 0) { throw new Error(`Workflow "${workflow_name}" not found`); } // Calculate progress const totalTasks = tasks.length; const completedTasks = tasks.filter(t => t.status === 'done').length; const inProgressTasks = tasks.filter(t => t.status === 'in_progress').length; const todoTasks = tasks.filter(t => t.status === 'todo').length; // Get dependencies for this workflow const dependencies = await supabaseService.getProjectDependencies(project_id); const workflowDependencies = dependencies.filter(d => tasks.some(t => t.id === d.task_id) && tasks.some(t => t.id === d.depends_on_task_id)); // Calculate critical path const criticalPath = calculateCriticalPath(tasks, workflowDependencies); // Identify bottlenecks const bottlenecks = identifyWorkflowBottlenecks(tasks, workflowDependencies); return { workflow_name, progress: { total_tasks: totalTasks, completed_tasks: completedTasks, in_progress_tasks: inProgressTasks, todo_tasks: todoTasks, completion_percentage: Math.round((completedTasks / totalTasks) * 100) }, critical_path: criticalPath, bottlenecks, next_actions: getNextWorkflowActions(tasks, workflowDependencies), estimated_completion: estimateWorkflowCompletion(tasks, workflowDependencies) }; }); // Helper functions async function checkCircularDependency(taskId, dependsOnTaskId) { // Implement circular dependency check const visited = new Set(); const recursionStack = new Set(); async function hasCycle(currentTaskId) { if (recursionStack.has(currentTaskId)) return true; if (visited.has(currentTaskId)) return false; visited.add(currentTaskId); recursionStack.add(currentTaskId); const dependencies = await supabaseService.getTaskDependencies(currentTaskId); for (const dep of dependencies) { if (await hasCycle(dep.depends_on_task_id)) return true; } recursionStack.delete(currentTaskId); return false; } return await hasCycle(dependsOnTaskId); } function buildDependencyGraph(dependencies) { const graph = {}; dependencies.forEach(dep => { if (!graph[dep.depends_on_task_id]) graph[dep.depends_on_task_id] = []; graph[dep.depends_on_task_id].push(dep.task_id); }); return graph; } function calculateCriticalPath(tasks, dependencies) { // Simplified critical path calculation const taskMap = new Map(tasks.map(t => [t.id, t])); const graph = buildDependencyGraph(dependencies); // Calculate earliest start times const earliestStart = {}; const duration = {}; tasks.forEach(task => { duration[task.id] = task.estimated_hours || 8; // Default 1 day earliestStart[task.id] = 0; }); // Topological sort and calculate earliest times const sorted = topologicalSort(tasks, dependencies); sorted.forEach(taskId => { const deps = dependencies.filter(d => d.task_id === taskId); if (deps.length > 0) { earliestStart[taskId] = Math.max(...deps.map(d => earliestStart[d.depends_on_task_id] + duration[d.depends_on_task_id])); } }); // Find critical path (longest path) const criticalTasks = sorted.filter(taskId => { const task = taskMap.get(taskId); return task && (task.priority === 'high' || task.priority === 'urgent'); }); return { path: criticalTasks, total_duration: Math.max(...Object.values(earliestStart).map((start, i) => start + Object.values(duration)[i])), bottleneck_tasks: criticalTasks.slice(0, 3) }; } function topologicalSort(tasks, dependencies) { const inDegree = {}; const graph = {}; // Initialize tasks.forEach(task => { inDegree[task.id] = 0; graph[task.id] = []; }); // Build graph and count in-degrees dependencies.forEach(dep => { graph[dep.depends_on_task_id].push(dep.task_id); inDegree[dep.task_id]++; }); // Kahn's algorithm const queue = tasks.filter(task => inDegree[task.id] === 0).map(t => t.id); const result = []; while (queue.length > 0) { const current = queue.shift(); result.push(current); graph[current].forEach(neighbor => { inDegree[neighbor]--; if (inDegree[neighbor] === 0) { queue.push(neighbor); } }); } return result; } function analyzeDependencies(dependencies) { return { total_dependencies: dependencies.length, blocking_dependencies: dependencies.filter(d => d.dependency_type === 'blocks').length, subtask_relationships: dependencies.filter(d => d.dependency_type === 'subtask').length, related_links: dependencies.filter(d => d.dependency_type === 'related').length, complexity_score: Math.min(dependencies.length * 0.1, 1) // 0-1 scale }; } async function handleTaskCompletion(taskId) { // Get dependent tasks const dependencies = await supabaseService.getTaskDependencies(taskId); const dependentTasks = dependencies.filter(d => d.depends_on_task_id === taskId); // Update dependent tasks that are no longer blocked for (const dep of dependentTasks) { const otherDeps = await supabaseService.getTaskDependencies(dep.task_id); const stillBlocked = otherDeps.some(d => d.depends_on_task_id !== taskId && d.dependency_type === 'blocks' && d.depends_on_task?.status !== 'done'); if (!stillBlocked && dep.task?.status === 'blocked') { await supabaseService.updateTask(dep.task_id, { status: 'todo', updated_at: new Date().toISOString() }); } } } function identifyWorkflowBottlenecks(tasks, dependencies) { const bottlenecks = []; // Tasks with many dependencies const dependencyCounts = new Map(); dependencies.forEach(dep => { dependencyCounts.set(dep.task_id, (dependencyCounts.get(dep.task_id) || 0) + 1); }); // High-dependency tasks const highDependencyTasks = tasks.filter(task => (dependencyCounts.get(task.id) || 0) > 2); bottlenecks.push(...highDependencyTasks.map(task => ({ task_id: task.id, title: task.title, type: 'high_dependencies', dependency_count: dependencyCounts.get(task.id) || 0 }))); // Overdue tasks that block others const overdueBlockers = tasks.filter(task => task.due_date && new Date(task.due_date) < new Date() && task.status !== 'done' && dependencies.some(d => d.depends_on_task_id === task.id)); bottlenecks.push(...overdueBlockers.map(task => ({ task_id: task.id, title: task.title, type: 'overdue_blocker', days_overdue: Math.ceil((Date.now() - new Date(task.due_date).getTime()) / (1000 * 60 * 60 * 24)) }))); return bottlenecks; } function getNextWorkflowActions(tasks, dependencies) { const actions = []; // Todo tasks const todoTasks = tasks.filter(t => t.status === 'todo'); if (todoTasks.length > 0) { actions.push(`Start ${todoTasks.length} todo task(s): ${todoTasks.map(t => t.title).join(', ')}`); } // Blocked tasks that could be unblocked const blockedTasks = tasks.filter(t => t.status === 'blocked'); if (blockedTasks.length > 0) { actions.push(`Review ${blockedTasks.length} blocked task(s) for potential unblocking`); } // Overdue tasks const overdueTasks = tasks.filter(t => t.due_date && new Date(t.due_date) < new Date() && t.status !== 'done'); if (overdueTasks.length > 0) { actions.push(`Address ${overdueTasks.length} overdue task(s) immediately`); } return actions; } function estimateWorkflowCompletion(tasks, dependencies) { const remainingTasks = tasks.filter(t => t.status !== 'done'); const totalEstimatedHours = remainingTasks.reduce((sum, task) => sum + (task.estimated_hours || 8), 0); // Assume 8 hours per day, 5 days per week const estimatedDays = Math.ceil(totalEstimatedHours / 8); const estimatedDate = new Date(); estimatedDate.setDate(estimatedDate.getDate() + estimatedDays); return { remaining_tasks: remainingTasks.length, estimated_hours: totalEstimatedHours, estimated_days: estimatedDays, estimated_completion_date: estimatedDate.toISOString().split('T')[0], confidence: remainingTasks.every(t => t.estimated_hours) ? 'high' : 'medium' }; } // Export all task tools export const taskTools = { listTasksTool, createTaskTool, getTaskTool, updateTaskTool, addTaskDependencyTool, getTaskDependenciesTool, createTaskWorkflowTool, bulkUpdateTasksTool, getTaskWorkflowStatusTool }; export const taskHandlers = { list_tasks: listTasks, create_task: createTask, get_task: getTask, update_task: updateTask, add_task_dependency: addTaskDependency, get_task_dependencies: getTaskDependencies, create_task_workflow: createTaskWorkflow, bulk_update_tasks: bulkUpdateTasks, get_task_workflow_status: getTaskWorkflowStatus };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jakedx6/helios9-MCP-Server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server