UNPKG

ucm-mcp-server

Version:

Universal Context Manager MCP Server - AI-native artifact management

178 lines 6.46 kB
/** * Utility functions for parsing UCM paths into component parts */ /** * Parse a UCM path into its component parts * Supports formats like: * - "utaba/main/commands/user" (namespace only) * - "utaba/main/commands/user/CreateUserCommand.ts" (with filename) * - "utaba/main/commands/user/CreateUserCommand.ts@1.0.0" (with version) */ export function parsePath(path) { if (!path) path = ""; // Remove leading/trailing slashes const cleanPath = path.replace(/^\/+|\/+$/g, ''); // Split into parts const parts = cleanPath.split('/'); const result = { author: parts.length > 0 ? parts[0] : undefined, repository: parts.length > 1 ? parts[1] : undefined, category: parts.length > 2 ? parts[2] : undefined, subcategory: parts.length > 3 ? parts[3] : undefined, }; if (parts.length > 4) { let filenameParts = parts[4].split('@'); //filename result.filename = filenameParts[0]; if (filenameParts.length > 1) { result.version = filenameParts[1]; } } return result; } /** * Build a namespace path from components (author/repository/category/subcategory) */ export function buildNamespacePath(author, repository, category, subcategory) { return `${author}/${repository}/${category}/${subcategory}`; } /** * Build a full path with filename and optional version */ export function buildFullPath(author, repository, category, subcategory, filename, version) { let path = buildNamespacePath(author, repository, category, subcategory); if (filename) { path += `/${filename}`; if (version) { path += `@${version}`; } } return path; } /** * Extract namespace from a full path */ export function extractNamespace(path) { const parsed = parsePath(path); return buildNamespacePath(parsed.author || '', parsed.repository || '', parsed.category || '', parsed.subcategory || ''); } /** * Validate if a path follows UCM naming conventions */ export function validatePath(path) { const errors = []; try { const parsed = parsePath(path); // Validate author (lowercase alphanumeric and hyphens) if (!/^[a-z0-9-]+$/.test(parsed.author || '')) { errors.push('Author must contain only lowercase letters, numbers, and hyphens'); } // Validate repository (lowercase alphanumeric and hyphens) if (!/^[a-z0-9-]+$/.test(parsed.repository || '')) { errors.push('Repository must contain only lowercase letters, numbers, and hyphens'); } // Validate category (must be valid UCM category) const validCategories = ['commands', 'services', 'patterns', 'implementations', 'contracts', 'guidance', 'project']; if (!validCategories.includes(parsed.category || '')) { errors.push(`Category must be one of: ${validCategories.join(', ')}`); } // Validate subcategory (lowercase alphanumeric and hyphens) if (!/^[a-z0-9-]+$/.test(parsed.subcategory || '')) { errors.push('Subcategory must contain only lowercase letters, numbers, and hyphens'); } // Validate filename if present if (parsed.filename) { if (!/^[a-zA-Z0-9._-]+\.[a-z]+$/.test(parsed.filename)) { errors.push('Filename must be valid with an extension'); } } // Validate version if present if (parsed.version) { if (!/^v?\d+\.\d+\.\d+(-[a-z0-9-]+)?(\+[a-z0-9-]+)?$/.test(parsed.version)) { errors.push('Version must follow semantic versioning (e.g., 1.0.0, v2.1.0)'); } } } catch (error) { errors.push(error instanceof Error ? error.message : 'Invalid path format'); } return { isValid: errors.length === 0, errors }; } /** * Compare two semantic versions * Returns: -1 if version1 < version2, 0 if equal, 1 if version1 > version2 */ export function compareVersions(version1, version2) { // Normalize versions by removing 'v' prefix if present const v1 = version1.replace(/^v/, ''); const v2 = version2.replace(/^v/, ''); // Split versions into parts const parts1 = v1.split(/[.-]/); const parts2 = v2.split(/[.-]/); // Compare major, minor, patch for (let i = 0; i < 3; i++) { const num1 = parseInt(parts1[i] || '0', 10); const num2 = parseInt(parts2[i] || '0', 10); if (num1 > num2) return 1; if (num1 < num2) return -1; } // If major.minor.patch are equal, compare pre-release versions const preRelease1 = parts1.slice(3).join('.'); const preRelease2 = parts2.slice(3).join('.'); // No pre-release is greater than pre-release if (!preRelease1 && preRelease2) return 1; if (preRelease1 && !preRelease2) return -1; if (!preRelease1 && !preRelease2) return 0; // Compare pre-release versions lexically return preRelease1.localeCompare(preRelease2); } /** * Check if a version is greater than another version */ export function isVersionGreater(version1, version2) { return compareVersions(version1, version2) > 0; } /** * Check if a version is less than another version */ export function isVersionLess(version1, version2) { return compareVersions(version1, version2) < 0; } /** * Check if two versions are equal */ export function isVersionEqual(version1, version2) { return compareVersions(version1, version2) === 0; } /** * Increment the patch version of a semantic version * Examples: 1.0.0 -> 1.0.1, 2.1.5 -> 2.1.6 */ export function incrementPatchVersion(version) { // Normalize version by removing 'v' prefix if present const normalized = version.replace(/^v/, ''); // Split version into parts const parts = normalized.split('.'); if (parts.length < 3) { throw new Error(`Invalid semantic version format: ${version}`); } // Parse major, minor, patch const major = parseInt(parts[0], 10); const minor = parseInt(parts[1], 10); const patch = parseInt(parts[2].split('-')[0], 10); // Handle pre-release versions if (isNaN(major) || isNaN(minor) || isNaN(patch)) { throw new Error(`Invalid semantic version format: ${version}`); } // Increment patch version return `${major}.${minor}.${patch + 1}`; } //# sourceMappingURL=PathUtils.js.map