commit
Turn whatever is uncommitted in the working tree into logical, atomic
Conventional Commits — but only the files that belong to this branch. The
non-obvious value is the out-of-scope guard: multi-worktree and multi-agent
setups can leave stray files in the working tree that belong to another branch,
and this skill never sweeps them into a commit.
This skill is the single source of truth for the commit-grouping contract. It is
invoked two ways:
- Standalone (
/commit) — "commit my WIP nicely." It creates the commits on
the current branch and stops. It never pushes, opens a PR, writes a changelog,
or touches Linear.
- Inside a ship flow (e.g.
/send-it) — the commit step that runs before the
lint gate, so the branch is clean before the changelog/PR work begins. The ship
flow owns everything after the commits (lint, changelog, push, PR, Linear);
this skill owns only the staging decision and the commits.
Configuration
One knob lives in config.json beside this skill (a neutral
config.example.json ships as a template):
| Key |
Meaning |
Default |
baseBranch |
The trunk the branch diff is taken against (origin/<baseBranch>), used to compute the merge base for scope classification. |
"main" |
Throughout this document <base> is the baseBranch value.
When a ship flow delegates to this skill (e.g. /send-it), it may direct the
classification against a different base for that run — for instance send-it's
--base for a stacked PR. Honour the base the caller resolves; fall back to
config.json baseBranch only for a standalone /commit run.
Process
git status --porcelain. If clean, there is nothing to commit — say so and
stop.
- Inspect the uncommitted files:
git status --porcelain for the list, git diff and git diff --cached for the hunks.
- Filter for branch relevance. Decide which uncommitted files are in scope:
- Compute the merge base:
git merge-base HEAD origin/<base>.
- Files the branch has already touched directly:
git diff --name-only <merge-base>...HEAD.
- In scope by default: an uncommitted file whose path is in that
branch-touched list.
- Out of scope (uncertain): everything else once the branch has its own
commits. This deliberately includes a file that merely sits in a directory
the branch has touched but is not itself a path the branch changed — a shared
directory is not enough to claim a file. A stray file from another branch or
worktree can easily land in a directory this branch happens to have edited, and
silently sweeping it in is exactly the out-of-scope leak this guard exists to
prevent. Treat directory-only matches as out of scope unless the user
explicitly confirms them.
- Fresh branch (no commits of its own yet): there is no branch-touched list
to diff against, so nothing distinguishes your own work from a stray file left
by another branch or worktree. Do not auto-promote every uncommitted file to
in scope — that is exactly the leak this guard exists to catch. Treat all
uncommitted files as uncertain and have the user confirm which belong before
staging any of them.
- Show the user the staging plan: in-scope files grouped by proposed commit, plus
an explicit list of out-of-scope / uncertain files flagged as "uncertain —
possibly from another branch/worktree." Ask: "Stage in-scope files and create the
commits below? (yes / no / customise)". Uncertain files are never staged
automatically. On a fresh branch the uncertain list is every uncommitted
file (step 3): ask the user to confirm which belong, and treat only the confirmed
files as in scope.
- Group in-scope files into logical atomic commits:
- One commit per coherent unit (a feature, a bug fix, a refactor, a docs change,
a tooling tweak). Don't bundle unrelated edits.
- Use Conventional Commits subjects (
feat:, fix:, chore:, docs:,
refactor:, perf:, test:), with a scope when one is obvious
(feat(commit): …).
- For a breaking change, mark it honestly: a
! after the type/scope
(feat(api)!: …) and/or a BREAKING CHANGE: footer in the body. A ship flow
reads the bump signal back out of these commit messages, so the markers must
be accurate.
- On confirmation, create the commits with
git add <specific files> (never
git add -A) and git commit. Stage only the files named in the plan;
out-of-scope files stay in the working tree, untouched. Pass one -m per block
to add a body or footer beyond the subject — git commit -m "<subject>" -m "<body>", and for a breaking change git commit -m "feat(api)!: <subject>" -m "BREAKING CHANGE: <what changed and the migration>" (or git commit -F <message-file> for a longer body). A bare git commit -m "<subject>" is fine
when no body is needed.
If a pre-commit hook reformats files, the commit still succeeds with the formatted
content.
Commit-message prose
Author commit subjects and bodies in the consuming repo's documented prose
language. Across this estate that is British English (colour, behaviour,
-ise/-yse). This governs prose only — never identifiers, dependency names, or
upstream API field names.
Grouping granularity — per-component splitting is parked
/commit groups by intent (one commit per coherent change), not by component
or package boundaries. Per-component atomic-commit splitting (attributing files to
a package and cutting a commit per package) stays parked (A-374) — no estate
repo does per-component path attribution today. Revisit only if a published
multi-package monorepo doing per-component versioning off squash enters the estate.
Arguments
$ARGUMENTS
1---2name: commit3description: Turn the working tree into logical, atomic Conventional Commits — classify uncommitted files as in-scope vs out-of-scope against the branch's merge base, show a staging plan, and create one commit per coherent unit (type + optional scope + British-English body; `!` / `BREAKING CHANGE:` for breaking changes). Never `git add -A`; files that look like they belong to another branch/worktree are never staged silently. Use when asked to commit uncommitted work, tidy WIP into atomic commits, or as the commit step inside a ship flow (e.g. `/send-it`). It commits only — no push, PR, changelog, or Linear writeback.4license: MIT5---67# commit89Turn whatever is uncommitted in the working tree into **logical, atomic10Conventional Commits** — but only the files that belong to _this_ branch. The11non-obvious value is the **out-of-scope guard**: multi-worktree and multi-agent12setups can leave stray files in the working tree that belong to another branch,13and this skill never sweeps them into a commit.1415This skill is the single source of truth for the commit-grouping contract. It is16invoked two ways:1718- **Standalone** (`/commit`) — "commit my WIP nicely." It creates the commits on19 the current branch and stops. It never pushes, opens a PR, writes a changelog,20 or touches Linear.21- **Inside a ship flow** (e.g. `/send-it`) — the commit step that runs before the22 lint gate, so the branch is clean before the changelog/PR work begins. The ship23 flow owns everything after the commits (lint, changelog, push, PR, Linear);24 this skill owns only the staging decision and the commits.2526## Configuration2728One knob lives in [`config.json`](config.json) beside this skill (a neutral29[`config.example.json`](config.example.json) ships as a template):3031| Key | Meaning | Default |32| --- | --- | --- |33| `baseBranch` | The trunk the branch diff is taken against (`origin/<baseBranch>`), used to compute the merge base for scope classification. | `"main"` |3435Throughout this document `<base>` is the `baseBranch` value.3637When a ship flow delegates to this skill (e.g. `/send-it`), it may direct the38classification against a **different** base for that run — for instance send-it's39`--base` for a stacked PR. Honour the base the caller resolves; fall back to40`config.json` `baseBranch` only for a standalone `/commit` run.4142## Process43441. `git status --porcelain`. If clean, there is nothing to commit — say so and45 stop.462. Inspect the uncommitted files: `git status --porcelain` for the list, `git47 diff` and `git diff --cached` for the hunks.483. **Filter for branch relevance.** Decide which uncommitted files are in scope:49 - Compute the merge base: `git merge-base HEAD origin/<base>`.50 - Files the branch has already touched **directly**: `git diff --name-only51 <merge-base>...HEAD`.52 - **In scope** by default: an uncommitted file whose path is in that53 branch-touched list.54 - **Out of scope** (uncertain): everything else once the branch has its own55 commits. This deliberately includes a file that merely **sits in a directory56 the branch has touched** but is not itself a path the branch changed — a shared57 directory is not enough to claim a file. A stray file from another branch or58 worktree can easily land in a directory this branch happens to have edited, and59 silently sweeping it in is exactly the out-of-scope leak this guard exists to60 prevent. Treat directory-only matches as out of scope unless the user61 explicitly confirms them.62 - **Fresh branch (no commits of its own yet):** there is no branch-touched list63 to diff against, so _nothing_ distinguishes your own work from a stray file left64 by another branch or worktree. Do **not** auto-promote every uncommitted file to65 in scope — that is exactly the leak this guard exists to catch. Treat **all**66 uncommitted files as uncertain and have the user confirm which belong before67 staging any of them.684. Show the user the staging plan: in-scope files grouped by proposed commit, plus69 an explicit list of **out-of-scope / uncertain files** flagged as "uncertain —70 possibly from another branch/worktree." Ask: "Stage in-scope files and create the71 commits below? (yes / no / customise)". Uncertain files are never staged72 automatically. On a **fresh branch** the uncertain list is _every_ uncommitted73 file (step 3): ask the user to confirm which belong, and treat only the confirmed74 files as in scope.755. Group in-scope files into **logical atomic commits**:76 - One commit per coherent unit (a feature, a bug fix, a refactor, a docs change,77 a tooling tweak). Don't bundle unrelated edits.78 - Use Conventional Commits subjects (`feat:`, `fix:`, `chore:`, `docs:`,79 `refactor:`, `perf:`, `test:`), with a scope when one is obvious80 (`feat(commit): …`).81 - For a **breaking change**, mark it honestly: a `!` after the type/scope82 (`feat(api)!: …`) and/or a `BREAKING CHANGE:` footer in the body. A ship flow83 reads the bump signal back out of these commit messages, so the markers must84 be accurate.856. On confirmation, create the commits with `git add <specific files>` (**never**86 `git add -A`) and `git commit`. Stage only the files named in the plan;87 out-of-scope files stay in the working tree, untouched. Pass one `-m` per block88 to add a body or footer beyond the subject — `git commit -m "<subject>" -m89 "<body>"`, and for a breaking change `git commit -m "feat(api)!: <subject>" -m90 "BREAKING CHANGE: <what changed and the migration>"` (or `git commit -F91 <message-file>` for a longer body). A bare `git commit -m "<subject>"` is fine92 when no body is needed.9394If a pre-commit hook reformats files, the commit still succeeds with the formatted95content.9697## Commit-message prose9899Author commit subjects and bodies in the consuming repo's documented prose100language. Across this estate that is **British English** (`colour`, `behaviour`,101`-ise`/`-yse`). This governs prose only — never identifiers, dependency names, or102upstream API field names.103104## Grouping granularity — per-component splitting is parked105106`/commit` groups by **intent** (one commit per coherent change), not by component107or package boundaries. Per-component atomic-commit splitting (attributing files to108a package and cutting a commit per package) stays **parked** (A-374) — no estate109repo does per-component path attribution today. Revisit only if a published110multi-package monorepo doing per-component versioning off squash enters the estate.111112## Arguments113114$ARGUMENTS