~/resources/decisions/pollution-guards-over-governance
// decision · status: active

Pollution Guards Over Governance

If a repo is meant to stay generic, defend it with a pre-commit hook, not a willpower-dependent rule. Mechanical guards beat governance — they don't depend on the author remembering what's allowed at commit time.

Status: active · two repos protected Domain: repo hygiene · automation

The problem

Some repos are meant to stay generic — pattern libraries, R&D vaults, anything you might publish later. Others are full of business specifics — customer data, internal product names, sensitive workflow logic. The two should never mix. Once business names land in a generic repo, the option to publish it is gone, and the cost of cleanup grows with every commit.

The natural defense is a written rule: "don't put business specifics in this repo." Three sessions later, you're heads-down on a problem, you commit a fix that includes one customer name in a comment, and the rule didn't fire. By the time you notice, it's in git history.

The decision

Replace the written rule with a mechanical guard. A pre-commit hook in .git/hooks/pre-commit that does this:

#!/bin/bash
PATTERN='AcmeCorp|InternalProductName|CustomerNameA|customer.example.com'
HITS=$(git diff --cached --name-only -z \
       | xargs -0 grep -lE "$PATTERN" 2>/dev/null \
       | grep -v '^decisions/' \
       | grep -v 'README.md')
if [ -n "$HITS" ]; then
  echo "Pollution guard: business names in staged files:"
  echo "$HITS"
  exit 1
fi
exit 0

The hook runs at git commit time. If any staged file contains a forbidden name, the commit fails with a clear message naming the offending files. The author either removes the reference, moves the file to an allowed exception path (decisions/, README.md, etc., where pointers are legitimate), or commits with --no-verify after a deliberate decision.

Why this beats governance

What this rejects

What I'd revisit

The pattern is a regex, which can produce false positives. A name that's also a common English word (rare, but possible) would block legitimate commits. The override is --no-verify, which is the right escape hatch — explicit, single-commit, doesn't disable the guard for future commits.

The hook lives at .git/hooks/pre-commit, which is per-clone, not committed. New clones have to install it. A reinstall recipe lives at the bottom of the README so cloning is one copy-paste away from being protected. If this becomes a team practice rather than a personal one, a tracked scripts/install-hooks.sh would close the loop.