Markdown To

Specifications v0.1 draft Sheet 01 of 03

todo@0.1

A checklist or backlog as a plain Markdown file — task items, optionally grouped into sections, where the file is the complete state of the list. It also defines the shared item model that [email protected] builds on, so a todo list and a one-column board are the same thing seen from different angles.

Status
normative · draft, will change
Inherits
shared conventions envelope, ids, fields, MDTO001–099
Artifacts
schema.json
fixtures/
Owns codes
MDTO100–MDTO199 4 allocated

§ 01

The file, and the app

On the left, a conformance fixture — specs/todo/fixtures/valid/full-metadata.md, byte for byte. On the right, what mdto render makes of it. There is no database between them, and no second copy of anything.

Fig. 1 One file, two views — [email protected] static render
launch.todo.md
---
markdownto: [email protected]
title: Markdown To — launch checklist
id: mdto-launch
maintainer: akshay
---

## Specs

- [x] Settle the shared authoring conventions ^t1
- [ ] Publish [email protected] fixtures [priority:: high] [due:: 2026-08-20] #specs
- [ ] Publish [email protected] fixtures [priority:: high] [due:: 2026-08-22] #specs

## Engineering

- [ ] Build the patch engine [owner:: @akshay] [priority:: high] ^t4
- [ ] Fuzz the round-trip until a random edit never touches a neighbouring line [priority:: medium] [effort:: 2d] #infra
- [ ] Wire the CLI verbs [owner:: @akshay] [due:: 2026-09-01]

## Someday

- [ ] Obsidian Kanban importer [priority:: low]
rendered · mdto render static · no script

Markdown To — launch checklist

1 doneof 7 items across 3 sectionsread-only

Specs

1/3
  • Settle the shared authoring conventions

    ^t1

  • Publish [email protected] fixtures #specs

    priority highdue 2026-08-20

  • Publish [email protected] fixtures #specs

    priority highdue 2026-08-22

Engineering

0/3
  • Build the patch engine

    priority highowner @akshay^t4

  • Fuzz the round-trip until a random edit never touches a neighbouring line #infra

    priority mediumeffort 2d

  • Wire the CLI verbs

    due 2026-09-01owner @akshay

Someday

0/1
  • Obsidian Kanban importer

    priority low

Every value on the right went through the escaper on its way out — titles, section names, item text, field values and tags are all untrusted input, and raw HTML in the source is never passed through. The rendered pane is static: no script, no network, correct with JavaScript disabled because it never had any. The markup was produced by @markdownto/web at build time and pasted in; see site/tools/build-specs.mjs.

The degradation is the promise

Open that same file in GitHub, in Obsidian, in less, or in a mail client and you get a sectioned checklist that is still genuinely useful. Nothing in it needs the tool in order to mean something. That is not a nice side effect of the design — it is the design, and every rule below is downstream of it.

§ 02

Grammar

Distilled for this page. The normative text — with the full recovery rules, the RFC 2119 keywords, and every diagnostic’s exact wording — is specs/todo/SPEC.md, and it is the one that governs.

2.1

The envelope

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

the envelope required
---
markdownto: [email protected]
title: Household        # optional, reserved
id: household-list      # optional, reserved
maintainer: akshay      # unknown key — preserved verbatim
---
markdownto
Required. The exact string form [email protected]. A sequence or mapping here is MDTO004 — that is how “one spec per file” is enforced mechanically.
title, id
The reserved keys from the shared conventions. Optional, both strings.
anything else
[email protected] defines no additional keys. Everything else is an unknown key: parsed, preserved, written back verbatim, never validated. This is what lets a Markdown To envelope share one frontmatter block with another system’s.
2.2

Document structure

a leading #
Document title. Informative; it does not open a section. Permitted once, before any other content.
##
A section. It runs to the next ## or to end of file. Names are compared case-insensitively after trimming; the source casing is preserved.
- [ ] / - [x]
An item, on a bullet list (-, * or +). Order within a section is meaningful — it is the author’s priority order, and nothing sorts it.
nesting
A subtask of the item above it. Unbounded depth. A parent’s checked state is never derived from its children in 0.1.
everything else
Prose. Paragraphs, blockquotes, tables, code fences, images, thematic breaks: preserved, rendered, never interpreted. A paragraph between two lists does not split a section.
### and deeper
MDTO101. Two grouping levels already exist — headings for sections, nesting for subtasks. A third would be ambiguity, not power.
Why sections are optional

