This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch build-hygiene-from-core
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git

commit 3ce3e0f2e8b6b157162436107397b6cca6cd0685
Author: James Daugherty <[email protected]>
AuthorDate: Sat Jul 18 22:05:27 2026 -0400

    Port build-hygiene tooling from grails-core
    
    Add a build-logic included build with Kotlin convention plugins for
    build reproducibility, JaCoCo code coverage, and Sonatype OSS Index
    vulnerability scanning, applied to the root project and every module.
    
    - reproducible archives (preserveFileTimestamps=false, 
reproducibleFileOrder,
      fixed permissions) so the plugin ZIP and module jars are byte-stable
    - JaCoCo per-module + root jacoco-report-aggregation 
(testCodeCoverageReport),
      CC-safe under this repo's org.gradle.configuration-cache=true
    - ossIndexAudit with an OssIndexAuditThrottle build service and fail-fast 
when
      SONATYPE_GUIDE_TOKEN is absent
    - CI: coverage.yml (Codecov), vulnerability-scan.yml (OSS Index), codeql.yml
    - codecov.yml and .github/dependabot.yml (gradle, /build-logic, 
github-actions)
    - RELEASE.md documenting the release pipeline + reproducibility verification
    - etc/bin/test-reproducible-build.sh and etc/bin/Dockerfile for repro 
testing
    - .gitattributes pinning LF; enable GitHub issues in .asf.yaml
---
 .asf.yaml                                          |   3 +
 .gitattributes                                     |   8 ++
 .github/dependabot.yml                             |  43 +++++++
 .github/workflows/codeql.yml                       |  55 +++++++++
 .github/workflows/coverage.yml                     |  60 ++++++++++
 .github/workflows/vulnerability-scan.yml           |  85 ++++++++++++++
 .gitignore                                         |   4 +
 RELEASE.md                                         | 126 +++++++++++++++++++++
 build-logic/build.gradle.kts                       |  29 +++++
 build-logic/settings.gradle.kts                    |  26 +++++
 .../src/main/kotlin/OssIndexAuditThrottle.kt       |  35 ++++++
 .../main/kotlin/grails-intellij.jacoco.gradle.kts  |  46 ++++++++
 .../kotlin/grails-intellij.reproducible.gradle.kts |  34 ++++++
 .../grails-intellij.vulnerability-scan.gradle.kts  |  66 +++++++++++
 build.gradle.kts                                   |  35 ++++++
 codecov.yml                                        |  37 ++++++
 copyright/build.gradle.kts                         |   3 +
 coverage/build.gradle.kts                          |   3 +
 etc/bin/Dockerfile                                 |  48 ++++++++
 etc/bin/test-reproducible-build.sh                 |  84 ++++++++++++++
 gradle-tooling/build.gradle.kts                    |   3 +
 gradle.properties                                  |   3 +
 grails-compiler-patch/build.gradle.kts             |   3 +
 grails-rt/build.gradle.kts                         |   3 +
 hibernate/build.gradle.kts                         |   3 +
 i18n/build.gradle.kts                              |   3 +
 jps-plugin/build.gradle.kts                        |   3 +
 langInjection/build.gradle.kts                     |   3 +
 maven/build.gradle.kts                             |   3 +
 settings.gradle.kts                                |   4 +
 testFramework/build.gradle.kts                     |   3 +
 31 files changed, 864 insertions(+)

diff --git a/.asf.yaml b/.asf.yaml
index 60fbd2d..79c7763 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -32,6 +32,9 @@ github:
       prevent_self_review: false
   description: "Grails plugin for IntelliJ IDEA"
   homepage: https://grails.apache.org/
