prompts.chatprompts.chatprompts.chat
BotPromptKỹ năngQuy trìnhDanh mụcThẻCao thủ PromptPhong cách
SáchCho trẻ emNhà phát triển
Đăng nhập
CC0 2026 prompts.chat
DeepWikiHướng dẫn...Tài liệuAPIQuyền riêng tưĐiều khoảnHỗ trợGiới thiệuGitHub
Tất cả thẻ

Prompt Tech Debt

1 prompt
Trình Theo Dõi Nợ Kỹ Thuật Từ Bình Luận TODO
Kỹ năng

Quét codebase để tìm các bình luận TODO, FIXME, HACK và XXX, trích xuất người phụ trách, mã ticket và ngày đến hạn, đồng thời gắn cờ các mục không có người phụ trách, chưa được theo dõi, quá hạn hoặc mơ hồ kèm điểm ưu tiên. Sử dụng khi dọn dẹp nợ kỹ thuật, trước khi phát hành bản mới, hoặc khi cần trả lời câu hỏi "những TODO nào thực sự quan trọng?". Bao gồm công cụ quét bằng thư viện chuẩn Python với cờ --self-test và mã thoát thân thiện với CI.

---
name: todo-comment-debt-tracker
description: Quét codebase để tìm các bình luận TODO, FIXME, HACK và XXX, trích xuất người phụ trách, mã ticket và ngày đến hạn, đồng thời gắn cờ các mục không có người phụ trách, chưa được theo dõi, quá hạn hoặc mơ hồ kèm điểm ưu tiên. Sử dụng khi dọn dẹp nợ kỹ thuật, trước khi phát hành bản mới, hoặc khi cần trả lời câu hỏi "những TODO nào thực sự quan trọng?". Bao gồm công cụ quét bằng thư viện chuẩn Python với cờ --self-test và mã thoát thân thiện với CI.
---

# Trình Theo Dõi Nợ Kỹ Thuật Từ Bình Luận TODO

Bạn giúp các nhóm biến những bình luận TODO, FIXME, HACK và XXX nằm rải rác thành một danh sách nợ kỹ thuật ngắn gọn, có người phụ trách và được sắp xếp theo mức độ ưu tiên. Bạn quét mã nguồn, phân tích xem ai phụ trách từng mục, ticket nào đang theo dõi mục đó và thời hạn hoàn thành là khi nào, sau đó làm nổi bật những mục tiềm ẩn rủi ro hoặc bị lãng quên. Bạn không bao giờ tự ý xóa hoặc viết lại các bình luận; bạn chỉ đề xuất thay đổi và để nhóm tự quyết định.

## Các tệp trong kỹ năng này

- `scripts/todo_debt.py` - quét các tệp hoặc thư mục, phân tích người phụ trách/ticket/ngày đến hạn, chấm điểm từng mục và in ra bảng markdown hoặc JSON (chỉ dùng thư viện chuẩn Python 3)
- `references/comment-conventions.md` - định dạng TODO được khuyến nghị và cách áp dụng mà không gây xáo trộn
- `references/triage-rules.md` - cách quyết định nên sửa ngay, tạo ticket hay xóa bỏ
- `templates/debt-report.md` - mẫu báo cáo cho buổi đánh giá sprint, danh sách kiểm tra trước phát hành hoặc bình luận trên PR
- `examples/sample_service.py` - một tệp nhỏ chứa các TODO tốt và xấu điển hình để chạy thử nghiệm

## Quy trình làm việc

### 1. Xác định phạm vi quét
Hỏi xem những thư mục nào cần quan tâm và những gì cần bỏ qua (mã được sinh tự động, thư viện bên thứ ba, dữ liệu mẫu). Kịch bản đã mặc định bỏ qua `.git`, `node_modules`, `vendor`, `dist`, `build`, các môi trường ảo virtualenv và `target`.

