virtue-cli
Version:
Personal character development CLI tool for tracking virtues and philosophical alignment
476 lines (470 loc) ⢠23.6 kB
JavaScript
import { intro, outro, select, confirm, log, spinner } from '@clack/prompts';
import { ClaudeService } from '../services/ClaudeService.js';
import { calculateAlignment, calculateCoherence, formatDateDisplay } from '../utils/helpers.js';
import picocolors from 'picocolors';
export class DriftDetection {
db;
profile;
claudeService;
constructor(db, profile) {
this.db = db;
this.profile = profile;
this.claudeService = new ClaudeService();
}
async run() {
intro(picocolors.magenta('š§ Philosophical Drift Assessment'));
try {
// Check if there's enough data for meaningful drift detection
const hasEnoughData = await this.checkDataSufficiency();
if (!hasEnoughData) {
log.warning('Not enough data for drift analysis. Need at least 30 entries over 6+ weeks.');
outro(picocolors.yellow('š” Continue your daily practice and return in a few weeks!'));
return;
}
const analysisType = await select({
message: 'What type of drift assessment would you like?',
options: [
{ value: 'comprehensive', label: 'š Comprehensive Analysis', hint: 'Full drift assessment with AI insights' },
{ value: 'virtue-specific', label: 'šÆ Virtue-Specific Drift', hint: 'Focus on individual virtue changes' },
{ value: 'philosophical', label: 'šļø Philosophical Alignment', hint: 'Check alignment with core philosophy' },
{ value: 'intervention', label: 'ā” Drift Intervention', hint: 'Get corrective action plan' }
]
});
switch (analysisType) {
case 'comprehensive':
await this.runComprehensiveAnalysis();
break;
case 'virtue-specific':
await this.runVirtueSpecificAnalysis();
break;
case 'philosophical':
await this.runPhilosophicalAnalysis();
break;
case 'intervention':
await this.runInterventionPlanning();
break;
}
}
catch (error) {
log.error(`Drift analysis failed: ${error}`);
outro(picocolors.red('Drift analysis failed'));
}
}
async checkDataSufficiency() {
const allEntries = this.db.getEntriesForProfile(this.profile.id);
if (allEntries.length < 30)
return false;
// Check time span - need at least 6 weeks
const sortedEntries = allEntries.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
const firstEntry = new Date(sortedEntries[0].date);
const lastEntry = new Date(sortedEntries[sortedEntries.length - 1].date);
const daysDiff = Math.floor((lastEntry.getTime() - firstEntry.getTime()) / (1000 * 60 * 60 * 24));
return daysDiff >= 42; // 6 weeks
}
async runComprehensiveAnalysis() {
const s = spinner();
s.start('Analyzing philosophical drift patterns...');
const driftAnalysis = await this.generateDriftAnalysis();
s.stop('Drift analysis complete!');
this.displayDriftOverview(driftAnalysis);
this.displayVirtueSpecificDrift(driftAnalysis);
this.displayOverallDriftAssessment(driftAnalysis);
// AI insights if available
if (this.claudeService.isConfigured()) {
const aiAnalysis = await this.generateAIDriftInsights(driftAnalysis);
this.displayAIInsights(aiAnalysis);
const wantsIntervention = await confirm({
message: 'Would you like a personalized intervention plan to address any concerning drift?',
initialValue: driftAnalysis.overallDrift.riskLevel !== 'low'
});
if (wantsIntervention && typeof wantsIntervention !== 'symbol') {
const interventionPlan = await this.createInterventionPlan(driftAnalysis);
this.displayInterventionPlan(interventionPlan);
}
}
else {
log.info('š” AI-powered drift insights available when run via Claude Code');
}
outro(picocolors.green('š§ Drift assessment complete! Stay aligned with your values.'));
}
async runVirtueSpecificAnalysis() {
const virtues = this.db.getVirtuesForProfile(this.profile.id);
const selectedVirtue = await select({
message: 'Which virtue would you like to analyze for drift?',
options: virtues.map(v => ({
value: v.id,
label: `${v.emoji} ${v.name}`,
hint: v.definition
}))
});
const s = spinner();
s.start('Analyzing virtue-specific drift...');
const driftAnalysis = await this.generateDriftAnalysis();
const virtueDrift = driftAnalysis.virtualDrift.find(vd => vd.virtue.id === selectedVirtue);
s.stop('Analysis complete!');
if (virtueDrift) {
this.displaySingleVirtueDrift(virtueDrift);
}
outro(picocolors.green('šÆ Virtue analysis complete!'));
}
async runPhilosophicalAnalysis() {
const s = spinner();
s.start('Assessing philosophical alignment...');
const driftAnalysis = await this.generateDriftAnalysis();
s.stop('Assessment complete!');
console.log(picocolors.bold('\nšļø Philosophical Alignment Assessment'));
console.log('ā'.repeat(60));
const { philosophicalAlignment, riskLevel } = driftAnalysis.overallDrift;
const alignmentColor = philosophicalAlignment >= 0.8 ? picocolors.green :
philosophicalAlignment >= 0.6 ? picocolors.yellow : picocolors.red;
console.log(`š Core Alignment: ${alignmentColor((philosophicalAlignment * 100).toFixed(1) + '%')}`);
console.log(`ā ļø Risk Level: ${this.formatRiskLevel(riskLevel)}`);
console.log(`š Template: ${picocolors.cyan(this.profile.philosophical_template)}`);
// Show virtue alignment with core philosophy
console.log(picocolors.bold('\nšÆ Virtue-Philosophy Alignment'));
driftAnalysis.virtualDrift.forEach(vd => {
const alignmentIcon = vd.recentAlignment >= 0.8 ? 'š¢' :
vd.recentAlignment >= 0.6 ? 'š”' : 'š“';
console.log(`${vd.virtue.emoji} ${vd.virtue.name}: ${alignmentIcon} ${(vd.recentAlignment * 100).toFixed(1)}%`);
});
outro(picocolors.green('šļø Philosophical assessment complete!'));
}
async runInterventionPlanning() {
const s = spinner();
s.start('Creating intervention plan...');
const driftAnalysis = await this.generateDriftAnalysis();
const interventionPlan = await this.createInterventionPlan(driftAnalysis);
s.stop('Intervention plan ready!');
this.displayInterventionPlan(interventionPlan);
outro(picocolors.green('ā” Intervention plan complete! Take action to realign.'));
}
async generateDriftAnalysis() {
const allEntries = this.db.getEntriesForProfile(this.profile.id);
const parsedEntries = this.parseEntries(allEntries);
const virtues = this.db.getVirtuesForProfile(this.profile.id);
// Sort entries by date
const sortedEntries = parsedEntries.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
// Divide into early and recent periods
const totalEntries = sortedEntries.length;
const splitPoint = Math.floor(totalEntries * 0.4); // First 40% vs last 40%
const earlyEntries = sortedEntries.slice(0, splitPoint);
const recentEntries = sortedEntries.slice(-splitPoint);
// Calculate timeframes
const timeframes = {
early: {
start: earlyEntries[0]?.date || '',
end: earlyEntries[earlyEntries.length - 1]?.date || '',
entries: earlyEntries,
avgCoherence: this.getAverageCoherence(earlyEntries)
},
recent: {
start: recentEntries[0]?.date || '',
end: recentEntries[recentEntries.length - 1]?.date || '',
entries: recentEntries,
avgCoherence: this.getAverageCoherence(recentEntries)
}
};
// Calculate virtue-specific drift
const virtualDrift = virtues.map(virtue => {
const earlyVirtueEntries = earlyEntries.filter(entry => entry.virtue_scores.some((vs) => vs.virtue_id === virtue.id));
const recentVirtueEntries = recentEntries.filter(entry => entry.virtue_scores.some((vs) => vs.virtue_id === virtue.id));
const earlyAlignments = earlyVirtueEntries.map(entry => {
const score = entry.virtue_scores.find((vs) => vs.virtue_id === virtue.id);
return score ? calculateAlignment(score.want_score, score.pull_score) : 0;
}).filter(a => a > 0);
const recentAlignments = recentVirtueEntries.map(entry => {
const score = entry.virtue_scores.find((vs) => vs.virtue_id === virtue.id);
return score ? calculateAlignment(score.want_score, score.pull_score) : 0;
}).filter(a => a > 0);
const earlyAlignment = earlyAlignments.length > 0 ?
earlyAlignments.reduce((s, v) => s + v, 0) / earlyAlignments.length : 0;
const recentAlignment = recentAlignments.length > 0 ?
recentAlignments.reduce((s, v) => s + v, 0) / recentAlignments.length : 0;
const driftScore = Math.abs(recentAlignment - earlyAlignment);
const driftDirection = recentAlignment > earlyAlignment + 0.1 ? 'positive' :
recentAlignment < earlyAlignment - 0.1 ? 'negative' : 'stable';
const significance = driftScore > 0.2 ? 'high' :
driftScore > 0.1 ? 'medium' : 'low';
return {
virtue,
earlyAlignment,
recentAlignment,
driftScore,
driftDirection,
significance
};
});
// Calculate overall drift metrics
const coherenceDrift = timeframes.recent.avgCoherence - timeframes.early.avgCoherence;
// Consistency drift (how regular are the check-ins)
const earlyDays = this.getUniqueDays(earlyEntries);
const recentDays = this.getUniqueDays(recentEntries);
const earlyTimeSpan = this.getTimeSpan(earlyEntries);
const recentTimeSpan = this.getTimeSpan(recentEntries);
const earlyConsistency = earlyTimeSpan > 0 ? earlyDays / earlyTimeSpan : 0;
const recentConsistency = recentTimeSpan > 0 ? recentDays / recentTimeSpan : 0;
const consistencyDrift = recentConsistency - earlyConsistency;
// Philosophical alignment (average of all virtue alignments)
const recentAlignments = virtualDrift.map(vd => vd.recentAlignment).filter(a => a > 0);
const philosophicalAlignment = recentAlignments.length > 0 ?
recentAlignments.reduce((s, v) => s + v, 0) / recentAlignments.length : 0;
// Risk assessment
const highRiskVirtues = virtualDrift.filter(vd => vd.significance === 'high' && vd.driftDirection === 'negative');
const riskLevel = highRiskVirtues.length > 1 || coherenceDrift < -0.2 ? 'high' :
highRiskVirtues.length > 0 || coherenceDrift < -0.1 ? 'medium' : 'low';
return {
timeframes,
virtualDrift,
overallDrift: {
coherenceDrift,
consistencyDrift,
philosophicalAlignment,
riskLevel
},
recommendations: this.generateRecommendations(virtualDrift, coherenceDrift, riskLevel)
};
}
parseEntries(rawEntries) {
return rawEntries.map(entry => ({
...entry,
virtue_scores: typeof entry.virtue_scores === 'string'
? JSON.parse(entry.virtue_scores)
: entry.virtue_scores
}));
}
getAverageCoherence(entries) {
if (entries.length === 0)
return 0;
const coherences = entries.map(entry => {
const scores = {};
entry.virtue_scores.forEach((vs) => {
scores[vs.virtue_id] = { want: vs.want_score, pull: vs.pull_score };
});
return calculateCoherence(scores);
});
return coherences.reduce((sum, val) => sum + val, 0) / coherences.length;
}
getUniqueDays(entries) {
const uniqueDates = new Set(entries.map(entry => entry.date));
return uniqueDates.size;
}
getTimeSpan(entries) {
if (entries.length === 0)
return 0;
const dates = entries.map(entry => new Date(entry.date));
const earliest = new Date(Math.min(...dates.map(d => d.getTime())));
const latest = new Date(Math.max(...dates.map(d => d.getTime())));
return Math.floor((latest.getTime() - earliest.getTime()) / (1000 * 60 * 60 * 24)) + 1;
}
generateRecommendations(virtualDrift, coherenceDrift, riskLevel) {
const recommendations = [];
if (riskLevel === 'high') {
recommendations.push('Consider scheduling a philosophical realignment session');
recommendations.push('Review your core values and virtue definitions');
}
if (coherenceDrift < -0.1) {
recommendations.push('Focus on improving want/pull alignment in daily practice');
}
const decliningVirtues = virtualDrift.filter(vd => vd.driftDirection === 'negative' && vd.significance !== 'low');
if (decliningVirtues.length > 0) {
recommendations.push(`Pay special attention to: ${decliningVirtues.map(v => v.virtue.name).join(', ')}`);
}
if (recommendations.length === 0) {
recommendations.push('Continue your excellent virtue practice');
recommendations.push('Consider deepening your philosophical study');
}
return recommendations;
}
async generateAIDriftInsights(analysis) {
const prompt = this.buildDriftInsightPrompt(analysis);
return await this.claudeService.generateInsights(prompt);
}
buildDriftInsightPrompt(analysis) {
return `You are a wise philosopher analyzing someone's philosophical drift over time.
PROFILE: ${this.profile.name} (${this.profile.philosophical_template})
DRIFT ANALYSIS SUMMARY:
- Overall Risk Level: ${analysis.overallDrift.riskLevel}
- Coherence Change: ${(analysis.overallDrift.coherenceDrift * 100).toFixed(1)}%
- Current Philosophical Alignment: ${(analysis.overallDrift.philosophicalAlignment * 100).toFixed(1)}%
- Consistency Change: ${(analysis.overallDrift.consistencyDrift * 100).toFixed(1)}%
VIRTUE-SPECIFIC DRIFT:
${analysis.virtualDrift.map(vd => `- ${vd.virtue.name}: ${vd.earlyAlignment.toFixed(2)} ā ${vd.recentAlignment.toFixed(2)} (${vd.driftDirection}, ${vd.significance} significance)`).join('\n')}
TIME COMPARISON:
- Early Period: ${analysis.timeframes.early.start} to ${analysis.timeframes.early.end} (${(analysis.timeframes.early.avgCoherence * 100).toFixed(1)}% avg coherence)
- Recent Period: ${analysis.timeframes.recent.start} to ${analysis.timeframes.recent.end} (${(analysis.timeframes.recent.avgCoherence * 100).toFixed(1)}% avg coherence)
Provide a compassionate but honest assessment of their philosophical drift including:
1. What the drift patterns reveal about their character development
2. Whether the changes are concerning or natural growth
3. Specific insights about virtues showing significant drift
4. Wisdom about maintaining philosophical consistency while allowing growth
5. Encouragement for their continued practice
Be philosophical yet practical. Reference their specific virtue names and data. 300-400 words.`;
}
async createInterventionPlan(analysis) {
const { riskLevel } = analysis.overallDrift;
const decliningVirtues = analysis.virtualDrift.filter(vd => vd.driftDirection === 'negative' && vd.significance !== 'low');
const urgency = riskLevel === 'high' ? 'immediate' :
riskLevel === 'medium' ? 'moderate' : 'maintenance';
let focusAreas = [];
let practices = [];
let timeframe = '';
let checkInFrequency = '';
if (urgency === 'immediate') {
focusAreas = [
'Core value realignment',
'Virtue definition review',
...decliningVirtues.map(v => `${v.virtue.name} restoration`)
];
practices = [
'Daily philosophical reflection (10 minutes)',
'Weekly virtue journaling',
'Morning intention setting',
'Evening alignment review'
];
timeframe = '2-4 weeks intensive focus';
checkInFrequency = 'Every 3 days';
}
else if (urgency === 'moderate') {
focusAreas = [
'Strengthen weakening virtues',
'Maintain strong alignments',
'Philosophical consistency check'
];
practices = [
'Focused virtue practice',
'Bi-weekly reflection sessions',
'Virtue-specific exercises'
];
timeframe = '4-6 weeks gradual improvement';
checkInFrequency = 'Weekly';
}
else {
focusAreas = [
'Continued growth',
'Deepen philosophical understanding',
'Explore new virtue expressions'
];
practices = [
'Monthly philosophical reading',
'Virtue integration exercises',
'Growth-oriented challenges'
];
timeframe = '8-12 weeks enhancement';
checkInFrequency = 'Bi-weekly';
}
return {
urgency,
focusAreas,
practices,
timeframe,
checkInFrequency
};
}
displayDriftOverview(analysis) {
console.log(picocolors.bold('\nš§ Drift Overview'));
console.log('ā'.repeat(50));
const { coherenceDrift, riskLevel, philosophicalAlignment } = analysis.overallDrift;
console.log(`šÆ Current Alignment: ${this.formatAlignment(philosophicalAlignment)}`);
console.log(`ā ļø Risk Level: ${this.formatRiskLevel(riskLevel)}`);
console.log(`š Coherence Change: ${this.formatDrift(coherenceDrift)}`);
console.log(picocolors.bold('\nš
Time Periods'));
console.log(`š
Early: ${formatDateDisplay(new Date(analysis.timeframes.early.start))} - ${formatDateDisplay(new Date(analysis.timeframes.early.end))}`);
console.log(`š Recent: ${formatDateDisplay(new Date(analysis.timeframes.recent.start))} - ${formatDateDisplay(new Date(analysis.timeframes.recent.end))}`);
}
displayVirtueSpecificDrift(analysis) {
console.log(picocolors.bold('\nšÆ Virtue Drift Analysis'));
console.log('ā'.repeat(50));
analysis.virtualDrift
.sort((a, b) => b.driftScore - a.driftScore)
.forEach(vd => {
const driftIcon = vd.driftDirection === 'positive' ? 'š' :
vd.driftDirection === 'negative' ? 'š' : 'š';
const significanceColor = vd.significance === 'high' ? picocolors.red :
vd.significance === 'medium' ? picocolors.yellow : picocolors.green;
console.log(`${vd.virtue.emoji} ${vd.virtue.name}: ${this.formatAlignment(vd.earlyAlignment)} ā ${this.formatAlignment(vd.recentAlignment)} ${driftIcon} ${significanceColor(vd.significance)}`);
});
}
displaySingleVirtueDrift(virtueDrift) {
console.log(picocolors.bold(`\nšÆ ${virtueDrift.virtue.name} Drift Analysis`));
console.log('ā'.repeat(50));
console.log(`${virtueDrift.virtue.emoji} ${virtueDrift.virtue.name}`);
console.log(`š Definition: ${virtueDrift.virtue.definition}`);
console.log(`š Early Alignment: ${this.formatAlignment(virtueDrift.earlyAlignment)}`);
console.log(`š Recent Alignment: ${this.formatAlignment(virtueDrift.recentAlignment)}`);
const driftIcon = virtueDrift.driftDirection === 'positive' ? 'š Improving' :
virtueDrift.driftDirection === 'negative' ? 'š Declining' : 'š Stable';
console.log(`š Trend: ${driftIcon}`);
console.log(`ā ļø Significance: ${this.formatSignificance(virtueDrift.significance)}`);
}
displayOverallDriftAssessment(analysis) {
console.log(picocolors.bold('\nš Assessment & Recommendations'));
console.log('ā'.repeat(50));
analysis.recommendations.forEach(rec => {
console.log(`⢠${rec}`);
});
}
displayAIInsights(insights) {
console.log(picocolors.bold('\nš§ AI Drift Analysis'));
console.log('ā'.repeat(50));
console.log(insights);
}
displayInterventionPlan(plan) {
console.log(picocolors.bold('\nā” Drift Intervention Plan'));
console.log('ā'.repeat(50));
console.log(`šØ Urgency: ${this.formatUrgency(plan.urgency)}`);
console.log(`ā±ļø Timeframe: ${plan.timeframe}`);
console.log(`š
Check-ins: ${plan.checkInFrequency}`);
console.log(picocolors.bold('\nšÆ Focus Areas:'));
plan.focusAreas.forEach(area => console.log(`⢠${area}`));
console.log(picocolors.bold('\nš Recommended Practices:'));
plan.practices.forEach(practice => console.log(`⢠${practice}`));
}
formatAlignment(alignment) {
const percentage = (alignment * 100).toFixed(1);
const color = alignment >= 0.8 ? picocolors.green :
alignment >= 0.6 ? picocolors.yellow : picocolors.red;
return `${color(percentage + '%')}`;
}
formatDrift(drift) {
const percentage = (drift * 100).toFixed(1);
const sign = drift >= 0 ? '+' : '';
const color = drift >= 0.1 ? picocolors.green :
drift <= -0.1 ? picocolors.red : picocolors.yellow;
return `${color(sign + percentage + '%')}`;
}
formatRiskLevel(level) {
const colors = {
high: picocolors.red,
medium: picocolors.yellow,
low: picocolors.green
};
const icons = {
high: 'š“',
medium: 'š”',
low: 'š¢'
};
return `${colors[level](level.toUpperCase())} ${icons[level]}`;
}
formatSignificance(significance) {
const colors = {
high: picocolors.red,
medium: picocolors.yellow,
low: picocolors.green
};
return colors[significance](significance.toUpperCase());
}
formatUrgency(urgency) {
const colors = {
immediate: picocolors.red,
moderate: picocolors.yellow,
maintenance: picocolors.green
};
const icons = {
immediate: 'šØ',
moderate: 'ā ļø',
maintenance: 'ā
'
};
return `${colors[urgency](urgency.toUpperCase())} ${icons[urgency]}`;
}
}
//# sourceMappingURL=DriftDetection.js.map