The lifecycle
changeRig follows the @changesets model: contributors describe intent in small markdown files, and a later version step turns the accumulated intent into version bumps and a changelog.
init
changerig init
changerig init --source commits # changesets | commits | bothCreates the .changeset/ directory with a config.json. The config schema (.changeset/config.json) covers changelog/format specs and ignore globs. --source picks where releases are sourced from — accumulated changeset files, conventional-commit messages, or both (interactive when the flag is omitted).
Where the config lives
init writes the canonical .changeset/config.json, but the config is resolved from one of these locations (at most one — more than one is an error that lists them; a .json + .jsonc pair counts as two):
.changeset/config.jsonc·.changeset/config.json.changeset/changerig.jsonc·.changeset/changerig.jsonchangerig.jsonc·changerig.json(repo root)- a
"changerig"(or"changeset") key inside.rig.json - a
"changeset"key inside ashiprig.jsonc/release.jsonc— so a single shiprig config file can carry the changeset config too (see the release pipeline)
.changeset/config.json keeps the @changesets layout so the JS tool reads it too; the alternate names and the .rig.json key are rigsmith conveniences. changerig config set edits whichever single file is in use (when the config lives in a .rig.json key, edit it there).
add
changerig add -p my/pkg --bump minor -m "Add a feature"
changerig add -p my/pkg -t fix -m "Stop the crash" # type-driven bump (! = breaking)
changerig add -p my/pkg -t feat --scope rig -m "…" # files the entry under rig:
changerig add # interactive: pick packages, bump, message, scope--package can be omitted only where there is one package to choose; with several, add says so rather than writing a changeset that names none — such a file is silently ignored by every later step.
Writes a .changeset/*.md file in the shared @changesets format: which packages change, at what bump level (major/minor/patch), and a summary line that becomes the changelog entry.
Type and scope
A changeset can carry a conventional type and scope, either as frontmatter or as a feat(rig): … prefix on the summary:
---
type: feat
scope: rig
"github.com/you/repo"
---
new `rig stack` — several repos fused into one historyThey do different jobs. The type picks the changelog section and, when no explicit bump is given, decides the bump (feat → minor, fix → patch, per changelogGroups). The scope names which tool the entry belongs to: it becomes the bullet's lead-in and groups that tool's lines together within a section. Neither is required — an untyped changeset still renders under the section for its bump (Minor Changes, Patch Changes) as before.
Note the package line above carries no bump. Leave it off and the type decides; give one and it wins, per package — which is how one changeset can be a feature for an app and a patch for the library under it.
--scope is inferred from what the branch changed, so it is one less thing to remember: a diff confined to cmd/rig or internal/rig infers rig, and a diff spanning several tools infers nothing rather than guessing.
A ! on the type marks a breaking change — feat(rig)!: …, or type: feat! — which renders under 💥 Breaking Changes, ahead of every other section, and derives a major bump. Like any derived bump, an explicit per-package bump on the changeset still wins, so "pkg": patch with feat! releases a patch.
Choosing the sections and their order
changelogGroups maps each type to a heading and an implied bump, and the list order is the section order. Drop the emoji, rename a section, or move fixes above features by rewriting it:
{
"changelogGroups": [
{ "type": "feat", "section": "Features", "bump": "minor" },
{ "type": "fix", "section": "Fixes", "bump": "patch" }
]
}changelogScopes does the same for scopes within a section — which tool a reader sees first:
{ "changelogScopes": ["rig", "clauderig"] }Scopes left out follow alphabetically, and unscoped entries come last. Omit the key entirely and every scope sorts alphabetically.
Flags:
| Flag | Meaning |
|---|---|
-p, --package | Package to include (repeatable) |
--bump | Explicit bump: major / minor / patch / auto |
-t, --type | Conventional type (feat/fix/…, suffix ! for breaking); the bump derives from it when --bump is omitted |
-m, --message | Changeset summary (skip the prompt) |
--empty | Write an empty changeset that names no packages |
--scope | Which part of the repo the change belongs to — the tool, in a monorepo. Inferred from the changed files when omitted; - for none |
--since <ref> | Preselect packages changed since a git ref in the picker |
--open | Open the created changeset in $EDITOR |
status
changerig status --verboseShows the pending release plan — every package that will bump, the level, and why (including the dependency cascade: a dependent is patch-bumped when one of its dependencies releases). Supports --since and --output.
version
changerig version
changerig version --dry-run # print the plan without writing files
changerig version --snapshot # snapshot release (optional tag; bare --snapshot works)
changerig version --independent # version each package on its own changesetsConsumes the pending changesets and:
- parses them via the core engine,
- cascades bumps to dependents (range-aware),
- applies linked / fixed / lockstep grouping,
- stamps the new version into each ecosystem's manifest, and
- writes
CHANGELOG.md.
Flags: -n, --dry-run (plan only), --snapshot [tag] and --snapshot-template ({tag}/{commit}/{datetime}/{timestamp} suffix) for snapshot releases, --independent to version each package separately instead of via a shared version file, -y, --yes to accept the computed versions without the interactive override prompt, and --no-stamp to write nothing into any manifest.
Versions that do not live in the tree
Step 4 assumes the manifest is where the version lives. Two kinds of package break that assumption:
- one whose version is computed at build time — MinVer reading git tags, a CI-stamped build — carries no number in the tree at all. Such a project is still discovered as a package (a
.NETproject is, when its effectiveIsPackableis true — or, whenIsPackableis not set to false, when it declares aPackageIdor references MinVer), listed as no version in the tree. The effectiveIsPackableis read across the project and its ancestorDirectory.Build.propsfiles in import order: the last unconditional assignment wins, as in MSBuild, and since conditions are not evaluated a conditionaltrueanywhere counts as true — so a shared props file that sets it false for everything and true again underCondition="…Contains('/src/')"makes every project beneath it packable — while a conditionalfalseis ignored. A MinVer reference counts whether it is aPackageReferencein the project or aGlobalPackageReferencein the nearestDirectory.Packages.props(an outer one is read only where the nearer file imports it, as restore does). Commented-out elements are ignored throughout; - one whose manifest is not this repository's to write — a member of a stackspace, whose directory leaves in pull requests to its upstream, where a stamped version would be a bump nobody asked for.
For both, version still computes the number, cascades it to dependents, consumes the changesets and writes the changelog — and instead of stamping the manifest it records the result in .changeset/versions.json. Discovery reads that record as the package's current version from then on, so the next plan bumps from it, and the release pipeline's ${version.<pkg>} hands it to the build (-p:Version= for dotnet, say). A stackspace member gets this without asking; --no-stamp asks for it on one run, and "versioning": { "stamp": false } in the config asks for it on every run. A member's changelog goes to the stackspace root's CHANGELOG.md, one section per member, since its own directory is not the stackspace's either.
A package with no version anywhere yet — nothing in the tree, nothing recorded — plans from 0.0.0; seed .changeset/versions.json with its real current version, or type the exact version at the override prompt, and it is remembered. A package with no version in the tree is never stamped, whatever the config says: a <Version> inserted into a MinVer project would fight the tool that owns the number. The record and the manifest never disagree for long, either: a later stamped run bumps from whichever is newer and, having written the manifest, drops the record — so a one-off --no-stamp release is not repeated by the next ordinary one.
Changelog generators are pluggable — the built-in renderer dogfoods the same JSON contract external plugins speak. Set "changelog": "<plugin>" in config to swap it in.
pre
changerig pre enter next # enter prerelease mode tagged "next" (1.2.0-next.0)
changerig pre exit # leave prerelease mode; the next version is a normal releasePrerelease mode makes version produce tagged pre-releases (e.g. -next.N) until you exit. The mode is tracked in .changeset/pre.json.
changelog
Hand-author changelog entries outside the changeset flow — useful for notes that don't map to a version bump:
changerig changelog add -m "Document the new flag" -t docs # prepend an entry
changerig changelog add -m "…" --version 1.4.0 # file under a release heading
changerig changelog format my/pkg # reformat a CHANGELOG.mdadd prepends an entry under an Unreleased heading by default (--version files it under a specific release; -t/--type adds a label). format re-runs the native markdown formatter over a package's CHANGELOG.md.
browse, info, config
changerig browse(aliasls/list) — browse and manage the pending changesets.changerig info— show the resolved config and the packages discovered across every ecosystem.changerig config—show/get/set/path/editthe.changeset/config.json(comment-preserving writes).