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