/** * rigstate hooks - Manage git hooks for Guardian integration * * Usage: * rigstate hooks install # Install pre-commit hook * rigstate hooks uninstall # Remove pre-commit hook */ import { Command } from 'commander'; import chalk from 'chalk'; import fs from 'fs/promises'; import path from 'path'; const PRE_COMMIT_SCRIPT = `#!/bin/sh # Rigstate Guardian Pre-commit Hook # Installed by: rigstate hooks install # 1. Silent Sentinel Check (Phase 5) if [ -f .rigstate/guardian.lock ]; then echo "🛑 INTERVENTION ACTIVE: Commit blocked by Silent Sentinel." echo " A critical violation ('HARD_LOCK') was detected by the Guardian Daemon." echo " Please fix the violation to unlock the repo." echo "" if grep -q "HARD_LOCK_ACTIVE" .rigstate/guardian.lock; then cat .rigstate/guardian.lock fi exit 1 fi echo "🛡️ Running Guardian checks..." # Run check with strict mode for critical violations rigstate check --staged --strict=critical # Exit with the same code as rigstate check exit $? `; export function createHooksCommand(): Command { const hooks = new Command('hooks') .description('Manage git hooks for Guardian integration'); hooks .command('install') .description('Install pre-commit hook to run Guardian checks') .option('--strict [level]', 'Strict level: "all" or "critical" (default)', 'critical') .action(async (options: { strict: string }) => { try { // 1. Find .git directory const gitDir = path.join(process.cwd(), '.git'); try { await fs.access(gitDir); } catch { console.log(chalk.red('❌ Not a git repository.')); console.log(chalk.dim(' Initialize with "git init" first.')); process.exit(1); } // 2. Ensure hooks directory exists const hooksDir = path.join(gitDir, 'hooks'); await fs.mkdir(hooksDir, { recursive: true }); // 3. Check for existing pre-commit const preCommitPath = path.join(hooksDir, 'pre-commit'); let existingContent = ''; try { existingContent = await fs.readFile(preCommitPath, 'utf-8'); if (existingContent.includes('rigstate')) { console.log(chalk.yellow('⚠ Rigstate pre-commit hook already installed.')); console.log(chalk.dim(' Use "rigstate hooks uninstall" to remove first.')); return; } } catch { // File doesn't exist, that's fine } // 4. Create hook script with configurable strict level let script = PRE_COMMIT_SCRIPT; if (options.strict === 'all') { script = script.replace('--strict=critical', '--strict'); } // 5. If existing hook, append to it if (existingContent && !existingContent.includes('rigstate')) { // Append our hook to existing const combinedScript = existingContent + '\n\n' + script.replace('#!/bin/sh\n', ''); await fs.writeFile(preCommitPath, combinedScript, { mode: 0o755 }); console.log(chalk.green('✅ Rigstate hook appended to existing pre-commit.')); } else { // Create new hook await fs.writeFile(preCommitPath, script, { mode: 0o755 }); console.log(chalk.green('✅ Pre-commit hook installed!')); } console.log(chalk.dim(` Path: ${preCommitPath}`)); console.log(chalk.dim(` Strict level: ${options.strict}`)); console.log(''); console.log(chalk.cyan('Guardian will now check your code before each commit.')); console.log(chalk.dim('Use "rigstate hooks uninstall" to remove the hook.')); } catch (error: any) { console.error(chalk.red('Failed to install hook:'), error.message); process.exit(1); } }); hooks .command('uninstall') .description('Remove Rigstate pre-commit hook') .action(async () => { try { const preCommitPath = path.join(process.cwd(), '.git', 'hooks', 'pre-commit'); try { const content = await fs.readFile(preCommitPath, 'utf-8'); if (!content.includes('rigstate')) { console.log(chalk.yellow('⚠ No Rigstate hook found in pre-commit.')); return; } // Check if it's our hook exclusively if (content.includes('# Rigstate Guardian Pre-commit Hook') && content.trim().split('\n').filter(l => l && !l.startsWith('#')).length <= 4) { // It's only our hook, remove the file await fs.unlink(preCommitPath); console.log(chalk.green('✅ Pre-commit hook removed.')); } else { // There's other content, just remove our section const lines = content.split('\n'); const filteredLines = []; let inRigstateSection = false; for (const line of lines) { if (line.includes('Rigstate Guardian Pre-commit Hook')) { inRigstateSection = true; continue; } if (inRigstateSection && line.includes('exit $?')) { inRigstateSection = false; continue; } if (!inRigstateSection && !line.includes('rigstate check')) { filteredLines.push(line); } } await fs.writeFile(preCommitPath, filteredLines.join('\n'), { mode: 0o755 }); console.log(chalk.green('✅ Rigstate section removed from pre-commit hook.')); } } catch { console.log(chalk.yellow('⚠ No pre-commit hook found.')); } } catch (error: any) { console.error(chalk.red('Failed to uninstall hook:'), error.message); process.exit(1); } }); return hooks; }