↑ ↓ 移动 · Enter 打开 · Esc 关闭点按一个结果

← 从哪里开始
低于 Spring Boot 4.0 的项目 · skill,build 类 · 自 v0.41.0 起

低于下限?先升级这条线。

/arch-adopt 拒绝 Spring Boot 线低于 4.0 的项目。/platform-upgrade 把构建带到 Initializr 今天提供的那条线,让构建保持绿色,diff 不提交,并在最后打印 /arch-adopt。

§ 拒绝信息

export 今天会说什么

export 从项目的构建中读取 Spring Boot 线,并与 doctor.platform.min_line(目前是 4.0)比较。低于它时,在写入任何字节之前以 2 退出,通过它写入的 /arch-adopt 也一样。在有 /platform-upgrade 之前,这条拒绝只指出问题,然后就停下。

# example: /work/legacy-app, a project on Spring Boot 3.5 (the path and the line vary)
❌ /work/legacy-app builds on Spring Boot 3.5 — this .claude/ is written for 4.0 and later.
   Its norms, exemplars and architecture-test allowlist use APIs and packages that do
   not exist on 3.5, so what they generate would not build or would misbehave there.
   Nothing was written. Migrate the project to Spring Boot 4.0 or later first,
   then run this again. The `platform-upgrade` skill does that migration; it is delivered
   with `arch-adopt`. The floor is `doctor.platform.min_line` in .claude/schemas/extensions.json.

文本是 ArchHook.java 自己的,即 exportPlatformGate,逐字不变。路径和 3.5 这条线仅作示例。

§ 两个引擎,按此顺序

先是配方,再是引导式处理

机械的部分是一份配方,由数据写成,所以相同输入得到相同配方。不机械的部分留给模型,一次处理一个编译错误。

1 · 机械

一份 OpenRewrite 配方

ArchHook.jar upgrade-recipe 根据 doctor.platform.upgrade 以及 Spring 自己的 jar 发布的属性重命名来写它。Apache-2.0 的 OpenRewrite 核心通过项目自己的 Maven 或 Gradle wrapper 运行它;skill 从不假定 PATH 上有其中之一,也从不添加 wrapper。分两遍:先是升级版本号,含 starter、BOM 和类型,再是配置属性,它需要第一遍留下的 classpath。

2 · 引导

一次一个编译错误

任何重命名都覆盖不到的部分:测试 starter、Jackson 3 代码、形态改变的 API。模型读取最先出现的错误,对照规范和供应商的迁移指南修复原因,再次编译。失败的测试会被报告,绝不会被删除、跳过或放宽;同一个错误连续出现三次,就停止循环并询问。

测试先在未改动的树上运行,最后再运行一次。ArchHook.jar test-diff 对每个用例分类:回归、消失的测试,以及基线从未见过的失败,各自以 2 退出;早已存在的失败,或一次修复,则不会。原本就是红色的构建,会被如实报告为原本就是红色。

§ 逐步说明

skill 按顺序做什么

流程在项目之外下载或写入的一切,都放在同一个目录 $WORK 里。它需要干净的工作区、构建可接受的 JDK、项目根目录的 wrapper,以及访问 Initializr 和引擎仓库的网络。

  1. 1拒绝不干净的工作区;找到构建工具及其 wrapper。没有 wrapper:停止。
  2. 2像 arch-adopt 一样把来源取到 $SRC:低于下限的项目没有这种形态的 ArchHook.jar。
  3. 3通过 Initializr 生成的一次性项目解析目标版本,并下载其 dependencies 元数据。版本号绝不凭记忆写入。
  4. 4从提供引擎的仓库解析引擎版本。
  5. 4b基线:在未改动的树上运行测试,然后执行 test-diff snapshot。
  6. 5第一遍:版本号、BOM、坐标、starter 和类型。
  7. 5b仅 Gradle:修复新版 Boot 插件拒绝的 wrapper。
  8. 6列出新的 classpath。
  9. 7第二遍,不编译:新 jar 声明的属性重命名。
  10. 8引导式残余处理:编译、阅读、修复原因、再编译。
  11. 8b上下文能加载:@SpringBootTest 类最先、并单独运行。
  12. 9验证:clean verify 或 clean build,然后 test-diff compare。
  13. 10交出 diff:不提交,并打印 /arch-adopt。
› /platform-upgrade [--ref <tag|sha|main>] [--source <local path>]

✅ upgraded — <from line> → <target>, <Maven | Gradle>

