Ubuntu's gitleaks 8.16 package has no git subcommand, so the hook's
"unknown command" exit 1 blocked every commit as a leak. Probe
gitleaks git --help once, fall back to protect --staged; regenerate the
installed hooks. T16c simulates a missing binary with a /usr/bin symlink
farm minus gitleaks instead of a shorter PATH.
Since BDR-095 `start` sets an auto-pushed upstream, so `git branch -d`
checked "merged into origin/<branch>" (always true, the post-commit hook
keeps it in sync) instead of "merged into develop". T22a proves it: an
unmerged feature with its upstream in sync is deleted by `-d` alone.
- `gitflow_delete` is the single delete path (finish + CLI `delete`):
refuses main/develop (rc 6) and any branch that is not an ancestor of
develop or main (rc 5, `gitflow_merged_into_base`, fail closed when
neither base exists), then `-d` as a second layer. CLI `merged`, `hooks`.
- Fourth generated hook `reference-transaction`: in the `prepared` call,
a deletion of refs/heads/main or refs/heads/develop exits 1, whatever
issued it (branch -d/-D, update-ref -d, rename, script, sub-agent).
`git config gitflow.protect false` opts a foreign clone out.
- `GITFLOW_HOOKS` is the one hook list: write/emit/reconcile, T19d and
doctor.sh (`gitflow.sh hooks`) read it. `.githooks/` and `githooks/`
regenerated with the fourth hook.
- settings.json: static deny on hand `git branch -d/--delete/-dr/-rd` and
on renames of main/develop; hard_deny "Branch deletion by hand"; the
Disarming entry covers all four hooks and `gitflow.*` config; the
protected-branches environment line states the rule.
- Doctrine (CLAUDE.global.md gitflow section), gitflow SKILL (`delete`
op, rc 5/6 rows, common mistake), guard-bash spec T8w flips to deny,
SETTINGS.md, README, CHANGELOG.
- Tests: T22 (12) lib guard incl. the premise proof, T23 (11) hook;
T19 covers the fourth hook. 152/154, the 2 failures are the
pre-existing T16a (gitleaks absent on this host).
Global: `make link` generates githooks/ from lib/gitflow.sh and sets git's
global core.hooksPath to ~/.claude/githooks, so every repo on the machine
runs the pre-commit protection and the post-commit / post-merge push, even
one that never ran gitflow init. A repo's own local core.hooksPath still
wins, so hooks/session-start.sh calls `gitflow reconcile-hooks` once per
session and rewrites a .githooks/ that lags the lib (LRN-114 automated);
the pre-commit exemption now covers .githooks/** next to .claude/**.
Per-repo opt-outs for a foreign clone: `git config gitflow.protect false`
(branch model) and `git config gitflow.autopush false` (push). Both, and
the GIT_CONFIG_GLOBAL= / GIT_CONFIG= env bypass, are static deny rules.
`make test` and the two suites that commit on main export
GIT_CONFIG_GLOBAL=/dev/null so the machine's global hooks never fire in
throwaway repos. doctor gains "Git hooks" (global setting, githooks/ equal
to the emitters) and "Scratchpad" (warn when TMPDIR sits on a tmpfs with
usrquota: systemd caps each user at 80% of it, which killed two shells
today, BLK-021). Tests: T18h, T19d, T20 (reconcile), T21 (whitelist and
protect opt-out); this repo's own stale .githooks/ refreshed.