The docs commands

What torb docs check, index, skill and bundle each do, which rules they decide, and which rules only a reviewer can decide.

The documentation has a gate. torb docs check decides everything about a page that a program can decide: the front matter, the sections, the headings, the links, the anchors, the summaries, a short list of words that say nothing exactly, and every code block. What it cannot decide is whether a page is true and useful, which is what a review is for.

Synopsis#

torb docs check [root]                     Check the documentation (default root: docs)
    --standard-library <path>              Where std/ is. Default: <root>/../std
    --no-snippets                          Do not type check the snippets, which is the slow part
    --no-native                            Do not build the snippets: for a machine without a C compiler
torb docs index [root]                     Write the generated part of every index.md
    --check                                Report instead of writing, for the gate
torb docs declarations [root]              Write the marked signature blocks of the package pages, from std/
    --standard-library <path>              Where std/ is. Default: <root>/../std
    --check                                Report the stale blocks instead of writing, for the gate
torb docs skill <root> <out>               Write the Agent Skills of <root>/skills/ below <out> (fails on a dangling link)
    --check                                Report the stale files instead of writing, for the gate
torb docs bundle <root> <out>              Write llms.txt and llms-full.txt
torb docs site <root> --output <dir>       Write the website: the front page, every page, the search
    --version <v>                          The version the documentation is of (default: latest)
    --check                                Build it and check its links, and write nothing
torb docs source <path>...                 Check the doc comments of the code itself

Every command exits with 1 when it reports a problem and with 0 when it does not, and both run from the repository root:

torb docs check docs
torb docs index --check docs
torb docs declarations --check docs

All three are gates of every change. docs site writes the website from the same tree and has its own page, torb docs site. docs source is about the doc comments of std/, compiler/ and examples/ instead of about these pages, it has rules of its own, and it is not a gate yet - it has its own page, torb docs source.

What it does#

The shape of the tree#

  • Every folder has an index.md, and a folder that has nothing but its index is an error.
  • Every .md file that starts with a --- line is a page. One that does not is a plain design document and has to be named in the documents field of some index, so it is reachable and nothing is silently ignored.
  • Every page is reachable from docs/index.md, which follows from the two rules above: the body of an index is generated from its children.
  • A file name is lowercase and uses hyphens.

The front matter#

  • The opening --- is the first line, and the front matter is closed by a second one.
  • Only the nine known fields, each at most once, in the legal subset described in front-matter.md. A value that needs quotes and does not have them is an error, not a guess.
  • title, summary, kind and status are present. kind and status are in their vocabularies. A reference or package page has a source.
  • A title is at most 60 characters and has no full stop. A summary is 40 to 240 characters, ends a sentence, and does not contain this page.
  • documents only on an index, prerequisites only on a guide, and skill only as skill: omit.

The body#

  • No # heading: the title is the heading of the page, so a body starts at ##.
  • Sentence case, no full stop and no colon at the end of a heading, and no skipped heading level.
  • Two headings of one page may not produce the same anchor.
  • The ## sections the kind requires are present, in the required relative order. Other sections may stand between them.
  • A planned or draft page opens its body with the banner of its status.
  • The words usually, simply, obviously, basically, easily, of course, as mentioned, see above, and so on, please note and etc. are errors in prose. What stands between backticks is not prose, so a page about the rule can quote the word.
  • An internal link resolves to a page of the tree or to a plain document of it, and an anchor resolves to a heading of the page it names. Both are errors when they do not: an agent follows a path instead of guessing.
  • A link that leaves the tree (../../CONCEPT.md) has to exist on disk.
  • A link that starts with http:// or https:// is not followed at all. A gate has to answer the same offline as online, and an unreachable host is not a defect of the documentation.
  • A link inside the documentation is relative and names a .md file.

The code blocks#

A fence is at least three backticks and names its language: trb, text, console, json, md, yaml or diff. A closing fence is at least as long as the one it closes, so a block that shows a fence opens with four backticks. A trb fence may carry one marker, and the marker decides how hard the block is checked.

FenceWhat is checked
trbLexes and parses without a diagnostic, and is in the formatter canon
trb checkThe same, and type checks against the real std/ as the entry file of a package
trb runThe same, and is built natively and run; its output has to be what its // prints comments say
trb fragmentLexes without a diagnostic. For a signature or a shape that is not a whole program
trb errorProduces exactly the diagnostics the block declares in its // error: lines, and no other
trb exerciseParses, is in the canon and type checks: the starting code of an exercise, whose output is not compared
trb exercise incompleteLexes without a diagnostic: the starting code of an exercise, with a gap on purpose
trb skip <reason>Nothing. The reason is required and the gate prints every skip

An exercise is followed by its solution: the first trb run block after it and before the next exercise, whose // prints comments are the output the exercise asks for. An exercise without one is a problem (the contract).

A trb error block writes what it expects into the code, as a comment:

```trb error
const small: Int8 = 300
// error: `300` does not fit into `Int8`
```

The comment stays in the block, because it parses like any other comment and because an expectation that sits next to the line it belongs to cannot drift away from it. Every expectation has to appear in some diagnostic of the block, and every diagnostic has to contain one of the expectations: a block that is also wrong for a second reason teaches a second wrong line, and a block whose intended diagnostic went away would otherwise pass on the incidental one. A block that legitimately produces several diagnostics - two unknown trait names, a parse error and what follows from it - writes one // error: line per diagnostic. A trb error block that produces no diagnostic at all is an error itself.

