Workspaces
Curie inherits Cargo's workspace model. One root Curie.toml lists members; each member is a self-contained project; the root inherits shared config down to every member.
Layout
monorepo/ ├── Curie.toml # workspace root — no [application] / [library] ├── app/ │ ├── Curie.toml # declares [application] │ └── src/... ├── core/ │ ├── Curie.toml # declares [library] │ └── src/... └── utils/ ├── Curie.toml # declares [library] └── src/...
Root configuration
The root Curie.toml declares members and any config that should be inherited:
[workspace] members = ["app", "core", "utils"] [java] releaseVersion = "21" [bom-imports] "com.fasterxml.jackson:jackson-bom" = "2.17.2" [test-bom-imports] "org.junit:junit-bom" = "5.11.0"
Inheritance rules
[java]— applies to every member that does not declare its own.[bom-imports]/[test-bom-imports]— merged with each member's own imports. Member entries take precedence on a name collision.[repositories]— also inherited; member entries appended.- Boolean flags (
[java] enablePreview,[spock] enabled) — a member's explicit value wins, includingfalse. A member can setenablePreview = falseor[spock] enabled = falseto opt out of a flag the workspace turned on.
Members declare dependencies with empty version strings ("") to defer to the inherited BOMs:
[application] name = "app" version = "0.1.0" mainClass = "com.example.App" [dependencies] # resolved from the workspace's jackson-bom "com.fasterxml.jackson.core:jackson-databind" = "" [workspace-dependencies] core = { path = "../core" } utils = { path = "../utils" }
Topo-sorted builds
Run curie build from the workspace root and Curie computes a topological order over [workspace-dependencies], then builds each member in dependency order. Upstream target/classes directories — not just their JARs — are placed on downstream compile classpaths, so downstream tests don't have to wait for upstream JAR packaging to complete.
Workspace . build (3 members) [1/3] utils Compile 3 source file(s) Tests ✔ 8 tests successful [2/3] core Compile 5 source file(s) Tests ✔ 12 tests successful [3/3] app Resolve deps 7 JAR(s) Compile 4 source file(s) Tests ✔ 7 tests successful Done target/app-0.1.0.jar
Targeted builds
Run curie build from a member directory and Curie still respects topology — it builds the member plus its transitive workspace dependencies, but skips siblings the target doesn't depend on. This is the everyday loop in a large monorepo: edit one package, build only what's affected.
List members
curie list from the root prints the member roster and their declared roles (application vs library):
Workspace . (3 members)
app application v0.1.0
core library v0.1.0
utils library v0.1.0
Nested workspaces
A workspace member can itself be a workspace. Curie recursively flattens nested workspaces into a single member list, and configuration inheritance cascades through all levels — from the outermost root, through each intermediate workspace, down to every leaf member.
# root/ — level 0 [workspace] members = ["core-lib", "services"] # root/services/ — level 1 [workspace] members = ["greeter-lib", "apps"] # root/services/apps/ — level 2 [workspace] members = ["hello-app"]
Workspace dependencies can cross nesting levels. When curie build runs from the root, all leaf members are visible and built in topo order.
Running from any directory: a command invoked from anywhere inside the tree resolves to the outermost enclosing workspace — so the full inheritance chain applies and cross-level workspace-dependencies resolve. From a leaf member, only that member and its transitive deps build. From an intermediate sub-workspace directory (e.g. services/), Curie builds only that subtree's members plus their transitive deps — for example greeter-lib + hello-app, pulling in core-lib as a dependency, while leaving unrelated root-level siblings untouched.
Duplicate detection: if the same project directory appears more than once (directly or through nesting), Curie errors immediately. This also catches cycles — a nested workspace that references its parent triggers the duplicate check.
curie list shows a focused tree rooted at the outermost workspace, highlighting the current project/workspace with ← current. Nodes outside the current subtree are pruned unless they are workspace dependencies (or their ancestors). Each dependency target shows a reverse required by annotation listing its requirers, with paths relative to the target's parent workspace. curie list --all shows the complete tree including unrelated siblings.
examples (workspace)
└─ nested-workspace-demo (workspace)
├─ core-lib library v0.1.0
│ (required by: services/greeter-lib, services/apps/hello-app)
└─ services (workspace) ← current
├─ greeter-lib library v0.1.0
│ (required by: apps/hello-app)
└─ apps (workspace)
└─ hello-app application v0.1.0
Parallel build and test
When a workspace has more than one member to build (or test/clean), Curie runs them in parallel — bounded by --jobs (default: CPU count) and respecting the dependency DAG so no member starts before its upstream dependencies are ready.
# Build all members across 8 cores. curie build -j8 --no-docker --no-native # Run tests in parallel (default: all available cores). curie test # Clean everything in parallel. curie clean -j4
Each member's external-command output (javac, kotlinc, junit) is captured on a PTY, buffered per-member, and written to stdout in contiguous blocks prefixed with the member name — for example string-utils | ✔ isBlank_empty_returns_true(). Raw output (including ANSI color codes) is also saved to target/build.log (or test.log, clean.log) inside each member's directory.
If any member fails, Curie stops dispatching new jobs (fail-early) but lets already-running jobs complete. The final error lists every failed member.
Single-member invocations (curie --project services/greeter-lib build where greeter-lib has no dependencies) keep today's direct output with no prefix overhead.
Parallel formatting
curie fmt at the workspace root fans out across every member in parallel. palantir-java-format is resolved once, then the JAR list is shared across the worker threads so there's no contention on the local cache.