From 2e331a0aa554adc1825b21c7f02f3cf942ca0b7a Mon Sep 17 00:00:00 2001 From: jrdrew Date: Mon, 12 Oct 2026 11:54:37 -0400 Subject: [PATCH] update the readme and claude documentation to simplify some things and fix formatting inconsistency --- CLAUDE.md | 46 ++++++++++++++++++++-------------------------- README.md | 4 ++-- build.gradle.kts | 17 ++++++++++++++--- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 26cad2b..835f110 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,26 +1,20 @@ -# CLAUDE.md - -Guidance for Claude Code (and other agents) working in this repository. - -## What this is - -A Java project template: Gradle (Kotlin DSL), Checkstyle, PMD, google-java-format (via Spotless), JaCoCo, SLF4J/Logback, and a unit/integration test split (`src/test` vs `src/int-test`). See `README.md` for the full command reference. - -## Conventions to follow - -- **Logging: SLF4J, never `System.out`/`System.err`.** Get a logger with `private static final Logger LOG = LoggerFactory.getLogger(ThisClass.class);` and use parameterized messages (`LOG.info("thing happened: {}", value)`), not string concatenation. PMD's `SystemPrintln` rule (bestpractices, unexcluded) will fail the build on raw console I/O in `src/main`. -- **Formatting is not manual.** Don't hand-format Java files or argue with Checkstyle about whitespace/import order — run `./gradlew spotlessApply` and let google-java-format do it. Checkstyle and PMD are deliberately scoped to things the formatter doesn't cover (naming, imports, coding problems, bug patterns); they're not fighting the formatter. -- **Unit vs integration tests are not interchangeable.** `src/test` (the `test` task) is for fast, isolated unit tests. `src/int-test` (the `intTest` task) is for broader/slower tests. Don't add integration-style tests under `src/test`, and don't assume `./gradlew test` alone exercises integration coverage — see the JaCoCo note below. -- **The 80%-line / 70%-branch coverage gate (`jacocoTestCoverageVerification`) is intentionally wired to combined unit + integration execution data** (`executionData(tasks.test.get(), intTestTask.get())` in `build.gradle.kts`). If you add a new source set or test task, JaCoCo's default conventions won't pick it up automatically — you have to wire its exec data in explicitly, the same way, or coverage numbers will silently look wrong (lower than reality) without the gate failing to catch it, or vice versa. -- **PR descriptions must list Jira story keys** per `.github/pull_request_template.md` — that's the org's convention (see the Trident Jira Project Management Playbook), not something to improvise away. - -## Known gotchas / do-not-"fix"-without-checking - -- **`googleJavaFormat("1.28.0")` is pinned deliberately, not stale.** Spotless 8.9.0's reflective glue adapter (`GoogleJavaFormatFormatterFunc`) breaks against newer google-java-format releases (1.37.0 reproducibly threw `InvocationTargetException` in testing here). If you bump Spotless, re-verify the gjf version still works before bumping it too — don't just grab "latest" for both. -- **`gradle/gradle-daemon-jvm.properties` pins the Gradle daemon itself to JDK 25**, separately from the project's `java.toolchain` (also 25). This was necessary because an ambient JDK 27 on the dev machine caused flaky in-process formatter failures. Don't remove this file assuming the project toolchain setting alone is enough — they serve different purposes (daemon JVM vs. compile/run JVM). -- **PMD's `classpath` is explicitly given the toolchain's `jrt-fs.jar`** (`build.gradle.kts`, near the PMD config) to stop it from guessing "current platform" JDK classes and printing a warning every run. If PMD warnings about aux classpath reappear after a Gradle/PMD version bump, check this block still resolves correctly rather than re-adding `PMD_JAVA_DISABLE_AUX_CLASSPATH_WARNINGS` workarounds (that env var doesn't reliably propagate into PMD's forked worker process anyway — verified in testing). -- **`LoosePackageCoupling` and `GuardLogStatement` are deliberately excluded from PMD's design/bestpractices rules** (`config/pmd/ruleset.xml`) — the former needs explicit package config to be meaningful, the latter is largely redundant once logging is parameterized via SLF4J. Don't silently re-add them without updating the ruleset's comments explaining why they're off. - -## Before considering a change "done" - -Run `./gradlew check` (compiles, formats-checks, Checkstyle, PMD, unit + integration tests, coverage gate) clean before reporting work finished. CI (`.github/workflows/ci.yml`) runs the same tasks as separate matrix jobs so each shows up as its own PR check. +# Project +templateJavaProject — Java template +Gradle (Kotlin DSL), Java 25 (see `gradle/gradle-daemon-jvm.properties`), JUnit 6, SLF4J/Logback + +## Build Commands +- Verify everything: `./gradlew check` (compile, format check, Checkstyle, PMD, unit + integration tests, coverage gate) +- Test: `./gradlew test` +- Integration test: `./gradlew intTest` +- Format: `./gradlew spotlessApply` + +## Package Structure +com.example.app — application code (`src/main`) +src/test — unit tests (JUnit 6), run by `test` +src/int-test — integration tests, run by `intTest`, not part of `test` + +## Key Conventions +- Logging via SLF4J (`LoggerFactory`) — never `System.out`/`System.err` +- Formatting via Spotless + google-java-format — run `spotlessApply`, don't hand-format +- Coverage gate requires 80% line / 70% branch, combined across unit + integration tests +- PRs must list Jira story keys per `.github/pull_request_template.md` diff --git a/README.md b/README.md index 879cf8f..67b6be6 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ A Java project template with Gradle (Kotlin DSL), Checkstyle, PMD, [google-java- ## Requirements - The [Gradle Wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html) (`./gradlew`) is checked in — you don't need Gradle installed locally. -- No JDK needs to be pre-installed either: the project targets **Java 25** via a Gradle toolchain, and the `foojay-resolver-convention` plugin (`settings.gradle.kts`) will auto-download a matching JDK if one isn't already on your machine. -- The Gradle **daemon** itself is also pinned to JDK 25, via `gradle/gradle-daemon-jvm.properties` (generated with `./gradlew updateDaemonJvm --jvm-version=25`). This keeps the build tooling (Spotless/google-java-format in particular) running on a JDK it's actually compatible with, regardless of whatever JDK happens to be on your `PATH`. +- No JDK needs to be pre-installed either: the project targets **Java 25**, and the `foojay-resolver-convention` plugin (`settings.gradle.kts`) will auto-download a matching JDK if one isn't already on your machine. +- The Java version is defined in exactly one place: `gradle/gradle-daemon-jvm.properties`. It pins the Gradle **daemon** itself (keeping build tooling like Spotless/google-java-format on a JDK it's actually compatible with, regardless of what's on your `PATH`), and `build.gradle.kts` reads that same file to set the project's `java.toolchain`. To bump the Java version, run `./gradlew updateDaemonJvm --jvm-version=` — don't edit a version number directly in `build.gradle.kts`, there isn't one. ## Project layout diff --git a/build.gradle.kts b/build.gradle.kts index 902c186..4816c81 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ plugins { jacoco checkstyle pmd - id("com.diffplug.spotless") version "8.9.0" + id("com.diffplug.spotless") version "8.10.4" } group = "com.example" @@ -13,9 +13,20 @@ repositories { mavenCentral() } +val daemonJvmLanguageVersion = + providers + .fileContents(layout.projectDirectory.file("gradle/gradle-daemon-jvm.properties")) + .asText + .map { text -> + val version = + text.lineSequence().firstOrNull { it.startsWith("toolchainVersion=") }?.substringAfter("=") + ?: error("toolchainVersion not found in gradle/gradle-daemon-jvm.properties") + JavaLanguageVersion.of(version) + } + java { toolchain { - languageVersion.set(JavaLanguageVersion.of(25)) + languageVersion.set(daemonJvmLanguageVersion) } } @@ -137,7 +148,7 @@ tasks.withType().configureEach { spotless { java { target("src/*/java/**/*.java") - googleJavaFormat("1.28.0") + googleJavaFormat("1.36.1") removeUnusedImports() trimTrailingWhitespace() endWithNewline()