**Cache-Busting Improvements:** - Switched from timestamp-based to semantic versioning (v1.0.2) - Updated all HTML files: index.html, docs.html, leader.html - CSS: tailwind.css?v=1.0.2 - JS: navbar.js, document-cards.js, docs-app.js v1.0.2 - Professional versioning approach for production stability **systemd Service Implementation:** - Created tractatus-dev.service for development environment - Created tractatus-prod.service for production environment - Added install-systemd.sh script for easy deployment - Security hardening: NoNewPrivileges, PrivateTmp, ProtectSystem - Resource limits: 1GB dev, 2GB prod memory limits - Proper logging integration with journalctl - Automatic restart on failure (RestartSec=10) **Why systemd over pm2:** 1. Native Linux integration, no additional dependencies 2. Better OS-level security controls (ProtectSystem, ProtectHome) 3. Superior logging with journalctl integration 4. Standard across Linux distributions 5. More robust process management for production **Usage:** # Development: sudo ./scripts/install-systemd.sh dev # Production: sudo ./scripts/install-systemd.sh prod # View logs: sudo journalctl -u tractatus -f 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
176 lines
6.3 KiB
JavaScript
Executable file
176 lines
6.3 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Framework Watchdog - Continuous Monitoring for Tractatus Governance
|
|
*
|
|
* This script runs in the background and monitors the active use of all five
|
|
* Tractatus framework components throughout a Claude Code session.
|
|
*
|
|
* CRITICAL: This is a Claude Code-specific enforcement mechanism.
|
|
*
|
|
* Monitored Components:
|
|
* 1. ContextPressureMonitor
|
|
* 2. InstructionPersistenceClassifier
|
|
* 3. CrossReferenceValidator
|
|
* 4. BoundaryEnforcer
|
|
* 5. MetacognitiveVerifier
|
|
*
|
|
* Copyright 2025 Tractatus Project
|
|
* Licensed under Apache License 2.0
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const SESSION_STATE_PATH = path.join(__dirname, '../.claude/session-state.json');
|
|
const TOKEN_CHECKPOINTS_PATH = path.join(__dirname, '../.claude/token-checkpoints.json');
|
|
const CHECK_INTERVAL = 30000; // 30 seconds
|
|
|
|
// ANSI color codes for output
|
|
const colors = {
|
|
reset: '\x1b[0m',
|
|
red: '\x1b[31m',
|
|
yellow: '\x1b[33m',
|
|
green: '\x1b[32m',
|
|
cyan: '\x1b[36m',
|
|
bold: '\x1b[1m'
|
|
};
|
|
|
|
function log(level, message) {
|
|
const timestamp = new Date().toISOString();
|
|
const prefix = {
|
|
INFO: `${colors.cyan}[WATCHDOG INFO]${colors.reset}`,
|
|
WARN: `${colors.yellow}${colors.bold}[WATCHDOG WARNING]${colors.reset}`,
|
|
ERROR: `${colors.red}${colors.bold}[WATCHDOG ERROR]${colors.reset}`,
|
|
SUCCESS: `${colors.green}[WATCHDOG OK]${colors.reset}`
|
|
}[level] || '[WATCHDOG]';
|
|
|
|
console.log(`${prefix} ${timestamp} - ${message}`);
|
|
}
|
|
|
|
function checkSessionState() {
|
|
try {
|
|
if (!fs.existsSync(SESSION_STATE_PATH)) {
|
|
log('WARN', 'session-state.json not found. Framework may not be initialized.');
|
|
return;
|
|
}
|
|
|
|
const state = JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8'));
|
|
const alerts = [];
|
|
|
|
// Check each component for staleness
|
|
const components = [
|
|
'ContextPressureMonitor',
|
|
'InstructionPersistenceClassifier',
|
|
'CrossReferenceValidator',
|
|
'BoundaryEnforcer',
|
|
'MetacognitiveVerifier'
|
|
];
|
|
|
|
const currentMessage = state.message_count;
|
|
const currentTokens = state.token_estimate;
|
|
|
|
components.forEach(component => {
|
|
const activity = state.last_framework_activity[component];
|
|
const messagesSince = currentMessage - activity.message;
|
|
const tokensSince = currentTokens - activity.tokens;
|
|
|
|
// Check staleness
|
|
if (messagesSince > state.staleness_thresholds.messages) {
|
|
alerts.push({
|
|
severity: 'HIGH',
|
|
component,
|
|
message: `${component} not used in ${messagesSince} messages (threshold: ${state.staleness_thresholds.messages})`
|
|
});
|
|
}
|
|
|
|
if (tokensSince > state.staleness_thresholds.tokens) {
|
|
alerts.push({
|
|
severity: 'HIGH',
|
|
component,
|
|
message: `${component} not used in ~${tokensSince} tokens (threshold: ${state.staleness_thresholds.tokens})`
|
|
});
|
|
}
|
|
});
|
|
|
|
// Check token checkpoints
|
|
if (fs.existsSync(TOKEN_CHECKPOINTS_PATH)) {
|
|
const checkpoints = JSON.parse(fs.readFileSync(TOKEN_CHECKPOINTS_PATH, 'utf8'));
|
|
|
|
if (checkpoints.overdue) {
|
|
alerts.push({
|
|
severity: 'CRITICAL',
|
|
component: 'ContextPressureMonitor',
|
|
message: `Token checkpoint OVERDUE! Next checkpoint: ${checkpoints.next_checkpoint}, Current: ${currentTokens}`
|
|
});
|
|
} else if (currentTokens >= checkpoints.next_checkpoint) {
|
|
alerts.push({
|
|
severity: 'HIGH',
|
|
component: 'ContextPressureMonitor',
|
|
message: `Token checkpoint reached: ${checkpoints.next_checkpoint}. Pressure check required NOW.`
|
|
});
|
|
|
|
// Mark as overdue
|
|
checkpoints.overdue = true;
|
|
fs.writeFileSync(TOKEN_CHECKPOINTS_PATH, JSON.stringify(checkpoints, null, 2));
|
|
}
|
|
}
|
|
|
|
// Report alerts
|
|
if (alerts.length > 0) {
|
|
log('ERROR', '═══════════════════════════════════════════════════════════');
|
|
log('ERROR', `FRAMEWORK FADE DETECTED - ${alerts.length} issues found`);
|
|
log('ERROR', '═══════════════════════════════════════════════════════════');
|
|
|
|
alerts.forEach(alert => {
|
|
log('ERROR', `[${alert.severity}] ${alert.message}`);
|
|
});
|
|
|
|
log('ERROR', '');
|
|
log('ERROR', 'REQUIRED ACTION: Run recovery protocol immediately');
|
|
log('ERROR', 'Command: node scripts/recover-framework.js');
|
|
log('ERROR', '═══════════════════════════════════════════════════════════');
|
|
|
|
// Update session state with alerts
|
|
state.alerts = alerts;
|
|
state.last_updated = new Date().toISOString();
|
|
fs.writeFileSync(SESSION_STATE_PATH, JSON.stringify(state, null, 2));
|
|
} else {
|
|
// Periodic status report (every 5 minutes)
|
|
const now = Date.now();
|
|
const lastUpdate = new Date(state.last_updated).getTime();
|
|
if (now - lastUpdate > 300000) {
|
|
log('SUCCESS', `All components active. Messages: ${currentMessage}, Tokens: ~${currentTokens}`);
|
|
}
|
|
}
|
|
|
|
} catch (error) {
|
|
log('ERROR', `Watchdog check failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Main watchdog loop
|
|
log('INFO', '═══════════════════════════════════════════════════════════');
|
|
log('INFO', 'Tractatus Framework Watchdog STARTED');
|
|
log('INFO', 'Monitoring session for framework component usage');
|
|
log('INFO', `Check interval: ${CHECK_INTERVAL / 1000}s`);
|
|
log('INFO', '═══════════════════════════════════════════════════════════');
|
|
|
|
// Run immediate check
|
|
checkSessionState();
|
|
|
|
// Set up periodic monitoring
|
|
const intervalId = setInterval(checkSessionState, CHECK_INTERVAL);
|
|
|
|
// Graceful shutdown
|
|
process.on('SIGINT', () => {
|
|
log('INFO', 'Watchdog shutting down gracefully...');
|
|
clearInterval(intervalId);
|
|
process.exit(0);
|
|
});
|
|
|
|
process.on('SIGTERM', () => {
|
|
log('INFO', 'Watchdog shutting down gracefully...');
|
|
clearInterval(intervalId);
|
|
process.exit(0);
|
|
});
|