🦄 I shipped a linter that fails your commit if you won't admit AI touched the code, and then did the most predictable thing possible—let nine months of the data sit there untouched while I busied myself with other things.
Then I actually looked at it: nine months of footers piled up in
git log
like a lonely change jar. Every one of them said how much of those commits were mine, but I hadn't ever sat down and actually counted the jar. So I built the thing to count it. 🪙
Nine months of commits, every single one carrying a footer that states how much of it I actually wrote, and I could not have told you the number—not roughly or even within twenty points. It was all sitting in git log
, structured, and enforced on every commit by a hook I built specifically for that purpose. But it was completely inert.
Dropping change in a jar isn't the same as knowing how much money is in it. rai-lint
will block your commit until you write the footer, but then it's done—the pile just sits there, and I never built the thing that adds it up.
Stop playing hide-and-seek with AI in your commits.
A dual-language validation framework that makes AI attribution non-negotiable.
Installation • Quick Start • Required Commit Footers • Documentation
RAI Lint enforces Responsible AI (RAI) attribution in every commit. No more "who wrote this?" moments. No more mystery code. Just honest, trackable AI contributions.
Read the full story: Did AI Erase Attribution? Your Git History Is Missing a Co-Author
%%{init: {'theme':'dark'}}%%
flowchart LR
A[Developer Commits] --> B{Has AI Footer?}
B -->|Yes| C[Commit Accepted ✅]
B -->|No| D[Commit Rejected ❌]
C --> E[Clear AI Attribution]
D --> F[Add Footer & Retry]
Because transparency matters. When AI writes code, everyone should know. This isn't about fear or compliance theater—it's about building trust and maintaining clear audit trails.
🔒… | #
The footers aren't some format I made up, either. They're Git trailers—the same key-value convention Signed-off-by
and Co-authored-by
have used for years, which is exactly why git log
can hand them back without anything having to parse prose. That was my first idea: encode attribution in a shape Git already understands, so something else can read it later.
Turns out everyone had roughly the same idea. The Linux kernel codified Assisted-by: AGENT_NAME:MODEL_VERSION
, Fedora and LLVM recommend the same trailer in their AI contribution policies, and Artsy argued the whole thing out in public in an RFC titled disclose LLM usage in commits or PRs, opened in May 2026 and merged in June. Three projects that don't talk to each other landed on the same trailer.
So rai-lint
isn't a special private dialect—it enforces vocabulary the rest of the industry already uses, and it fails the commit if yours is missing, which is more than a policy document can do.
Now rai-commit-badge
reads them back. It weights every footer in the history by the lines that commit actually changed, then writes the result into a README as a Shields.io badge.
Your git history already knows how much AI wrote. This reads it back.
A GitHub Action that scores RAI attribution footers and publishes a shields.io badge.
That badge is this action, scoring itself.
Getting Started • Configuration • About • Score • Related
1. Mark where the badge belongs in your README.md
:
<!--START_SECTION:rai-badge-->
<!--END_SECTION:rai-badge-->
Tip
If prettier formats your README, wrap the pair in <!-- prettier-ignore-start -->
and <!-- prettier-ignore-end -->
. Prettier adds a blank line after the start marker, which the action then rewrites on the next run — the fences keep the block byte-stable.
2. Add the workflow:
name: RAI Attribution
on:
push:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
score:
runs-on
…The color moves with the number—blue at 33% or less, purple through 66%, and magenta once you clear 67%—so the band reads before the numbers do.
💡
ProTip:Purple is where[landed—66% across 158 commits going back to October. The badge's own repo,]rai-lint
[, sits in the pink band at 88%, which is what two days of mostly-Claude output looks like next to nine months of my own typing.]rai-commit-badge
Assisted-by
and Generated-by
are not the same confession, and a scorer that flattens them into one bit—AI touched this, yes or no—throws away the only interesting thing the convention captured. So every footer is worth something different, the same way a handful of change would be:
| Footer | Declares | Weight |
|---|---|---|
Authored-by |
||
| Zero AI | 0.00 | |
Commit-generated-by |
||
| Trivial AI, no code | 0.05 | |
Assisted-by |
||
| AI helped, human led | 0.25 | |
Co-authored-by |
||
| Roughly 50/50 | 0.50 | |
Generated-by |
||
| Majority AI | 0.90 |
Keeping Co-authored-by
in that scale is a call some of the newer policies would argue with, but I made it anyway: it's already widely accepted. Nothing I do changes that and dropping it would mean I'm scoring a vocabulary nobody actually uses. The cost is real though—the scorer has to know which co-authors are AI, and that list gets unmanageable eventually—but until there's a better system, this one works just fine.
Counting commits tells you nothing, since one commit is a typo fix and the next is a new module, so each commit gets weighted by the lines it actually changed. Lockfiles, dependency trees, build output, and minified assets don't count toward that total—a regenerated lockfile says nothing about who wrote the feature.
The ceiling is 0.90 and it doesn't move. Even on a commit where the model wrote every line, a human still chose to build the thing and at least directed it—even if that human reviews code the way I do. Ninety percent is the highest this system will reasonably attribute to AI, so nothing here ever reads 100%.
💡
ProTip:0.05 covers trivial contributions, an AI-generated commit message being the obvious one. I didn't put it at zero because it isn't the same as writing your own.
A bare percentage doesn't tell you much. 66% of what—everything since the repo was created, back when there were no footers at all and every commit scores as human? That version of the number only ever gets smaller, and it says more about how old the repo is than about how I work now.
So the window opens at the earliest RAI footer, auto-detected, and the date sits right there in the badge: 66% since 2025-10
. In practice that reads as "since we adopted this," which is what it actually means. I stopped at month precision on purpose, because since 2025-10-31
makes people ask what happened on Halloween, and the answer is nothing—that's just the day the first footer went in. A day-precise date claims more accuracy than the input has, and three extra characters on an already-wide badge aren't worth it. You don't lose the detail: the workflow job summary prints the exact window start next to the commit counts.
That table is also where the rest of the story shows up. Of those 158 commits, 114 carry a footer and 44 don't, and every one of those 44 scores as human. Twenty-eight are squashed, so their footer weights got averaged, which the summary says out loud instead of folding it quietly into the number.
Inside that window, a commit with no footer at all counts as human, at weight zero. That's a hole and I know it's a hole—skip the footer and the number drops, and nothing in git distinguishes a commit a human wrote from a commit someone didn't feel like labeling. Erring toward human is still the right default. This tool exists to attribute AI where AI is known, and if I started guessing at the commits I can't account for, the badge would stop being a measurement.
I'm not the only one stuck there. An IETF Internet-Draft, Identity-Attributed Git Commits via Tier-Structured Trailers, goes considerably further than a footer and a hook, splitting attribution across three tiers:
| Trailer | Tier | Can sign |
|---|---|---|
Acted-By: |
||
| Human | Yes | |
Executed-By: |
||
| Bot | Yes, scoped | |
Drafted-With: |
||
| AI instrument | No keys, ever |
An Ed25519 signature over the commit's tree hash rather than its commit hash keeps that attribution intact through rebase and squash, which is genuinely more than I built. Section 11.7 still lands where I did: it names negative-attribution risk and concludes the protocol layer can't close it. The draft gives honest committers a way to say so and stops there.
🦄 Before anyone cites that at me as settled: it's an individual submission, it has no formal standing in the IETF process, and it expires in November 2026. I'm pointing at it because someone else mapped the same territory and reached the same dead end.
You should find out your attribution number changed by reading a diff, not by noticing a commit you didn't make. So nothing here touches your default branch: every run cuts rai-badge--branches--<base>
, force-pushes the rewritten README onto it, and opens a pull request, reusing the one already open if there is one. The branch gets rebuilt from base each time, so that PR holds exactly one commit no matter how many times you push.
Setup is two markers and a workflow. The markers go wherever the badge belongs:
<!--START_SECTION:rai-badge-->
<!--END_SECTION:rai-badge-->
If prettier formats your README, wrap the pair in <!-- prettier-ignore-start -->
and <!-- prettier-ignore-end -->
, since prettier adds a blank line after the start marker that the action rewrites on the next run. The fences keep the block byte-stable.
Then the workflow:
name: RAI Attribution
on:
push:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
score:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: anchildress1/rai-commit-badge@v1
fetch-depth: 0
is required, because the default checkout is shallow and a shallow clone doesn't carry the history the score is made of—the run fails rather than publish a number derived from four commits. Over in Settings → Actions → General → Workflow permissions, Actions also needs permission to create and approve pull requests, or you get a clean score and a 403 where the PR should be.
🦄 Check it out on the Marketplace at
[https://github.com/marketplace/actions/rai-commit-attribution-badge]and leave a star if you found it useful.
Every commit in that history already stated how much of it was mine. All I added was arithmetic.
rai-lint enforces the footer at commit time. rai-commit-badge scores what it collected.
If you've been writing RAI footers, your number already exists whether or not you go looking for it. Mine is 66% across nine months, and I'd rather publish that than pretend I don't know.
Claude wrote most of the scorer, then wrote this footer about the scorer, which under my own weights lands at Generated-by — 0.90, the heaviest coin in the jar. I counted the change. It did the multiplication.