skref 1.1.0

Reference implementation of the Agent Skills tooling: validate, read-properties, and to-prompt for SKILL.md skills
Documentation
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

    # Source-build fallback: only runs when no matching prebuilt binary was
    # installed (e.g. the action is referenced from a ref with no release yet).
    - 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
        # hashFiles() can only read inside GITHUB_WORKSPACE, but the action's
        # Cargo.lock lives under github.action_path (outside it). The built
        # skref version is fixed per action ref, so key on OS + ref instead.
        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