Skip to content
Open
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
41 changes: 41 additions & 0 deletions .github/ISSUE_TEMPLATE/01-bug-report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Bug report
description: Report a problem in a skill, reference file, eval fixture, or release artifact.
title: "[Bug]: "
labels:
- bug
body:
- type: dropdown
id: area
attributes:
label: Affected area
options:
- github-profile
- github-repo
- wp-github-actions
- wp-readme-optimizer
- Repository infrastructure
validations:
required: true
- type: textarea
id: summary
attributes:
label: What happened?
description: Describe the current behavior and the expected behavior.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Reproduction
description: Include the prompt, relevant files, and steps needed to reproduce.
placeholder: |
1. Use skill ...
2. Provide prompt ...
3. Observe ...
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional context
description: Link to the affected file paths, workflow run, or release asset if applicable.
25 changes: 25 additions & 0 deletions .github/ISSUE_TEMPLATE/02-feature-request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Feature request
description: Propose a new skill, a skill expansion, or repository-level automation improvement.
title: "[Feature]: "
labels:
- enhancement
body:
- type: textarea
id: problem
attributes:
label: What problem should this solve?
description: Describe the user problem or workflow gap.
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed change
description: Describe the skill behavior, references, or templates you want added.
validations:
required: true
- type: textarea
id: evidence
attributes:
label: Supporting material
description: Include source links, example prompts, or related upstream work if relevant.
20 changes: 20 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Pull Request Summary

- What changed?
- Why does it matter?

## Validation

- [ ] `bash .github/scripts/validate-skills.sh`
- [ ] Markdown checked locally or in CI
- [ ] Links checked locally or in CI

## Release Impact

- [ ] No release impact
- [ ] Existing `.skill` package changes
- [ ] New or renamed skill directory

## Notes

Link related issues, upstream references, or follow-up work here.
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
131 changes: 131 additions & 0 deletions .github/scripts/validate-skills.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#!/usr/bin/env bash

set -euo pipefail

status=0

error() {
local file="$1"
local message="$2"
echo "::error file=${file}::${message}"
status=1
}

info() {
local message="$1"
echo "${message}"
}

extract_frontmatter() {
local skill="$1"
local closing_line

if [[ "$(head -n 1 "$skill")" != "---" ]]; then
error "$skill" "Missing YAML frontmatter opening delimiter"
return 1
fi

closing_line="$(awk 'NR > 1 && /^---$/ { print NR; exit }' "$skill")"
if [[ -z "$closing_line" ]]; then
error "$skill" "Missing YAML frontmatter closing delimiter"
return 1
fi

sed -n "2,$((closing_line - 1))p" "$skill"
}

validate_skill_frontmatter() {
local skill="$1"
local dir
local frontmatter
local required_fields=("name" "description")

dir="$(dirname "$skill")"
frontmatter="$(extract_frontmatter "$skill")" || return

for field in "${required_fields[@]}"; do
if ! grep -q "^${field}:" <<<"$frontmatter"; then
error "$skill" "Missing required '${field}' field in frontmatter"
fi
done

local declared_name
declared_name="$(sed -n 's/^name:[[:space:]]*//p' <<<"$frontmatter" | head -n 1)"
if [[ -n "$declared_name" && "$declared_name" != "$(basename "$dir")" ]]; then
error "$skill" "Frontmatter name '${declared_name}' must match directory name '$(basename "$dir")'"
fi
}

validate_skill_references() {
local skill="$1"
local dir

dir="$(dirname "$skill")"

while IFS= read -r ref; do
[[ -n "$ref" ]] || continue
if [[ ! -e "${dir}/${ref}" ]]; then
error "$skill" "Referenced file '${ref}' does not exist relative to ${dir}"
fi
done < <(grep -oE '(references|evals|test-cases|assets|scripts)/[^` )"]+' "$skill" | sort -u || true)
}

validate_evals_json() {
local evals_file="$1"
local skill_dir

skill_dir="$(basename "$(dirname "$(dirname "$evals_file")")")"

if ! python3 - "$evals_file" "$skill_dir" <<'PY'
import json
import sys
from pathlib import Path

evals_path = Path(sys.argv[1])
skill_dir = sys.argv[2]

try:
data = json.loads(evals_path.read_text())
except json.JSONDecodeError as exc:
print(f"::error file={evals_path}::Invalid JSON: {exc}")
sys.exit(1)

skill_name = data.get("skill_name")
if skill_name != skill_dir:
print(
f"::error file={evals_path}::skill_name must match directory name '{skill_dir}', found '{skill_name}'"
)
sys.exit(1)

evals = data.get("evals")
if not isinstance(evals, list) or not evals:
print(f"::error file={evals_path}::evals must be a non-empty array")
sys.exit(1)
PY
then
status=1
fi
}

main() {
shopt -s nullglob
local skill

for skill in */SKILL.md; do
validate_skill_frontmatter "$skill"
validate_skill_references "$skill"

local skill_dir
skill_dir="$(dirname "$skill")"

if [[ -f "${skill_dir}/evals/evals.json" ]]; then
validate_evals_json "${skill_dir}/evals/evals.json"
fi

info "Validated ${skill}"
done

exit "$status"
}

main "$@"
1 change: 1 addition & 0 deletions .github/workflows/link-check.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Link Check

on:
workflow_dispatch:
push:
branches: [main]
paths: ['**/*.md']
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/markdown-lint.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Markdown Lint

on:
workflow_dispatch:
push:
branches: [main]
paths: ['**/*.md']
Expand Down
53 changes: 20 additions & 33 deletions .github/workflows/validate-skills.yml
Original file line number Diff line number Diff line change
@@ -1,47 +1,34 @@
name: Validate Skills

on:
workflow_dispatch:
push:
branches: [main]
paths: ['**/SKILL.md']
paths:
- '**/SKILL.md'
- '**/references/**'
- '**/evals/**'
- '**/test-cases/**'
- '.github/scripts/validate-skills.sh'
- '.github/workflows/validate-skills.yml'
pull_request:
branches: [main]
paths: ['**/SKILL.md']
paths:
- '**/SKILL.md'
- '**/references/**'
- '**/evals/**'
- '**/test-cases/**'
- '.github/scripts/validate-skills.sh'
- '.github/workflows/validate-skills.yml'

permissions:
contents: read

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Check SKILL.md frontmatter
run: |
status=0
for skill in */SKILL.md; do
dir=$(dirname "$skill")

# Check for YAML frontmatter delimiters
if ! head -1 "$skill" | grep -q '^---$'; then
echo "::error file=$skill::Missing YAML frontmatter (no opening ---)"
status=1
continue
fi

# Extract frontmatter
frontmatter=$(sed -n '1,/^---$/{ /^---$/d; p; }' "$skill" | tail -n +1)

# Check required fields
if ! echo "$frontmatter" | grep -q '^name:'; then
echo "::error file=$skill::Missing required 'name' field in frontmatter"
status=1
fi
if ! echo "$frontmatter" | grep -q '^description:'; then
echo "::error file=$skill::Missing required 'description' field in frontmatter"
status=1
fi

if [ $status -eq 0 ]; then
echo "✓ $skill"
fi
done
exit $status
- name: Validate skills and bundled fixtures
run: bash .github/scripts/validate-skills.sh
Loading