Commit message reference
The current CCME 3 specification includes an
optional polyrepository Git profile, external VCS adapters, and explicit artifact rollback. Current dispat implements
the polyrepository profile as planning and repository behavior without changing its published message grammar.
External adapters and rollback remain specification contracts rather than implemented commands. This page documents
the installed parser behavior; use the CCME 2.0.0 reference
when checking its syntax. A rollback(scope) message is not a rollback command in this release.
Write your commit messages in a strict superset of Conventional Commits that adds the monorepo dimension. This format tells dispat which packages a change releases, how far it reaches, and which channel it lands on.
The pkg/ccme package parses your messages. A message holds one or more units separated by a line
of ---. Each unit has its own header, body, and footers.
<type>[(<scope-set>)][<directives>][!]: <description>
[body]
[footers]
The type decides the version bump. You can override these defaults in
parser.types:
| Type | Bump |
|---|---|
feat | minor |
fix, perf, revert | patch |
| any other known type | none |
any type with ! | major |
Scope sets
| Term | Resolves to | Unknown name |
|---|---|---|
core | that package | error |
core,ui | both | error |
* | every package in the workspace | (n/a) |
@acme/* | every package matching the glob | warning if it matches nothing |
. | the packages owning the commit's changed files | (n/a) |
-app | removes app from the set; exclusions always win | warning |
Omit the parentheses to derive the set from changed files using the longest matching path prefix, meaning a file under
a nested package belongs to the inner package only. A package declaring a src
owns only the files inside that sub-folder. dispat reports a unit as inert if it resolves to no packages.
Inline directives
Write inline directives between the scope-set and the :. Every directive has an equivalent footer. Stating both is
redundant, and contradicting them causes an error.
| Written | Footer equivalent | Meaning |
|---|---|---|
! | BREAKING CHANGE: <text> | Raises the unit's own bump to major. |
^ | Propagate-Depth: 1 | Reach direct consumers. |
^^ | Propagate-Depth: all | Reach the whole transitive closure. |
+N | Propagate-Depth: N | Reach consumers up to N edges away. +0 is no propagation. |
^minor | Propagate: minor | The bump consumers take. Default patch. |
^none | Propagate: none | Propagate nothing, whatever the depth. |
%beta | Channel: beta | The unit's own packages move to that channel. |
%%beta | Propagate-Channel: beta | The channel handed to the consumers reached below. |
++N | Propagate-Channel-Depth: N | How far the channel travels. Default 0. |
Both depths default to 0, and neither bounds the other. A unit reaches nobody on either axis until you say so.

Footers
| Footer | Effect |
|---|---|
BREAKING CHANGE: <text> | Major bump plus the text in the changelog. Case is significant; near-misses are warned about. |
Propagate: <bump> | none, patch, minor, major or inherit (copy the unit's own bump). |
Propagate-Depth: <N|all> | Bump-axis reach. |
Propagate-Scope: <scope-set> | Intersects the reached set. If nothing survives, a warning and no propagation. |
Propagate-Channel: <value> | inherit (default), none, stable, a channel name, or a <from>><to> transition. |
Propagate-Channel-Depth: <N|all> | Channel-axis reach. |
Propagate-Channel-Scope: <scope-set> | Restricts the channel axis. Defaults to Propagate-Scope. |
Channel: <value> | The unit's own channel, same grammar. |
Release-As: <none|auto|version> | Release control; see below. |
Reverts: <sha> | Informational for the bump; suppresses both changelog entries. See corrections. |
Edits: <sha[#n]|*> | Restate the records named, as this unit. See corrections. |
Deletes: <sha[#n]|*> | Discard the records named. See corrections. |
Channels and prereleases
dispat reads a package's channel from its baseline tag. For example, 1.5.0-beta.3 is on beta, 1.4.2 is on
stable, and an untagged package is on stable. Prerelease versions use a
<major>.<minor>.<patch>-<channel>.<counter> format with a separate numeric counter, so beta.10 sorts above
beta.9.
feat(core)%beta: core enters the beta line; nothing else moves
feat(core)^%beta: the same: the caret reaches the consumers and every
one is suppressed, because a stable consumer cannot
resolve a beta release
feat(core)^%beta++1: core and its direct consumers enter beta together
feat(core)^: an established train stays on beta with no directive
release(core)%stable: graduate core
release(core)%beta>stable%%beta>stable++*:
graduate core and everything still on beta behind it
A <from>><to> transition matches against the baseline channel, making the directive idempotent. Packages that
already graduated do not match, so the same directive works perfectly on the first run and the fifth. A bare stable
arriving by propagation never graduates a dependant, because only a direct directive or a transition naming the train
can do that.
A train converges exactly like stable releases do, meaning work a prerelease has already published cannot release
again, and a re-run with no new commits finds nothing to do. dispat computes the version of the next prerelease and
the graduation over the whole train, so a breaking change shipped in beta.0 keeps the target at the next major. A
Release-As consumed by a prerelease loses force, and a cancel cannot retract train-published work (a live cancel
aimed at published work warns W170).
Convergence happens quietly. The tag contains the directive that started the train, so dispat does not re-report it as
"already on beta" (W199) on later runs. That warning is reserved for a fresh directive pointing where the package
already is, and a spent cancel is not re-reported either.
Release control
| Written | Effect |
|---|---|
release(<pkg>) + Release-As: none | Hold: the bump is retained and reported, not released, until a later directive lifts it. |
release(<pkg>) + Release-As: auto | Resume: release at the max() of everything accumulated, catch-up included. |
release(<pkg>) + Release-As: <version> | Pin an exact version. |
cancel(<pkg>) | Discard the package's unreleased metadata. Irreversible; never reaches a published tag. |
dispat rejects a pin if it fails to move the package forward, falls below what pending commits require, or raises the
major version more than one above the computed version. A rejected pin has a unit-scoped blast radius: dispat reports
the error (stopping the run under commitErrors: "error"), ignores the bad directive, and falls back to the computed
version while leaving other valid units unaffected. A pin sets the exact version and channel, so
Release-As: 2.0.0-rc.0 enters the rc line and Release-As: 2.0.0 graduates, but the commit type still declares the
bump size.
A cancel is irreversible and only reaches backwards. It discards contributions from ancestor commits, but work
landing afterwards accumulates normally. A cancel on a provider that has already published is a no-op and says so,
because the version is public and the right target for stopping a pending catch-up is the consumer.
A cancel names nothing and discards everything behind it. To act on one record rather than the whole ledger, Edits
restates a named commit's record and Deletes discards it. Both directives reach only unreleased work and can be
undone by a later correction, as explained in Correcting a release record.