Git trunk branch and pr automation
Skill stealth-engine/skills/skills/git-trunk-branch-and-pr-automation
Trunk-based Git workflow with enforced branch naming and squash-merge PR titles. Use when setting up or standardising a branch/PR workflow, naming branches (feature/ fix/ hotfix/ and AI-agent prefixes claude/ cursor/ codex/ copilot/ codegen-bot/ dependabot/), configuring squash-only merges where the PR title becomes the commit and the body is the concatenated commits, making the PR title a valid Conventional Commit, adding GitHub Actions that validate branch names or auto-normalise PR titles, fixing a PR-title bot that loops, or deciding trunk vs release branches. Covers the GitHub repo settings, the validation/normalisation workflows, and how it feeds semantic-release.From its SKILL.md
npx -y skills add stealth-engine/skills --skill git-trunk-branch-and-pr-automationAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
8.5 KB, ~2.0k tokens by cl100k_base, as published. Nobody here has run it
Trunk-based branches + squash-PR automation
A trunk-based workflow where every change is a short-lived branch off main,
merged via squash with a Conventional Commit PR title. That title becomes
the single commit on main that semantic-release
reads — so naming and title hygiene aren't cosmetic, they drive the release.
Templates: templates/ — branch-name validation, the PR-title
normaliser workflow, and the shared normalize-pr-title.js.
The model
- Trunk-based: branch off
main, keep PRs small, merge frequently;mainstays releasable. Don't use long-liveddevelop/release branches unless you must stabilise a release while trunk moves on, or support multiple live versions. - Branch naming:
feature/<desc>,fix/<desc>,hotfix/<desc>— plus AI-agent prefixesclaude/ cursor/ codex/ copilot/ codegen-bot/ dependabot/for agent- and bot-authored branches. Validated bybranch-name-check.yml.
Squash merge: the PR title is the commit
Configure the repo so a merge collapses to one clean, semantic commit:
- GitHub → Settings → General → Pull Requests: enable Squash merging only
(turn off merge commits and rebase). Set "Default commit message" →
"Pull request title and commit details" — GitHub then uses the PR title as the
squash subject (it appends
(#<PR-number>), which doesn't affect Conventional Commit parsing) and concatenates the PR's commit messages into the body. - So: the title must be a valid Conventional Commit (it's what semantic-release analyses → version + changelog); the body (the concatenated commits) preserves the detailed history.
- Keep a PR to one logical change (in a monorepo, one package) so the single
squashed commit maps cleanly to one type+scope. See
conventional-commits.
PR-title automation
pr-title-manager.yml +
normalize-pr-title.js keep the title valid:
- On open/reopen/synchronize it normalises the title to Conventional Commits
(lowercasing the type, or synthesising
<type>: <subject>from the PR's commits / branch prefix when the title isn't conventional). - A correct title is left untouched; the
skip-title-automationlabel opts out of the auto-rewrite for the rare edge case (still validated — see Opting out below). - It posts a sticky comment explaining the squash convention.
- Install the script at
.github/scripts/normalize-pr-title.js; the workflowsparse-checkouts the.github/scriptsdirectory to load it.
Pair it with branch-name-check.yml, and in branch protection require both
checks (plus your release/CI checks) before a PR can merge.
Opting out of the auto-rewrite
There are two cases — and the first covers almost everyone:
- Just write a valid Conventional Commit title. The normaliser only rewrites a
title that isn't already conventional; a correct title (
feat: add x,fix(api): …) is left completely untouched. There's nothing to "skip" — this is the intended path. - Add the
skip-title-automationlabel (create it once in the repo) only for the rare case where the title isn't conventional yet and you don't want the bot auto-editing it / posting its comment while you sort it out (e.g. mid-review). The label suppresses the auto-rewrite + sticky comment — but not validation: the required check still fails until the title is a valid Conventional Commit (or atype!:breaking commit is reflected by a!). So the label changes who fixes the title (you, not the bot), never whether it must be valid before merge.
The opt-out is a label, not a string in the title, on purpose: the PR title becomes the squash commit semantic-release reads, so any marker left in the title would land in the release commit and silently produce no release. The label is also ignored on the fork/bot validate path (it never edits there, so there's nothing to suppress).
Gotchas
- Three modes — and don't loop on your own edits. The template classifies each
PR:
skiponly forgithub-actions[bot](its own title edits — the workflow listens foreditedto re-validate human changes, and skipping its own identity stops the loop). With the defaultGITHUB_TOKENthis is belt-and-braces — GitHub deliberately doesn't retrigger workflows on events its own token caused, so the bot's edit wouldn't re-fire anyway; the skip only matters if you swap in a PAT or GitHub App token (whose edits do retrigger), so keep it.validatefor forks and other bots like dependabot (read-only/untrusted → title is checked but never auto-edited);normalizefor same-repo human PRs (run the script + fix the title). Don't blanket-skip all bots — a non-self bot with a non-conventional title would otherwise merge unchecked. - Agent/bot branch prefixes don't infer a type.
claude/ cursor/ codex/ …are valid branch names, but the normaliser derives the type from the PR's commits (which should be conventional), not the prefix — falling back tochoreonly if neither title nor commits are conventional. Keep agent commits conventional so the bump is right. - Fork / bot PRs are validate-only.
pull_requestfrom a fork gets a read-only token and an attacker-controlled checkout, so the template never checks out or runs the (PR-modifiable) script for them — it validates the title inline via the API (read-only) and fails if it isn't a valid Conventional Commit (any type, including non-releasingdocs:/chore:), or if atype!:-style breaking commit isn't reflected by a!in the title. Same-repo human branches (the norm for this trunk-based workflow) are the only ones auto-edited. Configure dependabot with a conventionalcommit-message.prefixso its titles pass. - Squash settings are per-repo and easy to miss — if "Default commit message" is left as "Default" (the first commit's message) instead of "Pull request title and commit details", your carefully-named PR title is ignored at merge.
- The opt-out suppresses the rewrite, not the validation. The
skip-title-automationlabel stops the auto-edit + sticky comment but the title is still checked (the required check fails until it's a valid Conventional Commit) — full details in Opting out above. A green check always means a releasable title; the label can't bypass that.
See also
conventional-commits— the format the title must follow.semantic-release-automation— consumes the squashed commit to cut the release.production-release-gating— deploys only the resulting release.resolve-merge-conflicts— resolving the conflicts/behind-base updates a branch hits before it can squash-merge.
Sources
- Generalised from a production app's
branch-name-check.yml(thefeature|fix|hotfix|codegen-bot|copilot|codex|cursor|claude|dependabotprefix set) andpr-title-manager.yml+normalize-pr-title.js(title normalisation, theskip-title-automationlabel opt-out, bot-cascade guard, sticky comment). - GitHub squash-merge commit-message options: https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/configuring-commit-squashing-for-pull-requests
What ships with it: 3 files
21.5 KB alongside SKILL.md, 1 of them executable
templates/
- branch-name-check.yml3.3 KB
- normalize-pr-title.jsruns8.0 KB
- pr-title-manager.yml10.2 KB