tractatus/scripts/session-init.js
TheFlow 2298d36bed fix(submissions): restructure Economist package and fix article display
- 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>
2025-10-24 08:47:42 +13:00

697 lines
24 KiB
JavaScript
Executable file

#!/usr/bin/env node
/**
* Tractatus Session Initialization
*
* Automatically runs all mandatory framework checks at session start.
* Should be called at the beginning of every Claude Code session.
*
* Copyright 2025 Tractatus Project
* Licensed under Apache License 2.0
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const SESSION_STATE_PATH = path.join(__dirname, '../.claude/session-state.json');
const INSTRUCTION_HISTORY_PATH = path.join(__dirname, '../.claude/instruction-history.json');
const TOKEN_CHECKPOINTS_PATH = path.join(__dirname, '../.claude/token-checkpoints.json');
/**
* Color output helpers
*/
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');
}
/**
* Check if this is a new session or restart
*/
function isNewSession() {
try {
const sessionState = JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8'));
// Check if session_id is today's date
const today = new Date().toISOString().split('T')[0];
const sessionDate = sessionState.session_id.split('-').slice(0, 3).join('-');
// Check if message count is 0 (new session)
const isNew = sessionState.message_count === 0;
// Check if session started today
const isToday = sessionDate === today;
return { isNew, isToday, sessionState };
} catch (err) {
// If file doesn't exist or can't be read, treat as new session
return { isNew: true, isToday: true, sessionState: null };
}
}
/**
* Initialize session state
*/
function initializeSessionState() {
const sessionId = new Date().toISOString().split('T')[0] + '-001';
const timestamp = new Date().toISOString();
const sessionState = {
version: '1.0.0',
session_id: sessionId,
started: timestamp,
message_count: 1,
token_estimate: 0,
last_framework_activity: {
ContextPressureMonitor: {
message: 1,
tokens: 0,
timestamp: timestamp,
last_level: 'NORMAL',
last_score: 0
},
InstructionPersistenceClassifier: {
message: 0,
tokens: 0,
timestamp: null,
last_classification: null
},
CrossReferenceValidator: {
message: 0,
tokens: 0,
timestamp: null,
last_validation: null
},
BoundaryEnforcer: {
message: 0,
tokens: 0,
timestamp: null,
last_check: null
},
MetacognitiveVerifier: {
message: 0,
tokens: 0,
timestamp: null,
last_verification: null
},
PluralisticDeliberationOrchestrator: {
message: 0,
tokens: 0,
timestamp: null,
last_deliberation: null
}
},
staleness_thresholds: {
messages: 20,
tokens: 30000
},
alerts: [],
last_updated: timestamp,
initialized: true
};
fs.writeFileSync(SESSION_STATE_PATH, JSON.stringify(sessionState, null, 2));
return sessionState;
}
/**
* Reset token checkpoints for new session
*/
function resetTokenCheckpoints() {
const checkpoints = {
version: '1.0.0',
budget: 200000,
checkpoints: [
{ percentage: 25, tokens: 50000, completed: false, timestamp: null },
{ percentage: 50, tokens: 100000, completed: false, timestamp: null },
{ percentage: 75, tokens: 150000, completed: false, timestamp: null }
],
next_checkpoint: 50000,
overdue: false,
last_check: new Date().toISOString()
};
fs.writeFileSync(TOKEN_CHECKPOINTS_PATH, JSON.stringify(checkpoints, null, 2));
return checkpoints;
}
/**
* Load and summarize instruction history
*/
function loadInstructionHistory() {
try {
if (!fs.existsSync(INSTRUCTION_HISTORY_PATH)) {
return { total: 0, high: 0, medium: 0, low: 0 };
}
const history = JSON.parse(fs.readFileSync(INSTRUCTION_HISTORY_PATH, 'utf8'));
const active = history.instructions?.filter(i => i.active) || [];
const summary = {
total: active.length,
high: active.filter(i => i.persistence === 'HIGH').length,
medium: active.filter(i => i.persistence === 'MEDIUM').length,
low: active.filter(i => i.persistence === 'LOW').length,
strategic: active.filter(i => i.quadrant === 'STRATEGIC').length,
system: active.filter(i => i.quadrant === 'SYSTEM').length
};
return summary;
} catch (err) {
warning(`Could not load instruction history: ${err.message}`);
return { total: 0, high: 0, medium: 0, low: 0 };
}
}
/**
* Run initial pressure check
*/
function runPressureCheck() {
try {
const output = execSync(
'node scripts/check-session-pressure.js --tokens 0/200000 --messages 1 --tasks 0',
{ encoding: 'utf8', stdio: 'pipe' }
);
// Extract pressure level from output
const levelMatch = output.match(/Pressure Level:\s+\[.*?m(.*?)\[/);
const scoreMatch = output.match(/Overall Score:\s+([\d.]+)%/);
return {
level: levelMatch ? levelMatch[1] : 'NORMAL',
score: scoreMatch ? parseFloat(scoreMatch[1]) : 0,
output: output
};
} catch (err) {
error(`Pressure check failed: ${err.message}`);
return { level: 'UNKNOWN', score: 0, output: '' };
}
}
/**
* Check if local development server is running on port 9000
* ENFORCEMENT: Blocks session if not running during development work
*/
function checkLocalServer() {
try {
const output = execSync('lsof -i :9000 -t', { encoding: 'utf8', stdio: 'pipe' });
return output.trim().length > 0;
} catch (err) {
// lsof returns non-zero exit code if no process found
return false;
}
}
/**
* Start local development server
*/
function startLocalServer() {
try {
log(' Attempting to start local server...', 'cyan');
execSync('npm start &', { encoding: 'utf8', stdio: 'inherit', detached: true });
// Wait for server to start
let attempts = 0;
while (attempts < 10) {
if (checkLocalServer()) {
return true;
}
execSync('sleep 1');
attempts++;
}
return false;
} catch (err) {
return false;
}
}
/**
* Main initialization
*/
async function main() {
header('Tractatus Framework - Session Initialization');
// Check session status
section('1. Checking Session Status');
const { isNew, isToday, sessionState } = isNewSession();
if (!isNew && sessionState) {
log(` Session: ${sessionState.session_id}`, 'cyan');
log(` Messages: ${sessionState.message_count}`, 'cyan');
log(` Status: Continuing existing session`, 'yellow');
console.log('');
warning('This is a CONTINUED session - framework should already be active');
warning('If this is actually a NEW session, delete .claude/session-state.json');
} else {
success('New session detected - initializing framework');
const newState = initializeSessionState();
log(` Session ID: ${newState.session_id}`, 'cyan');
}
// Reset checkpoints for new day
section('2. Resetting Token Checkpoints');
const checkpoints = resetTokenCheckpoints();
success(`Token budget: ${checkpoints.budget.toLocaleString()}`);
success(`Next checkpoint: ${checkpoints.next_checkpoint.toLocaleString()} tokens (25%)`);
// Load instruction history
section('3. Loading Instruction History');
const instructions = loadInstructionHistory();
if (instructions.total === 0) {
log(' No active instructions stored', 'yellow');
} else {
success(`Active instructions: ${instructions.total}`);
if (instructions.high > 0) {
log(` HIGH persistence: ${instructions.high}`, 'cyan');
}
if (instructions.medium > 0) {
log(` MEDIUM persistence: ${instructions.medium}`, 'cyan');
}
if (instructions.low > 0) {
log(` LOW persistence: ${instructions.low}`, 'cyan');
}
console.log('');
if (instructions.strategic > 0 || instructions.system > 0) {
warning(`Critical instructions active (STRATEGIC: ${instructions.strategic}, SYSTEM: ${instructions.system})`);
warning('These must be validated before conflicting actions');
}
}
// Run initial pressure check
section('4. Running Initial Pressure Check');
const pressure = runPressureCheck();
success(`Pressure Level: ${pressure.level}`);
success(`Overall Score: ${pressure.score}%`);
// Framework component status
section('5. Framework Components');
success('ContextPressureMonitor: ACTIVE');
success('InstructionPersistenceClassifier: READY');
success('CrossReferenceValidator: READY');
success('BoundaryEnforcer: READY');
success('MetacognitiveVerifier: READY (selective mode)');
success('PluralisticDeliberationOrchestrator: READY');
// Run framework tests
section('6. Running Framework Tests');
try {
log(' Running unit tests for Tractatus services...', 'cyan');
const testOutput = execSync(
'npm test -- --testPathPattern="tests/unit/(ContextPressureMonitor|InstructionPersistenceClassifier|CrossReferenceValidator|BoundaryEnforcer|MetacognitiveVerifier|PluralisticDeliberationOrchestrator)" 2>&1',
{ encoding: 'utf8' }
);
// Extract test results
const passMatch = testOutput.match(/Tests:\s+(\d+) passed/);
const failMatch = testOutput.match(/(\d+) failed/);
const totalMatch = testOutput.match(/(\d+) total/);
if (failMatch && parseInt(failMatch[1]) > 0) {
console.log('');
error(`Framework tests FAILED: ${failMatch[1]} failures`);
error('Framework components are not functioning correctly - cannot proceed');
log(' Run: npm test -- --testPathPattern="tests/unit" for details', 'yellow');
console.log('');
error('Session initialization ABORTED due to test failures');
console.log('');
process.exit(1); // Exit with failure code
} else if (passMatch) {
success(`All framework tests passed (${passMatch[1]}/${totalMatch ? totalMatch[1] : passMatch[1]} tests)`);
} else {
warning('Could not parse test results - tests may have run successfully');
}
} catch (err) {
// Test failures throw non-zero exit code - this is a FAILURE condition
const output = err.stdout || err.stderr || err.message;
const passMatch = output.match(/Tests:\s+(\d+) passed/);
const failMatch = output.match(/(\d+) failed/);
// Check if tests actually passed despite stderr output
if (passMatch && (!failMatch || parseInt(failMatch[1]) === 0)) {
const totalMatch = output.match(/(\d+) total/);
success(`All framework tests passed (${passMatch[1]}/${totalMatch ? totalMatch[1] : passMatch[1]} tests)`);
return; // Tests passed, continue with init
}
console.log('');
if (failMatch && parseInt(failMatch[1]) > 0) {
error(`Framework tests FAILED: ${failMatch[1]} failures`);
error('Framework components are not functioning correctly - cannot proceed');
log(' Run: npm test -- --testPathPattern="tests/unit" to see failures', 'yellow');
} else {
error('Framework tests encountered an error');
error(err.message);
}
console.log('');
error('Session initialization ABORTED due to test failures');
console.log('');
process.exit(1); // Exit with failure code
}
// Prohibited Terms Scan (Framework Phase 1)
section('7. Scanning for Prohibited Terms');
try {
const ProhibitedTermsScanner = require('./framework-components/ProhibitedTermsScanner');
const scanner = new ProhibitedTermsScanner({ silent: false });
const violations = await scanner.scan();
if (violations.length === 0) {
success('No prohibited terms found (inst_016/017/018 compliant)');
} else {
console.log('');
warning(`Found ${violations.length} violation(s) in user-facing content:`);
// Group by rule
const byRule = violations.reduce((acc, v) => {
if (!acc[v.rule]) acc[v.rule] = [];
acc[v.rule].push(v);
return acc;
}, {});
Object.entries(byRule).forEach(([rule, items]) => {
log(` ${rule}: ${items.length} violation(s)`, 'yellow');
});
console.log('');
log(' Run: node scripts/framework-components/ProhibitedTermsScanner.js --details', 'cyan');
log(' Or: node scripts/framework-components/ProhibitedTermsScanner.js --fix', 'cyan');
console.log('');
}
} catch (err) {
warning(`Could not run prohibited terms scanner: ${err.message}`);
}
// CSP Compliance Scan
section('8. CSP Compliance Scan (inst_008)');
try {
const { scanForViolations, displayViolations } = require('./check-csp-violations');
const violations = scanForViolations();
if (violations.length === 0) {
success('No CSP violations found in public files');
} else {
error(`Found ${violations.length} CSP violation(s) in codebase`);
console.log('');
// Group by file for summary
const fileGroups = {};
violations.forEach(v => {
fileGroups[v.file] = (fileGroups[v.file] || 0) + 1;
});
Object.entries(fileGroups).forEach(([file, count]) => {
log(`${file}: ${count} violation(s)`, 'yellow');
});
console.log('');
warning('Run: node scripts/check-csp-violations.js for details');
warning('Run: node scripts/fix-csp-violations.js to remediate');
console.log('');
}
} catch (err) {
warning(`Could not run CSP scan: ${err.message}`);
}
// ENFORCEMENT: Local development server check
section('9. Development Environment Enforcement');
const localServerRunning = checkLocalServer();
if (!localServerRunning) {
error('LOCAL DEVELOPMENT SERVER NOT RUNNING ON PORT 9000');
console.log('');
log(' ⚠️ MANDATORY REQUIREMENT:', 'bright');
log(' All development work MUST be tested locally before production deployment.', 'yellow');
log(' The local server on port 9000 is required for:', 'yellow');
log(' • Testing changes before deployment', 'cyan');
log(' • Verifying integrations work correctly', 'cyan');
log(' • Preventing production-first development', 'cyan');
log(' • Framework fade prevention', 'cyan');
console.log('');
log(' To fix:', 'bright');
log(' 1. Open a new terminal', 'cyan');
log(' 2. cd /home/theflow/projects/tractatus', 'cyan');
log(' 3. npm start', 'cyan');
log(' 4. Re-run: node scripts/session-init.js', 'cyan');
console.log('');
log(' Once the server is running, session-init will pass.', 'green');
console.log('');
error('SESSION BLOCKED: Start local server before proceeding');
console.log('');
process.exit(1);
}
success('Local development server running on port 9000');
success('Development environment ready');
// Hook Architecture Status
section('10. Continuous Enforcement Architecture');
const hookValidatorsExist = fs.existsSync(path.join(__dirname, 'hook-validators'));
if (hookValidatorsExist) {
success('Hook validators installed (architectural enforcement)');
log(' • validate-file-edit.js: Enforces pre-action checks, CSP, conflicts', 'cyan');
log(' • validate-file-write.js: Prevents overwrites, enforces boundaries', 'cyan');
log(' • check-token-checkpoint.js: Prevents checkpoint fade', 'cyan');
console.log('');
log(' 📋 Pre-approved commands documented in PRE_APPROVED_COMMANDS.md', 'cyan');
log(' 🔍 Hook architecture prevents voluntary compliance failures', 'cyan');
} else {
warning('Hook validators not yet installed');
log(' Hooks provide architectural enforcement beyond documentation', 'yellow');
}
// Database Sync
section('10. Syncing Instructions to Database');
try {
log(' Synchronizing .claude/instruction-history.json to MongoDB...', 'cyan');
const { syncInstructions } = require('./sync-instructions-to-db.js');
// Run sync in silent mode (no verbose output)
const syncResult = await syncInstructions();
if (syncResult && syncResult.success) {
success(`Database synchronized: ${syncResult.finalCount} active rules`);
if (syncResult.added > 0) {
log(` Added: ${syncResult.added} new rules`, 'cyan');
}
if (syncResult.updated > 0) {
log(` Updated: ${syncResult.updated} existing rules`, 'cyan');
}
if (syncResult.deactivated > 0) {
log(` Deactivated: ${syncResult.deactivated} orphaned rules`, 'cyan');
}
} else {
warning('Database sync skipped or failed - admin UI may show stale data');
}
} catch (err) {
warning(`Database sync failed: ${err.message}`);
log(' Admin UI may show outdated rule counts', 'yellow');
log(' Run: node scripts/sync-instructions-to-db.js --force to sync manually', 'yellow');
}
// Credential Vault Server (Optional)
section('11. Credential Vault Server');
const vaultPath = path.join(process.env.HOME, 'Documents/credentials/vault.kdbx');
const vaultServerPath = path.join(__dirname, '../.credential-vault');
if (fs.existsSync(vaultPath)) {
// Vault exists, try to start server
try {
// Check if already running
let vaultServerRunning = false;
try {
const lsofOutput = execSync('lsof -i :8888 -t', { encoding: 'utf8', stdio: 'pipe' });
vaultServerRunning = lsofOutput.trim().length > 0;
} catch (err) {
vaultServerRunning = false;
}
if (vaultServerRunning) {
success('Credential vault server already running on port 8888');
log(' URL: http://127.0.0.1:8888', 'cyan');
} else {
log(' Starting credential vault server...', 'cyan');
// Install dependencies if needed
const nodeModulesExists = fs.existsSync(path.join(vaultServerPath, 'node_modules'));
if (!nodeModulesExists) {
log(' Installing vault server dependencies...', 'cyan');
execSync('npm install', { cwd: vaultServerPath, stdio: 'pipe' });
}
// Start server in background
const { spawn } = require('child_process');
const vaultServer = spawn('node', ['server.js'], {
cwd: vaultServerPath,
detached: true,
stdio: 'ignore'
});
vaultServer.unref();
// Wait briefly for server to start
execSync('sleep 2');
// Verify it started
try {
const lsofOutput = execSync('lsof -i :8888 -t', { encoding: 'utf8', stdio: 'pipe' });
if (lsofOutput.trim().length > 0) {
success('Credential vault server started on port 8888');
log(' URL: http://127.0.0.1:8888', 'cyan');
log(' Features: Interactive UI, WebSocket, auto-lock', 'cyan');
} else {
warning('Vault server may not have started successfully');
}
} catch (err) {
warning('Could not verify vault server status');
}
}
} catch (err) {
warning(`Could not start credential vault server: ${err.message}`);
log(' Vault server is optional - manual start: cd .credential-vault && npm start', 'yellow');
}
} else {
log(' Vault not created yet - skipping server startup', 'cyan');
log(' Create vault: ~/Documents/credentials/scripts/create-vault.sh', 'yellow');
}
// Calendar Reminders
section('12. Scheduled Task Reminders');
try {
// Check if MongoDB is running and calendar system is available
let dueSoonTasks = [];
let overdueTasks = [];
// Try to connect to MongoDB and query tasks directly
const mongoose = require('mongoose');
const ScheduledTask = require('../src/models/ScheduledTask.model.js');
// Only attempt if mongoose has an active connection or can connect
if (mongoose.connection.readyState === 1) {
// Already connected
dueSoonTasks = await ScheduledTask.getDueSoon(7);
overdueTasks = await ScheduledTask.getOverdue();
} else {
// Try to connect to MongoDB
try {
await mongoose.connect('mongodb://localhost:27017/tractatus_dev', {
serverSelectionTimeoutMS: 2000
});
dueSoonTasks = await ScheduledTask.getDueSoon(7);
overdueTasks = await ScheduledTask.getOverdue();
} catch (dbErr) {
log(' Calendar database not available - skipping reminders', 'cyan');
log(' Start MongoDB to see scheduled tasks', 'yellow');
throw dbErr; // Re-throw to skip to catch block
}
}
if (overdueTasks.length === 0 && dueSoonTasks.length === 0) {
success('No urgent tasks - all clear');
} else {
if (overdueTasks.length > 0) {
console.log('');
error(`⚠️ ${overdueTasks.length} OVERDUE TASK(S):`);
overdueTasks.slice(0, 5).forEach(task => {
const dueDate = new Date(task.dueDate).toLocaleDateString();
log(` • [${task.priority}] ${task.title}`, 'red');
log(` Due: ${dueDate} | Category: ${task.category}`, 'yellow');
if (task.documentRef) {
log(` Doc: ${task.documentRef}`, 'cyan');
}
});
if (overdueTasks.length > 5) {
log(` ... and ${overdueTasks.length - 5} more`, 'yellow');
}
}
if (dueSoonTasks.length > 0) {
console.log('');
warning(`📅 ${dueSoonTasks.length} task(s) due within 7 days:`);
dueSoonTasks.slice(0, 5).forEach(task => {
const dueDate = new Date(task.dueDate).toLocaleDateString();
const daysUntil = Math.ceil((new Date(task.dueDate) - new Date()) / (1000 * 60 * 60 * 24));
log(` • [${task.priority}] ${task.title}`, 'yellow');
log(` Due in ${daysUntil} day(s) (${dueDate}) | ${task.category}`, 'cyan');
if (task.documentRef) {
log(` Doc: ${task.documentRef}`, 'cyan');
}
});
if (dueSoonTasks.length > 5) {
log(` ... and ${dueSoonTasks.length - 5} more`, 'yellow');
}
}
console.log('');
log(' 📋 View all tasks: http://localhost:9000/admin/calendar.html', 'cyan');
}
} catch (err) {
warning(`Could not load calendar reminders: ${err.message}`);
log(' Calendar system may not be initialized yet', 'yellow');
}
// Summary
header('Framework Initialization Complete');
console.log('');
log(' All 6 components are active and monitoring this session.', 'green');
console.log('');
log(' Next Actions:', 'bright');
log(' • Framework will monitor continuously', 'cyan');
log(' • Token checkpoint at 50,000 tokens (25%)', 'cyan');
log(' • Pressure checks at 25%, 50%, 75% milestones', 'cyan');
log(' • All instructions will be cross-referenced', 'cyan');
console.log('');
log(' ⚠️ MANDATORY REPORTING (Framework Discipline):', 'bright');
log(' • MUST report pressure to user at 50k, 100k, 150k tokens', 'yellow');
log(' • Format: "📊 Context Pressure: [LEVEL] ([SCORE]%) | Tokens: [X]/200000 | Next: [Y]"', 'yellow');
log(' • Failure to report = Framework fade (triggers recovery)', 'yellow');
console.log('');
log(' Claude: You may now proceed with session work.', 'green');
console.log('');
// Exit successfully
process.exit(0);
}
// Run
main().catch(err => {
console.error('');
error(`Initialization failed: ${err.message}`);
console.error('');
process.exit(1);
});