The smallest useful todo file is a frontmatter block and three lines. Requiring a heading to write down three errands would tax the most common case, so a file with no headings at all is valid: its items live in a single implicit section whose name is null. Items above the first ## in a sectioned document land there too — the “inbox at the top” pattern, deliberately legal.

The implicit section exists so the shape of the intermediate representation does not change between a bare list and a sectioned one. Consumers write one code path.

2.3

Item metadata

All of it inherited from the shared conventions, summarised here so this sheet stands alone. Nothing inside a code span or a code fence is scanned — which is what makes it possible to write documentation about Markdown To in a Markdown To file.

one item, every construct
- [ ] Publish the fixtures [priority:: high] [due:: 2026-08-20] #specs ^t7
[priority:: …]
low | medium | high. Case-insensitive, lowercased in the IR.
[due:: …]
YYYY-MM-DD, a real calendar date. No time component in 0.1.
[owner:: …]
Free text. A leading @ is conventional and preserved verbatim.
[anything:: …]
An unknown field. Not an error, not a warning: carried as a string and written back verbatim. Specs extend by promoting one to a known field, which is an additive minor version.
#tag, #tag/sub
A label. Stays in the item’s rendered text as well as in tags — tags read as part of the sentence, fields do not. #1234 is all digits and is therefore a literal issue reference.
^id
A block identifier, at end of line, pinned by tooling only. An untouched item carries none. In the file above, exactly one item has one.
Why every list item must be a task item

A bullet without a checkbox is genuinely ambiguous: an item somebody forgot to mark, or a note? Guessing either way loses data — a “note” that was really an item vanishes from the list; an “item” that was really a note acquires a checkbox it never had. So the spec refuses to guess and says so with MDTO030. The repair is to add [ ] if it is a task, or to turn the line into a paragraph if it is not.

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

§ 03

Verb reference

[email protected] owns the vocabulary add, done, undone, rm, move. It overlaps [email protected] because the two share an item model, not because a platform contract requires it. Verbs exist so that an agent never has to rewrite a file freehand: each one validates first, edits the smallest possible span, leaves every other byte alone, and refuses rather than guesses.

verbsignaturesource edit

add

mdto todo add "<text>" [--section <name>] [--priority p] [--due d] [--owner o] [--top | --before <ref> | --after <ref>]

Inserts one line, after the last top-level item of the target section. The bullet marker and indentation are copied from the nearest sibling so the insertion matches the file’s existing style. Fields are appended in the canonical order priority, due, owner; tags stay wherever the author wrote them. Nothing above or below the new line changes.

done

mdto todo done <ref>…

A three-character replacement inside the checkbox. The item does not move, no section changes, no completed:: field is stamped — 0.1 has no such field — and subtasks are untouched. Running it on an already-checked item is a successful no-op with an empty diff.

undone

mdto todo undone <ref>…

The inverse, and nothing else. Un-checking is a routine operation on a checklist, which is why todo spends a verb on it and [email protected] — where it is a rare correction — does not.

rm

mdto todo rm <ref>…

Removes the item’s line and every line of its nested subtasks, then collapses at most one blank line left doubled by the removal. It never renumbers, reflows or re-indents siblings. Removing a pinned item retires its identifier permanently; it is never reissued.

move

mdto todo move <ref> [--section <name>] [--top | --bottom | --before <ref> | --after <ref>]

Deletes those lines and reinserts them verbatim elsewhere, adjusting only leading indentation if the nesting depth changed. Text, fields, tags, identifier and checked state all survive unchanged: moving and completing are independent facts, and move never checks a box for you.

3.1

Addressing an item, and the common flags

<ref>
A pinned identifier with or without the caret (t4, ^t4); otherwise a case-insensitive substring of the item’s normalized text ("plumber"). If a text ref matches zero or more than one item, the verb fails with a non-zero exit code and changes nothing. Ambiguity is never resolved by picking the first match.
--section
Narrows the search for a text ref.
--file
Defaults to the single *.todo.md in the working directory.
--dry-run
Prints the unified diff and writes nothing.
--json
Emits the resulting item as IR, for a caller that wants a value rather than a file.
--pin
Pins an identifier on the touched item and returns it. No verb pins one otherwise. The standing exception is a live editing surface such as the web board, which must track items across re-renders and therefore pins on first interaction.

