whyso — What git blame doesn’t tell you

git blame tells you who changed what and when. whyso tells you why.


The problem

When you want to understand why a line of code looks the way it does, your options are git blame and the commit message.

$ git blame internal/handler/page.go
a3f1b2c (parkjunwoo 2026-03-08) func CreatePage(c *gin.Context) {

Who, when, what — all there. Why — nowhere to be found.

You could just write better commit messages, right? Here’s what they actually look like:

fix: update handler
refactor: clean up
wip

Writing good commit messages is a matter of discipline. No matter how strict your team conventions are, not many people think “I should carefully document the context and rationale for this change” while fixing a bug at 3am.

But in the age of AI coding, something is different. The reason for every change is already recorded.


Claude Code session data

Claude Code stores every conversation as JSONL files under ~/.claude/projects/. Each record contains the user message, the AI response, tool_use calls (Write, Edit, Bash), and the parentUuid chain that links them all together.

User: "Start implementing the whyso validate command"
  → AI: Write internal/crosscheck/crosscheck.go
    → AI: Edit internal/crosscheck/crosscheck.go
      → AI: "Initial crosscheck package — validates SSaC↔OpenAPI operationId matching"

Why a file was created, why it was modified — it’s all there in the conversation itself. Richer than any commit message, and you never have to write it intentionally. If you had the conversation, the record is automatic.

whyso parses this data and extracts a per-file change history.


A file’s entire life

file: internal/crosscheck/crosscheck.go
created: 2026-03-08T14:23:01Z
history:
  - timestamp: 2026-03-08T14:23:01Z
    session: 09351222-d7be-41fe-994f-87c2d7067e5d
    user_request: "Start implementing the whyso validate command"
    answer: "Initial crosscheck package — validates SSaC↔OpenAPI operationId matching"
    tool: Write

  - timestamp: 2026-03-09T10:15:33Z
    session: 4e9b4e5e-3a50-43f2-be6e-e5db228ecc3b
    user_request: "Add validation for x-sort columns existing in DDL"
    answer: "Added x-sort/x-filter column → DDL cross-validation"
    tool: Edit

  - timestamp: 2026-03-10T09:41:22Z
    session: b2e43b4f-cb21-4286-975d-1eb9de8a16c0
    user_request: "Add Func spec cross-validation too"
    answer: "Added @call ↔ Func spec argument count/type cross-validation"
    tool: Edit

Why it was created, what requests drove its evolution. The complete life of a single file, laid out.


Why this matters

The missing piece in code review

There’s always that moment in a PR review when you ask yourself: “why was this changed?” If the commit message doesn’t say, you ask the author. If the author doesn’t remember, you re-read the code and try to reconstruct the reasoning.

In an AI coding environment, the answer to that question lives in the session data. whyso surfaces it.

Onboarding

The fastest way for a new team member to understand a codebase is to know why the code became what it is. git log is a chronological list of changes. Documentation is a snapshot of the current state. whyso’s per-file history fills the gap between them — the chain of intent.

Your own memory

Even when working solo, there comes a moment three months later when you have no idea why you wrote something that way. whyso restores the conversation between your past self and the AI. The context you never put in a commit message is right there.


How it relates to git blame

whyso doesn’t replace git blame. It complements it.

git blamewhyso
WhoYes
WhenYesYes
What changedYes (line level)Yes (tool_use level)
Why it changedPartial (commit message)Yes (user request + AI explanation)
Data sourcegit historyClaude Code sessions

If git blame is the “official record” based on commits, whyso is the “work log” based on conversation. The context that never made it into the official record is in the work log.


When the AI reads its own history

The most interesting use case for whyso isn’t humans reading it — it’s the AI reading it.

Claude Code forgets everything when a session ends. Open the same project in a new session, and there’s no trace of why you wrote something yesterday, which options were considered and discarded, or what context was behind the user’s request. The AI reads the code and infers. That’s all it can do.

Now imagine adding one line to CLAUDE.md:

Always run `whyso history <file>` before editing a file. If history exists, read it first.

That one line changes everything:

It knows what not to undo

You’re about to edit a file and something in the code looks suspicious — like a mistake. On its own, the AI might want to “fix” it. But if whyso history says “user explicitly requested this structure, not for performance but for readability,” it leaves it alone.

It doesn’t re-propose rejected options

A previous session had the conversation: “should we go with approach A?” → “no, let’s do B.” A new session has no idea. So it proposes A again. Frustrating. With whyso, it knows which directions have already been evaluated and rejected.

It picks up where the last session left off

“Continue the refactoring from yesterday” currently means reading the diff and inferring. With whyso history, it reads the user’s intent and the AI’s reasoning from the previous session directly. Continuation based on facts, not inference.

The record compounds across sessions

Under this setup, whyso’s history accumulates with every session. The reason for a change made today gets passed to the AI in the next session. The problem of no memory between sessions gets solved at the file level — no separate memory system required.

What whyso records is what the AI did. But the AI doesn’t remember doing it. The record it created is exactly what it needs most. That’s the unique thing that happens when whyso meets AI coding tools.

For humans, whyso is “a tool that automatically leaves good commit messages.” For AI, whyso is memory across sessions.


Usage

Install

go install github.com/park-jun-woo/whyso/cmd/whyso@latest

File history

# Single file
whyso history README.md

# Entire directory
whyso history internal/ --all

# Mirror file structure to output directory
whyso history . --all --output .file-histories/

# JSON format
whyso history README.md --format json

Session list

whyso sessions

How it works

Tracing the parentUuid chain

Every record in the JSONL has a uuid and a parentUuid. Starting from a tool_use that modified a file, tracing the parentUuid chain backwards leads to the original user message that triggered the change.

user message (uuid: A)          ← "Add validation for x-sort columns in DDL"
  → assistant tool_use (parentUuid: A)
    → tool_result (parentUuid: B)
      → assistant Edit (parentUuid: C)  ← reason for this edit = A

Since tool_result messages are interspersed in the chain, filtering for records where type == "user" and content is a string surfaces the real user requests.

Design decisions

Only Write and Edit are tracked. Claude Code’s Write and Edit tool_use calls explicitly include the file path and the change. File manipulation via Bash commands (rm, mv, cp) is detected heuristically, but Write and Edit are the primary targets. Since Claude Code is designed to use Write and Edit for file modifications, these two tools capture the vast majority of changes.

Sub-agents are included. When Claude Code spawns sub-agents for complex tasks, their sessions are stored under the parent session directory. whyso parses these sub-agent sessions to build a complete history.

Incremental updates. When using --output to write to a directory, already-parsed sessions are skipped. Even if you accumulate hundreds of sessions, it doesn’t re-parse everything from scratch each time.

Numbers

Tracking whyso’s own development with whyso:

MetricCount
Sessions17
Write calls660
Edit calls1,415
Bash file operations206
Unique files changed480

17 sessions. Change history extracted for 480 files. Every file recording why it was created and why it changed.


Limitations

  • Claude Code only. Other AI coding tools use different session formats. Currently only Claude Code’s JSONL format is supported.
  • Text files only. Write and Edit tool_use calls target text files. Changes to images and binary files are not tracked.
  • Session data must be local. Session files need to exist at ~/.claude/projects/. Work done on another machine, or sessions that have been deleted, won’t appear.

Summary

In the age of writing code with AI, the reason behind a change no longer has to live only in commit messages. The conversation itself is the record, and per-file change history can be extracted from it automatically.

git blame shows you who changed what and when. whyso shows you why.

Code: github.com/park-jun-woo/whyso