想像你接手一個系統,它有 35 個 GitHub repo。

event-ingestion-servicestream-processoranalytics-apiops-dashboardstreaming-clusterdatabase-cluster⋯⋯每個都在獨立的 repo 裡,各自有 CI、各自的 PR review、各自的 release 流程。你打開 Claude Code,cd 進其中一個 repo,問它:「使用者按下儀表板的『重新計算』按鈕之後,後台會發生什麼事?」

它只能看到當下這個 repo 的程式碼。後台處理在 stream-processor,重新計算邏輯在 analytics-api,資料來源在 event-ingestion-service——這些 Claude 一個都看不到。它能告訴你的,最多就是「這個按鈕呼叫了 /recompute 端點」,後面發生什麼事就要靠你自己腦補。

這不是 Claude 不夠聰明的問題。是它沒看到那張地圖。


問題不在 repo 結構,在「可視範圍」

很多人聽到「跨 repo 開發」第一反應是:那我們把 35 個 repo 合併成 monorepo 好了。

這想法的問題是它太大。每個 repo 的 CI/CD、release 流程、權限切割都是團隊現有運作的一部分,動結構代價極高。而且就算合併成功,Claude Code 也不會神奇地變強——你還是得告訴它哪段程式碼跟哪段有關係,只是現在它們躺在同一個目錄樹裡。

真正的問題不是「程式碼分散在多個 repo」,是「Claude 沒辦法看到這些 repo 之間的關係」。

換個角度想,就像你在一間有 35 個房間的辦公大樓工作。每個房間裡都有不同的部門。你不需要把所有牆推倒變成開放空間,你只需要一張樓層平面圖,知道採購部在 3 樓、財務在 7 樓、IT 在 B1。

Virtual Monorepo 就是這張平面圖。


三個檔案搞定整張地圖

整個模式只需要三個檔案,放在一個輕量的 workspace 目錄裡:

.repos — 一個 bash script,把所有 repo 用 git clone 拉下來,放到統一的目錄結構。一次設好,新成員入職跑一次就拿到完整環境。

CLAUDE.md — 系統地圖。每個服務有什麼用、跟哪些服務有依賴關係、資料怎麼流動。這份是 Claude 每次啟動 session 都會自動讀的。

README.md — 給人看的高層架構脈絡。新人入職、外部協作者都看這個。

目錄長這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
workspace/
├── services/
│ ├── event-ingestion-service/ # 從 GitHub clone
│ ├── stream-processor/
│ └── analytics-api/
├── infrastructure/
│ ├── streaming-cluster/
│ ├── database-cluster/
│ └── networking/
├── frontends/
│ ├── ops-dashboard/
│ └── customer-portal/
├── .repos # 一次 clone 全部
├── CLAUDE.md # 系統地圖
└── README.md # 給人讀的脈絡

關鍵在於:workspace/ 本身只是一個容器。底下每個子目錄都還是獨立的 git repo,各自有自己的 .git、自己的 CLAUDE.md、自己的 CI。Workspace 不接管它們,只是提供一個共同的「閱讀視野」。


CLAUDE.md 的層級結構(這是核心)

Claude Code 啟動時,會從你 cwd 開始往上爬目錄樹,把所有遇到的 CLAUDE.md 都讀進來。這個行為是 Virtual Monorepo 模式的關鍵。

意思是當你在 workspace/services/event-ingestion-service/ 裡跑 Claude Code,它會自動把這三個檔案串起來看:

  1. workspace/services/event-ingestion-service/CLAUDE.md(repo 層級——這個服務的具體細節)
  2. workspace/services/CLAUDE.md(如果有的話,team 層級——這群服務的共同約定)
  3. workspace/CLAUDE.md(org 層級——整個系統的地圖)

所以你的工作其實是設計三層內容,從特定到通用:

Repo 層級——build 怎麼跑、測試怎麼下、有什麼怪 patch 要小心。這層保留在每個 repo 裡,跟著該 repo 的 commit 走。

