Markdown To

Specifications v0.1 draft Sheet 04 of 04

backlog@0.1

A prioritized backlog as a plain Markdown file — level-2 headings are bands in priority order, each task carries one of four markers, and a task may say what it is waiting on. It is [email protected] plus exactly three deltas, so that the page an agent works from is the same file a person reads.

Status
normative · draft, will change
Inherits
shared conventions · the item model
Artifacts
schema.json
fixtures/
Owns codes
MDTO400–MDTO499 15 allocated

§ 01

The file, and the ladder

On the left, a conformance fixture — specs/backlog/fixtures/valid/full-bands.md, byte for byte. On the right, what mdto render makes of it. A checklist has one axis and a board has one; a backlog has three at once — which band a task sits in, which of four states it is in, and what is holding it — so the page is built to show all three without deriving any of them.

Fig. 1 One file, two views — [email protected] static render
tidepool.backlog.md
---
markdownto: [email protected]
title: Tidepool
id: tidepool-backlog
---

# Tidepool

> Bands run in priority order: Now, Next, Later, Someday, Done. Top of a band is
> highest priority — reordering lines is how we reprioritize. Nest children to
> break a task down; a parent finishes only when its children do.

## Now

- [/] Cache the tide tables so the app works offline [owner:: @rosa] ^offline-cache
  - [x] Decide the on-disk format — one row per station-day
  - [/] Write the importer
  - [ ] Evict stations nobody has opened in ninety days
- [ ] Fix the DST jump that shifts every time by an hour twice a year [priority:: high] [due:: 2026-09-15] ^dst-bug

## Next

