<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lucia Adams</title>
    <description>The latest articles on DEV Community by Lucia Adams (@maledadams).</description>
    <link>https://dev.to/maledadams</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4115800%2F96c77c09-c4ca-476f-8555-d6036c16d5f6.jpg</url>
      <title>DEV Community: Lucia Adams</title>
      <link>https://dev.to/maledadams</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maledadams"/>
    <language>en</language>
    <item>
      <title>I Kept Losing the "Why" Behind AI-Written Code, So I Built git why</title>
      <dc:creator>Lucia Adams</dc:creator>
      <pubDate>Tue, 08 Sep 2026 13:56:21 +0000</pubDate>
      <link>https://dev.to/maledadams/i-kept-losing-the-why-behind-ai-written-code-so-i-built-git-why-51f7</link>
      <guid>https://dev.to/maledadams/i-kept-losing-the-why-behind-ai-written-code-so-i-built-git-why-51f7</guid>
      <description>&lt;p&gt;git blame tells you who changed a line and when. It never tells you why — especially when an AI agent wrote the change. git why is a zero-dependency CLI that stores the reasoning behind a commit as a git trailer, so the "why" survives a clone instead of dying in a chat transcript.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Short version, if you're skimming: &lt;code&gt;git blame&lt;/code&gt; answers "who and when." Nothing answers "why," especially once an AI agent is the one writing your commits. &lt;code&gt;git why&lt;/code&gt; is a small CLI that stores the reasoning as a git trailer — no server, no database, survives a clone — and a matching &lt;code&gt;git why &amp;lt;path&amp;gt;&lt;/code&gt; command reads it back. Repo's at the bottom.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem, honestly
&lt;/h2&gt;

&lt;p&gt;Here's how I build most things these days: I describe what I want to an agent, it writes the code, I review it, and — assuming it's not obviously wrong — it gets committed. Fast. Genuinely fast. I'm not going to pretend otherwise.&lt;/p&gt;

&lt;p&gt;But somewhere in that loop there's a hole, and it took me embarrassingly long to actually name it.&lt;/p&gt;

&lt;p&gt;A commit records &lt;em&gt;what&lt;/em&gt; changed. Git blame will tell you &lt;em&gt;who&lt;/em&gt; did it, down to the timestamp. What neither of them records is &lt;em&gt;why&lt;/em&gt;. Why this approach and not the other one? What did we try first and throw away? What did I actually ask for, in my own words, before the agent turned it into a diff?&lt;/p&gt;

&lt;p&gt;That reasoning exists — for about twenty minutes. Then the chat window closes and it's gone. Not deleted, exactly. Just... unreachable. Buried in a transcript I'm never opening again.&lt;/p&gt;

&lt;p&gt;Three months later I'm staring at some function wondering why it's shaped the way it is, I run &lt;code&gt;git blame&lt;/code&gt;, and the answer comes back: "you, in April." Cool. Thanks. Extremely helpful.&lt;/p&gt;

&lt;p&gt;That's the gap. Not a tooling gap, really — a memory gap. The codebase remembers everything except the one thing I actually need when I come back to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually wanted (a short, stubborn list)
&lt;/h2&gt;

&lt;p&gt;I went in with a few non-negotiables, mostly because I've watched "let's add a system for X" turn into its own maintenance burden more than once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The reasoning lives &lt;strong&gt;attached to the commit itself&lt;/strong&gt; — not in a side database I have to keep in sync&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No service to run.&lt;/strong&gt; If it needs a daemon, I'm not doing it&lt;/li&gt;
&lt;li&gt;Visible in tools I already reach for — &lt;code&gt;git log&lt;/code&gt;, nothing new to learn&lt;/li&gt;
&lt;li&gt;It has to &lt;strong&gt;survive a clone.&lt;/strong&gt; If reasoning doesn't travel with the repo, it isn't really infrastructure, it's just notes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one ruled out basically every "AI decision log" tool I looked at. Pretty ones, too. But if the why lives in a hosted dashboard and the code lives in git, you've just built two sources of truth that will drift apart the moment nobody's watching. And somebody's always not watching.&lt;/p&gt;

