git-workflow
One consistent, opinionated git workflow that scales from a solo weekend project to a multi-person, multi-month repo — so you (and your agents) stop improvising commit messages, branch names, and release habits per project. The defaults here favor a clean, linear, reviewable history.
Surfaces, separated by the question they answer:
| Surface | Question it answers |
|---|---|
references/project-tiers.md |
"Do I commit to main, use a dev branch, or open PRs?" |
references/conventional-commits.md |
"What exactly goes in the commit message, including AI provenance?" |
references/worktrees-parallel-agents.md |
"How do I run parallel agents without collisions?" |
references/versioning-and-releases.md |
"When and how do I tag a version?" |
references/branch-hygiene.md |
"Which local branches are done vs still in-dev?" |
scripts/branch-status.sh |
Same, as data — classify every local branch. |
scripts/check-commit-msg.sh |
"Is this commit message valid?" |
references/lazygit-cheatsheet.md |
"How do I do this in lazygit?" (learning aid) |
Core principles
- Commit messages are English and follow Conventional Commits, even when the prompt/conversation is in Chinese. Code history is read by tooling (changelogs, SemVer bumps) and future collaborators; a mixed-language log is harder to grep and automate. Prompts stay whatever language you like.
- Prefer a linear history. Default to
pull --rebaseand fast-forward merges; reserve merge commits and squashes for PR integration points. A linear log makesgit bisect,git revert, and blame legible. - One logical change = one commit; one workflow phase = one commit. For
multi-phase /
/workflowswork, commit at each phase boundary so you can roll back or resume cleanly if interrupted. Don't batch unrelated changes. - Never leak secrets into history. Secret scanning + agent-transcript
handling is owned by the
agent-history-hygieneskill — this skill defers to it at commit/merge time rather than reimplementing it. - Keep authorship, AI provenance, and cryptographic signing separate. The human remains Git author/committer; agent harness + model live in canonical trailers; SSH/GPG signing proves key possession, not who wrote each line.
Pick your tier first
Match the workflow to the project's real collaboration needs, not its ambitions. Promote a tier only when the signal actually appears.
| Tier | Shape | Branching | Integration | Promote when… |
|---|---|---|---|---|
| 1 — Solo / early | one main |
commit to main; short-lived local branches optional |
pull --rebase, merge --ff-only |
you need a stable line separate from WIP |
| 2 — prod/dev split | main = released, dev = integration |
feature branches off dev |
merge to dev; fast-forward dev→main at release |
others rely on a deployed/tagged version |
| 3 — Team / grown vibe-coding | main always deployable |
short-lived feat/… etc. → PR |
PR review/CI → squash-merge | ≥2 contributors, or you want a per-feature ship+review gate |
A solo vibe-coding project can jump straight to Tier 3 even with one human:
the PR becomes the "ship a feature" boundary that separates finished work from
in-progress work, and gives CI a place to run. See
references/project-tiers.md for per-tier command recipes and the GitHub-Flow
summary. Read it when deciding whether to branch/PR or when a project outgrows
committing straight to main.
Commit messages
Format (full spec + type table in references/conventional-commits.md — read
it when writing a non-obvious commit or wiring check-commit-msg.sh):
<type>(<optional scope>): <subject>
<body — the "why" for non-trivial agent commits, wrapped ~72 cols>
<optional standard/native footers>
<canonical agent trailers — AI-Assisted-By / Agent-Transcript / Agent-Plan>
- Types:
feat,fix,docs,style,refactor,perf,test,build,ci,chore,revert. - Subject: imperative mood, lowercase start, no trailing period, keep the header ≤ ~72 chars. "add retry to client", not "Added retry." / "Adds…".
- Breaking change:
feat!:/refactor(api)!:or aBREAKING CHANGE:footer → drives a major SemVer bump. - Validate before committing when unsure:
printf 'feat(auth): add token refresh\n' | \
bash skills/local/git-workflow/scripts/check-commit-msg.sh
- Install the message template so the shape is always in front of you:
git config commit.template skills/local/git-workflow/assets/commit-template.txt
Agentic commit contract
Use this portable minimum for commits produced with Claude Code, Codex, Cursor, OpenCode, or another coding-agent harness:
feat(scope): add concise imperative summary
Explain why the change was needed, the resulting behavior, and meaningful
validation. Keep this in English even when the prompt was not English.
AI-Assisted-By: Codex CLI (gpt-5.6-sol)
Agent-Transcript: .specstory/history/session.md
Agent-Plan: .claude/plans/plan.md
- A non-trivial agent commit requires a body. Use the validator's
--allow-no-bodyonly for a genuinely self-explanatory change such as a typo. - Repeat
AI-Assisted-Byfor every distinct harness/model that materially contributed. Never invent a vendor email merely to make GitHub show an AI as a co-author. - Repeat transcript/plan trailers for every staged artifact, using repo-relative
paths. Omit
Agent-Planwhen no plan file is in the commit. - Preserve tool-native metadata (for example Claude's
Co-Authored-By) as additive evidence. Put the canonical fields in the final Git trailer block sogit interpret-trailers --parsecan query them. - When
agent-history-hygieneis installed, generate the block from the staged snapshot instead of copying paths/model names by hand:
bash skills/local/agent-history-hygiene/scripts/agent-commit-metadata.sh
Validate the complete message before committing:
bash skills/local/git-workflow/scripts/check-commit-msg.sh \
--agentic --staged --file /path/to/commit-message.txt
Cryptographic signing is opt-in and user-owned. Configure SSH/GPG signing only when the user explicitly asks; never silently change global Git or harness settings. A vendor-signed cloud-agent commit must not be amended solely to add metadata, because amending replaces the signed commit object.
Branch naming
Use a <prefix>/<kebab-desc> namespace so branches sort and clean up by
intent, and so agent-generated branches are visually distinct from yours:
- Human intent:
feat/…,fix/…,chore/…,docs/…,refactor/…,exp/…(throwaway experiments). Optionally embed an issue:feat/123-oauth. - Agent / vibe-coding:
agent/<desc>— a dedicated namespace you can bulk- triage or delete separately from hand-authored branches. - Claude worktrees:
worktree-<name>(auto-created byclaude --worktree) is a third namespace — see below.
Keeping these separate is what makes branch-status.sh and
git branch --list 'agent/*' cleanup targeted rather than risky.
Merge & sync
Set linear-history defaults (per-repo, or --global to your taste):
git config pull.rebase true # rebase local work onto upstream on pull
git config merge.ff only # refuse merges that can't fast-forward
- Solo/local:
git pull --rebase, thengit merge --ff-only <branch>to land a short-lived branch with no merge commit. - PRs (Tier 3): prefer squash-merge for vibe-coding branches (collapses
noisy WIP into one clean
feat: …commit onmain); use rebase-merge when every commit is already meaningful and you want them preserved. - Drive PRs from the terminal with the forge CLI —
gh(GitHub) orglab(GitLab). These are strongly recommended (not required — the scripts here degrade to plain git if absent):
gh pr create --fill # or: glab mr create --fill
gh pr checks # watch CI
gh pr merge --squash --delete-branch # glab mr merge --squash --remove-source-branch
The vendored create-pull-request skill (github-workflow group) is a
companion for richer PR bodies.
Worktrees for parallel agents
Run each parallel agent/session in its own git worktree so file edits never
collide. Full mechanics + caveats in references/worktrees-parallel-agents.md
— read it before setting up parallel agents or debugging a missing .env in
a worktree.
Essentials:
claude --worktree feature-auth # → .claude/worktrees/worktree-feature-auth/
- Worktrees branch from
origin/HEADby default; setworktree.baseRef: "head"in settings to branch from your local HEAD (unpushed work). - Subagents: add
isolation: worktreefrontmatter, or ask Claude to "use worktrees for your agents". - Gitignore
.claude/worktrees/so worktree contents don't show as untracked files in the main checkout. - Bring gitignored files (e.g.
.env) into new worktrees with a.worktreeincludeat the repo root (seeassets/worktreeinclude.template). It uses.gitignoresyntax and copies only gitignored matches — already- tracked files are in the checkout already, so don't list them. - Bonus: because SpecStory tracks transcripts by directory, a worktree's fresh dir cleanly separates each parallel agent's chat history.
Versioning & releases
Use SemVer (MAJOR.MINOR.PATCH) and let commit types imply the bump:
fix: → PATCH, feat: → MINOR, !/BREAKING CHANGE: → MAJOR. Tag releases
with an annotated, v-prefixed tag on the release commit:
git tag -a v1.4.0 -m "release: v1.4.0" && git push origin v1.4.0
When the whole project is a Python package, make the git tag the single
source of truth (setuptools-scm or hatch-vcs) instead of hand-editing a
version string. Details + config in references/versioning-and-releases.md —
read it when cutting a release or wiring package versioning.
Before you merge / ship
- Phase commits: ensure each phase of the work is its own commit (easy rollback / resume).
- Dev-env reproducibility (optional, recommended): commit
.vscode/settings.json+.vscode/extensions.jsonso a fresh clone gets the same editor setup. (These are tracked, so they don't belong in.worktreeinclude.) - Secret + agent-artifact hygiene — defer to
agent-history-hygiene:
# clean = exit 0; redacted = 10; leaks = 20 → stop and remediate
bash skills/local/agent-history-hygiene/scripts/scan-staged.sh
If the project doesn't check in agent transcripts/plans, drop them
before a squash-merge; if it does, stage them with that skill's
stage-agent-artifacts.sh. Either way, a leaked secret is remediated via
its references/remediation.md (rotate first — never reflexive
git push --force).
4. Agentic squash metadata: a squash merge creates a new commit, so copy the
canonical AI-Assisted-By / artifact trailers into the squash message. The
source commit's cryptographic signature cannot survive object replacement.
5. Branch cleanup after the PR merges:
git fetch --prune
bash skills/local/git-workflow/scripts/branch-status.sh # find gone/merged/stale
When to use this skill
- "Commit this" / "幫我 commit" / "整理一下 git" / "write a commit message".
- "Should I branch or just commit to main?" / "should I open a PR?".
- "Set up worktrees for parallel agents" / "carry my
.envinto the worktree". - "How do I tag / release a version?" / "bump the version".
- "My local branches are a mess — which are done?" / "clean up branches".
- Starting a new repo and wanting a consistent commit/branch/release convention.
When NOT to use
- Secret leaked / scrubbing transcripts / committing chat history — that's
agent-history-hygiene; come back here only for the surrounding commit/merge flow. - GitHub platform automation (CI diagnosis, issue triage, rich PR bodies)
— the vendored
github-workflowgroup skills own that. - A single trivial
git add+git committhe agent already does correctly — no skill needed.
Available scripts
scripts/branch-status.sh [--json] [--stale-days N] [--help]— classify every local branch asactive/merged/gone(upstream deleted, e.g. after a squash-merge) /stale(no commits in N days, default 30). TSV on stdout by default,--jsonfor callers, prose on stderr. Always exits 0 (state is data). Enrichesmerged/gonewithgh/glabwhen available, falling back to pure git otherwise.scripts/check-commit-msg.sh [--file PATH] [--agentic] [--staged] [--allow-no-body] [--types]— default mode validates a Conventional Commits header.--agenticalso requires an English body + canonical harness/model trailers;--stagedcross-checks transcript/plan paths against the index. Exit 0 valid / 1 invalid / 2 bad args or repository context.
Bundled assets
assets/commit-template.txt— commit message skeleton forgit config commit.template.assets/worktreeinclude.template— example.worktreeinclude(gitignored env files only; annotated so tracked files aren't added by mistake).
Reference files
references/project-tiers.md— read when deciding main-vs-dev-vs-PR or when a project outgrows its current tier. Per-tier recipes + GitHub-Flow summary.references/conventional-commits.md— read when writing a non-trivial commit or wiring the validator. Full type table, footers, examples.references/worktrees-parallel-agents.md— read before setting up parallel agents or debugging worktree file-copy / cleanup issues.references/versioning-and-releases.md— read when cutting a release or wiring Python tag-driven versioning (setuptools-scm / hatch-vcs).references/branch-hygiene.md— read when local branches are confusing (PR merged but branch lingers; work continued after merge).references/lazygit-cheatsheet.md— read when you'd rather drive git through lazygit; a learning aid, safe to ignore once the CLI is second nature.
Gotchas
merge.ff onlyrefuses a diverged pull. If upstream moved and you have local commits, a plaingit pull/mergeerrors instead of making a merge commit — that's intended. Resolve withgit pull --rebase(or an explicitgit merge --no-ffwhen you truly want the merge commit).- Squash-merge orphans the source branch. After a squashed PR, the branch's
commits never appear on
main, sogit branch --mergedwon't list it; it shows up asgoneupstream (git branch -vv).branch-status.shclassifies this correctly — delete withgit branch -D, not-d. - A PR can merge and work still continue on the branch. "Merged" is not
always "done".
branch-status.shflagsgoneupstream but won't delete — confirm the branch has no un-pushed commits before removing it. - Tracked files in
.worktreeincludedo nothing. It copies only gitignored matches. A committed.vscode/settings.jsonis already in the worktree; put only truly-ignored files (.env,.env.local,config/secrets.json) there. .worktreeincludeis skipped with a customWorktreeCreatehook. For non-git VCS (SVN/Perforce/hg) the hook replaces default logic — copy local files inside the hook script instead.- English-commit rule holds under Chinese prompts. Don't mirror the prompt's language into the commit subject; translate the intent to English.
- A commit template does not affect
git commit -m. Agent harnesses usually pass-mdirectly, so the template is guidance, not enforcement. Usecheck-commit-msg.sh --agenticfor the actual contract. - Native attribution is not cryptographic signing.
Co-Authored-ByandAI-Assisted-Byare message metadata; a Verified badge comes from a signed commit object. Preserve both when present, but do not describe them as the same guarantee. - Don't tag before the release commit exists. An annotated tag points at a
commit — create/land the release commit first, then
git tag -a, then push the tag. A tag on the wrong commit means re-tagging (-f) and a force-push. gh/glabare optional. Recommend them, but never make a workflow step hard-depend on a forge CLI being installed or authenticated.