- Create Economist SubmissionTracking package correctly: * mainArticle = full blog post content * coverLetter = 216-word SIR— letter * Links to blog post via blogPostId - Archive 'Letter to The Economist' from blog posts (it's the cover letter) - Fix date display on article cards (use published_at) - Target publication already displaying via blue badge Database changes: - Make blogPostId optional in SubmissionTracking model - Economist package ID: 68fa85ae49d4900e7f2ecd83 - Le Monde package ID: 68fa2abd2e6acd5691932150 Next: Enhanced modal with tabs, validation, export 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
248 lines
6.5 KiB
JavaScript
Executable file
248 lines
6.5 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Token Checkpoint Monitor
|
|
*
|
|
* Checks if current token usage has passed defined checkpoints
|
|
* and generates pressure reports when thresholds are crossed.
|
|
*
|
|
* Usage:
|
|
* node scripts/check-token-checkpoint.js --tokens 55000
|
|
* node scripts/check-token-checkpoint.js --tokens 55000/200000
|
|
*
|
|
* This should be called AFTER each response when token count is visible
|
|
* in <system-warning> tags.
|
|
*
|
|
* Copyright 2025 Tractatus Project
|
|
* Licensed under Apache License 2.0
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { execSync } = require('child_process');
|
|
|
|
const TOKEN_CHECKPOINTS_PATH = path.join(__dirname, '../.claude/token-checkpoints.json');
|
|
const SESSION_STATE_PATH = path.join(__dirname, '../.claude/session-state.json');
|
|
|
|
/**
|
|
* Color output
|
|
*/
|
|
const colors = {
|
|
reset: '\x1b[0m',
|
|
bright: '\x1b[1m',
|
|
green: '\x1b[32m',
|
|
yellow: '\x1b[33m',
|
|
blue: '\x1b[34m',
|
|
red: '\x1b[31m',
|
|
cyan: '\x1b[36m'
|
|
};
|
|
|
|
function log(message, color = 'reset') {
|
|
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
}
|
|
|
|
function header(message) {
|
|
console.log('');
|
|
log('═'.repeat(70), 'cyan');
|
|
log(` ${message}`, 'bright');
|
|
log('═'.repeat(70), 'cyan');
|
|
console.log('');
|
|
}
|
|
|
|
function section(message) {
|
|
console.log('');
|
|
log(`▶ ${message}`, 'blue');
|
|
}
|
|
|
|
function success(message) {
|
|
log(` ✓ ${message}`, 'green');
|
|
}
|
|
|
|
function warning(message) {
|
|
log(` ⚠ ${message}`, 'yellow');
|
|
}
|
|
|
|
function error(message) {
|
|
log(` ✗ ${message}`, 'red');
|
|
}
|
|
|
|
/**
|
|
* Parse token count from command line
|
|
*/
|
|
function parseTokenCount(arg) {
|
|
if (!arg) {
|
|
error('No token count provided');
|
|
console.log('Usage: node scripts/check-token-checkpoint.js --tokens 55000');
|
|
console.log(' or: node scripts/check-token-checkpoint.js --tokens 55000/200000');
|
|
process.exit(1);
|
|
}
|
|
|
|
// Handle both "55000" and "55000/200000" formats
|
|
const parts = arg.split('/');
|
|
const current = parseInt(parts[0]);
|
|
const budget = parts.length > 1 ? parseInt(parts[1]) : 200000;
|
|
|
|
if (isNaN(current) || isNaN(budget)) {
|
|
error(`Invalid token count: ${arg}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
return { current, budget };
|
|
}
|
|
|
|
/**
|
|
* Load checkpoint configuration
|
|
*/
|
|
function loadCheckpoints() {
|
|
try {
|
|
if (!fs.existsSync(TOKEN_CHECKPOINTS_PATH)) {
|
|
error('Token checkpoints file not found');
|
|
error('Run: node scripts/session-init.js');
|
|
process.exit(1);
|
|
}
|
|
|
|
return JSON.parse(fs.readFileSync(TOKEN_CHECKPOINTS_PATH, 'utf8'));
|
|
} catch (err) {
|
|
error(`Failed to load checkpoints: ${err.message}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if any checkpoints have been passed
|
|
*/
|
|
function checkCheckpoints(current, checkpoints) {
|
|
const passed = [];
|
|
const upcoming = [];
|
|
|
|
for (const checkpoint of checkpoints.checkpoints) {
|
|
if (current >= checkpoint.tokens && !checkpoint.completed) {
|
|
passed.push(checkpoint);
|
|
} else if (current < checkpoint.tokens) {
|
|
upcoming.push(checkpoint);
|
|
}
|
|
}
|
|
|
|
return { passed, upcoming };
|
|
}
|
|
|
|
/**
|
|
* Mark checkpoint as completed
|
|
*/
|
|
function markCheckpointCompleted(checkpoints, checkpoint) {
|
|
const idx = checkpoints.checkpoints.findIndex(c => c.tokens === checkpoint.tokens);
|
|
if (idx !== -1) {
|
|
checkpoints.checkpoints[idx].completed = true;
|
|
checkpoints.checkpoints[idx].timestamp = new Date().toISOString();
|
|
}
|
|
|
|
// Update next_checkpoint to next uncompleted
|
|
const nextUncompleted = checkpoints.checkpoints.find(c => !c.completed);
|
|
checkpoints.next_checkpoint = nextUncompleted ? nextUncompleted.tokens : null;
|
|
checkpoints.overdue = false;
|
|
checkpoints.last_check = new Date().toISOString();
|
|
|
|
fs.writeFileSync(TOKEN_CHECKPOINTS_PATH, JSON.stringify(checkpoints, null, 2));
|
|
}
|
|
|
|
/**
|
|
* Generate pressure report for checkpoint
|
|
*/
|
|
function generatePressureReport(current, budget, checkpoint) {
|
|
try {
|
|
section(`Generating Pressure Report for ${checkpoint.percentage}% Checkpoint`);
|
|
|
|
const output = execSync(
|
|
`node scripts/check-session-pressure.js --tokens ${current}/${budget} --messages 1 --tasks 0`,
|
|
{ encoding: 'utf8', stdio: 'pipe' }
|
|
);
|
|
|
|
console.log(output);
|
|
|
|
return true;
|
|
} catch (err) {
|
|
error(`Pressure report failed: ${err.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main execution
|
|
*/
|
|
function main() {
|
|
// Parse arguments
|
|
const args = process.argv.slice(2);
|
|
const tokensIdx = args.indexOf('--tokens');
|
|
|
|
if (tokensIdx === -1 || tokensIdx === args.length - 1) {
|
|
error('Missing --tokens argument');
|
|
console.log('Usage: node scripts/check-token-checkpoint.js --tokens 55000');
|
|
process.exit(1);
|
|
}
|
|
|
|
const tokenArg = args[tokensIdx + 1];
|
|
const { current, budget } = parseTokenCount(tokenArg);
|
|
|
|
header('Token Checkpoint Monitor');
|
|
|
|
log(` Current Token Usage: ${current.toLocaleString()} / ${budget.toLocaleString()}`, 'cyan');
|
|
log(` Percentage: ${((current / budget) * 100).toFixed(1)}%`, 'cyan');
|
|
|
|
// Load checkpoints
|
|
const checkpoints = loadCheckpoints();
|
|
|
|
// Check which checkpoints passed
|
|
const { passed, upcoming } = checkCheckpoints(current, checkpoints);
|
|
|
|
if (passed.length === 0) {
|
|
section('Checkpoint Status');
|
|
success('No new checkpoints passed');
|
|
|
|
if (upcoming.length > 0) {
|
|
const next = upcoming[0];
|
|
const remaining = next.tokens - current;
|
|
log(` Next checkpoint: ${next.tokens.toLocaleString()} tokens (${next.percentage}%)`, 'cyan');
|
|
log(` Remaining: ${remaining.toLocaleString()} tokens`, 'cyan');
|
|
} else {
|
|
success('All checkpoints completed!');
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Process passed checkpoints
|
|
section('Checkpoints Passed');
|
|
|
|
for (const checkpoint of passed) {
|
|
warning(`Checkpoint ${checkpoint.percentage}% (${checkpoint.tokens.toLocaleString()} tokens) PASSED`);
|
|
|
|
// Mark as completed
|
|
markCheckpointCompleted(checkpoints, checkpoint);
|
|
success(`Marked checkpoint ${checkpoint.percentage}% as completed`);
|
|
|
|
// Generate pressure report
|
|
generatePressureReport(current, budget, checkpoint);
|
|
}
|
|
|
|
// Show upcoming checkpoints
|
|
if (upcoming.length > 0) {
|
|
section('Upcoming Checkpoints');
|
|
for (const checkpoint of upcoming) {
|
|
const remaining = checkpoint.tokens - current;
|
|
log(` ${checkpoint.percentage}%: ${checkpoint.tokens.toLocaleString()} tokens (${remaining.toLocaleString()} remaining)`, 'cyan');
|
|
}
|
|
}
|
|
|
|
console.log('');
|
|
log('═'.repeat(70), 'cyan');
|
|
success('Checkpoint monitoring complete');
|
|
log('═'.repeat(70), 'cyan');
|
|
console.log('');
|
|
}
|
|
|
|
// Run if called directly
|
|
if (require.main === module) {
|
|
main();
|
|
}
|
|
|
|
module.exports = { parseTokenCount, loadCheckpoints, checkCheckpoints };
|