#!/usr/bin/env node /** * Action Pattern Tracker * * Tracks patterns indicating "stuck in loop" situations: * - Same file edited multiple times consecutively * - Same function/section modified repeatedly * - User frustration signals in messages * * This feeds into: * - MetacognitiveVerifier (triggers verification on 3+ failed attempts) * - ContextPressureMonitor (elevates pressure on loop detection) * * Usage: * node scripts/track-action-patterns.js --action edit --file path/to/file * node scripts/track-action-patterns.js --check */ const fs = require('fs'); const path = require('path'); const SESSION_STATE_PATH = path.join(__dirname, '../.claude/session-state.json'); const ACTION_PATTERNS_PATH = path.join(__dirname, '../.claude/action-patterns.json'); /** * Load session state */ function loadSessionState() { try { return JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8')); } catch (err) { console.error(`Error loading session state: ${err.message}`); process.exit(1); } } /** * Load action patterns (or initialize) */ function loadActionPatterns() { try { if (fs.existsSync(ACTION_PATTERNS_PATH)) { return JSON.parse(fs.readFileSync(ACTION_PATTERNS_PATH, 'utf8')); } } catch (err) { console.warn(`Warning: Could not load action patterns: ${err.message}`); } // Initialize new patterns structure return { version: "1.0", session_id: null, started: new Date().toISOString(), actions: [], patterns: { repeated_edits: [], same_file_edits: {}, user_frustration_signals: [] }, alerts: [], last_updated: new Date().toISOString() }; } /** * Save action patterns */ function saveActionPatterns(patterns) { patterns.last_updated = new Date().toISOString(); fs.writeFileSync(ACTION_PATTERNS_PATH, JSON.stringify(patterns, null, 2)); } /** * Track new action */ function trackAction(actionType, filePath) { const patterns = loadActionPatterns(); const sessionState = loadSessionState(); // Update session ID if changed if (patterns.session_id !== sessionState.session_id) { patterns.session_id = sessionState.session_id; patterns.actions = []; // Reset for new session patterns.patterns.same_file_edits = {}; patterns.patterns.repeated_edits = []; patterns.alerts = []; } // Add action const action = { type: actionType, file: filePath, timestamp: new Date().toISOString(), message_number: sessionState.message_count }; patterns.actions.push(action); // Track same-file edits if (actionType === 'edit' || actionType === 'write') { if (!patterns.patterns.same_file_edits[filePath]) { patterns.patterns.same_file_edits[filePath] = { count: 0, timestamps: [], alert_threshold: 3 }; } patterns.patterns.same_file_edits[filePath].count++; patterns.patterns.same_file_edits[filePath].timestamps.push(action.timestamp); // Check for loop pattern if (patterns.patterns.same_file_edits[filePath].count >= 3) { // Check if edits are recent (within last 10 actions) const recentActions = patterns.actions.slice(-10); const recentEditsOfThisFile = recentActions.filter(a => (a.type === 'edit' || a.type === 'write') && a.file === filePath ); if (recentEditsOfThisFile.length >= 3) { const alert = { type: 'repeated_file_edit', severity: 'MEDIUM', file: filePath, count: patterns.patterns.same_file_edits[filePath].count, message: `File edited ${patterns.patterns.same_file_edits[filePath].count} times - possible stuck loop`, timestamp: new Date().toISOString(), recommendation: 'Run MetacognitiveVerifier to assess current approach' }; patterns.alerts.push(alert); console.log(`⚠️ ALERT: Repeated edits to ${path.basename(filePath)}`); console.log(` Count: ${patterns.patterns.same_file_edits[filePath].count}`); console.log(` Recommendation: Pause and verify approach`); } } } // Keep only last 100 actions if (patterns.actions.length > 100) { patterns.actions = patterns.actions.slice(-100); } saveActionPatterns(patterns); return patterns; } /** * Check for active patterns/alerts */ function checkPatterns() { const patterns = loadActionPatterns(); const activeAlerts = patterns.alerts.filter(alert => { // Consider alerts from last hour as active const alertTime = new Date(alert.timestamp); const hourAgo = new Date(Date.now() - 60 * 60 * 1000); return alertTime > hourAgo; }); if (activeAlerts.length === 0) { console.log('✅ No active pattern alerts'); return { hasAlerts: false, alerts: [] }; } console.log(`⚠️ ${activeAlerts.length} active alert(s):`); activeAlerts.forEach((alert, i) => { console.log(`\n${i + 1}. [${alert.severity}] ${alert.type}`); console.log(` ${alert.message}`); console.log(` Recommendation: ${alert.recommendation}`); }); return { hasAlerts: true, alerts: activeAlerts }; } /** * Get pattern summary */ function getPatternSummary() { const patterns = loadActionPatterns(); const summary = { total_actions: patterns.actions.length, files_edited_multiple_times: Object.keys(patterns.patterns.same_file_edits).filter( file => patterns.patterns.same_file_edits[file].count >= 2 ), active_alerts: patterns.alerts.filter(alert => { const alertTime = new Date(alert.timestamp); const hourAgo = new Date(Date.now() - 60 * 60 * 1000); return alertTime > hourAgo; }).length }; return summary; } /** * Main */ function main() { const args = process.argv.slice(2); if (args.includes('--check')) { checkPatterns(); } else if (args.includes('--summary')) { const summary = getPatternSummary(); console.log('Action Pattern Summary:'); console.log(` Total actions: ${summary.total_actions}`); console.log(` Files with multiple edits: ${summary.files_edited_multiple_times.length}`); if (summary.files_edited_multiple_times.length > 0) { summary.files_edited_multiple_times.forEach(file => { console.log(` - ${path.basename(file)}`); }); } console.log(` Active alerts: ${summary.active_alerts}`); } else if (args.includes('--action')) { const actionIndex = args.indexOf('--action'); const fileIndex = args.indexOf('--file'); if (actionIndex === -1 || fileIndex === -1) { console.error('Usage: --action --file '); process.exit(1); } const actionType = args[actionIndex + 1]; const filePath = args[fileIndex + 1]; trackAction(actionType, filePath); console.log(`✅ Tracked: ${actionType} on ${path.basename(filePath)}`); } else { console.log('Usage:'); console.log(' --action --file Track an action'); console.log(' --check Check for active alerts'); console.log(' --summary Show pattern summary'); } } main();