Semifold
Workspace

Package discovery

Understand how Semifold builds one workspace from different manifests and where stable configured package identities come from.

Each ecosystem adapter discovers its own packages, then Semifold combines those results into one workspace graph. Changeset validation, version propagation, file edits, and publish ordering all use this graph.

What discovery returns

Every discovered package provides at least:

  • its manifest name and current version;
  • its ecosystem and repository-relative path;
  • whether it can be published to an external registry;
  • internal dependencies that can be recognized from its manifest.

smif init uses these facts to create the initial [packages] configuration. smif config sync compares existing configuration with the latest discovery result. An adapter does not decide repository-wide versions, write files directly, run publish commands, or access GitHub.

Discovered publishability is the default. Package configuration may override it with optional publish = true or publish = false; when omitted, Semifold keeps the marker inferred by Rust, Node.js, or a plugin. This override controls Semifold's registry flow only and does not edit the manifest.

Built-in discovery boundaries

EcosystemPrimary manifest or version sourceDiscovery entry pointComplete rules
RustCargo.tomlA single package or Cargo workspace membersRust workspaces
Node.jspackage.jsonRoot package, workspaces, or pnpm-workspace.yamlNode.js workspaces
Pythonpyproject.toml, setup.cfg, or a source version fileRoot plus packages/*, libs/*, and apps/*Python workspaces
C++CMakeLists.txt with project(... VERSION ...)Root project and statically reachable add_subdirectory(...) entriesC++ workspaces

This table only helps select an entry point. Each ecosystem page explains dynamic versions, private packages, dependency categories, and static-analysis limits.

PackageId versus manifest name

A package table key is its stable PackageId:

[packages.rust-core]
path = "crates/core"
resolver = "rust"

The manifest name is the native Cargo, npm, PyPI, or CMake name. Discovery proposes it as an initial ID, but configured identity is stable. After configuration exists, Semifold uses rust-core for this package even if its directory or manifest name later changes.

Every PackageId in one configuration must be globally unique. Manifest names must also be unique within an ecosystem so manifest dependencies have one unambiguous target. Different ecosystems may use the same manifest name. On the first smif init, Semifold automatically adds the ecosystem prefix to those packages. For example, Rust and Node.js packages both named shared become rust-shared and nodejs-shared:

[packages.rust-shared]
path = "crates/shared"
resolver = "rust"

[packages.nodejs-shared]
path = "packages/shared"
resolver = "nodejs"

Packages without a name collision still use the manifest name directly. If another package already occupies a prefixed ID, initialization appends a numeric suffix in stable discovery order, such as rust-shared-2 or rust-shared-3. Duplicate manifest names within one ecosystem still report DuplicatePackageId, because renaming the configured IDs cannot resolve ambiguous manifest dependencies.

Automatic prefixing only applies when creating the initial configuration. Existing configuration preserves stable IDs by ecosystem and path, and config sync does not rename configured packages. If a later discovery finds multiple unconfigured packages with the same name across ecosystems, config sync still reports a conflict so a maintainer can choose their IDs explicitly.

Matching names in two ecosystems never imply a dependency. A cross-ecosystem edge must use depends-on to reference a stable ID.

Plugin-defined ecosystems

Built-in adapters are not a closed list. A JavaScript plugin can implement the same three workspace operations:

  1. discover packages;
  2. inspect the current facts for a configured package;
  3. return candidate file edits for target versions.

Plugin results enter the same workspace graph and receive the same identity, unknown-dependency, cycle, and file-edit validation. A plugin does not publish packages directly; registry pre-checks and commands remain in [resolver.<ecosystem-id>] configuration. See Plugin overview.

What a discovery failure means

The following problems stop workspace loading instead of being silently ignored:

  • a configured path leaves the project root or no longer contains the expected manifest;
  • duplicate manifest names within one ecosystem are ambiguous;
  • depends-on references an unknown PackageId;
  • internal dependencies form a cycle;
  • a manifest exists but its name, explicit version, or structure is invalid.

Fix the manifest or configuration and rerun smif config sync --check. If the issue is an ecosystem discovery boundary, use the matching page to decide whether to adjust repository structure, configure an explicit relationship, or write a plugin.

On this page