- Make analyzeSession() async in check-session-pressure.js - Add await before monitor.analyzePressure() call - Wrap main execution in async IIFE with error handling - Update all ContextPressureMonitor tests to use async/await - Fix MetacognitiveVerifier edge case assertion (toBeLessThanOrEqual) Fixes TypeError: Cannot read properties of undefined (reading 'tokenUsage') that was blocking session initialization. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
443 lines
16 KiB
JavaScript
Executable file
443 lines
16 KiB
JavaScript
Executable file
#!/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>/<budget> Current token usage (e.g., 89195/200000)
|
|
* --messages <count> Number of messages in conversation
|
|
* --tasks <count> Number of active tasks
|
|
* --errors <count> 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 <current>/<budget> Token usage (e.g., 89195/200000)
|
|
--messages <count> Conversation length
|
|
--tasks <count> Active tasks
|
|
--errors <count> 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 <current>/<budget>\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 <current>/<budget>');
|
|
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 };
|