Bỏ qua để đến nội dung

Thiết lập Claude Code cho monorepo hoặc codebase lớn

Bài viết được dịch tự động từ bài viết gốc, chưa được kiểm tra lại bởi con người. Chỉ những bài viết có dấu tick xanh cạnh tiêu đề là đã được kiểm tra.

Codebase lớn có thể là một repository hàng triệu dòng code, hoặc một monorepo nhiều package. Claude Code hoạt động ở mọi quy mô, nhưng khi codebase lớn dần, các mặc định tối ưu cho project nhỏ có thể lấp đầy context window bằng chỉ dẫn và file đọc không liên quan tới tác vụ, tốn token và làm giảm hiệu quả của Claude.

Bài này hướng dẫn cả developer cá nhân lẫn team engineering cách giới hạn phạm vi Claude vào đúng phần codebase một tác vụ đang chạm tới.

Các ví dụ trong bài dùng chung một monorepo ba package. Cùng pattern áp dụng được cho codebase một cây lớn: nơi ví dụ dùng packages/api/, thay bằng thư mục subsystem của bạn như src/backend/ hay lib/core/.

monorepo/
CLAUDE.md # chỉ dẫn gốc
packages/
api/
CLAUDE.md # chỉ dẫn riêng cho API
.claude/skills/
src/
web/
CLAUDE.md # chỉ dẫn riêng cho frontend
.claude/skills/
src/
shared/
CLAUDE.md # chỉ dẫn cho shared library
src/
MuốnDùng
Chỉ nạp convention cho code bạn đang chạm tới, thay vì một file gốc bao trùm mọi subsystemCLAUDE.md phân theo thư mục
Loại bỏ CLAUDE.md của các package bạn không bao giờ làm việcclaudeMdExcludes
Chặn Claude mở build output, generated code, và vendored dependencyRead deny rule trong permissions.deny
Tìm định nghĩa hay nơi gọi một symbol qua language server thay vì scan filePlugin code intelligence
Chỉ checkout thư mục cần thiết khi Claude tạo worktreeworktree.sparsePaths
Đọc và sửa một package anh em hay repository khác từ cùng một phiên--add-dir hoặc additionalDirectories
Cho Claude thủ tục riêng cho một khu vực, chỉ nạp khi liên quanSkill theo từng thư mục
Thay nhiều CLAUDE.md rải rác bằng một bộ convention mọi người cùng càiPlugin trong internal marketplace

Nơi bạn chạy claude quyết định file nào Claude đọc/sửa được mà không cần permission bổ sung, CLAUDE.md nào nạp lúc khởi động, và project settings nào áp dụng.

Bắt đầu từQuyền truy cập fileCLAUDE.md nạp lúc khởi độngDùng khi
Repository rootMọi fileChỉ root; file thư mục con nạp khi cầnTác vụ trải rộng nhiều package/subsystem
Một thư mục conChỉ subtree đó, tới khi cấp thêmFile thư mục đó cộng mọi thư mục tổ tiênCông việc giới hạn trong một package/subsystem

Project settings trong .claude/settings.json chỉ nạp từ thư mục bạn khởi động, không kế thừa từ thư mục cha như CLAUDE.md: .claude/settings.json ở repository root chỉ áp dụng khi bạn khởi động Claude từ root.

Trong codebase lớn, một CLAUDE.md duy nhất ở root có xu hướng phình to để bao trùm mọi subsystem (tốn context cho chỉ dẫn không liên quan), hoặc trở nên quá chung chung để hữu ích. Chia chỉ dẫn theo từng thư mục nghĩa là Claude nạp quy tắc toàn repo cộng chỉ những convention của code đang làm.

Claude Code nạp mọi file CLAUDE.md từ thư mục làm việc và mọi thư mục cha lúc khởi động, sau đó nạp file của từng thư mục con khi cần khi Claude đọc file ở đó. File gốc đặt quy tắc toàn repo, mỗi thư mục con thêm quy tắc riêng.

