Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/skill-proposal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ body:
id: home
attributes:
label: Where should this skill live?
description: See the two contribution paths in `CONTRIBUTING.md`. Path A skills are authored under `skills/` in this repo; Path B skills live in a product repo and are registered here via `scripts/sources.yml`.
description: See the two contribution paths in `CONTRIBUTING.md`. Path A skills are authored under `skills/` in this repo; Path B skills live in a product repo and are registered here via `.github/scripts/sources.yml`.
options:
- "Path A: incubated in this repo (may move to a product repo and continbue to be registered here)"
- "Path B: authored in a product repo (HIP, ROCm, Ryzen AI, Lemonade, ...) and registered here"
Expand Down
10 changes: 5 additions & 5 deletions scripts/check.sh → .github/scripts/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
# Validate every SKILL.md and that generated plugin manifests are up to date.
#
# Usage:
# ./scripts/check.sh Validate every skill and check manifests.
# ./scripts/check.sh -h|--help Print this help.
# ./.github/scripts/check.sh Validate every skill and check manifests.
# ./.github/scripts/check.sh -h|--help Print this help.
#
# Requires `uv` (https://github.com/astral-sh/uv).

set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT_DIR"

usage() {
Expand All @@ -18,8 +18,8 @@ usage() {

case "${1:-}" in
"")
uv run scripts/validate_skills.py
uv run scripts/generate_cursor_marketplace.py --check
uv run .github/scripts/validate_skills.py
uv run .github/scripts/generate_cursor_marketplace.py --check
;;
-h|--help)
usage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
Cursor exposes exactly the same skills as Claude.

Usage:
uv run scripts/generate_cursor_marketplace.py # write
uv run scripts/generate_cursor_marketplace.py --check # validate only
uv run .github/scripts/generate_cursor_marketplace.py # write
uv run .github/scripts/generate_cursor_marketplace.py --check # validate only

`--check` fails if the generated file is stale or if the Claude marketplace
top-level identity has drifted from `plugin-metadata.json`.
Expand All @@ -37,7 +37,7 @@
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
ROOT = Path(__file__).resolve().parent.parent.parent
PLUGIN_METADATA = ROOT / "plugin-metadata.json"
CLAUDE_MARKETPLACE = ROOT / ".claude-plugin" / "marketplace.json"
CURSOR_MARKETPLACE = ROOT / ".cursor-plugin" / "marketplace.json"
Expand Down Expand Up @@ -142,7 +142,7 @@ def main(argv: list[str] | None = None) -> int:
file=sys.stderr,
)
print(
"Run: uv run scripts/generate_cursor_marketplace.py",
"Run: uv run .github/scripts/generate_cursor_marketplace.py",
file=sys.stderr,
)
return 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# requires-python = ">=3.10"
# dependencies = ["pyyaml>=6.0"]
# ///
"""Import skills from external repositories listed in `scripts/sources.yml`.
"""Import skills from external repositories listed in `.github/scripts/sources.yml`.

For each source, the script:

Expand All @@ -21,11 +21,11 @@
skill (using the SKILL.md `description` as the marketplace blurb,
unless the source declares an override).
6. Removes any previously imported skill (one with a `.federated.json`)
that is no longer listed in `scripts/sources.yml`.
that is no longer listed in `.github/scripts/sources.yml`.

Usage:
uv run scripts/import_external_skills.py # write changes
uv run scripts/import_external_skills.py --dry-run # report only
uv run .github/scripts/import_external_skills.py # write changes
uv run .github/scripts/import_external_skills.py --dry-run # report only

The companion GitHub Actions workflow `import-external-skills` calls this
script on manual dispatch and opens a pull request with the result.
Expand All @@ -48,7 +48,7 @@

import yaml

REPO_ROOT = Path(__file__).resolve().parent.parent
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
CATALOG_FILE = Path(__file__).resolve().parent / "sources.yml"
SKILLS_DIR = REPO_ROOT / "skills"
CLAUDE_MARKETPLACE = REPO_ROOT / ".claude-plugin" / "marketplace.json"
Expand Down Expand Up @@ -400,7 +400,7 @@ def update_marketplace(results: Iterable[ImportResult], dry_run: bool) -> bool:

