Skip to content

Repository files navigation

tudu

Keep the TODO comments in your codebase in sync with your issue tracker.

tudu scans source files for structured TODO(...) comments, validates the referenced issues against your tracker (Notion, GitHub, GitLab, Jira, or Linear), and optionally reconciles them bidirectionally, creating issues from code, updating tracker state, and producing safe, reviewable patches.

  • Validate by default.tudu scan is read-only and exits non-zero on closed, unknown, or malformed tracked TODOs, so it drops straight into CI.
  • Opt-in two-way sync. Flip to tudu sync and nothing is written without --apply; by default you get unified diffs under .tudu/patches/.
  • 50+ languages. The language registry covers //, #, --, ;, %, (* *), {- -}, """ """, <!-- -->, =begin/=end, and more.
  • Stable anchors. Each TODO is anchored by an xxhash of its surrounding context, so it survives line shifts and renames across runs.
  • No surprises. Legacy TODO TASK-123: and plain TODO: comments are recognized but never rewritten unless you ask.

Status: work in progress / learning project. The scanner, parser, providers, sync engine, and CLI are functional; the tool is not yet packaged for Homebrew or containers.


Table of contents


Install

From source (a Rust toolchain is required):

git clone https://github.com/michaelfromorg/tudu
cd tudu/tudu
cargo install --path .

tudu is a single static binary. Put it on your PATH and you're done.

Run from the checkout without installing:

cargo run -- scan .

Quickstart

# 1. From a repository root, list every TODO/FIXME comment.
tudu scan
# 2. Add a .tudu.yaml (see Configuration) and validate tracked TODOs.
tudu scan --verbose
# 3. Plan a sync without touching anything.
tudu sync --dry-run
# 4. Apply the planned edits and tracker updates.
tudu sync --apply

scan exits 0 when everything is clean and non-zero when it finds malformed TODOs or tracked references that are closed/unknown, so it works as a CI gate.


Comment syntax

Canonical form

TODO(<ref>[, attributes...]): human-friendly comment

References

FormMeaningExample
gh:org/repo#1234GitHub qualifiedTODO(gh:org/repo#1234):
gl:group/proj#77GitLab qualifiedTODO(gl:group/proj#77):
jira:PROJ-456Jira qualifiedTODO(jira:PROJ-456):
nt:PAGE_IDNotion page qualifiedTODO(nt:abc123):
lin:LIN-123Linear qualifiedTODO(lin:LIN-123):
TASK-123Shorthand (routed by prefix/config)TODO(TASK-123):
#1234Project-local shorthand (via origin)TODO(#1234):
new / new="title"File a new issueTODO(new="Add retry"): ...
(none)Untracked (reported, never synced)TODO: fix this
(person)Untracked (not a valid ID)TODO(alice): review

Attributes

Attributes are comma-separated inside the parentheses.

AttributeFormExample
bidir / one_wayflagTODO(TASK-1, bidir):
labelscomma listlabels=perf,cleanup
assigneetextassignee=@alice
duedate (YYYY-MM-DD)due=2025-09-01
close_on_deleteflag or true/falseclose_on_delete=true
statustext (quoted ok)status="In Progress"
sectiontext (quoted ok)section="parser"
dbNotion database aliasdb=tasks
prop.<Name>provider property overrideprop.priority=high

Quoted values may contain commas and spaces: section="a,b,c" is a single text value, not a list.

Legacy and untracked forms

  • TODO TASK-123: (no parens, space-separated) is recognized as tracked.
  • FIXME is treated identically to TODO for every form above.
  • Plain TODO: and TODO(person): are untracked, reported but never synced.
  • Markers match case-insensitively (todo, Todo, TODO all work); ID prefixes are uppercase by default (scan.match_case_insensitive relaxes this).

Examples

// Rust// TODO(TASK-1234, bidir, labels=parser): rewrite tokenizer for streaming// TODO: optimize this loop // Untracked — won't sync
# Python# TODO(TASK-9, due=2025-10-01): account for leap seconds in scheduler
<!-- TODO(TASK-333): HTML style comment -->

Configuration

tudu looks for .tudu.yaml at the base path (the scanned directory, or --basepath). It is optional: without it, scan reports TODOs without validating them. A commented example lives at tudu/.tudu.yaml.

