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>
323 lines
10 KiB
JavaScript
Executable file
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);
|
|
});
|