Skip to main content
Glama

mcp-github-project-manager

ExpandTaskTool.ts15.7 kB
import { z } from 'zod'; import { ToolDefinition, ToolSchema } from '../ToolValidator.js'; import { TaskGenerationService } from '../../../services/TaskGenerationService.js'; import { TaskStatus, TaskPriority, TaskComplexity } from '../../../domain/ai-types.js'; import { MCPResponse } from '../../../domain/mcp-types.js'; import { ToolResultFormatter } from '../ToolResultFormatter.js'; // Schema for expand_task tool const expandTaskSchema = z.object({ taskTitle: z.string().min(3).describe('Title of the task to expand'), taskDescription: z.string().min(10).describe('Detailed description of the task'), currentComplexity: z.number().min(1).max(10).describe('Current complexity score of the task'), maxSubtasks: z.number().min(2).max(15).default(8).describe('Maximum number of subtasks to create'), maxDepth: z.number().min(1).max(3).default(2).describe('Maximum depth of subtask breakdown'), targetComplexity: z.number().min(1).max(5).default(3).describe('Target complexity for each subtask'), includeEstimates: z.boolean().default(true).describe('Whether to include effort estimates for subtasks'), includeDependencies: z.boolean().default(true).describe('Whether to identify dependencies between subtasks'), includeAcceptanceCriteria: z.boolean().default(true).describe('Whether to generate acceptance criteria for subtasks'), projectType: z.string().optional().describe('Type of project (web-app, mobile-app, api, etc.)'), teamSkills: z.array(z.string()).optional().describe('Team skills to consider for subtask assignment') }); export type ExpandTaskArgs = z.infer<typeof expandTaskSchema>; /** * Implementation function for expand_task tool */ async function executeExpandTask(args: ExpandTaskArgs): Promise<MCPResponse> { const taskService = new TaskGenerationService(); try { // Create a mock parent task const parentTask = { id: 'parent-task', title: args.taskTitle, description: args.taskDescription, complexity: args.currentComplexity as TaskComplexity, estimatedHours: args.currentComplexity * 4, // Simple estimation priority: TaskPriority.MEDIUM, status: TaskStatus.PENDING, dependencies: [], acceptanceCriteria: [], tags: [], aiGenerated: false, subtasks: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; // Generate subtasks using AI const subtasks = await taskService.expandTaskIntoSubtasks({ task: parentTask, maxDepth: args.maxDepth, autoEstimate: args.includeEstimates }); // Enhance subtasks with additional details const enhancedSubtasks = enhanceSubtasks(subtasks, args); // Detect dependencies if requested const dependencies = args.includeDependencies ? detectSubtaskDependencies(enhancedSubtasks) : []; // Calculate metrics const metrics = calculateSubtaskMetrics(enhancedSubtasks, parentTask); // Generate recommendations const recommendations = generateSubtaskRecommendations(enhancedSubtasks, args, metrics); // Format response const summary = formatTaskExpansion( parentTask, enhancedSubtasks, dependencies, metrics, recommendations, args ); return ToolResultFormatter.formatSuccess('expand_task', { summary, parentTask, subtasks: enhancedSubtasks, dependencies, metrics, recommendations }); } catch (error) { process.stderr.write(`Error in expand_task tool: ${error}\n`); return ToolResultFormatter.formatSuccess('expand_task', { error: `Failed to expand task: ${error instanceof Error ? error.message : 'Unknown error'}`, success: false }); } } /** * Enhance subtasks with additional details */ function enhanceSubtasks(subtasks: any[], args: ExpandTaskArgs) { return subtasks.map((subtask, index) => { // Generate acceptance criteria if requested const acceptanceCriteria = args.includeAcceptanceCriteria ? generateAcceptanceCriteria(subtask) : []; // Assign appropriate tags const tags = generateSubtaskTags(subtask, args.projectType); // Adjust complexity to target if needed const adjustedComplexity = Math.min(subtask.complexity, args.targetComplexity); return { ...subtask, id: `subtask-${index + 1}`, complexity: adjustedComplexity, estimatedHours: adjustedComplexity * 2, // Subtasks are smaller acceptanceCriteria, tags, priority: index < 2 ? 'high' : 'medium', // First few subtasks are high priority status: 'pending' }; }); } /** * Generate acceptance criteria for a subtask */ function generateAcceptanceCriteria(subtask: any): Array<{id: string, description: string, completed: boolean}> { const criteria = []; // Generic criteria criteria.push({ id: `criteria-${Date.now()}-1`, description: `${subtask.title} is implemented according to specifications`, completed: false }); criteria.push({ id: `criteria-${Date.now()}-2`, description: 'All unit tests pass for this subtask', completed: false }); // Task-specific criteria based on title/description if (subtask.title.toLowerCase().includes('api')) { criteria.push({ id: `criteria-${Date.now()}-3`, description: 'API endpoints return correct responses and status codes', completed: false }); } if (subtask.title.toLowerCase().includes('ui') || subtask.title.toLowerCase().includes('frontend')) { criteria.push({ id: `criteria-${Date.now()}-4`, description: 'UI is responsive and follows design specifications', completed: false }); } if (subtask.title.toLowerCase().includes('database')) { criteria.push({ id: `criteria-${Date.now()}-5`, description: 'Database schema changes are properly migrated', completed: false }); } return criteria; } /** * Generate appropriate tags for subtasks */ function generateSubtaskTags(subtask: any, projectType?: string): string[] { const tags = ['subtask']; if (projectType) { tags.push(projectType); } // Add tags based on subtask content const title = subtask.title.toLowerCase(); const description = subtask.description.toLowerCase(); if (title.includes('setup') || title.includes('config')) tags.push('setup'); if (title.includes('api') || description.includes('endpoint')) tags.push('backend'); if (title.includes('ui') || title.includes('frontend')) tags.push('frontend'); if (title.includes('test') || description.includes('testing')) tags.push('testing'); if (title.includes('database') || title.includes('migration')) tags.push('database'); if (title.includes('deploy') || title.includes('infrastructure')) tags.push('devops'); return tags; } /** * Detect dependencies between subtasks */ function detectSubtaskDependencies(subtasks: any[]): Array<{ from: string; to: string; type: 'blocks' | 'depends_on'; description: string; }> { const dependencies = []; // Simple heuristic-based dependency detection for (let i = 0; i < subtasks.length; i++) { for (let j = i + 1; j < subtasks.length; j++) { const taskA = subtasks[i]; const taskB = subtasks[j]; // Setup tasks should come before implementation tasks if (taskA.title.toLowerCase().includes('setup') && !taskB.title.toLowerCase().includes('setup')) { dependencies.push({ from: taskA.id, to: taskB.id, type: 'blocks' as const, description: `${taskA.title} must be completed before ${taskB.title}` }); } // Database tasks should come before API tasks if (taskA.title.toLowerCase().includes('database') && taskB.title.toLowerCase().includes('api')) { dependencies.push({ from: taskA.id, to: taskB.id, type: 'blocks' as const, description: 'Database schema must be ready before API implementation' }); } // API tasks should come before UI tasks if (taskA.title.toLowerCase().includes('api') && taskB.title.toLowerCase().includes('ui')) { dependencies.push({ from: taskA.id, to: taskB.id, type: 'blocks' as const, description: 'API must be available before UI implementation' }); } } } return dependencies; } /** * Calculate metrics for subtasks */ function calculateSubtaskMetrics(subtasks: any[], parentTask: any) { const totalEffort = subtasks.reduce((sum, task) => sum + task.estimatedHours, 0); const avgComplexity = subtasks.reduce((sum, task) => sum + task.complexity, 0) / subtasks.length; const complexityReduction = parentTask.complexity - avgComplexity; const effortIncrease = totalEffort - parentTask.estimatedHours; return { totalSubtasks: subtasks.length, totalEffort, avgComplexity: Math.round(avgComplexity * 10) / 10, complexityReduction: Math.round(complexityReduction * 10) / 10, effortIncrease, effortIncreasePercent: Math.round((effortIncrease / parentTask.estimatedHours) * 100) }; } /** * Generate recommendations for subtask management */ function generateSubtaskRecommendations( subtasks: any[], args: ExpandTaskArgs, metrics: any ): string[] { const recommendations = []; // Complexity recommendations if (metrics.avgComplexity > args.targetComplexity) { recommendations.push('Some subtasks still exceed target complexity - consider further breakdown'); } else { recommendations.push('Subtasks are appropriately sized for implementation'); } // Effort recommendations if (metrics.effortIncreasePercent > 20) { recommendations.push('Breaking down the task revealed additional complexity - adjust timeline accordingly'); } // Dependency recommendations const setupTasks = subtasks.filter(task => task.tags.includes('setup')); if (setupTasks.length > 0) { recommendations.push('Start with setup and infrastructure tasks to establish foundation'); } // Parallel work recommendations const parallelTasks = subtasks.filter(task => !task.tags.includes('setup') && task.complexity <= 3 ); if (parallelTasks.length >= 2) { recommendations.push(`${parallelTasks.length} tasks can potentially be worked on in parallel`); } // Team assignment recommendations if (args.teamSkills && args.teamSkills.length > 0) { recommendations.push('Consider assigning subtasks based on team member expertise'); } return recommendations; } /** * Format task expansion summary */ function formatTaskExpansion( parentTask: any, subtasks: any[], dependencies: any[], metrics: any, recommendations: string[], args: ExpandTaskArgs ): string { const sections = [ '# Task Expansion Complete', '', `## Original Task: ${parentTask.title}`, `**Original Complexity:** ${parentTask.complexity}/10`, `**Original Estimate:** ${parentTask.estimatedHours} hours`, '' ]; // Expansion summary sections.push( '## Expansion Summary', `**Subtasks Created:** ${metrics.totalSubtasks}`, `**Total Effort:** ${metrics.totalEffort} hours`, `**Average Complexity:** ${metrics.avgComplexity}/10`, `**Complexity Reduction:** ${metrics.complexityReduction} points per task`, `**Effort Adjustment:** ${metrics.effortIncrease > 0 ? '+' : ''}${metrics.effortIncrease} hours (${metrics.effortIncreasePercent > 0 ? '+' : ''}${metrics.effortIncreasePercent}%)`, '' ); // Subtasks sections.push('## Generated Subtasks'); subtasks.forEach((subtask, index) => { sections.push( `### ${index + 1}. ${subtask.title}`, `**Complexity:** ${subtask.complexity}/10 | **Effort:** ${subtask.estimatedHours}h | **Priority:** ${subtask.priority}`, `**Description:** ${subtask.description}`, '' ); if (subtask.acceptanceCriteria.length > 0) { sections.push( '**Acceptance Criteria:**', ...subtask.acceptanceCriteria.slice(0, 2).map((criteria: any) => `- ${criteria.description}`), '' ); } if (subtask.tags.length > 0) { sections.push( `**Tags:** ${subtask.tags.join(', ')}`, '' ); } sections.push('---', ''); }); // Dependencies if (dependencies.length > 0) { sections.push( '## Task Dependencies', `**Total Dependencies:** ${dependencies.length}`, '' ); dependencies.forEach(dep => { const fromTask = subtasks.find(t => t.id === dep.from); const toTask = subtasks.find(t => t.id === dep.to); sections.push( `- **${fromTask?.title}** ${dep.type === 'blocks' ? 'blocks' : 'depends on'} **${toTask?.title}**`, ` ${dep.description}`, '' ); }); } // Recommendations if (recommendations.length > 0) { sections.push( '## Recommendations', ...recommendations.map(rec => `- ${rec}`), '' ); } // Implementation order const orderedTasks = [...subtasks].sort((a, b) => { // Setup tasks first if (a.tags.includes('setup') && !b.tags.includes('setup')) return -1; if (!a.tags.includes('setup') && b.tags.includes('setup')) return 1; // Then by priority const priorityOrder = { high: 3, medium: 2, low: 1 }; const priorityDiff = (priorityOrder[b.priority as keyof typeof priorityOrder] || 0) - (priorityOrder[a.priority as keyof typeof priorityOrder] || 0); if (priorityDiff !== 0) return priorityDiff; // Then by complexity (easier first) return a.complexity - b.complexity; }); sections.push( '## Suggested Implementation Order', ...orderedTasks.slice(0, 5).map((task, index) => `${index + 1}. ${task.title} (${task.complexity}/10, ${task.estimatedHours}h)` ), '' ); // Next steps sections.push( '## Next Steps', '1. Review the subtasks and adjust if needed', '2. Assign subtasks to team members based on skills', '3. Start with setup and high-priority tasks', '4. Use `update_task_lifecycle` to track progress on each subtask', '5. Consider creating GitHub issues for each subtask', '' ); // Related commands sections.push( '## Related Commands', '- `get_next_task` - Get recommendations for which subtask to work on first', '- `analyze_task_complexity` - Analyze individual subtasks if still too complex', '- `update_task_lifecycle` - Track progress on subtasks', '- `create_issue` - Create GitHub issues for subtasks' ); return sections.join('\n'); } // Tool definition export const expandTaskTool: ToolDefinition<ExpandTaskArgs> = { name: "expand_task", description: "Break down a complex task into smaller, manageable subtasks with AI-powered analysis, dependency detection, and implementation recommendations", schema: expandTaskSchema as unknown as ToolSchema<ExpandTaskArgs>, examples: [ { name: "Expand complex feature task", description: "Break down a complex feature into manageable subtasks", args: { taskTitle: "Implement user dashboard", taskDescription: "Create a comprehensive user dashboard with analytics, settings, notifications, and profile management", currentComplexity: 8, maxSubtasks: 6, maxDepth: 2, targetComplexity: 3, includeEstimates: true, includeDependencies: true, includeAcceptanceCriteria: true, projectType: "web-app", teamSkills: ["react", "typescript", "node.js"] } } ] }; // Export the execution function export { executeExpandTask };

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/kunwarVivek/mcp-github-project-manager'

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