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

jamesfredley pushed a commit to branch docs/publish-grails-skillsjars
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 5ed1a0f83e6ae7eaeac5af8fe98e47da52e0289e
Author: James Fredley <[email protected]>
AuthorDate: Wed Jul 1 10:25:30 2026 -0400

    Publish Grails skills through SkillsJars
    
    Expose the canonical .agents/skills directory through a root skills symlink 
so SkillsJars can discover the existing skill definitions without duplicating 
files.
    
    Add a verification task and publishing notes for submitting the skills from 
skillsjars.com.
    
    Assisted-by: Hephaestus:openai/gpt-5.5 codex-review
---
 .agents/skills/README.md      | 26 +++++++++++++++++++++++++
 build.gradle                  | 44 ++++++++++++++++++++++++++++++++++++++++++-
 gradle/rat-root-config.gradle |  3 ++-
 skills                        |  1 +
 4 files changed, 72 insertions(+), 2 deletions(-)

diff --git a/.agents/skills/README.md b/.agents/skills/README.md
new file mode 100644
index 0000000000..99ed315af9
--- /dev/null
+++ b/.agents/skills/README.md
@@ -0,0 +1,26 @@
+<!--
+SPDX-License-Identifier: Apache-2.0
+
+Licensed to the Apache Software Foundation (ASF) under one or more contributor 
license agreements; and to You under the Apache License, Version 2.0.
+-->
+
+# Grails Agent Skills
+
+The canonical Grails agent skills live in this directory.
+
+The repository root `skills` path is intentionally a symbolic link to this 
directory so [SkillsJars](https://www.skillsjars.com/) can discover the skills 
without maintaining duplicate files. SkillsJars deploys from public GitHub 
repositories by scanning `skills/**/SKILL.md` and then publishes one Maven 
Central artifact per skill under `com.skillsjars`.
+
+Run this check before publishing or updating a skill:
+
+```bash
+./gradlew verifySkillsJarsSources
+```
+
+After changes are merged to the public branch, publish them from SkillsJars by 
submitting:
+
+| Field | Value |
+|-------|-------|
+| GitHub Org | `apache` |
+| GitHub Repo | `grails-core` |
+
+Expected coordinates use the `apache__grails-core__<skill-name>` artifact 
pattern, for example 
`com.skillsjars:apache__grails-core__grails-developer:<date>-<commit>`.
diff --git a/build.gradle b/build.gradle
index 433eba3b5a..b41269402d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -119,6 +119,48 @@ tasks.register('publishAllToMavenLocal') {
     }
 }
 
+tasks.register('verifySkillsJarsSources') {
+    description = 'Verifies the SkillsJars publication path points at the 
agent-local skill sources.'
+    group = 'verification'
+
+    def agentSkillsDir = layout.projectDirectory.dir('.agents/skills')
+    def skillsJarsLink = layout.projectDirectory.file('skills')
+
+    inputs.dir(agentSkillsDir)
+    inputs.files(skillsJarsLink)
+
+    doLast {
+        def expectedTarget = '.agents/skills'
+        def skillsPath = skillsJarsLink.asFile.toPath()
+        def actualTarget = null
+
+        if (java.nio.file.Files.isSymbolicLink(skillsPath)) {
+            actualTarget = 
java.nio.file.Files.readSymbolicLink(skillsPath).toString().replace('\\', '/')
+        }
+        else if (skillsJarsLink.asFile.isFile()) {
+            actualTarget = 
skillsJarsLink.asFile.getText('UTF-8').trim().replace('\\', '/')
+        }
+
+        if (actualTarget != expectedTarget) {
+            throw new GradleException("The SkillsJars publication path must be 
a symlink to ${expectedTarget}, but found ${actualTarget ?: 'no link target'}.")
+        }
+
+        def gitMode = providers.exec {
+            commandLine 'git', 'ls-files', '-s', 'skills'
+        }.standardOutput.asText.get().trim().split(' ')[0]
+        if (gitMode != '120000') {
+            throw new GradleException("The SkillsJars publication path must be 
committed as a symlink with git mode 120000, but found mode ${gitMode ?: 
'none'}.")
+        }
+
+        def skillMarkers = fileTree(agentSkillsDir).matching {
+            include '**/SKILL.md'
+        }.files
+        if (skillMarkers.empty) {
+            throw new GradleException("No SkillsJars skill marker files found 
under ${expectedTarget}.")
+        }
+    }
+}
+
 apply {
     from layout.projectDirectory.file('gradle/publish-root-config.gradle')
     from layout.projectDirectory.file('gradle/rat-root-config.gradle')
@@ -133,4 +175,4 @@ apply {
 //            logger.lifecycle("\t- ${dep}")
 //        }
 //    }
-//}
\ No newline at end of file
+//}
diff --git a/gradle/rat-root-config.gradle b/gradle/rat-root-config.gradle
index acd52b23db..f3e81bded6 100644
--- a/gradle/rat-root-config.gradle
+++ b/gradle/rat-root-config.gradle
@@ -26,6 +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 symlink; 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',
             
'grails-web-common/src/main/groovy/org/grails/web/json/JSONArray.java',
@@ -138,4 +139,4 @@ tasks.named('rat') {
     excludes = allExcludes
     // never cache license audits
     it.outputs.upToDateWhen { false }
-}
\ No newline at end of file
+}
diff --git a/skills b/skills
new file mode 120000
index 0000000000..5018230063
--- /dev/null
+++ b/skills
@@ -0,0 +1 @@
+.agents/skills
\ No newline at end of file

Reply via email to