tractatus/scripts/framework-components/LoopDetector.js
TheFlow 9bc2410420 feat(framework): implement 6 high-priority governance enhancements
SUMMARY:
Implemented 6 framework refinements identified from incident analysis
(inst_049 and inst_025 violations). These enhancements provide architectural
enforcement for patterns that previously relied on voluntary compliance.

ENHANCEMENTS IMPLEMENTED:

1. Instruction Analytics Script (Priority 8)
   - scripts/analyze-instruction-violations.js
   - Analyzes instruction-history.json for usage patterns
   - Identifies most violated instructions
   - Calculates enforcement effectiveness (hook vs. voluntary)
   - Shows 97.2% voluntary compliance, 75% hook enforcement
   - Recommendations for converting voluntary → architectural

2. Framework Incidents Database (Priority 7)
   - .claude/framework-incidents.json
   - Structured tracking of framework violations
   - INC-001: Ignored user hypothesis (70k tokens wasted)
   - INC-002: Deployment directory flattening (inst_025 violation)
   - Statistics: 2 incidents, 75k tokens wasted, 4.5 hours lost

3. Loop Detector Module (Priorities 3 & 4)
   - scripts/framework-components/LoopDetector.js
   - Detects "stuck in loop" patterns
   - Triggers: 3+ edits to same file, repeated action types
   - Feeds into MetacognitiveVerifier and ContextPressureMonitor
   - Calculates pressure contribution (5-40 points by severity)

4. Action Pattern Tracker (Priority 3 & 4)
   - scripts/track-action-patterns.js
   - Tracks edit/write actions to detect repetition
   - Alerts after 3 consecutive edits to same file
   - Maintains action history (last 100 actions)
   - Recommendations for metacognitive verification

5. Pre-Deployment Validation (Priority 5)
   - scripts/validate-deployment.js
   - Validates rsync/scp commands against inst_025
   - Detects directory structure flattening
   - Suggests separate commands for different directories
   - Prevents 4th documented occurrence of deployment errors

6. User Suggestion Tracker (Priority 6)
   - scripts/track-user-suggestions.js
   - Implements inst_049: "Test user hypothesis first"
   - Tracks user technical hypotheses
   - Flags untested hypotheses as HIGH priority
   - Integrates with MetacognitiveVerifier for compliance

USAGE:

Instruction Analytics:
  node scripts/analyze-instruction-violations.js

Loop Detection:
  node scripts/track-action-patterns.js --check
  node scripts/track-action-patterns.js --summary

Deployment Validation:
  node scripts/validate-deployment.js --command "rsync ..."

User Suggestions:
  node scripts/track-user-suggestions.js --add "hypothesis text"
  node scripts/track-user-suggestions.js --check-untested

IMPACT:
- Converts 6 voluntary compliance patterns to architectural enforcement
- Prevents repeat of documented 75k token waste
- Provides visibility into framework effectiveness
- Establishes foundation for future hook integration

METRICS FROM ANALYTICS:
- Active Instructions: 40
- Voluntary Compliance: 97.2%
- Hook Enforcement: 75.0%
- Recorded Violations: 2
- Tokens Wasted: 75,000

NEXT STEPS:
- Integrate LoopDetector into MetacognitiveVerifier.service.js
- Add Pre-Deployment Validation to Bash command validator hook
- Wire User Suggestion Tracker into BoundaryEnforcer checks
- Document successful compliance patterns (7 STRATEGIC instructions at 100%)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 20:41:10 +13:00

233 lines
5.8 KiB
JavaScript

