mcpbeat Sign in

Git Workflow Agent Skill

Use when naming or scoping a branch, writing or fixing a commit message, picking the gitmoji for a commit, untangling history (rebase versus merge versus squash), or cutting a versioned release — the portable git-convention layer for any repo. Covers gitmoji + Conventional Commits, SemVer tags, branch hygiene, force-push safety and gh pr/release mechanics. NOT the land-it decision and pre-ship checklist (that is `ship`), NOT an isolated checkout before coding (that is `worktrees`), NOT CI/CD release automation (that is `deployment`).

8k tokens
context cost
the whole folder, loaded on every use
5
files
instructions only
0
copies elsewhere
how many repositories repackaged it
105
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/ericrisco/rsc-harness --skill git-workflow

What comes with it

16 392 bytes besides the instruction
evals/README.md
evals/cases.yaml
references/gitmoji.md
references/interactive-rebase.md

The instruction itself

8 sections, as written by the author

git-workflow — the grammar and hygiene of version control

Git history is a message to the next human who reads git log, runs git blame on a broken line,

or bisects a regression at 2am. That human is usually future-you. **Every rule in this skill exists

to make the next reader's job faster**, not to make this moment cheaper. A repo with legible branch

names, conventional commits, and a clean linear narrative is a repo you can reason about; a repo with

wip, fix stuff, and force-pushed shared history is one you fight.

This is the portable convention layer. It is independent of any SDD phase or CI platform — it is

the grammar that ../ship/SKILL.md, ../worktrees/SKILL.md, and ../deployment/SKILL.md all lean on.

It does not decide *whether to land* the work (that is ship) and it does not automate releases in a

pipeline (that is deployment). It tells you how to name, commit, untangle, and tag — correctly.

Branch naming

Name a branch from its *intent*, prefixed by its kind, as a kebab-case slug. Keep it short-lived:

hours to days, not weeks. Long branches drift from main and turn into merge pain.

| Prefix | Use for | Why |

|--------------|----------------------------------------------|--------------------------------------------------|

| feat/ | a new capability | matches the feat commit type; signals a MINOR |

| fix/ | a bug fix | matches fix; signals a PATCH |

| hotfix/ | an urgent fix landing straight to production | flags "skip the slow path" to reviewers |

| chore/ | tooling, deps, config — no product behavior | keeps non-feature noise out of the feature log |

| docs/ | documentation only | reviewers can fast-track, no test gate needed |

| refactor/ | restructure without behavior change | sets the expectation: tests stay green, no new behavior |

Slug rules: derive it from the issue title or the one-sentence intent, lowercase, dash-separated, no

spaces or / inside the slug. Optionally suffix the issue number.

Bad   my-stuff            (kind unknown, intent unknown)
Bad   eric-branch-2       (names the author and a counter, not the work)
Good  feat/oauth-pkce-flow
Good  fix/expired-refresh-token-401
Good  chore/bump-node-22

Commit grammar — gitmoji + Conventional Commits

Write every commit to Conventional Commits 1.0.0, opened by a gitmoji. The structure:

<gitmoji> type(scope)!: subject

body — what changed and why, wrapped, optional

BREAKING CHANGE: description of the incompatible change
Fixes #123
  • The gitmoji is mandatory and comes first — the intention of the change, readable in one glyph

when you scan git log --oneline. type is what tooling reads; the emoji is what humans read.

  • type is mandatory. scope in parentheses is optional. ! before the colon marks a breaking change.
  • Subject: imperative mood ("add", not "added"/"adds"), ≤72 chars, no trailing period.
  • Body explains *why*, not *what the diff already shows*. Separate from subject by a blank line.
  • Footers go last. Fixes #123 / Closes #123 in the body auto-closes that issue when the PR merges.

Type → SemVer effect:

| Type | SemVer bump | Notes |

|-----------------------------------|-------------|----------------------------------------------------|

