AI-SDLC 实践 05|开发阶段的工程结构:在一个主仓里跨多个仓库写代码

完成一个需求,常常要同时改好几个仓库:

  • 全栈项目:前端、后端服务、接口定义各在一个仓库,有时还有配置仓库;
  • 移动端组件化:壳工程、业务组件、基础组件库分在不同仓库,一个功能经常同时改业务组件和基础库;
  • 同一个功能要在几个端各实现一遍,每个端一个仓库。

而 Agent 每次只在一个目录里启动,只看得到这个目录里的代码。

常见的做法是用 MonoRepo:把所有代码合进一个仓库,Agent 在一个目录下就能看到全部代码。但这解决不了存量项目的问题。存量项目早就分散在多个 git 仓库里,完成一个需求,还是要跨几个仓库改代码。

所以开发阶段先要解决的是:不合并仓库,怎么让 Agent 跨仓库写代码。具体是三个问题:Agent 在哪里启动,仓库放在哪里,代码写在哪里。

我们的做法是用一个主仓统一管理:

  • Agent 在主仓里启动;
  • 主仓用一份清单登记所有仓库,每次交付只拉取涉及的那几个;
  • 代码写在每个仓库从基线建出来的 worktree 里,原工程目录不动。

11 月 10 日,Matt Pocock 在 X 上分享了他的做法 Home Repo,也是这个方向:一个主仓存放所有 skill、引导文件和文档,Agent 在主仓里运行,主仓的 AGENTS.md 里写明其他仓库在哪。下文会逐项和它对照。

本篇讲这几件事:

  • 主仓里放什么;
  • 仓库怎么登记,每次交付读哪些仓库、写哪些仓库;
  • 代码写在哪里:每个可写仓库一个 worktree,原工程目录不动;
  • 构建和测试命令怎么统一;
  • 同一个主仓怎么同时支持 Claude Code 和 Codex。

其中有两处是我们踩过的坑:worktree 占满磁盘,以及 worktree 里的改动没合回导致丢代码。

开发阶段分几篇讲。本篇讲工程结构,后面两篇讲知识库和 D2C。

Matt Pocock 的 Home Repo

Matt 在这条推文里说,很多人问他怎么让 Agent 同时在多个仓库里工作,他的办法是 Home Repo:

  • 一个单独的仓库作为主仓,所有 skill、引导文件、wiki 都放在这里;
  • Agent 在主仓里运行;
  • 主仓的 AGENTS.md 里给每个仓库写一句话,比如「要用 skills,就把 mattpocock/skills 克隆到 ~/repos/skills」。

他列了三个好处:引导文件和工具集中在一处,所有 Agent 在同一个地方运行,可以对多个仓库做大范围的改动。推文配图里,一位学员说自己维护 38 个仓库,只是把它们克隆进主仓、加进 gitignore,效果就很好。

回复区里,Matt 又补充了几条:

  • 每个仓库自己的东西,比如构建命令,放在各自仓库里,主仓里只写这些仓库在哪;
  • 同一个仓库要并行开发几个功能,就在克隆里用 worktree;
  • 团队使用时,主仓归团队所有,所有人都在主仓里运行 Agent;
  • 跨仓库的事项记录在主仓;
  • 用完的 worktree,他的做法是直接清掉。
  • 有人问 MonoRepo 怎么样,他说也很好,只是需要大量配套工具,而且不一定能做成。

回复区也有人反过来做:在各个仓库里运行 Agent,每个仓库的 AGENTS.md 写明主仓在哪、该读主仓里的哪些文件。理由是在主仓里运行时,Agent 总想去翻其他仓库。本篇不讨论这两个方向的取舍,我们选的是在主仓里运行。

下面按我们的工程结构逐项讲,每一项都和 Matt 的做法对照。

主仓里放什么

我们的主仓就是 Harness 仓库本身,团队共用。里面放的是:

  • skill 和子 agent 的角色文件;
  • 通用规范和各业务的知识文档;
  • 各个阶段和工作流的定义;
  • 每类仓库的构建、测试命令脚本;
  • 团队配置。

