Astro Monorepo 的 main→next 合并冲突解决规范:resolve-conflicts 技能全解
本文基于 Astro 仓库中 resolve-conflicts.md 技能文档,系统讲解 Astro monorepo 将 main 分支合并进 next 分支时的冲突解决流程:如何枚举带冲突标记的文件、按 package.json / pnpm-lock.yaml / 源码 / 文档等不同文件类型套用解决规则,以及如何验证并提交给后续编排器。读完后你将掌握这套“版本保留 next、bug 修复吸收 main、锁文件整体弃用”的分支合并方法论,并能理解其背后 Changesets 预发布版本管理的机制依据。
背景:main→next 合并流水线中的定位
Astro 仓库采用双分支发布模式:main 承载稳定版本(stable release),next 承载预发布版本(pre-release,如 6.0.0-beta.4)。定期需要将 main 的修复合并进 next,这一过程由 merge 技能编排,拆分为三个串行子技能(见 merge/SKILL.md):
- resolve-conflicts(本文主题)— 解决
git merge origin/main产生的所有合并冲突; - clean-changesets — 清理已在
main上发布过的陈旧 changeset 文件(见 clean-changesets.md); - fix-ci — 修复合并后的构建错误、类型错误与测试失败(见 fix-ci.md)。
关键的职责边界是:resolve-conflicts 阶段不安装依赖、不提交、不构建。文档明确规定 “SCOPE: Do not spawn tasks/sub-agents”,且 pnpm install 由编排器(orchestrator)在解决完成后执行。这个边界设计的目的是让冲突解决这一高确定性任务保持纯粹,把不确定性高的依赖树重解析推迟到统一节点进行。
前置条件:任务启动时的工作区状态
文档列出的前置条件(Prerequisites)定义了任务合法启动的全部假设:
| 条件 | 说明 |
|---|---|
branch |
分支名参数,例如 ci/merge-main-to-next,即本次合并使用的临时分支 |
hasConflicts |
布尔参数,标识本次合并是否产生了冲突 |
| 工作目录 | 仓库根目录,且已检出(checkout)在合并分支上 |
| 合并状态 | git merge origin/main 已执行但未提交——冲突标记(<<<<<<< 等)存在于工作树中 |
| 依赖状态 | 依赖尚未安装,禁止运行 pnpm install——安装由编排器在解决完成后负责 |
其中两条是最容易被违反的:不要运行 pnpm install(依赖树会干扰后续编排),不要自己 git commit(提交权在编排器)。
Step 1:枚举所有带冲突标记的文件
解决冲突的第一步是全量扫描,而不是逐个看 git status 的输出。文档给出的命令按扩展名白名单过滤(TS/JS 源码、Astro 组件、JSON、YAML、Markdown),并排除 node_modules:
# 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 的实际构成一致:核心包位于 packages/astro/src 的 .ts 源码、构建配置 .mjs、.astro 组件(如 components)、各包 package.json、pnpm-lock.yaml 以及大量 .md 文档。使用 grep -rl 而非 git diff --name-only 的好处是它同时能捕捉到二进制冲突以外、因内容重叠产生的标记。
Step 2:按文件类型套用解决规则
这是文档的核心。Astro 的规则不是笼统的“以某一侧为准”,而是按文件类型分治:
package.json:版本取 next,依赖看源码 import
对每个产生冲突的 package.json:
version字段——永远保留next的预发布版本(如5.0.0-beta.1),绝不使用main的稳定版本。这是整个规则体系的第一原则:next分支的身份就是预发布线,若误取main的稳定版本号,后续 Changesets 预发布计算会错乱。- 依赖项——共享依赖保留
next的版本。若main新增了一个next上不存在的依赖,则纳入;若main把某个依赖换成了另一个(文档举的例子:get-tsconfig→tsconfck),则以next分支源码实际 import 的那个为准——依赖清单必须服从源码引用,而非分支历史。 - scripts——两侧合并:保留
next的 scripts,并加入main新增的 script。 - 其他字段——优先
next,除非main侧是明显的 bug 修复。
pnpm-lock.yaml:整体放弃手工解决
锁文件是合并冲突中最难手工处理的一类——冲突标记可能散布数百处,且手工拼接极易产生不自洽的条目。文档的策略是彻底的:
Do NOT try to manually resolve the lockfile. Just delete it — the orchestrator will regenerate it via
pnpm install --no-frozen-lockfileafter you're done.
具体命令为:
git checkout --theirs pnpm-lock.yaml 2>/dev/null || true
这里用 --theirs(即 origin/main 侧)只是给文件一个合法的冲突解除状态以便 git add;真正的锁文件会由编排器依据解决后的 package.json 集合重新生成。这与仓库根 package.json 中 version 脚本的做法一致——根脚本在 changeset version 之后同样执行 pnpm install --no-frozen-lockfile 来重算锁文件,说明“改包清单 → 整体重装”是本仓库处理依赖一致性的标准手段。
源码(.ts / .js / .mjs / .cjs / .astro):修复要过桥,API 听 next
对源代码冲突,文档给出两条互补的原则:
main的 bug 修复——应携带到next,必要时把修复适配到next的 API 上。也就是说,不是整段拷贝main的代码,而是把“修复语义”移植过去;next的 API 变更——保留next版本,反过来把main的代码适配到next的新 API;- 拿不准时,一律偏向
next——它是面向未来的分支。
这一原则的边界判断依据是仓库 AGENTS.md 描述的 monorepo 结构:所有包位于 packages/,node_modules 中的构建产物对应 packages/ 下的 TypeScript 源码(如 node_modules/astro/dist/... → packages/astro/src/...)。因此“next 的源码实际 import 什么”是有据可查的:直接检查 packages/ 下对应源码即可。
Markdown 与配置文件
- Changesets(
.changeset/*.md)——本阶段先接受两侧(accept both sides),留给下一阶段 clean-changesets 统一处理; - 其他
.md文件——优先next。
之所以把 changeset 单独摘出来延后处理,是因为它涉及发布版本判断(详见下文的“机制佐证”),在冲突解决阶段做不划算。仓库中确实存在大量此类文件,例如 .changeset/better-lines-show.md,其 frontmatter 形如 '@astrojs/cloudflare': patch。
Step 3:逐个暂存已解决的文件
每解决完一个文件立即暂存:
git add <resolved-file>
暂存后 git status 能清晰区分“已解决”与“未解决”的文件,也为 Step 4 的全量校验提供干净的基线。
Step 4:校验无冲突标记残留
文档给出的验证命令同时匹配三种标记(起始标记、分隔线、结束标记),与 Step 1 相同的扩展名过滤逻辑:
grep -r "<<<<<<< \|=======$\|>>>>>>> " . --include="*.ts" --include="*.js" --include="*.mjs" --include="*.cjs" --include="*.md" --include="*.astro" --include="*.json" --include="*.yaml" --include="*.yml" | grep -v node_modules
注意分隔线用的是 =======$(行尾锚定),避免误伤 Markdown 标题分隔等合法用法。若仍有残留输出,回到 Step 2 继续解决。“零残留”是本阶段的完成判据——它保证编排器随后的 pnpm install / pnpm build 不会在任何文件中遇到未解析的冲突标记。
Step 5:止步——不提交、不安装
文档把这一条单列为一步,强调编排器接管后续:提交、pnpm install --no-frozen-lockfile、构建全部发生在此阶段之后。resolve-conflicts 的输出物是已暂存的、无冲突标记的工作树,加上“解决了哪些文件”的清单。
规则背后的机制佐证:来自仓库证据
上述规则并非随意约定,仓库中有多处互相印证的证据。
1. Evals 干跑用例完整演示了一条规则链。 merge 技能的评测集 中第一条用例构造了 packages/astro/package.json 的合成冲突:next 侧为 "version": "6.0.0-beta.4" + tsconfck,main 侧为 "version": "5.14.2" + get-tsconfig + 额外依赖 kleur 与 script test:smoke。预期解法与文档规则逐条对应:保留 6.0.0-beta.4 与 tsconfck(源码 import 的那个),纳入 kleur 和 test:smoke(新增项),但不引入 get-tsconfig(被换掉的旧依赖)。同一用例还包含 packages/astro/src/core/config/read.ts 的源码冲突:main 侧多了一个空值守卫(if (!result) return undefined),但 next 侧 API 已变为 result.tsconfig——预期解法是移植守卫、保留 next 的字段名,正是“bug 修复要过桥、API 听 next”的教科书式落地。评测断言还明确要求:不执行 pnpm install、不执行 git commit,锁文件走 git checkout --theirs。
2. Changesets 机制解释了“先接受、后清理”的原因。 clean-changesets.md 的背景说明指出:当 main 在 next 分叉之后发布时,main 已消费掉的 changeset 会以“新文件”形式出现在 next 上;若不清理,next 的预发布会包含已按稳定版发布的版本号,导致类似 @astrojs/sitemap@3.6.1-beta.3 在 3.7.0 之后被发布的版本倒挂问题。这与本阶段“changeset 冲突先接受两侧”的约定形成完整闭环。仓库的 .changeset/config.json 中 baseBranch 为 origin/main,也印证了 main 作为稳定基线的地位。
3. 根目录工具链解释了“install/build 由谁负责”。 根 package.json 显示这是一个 pnpm workspace(工作区定义见 pnpm-workspace.yaml),build 脚本经由 Turbo 按包过滤执行(turbo run build --filter=astro ...),要求 Node >=22.12.0、packageManager 为 pnpm@11.13.1。因此“冲突解决后 pnpm install 和 pnpm build 必须能成功”是有明确执行入口的;而 fix-ci.md 更进一步规定 CI 修复阶段反而禁止再跑 pnpm install(--no-frozen-lockfile 会重解析整棵依赖树、破坏传递依赖)——两个阶段的 install 禁令方向相反,恰好说明 resolve-conflicts 阶段“不装”是为了把唯一一次重装收敛到编排器节点。
4. AGENTS.md 提供了命令语境的佐证。 AGENTS.md 中“Running Tests”一节说明单包测试经由 astro-scripts test 执行(如 pnpm -C packages/astro exec astro-scripts test "test/xxx.test.ts"),且修改 packages/ 源码后需 pnpm build 才能生效——这正是后续 fix-ci 阶段验证手段的基础,也说明冲突解决阶段刻意回避这些重命令是合理的。
关键规则速查
| 文件类型 | 解决策略 | 判定依据 |
|---|---|---|
package.json 的 version |
永远取 next 的预发布版本 |
next 分支的版本线身份 |
package.json 依赖 |
共享依赖取 next;main 新增依赖纳入;被替换的依赖看 next 源码 import |
依赖清单服从源码引用 |
package.json scripts |
两侧合并,main 新 script 追加 |
功能叠加不冲突 |
pnpm-lock.yaml |
不手工解决,git checkout --theirs 后交编排器重装 |
手工拼接不可靠 |
| 源码 | bug 修复移植到 next API;API 变更取 next |
拿不准偏向 next |
.changeset/*.md |
接受两侧,交给 clean-changesets | 涉及发布版本判断 |
其他 .md |
优先 next |
面向未来的分支 |
| 完成判据 | 全量 grep 零冲突标记 + 全部 git add |
保障后续 install/build |
输出与移交
阶段完成时的交付物有两部分:一是已解决文件的清单(文档 Output 一节的要求);二是干净的工作树状态——所有冲突标记清除、全部解决文件已暂存、未提交、未安装。编排器随后执行提交与 pnpm install --no-frozen-lockfile,并进入 clean-changesets 与 fix-ci 阶段;若构建或测试仍有失败,将由 fix-ci.md 的“先 diff、后读码、最小修复、目标化验证”流程接管。
掌握本文规则后,你不仅能执行 Astro 仓库的 main→next 合并任务,更能把其中“预发布分支版本保护、锁文件整体弃用、修复语义移植而非整段拷贝”的策略泛化到任何采用 Changesets 预发布工作流的 monorepo 合并场景中。
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 StartedRust0623
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