↑ ↓ to move · Enter to open · Esc to closeTap a result

← Where to start
Project below Spring Boot 4.0 · skill, class build · since v0.41.0

Below the floor? Raise the line first.

/arch-adopt refuses a project on a Spring Boot line below 4.0. /platform-upgrade takes the build to the line the Initializr serves today, leaves the build green and the diff uncommitted, and ends by printing /arch-adopt.

§ the refusal

What the export says today

export reads the Spring Boot line from the project's build and compares it with doctor.platform.min_line, today 4.0. Below it, it exits 2 before writing a byte, and so does /arch-adopt, which writes through it. Until /platform-upgrade, the refusal named the problem and stopped.

# 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.

The text is ArchHook.java's own, exportPlatformGate, verbatim. The path and the 3.5 line are an example.

§ two engines, in this order

A recipe first, a guided pass for the rest

What is mechanical is a recipe, written from data, so the same input gives the same recipe. What is not is left to the model, one compile error at a time.

1 · mechanical

An OpenRewrite recipe

ArchHook.jar upgrade-recipe writes it from doctor.platform.upgrade and from the property renames Spring's own jars publish. The Apache-2.0 OpenRewrite core runs it through the project's own Maven or Gradle wrapper; the skill never assumes one on the PATH and never adds one. Two passes: the bump, with the starters, BOMs and types, then the configuration properties, which need the classpath the first pass leaves.

2 · guided

One compile error at a time

What no rename covers: a test starter, Jackson 3 code, an API that changed shape. The model reads the first errors, fixes the cause against the norms and the vendor's migration guide, and compiles again. A failing test is reported, never deleted, skipped or loosened, and the same error three times in a row stops the loop and asks.

Tests run on the untouched tree first and again at the end. ArchHook.jar test-diff sorts every case: a regression, a test that vanished and a failure the baseline never saw each exit 2; a failure that was already there, or a fix, does not. A red build that was red before is reported as that.

§ step by step

What the skill does, in order

Everything the procedure downloads or writes outside the project goes in one directory, $WORK. It needs a clean worktree, a JDK the build accepts, a wrapper at the project root and network access to the Initializr and the engine's repositories.

  1. 1Refuses a dirty worktree; finds the build tool and its wrapper. No wrapper: it stops.
  2. 2Fetches the source into $SRC, as arch-adopt does: a project below the floor has no ArchHook.jar of this shape.
  3. 3Resolves the target from a throwaway project the Initializr generates, and downloads its dependencies metadata. A version is never written from memory.
  4. 4Resolves the engine's version from the repository that serves it.
  5. 4bThe baseline: runs the tests on the untouched tree, then test-diff snapshot.
  6. 5Pass 1: the version, BOMs, coordinates, starters and types.
  7. 5bGradle only: fixes a wrapper the new Boot plugin refuses.
  8. 6Lists the new classpath.
  9. 7Pass 2, without compiling: the property renames the new jars declare.
  10. 8The guided residue: compile, read, fix the cause, compile again.
  11. 8bThe context loads: the @SpringBootTest classes run first and on their own.
  12. 9Verifies: clean verify or clean build, then test-diff compare.
  13. 10Hands the diff over: no commit, and it prints /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

The report is the format in nerviz's docs; the angle brackets are what each run fills in.

§ a real run

One run, start to finish

A private Gradle project on Spring Boot 3, upgraded with nerviz v0.41.4 on 2026-10-20. The user and project names are blurred. The run took 8m17s and changed 12 files.

8m17s of run in 26 s: one frame every 2 s, frames that barely change dropped. The last frame is the report.
  • Pre-checks: a clean worktree, Gradle 8.10.2 with its wrapper, JDK 21.
  • Target, from the Initializr: Spring Boot 4.1.1. Engine: org.openrewrite:plugin 7.39.0 with rewrite-bom 8.89.0.
  • Baseline: 41 test cases, 0 failing.
  • Pass 1, in build.gradle: Boot, Spring Cloud and springdoc moved, 3 starters renamed, spring-boot-starter-cache added. The Gradle wrapper went from 8.10.2 to 8.14.6.
  • The guided pass: DefaultAuthorizationCodeTokenResponseClient, removed in Spring Security 7, became RestClientAuthorizationCodeTokenResponseClient; a test starter became spring-boot-starter-jdbc-test; Spring Cloud AWS went from 3.1.0 to 4.2.0; two beans moved to Jackson 3's ObjectMapper.
  • Context: 5 @SpringBootTest classes green. Verify: green, and test-diff found 0 regressions, 0 missing, 0 new failures.

It did not commit. Under Not verified it named the OAuth2 login, which no test covers, and it left three decisions to the user: the version pins, the OAuth2 converter still on Jackson 2, and where the cache starter sits in build.gradle. Then it printed /arch-adopt.

§ what it does not do

Limits, said plainly

It is manual only: it rewrites a person's build and sources, so it runs when you ask for it by name. Its write territory is the root build file, the wrapper files and src/**, at the project root and under any module. .claude/** is outside it.

  • Commit. The diff is yours to review.
  • Write .claude/. That is /arch-adopt's, which it hands over to.
  • Delete, skip or loosen a test that fails.
  • Rename Jackson 3 code. It is a decision per site, not a rename, so it stays in the guided pass.
  • Reach a rename that none of nerviz's own exemplars uses; a moved package none of them touches is the guided pass's.
  • Edit around a build that hides its version. A Boot version in a parent POM that is not the project's, or in a version catalog, is not read: it names the file and asks you to change that line.
  • Run on a project already at or above the floor. It says so and stops.
§ what CI proves

Three jobs, and what they leave open

JobWhat it proves
templates › platform-upgradeWith network, on Maven and on Gradle: a fixture on the newest GA line below the floor goes through the skill's procedure and builds green on the Initializr's line. The context loads with the cache and Flyway starters, a Spring Cloud BOM moves, a pinned starter loses its pin, an old Gradle wrapper is fixed, and no test is worse than before.
validate › hooks-cross-platform › upgrade-recipe …The bump carries the target and one step per rename of the production data; each refusal exits 2 with nothing written.
validate › hooks-cross-platform › test-diff …A regression, a vanished test and a new failure each exit 2 alone; a failure already there, a fix and a skip exit 0.

Not proven, on purpose: a scheduled run of the engine job; the Gradle Kotlin DSL and multi-module Gradle builds; and how often a rename is missing from the data.