CtrlK
BlogDocsLog inGet started
Tessl Logo
PluginPublic

apavithraa/springboot-architecture

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.

Quality

93%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

SKILL.mdskills/spring-boot-team-harness/

name:
spring-boot-team-harness
description:
Use when a team wants every developer's coding agent (Claude Code or Codex) to follow the same rules and coding conventions on a Maven or Gradle Spring Boot 4 repository: setting up AGENTS.md, coding standards, guardrails, shared agent settings, or build and CI checks that agents must pass before code merges. It adds ./mvnw verify or ./gradlew build gates that fail on null-safety bugs (NullAway with JSpecify), module-boundary breaches (Spring Modulith), unknown or deprecated Boot property keys, Spring mistakes that compile (ArchUnit) and unformatted code, Claude Code and Codex hooks that run those gates before a turn ends, and a CI workflow.

Spring Boot team harness

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.

Apply it

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

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

  3. Get ./mvnw verify green. New code must meet the gates; code that already fails them is recorded, not rewritten:

    • If there are only a few findings, fix them with the user. Each NullAway finding is a question about what a missing value means, so don't guess.
    • Otherwise run 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.
  4. 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.

  5. Commit everything, including .claude/settings.json, the marked classes and archunit_store.

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

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

    • The app has a UI, or signs users in with 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.
    • The riskiest code is a state machine, retries, idempotency or concurrent updates: /pstack:create-formal-verification-skill. It checks one property of that code against every case in a stated scope.
    • Both apply: start with the first; the second can follow in its own pull request. If the project has 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.

Rerunning

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.

What each piece does

FileEffect
AGENTS.mdInstructions both Claude Code and Codex read. Says that done means ./mvnw verify passed, and how to fix each gate
CLAUDE.mdImports AGENTS.md for Claude Code versions that don't read it directly
REVIEW.mdAt 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.jsonLets ./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.jsonRegisters 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.shSessionStart 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.shPreToolUse 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.pySummarises 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.pyFor 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
ConfigurationKeysTestsFails on spring., management., server. and logging. keys that no dependency defines, or that are deprecated. Boot ignores those keys silently
BuildSetupTestsFails 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
UnusedCodeTestsFails 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
ModuleBoundariesTestsSpring 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
CodeRulesTestsArchUnit 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.shFails 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.shFails 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.ymlRuns ./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.gradleOn 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 gatesNullAway 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 skillsadd-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

Limits

  • Single-project builds only: a Maven project, or a Gradle build in Groovy or Kotlin DSL. A Gradle build that includes other projects needs the script applied to each by hand.
  • On Gradle, javac runs on the toolchain's JDK, so Error Prone needs a toolchain of 21.0.8 or later. With an older toolchain, build on a newer JDK and set options.release instead.
  • Error Prone needs JDK 21.0.8 or later to build. Projects that set up the compiler plugin before the -XDaddTypeAnnotationsToSymbol=true argument was added need it added by hand to build on JDK 21.
  • Codex runs the project's hooks only once the developer trusts the project's .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.
  • The hooks are bash scripts. On Windows, Claude Code runs them with Git for Windows; without it they fail. Codex 0.166 on Windows runs them the same way.
  • The hooks refuse and block by printing a decision as JSON (permissionDecision: deny, decision: block), which both tools honour; Codex 0.166 let an apply_patch edit through when the guard exited 2.
  • The integrity check reads the hook registrations with python3, which GitHub's ubuntu-latest runners have.
  • Tool versions are pinned in the pom properties and move only when the plugin is updated and the script rerun. To move one sooner, raise it in the pom and run ./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.

MCP servers

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.

tile.json