+  # enable the GitHub Issues tracker for this repository
+  features:
+    issues: true
   rulesets:
     - name: "Default Branch Protection"
       type: branch
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..45d9ad4
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,8 @@
+# Files that must keep LF line endings on every platform. The 
reproducible-build
+# Docker image (etc/bin/Dockerfile) refuses shell scripts with CRLF shebangs, 
and
+# Gradle wrapper/property parsing expects LF. Pin them so a checkout under
+# core.autocrlf=true cannot corrupt the build.
+*.sh            text eol=lf
+gradlew         text eol=lf
+Dockerfile      text eol=lf
+*.properties    text eol=lf
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..24cdef9
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,43 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+version: 2
+updates:
+  # Main build (root Gradle project + all modules)
+  - package-ecosystem: gradle
+    directory: "/"
+    schedule:
+      interval: daily
+    open-pull-requests-limit: 10
+    target-branch: main
+    labels:
+      - "type: dependency upgrade"
+  # build-logic included build (convention plugins + their tool dependencies)
+  - package-ecosystem: gradle
+    directory: "/build-logic"
+    schedule:
+      interval: daily
+    open-pull-requests-limit: 10
+    target-branch: main
+    labels:
+      - "type: dependency upgrade"
+  # GitHub Actions used by the workflows
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+    target-branch: main
+    labels:
+      - "type: dependency upgrade"
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..84b35b1
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,55 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: "CodeQL"
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
+# queue jobs and only allow 1 run per branch due to the likelihood of hitting 
GitHub resource limits
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+    permissions:
+      actions: read
+      contents: read
+      security-events: write
+    strategy:
+      fail-fast: false
+      matrix:
+        # The plugin is Kotlin + Java; the CodeQL 'java' pack covers both JVM 
languages.
+        language: [ 'java' ]
+    steps:
+      - name: "📥 Checkout repository"
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 
v6.0.2
+      - name: "☕️ Setup JDK"
+        uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # 
v5.2.0
+        with:
+          distribution: liberica # .sdkmanrc pins librca = Liberica
+          java-version: 25
+      - name: "🔎 Initialize CodeQL"
+        uses: 
github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
+        with:
+          languages: ${{ matrix.language }}
+          build-mode: none
+      - name: "🔬 Perform CodeQL Analysis"
+        uses: 
github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
new file mode 100644
index 0000000..5b68590
--- /dev/null
+++ b/.github/workflows/coverage.yml
@@ -0,0 +1,60 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: "Coverage"
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+  workflow_dispatch:
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+jobs:
+  coverage:
+    name: "Coverage"
+    runs-on: ubuntu-latest
+    steps:
+      - name: "📥 Checkout repository"
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 
v6.0.2
+      - name: "📝 Read JDK version from .sdkmanrc"
+        id: sdkmanrc
+        run: echo "java=$(grep -E '^java=' .sdkmanrc | cut -d= -f2 | sed 
's/-librca$//')" >> "$GITHUB_OUTPUT"
+      - name: "☕️ Setup JDK"
+        uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # 
v5.2.0
+        with:
+          distribution: liberica # .sdkmanrc pins librca = Liberica
+          java-version: ${{ steps.sdkmanrc.outputs.java }}
+      - name: "🐘 Setup Gradle"
+        uses: 
gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
+        with:
+          cache-provider: basic # 'basic' uses the MIT-licensed, open-source 
cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle 
commercial Terms of Use)
+      - name: "🌡️ Run tests with coverage"
+        run: ./gradlew testCodeCoverageReport --continue --stacktrace
+      - name: "📤 Upload coverage artifact"
+        if: always()
+        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a 
# v7.0.1
+        with:
+          name: coverage-report
+          path: 
build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml
+          if-no-files-found: warn
+      - name: "📊 Upload coverage to Codecov"
+        continue-on-error: true
+        uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 
# v6.0.0
+        with:
+          token: ${{ secrets.CODECOV_TOKEN }}
+          files: 
build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml
+          verbose: true
diff --git a/.github/workflows/vulnerability-scan.yml 
b/.github/workflows/vulnerability-scan.yml
new file mode 100644
index 0000000..5a47483
--- /dev/null
+++ b/.github/workflows/vulnerability-scan.yml
@@ -0,0 +1,85 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: "Vulnerability Scan"
+on:
+  push:
+    branches:
+      - main
+  schedule:
+    # Run every Monday at 03:00 UTC
+    - cron: '0 3 * * 1'
+  workflow_dispatch:
+# Do not scan concurrently; OSS Index has per-account rate limits
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: false
+jobs:
+  scan:
+    name: "OSS Index Scan"
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+    steps:
+      - name: "📥 Checkout repository"
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 
v6.0.2
+      - name: "📝 Read JDK version from .sdkmanrc"
+        id: sdkmanrc
+        run: echo "java=$(grep -E '^java=' .sdkmanrc | cut -d= -f2 | sed 
's/-librca$//')" >> "$GITHUB_OUTPUT"
+      - name: "☕️ Setup JDK"
+        uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # 
v5.2.0
+        with:
+          distribution: liberica # .sdkmanrc pins librca = Liberica
+          java-version: ${{ steps.sdkmanrc.outputs.java }}
+      - name: "🐘 Setup Gradle"
+        uses: 
gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
+        with:
+          cache-provider: basic # 'basic' uses the MIT-licensed, open-source 
cache provider; the default 'enhanced' provider (v6+) is proprietary (Gradle 
commercial Terms of Use)
+      - name: "🔍 Run OSS Index Vulnerability Scan"
+        env:
+          SONATYPE_GUIDE_USERNAME: ${{ secrets.SONATYPE_GUIDE_USERNAME }}
+          SONATYPE_GUIDE_TOKEN: ${{ secrets.SONATYPE_GUIDE_TOKEN }}
+        continue-on-error: true
+        id: scan
+        run: ./gradlew ossIndexAudit --continue --info 2>&1 | tee 
/tmp/ossindex-scan.log; exit ${PIPESTATUS[0]}
+      - name: "📋 Publish Vulnerability Summary"
+        if: always()
+        run: |
+          echo "## 🔍 OSS Index Vulnerability Scan" >> $GITHUB_STEP_SUMMARY
+          if [ "${{ steps.scan.outcome }}" = "success" ]; then
+            echo "✅ No vulnerabilities found." >> $GITHUB_STEP_SUMMARY
+          else
+            echo "❌ Vulnerabilities detected." >> $GITHUB_STEP_SUMMARY
+            echo "" >> $GITHUB_STEP_SUMMARY
+            echo '```' >> $GITHUB_STEP_SUMMARY
+            awk '
+              BEGIN { in_section=0; in_vuln=0 }
+              { gsub(/\033\[[0-9;]*m/, "") }
+              /^##\[ossIndexAudit:begin\]/ { in_section=1; next }
+              /^##\[ossIndexAudit:end\]/ { in_section=0; in_vuln=0; next }
+              !in_section { next }
+              /^\[[0-9]+\/[0-9]+\] - pkg:maven\// {
+                sub(/^\[[0-9]+\/[0-9]+\] - /, "")
+                if (!seen_coord[$0]++) { print ""; print }
+                next
+              }
+              /^   Vulnerability Title:/ { in_vuln=1; block=$0 "\n"; 
cve_id=""; next }
+              in_vuln && /^   CVE:/ { match($0,/CVE-[0-9-]+/); if (RSTART) 
cve_id=substr($0,RSTART,RLENGTH); block=block $0 "\n"; next }
+              in_vuln && /^   Reference:/ { block=block $0 "\n"; if (cve_id && 
!seen_cve[cve_id]++) printf "%s",block; in_vuln=0; next }
+              in_vuln { block=block $0 "\n" }
+            ' /tmp/ossindex-scan.log >> $GITHUB_STEP_SUMMARY \
+              || echo "(no scan output captured — check the full log)" >> 
$GITHUB_STEP_SUMMARY
+            echo '```' >> $GITHUB_STEP_SUMMARY
+          fi
diff --git a/.gitignore b/.gitignore
index 91d7666..d3586d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,7 @@ gradle-bootstrap/gradlew.bat
 gradle-bootstrap/gradle
 /.claude
 *.hprof
