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
#!/usr/bin/env bash
# Fails a pull request that changes what the build accepts without saying so. The guard hook refuses these edits
# during a Claude Code or Codex session, but it is advisory: a Bash command, another agent or a person can make them
# anyway. This check is the backstop, and it runs the same way for every tool.
#
# Usage: check-protected.sh <base-commit>
# Set HARNESS_RULE_CHANGE=true (the harness-rule-change label on the pull request) to accept the changes on purpose;
# they are still listed, so the reviewer sees them.
#
# A pull request fails when it:
# - adds lines or files to src/test/resources/archunit_store (removing fixed violations is fine)
# - adds lines to src/test/resources/modulith_store, or creates it (removing fixed violations is fine)
# - widens a module's boundary rules in package-info.java: adds to allowedDependencies, removes it, or changes
# @ApplicationModule otherwise (removing entries from allowedDependencies is fine)
# - changes archunit.properties
# - changes or deletes an existing rule test (adding a new one is fine)
# - changes or deletes a migration that is on the base branch (adding a new one is fine)
# - changes or deletes the harness hooks, this script or its workflow, or the verify workflow the harness added
# - unregisters a harness hook, or narrows when it runs, in .claude/settings.json or .codex/hooks.json
# - changes the harness's plugin block in a pom.xml (NullAway, Spotless), or removes a line from .mvn/jvm.config
# - changes or deletes gradle/harness.gradle, or stops a Gradle build file applying it
# - stops LogLeakCheck running, by unregistering it or turning off JUnit's extension autodetection
set -u
base=${1:?usage: check-protected.sh <base-commit>}
rule_tests='CodeRulesTests|ConfigurationKeysTests|BuildSetupTests|UnusedCodeTests|ModuleBoundariesTests|ModularityTests|ArchUnitTests|ArchitectureRulesTest|LogLeakCheck'
extensions=META-INF/services/org.junit.jupiter.api.extension.Extension
fork=$(git merge-base "$base" HEAD)
problems=()
problem() {
problems+=("$1"$'\t'"$2")
}
# log_leak_check_registered <commit> <services-file>
log_leak_check_registered() {
git show "$1:$2" 2> /dev/null | grep -Eq '^[[:space:]]*[^#[:space:]]+\.LogLeakCheck[[:space:]]*$'
}
# autodetection_on <commit> <properties-file>. The last assignment wins, as in java.util.Properties.
autodetection_on() {
git show "$1:$2" 2> /dev/null | grep -E '^[[:space:]]*junit\.jupiter\.extensions\.autodetection\.enabled[[:space:]=:]' \
| tail -1 | grep -Eiq '[[:space:]=:]true[[:space:]]*$'
}
# module_rules <commit> <package-info>: the @ApplicationModule annotation on one line, without whitespace. Other
# annotations between it and the package line, such as @NullMarked, are left out.
module_rules() {
git show "$1:$2" 2> /dev/null \
| sed -n '/^[[:space:]]*@\(org\.springframework\.modulith\.\)\{0,1\}ApplicationModule\b/,/^[[:space:]]*package[[:space:]]/p' \
| sed '$d' | awk 'NR == 1 || !/^[[:space:]]*@/' | tr -d '[:space:]'
}
# hook_entries <commit> <file>: each registration of a .claude/hooks/ script, one per line: event, matcher, command.
# A file that is missing or isn't valid JSON registers nothing.
hook_entries() {
git show "$1:$2" 2> /dev/null | python3 -c '
import json, sys
try:
hooks = json.load(sys.stdin).get("hooks") or {}
for event, groups in hooks.items():
for group in groups:
for hook in group.get("hooks") or []:
if ".claude/hooks/" in str(hook.get("command", "")):
print(event, group.get("matcher", ""), hook["command"], sep="\t")
except (ValueError, AttributeError, TypeError):
pass
' | sort -u
}
# plugins_block <commit> <pom>: the plugins the harness added, between its markers; empty when there are none.
plugins_block() {
git show "$1:$2" 2> /dev/null | tr -d '\r' | sed -n '/<!-- harness:begin plugins -->/,/<!-- harness:end plugins -->/p'
}
# applies_harness <commit> <build-file>: true when a Gradle build file applies the harness's script.
applies_harness() {
git show "$1:$2" 2> /dev/null | grep -Eq '^[[:space:]]*apply[[:space:](]+from[[:space:]]*[:=][[:space:]]*["'"'"'][^"'"'"']*gradle/harness\.gradle["'"'"']'
}
# harness_owned <commit> <path>: true when the harness wrote the file, as its lock records.
harness_owned() {
git show "$1:.claude/harness.lock" 2> /dev/null | grep -Fq "\"$2\":"
}
allowed_value='allowedDependencies=(\{[^}]*\}|"[^"]*")'
# allowed_entries <rules>: the quoted entries of allowedDependencies, one per line, sorted.
allowed_entries() {
printf '%s' "$1" | grep -oE "$allowed_value" | grep -o '"[^"]*"' | sort -u
}
# plain_names <rules>: true when allowedDependencies holds only quoted module names. A constant, a concatenation, a
# comment or a Unicode escape can name a module that the quoted entries don't show.
plain_names() {
printf '%s' "$1" | grep -oE "$allowed_value" | sed 's/^allowedDependencies=//' \
| grep -Eqx '\{("[^"\\]*"(,"[^"\\]*")*,?)?\}|"[^"\\]*"'
}
# narrowed <base-rules> <head-rules>: true when head only drops entries from a non-empty allowedDependencies.
narrowed() {
local before after
before=$(allowed_entries "$1")
after=$(allowed_entries "$2")
plain_names "$2" && [ -n "$before" ] && [ -n "$after" ] && [ -z "$(comm -13 <(echo "$before") <(echo "$after"))" ] \
&& [ "$(printf '%s' "$1" | sed -E "s/$allowed_value//")" = "$(printf '%s' "$2" | sed -E "s/$allowed_value//")" ]
}
while IFS=$'\t' read -r status path; do
[ -n "$path" ] || continue
case "$path" in
*/archunit_store/* | archunit_store/*)
if [ "$status" = A ]; then
problem "$path" "records rule violations as accepted. Fix the violation instead."
elif [ "$status" = M ] && [ "$(git diff --numstat "$base"...HEAD -- "$path" | cut -f1)" != 0 ]; then
problem "$path" "adds entries to the ArchUnit freeze record, which accepts new violations. Fix them instead."
fi ;;
*/modulith_store | modulith_store)
if [ "$status" = A ]; then
problem "$path" "records module-boundary violations as accepted. Fix the violation instead."
elif [ "$status" = M ] && [ "$(git diff --numstat "$base"...HEAD -- "$path" | cut -f1)" != 0 ]; then
problem "$path" "adds entries to the module-boundary record, which accepts new violations. Fix them instead."
fi ;;
*/package-info.java)
before=$(module_rules "$fork" "$path")
if [ -n "$before" ] && [ "$status" != A ]; then
after=$(module_rules HEAD "$path")
[ "$before" = "$after" ] || narrowed "$before" "$after" \
|| problem "$path" "changes the module's boundary rules (allowedDependencies or its type), which lets new dependencies through."
fi ;;
*/archunit.properties | archunit.properties)
problem "$path" "changes how the ArchUnit freeze record works." ;;
*/db/migration/*)
[ "$status" = A ] || problem "$path" "is on the base branch, so it may already have run; Flyway fails when its checksum changes. Add a new migration instead." ;;
*/"$extensions")
if log_leak_check_registered "$fork" "$path" && ! log_leak_check_registered HEAD "$path"; then
problem "$path" "stops LogLeakCheck running, so nothing fails on personal data in logs."
fi ;;
*/junit-platform.properties)
if log_leak_check_registered "$fork" "${path%/*}/$extensions" && autodetection_on "$fork" "$path" \
&& ! autodetection_on HEAD "$path"; then
problem "$path" "turns off extension autodetection, which stops LogLeakCheck running."
fi ;;
.claude/hooks/* | .github/harness/* | .github/workflows/harness-integrity.yml)
problem "$path" "is part of the harness's own enforcement." ;;
.github/workflows/verify.yml | .github/workflows/harness-verify.yml)
harness_owned "$fork" "$path" && problem "$path" "runs the build on every pull request, so the gates hold for code nobody ran locally." ;;
.claude/settings.json | .codex/hooks.json)
[ -z "$(comm -23 <(hook_entries "$fork" "$path") <(hook_entries HEAD "$path"))" ] \
|| problem "$path" "unregisters a harness hook or narrows when it runs, so agents' changes go unchecked." ;;
pom.xml | */pom.xml)
before=$(plugins_block "$fork" "$path")
[ -z "$before" ] || [ "$before" = "$(plugins_block HEAD "$path")" ] \
|| problem "$path" "changes the harness's plugin block (NullAway, Spotless), which changes what the build accepts." ;;
gradle/harness.gradle | */gradle/harness.gradle)
[ "$status" = A ] || problem "$path" "holds the harness's build gates (NullAway, Spotless), so changing it changes what the build accepts." ;;
build.gradle | */build.gradle | build.gradle.kts | */build.gradle.kts)
if applies_harness "$fork" "$path" && ! applies_harness HEAD "$path"; then
problem "$path" "stops applying gradle/harness.gradle, which turns off NullAway and the formatter check."
fi ;;
.mvn/jvm.config | */.mvn/jvm.config)
[ -z "$(comm -23 <(git show "$fork:$path" 2> /dev/null | tr -d '\r' | sort -u) \
<(git show "HEAD:$path" 2> /dev/null | tr -d '\r' | sort -u))" ] \
|| problem "$path" "removes a JVM option the compiler needs to run Error Prone and NullAway." ;;
*)
if [ "$status" != A ] && printf '%s' "${path##*/}" | grep -Eqx "($rule_tests)\.java"; then
problem "$path" "holds the team's build rules; changing it changes what every build accepts."
fi ;;
esac
done < <(git diff --name-status --no-renames "$base"...HEAD) || true
if [ ${#problems[@]} -eq 0 ]; then
echo "Harness integrity: no protected files changed."
exit 0
fi
level=error
[ "${HARNESS_RULE_CHANGE:-false}" = true ] && level=warning
for entry in "${problems[@]}"; do
echo "::$level file=${entry%%$'\t'*}::${entry%%$'\t'*} ${entry#*$'\t'}"
done
if [ "$level" = warning ]; then
echo "Harness integrity: ${#problems[@]} protected file(s) changed, accepted by the harness-rule-change label."
exit 0
fi
echo "Harness integrity: ${#problems[@]} protected file(s) changed. If the change is intended, a maintainer adds the" \
"harness-rule-change label to the pull request."
exit 1