| feat | MINOR | a new capability |

| fix | PATCH | a bug fix |

| docs, chore, refactor, test, build, ci, perf, style, revert | none | allowed, but no implicit version bump |

| any type with ! or a BREAKING CHANGE: footer | MAJOR | overrides the above regardless of type |

BREAKING CHANGE must be uppercase in the footer; the type/scope units are case-insensitive but

write them lowercase by convention.

Type → gitmoji, the everyday set (the full 75-emoji table, and *why* the emoji never replaces the

type, are in references/gitmoji.md):

| Type | gitmoji | Type | gitmoji | Type | gitmoji |

|------------|---------|------------|---------|-----------|---------|

| feat | ✨ | refactor | ♻️ | build | 📦️ |

| fix | 🐛 | test | ✅ | style | 🎨 |

| docs | 📝 | perf | ⚡️ | revert | ⏪️ |

| chore | 🔧 | ci | 👷 | breaking | 💥 |

Pick by intention, not by which file changed, and prefer the specific one: 🚑️ for a production

hotfix, 🩹 for a trivial non-critical fix, 🔥 for a deletion, 🚚 for a rename, ⬆️ for a dep bump,

🔖 for a release commit.

Bad   fix stuff
Bad   updates
Bad   Fixed the login bug.            (past tense, capitalized, trailing period)
Bad   fix(auth): reject expired refresh tokens        (no gitmoji)
Bad   ✨ added a search endpoint                       (gitmoji but no type → no derivable bump)
Good  🐛 fix(auth): reject expired refresh tokens
Good  ✨ feat(api): add /v2/search endpoint with cursor paging
Good  ♻️ refactor(parser): extract token scanner, no behavior change

A breaking change, both forms equivalent:

💥 feat(api)!: drop the legacy /v1 search endpoint

BREAKING CHANGE: /v1/search is removed; callers must migrate to /v2/search.

If the repo runs a strict conventional parser (commitlint, semantic-release) it anchors the type

at position 0 and rejects the emoji prefix. Either widen its headerPattern — the config is in

references/gitmoji.md — or move the emoji behind the header (feat(api): ✨ add cursor paging),

which every parser accepts. Both forms satisfy this convention; dropping the gitmoji does not.

Authorship is always Eric. Never add a Co-Authored-By: Claude trailer, never a

"Generated with" footer, never any line crediting an AI tool — in a commit *or* a PR body. The work

is Eric's; the agent is a tool, like the compiler.

History hygiene — rebase, merge, or squash?

Decide by who else has the commits. The lease rule below is non-negotiable.

| Situation | Do this | Why |

|------------------------------------------------------|------------------------------------------------------|------------------------------------------------------------|

| Private branch, only you have it, want linear history | git rebase main, then git push --force-with-lease| rebase rewrites hashes; safe because nobody built on them |

| Branch others have pulled / built on | git merge mainnever rebase it | rebase changes every hash; collaborators' work diverges |

| Noisy PR (many wip commits) | squash-merge into one gitmoji + conventional commit | main gets one meaningful entry, not 9 scratch commits |

| Already pushed, shared, *and* you rewrote it | STOP — coordinate, or git revert instead | force-pushing shared history breaks everyone downstream |

After a rebase, push with --force-with-lease, never bare --force:

git push --force-with-lease   # refuses if the remote moved since you fetched — catches a teammate's push
git push --force              # blindly overwrites — can erase a teammate's commits

The interactive cleanup loop (rebase -i, fixup/squash/reword/drop, --autosquash,

the conflict→continue cycle, and recovery via git reflog) is a long branchy procedure — see

references/interactive-rebase.md rather than reaching for it on every commit.

Releases

Derive the version bump from the commit log, never by guessing. Scan the commits since the last tag:

  • any BREAKING CHANGE: / !MAJOR (v1.4.2v2.0.0)
  • otherwise any feat:MINOR (v1.4.2v1.5.0)
  • otherwise only fix:/others → PATCH (v1.4.2v1.4.3)

