Files
code-plan/src/services/WorkflowOptimizer.ts
T
William Valentin 0625d457be Initial commit: Claude Session Manager with complete CLI toolset
- Add TypeScript project structure with Bun runtime
- Implement CLI commands for session, project, stats, and optimization
- Add SQLite database integration with prepared statements
- Include AGENTS.md for development guidelines
- Add Makefile for common development tasks
- Configure ESLint and TypeScript with strict mode
2025-10-31 17:38:48 -07:00

394 lines
14 KiB
TypeScript

import { Database } from '../database/Database';
import { OptimizationRecommendation } from '../models';
import { TokenTracker } from './TokenTracker';
import { SessionManager } from './SessionManager';
export class WorkflowOptimizer {
private db: Database;
private tokenTracker: TokenTracker;
private sessionManager: SessionManager;
constructor(db: Database, tokenTracker: TokenTracker, sessionManager: SessionManager) {
this.db = db;
this.tokenTracker = tokenTracker;
this.sessionManager = sessionManager;
}
async analyzeWorkflowEfficiency(projectId?: string): Promise<{
score: number;
recommendations: OptimizationRecommendation[];
insights: string[];
}> {
const recommendations: OptimizationRecommendation[] = [];
const insights: string[] = [];
let score = 100;
// Analyze session patterns
const sessionAnalysis = await this.analyzeSessionPatterns(projectId);
score -= sessionAnalysis.penalties;
recommendations.push(...sessionAnalysis.recommendations);
insights.push(...sessionAnalysis.insights);
// Analyze token usage
const tokenAnalysis = await this.analyzeTokenUsage();
score -= tokenAnalysis.penalties;
recommendations.push(...tokenAnalysis.recommendations);
insights.push(...tokenAnalysis.insights);
// Analyze scheduling efficiency
const schedulingAnalysis = await this.analyzeSchedulingEfficiency();
score -= schedulingAnalysis.penalties;
recommendations.push(...schedulingAnalysis.recommendations);
insights.push(...schedulingAnalysis.insights);
return {
score: Math.max(0, Math.min(100, score)),
recommendations: this.prioritizeRecommendations(recommendations),
insights
};
}
async suggestOptimalSession(taskDescription: string, complexity: 'simple' | 'medium' | 'complex'): Promise<{
modelType: 'opus' | 'sonnet';
estimatedTokens: number;
batchingSuggestions: string[];
preparationTips: string[];
}> {
const modelType = this.recommendModel(taskDescription, complexity);
const estimatedTokens = this.estimateTokenUsage(taskDescription, complexity, modelType);
return {
modelType,
estimatedTokens,
batchingSuggestions: this.generateBatchingSuggestions(taskDescription),
preparationTips: this.generatePreparationTips(complexity)
};
}
async generateOptimalSchedule(upcomingTasks: Array<{
description: string;
complexity: 'simple' | 'medium' | 'complex';
estimatedDuration: number; // minutes
priority: 'high' | 'medium' | 'low';
}>): Promise<{
schedule: Array<{
task: string;
recommendedTime: Date;
modelType: 'opus' | 'sonnet';
grouping: string;
}>;
totalEstimatedTokens: number;
optimizationNotes: string[];
}> {
const schedule = [];
let totalTokens = 0;
const optimizationNotes = [];
// Group tasks by complexity and type
const groupedTasks = this.groupTasksByType(upcomingTasks);
// Get current window status
const currentWindow = await this.sessionManager.getCurrentWindow();
const nextWindow = await this.sessionManager.getNextAvailableWindow();
let currentTime = currentWindow ? new Date() : (nextWindow || new Date());
for (const [groupName, tasks] of Object.entries(groupedTasks)) {
for (const task of tasks) {
const suggestion = await this.suggestOptimalSession(task.description, task.complexity);
schedule.push({
task: task.description,
recommendedTime: new Date(currentTime),
modelType: suggestion.modelType,
grouping: groupName
});
totalTokens += suggestion.estimatedTokens;
currentTime = new Date(currentTime.getTime() + task.estimatedDuration * 60 * 1000);
}
}
// Add optimization notes
if (totalTokens > 400000) {
optimizationNotes.push('High token usage expected. Consider splitting across multiple 5-hour windows');
}
const opusTasks = upcomingTasks.filter(t => t.complexity === 'complex').length;
if (opusTasks > 3) {
optimizationNotes.push('Multiple complex tasks detected. Consider using overlap hack for extended sessions');
}
return {
schedule,
totalEstimatedTokens: totalTokens,
optimizationNotes
};
}
async detectWorkflowAnomalies(): Promise<{
anomalies: Array<{
type: 'token_spike' | 'session_fragmentation' | 'model_misuse' | 'scheduling_conflict';
severity: 'low' | 'medium' | 'high';
description: string;
recommendation: string;
}>;
trends: Array<{
metric: string;
trend: 'improving' | 'declining' | 'stable';
change: number;
}>;
}> {
const anomalies = [];
const trends = [];
// Detect token spikes
const recentUsage = await this.tokenTracker.getUsageStats(7);
const avgDailyTokens = recentUsage.reduce((sum, day) => sum + day.totalTokens, 0) / 7;
if (avgDailyTokens > 100000) {
anomalies.push({
type: 'token_spike',
severity: 'high',
description: `Daily token usage (${avgDailyTokens.toLocaleString()}) is unusually high`,
recommendation: 'Review recent sessions for inefficiencies and consider using /compact more frequently'
});
}
// Detect session fragmentation
const recentSessions = await this.sessionManager.getSessionHistory(20);
const shortSessions = recentSessions.filter(s => {
const duration = s.endTime ? (s.endTime.getTime() - s.startTime.getTime()) / (1000 * 60) : 0;
return duration < 10;
}).length;
if (shortSessions > recentSessions.length * 0.5) {
anomalies.push({
type: 'session_fragmentation',
severity: 'medium',
description: `${shortSessions} out of ${recentSessions.length} recent sessions were shorter than 10 minutes`,
recommendation: 'Consider batching similar tasks into longer, more focused sessions'
});
}
// Detect model misuse
const opusHeavyDays = recentUsage.filter(day => day.opusSessions > day.sonnetSessions).length;
if (opusHeavyDays > recentUsage.length * 0.6) {
anomalies.push({
type: 'model_misuse',
severity: 'medium',
description: 'Opus is being used more frequently than Sonnet',
recommendation: 'Reserve Opus for complex tasks and use Sonnet for routine implementation work'
});
}
// Calculate trends
if (recentUsage.length >= 2) {
const recentAvg = recentUsage.slice(0, 3).reduce((sum, day) => sum + day.optimizationScore, 0) / 3;
const olderAvg = recentUsage.slice(3, 6).reduce((sum, day) => sum + day.optimizationScore, 0) / Math.min(3, recentUsage.length - 3);
if (recentAvg > olderAvg + 5) {
trends.push({ metric: 'optimization_score', trend: 'improving', change: recentAvg - olderAvg });
} else if (recentAvg < olderAvg - 5) {
trends.push({ metric: 'optimization_score', trend: 'declining', change: olderAvg - recentAvg });
} else {
trends.push({ metric: 'optimization_score', trend: 'stable', change: 0 });
}
}
return { anomalies, trends };
}
private async analyzeSessionPatterns(projectId?: string): Promise<{
penalties: number;
recommendations: OptimizationRecommendation[];
insights: string[];
}> {
const penalties = 0;
const recommendations: OptimizationRecommendation[] = [];
const insights: string[] = [];
const sessions = projectId
? await this.db.all('SELECT * FROM sessions WHERE projectId = ? ORDER BY startTime DESC LIMIT 10', [projectId])
: await this.sessionManager.getSessionHistory(10);
// Analyze session length distribution
const sessionLengths = sessions.map(session => {
const endTime = (session as { endTime?: string | Date }).endTime || new Date();
const endTimeDate = typeof endTime === 'string' ? new Date(endTime) : endTime;
return (endTimeDate.getTime() - new Date((session as { startTime: string }).startTime).getTime()) / (1000 * 60); // minutes
});
const avgLength = sessionLengths.reduce((sum, length) => sum + length, 0) / sessionLengths.length;
if (avgLength < 15) {
recommendations.push({
type: 'workflow',
priority: 'medium',
message: 'Average session length is quite short. Consider batching related tasks.'
});
}
insights.push(`Average session duration: ${avgLength.toFixed(1)} minutes`);
return { penalties, recommendations, insights };
}
private async analyzeTokenUsage(): Promise<{
penalties: number;
recommendations: OptimizationRecommendation[];
insights: string[];
}> {
const penalties = 0;
const recommendations: OptimizationRecommendation[] = [];
const insights: string[] = [];
const usageStats = await this.tokenTracker.getUsageStats(7);
usageStats.reduce((sum, day) => sum + day.totalTokens, 0);
const avgTokensPerSession = usageStats.reduce((sum, day) => sum + (day.totalTokens / Math.max(1, day.sessionsCount)), 0) / usageStats.length;
if (avgTokensPerSession > 50000) {
recommendations.push({
type: 'token',
priority: 'high',
message: 'High token usage per session detected. Consider using /compact more frequently.',
potentialSavings: avgTokensPerSession * 0.3
});
}
insights.push(`Average tokens per session: ${avgTokensPerSession.toLocaleString()}`);
return { penalties, recommendations, insights };
}
private async analyzeSchedulingEfficiency(): Promise<{
penalties: number;
recommendations: OptimizationRecommendation[];
insights: string[];
}> {
const penalties = 0;
const recommendations: OptimizationRecommendation[] = [];
const insights: string[] = [];
const currentWindow = await this.sessionManager.getCurrentWindow();
if (!currentWindow) {
recommendations.push({
type: 'scheduling',
priority: 'medium',
message: 'No active 5-hour window. Consider using the overlap hack to prepare for intensive sessions.'
});
}
insights.push(currentWindow ? 'Currently within an active 5-hour window' : 'No active window detected');
return { penalties, recommendations, insights };
}
private recommendModel(taskDescription: string, complexity: 'simple' | 'medium' | 'complex'): 'opus' | 'sonnet' {
if (complexity === 'complex') return 'opus';
if (complexity === 'simple') return 'sonnet';
// For medium complexity, analyze task description
const complexKeywords = ['architecture', 'design', 'refactor', 'optimize', 'debug complex', 'implement system'];
const hasComplexKeywords = complexKeywords.some(keyword =>
taskDescription.toLowerCase().includes(keyword)
);
return hasComplexKeywords ? 'opus' : 'sonnet';
}
private estimateTokenUsage(taskDescription: string, complexity: 'simple' | 'medium' | 'complex', modelType: 'opus' | 'sonnet'): number {
const baseTokens = {
simple: { sonnet: 5000, opus: 8000 },
medium: { sonnet: 15000, opus: 25000 },
complex: { sonnet: 30000, opus: 50000 }
};
return baseTokens[complexity][modelType];
}
private generateBatchingSuggestions(taskDescription: string): string[] {
const suggestions = [
'Prepare all related files and context before starting',
'Group similar implementation tasks together',
'Plan testing and linting as part of the same session'
];
if (taskDescription.includes('test')) {
suggestions.push('Include test writing in the same session as implementation');
}
if (taskDescription.includes('refactor')) {
suggestions.push('Batch related refactoring tasks to maintain context');
}
return suggestions;
}
private generatePreparationTips(complexity: 'simple' | 'medium' | 'complex'): string[] {
const tips = {
simple: [
'Have specific files ready to reference',
'Prepare clear, concise instructions'
],
medium: [
'Review relevant documentation beforehand',
'Prepare test cases or expected outcomes',
'Consider edge cases in advance'
],
complex: [
'Create a detailed step-by-step plan',
'Prepare comprehensive project context',
'Have fallback approaches ready',
'Schedule adequate time for implementation'
]
};
return tips[complexity];
}
private groupTasksByType(tasks: Array<{
description: string;
complexity: 'simple' | 'medium' | 'complex';
estimatedDuration: number;
priority: 'high' | 'medium' | 'low';
}>): Record<string, Array<{
description: string;
complexity: 'simple' | 'medium' | 'complex';
estimatedDuration: number;
priority: 'high' | 'medium' | 'low';
}>> {
return tasks.reduce((groups, task) => {
const category = this.categorizeTask(task);
if (!groups[category]) groups[category] = [];
groups[category].push(task);
return groups;
}, {} as Record<string, Array<{
description: string;
complexity: 'simple' | 'medium' | 'complex';
estimatedDuration: number;
priority: 'high' | 'medium' | 'low';
}>>);
}
private categorizeTask(task: {
description: string;
complexity: 'simple' | 'medium' | 'complex';
estimatedDuration: number;
priority: 'high' | 'medium' | 'low';
}): string {
if (task.complexity === 'complex') return 'Complex Architecture';
if (task.description.includes('test')) return 'Testing';
if (task.description.includes('fix') || task.description.includes('bug')) return 'Bug Fixes';
if (task.description.includes('feature')) return 'Feature Development';
return 'General Tasks';
}
private prioritizeRecommendations(recommendations: OptimizationRecommendation[]): OptimizationRecommendation[] {
return recommendations.sort((a, b) => {
const priorityOrder = { high: 3, medium: 2, low: 1 };
return priorityOrder[b.priority] - priorityOrder[a.priority];
});
}
}