<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
  <title id="root">PROP-001: Git-backed registry for `vibe-registry`</title>
  <status stage="impl" state="done" comment="B0 2026-07-24: accepted and shipped 2026-04-22; partially superseded by PROP-002"/>
  <p p="1"><fact id="milestone-line" status="impl/done">**Milestone:** M1.1 ([`ROADMAP.md`](../../../ROADMAP.md#m11--git-backed-registry)).</fact></p>
  <p p="2"><fact id="status-line" status="impl/done">**Status:** accepted 2026-04-22, shipped 2026-04-22. **Partially superseded by [PROP-002](PROP-002-decentralized-registry.xml) (2026-04-24).** See the "Superseded parts" block below.</fact></p>
  <p p="3"><fact id="supersedes-line" status="spec/done">**Supersedes:** nothing.</fact></p>
  <p p="4"><fact id="related" status="spec/done">**Related:** [spec://org.vibevm.core/vibevm/common/PROP-000#registry](../../common/PROP-000.xml#registry), [`VIBEVM-SPEC.md` §8](../../../VIBEVM-SPEC.md), [PROP-002](PROP-002-decentralized-registry.xml).</fact></p>
  <section title="Superseded parts (by PROP-002)">
    <p p="5"><fact id="superseded-lead" status="spec/done">The following decisions in this PROP were revised by [PROP-002](PROP-002-decentralized-registry.xml) when the registry model moved from monorepo-as-registry to decentralized per-package repos. Use PROP-002 as the authoritative source for these:</fact></p>
    <list ordered="false" p="6">
      <item><fact id="SUP-REGISTRY-TRAIT" status="spec/done">**§2.3 `Registry` trait** — the single-registry trait is extended by a `MultiRegistryResolver` coordinating several `[[registry]]` entries, each wrapped as a `GitPackageRegistry`. The monorepo-era `GitRegistry` is retired.</fact></item>
      <item><fact id="SUP-CACHE-LAYOUT" status="spec/done">**§2.4 Cache layout** — `~/.vibe/registries/&lt;hash&gt;/clone/` (one clone per registry URL) is replaced by `~/.vibe/registries/&lt;canonical-url-hash&gt;/packages/&lt;kind&gt;-&lt;name&gt;/{clone,meta.toml}` (one clone per package; the package directory was later re-keyed by identity to `&lt;group&gt;.&lt;name&gt;` — PROP-002 §2.6).</fact></item>
      <item><fact id="SUP-SOURCE-URI" status="spec/done">**§2.6 Lockfile `source_uri` format** — `git+&lt;transport&gt;://&lt;host&gt;/&lt;path&gt;.git#&lt;kind&gt;/&lt;name&gt;/v&lt;ver&gt;` (path-in-monorepo) is replaced by full lockfile fields: `registry`, `source_url`, `source_ref`, `resolved_commit`, `content_hash`; `#fragment` is no longer used.</fact></item>
    </list>
    <p p="7"><fact id="NOT-SUPERSEDED" status="spec/done">What is **not** superseded (and remains authoritative here): §2.1 (shell-out-to-git backend choice), §2.2 (`GitBackend` trait), §2.5 (1-hour freshness TTL), §2.7 (Windows UX and stderr classification).</fact></p>
    <list ordered="false" p="8">
      <item><fact id="ARG-PRUNED" status="spec/done">Additionally, the size-footprint argument in §2.1 is pruned by [PROP-000 §15](../../common/PROP-000.xml#dep-weight) (dependency weight is not a decision factor).</fact></item>
      <item><fact id="remaining-args" status="spec/done">The remaining arguments against `git2` (Windows SSH-auth lottery, diagnostic clarity of shell-out error messages) still carry the decision for M1 — but the argument tree is narrower now.</fact></item>
      <item><fact id="revisit-narrowed" status="spec/done">Revisit when a concrete reason arises, e.g. programmatic object reads that shell-out can't do cheaply.</fact></item>
    </list>
  </section>
  <section id="motivation" title="1. Motivation">
    <p p="9"><fact id="m0-state" status="impl/done">M0 shipped with a local-directory registry only.</fact></p>
    <p p="10"><fact id="M1-GOAL" status="impl/done">M1 makes the registry a git
repository hosted on GitVerse (default
`git@gitverse.ru:anarchic/vibespecs.git`). The implementation must:</fact></p>
    <list ordered="false" p="11">
      <item><fact id="REQ-CLONE-PULL" status="impl/done">Clone the registry into `~/.vibe/registries/&lt;hash&gt;/` on first use and
  `git pull` on subsequent use (`VIBEVM-SPEC.md` §8.3).</fact></item>
      <item><fact id="REQ-PRESERVE-LOCAL" status="impl/done">Preserve the existing `LocalRegistry` code path so tests and the
  `--registry &lt;path&gt;` override keep working.</fact></item>
      <item><fact id="REQ-SSH-AUTH" status="impl/done">Authenticate against GitVerse using the SSH identity the user has
  already configured (see [`vibevm/vibespecs/boot/90-user.xml`](../../boot/90-user.xml)).</fact></item>
      <item><fact id="REQ-CROSS-PLATFORM" status="impl/done">Run on Windows, macOS, and Linux with no per-platform build hoops.</fact></item>
      <item><fact id="REQ-FOOTPRINT" status="impl/done">Carry its operational weight on constrained dev machines without
  bloating the `vibe` binary or adding a C toolchain requirement.</fact></item>
    </list>
    <p p="12"><fact id="mechanics-in-readme" status="impl/done">This PROP records the architectural decisions. The mechanics
(`Registry` trait surface, error variants, wire-level command lines)
live in the crate's module documentation — `lib.rs` doc comments and the error
strings that cite `spec://` anchors — not in a README; `crates/vibe-registry`
has none.</fact></p>
  </section>
  <section id="decisions" title="2. Decisions">
    <section id="backend" title="2.1 Backend: shell out to `git`, not `git2`">
      <p p="13"><fact id="SHELL-OUT" status="impl/done">**Decision:** `vibe-registry` performs all git operations by spawning the
system `git` binary via `std::process::Command`. We do **not** link
against `libgit2` (via the `git2` crate) in v1.</fact></p>
      <p p="14"><fact id="backend-why-lead" status="impl/done">**Why:**</fact></p>
      <list ordered="true" p="15">
        <item><fact id="WHY-SSH-WINDOWS" status="impl/done">**SSH on Windows is the killer.** GitVerse authenticates via SSH.
   Git for Windows ships OpenSSH and a working `ssh-agent`; the user's
   identity (`olegchir@UNIT-2040`) is already loaded and the push to
   `gitverse.ru` is proven (see [`vibevm/vibespecs/boot/90-user.xml`](../../boot/90-user.xml)).
   `libgit2` uses `libssh2` for SSH, which talks to `ssh-agent`
   through a named-pipe protocol that is fragile on Windows and
   routinely requires `SSH_AUTH_SOCK` juggling or explicit key paths.
   Cargo itself falls back to the system `git` on auth failure for
   this exact reason. Shell-out makes that fallback the primary path
   and retires the class of bug.</fact></item>
      </list>
      <list ordered="true" p="16">
        <item><fact id="WHY-FOOTPRINT" status="impl/done">**Dependency footprint.** `git2` pulls `libgit2-sys`,
   `libssh2-sys`, `libz-sys`, and `openssl-sys` (or a vendored
   alternative). Non-vendored builds demand a working C toolchain on
   every developer and CI machine; vendored builds add 3–8 MB to the
   release binary. Shell-out adds zero bytes and zero build-time
   native dependencies.</fact></item>
      </list>
      <list ordered="true" p="17">
        <item><fact id="WHY-DEBUGGABILITY" status="impl/done">**Feature parity and debuggability.** The user's `git` is by
   definition current. Errors surface with the full native message;
   `tracing` logs the exact argv so a user can re-run the failing
   command by hand. `libgit2`'s error strings (`ERROR class=Net (12):
   unexpected http status code: 401`) are harder to diagnose.</fact></item>
      </list>
      <list ordered="true" p="18">
        <item><fact id="WHY-NO-PROGRAMMATIC" status="impl/done">**We do not need programmatic git.** The v1 operations are
   `git clone`, `git fetch`, `git pull --ff-only`, and `git
   --version` for preflight. No partial clone, no in-memory object
   reads, no custom refspecs, no progress UI. Shell-out handles this
   trivially.</fact></item>
      </list>
      <list ordered="true" p="19">
        <item><fact id="WHY-LICENSING" status="impl/done">**Licensing.** `git` is GPL v2, but shell-out is `exec` not
   linkage — the GNU FAQ explicitly separates these. Our binary stays
   unambiguously permissive.
   `libgit2` is GPL v2 with a Linking Exception (permissive for our
   purposes), but shell-out leaves the entire conversation at the
   door.</fact></item>
      </list>
      <p p="20"><fact id="risks-lead" status="impl/done">**Risks accepted:**</fact></p>
      <list ordered="false" p="21">
        <item><fact id="RISK-GIT-IN-PATH" status="impl/done" action="continue" actionstage="doc" audience="user">**Runtime dependency on `git` in `PATH`.** Acceptable: our target
  audience is developers who already have git installed. We perform a
  preflight `git --version` check and emit an actionable error (with
  a pointer to `https://git-scm.com/downloads`) if it is missing.</fact></item>
        <item><fact id="RISK-STDERR-PARSING" status="impl/done">**stderr parsing for fine-grained error classification.** We
  mitigate by running git with `LC_ALL=C` and keying off exit
  code + substring markers (`fatal: ` prefix, `Permission denied
  (publickey)`, `Repository not found`). See §2.7.</fact></item>
      </list>
      <p p="22"><fact id="revisit-lead" status="spec/done">**When to revisit:** if and when we need one of:</fact></p>
      <list ordered="false" p="23">
        <item><fact id="revisit-sparse" status="spec/done">partial/sparse clone with custom filters,</fact></item>
        <item><fact id="revisit-object-reads" status="spec/done">programmatic object reads (e.g. to fetch a `latest` marker file
  without a working-tree checkout),</fact></item>
        <item><fact id="revisit-credential-store" status="spec/done">OS-credential-store integration that can't be delegated to `git`,</fact></item>
        <item><fact id="revisit-bundling" status="spec/done">running on a platform where bundling `git` is easier than requiring
  it.</fact></item>
      </list>
      <p p="24"><fact id="REVISIT-PATH" status="spec/done">At that point, add a `libgit2` feature behind the `GitBackend` trait
(§2.2). The trait is designed so the switch costs one `impl` block
and one line in the factory, and nothing else in the codebase moves.</fact></p>
    </section>
    <section id="backend-trait" title="2.2 `GitBackend` trait">
      <p p="25"><fact id="GITBACKEND-TRAIT" status="impl/done">**Decision:** `vibe-registry::git_backend::GitBackend` is the single
interface through which the registry layer touches git. It has exactly
the operations we use:</fact></p>
      <fence lang="rust" p="26">pub trait GitBackend: Send + Sync {
    /// Clone `url` (checked out at `refname`) into `dest`.
    /// Caller guarantees `dest` is either empty or absent.
    fn bootstrap(&amp;self, url: &amp;str, refname: &amp;str, dest: &amp;Path) -&gt; Result&lt;(), GitError&gt;;

    /// Fast-forward `dest` to the tip of `refname` on origin.
    /// No-op if already up to date.
    fn update(&amp;self, dest: &amp;Path, refname: &amp;str) -&gt; Result&lt;(), GitError&gt;;
}</fence>
      <p p="27"><fact id="METHOD-NAME-NOTE" status="impl/done">**Method-name note.** The "make a fresh clone" operation is called
`bootstrap` rather than the obvious `clone` or `clone_into` because
the backend is held as `Arc&lt;dyn GitBackend&gt;` at its call sites and
both of those names collide with blanket-impl methods from the
standard library (`std::clone::Clone::clone`,
`std::borrow::ToOwned::clone_into`), forcing ugly `&lt;T as
GitBackend&gt;::…` disambiguations at every call. `bootstrap` is
semantically accurate — it's how we initialise the registry cache
from empty state — and has no std-library namesake.</fact></p>
      <p p="28"><fact id="WHY-NARROW" status="impl/done">**Why narrow.** The narrower the trait, the cheaper the backend swap.
If we need `ls_remote` or `fetch_ref` later, we add a method — that
addition is a visible, deliberate change, not a quiet interface drift.</fact></p>
      <p p="29"><fact id="implementations-lead" status="impl/done">**Implementations:**</fact></p>
      <list ordered="false" p="30">
        <item><fact id="IMPL-SHELLGIT" status="impl/done">`ShellGit` — default, built from `std::process::Command`. See §2.7.</fact></item>
        <item><fact id="IMPL-LIBGIT2-RESERVED" status="spec/done">`LibGit2` — reserved. Not implemented in M1; the trait is the entry
  point for a future feature-gated addition.</fact></item>
      </list>
      <p p="31"><fact id="NO-MOCK" status="impl/done">The `vibe-registry` crate does not expose a mock implementation.
Tests use `ShellGit` against a bare git repository created in a
`tempdir` — exercising the production code path end-to-end.</fact></p>
    </section>
    <section id="registry-trait" title="2.3 `Registry` trait">
      <p p="32"><fact id="REGISTRY-TRAIT" status="spec/done">**Decision:** introduce a `vibe-registry::Registry` trait that both
`LocalRegistry` and `GitRegistry` implement:</fact></p>
      <fence lang="rust" p="33">pub trait Registry {
    fn list_versions(&amp;self, kind: PackageKind, name: &amp;str)
        -&gt; Result&lt;Vec&lt;semver::Version&gt;, RegistryError&gt;;
    fn resolve(&amp;self, pkgref: &amp;PackageRef)
        -&gt; Result&lt;ResolvedPackage, RegistryError&gt;;
    fn fetch(&amp;self, resolved: &amp;ResolvedPackage, cache_root: &amp;Path)
        -&gt; Result&lt;CachedPackage, RegistryError&gt;;
}</fence>
      <p p="34"><fact id="CONSUMERS-UNCHANGED" status="spec/done">`vibe-install` and `vibe-cli` continue to consume `ResolvedPackage` /
`CachedPackage` exactly as in M0; the only change is that the concrete
type is chosen at CLI-arg-parse time.</fact></p>
      <p p="35"><fact id="SELECTION-RULE" status="impl/done">**Selection rule.** CLI precedence stays as defined in `VIBEVM-SPEC.md`
§9.1: `--registry &lt;path&gt;` (explicit, always a local directory) wins
over the `[registry]` section in `vibe.toml` (a URL — git or
`file://`).</fact></p>
    </section>
    <section id="cache-layout" title="2.4 Cache layout">
      <p p="36"><fact id="CACHE-LAYOUT" status="spec/done">**Decision:** the on-disk layout under `~/.vibe/registries/` is:</fact></p>
      <fence p="37">~/.vibe/registries/
└── &lt;hash&gt;/
    ├── clone/        ← the git working tree
    └── meta.toml     ← { url, ref, last_pulled_at }</fence>
      <list ordered="false" p="38">
        <item><fact id="LAYOUT-HASH" status="spec/done">`&lt;hash&gt;` = lowercase hex of the first 16 bytes of
  `sha256(normalized_url)`. 16 hex chars is enough to avoid realistic
  collisions while keeping the directory name tab-completable (same
  trick Cargo uses for its git cache). The full hash lives in
  `meta.toml` for audit.</fact></item>
        <item><fact id="LAYOUT-NORMALIZED-URL" status="spec/done">`normalized_url` strips a trailing `.git` and lowercases the
  scheme + host so `git@gitverse.ru:anarchic/vibespecs.git` and
  `ssh://git@gitverse.ru/anarchic/vibespecs` hash to the same
  registry.</fact></item>
        <item><fact id="LAYOUT-META" status="spec/done">`meta.toml` is written after each successful clone or update. It
  carries the url (for debugging), the ref, and the UTC RFC3339
  timestamp of the last successful fetch.</fact></item>
        <item><fact id="LAYOUT-CLONE-DELEGATE" status="spec/done">The `clone/` subdirectory is the registry working tree. `GitRegistry`
  internally wraps a `LocalRegistry::new(clone_dir)` and delegates
  `resolve` / `list_versions` / `fetch` to it — the packaged layout
  (`&lt;kind&gt;/&lt;name&gt;/v&lt;ver&gt;/…`) is identical in both worlds.</fact></item>
      </list>
      <p p="39"><fact id="PER-PROJECT-CACHE-UNCHANGED" status="spec/done">Per-project package cache (`&lt;project&gt;/.vibe/cache/&lt;kind&gt;/&lt;name&gt;/&lt;ver&gt;/`)
is unchanged from M0.</fact></p>
    </section>
    <section id="freshness" title="2.5 Freshness policy">
      <p p="40"><fact id="FRESHNESS-TTL" status="impl/done" action="continue" actionstage="doc" audience="user">**Decision:** the default freshness TTL is **1 hour**, checked against
`meta.toml.last_pulled_at`. An install whose registry cache is older
than the TTL triggers an implicit `update`. An install whose cache is
younger skips the pull. `vibe registry sync` forces an update
regardless of age.</fact></p>
      <p p="41"><fact id="ttl-why" status="spec/done">**Why 1 hour:** short enough to pick up new package versions within
one working session, long enough to amortise network round-trips over
a burst of installs. Revisit once real usage arrives.</fact></p>
      <p p="42"><fact id="NO-OFFLINE-YET" status="impl/done">**Superseded — `--offline` shipped.** This recorded the M1 state: a
network failure during an implicit update failed the install with a clear
message. Offline resolution landed with [PROP-002 §2.2.2.1](PROP-002-decentralized-registry.xml)
(`url_is_local`) and the PROP-030 flag, and is live in `vibe install --help`.</fact></p>
    </section>
    <section id="source-uri" title="2.6 Lockfile `source_uri` format">
      <p p="43"><fact id="SOURCE-URI-FORMAT" status="spec/done">**Decision:** when a package originates from a git registry, the
lockfile records its source as</fact></p>
      <fence p="44">git+ssh://git@gitverse.ru/anarchic/vibespecs.git#&lt;kind&gt;/&lt;name&gt;/v&lt;ver&gt;</fence>
      <p p="45"><fact id="fragment-note" status="spec/done">The `#fragment` names the package directory inside the registry
relative to the registry root. The scheme prefix (`git+ssh` /
`git+https` / `git+file`) encodes the transport. Local-directory
registries continue to produce `file://…` URIs as in M0.</fact></p>
      <p p="46"><fact id="scheme-prefix-why" status="spec/done">**Why a scheme prefix.** `pip` and Cargo both use `git+…` prefixes to
disambiguate a git source from a plain URL; it reads obviously in
the lockfile.</fact></p>
    </section>
    <section id="windows-ux" title="2.7 Windows UX and stderr parsing">
      <p p="47"><fact id="NO-WINDOW-FLAG" status="impl/done">**Decision:** on Windows, every `git` subprocess is spawned with the
`CREATE_NO_WINDOW` creation flag (`0x08000000`) via
`std::os::windows::process::CommandExt::creation_flags`.</fact></p>
      <p p="48"><fact id="no-window-why" status="impl/done">**Why.** If `vibe` ever runs inside a process without a console of its
own (a GUI launcher, IDE plugin, Windows service), a child with
`CREATE_CONSOLE` semantics would flash a separate black window. The
flag costs nothing in the console-attached case (stdio still
inherits), and covers the hypothetical hostless case for free.</fact></p>
      <p p="49"><fact id="LOCALE-STDERR" status="impl/done">**Decision:** every `git` invocation runs with `LC_ALL=C` and
`LANG=C` in the environment so error strings are stable across user
locales. We key error classification off:</fact></p>
      <list ordered="false" p="50">
        <item><fact id="CLASS-EXIT-CODE" status="impl/done">exit code (zero vs non-zero),</fact></item>
        <item><fact id="CLASS-STDERR-SUBSTRINGS" status="impl/done">stderr substrings: `fatal: repository … not found`,
  `Permission denied (publickey)`, `Could not resolve host`,
  `Repository .* is empty`, `unable to access`.</fact></item>
      </list>
      <p p="51"><fact id="CATCH-ALL" status="impl/done">Anything unmatched is reported as a generic "git command failed"
with the raw stderr attached. Stable classification covers the
diagnoses we hand-hold the user through; the catch-all covers the
rest without hiding information.</fact></p>
    </section>
  </section>
  <section id="rejected" title="3. Rejected alternatives">
    <section title="3.1 `git2` crate as the primary backend">
      <p p="52"><fact id="REJ-GIT2" status="spec/done">Rejected for M1. See §2.1. The decision is reversible via
`GitBackend` (§2.2).</fact></p>
    </section>
    <section title="3.2 Hybrid `git2` + shell-out fallback">
      <p p="53"><fact id="REJ-HYBRID" status="spec/done">Cargo does this. Rejected for v1 because it doubles the surface area
(two backends under one implementation), makes error messages
conditional on which path fired, and provides zero benefit on our
target matrix. Revisit only if we ever take the `libgit2` branch and
need auth fallback to system `git`.</fact></p>
    </section>
    <section title="3.3 Sparse / partial clone in M1">
      <p p="54"><fact id="REJ-SPARSE" status="spec/done">Rejected: `vibespecs` is tiny. Optimisation is M2. The `GitBackend`
trait is narrow enough that adding a `clone_sparse` method later is a
one-line extension.</fact></p>
    </section>
    <section title="3.4 Hosting the registry cache under the project">
      <p p="55"><fact id="REJ-PROJECT-CACHE" status="spec/done">Rejected: cache-per-project duplicates the same git clone across every
project on the same machine. `VIBEVM-SPEC.md` §8.3 already pins the
cache at `~/.vibe/registries/&lt;hash&gt;/` for this reason.</fact></p>
    </section>
    <section title="3.5 Vendoring `git` with the `vibe` binary">
      <p p="56"><fact id="REJ-VENDORING" status="spec/done">Rejected: vendoring a full git is the antithesis of "single Rust
binary". If we ever want zero runtime dependencies, the answer is
`libgit2`, not a bundled git.</fact></p>
    </section>
  </section>
  <section id="out-of-scope" title="4. Out of scope for M1.1">
    <list ordered="false" p="57">
      <item><fact id="OOS-HTTPS-AUTH" status="spec/done">Authentication for HTTPS registries with token / PAT
  (M2, PROP later).</fact></item>
      <item><fact id="OOS-PUBLISH" status="spec/done">`vibe publish` (`VIBEVM-SPEC.md` §8.4 pins this to v2+).</fact></item>
      <item><fact id="OOS-LLM-REVIEW" status="spec/done">LLM-based install review (`VIBEVM-SPEC.md` §8.5, M2).</fact></item>
      <item><fact id="OOS-PROGRESS-UI" status="spec/done">Progress UI for long clones.</fact></item>
      <item><fact id="OOS-MULTI-REGISTRY" status="spec/done">Multiple registries per project.</fact></item>
      <item><fact id="OOS-OFFLINE" status="spec/done">`--offline` flag.</fact></item>
    </list>
  </section>
  <section id="acceptance" title="5. Acceptance (for M1.1 implementation)">
    <p p="58"><fact id="acceptance-lead" status="impl/done">Code-complete and live on 2026-04-22. Every box below ticks; the
milestone is shippable.</fact></p>
    <list ordered="false" p="59">
      <item><fact id="ACC-REGISTRY-TRAIT" status="impl/done">[x] `vibe-registry` exposes a `Registry` trait and two
  implementations (`LocalRegistry`, `GitRegistry`).</fact></item>
      <item><fact id="ACC-GITBACKEND" status="impl/done">[x] `GitBackend` trait + `ShellGit` implementation land in
  `vibe-registry::git_backend`.</fact></item>
      <item><fact id="ACC-PREFLIGHT" status="impl/done">[x] `ShellGit` preflight (`git --version`) runs once per instance
  (cached via `OnceLock`) and emits `GitError::NotInstalled` with
  an actionable message if absent.</fact></item>
      <item><fact id="ACC-BOOTSTRAP-UPDATE" status="impl/done">[x] `ShellGit::bootstrap` and `ShellGit::update` succeed against
  a bare fixture repo in an integration test.</fact></item>
      <item><fact id="ACC-CACHE-PATH" status="impl/done">[x] Cache lives at `~/.vibe/registries/&lt;hash&gt;/{clone,meta.toml}`.</fact></item>
      <item><fact id="ACC-META-TIMESTAMP" status="impl/done">[x] `meta.toml` gains a well-formed `last_pulled_at` after each
  fetch.</fact></item>
      <item><fact id="ACC-FRESHNESS" status="impl/done">[x] Freshness policy: ≤1h skips pull; &gt;1h pulls; `vibe registry
  sync` always pulls (TTL=0 uses `&gt;=` so same-second wallclock
  still triggers).</fact></item>
      <item><fact id="ACC-E2E-INSTALL" status="impl/done">[x] End-to-end install against a `git+file://…` registry seeded
  with the canonical `flow:wal@0.1.0` fixture succeeds; the
  lockfile records a `git+…#flow/wal/v0.1.0` source URI.</fact></item>
      <item><fact id="ACC-MANUAL-SMOKE" status="impl/done">[x] Manual smoke-test against the real
  `git@gitverse.ru:anarchic/vibespecs.git` (commit `98e51fc`)
  ran 2026-04-22 on Windows / Git Bash; every step matched the
  expected output, including the
  `git+ssh://git@gitverse.ru/anarchic/vibespecs.git#flow/wal/v0.1.0`
  lockfile source URI. Procedure and last-pass metadata live in
  [`manual-tests/M1.1-git-registry-smoke.md`](../../../manual-tests/M1.1-git-registry-smoke.md).</fact></item>
      <item><fact id="ACC-SYNC-FORCE" status="impl/done">[x] `vibe registry sync` (no args) force-pulls the configured
  registry.</fact></item>
      <item><fact id="ACC-NO-WINDOW" status="impl/done">[x] Windows: every spawned git carries `CREATE_NO_WINDOW`; no
  stray console windows from a hostless parent.</fact></item>
      <item><fact id="ACC-TESTS-GREEN" status="impl/done">[x] `cargo test --workspace` green (77 tests).</fact></item>
      <item><fact id="ACC-CLIPPY" status="impl/done">[x] `cargo clippy --workspace --all-targets -- -D warnings` clean.</fact></item>
    </list>
  </section>
  <section id="open-questions" title="6. Open questions">
    <p p="60"><fact id="parking-lot-lead" status="spec/work">None blocking. Parking lot:</fact></p>
    <list ordered="false" p="61">
      <item><fact id="OPEN-GIT-BINARY-PATH" status="impl/done" action="continue" actionstage="doc" audience="user">**Resolved — shipped as proposed.** The `VIBE_GIT_BINARY`
  PATH override lives in `git_backend/shell.rs` and its comment cites §6 of this
  PROP; the env-var form was chosen over a CLI flag exactly to keep the CLI
  surface stable.</fact></item>
      <item><fact id="OPEN-CACHE-LOCK" status="spec/work">Does the registry cache need a lock file against concurrent `vibe`
  invocations? Probably yes for M2; a crash mid-clone leaves a
  half-populated `clone/`. For M1, document the behaviour ("if a
  clone fails, delete the cache dir and retry") rather than
  mechanising it.</fact></item>
    </list>
  </section>
</spec>
