tractatus/scripts/session-closedown.js
TheFlow 6e02150891 feat(governance): Phase 0 complete - 100% enforcement + defense coverage
Phase 0 fixes completed before baseline collection:

1. Defense-in-Depth Layer 1 (.gitignore)
   - Added missing credential file patterns
   - *.pem, *.key, *.p12, *.pfx
   - credentials.json, secrets, *.secret
   - config/secrets.json, auth.json
   - Verification:  All critical patterns in .gitignore

2. Defense-in-Depth Layer 5 (Credential Rotation)
   - Created docs/CREDENTIAL_ROTATION_PROCEDURES.md
   - MongoDB password rotation procedures
   - API key rotation procedures
   - SSH/deployment key rotation
   - Git history credential removal
   - Emergency contact procedures
   - Verification:  Rotation procedures documented

3. inst_083 Enforcement Recognition
   - Updated scripts/audit-enforcement.js
   - Added inst_083: ['scripts/session-init.js']
   - Documents handoff auto-injection enforcement
   - Verification:  40/40 imperative instructions (100%)

4. Session-closedown Dev Server Protection
   - Fixed scripts/session-closedown.js
   - Added port 9000 check to prevent killing dev server
   - Prevents disruption during active development
   - Verification:  Dev server preserved during cleanup

Baseline Metrics Collected:

- Enforcement Coverage: 40/40 (100%)
- Defense-in-Depth: 5/5 layers (100%)
- Framework Activity: 1,204+ audit logs, 162 blocks
- Research data saved to docs/research-data/metrics/

Research Documentation Plan:

- Created docs/RESEARCH_DOCUMENTATION_DETAILED_PLAN.md
- 150+ granular tasks across 6 phases
- User decisions confirmed (Working Paper v0.1)
- Scope: Development-time governance only
- Author: John G Stroh
- Contact: research@agenticgovernance.digital
- Status: Phase 0 complete, ready for Phase 1

Results:

 100% enforcement coverage (architectural)
 100% defense-in-depth (all 5 layers)
 All 6 framework services operational
 Clean baseline established for research paper
 Dev server protection implemented

Next: Phase 1 (Metrics Gathering & Verification)

Related: inst_072 (defense-in-depth), inst_083 (handoff auto-injection)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 16:15:21 +13:00

1012 lines
32 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 tracked background processes (inst_023)
try {
info('Cleaning up tracked background processes (inst_023)...');
// Use the new tracking script
try {
const cleanupOutput = execSync('node scripts/track-background-process.js cleanup', {
encoding: 'utf8',
stdio: 'pipe'
});
const killedMatch = cleanupOutput.match(/(\d+) killed/);
if (killedMatch) {
cleanupResults.processes_killed = parseInt(killedMatch[1]);
success(`Killed ${cleanupResults.processes_killed} tracked background process(es)`);
}
} catch (trackErr) {
warning(` Tracked process cleanup failed: ${trackErr.message}`);
}
// Also check for untracked node/npm processes
info('Checking for untracked background 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 untracked background processes found');
} 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;
}
// Don't kill the dev server on port 9000 (inst_002: app runs on port 9000)
// Killing dev server during session closedown breaks active development
try {
const portCheck = execSync(`lsof -i :9000 -t 2>/dev/null || true`, { encoding: 'utf8' });
if (portCheck.trim() === pid) {
info(` Skipping dev server: ${command.substring(0, 60)}... (port 9000)`);
return;
}
} catch (portErr) {
// lsof failed, continue with kill attempt
}
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;
}
/**
* Helper: Get scope adjustment summary (inst_052)
*/
function getScopeAdjustmentSummary() {
const scopeLogPath = path.join(__dirname, '../.claude/scope-adjustments.json');
if (!fs.existsSync(scopeLogPath)) {
return '✅ No scope adjustments made this session';
}
const log = JSON.parse(fs.readFileSync(scopeLogPath, 'utf8'));
const sessionState = JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8'));
const sessionAdjustments = log.adjustments.filter(a => a.sessionId === sessionState.sessionId);
if (sessionAdjustments.length === 0) {
return '✅ No scope adjustments made this session';
}
let summary = `⚠️ **${sessionAdjustments.length} scope adjustment(s) made:**\n\n`;
sessionAdjustments.forEach((adj, idx) => {
summary += `${idx + 1}. **${adj.type.toUpperCase()}** (message #${adj.messageNumber})\n`;
summary += ` - Rationale: ${adj.rationale}\n`;
if (adj.details) {
summary += ` - Details: ${adj.details}\n`;
}
summary += ` - User discretion: ${adj.userGrantedDiscretion ? 'Yes' : 'No'}\n\n`;
});
return summary;
}
/**
* Helper: Get hook approval summary (inst_061)
*/
function getHookApprovalSummary() {
const approvalCachePath = path.join(__dirname, '../.claude/approval-cache.json');
if (!fs.existsSync(approvalCachePath)) {
return '✅ No hook approvals cached';
}
const cache = JSON.parse(fs.readFileSync(approvalCachePath, 'utf8'));
const sessionState = JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8'));
if (cache.sessionId !== sessionState.sessionId || cache.approvals.length === 0) {
return '✅ No hook approvals cached for this session';
}
let summary = `📋 **${cache.approvals.length} approval(s) cached:**\n\n`;
summary += '_These "don\'t ask again" approvals will be reset at next session start._\n\n';
cache.approvals.forEach((approval, idx) => {
summary += `${idx + 1}. \`${approval.commandType}\` in \`${approval.directory}\`\n`;
});
return summary + '\n';
}
/**
* 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
---
## Session Activity Tracking
### Scope Adjustments (inst_052)
${getScopeAdjustmentSummary()}
### Hook Approvals (inst_061)
${getHookApprovalSummary()}
---
## 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);
});