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 kanban@0.1
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.
todo@0.1
static render
--- markdownto: todo@0.1 title: Markdown To — launch checklist id: mdto-launch maintainer: akshay --- ## Specs - [x] Settle the shared authoring conventions ^t1 - [ ] Publish todo@0.1 fixtures [priority:: high] [due:: 2026-08-20] #specs - [ ] Publish kanban@0.1 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]
Markdown To — launch checklist
1 doneof 7 items across 3 sectionsread-only
Specs
1/3Settle the shared authoring conventions
Publish todo@0.1 fixtures #specs
Publish kanban@0.1 fixtures #specs
Engineering
0/3Build the patch engine
Fuzz the round-trip until a random edit never touches a neighbouring line #infra
Wire the CLI verbs
Someday
0/1Obsidian Kanban importer
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.
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.
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.
--- markdownto: todo@0.1 title: Household # optional, reserved id: household-list # optional, reserved maintainer: akshay # unknown key — preserved verbatim ---
markdownto- Required. The exact string form
name@major.minor. A sequence or mapping here isMDTO004— that is how “one spec per file” is enforced mechanically. title,id- The reserved keys from the shared conventions. Optional, both strings.
- anything else
todo@0.1defines 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.
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 deeperMDTO101. Two grouping levels already exist — headings for sections, nesting for subtasks. A third would be ambiguity, not power.
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.
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.
- [ ] 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.#1234is 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.
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 todo@0.1 in full — the normative text Shared conventions
§ 03
Verb reference
todo@0.1 owns the vocabulary add, done,
undone, rm, move. It overlaps
kanban@0.1 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.
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
kanban@0.1 — 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.
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.mdin 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.
--- markdownto: todo@0.1 title: Household --- ## This week - [ ] Renew the car registration - Call the plumber - [/] Book a dentist appointment - [ ] [priority:: high] 1. [ ] Pay the council tax
-
MDTO030 error line 9
- Call the plumberList 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 appointmentUnrecognized checkbox state
/: todo@0.1 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 taxTask checkbox on an ordered list item: todo@0.1 items must be bullet list items (
-,*, or+).
- [/] 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.
The codes this spec owns
MDTO100–MDTO199 belongs to todo@0.1. 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
MDTO001–MDTO099.
# title and ## sections.
[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 spec todo todo@0.1 — 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 todo@0.1 · 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 todoprints 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.txtso an agent never has to scrape a human documentation site to author a conforming file.fetch
markdownto.ai/specs/todo.mdfetch
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.jsonlisting the exact codes, severities and lines it must produce.universal
validaterenderspec