|
| 1 | +--- |
| 2 | +name: commit |
| 3 | +description: >- |
| 4 | + Stages and commits the current changes onto a safe working branch, enforcing branch discipline |
| 5 | + and optionally pushing upstream. TRIGGER when: the user wants to commit, save, or check in the |
| 6 | + current changes. DO NOT TRIGGER when: opening a pull request → pr. |
| 7 | +argument-hint: Optional `push` to push the branch upstream after committing. |
| 8 | +compatibility: Requires a Git working tree. |
| 9 | +metadata: |
| 10 | + version: 0.1.0 |
| 11 | + author: OpsMill |
| 12 | +--- |
| 13 | + |
| 14 | +# Commit Changes |
| 15 | + |
| 16 | +## Introduction |
| 17 | + |
| 18 | +Stage and commit the current changes onto a safe working branch. This skill enforces branch discipline: when the current branch is unsafe to commit to (the canonical rules live in **step 2** below), it proposes a new `fix/`, `feat/`, `docs/`, etc. branch and switches to it after approval. It works equally well for brand-new work and for additional commits on an existing feature branch. If the user passes `push`, the branch is pushed upstream after the commit. |
| 19 | + |
| 20 | +## Arguments |
| 21 | + |
| 22 | +<arguments> $ARGUMENTS </arguments> |
| 23 | + |
| 24 | +**Supported arguments:** |
| 25 | + |
| 26 | +- `push` — After committing, push the branch upstream (`git push -u origin <branch>` on first push, otherwise `git push`). |
| 27 | + |
| 28 | +## Main Tasks |
| 29 | + |
| 30 | +### 1. Assess Current State |
| 31 | + |
| 32 | +1. Run `git status` to see staged, unstaged, and untracked files. |
| 33 | +2. Run `git branch --show-current` to identify the current branch. |
| 34 | +3. Run `git diff --stat` and `git diff --cached --stat` to summarise the change footprint. |
| 35 | +4. If there are no changes to commit (working tree clean, nothing staged) — STOP and tell the user there's nothing to commit. Do not create an empty commit. |
| 36 | +5. If a merge, rebase, or cherry-pick is in progress (`git status` reports it, or `.git/MERGE_HEAD` / `.git/rebase-*` / `.git/CHERRY_PICK_HEAD` exist) — STOP and surface it to the user rather than committing into the middle of that operation. |
| 37 | + |
| 38 | +### 2. Branch Safety — Never Commit to a Protected or Placeholder Branch |
| 39 | + |
| 40 | +A branch is **unsafe to commit to** if any of the following hold: |
| 41 | + |
| 42 | +- It is the repository's default branch (resolve with `git symbolic-ref refs/remotes/origin/HEAD --short 2>/dev/null | sed 's@^origin/@@'`, falling back to `git remote show origin | sed -n 's/ *HEAD branch: //p'`). |
| 43 | +- It is a long-standing integration branch by name: `main`, `master`, `stable`, `develop`, `dev`, `trunk`. |
| 44 | +- It is a release branch: matches `release/*`, `release-*`, or is otherwise clearly named `release-something` (e.g. `release-2026.05`, `releases/v3`). |
| 45 | +- It is a placeholder / scratch branch generated by the harness or a worktree — specifically the auto-generated **`adjective-animal`** pattern (e.g. `wary-cuckoo`, `lumbar-gorilla`, `wacky-otter`, `silent-fox`), where the second word is an animal. These exist only to host a session and should not be committed to directly. Be careful **not** to over-match: legitimate two-word branches like `dark-mode`, `rate-limit`, `cache-layer`, or `user-auth` are real feature branches, not scratch branches. When a name is ambiguous (two hyphenated words but not clearly `adjective-animal`), do not assert it is unsafe — instead ask the user "this looks like it might be a generated scratch branch — is it, or is it a real branch you want to commit to?" and proceed on their answer. |
| 46 | + |
| 47 | +If the current branch is unsafe by any of those rules: |
| 48 | + |
| 49 | +1. Do NOT commit on the current branch. |
| 50 | +2. Analyse the changes (diffs from step 1) to understand whether the work is a bug fix or new/extended behaviour. |
| 51 | +3. Propose a branch name using the conventional prefix that matches the change: |
| 52 | + - **Bug fix** → `fix/<short-description>` (e.g. `fix/graphql-codegen-missing-types`). |
| 53 | + - **New feature or enhancement** → `feat/<short-description>` (e.g. `feat/add-commit-skill`). |
| 54 | + - **Docs-only, chore, refactor, etc.** → mirror the conventional-commit type: `docs/<…>`, `chore/<…>`, `refactor/<…>`. |
| 55 | + - Use a concise kebab-case slug. If the repo has a visible naming convention in `git branch -a` or in `AGENTS.md` / `CONTRIBUTING.md`, follow that instead. |
| 56 | +4. Present the proposed branch name to the user and wait for explicit approval (the user may suggest a different name). |
| 57 | +5. After approval, create and switch to the branch: `git checkout -b <branch-name>`. The uncommitted changes carry across automatically. |
| 58 | + |
| 59 | +If the current branch is already a real feature branch (i.e. not in any of the unsafe categories above), continue on it. Do not create a new branch unless the user explicitly asks for one. |
| 60 | + |
| 61 | +### 3. Stage Changes |
| 62 | + |
| 63 | +1. Review what will be staged. Prefer adding files explicitly by path rather than `git add -A` or `git add .`, especially when untracked files are present — this avoids accidentally committing secrets (`.env`, credentials, key files) or large/generated artefacts. |
| 64 | +2. If staged changes already exist (the user pre-staged), respect that staging and only add additional files when it's clearly intended. |
| 65 | +3. Warn the user and require explicit confirmation before staging any file that looks sensitive (`.env*`, `*secret*`, `*credential*`, `*.key`, `*.pem`) or generated/large (`node_modules/`, build artefacts, `dist/`, `__pycache__/`, lockfiles changed unexpectedly). |
| 66 | + |
| 67 | +### 4. Draft the Commit Message |
| 68 | + |
| 69 | +Follow the repo's conventional-commit style (visible in `git log`): |
| 70 | + |
| 71 | +- Use a type prefix: `feat:`, `fix:`, `refactor:`, `docs:`, `test:`, `chore:`, `ci:`, etc. |
| 72 | +- Optional scope in parentheses: `feat(backend): ...`, `fix(e2e): ...`. |
| 73 | +- Subject line under ~72 characters, imperative mood, no trailing period. |
| 74 | +- Focus on the *why* — what changed in terms of behaviour or capability, not a file list. |
| 75 | +- For multi-area changes, add a short body explaining the motivation. |
| 76 | +- Respect any repo or harness commit-trailer convention (e.g. a `Co-Authored-By` trailer the harness expects to append). Don't strip trailers that are already part of the project's workflow. |
| 77 | +- **Never write a session-link trailer into the commit — strip it if the harness added one.** Some harnesses append a line pointing at the private agent session, e.g. `Claude-Session: https://claude.ai/code/session_…` (or any trailer carrying a URL to an agent, chat, or coding session). These leak an internal, usually unshareable URL into the project's permanent — often public — git history, and they reference session state no future reader can open, so they're pure noise at best and a disclosure at worst. This is the one exception to "don't strip trailers" above: if the harness has inserted such a line, remove it before committing, and never author one yourself. Legitimate project trailers (`Co-Authored-By`, `Signed-off-by`, `Reviewed-by`, etc.) still stay. |
| 78 | + |
| 79 | +Inspect the most recent ~20 commits with `git log --oneline -20` and mirror the style you see (scope conventions, capitalisation, whether bodies are used). Generic examples: |
| 80 | + |
| 81 | +- `fix: correct off-by-one in pagination cursor` |
| 82 | +- `docs: archive completed specs and extract durable knowledge` |
| 83 | +- `feat: add commit skill for safe branch discipline` |
| 84 | + |
| 85 | +Present the proposed commit message to the user before committing. Adjust based on feedback. |
| 86 | + |
| 87 | +### 5. Create the Commit |
| 88 | + |
| 89 | +1. Run `git commit -m "<message>"`. For multi-line messages, use a HEREDOC: |
| 90 | + |
| 91 | + ```bash |
| 92 | + git commit -m "$(cat <<'EOF' |
| 93 | + <subject> |
| 94 | +
|
| 95 | + <body> |
| 96 | + EOF |
| 97 | + )" |
| 98 | + ``` |
| 99 | +
|
| 100 | +2. Do NOT pass `--no-verify` — let pre-commit hooks run. If a hook fails: |
| 101 | + - Read the hook output carefully. |
| 102 | + - Fix the underlying issue (formatting, lint, etc.) rather than bypassing. |
| 103 | + - Re-stage the fixed files and create a NEW commit so each fix stays traceable. (Don't reach for `--amend` to absorb hook fixes — only amend a commit you deliberately intend to rewrite.) |
| 104 | +3. Run `git status` after the commit to confirm a clean tree and verify success. |
| 105 | +4. Run `git log -1 --stat` so the user can see what landed. |
| 106 | +
|
| 107 | +### 6. Push Upstream (only when `push` argument is provided) |
| 108 | +
|
| 109 | +**Skip this phase entirely if `push` was NOT passed.** |
| 110 | +
|
| 111 | +When `push` IS provided: |
| 112 | +
|
| 113 | +1. Re-verify the branch is not unsafe per the rules in step 2 (defence in depth, though step 2 should have prevented this). |
| 114 | +2. Check whether the branch already tracks a remote: |
| 115 | + - `git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null` |
| 116 | +3. If no upstream exists: `git push -u origin <branch-name>`. |
| 117 | +4. If an upstream exists: `git push`. |
| 118 | +5. **Never use `--force` or `--force-with-lease`** unless the user explicitly asks for it. Regular `git push` will fail if the remote has diverged — surface that error to the user instead of overwriting. |
| 119 | +6. Report the push result and, if a remote URL is configured (`git config --get remote.origin.url`), the branch URL the user can open. |
| 120 | +
|
| 121 | +## Quality gates |
| 122 | +
|
| 123 | +Per `../quality-gates/gates/gate-model.md`. `commit` is **Tier 1** (mutates a branch, does not ship). |
| 124 | +
|
| 125 | +| Gate | Trigger | Tier | Primitives | Pass criteria | On-fail | |
| 126 | +|---|---|---|---|---|---| |
| 127 | +| Branch-safety | before commit | T1 | P1 | Paste `git rev-parse --abbrev-ref HEAD` proving HEAD is not a protected/release/scratch branch. | STOP; create a feature branch first | |
| 128 | +
|
| 129 | +## Notes |
| 130 | +
|
| 131 | +**Branch Safety:** |
| 132 | +
|
| 133 | +- The canonical list of unsafe branches lives in **step 2** — that is the single source of truth; don't re-derive it from memory. |
| 134 | +- If asked to override the rule, refuse and explain — the user can switch branches themselves first if they truly intend to commit there (in which case this skill no longer applies). |
| 135 | +
|
| 136 | +**This is a discipline skill — hold the line under pressure.** Refusing to commit to a protected branch is the whole point, and the excuses below will appear. None of them change where the commit should land: |
| 137 | +
|
| 138 | +| Excuse / pressure | Reality | |
| 139 | +|-------------------|---------| |
| 140 | +| "It's urgent / it's an emergency, just commit to main." | Urgency doesn't change where the commit lands. A `feat/`/`fix/` branch takes seconds and is just as fast to merge. | |
| 141 | +| "Just this once, skip the branch." | There is no "just once" — the rule exists precisely for the tempting one-off. Propose a branch. | |
| 142 | +| "It's a tiny change, the branch is overkill." | Size is irrelevant to branch safety; a one-line fix on `main` is still a direct commit to a protected branch. | |
| 143 | +| "The hook is failing, just use `--no-verify`." | Fix the violation instead. Bypassing the hook defeats its purpose. | |
| 144 | +| "The remote diverged, just `--force`." | Never force-push unless the user explicitly asks. Surface the divergence instead. | |
| 145 | +
|
| 146 | +Red-flags self-check — if you catch yourself thinking any of these, STOP and re-read step 2: |
| 147 | +
|
| 148 | +- "This branch is *probably* fine to commit to." |
| 149 | +- "I'll commit here and sort the branch out later." |
| 150 | +- "The user clearly wants this on `main`, so the rule doesn't apply." |
| 151 | +
|
| 152 | +**Secret Hygiene:** |
| 153 | +
|
| 154 | +- Prefer explicit `git add <path>` over wholesale `git add -A`. |
| 155 | +- Warn before staging anything that looks like a secret or credential. |
| 156 | +- A session-link trailer (`Claude-Session:` / any agent-session URL) is a disclosure too — strip it from the commit message before committing, per step 4. |
| 157 | +
|
| 158 | +**Hook Discipline:** |
| 159 | +
|
| 160 | +- Pre-commit hooks exist for good reasons. Fix violations rather than bypassing with `--no-verify`. |
| 161 | +
|
| 162 | +**Idempotency:** |
| 163 | +
|
| 164 | +- Safe to invoke repeatedly. With no changes to commit, the skill exits cleanly without creating an empty commit. |
| 165 | +
|
| 166 | +## Expected Outcome |
| 167 | +
|
| 168 | +- All staged/unstaged changes that the user wanted to capture are committed on a safe working branch. |
| 169 | +- The branch is named meaningfully (existing real feature branch preserved, or a new `fix/<…>` / `feat/<…>` / `docs/<…>` / etc. name approved by the user). |
| 170 | +- The commit message follows the repo's existing conventional-commit style. |
| 171 | +- If `push` was provided, the branch is pushed upstream. |
| 172 | +- Every branch deemed unsafe in step 2 is left untouched in all cases. |
0 commit comments