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.

716 lines (715 loc) 25.3 kB
import { TagManagementService } from './tag-management-service.js'; import logger from '../../../logger.js'; export class MetadataService { static instance; config; tagService; metadataCache = new Map(); changeHistory = new Map(); constructor(config) { this.config = config; this.tagService = TagManagementService.getInstance(config); } static getInstance(config) { if (!MetadataService.instance) { MetadataService.instance = new MetadataService(config); } return MetadataService.instance; } async createTaskMetadata(task, options = {}) { const baseMetadata = this.createBaseMetadata(task.createdBy); let metadata = { ...baseMetadata, tags: await this.createTagCollection(task, options.enhanceTags), complexity: await this.analyzeComplexity(task, options.analyzeComplexity), performance: await this.estimatePerformance(task, options.estimatePerformance), quality: await this.assessQuality(task, options.assessQuality), collaboration: await this.createCollaborationMetadata(task), integration: await this.createIntegrationMetadata(task) }; if (options.useAI) { metadata = await this.enrichTaskMetadataWithAI(task, metadata); } this.metadataCache.set(task.id, metadata); this.recordChange(task.id, { timestamp: new Date(), changedBy: task.createdBy, type: 'create', field: 'metadata', newValue: metadata, reason: 'Initial metadata creation' }); logger.debug({ taskId: task.id, metadata }, 'Created task metadata'); return metadata; } async createEpicMetadata(epic, options = {}) { const baseMetadata = this.createBaseMetadata(epic.metadata.createdBy); let metadata = { ...baseMetadata, tags: await this.createTagCollection({ title: epic.title, description: epic.description, type: 'epic' }, options.enhanceTags), scope: await this.createScopeMetadata(epic), progress: await this.createProgressMetadata(epic), resources: await this.createResourceMetadata(epic) }; if (options.useAI) { metadata = await this.enrichEpicMetadataWithAI(epic, metadata); } this.metadataCache.set(epic.id, metadata); logger.debug({ epicId: epic.id, metadata }, 'Created epic metadata'); return metadata; } async createProjectMetadata(project, options = {}) { const baseMetadata = this.createBaseMetadata(project.metadata.createdBy); let metadata = { ...baseMetadata, tags: await this.createTagCollection({ title: project.name, description: project.description, type: 'project' }, options.enhanceTags), classification: await this.createProjectClassification(project), business: await this.createBusinessMetadata(project), technical: await this.createTechnicalMetadata(project), governance: await this.createGovernanceMetadata(project) }; if (options.useAI) { metadata = await this.enrichProjectMetadataWithAI(project, metadata); } this.metadataCache.set(project.id, metadata); logger.debug({ projectId: project.id, metadata }, 'Created project metadata'); return metadata; } async updateMetadata(entityId, updates, updatedBy, reason) { const existingMetadata = this.metadataCache.get(entityId); if (!existingMetadata) { throw new Error(`Metadata not found for entity: ${entityId}`); } const updatedMetadata = { ...existingMetadata, ...updates, updatedAt: new Date(), updatedBy, version: existingMetadata.version + 1 }; for (const [field, newValue] of Object.entries(updates)) { const previousValue = existingMetadata[field]; if (JSON.stringify(previousValue) !== JSON.stringify(newValue)) { this.recordChange(entityId, { timestamp: new Date(), changedBy: updatedBy, type: 'update', field, previousValue, newValue, reason }); } } this.metadataCache.set(entityId, updatedMetadata); logger.debug({ entityId, updates, version: updatedMetadata.version }, 'Updated metadata'); return updatedMetadata; } async validateMetadata(metadata) { const errors = []; const warnings = []; const suggestions = []; if (!metadata.createdAt) { errors.push({ field: 'createdAt', message: 'Creation date is required', severity: 'error', suggestedFix: 'Set createdAt to current date' }); } if (!metadata.createdBy) { errors.push({ field: 'createdBy', message: 'Creator is required', severity: 'error', suggestedFix: 'Set createdBy to current user' }); } if (metadata.version < 1) { errors.push({ field: 'version', message: 'Version must be positive', severity: 'error', suggestedFix: 'Set version to 1 or higher' }); } const validLifecycles = ['draft', 'active', 'in_progress', 'completed', 'archived', 'deprecated']; if (!validLifecycles.includes(metadata.lifecycle)) { errors.push({ field: 'lifecycle', message: `Invalid lifecycle: ${metadata.lifecycle}`, severity: 'error', suggestedFix: `Use one of: ${validLifecycles.join(', ')}` }); } if (!metadata.attributes || Object.keys(metadata.attributes).length === 0) { warnings.push({ field: 'attributes', message: 'No custom attributes defined', impact: 'medium' }); suggestions.push({ field: 'attributes', description: 'Add custom attributes for better organization', benefit: 'Improved searchability and organization' }); } if (metadata.updatedAt && metadata.updatedAt instanceof Date) { const daysSinceUpdate = (Date.now() - metadata.updatedAt.getTime()) / (1000 * 60 * 60 * 24); if (daysSinceUpdate > 30) { warnings.push({ field: 'updatedAt', message: `Metadata hasn't been updated in ${Math.floor(daysSinceUpdate)} days`, impact: 'low' }); } } return { isValid: errors.length === 0, errors, warnings, suggestions }; } async getMetadataAnalytics(filters = {}) { const allMetadata = Array.from(this.metadataCache.values()); const filteredMetadata = this.applyFilters(allMetadata, filters); const totalEntities = filteredMetadata.length; const changeHistoryEntries = Array.from(this.changeHistory.values()).flat(); return { totalEntities, completeness: { average: this.calculateAverageCompleteness(filteredMetadata), byEntityType: this.calculateCompletenessByType(filteredMetadata), byLifecycle: this.calculateCompletenessByLifecycle(filteredMetadata) }, changeFrequency: this.calculateChangeFrequency(changeHistoryEntries), activeUsers: this.calculateActiveUsers(changeHistoryEntries), commonAttributes: this.calculateCommonAttributes(filteredMetadata), quality: this.calculateQualityMetrics(filteredMetadata) }; } async searchMetadata(filters) { const allMetadata = Array.from(this.metadataCache.values()); return this.applyFilters(allMetadata, filters); } getChangeHistory(entityId) { return this.changeHistory.get(entityId) || []; } createBaseMetadata(createdBy) { return { createdAt: new Date(), updatedAt: new Date(), createdBy, version: 1, lifecycle: 'draft', attributes: {}, changeHistory: [] }; } async createTagCollection(content, enhance = true) { if (enhance) { return this.tagService.enhanceTagCollection(content); } return { functional: [], technical: [], business: [], process: [], quality: [], custom: [], generated: [] }; } async analyzeComplexity(task, analyze = true) { if (!analyze) { return { overallScore: 0.5, technical: 0.5, business: 0.5, integration: 0.5, factors: [], analysis: { computedAt: new Date(), method: 'default', confidence: 0.1 } }; } const technicalComplexity = this.calculateTechnicalComplexity(task); const businessComplexity = this.calculateBusinessComplexity(task); const integrationComplexity = this.calculateIntegrationComplexity(task); const overallScore = (technicalComplexity + businessComplexity + integrationComplexity) / 3; return { overallScore, technical: technicalComplexity, business: businessComplexity, integration: integrationComplexity, factors: await this.identifyComplexityFactors(task), analysis: { computedAt: new Date(), method: 'heuristic', confidence: 0.7 } }; } async estimatePerformance(task, estimate = true) { if (!estimate) { return { estimatedTime: task.estimatedHours * 60, targets: {}, metrics: { efficiency: 0.8, resourceUtilization: 0.7, scalability: 0.6 } }; } return { estimatedTime: task.estimatedHours * 60, targets: { responseTime: 200, throughput: 1000, memoryUsage: 512, cpuUsage: 50 }, metrics: { efficiency: this.calculateEfficiency(task), resourceUtilization: this.calculateResourceUtilization(task), scalability: this.calculateScalability(task) } }; } async assessQuality(task, assess = true) { if (!assess) { return { score: 0.8, dimensions: { codeQuality: 0.8, testCoverage: 0.7, documentation: 0.6, maintainability: 0.8, reliability: 0.9 }, gates: [], standards: [] }; } const dimensions = { codeQuality: 0.8, testCoverage: task.testingRequirements.coverageTarget / 100, documentation: task.qualityCriteria.documentation.length > 0 ? 0.8 : 0.3, maintainability: 0.8, reliability: 0.9 }; const score = Object.values(dimensions).reduce((sum, val) => sum + val, 0) / Object.keys(dimensions).length; return { score, dimensions, gates: await this.createQualityGates(task), standards: ['coding-standards', 'security-standards'] }; } async createCollaborationMetadata(task) { return { assignees: task.assignedAgent ? [task.assignedAgent] : [], reviewers: [], stakeholders: [task.createdBy], patterns: { pairProgramming: false, codeReview: true, mobProgramming: false }, channels: ['slack', 'email'] }; } async createIntegrationMetadata(task) { return { externalSystems: [], dependencies: { internal: task.dependencies, external: [], optional: [] }, integrationPoints: [], contracts: [] }; } recordChange(entityId, change) { if (!this.changeHistory.has(entityId)) { this.changeHistory.set(entityId, []); } this.changeHistory.get(entityId).push(change); const history = this.changeHistory.get(entityId); if (history.length > 100) { this.changeHistory.set(entityId, history.slice(-100)); } } calculateTechnicalComplexity(task) { let complexity = 0.3; complexity += Math.min(task.filePaths.length * 0.1, 0.3); if (task.testingRequirements.coverageTarget > 80) complexity += 0.1; if (task.testingRequirements.unitTests.length > 0) complexity += 0.1; if (task.testingRequirements.integrationTests.length > 0) complexity += 0.1; if (task.qualityCriteria.typeScript) complexity += 0.05; if (task.qualityCriteria.eslint) complexity += 0.05; return Math.min(complexity, 1.0); } calculateBusinessComplexity(task) { let complexity = 0.2; switch (task.priority) { case 'critical': complexity += 0.4; break; case 'high': complexity += 0.3; break; case 'medium': complexity += 0.2; break; case 'low': complexity += 0.1; break; } complexity += Math.min(task.acceptanceCriteria.length * 0.05, 0.2); return Math.min(complexity, 1.0); } calculateIntegrationComplexity(task) { let complexity = 0.1; complexity += Math.min(task.dependencies.length * 0.1, 0.4); complexity += Math.min(task.integrationCriteria.compatibility.length * 0.1, 0.3); complexity += Math.min(task.integrationCriteria.patterns.length * 0.1, 0.2); return Math.min(complexity, 1.0); } async identifyComplexityFactors(task) { const factors = []; if (task.filePaths.length > 5) { factors.push({ name: 'Multiple Files', weight: 0.3, description: 'Task affects multiple files', category: 'technical' }); } if (task.dependencies.length > 3) { factors.push({ name: 'Complex Dependencies', weight: 0.4, description: 'Task has multiple dependencies', category: 'integration' }); } if (task.priority === 'critical') { factors.push({ name: 'Critical Priority', weight: 0.5, description: 'Task is business critical', category: 'business' }); } return factors; } calculateEfficiency(task) { if (task.estimatedHours <= 1) return 0.9; if (task.estimatedHours <= 4) return 0.8; if (task.estimatedHours <= 8) return 0.7; return 0.6; } calculateResourceUtilization(task) { switch (task.type) { case 'development': return 0.8; case 'testing': return 0.7; case 'documentation': return 0.6; case 'research': return 0.5; default: return 0.7; } } calculateScalability(task) { const architecturalKeywords = ['architecture', 'framework', 'infrastructure', 'scalability']; const hasArchitecturalImpact = architecturalKeywords.some(keyword => task.title.toLowerCase().includes(keyword) || task.description.toLowerCase().includes(keyword)); return hasArchitecturalImpact ? 0.9 : 0.6; } async createQualityGates(task) { const gates = []; if (task.testingRequirements.coverageTarget > 0) { gates.push({ name: 'Test Coverage', criteria: `Minimum ${task.testingRequirements.coverageTarget}% coverage`, status: 'pending', result: { value: 0, threshold: task.testingRequirements.coverageTarget, message: 'Test coverage gate' } }); } if (task.qualityCriteria.typeScript) { gates.push({ name: 'TypeScript Compliance', criteria: 'No TypeScript errors', status: 'pending' }); } return gates; } applyFilters(metadata, filters) { let filtered = metadata; if (filters.lifecycles && filters.lifecycles.length > 0) { filtered = filtered.filter(m => filters.lifecycles.includes(m.lifecycle)); } if (filters.createdBy && filters.createdBy.length > 0) { filtered = filtered.filter(m => filters.createdBy.includes(m.createdBy)); } if (filters.dateRange) { filtered = filtered.filter(m => m.createdAt >= filters.dateRange.start && m.createdAt <= filters.dateRange.end); } if (filters.minVersion) { filtered = filtered.filter(m => m.version >= filters.minVersion); } return filtered; } calculateAverageCompleteness(metadata) { if (metadata.length === 0) return 0; const totalCompleteness = metadata.reduce((sum, m) => { const attributeCount = Object.keys(m.attributes).length; const completeness = Math.min(attributeCount / 5, 1); return sum + completeness; }, 0); return totalCompleteness / metadata.length; } calculateCompletenessByType(_metadata) { return { task: 0.8, epic: 0.7, project: 0.9 }; } calculateCompletenessByLifecycle(metadata) { const byLifecycle = { draft: [], active: [], in_progress: [], completed: [], archived: [], deprecated: [] }; metadata.forEach(m => { byLifecycle[m.lifecycle].push(m); }); const result = {}; for (const [lifecycle, items] of Object.entries(byLifecycle)) { result[lifecycle] = this.calculateAverageCompleteness(items); } return result; } calculateChangeFrequency(changes) { const now = new Date(); const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000); const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const oneMonthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); return { daily: changes.filter(c => c.timestamp >= oneDayAgo).length, weekly: changes.filter(c => c.timestamp >= oneWeekAgo).length, monthly: changes.filter(c => c.timestamp >= oneMonthAgo).length }; } calculateActiveUsers(changes) { const userChanges = new Map(); changes.forEach(change => { const count = userChanges.get(change.changedBy) || 0; userChanges.set(change.changedBy, count + 1); }); const totalChanges = changes.length; return Array.from(userChanges.entries()) .map(([user, changes]) => ({ user, changes, percentage: (changes / totalChanges) * 100 })) .sort((a, b) => b.changes - a.changes) .slice(0, 10); } calculateCommonAttributes(metadata) { const attributeCounts = new Map(); let totalAttributes = 0; metadata.forEach(m => { Object.keys(m.attributes).forEach(attr => { const count = attributeCounts.get(attr) || 0; attributeCounts.set(attr, count + 1); totalAttributes++; }); }); return Array.from(attributeCounts.entries()) .map(([attribute, usage]) => ({ attribute, usage, percentage: (usage / totalAttributes) * 100 })) .sort((a, b) => b.usage - a.usage) .slice(0, 10); } calculateQualityMetrics(_metadata) { return { average: 0.8, distribution: { excellent: 30, good: 45, fair: 20, poor: 5 }, trends: { improving: 60, stable: 30, declining: 10 } }; } async enrichTaskMetadataWithAI(task, metadata) { return metadata; } async enrichEpicMetadataWithAI(epic, metadata) { return metadata; } async enrichProjectMetadataWithAI(project, metadata) { return metadata; } async createScopeMetadata(_epic) { return { definition: _epic.description, boundaries: [], includes: [], excludes: [], changes: [] }; } async createProgressMetadata(_epic) { return { percentage: 0, milestones: [], tracking: { method: 'manual', frequency: 'weekly', lastUpdated: new Date() }, blockers: [] }; } async createResourceMetadata(_epic) { return { allocated: { people: 1, budget: 10000, tools: [], timeframe: { start: new Date(), end: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) } }, utilization: { actual: 0, planned: 1, efficiency: 0.8 }, constraints: [] }; } async createProjectClassification(_project) { return { type: 'greenfield', size: 'medium', domain: [], methodologies: ['agile'], riskLevel: 'medium' }; } async createBusinessMetadata(_project) { return { objectives: [], successMetrics: [], stakeholders: [], value: { financial: 0, strategic: 0, operational: 0 }, market: { segment: '', competition: [], opportunities: [] } }; } async createTechnicalMetadata(project) { return { architecture: [], stack: { frontend: project.techStack?.frameworks?.filter(f => ['react', 'vue', 'angular'].includes(f.toLowerCase())) || [], backend: project.techStack?.frameworks?.filter(f => ['express', 'fastify', 'koa'].includes(f.toLowerCase())) || [], database: [], infrastructure: [], tools: project.techStack?.tools || [] }, constraints: [], performance: { responseTime: 200, throughput: 1000, availability: 99.9, scalability: 'horizontal' }, security: { classification: 'internal', compliance: [], threats: [] } }; } async createGovernanceMetadata(_project) { return { approvals: [], compliance: [], audit: [], risk: { overallScore: 0.5, categories: { technical: 0.4, business: 0.5, security: 0.3, operational: 0.6 }, risks: [], mitigation: [], assessedAt: new Date(), nextReview: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000) }, changeControl: { process: 'standard', approvers: [], documentation: [] } }; } async cleanup() { this.metadataCache.clear(); this.changeHistory.clear(); await this.tagService.cleanup(); } }