This is the maintainer-facing release playbook. For contributor flow see
CONTRIBUTING.md.
stagingaccumulates merged PRs.- A promotion PR (
staging → main) is how a release happens. - The promotion PR is the only place version fields are bumped.
- Merging the promotion PR into
maintriggers theauto-tagworkflow which pushes avX.Y.Zgit tag pointing at the merge commit. - Marketplace consumers installing
winui@win-dev-skillsget whatever is onmainHEAD. Tag-pinning consumers can installhttps://github.com/microsoft/win-dev-skills.git#vX.Y.Z(see README §Pinning to a release).
Roughly:
- Patch (
0.X.Y → 0.X.Y+1): any timestaginghas bug-only changes you want users to pick up. - Minor (
0.X.0 → 0.X+1.0): whenstagingcontains any of:- A new skill, agent, or tool.
- A new public CLI subcommand or analyzer rule.
- A change to skill names, on-disk layout, agent config schema, analyzer rule IDs, or CLI surfaces (these are explicitly allowed to break in v0.x; minor bump is the public signal).
- A new payload rebuild that changes behavior in a user-visible way.
- Major (
→ 1.0.0): out of preview. Separate planning required.
There is no fixed cadence. A reasonable rule of thumb: don't let staging get
more than ~10 PRs ahead of main without cutting a release; users want
incremental improvements, not surprise drops.
./scripts/open-release-pr.ps1The script:
- Checks you're on a clean local checkout with
stagingandmainboth up-to-date with origin. - Diffs
origin/stagingagainstorigin/mainto enumerate the commits going into the release. - Suggests a semver bump (patch by default; minor if any commit message
contains
BREAKING:orbreaking change, or if any commit touchesplugins/winui/agents/, removes a skill directory, or changes the plugin manifest schema). - Lets you accept or override the suggested version.
- Writes the bumped version into all five version fields:
plugins/winui/plugin.json→version.github/plugin/marketplace.json→metadata.versionandplugins[0].version.claude-plugin/marketplace.json→versionandplugins[0].version
- Drafts a
## [X.Y.Z] — YYYY-MM-DDCHANGELOG section by promoting bullets currently under## [Unreleased]and grouping any unbucketed commits by path heuristic. - Pushes a
release/X.Y.Zbranch and opens a PRrelease/X.Y.Z → mainviagh pr create, with the changelog rendered into the PR body.
If the helper doesn't work for some reason:
git checkout -b release/X.Y.Z origin/staging- Edit all five version fields (use
git grep -n '"version"'to find them). - Edit
CHANGELOG.md: rename## [Unreleased]to## [X.Y.Z] — YYYY-MM-DDand add a fresh empty## [Unreleased]section above it. - Commit, push,
gh pr create --base main --head release/X.Y.Z.
Before merging:
- ✅ All status checks green (
build-tools, provenance jobs,version-bump,changelog-entry). - ✅ CHANGELOG entry reads well — every user-facing change in the diff is reflected, every bullet is something a user could actually notice.
- ✅ Version bump matches the change content (don't ship a new skill as a patch).
- ✅ No surprise diffs — the promotion PR should ONLY contain the version-field
edits and the CHANGELOG edit. Anything else means someone landed an
out-of-band commit on
stagingthat didn't go through review; investigate.
- Merge strategy: merge commit, NOT squash. This preserves the
staging-side commit history on
main, which makes blame and rollback work. - Click "Merge pull request" → "Create a merge commit".
Automatically:
- The
auto-tagworkflow runs onpushtomain. - It reads the new version from
plugins/winui/plugin.json. - If a tag
vX.Y.Zalready exists at the currentmainHEAD, it logs a notice and exits (idempotent re-run case). If the tag exists at a different SHA, it fails loudly — investigate before doing anything else (someone created the tag manually, or a previous release of this same version landed at a different commit). - Otherwise it creates and pushes
vX.Y.Zpointing at the merge commit. - The marketplace cache update is whatever cadence GitHub Copilot CLI uses
(consumers may need to run
copilot plugin marketplace update win-dev-skillsto see the new version listed).
Important
Don't announce the new tag externally until auto-tag succeeds.
Marketplace consumers (winui@win-dev-skills) get the new code as soon as
the promotion PR merges — that's fine. But tag-pinning consumers
(...git#vX.Y.Z) will see an install failure until the tag exists. Watch
the workflow run on the Actions tab before sending an "X.Y.Z is out"
message anywhere.
If a release on main is broken:
git revert -m 1 <merge-commit-sha>on a new branch frommain.- Bump the patch version (
0.X.Y → 0.X.Y+1) in all five fields. - Add a CHANGELOG entry under the new version explaining what was reverted and why.
- PR against
maindirectly — this is treated like a hotfix. - Back-merge the revert into
stagingafter merging.
The original tag stays in place (we don't delete tags); the rollback creates a new tag. Consumers updating past the broken version skip cleanly.
These are the manual GitHub-side steps required to bring this strategy live; the CI workflows alone are not enough.
-
Create the
stagingbranch from currentmainHEAD:git fetch origin git push origin origin/main:refs/heads/staging
-
Branch protection on
staging(CRITICAL — strict mode is REQUIRED, not optional):- Require PR before merging.
- Require status checks:
build-tools,analyzer-provenance,winui-search-provenance,validate-plugin-manifest,validate-skill-frontmatter,analyzer-targets-sync,version-sync,staging-up-to-date-with-main. - "Require branches to be up to date before merging" — MUST be on.
Without this,
staging-up-to-date-with-mainonly runs at PR open/sync, and a hotfix landing onmainbetween check-pass and merge can be lost. - Allow squash-merge only.
- Do not allow admins to bypass.
-
Branch protection on
main(CRITICAL — strict mode is REQUIRED, not optional):- Require PR before merging.
- Require status checks: all of the above plus
pr-target-policy,version-bump,changelog-entry. - "Require branches to be up to date before merging" — MUST be on.
- Allow merge commits (for the promotion PR) AND squash (for hotfixes).
- Restrict who can push to maintainers.
- Do not allow admins to bypass.
-
Repository settings → Pull Requests:
- Set "Default branch for new pull requests" to
staging(Settings → General → Pull Requests, if available; otherwise rely on the PR template'sbase:hint and thepr-target-policycheck).
- Set "Default branch for new pull requests" to
-
Re-target any open PRs that currently target
mainand are not hotfixes to instead targetstaging. -
Verify the
auto-tagworkflow has a token withcontents: writeso it can push tags. The defaultGITHUB_TOKENdoes, as long as the workflow declares the permission (it does inrelease.yml).
- Track upstream support for first-class version pinning in
copilot plugin install/ Claude Code plugin install. Today the only way consumers can pin to a release is the git-URL form (https://github.com/microsoft/win-dev-skills.git#vX.Y.Z), which bypasses the marketplace catalog. File issues against github/copilot and the Claude Code plugin spec when the syntax stabilizes.