Team 層級——這群服務共用的規範(例如「我們所有的 data service 都用 Protobuf 不用 JSON」)、跨服務的測試策略。這層放在 workspace 裡用 git 追蹤。

Org 層級——整個系統的服務圖、資料流方向、最重要的:每個服務在什麼路徑、做什麼事。這層也放在 workspace 裡用 git 追蹤。

注意 org 層級要寫得「短」。Claude 每次啟動都會讀,太長就會吃掉你寶貴的 context window。


系統地圖怎麼寫才好用

CLAUDE.md 的 org 層級,內容該長什麼樣?最有效的格式是「服務目錄 + 一行描述 + 依賴指向」。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 系統地圖

## 資料服務(services/)

- `@services/event-ingestion-service`
用途:接收前端 SDK 上報的事件,寫進 Kafka topic `events.raw`
依賴:→ `streaming-cluster` 提供的 Kafka

- `@services/stream-processor`
用途:消費 `events.raw`,做 enrichment 和去重,產出 `events.enriched`
依賴:→ `event-ingestion-service`(上游 topic)
→ `analytics-api`(呼叫 dimension lookup)

- `@services/analytics-api`
用途:對外提供查詢 API,背後接 ClickHouse。
依賴:→ `database-cluster` 提供的 ClickHouse

@path/reference 這種寫法是給 Claude 看的快捷指路標。當你問「使用者按下重新計算之後會怎樣?」,它一眼就能跟著 @services/analytics-api@services/stream-processor@services/event-ingestion-service 把整條鏈追完。

關鍵原則:寫架構關係,不寫程式細節。細節的部分讓 Claude 自己去看程式碼,地圖只負責告訴它「該去看哪些檔案」。


設定流程:從零到能用

實際操作只需要四步。

第一步:建 workspace 目錄並 init 一個 bootstrap repo

1
2
3
mkdir my-system-workspace && cd my-system-workspace
git init
echo "services/*\ninfrastructure/*\nfrontends/*\n!*/CLAUDE.md" > .gitignore

.gitignore 那行是關鍵——讓 git 忽略所有子目錄裡的 repo,但保留每個子目錄裡的 CLAUDE.md。這樣 team 層級的文件能跟著 workspace 一起進版控。

第二步:寫 .repos 腳本

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env bash
set -e
mkdir -p services infrastructure frontends

(cd services && \
git clone git@github.com:myorg/event-ingestion-service.git && \
git clone git@github.com:myorg/stream-processor.git && \
git clone git@github.com:myorg/analytics-api.git)

(cd infrastructure && \
git clone git@github.com:myorg/streaming-cluster.git && \
...)

第一次跑這個腳本花幾分鐘,之後新成員或新機器只要 ./.repos 一次就拿到完整環境。

第三步:寫 CLAUDE.md(org 層級)

照前面講的格式,列出每個服務的用途和依賴。先寫個 v1,能跑就好,後面用的時候再補。

第四步:開 Claude Code 在 workspace 根目錄

1
2
cd my-system-workspace
claude

第一個指令可以是「請讀 CLAUDE.md,然後幫我畫出整個系統的資料流」。如果你的 CLAUDE.md 寫得好,它應該能直接畫出 ASCII 圖。


實戰:跨 repo 任務怎麼跑

光有地圖還不夠,重點是能用它做事。三個常見場景。

場景一:跨服務 schema 變更

你想在 events.enriched 這個 Kafka topic 的 schema 加一個 user_segment 欄位。傳統做法:開 event-ingestion-service 改 schema,去 stream-processor 改處理邏輯,去 analytics-api 改查詢介面,每個 repo 開一個 PR。

有了 Virtual Monorepo:

1
2
你:在 events.enriched 加 user_segment 欄位,
找出所有需要同步改的地方。

Claude 會自己跟著地圖去看 producer、consumer、和下游消費者,列出所有需要改的檔案。它不能幫你開 PR(每個 repo 還是要各自 commit),但它可以給你完整的 todo list。

場景二:跨服務 bug 追查

ops dashboard 顯示某個指標不對。指標的計算經過了三個服務。