&lt;h2&gt;
  
  
  The primitive was already there: git trailers
&lt;/h2&gt;

&lt;p&gt;Turns out git already solved the structured-metadata-on-a-commit problem, ages ago. It's called a &lt;strong&gt;trailer&lt;/strong&gt; — those &lt;code&gt;Key: value&lt;/code&gt; lines you've definitely seen at the bottom of commit messages without thinking about them much: &lt;code&gt;Co-Authored-By:&lt;/code&gt;, &lt;code&gt;Signed-off-by:&lt;/code&gt;, &lt;code&gt;Reviewed-by:&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Git ships tooling for this out of the box. &lt;code&gt;git interpret-trailers&lt;/code&gt; parses them. &lt;code&gt;git commit --trailer "Key: value"&lt;/code&gt; writes them. Nothing exotic.&lt;/p&gt;

&lt;p&gt;And because a trailer is just part of the commit message, it's part of the commit object. It pushes. It clones. It shows up in plain &lt;code&gt;git log&lt;/code&gt; with zero configuration on the other end. No plugin required to &lt;em&gt;read&lt;/em&gt; it — you could &lt;code&gt;git log&lt;/code&gt; and eyeball it manually if you had to.&lt;/p&gt;

&lt;p&gt;So the schema for &lt;code&gt;git why&lt;/code&gt; is just more of the same pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Why&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;why this change exists&lt;/span&gt;
&lt;span class="na"&gt;Why-Prompt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;the instruction that produced it&lt;/span&gt;
&lt;span class="na"&gt;Why-Rationale&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;why this approach, specifically&lt;/span&gt;
&lt;span class="na"&gt;Why-Alternatives&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;what was considered and rejected&lt;/span&gt;
&lt;span class="na"&gt;Why-Agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;model + tool that wrote it&lt;/span&gt;
&lt;span class="na"&gt;Why-Session&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;id grouping commits from one work session&lt;/span&gt;
&lt;span class="na"&gt;Why-Skip&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;this commit deliberately has no reason, and here's why&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;Why-Skip&lt;/code&gt; field matters more than it looks like it should. Not every commit needs a paragraph of justification — bumping a lockfile doesn't. But a tool that can't tell the difference between "no reason given" and "no reason needed" is going to train people to ignore it within a week. I'd rather it stay quiet on trivial stuff and only push when it counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tool itself
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;git why&lt;/code&gt; is one Python file. No dependencies to install, nothing to configure beyond running the setup once. It reads the trailers back out in a format meant for a human glancing at a terminal, not a machine parsing JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git why HEAD
commit a1b2c3d  2026-09-08 14:22  Lucia Adams
Add breed filter to cat-finder

  why       Let &lt;span class="nb"&gt;users &lt;/span&gt;narrow &lt;span class="nb"&gt;cat &lt;/span&gt;results by breed
  | rationale  client-side filter on the already-fetched list
  | rejected   server-side query per breed — extra requests
  | agent      claude-sonnet-5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;git why &amp;lt;path&amp;gt;&lt;/code&gt; gives you the reasoning behind the last commit that actually touched that file — think of it as &lt;code&gt;git blame&lt;/code&gt;'s companion, the part blame was always missing. &lt;code&gt;git why log&lt;/code&gt; gives the full timeline if you want to scroll through the story of a project rather than one file.&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;git why export&lt;/code&gt; dumps every recorded decision as NDJSON, which sounds like a small feature until you need it. "Show me every decision &lt;code&gt;claude-sonnet-5&lt;/code&gt; made last month" stops being a &lt;code&gt;git log | grep&lt;/code&gt; regex nightmare and becomes a one-line &lt;code&gt;jq&lt;/code&gt; query instead.&lt;/p&gt;

&lt;p&gt;For setup, &lt;code&gt;git why init&lt;/code&gt; installs a &lt;code&gt;commit-msg&lt;/code&gt; hook. Important bit: &lt;strong&gt;it doesn't block anything by default.&lt;/strong&gt; It just prints a one-line nudge if a meaningfully sized change landed with no reasoning attached, and leaves it there. If you want it stricter, &lt;code&gt;git why init --enforce&lt;/code&gt; turns on actual blocking — and even then, it's not dumb about it. It skips trivial diffs (lockfile bumps, typo fixes) and it rejects lazy non-answers too, like a reason that's literally just "update" or one that echoes the commit subject back at you word for word.&lt;/p&gt;

