| name | release-pr |
| description | Open a release PR: bumps versions in melos.yaml and pubspecs, finalises CHANGELOGs, opens a PR with auto-generated release notes against master (stable) or v10.0.0 (beta).
|
| disable-model-invocation | true |
| argument-hint | [version] |
| arguments | ["version"] |
| allowed-tools | ["Bash(git *)","Bash(gh *)","Bash(melos *)","Bash(which *)","Bash(grep *)","Read","Edit","Write"] |
release-pr
Opens a release PR for stream-chat-flutter. Branch release/v<X.Y.Z> → base master (stable) or v10.0.0 (beta) →
title chore(repo): release v<X.Y.Z>.
This skill only opens the PR. After merge, tagging and pub.dev publishing happen via release_tag.yml +
release_publish.yml (stable: automatic; beta/named: maintainer pushes the tag manually).
If $version is provided (e.g. /release-pr 9.24.0), use it. Strip any leading v. Otherwise ask the user.
Release types
| Type | Base | Version shape | Tagging |
|---|
| Stable | master | X.Y.Z | Auto via release_tag.yml on merge |
| Beta | v10.0.0 | X.Y.Z-beta.N | Manual (release_tag.yml only watches master) |
| Named pre-release | feature branch | X.Y.Z-<name>.N | Manual; same as beta |
distribute_internal.yml builds sample apps for branches like feat/design-refresh — not a release. Don't use
this skill for those.
Inputs
- Version (
X.Y.Z or X.Y.Z-suffix). Use $version if supplied, otherwise ask. Don't infer.
- Base branch — auto-derive from
$version:
- No suffix (
X.Y.Z) → master. Stable release.
-beta.N suffix → v10.0.0. Verify it exists: git ls-remote --heads origin v10.0.0.
- Any other suffix (
-alpha.N, -rc.N, -design-refresh.N, …) → ask the user which feature branch to target.
- Previous tag for the release-notes diff. Run
gh release list --limit 10; pick the most recent tag of the same
train (stable = no hyphen in tag; beta = matches -beta.; named = matches the same suffix prefix).
Pre-flight
Run these checks. If any fail, stop the skill, surface the failing check to the user, and do not try to auto-fix
(no stashing uncommitted work, no force-pulling, no killing processes).
-
git status --short -uno clean after git checkout <base> + git pull --ff-only.
-
which melos succeeds.
-
gh auth status succeeds.
-
gh pr list --head release/v<version> --state all --json number returns [].
-
Latest CI on the base-branch tip is green: gh run list --branch <base> --limit 5 — no failures on the most
recent runs.
-
No publishable package depends on stream_core_flutter by git ref or path — pub.dev rejects both, and
release_publish.yml publishes in dependency order, so the earlier packages go live and only
stream_chat_flutter fails, leaving the version half-published:
grep -n -A4 '^\s*stream_core_flutter:' melos.yaml packages/*/pubspec.yaml
Match on the dependency key and its source, not on a URL spelling — git: takes a scalar URL as well as a map,
and the URL need not end in .git. Anything other than a single-line version constraint is a hard stop:
stream_core_flutter must be released to pub.dev first (its own repo has a release-pr skill), then the pin
swapped to a version constraint. Surface it and stop — don't pick the version yourself.
Steps
1. Branch off the chosen base
Pre-flight already left you on <base> with latest. Just create the release branch:
git checkout -b release/v<version>
2. Bump versions
Edit two sets of files by hand, then let melos bs propagate the rest.
Edit:
melos.yaml — in the command.bootstrap.environment.dependencies block, bump all five stream_chat*: ^<version>
entries. Locate with grep -n "^ stream_chat" melos.yaml.
- Each
packages/*/pubspec.yaml (5 files) — set version: <version>. Do not touch
packages/*/example/pubspec.yaml or sample_app/pubspec.yaml version: fields; their deps are synced by
melos bs.
Then run:
melos bs
melos bs does the rest:
- Propagates the
melos.yaml deps block into every workspace pubspec, including each package's intra-monorepo dep
constraints, every packages/*/example/pubspec.yaml, and sample_app/pubspec.yaml.
- Fires the
command.bootstrap.hooks.post: melos run version:update hook, which runs tools/generate_version.dart
and regenerates packages/stream_chat/lib/version.dart from the new pubspec version.
Do not run ./tools/version.sh. It calls melos version which leaves melos.yaml's deps block stale — the next
melos bs would re-write every pubspec dep constraint back to the old version.
Verify the diff shape matches the previous release PR. Find its number with:
gh pr list --search "chore(repo): release in:title" --state merged --limit 5 --json number,title
Then compare:
git diff --stat
gh pr diff <prev-release-pr-number> --name-only
3. Finalise the CHANGELOGs
Five files: packages/{stream_chat, stream_chat_flutter, stream_chat_flutter_core, stream_chat_localizations, stream_chat_persistence}/CHANGELOG.md.
For each, apply the first matching rule below — it's a decision tree, not a sequence:
- Top section is
## Upcoming Changes or ## Upcoming → rename to ## <version>. Keep bullets.
- User-facing changes since
v<prev> — new APIs, bug fixes users would notice, deprecations. Check with
git log v<prev>..HEAD --oneline -- packages/<pkg>. Add a ## <version> header with bullets in the existing
emoji-prefixed sections only (✅ Added, 🚀 Performance, 🐞 Fixed, 🔄 Changed). Don't invent new
section names.
- Only
stream_chat dep bump (no in-package changes, but depends on stream_chat) → add the dep-bump line:
## <version>
- Updated `stream_chat` dependency to [`<version>`](https://pub.dev/packages/stream_chat/changelog).
- Anything else (internal-only changes, test fixes, refactors, or truly nothing) → add
## <version> +
- Minor bug fixes and improvements.
Every package gets a ## <version> header, even if it's only a dep-bump line. Empty version sections and
missing headers both fail pana.
4. Analyze, then commit
melos run analyze
If it fails, surface to the user and stop.
git status --short
git add -u
git commit -m "chore(repo): release v<version>"
git add -u, not -A: pre-flight's git status --short -uno ignores untracked files, so -A would sweep local
artifacts into the release commit. If a release ever does need a genuinely new tracked file, add it by path — the
git diff --stat comparison in step 2 is what catches the omission.
Single commit. The message format is load-bearing: release_tag.yml parses vX.Y.Z from it after merge — and it
gates on the tip commit of master, so the PR must be squash-merged. A merge commit would leave
Merge pull request #… at the tip and the tag job would silently never fire.
melos run lint:pub is deliberately not here: it shells out to pub publish -n, which fails any dirty tree with
"N checked-in files are modified in git". It can only pass once the release commit exists — hence step 5.
5. Verify publishability, then push
melos run lint:pub
This is the real publish gate. Read failures carefully — pub reports two severities and only one blocks:
- "Package validation found the following error" — blocks.
release_publish.yml runs pub publish -f, and
-f does not bypass errors. Must be fixed before merge.
- "potential issue" / "Package has N warnings" —
-f publishes through these. Worth fixing, not blocking.
A common error is a lib/ or test/ file importing a package absent from that package's own dependencies /
dev_dependencies; it resolves locally through a transitive dep and only pub publish catches it. Fix at the
import (prefer the barrel the rest of the package already uses) or by declaring the dep, and tell the user the
release PR now carries a source change.
If it fails, surface to the user and stop — don't push.
git push -u origin release/v<version>
6. Generate the PR body
The body must be exactly what GitHub's release UI produces when you click "Generate release notes" — no template
wrapper, no extra description, no CLA checkboxes. The "New Contributors" block GitHub auto-appends stays in; that's
part of the convention.
gh api repos/GetStream/stream-chat-flutter/releases/generate-notes \
-f tag_name=v<version> \
-f previous_tag_name=v<previous> \
-f target_commitish=<base> \
--jq .body > /tmp/release-notes.md
tag_name: the tag we'll create (need not exist yet).
previous_tag_name: most recent tag of the same train (stable or beta).
target_commitish: the base branch (master or v10.0.0), not the release branch — the notes should cover
every commit between previous_tag_name and where the tag will land after merge.
Read the file once to skim. If a PR title looks wrong, fix it on the originating PR upstream and re-run the API
call; don't hand-edit /tmp/release-notes.md.
7. Open the PR
gh pr create \
--base <base> \
--head release/v<version> \
--title "chore(repo): release v<version>" \
--body-file /tmp/release-notes.md
Return the PR URL.
If this is a beta or named pre-release, also include this reminder in your message to the user:
After this PR merges, manually create and push the tag — release_tag.yml only fires on master:
git checkout <base>
git pull --ff-only
git tag v<version>
git push origin v<version>
For stable releases (base master), no reminder needed — release_tag.yml handles it on merge.
After merge (FYI)
Stable (base master): release_tag.yml extracts vX.Y.Z from the commit, creates and pushes the tag.
release_publish.yml runs melos run release:pub and creates the GitHub release.
Beta / named pre-release: maintainer runs the tag commands surfaced at the end of step 7.
Don't
- Never create a GitHub release (
gh release create, POST /repos/.../releases). Step 6 uses
generate-notes, which is read-only. The release itself is created by release_publish.yml after the tag is
pushed.
- Never push a tag. Stable is automatic on merge; beta/named is the maintainer's manual step.
- Never run
melos run release:pub. That's the publish step, triggered by the workflow on tag push. Even if the
user asks, refuse — running it locally publishes from an unreviewed working tree.
- Never merge the PR. Return the URL and stop.