A trb run block writes its output the same way, one // prints <line> comment per line of output, behind the statement that prints or on a line of its own:

```trb run
const text = "Grüße"
print text.byteLength()   // prints 7
```

The program has to end with exit code 0. A claim about what a program prints belongs in such a block rather than in the prose around a trb check block, because only the block is compared with what the program does.

Type checking happens in one run of the front end for the whole documentation: the standard library is read and resolved once, and every block that asked for it becomes the entry file of a package of its own, snippets/<name>/src/main.trb, which is a member of a synthetic workspace next to std/ - the way examples/<name> is a member of the repository. A block sees std/ through its imports, not as files of its own package, and sees no other block. One run costs seconds; one run per block would cost minutes.

What the native back end says#

A block that the back end refuses is a block a reader cannot run, in the VM of torb run or natively. Two blocks are asked, each lowered on its own so that a refusal belongs to it:

  • Every trb run block. A refusal is an error. The blocks that are not refused become the entries of one program, compiled once with the C compiler at -O0 into build/docs/ beside the documentation root and run; a marker line before each entry cuts the output back into one piece per block.
  • Every trb check block of a stable page. A stable page promises that what it shows works. When the back end refuses a block of it, the page either shows a form that builds or says so in a paragraph that opens with > **Not built natively yet.** and names what is refused. The note is held to the truth the other way as well: a page that has it and whose blocks all build is reported, so the note goes away when the back end catches up. A draft or planned page is not asked, because its banner already says it may not work.

The C runtime is $TORB_RUNTIME, or the runtime/ beside the standard library.

The canon#

Two of the rules of the formatter canon are decided on every block that parses:

  • calls: a call is a command wherever the grammar allows it, and has parentheses everywhere else. Both directions are reported. A call of a field that holds a function is left alone, because there the parentheses are meaning and not style: debugger.stop(event) calls the field, and debugger.stop event would write it. Only the type checker knows what a callee is, so a trb check and a trb run block are held to this rule in the type check, with what the checker resolved - a call of a field, and one whose callee it did not resolve, keeps the way it is written, as torb format keeps it. A trb block, which nothing type checks, leaves alone a callee that names a field of the type it stands in.
  • strings: a multi-line """ or raw""" is indented two spaces deeper than the line its statement starts on, with a closing """ that stands alone aligned with the content.

What is not checked is the layout torb format adds to the canon: indentation, spaces and blank lines. A snippet that passes here is in the canon; whether it is in the layout of torb format is not promised.

The declarations of a package page#

torb docs declarations writes the signature blocks of the ## Declarations section of a std/ page. A line <!-- torb:declaration: <Name> --> directly above a trb fragment block names the construct the block shows, and the command replaces the block with what the model of torb doc says of it:

  • the signature, the way the source writes it and without public,
  • every member that is not private, one per line and indented,
  • the extend blocks of the package that target it: the header of one that implements a trait, whose members the trait documents, and the whole block of one that adds members of its own.

The package is the one the title of a kind: package page names (title: std/uri). Several markers above one block show several constructs in it, in their order, and a name that is no construct of the package but a type its extend blocks add to (Bytes, String) shows those blocks. Everything else of the page is untouched, byte for byte: the headings, the prose, and every block without a marker.

A marker that names nothing of the package, one that stands above no trb fragment block and one on a page whose title is no package are problems at their line, and so is a std/ that does not type check. --check writes nothing and reports every block that is not what std/ declares, with the constructs it shows and its first line that differs.

The indexes#

torb docs index writes the lines between <!-- torb:index:begin --> and <!-- torb:index:end --> of every index.md, from the title, summary, order and status of the children. Everything above the first marker is untouched, byte for byte. Below the closing marker nothing is allowed.

The generated body has up to three sections: ## Sections for the folders below, ## Pages for the pages of the folder, and ## Design documents for what the documents field names. A child with status: planned or status: draft is marked in its entry.

--check reports every index whose body is not what it would write, and writes nothing. That is the form the gate uses.

The translations#

A page below translations/<language>/ translates the English page at the rest of its path, and is checked beside it: its front matter, with the kind of its original and a translates field; its headings, but not the English names of the sections a kind requires; its links, resolved from the folder of its original; and its trb blocks. The strings.json of a language is one JSON object of texts whose keys are the keys of the site's interface, with the language's own name as language.

A translation whose original changed since it was made is no problem. The check lists it after the problems, on a line that starts with outdated: and names the value translates gets once the translation is brought up to date.

Examples#

A green run names the numbers, so a change in them is visible in a diff:

$ torb docs check docs
224 pages, 24 folders, 979 snippets, no problems

A problem names the file, the line and the rule:

$ torb docs check docs
../docs/language/types/values.md:31: A call is a command wherever the grammar allows it. Write `Ok ...` without parentheses
../docs/language/types/values.md:52: `soon` is not a status. One of: stable, draft, planned

2 problems in 42 pages

What no command decides#

These are the reviewer's, and they are the ones that matter:

  • Whether a claim is true of the compiler as it is today.
  • Whether the example is the smallest one that shows the construct.
  • Whether ## What this is not names the mistake a reader will actually make.
  • Whether the summary is enough to decide whether to open the page.
  • Whether the page is understandable when it is the only page loaded.
Esc