&lt;p&gt;Under the hood, writing a record is nothing more than &lt;code&gt;git commit --trailer&lt;/code&gt; with the right keys. &lt;code&gt;git why commit -m "..." -b "..."&lt;/code&gt; is just a shorter way to type the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring it into an agent's workflow
&lt;/h2&gt;

&lt;p&gt;This is really the part I built the tool for. Run &lt;code&gt;git why agent-setup&lt;/code&gt; and it prints a block you drop straight into &lt;code&gt;CLAUDE.md&lt;/code&gt; or &lt;code&gt;.cursorrules&lt;/code&gt;. The instruction it gives the agent, roughly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Commit with &lt;code&gt;git why commit -m "&amp;lt;subject&amp;gt;" -b "&amp;lt;why this change exists&amp;gt;" --prompt "&amp;lt;the request&amp;gt;" --agent "&amp;lt;model&amp;gt;"&lt;/code&gt;. Write the reason yourself, from the conversation — it's intent the user never actually typed into the code. Trivial changes can still use a bare &lt;code&gt;git commit&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's it. The agent writes its own reasoning down at commit time, while it still has the context, instead of me trying to reconstruct it from memory three months later. And if it forgets on something that mattered? The hook says something. Quietly. Once.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is &lt;em&gt;not&lt;/em&gt;, to be clear
&lt;/h2&gt;

&lt;p&gt;It's not a decision-tracking platform. It's not a chat archive, and it's definitely not trying to replace whatever ADR process your team already has for the big stuff. It's a convention plus a small reader, nothing more ambitious than that.&lt;/p&gt;

&lt;p&gt;If the reasoning genuinely doesn't fit inside a commit message — if it needs paragraphs and diagrams and a room full of people nodding — that's not a &lt;code&gt;git why&lt;/code&gt; problem. That's a signal the change itself is too big and probably needed a design doc before a single line got written.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What's the difference between &lt;code&gt;git why&lt;/code&gt; and &lt;code&gt;git blame&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;git blame&lt;/code&gt; tells you who last touched a line and when. &lt;code&gt;git why&lt;/code&gt; tells you &lt;em&gt;why&lt;/em&gt; that change happened — the reasoning, the alternatives considered, and (if an agent wrote it) which model and what prompt produced it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does &lt;code&gt;git why&lt;/code&gt; need a server or database?&lt;/strong&gt;&lt;br&gt;
No. Everything's stored as a git trailer inside the commit message itself, so it lives in the commit object and travels with the repo on every clone and push. There's nothing to host and nothing to sync.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will it block my commits?&lt;/strong&gt;&lt;br&gt;
Not unless you turn it on. &lt;code&gt;git why init&lt;/code&gt; only prints a soft nudge by default. &lt;code&gt;--enforce&lt;/code&gt; is opt-in, and even then it skips trivial diffs and rejects low-effort placeholder reasons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use it with any AI coding agent, not just Claude?&lt;/strong&gt;&lt;br&gt;
Yes — the trailer schema and the hook don't care which model wrote the commit. &lt;code&gt;Why-Agent&lt;/code&gt; is just a string; put whatever tool or model actually did the work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I query decisions across a whole project's history?&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;git why export&lt;/code&gt; dumps every recorded decision as NDJSON, which you can pipe into &lt;code&gt;jq&lt;/code&gt; or load anywhere you'd normally process structured logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;git+https://github.com/maledadams/git-why
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo's &lt;a href="https://github.com/maledadams/git-why" rel="noopener noreferrer"&gt;here&lt;/a&gt;, MIT licensed&lt;/p&gt;

&lt;p&gt;If you've got opinions on the trailer schema — missing fields, different naming, whatever — that's exactly the kind of feedback I want. Open an issue.&lt;/p&gt;

</description>
      <category>git</category>
      <category>ai</category>
      <category>python</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