+# reproducible-build test output (etc/bin/test-reproducible-build.sh)
+/first.txt
+/second.txt
+etc/bin/results
diff --git a/RELEASE.md b/RELEASE.md
new file mode 100644
index 0000000..ea0aded
--- /dev/null
+++ b/RELEASE.md
@@ -0,0 +1,126 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+# Apache Grails IntelliJ Plugin — Release Process
+
+This document describes how a version of the Apache Grails IntelliJ plugin is 
released.
+
+**ASF framing.** The official release voted on by the PMC is the **signed 
source
+distribution** published to `dist.apache.org`. The plugin ZIP on the JetBrains
+Marketplace is a *convenience binary* built from that same tagged source. Both 
must be
+reproducible from the tagged commit (see [Verifying a Reproducible 
Build](#verifying-a-reproducible-build)).
+
+The pipeline is encoded in 
[`.github/workflows/release.yml`](.github/workflows/release.yml)
+and is triggered when a **GitHub Release is published** for a tag (`vX.Y.Z`). 
It runs in
+four stages.
+
+## Prerequisites
+
+- The release tag (`vX.Y.Z`) points at the exact commit to be released.
+- Marketplace signing / publishing secrets are configured: `CERTIFICATE_CHAIN`,
+  `PRIVATE_KEY`, `PRIVATE_KEY_PASSWORD`, `PUBLISH_TOKEN`.
+- ASF signing + dist secrets are configured: `GRAILS_GPG_KEY`, `GPG_KEY_ID`,
+  `SVC_DIST_GRAILS_USERNAME`, `SVC_DIST_GRAILS_PASSWORD`.
+- The release environment reviewers are set in [`.asf.yaml`](.asf.yaml) 
(GitHub does not
+  allow private groups as approvers, so approvers are listed individually).
+- The JDK is pinned by [`.sdkmanrc`](.sdkmanrc); CI reads the version from it 
so local and
+  CI builds match. See [INSTALL](INSTALL) for local build setup.
+
+## 1. Build & stage the plugin ZIP (`publish` job)
+
+1. Establish `VERSION` from the tag (`${TAG#v}`).
+2. `./gradlew check buildPlugin -Pversion=${VERSION}` — full build, tests, and 
Plugin ZIP.
+3. `./gradlew signPlugin` — JetBrains Marketplace signature.
+4. Rename to `apache-grails-intellij-plugin-${VERSION}-bin.zip`.
+5. GPG-detached-sign (`.asc`) and checksum (`.sha512`).
+6. Upload the ZIP, `.asc`, and `.sha512` to the GitHub Release.
+
+## 2. Create the source distribution (`source` job)
+
+1. Check out the tag, then remove files that must not ship in a source release
+   (`.git`, `.github`, `.asf.yaml`, `gradlew`/`gradlew.bat`, `gradle/wrapper`).
+2. Zip to `apache-grails-intellij-plugin-${VERSION}-src.zip`.
+3. GPG-detached-sign (`.asc`) and checksum (`.sha512`).
+4. Upload all three to the GitHub Release.
+
+## 3. Stage to dist.apache.org & start the vote (`upload` job)
+
+1. Ensure `dist/dev/grails/INTELLIJ` exists in the ASF dist SVN.
+2. Upload the source + binary distributions (with signatures and checksums) 
under
+   `dist/dev/grails/INTELLIJ/${VERSION}/`.
+3. **MANUAL:** start a `[VOTE]` thread for the release on 
`[email protected]`.
+
+Reviewers should verify signatures, checksums, the RAT license audit, and a
+**reproducible build** before voting (next section).
+
+## 4. Publish the release (`release` job — post-vote, environment-gated)
+
+After the vote succeeds (the `release` GitHub environment requires the 
reviewers in
+`.asf.yaml`):
+
+1. **MANUAL:** confirm the vote result on `[email protected]`.
+2. **MANUAL:** promote the staged artifacts from `dist/dev` to `dist/release` 
via
+   
[`.github/scripts/releaseDistributions.sh`](.github/scripts/releaseDistributions.sh).
+3. `./gradlew publishPlugin -Pversion=${VERSION}` — publish the convenience 
binary to the
+   JetBrains Marketplace.
+4. **MANUAL:** update the [ASF release reporter](https://reporter.apache.org/) 
and send
+   the `[RESULT][VOTE]` + `[ANNOUNCE]` emails.
+
+## Verifying a Reproducible Build
+
+A vote is a good time to confirm the artifacts can be rebuilt bit-for-bit from 
the tag.
+Reproducibility is enforced by the `grails-intellij.reproducible` convention 
plugin
+(`build-logic/`), which pins archive timestamps, file order, and entry 
permissions across
+every module and the plugin ZIP.
+
+### On the same machine
+
+From a clean checkout of the tagged commit:
+
+```bash
+etc/bin/test-reproducible-build.sh
+```
+
+The script pins `SOURCE_DATE_EPOCH` to the last commit time, builds 
`buildPlugin` twice
+from a clean tree (`--rerun-tasks --no-build-cache`), and compares the SHA-256 
of the
+plugin ZIP and every module jar. It prints `((none) ✅ build is reproducible)` 
on success
+and lists any differing artifacts on failure.
+
+> ⚠️ The script runs `git clean -xdf` between builds, which deletes **all** 
untracked
+> files. Run it in a throwaway checkout or after stashing your work.
+
+### In a container (build-environment parity)
+
+To rule out host-OS differences, reproduce inside a container that resembles 
the CI Linux
+environment ([`etc/bin/Dockerfile`](etc/bin/Dockerfile), pinned to the same 
Liberica JDK
+as `.sdkmanrc`):
+
+```bash
+docker build -t grails-ij:testing -f etc/bin/Dockerfile .
+docker run -it --rm -v "$(pwd):/home/builder/project" grails-ij:testing bash
+# inside the container:
+etc/bin/test-reproducible-build.sh
+```
+
+### Common reproducibility gotchas
+
+- **Archive timestamps / order** — handled by the reproducible convention 
plugin
+  (`preserveFileTimestamps=false`, `reproducibleFileOrder=true`, fixed 
permissions).
+- **`SOURCE_DATE_EPOCH`** — most tools honor it for embedded dates; the script 
sets it
+  from the last git commit so successive builds agree.
+- **JDK version** — a different JDK can change bytecode. Always build with the 
JDK pinned
+  in `.sdkmanrc`; the Docker image installs exactly that version.
diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts
new file mode 100644
index 0000000..2ef3348
--- /dev/null
+++ b/build-logic/build.gradle.kts
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+plugins {
+    `kotlin-dsl`
+}
+
+// The convention plugins here are consumed by the root build and every 
module. The
+// vulnerability-scan plugin applies the Sonatype OSS Index plugin and 
references its
+// extension type, so that plugin must be on the build-logic compile classpath.
+dependencies {
+    // Dependabot (gradle ecosystem, /build-logic) keeps this version current.
+    implementation("org.sonatype.gradle.plugins:scan-gradle-plugin:3.1.6")
+}
diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts
new file mode 100644
index 0000000..8334e44
--- /dev/null
+++ b/build-logic/settings.gradle.kts
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+dependencyResolutionManagement {
+    repositories {
+        mavenCentral()
+        gradlePluginPortal()
+    }
+}
+
+rootProject.name = "build-logic"
diff --git a/build-logic/src/main/kotlin/OssIndexAuditThrottle.kt 
b/build-logic/src/main/kotlin/OssIndexAuditThrottle.kt
new file mode 100644
index 0000000..4f8619f
--- /dev/null
+++ b/build-logic/src/main/kotlin/OssIndexAuditThrottle.kt
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.grails.intellij.buildlogic
+
+import org.gradle.api.services.BuildService
+import org.gradle.api.services.BuildServiceParameters
+
+/**
+ * Shared build service used solely as a concurrency gate for `ossIndexAudit` 
tasks.
+ *
+ * The Sonatype OSS Index Gradle plugin backs every audit task with a single 
shared
+ * on-disk response cache guarded by a file lock. When Gradle runs audit tasks 
in parallel,
+ * they contend for that lock and fail with `OverlappingFileLockException`, 
surfaced by the
+ * plugin as the opaque "Could not audit the project: null".
+ *
+ * Registered with `maxParallelUsages = 1`, this service forces Gradle to run 
at most one
+ * audit task at a time while leaving parallel execution enabled for the rest 
of the build.
+ */
+abstract class OssIndexAuditThrottle : 
BuildService<BuildServiceParameters.None>
diff --git a/build-logic/src/main/kotlin/grails-intellij.jacoco.gradle.kts 
b/build-logic/src/main/kotlin/grails-intellij.jacoco.gradle.kts
new file mode 100644
index 0000000..9011919
--- /dev/null
+++ b/build-logic/src/main/kotlin/grails-intellij.jacoco.gradle.kts
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import org.gradle.testing.jacoco.tasks.JacocoReport
+
+// Per-project JaCoCo conventions. Ported from grails-core's 
GrailsJacocoPlugin (per-module
+// portion only). The cross-project aggregate is produced by the root build 
via Gradle's
+// built-in `jacoco-report-aggregation` plugin, which is safe under the 
configuration cache
+// (this repo has org.gradle.configuration-cache=true, unlike grails-core).
+
+plugins {
+    jacoco
+}
+
+jacoco {
+    // keep in sync with `jacocoVersion` in gradle.properties
+    toolVersion = (project.findProperty("jacocoVersion") ?: 
"0.8.14").toString()
+}
+
+tasks.withType<Test>().configureEach {
+    finalizedBy(tasks.withType<JacocoReport>())
+}
+
+tasks.withType<JacocoReport>().configureEach {
+    dependsOn(tasks.withType<Test>())
+    reports {
+        xml.required.set(true)
+        html.required.set(true)
+        csv.required.set(false)
+    }
+}
diff --git 
a/build-logic/src/main/kotlin/grails-intellij.reproducible.gradle.kts 
b/build-logic/src/main/kotlin/grails-intellij.reproducible.gradle.kts
new file mode 100644
index 0000000..2644224
--- /dev/null
+++ b/build-logic/src/main/kotlin/grails-intellij.reproducible.gradle.kts
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Reproducible-build conventions. Ported from grails-core's 
CompilePlugin.configureReproducible.
+// Applied to the root project and every module so that the plugin ZIP
+// (build/distributions/*.zip) and all composed/module jars are byte-for-byte 
stable.
+//
+// preserveFileTimestamps = false pins every archive entry to a constant 
timestamp, so
+// SOURCE_DATE_EPOCH is not required for archive reproducibility; 
reproducibleFileOrder
+// removes the only other common source of non-determinism (filesystem 
iteration order).
+// Fixed permissions guarantee the same entry metadata regardless of the host 
umask.
+
+tasks.withType<AbstractArchiveTask>().configureEach {
+    isPreserveFileTimestamps = false
+    isReproducibleFileOrder = true
+    filePermissions { unix("0644") }
+    dirPermissions { unix("0755") }
+}
diff --git 
a/build-logic/src/main/kotlin/grails-intellij.vulnerability-scan.gradle.kts 
b/build-logic/src/main/kotlin/grails-intellij.vulnerability-scan.gradle.kts
new file mode 100644
index 0000000..d4d8205
--- /dev/null
+++ b/build-logic/src/main/kotlin/grails-intellij.vulnerability-scan.gradle.kts
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import org.apache.grails.intellij.buildlogic.OssIndexAuditThrottle
+import org.sonatype.gradle.plugins.scan.ossindex.OssIndexPluginExtension
+
+// Sonatype OSS Index / Guide vulnerability scanning. Ported from grails-core's
+// VulnerabilityScanPlugin. Credentials come from the environment:
+//   SONATYPE_GUIDE_USERNAME - Guide account email / user token username
+//   SONATYPE_GUIDE_TOKEN    - Guide Personal Access Token (PAT)
+// The `ossIndexAudit` task fails fast when SONATYPE_GUIDE_TOKEN is absent.
+
+plugins {
+    id("org.sonatype.gradle.plugins.scan")
+}
+
+// The OSS Index plugin guards its shared on-disk response cache with a file 
lock, so audit
+// tasks cannot run concurrently without hitting OverlappingFileLockException. 
Gate every
+// audit task on a single-usage build service to serialize them while leaving 
parallel
+// execution enabled for the rest of the build.
+val throttle = gradle.sharedServices.registerIfAbsent("ossIndexAuditThrottle", 
OssIndexAuditThrottle::class.java) {
+    maxParallelUsages.set(1)
+}
+
+configure<OssIndexPluginExtension> {
+    username = System.getenv("SONATYPE_GUIDE_USERNAME") ?: ""
+    password = System.getenv("SONATYPE_GUIDE_TOKEN") ?: ""
+    // Exemptions are limited to vulnerabilities with NO available upstream 
fix. Everything
+    // with a published fix must be bumped instead. Each entry must name the 
CVE and the
+    // removal condition, and be revisited whenever a fix ships. Empty until a 
real,
+    // unfixable CVE is encountered.
+    excludeCoordinates = emptySet()
+}
+
+tasks.named("ossIndexAudit") {
+    group = "verification"
+    description = "Scans ${project.name} runtime dependencies for known 
vulnerabilities via Sonatype Guide"
+    usesService(throttle)
+    doFirst {
+        if (System.getenv("SONATYPE_GUIDE_TOKEN").isNullOrEmpty()) {
+            throw GradleException(
+                "SONATYPE_GUIDE_TOKEN environment variable is not set. " +
+                    "A Sonatype Guide Personal Access Token is required to run 
the vulnerability scan."
+            )
+        }
+        println("##[ossIndexAudit:begin] ${project.path}")
+    }
+    doLast {
+        println("##[ossIndexAudit:end] ${project.path}")
+    }
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index 4c8be96..b656489 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import org.gradle.testing.jacoco.tasks.JacocoReport
 import org.jetbrains.intellij.platform.gradle.TestFrameworkType
 import org.jetbrains.intellij.platform.gradle.tasks.PrepareSandboxTask
 
@@ -23,6 +24,11 @@ plugins {
     id("org.jetbrains.kotlin.jvm") version "2.3.0"
     id("org.jetbrains.intellij.platform") version "2.18.1"
     id("org.nosphere.apache.rat") version "0.8.1"
+    // build-hygiene conventions (build-logic included build)
+    id("jacoco-report-aggregation")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
 }
 
 group = "org.apache.grails.intellij"
@@ -87,6 +93,30 @@ dependencies {
     // JPS/build
     runtimeOnly(project(":grails-compiler-patch"))
     runtimeOnly(project(":jps-plugin"))
+
+    // Aggregate JaCoCo coverage from every module into the root 
testCodeCoverageReport.
+    jacocoAggregation(project(":copyright"))
+    jacocoAggregation(project(":coverage"))
+    jacocoAggregation(project(":gradle-tooling"))
+    jacocoAggregation(project(":grails-compiler-patch"))
+    jacocoAggregation(project(":grails-rt"))
+    jacocoAggregation(project(":hibernate"))
+    jacocoAggregation(project(":i18n"))
+    jacocoAggregation(project(":jps-plugin"))
+    jacocoAggregation(project(":langInjection"))
+    jacocoAggregation(project(":maven"))
+    jacocoAggregation(project(":testFramework"))
+}
+
+// jacoco-report-aggregation auto-registers testCodeCoverageReport from the 
default `test`
+// suite, producing build/reports/jacoco/testCodeCoverageReport/... ; ensure 
the XML variant
+// is emitted for Codecov upload.
+tasks.named<JacocoReport>("testCodeCoverageReport") {
+    reports {
+        xml.required.set(true)
+        html.required.set(true)
+        csv.required.set(false)
+    }
 }
 
 kotlin {
@@ -210,6 +240,11 @@ tasks.rat {
             "out/**",
             // IntelliJ Platform Gradle Plugin local cache (generated Ivy 
descriptors, git-ignored)
             ".intellijPlatform/**",
+            // build-logic included build: generated output and local caches 
(sources are headered)
+            "build-logic/build/**",
+            "build-logic/.gradle/**",
+            // reproducible-build test output (git-ignored, regenerated by 
etc/bin/test-reproducible-build.sh)
+            "etc/bin/results/**",
             // generated code (JFlex lexers; headers are injected by the 
generator config)
             "gen/**",
             // test fixtures: content *is* the test input (mock Grails 
projects/JARs);
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..44ee4d7
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,37 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+codecov:
+  require_ci_to_pass: yes
+
+comment:
+  layout: "reach, diff, flags, files"
+  behavior: default
+  require_changes: false
+  require_base: no
+  require_head: yes
+
+coverage:
+  precision: 4
+  round: nearest
+  status:
+    patch:
+      default:
+        target: auto
+        informational: true
+    project:
+      default:
+        target: auto
+        informational: true
diff --git a/copyright/build.gradle.kts b/copyright/build.gradle.kts
index 3700f32..8408920 100644
--- a/copyright/build.gradle.kts
+++ b/copyright/build.gradle.kts
@@ -18,6 +18,9 @@ fun properties(key: String) = 
project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/coverage/build.gradle.kts b/coverage/build.gradle.kts
index 572a710..d4eda8d 100644
--- a/coverage/build.gradle.kts
+++ b/coverage/build.gradle.kts
@@ -18,6 +18,9 @@ fun properties(key: String) = 
project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.kotlin.jvm") version "2.3.0"
     id("org.jetbrains.intellij.platform.module")
 }
diff --git a/etc/bin/Dockerfile b/etc/bin/Dockerfile
new file mode 100644
index 0000000..8b8cd13
--- /dev/null
+++ b/etc/bin/Dockerfile
@@ -0,0 +1,48 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# A container that resembles the GitHub Actions Linux build environment, used 
to test
+# reproducible builds independently of the host OS. Mount the project 
directory into the
+# container so builds run in the container but artifacts can be compared on 
the host.
+#
+# Build & run from the project root:
+#   docker build -t grails-ij:testing -f etc/bin/Dockerfile .
+#   docker run -it --rm -v "$(pwd):/home/builder/project" grails-ij:testing 
bash
+# Then, inside the container:
+#   cd project && etc/bin/test-reproducible-build.sh
+#
+# Keep the JDK version synced with .sdkmanrc (java=<version>-librca).
+FROM bellsoft/liberica-openjdk-debian:25.0.3
+
+USER root
+RUN apt-get update \
+    && apt-get install -y --no-install-recommends \
+        ca-certificates curl unzip coreutils git gpg sudo psmisc locales \
+    && rm -rf /var/lib/apt/lists/*
+
+RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \
+    && dpkg-reconfigure --frontend=noninteractive locales \
+    && update-locale LANG=en_US.UTF-8
+
+RUN useradd --create-home --home-dir /home/builder builder \
+    && echo 'builder ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
+USER builder
+WORKDIR /home/builder/project
+
+ENV CI=true
+ENV LANG=en_US.UTF-8
+ENV LC_ALL=en_US.UTF-8
+
+CMD ["/bin/bash"]
diff --git a/etc/bin/test-reproducible-build.sh 
b/etc/bin/test-reproducible-build.sh
new file mode 100755
index 0000000..628889a
--- /dev/null
+++ b/etc/bin/test-reproducible-build.sh
@@ -0,0 +1,84 @@
+#!/usr/bin/env bash
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+# Verifies that the Grails IntelliJ plugin builds reproducibly: it builds the 
plugin
+# ZIP (and every module jar) twice from a clean tree with SOURCE_DATE_EPOCH 
pinned, then
+# compares the SHA-256 of each produced artifact. Any artifact whose hash 
differs between
+# the two builds is printed and the script exits non-zero.
+#
+# WARNING: this runs `git clean -xdf`, which deletes ALL untracked files 
(build output,
+# .idea, IDE caches, etc.). Run it only in a throwaway checkout or after 
stashing work.
+#
+# Usage: run from anywhere; the script locates the project root relative to 
itself.
+#   etc/bin/test-reproducible-build.sh
+set -euo pipefail
+
+export SOURCE_DATE_EPOCH="$(git log -1 --pretty=%ct)"
+
+SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
+PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/../.." &> /dev/null && pwd)"
+cd "${PROJECT_ROOT}"
+
+# Portable SHA-256: prefer GNU coreutils, fall back to macOS `shasum`.
+if command -v sha256sum &> /dev/null; then
+  sha() { sha256sum "$@"; }
+else
+  sha() { shasum -a 256 "$@"; }
+fi
+
+# Hash the release artifacts (plugin ZIP) and every module jar, sorted by path 
so the
+# output is order-stable across runs.
+hash_artifacts() {
+  {
+    find build/distributions -type f -name '*.zip' 2> /dev/null || true
+    find . -type f -path '*/build/libs/*.jar' 2> /dev/null || true
+  } | sort | while IFS= read -r f; do
+    [ -f "$f" ] && sha "$f"
+  done
+}
+
+build_once() {
+  git clean -xdf
+  ./gradlew buildPlugin --rerun-tasks --no-build-cache --no-daemon
+}
+
+echo "==> First build (SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH})"
+build_once
+FIRST="$(hash_artifacts)"
+
+echo "==> Second build"
+build_once
+SECOND="$(hash_artifacts)"
+
+printf '%s\n' "$FIRST" > first.txt
+printf '%s\n' "$SECOND" > second.txt
+
+echo ""
+echo "Differing artifacts:"
+# comm on the second (hash) field: lines present in only one build indicate a 
hash mismatch.
+DIFF="$(comm -3 <(printf '%s\n' "$FIRST" | sort) <(printf '%s\n' "$SECOND" | 
sort) \
+  | awk '{print $2}' | sed 's|^[[:space:]]*||' | grep -v '^$' | sort -u || 
true)"
+
+if [ -z "$DIFF" ]; then
+  echo "  (none) ✅ build is reproducible"
+  exit 0
+else
+  printf '  %s\n' "$DIFF"
+  echo ""
+  echo "❌ build is NOT reproducible — see first.txt / second.txt for the full 
hash lists"
+  exit 1
+fi
diff --git a/gradle-tooling/build.gradle.kts b/gradle-tooling/build.gradle.kts
index 05786b0..f51f633 100644
--- a/gradle-tooling/build.gradle.kts
+++ b/gradle-tooling/build.gradle.kts
@@ -17,6 +17,9 @@ fun properties(key: String) = 
project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/gradle.properties b/gradle.properties
index 4b6cf1a..473db41 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -18,6 +18,9 @@ version=262.0.0
 
 kotlin.stdlib.default.dependency=false
 
+# Build-hygiene tool versions
+jacocoVersion=0.8.14
+
 # 
https://plugins.jetbrains.com/docs/intellij/testing-faq.html#how-to-test-a-jvm-language
 # set path to local intellij-community project
 test.idea.home.path=/Users/yann/idea-ultimate/ultimate/community
diff --git a/grails-compiler-patch/build.gradle.kts 
b/grails-compiler-patch/build.gradle.kts
index dc323c3..896fa65 100644
--- a/grails-compiler-patch/build.gradle.kts
+++ b/grails-compiler-patch/build.gradle.kts
@@ -17,6 +17,9 @@ fun properties(key: String) = 
project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/grails-rt/build.gradle.kts b/grails-rt/build.gradle.kts
index 925a933..da5ee88 100644
--- a/grails-rt/build.gradle.kts
+++ b/grails-rt/build.gradle.kts
@@ -15,6 +15,9 @@
  */
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
 }
 
 group = "org.apache.grails.intellij.rt"