/**
* Loop Detector
*
* Detects "stuck in loop" patterns that indicate:
* - Repeated failed attempts on same problem
* - Same file being edited multiple times without progress
* - User frustration signals
*
* Used by:
* - MetacognitiveVerifier (triggers verification on loop detection)
* - ContextPressureMonitor (elevates pressure on loop detection)
*
* Copyright 2025 Tractatus Project
* Licensed under Apache License 2.0
*/
const fs = require('fs');
const path = require('path');
class LoopDetector {
constructor(options = {}) {
this.silent = options.silent || false;
this.actionPatternsPath = path.join(__dirname, '../../.claude/action-patterns.json');
this.sessionStatePath = path.join(__dirname, '../../.claude/session-state.json');
// Thresholds
this.thresholds = {
same_file_edits: 3, // Alert after 3 edits to same file
consecutive_actions: 5, // Within last 5 actions
user_frustration_phrases: [
'you ignored me',
'i told you',
'not working',
'still broken',
'same error',
'again',
'why are you'
]
};
}
/**
* Load action patterns
*/
loadActionPatterns() {
try {
if (fs.existsSync(this.actionPatternsPath)) {
return JSON.parse(fs.readFileSync(this.actionPatternsPath, 'utf8'));
}
} catch (err) {
if (!this.silent) {
console.warn(`LoopDetector: Could not load action patterns: ${err.message}`);
}
}
return null;
}
/**
* Detect loops based on action patterns
*/
detectLoop() {
const patterns = this.loadActionPatterns();
if (!patterns || !patterns.actions || patterns.actions.length === 0) {
return {
detected: false,
type: null,
severity: 'NONE',
details: null
};
}
// Check 1: Repeated file edits
const fileEditLoop = this.detectRepeatedFileEdits(patterns);
if (fileEditLoop.detected) {
return fileEditLoop;
}
// Check 2: Same action type repeated
const actionLoop = this.detectRepeatedActionType(patterns);
if (actionLoop.detected) {
return actionLoop;
}
// Check 3: User frustration signals (would need message history)
// TODO: Implement if message history tracking is added
return {
detected: false,
type: null,
severity: 'NONE',
details: null
};
}
/**
* Detect repeated edits to same file
*/
detectRepeatedFileEdits(patterns) {
const recentActions = patterns.actions.slice(-this.thresholds.consecutive_actions);
// Group by file
const fileGroups = {};
recentActions.forEach(action => {
if (action.type === 'edit' || action.type === 'write') {
if (!fileGroups[action.file]) {
fileGroups[action.file] = [];
}
fileGroups[action.file].push(action);
}
});
// Check for files edited multiple times
for (const [file, actions] of Object.entries(fileGroups)) {
if (actions.length >= this.thresholds.same_file_edits) {
return {
detected: true,
type: 'repeated_file_edit',
severity: 'MEDIUM',
details: {
file: file,
count: actions.length,
recent_count: actions.length,
message: `File edited ${actions.length} times in last ${this.thresholds.consecutive_actions} actions`,
recommendation: 'Pause and run MetacognitiveVerifier to assess approach'
}
};
}
}
return { detected: false };
}
/**
* Detect repeated action type (e.g., multiple failed bash commands)
*/
detectRepeatedActionType(patterns) {
const recentActions = patterns.actions.slice(-this.thresholds.consecutive_actions);
// Count consecutive actions of same type
let consecutiveCount = 1;
let currentType = null;
for (let i = recentActions.length - 1; i >= 0; i--) {
const action = recentActions[i];
if (currentType === null) {
currentType = action.type;
} else if (action.type === currentType) {
consecutiveCount++;
} else {
break;
}
}
if (consecutiveCount >= 3) {
return {
detected: true,
type: 'repeated_action_type',
severity: 'LOW',
details: {
action_type: currentType,
count: consecutiveCount,
message: `${consecutiveCount} consecutive ${currentType} actions`,
recommendation: 'Consider if current approach is effective'
}
};
}
return { detected: false };
}
/**
* Get loop status for MetacognitiveVerifier
*/
getLoopStatus() {
const loop = this.detectLoop();
if (!loop.detected) {
return {
in_loop: false,
should_verify: false
};
}
return {
in_loop: true,
should_verify: loop.severity === 'MEDIUM' || loop.severity === 'HIGH',
loop_type: loop.type,
severity: loop.severity,
details: loop.details
};
}
/**
* Calculate pressure contribution for ContextPressureMonitor
*/
calculatePressureContribution() {
const loop = this.detectLoop();
if (!loop.detected) {
return 0;
}
// Pressure contribution based on severity
const pressureMap = {
'LOW': 5,
'MEDIUM': 15,
'HIGH': 25,
'CRITICAL': 40
};
return pressureMap[loop.severity] || 0;
}
/**
* Log loop detection result
*/
log(message, level = 'info') {
if (this.silent) return;
const colors = {
info: '\x1b[36m',
warn: '\x1b[33m',
error: '\x1b[31m',
success: '\x1b[32m',
reset: '\x1b[0m'
};
const color = colors[level] || colors.reset;
console.log(`${color}[LoopDetector] ${message}${colors.reset}`);
}
}
module.exports = LoopDetector;