Implements architectural enforcement of governance rules (inst_016/017/018/079) for all external communications. Publication blocked at API level if violations detected. New Features: - Framework content checker script with pattern matching for prohibited terms - Admin UI displays framework violations with severity indicators - Manual "Check Framework" button for pre-publication validation - API endpoint /api/blog/check-framework for real-time content analysis Governance Rules Added: - inst_078: "ff" trigger for manual framework invocation in conversations - inst_079: Dark patterns prohibition (sovereignty principle) - inst_080: Open source commitment enforcement (community principle) - inst_081: Pluralism principle with indigenous framework recognition Session Management: - Fix session-init.js infinite loop (removed early return after tests) - Add session-closedown.js for comprehensive session handoff - Refactor check-csp-violations.js to prevent parent process exit Framework Services: - Enhanced PluralisticDeliberationOrchestrator with audit logging - Updated all 6 services with consistent initialization patterns - Added framework invocation scripts for blog content validation Files: blog.controller.js:1211-1305, blog.routes.js:77-82, blog-curation.html:61-72, blog-curation.js:320-446 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
914 lines
29 KiB
JavaScript
Executable file
914 lines
29 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Tractatus Session Closedown (v2.0)
|
|
*
|
|
* Comprehensive session shutdown procedure:
|
|
* - Cleanup (ALL processes, files, sync)
|
|
* - Framework performance analysis (TERMINAL + HANDOFF)
|
|
* - Git change categorization (deployment readiness)
|
|
* - Interactive deployment prompt
|
|
* - Handoff document creation (AT END with deployment results)
|
|
* - Compaction marker placement
|
|
*
|
|
* Copyright 2025 Tractatus Project
|
|
* Licensed under Apache License 2.0
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { execSync } = require('child_process');
|
|
const readline = require('readline');
|
|
|
|
const SESSION_STATE_PATH = path.join(__dirname, '../.claude/session-state.json');
|
|
const SESSION_MARKER_PATH = path.join(__dirname, '../.claude/session-complete.marker');
|
|
const INSTRUCTION_HISTORY_PATH = path.join(__dirname, '../.claude/instruction-history.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',
|
|
magenta: '\x1b[35m'
|
|
};
|
|
|
|
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');
|
|
}
|
|
|
|
function info(message) {
|
|
log(` ${message}`, 'cyan');
|
|
}
|
|
|
|
/**
|
|
* Prompt user for input
|
|
*/
|
|
function prompt(question) {
|
|
const rl = readline.createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout
|
|
});
|
|
|
|
return new Promise(resolve => {
|
|
rl.question(colors.yellow + question + colors.reset + ' ', answer => {
|
|
rl.close();
|
|
resolve(answer.trim().toLowerCase());
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create visual pressure gauge
|
|
*/
|
|
function createPressureGauge(pressure, level) {
|
|
// Normalize pressure to 0-1 range
|
|
const normalizedPressure = Math.max(0, Math.min(1, pressure));
|
|
|
|
// Gauge parameters
|
|
const gaugeWidth = 50;
|
|
const filledLength = Math.round(normalizedPressure * gaugeWidth);
|
|
const emptyLength = gaugeWidth - filledLength;
|
|
|
|
// Choose color based on pressure level
|
|
let gaugeColor = 'green';
|
|
let levelIndicator = '✓';
|
|
|
|
if (level === 'ELEVATED' || level === 'HIGH') {
|
|
gaugeColor = 'yellow';
|
|
levelIndicator = '⚠';
|
|
} else if (level === 'CRITICAL' || level === 'SEVERE') {
|
|
gaugeColor = 'red';
|
|
levelIndicator = '✗';
|
|
}
|
|
|
|
// Build gauge
|
|
const filled = '█'.repeat(filledLength);
|
|
const empty = '░'.repeat(emptyLength);
|
|
const percentage = (normalizedPressure * 100).toFixed(1);
|
|
|
|
const gaugeLine = ` [${colors[gaugeColor]}${filled}${colors.reset}${empty}] ${percentage}%`;
|
|
const statusLine = ` ${colors[gaugeColor]}${levelIndicator} Status: ${level}${colors.reset}`;
|
|
|
|
return gaugeLine + '\n' + statusLine;
|
|
}
|
|
|
|
/**
|
|
* Phase 1: Comprehensive Cleanup
|
|
*/
|
|
async function cleanup() {
|
|
section('Phase 1: Comprehensive Cleanup');
|
|
|
|
const cleanupResults = {
|
|
processes_killed: 0,
|
|
files_cleaned: 0,
|
|
instructions_synced: false,
|
|
sync_verified: false
|
|
};
|
|
|
|
// 1. Kill ALL background processes (including Claude Code shells)
|
|
try {
|
|
info('Checking for ALL background processes...');
|
|
|
|
// Get all node/npm processes
|
|
let processes = [];
|
|
try {
|
|
const psOutput = execSync('ps aux | grep -E "npm|jest|node" | grep -v grep', { encoding: 'utf8' });
|
|
processes = psOutput.trim().split('\n').filter(line => line.length > 0);
|
|
} catch (err) {
|
|
// No processes found (grep returns non-zero)
|
|
processes = [];
|
|
}
|
|
|
|
if (processes.length === 0) {
|
|
success('No background processes to clean up');
|
|
} else {
|
|
info(`Found ${processes.length} background process(es)`);
|
|
|
|
processes.forEach((proc, index) => {
|
|
const parts = proc.trim().split(/\s+/);
|
|
const pid = parts[1];
|
|
const command = parts.slice(10).join(' ');
|
|
|
|
// Don't kill system processes, systemd, or this script
|
|
if (command.includes('session-closedown') ||
|
|
command.includes('systemd') ||
|
|
command.includes('/usr/lib/') ||
|
|
pid === process.pid.toString()) {
|
|
info(` Skipping: ${command.substring(0, 60)}...`);
|
|
return;
|
|
}
|
|
|
|
info(` Killing PID ${pid}: ${command.substring(0, 60)}...`);
|
|
try {
|
|
execSync(`kill ${pid}`, { stdio: 'pipe' });
|
|
cleanupResults.processes_killed++;
|
|
} catch (killErr) {
|
|
// Process may already be dead, that's ok
|
|
if (!killErr.message.includes('No such process')) {
|
|
warning(` Could not kill PID ${pid}: ${killErr.message}`);
|
|
}
|
|
}
|
|
});
|
|
|
|
success(`Killed ${cleanupResults.processes_killed} background process(es)`);
|
|
|
|
// Verify cleanup
|
|
try {
|
|
const remainingOutput = execSync('ps aux | grep -E "npm start|node src/server" | grep -v grep', { encoding: 'utf8' });
|
|
if (remainingOutput.trim().length > 0) {
|
|
warning('Some processes may still be running. Run `ps aux | grep node` to check.');
|
|
} else {
|
|
success('Process cleanup verified - all target processes terminated');
|
|
}
|
|
} catch (err) {
|
|
// No processes found - cleanup successful
|
|
success('Process cleanup verified - all target processes terminated');
|
|
}
|
|
}
|
|
} catch (err) {
|
|
warning(`Process cleanup error: ${err.message}`);
|
|
}
|
|
|
|
// 2. Clean temporary artifacts
|
|
try {
|
|
info('Cleaning temporary artifacts...');
|
|
|
|
const artifactsToCheck = [
|
|
{ path: '.memory-test', type: 'directory' },
|
|
{ path: 'test-audit-logging.js', type: 'file' },
|
|
{ path: 'test-audit-error.js', type: 'file' },
|
|
{ path: 'trigger-all-services.js', type: 'file' },
|
|
{ path: 'trigger-remaining-services.js', type: 'file' },
|
|
{ path: 'test-audit-direct.js', type: 'file' },
|
|
{ path: 'trigger-audit-test.js', type: 'file' }
|
|
];
|
|
|
|
artifactsToCheck.forEach(artifact => {
|
|
const fullPath = path.join(__dirname, '..', artifact.path);
|
|
if (fs.existsSync(fullPath)) {
|
|
if (artifact.type === 'directory') {
|
|
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
} else {
|
|
fs.unlinkSync(fullPath);
|
|
}
|
|
cleanupResults.files_cleaned++;
|
|
info(` Removed: ${artifact.path}`);
|
|
}
|
|
});
|
|
|
|
if (cleanupResults.files_cleaned === 0) {
|
|
success('No temporary artifacts to clean');
|
|
} else {
|
|
success(`Cleaned ${cleanupResults.files_cleaned} temporary artifact(s)`);
|
|
}
|
|
} catch (err) {
|
|
warning(`Artifact cleanup error: ${err.message}`);
|
|
}
|
|
|
|
// 3. Sync instruction history to database
|
|
try {
|
|
info('Syncing instruction history to database...');
|
|
|
|
const syncScript = path.join(__dirname, 'sync-instructions-to-db.js');
|
|
const output = execSync(`node ${syncScript} --force`, { encoding: 'utf8' });
|
|
|
|
// Parse output for counts
|
|
const addedMatch = output.match(/Added: (\d+)/);
|
|
const updatedMatch = output.match(/Updated: (\d+)/);
|
|
|
|
cleanupResults.instructions_synced = true;
|
|
cleanupResults.sync_verified = true;
|
|
|
|
success('Instructions synced to database');
|
|
if (addedMatch) info(` Added: ${addedMatch[1]}`);
|
|
if (updatedMatch) info(` Updated: ${updatedMatch[1]}`);
|
|
} catch (err) {
|
|
warning(`Instruction sync failed: ${err.message}`);
|
|
}
|
|
|
|
return cleanupResults;
|
|
}
|
|
|
|
/**
|
|
* Phase 2: Framework Performance Analysis (WITH TERMINAL OUTPUT)
|
|
*/
|
|
async function analyzeFrameworkPerformance() {
|
|
section('Phase 2: Framework Performance Analysis');
|
|
|
|
try {
|
|
// Import services
|
|
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;
|
|
|
|
// *** OUTPUT TO TERMINAL ***
|
|
|
|
// Display pressure gauge first
|
|
const pressureValue = stats.pressure.current_pressure || 0;
|
|
const pressureLevel = stats.pressure.pressure_level || 'NORMAL';
|
|
const pressureGauge = createPressureGauge(pressureValue, pressureLevel);
|
|
|
|
console.log('');
|
|
log(' Context Pressure Gauge:', 'bright');
|
|
console.log(pressureGauge);
|
|
console.log('');
|
|
|
|
if (totalActivity === 0) {
|
|
warning('No framework activity recorded this session');
|
|
info('Framework services may not have been triggered');
|
|
info('This is expected if PreToolUse hook is not yet active');
|
|
} else {
|
|
success(`Framework activity: ${totalActivity} total operations`);
|
|
console.log('');
|
|
|
|
// Display per-service stats
|
|
if (stats.boundary.total_enforcements > 0) {
|
|
info(`BoundaryEnforcer: ${stats.boundary.total_enforcements} enforcements`);
|
|
info(` Violations: ${stats.boundary.boundaries_violated}`);
|
|
info(` Allowed: ${stats.boundary.allowed_count}`);
|
|
}
|
|
|
|
if (stats.pressure.total_analyses > 0) {
|
|
info(`ContextPressureMonitor: ${stats.pressure.total_analyses} analyses`);
|
|
info(` Current pressure: ${pressureValue.toFixed(2)} (${pressureLevel})`);
|
|
}
|
|
|
|
if (stats.validator.total_validations > 0) {
|
|
info(`CrossReferenceValidator: ${stats.validator.total_validations} validations`);
|
|
info(` Conflicts: ${stats.validator.conflicts_detected}`);
|
|
}
|
|
|
|
if (stats.classifier.total_classifications > 0) {
|
|
info(`InstructionPersistenceClassifier: ${stats.classifier.total_classifications} classifications`);
|
|
}
|
|
|
|
if (stats.verifier.total_verifications > 0) {
|
|
info(`MetacognitiveVerifier: ${stats.verifier.total_verifications} verifications`);
|
|
}
|
|
|
|
if (stats.deliberation.total_deliberations > 0) {
|
|
info(`PluralisticDeliberationOrchestrator: ${stats.deliberation.total_deliberations} deliberations`);
|
|
}
|
|
|
|
// Calculate health score (0-100)
|
|
const violations = stats.boundary.boundaries_violated || 0;
|
|
const conflicts = stats.validator.conflicts_detected || 0;
|
|
const totalChecks = totalActivity || 1;
|
|
const healthScore = Math.max(0, 100 - ((violations + conflicts) / totalChecks * 100));
|
|
|
|
console.log('');
|
|
success(`Health Score: ${healthScore.toFixed(0)}/100`);
|
|
}
|
|
|
|
// Connect to MongoDB for audit log analysis
|
|
const mongoose = require('mongoose');
|
|
await mongoose.connect('mongodb://localhost:27017/tractatus_dev', {
|
|
serverSelectionTimeoutMS: 2000
|
|
});
|
|
|
|
const AuditLog = mongoose.model('AuditLog');
|
|
const auditLogs = await AuditLog.find({}).sort({ timestamp: -1 }).lean();
|
|
|
|
// Count by service
|
|
const byService = {};
|
|
auditLogs.forEach(log => {
|
|
byService[log.service] = (byService[log.service] || 0) + 1;
|
|
});
|
|
|
|
const servicesActive = Object.keys(byService).length;
|
|
|
|
console.log('');
|
|
info(`Audit Logs: ${auditLogs.length} total`);
|
|
info(`Services Logging: ${servicesActive}/6`);
|
|
|
|
if (servicesActive < 6) {
|
|
warning('Not all framework services are logging to database');
|
|
const missing = ['BoundaryEnforcer', 'ContextPressureMonitor', 'CrossReferenceValidator',
|
|
'InstructionPersistenceClassifier', 'MetacognitiveVerifier', 'PluralisticDeliberationOrchestrator']
|
|
.filter(s => !byService[s]);
|
|
info(` Missing: ${missing.join(', ')}`);
|
|
} else {
|
|
success('All 6 framework services are logging ✓');
|
|
}
|
|
|
|
// Calculate health score
|
|
const violations = stats.boundary?.boundaries_violated || 0;
|
|
const conflicts = stats.validator?.conflicts_detected || 0;
|
|
const totalChecks = totalActivity || 1;
|
|
const healthScore = Math.max(0, 100 - ((violations + conflicts) / totalChecks * 100));
|
|
|
|
return {
|
|
stats,
|
|
totalActivity,
|
|
healthScore,
|
|
auditLogsCount: auditLogs.length,
|
|
servicesActive,
|
|
pressure: {
|
|
value: pressureValue,
|
|
level: pressureLevel,
|
|
gauge: pressureGauge
|
|
}
|
|
};
|
|
|
|
} catch (err) {
|
|
error(`Framework analysis failed: ${err.message}`);
|
|
return {
|
|
stats: null,
|
|
totalActivity: 0,
|
|
healthScore: 0,
|
|
auditLogsCount: 0,
|
|
servicesActive: 0,
|
|
pressure: { value: 0, level: 'UNKNOWN', gauge: '' }
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Phase 3: Git Change Categorization & Deployment Readiness
|
|
*/
|
|
function analyzeGitChanges() {
|
|
section('Phase 3: Git Change Categorization');
|
|
|
|
const gitAnalysis = {
|
|
branch: 'unknown',
|
|
working_tree: 'unknown',
|
|
modified_files: [],
|
|
recent_commits: [],
|
|
categories: {
|
|
framework_services: [],
|
|
documentation: [],
|
|
configuration: [],
|
|
features: [],
|
|
temp_files: [],
|
|
scripts: [],
|
|
hooks: []
|
|
},
|
|
deployment_ready: [],
|
|
excluded: []
|
|
};
|
|
|
|
try {
|
|
// Get current branch
|
|
gitAnalysis.branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();
|
|
info(`Branch: ${gitAnalysis.branch}`);
|
|
|
|
// Get status
|
|
const status = execSync('git status --porcelain', { encoding: 'utf8' });
|
|
if (status.trim().length === 0) {
|
|
gitAnalysis.working_tree = 'clean';
|
|
success('Working tree: clean - no changes to deploy');
|
|
return gitAnalysis;
|
|
}
|
|
|
|
gitAnalysis.working_tree = 'modified';
|
|
gitAnalysis.modified_files = status.trim().split('\n').map(line => line.substring(3));
|
|
info(`Working tree: ${gitAnalysis.modified_files.length} file(s) modified`);
|
|
|
|
// Categorize changes
|
|
gitAnalysis.modified_files.forEach(file => {
|
|
// Framework services
|
|
if (file.includes('src/services/') && file.endsWith('.service.js')) {
|
|
gitAnalysis.categories.framework_services.push(file);
|
|
}
|
|
// Documentation
|
|
else if (file.match(/\.(md|txt)$/i) && !file.includes('SESSION_')) {
|
|
gitAnalysis.categories.documentation.push(file);
|
|
}
|
|
// Configuration
|
|
else if (file.match(/\.(json|yaml|yml|env)$/) && !file.includes('session-state') && !file.includes('token-checkpoints')) {
|
|
gitAnalysis.categories.configuration.push(file);
|
|
}
|
|
// Hooks
|
|
else if (file.includes('.claude/hooks/')) {
|
|
gitAnalysis.categories.hooks.push(file);
|
|
}
|
|
// Scripts
|
|
else if (file.includes('scripts/')) {
|
|
gitAnalysis.categories.scripts.push(file);
|
|
}
|
|
// Temp/test files
|
|
else if (file.match(/test-|SESSION_|session-state|token-checkpoints/)) {
|
|
gitAnalysis.categories.temp_files.push(file);
|
|
}
|
|
// Features (controllers, middleware, public files)
|
|
else if (file.match(/src\/(controllers|middleware|models)|public\//)) {
|
|
gitAnalysis.categories.features.push(file);
|
|
}
|
|
});
|
|
|
|
// Determine deployment-ready vs excluded
|
|
gitAnalysis.deployment_ready = [
|
|
...gitAnalysis.categories.framework_services,
|
|
...gitAnalysis.categories.features,
|
|
...gitAnalysis.categories.configuration,
|
|
...gitAnalysis.categories.hooks,
|
|
...gitAnalysis.categories.scripts
|
|
].filter(f =>
|
|
!f.includes('test-') &&
|
|
!f.includes('.test.') &&
|
|
!f.includes('session-state') &&
|
|
!f.includes('token-checkpoints')
|
|
);
|
|
|
|
gitAnalysis.excluded = [
|
|
...gitAnalysis.categories.temp_files,
|
|
...gitAnalysis.categories.documentation
|
|
];
|
|
|
|
// *** OUTPUT TO TERMINAL ***
|
|
console.log('');
|
|
success('Git Change Analysis:');
|
|
console.log('');
|
|
|
|
if (gitAnalysis.categories.framework_services.length > 0) {
|
|
log(' Framework Services:', 'bright');
|
|
gitAnalysis.categories.framework_services.forEach(f => info(` • ${f}`));
|
|
}
|
|
|
|
if (gitAnalysis.categories.features.length > 0) {
|
|
log(' Features:', 'bright');
|
|
gitAnalysis.categories.features.forEach(f => info(` • ${f}`));
|
|
}
|
|
|
|
if (gitAnalysis.categories.configuration.length > 0) {
|
|
log(' Configuration:', 'bright');
|
|
gitAnalysis.categories.configuration.forEach(f => info(` • ${f}`));
|
|
}
|
|
|
|
if (gitAnalysis.categories.hooks.length > 0) {
|
|
log(' Hooks:', 'bright');
|
|
gitAnalysis.categories.hooks.forEach(f => info(` • ${f}`));
|
|
}
|
|
|
|
if (gitAnalysis.categories.scripts.length > 0) {
|
|
log(' Scripts:', 'bright');
|
|
gitAnalysis.categories.scripts.forEach(f => info(` • ${f}`));
|
|
}
|
|
|
|
if (gitAnalysis.categories.documentation.length > 0) {
|
|
log(' Documentation (not deployed):', 'bright');
|
|
gitAnalysis.categories.documentation.forEach(f => info(` • ${f}`));
|
|
}
|
|
|
|
if (gitAnalysis.categories.temp_files.length > 0) {
|
|
log(' Temporary Files (excluded from deployment):', 'bright');
|
|
gitAnalysis.categories.temp_files.forEach(f => warning(` • ${f}`));
|
|
}
|
|
|
|
console.log('');
|
|
success(`Deployment-ready changes: ${gitAnalysis.deployment_ready.length}`);
|
|
warning(`Excluded from deployment: ${gitAnalysis.excluded.length}`);
|
|
|
|
// Get recent commits
|
|
const commits = execSync('git log --oneline -5', { encoding: 'utf8' }).trim();
|
|
gitAnalysis.recent_commits = commits.split('\n');
|
|
|
|
} catch (err) {
|
|
error(`Git analysis error: ${err.message}`);
|
|
}
|
|
|
|
return gitAnalysis;
|
|
}
|
|
|
|
/**
|
|
* Phase 4: Interactive Deployment
|
|
*/
|
|
async function interactiveDeployment(gitAnalysis, autoAnswer = null) {
|
|
section('Phase 4: Deployment Decision');
|
|
|
|
const deploymentResult = {
|
|
deployed: false,
|
|
skipped: false,
|
|
failed: false,
|
|
output: '',
|
|
error: null
|
|
};
|
|
|
|
if (gitAnalysis.working_tree === 'clean') {
|
|
info('No changes to deploy - working tree is clean');
|
|
deploymentResult.skipped = true;
|
|
return deploymentResult;
|
|
}
|
|
|
|
if (gitAnalysis.deployment_ready.length === 0) {
|
|
warning('No deployment-ready changes found');
|
|
info('All changes are documentation or temp files');
|
|
deploymentResult.skipped = true;
|
|
return deploymentResult;
|
|
}
|
|
|
|
// Show summary
|
|
console.log('');
|
|
log('═══════════════════════════════════════════════════════════', 'cyan');
|
|
log(' DEPLOYMENT SUMMARY', 'bright');
|
|
log('═══════════════════════════════════════════════════════════', 'cyan');
|
|
console.log('');
|
|
info(`Files ready for deployment: ${gitAnalysis.deployment_ready.length}`);
|
|
console.log('');
|
|
|
|
gitAnalysis.deployment_ready.forEach(f => {
|
|
info(` • ${f}`);
|
|
});
|
|
|
|
console.log('');
|
|
log('═══════════════════════════════════════════════════════════', 'cyan');
|
|
console.log('');
|
|
|
|
// Use auto-answer if provided (non-interactive mode), otherwise prompt
|
|
let answer;
|
|
if (autoAnswer !== null) {
|
|
answer = autoAnswer;
|
|
info(`Auto-deploying: ${answer ? 'yes' : 'no'} (non-interactive mode)`);
|
|
} else {
|
|
answer = await prompt('Deploy these changes to production? (yes/no):');
|
|
}
|
|
|
|
if (answer === 'yes' || answer === 'y' || answer === true) {
|
|
info('Starting deployment...');
|
|
|
|
try {
|
|
const deployScript = path.join(__dirname, 'deploy-full-project-SAFE.sh');
|
|
|
|
if (!fs.existsSync(deployScript)) {
|
|
error('Deployment script not found: deploy-full-project-SAFE.sh');
|
|
deploymentResult.failed = true;
|
|
deploymentResult.error = 'Deployment script not found';
|
|
return deploymentResult;
|
|
}
|
|
|
|
const output = execSync(`bash ${deployScript}`, { encoding: 'utf8', cwd: __dirname });
|
|
|
|
deploymentResult.deployed = true;
|
|
deploymentResult.output = output;
|
|
|
|
success('Deployment completed successfully!');
|
|
console.log('');
|
|
info('Deployment output:');
|
|
console.log(output);
|
|
|
|
} catch (err) {
|
|
error('Deployment failed!');
|
|
error(err.message);
|
|
deploymentResult.failed = true;
|
|
deploymentResult.error = err.message;
|
|
deploymentResult.output = err.stdout || err.stderr || '';
|
|
}
|
|
} else {
|
|
info('Deployment skipped by user');
|
|
deploymentResult.skipped = true;
|
|
}
|
|
|
|
return deploymentResult;
|
|
}
|
|
|
|
/**
|
|
* Phase 5: Create Handoff Document (AT END with all results)
|
|
*/
|
|
function createHandoffDocument(cleanupResults, frameworkAnalysis, gitAnalysis, deploymentResult) {
|
|
section('Phase 5: Creating Session Handoff Document');
|
|
|
|
const today = new Date().toISOString().split('T')[0];
|
|
const handoffPath = path.join(__dirname, `../SESSION_CLOSEDOWN_${today}.md`);
|
|
|
|
const content = `# Session Closedown - ${today}
|
|
|
|
## ⚠️ MANDATORY STARTUP PROCEDURE
|
|
|
|
**FIRST ACTION - NO EXCEPTIONS**: Run the session initialization script:
|
|
|
|
\`\`\`bash
|
|
node scripts/session-init.js
|
|
\`\`\`
|
|
|
|
This will:
|
|
- ✅ Verify local server running on port 9000
|
|
- ✅ Initialize all 6 framework components
|
|
- ✅ Reset token checkpoints
|
|
- ✅ Load instruction history
|
|
- ✅ Display framework statistics
|
|
- ✅ Run framework tests
|
|
|
|
**Per CLAUDE.md**: This is MANDATORY at start of every session AND after context compaction.
|
|
|
|
---
|
|
|
|
## Session Summary
|
|
|
|
**Date**: ${today}
|
|
**Session ID**: ${gitAnalysis.branch}
|
|
|
|
---
|
|
|
|
## Framework Performance
|
|
|
|
### Context Pressure Gauge
|
|
|
|
\`\`\`
|
|
Pressure: ${(frameworkAnalysis.pressure.value * 100).toFixed(1)}%
|
|
Status: ${frameworkAnalysis.pressure.level}
|
|
${'█'.repeat(Math.round(frameworkAnalysis.pressure.value * 50))}${'░'.repeat(50 - Math.round(frameworkAnalysis.pressure.value * 50))}
|
|
\`\`\`
|
|
|
|
${frameworkAnalysis.pressure.level === 'CRITICAL' || frameworkAnalysis.pressure.level === 'SEVERE'
|
|
? '⚠️ **WARNING**: Context pressure is high. Consider session closedown.\n'
|
|
: frameworkAnalysis.pressure.level === 'ELEVATED' || frameworkAnalysis.pressure.level === 'HIGH'
|
|
? '⚠️ **CAUTION**: Context pressure is elevated. Monitor closely.\n'
|
|
: '✅ Context pressure is normal.\n'}
|
|
|
|
### Statistics
|
|
|
|
${frameworkAnalysis.totalActivity === 0
|
|
? '⚠️ **No framework activity recorded**\n\nFramework services were not triggered during this session. This is expected if the PreToolUse hook is not yet active (requires session restart).'
|
|
: `✅ **Framework Active**
|
|
|
|
**Total Operations**: ${frameworkAnalysis.totalActivity}
|
|
**Health Score**: ${frameworkAnalysis.healthScore.toFixed(0)}/100
|
|
|
|
**By Service**:
|
|
${frameworkAnalysis.stats.boundary.total_enforcements > 0 ? `- BoundaryEnforcer: ${frameworkAnalysis.stats.boundary.total_enforcements} enforcements\n` : ''}${frameworkAnalysis.stats.pressure.total_analyses > 0 ? `- ContextPressureMonitor: ${frameworkAnalysis.stats.pressure.total_analyses} analyses\n` : ''}${frameworkAnalysis.stats.validator.total_validations > 0 ? `- CrossReferenceValidator: ${frameworkAnalysis.stats.validator.total_validations} validations\n` : ''}${frameworkAnalysis.stats.classifier.total_classifications > 0 ? `- InstructionPersistenceClassifier: ${frameworkAnalysis.stats.classifier.total_classifications} classifications\n` : ''}${frameworkAnalysis.stats.verifier.total_verifications > 0 ? `- MetacognitiveVerifier: ${frameworkAnalysis.stats.verifier.total_verifications} verifications\n` : ''}${frameworkAnalysis.stats.deliberation.total_deliberations > 0 ? `- PluralisticDeliberationOrchestrator: ${frameworkAnalysis.stats.deliberation.total_deliberations} deliberations\n` : ''}`}
|
|
|
|
### Audit Logs
|
|
|
|
**Total Logs**: ${frameworkAnalysis.auditLogsCount}
|
|
**Services Logging**: ${frameworkAnalysis.servicesActive}/6
|
|
|
|
${frameworkAnalysis.servicesActive < 6
|
|
? `⚠️ **Warning**: Not all framework services are logging audit data.`
|
|
: `✅ All framework services are operational.`}
|
|
|
|
---
|
|
|
|
## Git Changes & Deployment
|
|
|
|
**Branch**: \`${gitAnalysis.branch}\`
|
|
**Working Tree**: ${gitAnalysis.working_tree}
|
|
|
|
${gitAnalysis.deployment_ready.length > 0
|
|
? `### Deployment-Ready Changes (${gitAnalysis.deployment_ready.length})
|
|
|
|
${gitAnalysis.deployment_ready.map(f => `- ${f}`).join('\n')}
|
|
|
|
### Deployment Status
|
|
|
|
${deploymentResult.deployed
|
|
? `✅ **DEPLOYED TO PRODUCTION**
|
|
|
|
Deployment completed successfully.
|
|
|
|
\`\`\`
|
|
${deploymentResult.output}
|
|
\`\`\``
|
|
: deploymentResult.skipped
|
|
? '⏭️ **SKIPPED** - Deployment was not performed'
|
|
: deploymentResult.failed
|
|
? `❌ **FAILED**
|
|
|
|
Error: ${deploymentResult.error}
|
|
|
|
\`\`\`
|
|
${deploymentResult.output}
|
|
\`\`\``
|
|
: '❓ **UNKNOWN** - Deployment status unclear'}
|
|
`
|
|
: 'No deployment-ready changes.'}
|
|
|
|
${gitAnalysis.excluded.length > 0
|
|
? `### Excluded from Deployment (${gitAnalysis.excluded.length})
|
|
|
|
${gitAnalysis.excluded.map(f => `- ${f}`).join('\n')}`
|
|
: ''}
|
|
|
|
**Recent Commits**:
|
|
\`\`\`
|
|
${gitAnalysis.recent_commits.join('\n')}
|
|
\`\`\`
|
|
|
|
---
|
|
|
|
## Cleanup Summary
|
|
|
|
- ✅ Background processes killed: ${cleanupResults.processes_killed}
|
|
- ✅ Temporary files cleaned: ${cleanupResults.files_cleaned}
|
|
- ${cleanupResults.instructions_synced ? '✅' : '❌'} Instructions synced to database
|
|
- ${cleanupResults.sync_verified ? '✅' : '❌'} Sync verification complete
|
|
|
|
---
|
|
|
|
## Next Session
|
|
|
|
**Startup Sequence**:
|
|
1. Run \`node scripts/session-init.js\` (MANDATORY)
|
|
2. Review this closedown document
|
|
3. ${deploymentResult.deployed ? 'Verify production deployment' : 'Consider deploying changes if ready'}
|
|
|
|
**Priorities**:
|
|
- Review framework performance
|
|
${frameworkAnalysis.servicesActive < 6 ? '- Fix missing framework service logging\n' : ''}- Continue development work
|
|
|
|
---
|
|
|
|
## 📊 Dashboard
|
|
|
|
View framework analytics:
|
|
- **Audit Dashboard**: http://localhost:9000/admin/audit-analytics.html
|
|
- **Calendar**: http://localhost:9000/admin/calendar.html
|
|
|
|
---
|
|
|
|
**Session closed**: ${new Date().toISOString()}
|
|
**Next action**: Run session-init.js at start of new session
|
|
`;
|
|
|
|
fs.writeFileSync(handoffPath, content, 'utf8');
|
|
success(`Handoff document created: SESSION_CLOSEDOWN_${today}.md`);
|
|
|
|
return handoffPath;
|
|
}
|
|
|
|
/**
|
|
* Phase 6: Create Compaction Marker
|
|
*/
|
|
function createCompactionMarker(handoffPath) {
|
|
section('Phase 6: Setting Compaction Marker');
|
|
|
|
const marker = {
|
|
session_completed: true,
|
|
closedown_timestamp: new Date().toISOString(),
|
|
next_action: 'compaction_expected',
|
|
recovery_doc: path.basename(handoffPath)
|
|
};
|
|
|
|
fs.writeFileSync(SESSION_MARKER_PATH, JSON.stringify(marker, null, 2), 'utf8');
|
|
success('Compaction marker created');
|
|
info('Next session-init will detect post-compaction restart');
|
|
}
|
|
|
|
/**
|
|
* Main execution
|
|
*/
|
|
async function main() {
|
|
// Parse command-line arguments
|
|
const args = process.argv.slice(2);
|
|
let autoAnswer = null;
|
|
|
|
if (args.includes('--deploy') || args.includes('-y')) {
|
|
autoAnswer = true;
|
|
} else if (args.includes('--no-deploy') || args.includes('-n')) {
|
|
autoAnswer = false;
|
|
}
|
|
|
|
header('Tractatus Session Closedown (v2.0)');
|
|
|
|
log(' Comprehensive session shutdown with deployment support', 'cyan');
|
|
log(' This will cleanup, analyze, optionally deploy, and create handoff', 'cyan');
|
|
if (autoAnswer !== null) {
|
|
log(` Non-interactive mode: ${autoAnswer ? 'will deploy' : 'will skip deployment'}`, 'cyan');
|
|
}
|
|
console.log('');
|
|
|
|
// Phase 1: Comprehensive Cleanup
|
|
const cleanupResults = await cleanup();
|
|
|
|
// Phase 2: Framework Analysis (with terminal output)
|
|
const frameworkAnalysis = await analyzeFrameworkPerformance();
|
|
|
|
// Phase 3: Git Change Categorization
|
|
const gitAnalysis = analyzeGitChanges();
|
|
|
|
// Phase 4: Interactive Deployment
|
|
const deploymentResult = await interactiveDeployment(gitAnalysis, autoAnswer);
|
|
|
|
// Phase 5: Create Handoff (AT END with all results)
|
|
const handoffPath = createHandoffDocument(cleanupResults, frameworkAnalysis, gitAnalysis, deploymentResult);
|
|
|
|
// Phase 6: Compaction Marker
|
|
createCompactionMarker(handoffPath);
|
|
|
|
// Summary
|
|
header('Session Closedown Complete');
|
|
console.log('');
|
|
success('All phases completed successfully');
|
|
console.log('');
|
|
|
|
if (deploymentResult.deployed) {
|
|
success('✓ Changes deployed to production');
|
|
} else if (deploymentResult.skipped) {
|
|
info('⏭ Deployment skipped');
|
|
} else if (deploymentResult.failed) {
|
|
error('✗ Deployment failed - see handoff document');
|
|
}
|
|
|
|
console.log('');
|
|
info('Handoff document created:');
|
|
log(` ${handoffPath}`, 'bright');
|
|
console.log('');
|
|
info('Next session startup:');
|
|
log(' node scripts/session-init.js', 'bright');
|
|
console.log('');
|
|
warning('⚠️ STOP ALL WORK NOW - Session is complete');
|
|
console.log('');
|
|
log(' Handoff signals intent to start NEW session with fresh context.', 'yellow');
|
|
log(' Do NOT continue working after closedown.', 'yellow');
|
|
console.log('');
|
|
|
|
process.exit(0);
|
|
}
|
|
|
|
// Run
|
|
main().catch(err => {
|
|
console.error('');
|
|
error(`Closedown failed: ${err.message}`);
|
|
console.error('');
|
|
process.exit(1);
|
|
});
|