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>
260 lines
9.3 KiB
JavaScript
Executable file
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 };
|