Diagram

I was trying to push 2,000 files at 11pm. Git said no. Twice.

The second time I was already annoyed — then I realized the thing blocking me was the thing I built.

The Setup

I was consolidating two repos into one. My career workspace lived on GitHub. My homepage lived on GitLab.

I moved everything into the homepage monorepo and ran git push. The pre-commit hook fired. It blocked me.

What It Caught

Block File Finding Severity
1 career/Personal/workspace/week1-2026-07-14-20.md NRIC S********G CRITICAL
2 career/*.md (3 session transcripts) SG phone +65 9XXXXXXX CRITICAL

Both are real PII — my own. Buried in old weekly planning notes and session transcripts. I wrote them months ago and forgot they were there.

The scanner uses two stages. First, ripgrep finds anything that looks like an NRIC. Second, the Mod-11 checksum verifies it. That second stage is why it catches real numbers and ignores hex strings.

Why This Matters

A regex alone would flag every 9-character hex string in my git history. I would have disabled it on day two.

pdpa-sg-clj does not work that way. It parses ripgrep’s NDJSON output, extracts candidates, then runs each through the Singapore ICA Mod-11 algorithm. Only numbers with a valid check letter count.

That is why it stayed quiet for 200 posts and fired exactly when it should. Zero false positives in normal writing. Two true positives when real PII was about to leave the machine.

The Fix Took 30 Seconds

sed -i 's/S8602645G/S********G/g' career/Personal/workspace/*.md
sed -i 's/+6596577152/+65 9XXXXXXX/g' career/*.md
git add career/ && git commit -m "Redacted PII"

Re-scan passed. Push went through as ceed690. No PII ever reached GitLab.

The Dogfooding Loop

Step What happened
Build I wrote the scanner to protect the blog before publishing
Install Added it as a pre-commit hook and a publish gate
Forget Wrote old notes containing my own NRIC and phone number
Catch Scanner blocked the push that would have exposed them
Prove The tool protected its own author from himself

This is the highest form of dogfooding. Not a demo. Not a test fixture. My own data, my own mistake, caught by my own tool, with the same blocking rule that protects every future commit.

What I Changed After

I added the scan to every path that can leak. The commit hook blocks staged files. The publish script scans the entire blog before building. The CI check validates again.

One scanner. Three gates. Same severity model: CRITICAL and high block, medium and low warn.

Build your tools so they can say no to you.