Cách chia phổ biến là hai cấp:

  • CLAUDE.md gốc: chỉ dẫn áp dụng khắp nơi, như coding standard, quy ước commit, cấu trúc repo
  • CLAUDE.md từng thư mục con: convention riêng cho stack của khu vực đó. Trong monorepo là mỗi package một file; trong codebase một cây lớn là mỗi subsystem như src/db/ hay src/api/

Commit các file này vào repository để đồng nghiệp kế thừa. Chủ sở hữu mỗi thư mục thường là người bảo trì file của mình.

CLAUDE.md gốc định hướng Claude về cấu trúc repository:

CLAUDE.md
This is a monorepo with three packages under packages/:
- packages/api: Node.js REST API with Express, TypeScript, and PostgreSQL
- packages/web: React frontend with Vite, TypeScript, and TailwindCSS
- packages/shared: shared TypeScript utilities used by both api and web
Run commands from the package directory, not the monorepo root.
Each package has its own tsconfig.json, package.json, and test suite.

CLAUDE.md của thư mục con, ở đây là packages/api/CLAUDE.md, thêm ngữ cảnh riêng cho stack của khu vực đó:

packages/api/CLAUDE.md
This package is the REST API server.
- Run tests: `npm test` (uses Vitest)
- Run dev server: `npm run dev` (port 3001)
- Database migrations: `npm run migrate`
- Environment variables: copy `.env.example` to `.env`
API routes are in src/routes/. Each route file exports an Express router.
Database queries use Knex in src/db/. Never write raw SQL strings in route handlers.

Khi khởi động Claude từ packages/api/, nó nạp cả packages/api/CLAUDE.md lẫn CLAUDE.md gốc - không có chỉ dẫn nào từ packages/web/ trong context. Chạy /context để xem danh sách dưới mục Memory files xác nhận file nào đã nạp.

Một vài cách giữ file luôn cập nhật khi codebase và model thay đổi:

  • Review trong pull request: coi sửa CLAUDE.md như bất kỳ thay đổi tài liệu nào khác
  • Rà soát lại sau các bản model lớn: chỉ dẫn từng khắc phục hạn chế của model cũ có thể thành thừa khi model mới đã tự xử lý được
  • Thêm Stop hook đề xuất cập nhật: một Stop hook nhận đường dẫn transcript phiên khi Claude hoàn thành phản hồi, để script rà soát phiên và đề xuất cập nhật CLAUDE.md ngay khi khoảng trống còn mới

Chọn giữa CLAUDE.md từng thư mục và path-scoped rule

Phần tiêu đề “Chọn giữa CLAUDE.md từng thư mục và path-scoped rule”

CLAUDE.md từng thư mục và path-scoped rule dưới .claude/rules/ đều cho phép nhắm chỉ dẫn vào một phần cây thư mục, khác nhau ở nơi file nằm và lúc nào nạp.

Cách tiếp cậnVị trí fileNạp khiDùng khi
CLAUDE.md từng thư mụcBên trong thư mục, cạnh codeLúc khởi động nếu chạy từ thư mục đó, hoặc khi Claude đọc file ở đóChủ sở hữu thư mục tự bảo trì convention riêng; chỉ dẫn được version cùng code
Path-scoped rule trong .claude/rules/Trung tâm .claude/ ở repo rootKhi Claude làm việc với file khớp glob paths: của ruleMuốn mọi convention ở một chỗ, hoặc cùng một rule áp dụng cho nhiều path rải rác

Khi khởi động Claude từ repository root, CLAUDE.md của mỗi thư mục con nạp ngay khi Claude đọc file trong thư mục đó. Setting claudeMdExcludes bỏ qua file cụ thể theo path hoặc glob pattern để chúng không bao giờ nạp.

Dùng cho các thư mục bạn không bao giờ làm việc, như package của team khác, code cũ, hay subtree vendored. Danh sách loại trừ là tĩnh, không phải công tắc theo từng tác vụ. Muốn tập trung vào một package hôm nay và package khác hôm sau, khởi động Claude từ thư mục của package đó thay vì sửa exclusion.

Nếu chỉ muốn loại trừ này cho riêng bạn, đặt setting trong .claude/settings.local.json. Pattern dùng cú pháp glob khớp với đường dẫn tuyệt đối, nên bắt đầu pattern kiểu tương đối bằng **/ để khớp bất cứ đâu trong cây. Ví dụ dưới loại trừ một package do team khác sở hữu:

