Every PR needs a description. Every developer hates writing them. The result: PRs with descriptions like βfixed stuffβ or βupdatesβ that tell reviewers nothing.
In this tutorial, weβll build a CLI tool that reads your git diff and generates a structured PR description β summary, list of changes, testing notes, and breaking changes. Run it before opening a PR and paste the output.
What weβre building
$ pr-describe
## Summary
Add rate limiting to the API to prevent abuse. Implements token bucket
algorithm with Redis backend. Configurable per-route limits.
## Changes
- **New**: `src/middleware/rateLimit.js` β Token bucket rate limiter
- **New**: `src/config/rateLimits.js` β Per-route limit configuration
- **Modified**: `src/app.js` β Applied rate limit middleware to all routes
- **Modified**: `package.json` β Added `ioredis` dependency
- **Modified**: `docker-compose.yml` β Added Redis service
## Testing
- Run `npm test` β 3 new tests for rate limiting
- Manual: Hit `/api/users` 101 times in 60 seconds, verify 429 response
- Check Redis: `redis-cli GET ratelimit:127.0.0.1:/api/users`
## Breaking Changes
None
The code
#!/usr/bin/env node
// index.js
import Anthropic from '@anthropic-ai/sdk';
import { execSync } from 'child_process';
const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) { console.error('Set ANTHROPIC_API_KEY'); process.exit(1); }
// Get the diff against main
const baseBranch = process.argv[2] || 'main';
let diff;
try {
diff = execSync(`git diff ${baseBranch}...HEAD`, { encoding: 'utf-8', maxBuffer: 1024 * 1024 });
} catch {
diff = execSync('git diff --cached', { encoding: 'utf-8', maxBuffer: 1024 * 1024 });
}
if (!diff.trim()) { console.log('No changes found.'); process.exit(0); }
// Get commit messages for additional context
const commits = execSync(`git log ${baseBranch}..HEAD --oneline 2>/dev/null || echo "no commits"`, { encoding: 'utf-8' });
// Truncate large diffs
const maxDiff = diff.length > 15000 ? diff.slice(0, 15000) + '\n... (truncated)' : diff;
const anthropic = new Anthropic({ apiKey });
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 1024,
messages: [{
role: 'user',
content: `Generate a GitHub PR description from this git diff.
Commits: ${commits}
Diff:
${maxDiff}
Format:
## Summary
[2-3 sentences explaining WHAT changed and WHY]
## Changes
[Bullet list: New/Modified/Deleted files with brief description]
## Testing
[How to test these changes β commands, manual steps]
## Breaking Changes
[List any, or "None"]
Be specific. Reference actual file names and functions.`
}],
});
console.log(response.content[0].text);
Setup
mkdir pr-describe && cd pr-describe
npm init -y && npm install @anthropic-ai/sdk
# Add "bin": {"pr-describe": "./index.js"} to package.json
chmod +x index.js && npm link
Usage
# Before opening a PR
pr-describe # diff against main
pr-describe develop # diff against develop
# Copy to clipboard (macOS)
pr-describe | pbcopy
# Pipe to a file
pr-describe > pr-description.md
Making it better
- Git hook: Add as a
prepare-commit-msghook to auto-generate on every commit - Template support: Load a custom PR template from
.github/pr-template.md - Jira integration: Extract ticket numbers from branch names and link them
- Cost optimization: Use Haiku for small diffs, Sonnet for large ones
Example output for different change types
Bug fix PR:
## Summary
Fix race condition in WebSocket reconnection that caused duplicate messages
when clients reconnected within 5 seconds of disconnecting.
## Changes
- **Modified**: `src/ws/handler.js` β Added connection dedup check using client ID
- **Modified**: `src/ws/store.js` β Track last disconnect timestamp per client
## Testing
- Run `npm test -- --grep "websocket"` β 2 new tests
- Manual: Open two tabs, disconnect one, reconnect within 3 seconds β verify no duplicate messages
## Breaking Changes
None
Dependency update PR:
## Summary
Upgrade Express from 4.18 to 5.0 and update middleware for compatibility.
## Changes
- **Modified**: `package.json` β Express 4.18.2 β 5.0.1
- **Modified**: `src/app.js` β Updated middleware registration (Express 5 syntax)
- **Deleted**: `src/middleware/bodyParser.js` β Built into Express 5
## Breaking Changes
- Express 5 drops support for `app.del()` β use `app.delete()` instead
- `req.host` no longer includes port number
Total build time: ~20 minutes. API cost: ~$0.01-0.03 per description.
Previous: Build a Local AI Chatbot for Your Docs
Related: Git Cheat Sheet Β· Build Changelog Generator