UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

775 lines (774 loc) 34.7 kB
import { getStorageManager } from '../storage/storage-manager.js'; import { getVibeTaskManagerConfig } from '../../utils/config-loader.js'; import { getIdGenerator } from '../../utils/id-generator.js'; import logger from '../../../../logger.js'; export class ProjectOperations { static instance; constructor() { } static getInstance() { if (!ProjectOperations.instance) { ProjectOperations.instance = new ProjectOperations(); } return ProjectOperations.instance; } resolveProjectRootPath(providedPath) { if (providedPath && providedPath !== '/' && providedPath.length > 1) { return providedPath; } const envProjectPath = process.env.VIBE_TASK_MANAGER_READ_DIR; if (envProjectPath && envProjectPath !== '/' && envProjectPath.length > 1) { return envProjectPath; } const cwd = process.cwd(); logger.debug({ providedPath, envProjectPath, cwd }, 'Project root path resolution completed'); return cwd; } async createProject(params, createdBy = 'system') { try { logger.info({ projectName: params.name, createdBy }, 'Creating new project'); const validationResult = this.validateCreateParams(params); if (!validationResult.valid) { return { success: false, error: `Project creation validation failed: ${validationResult.errors.join(', ')}`, metadata: { filePath: 'project-operations', operation: 'create_project', timestamp: new Date() } }; } const config = await getVibeTaskManagerConfig(); if (!config) { return { success: false, error: 'Failed to load task manager configuration', metadata: { filePath: 'project-operations', operation: 'create_project', timestamp: new Date() } }; } const idGenerator = getIdGenerator(); const idResult = await idGenerator.generateProjectId(params.name); if (!idResult.success) { return { success: false, error: `Failed to generate project ID: ${idResult.error}`, metadata: { filePath: 'project-operations', operation: 'create_project', timestamp: new Date() } }; } const projectId = idResult.id; const agentResult = await this.determineOptimalAgentConfig(params, config); const defaultConfig = { maxConcurrentTasks: config.taskManager.maxConcurrentTasks, defaultTaskTemplate: config.taskManager.defaultTaskTemplate, agentConfig: { maxAgents: agentResult.maxAgents, defaultAgent: agentResult.defaultAgent, agentCapabilities: agentResult.agentCapabilities }, performanceTargets: { maxResponseTime: config.taskManager.performanceTargets.maxResponseTime, maxMemoryUsage: config.taskManager.performanceTargets.maxMemoryUsage, minTestCoverage: config.taskManager.performanceTargets.minTestCoverage }, integrationSettings: { codeMapEnabled: true, researchEnabled: true, notificationsEnabled: true }, fileSystemSettings: { cacheSize: 100, cacheTTL: 3600, backupEnabled: true } }; const projectConfig = { ...defaultConfig, ...params.config, agentConfig: { ...defaultConfig.agentConfig, ...params.config?.agentConfig }, performanceTargets: { ...defaultConfig.performanceTargets, ...params.config?.performanceTargets }, integrationSettings: { ...defaultConfig.integrationSettings, ...params.config?.integrationSettings }, fileSystemSettings: { ...defaultConfig.fileSystemSettings, ...params.config?.fileSystemSettings } }; const finalTechStack = { languages: params.techStack?.languages?.length ? params.techStack.languages : (agentResult.detectedTechStack?.languages || []), frameworks: params.techStack?.frameworks?.length ? params.techStack.frameworks : (agentResult.detectedTechStack?.frameworks || []), tools: params.techStack?.tools?.length ? params.techStack.tools : (agentResult.detectedTechStack?.tools || []) }; const project = { id: projectId, name: params.name, description: params.description, status: 'pending', config: projectConfig, epicIds: [], rootPath: this.resolveProjectRootPath(params.rootPath), techStack: finalTechStack, metadata: { createdAt: new Date(), updatedAt: new Date(), createdBy, tags: params.tags || [], version: '1.0.0' } }; const storageManager = await getStorageManager(); const createResult = await storageManager.createProject(project); if (!createResult.success) { return { success: false, error: `Failed to save project: ${createResult.error}`, metadata: createResult.metadata }; } logger.info({ projectId, projectName: params.name }, 'Project created successfully'); try { const { getEpicService } = await import('../../services/epic-service.js'); const epicService = getEpicService(); const epicResult = await epicService.createEpic({ title: `${params.name} Main Development`, description: `Main epic for ${params.name} project. All initial tasks will be organized under this epic.`, projectId: projectId, priority: 'high', functionalArea: 'data-management', tags: ['main-epic', 'auto-generated'] }, createdBy); if (epicResult.success) { logger.info({ projectId, epicId: epicResult.data.id, epicTitle: epicResult.data.title }, 'Main epic created for project'); const project = createResult.data; project.epicIds.push(epicResult.data.id); } else { logger.warn({ projectId, error: epicResult.error }, 'Failed to create main epic for project - project creation will continue'); } } catch (error) { logger.warn({ err: error, projectId }, 'Error creating main epic - project creation will continue'); } return { success: true, data: createResult.data, metadata: { filePath: 'project-operations', operation: 'create_project', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, projectName: params.name }, 'Failed to create project'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'project-operations', operation: 'create_project', timestamp: new Date() } }; } } async getProject(projectId) { try { logger.debug({ projectId }, 'Getting project'); const storageManager = await getStorageManager(); return await storageManager.getProject(projectId); } catch (error) { logger.error({ err: error, projectId }, 'Failed to get project'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'project-operations', operation: 'get_project', timestamp: new Date() } }; } } async updateProject(projectId, params, updatedBy = 'system') { try { logger.info({ projectId, updates: Object.keys(params), updatedBy }, 'Updating project'); const validationResult = this.validateUpdateParams(params); if (!validationResult.valid) { return { success: false, error: `Project update validation failed: ${validationResult.errors.join(', ')}`, metadata: { filePath: 'project-operations', operation: 'update_project', timestamp: new Date() } }; } const storageManager = await getStorageManager(); const existingResult = await storageManager.getProject(projectId); if (!existingResult.success) { return { success: false, error: `Project not found: ${existingResult.error}`, metadata: existingResult.metadata }; } const existingProject = existingResult.data; const updates = { ...params, metadata: { ...existingProject.metadata, updatedAt: new Date(), ...(params.tags && { tags: params.tags }) } }; const updateResult = await storageManager.updateProject(projectId, updates); if (!updateResult.success) { return { success: false, error: `Failed to update project: ${updateResult.error}`, metadata: updateResult.metadata }; } logger.info({ projectId }, 'Project updated successfully'); return { success: true, data: updateResult.data, metadata: { filePath: 'project-operations', operation: 'update_project', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, projectId }, 'Failed to update project'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'project-operations', operation: 'update_project', timestamp: new Date() } }; } } async createProjectFromPRD(prdData, createdBy = 'system') { try { logger.info({ projectName: prdData.metadata?.projectName, createdBy }, 'Creating project from PRD'); const technical = prdData.technical; let languages = Array.isArray(technical?.techStack) ? technical.techStack : []; let frameworks = Array.isArray(technical?.architecturalPatterns) ? technical.architecturalPatterns : []; let tools = []; if (languages.length === 0 || frameworks.length === 0) { logger.info({ prdLanguages: languages.length, prdFrameworks: frameworks.length }, 'PRD tech stack insufficient, using ProjectAnalyzer for detection'); try { const { ProjectAnalyzer } = await import('../../utils/project-analyzer.js'); const projectAnalyzer = ProjectAnalyzer.getInstance(); const projectPath = this.resolveProjectRootPath(); if (languages.length === 0) { languages = await projectAnalyzer.detectProjectLanguages(projectPath); logger.debug({ detectedLanguages: languages }, 'Languages detected by ProjectAnalyzer'); } if (frameworks.length === 0) { frameworks = await projectAnalyzer.detectProjectFrameworks(projectPath); logger.debug({ detectedFrameworks: frameworks }, 'Frameworks detected by ProjectAnalyzer'); } tools = await projectAnalyzer.detectProjectTools(projectPath); logger.debug({ detectedTools: tools }, 'Tools detected by ProjectAnalyzer'); } catch (analyzerError) { logger.warn({ err: analyzerError, projectName: prdData.metadata?.projectName }, 'ProjectAnalyzer detection failed, using fallback values'); if (languages.length === 0) languages = ['typescript', 'javascript']; if (frameworks.length === 0) frameworks = ['node.js']; if (tools.length === 0) tools = ['git', 'npm']; } } const metadata = prdData.metadata; const overview = prdData.overview; const projectParams = { name: (typeof metadata?.projectName === 'string' ? metadata.projectName : 'Untitled Project'), description: (typeof overview?.description === 'string' ? overview.description : 'Project created from PRD'), tags: ['prd-generated'], techStack: { languages, frameworks, tools } }; logger.info({ projectName: projectParams.name, techStack: projectParams.techStack, source: 'PRD + ProjectAnalyzer' }, 'Enhanced project tech stack for PRD project creation'); return await this.createProject(projectParams, createdBy); } catch (error) { logger.error({ err: error, prdData }, 'Failed to create project from PRD'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'project-operations', operation: 'create_project_from_prd', timestamp: new Date() } }; } } async createProjectFromTaskList(taskListData, createdBy = 'system') { try { logger.info({ projectName: taskListData.metadata?.projectName, createdBy }, 'Creating project from task list'); const metadata = taskListData.metadata; const techStack = metadata?.techStack; const projectParams = { name: (typeof metadata?.projectName === 'string' ? metadata.projectName : 'Untitled Project'), description: (typeof metadata?.description === 'string' ? metadata.description : 'Project created from task list'), tags: ['task-list-generated'], techStack: { languages: Array.isArray(techStack?.languages) ? techStack.languages : [], frameworks: Array.isArray(techStack?.frameworks) ? techStack.frameworks : [], tools: Array.isArray(techStack?.tools) ? techStack.tools : [] } }; return await this.createProject(projectParams, createdBy); } catch (error) { logger.error({ err: error, taskListData }, 'Failed to create project from task list'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'project-operations', operation: 'create_project_from_task_list', timestamp: new Date() } }; } } async deleteProject(projectId, deletedBy = 'system') { try { logger.info({ projectId, deletedBy }, 'Deleting project'); const storageManager = await getStorageManager(); const projectExists = await storageManager.projectExists(projectId); if (!projectExists) { return { success: false, error: `Project ${projectId} not found`, metadata: { filePath: 'project-operations', operation: 'delete_project', timestamp: new Date() } }; } const deleteResult = await storageManager.deleteProject(projectId); if (!deleteResult.success) { return { success: false, error: `Failed to delete project: ${deleteResult.error}`, metadata: deleteResult.metadata }; } logger.info({ projectId }, 'Project deleted successfully'); return { success: true, metadata: { filePath: 'project-operations', operation: 'delete_project', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, projectId }, 'Failed to delete project'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'project-operations', operation: 'delete_project', timestamp: new Date() } }; } } async listProjects(query) { try { logger.debug({ query }, 'Listing projects'); const storageManager = await getStorageManager(); let result; if (query?.status) { result = await storageManager.getProjectsByStatus(query.status); } else { result = await storageManager.listProjects(); } if (!result.success) { return result; } let projects = result.data; if (query) { projects = this.applyProjectFilters(projects, query); } return { success: true, data: projects, metadata: { filePath: 'project-operations', operation: 'list_projects', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, query }, 'Failed to list projects'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'project-operations', operation: 'list_projects', timestamp: new Date() } }; } } async searchProjects(searchQuery, query) { try { logger.debug({ searchQuery, query }, 'Searching projects'); const storageManager = await getStorageManager(); const searchResult = await storageManager.searchProjects(searchQuery); if (!searchResult.success) { return searchResult; } let projects = searchResult.data; if (query) { projects = this.applyProjectFilters(projects, query); } return { success: true, data: projects, metadata: { filePath: 'project-operations', operation: 'search_projects', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, searchQuery }, 'Failed to search projects'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'project-operations', operation: 'search_projects', timestamp: new Date() } }; } } async determineOptimalAgentConfig(params, config) { try { logger.debug({ projectName: params.name, techStack: params.techStack }, 'Determining optimal agent configuration'); let languages = params.techStack?.languages || []; let frameworks = params.techStack?.frameworks || []; let tools = params.techStack?.tools || []; if (languages.length === 0 || frameworks.length === 0) { try { const { ProjectAnalyzer } = await import('../../utils/project-analyzer.js'); const projectAnalyzer = ProjectAnalyzer.getInstance(); const projectPath = this.resolveProjectRootPath(params.rootPath); if (languages.length === 0) { languages = await projectAnalyzer.detectProjectLanguages(projectPath); } if (frameworks.length === 0) { frameworks = await projectAnalyzer.detectProjectFrameworks(projectPath); } if (tools.length === 0) { tools = await projectAnalyzer.detectProjectTools(projectPath); } logger.debug({ detectedLanguages: languages, detectedFrameworks: frameworks, detectedTools: tools }, 'ProjectAnalyzer enhanced tech stack for agent configuration'); } catch (analyzerError) { logger.warn({ err: analyzerError, projectName: params.name }, 'ProjectAnalyzer failed for agent configuration, using defaults'); } } const optimalAgent = this.selectOptimalAgent(languages, frameworks, tools); const agentCapabilities = this.buildAgentCapabilities(languages, frameworks, tools); const maxAgents = this.calculateOptimalAgentCount(languages, frameworks, tools, config); logger.info({ projectName: params.name, selectedAgent: optimalAgent, maxAgents, agentCapabilities: Object.keys(agentCapabilities), techStackBasis: { languages, frameworks, tools } }, 'Optimal agent configuration determined'); const wasDetected = languages.length > 0 || frameworks.length > 0 || tools.length > 0; return { maxAgents, defaultAgent: optimalAgent, agentCapabilities, ...(wasDetected && { detectedTechStack: { languages, frameworks, tools } }) }; } catch (error) { logger.warn({ err: error, projectName: params.name }, 'Failed to determine optimal agent configuration, using defaults'); const taskManager = config.taskManager; const agentSettings = taskManager?.agentSettings; return { maxAgents: typeof agentSettings?.maxAgents === 'number' ? agentSettings.maxAgents : 3, defaultAgent: typeof agentSettings?.defaultAgent === 'string' ? agentSettings.defaultAgent : 'general', agentCapabilities: {} }; } } selectOptimalAgent(languages, frameworks, tools) { const agentSpecializations = { 'frontend-specialist': { languages: ['javascript', 'typescript', 'html', 'css'], frameworks: ['react', 'vue', 'angular', 'svelte', 'next.js', 'nuxt.js'], tools: ['webpack', 'vite', 'rollup', 'tailwind'], score: 0 }, 'backend-specialist': { languages: ['javascript', 'typescript', 'python', 'java', 'csharp', 'go'], frameworks: ['node.js', 'express', 'fastapi', 'django', 'spring', 'dotnet'], tools: ['docker', 'kubernetes', 'nginx'], score: 0 }, 'fullstack-developer': { languages: ['javascript', 'typescript', 'python'], frameworks: ['react', 'node.js', 'next.js', 'django', 'fastapi'], tools: ['docker', 'git', 'npm', 'yarn'], score: 0 }, 'mobile-specialist': { languages: ['javascript', 'typescript', 'swift', 'kotlin', 'dart'], frameworks: ['react-native', 'flutter', 'ionic'], tools: ['xcode', 'android-studio'], score: 0 }, 'devops-specialist': { languages: ['bash', 'python', 'yaml'], frameworks: ['terraform', 'ansible'], tools: ['docker', 'kubernetes', 'jenkins', 'github-actions'], score: 0 }, 'data-specialist': { languages: ['python', 'r', 'sql'], frameworks: ['pandas', 'tensorflow', 'pytorch'], tools: ['jupyter', 'docker'], score: 0 } }; for (const [, spec] of Object.entries(agentSpecializations)) { const languageMatches = languages.filter(lang => spec.languages.some(specLang => lang.toLowerCase().includes(specLang))).length; const languageScore = (languageMatches / Math.max(languages.length, 1)) * 0.4; const frameworkMatches = frameworks.filter(fw => spec.frameworks.some(specFw => fw.toLowerCase().includes(specFw))).length; const frameworkScore = (frameworkMatches / Math.max(frameworks.length, 1)) * 0.35; const toolMatches = tools.filter(tool => spec.tools.some(specTool => tool.toLowerCase().includes(specTool))).length; const toolScore = (toolMatches / Math.max(tools.length, 1)) * 0.25; spec.score = languageScore + frameworkScore + toolScore; } const bestAgent = Object.entries(agentSpecializations) .sort(([, a], [, b]) => b.score - a.score)[0]; const selectedAgent = bestAgent[1].score > 0.3 ? bestAgent[0] : 'fullstack-developer'; logger.debug({ agentScores: Object.fromEntries(Object.entries(agentSpecializations).map(([name, spec]) => [name, spec.score])), selectedAgent, threshold: 0.3 }, 'Agent selection analysis completed'); return selectedAgent; } buildAgentCapabilities(languages, frameworks, tools) { const capabilities = {}; if (languages.length > 0) { capabilities.languages = languages; capabilities.primaryLanguage = [languages[0]]; } if (frameworks.length > 0) { capabilities.frameworks = frameworks; capabilities.primaryFramework = [frameworks[0]]; } if (tools.length > 0) { capabilities.tools = tools; capabilities.buildTools = tools.filter(tool => ['npm', 'yarn', 'pnpm', 'webpack', 'vite', 'rollup'].includes(tool)); capabilities.deploymentTools = tools.filter(tool => ['docker', 'kubernetes', 'jenkins'].includes(tool)); } capabilities.isFullStack = languages.includes('javascript') || languages.includes('typescript'); capabilities.isMobile = frameworks.some(fw => ['react-native', 'flutter', 'ionic'].includes(fw)); capabilities.isBackend = frameworks.some(fw => ['node.js', 'express', 'django', 'fastapi', 'spring'].includes(fw)); capabilities.isFrontend = frameworks.some(fw => ['react', 'vue', 'angular', 'svelte'].includes(fw)); return capabilities; } calculateOptimalAgentCount(languages, frameworks, tools, config) { const taskManager = config.taskManager; const agentSettings = taskManager?.agentSettings; const baseAgents = typeof agentSettings?.maxAgents === 'number' ? agentSettings.maxAgents : 3; let complexityScore = 0; complexityScore += Math.min(languages.length * 0.5, 2); complexityScore += Math.min(frameworks.length * 0.3, 1.5); const sophisticatedTools = tools.filter(tool => ['docker', 'kubernetes', 'webpack', 'vite', 'jenkins', 'terraform'].includes(tool)); complexityScore += Math.min(sophisticatedTools.length * 0.2, 1); const optimalCount = Math.max(1, Math.min(Math.ceil(baseAgents * (0.5 + complexityScore * 0.1)), baseAgents)); logger.debug({ complexityScore, languageCount: languages.length, frameworkCount: frameworks.length, sophisticatedToolCount: sophisticatedTools.length, baseAgents, optimalCount }, 'Agent count calculation completed'); return optimalCount; } validateCreateParams(params) { const errors = []; if (!params.name || typeof params.name !== 'string' || params.name.trim().length === 0) { errors.push('Project name is required and must be a non-empty string'); } if (params.name && params.name.length > 100) { errors.push('Project name must be 100 characters or less'); } if (!params.description || typeof params.description !== 'string' || params.description.trim().length === 0) { errors.push('Project description is required and must be a non-empty string'); } if (params.description && params.description.length > 1000) { errors.push('Project description must be 1000 characters or less'); } if (params.rootPath && typeof params.rootPath !== 'string') { errors.push('Root path must be a string'); } if (params.tags && !Array.isArray(params.tags)) { errors.push('Tags must be an array of strings'); } if (params.tags && params.tags.some(tag => typeof tag !== 'string')) { errors.push('All tags must be strings'); } return { valid: errors.length === 0, errors }; } validateUpdateParams(params) { const errors = []; if (params.name !== undefined) { if (typeof params.name !== 'string' || params.name.trim().length === 0) { errors.push('Project name must be a non-empty string'); } if (params.name.length > 100) { errors.push('Project name must be 100 characters or less'); } } if (params.description !== undefined) { if (typeof params.description !== 'string' || params.description.trim().length === 0) { errors.push('Project description must be a non-empty string'); } if (params.description.length > 1000) { errors.push('Project description must be 1000 characters or less'); } } if (params.status !== undefined) { if (!['pending', 'in_progress', 'completed', 'blocked', 'cancelled'].includes(params.status)) { errors.push('Status must be one of: pending, in_progress, completed, blocked, cancelled'); } } if (params.rootPath !== undefined && typeof params.rootPath !== 'string') { errors.push('Root path must be a string'); } if (params.tags !== undefined) { if (!Array.isArray(params.tags)) { errors.push('Tags must be an array of strings'); } else if (params.tags.some(tag => typeof tag !== 'string')) { errors.push('All tags must be strings'); } } return { valid: errors.length === 0, errors }; } applyProjectFilters(projects, query) { let filtered = projects; if (query.tags && query.tags.length > 0) { filtered = filtered.filter(project => query.tags.some(tag => project.metadata.tags.includes(tag))); } if (query.createdAfter) { filtered = filtered.filter(project => project.metadata.createdAt >= query.createdAfter); } if (query.createdBefore) { filtered = filtered.filter(project => project.metadata.createdAt <= query.createdBefore); } if (query.offset) { filtered = filtered.slice(query.offset); } if (query.limit) { filtered = filtered.slice(0, query.limit); } return filtered; } } export function getProjectOperations() { return ProjectOperations.getInstance(); }