Bạn không cần đọc hết một cuốn sách để biết chương nào nói về chủ đề bạn cần — chỉ cần mục lục tốt. AI agent làm việc với codebase cũng vậy: thứ nó cần trước tiên không phải là toàn bộ code, mà là một bản đồ đủ tốt để biết "nên nhìn vào đâu".
Ở bài trước, chúng ta đã thấy cái giá phải trả khi nhồi nguyên file vào context: token bị đốt vô tội vạ, chất lượng câu trả lời giảm vì hiệu ứng "lost in the middle" (thông tin quan trọng bị chìm giữa hàng nghìn dòng không liên quan). Bài này đi vào giải pháp cụ thể: xây dựng các lớp ngữ cảnh cấu trúc (structural context) — repo map, file summary, và cách kết hợp chúng — để AI agent hiểu "bộ xương" của codebase với chi phí token thấp hơn hàng chục, thậm chí hàng trăm lần so với việc đọc toàn bộ source.
Repo Map Là Gì và Vì Sao Nó Hiệu Quả
Repo map (bản đồ cấu trúc repo) là một bản trình bày rút gọn của codebase — thường gồm cây thư mục (file tree) cộng với danh sách các symbol quan trọng trong mỗi file: tên class, tên hàm/method, export, interface, type — nhưng KHÔNG chứa phần thân (body) của các hàm đó. Nói cách khác, nó là "chương mục" của code, không phải "toàn văn".
Hãy tưởng tượng một service backend có 300 file. Nhồi nguyên 300 file vào context có thể tốn 1-2 triệu token. Nhưng nếu chỉ liệt kê:
src/services/order_service.py
class OrderService
def create_order(user_id, items, payment_method) -> Order
def cancel_order(order_id) -> bool
def _validate_inventory(items) -> ValidationResult
src/services/payment_service.py
class PaymentService
def charge(order_id, amount, method) -> ChargeResult
def refund(charge_id, amount) -> RefundResult
...thì con số đó chỉ còn vài nghìn token cho toàn bộ 300 file. AI agent đọc bản đồ này và lập luận được: "muốn sửa logic hủy đơn, tôi cần mở order_service.py, hàm cancel_order, và có thể cần xem PaymentService.refund vì hủy đơn thường kéo theo hoàn tiền." Đây chính xác là cách một kỹ sư senior mới join team sẽ định hướng — họ không đọc hết repo, họ lướt qua cấu trúc, đoán ra luồng, rồi mới đào sâu vào đúng chỗ.
Ý tưởng này không mới. Aider — một trong những công cụ pair-programming với AI phổ biến nhất — đã xây dựng tính năng "repo map" từ rất sớm, dựa trên một insight đơn giản nhưng mạnh: dùng thuật toán ranking giống PageRank (thuật toán Google từng dùng để xếp hạng trang web) áp lên graph các symbol trong repo. Symbol nào được tham chiếu (reference) nhiều nơi — ví dụ một class User được import ở 40 file khác — sẽ được xếp hạng cao và ưu tiên xuất hiện đầy đủ hơn trong bản đồ khi ngân sách token (token budget) có hạn. Symbol ít quan trọng, ít được dùng, sẽ bị rút gọn hoặc bỏ qua. Cơ chế này giúp Aider tự động "co giãn" độ chi tiết của repo map theo dung lượng context còn trống — nhiều context trống thì bản đồ chi tiết hơn, ít context trống thì bản đồ cô đọng hơn nhưng vẫn giữ được các symbol trọng yếu nhất.
Điểm mấu chốt cần hiểu: repo map không thay thế việc đọc code thật khi cần sửa logic. Nó là bước định hướng (orientation) trước khi đào sâu — giống như xem bản đồ trước khi lái xe vào một thành phố lạ, bạn vẫn cần nhìn đường thật khi đến gần đích, nhưng bản đồ giúp bạn không đi lạc ngay từ đầu.
Mẹo
- Đừng nghĩ repo map chỉ dành cho công cụ tự động. Ngay cả khi bạn copy-paste tay vào ChatGPT, hãy tự tay viết một bản tóm tắt cấu trúc 20-30 dòng trước khi paste code — hiệu quả tương tự, chi phí bằng 0.
- Với codebase polyglot (nhiều ngôn ngữ: backend Python, frontend TypeScript, mobile Kotlin), nên tạo repo map riêng cho từng phần, đừng trộn chung — AI dễ nhầm ngữ cảnh ngôn ngữ nếu bị trộn lẫn.
- Repo map hữu ích nhất ở giai đoạn "explore" (tìm hiểu, định hướng) của một task. Khi đã xác định rõ file cần sửa, chuyển sang đọc file đầy đủ — đừng cố dùng mãi bản đồ cho việc cần độ chính xác cao.
Sinh Repo Map: Công Cụ và Kỹ Thuật
Có nhiều cách tạo repo map, từ dùng công cụ có sẵn đến viết script tự chế. Dưới đây là các hướng phổ biến nhất theo thứ tự từ dễ đến linh hoạt nhất.
1. Dùng tính năng repo map có sẵn trong Aider
Nếu bạn đang dùng Aider, repo map được sinh tự động — không cần cấu hình gì thêm. Bạn có thể xem trực tiếp bản đồ mà Aider gửi cho model bằng cờ debug:
aider --show-repo-map
aider --map-tokens 2048
Aider dùng tree-sitter (một thư viện parser cấu trúc mã nguồn, hỗ trợ hàng chục ngôn ngữ) để trích symbol chính xác theo cú pháp thật của từng ngôn ngữ, chứ không chỉ regex đoán mò.
2. Dùng ctags / universal-ctags để trích symbol thủ công
ctags là công cụ kinh điển (ban đầu dùng cho Vim/Emacs) để quét source code và sinh ra danh sách "tag" — vị trí định nghĩa của hàm, class, biến. universal-ctags là bản fork hiện đại, hỗ trợ nhiều ngôn ngữ hơn và vẫn được duy trì tích cực.
sudo apt install universal-ctags
ctags -R --output-format=json -f - src/ | jq -r \
'"\(.path):\(.line) [\(.kind)] \(.name)"' > repo-map.txt
head -20 repo-map.txt
Kết quả là một danh sách phẳng: file, dòng, loại symbol (function, class, method...), và tên. Bạn có thể lọc theo kind để chỉ giữ các symbol quan trọng (class, function, interface) và loại bỏ biến local ít giá trị.
3. Dùng tree-sitter để parse cấu trúc chính xác hơn cho nhiều ngôn ngữ
tree-sitter mạnh hơn ctags vì nó xây dựng cây cú pháp trừu tượng (AST — Abstract Syntax Tree) đầy đủ, cho phép trích không chỉ tên symbol mà cả chữ ký hàm (function signature), kiểu trả về, decorator, docstring dòng đầu. Đây là script Python đơn giản dùng tree-sitter để trích signature của tất cả hàm/class trong một file:
import tree_sitter_python as tspython
from tree_sitter import Language, Parser
import sys, os
PY_LANGUAGE = Language(tspython.language())
parser = Parser(PY_LANGUAGE)
def extract_symbols(filepath):
with open(filepath, "rb") as f:
code = f.read()
tree = parser.parse(code)
symbols = []
def walk(node):
if node.type in ("function_definition", "class_definition"):
name_node = node.child_by_field_name("name")
if name_node:
line = node.start_point[0] + 1
symbols.append((line, node.type, name_node.text.decode()))
for child in node.children:
walk(child)
walk(tree.root_node)
return symbols
if __name__ == "__main__":
root = sys.argv[1] if len(sys.argv) > 1 else "."
for dirpath, _, files in os.walk(root):
for fname in files:
if fname.endswith(".py"):
fpath = os.path.join(dirpath, fname)
syms = extract_symbols(fpath)
if syms:
print(f"\n{fpath}")
for line, kind, name in syms:
label = "class" if kind == "class_definition" else "def"
print(f" L{line} {label} {name}")
Script này chỉ khoảng 30 dòng nhưng cho ra một bản đồ khá sát với những gì Aider làm bên trong — vì Aider cũng dùng tree-sitter làm nền. Bạn có thể mở rộng để trích thêm tham số, decorator, hoặc parse nhiều ngôn ngữ bằng cách đổi grammar (tree-sitter-javascript, tree-sitter-go, v.v.).
4. Viết script sinh cây thư mục + export nhanh gọn (không cần parser chuyên sâu)
Nếu không muốn cài thêm thư viện, một cách "80% hiệu quả với 20% công sức" là kết hợp tree (lệnh liệt kê cây thư mục) với grep để bắt các dòng export/định nghĩa theo pattern:
tree -I 'node_modules|.git|dist|__pycache__' src/ > structure.txt
grep -rn -E '^(export |class |function |const .* = \(|def )' src/ \
--include='*.ts' --include='*.tsx' --include='*.py' \
> symbols.txt
Cách này không hiểu ngữ pháp thật của ngôn ngữ nên có thể bắt sai một vài dòng (ví dụ comment chứa chữ "function"), nhưng tốc độ sinh cực nhanh và không cần cài gì ngoài công cụ Unix có sẵn — phù hợp cho việc thử nghiệm nhanh hoặc repo nhỏ.
Mẹo
- Với repo lớn (hàng nghìn file), luôn giới hạn độ sâu quét (
tree -L 3, hoặc lọc theo module) trước khi đưa cho AI — bản đồ quá dài cũng chính là một dạng "naive inclusion" thu nhỏ. - Cache lại repo map đã sinh (ví dụ lưu vào
.repo-map-cache.json) và chỉ regenerate phần thay đổi (dùnggit diff --name-onlyđể biết file nào vừa sửa) — đỡ tốn thời gian và CPU cho repo lớn. - Khi dùng tree-sitter, ưu tiên trích luôn function signature và docstring dòng đầu (nếu có) — chỉ tốn thêm vài chục token mỗi symbol nhưng tăng đáng kể độ hữu ích của bản đồ.
File Summary: Mô Tả File Mà Không Cần Hiển Thị Nó
Repo map cho biết "file này có gì" ở mức symbol. Nhưng đôi khi bạn cần một tầng thông tin cao hơn nữa: "file này TỒN TẠI ĐỂ LÀM GÌ" — điều mà danh sách tên hàm không luôn nói rõ. Đây là lúc file summary (tóm tắt file) phát huy tác dụng.
File summary là một đoạn mô tả ngôn ngữ tự nhiên, ngắn (thường 1-3 câu) cho mỗi file hoặc module, trả lời ba câu hỏi:
- Mục đích: file này giải quyết vấn đề gì, thuộc luồng nghiệp vụ nào?
- Export/interface chính: những gì file này "cho bên ngoài dùng" — không cần liệt kê hết, chỉ cần cái quan trọng nhất.
- Phụ thuộc đáng chú ý: file này dựa vào service/module nào, hoặc bị dùng bởi ai (nếu biết).
Ví dụ một file order_summaries.json cho một service thương mại điện tử:
{
"src/services/order_service.py": {
"purpose": "Xử lý toàn bộ vòng đời đơn hàng: tạo, hủy, cập nhật trạng thái. Là entrypoint chính cho mọi thao tác liên quan đến order.",
"exports": ["OrderService.create_order", "OrderService.cancel_order"],
"depends_on": ["payment_service.py", "inventory_service.py", "models/order.py"],
"used_by": ["api/orders.py", "workers/fulfillment_worker.py"]
},
"src/services/payment_service.py": {
"purpose": "Wrapper quanh Stripe API, xử lý charge, refund, và webhook xác nhận thanh toán.",
"exports": ["PaymentService.charge", "PaymentService.refund"],
"depends_on": ["config/stripe_client.py"],
"used_by": ["order_service.py"]
}
}
Với format này, AI agent có thể tải TOÀN BỘ index của một service 300 file chỉ với vài nghìn token, nhưng vẫn nắm được luồng nghiệp vụ ở mức "ai gọi ai, ai phụ thuộc ai" — thông tin mà repo map thuần symbol không diễn đạt tốt (repo map cho biết "có hàm charge()", nhưng không nói "hàm này wrap Stripe và được order_service gọi khi thanh toán").
Cách tạo và duy trì file summary:
- Viết tay: hiệu quả nhất cho các file/module lõi, quan trọng nhất trong hệ thống (10-20 file "trái tim" của mỗi service). Đầu tư viết tay ở đây rất đáng vì đây là những file AI sẽ cần hiểu đúng ngữ cảnh nhất.
- Sinh bằng AI theo lô (batch summarization): với hàng trăm file còn lại, dùng chính AI để đọc từng file và viết summary, chạy theo batch qua toàn repo. Ví dụ prompt cho một script tự động:
import anthropic, json, os
client = anthropic.Anthropic()
def summarize_file(filepath, code):
prompt = f"""Read this file and write a JSON summary with exactly these
keys: "purpose" (1-2 sentences, what this file does and why it exists),
"exports" (list of the 3-5 most important public functions/classes),
"depends_on" (list of internal modules it imports, not stdlib/3rd-party).
Be concise. File: {filepath}
{code}
```"""
resp = client.messages.create(
model="claude-sonnet-5",
max_tokens=400,
messages=[{"role": "user", "content": prompt}],
)
return json.loads(resp.content[0].text)
summaries = {}
for dirpath, _, files in os.walk("src"):
for fname in files:
if fname.endswith(".py"):
fpath = os.path.join(dirpath, fname)
with open(fpath) as f:
code = f.read()
if len(code) > 200: # skip trivial/empty files
summaries[fpath] = summarize_file(fpath, code)
with open("file-summaries.json", "w") as f:
json.dump(summaries, f, indent=2, ensure_ascii=False)
Cách này tốn một lần chi phí AI call cho mỗi file (có thể vài trăm đến vài nghìn file, tùy repo), nhưng chi phí đó CHỈ trả một lần khi tạo (và mỗi lần regenerate sau này), còn lợi ích tái sử dụng ở mọi session làm việc tiếp theo — đổi 1 lần tốn kém thành N lần tiết kiệm.
### Mẹo
- Với QA engineer và product manager: file summary nên có thêm field như `"business_flow"` — mô tả file này thuộc luồng nghiệp vụ nào bằng ngôn ngữ non-technical (ví dụ "thuộc luồng thanh toán khi khách checkout"), giúp các persona không rành code vẫn tra được đúng chỗ cần hỏi AI.
- Đừng để AI tự sinh summary rồi tin tuyệt đối — luôn review nhanh (spot-check) 10-15% các summary được sinh tự động, đặc biệt với file logic phức tạp, vì AI có thể tóm tắt sai ý đồ thiết kế ban đầu nếu code không có comment giải thích.
- Giữ summary NGẮN. Nếu một summary dài quá 3-4 câu, đó là dấu hiệu file đang làm quá nhiều việc (vi phạm single responsibility) — coi đây như một tín hiệu phụ để phát hiện code cần refactor.
## Các Lớp Ngữ Cảnh Cấu Trúc: Kết Hợp Bản Đồ và Tóm Tắt
Repo map và file summary mạnh nhất khi dùng CÙNG NHAU theo mô hình phân lớp (layered context), không phải chọn một trong hai. Hãy nghĩ về ngữ cảnh cấu trúc như một kim tự tháp gồm 4 lớp, từ rộng-nhẹ đến hẹp-nặng:
Lớp 1: Cây thư mục (directory tree)
→ cho biết codebase được tổ chức thế nào, module nào thuộc domain nào
→ vài trăm token cho cả repo
Lớp 2: Symbol map (repo map)
→ cho biết mỗi file có class/hàm gì, không có logic bên trong
→ vài nghìn token cho cả repo
Lớp 3: File summary
→ cho biết MỤC ĐÍCH và quan hệ phụ thuộc của từng file
→ tương đương lớp 2 về chi phí, bổ sung chiều ngữ nghĩa
Lớp 4: Full file content — CHỈ cho file đang thực sự sửa
→ toàn bộ code thật, cần cho việc viết/sửa chính xác
→ vài trăm đến vài nghìn token MỖI file, chỉ nạp khi cần
Chiến lược thực tế: với một task cụ thể (ví dụ "sửa lỗi tính phí vận chuyển sai khi có coupon"), bạn nạp Lớp 1-3 cho TOÀN BỘ hoặc phần liên quan của repo (chi phí thấp, cho AI cái nhìn toàn cảnh để tự quyết định cần xem file nào), rồi chỉ nạp Lớp 4 — full content — cho đúng 2-3 file mà cả AI và bạn xác định là trung tâm của bug (ví dụ `shipping_calculator.py` và `coupon_service.py`).
Cách làm này khác căn bản với "nhồi hết mọi thứ" (naive inclusion) ở điểm: bạn không đoán trước file nào cần đọc kỹ rồi nhồi nguyên si — bạn cho AI đủ ngữ cảnh cấu trúc để nó (hoặc bạn) TỰ QUYẾT ĐỊNH file nào cần đọc sâu, sau đó mới trả tiền token cho đúng những file đó. Đây chính là nguyên tắc "progressive disclosure" (bộc lộ thông tin dần dần theo nhu cầu) áp dụng vào context engineering.
Một cách hình dung khác: Lớp 1-3 đóng vai trò như "chỉ mục" (index) của một cuốn sách tra cứu — bạn không đọc hết sách, bạn dùng chỉ mục để nhảy thẳng đến đúng trang, rồi mới đọc kỹ trang đó (Lớp 4).
Với agentic tool có khả năng tự đọc file (Claude Code, Cursor agent mode, Aider), bạn thậm chí không cần tự quyết định Lớp 4 — bạn chỉ cần đảm bảo Lớp 1-3 đủ tốt, AI sẽ tự dùng công cụ đọc file (`Read`, `grep`, v.v.) để tự nạp Lớp 4 đúng lúc cần, mà không tốn token cho những file nó không đụng tới.
### Mẹo
- Đo thử tỷ lệ token giữa 4 lớp trên chính repo của bạn — nếu Lớp 2+3 đã chiếm hơn 15-20% ngân sách context, bản đồ/summary của bạn đang quá chi tiết, cần rút gọn.
- Với task sửa bug nhỏ, đôi khi chỉ cần Lớp 1 + Lớp 4 (bỏ qua Lớp 2-3) nếu bạn đã biết chính xác file cần sửa — đừng áp dụng máy móc đủ 4 lớp cho mọi task.
- Luôn để lớp cao hơn (full file) "thắng" nếu có mâu thuẫn với lớp thấp (summary) — summary có thể lỗi thời, code thật luôn là nguồn sự thật (source of truth).
## Ngữ Cảnh Cấu Trúc Trong Các Công Cụ AI Cụ Thể
Mỗi công cụ AI coding phổ biến hiện nay có cách riêng để "tự động hóa" hoặc hỗ trợ bạn xây các lớp ngữ cảnh cấu trúc nói trên. Hiểu cơ chế của từng tool giúp bạn tận dụng đúng, tránh làm lại thủ công những gì tool đã có sẵn.
**Aider**: như đã nói ở phần trước, Aider tự sinh repo map bằng tree-sitter + ranking kiểu PageRank, hoàn toàn tự động, không cần bạn cấu hình. Bạn có thể chủ động thêm file vào "chat context" (`/add file.py`) để nâng file đó lên Lớp 4, còn phần còn lại của repo tự động ở Lớp 2 (repo map). File `.aiderignore` giúp loại các file/thư mục không nên xuất hiện cả ở Lớp 1 lẫn Lớp 2 (build artifacts, file generated, v.v.).
**Cursor**: Cursor có cơ chế index toàn bộ codebase dùng embedding (biểu diễn vector ngữ nghĩa của code) để hỗ trợ semantic search (@codebase), đây là một dạng "ngữ cảnh cấu trúc" khác — không phải symbol map tường minh, mà là index có thể truy vấn theo ngữ nghĩa. Bạn có thể bổ sung ngữ cảnh cấu trúc tường minh hơn qua file `.cursorrules` hoặc thư mục `.cursor/rules/` — nơi bạn có thể viết một mục "Codebase Map" tóm tắt các module chính, giúp Cursor không phải dựa hoàn toàn vào semantic search (vốn đôi khi trả về kết quả không chính xác với truy vấn mơ hồ).
**Claude Code / CLAUDE.md**: đây là nơi bạn có toàn quyền kiểm soát ngữ cảnh cấu trúc nhất, vì `CLAUDE.md` được nạp vào đầu mọi session và hoàn toàn do bạn viết tay (hoặc sinh tự động). Một mẫu section "Codebase Summary" hiệu quả:
```markdown
## Codebase Summary
### Stack
- Backend: Python 3.12, FastAPI, PostgreSQL, Redis (cache + queue)
- Frontend: React + TypeScript, Vite, TanStack Query
- Infra: Deployed on AWS ECS, CI/CD via GitHub Actions
### Key Directories
- `src/services/` — business logic, one file per domain (order, payment, inventory)
- `src/api/` — FastAPI route handlers, thin — delegate to services, no logic here
- `src/models/` — SQLAlchemy ORM models, source of truth for DB schema
- `src/workers/` — async background jobs (Celery), triggered by queue events
- `tests/` — mirrors src/ structure 1:1, one test file per source file
### Critical Patterns
- All money amounts are stored as integer cents, never float — see `src/utils/money.py`
- Every service method that writes to DB must be wrapped in `@transactional`
decorator (src/utils/db.py) — do not use raw session.commit() in services
- Payment operations MUST go through `PaymentService`, never call Stripe SDK directly
Section này chỉ tốn vài trăm token nhưng cho Claude Code toàn bộ "la bàn" cần để định hướng — không cần AI tự dò tìm cấu trúc từ đầu ở mỗi session, vì thông tin này đã có sẵn trong context ban đầu. Chi tiết đầy đủ hơn về cách viết CLAUDE.md sẽ được bàn kỹ ở bài sau trong module này.
GitHub Copilot: Copilot Chat hỗ trợ file .github/copilot-instructions.md (tương tự vai trò CLAUDE.md) để cung cấp ngữ cảnh persistent. Ngoài ra Copilot có cơ chế lấy ngữ cảnh từ các file đang mở trong workspace và từ codebase index (với Copilot Workspace) — nhưng mức độ kiểm soát tường minh về "bản đồ cấu trúc" kém linh hoạt hơn so với việc tự viết CLAUDE.md hay .cursorrules, nên nếu team dùng Copilot là chính, càng nên đầu tư viết copilot-instructions.md chi tiết để bù đắp.
Mẹo
- Nếu team dùng nhiều tool AI khác nhau (một số dev dùng Cursor, số khác dùng Claude Code), cân nhắc giữ MỘT nguồn sự thật cho ngữ cảnh cấu trúc (ví dụ luôn viết đầy đủ trong CLAUDE.md), rồi để các file cấu hình khác (
.cursorrules,copilot-instructions.md) tham chiếu hoặc trích ngắn từ đó — tránh bảo trì trùng lặp nhiều nơi cùng lúc. - Với Cursor, đừng chỉ dựa vào semantic search tự động — luôn bổ sung một mục liệt kê rõ các module chính trong
.cursorrules, vì semantic search có thể bỏ lỡ file liên quan nếu tên biến/hàm không khớp ngữ nghĩa với câu hỏi. - Test thử "độ hiểu" của từng tool bằng cách hỏi nó mô tả lại kiến trúc hệ thống mà không cho xem code — nếu câu trả lời sai lệch nhiều, đó là dấu hiệu ngữ cảnh cấu trúc bạn cung cấp chưa đủ hoặc đã lỗi thời.
Duy Trì Ngữ Cảnh Cấu Trúc Theo Thời Gian
Vấn đề lớn nhất của mọi loại ngữ cảnh "được sinh sẵn" — repo map, file summary, CLAUDE.md — là chúng dễ trở nên lỗi thời (stale) khi code thay đổi mà bản đồ không được cập nhật theo. Hậu quả của staleness (tình trạng lỗi thời) còi hơn bạn nghĩ: AI không chỉ "thiếu thông tin", nó còn tự tin dựa vào thông tin SAI — ví dụ summary nói file A gọi service B, nhưng thực tế code đã refactor để gọi service C từ 3 tháng trước — dẫn AI đến những đề xuất sửa sai chỗ, sai giả định về luồng dữ liệu.
Có ba tầng chiến lược bảo trì, nên áp dụng kết hợp:
1. Regenerate tự động qua pre-commit hook — cho các phần có thể sinh hoàn toàn tự động (repo map dạng symbol, cây thư mục):
#!/bin/bash
CHANGED=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(py|ts|tsx)$')
if [ -n "$CHANGED" ]; then
echo "Regenerating repo map..."
python scripts/generate_repo_map.py src/ > repo-map.txt
git add repo-map.txt
fi
Cách này đảm bảo repo map KHÔNG BAO GIỜ lệch quá 1 commit so với code thật — chi phí gần như bằng 0 vì việc sinh symbol map chạy rất nhanh (thường dưới 1-2 giây cho repo cỡ vừa).
2. Regenerate theo lịch qua CI job — phù hợp cho phần cần AI call (file summary, vì gọi API tốn thời gian/tiền, không nên chạy ở mỗi commit):
name: Regenerate File Summaries
on:
schedule:
- cron: '0 3 * * 1' # Chạy 3h sáng thứ Hai mỗi tuần
workflow_dispatch: # Cho phép trigger tay khi cần
jobs:
regenerate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install deps
run: pip install anthropic
- name: Regenerate summaries for changed files
run: python scripts/batch_summarize.py --only-changed --since="7 days ago"
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- name: Open PR if summaries changed
uses: peter-evans/create-pull-request@v6
with:
commit-message: "chore: regenerate file summaries"
title: "Weekly file summary refresh"
branch: auto/refresh-summaries
Điểm quan trọng: script batch_summarize.py nên có cờ --only-changed để chỉ re-summarize những file thực sự thay đổi (dựa vào git diff hoặc so sánh hash nội dung file với lần chạy trước) — tránh gọi AI lại cho toàn bộ repo mỗi lần, vừa tốn tiền vừa tốn thời gian không cần thiết.
3. Review định kỳ thủ công — cho các phần mang tính "chiến lược/kiến trúc" mà máy không tự đánh giá được, đặc biệt là section "Critical Patterns" trong CLAUDE.md. Nên đưa vào quy trình sprint/retro định kỳ (ví dụ mỗi 2-4 sprint) một checklist ngắn: "CLAUDE.md có còn phản ánh đúng pattern hiện tại của team không? Có convention mới nào cần thêm vào không?" Đây là việc mang tính con người, không nên cố tự động hóa 100%, vì đòi hỏi đánh giá về "cái gì đáng ghi vào ngữ cảnh persistent", không chỉ là phát hiện thay đổi cơ học.
Một chỉ báo hữu ích để biết khi nào ngữ cảnh cấu trúc đang lỗi thời nghiêm trọng: theo dõi số lần AI đề xuất giải pháp dựa trên một pattern/API đã bị deprecate hoặc gọi sai tên hàm đã đổi tên — nếu tần suất này tăng lên, đó là dấu hiệu rõ ràng cần regenerate ngay, không chờ đến lịch định kỳ.
Mẹo
- Gắn timestamp vào mỗi file summary/repo map khi sinh (
"generated_at": "2026-07-09") — giúp cả người và AI biết ngay thông tin này có thể đã cũ đến mức nào, tránh tin tưởng mù quáng vào dữ liệu quá cũ. - Ưu tiên tự động hóa phần "cấu trúc" (symbol, cây thư mục — rẻ, máy làm tốt) và giữ review thủ công cho phần "ngữ nghĩa/chiến lược" (pattern, convention, business logic quan trọng — cần đánh giá của người).
- Nếu team lớn và nhiều người cùng sửa CLAUDE.md, coi file này như code — bắt buộc qua PR review, đừng để một người tự tiện chỉnh sửa mà không ai kiểm tra, vì thông tin sai trong CLAUDE.md ảnh hưởng đến MỌI session AI của TOÀN team.
Những Điểm Chính
- Repo map là bản đồ cấu trúc gồm cây thư mục + symbol (class/hàm/export) mà không có nội dung logic bên trong, giúp AI định hướng "nên xem file nào" với chi phí token rất thấp so với đọc nguyên file.
- Có thể sinh repo map bằng công cụ có sẵn (tính năng repo-map của Aider), bằng
ctags/universal-ctags, bằngtree-sitter(chính xác nhất, hỗ trợ đa ngôn ngữ), hoặc bằng script tự chế kết hợptree+grepcho nhu cầu nhanh gọn. - File summary bổ sung tầng ngữ nghĩa mà symbol map thiếu: mục đích của file, export chính, quan hệ phụ thuộc — có thể viết tay cho file lõi quan trọng và sinh tự động bằng AI theo batch cho phần còn lại.
- Chiến lược hiệu quả nhất là phân lớp: cây thư mục → symbol map → file summary → full file content (chỉ cho file đang thực sự sửa) — cho AI đủ ngữ cảnh để tự quyết định cần đọc sâu ở đâu, thay vì đoán trước và nhồi hết.
- Mỗi công cụ AI (Aider, Cursor, Claude Code, Copilot) có cơ chế riêng để tận dụng ngữ cảnh cấu trúc — hiểu đúng cơ chế của tool bạn dùng giúp tránh làm lại thủ công cái tool đã tự động hóa sẵn.
- Ngữ cảnh cấu trúc (repo map, file summary, CLAUDE.md) dễ lỗi thời và gây hại nhiều hơn không có gì nếu AI tin vào thông tin sai — cần chiến lược bảo trì kết hợp: tự động qua pre-commit hook (cho phần rẻ, sinh nhanh), CI job theo lịch (cho phần cần AI call, tốn chi phí), và review thủ công định kỳ (cho phần chiến lược/kiến trúc).