component-lib@0.3.2, any engineer building it will
use exactly that version, producing identical results regardless of when or where
the build runs.
Principles
The package system is built on three principles: compatibility, repeatability, and cooperation. These principles reinforce each other and guide every design decision.Compatibility
The meaning of a package version should not change over time.
- Patch versions (0.3.1 → 0.3.2): Metadata changes only. No changes to connectivity, layout, or electrical behavior. Examples: updating documentation, fixing a typo in a property value, adding manufacturer aliases.
- Minor versions (0.3.x → 0.4.0): New functionality that doesn’t break existing designs. Examples: adding optional pins, new configuration options, additional footprint variants.
- Major versions (0.x → 1.0): Breaking changes that may require design updates. Examples: changing pin definitions, restructuring interfaces, removing deprecated features.
Repeatability
The result of a build should not change over time.
pcb.toml manifests. MVS selects the minimum version that
satisfies the dependency graph. pcb sync records that selected graph in
manifests, including pseudo-versions for branch and commit dependencies, so
locked and offline builds reuse immutable versions.
Cooperation
To maintain the package ecosystem, we must all work together.
- Follow semantic versioning conventions
- Test changes before releasing new versions
- Avoid breaking changes within a semver family
- Communicate clearly when breaking changes are necessary
- Gradual migration: Multiple major versions can coexist in a single build, allowing dependent packages to upgrade at their own pace
- Publishing workflow: The
pcb publishcommand validates packages before release, catching common mistakes early
Workspace Package Discovery
Workspace package discovery is implicit. Starting from the workspace root,pcb
looks up to eight directory levels deep and treats each descendant directory with a
pcb.toml as a workspace package.
[workspace].exclude controls discovery:
pcb neither discovers a package there nor
searches below it. The tool also always skips generated/cache directories such as
.git, .pcb, vendor, target, node_modules, and fork.
Older manifests may still contain [workspace].members; the field is parsed for
compatibility but ignored. pcb sync removes it when it rewrites the workspace
manifest.
Coexisting Versions
Sometimes breaking changes are necessary. A component library might need to restructure its pin definitions, or a shared component library might change how interfaces are represented. When this happens, packages that depend on the old API cannot immediately upgrade—they need time to adapt. The package system supports multiple major versions in a single build. If your dependency graph contains:component-lib@0.3.x and component-lib@1.0.x, each used by the packages that require them.
Types and values from different major versions are distinct and cannot be mixed.
This approach has two key benefits:
No flag day upgrades. Teams can migrate package-by-package over weeks or months,
validating each migration before moving to the next. The old version remains
available as long as any package needs it.
Diamond dependencies work. If Board A uses Library X (component-lib 0.3) and Library Y
(component-lib 1.0), both libraries function correctly. Library X sees component-lib 0.3 types,
Library Y sees component-lib 1.0 types, and the board sees whatever version it declares.
The limitation is that types from different major versions may be incompatible. A
Foo from component-lib 0.3 cannot be passed to a function expecting a component-lib 1.0 Foo.
Major versions represent potential breaking changes, and the system cannot guarantee
compatibility across that boundary.
Minimal Version Selection
Our approach to dependency resolution is heavily inspired by Go modules, which pioneered Minimal Version Selection. Most package managers (npm, Cargo, pip) use SAT solvers to find the newest versions that satisfy all constraints. This approach has a fundamental problem: the result depends on what versions exist at resolution time. If a new version is published between two builds, the resolver might select it, changing your dependencies without any change to your code. SAT solvers also introduce complexity. When constraints conflict, the solver must backtrack and try alternative versions. This can be slow, and when it fails, the error messages are often inscrutable—the solver tried thousands of combinations and none worked. Minimal Version Selection (MVS) takes a different approach: instead of finding the newest versions that work, find the minimum versions that each package explicitly requires. This simple change has profound consequences.How MVS Works
Consider this dependency graph:component-lib >= 0.3.0. The regulator requires component-lib >= 0.3.2.
MVS selects component-lib@0.3.2—the minimum version that satisfies both constraints.
Even if component-lib@0.3.9 exists and is compatible, MVS chooses 0.3.2 because that’s
what the dependencies explicitly require. The existence of newer versions is
irrelevant.
Why Minimum, Not Maximum?
This seems counterintuitive. Wouldn’t you want the newest compatible version with all its bug fixes? The key insight is that the author tested with specific versions. When the regulator author published version 1.0.0, they tested it withcomponent-lib@0.3.2.
They know it works with that version. They hope it works with 0.3.9, but they
haven’t tested it. By selecting the minimum, MVS chooses the versions that were
actually tested together.
If you want newer versions, you explicitly upgrade:
Properties of MVS
Deterministic. The selected versions depend only on the dependency graph, not on what versions exist remotely. Two engineers building the same code get the same versions, even if one builds months later when new versions exist. No backtracking. MVS never needs to “try” a version and backtrack if it fails. It computes the answer directly: for each package, find the maximum of the minimum versions required by all dependents. Understandable. You can compute the result by hand. For each package, look at every place it’s required and take the highest version. That’s it. Fast. Linear in the size of the dependency graph. No exponential search space.Resolution Algorithm
- Seed: Collect direct dependencies from all workspace packages. Group by (package path, semver family). Initialize each family to the highest version explicitly required.
- Discover: Fetch manifests for selected versions. For each transitive dependency, if it requires a higher version within an existing family, upgrade. Repeat until no changes (fixed point).
- Build closure: Trace the dependency graph from workspace roots using final versions. This filters out any versions that were superseded during discovery.
Import Paths as Identity
Import paths serve as globally unique package identifiers:pcb.toml
manifest declares which version of each package to use:
- Import statements remain stable across version upgrades
- Version changes are localized to manifest files
- The same source file can work with different versions in different contexts
Hydrated Manifests
Workspaces store resolved dependency state inpcb.toml. pcb sync updates:
[dependencies]: direct dependencies the package imports or explicitly owns.[dependencies.indirect]: the tool-managed MVS closure needed to build it.
@1 and @9 suffixes are compatibility lanes. They allow multiple
incompatible versions of the same package path to coexist while keeping the
selected version exact.
Do not edit [dependencies.indirect] by hand. Commit hydrated pcb.toml files.
KiCad-Style Libraries
KiCad symbol, footprint, and 3D model relationships are configured at workspace root with[[workspace.kicad_library]] entries. Workspaces default to KiCad
9.0.3 and 10.0.3; stdlib generics use KiCad 10.0.3.
parts is optional. When set, the file is fetched alongside the selected symbols repo and parsed as a virtual pcb.toml containing parts = [...] entries for manifest-backed symbol→part inheritance. It supports the same {repo}, {repo_name}, {version}, and {major} placeholders as http_mirror.
http_mirror is optional and supports {repo}, {repo_name}, {version}, and {major} placeholders.
KiCad aliases in .zen files are reconciled by pcb sync. Transitive KiCad
dependencies are written to [dependencies.indirect] using lane-qualified keys:
@kicad-symbols, @kicad-footprints).
Vendoring ([workspace].vendor)
Vendoring policy is controlled by the root workspace manifest:
pcb buildandpcb publishuse the same[workspace].vendorpatterns.pcb vendorwithout--alluses[workspace].vendor.pcb vendor --allvendors everything.
Workspace Name ([workspace].name)
Workspace manifests can override the Diode workspace name used by board release uploads:
name is omitted, pcb publish derives the workspace name from the first path segment of [workspace].repository. For example, anything.com/XYZ/boards/MyBoard uses XYZ.
Endpoint ([workspace].endpoint)
Workspace manifests can override the Diode host suffix used by CLI commands that talk to Diode services:
endpoint = "diode.computer"resolves app/API URLs underapp.diode.computerandapi.diode.computer.- This applies to workspace-aware commands such as
pcb auth,pcb bom,pcb publish,pcb preview, and routing flows. - Authentication is scoped per resolved endpoint, so logging into one workspace endpoint does not overwrite tokens for another.
BOM Matching ([workspace.bom])
Workspace manifests can opt into strict BOM matching for pcb bom availability lookups:
pcb bom asks the BOM service to require exact MPN matches. The default is false, which preserves the legacy fuzzy matching behavior.
Registry Search Scope
Registry-backedpcb search defaults to the public Diode registry and registries in the workspace configured by [workspace].repository.
pcb search --registry github.com/diodeinc/registry ...overrides the default scope for that invocation.- Repeat
--registryto search more than one registry.
Pseudo-Versions
Sometimes you need to depend on unreleased code—a bug fix that hasn’t been tagged, or a feature branch under development. Pseudo-versions provide a way to reference specific commits while maintaining version ordering. Format:v<base>-0.<timestamp>-<commit>
pcb sync writes the resolved pseudo-version back to the package manifest with
the full 40-character commit hash.
The base version (0.3.15) is the next patch version after the most recent tag
reachable from that commit. This ensures pseudo-versions sort correctly: they’re
newer than the tag they follow but older than the next official release.
If the package has never been tagged, pseudo-versions start in the 0.1.1
family (for example 0.1.1-0.<timestamp>-<commit>), one patch above the
initial unpublished release version 0.1.0.
Pseudo-versions participate fully in MVS. If one package requires component-lib@0.3.14
and another requires the pseudo-version above, MVS selects the pseudo-version
(it’s higher). This enables testing unreleased fixes without breaking version
resolution.
Use pseudo-versions sparingly. They represent unreleased, potentially unstable
code. For production builds, prefer tagged releases.
Commands
pcb migrate
Runs project migrations using the latest stablepcbc toolchain, regardless of
the workspace’s current pcb-version lane. After all migrations succeed, the
command updates [workspace].pcb-version in pcb.toml to the target toolchain
lane.
pcb sync
Reconciles imports and hydrates package manifests. Run this after adding or removing imports or changing dependency versions.[workspace].vendor.
pcb add
Adds or upgrades a direct dependency for the package in the current directory.pcb add rewrites the direct dependency entry and rehydrates the package’s
dependency closure.
pcb build
Builds a board or workspace package.--locked checks that the hydrated state is sufficient and does not rewrite
pcb.toml.
pcb list
Lists read-only package dependency information.pcb list -m -u must be run from a package directory. It reports direct remote
dependencies only, showing the latest stable version in the same compatibility
lane and the latest newer breaking lane when available. It does not update manifests.
pcb update
pcb update is deprecated. Use pcb add -u instead.
pcb publish
Publishes packages by creating annotated git tags. Discovers which packages have changed since their last published version and tags them.- No version tag exists (unpublished)
- Content hash differs from the published tag
- Manifest (
pcb.toml) hash differs from the published tag
- Unpublished: starts at
0.1.0 - Published packages: apply the requested semver bump (
patch,minor, ormajor) --bump=infer: infers each package bump from conventional commits since its last tag, then raises dependents to at least the highest bump among published internal dependencies-y/--yes: skips the final package publish confirmation prompt
pcb.toml files are updated.