Easy Prompt
写作生成文字进阶

TODO Comment Debt Tracker

来自 prompts.chat 的提示词:TODO Comment Debt Tracker

提示词正文

复制后可直接粘贴到模型或内部评测工具。


name: todo-comment-debt-tracker description: Scans a codebase for TODO, FIXME, HACK, and XXX comments, extracts owner, ticket, and due date, and flags unowned, untracked, overdue, or vague items with a priority score. Use when cleaning up tech debt, before a release, or asking "which TODOs actually matter?". Includes a stdlib Python scanner with --self-test and a CI-friendly exit code.

TODO Comment Debt Tracker

You help teams turn scattered TODO, FIXME, HACK, and XXX comments into a short, owned, prioritized debt list. You scan the code, parse who owns each item, which ticket tracks it, and when it is due, then highlight the ones that are risky or forgotten. You never delete or rewrite comments on your own; you propose changes and let the team decide.

Files in this skill

  • scripts/todo_debt.py - scans files or directories, parses owner/ticket/due date, scores each item, and prints a markdown table or JSON (Python 3 standard library only)
  • references/comment-conventions.md - a recommended TODO format and how to adopt it without churn
  • references/triage-rules.md - how to decide fix now, ticket, or delete
  • templates/debt-report.md - report for a sprint review, release checklist, or PR comment
  • examples/sample_service.py - a small file with typical good and bad TODOs for demos

Workflow

1. Scope the scan

Ask which directories matter and what to skip (generated code, vendored libraries, fixtures). The script already skips .git, node_modules, vendor, dist, build, virtualenvs, and target.

2. Run the scanner

python3 scripts/todo_debt.py src/ --today 2026-10-18
python3 scripts/todo_debt.py src/ tests/ --ext .py,.ts --json > todo-debt.json
python3 scripts/todo_debt.py examples/sample_service.py --strict
python3 scripts/todo_debt.py --self-test

Exit code 1 when an item is overdue or a FIXME/HACK/XXX has no ticket (--strict: any problem). Use it as a soft CI gate or a weekly report job.

3. Read the problems

  • overdue: due date passed. Highest priority.
  • no_ticket: nothing tracks the work; it will be forgotten.
  • stale_hack: a HACK or XXX with no ticket, often a workaround that outlived its reason.
  • no_owner: nobody is responsible.
  • vague: fewer than three words, such as "TODO: fix".

Scores: FIXME/HACK/XXX start at 3, TODO at 1; +2 if overdue, +1 if no ticket.

4. Triage with the team

Apply references/triage-rules.md: fix now if it is under 30 minutes, create a ticket and add its id if it is real work, delete it if it is obsolete. Use git blame on the line to find a likely owner when none is written.

5. Report

Fill templates/debt-report.md with the top items, totals by marker and owner, and agreed actions. Suggest the convention in references/comment-conventions.md for new comments.

Rules

  • Do not count markers inside string literals or plain prose; only comments.
  • Never invent ticket numbers or owners; mark them unknown and suggest how to find them.
  • Do not remove comments in bulk without review; propose a patch instead.
  • Respect generated and vendored code: report it separately or skip it. FILE:references/comment-conventions.md

Recommended TODO convention

# TODO(owner, TICKET-123, 2026-12-01): what needs to happen and why
  • owner: a username or team handle (alice, @payments-team). One name, the person who will make the call.
  • ticket: #123, PROJ-123, or a full issue URL. Required for FIXME, HACK, and XXX.
  • due date: ISO YYYY-MM-DD, optional for TODO, recommended for HACK workarounds.
  • text: an action plus the reason, at least a short sentence. "TODO: fix" is not useful.

Which marker to use

MarkerMeaningTicket required?
TODOPlanned improvement, not wrong todayRecommended
FIXMEKnown bug or incorrect behaviorYes
HACKDeliberate workaround that should be removedYes, plus a due date
XXXDanger, surprising code, review neededYes

Adopting without churn

  1. Run the scanner and publish the report; do not mass-edit.
  2. Apply the convention to new comments via code review or a lint rule.
  3. Fix old comments when you touch that code anyway.
  4. Revisit the totals monthly; aim for "no overdue, no untracked FIXME". FILE:references/triage-rules.md

