Upgrading to 0.11
Version 0.11 makes the CLI output human-first, tightens publish readiness before any registry is touched, and updates several library APIs. Most repositories only need the CLI and changeset-authoring sections; Rust consumers should also review the library API section.
Each section states who is affected, what changed, and the exact update step. The complete change list for this release lives in changelog.md.
CLI results are human-first by default
Affects: scripts, CI jobs, and agents that parse default output.
Commands now print concise human-readable text by default. The previous default was Markdown, which a terminal rendered and a pipe received as Markdown text. One format now has one meaning:
- the default and
--format textreturn concise human-readable text; --format markdownalways returns raw Markdown and is never terminal-rendered;--format jsonand--format json-minreturn structured, ANSI-free data.
Update steps:
-
Replace parsers of the old default output with
--format json(complete report) or--format json-min(one line). -
monochange step configprints a short workspace summary. Use--format jsonfor the complete resolved configuration. -
--jqnow requires JSON output, so it can never run a command and only then discover that the result was not JSON:# before monochange <command> --jq '<query>' # after monochange <command> --format json --jq '<query>' -
--quietnow changes output only. It no longer silently turns a real operation into a dry run, so add--dry-runexplicitly:# before: this performed a dry run monochange run release --quiet # after monochange run release --dry-run --quiet
Workflows that set an explicit format default in monochange.toml keep that choice.
check failures exit non-zero in every format
Affects: CI jobs that run monochange check with JSON output.
A lint failure previously returned status 0 under --format json and --format json-min. Every format now exits 1 when lint errors exist, and the complete lint report still arrives on stdout:
monochange check --format json
echo $? # 1 when lint errors exist
CI can stop on the exit status without parsing error_count first.
Progress and diagnostics are safe for automation
Affects: CI log scrapers and automation that watched progress output.
- Captured and CI output never contains cursor-control sequences such as
ESC[2K. Progress is emitted as complete, deterministic lines. MONOCHANGE_NO_PROGRESS=1now also suppresses lint progress, as was already documented.- Failures print a stable diagnostic code, the relevant command or file context, and a suggested next action.
--progress-format jsonemits a newline-delimited event stream for tooling that wants machine-readable progress.--log-level debugstays a maintainer trace and now disables animation.
If a script detected progress by scraping spinner frames or control sequences, switch it to --progress-format json.
Publishing defaults and readiness
Longer default publish timeout (schema v0.6)
Affects: releases that publish to slow registries.
The default publish.timeout.timeout_seconds rises from 60 to 300 because crates.io server-side verification can take minutes. The timeout is a ceiling, not a delay: publishes that finish quickly are unaffected. The configuration and release-record schemas advance to v0.6; existing records migrate automatically and need no action.
Override the default per ecosystem or per package, and set timeout_seconds = 0 to disable the ceiling entirely:
[ecosystems.cargo.publish.timeout]
timeout_seconds = 300
retries = 2
Readiness blocks packages that cannot publish (artifact v3)
Affects: workflows that run monochange step publish-readiness or monochange step publish-packages.
Publish readiness now verifies trusted publishing for every selected package and validates the publication order against the workspace dependency graph before any registry state changes. A real publish run executes every readiness check before the first publish command, so a package that cannot publish aborts the run instead of failing midway.
Update steps:
- The readiness artifact schema advances from
2to3. Regenerate stored readiness artifacts after upgrading. - For each package with
publish.trusted_publishing = true, make surerepository,workflow, and the optionalenvironmentresolve from configuration, source settings, or the CI environment, and that the workflow file exists under.github/workflows/. - npm, crates.io, and pub.dev only accept trusted publishing for packages that already exist. Bootstrap first-time packages with
monochange step placeholder-publish, otherwise readiness blocks them with guidance.
Changeset authoring rules tightened
Affects: changeset authors and repositories using the changesets/recommended lint preset.
The preset now requires an H1 source summary (# Outcome) and rejects a first description sentence that merely repeats the summary. Repositories that do not use the preset can enable the standalone rule:
[lint.rules]
"changesets/summary-description" = "error"
Existing changeset files keep working, but new ones must lead with the H1 summary. The rendered changelog keeps choosing its own heading depth.
Library API updates
Affects: Rust consumers of the published crates.
PackagePublishSummary reports domain statuses
Replace expected, succeeded, and skipped with the explicit counters:
#![allow(unused)]
fn main() {
let summary = report.summary();
assert_eq!(summary.total(), report.packages.len());
assert_eq!(summary.published, 3);
assert_eq!(summary.already_exists, 2);
}
planned rows no longer collapse into skipped: use published, already_exists, blocked, and not_attempted for the states you care about.
Release notes stay structured until rendered
ReleaseNotesDocument is generic over its entry type. The previous Vec<String> of rendered Markdown entries still works, while typed entries expose summary, details, packages, change type, bump, stream, style, and provenance as fields:
#![allow(unused)]
fn main() {
let notes: ReleaseNotesDocument<ReleaseNotesEntry> = ReleaseNotesDocument {
title: "1.2.3".into(),
summary: vec![],
sections: vec![ReleaseNotesSection {
title: "Fixes".into(),
collapsed: false,
entries: vec![ReleaseNotesEntry {
summary: "Keep JSON failures non-zero".into(),
details_markdown: Some("CI can trust the exit status.".into()),
packages: vec![],
change_type: Some("fix".into()),
bump: BumpSeverity::Patch,
stream: "default".into(),
style: ReleaseNoteEntryStyle::Compact,
provenance: ReleaseNoteProvenance::default(),
}],
}],
};
let legacy = notes.to_legacy_markdown(&ChangelogStyle::default());
}
Built-in section names no longer contain emoji. Repositories that want emoji put them in their configured section headings.
SemanticChange is non-exhaustive
Custom semantic analyzers construct findings with SemanticChange::new and attach compiler evidence with with_assessment instead of using a struct literal:
#![allow(unused)]
fn main() {
let change = SemanticChange::new(
SemanticChangeCategory::PublicApi,
SemanticChangeKind::Modified,
"function",
"parse",
"function `parse` changed",
"src/lib.rs",
)
.with_assessment(SemanticChangeAssessment::new(
SemanticAnalysisOutcome::Breaking,
BumpSeverity::Major,
ApiConfidence::High,
SemanticAnalyzerEvidence::new(
"example/analyzer",
"example-engine",
SemanticAnalysisCompleteness::Complete,
"all public entrypoints checked",
),
));
}
CargoSemanticAnalyzer uses constructors
EcosystemSettings gains a semver_checks field, so struct literals must initialize it. CargoSemanticAnalyzer is no longer a unit struct; construct it explicitly:
#![allow(unused)]
fn main() {
let disabled = monochange_cargo::semantic_analyzer();
let with_settings = monochange_cargo::semantic_analyzer_with_settings(settings);
}
Exact ranges replace merge-base semantics for custom ranges
ChangeFrame::CustomRange now compares exactly base..head, while pull request frames keep merge-base semantics. Tools that compare several git frames should build one AnalysisSession and reuse it, and shared package path policy moved into monochange_core::PackagePathMatcher:
#![allow(unused)]
fn main() {
use monochange_analysis::{AnalysisConfig, AnalysisSession, ChangeFrame};
let session = AnalysisSession::new(root, AnalysisConfig::default())?;
let pull_request = session.analyze(&ChangeFrame::CustomRange {
base: "origin/main".into(),
head: "HEAD".into(),
})?;
}
#![allow(unused)]
fn main() {
use monochange_core::{PackagePathMatch, PackagePathMatcher};
let matcher = PackagePathMatcher::new(
"web",
"packages/web".as_ref(),
&["shared/schema/**".into()],
&["fixtures/**".into()],
);
assert_eq!(
matcher.classify("shared/schema/api.json".as_ref()),
PackagePathMatch::Touched,
);
}
Machine-readable schema versions
Affects: tools that pin monochange’s JSON schemas.
| Artifact | Previous | Current | Action |
|---|---|---|---|
| Configuration and release-record schema | 0.5 | 0.6 | None; records migrate automatically |
| Publish-readiness artifact | 2 | 3 | Regenerate stored artifacts |
| Change-classification report | older | 3 | Update parsers; coverage may carry a checks array |