πŸ“ Tutorials
Β· 4 min read

Build a CLI Tool That Summarizes Git Diffs With AI


You just finished a feature branch with 47 changed files. Your team lead asks for a summary of what changed. You could spend 20 minutes writing it up β€” or you could run one command and let AI do it.

In this tutorial, we’ll build git-summary, a CLI tool that reads your git diff and generates a clean, human-readable changelog. It takes about 15 minutes to build and you’ll actually use it daily.

What we’re building

$ git-summary

## Changes Summary

### New Features
- Added user authentication with JWT tokens
- Created /api/users endpoint with CRUD operations

### Bug Fixes
- Fixed race condition in WebSocket connection handler
- Resolved null pointer when user profile is incomplete

### Refactoring
- Extracted database queries into repository pattern
- Moved validation logic to middleware

**Files changed:** 12 | **Insertions:** 847 | **Deletions:** 234

Prerequisites

Step 1: Set up the project

mkdir git-summary && cd git-summary
npm init -y
npm install @anthropic-ai/sdk

Add to package.json:

{
  "type": "module",
  "bin": { "git-summary": "./index.js" }
}

Step 2: Build the CLI

Create index.js:

#!/usr/bin/env node
import Anthropic from '@anthropic-ai/sdk';
import { execSync } from 'child_process';

// Get the git diff
function getGitDiff() {
  try {
    // Try staged changes first, then unstaged, then last commit
    let diff = execSync('git diff --cached --stat -p', { encoding: 'utf-8' });
    if (!diff.trim()) {
      diff = execSync('git diff --stat -p', { encoding: 'utf-8' });
    }
    if (!diff.trim()) {
      diff = execSync('git diff HEAD~1 --stat -p', { encoding: 'utf-8' });
    }
    return diff;
  } catch {
    console.error('Not a git repository or no changes found.');
    process.exit(1);
  }
}

// Truncate diff if too long (API has token limits)
function truncateDiff(diff, maxChars = 80000) {
  if (diff.length <= maxChars) return diff;
  return diff.slice(0, maxChars) + '\n\n... (diff truncated for length)';
}

async function summarize() {
  const diff = getGitDiff();
  if (!diff.trim()) {
    console.log('No changes to summarize.');
    return;
  }

  const client = new Anthropic();

  const message = await client.messages.create({
    model: 'claude-sonnet-4-20250514',
    max_tokens: 1024,
    messages: [{
      role: 'user',
      content: `Summarize this git diff into a clean changelog. Group changes into categories like "New Features", "Bug Fixes", "Refactoring", "Configuration", etc. Only include categories that have changes. Use bullet points. Be concise but specific. At the end, include a stats line with files changed, insertions, and deletions.

\`\`\`diff
${truncateDiff(diff)}
\`\`\``
    }],
  });

  console.log(message.content[0].text);
}

summarize();

Step 3: Set your API key

export ANTHROPIC_API_KEY=your-key-here

# Add to your shell profile for persistence
echo 'export ANTHROPIC_API_KEY=your-key-here' >> ~/.zshrc

Step 4: Test it

# Make some changes in a git repo, then:
node index.js

# Or link it globally:
npm link
git-summary

Making it smarter

Compare specific branches

Add branch comparison support by accepting arguments:

const args = process.argv.slice(2);
const target = args[0] || 'HEAD~1';

const diff = execSync(`git diff ${target} --stat -p`, { encoding: 'utf-8' });

Now you can run:

git-summary main          # compare current branch to main
git-summary HEAD~5        # last 5 commits
git-summary v1.2.0        # since a tag

Add a commit message generator

Extend the tool to also suggest a commit message:

async function generateCommitMessage() {
  const diff = execSync('git diff --cached --stat -p', { encoding: 'utf-8' });
  if (!diff.trim()) {
    console.log('No staged changes. Run `git add` first.');
    return;
  }

  const client = new Anthropic();
  const message = await client.messages.create({
    model: 'claude-sonnet-4-20250514',
    max_tokens: 256,
    messages: [{
      role: 'user',
      content: `Write a concise conventional commit message for this diff. Format: type(scope): description. Keep it under 72 characters. Only output the commit message, nothing else.

\`\`\`diff
${truncateDiff(diff)}
\`\`\``
    }],
  });

  console.log(message.content[0].text);
}

// Route based on flag
if (process.argv.includes('--commit')) {
  generateCommitMessage();
} else {
  summarize();
}
git-summary --commit
# Output: feat(auth): add JWT authentication with refresh token rotation

Save summaries to a file

import { writeFileSync } from 'fs';

if (process.argv.includes('--save')) {
  const date = new Date().toISOString().split('T')[0];
  writeFileSync(`changelog-${date}.md`, summary);
  console.log(`Saved to changelog-${date}.md`);
}

Cost

Each summary costs roughly $0.003-0.01 depending on diff size. You could run this 1,000 times for under $10. For most developers, the monthly cost is negligible.

What you learned

  • How to build a CLI tool with Node.js and the bin field in package.json
  • How to use the Anthropic SDK to call Claude’s API
  • How to pipe git output into an AI prompt
  • How to structure prompts for consistent, formatted output

The full code is about 60 lines. You can extend it with PR description generation, release notes, or even automatic CHANGELOG.md updates. The pattern is always the same: get the diff, send it to the API with a clear prompt, format the output.

πŸ“˜