.claude/settings.local.json
{
"claudeMdExcludes": [
"**/packages/web/**"
]
}

Managed policy CLAUDE.md không thể bị loại trừ, nên chỉ dẫn toàn tổ chức luôn áp dụng. Bạn có thể đặt claudeMdExcludes ở bất kỳ scope settings nào: user, project, local, hay managed. Mảng được gộp qua các scope, nên team có thể đặt mặc định ở cấp project trong khi từng cá nhân thêm override cục bộ.

Chỉ dẫn chỉ là một phần nội dung nạp vào context. File đọc là chi phí khác tăng theo quy mô codebase. Các setting dưới chặn đọc path không liên quan và thay việc scan file toàn diện bằng tra cứu qua language server.

Việc tìm kiếm nội dung của Claude tôn trọng .gitignore mặc định, nên các path đã liệt kê ở đó như node_modules/, dist/, build/ đã nằm ngoài kết quả tìm kiếm.

Với các path được checkin, như một SDK vendored hay generated code đã commit, thêm Read deny rule trong permissions.deny để chặn Claude mở các file đó kể cả khi tìm kiếm liệt kê chúng.

.claude/settings.json
{
"permissions": {
"deny": [
"Read(./**/dist/**)",
"Read(./**/build/**)",
"Read(./**/*.generated.*)",
"Read(./vendor/**)"
]
}
}

Deny rule bao phủ các tool file có sẵn của Claude và lệnh Bash file được nhận diện (cat, head, grep, find) khi path bị chặn được truyền làm tham số. Chúng không lọc path bị chặn khỏi output của một lệnh tìm kiếm đệ quy, và không bao phủ các subprocess tự mở file.

Trong codebase lớn, tìm nơi định nghĩa hay nơi sử dụng một symbol có thể tốn nhiều lần đọc file và grep. Plugin code intelligence kết nối Claude với language server để nhảy tới định nghĩa, tìm reference, và thấy lỗi kiểu dữ liệu trực tiếp thay vì scan cây thư mục.

Official marketplace có plugin cho TypeScript, Python, Go, Rust, và các ngôn ngữ phổ biến khác:

Terminal window
/plugin install typescript-lsp@claude-plugins-official

Plugin code intelligence cần binary language server của ngôn ngữ đó cài trên máy mỗi developer.

Cờ --worktree khởi động phiên trong một git worktree mới để thay đổi tách biệt khỏi checkout chính. Mặc định nó checkout toàn bộ repo. Trong repo lớn, setting worktree.sparsePaths dùng git sparse-checkout để chỉ ghi ra đĩa các thư mục liệt kê cộng file cấp root, nên worktree khởi động nhanh hơn và dùng ít dung lượng hơn.

.claude/settings.json
{
"worktree": {
"sparsePaths": [
".claude",
"packages/api",
"packages/shared"
]
}
}

Khi Claude tạo worktree, nó chỉ checkout .claude/, packages/api/, và packages/shared/ thay vì toàn cây. Path trong sparsePaths tính tương đối với repository root, bất kể bạn khởi động Claude từ thư mục con nào.

Điều này đặc biệt hữu ích cho cô lập subagent bằng worktree. Subagent là các phiên Claude song song sinh ra cho subtask, mỗi cái chạy trong worktree sẽ có checkout gọn nhẹ thay vì toàn cây.

Để tránh nhân bản các thư mục lớn như node_modules qua nhiều worktree, kết hợp sparsePaths với symlinkDirectories trong cùng .claude/settings.json:

.claude/settings.json
{
"worktree": {
"sparsePaths": [
".claude",
"packages/api",
"packages/shared"
],
"symlinkDirectories": [
"node_modules"
]
}
}

Cấp quyền truy cập qua package hoặc repository khác

Phần tiêu đề “Cấp quyền truy cập qua package hoặc repository khác”

Phần này áp dụng khi bạn khởi động Claude từ thư mục con, hoặc khi một tác vụ trải rộng nhiều checkout. Nếu khởi động từ repository root trong một cây lớn duy nhất, Claude đã có quyền truy cập mọi file nên có thể bỏ qua phần này.

