# Опубликовать пакет {#root}

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

[p01] Вы написали пакет и хотите, чтобы его ставили другие. Эта страница публикует его туда, откуда ставят ваши проекты, отдельным репозиторием, помечает версию тегом и проверяет, что свежий проект может его установить.

[p02]
```prompt
Опубликуй пакет org.acme/notes из дерева этого проекта, слот vibevm/vibepacks/org.acme/notes/v0.1.0, в первый реестр этого проекта токеном публикации, который уже есть у меня в окружении, затем создай черновой проект в другом месте и установи в него опубликованный пакет, чтобы доказать, что он работает. Перед настоящим пушем спроси меня.
```

- needs: навык vibevm, установленный у вашего агента; токен публикации для хоста реестра в окружении или под `~/.vibe/`; заполненный `vibe.toml` пакета с версией, которая ещё не публиковалась

outcome: в организации реестра существует репозиторий, названный по координате пакета, с тегом версии; черновой проект ставит его, и `vibe list` показывает версию

- assert: `vibe registry publish vibevm/vibepacks/org.acme/notes/v0.1.0 --dry-run`

## Что происходит {#what-happens}

[p03] Сначала агент выполняет `vibe registry publish vibevm/vibepacks/org.acme/notes/v0.1.0 --dry-run` и показывает вам, что произойдёт: [реестр](../glossary/index.xml#registry), имя репозитория, выведенное из [координаты](../glossary/index.xml#coordinate), тег версии. После вашего «да» он выполняет настоящую команду: vibe создаёт репозиторий в организации реестра через API хоста, если его ещё нет, отправляет поставляемое дерево пакета и помечает версию тегом. С этого момента пакет — ещё один репозиторий, который подхватит [индекс](../glossary/index.xml#index-registry) реестра. Чтобы доказать это, агент создаёт черновой проект и ставит пакет по координате.

> [p04] Each package is its **own** git repository — no monorepo. Per-package maintainer permissions are hosting-native (a package repo's owner controls access); no central merge queue.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-002#SHAPE-OWN-REPO>

[p05] Публикатор — механический инструмент: он создаёт репозиторий, отправляет содержимое и помечает версию тегом, и больше ничего. Хост из адреса реестра выбирает адаптер, который создаёт репозитории; GitHub и GitVerse известны, а неизвестный хост — понятная ошибка, а не догадка. В репозитории содержимое пакета лежит плоско в корне, а версия — это тег из `v` и номера версии.

> [p06] **Decision.** Ship a maintainer utility in v1. Scope: mechanical-only publish — **create repo, push contents, tag version**. Semantic review (LLM-backed safety analysis per `VIBEVM-SPEC.md` §8.5) remains v2+.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-002#PUBLISH-UTILITY>

> [p07] **Adapter selection.** The CLI picks an adapter from the registry URL's host segment. `github.com` (or any subdomain) → `GitHubCreator`; `gitverse.ru` → `GitVerseCreator`; unknown hosts surface a clean error pointing at PROP-002 §2.10 rather than guessing a Gitea-compatible shape that may not match the host's actual API.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-002#PUB-ADAPTER-SELECTION>

> [p08] **Decision.** A package repository contains the package content flat at the repository root:
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-002#FLAT-LAYOUT>

> [p09] Version = git tag with `v<semver>` prefix. The tag is a mutable logical label by default: republishing the same version moves it, while the resolved commit and content hash preserve exact identity in each consumer lock.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-002#LAYOUT-TAG-VERSION>

## Руками {#by-hand}

[p10] 1. Положите токен публикации туда, где vibe его читает. Это переменная окружения `VIBEVM_PUBLISH_TOKEN` или файл `~/.vibe/<host>.publish.token`, например `~/.vibe/github.publish.token`, доступный для чтения только вам. Токену нужно право создавать репозитории в организации.

> [p11] 20. Token secrecy and adapter scope
>
> <spec://org.vibevm.core/vibevm/common/PROP-000#token-secrecy>

[p12] vibe ищет токен в фиксированном порядке и берёт первый найденный. Сначала `VIBEVM_PUBLISH_TOKEN_<HOST>` для хоста реестра, например `VIBEVM_PUBLISH_TOKEN_GITHUB`. Потом `VIBEVM_PUBLISH_TOKEN`, потом файл для хоста, потом старый `~/.vibe/git.publish.token`. Токен — поверхностный секрет: он никогда не печатается, не пишется в журнал и не попадает в файлы, которые пишет vibe. Он покидает процесс только в запросе к хосту, по шифрованному соединению.

> [p13] **Token loading.** The publish token loader (`crate::token::load_token(host)` → `load_token_for_host`) iterates these sources in order, returning the first non-empty value:
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-002#PUB-TOKEN-LOADING>

> [p14] `VIBEVM_PUBLISH_TOKEN_<HOST>` environment variable — host-specific, and the **highest-precedence source**. The suffix is the uppercased first label of the host (`VIBEVM_PUBLISH_TOKEN_GITHUB` for `github.com`, `VIBEVM_PUBLISH_TOKEN_GITVERSE` for `gitverse.ru`); non-alphanumerics fold to `_` so the name stays a valid POSIX identifier. Lets CI hold tokens for several hosts in the same environment without one host-agnostic variable clobbering them all.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-002#TOK-HOST-ENV-VAR>

> [p15] `<settings-dir>/<host-prefix>.publish.token` — per-host file. The prefix is the first label of the host (`github` for `github.com`, `gitverse` for `gitverse.ru`, `gitlab` for `gitlab.com`).
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-002#TOK-PER-HOST-FILE>

> [p16] **Token secrecy invariant.** The token is a surface secret. It is **never** displayed in CLI output, log lines, error messages, JSON event payloads, the lockfile, `.git/config`, or any committed file. The only sanctioned paths through which the value crosses a process boundary are: (a) the GitHub / GitVerse `Authorization: Bearer …` HTTP header, sent over TLS to the hosting API; (b) the `x-access-token:<TOKEN>@…` embed in the URL passed directly to the bounded `git ls-remote` / `git fetch` / `git push` invocations of one publish, never configured as a remote; (c) the in-memory `Token` struct, which redacts on `Display` and `Debug`. The CLI prints the *source* of the token (explicit / env-var / file path) but never the value. Implementations must verify token redaction in unit tests (cf. `vibe_publish::token::tests::debug_redacts_value`).
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-002#TOKEN-SECRECY-INVARIANT>

[p17] 2. Отрепетируйте:

[p18] Example `publish-dry-run` is copied from the source page at projection time.

[p19] 3. Опубликуйте. `--registry` выбирает реестр по имени; без него берётся первый в [манифесте](../glossary/index.xml#manifest):

[p20] Example `publish` is copied from the source page at projection time.

[p21] 4. Для верности установите пакет из свежего проекта: `vibe init scratch` и `vibe install <group>/notes --path scratch`.

## Публикация версии заново {#versions}

[p22] Публикация версии, которая уже существует, заменяет её: vibe добавляет коммит с новым содержимым и переносит на него тег версии. Номер версии меняется, только когда вы меняете его в манифесте. Потребитель, который эту версию уже разрешил, хранит точные байты, записанные в [лок-файле](../glossary/index.xml#lock-file), и при каждой установке сверяет [отпечаток](../glossary/index.xml#fingerprint) содержимого, так что перенесённый тег будет замечен, а не принят молча; свежая установка получает новое содержимое. Когда изменение важно потребителям, поднимите версию.

> [p23] **Versions are mutable by default (owner ruling, 2026-09-10).** Publishing an already-present version is the ordinary replacement flow, not a collision and not an implicit request to bump the version. The publisher appends an exact-payload commit on the observed `main`, then moves that version tag to the new commit. A version bump happens only when the author explicitly changes `[package].version`. An identical retry whose payload and selected tag already match is a no-op.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-002#PUBLISH-MUTABLE-VERSIONS>

> [p24] **2a. Frozen and snapshot versions (owner rulings,
> 2026-08-10; terminology fixed 2026-08-13).** A version is a **snapshot by
> default** — the word carries its Maven sense, *mutable*: content may change
> under the same version string, `vibe update` brings the fresh content without
> regard for hash continuity, and the lockfile pins the delivered capture's
> `content_hash` plus an opaque provider locator for reproduction. The
> **freeze** is the package author's one-way act: `frozen = true` in the
> manifest — never a registry's opinion, never part of the version string. The
> carrier decisions and their reasons: *(i)* the flag lives **inside the hashed
> content**, so a frozen version self-describes even offline and every registry
> serving those bytes necessarily agrees — in a multi-registry world with no
> global journal, content is the only carrier that cannot diverge; registries
> merely *observe* a freeze in their journals and project it into catalogs;
> *(ii)* the version string carries version ordering **only** — two entities
> never share one name, which keeps the full matrix expressible: a frozen
> prerelease (an immutable published beta) and a mutable bare version (being
> stabilised in place) are both legal; *(iii)* the transition is **one-way and
> single** — unfreezing is forbidden, further work is a new version string; a
> registry may never accept a frozen coordinate's re-publication with different
> bytes; *(iv)* same coordinate + different bytes + any party claiming frozen =
> **loud conflict** through the candidate machinery, never a quiet pick.
> **Every surface that shows a version shows its frozen state** — machine
> outputs carry the field by schema; CLI, TUI, GUI and MCP render it always
> (the Maven lesson: mutability a human cannot see is mutability that will
> surprise them). Yank remains journal-borne — it is the act frozen content can
> no longer carry itself.
>
> <spec://org.vibevm.core/vibevm/common/PROP-044#THE-FREEZE-MODEL>

> [p25] a force-pushed tag upstream is caught by the same machinery on the next install.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-002#EFF-FORCE-PUSH-CAUGHT>

[p26] Публикатор никогда не переписывает историю реестра: каждый коммит публикации — потомок головы, которую он видел, тег переносится только внутри одного атомарного пуша, защищённого ожидаемым состоянием обоих рефов, старые теги не трогаются, а прогон не выходит за пределы организации, названной в манифесте.

> [p27] Never rewrite `main`: every changed publish commit is a child of the exact observed head. Never issue an unconditional force: moving the selected mutable version tag is permitted only inside one `git push --atomic` that carries exact `--force-with-lease` expectations for both `main` and the tag. Never fall back to sequential branch/tag pushes; if atomic push is unsupported or either lease is stale, neither ref moves. Never alter older version tags. Never create a repo in a different org than the configured one unless `--org <other>` is passed explicitly. Never escalate scope: a publish run targets exactly the org named in the project's `[[registry]]` URL — adapters MUST refuse to create or modify anything outside that org.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-registry/PROP-002#PUBLISH-NEVER-RULES>

## Несколько пакетов сразу {#workspaces}

[p28] Репозиторий, в котором разрабатывают несколько пакетов, публикует их командой `vibe workspace publish`. Она упорядочивает участников по их зависимостям друг от друга и публикует каждого отдельным репозиторием. На первой неудаче она останавливается с отчётом, что опубликовано и что осталось. Каждая опубликованная копия несёт таблицу `[origin]` с именем репозитория и коммита, из которых она вышла, так что копию всегда можно проследить до источника. `--member` ограничивает прогон одним узлом; `--dry-run` показывает выбор и порядок без пуша.

> [p29] **Decision.** `vibe workspace publish` (PROP-007 §2.7) regenerates the boot artifacts of each staged copy for the **published shape** — where dependencies are registry-resolved and version-pinned, not path-sourced.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-009#PUBLISH-REGEN>

## Особые случаи и правила {#edge-cases}

[p30] `--repo-url` отправляет прямо в существующий git-репозиторий вашими локальными учётными данными git и не загружает токен публикации; используйте его для хостов без API-адаптера.

[p31] Токен никогда не передаётся установочному скрипту пакета и никогда не печатается, даже в машинном выводе; если команда его напечатала, это ошибка, о которой стоит сообщить.

> [p32] The publish token
>   ([PROP-000 §20](../../common/PROP-000.xml#token-secrecy)) is **never** placed in
>   a hook's environment.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-020#TOKEN-NEVER-IN-ENV>

[p33] Пакет документации публикуется так же; отличается лишь то, как его потребляют: через `vibe cache add` и сайт, а не через `vibe install`.

