This is an automated email from the ASF dual-hosted git repository. jdaugherty pushed a commit to branch rework/publish-grails-skills in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit e1d358be7849035f9f9455e26c124dffc8f0df89 Author: James Daugherty <[email protected]> AuthorDate: Thu Jul 2 08:53:14 2026 -0400 Restructure skills publishing into separate gradle projects --- .agents/skills/README.md | 35 ++++++------ build.gradle | 62 ---------------------- gradle/publish-root-config.gradle | 3 ++ gradle/rat-root-config.gradle | 2 +- grails-skills/developer/build.gradle | 29 ++++++++++ .../developer/src/main/resources/SKILL.md | 1 + grails-skills/upgrade-guide-8/build.gradle | 29 ++++++++++ .../upgrade-guide-8/src/main/resources/SKILL.md | 1 + settings.gradle | 7 +++ skills/grails-8-upgrade/SKILL.md | 1 - skills/grails-developer/SKILL.md | 1 - 11 files changed, 89 insertions(+), 82 deletions(-) diff --git a/.agents/skills/README.md b/.agents/skills/README.md index c727193d0d..a951e40c71 100644 --- a/.agents/skills/README.md +++ b/.agents/skills/README.md @@ -6,28 +6,29 @@ Licensed to the Apache Software Foundation (ASF) under one or more contributor l # Grails Agent Skills -The canonical Grails agent skills live in this directory. Skills that are only useful while working in the Grails framework repository stay here and are not published through SkillsJars. +The canonical Grails agent skills live in this directory. Skills that are only useful while working in the Grails framework repository stay here and are not published. -The repository root `skills/` directory contains normal skill directories for app-facing skills that are useful when building or upgrading end-user Grails applications. Each published directory contains only a `SKILL.md` symbolic link back to the canonical source in `.agents/skills`, so [SkillsJars](https://www.skillsjars.com/) can discover and package the skills without duplicating content. SkillsJars deploys from public GitHub repositories by scanning `skills/**/SKILL.md` and then publ [...] +App-facing skills that are useful when building or upgrading end-user Grails applications are published to Maven Central as individual artifacts. Each published skill has its own Gradle project under `grails-skills/`. The project packages the skill's `SKILL.md` into a jar via a symlink back to the canonical source in this directory, so there is no content duplication. -| Published skill | Source skill | Use | -|-----------------|--------------|-----| -| `grails-developer` | `.agents/skills/grails-developer` | Building current Grails web applications, REST APIs, GORM models, controllers, services, views, plugins, and tests | -| `grails-8-upgrade` | `.agents/skills/grails-8-upgrade` | Upgrading Grails applications from Grails 7.x to Grails 8 | +| Published skill | Gradle project | Maven coordinates | Source skill | Use | +|-----------------|----------------|-------------------|--------------|-----| +| `grails-developer` | `grails-skills/developer` | `org.apache.grails.skills:grails-developer` | `.agents/skills/grails-developer` | Building current Grails web applications, REST APIs, GORM models, controllers, services, views, plugins, and tests | +| `grails-8-upgrade` | `grails-skills/upgrade-guide-8` | `org.apache.grails.skills:grails-8-upgrade` | `.agents/skills/grails-8-upgrade` | Upgrading Grails applications from Grails 7.x to Grails 8 | -The repository-specific `hibernate-developer`, `test-fixer`, and `violation-fixer` skills are intentionally not linked from `skills/` because they are for Grails framework development. Contributors already receive them from this repository when working on Grails core. +The repository-specific `hibernate-developer`, `test-fixer`, `violation-fixer`, `groovy-developer`, and `java-developer` skills are intentionally not published because they are for Grails framework development. Contributors already receive them from this repository when working on Grails core. -Run this check before publishing or updating a skill: +Each `grails-skills/<name>/src/main/resources/SKILL.md` is a symbolic link to the canonical source under `.agents/skills/<name>/SKILL.md`. Edit the canonical source; the published jar always packages the current content. -```bash -./gradlew verifySkillsJarsSources -``` +To add a new published skill: -After changes are merged to the public branch, publish them from SkillsJars by submitting: +1. Add the canonical `SKILL.md` under `.agents/skills/<name>/`. +2. Create a Gradle project under `grails-skills/<project>/` with a `build.gradle` that applies `java` and `org.apache.grails.buildsrc.publish`, sets `group = 'org.apache.grails.skills'`, and declares `pomArtifactId`, `pomTitle`, and `pomDescription`. +3. Symlink `grails-skills/<project>/src/main/resources/SKILL.md` to the canonical source. +4. Register the project in `settings.gradle` and add it to the `publishedProjects` list in `gradle/publish-root-config.gradle`. -| Field | Value | -|-------|-------| -| GitHub Org | `apache` | -| GitHub Repo | `grails-core` | +Build a skill jar locally with: -Expected coordinates use the `apache__grails-core__<skill-name>` artifact pattern, for example `com.skillsjars:apache__grails-core__grails-developer:<date>-<commit>`. +```bash +./gradlew :grails-skills-developer:build +./gradlew :grails-skills-developer:publishToMavenLocal +``` diff --git a/build.gradle b/build.gradle index 6e5908e012..36c73e7c5d 100644 --- a/build.gradle +++ b/build.gradle @@ -119,68 +119,6 @@ tasks.register('publishAllToMavenLocal') { } } -tasks.register('verifySkillsJarsSources') { - description = 'Verifies the SkillsJars publication paths point at the app-facing Grails skill sources.' - group = 'verification' - - def skillsJarsDir = layout.projectDirectory.dir('skills') - def publishedSkills = [ - 'grails-8-upgrade': '../../.agents/skills/grails-8-upgrade/SKILL.md', - 'grails-developer': '../../.agents/skills/grails-developer/SKILL.md', - ] - - inputs.dir(layout.projectDirectory.dir('.agents/skills')) - inputs.dir(skillsJarsDir) - - doLast { - if (!skillsJarsDir.asFile.directory) { - throw new GradleException('The SkillsJars publication path must be a skills directory containing app-facing skill symlinks.') - } - - def actualSkillNames = skillsJarsDir.asFile.listFiles().findAll { File file -> file.directory }.collect { File file -> file.name } as Set - def expectedSkillNames = publishedSkills.keySet() as Set - def missingSkillNames = expectedSkillNames - actualSkillNames - def extraSkillNames = actualSkillNames - expectedSkillNames - if (missingSkillNames || extraSkillNames) { - throw new GradleException("SkillsJars must publish exactly ${expectedSkillNames}, but missing ${missingSkillNames} and found extra ${extraSkillNames}.") - } - - publishedSkills.each { String skillName, String expectedSkillTarget -> - def skillDir = skillsJarsDir.dir(skillName).asFile - if (!skillDir.directory) { - throw new GradleException("The SkillsJars skill ${skillName} must be a directory containing SKILL.md.") - } - - def skillLink = skillsJarsDir.file("${skillName}/SKILL.md").asFile - def skillPath = skillLink.toPath() - def actualTarget = null - - if (java.nio.file.Files.isSymbolicLink(skillPath)) { - actualTarget = java.nio.file.Files.readSymbolicLink(skillPath).toString().replace('\\', '/') - } - else if (skillLink.isFile()) { - actualTarget = skillLink.getText('UTF-8').trim().replace('\\', '/') - } - - if (actualTarget != expectedSkillTarget) { - throw new GradleException("The SkillsJars skill ${skillName} marker must be a symlink to ${expectedSkillTarget}, but found ${actualTarget ?: 'no link target'}.") - } - - def gitMode = providers.exec { - commandLine 'git', 'ls-files', '-s', "skills/${skillName}/SKILL.md" - }.standardOutput.asText.get().trim().split(' ')[0] - if (gitMode != '120000') { - throw new GradleException("The SkillsJars skill ${skillName} marker must be committed as a symlink with git mode 120000, but found mode ${gitMode ?: 'none'}.") - } - - def sourceSkillFile = layout.projectDirectory.file(expectedSkillTarget.substring(6)) - if (!sourceSkillFile.asFile.file) { - throw new GradleException("The SkillsJars skill ${skillName} target ${expectedSkillTarget} does not exist.") - } - } - } -} - apply { from layout.projectDirectory.file('gradle/publish-root-config.gradle') from layout.projectDirectory.file('gradle/rat-root-config.gradle') diff --git a/gradle/publish-root-config.gradle b/gradle/publish-root-config.gradle index 123bafc419..6959e78e58 100644 --- a/gradle/publish-root-config.gradle +++ b/gradle/publish-root-config.gradle @@ -140,6 +140,9 @@ def publishedProjects = [ 'grails-data-graphql-core', // wrapper 'grails-wrapper', + // agent skills + 'grails-skills-developer', + 'grails-skills-upgrade-guide-8', // profiles 'grails-profiles-base', 'grails-profiles-plugin', diff --git a/gradle/rat-root-config.gradle b/gradle/rat-root-config.gradle index c160aac011..23d127f38b 100644 --- a/gradle/rat-root-config.gradle +++ b/gradle/rat-root-config.gradle @@ -26,7 +26,7 @@ tasks.named('rat') { 'CHECKSUMS', // build artifact for storing checksums for easy verification 'PUBLISHED_ARTIFACTS', // build artifact for storing published artifacts coordinates & paths 'licenses/**', // licenses directory excluded - 'skills/**', // SkillsJars discovery symlinks; licensed skill sources live under .agents/skills + 'grails-skills/*/src/main/resources/SKILL.md', // published skill symlinks; licensed skill sources live under .agents/skills '.claude/skills/**', // Claude skill aliases; licensed skill sources live under .agents/skills '.git-blame-ignore-revs', // git configuration isn't code 'grails-web-common/src/main/groovy/org/grails/web/json/JSONObject.java', diff --git a/grails-skills/developer/build.gradle b/grails-skills/developer/build.gradle new file mode 100644 index 0000000000..1d2f8a2f6f --- /dev/null +++ b/grails-skills/developer/build.gradle @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +plugins { + id 'java' + id 'org.apache.grails.buildsrc.publish' +} + +group = 'org.apache.grails.skills' +version = projectVersion + +ext { + pomArtifactId = 'grails-developer' + pomTitle = 'Apache Grails® Developer Agent Skill' + pomDescription = 'Agent skill for building Apache Grails® applications: controllers, services, GORM, views, plugins, and testing with Spock and Geb. The canonical SKILL.md source lives under .agents/skills/grails-developer.' +} diff --git a/grails-skills/developer/src/main/resources/SKILL.md b/grails-skills/developer/src/main/resources/SKILL.md new file mode 120000 index 0000000000..fdc0c1877e --- /dev/null +++ b/grails-skills/developer/src/main/resources/SKILL.md @@ -0,0 +1 @@ +../../../../../.agents/skills/grails-developer/SKILL.md \ No newline at end of file diff --git a/grails-skills/upgrade-guide-8/build.gradle b/grails-skills/upgrade-guide-8/build.gradle new file mode 100644 index 0000000000..5a0b7d7e91 --- /dev/null +++ b/grails-skills/upgrade-guide-8/build.gradle @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +plugins { + id 'java' + id 'org.apache.grails.buildsrc.publish' +} + +group = 'org.apache.grails.skills' +version = projectVersion + +ext { + pomArtifactId = 'grails-8-upgrade' + pomTitle = 'Apache Grails® 8 Upgrade Agent Skill' + pomDescription = 'Agent skill for upgrading Apache Grails® applications from Grails 7.x to Grails 8. The canonical SKILL.md source lives under .agents/skills/grails-8-upgrade.' +} diff --git a/grails-skills/upgrade-guide-8/src/main/resources/SKILL.md b/grails-skills/upgrade-guide-8/src/main/resources/SKILL.md new file mode 120000 index 0000000000..54634cb5ff --- /dev/null +++ b/grails-skills/upgrade-guide-8/src/main/resources/SKILL.md @@ -0,0 +1 @@ +../../../../../.agents/skills/grails-8-upgrade/SKILL.md \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index b0b8843788..1a1ffb303b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -224,6 +224,10 @@ include( // wrapper 'grails-wrapper', + + // Agent Skills (published for consumption by AI coding agents) + 'grails-skills-developer', + 'grails-skills-upgrade-guide-8', ) project(':grails-bom').projectDir = file('grails-bom/default') @@ -249,6 +253,9 @@ project(':grails-async-rxjava2').projectDir = file('grails-async/rxjava2') project(':grails-async-rxjava3').projectDir = file('grails-async/rxjava3') project(':grails-async').projectDir = file('grails-async/plugin') +project(':grails-skills-developer').projectDir = file('grails-skills/developer') +project(':grails-skills-upgrade-guide-8').projectDir = file('grails-skills/upgrade-guide-8') + project(':grails-dependencies-starter-web').projectDir = file('grails-dependencies/starter-web') project(':grails-dependencies-test').projectDir = file('grails-dependencies/test') project(':grails-dependencies-assets').projectDir = file('grails-dependencies/assets') diff --git a/skills/grails-8-upgrade/SKILL.md b/skills/grails-8-upgrade/SKILL.md deleted file mode 120000 index d4c2988030..0000000000 --- a/skills/grails-8-upgrade/SKILL.md +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/grails-8-upgrade/SKILL.md \ No newline at end of file diff --git a/skills/grails-developer/SKILL.md b/skills/grails-developer/SKILL.md deleted file mode 120000 index 9128af3297..0000000000 --- a/skills/grails-developer/SKILL.md +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/grails-developer/SKILL.md \ No newline at end of file
