Skip to content

feat: add /release skill for version assessment and release execution - #146

Merged
markshust merged 3 commits into
developfrom
feature/release-skill
Jul 26, 2026
Merged

feat: add /release skill for version assessment and release execution#146
markshust merged 3 commits into
developfrom
feature/release-skill

Conversation

@markshust

@markshust markshust commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Cutting a release has two judgment steps that .claude/release-process.md explicitly lists as manual, with no tooling behind them:

  1. Audit labels on every PR merged since the last tag. .github/release.yml buckets 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.
  2. Pick the version number. All 70 packages share it, and while in 0.x the patch-vs-minor call decides whether downstream projects can even resolve the release — Composer treats the minor as the breaking position below 1.0, so ^0.8.4 accepts 0.8.5 but refuses 0.9.0.

This adds a zero-argument /release skill that does the assessment and then gets out of the way.

What it does

  • Step 1 — verifies preconditions (on develop, clean tree, in sync with origin, PHP 8.5, gh/jq present, something actually to release) and aborts with the specific failure rather than self-fixing.
  • Step 2 — resolves every PR merged since the last tag from git log, reading title/body/labels rather than trusting commit subjects. Flags commits with no #NN reference, since bin/release.sh resolves notes by PR number and silently omits those.
  • Step 3 — audits labels against every category in .github/release.yml, using the gh api REST endpoint (gh pr edit silently fails on this repo).
  • Step 4 — classifies each PR into a tier (below), recommends a version including the Composer reachability tiebreak for a genuinely mixed batch, and says whether the release is urgent or worth deferring.
  • Step 5 — presents PRs with labels and assigned tiers, label fixes, version + rationale, and what will happen — then stops. One approval gate; confirmation or an override both land here.
  • Steps 6–7 — runs ./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 enhancement bucket 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 after composer update, and only tier 3 forces a minor:

Tier What it is Effect on version
1 Never leaves the monorepo — .claude/, bin/, .github/, root tests/. Only packages/* is copied into split repos, so this cannot reach anyone's vendor/. None, whatever the label
2 Ships in a package but adds no callable surface — CLI wording, docs, internal refactor Patch
3 Adds or changes surface a consumer builds against — interface, method, config key, binding, command, package; or changes existing behavior Minor

Tier 1 has a mechanical check rather than a judgment call — gh pr view <N> --json files returning zero packages/ paths proves it, which is the common shape for maintainer-facing enhancement PRs 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 enhancement did 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-edits CHANGELOG.md (the script generates and commits it), never runs from a branch other than develop, and never narrows the test suite to get past a failure.

Structure follows the existing marko-skills plugin skills (name/description frontmatter, 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 to bin/release.sh, and the precondition checks. Follows the content-assertion style of the existing ReleaseDocumentationTest.
  • One test parses .github/release.yml and 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)
  • phpcs and php-cs-fixer --dry-run clean on tests/ReleaseSkillTest.php

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>
@markshust markshust added the enhancement New feature or request label Jul 26, 2026
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jul 26, 2026
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>
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jul 26, 2026
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>
@markshust
markshust merged commit d3a24d2 into develop Jul 26, 2026
@markshust
markshust deleted the feature/release-skill branch July 26, 2026 15:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant