commit-gate
The only path to a commit. Routed to when the user invokes /commit or otherwise instructs codeArbiter to persist staged changes. Nine gated phases — permission, branch, classification, verification (test/lint/secrets), behavioral proof, diff review, selective stage, message, commit. Nothing reaches version control without clearing every gate; “it looks good” is not authorization.
What it does
Section titled “What it does”This is the only door into version control — nothing else in the project runs git commit.
Invoking it (via the commit command) hands control to a sequence of checks that read the
repository’s actual state rather than taking your word for it: what’s staged, what changed,
whether the suite is green. It walks from confirming you actually asked for a commit through
classification, verification, a behavioral proof, and a full diff review before anything lands.
Phases
Section titled “Phases”- Confirm explicit authorization to commit.
- Confirm the branch is not a protected one.
- Classify the staged change into a single commit type and flag any database migration.
- Run tests, lint, and a secrets scan; route through the crypto, secret, migration, or CI/deploy review gates when the diff touches those areas.
- Prove the change’s claimed behavior with a fresh command run, not a self-report. 5.5. When a tracked source file drifted, re-scout just that slice and either silently re-baseline or route a changed claim into diff review.
- Read the complete staged diff for stray files, secrets, unfinished work, or scope creep.
- Run the follow-up harvest, then stage exactly the intended files by explicit path.
- Compose a Conventional Commits message with a body explaining why.
- Commit, capturing the resulting SHA, and confirm the tree is clean afterward.
A clean run ends in a commit SHA, a clean working tree, and a report of what each gate found. A BLOCK at any gate halts the commit entirely and surfaces exactly what needs fixing before the sequence can be re-run.
| Gate | When | Effect |
|---|---|---|
| permission | every invocation | an explicit instruction to commit must be on record; “looks good” does not count |
| verification | after classification | tests, lint, and a secrets scan must all come back clean, plus crypto/secret/migration/CI review where the diff touches those areas, before anything else runs |
| behavioral proof | after verification passes | a freshly run command must demonstrate the change does what the spec claims — a self-report is not accepted |
| diff review | before staging | the full staged diff is read for stray files, secrets, incomplete work, and scope creep; any finding unstages the offending files and stops |
Related
Section titled “Related”Source
Section titled “Source”Source — plugins/ca/skills/commit-gate/SKILL.md (v2.9.1)
---name: commit-gatedescription: The only path to a commit. Routed to when the user invokes /commit or otherwise instructs codeArbiter to persist staged changes. Nine gated phases — permission, branch, classification, verification (test/lint/secrets), behavioral proof, diff review, selective stage, message, commit. Nothing reaches version control without clearing every gate; "it looks good" is not authorization.---
# 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.
## Pre-flight
Read these, or STOP and surface the gap — never guess a command:
- `${CLAUDE_PROJECT_DIR}/.codearbiter/tech-stack.md` — test, lint, and secrets-scan invocations. Stop if missing; do not guess.- 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.
## 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:
- Run the test command. ALL tests green. Any failure blocks.- 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` and/or `secret-handling` (`${CLAUDE_PLUGIN_ROOT}/skills/`) — 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 (`${CLAUDE_PLUGIN_ROOT}/agents/migration-reviewer.md`). **On a genuine PASS only**, record the content-bound marker `.codearbiter/.markers/migration-gate-passed` by running `python3 "${CLAUDE_PLUGIN_ROOT}/hooks/migration-pass.py" || python "${CLAUDE_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 (`${CLAUDE_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: test, lint, secrets scan, and (when crypto/secret is touched) the security gate all PASS. Any failure halts the commit until fixed and re-run.
## Phase 5 — Behavioral proof · gate: BLOCK
Apply the shared fresh-run discipline in `${CLAUDE_PLUGIN_ROOT}/includes/fresh-verification.md`, with**the spec's acceptance criterion** as the target — prove the behavior against the spec, not against aself-report.
- Identify the proving command or observable: the acceptance criterion from `${CLAUDE_PROJECT_DIR}/.codearbiter/specs/<slug>.md` (or the task's verification in the plan). 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 → run an **incremental re-scout scoped to those paths only** (not the full repo). For each path, re-examine whether the claims in the backing doc still hold:
- **Claim still holds → silently re-baseline.** Call `_provenancelib.rebaseline` to update the stored hash in `.codearbiter/.provenance/<doc>.json` and stage that file 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.- **Claim changed → route to the existing Phase 6 diff-review.** The doc or code-map edit required to reflect the changed claim is proposed in diff review — the user reviews it as part of the normal diff; nothing is silently rewritten.
## 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):** an edit to `open-tasks.md` where `_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 `[ ]`) is **expected and RETAINED** — it is not scope creep and MUST NOT be unstaged. Any other `open-tasks.md` change — a reworded or deleted entry, or an arbitrary content edit — does not classify as a transition and still flags as scope creep.
**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 (`${CLAUDE_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 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 (`${CLAUDE_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.