# Packages and their kinds {#root}

@status:doc/work @audience:user,author

[p01] Everything vibe installs is a package: a folder with a small description file and the text or tools it delivers. Packages come in eight kinds, and the kind tells you what a package is for before you open it: a way of working, a feature, a technology, a tool, a language guide, an agent server, documentation, or an application.

[p02]
```sh
vibe list --path hello-vibe
```

```output
KIND  NAME  VERSION  ROLE     BOOT SNIPPET
flow  wal  1.0.0    package  —

1 package installed.
```

## What a package is {#a-package}

[p03] A package is a project made installable. It has the same layout as a project, with its own `vibe.toml` and its own `vibevm/vibespecs/`, and it may carry code beside them. When a project installs it, the package's published tree is copied verbatim into the project's dependency tree; nothing is extracted, rewritten or merged.

> [p04] **L4 — packages too.** Every package root mirrors the same
> layout (`vibevm/vibespecs` inside the package instead of `spec/`);
> materialisation mirrors package layout into the slots, boot-snippet
> paths and INDEX targets follow.
>
> <spec://org.vibevm.core/vibevm/common/PROP-052#PACKAGES-CARRY-THE-LAYOUT-TOO>

[p05] A package is named by a *[coordinate](../glossary/index.xml#coordinate)*: a group, a slash, and a name, as in `org.vibevm.world/wal`. The group looks like a reversed domain name and says who publishes; the name is unique within the group. A version completes the address when one is needed: `org.vibevm.world/wal@1.0.0`. The kind is not part of the name. It may be written as a prefix on the command line, `flow:org.vibevm.world/wal`, and then vibe checks that the package really is of that kind.

> [p06] **Decision.** Package identity becomes `(group, name, version, content_hash)`. `kind` **leaves the identity tuple**.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-008#IDENTITY-TUPLE>

[p07] The group is a claim, not a credential: nothing checks that the publisher of `com.google/x` owns that domain, and nothing ever will, because vibe has no central verifier to ask. The name is unique within its group, so the coordinate alone is the identity. On the command line the kind prefix and the group are both optional; in a [manifest](../glossary/index.xml#manifest) the coordinate is always written in full. In a [registry](../glossary/index.xml#registry) the repository is named by joining group and name with a dot, `org.vibevm.world.wal`, which is itself a valid reversed domain name.

> [p08] **Decision (owner ruling, 2026-08-13).** A `group` is a **claim, not a credential**. Nothing verifies that the author of `com.google/x` owns `google.com` — and nothing ever will: vibevm is decentralised and has no central verifier to delegate to (Maven's central domain verification is the model we deliberately do not inherit). The claim's grammar is domain-shaped (§2.1); its **semantics carry no domain-ownership assertion**.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-008#GROUP-IS-A-CLAIM>

> [p09] `name` becomes unique **within a `group`** (was: within a `kind`, `VIBEVM-SPEC.md` §7.1). `(group, name)` is therefore unique on its own — `kind` is no longer needed to disambiguate.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-008#NAME-UNIQUE-IN-GROUP>

> [p10] **Decision.** The pkgref grammar gains an optional `group` segment and makes the `kind` prefix optional:
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-008#PKGREF-GRAMMAR>

> [p11] **The short form is CLI-only sugar.** It is never written to a manifest (§2.6).
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-008#SHORT-CLI-ONLY>

> [p12] **Decision:** `[<kind>:]<group>/<name>@<version>` — identity is **qualified** since M1.19 ([PROP-008 §2.2](../modules/vibe-registry/PROP-008-qualified-naming.xml#identity)); the unqualified `<kind>:<name>@<version>` of `VIBEVM-SPEC.md` §7.1 is CLI sugar that resolves once, at the human boundary.
>
> <spec://org.vibevm.core/vibevm/common/PROP-000#IDENTITY-FORM>

> [p13] `naming = "fqdn"` maps a pkgref to the repository name `<group>.<name>` (`org.vibevm.world/wal` → `org.vibevm.world.wal`) — the composite is itself a valid reversed FQDN, and that is the point of the ruling. **Owner ruling 2026-08-13** («убери подчёркивания везде, чтобы получились настоящие FQDN с доменными правилами»), superseding the 2026-05 `_`-joiner decision recorded in this unit's earlier text. The split stays deterministic without a charset-excluded joiner: the name is a **single dot-free LDH label** (`validate_package_name`), so the **last dot** is always the boundary — parse back by taking the last label as `name`, the rest as `group`. The old rationale's premises are both gone: `_` was then legal inside groups (no longer — §2.1 LDH), and the composite was not required to be a domain (now it is). *Considered and rejected:* keeping `_` (the composite is not even formally a domain; and a `_`-joined name contradicts the LDH ruling the halves now obey). *Migration:* live `_`-joined repositories in `vibespecs` (M0/M1 scale) rename to the dot form as a follow-up of the 2026-08-13 landing — pre-public and cheap, and GitHub redirects renamed repositories; the §3 history below records the `_`-era as it happened and is not rewritten. *Revisit:* a hosting provider that forbids `.` in repository names appears in the registry set — then that provider's adapter gets its own naming value, never a silent re-join.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-008#JOINER-UNDERSCORE>

[p14] Wherever a package is named in a project, the full coordinate stands: `group/name` in a requirement, `group.name` as a repository name, `group/name` as the first segment of a `spec://` address. Short names survive only as what a person types once at the command line.

> [p15] A package address MUST carry its full coordinate — `group` **and** `name` — in **every** occurrence across the project:
>
> <spec://org.vibevm.core/vibevm/common/PROP-029#ADDR-LAW>

> [p16] `[<kind>:]<group>/<name>`
>
> <spec://org.vibevm.core/vibevm/common/PROP-029#CARRIER-PKGREF-FORM>

> [p17] `<group>.<name>` — `/` is illegal in a repo name; the name is the last label
>
> <spec://org.vibevm.core/vibevm/common/PROP-029#CARRIER-REPO-NAME-FORM>

> [p18] `<group>/<name>` — the name is the first path segment
>
> <spec://org.vibevm.core/vibevm/common/PROP-029#CARRIER-SPEC-URI-FORM>

> [p19] Short or bare names survive only as a one-time human CLI input, resolved to the qualified form at the boundary (PROP-008 §2.6).
>
> <spec://org.vibevm.core/vibevm/common/PROP-029#ADDR-SHORT-NAMES>

## The eight kinds {#the-kinds}

[p20]
| Kind | What it delivers | Example |
| --- | --- | --- |
| `flow` | a way of working: commit rules, session notes, review conventions; usually a [boot snippet](../glossary/index.xml#boot-snippet) the agent reads every session | `org.vibevm.world/wal` |
| `feat` | a description of what to build, without saying how | a welcome page, e-mail sign-in |
| `stack` | a technology context that says how a feature is built with it, or a bundle of a family's members at one version | `org.vibevm.ai-native/rust-ai-native` |
| `tool` | a script or utility a build step can invoke | a formatter wrapper |
| `lang` | guidance on how to write in a language or notation | `org.vibevm.ai-native/rust-ai-native-lang` |
| `mcp` | a server an agent talks to, built from the package's own code | `org.vibevm.ai-native/rust-ai-native-mcp` |
| `doc` | documentation of other packages: read, never installed | `org.vibevm.core/vibevm-docs` |
| `app` | a standalone product with its own deployment | `org.vibevm.doc/web` |

[p21] The set is closed and grows only by an amendment to the [specification](../glossary/index.xml#specification); a [manifest](../glossary/index.xml#manifest) with an unknown kind is rejected rather than guessed.

> [p22] `kind ∈ {flow, feat, stack, tool, mcp, lang, doc, app}` — eight kinds; `mcp` shipped with [PROP-027](../modules/vibe-mcp/PROP-027-mcp-packages.xml); `doc` and `app` admitted by [PROP-057](PROP-057-documentation-packages-and-site.xml) through the `VIBEVM-SPEC.md` §4.1 amendment of 2026-09-12 (pending the owner's ratification at the merge of the docs-2026-09 branch; the code learns the two kinds in that campaign's phase 2). (§Invariants `INV-VOCABULARY` in this file carries the same list.)
>
> <spec://org.vibevm.core/vibevm/common/PROP-000#KIND-SET>

[p23] The kind is metadata about the package, not part of its identity: it decides where content is placed, what the `--kind` filter of `vibe list` and `vibe search` shows, and whether a kind-prefixed name is accepted. Two packages of different kinds may not share a coordinate, because the coordinate alone is the identity.

> [p24] **Decision.** `kind` (`flow` / `feat` / `stack` / `tool`) stays a **mandatory `[package]` field** but is now a pure attribute — it identifies nothing and names nothing.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-008#KIND-METADATA>

[p25] An `app` differs from a `tool` mechanically: a tool lives in a project and runs through `vibe bin exec` by the lock file, while an app runs in no consumer project and is built and deployed on its own.

> [p26] The boundary with `tool` is mechanical: a `tool` lives in a project and runs through `vibe bin exec` by the lock file; an `app` runs nowhere in a consumer project and is built and deployed on its own.
>
> <spec://org.vibevm.core/vibevm/common/PROP-057#KIND-APP-VS-TOOL>

## Families and companions {#families}

[p27] Some [capabilities](../glossary/index.xml#capability) arrive as several packages that share a name stem: the language guide, the server that serves its tools, and a small bundle that pins both at one version. This is a *family*. Requiring the bundle installs the family, and a change to any member bumps every member to one shared version, so the pieces can never drift apart.

> [p28] Within a family the members move in **unison**: a content change to any member
>   bumps EVERY member of that family to one shared version, and the aggregator's
>   version IS that family version.
>
> <spec://org.vibevm.core/vibevm/common/PROP-028#UNISON-LAW>

[p29] The bundle itself is the smallest package there is: a manifest and a README, no code and no [boot snippet](../glossary/index.xml#boot-snippet). Its one job is to name the family's members at one version.

> [p30] **`<family>`** — the *aggregator*. `kind = "stack"`, content-minimal: a
>   `vibe.toml` and a `README.md`, and nothing else — no code, no boot snippet,
>   no `specmap.toml` / `conform.toml`. Its whole job is to name the family's
>   members at one resolved version set through exact `=X.Y.Z` pins in
>   `[requires]`. Requiring the aggregator installs the family.
>
> <spec://org.vibevm.core/vibevm/common/PROP-028#ROLE-AGGREGATOR>

[p31] Documentation is the exception. A package's manual is its *[companion](../glossary/index.xml#companion)*, named with the suffix `-docs` in the same group, and it keeps its own version line. A fixed typo in the manual does not release the tools, and a new tool version does not demand a new manual. The manual says which versions of its [subject](../glossary/index.xml#subject) it describes, and the site picks the newest manual that fits.

> [p32] **`<family>-docs`** — the *documentation companion* (`kind = "doc"`,
>   [PROP-057 §3](PROP-057-documentation-packages-and-site.xml)): the
>   official-by-default documentation of the family's subject, in the subject's
>   group. Unlike the three code roles it is a **companion, not a member**: it
>   is never pinned by the aggregator, it does not take part in the family's
>   unison (§2.2), it keeps its own version line, and it states compatibility
>   with its subject through the version constraint of its `[[documents]]`
>   table. Its translations follow the same companion form, one package per
>   language, named `<family>-docs-<lang>` with a lower-case BCP-47 tag
>   (`rust-ai-native-docs-ru`). Amended 2026-09-11.
>
> <spec://org.vibevm.core/vibevm/common/PROP-028#ROLE-DOCS>

## How a package lands on disk {#on-disk}

[p33] `[package].materialization` says how a package arrives in a consumer's tree. `copy` is the default and all an ordinary package needs; `hardlink` is the same content sharing bytes with the [store](../glossary/index.xml#store). Both are vendored: the folder is committed with the project and restores offline from it. `in-place` keeps a live git checkout with its own `.git`, ignored by git and restored by a fresh clone at the locked commit, so it needs the network and a [git source](../glossary/index.xml#git-source). Anything destructive on such a slot, an uninstall, a forced reinstall or a version switch, asks first, or needs `--force` when nobody can answer.

> [p34] `[package].materialization` selects how the package lands on disk:
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-022#MODE-FIELD>

> [p35] `copy` is the default and the only mode an ordinary package needs. (It was named `snapshot` until the owner's 2026-08-13 terminology ruling reserved that word for the unfrozen version — PROP-044 §2b; the legacy spelling is refused with the rename recipe, never aliased.)
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-022#SNAPSHOT-DEFAULT>

> [p36] **`copy` / `hardlink`** are vendored — the slot is committed into the
>   project's git and is offline-reproducible from it (a `hardlink` slot's bytes
>   are materialised into git on `git add` like any file); a `copy` slot trivially so.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-022#VENDORED-COPY-MODES>

> [p37] **`in-place`** is **not** vendored — the slot (a nested `.git` plus possibly
>   millions of files) is `.gitignore`d in the project; restoration is a re-clone
>   at the lockfile's `resolved_commit`. The honest trade: `in-place` packages
>   need the network to restore, where `snapshot` packages do not.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-022#IN-PLACE-NOT-VENDORED>

> [p38] **Requires a git source.** Incremental update and `git clean` reset both need
>   git; a non-git source has no `in-place` story (§4).
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-022#IP-REQUIRES-GIT>

> [p39] An `in-place` slot may be a multi-hour download. Any **destructive** operation
>   on it — `uninstall`, `reinstall --force`, a version switch that requires a
>   re-clone, or slot removal — must be confirmed: interactively a `y/n`, and in a
>   non-interactive run it requires an explicit flag (`--force`) or it **aborts**
>   rather than silently deleting an expensive resource.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-022#DESTRUCTIVE-CONFIRM>

## Edge cases and rules {#edge-cases}

[p40] Changing a package's group or name creates a new package, not a rename: versions do not carry across, and old coordinates are never reused for different content.

> [p41] Changing a package's `group` is a new package, not a rename — same discipline as changing `name`.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-008#GROUP-CHANGE-NEW-PACKAGE>

[p42] A documentation package cannot be installed into a project. `vibe install` refuses it and names the command that fetches it for reading instead.

> [p43] `vibe install` MUST refuse a `doc` package with a hint naming the warm-up command; documentation is warmed into the machine store with `vibe cache add` (§11), and the local reader and `vibe explain` read the store.
>
> <spec://org.vibevm.core/vibevm/common/PROP-057#KIND-DOC-NOT-INSTALLED>

[p44] A short name without a group, such as `wal`, is accepted on the command line and resolved through the [registry](../glossary/index.xml#registry) [index](../glossary/index.xml#index-registry); it is convenience, and the manifest always records the full coordinate.

> [p45] resolved via the index (§2.6)
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-008#ROW-SHORT-BEHAVIOUR>

> [p46] `vibe install wal` resolves the collision once, at the top level, and writes `org.vibevm.world/wal` into `[requires]`. Manifests are therefore always qualified — exactly the cargo/npm pattern (`cargo add serde` on the CLI, `serde = "1"` in `Cargo.toml`).
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-008#RESOLVE-ONCE-WRITE-QUALIFIED>

[p47] Resolving a short name needs the index, one lookup per registry; a registry without one offers no short names, and the full coordinate is required. If the lock file already pins a package of that name, the short name means the locked one. When two registries offer different packages under one name, vibe stops with exit code 7 and lists the candidates, and you repeat the command with the group. A kind prefix checks the result and never disambiguates, because two packages of different kinds cannot share a coordinate.

> [p48] **Index dependency.** Resolving a short name requires enumerating candidates `(*, name)` across registries. The host cannot list an org cheaply ([PROP-005 §1](../vibe-index/PROP-005-package-index.xml) — GitVerse exposes no org listing, GitHub is rate-limited). Therefore short-name resolution **requires [PROP-005](../vibe-index/PROP-005-package-index.xml)**: one HTTP GET of `by-name/<name>.json` per registry yields the candidate set. Without an index, a registry's short names are unavailable and the qualified form is required.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-008#INDEX-DEPENDENCY>

> [p49] **Lockfile is authoritative.** If `vibe.lock` already pins `org.vibevm.world/wal`, a later `vibe install wal` resolves to the locked entry — the short name prefers what is already locked.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-008#LOCKFILE-AUTHORITATIVE>

> [p50] One candidate → resolve. Multiple candidates with different identity → **collision**:
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-008#COLLISION-BEHAVIOR>

> [p51] A new exit code **`7`** ("ambiguous package") is assigned, distinct from `3` ("package conflict", `VIBEVM-SPEC.md` §9.4).
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-008#EXIT-CODE-7>

> [p52] **kind validation.** If the `kind` prefix is present, after resolution the resolver asserts `resolved.kind == prefix`; mismatch is a `KindMismatch` error. A kind prefix is validation + a UX signal — it does **not** disambiguate, because by §2.2 `name` is unique within a `group`, so `flow:org.vibevm.world/wal` and `feat:org.vibevm.world/wal` cannot co-exist. A short-name collision is always a *group* collision (§2.7), resolved by group-qualification, never by kind.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-008#KIND-VALIDATION>

