Repository navigation
Conversation
A new Conventional PR title check ties each pull request title to what it changes. After merge, the Version workflow reads the titles since the last v* tag, writes the next version into every version file and the changelog, then pushes the release commit and tag with a GitHub App token and starts the existing draft release workflow. No workflow publishes a release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The bootstrap recovery run only tags the historical baseline without triggering a follow-up Version run, leaving pending feature and fix commits unreleased until another push or manual run.
1 open issue
What changed in this PR
Automates plugin versioning from conventional pull request titles, replacing manual version edits while keeping release publication manual.
Changes:
- Adds a required title check covering version changes, changelog entries, and migration notes.
- Adds version computation and atomic commit/tag pushes, including bootstrap tagging of the existing 1.0.0 release commit.
- Automatically starts draft preparation and updates release guards, tests, and contributor guidance.
| File | Description |
|---|---|
tests/test_versioning.py |
Tests title rules, version planning, rewrites, and changelog handling. |
tests/test_release_source.py |
Tests the fifth required check and release-tag verification. |
scripts/versioning.py |
Centralizes version files, title parsing, bump rules, and changelog updates. |
scripts/verify_release_source.py |
Requires the title check and a matching release tag. |
scripts/validate.py |
Checks shared version files and changelog heading order. |
scripts/next_version.py |
Computes release plans and applies version updates. |
scripts/check_pr_title.py |
Validates titles against package changes and release-note requirements. |
docs/releasing.md |
Documents automated versioning, protections, and draft-release procedures. |
docs/compatibility.md |
Points to the changelog for the current version while preserving dated evidence. |
CONTRIBUTING.md |
Explains title conventions and contributor changelog responsibilities. |
CHANGELOG.md |
Adds the Unreleased section and entry guidance. |
.github/workflows/version.yml |
Computes updates, pushes release commits and tags, and dispatches drafts. |
.github/workflows/release.yml |
Requires an existing matching tag before creating a draft. |
.github/workflows/pr-title.yml |
Runs the read-only pull request title check. |
.github/dependabot.yml |
Gives dependency update titles the conventional ci: prefix. |
Review effort: Balanced
| fi | ||
| echo "Pushed $tag at $commit." | ||
| echo "commit=$commit" >> "$GITHUB_OUTPUT" | ||
| - name: Start the draft release for the new tag |
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.

