diff --git a/.claude/hooks/guard-generated-files.sh b/.claude/hooks/guard-generated-files.sh new file mode 100755 index 0000000..f9b27b0 --- /dev/null +++ b/.claude/hooks/guard-generated-files.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# +# PreToolUse guard (Write|Edit): block edits to generated inventory files. +# `inventories//hosts.yml` is produced by tf_to_inventory.py — editing it by +# hand is overwritten on the next `make tf-inventory`. The git pre-commit hooks do +# NOT catch this, so we enforce it here. +# +# Fails OPEN: any parsing/other error allows the action (never wedge tool use). +# +set -uo pipefail + +input=$(cat 2>/dev/null) || exit 0 +file=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty' 2>/dev/null) || exit 0 +[ -n "$file" ] || exit 0 + +case "$file" in + */inventories/*/hosts.yml | inventories/*/hosts.yml) + cat <<'JSON' +{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"inventories//hosts.yml is GENERATED by tf_to_inventory.py. Edit terraform/environments//main.tf (local.vms) and run `make tf-inventory`. The control node is the documented manual exception (docs/runbooks/new-host.md)."}} +JSON + exit 0 + ;; +esac + +exit 0 diff --git a/.claude/hooks/guard-vault-preflight.sh b/.claude/hooks/guard-vault-preflight.sh new file mode 100755 index 0000000..d664bbf --- /dev/null +++ b/.claude/hooks/guard-vault-preflight.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# +# PreToolUse guard (Bash): block `git commit` when the rbw vault agent is locked. +# The pre-commit ansible-lint hook decrypts vault.yml via rbw, so a commit while +# locked fails deep with a confusing error. This catches it early with a clear fix. +# +# Fails OPEN: only blocks on a definitive "rbw present AND not unlocked" signal. +# If rbw is missing, the command isn't a plain `git commit`, or `--no-verify` is +# used, the action is allowed. +# +set -uo pipefail + +input=$(cat 2>/dev/null) || exit 0 +cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null) || exit 0 + +case "$cmd" in + *"git commit"*) : ;; # a git commit — check further + *) exit 0 ;; # not a commit — allow +esac +case "$cmd" in + *"--no-verify"*) exit 0 ;; # hooks skipped anyway — allow +esac + +command -v rbw >/dev/null 2>&1 || exit 0 # rbw not installed — allow + +if rbw unlocked >/dev/null 2>&1; then + exit 0 # unlocked — allow +fi + +# rbw present but not unlocked (locked or agent not running) — the commit would +# fail in the pre-commit hook, so block early with guidance. +cat <<'JSON' +{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"rbw is locked — the pre-commit ansible-lint hook needs the vault password to decrypt vault.yml. Run: rbw unlock"}} +JSON +exit 0 diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..1853e42 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-settings.json", + "hooks": { + "PreToolUse": [ + { + "matcher": "Write|Edit", + "hooks": [ + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/guard-generated-files.sh\"", + "timeout": 10, + "statusMessage": "Checking generated-file guard" + } + ] + }, + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "bash \"${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/guard-vault-preflight.sh\"", + "timeout": 10, + "statusMessage": "Checking rbw vault pre-flight" + } + ] + } + ] + } +}