Release planning
Create a changeset with the CLI:
mc change --package sdk-core --bump minor --reason "public API addition"
mc change --package sdk-core --bump patch --type security --reason "rotate signing keys" --details "Roll the signing key before the release window closes."
mc change --package sdk-core --bump none --type docs --reason "clarify migration guidance" --output .changeset/sdk-core-docs.md
mc 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:
mc 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
Use scalar shorthand for plain bumps (sdk-core: minor) or for configured change types (sdk-core: security). To pin an exact version or combine bump, version, and type, use the object syntax:
---
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.
When a dependent package changes only because another package moved first, author that context explicitly with caused_by:
mc 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:
mc step:validate
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:
mc release --dry-run --format json
For human-readable local output, mc release --dry-run now defaults to terminal-friendly markdown. Use --format text when you want the older plain-text style, or keep --format json for automation.
Preferred repository command flow:
mc release --dry-run --format json
When you want a reviewable patch-style preview of the filesystem changes without mutating the workspace, add --diff:
mc release --dry-run --diff
mc 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:
mc step:validate
mc discover --format json
mc step:diagnose-changesets --format json
mc release --dry-run --diff
Use each command for a different question:
mc step:validate— is the config and changeset set valid?mc discover --format json— which package ids, groups, and dependency edges exist?mc step:diagnose-changesets --format json— who introduced these changesets and what review context is attached?mc 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 |
|---|---|
mc release --dry-run | Human review in the terminal |
mc release --dry-run --diff | Human review plus exact file patches |
mc release --dry-run --format json | Automation, scripts, MCP clients |
mc 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 suppresses stdout/stderr and uses dry-run behavior for release-oriented commands so the workspace stays unchanged.
mc release
mc release is a config-driven workflow command only when your repository defines a [cli.release] table. mc init writes a minimal starter config and does not seed default workflow aliases, so use the immutable mc 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 only when your config defines them. If a repository has not opted into a named workflow, use the immutable step command instead, for example mc step:discover, mc step:create-change-file, mc step:prepare-release, mc step:affected-packages, mc step:diagnose-changesets, mc step:retarget-release, mc step:publish-readiness, or mc step:plan-publish-rate-limits.
mc step:validate is the immutable built-in step command for normal preflight checks. Do not define [cli.validate] or any [cli.step:*] command in monochange.toml; those names are reserved for built-in commands.
Commands like commit-release combine PrepareRelease with later stateful steps such as CommitRelease. Provider request workflows such as 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 throughextra_changelog_sections - applies workspace-wide release-note templates from
[release_notes].change_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 -- mc "${args[@]}" | tee policy.raw
awk 'BEGIN { capture = 0 } /^\{/ { capture = 1 } capture { print }' policy.raw > policy.json
jq -e '.status != "failed"' policy.json >/dev/null
Current planning rules:
mc 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 itmc 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 bumpmc 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 default to the configured
parent_bump, including packages outside a changed version group when they depend on a synchronized member - 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:
mc step:diagnose-changesets --format json— what is currently pending in.changeset/*.md, and who introduced it?mc step:release-record --from <ref>— what did a past release commit declare durably in git history?mc 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 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 and reuses dry-run behavior for commands that support it.
Concurrency
mc release is designed for sequential execution. Do not run multiple mc 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.