Skip to main content
The project manifest declares which facets a project depends on and how it wants their contributions materialized. It is the input every install reads, and the record a team commits: a fresh clone plus facet install reproduces the full set.
This is distinct from the facet manifest (facet.json), which lives inside a facet and declares its identity and assets. A project has one facets.json; every facet ships its own facet.json.

Example

facets.json

Fields

number
required
The schema version. The current version is 0.2, which added the servers override group. Version 0.1 and a document that omits the field entirely are still read.This axis is independent of the archive and lockfile version axes.
map<name, entry>
required
One entry per declared facet, keyed by facet name.

Entry values

An entry takes one of two forms. Both express the same source; the expanded form additionally carries materialization intent.
string
The source specifier as the user wrote it. This is the canonical form for a facet with no overrides.
{ source, materialization }
source carries the same specifier string. materialization records dispositions grouped by contribution kind (skills, agents, commands, and under 0.2, servers) and keyed by authored name.An expanded entry MUST declare at least one override, because the compact form already expresses none.
A consumer reading facets.json MUST handle both forms. Treating every value as a string is the failure this schema is shaped to make obvious rather than silent. The specifier’s meaning depends on the source kind:
  • Registry: a bare version specifier such as 1.2.3, 1.*, or latest. The facet name lives in the key.
  • Git: a self-contained URL or github: shorthand with an optional ref. The ref here is what the user requested; the resolved commit lives in the lockfile.
  • Local: a directory path that MUST resolve inside the project tree.
Accepted specifier forms are documented at facet add. Specifier grammar is not validated at load: a malformed specifier fails the install, not the read.

Validation

Version dispatch is by exact equality, never by shape.
  • manifestVersion absent selects the legacy schema. Every entry value MUST be a string, and an expanded entry is rejected rather than promoted.
  • Exactly 0.1 selects the preceding schema, which supports the three text-asset groups and MUST reject a servers group.
  • Exactly 0.2 selects the current schema. The string "0.2" is not the number 0.2 and MUST be rejected.
  • Anything else is an unsupported version failure naming the observed and supported versions.
There is no cross-version fallback. A document that violates its selected schema MUST NOT be retried under another, which would let a typo silently downgrade a manifest. Duplicate JSON members MUST be rejected before dispatch, since last-member-wins could otherwise discard one of two conflicting decisions for the same facet. Beyond shape, a loader validates what it can prove locally:
  • An override group MUST be one the selected version recognizes. Any other key is rejected, including beside a valid group, because accepting it would silently drop the intent it carries.
  • An override key is checked for path safety, so a project can still address a legacy multi-segment authored name.
  • An alias value for a text asset MUST satisfy the single-segment asset-name grammar. A server alias MUST satisfy the stricter server name grammar, which is what keeps a server key portable across the JSON, JSONC, and TOML documents adapters write it into. Neither is normalized.
  • An explicit { "kind": "authored" } override MUST be rejected, because authored materialization is the absence of an override.

Write rules

  • The manifest MUST NOT be written ahead of the install, and a failed operation MUST leave it untouched.
  • Every successful non-frozen write stamps the current manifestVersion, migrating a legacy or 0.1 document in the same transaction. There is no separate migration command.
  • Entry form follows content: an entry with overrides is written expanded, and one whose last override is removed collapses back to a compact string.
  • Comments survive. Writes mutate the document in place rather than re-serializing it, including inside a materialization block, so a note explaining why you aliased something outlives every later install.
What gets written per specifier shape is defined by the manifest write policy.
A frozen install never writes the manifest, so a legacy or 0.1 document stays as it is, and stale overrides are blocking drift rather than pruned.