| name | commit |
| disable-model-invocation | true |
| description | Safe commit workflow with optional scope hint. Inventories all changes, classifies related vs. unrelated files, traces dependencies, protects other agents' work, and optionally pushes, lands worktree commits, or opens a PR via /land-pr. Positional auto enables auto-merge (PR mode only). |
| argument-hint | [pr] [scope] [push|land] [auto|automerge] |
| metadata | {"version":"2026.06.15+dc691a"} |
/commit [pr] [scope] [push|land] [auto] — Safe Commit Workflow
Commit current work without picking up or harming unrelated changes.
Arguments:
/commit — commit only, infer scope from diffs
/commit skill updates — commit, scope guided by "skill updates"
/commit push — commit and push to remote
/commit parser reset button fix push — scope-guided commit + push
/commit land — cherry-pick worktree commits into main (worktree only)
/commit pr — push current branch and create a PR to main (requires clean working tree)
/commit pr fix pr comments — PR mode with scope hint "fix pr comments"
Parsing: pr is recognized ONLY when it is the FIRST token in
$ARGUMENTS. This prevents false-triggering on scope hints that contain
"pr" mid-string. push and land are reserved keywords for non-PR mode.
When no explicit mode token is supplied, the skill consults
execution.landing in .claude/zskills-config.json to pick the default
(see the config-driven default-mode block below).
if [ -n "${ZSH_VERSION:-}" ]; then setopt KSH_ARRAYS BASH_REMATCH SH_WORD_SPLIT 2>/dev/null || true; fi
FIRST_TOKEN=$(echo "$ARGUMENTS" | awk '{print $1}')
AUTOMERGE_FLAG=0
if [[ "$ARGUMENTS" =~ (^|[[:space:]])[aA][uU][tT][oO][mM][eE][rR][gG][eE]($|[[:space:]]) ]]; then
AUTOMERGE_FLAG=1
fi
if [[ "$ARGUMENTS" =~ (^|[[:space:]])[aA][uU][tT][oO][[:space:]]+[mM][eE][rR][gG][eE]($|[[:space:]]) ]]; then
AUTOMERGE_FLAG=1
fi
AUTO_FLAG=0
if [[ "$ARGUMENTS" =~ (^|[[:space:]])([aA][uU][tT][oO])($|[[:space:]]) ]] || [ "$AUTOMERGE_FLAG" = "1" ]; then
AUTO_FLAG=1
fi
if [[ "$FIRST_TOKEN" == "pr" ]]; then
SCOPE_HINT=$(echo "$ARGUMENTS" | cut -d' ' -f2-)
SCOPE_HINT=$(echo "$SCOPE_HINT" | sed -E 's/(^|[[:space:]])[aA][uU][tT][oO][[:space:]]+[mM][eE][rR][gG][eE]($|[[:space:]])/\1\2/g' | sed -E 's/(^|[[:space:]])[aA][uU][tT][oO][mM][eE][rR][gG][eE]($|[[:space:]])/\1\2/g' | sed -E 's/(^|[[:space:]])[aA][uU][tT][oO]($|[[:space:]])/\1\2/g' | sed -E 's/^[[:space:]]+|[[:space:]]+$//g; s/[[:space:]]+/ /g')
fi
Config-driven default mode (when no explicit mode token is given):
If $ARGUMENTS contains no explicit mode token (pr as first token, or
push/land anywhere), read execution.landing from
.claude/zskills-config.json to pick the default. This mirrors the
landing-mode resolution in /run-plan, /fix-issues, and /do so
projects with execution.landing: "pr" (the default preset, which also
sets main_protected: true) get PR mode for /commit without users
needing to retype pr every time. Bash regex only — no jq, matching the
co-author read in Phase 5.
if [ -n "${ZSH_VERSION:-}" ]; then setopt KSH_ARRAYS BASH_REMATCH SH_WORD_SPLIT 2>/dev/null || true; fi
HAS_EXPLICIT_MODE=0
if [[ "$FIRST_TOKEN" == "pr" ]]; then
HAS_EXPLICIT_MODE=1
elif [[ "$ARGUMENTS" =~ (^|[[:space:]])(push|land)($|[[:space:]]) ]]; then
HAS_EXPLICIT_MODE=1
fi
if [ "$HAS_EXPLICIT_MODE" -eq 0 ]; then
if [ -f "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh" ]; then
. "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh"
else
. "$CLAUDE_PROJECT_DIR/.claude/skills/update-zskills/scripts/zskills-resolve-config.sh"
fi
CFG_LANDING="$ZSKILLS_CFG_LANDING"
case "$CFG_LANDING" in
pr)
SCOPE_HINT=$(echo "$ARGUMENTS" | sed -E 's/(^|[[:space:]])[aA][uU][tT][oO][[:space:]]+[mM][eE][rR][gG][eE]($|[[:space:]])/\1\2/g' | sed -E 's/(^|[[:space:]])[aA][uU][tT][oO][mM][eE][rR][gG][eE]($|[[:space:]])/\1\2/g' | sed -E 's/(^|[[:space:]])[aA][uU][tT][oO]($|[[:space:]])/\1\2/g' | sed -E 's/^[[:space:]]+|[[:space:]]+$//g; s/[[:space:]]+/ /g')
DEFAULT_MODE="pr"
;;
direct|"")
DEFAULT_MODE="commit"
;;
cherry-pick)
echo "ERROR: execution.landing=\"cherry-pick\" is not a valid default for /commit." >&2
echo " /commit land is the cherry-pick subcommand for landing worktree" >&2
echo " commits onto main; it is NOT a default-mode selector. Set" >&2
echo " execution.landing to \"pr\" or \"direct\" in" >&2
echo " .claude/zskills-config.json, or invoke /commit land explicitly." >&2
exit 1
;;
*)
DEFAULT_MODE="commit"
;;
esac
if [ "$DEFAULT_MODE" = "pr" ]; then
FIRST_TOKEN="pr"
fi
fi
Disambiguation:
/commit pr → PR mode (first token is pr)
/commit pr comments fix → PR mode (first token is pr), scope hint: "comments fix"
/commit fix pr format → scope hint "fix pr format", regular commit (first token is "fix")
/commit with execution.landing: "pr" in config → PR mode (config default)
/commit with execution.landing: "direct" or no config → commit-only (preserved default)
/commit push or /commit land always overrides config — explicit wins
/commit pr auto → PR mode + auto-merge via /land-pr --auto
/commit auto (config default = pr) → PR mode + auto-merge
Examples:
/commit → scope: (none), action: commit
/commit codegen fixes → scope: "codegen fixes", action: commit
/commit skill updates push → scope: "skill updates", action: commit + push
/commit push → scope: (none), action: commit + push
/commit land → scope: (none), action: land
/commit parser fixes land → scope: "parser fixes", action: land
/commit pr → action: PR mode (push + create PR; settles at pr-ready)
/commit pr fix pr comments → PR mode, scope hint: "fix pr comments"
/commit pr auto → PR mode + auto-merge (passes --auto to /land-pr)
/commit auto (config default = pr) → PR mode + auto-merge
PR subcommand behavior is defined in modes/pr.md; land behavior in modes/land.md.
Phase 1 — Inventory
Run these in parallel:
git status -s
git diff
git diff --cached
git log --oneline -10
Also determine context:
- Which branch am I on?
- Am I in a worktree? (
git rev-parse --show-toplevel differs from main repo)
- Is there a remote tracking branch?
Phase 2 — Classify Changes
For every changed and untracked file from git status -s, decide:
- Related to current work — include in this commit
- Unrelated (other agents/sessions) — leave alone, do NOT touch
If a scope hint was provided
The scope hint tells you what this commit is about. Use it to drive
classification:
-
Keyword grep — search the file list for scope-related terms:
git status -s | grep -i <keyword>
For /commit skill updates, grep for skill, SKILL, .claude/skills/.
For /commit codegen fixes, grep for codegen, block-emitter, Rust.
-
Diff-check remaining files — for files that don't match the keyword,
read the diff to confirm they're unrelated. Some related files won't match
a simple grep (e.g., a memory file updated alongside skill changes).
-
Confidence is higher — with a scope hint, you can be more decisive
about what's in vs. out. Without one, you have to read every diff and infer.
If no scope hint was provided
Fall back to the original approach:
- Read the diff for each modified file. Does it relate to what we worked on?
- For untracked files (
??): are they part of this feature? If unsure, ask.
Always
- Context compaction warning: Do NOT rely on session memory for "what I
changed." Context compaction creates artificial boundaries. Always classify
from the actual diffs and the scope hint.
- If a file has mixed changes (yours + someone else's): STOP. Tell the
user which file and what the mixed changes are. Ask what to do. Do NOT
attempt to split hunks or selectively stage — that risks losing work.
Phase 3 — Trace Dependencies
For every file classified as "related":
- Check its imports. If it imports an uncommitted file, that file MUST be
included. Recurse.
- Check for associated files:
- New module → its tests?
- New component → styles, config, registration?
- Plan/doc changes related to the code change?
- Search broadly:
git status -s | grep -i <feature-keyword>
If a scope hint was provided, use terms from the hint as keywords.
- Check
.claude/logs/ — include session logs for this session.
Common mistakes to avoid:
- Committing
A.js which imports B.js without committing B.js → 404
- Committing a module but not its tests
- Missing files from a prior compacted context (they show as
??)
Phase 4 — Stage & Review
-
Check for pre-staged files before adding anything:
git diff --cached --stat
If files are already staged that you didn't stage, another session left
them in the index. STOP and report — do not commit on top of someone
else's staged work. Past failure: commit b69ec3f swept in 146 lines of
another session's codegen changes because they were pre-staged in the index.
-
Stage only the related files by name:
git add file1 file2 ...
NEVER use git add . or git add -A — these grab everything.
-
Review what's staged — verify the count matches what you just added:
git diff --cached --stat
If the file count is higher than the number of files you staged in step 2,
something else was already in the index. Investigate before committing.
-
Present the staged file list to the user and confirm it looks correct.
Phase 5 — Commit
-
Draft a commit message:
- Summarize the nature of the change (feat, fix, refactor, docs, chore)
- Focus on the why, not the what
- 1-2 sentences, concise
- Follow the style of recent commits (
git log --oneline -10)
- If a scope hint was provided, use it to inform the message (but don't
just parrot it — write a proper commit message)
-
Run tests if code was staged — if any staged files are code (.js,
.css, .html, .rs), run the full test suite before committing using
the canonical capture idiom:
if [ -f "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh" ]; then
export CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT}"
. "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh"
else
. "$CLAUDE_PROJECT_DIR/.claude/skills/update-zskills/scripts/zskills-resolve-config.sh"
fi
if [ -z "$FULL_TEST_CMD" ]; then
echo "ERROR: testing.full_cmd not configured. Run /update-zskills." >&2
exit 1
fi
TEST_OUT="/tmp/zskills-tests/$(basename "$(pwd)")"
mkdir -p "$TEST_OUT"
$FULL_TEST_CMD > "$TEST_OUT/${TEST_OUTPUT_FILE:-.test-results.txt}" 2>&1
Read "$TEST_OUT/${TEST_OUTPUT_FILE:-.test-results.txt}" to check
results. All suites must pass. If tests fail, read the captured output
file to diagnose. If tests fail after two fix attempts on the same error,
STOP and report to the user (CLAUDE.md: "NEVER thrash on a failing fix").
Skip this step for content-only commits (.md, .jpg, .png, logs).
2.5. Skill-version bump check. For every staged file under
skills/<owner>/..., verify each affected
skill's metadata.version was correctly bumped (date refreshed AND hash
matches recomputed projection):
bash "$CLAUDE_PROJECT_DIR/scripts/skill-version-stage-check.sh" || {
echo "STOP: skill version check failed; see message above." >&2
exit 1
}
Exit non-zero halts /commit until the agent fixes. The script's STOP
message includes the exact bump command for each affected skill.
-
Dispatch a fresh agent to review the staged changes before committing.
The agent receives git diff --cached and the proposed commit message.
It checks:
- Are all related files included? Any missing dependencies?
- Are any unrelated files accidentally staged?
- Does the commit message accurately describe the changes?
- Any obvious issues in the diff (debug code, TODOs, broken imports)?
Do NOT review the staged changes yourself — you selected the files, so
you have selection bias. A fresh agent catches files you missed or
included by mistake.
Dispatch shape. Use the Agent tool with subagent_type: "verifier". The dispatch prompt MUST include the verbatim preamble: "You are reviewing the staged diff. Read-only review — do NOT git stash, checkout, restore, reset, add, rm, commit, push, merge, rebase, cherry-pick, revert, tag, or branch -D. Do NOT edit, write, or delete any file. Read the diff, run any read-only checks (git diff, git log, git show), and report concerns or approve." After the dispatch returns, pipe $VERIFIER_RESPONSE through bash "$ZSKILLS_SKILLS_ROOT/update-zskills/scripts/verify-response-validate.sh"; on exit 1 STOP without committing.
The reviewer is READ-ONLY. Include this verbatim in the dispatch prompt (defense-in-depth, preserved alongside the Dispatch-shape preamble above):
You are read-only. Allowed: Read files, run read-only git (diff, log,
show, show-ref, ls-files, ls-remote, status), run existing
tests. FORBIDDEN: git stash (push/-u/save/bare), checkout, restore,
reset, add, rm, commit, editing files, creating worktrees. For
pre-fix behavior, use git show <commit>:<file> — don't modify reality.
(Past failure: reviewer ran git stash -u && test && git stash pop; the
pop silently unstaged the caller's staged files.)
Layer 3 — verifier response validation. Immediately after the dispatch returns:
if [ -f "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh" ]; then
export CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT}"
. "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh"
else
. "$CLAUDE_PROJECT_DIR/.claude/skills/update-zskills/scripts/zskills-resolve-config.sh"
fi
printf '%s' "$VERIFIER_RESPONSE" | bash "$ZSKILLS_SKILLS_ROOT/update-zskills/scripts/verify-response-validate.sh"
VALIDATE_EXIT=$?
On VALIDATE_EXIT=1 — STOP without committing. Do NOT stage anything else, do NOT amend, do NOT proceed to step 4. Emit:
STOP: verifier returned without meaningful results.
$(cat /tmp/last-validate-stderr)
This is a verification FAIL, not a license to commit. Resolve the
verifier failure (re-dispatch only after confirming the verifier
agent file is installed: .claude/agents/verifier.md exists; bash
$CLAUDE_PROJECT_DIR/.claude/hooks/inject-bash-timeout.sh < /dev/null
exits 0). If the verifier agent is missing, run /update-zskills.
If the agent raises concerns: STOP. Report the concerns to the user.
Do not commit until concerns are resolved.
If the agent approves AND VALIDATE_EXIT=0: proceed to step 4.
-
Resolve the Co-Authored-By trailer from config, then commit.
The trailer value lives in .claude/zskills-config.json under
commit.co_author. Source the canonical helper to populate
$COMMIT_CO_AUTHOR. If the field is absent or the file is missing,
$COMMIT_CO_AUTHOR is empty — that's a valid consumer opt-out
(no Co-Authored-By trailer):
if [ -f "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh" ]; then
export CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT}"
. "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh"
else
. "$CLAUDE_PROJECT_DIR/.claude/skills/update-zskills/scripts/zskills-resolve-config.sh"
fi
Commit using a HEREDOC for clean formatting. The heredoc delimiter
must stay quoted (<<'EOF') so that any $(...), backticks, or
$VAR substrings that happen to appear in <type> or <message>
are treated as literal text rather than executed by the shell. The
trailer is injected separately via git commit --trailer, which
handles RFC 5322 formatting and keeps $COMMIT_CO_AUTHOR out of the
heredoc body entirely. Emit the trailer ONLY if $COMMIT_CO_AUTHOR
is non-empty:
if [ -f "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh" ]; then
export CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT}"
. "${CLAUDE_PLUGIN_ROOT}/skills/update-zskills/scripts/zskills-resolve-config.sh"
else
. "$CLAUDE_PROJECT_DIR/.claude/skills/update-zskills/scripts/zskills-resolve-config.sh"
fi
if [ -n "$COMMIT_CO_AUTHOR" ]; then
git commit \
--trailer "Co-Authored-By: $COMMIT_CO_AUTHOR" \
-m "$(cat <<'EOF'
<type>: <message>
EOF
)"
else
git commit \
-m "$(cat <<'EOF'
<type>: <message>
EOF
)"
fi
-
Verify:
git status -s
If a pre-commit hook fails: fix the issue, re-stage, and create a NEW
commit. NEVER use --amend after a hook failure — the commit didn't happen,
so amend would modify the PREVIOUS commit. Maximum 2 attempts on the same
hook error — if it fails twice, STOP and report.
Phase 6 — Push (if push argument)
Only if push was in the arguments:
git push
If there's no upstream tracking branch:
git push -u origin <branch-name>
NEVER force-push to main/master. If push is rejected, tell the user why
and ask what to do.
Phase 6 (PR subcommand) — PR Mode (if pr is the first token)
When the first argument token is pr, this flow replaces Phases 1–5.
Read modes/pr.md in full and follow its procedure
end-to-end. Do not proceed until you have read that file.
Phase 7 — Land (if land argument)
Read modes/land.md in full and follow its procedure
end-to-end. Do not proceed until you have read that file.
Key Rules
- NEVER use
git add . or git add -A — stage files by name only.
- NEVER touch unrelated changes — other agents may have work in progress.
Do not
git checkout --, git restore, git reset, or otherwise discard
changes you didn't make.
- NEVER amend after hook failure — create a new commit instead.
- NEVER force-push to main/master — warn and ask.
- NEVER push without the
push argument — commit only by default.
- Do NOT stash — hook blocks
git stash push/-u/save/bare. For
cherry-pick flows, use the try-without-stash pattern (Phase 7 step 3).
- If unsure whether a file is yours: ask. The cost of asking is low. The
cost of committing or discarding someone else's work is high.
- Include
.claude/logs/ — session logs should be committed alongside
code changes.
- Always use HEREDOC for commit messages — ensures clean multi-line
formatting.
- Scope hint is advisory, not absolute — even with a scope hint, check
diffs. Some related files won't match the hint keywords, and some keyword
matches may be unrelated. The hint narrows the search; it doesn't replace
judgment.
/commit pr requires a clean working tree — all changes must be
committed before running PR mode. The pre-check enforces this.
/commit pr keyword is first-token-only — prevents false-triggering
on scope hints that contain "pr" mid-string.
- PR body uses
git log origin/main..HEAD — not git log main..HEAD
(local main may be stale after rebase).
- PR number from URL, not bare
gh pr view — bare view uses ambient
branch autodetect, which is subject to race conditions.