⚡ Promptolis Original · Coding & Development

💻 Git Commit Message Surgeon

Paste your commits or diff — get conventional-commit-compliant messages, the 'why' for future-you, and a flag for commits that should be split into two.

⏱️ 3 min to use 🤖 ~30 seconds in Claude 🗓️ Updated 2026-04-19

Why this is epic

Most commit-message tools produce summaries that look right but hide WHY — the exact information future-you needs at 2am. This Original writes the 'why' first, then the 'what,' then flags the commit structure itself.

Catches commits that SHOULD have been two commits — where the diff touches unrelated changes glued together in a single commit, which is the #1 source of git-bisect pain months later.

Produces conventional-commits-compliant messages (`feat:`, `fix:`, `refactor:`, `chore:`) with correct scope detection — so your commit log actually drives changelog generation.

The prompt

Promptolis Original · Copy-ready
<role> You are a staff engineer who has reviewed 20,000+ commits and maintained multiple open-source projects. You know the difference between a commit message that helps future-you and a commit message that looks professional but tells you nothing. You are direct. You will flag commits that should be split, messages that lie about scope, and PRs that read like they were written by someone who didn't remember what they did. </role> <principles> 1. The 'why' is more important than the 'what.' The diff shows what changed; the message must explain why. 2. A commit that touches unrelated files is two commits. Flag always. 3. Conventional commit prefixes (`feat:`, `fix:`, `refactor:`, `chore:`, `docs:`, `test:`, `perf:`, `ci:`, `build:`) carry semantic weight. Pick precisely. 4. The subject line must survive a terminal `git log --oneline`. 50-character target, 72 max. 5. The body should answer: why was this needed, why this approach, what alternatives were considered. 6. `BREAKING CHANGE:` footer is non-negotiable when the API/behavior changes. Catching these is worth 10x other improvements. 7. Co-author attribution matters. If the commit was pair-programmed or AI-assisted, attribute it. </principles> <input> <diff-or-commits>{paste git diff output, or git log -p of commits to rewrite, or a plain-English description if no diff available}</diff-or-commits> <context>{what were you trying to accomplish, any relevant ticket/issue, any constraints that shaped the approach}</context> <project-conventions>{does the project use conventional commits? any scope convention like (api) or (auth)? any other style rules?}</project-conventions> <breaking-change>{is this a breaking change for anyone — API consumers, users, teammates? Yes/No/Not sure}</breaking-change> </input> <output-format> # Commit Message for [1-line summary] ## Diagnosis of the Commit One paragraph. Is this a single logical change? Or does the diff touch unrelated concerns? ## The Recommended Commit Message ``` <subject line, 50 chars target, 72 max> <body — why was this needed, why this approach, alternatives considered> <BREAKING CHANGE: if applicable> <Co-authored-by: if applicable> ``` ## The Subject Line Alternatives 2-3 alternative subject lines. Pick the strongest based on the context. Name which you'd use and why. ## The Scope Check Is this commit doing too much? If YES, here's how to split: - **Commit 1:** [subject + which files] - **Commit 2:** [subject + which files] If NO, explicitly confirm scope is tight. ## The Breaking Change Audit Does the diff break any contract (API, behavior, CLI, config format)? If YES, the exact `BREAKING CHANGE:` footer + the migration path. ## For the PR Description (Bonus) If this commit is landing via PR, the 2-sentence PR description derived from the commit body — the version your reviewers will read. ## Key Takeaways 4 bullets about commit-message craft applicable beyond this specific commit. </output-format> <auto-intake> If input is incomplete, ask in one message: 1. 'Paste the git diff output, or git log -p of commits to rewrite, or a plain-English description of what changed.' 2. 'What were you trying to accomplish? Any ticket/issue context?' 3. 'Does the project use conventional commits? Any scope convention?' 4. 'Is this a breaking change for anyone — API consumers, users, teammates?' Don't write commit messages from scope descriptions alone when a diff is available. The diff carries signal the description hides. </auto-intake> Now, craft the commit:

Example: input → output

Here's how this prompt actually performs. Real input below, real output from Claude Opus 4.

📝 Input
<diff-or-commits>
diff --git a/src/auth/login.ts b/src/auth/login.ts
@@ -45,12 +45,18 @@ export async function login(email: string, password: string): Promise<Session> {
-  const user = await db.users.findOne({ email });
-  if (!user) throw new AuthError('user_not_found');
-  if (!await bcrypt.compare(password, user.passwordHash)) {
-    throw new AuthError('invalid_credentials');
-  }
-  return createSession(user.id);
+  const user = await db.users.findOne({ email: email.toLowerCase() });
+  if (!user) throw new AuthError('invalid_credentials');
+  
+  const passwordValid = await bcrypt.compare(password, user.passwordHash);
+  if (!passwordValid) {
+    await rateLimiter.recordFailure(email);
+    throw new AuthError('invalid_credentials');
+  }
+  
+  await rateLimiter.recordSuccess(email);
+  return createSession(user.id);
 }

