OverviewFilesCommitsIssuesDocsReleasesPeers

knowledge-ratchet / migrate-down.js

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

// Downward Migration Tracker
// Tracks how many times each principle has been violated (corrections in its domain)
// Escalates enforcement level: context → reminder → enforced
//
// Levels:
//   context  — principle in rules file (advisory, always loaded)
//   reminder — PreToolUse hook surfaces principle at moment of action
//   enforced — PreToolUse hook blocks the action until confirmed

const fs = require('fs')
const path = require('path')

const PRINCIPLES_DATA = path.join(__dirname, 'data', 'principles.json')
const MIGRATION_DATA = path.join(__dirname, 'data', 'migration-state.json')
const CORRECTIONS_LOG = path.join(__dirname, 'corrections.log')

const REMINDER_THRESHOLD = 2  // corrections before escalating to reminder
const ENFORCE_THRESHOLD = 4   // corrections before escalating to enforced

function loadMigrationState () {
  if (!fs.existsSync(MIGRATION_DATA)) return {}
  try { return JSON.parse(fs.readFileSync(MIGRATION_DATA, 'utf8')) }
  catch { return {} }
}

function saveMigrationState (state) {
  const dir = path.dirname(MIGRATION_DATA)
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
  fs.writeFileSync(MIGRATION_DATA, JSON.stringify(state, null, 2))
}

function loadPrinciples () {
  if (!fs.existsSync(PRINCIPLES_DATA)) return []
  return JSON.parse(fs.readFileSync(PRINCIPLES_DATA, 'utf8'))
}

function loadCorrections () {
  if (!fs.existsSync(CORRECTIONS_LOG)) return []
  return fs.readFileSync(CORRECTIONS_LOG, 'utf8')
    .split('\n---\n')
    .filter(e => e.includes('[correction]'))
}

function countViolations (principle, corrections) {
  const strip = w => w.replace(/[^a-z0-9]/g, '')
  const principleWords = principle.principle.toLowerCase().split(/\s+/).map(strip).filter(w => w.length > 2)
  let count = 0

  for (const c of corrections) {
    const cWords = c.toLowerCase().split(/\s+/).map(strip).filter(w => w.length > 2)
    const overlap = cWords.filter(w => principleWords.includes(w))
    if (overlap.length >= 2) count++
  }

  return count
}

function determineLevel (violations) {
  if (violations >= ENFORCE_THRESHOLD) return 'enforced'
  if (violations >= REMINDER_THRESHOLD) return 'reminder'
  return 'context'
}

function main () {
  const principles = loadPrinciples()
  const corrections = loadCorrections()
  const state = loadMigrationState()

  if (principles.length === 0) {
    console.log('No principles loaded.')
    return
  }

  console.log(`Principles: ${principles.length}`)
  console.log(`Corrections: ${corrections.length}`)
  console.log('')

  const updates = []

  for (const p of principles) {
    if (p.status !== 'active') continue

    const key = p.principle.slice(0, 60)
    const violations = countViolations(p, corrections)
    const currentLevel = state[key]?.level || 'context'
    const newLevel = determineLevel(violations)

    const changed = newLevel !== currentLevel

    state[key] = {
      level: newLevel,
      violations,
      lastChecked: new Date().toISOString()
    }

    const icon = newLevel === 'enforced' ? '🔒' : newLevel === 'reminder' ? '⚡' : '📄'
    const arrow = changed ? ` ← was ${currentLevel}` : ''
    console.log(`  ${icon} [${newLevel}] ${key} (${violations} violations)${arrow}`)

    if (changed) {
      updates.push({ principle: p.principle, from: currentLevel, to: newLevel, violations })
    }
  }

  saveMigrationState(state)

  if (updates.length > 0) {
    console.log('\n═══ LEVEL CHANGES ═══')
    for (const u of updates) {
      console.log(`  ${u.from} → ${u.to}: "${u.principle.slice(0, 60)}" (${u.violations} violations)`)
    }
    console.log('\nTo apply enforcement changes, update hooks in .claude/settings.json')
    console.log('Principles at "enforced" level should have PreToolUse hooks that block violations')
    console.log('Principles at "reminder" level should have PreToolUse hooks that warn')
  } else {
    console.log('\nNo level changes.')
  }
}

const command = process.argv[2]

if (command === 'status') {
  const state = loadMigrationState()
  if (Object.keys(state).length === 0) {
    console.log('No migration state yet. Run without arguments to analyze.')
  } else {
    console.log('═══ ENFORCEMENT LEVELS ═══')
    for (const [key, info] of Object.entries(state)) {
      const icon = info.level === 'enforced' ? '🔒' : info.level === 'reminder' ? '⚡' : '📄'
      console.log(`  ${icon} [${info.level}] ${key} (${info.violations} violations)`)
    }
  }
} else {
  main()
}