Advanced Markdown & XML
01This page takes a package you write in Markdown and shows you the XML your agent reads in its place. On the way you learn why VibeVM turns every text into XML, and why that costs an author nothing. You learn how to name a rule so that a machine can point at it. And you learn to split a long text into a short header and a long body, the way C and C++ programmers split a library. You build one small package by hand, watch it compile, and prove that the two forms are one. Plan for about half an hour, with no agent required.
What you need
| What | Why | Where to get it |
|---|---|---|
vibe |
creates the project, converts the text and compiles it | Install vibe |
| a text editor | you write three short files by hand | any |
| about thirty minutes | the whole walk, with no agent in it |
Why everything becomes XML
03In a project that asks for it, every text a package brings lands on disk as XML, whatever its author wrote it in. The texts are specifications, the rules a package states for the agents that work under it, and the reason for the conversion is the reader. A model that reads <TESTS-FIRST fact="true" status="spec/done"> knows where the rule starts, where it ends, what it is called and what state it is in, without guessing at layout. Prose asks the model to infer all four.
04The measurement agrees with the instinct. In a benchmark study, Yuan Sui and colleagues gave GPT-3.5 and GPT-4 the same tables written six ways, from plain text with separators to CSV, JSON, XML, HTML and Markdown (WSDM 2024). Across seven kinds of question, markup that names its parts beat the same content in prose. HTML did best overall, by 6.76 percent in the paper's own figure. On one task, telling where a table's parts begin and end, XML scored 96.00 percent against 93.00 for plain text and 92.33 for Markdown. The study is about tables, and that is exactly as far as this page takes it.
05Vendor advice says the same from the other side. Anthropic's prompting guide for Claude, guidance rather than research, tells you to wrap each kind of content in its own XML tag so that the model parses a long prompt without ambiguity (the guide). Oleg Chirukhin, the author of VibeVM, found this in practice before any published work said it. The xml target is the form the projects of this manual use, and the one the specification names as the future primary form.
06 spec: xml target
xml target: every source — including Markdown —
emits as dialect XML through the pivot. Mixed input translating into
CLEAN XML (never mixed output) is this target's acceptance bar, because
it is the future primary.
07The names matter as much as the brackets. A section becomes an element named after itself, <tests-first title="Tests before fixes">. A rule becomes an element named after its id with one marker attribute, fact="true", so that a reader who knows nothing of your vocabulary finds every rule by one test. The first reader of this dialect is an agent, and a tag that says what it holds needs no legend.
08 spec: Decision (ADR-part; owner ruling 2026-08-22, verbatim)…
Decision (ADR-part; owner ruling 2026-08-22, verbatim): «Гораздо логичней<three-bands title=\"…\">. … Вся суть XML нотации в том, что у тебя названия тэгов несут названия сущностей, это упрощает работу нейросети». A section serialises with its ANCHOR as the element name —<three-bands title="1. The three bands">— because the dialect's first reader is an agent, and a tag that names its entity is self-describing where an endless<section>river is not. The generic form<section id="…" title="…">remains in the dialect as the REQUIRED fallback for the two cases XML itself forbids or the grammar reserves: an anchor that is not a valid XML name (leading digit) and an anchor colliding with the structural vocabulary (spec,title,status,section,p,fact,list,item,table,tr,td,fence,quote») — measured over the live corpus, that tail is 2 anchors of 1393; the emitter writes the named form everywhere else, the readers accept both. The converter recipe bumps (specdoc/1→specdoc/2), so every transformed slot re-materialises by the derived-manifest law rather than lingering in the old shape. The owner's next call arrived the same day — facts follow, see##NAMED-FACT-ELEMENTS`. Landed: the emitter writes named sections everywhere the predicate allows (the live redbook README golden carries 7 named / 0 generic), both readers accept both forms, and the engine mirrors the predicate verbatim across the separability seam.
09 spec: Decision (ADR-part; owner ruling 2026-08-22, verbatim)…
Decision (ADR-part; owner ruling 2026-08-22, verbatim): «сконвертируй и факты тоже. Предлагаю такой формат<fact-name fact="true" ...>. Таким образом кастомный XML-парсер всегда может найти соответствующие элементы». A fact serialises with its ID as the element name, carrying the DISCRIMINATOR attribute —<THE-LAW fact="true" status="impl/done">body</THE-LAW>— so a reader that knows nothing of the vocabulary still finds every fact by one attribute test. The recognition law: an element IS a fact iff its name isfact(the generic form, which stays in the dialect) or it carriesfact="true". The named form is emitted whenever the id passes the same elementability predicate sections use (fact-id grammar already forbids leading digits, so the fallback tail is vocabulary collisions only); the typed-fact fence binding stays by id and does not change. The owner's second clause binds the scanners: the progress machinery must work when a fact's SOURCE — not a materialised copy — is authored XML; the host lane holds by construction (XML sources enter progress through the canonical MD projection) and is PINNED by explicit tests (an observed .xml source scans unit-for-unit equal to its MD twin), while the specmap engine's native reader learns the named form mirror-wise. The converter recipe bumps again (specdoc/2→specdoc/3); the host re-materialises once, after both shapes land. The owner's third clause (2026-08-22, same sitting) binds the boot lanes: «статические и динамические лоадеры должны хорошо работать с новым синтаксисом фактов» — pinned at the transition's landing by (a) the static-splice determinism test running over a NAMED-shape snippet whose projected facts survive into STATIC, (b) the vibe-spec normal-closure byte-equality test running over BOTH serialisations (generic and named) of one dependency, and (c) the polygon re-run at specdoc/3, whose control package auto-adopts the named shape through to_xml — INDEX targets, STATIC splice and every machine loader then exercise the final syntax end-to-end; the agent half of the dynamic router is §5a's measurement, deliberately run AFTER this transition so it measures the shape that ships. Landed: the recipe isspecdoc/3, the host's 37 slots re-materialised once with named facts live (the redbook README golden pins 45), the recognition law holds in both readers withfact="false"a loud error, progress holds full ParsedDoc parity between an XML source and its hand-pinned MD twin across two scans, and pins (a)–(c) are in the tree — the splice snippet ships<BOOT-RULE fact="true">, the normal closure compiles three lanes byte-equal, the polygon re-ran 3/3. A live lesson worth its line: XML reserves every case-insensitivexml-prefixed name, soXMLBOOTcannot be an element — the predicate refuses it and the generic form carries such ids.
10One caution for the readers who train models. The evidence above is about text a model reads. For text a model writes, it runs the other way: forcing an answer into JSON, XML or YAML can cost reasoning quality (Tam and colleagues, EMNLP 2024). VibeVM shapes what an agent reads and never what it answers, so that finding does not touch it.
Markdown and XML: one model underneath
11Under the hood the two forms are one thing. Every document, Markdown or XML, is parsed into one tree: a title, a status, and sections nested by heading depth. Inside the sections sit paragraphs, lists, tables, fenced code and quotes, some carrying a fact, one named rule with a status. vibe never rewrites Markdown text into XML text. It parses into the tree and prints from it, in either direction.
12 spec: Decision (ADR-part)
Decision (ADR-part). There is ONE internal document model — the pivot — and every format is a frontend (parse into it) or a backend (emit from it). The pivot is the progress-markup semantic tree the tree already owns: document → nested sections (the heading hierarchy with anchors) → blocks (paragraph / list / table / fence / quote) → facts (anchored units with status and body spans), plus the<status>document element and fragment wrappers. Conversion between formats is always parse → pivot → emit; there is no direct MD↔XML text rewriting. Alternatives weighed: per-pair converters (N² growth, drift between pairs) and a lossless-CST pivot preserving all whitespace (cost without a consumer; the degradation law below makes semantic-level fidelity the contract). *Resolved by the XML-MEASURE map (2026-08-21): there is no single Markdown frontend to widen — FOUR independent families read different MD subsets today (progress-core's scanner, the vendored specmap engine's mdspec, the boot/tree directive readers, vibe-check's point scanners), with real dialect drift already between them (fence grammar run-matching vs prefix-toggling). The pivot is therefore a NEW shared crate —vibe-specdoc— owning the document IR and both frontends/backends; host consumers converge on it. The vendored specmap engine is engine-workspace territory (sync-engines law): its XML frontend is built in the AUTHORED engine workspace and写-throughs as its own slice (S4b), never patched in the vendored copy.
13Compiler writers call such a tree an intermediate representation, or IR: the one form that every front end parses into and every back end prints from. A compiler for three languages and four processors needs three front ends and four back ends, not twelve translators, and a rule stated once about the tree holds for every language. VibeVM has the same shape with two front ends and two back ends, Markdown and XML on each side.
14 spec: Everything downstream operates on a single…
Everything downstream operates on a single document IR: a DOM-like tree. Markdown and (future) XML are two frontends parsed into the same tree, so algorithms written against the IR scale to deeply nested XML for free.
15It needs the tree for the same two reasons. Without it, each pair of forms would need its own converter, and converters drift. A survey of the code in 2026 found four readers of Markdown inside vibe, each accepting a slightly different dialect: the disease one shared tree is meant to cure. And every tool that reads a specification reads XML through the same tree: the checker of facts, the compiler that builds an agent's reading list, the router that resolves an address. So an XML document and its Markdown twin give every tool the same answer.
16 spec: Decision (ADR-part): scanners read XML through…
Decision (ADR-part): scanners read XML through its canonical Markdown projection — one dispatch layer above the parser, no dependency cycle.vibe-specdocdepends onprogress-core(its MD frontend is the adapter), so progress-core cannot itself call specdoc. The consumers dispatch instead: a.xmlspec entering any scanner (progress, check, specmap-host, show) is first projectedfrom_xml → to_markdown— deterministic and canonical by S1's emitter — and the projection feeds the existing MD machinery; units, facts, anchors, hashes and verdict staleness all work unchanged, and a source edit moves the projection exactly when it moves meaning. Alternatives weighed: a native XML unit-walker in every scanner (a fifth and sixth parser family — the disease the measure named), and inverting the crate dependency (progress-core consuming specdoc — a cycle). RECORDED DEGRADATION, honest: a diagnostic for an XML source cites projection-relative line numbers, and v1 marks such diagnostics with the projection notice rather than pretending; native source positions are follow-up work riding the specmap-engine slice (S4b).
17The tree has a future beyond conversion. The compiler names its levels, from one document's text up to the whole reachable closure of a project, and a compiler plugin may read and rewrite them. A long-horizon agent could plan over that closure one day: not a wall of text but a graph of named units with addresses. That is a direction, not a feature you can run today. And VibeVM itself is not an agent. It never reads your rules to act on them; it prepares the text, the tree and the addresses for whichever agent you run.
18 spec: The compiler's IR is multi-level…
The compiler's IR is multi-level (the MLIR shape, because compilation is progressive lowering):source— one document's raw text plus its spec address;document— one parsed tree;closure— the whole reachable compilation unit and graph;lane— the assembled ordered nodes and provenance;emitted— serialised bytes per artifact. R3 made these explicit typed carriers and R6.2 froze their epoch-1 wire.
19 spec: Decision (owner ruling 2026-08-25; implemented through R6.5-D)
Decision (owner ruling 2026-08-25; implemented through R6.5-D). A plugin may be a full compiler extension — a pass with complete access to the packages' IR, free to change it arbitrarily: new optimisations, new frontends, new backends, the LLVM posture verbatim. Users can build the compiler onward from here. The price of the power is explicitness: the pass tier exists only behind a dedicated manifest flag, only under host activation, only in-process (builtin/native), and always on the record (§7.4.3). D1 exposes exact selected native backends throughvibe extensions compile; D2 commissions a genuine installed dependency whose first-class TXT frontend feeds its JSON backend. Evidence:ee7f6f2d,def9909a,56307492.
20The consequence for you is plain. Write the specifications of your own packages in Markdown, the form your editor and your reviewers already handle. Every property of the XML form arrives on its own: named elements, machine-checked facts, an address on every rule. The steps below do exactly that, and end by proving it.
Step 1: a project that materialises into XML
211. Create a project in an empty folder, as on Create your first project. The name becomes the folder:
vibe init review-lab
Initializing project `review-lab` in `review-lab`
✓ created vibevm/vibespecs/boot/00-core.md
✓ created vibevm/vibespecs/boot/90-user.md
✓ created vibe.toml
✓ created vibe.lock
✓ created .vibe/.gitignore
✓ created .gitignore
✓ created vibevm/vibespecs/boot/INDEX.md
✓ created CLAUDE.md
✓ created AGENTS.md
✓ created GEMINI.md
Done. Project `review-lab`: 10 files created, 0 kept.
Next:
• edit vibevm/vibespecs/boot/00-core.md and vibevm/vibespecs/common as your project takes shape
• install packages with `vibe install <kind>:<name>` (e.g. flow:wal)
232. Open review-lab/vibe.toml, the project's manifest, and add one line under [project]:
24[project]
spec_format = "xml"
name = "review-lab"
253. Work inside the folder from here on: cd review-lab.
26The line decides the form of every text a package brings in. With xml, vibe converts what an author wrote in Markdown as it copies the package in, and copies XML as it is. Your own files are never converted. Without the line, each file keeps its author's form.
27 spec: The setting and its home (revised…
The setting and its home (revised by the measure — reproducibility rules). The materialisation format is a REPRODUCIBLE project property, so its canonical home is the project manifest:vibe.toml [project] spec_format = "mixed" | "markdown" | "xml", with the effective value recorded in the slot record defined by PROP-054 §9.2 so two machines materialise identically. The user-config family supplies only the operator DEFAULT for projects that do not pin one ([install] spec_format, besideslot_integrity), per the standing precedence CLI > env > project > user > built-in; vibe-settings is barred from this key by PROP-040's own boundary (app prefs never extend vibe.toml).mixedis the built-in default — for an all-MD world byte-for-byte today's behaviour; the flip of the default to XML is the owner's word, never silent.
Step 2: scaffold the package
291. Add a package to the project. It is a flow, a package of working rules for an agent, and it asks for the normal format, whose meaning step 4 explains:
vibe init package org.acme/review --kind flow --format normal
Creating package `org.acme/review` in `<TMP>/work/review-lab`
✓ created vibevm/vibepacks/org.acme/review/v0.1.0/vibe.toml
✓ created vibevm/vibepacks/org.acme/review/v0.1.0/vibevm/vibespecs/boot/10-flow-review.md
✓ created vibevm/vibepacks/org.acme/review/v0.1.0/README.md
• kept vibevm/vibespecs/boot/INDEX.md (regenerated)
• kept CLAUDE.md (regenerated)
• kept AGENTS.md (regenerated)
• kept GEMINI.md (regenerated)
Done. Project `org.acme/review`: 3 files created, 4 kept.
Next:
• edit vibevm/vibespecs/boot/00-core.md and vibevm/vibespecs/common as your project takes shape
• install packages with `vibe install <kind>:<name>` (e.g. flow:wal)
312. Open the manifest the scaffold wrote:
cat vibevm/vibepacks/org.acme/review/v0.1.0/vibe.toml
[package]
group = "org.acme"
name = "review"
kind = "flow"
version = "0.1.0"
epoch = 1
authors = ["vibevm docs fixtures"]
license = "UPL-1.0"
description = ""
format = "normal"
[boot_snippet]
source = "vibevm/vibespecs/boot/10-flow-review.md"
category = "flow"
link = "dynamic"
333. Change two lines: give the package a description, and set link = "static". The end of the file then reads:
34description = "The team's code review rules, as a contract with its reasons."
format = "normal"
[boot_snippet]
source = "vibevm/vibespecs/boot/10-flow-review.md"
category = "flow"
link = "static"
35The link type says how the package's text reaches the agent. With static, vibe compiles the text into the one file an agent reads first, in full, at every session start. With dynamic, the scaffold's choice, it lists the file for the agent to open on its own. This page uses static, so that you can watch the compiler work.
36 spec: link = "static" — the contribution's…
link = "static"— the contribution's boot text is compiled intoSTATIC.mdahead of time (whole, anchor-qualified — §2.3). Read first, one read, maximum attention weight. The emergency priority lane — for top-level skills and critical disciplines whose priority must be guaranteed by position, not by trusting agent-side resolution. Used sparingly; it duplicates the text on disk.
37 spec: link = "dynamic" — the default.…
link = "dynamic"— the default.viberesolves the contribution to a concrete path inINDEX.md; the agent reads it dynamically, on demand. An optionalwhencondition gates the read: with awhenit is a conditional INCLUDE (loaded only when the condition holds) — mechanically the subskilllazy-pulldelivery mode; without one it is read unconditionally. Thewhendraws on the subskill[activation]probe vocabulary (PROP-003 §2.5) — one probe grammar across both mechanisms. v1 implements theos:probe end-to-end —when = "os:windows"matches the session's operating system (windows/macos/linux); the remaining probes are reserved until PROP-003's activation engine is built.
Step 3: anchors and facts, in Markdown
38The package needs two documents. The first is the note an agent reads at session start, the package's boot snippet. The second is the contract: the rules themselves, each with an address.
391. Replace the scaffold's note, vibevm/vibepacks/org.acme/review/v0.1.0/vibevm/vibespecs/boot/10-flow-review.md, with this:
40# Review flow
Before you open a change for review, hold it to the review rules. This
note pulls the rules in; each rule is one fact with an address, and the
reasons follow the rules. Cite a rule by its address, for example
`spec://org.acme/review/contract/REVIEW#ONE-IDEA`.
#use spec://org.acme/review/contract/REVIEW#root
41The line that starts with #use is a directive: an instruction to the compiler, not prose. It pulls the contract in ahead of the note, so that the agent meets the rules before the note referring to them.
422. Create vibevm/vibespecs/contract/REVIEW.md inside the same package:
43# Review rules {#root}
<status stage="spec" state="done"/>
The rules a reviewer holds every change to. Each rule is one anchored
fact with a status.
## One idea per change {#one-idea}
@fact:ONE-IDEA A change under review carries one idea, named in its first line. @status:spec/done
## Tests before fixes {#tests-first}
@fact:TESTS-FIRST A change that alters behaviour carries a test that fails without it. @status:spec/done
44Read the file as a machine does. A heading carries an anchor in braces, {#one-idea}: the name a citation uses, the part after # in an address. A paragraph that opens with @fact:ONE-IDEA is a fact, one anchored rule with a status. The @status:spec/done at its end says that the rule is settled and not yet built. The <status> element under the title is the same marker for the whole document. Heading anchors and fact ids share one namespace, so no two may repeat in a document.
45 spec: Fact anchors — the anchored-when-marked law
Fact anchors — the anchored-when-marked law (owner, 2026-07-24; spelling amended 2026-08-06). A stable fact address is written@fact:<ID>as the first token of a paragraph or list item. The legacy spelling##<ID>means the same and is still read.
46 spec: Every unit that carries a status…
Every unit that carries a status marker — paragraph or list item — MUST also carry a@fact:<ID>anchor; a marked, anchor-less unit is acheckerror.
47 spec: @status:<stage>/<state> and @status:<stage> are macro-equivalents…
@status:<stage>/<state>and@status:<stage>are macro-equivalents of a point marker. The legacy spellings@<stage>/<state>and@<stage>mean exactly the same and are still read, so a document written before the qualified form keeps parsing.
48 spec: <ID> is [A-Za-z][A-Za-z0-9_-]*; the unit…
<ID>is[A-Za-z][A-Za-z0-9_-]*; the unit is then addressable asspec://…/<doc>#<ID>, sharing one address space with the heading{#anchor}s — a duplicate across both forms is acheckerror. The address is unchanged by the spelling: it names the id, never the opener.
49The case of an id is a signal. Upper-case ids mark rules with binding weight; lower-case ids mark headings, lead-ins and notes. The first rule's address is spec://org.acme/review/contract/REVIEW#ONE-IDEA. It joins the package's coordinate, the document's path under vibevm/vibespecs/ without its extension, and the anchor. The address does not change when the file changes form.
50 spec: Decision — two anchor-id registers (owner ruling, 2026-07-24)
Decision — two anchor-id registers (owner ruling, 2026-07-24).@fact:UPPER-SLUGnames a normative fact (a law, rule, carrier, changelog entry — content with binding weight);@fact:kebab-casenames a service unit (status lines, lead-ins, connective prose).
51 spec: spec:// addressing is format-blind: anchors…
spec://addressing is format-blind: anchors areidattributes in XML and{#…}/first-token anchors in MD, minted into the same address space; a document's address does not change when its serialisation does.
523. Check the markup of the package:
vibe facts check --path vibevm/vibepacks/org.acme/review/v0.1.0
progress check: clean (2 files, 0 warning(s))
54The check reads every document under the package's vibevm/vibespecs/. A marker on a paragraph without an anchor, and an id defined twice, are errors that name the line.
554. Install the package into its own project:
vibe install org.acme/review --assume-yes
Resolving 1 root package…
Materialising 1 package into vibedeps/:
org.acme/review@0.1.0
closure diff:
→ + org.acme/review@0.1.0 (root-edge)
→ lane vibevm/vibespecs/boot/INDEX.md: 737 -> 781 B
→ lane vibevm/vibespecs/boot/STATIC.xml: absent -> 2809 B
Materialised 1 package into vibedeps/; regenerated boot artifacts for 1 node(s).
57The install copies the package into vibevm/vibedeps/, converting both documents to XML. It also compiles the boot lane, the ordered list of files an agent reads at session start. The last two lines of the diff are that lane: INDEX.md gained the line that names the compiled file, and STATIC.xml appeared.
585. Open the contract as the agent will read it:
cat vibevm/vibedeps/org.acme.review/0.1.0/vibevm/vibespecs/contract/REVIEW.xml
<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
<title id="root">Review rules</title>
<status stage="spec" state="done"/>
<p>The rules a reviewer holds every change to. Each rule is one anchored
fact with a status.</p>
<one-idea title="One idea per change">
<p><ONE-IDEA fact="true" status="spec/done">A change under review carries one idea, named in its first line.</ONE-IDEA></p>
</one-idea>
<tests-first title="Tests before fixes">
<p><TESTS-FIRST fact="true" status="spec/done">A change that alters behaviour carries a test that fails without it.</TESTS-FIRST></p>
</tests-first>
</spec>
60Every part is named after itself. The section {#one-idea} became the element <one-idea>, with its title as an attribute. The fact became <ONE-IDEA fact="true" status="spec/done">. The @fact: prefix and the @status: suffix are gone from the text: they were spelling, and the tree keeps only meaning. Inline Markdown, such as backticks and links, rides inside the text unchanged.
61 spec: Decision (ADR-part)
Decision (ADR-part). Inline content — emphasis, inline code, links,##NAMEcitations,spec://addresses — rides INSIDE text nodes as literal Markdown conventions, in both directions. The pivot does not model inline grammar. Why: the markup contract already treats inline code as opaque; round-tripping stays byte-stable at the text level; XML authorship needs no inline vocabulary; and every consumer that reads fact bodies today keeps reading the same strings. Alternative weighed — a full inline element vocabulary (<code>,<a>,<b>) — rejected as cost without a consumer and a fresh drift surface between two inline grammars.
A header and its implementation
62A contract that says everything is expensive. Every word in the boot lane is read by every agent at every session start. So the text that must always be present wants to be short, and the reasoning behind it wants to sit where an agent can reach it when it asks. C solved a problem of this shape in the 1970s with two files, and C++ kept the answer. A header, .h, declares in a few lines what a library offers; a translation unit, .c or .cpp, carries the implementation. Everyone who uses the library includes the header, and nobody pastes the implementation into their own code.
63 quote from the specification
Inspired by C/C++.h/.cpp:
64VibeVM borrows the split for text. A package in the normal format keeps two folders under vibevm/vibespecs/. contract/ is the header: small, cheap to load, the surface other packages and agents see. source/ is the implementation: the heavy body, pulled in only when something asks for it. A C compiler sees the whole program and matches declarations to definitions itself; vibe has no such view of your text, so the contract names its implementation with a #source directive. The default format, simple, has none of this: such a package is carried whole and read because it is present.
65 spec: contract/: — small, simple, boot-snippet-like.…
contract/ — small, simple, boot-snippet-like. The surface a package exposes outward; short files, cheap to load. The analogue of a header.
66 spec: source/: — large, heavy. The full…
source/ — large, heavy. The full implementation; pulled only when actually needed. The analogue of a translation unit.
67 spec: Because the structural executor lacks…
Because the structural executor lacks a C++ compiler's global view of the source tree, we use a deliberate hack: the contract author declares what implements it, via #source (§7.3). The author hand-draws edges a globally-aware compiler would infer.
68 spec: format = "simple"
format = "simple"— the default (absentformat, a package issimple). Legacy / adapted prompts, carried whole, with no VibeVM-specific structure — for importing existing corpora without rewriting them, and the fail-safe posture. Rules: inclusion in[requires.packages]means (a) structural — the agent reads the file; (b) static — its text is compiled into the target. If[boot_snippet].sourcenames a file, only that file is read/spliced; absent even that, every file in the package is read/spliced by a recursive walk — the over-load is the author's problem, the deliberate cost of not adoptingnormal.
69 spec: format = "normal"
format = "normal"— the VibeVM-native form, opt-in: thecontract/sourcesplit (§4), directives (§7), and the compiler (§8). Anormalpackage is not read just because it is present — it participates only when something actually#uses it (§7.2). This is tree-shaking; the optimized posture for authors who understand the machinery, at the price of structuring the package correctly.
70The specifications call this machinery inheritance, as in C++: one document builds on another without copying its text. Two directives do the pulling, and you have met one. #use names a document or a section that must be read before the text that uses it; the compiler copies it in ahead, and the copy brings along whatever the copied document pulls in itself. #source names the implementation of a contract, and the compiler compiles it in behind the contract. In the compiled file both directives are gone, consumed. A third, #embed, splices exactly one addressed node into the place where it stands, a macro rather than an include.
71 spec: Inline mode
Inline mode. The same, statically: the#used library's text is fully copied higher up inSTATIC.mdso it is available before the user.
72 spec: Like a C++ interface, but…
Like a C++ interface, but with section-level merging.contractsections are the exposed surface;#sourcenames the file(s) that implement them. Sections are treated as the analogue of class methods, needing a merge (in the static build) or virtual-lookup (in structural mode) mechanism.
73 spec: #embed has arbitrary granularity
#embed has arbitrary granularity — it splices exactly the addressed node, no more.
Step 4: split the contract from its reasons
741. Create vibevm/vibespecs/source/details.md in the package, the implementation of the contract:
75# Review rules, the reasons {#details}
## Why one idea per change {#one-idea-why}
@fact:ONE-IDEA-WHY A change with two ideas cannot be reverted one idea at a time, and its review takes twice as long. @status:spec/done
## Why tests before fixes {#tests-first-why}
@fact:TESTS-FIRST-WHY A fix without a failing test proves nothing: the test states what was wrong. @status:spec/done
## What a reviewer checks {#checklist}
- @fact:CHECK-SCOPE The first line names the one idea, and every hunk serves it. @status:spec/done
- @fact:CHECK-TEST The test fails on the parent commit and passes on this one. @status:spec/done
76Every anchor here differs from the contract's, one-idea-why beside one-idea; the edge cases below say why. The last section is a list in which every item is a fact.
772. In contract/REVIEW.md, add one line after the first paragraph:
78#source spec://org.acme/review/source/details
79The address names a whole document, with no anchor, so the compiler takes it from the title down.
803. Install again:
vibe install org.acme/review --assume-yes
Resolving 1 root package…
Materialising 1 package into vibedeps/:
org.acme/review@0.1.0
closure diff:
→ lane vibevm/vibespecs/boot/STATIC.xml: 2809 -> 4557 B
Materialised 1 package into vibedeps/; regenerated boot artifacts for 1 node(s).
82No package was added, so the lane is the only line of the diff: the compiled file grew by the implementation.
834. Ask vibe what an agent reads first:
vibe tree --plain
project: <TMP>/work/review-lab
STATIC.xml: 4557 bytes, 69 lines, 1 contribution(s)
packages: 1 roots: 1
columns: load T=transitive C=condition S=in STATIC.xml
org.acme/review static . . x
85One package, linked static, and the x says that its text is compiled into STATIC.xml.
865. Open the compiled file:
cat vibevm/vibespecs/boot/STATIC.xml
<!-- vibe:c1 vibevm/vibespecs/boot/STATIC.xml — generated by vibe, do not edit. -->
<!-- vibe:c1 The static boot lane (PROP-009 §2.3): the highest-priority -->
<!-- vibe:c1 contributions, compiled anchor-qualified. Read this first, in full. -->
<!-- vibe:c1 RESOLUTION RULES — read these five lines before anything else:
1. Labels in this file are qualified: <origin-slug>-%2D<original>. The origin
is named by the provenance comment above each block; the original short
label is the tail after the last `-%2D`.
2. A short label you cannot find here → check the RENAMED ANCHORS table
below for its qualified heirs; never guess among look-alikes.
3. Full spec:// addresses resolve against package SOURCES under vibevm/vibedeps/,
never against this generated file. This file is a cache, not a target.
4. `#use spec://… as X` binds a file-local alias; `@!X` is a mandatory read
of X's target (same rules as @spec://…). In this compiled file every @!X
is already rewritten to its full address.
5. An ambiguous or unresolvable short reference is an ERROR to surface with
candidates — never silently pick one. -->
<!-- vibe:c1 RENAMED ANCHORS (short → qualified heirs):
CHECK-SCOPE → org-acme-%2Dreview-%2DCHECK-SCOPE (org.acme/review)
CHECK-TEST → org-acme-%2Dreview-%2DCHECK-TEST (org.acme/review)
ONE-IDEA → org-acme-%2Dreview-%2DONE-IDEA (org.acme/review)
ONE-IDEA-WHY → org-acme-%2Dreview-%2DONE-IDEA-WHY (org.acme/review)
TESTS-FIRST → org-acme-%2Dreview-%2DTESTS-FIRST (org.acme/review)
TESTS-FIRST-WHY → org-acme-%2Dreview-%2DTESTS-FIRST-WHY (org.acme/review)
checklist → org-acme-%2Dreview-%2Dchecklist (org.acme/review)
details → org-acme-%2Dreview-%2Ddetails (org.acme/review)
one-idea → org-acme-%2Dreview-%2Done-idea (org.acme/review)
one-idea-why → org-acme-%2Dreview-%2Done-idea-why (org.acme/review)
root → org-acme-%2Dreview-%2Droot (org.acme/review)
tests-first → org-acme-%2Dreview-%2Dtests-first (org.acme/review)
tests-first-why → org-acme-%2Dreview-%2Dtests-first-why (org.acme/review) -->
<!-- vibe:c1 vibe:static org.acme/review — vibevm/vibedeps/org.acme.review/0.1.0/vibevm/vibespecs/boot/10-flow-review.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
<title id="org-acme--review--root">Review rules</title>
<status stage="spec" state="done"/>
<p>The rules a reviewer holds every change to. Each rule is one anchored
fact with a status.</p>
<org-acme--review--one-idea title="One idea per change">
<p><org-acme--review--ONE-IDEA fact="true" status="spec/done">A change under review carries one idea, named in its first line.</org-acme--review--ONE-IDEA></p>
</org-acme--review--one-idea>
<org-acme--review--tests-first title="Tests before fixes">
<p><org-acme--review--TESTS-FIRST fact="true" status="spec/done">A change that alters behaviour carries a test that fails without it.</org-acme--review--TESTS-FIRST></p>
</org-acme--review--tests-first>
<org-acme--review--details title="Review rules, the reasons">
<org-acme--review--one-idea-why title="Why one idea per change">
<p><org-acme--review--ONE-IDEA-WHY fact="true" status="spec/done">A change with two ideas cannot be reverted one idea at a time, and its review takes twice as long.</org-acme--review--ONE-IDEA-WHY></p>
</org-acme--review--one-idea-why>
<org-acme--review--tests-first-why title="Why tests before fixes">
<p><org-acme--review--TESTS-FIRST-WHY fact="true" status="spec/done">A fix without a failing test proves nothing: the test states what was wrong.</org-acme--review--TESTS-FIRST-WHY></p>
</org-acme--review--tests-first-why>
<org-acme--review--checklist title="What a reviewer checks">
<facts ordered="false">
<org-acme--review--CHECK-SCOPE fact="true" status="spec/done">The first line names the one idea, and every hunk serves it.</org-acme--review--CHECK-SCOPE>
<org-acme--review--CHECK-TEST fact="true" status="spec/done">The test fails on the parent commit and passes on this one.</org-acme--review--CHECK-TEST>
</facts>
</org-acme--review--checklist>
</org-acme--review--details>
<section title="Review flow">
<p>Before you open a change for review, hold it to the review rules. This
note pulls the rules in; each rule is one fact with an address, and the
reasons follow the rules. Cite a rule by its address, for example
`spec://org.acme/review/contract/REVIEW#ONE-IDEA`.</p>
</section>
</spec>
88Read it from the top. The comments come first: the rules an agent applies to the labels in this file, then a table of every renamed anchor. XML forbids two hyphens inside a comment, so the -- of a label is written -%2D there; the elements below carry the real --. Then one XML document: the contract first, then the implementation compiled in behind it as one nested section. Last comes the note from the snippet, as a plain <section>, because its heading had no anchor. The #source and #use lines are gone.
89 spec: In source, absent in contract
In source, absent in contract — always counted; structural: readable at will; static: compiled in whole. (Calling a section that exists only in the implementation is poor taste, but permitted — we deliberately impose noprivate/publicaccess control.)
90Every anchor now carries the prefix org-acme--review--, the label of its origin. Two packages that both name a section root cannot collide in one file, and the table at the top says what each short name became. Cite the source document, never this file: it is a cache that changes whenever a package changes.
91 spec: Compiled labels are origin-qualified (B-011)
Compiled labels are origin-qualified (B-011). A compiled block's heading anchors and fact ids carry the §8 qualify phase's <origin-slug>-- prefix, so the compiled document's label namespace is collision-free by construction; reversibility survives because the block's own marker key names the origin, and stripping that block's prefix restores the source labels.
92 spec: A generated STATIC.md is not a citation target
A generatedSTATIC.mdis not a citation target — authored text never citesspec://…/boot/STATIC#…; the lane is compiler output, and source-of-truth is the package source undervibedeps/(PROP-035 §11's lint, B-011 §6.1).
936. Open the record vibe keeps beside its copy of the package:
cat vibevm/vibedeps/org.acme.review/0.1.0/.vibe-slot.toml
schema = 1
source_hash = "sha256:30fef78663fe343057c2d36d65514d3708d2f5053a2d41b713a749a0b28cc8eb"
spec_format = "xml"
converter_recipe = "specdoc/4"
derived_hash = "sha256:e420b6841c71fab586dfd72fe4720881b77d056e8f7a86217d1174b5fde0feba"
[[file]]
path = "README.xml"
sha256 = "c26e751356947bca54346b4099dc297dcdcac44831087d06436b1cb5f51ecec1"
disposition = "converted"
source = "README.md"
[[file]]
path = "vibe.toml"
sha256 = "42cb5b83bdc0e8ef61bdbb46095688593b05e4ad07bc61b8b346c13be0e753c6"
disposition = "copied"
source = "vibe.toml"
[[file]]
path = "vibevm/vibespecs/boot/10-flow-review.xml"
sha256 = "925cd961b99debb27cd179a42908bdf006c5103422c2a216ee8fc874d30865d3"
disposition = "converted"
source = "vibevm/vibespecs/boot/10-flow-review.md"
[[file]]
path = "vibevm/vibespecs/contract/REVIEW.xml"
sha256 = "ebcda3b670be21b409aa07b2600582ceb57cdc7eef8dbb3ef0dfe92dbc4f7a11"
disposition = "converted"
source = "vibevm/vibespecs/contract/REVIEW.md"
[[file]]
path = "vibevm/vibespecs/source/details.xml"
sha256 = "d35c2052468004ff1fb0dfc7724a496d8773aa280e686ce597fd14615acf6a2d"
disposition = "converted"
source = "vibevm/vibespecs/source/details.md"
95spec_format names the target and converter_recipe the converter's version. Each row says whether a file was copied or converted, with the hash of what landed. Keep the file in mind: step 5 reads it again.
96 spec: The hash law under transformation
The hash law under transformation. Source identity is unchanged: lockfilecontent_hashand the machine store hash the source form. A transformed slot is a derived artifact whose identity and owned footprint are recorded in the single.vibe-slot.toml:source_hash,spec_format, versionedconverter_recipe, optionaloverlay_hash,derived_hash, and per-file source/output/disposition/SHA-256 rows. The legacy.vibe-derived.tomlis no longer written; its schema-1 reader remains only as a read-only compatibility tombstone until a real rematerialisation migrates the slot. Mixed slots verify their recorded source identity and payload; transformed slots additionally verify recipe, representation andderived_hash. A missing legacy record triggers one final migration, a malformed new record refuses, and mismatched valid state rematerialises through the owned diff; changingspec_formatcan never earn a presence skip. Semantic equivalence remains the converter's proof through the shared IR, never the hash's job.
The equivalent forms
97The constructs this page used, side by side, from its own files:
| Markdown | XML |
|---|---|
# Review rules {#root} |
<title id="root">Review rules</title> |
<status stage="spec" state="done"/> |
the same element, unchanged |
## One idea per change {#one-idea} and the text under it |
<one-idea title="One idea per change">…</one-idea> |
| a paragraph | <p>…</p> |
@fact:ONE-IDEA … @status:spec/done |
<p><ONE-IDEA fact="true" status="spec/done">…</ONE-IDEA></p> |
- @fact:CHECK-SCOPE … @status:spec/done, a list of facts |
<facts ordered="false"><CHECK-SCOPE fact="true" status="spec/done">…</CHECK-SCOPE></facts> |
#source spec://…, a directive |
<p>#source spec://…</p>, a plain paragraph |
# Review flow, a title without an anchor |
<title>Review flow</title> |
backticks, emphasis, links, spec:// addresses |
the same characters inside the text |
99The dialect is closed, and it is small on purpose. It expresses exactly what Markdown can express, so that converting in either direction loses nothing in meaning, and an element outside it is a loud error, never a silent skip. Two anchors have no element of their own: one that starts with a digit, and one that collides with a word of the dialect such as title or list. For those, a generic <section id="…"> or <fact id="…"> stands in, and every reader accepts both spellings. The one exception to the rule of equivalence is the vocabulary of documentation packages, this manual among them: their runnable examples and live citations have no Markdown form, and project to Markdown one way only.
100 spec: Decision (ADR-part)
Decision (ADR-part). The XML dialect is deliberately ISOMORPHIC to the Markdown-expressible structure — exactly the constructs the markup contract names, in XML syntax, and nothing more. A schema-foreign element or attribute is a loud parse error, never a silent skip (the same closed-vocabulary law the typed-fact grammar took). This is what makes the owner's degradation law hold by construction: XML→MD loses nothing semantic because the dialect cannot express what MD cannot; «всё невыразимое — не поддерживается» is enforced by the schema, not by a lossy converter. Reopened once, 2026-09-11, for exactly one genre — §7##DOC-VOCAB-REOPENING: the documentation vocabulary of PROP-057 is additive, gated by the package kinddoc, and one-way to Markdown by law; for the spec vocabulary this decision stands unchanged.
101 spec: Decision (ADR-part; owner ruling 2026-08-22, near-verbatim)…
Decision (ADR-part; owner ruling 2026-08-22, near-verbatim): «Не правильней ли не включать внутри list элементы item, а сразу ставить в тело list элементы типа<THE-LAW fact="true"...>? И вместо<list>использовать тэг<facts>— это новое слово для зарезервированного словарика… Если же список состоит из обычного текста (там могут даже иногда встречаться факты), то все элементы — это item и группировка — list, а факты в нём рендерятся как сейчас». The law: a list whose every item is exactly one meaningful fact materialises as the vocabulary element<facts ordered="…">with the fact elements (named or generic) directly in its body — no<item>wrappers; any other list (plain text, or mixed with occasional facts) keeps today's<list>/<item>shape with facts rendered inside items.factsjoins the reserved vocabulary (an anchor namedfactsfalls back to the generic form);orderedcarries over exactly as on<list>; the model is unchanged — both shapes parse to the sameBlock::List, so the reader accepts BOTH forms (old materialisations in the wild stay readable) and a rewrite normalises the all-fact shape to<facts>. Loud errors guard the grouping: a non-fact child inside<facts>, bare text inside<facts>, an empty<facts>. Both readers — the pivot and the specmap engine's native one — learn the form mirror-wise; the converter recipe bumpsspecdoc/3→specdoc/4and the host re-materialises once. Landed: the writer branch, thefacts_blockparser (split into the pivot's ownxml_facts.rsalong the engine's seam) and the vocabulary word sit in both readers with the four loud errors pinned; the engine proves model-identity by content hash between the two shapes; the redbook README golden re-pins with two<facts>groups and the same 45 named facts; the host's 37 slots re-materialised atspecdoc/4; and the §5a stand re-ran as the regression tool it was left as — polygons rebuilt on the new shape (121 files carry groups), the sensitive tier (gpt-5.5@low) swept 9/9 with the negative control clean.
102 spec: Decision (ADR-part; the recorded reopening of ##XML-DIALECT-IS-THE-MD-SUBSET, 2026-09-11)
Decision (ADR-part; the recorded reopening of##XML-DIALECT-IS-THE-MD-SUBSET, 2026-09-11). The decision-records law lets a decision be reopened only by a named trigger, and the trigger is named: a consumer appeared that needs constructs Markdown cannot express — the documentation genre of PROP-057: a verifiable example with its expected output, a live citation of a specification rule, a generated reference block, an agent prompt with asserts. The dialect gains a second, additive vocabulary for that genre. The spec vocabulary keeps the MD-subset law unchanged; the documentation vocabulary is open only inside packages of kinddocand projects to Markdown one way, by law, not by defect.
Step 5: convert, and see nothing change
103You wrote Markdown and shipped XML. The last step shows that the XML you would have written by hand is the same file, to the byte.
1041. Ask the converter what a conversion would lose:
vibe refactor convert-source --to xml --dry-run vibevm/vibepacks/org.acme/review/v0.1.0/vibevm/vibespecs
dry-run ir-stable-loss vibevm/vibepacks/org.acme/review/v0.1.0/vibevm/vibespecs/boot/10-flow-review.md
--- source
+++ reverse-projection
@@ -8,2 +8,3 @@
#use spec://org.acme/review/contract/REVIEW#root
+
dry-run ir-stable-loss vibevm/vibepacks/org.acme/review/v0.1.0/vibevm/vibespecs/contract/REVIEW.md
--- source
+++ reverse-projection
@@ -16,2 +16,3 @@
@fact:TESTS-FIRST A change that alters behaviour carries a test that fails without it. @status:spec/done
+
dry-run ir-stable-loss vibevm/vibepacks/org.acme/review/v0.1.0/vibevm/vibespecs/source/details.md
--- source
+++ reverse-projection
@@ -14,2 +14,3 @@
- @fact:CHECK-TEST The test fails on the parent commit and passes on this one. @status:spec/done
+
summary converted=0 already=0 lossy-confirmed=0 refused=0 skipped-generated=0 skipped-foreign=0 skipped-harness=0 dry-run=3
106The converter parses each file into the tree, prints the XML, reads that back and prints Markdown again, then compares. The verdict ir-stable-loss means that the tree survived and the bytes did not, and the diff shows what changed. Here it is one blank line at the end of each file, added by the Markdown printer. Without --force the command refuses a file over such a loss. A change of meaning it refuses always, because that would be a defect of the converter, never of your file.
107 spec: The destructiveness check is the owner's…
The destructiveness check is the owner's law, implemented by reverse reconversion through the pivot: for a sourceS, parse to IR, emit the target form, read the target form back, and project it into the SOURCE form again; compare. Three classes: (1) byte-stable — the back-projection equalsSbyte-for-byte: converts silently. (2) IR-stable loss — the back-projection re-parses to the SAME IR but differs in bytes (dropped MD/XML comments, normalised layout): the verb REFUSES with a per-file description of what is lost, and proceeds only on interactive confirmation or--force. (3) IR-divergent — the back-projection re-parses to a DIFFERENT IR: always refused,--forcedoes not apply; that class is avibe-specdocdefect to file (the pivot broke its own round-trip law), never a corpus to damage.
108 spec: On a TTY, class-2 files prompt…
On a TTY, class-2 files prompt per file (yes / no / all); off a TTY, class-2 without--forceis an error listing every lossy file and what each loses.--forcewaives class 2 only.--dry-runclassifies and reports every file, writes nothing, and always exits 0 (an inventory, not an attempt); a real run exits 0 iff every requested conversion landed, non-zero when any file was refused or declined.
1092. Convert the three documents. The command writes each .xml beside its .md and deletes the .md in one act. The tree never holds a document in both forms:
110vibe refactor convert-source --to xml --force vibevm/vibepacks/org.acme/review/v0.1.0/vibevm/vibespecs
111 spec: A conversion writes the sibling serialisation…
A conversion writes the sibling serialisation and deletes the original in the same act, so the one-document-one-form law (PROP-045 ##TARGET-MIXED, the pair-collision check) holds at every instant — the tree never holdsX.mdandX.xmltogether, not even transiently between files. The verb assumes version control underneath it and keeps no backups of its own.
1123. In the package manifest, point source at the new file, vibevm/vibespecs/boot/10-flow-review.xml.
1134. Install again:
vibe install org.acme/review --assume-yes
Resolving 1 root package…
Materialising 1 package into vibedeps/:
org.acme/review@0.1.0
→ closure unchanged (1 packages)
Materialised 1 package into vibedeps/; regenerated boot artifacts for 1 node(s).
1155. Open the record again:
cat vibevm/vibedeps/org.acme.review/0.1.0/.vibe-slot.toml
schema = 1
source_hash = "sha256:6c7b5d64df7efadb3ebb7ba950902e3e3e1cc1a0a9c0a06f464c55a9f01bd209"
spec_format = "xml"
converter_recipe = "specdoc/4"
derived_hash = "sha256:908b2213850eba6789f655e7cfac4722328659e1eb5ea5017b8eb71055a152dc"
[[file]]
path = "README.xml"
sha256 = "c26e751356947bca54346b4099dc297dcdcac44831087d06436b1cb5f51ecec1"
disposition = "converted"
source = "README.md"
[[file]]
path = "vibe.toml"
sha256 = "018fe59c3ed0b41a24ad236798f5b8198c6ad983a58a9c72d7f73897a4d56e62"
disposition = "copied"
source = "vibe.toml"
[[file]]
path = "vibevm/vibespecs/boot/10-flow-review.xml"
sha256 = "925cd961b99debb27cd179a42908bdf006c5103422c2a216ee8fc874d30865d3"
disposition = "copied"
source = "vibevm/vibespecs/boot/10-flow-review.xml"
[[file]]
path = "vibevm/vibespecs/contract/REVIEW.xml"
sha256 = "ebcda3b670be21b409aa07b2600582ceb57cdc7eef8dbb3ef0dfe92dbc4f7a11"
disposition = "copied"
source = "vibevm/vibespecs/contract/REVIEW.xml"
[[file]]
path = "vibevm/vibespecs/source/details.xml"
sha256 = "d35c2052468004ff1fb0dfc7724a496d8773aa280e686ce597fd14615acf6a2d"
disposition = "copied"
source = "vibevm/vibespecs/source/details.xml"
117Compare it with the record of step 4. Each of the three documents is now copied, and the hash of each is the hash it had as converted. What the converter wrote from your Markdown is what the install wrote from it, to the byte. The compiled file did not change either:
vibe tree --plain
project: <TMP>/work/review-lab
STATIC.xml: 4557 bytes, 69 lines, 1 contribution(s)
packages: 1 roots: 1
columns: load T=transitive C=condition S=in STATIC.xml
org.acme/review static . . x
119The same bytes, the same lines. This is what one model underneath means in practice. The form you write in is your choice; the text that reaches the agent is the same either way. A project with spec_format = "markdown" runs the same road the other way. The Markdown it writes from your XML is your original file plus that one blank line.
120 spec: The C++-inheritance machinery is format-blind —…
The C++-inheritance machinery is format-blind — verified, not assumed (owner clause 2026-08-22, verbatim: «проверь что все механизмы "наследования как в C++" которые мы сделали для Markdown, точно так же работают и для XML, включая новый синтаксис секций и фактов. Если нет, это тоже нужно улучшить»). The B-011 family —#use spec://… as Xaliasing,@!Xreferences, the qualified splice (rename-on-splice with every reference kept valid), hoist/elision stubs, de-substitution of covered units, rename tombstones, and the dynamic-STATIC case — must produce BYTE-IDENTICAL compiled closures whether a dependency is authored in Markdown or in dialect XML (generic and named shapes both). Pinned by a twin-test family in vibe-spec's pipeline: each mechanism runs over an MD twin and its to_xml serialisation, outputs compared byte-for-byte; mixed trees (one dep MD, one XML) ride the same pins. Gaps found by the twins are fixed in the machinery, never by relaxing the assert. Landed: eight twins (four in vibe-spec's pipeline — alias +@!X, an alias declared inside the projected node over a mixed tree, the three-way same-short-anchor splice, fact-grain through an alias — comparing lane AND rename map; four on the bootgen floor — the static-transitive zone, single-copy hoist, de-substitution over a mixed lane, and the dynamic-STATIC install case), every twin minting its XML at run time so the family always carries the live dialect form. Parity held out of the box at the compile floor: byte-identical, no machinery change. The twins NAMED the lawful residue at the install floor —vibe:staticprovenance comments cite the true source file (extension included) and INDEX raw-snippet paths carry the materialised extension, while the dynamic-STATIC target stays the generated, extension-stableSTATIC.md— honest provenance, not a format leak.
121 spec: markdown target
markdown target: XML sources emit as Markdown through
the pivot (nested sections → heading levels, facts → anchored units,
fences/tables/quotes → their MD forms); MD sources copy verbatim. This
target exists for tooling that cannot read XML or mixed trees — named in
the mandate as load-bearing, and it stays supported for as long as this
PROP stands.
What appeared on disk
122Your package lives under vibevm/vibepacks/org.acme/review/v0.1.0/: a manifest, a README and three XML documents, yours to edit. vibe's copy of it lives under vibevm/vibedeps/org.acme.review/0.1.0/, with the record .vibe-slot.toml beside it, and is rewritten at every install. vibevm/vibespecs/boot/ holds the compiled STATIC.xml and INDEX.md, whose static line names the compiled file, next to your two boot files, which no install touches. vibe.lock, the project's lock file, pins the package to its version and its hash.
123 spec: Decision: A node's authored spec/…
Decision. A node's authoredspec/and its materialised dependencies live in physically separate trees.vibe installnever writes into any node's authoredspec/.
Edge cases and rules
124Under spec_format = "xml", a source section must not reuse an anchor of the contract, and the note's title carries no anchor. The compiler merges sections that share an anchor, and the merged result does not compile into STATIC.xml today. The install stops with fact id … is defined twice; the copy of the package is already on disk, and the lock is not written. Two documents whose titles are both {#root} collide the same way. Under the default spec_format the same package compiles.
125A dot inside an anchor is a path, not a character. {#verification.timeout} cannot be addressed, while #verification.timeout reaches a section timeout nested under a section verification. An anchor is a letter followed by letters, digits, _ and -.
126 quote from the specification
#<anchor>.<sub>… is a tree path into the document IR (§5).
127vibe init package writes link = "dynamic" whatever --link you pass. Set the link in the manifest, as step 2 does.
128With link = "dynamic" there is no compiled file. INDEX.md names the note itself, and the agent that opens it meets the #use line and must follow it on its own.
129vibe explain answers only for a package that carries a traceability map. For this one it says so and stops.
130vibe facts check catches a misspelt state in the element form, state="finished", and names the value. In the shorthand, @status:spec/finished is not read as a marker at all, and the file passes as clean. The states are plan, work, done, hold and void; the stages are idea, spec, impl, test, doc, freeze and unknown.
131 spec: One XML-shaped element, embedded in Markdown…
One XML-shaped element, embedded in Markdown (and, later, native in XML documents — the frontend duality of PROP-035 §5):