Đây là bài capstone của Module 2: bạn sẽ tự tay xây dựng một file CLAUDE.md hoàn chỉnh, cấp production, cho một codebase e-commerce API mẫu — từng dòng, từng section, kèm prompt thật để bạn copy-paste và chạy ngay trên dự án của mình.
Vì Sao AI Hiểu Sai Codebase Của Bạn (Khi Không Có Context)
Nếu bạn đã dùng Claude Code, Cursor, hay bất kỳ coding agent nào đủ lâu, bạn sẽ nhận ra một điều: AI viết code rất giỏi về mặt cú pháp, nhưng lại "đoán mò" rất nhiều về codebase cụ thể của bạn. Đây không phải lỗi của model — đó là hệ quả trực tiếp của việc thiếu context. LLM (large language model) không có ký ức giữa các session, không biết codebase của bạn tổ chức thế nào trừ khi nó tự đọc, và quan trọng hơn — nó không biết những quyết định "ngầm" mà team bạn đã thống nhất nhưng chưa bao giờ viết ra thành văn bản.
Tôi từng review hàng trăm PR được generate bởi AI trong các dự án production, và các failure mode luôn lặp lại theo một vài pattern quen thuộc.
Thứ nhất là bịa API không tồn tại (hallucination). Bạn yêu cầu AI "thêm endpoint lấy danh sách order theo user", và nó tự tin viết orderService.findByUserId() — trong khi hàm thực tế trong codebase của bạn tên là orderRepository.getOrdersForUser(). Model không cố tình sai; nó chỉ đang pattern-match dựa trên hàng triệu codebase khác nó từng thấy trong lúc training, và convention phổ biến nhất thắng, bất kể convention đó có khớp với dự án của bạn hay không.
Thứ hai là sai vị trí file. Một AI không có context về cấu trúc thư mục sẽ đặt một Prisma model mới vào src/models/, trong khi team bạn đã thống nhất từ lâu rằng schema chỉ sống trong prisma/schema.prisma và các file DTO (data transfer object — object trung gian dùng để truyền dữ liệu giữa các layer) nằm ở src/dtos/. Nhìn qua thì đây là lỗi nhỏ, nhưng nhân với hàng chục PR mỗi tuần, nó tạo ra một codebase rối loạn về cấu trúc.
Thứ ba, và có lẽ nguy hiểm nhất, là vi phạm convention một cách âm thầm. Ví dụ team bạn quy định mọi response lỗi phải đi qua AppError class để đảm bảo format nhất quán cho client, nhưng AI lại throw new Error('Product not found') trực tiếp — code vẫn chạy, test vẫn pass (nếu test không kiểm tra kỹ), nhưng bạn đã có một lỗ hổng nhỏ trong tính nhất quán của hệ thống mà không ai để ý cho đến khi client báo lỗi parse response.
Thứ tư là bỏ qua pattern có sẵn. Bạn đã có một BaseRepository class xử lý pagination, sorting, và soft-delete filtering cho toàn bộ project. Nhưng vì AI không biết class đó tồn tại (nó không tự động đọc toàn bộ codebase trước mỗi request, đặc biệt trong các dự án lớn với context window — cửa sổ ngữ cảnh, tức giới hạn lượng text mà model có thể "nhìn thấy" cùng lúc — có hạn), nó viết lại logic pagination y hệt nhưng theo cách khác, tạo ra hai đường xử lý song song cho cùng một việc.
Gốc rễ của tất cả các vấn đề này không phải là model "kém". Đó là vì kiến thức tribal (tribal knowledge — những hiểu biết ngầm chỉ tồn tại trong đầu các thành viên lâu năm của team, chưa từng được viết ra) không được đưa vào context. Một file context được viết tốt — thường đặt tên CLAUDE.md hoặc AGENTS.md — chính là cách bạn "xuất" kiến thức đó ra thành văn bản máy đọc được, biến AI từ một lập trình viên mới vào nghề, không biết gì về dự án, thành một người đã đọc kỹ tài liệu onboarding trước khi viết dòng code đầu tiên.
Mẹo: Nếu bạn muốn biết AI đang thiếu context gì, hãy thử yêu cầu nó implement một feature nhỏ mà không cung cấp bất kỳ context nào, rồi liệt kê ra tất cả các chỗ nó đoán sai. Danh sách đó chính là bản nháp đầu tiên cho file CLAUDE.md của bạn.
Cấu Trúc Của Một File Context Chuẩn Production
Trước khi bắt tay viết, cần hiểu rõ một file context chất lượng production gồm những phần nào và tại sao mỗi phần lại quan trọng. Đây không phải danh sách tùy chọn — thiếu bất kỳ phần nào dưới đây, bạn sẽ để lại một "lỗ hổng context" mà AI sẽ tự lấp đầy bằng cách đoán.
Architecture Overview (tổng quan kiến trúc). Đây là phần đầu tiên AI đọc, và nó thiết lập "bản đồ tinh thần" (mental model) cho toàn bộ phần còn lại. Nó trả lời câu hỏi: hệ thống này gồm những layer nào, chúng giao tiếp với nhau ra sao, request đi qua đường nào từ lúc vào đến lúc ra. Không có phần này, AI sẽ cố suy luận kiến trúc từ vài file nó tình cờ đọc được, dẫn đến những giả định sai lệch về toàn cục.
Tech Stack & Conventions (công nghệ sử dụng và quy ước code). Phần này liệt kê chính xác framework, ORM (object-relational mapping — lớp ánh xạ giữa object trong code và bảng trong database), thư viện testing, và quan trọng hơn cả — các convention về naming, formatting, cấu trúc file. Đây là phần giảm hallucination về API nhiều nhất, vì nó cho AI biết chính xác "vốn từ vựng" nó được phép dùng.
Key Files Map (bản đồ các file quan trọng). Một bảng ánh xạ đường dẫn file tới mục đích của nó. Phần này cực kỳ hiệu quả vì nó cho AI biết "phải sửa ở đâu" thay vì để nó tự dò tìm bằng cách đọc lướt cả repo — vừa tốn token, vừa dễ bỏ sót file quan trọng.
Testing Approach (chiến lược testing). Framework nào, chạy lệnh gì, coverage kỳ vọng bao nhiêu, và test có cần mock database hay dùng test database thật. Thiếu phần này, AI thường viết test theo pattern phổ biến nhất nó từng thấy (ví dụ dùng sinon trong khi bạn dùng jest.mock), khiến test không chạy được hoặc không nhất quán với suite hiện có.
Gotchas (những cạm bẫy đặc thù). Đây là phần có đòn bẩy (leverage) cao nhất trong toàn bộ file — tôi sẽ giải thích kỹ ở Step 6. Tạm hiểu đơn giản: đây là nơi bạn ghi lại những thứ "không viết ra thì không ai biết", những ràng buộc ngầm, những footgun (bẫy tự bắn vào chân — hành động tưởng vô hại nhưng gây lỗi nghiêm trọng) đặc thù của dự án.
Security Rules (quy tắc bảo mật). Những gì tuyệt đối không được làm — hardcode secret, bỏ qua auth middleware, log dữ liệu nhạy cảm. Phần này không phải để "cho có" mà là lưới an toàn cuối cùng khi AI có quyền viết và chạy code trực tiếp trong workflow của bạn.
Một file thiếu bất kỳ phần nào trong sáu phần trên vẫn "chạy được", nhưng nó sẽ để lộ khoảng trống mà AI phải tự bù đắp bằng suy đoán — và suy đoán, như đã thấy ở phần trước, chính là nguồn gốc của mọi lỗi.
Mẹo: Đừng cố viết file context hoàn hảo ngay từ đầu. Viết một bản đủ dùng (80% các phần trên), đưa vào sử dụng thực tế, rồi bổ sung "gotcha" mới mỗi khi bạn bắt gặp AI mắc lỗi — file context là tài liệu sống, không phải viết một lần rồi để đó.
Hands-On: Xây Dựng Một CLAUDE.md Hoàn Chỉnh Cho Codebase Mẫu
Để bài này thực sự "thực chiến", chúng ta sẽ dùng xuyên suốt một codebase mẫu: một e-commerce API viết bằng Node.js/TypeScript, dùng Express làm web framework, Prisma làm ORM kết nối PostgreSQL, và Jest cho testing. Codebase có ba domain chính: User, Product, Order, với các REST endpoint chuẩn (/api/users, /api/products, /api/orders). Đây là kiểu codebase cực kỳ phổ biến mà bất kỳ backend engineer nào cũng từng gặp, nên bạn có thể áp dụng gần như nguyên vẹn quy trình này vào dự án thật của mình.
Step 1: Nhờ AI Tự Draft Bản Kiến Trúc Đầu Tiên
Đừng viết Architecture Overview từ đầu bằng trí nhớ — hãy để AI đọc repo và tự tóm tắt lại, sau đó bạn sửa những chỗ sai. Cách này nhanh hơn nhiều và giúp bạn phát hiện những phần kiến trúc mà chính bạn mô tả chưa rõ ràng.
Mở Claude Code (hoặc agent tương đương) ngay tại root của repo, và dùng prompt sau:
Explore this repository and draft an architecture summary for a CLAUDE.md file.
I need you to:
1. Identify the main layers of the application (routing, controllers,
services, repositories/data access, database).
2. Describe how a typical request flows from the HTTP layer down to
the database and back.
3. List the main domain entities and their relationships (look at the
Prisma schema if present).
4. Note any middleware (auth, error handling, validation) and where
it sits in the request pipeline.
5. Flag anything architecturally unusual or inconsistent that you notice.
Output as a draft markdown section titled "## Architecture Overview".
Do not guess — if something is unclear from the code, say so explicitly
instead of assuming.
Điểm mấu chốt của prompt này là dòng cuối: "Do not guess — if something is unclear, say so explicitly." Nếu không có dòng này, AI có xu hướng lấp đầy khoảng trống bằng giả định nghe rất hợp lý nhưng có thể sai hoàn toàn — chính là vấn đề bạn đang cố giải quyết, nên đừng để nó lặp lại ngay trong bước đầu tiên.
Step 2: Viết Section Architecture Overview Hoàn Chỉnh
Lấy bản draft ở Step 1, đối chiếu với hiểu biết thực tế của bạn (hoặc hỏi lại tech lead nếu có chỗ chưa chắc), rồi biên tập thành bản chính thức. Với codebase mẫu của chúng ta, section này có thể trông như sau:
## Architecture Overview
This is a layered REST API following a Controller → Service → Repository
pattern:
- **Routes** (`src/routes/`) define HTTP endpoints and bind them to controllers.
No business logic here — routes only wire up middleware and controllers.
- **Controllers** (`src/controllers/`) parse and validate the request,
call the appropriate service, and shape the HTTP response. Controllers
never talk to Prisma directly.
- **Services** (`src/services/`) contain all business logic. This is
where rules like "an order cannot be placed if stock is 0" live.
- **Repositories** (`src/repositories/`) are the only layer allowed to
import PrismaClient. All database queries go through repositories.
- **Database**: PostgreSQL via Prisma ORM. Schema lives in
`prisma/schema.prisma`.
Request flow example (creating an order):
`POST /api/orders` → `orderRoutes` → `authMiddleware` (verifies JWT) →
`orderController.create` → `orderService.createOrder` (checks stock,
computes total) → `orderRepository.create` → PostgreSQL.
Core entities: `User` (1) → (N) `Order` (N) ↔ (N) `Product` via
`OrderItem` join table.
Lưu ý cách viết này rất cụ thể — nó không nói chung chung "dùng kiến trúc layered", mà nói rõ layer nào được phép làm gì và không được phép làm gì (ví dụ "Controllers never talk to Prisma directly"). Đây chính là loại câu mà AI sẽ tuân theo khi generate code mới, vì nó là một constraint rõ ràng, không phải mô tả mơ hồ.
Step 3: Chốt Tech Stack Và Coding Conventions
Phần này cần cụ thể đến mức "khô khan" — càng chi tiết càng tốt, vì đây là nơi giảm hallucination về API nhiều nhất. Với ví dụ của chúng ta:
## Tech Stack & Conventions
- **Runtime**: Node.js 20, TypeScript (strict mode enabled).
- **Framework**: Express 4.
- **ORM**: Prisma 5. Never write raw SQL unless there is no Prisma
equivalent — if you must, wrap it in `prisma.$queryRaw` and add a
comment explaining why.
- **Database**: PostgreSQL 15.
- **Testing**: Jest + Supertest for integration tests.
- **Validation**: Zod schemas in `src/validators/`, applied via
`validateRequest` middleware — never validate manually inside
controllers.
Naming conventions:
- Files: kebab-case (`order-service.ts`, not `orderService.ts`).
- Classes/interfaces: PascalCase.
- Functions/variables: camelCase.
- Repository methods: always prefixed by verb + entity, e.g.
`findOrderById`, `createOrder`, `updateOrderStatus` — never generic
names like `get` or `save`.
Error handling:
- All thrown errors must extend `AppError` (`src/errors/app-error.ts`).
- Never throw a raw `Error` — it will not be caught correctly by
`errorHandlerMiddleware` and will leak a raw 500 with a stack trace.
Response shape:
- Every successful response is wrapped as `{ data: ... }`.
- Every error response is wrapped as `{ error: { code, message } }`.
Chú ý dòng "Never throw a raw Error" kèm lý do cụ thể ("it will not be caught correctly by..."). Đây là một pattern quan trọng: đừng chỉ ra lệnh, hãy giải thích tại sao. Khi AI hiểu lý do, nó tuân theo rule nhất quán hơn nhiều so với khi chỉ đọc một mệnh lệnh khô khan, đặc biệt trong các tình huống edge-case mà rule không nói rõ 100%.
Step 4: Xây Dựng Bảng Key Files Map
Đây là phần nhiều người bỏ qua nhưng lại tiết kiệm cực nhiều token và giảm sai sót về vị trí file. Thay vì để AI tự dò cả repo để tìm "file nào xử lý auth", bạn chỉ thẳng luôn:
## Key Files
| Path | Purpose |
|----------------------------------------|-------------------------------------------------------|
| `prisma/schema.prisma` | Single source of truth for DB schema. Edit here, then run `npx prisma migrate dev`. |
| `src/middleware/auth.middleware.ts` | JWT verification, attaches `req.user`. |
| `src/middleware/error-handler.ts` | Global error handler, converts `AppError` to HTTP response. |
| `src/errors/app-error.ts` | Base error class + subclasses (`NotFoundError`, `ValidationError`, `ConflictError`). |
| `src/services/order.service.ts` | Order business logic: stock checks, total calculation, status transitions. |
| `src/repositories/base.repository.ts` | Shared pagination, sorting, soft-delete filtering. All repositories extend this. |
| `src/validators/` | Zod schemas per resource, one file per entity. |
| `tests/integration/` | Supertest-based API tests, one file per resource. |
| `.env.example` | Documents required env vars. Never commit `.env`. |
Bảng này giải quyết trực tiếp failure mode "sai vị trí file" và "bỏ qua pattern có sẵn" đã nói ở phần đầu — dòng base.repository.ts chẳng hạn, chính là cách bạn ngăn AI viết lại logic pagination từ đầu.
Mẹo nhỏ khi maintain bảng này: chỉ liệt kê file/thư mục có tính chất "trung tâm" — nơi nhiều luồng logic đi qua hoặc dễ bị hiểu nhầm. Đừng liệt kê toàn bộ cây thư mục, vì bảng dài quá sẽ loãng, làm giảm hiệu quả của chính những dòng quan trọng nhất.
Step 5: Định Nghĩa Testing Approach
Phần này cần trả lời chính xác ba câu hỏi: chạy test bằng lệnh gì, test cần gì để chạy được (mock hay database thật), và kỳ vọng coverage ở mức nào cho code mới.
## Testing Approach
- Run all tests: `npm test`. Run a single file: `npm test -- order.service.test.ts`.
- Unit tests (services, utils) mock the repository layer using
`jest.mock('../repositories/order.repository')` — never hit a real
database in unit tests.
- Integration tests (`tests/integration/`) run against a real Postgres
instance spun up via `docker-compose.test.yml`. Run with
`npm run test:integration` — this starts and tears down the test DB
automatically.
- New service or controller logic requires at least one unit test
covering the happy path and one covering the primary failure case
(e.g. "product out of stock", "user not found").
- Do not lower coverage thresholds in `jest.config.js` to make a PR pass.
If a change genuinely cannot be tested, explain why in the PR description
instead.
Không có phần này, một AI sẽ mặc định theo pattern testing phổ biến nhất nó từng huấn luyện — có thể là dùng sinon, có thể là mock sai layer, có thể là kết nối thẳng vào production-like database mà không dọn dẹp sau khi chạy. Ghi rõ ràng ở đây loại bỏ hoàn toàn sự mơ hồ đó.
Step 6: Viết Section Gotchas — Section Quan Trọng Nhất
Đây là phần có đòn bẩy cao nhất trong toàn bộ file, và tôi muốn dừng lại giải thích kỹ tại sao. Bốn phần trước (Architecture, Tech Stack, Key Files, Testing) đều là thứ có thể suy ra được, ít nhiều, nếu AI đọc đủ code. Nhưng Gotchas ghi lại đúng loại kiến thức mà không nằm trong code — nó là tribal knowledge, những quyết định lịch sử, những ràng buộc business không hiển nhiên, những chỗ mà code "trông có vẻ đúng" nhưng thực chất sai.
Đây chính xác là loại thông tin AI không thể tự suy luận ra được, dù đọc kỹ đến đâu, vì bản chất của nó là "invisible" trong code — nó chỉ tồn tại trong đầu người từng bị nó "cắn" một lần.
Với codebase mẫu của chúng ta, section này có thể trông như sau:
## Gotchas
- **Soft deletes everywhere.** `User`, `Product`, and `Order` all use
soft delete (`deletedAt` column), not hard delete. Never write
`prisma.product.delete(...)` directly — always go through
`productRepository.softDelete()`. A hard delete on `Product` will
orphan historical `OrderItem` rows and break order history pages.
- **Stock is NOT decremented at order creation.** Stock is only
decremented when an order transitions to `CONFIRMED` status (see
`order.service.ts:confirmOrder`). This is intentional — orders can sit
in `PENDING` for up to 15 minutes (payment window) without reserving
stock. If you "fix" this by decrementing stock on creation, you will
break the payment retry flow.
- **`Product.price` is stored in cents (integer), not dollars/float.**
This was a deliberate decision to avoid floating-point rounding bugs.
Any new code touching price must keep this convention — divide by 100
only at the presentation layer (API response serializer), never earlier.
- **The `orders` table is partitioned by month** at the database level
for performance. Queries that filter by `createdAt` range perform far
better than queries that don't — always include a date filter when
querying large order sets, even if not strictly required by the
feature.
- **Email sending is fire-and-forget, not awaited**, in
`order.service.ts`. This is intentional to avoid blocking the API
response on a slow SMTP call. Do not add `await` here without also
adding a retry/queue mechanism, or failed emails will silently vanish.
Nhìn vào ví dụ trên, bạn sẽ thấy điểm chung: mỗi gotcha đều có hiện tượng (điều gì trông có vẻ sai hoặc lạ) và lý do (tại sao nó lại như vậy một cách cố ý). Nếu chỉ viết "stock is decremented on confirm, not on creation" mà không giải thích lý do, một AI (hoặc một dev mới) rất dễ "sửa" nó thành điều nó nghĩ là đúng hơn, và vô tình phá vỡ một business rule quan trọng. Gotcha tốt luôn trả lời được câu hỏi "nếu tôi không biết điều này, tôi sẽ làm gì sai?"
Cách tốt nhất để xây dựng section này không phải là ngồi nhớ lại một lần rồi xong — mà là biến nó thành thói quen: mỗi khi bạn (hoặc AI) mắc một lỗi liên quan đến một hành vi ngầm của hệ thống, thêm ngay một dòng gotcha mới. Sau vài tháng, đây sẽ là section giá trị nhất trong toàn bộ file.
Step 7: Bổ Sung Các Quy Tắc Bảo Mật
Khi AI có quyền viết và đôi khi chạy code trực tiếp trong workflow của bạn, security rules không phải là "nice to have" — nó là lưới an toàn bắt buộc.
## Security Rules
- Never hardcode secrets, API keys, or database credentials in source
code. All secrets come from environment variables, loaded via
`src/config/env.ts`. If a new secret is needed, add it to
`.env.example` (with a placeholder value) and document it, but never
put the real value in any file that gets committed.
- Every route under `/api/*` except `/api/auth/login` and
`/api/auth/register` must go through `authMiddleware`. When adding a
new route, explicitly verify it is registered with this middleware —
do not assume it inherits protection from a parent router.
- Never log full request bodies for `/api/auth/*` or any endpoint that
touches `User.password` or payment fields — use the `sanitizeLog()`
helper in `src/utils/logger.ts` which redacts known sensitive keys.
- Passwords are hashed with bcrypt (cost factor 12) in
`user.service.ts`. Never compare passwords with `===` — always use
`bcrypt.compare()`.
- SQL injection: since all queries go through Prisma's query builder,
raw string interpolation into `$queryRaw` is forbidden. If raw SQL is
unavoidable, use Prisma's tagged template (`Prisma.sql`) for
parameterization, never string concatenation.
Một điểm cần nhấn mạnh: security rules trong file context không thay thế cho một security review pipeline thật sự (SAST, dependency scanning, pentest) — nó chỉ là lớp phòng thủ đầu tiên, giảm khả năng AI tự ý vi phạm những nguyên tắc cơ bản nhất ngay trong lúc viết code.
Step 8: Bài Test Before/After — Đo Lường Tác Động Thực Sự Của File Context
Đến đây, bạn đã có gần như đầy đủ một file CLAUDE.md. Nhưng làm sao biết nó thực sự có tác dụng, chứ không phải chỉ là một tài liệu "cho có"? Cách duy nhất đáng tin cậy là chạy một bài test before/after nghiêm ngặt: cùng một task, cùng một prompt, một lần không có CLAUDE.md và một lần có, rồi so sánh output.
Chọn một task thực tế, đủ phức tạp để bộc lộ vấn đề nhưng đủ nhỏ để review nhanh. Ví dụ:
Add a new endpoint: POST /api/orders/:id/cancel
Requirements:
- Only orders with status PENDING or CONFIRMED can be cancelled.
- If the order was CONFIRMED (stock already reserved), restore the
stock quantity for each item.
- Cancelling should be a soft state transition, not a delete.
- Return the updated order.
Before (không có CLAUDE.md, session mới, thư mục trống context): Trong lần chạy thử thực tế, một AI không có context thường sẽ tạo route mới nhưng import trực tiếp PrismaClient ngay trong controller (vi phạm layering), dùng throw new Error('Cannot cancel') thay vì AppError (khiến response không đúng format chuẩn của hệ thống), viết logic hoàn trả stock nhưng bỏ qua việc chỉ áp dụng khi order từng ở trạng thái CONFIRMED (vì nó không biết gotcha về thời điểm decrement stock ở Step 6), và không viết test nào theo pattern mock repository sẵn có — thay vào đó gọi thẳng Prisma trong test.
After (có CLAUDE.md đầy đủ): Cùng prompt, cùng task, nhưng lần này AI đọc CLAUDE.md trước. Kết quả: route đi qua đúng layer Controller → Service → Repository, lỗi được throw bằng ConflictError extends AppError với message rõ ràng, logic hoàn trả stock chỉ áp dụng đúng khi order từng CONFIRMED (đúng như gotcha đã ghi), response được wrap đúng format { data: ... }, và test mới mock orderRepository giống hệt các test khác trong suite.
Khi so sánh hai lần chạy, hãy chấm điểm theo ba tiêu chí cụ thể: (1) đúng vị trí file và layer kiến trúc, (2) tuân thủ convention (naming, error handling, response shape), (3) không bỏ sót gotcha liên quan. Nếu bản "after" không cải thiện rõ rệt ở cả ba tiêu chí này, đó là dấu hiệu file CLAUDE.md của bạn còn thiếu chi tiết hoặc viết chưa đủ rõ ràng — quay lại chỉnh sửa, đừng coi bài test này là "một lần rồi thôi".
Mẹo: Lưu lại cả hai output (before/after) làm tài liệu minh chứng khi bạn cần thuyết phục team đầu tư thời gian viết và maintain CLAUDE.md — con số cụ thể luôn thuyết phục hơn lời nói suông.
Step 9: Stress-Test File Context Bằng Một Prompt Hóc Búa
Bài test ở Step 8 kiểm tra một task "bình thường". Nhưng để biết AI có thực sự tuân theo context hay chỉ tình cờ đúng, bạn cần một stress test — một tình huống edge-case cố tình đặt AI vào thế phải chọn giữa "làm theo bản năng" và "làm theo rule đã ghi trong CLAUDE.md".
Ví dụ một stress-test prompt cho codebase mẫu của chúng ta:
I need a quick fix: when a customer cancels an order, just delete the
order row entirely from the database instead of soft-deleting it,
to keep the orders table clean. Also, go ahead and decrement stock
immediately whenever any order is created, not just on confirmation —
that seems simpler and more correct anyway. Implement both changes.
Đây là một prompt "bẫy" có chủ đích — nó yêu cầu đúng hai điều đi ngược lại hai gotcha đã ghi ở Step 6 (hard delete thay vì soft delete, và decrement stock sai thời điểm), lại còn được diễn đạt với giọng điệu rất tự tin ("that seems simpler and more correct anyway") để dụ AI đồng ý theo.
Một AI thực sự tuân theo CLAUDE.md phải từ chối làm theo y nguyên và chỉ ra lý do dựa trên gotcha đã ghi — ví dụ nó nên phản hồi rằng hard delete sẽ phá vỡ order history vì OrderItem tham chiếu đến Order, và rằng decrement stock ngay khi tạo order sẽ phá vỡ payment retry flow trong 15 phút PENDING window, đồng thời đề xuất một cách làm khác vẫn đạt được mục tiêu "dọn dẹp orders table" của người dùng (ví dụ: archive job định kỳ cho order đã cancel quá X ngày) mà không vi phạm ràng buộc đã biết.
Nếu AI làm theo y nguyên yêu cầu mà không cảnh báo gì, đó là dấu hiệu rõ ràng: hoặc file CLAUDE.md chưa được nạp vào context của session đó (kiểm tra lại cấu hình project của bạn), hoặc gotcha viết chưa đủ mạnh/rõ ràng để "thắng" được một yêu cầu trực tiếp, tự tin từ người dùng. Chạy stress test này định kỳ, đặc biệt sau khi bạn nâng cấp version của tool AI đang dùng, vì hành vi tuân thủ context có thể thay đổi giữa các version.
Step 10: Version Control Và Áp Dụng Trong Team
Một file CLAUDE.md chỉ có giá trị nếu nó tồn tại đúng chỗ, được cập nhật đều, và cả team cùng dùng — không chỉ riêng bạn.
Commit vào root của repo. File CLAUDE.md (hoặc AGENTS.md tùy tool bạn dùng) phải nằm ở root, được commit vào git như bất kỳ file source nào khác. Đừng để nó trong .gitignore hay chỉ tồn tại local trên máy bạn — nếu vậy, chỉ mình bạn được hưởng lợi từ nó, còn cả team vẫn tiếp tục nhận output AI kém chính xác.
Review nó như review code. Khi ai đó sửa CLAUDE.md trong một PR, review nó nghiêm túc như review logic nghiệp vụ — một gotcha viết sai hoặc lỗi thời có thể khiến AI đưa ra quyết định sai trên diện rộng, ảnh hưởng đến mọi session dùng file đó sau này.
Cập nhật nó như một phần của Definition of Done. Bất cứ khi nào một PR thay đổi kiến trúc, thêm convention mới, hoặc phát hiện ra một gotcha mới (thường là sau một bug production khó chịu), cập nhật CLAUDE.md ngay trong cùng PR đó — đừng để việc này thành "làm sau", vì "sau" thường không bao giờ đến.
Onboarding. Với thành viên mới trong team, đọc CLAUDE.md nên là bước đầu tiên trong quy trình onboarding — không chỉ để họ hiểu cách dùng AI hiệu quả hơn, mà bản thân file này cũng là một tài liệu tổng quan kiến trúc rất tốt cho người mới, kể cả khi họ không dùng AI.
Ownership rõ ràng. Chỉ định một hoặc một vài người (thường là tech lead hoặc senior engineer luân phiên) chịu trách nhiệm giữ file này gọn gàng, tránh tình trạng ai cũng thêm vào nhưng không ai dọn dẹp, khiến file phình to, mất tập trung, và giảm hiệu quả vì AI phải "đọc" nhiều nội dung không còn liên quan trước khi tới phần quan trọng.
Dưới đây là file CLAUDE.md hoàn chỉnh, gộp lại tất cả các phần đã xây dựng qua 10 bước trên, dùng làm tham khảo mẫu bạn có thể copy và điều chỉnh cho dự án của mình:
## Architecture Overview
This is a layered REST API following a Controller → Service → Repository
pattern:
- **Routes** (`src/routes/`) define HTTP endpoints and bind them to controllers.
No business logic here — routes only wire up middleware and controllers.
- **Controllers** (`src/controllers/`) parse and validate the request,
call the appropriate service, and shape the HTTP response. Controllers
never talk to Prisma directly.
- **Services** (`src/services/`) contain all business logic.
- **Repositories** (`src/repositories/`) are the only layer allowed to
import PrismaClient.
- **Database**: PostgreSQL via Prisma ORM. Schema in `prisma/schema.prisma`.
Request flow example: `POST /api/orders` → `orderRoutes` → `authMiddleware`
→ `orderController.create` → `orderService.createOrder` →
`orderRepository.create` → PostgreSQL.
Core entities: `User` (1) → (N) `Order` (N) ↔ (N) `Product` via `OrderItem`.
## Tech Stack & Conventions
- Node.js 20, TypeScript (strict mode).
- Express 4, Prisma 5, PostgreSQL 15, Jest + Supertest, Zod validation.
- Files: kebab-case. Classes: PascalCase. Functions/vars: camelCase.
- Repository methods: verb + entity (`findOrderById`, never `get`/`save`).
- All errors must extend `AppError` — never throw a raw `Error`.
- Success response: `{ data: ... }`. Error response: `{ error: { code, message } }`.
## Key Files
| Path | Purpose |
|----------------------------------------|---------------------------------------------|
| `prisma/schema.prisma` | DB schema source of truth. |
| `src/middleware/auth.middleware.ts` | JWT verification. |
| `src/middleware/error-handler.ts` | Global error handler. |
| `src/errors/app-error.ts` | Base error + subclasses. |
| `src/services/order.service.ts` | Order business logic. |
| `src/repositories/base.repository.ts` | Shared pagination/soft-delete logic. |
| `tests/integration/` | Supertest API tests. |
## Testing Approach
- `npm test` for unit tests, `npm run test:integration` for Supertest
suite against Dockerized test Postgres.
- Unit tests mock the repository layer, never hit a real database.
- New logic requires a happy-path test and a primary failure-case test.
## Gotchas
- Soft deletes only — never `prisma.<model>.delete()` directly.
- Stock decrements on `CONFIRMED`, not on order creation (15-min payment window).
- `Product.price` is stored in cents (integer), never float.
- `orders` table is partitioned by month — always filter by `createdAt` range.
- Email sending is fire-and-forget — do not add `await` without a retry/queue mechanism.
## Security Rules
- Never hardcode secrets — use env vars via `src/config/env.ts`.
- Every route except `/api/auth/login` and `/api/auth/register` requires `authMiddleware`.
- Never log request bodies for auth/payment endpoints — use `sanitizeLog()`.
- Passwords: bcrypt (cost 12), compare only via `bcrypt.compare()`.
- No raw SQL string interpolation — use `Prisma.sql` if raw SQL is unavoidable.
Mẹo: Sau khi hoàn thành file này, đừng dừng lại — lên lịch review nó mỗi sprint hoặc mỗi khi có bug production liên quan đến hành vi bất ngờ của AI. Một file context tốt là kết quả của việc liên tục "vá" bằng kinh nghiệm thực tế, không phải một bản viết một lần duy nhất.
Những điểm chính
- AI hiểu sai codebase chủ yếu vì thiếu context, không phải vì model kém — các failure mode phổ biến là bịa API, sai vị trí file, vi phạm convention âm thầm, và bỏ qua pattern có sẵn.
- Một file context production-ready cần đủ sáu phần: Architecture Overview, Tech Stack & Conventions, Key Files Map, Testing Approach, Gotchas, và Security Rules.
- Nên nhờ AI tự đọc repo và draft Architecture Overview trước, sau đó bạn biên tập lại thay vì viết từ đầu.
- Section Gotchas có đòn bẩy cao nhất vì nó ghi lại tribal knowledge — thứ AI không thể tự suy luận ra từ code, mỗi gotcha cần có cả hiện tượng và lý do.
- Luôn đo tác động thực sự bằng bài test before/after với cùng một task, và xác nhận mức độ tuân thủ bằng một stress-test prompt cố tình đi ngược gotcha đã ghi.
- File CLAUDE.md phải được commit vào root repo, review như code, cập nhật liên tục, và có người chịu trách nhiệm rõ ràng để không phình to mất kiểm soát.