1
2
你:ops-dashboard 上的 daily-active-users 數字
今天比昨天少了 30%,幫我從前端追到資料源頭。

Claude 從 frontends/ops-dashboard 開始看 query,跟著 API 呼叫到 services/analytics-api,再追到 services/stream-processor 的去重邏輯,最後可能發現是 event-ingestion-service 改了 client 端的事件命名。沒有跨 repo 視野,這條追查鏈每一段都得你手動切換 cwd。

場景三:infrastructure 跟 application 一起改

你發現 Kafka 的某個 topic partition 數不夠。partition 數定義在 infrastructure/streaming-cluster/terraform/topics.tf,但消費這個 topic 的服務 services/stream-processor 也假設了 partition 數做了 hash routing。

地圖讓 Claude 同時看到這兩端,給你一份「Terraform 改完之後,application 還要改哪些常數」的 checklist。


兩個常見的踩坑點

坑一:CLAUDE.md 寫太長

第一次寫的人很容易把所有架構決策、歷史脈絡、命名約定都塞進 org 層級的 CLAUDE.md。然後跑 Claude Code 就會發現 context window 立刻吃掉一半,剩下的空間不夠 Claude 真正讀程式碼。

原則:org 層級只寫「導航資訊」——服務在哪、做什麼、依賴誰。決策脈絡放 README。命名約定放 team 層級。

坑二:忘記 sync repo

.repos 腳本只負責第一次 clone。之後每個服務的 main branch 一直在動,你 workspace 裡的副本可能過時。建議搭配 manimeta 這類 multi-repo 工具,可以一行指令 mani sync 把所有 repo git pull

如果你不想多裝工具,最低需求是寫一個 sync.sh

1
2
3
for dir in services/* infrastructure/* frontends/*; do
(cd "$dir" && git pull --rebase origin main || echo "Skip: $dir")
done

跟其他 AI 工具比的差異

Cursor 和 GitHub Copilot 也支援多 repo workspace。差異在於:

  • Cursor 預設行為是「開哪個 workspace 看哪些檔案」,沒有層級式的 context 設計。你可以用 .cursorrules 但只有一層,且不會自動繼承。
  • GitHub Copilot 的 context 主要靠開啟的 tab 和最近編輯過的檔案,沒有顯式的「系統地圖」概念。
  • Claude CodeCLAUDE.md 是顯式設計成可以分層繼承的,這是 Virtual Monorepo 模式能成立的關鍵。

如果你的系統真的是 5 個 repo 以下,三種工具差不多。但 repo 數量越多,分層 context 的價值越明顯。


學會這個之後可以接著做什麼

理解了 Virtual Monorepo 之後,下一步可以往三個方向延伸。

方向一:把這個地圖跟 MCP 整合。寫一個 MCP server,把 CLAUDE.md 裡定義的服務關係變成可查詢的 graph。Claude 不只「讀地圖」,還能「查地圖」——例如問「找出所有依賴 user-service 的服務」,MCP 直接給結構化答案。

方向二:用 git worktree 開平行 session。一邊讓 Claude 在 services/A 改 schema,另一邊讓另一個 Claude 在 services/B 改對應的 consumer。各自有獨立 context,互不污染。之前寫過 Claude Code Git Worktrees 完整教學有更深入的細節。

方向三:把地圖當文件來自動化維護。讓 Claude 每次開新 PR 時,順便檢查 CLAUDE.md 裡的依賴描述是不是還準確。設計成 CI step,地圖就不會腐化。

回到一開始的場景:那個 35 個 repo 的系統,第一個禮拜你需要花一兩天寫地圖,但之後每次跨服務任務都會省回大量切換 cwd 和重新跟 AI 解釋脈絡的時間。投資跟回收的比例,比合併成 monorepo 划算太多。

原文來源:The “Virtual Monorepo” Pattern — Owen Zanzal, Medium
原文來源:Structuring Claude Code for Multi-Repo Workspaces — karun.me
參考來源:Claude Code Docs — Overview