# Conventional Commits

> Construct a valid Conventional Commits message for this repo before running `git commit`, covering type/scope, the "!" breaking-change marker, description rules, and the required trailing tracker record id. Use whenever writing or reviewing a commit message, or when a commit is rejected by the commit-msg/tracker-commit-msg hooks.

- Skill: `niksavis/conventional-commits` (Agent Skill)
- Install (CLI): `npx skillmds@latest add niksavis/conventional-commits`
- Raw SKILL.md: https://api.skillmd.com/api/skills/niksavis/conventional-commits/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: niksavis (https://skillmd.com/u/niksavis)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/niksavis/conventional-commits

---

<!-- Generated by `basicly skills-build` from skill.yaml. Do not edit; edit the source. -->

# Conventional Commits

## Scope

Owns constructing a single-line commit subject that passes this repo's `commit-msg` and
`tracker-commit-msg` git hooks on the first attempt.

It is not for:

- Deciding whether to commit, what to stage, or how to split a diff (see `tool-git`).
- Creating or managing tracker records (see `work-tracker`); this skill only covers
  referencing an existing id in the message.
- Body/footer content — the hooks here only validate the subject line.

## Inputs

- The change being committed (already staged, or about to be).
- An existing (or just-created) tracker record id — see `work-tracker` to resolve or
  create one.

## Outputs

- A single commit subject line that passes `commit-msg.py` and `tracker-commit-msg.py` on
  the first attempt, with no `--no-verify` needed.

## Format

```text
type(scope)!: description (issue-id[, issue-id...])
```

- `type` — one of: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert.
- `(scope)` — optional, lowercase-kebab-case, e.g. `(basicly)`.
- `!` — optional, immediately before the colon, marks a breaking change.
- `description` — required: **entirely lowercase, not just the first letter** — proper
  nouns and acronyms get lowercased too ("json", "conventional commits", not "JSON",
  "Conventional Commits"). Letters/digits/space/hyphen only — **no underscores** (write
  `test-cli`, not `test_cli`) and no other punctuation. No trailing punctuation, at
  least 3 characters. This means version strings, filenames, and proper nouns
  (`0.2.0`, `AGENTS.md`, `Font Awesome`) can never appear verbatim in the subject —
  dots and uppercase are rejected. Keep the exact form in the free-form commit **body**
  (the hooks validate only the subject line), or reword the subject ("the first
  release", "agents-md", "font awesome"). A rejected commit prints the offending
  character(s) — lowercase them or move them to the body.
- `(issue-id[, issue-id...])` — required by this repo's `tracker-commit-msg` hook: one or
  more record ids the tracker holds, comma-separated in a single trailing parenthetical.
  An id is `<prefix>-<base>` with optional dotted hierarchy levels — e.g. `basicly-q49`,
  `basicly-zrj.8`, `basicly-zrj.4.1` — where `<prefix>` is your repo's configured tracker
  prefix. The dotted forms are how the tracker names child records and are accepted here.
  The hook matches the id **prefix-anchored, by word boundary anywhere in the message**,
  so ordinary hyphenated words in the description are never
  mistaken for ids, and a git-trailer footer (`Refs: basicly-q49`) is recognized too — the
  trailing parenthetical above stays the default.

## Workflow

Default to `basicly commit "<description>"`: it derives the type from the bead's work
class, the scope from the staged paths, and the trailing bead id from the branch's
worktree binding, then commits — so only the description is yours to write, and a
description the charset rules reject is refused with the offending character named
before any commit is attempted. `--body` carries the capitals, dots, and filenames;
`--type`/`--scope`/`--issue` override one derived part; `--dry-run` prints the message
without committing.

Compose the subject by hand only when there is no envelope to derive from — a commit
outside a bead's worktree, or a message the command cannot assemble:

1. Pick the type that matches the change's intent (feat/fix/docs/...).
2. Resolve or create a tracker record first (see `work-tracker`) — never invent an id.
3. Add `!` only when the change is a breaking API/behavior change.
4. Write the description in the imperative, lowercase, no trailing period.
5. Append the issue id(s) as a single trailing parenthetical.
6. Run `git commit -m "..."` — the hooks re-validate; if rejected, read the hook's error
   output rather than guessing at the fix.

## Examples

Valid:

- `feat(basicly): add fragment loader (basicly-idr)`
- `fix: correct sorting order in planner (basicly-abc)`
- `feat(basicly)!: remove deprecated config format (basicly-idr)`
- `fix: correct sorting order (basicly-idr, basicly-abc)`
- `docs: document the update flow (basicly-zrj.8)` — dotted child id is allowed.

Invalid:

- `Fixed the bug` — no type/colon.
- `chore(scope): Message` — description starts uppercase.
- `feat: support Conventional Commits marker` — description has an uppercase proper
  noun mid-string; must be `feat: support conventional commits marker`.
- `fix: fix test_cli mutation bug` — underscore isn't allowed; must be
  `fix: fix test-cli mutation bug`.
- `docs: update AGENTS.md for v0.1.0` — dots and uppercase aren't allowed; must be
  reworded, e.g. `docs: update agents-md for the first release`.
- `feat: add Font Awesome v4 icons` — capitals aren't allowed; lowercase the subject
  (`feat: add font awesome v4 icons`) and keep the exact "Font Awesome v4" in the body.
- `chore: restamp the install to 0.2.0` — the dots aren't allowed; move the version to
  the body, e.g. `chore: restamp the install` with `Restamp to 0.2.0.` in the body.
- `fix: correct sorting order (not an id)` — trailing parenthetical isn't an issue-id list.
- `chore(word description): message` — scope contains a space.

## Guardrails

- This is a stricter-than-spec profile: it enforces the Conventional Commits v1.0.0
  header structure **plus** the house description rules above (lowercase, restricted
  charset, no trailing punctuation). Passing plain CC is necessary but not sufficient.
- The same `commit-msg.py` and `tracker-commit-msg.py` run as your local git hook **and**
  in CI, so a subject that passes locally passes CI — but only if the hooks are actually
  installed. Run `basicly hooks-build` once per clone/worktree — **not** `pre-commit
  install`, which rewrites the pre-push hook without the ledger guard and leaves
  `basicly hooks-check` still naming pre-push as missing (basicly-7owkkz, measured on
  a fresh clone: guard count 0 after `pre-commit install`, 5 after `hooks-build`). An
  uninstalled hook silently lets a bad message through to CI.
- **`git commit` takes the whole index, not the paths you just added.** A file staged
  by an earlier attempt that a hook then refused is still staged, and it rides into
  the next commit under a subject that does not mention it. Read column 1 of
  `git status --short` immediately before committing, or name the paths on the commit
  itself (`git commit -F - -- <paths>`).
- Never bypass the hooks (`--no-verify`) to force a non-conforming message through.
- Never invent a record id the tracker does not hold; the hook builds its id set from
  `.basicly/ledger/events-*.jsonl`.
- Merge commits and `Revert "..."` auto-generated subjects are exempt from this format.

## Enforcement

This format is mechanically enforced by:

- `.basicly/core/hooks/commit-msg.py` — type/scope/`!`/description rules.
- `.basicly/core/hooks/tracker-commit-msg.py` — issue id presence and existence.

This skill exists to get the message right on the first attempt; the hooks are the
actual gate. `basicly commit` (`src/basicly/commit.py`) is the mechanical path to the
same result: it assembles the derivable envelope and applies the description rules
above before invoking `git commit`, so the rules below are what to reason about only
when a message has to be hand-composed.