# Drop entries that point at skills that no longer exist on disk so
# the importer also cleans up the marketplace when an entry is
# removed from `scripts/sources.yml`.
# removed from `.github/scripts/sources.yml`.
existing_dirs = {p.name for p in SKILLS_DIR.iterdir() if p.is_dir()}
pruned = [p for p in plugins if not isinstance(p, dict) or p.get("name") in existing_dirs]
if len(pruned) != len(plugins):
Expand Down Expand Up @@ -536,7 +536,7 @@ def main(argv: list[str] | None = None) -> int:
if spec.folder in declared:
raise ValueError(
f"Skill name collision: {spec.folder!r} is listed by "
"more than one source in scripts/sources.yml."
"more than one source in .github/scripts/sources.yml."
)
declared.add(spec.folder)
all_results.extend(import_source(source, args.dry_run, log))
Expand Down
14 changes: 7 additions & 7 deletions scripts/publish.sh → .github/scripts/publish.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@
# canonical marketplace + metadata sources.
#
# Usage:
# ./scripts/publish.sh Regenerate all derived artifacts.
# ./scripts/publish.sh --check Verify derived artifacts are up to date.
# ./scripts/publish.sh -h|--help Print this help.
# ./.github/scripts/publish.sh Regenerate all derived artifacts.
# ./.github/scripts/publish.sh --check Verify derived artifacts are up to date.
# ./.github/scripts/publish.sh -h|--help Print this help.
#
# Currently regenerates:
# - .cursor-plugin/marketplace.json (mirror of .claude-plugin/marketplace.json
# + plugin-metadata.json)
#
# `.claude-plugin/marketplace.json` is hand-maintained because its
# human-facing plugin descriptions intentionally differ from the SKILL.md
# routing descriptions; ./scripts/check.sh enforces that the marketplace
# routing descriptions; ./.github/scripts/check.sh enforces that the marketplace
# listing matches skills/ on disk.
#
# Requires `uv` (https://github.com/astral-sh/uv).

set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT_DIR"

usage() {
Expand All @@ -29,11 +29,11 @@ usage() {

case "${1:-}" in
"")
uv run scripts/generate_cursor_marketplace.py
uv run .github/scripts/generate_cursor_marketplace.py
echo "Publish artifacts generated successfully."
;;
--check)
uv run scripts/generate_cursor_marketplace.py --check
uv run .github/scripts/generate_cursor_marketplace.py --check
;;
-h|--help)
usage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

Usage:

uv run scripts/skillspector_gate.py \
uv run .github/scripts/skillspector_gate.py \
--report reports/rocm-doctor.md \
--skill rocm-doctor \
--allowlist .github/skillspector-allow.yml
Expand Down Expand Up @@ -156,7 +156,7 @@ def main() -> int:
parser.add_argument(
"--allowlist",
type=Path,
default=Path(__file__).resolve().parent.parent / ".github" / "skillspector-allow.yml",
default=Path(__file__).resolve().parent.parent / "skillspector-allow.yml",
help="Path to the suppression allowlist (YAML).",
)
args = parser.parse_args()
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@

Run from the repo root:

./scripts/check.sh # used locally; thin wrapper
uv run scripts/validate_skills.py # validate every skill + manifest
uv run scripts/validate_skills.py --skills-dir skills
uv run scripts/validate_skills.py --list # print skill names as JSON
uv run scripts/validate_skills.py --skill rocm-doctor # one skill only
uv run scripts/validate_skills.py --marketplace-only # manifest only
./.github/scripts/check.sh # used locally; thin wrapper
uv run .github/scripts/validate_skills.py # validate every skill + manifest
uv run .github/scripts/validate_skills.py --skills-dir skills
uv run .github/scripts/validate_skills.py --list # print skill names as JSON
uv run .github/scripts/validate_skills.py --skill rocm-doctor # one skill only
uv run .github/scripts/validate_skills.py --marketplace-only # manifest only

The `--list` / `--skill` options let CI validate each skill in its own job
(see .github/workflows/validate.yml) so a single bad skill doesn't mask the
Expand All @@ -45,7 +45,7 @@

import yaml

REPO_ROOT = Path(__file__).resolve().parent.parent
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
DEFAULT_SKILLS_DIR = REPO_ROOT / "skills"
CLAUDE_MARKETPLACE = REPO_ROOT / ".claude-plugin" / "marketplace.json"

Expand Down
2 changes: 1 addition & 1 deletion .github/skillspector-allow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# SkillSpector's static scan is high-recall / moderate-precision and has no
# native per-finding suppression. This file is the auditable place to record
# findings that are genuinely false positives so the CI gate
# (scripts/skillspector_gate.py) does not fail on them. Everything not listed
# (.github/scripts/skillspector_gate.py) does not fail on them. Everything not listed
# here still fails the build at HIGH/CRITICAL.
#
# Each entry suppresses ONE rule for ONE file within ONE skill:
Expand Down
18 changes: 9 additions & 9 deletions .github/workflows/import-external-skills.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: import-external-skills

