UNPKG

virtue-cli

Version:

Personal character development CLI tool for tracking virtues and philosophical alignment

259 lines 11.1 kB
import { VirtueVersionRepository } from '../database/VirtueVersionRepository.js'; export class VirtueVersionService { db; repository; constructor(db) { this.db = db; this.repository = new VirtueVersionRepository(db); } /** * Create a new version of a virtue */ async createNewVersion(virtueId, name, definition, subDimensions = null, behavioralIndicators = null, sourceType = 'refinement', sourceContext = null, createdReason = null) { const nextVersionNumber = await this.repository.getNextVersionNumber(virtueId); // Deactivate current active version await this.deactivateAllVersions(virtueId); // Create the new version const newVersion = await this.repository.createVersion({ virtue_id: virtueId, version_number: nextVersionNumber, name, definition, sub_dimensions: subDimensions ? JSON.stringify(subDimensions) : null, behavioral_indicators: behavioralIndicators ? JSON.stringify(behavioralIndicators) : null, source_type: sourceType, source_context: sourceContext, is_active: true, created_reason: createdReason }); // Update virtue's version counts await this.updateVirtueVersionCounts(virtueId); return newVersion; } /** * Activate a specific version */ async activateVersion(virtueId, versionNumber) { const result = await this.repository.activateVersion(virtueId, versionNumber); if (result) { // Create a comparison record if there was a previous active version await this.createVersionComparison(virtueId, versionNumber); } return result; } /** * Get all versions for a virtue with details */ async getVersionHistory(virtueId) { const versions = this.repository.getVersionsForVirtue(virtueId); const detailedVersions = []; for (const version of versions) { const detailed = await this.repository.getVersionWithDetails(virtueId, version.version_number); if (detailed) { detailedVersions.push(detailed); } } return detailedVersions; } /** * Get the active version for a virtue */ getActiveVersion(virtueId) { return this.repository.getActiveVersion(virtueId); } /** * Get detailed information for a specific version */ async getVersionDetails(virtueId, versionNumber) { return await this.repository.getVersionWithDetails(virtueId, versionNumber); } /** * Get usage statistics for a version */ async getVersionStatistics(virtueId, versionNumber) { return await this.repository.getVersionStats(virtueId, versionNumber); } /** * Compare two versions of a virtue */ async compareVersions(virtueId, fromVersion, toVersion) { const fromVersionDetails = await this.repository.getVersionWithDetails(virtueId, fromVersion); const toVersionDetails = await this.repository.getVersionWithDetails(virtueId, toVersion); if (!fromVersionDetails || !toVersionDetails) { throw new Error('One or both versions not found'); } const changes = { definitionChanged: fromVersionDetails.definition !== toVersionDetails.definition, nameChanged: fromVersionDetails.name !== toVersionDetails.name, subDimensionsAdded: [], subDimensionsRemoved: [], subDimensionsModified: [], indicatorsAdded: 0, indicatorsRemoved: 0, indicatorsModified: 0 }; // Compare sub-dimensions const fromSubDims = fromVersionDetails.parsed_sub_dimensions || []; const toSubDims = toVersionDetails.parsed_sub_dimensions || []; const fromSubDimNames = new Set(fromSubDims.map(sd => sd.name)); const toSubDimNames = new Set(toSubDims.map(sd => sd.name)); // Find added sub-dimensions for (const toSubDim of toSubDims) { if (!fromSubDimNames.has(toSubDim.name)) { changes.subDimensionsAdded.push(toSubDim.name); } } // Find removed sub-dimensions for (const fromSubDim of fromSubDims) { if (!toSubDimNames.has(fromSubDim.name)) { changes.subDimensionsRemoved.push(fromSubDim.name); } } // Find modified sub-dimensions for (const fromSubDim of fromSubDims) { const toSubDim = toSubDims.find(sd => sd.name === fromSubDim.name); if (toSubDim && (fromSubDim.definition !== toSubDim.definition || JSON.stringify(fromSubDim.indicators) !== JSON.stringify(toSubDim.indicators))) { changes.subDimensionsModified.push(fromSubDim.name); } } // Compare behavioral indicators const fromIndicators = fromVersionDetails.parsed_behavioral_indicators || []; const toIndicators = toVersionDetails.parsed_behavioral_indicators || []; changes.indicatorsAdded = Math.max(0, toIndicators.length - fromIndicators.length); changes.indicatorsRemoved = Math.max(0, fromIndicators.length - toIndicators.length); // Count modified indicators (rough estimate) const minLength = Math.min(fromIndicators.length, toIndicators.length); let modifiedCount = 0; for (let i = 0; i < minLength; i++) { if (fromIndicators[i] !== toIndicators[i]) { modifiedCount++; } } changes.indicatorsModified = modifiedCount; // Store the comparison await this.repository.createComparison({ virtue_id: virtueId, from_version: fromVersion, to_version: toVersion, changes_summary: JSON.stringify(changes) }); return changes; } /** * Delete a version (with safety checks) */ async deleteVersion(virtueId, versionNumber) { const result = await this.repository.deleteVersion(virtueId, versionNumber); if (result) { await this.updateVirtueVersionCounts(virtueId); } return result; } /** * Create a version from an existing virtue (for initial migration) */ async createInitialVersion(virtue) { let subDimensions = null; let behavioralIndicators = null; // Try to extract sub-dimensions from virtue if they exist if (virtue.sub_dimensions && virtue.sub_dimensions.length > 0) { subDimensions = JSON.stringify(virtue.sub_dimensions); // Extract behavioral indicators from sub-dimensions const indicators = []; for (const subDim of virtue.sub_dimensions) { indicators.push(...subDim.indicators); } if (indicators.length > 0) { behavioralIndicators = JSON.stringify(indicators); } } return await this.repository.createVersion({ virtue_id: virtue.id, version_number: 1, name: virtue.name, definition: virtue.definition, sub_dimensions: subDimensions, behavioral_indicators: behavioralIndicators, source_type: virtue.template_source ? 'template' : 'manual', source_context: 'Initial version from existing virtue', is_active: true, created_reason: 'Migration from non-versioned virtue' }); } /** * Refine a virtue definition based on AI or user input */ async refineVirtueDefinition(virtueId, refinements) { const currentVersion = this.repository.getActiveVersion(virtueId); if (!currentVersion) { throw new Error('No active version found for virtue'); } const currentDetails = await this.repository.getVersionWithDetails(virtueId, currentVersion.version_number); if (!currentDetails) { throw new Error('Could not load current version details'); } // Use current values as fallback for undefined refinements const newName = refinements.name ?? currentDetails.name; const newDefinition = refinements.definition ?? currentDetails.definition; const newSubDimensions = refinements.subDimensions ?? currentDetails.parsed_sub_dimensions; const newBehavioralIndicators = refinements.behavioralIndicators ?? currentDetails.parsed_behavioral_indicators; return await this.createNewVersion(virtueId, newName, newDefinition, newSubDimensions, newBehavioralIndicators, 'refinement', refinements.sourceContext || 'User-initiated refinement', refinements.refinementReason); } /** * Get version summary for virtue management */ async getVersionSummary(virtueId) { const versions = this.repository.getVersionsForVirtue(virtueId); const activeVersion = this.repository.getActiveVersion(virtueId); return { totalVersions: versions.length, activeVersion: activeVersion?.version_number || 1, lastModified: versions[0]?.created_at || '', hasMultipleVersions: versions.length > 1 }; } // Private helper methods async deactivateAllVersions(virtueId) { const db = this.db.getDatabase(); db.prepare(` UPDATE virtue_versions SET is_active = 0 WHERE virtue_id = ? `).run(virtueId); } async updateVirtueVersionCounts(virtueId) { const versions = this.repository.getVersionsForVirtue(virtueId); const activeVersion = this.repository.getActiveVersion(virtueId); const db = this.db.getDatabase(); db.prepare(` UPDATE virtues SET total_versions = ?, current_version = ? WHERE id = ? `).run(versions.length, activeVersion?.version_number || 1, virtueId); } async createVersionComparison(virtueId, newActiveVersion) { // Find the previously active version const allVersions = this.repository.getVersionsForVirtue(virtueId); const sortedVersions = allVersions.sort((a, b) => b.version_number - a.version_number); // Find the version that was active before this one let previousVersion = null; for (const version of sortedVersions) { if (version.version_number !== newActiveVersion) { previousVersion = version; break; } } if (previousVersion) { // Create comparison record try { await this.compareVersions(virtueId, previousVersion.version_number, newActiveVersion); } catch (error) { // Log error but don't fail the activation console.warn('Failed to create version comparison:', error); } } } } //# sourceMappingURL=VirtueVersionService.js.map