Skip to content

Versioning

Matrix packages version using semantic versioning. The package.json file is the source of truth for the publishable version; the matrix.json file must declare a non-empty version but only package.json drives publishes and installs.

Source of truth

mx publish and mx install read the version from package.json, not matrix.json:

ts
// projects/matrix-3/packages/mx-cli/src/commands/publish.ts:107
const version = readVersionFromPackageDir(cwd);

readVersionFromPackageDir (in packages/mx-cli/src/utils/package-store.ts) reads package.json:version. The validator still requires matrix.json:version to exist as a non-empty string; keep them in sync to avoid confusing readers.

Semver comparator

The CLI compares versions with a numeric, dot-segment comparator (packages/mx-cli/src/utils/versioning.ts):

ts
compareSemver("0.1.10", "0.1.9");  // 1
compareSemver("1.0.0", "0.99.99"); // 1
maxSemver(["0.1.0", "0.1.10", "0.1.2"]); // "0.1.10"

Pre-release suffixes (-rc.1, -beta) are accepted but compared only by the leading numeric segments. If you use pre-release tags, treat the comparator as approximate and rely on dist-tags or explicit version pins.

When to bump

CLAUDE.md Rule 9.9 (additive-only schema evolution) gives the practical rule:

ChangeBump
Adding a new optional field to an actor's accepts schemapatch
Adding a new component to matrix.json:components[]minor
Adding a new actor opminor
Adding a webapp block to a previously headless packageminor
Renaming or removing a field, op, or componentforbidden without a major version + replacement plan
Internal refactor onlypatch

The "additive only" rule is what makes minor bumps safe across runtimes that haven't restarted yet: a new field is invisible to old code, a removed field breaks it. Don't remove.

Bumping process

Practical steps for a routine release:

  1. Update package.json:version. Match it in matrix.json:version.
  2. pnpm build — produce the artifact.
  3. pnpm test — confirm tests pass.
  4. mx publish --dry-run — confirm preflight passes.
  5. mx publish — push to the registry.
  6. Commit and push the version bump (CLAUDE.md Rule 8: commit when a coherent unit of work is complete).

Fork versioning

mx fork <packageName> (packages/mx-cli/src/commands/fork.ts) takes an installed package and forks it under a new name. It writes fork.upstream.{name,version} into the forked manifest, so subsequent upgrades can be tracked:

bash
mx fork @open-matrix/chat --name @my-org/my-chat

mx outdated (packages/mx-cli/src/commands/outdated.ts) reports forks whose pinned upstream version is older than what the registry holds:

bash
mx outdated
@my-org/my-chat 0.2.5 -> 0.2.9 (outdated)

mx update-fork @my-org/my-chat updates the fork metadata to the latest upstream version. It does not auto-merge code; use it after you've integrated upstream changes manually.

Version pinning at install time

bash
mx install @open-matrix/chat            # latest
mx install @open-matrix/chat@0.2.9      # exact version

Source: parsePackageSpecifier in packages/mx-cli/src/commands/install.ts:66-91 parses the <name>@<version> syntax. Without a version, the npm registry returns its latest entry; the local registry returns the maximum semver currently published.

Why matrix.json:version exists at all

Historical reason: package introspection used to read manifests directly, before npm metadata was a hard dependency. It is now redundant with package.json:version but the validator still checks for it. Keep them aligned. Removing the matrix.json:version requirement is filed in WORKSTREAMS/loose-ends/ as a small cleanup.

See also

Source: projects/matrix-3/packages/mx-cli/src/utils/versioning.ts is the comparator; packages/mx-cli/src/utils/package-store.ts:readVersionFromPackageDir is the version source.