diff --git a/hibernate/build.gradle.kts b/hibernate/build.gradle.kts
index 6faff4a..2e88bf9 100644
--- a/hibernate/build.gradle.kts
+++ b/hibernate/build.gradle.kts
@@ -17,6 +17,9 @@ fun properties(key: String) = 
project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/i18n/build.gradle.kts b/i18n/build.gradle.kts
index d134dac..99961d9 100644
--- a/i18n/build.gradle.kts
+++ b/i18n/build.gradle.kts
@@ -17,6 +17,9 @@ fun properties(key: String) = 
project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.kotlin.jvm") version "2.3.0"
     id("org.jetbrains.intellij.platform.module")
 }
diff --git a/jps-plugin/build.gradle.kts b/jps-plugin/build.gradle.kts
index 0ae7944..cee000e 100644
--- a/jps-plugin/build.gradle.kts
+++ b/jps-plugin/build.gradle.kts
@@ -17,6 +17,9 @@ fun properties(key: String) = 
project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/langInjection/build.gradle.kts b/langInjection/build.gradle.kts
index e74c2d1..c84fbac 100644
--- a/langInjection/build.gradle.kts
+++ b/langInjection/build.gradle.kts
@@ -17,6 +17,9 @@ fun properties(key: String) = 
project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.intellij.platform.module")
 }
 
diff --git a/maven/build.gradle.kts b/maven/build.gradle.kts
index 4b47727..2e1300a 100644
--- a/maven/build.gradle.kts
+++ b/maven/build.gradle.kts
@@ -17,6 +17,9 @@ fun properties(key: String) = 
project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.kotlin.jvm") version "2.3.0"
     id("org.jetbrains.intellij.platform.module")
 }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index caefd5a..e62f760 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -13,6 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+pluginManagement {
+    includeBuild("build-logic")
+}
+
 rootProject.name = "Grails"
 
 include("copyright")
diff --git a/testFramework/build.gradle.kts b/testFramework/build.gradle.kts
index f965f64..0822f0b 100644
--- a/testFramework/build.gradle.kts
+++ b/testFramework/build.gradle.kts
@@ -20,6 +20,9 @@ fun properties(key: String) = 
project.findProperty(key).toString()
 
 plugins {
     id("java")
+    id("grails-intellij.reproducible")
+    id("grails-intellij.jacoco")
+    id("grails-intellij.vulnerability-scan")
     id("org.jetbrains.kotlin.jvm") version "2.3.0"
     id("org.jetbrains.intellij.platform.module")
 }

Reply via email to