Setting additionalDirectories trong .claude/settings.json cấp cho Claude quyền truy cập thư mục ngoài thư mục làm việc:

packages/api/.claude/settings.json
{
"permissions": {
"additionalDirectories": [
"../shared",
"../web"
]
}
}

Bạn cũng có thể cấp quyền lúc chạy mà không sửa settings bằng cách truyền --add-dir khi khởi động Claude:

Terminal window
claude --add-dir ../shared

Dù thêm thư mục cách nào, Claude cũng đọc/sửa được file trong đó. Việc CLAUDE.md, .claude/rules/, và skill của thư mục đó có nạp hay không phụ thuộc cách bạn thêm:

Thêm bằngNạp CLAUDE.md và ruleNạp skill
Setting additionalDirectoriesKhông bao giờKhông bao giờ
Cờ --add-dir hoặc lệnh /add-dirChỉ khi có biến môi trường dưới đây

Để nạp CLAUDE.md và rule từ thư mục thêm bằng --add-dir hay /add-dir, đặt biến môi trường CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD:

Terminal window
CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 claude --add-dir ../shared

Bất kỳ thư mục con nào cũng có thể định nghĩa skill riêng cho stack của mình. Skill chỉ nạp khi Claude xác định nó liên quan, nên tooling riêng của API không tốn context trong lúc làm frontend.

Skill nằm dưới .claude/skills/ bên trong thư mục. Commit chúng cùng code của khu vực đó.

Terminal window
mkdir -p packages/api/.claude/skills/api-testing

Rồi viết SKILL.md bên trong, ở đây là packages/api/.claude/skills/api-testing/SKILL.md:

packages/api/.claude/skills/api-testing/SKILL.md
---
name: api-testing
description: Testing patterns for the API package. Use when writing or modifying tests in packages/api/.
---
## Test structure
Tests are in `src/__tests__/` mirroring the `src/` directory structure.
Each route file has a corresponding `.test.ts` file.
## Running tests
- All tests: `npm test`
- Single file: `npm test -- src/__tests__/routes/users.test.ts`
- Watch mode: `npm test -- --watch`
## Patterns
- Use `supertest` for HTTP assertions, not raw fetch
- Always wrap database tests in a transaction that rolls back
- Mock external services in `src/__tests__/mocks/`

Một thư mục khác có skill khác theo cách tương tự: packages/web/.claude/skills/component-patterns/ mô tả convention component của frontend thay vì testing. Khi Claude làm việc trong packages/api/, nó nạp skill api-testing; khi làm trong packages/web/, nạp component-patterns thay vào.

Bạn cũng có thể scope skill theo file pattern thay vì theo vị trí đặt file. Trường paths trong frontmatter nhận glob pattern, Claude tự nạp skill khi làm việc với file khớp pattern.

Với skill rải rác nhiều thư mục, danh sách Claude chọn có thể phình to. Claude chọn skill bằng cách đọc tên và mô tả của mọi skill được phát hiện, chỉ nội dung đầy đủ của skill được chọn mới nạp vào context.

Phạm vi skill phụ thuộc nơi bạn khởi động Claude:

  • Từ thư mục con như packages/api/: skill của thư mục đó, mọi cha tới repository root, và cấp user/enterprise
  • Từ repository root: skill gốc, cộng skill của mọi thư mục con Claude chạm tới trong phiên, có thể lên tới hàng trăm
  • Sau khi thêm thư mục anh em bằng --add-dir: skill của thư mục đó cũng nạp. Setting additionalDirectories chỉ cấp quyền truy cập file, không nạp skill

Tên luôn nạp, nhưng mô tả bị rút gọn khi có nhiều skill, có thể mất từ khoá Claude dùng để quyết định skill có liên quan không. Giữ mô tả ngắn và mở đầu bằng từ khoá mà một yêu cầu thực tế sẽ chứa.

