PROP-036: vibe tree — the spec-tree analyzer
01Status: 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.
02Related: PROP-009 §2.4
(the static/dynamic link types + when), PROP-009 §2.3
(STATIC.md / INDEX.md), PROP-034
(static-transitive), PROP-035
(@spec in-place uses, STATIC.md markers), PROP-002 §lockfile
(the resolved graph). Plan: PACKAGE-TREE-PLAN-v0.1.
03Non-goal (deferred): the runtime "what the agent actually loaded" skill and a
GUI client — a future tool:org.vibevm.core/package-tree (§7).
1. Motivation
- 04A project's boot is composed by
vibefrom a dependency closure (PROP-009): some packages are compiled verbatim intoSTATIC.md, others are read by reference fromINDEX.md, some carry an OSwhen, andstatic-transitiveedges (PROP-034) silently promote whole subtrees. - Today a human cannot see this at a glance — what is connected, and how does it load?
vibe treeanswers 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.
2. Decisions
2.1 The command
- 05
vibe treeis a read-only analyzer over the current project. It mutates nothing (REQ: never writes to the tree, manifests, lockfile, or artifacts). - It
operates on the workspace discovered from
--path(default.), reading the committedvibe.lock, the node manifests, and the generated boot artifacts.
06Three output surfaces:
- 07interactive TUI — the default on a tty (§2.11);
--json— the machine surface (§2.7), the same data a GUI consumes;- plain — a static ASCII tree when not on a tty, or under
--plain.
- 08
--jsonand--plainnever launch the TUI. - The command is
Tree(TreeArgs)in the CLI surface;--json/--quietare the global flags (never redeclared).
2.2 Row semantics — the columns
09Each package is one row. Columns, left to right (REQ: this order):
- 10name — the tree column:
group/name, drawn with indentation + branch glyphs + an expand/collapse indicator when the node has children. - load — the effective load type (§2.3):
static,dynamic, ornone. - T (transitive) — a checkbox: the effective
staticwas forced by astatic-transitiveancestor, not the package's own declaration (§2.4). - C (condition) — a checkbox: the boot entry carries a
when(§2.5). - S (STATIC.md) — a checkbox: the package physically contributes a block to
STATIC.md.
- 11
T,C,Sare the "checkbox" cluster;loadis a value column. - Detail beyond
a checkbox (the full
whentext, the source path) is shown on demand (§2.11), never crammed into the row.
2.3 Effective load type
12The load value is the lane a package's boot snippet actually lands in, read
from the committed artifacts (REQ):
- 13
static— the package appears as a<!-- vibe:static {origin} … -->contribution inSTATIC.md(origin = group/name). dynamic— the package's boot file appears as an[[entry]]inINDEX.md.none— the package ships no[boot_snippet], so it contributes to neither lane (e.g. a content-minimal family aggregator, PROP-028).
14Decision — read the effective type from the artifacts, not a fresh recompute.
- 15Why: the committed
STATIC.md/INDEX.mdare 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. - Considered and rejected: recomputing
EffectiveBootfresh every run — shows what should be, masking drift the tool exists to reveal. - 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.
2.4 The transitive flag
- 16
Tis set (REQ) iff the package's effective type isstaticand that static-ness was inherited from an ancestor edge declaredstatic-transitive(PROP-034), rather than from the package's own consumer-declaredlink, its own[boot_snippet].linksuggestion, or a directstaticedge. - The
static-transitivedeclarer itself carriesT = false(its static-ness is its own declaration); every member of its closure that is not otherwise static carriesT = true.
2.5 The condition flag
- 17
Cis set (REQ) iff the package's boot entry carries awhencondition (PROP-009 §2.4; wire formos:<name>). - A
whenforces the entrydynamicregardless of link (soC = trueimpliesload = dynamic). - The full condition text is surfaced only in the detail view (§2.11) and the JSON (§2.7), never in the row.
2.6 The STATIC.md size indicator
- 18The status line shows the size of the statically-compiled lane: the byte count
and line count of
STATIC.md(REQ). - This is the "how much am I loading verbatim every session" budget the human watches.
2.7 JSON output
- 19
vibe tree --jsonemits one object, the same data model the TUI renders, valid against the shipped schema (REQ:schema_version = 1). - The envelope follows the
CLI convention (
{"ok": true, "command": "tree", …}). - The model carries: the
projectcontext, the declaredroots, thepackagesarray (each withload{type,transitive,declared,origin,in_static_md,in_index_md,boot_path} andcondition), the two boot lanes underboot(static_md/index_md, with the lane sizes), the collectedin_place_specs(§2.9), anddiagnostics(§2.10). - Display state (mode, ordering, tab, selection) is TUI-only and is not in the JSON.
- Schema home:
crates/vibe-cli/resources/package-tree.schema.v1.json.
2.8 STATIC.md decompilation
- 20
vibe treedecompilesSTATIC.mdinto its contributions (REQ): each<!-- vibe:static {origin} — {path} -->marker opens a region running to the next marker or EOF; the region yieldsorigin(the sourcegroup/nameor host rel-path) andpath(the workspace-relative source file). - Nested
<!-- embed: {addr} -->…<!-- /embed: {addr} -->pairs within a region are attributed as embed spans. - This is a dedicated decompiler for the on-disk
vibe:staticopen-marker format — it is notvibe_spec::decompile(), which parses the distinctvibe:begin/vibe:endcompiler format and returns empty onSTATIC.md.
2.9 In-place @spec collection
- 21
vibe treecollects the in-place boot-lane spec markers (REQ):@spec://uses and#use/#embed/#sourcedirectives, via the canonical fence-awarevibe_spec::Directives::parse. - A bare
spec://(no@) is discretionary and is not collected. - (Out of scope: the
#[spec(...)]code-traceability surface — that is PROP-014.)
2.10 Diagnostics
22vibe tree reports, non-fatally (REQ: never aborts rendering on these):
- 23stale-artifacts — the committed lanes disagree with a fresh
EffectiveBootrecompute (the tree needsvibe reinstall). - root-drift —
vibe.lockmeta.root_dependenciesdisagrees with the rootvibe.toml[requires.packages](the lock is behind).
24Each diagnostic carries a severity, a stable code, a message, and an optional locator.
2.11 The interactive TUI
25On a tty (default), vibe tree is an interactive pseudographic browser (REQ).
Contract:
- 26Navigation:
↑/↓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. - The key bindings sketched in this section are superseded. This §2.11 is the analyzer-era sketch; the shipped keymap is PROP-037 Spec 2's F-key scheme, and it is normative there — this section names the capabilities, never the keys.
- Folding: the selected node folds and unfolds (shipped as the
fold.toggleaction, PROP-037 §13.5). The whole-tree fold sketched here was not carried into Spec 2 and does not ship — the action catalogue hasfold.toggleonly. - Detail:
Enteropens 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);Esccloses it. Quitting isEscplus a confirm dialog (PROP-037 §7.4), not the bareqthis sketch assumed. - Ordering (shown in the status line): topological (the analysis order, default) ↔ alphabetical. Chosen from the F2 sort menu (PROP-037 §7.2, which replaces any bare mode-cycle key).
- Display mode: (a) all-together tree; (b) two stacked
sub-tables
static dependencies/dynamic dependencies(a header line each); (c) two tabsStatic/Dynamic, with a swappable static/dynamic priority in (b) and (c). Modes are chosen from the F3 menu and tabs switch withShift+arrows (PROP-037 §4.4 / §5.3). - Status line: current ordering · current display mode · the
STATIC.mdsize indicator (§2.6). - Fallback: non-tty and
--plainrender a static ASCII tree;--jsonthe JSON — neither enters interactive mode.
2.12 The graph is a DAG
- 27The dependency graph has diamonds (a shared package reached by several parents).
vibe treerenders 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 qualifiedgroup/name(REQ).- The flat display modes (§2.11) collapse the DAG to one row per package.
2.13 Project resolution — VibeTree works from anywhere
28vibe 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),
and are governed there — this host contract only specifies what vibe tree does
when invoked from outside a project.
29Resolution order for the human surfaces (the TUI and
-t; not --json, a scripting surface resolved strictly from --path)
(REQ):
- 30The given path —
--path(default: cwd, walked up forvibe.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--paththat is not a project is a hard error — never silently redirected. - The remembered last project — when the cwd is not a project and no
explicit
--pathwas given, the recordedlast-projectopens (if it is still a project). - 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.
31A console launch (no -t) with neither a cwd project nor a memory keeps the
original run vibe init guidance.
3. Data sources
32vibe tree joins, using the canonical parsers (REQ — no re-implemented format
readers where a vibe-* crate already parses it):
- 33graph —
vibe.lock(vibe_core::manifest::Lockfile): roots frommeta.root_dependencies, edges from eachLockedPackage.dependencies. - links — the node manifests (
vibe_coreRequires): consumerdeclared_link+ the target's[boot_snippet]suggested link +when. - effective lanes + sizes — the committed
vibevm/vibespecs/boot/STATIC.xml+vibevm/vibespecs/boot/INDEX.md. - cross-check —
vibe_workspaceEffectiveBoot(for the stale-artifacts diagnostic). - in-place specs —
vibe_spec::Directives::parse.
4. Non-goals
- 34The runtime skill / prompt — inferring what the agent actually loaded at
runtime (the
loading spec://…convention,.vibe/logging, multi-agency) is deferred totool:org.vibevm.core/package-tree. - A GUI client — deferred to the same future package; the
--jsonschema is its contract. - Spec-graph validation —
vibe treeattributes and reports; it does not validatespec://targets. - Mutation — never; see §2.1.