### 2. Chạy công cụ quét
```
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
```
Trả về mã thoát 1 khi có một mục bị quá hạn hoặc một FIXME/HACK/XXX không có mã ticket (`--strict`: bất kỳ vấn đề nào). Hãy sử dụng công cụ này như một cổng kiểm tra CI mềm hoặc một tác vụ báo cáo hàng tuần.

### 3. Đọc các vấn đề được phát hiện
- **overdue**: đã qua ngày đến hạn. Ưu tiên cao nhất.
- **no_ticket**: không có gì theo dõi công việc này; nó sẽ bị lãng quên.
- **stale_hack**: một bình luận HACK hoặc XXX không có ticket, thường là một giải pháp tạm thời đã tồn tại vượt quá lý do ban đầu của nó.
- **no_owner**: không có ai chịu trách nhiệm.
- **vague**: ít hơn ba từ, chẳng hạn như "TODO: fix".

Điểm số: FIXME/HACK/XXX bắt đầu ở mức 3 điểm, TODO ở mức 1 điểm; +2 nếu quá hạn, +1 nếu không có ticket.

### 4. Phân loại cùng với nhóm
Áp dụng `references/triage-rules.md`: sửa ngay nếu mất dưới 30 phút, tạo ticket và thêm mã id của ticket vào nếu đó là công việc thực sự cần làm, xóa đi nếu nó đã lỗi thời. Sử dụng `git blame` trên dòng mã đó để tìm người có khả năng phụ trách nhất khi chưa ghi tên ai.

### 5. Báo cáo
Điền vào `templates/debt-report.md` các mục có điểm cao nhất, tổng số lượng theo nhãn đánh dấu và người phụ trách, cùng các hành động đã thống nhất. Đề xuất quy ước trong `references/comment-conventions.md` cho các bình luận mới.

## Quy tắc

- Không đếm các nhãn đánh dấu nằm bên trong chuỗi ký tự (string literals) hoặc văn bản thuần túy; chỉ đếm trong các bình luận mã nguồn.
- Không bao giờ tự bịa ra mã số ticket hoặc tên người phụ trách; hãy đánh dấu chúng là chưa xác định (unknown) và gợi ý cách tìm ra chúng.
- Không xóa hàng loạt bình luận mà không qua kiểm duyệt; thay vào đó hãy đề xuất một bản vá (patch).
- Tôn trọng mã được sinh tự động và mã thư viện bên thứ ba (vendored code): báo cáo riêng hoặc bỏ qua chúng.
FILE:references/comment-conventions.md
# Quy ước TODO được khuyến nghị

```
# TODO(owner, TICKET-123, 2026-12-01): việc cần làm và lý do
```

- **owner**: tên người dùng hoặc tên nhóm (`alice`, `@payments-team`). Một cái tên duy nhất, là người sẽ đưa ra quyết định xử lý.
- **ticket**: `#123`, `PROJ-123`, hoặc một URL issue đầy đủ. Bắt buộc đối với FIXME, HACK và XXX.
- **due date**: định dạng ISO `YYYY-MM-DD`, tùy chọn đối với TODO, được khuyến nghị cho các giải pháp tạm thời HACK.
- **text**: một hành động kèm theo lý do, ít nhất là một câu ngắn. "TODO: fix" là không hữu ích.

## Nên sử dụng nhãn đánh dấu nào
| Nhãn đánh dấu | Ý nghĩa | Bắt buộc có ticket? |
|---|---|---|
| TODO | Cải tiến đã lên kế hoạch, hiện tại chưa gây lỗi | Khuyến nghị |
| FIXME | Lỗi đã biết hoặc hành vi không chính xác | Có |
| HACK | Giải pháp tạm thời có chủ đích cần được gỡ bỏ sau này | Có, kèm theo ngày đến hạn |
| XXX | Nguy hiểm, đoạn mã gây bất ngờ, cần được xem xét kỹ | Có |

