<!--
SPDX-License-Identifier: Apache-2.0
Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements; and to You under the Apache License, Version 2.0.
-->
What I Do
- Explain how
GrailsCodeStylePlugin, GrailsCodeAnalysisPlugin, and GrailsViolationAggregationPlugin enforce code quality across all 60+ modules.
- Guide you through running style and analysis checks, interpreting the per-tool Markdown violation reports, and fixing each class of violation.
- Describe which tools are always-on vs. opt-in, how to configure them via Gradle properties, and which violations can be auto-fixed.
When to Use Me
Activate this skill when:
- Running
./gradlew aggregateViolations and interpreting the resulting *_VIOLATIONS.md files.
- Fixing CodeNarc, Checkstyle, PMD, SpotBugs, or Spotless violations reported in those files.
- Configuring code style or analysis tools across the repo (enabling/disabling tools or adjusting rule files).
- Preparing a commit - the plugin output must be clean before merging.
Plugin Overview
| Plugin | Applied to | Responsibility |
|---|
org.apache.grails.gradle.grails-code-style | Every subproject | Applies Checkstyle, CodeNarc, and code analysis; registers per-project codeStyle task; redirects XML reports to root build/reports/code-style/ |
org.apache.grails.gradle.grails-code-analysis | Every subproject | Applies PMD and SpotBugs (both opt-in); registers per-project codeAnalysis task; redirects XML reports to root build/reports/code-analysis/ |
org.apache.grails.gradle.grails-jacoco | Every subproject | Applies JaCoCo; wires jacocoTestReport to run after each test task |
org.apache.grails.gradle.grails-violation-aggregation | Root project only | Registers aggregateViolations and aggregateJacocoCoverage tasks; writes Markdown summaries to build/reports/violations/ |
Key Tasks
| Task | Scope | Description |
|---|
./gradlew codeStyle | per-project | Runs Checkstyle and CodeNarc for that project |
./gradlew codeAnalysis | per-project | Runs PMD and/or SpotBugs for that project (when enabled) |
./gradlew aggregateViolations | root | Runs all checks across every module, then writes *_VIOLATIONS.md to build/reports/violations/ |
./gradlew validateRepositoryConventions | root | Validates canonical skill metadata, AGENTS paths, GitHub Action pins, and message keys. RAT provenance is the separate rat task, which aggregateViolations runs |
./gradlew cleanViolationReports | root | Deletes analyzer XML reports, their markers, and the aggregate Markdown so the next aggregate run re-analyzes every module. The root clean runs it |
./gradlew aggregateJacocoCoverage | root | Runs JaCoCo reports across every module, then writes JACOCO_COVERAGE.md to build/reports/violations/ |
./gradlew codenarcFix | per-project | Auto-fixes a subset of CodeNarc violations |
Quick commands
# Check a single module (style only)
./gradlew :grails-core:codeStyle
# Check a single module (analysis - enable through the module extension or an override)
./gradlew :grails-core:codeAnalysis -Pgrails.code-analysis.enabled.pmd=true
# Full multi-module check + report (use --continue so the reports are written even when an analyzer fails)
./gradlew aggregateViolations --continue
# Force every module to be re-analyzed instead of reusing UP-TO-DATE analyzer results
./gradlew cleanViolationReports aggregateViolations --continue
# Repository conventions only
./gradlew validateRepositoryConventions
# Include test sources in style checks
./gradlew aggregateViolations -Pgrails.code-style.enabled.tests=true
# Include test sources in analysis
./gradlew aggregateViolations -Pgrails.code-analysis.enabled.tests=true
# Ignore failures (collect reports without failing the build)
./gradlew aggregateViolations -Pgrails.code-style.ignoreFailures=true -Pgrails.code-analysis.ignoreFailures=true
# Auto-fix some CodeNarc violations before running checks
./gradlew codenarcFix codeStyle
# JaCoCo coverage report
./gradlew aggregateJacocoCoverage
Output Files
After running aggregateViolations, these files appear under build/reports/violations/ in the root project build directory:
| File | Tool | Always generated |
|---|
build/reports/violations/CODENARC_VIOLATIONS.md | CodeNarc | Yes |
build/reports/violations/CHECKSTYLE_VIOLATIONS.md | Checkstyle | Yes |
build/reports/violations/PMD_VIOLATIONS.md | PMD | Yes - reports PMD is disabled. when PMD is disabled |
build/reports/violations/SPOTBUGS_VIOLATIONS.md | SpotBugs | Yes - reports SpotBugs is disabled. when SpotBugs is disabled |
build/reports/violations/REPOSITORY_CONVENTIONS.md | Repository conventions | Yes - lists skill, Action, or message-key failures. Ordered after rat in the aggregateViolations lane |
After running aggregateJacocoCoverage:
| File | Tool | Generated |
|---|
build/reports/violations/JACOCO_COVERAGE.md | JaCoCo | Only when at least one subproject has a JaCoCo CSV report |
All reports are inside build/ and are excluded from version control via .gitignore. A clean run produces No violations found! 🎉 in each style file. The build must be clean before committing.
Each aggregated style or analysis report begins with Modules analyzed:, which names only modules that contributed data. Each file is a Markdown table grouped by module, with columns: Class, Tool, Violation, Line, Message.
Only the aggregate lane (aggregateViolations, aggregateStyleViolations, aggregateAnalysisViolations) writes these Markdown files. Running an analyzer task directly, such as ./gradlew :grails-core:checkstyleMain, produces only that task's own XML report and deliberately leaves the aggregate Markdown untouched, so a partial run can never overwrite an authoritative full-repository report. Because the writer is part of that lane rather than a per-task finalizer, pass --continue when you expect violations, otherwise the failing analyzer stops the build before the report explaining the failure is written. Analyzers for unchanged modules stay UP-TO-DATE between aggregate runs and their previous results are aggregated; run cleanViolationReports (or the root clean) first to force a full re-analysis.
Repository Conventions
Run ./gradlew validateRepositoryConventions to write build/reports/violations/REPOSITORY_CONVENTIONS.md. Fix the reported source rather than suppressing the validation.
| Finding | Fix |
|---|
| Skill | Start SKILL.md with YAML front matter, supply string name, description, and license values, use a valid directory name that matches name, and keep names unique. Every skill path that AGENTS.md references must exist, but AGENTS.md is not required to index every skill. |
| GitHub Action | Pin third-party references to one lowercase 40-hex immutable reference for that action across workflows and repository-local action.yml or action.yaml manifests. A 40-hex value may be a commit or annotated-tag object SHA. actions/* must use the full tag of a release (vX.Y.Z) and apache/* a version or branch reference, never a 40-hex SHA, and local ./... uses are permitted. Pin Docker uses, Docker action runs.image, and workflow job/service container images to literal immutable name@sha256:<digest> values. |
| Message key | Remove or rename the duplicate logical key in the reported grails-app/i18n/**/*.properties file, preserving escaped separators and continuation semantics. |
Tool Details
CodeNarc (Groovy - always enabled)
Rule file: build/code-style/codenarc/codenarc.groovy (generated by the plugin during setup; not intended to be edited directly).
Most common violations and how to fix them:
| Rule | Fix |
|---|
UnnecessaryGString | Replace "plain string" with 'plain string' |
UnnecessarySemicolon | Remove trailing ; |
SpaceBeforeOpeningBrace | Add space before { → method() { |
SpaceAroundMapEntryColon | [key: value] not [key:value] |
ConsecutiveBlankLines | Collapse 3+ blank lines to 2 |
ClassStartsWithBlankLine | Remove blank line right after class Foo { |
NoWildcardImports | Expand import org.foo.* to explicit imports |
UnusedImport | Remove imports not referenced in the file |
MethodName | Method names must be camelCase (not snake_case) |
VariableName | Variable names must be camelCase |
LineLength | Keep lines ≤ 200 chars (default) |
Auto-fixable via codenarcFix: ClassStartsWithBlankLine, SpaceAroundMapEntryColon, UnnecessaryGString, UnnecessarySemicolon, SpaceBeforeOpeningBrace, ConsecutiveBlankLines.
Checkstyle (Java - always enabled)
Rule file: build/code-style/checkstyle/checkstyle.xml.
Common violations:
| Rule | Fix |
|---|
ImportOrder | Re-order imports: `java |
AvoidStarImport | Use explicit class imports |
UnusedImports | Remove unused imports |
WhitespaceAround | Add spaces around operators and keywords |
NeedBraces | Add {} to single-statement if/for/while |
FileTabCharacter | Replace tabs with 4 spaces |
NewlineAtEndOfFile | Ensure file ends with \n |
PMD (Java/Groovy - opt-in)
Enable PMD in each clean module's build.gradle with grailsCodeAnalysis { enablePmd() }. Use -Pgrails.code-analysis.enabled.pmd.projects=:project-a,:project-b to also enable selected project paths for a baseline run. Use -Pgrails.code-analysis.enabled.pmd=true or =false to switch PMD on or off for every project; when set, it wins over both the module opt-ins and the .projects list. PMD excludes sources under each project's configured build directory.
enablePmd() configures PMD immediately, so customize the pmd* tasks directly after it, for example tasks.named('pmdMain') { ... }.
Rule file: build/code-analysis/pmd/pmd.xml.
SpotBugs (Java bytecode - opt-in)
Enable SpotBugs in each clean module's build.gradle with grailsCodeAnalysis { enableSpotbugs() }. Use -Pgrails.code-analysis.enabled.spotbugs.projects=:project-a,:project-b to also enable selected project paths for a baseline run. Use -Pgrails.code-analysis.enabled.spotbugs=true or =false to switch SpotBugs on or off for every project; when set, it wins over both the module opt-ins and the .projects list.
enableSpotbugs() configures SpotBugs immediately, so customize the spotbugs* tasks directly after it, for example tasks.named('spotbugsMain') { ... }.
Runs at Effort.MAX / Confidence.HIGH. Only high-confidence bugs are reported.
Spotless (Java auto-formatting - opt-in)
Enable: -Pgrails.code-style.enabled.spotless=true
Uses Palantir Java Format. Can auto-fix by running:
./gradlew spotlessApply
Configuration Properties
All properties can be set in gradle.properties or passed as -P flags:
grails-code-style plugin (Checkstyle + CodeNarc)
| Property | Default | Description |
|---|
grails.code-style.enabled.checkstyle | true | Enable Checkstyle |
grails.code-style.enabled.codenarc | true | Enable CodeNarc |
grails.code-style.enabled.spotless | false | Enable Spotless |
grails.code-style.enabled.tests | false | Also check test source sets |
grails.code-style.ignoreFailures | false | Collect reports without failing build |
grails.code-style.codenarc.fix | false | Run codenarcFix before CodeNarc tasks |
grails.codestyle.dir.checkstyle | (auto) | Custom path to Checkstyle config dir |
grails.codestyle.dir.codenarc | (auto) | Custom path to CodeNarc config dir |
skipCodeStyle | unset | If present, every static check is skipped: CodeNarc and Checkstyle, and PMD and SpotBugs as well. The aggregate reports say the tools were skipped |
grails-code-analysis plugin (PMD + SpotBugs)
Enable PMD and SpotBugs primarily in each module's build.gradle:
grailsCodeAnalysis {
enablePmd()
enableSpotbugs()
}
The Gradle properties below are all-project or selected-project overrides for baseline runs.
| Property | Default | Description |
|---|
grails.code-analysis.enabled.pmd | unset | When set, true or false overrides PMD for every project, including enablePmd() opt-ins and the .projects list |
grails.code-analysis.enabled.pmd.projects | unset | Also enable PMD for comma-separated project paths, unless the all-project property is set |
grails.code-analysis.enabled.spotbugs | unset | When set, true or false overrides SpotBugs for every project, including enableSpotbugs() opt-ins and the .projects list |
grails.code-analysis.enabled.spotbugs.projects | unset | Also enable SpotBugs for comma-separated project paths, unless the all-project property is set |
grails.code-analysis.enabled.tests | false | Also analyse test source sets |
grails.code-analysis.ignoreFailures | false | Collect ordinary findings without failing the build; missing expected XML always fails |
grails.code-analysis.dir.pmd | (auto) | Custom path to PMD config dir |
skipCodeAnalysis | unset | If present, PMD and SpotBugs are skipped while CodeNarc and Checkstyle still run. skipCodeStyle also skips them |
Fixing Violations Workflow
- Run
./gradlew aggregateViolations -Pgrails.code-style.ignoreFailures=true -Pgrails.code-analysis.ignoreFailures=true
- Open
build/reports/violations/CODENARC_VIOLATIONS.md and build/reports/violations/CHECKSTYLE_VIOLATIONS.md to see all issues by module
- For CodeNarc, run
./gradlew codenarcFix to auto-fix what it can
- Fix remaining violations manually using the table above
- Re-run
./gradlew aggregateViolations and confirm files contain No violations found! 🎉
- The reports are inside
build/ and do not need to be deleted before committing
Reports Directory Structure
All XML reports are consolidated at:
build/reports/code-style/ ← XML inputs for style aggregation
├── checkstyle/
│ ├── <hex-project-path>-checkstyleMain.xml
│ ├── <hex-project-path>-checkstyleCli.xml
│ └── ...
└── codenarc/
├── <hex-project-path>-codenarcMain.xml
└── ...
build/reports/code-analysis/ ← XML inputs for analysis aggregation (if enabled)
├── pmd/
└── spotbugs/
build/reports/violations/ ← Markdown summaries written by aggregateViolations
├── CODENARC_VIOLATIONS.md
├── CHECKSTYLE_VIOLATIONS.md
├── PMD_VIOLATIONS.md
├── SPOTBUGS_VIOLATIONS.md
├── REPOSITORY_CONVENTIONS.md
└── JACOCO_COVERAGE.md ← written by aggregateJacocoCoverage
The filename prefix is the UTF-8 hexadecimal encoding of the full Gradle project path. Aggregation decodes it back to paths such as :grails-core, preventing nested projects with the same leaf name from colliding.