Rusty TODO.md — A Pre-Commit Hook & CLI for Managing TODOs
Rusty TODO.md helps you find, centralize, and maintain all your TODO comments across your codebase.
It can run as a pre-commit hook or from the CLI, automatically extracting TODO-style comments into a structured TODO.md file.
Supports a wide range of languages and file types, with sectioned formatting, multi-line support, and smart sync.
📌 Recommended usage: Pre-commit via shim repo
When pre-commit installs a hook from a Git repo, it runs pip install . from that repo — which would normally build Rusty TODO.md from source (requiring a Rust toolchain).
The shim repository (rusty-todo-md-pre-commit) solves this by depending on the rusty_todo_md PyPI package, ensuring prebuilt wheels are used.
Add this to your .pre-commit-config.yaml:
repos:
- repo: https://github.com/simone-viozzi/rusty-todo-md-pre-commit
rev: v1.9.1 # Use the latest upstream tag (shim mirrors upstream)
hooks:
- id: rusty-todo-md
args: ["--auto-add", "--markers", "TODO", "FIXME", "HACK", "--"]
language_version: python3.11
argscustomise the markers to scan for and enable--auto-addto stageTODO.mdautomatically.language_versionforces the hook to run with a specific Python interpreter.
Example with exclusions:
repos:
- repo: https://github.com/simone-viozzi/rusty-todo-md-pre-commit
rev: v1.9.1
hooks:
- id: rusty-todo-md
args:
- "--auto-add"
- "--markers"
- "TODO"
- "FIXME"
- "HACK"
- "--exclude-dir"
- "node_modules"
- "--exclude-dir"
- "target"
- "--exclude"
- "**/*.test.js"
- --
language_version: python3.11
Then install the hook:
pre-commit install
✅ No Rust toolchain is required when using the shim and a supported platform.
⚙️ CLI installation
You can also install Rusty TODO.md directly for manual CLI use:
pip install rusty_todo_md
Then run:
rusty-todo-md --help
✨ Key Features
-
Automatic TODO Collection By default, Rusty TODO.md scans the files passed to it (typically staged files from pre-commit) for markers like
TODOandFIXME, and updates yourTODO.mdwith any new entries. -
Sectioned TODO.md Format The
TODO.mdfile is now organized into sections, grouped first by marker (e.g.,# TODO,# FIXME), then by file. Each marker section begins with a header (# <MARKER>), each file with a sub-header (## <file-path>), followed by a list of extracted TODO items. -
Multi-line TODO Support Handles multi-line and indented TODO comments, merging them into a single entry.
-
Sync Mechanism
- Each run rescans the files being committed plus every tracked file
TODO.mdalready lists, and rewritesTODO.mdfrom that scan. - Removes entries when their corresponding TODOs (or files) are no longer present in the source code.
- Each run rescans the files being committed plus every tracked file
-
Language-Aware Parsing Supports precise parsing for Python, Rust, JavaScript, and Go out-of-the-box, with plans for additional languages such as TypeScript, PHP, and Java.
-
Seamless Pre-Commit Integration Easily integrate Rusty TODO.md into your workflow by adding it to your
.pre-commit-config.yaml. -
Auto-stage Updated TODO.md With the
--auto-addflag, the tool can automatically stage theTODO.mdfile after updates.
🧩 CLI usage
Scan staged files
rusty-todo-md
Use multiple markers
rusty-todo-md --markers TODO FIXME HACK
Specify files to process with markers
When using --markers as the last option before specifying files, use -- to separate markers from files:
rusty-todo-md --markers TODO FIXME HACK -- file1.rs file2.rs
Without the -- separator, the files would be incorrectly treated as additional markers.
Automatically stage TODO.md
rusty-todo-md --auto-add path/to/file.rs
Custom TODO.md path
rusty-todo-md --todo-path docs/TODOS.md
Exclude files and directories
Rusty TODO.md supports glob-based exclusion patterns to filter out files and directories from TODO extraction.
Exclude specific files or patterns
# Exclude all log files
rusty-todo-md --exclude "*.log"
# Exclude specific file
rusty-todo-md --exclude "config.rs"
# Exclude files in a specific directory
rusty-todo-md --exclude "src/generated/*"
Exclude directories
# Exclude a directory (and all files within it)
rusty-todo-md --exclude-dir "build"
# Or use --exclude with trailing slash
rusty-todo-md --exclude "build/"
# Exclude multiple directories
rusty-todo-md --exclude-dir "node_modules" --exclude-dir "target"
Recursive exclusion with wildcards
# Exclude all files under src/ recursively
rusty-todo-md --exclude "src/**"
# Exclude all .test.js files anywhere in the tree
rusty-todo-md --exclude "**/*.test.js"
# Exclude all 'vendor' directories at any depth
rusty-todo-md --exclude-dir "**/vendor"
Multiple exclusion patterns
# Combine multiple exclusions
rusty-todo-md \
--exclude "*.log" \
--exclude "*.tmp" \
--exclude-dir "build" \
--exclude-dir "dist" \
--markers TODO FIXME
Glob pattern syntax
*— matches any sequence of characters within a single path component?— matches any single character**— matches zero or more path components (recursive)/suffix — indicates directory-only matching (when used with--exclude)
Note: Patterns are matched relative to the scan root. The
--exclude-dirflag automatically ensures directory-only matching.
🔀 Rebase conflicts in TODO.md
Why it happens
Each TODO.md bullet embeds the line number twice — once in the label and once in the link anchor (* [file:line](file#L<line>)). When two branches both shift the same TODO to different line numbers, git's line-by-line text merge sees the same physical TODO.md line edited differently on each side and produces a conflict — at every replayed commit during a rebase. The user did not meaningfully edit TODO.md; the line numbers just moved.
Fix: one line in .gitattributes
Commit this to your repository. If you use --todo-path, write that path instead of TODO.md, relative to the repository root (.gitattributes patterns are repository-relative); a path containing spaces or *, ?, [, #, ", \, or starting with !, needs gitattributes quoting/escaping:
/TODO.md merge=union
(The leading / anchors the rule to the root TODO.md; without it, it would also apply to any other file named TODO.md in a subdirectory.)
union is built into git, so there is nothing to install or configure per clone. Git then merges TODO.md by keeping the lines from both sides instead of stopping with a conflict, so rebases and merges never stop on TODO.md.
The merged TODO.md is temporarily wrong (old and new line numbers side by side, out of order), and that's expected. Each hook run rebuilds TODO.md from the files being committed plus every file TODO.md already lists, so the next normal commit replaces whatever the merge left, whatever files it touches. Commits replayed during the rebase keep the approximate TODO.md, because git doesn't run hooks during a rebase.
Without the .gitattributes line, resolve a TODO.md conflict by keeping both sides (leaving the conflict markers in is fine too); the next hook run rebuilds it the same way, and prints a hint with the exact merge=union line for your path (or points here when the path needs escaping). Don't resolve it by taking only one side (e.g. git checkout --ours TODO.md): the files listed only on the other side would no longer be listed, so their TODOs would not come back until those files change or you run --regenerate. To rebuild it right away:
rusty-todo-md --regenerate
This re-scans every tracked file and rewrites TODO.md from scratch. Source files that still contain conflict markers are skipped with a warning rather than producing garbled TODOs.
📝 Supported languages & extensions
Rusty TODO.md detects comment syntax based on file extension:
| Language / Type | Extensions |
|---|---|
| Python | py |
| Rust | rs |
| JavaScript / JSX | js, jsx, mjs |
| TypeScript | ts, tsx |
| Java | java |
| C / C++ headers | cpp, hpp, cc, hh |
| C# | cs |
| Swift | swift |
| Kotlin | kt, kts |
| JSON | json |
| Go | go |
| Shell | sh |
| YAML | yml, yaml |
| TOML | toml |
| Dockerfile | dockerfile |
| Markdown | md |
Many extensions share the same parser (e.g., JS-style comment parsing for TS, Java, C-like languages).
🔍 Output format (stable)
Entries in TODO.md use this format:
* [path/to/file.ext:LINE](path/to/file.ext#L{LINE}): MESSAGE
This format is stable and designed for easy linking to code in hosted repos.
The file has a single # TODOs title, one ## section per marker and one ### subsection per file:
# TODOs
## TODO
### src/main.rs
* [src/main.rs:10](src/main.rs#L10): Refactor initialization logic
A TODO.md written by a version before 1.11 (markers as #, files as ##) is rebuilt automatically, from all tracked files, the first time the hook runs.
📦 Requirements & Supported Platforms
- Python ≥ 3.10
- No Rust toolchain needed if using the shim or PyPI wheels
Prebuilt wheels are published for:
| OS / libc | Architectures |
|---|---|
| Linux (manylinux) | x86_64, x86, aarch64, armv7, ppc64le |
| Linux (musllinux) | x86_64, x86, aarch64, armv7 |
| Windows | x64, x86 |
| macOS | x86_64 (macOS 15), aarch64 (macOS 14) |
🛠 Troubleshooting
- If no wheel is available for your platform,
pipwill try to build from source — which requires a Rust toolchain. - If you encounter build errors, please:
- Check the latest releases to confirm wheel availability.
- Open an issue with your OS/arch details.
👩💻 Development
If you want to run Rusty TODO.md directly from the main repo via pre-commit (building from source):
repos:
- repo: https://github.com/simone-viozzi/rusty-todo-md
rev: v1.7.5
hooks:
- id: rusty-todo-md
⚠️ This will compile the Rust source and requires a working Rust toolchain.
🤝 Contributing
Contributions are welcome!
- Open an issue for bug reports or feature requests.
- Submit a pull request with improvements, new parsers, or fixes.
📚 Links
- Shim repo (recommended for pre-commit): rusty-todo-md-pre-commit
- PyPI package: rusty_todo_md
- Releases: GitHub Releases
⚖️ License
Licensed under the MIT License.
❤️ Support
If you find Rusty TODO.md helpful, please consider giving it a ⭐ on GitHub to help others discover the project.
Metadata
Release files for rusty-todo-md 1.11.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 | |
|---|---|---|---|
| rusty_todo_md-1.11.0.tar.gz | 112.3 kB | Details |
Built distributions (wheels)
Total release size: 23.6 MB
Release files / rusty_todo_md-1.11.0.tar.gz
| Download URL | rusty_todo_md-1.11.0.tar.gz |
|---|---|
| Size | 112.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cdece256f2df6c215aa7a54736e86835b340250c2e721830a7a9af60ba863166
|
|
BLAKE2b-256 checksum How to use checksums |
60cd47eed046e7e62cdf07cd46648de9af8d9475db0b24ef54acfc4cf0fd845d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / rusty_todo_md-1.11.0-py3-none-win_amd64.whl
| Download URL | rusty_todo_md-1.11.0-py3-none-win_amd64.whl |
|---|---|
| Size | 1.8 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
99627dd8a1b821c760bdf72cf792059f6f00933c0ac38091cbab6af31d52f0e9
|
|
BLAKE2b-256 checksum How to use checksums |
20568d655a9fe24e35023b4a9f73f5f4859ff70693e59415829c82b892cf7328
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / rusty_todo_md-1.11.0-py3-none-win32.whl
| Download URL | rusty_todo_md-1.11.0-py3-none-win32.whl |
|---|---|
| Size | 1.6 MB |
| Tags | Python 3 Windows x86-32 |
|
SHA-256 checksum How to use checksums |
5492c9aafb77d3b94c9f9468c77b50381c1057d4f8be763c3b4c5ab2913d0829
|
|
BLAKE2b-256 checksum How to use checksums |
f9ae55abf3451bf3e395c1f563986a634b24b1886948e88cd56dee1b8d669b8e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / rusty_todo_md-1.11.0-py3-none-musllinux_1_2_x86_64.whl
| Download URL | rusty_todo_md-1.11.0-py3-none-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 1.9 MB |
| Tags | Linux musl 1.2+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
80471f8e3038f777e4445d0ed95322594ea03230c8a6d99bfab4222c1803894a
|
|
BLAKE2b-256 checksum How to use checksums |
a2d194ed2c38d0d1b9fbe959fb05fa7afb2810f688932c52b9b8ff8aa2674dd3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / rusty_todo_md-1.11.0-py3-none-musllinux_1_2_i686.whl
| Download URL | rusty_todo_md-1.11.0-py3-none-musllinux_1_2_i686.whl |
|---|---|
| Size | 1.9 MB |
| Tags | Linux musl 1.2+ x86-32 Python 3 |
|
SHA-256 checksum How to use checksums |
851227a636735d9138944e77520c1dfcfaf00b8f1c1545cf3709ab6ae0f286ab
|
|
BLAKE2b-256 checksum How to use checksums |
f087c0904402f809e94d1a12be9d49fe9c9fee1f1c8e73ecd7859f13f42389bc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / rusty_todo_md-1.11.0-py3-none-musllinux_1_2_armv7l.whl
| Download URL | rusty_todo_md-1.11.0-py3-none-musllinux_1_2_armv7l.whl |
|---|---|
| Size | 1.8 MB |
| Tags | Linux musl 1.2+ ARMv7l Python 3 |
|
SHA-256 checksum How to use checksums |
33cdabab360444797d238cdb26c15d83f3c8445317dc74e625b9c46f34d1a13b
|
|
BLAKE2b-256 checksum How to use checksums |
da43cf515284353c6f13d88bf836cbf27618f46cd5ded972b9847fa0dd7eecda
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / rusty_todo_md-1.11.0-py3-none-musllinux_1_2_aarch64.whl
| Download URL | rusty_todo_md-1.11.0-py3-none-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 1.8 MB |
| Tags | Linux musl 1.2+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
b2667339ef4e26010197473140f40180e632a215c35548f19c84360e7765047b
|
|
BLAKE2b-256 checksum How to use checksums |
bef23fd08214756d0d4547863ab22c399338caef96ed78632410fdd421ddfd09
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / rusty_todo_md-1.11.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | rusty_todo_md-1.11.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 1.8 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
42aaadfb5f3ffe5edd70540d5bc2b4f6eaf7e62204b8fae7f8c72eb8be9baaeb
|
|
BLAKE2b-256 checksum How to use checksums |
a7af18d28de10acc6d8bb6872ef557522ebd9041d5f300b7c3b0afa2e212a9fb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / rusty_todo_md-1.11.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
| Download URL | rusty_todo_md-1.11.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl |
|---|---|
| Size | 2.2 MB |
| Tags | Linux glibc 2.17+ PowerPC 64-le Python 3 |
|
SHA-256 checksum How to use checksums |
4e7b214a1e3127c1593364d35865496cf04285375cad410e785a23d2b26ead57
|
|
BLAKE2b-256 checksum How to use checksums |
a87ed8c28c2fdda19ad8fc8b6500bc34700b482b3eb71161fa00b680ba5a72d0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / rusty_todo_md-1.11.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl
| Download URL | rusty_todo_md-1.11.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl |
|---|---|
| Size | 1.9 MB |
| Tags | Linux glibc 2.17+ x86-32 Python 3 |
|
SHA-256 checksum How to use checksums |
f5ed030154b4797163e5f089bdd5a28eb855bbe40c87e9ce48bcccfc9ea7006b
|
|
BLAKE2b-256 checksum How to use checksums |
00b2867fce8eea8b19c82d7bbb751e8829dfa7e00b1d3ff8d21b8473c691dafa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / rusty_todo_md-1.11.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
| Download URL | rusty_todo_md-1.11.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl |
|---|---|
| Size | 1.7 MB |
| Tags | Linux glibc 2.17+ ARMv7l Python 3 |
|
SHA-256 checksum How to use checksums |
d0421144a6ae00a07d9884686cdfceb1c1e95bd6f5734297984337676e8fc3f4
|
|
BLAKE2b-256 checksum How to use checksums |
f1f4eb6a2b787f68af00b8131b72eb8339b8c90def95cef6e12411ec88c76923
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / rusty_todo_md-1.11.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | rusty_todo_md-1.11.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 1.8 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
a2cffe9514909cbc82b483ddddbcaa8276893fc3a17d24c4b52078135b7988fa
|
|
BLAKE2b-256 checksum How to use checksums |
586652420884cf927f72ed5f068b19090cd6b996f955c466d7fb9441df1b416a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / rusty_todo_md-1.11.0-py3-none-macosx_11_0_arm64.whl
| Download URL | rusty_todo_md-1.11.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.6 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
078ead08b6f4789db82cf8ac9a3d36c9c5dd64ae976c87fe03e5438d31346268
|
|
BLAKE2b-256 checksum How to use checksums |
c61bc941d877f4e106bfb786a4f2cdbf48d00d9c62a64dae4e9ffb9fee8f0a75
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / rusty_todo_md-1.11.0-py3-none-macosx_10_12_x86_64.whl
| Download URL | rusty_todo_md-1.11.0-py3-none-macosx_10_12_x86_64.whl |
|---|---|
| Size | 1.6 MB |
| Tags | Python 3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
118b314a61d5aeafa23d0d00602a74ac9ce8a527fa9e5784250be2c8f141751e
|
|
BLAKE2b-256 checksum How to use checksums |
1d7430975a9060e26f03beff023d64b3b2a85432d14201268c362aaebd00fedd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|