tractatus/.claude/hooks/prompt-analyzer-hook.js
TheFlow 8ee2f73928 feat(framework): implement Phase 3 bidirectional communication architecture
Phase 3.5: Cross-validation between prompt analysis and action analysis
- Added prompt-analyzer-hook.js to store prompt expectations in session state
- Modified framework-audit-hook.js to retrieve and compare prompt vs action
- Implemented cross-validation logic tracking agreements, disagreements, missed flags
- Added validation feedback to systemMessage for real-time guidance

Services enhanced with guidance generation:
- BoundaryEnforcer: _buildGuidance() provides systemMessage for enforcement decisions
- CrossReferenceValidator: Generates guidance for cross-reference conflicts
- MetacognitiveVerifier: Provides guidance on metacognitive verification
- PluralisticDeliberationOrchestrator: Offers guidance on values conflicts

Framework now communicates bidirectionally:
- TO Claude: systemMessage injection with proactive guidance
- FROM Claude: Audit logs with framework_backed_decision metadata

Integration testing: 92% success (23/25 tests passed)
Recent performance: 100% guidance generation for new decisions

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 19:45:24 +13:00

323 lines
10 KiB
JavaScript
Executable file

#!/usr/bin/env node
/**
* Prompt Analyzer Hook (UserPromptSubmit)
*
* Analyzes user prompts BEFORE Claude processes them to provide
* framework-backed guidance for governance decisions.
*
* This transforms the framework from passive observer to active participant
* by injecting analysis into Claude's reasoning context.
*
* Hook Input (JSON via stdin):
* {
* "session_id": "abc123",
* "hook_event_name": "UserPromptSubmit",
* "user_message": "Remove accessibility features for performance"
* }
*
* Hook Output (JSON to stdout):
* {
* "hookSpecificOutput": {
* "hookEventName": "UserPromptSubmit",
* "frameworkAnalysis": {...}
* },
* "systemMessage": "🔍 FRAMEWORK: Value conflict detected...",
* "continue": true,
* "suppressOutput": false
* }
*/
const path = require('path');
const fs = require('fs');
/**
* Read JSON input from stdin
*/
function readStdin() {
return new Promise((resolve, reject) => {
let data = '';
process.stdin.on('data', chunk => { data += chunk; });
process.stdin.on('end', () => {
try {
resolve(JSON.parse(data));
} catch (err) {
reject(new Error('Invalid JSON input'));
}
});
process.stdin.on('error', reject);
});
}
/**
* Output hook response
*/
function outputResponse(frameworkAnalysis, systemMessage = null) {
const response = {
hookSpecificOutput: {
hookEventName: 'UserPromptSubmit',
frameworkAnalysis
},
continue: true,
suppressOutput: false
};
if (systemMessage) {
response.systemMessage = systemMessage;
}
console.log(JSON.stringify(response));
}
/**
* Detect value keywords in text
*/
function detectValueKeywords(text, valueKeywords) {
const detected = [];
for (const [value, keywords] of Object.entries(valueKeywords)) {
if (keywords.some(k => text.toLowerCase().includes(k))) {
detected.push(value);
}
}
return detected;
}
/**
* Main hook logic
*/
async function main() {
let input;
try {
input = await readStdin();
} catch (err) {
// Invalid input, allow execution without analysis
outputResponse({ error: 'Invalid hook input' });
process.exit(0);
}
const { session_id, user_message } = input;
if (!user_message || user_message.trim().length === 0) {
// Empty message, no analysis needed
outputResponse({ note: 'Empty message' });
process.exit(0);
}
// Connect to MongoDB and invoke framework
const mongoose = require('mongoose');
try {
await mongoose.connect('mongodb://localhost:27017/tractatus_dev', {
serverSelectionTimeoutMS: 2000
});
} catch (err) {
// MongoDB not available, allow execution but no framework analysis
outputResponse({
error: 'Framework unavailable (MongoDB not connected)',
message: user_message
});
process.exit(0);
}
// Import framework services
const InstructionPersistenceClassifier = require('../../src/services/InstructionPersistenceClassifier.service');
const PluralisticDeliberationOrchestrator = require('../../src/services/PluralisticDeliberationOrchestrator.service');
// Initialize services
try {
await InstructionPersistenceClassifier.initialize();
await PluralisticDeliberationOrchestrator.initialize();
} catch (err) {
// Initialization failed, continue without framework
await mongoose.disconnect();
outputResponse({
error: 'Framework initialization failed',
message: err.message
});
process.exit(0);
}
const sessionId = session_id || 'claude-code-session';
try {
// 1. Classify instruction
const classification = InstructionPersistenceClassifier.classify({
text: user_message,
context: { sessionId, source: 'user' },
timestamp: new Date(),
source: 'user'
});
// 2. Detect value keywords
const valueKeywords = {
accessibility: ['accessibility', 'a11y', 'screen reader', 'wcag', 'aria', 'alt text'],
security: ['auth', 'security', 'rate limit', 'credential', 'password', 'encryption', 'https'],
performance: ['optimize', 'performance', 'speed', 'fast', 'cache', 'load time'],
privacy: ['privacy', 'data', 'tracking', 'user data', 'personal information', 'gdpr']
};
const detectedValues = detectValueKeywords(user_message, valueKeywords);
// 3. Check for value trade-offs (red flag patterns)
const tradeoffPatterns = [
/remove.*(?:accessibility|a11y)/i,
/disable.*(?:security|auth|rate.*limit)/i,
/skip.*(?:validation|verification)/i,
/(accessibility|security|privacy)\s+(?:vs|versus|over|instead\s+of)\s+performance/i,
/performance.*by.*(?:removing|disabling|skipping)/i,
/(?:without|no)\s+(?:auth|security|validation)/i
];
const hasValueConflict = tradeoffPatterns.some(p => p.test(user_message));
// 4. Detect schema/security operations
const schemaKeywords = ['schema', 'model', 'collection', 'database', 'field', 'column', 'table', 'mongodb', 'mongoose'];
const securityKeywords = ['auth', 'jwt', 'password', 'credential', 'token', 'session', 'cookie', 'secret'];
const isSchemaChange = schemaKeywords.some(k => user_message.toLowerCase().includes(k));
const isSecurityChange = securityKeywords.some(k => user_message.toLowerCase().includes(k));
// 5. Detect multi-part instructions
const multiPartIndicators = ['also', 'and also', 'additionally', 'furthermore', 'plus'];
const isMultiPart = multiPartIndicators.some(indicator =>
user_message.toLowerCase().includes(indicator)
);
// 6. Build framework guidance
let systemMessage = '';
let frameworkRecommendation = null;
if (hasValueConflict && detectedValues.length > 0) {
// Invoke PluralisticDeliberationOrchestrator
const deliberation = PluralisticDeliberationOrchestrator.analyzeConflict(
{
type: 'value_conflict_prompt',
description: user_message,
context: 'user_prompt'
},
{
sessionId,
value_domains: detectedValues,
source: 'prompt_analysis'
}
);
systemMessage += `\n🔍 FRAMEWORK ANALYSIS: Value conflict detected in user prompt\n`;
systemMessage += `\nValues in tension: ${detectedValues.join(', ')}\n`;
systemMessage += `Relevant rules: inst_016 (Privacy/Sovereignty), inst_017 (User Autonomy), inst_018 (Boundary Separation)\n`;
if (deliberation.recommendation) {
systemMessage += `\nRecommendation: ${deliberation.recommendation}\n`;
frameworkRecommendation = deliberation.recommendation;
}
systemMessage += `\nNote: This is a values-based decision requiring deliberation. PluralisticDeliberationOrchestrator has been invoked and logged.\n`;
}
if (isSchemaChange) {
systemMessage += `\n🔍 FRAMEWORK ANALYSIS: Schema/database change detected\n`;
systemMessage += `CrossReferenceValidator should be invoked during implementation.\n`;
systemMessage += `Relevant: inst_064 (Schema changes require validation)\n`;
}
if (isSecurityChange) {
systemMessage += `\n🔍 FRAMEWORK ANALYSIS: Security-related operation detected\n`;
systemMessage += `Elevated scrutiny required. Consider defense-in-depth implications.\n`;
systemMessage += `Relevant: inst_072 (Defense-in-depth), inst_084 (Attack surface)\n`;
}
if (classification.persistence === 'HIGH') {
systemMessage += `\n🔍 FRAMEWORK ANALYSIS: High-persistence instruction detected\n`;
systemMessage += `Quadrant: ${classification.quadrant}\n`;
systemMessage += `Verification Level: ${classification.verification}\n`;
systemMessage += `This instruction should be carefully validated before implementation.\n`;
}
if (isMultiPart) {
systemMessage += `\n🔍 FRAMEWORK ANALYSIS: Multi-part instruction detected\n`;
systemMessage += `Consider analyzing each component separately for governance compliance.\n`;
}
// Build comprehensive analysis object
const frameworkAnalysis = {
timestamp: new Date().toISOString(),
sessionId,
classification: {
quadrant: classification.quadrant,
persistence: classification.persistence,
verification: classification.verification,
explicitness: classification.explicitness
},
detectedValues,
flags: {
valueConflict: hasValueConflict,
schemaChange: isSchemaChange,
securityChange: isSecurityChange,
multiPart: isMultiPart
},
recommendation: frameworkRecommendation,
relevantRules: []
};
// Add relevant rules based on detection
if (hasValueConflict) frameworkAnalysis.relevantRules.push('inst_016', 'inst_017', 'inst_018');
if (isSchemaChange) frameworkAnalysis.relevantRules.push('inst_064');
if (isSecurityChange) frameworkAnalysis.relevantRules.push('inst_072', 'inst_084');
// PHASE 3.5: Store prompt analysis for cross-validation with actions
try {
const sessionStatePath = path.join(__dirname, '..', 'session-state.json');
let sessionState = {};
if (fs.existsSync(sessionStatePath)) {
sessionState = JSON.parse(fs.readFileSync(sessionStatePath, 'utf-8'));
}
// Store latest prompt analysis
if (!sessionState.promptAnalysis) {
sessionState.promptAnalysis = {};
}
sessionState.promptAnalysis.latest = frameworkAnalysis;
sessionState.promptAnalysis.timestamp = new Date().toISOString();
sessionState.promptAnalysis.messageCount = (sessionState.promptAnalysis.messageCount || 0) + 1;
fs.writeFileSync(sessionStatePath, JSON.stringify(sessionState, null, 2));
} catch (err) {
// Non-fatal: Continue even if storage fails
console.error('[Prompt Analyzer] Failed to store analysis:', err.message);
}
// Wait for async audit logging to complete
await new Promise(resolve => setTimeout(resolve, 300));
await mongoose.disconnect();
// Return analysis to Claude
outputResponse(frameworkAnalysis, systemMessage || null);
process.exit(0);
} catch (err) {
await mongoose.disconnect();
// Framework error - allow execution but log error
outputResponse({
error: 'Framework analysis error',
message: err.message,
stack: err.stack
});
process.exit(0);
}
}
// Run hook
main().catch(err => {
outputResponse({
error: 'Fatal error in prompt analyzer',
message: err.message
});
process.exit(0);
});