)]}'
{
  "log": [
    {
      "commit": "5ec9ac497293fcb8478b7db6e7fae7ef2360bbe9",
      "tree": "5b2719befc220aa85b47bd5fc296e0c73d4425fd",
      "parents": [
        "00efc65263e9ddaa9c0c20061520744179573866",
        "86c4a9baf69c6bf3ac101866f20ded7fd2100b71"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Fri Sep 11 13:10:40 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Sep 11 13:10:40 2026 -0400"
      },
      "message": "Merge pull request #415 from apache/dependencies-and-cleanup\n\nDependencies and cleanup"
    },
    {
      "commit": "86c4a9baf69c6bf3ac101866f20ded7fd2100b71",
      "tree": "5b2719befc220aa85b47bd5fc296e0c73d4425fd",
      "parents": [
        "778a1514df9609f41d770c30643e42937c22a0d8"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Fri Sep 11 13:09:57 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Fri Sep 11 13:09:57 2026 -0400"
      },
      "message": "Make the JSP plugin optional\n"
    },
    {
      "commit": "778a1514df9609f41d770c30643e42937c22a0d8",
      "tree": "f30b0940d63605189e45ee3c03690cba0fb5fd0a",
      "parents": [
        "789d94db3aa9b53ad648d4a44404a8d88d5cd5a9"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Fri Sep 11 10:01:20 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Fri Sep 11 10:01:20 2026 -0400"
      },
      "message": "Review feedback on dependencies and clean-up\n\nGradleSettingsFile: recognise include and projectDir statements instead of\ntesting the whole file for a quoted module name, so a name in a comment, an\nincludeBuild or an unrelated literal no longer suppresses setup, an include\nwithout a mapping is completed rather than taken for a finished one, and the\nlegacy include-plus-rename form is left untouched. Only the missing statement\nis appended.\n\nGrailsBackgroundService: release the queue when ProgressManager.run fails\nsynchronously, so nothing queued afterwards is stranded.\n\nGrailsApplicationManager: use ProjectUtil.guessProjectDir.\n\nDocument the JSP plugin, unbundled since 2026.2, as a run requirement.\n"
    },
    {
      "commit": "789d94db3aa9b53ad648d4a44404a8d88d5cd5a9",
      "tree": "0970b12e1cf8cc1ea1439febb994d4bd64f2c914",
      "parents": [
        "52311ac40fe3dee89b8eec85ff8e163d92d6feb7"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Tue Sep 08 16:44:16 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Tue Sep 08 16:44:16 2026 -0400"
      },
      "message": "Project clean-up from original migration plan\n"
    },
    {
      "commit": "52311ac40fe3dee89b8eec85ff8e163d92d6feb7",
      "tree": "5a7f4d910b19701b04b4269dac56c400578e9d27",
      "parents": [
        "00efc65263e9ddaa9c0c20061520744179573866"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Tue Sep 08 16:27:09 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Tue Sep 08 16:27:19 2026 -0400"
      },
      "message": "Updating dependencies \u0026 fixing deprecations\n"
    },
    {
      "commit": "00efc65263e9ddaa9c0c20061520744179573866",
      "tree": "1f315b59272d5cf512dac7deff3a9dd02436528e",
      "parents": [
        "e70ee01da9d16f8ab69935d4f87304a46ebce91d",
        "c106d5c602ac4d6e5ff28da95ed8c8b7293689ab"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Aug 31 11:12:03 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Aug 31 11:12:03 2026 -0400"
      },
      "message": "Merge pull request #35 from fernando88to/main\n\nGo To Test: reach tests of sibling artefacts kept in another package"
    },
    {
      "commit": "e70ee01da9d16f8ab69935d4f87304a46ebce91d",
      "tree": "bbdd017d93a7ab51ed0cd611ffd4b90e6173f997",
      "parents": [
        "c8efb0e86c941f94bb21ab785704bc46c6d8c284"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Aug 31 11:11:14 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Aug 31 11:11:14 2026 -0400"
      },
      "message": "Increment version"
    },
    {
      "commit": "c8efb0e86c941f94bb21ab785704bc46c6d8c284",
      "tree": "986acc5a8aabb06e886953ffb822fe1508afef72",
      "parents": [
        "c1c8dc6604a9e4b9beb920a22ca8770e97760399",
        "1e71287561ef936496be11f47c826d0577e4afe8"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Thu Aug 27 12:33:38 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Aug 27 12:33:38 2026 -0400"
      },
      "message": "Merge pull request #36 from apache/asfCompilance"
    },
    {
      "commit": "1e71287561ef936496be11f47c826d0577e4afe8",
      "tree": "986acc5a8aabb06e886953ffb822fe1508afef72",
      "parents": [
        "345ee17a7732da08d9132c09421ab5e0d08a2fb5"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Tue Aug 25 11:41:46 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Tue Aug 25 11:41:46 2026 -0400"
      },
      "message": "Fix test pollution\n"
    },
    {
      "commit": "345ee17a7732da08d9132c09421ab5e0d08a2fb5",
      "tree": "5225db77165bdc88556ae8fa9c183c979bab5832",
      "parents": [
        "8c00c3a5966a59e78f58d4e8171182b82be71b2b"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Aug 23 13:03:07 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Aug 23 13:03:07 2026 -0400"
      },
      "message": "Keep AGENTS.md in the source distribution\n\nReverts stripping AGENTS.md at the tag. That came out of the issue checklist, which paired\nit with MIGRATION-PLAN.md and IMPROVEMENT-PLAN.md on the grounds that it \"ships with no\nheader and no RAT exclude\" -- but it did have a header, just the non-canonical SPDX form,\nand it now carries the canonical ASF header like every other file. A documentation file with\na valid header that RAT audits and passes has no reason not to ship.\n\nThe two planning documents still go: the RAT config excludes them specifically as \"removed\nbefore the first release\", so leaving them in a distribution would ship files nothing audits.\n\nverify-distributions.sh no longer forbids AGENTS.md in the source distribution.\n"
    },
    {
      "commit": "8c00c3a5966a59e78f58d4e8171182b82be71b2b",
      "tree": "d8ce8c45ec4bdfd42d278f6bd226109eeef20810",
      "parents": [
        "c87177b69af02d42b6712b86c5d2cb6652a72f6f"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Aug 23 12:29:52 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Aug 23 13:01:28 2026 -0400"
      },
      "message": "Updating documentation\n"
    },
    {
      "commit": "c87177b69af02d42b6712b86c5d2cb6652a72f6f",
      "tree": "5a1e01c5e486ccc8cc1af1909b30df03252d46fe",
      "parents": [
        "b6cd58981665f832d312ecb48f4c1f77dd30af11"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Aug 23 12:28:48 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Aug 23 12:28:48 2026 -0400"
      },
      "message": "Put LICENSE and NOTICE in the plugin ZIP and tidy the source distribution\n\nThe plugin ZIP is the unit that gets staged to dist.apache.org, voted on and distributed,\nand ASF policy requires LICENSE and NOTICE in every unit of distribution \"whether the unit\nof distribution is a .jar, .msi, .tar.gz, .zip, .exe installer, or any other file format\".\nBoth files existed only inside the composed plugin jar\u0027s META-INF, which does not satisfy\nthat for the ZIP itself. PrepareSandboxTask now puts them at the root of the plugin\ndirectory, alongside where standardDsls/ is already staged, so they land at\napache-grails-intellij-plugin/ in the ZIP. Verified against a local buildPlugin.\n\nThey are deliberately not added to the ten sub-jars under lib/. Those are not separately\ndistributed -- the ZIP is -- and the composed jar that is the plugin already carries them.\n\nverify-distributions.sh gains the matching assertion, and two more on the source\ndistribution:\n\n- No *.jar at all, rather than just gradle-wrapper.jar. The wrapper is now the only binary\n  the repository tracks, so once the source job strips it the source distribution has no\n  compiled binaries left; a jar showing up means either that strip or the Maven-resolved\n  test fixtures regressed.\n- No MIGRATION-PLAN.md, IMPROVEMENT-PLAN.md or AGENTS.md. These were RAT-excluded as\n  \"removed before the first release\" but nothing actually removed them, so they would have\n  shipped; the source job now deletes them.\n\nRELEASE.md documents both, and records that running the test suite from an extracted source\ndistribution needs network on its first run for the fixture jars -- while the release\nverification path (verify.sh: checksums, signatures, rat, reproducible buildPlugin) never\nruns the tests and so is unaffected.\n"
    },
    {
      "commit": "b6cd58981665f832d312ecb48f4c1f77dd30af11",
      "tree": "94f30d3d04eca6e1e538d72d1d4f176d7c5c8dd1",
      "parents": [
        "32334328a8db614794ef610c057f293c3e614259"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Aug 23 12:23:47 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Aug 23 12:23:47 2026 -0400"
      },
      "message": "Relocate the JetBrains attribution in NOTICE and guard the header form\n\nNOTICE now says the code was \"donated to the Apache Software Foundation under a software\ngrant\" rather than only naming where it came from, and drops \"Licensed under the Apache\nLicense, Version 2.0.\" -- redundant in a file that should carry only required attributions,\nand the kind of line reviewers comment on. The wording still wants checking against the\nexecuted grant text, which is noted in MIGRATION-PLAN.md as the one open item.\n\nThe header guard is apply-asf-headers.py --check, wired into the RAT workflow. It exists\nbecause RAT cannot see the difference that matters here: the JetBrains-attributed\nboilerplate and the ASF header are both the ALv2 appendix to RAT, so it was green before the\nsweep and stays green after. Anchoring the guard on the copyright notice rather than on a\nfile list also means it needs no second copy of RAT\u0027s exclusion list to drift out of sync --\nRAT keeps proving every non-excluded file has an ALv2 header, and the guard proves it is the\ncanonical one.\n\nVerified both ways: green on the tree as it stands, and failing (exit 1, naming the file) on\na scratch file carrying the old header.\n"
    },
    {
      "commit": "32334328a8db614794ef610c057f293c3e614259",
      "tree": "123876a80af380f34d1a977f0f1e60cc4f6b243a",
      "parents": [
        "be58a09d504009891e3466d46dc746e6b62a4875"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Aug 23 12:21:49 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Aug 23 12:21:49 2026 -0400"
      },
      "message": "Replace the JetBrains-attributed headers with the canonical ASF header\n\nThe software grant is filed, which unblocks Phase 9 of MIGRATION-PLAN.md. For work\nsubmitted to the ASF by the copyright owner, the source header policy requires the owner to\n\"remove such notices, or move them to the NOTICE file\", and that \"each source file should\ninclude the following license header -- note that there should be no copyright notice in\nthe header\"; the release policy restates it as a must. NOTICE already carries the relocated\nJetBrains attribution.\n\n948 files, mechanical, no functional change. The header text comes from the HEADER file, so\nthat stays the single definition of what the header says, rendered in whichever comment\nsyntax each file already used: block comments for Java/Kotlin/Gradle/Groovy/JFlex, \u0027#\u0027 for\nproperties and META-INF/services provider files, XML comments for xml/tld. All three older\nJetBrains variants are covered (2000-2007, 2000-2024, 2000-2026).\n\netc/bin/apply-asf-headers.py does the work and is kept as the record of the transformation\nfor the IP clearance reviewers. It anchors on the copyright line and bounds the block by\ncomment syntax rather than matching the boilerplate, which is what makes the variants and\nthe odd shapes fall out for free: the generated JFlex lexers put two \"// Generated by\"\nlines above their header, log4j.properties runs its own comments straight on from the\nlicence text, and the services files put content on the very next line. It exits non-zero\non any JetBrains-attributed header it cannot identify, so nothing is silently left behind,\nand --check reports without touching anything.\n\nIt replaces etc/bin/expand-jetbrains-headers.py, the pre-grant tool that expanded\nabbreviated notices while *keeping* JetBrains attribution -- now both obsolete and, since it\ncarries the old header text in string literals, a false positive for any grep guard.\n\nWhat still names JetBrains, correctly: NOTICE, and the provenance prose in README.md,\nAGENTS.md and MIGRATION-PLAN.md.\n\nRAT was green before and stays green -- it accepts the ALv2 appendix boilerplate either way\n(996 standards, all Apache licensed, 0 unknown). check and buildPlugin pass; 1017 tests, 0\nfailures.\n"
    },
    {
      "commit": "be58a09d504009891e3466d46dc746e6b62a4875",
      "tree": "678b0b35fcd595e0e5de668bc76a5d82dcb170cb",
      "parents": [
        "c1c8dc6604a9e4b9beb920a22ca8770e97760399"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Aug 23 12:14:18 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Aug 23 12:14:18 2026 -0400"
      },
      "message": "Resolve the test fixture jars from Maven instead of committing them\n\nplugin/testdata/ held 35 third-party jars (~5.4 MB of real bytecode). They cannot be\nstubbed -- the tests resolve PSI against them, so the plugin has to see the same class\nhierarchies a real Grails project would -- but ASF policy does not want them here:\n\n- hibernate-core-3.3.1.GA.jar is LGPL 2.1, Category X, which may not sit in an ASF\n  repository at all, in source or binary form.\n- junit-4.8.1.jar is CPL 1.0, Category B: must not ship in a source release.\n- The other 33 are ALv2, Category A: should not ship in a source release either.\n\nAll 35 are resolved from Maven at test time into git-ignored fixture directories, so the\njars are neither committed nor distributed while the tests keep running against the real\nartifacts. Filenames are pinned to what GrailsTestCase and GrailsTestUtil look for -- the\nmock installs mirror a real Grails layout, so a few deliberately differ from the Maven\nartifact name and are renamed on the way in -- which is why no test code changes.\n\nMaven Central serves 24 of them. The other eleven are the Grails 2.0.0/1.4.0 milestones\nand spring-test 3.1.0.M2, published only to the long-defunct SpringSource repositories;\nthey survive in the Apache Grails Artifactory, added as a second repository after\nmavenCentral and pinned to those exact versions so it can never quietly become the source\nof anything else. It serves artifacts only, without POM parsing: spring-test 3.1.0.M2\nnames an org.springframework:spring-parent:3.1.0.M2 that was never published anywhere, and\nreading that metadata fails the resolution outright.\n\nEach coordinate gets its own configuration. Two of them are the classes and sources jars\nof the same module, which a shared configuration would collapse to a single artifact, and\na one-artifact-at-a-time spec can carry the fixture name as a literal rename rather than a\nlookup table consulted at execution time. Sync deletes what it did not write, so each task\nalso asserts the full expected set landed: a coordinate that resolved to an unexpected\nartifact name would otherwise leave the fixture a jar short and the tests failing far from\nthe cause.\n\nThe Gradle wrapper jar is now the only binary the repository tracks, and the release\nworkflow already strips it from the source distribution.\n\nSuite unchanged: 1017 tests, 0 failures. RAT green.\n"
    },
    {
      "commit": "c106d5c602ac4d6e5ff28da95ed8c8b7293689ab",
      "tree": "89bfec40f39a253155176de2d90c28ef62d37cac",
      "parents": [
        "a5d4d521759bf411d706652d1b93aba8e6f6980b",
        "c1c8dc6604a9e4b9beb920a22ca8770e97760399"
      ],
      "author": {
        "name": "fernando henrique",
        "email": "fernando88to@gmail.com",
        "time": "Mon Aug 17 21:55:59 2026 -0300"
      },
      "committer": {
        "name": "fernando henrique",
        "email": "fernando88to@gmail.com",
        "time": "Mon Aug 17 21:55:59 2026 -0300"
      },
      "message": "Merge remote-tracking branch \u0027origin/main\u0027\n"
    },
    {
      "commit": "a5d4d521759bf411d706652d1b93aba8e6f6980b",
      "tree": "89bfec40f39a253155176de2d90c28ef62d37cac",
      "parents": [
        "e8f3d2837274938ab5a36da0384713591ad8d4ca"
      ],
      "author": {
        "name": "fernando henrique",
        "email": "fernando88to@gmail.com",
        "time": "Mon Aug 17 21:55:50 2026 -0300"
      },
      "committer": {
        "name": "fernando henrique",
        "email": "fernando88to@gmail.com",
        "time": "Mon Aug 17 21:55:50 2026 -0300"
      },
      "message": " Reach tests of siblings kept in another package.\n"
    },
    {
      "commit": "c1c8dc6604a9e4b9beb920a22ca8770e97760399",
      "tree": "56de5deb6ec0eecc3c5c7ebda691958acbb37650",
      "parents": [
        "4dc98b8bee8d9de767dafecfee724d1aa3d0d6ec",
        "e8f3d2837274938ab5a36da0384713591ad8d4ca"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Aug 17 14:54:59 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Aug 17 14:54:59 2026 -0400"
      },
      "message": "Merge pull request #32 from fernando88to/main\n\nStop injecting Grails 1.x controller members over the traits."
    },
    {
      "commit": "e8f3d2837274938ab5a36da0384713591ad8d4ca",
      "tree": "56de5deb6ec0eecc3c5c7ebda691958acbb37650",
      "parents": [
        "4dc98b8bee8d9de767dafecfee724d1aa3d0d6ec"
      ],
      "author": {
        "name": "fernando henrique",
        "email": "fernando88to@gmail.com",
        "time": "Sat Aug 15 12:08:53 2026 -0300"
      },
      "committer": {
        "name": "fernando henrique",
        "email": "fernando88to@gmail.com",
        "time": "Sat Aug 15 12:08:53 2026 -0300"
      },
      "message": "Stop injecting Grails 1.x controller members over the traits.\n\n Skip the injection when grails.artefact.Controller is on the classpath — the\n  trait set declares every member CLASS_SOURCE provided\n"
    },
    {
      "commit": "4dc98b8bee8d9de767dafecfee724d1aa3d0d6ec",
      "tree": "8bb06025abbb3edc79a07f7232e18db65bfd64b7",
      "parents": [
        "a912b6543c5f5c53b1e3a58f90eb87a13ef03f73",
        "ebd3f85221bdf466507fcee875a583cab45763be"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Thu Aug 13 09:33:41 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Aug 13 09:33:41 2026 -0400"
      },
      "message": "Merge pull request #17 from fernando88to/main\n\nResolve criteria count and shorthand calls on GORM 4+"
    },
    {
      "commit": "ebd3f85221bdf466507fcee875a583cab45763be",
      "tree": "18a70779c6367f227102d900cec038c57f9965d7",
      "parents": [
        "39697b70e45319130ea76f1eebdf2ac07475ca5a"
      ],
      "author": {
        "name": "fernando henrique",
        "email": "fernando88to@gmail.com",
        "time": "Thu Aug 13 08:45:59 2026 -0300"
      },
      "committer": {
        "name": "fernando henrique",
        "email": "fernando88to@gmail.com",
        "time": "Thu Aug 13 08:45:59 2026 -0300"
      },
      "message": " Cover projections, the non-Hibernate implementor and the paginated terminal\n"
    },
    {
      "commit": "39697b70e45319130ea76f1eebdf2ac07475ca5a",
      "tree": "fb09f661aedf8dae60d717d8d693ba32a2dd058c",
      "parents": [
        "4446c91f99418f007c67e5b6f8e68afbe9ec4ef6"
      ],
      "author": {
        "name": "fernando henrique",
        "email": "fernando88to@gmail.com",
        "time": "Thu Aug 13 08:20:09 2026 -0300"
      },
      "committer": {
        "name": "fernando henrique",
        "email": "fernando88to@gmail.com",
        "time": "Thu Aug 13 08:20:09 2026 -0300"
      },
      "message": "Clarify contributed member set and move BUILDABLE_CRITERIA\n"
    },
    {
      "commit": "4446c91f99418f007c67e5b6f8e68afbe9ec4ef6",
      "tree": "6f9cadbcf24ce09c8d2397a3c9cec3ee2183dbcf",
      "parents": [
        "96c86751c2b86102c666767580495667755d57f7"
      ],
      "author": {
        "name": "Fernando Henrique",
        "email": "fernando88to@gmail.com",
        "time": "Thu Aug 13 08:05:29 2026 -0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Aug 13 08:05:29 2026 -0300"
      },
      "message": "Update plugin/src/main/java/org/apache/grails/intellij/plugin/references/domain/criteria/BuildableCriteriaImplicitMemberContributor.java\n\nCo-authored-by: James Daugherty \u003cjdaugherty@jdresources.net\u003e"
    },
    {
      "commit": "a912b6543c5f5c53b1e3a58f90eb87a13ef03f73",
      "tree": "2778dc39a5d430fe317782d62a150df63d2a00e0",
      "parents": [
        "0821bbe26b85c653231a6b08f694c1c566033cf0"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Thu Aug 06 13:07:28 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Thu Aug 06 13:07:28 2026 -0400"
      },
      "message": "Fix vargs calls causing isHide to error\n"
    },
    {
      "commit": "0821bbe26b85c653231a6b08f694c1c566033cf0",
      "tree": "7f48bde2deb3b6b3eb5d8f34400f3a04fcd2d4a3",
      "parents": [
        "45e7363baf3fe6b193afc3a7c3ecd5cb68e72cd9"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Wed Aug 05 17:21:30 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Wed Aug 05 17:21:30 2026 -0400"
      },
      "message": "Add snapshot language to the CI artifact\n"
    },
    {
      "commit": "45e7363baf3fe6b193afc3a7c3ecd5cb68e72cd9",
      "tree": "0a656a194aa03acba20514436ee2e1c47db4dbda",
      "parents": [
        "a9d13dc98914c3462376d1eaf9f5bc465852e1a8"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Wed Aug 05 10:18:02 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Wed Aug 05 10:18:02 2026 -0400"
      },
      "message": "IdeModalityType.PROJECT -\u003e IdeModalityType.IDE fix\n"
    },
    {
      "commit": "a9d13dc98914c3462376d1eaf9f5bc465852e1a8",
      "tree": "7fc0aef718368ccd32bc6a0f7f80ee9d0bbfd929",
      "parents": [
        "d5df460e19a9cb2712de49857e0354f813946b9e"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Thu Jul 30 11:54:15 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Thu Jul 30 11:54:15 2026 -0400"
      },
      "message": "Fix expensive method calls from being invoked\n"
    },
    {
      "commit": "96c86751c2b86102c666767580495667755d57f7",
      "tree": "d19234afbe9e688969a3e588b11a82da5c9d6ecf",
      "parents": [
        "cb8bf33f3f75e3cbe44a6d1ff90e2fb35feea415",
        "d5df460e19a9cb2712de49857e0354f813946b9e"
      ],
      "author": {
        "name": "Fernando Henrique",
        "email": "fernando88to@gmail.com",
        "time": "Tue Jul 28 08:28:19 2026 -0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Jul 28 08:28:19 2026 -0300"
      },
      "message": "Merge branch \u0027apache:main\u0027 into main\n"
    },
    {
      "commit": "cb8bf33f3f75e3cbe44a6d1ff90e2fb35feea415",
      "tree": "8ab0d6a5309edcffaa0827f4c2a13a62738aad22",
      "parents": [
        "0ac91197f880a5a73e454d65a7563f49be008f77"
      ],
      "author": {
        "name": "fernando henrique",
        "email": "fernando88to@gmail.com",
        "time": "Tue Jul 28 08:18:56 2026 -0300"
      },
      "committer": {
        "name": "fernando henrique",
        "email": "fernando88to@gmail.com",
        "time": "Tue Jul 28 08:18:56 2026 -0300"
      },
      "message": "Fix criteria count() not resolving on GORM 4+\n"
    },
    {
      "commit": "d5df460e19a9cb2712de49857e0354f813946b9e",
      "tree": "ebb9138ebb3ea29be55935318e394066ecd970f6",
      "parents": [
        "0ac91197f880a5a73e454d65a7563f49be008f77"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 27 14:16:12 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 27 14:16:12 2026 -0400"
      },
      "message": "Make build reproducible\n"
    },
    {
      "commit": "0ac91197f880a5a73e454d65a7563f49be008f77",
      "tree": "1a8b847419aa78f79a70949377bfc9c9d298320d",
      "parents": [
        "4c431a8bc3b81f108d9b216be15e2d7f96e4b7ce"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 27 09:41:44 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 27 09:41:44 2026 -0400"
      },
      "message": "Updates from release testing\n"
    },
    {
      "commit": "4c431a8bc3b81f108d9b216be15e2d7f96e4b7ce",
      "tree": "b64c9d940db10956f36bc9f78bf60507c15db740",
      "parents": [
        "0d8b1d4b70ccabd7fde7600d20376b4f97dd1b34"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 27 08:58:20 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 27 08:58:20 2026 -0400"
      },
      "message": "Fix svn folder name\n"
    },
    {
      "commit": "0d8b1d4b70ccabd7fde7600d20376b4f97dd1b34",
      "tree": "9b3de087ba54417efb1ba0b705d1c2d5ea5e38ae",
      "parents": [
        "72f171d17a44ea39e56449196ebb2470e3c54b26"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 27 08:18:00 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 27 08:18:00 2026 -0400"
      },
      "message": "Remove push triggre due to api limits\n"
    },
    {
      "commit": "72f171d17a44ea39e56449196ebb2470e3c54b26",
      "tree": "5eb878b454c6ff7bdfd7942a3568cd5629d7f703",
      "parents": [
        "11e9243d59f5052ef050a111407a4b04ec2924d1"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 27 08:12:52 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 27 08:12:52 2026 -0400"
      },
      "message": "Change startImport per upstream changes\n"
    },
    {
      "commit": "11e9243d59f5052ef050a111407a4b04ec2924d1",
      "tree": "c8bcb6e5b08754b6835554b1f4b58784dcf76644",
      "parents": [
        "ad864ff233bf5d29487e536f57d0fddae882bb68"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 27 08:11:39 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 27 08:11:39 2026 -0400"
      },
      "message": "Change namespace to grails so we can publish\n"
    },
    {
      "commit": "ad864ff233bf5d29487e536f57d0fddae882bb68",
      "tree": "b65ed78691108615e8d5743e3922222ac0e3dee9",
      "parents": [
        "54dbb1cac0a7c49fffcf8e6db83628d53db6632c"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 18:07:44 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 18:07:44 2026 -0400"
      },
      "message": "Updating release workflow\n"
    },
    {
      "commit": "54dbb1cac0a7c49fffcf8e6db83628d53db6632c",
      "tree": "dc2a36fc7b87eac298aafc39a6b3add17721ad20",
      "parents": [
        "25f4c7dfc419c7a7d6661b384fe30d57461c94ba"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 17:37:53 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 17:37:53 2026 -0400"
      },
      "message": "Update dependencies\n"
    },
    {
      "commit": "25f4c7dfc419c7a7d6661b384fe30d57461c94ba",
      "tree": "e8977fbb8c63277ec994b048f0cc69c3513afa92",
      "parents": [
        "1f1786d595c48ad97b71de43b41e75bffe3673c7"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 17:26:46 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 17:26:46 2026 -0400"
      },
      "message": "Restore JavaVersionMatcher\u0027s package, broken by the repackage\n\nCI failed compiling libs/testFramework:\n\n  JavaVersionRestriction.kt:22 Unresolved reference \u0027JavaVersionMatcher\u0027\n\nFallout from the repackage. That commit had to rewrite package declarations from each\nfile\u0027s path, because for the two split packages a prefix rewrite could not tell the\nowning project apart. Deriving the package from the path assumes package \u003d\u003d directory,\nwhich Kotlin does not require: JavaVersionMatcher.kt sits in tooling/ but declares\ntooling.util, so it got \"corrected\" to tooling and the import in JavaVersionRestriction.kt\nstopped resolving.\n\nI saw this file in the fixer\u0027s output at the time and reverted the wrong one -- I matched\nthe reported package against `git ls-files .../tooling/util` and landed on\nVersionMatcher.java, which had never been touched. Audited properly this time by\ncomparing every libs/testFramework package declaration against the commit before the\nrepackage: JavaVersionMatcher.kt is the only one that differs, and it is now byte-identical\nto its pre-repackage state.\n\nIt stays where it is with its original package rather than moving into util/ to make\npackage match path: these are vendored copies of platform test-framework sources and\nkeeping them identical to upstream is what makes future re-syncs possible.\n\nLocal builds hid this because Kotlin\u0027s incremental cache still had the old output;\nverified now by compiling from a pristine `git archive` checkout, and by running\ntestCodeCoverageReport, the task the coverage job runs.\n\nVerified: 999 tests / 0 failures; rat clean.\n"
    },
    {
      "commit": "1f1786d595c48ad97b71de43b41e75bffe3673c7",
      "tree": "dc302735858b4d44c66b59bc845d72a23dd57a77",
      "parents": [
        "731dfdd41b717a5381befc40bac272927a34847a"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 17:20:15 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 17:20:15 2026 -0400"
      },
      "message": "Support build-test-data 3.x+ build/findOrBuild on any GORM entity\n\nThe existing support is Grails 2 era: it is gated on the @grails.buildtestdata.mixin.Build\nannotation and only contributes inside a method body, so on a modern app -- where the\nmethods come from the entity\u0027s static metaclass and no annotation is involved -- nothing\nresolved at all. findOrBuild was absent entirely.\n\nSignatures are taken from the plugin\u0027s own source rather than its GDSL, checked against\n/Users/jdaugherty/sandbox/external/build-test-data at 6.0.0-SNAPSHOT.\ngrails.buildtestdata.utils.MetaHelper#addBuildMetaMethods registers exactly:\nbuild(), build(Map), build(Map, Map), findOrBuild(), findOrBuild(Map). The shipped GDSL\nlists only build/findOrBuild with per-field named parameters, so it describes the\ncontribution but not the arities; the source is authoritative.\n\nbuild-test-data is a test dependency, so contribution is gated on resolving\ngrails.buildtestdata.TestData from the *place\u0027s* resolve scope. That is one check doing\ntwo jobs: production code cannot see a test-scoped library, so it gets no members, and a\nproject that does not use the plugin gets none either. TestData is the right marker\nbecause MetaHelper delegates every one of these methods to it, so it is present wherever\nthe runtime methods are. It also predates BuildDataTest in the plugin\u0027s history, so it\nholds for older versions too.\n\nAdded alongside the @Build path rather than replacing it: mixin/Build.groovy still exists\nin 6.0.0-SNAPSHOT, and the legacy plugin line has to keep working. All three existing\n@Build tests stay green.\n\nNot a new pluginModule: those exist only to make dependencies on other *IntelliJ* plugins\noptional. build-test-data is a Grails plugin -- a library on the user\u0027s classpath -- so a\ncontent module would add the descriptor/jar-name contract for no isolation benefit. This\nlives in pluginSupport/ with the other Grails-plugin integrations.\n\nVerified: 999 tests / 0 failures; rat clean. The scope gate has its own test, confirmed\nmeaningful by removing the gate -- only that test fails, the other two still pass.\n"
    },
    {
      "commit": "731dfdd41b717a5381befc40bac272927a34847a",
      "tree": "4f536eb47ed840620e531fba803dcd9122e59150",
      "parents": [
        "7fda17f56ffde80af469729aad0531d83d41069c"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 17:05:56 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 17:05:56 2026 -0400"
      },
      "message": "Repackage every project to group + artifact, and rename the plugin to Apache Grails\n\n889 files move so each project\u0027s base package equals its Gradle group plus artifact\nshort name, and so no package is shared between two projects:\n\n  plugin                          org.apache.grails.intellij.plugin\n  pluginModules/\u003cname\u003e            org.apache.grails.intellij.module.\u003cname\u003e\n  libs/gradle-tooling             org.apache.grails.intellij.lib.gradle.tooling\n  libs/grails-rt                  org.apache.grails.intellij.lib.grails.rt\n  libs/testFramework              org.apache.grails.intellij.lib.testFramework\n  compilers/grails-compiler-patch org.apache.grails.intellij.compiler.grails.compiler.patch\n  compilers/jps-plugin            org.apache.grails.intellij.compiler.jps.plugin\n\nHyphens in artifact names are illegal in package segments, so they split on the\nhyphen. That leaves grails-compiler-patch stuttering as compiler.grails.compiler.patch;\nkept literal for consistency with the rule rather than special-cased -- say so and I\nwill shorten it.\n\nTwo packages were owned by two projects each, which is exactly what this was meant to\nend: org.jetbrains.plugins.groovy.grails (libs/testFramework\u0027s 5 classes plus 23 plugin\ntests) and com.intellij.groovy.grails.copyright (the copyright module plus one plugin\ntest). Targets are therefore computed per file from (project, old package); a prefix\nrewrite cannot tell the owners apart. Verified afterwards: no package is shared.\n\nConsequences of splitting those packages, which the compiler found:\n\n  - 16 plugin tests referenced GrailsTestCase and friends with no import, because they\n    used to share testFramework\u0027s package. Imports added.\n  - GspCopyrightUpdaterTest used UpdateGspCopyrightsProvider the same way. That class is\n    registered by FQN in the copyright module descriptor, so it is already part of that\n    module\u0027s surface and is now public rather than moving the test back into the module\u0027s\n    package, which would have re-created a split package.\n  - GrailsScriptProvider sat in the platform\u0027s org.jetbrains.plugins.groovy.dsl and so\n    resolved GdslScriptProvider without an import; it needs one now.\n\nlibs/testFramework keeps its vendored com.intellij.* / org.jetbrains.plugins.gradle.* /\norg.jetbrains.plugins.groovy.* copies -- those only work because their FQN matches the\nplatform\u0027s -- so it is the one project without a single base package.\n\nMoved in lockstep with the classes: 227 descriptor FQNs, plugin.xml\u0027s package\u003d prefix,\n8 .form bindings, both META-INF/services entries, grails-rt\u0027s Premain-Class manifest\nattribute, and the two GrailsIdeaTestListener strings that cross into the user\u0027s build.\nThe .flex lexer inputs carry wildcard imports of our own packages and moved too.\n\nPlugin display name is now \"Apache Grails\" (the Marketplace id stays org.intellij.grails\nper MIGRATION-PLAN decision 2).\n\nVerified: 996 tests / 0 failures; rat clean; buildPlugin clean; all 227 descriptor\nreferences resolve and none still name an old package. Note the suite reports 189\nclasses / 996 tests where I previously recorded 188 / 991 -- GspCopyrightUpdaterTest\nappears in this run and its declaration is unchanged at HEAD, so I have not explained\nthe delta and am not claiming the rename caused it.\n"
    },
    {
      "commit": "7fda17f56ffde80af469729aad0531d83d41069c",
      "tree": "2221deb4f819bcba41e6201023e9b90bd7e78e38",
      "parents": [
        "696da9fe00d2e834f081f71d04944f0305b7e0ed"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 16:46:35 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 16:46:35 2026 -0400"
      },
      "message": "Commit the build-logic helper classes that .gitignore was swallowing\n\nThe rat job failed on CI with \"unable to resolve class\norg.apache.grails.intellij.build.LegacyBytecodeExtension\" while every local\nbuild passed.\n\nCause: build-logic\u0027s helper classes sit in package\norg.apache.grails.intellij.build, which is a directory literally named `build`,\nand .gitignore\u0027s second line was `**/build`. So\nbuild-logic/src/main/groovy/org/apache/grails/intellij/build/ was ignored and\nLegacyBytecodeExtension.groovy and OssIndexAuditThrottle.groovy were never\ncommitted -- `git add -A` had nothing to add. Local builds compiled against the\nuntracked working-tree copies; a fresh checkout has neither, so compiling the\nprecompiled script plugins fails. vulnerability-scan was broken the same way,\non OssIndexAuditThrottle.\n\nNo amount of cleaning the working tree reproduces this, which is why it went\nunnoticed: the failure only exists in a checkout. Reproduced by building a\n`git archive HEAD` tree, which fails exactly as CI does, and confirmed fixed by\nbuilding the same tree with these files present.\n\nThe ignore patterns are now depth-anchored (/build/, /*/build/, /*/*/build/),\ncovering the root, \u003cproject\u003e/build and \u003ctier\u003e/\u003cproject\u003e/build -- the same shape\nthe RAT excludes already use -- so a package segment named `build` at any depth\nis unaffected. Verified the real Gradle build directories are still ignored.\n\nAGENTS.md records the trap and the one-liner to check for it, since the natural\nguard cannot live in CI: on CI the files are simply absent, so there is nothing\nfor `git status --ignored` to report.\n"
    },
    {
      "commit": "696da9fe00d2e834f081f71d04944f0305b7e0ed",
      "tree": "fe14dd81d5c54936606c3237095d4571f1ab653c",
      "parents": [
        "2f37ce342aed9427373d8e1d5685242272731cc9",
        "623a75747db01eb3ab08838305ab82e82a59d3c7"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 16:32:21 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 16:32:21 2026 -0400"
      },
      "message": "Merge main into gradle-restructure\n\nBrings in the GORM 4+ HQL injection and criteria-resolution fixes from\nmain (PR #6 plus the criteria/property-navigation commit), relocated onto\nthe restructured layout:\n\n  hibernate/resources/intellij.groovy.grails.hibernate.xml\n    -\u003e pluginModules/hibernate/src/main/resources/org.apache.grails.intellij.module.hibernate.xml\n  hibernate/src/.../Grails3HibernateFacetActivity.java\n    -\u003e pluginModules/hibernate/src/main/java/...\n  langInjection/resources/META-INF/languageInjections.xml\n    -\u003e pluginModules/langInjection/src/main/resources/META-INF/...\n  resources/META-INF/plugin.xml\n    -\u003e plugin/src/main/resources/META-INF/plugin.xml\n  src/... -\u003e plugin/src/main/java/...\n  test/... -\u003e plugin/src/test/java/...\n\nThe restructuring is kept in full; the merge delta against the\npre-merge branch tip is exactly main\u0027s 8-file, +431/-14 change.\n"
    },
    {
      "commit": "2f37ce342aed9427373d8e1d5685242272731cc9",
      "tree": "a7a9f22bbdc04ca583e064a8b084f8b952c8c15f",
      "parents": [
        "e0395ce7f7ce79a173b70dd44adacce06294aa26"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 16:13:23 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 16:13:23 2026 -0400"
      },
      "message": "Resolve a spec back to its artefact across modules and via the unit-test trait\n\nThe editor toolbar was inert whenever a Grails 3+ spec was open, while the same\ntoolbar worked from the artefact side. Both directions go through\nGrailsActionUtil.getArtefactData, but for a test file that delegates to\nGrailsTestUtils.getTestedClass, which had no way to answer:\n\n  - Nothing in the plugin read the Grails 3+ unit-test traits. The test-linking\n    code is all Grails 2 era (@TestFor, ControllerUnitTestMixin,\n    ControllerUnitTestCase), so `implements ControllerUnitTest\u003cBookController\u003e`\n    -- the authoritative link, and the one that needs no naming convention --\n    was ignored entirely.\n  - That left only a name-suffix fallback, scoped to\n    findAppDirectory(module) of the spec\u0027s own module. A spec lives in a test\n    source set, so under Gradle\u0027s module-per-source-set layout that module\u0027s\n    content roots are under src/test and have no grails-app child: the lookup\n    returned null before searching anything. In a multi-project build the\n    artefact can also live in an upstream project altogether.\n\nSo getTestedClass now prefers the trait\u0027s type argument, resolved with\ngetSuperClassSubstitutor so a shared base spec that implements the trait also\nworks, and the name fallback searches the related-module set instead of one\ngrails-app. This mirrors what the artefact -\u003e test direction already did, and is\nthe same class of bug as dea2ce5 (Go To View across modules).\n\nWidening the scope cannot produce false matches: GrailsArtifact.getType still\nconfirms each candidate sits in an artefact source root.\n\nThe trait test uses a non-conventional spec name (BookCtrlSpec) so the name\nfallback cannot satisfy it -- verified by disabling the trait path, which fails\nthat test alone and leaves the name-convention test passing.\n\nVerified: 991 tests / 0 failures (188 classes, two new); rat clean.\n"
    },
    {
      "commit": "e0395ce7f7ce79a173b70dd44adacce06294aa26",
      "tree": "9dd1ca20e3435f3ba7bdd8bf771986124901a908",
      "parents": [
        "c97f99a330ec5171e3282ab2f622c050f71b5c12"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 15:59:06 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 15:59:06 2026 -0400"
      },
      "message": "Update the plans to reflect what has shipped\n\nMIGRATION-PLAN.md, 423 -\u003e 222 lines. Phases 1-7 are all done, so their\nstep-by-step prose is replaced by a table recording what each one landed and\nwhere that detail now lives. Also:\n\n  - \"Current state (surveyed 2026-07-06)\" is retitled \"State at import\" and\n    trimmed. It described the pre-restructure build as current, which is why it\n    read as stale; as a dated snapshot of what was inherited it is still worth\n    keeping.\n  - \"Open decisions\" becomes \"Decisions of record\". Decisions 3, 4 and 5 were\n    settled by what actually got built (no Develocity, workflow artifact rather\n    than channel publishing, platform-branch versioning) and now say so.\n    Numbering is unchanged because later phases cite these by number.\n  - Phase 8 is split into the six items verified locally and the four that need\n    a real runner, a tag, or the PMC. The snapshot-publish gating item is\n    dropped as moot: decision 4 means CI publishes nothing, so there is no job\n    to gate. Worth flagging that no workflow has ever run on a GitHub runner —\n    that is the largest untested area left.\n  - The suggested commit sequence is removed; it is history now.\n  - Phase 3\u0027s testdata-JAR question and Phase 7\u0027s SBOM stretch goal are pulled\n    out as the two genuinely-open items rather than left buried in done phases.\n\nIMPROVEMENT-PLAN.md: the survey said 525 Java / 110 Kotlin, 11 flat submodules,\nplatform 2025.3. Updated to the real numbers and layout, with a note on why 19\nKotlin files remain and that Kotlin therefore cannot be dropped from the build\nyet. The assetPipeline cleanup item is closed. Phase 1\u0027s dumb-mode item records\nthe two lessons from the IndexNotReadyException fix, since they apply to the\ncontributors that have not been swept yet.\n\nMIGRATION-PLAN Phase 9 is untouched — still blocked on the software grant.\n"
    },
    {
      "commit": "c97f99a330ec5171e3282ab2f622c050f71b5c12",
      "tree": "118813382333312938ed82c4747c4323294b35e1",
      "parents": [
        "c315ab149a4cd1f99185229e5af119e86e402679"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 15:57:58 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 15:57:58 2026 -0400"
      },
      "message": "Stop GSP taglib lookup throwing IndexNotReadyException during indexing\n\nOpening a GSP before Gradle import finished threw IndexNotReadyException. The\nplatform\u0027s XML machinery calls XmlTag.getNamespace() from the daemon (in the\nreport, via the XSLT plugin\u0027s UnusedElementInspection), which reaches\nGspXmlRootTagImpl.getNamespaceByPrefix -\u003e GspTagLibUtil -\u003e GrailsArtifact and\nqueried the stub index while dumb.\n\nThe interesting part is where it actually breaks. Discovering taglib classes\nfrom grails-app/taglib/ is a plain directory scan and needs no index, so the\nobvious guard around the index-based lookups is not enough: resolving a found\nclass\u0027s `namespace` field goes through getPrefixByTagLibClass -\u003e\nfindCodeFieldByName, which resolves supertypes and hits JavaFullClassNameIndex.\nMy first attempt guarded only the lookups and the test still reproduced the\ncrash on that path. So computeCustomTaglibClasses now returns just the default\n\"g\" prefix while dumb, which keeps core GSP editing working, and the two\nlookups (GrailsArtifact.collectFromPluginXmls and doSearchTaglibsInClassPath)\nare guarded too since the toolbar Go-To actions reach them directly — that is\nwhat was being clicked when this fired.\n\nEvery cache in the chain (GspXmlRootTagImpl.getNamespaceData,\nGspTagLibUtil.getTagLibClasses, searchTaglibsInClassPath,\nGrailsArtifact.getCache) additionally depends on the DumbService modification\ntracker, so a degraded result cannot outlive indexing.\n\nThe new test reproduces the reported stack (it failed with exactly that\nexception before the fix) and asserts recovery afterwards. It asserts recovery\nend-to-end rather than isolating the tracker: the fixture bumps\nPsiModificationTracker often enough that it still passes with the tracker\nremoved, which I verified. The tracker is kept regardless, because indexing\nfinishing does not inherently modify PSI, so in a real IDE it is the only\ndependency guaranteed to change.\n\nCloses the \"dumb-mode safety of the heavier contributors\" item in\nIMPROVEMENT-PLAN Phase 1 for this path.\n\nVerified: 989 tests / 0 failures (187 classes, two new); rat clean.\n"
    },
    {
      "commit": "c315ab149a4cd1f99185229e5af119e86e402679",
      "tree": "11cf60008889176972dc3222b33c5eabc3fc8383",
      "parents": [
        "8af0a4805872f46f58c6e3e31a12401692c77080"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 15:40:58 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 15:40:58 2026 -0400"
      },
      "message": "Update the build-hygiene tooling and docs for the restructure\n\nThe build-hygiene work predates the composed-build restructure, so several of\nthe paths it documents and inspects had gone stale. Audited every file those\ncommits introduced.\n\nDocs, all verified against the tree afterwards:\n\n  - AGENTS.md\u0027s \"Project Structure\" described the pre-restructure layout: the\n    root project as the plugin, non-standard source dirs, and\n    compatibilityResources/, which no longer exists anywhere else in the repo.\n    Replaced with the tier directories, their Gradle project names, and the\n    standard source layout, including why testdata/ sits next to the tests.\n  - The plugin ZIP path in README.md and INSTALL, and the test-results path in\n    AGENTS.md, are under plugin/ now. RAT and coverage aggregation genuinely do\n    stay on the root project, so those paths are left alone.\n  - RAT excludes, the reproducible convention plugin, and the bundledPlugin\n    list moved out of build.gradle.kts into build-logic and plugin/build.gradle.\n\netc/bin/verify-distributions.sh picked the composed plugin jar with\n`grep \u0027\u003cdir\u003e/lib/*.jar\u0027 | head -n 1`. That resolves correctly today only\nbecause \"apache-grails-intellij-plugin-262.0.0.jar\" happens to sort before the\nrenamed \"org.apache.grails.intellij.*\" lib jars beside it -- an accident of\nnaming for a check that asserts LICENSE/NOTICE for ASF policy. It now matches\n\u003cplugin-dir\u003e/lib/\u003cplugin-dir\u003e-\u003cversion\u003e.jar and fails unless exactly one\nmatches; tested against the built ZIP and against a ZIP with no composed jar.\nWritten without mapfile, since these scripts declare #!/usr/bin/env bash and a\nrelease manager on stock macOS gets bash 3.2.\n\nChecked and found already correct: the coverage.yml and rat.yml report paths\n(both root-owned, confirmed by running the tasks), gradle.yml and release.yml\n(already repathed to plugin/build), ossIndexAudit (resolves across all renamed\nprojects), dependabot, codecov, .asf.yaml, .gitattributes and gradle.properties.\n\nMIGRATION-PLAN.md is left untouched: its \"Current state (surveyed 2026-07-06)\"\nsection is a dated snapshot of the import, and rewriting it would destroy the\nrecord of where this started.\n"
    },
    {
      "commit": "8af0a4805872f46f58c6e3e31a12401692c77080",
      "tree": "230e87d1ac51784d93a6b802cabd7686eea5cf2c",
      "parents": [
        "58f5e0ba0788c163d3577aa13f789bdc7f3747a9"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 15:22:49 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 15:22:49 2026 -0400"
      },
      "message": "Convert the five Kotlin test classes to Java\n\nThese lean on Kotlin raw strings for the Groovy and GSP fixtures, where the\nexact bytes matter: the fixtures embed \u003ccaret\u003e, \u003cselection\u003e and inline\n\u003cerror descr\u003d\"...\"\u003e markers that the test framework parses by offset.\n\nA Kotlin raw string keeps the newline immediately after the opening quotes,\nwhile a Java text block strips it, so every block gains a leading blank line to\ncompensate, and content plus closing delimiter sit flush-left so incidental-\nindentation stripping is a no-op. DomainFieldIntentionsTest holds 38 of these,\nso it was transformed by script and then checked by extracting every literal\nfrom both files and comparing: all 38 are byte-identical. The shorter GSP\nfixtures use explicit escapes instead of text blocks, being single-line.\n\nTwo platform test-framework methods are Kotlin with default arguments, which\nJava cannot omit. Their values come from the $default bridges:\ncreateStdProjectFolders takes \"\" and importProject takes false. WebRoot is in\ncom.intellij.javaee.web, not the .facet subpackage Kotlin\u0027s import implied.\n\nKotlin default parameters in the two i18n intention tests become overloads.\n\nVerified: 186 classes / 987 tests / 0 failures / 0 errors, unchanged; each\nconverted class ran its full count (23, 2, 8, 14, 14); rat clean.\n"
    },
    {
      "commit": "58f5e0ba0788c163d3577aa13f789bdc7f3747a9",
      "tree": "98b9d7b3f5631297966a79f56c09fd07442502d8",
      "parents": [
        "f4b26662f0c12344855230117f2f7e0e124dffe6"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 15:12:09 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 15:12:09 2026 -0400"
      },
      "message": "Convert the last of the main-source Kotlin to Java\n\nThirteen files: gorm, feature/linkable, the two lang AST contributors, the\nGradle-aware module builder, and both maven-module files. Everything under\nplugin/src/main and pluginModules is now Java apart from the three files that\ncannot be rewritten.\n\nFour places needed more than transcription:\n\n  - namedArguments.kt used interface delegation (`by delegate`), so\n    LinkDescriptor now forwards all five NamedArgumentDescriptor members\n    explicitly and overrides only createReference, as the delegation did.\n  - transform.kt built methods through MemberBuilder.method(name) { ... },\n    whose only signature takes a kotlin.jvm.functions.Function1. Decompiling\n    it showed the body is just `GrLightMethodBuilder(context.manager, name)`\n    followed by the block, with no hidden defaults, so the Java constructs the\n    builder directly and needs no Kotlin stdlib.\n  - GrailsGradleAwareModuleBuilder.getModuleRoot throws the checked\n    ExecutionException, which linkModule cannot declare because\n    GrailsModuleBuilder passes it as a java.util.function.Consumer. It wraps\n    instead; nothing on that path catches ExecutionException, so only the type\n    reaching the log changes. Kotlin\u0027s invariantSeparatorsPathString is\n    reproduced locally rather than approximated with\n    FileUtil.toSystemIndependentName.\n  - MavenWorkspaceConfigurator.getMavenProjectsWithModules returns a\n    kotlin.sequences.Sequence, which is not Iterable, so the loop drives its\n    iterator. It resolves from the platform classpath, so the maven module\n    compiles with no Kotlin plugin at all -- verified by removing it.\n\nGormVersion\u0027s two lookup functions become static factories on the enum rather\nthan a separate holder, since the enum and the functions shared one Kotlin file\nand there were only two call sites.\n\nVerified: 186 classes / 987 tests / 0 failures / 0 errors, matching baseline;\nrat clean; all 220 descriptor-referenced classes resolve; the built ZIP carries\nall six content-module jars and every compileServer.plugin entry resolves in\nlib/.\n"
    },
    {
      "commit": "f4b26662f0c12344855230117f2f7e0e124dffe6",
      "tree": "cc2afbb89cbf1300fc56b5e1e88fa6109739cc23",
      "parents": [
        "fa7001cf065367020ce7428b2a2e03c3743079ff"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 14:57:08 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 14:57:08 2026 -0400"
      },
      "message": "Convert the gsp parsing, plugin discovery and test-runner setup to Java\n\nTwenty more files, in four groups.\n\nlang/gsp: the four chameleon element types and the highlighter provider are\nmechanical. gspImportUtil.kt already carried @file:JvmName(\"GspImportUtil\"),\nso its one Java caller is unaffected by the file becoming a real class.\n\nplugins/: descriptors.kt splits into the interface plus its two\nimplementations, and impl.kt becomes GrailsPlugins. GrailsPluginDescriptor\ncomputes the plugin name with an explicit Character.toLowerCase rather than\nStringUtil.decapitalize -- decapitalize leaves an all-caps prefix alone, so\nURLMappingsGrailsPlugin would have stopped matching what Kotlin\u0027s\nreplaceFirstChar produced.\n\ntests/runner: byClass.kt, byDirectory.kt and oldUtils.kt used file names as\nnamespaces, which has no Java equivalent; they collapse into\nGrailsTestConfigurationSetup, the unit they always were. versions.kt held\nthree constants, two of which (1.1, 3.0) already existed on Version as\nGRAILS_1_1 and GRAILS_3_0; the third is added there as GRAILS_1_3 and the\nseparate holder goes away.\n\nassetPipeline: references.kt splits by class, with the private asset-folder\ncomputation becoming static helpers on AssetsFileReferenceSet, its only user.\n\nTraitInjectorService.kt stays Kotlin (coroutines) but had called\ndoGetEnhancesTraits as a same-package top-level function, so it now names the\nholder class.\n"
    },
    {
      "commit": "fa7001cf065367020ce7428b2a2e03c3743079ff",
      "tree": "d9bf581751224964fe6d74d551ce7b599aa85ad0",
      "parents": [
        "e74cae53e94501bb5615d14b2cd53f9d50a1b9cf"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 14:43:14 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 14:43:14 2026 -0400"
      },
      "message": "Convert the editor toolbar and action-context helpers to Java\n\nFinishes the editor/ and editor/toolbar/ packages, and converts the two\nKotlin files they leaned on so the new Java does not have to reach through\nsynthetic *Kt facades:\n\n  - GrailsActionUtil.kt becomes GrailsActionUtil (static) plus ArtefactData\n    in its own file. Fourteen Java files already called this through\n    GrailsActionUtilKt; they now call the class directly, and the three\n    DataKey references drop the getGRAILS_X() accessor form for plain fields.\n  - GrailsTestUtils.kt held a single helper that only GrailsTestUtils.java\n    called. It moves into that class as collectGrails3TestsForArtifact and\n    the file goes away, so the package no longer has a Java class and a\n    Kotlin facade sharing one name.\n\nThe Kotlin files with several public classes each split by class:\nGrailsToolbarActions.kt into the five GoTo*Action files, and\nGrailsGenerateActions.kt into GenerateActionBase plus its four subclasses.\n\nTwo details worth recording:\n\n  - showEditorToolBar used propComponentProperty, which has no Java\n    equivalent. GrailsEditorToolbar.isShowEditorToolbar/setShowEditorToolbar\n    reproduce what the delegate compiles to, verified against its bytecode:\n    getBoolean(name, default) / setValue(name, value, default) on the\n    application-level PropertiesComponent, guarded by\n    LoadingState.COMPONENTS_LOADED. The property key is unchanged, so\n    existing user settings carry over.\n  - The default came from toBooleanStrict(), which throws on anything that\n    is not exactly \"true\"/\"false\". Boolean.parseBoolean treats a malformed\n    -Dgrails.show.editor.toolbar as false instead of failing class init.\n\nVerified: 186 classes / 987 tests / 0 failures / 0 errors, matching the\npre-conversion baseline; rat clean; all 220 descriptor-referenced classes\nresolve against the compiled output.\n"
    },
    {
      "commit": "e74cae53e94501bb5615d14b2cd53f9d50a1b9cf",
      "tree": "2595f0de7d6fed1225512b837f739fbc01b278b0",
      "parents": [
        "abc719b15a2c8d05639b10056452a60cf66909e6"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 14:26:59 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 14:26:59 2026 -0400"
      },
      "message": "Convert the runner package and coverage module to Java\n\nConverts the seven remaining Kotlin files under runner/ plus the coverage\nmodule\u0027s only source file. The runner hierarchy alternated Kotlin -\u003e Java -\u003e\nKotlin (BaseGrailsCommandLineState -\u003e GrailsCommandLineState -\u003e\nGrailsTestApp/GrailsRunApp), so it had to move as a unit.\n\nsetup.kt\u0027s top-level declarations become static members of GrailsRunnerSetup;\nthe three Java call sites that referenced the synthetic SetupKt class are\nrepathed. Declarations that were Kotlin `internal` but cross package\nboundaries are public with @ApiStatus.Internal, matching what the earlier\nGrails2Application conversion did.\n\nBehaviour preserved deliberately in three places:\n\n  - GrailsConsoleState keeps getAutoCloseEnabled/setAutoCloseEnabled and a\n    no-arg constructor. Those names are the persisted XML form, and\n    XmlSerializer needs the default constructor Kotlin was synthesising.\n  - Grails3TestCommandLineState\u0027s proxy command stays lazy behind a\n    double-checked getCommand(). Kotlin\u0027s `by lazy` deferred\n    createTestInitScript(), which touches the filesystem; building it in the\n    constructor would move that I/O onto the caller\u0027s thread.\n  - canPassVmArgs was a Kotlin extension on GrailsApplication?, so its one\n    caller in the coverage module changes to a static call. That module now\n    has no Kotlin sources, so it drops the kotlin convention plugin.\n\nProcessListener.onTextAvailable declares a raw Key, so the override is raw\ntoo; Kotlin\u0027s Key\u003c*\u003e had hidden that.\n"
    },
    {
      "commit": "abc719b15a2c8d05639b10056452a60cf66909e6",
      "tree": "8269e9543f47dddf3638972ef766d4e8ab2cc3b8",
      "parents": [
        "83356c517c644379c998352d3bd28f69f3d01dc2"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 14:09:33 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 14:09:33 2026 -0400"
      },
      "message": "Convert the gson package from Kotlin to Java\n\n11 Kotlin files become 17 Java files, leaving no Kotlin under gson/.\n987 tests / 0 failures, RAT clean.\n\nThe package turned out to be self-contained -- nothing outside gson/ references it,\nall eleven extension classes are instantiated from plugin.xml, and the top-level\ndeclarations were only used in-package -- so unlike the structure batch it needed no\ncall-site changes anywhere else.\n\nThe file count grows because Kotlin allowed several public classes per file. The\nseven multi-class files split one class per file, and the PSI patterns that were\ntop-level vals in GsonControllerReferences.kt and GsonRenderReferences.kt collect\ninto a package-private GsonPatterns holder rather than becoming two *Kt classes.\n\nDropped one piece of dead code: COMPLETION_PLACE in GsonControllerReferences.kt was\na private top-level val with exactly one occurrence, its own declaration. Private\ntop-level means nothing outside the file could reach it, so this is not a judgement\ncall. Everything else is preserved, including the commented-out \"bean\" named\nargument, which documents a deliberate omission.\n\nThree translation details worth recording:\n\n* GroovyFile.getTopStatements() returns GrTopStatement, not GrStatement. Kotlin\u0027s\n  inferred loop variable hid the distinction; Java needs the exact type, and it\n  lives in ...lang.psi.api.toplevel, not ...lang.psi.\n* Kotlin\u0027s replaceAfterLast(delimiter, replacement) has no Java equivalent, so the\n  template-path rewriting in GsonTemplateReference is written out with lastIndexOf.\n  Its fallback matters: when the delimiter is absent the Kotlin returns the original\n  string, which is why the rename path checks startsWith(\"/\") before splitting.\n* TraitInjectorService\u0027s two-argument getInjectedTraits lacked @JvmStatic, so Java\n  would have had to reach it through .Companion. That file stays Kotlin because it\n  is built on coroutines, so the annotation was added there instead -- a one-line\n  change that keeps a clean surface for the converted callers.\n\nGsonImportContributor\u0027s `by lazy` over a fixed list became a plain static constant;\nthe laziness bought nothing.\n\n51 files left in the conversion target set.\n"
    },
    {
      "commit": "83356c517c644379c998352d3bd28f69f3d01dc2",
      "tree": "65103c355d8aef8f663a0bb9ad2a624b31d0580b",
      "parents": [
        "b2f4c442e0bba8d21067fe61d1f90338870916f7"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 13:55:54 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 13:55:54 2026 -0400"
      },
      "message": "Convert the structure package from Kotlin to Java\n\n14 Kotlin files become 15 Java files, leaving no Kotlin under structure/.\n987 tests / 0 failures, RAT clean.\n\nThis package had to be converted as one unit rather than file by file, which is a\nconstraint the earlier batches did not hit: Kotlin\u0027s `override val project` cannot\noverride a Java getter. Converting GrailsApplication alone produced\n\"\u0027project\u0027 overrides nothing\" from every Kotlin subclass, because Kotlin\nsynthesises property syntax for *reading* a Java getter but requires\n`override fun getProject()` to implement one. So a class hierarchy crossing the\nlanguage boundary is not partially convertible -- interface and implementors move\ntogether.\n\nTwo smaller translation traps, both caught by the compiler:\n\n* Grails2Application was Kotlin `internal`, which is public in bytecode, so the\n  runner package could reach it across packages. Package-private in Java could not;\n  it is public with @ApiStatus.Internal, the same fix the projectView batch needed\n  for its plugin.xml-instantiated classes.\n* GrailsSettings.properties is a public FIELD, not a getter. Kotlin\u0027s\n  `settings.properties[key]` reads fields and getters identically, so the literal\n  translation to .getProperties() does not compile.\n\nNaming: structure/util.kt held a single @JvmField COMPARATOR, so Java already\nreferenced it as UtilKt.COMPARATOR; it becomes GrailsApplications.COMPARATOR, still\na static field, with the one import updated in GrailsApplicationCombobox.\nimpl/util.kt\u0027s nullable-receiver extension becomes GrailsProperties.getPropertyValue.\n\n`by lazy` / `lazyUnsafe` fields become volatile lazily-computed fields, as in the\nearlier batches. Each is derived from immutable external-system data or the VFS, so\nan idempotent recompute under a race is harmless and only publication needs care.\n\n62 files left in the conversion target set.\n"
    },
    {
      "commit": "b2f4c442e0bba8d21067fe61d1f90338870916f7",
      "tree": "70a8559d657fe06e9d3356ade668d88dc0915b2b",
      "parents": [
        "b40eecea31e6dd9494d41ec8b888940f32df60f1"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 13:14:34 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 13:14:34 2026 -0400"
      },
      "message": "Rename the six content modules into the org.apache.grails namespace\n\nFinal step of the restructure. intellij.groovy.grails.\u003cx\u003e becomes\norg.apache.grails.intellij.module.\u003cx\u003e across all three places the name is\nduplicated, which the platform requires to agree exactly:\n\n  plugin.xml \u003ccontent\u003e\u003cmodule name\u003d\"...\"/\u003e\n  the descriptor file name at each module jar\u0027s root\n  composedJar.archiveBaseName\n\nOnly the third is written by hand -- the convention plugin derives it from\nprojectDir.name, so the prefix changed in one place rather than six.\n\nVerified by parsing the built artifact rather than the sources, since this is the\none change that is invisible to compilation and to the test suite:\n\n  declared in \u003ccontent\u003e          6\n  jars in lib/modules/           6\n  declared-but-missing           none\n  shipped-but-undeclared         none\n  descriptor \u003cname\u003e.xml at root  all 6\n\nverifyPlugin passes with no INVALID_PLUGIN, MISSING_DEPENDENCIES or\nCOMPATIBILITY_PROBLEMS -- only the pre-existing deprecated/internal API usages that\nthe failureLevel deliberately does not gate on. 987 tests / 0 failures, RAT clean.\n\nTwo things that needed care. The rename had to be scoped to `name\u003d\"intellij.groovy.grails.`\nrather than the bare prefix: the Java packages are com.intellij.groovy.grails.* and\nthe extension namespace appears as `intellij.grails.commandExecutor` (from\ndefaultExtensionNs\u003d\"org\"), neither of which is a module name. And the platform\nmodule references in \u003cdependencies\u003e -- intellij.spring, intellij.regexp,\nintellij.javaee.platform and friends -- are JetBrains\u0027 own and must not be touched.\n\n\u003ccontent namespace\u003d\"jetbrains\"\u003e is left as-is. The modules no longer sit in a\nJetBrains namespace by name, so that attribute now reads oddly, but its semantics\nare not documented well enough to change safely and module resolution demonstrably\nworks without touching it. Worth revisiting if runIde ever shows a resolution\nproblem.\n"
    },
    {
      "commit": "b40eecea31e6dd9494d41ec8b888940f32df60f1",
      "tree": "f62a93d87ad4980d7166a4fe1b590a2607043ab8",
      "parents": [
        "88f836a96ce79c10cf447eae41eeb2d870ca39d9"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 13:01:14 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 13:01:14 2026 -0400"
      },
      "message": "Centralize repository declarations in a settings plugin\n\nAdds org.apache.grails.intellij.build.repositories, a precompiled *settings*\nscript plugin (the .settings.gradle suffix makes it target Settings rather than\nProject) applied from settings.gradle. It is the single source of truth for where\ndependencies come from, and it sets FAIL_ON_PROJECT_REPOS so the centralization is\nenforced rather than merely conventional.\n\nVerified enforcement is real, not just declared: adding repositories { } to a\nmodule fails with \"Build was configured to prefer settings repositories over\nproject repositories but repository \u0027MavenRepo\u0027 was added by build file\npluginModules/hibernate/build.gradle\".\n\nThe IntelliJ Platform repositories come from org.jetbrains.intellij.platform.settings,\nwhich registers the intellijPlatform repositories extension onto\ndependencyResolutionManagement -- so even those no longer need a per-project block.\n\nThis removes duplicate repositories { } blocks from two convention plugins that had\nto be kept in sync, and closes the failure mode that surfaced during the plugin\nmove: the root project silently lost its repositories along with the java plugin,\nand RAT -- which resolves its own tool jar at execution time -- failed the\nconfiguration cache with \"Cannot resolve external dependency\norg.apache.rat:apache-rat because no repositories are defined\". Repositories are\nnow independent of which convention plugins a project happens to apply.\n\nbuild-logic/settings.gradle keeps its own declarations: it is a separate build and\ncannot consume a plugin it has not yet compiled.\n\nPackaging is byte-identical, 987 tests / 0 failures, RAT clean, configuration cache\nstill reused.\n"
    },
    {
      "commit": "88f836a96ce79c10cf447eae41eeb2d870ca39d9",
      "tree": "f9e9974ee2b6c65922d748f014593eacfe75a5a8",
      "parents": [
        "9250ef6ebf3e53605b94695a7ace91f7c3797829"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 12:48:20 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 12:48:20 2026 -0400"
      },
      "message": "Move the plugin into plugin/, leaving the root a pure aggregator\n\nStep 6, the last relocation. src/ -\u003e plugin/src/main/java, gen/ -\u003e\nplugin/src/main/gen, resources/ -\u003e plugin/src/main/resources, test/ -\u003e\nplugin/src/test/java, testdata/ -\u003e plugin/testdata, and the build file with them.\nThe root project now compiles nothing: 42 lines owning only the licence audit and\nthe coverage aggregation, both of which must run from the repository root.\n\ntestdata/ moved in the same commit as the tests, which it has to: GrailsTestUtil\nresolves it from new File(\"\"), the Test task\u0027s working directory, which Gradle\ndefaults to the owning project\u0027s directory. 987 tests / 0 failures confirms it.\n\nThe transitional legacy-layout convention plugin is deleted -- every project is on\nstandard layout now, and src/main/gen is the only remaining override.\n\nThree real problems surfaced, all of which built green:\n\n* The ZIP diff caught IPGP moving gradle-tooling, jps-plugin and\n  grails-compiler-patch from lib/ into lib/modules/. IPGP infers content modules\n  from api/implementation/runtimeOnly project deps that apply .module, and the\n  inference is configuration-order dependent -- it only bites once the plugin\n  project is configured after them, which is exactly what moving it off the root\n  did. The consequence would have been silent: compileServer.plugin resolves its\n  classpath from lib/ by file name, and a jar in lib/modules/ without a\n  \u003ccontent\u003e\u003cmodule\u003e entry is never loaded at all. Fixed by modelling these three\n  correctly -- they are plain jars bundled into lib/, not IntelliJ modules, so\n  they no longer apply .module and pin their jar name instead of composedJar.\n\n* standardDsls packaging was broken earlier in this restructure, by me, when the\n  Groovy conversion dropped the resources exclude: the .gdsl ended up inside the\n  main jar and absent from the ZIP, so the platform -- which loads GDSL from\n  \u003cplugin\u003e/lib/standardDsls/ -- would have silently lost Grails GDSL completion.\n  Fixed properly rather than restored: standardDsls now lives outside the resource\n  root at plugin/standardDsls, so no exclude is needed and the two cannot drift\n  apart again.\n\n* The root lost its repositories along with the java plugin, so RAT could not\n  resolve its own tool jar (org.apache.rat:apache-rat) and failed the\n  configuration cache. The rat convention plugin now applies base.\n\nVerified after the move: artifact names identical to the pre-move baseline apart\nfrom standardDsls being added back, lib/modules/ holds exactly the six content\nmodules, compileServer.plugin classpath resolves, :rat is still in the check graph,\nand testCodeCoverageReport still writes to the root path coverage.yml uploads.\n\nCI and script paths updated for the ZIP\u0027s new location under plugin/build; the rat\nand coverage report paths stay at the root, which is why aggregation was kept there.\n"
    },
    {
      "commit": "9250ef6ebf3e53605b94695a7ace91f7c3797829",
      "tree": "6f7d6431eab6bec10a6cc7c0528258a48efa47ea",
      "parents": [
        "897b2e068a7fd7bb722370ac04beae25b2da6d26"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 12:25:26 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 12:25:26 2026 -0400"
      },
      "message": "Promote compilers/ to a top-level tier alongside libs/\n\nThe compiler plugins were nested at libs/compiler/* while their namespace was\nalready a sibling of lib rather than a child of it. That asymmetry had no upside:\nthe directory layout said one thing and the coordinates said another.\n\nThey are now a top-level compilers/ tier, so the four tiers line up:\n\n  build-logic/      org.apache.grails.intellij.build\n  pluginModules/    org.apache.grails.intellij.module\n  libs/             org.apache.grails.intellij.lib\n  compilers/        org.apache.grails.intellij.compiler\n\nDirectory plural, namespace singular, matching libs/ -\u003e ...intellij.lib: the\ndirectory holds several projects, the namespace names one tier.\n\nWhere a jar lands in the distribution is a separate question and unchanged --\ncompiler plugins still ship to lib/ alongside the libraries, and only content\nmodules go to lib/modules/. Packaging is a detail of the artifact; the tier\nrecords what the project is.\n\nFlattening also removes the fourth RAT depth glob that libs/compiler/\u003cproject\u003e/build\nhad required, since Ant patterns do not cross \u0027/\u0027. Back to three levels.\n\nArtifact names are unchanged, verified against the pre-move baseline: the\nconvention plugin derives them from projectDir.name, which is still jps-plugin and\ngrails-compiler-patch. compileServer.plugin classpath still resolves, bytecode\nlevels still 55 and 52, 987 tests / 0 failures, RAT clean, no stale references to\nthe old paths anywhere.\n"
    },
    {
      "commit": "897b2e068a7fd7bb722370ac04beae25b2da6d26",
      "tree": "f9ddbb68a4763b293ff2e9c8e517566aadae95fa",
      "parents": [
        "33967babe3a45134725e838c2821d7b6b60d6a45"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 12:12:06 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 12:12:06 2026 -0400"
      },
      "message": "Move libs and compiler tiers, and drop the unused raster icons\n\nStep 5 of the restructure. gradle-tooling, grails-rt and testFramework move to\nlibs/*; jps-plugin and grails-compiler-patch move to libs/compiler/*, with\nsrc/ -\u003e src/main/java and resources/ -\u003e src/main/resources throughout. Project\nnames carry their subpath (:libs-grails-rt, :libs-compiler-jps-plugin) with\ndirectories remapped via projectDir.\n\nThe invariants all held across the move, checked rather than assumed:\n\n  packaged artifact names   byte-identical to the pre-move clean build\n  compileServer classpath   both entries still resolve to real jars in lib/\n  bytecode levels           52/52/52 and 55, unchanged\n  grails-rt agent manifest  Premain-Class still present\n  tests                     987 / 0 failures\n  RAT                       clean\n\nRAT needed a fourth depth glob: libs/compiler/\u003cproject\u003e/build sits one level\ndeeper than anything before it, and Ant patterns do not cross \u0027/\u0027. Without\n\u0027*/*/*/build/**\u0027 the audit would have started scanning compiler build output.\n\nAlso removes compatibilityResources/ -- 16 raster icons that shipped but were\nunreachable. Evidence: the generated GroovyMvcIcons loads only .svg paths, an\nexhaustive sweep of src/gen/resources/pluginModules/libs finds exactly nine\nicons/ references and all nine are .svg, XML refers to icons through the\ngenerated accessor rather than by path, and there are no IconLoader call sites at\nall, so nothing can resolve an icon by base name. @2x variants are a raster-only\nconcept -- SVG scales intrinsically -- and 12 of the 16 files were @2x or _dark\nvariants, so they only ever made sense in the pre-SVG scheme. sinceBuild is 262,\nyears past the platform\u0027s SVG migration, so no supported target needs fallbacks.\nThey can be regenerated from the SVGs if that ever changes, and git retains them.\n\nVerified after removal that all nine icon paths GroovyMvcIcons loads still\nresolve inside the packaged jar: PNG count 16 -\u003e 0, SVG count unchanged at 10.\n\nThat was the last non-standard resources root, so legacy-layout is now down to\nsrc/, gen/, resources/ and test/ for the plugin project alone; it can be deleted\nonce that project moves.\n"
    },
    {
      "commit": "33967babe3a45134725e838c2821d7b6b60d6a45",
      "tree": "792ec1fe5713800d67a4d79df3a425fb9dc40c87",
      "parents": [
        "02f421815a4c93b6969cdc0ccb2263b8f3ff0361"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 11:54:28 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 11:54:28 2026 -0400"
      },
      "message": "Move the six content modules into pluginModules/ with standard source layout\n\nStep 4 of the restructure, and the first file relocation. copyright, coverage,\nhibernate, i18n, langInjection and maven move to pluginModules/\u003cname\u003e, with\nsrc/ -\u003e src/main/java and resources/ -\u003e src/main/resources.\n\nProject names carry their subpath (:pluginModules-hibernate) with directories\nremapped via projectDir, so every project path stays unique and readable and\nIPGP\u0027s path-derived naming defaults never see a nested path.\n\nThe checkpoint for this step was that the packaged artifact names come out\nUNCHANGED, proving the convention plugins derive them from projectDir.name rather\nthan from project.name or project.path. Diffing the ZIP listing against a clean\npre-move build: identical, and all six content-module descriptors are still at\ntheir jar roots. Full gate green: 987 tests / 0 failures, RAT clean.\n\nBecause the layout is now standard, the six modules drop the transitional\nlegacy-layout convention plugin; their build files are down to a plugins block and\ntheir own dependencies. Kotlin sources in coverage and maven sit under\nsrc/main/java, which the Kotlin plugin compiles, and where they will land as .java\nwhen the Kotlin conversion resumes.\n\nRecorded as pure renames -- git reports rename {copyright \u003d\u003e pluginModules/copyright}\nat 100% for sources -- so git log --follow and blame survive. Review with\ngit -c diff.renameLimit\u003d4000 diff --summary -M; the default rename limit is below\nthe file count this restructure will ultimately move.\n\nRAT\u0027s exclude list already carried the \u0027*/*/build/**\u0027 glob needed for the extra\ndirectory level, added when the convention plugin was written.\n"
    },
    {
      "commit": "02f421815a4c93b6969cdc0ccb2263b8f3ff0361",
      "tree": "ecc078c5d66b3cb94a19b81bd9de5dfabb427448",
      "parents": [
        "a26b55228761455487f2e9d224c633c6a8c018d5"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 11:47:24 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 11:47:24 2026 -0400"
      },
      "message": "Track the dormant grails-rt code in the improvement plan\n\nExpands the Dormant code section of grails-rt/README.md into a table of what is\nunreachable and why, and adds the matching item 0.1b to IMPROVEMENT-PLAN.md so\nrevive-or-delete is a decision someone owns rather than a comment.\n\nFive entries, roughly half the jar:\n\n  addAgentJar                      no callers, so -javaagent: never runs\n  Agent                            reached only via addAgentJar\n  AddAgentJarToClassPathTransformer  installed only by Agent.premain\n  ForkListenerTransformer          installed only by Agent.premain\n  GrailsIdeaTestListener           Grails 2 GrailsBuildListener API; its only\n                                   reference is gated on a Gradle task that only\n                                   the old Grails Gradle plugin defines\n\nEach claim is re-checked against the tree rather than asserted: addAgentJar has\nexactly one occurrence (its own definition), Agent and ForkListenerTransformer\nhave zero references outside grails-rt, and GrailsIdeaTestListener is referenced\ntwice but only as a string class name passed through\n-Dgrails.build.listeners, never linked.\n\nThe README states plainly that these are not bugs and points at the plan item, so\nthe next person to notice an unused Java agent does not go looking for the missing\ncall site. Deleting them would reduce grails-rt to the two Groovy resources the\nGrails 3+ paths actually use.\n\nThe plan item also picks up the leftover untracked assetPipeline/ build output from\na module that was removed from settings.gradle.\n"
    },
    {
      "commit": "a26b55228761455487f2e9d224c633c6a8c018d5",
      "tree": "9dc1dd38a0f6122200b38df638de0d4075785664",
      "parents": [
        "2d2d1265b96ea85697155be4bf3c69ab4e1770c8"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 11:46:00 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 11:46:00 2026 -0400"
      },
      "message": "Document every subproject, and restore the grails-rt agent manifest\n\nAdds a README.md to all eleven subprojects plus build-logic, each covering what\nthe project is for, what it contains, how it is packaged, and the Grails versions\nit supports. Version ranges are derived from the version gates actually present\nin the code rather than asserted, and the READMEs say \"no version gating\" where\nthe code has none.\n\n  grails-compiler-patch   1.0  - 2.x     (jps-plugin injects it only below 3.0)\n  jps-plugin              1.0  - 2.x     (shouldInjectGrails() false from 3.0)\n  maven                   1.x  - 2.x     (Maven-built Grails; 3+ is Gradle)\n  gradle-tooling          3.0+           (Gradle-built projects only)\n  grails-rt               1.1+ , no max  (see below)\n  copyright, coverage, hibernate, i18n, langInjection   no version gating\n  testFramework           n/a (build-time only, not shipped)\n\ngrails-rt turned out to be split by era rather than legacy-only, which is worth\nrecording because the name suggests otherwise. Its Java classes are Grails 2.x\nvintage and dormant -- GrailsIdeaTestListener implements\ngrails.build.GrailsBuildListener, which does not exist in Grails 3+ -- while the\ntwo Groovy resources it ships are what the Grails 3.0+ code paths actually use\n(TraitInjectorService, Grails3TestCommandLineState, GrailsProjectResolverExtension,\nGrailsTaskManagerExtension). Grails 3+ never loads the legacy classes, so their\nbeing unloadable there is harmless.\n\nAlso fixes the agent manifest. grails-rt declares Premain-Class in a\nMETA-INF/MANIFEST.MF *resource*, but Gradle\u0027s Jar task writes its own manifest and\nsilently shadows a resource at that path, so the packaged jar carried only\n\"Manifest-Version: 1.0\" and a -javaagent: launch would have failed with \"Failed to\nfind Premain-Class manifest attribute\". The attributes are now declared on the jar\ntask and the dead resource is deleted.\n\nConfirmed pre-existing, not caused by this restructure: building grails-rt from\nthe pre-restructure branch produces the same stripped manifest. It also changes no\nbehaviour today, because GrailsExecutionUtils.addAgentJar has no callers --\ngit log -S shows it arrived already-orphaned in the initial JetBrains import\n(3d4bc40) and was never wired up. Both dormant paths are documented as such rather\nthan left to be rediscovered.\n"
    },
    {
      "commit": "2d2d1265b96ea85697155be4bf3c69ab4e1770c8",
      "tree": "734df45db9c819c1c53842ebe651fcfb3df09785",
      "parents": [
        "369fa5731db2f148ac6d3168c1e1c34c86973521"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 11:31:58 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 11:31:58 2026 -0400"
      },
      "message": "Flatten the compiler namespace so no tier is a prefix of another\n\norg.apache.grails.intellij.lib.compiler nested the compiler tier inside the lib\nnamespace, making one project\u0027s coordinates a prefix of another\u0027s. The tier\nnamespaces are now all siblings:\n\n  org.apache.grails.intellij.build       build-logic\n  org.apache.grails.intellij.plugin      the IntelliJ plugin\n  org.apache.grails.intellij.module      the six content modules\n  org.apache.grails.intellij.lib         gradle-tooling, grails-rt, testFramework\n  org.apache.grails.intellij.compiler    jps-plugin, grails-compiler-patch\n\nChecked mechanically: no tier namespace is a prefix of any other.\n\nThe directory layout is unaffected -- compiler plugins stay at libs/compiler/*\nso the source tiers keep mirroring the packaged layout (pluginModules/* ship to\nlib/modules/, everything under libs/ ships to lib/). Only the namespace is a\nsibling rather than a child, so on-disk grouping and coordinates are allowed to\ndiffer where they answer different questions.\n\nConvention plugin renamed build.lib-compiler -\u003e build.compiler to match, and the\ncompileServer.plugin classpath in plugin.xml updated to the new jar names;\nverified every entry still resolves to a real jar in lib/.\n"
    },
    {
      "commit": "369fa5731db2f148ac6d3168c1e1c34c86973521",
      "tree": "22dc89d605eec40ae086c2c1d56377377d7c890a",
      "parents": [
        "e71acf34cc290499b800457200cf59dbce6002a4"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 11:29:14 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 11:29:14 2026 -0400"
      },
      "message": "Nest the compiler tier under libs and complete the group hierarchy\n\nTwo corrections.\n\nThe compiler plugins move from a top-level compilerPlugins tier to a\nsubdirectory of the lib tier, so the source tiers mirror the packaged layout:\npluginModules/* are content modules and ship to lib/modules/, while everything\nunder libs/ -- compiler plugins included -- ships to lib/. The compiler\nsubdivision is a grouping within that tier, not a separate destination. The\nconvention plugin is renamed accordingly (build.compiler-plugin -\u003e\nbuild.lib-compiler) and the group becomes org.apache.grails.intellij.lib.compiler.\n\nTwo groups did not match the intended hierarchy:\n\n  plugin project   org.apache.grails.intellij  -\u003e  org.apache.grails.intellij.plugin\n  build-logic      (unset)                     -\u003e  org.apache.grails.intellij.build\n\nThe full hierarchy is now:\n\n  org.apache.grails.intellij.build           build-logic (Gradle DSL build code)\n  org.apache.grails.intellij.plugin          the IntelliJ plugin\n  org.apache.grails.intellij.module          the six content modules\n  org.apache.grails.intellij.lib             gradle-tooling, grails-rt, testFramework\n  org.apache.grails.intellij.lib.compiler    jps-plugin, grails-compiler-patch\n\nPackaged jar names follow the same tiers, and the compileServer.plugin classpath\nin plugin.xml is updated to match. Group is not part of any archive name, so the\ndistribution layout is unchanged by the group fixes -- verified.\n\nStill outstanding, and deliberately last: the six content module NAMES are still\nintellij.groovy.grails.*, because those are a three-way runtime contract with the\ndescriptor filenames and lib/modules/ jar names. They change in the dedicated\nrename step, gated on runIde.\n"
    },
    {
      "commit": "e71acf34cc290499b800457200cf59dbce6002a4",
      "tree": "f30e249cffb6b53aec78ee89f35a3aeea4a741f0",
      "parents": [
        "0b9505e14f5d19980a5c3c54d526c46bf0f38ed8"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 11:24:40 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 11:24:40 2026 -0400"
      },
      "message": "Split compiler plugins into their own tier, distinct from libraries\n\nThe tier directories exist to make it obvious what each subproject is, and\nlumping jps-plugin and grails-compiler-patch in with the libraries lost that:\nthey are not dependencies the plugin links against, they are the jars named by\n\u003ccompileServer.plugin classpath\u003d\"...\"/\u003e and they run inside the end user\u0027s JPS\nbuild process.\n\nAdds an org.apache.grails.intellij.build.compiler-plugin convention plugin with\ngroup org.apache.grails.intellij.compiler, and moves jps-plugin and\ngrails-compiler-patch onto it. Packaged names become\norg.apache.grails.intellij.compiler.{jps-plugin,grails-compiler-patch}.jar with\nthe descriptor updated to match; verified that every classpath entry resolves to\na real jar in lib/.\n\nThe distinction is not cosmetic: JPS resolves that attribute by file name and\nsilently skips anything missing, so these two jar names are part of the plugin\u0027s\ncontract and must stay pinned and in sync with the descriptor. Nothing depends\non the name of a plain library. Giving them their own directory makes that\nobligation visible instead of leaving it as a comment.\n\nThat leaves the lib tier as genuine dependencies: gradle-tooling (Gradle tooling\nAPI model builders), grails-rt (runtime jar pushed into user JVMs, located by\nclass rather than by name) and testFramework (test-only support).\n"
    },
    {
      "commit": "0b9505e14f5d19980a5c3c54d526c46bf0f38ed8",
      "tree": "8186c7e95c9d10395f4b04f7f5687f4c5955e8d5",
      "parents": [
        "cb817c4c141aa7179a690bd59f3822b511425276"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 11:23:07 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 11:23:07 2026 -0400"
      },
      "message": "Convert every build file to Groovy DSL and pin all artifact names\n\nStep 3 of the restructure. settings.gradle.kts, the root build and all eleven\nmodule build files become Groovy; each module now applies one tier convention\nplugin and carries only content. hibernate went from 68 lines to 27.\n\nVerified: 987 tests / 0 failures (baseline), RAT clean, configuration cache\nreused, and bytecode levels asserted from the class files rather than trusted:\ngradle-tooling/grails-compiler-patch/grails-rt \u003d major 52 (Java 8),\njps-plugin \u003d 55 (Java 11), hibernate \u003d 69 (Java 25).\n\nArtifact names are now pinned rather than derived, which is the point of the\nexercise. Packaged layout:\n\n  apache-grails-intellij-plugin-262.0.0.zip\n    lib/apache-grails-intellij-plugin-262.0.0.jar\n    lib/grails-rt-262.0.0.jar\n    lib/org.apache.grails.intellij.lib.{gradle-tooling,grails-compiler-patch,jps-plugin}.jar\n    lib/modules/intellij.groovy.grails.{copyright,coverage,hibernate,i18n,langInjection,maven}.jar\n\nThis fixes a pre-existing silent breakage: plugin.xml declared\ncompileServer.plugin classpath\u003d\"Grails.jps-plugin.jar;Grails.grails-compiler-patch.jar\",\nwhich no build has produced since rootProject.name was renamed. JPS resolves\nthose entries by name and silently skips what it cannot find, so the Grails JPS\nbuilders have simply not been loading. The names are now pinned by convention\nplugin instead of derived from rootProject.name, the attribute matches, and a\ncheck confirms every classpath entry resolves to a real jar in lib/.\n\nGroovy-vs-Kotlin differences that needed real fixes:\n\n* testFramework(type \u003d X, configurationName \u003d Y) used Kotlin named arguments to\n  skip the middle `version` parameter. Groovy cannot, so all three are passed\n  with the default spelled out as Constants.Constraints.CLOSEST_VERSION.\n* TestFrameworkType.Platform and friends are Kotlin objects with private\n  constructors, so .INSTANCE is required -- a bare reference is a Class in\n  Groovy, not a value.\n* pluginModule needs project(path: \u0027:x\u0027); a bare project(\u0027:x\u0027) resolves to\n  Project.project(String) and returns a Project rather than a Dependency.\n* JvmConstants.COMPILE_ONLY_CONFIGURATION_NAME is Gradle internal API, replaced\n  with the \u0027compileOnly\u0027 literal.\n* A convention plugin that configures the intellijPlatform extensions has to\n  apply org.jetbrains.intellij.platform.base itself; the consuming tier plugin\n  applies IPGP too late, after this body has already run.\n* jacoco-report-aggregation auto-registers testCodeCoverageReport only for\n  projects applying jvm-test-suite, so the registration is guarded and takes\n  effect only once the root stops compiling code.\n\nNote the nested-extension closures do NOT need explicit parameters: once IPGP\nis applied, delegate-first resolution finds the right intellijPlatform\nextension, so the idiomatic `dependencies { intellijPlatform { ... } }` works.\n"
    },
    {
      "commit": "cb817c4c141aa7179a690bd59f3822b511425276",
      "tree": "a4bac30cca527b6dfabcbec8a66ddf6cf1aead11",
      "parents": [
        "134d0df52344e7f879c869ef27bff6da13940497"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 11:08:08 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 11:08:08 2026 -0400"
      },
      "message": "Add the Groovy convention plugins and convert hibernate as a pilot\n\nStep 2 of the restructure: authors the convention plugins that the tiered\nprojects will apply, and converts one module (hibernate) to prove the whole\nchain end to end. hibernate/build.gradle is now 27 lines of pure content,\ndown from 68, and still produces intellij.groovy.grails.hibernate.jar.\n\nPlugins added under build-logic/src/main/groovy:\n  base, java, java-legacy (+LegacyBytecodeExtension), kotlin, test,\n  intellij-platform, intellij-plugin, intellij-module, lib, intellij-lib,\n  rat, coverage-aggregation, and a transitional legacy-layout.\n\nCorrections to the plan, found by testing rather than reading:\n\n* archivesName does NOT name the shipped artifact for IPGP projects. hibernate\n  ships intellij.groovy.grails.hibernate.jar (its composedJar pin) while\n  hibernate-262.0.0-base.jar is a throwaway intermediate, and gradle-tooling\n  ships \"Grails IntelliJ Plugin.gradle-tooling.jar\" rather than its\n  archivesName. IPGP\u0027s ComposedJarTask applies its own archiveBaseName\n  convention after the base plugin\u0027s, so composedJar.archiveBaseName is the\n  only knob that decides what users get. Both are now set, with the pin\n  carrying the comment explaining which one matters.\n\n* IPGP registers THREE extensions named intellijPlatform -- on the Project, on\n  RepositoryHandler and on DependencyHandler. Groovy resolves closures\n  owner-first, so a bare `intellijPlatform { }` nested inside `repositories { }`\n  resolves to the PROJECT extension and dies with \"Could not find method\n  defaultRepositories()\". Kotlin scopes by receiver type and has no such clash.\n  Every nested closure now binds its target to an explicit parameter.\n\n* A convention plugin that configures the intellijPlatform extensions must\n  apply org.jetbrains.intellij.platform.base itself. Relying on the consuming\n  tier plugin to apply .module afterwards is too late: the script body runs\n  first, when the extensions do not yet exist.\n\n* rat had outputs.upToDateWhen { false } but RatTask is @CacheableTask and\n  org.gradle.caching\u003dtrue, so a build-cache hit skipped the audit entirely.\n  Added outputs.cacheIf { false }.\n\n* The rat convention plugin applies lifecycle-base. RatPlugin hooks rat into\n  `check` only via plugins.withType(LifecycleBasePlugin), so once the root\n  project stops applying java, `./gradlew check buildPlugin` in CI would have\n  silently stopped auditing licences.\n\n* jacoco-report-aggregation only auto-registers \u003csuite\u003eCodeCoverageReport for\n  projects applying jvm-test-suite. The root compiles nothing, so the report is\n  registered explicitly -- this keeps the XML at the path coverage.yml uploads\n  instead of moving it under plugin/.\n\nAlso drops kotlin.jvm from i18n, which applies it with zero Kotlin sources.\n"
    },
    {
      "commit": "134d0df52344e7f879c869ef27bff6da13940497",
      "tree": "b85b3f83525da74d5be6288afbee58f5443c1224",
      "parents": [
        "ba98364204d81656f1e328b88ada5f87038572f0"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 10:57:49 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 10:57:49 2026 -0400"
      },
      "message": "Convert build-logic to Groovy precompiled script plugins\n\nFirst step of the Gradle restructure. build-logic moves from Kotlin DSL\n(kotlin-dsl, src/main/kotlin/*.gradle.kts) to Groovy precompiled script plugins\n(groovy-gradle-plugin, src/main/groovy/*.gradle), and the plugin ids move from\ngrails-intellij.* to org.apache.grails.intellij.build.*.\n\nVerified against the pre-restructure baseline: 987 tests, 0 failures, RAT clean,\nconfiguration cache still reused with no problems.\n\nTwo substantive changes beyond the language port:\n\n* build-logic now declares the Gradle plugins the convention plugins apply\n  (IntelliJ Platform 2.18.1, Kotlin 2.3.0, RAT 0.8.1, Sonatype scan 3.1.6) as\n  implementation dependencies. Today org.jetbrains.intellij.platform.module is\n  applied with no version in ten modules and resolves only by inheriting the\n  root build script\u0027s plugin classpath -- which would break the moment the\n  version-bearing declaration moves into a nested plugin/ project. Putting the\n  plugins on build-logic\u0027s classpath removes that coupling. These versions live\n  in build-logic/build.gradle rather than gradle.properties because build-logic\n  is a separate build and cannot read the root build\u0027s properties.\n\n* Fixed a latent configuration-cache violation in the vulnerability-scan\n  plugin: it dereferenced project.path inside doFirst/doLast, which is\n  unsupported at execution time, and read System.getenv eagerly. project.path\n  is now captured at configuration time and the environment is read through\n  providers.environmentVariable. This only escaped detection because\n  ossIndexAudit is not part of check.\n\nOne Groovy-vs-Kotlin DSL difference worth recording: Groovy closures resolve\nowner-first, so a bare `username \u003d ...` inside extensions.configure(...) was\nsilently assigned to the Project rather than the extension and failed with\nMissingPropertyException. The extension is now bound to an explicit closure\nparameter; Kotlin\u0027s configure\u003cT\u003e {} has no such fallback.\n\nVerified the ported conventions actually take effect rather than silently\nno-op\u0027ing: archive tasks report preserveFileTimestamps\u003dfalse and\nreproducibleFileOrder\u003dtrue, jacoco resolves toolVersion 0.8.14, and\nossIndexAudit is registered.\n"
    },
    {
      "commit": "ba98364204d81656f1e328b88ada5f87038572f0",
      "tree": "f8724708188e84c840c75d958937b338494235d6",
      "parents": [
        "63fa492daed68b96a43884b978a29588467bf50b"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 10:18:17 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 10:18:17 2026 -0400"
      },
      "message": "Convert the projectView package from Kotlin to Java\n\nSecond batch: 28 Kotlin files become 30 Java files, leaving no Kotlin under\nprojectView. Verified against the pre-conversion baseline -- 987 tests, 0\nfailures, no test classes lost -- and RAT still passes.\n\nTwo constructs had no direct Java equivalent:\n\n* typealias Classes / TreeNodes in nodes/util.kt are expanded to\n  Collection\u003cPsiClass\u003e and Collection\u003cAbstractTreeNode\u003c?\u003e\u003e. The ripple stayed\n  inside nodes/ -- the earlier grep hits in EnhancesTraitsCollector.kt and\n  GormTraitContributor.kt were substring matches on unrelated identifiers.\n* The reified findValueOfType/findNotNullValueOfType extensions become\n  TreeNodeUtil.findValueOfType(node, Class\u003cT\u003e), so the six call sites now pass\n  the class explicitly.\n\nTop-level declarations again became named holders: nodes/util.kt -\u003e TreeNodeUtil,\nnodes/impl.kt -\u003e GrailsNodes, projectView/impl/impl.kt -\u003e GrailsViewItems,\nshowHide.kt -\u003e GrailsProjectViewPanes, and ep.kt\u0027s EP_NAME moved onto\nGrailsViewNodeProvider as a constant, matching how platform extension points are\ndeclared. Kotlin\u0027s Triple for special folders became a GrailsViewItems.SpecialFolder\nrecord, and `by lazyUnsafe` node fields became volatile lazily-computed fields.\n\nVisibility needed care: GrailsProjectViewPane, GrailsTreeStructureProvider,\nGrailsAppNodeProvider, Grails3NodeProvider and OldGrailsNodeProvider are\ninstantiated reflectively from plugin.xml. Kotlin `internal` is public in\nbytecode, so these are explicitly public in Java -- package-private would have\ncompiled and then failed at runtime. GrailsNodeComparator is public for the same\ncross-package reason.\n\nBehaviour deliberately preserved rather than fixed: getPackagesRegular\u0027s trailing\n`filter { !it.isEmpty() }` dropped empty lists, which split never produces, so a\nclass in the default package still contributes an empty-named package node. That\nquirk predates this conversion and is documented in place.\n"
    },
    {
      "commit": "623a75747db01eb3ab08838305ab82e82a59d3c7",
      "tree": "a72b45867df0e7ae06686c3bcb36473907077a60",
      "parents": [
        "81dbe84c1d6df0e7e07d4e5c1899a498525e147b",
        "4097e065b8369a6bbdb9f7acdbc871d8fe554a84"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 10:02:06 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Jul 26 10:02:06 2026 -0400"
      },
      "message": "Merge pull request #6 from fernando88to/main\n\nFix HQL navigation and rename for GORM domains in Grails 3+"
    },
    {
      "commit": "63fa492daed68b96a43884b978a29588467bf50b",
      "tree": "183da6fccc57077f57137e845ae292f2d412d600",
      "parents": [
        "88ae0a2653569f45921483290b661b8c70508476"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 07:42:07 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 07:42:07 2026 -0400"
      },
      "message": "Convert the artefact api and impl packages from Kotlin to Java\n\nFirst batch of the Kotlin -\u003e Java source conversion: 11 Kotlin files become 12\nJava files. Verified against the pre-conversion baseline -- 987 tests, 0\nfailures, no test classes lost -- and the RAT audit still passes.\n\nConversion decisions that the rest of the batches will follow:\n\n* Kotlin `object` singletons become final classes with a public static INSTANCE\n  and a private constructor. Existing Java already called\n  ServiceArtefactHandler.INSTANCE, so this shape is load-bearing.\n* Kotlin `val` members become getters, so remaining Kotlin callers keep working\n  through synthetic property access and only bare top-level references need\n  editing.\n* Top-level declarations, which the compiler otherwise exposes as \u003cFile\u003eKt,\n  become explicitly named holders: constants.kt splits into ArtefactHandlers\n  (the extension point and handler list) and HandlerCache (the @Service), and\n  artefact/impl/util.kt becomes GrailsArtefacts.\n* Extension functions become static methods taking the receiver first --\n  handler.getArtefacts(app, scope) is now\n  GrailsArtefacts.getArtefacts(handler, app, scope).\n* HandlerCache keeps its lazily-computed maps. They stay lazy because handlers\n  contributed through the extension point are not registered when the service\n  is constructed; the fields are volatile so the idempotent recompute in a race\n  is published safely.\n\nCall sites updated in GrailsIconProvider.java, projectView/impl/impl.kt,\nGrailsAppNodeProvider.kt, OtherGrailsAppSourcesNode.kt, and\nGrailsArtefactHandlerNodeBase.kt.\n"
    },
    {
      "commit": "88ae0a2653569f45921483290b661b8c70508476",
      "tree": "1e2ceb2033cc71849d067e379443c6a0c8547bf8",
      "parents": [
        "0e3a95c97d4db8c18aee9dbd84f918696799d62e"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 07:32:27 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 07:32:27 2026 -0400"
      },
      "message": "Generate the vote, result, and announcement emails from checked-in templates\n\nFollows the precedent set for Grails releases: the release workflow renders the\nemails instead of leaving the release manager to write them by hand.\n\nAdds .github/vote_templates/{staged,vote_succeeded,announce}.txt, expanded with\nenvsubst and printed to the job log with recipient, subject, and body.\n\nThe upload job now records the dist.apache.org SVN revision the artifacts were\ncommitted at and resolves the tag commit id (dereferencing annotated tags), so\nthe [VOTE] email cites exactly what was staged. It gains contents:read to check\nout the templates and query the tag.\n\nThe release job prints the [RESULT][VOTE] and [ANNOUNCE] emails in place of the\nprevious one-line reminder.\n\nRELEASE.md now points at the templates as the single source of truth rather than\nduplicating a hand-written vote email.\n"
    },
    {
      "commit": "0e3a95c97d4db8c18aee9dbd84f918696799d62e",
      "tree": "3907ad1a5f663fde0cbe949d0aadae4820a79609",
      "parents": [
        "ecfd3f0843451d6f05dac10e2033f0feec66d88f"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 07:21:35 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 07:21:35 2026 -0400"
      },
      "message": "Drop grails-core references from the release docs\n\nThe release process should stand on its own rather than being described by\ncomparison with another repository.\n"
    },
    {
      "commit": "ecfd3f0843451d6f05dac10e2033f0feec66d88f",
      "tree": "fcf2afb5ca48bc0a3738efa7697409b8c6bb756d",
      "parents": [
        "78171a4b365d826de948c0a809a43e590f10d114"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 07:20:22 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 07:20:22 2026 -0400"
      },
      "message": "Add release-abort workflow and drop the stale test.idea.home.path default\n\nAdds .github/workflows/release-abort.yml, modelled on grails-core\u0027s but without\nthe Nexus staging drop -- this repository publishes no Maven artifacts, only the\ndist.apache.org distributions and the Marketplace plugin.\n\nThe workflow removes the staged artifacts from dist/dev, cancels in-flight\nRelease runs, and deletes the GitHub Release and tag. It requires an explicit\nconfirmation input, and hard-fails if the version already exists under\ndist/release, since that means the vote passed and the release is public.\n\nAlso removes the test.idea.home.path default from gradle.properties, which\npointed at a JetBrains developer\u0027s local checkout (/Users/yann/...). The\nbuild.gradle.kts block that reads the property stays, so it can still be set\nper-developer with -Ptest.idea.home.path.\n"
    },
    {
      "commit": "78171a4b365d826de948c0a809a43e590f10d114",
      "tree": "d56192327110bbc28f213fb4228548f580f6fd42",
      "parents": [
        "6be9d414b9bdf6059ec97234eebac732e7606918"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 07:15:31 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 07:15:31 2026 -0400"
      },
      "message": "Add release verification tooling and document the release process\n\nBrings the release process closer to grails-core\u0027s, scaled to this repo\u0027s two\nartifacts (source distribution + convenience binary).\n\nNew verification scripts under etc/bin, mirroring grails-core\u0027s:\n  * download-release-artifacts.sh - fetch staged artifacts and the Grails KEYS\n  * verify-distributions.sh - checksums, GPG signatures (in a throwaway GPG\n    home), and required/forbidden archive contents. Asserts the source\n    distribution ships gradle-bootstrap and does NOT ship the wrapper jar.\n  * verify-reproducible.sh - rebuild from the staged source distribution and\n    compare to the staged binary. Compares extracted contents rather than ZIP\n    hashes, because marketplace signing means the ZIPs can never match.\n  * verify.sh - runs all of the above plus the RAT audit.\n\nRELEASE.md gains the sections grails-core has and this repo lacked: the\nversioning scheme, how to cut the release, a pre-vote verification guide, a\nvote email template, rollback, a GPG/KEYS appendix, a release-secrets\nappendix, and an explanation of why ASF policy requires the build to be\nreproducible.\n\ntest-reproducible-build.sh now writes to etc/bin/results (which is what\n.gitignore already claimed) and keeps a copy of both builds\u0027 artifacts there,\nso a failure can actually be diagnosed instead of just reported. Comparison is\nnow keyed on path rather than whole lines, so an artifact missing from one\nbuild is reported as missing.\n\nREADME.md and INSTALL cover building from nothing: prerequisites with disk and\nmemory requirements, installing the pinned JDK/Gradle with and without SDKMAN!,\nwhat gradle-bootstrap does and when it is required, and the build itself.\n"
    },
    {
      "commit": "6be9d414b9bdf6059ec97234eebac732e7606918",
      "tree": "529adf258a98a85420bb883282c27254ea93a8a7",
      "parents": [
        "30fadfb488eb0839ddbebf06632488d49c7e13cc"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 07:15:17 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 07:15:17 2026 -0400"
      },
      "message": "Fix release workflow hardcoding the pre-rename plugin ZIP name\n\nThe publish job renamed build/distributions/Grails-${VERSION}-signed.zip to the\nASF distribution name. That filename is derived from the IntelliJ plugin name\n(rootProject.name), which is now \"Grails IntelliJ Plugin\", so the step would\nhave failed on the next release.\n\nGlob for *-${VERSION}-signed.zip instead of hardcoding the base name, quote the\nresult so the spaces in the current name are handled, and fail with a clear\nerror unless exactly one file matches -- so a future rename cannot silently\nbreak the release again.\n"
    },
    {
      "commit": "30fadfb488eb0839ddbebf06632488d49c7e13cc",
      "tree": "9c6946ad936890fd3c75699f61acec885034ef14",
      "parents": [
        "8adc98ab9b14b1fb2bbaa4e2572cac990eb809ba"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sat Jul 18 22:09:52 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 07:00:03 2026 -0400"
      },
      "message": "Document build-from-scratch and wrapper bootstrap in README\n\nExpand the README\u0027s build section with tooling setup (.sdkmanrc via SDKMAN),\nthe gradle-bootstrap step needed when building from a source distribution that\nships without the wrapper jar, and the new hygiene tasks (coverage, ossIndexAudit),\nmirroring grails-core\u0027s from-source guidance.\n"
    },
    {
      "commit": "8adc98ab9b14b1fb2bbaa4e2572cac990eb809ba",
      "tree": "7ff604450f0aab6e9e976a45f84a51420aa46deb",
      "parents": [
        "81dbe84c1d6df0e7e07d4e5c1899a498525e147b"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sat Jul 18 22:05:27 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 07:00:03 2026 -0400"
      },
      "message": "Port build-hygiene tooling from grails-core\n\nAdd a build-logic included build with Kotlin convention plugins for\nbuild reproducibility, JaCoCo code coverage, and Sonatype OSS Index\nvulnerability scanning, applied to the root project and every module.\n\n- reproducible archives (preserveFileTimestamps\u003dfalse, reproducibleFileOrder,\n  fixed permissions) so the plugin ZIP and module jars are byte-stable\n- JaCoCo per-module + root jacoco-report-aggregation (testCodeCoverageReport),\n  CC-safe under this repo\u0027s org.gradle.configuration-cache\u003dtrue\n- ossIndexAudit with an OssIndexAuditThrottle build service and fail-fast when\n  SONATYPE_GUIDE_TOKEN is absent\n- CI: coverage.yml (Codecov), vulnerability-scan.yml (OSS Index), codeql.yml\n- codecov.yml and .github/dependabot.yml (gradle, /build-logic, github-actions)\n- RELEASE.md documenting the release pipeline + reproducibility verification\n- etc/bin/test-reproducible-build.sh and etc/bin/Dockerfile for repro testing\n- .gitattributes pinning LF; enable GitHub issues in .asf.yaml\n"
    },
    {
      "commit": "81dbe84c1d6df0e7e07d4e5c1899a498525e147b",
      "tree": "ec92eabde4fd5f39315f745e42e4816387159604",
      "parents": [
        "ac0f00399c41e4992e3e093128899470d91e4476"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 06:57:32 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 26 06:57:32 2026 -0400"
      },
      "message": "Pull upstream 2026.2.x changes into our version\n"
    },
    {
      "commit": "4097e065b8369a6bbdb9f7acdbc871d8fe554a84",
      "tree": "9be8d1ea2c67b1e0e50c2ee4d4cd7134be8c1fa8",
      "parents": [
        "b013f2950ca7811df94c2fe6d31925278b285d0f"
      ],
      "author": {
        "name": "fernando henrique",
        "email": "fernando88to@gmail.com",
        "time": "Sat Jul 25 15:11:51 2026 -0300"
      },
      "committer": {
        "name": "fernando henrique",
        "email": "fernando88to@gmail.com",
        "time": "Sat Jul 25 15:11:51 2026 -0300"
      },
      "message": "Fix criteria closure resolution and property navigation for GORM 4+\n"
    },
    {
      "commit": "b013f2950ca7811df94c2fe6d31925278b285d0f",
      "tree": "5dc25e2cec1a8eb62b0aa1b601eff4599796526d",
      "parents": [
        "ac0f00399c41e4992e3e093128899470d91e4476"
      ],
      "author": {
        "name": "fernando henrique",
        "email": "fernando88to@gmail.com",
        "time": "Sat Jul 25 10:32:46 2026 -0300"
      },
      "committer": {
        "name": "fernando henrique",
        "email": "fernando88to@gmail.com",
        "time": "Sat Jul 25 10:32:46 2026 -0300"
      },
      "message": "Fix HQL navigation and rename for GORM domains in Grails 3+\n"
    },
    {
      "commit": "ac0f00399c41e4992e3e093128899470d91e4476",
      "tree": "1e7a9e52d25089968aa09b9bcefb4c02d0eb6625",
      "parents": [
        "5d4100a1b232af5eff8e19ff36a27fc8f3172952"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 23:05:15 2026 -0500"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 23:05:15 2026 -0500"
      },
      "message": "Fix project generator ignoring Tomcat servlet selection\n\nThe Forge module builder hardcoded the servlet type to JETTY after\nbuilding the wizard step, and afterChange only fired on user-initiated\nchanges. Leaving the segmented button on its default (Tomcat) therefore\nproduced a Jetty project. Seed the stored value from the property\u0027s\nactual value and default the fallback to TOMCAT to match the UI.\n"
    },
    {
      "commit": "5d4100a1b232af5eff8e19ff36a27fc8f3172952",
      "tree": "9d0218d7ad074a91d3f896a78f58a2ce50d4c907",
      "parents": [
        "dea2ce5b2dc8bf58f773291434feb2ba2ecb5e6d"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 15:35:13 2026 -0500"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 15:35:13 2026 -0500"
      },
      "message": "Add .DS_Store to gitignore\n"
    },
    {
      "commit": "dea2ce5b2dc8bf58f773291434feb2ba2ecb5e6d",
      "tree": "494fda55a7aa3198836fae4792f45132037b99b2",
      "parents": [
        "a67b6551d79aa9365a90ca1313f4edd5fcba809f"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 15:21:35 2026 -0500"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 15:21:35 2026 -0500"
      },
      "message": "Fix Go To View for artefacts whose views live in another module\n\nGoToViewAction only searched the artefact\u0027s own application root, so in a\nmulti-project build a service/controller whose views are defined in a\ndifferent module (e.g. an upstream artefact consumed by a downstream app)\nresolved no views.\n\nSearch every Grails application in the related-module set instead - the same\nmodule + dependencies + dependents set that artefact instance discovery uses,\nnow extracted as GrailsArtifact.getRelatedModules - looking under each\napplication\u0027s resolved appRoot/views/\u003cname\u003e and deduping by file.\n"
    },
    {
      "commit": "a67b6551d79aa9365a90ca1313f4edd5fcba809f",
      "tree": "31f23cdbe13020c2f2a4b29c72c4f78e05c7ffa9",
      "parents": [
        "2aa08f1c87e61eb56bf2e747db4ad86a14356fe9"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 15:10:10 2026 -0500"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 15:10:10 2026 -0500"
      },
      "message": "Reconcile asset-pipeline JS support: cross-block injection via extension point\n\nAsset-pipeline highlighting already existed in core (AssetScriptInjector,\nreferences.kt), but treated each \u003casset:script\u003e block as an isolated JS\nfragment, so symbols defined in one block were invisible to another.\n\nPort the parked branch\u0027s gspJsInjectionTag extension point into core:\nrecognizing asset:script as a JS-injection tag routes it through the core\nJS injector (GrailsJavaScriptInjector) and getJsTags(), granting language\ninjection AND cross-tag symbol visibility. The EP keeps third-party taglib\nknowledge extensible; asset:script ships as a core contribution.\n\n- Add GspJsInjectionTagBean + EP-driven JavaScriptIntegrationUtil.isJsInjectionTag\n- Invalidate GspFileImpl JS-tag cache on EP mod-count change\n- Guard AssetScriptInjector to skip JS-injection tags (avoid double injection)\n- Register asset:script in plugin.xml\n- Add test coverage (injection, cross-block completion, EP, path references)\n"
    },
    {
      "commit": "2aa08f1c87e61eb56bf2e747db4ad86a14356fe9",
      "tree": "d30e5d9c9936f22cc3b2cdf2c62ea0db61ab5d07",
      "parents": [
        "1fcc630daf60cada9d6ba29b9f3c7c1f6234b572"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 14:38:01 2026 -0500"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 14:38:01 2026 -0500"
      },
      "message": "Improve toolbar artefact navigation for multi-project builds\n\n- Deduplicate navigate targets: collectFromPluginXmls ran once per dependency\n  module, adding each descriptor-declared artefact N times; run it once and\n  dedupe files.\n- Navigate to artefacts in downstream (dependent) modules, not just\n  dependencies, so a shared upstream artefact can reach its consumers.\n- Fall back to name-only matching when no same-package artefact exists, for\n  shared artefacts living in a different package than the referencing class.\n"
    },
    {
      "commit": "1fcc630daf60cada9d6ba29b9f3c7c1f6234b572",
      "tree": "675b0df8b2fc71658207cb0264baf8702ca650d7",
      "parents": [
        "b19e81bac9756131f29ac1488a5c8f4b967c772a"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 14:18:04 2026 -0500"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 14:18:04 2026 -0500"
      },
      "message": "Remove debug logging from toolbar resolution fix\n"
    },
    {
      "commit": "b19e81bac9756131f29ac1488a5c8f4b967c772a",
      "tree": "796ff589713aeedfd835c06e500fb88d6d8ed503",
      "parents": [
        "c1935d8b25c4efc3b479d52b3b3659fe98026c07"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 14:07:48 2026 -0500"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 14:07:48 2026 -0500"
      },
      "message": "Fix toolbar resolution\n"
    },
    {
      "commit": "c1935d8b25c4efc3b479d52b3b3659fe98026c07",
      "tree": "4ecbfc53f0f887b4b836befde05e4e8f9469029d",
      "parents": [
        "6e74aa109f836adf7612fc96255669a3d2819be7"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 12:00:58 2026 -0500"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 12:00:58 2026 -0500"
      },
      "message": "Fix mechanical deprecation warnings\n\nReplace deprecated APIs with their supported equivalents:\n- Kotlin String.capitalize()/decapitalize() -\u003e replaceFirstChar\n- Query.asIterable() -\u003e findAll()\n- Class.newInstance() -\u003e getDeclaredConstructor().newInstance()\n- new URL(String) -\u003e new URI(String).toURL()\n- JDOMExternalizer -\u003e local helpers preserving the \u003csetting\u003e XML format\n- FilenameIndex.getFilesByName -\u003e getVirtualFilesByName + PsiManager\n"
    },
    {
      "commit": "6e74aa109f836adf7612fc96255669a3d2819be7",
      "tree": "7a5b8c6806b99740e59a8561f076373c53d4b071",
      "parents": [
        "2195770767490ec45b8de85c0e71d4bbc464eb42"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 09:39:39 2026 -0500"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 09:39:39 2026 -0500"
      },
      "message": "Remove deprecated document api usages\n"
    },
    {
      "commit": "2195770767490ec45b8de85c0e71d4bbc464eb42",
      "tree": "0c0dda289121d15b215ad216a985d755bfa02cd8",
      "parents": [
        "93eee30670b0470287f7298ff7ffc0089c222614"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 09:12:32 2026 -0500"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 09:12:41 2026 -0500"
      },
      "message": "Do not double zip the plugin install zip\n"
    },
    {
      "commit": "93eee30670b0470287f7298ff7ffc0089c222614",
      "tree": "4e443c62c5f7546b61930926aed3258d5f087c77",
      "parents": [
        "3f5da509771cdaf25e036bce22613bcba8078b8f",
        "e8c9385421fcabf1a3dc77ae7bd439a418750726"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 09:05:28 2026 -0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jul 20 09:05:28 2026 -0500"
      },
      "message": "Merge pull request #2 from apache/naming-updates\n\nNaming and description updates"
    },
    {
      "commit": "3f5da509771cdaf25e036bce22613bcba8078b8f",
      "tree": "b78e9d39b9d32a47675f0d52b1fba67416771f4c",
      "parents": [
        "529d03fbf9d381998da6c27fe59dc96178b85d3c",
        "0f15663f6ceaecbc735cc72c80279f1859f7ab03"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Mon Jul 20 07:23:24 2026 -0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jul 20 07:23:24 2026 -0500"
      },
      "message": "Merge pull request #1 from fernando88to/main"
    },
    {
      "commit": "0f15663f6ceaecbc735cc72c80279f1859f7ab03",
      "tree": "b78e9d39b9d32a47675f0d52b1fba67416771f4c",
      "parents": [
        "1c7653897d268191ce955e85a8036a18ab690bb9",
        "529d03fbf9d381998da6c27fe59dc96178b85d3c"
      ],
      "author": {
        "name": "Fernando Henrique",
        "email": "fernando88to@gmail.com",
        "time": "Mon Jul 20 07:15:26 2026 -0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Jul 20 07:15:26 2026 -0300"
      },
      "message": "Merge branch \u0027apache:main\u0027 into main\n"
    },
    {
      "commit": "529d03fbf9d381998da6c27fe59dc96178b85d3c",
      "tree": "57648d1e288f290cb0a839cd35b7faf5b268eb91",
      "parents": [
        "9c9ce3811e219c479a8619d715245a48316380e2"
      ],
      "author": {
        "name": "Mattias Reichel",
        "email": "mattias.reichel@gmail.com",
        "time": "Mon Jul 20 11:27:46 2026 +0200"
      },
      "committer": {
        "name": "Mattias Reichel",
        "email": "mattias.reichel@gmail.com",
        "time": "Mon Jul 20 11:27:46 2026 +0200"
      },
      "message": "[skip ci] Enable GitHub issues\n"
    },
    {
      "commit": "e8c9385421fcabf1a3dc77ae7bd439a418750726",
      "tree": "739f42574086db91b3ec2f4a1b7847076ba58657",
      "parents": [
        "1f83e92f8220b611f5e6518580e07da2f0de35a6"
      ],
      "author": {
        "name": "Mattias Reichel",
        "email": "mattias.reichel@gmail.com",
        "time": "Mon Jul 20 11:00:57 2026 +0200"
      },
      "committer": {
        "name": "Mattias Reichel",
        "email": "mattias.reichel@gmail.com",
        "time": "Mon Jul 20 11:00:57 2026 +0200"
      },
      "message": "Update plugin description\n"
    },
    {
      "commit": "1f83e92f8220b611f5e6518580e07da2f0de35a6",
      "tree": "f002c7e68830831369f9c29eab8d1bdc08de1a9e",
      "parents": [
        "f54cbb4cf1d17e5751e1fd6c2fc808a9144ef958"
      ],
      "author": {
        "name": "Mattias Reichel",
        "email": "mattias.reichel@gmail.com",
        "time": "Mon Jul 20 11:00:36 2026 +0200"
      },
      "committer": {
        "name": "Mattias Reichel",
        "email": "mattias.reichel@gmail.com",
        "time": "Mon Jul 20 11:00:36 2026 +0200"
      },
      "message": "Align generator naming with other frameworks\n"
    },
    {
      "commit": "f54cbb4cf1d17e5751e1fd6c2fc808a9144ef958",
      "tree": "118b85458040864ffccb5e3588557ff9864e2287",
      "parents": [
        "9c9ce3811e219c479a8619d715245a48316380e2"
      ],
      "author": {
        "name": "Mattias Reichel",
        "email": "mattias.reichel@gmail.com",
        "time": "Mon Jul 20 10:59:19 2026 +0200"
      },
      "committer": {
        "name": "Mattias Reichel",
        "email": "mattias.reichel@gmail.com",
        "time": "Mon Jul 20 10:59:19 2026 +0200"
      },
      "message": "Rename the project\n"
    },
    {
      "commit": "9c9ce3811e219c479a8619d715245a48316380e2",
      "tree": "705f04f88173bc4730313e99e610d46047ef78e2",
      "parents": [
        "719de11aae13d6713985f628fac9faed39194820"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 19 14:08:44 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 19 14:08:44 2026 -0400"
      },
      "message": "Fix coverage module NoClassDefFoundError at startup\n\nThe coverage content module registers a javaCoverageEngineExtension whose\nimplementation (GrailsCoverageExtension) extends\ncom.intellij.coverage.JavaCoverageEngineExtension. Its descriptor declared a\nruntime dependency on the platform alias com.intellij.modules.coverage, which\nresolves (so the module loaded) but does not contain JavaCoverageEngineExtension\n— that class and the com.intellij.javaCoverageEngineExtension EP live in the\n\"Coverage\" plugin (Code Coverage for Java). The module classloader therefore\nlacked the base class, throwing NoClassDefFoundError during startup when run\nconfigurations were deserialized.\n\nDepend on the class-bearing plugin id instead, matching build.gradle.kts\u0027s\nbundledPlugin(\"Coverage\").\n"
    },
    {
      "commit": "719de11aae13d6713985f628fac9faed39194820",
      "tree": "4ed9edd6ac98bb3038b0d06c733d1fc86061f090",
      "parents": [
        "03c9ece971c250b093278dddd657d77fe982d8c4"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 19 13:59:59 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 19 13:59:59 2026 -0400"
      },
      "message": "Document ASF license header requirement for new files in AGENTS.md\n\nNew source files must carry the ASF header from the repo-root HEADER file, not\nthe inherited \u0027Copyright JetBrains s.r.o. and contributors\u0027 header that appears\non migrated files.\n"
    },
    {
      "commit": "03c9ece971c250b093278dddd657d77fe982d8c4",
      "tree": "832adf3c80d8f8f7ca4e190cc279898db4e79e40",
      "parents": [
        "9a21a4b44b3a6099537d1fc0058772e7bbeb4b57"
      ],
      "author": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 19 13:36:00 2026 -0400"
      },
      "committer": {
        "name": "James Daugherty",
        "email": "jdaugherty@jdresources.net",
        "time": "Sun Jul 19 13:36:00 2026 -0400"
      },
      "message": "Add AGENTS.md\n"
    }
  ],
  "next": "9a21a4b44b3a6099537d1fc0058772e7bbeb4b57"
}
