| name | commit-message |
| description | Write and apply Git commit messages. Use this skill whenever committing code, staging a commit, writing a commit message, or when the user says "commit", "git commit", "save my changes", or asks to describe what changed. Also trigger when reviewing staged changes before committing. This skill enforces file-based commit messages — never pass messages via `-m`. |
Commit Message Creation
Write commit messages to a temporary file and commit with git commit -F.
Never use git commit -m.
Workflow
- Examine the diff (staged or working tree)
- Write the message to a temp file
- Commit using that file
- Clean up
git diff --cached
git diff
git status --short
COMMIT_MSG_DIR=$(mktemp -d)
cat > "$COMMIT_MSG_DIR/COMMIT_MSG.md" << 'ENDOFMSG'
<summary line>
<body>
ENDOFMSG
git commit -F "$COMMIT_MSG_DIR/COMMIT_MSG.md"
rm -rf "$COMMIT_MSG_DIR"
Message Format
The commit message is a Markdown file with two parts: a summary line and an
optional body separated by a blank line.
Summary Line
- Imperative mood ("Add search endpoint", not "Added search endpoint" or
"Adds search endpoint")
- No conventional-commit prefixes — no
fix(scope):, feat:, chore:, or
any variant
- No trailing full stop
- ≤ 72 characters
- If the commit addresses a GitHub issue, append the issue number in
parentheses at the end:
Enforce rate limiting on public API (#42)
Body
- Wrap at 72 characters
- Write in Markdown — use backticks for identifiers, fenced blocks for code,
and lists where they aid clarity
- Imperative mood throughout ("Remove the fallback", not
"Removed the fallback")
- Explain what changed and why, not how (the diff shows how)
- Reference related issues or pull requests (PRs) where relevant
Attribution
Do not append any attribution line. No "Co-authored-by", "Signed-off-by",
"Generated by", or similar trailer attributing the message to an AI, a bot, or
any non-human agent. The commit message describes the change; it does not
advertise the toolchain.
Hard Rules
These are non-negotiable. Violating any of them means the message is wrong.
- Never use
git commit -m or git commit -m "...".
Multi-line messages are unreadable as shell arguments. The file-based
workflow avoids quoting issues, supports proper formatting, and encourages
thoughtful messages.
- Always write to a
mktemp -d directory.
This avoids polluting the working tree or repo root with scratch files.
- Use imperative mood in summary and body.
This matches the convention established by Git itself (
Merge branch ...,
Revert "...").
- Avoid conventional-commit prefixes.
The summary describes the change in plain English. Prefixes add taxonomy
noise that belongs in labels, changelogs, or CI config, not in the commit
log.
- Do not add AI/bot attribution trailers.
The commit records what changed, not who typed it.
- Write the message file as Markdown.
Markdown enables backtick formatting for identifiers and fenced code blocks
when referencing specific symbols or snippets.
Why a file, not a string
It is tempting to squeeze the message into a single line with git commit -m
or to pipe a heredoc into git commit -F -. Resist. Both approaches reliably
produce embarrassing artefacts: the shell expands backticked code spans as
command substitutions, $variable references get interpolated, nested quotes
break in ways that depend on the outer quoting style, and attempts to embed
literal newlines scatter \n\n through the log.
A here-document with a quoted delimiter (<< 'EOF') avoids some of these,
but still leaves the message buried in a pipeline where it cannot be inspected
before it lands.
Writing to a file in a mktemp -d directory is the boring option. It is also
the correct one. The message is visible on disk before the commit, trivially
reviewable, immune to shell expansion, and cleaned up afterwards. Better to be
correct than to be clever.
Deciding What to Say
Read the diff carefully. Ask:
- What is the net effect on the system's behaviour? Lead with this.
- Why was this change necessary? Include if not obvious from the summary.
- What alternatives were considered or rejected? Include if it saves a
future reader from retreading the same ground.
- Are there non-obvious consequences? Migration steps, deprecations, perf
implications — mention them.
For trivial changes (typo fixes, import reordering), a summary line alone
suffices. Do not pad with filler.
Examples
Single-line (trivial fix)
Fix null dereference in session cleanup
With issue reference
Add request deduplication to webhook handler (#137)
Multi-line (non-trivial change)
Replace hand-rolled least recently used (LRU) cache with `lru-cache` crate
The previous implementation had an off-by-one in eviction that surfaced
under concurrent access. Rather than fix the edge case, switch to a
well-tested upstream crate.
`lru-cache` 0.6 supports `Send + Sync` out of the box, so the
`Arc<Mutex<...>>` wrapper around the old cache is no longer necessary.
Multi-line with issue
Enforce rate limiting on public API endpoints (#42)
Apply a sliding-window rate limiter (100 req/min per API key) to all
routes under `/v1/public`. Exceeding the limit returns `429 Too Many
Requests` with a `Retry-After` header.
Internal endpoints under `/v1/admin` remain unlimited. The limiter
stores counters in Redis, keyed by `rl:{api_key}:{window}`.