Skip to content

feat(synopsis): add AI-generated commit narrative synopses#566

Open
jwiegley wants to merge 8 commits intomainfrom
johnw/synopsis
Open

feat(synopsis): add AI-generated commit narrative synopses#566
jwiegley wants to merge 8 commits intomainfrom
johnw/synopsis

Conversation

@jwiegley
Copy link
Copy Markdown
Contributor

@jwiegley jwiegley commented Feb 20, 2026

Summary

  • Adds a new git ai synopsis command 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
  • Collects three input sources: the Claude Code AI conversation (auto-detected from ~/.claude/projects/), the commit diff, and the commit message, then sends them to the Anthropic Claude API
  • Stores synopses as git notes under refs/notes/ai-synopsis, which can be pushed/pulled alongside the repository

Motivation

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

# Generate a synopsis for the most recent commit
git ai synopsis generate

# Generate for a specific commit, with explicit API key
git ai synopsis generate --commit abc1234 --api-key $KEY

# Generate without conversation context; target shorter output
git ai synopsis generate --no-conversation --length brief

# Preview the prompt that would be sent (no API call)
git ai synopsis generate --dry-run

# Read back a stored synopsis
git ai synopsis show HEAD
git ai synopsis show abc1234

# List all commits with synopses
git ai synopsis list

Implementation Details

Module layout (src/synopsis/):

File Responsibility
types.rs Core structs: Synopsis, SynopsisMetadata, ConversationLog, DiffBundle, SynopsisInput
config.rs SynopsisConfig with defaults from env vars
conversation.rs Claude Code JSONL parser; project-hash derivation; time-window filter
collector.rs Assembles all three input sources; conversation loading is non-fatal
generator.rs Builds the structured prompt; calls Anthropic Messages API via minreq
storage.rs Reads/writes git notes under refs/notes/ai-synopsis
commands.rs generate, show, list CLI subcommands

Conversation 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 .jsonl file 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 to https://api.anthropic.com/v1/messages. Requires ANTHROPIC_API_KEY (or --api-key). Model defaults to claude-opus-4-6, overridable via GIT_AI_SYNOPSIS_MODEL or --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

  • 14 unit tests pass (cargo test --lib synopsis)
  • Project builds cleanly with no errors (cargo build)
  • Manual: git ai synopsis generate on a real repo with Claude Code session
  • Manual: git ai synopsis show HEAD reads back the stored note
  • Manual: git ai synopsis generate --dry-run shows prompt without API call
  • Manual: git ai synopsis generate --no-conversation works without a JSONL file present

🤖 Generated with Claude Code


Open with Devin

@git-ai-cloud-dev
Copy link
Copy Markdown

git-ai-cloud-dev Bot commented Feb 20, 2026

Stats powered by Git AI

🧠 you    █░░░░░░░░░░░░░░░░░░░  2%
🤖 ai     ████████████████████  98%
More stats
  • 0.1 lines generated for every 1 accepted
  • 1 minute waiting for AI
  • Top model: claude:: (1621 accepted lines, 188 generated lines)

AI code tracked with git-ai

@git-ai-cloud
Copy link
Copy Markdown

git-ai-cloud Bot commented Feb 20, 2026

Stats powered by Git AI

🧠 you    ██░░░░░░░░░░░░░░░░░░  11%
🤖 ai     ░░██████████████████  89%
More stats
  • 1.0 lines generated for every 1 accepted
  • 1 minute waiting for AI
  • Top model: claude:: (179 accepted lines, 188 generated lines)

AI code tracked with git-ai

@jwiegley jwiegley marked this pull request as ready for review February 20, 2026 00:30
@jwiegley jwiegley requested a review from svarlamov February 20, 2026 00:30
@jwiegley jwiegley self-assigned this Feb 20, 2026
devin-ai-integration[bot]

This comment was marked as resolved.

@jwiegley jwiegley force-pushed the johnw/synopsis branch 2 times, most recently from da07bd4 to e183217 Compare February 27, 2026 02:05
@git-ai-bot-svarlamov-dev
Copy link
Copy Markdown

Stats powered by Git AI

🧠 you    ███████████████░░░░░  76%
🤖 ai     ░░░░░░░░░░░█████████  45%
More stats
  • 1.0 lines generated for every 1 accepted
  • 4 minutes waiting for AI
  • Top model: claude:: (752 accepted lines, 752 generated lines)

AI code tracked with git-ai

github-advanced-security[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

jwiegley commented Apr 3, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@jwiegley jwiegley force-pushed the johnw/synopsis branch 2 times, most recently from 963c34e to d56d1f9 Compare April 8, 2026 00:39
devin-ai-integration[bot]

This comment was marked as resolved.

jwiegley added a commit that referenced this pull request Apr 9, 2026
Address Devin review comments on PR #566:
- Use current_git_ai_exe() instead of current_exe() to resolve through
  symlinks, preventing dispatch failure when running as the git shim.
- Add #[cfg(windows)] block with CREATE_NO_WINDOW and
  CREATE_NEW_PROCESS_GROUP to prevent console window flash on Windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jwiegley added a commit that referenced this pull request Apr 15, 2026
Address Devin review comments on PR #566:
- Use current_git_ai_exe() instead of current_exe() to resolve through
  symlinks, preventing dispatch failure when running as the git shim.
- Add #[cfg(windows)] block with CREATE_NO_WINDOW and
  CREATE_NEW_PROCESS_GROUP to prevent console window flash on Windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jwiegley added a commit that referenced this pull request Apr 16, 2026
Address Devin review comments on PR #566:
- Use current_git_ai_exe() instead of current_exe() to resolve through
  symlinks, preventing dispatch failure when running as the git shim.
- Add #[cfg(windows)] block with CREATE_NO_WINDOW and
  CREATE_NEW_PROCESS_GROUP to prevent console window flash on Windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jwiegley and others added 4 commits April 22, 2026 11:04
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>
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>
…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>
jwiegley and others added 4 commits April 22, 2026 11:04
Fix six clippy lints that were failing CI across all platforms:
- collapsible_str_replace: use replace(['\\', '/'], "-") array pattern
- collapsible_if: merge nested if-let blocks with &&
- unnecessary_map_or: use is_none_or() instead of map_or(true, ...)
- manual_range_contains: use !(200..300).contains(&status)
- single_char_add_str: use push('\n') instead of push_str("\n")
- manual_div_ceil: use .div_ceil(4) instead of (len + 3) / 4

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Collapse nested if in fetch_hooks.rs (clippy::collapsible_if)
- Fix formatting in fetch_hooks.rs and remove leading blank line in main.rs
- Use floor_char_boundary for safe UTF-8 truncation in conversation.rs
- Remove GIT_AI env var from spawned synopsis background process

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolve symlinks and relative path components before reading the
Claude Code conversation JSONL file. Without canonicalization,
paths containing symlinks or relative segments could fail to
resolve correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address Devin review comments on PR #566:
- Use current_git_ai_exe() instead of current_exe() to resolve through
  symlinks, preventing dispatch failure when running as the git shim.
- Add #[cfg(windows)] block with CREATE_NO_WINDOW and
  CREATE_NEW_PROCESS_GROUP to prevent console window flash on Windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants