<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
  <title id="root">PROP-028 — Package families: `&lt;family&gt;` / `-lang` / `-mcp`</title>
  <status stage="impl" state="done" comment="B0 2026-07-24: IMPLEMENTED 2026-07-07; the three families in force; fact grain 2026-07-24; amended 2026-09-11 by PROP-057 — the companion role -docs outside family unison (§2.1, §2.2, §3), specified, implementation with the docs-2026-09 campaign"/>
  <p p="1"><fact id="status-line" status="impl/done">**Status:** IMPLEMENTED 2026-07-07 — owner-directed (the package-family
rename). The three families in force ship under this convention: requiring one
aggregator installs its whole family at a single resolved version set, and the
served engines can never skew from the consumer's gates. Units typed at REQ
grain.
**Related:** [PROP-008 §2.2](../modules/vibe-registry/PROP-008-qualified-naming.xml)
(a renamed package is a NEW identity — a family member is not an alias of the
name it replaced), [PROP-027](../modules/vibe-mcp/PROP-027-mcp-packages.xml)
(the `mcp` kind and the exact-pin law the `-mcp` member obeys),
[PROP-024](PROP-024-code-bearing-packages.xml) (code-bearing packages — every
`-lang` / `-mcp` member is one), [PROP-009](../modules/vibe-workspace/PROP-009-loading-model.xml)
(boot loading — the aggregator carries no snippet; members' snippets reach
`INDEX.md` through the transitive BFS closure of `[requires]`).</fact></p>
  <section id="context" title="1. Context">
    <list ordered="false" p="2">
      <item><fact id="context-delivery-shape" status="impl/done">The AI-Native discipline reaches a consumer as several installable packages per
  language: the language STACK (guide, cards, runnable toolchain), the MCP SERVER
  that serves that toolchain over the wire (PROP-027), and — beneath both — the
  language-neutral FLOW core the stack projects.</fact></item>
      <item><fact id="context-before" status="impl/done">Before this convention a single name (`rust-ai-native`) meant the stack alone,
  and the server borrowed an unrelated name (`discipline-rust`).</fact></item>
      <item><fact id="context-costs" status="impl/done">Two costs followed:</fact></item>
      <item><fact id="context-cost-pinning" status="impl/done">a consumer who wanted "the Rust discipline, whole" had to know and hand-pin
    three loosely-related packages;</fact></item>
      <item><fact id="context-cost-name" status="impl/done">the server's name hid which stack it served (so the exact-pin law
    of PROP-027 §2.3 read as an accident rather than a family tie).</fact></item>
    </list>
  </section>
  <section id="decision" title="2. Decision">
    <section id="roles" title="2.1 Three roles, one family stem">
      <p p="3"><fact id="req-roles" status="impl/done">`req r2`</fact></p>
      <p p="4"><fact id="FAMILY-DEF" status="impl/done" action="continue" actionstage="doc" audience="author">A **package family** is a set of packages sharing a `&lt;family&gt;` stem and
