Run B of manual-push mode (BDR-111). With `gitflow.autopush false`
nothing stops Claude from typing `git push` itself: the `ask` tier is
inert under auto mode. This adds the mechanical block the user chose.
- hooks/push-guard.sh (PreToolUse, matcher Bash|Monitor, timeout 10):
detects a push in the command text (strict, quote-stripped loose and
alias patterns; backslash-newline folded in bash, BSD sed/grep only),
reads gitflow.autopush in the payload cwd and in every literal -C/cd
dir the command names (global config counts outside a repo), denies
with the documented JSON form and a reason that tells the user to run
the command with `!`. Unparseable value = manual (fail closed); once
a push is detected an EXIT trap emits a static deny on any internal
error. jq missing = one stderr warning, allow (sibling-hook policy).
- settings.json: hook wiring; 18 deny entries closing the write forms
of the human-only toggle (any `git … config` spelling, section
removal/rename, `-c`, config env overrides, direct edits of git config
files); one soft_deny on pushing in manual mode in any form, with no
per-turn clearance; the routing-around rule names hook refusals.
- hooks/session-start.sh: `🔒 push : manual (autopush=false) — ! git push`
banner line when the key reads false (padding in bytes).
- lib/tests/push-guard.test.sh: 61 checks (push forms, over-blocks,
invalid value, global key, fail-closed trap, no-jq, wiring, banner).
Known limits are listed in the hook header; the soft_deny rule is the
backstop. Run C (skills that push on their own) and run D (fail-closed
readers everywhere) follow. Do not enable manual mode at work before C.