origin: github.com/org/repoissue_tracker: notion # notion | github | gitlab | jira | linearmode: validate # validate | syncscan:
ignore:
- target/
- vendor/**include:
- "**/*"match_case_insensitive: falseproviders:
notion:
type: notiondatabases:
tasks:
database_id: "25dc4188fa0e806c9dd1f4e7327751c5"prefix: TASKstatus_property: Statuslocation_property: Locationdone_statuses: ["Done", "Resolved"]default_database: taskssync:
default_direction: one_way # one_way | bidircreate_on_missing: falseclose_on_todo_delete: ask # ask | never | alwaysupdate_from: none # none | issue | commentclosed_issue_action: annotate # annotate | removepatch_dir: .tudu/patchesstate_file: .tudu/state.jsonanchor_context_lines: 3auth:
type: apitokentokens_cache: ~/.tudu/authtokens.yamloutput:
format: standard # standard | jsonverbose: false

Tokens

Tokens are resolved in this order for each provider:

  1. The token: field in .tudu.yaml (avoid committing secrets).
  2. Provider-specific environment variables (recommended).
  3. TUDU_AUTH_TOKEN (a single override for CI).
ProviderEnvironment variable(s)
NotionNOTION_TOKEN
GitHubGITHUB_TOKEN
GitLabGITLAB_TOKEN
JiraJIRA_API_TOKEN (+ JIRA_EMAIL, JIRA_SERVER)
LinearLINEAR_API_KEY

The token cache at ~/.tudu/authtokens.yaml is created with 0600 permissions.

Ignore files

  • .gitignore is respected via the ignore crate.
  • .tuduignore adds project-local ignore rules.
  • scan.ignore globs in .tudu.yaml apply on top of both.
  • scan.include is an optional allowlist; when non-empty, only matching files are scanned (ignored files stay excluded even if they match an include).

Providers

Notion (multi-database)

Notion supports any number of databases, each with its own prefix and property mapping. TODOs are routed to the correct database by prefix match.

providers:
notion:
type: notiondatabases:
tasks:
database_id: "aaa..."prefix: TASKid_property: IDtitle_property: Namestatus_property: Statuslabels_property: Labelsassignee_property: Assigneeassignee_property_type: people # people | rich_textdue_property: Duedone_statuses: ["Done", "Resolved"]bugs:
database_id: "bbb..."prefix: BUGstatus_property: Statedone_statuses: ["Fixed", "Won't Fix"]default_database: tasks
  • tudu file --db tasks files untracked TODOs into the tasks database and rewrites them with the new ID.
  • prop.<Name>=value attributes map to arbitrary Notion properties.

GitHub

providers:
github:
type: githubowner: orgrepo: repo

Reference forms: gh:org/repo#1234, #1234 (via origin).

GitLab

providers:
gitlab:
type: gitlabproject: group/proj # numeric ID or "group/project"

Reference forms: gl:group/proj#77, #77 (via origin).

Jira

providers:
jira:
type: jiraserver: "https://example.atlassian.net"project: PROJ

Auth uses basic auth with JIRA_EMAIL + JIRA_API_TOKEN. Reference forms: jira:PROJ-456, PROJ-456 (shorthand via issue_tracker: jira).

Linear

providers:
linear:
type: linearteam: ENG

Reference forms: lin:LIN-123, LIN-123 (shorthand via issue_tracker: linear).


Commands

tudu scan [PATH] [--format json|standard] [--verbose] [--config FILE] [--basepath DIR]
tudu sync [PATH] [--dry-run] [--apply] [--update-from SOURCE] [--close-on-todo-delete POLICY] [flags]
tudu file [PATH] [--db NAME] [--interactive] [--apply] [flags]
tudu link --id REF --file PATH --line N -- "comment text"
tudu status [PATH] [--since DATE] [flags]

Common flags (available on most subcommands): --config FILE, --basepath DIR, --format json|standard, --verbose. Availability is noted per command below; for example --format applies to scan, sync, file, and status, while --verbose is offered by scan and sync.

tudu scan

Lists every TODO/FIXME comment (tracked and untracked) and validates tracked references against configured providers. Exits non-zero on malformed TODOs or closed/unknown tracked references.

tudu scan src/
tudu scan --format json | jq .
tudu scan --verbose

tudu sync

