name: "skref — validate Agent Skills"
description: "Validate Agent Skill (SKILL.md) directories in a repository with skref."
author: "Alephic AI"
branding:
icon: "check-circle"
color: "purple"
inputs:
path:
description: "Directory to scan recursively for skills (directories containing a SKILL.md)."
required: false
default: "."
fail-on-error:
description: "Fail the action if any skill is invalid."
required: false
default: "true"
to-prompt:
description: "If 'true', also print the <available_skills> XML block for all valid skills."
required: false
default: "false"
allow-claude-fields:
description: "If 'true', also accept Claude Code's extra frontmatter fields (model, when_to_use, argument-hint, hooks, …) during validation."
required: false
default: "false"
from-source:
description: "If 'true', always build skref from this action's source instead of downloading a prebuilt release binary. Use to test unreleased changes (this repo's own demo workflow sets it)."
required: false
default: "false"
runs:
using: "composite"
steps:
- name: Install skref (prebuilt binary)
id: install
shell: bash
env:
FROM_SOURCE: ${{ inputs.from-source }}
run: |
set -uo pipefail
# Opt-in: build from this checkout's source instead of a released
# binary (e.g. to exercise unreleased changes on a PR).
if [ "$FROM_SOURCE" = "true" ]; then
echo "Building skref from source (from-source=true)."
echo "method=source" >> "$GITHUB_OUTPUT"
exit 0
fi
# Pin to the binary built from this same action ref: the version in the
# bundled Cargo.toml is the one published by the matching "vX.Y.Z" tag.
# Releases are always tagged "vX.Y.Z" (see AGENTS.md). Anchor on the `=` so
# a future "version-..." key in Cargo.toml can't be matched by accident.
VERSION="$(grep -m1 '^version[[:space:]]*=' "$GITHUB_ACTION_PATH/Cargo.toml" | sed -E 's/.*"([^"]+)".*/\1/')"
if [ -z "$VERSION" ]; then
echo "::warning::Could not read version from $GITHUB_ACTION_PATH/Cargo.toml; building from source."
echo "method=source" >> "$GITHUB_OUTPUT"
exit 0
fi
TAG="v$VERSION"
# Map the runner platform to a dist target triple + archive extension.
case "$RUNNER_OS $RUNNER_ARCH" in
"Linux X64") TRIPLE=x86_64-unknown-linux-gnu; EXT=tar.xz; BIN=skref ;;
"Linux ARM64") TRIPLE=aarch64-unknown-linux-gnu; EXT=tar.xz; BIN=skref ;;
"macOS X64") TRIPLE=x86_64-apple-darwin; EXT=tar.xz; BIN=skref ;;
"macOS ARM64") TRIPLE=aarch64-apple-darwin; EXT=tar.xz; BIN=skref ;;
"Windows X64") TRIPLE=x86_64-pc-windows-msvc; EXT=zip; BIN=skref.exe ;;
*) TRIPLE="" ;;
esac
if [ -z "$TRIPLE" ]; then
echo "::warning::No prebuilt skref binary for '$RUNNER_OS $RUNNER_ARCH'; building from source."
echo "method=source" >> "$GITHUB_OUTPUT"
exit 0
fi
mkdir -p "$HOME/.skref-bin/bin"
url="https://github.com/alephic-ai/skref/releases/download/$TAG/skref-$TRIPLE.$EXT"
tmp="$(mktemp -d)"
echo "Downloading $url"
# &&-chain so a 404 short-circuits cleanly (set -e is unreliable inside
# an `if (...)` subshell on bash 3.2). modern GNU tar (Linux .tar.xz) and
# bsdtar (macOS/Windows .tar.xz and .zip) both auto-detect from -xf.
# dist nests the binary under a "skref-<triple>/" prefix dir, so locate it
# by name to stay agnostic to the exact archive layout.
if curl --proto '=https' --tlsv1.2 -fsSL "$url" -o "$tmp/skref.$EXT" \
&& tar -xf "$tmp/skref.$EXT" -C "$tmp" \
&& bin_path="$(find "$tmp" -type f -name "$BIN" | head -n1)" \
&& [ -n "$bin_path" ] \
&& mv "$bin_path" "$HOME/.skref-bin/bin/$BIN" \
&& chmod +x "$HOME/.skref-bin/bin/$BIN"; then
echo "method=prebuilt" >> "$GITHUB_OUTPUT"
echo "Installed prebuilt skref $VERSION ($TRIPLE) from tag $TAG."
else
echo "::warning::Could not install prebuilt skref $TAG ($TRIPLE); building from source."
echo "method=source" >> "$GITHUB_OUTPUT"
fi
- name: Install Rust toolchain
if: ${{ steps.install.outputs.method == 'source' }}
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo build
if: ${{ steps.install.outputs.method == 'source' }}
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
${{ github.action_path }}/target
key: skref-${{ runner.os }}-${{ github.action_ref || 'local' }}
- name: Build and install skref
if: ${{ steps.install.outputs.method == 'source' }}
shell: bash
run: cargo install --path "$GITHUB_ACTION_PATH" --locked --root "$HOME/.skref-bin"
- name: Validate skills
shell: bash
env:
SCAN_PATH: ${{ inputs.path }}
FAIL_ON_ERROR: ${{ inputs.fail-on-error }}
TO_PROMPT: ${{ inputs.to-prompt }}
ALLOW_CLAUDE_FIELDS: ${{ inputs.allow-claude-fields }}
run: |
export PATH="$HOME/.skref-bin/bin:$PATH"
set -uo pipefail
# Optional validate flags, collected as an array so empty stays safe.
validate_args=()
if [ "$ALLOW_CLAUDE_FIELDS" = "true" ]; then
validate_args+=(--allow-claude-fields)
fi
# Discover every directory that contains a SKILL.md / skill.md.
mapfile -t skill_files < <(find "$SCAN_PATH" -iname 'SKILL.md' -type f | sort)
if [ "${#skill_files[@]}" -eq 0 ]; then
echo "::warning::No SKILL.md files found under '$SCAN_PATH'."
exit 0
fi
echo "Found ${#skill_files[@]} skill(s) under '$SCAN_PATH'."
failures=0
valid_dirs=()
for f in "${skill_files[@]}"; do
dir="$(dirname "$f")"
# An empty array expands to zero arguments here (this step already
# requires bash 4+ for `mapfile` above).
if skref validate "$dir" "${validate_args[@]}"; then
valid_dirs+=("$dir")
else
echo "::error file=$f::Skill validation failed for $dir"
failures=$((failures + 1))
fi
done
echo
echo "Summary: $(( ${#skill_files[@]} - failures )) valid, $failures invalid."
if [ "$TO_PROMPT" = "true" ] && [ "${#valid_dirs[@]}" -gt 0 ]; then
echo
echo "<available_skills> block for valid skills:"
skref to-prompt "${valid_dirs[@]}"
fi
if [ "$failures" -gt 0 ] && [ "$FAIL_ON_ERROR" = "true" ]; then
exit 1
fi