Release planning
Create a changeset with the CLI:
monochange run change --package sdk-core --bump minor --reason "public API addition"
monochange run change --package sdk-core --bump patch --type security --reason "rotate signing keys" --details "Roll the signing key before the release window closes."
monochange run change --package sdk-core --bump none --type docs --reason "clarify migration guidance" --output .changeset/sdk-core-docs.md
monochange run change --package sdk-core --bump major --version 2.0.0 --reason "break the public API" --output .changeset/sdk-core-major.md
Or use interactive mode to select packages, bumps, and options from a guided wizard:
monochange run change -i
Interactive mode automatically prevents conflicting selections (a group and one of its members) and lets you pick per-package bumps and optional explicit versions.
Or write one manually with configured package or group ids:
---
sdk-core:
bump: patch
type: security
---
# rotate signing keys
Roll the signing key before the release window closes.
Group-targeted changesets are also valid:
---
sdk: minor
---
# coordinated SDK release
Package ids first, group ids when the release boundary is shared
Use a package id when one package changed directly:
---
sdk-core: minor
---
# add changelog rendering API
Use a group id when the outward release note should be owned by the whole group:
---
sdk: minor
---
# coordinated SDK release
A quick rule of thumb:
- package id: the leaf package changed and monochange can propagate the rest
- group id: the note should read as one coordinated release for all members
Prefer the inline form: write a configured change type as the target value when its default bump is what you want (sdk-core: docs for a documentation-only change, sdk-core: fix for a patch fix). Use scalar bumps (sdk-core: minor) for plain bumps without a custom type. Use the object syntax only when you need to pin an exact version, combine bump, version, and type, or override a type’s default bump (for example docs with a patch bump):
---
sdk-core:
bump: major
version: "2.0.0"
---
# promote to stable
When version is provided without bump, the bump is inferred from the current version. If the package belongs to a version group, the explicit version propagates to the whole group. Overriding a type’s default bump is possible but should be avoided unless the user explicitly asks for that version behavior.
When a dependent package changes only because another package moved first, author that context explicitly with caused_by:
monochange run change --package sdk-config --bump none --caused-by sdk-core --reason "dependency-only follow-up"
---
sdk-config:
bump: patch
caused_by: ["sdk-core"]
---
# update dependency on sdk-core
And when the package is affected but does not deserve a consumer-facing version bump, use bump: none:
---
sdk-config:
bump: none
caused_by: ["sdk-core"]
type: docs
---
# document the coordinated release
If multiple changesets specify conflicting explicit versions for the same package or group, monochange uses the highest semver version and emits a warning by default. Set defaults.strict_version_conflicts = true to fail instead.
monochange keeps its own changeset standard rather than reusing a narrower external parser. Top-level frontmatter keys are package ids or group ids only. Each target can use scalar shorthand or the object syntax with bump, version, and type, while the markdown body is split into a summary plus optional detailed follow-up paragraphs. Authored heading depth is normalized when release notes are rendered, so use natural markdown headings in the changeset body instead of hard-coding output depth.
Validate before planning:
monochange step validate
Semantic SemVer guardrails
Release planning also runs semantic analysis for the detected git change frame when it can. The analyzer output is advisory release evidence, not a replacement for changesets:
- removed or modified public API/export evidence can raise the minimum bump to
major; - added public API/export evidence can raise the minimum bump to
minor; - dependency and metadata evidence is treated as
patchadvisory context; - analyzer failures become release-plan warnings instead of blocking the command.
Dry-run JSON and release manifest payloads include this data under compatibilityEvidence. Human reviewers should compare that evidence with the authored changesets before preparing a release.
Before writing a changeset, use monochange change classify --format json to compare the pull request with both the default branch and the latest release. See Change classification for finding confidence, coverage limits, and changeset validation.
Release manifests vs release records
Release planning and release repair use two different artifacts on purpose.
- the cached release manifest at
.monochange/release-manifest.jsoncaptures what monochange is preparing right now during command execution. ReleaseRecordcaptures what a release commit historically declared inside the monochange-managed release commit body.
Use the manifest when you want execution-time automation such as CI artifacts, MCP/server responses, previews, or downstream machine-readable release data.
Use the release record when you want to rediscover or repair a release later from a tag or descendant commit.
That is why ReleaseRecord does not replace the cached release manifest: one is an execution-time automation artifact, the other is a durable git-history artifact.
When you need to inspect or repair a recent release, see Repairable releases.
Generate a plan directly when you want to inspect the raw planner output:
monochange run release --dry-run --format json
For human-readable local output, monochange run release --dry-run defaults to concise text. Use --format markdown for a raw Markdown artifact or --format json for automation.
Preferred repository command flow:
monochange run release --dry-run --format json
When you want a reviewable patch-style preview of the filesystem changes without mutating the workspace, add --diff:
monochange run release --dry-run --diff
monochange run release --dry-run --format json --diff
Markdown and text output render unified diffs directly in the terminal. JSON output wraps the normal manifest payload under manifest and adds fileDiffs entries for each changed file.
A good planning loop looks like this:
monochange step validate
monochange step discover --format json
monochange step diagnose-changesets --format json
monochange run release --dry-run --diff
Use each command for a different question:
monochange step validate: is the config and changeset set valid?monochange step discover --format json: which package ids, groups, and dependency edges exist?monochange step diagnose-changesets --format json: who introduced these changesets and what review context is attached?monochange run release --dry-run --diff: what exact files would change if I prepared the release now?
Compare preview modes
Use the preview mode that matches the decision you are trying to make:
| Command | Best for |
|---|---|
monochange run release --dry-run | Human review in the terminal |
monochange run release --dry-run --diff | Human review plus exact file patches |
monochange run release --dry-run --format json | Automation, scripts, MCP clients |
monochange run release --dry-run --format json --diff | Automation that also needs file patch details |
When you want command semantics without any command-line noise, add --quiet. Quiet mode does not change execution; add --dry-run separately when the workspace must stay unchanged.
monochange run release
monochange run release is a config-driven workflow command only when your repository defines a [cli.release] table. monochange init writes a minimal starter config and does not seed default workflow aliases, so use the immutable monochange step prepare-release command unless you add your own named workflow.
The binary no longer ships a hidden default workflow set for commands such as discover, change, release, affected, diagnostics, repair-release, publish, or publish-plan. Those names exist under monochange run <name> only when your config defines them. If a repository has not opted into a named workflow, use the immutable step command instead, for example monochange step discover, monochange step create-change-file, monochange step prepare-release, monochange step affected-packages, monochange step diagnose-changesets, monochange step retarget-release, monochange step publish-readiness, or monochange step plan-publish-rate-limits.
monochange step validate is the immutable built-in step command for normal preflight checks. Do not define [cli.validate] or [cli.step] in monochange.toml; those names are reserved for built-in commands.
Configured workflows like monochange run commit-release combine PrepareRelease with later stateful steps such as CommitRelease. Provider request workflows such as monochange run release-pr can add OpenReleaseRequest. Keep both as explicit [cli.*] workflow commands when you want a durable, named release process.
Current PrepareRelease behavior:
- reads
.changeset/*.md - computes one synchronized release plan from discovered change files
- updates native manifests plus configured changelogs and versioned files
- renders changelog files through structured release notes using the configured
monochangeorkeep_a_changelogformat - groups release notes into default
Breaking changes,Features,Fixes, andNotessections, with package/group overrides available through changelogtypesandsections - applies workspace-wide release-note templates from
[changelog].templates - refreshes the cached
.monochange/release-manifest.jsonartifact duringPrepareReleasefor downstream automation - can preview or publish provider releases via
PublishRelease - can preview or open/update release requests via
OpenReleaseRequest - can comment on released issues via
CommentReleasedIssues - can evaluate pull-request changeset policy via
AffectedPackagesusing changed paths and labels supplied by CI - applies group-owned release identity for outward
tag,release, andversion_format - deletes consumed change files only after a successful non-dry-run execution
- leaves the workspace untouched during
--dry-runexcept for explicitly requested outputs such as a rendered release manifest or release preview
A GitHub Actions check can pass changed paths and labels directly into a policy workflow, for example:
name: changeset-policy
on:
pull_request:
types:
- opened
- synchronize
- reopened
- labeled
- unlabeled
concurrency:
group: changeset-policy-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
check:
timeout-minutes: 60
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
- name: checkout repository
uses: actions/checkout@v6
- name: setup
uses: ./.github/actions/devenv
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: collect changed files
id: changed
uses: tj-actions/changed-files@v46
- name: run changeset policy
env:
PR_LABELS_JSON: ${{ toJson(github.event.pull_request.labels.*.name) }}
CHANGED_FILES: ${{ steps.changed.outputs.all_changed_files }}
shell: bash
run: |
set -euo pipefail
mapfile -t labels < <(jq -r '.[]' <<<"$PR_LABELS_JSON")
args=(step affected-packages --format json --verify)
for path in $CHANGED_FILES; do
args+=(--changed-paths "$path")
done
for label in "${labels[@]}"; do
args+=(--label "$label")
done
devenv shell -- monochange "${args[@]}" | tee policy.raw
awk 'BEGIN { capture = 0 } /^\{/ { capture = 1 } capture { print }' policy.raw > policy.json
jq -e '.status != "failed"' policy.json >/dev/null
Planning rules:
monochange run changedefaults--bumptopatch; use--bump nonewhen you want a type-only or version-only entry, and pass--versionto pin an explicit release version- markdown change files use package/group ids as the only top-level frontmatter keys, with scalar shorthand for
none/patch/minor/majoror configured change types, plus object syntax forbump,version,type, andcaused_by - when
versionis given withoutbump, the bump is inferred by comparing the current and target versions - explicit versions from grouped members propagate to the group version; conflicts take the highest semver or fail when
defaults.strict_version_conflicts = true - prefer package ids over group ids in authored changesets when possible; direct package changes still propagate to dependents and synchronize configured groups
- optional change
typevalues can route entries into custom changelog sections, and configured sectiondefault_bumpvalues let scalar type shorthand imply the desired semver behavior caused_byreferences package or group ids and suppresses only the matching dependency-propagation records; use object syntax whenever you need itmonochange run changeaccepts repeated--caused-by <id>flags, and--bump noneis the right fit when you want to acknowledge an affected package without forcing a user-facing version bumpmonochange run changecan write to a deterministic path with--output ...- change templates support detailed multi-line release-note entries through
{{ details }}, compact metadata blocks through{{ context }}, and fine-grained linked metadata like{{ change_owner_link }},{{ review_request_link }}, and{{ closed_issue_links }} - dependents resolve their propagation policy most-specific-first:
[[package]].bump_propagationoverridesgroupdeclarations, which override[defaults].bump_propagation, which falls back to the[defaults].parent_bumpfloor;inheritmatches the source’s own severity (optionally clamped bybump_propagation_max) and fixed severities act as floors - computed compatibility evidence can still escalate both the changed crate and its dependents when provider analysis produces it
- configured groups synchronize before final output is rendered
- release targets carry effective
tag,release, andversion_formatmetadata - release-manifest JSON captures release targets, changelog payloads, authored changesets, linked changeset context metadata, changed files, and the synchronized release plan for downstream automation
PublishReleasereuses the same structured release data to build provider release requests for grouped and package-owned releasesOpenReleaseRequestreuses the same structured release data to render release-request summaries, branch names, and idempotent provider updatesCommentReleasedIssuescan use linked changeset context metadata to add follow-up comments to closed issues after a release is publishedAffectedPackagesevaluates changed paths, skip labels, and changed.changeset/*.mdfiles into reusable pass/skip/fail diagnostics and optional failure comments- CLI text and JSON output render workspace paths relative to the repository root for stable snapshots and automation
Diagnostics vs. release records
These commands answer different questions:
monochange step diagnose-changesets --format json: what is currently pending in.changeset/*.md, and who introduced it?monochange step release-record --from <ref>: what did a past release commit declare durably in git history?monochange step tag-release --from HEAD: ifHEADis the merged release commit, which release tags should be created now?
Use diagnostics before you release. Use release records after a release exists and you need to inspect it. Use monochange step tag-release in post-merge CI when the release commit has landed on the default branch and you want to create the declared tag set from that durable history record.
Across release-oriented commands, global --quiet suppresses stdout/stderr without changing whether the command mutates the workspace.
Concurrency
monochange run release is designed for sequential execution. Do not run multiple monochange run release commands concurrently on the same workspace. There is no file locking, so concurrent runs could produce duplicate changelog entries, inconsistent version files, or corrupted release records. If you need parallel release preparation across workspaces, use separate working copies.