Với skill nhiều thư mục cùng dùng chung, như convention PR hay checklist deploy, đặt trong .claude/skills/ ở repository root để nạp từ mọi thư mục khởi động. Khi skill dùng chung cần lịch sử version riêng hoặc phải hoạt động qua nhiều repository, đóng gói thành plugin thay vào đó.

Tập trung hoá convention khi phân lớp không còn scale nổi

Phần tiêu đề “Tập trung hoá convention khi phân lớp không còn scale nổi”

CLAUDE.md phân theo thư mục có thể khó quản trị khi codebase lớn dần. Convention trôi dạt, file cũ đi, không ai sở hữu file gốc. Chuyển convention và tài liệu tham khảo ra khỏi CLAUDE.md luôn-nạp, sang các cơ chế nạp theo yêu cầu:

  • Skills: tài liệu tham khảo chỉ nạp khi liên quan tới tác vụ
  • Plugins: gói skill, hook, và command được version, do platform team sở hữu tập trung
  • MCP servers: nếu tổ chức đã có code search hay RAG index riêng, expose nó thành MCP tool để Claude query thay vì đọc file trực tiếp

Đề xuất đúng plugin lúc bắt đầu phiên

Phần tiêu đề “Đề xuất đúng plugin lúc bắt đầu phiên”

Khi convention đã sống trong plugin, một đồng nghiệp khởi động Claude ở phần cây lạ không có tín hiệu nào cho biết plugin nào chủ sở hữu khu vực đó dùng. Một SessionStart hook có thể lấp khoảng trống này, vì mọi thứ hook in ra stdout được thêm vào context của Claude trước prompt đầu tiên.

Cấu hình kết hợp dưới đây dùng layout monorepo ví dụ. Cùng file này áp dụng cho bất kỳ thư mục con nào trong một cây lớn.

packages/api/.claude/settings.json
{
"worktree": {
"sparsePaths": [
".claude",
"packages/api",
"packages/shared"
],
"symlinkDirectories": [
"node_modules"
]
},
"permissions": {
"additionalDirectories": [
"../shared"
],
"deny": [
"Read(./**/dist/**)",
"Read(./**/build/**)"
]
}
}

Vì phiên này khởi động từ packages/api/, CLAUDE.md của package anh em đã nằm ngoài phạm vi nên không cần claudeMdExcludes ở đây. Deny rule cần thêm một bản sao ở .claude/settings.json của repository root để phiên chạy trong worktree cũng áp dụng được:

.claude/settings.json
{
"permissions": {
"deny": [
"Read(./**/dist/**)",
"Read(./**/build/**)"
]
}
}

Với cấu hình này, khởi động Claude từ packages/api/:

  • Nạp CLAUDE.md gốc và packages/api/CLAUDE.md, bỏ qua packages/web/CLAUDE.md
  • Đọc/sửa được file trong packages/api/packages/shared/
  • Bỏ qua đọc build output dưới dist/build/ trong packages/api/
  • Có sẵn skill api-testing khi cần
  • Tạo worktree gồm .claude/, packages/api/, packages/shared/, và file cấp root, với deny rule áp dụng toàn worktree từ file settings gốc

Phạm vi hoá và lên kế hoạch thay đổi trải nhiều package

Phần tiêu đề “Phạm vi hoá và lên kế hoạch thay đổi trải nhiều package”

Cấu hình trên kiểm soát những gì Claude thấy. Khi một thay đổi đơn lẻ chạm tới nhiều package, như cập nhật một type dùng chung cùng mọi nơi gọi nó, cách bạn giới hạn phạm vi và sắp xếp thứ tự tác vụ cũng ảnh hưởng tới kết quả.

Hai kỹ thuật giúp giữ thay đổi xuyên-package nhất quán:

  • Đưa Claude toàn bộ thay đổi trong một phiên: giao cả phần sửa dùng chung lẫn mọi nơi gọi nó cùng lúc giữ các quyết định đằng sau mỗi lần sửa nhất quán
  • Lưu kế hoạch ra file trước khi sửa: lên kế hoạch trước, nhờ Claude ghi kế hoạch ra một file markdown. Một phiên xuyên-package dài sẽ nén (compact) context dọc đường, và kế hoạch đã lưu sống sót qua đó dù lịch sử hội thoại có thể không