#!/usr/bin/env node /** * PreToolUse hook: auto-backup config files before Edit/Write. * Reads $TOOL_INPUT to check if the target file is a config file. * If yes, backs it up via scanners/lib/backup.mjs. * Fast path — no scanner execution. */ import { existsSync } from 'node:fs'; import { basename, dirname, sep } from 'node:path'; // Config file patterns to protect const CONFIG_PATTERNS = [ /CLAUDE\.md$/i, /CLAUDE\.local\.md$/i, /settings\.json$/, /settings\.local\.json$/, /hooks\.json$/, /\.mcp\.json$/, /keybindings\.json$/, ]; const CONFIG_DIRS = ['rules']; function isConfigFile(filePath) { if (!filePath) return false; const name = basename(filePath); const dir = dirname(filePath); // Check filename patterns for (const pattern of CONFIG_PATTERNS) { if (pattern.test(name)) return true; } // Check if inside a rules/ directory for (const d of CONFIG_DIRS) { if (dir.includes(`${sep}${d}${sep}`) || dir.endsWith(`${sep}${d}`)) { if (name.endsWith('.md')) return true; } } return false; } /** * Read all data from stdin asynchronously. * @returns {Promise} */ function readStdin() { return new Promise((resolve, reject) => { const chunks = []; process.stdin.setEncoding('utf-8'); process.stdin.on('data', chunk => chunks.push(chunk)); process.stdin.on('end', () => resolve(chunks.join(''))); process.stdin.on('error', reject); }); } async function main() { let input; try { input = await readStdin(); } catch { process.exit(0); } let toolInput; try { toolInput = JSON.parse(input); } catch { process.exit(0); } const filePath = toolInput.file_path || toolInput.path; if (!filePath || !isConfigFile(filePath) || !existsSync(filePath)) { process.exit(0); } const { createBackup } = await import('../../scanners/lib/backup.mjs'); const { backupPath } = createBackup([filePath]); process.stderr.write(`[config-audit] Auto-backup: ${basename(filePath)} → ${backupPath}\n`); } main().catch(() => process.exit(0));