Skip to content
← Back to “From changed files to commit messages”
Artifactslash-command

/commit-message — full skill source

The actual SKILL.md file behind /commit-message — the slash-command that generates Conventional Commits from staged files using pks brain commit-plan's reverse-query on the graph.

This artifact backs the main post on /commit-message. The skill is a single markdown file under .claude/skills/commit-message/. The file tree is short:

.claude/
└── skills/
    └── commit-message/
        └── SKILL.md

That's it — no helper scripts, no fixtures. The whole skill is markdown instructions that Claude Code reads at invocation time. The actual pks brain commit-plan call and JSON parsing happens inline in Claude's tool use; the skill just declares the steps, the prompt, the output format, and the rules.

The current source follows below verbatim. It's a living file — likely to grow as future posts surface new rules (the "no nested scroll on primary content" rule, the pseudo-terminal trap, etc. all started here as ad-hoc decisions during writing and got persisted as skill text).

SKILL.md

---
name: commit-message
description: "Generate a Conventional Commit message for currently-staged files, using `pks brain commit-plan` to recover the *why* (the prompts that drove the edits) and `git diff --cached` for the *what*. Print the message, then offer to commit. Use when the user asks to \"write a commit message\", \"commit this\", \"/commit-message\", or wants release-notes-friendly commits over their staged changes."
argument-hint: "(no args — operates on staged files)"
allowed-tools: Bash, Read, Write, AskUserQuestion
---

# /commit-message — Brain-backed Conventional Commit for staged files

Produces a Conventional Commits message where the **subject** is the *what* (one specific user-visible line, suitable for release-notes roll-up) and the **body** is the *why* — synthesised from the user prompts that drove those exact edits, recovered via `pks brain commit-plan`.

## When to use

- User asks "write a commit message", "commit this", or runs `/commit-message`.
- Anything is staged (`git diff --cached --name-only` is non-empty).
- A release pipeline downstream aggregates Conventional Commits (semantic-release / conventional-changelog / Release Drafter). Subjects become the changelog headline list; bodies survive as expandable detail.

Do **not** use this skill for: amending an existing commit (use `git commit --amend` directly), rewriting history, or generating a message for unstaged work (stage first, then run).

## Step 1 — Verify there is something to commit

```bash
git diff --cached --name-only > /tmp/.commit-msg-staged.txt
test -s /tmp/.commit-msg-staged.txt || { echo "Nothing staged."; exit 0; }
```

If empty, stop and tell the user: "Nothing staged. Stage what you want to commit, then run me again."

## Step 2 — Recover the *why* from the brain

```bash
pks brain commit-plan \
  --files-from /tmp/.commit-msg-staged.txt \
  --include-prompts \
  --min-files 1 \
  --format json \
  2>/dev/null \
  | awk '/^{/{flag=1} flag' \
  > /tmp/.commit-msg-plan.json
```

Notes:

- `pks` prints its banner on stdout — the `awk '/^{/{flag=1}flag'` strips everything up to the first `{` so the result is parseable JSON.
- `--min-files 1` ensures even single-file commits get their prompts (the default `--min-files 2` would put them in `ungrouped` without prompts).
- If pks-cli is not installed or the brain has never been ingested, this step yields an empty/error file. That's tolerable — fall back to step 3's diff-only mode.

Parse the JSON. For each group, you get:

- `files[]` — the staged files in this group
- `prompts[]` — `{timestamp, text}` user prompts that drove the edits (max 10)
- `contributing_sessions[]` — session IDs (for traceability, not the message)

If `groups` is empty *and* `ungrouped` is the full staged set, the brain didn't see these files (new files, or current session not yet ingested). Continue without prompts — the diff alone has to carry the message.

## Step 3 — Collect the *what* from the diff

```bash
git diff --cached --stat
git diff --cached
```

Read both. The `--stat` gives you scope (which areas changed, how many lines); the full diff lets you name the actual user-visible change in the subject.

## Step 4 — Compose the message

**Subject line** (1 line, ≤72 chars, ideally ≤50):

- Conventional Commits format: `type(scope): summary`
  - `type` ∈ `feat | fix | chore | refactor | docs | test | perf | build | ci | style`
  - `scope` = the most specific area (e.g. `blog`, `ws-relay`, `brain`, `aspire`, `commit-message`). Omit `()` if it would be vague.
- Summary: imperative mood ("add", "fix", "rename"), no trailing period.
- Pick the **single most user-visible change**. If the staged set spans unrelated concerns, that's a hint the user should have made two commits — surface it (see step 5) rather than smushing them into a vague subject.

**Body** (wrapped at 72 chars, blank line after subject):

- Lead with **why**, drawn from the prompts. Quote nothing verbatim — synthesise the intent in your own words. 2–4 sentences.
- If multiple groups: one short paragraph per group, separated by a blank line.
- Don't restate the file list — that's in the diff.
- Don't include session UUIDs, timestamps, prompt counts, or any "generated by" footer.
- Don't add `Co-Authored-By` unless the user explicitly asks for it.

**Skip the body** if and only if: brain returned no prompts AND the diff is trivial enough that the subject is self-explanatory (e.g. a one-line typo fix).

## Step 5 — Sanity check before showing the message

Before printing, ask yourself:

- Does the subject describe **one** change? If you find yourself writing "and" / "; also" / a comma list of unrelated things, the staged set is mixed. **Stop and tell the user** which files look like they belong to a different commit, and offer to unstage them. Don't ship a vague "various changes" subject.
- Does the body answer "why" — not "what"? If every sentence describes code, you've drifted into restating the diff. Rewrite.
- Are there secrets in the diff? If `git diff --cached` contains anything resembling a token, key, password, or `.env` value, **stop** and warn the user — don't include it in the message, don't commit.

## Step 6 — Present + offer to commit

Print the proposed message in a single fenced block, exactly as it will appear in `git log`. Then ask with `AskUserQuestion`:

- **Commit as-is** → run `git commit -F <tmpfile>`; do **not** add `-a` (only what's staged).
- **Edit first** → write the message to `.git/COMMIT_EDITMSG` and tell the user to run `git commit -e -F .git/COMMIT_EDITMSG` themselves (interactive editor).
- **Cancel** → leave the staged set untouched, exit.

Never commit without confirmation, even if the user previously approved a commit in this session — match the spirit of "shared-state actions require confirmation."

## Output discipline

- One-sentence preamble before the fenced message ("Proposed commit message:"). No headers, no analysis paragraph.
- Do not narrate steps 1–3 to the user; they only need the result and the choice.
- If you fell back to diff-only mode (no brain hits), say so in one line: "(no brain prompts found — body inferred from diff)."

Where it depends on the brain

Step 2 is the only call out to anything external. Everything else is plain git + Claude's own composition. The dependency on pks brain commit-plan is what makes the why recoverable — without it the skill collapses gracefully to step 3's diff-only mode, but the bodies lose their primary signal.

The flip from "the planner is slow" to "the skill is usable" happened mid-session; see the main post for the 200s → 1.3s timeline.