#!/usr/bin/env node /** * CrossReferenceValidator - Active Enforcement Module * * Validates proposed actions against instruction history to prevent * pattern recognition bias (the 27027 problem). * * This module can be called from: * - Hook validators (automatic enforcement) * - Pre-action-check script (manual invocation) * - Directly by AI in code (voluntary invocation) * * Copyright 2025 Tractatus Project * Licensed under Apache License 2.0 */ const fs = require('fs'); const path = require('path'); const INSTRUCTION_HISTORY_PATH = path.join(__dirname, '../../.claude/instruction-history.json'); const SESSION_STATE_PATH = path.join(__dirname, '../../.claude/session-state.json'); class CrossReferenceValidator { constructor(options = {}) { this.silent = options.silent || false; this.instructions = null; this.loadInstructions(); } loadInstructions() { try { if (!fs.existsSync(INSTRUCTION_HISTORY_PATH)) { this.log('warning', 'Instruction history not found'); this.instructions = []; return; } const data = JSON.parse(fs.readFileSync(INSTRUCTION_HISTORY_PATH, 'utf8')); this.instructions = data.instructions || []; this.log('info', `Loaded ${this.instructions.length} instructions`); } catch (error) { this.log('error', `Failed to load instructions: ${error.message}`); this.instructions = []; } } log(level, message) { if (this.silent && level !== 'error') return; const colors = { info: '\x1b[36m', warning: '\x1b[33m', error: '\x1b[31m', success: '\x1b[32m', reset: '\x1b[0m' }; const prefix = { info: '[CrossReferenceValidator]', warning: '[⚠ CrossReferenceValidator]', error: '[✗ CrossReferenceValidator]', success: '[✓ CrossReferenceValidator]' }[level]; console.log(`${colors[level]}${prefix} ${message}${colors.reset}`); } /** * Validate a file edit action */ validateFileEdit(filePath, oldString, newString) { const relevantInstructions = this.findRelevantInstructions('file-edit', filePath); if (relevantInstructions.length === 0) { return { passed: true, relevantInstructions: [] }; } this.log('info', `Found ${relevantInstructions.length} relevant instructions for file edit`); // Check for conflicts const conflicts = []; relevantInstructions.forEach(inst => { // Check CSP instructions (inst_008) if (inst.id === 'inst_008' && (filePath.endsWith('.html') || filePath.endsWith('.js'))) { const cspPatterns = [ { name: 'inline event handlers', regex: /\son\w+\s*=\s*["'][^"']*["']/gi }, { name: 'inline styles', regex: /\sstyle\s*=\s*["'][^"']+["']/gi }, { name: 'javascript: URLs', regex: /href\s*=\s*["']javascript:[^"']*["']/gi } ]; cspPatterns.forEach(pattern => { if (pattern.regex.test(newString)) { conflicts.push({ instruction: inst.id, issue: `New content contains ${pattern.name} (CSP violation)`, severity: 'HIGH' }); } }); } // Check pre-action-check requirement (inst_038) if (inst.id === 'inst_038') { // This will be checked in the hook itself } }); return { passed: conflicts.length === 0, conflicts, relevantInstructions }; } /** * Validate a Bash command */ validateBashCommand(command) { const relevantInstructions = this.findRelevantInstructions('bash', command); if (relevantInstructions.length === 0) { return { passed: true, relevantInstructions: [] }; } this.log('info', `Found ${relevantInstructions.length} relevant instructions for Bash command`); const conflicts = []; relevantInstructions.forEach(inst => { // Check deployment instructions if (inst.id === 'inst_025' && command.includes('rsync')) { // Deployment directory structure check // This is handled in validate-bash-command.js for detailed parsing // Here we just flag it as relevant this.log('info', `inst_025 (deployment structure) applies to this rsync command`); } // Check permission instructions if (inst.id === 'inst_022' && command.includes('rsync') && !command.includes('--chmod')) { if (command.includes('vps-93a693da') || command.includes('/var/www/')) { conflicts.push({ instruction: inst.id, issue: 'rsync to production without --chmod flag', severity: 'MEDIUM' }); } } }); return { passed: conflicts.length === 0, conflicts, relevantInstructions }; } /** * Find instructions relevant to a specific action */ findRelevantInstructions(actionType, context) { if (!this.instructions) { return []; } const relevant = []; this.instructions.forEach(inst => { // Only check active HIGH or MEDIUM persistence instructions if (!inst.active) return; if (inst.persistence !== 'HIGH' && inst.persistence !== 'MEDIUM') return; // Match by action type and context let isRelevant = false; if (actionType === 'file-edit' || actionType === 'file-write') { // CSP instructions if (inst.id === 'inst_008') isRelevant = true; // Pre-action-check requirement if (inst.id === 'inst_038') isRelevant = true; // File-specific instructions if (inst.text && inst.text.toLowerCase().includes('file')) isRelevant = true; } if (actionType === 'bash') { // Deployment instructions if (inst.id === 'inst_025' && context.includes('rsync')) isRelevant = true; if (inst.id === 'inst_020' && context.includes('rsync')) isRelevant = true; if (inst.id === 'inst_022' && context.includes('rsync')) isRelevant = true; // Git commit instructions if (context.includes('git commit') && inst.text && inst.text.includes('git')) { isRelevant = true; } } if (isRelevant) { relevant.push(inst); } }); return relevant; } /** * Update session state with validation activity */ updateSessionState() { try { let sessionState = {}; if (fs.existsSync(SESSION_STATE_PATH)) { sessionState = JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8')); } if (!sessionState.framework_components) { sessionState.framework_components = {}; } if (!sessionState.framework_components.CrossReferenceValidator) { sessionState.framework_components.CrossReferenceValidator = { message: 0, tokens: 0, timestamp: null, last_validation: null, validations_performed: 0 }; } sessionState.framework_components.CrossReferenceValidator.last_validation = new Date().toISOString(); sessionState.framework_components.CrossReferenceValidator.validations_performed += 1; sessionState.framework_components.CrossReferenceValidator.timestamp = new Date().toISOString(); fs.writeFileSync(SESSION_STATE_PATH, JSON.stringify(sessionState, null, 2)); } catch (error) { this.log('warning', `Could not update session state: ${error.message}`); } } /** * Main validation entry point */ validate(actionType, context) { this.log('info', `Validating ${actionType} action`); let result; if (actionType === 'file-edit') { result = this.validateFileEdit(context.filePath, context.oldString, context.newString); } else if (actionType === 'file-write') { result = this.validateFileEdit(context.filePath, '', context.content); } else if (actionType === 'bash') { result = this.validateBashCommand(context.command); } else { result = { passed: true, relevantInstructions: [] }; } this.updateSessionState(); if (!result.passed) { this.log('error', `Validation failed: ${result.conflicts.length} conflicts found`); result.conflicts.forEach(c => { this.log('error', ` ${c.instruction}: ${c.issue}`); }); } else if (result.relevantInstructions.length > 0) { this.log('success', `Validation passed (${result.relevantInstructions.length} relevant instructions checked)`); } else { this.log('success', 'Validation passed (no relevant instructions)'); } return result; } } // Export for use as module if (typeof module !== 'undefined' && module.exports) { module.exports = CrossReferenceValidator; } // CLI mode - allow direct invocation if (require.main === module) { const args = process.argv.slice(2); const actionType = args[0]; const contextJSON = args[1]; if (!actionType || !contextJSON) { console.error('Usage: node CrossReferenceValidator.js '); console.error('Example: node CrossReferenceValidator.js bash \'{"command":"rsync ..."}\''); process.exit(1); } try { const context = JSON.parse(contextJSON); const validator = new CrossReferenceValidator(); const result = validator.validate(actionType, context); if (result.passed) { console.log('✓ Validation passed'); process.exit(0); } else { console.log('✗ Validation failed'); process.exit(1); } } catch (error) { console.error(`Error: ${error.message}`); process.exit(2); } }