Tasks You Can Assign in Markdown, Rolled Up for GitHub
A plain checkbox line now carries an owner, tags and a due date, and a commit hook keeps one task list current for GitHub.
Tasks You Can Assign in Markdown, Rolled Up for GitHub
Why Care?
Our plans and specs keep turning into to-do lists, and those lists need owners. Markdown has checkboxes, but it has no idea who a task belongs to or when it’s due. We also write in Obsidian and collaborate on GitHub, and neither tool’s task syntax works in the other.
Now one plain line works in both, and a script gathers every open task in a repo into a single file you can read on GitHub, with each task linking to the line it came from.
What’s New?
- [ ] @mpstaton @tanujsk Review [[path/to/Page-Name.md]] #Docs ~3d 2026-11-15
lfm-task-metadata, an opt-in plugin that reads assignees, tags, estimates and due dates from that line and leaves the text as written.lfm-tasks, a script that aggregates a repo’s tracked Markdown files into one Markdown file, grouped by assignee.lfm-tasks --install-hook, a local git hook that regeneratesTASKS.mdwhen a commit touchescontext-v/, so the list goes up with the same push.- Page references that work on GitHub.
[[path/to/Page-Name.md]]stays in your source for Obsidian and becomes a real link in the output.
How We Got Here
It started as a question: is there a convention for assigning tasks in Markdown? There isn’t. TODO.md is the closest thing to a written spec, and it inspired the ~3d estimate and grouping by heading.
We worried that #type would turn into a heading, so we ran it through the parser. It can’t: a heading needs a space after the #. That took a whole parser requirement off the list.
We also changed direction along the way:
| We first built | Then learned | So now |
|---|---|---|
| Output in TODO.md’s format | TODO.md was inspiration, and the target is GitHub | Plain Markdown with source links |
| Assignees read from the start or end of a line | The trailing form existed only for TODO.md, and it made Add @page read as an assignee | Assignees must directly follow the checkbox |
| Tasks from every tracked file | Changelogs are history, so old unticked items aren’t open work | changelog/ directories are skipped by default |
Automated generation on GitHub was on the table too, and we declined it. The hook runs locally and adds TASKS.md to the commit it belongs to.
The Bug the Tests Caught
Real commits through the hook exposed a nasty one. git commit <path> builds its commit from a temporary index and locks the real one, so the hook’s git add TASKS.md only reached the temporary copy. The commit looked fine, but afterwards git showed TASKS.md as staged for deletion, and the next commit would have quietly removed it.
A pre-commit hook can’t fix that, so the installer now adds a one-line post-commit hook that syncs the index. The same pass replaced the Homebrew path the hook had pinned for node, which the next brew upgrade node would have broken.
Is It Too Noisy?
We ran the parser over 996 tracked monorepo documents containing 11,997 tasks. None produced an assignee, tag, estimate or due date, so turning the plugin on stays quiet on existing content. That also means nothing real uses the syntax yet. So far it has only been exercised by tests.
What’s Next?
- Check the source links on GitHub after this push.
?plain=1#L5comes from what we know of GitHub, and we haven’t seen it rendered yet. - Install the hook in other repos once this has proven itself here.
- Find a way to run
lfm-tasksfrom repos that install LFM through JSR, which doesn’t expose the command today.
References
- [[Maintain-Project-Task-Management-w-LFM]]: the spec, with every decision recorded
changelog/releases/0.6.1.md: the release notes