feat: add /release skill for version assessment and release execution - #146
Merged
Conversation
Cutting a release had two manual judgment steps documented in .claude/release-process.md but no tooling: auditing release-notes labels on every PR merged since the last tag, and choosing the version number. Both are easy to get wrong — an unlabeled PR silently lands in a generic "Other Changes" bucket, and the patch-vs-minor call determines whether downstream projects on a `^0.8.x` constraint can resolve the release at all. Add a zero-argument /release skill that reads every merged PR since the last tag, proposes label corrections, recommends a version with reasoning, then stops at a single approval gate before handing the mechanical work to bin/release.sh. Document the Composer caret reachability rule in release-process.md so the patch-vs-minor tiebreak is authoritative rather than living only in the skill. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Step 3 has the skill reading labels, and the `enhancement` bucket feeds a release-notes section titled "New Features" — close enough to a minor bump to invite the conflation. Nothing in Step 4 said labels route notes rather than decide versions, so a batch containing any `enhancement` could plausibly get escalated to a minor it did not earn. Add a three-tier per-PR classification with a mechanical check for tier 1: only `packages/*` is copied into split repos, so a PR touching no package path cannot reach a consumer's vendor/ and is irrelevant to the version regardless of label. Tier 2 ships but adds no callable surface (patch); only tier 3 adds surface a consumer builds against (minor). Present the assigned tier per PR so the user can challenge one classification instead of the whole recommendation, and state explicitly when an enhancement did not escalate the bump. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Step 5 told the user a failed run "leaves nothing to clean up". That is wrong: bin/release.sh checks out main and merges develop at line 25, well before the test suite runs at line 61. A test failure leaves the repo on main with the merge already made — nothing pushed and no tag, but not a no-op, and a retry from there trips the skill's own on-develop precondition with a misleading error. State the real failure state and the `git checkout develop` recovery. Add a test that asserts the merge still precedes the test run in bin/release.sh, so the claim cannot silently rot if the script is reordered. Also narrow tier 2: internals added in service of a fix (a new exception factory, say) are thrown at consumers rather than built against, so they do not promote a bug fix to a feature. Without that, PR #145 in the calibration table reads as tier 3 on a strict pass and would wrongly escalate a patch to a minor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Cutting a release has two judgment steps that
.claude/release-process.mdexplicitly lists as manual, with no tooling behind them:.github/release.ymlbuckets PRs into release-notes sections by label; anything unlabeled silently lands in a generic "Other Changes" bucket, and nothing catches it before the tag is pushed.0.xthe patch-vs-minor call decides whether downstream projects can even resolve the release — Composer treats the minor as the breaking position below1.0, so^0.8.4accepts0.8.5but refuses0.9.0.This adds a zero-argument
/releaseskill that does the assessment and then gets out of the way.What it does
develop, clean tree, in sync with origin, PHP 8.5,gh/jqpresent, something actually to release) and aborts with the specific failure rather than self-fixing.git log, reading title/body/labels rather than trusting commit subjects. Flags commits with no#NNreference, sincebin/release.shresolves notes by PR number and silently omits those..github/release.yml, using thegh apiREST endpoint (gh pr editsilently fails on this repo)../bin/release.sh <version>and reports the release URL plus the async split/Packagist follow-ups.Labels route notes; they do not decide the version
Step 3 has the skill reading labels, and the
enhancementbucket feeds a section titled "New Features" — close enough to a minor bump to invite the conflation. So Step 4 classifies each PR by what a consumer can actually observe aftercomposer update, and only tier 3 forces a minor:.claude/,bin/,.github/, roottests/. Onlypackages/*is copied into split repos, so this cannot reach anyone'svendor/.Tier 1 has a mechanical check rather than a judgment call —
gh pr view <N> --json filesreturning zeropackages/paths proves it, which is the common shape for maintainer-facingenhancementPRs like this one.The skill presents the assigned tier per PR, so a disagreement lands on one classification instead of the whole recommendation, and it states explicitly when an
enhancementdid not escalate the bump rather than leaving it ambiguous whether it was overlooked.All mechanical work stays in
bin/release.sh. The skill never hand-editsCHANGELOG.md(the script generates and commits it), never runs from a branch other thandevelop, and never narrows the test suite to get past a failure.Structure follows the existing
marko-skillsplugin skills (name/descriptionfrontmatter, numbered steps, explicit guardrails), but lives in.claude/skills/because this is maintainer-only tooling for the monorepo — not something to ship to Marko app developers.Also included
.claude/release-process.md— points at the skill from Cutting a Release, and documents the patch-vs-minor reachability rule under Versioning so it is authoritative rather than living only in the skill.CLAUDE.md— lists the skill alongside the other.claude/configuration files.Test plan
tests/ReleaseSkillTest.php— 9 tests covering frontmatter discoverability, the zero-argument contract, the approval gate, the version-decision rules, the enhancement tier classification, delegation tobin/release.sh, and the precondition checks. Follows the content-assertion style of the existingReleaseDocumentationTest..github/release.ymland asserts the skill documents every category title and label, so adding a release-notes bucket without updating the skill fails the suite.composer test— 6870 passed (up from 6861)phpcsandphp-cs-fixer --dry-runclean ontests/ReleaseSkillTest.php