What this does
After this merges, every merge to
mainsets the next plugin version from the pull request title, like the other Voiceflow repositories.featis a minor release,fixis a patch, and a!or aBREAKING CHANGE:footer is a major release. A new required check keeps the title, the version files, and the changelog consistent. A new workflow pushes the release commit and tag, then starts the existing draft release.Why
Today someone edits four manifests, the skill metadata, and the changelog by hand for each release. Nothing ties a pull request to the version change it needs. No release has a tag yet, so 1.0.0 is on
mainbut untagged.Decisions that need your approval
These change written policy in
docs/releasing.md. GitHub settings stay as they are until an admin applies the steps below.1. One App may bypass the pull request rule.
Change: force-push and deletion rules keep an empty bypass list. The pull request rule and required checks move to a second ruleset whose only bypass is a new release App.
Recommended: approve, so the workflow can push
chore(release): X.Y.Ztomain.Alternative: the workflow opens a version pull request that someone merges.
Trade-off: the workflow only pushes version files, but the App token itself could push anything if the workflow were changed. The alternative keeps the bypass list empty but adds one pull request per release.
2. Required checks go from four to five.
Change:
Conventional PR titlebecomes required. The release guard now requires all five.Recommended: approve. Otherwise a title can skip the check and release the wrong version.
Alternative: keep the check advisory.
Trade-off: open pull requests must update from
mainonce to report it.Other written changes: pull requests no longer edit versions. They add a customer-facing line under
## Unreleased. Tags exist before publication, and drafts start on their own. "No workflow publishes a release automatically" stays true.What I am most likely to have got wrong
b0adc80, the merge of feat!: move the skills to MCP v3 tool names and release 1.0.0 #11 that set 1.0.0, not at this pull request's merge. That stays correct if other pull requests merge before the App is ready. The package content is the same either way.featorfixmerge, the bot adds one commit tomain. A pull request updated in the minute before that push must update again.Admin setup (not done by this PR)
Apply in this order.
voiceflow/agent-pluginsonly.release, with deployment branches limited tomain. Add the secretsRELEASE_APP_CLIENT_ID(the App's client ID) andRELEASE_APP_PRIVATE_KEY. Do this before merging; otherwise the first run creates an empty, unrestricted environment.refs/tags/v*. The first restricts updates and deletions, with an empty bypass list. The second restricts creations, with only the App as bypass. A bypass covers a whole ruleset, so creation needs its own.Conventional PR title(source: GitHub Actions) to the required checks. This pull request already reports it. Other open pull requests wait on it until they update frommainafter the merge.v1.0.0atb0adc80and starts Prepare draft package release. Review the draft and publish it by hand.If this merges before steps 1 and 2, the write job fails with "The release environment needs RELEASE_APP_CLIENT_ID and RELEASE_APP_PRIVATE_KEY. Nothing was changed." After setup, run Version by hand from
main.How it works
Workflows and scripts
Conventional PR title(.github/workflows/pr-title.yml). Usespull_request, notpull_request_target. Read-only token, no secrets. It checks out the merge commit like the existing checks, andscripts/check_pr_title.pycompares it with its base. The title and body come from the event file, never from shell interpolation. It fails when:type(scope)!: summarywith a known type;chore(release);voiceflow/change but the title is notfeat,fix, or breaking;feat,fix, or breaking change adds no line under## Unreleased;BREAKING CHANGE:footer but the title has no!..github/workflows/version.yml). Runs on push tomainor by hand frommain, one run at a time.scripts/next_version.pyreads first-parent commits since the lastv*tag, skipschore(release)commits, and takes the largest bump. If nothing bumps, the run stops. Otherwise it rewrites the version files, renames## Unreleasedto a bare## X.Y.Z, adds a new empty## Unreleased, runsvalidate.py, and uploads a patch.releaseenvironment and runs no repository code. It mints a token with the SHA-pinnedactions/create-github-app-tokenv3.3.0, limited to contents write. It refuses a patch that touches any other file, or adds, deletes, renames, or changes the mode of a file. It pushes the commit and tag in one atomic push. Ifmainmoved, it pushes nothing and exits cleanly. Then it dispatchesrelease.ymlwith the job token, which hasactions: write.release.ymlandscripts/verify_release_source.pynow require tagvX.Y.Zat the release commit, following annotated tags. They still never create, move, or delete a tag, never replace a release or draft, and never publish.VERSION_FILESinscripts/versioning.pylists the four manifests and the skill documentation metadata.validate.pyand the bump script both use it. The write job repeats the list, and a test keeps them equal. MCP Registryserver.jsonversions are not included.validate.pyalso requires the changelog to start with## Unreleased, then## <manifest version>.ci:, so its pull requests pass the check.docs/compatibility.mdno longer names a current candidate version.CONTRIBUTING.mdanddocs/releasing.mddescribe the new flow.Verification
python3 scripts/validate.py: passes.python3 -m unittest discover -s tests -v: 141 tests pass, including the Gitleaks scanner tests.python3 scripts/build.py: passes.python3 scripts/scan_secrets.pywith Gitleaks 8.30.1: 0 findings in history, files, and packages.version.ymlagainst a bare clone. It used the write job's shell steps taken from the YAML, with a stub for the bot user lookup.v1.0.0atb0adc80.featmerge: pushedchore(release): 1.1.0andv1.1.0together.prepare_release.pyat the tag produced the right notes.mainmoved before the push: nothing pushed, clean exit. The next run released 1.1.1 for both merges.README.md, or changing a file mode: refused, nothing pushed.main: refused.docs:title that changedvoiceflow/, edited a version, and had a footer without!. It passed aci:title.mainmoves during the push itself.🤖 Generated with Claude Code