ci: auto-create GitHub Release on tag push (port mcp-synology github-release job)#127
Conversation
…release job) Adds a github-release job to publish.yml that runs after publish-pypi on every tag push. The job extracts the matching CHANGELOG section and creates (or updates) a GitHub Release with those notes. Closes the Releases-page-drift gap that surfaced after v2.4.0 / v2.5.0 / v2.5.1 all landed on PyPI without matching Releases (backfilled manually on 2026-05-06). Pattern ported from cmeans/mcp-synology publish.yml with one CHANGELOG-format adaptation: - mcp-synology CHANGELOG uses `## VERSION (DATE)` headings (no brackets), and its awk matcher anchors on `^## VERSION( |\()`. - mcp-clipboard CHANGELOG uses Keep-a-Changelog `## [VERSION] - DATE` headings (literal brackets). The awk matcher here anchors on `^## \[VERSION\]` to match. Verified locally against the live CHANGELOG: extraction returns the right notes block for v2.5.1, v2.5.0, v2.4.0, and v2.3.0. Job shape: - needs: publish-pypi (Release only lands after the wheel is on PyPI) - permissions: contents: write (enables gh release create/edit) - Step 1: awk-extract the matching CHANGELOG section to release_notes.md. Strip leading blank lines so the body doesn't start with an empty paragraph. Set use_changelog=true/false output. - Step 2: Idempotent gh release create / edit. If a Release already exists for the tag (hand-crafted, or re-running a partially-failed publish), edit it in place rather than failing with HTTP 422. Fall through to --generate-notes (auto commit-list) when the CHANGELOG has no matching entry. CHANGELOG entry under [Unreleased] / Added. No runtime code change. 599 tests still pass; ruff clean; sync-server- json --check passes. Closes #126. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
cmeans
left a comment
There was a problem hiding this comment.
QA Round 1 — Ready for QA Signoff
Single round, zero findings. The diff is faithfully ported from cmeans/mcp-synology publish.yml with the documented Keep-a-Changelog adaptation (awk anchor ^## \[VERSION\] instead of mcp-synology's ^## VERSION( |\()).
Code review. The job is small, well-commented, idempotent, and gated behind publish-pypi so the GitHub Release only lands after the wheel is on PyPI. permissions: contents: write is correctly scoped to this job only. The gh release view precheck before create vs edit handles re-runs of partially-failed publishes (avoids HTTP 422 on duplicate-tag create). The --generate-notes fallback is correctly only wired into the create branch — gh release edit cannot regenerate auto notes, so the existing body is preserved on re-run when no CHANGELOG entry exists. sed -i '/./,$!d' is the standard idiom to drop leading blank lines, so the Release body never starts with an empty paragraph.
Pattern-port fidelity. Side-by-side diff against mcp-synology publish.yml:117-180 shows: same needs:, same permissions, same fetch-depth: 0, same idempotency logic, same --generate-notes fallback. Only intentional change is the awk anchor format.
Awk extraction — live-verified against the current CHANGELOG.md:
| Version | Extracted lines | Result |
|---|---|---|
| v2.5.1 | 17 | starts with ### Fixed (mcp-name token) |
| v2.5.0 | 59 | starts with ### Added (PRIMARY/Wayland + markdown + registry) |
| v2.4.0 | 52 | starts with ### Fixed (macOS osascript stdin) |
| v2.3.0 | 164 | starts with ### Added (reset_backend_cache helper) |
| v9.9.9 (negative test) | 0 | empty → use_changelog=false → falls through to --generate-notes |
Each section terminates correctly at the next ## [ heading.
Local verification on head 41010f6:
uv run pytest -q→ 599 passed, 19 deselected, 5 xfailed in 4.10s. The 19 deselected are theintegration-marked tests (pyproject.toml: addopts = "-m 'not integration'"); they run separately in theintegration-x11CI job, which is green on this PR.uv run ruff check src tests scripts→ clean.uv run mypy src→ clean (4 source files).python scripts/sync-server-json.py --check→ in sync at2.5.1.- Pre-existing 13-check CI suite all green incl
codecov/patch.
Test-plan status. Checkbox 1 (CI green) is satisfied — ticked in the PR body. Checkboxes 2 (next tag push produces a Release with CHANGELOG notes) and 3 (re-run on partial failure edits in place rather than 422'ing) are post-merge verification — they only exercise on the next real vX.Y.Z tag push. Out-of-scope items in the PR body (SHA-pinning the existing @v6 action refs, hand-crafted release titles like v2.3.0:) are noted and not blocking.
Verdict: Ready for QA Signoff. Awaiting maintainer QA Approved.
|
Adding Ready for QA Signoff — local verification clean (599 passed / 19 deselected integration / 5 xfailed; ruff, mypy, sync-server-json all clean), CI fully green, awk extraction live-verified against the current CHANGELOG for v2.5.1/v2.5.0/v2.4.0/v2.3.0, zero findings on round 1. Awaiting maintainer |
QA reviewer flagged the bump level: this release contains two `### Added` entries (CLI flags --version/--help/--check via #134, and the github-release job via #127), both of which are backwards-compatible feature additions. Per semver §6, that's a MINOR bump trigger, not patch. This repo's own precedent confirms the convention: - v2.5.0 (2026-05-05): minor bump for the same release shape (Added + Changed + Fixed aggregated). Direct precedent. - v2.5.1 (2026-05-05): patch bump for fix-only release. - v2.5.2 (proposed): mismatched -- patch bump for an Added-bearing release. Concrete consumer impact of getting it wrong: a `~=2.5.0` pin would silently auto-upgrade across this release and pull in the new --check surface unannounced. With 2.6.0, that pin correctly does not match. Mechanical fix: - pyproject.toml: version "2.5.2" -> "2.6.0" - server.json: top-level + packages[0] versions synced to 2.6.0 via scripts/sync-server-json.py - CHANGELOG.md: heading "## [2.5.2] - 2026-05-07" -> "## [2.6.0] - 2026-05-07" Branch name `release/v2.5.2` is now a cosmetic mismatch -- preserved to keep PR #135's QA review history intact rather than re-opening a fresh PR. The squash-merge will collapse this commit into the release commit and the branch will be deleted. Local verification at this commit: - python scripts/sync-server-json.py --check: in sync at 2.6.0 - uv run pytest -q: 610 passed, 19 deselected, 5 xfailed - uv run ruff check src tests: clean - uv run mypy src: clean - uv build --wheel: dist/mcp_clipboard-2.6.0-py3-none-any.whl built cleanly - grep "2.5.2" across pyproject.toml + server.json + CHANGELOG.md: zero hits Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aggregates the changes that landed on `main` since v2.5.1: - fix(windows): UTF-8 stdin encoding on PowerShell write paths so non-ASCII characters (em dash, curly quotes, non-Latin scripts) are no longer corrupted by clipboard_copy / clipboard_copy_markdown. Reproduced and verified on a QEMU Windows guest. Closes #129 via #131. - feat(cli): --version, --help, --check flags on the mcp-clipboard binary so users can verify their install before any MCP-host wiring. --check exits 1 with a stderr diagnostic on failure so it doubles as a CI smoke check. Closes #130 via #134. - ci: github-release job in publish.yml auto-creates the GitHub Release on tag push, with notes pulled from the matching CHANGELOG section. v2.6.0 is the first firing of this workflow on a real release. Closes #126 via #127. - docs: README ## Setup rewritten into a five-step quick-start that acknowledges both pipx and uv as install runners; platform status corrected to reflect that Windows has been exercised on a QEMU guest as of v2.5.x. Closes #130 via #134. Bumped MINOR per semver: this release contains backwards-compatible Added entries (CLI flags + github-release job), matching v2.5.0's precedent for the same release shape. Three-file release commit per repo convention: pyproject.toml + server.json (synced) + CHANGELOG.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes #126. Adds a
github-releasejob topublish.ymlthat runs afterpublish-pypion every tag push. The job awk-extracts the matching CHANGELOG section and creates (or updates) a GitHub Release with those notes. Closes the Releases-page-drift gap that surfaced after v2.4.0 / v2.5.0 / v2.5.1 all landed on PyPI without matching GitHub Releases (manually backfilled on 2026-05-06).What landed
.github/workflows/publish.yml:github-releasejob,needs: publish-pypi. Release only lands after the wheel is on PyPI.permissions: contents: writeenablesgh release create/gh release edit.^## \[VERSION\](literal brackets, matching the Keep-a-Changelog format mcp-clipboard uses), then strips leading blank lines, then sets ause_changelog=true/falsestep output.--generate-notes(auto commit-list) when the CHANGELOG has no matching entry.CHANGELOG.md:[Unreleased] / ### Addedentry.Pattern source
Ported from cmeans/mcp-synology
publish.yml. One CHANGELOG-format adaptation: mcp-synology uses## 0.5.2 (2026-05-01)headings (no brackets) and its awk matcher anchors on^## VERSION( |\(). mcp-clipboard uses Keep-a-Changelog## [2.5.1] - 2026-05-05(literal brackets), so the awk matcher here anchors on^## \[VERSION\]instead. Verified locally against the live CHANGELOG: extraction returns the right notes block for v2.5.1, v2.5.0, v2.4.0, and v2.3.0.Verification
uv run pytest -q: 599 passed, 19 deselected, 5 xfailed (no runtime code touched).uv run ruff check src/ tests/ scripts/: clean.python scripts/sync-server-json.py --check: in sync (2.5.1).## [heading correctly terminating the section.Out of scope
actions/checkout@v6andactions/setup-python@v6references inpublish.yml(mcp-synology uses SHA pins); that's a separate hardening pass.vX.Y.Ztag as the title; richer titles can be a follow-up.Test plan
lint,typecheck,test (3.11/3.12/3.13),integration-x11,version-sync,validate-server-json. (publish.ymlonly fires on tag push, so this PR's CI doesn't exercise the new job — it'll exercise on the next release tag.)gh release createneeded).publish.ymldoesn't crash on "release already exists"; it edits in place.Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com