Create a verified local Git commit when committing changes is requested. Not for explaining commit history, drafting a message only, or postponing a commit. Applies every commit gate and never implies a push or PR.
Installs into .claude/skills of the current project.
Are you the author of Commit Gate?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/arbiterforge-commit-gate-64a2f103)
---
name: commit-gate
description: Create a verified local Git commit when committing changes is requested. Not for explaining commit history, drafting a message only, or postponing a commit. Applies every commit gate and never implies a push or PR.
argument-hint: (none)
---
# commit-gate
The only permitted path to a commit. Bypassing it is a hard-rule violation. Routed to when the user invokes `/commit` or any equivalent instruction to persist staged changes.
No command arguments are required; inspect current Git state. Test-only requests use the
recorded invocation in `tech-stack.md` without entering this committing procedure. PR
readiness belongs to the existing PR workflow; an informational or message-only request
must not reach staging, commit, or this procedure's writing close.
## Pre-flight
Read these, or STOP and surface the gap — never guess a command:
- `<project-root>/.codearbiter/tech-stack.md` — test, lint, and secrets-scan invocations. Stop if missing; do not guess.
- `<plugin-root>/includes/verification-boundary.md` — the required split between focused local proof and exhaustive exact-head hosted CI.
- A git repository must be present and `git status` available.
- The `tdd` skill must have cleared all six phases for any new or modified feature code in the staged set. If `tdd` is incomplete, STOP and surface the gap.
- The discovered authoritative spec/plan pair, when this commit belongs to a
`/feature` or `/sprint` pipeline. For HTML, load
`<plugin-root>/includes/artifacts.md`; use the engine `identity` and `eligible`
results, and treat the exact spec and plan identities plus the acceptance
receipts bound in engine state as the commit proof. Require
`all_accepted_and_current: true` before this gate can commit completed plan
work. The gate must not parse rendered HTML and must not consult a shadow
Markdown ledger. Displayed state, checkboxes, hashes, or caller-supplied
labels cannot substitute for current engine acceptance.
For that HTML pair, invoke the installed bridge's
`_preflight_current_acceptance` with the exact `_resolve_workflow_pair` result,
the repository-bound client, and the spec/plan IDs returned by the engine. Use
its returned identities and receipts as the proof for this gate. A refusal is a
BLOCK before staging or committing; never construct an equivalent proof from
caller metadata.
## Phase 1 — Permission · gate: BLOCK
Confirm the user explicitly authorized this commit. Speculative commits are prohibited.
Explicit instructions: "commit", "commit this", "go ahead and commit", "create the commit". Ambiguous signals — "looks good", "that should work" — are NOT authorization. Record the instruction text for the report.
Gate: explicit user authorization is on record. Inferred or assumed permission does not pass.
## Phase 2 — Branch · gate: BLOCK
Run `git branch --show-current`. If the branch is `main`, `master`, or any protected branch, STOP and instruct the user to create a feature branch. Record the branch name for the report.
Gate: the working branch is not protected.
## Phase 3 — Classification · gate: BLOCK
Read the staged set (`git diff --cached --name-only` and `--stat`). Classify the change into a commit type:
- `feat` — new capability or behavior
- `fix` — corrects a defect
- `test` — tests only
- `refactor` — restructures without behavior change
- `docs` — documentation only
- `chore` — build, tooling, dependency updates
- `ci` — pipeline changes
Derive the scope from the staged paths. If the staged set spans more than one type, split it — stage and commit each type separately.
Flag any staged **database migration** (per `_hooklib.is_migration_path`) here — it carries a mandatory migration-review routing in Phase 4 (the H-14 gate), independent of the commit type.
Gate: the staged set is type-homogeneous with a single type and scope.
## Phase 4 — Verification · gate: BLOCK
Read the test, lint, and secrets-scan commands from `tech-stack.md`, then apply
`<plugin-root>/includes/verification-boundary.md`:
- Run the impact-bounded local tests that directly cover the staged paths and obligations. Any failure blocks. Repository-wide and cross-platform suites belong to exact-head hosted CI before merge, not this commit gate.
- Run lint, and the type-check if the project is statically typed. Zero errors.
- Run the secrets scan on ALL staged files, regardless of commit type. Any finding blocks.
- **Security gates (mandatory routing):** if the staged diff touches crypto/TLS or secret patterns, route it through `crypto-compliance` (`<plugin-root>/routines/crypto-compliance/SKILL.md`) and/or `secret-handling` (`<plugin-root>/routines/secret-handling/SKILL.md`) — they scan against `security-controls.md` and, on pass, record the diff-bound marker `.codearbiter/.markers/security-gate-passed` (via `hooks/security-pass.py`). This is not optional: the PreToolUse commit hook **H-09b/H-10b blocks the commit** until that gate pass is recorded AND covers every sensitive line being committed.
- **Migration gate (mandatory routing):** if the staged set contains a database migration (Phase 3 flags it; the detection rule is `_hooklib.is_migration_path` — default migration globs, extendable/narrowable via a `migration-paths` block in `security-controls.md`), dispatch the `migration-reviewer` agent (`<plugin-root>/agents/migration-reviewer.md`). **On a genuine PASS only**, record the content-bound marker `.codearbiter/.markers/migration-gate-passed` by resolving the interpreter once by presence — `PY=python3; { command -v python3 >/dev/null 2>&1 && python3 --version >/dev/null 2>&1; } || PY=python` — never `python3 X || python X`, which reruns X on any nonzero exit (#577) — and running `"$PY" "<plugin-root>/hooks/migration-pass.py"`. This is not optional: the PreToolUse commit hook **H-14 blocks the commit** until the pass is recorded AND covers every migration file being committed (by content digest, no freshness window — an edit to a reviewed migration re-blocks). This closes the bare-`/commit` / small-lane gap from issue #77. On a BLOCK, do not record the pass.
- **CI/deploy review (mandatory routing, no marker gate):** if the staged set touches a CI/CD workflow (`_hooklib.is_ci_path` — defaults extendable via a `ci-paths` block in `security-controls.md`) or a deployment/IaC manifest (`_hooklib.is_deploy_path` — `deploy-paths` block), dispatch the `security-reviewer` agent (`<plugin-root>/agents/security-reviewer.md`). This is the enforcement point the advisory `post-write-edit` reminders **H-15/H-16** point to, and it closes the bare-`/commit` / small-lane gap for CI/deploy (the `/review`, `/pr`, `/checkpoint`, and sprint lanes already dispatch it). Unlike crypto/secret/migration there is **no commit-block marker** — a CI workflow runs only once merged and IaC bites only on apply, so a BLOCK-level finding halts the commit via Phase 6 review, but routine CI/deploy edits are not gated per-commit. Act on the findings by severity; do not record a marker.
Record each result (PASS / BLOCK) for the report.
Gate: the applicable focused tests, lint/type-check, secrets scan, and specialized gates all PASS. Any failure halts the commit until fixed and re-run. This gate does not claim that exhaustive hosted CI has run; `finishing-a-development-branch` proves that separately before merge.
## Phase 5 — Behavioral proof · gate: BLOCK
Apply the shared fresh-run discipline in `<plugin-root>/includes/fresh-verification.md`, with
**the spec's acceptance criterion** as the target — prove the behavior against the spec, not against a
self-report.
- Identify the proving command or observable from the selected authoritative
pair. For Markdown, use the existing criterion and task-verification text. For
HTML, use the exact criterion and task records returned by symbol-scoped
engine `read`; never scrape the rendered document. If none exists, derive the
smallest command that exercises the claimed behavior.
- Run it fresh in this phase, read its output and exit code, and confirm the observed behavior matches the spec's acceptance criteria. A mismatch, or an unverifiable claim, blocks.
**Stakes:** a behavioral-proof mismatch means the change does not do what the spec claims — state what would ship broken if this passed ("the retry path never fires; a transient error would hang the caller"), not just "proof mismatch." That gap is exactly what a green-looking suite hides.
Gate: the change is proven to do what it claimed by fresh evidence — command output and exit code read in this phase. A self-reported "it works" does not pass.
## Phase 5.5 — Provenance auto-heal (conditional)
Compute the heal worklist from the staged set via `_provenancelib.heal_worklist(staged_paths, provenance, current_hashes)` — the subset of staged paths that are `drift_trigger:true` provenance entries whose recorded hash has diverged or is absent. **Empty worklist → skip this phase entirely; most commits pay nothing** (cost guarantee: ordinary commits touching no provenance source do zero re-scout work).
Non-empty worklist → call `_provenancelib.compute_drift(provenance, current_hashes)` to retain the document/source mapping. For each stale document in that drift report, get its record as `doc_provenance` and call `_provenancelib.changed_scope(doc_provenance, drift)`. Intersect that document's drifted paths with the worklist; skip a document when the intersection is empty. The hash map can omit unstaged paths, so a missing entry outside the worklist must not broaden this commit's re-scout. Confirm that every selected worklist path maps to at least one stale document after this filtering; if a record is malformed or the mapping is incomplete, STOP and report the gap rather than treating the path as fresh.
Run an **incremental re-scout for each affected document**, scoped to its selected staged paths only (not the full repo). A fresh first document does not discharge a stale second document's claims for the same source. Resolve each selected document/path dependency independently:
- **Claim still holds → silently re-baseline only that document's provenance record.** Call `_provenancelib.rebaseline(doc_provenance, selected_current_hashes)` where `selected_current_hashes` contains current hashes only for the selected staged paths whose claims in that document were inspected and still hold. Do not re-baseline a path whose claim changed or was not inspected. Stage only `.codearbiter/.provenance/<doc>.json` by explicit path so the re-baselined record rides THIS commit (ADR-0008 ride-along pattern — nothing is surfaced to the user). After staging, re-run the secrets scan from `tech-stack.md` over the newly-staged path(s) — any file staged after Phase 4 must still pass the automated secrets scan before the commit proceeds. A missing current hash (deletion or rename) cannot be silently re-baselined; surface it in Phase 6 for review.
- **Claim changed → route to the existing Phase 6 diff-review.** Propose only the affected doc or code-map edit required to reflect its changed claim. The user reviews that edit in the normal diff, and Phase 7 stages approved paths explicitly; do not silently rewrite or add unrelated documentation.
## Phase 6 — Diff review · gate: BLOCK
Read the complete staged diff (`git diff --cached`). Flag as blocking:
- Unexpected files — not discussed in the session.
- Credentials, tokens, API keys, or any secret — belt-and-suspenders to Phase 4.
- Incomplete changes — TODO markers, placeholder values, dead commented-out code, partial stubs.
- Tests disabled or skipped that were not intentionally disabled.
- Scope creep — changes outside the agreed feature or fix boundary.
**Board-edit exemption (ADR-0008):** ADR-0008 governs three transitions riding a commit whose primary purpose is something else — a board edit inside such a commit is **expected and RETAINED** (not scope creep, MUST NOT be unstaged) only when `_taskboardlib.classify_board_diff(old, new)` returns a clean transition (done-flip `[~]`→`[x]`, start-flip `[ ]`→`[~]` with its optional minted dotted ID, or a single queued-add `[ ]`). Any other `open-tasks.md` edit riding an unrelated commit does not classify as one of those transitions and still flags as scope creep — it does not belong to that commit's agreed boundary.
This does not cover a commit whose own Phase 3 classification **is** the board change — e.g. a `docs` chore whose sole content is a `/ca-standup` archival sweep (open-tasks.md removals plus the matching `done-tasks.md` append). There, the boundary was already agreed at Phase 3, so those edits are in-scope by that classification directly; `classify_board_diff` is not consulted and does not need to recognize archival as a fourth transition.
**Provenance re-baseline exemption (ADR-0008):** a `.codearbiter/.provenance/<doc>.json` file written by the Phase 5.5 auto-heal re-baseline is likewise **expected and RETAINED** — it is not scope creep and MUST NOT be unstaged. A heal-proposed doc or code-map edit (the claim-changed path from Phase 5.5) appears in the diff for normal review; treat it as any other finding. This exemption waives the scope-creep flag only; the secrets check is not waived — the automated re-scan in Phase 5.5 covers the provenance file.
On any blocking finding, unstage the affected files, surface the finding, and STOP. An out-of-scope change that should not be lost gets an inline `[NEEDS-TRIAGE]` marker before it is set aside.
**Stakes:** name what the finding would have cost if committed — a leaked credential is live the moment it lands and must be rotated; a scope-creep file ships untested behavior the review waved through. State that consequence on a credential or scope finding, not just "out of scope."
Gate: the diff is clean — zero blocking findings.
## Phase 7 — Selective stage · gate: BLOCK
**First, run the follow-up harvest — before staging anything.** Run the follow-up harvest (`<plugin-root>/includes/harvest.md`) over any Phase 6 `[NEEDS-TRIAGE]` set-asides — promote discovered follow-ups to `open-tasks.md` (work) or `open-questions.md` (decision) via the existing harvest procedure. Running this before the commit means raised board tasks are staged and ride the work commit in the same payload.
**Atomicity rule:** a raised task riding the work commit is a **contingent default** — if the PR/branch is abandoned, the board additions are abandoned with it (self-correcting, ADR-0008). A follow-up that **must survive** PR abandonment is filed as a **GitHub issue**, not the board.
Then selectively stage: if files were unstaged in Phase 6, re-stage only the clean files by explicit path: `git add path/to/file`. When a clean task-board transition was retained by the Phase 6 board-edit exemption, include `open-tasks.md` in the selective stage by explicit path (`git add open-tasks.md`) alongside the work files, so the flip rides the same commit. Include any `open-tasks.md` additions produced by the harvest step in the same explicit-path stage. When Phase 5.5 produced a re-baselined provenance record, include the affected `.codearbiter/.provenance/<doc>.json` file(s) by explicit path in the same selective stage so the re-baselined record rides the work commit. Re-run `git diff --cached --name-only` and confirm the staged list matches the intended set exactly — no extra files. Unstage any extra and report the discrepancy.
Gate: the staged set contains exactly the intended files. MUST NOT use `git add -A`, `git add .`, or any wildcard.
## Phase 8 — Message · gate: BLOCK
Compose a Conventional Commits message:
- Subject: `<type>(<scope>): <imperative summary>`, MUST NOT exceed 72 characters.
- Blank line, then a body explaining WHY — not a restatement of what changed.
- For `feat` and `fix`, add a `CHANGELOG:` footer summarizing the user-visible impact.
- If the commit closes an issue or references a decision, add the footer (`Closes #NN`, `Ref: ADR-NNNN`).
- No content-free subjects — "fix bug", "update code", "changes" do not pass.
Gate: subject ≤ 72 chars, a body is present, and any `feat`/`fix` carries a `CHANGELOG:` footer.
## Phase 9 — Commit · gate: BLOCK
Commit with the approved message via `-m` or heredoc with proper quoting — never an interactive editor. Capture the resulting SHA.
If a pre-commit hook fails: read its output in full, fix the issue, re-stage by explicit path (Phase 7 rules apply), and create a NEW commit. MUST NOT `--amend` after a hook failure.
After a successful commit, run `git status` to confirm the tree is clean. Deliver the report: SHA, branch, explicit file list, gate results (test / lint / secrets), and the message used.
Gate: the commit lands and `git status` is clean. Unexpected uncommitted changes after the commit block closure — report the discrepancy.
## Hard rules
- MUST NOT commit without explicit user authorization. "It looks good" is not permission.
- MUST NOT commit to `main`, `master`, or any protected branch.
- MUST NOT run `git add -A`, `git add .`, or any wildcard staging.
- MUST NOT commit while any test is failing, any lint error stands, or any secret is present.
- MUST NOT turn exhaustive repository-wide or cross-platform CI into a local prerequisite when the shared `verification-boundary` assigns it to exact-head hosted CI.
- MUST NOT accept a self-reported "it works" — prove the behavior against the spec with a fresh command run (Phase 5) before committing.
- MUST NOT skip, disable, or work around any automated gate.
- MUST NOT commit a staged database migration without a recorded migration-review pass — the H-14 hook blocks it until `migration-reviewer` passes and `hooks/migration-pass.py` records the content-bound marker.
- MUST NOT `--amend` after a pre-commit hook failure — create a new commit.
- MUST NOT guess the test, lint, or secrets-scan command — read `tech-stack.md` or STOP.
- MUST NOT silently rewrite a doc's claims — a claim-change edit proposed by Phase 5.5 goes through diff review (Phase 6), never through the silent re-baseline path. The re-baseline path is strictly for claims that still hold; the Phase 5.5 re-baseline MUST ride the work commit (staged by explicit path in Phase 7).
- MUST, **at Phase 7 before staging**, run the follow-up harvest (`<plugin-root>/includes/harvest.md`) over any Phase 6 `[NEEDS-TRIAGE]` set-aside — promote to `open-tasks.md` (work) or `open-questions.md` (decision) so raised tasks ride the work commit. A follow-up that must survive PR abandonment is filed as a GitHub issue, not the board.