Skip to content

Cross-Tool Synthesis & the Dub Delta

The capstone of the monorepo-tooling survey. Part 1 distils the 44 deep-dives and the concepts vocabulary into a head-to-head comparison across the five axes that decide a monorepo tool's character: workspace declaration, dependency isolation, task DAG & change detection, caching & remote execution, and CLI/filter ergonomics. From that synthesis it extracts The Consensus Standard (the baseline shared across almost every modern ecosystem), maps the Architectural Trade-offs between the tool families, and closes with The Dub Delta — an explicit capability-by-capability gap analysis of dub against the consensus, the bridge into the milestoned proposal.

Scope. This is the synthesis leaf of the survey. It assumes the per-tool mechanics (deep-dives) and the shared definitions (concepts) as given and cross-links rather than re-derives them. For dub's current architecture read directly from the dlang/dub source tree, see the baseline; for the concrete enhancement plan, see the proposal. The monorepo-tooling analogue of async-io's comparison.

Last reviewed: June 5, 2026


Part 1 — Cross-tool synthesis

1.1 The 44 tools at a glance

The master catalog, grouped by category. DAG = does the tool build a multi-member task/target graph (vs. a single package's compile DAG). Cache summarises the deepest tier reached: dl (download/metadata only), local (local task-output), remote (shared/HTTP/gRPC cache), REAPI (Remote Execution API). XRef = the local cross-reference mechanism. The link cell points at each deep-dive.

Package managers (resolve & isolate; mostly no task DAG)

ToolEcosystemWorkspace modelDAGCacheLocal cross-refs
npmJS/TSRoot-package (workspaces)nodlsemver + symlink
yarn-berryJS/TSRoot-package (workspaces)pkg (foreach)dlworkspace:
pnpmJS/TSVirtual (pnpm-workspace)pkg (-r)dlworkspace: + catalog:
bunJS/TSRoot/virtual (workspaces)pkg (--filter)dlworkspace: + catalog:
uvPythonCargo-style (dual-mode)nolocal{ workspace = true }
poetryPythonNone nativenodlpath + develop=true
hatchPythonEnv-scoped workspace.membersnolocaleditable members + path
cargoRust[workspace] (dual-mode)actionlocalpath + .workspace = true
go-workGoVirtual (go.work)nolocalimplicit (MVS main modules)
gradleJVMMulti-project + compositetaskremoteproject(":path") + substitution
mavenJVMAggregator (<modules>)task (reactor)dlGAV + ReactorReader
sbtScala/JVMMulti-project (Scala DSL)taskREAPI (2.x)dependsOn(proj)
millScala/JVMModule tree (Scala DSL)actionlocalmoduleDeps
composerPHPNone nativenodlpath repos + replace

JS/TS task orchestrators (overlay a DAG + cache on a package manager's workspace)

ToolEcosystemWorkspace modelDAGCacheLocal cross-refs
nxJS/TSInherited from PMtaskremotedelegated to PM
turborepoJS/TSInherited from PMtaskremotedelegated to PM
lernaJS/TSInherited from PMtask (Nx)remote (Nx)delegated to PM
rushJS/TSExplicit projects[]taskremoteworkspace: (via pnpm)
lageJS/TSInherited from PMtaskremotedelegated to PM
wireitJS/TSInherited from PMtaskremote (GH)relative path + script

Polyglot build orchestrators (own the action graph, often down to REAPI)

ToolEcosystemWorkspace modelDAGCacheLocal cross-refs
bazelPolyglotFS auto-detect (BUILD)actionREAPI//path:target label
buck2PolyglotFS auto-detect (BUCK)actionREAPIcell//path:target
pantsPolyglotFS auto-detect (BUILD)actionREAPIinferred from imports
pleasePolyglotFS auto-detect (BUILD)actionREAPI//path:target label
moonPolyglotRoot-anchored (.moon/)actionremotehost PM + dependsOn
gnPolyglotFS auto-detect (.gn)actionnone//path:target label

Container / CI-oriented (data-flow DAG over containerised steps)

ToolEcosystemWorkspace modelDAGCacheLocal cross-refs
daggerContainerModule graphdata-flowremotedagger install ./mod
earthlyContainerEmergent +target graphdata-flowremote+target (cross-repo)
gardenContainerStack Graph (scanned)data-flowremoteaction-to-action

Generic task runners (a deps DAG; no package model)

ToolEcosystemWorkspace modelDAGCacheLocal cross-refs
taskPolyglotincludes namespacestasklocal (skip)task edges
justPolyglotmod namespace treetasknonetask edges
misePolyglotconfig_roots (experimental)tasklocal (skip)task edges (//path:task)
makePolyglotNone (recursive $(MAKE))tasklocal (mtime)file edges

Native build systems (one global target DAG; toolchain owns linkage)

ToolEcosystemWorkspace modelDAGCacheLocal cross-refs
mesonNativesubprojects/ + .wraptargetlocal (mtime).wrap [provide]
cmakeNativeNone (add_subdirectory)targetnonetarget name
sconsNativeSConscript treeactionremote (CacheDir)graph edges
wafNativerecurse() treeactionremote (wafcache)use='A B'
ninjaNativeGenerated build.ninjaactionlocal (mtime)file edges

Remote-execution backends (REAPI servers; no client-side workspace)

ToolEcosystemWorkspace modelDAGCacheLocal cross-refs
buildbuddyREAPInone (server)no (consumes)REAPIn/a
buildbarnREAPInone (server)no (consumes)REAPIn/a
nativelinkREAPInone (server)no (consumes)REAPIn/a

Minimalist / research & polyglot glue

ToolEcosystemWorkspace modelDAGCacheLocal cross-refs
redoResearch.do tree + .redo dbaction (dynamic)localredo-ifchange ../x
tupResearch.tup SQLite dbaction (file)nonegroup/bin edges
nix-flakesPolyglotFlake graph (flake.lock)derivationremote (binary cache)relative path:

NOTE

The seven category bands above are the same grouping the umbrella index carries in its sidebar. dub is omitted from these tables because it is the baseline, not a surveyed peer — its row is the subject of The Dub Delta.

The single most useful lens on the catalog: the five axes are largely orthogonal, and a tool's category predicts where it sits on each. Package managers max out isolation and lockfiles but stop before the task DAG; orchestrators inherit the workspace and add a DAG + cache but own no dependency resolution; the polyglot engines fuse all five into one content-addressed action graph at the cost of adopting a foreign build language. The sections below walk each axis.


1.2 Workspace declaration & topology

How are members discovered, and is the root itself a buildable package? Three discovery mechanisms and two root models (full taxonomy in concepts §1):

DiscoveryRoot modelExemplars
Glob arrayroot-package or virtualcargo, uv, pnpm, npm, bun, yarn-berry
Explicit arrayaggregator/registrymaven (<modules>), go-work (use), rush (projects[]), gradle (include())
Filesystem auto-detectimplicit (tree)bazel, buck2, pants, please, gn, nix-flakes
Inherited from PMnone of its ownnx, turborepo, lerna, lage, wireit, moon

The reference design is cargo's dual-mode root: a [workspace] table either sits inside a buildable crate (root-package form) or stands alone with no [package] (virtual form), and members are glob-expanded path arrays plus the transitive closure of path dependencies. uv ports this verbatim ([tool.uv.workspace] with members/exclude, root-package or virtual), and bun supports both. pnpm is purely virtual (a dedicated pnpm-workspace.yaml), and go-work is virtual and developer-local — its go.work use-lists modules and is deliberately kept out of VCS.

At the opposite pole, the polyglot engines need no member array at all: every directory with a marker file is a package. bazel addresses each as a //-rooted label and discovers topology from deps edges; pants goes furthest, inferring even the cross-references from import statements. The JS/TS orchestrators are parasiticlage, turborepo, and nx declare no members, reading the host package manager's workspaces globs and overlaying only a task layer. Finally, several mainstream tools have no native workspace (poetry, composer, cmake): multi-package trees are improvised from per-project manifests glued by relative paths — exactly where dub's subPackages array sits today.

NOTE

A workspace boundary is anchored by a marker file the CLI walks up to find: Cargo.toml with [workspace], pnpm-workspace.yaml, MODULE.bazel, .buckconfig, pants.toml, .plzconfig, a .gn dotfile, or a .tup/.redo database. dub has no such workspace marker — only a root recipe enumerating sub-packages by path.


1.3 Dependency isolation & local cross-references

Two coupled questions: where do third-party deps land on disk, and how does a member reference a sibling member's source (concepts §2–3).

Isolation. Four models, least to most isolated: flat hoisting (one root node_modules, npm; trades on phantom deps), isolated symlinks (pnpm's node_modules/.pnpm virtual store, rush), content-addressed store (yarn-berry Plug'n'Play .pnp.cjs, uv LinkMode::Clone reflink, bun), and per-project vendoring (composer vendor/, poetry/hatch per-project .venv). The REAPI engines (bazel, buck2) and nix-flakes generalise the content-addressed store to all inputs and outputs — the same CAS underpins both their dependency model and their cache (§1.5). The native runners (make, ninja, just, task) have no isolation layer at all: package placement is the native toolchain's job — the model dub's shared $DUB_HOME/packages/ cache already follows.

Local cross-references. The defining monorepo capability — depending on a sibling's source without publishing. Four mechanisms recur:

MechanismEdge is…Exemplars
workspace: protocollocal-first selector, rewritten on publishyarn-berry, pnpm, bun, rush
Workspace-source flag{ workspace = true } / dep.workspaceuv, cargo
Relative path= depa directory path to the siblingcargo, composer, poetry, dub today
Typed project / labela project value or graph labelsbt (dependsOn), mill (moduleDeps), bazel (//path:target)

yarn-berry is the reference: a workspace:*/^/~ selector links to the member's source with LinkType.SOFT (symlinked, never fetched, not persisted to the lockfile), then the beforeWorkspacePacking hook rewrites it to a real registry range at publish. go-work makes the same idea implicit — every used module is a co-equal main module, so a cross-member import resolves to on-disk source through MVS with no replace, version, or publish step. maven has a unique variant: a sibling is an ordinary <dependency> GAV, and the reactor's ReactorReader intercepts resolution within the build to serve the freshly-built target/.

A second layer is version unification: a central [workspace.dependencies] (cargo) or catalog: (pnpm, bun, yarn-berry) registry at the root, so every member writes react.workspace = true / "react": "catalog:" and a shared upstream is pinned once. cargo couples this with field inheritance (version.workspace = true, authors.workspace = true). This is precisely the fragmentation dub suffers: each Sparkles member pins expected/silly independently, with nothing reconciling them — the Milestone-2 target of the proposal.


1.4 Task DAG & change detection

Dependency placement determines what builds against what; the task graph determines what runs in what order. The pivotal distinction is graph granularity (concepts §4):

Graph kindNodes are…Exemplars
Package graphwhole memberspnpm -r, yarn-berry foreach -t, bun --filter
Task graph(package × task) pairsturborepo, nx, lage, wireit, rush, maven reactor
Action graphsingle tool invocationscargo units, bazel (Skyframe), buck2 (DICE), ninja file edges, mill
Implicit data-flowAPI/command callsdagger (BuildKit LLB), earthly, garden (Stack Graph)

A package graph topo-sorts whole members under a concurrency cap — pnpm chunks the project DAG via graphSequencer and runs it under pLimit-bounded --workspace-concurrency; yarn-berry's yarn workspaces foreach -t is the direct inspiration for the dub proposal's loop. A task graph crosses that package graph with a per-task pipeline: lage's dep-specs ^build (dependencies' build), ^^transpile (transitive), pkg#task (specific node), and bare build (same package) are the canonical vocabulary, shared by turborepo (dependsOn) and nx. An action graph dissolves packages entirely: bazel's action graph is the task DAG, so a Skyframe rebuild touches only the reverse-transitive closure of changed inputs.

A crucial gap: most package managers stop before any task DAG. npm run --workspaces runs scripts sequentially in declared order — no topology, no parallelism — which is the entire reason turborepo, nx, lage, and wireit exist as overlays. uv, poetry, composer, and dub own no member-level task graph at all.

Change detection bounds work to what changed, in three increasingly precise families:

MechanismCompares…Exemplars
mtime / timestampfile mtimes vs. outputsmake, ninja, cmake/meson (delegated)
Input hashingcontent hash vs. stored fingerprintturborepo, nx (xxh3_64), bazel, gradle, cargo fingerprints
Affected / --sincea VCS diff → changed members + dependentslerna, nx affected, moon, please (plz query changes --since)

These compose: nx layers --since affected detection over its xxh3_64 computation hash, and moon pairs per-task content hashing with a Git-aware tracker. mtime detection is fragile on ephemeral CImake's incrementality "vanishes on a fresh checkout," the structural reason CI-heavy monorepos migrate to input-hashing tools. dub's build cache is content-addressed (a genuine strength, §1.5) but per-package, with no --since slicing — the Milestone-4 target of the proposal.


1.5 Caching & remote execution

The caching ladder, deepest tier reached varying by where and what (concepts §5):

TierReuses…Exemplars
Download / metadatafetched package archives onlynpm, composer, maven (~/.m2), poetry, go-work, dub ($DUB_HOME)
Local task-outputbuild/test outputs on this machineturborepo, nx, gradle, mill, cargo target/, bazel --disk_cache
Remote / sharedoutputs across machines & CIturborepo (HttpCache), nx (Nx Cloud), gradle (HttpBuildCache), scons, waf, sbt 2.x
Remote execution (REAPI)runs the action on a remote workerbazel, buck2, pants, please, buildbuddy, buildbarn, nativelink

The crucial cliff is between remote cache and remote execution. Almost every modern orchestrator now offers a remote cache that replays a prior result (terminal logs + output files) keyed by a content hash — turborepo, nx, lage, rush, wireit, gradle, moon. Far fewer run the action remotely: remote execution ships an action's inputs to a worker fleet and runs the compiler there, which requires the action to be hermetic (fully described by its declared inputs). Only the polyglot engines and the dedicated REAPI servers — buildbuddy, buildbarn, nativelink — implement the Remote Execution API (REAPI v2): ContentAddressableStorage + ByteStream + ActionCache + (optionally) Execution. These backends own no workspace; they only ever see the hashed, post-analysis action graph a client emits. sbt 2.x is the rare language package manager with native REAPI-compatible task caching; nix-flakes reaches the deepest hermeticity in the catalog (a content-addressed store keyed by the full build closure) but over Nix's own protocol, orthogonal to REAPI.

This is the largest single capability gap. The language package managers (cargo, uv, go-work, npm, pnpm, composer) and dub cache only downloads and local incremental build state; none has a shared task-output cache. dub does have a real, content-addressed local build cache (its computeBuildID hashes the full build inputs) — but it is local-only, per-package, and has no remote tier.


1.6 CLI & filter ergonomics

The developer command boundary — how to target one member, several, or a subgraph:

ErgonomicVocabularyExemplars
Per-package selector-p <pkg> / :pkg / colon-labelcargo (-p), bazel (//path:target), dub (:subpkg)
Filter grammar--filter <pattern> (name/path/glob)pnpm, turborepo, bun, nx, lerna (--scope)
Topological broadcastforeach -t / -r over all membersyarn-berry, pnpm (-r), maven reactor, cargo (--workspace)
Subgraph traversal--from / dependents / ...pkgpnpm (...pkg), turborepo (...), nx (affected)
Change-based slicing--since <ref>lerna, nx, moon, please
Concurrency controls-j/--jobs, --parallel, --concurrencycargo, ninja, turborepo (10), nx (3)

pnpm's --filter is the richest grammar — name globs, path globs, the ...pkg dependents/dependencies expansion, and [<ref>] git-range selectors all compose. cargo offers the cleanest binary pair: -p <member> for one, --workspace for all. The orchestrators add change-aware selection on top — nx affected and lerna --since compute the changed members plus their impacted dependents from a git diff.

dub's CLI surface is the minimal end of this axis. It offers the :subpkg selector and --root <path>, but no filter/selection vocabulary for multiple members: no --filter, no repeatable -p, no --recursive/--from/--since, no "all members" broadcast. The one fan-out flag, upgrade -s/--sub-packages, is a one-off that re-enters dub's single-root machinery N times. Multi-package work is "loop it yourself" — exactly what the Sparkles apps/ci helper does.


The Consensus Standard

Cutting across the 44 tools, a baseline feature set has emerged that almost every modern monorepo ecosystem now ships (or that teams bolt on if the native tool lacks it). A tool is "consensus-grade" when it offers all of:

  1. A declared workspace with glob member discovery — a root manifest naming members by glob (members = ["libs/*", "apps/*"]), with a virtual (non-buildable) root option. Shipped natively by cargo, uv, pnpm, bun, npm/yarn-berry. The polyglot engines substitute filesystem auto-detection (bazel).
  2. A workspace:-style local-first cross-reference — depend on a sibling by name, resolved to its source in dev, rewritten to a real range on publish. The yarn-berry/pnpm workspace: protocol, cargo's dep.workspace = true, go-work's implicit MVS.
  3. A unified root lockfile — one lock resolving all members together so a transitive dependency is pinned once, monorepo-wide. cargo (Cargo.lock), pnpm, uv, bun, yarn-berry.
  4. Central version unification — a [workspace.dependencies] table or catalog: protocol eliminating per-member version drift. cargo, pnpm, bun, yarn-berry.
  5. A topological task DAG with bounded concurrency — build/test a member only after its workspace dependencies, running independent legs in parallel under a -j/--concurrency cap. yarn-berry foreach -t, pnpm -r, turborepo, nx, cargo, gradle, maven.
  6. A filter/selection vocabulary — target one member, a glob of members, or a change-bounded subgraph (--filter, -p, --since). pnpm, turborepo, nx, cargo, lerna.
  7. Content-addressed task-output caching — a cache key over source contents + command + env + upstream output hashes, replaying a prior result instead of recomputing. turborepo, nx, gradle, mill, bazel — increasingly with a remote tier.

Items 1–6 are now table stakes for a serious monorepo; item 7's local form is common, its remote form is the current frontier, and REAPI remote execution (the eighth, aspirational tier) remains the exclusive province of the heavy polyglot engines. The consensus tool lands in the middle-to-right of every axis in the concepts grid: dual-mode root, glob discovery, a workspace: protocol, a unified lockfile, a topological DAG with input-hash change detection, a filter grammar, and at least a local content-addressed cache.


Architectural Trade-offs

The catalog's tools cluster into seven families, each making a different bet about what to own and what to delegate. The central tension is power vs. ceremony: the more of the five axes a tool owns natively, the more it imposes its own build language and the less it cooperates with the host ecosystem.

FamilyOwnsDelegatesThe bet
Heavy polyglot enginesall five axes; action graph; REAPIthe host language's idiomshermeticity + scale are worth a foreign build language
Language-native workspacesworkspace, lockfile, package DAGtask pipelines, remote cachethe ecosystem's own model is enough; stay idiomatic
JS task orchestratorstask DAG, cache, change detectionworkspace + deps (to the package manager)overlay a graph on the PM you already use
Minimalist task runnersa deps DAG, namespacingeverything else (packages, cache, isolation)be a thin, universal command front-end
Container / CI enginesa data-flow DAG, layer cache, hermetic stepsthe package modelthe container is the unit of reproducibility
Remote-execution backendsthe CAS + scheduler + worker fleetthe entire workspace (to the client)be the server half of REAPI; own nothing above the action
Polyglot gluea content-hashed dependency/derivation graphtask pipelinespin and wire what no single ecosystem tool can see whole

Heavy polyglot action-graph engines (bazel, buck2, pants, please) own everything down to the leaf compiler invocation. They dissolve packages into a fine-grained action DAG, content-address every action, and reach REAPI remote execution — bazel's Skyframe rebuilds only the reverse-transitive closure of changed inputs; buck2's DICE graph gives near-instant no-op rebuilds; pants infers the graph from imports so there is nothing to hand-wire. The price is a foreign build language (Starlark BUILD files), a steep hermeticity discipline, and abandoning the host ecosystem's idioms. This is the right bet at Google/Meta scale and the wrong one for a five-package library.

Language-native workspaces (cargo, uv, pnpm, yarn-berry, go-work) own the workspace, the unified lockfile, and a package-level DAG, but stop before user-defined task pipelines and remote caching. They stay maximally idiomatic — a Rust dev needs no new tool, just a [workspace] table — at the cost of a coarse graph (whole-member, not per-action) and no shared cache. cargo is the canonical example and the most direct precedent for the proposed dub workspace feature: one root manifest, one Cargo.lock, one shared target/, a topologically-scheduled fingerprint-cached build DAG, dual-mode root, and [workspace.dependencies] inheritance. This is the family dub should join.

JS task orchestrators (nx, turborepo, lage, wireit, rush) are the overlay answer: they own the task DAG, content-addressed cache, and change detection, but delegate the workspace and dependency resolution to the package manager underneath. This exists precisely because the JS package managers stopped at item 5 — npm run --workspaces has no topology, so an entire tool category sprang up to add one. turborepo is the lean extreme (one turbo.json, reads the PM's lockfile, hashes every task); nx the maximal (project-graph inference, affected, Nx Cloud); wireit the most minimal (a wireit block inside the existing package.json, no new binary). The bet — "overlay a graph on the PM you already use" — is itself a lesson for dub: the proposal can add a topological loop without rebuilding resolution, because dub already owns the resolver.

Minimalist task runners (just, task, make, mise, redo, tup) own a deps DAG and namespacing and deliberately delegate everything else. They are universal polyglot front doors — make's 1976 mtime engine, task's declarative YAML, just's parameterised recipes — with no package model, no isolation, and (mostly) no content cache. redo and tup are the research-grade outliers: tup discovers the dependency graph by intercepting real syscalls and updates only the change-reachable slice. These are what dub users reach for today to glue members together (the Sparkles apps/ci helper is this pattern in hand-written D).

Container / CI engines (dagger, earthly, garden) make the container the unit of reproducibility: every step runs in a BuildKit container, the graph is an implicit data-flow DAG that caches and parallelises automatically, and earthly's +target references reach across files and even git repositories. The bet trades fine-grained incrementality for environmental hermeticity — and is orthogonal to dub (it would wrap a dub build, not replace it). Note earthly is now frozen/unmaintained (mid-2025).

Remote-execution backends (buildbuddy, buildbarn, nativelink) own only the server half — a CAS + ActionCache + scheduler + worker fleet speaking REAPI v2 — and nothing above the action. The workspace, members, and dependency DAG live entirely on the client (bazel/buck2); the backend only ever sees hashed, post-analysis actions. nativelink's Nix-pinned Local Remote Execution makes local and remote toolchains bit-for-bit identical. These are irrelevant to dub until it has a content-addressed action graph to feed them — a post-proposal horizon.

Polyglot glue (nix-flakes) pins and wires what no single ecosystem tool can see whole: each flake.nix is a content-hash-locked node, relative path: inputs reference sibling flakes, and flake.lock pins the whole transitive DAG. Sparkles already uses this (the Nix flake, nix/dub-lock.json) as the layer above dub — the glue that does what dub cannot. The proposal's goal is to move workspace concerns intodub so the Nix layer is reproducibility, not orchestration.


The Dub Delta

Measuring dub — read directly from the dlang/dub source at v1.42.0-beta.1 — against The Consensus Standard yields a concrete, addressable gap. dub already owns the two hard primitives a workspace needs — a working dependency resolver and a solid content-addressed local build cache — but it has no organising concept above the single package. A grep of the entire dub source and docs trees for "workspace" or "monorepo" returns zero matches.

CapabilityConsensus (exemplar)Dub today (baseline)The gap
Workspace declaration[workspace] members = ["libs/*"], virtual root (cargo)subPackages array; root is always a package; no globbingNo [workspace] block, no member globbing, no virtual (non-buildable) root
Local cross-referencesworkspace: protocol; resolve-by-name (yarn-berry, go-work)manual path="../.." + :subpkg, depth-sensitiveNo local-first protocol; hand-maintained relative paths that drift vs. published
Unified lockfileone root lock for all members (cargo, pnpm)one dub.selections.json per member; no root lockFragmented per-member lockfiles; version drift structurally possible
Config / version inheritanceversion.workspace = true, [workspace.dependencies] (cargo)none (only the obscure inheritable selections flag)No metadata or dependency-version inheritance from a root
Cross-member task DAGtopological foreach -t (yarn-berry), dependsOn (turborepo)none; per-package verbs + bespoke apps/ci scriptsNo dub build --workspace; orchestration is uncoordinated external scripts
Filter / slicing--filter, -p, --recursive, --since (pnpm, nx):subpkg, --root, one-off upgrade -sNo multi-member selection or change-based slicing
Change detectioninput-hash + --since affected (nx, moon)content-addressed per-package build cache, no --sinceNo workspace-aware affected slicing
Local cachingcontent-addressed task-output cache (turborepo)content-addressed computeBuildID package cache (real, good)Per-package, not workspace-aware; configs produce distinct ids → redundant recompiles
Remote cache / executionremote cache (turborepo) → REAPI (bazel)none — local $DUB_HOME onlyNo remote cache, no REAPI; CI cannot pull a teammate's artifacts

The deficits cluster into four addressable themes, which are exactly the milestone structure of the proposal:

  1. Structural workspace layout — a [workspace] block (root-package and virtual modes) with glob member discovery and a unified root dub.selections.json, borrowing the cargo model. (Proposal Milestone 1.)
  2. Config & dependency inheritance — a workspace:-style local cross-reference protocol and a central [workspace.dependencies] registry, eliminating the depth-sensitive path="../.." wiring and version drift. (Milestone 2, modelled on yarn-berry + cargo.)
  3. Topological multi-member task routing — a dub build --workspace / dub test -p <member> loop using dub's existing resolver topology, with filter ergonomics and -j/--parallel concurrency, inspired by yarn workspaces foreach. (Milestone 3.)
  4. Change tracking & remote caching--since <ref> git-diff slicing and a workspace constraints engine, the nx/lerna affected model. (Milestone 4.)

The honest summary: every multi-package capability in Sparkles today — looping tests across the five members, keeping expected/silly aligned, building dependents after their local libraries — is bolted on outside dub (in apps/ci, in the Nix flake, in hand-maintained path= strings and per-member lockfiles). dub has the resolver and the cache; it lacks the workspace. The cross-tool evidence above shows the consensus shape that workspace should take, and the proposal is the milestoned plan to build it.


Sources

  • Cross-cutting concept doc: concepts (workspace topology, isolation, task DAG, caching, lockfiles — the layered vocabulary this synthesis re-cuts by axis).
  • The system under improvement: dub baseline (read from the dlang/dub source at v1.42.0-beta.1) and the proposal it bridges into.
  • Per-tool primary sources are cited in each deep-dive; this page synthesises the 44-tool catalog.
  • Structural model: async-io's comparison (the synthesis-doc pattern this imitates).