UNPKG

virtue-cli

Version:

Personal character development CLI tool for tracking virtues and philosophical alignment

257 lines 8.53 kB
import { randomUUID } from 'crypto'; export class VirtueVersionRepository { database; constructor(database) { this.database = database; } get db() { return this.database.getDatabase(); } /** * Create a new version for a virtue */ async createVersion(version) { const id = randomUUID(); const stmt = this.db.prepare(` INSERT INTO virtue_versions ( id, virtue_id, version_number, name, definition, sub_dimensions, behavioral_indicators, source_type, source_context, is_active, created_reason ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); stmt.run(id, version.virtue_id, version.version_number, version.name, version.definition, version.sub_dimensions, version.behavioral_indicators, version.source_type, version.source_context, version.is_active ? 1 : 0, version.created_reason); return this.getVersion(id); } /** * Get a specific version by ID */ getVersion(id) { const stmt = this.db.prepare('SELECT * FROM virtue_versions WHERE id = ?'); const version = stmt.get(id); if (version) { // Convert SQLite boolean version.is_active = !!version.is_active; } return version; } /** * Get all versions for a virtue */ getVersionsForVirtue(virtueId) { const stmt = this.db.prepare(` SELECT * FROM virtue_versions WHERE virtue_id = ? ORDER BY version_number DESC `); const versions = stmt.all(virtueId); // Convert SQLite booleans return versions.map(v => ({ ...v, is_active: !!v.is_active })); } /** * Get the active version for a virtue */ getActiveVersion(virtueId) { const stmt = this.db.prepare(` SELECT * FROM virtue_versions WHERE virtue_id = ? AND is_active = 1 `); const version = stmt.get(virtueId); if (version) { version.is_active = true; return version; } return null; } /** * Get a specific version by virtue ID and version number */ getVersionByNumber(virtueId, versionNumber) { const stmt = this.db.prepare(` SELECT * FROM virtue_versions WHERE virtue_id = ? AND version_number = ? `); const version = stmt.get(virtueId, versionNumber); if (version) { version.is_active = !!version.is_active; return version; } return null; } /** * Activate a specific version (deactivates all others) */ async activateVersion(virtueId, versionNumber) { const db = this.db; try { db.prepare('BEGIN').run(); // Deactivate all versions db.prepare(` UPDATE virtue_versions SET is_active = 0 WHERE virtue_id = ? `).run(virtueId); // Activate the specific version const result = db.prepare(` UPDATE virtue_versions SET is_active = 1 WHERE virtue_id = ? AND version_number = ? `).run(virtueId, versionNumber); // Update the virtue's current_version db.prepare(` UPDATE virtues SET current_version = ? WHERE id = ? `).run(versionNumber, virtueId); db.prepare('COMMIT').run(); return result.changes > 0; } catch (error) { db.prepare('ROLLBACK').run(); throw error; } } /** * Get the next version number for a virtue */ async getNextVersionNumber(virtueId) { const stmt = this.db.prepare(` SELECT MAX(version_number) as max_version FROM virtue_versions WHERE virtue_id = ? `); const result = stmt.get(virtueId); return (result.max_version || 0) + 1; } /** * Create a version comparison record */ async createComparison(comparison) { const id = randomUUID(); const stmt = this.db.prepare(` INSERT INTO virtue_version_comparisons ( id, virtue_id, from_version, to_version, changes_summary ) VALUES (?, ?, ?, ?, ?) `); stmt.run(id, comparison.virtue_id, comparison.from_version, comparison.to_version, comparison.changes_summary); return this.getComparison(id); } /** * Get a comparison by ID */ getComparison(id) { const stmt = this.db.prepare(` SELECT * FROM virtue_version_comparisons WHERE id = ? `); return stmt.get(id); } /** * Get usage statistics for a virtue version */ async getVersionStats(virtueId, versionNumber) { const db = this.db; // Get usage count and score averages const usageStmt = db.prepare(` SELECT COUNT(*) as times_used, AVG(json_extract(de.virtue_scores, '$."' || ? || '".want')) as avg_want, AVG(json_extract(de.virtue_scores, '$."' || ? || '".pull')) as avg_pull, MIN(de.date) as first_used, MAX(de.date) as last_used FROM daily_entries de WHERE de.profile_id IN ( SELECT profile_id FROM virtues WHERE id = ? ) AND ( de.virtue_versions_used IS NULL OR json_extract(de.virtue_versions_used, '$."' || ? || '"') = ? ) `); const usageResult = usageStmt.get(virtueId, virtueId, virtueId, virtueId, versionNumber); // Calculate active days let activeDays = 0; if (usageResult.first_used && usageResult.last_used) { const firstDate = new Date(usageResult.first_used); const lastDate = new Date(usageResult.last_used); activeDays = Math.floor((lastDate.getTime() - firstDate.getTime()) / (1000 * 60 * 60 * 24)) + 1; } return { version_number: versionNumber, times_used: usageResult.times_used || 0, average_want_score: usageResult.avg_want || 0, average_pull_score: usageResult.avg_pull || 0, active_days: activeDays, first_used: usageResult.first_used, last_used: usageResult.last_used }; } /** * Get a version with parsed sub-dimensions and indicators */ async getVersionWithDetails(virtueId, versionNumber) { const version = this.getVersionByNumber(virtueId, versionNumber); if (!version) { return null; } const details = { ...version, parsed_sub_dimensions: undefined, parsed_behavioral_indicators: undefined }; // Parse sub-dimensions if (version.sub_dimensions) { try { details.parsed_sub_dimensions = JSON.parse(version.sub_dimensions); } catch { // Leave as undefined if parsing fails } } // Parse behavioral indicators if (version.behavioral_indicators) { try { details.parsed_behavioral_indicators = JSON.parse(version.behavioral_indicators); } catch { // Leave as undefined if parsing fails } } return details; } /** * Delete a version (if not the last one) */ async deleteVersion(virtueId, versionNumber) { const db = this.db; // Check if it's the only version const countStmt = db.prepare(` SELECT COUNT(*) as count FROM virtue_versions WHERE virtue_id = ? `); const { count } = countStmt.get(virtueId); if (count <= 1) { throw new Error('Cannot delete the only version of a virtue'); } // Check if it's the active version const version = this.getVersionByNumber(virtueId, versionNumber); if (version?.is_active) { throw new Error('Cannot delete the active version. Please activate another version first.'); } const stmt = db.prepare(` DELETE FROM virtue_versions WHERE virtue_id = ? AND version_number = ? `); const result = stmt.run(virtueId, versionNumber); // Update total_versions count if (result.changes > 0) { db.prepare(` UPDATE virtues SET total_versions = total_versions - 1 WHERE id = ? `).run(virtueId); } return result.changes > 0; } } //# sourceMappingURL=VirtueVersionRepository.js.map