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.

312 lines (311 loc) 12.8 kB
import { promises as fs } from 'fs'; import path from 'path'; import yaml from 'js-yaml'; import logger from '../../../logger.js'; import { XMLFormatter } from '../utils/xml-formatter.js'; export class OutputFormatterService { static instance; templateCache = new Map(); constructor() { } static getInstance() { if (!OutputFormatterService.instance) { OutputFormatterService.instance = new OutputFormatterService(); } return OutputFormatterService.instance; } async formatOutput(contextPackage, format, config, templateVariables) { const startTime = Date.now(); try { logger.debug({ format, packageId: contextPackage.metadata }, 'Formatting output'); const variables = { projectName: contextPackage.metadata.targetDirectory ? path.basename(contextPackage.metadata.targetDirectory) : 'unknown', taskType: contextPackage.metadata.taskType, userPrompt: contextPackage.metadata.originalPrompt, totalFiles: contextPackage.metadata.filesIncluded, totalTokens: contextPackage.metadata.totalTokenEstimate, generationTimestamp: (contextPackage.metadata.generationTimestamp || new Date()).toISOString(), ...templateVariables, ...(config.outputFormat?.templateOptions?.customVariables || {}) }; let content; let validation; switch (format) { case 'xml': content = await this.formatAsXML(contextPackage, variables, config); validation = this.validateXMLOutput(content); break; case 'json': content = await this.formatAsJSON(contextPackage, variables, config); validation = this.validateJSONOutput(content); break; case 'yaml': content = await this.formatAsYAML(contextPackage, variables, config); validation = this.validateYAMLOutput(content); break; default: throw new Error(`Unsupported output format: ${format}`); } const processingTimeMs = Date.now() - startTime; logger.info({ format, size: content.length, processingTimeMs, isValid: this.isValidationPassed(validation) }, 'Output formatting completed'); return { content, format, size: content.length, validation, processingTimeMs }; } catch (error) { const processingTimeMs = Date.now() - startTime; logger.error({ error: error instanceof Error ? error.message : 'Unknown error', format, processingTimeMs }, 'Output formatting failed'); throw new Error(`Output formatting failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async formatAsXML(contextPackage, variables, _config) { const template = await this.loadTemplate(variables.taskType, 'xml'); if (template.trim()) { const innerXml = this.generateInnerXMLContent(contextPackage); const enhancedXml = this.applyTemplateVariables(innerXml, variables, template); return enhancedXml; } else { const baseXml = XMLFormatter.formatContextPackage(contextPackage); return this.applyTemplateVariables(baseXml, variables, ''); } } generateInnerXMLContent(contextPackage) { const fullXml = XMLFormatter.formatContextPackage(contextPackage); let innerContent = fullXml; innerContent = innerContent.replace(/^\s*<\?xml[^>]*\?>\s*\n?/, ''); innerContent = innerContent.replace(/^\s*<context_package[^>]*>\s*\n?/, ''); innerContent = innerContent.replace(/\s*<\/context_package>\s*$/, ''); return innerContent.trim(); } async formatAsJSON(contextPackage, variables, _config) { const jsonData = { metadata: { ...contextPackage.metadata, generationTimestamp: (contextPackage.metadata.generationTimestamp || new Date()).toISOString(), taskType: variables.taskType, format: 'json' }, codemapPath: contextPackage.codemapPath, files: { highPriority: contextPackage.highPriorityFiles || [], mediumPriority: contextPackage.mediumPriorityFiles || [], lowPriority: contextPackage.lowPriorityFiles || [] }, metaPrompt: contextPackage.metaPrompt || contextPackage.fullMetaPrompt, templateVariables: { ...variables, aiAgentResponseFormat: this.extractAiAgentResponseFormat(contextPackage) } }; return JSON.stringify(jsonData, null, 2); } async formatAsYAML(contextPackage, variables, _config) { const yamlData = { metadata: { ...contextPackage.metadata, generationTimestamp: (contextPackage.metadata.generationTimestamp || new Date()).toISOString(), taskType: variables.taskType, format: 'yaml' }, codemapPath: contextPackage.codemapPath, files: { highPriority: contextPackage.highPriorityFiles || [], mediumPriority: contextPackage.mediumPriorityFiles || [], lowPriority: contextPackage.lowPriorityFiles || [] }, metaPrompt: contextPackage.metaPrompt || contextPackage.fullMetaPrompt, templateVariables: { ...variables, aiAgentResponseFormat: this.extractAiAgentResponseFormat(contextPackage) } }; return yaml.dump(yamlData, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false }); } async loadTemplate(taskType, format) { const cacheKey = `${taskType}-${format}`; if (this.templateCache.has(cacheKey)) { return this.templateCache.get(cacheKey); } try { const projectRoot = process.cwd(); const templatePath = path.join(projectRoot, 'src/tools/context-curator/templates', `${taskType}-template.${format}`); const template = await fs.readFile(templatePath, 'utf-8'); this.templateCache.set(cacheKey, template); logger.debug({ taskType, format, templatePath }, 'Template loaded and cached'); return template; } catch (error) { logger.warn({ taskType, format, error: error instanceof Error ? error.message : 'Unknown error' }, 'Failed to load template, using default'); return ''; } } applyTemplateVariables(content, variables, template) { if (template.trim()) { let result = template; Object.entries(variables).forEach(([key, value]) => { if (value !== undefined) { const placeholder = `{{${key}}}`; result = result.replace(new RegExp(placeholder, 'g'), String(value)); } }); let cleanContent = content; if (template.includes('<?xml') && content.includes('<?xml')) { cleanContent = content.replace(/<\?xml[^>]*\?>\s*\n?/g, ''); } result = result.replace('{{CONTENT}}', cleanContent); return result; } else { let result = content; Object.entries(variables).forEach(([key, value]) => { if (value !== undefined) { const placeholder = `{{${key}}}`; result = result.replace(new RegExp(placeholder, 'g'), String(value)); } }); return result; } } validateXMLOutput(content) { return { hasXmlDeclaration: content.startsWith('<?xml'), isWellFormed: this.isWellFormedXML(content), schemaCompliant: this.isXMLSchemaCompliant(content), validEncoding: this.hasValidXMLEncoding(content) }; } validateJSONOutput(content) { try { const parsed = JSON.parse(content); return { isValidJson: true, schemaCompliant: this.isJSONSchemaCompliant(parsed), hasRequiredFields: this.hasRequiredJSONFields(parsed) }; } catch { return { isValidJson: false, schemaCompliant: false, hasRequiredFields: false }; } } validateYAMLOutput(content) { try { const parsed = yaml.load(content); return { isValidYaml: true, schemaCompliant: this.isYAMLSchemaCompliant(parsed), hasRequiredFields: this.hasRequiredYAMLFields(parsed) }; } catch { return { isValidYaml: false, schemaCompliant: false, hasRequiredFields: false }; } } isValidationPassed(validation) { if ('isWellFormed' in validation) { return validation.hasXmlDeclaration && validation.isWellFormed && validation.schemaCompliant; } else if ('isValidJson' in validation) { return validation.isValidJson && validation.schemaCompliant && validation.hasRequiredFields; } else { return validation.isValidYaml && validation.schemaCompliant && validation.hasRequiredFields; } } isWellFormedXML(content) { const openTags = content.match(/<[^/][^>]*>/g) || []; const closeTags = content.match(/<\/[^>]*>/g) || []; return openTags.length >= closeTags.length; } isXMLSchemaCompliant(content) { return content.includes('<context_package') || content.includes('<feature_addition_context_package') || content.includes('<bug_fix_context_package') || content.includes('<refactoring_context_package') || content.includes('<general_context_package') || (content.includes('<package_metadata>') && (content.includes('</context_package>') || content.includes('</feature_addition_context_package>') || content.includes('</bug_fix_context_package>') || content.includes('</refactoring_context_package>') || content.includes('</general_context_package>'))); } extractAiAgentResponseFormat(contextPackage) { if (contextPackage && typeof contextPackage === 'object') { if (contextPackage.fullMetaPrompt && typeof contextPackage.fullMetaPrompt === 'object') { const fullMeta = contextPackage.fullMetaPrompt; if (fullMeta.aiAgentResponseFormat) { return fullMeta.aiAgentResponseFormat; } } if (contextPackage.metaPrompt && typeof contextPackage.metaPrompt === 'object') { const meta = contextPackage.metaPrompt; if (meta.aiAgentResponseFormat) { return meta.aiAgentResponseFormat; } } if (contextPackage.aiAgentResponseFormat) { return contextPackage.aiAgentResponseFormat; } } return undefined; } hasValidXMLEncoding(content) { return content.includes('encoding="UTF-8"') || !content.includes('encoding='); } isJSONSchemaCompliant(data) { return !!(data && typeof data === 'object' && data.metadata && data.files); } hasRequiredJSONFields(data) { const metadata = data.metadata; return !!(metadata?.taskType && metadata?.generationTimestamp && metadata?.refinedPrompt && data.files); } isYAMLSchemaCompliant(data) { return !!(data && typeof data === 'object' && data.metadata && data.files); } hasRequiredYAMLFields(data) { const metadata = data.metadata; return !!(metadata?.taskType && metadata?.generationTimestamp && metadata?.refinedPrompt && data.files); } }