The compute command
dispat compute reads what every package already declares about itself and turns it into configuration, so neither the
dependency graph nor the starting versions have to be transcribed by hand. It derives two things:
- the declared edges, diffed against the merged declaration list: the top-level
dependencieskey plus every package-declared list (apackagesentry's or an in-folder config file's); - the baselines a first release starts from, as
initialsentries taken from the version the manifests declare.
By default the suggestions are only printed; --write applies them, --interactive confirms each, --check gates CI.
The edges need no git history. The baselines read each package's release tags, and only those.
--package / --space / --group scope the report to the selected packages' own declarations.
Detection still reads every package's manifests whichever way you narrow: the workspace name index is what resolves a
declared dependency onto a provider, so an edge onto a package outside the selection stays recognised rather than
being proposed for removal.
What it reads. Every package folder is scanned for manifests, the same fifteen ecosystems dispat scanner reads:
npm (package.json), Go (go.mod), Cargo (Cargo.toml), Python (pyproject.toml, requirements files), Composer
(composer.json), Maven (pom.xml), NuGet (*.csproj and the flat lists), pub (pubspec.yaml), Ruby (Gemfile,
*.gemspec), CocoaPods (Podfile, *.podspec), Xcode (project.pbxproj), Apple bundles (Info.plist), Android
(AndroidManifest.xml), Gradle (libs.versions.toml, build.gradle(.kts)) and Docker (Dockerfile,
compose.yaml).
How a dependency becomes an edge. A declaration matches a workspace package by manifest name first (Python names are
PEP 503-normalised, Maven names are groupId:artifactId, Docker names are image repositories such as
ghcr.io/acme/api), then by a declared local path (file:, a relative replace, path =, a ProjectReference). Two packages declaring the same manifest name is ambiguous: reported as
W220, and no edges are derived from that name.
What it suggests. Four kinds of change, each printed with the manifest line that motivates it:
+ addfor a detected pair no source declares;~ kindfor a declared pair whosekinddisagrees with the manifests;- removefor a declared pair no manifest supports. Removal is only suggested when the consumer actually has parsed manifests, plus, unconditionally, when an edge names a package that no longer exists on disk (the one drift every other command refuses to load). An edge markedkeep: trueis never suggested for removal: the escape hatch for deliberate relations no manifest declares, a Docker image chain being the usual one.keepworks wherever the edge is declared, a package's own list included.+ initialfor a package whose starting version only its manifests know, described below.
A suggestion against a package-declared edge names its source ([packages/core/dispat.json: dependencies[0]]), so the
listing says which file an applied change would touch.
Baselines from manifest versions. A repository adopting dispat already carries its versions somewhere, and that
somewhere is the manifests. Without an entry for them dispat would start every package at 0.0.0 and release 0.0.1,
throwing away the history the files know about, so compute offers the missing entries:
+ initial core 1.4.2 packages/core/package.json declares 1.4.2; no release tag yet
An entry is proposed for a package only when all of this holds, and the last point is the one that keeps an established repository quiet:
- Its manifests declare a version. Root manifests are asked first and nested ones only when no root manifest has an answer, the same rank that decides manifest names.
- They agree on it. Two root manifests declaring different versions is
W225, and no baseline comes from them. - The version is a plain semver release. Something that is not semver at all, and a prerelease such as
1.0.0-SNAPSHOT(a version being worked toward rather than one released), are both passed over, as is0.0.0, which is already where a package with no entry starts. - The config has no
initialsentry for it yet. An entry already there is your decision, and compute never rewrites one, whatever the manifests say. - Its release tags cannot answer. The planner only ever reads
initialswhen a package has no parseable stable tag, so that is the only case an entry is worth writing. A package with a readable release tag is silent, and a package whose newest tag matches the format but is not a version gets the suggestion with that tag named in the evidence.
The entries land in the top-level initials map with every entry already there left exactly as it is, spelling
included. To silence a suggestion for good, write the entry yourself: "core": "0.0.0" is a decision like any other
and compute will leave it alone. Without a git repository the baselines are skipped altogether, with one warning, and
the edges are computed as usual.
How changes are applied. Nothing is written by default. The listing puts the edges first and the baselines after
them, by package name. --write applies every suggestion, and --interactive asks y/N per suggestion on stdin.
Each change is applied to the file that holds the declaration. An addition goes into the root config's top-level
dependencies object under its consumer, unless that consumer already declares its providers in a
packages.<name>.dependencies entry or in its own in-folder file, in which case the addition joins them there. A
removal and a kind correction edit the declaring source in place, and a baseline goes into the root config's
initials map.
Every write is guarded. Everything one file receives is written in a single pass, so a run that changes two of its keys
still leaves one backup. Every edited file is first copied to <name>.backup, which is untracked and worth a
.gitignore entry and is overwritten on every applying run, and each write is atomic.
Two cases are refused rather than guessed at. A TOML file is not rewritten in place, so --write prints a paste-ready
block for it and fails. And a key composed from both a referenced file and the keys
written beside it belongs to two files at once, so --write refuses it rather than choosing one. A key kept wholly in
a referenced file is written in that file, at the key it holds there, so the $ref survives the write and the backup
sits beside the file that changed.
--check overrides both apply modes. It writes nothing and exits 1 when any suggestion exists across any source,
which is the CI gate for a config lagging the manifests.
Flags
Beside the global flags:
| Flag | Default | Effect |
|---|---|---|
--package, -p | Every package-selecting command (release, status, run, preview, changelog, autoversion, autowriter, autoreplacer, commit, github, compute): narrow to the named packages. Repeatable and comma-separated, matched case-insensitively, * globs (-p '*' is every package); see Choosing the packages. | |
--space, -s | The same eleven commands: narrow to every package of the named spaces, with the same spellings. A standalone package belongs to no space; see Choosing the packages. | |
--group, -g | The same eleven commands: narrow to every package of the named versioning groups, with the same spellings. A group is a versionGroups entry or a space that versions as one, so it may cross spaces; see Choosing the packages. | |
--write | compute only: apply every suggestion to the config file (previous copy saved as <name>.backup). | |
--interactive, -i | compute only: confirm each suggestion (y/N on stdin) before applying it; wins over --write. | |
--check | compute and self-update: report only, change nothing, and exit 1 when there is something to do. For compute, any suggestion at all, edges and baselines alike, which is the CI gate for a config lagging the manifests, and it overrides both apply modes. |