<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
  <title id="root">PROP-036: `vibe tree` — the spec-tree analyzer</title>
  <status stage="impl" state="done" comment="C 2026-07-25: PACKAGE-TREE-PLAN executed; the analyzer ships (tree --json against package-tree.schema.v1.json, -t live)"/>
  <p p="1"><fact id="status-line" status="impl/done">**Status: IMPLEMENTED** (requirements authored 2026-07-15 at the owner's
request; the PACKAGE-TREE-PLAN executed against them, verified against the tree
2026-07-25 by the spec-actualization campaign — `vibe tree --json` validates
against the shipped `package-tree.schema.v1.json` per its own `--help`, and `-t`
is live). Governs the `vibe tree` command in `crates/vibe-cli`. Written in the
post-rename link vocabulary (PROP-035): two link types, `static` and `dynamic`.</fact></p>
  <p p="2"><fact id="related" status="spec/done">**Related:** [PROP-009 §2.4](../vibe-workspace/PROP-009-loading-model.xml#inclusion-types)
(the `static`/`dynamic` link types + `when`), [PROP-009 §2.3](../vibe-workspace/PROP-009-loading-model.xml#artifacts)
(`STATIC.md` / `INDEX.md`), [PROP-034](../vibe-workspace/PROP-034-transitive-links-boot-graph.xml)
(`static-transitive`), [PROP-035](../vibe-workspace/PROP-035-spec-compiler.xml)
(`@spec` in-place uses, `STATIC.md` markers), [PROP-002 §lockfile](../vibe-registry/PROP-002-decentralized-registry.xml)
(the resolved graph). Plan: [`PACKAGE-TREE-PLAN-v0.1`](../../../legacy-spec/terraforms/PACKAGE-TREE-PLAN-v0.1.md).</fact></p>
  <p p="3"><fact id="non-goal-line" status="spec/done">**Non-goal (deferred):** the runtime "what the agent actually loaded" skill and a
GUI client — a future `tool:org.vibevm.core/package-tree` (§7).</fact></p>
  <section id="motivation" title="1. Motivation">
    <list ordered="false" p="4">
      <item><fact id="boot-composition" status="spec/done">A project's boot is composed by `vibe` from a dependency closure (PROP-009):
  some packages are compiled verbatim into `STATIC.md`, others are read by
  reference from `INDEX.md`, some carry an OS `when`, and `static-transitive`
  edges (PROP-034) silently promote whole subtrees.</fact></item>
      <item><fact id="visibility-gap" status="spec/done">Today a human cannot see this
  at a glance — **what is connected, and how does it load?**</fact></item>
      <item><fact id="TREE-ANSWER" status="spec/done" action="continue" actionstage="doc" audience="user">`vibe tree` answers
  that algorithmically: it renders the resolved package tree annotated with the
  *effective* load type and the flags that explain it, and emits the same data as
  JSON for downstream tools.</fact></item>
    </list>
  </section>
  <section id="decisions" title="2. Decisions">
    <section id="command" title="2.1 The command">
      <list ordered="false" p="5">
        <item><fact id="TREE-READ-ONLY" status="spec/done" action="continue" actionstage="doc" audience="user">`vibe tree` is a **read-only** analyzer over the current project. It mutates
  nothing (REQ: never writes to the tree, manifests, lockfile, or artifacts).</fact></item>
        <item><fact id="TREE-INPUTS" status="spec/done" action="continue" actionstage="doc" audience="user">It
  operates on the workspace discovered from `--path` (default `.`), reading the
  committed `vibe.lock`, the node manifests, and the generated boot artifacts.</fact></item>
      </list>
      <p p="6"><fact id="output-surfaces-lead" status="spec/done">Three output surfaces:</fact></p>
      <list ordered="false" p="7">
        <item><fact id="OUT-TUI" status="spec/done" action="continue" actionstage="doc" audience="user">**interactive TUI** — the default on a tty (§2.11);</fact></item>
        <item><fact id="OUT-JSON" status="spec/done" action="continue" actionstage="doc" audience="user">**`--json`** — the machine surface (§2.7), the same data a GUI consumes;</fact></item>
        <item><fact id="OUT-PLAIN" status="spec/done" action="continue" actionstage="doc" audience="user">**plain** — a static ASCII tree when not on a tty, or under `--plain`.</fact></item>
      </list>
      <list ordered="false" p="8">
        <item><fact id="NO-TUI-UNDER-FLAGS" status="spec/done">`--json` and `--plain` never launch the TUI.</fact></item>
        <item><fact id="CLI-SURFACE" status="spec/done">The command is `Tree(TreeArgs)` in
  the CLI surface; `--json`/`--quiet` are the global flags (never redeclared).</fact></item>
      </list>
    </section>
    <section id="columns" title="2.2 Row semantics — the columns">
      <p p="9"><fact id="ROW-PER-PACKAGE" status="spec/done" action="continue" actionstage="doc" audience="user">Each package is one row. Columns, left to right (REQ: this order):</fact></p>
      <list ordered="true" p="10">
        <item><fact id="COL-NAME" status="spec/done">**name** — the tree column: `group/name`, drawn with indentation + branch
   glyphs + an expand/collapse indicator when the node has children.</fact></item>
        <item><fact id="COL-LOAD" status="spec/done">**load** — the *effective* load type (§2.3): `static`, `dynamic`, or `none`.</fact></item>
        <item><fact id="COL-TRANSITIVE" status="spec/done">**T** (transitive) — a checkbox: the effective `static` was forced by a
   `static-transitive` ancestor, not the package's own declaration (§2.4).</fact></item>
        <item><fact id="COL-CONDITION" status="spec/done">**C** (condition) — a checkbox: the boot entry carries a `when` (§2.5).</fact></item>
        <item><fact id="COL-STATIC" status="spec/done">**S** (STATIC.md) — a checkbox: the package physically contributes a block to
   `STATIC.md`.</fact></item>
      </list>
      <list ordered="false" p="11">
        <item><fact id="checkbox-cluster" status="spec/done">`T`, `C`, `S` are the "checkbox" cluster; `load` is a value column.</fact></item>
        <item><fact id="DETAIL-ON-DEMAND" status="spec/done">Detail beyond
  a checkbox (the full `when` text, the source path) is shown on demand (§2.11),
  never crammed into the row.</fact></item>
      </list>
    </section>
    <section id="effective-load" title="2.3 Effective load type">
      <p p="12"><fact id="EFFECTIVE-FROM-ARTIFACTS" status="spec/done" action="continue" actionstage="doc" audience="user">The `load` value is the lane a package's boot snippet **actually lands in**, read
from the committed artifacts (REQ):</fact></p>
      <list ordered="false" p="13">
        <item><fact id="LOAD-STATIC" status="spec/done">`static` — the package appears as a `&lt;!-- vibe:static {origin} … --&gt;`
  contribution in `STATIC.md` (`origin = group/name`).</fact></item>
        <item><fact id="LOAD-DYNAMIC" status="spec/done">`dynamic` — the package's boot file appears as an `[[entry]]` in `INDEX.md`.</fact></item>
        <item><fact id="LOAD-NONE" status="spec/done">`none` — the package ships no `[boot_snippet]`, so it contributes to neither
  lane (e.g. a content-minimal family aggregator, PROP-028).</fact></item>
      </list>
      <p p="14"><fact id="DECISION-ARTIFACTS-CANONICAL" status="spec/done">**Decision — read the effective type from the artifacts, not a fresh recompute.**</fact></p>
      <list ordered="false" p="15">
        <item><fact id="decision-artifacts-why" status="spec/done">**Why:** the committed `STATIC.md` / `INDEX.md` are exactly what an agent
  reads at boot; the tool's job is to show that reality ("what is actually
  loaded"). A stale artifact is a fact the human needs, not one to hide.</fact></item>
        <item><fact id="decision-artifacts-rejected" status="spec/done">**Considered and rejected:** recomputing `EffectiveBoot` fresh every run —
  shows what *should* be, masking drift the tool exists to reveal.</fact></item>
        <item><fact id="decision-artifacts-revisit" status="spec/done">**When to revisit:** if the artifacts stop being committed (generated
  on-demand), the source of truth moves to the recompute; until then the
  committed lane is canonical, cross-checked by §2.10.</fact></item>
      </list>
    </section>
    <section id="transitive-flag" title="2.4 The transitive flag">
      <list ordered="false" p="16">
        <item><fact id="TRANSITIVE-FLAG-DEF" status="spec/done">`T` is set (REQ) iff the package's effective type is `static` **and** that
  static-ness was inherited from an ancestor edge declared `static-transitive`
  (PROP-034), rather than from the package's own consumer-declared `link`, its own
  `[boot_snippet].link` suggestion, or a direct `static` edge.</fact></item>
        <item><fact id="TRANSITIVE-DECLARER" status="spec/done">The
  `static-transitive` *declarer* itself carries `T = false` (its static-ness is its
  own declaration); every member of its closure that is not otherwise static
  carries `T = true`.</fact></item>
      </list>
    </section>
    <section id="condition-flag" title="2.5 The condition flag">
      <list ordered="false" p="17">
        <item><fact id="CONDITION-FLAG-DEF" status="spec/done">`C` is set (REQ) iff the package's boot entry carries a `when` condition
  (PROP-009 §2.4; wire form `os:&lt;name&gt;`).</fact></item>
        <item><fact id="WHEN-FORCES-DYNAMIC" status="spec/done">A `when` forces the entry `dynamic`
  regardless of link (so `C = true` implies `load = dynamic`).</fact></item>
        <item><fact id="CONDITION-DETAIL-ONLY" status="spec/done">The full condition
  text is surfaced only in the detail view (§2.11) and the JSON (§2.7), never in
  the row.</fact></item>
      </list>
    </section>
    <section id="static-size" title="2.6 The STATIC.md size indicator">
      <list ordered="false" p="18">
        <item><fact id="STATIC-SIZE-INDICATOR" status="spec/done" action="continue" actionstage="doc" audience="user">The status line shows the size of the statically-compiled lane: the byte count
  and line count of `STATIC.md` (REQ).</fact></item>
        <item><fact id="static-size-purpose" status="spec/done">This is the "how much am I loading verbatim
  every session" budget the human watches.</fact></item>
      </list>
    </section>
    <section id="json" title="2.7 JSON output">
      <list ordered="false" p="19">
        <item><fact id="JSON-CONTRACT" status="spec/done" action="continue" actionstage="doc" audience="user">`vibe tree --json` emits one object, the same data model the TUI renders, valid
  against the shipped schema (REQ: `schema_version = 1`).</fact></item>
        <item><fact id="JSON-ENVELOPE" status="spec/done">The envelope follows the
  CLI convention (`{"ok": true, "command": "tree", …}`).</fact></item>
        <item><fact id="JSON-MODEL" status="spec/done">The model carries: the
  `project` context, the declared `roots`, the `packages` array (each with `load`
  {`type`, `transitive`, `declared`, `origin`, `in_static_md`, `in_index_md`,
  `boot_path`} and `condition`), the two boot lanes under `boot`
  (`static_md`/`index_md`, with the lane sizes), the collected `in_place_specs`
  (§2.9), and `diagnostics` (§2.10).</fact></item>
        <item><fact id="JSON-NO-DISPLAY-STATE" status="spec/done">Display state (mode, ordering, tab, selection)
  is TUI-only and is **not** in the JSON.</fact></item>
        <item><fact id="JSON-SCHEMA-HOME" status="spec/done">Schema home:
  `crates/vibe-cli/resources/package-tree.schema.v1.json`.</fact></item>
      </list>
    </section>
    <section id="static-decompile" title="2.8 STATIC.md decompilation">
      <list ordered="false" p="20">
        <item><fact id="STATIC-DECOMPILE" status="spec/done">`vibe tree` decompiles `STATIC.md` into its contributions (REQ): each
  `&lt;!-- vibe:static {origin} — {path} --&gt;` marker opens a region running to the
  next marker or EOF; the region yields `origin` (the source `group/name` or host
  rel-path) and `path` (the workspace-relative source file).</fact></item>
        <item><fact id="EMBED-SPANS" status="spec/done">Nested
  `&lt;!-- embed: {addr} --&gt;` … `&lt;!-- /embed: {addr} --&gt;` pairs within a region are
  attributed as embed spans.</fact></item>
        <item><fact id="DEDICATED-DECOMPILER" status="spec/done">This is a **dedicated** decompiler for the on-disk
  `vibe:static` open-marker format — it is not `vibe_spec::decompile()`, which
  parses the distinct `vibe:begin`/`vibe:end` compiler format and returns empty on
  `STATIC.md`.</fact></item>
      </list>
    </section>
    <section id="in-place-specs" title="2.9 In-place `@spec` collection">
      <list ordered="false" p="21">
        <item><fact id="INPLACE-COLLECTION" status="spec/done">`vibe tree` collects the in-place boot-lane spec markers (REQ): `@spec://` uses
  and `#use` / `#embed` / `#source` directives, via the canonical fence-aware
  `vibe_spec::Directives::parse`.</fact></item>
        <item><fact id="BARE-SPEC-SKIPPED" status="spec/done">A bare `spec://` (no `@`) is discretionary and is
  **not** collected.</fact></item>
        <item><fact id="inplace-oos" status="spec/done">(Out of scope: the `#[spec(...)]` code-traceability surface —
  that is PROP-014.)</fact></item>
      </list>
    </section>
    <section id="diagnostics" title="2.10 Diagnostics">
      <p p="22"><fact id="DIAG-NON-FATAL" status="spec/done">`vibe tree` reports, non-fatally (REQ: never aborts rendering on these):</fact></p>
      <list ordered="false" p="23">
        <item><fact id="DIAG-STALE-ARTIFACTS" status="spec/done" action="continue" actionstage="doc" audience="user">**stale-artifacts** — the committed lanes disagree with a fresh `EffectiveBoot`
  recompute (the tree needs `vibe reinstall`).</fact></item>
        <item><fact id="DIAG-ROOT-DRIFT" status="spec/done" action="continue" actionstage="doc" audience="user">**root-drift** — `vibe.lock` `meta.root_dependencies` disagrees with the root
  `vibe.toml` `[requires.packages]` (the lock is behind).</fact></item>
      </list>
      <p p="24"><fact id="DIAG-SHAPE" status="spec/done">Each diagnostic carries a severity, a stable code, a message, and an optional
locator.</fact></p>
    </section>
    <section id="tui" title="2.11 The interactive TUI">
      <p p="25"><fact id="TUI-DEFAULT" status="spec/done">On a tty (default), `vibe tree` is an interactive pseudographic browser (REQ).
Contract:</fact></p>
      <list ordered="false" p="26">
        <item><fact id="TUI-NAVIGATION" status="spec/done">**Navigation:** `↑`/`↓` move the selection (the table scrolls to keep it
  visible; the selected row is highlighted); `←`/`→` pan horizontally when the
  tree is wider than the viewport.</fact></item>
        <item><fact id="TUI-KEYMAP-SUPERSEDED" status="impl/done">**The key bindings sketched in this section are superseded.** This §2.11 is
  the analyzer-era sketch; the shipped keymap is [PROP-037](PROP-037-tree-tui.xml)
  Spec 2's F-key scheme, and it is normative there — this section names the
  *capabilities*, never the keys.</fact></item>
        <item><fact id="TUI-FOLDING" status="impl/done">**Folding:** the selected node folds and unfolds (shipped as the
  `fold.toggle` action, [PROP-037 §13.5](PROP-037-tree-tui.xml#actions)). The
  whole-tree fold sketched here was **not carried into Spec 2 and does not
  ship** — the action catalogue has `fold.toggle` only.</fact></item>
        <item><fact id="TUI-DETAIL" status="impl/done">**Detail:** `Enter` opens a modal showing the row's full detail vertically
  (name, group, version, kind, load type, transitive + why, condition full text,
  STATIC.md membership, source, content hash, dependencies, boot file);
  `Esc` closes it. Quitting is `Esc` plus a confirm dialog
  ([PROP-037 §7.4](PROP-037-tree-tui.xml)), not the bare `q` this sketch assumed.</fact></item>
        <item><fact id="TUI-ORDERING" status="impl/done">**Ordering** (shown in the status line): **topological** (the analysis
  order, default) ↔ **alphabetical**. Chosen from the F2 sort menu
  ([PROP-037 §7.2](PROP-037-tree-tui.xml), which replaces any bare mode-cycle key).</fact></item>
        <item><fact id="TUI-DISPLAY-MODES" status="impl/done">**Display mode:** (a) all-together tree; (b) two stacked
  sub-tables `static dependencies` / `dynamic dependencies` (a header line each);
  (c) two tabs `Static` / `Dynamic`, with a swappable static/dynamic priority in
  (b) and (c). Modes are chosen from the F3 menu and tabs switch with
  `Shift`+arrows ([PROP-037 §4.4 / §5.3](PROP-037-tree-tui.xml)).</fact></item>
        <item><fact id="TUI-STATUS-LINE" status="spec/done">**Status line:** current ordering · current display mode · the `STATIC.md` size
  indicator (§2.6).</fact></item>
        <item><fact id="TUI-FALLBACK" status="spec/done">**Fallback:** non-tty and `--plain` render a static ASCII tree; `--json` the
  JSON — neither enters interactive mode.</fact></item>
      </list>
    </section>
    <section id="dag" title="2.12 The graph is a DAG">
      <list ordered="false" p="27">
        <item><fact id="dag-diamonds" status="spec/done">The dependency graph has diamonds (a shared package reached by several parents).</fact></item>
        <item><fact id="DAG-RENDERING" status="spec/done" action="continue" actionstage="doc" audience="user">`vibe tree` renders each package under each parent, marks a re-occurrence with a
  trailing `(*)` and does not re-expand it, and cycle-guards the walk on the
  package's qualified `group/name` (REQ).</fact></item>
        <item><fact id="DAG-FLAT-COLLAPSE" status="spec/done">The flat display modes (§2.11) collapse
  the DAG to one row per package.</fact></item>
      </list>
    </section>
    <section id="project-resolution" title="2.13 Project resolution — VibeTree works from anywhere">
      <p p="28"><fact id="project-needed" status="impl/done">`vibe tree` shows a project's tree, so it needs one — but a GUI launcher
(`VibeTree.exe` / a Start-menu shortcut) or an arbitrary shell may sit
outside any project. The launchers live in the `vibevm-term` products repo with
vibeterm and vibeframe ([PROP-019 §STEP-VIBE-ONLY](../../common/PROP-019-version-manager.xml)),
and are governed there — this host contract only specifies what `vibe tree` does
when invoked from outside a project.</fact></p>
      <p p="29"><fact id="RESOLUTION-ORDER" status="spec/done">Resolution order for the **human** surfaces (the TUI and
`-t`; **not** `--json`, a scripting surface resolved strictly from `--path`)
(REQ):</fact></p>
      <list ordered="true" p="30">
        <item><fact id="RES-GIVEN-PATH" status="spec/done">**The given path** — `--path` (default: cwd, walked up for `vibe.toml`). On
   success it is **recorded as the last project** (`vibe.tree.last-project`, an
   L1 setting), so a later context-free launch reopens it. An explicit `--path`
   that is not a project is a hard error — never silently redirected.</fact></item>
        <item><fact id="RES-LAST-PROJECT" status="spec/done">**The remembered last project** — when the cwd is not a project and no
   explicit `--path` was given, the recorded `last-project` opens (if it is still
   a project).</fact></item>
        <item><fact id="RES-FOLDER-PICKER" status="spec/done">**A folder picker** — a `-t` (VibeTree / GUI) launch with neither of the above
   opens a native folder chooser; the pick is recorded as the last project.
   Cancelling is a clean no-op (no error dialog), never a failure.</fact></item>
      </list>
      <p p="31"><fact id="console-fallback" status="spec/done">A console launch (no `-t`) with neither a cwd project nor a memory keeps the
original `run vibe init` guidance.</fact></p>
    </section>
  </section>
  <section id="data-sources" title="3. Data sources">
    <p p="32"><fact id="CANONICAL-PARSERS" status="spec/done">`vibe tree` joins, using the canonical parsers (REQ — no re-implemented format
readers where a `vibe-*` crate already parses it):</fact></p>
    <list ordered="false" p="33">
      <item><fact id="SRC-GRAPH" status="spec/done">**graph** — `vibe.lock` (`vibe_core::manifest::Lockfile`): roots from
  `meta.root_dependencies`, edges from each `LockedPackage.dependencies`.</fact></item>
      <item><fact id="SRC-LINKS" status="spec/done">**links** — the node manifests (`vibe_core` `Requires`): consumer
  `declared_link` + the target's `[boot_snippet]` suggested link + `when`.</fact></item>
      <item><fact id="SRC-LANES" status="spec/done">**effective lanes + sizes** — the committed `vibevm/vibespecs/boot/STATIC.xml` +
  `vibevm/vibespecs/boot/INDEX.md`.</fact></item>
      <item><fact id="SRC-CROSS-CHECK" status="spec/done">**cross-check** — `vibe_workspace` `EffectiveBoot` (for the stale-artifacts
  diagnostic).</fact></item>
      <item><fact id="SRC-INPLACE" status="spec/done">**in-place specs** — `vibe_spec::Directives::parse`.</fact></item>
    </list>
  </section>
  <section id="non-goals" title="4. Non-goals">
    <list ordered="false" p="34">
      <item><fact id="NG-RUNTIME-SKILL" status="spec/done">**The runtime skill / prompt** — inferring what the agent *actually* loaded at
  runtime (the `loading spec://…` convention, `.vibe/` logging, multi-agency) is
  deferred to `tool:org.vibevm.core/package-tree`.</fact></item>
      <item><fact id="NG-GUI-CLIENT" status="spec/done">**A GUI client** — deferred to the same future package; the `--json` schema is
  its contract.</fact></item>
      <item><fact id="NG-SPEC-VALIDATION" status="spec/done">**Spec-graph validation** — `vibe tree` attributes and reports; it does not
  validate `spec://` targets.</fact></item>
      <item><fact id="NG-MUTATION" status="spec/done">**Mutation** — never; see §2.1.</fact></item>
    </list>
  </section>
</spec>
