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.

254 lines (253 loc) 10.7 kB
import { z } from 'zod'; import logger from '../../../logger.js'; import { contextPackageSchema } from '../types/context-curator.js'; export class PackageValidator { static MIN_QUALITY_SCORE = 0.7; static MIN_FILES_COUNT = 1; static MAX_TOKEN_COUNT = 100000; static MIN_RELEVANCE_SCORE = 0.3; static async validatePackage(contextPackage) { const errors = []; const warnings = []; logger.info({ packageId: contextPackage.id }, 'Starting package validation'); try { const schemaCompliance = this.validateSchema(contextPackage, errors); const contentCompleteness = this.validateContent(contextPackage, errors, warnings); const metaPromptQuality = this.validateMetaPrompt(contextPackage, errors, warnings); const fileRelevance = this.validateFileRelevance(contextPackage, warnings); const tokenEfficiency = this.validateTokenEfficiency(contextPackage, warnings); const taskDecompositionQuality = this.validateTaskDecomposition(contextPackage, warnings); const qualityMetrics = { schemaCompliance, contentCompleteness, metaPromptQuality, fileRelevance, tokenEfficiency, taskDecompositionQuality }; const qualityScore = this.calculateOverallQualityScore(qualityMetrics); const result = { isValid: errors.length === 0 && qualityScore >= this.MIN_QUALITY_SCORE, errors, warnings, qualityScore, qualityMetrics }; logger.info({ packageId: contextPackage.id, isValid: result.isValid, qualityScore: result.qualityScore, errorsCount: errors.length, warningsCount: warnings.length }, 'Package validation completed'); return result; } catch (error) { logger.error({ packageId: contextPackage.id, error: error instanceof Error ? error.message : 'Unknown error' }, 'Package validation failed'); return { isValid: false, errors: [`Validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`], warnings: [], qualityScore: 0, qualityMetrics: { schemaCompliance: 0, contentCompleteness: 0, metaPromptQuality: 0, fileRelevance: 0, tokenEfficiency: 0, taskDecompositionQuality: 0 } }; } } static validateSchema(contextPackage, errors) { try { contextPackageSchema.parse(contextPackage); return 1.0; } catch (error) { if (error instanceof z.ZodError) { error.errors.forEach(err => { errors.push(`Schema validation error: ${err.path.join('.')} - ${err.message}`); }); const errorRatio = Math.min(error.errors.length / 10, 1); return Math.max(0, 1 - errorRatio); } errors.push('Unknown schema validation error'); return 0; } } static validateContent(contextPackage, errors, warnings) { let score = 1.0; if (contextPackage.files.length < this.MIN_FILES_COUNT) { errors.push(`Insufficient files: ${contextPackage.files.length} < ${this.MIN_FILES_COUNT}`); score -= 0.3; } if (!contextPackage.userPrompt || contextPackage.userPrompt.trim().length === 0) { errors.push('User prompt is empty'); score -= 0.2; } if (!contextPackage.projectPath || contextPackage.projectPath.trim().length === 0) { errors.push('Project path is empty'); score -= 0.2; } if (!contextPackage.taskType) { errors.push('Task type is missing'); score -= 0.1; } if (contextPackage.statistics.totalFiles !== contextPackage.files.length) { warnings.push('Statistics total files mismatch with actual files count'); score -= 0.1; } if (contextPackage.statistics.totalTokens > this.MAX_TOKEN_COUNT) { warnings.push(`High token count: ${contextPackage.statistics.totalTokens} > ${this.MAX_TOKEN_COUNT}`); score -= 0.1; } return Math.max(0, score); } static validateMetaPrompt(contextPackage, errors, warnings) { let score = 1.0; if (!contextPackage.metaPrompt) { errors.push('Meta-prompt is missing'); return 0; } const metaPrompt = contextPackage.metaPrompt; if (!metaPrompt.systemPrompt || metaPrompt.systemPrompt.trim().length < 50) { errors.push('System prompt is missing or too short'); score -= 0.2; } if (!metaPrompt.userPrompt || metaPrompt.userPrompt.trim().length < 20) { errors.push('Meta-prompt user prompt is missing or too short'); score -= 0.2; } if (!metaPrompt.contextSummary || metaPrompt.contextSummary.trim().length < 50) { warnings.push('Context summary is missing or too short'); score -= 0.1; } if (!metaPrompt.guidelines || metaPrompt.guidelines.length === 0) { warnings.push('No guidelines provided in meta-prompt'); score -= 0.1; } if (!metaPrompt.estimatedComplexity) { warnings.push('Estimated complexity is missing'); score -= 0.1; } return Math.max(0, score); } static validateFileRelevance(contextPackage, warnings) { if (contextPackage.files.length === 0) { return 0; } let totalRelevance = 0; let lowRelevanceCount = 0; for (const file of contextPackage.files) { const relevance = file.relevanceScore.score; totalRelevance += relevance; if (relevance < this.MIN_RELEVANCE_SCORE) { lowRelevanceCount++; } } const averageRelevance = totalRelevance / contextPackage.files.length; if (lowRelevanceCount > contextPackage.files.length * 0.3) { warnings.push(`High number of low-relevance files: ${lowRelevanceCount}/${contextPackage.files.length}`); } if (averageRelevance < 0.5) { warnings.push(`Low average relevance score: ${averageRelevance.toFixed(2)}`); } return Math.min(1.0, averageRelevance * 2); } static validateTokenEfficiency(contextPackage, warnings) { const totalTokens = contextPackage.statistics.totalTokens; const totalFiles = contextPackage.files.length; if (totalFiles === 0) { return 0; } const averageTokensPerFile = totalTokens / totalFiles; const largeFiles = contextPackage.files.filter(file => file.file.tokenCount > 5000); if (largeFiles.length > 0) { warnings.push(`${largeFiles.length} files have high token counts (>5000 tokens)`); } const smallFiles = contextPackage.files.filter(file => file.file.tokenCount < 50); if (smallFiles.length > totalFiles * 0.3) { warnings.push(`${smallFiles.length} files have very low token counts (<50 tokens)`); } let efficiencyScore = 1.0; if (averageTokensPerFile > 3000) { efficiencyScore -= 0.2; } if (averageTokensPerFile < 100) { efficiencyScore -= 0.2; } if (totalTokens > this.MAX_TOKEN_COUNT) { efficiencyScore -= 0.3; } return Math.max(0, efficiencyScore); } static validateTaskDecomposition(contextPackage, warnings) { if (!contextPackage.metaPrompt?.taskDecomposition) { warnings.push('Task decomposition is missing'); return 0; } const decomposition = contextPackage.metaPrompt.taskDecomposition; let score = 1.0; if (!decomposition.epics || decomposition.epics.length === 0) { warnings.push('No epics defined in task decomposition'); return 0; } if (decomposition.epics.length < 2) { warnings.push('Very few epics defined (recommended: 3-10)'); score -= 0.2; } let totalTasks = 0; let totalSubtasks = 0; for (const epic of decomposition.epics) { if (!epic.tasks || epic.tasks.length === 0) { warnings.push(`Epic "${epic.title}" has no tasks`); score -= 0.1; continue; } totalTasks += epic.tasks.length; for (const task of epic.tasks) { if (task.subtasks) { totalSubtasks += task.subtasks.length; } } } const averageTasksPerEpic = totalTasks / decomposition.epics.length; if (averageTasksPerEpic < 2) { warnings.push('Low average tasks per epic (recommended: 3-7)'); score -= 0.1; } if (totalSubtasks === 0) { warnings.push('No subtasks defined (recommended for detailed planning)'); score -= 0.1; } return Math.max(0, score); } static calculateOverallQualityScore(metrics) { const weights = { schemaCompliance: 0.25, contentCompleteness: 0.20, metaPromptQuality: 0.20, fileRelevance: 0.15, tokenEfficiency: 0.10, taskDecompositionQuality: 0.10 }; return (metrics.schemaCompliance * weights.schemaCompliance + metrics.contentCompleteness * weights.contentCompleteness + metrics.metaPromptQuality * weights.metaPromptQuality + metrics.fileRelevance * weights.fileRelevance + metrics.tokenEfficiency * weights.tokenEfficiency + metrics.taskDecompositionQuality * weights.taskDecompositionQuality); } static getValidationSummary(result) { const { isValid, qualityScore, errors, warnings } = result; return `Validation ${isValid ? 'PASSED' : 'FAILED'} - ` + `Quality: ${(qualityScore * 100).toFixed(1)}% - ` + `Errors: ${errors.length} - ` + `Warnings: ${warnings.length}`; } }