fix(hooks): scope vault-preflight to staged ansible; catch prose exec re-asks
guard-vault-preflight: block a locked 'git commit' only when the staged set
(git diff --cached, plus -a/--all) contains ansible content matching the
pre-commit ansible-lint hook's files: scope. Docs-/config-only commits never
trigger that hook, so they no longer need the vault — fixing the false block on
docs-only commits. Fails safe to block when unsure.
guard-execution-mode-menu: widen the execution-mode arm to also catch free-form
prose re-asks of the subagent-vs-inline choice ('which execution approach?',
'subagent vs inline', ...), which the literal-menu matcher missed; the push
re-ask is intentionally left to the dont-reask-settled-defaults memory.
Consumes two 2026-06-17 signals in docs/FRICTION.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8f1c7d47ec
commit
39904a778a
2 changed files with 38 additions and 14 deletions
|
|
@ -6,7 +6,12 @@
|
||||||
# 1. The execution-mode menu — writing-plans / subagent-driven-development script a
|
# 1. The execution-mode menu — writing-plans / subagent-driven-development script a
|
||||||
# "Subagent-Driven vs Inline Execution — which approach?" menu at the plan→execution
|
# "Subagent-Driven vs Inline Execution — which approach?" menu at the plan→execution
|
||||||
# handoff. boma's standing preference is to NEVER present it and proceed
|
# handoff. boma's standing preference is to NEVER present it and proceed
|
||||||
# subagent-driven. (Recorded by the 2026-06-10 kaizen review.)
|
# subagent-driven. (Recorded by the 2026-06-10 kaizen review; the 2026-06-17 review
|
||||||
|
# widened the matcher to also catch free-form *prose* re-asks of the same choice —
|
||||||
|
# e.g. "which execution approach?" — which the literal-menu matcher missed. The
|
||||||
|
# sibling push-vs-not re-ask is deliberately NOT hooked: a genuine "should I push?"
|
||||||
|
# is sometimes legitimate, so it stays a soft default via the
|
||||||
|
# dont-reask-settled-defaults memory rather than a hard block.)
|
||||||
# 2. The brainstorming spec-review gate — the brainstorming skill scripts "Spec written
|
# 2. The brainstorming spec-review gate — the brainstorming skill scripts "Spec written
|
||||||
# and committed … please review it before … the implementation plan." The standing
|
# and committed … please review it before … the implementation plan." The standing
|
||||||
# agreement is to move directly from the committed spec to writing-plans. (Recorded
|
# agreement is to move directly from the committed spec to writing-plans. (Recorded
|
||||||
|
|
@ -39,7 +44,11 @@ text=$(jq -rs '
|
||||||
low="${text,,}"
|
low="${text,,}"
|
||||||
|
|
||||||
if [[ "$low" == *"inline execution"* \
|
if [[ "$low" == *"inline execution"* \
|
||||||
&& ( "$low" == *"which approach"* || "$low" == *"two execution options"* ) ]]; then
|
&& ( "$low" == *"which approach"* || "$low" == *"two execution options"* ) ]] \
|
||||||
|
|| [[ "$low" == *"subagent-driven or inline"* || "$low" == *"inline or subagent"* ]] \
|
||||||
|
|| [[ "$low" == *"subagent-driven vs inline"* || "$low" == *"subagent vs inline"* \
|
||||||
|
|| "$low" == *"inline vs subagent"* ]] \
|
||||||
|
|| [[ "$low" == *"execution approach"* && "$low" == *"?"* ]]; then
|
||||||
cat <<'JSON'
|
cat <<'JSON'
|
||||||
{"decision":"block","reason":"Execution-mode menu detected in your final message. boma standing preference (docs/FRICTION.md + always-subagent-driven-execution memory): never present the subagent-driven-vs-inline menu. Drop the menu and proceed with subagent-driven execution directly (superpowers:subagent-driven-development)."}
|
{"decision":"block","reason":"Execution-mode menu detected in your final message. boma standing preference (docs/FRICTION.md + always-subagent-driven-execution memory): never present the subagent-driven-vs-inline menu. Drop the menu and proceed with subagent-driven execution directly (superpowers:subagent-driven-development)."}
|
||||||
JSON
|
JSON
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
#
|
#
|
||||||
# PreToolUse guard (Bash): block `git commit` when the rbw vault agent is locked.
|
# PreToolUse guard (Bash): block `git commit` ONLY when the rbw vault agent is locked
|
||||||
# The pre-commit ansible-lint hook decrypts vault.yml via rbw, so a commit while
|
# AND the commit would actually need the vault. The pre-commit ansible-lint hook decrypts
|
||||||
# locked fails deep with a confusing error. This catches it early with a clear fix.
|
# vault.yml via rbw — but it is scoped (`files: ^(roles|playbooks|inventories)/.*\.ya?ml$`,
|
||||||
|
# always_run:false), so a docs-/config-only commit never triggers it and needs no vault.
|
||||||
|
# (2026-06-17 kaizen, docs/FRICTION.md: the old guard blocked *every* locked commit, so a
|
||||||
|
# docs-only commit got snagged needing a vault password it never uses.)
|
||||||
#
|
#
|
||||||
# Fails OPEN: only blocks on a definitive "rbw present AND not unlocked" signal.
|
# Fails OPEN: blocks only on a definitive "Ansible content staged AND rbw locked" signal.
|
||||||
# If rbw is missing, the command isn't a plain `git commit`, or `--no-verify` is
|
# rbw missing, not a plain `git commit`, `--no-verify`, or no Ansible content staged → allow.
|
||||||
# used, the action is allowed.
|
# When unsure it errs toward blocking (asking for an unlock is cheap; a deep pre-commit
|
||||||
|
# failure is not).
|
||||||
#
|
#
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
|
|
@ -22,14 +26,25 @@ case "$cmd" in
|
||||||
esac
|
esac
|
||||||
|
|
||||||
command -v rbw >/dev/null 2>&1 || exit 0 # rbw not installed — allow
|
command -v rbw >/dev/null 2>&1 || exit 0 # rbw not installed — allow
|
||||||
|
rbw unlocked >/dev/null 2>&1 && exit 0 # unlocked — allow
|
||||||
|
|
||||||
if rbw unlocked >/dev/null 2>&1; then
|
# rbw is LOCKED. Only block if this commit would run the vault-decrypting ansible-lint
|
||||||
exit 0 # unlocked — allow
|
# hook — i.e. staged content matches its `files:` scope. Mirror that regex exactly.
|
||||||
fi
|
ANSIBLE_RE='^(roles|playbooks|inventories)/.*\.ya?ml$'
|
||||||
|
|
||||||
# rbw present but not unlocked (locked or agent not running) — the commit would
|
cd "${CLAUDE_PROJECT_DIR:-.}" 2>/dev/null || exit 0
|
||||||
# fail in the pre-commit hook, so block early with guidance.
|
files=$(git diff --cached --name-only 2>/dev/null) || exit 0
|
||||||
|
# `git commit -a/--all` also sweeps in modified tracked files that aren't staged yet.
|
||||||
|
# (Substring match — errs toward including them, which only ever over-blocks. Safe.)
|
||||||
|
case " $cmd " in
|
||||||
|
*" -a"*|*"--all"*) files="$files"$'\n'"$(git diff --name-only 2>/dev/null)" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# No Ansible content in the fileset → ansible-lint hook won't run → no vault needed → allow.
|
||||||
|
printf '%s\n' "$files" | grep -Eq "$ANSIBLE_RE" || exit 0
|
||||||
|
|
||||||
|
# Ansible content staged AND rbw locked — the commit would fail deep in pre-commit. Block.
|
||||||
cat <<'JSON'
|
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"}}
|
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"rbw is locked and this commit stages Ansible content — the pre-commit ansible-lint hook needs the vault password to decrypt vault.yml. Run: rbw unlock (docs-/config-only commits are exempt and won't hit this guard.)"}}
|
||||||
JSON
|
JSON
|
||||||
exit 0
|
exit 0
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue