UNPKG

@utaba/ucm-mcp-server

Version:

Universal Context Manager MCP Server - AI Productivity Platform

530 lines 22.1 kB
import { BaseToolController } from '../base/BaseToolController.js'; import { ValidationUtils } from '../../utils/ValidationUtils.js'; import { parsePath } from '../../utils/PathUtils.js'; export class ListVersionsController extends BaseToolController { constructor(ucmClient, logger) { super(ucmClient, logger); } get name() { return 'mcp_ucm_list_versions'; } get description() { return 'List all available versions of a UCM artifact with detailed version information and comparison'; } get inputSchema() { return { type: 'object', properties: { artifactPath: { type: 'string', description: 'Artifact path without version (e.g., "utaba/commands/create-user/typescript" or "utaba/commands/create-user")', minLength: 5, maxLength: 150 }, includeMetadata: { type: 'boolean', default: false, description: 'Include detailed metadata for each version' }, includeChangeSummary: { type: 'boolean', default: true, description: 'Include summary of changes between versions' }, includeStats: { type: 'boolean', default: false, description: 'Include download and usage statistics for each version' }, sortBy: { type: 'string', enum: ['version', 'date', 'downloads', 'rating'], default: 'version', description: 'Sort versions by specified criteria' }, sortOrder: { type: 'string', enum: ['asc', 'desc'], default: 'desc', description: 'Sort order (newest first by default)' }, limit: { type: 'number', minimum: 1, maximum: 100, default: 20, description: 'Maximum number of versions to return' }, sinceVersion: { type: 'string', pattern: '^[0-9]+\\.[0-9]+\\.[0-9]+', description: 'Only show versions newer than this version' }, versionPattern: { type: 'string', description: 'Filter versions matching this pattern (e.g., "1.*", "*.0.0")' } }, required: ['artifactPath'] }; } async handleExecute(params) { const { artifactPath, includeMetadata = false, includeChangeSummary = true, includeStats = false, sortBy = 'version', sortOrder = 'desc', limit = 20, sinceVersion, versionPattern } = params; // Validate the artifact path (should not include version) this.validateBasePath(artifactPath); if (sinceVersion) { ValidationUtils.validateVersion(sinceVersion); } this.logger.debug('ListVersionsController', `Listing versions for: ${artifactPath}`); try { // Parse the path to get component parts const parsed = parsePath(artifactPath); // Get all versions from UCM API const allVersions = await this.ucmClient.getArtifactVersions(parsed.author, parsed.category, parsed.subcategory); if (!allVersions || allVersions.length === 0) { return { artifactPath, versions: [], totalVersions: 0, message: 'No versions found for this artifact' }; } // Apply filters let filteredVersions = allVersions; if (sinceVersion) { filteredVersions = this.filterVersionsSince(filteredVersions, sinceVersion); } if (versionPattern) { filteredVersions = this.filterVersionsByPattern(filteredVersions, versionPattern); } // Process and enrich version data const enrichedVersions = await this.enrichVersionData(filteredVersions, { includeMetadata, includeChangeSummary, includeStats }); // Sort versions const sortedVersions = this.sortVersions(enrichedVersions, sortBy, sortOrder); // Apply limit const limitedVersions = sortedVersions.slice(0, limit); // Build response const response = { artifactPath, versions: limitedVersions, pagination: { total: filteredVersions.length, returned: limitedVersions.length, hasMore: filteredVersions.length > limit }, sorting: { sortBy, sortOrder }, filters: { sinceVersion, versionPattern }, summary: this.generateVersionSummary(allVersions, filteredVersions), versionAnalysis: this.analyzeVersionHistory(sortedVersions), recommendations: this.generateVersionRecommendations(sortedVersions), metadata: { timestamp: new Date().toISOString(), includeMetadata, includeChangeSummary, includeStats } }; this.logger.info('ListVersionsController', `Listed ${limitedVersions.length} versions (${filteredVersions.length} total)`); return response; } catch (error) { this.logger.error('ListVersionsController', `Failed to list versions for: ${artifactPath}`, '', error); throw error; } } validateBasePath(path) { // Ensure path doesn't end with a version number const pathParts = path.split('/'); const lastPart = pathParts[pathParts.length - 1]; if (lastPart.match(/^[0-9]+\.[0-9]+\.[0-9]+/)) { throw this.formatError(new Error('Artifact path should not include version number')); } if (pathParts.length < 3) { throw this.formatError(new Error('Artifact path must include at least author/category/subcategory')); } } filterVersionsSince(versions, sinceVersion) { return versions.filter(version => { const versionNumber = version.metadata?.version || '0.0.0'; return this.compareVersions(versionNumber, sinceVersion) > 0; }); } filterVersionsByPattern(versions, pattern) { const regex = new RegExp(pattern.replace(/\*/g, '.*').replace(/\./g, '\\.')); return versions.filter(version => { const versionNumber = version.metadata?.version || '0.0.0'; return regex.test(versionNumber); }); } async enrichVersionData(versions, options) { const { includeMetadata, includeChangeSummary, includeStats } = options; const enrichedVersions = []; for (let i = 0; i < versions.length; i++) { const version = versions[i]; const enriched = { version: version.metadata?.version || 'unknown', path: version.path, publishedAt: version.publishedAt, lastUpdated: version.lastUpdated, contractVersion: version.metadata?.contractVersion, // Basic version information releaseType: this.determineReleaseType(version.metadata?.version), isPreRelease: this.isPreRelease(version.metadata?.version), lifecycle: this.determineLifecycleStage(version), stability: this.assessVersionStability(version) }; // Include metadata if requested if (includeMetadata && version.metadata) { enriched.metadata = { ...version.metadata, qualityScore: this.calculateQualityScore(version.metadata), completeness: this.assessMetadataCompleteness(version.metadata) }; } // Include change summary if requested if (includeChangeSummary) { const previousVersion = i < versions.length - 1 ? versions[i + 1] : null; enriched.changeSummary = await this.generateChangeSummary(version, previousVersion); } // Include statistics if requested if (includeStats) { enriched.statistics = { downloadCount: this.getDownloadCount(version), rating: this.calculateRating(version), usageMetrics: this.getUsageMetrics(version), adoptionRate: this.calculateAdoptionRate(version, versions) }; } enrichedVersions.push(enriched); } return enrichedVersions; } sortVersions(versions, sortBy, sortOrder) { return versions.sort((a, b) => { let comparison = 0; switch (sortBy) { case 'version': comparison = this.compareVersions(a.version, b.version); break; case 'date': const aDate = new Date(a.publishedAt || 0); const bDate = new Date(b.publishedAt || 0); comparison = aDate.getTime() - bDate.getTime(); break; case 'downloads': const aDownloads = a.statistics?.downloadCount || 0; const bDownloads = b.statistics?.downloadCount || 0; comparison = aDownloads - bDownloads; break; case 'rating': const aRating = a.statistics?.rating || 0; const bRating = b.statistics?.rating || 0; comparison = aRating - bRating; break; default: comparison = this.compareVersions(a.version, b.version); } return sortOrder === 'desc' ? -comparison : comparison; }); } generateVersionSummary(allVersions, filteredVersions) { const versionNumbers = allVersions.map(v => v.metadata?.version || '0.0.0'); const latest = this.getLatestVersion(versionNumbers); const oldest = this.getOldestVersion(versionNumbers); return { totalVersions: allVersions.length, filteredVersions: filteredVersions.length, latestVersion: latest, oldestVersion: oldest, versionSpan: this.calculateVersionSpan(oldest, latest), releaseFrequency: this.calculateReleaseFrequency(allVersions), averageTimeBetweenReleases: this.calculateAverageTimeBetweenReleases(allVersions) }; } analyzeVersionHistory(versions) { const analysis = { patterns: { majorReleases: 0, minorReleases: 0, patchReleases: 0, preReleases: 0 }, trends: { releaseVelocity: 'stable', breakingChangeFrequency: 'low', stabilityTrend: 'improving' }, insights: [] }; // Analyze release patterns versions.forEach(version => { switch (version.releaseType) { case 'major': analysis.patterns.majorReleases++; break; case 'minor': analysis.patterns.minorReleases++; break; case 'patch': analysis.patterns.patchReleases++; break; default: if (version.isPreRelease) { analysis.patterns.preReleases++; } } }); // Generate insights if (analysis.patterns.majorReleases > versions.length * 0.3) { analysis.insights.push('Frequent major releases - API may be evolving rapidly'); } if (analysis.patterns.patchReleases > versions.length * 0.6) { analysis.insights.push('High patch release frequency - good maintenance practices'); } if (analysis.patterns.preReleases > 0) { analysis.insights.push('Pre-release versions available - bleeding edge features'); } return analysis; } generateVersionRecommendations(versions) { const recommendations = { recommended: null, alternatives: [], warnings: [] }; if (versions.length === 0) { return recommendations; } // Find recommended version (latest stable) const stableVersions = versions.filter(v => !v.isPreRelease && v.stability !== 'experimental'); if (stableVersions.length > 0) { recommendations.recommended = { version: stableVersions[0].version, reason: 'Latest stable version', confidence: 'high' }; } else { recommendations.recommended = { version: versions[0].version, reason: 'Latest available version', confidence: 'medium' }; } // Find alternatives const majorVersions = this.groupByMajorVersion(versions); for (const [major, versionList] of Object.entries(majorVersions)) { if (versionList.length > 0 && versionList[0].version !== recommendations.recommended.version) { recommendations.alternatives.push({ version: versionList[0].version, reason: `Latest in v${major}.x series`, stability: versionList[0].stability }); } } // Generate warnings const latestVersion = versions[0]; if (latestVersion?.isPreRelease) { recommendations.warnings.push('Latest version is a pre-release - use with caution in production'); } if (latestVersion?.lifecycle === 'legacy') { recommendations.warnings.push('This artifact may be deprecated - consider alternatives'); } return recommendations; } async generateChangeSummary(version, previousVersion) { if (!previousVersion) { return { isFirst: true, message: 'Initial version' }; } const summary = { isFirst: false, previousVersion: previousVersion.metadata?.version, changeType: this.determineChangeType(previousVersion.metadata?.version, version.metadata?.version), timespan: this.calculateTimespan(previousVersion.publishedAt, version.publishedAt), breaking: this.hasBreakingChanges(previousVersion, version), features: this.detectNewFeatures(previousVersion, version), improvements: this.detectImprovements(previousVersion, version), fixes: this.detectFixes(previousVersion, version) }; return summary; } // Helper methods compareVersions(version1, version2) { const v1Parts = version1.split('.').map(Number); const v2Parts = version2.split('.').map(Number); for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) { const v1Part = v1Parts[i] || 0; const v2Part = v2Parts[i] || 0; if (v1Part !== v2Part) { return v1Part - v2Part; } } return 0; } determineReleaseType(version) { if (!version) return 'unknown'; const [major, minor, patch] = version.split('.').map(Number); if (major > 0 && minor === 0 && patch === 0) return 'major'; if (minor > 0 && patch === 0) return 'minor'; return 'patch'; } isPreRelease(version) { return !!(version && (version.includes('alpha') || version.includes('beta') || version.includes('rc'))); } determineLifecycleStage(version) { const publishedAt = new Date(version.publishedAt || Date.now()); const daysSincePublished = (Date.now() - publishedAt.getTime()) / (1000 * 60 * 60 * 24); if (daysSincePublished < 30) return 'new'; if (daysSincePublished < 365) return 'active'; if (daysSincePublished < 1095) return 'mature'; return 'legacy'; } assessVersionStability(version) { if (this.isPreRelease(version.metadata?.version)) return 'experimental'; if (this.determineLifecycleStage(version) === 'new') return 'testing'; return 'stable'; } calculateQualityScore(metadata) { let score = 50; if (metadata.description?.length > 20) score += 20; if (metadata.tags?.length > 0) score += 10; if (metadata.dependencies) score += 10; if (metadata.contractVersion) score += 10; return Math.min(score, 100); } assessMetadataCompleteness(metadata) { const fields = ['name', 'description', 'author', 'category', 'version']; const presentFields = fields.filter(field => metadata[field]); return Math.round((presentFields.length / fields.length) * 100); } getLatestVersion(versions) { return versions.sort((a, b) => this.compareVersions(b, a))[0]; } getOldestVersion(versions) { return versions.sort((a, b) => this.compareVersions(a, b))[0]; } calculateVersionSpan(oldest, latest) { const comparison = this.compareVersions(latest, oldest); if (comparison === 0) return 'Single version'; const [oldMajor] = oldest.split('.').map(Number); const [newMajor] = latest.split('.').map(Number); const majorSpan = newMajor - oldMajor; if (majorSpan > 0) return `${majorSpan} major version${majorSpan > 1 ? 's' : ''}`; return 'Minor/patch versions'; } calculateReleaseFrequency(versions) { if (versions.length < 2) return 'insufficient-data'; const timespan = this.calculateAverageTimeBetweenReleases(versions); if (timespan < 30) return 'high'; if (timespan < 90) return 'moderate'; return 'low'; } calculateAverageTimeBetweenReleases(versions) { if (versions.length < 2) return 0; const dates = versions .map(v => new Date(v.publishedAt || 0)) .sort((a, b) => a.getTime() - b.getTime()); let totalDays = 0; for (let i = 1; i < dates.length; i++) { totalDays += (dates[i].getTime() - dates[i - 1].getTime()) / (1000 * 60 * 60 * 24); } return totalDays / (dates.length - 1); } groupByMajorVersion(versions) { const groups = {}; versions.forEach(version => { const [major] = version.version.split('.'); if (!groups[major]) groups[major] = []; groups[major].push(version); }); return groups; } determineChangeType(oldVersion, newVersion) { const comparison = this.compareVersions(newVersion, oldVersion); const [oldMajor, oldMinor] = oldVersion.split('.').map(Number); const [newMajor, newMinor] = newVersion.split('.').map(Number); if (newMajor > oldMajor) return 'major'; if (newMinor > oldMinor) return 'minor'; return 'patch'; } calculateTimespan(oldDate, newDate) { const old = new Date(oldDate || 0); const current = new Date(newDate || Date.now()); const diffMs = current.getTime() - old.getTime(); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); if (diffDays < 1) return 'same day'; if (diffDays < 7) return `${diffDays} days`; if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks`; if (diffDays < 365) return `${Math.floor(diffDays / 30)} months`; return `${Math.floor(diffDays / 365)} years`; } // Simulated methods for demo hasBreakingChanges(oldVersion, newVersion) { return oldVersion.metadata?.contractVersion !== newVersion.metadata?.contractVersion; } detectNewFeatures(oldVersion, newVersion) { const oldTags = oldVersion.metadata?.tags || []; const newTags = newVersion.metadata?.tags || []; return newTags.filter((tag) => !oldTags.includes(tag)); } detectImprovements(oldVersion, newVersion) { const improvements = []; if ((newVersion.metadata?.description?.length || 0) > (oldVersion.metadata?.description?.length || 0)) { improvements.push('Enhanced documentation'); } return improvements; } detectFixes(oldVersion, newVersion) { return ['General bug fixes and improvements']; // Simulated } getDownloadCount(version) { return Math.floor(Math.random() * 1000); // Simulated } calculateRating(version) { return 3.0 + Math.random() * 2; // Simulated } getUsageMetrics(version) { return { activeUsers: Math.floor(Math.random() * 100), lastAccessed: new Date().toISOString() }; } calculateAdoptionRate(version, allVersions) { return Math.random(); // Simulated } } //# sourceMappingURL=ListVersionsController.js.map