▶ YouTube Summaries

← All summaries

Creating tasks from TODO comments in Tsoding's tatr, in C

2026-10-09 Fri ⏱ 1 hr 54 min tsodingdaily

Tsoding adds a todo subcommand to tatr, his in-repository task tracker written in C. The command scans the given source files line by line and finds TODO markers. For each one it asks yes/no whether to turn it into a task. It then creates the task folder and rewrites the TODO in the file to reference the new task ID. This replaces an Emacs Lisp function he used for the same job. Along the way he writes a naive substring search, looks at how musl implements strstr, and learns the name of the rolling hash algorithm he was taught at university. He also implements a small yes/no prompt helper and handles task ID collisions by sleeping for one second. The stream ends with a long chat Q&A on "soy devs", Jai, big tech employers and open source.

How tatr works

  • Each task is a folder with a unique ID inside a tasks directory. The folder holds a task.md with a level-1 header and properties; anything after that is free-form.
  • tatr ls lists open tasks sorted by priority. Its output is in compiler-diagnostic style, so Emacs compilation mode turns it into clickable links. Tags allow filtering (e.g. bugs). A special scope tag marks what to focus on now, out of about 32 open issues.
  • tatr ref greps the whole repo for the ID of the current task, and tatr find goes from an ID back to the task. Together they give jump-both-ways navigation between a task and the code locations that mention it.
  • The only feature still living in Emacs Lisp was creating a task from a TODO at point, which replaces the TODO with the new task's ID. The goal is to move it into the tool itself.
  • The task for it proposed a todo subcommand that walks through every TODO and asks whether to turn it into a task, similar to interactive staging in git.

Git interactive patch mode

  • Tsoding explains hunks: the localized pieces of a diff. git add -p walks through them and asks whether to stage each one, so some changes end up staged and others not.
  • He recommends a graphical git client for everyday use, but says a professional should know the vanilla commands for broken environments where nothing else is available.
  • This was a digression to motivate the interactive design of the new command.

Adding the todo subcommand

  • Commands live in a table (name, signature, description, run function), so adding one means a new entry placed next to new.
  • To keep the first version small, it takes one or more files as arguments and does not walk the whole repo. A whole-repo walk would need ignore handling, and he does not want to depend on git, in case someone uses another version control system.
  • With no files it prints an error and the command usage. The flag context is made nullable, so a command with no flags can print usage without one.
  • The file is read into a string builder and converted to a string view. It is chopped by newline, and each line is printed framed to confirm the chopping works.

Finding a substring

  • The string view library has no find function, so he writes one that returns a signed index (-1 for not found), with haystack and needle naming.
  • First attempt: loop over offsets and check starts_with on the remaining suffix. A first version matched without the colon. The simpler fix was a loop that chops one character off the haystack at a time while the needle still fits, checking starts-with at each step.
  • Chat's "TODO:" false positives turned out to come from the output of the TODO macro itself in his own source, not from a bug in the search.
  • He opens musl's strstr. It has special cases for 2, 3 and 4-byte needles and a two-way algorithm for longer ones.
  • He recalls a hash-based search from university: sum the bytes of the window and of the needle, slide the window by subtracting the outgoing character and adding the incoming one, and compare strings only when the hashes match. It turned out to be called a rolling hash. He had not known the name for over ten years and added it to the description.

Reconstructing the TODO line

  • Each matching line is split into a prefix, the tag (TODO:) and a suffix. The suffix, trimmed, becomes the task title.
  • A mock of the rewritten line is built as prefix, a task tag with a fresh ID, then the suffix. Every non-matching line is appended to an output builder unchanged, with the newline restored.
  • The file is renamed to input and output builders, and the output is written back at the end.
  • He keeps the tool per-file on purpose. Creating tasks across a whole codebase felt out of control, and he jokes that it could be sold as a misaligned agent.

Creating the task

  • The existing new-task code is factored into a function taking the tasks directory and the title. The directory lookup moves outside it, and the function returns the task ID.
  • Fixed priority and no tags are used at first. The user can edit these in the task afterwards.
  • IDs are time-based, so fast creation collides. His fix is to sleep one second and retry once, then fail on the second collision. Not busy-looping and not using line numbers as a suffix, because the ID suffix is already a user-chosen collision tool (e.g. a nickname in a two-person team).
  • A cross-platform sleep is added as a TODO for the nob build library.
  • The function reports the task in the same format as ls. A hard-coded ID suffix of NULL is used for now, with a TODO to make it configurable.
  • He found a bug while testing: the ID was generated once outside the retry, so the second attempt collided again. Generating it inside the loop fixed it.
  • Because IDs are time-based, sorting tatr ls by ID shows the newest tasks first.
  • He compiles with gcc, clang (which flagged an unused variable) and tcc (very fast). The clang build with the memory sanitizer is his default.

Interactiveness

  • A yorn helper prints a prompt, flushes stdout, reads a line with fgets and trims it. It upper-cases the response and accepts only exact Y as yes. Anything else is no, and EOF is also a no.
  • Empty input re-prompts. A do { ... continue; } while (false) version did not loop as hoped (continue jumps to the false condition), so it became a plain infinite loop that returns on a real answer.
  • The command prints "file:line: found TODO: title" before asking. If the answer is no, the line is copied through unchanged.
  • Test: three TODOs answered yes, no, yes created exactly two tasks and changed only those lines.

Multiple files

  • The code is wrapped in a loop over the arguments, with input and output builders cleared at the start of each file.
  • Tested on two modified files with yes/no answers. Only the accepted TODOs were converted.
  • He uses git commit --amend to stack the work into one unpushed commit and notes that unpushed history can be rewritten freely.
  • Remaining: a configurable ID suffix, which he calls trivial enough for an LLM. He does not want to read dates from git, to stay VCS-agnostic. A config file for tatr is planned, starting with making the ref grep command configurable.

Chat Q&A and asides

  • GitHub and Google pages often load without CSS on first visit. He finds it baffling for companies that size.
  • Tatr will not be renamed, since chat disliked the name.
  • He might port tatr from C to Jai once the language is public, possibly as an "LLM slop rewrite" stream using locally hosted small models, since the job is mechanical translation. He says Jai is far from perfect but none of his complaints are showstoppers. He heard the Jai-based game is out and plans to buy and play it after the stream.
  • "Soy dev": he reads an Urban Dictionary definition and, matching himself against it, concludes he is perhaps half of one.
  • He would not want to work at Amazon or other FAANG companies, citing horror stories from engineers he knows. His dream job is what he does now, with more freedom to stream niche algorithm work.
  • He worries that open source is used in LLM training sets and monetized by others, and wonders whether it is still worth it.