DEV Community

Cover image for Every TODO in your codebase is lying about its age. So I built a CLI that blames them.
benjamin
benjamin

Posted on

Every TODO in your codebase is lying about its age. So I built a CLI that blames them.

Open any codebase older than a year and grep for TODO. You'll get dozens of
hits. Now answer me one question about any of them:

How long has that been there?

You can't. The comment says // TODO: validate the amount before charging and
gives you nothing — no date, no name, no sense of whether it was written last
Tuesday or by someone who left the company in 2021. So it just sits there,
quietly graduating from "I'll get to it" into permanent, load-bearing tech debt
that everyone is now too scared to touch.

The information is right there in git. git blame knows exactly who wrote that
TODO and when — but nobody runs it on their TODOs, because it's one line at a time
and the output is a wall of hashes.

So I built todoage — a zero-dependency CLI that does it for the whole tree.

npx todoage
Enter fullscreen mode Exit fullscreen mode
AGE   TAG    LOCATION                AUTHOR          TEXT
418d  FIXME  src/auth/session.js:71  Alice Dev       refresh tokens before they expire
402d  TODO   src/billing/charge.js:12  Alice Dev     validate the amount before charging
311d  HACK   src/api/proxy.ts:88     Bob Maintainer  works around the upstream 1.4 bug
9d    TODO   src/ui/cart.tsx:140     Carol           wire up the empty-cart state

summary:  4 markers · 3 older than 90d
Enter fullscreen mode Exit fullscreen mode

It scans for TODO / FIXME / HACK / XXX / BUG, runs git blame on every
hit, and sorts oldest-first. Rows older than your threshold come out red
so that 402-day-old "validate the amount" in the billing code is suddenly
impossible to ignore, which is the entire point.

Why not tickgit or todo-tree?

This idea isn't new, which is why it bugged me that nothing shipped it.

tickgit (324★) promised blame +
age tracking for TODOs… and went quiet in 2020 having never delivered that part.
VS Code's todo-tree is great, but it solves visibility inside the editor
there's no CLI you can point at a repo in CI for a JSON report or a build gate.

So todoage is deliberately the missing piece: age + author, on the command
line, CI-friendly.

It's built to gate a build

The flag I actually use day-to-day:

# fail CI if any TODO has been rotting for more than a quarter
npx todoage --max-age 90d --fail-on-stale
Enter fullscreen mode Exit fullscreen mode

Exit 0 if you're clean, 1 if anything's stale. Drop that in a workflow and
your debt now has a deadline instead of a graveyard. --max-age takes plain days
or 12w / 6m / 1y.

A few more you'll reach for:

# only the FIXMEs Alice left behind
todoage --tags FIXME --author alice

# feed a dashboard or a Slack bot
todoage --json | jq '.items[] | select(.stale)'
Enter fullscreen mode Exit fullscreen mode

It doesn't cry wolf

A naive grep -i todo matches todomvc, fixme_helper, and "todo" in prose.
todoage only fires on markers in an actual comment context (//, #, /* */,
<!-- -->, --, ;, leading *) and only on the case-sensitive uppercase
tag — so your identifiers stay quiet and the report stays trustworthy.

Not in a git repo? It still lists every marker; age and author just show ?.

Install

It ships on both registries — half the world's code is Node, half is Python:

npx todoage            # Node — zero deps
pipx run todoage       # Python — pure stdlib
Enter fullscreen mode Exit fullscreen mode

Both ports are tested against the same input→output vectors, so they produce
byte-for-byte identical output. Pick whichever runtime is already on the box.

A few design notes

  • One pure core, two runtimes. scanLine, ageDays, and isStale are pure functions — no I/O, no clock, no git. A shared vectors table drives both the Node and Python suites, which is why the two builds can be proven identical.
  • Age math never touches a Date/datetime. It's integer milliseconds → whole days. Deterministic, timezone-free, identical across languages.
  • Read-only and stateless. It blames, prints, and exits. No cache, no config file, nothing written to your tree. npx/pipx it on demand and forget it.

Try it / break it

It's MIT and tiny. I'd love to know the oldest TODO it surfaces in your repo —
paste me the age. And honestly: what's your current excuse for that one comment
you've been scrolling past for two years?

Top comments (0)