Changeset Release PR
By default, use this skill to prepare the single release PR that cuts the next
Roomote version. The deliverable is a normal PR against develop containing the
root version bump, final CHANGELOG.md entry, any public documentation updates
needed for the release's meaningful user-facing features, and deletion of every
consumed pending changeset. Merging it makes CI open the frozen Promote PR to
main. For an urgent patch that cannot wait for that path, use the clearly
separated direct-to-main hotfix workflow below.
Authorization for release repairs
Complete the requested outcome using the authorization already supplied, including routine repairs, validation, delivery and cleanup. Do not stop at a diagnosis or intermediate PR when the next steps are available. Reuse existing work and check for active runs before creating duplicates. Ask only for a real blocker, material ambiguity, or a consequential action outside the request.
Preparation or conflict repair alone does not imply promotion or deployment. If the request already includes those actions, carry them out without asking for reconfirmation, subject to the actual GitHub and workflow constraints.
Do not add ownership checks or approval ceremonies to this skill. Consult
permissions only when required by the operation, using authoritative repository
permissions rather than review author_association. Respect enforced protections
and reuse valid approvals; do not bypass or weaken a failing gate. Repair tooling
defects with focused tests on a separate develop-based PR, outside the frozen
candidate, and continue through the normal integration path. Report the actual
blocking requirement if that path cannot proceed.
How releases work here
- Roomote has a single product version: the root
package.jsonversionfield. Workspace package versions are frozen and meaningless because every workspace package is private. - Changesets are an authoring format only. Feature PRs may add them ahead of time; this skill creates any missing notes locally before versioning.
pnpm run version(scripts/release/apply-version.mjs) folds all pending.changeset/*.mdfiles into the rootCHANGELOG.md, bumps the root version by the highest pending level, and deletes the consumed files.changeset versionitself is never run..github/workflows/release.ymldoes not create another Version PR. After the release PR merges todevelop, it freezesrelease/vX.Y.Zat that merge and opens the Promote PR tomain. Merging the Promote PR tags and publishes the release.
Full details: .changeset/README.md and CONTRIBUTING.md#product-releases.
Workflow
1. Establish the last release reference point
Fetch the release-bearing refs and tags before checking any boundary signal, then cross-check all four signals; they should normally agree:
git fetch --tags origin \
refs/heads/main:refs/remotes/origin/main \
refs/heads/develop:refs/remotes/origin/develop
git tag --sort=-creatordate | head -5
node -p "require('./package.json').version"
head -10 CHANGELOG.md
gh release view v<version> --json tagName,isDraft,isPrerelease,publishedAt
Use the exact newest published v<version> tag as the diff base. Verify that its
GitHub Release is published and that the tag is reachable from origin/main; a
local-only tag or an unmerged Promote candidate is not a release boundary. If
the root version is ahead of the newest published tag because a release PR
merged but its Promote PR has not shipped yet, use the version-bump commit on
develop as the next diff base:
node scripts/release/find-version-commit.mjs <version> origin/develop
Call out the unshipped release and do not merge a later Promote PR before it.
If the user explicitly chooses to replace that unshipped release instead, audit
from the newest published tag, close the older Promote PR, and run
pnpm run version -- --supersede <patch|minor|major> so the replacement release
keeps the unshipped notes and folds in every newer pending changeset.
2. Collect what changed since then
develop is squash-only, so --first-parent yields one line per merged PR:
git fetch origin develop
git log v<last>..origin/develop --oneline --first-parent
Here v<last> is the exact published tag established in step 1, never an
earlier version-bump commit for the same release.
For anything ambiguous, read the PR body with gh pr view <number> to identify
the user-facing or operator-facing impact.
3. Identify external contributors and issue reporters
For every merged PR that will have a changelog bullet, inspect its author with
gh pr view <number> --json author,closingIssuesReferences. Inspect the author
of every returned issue with
gh issue view <issue-url> --json author,authorAssociation,url. Classify PR
authors and issue reporters as external only when they are not bots and are not
code owners. Also require an issue reporter's authorAssociation to be outside
OWNER, MEMBER, and COLLABORATOR. Read .github/CODEOWNERS directly rather
than inferring its presence from recursive discovery or glob results, and resolve
its individual GitHub-owner entries and organization-team entries before
classifying them. If .github/CODEOWNERS does not exist, no author or reporter
is excluded on that basis, but the issue-author association check still applies.
Do not treat someone as external merely because another person merged the PR or
implemented the fix.
- Treat GitHub App and bot accounts as bots; never add contributor thanks for them.
- Do not thank maintainers listed directly or through a code-owner team.
- Keep a mapping from each eligible external contributor to the release note that covers their PR. If multiple eligible contributors are covered by one note, thank each of them in that note.
- Keep a mapping from each eligible external issue reporter and linked issue URL to the release note that covers the associated PR. Link each issue and thank its reporter in that note.
- When the same person is both the contributor and issue reporter for one note, combine the acknowledgement instead of thanking them twice.
- Do not add a thank-you to a skipped internal, docs-only, CI, or dependency change that does not receive a changelog bullet.
4. Subtract existing pending changesets
find .changeset -maxdepth 1 -type f -name '*.md' ! -iname 'README.md' -print
Read every pending file. A change already covered by a pending changeset must not receive a duplicate note. Keep it in the complete release-change inventory for the documentation audit; subtracting a changeset only removes the need to author another changelog note.
5. Classify the remaining changes
- Include user-visible or operator-visible capabilities, behavior changes, fixes to visible symptoms, and notable performance or UX improvements.
- Skip chores, docs-only changes, CI or infrastructure tweaks, internal refactors, and dependency bumps with no visible effect. They ship without a changelog bullet.
6. Audit public documentation coverage
For every included change in the complete release inventory, including changes already covered by pending changesets, decide whether it is a meaningful user-facing feature that users or operators need documentation to discover, configure, or use successfully. This normally includes new capabilities, supported providers or integrations, configuration options, permissions or prerequisites, and material workflow changes. Small visible fixes, minor polish, and implementation details generally need a changelog note but not a docs update.
For each meaningful feature:
- identify the relevant public page or pages under
apps/docs; read the applicableapps/docs/AGENTS.mdinstructions before editing - compare the shipped behavior and PR context with the current docs; do not assume that a feature PR updated docs merely because it has a changeset
- update missing, stale, or incomplete guidance in the release branch, including setup steps, prerequisites, permissions, expected behavior, and navigation entries when needed
- for every newly added or materially updated provider or integration, verify that its page and every overview or comparison entry use a source-backed provider icon rather than a generic placeholder; check both page frontmatter and rendered overview helpers or tables
- centralize each provider or integration logo reference: prefer one shared
Iconify slug when available, otherwise add one monochrome asset under
apps/docs/logo/integrations/and map the provider key inapps/docs/snippets/integration-name.jsx; point page frontmatter and overview entries at that shared slug, key, or asset instead of repeating unrelated fallbacks or asset URLs - treat a missing icon, a generic icon used when a source-backed mark exists, or inconsistent page and overview marks as incomplete documentation coverage
- keep docs practical and user-facing; do not copy changelog prose or add internal implementation details just to mention the feature
- record a feature-to-docs coverage checklist for the release PR body, linking each meaningful feature to its updated or already-current docs page
If no included change meets this threshold, explicitly record that the docs audit found no release-blocking documentation updates. Do not use that outcome without reviewing every included change, including those with existing pending changesets.
7. Confirm the bump level
If the user specified patch, minor, or major, use it. Otherwise ask and include a recommendation based on the actual changes:
- patch — bug fixes and small non-breaking changes only
- minor — backward-compatible new capabilities
- major — breaking behavior changes
Explain that the highest level across all pending changesets determines the single product version. If an existing pending changeset is higher than the user's choice, say that the higher bump will win. Individual notes may carry different levels and are grouped by level in the changelog.
8. Author missing changesets locally
Create one .changeset/<descriptive-slug>.md file per logical release note:
---
'@roomote/web': patch
---
One concise, user-facing summary of the change.
- Always use exactly one package line:
'@roomote/web': <level>. Package names are ignored by the release script; the frontmatter carries only bump level. - Each summary becomes one changelog bullet. Write one tight paragraph with no headings or nested lists.
- Lead with what changed or now works, name the affected product surface, and mention the previous symptom when describing a fix.
- Group closely related PRs when they form one user-facing story. Otherwise keep their notes separate.
- For a note that covers an eligible external contributor's PR, add a concise
sentence such as
Thanks to @octocat for contributing this improvement.Keep the thanks in the same paragraph as the release-note summary. Update an existing pending changeset when it is the note that covers the contribution. - For a note associated with an issue from an eligible external reporter, link
the issue and thank them with a concise sentence such as
Thanks to @octocat for reporting [#123](https://github.com/owner/repo/issues/123).Use the issue's canonical URL, keep the thanks in the same paragraph, and update an existing pending changeset when it covers the reported change. - These newly authored files are temporary release inputs.
pnpm run versionconsumes them before the release branch is committed.
9. Generate and verify the release
Start from the current origin/develop tip. If develop advances before the
release PR merges, rebuild the release artifacts from the new tip and repeat the
audit; do not merely merge the new commits into an already-generated release
branch, because their changesets and notes would not have been consumed.
Run the repository-owned version command; never hand-edit the output:
pnpm run version
Then author the in-app release summary and highlights for the new top
CHANGELOG.md section. The version script seeds a one-sentence summary and a
### Highlights list from the bump bullets — replace those with polished,
user-facing copy before opening the PR:
- Summary: one plain-language sentence about what this release delivers.
- Highlights: 1–4 bullets of the most important changes operators and users
should notice. Avoid internal jargon, commit SHAs, and package names. Always
include this section; if there is nothing more specific to call out, use the
single bullet
Bug fixes and small improvements. - Omit any Major/Minor/Patch section with no bullets; never use placeholders or
filler such as
Nothing of note.
These fields power the in-app What's new / Update available dialogs (they ship
through GitHub Releases via extract-changelog-section). Leave
### Major/Minor/Patch changes bullets as generated unless a bullet needs a
clarity fix; do not pad them.
Then verify:
package.jsoncontains the expected next version using ordinary semver (patchincrements patch,minorincrements minor and zeros patch,majorincrements major and zeros minor and patch)- the new top
CHANGELOG.mdsection contains every intended note under the correct bump-level heading, plus an edited summary paragraph and a### Highlightslist suitable for in-app display - every pending changeset was consumed and
.changeset/README.mdremains - workspace package versions did not change
- every meaningful user-facing feature has an accurate public docs destination,
with any required
apps/docsupdates, navigation changes, source-backed provider icons, and centralized logo references included - the diff contains only release artifacts and required public docs updates:
root
package.json,CHANGELOG.md, relevant files underapps/docs, and deletions of changesets that already existed on the base branch. Locally created missing changesets normally leave no final diff because they are created and consumed in the same working tree.
Do not manually rerun the release-script tests, docs validation, repository
lint/format, type checks, Knip, or the pre-push script while preparing the PR.
The PR workflows run the full versions of those checks for changes targeting
develop; allow the ordinary push hook to run exactly once as the local static
gate instead of invoking it separately. Do not merge the release PR until the
CI workflow and, when apps/docs changed, the Docs workflow succeed.
Treat that as a release-process gate even when repository rules do not require
status checks.
10. Open the release PR
Commit the generated release artifacts on a feature branch and open a PR against
develop titled Release Roomote X.Y.Z. The PR body should include:
- the previous and next product versions
- the final changelog bullets grouped by bump level
- the feature-to-docs coverage checklist, including required updates or an explicit no-updates-needed result
- validation performed
- a note that squash-merging the PR cuts the release and automatically opens
the frozen Promote vX.Y.Z to production PR against
main
11. Monitor the promotion candidate, not just release preparation
Release preparation and promotion are separate validation boundaries. Passing
checks or reviews on the release-preparation PR do not clear the Promote PR.
When the preparation PR merges, find the open Promote PR by its exact
release/vX.Y.Z head and main base, and record its current head SHA. If the
preparation PR has not merged, report that boundary; do not merge it just to
continue monitoring.
Inspect both CI and reviews on the current promotion head:
gh pr view <promote-pr> --json state,headRefOid,baseRefName,mergeable,reviewDecision,statusCheckRollup
gh pr checks <promote-pr>
gh api --paginate repos/<owner>/<repo>/pulls/<promote-pr>/reviews
gh api --paginate repos/<owner>/<repo>/pulls/<promote-pr>/comments
Also retrieve all review threads, including their isResolved state, through
the source-control review tool or paginated GraphQL. Inline comments alone do
not establish whether a finding is resolved. Include top-level review summaries
and the Roomote code-review check; green CI is not a substitute for review.
- Require successful CI, applicable Docs, and required checks on the promotion head, not on the preparation head or a previous candidate. Missing, queued, running, cancelled, or failed checks are not a pass. Account explicitly for conditional skipped jobs rather than treating every skip as success.
- Report outstanding
CHANGES_REQUESTED, unresolved review findings, failed or pending automated reviews, and missing required human approvals separately from CI. An empty review list or a nullreviewDecisionis not approval. - Re-read the head and PR state during each observation. A refresh or reconciliation invalidates the old checks and review evidence; restart against the new SHA. If the head changes while collecting results, discard that mixed snapshot. If the PR merges externally, stop candidate mutation and report the observed publication state without initiating release actions.
- While authorized monitoring is active, poll at a bounded interval (for example 60 seconds for at most 30 minutes), report actionable failures promptly, and stop with an explicit pending/input-needed result at the bound. Do not wait indefinitely for a human review. Reuse an existing equivalent monitor instead of scheduling duplicates; use supported Session follow-up only when available and authorized, otherwise report the remaining checks without claiming a monitor is running.
- Review fixes stay within the audited candidate scope. Diagnose unrelated runtime findings separately; do not silently pull in newer develop commits. Candidate conflicts use the reconciliation procedure below, never a manual push to the release branch.
Report the Promote PR URL, observed head SHA, CI status, review status, and remaining blockers independently. Monitoring, a green status, or approval never authorizes merging, enabling auto-merge, tagging, publishing, or deployment by itself; use the user's requested scope, without reconfirming authorization already supplied.
Reconcile a frozen candidate with production
Use Reconcile Release Candidate (release-reconcile.yml) to complete a
requested repair of an open, unshipped candidate whose production base has
diverged. The workflow must already be available on trusted develop; preparing
its implementation PR is not evidence that reconciliation ran. It never imports
the current develop tree. No tag may exist for the candidate, and the candidate
must not already be contained in main.
- Fetch and pin the full candidate and main SHAs. Revalidate the published
baseline and open Promote PR. On an ordinary
reconcile/vX.Y.Zbranch rooted at that exact candidate, merge only the pinned main with--no-commit --no-ff. Do not merge develop or use blanket ours/theirs resolution. - Resolve each conflict by intent, retaining production hotfixes and candidate features/tests. Keep automatically merged files unchanged. Preserve the root candidate version, its complete release section, and all published main changelog history; do not run versioning or author new release notes. This is reconciliation of existing generated artifacts, not a new release cut.
- Validate the actual merged tree with focused tests and ordinary commit/push
gates. The review commit must have exactly two parents in order: pinned
candidate, pinned main. Push only the ordinary
reconcile/vX.Y.Zbranch and open a review-only PR targetingrelease/vX.Y.Z. Clearly mark it Do not merge: CI applies this reviewed tree. Record the pins, each conflict decision, hotfix preservation evidence, and validation. The current reconciliation script enforces independent-human approval at the exact resolution SHA and resolved review threads. Reuse valid existing approval; do not add manual reviewer-permission lookups or another approval round trip. Do not merge this review-only PR; CI applies its tree. - Dispatch Reconcile Release Candidate on
developwithversion(withoutv),expected_candidate_sha,expected_main_sha, andresolution_sha. It requiresRELEASE_BOT_TOKENso the candidate push can trigger fresh PR CI. CI verifies the open PR identities, approval and thread state, exact merge parents, unchanged automatic-merge paths, release artifacts, and repeated remote pins before creating its own merge commit and fast-forwarding the release branch. It never executes code from the candidate or resolution. - Inspect the run and actual remote head, then monitor the Promote PR's new CI and reviews using step 11. CI appends reconciliation provenance to the existing Promote PR body. If metadata fails after a successful push, report that partial result; do not retry with stale pins or claim no change occurred. Revalidate the actual remote head, parents, tree and open PR identity, then repair only the missing metadata within the authorized repair. A failed run after a successful push is not a reason to push or reconcile again. After verifying successful application, close the completed review-only PR without merging it as routine cleanup of the requested repair.
After reconciliation the candidate generally diverges from develop. Ordinary Release refresh deliberately refuses that state; push-triggered Release runs also refuse to replace its metadata with the original version-bump SHA. Do not force the candidate back onto develop. Reconcile a later pinned production base through another reviewed run, or obtain a separately audited replacement-release decision if newer develop work is needed. If any pin, approval, publication, or scope guard fails, stop and re-audit rather than bypassing it.
Emergency direct-to-main hotfix path
Use this path only for an urgent production patch that cannot wait for the
normal develop release flow above. The normal workflow remains the default.
This exception produces two PRs:
- a complete patch and generated release artifacts targeting
main - a companion PR targeting
developthat changes only rootpackage.jsonandCHANGELOG.md
The production PR must merge first. Do not merge the companion until the tag, images, and GitHub Release have all shipped successfully.
A. Establish the release state
Fetch both branches and tags, then branch from the exact latest main tip:
git fetch --tags origin \
refs/heads/main:refs/remotes/origin/main \
refs/heads/develop:refs/remotes/origin/develop
git switch --create hotfix/<concise-name> origin/main
git tag --sort=-version:refname | head -5
node -p "require('./package.json').version"
head -20 CHANGELOG.md
Before continuing, verify:
HEADis exactlyorigin/main.- The newest shipped
vX.Y.Ztag, root version, and top changelog section agree. mainhas no pending non-README changeset thatpnpm run versionwould accidentally consume with the hotfix.- No newer unshipped Promote PR or release candidate conflicts with the patch version. Resolve release ordering rather than guessing.
- The fix is a patch and every required prerequisite is already on
mainor is explicitly included because it is safe to ship with the fix. - The patch preserves the schema N-1 rollback guarantee and does not require an incompatible migration, destructive schema change, or coordinated rollout that makes direct release unsafe.
B. Apply the complete fix
Identify the exact merged fix on develop, including tests, docs, and required
supporting changes. Read its source PR and compare its merge commit to main;
do not copy only the most obvious runtime file. Cherry-pick the smallest complete
commit set, usually the squash commit:
git cherry-pick <develop-fix-sha>
git diff --stat origin/main...HEAD
git diff --name-status origin/main...HEAD
git diff <develop-fix-sha>^..<develop-fix-sha> -- <expected-fix-paths>
git diff origin/main...HEAD -- <expected-fix-paths>
Account for every intentional difference caused by prerequisites or conflict
resolution. Exclude unrelated features, pending develop changesets, and
release artifacts from another version.
When the fix depends on an external service or production-like integration, repeat the smallest safe live check that proves the original symptom is fixed. Record the environment and observable result without exposing secrets or private data. If a live check is irrelevant or unavailable, say why and rely on focused deterministic tests.
C. Generate and validate the patch release
Add one hotfix-specific patch changeset using the same format as step 8. Commit the complete fix and changeset separately when useful for auditability, then consume the changeset through the repository-owned command:
pnpm run version
Never run changeset version, and never hand-edit the generated version or
changelog heading. Polish the generated top changelog summary and highlights as
described in step 9, then commit root package.json and CHANGELOG.md as the
version commit.
Verify that:
- the root version is exactly one patch above the shipped
mainversion - the top
CHANGELOG.mdsection contains the complete hotfix note - the hotfix changeset was consumed and no non-README
.changeset/*.mdremains - no workspace package version changed
origin/main...HEADcontains only the complete fix plus rootpackage.jsonandCHANGELOG.md
Preserve the changeset commit in branch history even though the version commit
deletes its file. Do not manually rerun the release-script tests, focused product
tests and type checks covered by the repository suite, formatting, Knip, or the
pre-push script. The hotfix PR workflows run their full versions for changes
targeting main; allow the ordinary push hook to run exactly once as the local
static gate instead of invoking it separately. Do not merge the production
hotfix PR until the CI workflow and, when apps/docs changed, the Docs
workflow succeed. Treat that as a release-process gate even when repository
rules do not require status checks.
Repeat any relevant live check after final conflict resolution or release edits; CI cannot replace external-service or production-like evidence. Failed or unavailable release-specific or live checks are release blockers unless a maintainer explicitly accepts and records the risk.
D. Open the production hotfix PR
Open the hotfix PR against main. Include the source fix PR/commit and
prerequisites, previous and next versions, exact patch scope and intentional
differences, all validation and live-check results, rollback instructions, and a
link to the companion PR once available. Require a merge commit; never squash
or rebase this PR.
Rollback guidance must distinguish two states:
- Before the tag is consumed externally, revert the hotfix merge commit if that is still operationally safe.
- After publication, redeploy the previous immutable
vX.Y.Z; never move, delete, or reuse a published tag. Describe data or schema cleanup separately and preserve the N-1 rollback contract.
E. Prepare the companion develop sync
After the production branch is final, create a branch from latest
origin/develop and bring over only the generated product version and changelog:
git switch --create chore/sync-<version>-on-develop origin/develop
git restore --source <hotfix-tip> -- package.json CHANGELOG.md
git diff --name-only origin/develop
git diff --exit-code <hotfix-tip> -- package.json CHANGELOG.md
git diff --exit-code origin/develop -- .changeset
Do not cherry-pick the runtime fix; it should already be on develop. Do not run
pnpm run version on this branch because that would consume unrelated pending
develop changesets. The first diff must list exactly package.json and
CHANGELOG.md; the other two must be empty. If develop independently changed
either release artifact, reconcile deliberately without touching pending
changesets and stop if the companion cannot remain a truthful two-file sync.
Open the companion PR against develop. It must be squash-merged only after all
production gates below succeed. Merging it early can make
.github/workflows/release.yml freeze the moving develop tip as
release/vX.Y.Z and open an incorrect Promote PR.
F. Enforce merge and release order
Do not collapse or reorder these gates:
- Merge the production hotfix PR into
mainwith a merge commit. - Wait for Tag Product Release to succeed for that
mainmerge. - Verify the remote annotated tag exists and resolves to the released tree with
git ls-remote --tags origin refs/tags/vX.Y.Zand, after fetching,git rev-parse 'vX.Y.Z^{}'. - Wait for the tag-triggered Publish GHCR Images workflow to succeed, including image publication and GitHub Release creation. Verify the GitHub Release and immutable image tags; tag creation alone is not completion.
- Only then squash-merge the companion PR into
develop.
After the tag exists, the develop Release workflow sees an already shipped
version and exits without creating a candidate. If a production gate fails,
leave the companion open while the release is repaired or rolled back.
PRs #1840 and #1841 demonstrate this two-branch workflow. Their Notion-specific live checks are an optional example, not a requirement for unrelated hotfixes.
End the hotfix path by reporting both PR URLs, released version, source fix and prerequisite commits, exact file scope, live-check result or reason omitted, validation results, current merge-order gate, and rollback risks.
Guardrails
- Never edit
CHANGELOG.mdor the root version by hand; generate both withpnpm run version, review the result, and commit that output in the release PR. - Never run or commit
changeset versionoutput. - Never push to
release/v*manually. CI owns release branches; before a candidate reachesmain, maintainers may amend its notes withpnpm run version -- --amendand explicitly dispatch the Release workflow to fast-forward the open candidate fromdevelop. Production-base conflicts use the explicitly authorized, reviewed CI reconciliation procedure above. - Never leave an older Promote PR open when an explicit superseding release is prepared. Close it so versions cannot be promoted out of order.
- Never merge an out-of-date release PR. Regenerate it from the latest
developso every commit included in the cut was part of the release audit. - Never bump versions in workspace
package.jsonfiles. - Do not create the Promote PR manually.
- Ask for the bump level when unspecified; recommend one but let the user decide.
- Do not duplicate existing pending notes or pad the changelog with internal noise.
- Do not release a meaningful user-facing feature with missing or stale public
docs; update
apps/docsas part of the release PR when the original feature work did not keep it current. - Do not multi-package-attribute changesets; always emit one
'@roomote/web': <level>line. - Use the actual version produced by
scripts/release/lib.mjs; do not guess semver in the PR title or body.