Nếu bạn đã từng dùng AI agent để code, chắc hẳn bạn đã gặp cảnh này: mở một session mới, agent hỏi "dự án dùng framework gì?", rồi bạn phải giải thích lại stack, quy ước đặt tên, cách xử lý lỗi, cấu trúc thư mục... như thể agent chưa từng làm việc với bạn bao giờ. Vì thực tế là nó chưa — mỗi session (phiên làm việc) mới là một trang trắng, không có ký ức từ lần trước.
Đây chính là lúc các file context (ngữ cảnh) bền vững như CLAUDE.md, .cursorrules, hay system prompt (chỉ dẫn hệ thống) tùy biến phát huy giá trị. Chúng đóng vai trò như một "hồ sơ dự án" được nạp một lần khi agent khởi động, thay vì phải nhồi lại thông tin đó vào từng prompt. Bài viết này sẽ đi sâu vào cách viết, cấu trúc và duy trì các file này sao cho vừa tiết kiệm token, vừa giúp agent làm việc chính xác hơn — cho mọi persona (vai trò) trong team, từ dev, QA đến product manager.
Vai Trò Của File Context Bền Vững Trong Tối Ưu Token
Tại sao "nạp một lần" rẻ hơn "giải thích lại mỗi lần"
Hãy tưởng tượng team bạn có 10 kỹ sư, mỗi người chạy trung bình 15 prompt/ngày với AI coding agent. Nếu mỗi prompt đều phải kèm theo đoạn giải thích "dự án này dùng NestJS, database là PostgreSQL với Prisma, error handling theo pattern Result
Với các công cụ như Claude Code, Cursor, hay các agent hỗ trợ system prompt tùy biến, file context được đọc và đưa vào context window (cửa sổ ngữ cảnh) ngay từ đầu session, rồi ở đó xuyên suốt cuộc trò chuyện. Về bản chất, đây giống cơ chế prompt caching (đệm prompt) — phần nội dung tĩnh, ít thay đổi được xử lý một lần và tái sử dụng, chi phí biên (marginal cost) cho các turn sau gần như bằng không so với việc lặp lại toàn bộ mỗi lần.
Nhưng lợi ích không chỉ nằm ở số token tiết kiệm được. Một file context tốt còn giảm số lần agent phải tự dò tìm (explore) codebase để suy ra quy ước — điều này vốn tốn rất nhiều lượt gọi tool (đọc file, grep, list directory), mỗi lượt lại cộng thêm token và thời gian. Nói cách khác, file context không chỉ nén thông tin mà còn ngăn agent tạo ra chi phí phát sinh từ việc "dò đường" trong bóng tối.
Nên đưa gì vào, nên để gì ở ngoài
Đây là phần dễ làm sai nhất. Nhiều người có tâm lý "viết CLAUDE.md thật dài, thật chi tiết để AI hiểu hết dự án" — nhưng file context không phải là tài liệu kỹ thuật đầy đủ (comprehensive technical doc). Nó là bản tóm tắt định hướng (orientation summary). Vì file này được nạp vào mọi session, mọi prompt đều phải "trả phí" cho toàn bộ nội dung trong đó — kể cả khi task hiện tại chẳng liên quan gì đến 80% nội dung.
Nguyên tắc chọn lọc:
- Nên đưa vào: thông tin ổn định, áp dụng cho mọi task — stack công nghệ, kiến trúc tổng quan, coding convention, quy tắc test, các pattern lặp lại thường xuyên, danh sách file/thư mục quan trọng để định hướng, và những điều cấm/anti-pattern cần tránh.
- Nên để ngoài: chi tiết triển khai của một feature cụ thể, nội dung API docs đầy đủ, changelog dài dòng, hay bất kỳ thông tin chỉ liên quan đến một task hẹp. Những thứ này nên nằm trong file riêng, được agent đọc theo yêu cầu (on-demand) qua targeted file inclusion (nạp file có mục tiêu) — chủ đề đã đề cập ở phần trước của module này — chứ không nạp sẵn cho mọi request.
Một cách kiểm tra nhanh: nếu thông tin đó chỉ hữu ích cho dưới 20% các task, nó không nên nằm trong file context toàn cục.
Mẹo
Định kỳ ước tính số token của file context bằng công cụ đếm token (hoặc quy tắc ước lượng ~4 ký tự/token với tiếng Anh). Nếu file vượt quá 1500-2000 token, đó là dấu hiệu cần tách phần chi tiết ra file phụ và chỉ giữ lại phần cốt lõi, dẫn link tới file phụ khi cần.
Viết File CLAUDE.md Hiệu Quả
CLAUDE.md là file context được Claude Code (và ngày càng nhiều agent khác) tự động đọc khi khởi động trong một repo. Đây là nơi bạn "dạy" agent về dự án của mình một cách có cấu trúc. Một file CLAUDE.md tốt thường có các phần sau:
1. Stack công nghệ (Tech Stack) — Liệt kê ngắn gọn ngôn ngữ, framework, database, các thư viện quan trọng. Không cần giải thích tại sao chọn chúng, chỉ cần agent biết để không đề xuất sai công nghệ (ví dụ gợi ý dùng Redux trong khi team đang dùng Zustand).
2. Kiến trúc (Architecture) — Sơ đồ tổ chức thư mục, ranh giới giữa các module/service, luồng dữ liệu chính. Có thể vẽ bằng cây thư mục ASCII đơn giản kèm chú giải một dòng cho mỗi thư mục.
3. Coding Standards (Quy chuẩn code) — Quy ước đặt tên, style guide, cách tổ chức import, quy tắc về comment, có dùng TypeScript strict mode không, v.v.
4. Testing Requirements (Yêu cầu kiểm thử) — Framework test dùng gì, coverage tối thiểu, có bắt buộc viết test trước khi merge không, cách chạy test.
5. Common Patterns (Các pattern thường gặp) — Đây là phần giá trị nhất nhưng hay bị bỏ qua. Hãy đưa vào 2-3 ví dụ code ngắn cho các tình huống lặp lại nhiều nhất: tạo module mới, xử lý lỗi, transaction database. Agent học theo pattern cụ thể nhanh hơn nhiều so với mô tả bằng lời.
6. What NOT to Do (Những điều không nên làm) — Danh sách rõ ràng các anti-pattern, thư viện đã deprecated, cách làm cũ không còn dùng nữa. Phần này cực kỳ quan trọng vì agent (đặc biệt khi được train trên dữ liệu công khai) rất dễ đề xuất pattern "phổ biến" nhưng lại sai với quy ước riêng của team bạn.
7. Current Work in Progress (Công việc đang thực hiện) — Cập nhật ngắn về nhánh/feature đang phát triển, để agent không đề xuất giải pháp xung đột với công việc đang dở dang.
8. Key Files for Orientation (File chính để định hướng) — Danh sách 5-10 file "cửa ngõ" (entry point) quan trọng nhất, giúp agent biết bắt đầu đọc từ đâu khi cần hiểu sâu hơn, thay vì quét toàn bộ repo.
Dưới đây là ví dụ minh họa cho một dự án "Payment Processing API":
## Stack
- Node.js 20, TypeScript (strict mode)
- Fastify (not Express — do not suggest Express patterns)
- PostgreSQL via Prisma ORM
- Redis for idempotency key caching
- Jest + Supertest for testing
## Architecture
src/
routes/ # Fastify route handlers, thin — delegate to services
services/ # Business logic, one file per domain concept
repositories/ # DB access only, no business logic here
webhooks/ # Inbound webhook handlers from payment providers
jobs/ # Background jobs (BullMQ)
Money always flows: route -> service -> repository. Never call
Prisma directly from a route handler.
## Coding Standards
- All monetary values stored as integer cents, never floats.
- Every public service function must have a JSDoc block with @throws.
- Imports ordered: node builtins, external packages, internal modules.
## Testing Requirements
- Every new endpoint needs an integration test in tests/integration/.
- Payment state transitions must have a unit test per transition.
- Run `npm run test:unit` before every commit; CI blocks merge below 85% coverage.
## Common Patterns
### Error handling
Use the Result type, never throw across service boundaries:
```ts
async function chargeCard(input: ChargeInput): Promise<Result<Charge, ChargeError>> {
if (input.amountCents <= 0) {
return err({ code: "INVALID_AMOUNT" });
}
// ...
return ok(charge);
}
Database transactions
Always wrap multi-table writes in prisma.$transaction:
await prisma.$transaction(async (tx) => {
const charge = await tx.charge.create({ data: chargeInput });
await tx.ledgerEntry.create({ data: toLedgerEntry(charge) });
});
What NOT to Do
- Do not use
parseFloaton monetary strings — precision bugs. - Do not call external payment provider SDKs directly from route handlers.
- Legacy
services/legacyRefund.tsis deprecated, do not extend it.
Current Work in Progress
- Migrating refund flow to the new
RefundOrchestrator(branchfeat/refund-v2).
Key Files for Orientation
src/services/PaymentService.ts— core charge/refund logicsrc/webhooks/stripeWebhook.ts— reconciliation entry pointprisma/schema.prisma— data model source of truth
Chú ý: ví dụ trên không quá 400 từ nhưng đã truyền tải được convention, pattern, và điều cấm — đủ để agent tránh những lỗi phổ biến nhất mà không cần agent tự dò dẫm.
### Mẹo
Viết CLAUDE.md theo hướng "điều gì làm agent mắc lỗi nhiều nhất trong quá khứ" — mỗi lần bạn phải sửa lại code do AI viết vì sai convention, hãy hỏi "lẽ ra điều này nên được ghi vào CLAUDE.md từ đầu không?" và bổ sung ngay.
## Viết .cursorrules Cho Cursor IDE
Cursor có cơ chế tương tự nhưng dùng file `.cursorrules` (hoặc thư mục `.cursor/rules/` với các file `.mdc` cho phiên bản mới hơn, hỗ trợ rule theo glob pattern áp dụng cho từng loại file). File này được nạp vào system prompt của Cursor cho mọi lần chat hoặc mọi lần gợi ý inline, nên cùng nguyên tắc "súc tích, ổn định, đúng đối tượng" như CLAUDE.md vẫn áp dụng.
Điểm khác biệt quan trọng: `.cursorrules` thường được viết theo persona hoặc theo layer của codebase nhiều hơn, vì Cursor được dùng chủ yếu cho việc viết code inline (autocomplete, inline edit) — nên rule cần rất cụ thể, hành động được (actionable), không lý thuyết.
Ví dụ cho một **frontend engineer** làm việc trên dự án React/TypeScript:
Components
- Function components only, no class components.
- One component per file, filename matches component name (PascalCase).
- Props interface named
<ComponentName>Props, defined above the component.
State
- Local UI state: useState. Cross-component state: Zustand store in src/stores/.
- Do not introduce Redux, Context API for global state is discouraged in this codebase.
- Server data: always through React Query (useQuery/useMutation), never useEffect + fetch.
Styling
- Tailwind CSS utility classes only. No inline style objects except for dynamic values.
- Shared design tokens live in tailwind.config.js — do not hardcode hex colors.
API calls
- All API calls go through src/api/client.ts, never call fetch() directly in components.
- API response types are generated from OpenAPI spec in src/api/generated/ — do not hand-write them.
Tests
- Every component gets a test in the same folder,
<Component>.test.tsx. - Use React Testing Library, query by role/label, not by test-id unless unavoidable.
Anti-patterns to avoid
- Do not use
anytype — useunknownand narrow, or define a proper type. - Do not mutate props or state directly.
- Avoid useEffect for derived state — compute directly in render or use useMemo.
Và ví dụ cho một **QA engineer** dùng Cursor để viết test tự động và test case:
Testing philosophy
- Prefer black-box, user-facing assertions over implementation detail checks.
- Every bug fix PR must include a regression test reproducing the original bug.
Playwright test rules
- Page objects live in tests/e2e/pages/, one class per page/major flow.
- Use data-testid selectors only when there is no accessible role/label available.
- Every test must be independent — no shared mutable state between test files.
- Use
test.step()to break long flows into readable, reportable steps.
Manual test case rules
- Test cases follow Given/When/Then format in the shared test-case doc.
- Every new feature needs at least: 1 happy path, 1 boundary case, 1 negative case.
- Reference the Jira ticket ID in the test case title.
What to avoid
- Do not write tests that depend on execution order.
- Do not use hard-coded
sleep()/waitForTimeout()— use Playwright's auto-waiting
or explicit condition waits instead. - Do not test third-party library internals — only test our integration with them.
Hai ví dụ trên cho thấy cùng một cơ chế (.cursorrules) nhưng nội dung hoàn toàn khác nhau tùy theo ai đang dùng nó — điều này dẫn tới chủ đề cấu trúc theo persona ở phần sau.
### Mẹo
Với Cursor, ưu tiên dùng cấu trúc `.cursor/rules/*.mdc` có `globs` để rule chỉ áp dụng cho đúng loại file (ví dụ rule Playwright chỉ áp dụng cho file trong `tests/e2e/**`) — điều này giúp mỗi rule chỉ được nạp khi thực sự liên quan, tiết kiệm token hơn một file `.cursorrules` khổng lồ áp dụng cho tất cả.
## System Prompt và Custom Instructions Trong Các Công Cụ Khác
`CLAUDE.md` và `.cursorrules` không phải là duy nhất — hầu hết công cụ AI coding hiện đại đều có một cơ chế tương đương, chỉ khác tên gọi và format.
**GitHub Copilot** hỗ trợ custom instructions (chỉ dẫn tùy biến) qua file `.github/copilot-instructions.md` ở root repo. Copilot Chat và các gợi ý inline sẽ tự động đọc file này để hiểu convention của dự án:
```markdown
<!-- .github/copilot-instructions.md -->
This is a Django monolith. When generating code:
- Use Django ORM, not raw SQL, unless performance-critical and reviewed.
- All views must have permission_classes explicitly set.
- Follow PEP 8; run `black` formatting conventions in mind.
- Prefer class-based views for CRUD, function-based views for custom actions.
- Tests use pytest-django, fixtures live in tests/conftest.py.
Copilot còn hỗ trợ instructions theo path cụ thể (.github/instructions/*.instructions.md với applyTo glob), tương tự tinh thần rule theo glob của Cursor.
aider (công cụ AI pair-programming chạy CLI, phổ biến với dev thích làm việc qua terminal) dùng file cấu hình .aider.conf.yml để set các chỉ dẫn mặc định, cùng với file CONVENTIONS.md mà bạn có thể chỉ định nạp tự động qua flag --read:
read: CONVENTIONS.md
auto-test: true
test-cmd: "npm run test:unit"
lint-cmd: "npm run lint"
map-tokens: 2048
Ở đây, map-tokens kiểm soát ngân sách token dành cho repo map (bản đồ repo — đã đề cập ở bài trước của module này), còn read: CONVENTIONS.md đảm bảo file quy ước luôn được nạp vào context mỗi session mà không cần bạn gõ lại đường dẫn.
Ngoài ra, .aiderignore (tương tự .gitignore) giúp loại các thư mục như node_modules/, dist/, file build ra khỏi phạm vi mà aider quét — tránh lãng phí token vào việc index những file không cần thiết.
Các IDE/agent khác (Windsurf với .windsurfrules, JetBrains AI Assistant với custom prompts, các agent tự viết dựa trên OpenAI Assistants API hay Claude API) đều có biến thể của cùng ý tưởng: một khối text tĩnh, nạp một lần, định hình hành vi cho toàn bộ session.
Mẹo
Nếu team dùng nhiều công cụ AI khác nhau (một số dev dùng Cursor, số khác dùng Copilot hay Claude Code), hãy giữ một file "nguồn sự thật" duy nhất (ví dụ CLAUDE.md) rồi dùng symlink hoặc script nhỏ để đồng bộ nội dung sang .cursorrules, .github/copilot-instructions.md, tránh tình trạng mỗi tool có convention lệch nhau.
Cấu Trúc File Context Cho Các Persona Khác Nhau Trong Team
Một sai lầm phổ biến là nghĩ file context chỉ dành cho lập trình viên. Nhưng PM (product manager), QA, hay business analyst (chuyên viên phân tích nghiệp vụ) cũng ngày càng dùng AI agent để hỏi về codebase — "feature X nằm ở module nào?", "metric retention được tính thế nào trong code?", "PR này ảnh hưởng đến luồng thanh toán không?". Nếu họ dùng chung một CLAUDE.md đầy chi tiết coding standard, phần lớn nội dung đó vô nghĩa với họ và ngược lại, thứ họ cần thì file đó không có.
PM cần một loại context hoàn toàn khác:
- Domain glossary (từ điển thuật ngữ nghiệp vụ): các khái niệm nghiệp vụ được dùng trong code bằng tên khác — ví dụ "churn" trong business tương ứng với field
subscription_status = 'cancelled'trong database. - Feature-to-module mapping (bản đồ tính năng sang module): feature "Giỏ hàng" nằm ở đâu trong code, feature "Đăng ký dùng thử" liên quan tới những service nào.
- Cách đọc code reference: hướng dẫn PM hiểu một đường dẫn file hay số dòng agent trích dẫn có nghĩa là gì, không cần biết đọc code nhưng biết "đây là bằng chứng, có thể click vào xem".
- Định nghĩa metric: khi PM hỏi "tỷ lệ chuyển đổi được tính sao", agent cần biết công thức chính xác được code hóa ở đâu, tránh suy diễn sai.
Ví dụ một file "AI Context for Product Managers":
## Glossary
- "Active user" = a user with >=1 login event in the last 30 days
(see `analytics/definitions.py::is_active_user`).
- "Churn" = subscription.status transitions to `cancelled` (not `paused`).
- "Trial" = subscription.plan_id in TRIAL_PLAN_IDS (config/plans.yml).
## Feature-to-module map
- Shopping Cart -> src/modules/cart/
- Trial Signup -> src/modules/onboarding/trial/
- Referral Program -> src/modules/growth/referral/
## How to read code references
When the AI cites a file like `src/modules/cart/CartService.ts:42`, that
means line 42 of that file contains the relevant logic. You don't need to
understand the code — you can ask the AI to "explain line 42 in plain terms."
## Metric definitions
- Conversion rate = trials converted to paid / trials started, computed
in `analytics/queries/conversion.sql`, refreshed nightly.
- Do not confuse "MRR" (monthly recurring revenue, billing-based) with
"GMV" (gross merchandise value, order-based) — they use different tables.
## Questions this file is good for
- "Which module handles X feature?"
- "What does metric Y actually mean in our data?"
- "Where would I look to verify a claim about behavior Z?"
Về mặt tối ưu token, việc tách file context theo persona còn giúp mỗi session chỉ nạp phần liên quan — một PM hỏi về metric không cần tốn token cho toàn bộ coding standard, và ngược lại dev không cần tốn token cho glossary nghiệp vụ khi đang debug một lỗi kỹ thuật thuần túy. Nhiều team giải quyết việc này bằng cách có nhiều file (CLAUDE.md, CLAUDE.pm.md, CLAUDE.qa.md) và chọn nạp file tương ứng theo vai trò người dùng hoặc theo workspace/agent riêng.
Mẹo
Nếu công cụ bạn dùng cho phép nhiều "profile" hoặc nhiều workspace agent riêng biệt (ví dụ Claude Code cho phép nhiều CLAUDE.md ở các cấp thư mục, hoặc bạn dựng agent riêng cho từng persona), hãy tách file theo persona ngay từ đầu thay vì gộp chung rồi cố nhồi mọi thứ vào một file "chung cho tất cả".
Duy Trì File Context: Vòng Đời Và Sự Lỗi Thời
File context, giống mọi tài liệu, sẽ "mục nát" (rot) theo thời gian nếu không ai chăm sóc. Codebase đổi framework, đổi convention, refactor module — nhưng CLAUDE.md vẫn ghi thông tin từ 6 tháng trước. Hậu quả tệ hơn cả việc không có file context: agent tin tưởng tuyệt đối vào nội dung sai, tạo ra code theo pattern đã lỗi thời, và developer phải tốn công sửa lại — mất nhiều thời gian hơn cả việc không dùng file context ngay từ đầu.
Changelog ngay trong file
Cách đơn giản và hiệu quả nhất là thêm một header ghi lại lịch sử cập nhật ngay trong CLAUDE.md:
<!--
Maintenance Log
- 2026-07-09: Migrated ORM from TypeORM to Prisma, updated patterns. (@dat.hoang)
- 2026-05-02: Added refund orchestrator WIP note. (@team-payments)
- 2026-03-14: Initial version. (@dat.hoang)
Review cadence: review this file every sprint retro, or whenever a major
architectural decision merges to main.
-->
...
Header này không chỉ giúp con người biết file có được cập nhật gần đây không, mà còn là tín hiệu review nhanh: nếu lần cập nhật cuối cách đây 8 tháng trong khi codebase có hàng trăm commit, đó là cờ đỏ cần review ngay.
Checklist review định kỳ
Gợi ý một checklist ngắn để chạy mỗi sprint hoặc mỗi tháng:
- Stack/dependency liệt kê trong file còn khớp với
package.json/requirements.txthiện tại không? - Các code pattern ví dụ còn tồn tại trong codebase, hay đã bị refactor thay thế?
- Mục "Current Work in Progress" còn đúng, hay feature đó đã merge/hủy?
- Có anti-pattern mới nào cần bổ sung vào "What NOT to Do" (dựa trên các lần phải sửa code AI viết sai) không?
- "Key Files for Orientation" còn tồn tại đúng đường dẫn không (file có thể đã bị di chuyển/đổi tên)?
Script kiểm tra tự động trong CI
Ngoài review thủ công, có thể tự động hóa một phần việc phát hiện lỗi thời bằng cách so sánh thời điểm cập nhật gần nhất của file context với lịch sử commit của source code liên quan. Ý tưởng: nếu source code trong các thư mục được CLAUDE.md tham chiếu đã có nhiều commit đáng kể sau lần cập nhật CLAUDE.md gần nhất, cảnh báo team review lại.
#!/usr/bin/env bash
set -euo pipefail
CONTEXT_FILE="CLAUDE.md"
STALENESS_THRESHOLD_DAYS=60
last_context_commit_date=$(git log -1 --format=%ct -- "$CONTEXT_FILE")
now=$(date +%s)
context_age_days=$(( (now - last_context_commit_date) / 86400 ))
echo "CLAUDE.md last updated ${context_age_days} days ago."
if [ "$context_age_days" -gt "$STALENESS_THRESHOLD_DAYS" ]; then
# Count commits to src/ since CLAUDE.md was last touched
commits_since=$(git log --since="@${last_context_commit_date}" --oneline -- src/ | wc -l)
if [ "$commits_since" -gt 20 ]; then
echo "WARNING: CLAUDE.md is ${context_age_days} days old, but src/ has"
echo " ${commits_since} commits since then. Context file may be stale."
echo " Please review CLAUDE.md before merging further changes."
exit 1
fi
fi
echo "Context file freshness check passed."
Script này có thể được gắn vào một CI job chạy hàng tuần (không cần chặn mỗi PR, vì độ trễ vài ngày không nghiêm trọng), hoặc chạy như một cảnh báo (không block merge) để nhắc nhở team mà không gây cản trở tốc độ phát triển. Với repo lớn, có thể nâng cấp logic để kiểm tra riêng từng thư mục được tham chiếu trong "Key Files for Orientation" thay vì tính chung cho toàn bộ src/.
Mẹo
Gắn việc review CLAUDE.md như một tiêu chí nhỏ trong "definition of done" của các PR mang tính kiến trúc (đổi framework, đổi pattern chính) — người merge PR đó có trách nhiệm cập nhật luôn phần liên quan trong file context, tránh việc dồn hết vào một buổi "dọn dẹp context" lớn mà không ai có thời gian làm.