UNPKG

claude-code-automation

Version:

šŸš€ Generic project automation system with anti-compaction protection and recovery capabilities. Automatically detects project type (React, Node.js, Python, Rust, Go, Java) and provides intelligent analysis. Claude Code optimized - run 'welcome' after inst

654 lines (552 loc) • 21.3 kB
/** * Development Session Tracker - Intelligent Activity Detection * * Mission: Intelligently detect development patterns and optimize preservation timing * Strategy: Multi-dimensional activity tracking with predictive preservation * * Features: * - Session state management (active, idle, dormant) * - Development pattern recognition * - Predictive preservation timing * - Activity intensity analysis * - Smart interval adjustment */ const fs = require('fs').promises; const path = require('path'); const os = require('os'); class DevelopmentSessionTracker { constructor(options = {}) { this.projectRoot = options.projectRoot || path.resolve(__dirname, '../..'); this.isTracking = false; // Session states this.SESSION_STATES = { DORMANT: 'dormant', // No activity for >30 minutes IDLE: 'idle', // No activity for 5-30 minutes ACTIVE: 'active', // Recent activity within 5 minutes INTENSIVE: 'intensive' // High-frequency activity }; this.currentState = this.SESSION_STATES.DORMANT; this.stateChangeTime = Date.now(); // Configuration this.config = { idleThreshold: options.idleThreshold || 5 * 60 * 1000, // 5 minutes dormantThreshold: options.dormantThreshold || 30 * 60 * 1000, // 30 minutes intensiveThreshold: options.intensiveThreshold || 10, // 10 changes per minute preservationIntervals: { dormant: 60 * 60 * 1000, // 1 hour idle: 15 * 60 * 1000, // 15 minutes active: 2 * 60 * 1000, // 2 minutes intensive: 30 * 1000 // 30 seconds }, activityWindow: options.activityWindow || 60 * 1000 // 1 minute window for intensity calculation }; // Activity tracking this.activityLog = []; this.sessionMetrics = { startTime: Date.now(), totalFileChanges: 0, totalCommands: 0, totalPreservations: 0, stateTransitions: [], peakIntensity: 0, averageIntensity: 0, productiveMinutes: 0 }; // Activity patterns this.patterns = { fileTypes: new Map(), timeOfDay: new Map(), commandFrequency: new Map(), intensityPeriods: [] }; this.preservationTimer = null; this.metricsTimer = null; this.callbacks = { onStateChange: null, onPreservationNeeded: null, onIntensiveActivity: null }; } /** * Start session tracking */ async start() { if (this.isTracking) { console.log('šŸ“Š Session tracker already running'); return; } console.log('šŸš€ Starting Development Session Tracker...'); this.isTracking = true; this.sessionMetrics.startTime = Date.now(); // Start periodic metrics collection this.startMetricsCollection(); // Start preservation timer this.startPreservationTimer(); // Monitor system activities await this.startSystemMonitoring(); console.log('āœ… Session tracker started'); console.log(`šŸ“Š Initial state: ${this.currentState}`); } /** * Stop session tracking */ async stop() { if (!this.isTracking) return; console.log('šŸ”„ Stopping session tracker...'); this.isTracking = false; if (this.metricsTimer) { clearInterval(this.metricsTimer); this.metricsTimer = null; } if (this.preservationTimer) { clearTimeout(this.preservationTimer); this.preservationTimer = null; } // Save final session report await this.saveSessionReport(); console.log('āœ… Session tracker stopped'); } /** * Record activity event */ recordActivity(activityType, details = {}) { const timestamp = Date.now(); const activity = { timestamp, type: activityType, details, intensity: this.calculateCurrentIntensity() }; this.activityLog.push(activity); // Update metrics this.updateMetrics(activity); // Update patterns this.updatePatterns(activity); // Check for state change this.checkStateTransition(); // Trim old activities (keep last hour) const cutoff = timestamp - (60 * 60 * 1000); this.activityLog = this.activityLog.filter(a => a.timestamp > cutoff); } /** * Calculate current activity intensity */ calculateCurrentIntensity() { const now = Date.now(); const windowStart = now - this.config.activityWindow; const recentActivities = this.activityLog.filter( a => a.timestamp > windowStart ); return recentActivities.length; } /** * Update session metrics */ updateMetrics(activity) { switch (activity.type) { case 'file_change': this.sessionMetrics.totalFileChanges++; break; case 'command_execution': this.sessionMetrics.totalCommands++; break; case 'preservation': this.sessionMetrics.totalPreservations++; break; } const intensity = activity.intensity; if (intensity > this.sessionMetrics.peakIntensity) { this.sessionMetrics.peakIntensity = intensity; } // Update average intensity (exponential moving average) if (this.sessionMetrics.averageIntensity === 0) { this.sessionMetrics.averageIntensity = intensity; } else { this.sessionMetrics.averageIntensity = (this.sessionMetrics.averageIntensity * 0.9) + (intensity * 0.1); } } /** * Update activity patterns */ updatePatterns(activity) { // File type patterns if (activity.details.filePath) { const ext = path.extname(activity.details.filePath); this.patterns.fileTypes.set(ext, (this.patterns.fileTypes.get(ext) || 0) + 1); } // Time of day patterns const hour = new Date(activity.timestamp).getHours(); this.patterns.timeOfDay.set(hour, (this.patterns.timeOfDay.get(hour) || 0) + 1); // Command frequency patterns if (activity.details.command) { const cmd = activity.details.command; this.patterns.commandFrequency.set(cmd, (this.patterns.commandFrequency.get(cmd) || 0) + 1); } // Intensive periods if (activity.intensity >= this.config.intensiveThreshold) { this.patterns.intensityPeriods.push({ start: activity.timestamp, intensity: activity.intensity }); } } /** * Check for session state transition */ checkStateTransition() { const now = Date.now(); const timeSinceLastActivity = this.getTimeSinceLastActivity(); const currentIntensity = this.calculateCurrentIntensity(); let newState = this.currentState; // Determine new state based on activity patterns if (currentIntensity >= this.config.intensiveThreshold) { newState = this.SESSION_STATES.INTENSIVE; } else if (timeSinceLastActivity < this.config.idleThreshold) { newState = this.SESSION_STATES.ACTIVE; } else if (timeSinceLastActivity < this.config.dormantThreshold) { newState = this.SESSION_STATES.IDLE; } else { newState = this.SESSION_STATES.DORMANT; } // Transition if state changed if (newState !== this.currentState) { this.transitionToState(newState); } } /** * Transition to new session state */ transitionToState(newState) { const previousState = this.currentState; const now = Date.now(); console.log(`šŸ“Š Session state: ${previousState} → ${newState}`); // Record transition this.sessionMetrics.stateTransitions.push({ from: previousState, to: newState, timestamp: now, duration: now - this.stateChangeTime }); this.currentState = newState; this.stateChangeTime = now; // Update preservation timer this.updatePreservationTimer(); // Trigger callback if (this.callbacks.onStateChange) { this.callbacks.onStateChange(newState, previousState); } // Handle intensive activity if (newState === this.SESSION_STATES.INTENSIVE && this.callbacks.onIntensiveActivity) { this.callbacks.onIntensiveActivity(this.calculateCurrentIntensity()); } } /** * Get time since last activity */ getTimeSinceLastActivity() { if (this.activityLog.length === 0) { return Date.now() - this.sessionMetrics.startTime; } const lastActivity = this.activityLog[this.activityLog.length - 1]; return Date.now() - lastActivity.timestamp; } /** * Start preservation timer with current state interval */ updatePreservationTimer() { if (this.preservationTimer) { clearTimeout(this.preservationTimer); } const interval = this.config.preservationIntervals[this.currentState]; this.preservationTimer = setTimeout(() => { if (this.callbacks.onPreservationNeeded) { this.callbacks.onPreservationNeeded(this.currentState); } this.updatePreservationTimer(); // Schedule next preservation }, interval); } /** * Start preservation timer */ startPreservationTimer() { this.updatePreservationTimer(); } /** * Start periodic metrics collection */ startMetricsCollection() { this.metricsTimer = setInterval(() => { this.collectPeriodicMetrics(); }, 60 * 1000); // Every minute } /** * Collect periodic metrics */ collectPeriodicMetrics() { const now = Date.now(); const lastMinute = now - 60 * 1000; // Count productive minutes (minutes with activity) const recentActivity = this.activityLog.some(a => a.timestamp > lastMinute); if (recentActivity) { this.sessionMetrics.productiveMinutes++; } // Check for state transition this.checkStateTransition(); } /** * Start system monitoring for external activities */ async startSystemMonitoring() { // Monitor git operations this.monitorGitActivity(); // Monitor package.json changes this.monitorPackageChanges(); // Monitor npm/yarn commands this.monitorPackageManagerCommands(); } /** * Monitor git activity */ monitorGitActivity() { try { const gitDir = path.join(this.projectRoot, '.git'); const indexFile = path.join(gitDir, 'index'); fs.watch(indexFile, (eventType) => { if (eventType === 'change') { this.recordActivity('git_operation', { operation: 'index_change', timestamp: Date.now() }); } }).on('error', () => { // Git directory might not exist or be accessible }); } catch (error) { // Git monitoring not available } } /** * Monitor package.json changes */ monitorPackageChanges() { try { const packagePath = path.join(this.projectRoot, 'package.json'); fs.watch(packagePath, (eventType) => { if (eventType === 'change') { this.recordActivity('package_change', { file: 'package.json', timestamp: Date.now() }); } }).on('error', () => { // package.json might not exist }); } catch (error) { // Package monitoring not available } } /** * Monitor package manager commands safely via log files */ monitorPackageManagerCommands() { // Safe monitoring via npm log files instead of process hijacking try { const npmCacheDir = path.join(os.homedir(), '.npm'); const logFile = path.join(npmCacheDir, '_logs'); // Monitor npm log directory for changes fs.watch(logFile, { recursive: true }, (eventType, filename) => { if (eventType === 'change' && filename && filename.endsWith('.log')) { this.recordActivity('package_manager_activity', { logFile: filename, timestamp: Date.now() }); } }).on('error', () => { // Log monitoring not available, continue safely }); } catch (error) { // Safe fallback - monitor package.json changes instead this.monitorPackageJsonChanges(); } } /** * Monitor package.json changes as fallback for package manager activity */ monitorPackageJsonChanges() { try { const packagePath = path.join(this.projectRoot, 'package.json'); const lockPath = path.join(this.projectRoot, 'package-lock.json'); // Monitor package files for changes [packagePath, lockPath].forEach(filePath => { fs.watch(filePath, (eventType) => { if (eventType === 'change') { this.recordActivity('package_change', { file: path.basename(filePath), timestamp: Date.now() }); } }).on('error', () => { // File might not exist, ignore }); }); } catch (error) { // Package monitoring not available } } /** * Get current session statistics */ getSessionStats() { const now = Date.now(); const sessionDuration = now - this.sessionMetrics.startTime; return { state: this.currentState, duration: sessionDuration, currentIntensity: this.calculateCurrentIntensity(), timeSinceLastActivity: this.getTimeSinceLastActivity(), metrics: { ...this.sessionMetrics }, patterns: { fileTypes: Object.fromEntries(this.patterns.fileTypes), timeOfDay: Object.fromEntries(this.patterns.timeOfDay), commandFrequency: Object.fromEntries(this.patterns.commandFrequency), intensityPeriods: this.patterns.intensityPeriods.length }, recommendations: this.generateRecommendations() }; } /** * Generate preservation recommendations based on current patterns */ generateRecommendations() { const recommendations = []; const intensity = this.calculateCurrentIntensity(); const timeSinceActivity = this.getTimeSinceLastActivity(); if (intensity >= this.config.intensiveThreshold) { recommendations.push('High activity detected - preservation every 30 seconds'); } else if (this.currentState === this.SESSION_STATES.ACTIVE) { recommendations.push('Active development - preservation every 2 minutes'); } else if (timeSinceActivity > this.config.idleThreshold) { recommendations.push('Session idle - reduced preservation frequency'); } // Pattern-based recommendations const topFileType = this.getMostFrequentFileType(); if (topFileType && topFileType.includes('.js')) { recommendations.push('JavaScript development detected - prioritize JS file preservation'); } return recommendations; } /** * Get most frequently modified file type */ getMostFrequentFileType() { let maxCount = 0; let mostFrequent = null; for (const [fileType, count] of this.patterns.fileTypes) { if (count > maxCount) { maxCount = count; mostFrequent = fileType; } } return mostFrequent; } /** * Set callback functions */ setCallbacks(callbacks) { this.callbacks = { ...this.callbacks, ...callbacks }; } /** * Force state transition (for testing or manual control) */ forceStateTransition(newState) { if (Object.values(this.SESSION_STATES).includes(newState)) { this.transitionToState(newState); } } /** * Save session report */ async saveSessionReport() { const report = { sessionEnd: Date.now(), finalStats: this.getSessionStats(), fullActivityLog: this.activityLog.slice(-100), // Last 100 activities summary: this.generateSessionSummary() }; try { const reportsDir = path.join(this.projectRoot, 'docs/live-protection/session-reports'); await fs.mkdir(reportsDir, { recursive: true }); const filename = `session-report-${Date.now()}.json`; const reportPath = path.join(reportsDir, filename); await fs.writeFile(reportPath, JSON.stringify(report, null, 2)); console.log(`šŸ“Š Session report saved: ${filename}`); } catch (error) { console.warn('āš ļø Could not save session report:', error.message); } } /** * Generate session summary */ generateSessionSummary() { const stats = this.getSessionStats(); const duration = stats.duration; const hours = Math.floor(duration / (60 * 60 * 1000)); const minutes = Math.floor((duration % (60 * 60 * 1000)) / (60 * 1000)); return { totalDuration: `${hours}h ${minutes}m`, productivityRate: (stats.metrics.productiveMinutes / (duration / 60000) * 100).toFixed(1) + '%', peakIntensity: stats.metrics.peakIntensity, averageIntensity: stats.metrics.averageIntensity.toFixed(1), stateTransitions: stats.metrics.stateTransitions.length, mostActiveFileType: this.getMostFrequentFileType(), totalPreservations: stats.metrics.totalPreservations, finalState: stats.state }; } /** * Cleanup resources */ async cleanup() { await this.stop(); // Clean up file watchers and resources // All watchers are cleaned up in stop() method } } module.exports = DevelopmentSessionTracker; // Auto-execute if run directly if (require.main === module) { const tracker = new DevelopmentSessionTracker(); // Set up callbacks tracker.setCallbacks({ onStateChange: (newState, oldState) => { console.log(`šŸ“Š State changed: ${oldState} → ${newState}`); }, onPreservationNeeded: (state) => { console.log(`šŸ’¾ Preservation needed (state: ${state})`); }, onIntensiveActivity: (intensity) => { console.log(`⚔ Intensive activity detected (${intensity} actions/minute)`); } }); process.on('SIGINT', async () => { console.log('\nšŸ”„ Shutting down session tracker...'); await tracker.cleanup(); process.exit(0); }); tracker.start() .then(() => { console.log('šŸš€ Development session tracker running. Press Ctrl+C to stop.'); // Simulate some activities for testing setTimeout(() => tracker.recordActivity('file_change', { filePath: 'test.js' }), 1000); setTimeout(() => tracker.recordActivity('command_execution', { command: 'npm test' }), 3000); // Show stats every 30 seconds setInterval(() => { console.log('šŸ“Š Current stats:', tracker.getSessionStats()); }, 30000); }) .catch(error => { console.error('āŒ Failed to start session tracker:', error); process.exit(1); }); }