CLI step reference
monochange CLI commands are built in two layers:
- immutable built-in step commands: every built-in step except
Commandis exposed directly asmc step:<kebab-step-name>, for examplemc step:discover,mc step:prepare-release, andmc step:affected-packages. These commands are generated by the binary, derive their flags from the step schema, and do not require a[cli.*]entry inmonochange.toml. - config-driven workflow commands: every
[cli.<command>]table inmonochange.tomlbecomesmc <command>.mc initdoes not seed default workflow aliases; add these tables when you want a named workflow that chains steps, adds custom inputs, or runsCommandsteps.
A step is the smallest execution unit in a monochange workflow. Some steps are standalone (Validate, Discover, AffectedPackages, DiagnoseChangesets, RetargetRelease, VerifyReleaseBranch). Others are stateful and build on the result of an earlier PrepareRelease step (CommitRelease, PublishRelease, OpenReleaseRequest, and CommentReleasedIssues). PrepareRelease also refreshes the cached .monochange/release-manifest.json artifact exposed to later steps as manifest.path.
When you design a command, think in terms of:
- what state the command needs
- which step produces that state
- which later step consumes it
- what side effects are acceptable in normal mode vs
--dry-run
The reference pages in this section document each built-in step with:
- what the step does
- why you would choose it over a shell-only
Command - which inputs it accepts
- what prerequisite state it needs
- what it contributes to later steps
- examples of how it composes into full workflows
Explicit step input inheritance
Config-defined workflow commands have two input layers:
[[cli.<command>.inputs]]declares the flags and arguments accepted bymc <command>.inputson each step decides which of those parsed command inputs are visible while that step runs.
Command inputs are not inherited automatically. A step receives a command input only when the step explicitly lists it. This makes wrappers predictable when a command-level flag and a step-specific input share the same name.
Use the array shorthand when a step should inherit command inputs unchanged:
[cli.discover]
inputs = [
{ name = "format", type = "choice", choices = ["text", "json"], default = "text" },
]
steps = [
{ type = "Discover", inputs = ["format"] },
]
Use the map form when a step needs fixed values, renamed values, templates, or a mixture of inherited and overridden values:
[cli.release-pr]
inputs = [
{ name = "format", type = "choice", choices = ["text", "json", "markdown"], default = "markdown" },
{ name = "open_as_draft", type = "boolean", default = false },
]
steps = [
{ type = "PrepareRelease", inputs = ["format"] },
{ type = "OpenReleaseRequest", inputs = { format = "markdown", draft = "{{ inputs.open_as_draft }}" } },
]
Step-local when expressions and command templates evaluate against the same explicit step input context. If a when condition references inputs.publish, the step must include publish in its inputs array or map. Use inputs = ["publish"] for unchanged inheritance, or inputs = { publish = "{{ inputs.publish }}" } when you need the map form for other overrides.
Built-in mc step:* commands are different: they are generated directly from the step schema, so their CLI flags map to that single step without a [cli.*] wrapper.
Choosing the right step
| Step | Use it when you want to… | Requires previous step? | Typical follow-up |
|---|---|---|---|
Validate | fail fast on invalid config, groups, or changesets | no | CI gate or local preflight |
Discover | inspect normalized package discovery across ecosystems | no | local inspection, debug commands |
CreateChangeFile | author a .changeset/*.md file from CLI inputs | no | run independently, or before planning |
PrepareRelease | build the release result, update files, and refresh the cached manifest | no | CommitRelease, PublishRelease, OpenReleaseRequest, CommentReleasedIssues, Command |
DisplayVersions | display planned package and group versions without mutating release files | no | PrepareRelease |
CommitRelease | create a local release commit with an embedded ReleaseRecord | PrepareRelease | OpenReleaseRequest, manual review, custom Command |
VerifyReleaseBranch | verify a ref is reachable from configured release branches | [source.releases] | early release CI gates; enforced internally by tag and publish paths |
PublishRelease | create or update hosted provider releases | PrepareRelease + [source] | CommentReleasedIssues, custom notification commands |
OpenReleaseRequest | create or update a hosted release PR/MR | PrepareRelease + [source] | provider review, follow-up Command steps |
PlanPublishRateLimits | plan package-registry publish work against known rate limits | no | PublishPackages, PlaceholderPublish |
PlaceholderPublish | publish 0.0.0 placeholder versions for missing registry packages | no | normally before PublishPackages |
PublishPackages | publish package versions to registries using built-in ecosystem workflows | prepared or HEAD release state | custom Command steps using publish.* |
CommentReleasedIssues | post release follow-up comments to closed issues | PrepareRelease + GitHub source | normally after PublishRelease |
AffectedPackages | evaluate changeset coverage for changed files | no | CI enforcement, custom failure messaging |
DiagnoseChangesets | inspect changeset context, commit provenance, and linked review metadata | no | local debugging, CI inspection |
RetargetRelease | repair a recent release by moving its tag set | no | custom Command steps using retarget.* |
Command | run arbitrary shell/program commands with monochange context | depends on your workflow | any external tool |
A note on composition
monochange executes steps in order.
That means composition is explicit:
- a step can only consume state created by an earlier step in the same command
- a later step never runs “in parallel” with an earlier one
--dry-runflows through the whole command and changes the behavior of steps that support previews--quietsuppresses stdout/stderr and reuses dry-run behavior for commands that support it- a plain
Commandstep can bridge monochange and external tools, but built-in steps are preferable when you want stable semantics, structured JSON, or provider-aware behavior
In practice, most workflows fit one of four patterns:
- validation / inspection
ValidateDiscoverDisplayVersionsAffectedPackagesDiagnoseChangesets
- change authoring
CreateChangeFile
- release preparation and publication
PrepareRelease- then one or more of
CommitRelease,PublishRelease,OpenReleaseRequest,CommentReleasedIssues,Command
- post-release repair
RetargetRelease- optionally followed by
Command
Shared concepts
Step-local name
Every step can declare a name = "..." label.
Use that when you want human-friendly progress output such as plan release, publish tags, or announce release instead of the raw step kind.
Progress rendering
monochange can stream step progress on stderr while keeping command output on stdout.
Use --progress-format auto|unicode|ascii|json or MONOCHANGE_PROGRESS_FORMAT to choose the renderer:
autoenables the human renderer only when stderr is a terminalunicodeforces the human renderer with Unicode symbolsasciiforces the human renderer with ASCII-safe symbolsjsonemits newline-delimited progress events for automation and benchmarks
PrepareRelease and DisplayVersions steps also report per-phase timings when they compute release state. Those timings power the benchmark phase-budget checks for mc release --dry-run and mc release.
See Progress output for the full renderer behavior and JSON event shape.
Step-local when
Every step can declare a when = "..." expression.
It uses minijinja-style expression evaluation with template context and supports logical combinations like and, or, and not (for example: "{{ inputs.publish && !inputs.dry_run }}").
If the expression resolves to false, monochange skips that step and continues with the next step. Falsy values include false, 0, and the empty string.
Step-local always_run
Every step can declare always_run = true.
When a previous step in the same command fails, monochange normally aborts and returns the error. Steps marked with always_run = true are the exception: they still execute even after an earlier step has failed.
Use this for cleanup, notification, or dry-run preview steps that should run regardless of whether earlier work succeeded.
Step-local inputs
Every step can define an inputs = { ... } override inside the step table.
Use that when:
- a command-level input should be rebound to a built-in step input
- you want to hardcode a value for one step but not the entire command
- you want to pass list or boolean values through direct template references such as
"{{ inputs.changed_paths }}"
Step-local show_progress
Interactive steps do not show the spinner by default when monochange is waiting on the user. For step kinds that support it, you can also set show_progress = false to suppress progress output explicitly.
Structured template namespaces
When you compose Command steps after built-in steps, monochange exposes structured context values such as:
release.*afterPrepareReleasemanifest.pathafterPrepareReleaseaffected.*afterAffectedPackagesretarget.*afterRetargetReleaserelease_commit.*afterCommitReleasesteps.<id>.stdoutandsteps.<id>.stderrafter aCommandstep withid = "..."
Those namespaces are the main reason to prefer built-in steps over reimplementing the same workflow in shell.