Betaβ gitwhy is under active development. Feedback welcome via[GitHub Issues].
Every git commit
already knows what changed. gitwhy remembers why.
It drops a single post-commit hook into your repo. That hook is a passive listener β it catches the environment variables AI tools leave behind ($CLAUDE_CODE_MODEL
, $COPILOT_MODEL
, $AI_AGENT
), parses your branch name for a ticket, reads your commit message for intent, and writes a provenance record to git-notes. All without you typing a single flag.
Two steps. Zero flags. Plain git commit.
ghw init
git commit -m "feat: add login handler" # standard git, nothing special
ghw why HEAD
gitwhy provenance record
βββββββββββββββββββββββββ
schema: gitwhy/v1
target: commit a3f1d8c
by: agent:claude-code
when: 2026-06-25T10:00:00Z
intent: add login handler
origin: spec
context:
ticket: PROJ-42
prompt: unknown
model: claude-sonnet-4-6
branch: feature/PROJ-42-login
That's the entire default workflow. ghw init
once, then never think about it again.
ghw init
installs a single script into .git/hooks/post-commit
. Every time you (or an AI agent) run git commit
, that hook fires automatically and silently captures:
| What it sniffs | How |
|---|---|
| Who / what agent | |
Sniffs $AI_AGENT , $COPILOT_AGENT_MODEL β detects Claude Code, Copilot, Cursor, etc. |
|
| Which model | |
Reads $CLAUDE_CODE_MODEL , $COPILOT_MODEL , $ANTHROPIC_MODEL , $OPENAI_MODEL |
|
| Why (intent) | |
| Pulls the subject line from your commit message | |
| Ticket number | |
Scans git branch for PROJ-123 patterns |
|
| Origin (human vs spec) | |
Infers from conventional commit type (feat β spec , chore β human ) |
|
| Prompt (if AI) | |
Captures $COPILOT_AGENT_PROMPT or $CLAUDE_CODE_PROMPT |
All of these environment variables are ephemeral β they exist only while the AI tool is running and vanish the moment the process exits. The hook intercepts them before they disappear.
Just write a normal commit message. gitwhy parses it without any flags.
git commit -m "feat: add login handler"
intent: add login handler β from commit message description
origin: spec β inferred from "feat" type
ticket: PROJ-42 β parsed from branch feature/PROJ-42-login
model: claude-sonnet-4-6 β detected from $COPILOT_MODEL / $CLAUDE_CODE_MODEL
by: agent:claude-code β detected from $AI_AGENT env var
branch: feature/PROJ-42-login β from git
gitwhy reads conventional commit format and maps it to provenance fields automatically:
| Commit message | intent | origin |
|---|---|---|
feat: add login handler |
||
add login handler |
||
spec |
||
fix: null pointer in auth |
||
null pointer in auth |
||
spec |
||
perf: cache token lookup |
||
cache token lookup |
||
spec |
||
chore: update deps |
||
update deps |
||
human |
||
docs: add API examples |
||
add API examples |
||
human |
||
test: cover edge cases |
||
cover edge cases |
||
human |
||
feat!: breaking auth change |
||
BREAKING: breaking auth change |
||
spec |
Non-conventional messages fall back to LLM summarization (if configured) then "unknown"
.
gitwhy scans the branch name for a PROJECT-123
pattern and sets it as the ticket automatically. No flags needed.
feature/PROJ-42-login β ticket: PROJ-42
fix/AUTH-7-token-null β ticket: AUTH-7
main β ticket: unknown
gitwhy reads environment variables set by AI tools at commit time:
| Tool | Env var read | Captured as |
|---|---|---|
| Claude Code | AI_AGENT=claude-code/... |
|
by: agent:claude-code |
||
| Claude Code | CLAUDE_CODE_MODEL |
|
model: claude-sonnet-4-6 |
||
| GitHub Copilot CLI | COPILOT_AGENT_MODEL or COPILOT_MODEL |
|
by: copilot , model: gpt-4o |
||
| GitHub Copilot CLI | COPILOT_AGENT_PROMPT |
|
prompt: ... |
||
| Any tool | ANTHROPIC_MODEL , OPENAI_MODEL , GITHUB_MODEL , AI_MODEL |
|
model: ... |
If no env var is found, model
falls back to default_model
in .gitwhy/config.yaml
, then "unknown"
.
Set a default model once, and every commit picks it up:
ghw config set default_model claude-sonnet-4-6
git log |
git blame |
gitwhy |
|
|---|---|---|---|
| Shows what changed | β
| β
| β
|
| Shows who changed it | β
| β
| β
|
Shows why it changed |
β | β | β
|
| Captures AI model used | β | β | β
|
| Links ticket/spec | β | β | β
|
| Distinguishes human vs AI | β | β | β
|
Zero-friction (plain git commit ) |
β
| β
| β
|
git log
and git blame
tell you the what and who. gitwhy adds the why, what model, and what spec β the context that matters six months later when you're debugging AI-generated code.
| If you want to... | Run this |
|---|---|
| Set up the hook in a repo | ghw init |
| Check hook health and last capture | ghw status |
| See provenance for a commit | ghw why HEAD |
| Browse annotated history | ghw log --why |
| Export all records | ghw audit export |
| Set default model | ghw config set default_model claude-sonnet-4-6 |
| Toggle LLM summary | ghw config set summary.enabled false |
Tweak behavior in .gitwhy/config.yaml
:
backend: git-notes
auto_capture:
enabled: true
default_by: agent:opencode
summary:
enabled: true
command: llm
mode: filenames
Everything above happens automatically. But if you ever need to override what the hook captured β for an edge case, a CI commit, or a manual annotation β pass flags to ghw commit
:
| Flag | What it does |
|---|---|
--by |
|
Who: human , copilot , agent:<name> |
|
--intent |
|
| Why: one-line description | |
--origin |
|
Source: human , spec , prompt , template , upstream |
|
--ticket |
|
Reference: e.g. Ticket-42 |
|
--spec |
|
| Spec driving the change | |
--spec-hash |
|
| Spec content hash | |
--prompt |
|
| Prompt text (if AI-generated) | |
--model |
|
| Model name (overrides env detection) | |
-m / --message |
|
| Commit message |
ghw commit --by human --intent "harden auth middleware" --ticket SEC-99
Prerequisites: Git, the GitHub CLI (gh
), and optionally Go 1.21+ for building from source.
brew install surajsrivastav/tap/ghw
Download the latest release for your platform from the releases page:
curl -sL https://github.com/surajsrivastav/gitwhy/releases/latest/download/gitwhy_darwin_arm64.tar.gz | tar xz
sudo mv ghw /usr/local/bin/
curl -sL https://github.com/surajsrivastav/gitwhy/releases/latest/download/gitwhy_darwin_amd64.tar.gz | tar xz
sudo mv ghw /usr/local/bin/
curl -sL https://github.com/surajsrivastav/gitwhy/releases/latest/download/gitwhy_linux_amd64.tar.gz | tar xz
sudo mv ghw /usr/local/bin/
curl -sL https://github.com/surajsrivastav/gitwhy/releases/latest/download/gitwhy_linux_arm64.tar.gz | tar xz
sudo mv ghw /usr/local/bin/
curl -sLO https://github.com/surajsrivastav/gitwhy/releases/latest/download/gitwhy_windows_amd64.zip
Expand-Archive gitwhy_windows_amd64.zip -DestinationPath ~\bin
go install github.com/surajsrivastav/gitwhy@latest
git clone https://github.com/surajsrivastav/gitwhy.git
cd gitwhy
make build
sudo mv ghw /usr/local/bin/
curl -sSfL https://raw.githubusercontent.com/surajsrivastav/gitwhy/master/install.sh | sh
cmd/ - CLI commands
pkg/
provenance/ - What a record looks like
config/ - Reading/writing .gitwhy/config.yaml
storage/ - Where records live (git-notes or files)
drift/ - Tracking spec changes over time
audit/ - Reports and exports
passthrough/ - Handing unknown commands to `gh`
make test # run all tests
make coverage # coverage report
make vet # check for issues
MIT β see LICENSE.
Check the PRD for detailed specs, or open an issue on GitHub.