chore: migrate release pipeline to semantic-release (ruby-sdk parity) - #50
Conversation
Replaces the manual version-bump + tag-trigger + towncrier-committed-changelog release model with the commit-driven, zero-touch semantic-release model that mirrors ruby-sdk: workflow_run-triggered on a successful CI run on main, version computed from Conventional Commits, stamped into version.py at build time (uncommitted), published to PyPI via OIDC Trusted Publishing (pypa), then only the vX.Y.Z tag + a GitHub Release (notes = changelog) created after a successful upload. No commit is ever pushed to main. Implements qs-14 phases 1-6: - Dev-only Node release tooling: package.json, release.config.mjs, .yarnrc.yml, generated yarn.lock (Berry). prepareCmd uses `node -e` (present in both jobs) to stamp version.py, avoiding a runner python-symlink dependency. - release.yml rewritten to 3 jobs (prepare -> publish-pypi -> release) with the workflow_run trigger on ['CI'], twin if-guard, fork-PR safeguard, OIDC publish. - ci.yml: removed the towncrier changelog job, added a PR-title Conventional Commits job, removed workflow_call, carried the load-bearing CI-name comment. - Removed committed-changelog machinery: [tool.towncrier], towncrier dev-dep + mypy override, changes/, CHANGELOG.md, scripts/extract_release_notes.py, the verify_release.py towncrier gate. Added Changelog URL to [project.urls]. - version.py -> 0.0.0 dev placeholder (build stamps the real version). - Added root RELEASE.md (ruby-sdk parity, adapted to PyPI/semantic-release/pypa). - Deleted in-repo docs/ and swept every reference (README badges + wiki links, generalized docstrings, deleted test_docs_samples.py). Verification: yarn install --immutable OK; yarn release:dry-run OK; coverage 96.63% project / 97% evaluation (floors 85%/95%); git grep docs/ and towncrier both empty; built wheel/sdist exclude Node artifacts + docs/changes/CHANGELOG. Spec: ai-driven-product-dev qs-14-semantic-release-migration.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request transitions the project's release process from a manual, towncrier-based workflow to a fully automated pipeline using semantic-release and OIDC Trusted Publishing. It removes local documentation files (moving them to the project wiki), deletes towncrier configuration and news fragments, and introduces Node-based release tooling configuration (package.json, release.config.mjs, .yarnrc.yml). Additionally, __version__ is set to a "0.0.0" dev placeholder, which is stamped dynamically at build time. Feedback on the pull request highlights two key improvements in release.config.mjs: a security risk where expanding ${nextRelease.notes} directly inside a double-quoted shell string could lead to syntax errors or command injection, and a robustness issue where the regular expression used to stamp the version is too strict and could fail if formatting tools modify the quote style or spacing.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| verifyReleaseCmd: | ||
| 'if [ -n "$GITHUB_OUTPUT" ]; then { echo "released=true"; echo "version=${nextRelease.version}"; } >> "$GITHUB_OUTPUT"; fi; if [ -n "$RELEASE_NOTES_FILE" ]; then printf \'%s\' "${nextRelease.notes}" > "$RELEASE_NOTES_FILE"; fi', |
There was a problem hiding this comment.
Expanding ${nextRelease.notes} directly inside a double-quoted shell string ("${nextRelease.notes}") is highly risky. If any commit message contains double quotes, backticks, or shell metacharacters (e.g., $()), it will cause shell syntax errors, breaking the release pipeline, or potentially lead to command injection. Since @semantic-release/github automatically handles release notes and publishing, writing them to a file is unnecessary and can be safely removed to eliminate this risk.
| verifyReleaseCmd: | |
| 'if [ -n "$GITHUB_OUTPUT" ]; then { echo "released=true"; echo "version=${nextRelease.version}"; } >> "$GITHUB_OUTPUT"; fi; if [ -n "$RELEASE_NOTES_FILE" ]; then printf \'%s\' "${nextRelease.notes}" > "$RELEASE_NOTES_FILE"; fi', | |
| verifyReleaseCmd: | |
| 'if [ -n "$GITHUB_OUTPUT" ]; then { echo "released=true"; echo "version=${nextRelease.version}"; } >> "$GITHUB_OUTPUT"; fi', |
| prepareCmd: | ||
| 'node -e \'const fs=require("fs");const f="src/convert_sdk/version.py";fs.writeFileSync(f,fs.readFileSync(f,"utf8").replace(/__version__ = "[^"]*"/,`__version__ = "${nextRelease.version}"`))\'', |
There was a problem hiding this comment.
The regular expression /__version__ = "[^"]*"/ strictly expects double quotes and exact spacing around the assignment operator. If a developer or an automated formatter (like Ruff) changes the quote style to single quotes (e.g., __version__ = '0.0.0') or adjusts the spacing, the replacement will silently fail, resulting in the placeholder version 0.0.0 being built and published. Using a more flexible regex that supports both single and double quotes makes the version-stamping process much more robust.
| prepareCmd: | |
| 'node -e \'const fs=require("fs");const f="src/convert_sdk/version.py";fs.writeFileSync(f,fs.readFileSync(f,"utf8").replace(/__version__ = "[^"]*"/,`__version__ = "${nextRelease.version}"`))\'', | |
| prepareCmd: | |
| 'node -e \'const fs=require("fs");const f="src/convert_sdk/version.py";fs.writeFileSync(f,fs.readFileSync(f,"utf8").replace(/__version__ = ["\'][^"\']*["\']/,\'__version__ = "\' + nextRelease.version + \'"\'))\'', |
Summary
Migrates the
python-sdkrelease pipeline from the manual version-bump +v*-tag-trigger + towncrier-committed-changelog model to the commit-drivensemantic-releasemodel that mirrorsruby-sdk(the ratified Convert SDK release standard). Also deletes the in-repodocs/folder (the wiki is the canonical doc home) and sweeps every reference.Implements qs-14 (
ai-driven-product-dev→_bmad-output/implementation-artifacts/2026-04-06-convert-python-sdk/qs-14-semantic-release-migration.md), a fully-resolved, human-approved quick spec. Supersedes qs-10 (towncrier) and renegotiates qs-11 (tag-trigger publish).New release model
Merge a Conventional-Commit PR to
main→ onCIsuccess,release.ymlfires viaworkflow_runand runs three jobs:semantic-release --dry-runcomputes the next version + notes, stampsversion.py(uncommitted),uv build→ wheel + sdist.pypa/gh-action-pypi-publishuploads to PyPI via OIDC Trusted Publishing (environment: pypi,id-token: write, no tokens).@semantic-release/githubpushes thevX.Y.Ztag + creates the GitHub Release (notes = changelog), only after a successful upload (needs: [prepare, publish-pypi]).Tag-only (no commit to
main), publish-before-release, fork-PR safeguard (workflow_run.event == 'push'), and a load-bearing['CI']workflow-name coupling.Changes
package.json,release.config.mjs,.yarnrc.yml, generatedyarn.lock(Berry).prepareCmdusesnode -e(present in both jobs) to stampversion.py— avoids a brittle dependency on the runner'spythonsymlink in thereleasejob.release.ymlrewritten to the 3-jobworkflow_runmodel;ci.ymldrops the towncrierchangelogjob, adds apr-titleConventional-Commits job, removesworkflow_call, carries the coupling comment.[tool.towncrier]+ dev-dep + mypy override,changes/,CHANGELOG.md,scripts/extract_release_notes.py, theverify_release.pytowncrier gate. AddedChangelogto[project.urls].version.py→0.0.0dev placeholder (build stamps the real version).RELEASE.md(ruby-sdk parity, adapted to PyPI/semantic-release/pypa).docs/entirely + swept all references: README badges + wiki links, generalized docstrings (config.py,refresh.py,tracking/flush.py), deletedtests/test_docs_samples.py, prose-only docstring edits in two tests.Verification (local)
yarn install --immutableyarn release:dry-rungit grep -n 'docs/'git grep -n 'towncrier'evaluation/coveragedocs//changes//CHANGELOG.mdNOT verifiable locally (require a real merge to
main)convertcom/python-sdk, workflowrelease.yml, environmentpypi) and a realworkflow_runfrom a CI success onmain. The structure is verified; the live exchange is not.workflow_runend-to-end trigger / publish-before-release skip behavior — only observable in GitHub Actions onmain.semantic-releasewill emitv1.0.0on the first qualifying merge (no priorv*tag), replacing the previous hand-set0.1.0. Flagged for review per qs-14 Decision B. If a0.xseries is wanted, pre-create the initial tag before the first merge.One-time manual setup required before first release
convertcom, repopython-sdk, workflowrelease.yml, environmentpypi).pypiGitHub Environment has no required reviewers / wait timers (else publish blocks). SeeRELEASE.md→ One-Time Setup.Related (separate repos, committed there — not in this PR)
ai-driven-product-dev(branchdocs/python-sdk-semantic-release-quickspec): Phase 7 spec updates — qs-10 superseded, qs-11 renegotiated, Story 5.1 updated.python-sdk.wiki(branchmaster):ReleaseProcess.mdrewritten to the new model.🤖 Generated with Claude Code