Tag with the vMAJOR.MINOR.PATCH form, annotated, then create the release with auto-generated notes:

git tag -a v2.0.0 -m "v2.0.0"
git push origin v2.0.0
gh release create v2.0.0 --generate-notes              # notes via the GitHub Release Notes API
gh release create v2.0.0 --generate-notes --draft      # stage notes, publish later
gh release create v2.0.0-rc.1 --generate-notes --prerelease

GitHub auto-assigns the "latest" label by semver order unless you set it. With release immutability

enabled, a published release's tag cannot be edited or deleted — get the version right before you

publish.

Bad   added a feature + a breaking config change, tagged v1.5.0   (breaking change → must be MAJOR)
Good  same changes → v2.0.0, bump derived from the BREAKING CHANGE footer in the log

Automating any of this on tag push (a release.yml workflow, OIDC to a registry) is deployment

see ../deployment/SKILL.md. This skill covers the manual/local release act.

PR mechanics — then hand off to ship

Open the PR with autofilled title/body from the commits, against the right base:

gh pr create --fill --base main          # title/body from commits; --base falls back to repo default

Put Fixes #123 in the body to link and auto-close the issue on merge. A PR body should let the

reviewer understand the change without reading every line of the diff.

The decision to land — direct-merge vs PR vs park, the pre-ship safety checklist, the actual

merge — belongs to ../ship/SKILL.md. This skill only makes the branch, commits, and PR body clean

enough to hand over. Setting up the isolated checkout *before* you start coding is

../worktrees/SKILL.md.

Anti-patterns

| Anti-pattern | Why it hurts | Instead |

|-------------------------------------------------------|-----------------------------------------------------------|------------------------------------------------------|

| git push --force on a shared branch | silently erases teammates' commits | --force-with-lease, or don't rewrite shared history |

| git commit -m "wip" / "fix" / "updates" | the log carries zero signal for the next reader | <gitmoji> type(scope): imperative subject |

| Commit message with no gitmoji | git log --oneline reads as a wall of undifferentiated text | pick the intention's emoji (references/gitmoji.md) |

| Mixing unrelated changes in one commit | can't revert or review one concern in isolation | one logical change per commit |

| Long-lived branch (weeks) | diverges from main, merge becomes a battle | short-lived; rebase or merge main in often |

| Hand-computing the semver bump | breaking change shipped as a MINOR → broken downstream | derive the bump from the commit log |

| Rebasing a public/shared branch | rewrites hashes others built on | merge shared branches; rebase only private ones |

| Committing generated/secret files | leaks credentials, bloats history irreversibly | .gitignore; rotate any secret that slipped in |

| BREAKING CHANGE lowercase | tooling won't detect it → wrong (too-low) bump | uppercase BREAKING CHANGE: in the footer |

| PR with no description | reviewer reverse-engineers intent from the diff | --fill plus a why, link the issue |

| Tagging a release with no notes | users can't tell what changed | gh release create --generate-notes |

| Co-Authored-By: Claude / "Generated with" footer | forges authorship onto a tool | author is always Eric; no AI attribution |

Checklist — before a PR or a release

  • [ ] Working tree clean (git status), no stray or generated files staged.
  • [ ] Branch rebased on / merged with current main; no avoidable conflicts.
  • [ ] Every commit carries its gitmoji and a conventional header; wip/scratch commits squashed away.
  • [ ] No secrets, no AI-attribution trailers.
  • [ ] PR body explains the why and links the issue (Fixes #).
  • [ ] (Release) version bump derived from the commit log, tag is vX.Y.Z, annotated.
  • [ ] (Release) gh release create vX.Y.Z --generate-notes; version confirmed before publishing (immutable once published).

How to use it

Copy the folder

Take ericrisco/git-workflow from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.