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
JavaScript
/**
* 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);
});
}