首页
/ Astro 仓库 main→next 分支合并工作流:冲突解决、CI 修复与 Changeset 清理的 Agent 化实践

Astro 仓库 main→next 分支合并工作流:冲突解决、CI 修复与 Changeset 清理的 Agent 化实践

2026-09-05 14:11:35作者:胡唯隽

本文以 Astro monorepo 中定义的 merge Agent 技能(.agents/skills/merge/SKILL.md)为主体,完整拆解将 main 稳定分支合并回 next 预发布分支的三个标准阶段:解决 Git 冲突、清理过期的 changeset、修复合并后的 CI 失败。读完后你将掌握一套可直接复用的大仓库分支合并流程:如何按文件类型制定冲突裁决规则、如何避免预发布版本号回退冲突,以及如何在"绝不重新安装依赖"的前提下高效修复构建与测试失败。

一、总体设计:一个技能,三个子技能,一个编排器

merge 技能的主文件本身是一个轻量级的入口文件,其 YAML frontmatter 声明了技能名称与触发时机:

---
name: merge
description: Handle main-to-next merge tasks including conflict resolution,
  changeset cleanup, and CI fix-ups. Use when merging main into next.
---

它将完整的合并流程拆分为三个子技能,由外部编排器(orchestrator)通过 step 参数决定当前执行哪一步:

这个拆分本身体现了一个重要工程决策:三个阶段的责任边界被显式切开。每个子技能都声明了相同的 SCOPE 约束——"Do not spawn tasks/sub-agents"(不得再派生子任务),并严格规定了"哪些动作由自己完成、哪些动作留给编排器"。例如冲突解决阶段明确"依赖尚未安装,不要运行 pnpm install,编排器会在你完成后执行";而 CI 修复阶段则相反,"依赖已经装好,绝对不要再跑 pnpm install"。把 install、commit、build 等重活统一收归编排器,可以避免多个子任务各自为政地重装依赖树、破坏锁文件。

二、阶段一:解决合并冲突(resolve-conflicts)

这一阶段的前提状态(Prerequisites)在 resolve-conflicts.md 中被逐条列出:已知分支名(如 ci/merge-main-to-next)、已知是否存在冲突(hasConflicts)、工作目录位于仓库根且检出在合并分支上、git merge origin/main 已执行但尚未提交(冲突标记仍在工作区中)、依赖未安装。

2.1 找出所有冲突文件

第一步是用 grep 按文件类型批量扫描冲突标记,而不是依赖 git status 逐个查看:

# List all files with conflict markers
grep -rl "<<<<<<< " . --include="*.ts" --include="*.js" --include="*.mjs" \
  --include="*.cjs" --include="*.md" --include="*.astro" \
  --include="*.json" --include="*.yaml" --include="*.yml" | grep -v node_modules | sort

这个命令覆盖了 Astro monorepo 中的全部关键文件形态:TypeScript 源码、Astro 组件、JSON 配置、YAML 工作区配置。之所以按扩展名枚举而非全量扫描,是因为在含上千文件的 monorepo 中(如 packages/astro/test/fixtures 下有大量测试 fixture),限定类型能显著降低噪音。

2.2 按文件类型制定裁决规则

这是整个技能中最核心的部分:不同文件类型采用不同的裁决策略,而不是无脑选 ours/theirs。

package.json 文件(规则见 resolve-conflicts.md 第 28–33 行):

字段 规则
version 永远保留 next 的预发布版本号(如 6.0.0-beta.4),绝不用 main 的稳定版本号
依赖 共享依赖保留 next 的版本;若 main 新增了 next 没有的依赖,则纳入;若 main 把某个依赖替换成了另一个(文档给出的例子是 get-tsconfigtsconfck),以 next 源码实际 import 的那个为准
scripts 两侧合并:保留 next 的脚本,并加入 main 的新脚本
其他字段 默认偏好 next,除非 main 侧是明显的 bug 修复

