todoage
How old is your tech debt? todoage scans your codebase for
TODO / FIXME / HACK / XXX / BUG comments, then uses git blame to show
how old each one is and who wrote it — and flags anything that's been rotting
longer than your threshold.
Zero dependencies (pure standard library). Zero config. Read-only — it never writes to your repo.
pipx run todoage
AGE TAG LOCATION AUTHOR TEXT
418d FIXME app/auth/session.py:71 Alice Dev refresh tokens before they expire
402d TODO app/billing/charge.py:12 Alice Dev validate the amount before charging
311d HACK app/api/proxy.py:88 Bob Maintainer works around the upstream 1.4 bug
9d TODO app/ui/cart.py:140 Carol wire up the empty-cart state
summary: 4 markers · 3 older than 90d
The red rows are your stalest debt — the stuff that quietly became permanent.
Why it exists
tickgit (324★) promised exactly this — blame + age tracking for your TODOs — and then went quiet in 2020 having never shipped it. VS Code's todo-tree solves IDE visibility, but there's no small CLI that does the age + author part for a CI report or a Markdown summary.
So that's all todoage is: the missing CLI. It answers one question — which
of these TODOs has been here so long it's basically a lie? — and answers it in
a way you can drop into a CI gate.
How it works
It walks your tree (skipping .git, node_modules, .venv, dist, build
artifacts and obvious binaries), matches comment markers in real comment
contexts (#, //, /* */, <!-- -->, --, ;, leading *), and asks
git blame for the author and author-date of each matching line. Age is whole
days from the commit that introduced the line.
Markers are case-sensitive UPPER, so todomvc and fixme_helper don't
trigger false positives — only an actual # TODO does.
Not in a git repo? It still lists every marker; age and author just show as ?.
Install
pipx run todoage # no install, run on demand
pip install todoage # or install the `todoage` command
There's an identical Node build too: npx todoage / npm i -g todoage
(see todoage). Both ports are tested against
the same vectors, so they produce byte-for-byte identical output.
Usage
todoage [path] [options]
| Option | Description |
|---|---|
--max-age <days|90d> |
Staleness threshold (default 90). Suffixes: d w m y (e.g. 12w, 6m, 1y). |
--tags <list> |
Comma-separated markers to scan for (default TODO,FIXME,HACK,XXX,BUG). |
--author <substr> |
Only items whose blame author contains this substring. |
--json |
Emit compact JSON instead of a table. |
--fail-on-stale |
Exit 1 if any item is stale — a one-line CI gate. |
--no-color |
Disable ANSI color. Also respects NO_COLOR. |
-h, --help |
Show help. |
-v, --version |
Print version. |
Exit codes: 0 nothing stale · 1 stale found (with --fail-on-stale) ·
2 usage error.
Examples
# fail CI if any TODO is older than a quarter
pipx run todoage --max-age 90d --fail-on-stale
# just the FIXMEs Alice left behind
todoage --tags FIXME --author alice
# feed a dashboard / Markdown summary
todoage --json | jq '.items[] | select(.stale)'
# scan one subtree, tighter threshold
todoage app/ --max-age 30d
Design notes
- One pure core, two runtimes.
scan_line,age_days, andis_staleare pure functions with no I/O, clock, or git — driven by a shared input→output vectors table that the Python and Node suites both run. That's what proves the two ports agree. - Age math never touches a
datetime. It's integer milliseconds → whole days, so the result is deterministic and identical across languages. - Read-only. It blames, it prints, it exits. No state, no cache, no config file, nothing written to your tree.
License
MIT
Metadata
Release files for todoage 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| todoage-0.1.0.tar.gz | 13.6 kB | Without provenance | Details for todoage-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| todoage-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for todoage-0.1.0-py3-none-any.whl |
Total release size: 24.7 kB
Release files / todoage-0.1.0.tar.gz
| Download URL | todoage-0.1.0.tar.gz |
|---|---|
| Size | 13.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c45f0adea5f402336d3c12779e54c6afbf636f4c11f2f2387d944f50806d2c0d
|
|
BLAKE2b-256 checksum How to use checksums |
e5bbc6afa541f8652a18d0dc04d0b0d8edba60231be5e5c5136d26e80ea81699
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.7
|
Release files / todoage-0.1.0-py3-none-any.whl
| Download URL | todoage-0.1.0-py3-none-any.whl |
|---|---|
| Size | 11.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9c1c140452796e04da43f1477113fe30af5a20a913ab3265e83c071923de51b1
|
|
BLAKE2b-256 checksum How to use checksums |
7b23deab8cbeefb367cc33bff315d1384e8d13c12fe5034220907a27aab8fccf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.7
|
Security for todoage 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.