Packages and their kinds
01Everything 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.
vibe list --path hello-vibe
KIND NAME VERSION ROLE BOOT SNIPPET
flow wal 1.0.0 package —
1 package installed.
What a package is
03A 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.
04 L4 — packages too. Every package root mirrors the same layout (vibevm/vibespecsinside the package instead ofspec/); materialisation mirrors package layout into the slots, boot-snippet paths and INDEX targets follow.
05A package is named by a 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.
06 Decision. Package identity becomes(group, name, version, content_hash).kindleaves the identity tuple.
07The 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 the coordinate is always written in full. In a 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.
08 Decision (owner ruling, 2026-08-13). Agroupis a claim, not a credential. Nothing verifies that the author ofcom.google/xownsgoogle.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.
09namebecomes unique within agroup(was: within akind,VIBEVM-SPEC.md§7.1).(group, name)is therefore unique on its own —kindis no longer needed to disambiguate.
10 Decision. The pkgref grammar gains an optionalgroupsegment and makes thekindprefix optional:
11 The short form is CLI-only sugar. It is never written to a manifest (§2.6).
12 Decision:[<kind>:]<group>/<name>@<version>— identity is qualified since M1.19 (PROP-008 §2.2); the unqualified<kind>:<name>@<version>ofVIBEVM-SPEC.md§7.1 is CLI sugar that resolves once, at the human boundary.
13naming = "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 asname, the rest asgroup. 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 invibespecs(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.
14Wherever 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.
15 A package address MUST carry its full coordinate —groupandname— in every occurrence across the project:
16
[<kind>:]<group>/<name>
17<group>.<name>—/is illegal in a repo name; the name is the last label
18
<group>/<name> — the name is the first path segment
19 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).
The eight kinds
| Kind | What it delivers | Example |
|---|---|---|
flow |
a way of working: commit rules, session notes, review conventions; usually a 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 |
21The set is closed and grows only by an amendment to the specification; a manifest with an unknown kind is rejected rather than guessed.
22kind ∈ {flow, feat, stack, tool, mcp, lang, doc, app}— eight kinds;mcpshipped with PROP-027;docandappadmitted by PROP-057 through theVIBEVM-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). (§InvariantsINV-VOCABULARYin this file carries the same list.)
23The 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.
24 Decision.kind(flow/feat/stack/tool) stays a mandatory[package]field but is now a pure attribute — it identifies nothing and names nothing.
25An 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.
26 The boundary withtoolis mechanical: atoollives in a project and runs throughvibe bin execby the lock file; anappruns nowhere in a consumer project and is built and deployed on its own.
Families and companions
27Some capabilities 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.
28 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.
29The bundle itself is the smallest package there is: a manifest and a README, no code and no boot snippet. Its one job is to name the family's members at one version.
30<family>— the aggregator.kind = "stack", content-minimal: avibe.tomland aREADME.md, and nothing else — no code, no boot snippet, nospecmap.toml/conform.toml. Its whole job is to name the family's members at one resolved version set through exact=X.Y.Zpins in[requires]. Requiring the aggregator installs the family.
31Documentation is the exception. A package's manual is its 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 it describes, and the site picks the newest manual that fits.
32<family>-docs— the documentation companion (kind = "doc", PROP-057 §3): 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.
How a package lands on disk
33[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. 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. Anything destructive on such a slot, an uninstall, a forced reinstall or a version switch, asks first, or needs --force when nobody can answer.
34
[package].materialization selects how the package lands on disk:
35copyis the default and the only mode an ordinary package needs. (It was namedsnapshotuntil 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.)
36copy/hardlinkare vendored — the slot is committed into the project's git and is offline-reproducible from it (ahardlinkslot's bytes are materialised into git ongit addlike any file); acopyslot trivially so.
37in-placeis not vendored — the slot (a nested.gitplus possibly millions of files) is.gitignored in the project; restoration is a re-clone at the lockfile'sresolved_commit. The honest trade:in-placepackages need the network to restore, wheresnapshotpackages do not.
38 Requires a git source. Incremental update andgit cleanreset both need git; a non-git source has noin-placestory (§4).
39 Anin-placeslot 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 ay/n, and in a non-interactive run it requires an explicit flag (--force) or it aborts rather than silently deleting an expensive resource.
Edge cases and rules
40Changing 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.
41 Changing a package'sgroupis a new package, not a rename — same discipline as changingname.
42A documentation package cannot be installed into a project. vibe install refuses it and names the command that fetches it for reading instead.
43vibe installMUST refuse adocpackage with a hint naming the warm-up command; documentation is warmed into the machine store withvibe cache add(§11), and the local reader andvibe explainread the store.
44A short name without a group, such as wal, is accepted on the command line and resolved through the registry index; it is convenience, and the manifest always records the full coordinate.
45 resolved via the index (§2.6)
46vibe install walresolves the collision once, at the top level, and writesorg.vibevm.world/walinto[requires]. Manifests are therefore always qualified — exactly the cargo/npm pattern (cargo add serdeon the CLI,serde = "1"inCargo.toml).
47Resolving 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.
48 Index dependency. Resolving a short name requires enumerating candidates(*, name)across registries. The host cannot list an org cheaply (PROP-005 §1 — GitVerse exposes no org listing, GitHub is rate-limited). Therefore short-name resolution requires PROP-005: one HTTP GET ofby-name/<name>.jsonper registry yields the candidate set. Without an index, a registry's short names are unavailable and the qualified form is required.
49 Lockfile is authoritative. Ifvibe.lockalready pinsorg.vibevm.world/wal, a latervibe install walresolves to the locked entry — the short name prefers what is already locked.
50 One candidate → resolve. Multiple candidates with different identity → collision:
51 A new exit code7("ambiguous package") is assigned, distinct from3("package conflict",VIBEVM-SPEC.md§9.4).
52 kind validation. If thekindprefix is present, after resolution the resolver assertsresolved.kind == prefix; mismatch is aKindMismatcherror. A kind prefix is validation + a UX signal — it does not disambiguate, because by §2.2nameis unique within agroup, soflow:org.vibevm.world/walandfeat:org.vibevm.world/walcannot co-exist. A short-name collision is always a group collision (§2.7), resolved by group-qualification, never by kind.