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
JavaScript
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);
}
}