Agent 在主仓里启动。宿主读取的入口文件(AGENTS.md、CLAUDE.md)由模板生成,只写怎么推进工作流、什么情况下读哪份文档,不列仓库清单。

每次交付的 spec、技术方案、任务图和执行记录,放在主仓里一个被 git 忽略的目录下。这对应 Matt 说的「跨仓库的事项记录在主仓」。

团队的东西和个人的东西分开放。团队配置所有人共用;仓库在本机的路径、个人想调整的设置,写在不提交的本地文件里,不改团队配置。

用一份仓库清单登记二十多个仓库

Matt 是在 AGENTS.md 里给每个仓库写一句话,说明它克隆在哪个目录。我们改成一份单独的仓库清单,目前登记了二十多个仓库。每个仓库记下这些字段:

  • 所属业务线;
  • 角色:业务仓库、基础仓库还是工具仓库;
  • 读写权限:这个仓库允许改,还是只能读;
  • 远端地址和默认分支;
  • 平台和语言;
  • 可以执行的命令:编译、类型检查、测试、lint 等。

只写一句话不够,是因为 Agent 要知道的不只是仓库在哪,还有能不能改、从哪个分支开始、用什么命令编译和测试。只写一句话,这些都要 Agent 自己猜;写成清单里的固定字段,CLI 可以逐项检查。

仓库在本机怎么放,分两种情况:

  • 开发者本机已经有这个仓库:路径写在本地文件里,在主仓里专门放仓库的目录下建一个软链接指过去;
  • 本机没有:由 CLI 克隆到主仓里的一个专用目录。

放仓库的目录被 git 忽略,主仓的提交里不会混进业务代码。这和那位学员「克隆进主仓再 gitignore」是同一个做法,只是多了一份清单记录每个仓库的规则。

每次交付先划定范围:读哪些仓库、写哪些仓库

二十多个仓库,一次交付通常只涉及其中几个。任何 git 操作之前,必须先明确三项:这次涉及哪些业务线、哪些仓库、其中哪些仓库可写。少一项,CLI 直接拒绝执行,不会因为一次交付把所有仓库都拉取一遍。

涉及的仓库分两种处理:

  • 只读的仓库:在需求阶段确认的基线提交上,建一个分离 HEAD 的快照,用来查代码、对照实现。比如这次只改仓库 A,仓库 B 只用来对照接口和调用方式;
  • 可写的仓库:从基线提交建本次交付的分支和 worktree。

基线在需求阶段就固定了(第 03 篇讲过)。开发阶段不 pull、不 rebase、不追远端最新提交。确认之后远端又有新提交,只记录差异,不改基线,也不因此阻塞开发。

每个可写仓库一个 worktree,原工程目录不动

开发开始前,Agent 会问一次:用独立的 git worktree,还是直接改现有的工程目录。问题里写明具体是哪个工程,以及两种选择分别会发生什么:

  • 用 worktree:新建一个独立的工作目录完成本次修改,原工程目录里的文件保持原样;
  • 不用 worktree:直接修改现有工程目录里的文件。

回答记录下来,同一次交付不再问。团队配置里这一项默认是「问」,也可以配成总是用 worktree,或者总是改现有目录。直接改现有目录时,写入范围在开始前必须是干净的,范围外已有的修改保持不动。

用 worktree 时,按下面的方式组织:

  • 每个可写仓库从基线提交建一个集成分支和对应的 worktree,作为本次交付唯一的集成目标;
  • 并行写代码的任务,各自再从同一个基线建一个任务分支和 worktree,两个执行者不共用同一个目录或分支;
  • 有依赖的任务按顺序推进:上游检查通过后合入集成分支,下游从最新的集成提交继续,不从没通过检查的分支继续。

每个仓库的对应关系写进一份绑定记录:原工程目录的路径、分支、HEAD、有没有未提交的改动;开发用的目录、分支和基线提交。

两个真实的例子:

  • 一次交付同时改仓库 A 和仓库 B,两个仓库各有一个 worktree、一个任务分支,绑定记录里有两条;
  • 另一次,仓库 A 的原工程目录正停在另一个分支上,还有开发者没提交的改动。worktree 从确认过的基线建出来,原目录里的改动没受影响。

另外两条规则:不在受保护的分支(main、master、develop 这类)上开发、合并或推送;获得授权的交付合入之前,原工程目录保持不变。

worktree 踩过的两个坑:磁盘空间和丢代码

磁盘空间不足

这是最大的坑。每个 worktree 都是一份完整的源码,装完依赖、跑过构建之后,体积会成倍增长。本机实测:同一个仓库,只读快照 14 MB,写代码用的 worktree 装完依赖、跑过构建后是 1.8 GB;另一个仓库的 worktree 有 1.3 GB,几乎全是依赖目录。

几次交付同时进行,每个可写仓库一个集成 worktree,并行任务再各开一个,磁盘很快就满了。Codex 的官方文档也提到这一点:每个 worktree 都有自己的一套仓库文件、依赖和构建缓存,很占磁盘,所以 Codex 应用默认只保留最近 15 个由它管理的 worktree。

现在的做法:

  • 建新 worktree 之前,先查已有的 worktree,基线和范围相同、没有被占用的直接复用;
  • 源码和能重建的生成物(依赖目录、构建目录)分开处理:源码还要用就留着,生成物可以先回收;
  • 交付收尾时,必须盘点本次交付登记过的所有 worktree,能回收的回收,回收前后各量一次占用的字节数,写进清理报告;
  • 只按登记记录找要清理的 worktree,不按目录名或修改时间判断「看着旧」就删,也不扫描其他交付的目录;不一次删光所有依赖目录,不清共享的全局缓存。

改动没合回,丢代码

丢代码有两个来源。

第一个来源:worktree 里的改动一直没提交、没合回集成分支。worktree 一旦被清理,或者后续换了一个副本继续写,这部分改动就没了。

现在的做法:

  • 每个任务检查通过后合入集成分支,下游任务从最新的集成提交继续;
  • 每个任务完成时,记录它的 worktree、HEAD,以及是否已经合入;
  • 清理时只删同时满足这几条的 worktree:没有改动,没有未跟踪的文件,HEAD 已经包含在保留的集成分支里,没有别的任务或进程在用;
  • git worktree remove 本身只删干净的 worktree,有改动的要加 --force 才能删。我们不加 --force,也不用 reset、clean 绕过 git 的拒绝;
  • 用 squash 或 cherry-pick 合进去的改动,不能当作「已经包含」的证明;
  • 有改动或者没合入的 worktree 一律保留,写明下一步该做什么。

第二个来源:子 agent 的改动写到了别的地方,没写进任务的 worktree。

Claude Code 的子 agent 角色有一个选项 isolation: worktree。打开后,Claude Code 每次启动这个子 agent,都会自动给它另建一个副本,让它在副本里改代码。我们早期的角色文件打开了这个选项。

问题出在这个副本建在哪:Claude Code 复制的是启动目录所在的仓库,默认从这个仓库的默认分支开出来(Claude Code 文档)。我们的启动目录是主仓,所以子 agent 拿到的是一份主仓的副本,而不是任务对应的业务仓库 worktree。它的改动全写进了这份副本,任务 worktree 里什么都没有。

10 月 19 日,我们从角色文件里去掉了这个选项。现在的规则是:

  • 子 agent 只在流程分配给它的 worktree 里写代码,Claude Code 不再给它另建副本;
  • 返修时继续写原任务的 worktree,不新建副本;
  • 已经启动、绑错了目录的子 agent 不再复用,也不把它的补丁搬回来,重新派发一个;主 Agent 不代替它写业务代码。

Matt 对用完的 worktree 是直接清掉。我们因为这两个坑,把清理放到交付收尾统一做,按上面的条件逐个判断,交付那篇再细讲。

构建和测试命令由主仓统一声明

这一点和 Matt 不同。他在回复里说,构建命令这类仓库自己的东西放在各个仓库里。我们是在仓库清单里给每个仓库声明一组命令:编译、类型检查、测试、lint、运行、打包等。命令脚本按仓库类型放在主仓,同一类仓库共用一套脚本。

原因是仓库多、技术栈不止一种。前端、后端服务、客户端,构建和测试的方式各不相同,让 Agent 自己猜命令很容易出错。声明过的命令可以审计,结果也能复现。

执行时守几条规则:

  • 只执行声明过的命令,不自己编构建、测试、安装、启动命令;
  • 命令的工作目录是获得授权的目标仓库,或者它的 worktree;
  • 保留完整的日志和退出码,主会话里默认只返回最后 120 行;
  • 编译、类型检查、测试分别按要求执行,不能互相替代。

同一个主仓同时支持 Claude Code 和 Codex

第 01 篇讲过通用做法:角色、skill、hook 这些配置只写一份,由 CLI 生成两个宿主各自需要的文件。这里只讲和多仓工作区有关的几点。

主仓配置里声明宿主和能力。 每个宿主一份适配配置,列出它用得上的能力。Claude Code 这边生成 CLAUDE.md、角色、hook、MCP 配置和 skill;Codex 这边生成 AGENTS.md、config.toml、skill 和子 agent 角色。

跨仓的事不依赖某个宿主的独有功能。 仓库清单由 CLI 读取,worktree 由 CLI 调 git 建,构建和测试是普通的脚本。不管在 Claude Code 还是 Codex 里运行,跨仓的流程都一样。Claude Code 给子 agent 自动另建副本的选项也不再使用,原因见上一节。

各宿主的会话设置写在同一份配置里。 比如 Claude Code 的自动压缩窗口设为 100 万 token,用到 40% 时压缩;Codex 打开多 agent,单个会话最多 9 个子 agent 线程并发,MCP 服务启动时带上主仓的路径。

skill 正文以主仓为准。 宿主目录里生成的 skill 只用来让宿主发现它,执行时读的是主仓里的源文件。一次交付开始后,用到的 skill 固定文件路径和 hash,之后主仓里的 skill 再更新,也不影响这次交付。

和 Home Repo 的对照

Matt 的 Home Repo 我们的做法
Agent 在哪运行 主仓 主仓
仓库怎么登记 AGENTS.md 里每个仓库一句话 单独的仓库清单,记权限、分支、命令等字段
一次交付涉及哪些仓库 推文没提 先划定读哪些、写哪些,否则不执行 git 操作
代码写在哪 每个克隆里用 worktree 每个可写仓库从基线建集成 worktree,并行任务各开一个
用完的 worktree 直接清掉 交付收尾统一盘点,有改动、没合入的保留
构建命令 放在各仓库里 主仓按仓库类型统一声明
多个宿主 推文没提 同一份配置生成 Claude Code 和 Codex 各自的文件
团队 主仓归团队,所有人在主仓运行 同样;个人路径和偏好放在不提交的本地文件里

下一篇讲开发阶段的知识库。

相关推荐
boombb31 分钟前
AI Engineer in robotic系列:从地图导入到机器人执行:导览平台 MVP 技术设计与落地
机器人·ai编程
爱勇宝1 小时前
Claude Fable 5.2 发布后,开发者该关注什么
程序员·ai编程
挖掘狂人1 小时前
AI 把代码写得飞快,然后呢?聊聊我验收生成代码的一套流程
人工智能·程序员·ai编程
莪_幻尘1 小时前
4 个 3D 游戏,总体积 3.82MB:拆解 Claude 的 one-shot 工程链路
前端·ai编程·three.js
lucas_AI2 小时前
22 个开源项目公开拒绝 AI 代码:vibe coding 的账单,维护者不想买了
开源·ai编程
一缕82年的清风3 小时前
Claude Code 跑 DeepSeek 还带自动切号?开源 Magpie 彻底把大厂的协议护城河给拆了!
chatgpt·claude·gemini
唐老板3 小时前
Postgres 慢查询,先取证再动手
数据库·ai编程
聪明的笨猪猪16 小时前
实际工程问题:AI 生成 Spring Cache(@Cacheable / @CachePut / @CacheEvict)缓存注解踩坑
java·经验分享·面试·ai编程
逻辑与温度17 小时前
花了三毛六,我把 Claude Code 和 DSH 测了三轮:同一个 DeepSeek,两个壳差距有多大
claude·deepseek