本文是「V2R:AI 迁移多智能体实战」系列第 2 篇。系列目录:
- 2026
- 07-05 V2R Studio 架构:一个不写代码的编排大脑
- 07-12 一个页面的迁移之旅:S0-S4 流水线(本篇)
上一篇把 v2r-studio 的架构讲完了:pi 只做编排,Claude Code 干活,门禁全是确定性代码。这篇回答下一个问题:一个页面具体怎么走完这套系统。
为了讲得具体本次用一个审批流列表页。我们首批迁移的页面就是审批流的三个 entry(list、add、edit),各自独立 run,闭包取并集。选这个作为首次迁移是因为页面在管理后台中具有很强的代表性,同时又实用了新旧两套vue基建,能覆盖大部分场景。
这条流水线的设计意图希望把「AI 写的代码可信吗」这个没法一次回答的大问题,拆成几道各自可独立判定的小问题,每道判定都是确定性代码。下面按照列表页迁移过一遍流程,每个阶段讲四件事:输入什么、做什么、输出什么、门禁是什么。
S0:先证明门禁命令本身可信
S0 是一次性的项目级 onboarding,产出一份 target-baseline.json。它的门禁 G0 有一条硬规定:不绿则整个项目不开工。S0 要回答的问题只有一个:后面门禁要反复跑的几条检查命令,在这台机器、这个仓上,真的可信吗?
我们之前迁移的时候遇到一个问题:verify 把 scoped 检查解析成整仓 build 和 lint,目标仓的存量 pre-existing lint 的技术债让每个迁移单元必挂。S0 的第一件事就是把 scoped 命令的口径实测选定:tsc 和 eslint 走 fcli checker typescript --files <文件集> 这种按文件集调用的形式。这里有个假绿陷阱:checker 内部是全量建工程再按文件名取诊断,调用时传的路径形式和它内部的对不上,就会静默拿到零诊断。红灯变绿灯不是因为代码好,是因为检查根本没跑。
对策是负控制。S0 故意造一个带类型错误的临时文件,用相对路径和绝对路径两种调用形式各跑一遍,验证错误真的报出来。哪种调用形式可用、实测耗时多少(用来定超时),全部记进基线。
build 命令没有 scoped 能力,S0 在三个口径里实测选一个:生成临时过滤配置只编当页、早期页面少时直接全量 build 并实测耗时。默认从全量起步,页面数过阈值再升级,选定结果记进基线。最后是一份环境前置 checklist 逐项探测:内网 registry 可达、i18n 同步可用、后端服务可达、源仓 entries 在盘上且新鲜、CSI daemon 的扩展连接状态。其中公司 SSO 登录态和 CSI 两项只登记不阻断,它们到 S4 才真正需要。
整条 S0 是确定性代码,一次 Agent 都没有。门禁不经过 LLM 这条铁律,从流水线的第一步就开始执行。
S1:全量普查与 G1 加权门禁
S1 是普查,一次性全量,之后增量维护。dep-analyzer 扫全部 115 个 entry,产出依赖闭包和使用清单:哪个组件、哪些 props、出现在哪些文件、共多少处。同时做双端组件库源码对账,老组件库 A、老组件库 B 对 nova-design,每个用到的组件和 API 得到一个映射状态:direct(直接对应)、mapped(有映射规则)、gap(无映射)。源仓里 1300 多处高危操作(深度样式穿透、$refs 调子组件方法、事件总线、直操 DOM、局部 mixin 等)也在这一步自动归类,每类的处置策略进处置表,迁移中首次遇到没规则的,上报人工给方案再沉淀回表。
产出三样:inventory.json、mapping-survey.json,和一批 gap 工单。工单是人机通道的另一半:人在 markdown 文件里批量处置,收割器把处置结果回流知识库。
S1 的门禁是 G1:首批页面闭包内的 gap,按使用量加权,占比不超过 10% 才放行转码。加权口径是闭包内的出现次数,一个被用了 80 次的 gap 组件,比 80 个各出现一次的 gap 严重得多。
这个是为了解决知识库冷启动失败的问题,LLM 遇到没映射的组件可能直接猜 import 'antd',猜错的成本发生在转码阶段,一轮一轮修不完。这次把顺序倒过来:组件和 API 级的 gap 不允许在转码时反应式补,必须先经工单批量处置、映射规则带证据入库,加权覆盖达标,转码才被允许开始。普查先行,把「猜」这个动作从转码阶段挤出去。列表页能不能开工,不取决于模型多有信心,取决于它的闭包里还剩多少没有事实依据的引用。
S2:一页一份迁移计划
从 S2 开始进入每页面的循环,输入是 S1 的全量产物和 S0 的基线。
先做 entry 内文件的拓扑排序,被引用多的共享组件排在前面,保证转某个文件时它的依赖已经有了目标侧形态。然后派 CC 用 kimi-3 做页面迁移规划:每个文件怎么处理、行级处置策略是什么、风险点标在哪。规划是这批活里最复杂的一类推理,模型路由把它分给 kimi-3;转码用 glm-5.3,调查用 glm-5.3-flash,这张路由表的成本账留到后面单独讲。
CC 返回的是计划草案,pi 用确定性代码做标准化和校验:文件清单必须和闭包一一对应。定稿落盘成 plans/
S2 没有自己的门禁编号,但计划本身不合法就不许进入 S3。门禁不只是跑命令,schema 校验同样是判定,同样不经过 LLM。
S3:文件级转码与页级门禁里的修复循环
S3 是列表页真正被写出来的阶段,修复循环也在这里。
节点粒度是两级的:文件级 transform 节点加一个页级门禁节点。CC 用 glm-5.3 按计划逐文件转码,落进目标仓;覆盖账本经 MCP 工具按文件事务性落盘,和节点存活与否解耦。目标仓每页一个 git 分支,门禁绿即提交,提交哈希记进账本。
修复循环内聚在页级门禁节点里。门禁失败,错误先结构化提取(错误码、文件、行号、消息),整理成结构化列表喂回 CC 修下一轮。三道闸防止空转:同一个错误连续两轮逐字节相同,立即升级人工生成工单;单页修复上限 5 轮,超了同样升级;按页累计每个 result 帧的 token usage 作成本闸,超阈值一样升级人工。轮次和成本两道护栏并列,谁先触发都算停。
之前修复遇到的问题是逐字节相同的错误重复了很多轮没人管。这次的判重是机械字符串比较,排序用码点序而不用 locale 敏感排序,保证逐字节确定性。判重逻辑自己也是确定性代码,和门禁遵守同一条铁律。
页级门禁按顺序跑三道,前一道不绿不跑下一道。G2 静态:scoped tsc、eslint、build,口径全部按 S0 基线;前置项是五个事实源仓库的 git status 审计全空。G3 结构对账:i18n key 全量对账、props 和 events 映射核验、ref 调用点对账、文案零拼接的全量正则判定,是全量不是抽查。G4 覆盖:闭包内文件 100% 到达终态、行 100% 有处置标签、行区间并集覆盖全文件这件事由 dep-analyzer 核对而不是信转码自报、risk-high 行全部挂工单,没有工单直接判违规。G4 还带一条防腐规则:单页 skipped 超过 5%,或同一个跳过理由占比超过 80%,直接判违规,防无声放水。
修复循环收在门禁节点里,是为了让崩溃的损失有界。文件级账本按文件落盘,crash 最多丢当前文件的进度;修复轮次本身不落节点盘,重跑该轮即可,不会丢整页。
S4:双端 e2e 与 G5 的限制
静态门禁全绿之后,列表页进入最后一个阶段:在真实浏览器里证明行为等价。执行者是 CSI,经 daemon 驱动真实 Chrome。
基线产物是一次语义化录制:操作序列加关键断言。操作序列的 selector 必须语义化,用文本和 role 定位,禁用 CSS class。原因很实际:Vue 和 React 渲染出的 DOM 结构不同,class 名也不同,只有语义层的选择器才能双端 replay 同一条用例。断言取三类的子集:a11y tree、网络调用序列、可见文本快照。
mock 是双端可比的前提。源仓和目标仓都没有现成的 mock 层,方案是用本地配置覆盖把 dev server 的代理指向一个确定性 mock server:fixture 先对真实后端走一遍页面抓下来,mock server 按 method 加 path 路由回放;tracked 前缀内没命中的请求直接返回 500,不静默兜底,覆盖不全必须在录制期显形。双端用同一份 mock,mock id 记进基线产物,「同一份数据走双端」是 artifact 可以直接判定的事。
diff 的判定还是确定性代码。比如 a11y 断言的判据是有序子序列:基线里扁平化的 role 加名称序列,按顺序出现在 React 端采集到的序列里即通过。这个判据容忍双端 DOM 结构差异,不容忍行为差异。整个流程是:生成 mock,CSI 录制 Vue 基线(有问题改 mock 重录,基线必须干净),同 mock 走 React 页面,确定性对比,差异进修复循环,通过则页面交付。
这里有一个必须说清的限制:G5 永远不可能无人值守。源和目标两个 dev server 要同时在本机跑(各占一个端口),录制需要真实的公司 SSO 登录态,CSI 要连着真实 Chrome 的扩展。这些条件只再用户自己的机器上、保持登录的时候成立。所以 G5 的定位是「人在电脑前时跑的最后一道闸」,自动化把录制、回放、对比全包掉,留给用户的是发起和看结果。
总结
回头看这条链转码链路。G1 确认「事实备齐了吗」,G2 确认「代码静态上成立吗」,G3 确认「结构上和源仓对得上吗」,G4 确认「每一行都有着落吗」,G5 确认「行为上和旧页面一致吗」。「AI 写的代码可信吗」被拆成这五道可独立判定的小题;G0 在这五道之前垫底,先问「仪器可信吗」。每道判定都是确定性代码:输入是文件和命令输出,输出是布尔值加结构化 issue,可以单测、可以复跑、可以逐行核对。
阶段之间只经 artifact 传递数据,每个阶段可独立重跑;任何一个 run 的现场都可以 fork 出新 run,在上次产物上续跑。列表页旅程里任何一环断掉,现场都在 artifacts/ 里躺着,恢复不需要谁的记忆。
门禁链介绍完之后,下一篇会介绍 判定为什么必须是确定性代码,LLM 给出「我觉得没问题」为什么不算数;以及文件和行两级覆盖账本,是怎么确认「每一行都有着落」的