Triage rules

For each item, ask in order:

  1. Is it still true? Read the surrounding code and git log -L for the lines. If the issue is gone, delete the comment.
  2. Is it under 30 minutes? Fix it now in a small PR that removes the comment.
  3. Is it a bug or a risk? (FIXME/XXX, security, data loss, money) Create a ticket with priority, add the ticket id to the comment, and assign an owner.
  4. Is it a workaround? (HACK) Write down the condition that allows removal ("remove after vendor SDK 5.x") and a due date.
  5. Is it a wish? If nobody would schedule it in the next two quarters, delete it or move it to a backlog ticket.

Priority hints

  • Overdue items first, then FIXME/HACK/XXX without tickets, then unowned items in hot files (high churn).
  • Items in auth, payments, data migrations, or concurrency code deserve a closer look regardless of score.

Finding an owner

  • git blame -L <line>,<line> <file> shows who added it; that person or their team is the default owner.
  • CODEOWNERS files map paths to teams.
  • If the author has left, assign to the current maintainer of the module. FILE:templates/debt-report.md

TODO Debt Report

Repository: <name> Scanned: <paths> Date: <YYYY-MM-DD>

Totals

MarkerCountOverdueNo ticketNo owner
TODO
FIXME
HACK
XXX

Top 10 by score

ScoreLocationMarkerOwnerTicketDueProblemsProposed action

By owner

OwnerItemsOverdue

Agreed actions

  • Fix now:
  • Ticket created:
  • Delete (obsolete):

Convention reminder

# TODO(owner, TICKET-123, YYYY-MM-DD): action and reason FILE:examples/sample_service.py """Sample order service with typical TODO comments for demos of todo_debt.py.""" import json

TAX_RATE = 0.2 # XXX(PAY-311): confirm tax rate per region with finance

def load_orders(path): # TODO(maria, 2026-03-01): switch to streaming parser once files exceed 50 MB (#88) with open(path, encoding="utf-8") as fh: return json.load(fh)

def total(order): # FIXME: returns wrong total when a discount exceeds the subtotal subtotal = sum(item["price"] * item["qty"] for item in order["items"]) discount = order.get("discount", 0) return round((subtotal - discount) * (1 + TAX_RATE), 2)

def notify(order): # HACK [2026-11-30] skip emails for test accounts until the staging mail stub is fixed if order.get("email", "").endswith("@test.local"): return False # TODO: retry return True

def label(order): note = "TODO: this string is not a comment and should be ignored" # TODO(@ken, OPS-19): move label printing to the warehouse service return f"{order['id']} {note[:0]}" FILE:scripts/todo_debt.py #!/usr/bin/env python3 """Scan source files for TODO/FIXME/HACK/XXX comments and grade them (Python 3 standard library only).

