Skip to content

simone-viozzi/rusty-todo-md

Repository files navigation

Rusty TODO.md — A Pre-Commit Hook & CLI for Managing TODOs

PyPI - Version PyPI - Python Version License: MIT

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
  • args customise the markers to scan for and enable --auto-add to stage TODO.md automatically.
  • language_version forces 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

  1. Automatic TODO Collection By default, Rusty TODO.md scans the files passed to it (typically staged files from pre-commit) for markers like TODO and FIXME, and updates your TODO.md with any new entries.

  2. Sectioned TODO.md Format The TODO.md file 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.

  3. Multi-line TODO Support Handles multi-line and indented TODO comments, merging them into a single entry.

  4. Sync Mechanism

    • Automatically merges new TODO entries with existing ones, using an internal representation.
    • Removes entries when their corresponding TODOs are no longer present in the source code.
  5. 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.

  6. Seamless Pre-Commit Integration Easily integrate Rusty TODO.md into your workflow by adding it to your .pre-commit-config.yaml.

  7. Auto-stage Updated TODO.md With the --auto-add flag, the tool can automatically stage the TODO.md file 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-dir flag 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.

Workflow without the driver

You still get a TODO.md conflict whenever the underlying source files shift. When that happens:

rusty-todo-md --regenerate

This re-scans every tracked file and rewrites TODO.md from scratch, wiping any conflict markers. The tool also prints a one-line advisory whenever it detects <<<<<<< in TODO.md (during a regular pre-commit run), pointing at --regenerate and --install-merge-driver.

Workflow with the driver (one-time per-clone install)

To eliminate this conflict shape entirely:

rusty-todo-md --install-merge-driver

What this changes (printed verbatim on every run):

  1. merge.rusty-todo-md.name and merge.rusty-todo-md.driver in .git/config (clone-local; not committed).
  2. A managed block appended to .gitattributes (committable, so collaborators inherit the rule):
    # BEGIN rusty-todo-md (managed; do not edit between markers)
    TODO.md merge=rusty-todo-md
    # END rusty-todo-md
    

Pass --markers, --exclude, --exclude-dir, or --todo-path to bake non-default settings into the registered driver command — those args propagate into the merge-driver invocation.

What happens during a rebase, with the driver installed:

  1. Git replays a commit that touched source files which had also moved in your branch.
  2. Source files with overlapping edits get conflict markers (normal git behavior — you resolve those by hand).
  3. When git would have emitted a TODO.md conflict, it invokes our merge driver instead.
  4. The driver ignores both sides of the would-be TODO.md merge, re-scans every tracked source file in the current working tree (which already has commit N applied for files that merged cleanly), and writes the canonical TODO.md to the OURS path.
  5. Git accepts the result as the resolution. No TODO.md conflict to fix by hand.

Repo-maintainer opt-in (auto-install)

Maintainers who want every collaborator's clone to register the driver automatically can add the flag to pre-commit args:

- id: rusty-todo-md
  args: ["--auto-add", "--auto-install-merge-driver", "--markers", "TODO", "FIXME", "--"]

On every pre-commit invocation, the tool reconciles the registration: if .git/config and the .gitattributes block already match the args this run was invoked with, it does nothing silently. If they don't (first run on a fresh clone, or someone changed args: in .pre-commit-config.yaml and the registration went stale), it rewrites them and prints a loud summary to stdout describing exactly what changed.

The maintainer's PR adding the flag serves as team consent; the visible-on-change message ensures no collaborator is surprised by config mutation.

Failure modes

  • Source file in a conflicted state when the merge driver runs (e.g., a rebase that left <<<<<<< in some .rs file you haven't resolved yet). The extractor skips that file with a stderr warning rather than emitting garbled TODOs. TODO.md is then approximate until you resolve the source conflicts and re-run rusty-todo-md --regenerate to canonicalize.
  • Pre-commit hooks do not run during git rebase or git rebase --continue. The driver runs because git invokes it directly for the TODO.md file. Only after the rebase finishes (and you make a regular commit) does the pre-commit pipeline run again.

📝 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.

Example:

# TODO
## src/main.rs
* [src/main.rs:10](src/main.rs#L10): Refactor initialization logic

📦 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, pip will try to build from source — which requires a Rust toolchain.
  • If you encounter build errors, please:
    1. Check the latest releases to confirm wheel availability.
    2. 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


⚖️ 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.

About

Pre-commit hook & CLI that finds and syncs TODO/FIXME comments into a sectioned TODO.md across your repo. Multi-language, multi-line, smart merge.

Topics

Resources

License

Stars

Watchers

Forks

Contributors