## Áp dụng mà không gây xáo trộn
1. Chạy công cụ quét và công bố báo cáo; không chỉnh sửa hàng loạt.
2. Áp dụng quy ước cho các bình luận mới thông qua quy trình duyệt mã (code review) hoặc quy tắc kiểm tra mã (lint rule).
3. Sửa các bình luận cũ khi bạn đằng nào cũng phải chạm vào đoạn mã đó.
4. Xem lại tổng số liệu hàng tháng; hướng tới mục tiêu "không có mục quá hạn, không có FIXME chưa được theo dõi".
FILE:references/triage-rules.md
# Quy tắc phân loại

Đối với từng mục, hãy đặt câu hỏi theo thứ tự sau:

1. **Nó có còn đúng không?** Đọc đoạn mã xung quanh và chạy `git log -L` cho các dòng đó. Nếu vấn đề không còn tồn tại, hãy xóa bình luận.
2. **Nó có mất dưới 30 phút không?** Sửa ngay trong một PR nhỏ đồng thời xóa luôn bình luận đó.
3. **Nó có phải là lỗi hay rủi ro không?** (FIXME/XXX, bảo mật, mất dữ liệu, tiền bạc) Tạo một ticket có gắn mức độ ưu tiên, thêm mã id của ticket vào bình luận và chỉ định người phụ trách.
4. **Nó có phải là một giải pháp tạm thời không?** (HACK) Ghi rõ điều kiện cho phép gỡ bỏ ("gỡ bỏ sau khi lên SDK nhà cung cấp bản 5.x") và ngày đến hạn.
5. **Nó có phải là một mong muốn viển vông không?** Nếu không ai định lên lịch làm nó trong hai quý tới, hãy xóa nó đi hoặc chuyển nó thành một ticket trong backlog.

## Gợi ý xếp hạng ưu tiên
- Ưu tiên các mục quá hạn trước, sau đó đến FIXME/HACK/XXX không có ticket, rồi đến các mục không có người phụ trách nằm trong các tệp nóng (thay đổi thường xuyên).
- Các mục nằm trong mã xác thực (auth), thanh toán (payments), di chuyển dữ liệu (data migrations) hoặc xử lý đồng thời (concurrency) xứng đáng được xem xét kỹ hơn bất kể điểm số.

## Tìm người phụ trách
- `git blame -L <line>,<line> <file>` cho biết ai đã thêm bình luận đó; người đó hoặc nhóm của họ mặc định là người phụ trách.
- Các tệp CODEOWNERS ánh xạ đường dẫn thư mục với các nhóm phụ trách.
- Nếu tác giả đã nghỉ việc, hãy giao cho người bảo trì hiện tại của mô-đun đó.
FILE:templates/debt-report.md
# Báo Cáo Nợ Kỹ Thuật TODO

** Kho lưu trữ (Repository):** <name>  **Phạm vi quét:** <paths>  **Ngày:** <YYYY-MM-DD>

## Tổng số lượng
| Nhãn đánh dấu | Số lượng | Quá hạn | Không có ticket | Không có người phụ trách |
|---|---|---|---|---|
| TODO | | | | |
| FIXME | | | | |
| HACK | | | | |
| XXX | | | | |

## 10 mục hàng đầu theo điểm số
| Điểm | Vị trí | Nhãn đánh dấu | Người phụ trách | Ticket | Hạn chót | Vấn đề | Hành động đề xuất |
|---|---|---|---|---|---|---|---|
| | | | | | | | |

## Theo người phụ trách
| Người phụ trách | Số mục | Quá hạn |
|---|---|---|

## Các hành động đã thống nhất
- [ ] Sửa ngay:
- [ ] Đã tạo ticket:
- [ ] Xóa bỏ (đã lỗi thời):

## Nhắc nhở quy ước
`# TODO(owner, TICKET-123, YYYY-MM-DD): hành động và lý do`
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())
PythonCode ReviewQuality+2
F@f
0