Recognized forms (marker must follow a comment token: # // /* * -- ; <!--):

TODO: plain note

// TODO(alice): owner

FIXME(#123): issue number # TODO(PROJ-42) ticket key # TODO(https://...) link

TODO(bob, 2026-12-01): owner + due date # HACK [2026-11-15] due date in brackets

Problems flagged per item: no_owner, no_ticket, overdue (due date before --today), stale_hack (HACK/XXX without ticket), vague (fewer than 3 words of text) Exit code: 1 if any item is overdue or a FIXME/HACK/XXX has no ticket (with --strict: any problem). """ import argparse import datetime as dt import json import os import re import sys

MARKERS = ("TODO", "FIXME", "HACK", "XXX") SEVERITY = {"FIXME": 3, "HACK": 3, "XXX": 3, "TODO": 1} COMMENT_RE = re.compile( r"(?:#|//|/*|(?:^|\s)*|--|;|<!--)\s*(?P<marker>TODO|FIXME|HACK|XXX)\b" r"(?:\s*((?P<paren>[^)])))?(?:\s[(?P<bracket>[^]])])?\s:?\s*(?P<text>.)$") DATE_RE = re.compile(r"\b(\d{4}-\d{2}-\d{2})\b") TICKET_RE = re.compile(r"(#\d+\b|\b[A-Z][A-Z0-9]+-\d+\b|https?://\S+)") OWNER_RE = re.compile(r"^@?([A-Za-z][\w.-])$") DEFAULT_EXT = {".py", ".js", ".ts", ".tsx", ".jsx", ".go", ".rs", ".java", ".kt", ".c", ".h", ".cpp", ".cs", ".rb", ".php", ".swift", ".sh", ".sql", ".yaml", ".yml", ".toml", ".css", ".scss", ".html", ".vue", ".lua"} SKIP_DIRS = {".git", "node_modules", "vendor", "dist", "build", ".venv", "venv", "pycache", "target"}

def parse_line(line, path="<text>", lineno=0, today=None): m = COMMENT_RE.search(line) if not m: return None marker = m.group("marker") meta = ",".join(x for x in (m.group("paren"), m.group("bracket")) if x) text = (m.group("text") or "").strip().rstrip("*/").rstrip("-->").strip() owner = None ticket = None due = None for part in [p.strip() for p in meta.split(",") if p.strip()]: if DATE_RE.fullmatch(part.replace("by ", "").replace("until ", "").strip()): due = DATE_RE.search(part).group(1) elif TICKET_RE.fullmatch(part): ticket = part elif OWNER_RE.fullmatch(part) and owner is None: owner = OWNER_RE.fullmatch(part).group(1) if ticket is None: t = TICKET_RE.search(text) if t: ticket = t.group(1) if due is None: d = re.search(r"\b(?:by|until|before|due)\s+(\d{4}-\d{2}-\d{2})\b", text) if d: due = d.group(1) problems = [] if not owner: problems.append("no_owner") if not ticket: problems.append("no_ticket") if marker in ("HACK", "XXX"): problems.append("stale_hack") if due and today: try: if dt.date.fromisoformat(due) < today: problems.append("overdue") except ValueError: problems.append("bad_date") if len(re.findall(r"[A-Za-z]{2,}", text)) < 3: problems.append("vague") return {"file": path, "line": lineno, "marker": marker, "owner": owner, "ticket": ticket, "due": due, "text": text, "problems": problems, "score": SEVERITY[marker] + 2 * ("overdue" in problems) + ("no_ticket" in problems)}

def scan_text(text, path="<text>", today=None): items = [] for i, line in enumerate(text.splitlines(), 1): it = parse_line(line, path, i, today) if it: items.append(it) return items

def iter_files(root, exts): if os.path.isfile(root): yield root return for dirpath, dirnames, filenames in os.walk(root): dirnames[:] = sorted(d for d in dirnames if d not in SKIP_DIRS) for fn in sorted(filenames): if os.path.splitext(fn)[1].lower() in exts: yield os.path.join(dirpath, fn)

def is_blocking(item, strict=False): if strict: return bool(item["problems"]) return "overdue" in item["problems"] or (item["marker"] != "TODO" and "no_ticket" in item["problems"])

def summarize(items): s = {"total": len(items), "by_marker": {}, "by_owner": {}, "problems": {}} for it in items: s["by_marker"][it["marker"]] = s["by_marker"].get(it["marker"], 0) + 1 o = it["owner"] or "(unowned)" s["by_owner"][o] = s["by_owner"].get(o, 0) + 1 for p in it["problems"]: s["problems"][p] = s["problems"].get(p, 0) + 1 return s

def render(items): out = ["| Score | Marker | Location | Owner | Ticket | Due | Problems | Text |", "|---|---|---|---|---|---|---|---|"] for it in sorted(items, key=lambda x: (-x["score"], x["file"], x["line"])): out.append(f"| {it['score']} | {it['marker']} | {it['file']}:{it['line']} | {it['owner'] or '-'} | " f"{it['ticket'] or '-'} | {it['due'] or '-'} | {', '.join(it['problems']) or 'ok'} | {it['text'][:60]} |") return "\n".join(out)

SAMPLE = '''import os

TODO(alice, 2026-01-15): remove legacy CSV export after #412 ships

def f(): pass # FIXME: crashes when list is empty // HACK [2026-12-31] skip auth in local dev /* XXX(PROJ-77): double-check rounding with finance */ -- TODO(@bob): add index x = "TODO: this is a string, not a comment"

<!-- TODO(carol): https://example.com/issues/9 replace hero image -->
  • TODO update docs by 2025-05-01

todo lowercase is ignored

'''

def self_test(): today = dt.date(2026, 10, 18) items = scan_text(SAMPLE, "sample", today) by_line = {it["line"]: it for it in items} cases = [ ("count", len(items), 7), ("owner_parsed", by_line[2]["owner"], "alice"), ("due_parsed", by_line[2]["due"], "2026-01-15"), ("overdue_flag", "overdue" in by_line[2]["problems"], True), ("ticket_from_text", by_line[2]["ticket"], "#412"), ("fixme_no_ticket", by_line[4]["problems"][:2], ["no_owner", "no_ticket"]), ("fixme_blocking", is_blocking(by_line[4]), True), ("hack_bracket_due_future", "overdue" in by_line[5]["problems"], False), ("hack_stale", "stale_hack" in by_line[5]["problems"], True), ("xxx_ticket_key", by_line[6]["ticket"], "PROJ-77"), ("at_owner", by_line[7]["owner"], "bob"), ("vague_short", "vague" in by_line[7]["problems"], True), ("string_not_comment", 8 in by_line, False), ("html_comment_link", by_line[9]["ticket"], "https://example.com/issues/9"), ("star_comment_due_text", by_line[10]["due"], "2025-05-01"), ("lowercase_ignored", 11 in by_line, False), ("todo_not_blocking_default", is_blocking(by_line[7]), False), ("todo_blocking_strict", is_blocking(by_line[7], strict=True), True), ("summary_markers", summarize(items)["by_marker"], {"TODO": 4, "FIXME": 1, "HACK": 1, "XXX": 1}), ("score_overdue_todo", by_line[2]["score"], 3), ] failed = 0 for name, got, expected in cases: ok = got == expected failed += not ok print(f"{'PASS' if ok else 'FAIL'} {name}: got={got!r} expected={expected!r}") print(f"\n{len(cases) - failed}/{len(cases)} passed") return 1 if failed else 0

def main(argv=None): p = argparse.ArgumentParser(description="Find and grade TODO/FIXME/HACK/XXX comments.") p.add_argument("paths", nargs="*", default=[], help="files or directories to scan") p.add_argument("--today", help="YYYY-MM-DD for overdue checks (default: today)") p.add_argument("--ext", help="comma-separated extensions to include, e.g. .py,.ts") p.add_argument("--json", action="store_true") p.add_argument("--strict", action="store_true", help="exit 1 on any problem") p.add_argument("--self-test", action="store_true") a = p.parse_args(argv) if a.self_test: return self_test() if not a.paths: p.error("give files/directories to scan, or --self-test") today = dt.date.fromisoformat(a.today) if a.today else dt.date.today() exts = {e if e.startswith(".") else "." + e for e in a.ext.split(",")} if a.ext else DEFAULT_EXT items = [] for root in a.paths: for path in iter_files(root, exts): try: with open(path, encoding="utf-8", errors="replace") as fh: items += scan_text(fh.read(), path, today) except OSError as e: print(f"warn: {path}: {e}", file=sys.stderr) if a.json: print(json.dumps({"summary": summarize(items), "items": items}, indent=2)) else: print(render(items)) s = summarize(items) print(f"\nTotal {s['total']}; markers {s['by_marker']}; problems {s['problems']}") return 1 if any(is_blocking(it, a.strict) for it in items) else 0

if name == "main": sys.exit(main())

使用场景

同步入库后可继续人工润色

参考输出

暂无标准答案,建议按评分维度人工评审。

评分维度

重点评估可执行性、事实准确性、边界控制和结构完整度。

试用与模板

填写变量后复制,或保存到个人工作台模板。

这个模板没有变量,可直接复制使用。

用户评分

0 个评分
-

你的评分

登录后评分

评论

0

登录后评论

相关提示词