feat(synopsis): add AI-generated commit narrative synopses#566
feat(synopsis): add AI-generated commit narrative synopses#566
Conversation
Git commits capture *what* changed (the diff) and a brief *why* (the
commit message), but they lose the rich context of *how the developer
got there*. When AI-assisted tools like Claude Code are used, there is
often a conversational trail — hypotheses explored, approaches debated,
dead ends encountered, design tradeoffs weighed — that evaporates the
moment the commit is made.
This feature adds `git ai synopsis`, an opt-in command that generates a
narrative, blog-article-style document for any commit. Future readers of
the code get the full story: not just the diff, but the thinking behind
it.
Three input sources are collected and sent to the Anthropic Claude API:
1. **AI conversation context** — the Claude Code JSONL session file
for the repository is located automatically under
`~/.claude/projects/<project-hash>/`, parsed, and filtered to
exchanges within a configurable time window (default: 60 min).
Conversation loading is non-fatal; if it fails the synopsis is
generated from the diff and commit message alone.
2. **The diff** — `git show --stat` and `git show -U<N>` are run
against the target commit to produce a stat summary and a unified
diff with expanded context (default: 10 lines). Large diffs are
truncated to stay within the model's context window.
3. **The commit message** — retrieved via `git log -1 --format=%B`.
A structured prompt instructs Claude to write a technical blog post with
six sections: TL;DR, Background and Motivation, The Journey, The
Solution, Key Files Changed, and Reflections. Target length is
configurable (brief / standard / detailed).
The generated synopsis is stored as a git note under
`refs/notes/ai-synopsis`, using the same stdin-piped `git notes add`
pattern already used by the authorship tracking system. Notes can be
pushed and pulled alongside the repository.
```
git ai synopsis generate [--commit <sha>] [--model <m>]
[--api-key <key>] [--length brief|standard|detailed]
[--conversation <path>] [--no-conversation]
[--notes-ref <ref>] [--dry-run]
git ai synopsis show [<commit>] # default: HEAD
git ai synopsis list
```
- `ANTHROPIC_API_KEY` or `GIT_AI_SYNOPSIS_API_KEY` — API key
- `GIT_AI_SYNOPSIS_MODEL` — model override (default: claude-opus-4-6)
- `GIT_AI_SYNOPSIS=1` — enable auto-generation on every commit
```
src/synopsis/
types.rs — Synopsis, SynopsisMetadata, ConversationLog, DiffBundle, ...
config.rs — SynopsisConfig with env-var defaults
conversation.rs — Claude Code JSONL parser and time-window filter
collector.rs — diff, commit message, and conversation collection
generator.rs — Anthropic Messages API call and prompt construction
storage.rs — git notes read/write under refs/notes/ai-synopsis
commands.rs — generate, show, list subcommand handlers
```
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5c38927 to
9998386
Compare
Claude Code stores conversation files at: ~/.claude/projects/-Users-foo-myrepo/<uuid>.jsonl The project hash is derived by replacing `/` with `-`, which produces a leading `-` for absolute Unix paths. The original implementation stripped this leading dash, so `find_claude_code_conversation` would look for `Users-foo-myrepo` instead of `-Users-foo-myrepo` and always come up empty. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| // Truncate to fit within budget | ||
| let remaining = max_chars.saturating_sub(out.len()); | ||
| if remaining > 64 { | ||
| out.push_str(&line[..remaining]); |
There was a problem hiding this comment.
🔴 Byte-index slicing of String panics on multi-byte UTF-8 characters
In render_conversation, the expression &line[..remaining] at line 269 performs byte-index slicing on a String. If remaining falls in the middle of a multi-byte UTF-8 character (e.g., emoji, CJK characters, accented letters in conversation text), Rust will panic at runtime with byte index N is not a char boundary.
Root Cause and Impact
The line variable is built from user conversation text via format!("{}{}", prefix, exchange.text.trim()). Conversation logs from Claude Code sessions can easily contain non-ASCII characters (code comments in other languages, emoji, special symbols, etc.).
The remaining value is computed from max_chars.saturating_sub(out.len()), where both max_chars and out.len() are byte counts. However, when used as &line[..remaining], this byte offset may land inside a byte sequence of a multi-byte character in line, causing a panic.
For example, if line contains "**User**: café\n\n" and remaining is 14, the slice &line[..14] would cut into the middle of the é character (which is 2 bytes in UTF-8), causing a runtime panic.
Impact: Any synopsis generation that involves conversation text with multi-byte UTF-8 characters and hits the truncation path will crash the process.
| out.push_str(&line[..remaining]); | |
| let safe_remaining = line.floor_char_boundary(remaining); | |
| out.push_str(&line[..safe_remaining]); |
Was this helpful? React with 👍 or 👎 to provide feedback.
…kend Three related improvements: **Automation via GIT_AI_SYNOPSIS=1** The post-commit hook now checks GIT_AI_SYNOPSIS. When set, it spawns `git-ai synopsis generate` as a detached background process immediately after the commit lands, so the terminal is not blocked. On Unix the child is moved into its own process group to avoid receiving signals meant for the parent session. **ANTHROPIC_BASE_URL support** The SynopsisConfig now reads the standard ANTHROPIC_BASE_URL env var (the same variable used by the Anthropic SDK and most proxies) as the API base URL override. Previously the only way to change the base URL was to edit source code. **`claude` CLI backend (--via-claude)** A new GenerationBackend::ClaudeCli variant pipes the prompt directly to `claude --print` instead of calling the Anthropic API. This uses Claude Code's existing authentication — no separate API key is needed. Select it with --via-claude on the command line, or set: GIT_AI_SYNOPSIS_BACKEND=claude Usage examples: # Use the claude CLI (no API key required) git ai synopsis generate --via-claude # Use a corporate API gateway ANTHROPIC_BASE_URL=https://my-proxy/anthropic git ai synopsis generate # Auto-generate for every commit (background) GIT_AI_SYNOPSIS=1 GIT_AI_SYNOPSIS_BACKEND=claude git commit -m "..." Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
git ai synopsiscommand that generates blog-article-style narrative descriptions of commits, capturing the full story behind a change — not just the diff, but the thinking and exploration that led to it~/.claude/projects/), the commit diff, and the commit message, then sends them to the Anthropic Claude APIrefs/notes/ai-synopsis, which can be pushed/pulled alongside the repositoryMotivation
Traditional commits record what changed. When working with AI assistants, there's rich context — dead ends explored, tradeoffs weighed, approaches debated — that gets lost at commit time. This feature captures that context as a readable narrative for future readers of the code.
New Commands
Implementation Details
Module layout (
src/synopsis/):types.rsSynopsis,SynopsisMetadata,ConversationLog,DiffBundle,SynopsisInputconfig.rsSynopsisConfigwith defaults from env varsconversation.rscollector.rsgenerator.rsminreqstorage.rsrefs/notes/ai-synopsiscommands.rsgenerate,show,listCLI subcommandsConversation auto-detection: Claude Code stores sessions as JSONL files under
~/.claude/projects/<project-hash>/. The hash is derived by replacing/path separators with-and stripping the leading-. The most recently modified.jsonlfile in that directory is used and filtered to exchanges within the configurable time window (default: 60 min before the last exchange).API call: Uses
minreq(already a project dependency) to POST tohttps://api.anthropic.com/v1/messages. RequiresANTHROPIC_API_KEY(or--api-key). Model defaults toclaude-opus-4-6, overridable viaGIT_AI_SYNOPSIS_MODELor--model.Storage: Synopsis JSON (metadata + markdown content) is stored as a git note via
git notes --ref=ai-synopsis add -f -F -, using the same stdin-piped pattern as the existing authorship tracking system.Test Plan
cargo test --lib synopsis)cargo build)git ai synopsis generateon a real repo with Claude Code sessiongit ai synopsis show HEADreads back the stored notegit ai synopsis generate --dry-runshows prompt without API callgit ai synopsis generate --no-conversationworks without a JSONL file present🤖 Generated with Claude Code