← Changelog

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 regenerates TASKS.md when a commit touches context-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 builtThen learnedSo now
Output in TODO.md’s formatTODO.md was inspiration, and the target is GitHubPlain Markdown with source links
Assignees read from the start or end of a lineThe trailing form existed only for TODO.md, and it made Add @page read as an assigneeAssignees must directly follow the checkbox
Tasks from every tracked fileChangelogs are history, so old unticked items aren’t open workchangelog/ 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#L5 comes 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-tasks from 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