Thanks for considering a contribution to git-hygiene — the local git hooks that strip AI-attribution trailers and catch secrets before they land in your object store. This doc covers dev setup, the testing convention (manual recipes, no formal suite), code style, and PR expectations.
This project is opinionated software. Read the README's "The policy" and "What this is not" sections before opening a PR that changes behavior — contributions that drift from the three held positions will not merge.
.
├── hooks/
│ ├── commit-msg # Strips AI-attribution trailers + OPSEC scan on subject
│ ├── pre-commit # Scans staged files: credential patterns + OPSEC content
│ ├── opsec-scan.sh # Sourceable library: builds $opsec_patterns from .local files
│ └── opsec-patterns.local.example # Template for user's gitignored opsec-patterns.local
├── .github/FUNDING.yml
├── assets/ # README hero/terminal PNGs
├── README.md
├── SECURITY.md
└── SIGNATURE.md # PGP signing key + signature pattern
No build step. The hooks are bash scripts; you edit them in place.
Requires bash, grep, awk, git. gitleaks
is optional — when installed, pre-commit gets ~700 additional secret
detectors. shellcheck is recommended for
local linting.
git clone https://github.com/JordanNewell/git-hygiene.git ~/git-hygiene
# Symlink into your global hooks path so changes to the clone go live immediately
mkdir -p ~/.githooks
ln -s ~/git-hygiene/hooks/commit-msg ~/.githooks/commit-msg
ln -s ~/git-hygiene/hooks/pre-commit ~/.githooks/pre-commit
chmod +x ~/.githooks/*
git config --global core.hooksPath ~/.githooksFor per-repo development without affecting global state, set
core.hooksPath inside a scratch repo instead.
There is no formal test suite (no BATS, no shunit2). Verification is
manual, via the recipes in README.md under Verify. Reproduce them
before sending a PR that touches hook logic.
printf 'Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>\ntest subject\n' > /tmp/msg
git commit -F /tmp/msg --allow-empty # trailer should be stripped from the result
git log -1 --format='%B' # must NOT contain the Co-Authored-By lineFor OPSEC-scan coverage, set up a patterns file first (see README's Optional: OPSEC scan setup), then:
printf 'S100: leak in subject\n' > /tmp/msg
git commit -F /tmp/msg --allow-empty # should FAIL with OPSEC leak messageprintf 'AWS_SECRET_ACCESS_KEY=abcd1234...\n' > /tmp/secret
git add /tmp/secret # should FAIL or warn before allowing the commitIf your change adds a new pattern (a new AI trailer shape, a new credential regex, an OPSEC-baseline addition), add a recipe above to the PR description showing the before/after. "It works on my machine" is not reviewable — paste the terminal output.
If your change adds a whole new scan layer or a new code path through
opsec-scan.sh, consider whether a BATS suite is finally warranted.
Open an issue first to scope it.
- POSIX-compliant bash where feasible. The hooks must run on macOS
bash 3.2 (the system default) and Linux bash 4+/5. Avoid bashisms that
break under
sh. shellcheckclean. Runshellcheck hooks/*.shbefore sending a PR. SC2086 (unquoted variables) and SC2046 (word splitting) are the usual gotchas — quote aggressively.set -uonly, neverset -eorset -euo pipefailinside hook entrypoints. Scanner wrappers (if ! gitleaks ...) misbehave underset -e— flag errors kill the script before the exit-code branch runs. See commit history for the regression this caused.- No external runtime dependencies. The hooks must work with only
bash,grep,awk, andgit. gitleaks is optional and probed viacommand -v. Adding a Python/Node dependency is a hard "no" — open an issue first. - Comments explain why, not what. A comment that restates the grep below it gets deleted in review.
- Pattern word boundaries are added automatically by
opsec-scan.sh—adawon't false-match insidereadable. Don't hand-add\bto your patterns; the library does it.
- All commits must be PGP-signed (Ed25519, fingerprint in
SIGNATURE.md). Configuregit config commit.gpgsign truelocally. - Subject ≤ 72 chars, imperative mood (
Add X,Fix Y). - Conventional-commit prefixes (
feat:,fix:,docs:,chore:) are used in this repo — match them when you can. - Reference the issue number in the body if applicable.
- No
Co-Authored-By: Claudeor any AI-attribution trailer. Ironic, given what this repo does, but also a hard policy. Thecommit-msghook you're contributing to will strip it from your own commit — save it the work.
Open a PR against main. CI is not currently wired (the repo has no
GitHub Actions workflow beyond FUNDING.yml), so review is manual.
Before requesting review:
-
shellcheck hooks/*.shis clean - The
commit-msgrecipe above passes (trailer stripped, OPSEC blocks when configured) - The
pre-commitrecipe above passes (credential blocked) - No new runtime dependencies (gitleaks stays optional)
- README updated if user-visible behavior changed
-
opsec-patterns.local.exampleupdated if you added a new machine-level pattern category
There is no automated release. Hooks are consumed by symlink — once your
PR merges to main, anyone running git pull in their ~/git-hygiene
clone picks up the change immediately. Tag releases are advisory (for
pinning) and cut by a maintainer.
- 🐛 Bugs — include the OS, bash version (
bash --version), the hook that misbehaved (commit-msgorpre-commit), the exact terminal output, and a minimal reproduction. No real secrets — redact before pasting. - ✨ New patterns — for a new AI trailer shape, paste 3+ real examples from commits you've seen in the wild. For a new credential regex, link to the provider's token format docs.
- 📚 Docs — typos, dead links, missing detail.
Do not open a public issue for vulnerabilities in the hooks
themselves (e.g., a bypass that lets a real secret through the
pre-commit regex layer). See SECURITY.md for the
private reporting path.
By contributing, you agree your contributions are licensed under the MIT license.