ThangQT

05 NOV 2026

4 min read

← all writing

#TODO Is Your Best Friend

How TODO comments and better commits helped me keep AI coding on track.

§ text

The more I rely on AI agents for coding, the more I struggle with the small stuff. Not the high-level architecture, but the loose ends: an edge case we’d only partly handled, a deferred refactor, or a technical decision whose reasoning I’ve already forgotten two days later.

Why planning files fail

When planning modes landed in major coding CLIs, I tried the plan-first approach. It worked fine for high-level milestones, but fell apart in day-to-day use. A lot of the work only becomes obvious after you test something: an unhandled edge case, an awkward UI interaction, or a secondary requirement you couldn’t anticipate upfront.

So I tried keeping a progress.md file. It immediately ballooned. It became a chore to update, drifted out of sync with the codebase, and felt completely disconnected from the actual files I was touching.

Eventually, I ditched the external scratchpad entirely. Two simple conventions in AGENTS.md solved the problem using tools already in the repo: TODO comments for pending work and Git history for past context.


1. TODOs capture pending work in place

Instead of tracking future tasks in a separate document, the agent must leave an actionable TODO directly next to the relevant code whenever it leaves something unfinished or spots something out of scope.

Here is the convention in AGENTS.md:

TODO(<kind>): <what needs doing> — <why / context / where>

A few rules enforce it:

  • Add one whenever you leave partial work or spot something out of scope.
  • Keep each TODO actionable and specific; remove it in the change that resolves it.
  • Use categories like impl, refactor, shared, test, or perf.
  • Run pnpm todo (a project script) to list open items.

For example, when we implemented background thumbnail generation, we had a few loose ends (excerpts shortened here):

// TODO(impl): backfill thumbnails for existing saves — only new saves enqueue them today.
// TODO(impl): offer explicit regeneration ("New image") with fair-use accounting.
// TODO(impl): decide if background thumbnails consume the fair-use allowance — usage is recorded now.

The feature worked for new saves, but older saves still needed thumbnails, and billing decisions were pending. Instead of turning these follow-ups into a multi-page plan, we left them right where the work needed to happen.

It also keeps scope creep in check. When we noticed duplicate theme-handling scripts across three packages, we didn’t derail the current task to build a shared library. We just left a marker in the site layout (file and package names omitted):

<!-- TODO(shared): same logic as the app's theme script;
     move to the shared brand package so app, docs, and site share one script. -->

2. Git commit bodies capture context

TODOs handle what’s left to do. Git handles what was done and why.

Instead of logging completed milestones in a progress doc, AGENTS.md instructs the agent to write deliberate, structured commit messages. In short:

Git is the project’s memory. Commit bodies record what was done, why, and what was validated.

For example, when we added flashcard editing, we made a few choices that wouldn’t be obvious from the code alone. Here’s an adapted excerpt from that commit, shortened with file paths and library names left out:

feat(web): add, edit, delete and skip flashcards

What:
- Add a shared editor for creating and editing cards, with searchable
  module and unit pickers, Markdown preview, and an unsaved-changes prompt.
- Add edit, add, skip and delete actions to review sessions.
- Skipping a card doesn't count as seeing it again. Editing updates
  every queued showing in place, preserving review IDs.

Why / decisions (confirmed by the owner):
- Plain Markdown with preview rather than a rich editor: cards contain
  math and Mermaid, which rich editors do not round-trip.
- Skip is session-only; snooze/bury and suspend/archive are left out
  until a card management page exists to bring cards back.
- Use an inset sheet and searchable pickers per the owner's review.

Follow-ups:
- TODO(impl): card management outside a review, reusing the editor
  and delete dialog.

Validated:
- pnpm check, typecheck, build, test (new queue and skip-key tests).
- The owner reviewed the editor in the browser and waived further
  browser checks.

Without the body, a future agent might see the plain-text editor and suggest replacing it with a rich editor. Or add a suspend action without noticing that there’s no way to bring suspended cards back yet. The commit records why those choices were made and what needs to exist before we revisit them.

It also connects the finished work to the unfinished work. We built editing inside a review, but left a TODO for managing cards outside one, with a note to reuse what we’d already built. I don’t need a separate progress file to reconstruct that.


The loop: orient first

To tie this together, the agent must read context before touching any code. Its instructions start with:

Orient: Read the code you will touch, its open TODOs (pnpm todo), and its recent history (git log --oneline -15 -- <path>, then read the bodies that matter).

This small ritual noticeably cuts down on context loss:

  1. The agent is less likely to introduce duplicate patterns because it sees recent decisions in Git.
  2. The agent often spots an open TODO right next to its task and cleans it up naturally.
  3. When I have a random idea or catch a bug while testing, I just drop an inline TODO and keep moving.

No separate tracking files to maintain or keep in sync. Just the code and the commit history, where the context belonged all along.