VibeVM
Contents
On this page
en
Publisher
org.vibevm.core
Version
1.0.0latest
Audiences
user, author
Reading time
15 min
Rendered
Read aloud
never

PROP-024 — Code-bearing packages (a package is a project)

01Status: IMPLEMENTED (proposed 2026-06-27, owner-directed; verified against the tree 2026-07-25 by the spec-actualization campaign — this very session boots on a vendored toolchain delivered exactly this way). Makes a vibe package able to ship runnable code, not only prompt content, so the discipline's verification tools (the conform checker, the specmap/specmark traceability engine) can live inside the discipline packages instead of being hardcoded in the vibevm workspace. A consumer who installs stack:org.vibevm.ai-native/rust-ai-native-lang then has the working checkers, not a prose description of them.

02Related: PROP-002 §2.1 (content-hash identity — re-scoped here to the shippable tree), PROP-007 (workspace + vibedeps/), PROP-009 §2.1/§2.3 (the verbatim materialise step + the boot path it emits), PROP-011 §2.6 (in-workspace file:// mutability — the dev loop that re-materialises edited package source), PROP-020 (the post-install build hook), PROP-022 §2.2 (the copy-mode tree — re-scoped here), the discipline mechanism specs ENGINE-CONFORM and PROP-014 (the tools that relocate; the specs themselves now ship in flow:org.vibevm.ai-native/core-ai-nativespec://org.vibevm.ai-native/core-ai-native/mechanisms/…).

03Owner sanction: PROP-024 reshapes the owner-frozen VIBEVM-SPEC.md (§4.2 layout, §7.2 package contents, §7.3 manifest, §7.4 identity, §12 linter, §13.1 package layout). The VIBEVM-SPEC.md edits required explicit owner sanction; it was granted 2026-06-27 — the same precedent as PROP-009 §5 item 8.

1. Motivation

1.1 The problem — a package can describe a tool but not ship one

  • 04The discipline packages (flow:org.vibevm.ai-native/core-ai-native, stack:org.vibevm.ai-native/rust-ai-native-lang, …/typescript-ai-native) carry the manifesto, the guides, and the nine pattern cards — prompt content.
  • But the machinery that makes the discipline real — the conform checker (Class-F/G rules, the file-length budget, the unwrap ban) and the specmap/specmark traceability engine — is hardcoded as crates inside the vibevm workspace (crates/conform-core, crates/conform-frontend-rust, crates/specmark, crates/specmap-core, the cargo xtask conform/specmap drivers).
  • 05Install stack:org.vibevm.ai-native/rust-ai-native-lang today and you get a description of checkers you do not have. To actually run the discipline you would have to re-implement the very tools vibevm already wrote.
  • The discipline is therefore not distributable: its strong-author artifacts (guide, cards) ship, but its runtime (the checkers) does not.
  • This is the gap that, left open, makes spec-conformance "fall apart" for any consumer that is not vibevm itself.
  • 06The package format is the cause: a package is defined as a bundle of prompt files (VIBEVM-SPEC.md §7.2 — "vibe.toml, README.md, other content files referenced by the manifest"), materialised verbatim.
  • There is nowhere to put code.
  • The identity/materialisation machinery (content_hash over every file, full-tree copy) actively fights it — a Rust crate's target/ would make identity non-deterministic and the copy ruinous.

1.2 The shape — a package is a project

07A vibe project already has the right shape: an authored spec/ corpus (VIBEVM-SPEC.md §4.2 — "spec/ is the spec directory") plus arbitrary code at the root (Cargo.toml, crates/, src/) plus one vibe.toml. A package should be the same object, made installable:

08prompt/spec content under spec/, arbitrary code at the root, one vibe.toml.

  • 09Then a package can ship its tools; an installed package is immediately usable; and authoring a package is authoring a project — the same layout, the same vibe check, the same boot computation.
  • The discipline's own four-layer model lands cleanly: L1/L2/L3 (manifesto, guide, cards — prompts) live under spec/, and L4 (the implemented checkers — code) lives at the root.

2. Decisions

2.1 A package is a project — spec/ for prompts, the root for code

10req r1

11Decision. A package has the identical on-disk shape as a consumer project:

  • 12Prompt/spec content lives under the package's spec/ subtree — boot snippets (vibevm/vibespecs/boot/), cards, guides, manifesto, appendix — laid out exactly as an ordinary project's spec/ (VIBEVM-SPEC.md §4.2). [boot_snippet].source is a spec/-relative path (e.g. spec/boot/20-stack-rust-ai-native.md).
  • The package root holds arbitrary code (e.g. Cargo.toml + crates/) and vibe.toml, exactly as a project root does. Code is optional — a prompt-only package (e.g. discipline-core) simply has no code at its root.
  • 13Consequence. Developing a package is developing a project.
  • vibe check applies unchanged (its §12 Check 7 — vibevm/vibespecs/boot/ exists and holds only markdown — is satisfied by the package's own vibevm/vibespecs/boot/).
  • The package's own boot sequence is computed the same way as any project's.
  • There is no package-only directory convention to learn.

14This retires the flat package layout (boot snippets and content at the package root) that the real packages drifted into; it aligns them with — and extends — VIBEVM-SPEC.md §13.1's own canonical example, which already places a package's content under spec/.

2.2 The shippable tree excludes build output

15req r1

16Decision. A package's shippable tree is its directory minus a build-output denylist:

17.git/        .vibe/        target/        node_modules/
  • 18plus any glob listed in an optional .vibeignore at the package root.
  • The content_hash (PROP-002 §2.1), the snapshot copy (PROP-022 §2.2), and the verbatim materialised slot (PROP-009 §2.1) all operate over the shippable tree, never the raw directory.
  • 19Why. Identity is the source, never build artifacts: build output is non-deterministic (timestamps, host paths, incremental state) and may be gigabytes — hashing or copying it would make identity unstable and materialisation ruinous, the exact failure PROP-022 §1.1 names for "big in file count".
  • A package's source — what its author commits — is precisely what is hashed, copied, and vendored.
  • For a reference-backed bridge, the package source is only its maintainer-authored manifest and adapters. A declared external embedded tree is independently authenticated and cached; it is not part of the package shippable tree or package content_hash, and it is never published as if the bridge maintainer authored it.
  • 20"Verbatim" is preserved for the source. PROP-009 §2.1 / VIBEVM-SPEC.md §13.1 guarantee no path rewriting and no per-file write list — a human reading the package directory sees exactly what materialises.
  • That guarantee holds unchanged for the shippable tree: build output was never part of the authored tree (it is gitignored in the package's own repository too).
  • The denylist formalises "what was never source", it does not introduce selection.
  • Considered and rejected: hashing and copying build output too — rejected: non-deterministic and potentially gigabytes, the file-count/byte-count failure PROP-022 §1.1 exists to avoid (##REJ-HASH-BUILD-OUTPUT); a per-file [ship] / [files] allow-list in the manifest — rejected: it resurrects the per-file write list PROP-009 §2.6 retired, and a denylist keeps "what ships" == "the source", preserving the verbatim guarantee (##REJ-ALLOW-LIST, ##VERBATIM-PRESERVED).
  • Revisit when: a package ships in a language whose build output the four-name denylist does not cover and .vibeignore alone cannot carry — Python (__pycache__/, .venv/), JVM (build/), Go (vendor/) are the near candidates. The fired state is mechanically observable and already asserted: a package whose content_hash differs between a clean and a built checkout, which is exactly what ##ACC-HASH-EXCLUDES tests. Observation point: that acceptance check, run over the published package set.

2.3 Code materialises, then builds consumer-side into a gitignored target

21req r1

  • 22Decision. vibe install materialises the shippable tree — including code — into the vibedeps/ slot, as today.
  • Original hook-placement rule, retained as history. Turning source into a runnable tool is consumer-side and build output never enters the source hash. Its stronger “never write inside the committed slot” clause is superseded by ##BUILD-MECHANISM-SUCCESSOR: provider-root target/ is legal because the shippable tree and slot record own neither it nor its bytes.
  • 23The original generic path remains available: a code-bearing tool package may build via a post-install hook (PROP-020) whose build output is directed to a gitignored location (e.g. <project-root>/.vibe/<pkg>-target/), never the slot. To let a hook address that location, the hook runner gains a VIBE_PROJECT_ROOT environment variable (the workspace absolute root) alongside the existing VIBE_PACKAGE_DIR (the slot) — a small PROP-020 §2.2 addition.
  • A language-native consumer (vibevm itself is a Rust consumer) MAY instead reference the shipped crates directly through its own toolchain (§2.4) and skip the build hook entirely — the hook is the path for a consumer that wants a ready binary without driving the language's build system itself.
  • Lifecycle successor. Declared build targets are selected through the shared mechanism registry and recorded artifact DAG. The builtin build:cargo provider runs under the exact package/provider root, consumes cargo metadata plus cargo build --message-format=json-render-diagnostics, selects only the declared contained compiler artifact from Cargo's messages, and persists provider/config/platform/toolchain/output identity in the shared artifact record. Native extensions and compatibility-lowered [[binary]] targets may therefore build into the provider's own gitignored target/; that output remains outside the shippable tree and .vibe-slot.toml footprint. Package-supplied native Build and Package providers now use separate versioned four-operation wires and safe ABI-1 macros, one all-role prepared carriage, engine-owned staging/records and exact provider-fresh rollback; a real installed package with two ABI-isolated cdylibs displaces both Cargo and static-file defaults, writes the build A2 record and consumes it through Package with no builtin fallback. Evidence: a22da2a3, 1baac652, 332f8e28, 06f8f465, then M-007 00cabac8, 93df9d28, e9388559, 2193e6e7, fe0eb9df, 0ca2b781, 123ac504, 68ce9078, 55cfbf94. This lifecycle execution does not redefine dependency materialisation: bare vibe install still materialises package content and is not a build/package mechanism invocation.

24The record-aware reset successor does not own build output: whether a hook writes outside the slot or Cargo writes the provider-root target/ inside it, the path is outside .vibe-slot.toml. Same-slot refresh preserves it and unchanged source mtimes let the build system remain incremental; a version change selects a different current slot. Hook-created unrecorded state retains PROP-020's explicit idempotence boundary.

2.4 Consuming shipped code — external-path-dep, no nested workspace

25req r1

  • 26Decision. A code-bearing package carries its own workspace manifest (for Rust, a root Cargo.toml with [workspace]) — it is a standalone, independently-buildable project.
  • A language-native consumer that needs a shipped crate — a proc-macro that compiles into the consumer's own code (the specmark case), or a binary it invokes (the conform/specmap case) — references it by path into the materialised slot:
27# consumer's root Cargo.toml — one pinned alias, updated once per package bump
[workspace.dependencies]
specmark = { path = "vibevm/vibedeps/org.vibevm.ai-native.rust-ai-native-lang/1.0.0/crates/vendor/core-ai-native-specmark" }

[workspace]
exclude = ["vibevm/vibedeps", "vibevm/vibepacks"]   # disclaim the package's own workspaces
  • 28The consumer excludes vibevm/vibedeps/ (and, for a self-hosting repo, the in-repo vibevm/vibepacks/ source) from its [workspace], so the slot's crates belong to the package's workspace, not the consumer's — Cargo forbids a directory living in two workspaces, and this is the standard resolution for a repo that contains a sub-project with its own workspace.
  • The slot path is version-qualified; pinning it once in [workspace.dependencies] means a package version bump touches a single line.

29A binary tool (conform, specmap) is run from the package's workspace — cargo run --manifest-path vibedeps/<slot>/Cargo.toml --bin conform -- … with CARGO_TARGET_DIR pointed at a gitignored dir (§2.3) — so building it pollutes neither the slot nor the consumer's own target/.

  • 30Spike before the irreversible move. Cross-workspace path-deps and the exclude topology are validated empirically on the target host (Windows, where canonicalize() adds a \\?\ prefix and Cargo path handling has sharp edges) before any crate is physically relocated.
  • The fallback, if cross-workspace path-deps prove unworkable on a host, is §4's rejected-but-retained alternative (the consumer adds the slot crates as its own workspace members) — chosen only on evidence.
  • Why: an external constraint, not a preference — "Cargo forbids a directory living in two workspaces, and this is the standard resolution for a repo that contains a sub-project with its own workspace" (##WORKSPACE-EXCLUDE). Giving the package its own workspace is what makes it "a standalone, independently-buildable project" (##OWN-WORKSPACE) and keeps a version bump to one pinned line (##PIN-ONCE).
  • Considered and rejected: the consumer adding the slot crates as its own workspace members (no package workspace, no cross-workspace path-dep) — considered, and rejected as the primary model because it denies the package standalone-buildability and couples the consumer's workspace membership to generated vibedeps/ state (##REJ-CONSUMER-MEMBERS). Retained, not discarded: it is the §2.4 fallback, "chosen only on evidence" (##SPIKE-FALLBACK).
  • Revisit when: cross-workspace path-deps prove unworkable on a supported host — the condition ##SPIKE-FALLBACK already names, here given its observation point: a clean-checkout cargo build failing to resolve vibedeps/<slot>/crates/<crate> on any of the three platforms of PROP-000 §11 ##PLATFORMS-TRIO, Windows first (##SPIKE-FIRST). The fired state has a landing place already specified, so reopening is a switch, not a redesign.

2.5 Self-hosting bootstrap — the toolchain is vendored

31req r1

  • 32Decision. vibevm consumes its own discipline toolchain from the committed vibedeps/ slot.
  • Because vibedeps/ is committed (PROP-009 §2.1), a fresh clone builds from a clean checkout with no prior vibe install — the path-dep target (vibevm/vibedeps/org.vibevm.ai-native.rust-ai-native-lang/1.0.0/crates/vendor/core-ai-native-specmark, …) already exists in the tree.
  • There is no chicken-and-egg: the toolchain a build needs is vendored beside the code that needs it.
  • Why: committing the slot is what makes a fresh clone build "from a clean checkout with no prior vibe install" — the path-dep target already exists in the tree (##CLEAN-CLONE-BUILDS), so there is no chicken-and-egg between the toolchain a build needs and the build that would fetch it (##NO-CHICKEN-EGG). Acceptance already asserts it: ##ACC-CLEAN-CLONE.
  • Considered and rejected: materialization = "in-place" for the tool packages — rejected: in-place slots are .gitignored and unversioned (PROP-022 §2.4/§2.7), and the toolchain must be vendored and versioned so a clone is buildable offline (##REJ-IN-PLACE). Publishing the tool crates to crates.io and depending on the published versionsdeferred, not rejected: "the installed package is the distribution", and crates.io publication is "an optional later convenience for non-vibe Rust consumers, not a requirement of this model" (##REJ-CRATES-IO).
  • Revisit when: the committed slot's cost outgrows its guarantee — git count-objects -vH and the slot's on-disk size showing a clean clone expensive enough to outweigh the offline-buildability it buys (numeric threshold unset — owner 2026-08-01; event-shaped until set) — or the deferred demand arrives: a Rust consumer outside the vibe ecosystem needs conform / specmap without installing a vibe package, recorded in the F-NNN findings ledger so the signal has a place to be observed (##REJ-CRATES-IO).

33The development loop stays ergonomic: editing the in-repo package source under vibevm/vibepacks/org.vibevm.ai-native/rust-ai-native/… re-materialises the slot automatically on the next vibe install (PROP-011 §2.6 — in-workspace file:// sources are mutable), so the consumed vibedeps/ copy tracks the edited source without a manual rm -rf.

2.6 Placement follows the layer model; the engine split is a follow-up

34req r1

35Decision. The discipline's tools are code and obey the four-layer model: L4 (implemented checkers) ships in the package whose language they check.

  • 36For this pass, the entire Rust discipline toolchain — the conform engine (conform-core), its Rust frontend (conform-frontend-rust), the Rust traceability macros (specmark, specmark-grammar), the traceability engine (specmap-core), and the designated audit crate (env-audit) — ships in stack:org.vibevm.ai-native/rust-ai-native-lang. Its centre of gravity is Rust, and shipping the toolchain whole avoids carving language-neutral cores out under time pressure.
  • The condition fired. flow:org.vibevm.ai-native/core-ai-native was to stay prompt-only (manifesto, card format, scaffold catalog, RAID, appendix) until a second language actually needed the shared engine. The TypeScript pilot shipped, so core-ai-native now authors the neutral engines (conform / specmap / specmark / mcp cores), which each -lang and -mcp package vendors byte-identically.
  • Deferred (documented): the language-neutral conform engine (conform-core) is a genuine L1 artifact — a future conform-frontend-typescript would reuse it unchanged. Extracting conform-core up into discipline-core is a clean follow-up, taken when the first non-Rust pilot needs it (YAGNI until then). Likewise the neutral half of specmap-core (markdown parse, index, ledger, test-gate) versus its Rust rscan frontend. The end state is symmetric; the ordering is driven by real second-language demand, not built speculatively. Executed — the TypeScript pilot was that demand, and the neutral halves now live in core-ai-native, vendored into each family by cargo xtask sync-engines.
  • Why: the toolchain's "centre of gravity is Rust, and shipping the toolchain whole avoids carving language-neutral cores out under time pressure" (##THIS-PASS-WHOLE-TOOLCHAIN); the layer model then decides placement rather than convenience — L4 ships with the language it checks. Recorded the same session the decision was taken (§7 ##HIST-DRAFT-1, 2026-06-27).
  • Considered and rejected: extracting the language-neutral cores (conform-core, the neutral half of specmap-core) up into core-ai-native in the same passdeferred, not rejected, with its condition stated: "taken when the first non-Rust pilot needs it (YAGNI until then)" (##DEFERRED-ENGINE-SPLIT). The deferral has since been honoured: the TypeScript pilot was that demand, the condition fired, and the neutral engines now live in core-ai-native, vendored by cargo xtask sync-engines (##CORE-STAYS-PROMPT-ONLY).
  • Revisit when: (a successor trigger — the previous one fired with the TypeScript pilot and is spent) a third language family arrives and the neutral engines do not cover it — observed as cargo xtask sync-engines being unable to vendor a core byte-identically into the new -lang package, or a third family needing a core the two existing ones do not share. Observation point: the sync-engines task and the set of *-ai-native-lang / *-ai-native-mcp packages.

3. Manifest / schema surface

  • 37[project] and [package] are equipotent consumer nodes (owner law, 2026-08-23, near-verbatim: «и project, и package могут быть потребительской единицей — в репозитории, где разрабатывается пакет, корневой vibe.toml имеет тип package, и всё там обязано работать точно так же; сам маркер — косметическое свойство для человека или UI; разошлись по возможностям — дефект, один и тот же код обслуживает обе роли»). One grammar, one file, the role declared by its XOR section — and every consumer operation (install, lockfile, boot-lane generation, installed: resolution, vibe facts, spec_format, tree/show) reads the role-blind Manifest::consumer_node() view and never matches on the role. [package] gains spec_format so a package-rooted dev checkout pins its materialisation like a project. Role-SPECIFIC surfaces stay lawful where the role is the subject (publish ships packages; a package's [requires] is a consumer contract, a project's is its own install set). Landed with twin tests (spec_format resolution and the facts host coordinate answered identically from either role) after the divergence audit: bootgen's self coordinate, the facts host reader, spec_format resolution, the tree model and the workspace-origin name all went through the one view.
  • No new required manifest field. [boot_snippet].source becomes spec/-relative (a value change, not a schema change). [package].materialization stays snapshot for a vendored code-bearing package.
  • Optional .vibeignore at the package root — newline-delimited globs added to the §2.2 build-output denylist.
  • [hooks].post-install (PROP-020) is the build lever; VIBE_PROJECT_ROOT is added to the hook environment (§2.3).
  • Original code-bearing-package law: package identity remains content_hash over the shippable tree (§2.2). Reference-backed bridges extend the lock with separately nested embedded-source pins; those pins authenticate projected upstream bytes but do not become part of package identity.

4. Rejected / deferred alternatives

  • 38Hash and copy build output too — rejected: non-deterministic and potentially gigabytes; it is the file-count/byte-count failure PROP-022 §1.1 exists to avoid. Identity is source (§2.2).
  • Consumer adds the slot crates as its own workspace members (no package workspace, no cross-workspace path-dep) — considered; rejected as the primary model because it denies the package standalone-buildability (a package would not be a project) and couples the consumer's workspace membership to generated vibedeps/ state. Retained as the §2.4 fallback if cross-workspace path-deps prove unworkable on a host — a decision made on spike evidence, not by default.
  • materialization = "in-place" for tool packages — rejected: in-place slots are .gitignored and unversioned (PROP-022 §2.4/§2.7); the discipline toolchain must be vendored and versioned so a clone is buildable offline (§2.5). Snapshot-minus-build-output is the right mode.
  • Publish the tool crates to crates.io and depend on the published versions — deferred: the installed package is the distribution, so the consumer depends on the slot, not a registry crate. crates.io publication is an optional later convenience for non-vibe Rust consumers, not a requirement of this model.
  • A per-file [ship]/[files] allow-list in the manifest — rejected: it resurrects the per-file write list PROP-009 §2.6 retired. A denylist of build output (§2.2) keeps "what ships" == "the source", preserving the verbatim guarantee.

5. Out of scope

  • 39Auto-detecting the build system / language of a code-bearing package — the package declares a hook or explicit artifact/mechanism target; vibevm does not infer cargo vs npm. The commissioned builtin is selected only by the declared build:cargo target.
  • Sandboxing the build hook — inherits PROP-020 §4's posture (hooks run with the user's privileges; package installation is the consent and execution is made observable through exact provider/target evidence).
  • Out of scope when written; since delivered. TypeScript shipped no implemented tool to relocate, so its cards kept specified checker statuses until a TS pilot existed (§2.6 deferral). The typescript-ai-native family shipped with its floor, conform and specmap engines — the deferral is closed.

6. Acceptance

  • 40A package may carry code at its root and prompt content under spec/; vibe check passes on it as a project (vibevm/vibespecs/boot/ markdown-only).
  • content_hash, the snapshot copy, and the materialised slot exclude .git/, .vibe/, target/, node_modules/, and .vibeignore globs; identical source produces an identical hash regardless of build state.
  • [boot_snippet].source resolves spec/-relative; the generated INDEX.md names vibedeps/<slot>/spec/boot/<file>.
  • vibevm builds from a clean checkout, consuming its discipline toolchain (incl. the specmark proc-macro the product crates compile against) from the committed vibedeps/ slot, with no prior vibe install.
  • An external Rust project can install stack:org.vibevm.ai-native/rust-ai-native-lang and run conform / specmap against its own code.
  • Full self-check.sh green; conform 0/0/0; specmap clean.

7. Version history

  • 412026-06-27 — draft 1. Owner-directed: make the discipline self-sufficient by letting packages ship runnable code (spec/ for prompts, the root for code), then relocate the Rust toolchain (conform + specmap/specmark) out of the vibevm workspace and into stack:org.vibevm.ai-native/rust-ai-native-lang. The frozen VIBEVM-SPEC.md sanction was granted the same session (§0). Decisions taken in the owner session: the prompt directory is spec/ (singular, project-identical, not specs/); the full traceability stack moves alongside conform; conform is productised to run on an arbitrary external project (config-driven, not vibevm-hardcoded); conform-core ships in the Rust stack now with the L1 engine-extraction deferred (§2.6).

For an agent

This page has a machine mirror. The citation carries the version rather than latest, so what an agent quotes does not move under it.

spec://org.vibevm.core/vibevm@1.0.0/common/PROP-024-code-bearing-packages

.md.xmlllms.txt