prepare-release
Skill214 starsChanged 28 days agoMITView on GitHub
End-to-end release automation — reads commits since last tag, infers semver bump, drafts changelog, creates release PR, merges it, waits for CI green, tags, and monitors the Docker build to completion. Use when the user says: prepare release, cut a release, bump version, create release PR, ship a new version, tag a release, deploy new version, or when all feature PRs are merged and it is time to release.
- Deletes or force-pushes
- Commits and pushes
47 other projects use a version of this.
Adopted by
Most recently taken up by
- leancodepl/patroltheir own version, first seen Nov 9, 2026
- CherryHQ/cherry-studiotheir own version, first seen Nov 8, 2026
- Azure/azure-sdk-for-rusttheir own version, first seen Nov 8, 2026
- Krzysztof318/MailFathomtheir own version, first seen Nov 7, 2026
- epam/ketchertheir own version, first seen Nov 1, 2026
- netbox-community/netboxtheir own version, first seen Nov 1, 2026
Used with
Projects with it also use publish-release, sync-docs and 7 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
Model Context ProtocolSkillpublish-release
Publish a GitHub release for the C# MCP SDK after a prepare-release PR has been merged. Refreshes release notes to include any PRs merged since preparation, warns about version or breaking change impacts from late-arriving PRs, and creates a draft GitHub release. Use when asked to publish a release, finalize a release, create release notes, or complete a release after the prepare-release PR has been merged.
Also in 3 projects that use this
Eipi-TongSkillsync-docs
Update SPEC.md and docs/DECISIONS.md to reflect recent code changes. Use after completing a feature, or when the user says "sync docs", "update the spec", or "log this decision".
Also in 4 projects that use this
thlaureSkillreview-change
Use this skill when the user asks for a code review, a correctness pass, a check before PR, or asks whether a Signalist change follows project conventions.
Also in 3 projects that use this
DockerSkillfix-issue
Fix a single GitHub issue end-to-end: triage, research, write the fix, review, and create a PR. Use when asked to fix an issue: "fix issue 1234", "resolve #500", "create a PR for issue 200".
Also in 4 projects that use this
kiminmonacoSkillimplement-feature
Guide for implementing features in ClaudeBar following architecture-first design, TDD, rich domain models, and Swift 6.2 patterns. Use this skill when: (1) Adding new functionality to the app (2) Creating domain models that follow user's mental model (3) Building SwiftUI views that consume domain models directly (4) User asks "how do I implement X" or "add feature Y" (5) Implementing any feature that spans Domain, Infrastructure, and App layers
Also in 3 projects that use this
SentrySkillwrite-docs
Create or update documentation pages for the Abacus docs site. Use when adding new documentation, updating existing pages, or ensuring docs stay in sync with code changes. Ensures pages follow Starlight patterns and build correctly.
Also in 3 projects that use this
Other versions47 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 47
- CherryHQ/cherry-studio★ 53ksame name
- redis/go-redis★ 22ksame name
- netbox-community/netbox★ 22ksame name
- joke2k/faker★ 19ksame name
- pybind/pybind11★ 18ksame name
- telepresenceio/telepresence★ 7.3ksame name
- modelcontextprotocol/csharp-sdk★ 4.6ksame name
- tmoroney/auto-subs★ 4.5ksame name
- CLIUtils/CLI11★ 4.5ksame name
- ethereum/consensus-specs★ 4ksame name
37 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository12 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- claude-plugins/CLAUDE.mdCLAUDE.md
- add-componentSkill
- api-compat-reviewSkill
- feature-implementationSkill
- implementSkill
- migration-reviewSkill
- perf-reviewSkill
- plugin-impact-reviewSkill
- review-qualitySkill
- spec-qualitySkill
- test-authorSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
50 rules. 0 are common, 27 are distinctive, and 23 are very specific to this project.
0
Common
Most of the 46 edited versions we read keep these.
27
Distinctive
This author's own choices. Worth a look.
23
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 15
Run this from any branch after all feature PRs have been merged to main.
- Distinctive: line 33
If git status shows uncommitted changes, stop and resolve them before continuing.
- Distinctive: line 47
If this command fails (no tags exist yet), use the full history — note the first commit hash as the baseline and treat the release as the …
- Very specific: line 75
Commit only touches: build.gradle.kts, gradle/, *.properties, .gitignore, .github/, Dockerfile, docker-compose.yml, scripts/, *.md files …
- Distinctive: line 81
Use only these categories (omit any that have no entries):
- Distinctive: line 92
Start with a past-tense verb (Added, Fixed, Improved, Removed, Changed)
- Distinctive: line 95
Do not mention internal class names, Kotlin types, or file paths
- Very specific: line 100
Check if any files under claude-plugins/ changed since the last tag:
- Distinctive: line 106
If the output is non-empty, plugin content changed.
- Distinctive: line 135
If no plugin files changed, skip plugin versioning entirely (server-only release).
- Very specific: line 141
Plugin-only release: If the release type is plugin-only, skip the server bump entirely.
- Distinctive: line 154
If multiple conditions apply, use the highest applicable level.
- Very specific: line 191
Use today's date in YYYY-MM-DD format.
- Very specific: line 225
Update the OCI identifier in packages[] to the immutable release image tag: ghcr.io/jpicklyk/task-orchestrator:X.Y.Z (no v prefix — this is …
- Very specific: line 269
Read CHANGELOG.md. Feature PRs accumulate entries under a ## [Unreleased] section at the top of the file.
- Distinctive: line 300
Do not modify any existing entries below the new section.
- Distinctive: line 302
If plugin content changed (Step 4d), add under the appropriate section:
- Distinctive: line 335
Before creating the PR, verify these README items and fix any that are stale.
- Very specific: line 344
Every Docker image reference must use :latest — never a branch name or hardcoded version tag.
- Very specific: line 354
Do not ship a release carrying a known-vulnerable or stale dependency.
- Distinctive: line 370
Never write "addresses CVE-X" in a commit, changelog, PR body, or note without citing BOTH:
- Distinctive: line 380
If the audit finds a live CVE or a materially stale pin, land the dependency fix as its own PR before cutting the release — do not fold it …
- Very specific: line 408
If the branch already has an open PR, use gh pr edit with the same title and body instead.
- Distinctive: line 410
If the user says "show me the command" or "don't run it yet", print the full command as a code block instead of executing it.
- Distinctive: line 431
Ask the user: "Ready to merge the release PR?"
- Distinctive: line 438
If the user prefers to merge manually (e.g., via GitHub UI), wait for them to confirm it's merged before continuing.
- Distinctive: line 448
Verify the pull is a fast-forward.
- Very specific: line 466
If conclusion: success — proceed to 11e immediately, cancel the loop
- Very specific: line 467
If status: in_progress — wait for the loop to report completion
- Very specific: line 468
If conclusion: failure — stop and fix.
- Very specific: line 488
Use /loop to track the docker-publish workflow:
- Very specific: line 499
If conclusion: success — cancel the loop, proceed to 11g
- Very specific: line 500
If status: in_progress or queued — wait for the loop
- Very specific: line 501
If conclusion: failure — report the failure, investigate, and help the user resolve it.
- Distinctive: line 564
Verify the body now leads with the curated notes:
- Distinctive: line 577
No tag or Docker rebuild needed — only plugin content changed.
- Very specific: line 599
IMPORTANT: Do NOT use gh workflow run — the CI workflow is triggered by tag pushes (v*), not manual dispatch.
- Very specific: line 606
Use this flow when a long-lived branch (release/4.0) becomes the next major line on main.
- Very specific: line 611
a. Cut
release/3.xfirst: Before anything else, from the tip of main: - Very specific: line 656
d. Tag from
main: Before the landing PR leaves draft, re-merge origin/release/4.0 into the landing branch and fold anything under its ## … - Distinctive: line 685
Do not tag before CI is green on main — tagging a broken commit ships a broken Docker image
- Distinctive: line 686
Do not include raw commit hashes or internal file paths in the changelog
- Distinctive: line 687
Do not bump version without confirmation from the user
- Very specific: line 688
Do not stage files other than version.properties, server.json, CHANGELOG.md, claude-plugins/CLAUDE.md, plugin version files (if changed), …
- Very specific: line 689
Do not forget server.json — the registry publish step fails when it lags version.properties
- Distinctive: line 690
Do not create the PR if there are no commits ahead of the last tag
- Distinctive: line 691
Do not bump plugin versions outside of the release workflow
- Distinctive: line 692
Do not squash the major-release landing PR
- Distinctive: line 693
Do not tag before the ruleset edit and the merge commit are on main
- Very specific: line 694
Do not leave GitHub's auto-generated PR-title list as the final release notes — overwrite the GitHub Release body with the curated …
“Common” is measured: kept by at least 6 in 10 of the 46 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Prepare Release
End-to-end release workflow. Reads commits since the last release tag, infers the semver bump, drafts a user-facing changelog, confirms with you, creates a release PR, merges it, verifies CI is green, tags the release, and monitors the Docker build to completion.
Usage: /prepare-release
Run this from any branch after all feature PRs have been merged to main.
Steps 1-11 are the ordinary point-release path (3.x and 4.x). A major release that lands a long-lived
branch on main follows "Major release landing (X1, first used for 4.0.0)" below instead of Steps 1, 7, 10
and 11b-11e.
Step 1 — Ensure Clean Main
Switch to main and pull latest:
git checkout main
git pull origin main
git statusIf git status shows uncommitted changes, stop and resolve them before continuing. The
working tree must be clean.
Step 2 — Find Last Release Tag
git describe --tags --abbrev=0Note this as LAST_TAG (e.g., v2.0.2). All commits after this tag are candidates for
the release.
If this command fails (no tags exist yet), use the full history — note the first commit hash as the baseline and treat the release as the initial version.
Step 3 — Gather Commits Since Last Tag
git log <LAST_TAG>...HEAD --oneline
git diff <LAST_TAG>...HEAD --stat
git diff <LAST_TAG>...HEAD --name-onlyCollect the full output of each. Do not truncate.
Step 4 — Filter and Synthesize (Critical Reasoning Step)
This is not a raw dump of commit messages. Analyze the material and produce a curated, user-facing summary.
4a. Discard internal-only commits
Ignore commits that match any of the following:
- Subject starts with:
wip,WIP,checkpoint,fixup!,chore: rebase,Merge branch,version bump,release: - Commit only touches:
build.gradle.kts,gradle/,*.properties,.gitignore,.github/,Dockerfile,docker-compose.yml,scripts/,*.mdfiles with no API impact - Subject is a bare file list or CI housekeeping note
4b. Group meaningful changes by theme
Use only these categories (omit any that have no entries):
- Breaking Changes — removed tool, renamed/removed parameter, incompatible schema change
- New Features — new MCP tool, new operation on existing tool, new config option, new query parameter
- Bug Fixes — incorrect behavior corrected, crash fixed, data integrity issue resolved
- Performance — measurable throughput or latency improvement
- Documentation — user-visible docs (only if substantive)
4c. Write 3-8 user-facing bullet points
Rules for each bullet:
- Start with a past-tense verb (Added, Fixed, Improved, Removed, Changed)
- Name the tool or feature affected (e.g.,
items_read,advance) - Describe the benefit to the user, not the implementation detail
- Do not mention internal class names, Kotlin types, or file paths
- Example:
Added \fields: ["ancestors"]` on `items_read` `get` — eliminates parent-walk call chains for breadcrumb context`
4d. Detect plugin content changes
Check if any files under claude-plugins/ changed since the last tag:
git diff <LAST_TAG>...HEAD --name-only -- claude-plugins/If the output is non-empty, plugin content changed. Also check if any server source files (Kotlin, migrations, Gradle config) changed:
git diff <LAST_TAG>...HEAD --name-only -- current/src/ current/build.gradle.kts gradle/Classify the release type:
- server — server source changed, no plugin changes
- both — server source and plugin content both changed
- plugin-only — only plugin files changed (skills, hooks, orchestration context), no server source
Read the current plugin version from the authoritative source — do not assume it matches any git tag. The version in the repository files may have been bumped in a previous release:
cat claude-plugins/task-orchestrator/.claude-plugin/plugin.json | grep '"version"'Determine the plugin bump level using the same semver rules as the server version, but scoped to plugin content:
| Condition | Plugin Bump |
| Breaking change to skill interface, hook behavior, or orchestration context contract (including removing a shipped component) | major |
| New skill, new hook, new orchestration component added | minor |
| Content fixes, wording, skill adjustments, script tweaks | patch |
Note the plugin bump level separately from the server bump level — they are independent. If no plugin files changed, skip plugin versioning entirely (server-only release).
Step 5 — Infer Bump Level
Plugin-only release: If the release type is plugin-only, skip the server bump entirely.
The server version in version.properties stays unchanged. Only the plugin version (determined
in Step 4d) is bumped. Proceed to Step 6 with the server version as-is and the plugin version
as the new value.
Server or both release: Examine the synthesized changes and determine the bump level:
| Condition | Bump |
| Any breaking API change (removed tool, renamed/removed required parameter, incompatible response schema) | major |
| New tool, new capability on existing tool, new config option, new query parameter | minor |
| Bug fix, performance improvement, docs only, internal refactor with no API change | patch |
If multiple conditions apply, use the highest applicable level.
State the bump level and the one-sentence reason. Example:
Bump: minor — GitHub wiki CI sync and release automation added (new capability, no breaking change).
Calculate the proposed new version:
- patch: increment
VERSION_PATCHby 1, keep others - minor: increment
VERSION_MINORby 1, resetVERSION_PATCHto 0, keepVERSION_MAJOR - major: increment
VERSION_MAJORby 1, resetVERSION_MINORandVERSION_PATCHto 0
Step 6 — Present for Confirmation
Output the following block and stop. Wait for the user to confirm or request changes.
## Proposed Release: vX.Y.Z (CURRENT -> NEW)
**Release type:** <server | both | plugin-only>
**Bump level:** <major | minor | patch>
**Reason:** <one sentence>
**Plugin version:** <CURRENT -> NEW> (<bump level>) — or "No plugin changes"
### Changelog Draft
## [X.Y.Z] - YYYY-MM-DD
### <Added | Changed | Fixed>
- <bullet 1>
- <bullet 2>
...
---Use today's date in YYYY-MM-DD format. If the user requests changes, revise and
re-present before continuing.
Step 7 — Create Release Branch
After confirmation:
git checkout -b release/vX.Y.ZStep 8 — Apply Changes
8a. Update version.properties and server.json
Plugin-only release: Skip this step — the server version stays unchanged.
Server or both release: Edit version.properties in the project root. Set only the
lines that need to change. Reset lower components on a major or minor bump.
Example for a minor bump from 2.0.2 -> 2.1.0:
VERSION_MAJOR=2
VERSION_MINOR=1
VERSION_PATCH=0Then edit server.json (the MCP Registry record) in the project root — two fields:
- Set the top-level
versionto the new version (X.Y.Z, novprefix). - Update the OCI
identifierinpackages[]to the immutable release image tag:ghcr.io/jpicklyk/task-orchestrator:X.Y.Z(novprefix — this is the Docker tag, not the git tag). Never leave it on:latest; the MCP Registry record for a version is immutable, so a mutable tag makes that record drift to whatever image later ownslatest(#297).docker-publish.ymlfails the release if either field disagrees withversion.properties, and then publishes the record to the registry viamcp-publisher.
Do not skip the server.json edit. It is the one file in this step that no build reads, so a
missed bump is invisible until the registry publish fails (or, before the guard existed, silently
published a stale record — v3.3.0 through v3.13.1 shipped with server.json still at 3.2.0).
History: this paragraph was first added by #298 to a flat
.claude/skills/prepare-release.mdthat duplicated this file, and was lost when #321 deleted the duplicate. The skill lives only at.claude/skills/prepare-release/SKILL.md— never recreate the flat file.
8b. Update plugin version files (if plugin content changed)
Skip this step if no plugin files changed in Step 4d (claude-plugins/).
This repository ships two plugins, each versioned on its own: task-orchestrator and
task-orchestrator-mod. Bump only the plugin whose directory changed. Read each plugin's current
version from its plugin.json (claude-plugins/task-orchestrator/.claude-plugin/plugin.json and
claude-plugins/task-orchestrator-mod/.claude-plugin/plugin.json) and calculate the new version using
the plugin bump level from Step 4d.
For each plugin being bumped, update both files with the new version:
claude-plugins/<plugin>/.claude-plugin/plugin.json- update theversionfield.claude-plugin/marketplace.json- updateplugins[name="<plugin>"].version
Also update the version table in claude-plugins/CLAUDE.md: find the row for each bumped plugin and
replace the version number. Neither plugin has a package.json; the two files above plus the table row
are its only version fields.
Stage every file you changed (in addition to version.properties), for example when both plugins move:
git add claude-plugins/task-orchestrator/.claude-plugin/plugin.json \
claude-plugins/task-orchestrator-mod/.claude-plugin/plugin.json \
.claude-plugin/marketplace.json \
claude-plugins/CLAUDE.md8c. Insert new section into CHANGELOG.md
Read CHANGELOG.md. Feature PRs accumulate entries under a ## [Unreleased] section at the top
of the file; check whether one exists before writing anything.
If ## [Unreleased] exists: rename that header in place to ## [X.Y.Z] - YYYY-MM-DD and fold
your synthesized bullets from Step 4 into its existing ### Added / ### Changed / ### Fixed
subsections (create a subsection only if it is missing). Keep every existing bullet — they are
the per-PR record - and add only what Step 4 found missing. Categories in use are Added,
Changed, Removed, Fixed, Security and Plugin. Leave an empty ## [Unreleased] heading above the cut
section, with one blank line between the two headings (one rule for every release); the next feature PR
fills it. Match each merged PR in git log <LAST_TAG>..HEAD
to a bullet by the behaviour it describes (per-PR bullets usually cite no PR number) — merged PRs
with no bullet are the gaps to fill, except a PR whose body states Changelog: none (<why>),
which is intentionally bullet-less (the September 2026 release prep found three bug-wave PRs
entirely absent).
If no ## [Unreleased] section exists: find the first ## [ versioned entry (after the
header). Insert the new section immediately above it, with a trailing --- separator and a
blank line:
## [X.Y.Z] - YYYY-MM-DD
### Added
- bullet 1
- bullet 2
---
## [previous version] ...Do not modify any existing entries below the new section.
If plugin content changed (Step 4d), add under the appropriate section:
- Bumped plugin version to X.Y.Z (
<reason>)
8d. Stage, commit, and push
Plugin-only release (plugin files already staged from 8b):
git add CHANGELOG.md
git status # confirm only plugin version files + changelog are staged
git commit -m "release: bump to vX.Y.Z — plugin vA.B.C"
git push origin release/vX.Y.ZServer-only release:
git add version.properties server.json CHANGELOG.md
git status # confirm only expected files are staged
git commit -m "release: bump to vX.Y.Z"
git push origin release/vX.Y.ZBoth release (plugin files already staged from 8b):
git add version.properties server.json CHANGELOG.md
git status # confirm expected files are staged
git commit -m "release: bump to vX.Y.Z"
git push origin release/vX.Y.ZStep 9 — Pre-PR Checklist
Before creating the PR, verify these README items and fix any that are stale. If fixes are
needed, add README.md to the staged files and amend the commit before pushing.
Docker image references:
grep -n "ghcr.io/jpicklyk/task-orchestrator" README.mdEvery Docker image reference must use :latest — never a branch name or hardcoded version tag.
Version badge (line ~7 in README):
Both /github/v/tag/ and /github/v/release/ badge endpoints work — the CI workflow
creates a git tag and a GitHub release on every deploy. No change needed unless the URL
is broken.
Dependency & CVE audit:
Do not ship a release carrying a known-vulnerable or stale dependency. For each
security-sensitive dependency in gradle/libs.versions.toml (sqlite-jdbc, flyway-core,
exposed-*, ktor-*, nimbus-jose-jwt, google-tink), compare the pinned version against
the latest stable on Maven Central:
# Example for sqlite-jdbc (adjust the group path per artifact):
curl -s "https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/maven-metadata.xml" \
| grep -oE '<release>[^<]+</release>'Flag any pin that is behind latest stable, on a beta/alpha/-rc version, or named in an
open CVE advisory.
Critical — verify remediation, not just the bump. A version bump closes a CVE only when the dependency's bundled component version is at or above the CVE's upstream fixed-in version. Never write "addresses CVE-X" in a commit, changelog, PR body, or note without citing BOTH:
- the CVE's upstream fixed-in version (e.g., "SQLite 3.50.2"), and
- the version actually bundled by the pinned dependency (e.g., "sqlite-jdbc 3.49.1.0 bundles SQLite 3.49.1 — still exposed").
Precedent: CVE-2025-6965 (SQLite, fixed in 3.50.2) stayed live in this repo for ~3 months because a bump to
sqlite-jdbc 3.49.1.0(bundles SQLite 3.49.1) was recorded as remediation without checking the bundled version.
If the audit finds a live CVE or a materially stale pin, land the dependency fix as its own PR before cutting the release — do not fold it into the release commit.
Step 10 — Create the PR
gh pr create \
--base main \
--title "release: vX.Y.Z — <one-line summary of most significant change>" \
--body "$(cat <<'EOF'
## Summary
- <bullet 1>
- <bullet 2>
- <bullet 3>
## Version
**Release type:** <server | both | plugin-only>
<CURRENT> -> <NEW>
Prepared with /prepare-release
EOF
)"If the branch already has an open PR, use gh pr edit with the same title and body instead.
If the user says "show me the command" or "don't run it yet", print the full command as a code block instead of executing it.
Step 11 — Merge, Verify, Tag, and Monitor
This step automates the post-PR flow. After the PR is created, drive the release to completion rather than printing manual instructions.
11a. Print summary
Release prepared: CURRENT -> vX.Y.Z (<bump level>)
Release type: <server | both | plugin-only>
Branch: release/vX.Y.Z
PR: <URL from gh pr create>11b. Merge the release PR
Ask the user: "Ready to merge the release PR?"
If confirmed:
gh pr merge <PR-number> --squash --delete-branchIf the user prefers to merge manually (e.g., via GitHub UI), wait for them to confirm it's merged before continuing.
11c. Sync local main
git checkout main
git pull origin mainVerify the pull is a fast-forward. If it's not (local main has diverged from origin), stop and investigate — this should not happen under the PR-per-feature workflow.
11d. Wait for CI to pass on main
Server or both release: CI must be green before tagging. Use /loop to
monitor automatically:
/loop 2m gh run list --branch main --limit 1 --json status,conclusion,displayTitleWhile monitoring, check the first result immediately:
gh run list --branch main --limit 1 --json status,conclusion,displayTitle- If
conclusion: success— proceed to 11e immediately, cancel the loop - If
status: in_progress— wait for the loop to report completion - If
conclusion: failure— stop and fix. Do NOT tag. Report the failure to the user, investigate the cause, and merge a fix. After fixing, re-check CI before tagging. The tag must point to a green commit.
Plugin-only release: Skip CI monitoring — no server code changed, no Docker image to build. Proceed directly to the plugin-only completion in 11h.
11e. Tag and push (server or both release)
Once CI is green, cancel the monitoring loop and create the tag:
git tag vX.Y.Z
git push origin vX.Y.ZThis triggers the "Build, Publish, and Release" workflow (docker-publish.yml).
11f. Monitor the Docker build
Use /loop to track the docker-publish workflow:
/loop 2m gh run list --workflow=docker-publish.yml --limit=1 --json status,conclusion,displayTitleCheck immediately:
gh run list --workflow=docker-publish.yml --limit=1 --json status,conclusion,displayTitle- If
conclusion: success— cancel the loop, proceed to 11g - If
status: in_progressorqueued— wait for the loop - If
conclusion: failure— report the failure, investigate, and help the user resolve it. The Docker build may need a re-tag or a fix-and-retag cycle.
11g. Publish curated release notes (server or both release)
The docker-publish.yml workflow creates the GitHub Release automatically on the tag push, but it
fills the body with GitHub's auto-generated PR-title list — not the curated changelog. Once the
release exists (after 11f succeeds), overwrite its body with this version's CHANGELOG.md section so
the published release notes match what you actually wrote. CHANGELOG.md is the source of truth.
Extract the section between this version's ## [X.Y.Z] header and the next ## [ header, and set it
as the release body:
RELNOTES="$(mktemp)"
awk -v ver="X.Y.Z" '
BEGIN { hdr = "## [" ver "]" }
index($0, hdr) == 1 { capture=1; next } # skip the version header line (redundant with the release title)
capture && index($0, "## [") == 1 { exit } # stop at the next version section
capture { print }
' CHANGELOG.md > "$RELNOTES"
# Never publish an empty body. `gh release edit --notes-file` accepts an empty file without
# complaint, so a silently-failed extraction replaces the auto-generated notes with NOTHING —
# strictly worse than leaving them. Fail loudly instead.
if [ ! -s "$RELNOTES" ]; then
echo "ERROR: extracted release notes are empty — check that CHANGELOG.md contains a '## [X.Y.Z]' header" >&2
rm -f "$RELNOTES"
exit 1
fi
# A curated body wins when the release ships one (a major release's section is too long to publish).
if [ -s ".github/release-notes/vX.Y.Z.md" ]; then
cp ".github/release-notes/vX.Y.Z.md" "$RELNOTES"
fi
# GitHub rejects a very long release body; treat 120000 characters as the ceiling and fail loudly.
if [ "$(wc -m < "$RELNOTES")" -gt 120000 ]; then
echo "ERROR: release notes exceed 120000 characters; write a curated .github/release-notes/vX.Y.Z.md" >&2
rm -f "$RELNOTES"
exit 1
fi
gh release edit vX.Y.Z --notes-file "$RELNOTES"
rm -f "$RELNOTES"Match the header literally — do not build a regex from the version string. X.Y.Z contains
. (any-char in a regex) and sits inside […], so the obvious $0 ~ "^## \\[" ver "\\]" form
is fragile: in a dynamic (string-built) regex the bracket escaping is interpreted twice, and on
gawk it degrades to a character class — the match never fires and the extraction silently yields
zero lines. index($0, hdr) == 1 compares plain strings, so version dots and brackets carry no
special meaning. Verified failing on gawk 2026-08-04 during the v3.13.1 release.
A major release body keeps two lines from its "Upgrading from 3.x" section even when the rest is trimmed:
- the data-step line: the first start runs the data steps (
type-backfill,traits-from-properties,config-canonicalize,pin-backfill) before the readiness marker is written, and a failed step fails startup asDATA_STEPS; - the
UPGRADE_BACKUPline: the server writes a verified<db>.pre-4.0.<yyyyMMddTHHmmssZ>.bakbeside the database before the first migration (default on,UPGRADE_BACKUP=falseskips it), and the volume should still be copied by hand.
Verify the body now leads with the curated notes:
gh release view vX.Y.Z --json body -q '.body' | head -20To also keep GitHub's contributor/PR list beneath the curated notes, capture it first with
gh api "repos/{owner}/{repo}/releases/generate-notes" -f tag_name=vX.Y.Z -q '.body', append it to$RELNOTESunder a---separator, then rungh release edit.
Plugin-only release: skip this step — no tag and no GitHub Release are created.
11h. Plugin-only completion
No tag or Docker rebuild needed — only plugin content changed. Plugin users pick up the new version when they reinstall the marketplace.
Report:
Release complete: vX.Y.Z (plugin-only)
Plugin version: vA.B.CSkip to 11i.
11i. Final report
Server or both release:
Release complete: vX.Y.Z
Docker image: ghcr.io/jpicklyk/task-orchestrator:X.Y.Z (and :latest)
GitHub Release: https://github.com/jpicklyk/task-orchestrator/releases/tag/vX.Y.ZCancel any remaining monitoring loops.
IMPORTANT: Do NOT use gh workflow run — the CI workflow is triggered by tag
pushes (v*), not manual dispatch.
Major release landing (X1, first used for 4.0.0)
Use this flow when a long-lived branch (release/4.0) becomes the next major line on main. It
replaces Steps 1, 7, 10 and 11b-11e; Steps 2-6, 8, 9, 11f-11i still apply with the differences noted
here. The version-bump PR targets the long-lived branch (squash, as any PR there); the landing PR then
carries the branch to main. The merge, the tag and the publish wait for the user's go.
a. Cut release/3.x first. Before anything else, from the tip of main:
git branch release/3.x origin/main
git push origin release/3.xThe 3.x line keeps its 3.16.0 version, the 3.16 image tag and its own [Unreleased] changelog
section. The 3.x line is frozen except for security fixes; a security fix lands there as a twin of its 4.x PR.
b. Twin audit. main keeps taking 3.x fixes while the long-lived branch is open, and each one is
meant to have a twin on release/4.0 (or a stated "4.0 already covers it"). List them with
git log --format='%h %s' $(git merge-base origin/main origin/release/4.0)..origin/main and, for each,
find the twin (same bracketed item id in git log <merge-base>..origin/release/4.0), the matching
content on release/4.0, or the 4.0 code that replaced it. Every commit with none gets a 4.0 twin PR
merged into release/4.0 before the landing PR leaves draft. The landing keeps release/4.0's tree
(step c), so anything on main without a twin is dropped from main (it survives on release/3.x).
For 4.0.0 the audit found 21 of 22 covered; d8f23c81 (#574, carry-in plumbing) needed a twin.
c. Landing PR. The two lines conflict widely (for 4.0.0, 270 paths), so GitHub cannot
merge release/4.0 into main directly. Cut a landing branch from release/4.0, retire the guard
job by deleting .github/workflows/pr-base-guard.yml, and record main as a parent while keeping the
release/4.0 tree, which is correct only after step b passes:
git switch -c land/4.0.0 origin/release/4.0
git rm .github/workflows/pr-base-guard.yml
git commit -m "ci: retire the pr-base-guard guard job for the 4.0 landing"
git merge -s ours origin/main -m "Merge main into land/4.0.0: release/4.0 supersedes 3.x (twin audit passed)"
git push -u origin land/4.0.0Open land/4.0.0 -> main as a draft titled release: Task Orchestrator 4.0.0 with the CHANGELOG
## [4.0.0] section as its body. Merge it with ONE merge commit (gh pr merge <n> --merge), never
squash: a squash discards the branch history and the later release/3.x twins can no longer be matched
against it. guard is a required status check in both the "Main branch protection" and "Release 4.0
protection" rulesets, and the landing branch no longer runs it, so the user removes guard from both
rulesets before the merge; repository settings are outside the orchestrator's autonomy, so ask and
wait. Re-merge origin/release/4.0 into the landing branch (an ordinary merge) whenever
release/4.0 moves before the landing.
The 4.0.0 CHANGELOG section is the release record. main's [Unreleased] 3.x bullets are not folded
into it; they ship from release/3.x in its next point release. claude-plugins/task-orchestrator/scripts/resolve-changelog.mjs resolves
only [Unreleased] against [Unreleased], so it is not used here.
d. Tag from main. Before the landing PR leaves draft, re-merge origin/release/4.0 into the landing
branch and fold anything under its ## [Unreleased] (a twin or fix merged into release/4.0 after the
cut) into ## [4.0.0], so the release section is complete and Step 11g's extraction finds it. Once CI is
green on the merge commit, git tag v4.0.0 && git push origin v4.0.0.
docker-publish.yml then runs needs: test, the release-gate build, the fresh smoke, the upgrade smoke
from the 3.16 golden volume, Trivy, pushes latest, 4.0.0 and 4.0, publishes server.json with
mcp-publisher and creates the GitHub release with generated notes, which Step 11g overwrites.
e. After the merge. Confirm sync-wiki.yml ran on main (it triggers on a push to main that
touches current/docs/**). Once the marketplace shows plugin 4.0.0, refresh the plugin cache as
claude-plugins/CLAUDE.md "Plugin Discovery and Cache Refresh" describes:
rm -rf ~/.claude/plugins/cache/task-orchestrator-marketplace, then
claude plugin marketplace update task-orchestrator-marketplace; re-extraction is lazy, at the next
session start. This is a step to run after publishing, not part of the release PR.
f. Acceptance. ghcr.io/jpicklyk/task-orchestrator:4.0.0 exists, latest resolves to it,
3.16 still resolves, and the marketplace shows plugin 4.0.0.
Quick Reference
| Bump level | When | Version change |
| major | Breaking API change | X+1.0.0 |
| minor | New capability, no breaking change | X.Y+1.0 |
| patch | Bug fix, docs, refactor | X.Y.Z+1 |
Common mistakes to avoid:
- Do not tag before CI is green on main — tagging a broken commit ships a broken Docker image
- Do not include raw commit hashes or internal file paths in the changelog
- Do not bump version without confirmation from the user
- Do not stage files other than
version.properties,server.json,CHANGELOG.md,claude-plugins/CLAUDE.md, plugin version files (if changed), andREADME.md(if fixes were needed) - Do not forget
server.json— the registry publish step fails when it lagsversion.properties - Do not create the PR if there are no commits ahead of the last tag
- Do not bump plugin versions outside of the release workflow
- Do not squash the major-release landing PR
- Do not tag before the ruleset edit and the merge commit are on
main - Do not leave GitHub's auto-generated PR-title list as the final release notes — overwrite the GitHub Release body with the curated
CHANGELOG.mdsection (Step 11g)
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

