oh-my-openagent 的 PR 验证策略:三道门禁(CI / 5-Agent 评审 / Cubic)与合并恢复流程
本文以仓库中 verification-strategy.md 为核心,讲解 omo/lazycodex(oh-my-openagent)在 work-with-pr 技能框架下针对具体修复 PR(atlas hook 在 boulder.json 缺少 worktree_path 时崩溃)设计的三阶段验证策略:CI 门禁、5-Agent 并行评审门禁与 Cubic 自动审查门禁。读完后你将掌握一套可复用的“提交前本地预检 + 无上限验证循环 + 失败路由”工程化验证方法,并能对照仓库真实源码(boulder-state 存储层、atlas 空闲事件钩子、ci.yml)验证每个检查项的实际落点。
一、背景:验证策略服务的 PR 与三层门禁总览
该验证策略是为一个聚焦的小修复 PR 制定的:fix(atlas): prevent crash when boulder.json missing worktree_path。根因是 readBoulderState() 将 JSON.parse() 的原始输出直接断言为 BoulderState,当 boulder.json 中 "worktree_path": null(手动编辑、外部工具或状态损坏导致)时,运行时类型为 null,违反了 TypeScript 声明的 string | undefined 契约。
对应产物文档见同目录的 code-changes.md 与 pr-description.md。验证策略把整个验证流程拆为三道门禁(Gate A/B/C),任何一道失败都会把流程打回“修复—提交—推送”循环,直到全部通过才允许合并:
| 门禁 | 名称 | 验证内容 | 通过信号 |
|---|---|---|---|
| Gate A | CI | 测试(拆分执行)、Typecheck、Build | gh pr checks 全绿 |
| Gate B | review-work | 5 个并行 Agent 评审 | 5 个 Agent 全部 PASS |
| Gate C | Cubic | cubic-dev-ai[bot] 自动代码审查 |
"No issues found" |
注意一个事实边界:当前仓库主干的 work-with-pr SKILL.md 定义的是 CI + Cubic 两道门禁(且该技能规定用 merge commit 而非 squash);而本文档所在的 work-with-pr-workspace 迭代评估(eval-2)把 Gate B 扩展为 5-Agent 评审(review-work),并在合并阶段使用 --squash。两者是同一技能体系在不同评估场景下的编排变体,使用时需区分。
二、Gate A:CI 门禁
CI 实际执行的检查项(对照 ci.yml)
文档列出 CI 从 ci.yml 派生的三类检查:
- Tests(拆分执行):mock 密集型测试单独隔离运行 + 批量测试;
- Typecheck:
bun run typecheck(tsc --noEmit); - Build:
bun run build(ESM + 声明文件 + schema)。
对照仓库真实的 ci.yml 可以确认这一结构:test job 确实把测试拆成了“主批量”与“隔离批量”两次 bun test 调用——主批量运行 bun test packages/omo-opencode packages/memory-core,另一组则单独运行 Windows 特有测试、chaos-bench、安装器版本测试等长尾用例;typecheck job 独立执行 bun run typecheck,并覆盖 script 与各 package 的检查。也就是说,文档中“mock-heavy tests in isolation + batch tests”的说法与 CI 配置中“隔离运行 + 批量运行”的拆分方式相互印证。
提交前的本地预检(Pre-push local validation)
在推送前,先在本地运行与 CI 完全相同的检查步骤,尽早拦截失败:
# 先跑针对性测试(快速反馈)
bun test src/features/boulder-state/storage.test.ts
bun test src/hooks/atlas/index.test.ts
# 完整测试套件
bun test
# 类型检查
bun run typecheck
# 构建
bun run build
需要结合仓库现状补充一个关键事实:文档中写的 src/... 前缀是该评估当时 monorepo 布局下的路径;从当前仓库结构看,boulder-state 存储与 atlas 钩子已按 monorepo 规范拆包——readBoulderState 的真实实现位于 read-state.ts,由 storage.ts 从 @oh-my-opencode/boulder-state 包统一 re-export;测试文件现位于 storage.test.ts,atlas 钩子位于 idle-event.ts。因此本地预检命令在当前仓库应写为:
bun test packages/omo-opencode/src/features/boulder-state/storage.test.ts
bun test packages/omo-opencode/src/hooks/atlas
bun run typecheck
bun run build
这正是“先针对性测试、后全量”策略的意义:CI 一次往返约 3–5 分钟,本地按包过滤的测试能在几秒内给出反馈。
Gate A 失败处理
- 测试失败:阅读测试输出 → 修复代码 → 创建新 commit(绝不 amend 已推送的 commit)→ push;
- Typecheck 失败:对变更文件运行
lsp_diagnostics→ 修复类型错误 → commit → push; - Build 失败:检查构建输出中的缺失导出或循环依赖 → 修复 → commit → push。
每完成一轮“修复—提交—推送”,都要执行 gh pr checks --watch 重新进入 Gate A。
三、Gate B:review-work 五 Agent 并行评审
5 个并行 Agent 的分工
- Oracle(目标/约束核对):检查修复是否对题——
worktree_path崩溃是否真正解决、有无范围蔓延(scope creep); - Oracle(代码质量):验证代码遵循既有模式——工厂模式、given/when/then 测试风格、单文件 < 200 LOC、不使用 catch-all 文件;
- Oracle(安全):确认没有引入新安全问题——JSON 解析注入、
worktree_path的路径穿越; - QA Agent(动手执行):实际运行测试、对变更文件跑
lsp_diagnostics、验证修复在真实场景下生效; - 上下文挖掘 Agent:检索 GitHub issues、git 历史、相关 PR,确认与项目上下文对齐。
本 PR 的预期审查焦点
这是整个策略中最具可操作性的部分——把抽象门禁落到本 PR 的具体问题上:
- Oracle(目标):
readBoulderState中的消毒(sanitization)是否真正阻止了崩溃?typeof守卫是必要还是冗余? - Oracle(质量):新测试是否遵循 given/when/then 模式?是否复用了既有测试的 mock 搭建方式?
- Oracle(安全):
worktree_path的值是否会在未消毒的情况下参与路径操作?(文档给出的结论:否,该值只出现在模板字符串中。) - QA:运行
bun test src/hooks/atlas/index.test.ts——在修复前,worktree_path为 null 的测试用例是否确实触发 bug?
对照当前源码可以核验“只用于模板字符串”这一安全结论:idle-event.ts 中 worktreePath: boulderState.worktree_path 只是作为参数传给 injectContinuation()(同目录 idle-continuation.ts),并未直接进入 fs/path 操作,与文档判断一致。
Gate B 失败处理
- 每个 Oracle 输出 PASS/FAIL 结论并附具体问题清单;
- 若 FAIL:阅读具体问题 → 在 worktree 内修复 → commit → push → 重跑 review-work;
- 5 个 Agent 必须全部 PASS 才算该门禁通过。
四、Gate C:Cubic 自动审查
Cubic 检查什么
cubic-dev-ai[bot] 是分析 PR diff 的自动代码审查机器人,关注点包括:类型安全问题、缺失的错误处理、测试覆盖缺口、反模式。
预期结果
对这个小而聚焦的修复(文档口径:storage.ts、idle-event.ts、index.test.ts 三个文件 + 1 个测试文件),预期结果是 “No issues found”。
Gate C 失败处理
- 若 Cubic 提出问题:先评估是真问题还是误报;
- 真问题:修复 → commit → push;
- 误报:在 PR 中留言解释该模式是有意为之;
- push 后等待 Cubic 重新审查。
五、验证通过后的合并与冲突恢复
合并与 worktree 清理
三道门禁全部通过后:
gh pr merge --squash --delete-branch
git worktree remove ../omo-wt/fix-atlas-worktree-path-crash
合并失败(冲突)时的恢复路径
cd ../omo-wt/fix-atlas-worktree-path-crash
git fetch origin dev
git rebase origin/dev
# 如有冲突则逐一解决
git push --force-with-lease
# 从 Gate A 重新进入验证循环
这里 --force-with-lease 相对 --force 更安全:若远端分支被他人推进过,push 会被拒绝,避免覆盖他人提交。而“rebase 后必须从 Gate A 重新走完整验证”体现了该策略的核心不变量:任何代码变动都触发全量再验证,而不是只补跑失败的那一步。
六、源码纵深:被验证的修复在仓库中的实际形态
为让上述策略可被逐条核验,最后对照仓库真实实现说明修复的落点:
readBoulderState的消毒管道。当前实现位于 read-state.ts:函数对boulder.json做存在性检查与JSON.parse,随后调用normalizeState(parsed)统一修正字段(session_ids过滤非字符串项并归一化、session_origins强制为对象、task_sessions兜底为空对象),最后才执行parsed as BoulderState断言。文档要求“worktree_path必须为string | undefined,绝不接受null”的策略,正是这条 normalize-then-cast 管道的典型应用——先消毒再断言,而不是裸 cast。BoulderState接口中worktree_path?: string的声明可参见 boulder-state 包的 AGENTS.md 中的字段注释(git worktree root)。- 防御性
typeof守卫的调用点。idle-event.ts 通过scheduleRetry(约第 139、150 行两处)与直接injectContinuation调用(约第 164–172 行,worktreePath: boulderState.worktree_path处)把 worktree 路径传递给续写注入链,与 code-changes.md 中“两处调用点加守卫”的描述吻合。 - 回归测试锚点。storage.test.ts 中已存在使用
worktree_path的用例(缺失 worktree 的异常路径测试等),index.test.ts 则覆盖session.idle处理器——QA Agent “修复前该用例必须红、修复后必须绿”的要求可以直接落在这两个文件上验证。
七、可复用的策略要点小结
把该文档从单一 PR 场景抽象出来,得到一条通用的 PR 验证方法论:
- 三道门禁,失败即回流:CI(最便宜、最快)→ 多 Agent 评审(最深入)→ 外部自动审查机器人(最异步);任一失败都回到“读日志 → 定点修复 → 原子 commit → push → 从 Gate A 重进”的循环,不设迭代上限;
- 提交前先本地复刻 CI:先跑变更相关的窄测试,再跑全量、typecheck、build,用秒级反馈换掉分钟级 CI 往返;
- 门禁问题清单要具体到代码行:如“
typeof守卫是否必要”“worktree_path是否参与路径操作”,而不是泛泛的“检查质量”; - 测试必须能自证 bug:关键回归测试应满足“修复前触发、修复后通过”的双重可验证性;
- 合并后必清理 worktree,失败不删 worktree:保留现场供人工接管;冲突恢复统一走 fetch → rebase →
--force-with-lease→ 全量重验。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00