claude-flow-novice
Version:
Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes Local RuVector Accelerator and all CFN skills for complete functionality.
244 lines (243 loc) • 8.28 kB
JavaScript
/**
* Skill Versioning Service
*
* Provides semantic versioning management for skill deployment.
* Part of Task 1.1: Automated Skill Deployment Pipeline
*
* @example
* ```typescript
* const version = await getNextVersion('authentication', 'minor');
* // Returns '1.1.0' if current version is '1.0.0'
*
* const isValid = validateVersion('1.2.3'); // true
* const isValid = validateVersion('v1.2.3'); // false
* ```
*/ import { StandardError, ErrorCode } from '../lib/errors.js';
import { createLogger } from '../lib/logging.js';
const logger = createLogger('skill-versioning');
/**
* Validate semantic version format (x.y.z)
*
* @param version - Version string to validate
* @returns True if valid semantic version format
*/ export function validateVersion(version) {
// Must match semantic versioning format: x.y.z where x, y, z are non-negative integers
const semverRegex = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
return semverRegex.test(version);
}
/**
* Parse semantic version string into components
*
* @param version - Version string to parse
* @returns Parsed version components
* @throws StandardError if version format is invalid
*/ export function parseVersion(version) {
if (!validateVersion(version)) {
throw new StandardError(ErrorCode.VALIDATION_FAILED, `Invalid semantic version format: ${version}`, {
version,
expectedFormat: 'x.y.z'
});
}
const parts = version.split('.').map(Number);
return {
major: parts[0],
minor: parts[1],
patch: parts[2],
raw: version
};
}
/**
* Compare two semantic versions
*
* @param v1 - First version
* @param v2 - Second version
* @returns -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
*/ export function compareVersions(v1, v2) {
const parsed1 = parseVersion(v1);
const parsed2 = parseVersion(v2);
if (parsed1.major !== parsed2.major) {
return parsed1.major > parsed2.major ? 1 : -1;
}
if (parsed1.minor !== parsed2.minor) {
return parsed1.minor > parsed2.minor ? 1 : -1;
}
if (parsed1.patch !== parsed2.patch) {
return parsed1.patch > parsed2.patch ? 1 : -1;
}
return 0;
}
/**
* Increment version based on change type
*
* @param version - Current version
* @param changeType - Type of change (major, minor, patch)
* @returns New version string
*/ export function incrementVersion(version, changeType) {
const parsed = parseVersion(version);
switch(changeType){
case 'major':
return `${parsed.major + 1}.0.0`;
case 'minor':
return `${parsed.major}.${parsed.minor + 1}.0`;
case 'patch':
return `${parsed.major}.${parsed.minor}.${parsed.patch + 1}`;
default:
throw new StandardError(ErrorCode.INVALID_INPUT, `Invalid version change type: ${changeType}`, {
changeType,
validTypes: [
'major',
'minor',
'patch'
]
});
}
}
/**
* Get the next version for a skill based on change type
*
* @param dbService - Database service instance
* @param skillName - Name of the skill
* @param changeType - Type of change (major, minor, patch)
* @returns Next version string
* @throws StandardError if skill not found or version conflict
*/ export async function getNextVersion(dbService, skillName, changeType) {
logger.info('Getting next version for skill', {
skillName,
changeType
});
try {
const adapter = dbService.getAdapter('sqlite');
// Get current version from skills table
const result = await adapter.query('SELECT version FROM skills WHERE name = ? ORDER BY created_at DESC LIMIT 1', [
skillName
]);
if (!result.rows || result.rows.length === 0) {
// No existing version, start at 1.0.0
logger.info('No existing version found, starting at 1.0.0', {
skillName
});
return '1.0.0';
}
const currentVersion = result.rows[0].version;
const nextVersion = incrementVersion(currentVersion, changeType);
logger.info('Calculated next version', {
skillName,
currentVersion,
nextVersion,
changeType
});
return nextVersion;
} catch (error) {
logger.error('Failed to get next version', error, {
skillName,
changeType
});
throw new StandardError(ErrorCode.DB_QUERY_FAILED, `Failed to get next version for skill: ${skillName}`, {
skillName,
changeType
}, error);
}
}
/**
* Check if version already exists for a skill
*
* @param dbService - Database service instance
* @param skillName - Name of the skill
* @param version - Version to check
* @returns True if version exists
*/ export async function versionExists(dbService, skillName, version) {
logger.debug('Checking if version exists', {
skillName,
version
});
try {
const adapter = dbService.getAdapter('sqlite');
const result = await adapter.query('SELECT COUNT(*) as count FROM skills WHERE name = ? AND version = ?', [
skillName,
version
]);
const count = result.rows?.[0]?.count || 0;
const exists = count > 0;
logger.debug('Version existence check complete', {
skillName,
version,
exists
});
return exists;
} catch (error) {
logger.error('Failed to check version existence', error, {
skillName,
version
});
throw new StandardError(ErrorCode.DB_QUERY_FAILED, `Failed to check version existence for skill: ${skillName}`, {
skillName,
version
}, error);
}
}
/**
* Get all versions for a skill, sorted by semantic version
*
* @param dbService - Database service instance
* @param skillName - Name of the skill
* @returns Array of version strings, sorted from oldest to newest
*/ export async function getSkillVersions(dbService, skillName) {
logger.debug('Getting all versions for skill', {
skillName
});
try {
const adapter = dbService.getAdapter('sqlite');
const result = await adapter.query('SELECT version FROM skills WHERE name = ? ORDER BY created_at ASC', [
skillName
]);
const versions = (result.rows || []).map((row)=>row.version);
// Sort by semantic version
versions.sort(compareVersions);
logger.debug('Retrieved skill versions', {
skillName,
count: versions.length
});
return versions;
} catch (error) {
logger.error('Failed to get skill versions', error, {
skillName
});
throw new StandardError(ErrorCode.DB_QUERY_FAILED, `Failed to get versions for skill: ${skillName}`, {
skillName
}, error);
}
}
/**
* Get the latest version for a skill
*
* @param dbService - Database service instance
* @param skillName - Name of the skill
* @returns Latest version string, or null if no versions exist
*/ export async function getLatestVersion(dbService, skillName) {
logger.debug('Getting latest version for skill', {
skillName
});
try {
const versions = await getSkillVersions(dbService, skillName);
if (versions.length === 0) {
logger.debug('No versions found for skill', {
skillName
});
return null;
}
const latestVersion = versions[versions.length - 1];
logger.debug('Retrieved latest version', {
skillName,
latestVersion
});
return latestVersion;
} catch (error) {
logger.error('Failed to get latest version', error, {
skillName
});
throw new StandardError(ErrorCode.DB_QUERY_FAILED, `Failed to get latest version for skill: ${skillName}`, {
skillName
}, error);
}
}
//# sourceMappingURL=skill-versioning.js.map