#!/usr/bin/env node /** * Session Pressure Monitor Script * * Uses ContextPressureMonitor to analyze current session state and provide * recommendations for session management. * * This script demonstrates the Tractatus framework dogfooding itself - using * its own governance services to manage AI-assisted development sessions. * * Usage: * node scripts/check-session-pressure.js [options] * * Options: * --tokens / Current token usage (e.g., 89195/200000) * --messages Number of messages in conversation * --tasks Number of active tasks * --errors Recent errors in last 10 minutes * --json Output JSON format * --verbose Show detailed analysis */ const monitor = require('../src/services/ContextPressureMonitor.service'); const fs = require('fs'); const path = require('path'); const SESSION_STATE_PATH = path.join(__dirname, '../.claude/session-state.json'); // Load session state to get compact history function loadSessionState() { if (!fs.existsSync(SESSION_STATE_PATH)) { return null; } return JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8')); } // Analyze compact event patterns function analyzeCompactHistory(sessionState) { if (!sessionState || !sessionState.auto_compact_events || sessionState.auto_compact_events.length === 0) { return null; } const events = sessionState.auto_compact_events; // Calculate averages const avgTokens = events.reduce((sum, e) => sum + e.metrics.tokens_at_compact, 0) / events.length; const avgMessages = events.reduce((sum, e) => sum + e.metrics.messages, 0) / events.length; const avgActions = events.reduce((sum, e) => sum + e.metrics.action_count, 0) / events.length; const avgDuration = events.reduce((sum, e) => sum + e.metrics.session_duration_minutes, 0) / events.length; // Pressure levels at compact const pressureLevels = events.map(e => e.metrics.pressure_level); const pressureScores = events.filter(e => e.metrics.pressure_score !== null) .map(e => e.metrics.pressure_score); const avgPressureScore = pressureScores.length > 0 ? pressureScores.reduce((sum, s) => sum + s, 0) / pressureScores.length : null; // Token percentage distribution const tokenPercentages = events.map(e => parseFloat(e.metrics.token_percentage)); const avgTokenPercentage = tokenPercentages.reduce((sum, p) => sum + p, 0) / tokenPercentages.length; return { total_compacts: events.length, averages: { tokens: Math.round(avgTokens), messages: Math.round(avgMessages), actions: Math.round(avgActions), duration_minutes: Math.round(avgDuration), token_percentage: avgTokenPercentage.toFixed(1), pressure_score: avgPressureScore !== null ? (avgPressureScore * 100).toFixed(1) : null }, pressure_levels: pressureLevels, latest_compact: events[events.length - 1] }; } // Predict compact risk based on historical data function predictCompactRisk(currentMetrics, compactHistory) { if (!compactHistory) { return { level: 'UNKNOWN', confidence: 0, factors: [] }; } const factors = []; let riskScore = 0; // Token proximity to average compact point if (currentMetrics.tokens && compactHistory.averages.tokens) { const tokenProximity = currentMetrics.tokens / compactHistory.averages.tokens; if (tokenProximity > 0.9) { riskScore += 30; factors.push(`Tokens near historical compact point (${(tokenProximity * 100).toFixed(0)}%)`); } else if (tokenProximity > 0.8) { riskScore += 15; factors.push(`Tokens approaching compact threshold (${(tokenProximity * 100).toFixed(0)}%)`); } } // Message proximity to average compact point if (currentMetrics.messages && compactHistory.averages.messages) { const messageProximity = currentMetrics.messages / compactHistory.averages.messages; if (messageProximity > 0.9) { riskScore += 30; factors.push(`Messages near historical compact point (${(messageProximity * 100).toFixed(0)}%)`); } else if (messageProximity > 0.8) { riskScore += 15; factors.push(`Messages approaching compact threshold (${(messageProximity * 100).toFixed(0)}%)`); } } // Action count proximity if (currentMetrics.actions && compactHistory.averages.actions) { const actionProximity = currentMetrics.actions / compactHistory.averages.actions; if (actionProximity > 0.9) { riskScore += 20; factors.push(`Actions near historical compact point (${(actionProximity * 100).toFixed(0)}%)`); } } // Determine risk level let level; if (riskScore >= 60) level = 'HIGH'; else if (riskScore >= 30) level = 'MEDIUM'; else level = 'LOW'; return { level, score: riskScore, confidence: Math.min(compactHistory.total_compacts * 20, 100), // More data = more confidence factors }; } // Parse command line arguments function parseArgs() { const args = process.argv.slice(2); const options = { tokenUsage: null, tokenBudget: null, messages: 0, tasks: 1, errors: 0, json: false, verbose: false }; for (let i = 0; i < args.length; i++) { switch (args[i]) { case '--tokens': const [current, budget] = args[++i].split('/').map(Number); options.tokenUsage = current; options.tokenBudget = budget; break; case '--messages': options.messages = parseInt(args[++i]); break; case '--tasks': options.tasks = parseInt(args[++i]); break; case '--errors': options.errors = parseInt(args[++i]); break; case '--json': options.json = true; break; case '--verbose': options.verbose = true; break; case '--help': console.log(` Session Pressure Monitor - Tractatus Framework Usage: node scripts/check-session-pressure.js [options] Options: --tokens / Token usage (e.g., 89195/200000) --messages Conversation length --tasks Active tasks --errors Recent errors --json JSON output --verbose Detailed analysis --help Show this help Examples: # Check current session node scripts/check-session-pressure.js --tokens 89195/200000 --messages 28 --tasks 2 # JSON output for automation node scripts/check-session-pressure.js --tokens 150000/200000 --json # Verbose analysis node scripts/check-session-pressure.js --tokens 180000/200000 --messages 50 --verbose `); process.exit(0); } } return options; } // Format pressure level with color function formatLevel(level) { const colors = { NORMAL: '\x1b[32m', // Green ELEVATED: '\x1b[33m', // Yellow HIGH: '\x1b[35m', // Magenta CRITICAL: '\x1b[31m', // Red DANGEROUS: '\x1b[41m' // Red background }; const reset = '\x1b[0m'; return `${colors[level] || ''}${level}${reset}`; } // Format recommendation with icon function formatRecommendation(rec) { const icons = { CONTINUE_NORMAL: 'āœ…', INCREASE_VERIFICATION: 'āš ļø', SUGGEST_CONTEXT_REFRESH: 'šŸ”„', MANDATORY_VERIFICATION: '🚨', IMMEDIATE_HALT: 'šŸ›‘' }; return `${icons[rec] || '•'} ${rec}`; } // Main analysis function async function analyzeSession(options) { // Build context object const context = { messages_count: options.messages, task_depth: options.tasks, errors_recent: options.errors }; // Add token usage if provided if (options.tokenUsage && options.tokenBudget) { context.token_usage = options.tokenUsage / options.tokenBudget; context.token_limit = options.tokenBudget; } // Load session state and compact history const sessionState = loadSessionState(); const compactHistory = sessionState ? analyzeCompactHistory(sessionState) : null; // Predict compact risk const compactRisk = compactHistory ? predictCompactRisk({ tokens: options.tokenUsage, messages: options.messages, actions: sessionState?.action_count || 0 }, compactHistory) : null; // Run analysis const analysis = await monitor.analyzePressure(context); analysis.compactHistory = compactHistory; analysis.compactRisk = compactRisk; // Output results if (options.json) { console.log(JSON.stringify(analysis, null, 2)); } else { console.log('\n╔════════════════════════════════════════════════════════════════╗'); console.log('ā•‘ Tractatus Session Pressure Analysis ā•‘'); console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n'); // Pressure Level console.log(`Pressure Level: ${formatLevel(analysis.level)}`); console.log(`Overall Score: ${(analysis.overall_score * 100).toFixed(1)}%`); console.log(`Action: ${analysis.action}\n`); // Metrics console.log('Metrics:'); console.log(` Token Usage: ${(analysis.metrics.tokenUsage.score * 100).toFixed(1)}%`); console.log(` Conversation: ${(analysis.metrics.conversationLength.score * 100).toFixed(1)}%`); console.log(` Task Complexity: ${(analysis.metrics.taskComplexity.score * 100).toFixed(1)}%`); console.log(` Error Frequency: ${(analysis.metrics.errorFrequency.score * 100).toFixed(1)}%`); console.log(` Instructions: ${(analysis.metrics.instructionDensity.score * 100).toFixed(1)}%\n`); // Recommendations if (analysis.recommendations.length > 0) { console.log('Recommendations:'); analysis.recommendations.forEach(rec => { console.log(` ${formatRecommendation(rec)}`); }); console.log(); } // Warnings if (analysis.warnings.length > 0) { console.log('āš ļø Warnings:'); analysis.warnings.forEach(warning => { console.log(` • ${warning}`); }); console.log(); } // Trend if (analysis.trend) { const trendIcons = { escalating: 'šŸ“ˆ Escalating', improving: 'šŸ“‰ Improving', stable: 'āž”ļø Stable' }; console.log(`Trend: ${trendIcons[analysis.trend]}\n`); } // Compact History and Risk Prediction if (analysis.compactHistory) { console.log('═════════════════════════════════════════════════════════════════'); console.log('šŸ“Š Auto-Compact History\n'); console.log(`Total Compacts Recorded: ${analysis.compactHistory.total_compacts}`); console.log('\nAverage Metrics at Compact:'); console.log(` Tokens: ${analysis.compactHistory.averages.tokens} (${analysis.compactHistory.averages.token_percentage}% of budget)`); console.log(` Messages: ${analysis.compactHistory.averages.messages}`); console.log(` Actions: ${analysis.compactHistory.averages.actions}`); console.log(` Session Length: ${analysis.compactHistory.averages.duration_minutes} minutes`); if (analysis.compactHistory.averages.pressure_score) { console.log(` Pressure Score: ${analysis.compactHistory.averages.pressure_score}%`); } // Latest compact info const latest = analysis.compactHistory.latest_compact; const timeSince = Math.round((Date.now() - new Date(latest.timestamp).getTime()) / (1000 * 60)); console.log(`\nLast Compact: ${timeSince} minutes ago`); if (latest.note) { console.log(` Note: ${latest.note}`); } console.log(); // Compact risk prediction if (analysis.compactRisk && analysis.compactRisk.level !== 'UNKNOWN') { const riskIcons = { LOW: 'āœ…', MEDIUM: 'āš ļø', HIGH: '🚨' }; const riskColors = { LOW: '\x1b[32m', // Green MEDIUM: '\x1b[33m', // Yellow HIGH: '\x1b[31m' // Red }; const reset = '\x1b[0m'; console.log('šŸ”® Compact Risk Prediction\n'); console.log(`Risk Level: ${riskIcons[analysis.compactRisk.level]} ${riskColors[analysis.compactRisk.level]}${analysis.compactRisk.level}${reset}`); console.log(`Risk Score: ${analysis.compactRisk.score}/100`); console.log(`Confidence: ${analysis.compactRisk.confidence}% (based on ${analysis.compactHistory.total_compacts} recorded events)`); if (analysis.compactRisk.factors.length > 0) { console.log('\nRisk Factors:'); analysis.compactRisk.factors.forEach(factor => { console.log(` • ${factor}`); }); } console.log(); // Warning if high risk if (analysis.compactRisk.level === 'HIGH') { console.log('āš ļø HIGH COMPACT RISK: Consider summarizing progress and planning next steps'); console.log(' to minimize context loss if compaction occurs.\n'); } else if (analysis.compactRisk.level === 'MEDIUM') { console.log('šŸ“ MEDIUM COMPACT RISK: Compact may occur soon. Be prepared to\n'); console.log(' summarize key decisions and action items.\n'); } } console.log('═════════════════════════════════════════════════════════════════\n'); } else { console.log('═════════════════════════════════════════════════════════════════'); console.log('šŸ“Š Auto-Compact History\n'); console.log('No compact events recorded yet.'); console.log('\nWhen you notice an auto-compact (context compression), run:'); console.log(' node scripts/record-auto-compact.js --tokens /\n'); console.log('This builds empirical data to predict future compacts.\n'); console.log('═════════════════════════════════════════════════════════════════\n'); } // Verbose output if (options.verbose) { console.log('Detailed Metrics:'); Object.entries(analysis.metrics).forEach(([name, metric]) => { console.log(` ${name}:`); console.log(` Raw: ${metric.raw}`); console.log(` Normalized: ${metric.normalized.toFixed(3)}`); console.log(` Threshold: ${metric.threshold}`); if (metric.factors) { console.log(` Factors: ${metric.factors.join(', ')}`); } }); console.log(); } // Summary console.log('─────────────────────────────────────────────────────────────────'); if (analysis.level === 'NORMAL') { console.log('āœ… Session conditions are normal. Continue working.\n'); } else if (analysis.level === 'ELEVATED') { console.log('āš ļø Pressure is elevated. Increase verification and monitoring.\n'); } else if (analysis.level === 'HIGH') { console.log('šŸ”„ Pressure is high. Consider refreshing context soon.\n'); } else if (analysis.level === 'CRITICAL') { console.log('🚨 Critical pressure! Mandatory verification required.\n'); } else if (analysis.level === 'DANGEROUS') { console.log('šŸ›‘ DANGEROUS conditions! Halt and refresh context immediately.\n'); } } return analysis; } // Run if called directly if (require.main === module) { (async () => { const options = parseArgs(); // Validate inputs if (options.tokenUsage === null) { console.error('Error: --tokens argument required'); console.error('Usage: node scripts/check-session-pressure.js --tokens /'); console.error('Run with --help for more information'); process.exit(1); } const analysis = await analyzeSession(options); // Exit with appropriate code const exitCodes = { NORMAL: 0, ELEVATED: 0, HIGH: 1, CRITICAL: 2, DANGEROUS: 3 }; process.exit(exitCodes[analysis.level] || 0); })().catch(err => { console.error('Error during pressure analysis:', err); process.exit(1); }); } module.exports = { analyzeSession, parseArgs };