Skip to main content
Glama
expandTask.ts30.6 kB
import { GitHubConfig } from '../../shared/types.js'; /** * AI-powered task expansion system * Breaks down complex tasks into manageable subtasks using intelligent decomposition */ interface SubTask { title: string; description: string; complexity: number; priority: 'high' | 'medium' | 'low'; dependencies: string[]; labels: string[]; assignee?: string; category: string; estimatedHours: number; acceptanceCriteria: string[]; } interface TaskBreakdown { originalTask: { number: number; title: string; complexity: number; labels: string[]; }; subtasks: SubTask[]; totalComplexity: number; recommendedApproach: string; riskAssessment: string[]; timeline: string; dependencies: { [key: string]: string[] }; } interface DecompositionTemplate { name: string; pattern: RegExp[]; subtaskTemplate: (context: any) => SubTask[]; description: string; } export async function expandTask(config: GitHubConfig, args: any) { const { owner, repo, octokit } = config; if (!owner || !repo) { throw new Error('GITHUB_OWNER and GITHUB_REPO environment variables are required'); } try { const issueNumber = args.issue_number; const createSubIssues = args.create_sub_issues !== false; const assignToSprint = args.assign_to_sprint === true; const targetMilestone = args.target_milestone; const maxSubtasks = args.max_subtasks || 8; const minComplexity = args.min_complexity || 1; const templateType = args.template_type || 'auto'; const includeChecklist = args.include_checklist !== false; if (!issueNumber) { throw new Error('issue_number is required'); } // Get the issue details const issueResponse = await octokit.rest.issues.get({ owner, repo, issue_number: issueNumber }); const issue = issueResponse.data; // Analyze the task complexity const originalComplexity = analyzeTaskComplexity(issue); if (originalComplexity < 3 && args.force !== true) { return { content: [{ type: "text", text: `⚠️ **Task Expansion Not Recommended**\n\n` + `Issue #${issueNumber}: "${issue.title}"\n\n` + `**Complexity Analysis:** ${originalComplexity} story points\n\n` + `This task has relatively low complexity and may not benefit from decomposition. ` + `Tasks with complexity < 3 points are usually better kept as single units of work.\n\n` + `**Recommendations:**\n` + `• Keep as a single task for atomic completion\n` + `• Add detailed acceptance criteria instead\n` + `• Consider pair programming if knowledge transfer is needed\n\n` + `Use \`force: true\` parameter to expand anyway.` }] }; } // Perform intelligent task decomposition const breakdown = await performTaskDecomposition(issue, { templateType, maxSubtasks, minComplexity, originalComplexity }); // Create sub-issues if requested let createdIssues: any[] = []; if (createSubIssues && breakdown.subtasks.length > 0) { createdIssues = await createSubTaskIssues(config, breakdown, { parentIssue: issue, assignToSprint, targetMilestone, includeChecklist }); } // Generate response let result = `🧩 **AI-Powered Task Expansion**\n\n`; result += `**Original Task:** #${issue.number} - ${issue.title}\n`; result += `**Original Complexity:** ${originalComplexity} story points\n`; result += `**Decomposed Into:** ${breakdown.subtasks.length} subtasks (${breakdown.totalComplexity} total points)\n\n`; // Task breakdown summary result += `## 📊 **Decomposition Summary**\n\n`; result += `**Approach:** ${breakdown.recommendedApproach}\n`; result += `**Timeline:** ${breakdown.timeline}\n`; result += `**Complexity Distribution:**\n`; const complexityDistribution = breakdown.subtasks.reduce((acc, task) => { acc[task.complexity] = (acc[task.complexity] || 0) + 1; return acc; }, {} as { [key: number]: number }); Object.entries(complexityDistribution).forEach(([complexity, count]) => { result += `• ${complexity} points: ${count} tasks\n`; }); result += `\n`; // Risk assessment if (breakdown.riskAssessment.length > 0) { result += `## ⚠️ **Risk Assessment**\n\n`; breakdown.riskAssessment.forEach(risk => { result += `• ${risk}\n`; }); result += `\n`; } // Subtasks breakdown result += `## 🔍 **Detailed Subtask Breakdown**\n\n`; // Group subtasks by category const categorizedTasks = breakdown.subtasks.reduce((acc, task) => { if (!acc[task.category]) acc[task.category] = []; acc[task.category].push(task); return acc; }, {} as { [category: string]: SubTask[] }); Object.entries(categorizedTasks).forEach(([category, tasks]) => { result += `### 📂 ${category}\n\n`; tasks.forEach((task, index) => { const priorityEmoji = task.priority === 'high' ? '🔴' : task.priority === 'medium' ? '🟡' : '🟢'; result += `#### ${index + 1}. ${priorityEmoji} ${task.title}\n\n`; result += `**Description:** ${task.description}\n\n`; result += `**Details:**\n`; result += `• 📊 Complexity: ${task.complexity} story points\n`; result += `• ⏱️ Estimated Hours: ${task.estimatedHours}h\n`; result += `• 🎯 Priority: ${task.priority}\n`; result += `• 🏷️ Labels: ${task.labels.join(', ')}\n`; if (task.assignee) { result += `• 👤 Suggested Assignee: ${task.assignee}\n`; } if (task.dependencies.length > 0) { result += `• 🔗 Dependencies: ${task.dependencies.join(', ')}\n`; } if (task.acceptanceCriteria.length > 0) { result += `\n**Acceptance Criteria:**\n`; task.acceptanceCriteria.forEach(criteria => { result += `- [ ] ${criteria}\n`; }); } result += `\n---\n\n`; }); }); // Dependencies visualization if (Object.keys(breakdown.dependencies).length > 0) { result += `## 🔗 **Task Dependencies**\n\n`; result += `**Dependency Chain:**\n`; Object.entries(breakdown.dependencies).forEach(([task, deps]) => { if (deps.length > 0) { result += `• "${task}" depends on: ${deps.join(', ')}\n`; } }); result += `\n`; } // Implementation plan result += `## 🎯 **Recommended Implementation Plan**\n\n`; // Sort tasks by dependencies and priority const implementationOrder = calculateImplementationOrder(breakdown.subtasks, breakdown.dependencies); result += `**Phase 1: Foundation** (Parallel work possible)\n`; implementationOrder.phase1.forEach(task => { result += `• ${task.title} (${task.complexity}sp)\n`; }); if (implementationOrder.phase2.length > 0) { result += `\n**Phase 2: Core Development** (After dependencies resolved)\n`; implementationOrder.phase2.forEach(task => { result += `• ${task.title} (${task.complexity}sp)\n`; }); } if (implementationOrder.phase3.length > 0) { result += `\n**Phase 3: Integration & Finalization**\n`; implementationOrder.phase3.forEach(task => { result += `• ${task.title} (${task.complexity}sp)\n`; }); } // Created issues summary if (createdIssues.length > 0) { result += `\n## ✅ **Created Sub-Issues**\n\n`; result += `Successfully created ${createdIssues.length} sub-issues:\n\n`; createdIssues.forEach(createdIssue => { result += `• #${createdIssue.number}: ${createdIssue.title}\n`; result += ` 🔗 ${createdIssue.html_url}\n`; }); result += `\n**Parent Issue Updated:** Added task breakdown checklist and references to sub-issues.\n`; } // Next steps result += `\n## 🚀 **Next Steps**\n\n`; result += `1. **Review the breakdown** and adjust complexity estimates if needed\n`; result += `2. **Assign team members** based on skill requirements and availability\n`; result += `3. **Start with Phase 1 tasks** that have no dependencies\n`; result += `4. **Set up regular check-ins** to track progress and resolve blockers\n`; if (!createSubIssues) { result += `5. **Create GitHub issues** for each subtask using \`create_sub_issues: true\`\n`; } if (breakdown.riskAssessment.length > 0) { result += `6. **Address identified risks** before starting implementation\n`; } return { content: [{ type: "text", text: result }] }; } catch (error: any) { throw new Error(`Failed to expand task: ${error.message}`); } } async function performTaskDecomposition(issue: any, options: any): Promise<TaskBreakdown> { const { templateType, maxSubtasks, minComplexity, originalComplexity } = options; // Analyze the issue content const issueContext = analyzeIssueContext(issue); // Select appropriate decomposition template const template = selectDecompositionTemplate(issue, templateType); // Generate subtasks using the template let subtasks = template.subtaskTemplate(issueContext); // Apply constraints subtasks = subtasks.slice(0, maxSubtasks); subtasks = subtasks.filter(task => task.complexity >= minComplexity); // Ensure total complexity is reasonable const totalComplexity = subtasks.reduce((sum, task) => sum + task.complexity, 0); if (totalComplexity > originalComplexity * 1.3) { // Adjust complexity to avoid inflation const adjustmentFactor = (originalComplexity * 1.1) / totalComplexity; subtasks.forEach(task => { task.complexity = Math.max(1, Math.round(task.complexity * adjustmentFactor)); }); } // Build dependency graph const dependencies = buildDependencyGraph(subtasks); // Generate recommendations const recommendedApproach = generateRecommendedApproach(template, issueContext); const riskAssessment = performRiskAssessment(issue, subtasks); const timeline = estimateTimeline(subtasks, dependencies); return { originalTask: { number: issue.number, title: issue.title, complexity: originalComplexity, labels: issue.labels.map((l: any) => l.name) }, subtasks, totalComplexity: subtasks.reduce((sum, task) => sum + task.complexity, 0), recommendedApproach, riskAssessment, timeline, dependencies }; } function analyzeIssueContext(issue: any) { const title = issue.title.toLowerCase(); const body = (issue.body || '').toLowerCase(); const labels = issue.labels.map((l: any) => l.name.toLowerCase()); const context = { isFeature: labels.includes('feature') || labels.includes('enhancement') || title.includes('add') || title.includes('implement'), isBug: labels.includes('bug') || title.includes('fix') || title.includes('error'), isRefactor: labels.includes('refactor') || title.includes('refactor') || title.includes('improve'), isInfrastructure: labels.includes('infrastructure') || labels.includes('devops') || title.includes('deploy'), isResearch: labels.includes('research') || labels.includes('spike') || title.includes('investigate'), isDocumentation: labels.includes('documentation') || title.includes('docs') || title.includes('readme'), isTesting: labels.includes('testing') || labels.includes('test') || title.includes('test'), // Technical areas isFrontend: labels.some(l => ['frontend', 'ui', 'ux'].includes(l)) || body.includes('frontend') || body.includes('ui'), isBackend: labels.some(l => ['backend', 'api', 'server'].includes(l)) || body.includes('backend') || body.includes('api'), isDatabase: labels.some(l => ['database', 'db'].includes(l)) || body.includes('database') || body.includes('sql'), isMobile: labels.some(l => ['mobile', 'ios', 'android'].includes(l)) || body.includes('mobile'), // Complexity indicators hasApiIntegration: body.includes('api') || body.includes('integration'), hasDataMigration: body.includes('migration') || body.includes('migrate'), hasSecurityRequirements: body.includes('security') || body.includes('auth'), hasPerformanceRequirements: body.includes('performance') || body.includes('optimization'), // Project structure title, body, labels, assignees: issue.assignees?.map((a: any) => a.login) || [], milestone: issue.milestone?.title }; return context; } function selectDecompositionTemplate(issue: any, templateType: string): DecompositionTemplate { const templates: DecompositionTemplate[] = [ // Feature Implementation Template { name: 'Feature Implementation', pattern: [/implement|add|create|build/i, /feature|functionality/i], description: 'Standard feature development workflow', subtaskTemplate: (context) => [ { title: 'Research and Requirements Analysis', description: `Research existing solutions and define detailed requirements for ${context.title}`, complexity: 2, priority: 'high' as const, dependencies: [], labels: ['research', 'requirements'], category: 'Planning', estimatedHours: 8, acceptanceCriteria: [ 'Requirements documented and reviewed', 'Technical approach decided', 'Dependencies identified' ] }, { title: 'Design and Architecture', description: `Design the architecture and user interface for ${context.title}`, complexity: 3, priority: 'high' as const, dependencies: ['Research and Requirements Analysis'], labels: ['design', 'architecture'], category: 'Design', estimatedHours: 12, acceptanceCriteria: [ 'Architecture diagram created', 'API endpoints defined', 'UI/UX mockups ready' ] }, { title: 'Core Implementation', description: `Implement the main functionality for ${context.title}`, complexity: 5, priority: 'medium' as const, dependencies: ['Design and Architecture'], labels: ['implementation', context.isFrontend ? 'frontend' : 'backend'], category: 'Development', estimatedHours: 20, acceptanceCriteria: [ 'Core functionality implemented', 'Basic error handling added', 'Code follows standards' ] }, { title: 'Testing and Validation', description: `Create comprehensive tests for ${context.title}`, complexity: 3, priority: 'medium' as const, dependencies: ['Core Implementation'], labels: ['testing', 'qa'], category: 'Quality Assurance', estimatedHours: 12, acceptanceCriteria: [ 'Unit tests written and passing', 'Integration tests added', 'Edge cases covered' ] }, { title: 'Documentation and Polish', description: `Document ${context.title} and add final polish`, complexity: 2, priority: 'low' as const, dependencies: ['Testing and Validation'], labels: ['documentation', 'polish'], category: 'Documentation', estimatedHours: 6, acceptanceCriteria: [ 'User documentation written', 'Code comments added', 'README updated' ] } ] }, // Bug Fix Template { name: 'Bug Fix', pattern: [/fix|bug|error|issue/i], description: 'Systematic bug investigation and resolution', subtaskTemplate: (context) => [ { title: 'Bug Investigation and Reproduction', description: `Investigate and consistently reproduce the bug described in ${context.title}`, complexity: 2, priority: 'high' as const, dependencies: [], labels: ['investigation', 'bug'], category: 'Analysis', estimatedHours: 6, acceptanceCriteria: [ 'Bug consistently reproduced', 'Steps to reproduce documented', 'Impact assessment completed' ] }, { title: 'Root Cause Analysis', description: `Identify the root cause of the bug and plan the fix`, complexity: 3, priority: 'high' as const, dependencies: ['Bug Investigation and Reproduction'], labels: ['analysis', 'debugging'], category: 'Analysis', estimatedHours: 8, acceptanceCriteria: [ 'Root cause identified', 'Fix strategy planned', 'Potential side effects assessed' ] }, { title: 'Implement Fix', description: `Implement the fix for ${context.title}`, complexity: 3, priority: 'medium' as const, dependencies: ['Root Cause Analysis'], labels: ['fix', 'implementation'], category: 'Development', estimatedHours: 10, acceptanceCriteria: [ 'Fix implemented and tested', 'No new bugs introduced', 'Code reviewed' ] }, { title: 'Regression Testing', description: `Perform thorough regression testing to ensure the fix works correctly`, complexity: 2, priority: 'medium' as const, dependencies: ['Implement Fix'], labels: ['testing', 'regression'], category: 'Quality Assurance', estimatedHours: 6, acceptanceCriteria: [ 'Original bug resolved', 'No regression detected', 'Test cases added for prevention' ] } ] }, // Refactoring Template { name: 'Refactoring', pattern: [/refactor|improve|optimize|cleanup/i], description: 'Systematic code improvement and optimization', subtaskTemplate: (context) => [ { title: 'Code Analysis and Planning', description: `Analyze current code and plan refactoring approach for ${context.title}`, complexity: 2, priority: 'medium' as const, dependencies: [], labels: ['analysis', 'planning'], category: 'Planning', estimatedHours: 8, acceptanceCriteria: [ 'Current code analyzed', 'Refactoring plan documented', 'Risks identified' ] }, { title: 'Create Comprehensive Tests', description: `Create tests to ensure refactoring doesn't break functionality`, complexity: 3, priority: 'high' as const, dependencies: ['Code Analysis and Planning'], labels: ['testing', 'safety'], category: 'Quality Assurance', estimatedHours: 12, acceptanceCriteria: [ 'Comprehensive test coverage', 'All tests passing', 'Edge cases covered' ] }, { title: 'Implement Refactoring', description: `Perform the actual refactoring of ${context.title}`, complexity: 4, priority: 'medium' as const, dependencies: ['Create Comprehensive Tests'], labels: ['refactor', 'implementation'], category: 'Development', estimatedHours: 16, acceptanceCriteria: [ 'Code refactored as planned', 'All tests still passing', 'Performance maintained or improved' ] }, { title: 'Documentation Update', description: `Update documentation to reflect refactored code`, complexity: 1, priority: 'low' as const, dependencies: ['Implement Refactoring'], labels: ['documentation'], category: 'Documentation', estimatedHours: 4, acceptanceCriteria: [ 'Code comments updated', 'Architecture docs updated', 'API docs reflect changes' ] } ] } ]; // Auto-select template based on issue context if (templateType === 'auto') { const context = analyzeIssueContext(issue); if (context.isBug) { return templates.find(t => t.name === 'Bug Fix')!; } else if (context.isRefactor) { return templates.find(t => t.name === 'Refactoring')!; } else { return templates.find(t => t.name === 'Feature Implementation')!; } } // Find template by name const selectedTemplate = templates.find(t => t.name.toLowerCase().includes(templateType.toLowerCase()) ); return selectedTemplate || templates[0]; // Default to Feature Implementation } function buildDependencyGraph(subtasks: SubTask[]): { [key: string]: string[] } { const dependencies: { [key: string]: string[] } = {}; subtasks.forEach(task => { if (task.dependencies.length > 0) { dependencies[task.title] = task.dependencies; } }); return dependencies; } function generateRecommendedApproach(template: DecompositionTemplate, context: any): string { const approaches = { 'Feature Implementation': 'Incremental development with continuous testing and validation', 'Bug Fix': 'Systematic investigation followed by careful implementation and thorough testing', 'Refactoring': 'Test-driven refactoring with comprehensive safety checks' }; let approach = approaches[template.name as keyof typeof approaches] || 'Standard iterative development'; // Add context-specific recommendations if (context.hasApiIntegration) { approach += '. Include API contract testing and mocking.'; } if (context.hasSecurityRequirements) { approach += ' Pay special attention to security testing and code review.'; } if (context.hasPerformanceRequirements) { approach += ' Include performance benchmarking and optimization.'; } return approach; } function performRiskAssessment(issue: any, subtasks: SubTask[]): string[] { const risks: string[] = []; const context = analyzeIssueContext(issue); // Complexity risks const totalComplexity = subtasks.reduce((sum, task) => sum + task.complexity, 0); if (totalComplexity > 15) { risks.push('High total complexity may lead to scope creep or timeline overruns'); } // Dependency risks const hasManyDependencies = subtasks.some(task => task.dependencies.length > 2); if (hasManyDependencies) { risks.push('Complex dependencies may cause bottlenecks and coordination issues'); } // Technical risks if (context.hasApiIntegration) { risks.push('API integration may face rate limiting or external service downtime'); } if (context.hasDataMigration) { risks.push('Data migration carries risk of data loss or corruption'); } if (context.hasSecurityRequirements) { risks.push('Security implementation requires careful review to avoid vulnerabilities'); } // Resource risks if (context.assignees.length === 0) { risks.push('No assignees identified - resource allocation may be unclear'); } if (context.assignees.length === 1 && totalComplexity > 8) { risks.push('Single assignee for complex task may create knowledge silos'); } return risks; } function estimateTimeline(subtasks: SubTask[], dependencies: { [key: string]: string[] }): string { const totalHours = subtasks.reduce((sum, task) => sum + task.estimatedHours, 0); const totalDays = Math.ceil(totalHours / 6); // Assuming 6 productive hours per day // Calculate critical path const criticalPathLength = calculateCriticalPathLength(subtasks, dependencies); const parallelEfficiency = Math.max(0.6, 1 - (criticalPathLength / subtasks.length)); const adjustedDays = Math.ceil(totalDays * parallelEfficiency); if (adjustedDays <= 3) { return `3-5 days (${totalHours} hours total)`; } else if (adjustedDays <= 7) { return `1-2 weeks (${totalHours} hours total)`; } else if (adjustedDays <= 14) { return `2-3 weeks (${totalHours} hours total)`; } else { return `3-4 weeks (${totalHours} hours total)`; } } function calculateCriticalPathLength(subtasks: SubTask[], dependencies: { [key: string]: string[] }): number { // Simplified critical path calculation const taskMap = new Map(subtasks.map(task => [task.title, task])); const visited = new Set<string>(); function dfs(taskTitle: string): number { if (visited.has(taskTitle)) return 0; visited.add(taskTitle); const task = taskMap.get(taskTitle); if (!task) return 0; const deps = dependencies[taskTitle] || []; const maxDepPath = Math.max(0, ...deps.map(dep => dfs(dep))); return 1 + maxDepPath; } return Math.max(...subtasks.map(task => dfs(task.title))); } function calculateImplementationOrder(subtasks: SubTask[], dependencies: { [key: string]: string[] }) { const taskMap = new Map(subtasks.map(task => [task.title, task])); const phase1: SubTask[] = []; const phase2: SubTask[] = []; const phase3: SubTask[] = []; // Phase 1: No dependencies or research/planning tasks subtasks.forEach(task => { const deps = dependencies[task.title] || []; if (deps.length === 0 || task.category === 'Planning' || task.category === 'Analysis') { phase1.push(task); } }); // Phase 2: Dependent on Phase 1 subtasks.forEach(task => { if (phase1.includes(task)) return; const deps = dependencies[task.title] || []; const allDepsInPhase1 = deps.every(dep => phase1.some(p1Task => p1Task.title === dep) ); if (allDepsInPhase1 && deps.length > 0) { phase2.push(task); } }); // Phase 3: Everything else subtasks.forEach(task => { if (!phase1.includes(task) && !phase2.includes(task)) { phase3.push(task); } }); return { phase1, phase2, phase3 }; } async function createSubTaskIssues( config: GitHubConfig, breakdown: TaskBreakdown, options: any ): Promise<any[]> { const { octokit, owner, repo } = config; const { parentIssue, assignToSprint, targetMilestone, includeChecklist } = options; const createdIssues: any[] = []; // Create issues for each subtask for (const subtask of breakdown.subtasks) { const issueBody = buildSubTaskIssueBody(subtask, parentIssue, includeChecklist); const createIssueResponse = await octokit.rest.issues.create({ owner, repo, title: `${subtask.title} (Part of #${parentIssue.number})`, body: issueBody, labels: [...subtask.labels, 'subtask', `complexity-${subtask.complexity}`], assignees: subtask.assignee ? [subtask.assignee] : undefined, milestone: targetMilestone || parentIssue.milestone?.number }); createdIssues.push(createIssueResponse.data); } // Update parent issue with subtask references if (includeChecklist) { await updateParentIssueWithChecklist(config, parentIssue, createdIssues, breakdown); } return createdIssues; } function buildSubTaskIssueBody(subtask: SubTask, parentIssue: any, includeChecklist: boolean): string { let body = `**Parent Issue:** #${parentIssue.number}\n\n`; body += `**Description:** ${subtask.description}\n\n`; body += `**Category:** ${subtask.category}\n`; body += `**Complexity:** ${subtask.complexity} story points\n`; body += `**Estimated Hours:** ${subtask.estimatedHours}h\n`; body += `**Priority:** ${subtask.priority}\n\n`; if (subtask.dependencies.length > 0) { body += `**Dependencies:**\n`; subtask.dependencies.forEach(dep => { body += `- ${dep}\n`; }); body += `\n`; } if (subtask.acceptanceCriteria.length > 0) { body += `**Acceptance Criteria:**\n`; subtask.acceptanceCriteria.forEach(criteria => { body += `- [ ] ${criteria}\n`; }); body += `\n`; } body += `---\n`; body += `*This is a subtask generated by AI-powered task expansion.*`; return body; } async function updateParentIssueWithChecklist( config: GitHubConfig, parentIssue: any, createdIssues: any[], breakdown: TaskBreakdown ): Promise<void> { const { octokit, owner, repo } = config; let checklistSection = `\n\n---\n\n## 🧩 **Task Breakdown Checklist**\n\n`; checklistSection += `*This task has been decomposed into ${createdIssues.length} subtasks:*\n\n`; createdIssues.forEach(issue => { checklistSection += `- [ ] #${issue.number}: ${issue.title}\n`; }); checklistSection += `\n**Total Complexity:** ${breakdown.totalComplexity} story points\n`; checklistSection += `**Timeline:** ${breakdown.timeline}\n`; const updatedBody = (parentIssue.body || '') + checklistSection; await octokit.rest.issues.update({ owner, repo, issue_number: parentIssue.number, body: updatedBody, labels: [...parentIssue.labels.map((l: any) => l.name), 'expanded', 'parent-task'] }); } function analyzeTaskComplexity(issue: any): number { let complexity = 1; // Title complexity const titleWords = issue.title.split(' ').length; if (titleWords > 10) complexity += 1; // Body complexity if (issue.body) { const bodyLength = issue.body.length; if (bodyLength > 1000) complexity += 2; else if (bodyLength > 500) complexity += 1; // Technical keywords const technicalKeywords = [ 'api', 'database', 'migration', 'refactor', 'architecture', 'integration', 'security', 'performance', 'scalability' ]; const techCount = technicalKeywords.filter(keyword => issue.body.toLowerCase().includes(keyword) ).length; complexity += Math.min(techCount, 3); } // Label complexity const complexityLabels = issue.labels.filter((label: any) => ['epic', 'large', 'complex', 'research', 'spike'].some(keyword => label.name.toLowerCase().includes(keyword) ) ); complexity += complexityLabels.length; return Math.min(complexity, 8); // Cap at 8 story points }

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

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