Two defects found by testing the guard against itself:
1. When invoked through the .git/hooks/pre-commit symlink, deriving the repo
root from dirname(BASH_SOURCE)/.. resolved to .git/ instead of the work
tree, so the hook scanned nothing and never blocked. Use
'git rev-parse --show-toplevel' instead.
2. The blanket secrets/ rule rejected secrets/.gitkeep. Replaced with an
explicit allowlist: .gitkeep, README.md, *.example, *.template.
Verified: a staged file containing a Telegram bot token now aborts the commit
and leaves HEAD unchanged.