Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .agents/skills/README.md
Original file line number Diff line number Diff line change
@@ -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>`.
44 changes: 43 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -133,4 +175,4 @@ apply {
// logger.lifecycle("\t- ${dep}")
// }
// }
//}
//}
3 changes: 2 additions & 1 deletion gradle/rat-root-config.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -138,4 +139,4 @@ tasks.named('rat') {
excludes = allExcludes
// never cache license audits
it.outputs.upToDateWhen { false }
}
}
1 change: 1 addition & 0 deletions skills
Loading