Git trunk branch and pr automation
Skill stealth-engine/skills/skills/git-trunk-branch-and-pr-automation
Agent skills we use daily in the studio — reusable, on-demand context for Claude Code, Cursor & other skills.sh-compatible AI agents. Install: npx skills add stealth-engine/skills
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.
What its author says it does
Copied from the file, not written here
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.
SKILL.md
8.5 KB, 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