# 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.

### Returns

A commit message string in conventional-commits format: `type(scope): description`, plus an optional body and footer. Also returns structured metadata for downstream skills: the detected type, scope, whether the message describes a breaking change, and the list of staged files. A composing skill (such as a release notes generator) can use the type/scope to group changes without re-parsing the message.

## 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

## When not to use

- Nothing is staged. The skill needs a staged diff to work; if the working tree is dirty but no `git add` has happened, ask the user to stage first.
- The staged changes touch multiple unrelated concerns. The skill flags this in step 3 and asks the user to split the commit. Forcing a single message onto a multi-purpose commit produces something vague and unhelpful.
- The repo has its own commit-message convention that isn't conventional commits (some teams use Linear ticket prefixes or fixed templates). Read `.gitmessage`, `CONTRIBUTING.md`, or the existing `git log` to detect the local style before defaulting to conventional commits.
- The user wants to commit but doesn't want a message generated (some prefer to write their own). Take the cue and stop.

## 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