Reconciles tracked TODOs with the tracker. --dry-run (the default) plans changes and writes unified diffs to .tudu/patches/; --apply edits files in place and posts tracker updates.

tudu sync --dry-run
tudu sync --apply
tudu sync --update-from comment --close-on-todo-delete always

tudu file

Files untracked TODO: comments as issues and rewrites them with the new ID. Batch mode is the default; --interactive prompts per TODO. --apply is required to create issues and edit files.

tudu file --db tasks --apply
tudu file --interactive

tudu link

Add or update a tracked TODO at a specific file/line location.

tudu link --id TASK-321 --file src/lib.rs --line 120 -- "Refactor allocator"

tudu status

Report drift between TODOs and the tracker since a date (read-only).

tudu status --since 2025-08-01
tudu status --format json

Output formats

Standard (human-readable):

ERROR: Malformed TODO
src/main.ts:17: // TODO(): missing ref; expected TODO(<ref>):
ERROR: Issue doesn't exist
src/parser.rs:42: // TODO(gh:org/repo#999999): …

JSON (machine-readable, stdout only):

[
{"type":"Malformed TODO","filename":"src/main.ts","line":17,"message":"Expected TODO(<ref>):"},
{"type":"Issue doesn't exist","filename":"src/parser.rs","line":42,"metadata":{"ref":"gh:org/repo#999999"}}
]

In JSON mode, stdout carries only the JSON array; diagnostics go to stderr.


CI examples

GitHub Actions

name: tuduon: [push, pull_request]jobs:
validate:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo install --path tudu
- name: Validate tracked TODOsenv:
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}run: tudu scan --format json | tee tudu-report.json
- name: Plan sync (no writes)run: tudu sync --dry-run || true
- name: Upload patches and reportif: always()uses: actions/upload-artifact@v4with:
name: tudu-artifactspath: | tudu-report.json .tudu/patches/*.diff

GitLab CI

tudu:validate:
image: rust:latestscript:
- cargo install --path tudu
- tudu scan --format json > tudu-report.json
- tudu sync --dry-run || trueartifacts:
when: alwayspaths:
- tudu-report.json
- .tudu/patches/

Exit codes

  • 0 — clean scan, no malformed TODOs and no invalid tracked references.
  • non-zero — malformed TODOs, closed/unknown tracked references, or a runtime error (missing path, bad config, provider failure).

Performance

The scanner walks files in parallel with the ignore crate and reads each file once. Targets from the PRD, verified by tests/benchmarks.rs:

ScenarioTargetMeasured
Cold scan, ~100k LOC< 60swell under 1s on commodity hardware
Repeated/incremental scan< 10swell under 1s

Run the benchmarks yourself:

cargo test --test benchmarks -- --nocapture

Testing

cargo test# full suite (unit, integration, property, snapshot)
cargo clippy --all-targets -- -D warnings
cargo fmt -- --check

The suite is fully deterministic and environment-independent:

  • Provider tests use wiremock HTTP mocks matching real API shapes; no real tokens or network calls are required.
  • CLI tests use assert_cmd and insta snapshots.
  • Parser edge cases are fuzzed with proptest.
  • No test reads or writes outside its temp directory.

Architecture

tudu/src/
├── main.rs # Entry point, tracing setup, delegates to cli
├── cli.rs # clap subcommands (scan, sync, file, link, status)
├── config.rs # .tudu.yaml parsing
├── error.rs # TuduError (thiserror)
├── output.rs # Output formatting (standard + JSON)
├── scanner/ # File walker, 50+ language registry, comment extraction
├── parser/ # TODO grammar, references, attributes
├── anchor.rs # Context hashing (xxhash), relocation heuristics
├── providers/ # IssueProvider trait + Notion, GitHub, GitLab, Jira, Linear
├── reconciler.rs # Drift detection, conflict resolution, action plan
├── writer.rs # Unified diff generation, in-place editing
├── state.rs # .tudu/state.json management
├── sync.rs # sync command orchestration
├── link.rs # link command orchestration
└── status.rs # status command orchestration

Data flow:

Scan files → Extract comments → Parse TODOs → Validate against providers
→ Reconcile (compute plan) → Write (patches or in-place) → Persist state

See docs/PRD.md and docs/RFC.md for the full design and tudu/src/lib.rs for the public API surface.


License

MIT — see LICENSE.

About

A CLI program to help manage todos in your codebase.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages