Featured image of post Claude Code 多仓库协作最佳实践

Claude Code 多仓库协作最佳实践

AI 多端开发的仓库该怎么组织?工作台+submodule、工作台+ignore 文件夹、Monorepo 三种拓扑怎么选;选定之后 /add-dir 怎么用、CLAUDE.md 怎么分层、上下文怎么不爆炸。一份可以直接抄的决策清单与反模式对照。

适用范围:使用 Claude Code 在 Monorepo、多独立 Git 仓库、git worktree、submodule 等场景下进行协作开发。

先立个基调:多仓协作难不倒 AI,出问题的几乎都在人这一侧:拓扑没选对,约定没写成机制,上下文没做卫生。


一、先选仓库拓扑:结构决定协作方式

后面所有实践都建立在一个前提上:仓库结构本身选对了。AI 多端开发(前端 + 后端 + 小程序……)的仓库组织,主流是三种方案,外加一个轻量变体。

1.1 方案一:工作台仓库 + git submodule(最重)

按端拆分独立仓库,另建一个"工作台"仓库,把各端仓库以 submodule 引入,统一在工作台开发:

1
2
3
4
5
6
workspace/                  # 工作台仓库
├─ .gitmodules
├─ CLAUDE.md                # 跨仓协作约定
├─ scripts/                 # 联调脚本
├─ frontend/                # submodule → 前端仓库
└─ backend/                 # submodule → 后端仓库

这套方案真正买到的只有一样东西:版本锁定,工作台记录每个子仓的精确 commit。

  • 优点:git 原生强制锁定,“哪个前端版本配哪个后端版本"可复现、可回滚;CI checkout 工作台即得完整组合,集成测试可以直接自动化;各子仓权限与发布边界清晰
  • 缺点:两步提交、detached HEAD、update --remote,submodule 的这套摩擦一样不少;提交规则要显式写进工作台 CLAUDE.md 约束 AI,否则容易两步提交漏一步(见第六节);日常联调本要同步演进到最新,锁定旧 commit 反而是摩擦源

适用:多团队分治、发布需要组合版本审计。没有这个需求,摩擦全是白付的。

1.2 方案二:工作台仓库 + gitignore 普通文件夹(甜点位)

在方案一基础上去掉 submodule:各端仓库直接 git clone 进工作台目录,每个被忽略的文件夹内部都是一个完整的独立 git 仓库,有自己的 .git/、完整历史和远端;工作台的 .gitignore 只是让工作台对它们视而不见,工作台仓库本身只版本化"胶水层”:

1
2
3
4
5
6
7
workspace/                  # 工作台仓库(只版本化胶水)
├─ .gitignore               # 忽略 frontend/ backend/
├─ CLAUDE.md                # 跨仓约定、联调流程
├─ docs/api-contract.md     # 接口契约
├─ docker-compose.yml       # 联调环境
├─ frontend/                # 独立 git 仓库,被 ignore
└─ backend/                 # 独立 git 仓库,被 ignore

本质是 “meta repo” 模式(Android repometa 这类工具的手工版)。

  • 优点:零 submodule 摩擦;所有子仓都在主工作目录之内,AI 全部可见,不需要任何 /add-dir,各子仓自己的 CLAUDE.md 也会按需自动加载(见 4.3 节);跨仓胶水(CLAUDE.md、联调脚本、接口契约)有地方版本化;子仓各自保留独立历史与独立部署
  • 缺点:工作台是空壳、不可复现;没有版本锁定;集成 CI 跑不起来;嵌套仓库下 AI 容易搞错提交目标

缺点看着多,但每条都有便宜的机制兜底:

  • 工作台是个空壳,不可复现:clone 下来只有胶水层。但这条在 AI 时代成色大打折扣,把子仓清单(目录名、仓库地址、分支)写进工作台 CLAUDE.md 就够了:

    1
    2
    3
    
    ## 子仓清单
    - frontend/ → git@github.com:org/frontend.git(main)
    - backend/  → git@github.com:org/backend.git(main)
    

    新环境 clone 工作台后,让 Claude 照单把子仓拉齐即可;子仓一旦就位,每个目录都是完整 git 仓库,remote、分支这些信息 git 本身就带着,不需要额外维护。想要不依赖 AI 的确定性引导,再升级成 repos.yaml + setup.sh(Android repo 工具的 manifest.xml 干的就是这件事)。脚本化还有个副产品,穷人版版本锁定:联调到里程碑时,把各子仓当前 SHA 写进 lockfile 提交,“组合版本审计"的需求就覆盖了大半。方案一的适用面被进一步压缩到只剩"需要 git 原生强制锁定"一种。

  • AI 容易搞错目标仓:嵌套 git 仓库下,在工作台根目录跑 git add / git commit,作用的是工作台仓库而不是子仓。除了在工作台 CLAUDE.md 写明「每个子目录是独立 git 仓库,进入子目录分别提交」,更硬的兜底是用 PreToolUse hook 直接拦截工作台根目录的 git 提交。约定不如机制。

  • 集成 CI 跑不起来:CI checkout 工作台得到的同样是空壳。跨端集成测试要么靠 manifest 驱动多仓 checkout,要么退回各子仓自己的 CI(对选型的影响见 1.5)。

适用:个人 / 小团队 AI 开发、前后端紧耦合日常联调。

1.3 方案三:Monorepo(新项目默认)

所有端放同一个仓库。AI 时代它不只是"最简单”,更大的红利是跨栈原子提交:改一个 API,同时改掉服务端实现和所有客户端调用,接口漂移在物理上不可能发生。

  • 优点:跨栈原子提交,一个 commit、一次 review、一套 CI 验证全链路;没有任何跨仓机制要维护,上手心智成本最低;上下文对 AI 完整连续
  • 缺点:部署与发布耦合;访问权限只能到目录粒度;项目扩大后仓库内容持续膨胀,CI/CD 越跑越重,任何一端的改动默认触发全仓构建发布,不做增量裁剪时流水线时长随仓库体积线性上涨,这也是团队和代码量长大后必须付构建工具链(turborepo / nx 的 affected 增量构建)门槛的原因;仓库大了 AI 上下文有压力

最后一条的直接回应是 git sparse-checkout:只检出在做的子树,Claude 的可见范围和上下文随检出范围收缩。各端约束用嵌套 CLAUDE.md 分层管理(见 4.3 节)。

适用:新项目、有选择权 → 默认选它。

1.4 轻量变体:平级目录 + additionalDirectories

不建工作台,各仓平级摆放,在主仓 .claude/settings.json 里持久声明兄弟仓(写法见 2.2 节)。四个方案里最轻的一个,连工作台仓库都不用建,配一次每次启动自动生效。代价:跨仓胶水无处版本化;辅仓 CLAUDE.md 默认不加载(见 4.3 节);../ 相对路径依赖每个人本地目录摆放一致,仓库本身无法强制。

适用:“一主一辅"形态,比如主要在后端开发,偶尔看前端怎么调用。

1.5 选型一览

方案一句话判据
一:工作台 + submodule需要组合版本审计、可复现的集成点
二:工作台 + ignore 文件夹紧耦合多仓日常联调,跨仓胶水多到值得一个仓库来管
三:Monorepo新项目、有选择权,默认选它
四:平级目录 + additionalDirectories一主一辅,偶尔跨仓看看

方案二和方案四的分界线就一句话:跨仓的胶水(脚本、契约、共享规范)多到值得专门一个仓库来管吗?

还有一条隐形判据是 CI:如果跨端集成测试必须在 CI 里强制执行,可选项基本只剩方案一(submodule 可以直接 checkout)和方案三(本来就是一个仓库);方案二要靠 manifest 驱动多仓 checkout 才能补上这块。反过来,集成验证只在本地联调时做的团队,方案二没有任何损失。


二、/add-dir 命令

2.1 定位

把 Claude Code 从「单仓库工具」扩展为「多仓库协调器」:将当前会话之外的目录动态纳入工作上下文。

2.2 三种加入方式

  • claude --add-dir <path>...:启动即生效,适合本次任务的已知工作集

  • /add-dir <path>:会话内动态追加,适合临时引用参考实现,重启即清

  • settings.json 持久化:固定不变的工作集(如前后端两仓永远一起开)写进仓库级 .claude/settings.json,每次启动自动生效:

    1
    2
    3
    4
    5
    
    {
      "permissions": {
        "additionalDirectories": ["../frontend-repo"]
      }
    }
    

2.3 权限与边界

  • 额外目录与主工作目录共享同一权限模型:自动可读,编辑权限沿用 settings.json

  • 不会出现额外的权限提示 ⇒ 不要随手加敏感目录(如 ~/.ssh、家目录、根目录)

  • 工作目录内的敏感文件(.env、密钥、凭据)用 permissions.deny 挡住。这是安全防线,搜索效率上并不需要它(.gitignore 已经覆盖,见 7.1 节):

    1
    2
    3
    4
    5
    
    {
      "permissions": {
        "deny": ["Read(./.env*)", "Read(./secrets/**)"]
      }
    }
    
  • 额外目录的 CLAUDE.md 默认不加载,开启方式见 4.3 节


三、多仓库协作核心决策

3.1 同会话 vs 多会话

仓库关系仓库数量推荐策略
紧耦合(共享库、接口实时联调)≤ 3同会话 + /add-dir
松耦合(独立功能、各自上线)任意每仓一会话
主仓 + 临时参考仓≤ 5主仓会话 + 临时 /add-dir 参考仓
大型 Monorepo(单仓 > 50MB)1单会话 + 嵌套 CLAUDE.md + sparse-checkout 收缩可见范围
跨语言异构仓库≥ 3多会话 + MCP server / 共享工具桥接

3.2 跨仓代码引用策略

引用类型推荐方案避免
类型 / 接口契约抽共享包(npm / go module / proto)在多仓重复声明
工具函数 / 通用库独立仓库 + 包管理器引用copy-paste
紧耦合代码(前后端同步演进)git submodule长期 /add-dir
一次性参考别人怎么写临时 /add-dir + 注释标注来源不留痕直接 copy

3.3 契约优先:比拓扑更高杠杆

3.2 里"抽共享包"解决的是复用,真正消灭接口漂移的是契约优先(schema-first):OpenAPI / proto 文件作为唯一事实源,两端代码由生成器产出。AI 改接口时改的是 schema,改完跑 codegen,两端实现跟着走,“前端以为的接口"和"后端实际的接口"物理上就是同一份文件。

它对拓扑选型也有影响:接口漂移这个多仓最大的痛点被工具链消掉之后,拓扑本身的权重就下降了,松耦合方案的最大缺陷不复存在。落点上它和拓扑方案二配合最好:契约文件正好放工作台仓库里版本化。


四、CLAUDE.md 与 settings.json 组织

4.1 分层原则

  • 用户级 ~/.claude/:个人编码风格、全局偏好与 permissions 兜底

  • 仓库级 <repo>/CLAUDE.md + .claude/settings.json:该仓库的技术栈、构建/测试命令、编码规范、领域知识,以及仓库特定权限、hooks

  • 跨仓共通规范单独放一个文件,用 @import 语法引进各仓库的 CLAUDE.md(支持相对/绝对路径,最多递归 4 层):

    1
    2
    
    # 本仓库 CLAUDE.md
    @../shared/team-standards.md
    

    这比"抄一份进每个仓库"或"放 wiki 里指望人去看"都可靠:规范只维护一份,各仓库引用同源。不要把跨仓库的内容写进单一仓库的 CLAUDE.md,会随仓库漂移。

4.2 权限管理

跨仓共享的 Bash / MCP 权限放用户级 settings.json,仓库特有的放仓库级;.claude/settings.local.json 是个人临时配置,不要提交进 git。

4.3 多仓 CLAUDE.md 加载

关键分界是子仓在不在主工作目录之内,两边机制完全不同:

  • 在主工作目录之内(方案一的 submodule、方案二的 ignore 文件夹、Monorepo 的子包):嵌套 CLAUDE.md 按需自动加载,Claude 读到某个子目录下的文件时,该目录的 CLAUDE.md 才被拉进上下文。不需要任何配置,还顺便省 token:没碰过的子仓,其约束也不占上下文。

  • 在主工作目录之外--add-dir / additionalDirectories 加入的目录):CLAUDE.md 默认不加载,需显式开启:

    1
    
    export CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1
    

    注意两个限制:值是 1 而不是 true;且只对 --add-dir / /add-dir 加入的目录生效,对 settings.json 里 additionalDirectories 配置的目录不生效。

⚠️ 多份 CLAUDE.md 同时加载可能产生约束冲突,提前对齐风格。


五、git worktree 场景

同一仓库多分支并行的首选:物理隔离、无需切换 / stash,每个 worktree 一个 Claude 会话(~/code/demo/demo-feat-x/demo-fix-y/ 平级摆放),CLAUDE.md 共用同源,任务上下文互不污染。

不想手动 git worktree add 的话,Claude Code 已把这套流程产品化:

  • claude --worktree-w:启动时自动创建/进入一个隔离 worktree,任务做完没有改动会自动清理
  • .worktreeinclude:声明哪些未跟踪文件(如 .env、本地配置)要拷贝进新 worktree,解决"新 worktree 缺本地配置跑不起来"的经典问题

跨 worktree 对比代码:/add-dir ../demo-feat-x,临时查看后清理。


六、git submodule 场景

这是拓扑方案一(工作台 + submodule)的日常形态:

  • 在外层仓启动 Claude,submodule 路径自动在工作目录内,无需 /add-dir
  • 但需注意:
    • submodule 更新需手动 git submodule update --remote
    • 修改 submodule 内文件 → 需要在 submodule 仓提交,再到外层仓更新引用
    • 在工作台 CLAUDE.md 写明「这是 submodule,提交分两步」,写一次每个会话自动加载,不用口头反复提醒

七、上下文管理

多仓本身并不额外费上下文:搜索按需进行,嵌套 CLAUDE.md 按需加载,没碰过的子仓几乎是免费的。真正要管理的是长会话的上下文污染:跨仓任务战线长,读过的文件、跑过的命令残留累积,稀释注意力、拉低回答质量。下面的动作说到底就两件事:少装进来,及时卸载。

7.1 缩小搜索面

  • 使用 Grep / Glob 工具精准定位,禁止整仓扫描
  • 构建产物、依赖目录不需要任何额外配置:搜索工具默认尊重 .gitignore。顺带辟个谣:Claude Code 没有 .claudeignore 文件,网上不少文章以讹传讹

7.2 主动卸载上下文

  • 切换任务时用 /clear 清空上下文,效果等同重启,不用退出进程
  • 长任务中途上下文臃肿但还不能丢,用 /compact 智能压缩(保留结论、丢弃过程)

7.3 Agent Teams 分工

仓库多到单会话装不下时,改用分工:主 agent 只做调度与结果汇总,每个 teammate 负责一个仓(其会话只持有该仓上下文),避免单会话上下文爆炸。团队无需手动创建或销毁:通过 Agent 工具派生 teammate 时自动建团,任务结束自动回收。


八、决策清单(Quick Reference)

启动多仓任务前,先回答:

  • 仓库拓扑定了吗?(四选一,见第一节;新项目默认 Monorepo)
  • 这些仓库是紧耦合还是松耦合?数量是否 ≤ 3?若否,多会话或 agent teams
  • 接口契约是否 schema 化(OpenAPI / proto + codegen)?共享代码是否抽包?
  • 跨端集成测试要不要在 CI 强制执行?(直接影响拓扑选择,见 1.5 节)
  • 每个仓库是否都有自己的 CLAUDE.md?跨仓规范是否用 @import 同源引用?
  • 敏感文件(.env、密钥、凭据)是否用 permissions.deny 挡住?

九、反模式总结

反模式后果正确做法
拓扑没想清楚,靠 /add-dir 缝合结构问题被会话技巧掩盖,越缝越乱先按第一节选定拓扑
把 5 个以上仓库 add 进同一会话上下文爆炸agent teams 分工
/add-dir 替代版本化的共享包接口漂移、回归风险抽包 + 包管理器
接口靠口头 / 文档同步两端各写各的类型,必然漂移schema 单一事实源 + codegen
跨仓库写在同一 CLAUDE.md仓库迁移 / 拆分时失效仓库各自 CLAUDE.md + @import
把敏感目录 add 进会话凭据 / 密钥泄露给模型严格审查 add 路径
长会话不清理,反复 add / 切任务token 浪费、回答质量下降任务边界 = 会话边界,切任务先 /clear

参考

使用 Hugo 构建
主题 StackJimmy 设计