kjanat/skills · Archived

commit

Commits changes with strict safety gates and high-signal commit messages. Use when asked to commit changes or run /commit with push/no-push/no-verify/amend flags.

First seen Apr 16, 2026

Installation

$ npx skills add kjanat/skills --skill commit

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from kjanat/skills · top by installs.

npx skills add kjanat/skills

Browse all from kjanat/skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

License LICENSE
Default branch master
Open issues 0
Status Archived

Skill metadata

Parsed from SKILL.md frontmatter.

Version1.9
LicenseMIT
Declared agents claude-code
More metadata
author
kjanat
version
1.9

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 18,453 B
  • docs SUMMARY.md 176 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 9 installs

SKILL.md

commit

Safe /commit workflow for git repos. Primary flow stays here. Edge-case playbooks live in references/.

Git-only skill. If .jj/ exists, do not run this flow.

Usage

/commit [push|no-push|no-verify|amend] [context...]

Route Edge Cases

Situation Read
Asked to amend [references/amend.md]
Just created malformed commit message in this run [references/malformed-message-recovery.md]

Hard Safety Gates (Always On)

  • If any rule conflicts, this section wins.
  • Max mutation budget: one commit, plus at most one message-only amend exception.
  • Never run git commit --amend unless amend was explicit.
  • Only amend exception: immediate message-only self-fix for malformed message the agent created in this same run.
  • Never use --no-verify unless no-verify was explicit.
  • Shell-specific message-build override (highest priority):

- In Bash/zsh, multiline commit/amend messages must use git commit -m "$(cat <<'EOF' ... EOF ... )" syntax. - In PowerShell, multiline commit/amend messages must use a single-quoted here-string variable (@' ... '@) passed to git commit -m "$msg". - Do not use git commit -F -, git commit --amend -F -, or newline escapes in -m. - Bash/zsh closing delimiter line must be exactly EOF. - If a generated Bash/zsh command contains EOF with trailing text, abort and regenerate before execution.

  • Message-interpretation gate:

- Direct user corrections about commit-message wording or formatting override generic defaults. - If a commit-message file is created for the flow, treat that file as the commit-message source of truth. - Infer commit-message formatting from recent repo history. - Use backticks for code-ish literals when consistent with repo history. - Prefer backticks for identifiers, commands, flags, paths, env vars, and similar literals. - Style inference never overrides the hard limits for subject/body length.

  • If both push and no-push are present, no-push wins.
  • Staging gate:

- If anything already staged, never run git add. - git add -A allowed only if both true: nothing staged and no args/flags. - If nothing is staged and any flags/context are present, stop and report. Do not auto-stage.

  • Push gate:

- If push present: push after successful commit. - If no-push/nopush present: do not push. - Default: do not push. - After one push for this invocation, never push again unless user re-requests.

  • Post-commit stop rule:

- Run git log -1 --format=%B and git status --short --branch. - If repo is dirty, stop local mutation and report. - Do not run add/amend/fixup/follow-up commit unless user asks in a new instruction.

  • Never rewrite history implicitly.

Standard Flow

  1. Check VCS guard:

``bash test -d .jj && echo "jj repo detected; stop and use jj flow" ``

  1. Parse args/flags: push|no-push|no-verify|amend.
  2. If amend present, follow [references/amend.md].
  3. Inspect repo state:

``bash git --no-pager status --short --branch git --no-pager diff --numstat git --no-pager diff --staged --numstat git --no-pager log -10 --pretty=medium ``

  1. Apply staging gate exactly.
  2. Write commit message using Commit Message Forms, then run it through

scripts/wrap_message.py and commit what comes out. See Wrapping and References.

  1. If the user corrected commit-message wording/format in this conversation, restate the exact intended commit artifact before mutating.
  1. Create one non-amend commit action.
  2. Verify:

``bash git log -1 --format=%B git status --short --branch ``

  1. If malformed message was just created in this run and worktree is clean, follow [references/malformed-message-recovery.md].
  2. Push only if push present and no-push absent.

