Skip to main content
Glama
artifact-handlers.ts18 kB
/** * Artifact NLP Handlers * * Implements natural language handlers for PRD and task list parsing * using the existing artifact integration services. */ import { Intent, RecognizedIntent } from '../../types/nl.js'; import { CommandHandler, CommandExecutionContext, CommandExecutionResult } from '../command-handlers.js'; import { PRDIntegrationService } from '../../integrations/prd-integration.js'; import { TaskListIntegrationService } from '../../integrations/task-list-integration.js'; import { getProjectOperations } from '../../core/operations/project-operations.js'; import logger from '../../../../logger.js'; /** * Parse PRD Handler * Handles natural language requests to parse existing PRDs */ export class ParsePRDHandler implements CommandHandler { intent: Intent = 'parse_prd'; async handle( recognizedIntent: RecognizedIntent, toolParams: Record<string, unknown>, context: CommandExecutionContext ): Promise<CommandExecutionResult> { try { logger.info({ intent: recognizedIntent.intent, sessionId: context.sessionId }, 'Processing PRD parsing request'); // Extract parameters from natural language const projectName = this.extractProjectName(recognizedIntent, toolParams); const filePath = this.extractFilePath(recognizedIntent, toolParams); // Get PRD integration service const prdService = PRDIntegrationService.getInstance(); // Detect existing PRD let prdInfo; if (filePath) { // Use specific file path const result = await prdService.parsePRD(filePath); if (!result.success) { return { success: false, result: { content: [{ type: "text", text: `❌ Failed to parse PRD from ${filePath}: ${result.error}` }], isError: true } }; } prdInfo = result.prdData!; } else { // Auto-detect PRD const detectedPRD = await prdService.detectExistingPRD(projectName); if (!detectedPRD) { return { success: false, result: { content: [{ type: "text", text: `❌ No PRD found${projectName ? ` for project "${projectName}"` : ''}. Please ensure a PRD exists in the VibeCoderOutput/prd-generator/ directory.` }], isError: true } }; } // Parse the detected PRD const result = await prdService.parsePRD(detectedPRD.filePath); if (!result.success) { return { success: false, result: { content: [{ type: "text", text: `❌ Failed to parse PRD: ${result.error}` }], isError: true } }; } prdInfo = result.prdData!; } // Create project from PRD const projectOperations = getProjectOperations(); const projectResult = await projectOperations.createProjectFromPRD(prdInfo as unknown as Record<string, unknown>, context.sessionId); if (!projectResult.success) { return { success: false, result: { content: [{ type: "text", text: `❌ Failed to create project from PRD: ${projectResult.error}` }], isError: true } }; } const project = projectResult.data!; // Format successful PRD parsing results let responseText = `✅ Successfully parsed PRD "${prdInfo.metadata.projectName}" and created project:\n\n`; responseText += `📋 **Project Details:**\n`; responseText += `- Project ID: ${project.id}\n`; responseText += `- Name: ${project.name}\n`; responseText += `- Description: ${prdInfo.overview.description.substring(0, 200)}${prdInfo.overview.description.length > 200 ? '...' : ''}\n`; responseText += `- Features: ${prdInfo.features.length} features identified\n`; responseText += `- Tech Stack: ${prdInfo.technical.techStack.slice(0, 3).join(', ')}${prdInfo.technical.techStack.length > 3 ? '...' : ''}\n\n`; responseText += `🎯 **Key Features:**\n`; prdInfo.features.slice(0, 5).forEach((feature, index) => { responseText += `${index + 1}. ${feature.title} (${feature.priority})\n`; }); if (prdInfo.features.length > 5) { responseText += `... and ${prdInfo.features.length - 5} more features\n`; } responseText += `\n📊 **Next Steps:**\n`; responseText += `- Epic generation from PRD features\n`; responseText += `- Task decomposition for each epic\n`; responseText += `- Agent assignment and execution planning\n`; return { success: true, result: { content: [{ type: "text", text: responseText }] }, followUpSuggestions: [ `Generate epics for project ${project.id}`, `List all features from the PRD`, `Start task decomposition for ${project.name}` ] }; } catch (error) { logger.error({ err: error, intent: recognizedIntent.intent, sessionId: context.sessionId }, 'PRD parsing failed'); return { success: false, result: { content: [{ type: "text", text: `❌ Failed to parse PRD: ${error instanceof Error ? error.message : 'Unknown error'}` }], isError: true } }; } } /** * Extract project name from natural language input */ private extractProjectName(recognizedIntent: RecognizedIntent, toolParams: Record<string, unknown>): string | undefined { // Check tool params first if (toolParams.projectName) { return toolParams.projectName as string; } // Extract from entities const projectEntity = recognizedIntent.entities.find(e => e.type === 'projectName'); if (projectEntity) { return projectEntity.value; } // Pattern matching from original input const input = recognizedIntent.originalInput; const projectMatch = input.match(/(?:for|of)\s+(?:project\s+)?["']?([^"'\s]+)["']?/i); if (projectMatch) { return projectMatch[1]; } return undefined; } /** * Extract file path from natural language input */ private extractFilePath(recognizedIntent: RecognizedIntent, toolParams: Record<string, unknown>): string | undefined { // Check tool params first if (toolParams.filePath) { return toolParams.filePath as string; } // Extract from entities const fileEntity = recognizedIntent.entities.find(e => e.type === 'filePath'); if (fileEntity) { return fileEntity.value; } // Pattern matching for file paths const input = recognizedIntent.originalInput; const fileMatch = input.match(/(?:from|at)\s+["']?([^"'\s]+\.md)["']?/i); if (fileMatch) { return fileMatch[1]; } return undefined; } } /** * Parse Tasks Handler * Handles natural language requests to parse existing task lists */ export class ParseTasksHandler implements CommandHandler { intent: Intent = 'parse_tasks'; async handle( recognizedIntent: RecognizedIntent, toolParams: Record<string, unknown>, context: CommandExecutionContext ): Promise<CommandExecutionResult> { try { logger.info({ intent: recognizedIntent.intent, sessionId: context.sessionId }, 'Processing task list parsing request'); // Extract parameters from natural language const projectName = this.extractProjectName(recognizedIntent, toolParams); const filePath = this.extractFilePath(recognizedIntent, toolParams); // Get task list integration service const taskListService = TaskListIntegrationService.getInstance(); // Detect existing task list let taskListInfo; if (filePath) { // Use specific file path const result = await taskListService.parseTaskList(filePath); if (!result.success) { return { success: false, result: { content: [{ type: "text", text: `❌ Failed to parse task list from ${filePath}: ${result.error}` }], isError: true } }; } taskListInfo = result.taskListData!; } else { // Auto-detect task list const detectedTaskList = await taskListService.detectExistingTaskList(projectName); if (!detectedTaskList) { return { success: false, result: { content: [{ type: "text", text: `❌ No task list found${projectName ? ` for project "${projectName}"` : ''}. Please ensure a task list exists in the VibeCoderOutput/generated_task_lists/ directory.` }], isError: true } }; } // Parse the detected task list const result = await taskListService.parseTaskList(detectedTaskList.filePath); if (!result.success) { return { success: false, result: { content: [{ type: "text", text: `❌ Failed to parse task list: ${result.error}` }], isError: true } }; } taskListInfo = result.taskListData!; } // Create project and tasks from task list const projectOperations = getProjectOperations(); const projectResult = await projectOperations.createProjectFromTaskList(taskListInfo as unknown as Record<string, unknown>, context.sessionId); if (!projectResult.success) { return { success: false, result: { content: [{ type: "text", text: `❌ Failed to create project from task list: ${projectResult.error}` }], isError: true } }; } const project = projectResult.data!; // Convert task list to atomic tasks const atomicTasks = await taskListService.convertToAtomicTasks( taskListInfo, project.id, 'default-epic', 'system' ); // Format successful task list parsing results let responseText = `✅ Successfully parsed task list "${taskListInfo.metadata.projectName}" and created project:\n\n`; responseText += `📋 **Project Details:**\n`; responseText += `- Project ID: ${project.id}\n`; responseText += `- Name: ${project.name}\n`; responseText += `- Description: ${taskListInfo.overview.description.substring(0, 200)}${taskListInfo.overview.description.length > 200 ? '...' : ''}\n`; responseText += `- Phases: ${taskListInfo.phases.length} phases identified\n`; responseText += `- Total Tasks: ${taskListInfo.metadata.totalTasks}\n`; responseText += `- Estimated Hours: ${taskListInfo.statistics.totalEstimatedHours}\n\n`; responseText += `📊 **Phase Breakdown:**\n`; taskListInfo.phases.slice(0, 5).forEach((phase, index) => { responseText += `${index + 1}. ${phase.name} (${phase.tasks.length} tasks)\n`; }); if (taskListInfo.phases.length > 5) { responseText += `... and ${taskListInfo.phases.length - 5} more phases\n`; } responseText += `\n🎯 **Atomic Tasks Created:**\n`; responseText += `- ${atomicTasks.length} atomic tasks ready for execution\n`; responseText += `- Average task size: ${(taskListInfo.statistics.totalEstimatedHours / atomicTasks.length).toFixed(1)} hours\n`; responseText += `\n📊 **Next Steps:**\n`; responseText += `- Agent assignment for task execution\n`; responseText += `- Dependency resolution and scheduling\n`; responseText += `- Progress tracking and monitoring\n`; return { success: true, result: { content: [{ type: "text", text: responseText }] }, followUpSuggestions: [ `List all tasks for project ${project.id}`, `Start task execution for ${project.name}`, `Show task dependencies for ${project.name}` ] }; } catch (error) { logger.error({ err: error, intent: recognizedIntent.intent, sessionId: context.sessionId }, 'Task list parsing failed'); return { success: false, result: { content: [{ type: "text", text: `❌ Failed to parse task list: ${error instanceof Error ? error.message : 'Unknown error'}` }], isError: true } }; } } /** * Extract project name from natural language input */ private extractProjectName(recognizedIntent: RecognizedIntent, toolParams: Record<string, unknown>): string | undefined { // Check tool params first if (toolParams.projectName) { return toolParams.projectName as string; } // Extract from entities const projectEntity = recognizedIntent.entities.find(e => e.type === 'projectName'); if (projectEntity) { return projectEntity.value; } // Pattern matching from original input const input = recognizedIntent.originalInput; const projectMatch = input.match(/(?:for|of)\s+(?:project\s+)?["']?([^"'\s]+)["']?/i); if (projectMatch) { return projectMatch[1]; } return undefined; } /** * Extract file path from natural language input */ private extractFilePath(recognizedIntent: RecognizedIntent, toolParams: Record<string, unknown>): string | undefined { // Check tool params first if (toolParams.filePath) { return toolParams.filePath as string; } // Extract from entities const fileEntity = recognizedIntent.entities.find(e => e.type === 'filePath'); if (fileEntity) { return fileEntity.value; } // Pattern matching for file paths const input = recognizedIntent.originalInput; const fileMatch = input.match(/(?:from|at)\s+["']?([^"'\s]+\.md)["']?/i); if (fileMatch) { return fileMatch[1]; } return undefined; } } /** * Import Artifact Handler * Handles natural language requests to import artifacts with type routing */ export class ImportArtifactHandler implements CommandHandler { intent: Intent = 'import_artifact'; async handle( recognizedIntent: RecognizedIntent, toolParams: Record<string, unknown>, context: CommandExecutionContext ): Promise<CommandExecutionResult> { try { logger.info({ intent: recognizedIntent.intent, sessionId: context.sessionId }, 'Processing artifact import request'); // Extract artifact type from natural language const artifactType = this.extractArtifactType(recognizedIntent, toolParams); if (!artifactType) { return { success: false, result: { content: [{ type: "text", text: "❌ Please specify the artifact type to import. For example: 'import PRD' or 'import task list'" }], isError: true } }; } // Route to appropriate handler based on artifact type switch (artifactType.toLowerCase()) { case 'prd': case 'product_requirements_document': { const prdHandler = new ParsePRDHandler(); return await prdHandler.handle(recognizedIntent, toolParams, context); } case 'task_list': case 'tasks': case 'task-list': { const taskHandler = new ParseTasksHandler(); return await taskHandler.handle(recognizedIntent, toolParams, context); } default: return { success: false, result: { content: [{ type: "text", text: `❌ Unsupported artifact type: "${artifactType}". Supported types are: PRD, task list` }], isError: true } }; } } catch (error) { logger.error({ err: error, intent: recognizedIntent.intent, sessionId: context.sessionId }, 'Artifact import failed'); return { success: false, result: { content: [{ type: "text", text: `❌ Failed to import artifact: ${error instanceof Error ? error.message : 'Unknown error'}` }], isError: true } }; } } /** * Extract artifact type from natural language input */ private extractArtifactType(recognizedIntent: RecognizedIntent, toolParams: Record<string, unknown>): string | undefined { // Check tool params first if (toolParams.artifactType) { return toolParams.artifactType as string; } // Extract from entities const artifactEntity = recognizedIntent.entities.find(e => e.type === 'artifactType'); if (artifactEntity) { return artifactEntity.value; } // Pattern matching from original input const input = recognizedIntent.originalInput.toLowerCase(); // Check for PRD patterns if (input.includes('prd') || input.includes('product requirements') || input.includes('requirements document')) { return 'prd'; } // Check for task list patterns if (input.includes('task list') || input.includes('tasks') || input.includes('task-list')) { return 'task_list'; } // Check for generic artifact mention if (input.includes('artifact')) { // Try to infer from context if (input.includes('generator')) { if (input.includes('prd-generator')) { return 'prd'; } else if (input.includes('task-list-generator')) { return 'task_list'; } } } return undefined; } }

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/freshtechbro/vibe-coder-mcp'

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