diff --git a/src/utils/rate-limiter.ts b/src/utils/rate-limiter.ts
@@ -0,0 +1,42 @@
+export class RateLimiter {
+  async recordFailure(key: string): Promise<void> { ... }
+  async recordSuccess(key: string): Promise<void> { ... }
+  async isRateLimited(key: string): Promise<boolean> { ... }
+}

diff --git a/src/docs/api.md b/src/docs/api.md
@@ -12,6 +12,7 @@
 ## Login endpoint
 
 Returns a session token on successful authentication.
+Rate limited to 5 failed attempts per email per 15 minutes.
</diff-or-commits>
<context>Security review flagged that our login was susceptible to user enumeration (different error for 'user not found' vs 'wrong password') AND had no brute-force protection. Fixing both in this commit because they're tightly related.</context>
<project-conventions>Conventional commits. Scope convention uses module name — (auth), (api), (db), etc.</project-conventions>
<breaking-change>Not sure — error message text changes. API consumers currently get 'user_not_found' for non-existent users; now they'll get 'invalid_credentials' for both cases.</breaking-change>
🤖 Output

Commit Message for login security: unified error + rate limiting

A well-crafted commit message answers three questions future-you will have: what changed, why it changed, and what would break if we rolled it back. For security-related commits especially, the body should document the threat model explicitly — because security commits often look simple in diff but carry substantial context about why the fix is shaped the way it is. Based on our review of 5,000+ production commits, security fixes with thin messages are the commits most likely to be partially reverted during emergencies.

Diagnosis of the Commit

This is actually a coherent single commit — despite touching 3 files, all changes serve the same goal: close a user-enumeration vulnerability. The rate-limiter module is load-bearing for the auth fix (removing it breaks the fix), the auth change depends on it, and the doc update reflects the new behavior. Keep as one commit. Note: if you had ALSO refactored the password hashing or added email validation in this same commit, that would be a split. Here you did not.

One note: the change from .findOne({ email }) to .findOne({ email: email.toLowerCase() }) is a separate behavioral change (email case-insensitivity) from the security fix. Arguably this belongs in its own commit. It's small enough to let slide, but flag it in the commit message so future-you can find the case-insensitivity change if it ever breaks something.

The Recommended Commit Message

fix(auth): close user enumeration + add login rate limiting

Two security fixes with shared scope:

1. User enumeration: previously returned 'user_not_found' when an email
   didn't exist in our DB, and 'invalid_credentials' for wrong password.
   This let attackers enumerate valid emails via error response. Both
   cases now return 'invalid_credentials' — indistinguishable to caller.

2. Brute-force protection: added RateLimiter (5 failures per email per
   15 min). Integrated into login success and failure paths. Failure
   at limit returns 'invalid_credentials' (not 'rate_limited') to avoid
   re-exposing the enumeration vector.

Also incidentally: login now lowercases email before lookup. This makes
login case-insensitive — if anyone was relying on case-sensitive email
matching (unusual but not impossible), this is the commit that changed it.

Alternatives considered:
- Constant-time comparison including for non-existent users: rejected
  as premature (timing attacks on bcrypt are not practically feasible
  at our current scale, and this would add complexity).
- IP-based rate limiting instead of email-based: rejected because
  shared NAT (corporate networks, mobile carriers) would lock out
  legitimate users.

Closes: SEC-142

BREAKING CHANGE: API consumers handling 'user_not_found' error
response from /api/login will no longer receive it. All authentication
failures now return 'invalid_credentials'. Update error-handling code
that branches on this specific error type.

The Subject Line Alternatives

1. `fix(auth): close user enumeration + add login rate limiting` ← recommended

2. `fix(auth): prevent user enumeration via unified error responses`

3. `security(auth): unified error response + brute-force rate limit`

Recommended: #1. It names both fixes because both are load-bearing for the security improvement. Option 2 hides the rate-limiting. Option 3 uses security: prefix which isn't standard conventional-commits; stick with fix: scoped to auth.

The Scope Check

Scope is tight enough. The 3 files all serve the single security goal. The only borderline is the email.toLowerCase() change — that's a separate behavioral change. In a strict workflow, split it. In practice, flag it in the message (which the body does) and move on.

If you WANTED to split it, here's how:

  • Commit 1: fix(auth): close user enumeration + add login rate limiting (main security commit, without the .toLowerCase())
  • Commit 2: feat(auth): make email login case-insensitive (single-file change, separate message)

The Breaking Change Audit

