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
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:
- Pick the type that matches the change's intent (feat/fix/docs/...).
- Resolve or create a tracker record first (see
work-tracker) — never invent an id.
- Add
! only when the change is a breaking API/behavior change.
- Write the description in the imperative, lowercase, no trailing period.
- Append the issue id(s) as a single trailing parenthetical.
- 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.
1---2name: conventional-commits3description: 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.4---5<!-- Generated by `basicly skills-build` from skill.yaml. Do not edit; edit the source. -->67# Conventional Commits89## Scope1011Owns constructing a single-line commit subject that passes this repo's `commit-msg` and12`tracker-commit-msg` git hooks on the first attempt.1314It is not for:1516- Deciding whether to commit, what to stage, or how to split a diff (see `tool-git`).17- Creating or managing tracker records (see `work-tracker`); this skill only covers18 referencing an existing id in the message.19- Body/footer content — the hooks here only validate the subject line.2021## Inputs2223- The change being committed (already staged, or about to be).24- An existing (or just-created) tracker record id — see `work-tracker` to resolve or25 create one.2627## Outputs2829- A single commit subject line that passes `commit-msg.py` and `tracker-commit-msg.py` on30 the first attempt, with no `--no-verify` needed.3132## Format3334```text35type(scope)!: description (issue-id[, issue-id...])36```3738- `type` — one of: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert.39- `(scope)` — optional, lowercase-kebab-case, e.g. `(basicly)`.40- `!` — optional, immediately before the colon, marks a breaking change.41- `description` — required: **entirely lowercase, not just the first letter** — proper42 nouns and acronyms get lowercased too ("json", "conventional commits", not "JSON",43 "Conventional Commits"). Letters/digits/space/hyphen only — **no underscores** (write44 `test-cli`, not `test_cli`) and no other punctuation. No trailing punctuation, at45 least 3 characters. This means version strings, filenames, and proper nouns46 (`0.2.0`, `AGENTS.md`, `Font Awesome`) can never appear verbatim in the subject —47 dots and uppercase are rejected. Keep the exact form in the free-form commit **body**48 (the hooks validate only the subject line), or reword the subject ("the first49 release", "agents-md", "font awesome"). A rejected commit prints the offending50 character(s) — lowercase them or move them to the body.51- `(issue-id[, issue-id...])` — required by this repo's `tracker-commit-msg` hook: one or52 more record ids the tracker holds, comma-separated in a single trailing parenthetical.53 An id is `<prefix>-<base>` with optional dotted hierarchy levels — e.g. `basicly-q49`,54 `basicly-zrj.8`, `basicly-zrj.4.1` — where `<prefix>` is your repo's configured tracker55 prefix. The dotted forms are how the tracker names child records and are accepted here.56 The hook matches the id **prefix-anchored, by word boundary anywhere in the message**,57 so ordinary hyphenated words in the description are never58 mistaken for ids, and a git-trailer footer (`Refs: basicly-q49`) is recognized too — the59 trailing parenthetical above stays the default.6061## Workflow6263Default to `basicly commit "<description>"`: it derives the type from the bead's work64class, the scope from the staged paths, and the trailing bead id from the branch's65worktree binding, then commits — so only the description is yours to write, and a66description the charset rules reject is refused with the offending character named67before any commit is attempted. `--body` carries the capitals, dots, and filenames;68`--type`/`--scope`/`--issue` override one derived part; `--dry-run` prints the message69without committing.7071Compose the subject by hand only when there is no envelope to derive from — a commit72outside a bead's worktree, or a message the command cannot assemble:73741. Pick the type that matches the change's intent (feat/fix/docs/...).752. Resolve or create a tracker record first (see `work-tracker`) — never invent an id.763. Add `!` only when the change is a breaking API/behavior change.774. Write the description in the imperative, lowercase, no trailing period.785. Append the issue id(s) as a single trailing parenthetical.796. Run `git commit -m "..."` — the hooks re-validate; if rejected, read the hook's error80 output rather than guessing at the fix.8182## Examples8384Valid:8586- `feat(basicly): add fragment loader (basicly-idr)`87- `fix: correct sorting order in planner (basicly-abc)`88- `feat(basicly)!: remove deprecated config format (basicly-idr)`89- `fix: correct sorting order (basicly-idr, basicly-abc)`90- `docs: document the update flow (basicly-zrj.8)` — dotted child id is allowed.9192Invalid:9394- `Fixed the bug` — no type/colon.95- `chore(scope): Message` — description starts uppercase.96- `feat: support Conventional Commits marker` — description has an uppercase proper97 noun mid-string; must be `feat: support conventional commits marker`.98- `fix: fix test_cli mutation bug` — underscore isn't allowed; must be99 `fix: fix test-cli mutation bug`.100- `docs: update AGENTS.md for v0.1.0` — dots and uppercase aren't allowed; must be101 reworded, e.g. `docs: update agents-md for the first release`.102- `feat: add Font Awesome v4 icons` — capitals aren't allowed; lowercase the subject103 (`feat: add font awesome v4 icons`) and keep the exact "Font Awesome v4" in the body.104- `chore: restamp the install to 0.2.0` — the dots aren't allowed; move the version to105 the body, e.g. `chore: restamp the install` with `Restamp to 0.2.0.` in the body.106- `fix: correct sorting order (not an id)` — trailing parenthetical isn't an issue-id list.107- `chore(word description): message` — scope contains a space.108109## Guardrails110111- This is a stricter-than-spec profile: it enforces the Conventional Commits v1.0.0112 header structure **plus** the house description rules above (lowercase, restricted113 charset, no trailing punctuation). Passing plain CC is necessary but not sufficient.114- The same `commit-msg.py` and `tracker-commit-msg.py` run as your local git hook **and**115 in CI, so a subject that passes locally passes CI — but only if the hooks are actually116 installed. Run `basicly hooks-build` once per clone/worktree — **not** `pre-commit117 install`, which rewrites the pre-push hook without the ledger guard and leaves118 `basicly hooks-check` still naming pre-push as missing (basicly-7owkkz, measured on119 a fresh clone: guard count 0 after `pre-commit install`, 5 after `hooks-build`). An120 uninstalled hook silently lets a bad message through to CI.121- **`git commit` takes the whole index, not the paths you just added.** A file staged122 by an earlier attempt that a hook then refused is still staged, and it rides into123 the next commit under a subject that does not mention it. Read column 1 of124 `git status --short` immediately before committing, or name the paths on the commit125 itself (`git commit -F - -- <paths>`).126- Never bypass the hooks (`--no-verify`) to force a non-conforming message through.127- Never invent a record id the tracker does not hold; the hook builds its id set from128 `.basicly/ledger/events-*.jsonl`.129- Merge commits and `Revert "..."` auto-generated subjects are exempt from this format.130131## Enforcement132133This format is mechanically enforced by:134135- `.basicly/core/hooks/commit-msg.py` — type/scope/`!`/description rules.136- `.basicly/core/hooks/tracker-commit-msg.py` — issue id presence and existence.137138This skill exists to get the message right on the first attempt; the hooks are the139actual gate. `basicly commit` (`src/basicly/commit.py`) is the mechanical path to the140same result: it assembles the derivable envelope and applies the description rules141above before invoking `git commit`, so the rules below are what to reason about only142when a message has to be hand-composed.