三步给 Worktrunk 接上 CI 网关:让 Agent 每次改动都先过测试再合入主干
【免费下载链接】worktrunkWorktrunk is a CLI for Git worktree management, designed for parallel AI agent workflows项目地址: https://gitcode.com/GitHub_Trending/wo/worktrunk
多 Agent 并行的开发模式已经跑起来了:Claude Code、Codex 各自占一个 worktree,互不干扰地改代码、写提交、推分支。但真正的瓶颈从来不是"谁能改得快",而是"谁改的能安全地进主干"。Agent 不会等测试,不会看 CI 红灯,它只会做完任务然后报告"可以合了"。如果合入动作没有一道强制门禁,主干很快就会被一批"本地能编译、远程全红"的提交污染。
Worktrunk 给出的答案是把这道门禁做成命令级网关:用pre-merge钩子在合入动作发生前强制执行测试,用wt list --full让每个 Agent 分支的 CI 状态随时可见,再用wt merge的固定管线把"测试通过→合入→清理"变成不可跳过的单条命令。本文从源码出发,拆解三步落地方案,附带可直接抄走的配置。
一、网关设计思路:把门禁放在合入动作内部,而不是外部约定
先想清楚一个前提:为什么不能用"团队约定"来兜底?因为约定对 Agent 无效。Agent 不会自觉遵守"合入前跑一遍测试"的流程规范,它只会执行被编排进命令链里的步骤。所以门禁必须长在合入这个动作本身里——要么合入前跑测试,要么不允许合入。这就是网关的本质:在信任边界上做强制校验。
Worktrunk 把这道校验做进了wt merge的固定管线里,merge.rs 的执行顺序是:
- Commit/Squash— 未提交改动先被收敛成提交(默认 squash,合并前的工作树改动会先备份到
refs/wt-backup/<branch>); - Rebase— 把分支变基到目标分支之上;
- Pre-merge 钩子— 变基之后、真正合入之前执行,
FailureStrategy::FailFast,任何一个命令失败都会中止整个合入; - Merge— 快进推送到目标分支(
--no-ff时创建合并提交); - Pre-remove / Cleanup— 合入成功后移除 worktree 与分支;
- Post-钩子* — 后台运行,用于部署通知等非阻塞动作。
关键在第三步:pre-merge是阻塞型钩子,失败即中止,而中止发生时分支还停在原处、目标分支未动分毫——这就是"先过测试再合入主干"的语义保证(管线细节见 merge.md 的 Pipeline 一节)。
配套的可观测性同样重要。Agent 分支推上去之后,测试到底过了没有?wt list --full的 CI 列直接回答这个问题:每个分支显示其 PR/MR 编号(GitHub/Gitea/Azure 为#3035,GitLab 为!3035),并用颜色编码管道状态——绿色 passed、蓝色 running、红色 failed、黄色 conflicts、灰色 no-ci、⚠表示拉取失败(list.md)。也就是说,网关不是只在合入那一刻亮红灯,而是让每一个 Agent 分支的 CI 状态在整个生命周期里都处于可视状态。
二、命令级对接:三步配置,把测试变成合入的前置条件
第一步:声明项目钩子,写入.config/wt.toml
Worktrunk 的钩子支持三种 TOML 形态:字符串是单条命令,表是并发执行的多个命令,[[hook]]序列是带依赖顺序的管线。一个最简网关长这样:
# .config/wt.toml [[pre-merge]] test = "cargo nextest run" lint = "cargo clippy -- -D warnings" [[pre-merge]] build = "cargo build --release"第一个块里test和lint并发执行;第二个块在它们全部通过后才跑build。pre-merge钩子运行时,{{ branch }}指向被合入的特性分支,{{ target }}指向合入目标,模板变量可以用{{ branch | hash_port }}这类过滤器动态生成测试端口(hook.md)。
第二步:打通 forge 识别,让 CI 状态找得到门
pre-merge只管本地校验,而远程 CI 状态的可视化依赖 Worktrunk 对代码托管平台的识别。平台识别集中在 ci_platform.rs:ForgeKind枚举覆盖 GitHub、GitLab、Gitea 和 Azure DevOps,解析优先级是配置 > 分支所属 remote > 主 remote URL 主机名。也就是说,GitHub/GitLab/Gitea 这类带品牌名的主机可以自动识别,但自托管实例(比如git.company.example上的 Forgejo)需要在项目配置里显式点名:
# .config/wt.toml [forge] platform = "gitlab" # 或 github / gitea / azure-devops hostname = "git.company.example"多个仓库共享同一个自托管主机时,也可以在用户配置里用[projects."git.company.example/*"]模式一次声明,仓库自身的[forge]永远覆盖用户级条目(config.md)。
识别到平台后,CI 状态的实际查询走各平台的官方 CLI:gh、glab、tea、az。查询策略是先查 PR/MR,再回退到分支工作流:分支可能作为某个 PR 的 head 时,先问 PR 的检查结果;没有 PR 且分支有上游时,再查分支管道本身(platform.rs)。PrStatus里还带一个is_stale字段,本地 HEAD 与远端不一致时状态会变暗显示——这在多 Agent 场景下非常关键:它提醒你"CI 绿灯对应的提交不是本地这份"。
第三步:预批非交互执行,让 Agent 无需人工点确认
钩子是项目级命令,Worktrunk 的安全模型要求首次运行弹窗审批。但 Agent 是非交互的,弹窗等于卡死。两条路:
- 预审批:克隆项目后在 CI 编排环境里跑一次
wt config approvals add,把项目命令的批准持久化到~/.config/worktrunk/approvals.toml,之后合入不再弹窗(config.md 的 Approvals 一节); - 运行时绕过:给
wt merge传--yes,仅对当次运行授予同意且不落盘,下次仍会询问——适合"每轮合入都重新确认"的严格策略。
同样关键的是,Worktrunk 在调用gh/glab时显式设置了GH_PROMPT_DISABLED=1、NO_COLOR=1,并移除GH_FORCE_TTY,保证 forge CLI 在无 TTY 环境下既不弹交互也不输出终端专属格式(ci_status/mod.rs 的non_interactive_cmd)。这条容易被忽略,但对 Agent 流水线是生死线:一旦gh在批处理里弹了浏览器认证或交互提示,整条合入管线就会挂起。
三行命令的完整闭环:
$ wt config approvals add # 预批准项目命令,供无人值守使用 $ wt list --full # 观察各 Agent 分支的 CI 状态 $ wt merge # 测试通过后,一次完成合入+清理三、合并策略与失败回滚:网关的兜底机制
网关不是只拦"测试不过",它还要处理"测试过了但合入时出问题"的情况。wt merge的管线为此内置了三层防护。
第一层:变基冲突不销毁现场。默认管线会在合入前把分支变基到目标之上(step.md 的wt step rebase一节)。如果某个提交变基时产生冲突,Worktrunk 不会自动回滚或强推,而是把 rebase 停在冲突状态,保留 git 冲突标记,由你决定git rebase --continue(解决后继续)、git rebase --skip或git rebase --abort(放弃并回到变基前)。而且一旦检测到 rebase 进行中,wt merge会直接拒绝启动(merge.rs 的ensure_no_operation_in_progress),防止"带着一半变基的索引强行合入"这类二次破坏。
第二层:合入前的工作树改动有备份。默认的 squash 步骤会把工作树里未提交的改动先备份到refs/wt-backup/<branch>,再统一收敛成一个提交。这意味着即使合入中出了问题,未提交的内容也能从备份 ref 找回,不会随着 worktree 清理而蒸发。
第三层:合入策略可细分,回滚路径明确。wt merge的六个布尔开关覆盖了从"全自动"到"全手动"的策略谱系:
| 场景 | 命令 | 行为 |
|---|---|---|
| 默认(推荐给 Agent) | wt merge | squash + rebase + pre-merge 门禁 + 快进合入 + 清理 |
| 保留提交历史 | wt merge --no-squash | 不做 squash,逐个保留提交 |
| 要求线性历史、禁变基 | wt merge --no-rebase | 保留原图,要求目标能快进到分支尖端,否则拒绝 |
| 创建合并提交 | wt merge --no-ff | rebase 后创建 merge commit,保留半线性历史 |
| 合入后保留 worktree | wt merge --no-remove | 便于失败后立即诊断现场 |
| 完全跳过钩子 | wt merge --no-hooks | 显式放弃门禁(应仅用于人为决策的场合) |
注意到--no-rebase与--no-ff的组合语义:--no-rebase要求目标是分支历史的祖先,否则直接报NotRebased错误——这条检查在 merge.rs 里是硬性校验,没有 force 变体,从根上杜绝了"悄悄改写历史再合入"。
另外,合入失败时 worktree 与分支都还完整存在,你可以直接wt switch回该分支修 bug 再重试;合入成功后默认会移除 worktree 和分支,如果想给 Agent 留一个复核现场,用--no-remove即可。
落地建议:把网关接进 Agent 的调用链
最后把整套方案嵌入 Agent 工作流的实际调用链。对每个 Agent 任务:
- Agent 完成改动后,不直接合入,而是先
git push让远程 CI 跑起来; - 轮询
wt list --full --format=json,读取每个分支的checks对象,等待状态从running变绿(passed)或变红(failed); - 状态为
passed时,执行wt merge --format=json,一次完成"本地测试门禁 → 变基 → 合入 → 清理";状态为failed时,把失败信息连同 CI 链接交还给 Agent 修复; - 合入结果用 JSON 结构化输出解析,
committed/squashed/rebased/removed四个字段让编排器清楚知道每一步发生了什么。
这套方案的价值在于:本地pre-merge钩子兜住"快速失败"(秒级拿到编译/单测结果),远程 CI 状态兜住"合入前全量校验",wt merge的管线语义兜住"合入动作原子化"。三道兜底把"Agent 改完就合"变成了"Agent 改完、验证通过、才能合",而所有校验都长在命令内部——Agent 想跳过,只有显式传--no-hooks,而这本身就是一个可以被审计和禁止的动作。
【免费下载链接】worktrunkWorktrunk is a CLI for Git worktree management, designed for parallel AI agent workflows项目地址: https://gitcode.com/GitHub_Trending/wo/worktrunk
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考