Session Management: - Changed handoff document selection from alphabetical to modification time sort - Ensures most recent handoff is used regardless of date formatting variations - More reliable for continued sessions Service Initialization: - Explicitly initialize all 6 core governance services in server.js - Added: InstructionPersistenceClassifier, MetacognitiveVerifier, CrossReferenceValidator, ContextPressureMonitor - Ensures all services properly initialized before server starts Auth Improvements: - Added logging for authentication attempts without tokens - Helps detect potential unauthorized access attempts - Includes IP, path, and method for security auditing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1108 lines
40 KiB
JavaScript
Executable file
1108 lines
40 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Tractatus Session Initialization
|
|
* Hook test: 2025-10-24
|
|
*
|
|
* 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');
|
|
}
|
|
|
|
// Check for post-compaction restart marker
|
|
const markerPath = path.join(__dirname, '../.claude/session-complete.marker');
|
|
let explicitRecoveryDoc = null; // Store recovery_doc before deleting marker (inst_083)
|
|
|
|
if (fs.existsSync(markerPath)) {
|
|
try {
|
|
const marker = JSON.parse(fs.readFileSync(markerPath, 'utf8'));
|
|
|
|
// Store recovery_doc BEFORE deleting marker (inst_083: Ensure correct handoff)
|
|
if (marker.recovery_doc) {
|
|
explicitRecoveryDoc = marker.recovery_doc;
|
|
}
|
|
console.log('');
|
|
log('═'.repeat(70), 'yellow');
|
|
warning('⚠️ PREVIOUS SESSION ENDED WITH CLOSEDOWN');
|
|
log('═'.repeat(70), 'yellow');
|
|
console.log('');
|
|
log(`📄 Recovery document: ${marker.recovery_doc}`, 'cyan');
|
|
log(' This appears to be a POST-COMPACTION restart.', 'yellow');
|
|
log(' Read recovery document for full session context.', 'yellow');
|
|
console.log('');
|
|
log(' Closedown timestamp: ' + new Date(marker.closedown_timestamp).toLocaleString(), 'cyan');
|
|
if (marker.framework_health_score !== undefined) {
|
|
log(` Framework health: ${marker.framework_health_score}/100`, 'cyan');
|
|
}
|
|
if (marker.tasks_pending && marker.tasks_pending.length > 0) {
|
|
console.log('');
|
|
warning(` Pending tasks from previous session (${marker.tasks_pending.length}):`);
|
|
marker.tasks_pending.slice(0, 3).forEach(task => {
|
|
log(` • ${task}`, 'yellow');
|
|
});
|
|
if (marker.tasks_pending.length > 3) {
|
|
log(` ... and ${marker.tasks_pending.length - 3} more (see recovery doc)`, 'yellow');
|
|
}
|
|
}
|
|
console.log('');
|
|
log('═'.repeat(70), 'yellow');
|
|
console.log('');
|
|
|
|
// Delete marker (consumed)
|
|
fs.unlinkSync(markerPath);
|
|
success('Compaction marker consumed - proceeding with initialization');
|
|
} catch (markerErr) {
|
|
warning(`Could not parse compaction marker: ${markerErr.message}`);
|
|
// Delete malformed marker
|
|
fs.unlinkSync(markerPath);
|
|
}
|
|
}
|
|
|
|
// Check for handoff documents (inst_083: Auto-inject handoff context)
|
|
section('1a. Previous Session Handoff Detection');
|
|
try {
|
|
// Prefer explicit recovery_doc from marker, fall back to alphabetical sort
|
|
let latestHandoff = null;
|
|
let handoffPath = null;
|
|
|
|
if (explicitRecoveryDoc) {
|
|
// Use explicit recovery doc from compaction marker (inst_083: Reliable handoff)
|
|
const explicitPath = path.join(__dirname, '..', explicitRecoveryDoc);
|
|
if (fs.existsSync(explicitPath)) {
|
|
latestHandoff = explicitRecoveryDoc;
|
|
handoffPath = explicitPath;
|
|
log(` Using explicit recovery doc from marker: ${explicitRecoveryDoc}`, 'cyan');
|
|
} else {
|
|
warning(` Marker specified ${explicitRecoveryDoc} but file not found`);
|
|
warning(` Falling back to alphabetical sort`);
|
|
}
|
|
}
|
|
|
|
// Fall back to modification time sort if no explicit recovery doc
|
|
if (!latestHandoff) {
|
|
const baseDir = path.join(__dirname, '..');
|
|
const handoffFiles = fs.readdirSync(baseDir)
|
|
.filter(f => f.startsWith('SESSION_CLOSEDOWN_') && f.endsWith('.md'))
|
|
.map(f => ({
|
|
name: f,
|
|
path: path.join(baseDir, f),
|
|
mtime: fs.statSync(path.join(baseDir, f)).mtime
|
|
}))
|
|
.sort((a, b) => b.mtime - a.mtime); // Most recent first
|
|
|
|
if (handoffFiles.length > 0) {
|
|
latestHandoff = handoffFiles[0].name;
|
|
handoffPath = handoffFiles[0].path;
|
|
const mtimeStr = handoffFiles[0].mtime.toISOString();
|
|
log(` Using most recent handoff (${mtimeStr}): ${latestHandoff}`, 'cyan');
|
|
}
|
|
}
|
|
|
|
if (latestHandoff && handoffPath) {
|
|
const handoffContent = fs.readFileSync(handoffPath, 'utf8');
|
|
|
|
console.log('');
|
|
log('═'.repeat(70), 'yellow');
|
|
warning(`📄 PREVIOUS SESSION HANDOFF: ${latestHandoff}`);
|
|
log('═'.repeat(70), 'yellow');
|
|
console.log('');
|
|
|
|
// Extract and display Priorities section
|
|
const prioritiesMatch = handoffContent.match(/\*\*Priorities\*\*:([\s\S]*?)(?=\n##|\n---|\n\*\*|$)/);
|
|
if (prioritiesMatch) {
|
|
log(' 📋 PRIORITIES FROM PREVIOUS SESSION:', 'bright');
|
|
const priorities = prioritiesMatch[1].trim().split('\n').filter(line => line.trim());
|
|
priorities.forEach(priority => {
|
|
log(` ${priority.trim()}`, 'yellow');
|
|
});
|
|
console.log('');
|
|
}
|
|
|
|
// Extract and display Recent Commits (recent work)
|
|
const commitsMatch = handoffContent.match(/\*\*Recent Commits\*\*:\s*```([\s\S]*?)```/);
|
|
if (commitsMatch) {
|
|
log(' 📝 RECENT WORK (Last Commits):', 'bright');
|
|
const commits = commitsMatch[1].trim().split('\n').filter(line => line.trim()).slice(0, 3);
|
|
commits.forEach(commit => {
|
|
log(` ${commit.trim()}`, 'cyan');
|
|
});
|
|
console.log('');
|
|
}
|
|
|
|
// Extract and display Known Issues/Blockers
|
|
const issuesMatch = handoffContent.match(/\*\*Known Issues\*\*:([\s\S]*?)(?=\n##|\n---|\n\*\*|$)/);
|
|
if (issuesMatch) {
|
|
log(' ⚠️ KNOWN ISSUES:', 'bright');
|
|
const issues = issuesMatch[1].trim().split('\n').filter(line => line.trim());
|
|
issues.forEach(issue => {
|
|
log(` ${issue.trim()}`, 'red');
|
|
});
|
|
console.log('');
|
|
}
|
|
|
|
// Extract cleanup summary
|
|
const cleanupMatch = handoffContent.match(/Background processes killed:\s+(\d+)/);
|
|
if (cleanupMatch) {
|
|
log(` 🧹 Previous session cleanup: ${cleanupMatch[1]} background processes killed`, 'cyan');
|
|
console.log('');
|
|
}
|
|
|
|
log('═'.repeat(70), 'yellow');
|
|
log(' HANDOFF CONTEXT LOADED - Continue with priorities above', 'green');
|
|
log('═'.repeat(70), 'yellow');
|
|
console.log('');
|
|
|
|
} else {
|
|
log(' No recent handoff documents found - fresh start', 'cyan');
|
|
}
|
|
} catch (handoffErr) {
|
|
warning(`Could not load handoff document: ${handoffErr.message}`);
|
|
}
|
|
|
|
// 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%)`);
|
|
|
|
// Check for orphaned background processes (inst_023)
|
|
section('2a. Checking Background Processes (inst_023)');
|
|
try {
|
|
const bgCheckOutput = execSync('node scripts/track-background-process.js list', {
|
|
encoding: 'utf8',
|
|
stdio: 'pipe'
|
|
});
|
|
|
|
if (bgCheckOutput.includes('No tracked processes')) {
|
|
success('No background processes tracked');
|
|
} else {
|
|
log(bgCheckOutput, 'cyan');
|
|
|
|
// Check for orphaned processes from previous sessions
|
|
const state = JSON.parse(fs.readFileSync('.claude/session-state.json', 'utf8'));
|
|
const currentSession = state.session_id;
|
|
const procs = state.background_processes || [];
|
|
const orphaned = procs.filter(p => {
|
|
try {
|
|
process.kill(p.pid, 0); // Check if running
|
|
return p.session !== currentSession;
|
|
} catch (e) {
|
|
return false; // Not running
|
|
}
|
|
});
|
|
|
|
if (orphaned.length > 0) {
|
|
warning(`Found ${orphaned.length} orphaned process(es) from previous sessions`);
|
|
orphaned.forEach(p => {
|
|
log(` • PID ${p.pid}: ${p.description} (from ${p.session})`, 'yellow');
|
|
});
|
|
console.log('');
|
|
log(' Run "node scripts/track-background-process.js cleanup" to clean up', 'cyan');
|
|
}
|
|
}
|
|
} catch (err) {
|
|
log(` Could not check background processes: ${err.message}`, 'yellow');
|
|
}
|
|
|
|
// Hook approval cache reset (inst_061)
|
|
section('2b. Hook Approval Cache Reset (inst_061)');
|
|
try {
|
|
const approvalCache = '.claude/approval-cache.json';
|
|
if (fs.existsSync(approvalCache)) {
|
|
const cache = JSON.parse(fs.readFileSync(approvalCache, 'utf8'));
|
|
const state = JSON.parse(fs.readFileSync('.claude/session-state.json', 'utf8'));
|
|
|
|
if (cache.sessionId !== state.sessionId) {
|
|
// Session changed - reset cache
|
|
cache.sessionId = state.sessionId;
|
|
cache.approvals = [];
|
|
fs.writeFileSync(approvalCache, JSON.stringify(cache, null, 2));
|
|
success('Hook approval cache reset for new session');
|
|
} else {
|
|
log(` ${cache.approvals.length} approval(s) cached from current session`, 'cyan');
|
|
}
|
|
} else {
|
|
success('No approval cache (will be created on first use)');
|
|
}
|
|
} catch (err) {
|
|
log(` Could not check approval cache: ${err.message}`, 'yellow');
|
|
}
|
|
|
|
// 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');
|
|
|
|
// Framework operational statistics
|
|
section('5a. Framework Operational Statistics');
|
|
try {
|
|
// Import services to get stats
|
|
const BoundaryEnforcer = require('../src/services/BoundaryEnforcer.service');
|
|
const ContextPressureMonitor = require('../src/services/ContextPressureMonitor.service');
|
|
const CrossReferenceValidator = require('../src/services/CrossReferenceValidator.service');
|
|
const InstructionPersistenceClassifier = require('../src/services/InstructionPersistenceClassifier.service');
|
|
const MetacognitiveVerifier = require('../src/services/MetacognitiveVerifier.service');
|
|
const PluralisticDeliberationOrchestrator = require('../src/services/PluralisticDeliberationOrchestrator.service');
|
|
|
|
const stats = {
|
|
boundary: BoundaryEnforcer.getStats(),
|
|
pressure: ContextPressureMonitor.getStats(),
|
|
validator: CrossReferenceValidator.getStats(),
|
|
classifier: InstructionPersistenceClassifier.getStats(),
|
|
verifier: MetacognitiveVerifier.getStats(),
|
|
deliberation: PluralisticDeliberationOrchestrator.getStats()
|
|
};
|
|
|
|
const totalActivity =
|
|
stats.boundary.total_enforcements +
|
|
stats.pressure.total_analyses +
|
|
stats.validator.total_validations +
|
|
stats.classifier.total_classifications +
|
|
stats.verifier.total_verifications +
|
|
stats.deliberation.total_deliberations;
|
|
|
|
if (totalActivity === 0) {
|
|
log(' No framework activity yet (fresh start)', 'cyan');
|
|
log(' Services will begin logging during session operations', 'cyan');
|
|
} else {
|
|
success(`Framework activity detected: ${totalActivity} total operations`);
|
|
if (stats.boundary.total_enforcements > 0) {
|
|
log(` • BoundaryEnforcer: ${stats.boundary.total_enforcements} enforcements`, 'cyan');
|
|
}
|
|
if (stats.pressure.total_analyses > 0) {
|
|
log(` • ContextPressureMonitor: ${stats.pressure.total_analyses} analyses`, 'cyan');
|
|
}
|
|
if (stats.validator.total_validations > 0) {
|
|
log(` • CrossReferenceValidator: ${stats.validator.total_validations} validations`, 'cyan');
|
|
}
|
|
if (stats.classifier.total_classifications > 0) {
|
|
log(` • InstructionPersistenceClassifier: ${stats.classifier.total_classifications} classifications`, 'cyan');
|
|
}
|
|
if (stats.verifier.total_verifications > 0) {
|
|
log(` • MetacognitiveVerifier: ${stats.verifier.total_verifications} verifications`, 'cyan');
|
|
}
|
|
if (stats.deliberation.total_deliberations > 0) {
|
|
log(` • PluralisticDeliberationOrchestrator: ${stats.deliberation.total_deliberations} deliberations`, 'cyan');
|
|
}
|
|
}
|
|
|
|
// Check audit log database
|
|
try {
|
|
const mongoose = require('mongoose');
|
|
if (mongoose.connection.readyState !== 1) {
|
|
await mongoose.connect('mongodb://localhost:27017/tractatus_dev', {
|
|
serverSelectionTimeoutMS: 2000
|
|
});
|
|
}
|
|
|
|
const AuditLog = mongoose.model('AuditLog');
|
|
const auditCount = await AuditLog.countDocuments();
|
|
const serviceBreakdown = await AuditLog.aggregate([
|
|
{ $group: { _id: '$service', count: { $sum: 1 } } },
|
|
{ $sort: { count: -1 } }
|
|
]);
|
|
|
|
if (auditCount > 0) {
|
|
console.log('');
|
|
success(`Audit logs: ${auditCount} decisions recorded`);
|
|
const distinctServices = serviceBreakdown.length;
|
|
log(` Services logging: ${distinctServices}/6`, distinctServices === 6 ? 'green' : 'yellow');
|
|
|
|
serviceBreakdown.slice(0, 6).forEach(s => {
|
|
log(` ${s._id}: ${s.count} log${s.count !== 1 ? 's' : ''}`, 'cyan');
|
|
});
|
|
|
|
console.log('');
|
|
log(' 📊 Dashboard: http://localhost:9000/admin/audit-analytics.html', 'cyan');
|
|
}
|
|
} catch (dbErr) {
|
|
// MongoDB not available - skip audit stats
|
|
log(' Audit database not available - stats unavailable', 'yellow');
|
|
}
|
|
|
|
} catch (statsErr) {
|
|
warning(`Could not load framework statistics: ${statsErr.message}`);
|
|
}
|
|
|
|
// Framework fade detection (inst_064)
|
|
section('5b. Framework Activity Verification (inst_064)');
|
|
try {
|
|
const sessionState = JSON.parse(fs.readFileSync('.claude/session-state.json', 'utf8'));
|
|
const currentMessage = sessionState.message_count || 0;
|
|
const stalenessThreshold = sessionState.staleness_thresholds?.messages || 20;
|
|
|
|
const componentActivity = sessionState.last_framework_activity || {};
|
|
const fadeAlerts = [];
|
|
const activeComponents = [];
|
|
|
|
// Check each component for staleness
|
|
const requiredComponents = [
|
|
'ContextPressureMonitor',
|
|
'InstructionPersistenceClassifier',
|
|
'CrossReferenceValidator',
|
|
'BoundaryEnforcer',
|
|
'MetacognitiveVerifier',
|
|
'PluralisticDeliberationOrchestrator'
|
|
];
|
|
|
|
requiredComponents.forEach(component => {
|
|
const activity = componentActivity[component];
|
|
if (!activity || !activity.timestamp) {
|
|
// Never used
|
|
fadeAlerts.push(`${component}: Never used this session`);
|
|
} else {
|
|
const messagesSinceUse = currentMessage - (activity.message || 0);
|
|
if (messagesSinceUse > stalenessThreshold) {
|
|
fadeAlerts.push(`${component}: ${messagesSinceUse} messages since last use (threshold: ${stalenessThreshold})`);
|
|
} else {
|
|
activeComponents.push(component);
|
|
}
|
|
}
|
|
});
|
|
|
|
if (fadeAlerts.length === 0) {
|
|
success('All framework components active - no fade detected');
|
|
log(` Active components: ${activeComponents.length}/6`, 'cyan');
|
|
} else if (fadeAlerts.length === requiredComponents.length) {
|
|
// Fresh session - all components unused
|
|
log(' Fresh session - framework components not yet triggered', 'cyan');
|
|
log(' Components will activate during session operations', 'cyan');
|
|
} else {
|
|
// Partial fade detected
|
|
warning(`Framework fade detected: ${fadeAlerts.length}/6 components stale`);
|
|
fadeAlerts.forEach(alert => {
|
|
log(` ⚠ ${alert}`, 'yellow');
|
|
});
|
|
console.log('');
|
|
warning('CRITICAL: Framework fade = governance collapse (inst_064)');
|
|
log(' Ensure components are used per their triggers:', 'yellow');
|
|
log(' • ContextPressureMonitor: Session start, checkpoints, complex ops', 'cyan');
|
|
log(' • CrossReferenceValidator: Schema changes, config mods, architecture', 'cyan');
|
|
log(' • BoundaryEnforcer: Privacy, ethics, values-sensitive decisions', 'cyan');
|
|
log(' • MetacognitiveVerifier: 3+ file mods or 5+ sequential steps', 'cyan');
|
|
log(' • InstructionPersistenceClassifier: Explicit instructions given', 'cyan');
|
|
log(' • PluralisticDeliberationOrchestrator: Values conflicts flagged', 'cyan');
|
|
}
|
|
} catch (err) {
|
|
error(`Framework fade check failed: ${err.message}`);
|
|
}
|
|
|
|
// 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)`);
|
|
// Tests passed, continue with remaining initialization steps
|
|
} else {
|
|
|
|
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}`);
|
|
}
|
|
|
|
// Defense-in-Depth Health Check (inst_072)
|
|
section('8a. Defense-in-Depth Health Check (inst_072)');
|
|
try {
|
|
const auditResult = execSync('node scripts/audit-defense-in-depth.js 2>&1', {
|
|
encoding: 'utf8',
|
|
stdio: 'pipe'
|
|
});
|
|
|
|
if (auditResult.includes('All 5 layers')) {
|
|
success('All 5 credential protection layers verified');
|
|
} else {
|
|
const incompleteCount = (auditResult.match(/❌/g) || []).length;
|
|
warning(`${incompleteCount} defense layer(s) incomplete`);
|
|
log(' Run: node scripts/audit-defense-in-depth.js for details', 'cyan');
|
|
}
|
|
} catch (err) {
|
|
// Non-blocking - just warn
|
|
warning('Defense-in-depth audit incomplete (see details above)');
|
|
}
|
|
|
|
// Dependency License Check (inst_080)
|
|
section('8b. Dependency License Check (inst_080)');
|
|
try {
|
|
execSync('node scripts/check-dependency-licenses.js', {
|
|
encoding: 'utf8',
|
|
stdio: 'pipe'
|
|
});
|
|
success('All dependencies are Apache 2.0 compatible');
|
|
} catch (err) {
|
|
// Check if it's a critical failure or just warnings
|
|
const output = err.stdout || '';
|
|
if (output.includes('CRITICAL')) {
|
|
error('Prohibited dependency licenses detected');
|
|
log(' Run: node scripts/check-dependency-licenses.js for details', 'red');
|
|
} else if (output.includes('HIGH')) {
|
|
warning('Restrictive licenses detected (may require review)');
|
|
log(' Run: node scripts/check-dependency-licenses.js for details', 'yellow');
|
|
} else {
|
|
success('Dependency licenses checked (some flagged for review)');
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
});
|