# Manually-dispatched workflow that refreshes the federated portion of the
# catalog. It reads `scripts/sources.yml`, shallow-clones each declared
# catalog. It reads `.github/scripts/sources.yml`, shallow-clones each declared
# source, vendors the named skills into `skills/<name>/`, updates
# `.claude-plugin/marketplace.json`, regenerates the Cursor manifest, and
# opens a pull request with the result. Every imported skill goes through
Expand Down Expand Up @@ -42,22 +42,22 @@ jobs:
git --version
git config --global init.defaultBranch main

- name: Import skills declared in scripts/sources.yml
- name: Import skills declared in .github/scripts/sources.yml
id: import
run: |
if [ "${{ inputs.dry_run }}" = "true" ]; then
uv run scripts/import_external_skills.py --dry-run
uv run .github/scripts/import_external_skills.py --dry-run
else
uv run scripts/import_external_skills.py
uv run .github/scripts/import_external_skills.py
fi

- name: Regenerate Cursor marketplace manifest
if: ${{ inputs.dry_run != true }}
run: uv run scripts/generate_cursor_marketplace.py
run: uv run .github/scripts/generate_cursor_marketplace.py

- name: Validate skills and manifests
if: ${{ inputs.dry_run != true }}
run: ./scripts/check.sh
run: ./.github/scripts/check.sh

- name: Detect changes
if: ${{ inputs.dry_run != true }}
Expand All @@ -81,10 +81,10 @@ jobs:
with:
branch: bot/import-external-skills
delete-branch: true
commit-message: "chore(catalog): refresh federated skills from scripts/sources.yml"
commit-message: "chore(catalog): refresh federated skills from .github/scripts/sources.yml"
title: "Refresh federated skills"
body: |
Automated import driven by `scripts/sources.yml`.
Automated import driven by `.github/scripts/sources.yml`.

See the "A federated catalog" section of `README.md` for the
design. Each vendored skill includes a `.federated.json` marker
Expand All @@ -97,7 +97,7 @@ jobs:
catalog
automated
add-paths: |
scripts/sources.yml
.github/scripts/sources.yml
skills/**
.claude-plugin/**
.cursor-plugin/**
6 changes: 3 additions & 3 deletions .github/workflows/skillspector.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:

- name: List skills
id: discover
run: echo "skills=$(uv run scripts/validate_skills.py --list)" >> "$GITHUB_OUTPUT"
run: echo "skills=$(uv run .github/scripts/validate_skills.py --list)" >> "$GITHUB_OUTPUT"

scan-skill:
name: Scan skill
Expand Down Expand Up @@ -89,8 +89,8 @@ jobs:
# Fail when any individual finding is HIGH or CRITICAL, except for
# documented false positives recorded in .github/skillspector-allow.yml.
# SkillSpector has no native suppression, so the gate applies the
# allowlist here (see scripts/skillspector_gate.py).
uv run scripts/skillspector_gate.py \
# allowlist here (see .github/scripts/skillspector_gate.py).
uv run .github/scripts/skillspector_gate.py \
--report "$report" \
--skill "${{ matrix.skill }}" \
--allowlist .github/skillspector-allow.yml
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
branches: [main]
pull_request:
paths:
- "scripts/**"
- ".github/scripts/**"
- "**/*.md"
- "skills/**"
- ".claude-plugin/**"
Expand Down Expand Up @@ -33,7 +33,7 @@ jobs:

- name: List skills
id: discover
run: echo "skills=$(uv run scripts/validate_skills.py --list)" >> "$GITHUB_OUTPUT"
run: echo "skills=$(uv run .github/scripts/validate_skills.py --list)" >> "$GITHUB_OUTPUT"

validate-skill:
name: Validate skill
Expand All @@ -53,7 +53,7 @@ jobs:
uses: astral-sh/setup-uv@v7

- name: Validate skill
run: uv run scripts/validate_skills.py --skill "${{ matrix.skill }}"
run: uv run .github/scripts/validate_skills.py --skill "${{ matrix.skill }}"

# Repo-wide checks that aren't tied to a single skill: the generated plugin
# manifests and internal markdown references.
Expand All @@ -68,10 +68,10 @@ jobs:
uses: astral-sh/setup-uv@v7

