features • Installation • Usage
⭐️ Please press the star! It greatly helps development! ⭐️
CLI that scans source files for comment tags (`TODO`, `FIXME`, `HACK`) and generates a `TODO.md`.
npm i -g comment-todo
Usage: comment-todo [options]
Options:
-r, --root <path> Project root (default: .)
-o, --out <path> Output file (default: TODO.md)
-e, --ext <exts> Comma-separated file extensions to include (default: js,ts,jsx,tsx,py,sh)
-p, --pattern <glob> Additional include glob (can be used multiple times)
-x, --exclude <glob> Exclude glob (default: node_modules/**,.git/**)
-f, --format <format> Output format: markdown|json (default: markdown)
--dry-run Print results to stdout instead of writing file
--verbose Verbose logging
-h, --help display help for command
npx comment-todo --root . --out TODO.md
# or
npx comment-todo --out - --format json
- AST-backed scanning for JavaScript/TypeScript (reduces false positives).
- PHP comment extraction using php-parser.
- Fallback scanning for Python, Go, shell scripts and others.
- Metadata parsing for assignee/due etc. (e.g. TODO(@alice due:2025-09-01): message).
Below are representative comment examples (across languages) and the expected TODO.md
output produced by the CLI.
Notes: AST-backed parsing is used for JavaScript/TypeScript which avoids matching
TODO
inside string literals. For languages where we use line-based fallback (Python, Go, Shell), the extractor uses heuristics (comment prefixes) to reduce false positives but may still have edge-cases. Metadata parsing supports@assignee
,assignee:key
,due:YYYY-MM-DD
inside parentheses or comma-separated pairs. Example:TODO(@alice due:2025-09-01): message
.
src/example1.js
// TODO: implement user login
const s = "this is not a // TODO: fake"; // not a todo (string)
/* FIXME: remove this deprecated method */
/*
* HACK: temporary workaround for issue #42
* Details: this will be removed later
*/
Expected TODO.md
# TODOs (generated by comment-todo)
## src/example1.js
- [TODO] (line 1) implement user login
- [FIXME] (line 3) remove this deprecated method
- [HACK] (line 5) temporary workaround for issue #42 — _no meta_
---
Generated: <timestamp>
src/example2.js
// TODO(@alice, due:2025-09-01): add input validation
// FIXME(assignee:bob): fix edge-case on windows
Expected TODO.md
# TODOs (generated by comment-todo)
## src/example2.js
- [TODO] (line 1) add input validation — _assignee:alice, due:2025-09-01_
- [FIXME] (line 2) fix edge-case on windows — _assignee:bob_
---
Generated: <timestamp>
src/example3.js
/*
* Some header
*
* TODO: refactor this module to separate concerns
* More details follow...
*/
Expected TODO.md
# TODOs (generated by comment-todo)
## src/example3.js
- [TODO] (line 4) refactor this module to separate concerns
---
Generated: <timestamp>
src/example4.js
const msg = `// TODO: do not detect me`;
const txt = "FIXME: not a comment";
// TODO: real comment here
Expected TODO.md
# TODOs (generated by comment-todo)
## src/example4.js
- [TODO] (line 3) real comment here
---
Generated: <timestamp>
scripts/example5.py
# TODO: add retry logic
def foo():
x = "TODO: not a comment inside string"
# FIXME: handle zero division
Expected TODO.md
# TODOs (generated by comment-todo)
## scripts/example5.py
- [TODO] (line 1) add retry logic
- [FIXME] (line 4) handle zero division
---
Generated: <timestamp>
tools/deploy.sh
# HACK: use a fixed path for now
echo "deploying..."
# TODO(@devops): make path configurable via ENV
# TODOs (generated by comment-todo)
## tools/deploy.sh
- [HACK] (line 1) use a fixed path for now
- [TODO] (line 3) make path configurable via ENV — _assignee:devops_
---
Generated: <timestamp>
lib/example7.php
<?php
// TODO: support namespaced classes
/* FIXME: temporary API compatibility shim */
/**
* Some doc
* TODO: document examples
* @todo should we also pick up @todo? (note)
*/
Expected TODO.md
# TODOs (generated by comment-todo)
## lib/example7.php
- [TODO] (line 2) support namespaced classes
- [FIXME] (line 3) temporary API compatibility shim
- [TODO] (line 6) document examples
---
Generated: <timestamp>
Note: many PHP projects use
@todo
in docblocks. The current MVP looks for literal TODO/FIXME/HACK tokens — it will still match TODO inside a docblock line prefixed with *, but it will not match @todo (leading @) unless we add explicit support. We can add @todo recognition in a follow-up if you want.
cmd/example8.go
// TODO: improve error messages
fmt.Println("HACK: not a comment in string")
/* FIXME: consider concurrency issues */
Expected TODO.md
# TODOs (generated by comment-todo)
## cmd/example8.go
- [TODO] (line 1) improve error messages
- [FIXME] (line 3) consider concurrency issues
---
Generated: <timestamp>
src/edgecase.js
const pattern = /TODO:.*\/g; // regex containing TODO
// TODO: real one
Expected TODO.md
# TODOs (generated by comment-todo)
## src/edgecase.js
- [TODO] (line 3) real one
---
Generated: <timestamp>
The AST-backed JS/TS extraction avoids matching TODO inside regex or string literals. The fallback scanner for other languages tries to ensure the token appears after a comment prefix.
misc/empty.js
// TODO
// FIXME:
Expected TODO.md
# TODOs (generated by comment-todo)
## misc/empty.js
- [TODO] (line 1) _no message_
- [FIXME] (line 2) _no message_
---
Generated: <timestamp>
Contributions welcome! Please open issues for feature requests or bugs.
MIT — see LICENSE
for details.
Built with ❤️ by Ali Nazari, for developers.