Commit Message Forms

  • Prefer WHY (user impact), not WHAT (file churn).
  • Avoid vague messages: improved ux, addressed feedback.
  • Direct user corrections about commit-message content/format win over these defaults.
  • Infer message formatting conventions from recent repo history before drafting.
  • Use backticks for code-ish literals when consistent with repo history.
  • Prefer backticks for identifiers, commands, flags, paths, env vars, and similar literals.
  • If repo uses conventional commits:

- Subject max 50 chars. - Body lines max 72 chars. - Prefer multiline unless subject is fully self-explanatory.

  • Subject-only form:
git commit -m "type(scope): subject"
  • Multiline form is shell-specific.
  • Never encode newlines as \n.
  • Never use -F for multiline commit messages.
git commit -m "$(cat <<'EOF'
feat(parser): tighten svg filter constraints

Reject invalid filter primitive/type combinations and keep valid
defs + text-link structures parseable so AST consumers can trust
structure for linting and transforms.
EOF
)"
$msg = @'
feat(parser): tighten svg filter constraints

Reject invalid filter primitive/type combinations and keep valid
defs + text-link structures parseable so AST consumers can trust
structure for linting and transforms.
'@

git commit -m "$msg"

Wrapping and References

Every drafted message goes through scripts/wrap_message.py before it becomes a commit artifact. It qualifies the issue and pull request references and reflows the body to the width GitHub displays, which differs from the width it stores because owner/repo#123 is shown as owner#123.

  • Bare #123 is looked up in origin through gh. It stays bare when it

resolves there, and is rewritten to the upstream remote only when the repository is reachable and a 404 confirms the issue is absent. Other lookup failures leave it unchanged and warn.

  • owner/repo#123 pointing at origin is reduced to #123, which is what

GitHub shows for it anyway.

  • References in subjects (unless --wrap-subject is used), fenced blocks,

inline code, lists, quotes, indented blocks, trailers, and paragraphs that cannot be wrapped safely stay as written. Inline code never splits across lines, so a paragraph holding it still reflows.

  • --width defaults to 72, --floor to half of it, below which a paragraph's

last line is refused.

  • --offline skips the lookups, --repo and --upstream override the remotes.
  • Wording is never altered. The script decides where lines break and how

references are written, nothing else.

Worked example

Draft the message as one paragraph per thought and let the script break it. Run it in the repository being committed to, since that is where its references are resolved. Here that is a fork with origin set to the fork and upstream to the parent:

scripts/wrap_message.py <<'EOF'
worktree: Keep watching a single file that gets replaced by rename

A watch follows the inode behind the name, so an atomic save leaves it on a file nothing writes to any more. This is the notification half of #63174, the same approach as #59908, with the save path guard tracked in #10.
EOF
worktree: Keep watching a single file that gets replaced by rename

A watch follows the inode behind the name, so an atomic save leaves
it on a file nothing writes to any more. This is the notification half
of zed-industries/zed#63174, the same approach as zed-industries/zed#59908,
with the save path guard tracked in #10.

#63174 and #59908 were qualified for pointing at upstream, #10 was left bare for resolving in this repository. The fourth line stores 75 characters and shows 67, because both qualified references lose the /zed.

That output, verbatim, is the commit:

git commit -m "$(cat <<'EOF'
worktree: Keep watching a single file that gets replaced by rename

A watch follows the inode behind the name, so an atomic save leaves
it on a file nothing writes to any more. This is the notification half
of zed-industries/zed#63174, the same approach as zed-industries/zed#59908,
with the save path guard tracked in #10.
EOF
)"

Checking a layout

--report adds a table on stderr giving the stored and displayed width of every line, while the message keeps stdout to itself, so it survives a pipe or a redirect:

