An open standard for capturing the WHY in git history
# Add to your Claude Code skills
git clone https://github.com/berserkdisruptors/contextual-commitsConventional Commits standardised WHAT changed. Contextual Commits add WHY.
A convention for embedding decision traces in git commit bodies. Every commit carries not just the code change, but the intent and reasoning that shaped it — structured as typed action lines that any tool can parse and any agent can query.
No new tools. No infrastructure. Just better commits.
Standard AI commit:
feat(auth): implement Google OAuth provider
Added GoogleAuthProvider class with passport.js integration.
Created callback route handler at /api/auth/callback/google.
Added refresh token logic with offline access scope.
Updated auth middleware to support multiple providers.
This restates what the diff already shows. Zero signal added.
Contextual commit:
feat(auth): implement Google OAuth provider
intent(auth): social login starting with Google, then GitHub and Apple
decision(oauth-library): passport.js over auth0-sdk for multi-provider flexibility
rejected(oauth-library): auth0-sdk — locks into their session model, incompatible with redis store
constraint(callback-routes): must follow /api/auth/callback/:provider pattern per existing convention
constraint(session-store): redis 24h TTL means tokens must refresh within that window
learned(passport-google): requires explicit offline_access scope for refresh tokens
The subject line tells you what. The body tells you why.
AI coding tools are everywhere. Trust in their output isn't. The gap is context.
No comments yet. Be the first to share your thoughts!
Every AI coding session produces three outputs:
Two-thirds of every session's value evaporates when the conversation window closes. Commit history is the one context source every AI coding tool can access out of the box — yet the standard AI-generated commit body restates what the diff shows. What agents can't get from the diff is why an approach was chosen, what constraints shaped it, or what was tried and rejected.
Git tracks branches, diffs, and history. The one thing it doesn't track is reasoning. The commit body has always been available for this.
The agent proposes an approach you already tried and rejected last session — but the reasoning that ruled it out died with the conversation window. It writes a clean implementation that violates a constraint it has no way of knowing about, and discovers it by failing. Three months later, another session sees a pattern in the code that looks arbitrary — it wasn't, but the reason existed in a conversation that no longer exists.
Same problem, three forms. AI coding sessions produce decisions and understanding alongside code, but only the code survives in git.
Several categories of context shape AI coding quality. Most can be reverse-engineered from the codebase: architecture, code patterns, test strategy, naming conventions. An agent that reads your code can figure these out.
Two categories cannot be reverse-engineered: what you intended and what you already tried. Intent and historical context — the decisions made, alternatives rejected, constraints discovered, lessons learned — exist only in human memory and disappearing conversations.
Contextual commits capture exactly these two. Not because they're the most interesting, but because they're the ones that would otherwise be permanently lost.
The first contextual commit saves one future re-exploration. The hundredth means an agent starting a fresh session inherits every decision, rejection, constraint, and learning from every previous session — across every contributor.
This is not documentation you maintain. It's append-only history that accumulates as a side effect of committing code. No files to keep current. No wiki pages to update. No merge conflicts. Just git.
A contextual commit uses the commit body to carry structured context. The subject line is a standard Conventional Commit. The body extends it with typed action lines:
<type>(<scope>): <description>
<action-type>(<scope>): <content>
<action-type>(<scope>): <content>
scope is a human-readable label — the domain area, module, or concept. Use whatever vocabulary is natural in your project: auth, payment-flow, api-contracts, session-store.
| Type | Captures | Example |
|------|----------|---------|
| intent(scope) | What the user wanted and why | intent(notifications): batch emails instead of per-event |
| decision(scope) | What was chosen when alternatives existed | decision(queue): SQS over RabbitMQ for managed scaling |
| rejected(scope) | What was considered and discarded, with reason | rejected(queue): RabbitMQ — requires self-managed infra |
| constraint(scope) | Hard limits that shaped the approach | constraint(api): max 5MB payload, 30s timeout |
| learned(scope) | Discovered facts that prevent future mistakes | learned(stripe): presentment ≠ settlement currency |
Five types. Each captures signal no other type covers. Each is immediately useful to an agent starting a new session.
intent — what the user is trying to achieve. Without it, the agent reverse-engineers purpose from code.decision — what approach was chosen. Without it, the agent doesn't know if a pattern is intentional or accidental.rejected — what was tried and discarded. Without it, the agent re-explores dead ends. The highest-value type.constraint — hard limits on the implementation. Without it, the agent discovers them by failing.learned — API quirks, non-obvious behaviors, documentation gaps. Without it, the agent wastes cycles rediscovering gotchas. Distinct from constraints: constraints are boundaries to enforce, learnings are traps to avoid.git log shows useful context immediately, but the format is optimised for machine consumption.git log --all --grep="rejected(auth" instantly finds every rejected auth approach across the entire history. Simple regex extracts all action lines from any commit range.For the formal specification with numbered rules and ABNF grammar, see SPEC.md.
feat(search): add full-text search to product catalog
intent(search): replace LIKE queries — too slow beyond 50k products
decision(search-engine): Postgres full-text search over Elasticsearch
rejected(search-engine): Elasticsearch — operationally too heavy for current scale, revisit at 500k products
rejected(search-engine): Algolia — cost at scale prohibitive, vendor lock-in concern
constraint(search-index): index rebuild takes ~8 min on full catalog, must run off-peak
Two future re-explorations prevented. Anyone asking "why not Elasticsearch?" gets the answer without a meeting.
feat(exports): generate PDF reports from dashboard data
decision(pdf-engine): Puppeteer over pdfkit — design team needs pixel-perfect HTML/CSS rendering
learned(puppeteer): requires --no-sandbox in Docker; sandbox mode crashes the container silently
learned(puppeteer): must await networkidle0, not load — times out on pages with deferred JS
constraint(exports): PDF generation blocks for ~3s — must run as background job, never inline
Each learned line is two hours someone won't spend rediscovering it.
fix(checkout): prevent duplicate orders on double-click submit
rejected(checkout): debounce — 300ms delay feels broken on slow connections
The fix is obvious from the diff. The only thing worth capturing is why the obvious alternative was ruled out.
Trivial commits — dependency bumps, typo fixes, formatting — need zero action lines. A clean conventional commit subject is always better than invented context.
This convention is a spec for AI coding tools to implement natively.
The goal is for coding agents (Claude Code, OpenCode, Codex, Gemini CLI, and others) and IDEs (Cursor, Windsurf, and others) to follow this convention by default — replacing the noise they currently generate with signal. Every developer benefits when agents stop restating diffs and start preserving reasoning. The problem is universal; the