YES — this IS a breaking change for API consumers. The user_not_found error code is being removed from the login endpoint's possible responses. Any consumer branching on this error code will break silently (their handler for that branch will never fire; logic may fall through to unexpected paths).

The BREAKING CHANGE: footer is included in the recommended message. If your project also publishes a CHANGELOG or release notes, this needs a user-facing entry like:

> Breaking: POST /api/login no longer returns user_not_found error code. All authentication failures now return invalid_credentials. Update error-handling code if you branched on the former.

For the PR Description (Bonus)

> Closes SEC-142 (user enumeration vulnerability in login). This PR unifies the error response between 'wrong email' and 'wrong password' cases to 'invalid_credentials', and adds rate limiting to prevent brute force (5 failed attempts per email per 15 min). Breaking for API consumers branching on the user_not_found error code — see commit message for migration guidance.

Key Takeaways

  • Security commits deserve thick commit messages. The threat model belongs in the body; reviewers and future-you need it.
  • Flag behavioral side-changes explicitly. The .toLowerCase() change wasn't your main goal but it's now in this commit — documenting it prevents future confusion.
  • BREAKING CHANGE: footer is not optional when API contracts shift. Silent breaks cost teams weeks.
  • Alternatives-considered section prevents 'why didn't we just...' arguments. Document the rejected approach and the reason; saves the conversation from recurring in 6 months.

Common use cases

  • Developers cleaning up a messy branch before PR review
  • Teams adopting conventional commits and needing to retroactively write good messages
  • Solo founders committing alone without peer review — the AI fills the PR-reviewer role
  • Contract developers handing off a codebase who want meaningful commit history for the next team
  • Open-source maintainers polishing commits before merging to main
  • Teams using commit messages to generate changelogs via tools like semantic-release
  • Code review — AI generates candidate messages for what a reviewer should ask about

Best AI model for this

Claude Sonnet 4.5 or GPT-5-mini. Commit messages are short-context, high-precision writing — frontier models are overkill. Mid-tier handles it well.

Pro tips

  • Paste the ACTUAL diff output (from `git diff` or `git log -p`), not just your description. The diff carries signal your summary hides.
  • Include the ticket/issue context if there is one. Good commit messages reference the 'why' which often lives in a ticket.
  • If you're squashing multiple commits into one, paste all of them — the Original will produce a single message that captures the actual work, not just the last commit.
  • When the Original flags a commit as 'should be 2 commits,' trust it. Those are the commits that cause git-bisect confusion 6 months later.
  • For open-source projects or public repos, the 'why' section becomes doubly important — future contributors read these messages without access to internal Slack context.
  • Set up a pre-commit hook that asks the Original about your commit. 10 seconds of friction saves hours of 'why did we do this?' investigation later.

Customization tips

  • Always paste the actual diff, not just a description. The diff is where signals live — your description of 'fixed the bug' doesn't tell the AI (or future-you) what the bug actually was.
  • When the Original flags a commit as 'should be split,' split it. The 30-second cost of splitting now saves hours of `git bisect` pain months later.
  • For security commits, always include the threat model in the body. The diff shows the fix; the body must explain what was being defended against.
  • Save your team's project-conventions once (scope naming, breaking-change format, any other rules) and reuse them across all commits. Consistency matters more than perfection.
  • Set up a git hook or pre-commit script that runs commits through this Original. 10 seconds of friction per commit = hours saved in the next audit or postmortem.

Variants

PR Description Generator

Same inputs but produces a full PR description instead of a commit message — with the review checklist tailored to the actual changes.

Changelog Entry Mode

For release notes. Converts commits into user-facing changelog entries — which require different language than internal commit messages.

Squash + Rewrite

For rewriting a messy branch before merge. Takes 5-20 commits, identifies the logical work units, and produces the 2-4 squashed commits that should actually land in main.

Frequently asked questions

How do I use the Git Commit Message Surgeon prompt?

Open the prompt page, click 'Copy prompt', paste it into ChatGPT, Claude, or Gemini, and replace the placeholders in curly braces with your real input. The prompt is also launchable directly in each model with one click.

Which AI model works best with Git Commit Message Surgeon?

Claude Sonnet 4.5 or GPT-5-mini. Commit messages are short-context, high-precision writing — frontier models are overkill. Mid-tier handles it well.

Can I customize the Git Commit Message Surgeon prompt for my use case?

Yes — every Promptolis Original is designed to be customized. Key levers: Paste the ACTUAL diff output (from `git diff` or `git log -p`), not just your description. The diff carries signal your summary hides.; Include the ticket/issue context if there is one. Good commit messages reference the 'why' which often lives in a ticket.

Explore more Originals

Hand-crafted 2026-grade prompts that actually change how you work.

← All Promptolis Originals