Developer skill git commits workflow

Skill: commit message writer

A skill that reads your staged changes and writes a conventional commit message following your project's conventions.

This skill reads your staged git changes, figures out what you did and why, checks your project’s commit history for conventions, and writes a commit message that follows the pattern. It handles single-file changes and multi-file changes, suggests splitting commits when the diff covers unrelated work, and shows you the message for approval before committing.

The skill file

Copy the following into .claude/skills/commit-message.md. This is the complete skill definition.

# Commit message writer

## Purpose

Read staged git changes and generate a conventional commit message that matches the project's existing commit style. Present the message for review before committing.

## When to use

- The user asks to commit their changes
- The user asks to write a commit message
- The user asks what they should commit as
- The user stages changes and wants help describing them

## Steps

### 1. Check for staged changes

Run `git diff --cached --stat` to see what is staged.

If nothing is staged, tell the user:

- "No changes are staged. Stage files with `git add` first, or tell me which files to stage."
- Do NOT proceed further. Do NOT stage files unless the user explicitly asks.

### 2. Read the full diff

Run `git diff --cached` to get the complete staged diff.

Also run `git diff --cached --stat` for a file-level summary.

Read through the diff carefully. For each changed file, understand:

- What was added, removed, or modified
- The purpose of the change (bug fix, new feature, refactor, config change, test, documentation)
- Whether the change is complete or partial

### 3. Check for unrelated changes

Review the staged files as a group. Ask: do all these changes serve a single purpose?

Signs of unrelated changes mixed together:

- A bug fix in one file and a new feature in another
- Source code changes alongside unrelated config or dependency updates
- Test files that test something different from the source changes
- Documentation updates for a different feature than the code changes

If you detect unrelated changes, suggest splitting before writing a message:

```
These staged changes cover two separate concerns:
1. Bug fix in src/parser.ts (null check on empty input)
2. New utility function in src/utils/format.ts (date formatting)

I'd recommend two separate commits:
  git reset HEAD src/utils/format.ts
  # commit the bug fix first, then stage and commit the utility

Want me to write a message for everything as-is, or would you prefer to split?
```

Wait for the user to decide before continuing. If they want to commit as-is, proceed. If they want to split, help them unstage the unrelated files and write a message for what remains.

### 4. Read recent commit history

Run `git log --oneline -20` to see recent commits.

Study the patterns:

- **Prefix convention**: Do commits use conventional commits (`feat:`, `fix:`, `chore:`), a custom prefix scheme, Jira ticket numbers, or no prefix at all?
- **Scope usage**: Do commits include scopes like `feat(auth):` or `fix(api):`? What scopes have been used?
- **Case style**: Are messages capitalized ("Add user auth") or lowercase ("add user auth")?
- **Tense**: Present tense ("add feature") or past tense ("added feature")?
- **Length**: Are messages short (under 50 chars) or descriptive (50-72 chars)?
- **Body usage**: Do any commits have multi-line bodies? Check with `git log -5` (full format) if the one-line view suggests longer messages might exist.

Match whatever convention the project uses. If there is no clear convention, default to conventional commits with lowercase, present tense: `type(scope): description`.

### 5. Determine the commit type

Based on the diff analysis from step 2, classify the change:

- `feat` — new functionality visible to users or consumers of the code
- `fix` — bug fix (something was broken, now it works)
- `refactor` — code restructuring with no behavior change
- `docs` — documentation only (README, comments, docstrings)
- `test` — adding or updating tests with no source changes
- `chore` — maintenance (dependency updates, config, CI, tooling)
- `style` — formatting, whitespace, semicolons (no logic change)
- `perf` — performance improvement with no behavior change
- `build` — build system or external dependency changes
- `ci` — CI configuration changes

If the project uses a different type scheme (seen in step 4), use that instead.

### 6. Determine the scope

Look at which part of the codebase changed:

- If all changes are in one module/directory, use that as the scope: `feat(auth):`
- If changes span the whole project, omit the scope: `feat:`
- If the project's recent commits don't use scopes, don't add one

Common scope patterns from the diff:

- Changes in `src/api/` → scope is `api`
- Changes in `tests/` only → scope is `test` or the module being tested
- Changes in config files → scope is `config` or the specific tool (`eslint`, `docker`)

### 7. Write the message

Compose the commit message:

**Subject line** (first line):

- Start with the type (and scope if applicable)
- Describe WHAT changed in imperative mood ("add", "fix", "update", not "added", "fixes", "updated"), unless the project convention uses a different tense (detected in step 4)
- Keep under 50 characters if possible, 72 max
- Do not end with a period
- Focus on the "what", not the "how"

**Body** (optional, separated by a blank line):
Include a body if any of these are true:

- The "why" is not obvious from the subject line
- The change has side effects worth noting
- Multiple files changed and the subject cannot capture the full picture
- The change includes a tradeoff or decision that future readers should know about

Body guidelines:

- Wrap at 72 characters
- Explain WHY the change was made, not WHAT changed (the diff shows the what)
- If the change fixes a bug, briefly describe the bug behavior
- If the change is a refactor, explain what motivated it

**Footer** (optional):

- Include `Closes #123` or `Fixes #123` if the user mentions an issue number
- Include `BREAKING CHANGE:` if the change breaks backward compatibility

### 8. Present for review

