Stage 1 of #1724. Everything needed to cut, gate, and ship a release from a release/X.Y branch, with cherry-picks done by hand. No bot. Stage 2 (#1730) builds on this.
Line numbers below are at 7fd926273.
1. release.yml ref handling
- Line 83: accept
refs/heads/main or refs/heads/release/* for workflow_dispatch. The failure message should name the release skill, so a wrong dispatch teaches the agent right when it fails.
- Lines 96-98: the tag ancestry check should accept a commit reachable from main or from any
release/* branch.
- Line 187: stop hardcoding
refs/heads/main. Either push to $GITHUB_REF, or drop the in-workflow push entirely and require the bump to arrive by PR. The workflow already handles a pre-bumped ref at line 179 ("already contains the complete version update"), so that path is a no-op today.
I'd rather remove the push than generalize it. It's the one place CI writes to a protected branch, and dropping it makes the ruleset landmine in the parent moot.
This item needs a planning pass before anyone writes it. The rest of the issue is mechanical.
2. Five main_*.yml entries
main_{quality,website,linux,macos,windows}.yml all trigger on push: branches: [main]. Change to branches: [main, 'release/**']. Same five focused entries, same Main / <lane> check names, no new workflows, so the shape rules in .github/AGENTS.md still hold. pr_*.yml needs nothing, it plans from pull_request.base.sha.
3. release/** ruleset
New branch ruleset targeting refs/heads/release/**: deletion, non-fast-forward, linear history, the same five PR / <lane> required checks, PR required with 1 approval, and updates restricted to release managers (ndizazzo, i386, michaelneale, plus scama's identity if it's going to merge its own backports). That's the freeze, enforced on GitHub where the work is.
4. Backport labels, acted on by hand
Create backport release/0.76 and backport main. In this stage they're a marker, not a trigger.
- A merged PR carrying
backport <branch> is a to-do for the release owner.
- The owner runs
git cherry-pick -x <merge-sha> onto a branch off the target, opens a PR titled [backport <target>] <original title>, and links the source PR. The -x trailer is what the check in item 6 keys on.
- Conflicts get resolved in that PR, under the same rule Stage 2 will enforce mechanically. The resolved diff touches nothing outside the conflicted hunks, full CI green, a human merges.
The commands and the PR shape are spelled out here because Stage 2 automates this exact shape.
5. Release-branch mode for validation
.agents/skills/release-validation/SKILL.md step 1.4 and scripts/collect-release-inventory.py (require_release_base) allow zero commits above the merge-base with origin/main, or exactly one <tag>: prepare release source commit. A release branch has N merged PRs above the branch point, so both fail closed. They need a release-branch mode where the delta is "commits since the branch point, each one a merged PR."
6. A canonical release skill
New .agents/skills/release-train/ (name negotiable), built in a worktree off main. Short SKILL.md, long parts in references/, same pattern as release-validation. It owns cutting the branch, the bump PR, the manual backport flow from item 4, re-gating only the delta, the publish step, and bumping main to the next -dev right after the cut.
The publish step refuses to tag while any commit on the release branch is missing a merged forward-port to main. git cherry main release/0.76 plus a check is about 30 lines, and it's what actually enforces the invariant.
Discovery, without flooding every agent's context:
- Skill description triggers on release work only.
- One line in the root
AGENTS.md key docs table.
- Top of
RELEASE.md points at the skill.
release.yml failure messages name the skill (item 1).
The skill routes to manage-ci for workflow edits instead of duplicating it.
7. Docs
RELEASE.md, .agents/skills/manage-ci/references/current-inventory.md, and ci/ci.md all describe a main-only release today. Same change documents the branch, the labels, the manual backport flow, and the ruleset. Milestones for 0.76 through 0.80 already exist, so "what's on the branch" is just the milestone.
Done when
- 0.77.0 is cut from
release/0.77, gated on the branch, and tagged from the branch by a canary=false dispatch.
- At least one fix has gone through the manual backport flow in item 4 during that release.
- The forward-port check in the publish step has run for real.
Stage 1 of #1724. Everything needed to cut, gate, and ship a release from a
release/X.Ybranch, with cherry-picks done by hand. No bot. Stage 2 (#1730) builds on this.Line numbers below are at
7fd926273.1.
release.ymlref handlingrefs/heads/mainorrefs/heads/release/*forworkflow_dispatch. The failure message should name the release skill, so a wrong dispatch teaches the agent right when it fails.release/*branch.refs/heads/main. Either push to$GITHUB_REF, or drop the in-workflow push entirely and require the bump to arrive by PR. The workflow already handles a pre-bumped ref at line 179 ("already contains the complete version update"), so that path is a no-op today.I'd rather remove the push than generalize it. It's the one place CI writes to a protected branch, and dropping it makes the ruleset landmine in the parent moot.
This item needs a planning pass before anyone writes it. The rest of the issue is mechanical.
2. Five
main_*.ymlentriesmain_{quality,website,linux,macos,windows}.ymlall trigger onpush: branches: [main]. Change tobranches: [main, 'release/**']. Same five focused entries, sameMain / <lane>check names, no new workflows, so the shape rules in.github/AGENTS.mdstill hold.pr_*.ymlneeds nothing, it plans frompull_request.base.sha.3.
release/**rulesetNew branch ruleset targeting
refs/heads/release/**: deletion, non-fast-forward, linear history, the same fivePR / <lane>required checks, PR required with 1 approval, and updates restricted to release managers (ndizazzo, i386, michaelneale, plus scama's identity if it's going to merge its own backports). That's the freeze, enforced on GitHub where the work is.4. Backport labels, acted on by hand
Create
backport release/0.76andbackport main. In this stage they're a marker, not a trigger.backport <branch>is a to-do for the release owner.git cherry-pick -x <merge-sha>onto a branch off the target, opens a PR titled[backport <target>] <original title>, and links the source PR. The-xtrailer is what the check in item 6 keys on.The commands and the PR shape are spelled out here because Stage 2 automates this exact shape.
5. Release-branch mode for validation
.agents/skills/release-validation/SKILL.mdstep 1.4 andscripts/collect-release-inventory.py(require_release_base) allow zero commits above the merge-base withorigin/main, or exactly one<tag>: prepare release sourcecommit. A release branch has N merged PRs above the branch point, so both fail closed. They need a release-branch mode where the delta is "commits since the branch point, each one a merged PR."6. A canonical release skill
New
.agents/skills/release-train/(name negotiable), built in a worktree off main. ShortSKILL.md, long parts inreferences/, same pattern asrelease-validation. It owns cutting the branch, the bump PR, the manual backport flow from item 4, re-gating only the delta, the publish step, and bumping main to the next-devright after the cut.The publish step refuses to tag while any commit on the release branch is missing a merged forward-port to main.
git cherry main release/0.76plus a check is about 30 lines, and it's what actually enforces the invariant.Discovery, without flooding every agent's context:
AGENTS.mdkey docs table.RELEASE.mdpoints at the skill.release.ymlfailure messages name the skill (item 1).The skill routes to
manage-cifor workflow edits instead of duplicating it.7. Docs
RELEASE.md,.agents/skills/manage-ci/references/current-inventory.md, andci/ci.mdall describe a main-only release today. Same change documents the branch, the labels, the manual backport flow, and the ruleset. Milestones for 0.76 through 0.80 already exist, so "what's on the branch" is just the milestone.Done when
release/0.77, gated on the branch, and tagged from the branch by acanary=falsedispatch.