- name: Validate marketplace manifest
run: uv run scripts/validate_skills.py --marketplace-only
run: uv run .github/scripts/validate_skills.py --marketplace-only

- name: Validate generated Cursor manifest
run: uv run scripts/generate_cursor_marketplace.py --check
run: uv run .github/scripts/generate_cursor_marketplace.py --check

# Deterministic, offline-only reference check: relative paths and
# heading anchors. External URLs are intentionally not checked here
Expand Down
16 changes: 8 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,20 @@ Best for cross-cutting skills that do not have a natural product home.
4. Register the skill in `.claude-plugin/marketplace.json` with a human-readable description (the marketplace description is for humans browsing the catalog; the `SKILL.md` description is what the agent uses for routing).
5. Regenerate the Cursor manifest so it tracks the new skill:
```bash
./scripts/publish.sh # writes .cursor-plugin/marketplace.json
./.github/scripts/publish.sh # writes .cursor-plugin/marketplace.json
```
6. Validate the skill locally before pushing:
```bash
./scripts/check.sh # validates every SKILL.md and that manifests are in sync
./.github/scripts/check.sh # validates every SKILL.md and that manifests are in sync
```
7. Open a pull request. The `validate` GitHub Actions workflow runs `./scripts/check.sh` and must pass before merge. See [Validating locally](#validating-locally) for the full set of enforced rules.
7. Open a pull request. The `validate` GitHub Actions workflow runs `./.github/scripts/check.sh` and must pass before merge. See [Validating locally](#validating-locally) for the full set of enforced rules.

### Path B: Skills authored in a product repository

Best for skills that should ship and version with a product (HIP, MIGraphX, Ryzen AI, Lemonade, etc.).

1. Add the skill folder to your product repository; a common location is `.agents/skills/<skill-name>/`.
2. Open a pull request here that adds (or extends) an entry in [`scripts/sources.yml`](scripts/sources.yml) — the master list — naming your repo, a pinned ref, the sub-path that holds skill folders, and your skill's folder name.
2. Open a pull request here that adds (or extends) an entry in [`.github/scripts/sources.yml`](.github/scripts/sources.yml) — the master list — naming your repo, a pinned ref, the sub-path that holds skill folders, and your skill's folder name.
3. Once the catalog change merges, dispatch the **Import external skills** workflow from the Actions tab. It shallow-clones your repo at the pinned ref, vendors the skill into `skills/<name>/`, updates `.claude-plugin/marketplace.json`, and opens a follow-up pull request. Validation then runs against the same rules as in-repo skills before merge.

## Is this task a good fit for a skill?
Expand Down Expand Up @@ -182,14 +182,14 @@ Test the skill the way users will hit it:
- [ ] Scripts handle expected errors and document their constants and dependencies
- [ ] Prerequisites (ROCm version, GPU arch, container, env vars) are stated explicitly
- [ ] Tested end-to-end on the target hardware against real prompts
- [ ] `./scripts/check.sh` passes (CI runs this on every PR)
- [ ] `./.github/scripts/check.sh` passes (CI runs this on every PR)

## Validating locally

The structural rules from this guide (frontmatter shape, name format, description length, and `SKILL.md` body size) are enforced by `scripts/validate_skills.py` and run on every pull request. Run them locally before pushing:
The structural rules from this guide (frontmatter shape, name format, description length, and `SKILL.md` body size) are enforced by `.github/scripts/validate_skills.py` and run on every pull request. Run them locally before pushing:

```bash
./scripts/check.sh # validates every skill and plugin manifests (same command CI runs)
./.github/scripts/check.sh # validates every skill and plugin manifests (same command CI runs)
```

The validator checks every skill under `skills/` for:
Expand All @@ -202,4 +202,4 @@ The validator checks every skill under `skills/` for:
It also checks the plugin manifests:

- every skill under `skills/` has a matching entry in `.claude-plugin/marketplace.json` (and vice versa), with `source` set to `./skills/<name>` and a non-empty human-readable `description`
- `.cursor-plugin/marketplace.json` is up to date — it mirrors `.claude-plugin/marketplace.json` and pulls shared identity (name, description, version, author) from `plugin-metadata.json` (regenerate with `./scripts/publish.sh`)
- `.cursor-plugin/marketplace.json` is up to date — it mirrors `.claude-plugin/marketplace.json` and pulls shared identity (name, description, version, author) from `plugin-metadata.json` (regenerate with `./.github/scripts/publish.sh`)
Loading
Loading