delivering one coherent capability across three roles:</fact></p>
      <list ordered="false" p="5">
        <item><fact id="ROLE-AGGREGATOR" status="impl/done" action="continue" actionstage="doc" audience="user,author">**`&lt;family&gt;`** — 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.</fact></item>
        <item><fact id="ROLE-LANG" status="impl/done">**`&lt;family&gt;-lang`** — the *language stack* (`kind = "stack"`, PROP-024): the
  guide, the cards, the boot snippet, and the runnable toolchain. It requires
  the flow foundation it projects.</fact></item>
        <item><fact id="ROLE-MCP" status="impl/done">**`&lt;family&gt;-mcp`** — the *MCP server* (`kind = "mcp"`, PROP-027): that same
  toolchain served over MCP. It exact-pins its `-lang` stack (PROP-027 §2.3)
  and version-mirrors it, so one engine set answers both the CLI floor and the
  agent's tools.</fact></item>
        <item><fact id="ROLE-DOCS" status="spec/done" action="continue" actionstage="impl">**`&lt;family&gt;-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 `&lt;family&gt;-docs-&lt;lang&gt;` with a lower-case BCP-47 tag
  (`rust-ai-native-docs-ru`). Amended 2026-09-11.</fact></item>
      </list>
      <p p="6"><fact id="AGGREGATOR-PINS-DELIBERATE" status="impl/done">The aggregator's exact pins are deliberate, not kind-mandated: a stack may pin
its dependencies however it likes, but a family is a *tested version set*, so
the aggregator holds its members equal.</fact></p>
    </section>
    <section id="versioning" title="2.2 The version line follows the name, and the family moves in unison">
      <p p="7"><fact id="req-versioning" status="impl/done">`req r2`</fact></p>
      <list ordered="false" p="8">
        <item><fact id="VERSION-FOLLOWS-NAME" status="impl/done">A family member's version line is continuous with its NAME.</fact></item>
        <item><fact id="RENAME-NEW-IDENTITY" status="impl/done">Per PROP-008 §2.2 a
  renamed package is a new identity, so versions do not transfer across a rename:
  when a name is minted it continues past the highest version that name previously
  carried, and no `&lt;name&gt;@X.Y.Z` coordinate is ever reused for a different
  artifact.</fact></item>
        <item><fact id="AGGREGATOR-ABOVE-HISTORY" status="impl/done">In particular an aggregator name that reuses a stem the old stack
  used (e.g. `rust-ai-native`, once the 0.5.0 stack, now the aggregator) begins
  its aggregator line ABOVE that history, never at or below it.</fact></item>
      </list>
      <list ordered="false" p="9">
        <item><fact id="UNISON-LAW" status="impl/done" action="continue" actionstage="doc" audience="user,author">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.</fact></item>
        <item><fact id="NO-MIXED-NUMBERS" status="impl/done">A family is a *tested set*, so its members never
  carry mixed numbers — reading `rust-ai-native 0.7.0` tells you
  `rust-ai-native-lang`, `rust-ai-native-mcp`, and the aggregator are all 0.7.0.</fact></item>
        <item><fact id="MIRROR-PAIRWISE" status="impl/done">The `-mcp` member's version-mirroring of `-lang` (PROP-027 §2.3) is the pairwise
  case of this whole-family law.</fact></item>
        <item><fact id="family-standings" status="impl/done">The families currently stand at: rust **0.7.0**, typescript
  **0.6.0**, go **0.1.0** (the aggregator plus its `-lang` and `-mcp` members), and
  the shared foundation core-ai-native **0.8.0** — a foundation is not in any one
  family's unison, it bumps on its own content and every family widens its `^`
  floor to meet it.</fact></item>
        <item><fact id="COMPANION-OUTSIDE-UNISON" status="spec/done" action="continue" actionstage="impl">The `-docs` companion (§2.1) stands **outside** unison: a
  documentation edit never bumps the family's code, and a family release
  never forces a documentation release; the two are tied only by the
  `[[documents]]` constraint the companion declares, and the site picks the
  newest documentation whose constraint admits the family version
  ([PROP-057 §4](PROP-057-documentation-packages-and-site.xml)). Amended
  2026-09-11.</fact></item>
      </list>
    </section>
    <section id="families" title="2.3 The families in force">
      <p p="10"><fact id="req-families" status="impl/done">`req r1`</fact></p>
      <list ordered="false" p="11">
        <item><fact id="FAM-CORE" status="impl/done">**`core-ai-native`** (flow) — the language-neutral discipline core. It stands
  ALONE: it is the shared foundation every language family requires, not itself
  an aggregator (there is nothing to aggregate beneath a foundation), so no
  `core-ai-native-lang` / `-mcp` exist. Each `&lt;family&gt;-lang` requires it.</fact></item>
        <item><fact id="FAM-RUST" status="impl/done">**`rust-ai-native`** — aggregator over `rust-ai-native-lang` (the stack) and
  `rust-ai-native-mcp` (the server).</fact></item>
        <item><fact id="FAM-TYPESCRIPT" status="impl/done">**`typescript-ai-native`** — aggregator over `typescript-ai-native-lang` and
  `typescript-ai-native-mcp`.</fact></item>
      </list>
    </section>
    <section id="surface-naming" title="2.4 Naming below the package: crates, binaries, skills, servers">
      <p p="12"><fact id="req-surface-naming" status="impl/done">`req r1`</fact></p>
      <p p="13"><fact id="SURFACE-NAMING-LAW" status="impl/done" action="continue" actionstage="doc" audience="author">The family stem is language-FIRST and reaches every named surface a family
ships, not only the package identities. Each crate, binary, agent skill, and
MCP server carries the `&lt;family&gt;` prefix:</fact></p>
      <list ordered="false" p="14">
        <item><fact id="NAME-UMBRELLA-BINARY" status="impl/done">**The umbrella binary is the family name.** A `-lang` stack's driver binary —
  the tool a consumer puts on PATH — is named `&lt;family&gt;` itself
  (`rust-ai-native`, `typescript-ai-native`): `init` / `floor` / `conform` /
  `specmap` / `trace` / … all hang off it. Its crate is `&lt;family&gt;-cli`.</fact></item>
        <item><fact id="NAME-ROLE-BINARIES" status="impl/done">**Every other binary and its crate share the `&lt;family&gt;-&lt;role&gt;` form.** The
  standalone gates and the oracle: `&lt;family&gt;-conform`, `&lt;family&gt;-specmap`,
  `&lt;family&gt;-tcg`; their crates match name-for-name, so
  `cargo … -p &lt;family&gt;-conform --bin &lt;family&gt;-conform` and
  `vibe bin exec &lt;family&gt;-conform` read the same token. Library-only crates
  take the same law (`&lt;family&gt;-conform-frontend`, `&lt;family&gt;-tcg-bridge`,
  `&lt;family&gt;-env-audit`).</fact></item>
        <item><fact id="NAME-MCP-SURFACE" status="impl/done">**`&lt;family&gt;-mcp` is the family's MCP surface.** For a language family it is
  the server package, its single authored crate, AND its binary — all three the
  one name (maximal coherence: the package a consumer pins, the crate that
  builds, and the artifact that serves are indistinguishable). Its
  `[[mcp_server]].name` — the agent-visible key written into `.mcp.json` — is
  the FAMILY name (`rust-ai-native`), so the tool namespace an agent sees is the
  family, not an internal binary. For the flow foundation, `core-ai-native-mcp`
  is the neutral MCP transport crate the servers vendor.</fact></item>
        <item><fact id="NAME-SKILLS" status="impl/done">**Skills carry the stem too**: `&lt;family&gt;-sweep`, `&lt;family&gt;-terraform`.</fact></item>
        <item><fact id="NAME-NEUTRAL-ENGINES" status="impl/done">**The neutral engine crates the core authors** take the CORE stem —
  `core-ai-native-conform`, `core-ai-native-specmap`, `core-ai-native-specmark`,
  `core-ai-native-specmark-grammar`, `core-ai-native-mcp` — because they belong
  to no single language; each `-lang` / `-mcp` package vendors them
  byte-identically (PROP-024; `cargo xtask sync-engines`).</fact></item>
      </list>
      <list ordered="false" p="15">
        <item><fact id="D13-SUPERSEDED" status="impl/done">**Supersession of the `-rust` suffix policy (D13).** The earlier owner policy —
  «every artifact with a cross-language analog ends in `-rust` / `-typescript`»,
  recorded as the standing rule in GUIDE-AI-NATIVE-RUST §2 and referenced in
  PROP-026 and the WAL history — is SUPERSEDED by this language-FIRST family
  prefix.</fact></item>
        <item><fact id="D13-LANGUAGE-LEADS" status="impl/done">`conform-rust` becomes `rust-ai-native-conform`, not a suffixed
  `conform-rust`; the language LEADS the name so every artifact of one family
  sorts and reads together, and the aggregator name is the common prefix of its
  whole surface.</fact></item>
        <item><fact id="D13-GOAL-PRESERVED" status="impl/done">The suffix scheme's goal (a cross-language pair differs
  consistently, never sometimes) is preserved and strengthened — the whole name,
  not just its tail, now carries the family.</fact></item>
        <item><fact id="D13-NEUTRAL-OUTSIDE" status="impl/done">Language-NEUTRAL artifacts stay outside any family stem: vibevm's own generic
  `vibe-*` crates, the `vibe-tcg` product cell.</fact></item>
      </list>
    </section>
  </section>
  <section id="rejected" title="3. Rejected alternatives">
    <list ordered="false" p="16">
      <item><fact id="REJ-FEATURE-FLAGS" status="impl/done">**One package with feature flags** instead of a family: a package is a whole
  project of ONE kind (PROP-024); a stack and an MCP server are different kinds
  with different delivery machinery, and the flow core is language-neutral. One
  package cannot be three kinds.</fact></item>
      <item><fact id="REJ-ALIAS-WITH-CONTENT" status="impl/done">**The aggregator ships the stack's content** (an alias that also carries
  code): then `&lt;family&gt;` and `&lt;family&gt;-lang` would duplicate content and drift.
  The aggregator is deliberately empty so there is exactly one home for each
  artifact and the family is a pure naming/pinning layer.</fact></item>
      <item><fact id="REJ-CARET-PINS" status="impl/done">**Caret pins in the aggregator**: a caret would let members skew within a
  single install, dissolving the "one engine, one truth" the `-mcp` exact pin
  exists to guarantee.</fact></item>
      <item><fact id="REJ-DOCS-IN-UNISON" status="spec/done">**The `-docs` companion inside unison** (2026-09-11): a family is a
  *tested version set* of code, and prose moves in a different rhythm —
  every typo fix in the manual would bump the language stack and the MCP
  server, and every code release would demand a documentation release the
  owner has ruled out. Rejected together with a documentation subgroup
  (`&lt;group&gt;.docs/&lt;name&gt;`) and a language encoded in the group; the
  reasons are recorded at [PROP-057 §3](PROP-057-documentation-packages-and-site.xml).</fact></item>
    </list>
  </section>
  <section id="open" title="4. Open questions">
    <status stage="spec" state="work" comment="B1 2026-07-24: two questions still open, no owner ruling yet"/>
    <list ordered="true" p="17">
      <item><fact id="open-family-app" status="spec/work">A future `&lt;family&gt;-app` role (the `app` kind, admitted by the
   PROP-057 amendment of VIBEVM-SPEC §4.1) would join the aggregator's
   `[requires]` under the same exact-pin rule. Still open: no family ships an
   `app` today; the first `app` (the documentation site, `org.vibevm.doc/web`)
   belongs to no family.</fact></item>
      <item><fact id="open-member-opt-out" status="spec/work">Whether `vibe install &lt;family&gt;` should offer per-member opt-out (the mirror
   of PROP-027 §4's multi-server question); v1 is all-or-nothing per family.</fact></item>
    </list>
  </section>
</spec>