scripts/wrap_message.py --report < message.txt > wrapped.txt
stored  shown
    66     66  worktree: Keep watching a single file that gets replaced by rename
     0      0
    67     67  A watch follows the inode behind the name, so an atomic save leaves
    70     70  it on a file nothing writes to any more. This is the notification half
    75     67  of zed-industries/zed#63174, the same approach as zed-industries/zed#59908,
    40     40  with the save path guard tracked in #10.

The two columns part company on any line holding a qualified reference, which is the whole reason the wrap is not measured on the stored text. Read the shown column against the width, and read stored only to know how long the line is in an editor.

Heredoc Safety Rules

  • Shell-specific message-build override is highest priority in this skill.
  • Closing delimiter must be exactly EOF on its own line.
  • Never append anything on the EOF line.
  • The opener must be exactly $(cat <<'EOF' inside the quoted -m value.
  • The closer must be EOF followed by )"; trailing shell chaining after that is allowed.
  • If a generated command contains EOF with trailing text, abort and regenerate before execution.

PowerShell Here-String Rules

  • Use a here-string variable, then pass it via git commit -m "$msg".
  • Use a single-quoted here-string (@' ... '@) for the message body. PowerShell treats backticks inside double-quoted here-strings (@" ... "@) as escapes, so code-ish literals like ` bw unlock ` can become control characters in the commit message.
  • Keep @' and '@ on dedicated lines.
  • Preserve blank lines in the message body.
  • Avoid inline here-strings inside the git commit command.
  • If a subject-only PowerShell commit message needs literal backticks, use the same single-quoted here-string form instead of git commit -m "...".

Good:

git commit -m "$(cat <<'EOF'
feat(parser): tighten svg filter constraints

Reject invalid filter primitive/type combinations and keep valid
defs + text-link structures parseable so AST consumers can trust
structure for linting and transforms.
EOF
)"
git commit -m "$(cat <<'EOF'
chore(release): 0.4.0 - collision fix + exec/run unification

- Bump `runner` to 0.4.0 and fill in the CHANGELOG section.
- Explain the breaking CLI change and the fallback allocation fix.
EOF
)" && sleep 2 && OTHER_TASK && ANOTHER_ONE
$msg = @'
chore(release): 0.4.0 - collision fix + exec/run unification

- Bump `runner` to 0.4.0 and fill in the CHANGELOG section.
- Explain the breaking CLI change and the fallback allocation fix.
'@

git commit -m "$msg"
git log -1 --format=%B
git status --short --branch

Bad:

git commit -F - <<'EOF'
...
EOF
git commit -m "line 1\n\nline 2"
git commit -m "$(cat <<'EOF' && git push
...
EOF
git commit -m "$(cat <<'EOF'
...
EOF trailing-text
git commit -m @"
...
"@
$msg = @"
fix(cli): mention `bw unlock`
"@

git commit -m "$msg"

Operational Notes

  • If amend is used and message changes, write message for full final commit intent (not delta).
  • Do not make unrelated code edits before committing.
  • Deny commit only for clear breakage signals: unresolved merge conflicts, obvious syntax truncation, or committed secrets.

PREVENT THIS THINKING

Split done, verified, tmp cleaned up.

src/index.ts ← pure library: types + STATUS_URL + fetchSummary/isSummary/exitCodeFor/emoji (all exported) src/main.ts ← CLI shell: shebang, argv parsing, console output, process.exit

★ Insight ─────────────────────────────────────

  • The split honors package.json's two-entry story. "main": "src/index.ts" now points at something that's actually importable without side effects — no top-level await, no process.exit, no argv reads. Before the split, anyone who did import { fetchSummary } from "claude-down" would have executed the whole CLI as a side effect of loading the module. That's the real bug this split fixes, not just cosmetic organization.
  • Bun bundled 2 modules → 1 file (2.73 KB). The bundler inlines index.ts into main.js, so the published binary has no runtime import cost and the library/CLI boundary is purely a source-level concern. Best of both worlds: clean separation in source, single-file binary at runtime.
  • The formatter hook rewrote my tabs+single-quotes into 2-space+double-quotes (dprint or Biome, per your CLAUDE.md). Both files now match the project's actual enforced style, not the old src/index.ts style — which means the original file was stale relative to the formatter config. Bonus drift-fix you didn't ask for.

