The CLI checklist field

Research notes, 2026-10-15. Purpose: decide what a from-scratch plain-text checklist tool for this machine should be, by first knowing what already exists. Companion document: checklist-impl-plan.html.

1. The question

The house tracks work in a hand-maintained markdown file (rebuild-v2-checklist.md). We want a CLI to replace it with the property of many lists, one file each, chosen by passing a file path as the first argument: ~/todo.checklist for the system-wide list, ~/projects/rose-web/bugs.checklist for a project's bugs, and so on. Required operations: add, list (all or incomplete), mark done, delete.

This document surveys what exists, extracts the design lessons that matter, and records why we build our own small one instead of adopting a tool.

2. The field in four families

Command-line task tools cluster by storage model. The storage model decides almost everything else — portability, crash behavior, how a second surface (GUI/web) can attach, and what "simple" even means.

FamilyStorageExamplesCharacter
Format spec + thin clientsOne plain-text file per user, format is the product todo.txt (spec), todos (ruby), todo.sh (steveyegge), gtd.sh The format is canonical; clients are interchangeable; the community is large and the spec is stable.
Database-backed CLIsLocal database (SQLite / per-task TOML files) Taskwarrior (task), todoman Rich features (dependencies, recurrence, tags) at the cost of a private store and an rc-file ritual; the visible file is not the source of truth.
File-per-list plain textOne human-readable file, path chosen per invocation or per repo tick (markdown .tick/tasks/), Reinschrift (one markdown file), markdown-do, md2do, cli-kneeboard The file is the source of truth; every surface reads/writes the same file; git-versionable by default.
Project schedulersXML/INI project filesTaskJuggler Dates, dependencies, critical path, Gantt output. A different problem (planning, not tracking).

3. The tools, briefly

todo.txt (format) + todos / todo.sh

The todo.txt spec's first rule is the single most repeated invariant in this whole field:

> A single line in your todo.txt text file represents a single task.

