Workflow Phase 11 — Commit Preparation and Execution. Stages and commits the work. Requires `/grant-commit` first (Git Commit Guard enforces a 15-min ad-hoc consent window; inside a workflow the grant is slug-scoped and one grant covers the whole landing).
Installs into .claude/skills of the current project.
Are you the author of Commit?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/friedbotstudio-commit)
---
name: commit
owner: baseline
description: Workflow Phase 11 — Commit Preparation and Execution. Stages and commits the work. Requires `/grant-commit` first (Git Commit Guard enforces a 15-min ad-hoc consent window; inside a workflow the grant is slug-scoped and one grant covers the whole landing).
argument-hint: "[optional commit message; otherwise drafted from the spec/intake]"
---
Prereq: ALL of `archive` AND `memory-sync` in `completed` (Phase 10.5 archives slug artifacts; Phase 10.6 curates `_pending.md`) AND a valid consent token at `.claude/state/commit_consent` (the Git Commit Guard hook enforces this independently). On any workflow where `memory-sync` is in `exceptions` (e.g. non-git projects auto-except it), this skill SHALL refuse to proceed unless that exception is explicit in `workflow.json`.
**Applicability.** This skill applies only when the project is a git repository. Non-git projects auto-except `commit` at `/triage` time (CLAUDE.md Article IV); the workflow ends after `/archive`.
Steps:
0. **Git-repo precheck.** Run `git rev-parse --is-inside-work-tree 2>/dev/null`. If exit non-zero, exit cleanly with: "Not a git repository — `/commit` is inapplicable. Per CLAUDE.md Article IV, `commit` is auto-excepted on non-git projects; the workflow ended at `/archive`. Persistence outside git is your responsibility." Do not run any subsequent step.
1. **Archive `workflow.json` itself.** This is the final piece of the archival bundle, held back until now so phase-ordering checks worked up through this point. Read `.claude/state/workflow.json` to get the slug, then move the file into the already-existing archive bundle: `docs/archive/<date>/<slug>/workflow.json`. Use plain `mv`, **not `git mv`** — `.claude/state/` is gitignored, so git has no index entry for the source and `git mv` fails with `not under version control`. (Step 2.8 and `/archive` Step 4 both say `git mv`; those move tracked paths, which is the difference.) Use the bundle's `<date>` directory (the one `/archive` created — inspect `docs/archive/` to find the most recent bundle matching the slug).
2. Verify workflow prereq: memory-sync is the final non-commit entry in `completed`; `archive` is the entry immediately before it; no open consent gates remain.
2.5. **Branch topology (Art. VII).** Read `git.workflow_model`. If it resolves to an enforced model (`direct-to-main` / `github-flow`), the commit SHALL land on a branch the model permits — `git_commit_guard` blocks anything else (under `direct-to-main`, commit on a `release_branches` member; do NOT reflexively create a feature branch). If it resolves to `ask` (default), the guard passes on topology grounds and the branch decision is YOURS to surface: ask the user which branch this commit belongs on rather than improvising one. Never create/switch branches to satisfy a model the project did not declare.
2.7. **Stamp closure pre-stage (only when populated).** Read `source_backlog_keys` from the archived copy `docs/archive/<date>/<slug>/workflow.json` (Step 1 moved the live file). If the array is absent OR empty, skip this step (back-compat — unchanged commit behavior). Otherwise run `node .claude/skills/memory-sync/sweep.mjs --mode stamp-closure --memory-dir .claude/memory --backlog-keys <comma-separated keys>`. This writes `status: picked-up` + `superseded-at: <today>` to each named entry BEFORE staging, so the closure stamp rides THIS commit (atomic). The store is shape-aware: on a sharded store the stamp lands in the frontmatter of `.claude/memory/backlog/<key>.md`; on a flat store it lands in the `## <key>` block of `.claude/memory/backlog.md`. `sweep.mjs` is the sole writer to backlog entries — never edit them directly, and never add a commit-SHA provenance note (atomicity forbids a self-referential SHA).
2.8. **Epic fold — close the epic when this commit finishes it (on the `epic-child` and `power` tracks).** Read `workflow.json → track_id`; on any other track, skip this step. BEFORE staging, register the slices this commit closes in `.claude/state/epic/<epic>.json → children[]` via `.claude/skills/commit/epic_close.mjs → registerClosedChildren({rootDir, epic, slices})` — one slice on an `epic-child` (the child's own), every ticket id in `workflow.json → tickets[]` on a `power` batch, which lands them all in one cycle and has no per-slice commit to hang the flip on. The helper leaves an already-closed slice alone, so a superseded slice keeps the reason it closed. A refusal (`ok: false`) names the offending slice; stop and fix it rather than committing an epic record that disagrees with its roadmap rows. This registration flips each child to `status: "committed"` (this is the **pre-commit** flip — it must precede the all-committed check so the close can ride THIS commit; the harness post-commit flip is now only an idempotent backstop). Then invoke `node .claude/skills/commit/epic_close.mjs <epic>` (where `<epic>` is `workflow.json → epic`). The helper no-ops while any declared slice is still uncovered; once this registration covers them all, it `git mv`s the epic's discovery bundle into `docs/archive/<date>/<epic>/` (staged) and merges `closed:true` + `closed_at` into the epic state file. The staged bundle move then rides the SAME commit produced below — no separate consent gate. (Run outside the harness, the same helper is the standalone recovery path: it stages the move and prints a `/grant-commit` + `/commit` prompt.)
3. `git status` + `git diff --stat` to confirm the change set. The diff now includes: production code changes + archive bundle additions + the workflow.json move + (when Step 2.7 stamped) the `backlog.md` closure stamp + (when Step 2.8 closed an epic) the epic discovery-bundle move into `docs/archive/<date>/<epic>/`. Stage named paths explicitly (never `git add -A` / `git add .` — seed.md forbids it). When Step 2.7 stamped, stage the entry paths it reported so the closure rides this commit — `git add .claude/memory/backlog/<key>.md` per stamped key on a sharded store, or `git add .claude/memory/backlog.md` on a flat one. Staging a path that does not exist aborts the add and leaves the closure unstaged, which `git_commit_guard` then hard-blocks.
4. Draft the commit message from the spec + diff. Conventional-style prefix (`feat:` / `fix:` / `refactor:` / `docs:` / `test:`) followed by a 1-line summary and a short body explaining the WHY. The subject line is a fixed-register one-liner — leave it alone. The body is reviewer-facing prose — pass it through `Skill(humanizer)` before step 5 so AI-writing tells (em-dash overuse, rule of three, inflated verbs, vague attributions) get scrubbed. Keep the brief tight: tell humanizer the register is "factual reviewer-facing commit body — describe the diff faithfully, do not invent rationale, preserve any spec quotes verbatim".
4.5. **Closure preflight (only when Step 2.7 stamped).** Write the staged path list with `git diff --cached --name-only > .claude/state/.closure-staged` and the drafted message to a temp file, then run `node .claude/skills/commit/closure-precommit-check.mjs --memory-dir .claude/memory --backlog-keys <comma-separated keys> --staged-file .claude/state/.closure-staged --message-file <message temp>`. Exit 0 → proceed. Exit 1 → ABORT before `git commit`: the JSON report names what is unstamped/unstaged or which `Closes <key>` line is unreconciled; fix it and retry. This is the friendly pre-guard check — `git_commit_guard` independently re-verifies the same atomicity at commit time and will hard-block a split.
4.7. **Power-track batch split (only when `track_id == "power"`).** Read `track_id` from the archived `docs/archive/<date>/<slug>/workflow.json` (Step 1 moved the live file); if it is not `power`, skip this step and continue to Step 5 (one commit). Otherwise do NOT write a single commit. Parse the dirty tree yourself — `git status --porcelain`, one entry per line as `{path, status}` — and pass that array to `planCommits(entries)` from `.claude/skills/power/commit-split.mjs`:
```bash
node .claude/skills/power/commit-split.mjs plan --json # wraps commit-split.mjs -> planCommits
```
`planCommits` composes `commit-planner`'s `groupDirtyTree` internally for the single-concern grouping and adds the power-specific ordering; it takes the dirty-tree array directly and exposes no CLI of its own. Commit each returned group **in order** (build/config → implementation → tests → docs), staging that group's named paths explicitly. The closing `workflow.json` + `backlog.md` closure stamp lands on the **final commit** — `git_commit_guard` hard-blocks a closure split across commits. Every commit in the series rides the single workflow-scoped `/grant-commit` resolved by `.claude/hooks/lib/consent-decision.mjs`: one grant authorizes this workflow's whole landing, and only this workflow's. Per-commit reviewability (bisect / revert) is preserved even though the cycle is large.
5. Run `git commit` with the message via HEREDOC. The Git Commit Guard hook will verify consent AND the closure obligation (a staged closing `workflow.json` requires the staged `backlog.md` stamps in the same commit). If consent is missing/expired, stop and ask the user to run `/grant-commit`.
6. **Post-commit clean-tree report (replaces the old post-commit stamp).** Run `git status --porcelain`. Because Step 2.7 stamped and Step 3 staged the entry paths, the closure stamp is now IN the commit — expect a clean tree. Report `closure committed in <SHA>; tree clean` alongside the commit SHA, plus the stamped key count. If any stamped backlog entry is still dirty, surface `WARN: closure residue left uncommitted <path>` — the stamp did not ride the commit and must be investigated (it should not happen with Steps 2.7 + 3).
7. **Autonomous feature landing (github-flow only; AC-003).** After the commit lands, evaluate the live predicate: `node .claude/skills/commit/cli.mjs is-autonomous-landing` (wraps `hooks/lib/common.mjs -> isAutonomousFeatureLanding`). If it prints anything other than `true`, skip this step — the do-not-push rule below governs (protected branches, `ask`/`direct-to-main`, non-git, detached HEAD, linked worktrees are all `false`; the predicate is fail-safe). If `true` (github-flow, primary tree, named feature branch that is neither a release branch nor protected):
- `git push -u origin <branch>` (the current branch by name — never `--force`).
- `gh pr create --base <release>` where `<release>` is the first entry of `git.release_branches` (default `main`), with a title/body drafted from the commit message.
- On ANY failure — push rejected, `gh` absent, `gh` unauthenticated, PR creation error — STOP: write `harness_state` `{state: "yielded", slug, reason: "autonomous landing failed: <one-line cause>"}` (marker delete first) and surface the exact command output to the user. Never retry-loop, never fall back to a different remote/branch, never force. The commit itself stays intact; only the landing is handed back.
- `git_commit_guard`'s branch-aware policy remains the structural backstop: a non-protected branch already pushes without consent; a protected branch never reaches this step.
8. Outside the autonomous-landing step above, do NOT run `git push`, and never run `git commit --amend` or pass `--no-verify`/`--no-gpg-sign`, unless the user explicitly named the operation in their current request.
9. Do NOT append `"commit"` to `completed`. Step 1 moved `workflow.json` into the archive bundle and the commit above already landed it; the archived bundle is immutable, and a post-move append re-dirties the tree with a file that was just committed. The live `.claude/state/workflow.json` no longer exists, so there is nothing to record. Report the commit SHA (and, after an autonomous landing, the PR URL) to the user.