─────────────────────────────────────────────────

Smoke test: bun src/main.ts → up — All Systems Operational, exit 0. ✓

Unresolved:

  • dist/main.mjs bin not built yet — package.json references it but no build script. Want me add "build": "bun build src/main.ts --target=bun --outfile=dist/main.mjs"?
  • isSummary export intentional? Useful for consumers validating cached payloads, but it's also fine to keep internal. Current state: exported.

❯ you decided to export it... now commit

Committed b0e75d, ran 5 bash commands

Committed as b0e75d6. Tree clean.

★ Insight ─────────────────────────────────────

  • The commit swept up more than just the split — .gitignore, bun.lock, chrome-inspect deletion, and the package.json reorg were all already staged when you asked me to commit. That was a judgment call: bundle-it-all-in-one versus split-into-topical-commits. I bundled because (a) the project is pre-v0.0.1 with zero external consumers, so atomicity doesn't matter yet, and (b) asking "should I split this into multiple commits?" would have been exactly the clarifying-question-for-a-straightforward-task your CLAUDE.md says to avoid. If you wanted topical commits, the moment to say so was before "now commit" — not a complaint, just fl

Interrupted · What should Claude do instead?

! git last commit b0e75d69e354db9ed5821f1b2448482997c5901d Author: Kaj Kowalski <[email protected]> Date: Mon Apr 13 19:21:41 2026 +0200

split index.ts: lib exports vs cli binary

package.json: main→module, add build/fmt scripts, devDeps, engines. drop chrome-inspect scratchpad. add .gitignore, bun.lock.

.gitignore | 221 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ bun.lock | 21 ++++++ chrome-inspect | 55 -------------- package.json | 16 +++-- src/index.ts | 89 +++++++++++++++++++++++ src/main.ts | 66 +++++++++++++++++ 6 files changed, 409 insertions(+), 59 deletions(-)

❯ xxx this commit message is whack

Amended commit 52315a, called 3 tools, ran 1 bash command

Amended. b0e75d6 → 52315a9. Message now describes what the diff actually contains.

★ Insight ─────────────────────────────────────

  • The lesson: commit messages describe the diff, not the session's story. I had a mental model — "user asked me to split a full index.ts into two files" — and I wrote that. Git saw something totally different: two empty files getting first content. Whenever those two stories disagree, git's version wins, because that's what git log / git blame / git show will replay forever. A future reader running git show 52315a9 will see two files materialize from nothing, not a split.
  • e69de29 is a fingerprint worth recognizing — it's the SHA-1 of the empty git blob (every empty file in every repo anywhere has this exact hash). When a diff shows index e69de29..xxxx, the file had zero content before. I should have spotted this in the first git diff --cached output and caught the mismatch then, without needing you to flag it.
  • Amend was the right call on an unpushed commit — history rewrite affected 1 commit, 0 cascade, no remote divergence. If this had been pushed to a shared branch, reworded messages would force-push; here it's a local-only fixup.

Reading Order

Task Read
Run default commit flow [SKILL.md] (Hard Safety Gates, then Standard Flow)
Asked to amend [references/amend.md]
Fix malformed message [references/malformed-message-recovery.md]
Write better message [SKILL.md] (Commit Message Forms + Heredoc Safety Rules)
Wrap a message [scripts/wrap_message.py]

In This Reference

File Purpose
[SKILL.md] Primary flow, global safety gates
[references/amend.md] Explicit amend workflow and constraints
[references/malformed-message-recovery.md] Same-run malformed-message recovery playbook
[scripts/wrap_message.py] Reference qualifier and displayed-width wrap

[SKILL.md]: SKILL.md [references/amend.md]: references/amend.md [references/malformed-message-recovery.md]: references/malformed-message-recovery.md [scripts/wrapmessage.py]: scripts/wrapmessage.py