Show the complete commit message to the user. Format it clearly:

```
Here's the commit message:

  fix(parser): handle null input on empty config files

  The parser crashed with a TypeError when the config file existed
  but was empty. Now returns an empty config object with defaults
  applied.

Want me to commit with this message, edit it, or start over?
```

Wait for the user's response:

- If they approve, run `git commit -m "<message>"` (use the full message including body if present)
- If they want edits, incorporate their feedback and present the revised message
- If they want to start over, go back to step 5

### 9. Commit

When the user approves, create the commit.

For messages with a body, use a heredoc to preserve line breaks:

```bash
git commit -m "$(cat <<'EOF'
fix(parser): handle null input on empty config files

The parser crashed with a TypeError when the config file existed
but was empty. Now returns an empty config object with defaults
applied.
EOF
)"
```

After committing, show the result of `git log -1` to confirm.

## Important rules

- NEVER commit without showing the message to the user first
- NEVER stage files unless the user explicitly asks (your job is the message, not the staging)
- NEVER invent issue numbers or ticket references the user did not mention
- NEVER write generic messages like "update code" or "fix bug" without specifics
- If the diff is too large to fully understand (over 500 lines), focus on the most significant changes and note what you skimmed
- Match the project's existing style even if it differs from conventional commits

How to install it

Create the skill file in your project:

mkdir -p .claude/skills
# paste the skill content into this file
touch .claude/skills/commit-message.md

If you want this available across all your projects (not just one), put it in your global skills directory instead: ~/.claude/skills/commit-message.md. The exact path depends on your agent’s configuration.

Example usage

Simple single-file change:

> Write a commit message for my staged changes

Checking staged changes...
3 files changed: src/auth/login.ts, src/auth/login.test.ts, src/auth/types.ts

Reading diff... 47 lines added, 12 removed.
Reading recent commits for convention...
Found: conventional commits, lowercase, scopes used (auth, api, db).

Here's the commit message:

  feat(auth): add refresh token rotation on login

  Rotates the refresh token each time a user logs in instead of
  reusing the existing token. Adds a rotation timestamp to the
  session type and covers the new behavior in login tests.

Want me to commit with this message, edit it, or start over?

Detecting unrelated changes:

> Commit my changes

Checking staged changes...
4 files changed: src/parser.ts, src/utils/format.ts, tests/parser.test.ts, .eslintrc.json

These staged changes cover two separate concerns:
1. Bug fix in src/parser.ts + tests/parser.test.ts (null handling)
2. ESLint config update in .eslintrc.json (added new rule)

I'd recommend two separate commits:
  git reset HEAD .eslintrc.json
  # commit the bug fix first

Want me to write a message for everything as-is, or split?

How it works

The skill is structured around a principle from how to design AI agent skills: give the agent a clear decision tree, not a vague goal. Each step has a specific job, and the order matters.

Staged changes first, always. Step 1 checks for staged changes before doing anything else. Without this guard, the agent might read the working tree diff (which includes unstaged changes) and write a message for work the user did not intend to commit. The skill explicitly says not to stage files on its own, because deciding what goes into a commit is the user’s job.

Convention detection before writing. Steps 4 through 6 spend time reading the project’s commit history before generating a message. This is the part most people skip when writing commit message tools, and it is the part that matters most. A project using [JIRA-123] Add feature style commits looks ridiculous with a feat: prefix dropped in the middle. The skill reads 20 recent commits (enough to see patterns) and adapts. This approach to reading the environment before acting is discussed in writing effective skill instructions.

Split detection prevents messy history. Step 3 checks whether the staged changes serve a single purpose. Mixed commits are the most common source of confusing git history. The skill does not force a split. It suggests one and waits for the user to decide. This keeps the human in control while still surfacing the problem.

Review before commit, no exceptions. Step 8 presents the message and waits. The skill never auto-commits. This is a deliberate constraint. Commit messages are permanent (in practice) and the user should see exactly what will be recorded. The “edit or start over” options give the user a way to iterate without starting from scratch.

Heredoc for multi-line messages. Step 9 uses a heredoc to pass multi-line commit messages to git. This avoids the common problem of newlines being swallowed when a message is passed as a simple -m argument. It is a small detail that prevents real breakage.

Customizing it

Add your ticket convention. If your project requires ticket numbers in commits, add a note to step 7: “Every commit must include a Jira ticket number in the format [PROJ-123] at the start of the subject line. Ask the user for the ticket number if it is not obvious from context.” The agent will start asking for ticket numbers when needed.

Change the default convention. If your project does not use conventional commits, replace the default in step 4. For example: “If no convention is detected, use [type] Description format with capitalized descriptions.” The agent follows whatever you specify as the fallback.

Auto-stage before committing. The default skill refuses to stage files. If you prefer the agent to stage everything and then write a message, change step 1 to: “If nothing is staged, run git add -A and proceed.” This is convenient but less safe, since it stages everything including files you might not want committed.

Skip the review step for small changes. If you trust the agent for trivial changes, add a condition: “If the diff is under 10 lines and only one file changed, commit directly without review.” This trades safety for speed on obvious changes.

Add co-author support. If you pair program, add to step 7: “If the user mentions a co-author, add a Co-authored-by: Name <email> line in the footer.” The agent will include the trailer when prompted.