Skills for creating, testing, reviewing, migrating and guarding Spring Boot 4 projects with Java 25: project scaffolding with architecture patterns, Boot 4 test templates, code review, Boot 3 to 4 migration, and a team harness of build gates and agent hooks.
93%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
The harness steers the agent in three places. AGENTS.md tells it how this repository works before it starts. Hooks check its work before a turn ends, so it fixes its own mistakes. ./mvnw verify (./gradlew build on Gradle) in CI holds the same gates for every developer and every tool. Rules live in the build wherever they can: a gate holds whoever writes the code and costs no context.
Run the script from the project root. Preview with --dry-run first:
python3 ${CLAUDE_SKILL_DIR}/scripts/apply_harness.py . --dry-run
python3 ${CLAUDE_SKILL_DIR}/scripts/apply_harness.py .${CLAUDE_SKILL_DIR} is this skill's folder. Codex doesn't set it, so there use the folder this SKILL.md is in.
Run it in the folder with the pom.xml, or the build.gradle or build.gradle.kts. On Gradle, the gates go in gradle/harness.gradle, which the harness owns like its tests, so a rerun updates it while nobody has edited it; the build file gets one line that applies it, and the team's plugins {} block stays as it is. A build's own Spotless stays as it is, as on Maven; the script only narrows it to the session's files for the Stop hook. It stops on a multi-project build, and when the build already applies Error Prone: remove that, or merge assets/gradle/harness.gradle by hand. Starters are found in the build file and gradle/libs.versions.toml. The rest of this skill names the Maven commands; on Gradle they are ./gradlew build for verify, harnessCheck for the Stop hook's gates, and spotlessApply (or format) for the formatter.
When that folder is inside a larger repository (for example backend/ next to frontend/), the CI workflow, .claude/settings.json, the hooks and .gitattributes go to the repository root, where GitHub and Claude Code read them, and the root AGENTS.md gets a line pointing to the project's own. The Stop hook then reacts only to changes in that folder.
The observability pack comes by default: dependencies, safe defaults, a local Grafana stack, Service health and JVM dashboards, a filter that logs and marks every handled 5xx, a test check that fails on logged emails and credentials, and docs/observability.md. Telemetry stays off in deployments until OTLP_EXPORT_ENABLED=true. It narrows management.endpoints.web.exposure.include to what's safe on the public port, and says so. Pass --no-observability to skip it.
Pass --no-modules when the base package's subpackages are layers (controller, service, domain) rather than modules; Spring Modulith would report them as cycles. Rerunning with --no-modules after a run without it removes ModuleBoundariesTests and the add-module skill unless they were edited, and names what to remove from AGENTS.md by hand.
NullAway checks every class under the base package by default. On an existing codebase with many null findings, offer --nullaway marked: NullAway then checks only packages whose package-info.java has @NullMarked, so the team adopts null-safety one package at a time instead of marking classes @NullUnmarked across the codebase. The script reports how many packages are marked. With none, NullAway checks nothing, so mark at least the packages the team changes most. A rerun keeps the pom's mode unless --nullaway is passed again. Switch to --nullaway all once every package is marked.
It adds only what the project lacks. In the pom, it skips any property, dependency or plugin that is already declared, so an existing Spotless or Spring Modulith setup stays as it is. It adds CLAUDE.md (one line, @AGENTS.md), .claude/settings.json and its two hook scripts, .codex/hooks.json, which registers the same scripts for Codex, .mvn/jvm.config, the three tests, a CI workflow, the CI integrity check, and project skills in both .claude/skills/ and .agents/skills/. It merges its permissions, hooks and plugin into an existing .claude/settings.json, and its hooks into an existing .codex/hooks.json, adds its CI workflow only when no workflow runs Maven (as harness-verify.yml if verify.yml is taken), appends its gate section to an existing AGENTS.md, skips the boundary test when a test already calls ApplicationModules.of(...), and never overwrites a file the team has edited. If <build><plugins> already configures maven-compiler-plugin, it stops: merge that part of assets/pom/plugins.xml by hand and run it again.
Run ./mvnw spotless:apply (spring-javaformat:apply on a project that uses Spring Java Format: the script adds no Spotless there, and baseline.py, the Stop hook and AGENTS.md use the project's formatter; the hook only checks the session's files, since Spring Java Format can't format some files alone) and commit the result on its own, so the reformat stays out of later diffs. On an existing codebase, add that commit's hash to .git-blame-ignore-revs so git blame skips it.
Get ./mvnw verify green. New code must meet the gates; code that already fails them is recorded, not rewritten:
python3 ${CLAUDE_SKILL_DIR}/scripts/baseline.py .. It marks each class NullAway rejects today @NullUnmarked, records today's CodeRulesTests violations in src/test/resources/archunit_store, and today's module-boundary violations in src/test/resources/modulith_store. New classes are checked in full, and a fixed violation drops out of the record. NullAway skips all of a marked class, including code added to it later, so remove @NullUnmarked from a class once it's fixed; the template AGENTS.md tells agents to try when they change one. When an update of the harness adds rules, run baseline.py again: a rule the record doesn't list fails on existing code too, and the rerun records only the new rules' violations.
Don't suppress warnings or skip tests to get green. The Stop hook only blocks on problems in files the agent changed, but until verify is green it can't fully check those either.Draft the Modules, Run and test and Team conventions sections of AGENTS.md from the code, then go through them with the user. Read the packages, the pom, compose files, the test setup, and repeated patterns such as error handling, DTO mapping and naming. Write only what an agent would get wrong without being told: one line per module, naming the section of the project's glossary (GLOSSARY.md or CONTEXT.md) it owns, the commands and services the app needs, and the conventions the code follows. Mark each convention you inferred (inferred); the user confirms or deletes it. Leave out generic Spring advice and anything the code makes obvious, and keep the file short: every session reads all of it.
Commit everything, including .claude/settings.json, the marked classes and archunit_store.
On an existing codebase, find the files where comments make up a large share of the lines, often DTOs and records. Agents write comments as densely as the code they read, so the comment rule in AGENTS.md holds only once those files follow it. Offer to prune them module by module, each module in its own pull request, with code-reviewer's comment pass: it changes only comments.
Offer a project verification skill, which proves behaviour the gates can't see. Read the repository and recommend one generator from pstack. Only the user can start them:
oauth2Login: /pstack:create-verification-skill. It generates a skill that drives the real app and saves evidence. For a browser app, tell it to follow references/browser-verification.md./pstack:create-formal-verification-skill. It checks one property of that code against every case in a stated scope.docs/observability.md, tell the generator to read it, so journeys also check for server errors the page hides.
If pstack isn't installed, say so and skip this step. Adding the skill to AGENTS.md doesn't make agents use it. They verify when the task asks for proof, so tell the team to ask for it in the task.Rerun the script after updating this plugin. It adds the pieces the project is missing and updates the files it owns (hooks, tests, project skills, its workflow) that nobody has changed since, which it tracks in .claude/harness.lock. It reports each edited one so you can merge the new version by hand. It raises the tool versions it pinned in its own pom blocks (the harness:begin properties block and ArchUnit's) to this release's, keeps a version the team raised further, and names each one it raised: run ./mvnw verify after, since a newer Error Prone or NullAway can find new errors. It replaces its own hook entries in .claude/settings.json and .codex/hooks.json, the ones that run a script under .claude/hooks/, with this release's, and leaves the team's hooks alone. Don't copy harness files into a project by hand: rerun the script instead. A rerun also fills in template placeholders left in AGENTS.md, and reports an archunit.properties that allows store creation.
| File | Effect |
|---|---|
AGENTS.md | Instructions both Claude Code and Codex read. Says that done means ./mvnw verify passed, and how to fix each gate |
CLAUDE.md | Imports AGENTS.md for Claude Code versions that don't read it directly |
REVIEW.md | At the repository root, for reviewers: the failures the code doesn't show, which changes are high risk, what not to report, and how to report. The code-reviewer skill and Claude Code's GitHub review read it. Added only when missing; the team fills in the failures, one line each time a review misses a bug |
.claude/settings.json | Lets ./mvnw run without prompts, blocks reading .env files, enables this plugin for every teammate with this marketplace set to update itself, and registers the hooks. For the other skills the team uses, each developer runs install.py from this marketplace once |
.codex/hooks.json | Registers the same two hook scripts for Codex: the guard on Bash and apply_patch, the check on SessionStart and Stop. Codex runs them only after each developer trusts them with /hooks, and asks again whenever the file changes |
.claude/hooks/verify-on-stop.sh | SessionStart and Stop hook, for Claude Code and Codex. When a turn ends and the session has changed code, it formats it and runs the gates that need no Docker, and blocks the turn only on errors in files the session changed, so the agent fixes its own mistakes. Details in references/gates.md |
.claude/hooks/guard.sh | PreToolUse hook, for Claude Code and Codex. Refuses force pushes and hard resets, edits to migrations on the main branch, to the violation records, to a module's package-info.java and to the rule tests, and the Maven flags that accept violations. It is advisory: harness-integrity.yml backs it up in CI. Details in references/gates.md |
scripts/report.py | Summarises what the hooks did in this clone: turns checked, passed and blocked, blocks by gate, how many the agent fixed, check times, and what the guard refused. Run python3 ${CLAUDE_SKILL_DIR}/scripts/report.py . when the team asks whether the hooks are worth their time |
scripts/baseline.py | For an existing codebase: marks the classes NullAway rejects today @NullUnmarked, records today's CodeRulesTests violations, and records today's ModuleBoundariesTests violations in modulith_store, so verify passes while new code is held to the gates |
ConfigurationKeysTests | Fails on spring., management., server. and logging. keys that no dependency defines, or that are deprecated. Boot ignores those keys silently |
BuildSetupTests | Fails on shortcuts that pass the tests while production runs without the feature: a Boot starter or module in test scope, spring-boot-autoconfigure-classic, Flyway migrations without spring-boot-starter-flyway (Boot 4 doesn't run them with flyway-core alone), unless the app builds Flyway itself, and, when the project has migrations, ddl-auto set to create or update the schema or code that writes flyway_schema_history |
UnusedCodeTests | Fails on a non-private method that only tests call: dead code its tests keep alive, or an internal opened up so a test can look inside. Getters, Spring entry points, overrides and methods named in @PreAuthorize expressions are left out. It runs in verify but not in the Stop hook, because a service is often written and tested before anything calls it |
ModuleBoundariesTests | Spring Modulith's checks: no use of another module's internal types, no cycles, no dependency that a module's allowedDependencies leave out, and no unused allowedDependencies entry. Violations recorded in src/test/resources/modulith_store are tolerated; only baseline.py adds to that record. Set verifyAutomatically = false on module tests. Details in references/gates.md |
CodeRulesTests | ArchUnit rules for Spring mistakes that compile and pass most tests, such as a class calling its own @Transactional method, controllers using repositories or entities, HTTP calls inside a transaction and clients without timeouts. Each failure message says how to fix it. It uses the project's ArchUnit or Taikai, and adds ArchUnit only when neither is declared. Full list in references/gates.md |
.github/workflows/harness-integrity.yml, .github/harness/check-protected.sh | Fails a pull request that weakens a gate, whichever tool made the change: adds to the violation records, widens a module's allowedDependencies, or changes a rule test, a migration on the base branch, the hooks, the harness's build setup or its verify workflow. It runs the base branch's copy of the script. A maintainer accepts an intended change with the harness-rule-change label. Make it a required status check. Details in references/gates.md |
.github/harness/check-file-size.sh | Fails when a change adds a source file over 20 KB (about 5,000 tokens; LIMIT in the script), or makes a file over it larger. An agent reads a smaller file in one go; a larger one it reads in parts, and then misses code that already does what it needs. Old files over the limit pass until a change grows them, so no baseline is needed. It skips build output, dependencies, generated code and migrations, and its message says how to split by responsibility. It also fails a new numbered copy such as OrderService2 next to OrderService, the name a split takes when it moves code without grouping it; *Helper and *Utils are only warned against in the message, since a small generic one is fine. The Stop hook runs it on the session's files against the commit the session started from, so a file the session commits is still new to it, and harness-integrity.yml on the pull request against its base, using the base branch's copy |
.github/workflows/verify.yml | Runs ./mvnw verify on every pull request, so the gates hold for code nobody ran locally. Added only when no workflow already runs mvnw |
gradle/harness.gradle | On Gradle, the pom gates below, plus harnessCheck, the task the Stop hook runs, and a report of the declared dependencies that BuildSetupTests reads, since a Gradle build file can't be read from Java. Only baseline.py passes -Pharness.record=true, which turns on both record flags; the guard refuses it |
| pom gates | NullAway in JSpecify mode on main code at compile time (tests are excluded, since they routinely dereference response bodies), Spotless check (none on a project with Spring Java Format, which keeps its own) |
| Project skills | add-module (module layout and test, for module-based projects), move-code (moves a class or package with OpenRewrite's ChangeType or ChangePackage, which update imports, Javadoc links, properties values and META-INF/services entries; it then greps for the old name, since class names in Java string literals stay, and runs verify, which catches package-private code left behind. Agents can't call IntelliJ's move refactoring: its MCP server offers only rename) and db-migration (Flyway naming and safe changes, when the pom uses Flyway). Each tool sees only their names and descriptions until one is used |
options.release instead.-XDaddTypeAnnotationsToSymbol=true argument was added need it added by hand to build on JDK 21..codex/ folder and the hooks themselves with /hooks. It records trust against each hook's hash, so a rerun of the script that changes .codex/hooks.json means every developer trusts it again. Until then Codex sessions get AGENTS.md, the project skills and the CI gates.permissionDecision: deny, decision: block), which both tools honour; Codex 0.166 let an apply_patch edit through when the guard exited 2.python3, which GitHub's ubuntu-latest runners have../mvnw verify; a rerun keeps a version newer than the harness's. On Gradle they are pinned in gradle/harness.gradle, which a rerun replaces while nobody has edited it and keeps as it is once someone has.Add none by default. Each server's tool definitions take context in every session, and the gates above need none. If the team already runs SonarQube, its MCP server (sonarsource/sonarqube-mcp) can show issues and quality gates. It needs a SonarQube Cloud or Server token and Docker. Limit it with SONARQUBE_TOOLSETS=issues,quality-gates and SONARQUBE_READ_ONLY=true, and keep the token in each developer's environment, never in the repository.