Source ....... <host>, ref <ref>
Engine ....... <engine artifact> <version>
Baseline ..... <n> test cases, <n> failing before | none — the tree ran no tests
Pass 1 ....... <n> files changed (<n> sources, <n> build)
BOMs ......... <n> moved by the recipe, <n> by hand | not moved — no Initializr metadata
Wrapper ...... <unchanged | Gradle <from> → <to>> (Gradle only)
Pass 2 ....... <n> property renames applied | none declared by the jars
Residue ...... <n> edits by hand — <one line each>
Context ...... <n> @SpringBootTest classes green | Not verified: context load
Verify ....... <green | no worse than the baseline: <n> pre-existing | the regressions, verbatim first line>
Not verified . <what no step above ran — one line each | none>

Review: git diff · Undo: git checkout -- .
Next: /arch-adopt

报告是 nerviz 文档中的格式;尖括号标出每次运行要填入的内容。

§ 一次真实运行

一次运行,从头到尾

一个基于 Spring Boot 3 的私有 Gradle 项目,于 2026-10-20 用 nerviz v0.41.4 升级。用户名和项目名已模糊处理。这次运行用时 8m17s,改动了 12 个文件。

8m17s 的运行压缩为 26 秒:每 2 秒取一帧,去掉几乎没有变化的帧。最后一帧是报告。
  • 预检查:干净的 worktree,带 wrapper 的 Gradle 8.10.2,JDK 21。
  • 目标来自 Initializr:Spring Boot 4.1.1。引擎:org.openrewrite:plugin 7.39.0,配合 rewrite-bom 8.89.0。
  • 基线:41 个测试用例,0 个失败。
  • 第一遍,在 build.gradle 中:Boot、Spring Cloud 和 springdoc 升级,3 个 starter 改名,新增 spring-boot-starter-cache。Gradle wrapper 从 8.10.2 升到 8.14.6。
  • 引导式处理:Spring Security 7 移除了 DefaultAuthorizationCodeTokenResponseClient,换成 RestClientAuthorizationCodeTokenResponseClient;一个测试 starter 换成 spring-boot-starter-jdbc-test;Spring Cloud AWS 从 3.1.0 升到 4.2.0;两个 bean 改用 Jackson 3 的 ObjectMapper。
  • 上下文:5 个 @SpringBootTest 类全绿。验证:绿色,test-diff 发现 0 个回归、0 个缺失、0 个新失败。

它没有提交。在 Not verified 下,它指出没有任何测试覆盖 OAuth2 登录,并把三个决定留给用户:版本固定(pin)、仍在 Jackson 2 上的 OAuth2 converter,以及缓存 starter 在 build.gradle 中的位置。然后它打印了 /arch-adopt。

§ 它不做什么

界限,直说

它只能手动触发:它会改写一个人的构建和源码,所以只在你按名字要求时运行。它的写入领地是根目录的构建文件、wrapper 文件和 src/**,在项目根目录和任何模块下。.claude/** 在领地之外。

  • 提交。diff 由你审阅。
  • 写入 .claude/。那是 /arch-adopt 的事,它会把工作交过去。
  • 删除、跳过或放宽失败的测试。
  • 重命名 Jackson 3 代码。这是逐处的决定,不是重命名,所以留在引导式处理里。
  • 触及 nerviz 自己的示例都没有用到的重命名;它们都没碰过的包迁移,归引导式处理。
  • 绕过隐藏版本号的构建去编辑。不属于该项目的 parent POM 中的 Boot 版本,或版本目录中的版本,不会被读取:它会指出文件,并请你修改那一行。
  • 在已经达到或高于下限的项目上运行。它会说明并停止。
§ CI 证明了什么

三个 job,以及它们没覆盖的

Job它证明什么
templates › platform-upgrade联网情况下,在 Maven 和 Gradle 上:一个处于下限以下最新 GA 线的 fixture,走完 skill 的流程,并在 Initializr 的那条线上构建通过。上下文能随 cache 和 Flyway starter 加载,Spring Cloud BOM 会移动,被固定版本的 starter 失去固定,旧的 Gradle wrapper 被修复,且没有任何测试比之前更差。
validate › hooks-cross-platform › upgrade-recipe …升级步骤带着目标版本,并为生产数据中的每个重命名生成一步;每次拒绝都以 2 退出且不写入任何内容。
validate › hooks-cross-platform › test-diff …回归、消失的测试和新失败,各自单独就以 2 退出;早已存在的失败、一次修复和一次跳过则以 0 退出。

有意不做证明的部分:引擎 job 的定时运行;Gradle Kotlin DSL 和多模块 Gradle 构建;以及数据中缺少某个重命名的频率。