#!/usr/bin/env node
// Forward-Only Ratchet
// Wraps principle changes in git commits
// If validation score drops after a change, reverts the commit
// Principles can only improve, never degrade
const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')
const ROOT = path.join(__dirname, '..')
const VALIDATION_SCRIPT = path.join(__dirname, 'validate-principles.js')
const PRINCIPLES_DATA = path.join(__dirname, 'data', 'principles.json')
function git (cmd) {
return execSync(`git ${cmd}`, { cwd: ROOT, encoding: 'utf8', stdio: 'pipe' }).trim()
}
function getValidationScore () {
try {
execSync(`node ${VALIDATION_SCRIPT}`, { cwd: ROOT, stdio: 'pipe' })
const report = JSON.parse(fs.readFileSync(
path.join(__dirname, 'data', 'validation-report.json'), 'utf8'
))
return report.scores.lenientRate
} catch {
return null
}
}
function ratchetCommit (message) {
// Step 1: get baseline score
const baseline = getValidationScore()
console.log(`Baseline validation score: ${baseline !== null ? (baseline * 100).toFixed(0) + '%' : 'n/a'}`)
// Step 2: stage knowledge-ratchet changes
try {
git('add knowledge-ratchet/active-principles.md')
git('add knowledge-ratchet/data/')
git('add .claude/rules/09-ratchet-principles.md')
} catch {}
// Step 3: check if there's anything to commit
const status = git('status --porcelain knowledge-ratchet/ .claude/rules/09-ratchet-principles.md')
if (!status) {
console.log('Nothing to commit.')
return
}
// Step 4: commit
const fullMessage = `${message}\n\nKnowledge ratchet: principle update\n\nCo-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>`
try {
git(`commit -m "${fullMessage.replace(/"/g, '\\"')}"`)
} catch (e) {
console.error('Commit failed:', e.message)
return
}
console.log('Committed principle change.')
// Step 5: validate
const newScore = getValidationScore()
console.log(`New validation score: ${newScore !== null ? (newScore * 100).toFixed(0) + '%' : 'n/a'}`)
// Step 6: ratchet check
if (baseline !== null && newScore !== null && newScore < baseline) {
console.log(`\n⚠ REGRESSION: score dropped from ${(baseline * 100).toFixed(0)}% to ${(newScore * 100).toFixed(0)}%`)
console.log('Reverting commit...')
git('reset HEAD~1')
git('checkout -- knowledge-ratchet/ .claude/rules/09-ratchet-principles.md')
console.log('Reverted. Principles unchanged.')
} else {
console.log('✓ Ratchet holds. Principles improved or stable.')
}
}
// CLI
const command = process.argv[2]
if (command === 'commit') {
const message = process.argv.slice(3).join(' ') || 'Update knowledge ratchet principles'
ratchetCommit(message)
} else if (command === 'score') {
const score = getValidationScore()
if (score !== null) {
console.log(`Current validation score: ${(score * 100).toFixed(0)}%`)
} else {
console.log('Could not compute validation score.')
}
} else {
console.log('Knowledge Ratchet — Forward-Only Git Ratchet')
console.log('')
console.log('Commands:')
console.log(' commit [message] Commit principle changes with validation guard')
console.log(' score Show current validation score')
console.log('')
console.log('If validation score drops after commit, the change is reverted.')
console.log('Principles can only improve, never degrade.')
}