--- 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 1. Examine the diff (staged or working tree) 2. Write the message to a temp file 3. Commit using that file 4. Clean up ```bash # 1. Inspect what's staged (or stageable) git diff --cached # staged changes git diff # unstaged changes (for context) git status --short # overview # 2. Write the message COMMIT_MSG_DIR=$(mktemp -d) cat > "$COMMIT_MSG_DIR/COMMIT_MSG.md" << 'ENDOFMSG' ENDOFMSG # 3. Commit git commit -F "$COMMIT_MSG_DIR/COMMIT_MSG.md" # 4. Clean up 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, 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 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. | Rule | Rationale | |------|-----------| | 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 | Avoids polluting the working tree or repo root with scratch files. | | Imperative mood in summary and body | Convention established by Git itself (`Merge branch ...`, `Revert "..."`). | | No 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. | | No AI/bot attribution trailers | The commit records *what changed*, not *who typed it*. | | Message file is 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 "oneline" the message into `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: 1. **What is the net effect on the system's behaviour?** Lead with this. 2. **Why was this change necessary?** Include if not obvious from the summary. 3. **What alternatives were considered or rejected?** Include if it saves a future reader from retreading the same ground. 4. **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 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>` 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}`. ```