tractatus/scripts/record-auto-compact.js
TheFlow 50a512e532 feat(content): add About page with research focus and Te Tiriti acknowledgment
Add comprehensive About page emphasizing moral philosophy foundation over organizational theory. PluralisticDeliberationOrchestrator positioned as primary research focus. Te Tiriti o Waitangi content integrated to establish indigenous data sovereignty principles.

Also implements auto-compact tracking system to gather empirical data on Claude Code context compression events, enabling future heuristic predictions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 01:19:28 +13:00

260 lines
9.3 KiB
JavaScript
Executable file

#!/usr/bin/env node
/**
* Auto-Compact Event Recorder
*
* Records observable metrics when an auto-compact (context window compression)
* occurs in Claude Code sessions. This helps build empirical understanding of
* what triggers compaction since we cannot directly measure Claude's internal
* context window consumption.
*
* Usage:
* node scripts/record-auto-compact.js [options]
*
* Options:
* --tokens <current>/<budget> Token count from system reminder (e.g., 89195/200000)
* --messages <count> Message count from system reminder
* --note "message" Optional note about what was happening
* --auto Automatic detection (no prompts)
*
* This script should be run IMMEDIATELY AFTER noticing a compact event:
* - Session summary message appears
* - Context seems to reset
* - Previous conversation details become unavailable
*
* The goal is to build a dataset correlating observable metrics with actual
* compact events, which can later inform heuristic warnings.
*/
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const SESSION_STATE_PATH = path.join(__dirname, '../.claude/session-state.json');
// Parse command line arguments
function parseArgs() {
const args = process.argv.slice(2);
const options = {
tokens: null,
budget: null,
messages: null,
note: null,
auto: false
};
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '--tokens':
const [current, budget] = args[++i].split('/').map(s => parseInt(s.trim()));
options.tokens = current;
options.budget = budget;
break;
case '--messages':
options.messages = parseInt(args[++i]);
break;
case '--note':
options.note = args[++i];
break;
case '--auto':
options.auto = true;
break;
case '--help':
console.log(`
Auto-Compact Event Recorder - Tractatus Framework
This tool records observable metrics when Claude Code auto-compaction occurs.
Run IMMEDIATELY after noticing a compact event to capture accurate data.
Usage:
node scripts/record-auto-compact.js [options]
Options:
--tokens <current>/<budget> Token usage (e.g., 89195/200000)
--messages <count> Message count
--note "message" What were you doing when compact occurred
--auto Non-interactive mode
--help Show this help
Examples:
# Interactive mode (prompts for missing values)
node scripts/record-auto-compact.js --tokens 150000/200000
# Fully specified
node scripts/record-auto-compact.js --tokens 145000/200000 --messages 45 --note "Large file reads"
# Automatic mode (use current session state)
node scripts/record-auto-compact.js --auto
`);
process.exit(0);
}
}
return options;
}
// Load session state
function loadSessionState() {
if (!fs.existsSync(SESSION_STATE_PATH)) {
console.error(`Error: Session state not found at ${SESSION_STATE_PATH}`);
console.error('Run: node scripts/session-init.js');
process.exit(1);
}
return JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8'));
}
// Save session state
function saveSessionState(state) {
fs.writeFileSync(SESSION_STATE_PATH, JSON.stringify(state, null, 2));
}
// Prompt user for input
async function promptFor(question, defaultValue = null) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => {
const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
rl.question(prompt, (answer) => {
rl.close();
resolve(answer.trim() || defaultValue);
});
});
}
// Calculate session duration
function calculateDuration(startTime) {
const start = new Date(startTime);
const now = new Date();
const diffMs = now - start;
const hours = Math.floor(diffMs / (1000 * 60 * 60));
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
return { hours, minutes, totalMinutes: Math.floor(diffMs / (1000 * 60)) };
}
// Main recording function
async function recordCompactEvent(options) {
console.log('\n╔════════════════════════════════════════════════════════════════╗');
console.log('║ Auto-Compact Event Recorder ║');
console.log('╚════════════════════════════════════════════════════════════════╝\n');
// Load current session state
const sessionState = loadSessionState();
console.log(`Session ID: ${sessionState.session_id}`);
console.log(`Started: ${new Date(sessionState.started).toLocaleString()}\n`);
// Initialize auto_compact_events array if not present
if (!sessionState.auto_compact_events) {
sessionState.auto_compact_events = [];
}
// Calculate session duration
const duration = calculateDuration(sessionState.started);
console.log(`Session Duration: ${duration.hours}h ${duration.minutes}m\n`);
// Gather metrics (prompt if not provided)
let tokens = options.tokens;
let budget = options.budget;
let messages = options.messages;
let note = options.note;
if (!options.auto) {
if (tokens === null) {
const tokenInput = await promptFor('Token usage (format: current/budget)',
`${sessionState.token_estimate}/${sessionState.staleness_thresholds.tokens || 200000}`);
[tokens, budget] = tokenInput.split('/').map(s => parseInt(s.trim()));
}
if (messages === null) {
const messageInput = await promptFor('Message count', sessionState.message_count);
messages = parseInt(messageInput);
}
if (note === null) {
note = await promptFor('What were you doing? (optional)', null);
}
} else {
// Auto mode: use session state
tokens = sessionState.token_estimate || 0;
budget = 200000; // Default budget
messages = sessionState.message_count || 0;
note = 'Auto-detected compact';
}
// Get pressure score from last monitoring activity
const lastPressure = sessionState.last_framework_activity?.ContextPressureMonitor;
const pressureLevel = lastPressure?.last_level || 'UNKNOWN';
const pressureScore = lastPressure?.last_score || null;
// Build compact event record
const compactEvent = {
timestamp: new Date().toISOString(),
session_id: sessionState.session_id,
metrics: {
tokens_at_compact: tokens,
token_budget: budget,
token_percentage: budget > 0 ? ((tokens / budget) * 100).toFixed(1) : null,
messages: messages,
action_count: sessionState.action_count || 0,
session_duration_minutes: duration.totalMinutes,
pressure_level: pressureLevel,
pressure_score: pressureScore
},
framework_activity: {
cross_reference_validations: sessionState.framework_components?.CrossReferenceValidator?.validations_performed || 0,
bash_command_validations: sessionState.framework_components?.BashCommandValidator?.validations_performed || 0,
bash_blocks_issued: sessionState.framework_components?.BashCommandValidator?.blocks_issued || 0
},
note: note || null
};
// Add to history
sessionState.auto_compact_events.push(compactEvent);
// Save updated state
saveSessionState(sessionState);
// Display summary
console.log('✅ Compact event recorded!\n');
console.log('Metrics Captured:');
console.log(` Tokens: ${compactEvent.metrics.tokens_at_compact} / ${compactEvent.metrics.token_budget} (${compactEvent.metrics.token_percentage}%)`);
console.log(` Messages: ${compactEvent.metrics.messages}`);
console.log(` Actions: ${compactEvent.metrics.action_count}`);
console.log(` Duration: ${duration.hours}h ${duration.minutes}m`);
console.log(` Pressure Level: ${compactEvent.metrics.pressure_level}`);
if (compactEvent.metrics.pressure_score !== null) {
console.log(` Pressure Score: ${(compactEvent.metrics.pressure_score * 100).toFixed(1)}%`);
}
console.log(`\nTotal Compacts: ${sessionState.auto_compact_events.length}\n`);
// Show compact frequency
if (sessionState.auto_compact_events.length > 1) {
const allTokens = sessionState.auto_compact_events.map(e => e.metrics.tokens_at_compact);
const avgTokens = allTokens.reduce((a, b) => a + b, 0) / allTokens.length;
const allMessages = sessionState.auto_compact_events.map(e => e.metrics.messages);
const avgMessages = allMessages.reduce((a, b) => a + b, 0) / allMessages.length;
console.log('Historical Averages:');
console.log(` Avg Tokens at Compact: ${avgTokens.toFixed(0)}`);
console.log(` Avg Messages at Compact: ${avgMessages.toFixed(1)}`);
console.log();
}
console.log('Data saved to:', SESSION_STATE_PATH);
console.log('\nRun: node scripts/check-session-pressure.js --tokens <current>/<budget>');
console.log(' to see compact statistics and predictions\n');
}
// Run if called directly
if (require.main === module) {
const options = parseArgs();
recordCompactEvent(options).catch(err => {
console.error('Error:', err.message);
process.exit(1);
});
}
module.exports = { recordCompactEvent, loadSessionState };