virtue-cli
Version:
Personal character development CLI tool for tracking virtues and philosophical alignment
259 lines • 11.1 kB
JavaScript
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