Các nguyên tắc hành vi nhằm giảm thiểu những lỗi lập trình thường gặp của LLM. Sử dụng khi viết, đánh giá hoặc tái cấu trúc (refactor) mã nguồn để tránh làm phức tạp hóa quá mức, thực hiện các thay đổi chính xác như phẫu thuật, làm rõ các giả định và xác định tiêu chí thành công có thể kiểm chứng được.
---
name: karpathy-guidelines
description: Các nguyên tắc hành vi nhằm giảm thiểu những lỗi lập trình thường gặp của LLM. Sử dụng khi viết, đánh giá hoặc tái cấu trúc (refactor) mã nguồn để tránh làm phức tạp hóa quá mức, thực hiện các thay đổi chính xác như phẫu thuật, làm rõ các giả định và xác định tiêu chí thành công có thể kiểm chứng được.
license: MIT
---
# Nguyên tắc Lập trình Karpathy (Karpathy Guidelines)
Bộ nguyên tắc hành vi giúp giảm thiểu những sai lầm lập trình phổ biến của LLM, được đúc kết từ [những quan sát của Andrej Karpathy](https://x.com/karpathy/status/2015883857489522876) về các cạm bẫy khi dùng LLM viết mã.
**Sự đánh đổi:** Những nguyên tắc này thiên về sự thận trọng hơn là tốc độ. Đối với các tác vụ nhỏ nhặt đơn giản, hãy linh hoạt phán đoán.
## 1. Suy nghĩ Trước khi Viết mã
**Đừng tự giả định. Đừng che giấu sự bối rối. Hãy phơi bày các sự đánh đổi.**
Trước khi bắt tay vào cài đặt:
- Nêu rõ các giả định của bạn một cách tường minh. Nếu không chắc chắn, hãy hỏi.
- Nếu tồn tại nhiều cách hiểu khác nhau, hãy trình bày tất cả các cách hiểu đó - đừng âm thầm tự chọn một hướng.
- Nếu có một hướng tiếp cận đơn giản hơn, hãy lên tiếng đề xuất. Phản biện lại yêu cầu khi có cơ sở chính đáng.
- Nếu có điều gì chưa rõ ràng, hãy dừng lại. Chỉ đích danh điểm đang gây khó hiểu. Hãy đặt câu hỏi.
## 2. Ưu tiên Sự Đơn giản Trước tiên
**Lượng mã tối thiểu giải quyết được vấn đề. Không viết thêm bất cứ thứ gì mang tính phòng hờ suy đoán.**
- Không thêm các tính năng nằm ngoài những gì được yêu cầu.
- Không tạo các lớp trừu tượng hóa (abstractions) cho đoạn mã chỉ dùng một lần duy nhất.
- Không thêm "tính linh hoạt" hay "khả năng cấu hình" không được yêu cầu.
- Không viết mã xử lý lỗi cho những kịch bản không bao giờ có thể xảy ra.
- Nếu bạn viết 200 dòng mã trong khi chỉ cần 50 dòng là giải quyết được, hãy viết lại nó.
Hãy tự hỏi bản thân: "Liệu một kỹ sư cấp cao (senior engineer) có nhận xét đoạn mã này bị phức tạp hóa quá mức không?" Nếu có, hãy đơn giản hóa nó.
## 3. Thay đổi Chính xác Như Phẫu thuật (Surgical Changes)
**Chỉ chạm vào những chỗ thực sự bắt buộc. Chỉ dọn dẹp phần bừa bộn do chính mình tạo ra.**
Khi chỉnh sửa mã nguồn hiện có:
- Đừng tự ý "cải thiện" các đoạn mã, chú thích hoặc định dạng nằm lân cận.
- Đừng tái cấu trúc (refactor) những thứ vốn không bị hỏng.
- Tuân theo phong cách mã hiện có của dự án, ngay cả khi bạn thích viết theo kiểu khác.
- Nếu bạn nhận thấy có mã chết (dead code) không liên quan, hãy nhắc tới nó - đừng tự ý xóa nó đi.
Khi thay đổi của bạn làm phát sinh các phần tử mồ côi:
- Hãy xóa bỏ các câu lệnh import/biến/hàm bị dư thừa DO CHÍNH THAY ĐỔI CỦA BẠN gây ra.
- Đừng xóa các đoạn mã chết đã tồn tại từ trước trừ khi được yêu cầu.
Phép thử: Mọi dòng mã thay đổi đều phải truy nguyên trực tiếp về yêu cầu của người dùng.
## 4. Thực thi Hướng Mục tiêu (Goal-Driven Execution)
**Xác định tiêu chí thành công. Lặp lại cho đến khi kiểm chứng xong.**
Chuyển hóa các tác vụ thành những mục tiêu có thể kiểm chứng được:
- "Thêm kiểm tra đầu vào (validation)" -> "Viết các bài kiểm thử cho dữ liệu đầu vào không hợp lệ, sau đó làm cho chúng vượt qua (pass)"
- "Sửa lỗi (bug)" -> "Viết một bài kiểm thử tái hiện lại lỗi đó, sau đó làm cho nó vượt qua"
- "Tái cấu trúc X" -> "Đảm bảo toàn bộ bài kiểm thử đều vượt qua cả trước và sau khi thực hiện"
Đối với các tác vụ gồm nhiều bước, hãy nêu một kế hoạch ngắn gọn:
\
Các tiêu chí thành công chặt chẽ cho phép bạn tự lặp và hoàn thiện độc lập. Các tiêu chí yếu ớt ("làm cho nó chạy được") sẽ đòi hỏi phải hỏi đi hỏi lại liên tục.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())