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.

227 lines (226 loc) 9.27 kB
import { AppError, ValidationError } from '../../../utils/errors.js'; import logger from '../../../logger.js'; export class SentinelProtocol { static PROTOCOL_VERSION = '1.0.0'; static TASK_START_MARKER = '### VIBE_TASK_START'; static TASK_END_MARKER = '### VIBE_TASK_END'; static STATUS_PREFIX = 'VIBE_STATUS:'; config; constructor(config) { this.config = { version: SentinelProtocol.PROTOCOL_VERSION, timeout_minutes: 30, max_retries: 3, enable_compression: false, validate_responses: true, ...config }; logger.debug({ config: this.config }, 'Sentinel protocol initialized'); } formatTaskForAgent(task, context, epicTitle) { try { const payload = { task, context: { project_name: context.projectName, epic_title: epicTitle || 'Unknown Epic', codebase_context: this.summarizeCodebaseContext(context), related_files: context.entryPoints || [], dependencies: task.dependencies || [] }, instructions: { implementation_guide: this.generateImplementationGuide(task), acceptance_criteria: task.acceptanceCriteria || [], completion_signal: `${SentinelProtocol.STATUS_PREFIX} DONE`, timeout_minutes: this.config.timeout_minutes }, metadata: { protocol_version: this.config.version, task_id: task.id, timestamp: new Date().toISOString(), priority: this.mapPriorityToNumber(task.priority) } }; const jsonPayload = JSON.stringify(payload, null, 2); return [ SentinelProtocol.TASK_START_MARKER, jsonPayload, SentinelProtocol.TASK_END_MARKER, '', '**Instructions for AI Agent:**', '1. Read the task payload above carefully', '2. Implement the required functionality', '3. Follow the acceptance criteria exactly', '4. When complete, respond with: VIBE_STATUS: DONE', '5. If you need help, respond with: VIBE_STATUS: HELP', '6. If blocked, respond with: VIBE_STATUS: BLOCKED', '', '**Response Format:**', 'VIBE_STATUS: [DONE|HELP|BLOCKED]', 'Additional details can follow...' ].join('\n'); } catch (error) { logger.error({ err: error, taskId: task.id }, 'Failed to format task for agent'); throw new AppError('Failed to format task for agent delivery', { cause: error }); } } parseAgentResponse(responseText, expectedTaskId) { try { const lines = responseText.split('\n'); let status = null; let message = ''; const taskId = expectedTaskId || ''; for (const line of lines) { const trimmedLine = line.trim(); if (trimmedLine.startsWith(SentinelProtocol.STATUS_PREFIX)) { const statusPart = trimmedLine.substring(SentinelProtocol.STATUS_PREFIX.length).trim(); status = this.parseStatus(statusPart); break; } } if (!status) { throw new ValidationError('No valid status found in agent response'); } const statusLineIndex = lines.findIndex(line => line.trim().startsWith(SentinelProtocol.STATUS_PREFIX)); if (statusLineIndex >= 0 && statusLineIndex < lines.length - 1) { message = lines.slice(statusLineIndex + 1).join('\n').trim(); } const response = { status, task_id: taskId, message, timestamp: new Date().toISOString() }; if (status === 'DONE') { response.completion_details = this.parseCompletionDetails(message); } else if (status === 'HELP') { response.help_request = this.parseHelpRequest(message); } else if (status === 'BLOCKED') { response.blocker_details = this.parseBlockerDetails(message); } if (this.config.validate_responses) { this.validateResponse(response); } logger.debug({ response }, 'Parsed agent response'); return response; } catch (error) { logger.error({ err: error, responseText }, 'Failed to parse agent response'); throw new ValidationError('Invalid agent response format', undefined, { cause: error }); } } validateResponse(response) { if (!response.status || !['DONE', 'HELP', 'BLOCKED', 'IN_PROGRESS', 'FAILED'].includes(response.status)) { throw new ValidationError(`Invalid agent status: ${response.status}`); } if (!response.task_id) { throw new ValidationError('Missing task_id in agent response'); } if (!response.timestamp) { throw new ValidationError('Missing timestamp in agent response'); } if (response.status === 'DONE' && !response.completion_details) { logger.warn({ taskId: response.task_id }, 'DONE response missing completion details'); } if (response.status === 'HELP' && !response.help_request) { logger.warn({ taskId: response.task_id }, 'HELP response missing help request details'); } if (response.status === 'BLOCKED' && !response.blocker_details) { logger.warn({ taskId: response.task_id }, 'BLOCKED response missing blocker details'); } } generateImplementationGuide(task) { const guide = [ `**Task: ${task.title}**`, '', `**Description:**`, task.description, '', `**Type:** ${task.type}`, `**Priority:** ${task.priority}`, `**Estimated Duration:** ${task.estimatedHours || 'Not specified'} hours`, '' ]; if (task.dependencies && task.dependencies.length > 0) { guide.push('**Dependencies:**'); task.dependencies.forEach(dep => guide.push(`- ${dep}`)); guide.push(''); } if (task.tags && task.tags.length > 0) { guide.push(`**Tags:** ${task.tags.join(', ')}`); guide.push(''); } guide.push('**Implementation Notes:**'); guide.push('- Follow existing code patterns and conventions'); guide.push('- Write comprehensive tests for new functionality'); guide.push('- Update documentation as needed'); guide.push('- Ensure all acceptance criteria are met'); return guide.join('\n'); } summarizeCodebaseContext(context) { const summary = [ `Project: ${context.projectName}`, `Architecture: ${context.architecturalPatterns?.join(', ') || 'Not specified'}`, `Tech Stack: ${context.frameworks?.join(', ') || 'Not specified'}` ]; if (context.languages && context.languages.length > 0) { summary.push(`Languages: ${context.languages.join(', ')}`); } return summary.join('\n'); } mapPriorityToNumber(priority) { const priorityMap = { 'critical': 1, 'high': 2, 'medium': 3, 'low': 4 }; if (!priority || typeof priority !== 'string') { return 3; } return priorityMap[priority.toLowerCase()] || 3; } parseStatus(statusText) { const status = statusText.toUpperCase().trim(); if (['DONE', 'HELP', 'BLOCKED', 'IN_PROGRESS', 'FAILED'].includes(status)) { return status; } throw new ValidationError(`Invalid agent status: ${status}`); } parseCompletionDetails(message) { return { files_modified: [], tests_passed: message.toLowerCase().includes('tests pass'), build_successful: message.toLowerCase().includes('build success'), notes: message, implementation_guide: '', acceptance_criteria: [], completion_signal: '', timeout_minutes: 0 }; } parseHelpRequest(message) { return { issue_description: message, attempted_solutions: [], specific_questions: [] }; } parseBlockerDetails(message) { return { blocker_type: 'technical', description: message, suggested_resolution: 'Manual intervention required' }; } getConfig() { return { ...this.config }; } updateConfig(updates) { this.config = { ...this.config, ...updates }; logger.debug({ config: this.config }, 'Protocol configuration updated'); } }