OverviewFilesCommitsIssuesDocsReleasesPeers

knowledge-ratchet / detect-correction.js

85 lines · javascript ref:
#!/usr/bin/env node

// Correction detector hook — runs on UserPromptSubmit
// Detects correction signals in user messages
// Appends to corrections.log for pattern extraction

const fs = require('fs')
const path = require('path')
const { execFileSync } = require('child_process')

const LOG_FILE = path.join(__dirname, 'corrections.log')
const CONTRADICTION_SCRIPT = path.join(__dirname, 'contradiction-detector.js')

// Correction signal patterns — ratcheted from feedback_*.md history
const CORRECTION_PATTERNS = [
  // Direct corrections
  /\b(no|wrong|not that|that's not|incorrect|stop doing)\b/i,
  /\b(i said|i meant|i asked for|that's wrong)\b/i,
  // User providing the answer (shouldn't have to)
  /\b(it's actually|you should use|the function is|look at|use .+ instead)\b/i,
  // Frustration signals
  /\b(again|already told you|how many times|we discussed this)\b/i,
  // Explicit feedback
  /\b(don't ever|never do|stop|please don't|do not)\b/i
]

// Approval patterns — also tracked for scoring
const APPROVAL_PATTERNS = [
  /\b(perfect|exactly|great|yes|lgtm|looks good|nice|thanks)\b/i
]

async function main () {
  let input = ''
  for await (const chunk of process.stdin) {
    input += chunk
  }

  const data = JSON.parse(input)
  const userMessage = data.prompt || data.user_prompt || data.message || ''

  if (!userMessage || userMessage.length < 3) {
    process.exit(0)
  }

  // Skip LLM analyzer prompts (prevent feedback loop)
  if (userMessage.includes('You are analyzing a user') ||
      userMessage.includes('REGEX CLASSIFIED AS') ||
      userMessage.includes('respond with EXACTLY one line')) {
    process.exit(0)
  }

  const isCorrection = CORRECTION_PATTERNS.some(p => p.test(userMessage))
  const isApproval = APPROVAL_PATTERNS.some(p => p.test(userMessage))

  if (isCorrection || isApproval) {
    const entry = {
      timestamp: new Date().toISOString(),
      type: isCorrection ? 'correction' : 'approval',
      message: userMessage.slice(0, 500), // truncate long messages
      cwd: process.cwd()
    }

    const logLine = `\n---\n${entry.timestamp} [${entry.type}]\n${entry.message}\ncwd: ${entry.cwd}\n`

    try {
      fs.appendFileSync(LOG_FILE, logLine)
    } catch {}

    // Check corrections against existing principles for contradictions
    if (isCorrection) {
      try {
        execFileSync('node', [CONTRADICTION_SCRIPT, 'check', userMessage.slice(0, 500)], {
          timeout: 5000,
          stdio: 'pipe'
        })
      } catch {}
    }
  }

  // Never block user messages
  process.exit(0)
}

main().catch(() => process.exit(0))