pnpm-lock.yaml:明确规定不要手动解析锁文件,直接丢弃冲突、交给编排器用 pnpm install --no-frozen-lockfile 重新生成:

git checkout --theirs pnpm-lock.yaml 2>/dev/null || true

这是务实的选择——手工合并数千行的 lockfile 既慢又极易出错,而重新生成是可复现的确定性操作。

源码(.ts / .js / .mjs / .cjs / .astro

  • main 上的 bug 修复必须带到 next,必要时适配 next 的 API;
  • next 上发生的 API 变更,保留 next 版本,把 main 侧代码适配过来;
  • 拿不准时默认选 next——因为它是面向未来的分支。

Markdown 与配置文件.changeset/*.md 留给 clean-changesets 阶段处理,冲突时先两侧都接受;其余 .md 文件偏好 next

2.3 收尾:暂存与验证

每解决一个文件立即 git add <resolved-file> 暂存,然后再次全量扫描确认没有残留冲突标记:

grep -r "<<<<<<< \|=======$\|>>>>>>> " . --include="*.ts" --include="*.js" \
  --include="*.mjs" --include="*.cjs" --include="*.md" --include="*.astro" \
  --include="*.json" --include="*.yaml" --include="*.yml" | grep -v node_modules

最后一条铁律:不要 commit,不要 install——提交、安装、构建全部交给编排器。技能的输出契约也很清晰:返回已解决冲突的文件列表。

三、阶段二:清理过期 Changesets(clean-changesets)

这一阶段解决的问题非常具体,其背景说明(clean-changesets.md 第 12–14 行)值得逐字理解:

main 合并进 next 时,那些已经在 main 上被某次发布消费掉的 changeset 文件(.changeset/*.md),如果 next 分支在那次发布之前就分叉了,它们仍会作为"新文件"出现在 next 上。这些陈旧 changeset 会导致 next 的预发布版本号包含"已经以稳定版发布过的版本 bump",从而产生版本冲突——例如在 @astrojs/sitemap@3.7.0 之后又发布了 @astrojs/sitemap@3.6.1-beta.3 这种版本号倒退的预发布包。

这解释了 Astro 仓库为什么要采用 changesets 预发布模式(结合 pnpm-workspace.yaml.changeset/ 目录)。仓库中现有的 config.json 印证了发布配置:"baseBranch": "origin/main" 决定了 changeset 以哪个分支为基准比对,"changelog" 使用 @changesets/changelog-github 生成变更日志。而 great-moons-shine.md 这样的真实 changeset 文件则展示了标准格式——frontmatter 中声明包名与 bump 类型('astro': patch),正文是一句话的变更说明。

清理步骤

  1. 找出候选:用 git diff --diff-filter=A 列出相对 origin/next 新增的 changeset 文件:

    # List changeset .md files that are new (not in next already)
    git diff --name-only --diff-filter=A origin/next -- .changeset/
    
  2. 逐个核查:读取文件内容看它 bump 哪些包,再对照 origin/main 上各包的 package.json 当前版本号与 changelog——只有当"该 changeset 描述的 bump 已经发布"时,才判定为 stale(陈旧)。

  3. 检查 pre.json:若 .changeset/pre.json 存在(它记录预发布模式状态,其 changesets 数组列出当前预发布待发布的所有 changeset 标识),确保被删文件的标识也从数组中移除。

  4. 删除git rm .changeset/<stale-file>.md

  5. 验证:确认剩余的 changeset 文件格式合法(frontmatter 包含包名与 bump 类型)。

  6. 不提交、不 install——同样留给编排器。

保守原则

文档用一整节"Important Notes"划定了禁止事项,这些边界对防止 Agent"过度清理"至关重要:

  • 不得删除 next 专属的 changeset(描述主版本变更或 next 在研新特性的);
  • 不得删除 .changeset/config.json
  • 不得删除 .changeset/pre.json(预发布模式依赖它);
  • 拿不准时宁可保留——"err on the side of keeping it. A human reviewer can remove it later",把误删的代价转移给低成本的人工复查。

四、阶段三:修复 CI 失败(fix-ci)

此阶段的前提是:冲突已解决并提交、锁文件已重新生成、pnpm install 已执行、CI 日志已由编排器预先抓取进 ciLogs 参数。fix-ci.md 的核心是一套"修复并推送(fix and push)"的迭代循环:修完推到 PR,CI 自动重跑,如果还有新失败就用更新后的日志再跑一轮。

4.1 四条关键规则(Critical Rules)

这四条规则(fix-ci.md 第 15–23 行)是整个技能最具实操价值的部分:

  1. 绝不运行 pnpm install。依赖已正确安装,锁文件是合并动作中带着全部已解决冲突生成的;再次安装(尤其是 --no-frozen-lockfile)会重新解析整棵依赖树、破坏传递依赖。文档还点破了一个常见误区:"如果测试因缺模块而失败,那是源码问题,不是依赖问题"——应该修 import,而不是装包。
  2. 给调查设时间盒:单个失败分析超过 5 分钟还没有动手尝试修复时,停止调查,按当前最佳猜测直接改、直接跑,用"跑一遍看结果"代替"从第一性原理追完整条调用链"。
  3. 先 diff,后读码:排查失败时先看合并改了什么(git diff origin/next...HEAD -- <relevant-files>),而不是通读源码——diff 直接展示了两分支的差异,而合并后的失败几乎都源于这些差异。
  4. 批量执行 bash 命令:把相关的 ls/grep/cat 合并成一次调用,减少往返。

4.2 构建优先:先修 build 再看测试

CI 的第一步是构建,构建错误会阻塞一切,所以本地复现顺序也是:

pnpm build

合并后的常见构建错误被归纳为三类及对应修法:

  • 类型错误——两分支间 API 变更,某分支改了类型签名或新增必填字段,另一分支代码对不上;按当前分支状态适配代码;
  • 导入错误——文件被移动/重命名或导出被删除;更新 import 路径或适配新 API;
  • 重复声明——两分支都加了相似代码;删掉重复项,保留 next 版本。

用 diff 定位变化,修复后先构建受影响的包确认,再全量构建:

git diff origin/next...HEAD -- <path-to-failing-file>
pnpm -C packages/<affected-package> build
pnpm build

这里的 pnpm -C <dir> <command> 形式与仓库根 AGENTS.md "Monorepo Structure" 一节中的工作区约定一致:所有包位于 packages/,包内命令一律通过 -C 定位,且"对源码的修改需要 pnpm build 重新构建后才生效"(node_modules 中的构建产物映射回 packages/ 下的 TS 源码)。

4.3 测试失败的归因与最小修复

构建通过后,从 ciLogs 参数中提取:哪些测试文件失败、具体哪些用例失败、错误信息与断言 diff。注意:沙箱内 gh CLI 不可用,所有 CI 信息都来自预取的 ciLogs

对每个失败先跑 diff 归因,文档归纳了四类常见成因:

  1. 快照/输出不匹配——next 用了新编译器或有 API 变更,期望的 HTML 输出变了;更新期望值以匹配新行为;
  2. 导入/模块错误——main 代码引用了 next 上已变更的模块或导出;
  3. 测试中的类型错误——跨分支 API 变更导致的 TS 编译失败;
  4. 配置不匹配——测试 fixture 使用了 next 上已变更的配置项;更新 fixture。

修复原则是"最小化":只修 CI 报的失败,不重构无关代码,不改测试意图(只把期望值适配到当前分支状态)。同时明确列出了不修清单

  • 合并在 next 上就已经失败的测试——不确定时用 git log origin/next -- <test-file> 查证;
  • Smoke 测试允许失败
  • astro check 失败允许存在

这一"允许失败"清单与仓库根 package.json 中的脚本结构相互印证:test:smoke 本质是 turbo run build 构建全部示例项目,而 astro check 属于语言工具链的独立检查项,二者波动性与合并正确性无关。

4.4 本地验证:只跑定向测试

修完后只跑被修的具体测试,而非全量套件:

pnpm -C <package-directory> exec astro-scripts test "test/<specific-test>.test.js"

两个细节容易被忽视:一是不要把输出管道接 grep,否则会吞掉真正的通过/失败结果;二是这只是快速 sanity check,完整验证交给 push 之后的 CI。若修改了 packages/ 下的源码(而非仅测试文件),需重新执行第 4.2 节的定向构建,再重跑对应测试。

技能输出契约:是否修复了全部已识别的 CI 失败(构建 + 测试)、修改过的文件列表、以及无法自动修复、需要更深层架构理解的残留失败清单。

五、技能的可验证性:evals 驱动的回归测试

与大量"写完即弃"的 Agent 提示词不同,这个 merge 技能自带结构化评测用例 evals/evals.json,为三个阶段各定义了一个**输出型干跑(output-only dry run)**场景:给定合成的人为冲突片段/changeset 快照/CI 日志,要求 Agent 不触碰真实 checkout、不执行任何命令,仅返回"将会做"的解析结果与命令序列,并用 assertions 数组逐条校验行为边界。

以 resolve-conflicts 的用例为例,其断言精确到:

  • 解析后的 package.json 必须保留 6.0.0-beta.4 预发布版本、vite 7.1.0tsconfck,纳入 main 新增的 kleurtest:smoke 脚本,但不引入 get-tsconfig
  • 源码解析必须调用 loadTsconfig(path, { cache: false })next 的新 API)、保留 main 侧的 null 守卫、读取 result.tsconfig 而非 main 分支已过时的 result.config——恰好覆盖了第 2.2 节"API 变更保留 next、bug 修复保留 main"两条规则的组合;
  • 命令序列用 git checkout --theirs 处理 lockfile、暂存并扫描冲突标记,且不得出现 pnpm installgit commit

clean-changesets 与 fix-ci 的用例同样体现了边界纪律:前者只删"证据确凿已发布"的 changeset、保留 next 专属与状态不明者,后者只更新过期的 redirect 断言、明确将 smoke 与 astro-check 失败列入"有意不修"。这些 eval 由仓库根 package.json 的脚本驱动:

pnpm eval:skills          # vitest run --config vitest.skills.config.ts
pnpm eval:skills:validate # vitest list --config vitest.skills.config.ts

配套的加载器在 .agents/evals/load-evals.tsskills.eval.ts,说明该仓库对"技能行为本身"做单元/回归测试。这是把 Agent 工作流工程化的关键一步:提示词会改、模型会换,只有可执行的断言能守住行为底线。

六、总结:从文档中可提炼的合并方法论

把三个子技能合起来看,这套 main→next 合并流程的可复用要点是:

  1. 阶段切分 + 单一职责:冲突解决、依赖再生成、构建、提交各归其位,子技能之间通过明确的前置条件(Prerequisites)与后置约束("不 commit / 不 install / 绝不 install")交接;
  2. 按文件类型裁决,而非全局策略:版本号、锁文件、源码、文档各有专属规则,"拿不准选 next"只适用于源码层;
  3. 锁文件永远重生成,不手解
  4. CI 修复先 diff 后读码、最小化改动、给调查设时间盒,并把 smoke/astro check 等允许性失败显式豁免;
  5. 版本发布安全靠证据链:只有"版本号 + changelog 双重印证已发布"的 changeset 才被删除,其余一律保留交人工复查;
  6. 技能行为本身有测试:以 dry-run 合成场景 + 断言数组的形式纳入 vitest 回归。

对维护双分支(稳定 main + 前瞻 next 预发布)的大型 monorepo 而言,这套流程把最易出错的合并环节变成了可编排、可断言、可回归验证的自动化流水线。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
504
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384