Fields are positional and minimal: optional priority (A), optional created/completed dates, then free text with +project / @context tags in it. The philosophy stated in the spec: human-readable without any tool, and sensible to plain-text-editor operations (that's why completed tasks lead with a lowercase x — it sorts to the bottom alphabetically and looks like a filled checkbox).

The spec deliberately caps the field at key:value extensions and says nothing is required. Clients like todos (ruby) and steveyegge's todo.sh add subcommands (add, completed, ls, archive). Weakness for our use: the canonical model is one file per user, fixed location (~/todo.txt). "Many lists, path as argument" is not in the spec; clients get it by abusing the --file flag or symlinks.

Sources: too-much-todotxt/spec · todotxt.org · LWN "Managing tasks with todo.txt and Taskwarrior"

Taskwarrior (task)

The heavyweight. Feature-complete (priority, tags, dependencies, recurrence, annotations, reporting, Lua plugins), fast, beloved. Its storage is a private rc-configured store (~/.task, historically JSON-lines, now versioned), not a file you choose. Three facts matter for us: (1) one database per install — no natural many-lists-by-path story; (2) the store format evolves across versions, so files are not portable plain text; (3) it has a 25+ year feature-attraction problem — it is the tool that becomes your life, with reports and plugins and rc rituals. ToolGPA's comparison grades it a 2.8 vs todo.txt's 2.6 out of 4.0, with taskwarrior winning features and todo.txt winning ease of use. That gap is the whole field in one table.

Sources: taskwarrior.org (man task.1) · toolgpa.com/compare/taskwarrior-vs-todo-txt

todoman

Taskwarrior-compatible CLI, storage is one TOML file per task in a directory. Closer to us (file-based, git-friendly, path-addressable data dirs) but the one-file-per-task layout scatters state — you can't cat a list — and TOML round-tripping through a third-party dependency for a five-verb tool is complexity we don't need.

tick

Python, markdown storage in .tick/tasks/ inside a project. Notably the only serious tool in the field with stable task IDs — a task carries an id so references survive reordering and deletion. Also tracks agents/claims/done-claims, which is agent-harness territory, not personal tracking.

Reinschrift

Closest in spirit: one ordinary markdown file, with a stable ^ID token, +project/@context/due:/rec: tokens, and four surfaces (GNOME app, top-bar extension, CLI with JSON output, self-hosted web app) all reading and writing the same file. It proves the core proposition of our file-based design: a single plain-text file can be the shared system of truth for every surface, including machines (reinschrift-cli … --json). Weakness for us: it's a Linux/GNOME app, and its token grammar is todo.txt's full dialect — more surface area than we need on macOS.

Source: reinschrift.dumke.me

markdown-do / md2do / cli-kneeboard (the markdown-checkbox tools)

A small family of tools that treat TODO.md files as - [ ] checkbox lists: add/toggle/locate, sections, aliases. Useful to confirm two things: (1) the - [ ] / - [x] checkbox is the natural checkbox for humans — every editor and every markdown renderer understands it with zero training; (2) the common failure mode of this family is no stable ID. Toggle-by-text is brittle once two lines share wording; locate-by-text breaks on edits. That's a gap our design fills with tick's stable-id idea.

Sources: i-am-fran/markdown-do · md2do.com · chimbosonic/cli-kneeboard

4. The five design lessons that survive contact with the field

1. One line = one task. The universal invariant (todo.txt rule 1, Reinschrift, tick, kneeboard). It makes the file human-messable, line-addressable, git-diffable, grep-able, and trivially parseable. Non-negotiable in our design.
2. The visible file must be the source of truth. File-per-list tools (todo.txt, Reinschrift, tick) win on portability and trust: if the tool dies, the file doesn't. Database-backed tools win on features and lose the moment a version bump rewrites the store format. We take the file as SoT: one .checklist file per list, no second store.
3. Stable IDs, never text. Deletion and reordering are routine; addressing tasks by their text is how every checkbox tool quietly corrupts. tick and Reinschrift both evolved toward explicit id tokens. We give every item a monotonic id that is printed on the line and never reused.
4. Features multiply the parsing surface. todo.txt's optional everything (priority, two dates, tags anywhere) is famous for producing clients that each implement a slightly different dialect — the spec has an open issue about completed items not sorting to the bottom, caused by optional field positions. The fix is a fixed line grammar: five fields, one order, one canonical spacing. We reject priority, due dates, recurrence, dependencies, and tags entirely. The key:value extension clause is a footgun we do not inherit.
5. Write atomically; warn, don't destroy. Every respectable file-based tool writes via temp-file + atomic rename, and preserves lines it doesn't own. Our rule: a failed write must leave the on-disk file byte-identical; a line the tool can't parse is preserved verbatim, with a warning on stderr — never silently dropped, never "corrected". (The tool under construction was caught doing exactly the latter in testing; see the plan document.)

5. Adoption vs. build

OptionVerdict
TaskwarriorOverkill in two directions: one private DB (no multi-list-by-path), and a 25-year feature gravity. Wrong shape.
todos / todo.shSingle canonical list (~/todo.txt); ruby dependency; no stable ids. Would require constant --file gymnastics.
todomanClosest file-based fit, but one-TOML-per-task scatters state and ships a dependency for five verbs. Python deps on a 9B-LLM house that also runs ComfyUI are a RAM problem.
ReinschriftWrong platform focus (GNOME) and a token grammar we don't need. Its architecture validates our design; its code doesn't run here.
markdown-do / md2do / kneeboardDo the 80% (checkbox lists in files) but are missing the one thing that makes lists safe: stable ids.
Build a small oneThe gap is precisely the combination our need defines: file-per-list and stable ids and fixed grammar. No tool has all three without dragging along the todo.txt dialect. A ~150-line stdlib-only Python tool has the whole feature set we asked for. The owner's standard applies: fewer features = fewer bugs; the build is cheap, the alternatives each cost a dependency or a private store.

Superseded by owner ruling (2026-10-15). After reviewing this survey, the owner switched storage from one-file-per-list to one list per user (the todo.txt family): a single ~/todo.checklist, with zero-to-many tags per item validated against the house vocabulary file that mem already uses for topics (now ~/tags.txt, owned by its own tags tool after the decoupling; mem reads it too) — one shared tag world, one place to extend it (tags new NAME). No lists scattered across projects; no "should this list be in Git?" ambiguity. The survey below stands as written; the v1 conclusion's file-per-list stategy is retired, its build the small tool verdict survives. The plan document (→ ~/checklist-impl-plan.html, regenerated same day) reflects v2.

Conclusion (v1, superseded by the ruling above): build checklist — one stdlib-only executable, file-per-list, five fixed fields, stable monotonic id, atomic writes, 0/1/2 exit-code contract. Spec, reference implementation, tests, and installation plan are in checklist-impl-plan.html.

6. Sources