Dev Release¶
Cut a tagged three-platform work-buddy release with preflight gates, native artifact acceptance, one draft-release aggregation job, and a human-only publish step.
Workflow name: dev-release
Execution: main
Slash command: /wb-dev-release
Override not allowed
Steps¶
| # | ID | Name | Type | Depends on |
|---|---|---|---|---|
| 1 | assess |
Verify the repo is releasable (main, clean, synced, CI green) | reasoning |
|
| 2 | version_gate |
Version, tag, and CHANGELOG agree | reasoning |
assess |
| 3 | cross_repo |
Check companion-repo version pins (Obsidian plugin) | reasoning |
version_gate |
| 4 | tag_push |
Tag and push (requires explicit user consent) | reasoning |
cross_repo |
| 5 | watch_ci |
Watch the release workflow to completion | reasoning |
tag_push |
| 6 | verify_draft |
Verify the draft release and its installer assets | reasoning |
watch_ci |
| 7 | publish_handoff |
Hand the draft to the user to publish (never publish yourself) | reasoning |
verify_draft |
| 8 | post_verify |
Verify the published release resolves publicly | reasoning |
publish_handoff |
Step instructions¶
assess¶
Reasoning step. Verify the repo is releasable:
git checkout main && git pull origin main
git status --short # must be empty
git log origin/main..main # must be empty (nothing unpushed)
gh run list --branch main --limit 3 # latest test run on main must be green
Advance with {"on_main": true, "clean": true, "synced": true, "ready": true} only when all hold. Any false value: fix first (commit or stash strays, push or pull, chase the red CI) and re-check. Set ready: false only if the user aborts the release.
Advance via wb_advance(workflow_run_id=..., step_result={...}). The parameter is step_result (not result): FastMCP silently drops unknown kwargs.
version_gate¶
Reasoning step. The intended tag, pyproject.toml, and the CHANGELOG must agree before anything is tagged:
uv run python packaging/version.py # the version the release will carry
git tag -l "<version>" # must be EMPTY (tag not already used)
git ls-remote --tags origin "<version>" # must be EMPTY too
grep -n "<version>" CHANGELOG.md # an entry for this version must exist
The release CI hard-aborts when the tag differs from the pyproject version, so a mismatch here means the version bump has not landed yet: stop, land it through /wb-dev-pr, and restart this workflow. If the CHANGELOG lacks an entry, write one now (user-visible changes, not commit archaeology), land it the same way, and restart.
Advance with {"version": "<x.y.z>", "ready": true}.
Advance via wb_advance(workflow_run_id=..., step_result={...}). The parameter is step_result (not result): FastMCP silently drops unknown kwargs.
cross_repo¶
Reasoning step. work-buddy and its Obsidian plugin declare a two-way compatibility range; verify the release does not step outside it.
- The enforced direction (blocking):
work_buddy/obsidian/bridge.pydefinesPLUGIN_VERSION_MIN(inclusive) andPLUGIN_VERSION_MAX(exclusive), andrequire_available()raises outside that range. Fetch the plugin's latest released version (gh release view --repo KadenMc/obsidian-work-buddy --json tagName) and assertMIN <= released < MAX. Out of range means shipped work-buddy cannot talk to the shipped plugin: blocking, resolve before tagging. - The courtesy direction (advisory): the plugin's
WB_VERSION_MAX(in itssrc/handlers.ts) is the first work-buddy version it has not been tested against. If the version being released is>= WB_VERSION_MAX, the plugin will log a cosmetic "outdated" warning against this release; work-buddy never reads that field, so this does not block. Note it and recommend a plugin-side bump plus plugin release as a follow-up. - The paired-constants rule:
PLUGIN_VERSION_MAX(bridge.py) andWB_VERSION_MAX(plugin) each cap the OTHER project's version, so a major-line bump on either side requires moving the counterpart's constant in the same change. In particular, releasing a plugin version at or abovePLUGIN_VERSION_MAXgets hard-rejected by the bridge until bridge.py moves. - The Thunderbird extension carries no version coupling to work-buddy; nothing to check.
Advance with {"plugin_in_range": true, "blocking": false, "notes": "<advisory findings, if any>"}.
Advance via wb_advance(workflow_run_id=..., step_result={...}). The parameter is step_result (not result): FastMCP silently drops unknown kwargs.
tag_push¶
Reasoning step. Pushing a tag starts the release machinery: ask the user for explicit consent first, naming the exact version. On yes:
git tag <version>
git push origin <version>
Advance with {"tagged": true, "tag": "<version>"}. If the user declines, advance with {"tagged": false, "tag": ""} and let the workflow end there.
Advance via wb_advance(workflow_run_id=..., step_result={...}). The parameter is step_result (not result): FastMCP silently drops unknown kwargs.
watch_ci¶
Reasoning step. The tag triggers the release workflow: Windows installer compilation; native Linux x86-64 and macOS arm64 tarball builds; downloaded-artifact install, launch, repair, and uninstall acceptance on fresh hosted runners; then one draft-release aggregation job. Watch it:
gh run list --workflow release.yml --limit 1
gh run watch <run-id> --exit-status # or poll gh run view <run-id>
On success advance with {"ci_green": true}.
On failure: diagnose from gh run view <run-id> --log-failed. Do not leave a tag pointing at a commit whose release build failed: delete the remote tag (git push origin :refs/tags/<version> and git tag -d <version>), land the fix through /wb-dev-pr, and re-run this workflow from the top. Advance with {"ci_green": false, "notes": "<what failed and where the fix went>"}.
Advance via wb_advance(workflow_run_id=..., step_result={...}). The parameter is step_result (not result): FastMCP silently drops unknown kwargs.
verify_draft¶
Reasoning step. The CI creates a DRAFT release. Verify it before handing it to the user:
gh release view <version> --json isDraft,name,assets
Check: it is a draft; the name matches the version; and all accepted artifacts are present with plausible sizes: work-buddy-<version>-setup.exe, work-buddy-<version>-linux-x86_64.tar.gz, and work-buddy-<version>-macos-arm64.tar.gz. Write or polish the release notes body now (gh release edit <version> --notes-file ...): user-visible changes, installation instructions, supported architectures, and the unsigned macOS Gatekeeper expectation.
Advance with {"draft_ok": true, "assets": ["..."]}.
Advance via wb_advance(workflow_run_id=..., step_result={...}). The parameter is step_result (not result): FastMCP silently drops unknown kwargs.
publish_handoff¶
Reasoning step. Publishing is the user's act, always. Present the draft URL, the verified assets, and the release notes, then ask the user to review and publish it in the GitHub UI (or explicitly decline/postpone). Never run gh release edit --draft=false yourself, under any phrasing of user intent short of them publishing it with their own click.
Advance with {"published": true} once the user confirms it is live, or {"published": false, "notes": "postponed"} if they defer (the workflow then records the deferral and ends at post_verify with a no-op).
Advance via wb_advance(workflow_run_id=..., step_result={...}). The parameter is step_result (not result): FastMCP silently drops unknown kwargs.
post_verify¶
Reasoning step. Only meaningful after a publish:
gh release view --repo <owner>/work-buddy latest --json tagName # resolves to this version
curl -sI https://github.com/<owner>/work-buddy/releases/latest | head -3
Confirm releases/latest resolves to the new tag (this is what the README's install link points at), and report the final summary: version, release URL, assets, and any advisory follow-ups from cross_repo (e.g. a recommended plugin-side bump). If publishing was deferred, state what remains and where the draft lives.
Advance via wb_advance(workflow_run_id=..., step_result={...}). The parameter is step_result (not result): FastMCP silently drops unknown kwargs.
Context¶
Cut a tagged work-buddy release: preflight gates (releasable main, version/tag/CHANGELOG agreement, companion-repo pin check), a consent-gated tag push, a watch of the release CI, verification of the draft release's installer assets, and an explicit human publish step.
Philosophy¶
Releases are rarer than commits, which makes a prose checklist worse, not better: nobody reliably remembers a multi-step ritual performed every few months. The same reasoning that produced the dev-pr workflow applies with more force here, plus one release-specific rule: the workflow produces and verifies a DRAFT, and publishing it is always the user's click, because publishing is an outward-facing act on the project's public face.
What this workflow is NOT¶
- Not a version-bumping tool. Deciding the version (and landing the bump through /wb-dev-pr) happens before this workflow runs; the gates here verify agreement, they do not edit files.
- Not a package publisher. It watches the repo's own release CI; it does not upload artifacts anywhere itself.
- Not a substitute for the release CI's own guards (the tag-matches-pyproject assertion stays in the workflow file).