- [ ] Home-screen widget showing the next high tide — blocked by [[#^offline-cache]] ^widget
- [ ] Station search that tolerates misspelled harbours [priority:: medium] #ux
- [ ] Ship a proper icon — blocked by Marina's illustration ^icon

## Later

- [ ] Submit to the App Store — blocked by [[#^dst-bug]], [[#^icon]] ^submission
- [ ] Move the harbour dataset notes into the handover doc — blocked by [[handover#^dataset]]
- [ ] Swell height as well as tide height [priority:: low]

## Someday

- [ ] Apple Watch complication
- [ ] Barometric pressure overlay, if anyone ever asks for it

## Done

- [x] Pick a tide-data source with a licence we can live with ^data-source
- [x] Skeleton app with one hard-coded station
- [-] Rewrite the parser in Rust [reason:: the Swift one is already fast enough] ^rust-parser
rendered · mdto render static · no script

Tidepool

12 openof 16 tasks across 5 bands · 4 blockedread-only

Markersopenin progressdonedropped (cancelled)

01

Now

2 open · 2 in progress · 1 done
  • Cache the tide tables so the app works offline

    in progressowner @rosa^offline-cache

    • Decide the on-disk format — one row per station-day

    • Write the importer

      in progress

    • Evict stations nobody has opened in ninety days

  • Fix the DST jump that shifts every time by an hour twice a year

    priority highdue 2026-09-15^dst-bug

02

Next

3 open
  • Home-screen widget showing the next high tide

    blocked by ^offline-cache^widget

  • Station search that tolerates misspelled harbours #ux

    priority medium

  • Ship a proper icon

    blocked by Marina's illustration^icon

03

Later

3 open
  • Submit to the App Store

    blocked by ^dst-bugblocked by ^icon^submission

  • Move the harbour dataset notes into the handover doc

    blocked by handover#^dataset

  • Swell height as well as tide height

    priority low

04

Someday

parked2 open
  • Apple Watch complication

  • Barometric pressure overlay, if anyone ever asks for it

05

Done

closed2 done · 1 dropped
  • Pick a tide-data source with a licence we can live with

    ^data-source

  • Skeleton app with one hard-coded station

  • Rewrite the parser in Rust

    dropped the Swift one is already fast enough^rust-parser

The bands are drawn in the file’s own order and never sorted, so the rank numerals beside them are the priority the vocabulary gives each band rather than a position the renderer chose. Someday and Done step back onto a recessed ground because their names reserve a meaning; a held task loses its lift rather than its contrast, because the text of a blocked task is the text most worth reading. Every value went through the escaper on its way out — band names, task text, [reason:: …] values and a blocker’s own words are all untrusted input. The markup was produced by @markdownto/web at build time; see site/tools/build-specs.mjs.

Nothing on the right is computed

“Ready” is the question a backlog exists to answer, and this page does not answer it. The spec defines ready work precisely — open, unblocked, and outside Someday and Done — and then has the intermediate representation carry the inputs rather than the conclusion, so that a tool with more context is never arguing with a precomputed boolean.

The same restraint runs the other way. A blocker that has lifted is drawn as nothing at all, because leaving it as a chip would put the word “blocked” on a task that is not — and following blocked by ^offline-cache lands on the row it names, because that is a question the page can answer in one click without answering a different one on the reader’s behalf.

§ 02

Grammar

[email protected] is [email protected] with three additions and nothing else: an extended marker enum, a reserved band vocabulary, and a blocker relation. Everything not below is todo’s grammar verbatim. Distilled for this page; the normative text is specs/backlog/SPEC.md, and it is the one that governs.

2.1

The envelope, and the one key backlog adds

A conforming file begins with YAML frontmatter naming exactly one spec. The envelope, never the filename, is authoritative — .backlog.md aids discovery and nothing more.

the envelope one added key
---
markdownto: [email protected]
title: Tidepool            # optional, reserved
id: tidepool-backlog       # optional, reserved
bands:                     # optional, spec-defined
  - Commissioned
  - Drafting
  - Done
---
markdownto
Required. The exact string form [email protected]. A sequence or mapping here is MDTO004.
title, id
The reserved keys from the shared conventions. Optional, both strings.
bands
A sequence of strings, and the only key this spec adds. It replaces the reserved vocabulary rather than merely checking it: the declared names become the complete vocabulary and the declaration order becomes the priority order. A wrong type is MDTO007.
anything else
An unknown key: parsed, preserved, written back verbatim, never validated. This is what lets an agentsFS page carry description: and agentsfs_role: backlog in the same frontmatter block as the envelope and lose nothing on round-trip.
2.2

Bands, and the vocabulary they come from

A level-2 heading is a band. With no bands: declaration, a backlog’s bands are exactly these five names, in this priority order — and any other ## heading is MDTO401.

## Now
Rank 0. Being worked, or next to be picked up.
## Next
Rank 1. Committed, not started.
## Later
Rank 2. Real work, not yet scheduled.
## Someday
Rank 3. Parked. Never offered as ready work — ideas kept rather than lost.
## Done
Rank 4. Closed. Holds terminal tasks and is safe to prune, which is the one thing reserving the name buys.

There is no implicit band. A backlog is a partition by priority, so a task in no band has no priority: a task above the first ## is MDTO406, and the repair is a heading, never an invented inbox. Prose before the first band is normal and legal — most backlogs open with a legend explaining their own markers. A backlog may also have no bands at all: an empty backlog is a real state, and a file should not have to carry a ceremonial heading to be one.

Empty bands may be omitted and may be written out empty; both are correct and neither is a diagnostic, even when the document declares bands:. Heading levels 3 and deeper are not part of the spec (MDTO405): nesting breaks a task down, headings group bands, and there is no third mechanism. A band heading carries no metadata — an inline field or block identifier on one is MDTO407. Task order within a band is priority order, top highest, and nothing sorts it.

Why band names are reserved, when kanban refuses to reserve “Done”

kanban is right for boards: they are drawn by their author, “Done” might mean “done this sprint”, and hard-coding a name would be a guess about somebody’s workflow. A backlog is not drawn, it is queriedwhat should I pick up — and that question cannot be answered without knowing which band is parked and which is closed. So kanban’s columns: is opt-in strictness over a free vocabulary, and backlog’s bands: is opt-out from a fixed one. The polarity is inverted on purpose, which is why ## Icebox is MDTO401 here and renders happily there.

Reserved semantics attach to names, not to positions. A declaration that keeps Someday or Done keeps their meanings; one that drops them means the document simply has no parked band and no closed band. And document order is presentation while the vocabulary order is priority — when the two disagree the vocabulary wins and MDTO403 says so, as a warning.

2.3

Four markers, two of them terminal

A task is a GFM task list item on a bullet list, and [email protected] declares its full marker enum. [/] and [-] are what people already type in backlogs in the wild: they keep the file scannable, because the left edge of the line tells you everything.

the four states
- [ ] Ship a proper icon
- [/] Cache the tide tables so the app works offline
- [x] Pick a tide-data source with a licence we can live with
- [-] Rewrite the parser in Rust [reason:: the Swift one is already fast enough]
[ ]
open. Not terminal.
[/]
in-progress. Not terminal — and active rather than ready, which is why a tool offers it first.
[x], [X]
done. Terminal.
[-]
dropped. Terminal. A different outcome from done, and the same fact for everyone downstream: nobody is waiting on this any more.
anything else
The shared MDTO031, whose message names backlog’s enum rather than todo’s. Every tool must preserve an unrecognized marker character verbatim rather than coerce it to open or checked.
[reason:: …]
The one inline field this spec promotes to a recognized key. Free text, no diagnostic of its own, meaningful on any task — so a parked idea can record why it is parked. It is what drop --reason writes.
Done is a band; [x] is a state

They are independent, in the grammar and at the verb layer alike. - [x] marks a task finished wherever it sits; moving a task into Done does not check its box, and checking its box does not move it. There is no done-role bandpromote and demote never write a checkbox, whatever the destination is called.

This is the one place backlog and kanban genuinely differ, so do not reason across. Both keep the two facts independent in the grammar, but kanban couples them at the verb layer: its last column carries a done role and kanban move writes the box when it crosses it. backlog takes no such step. Do not check a box because you moved a task into Done, and do not move a task because you checked its box. What the reserved name buys is what a tool may do with the band — prune it — which is why a non-terminal task sitting in Done is called out as MDTO421: a prune would destroy it.

2.4

The blocker relation

A task may end with a blocker clause saying what it is waiting on: a space, an em dash, a space, the words blocked by matched case-insensitively, a space, and then a non-empty comma-separated list. The clause stays part of the task’s text, like a tag and unlike a field, because it reads as part of the sentence and a plain Markdown viewer has no other way to show it.

three blockers, three fates
- [ ] Home-screen widget showing the next high tide — blocked by [[#^offline-cache]] ^widget
- [ ] Submit to the App Store — blocked by [[#^dst-bug]], [[#^icon]] ^submission
- [ ] Ship a proper icon — blocked by Marina's illustration ^icon
[[#^slug]] found
Resolves in this file, and lifts itself the moment the target task reaches [x] or [-]. Keying on terminal rather than on done is what stops a dropped task silently deadlocking everything behind it.
[[#^slug]] missing
MDTO411. It names no block identifier in this file, so it can never lift; the repair is to fix the reference.
[[Name#^slug]]
External, and out of validation scope in 0.1 deliberately. Validation is a single-file parse that performs no I/O, so a claim about a file it has not been given is recorded and reported, never diagnosed. Do not “fix” a cross-file blocker by deleting it.
prose
First-class and expected. “blocked by Marina’s illustration” is more honest than inventing a task to point at. It holds until an author edits it away.
a cycle
A permanent block, reported once per member as MDTO415. A task that blocks itself is the one-member case.

A task is blocked when it has at least one blocker that is not satisfied. Nothing is scanned inside code spans or fences, so a file can document this clause without writing one. [[Name#^slug]] is always external even when Name is this file’s own name: the envelope and never the filename is authoritative, and a parser may have been handed a string rather than a path. Inside a backlog write [[#^x]]; from other files write [[backlog#^x]].

Why the clause comes before the identifier

The shared conventions require a block identifier to be the final token of its line, and that rule is load-bearing: it is how every implementation’s item parser finds an id at all. So the canonical order is text  [fields]  #tags  — blocked by …  ^id.

Files written under the older agentsFS convention have it the other way round and need a mechanical reorder before they will validate. Without it the identifier stops being an identifier and becomes literal text (MDTO012), and then every reference pointing at it fails to resolve (MDTO411) — one transposition, two families of diagnostic.

2.5

Ready work

The band vocabulary only means something through this derivation, so it is normative for any tool that offers what should I pick up. A task is ready when all three hold.

state
It is open. A task that is in-progress is active, not ready — and active work is offered first.
not blocked
Every blocker it declares is satisfied. A broken relation counts as blocked, never as clear.
band
Its band is neither Someday nor Done. Everything else ranks: Now before Next before Later, and within a band, document order.

A child of an open parent is itself ready — decomposition means the children are the work. And a parent that is terminal while a child is not is MDTO420, a warning rather than an error: the file is unambiguous, it says exactly what it says, and validation is advice. The verbs are the strict half. finish and drop refuse rather than guess.

Read [email protected] in full — the normative text Shared conventions

§ 03

Verb reference

[email protected] owns the vocabulary add, start, finish, drop, block, unblock, promote, demote, reorder, graduate — and spends it on the state machine and the relation that [email protected] and [email protected] do not have. Where a name is shared the contract still is not: backlog promote never writes a checkbox, while kanban move writes one when it crosses that spec’s done role. Read the verb reference of the spec the file declares.

verbsignaturesource edit

add

mdto backlog add "<text>" --band <name> [--create-band] [--priority p] [--due d] [--owner o] [--blocked-by <ref>…] [--id <slug>] [--top | --bottom | --before <ref> | --after <ref>]

Inserts one line at the end of the target band, or wherever the position flags say. --band is required — there is no implicit band to fall back on. The bullet marker and indentation are copied from the nearest sibling; fields land in the canonical order, then the blocker clause, then the identifier. A band that does not exist is an error unless --create-band, which also adds the name to a bands: declaration if the file has one, because leaving them inconsistent would immediately produce MDTO401.

start

mdto backlog start <ref>…

Writes / into the checkbox on one line. Defined from any state, so it doubles as the reopen path: start on a [x] task writes [/]. 0.1 has no verb that writes [ ] back onto an existing task — edit the line. That is deliberate, and narrow enough to be honest about.

finish

mdto backlog finish <ref>… [--cascade]

Writes x into the checkbox. Refuses — non-zero exit, nothing written — when the task has any non-terminal descendant, and says which lines. --cascade performs the batch; a descendant that is already terminal keeps its marker, so cascading a finish never overwrites a [-].

drop

mdto backlog drop <ref>… [--reason "<text>"] [--cascade]

Writes - into the checkbox, and with --reason appends [reason:: …] in the canonical field position — after the text and any existing fields, before the blocker clause and the identifier. Without --reason the line’s text is untouched. Same refusal as finish.

block

mdto backlog block <ref> --on <ref>… [--prose "<text>"]…

Rewrites one line’s clause, appending to an existing list rather than replacing it. The one verb allowed to pin without being asked: when --on names a task that has no identifier, block pins one — a slug derived from that task’s text — because a reference needs a referent, and it reports every id it pinned in --json. An --on matching no task is an error, not a prose blocker; write prose deliberately with --prose.

unblock

mdto backlog unblock <ref> [--on <ref>…]

Removes the entire clause together with the that opened it, leaving the identifier where it was. With --on it removes one element and its separating comma, and the whole clause if that was the last one. An --on matching zero or several elements refuses.

promote

mdto backlog promote <ref>… [--to <band>] [--create-band] [--top | --bottom | --before <ref> | --after <ref>]

Moves the task one band toward Now, skipping bands the document does not contain, or straight to --to. Its lines and its children’s are deleted and reinserted verbatim: same text, fields, tags, identifier, blocker clause, markers and relative indentation. It never writes a checkbox, whatever the destination band is called.

demote

mdto backlog demote <ref>… [--to <band>] [--create-band] [--top | --bottom | --before <ref> | --after <ref>]

The mirror, toward Done. The direction is checked either way: promote --to a lower-priority band is an error and so is its opposite, because a verb that silently did the reverse of its own name would be a trap. Promoting a task already in the top band is a successful no-op with an empty diff.

reorder

mdto backlog reorder <ref> [--top | --bottom | --before <ref> | --after <ref>]

The same delete-and-reinsert, constrained to the task’s own band. Reprioritizing within a band is the most common edit a backlog gets, and keeping it in a separate verb from promote means “move this up” can never cross a band boundary by accident.

graduatedeferred

mdto backlog graduate <ref> --to <path>

Writes a new note and rewrites the task’s text to link it — the edit agentsFS describes when a task accumulates enough state to deserve its own page. Specified, and deliberately unshipped. It is the only verb here that is not a patch-engine operation: it writes two files, and the engine’s whole safety story is one file, one source hash, all-or-nothing. Specifying the edit records the design while refusing to invent a multi-file transaction contract inside a verb. Do not implement it by writing the note first and hoping.

3.1

Addressing a task, and the common flags

<ref>
A pinned identifier with or without the caret (offline-cache, ^offline-cache); otherwise a case-insensitive substring of the task’s normalized text ("tide tables"). If a text ref matches zero or more than one task, the verb fails with a non-zero exit code and changes nothing. Ambiguity is never resolved by picking the first match.
--band
Narrows the search on start, finish, drop, block, unblock and reorder. On add, promote and demote it names the destination instead and never narrows anything.
--file
Defaults to the single *.backlog.md in the working directory; failing that, the single page whose frontmatter declares markdownto: [email protected] — which is how an agentsFS backlog page is found without knowing its name.
--expect <hash>
Every mutating verb requires a matching source hash before it writes. The CLI computes one from the file it just read unless this flag supplies one from an earlier read; a mismatch is a conflict and nothing is applied. --force overrides the validation refusal, never this check.
--dry-run
Prints the unified diff and writes nothing.
--json
Emits {edits, pinned, addresses, hash}, for a caller that wants a value rather than a file.
--pin, --id
Pins a minted identifier on the touched task, or pins this one instead — refusing on collision (MDTO011). No verb pins otherwise, with the single documented exception of block on a target that has none.

What no verb ever does

Reformats a line it was not asked to change·normalizes bullet markers, indentation or blank lines·reorders tasks or bands it was not asked to reorder·derives a parent’s state from its children, or a child’s from its parent·checks a box because of which band a task landed in, or moves a task because its box was checked·lifts a blocker by editing the clause when the target moved·prunes the Done band·pins an identifier without being asked, except block on a target that has none·rewrites the markdownto version·edits a file that fails validation with an error·writes a file whose source hash no longer matches the one it read.

§ 04

Diagnostics carry the repair

Three of the findings below exist because the failure they describe is silent: the author believes a task is blocked and a tool believes it is ready. A file can look completely ordinary and be wrong in exactly that way, so the messages quote the offending element back, name what it actually parsed as, and say which direction to repair it in.

launch.backlog.md 2 errors, 5 warnings
---
markdownto: [email protected]
title: Tidepool launch
---

## Now

- [ ] Ship the home-screen widget — blocked by
- [ ] Wire up the settings screen — blocked by [[#^onboarding]]
- [ ] Write the App Store copy — blocked by [[#^screenshots]] and the new icon
- [ ] Record the demo video, blocked by [[#^copy]] ^screenshots
- [ ] Draft the release notes — blocked by [[#^launch]] ^copy
- [ ] Book the launch tweet — blocked by [[#^copy]] ^launch

## Done

- [x] Pick a tide-data source — blocked by [[#^copy]]

mdto validate · verbatim 7 diagnostics

  • MDTO410 error line 8

    - [ ] Ship the home-screen widget — blocked by

    Malformed blocker clause: — blocked by is followed by nothing. List at least one blocker, or remove the clause.

  • MDTO411 error line 9

    - [ ] Wire up the settings screen — blocked by [[#^onboarding]]

    Blocker [[#^onboarding]] names no task in this file; #^ targets are resolved within the document.

  • MDTO414 warning line 10

    - [ ] Write the App Store copy — blocked by [[#^screenshots]] and the new icon

    Blocker [[#^screenshots]] and the new icon mixes a wikilink with prose, so it is one prose blocker and will never lift on its own. Separate blockers with commas.

  • MDTO413 warning line 11

    - [ ] Record the demo video, blocked by [[#^copy]] ^screenshots

    blocked by [[#^copy]] looks like a blocker clause but is not introduced by , so it is prose and this task is not blocked.

  • MDTO415 warning line 12

    - [ ] Draft the release notes — blocked by [[#^launch]] ^copy

    Blocker cycle: ^copy is blocked by ^launch, which is blocked by ^copy. Neither can ever become unblocked.

  • MDTO415 warning line 13

    - [ ] Book the launch tweet — blocked by [[#^copy]] ^launch

    Blocker cycle: ^launch is blocked by ^copy, which is blocked by ^launch. Neither can ever become unblocked.

  • MDTO412 warning line 17

    - [x] Pick a tide-data source — blocked by [[#^copy]]

    Blocker clause on a task that is already done; it has no effect.

Recovery always leaves a broken blocker blocking

The two possible mistakes are not symmetrical. Treating a blocked task as ready hands an agent work that cannot be done and hides the author’s own note that it was waiting; treating a ready task as blocked costs one glance. So every recovery rule takes the second failure over the first: a malformed clause recovers to a single prose blocker holding the body verbatim, an unresolvable target is kept as unresolved, and a cycle leaves every member blocked.

MDTO413 is the near miss worth studying. …, blocked by [[#^copy]] is not a clause — a comma is not an em dash — so the parser is right to read it as prose, and the task genuinely is not blocked. Reporting that as an error would be the validator overruling the grammar; saying nothing would leave the author certain of something false. A warning that names both readings is the only honest answer.

4.1

The codes this spec owns

MDTO400MDTO499 belongs to [email protected], in three families: bands in 401409, the blocker relation in 410419, state and hierarchy in 420429. Codes are never reused, renumbered or repurposed. Everything else a backlog file can get wrong is a shared code — and MDTO031, the unrecognized marker, is shared and not owned here: the same broken marker produces the same code in every spec, and only the message names this spec’s enum.

MDTO401 error Unknown band name: neither a reserved band nor declared in frontmatter bands.
MDTO402 error Duplicate band.
MDTO403 warning Band order in the document differs from the vocabulary’s priority order. A warning, because the vocabulary wins and the document still reads.
MDTO404 error Band heading has no name. It still opens a band, so tasks never migrate into the previous one while a file is being repaired.
MDTO405 error Heading level is not valid in a backlog document: only one leading # title and ## bands.
MDTO406 error Task appears before the first band heading; there is no implicit band.
MDTO407 error Inline field or block identifier on a band heading; bands carry no metadata in 0.1.
MDTO410 error Malformed blocker clause: the list is empty, or an element is.
MDTO411 error A [[#^slug]] blocker names no block identifier in this file.
MDTO412 warning Blocker clause on a task that is already terminal; it has no effect.
MDTO413 warning Text looks like a blocker clause but is not introduced by an em dash, so it is prose and the task is not blocked.
MDTO414 warning An element contains a wikilink but is not exactly one, so it is a prose blocker and will never lift on its own.
MDTO415 warning Blocker cycle: the tasks in it can never become unblocked. Reported once per member.
MDTO420 warning Terminal task has a non-terminal child. The soft half of “a parent finishes only when its children do”.
MDTO421 warning Task in the Done band is not in a terminal state, so a prune would destroy it.
One unusable key, not a cascade

When a bands: declaration cannot be read at all (MDTO007) it is discarded — and the vocabulary check MDTO401 and the order check MDTO403 are skipped with it rather than falling back to the reserved names. A file that mistyped one frontmatter key would otherwise report an unknown band on every heading it has, burying the one diagnostic that matters under a list of its consequences.

§ 05

For agents

A backlog is the page an agent reads before it decides what to do, so it is the spec where reading and writing meet most often. The value over freehand editing is determinism: guaranteed-valid output, minimal diffs, no whole-file context required — and a relation that lifts itself instead of needing to be swept.

mdto — working name in progress
$ mdto spec backlog
[email protected] — agent-facing specification
  1. Purpose   what the format is for
  2. Grammar   heading = band, item = task, — blocked by = a relation
  3. Rationale why it is shaped that way
  4. Examples  valid + invalid + diagnostics
  5. Verbs     add · start · finish · drop · block · unblock · …

$ mdto validate tidepool.backlog.md
ok conforms to [email protected] · 16 tasks, 5 bands, 4 blocked

$ mdto backlog start "station search" --dry-run
--- tidepool.backlog.md
+++ tidepool.backlog.md
-- [ ] Station search that tolerates misspelled harbours [priority:: medium] #ux
+- [/] Station search that tolerates misspelled harbours [priority:: medium] #ux
note --dry-run: nothing written

$ mdto backlog block submission --on "swell height"
ok 1 line rewritten · pinned ^swell-height on the target
  • The rationale is the repair manual

    Purpose, grammar, rationale, examples with their exact diagnostics, and the verb reference — versioned alongside the spec. mdto spec backlog prints it. This spec’s rationale is unusually blunt about the wrong repairs, because they are all plausible: do not delete a cross-file blocker, do not check a box because a task moved into Done, do not implement graduate by writing the note first and hoping.

  • Fetchable, not scrapeable

    The same document lives at a stable URL beside an llms.txt so an agent never has to scrape a human documentation site to author a conforming file.

    fetch markdownto.ai/specs/backlog.md

    fetch markdownto.ai/llms.txt

  • Fixtures are the contract

    An independent implementation conforms if and only if it agrees with specs/backlog/fixtures/. Valid fixtures must parse with no error and round-trip byte for byte; every invalid one carries a sibling .diagnostics.json listing the exact codes, severities and lines it must produce. All fifteen codes above are fixtured.

    universal validaterenderspec