Initial commit
This commit is contained in:
109
hooks/compact_start.js
Executable file
109
hooks/compact_start.js
Executable file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Contextune CompactStart Hook
|
||||
*
|
||||
* Tracks when Claude Code compacts the conversation context.
|
||||
* This reveals how well we preserve context for the main agent.
|
||||
*
|
||||
* Metrics:
|
||||
* - Time from session start to first compact
|
||||
* - Total compacts per session
|
||||
* - Average time between compacts
|
||||
*
|
||||
* Context Cost: 0 tokens (hook runs outside conversation)
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
function main() {
|
||||
try {
|
||||
const dbFile = path.join('.contextune', 'observability.db');
|
||||
|
||||
if (!fs.existsSync(dbFile)) {
|
||||
console.error('DEBUG: Observability DB not found, skipping compact tracking');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const compactTime = Date.now() / 1000; // Unix timestamp
|
||||
|
||||
try {
|
||||
// Get most recent session
|
||||
const getSessionQuery = `
|
||||
SELECT session_id, start_time, first_compact_time
|
||||
FROM sessions
|
||||
ORDER BY start_time DESC
|
||||
LIMIT 1
|
||||
`;
|
||||
|
||||
const sessionData = execSync(`sqlite3 "${dbFile}" "${getSessionQuery}"`, {
|
||||
encoding: 'utf-8',
|
||||
timeout: 1000
|
||||
}).trim();
|
||||
|
||||
if (!sessionData) {
|
||||
console.error('DEBUG: No active session found');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const [sessionId, startTime, firstCompactTime] = sessionData.split('|');
|
||||
|
||||
// Calculate duration if this is the first compact
|
||||
if (!firstCompactTime || firstCompactTime === '') {
|
||||
const duration = compactTime - parseFloat(startTime);
|
||||
|
||||
const updateQuery = `
|
||||
UPDATE sessions
|
||||
SET first_compact_time = ${compactTime},
|
||||
duration_to_compact = ${duration}
|
||||
WHERE session_id = '${sessionId}'
|
||||
`;
|
||||
|
||||
execSync(`sqlite3 "${dbFile}" "${updateQuery}"`, {
|
||||
stdio: 'pipe',
|
||||
timeout: 1000
|
||||
});
|
||||
|
||||
const minutes = (duration / 60).toFixed(1);
|
||||
console.error(`DEBUG: First compact at ${new Date(compactTime * 1000).toISOString()}`);
|
||||
console.error(`DEBUG: Context preserved for ${minutes} minutes`);
|
||||
console.error(`DEBUG: 🎯 Contextune effectiveness: ${minutes} min of full context`);
|
||||
} else {
|
||||
// Not first compact - just log
|
||||
const timeSinceFirst = compactTime - parseFloat(firstCompactTime);
|
||||
console.error(`DEBUG: Subsequent compact at +${(timeSinceFirst / 60).toFixed(1)} min from first`);
|
||||
}
|
||||
|
||||
// Log compact event to performance metrics
|
||||
const logQuery = `
|
||||
INSERT INTO performance_metrics (component, operation, latency_ms, timestamp, metadata)
|
||||
VALUES ('session_tracking', 'compact_event', 0, ${compactTime}, '{"compact_time": ${compactTime}}')
|
||||
`;
|
||||
|
||||
execSync(`sqlite3 "${dbFile}" "${logQuery}"`, {
|
||||
stdio: 'pipe',
|
||||
timeout: 1000
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error('DEBUG: Failed to track compact event:', err.message);
|
||||
}
|
||||
|
||||
// Always continue (don't block compaction)
|
||||
const output = {
|
||||
continue: true
|
||||
};
|
||||
|
||||
console.log(JSON.stringify(output));
|
||||
process.exit(0);
|
||||
|
||||
} catch (err) {
|
||||
console.error('Contextune CompactStart hook error:', err.message);
|
||||
// Always continue (don't block compaction)
|
||||
console.log(JSON.stringify({ continue: true }));
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user