Markdown fenced-block profile

markdown+ravel-fences-v1 is the first Ravel format adapter. It treats only fenced code blocks as chunks; headings, links, and prose have no effect on chunk identity.

Named chunks

Use Pandoc-style attributes after a fence language:

```typescript {.ravel #compiler--what type=ts .browser}
export function compile() {}
```

This produces document::compiler:what.ts. The first bare word is the display language (typescript); it becomes the default Ravel type when type is not present. #name supplies the chunk component. #name--minor is a shorthand for a chunk/minor pair; use chunk=name--with-double-hyphens when the base chunk literally contains --. Explicit minor= and type= override defaults.

.ravel, .no-ravel, .greedy, and .end are reserved control classes. Other classes become chunk tags. A named #chunk fence is Ravel even without .ravel; use .ravel to make this visible, and it is required for .greedy.

Greedy continuation

A .greedy named fence may be followed by plain fences of the same language. Their bodies are joined in source order and recorded as chunk fragments:

```typescript {.ravel #compiler .greedy}
export const first = true;
```

```typescript
export const second = true;
```

```typescript {.end}
export const last = true;
```

The .end fence is included, then closes greedy mode. A different-language fence, a .no-ravel fence, or a new named/Ravel fence closes greedy mode before that fence is processed.

Document identity and modes

Document identity is selected in this order: CLI --document, TOML files.document, YAML front matter (ravel.document), then a normalized source filename stem. The adapter has two modes:

  • opt-in (default): unnamed fences are ordinary Markdown examples.
  • primary: every non-excluded fence must start a named Ravel chunk or be a valid greedy continuation; use .no-ravel for ordinary examples.

Definition pipes

The profile parses pipe="..." into the chunk’s definitionPipeline and also retains the authored text at metadata.data.ravel.definitionPipe. The core runs the pipeline after ordinary substitutions have been protected. Built-in core transforms work directly; language transforms are supplied by a host or a separate transform package through transformGraph(map, { transforms }).

For example, a Node Pug package may register a pug transform without making the portable core depend on Pug. emit(...) remains retained metadata here; definition-time graph emission is not part of this phase runner.

Directive fences

Reserve the fenced language ravel for graph directives. A directive fence is not a source chunk, regardless of Markdown mode. Its commands may be separated by newlines or semicolons; calls can span lines and nest normally.

```ravel
in("shared.md")

create("program:browser.js", compose(
  _"shared::preamble.javascript",
  newline(2),
  _"main.javascript",
  pass(trim(), emit("observed.js")),
  pipe(trim(), emit("min.js"), indent(2))
))

alias("public.js", _"program:browser.js")
out("dist/program.js", _"public.js")
```

The directive expression grammar is deliberately small:

Command     = Identifier "(" [ Expression { "," Expression } ] ")"
Expression  = String | Number | QuotedReference | Command
QuotedReference = "_" String

create(name, compose(...)) requires a document-local name, such as program:browser.js; the adapter supplies the document component. A reference inside compose appends its completed chunk value. newline(n) changes only the separator before the next appended reference (the default is one newline). pipe(...) replaces the composition accumulator with its transformed value. pass(...) runs the same transform/emit sequence but forwards the original accumulator, which makes it a useful tee.

Transform calls currently accept string and number arguments. emit("minor"), emit("minor.type"), and emit(".type") follow the normal local emit rules: they retain the create node’s document and base chunk. alias is the deliberate way to introduce another graph name; its program provenance points back to the original target. out receives a file-like name and a quoted chunk reference; local references are qualified with the enclosing document by the adapter.

All Markdown documents and in(...) imports are mapped before directive evaluation. Thus forward references work regardless of source order. After the graph settles, missing references and cycles produce ordinary source-linked diagnostics. The Node host accepts both .md/.markdown and JSON Ravel Maps as in targets; paths resolve relative to the directive’s source document.

Filesystem boundary

Every build has a filesystem root. For a TOML run it is the directory containing the TOML file; for a direct Markdown or JSON run it is the directory containing the initial file. [[files]] paths and Markdown in(...) imports must stay under that root. Absolute paths and .. traversal that escape it, and any symbolic link at or below that root, are rejected.

build.out_dir, directive/TOML deliverable names, and --graph writes are also confined to that root. Deliverable names remain relative to build.out_dir. The explicit CLI --out-dir flag is the exception: it may select a new output root anywhere, but output below that chosen root still cannot escape it or traverse a symbolic link.

One TOML build run

A TOML file represents one build run and can list multiple Markdown files:

version = 1

[build]
name = "web"
out_dir = "dist/web"
clean = false
# backup = true
# backup = "backups/web-before-clean.zip"

[[files]]
path = "docs/guide.md"
document = "guide"
mode = "primary"

[[files]]
path = "docs/runtime.md"
document = "runtime"

[[outputs]]
name = "dist/main.js"
from = "guide::main.javascript"

Run ravel build --config ravel-web.toml. A project file named ravel.toml can instead be run with plain ravel. Paths, including build.out_dir, are resolved relative to the TOML file and must remain below its directory. Use a separate TOML for a different build pathway; configs are not merged.

For one document, use ravel build guide.md --out-dir dist or inspect without writing with ravel inspect guide.md --mode primary.