Skip to main content
Glama

mcp-github-project-manager

FeatureManagementService.ts22.3 kB
import { generateText, generateObject } from 'ai'; import { z } from 'zod'; import { AIServiceFactory } from './ai/AIServiceFactory.js'; import { FeatureAdditionRequest, FeatureExpansionResult, TaskLifecycleState, ProjectFeatureRoadmap, FeatureRequirement, AITask, PRDDocument, TaskPriority, TaskStatus, TaskComplexity, AITaskSchema, FeatureRequirementSchema, FeatureAdditionRequestSchema } from '../domain/ai-types.js'; import { FEATURE_PROMPT_CONFIGS, formatFeaturePrompt } from './ai/prompts/FeatureAdditionPrompts.js'; import { TaskGenerationService } from './TaskGenerationService.js'; import { PRDGenerationService } from './PRDGenerationService.js'; import { v4 as uuidv4 } from 'uuid'; /** * Service for managing feature additions and complete task lifecycle */ export class FeatureManagementService { private aiFactory: AIServiceFactory; private taskService: TaskGenerationService; private prdService: PRDGenerationService; constructor() { this.aiFactory = AIServiceFactory.getInstance(); this.taskService = new TaskGenerationService(); this.prdService = new PRDGenerationService(); } /** * Analyze a new feature request */ async analyzeFeatureRequest(params: { featureIdea: string; description: string; existingPRD?: PRDDocument; projectState?: any; businessJustification?: string; targetUsers?: string[]; requestedBy: string; }): Promise<{ analysis: string; recommendation: 'approve' | 'reject' | 'modify'; priority: TaskPriority; complexity: TaskComplexity; estimatedEffort: number; risks: string[]; dependencies: string[]; }> { try { const config = FEATURE_PROMPT_CONFIGS.analyzeRequest; const model = this.aiFactory.getMainModel() || this.aiFactory.getBestAvailableModel(); if (!model) { throw new Error('AI service is not available. Please configure at least one AI provider (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY, or PERPLEXITY_API_KEY).'); } const prompt = formatFeaturePrompt(config.userPrompt, { featureIdea: params.featureIdea, description: params.description, existingPRD: params.existingPRD ? JSON.stringify(params.existingPRD, null, 2) : 'No existing PRD provided', projectState: params.projectState ? JSON.stringify(params.projectState) : 'No project state provided', businessJustification: params.businessJustification || 'No business justification provided', targetUsers: params.targetUsers?.join(', ') || 'General users' }); const result = await generateText({ model, system: config.systemPrompt, prompt, maxTokens: config.maxTokens, temperature: config.temperature }); // Parse the analysis (in a real implementation, use structured output) const analysis = result.text; // Extract key information (simplified - would use structured output in production) const recommendation = this.extractRecommendation(analysis); const priority = this.extractPriority(analysis); const complexity = this.extractComplexity(analysis); return { analysis, recommendation, priority, complexity, estimatedEffort: complexity * 8, // 8 hours per complexity point risks: this.extractRisks(analysis), dependencies: this.extractDependencies(analysis) }; } catch (error) { process.stderr.write(`Error analyzing feature request: ${error instanceof Error ? error.message : String(error)}\n`); throw new Error(`Failed to analyze feature request: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Add a new feature to an existing PRD */ async addFeatureToPRD(params: { featureRequest: FeatureAdditionRequest; targetPRD: PRDDocument; autoApprove?: boolean; }): Promise<{ updatedPRD: PRDDocument; newFeature: FeatureRequirement; impactAssessment: string; }> { try { // First analyze the feature request const analysis = await this.analyzeFeatureRequest({ featureIdea: params.featureRequest.featureIdea, description: params.featureRequest.description, existingPRD: params.targetPRD, businessJustification: params.featureRequest.businessJustification, targetUsers: params.featureRequest.targetUsers, requestedBy: params.featureRequest.requestedBy }); // Check if feature should be approved if (!params.autoApprove && analysis.recommendation !== 'approve') { throw new Error(`Feature request not approved: ${analysis.analysis}`); } // Create the new feature const newFeature: FeatureRequirement = { id: uuidv4(), title: params.featureRequest.featureIdea, description: params.featureRequest.description, priority: analysis.priority, userStories: [ `As a user, I want ${params.featureRequest.featureIdea.toLowerCase()} so that I can achieve my goals more effectively` ], acceptanceCriteria: [ 'Feature is implemented according to specifications', 'Feature integrates seamlessly with existing functionality', 'Feature passes all quality gates' ], estimatedComplexity: analysis.complexity, dependencies: analysis.dependencies }; // Update the PRD with the new feature const updatedPRD: PRDDocument = { ...params.targetPRD, features: [...params.targetPRD.features, newFeature], updatedAt: new Date().toISOString(), version: this.incrementVersion(params.targetPRD.version) }; // Assess impact on existing features const impactAssessment = await this.assessFeatureImpact({ newFeature, existingFeatures: params.targetPRD.features, systemContext: params.targetPRD.technicalRequirements }); return { updatedPRD, newFeature, impactAssessment }; } catch (error) { process.stderr.write(`Error adding feature to PRD: ${error instanceof Error ? error.message : String(error)}\n`); throw new Error(`Failed to add feature to PRD: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Expand a feature into complete task breakdown */ async expandFeatureToTasks(params: { feature: FeatureRequirement; systemContext?: any; integrationPoints?: string[]; teamSkills?: string[]; }): Promise<FeatureExpansionResult> { try { const config = FEATURE_PROMPT_CONFIGS.expandToTasks; const model = this.aiFactory.getMainModel() || this.aiFactory.getBestAvailableModel(); if (!model) { throw new Error('AI service is not available. Please configure at least one AI provider (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY, or PERPLEXITY_API_KEY).'); } const prompt = formatFeaturePrompt(config.userPrompt, { featureTitle: params.feature.title, featureDescription: params.feature.description, userStories: params.feature.userStories.join('\n'), acceptanceCriteria: params.feature.acceptanceCriteria.join('\n'), systemContext: params.systemContext ? JSON.stringify(params.systemContext) : 'No system context provided', integrationPoints: params.integrationPoints?.join(', ') || 'No specific integration points' }); const result = await generateObject({ model, system: config.systemPrompt, prompt, schema: z.array(AITaskSchema), maxTokens: config.maxTokens, temperature: config.temperature }); // Enrich tasks with metadata const tasks = result.object.map(task => ({ ...task, id: task.id || uuidv4(), status: TaskStatus.PENDING, aiGenerated: true, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), sourcePRD: `feature-${params.feature.id}`, tags: [...(task.tags || []), 'feature-expansion', `feature-${params.feature.id}`] })); // Detect dependencies between tasks const tasksWithDependencies = await this.taskService.detectTaskDependencies(tasks); // Calculate total effort const totalEffort = tasksWithDependencies.reduce((sum, task) => sum + task.estimatedHours, 0); // Assess risks const riskAssessment = this.assessImplementationRisks(tasksWithDependencies, params.feature); return { feature: params.feature, tasks: tasksWithDependencies, dependencies: this.extractTaskDependencies(tasksWithDependencies), estimatedEffort: totalEffort, suggestedMilestone: this.suggestMilestone(totalEffort, params.feature.priority), riskAssessment }; } catch (error) { process.stderr.write(`Error expanding feature to tasks: ${error instanceof Error ? error.message : String(error)}\n`); throw new Error(`Failed to expand feature to tasks: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Create complete feature lifecycle from idea to implementation */ async createCompleteFeatureLifecycle(params: { featureIdea: string; description: string; targetPRD?: PRDDocument; targetProject?: string; requestedBy: string; businessJustification?: string; autoApprove?: boolean; }): Promise<{ featureRequest: FeatureAdditionRequest; analysis: any; updatedPRD?: PRDDocument; expansionResult: FeatureExpansionResult; lifecycleStates: TaskLifecycleState[]; roadmapUpdate?: ProjectFeatureRoadmap; }> { try { // Step 1: Create feature request const featureRequest: FeatureAdditionRequest = { id: uuidv4(), featureIdea: params.featureIdea, description: params.description, targetPRD: params.targetPRD?.id, targetProject: params.targetProject, requestedBy: params.requestedBy, businessJustification: params.businessJustification, createdAt: new Date().toISOString(), status: 'pending' }; // Step 2: Analyze the feature request const analysis = await this.analyzeFeatureRequest({ featureIdea: params.featureIdea, description: params.description, existingPRD: params.targetPRD, businessJustification: params.businessJustification, requestedBy: params.requestedBy }); // Step 3: Add to PRD if approved and PRD exists let updatedPRD: PRDDocument | undefined; let newFeature: FeatureRequirement; if (params.targetPRD && (params.autoApprove || analysis.recommendation === 'approve')) { const prdResult = await this.addFeatureToPRD({ featureRequest, targetPRD: params.targetPRD, autoApprove: params.autoApprove }); updatedPRD = prdResult.updatedPRD; newFeature = prdResult.newFeature; } else { // Create standalone feature newFeature = { id: uuidv4(), title: params.featureIdea, description: params.description, priority: analysis.priority, userStories: [`As a user, I want ${params.featureIdea.toLowerCase()}`], acceptanceCriteria: ['Feature meets requirements'], estimatedComplexity: analysis.complexity, dependencies: analysis.dependencies }; } // Step 4: Expand feature to tasks const expansionResult = await this.expandFeatureToTasks({ feature: newFeature }); // Step 5: Create lifecycle states for all tasks const lifecycleStates = expansionResult.tasks.map(task => this.createInitialTaskLifecycleState(task) ); // Step 6: Update roadmap if needed let roadmapUpdate: ProjectFeatureRoadmap | undefined; if (params.targetProject) { roadmapUpdate = await this.updateProjectRoadmap({ projectId: params.targetProject, newFeature, estimatedEffort: expansionResult.estimatedEffort }); } return { featureRequest, analysis, updatedPRD, expansionResult, lifecycleStates, roadmapUpdate }; } catch (error) { process.stderr.write(`Error creating complete feature lifecycle: ${error instanceof Error ? error.message : String(error)}\n`); throw new Error(`Failed to create feature lifecycle: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Track and update task lifecycle state */ async updateTaskLifecycle(params: { taskId: string; currentState: TaskLifecycleState; updateData: { phase?: string; status?: string; assignee?: string; notes?: string; artifacts?: string[]; blockers?: any[]; }; }): Promise<TaskLifecycleState> { try { const updatedState = { ...params.currentState }; // Update the specific phase if provided if (params.updateData.phase && params.updateData.status) { const phase = params.updateData.phase as keyof typeof updatedState.phases; if (updatedState.phases[phase]) { updatedState.phases[phase] = { ...updatedState.phases[phase], status: params.updateData.status as any, assignee: params.updateData.assignee, notes: params.updateData.notes, artifacts: params.updateData.artifacts || updatedState.phases[phase].artifacts }; // Update timestamps if (params.updateData.status === 'in_progress' && !updatedState.phases[phase].startedAt) { updatedState.phases[phase].startedAt = new Date().toISOString(); } if (params.updateData.status === 'completed') { updatedState.phases[phase].completedAt = new Date().toISOString(); } } } // Update blockers if provided if (params.updateData.blockers) { updatedState.blockers = params.updateData.blockers; } // Recalculate progress updatedState.progressPercentage = this.calculateTaskProgress(updatedState); // Update current phase based on progress updatedState.currentPhase = this.determineCurrentPhase(updatedState); return updatedState; } catch (error) { process.stderr.write(`Error updating task lifecycle: ${error instanceof Error ? error.message : String(error)}\n`); throw new Error(`Failed to update task lifecycle: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Get next recommended actions for a task */ async getNextTaskActions(taskLifecycle: TaskLifecycleState): Promise<{ nextActions: string[]; blockers: string[]; recommendations: string[]; estimatedCompletion: string; }> { try { const config = FEATURE_PROMPT_CONFIGS.trackLifecycle; const model = this.aiFactory.getMainModel() || this.aiFactory.getBestAvailableModel(); if (!model) { throw new Error('AI service is not available. Please configure at least one AI provider (ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY, or PERPLEXITY_API_KEY).'); } const prompt = formatFeaturePrompt(config.userPrompt, { taskTitle: `Task ${taskLifecycle.taskId}`, currentPhase: taskLifecycle.currentPhase, progressData: JSON.stringify(taskLifecycle.phases), blockers: JSON.stringify(taskLifecycle.blockers), teamContext: 'Standard development team' }); const result = await generateText({ model, system: config.systemPrompt, prompt, maxTokens: config.maxTokens, temperature: config.temperature }); // Parse recommendations (simplified - would use structured output in production) const analysis = result.text; return { nextActions: this.extractNextActions(analysis), blockers: taskLifecycle.blockers.map(b => b.description), recommendations: this.extractRecommendations(analysis), estimatedCompletion: this.calculateEstimatedCompletion(taskLifecycle) }; } catch (error) { process.stderr.write(`Error getting next task actions: ${error instanceof Error ? error.message : String(error)}\n`); throw new Error(`Failed to get next actions: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Private helper methods private extractRecommendation(analysis: string): 'approve' | 'reject' | 'modify' { if (analysis.toLowerCase().includes('approve')) return 'approve'; if (analysis.toLowerCase().includes('reject')) return 'reject'; return 'modify'; } private extractPriority(analysis: string): TaskPriority { if (analysis.toLowerCase().includes('critical')) return TaskPriority.CRITICAL; if (analysis.toLowerCase().includes('high')) return TaskPriority.HIGH; if (analysis.toLowerCase().includes('low')) return TaskPriority.LOW; return TaskPriority.MEDIUM; } private extractComplexity(analysis: string): TaskComplexity { const match = analysis.match(/complexity.*?(\d+)/i); if (match) { const complexity = parseInt(match[1]); return Math.min(Math.max(complexity, 1), 10) as TaskComplexity; } return 5; } private extractRisks(analysis: string): string[] { // Simplified risk extraction return ['Technical complexity', 'Integration challenges', 'Resource constraints']; } private extractDependencies(analysis: string): string[] { // Simplified dependency extraction return []; } private incrementVersion(version: string): string { const parts = version.split('.'); if (parts.length === 3) { const minor = parseInt(parts[1]) + 1; return `${parts[0]}.${minor}.0`; } return version; } private async assessFeatureImpact(params: { newFeature: FeatureRequirement; existingFeatures: FeatureRequirement[]; systemContext: any; }): Promise<string> { // Simplified impact assessment return `Adding ${params.newFeature.title} will require integration with ${params.existingFeatures.length} existing features.`; } private assessImplementationRisks(tasks: AITask[], feature: FeatureRequirement): { level: 'low' | 'medium' | 'high'; factors: string[]; mitigations: string[]; } { const highComplexityTasks = tasks.filter(t => t.complexity >= 7).length; const totalTasks = tasks.length; let level: 'low' | 'medium' | 'high' = 'low'; if (highComplexityTasks > totalTasks * 0.3) level = 'high'; else if (highComplexityTasks > totalTasks * 0.1) level = 'medium'; return { level, factors: [ `${highComplexityTasks} high-complexity tasks out of ${totalTasks}`, `Feature complexity: ${feature.estimatedComplexity}/10` ], mitigations: [ 'Break down complex tasks further', 'Assign experienced developers to high-risk tasks', 'Implement comprehensive testing strategy' ] }; } private extractTaskDependencies(tasks: AITask[]): any[] { return tasks.flatMap(task => task.dependencies); } private suggestMilestone(effort: number, priority: TaskPriority): string { if (priority === TaskPriority.CRITICAL) return 'Current Sprint'; if (effort <= 40) return 'Next Sprint'; if (effort <= 120) return 'Current Quarter'; return 'Next Quarter'; } private createInitialTaskLifecycleState(task: AITask): TaskLifecycleState { const basePhase = { status: 'not_started' as const, startedAt: undefined, completedAt: undefined, assignee: undefined, notes: undefined, artifacts: [] }; return { taskId: task.id, currentPhase: 'planning', phases: { planning: { ...basePhase }, development: { ...basePhase }, testing: { ...basePhase }, review: { ...basePhase }, deployment: { ...basePhase } }, blockers: [], progressPercentage: 0, estimatedCompletion: new Date(Date.now() + task.estimatedHours * 60 * 60 * 1000).toISOString() }; } private calculateTaskProgress(state: TaskLifecycleState): number { const phases = Object.values(state.phases); const completedPhases = phases.filter(p => p.status === 'completed').length; return Math.round((completedPhases / phases.length) * 100); } private determineCurrentPhase(state: TaskLifecycleState): TaskLifecycleState['currentPhase'] { const phaseOrder: (keyof TaskLifecycleState['phases'])[] = ['planning', 'development', 'testing', 'review', 'deployment']; for (const phase of phaseOrder) { if (state.phases[phase].status !== 'completed') { return phase; } } return 'completed'; } private async updateProjectRoadmap(params: { projectId: string; newFeature: FeatureRequirement; estimatedEffort: number; }): Promise<ProjectFeatureRoadmap> { // Simplified roadmap update return { projectId: params.projectId, features: { current: [], planned: [params.newFeature], backlog: [] }, timeline: { quarters: { 'Q1-2024': { features: [params.newFeature.id], themes: ['Feature Enhancement'], goals: ['Implement new feature'] } } }, dependencies: {} }; } private extractNextActions(analysis: string): string[] { return ['Review requirements', 'Start implementation', 'Set up testing environment']; } private extractRecommendations(analysis: string): string[] { return ['Focus on core functionality first', 'Implement comprehensive testing', 'Plan for gradual rollout']; } private calculateEstimatedCompletion(state: TaskLifecycleState): string { // Simple calculation based on current progress const remainingWork = (100 - state.progressPercentage) / 100; const estimatedDays = remainingWork * 5; // Assume 5 days total work return new Date(Date.now() + estimatedDays * 24 * 60 * 60 * 1000).toISOString(); } }

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