What no verb ever does

Reformats a line it was not asked to change·normalizes bullet markers, indentation or blank lines·reorders anything it was not asked to reorder·drops an unknown field or an unrecognized value·sorts a list·pins an identifier without --pin·rewrites the markdownto version·edits a file that fails validation with an error.

§ 04

Diagnostics carry the repair

A diagnostic that only says what is wrong sends a repairing agent off in a plausible wrong direction. So the message names the offending construct precisely and says what to do about it — and validation is a single pass that reports everything it can find, because an agent fixing a file should get the whole list at once.

household.todo.md 4 errors
---
markdownto: [email protected]
title: Household
---

## This week

- [ ] Renew the car registration
- Call the plumber
- [/] Book a dentist appointment
- [ ] [priority:: high]

1. [ ] Pay the council tax

mdto validate · verbatim 4 diagnostics

  • MDTO030 error line 9

    - Call the plumber

    List item is not a task item: expected - [ ] or - [x]. Prose that is not a task belongs in a paragraph.

  • MDTO031 error line 10

    - [/] Book a dentist appointment

    Unrecognized checkbox state /: [email protected] defines only [ ], [x], and [X].

  • MDTO033 error line 11

    - [ ] [priority:: high]

    Item has no text: it consists only of metadata.

  • MDTO032 error line 13

    1. [ ] Pay the council tax

    Task checkbox on an ordered list item: [email protected] items must be bullet list items (-, *, or +).

Why - [/] is MDTO031 and not MDTO030

A bullet whose text begins with [, one character, ] and a space is treated as an intended checkbox. So - [/] Draft the spec reports “unrecognized state /” rather than “not a task item” — because the second message would send a repairing agent to delete the line instead of mapping the state to [ ] or [x] on purpose.

Extended states such as [/] and [-] are a real convention in the wild and a known extension point, deliberately excluded from 0.1: a state machine is a bigger commitment than a checkbox. Every tool must preserve an unrecognized marker character verbatim on round-trip rather than coercing it to open or checked.

4.1

The codes this spec owns

MDTO100MDTO199 belongs to [email protected]. Codes are never reused, renumbered or repurposed; a retired code is retired permanently. Everything else a todo file can get wrong is a shared code — the envelope, item identity, inline fields and item structure all live in MDTO001MDTO099.

MDTO101 error Heading level is not valid in a todo document: only one leading # title and ## sections.
MDTO102 error Section heading has no name.
MDTO103 error Duplicate section name.
MDTO104 error Inline field or block identifier on a section heading; sections carry no metadata in 0.1.
Reported, never deleted

[due:: next Tuesday] is wrong, but it is what the author wrote and it is more informative than nothing. A value that fails validation stays in the source and in the IR’s preserved field map, so a repair never loses data. Validation is advice; the file belongs to the author.

§ 05

For agents

Agents are the primary users of the CLI, and the value over freehand editing is determinism: guaranteed-valid output, minimal diffs, no whole-file context required. This sheet is the human edition; the agent edition is the same document, fetched.

mdto — working name in progress
$ mdto spec todo
[email protected] — agent-facing specification
  1. Purpose   what the format is for
  2. Grammar   heading = section, item = task
  3. Rationale why it is shaped that way
  4. Examples  valid + invalid + diagnostics
  5. Verbs     add · done · undone · rm · move

$ mdto validate launch.todo.md
ok conforms to [email protected] · 7 items, 3 sections

$ mdto todo add "Draft the llms.txt endpoint" \
    --section Engineering --priority high --pin
ok 1 line inserted · pinned ^t8

$ mdto todo done t8 --dry-run
--- launch.todo.md
+++ launch.todo.md
-- [ ] Draft the llms.txt endpoint [priority:: high] ^t8
+- [x] Draft the llms.txt endpoint [priority:: high] ^t8
note --dry-run: nothing written
  • Every spec documents itself

    Purpose, grammar, rationale, examples with their exact diagnostics, and the verb reference — versioned alongside the spec and part of its definition of done. mdto spec todo prints it. Agents repair and extend files far better when they know why the structure is what it is.

  • 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/todo.md

    fetch markdownto.ai/llms.txt

  • Fixtures are the contract

    An independent implementation conforms if and only if it agrees with specs/todo/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.

    universal validaterenderspec