Hầu hết developer dùng AI coding agent để viết code. Ít ai dùng nó để xóa code. Đây là bài viết về việc sử dụng AI agent (Claude Code, Cursor, v.v.) để tìm và loại bỏ dead code, stale architecture, wrapper pattern thừa — và cách prompt sao cho kết quả thực sự toàn diện.
Cách phổ biến nhất để tìm dead code: grep tên function, thấy 0 kết quả, kết luận nó dead. Sai.
- Grep không hiểu re-exports.
export { X } from "./foo"rồiimport { X } from "./barrel"— grep tìm "foo" sẽ bỏ qua consumer thật. - Grep không phân biệt type-only import vs runtime import. Symbol có thể được import bởi 10 files nhưng chỉ dùng ở type position — runtime code vẫn dead.
- Grep match text, không match semantics.
toolResulttrongimport { toolResult }vàconst toolResult = { role: "toolResult" }là hai thứ hoàn toàn khác nhau.
Khi bạn bảo AI agent "tìm dead code", nó sẽ dùng grep. Và nó sẽ bỏ sót.
Cách duy nhất chứng minh code dead: xóa nó, chạy compiler, xem có gì vỡ không.
Với mỗi exported symbol trong src/utils/*.ts:
- Tạm xóa symbol đó
- Chạy bun check (hoặc tsc, cargo check)
- Nếu pass → symbol dead, ghi nhận
- Revert
Chỉ report, không fix.
Agent có khả năng edit, check, undo trong cùng 1 turn. Tận dụng nó. Đây không phải là cách con người audit code — quá chậm, quá tedious. Nhưng máy thì không care.
Thử xóa [module]. Chạy typecheck.
Pass → dead. Fail → revert, report consumers.
Đơn giản nhất, chính xác nhất. Không false positive. Agent xóa thử, compiler verify, agent revert. Bạn chỉ nhận report.
Khi nào dùng: Khi bạn nghi ngờ 1 file/module cụ thể là dead nhưng không chắc vì dependency graph phức tạp.
git log --diff-filter=D --name-only -20
Tìm references còn sót tới files đã xóa.
Dead code không xuất hiện từ hư không. Nó sinh ra khi ai đó xóa feature nhưng không clean hết references. Config entries, type re-exports, model roles, setting schemas — những thứ này hay bị bỏ sót vì chúng không gây compile error.
Khi nào dùng: Sau mỗi đợt refactor lớn. Hoặc khi bạn thấy codebase có nhiều deleted files trong git log gần đây.
Ví dụ thực tế: Một module commit/ với 50+ files bị xóa. Sau khi xóa, vẫn còn sót:
CommitSettingsinterface trong settings schema"commit"model role trong registryformat-promptsscript reference tớicommit/prompts/directory- Re-export trong
settings.ts
Không cái nào gây compile error. Tất cả đều là dead weight mà grep bình thường không target.
Cross-reference:
1. Tool registration (BUILTIN_TOOLS)
2. Settings schema (*.enabled keys)
3. Renderer registration (registerRenderer)
4. Agent allowlists (task/agents.ts)
5. Frontmatter tool lists (.md files)
Report mismatches.
Ứng dụng lớn thường có nhiều "registration layers" — nơi một entity (tool, plugin, feature) phải được khai báo ở nhiều chỗ. Khi xóa feature, dễ bỏ sót 1-2 layers.
Khi nào dùng: Khi codebase có plugin/tool architecture với multiple registration points.
Với mỗi exported symbol trong src/tools/*.ts:
dùng LSP references đếm consumers.
Report symbols có 0-1 consumers.
LSP (Language Server Protocol) hiểu code ở semantic level — nó biết import type { X } khác import { X }, biết re-export chain, biết interface implementation. Prompt agent dùng LSP thay vì grep cho kết quả chính xác hơn nhiều.
Khi nào dùng: Khi cần audit exports của utility/shared modules. Đặc biệt hiệu quả cho barrel files (index.ts) nơi re-exports tích tụ qua thời gian.
git log --since=3months --format= --name-only src/ | sort | uniq -c | sort -rn | head 20
Với 20 files churn cao nhất: audit wrapper patterns, over-abstraction, SRP violations.
Không phải mọi code xấu đều đáng fix. File 500 dòng không ai đụng 1 năm — để yên. File 50 dòng bị sửa mỗi tuần — mỗi lần sửa đều trả cost cho design tệ. Churn rate là proxy tốt nhất cho ROI của refactor.
Khi nào dùng: Khi cần prioritize — quá nhiều tech debt, không biết bắt đầu từ đâu.
Tìm files chỉ được import qua `import type`.
Nếu file export cả runtime code mà chỉ có type consumers → runtime code dead.
Đây là blind spot mà cả compiler lẫn linter đều bỏ qua. File export 1 class và 1 interface. Interface được import type bởi 5 files. Class không ai dùng. Compiler thấy file có consumers → không warn. Nhưng runtime code trong file đó hoàn toàn dead.
Khi nào dùng: Trong TypeScript codebases nặng type — monorepos với shared type packages.
Quá mơ hồ. Agent sẽ cherry-pick vài thứ dễ tìm (unused imports, obvious dead functions) rồi dừng. Bạn nghĩ scan xong, thực ra mới quét bề mặt.
Thay bằng: Nêu rõ category — dead exports, orphaned files, stale config, wrapper indirection.
# Đừng
Tìm và xóa tất cả dead code.
# Nên
Tìm dead code. Report theo category và priority. Không fix.
Agent sẽ tìm 3 thứ, fix luôn, rồi dừng — vì nó nghĩ task xong. Tách scan và fix thành 2 bước để bạn review findings trước khi quyết định.
Scope càng rộng, kết quả càng shallow. Nếu bạn biết vấn đề nằm ở packages/core/, nói rõ. Agent sẽ deep dive thay vì skim.
Bước 1 (Discovery): Scan rộng theo categories → nhận report
Bước 2 (Verification): Với mỗi finding, staged removal để confirm
Bước 3 (Review): Bạn quyết định cái nào fix, cái nào để
Bước 4 (Execution): Agent fix từng batch, verify sau mỗi batch
Mỗi bước là 1 prompt riêng. Bước 1-2 agent làm autonomous. Bước 3 bạn quyết định. Bước 4 agent thực thi.
Copy và điều chỉnh cho project của bạn:
Audit `[package/directory]`:
1. Orphaned files — .ts files với zero inbound imports (trừ entry points)
2. Dead exports — exported symbols với zero external consumers (dùng LSP, không grep)
3. Stale config — settings/schema entries reference features đã xóa
4. Wrapper indirection — class/function chỉ delegate 1:1 tới target khác
5. Registration mismatches — entity có mặt ở layer A nhưng vắng ở layer B
List findings theo category, kèm severity và recommended action.
Với mỗi finding nghi ngờ, dùng staged removal để confirm.
Không fix — chỉ report.
Dead code không gây bug. Nó gây friction — mỗi lần đọc, mỗi lần navigate, mỗi lần onboard người mới. AI agent là công cụ tốt nhất hiện tại để tìm nó, miễn bạn prompt đúng cách.