feat(create-gh-pages-site): support Spectator specs #83
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Skill Lint | |
| on: | |
| pull_request: | |
| # Anchored so the push trigger below reuses one list. Actions supports YAML | |
| # anchors and aliases; only merge keys (`<<:`) remain unsupported. | |
| paths: &skill_lint_paths | |
| - "skills/**/SKILL.md" | |
| - "skills/**/package.json" | |
| - "skills/**/test/**" | |
| - "skills/**/*.yaml" | |
| - "skills/**/*.yml" | |
| - "skills/**/eval.yaml" | |
| # Reference guidance and shipped scripts are part of the skill's | |
| # behaviour, so a change to either must be linted. Without these, a PR | |
| # that edits only references/ receives no CI at all: the skill still | |
| # lints clean by luck because nothing in the filter changed, and a broken | |
| # cross-link or a deleted reference reaches main unchecked. | |
| - "skills/**/references/**" | |
| - "skills/**/scripts/**" | |
| - "docs/specs/git-tidy/**" | |
| - "site/src/content/skills/**" | |
| - "site/public/images/**" | |
| - "site/package.json" | |
| - "site/package-lock.json" | |
| - ".github/tools/vally/**" | |
| - "README.md" | |
| - "marketplace.json" | |
| - "plugin.json" | |
| - ".agents/plugins/**" | |
| - ".codex-plugin/**" | |
| - ".claude-plugin/**" | |
| - ".cursor-plugin/**" | |
| - "gemini-extension.json" | |
| - "test/**" | |
| - "mcp.json" | |
| - ".mcp.json" | |
| - ".lsp.json" | |
| - "settings.json" | |
| - "hooks/**" | |
| - "agents/**" | |
| - "commands/**" | |
| - "workflows/**" | |
| - "output-styles/**" | |
| - "themes/**" | |
| - "monitors/**" | |
| - "bin/**" | |
| - "com.github.copilot/**" | |
| # Workflow policy tests inspect every workflow and must run whenever any | |
| # workflow changes. | |
| - ".github/workflows/**" | |
| push: | |
| branches: [main] | |
| paths: *skill_lint_paths | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| lint: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| # Node 24, not 22. Vally declares `npm>=11.11.1`, and Node 22 still | |
| # bundles npm 10.x, which makes every install log an EBADENGINE warning. | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: "24" | |
| cache: npm | |
| cache-dependency-path: "**/package-lock.json" | |
| # Use the locked CI toolchain instead of a floating global install. | |
| - name: Install Vally CLI | |
| run: | | |
| npm ci --prefix .github/tools/vally --ignore-scripts | |
| echo "$PWD/.github/tools/vally/node_modules/.bin" >> "$GITHUB_PATH" | |
| - name: Validate distribution manifests | |
| run: node --test test/*.test.mjs | |
| - name: Determine skills to lint | |
| id: skills | |
| env: | |
| # Routed through env for the same reason the steps below do it: no | |
| # workflow context value is interpolated straight into a shell script. | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| run: | | |
| set -euo pipefail | |
| if [ "$EVENT_NAME" = "pull_request" ]; then | |
| # Lint only skills with changes in this PR. Keep this list in step | |
| # with the `paths` filter above: a path that triggers the workflow | |
| # but is missing here selects no skill, so the job runs and lints | |
| # nothing while still reporting success. | |
| CHANGED=$(git diff --name-only "$BASE_SHA" HEAD) | |
| if printf '%s\n' "$CHANGED" \ | |
| | grep -Eq '^(README\.md|marketplace\.json|plugin\.json)$'; then | |
| DIRS=$(find skills -maxdepth 2 -name "SKILL.md" -printf '%h\n' | sort -u) | |
| else | |
| DIRS=$(printf '%s\n' "$CHANGED" \ | |
| | grep -E '^skills/[^/]+/(SKILL\.md|package\.json|.*\.ya?ml|evals/|test/|references/|scripts/)' \ | |
| | sed 's|^\(skills/[^/]*\)/.*|\1|' | sort -u || true) | |
| if printf '%s\n' "$CHANGED" | grep -Eq '^docs/specs/git-tidy/'; then | |
| DIRS=$(printf '%s\n%s\n' "$DIRS" "skills/git-tidy" \ | |
| | sed '/^$/d' | sort -u) | |
| fi | |
| fi | |
| else | |
| # Push to main or manual dispatch: lint all skills | |
| DIRS=$(find skills -maxdepth 2 -name "SKILL.md" -printf '%h\n' | sort -u) | |
| fi | |
| while IFS= read -r dir; do | |
| [ -z "$dir" ] && continue | |
| if [[ ! "$dir" =~ ^skills/[a-z0-9]+(-[a-z0-9]+)*$ ]]; then | |
| echo "::error::Refusing non-canonical skill directory." | |
| exit 1 | |
| fi | |
| done <<< "$DIRS" | |
| if [ -z "$DIRS" ]; then | |
| printf 'dirs=\n' >> "$GITHUB_OUTPUT" | |
| echo "No skill directories to lint" | |
| else | |
| { | |
| echo "dirs<<EOF" | |
| echo "$DIRS" | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| echo "Will lint: $DIRS" | |
| fi | |
| - name: Lint skills (spec-compliance) | |
| if: steps.skills.outputs.dirs != '' | |
| env: | |
| SKILL_DIRS: ${{ steps.skills.outputs.dirs }} | |
| run: | | |
| FAILED=0 | |
| while IFS= read -r dir; do | |
| echo "::group::Linting $dir" | |
| if ! vally lint "$dir"; then | |
| FAILED=1 | |
| fi | |
| echo "::endgroup::" | |
| done <<< "$SKILL_DIRS" | |
| exit $FAILED | |
| - name: Validate eval specs | |
| if: steps.skills.outputs.dirs != '' | |
| env: | |
| SKILL_DIRS: ${{ steps.skills.outputs.dirs }} | |
| run: | | |
| while IFS= read -r dir; do | |
| while IFS= read -r -d '' spec; do | |
| echo "Validating $spec" | |
| vally lint --eval-spec "$spec" --strict | |
| done < <( | |
| find "$dir" -name "eval.yaml" -path "*/evals/*" -print0 2>/dev/null | |
| ) | |
| done <<< "$SKILL_DIRS" | |
| - name: Run deterministic skill tests | |
| if: steps.skills.outputs.dirs != '' | |
| env: | |
| SKILL_DIRS: ${{ steps.skills.outputs.dirs }} | |
| run: | | |
| while IFS= read -r dir; do | |
| if [ -f "$dir/package.json" ] && npm pkg get scripts.test --prefix "$dir" | grep -qv '{}'; then | |
| if [ "$(npm pkg get dependencies --prefix "$dir")" != '{}' ]; then | |
| npm ci --prefix "$dir" --omit=dev --ignore-scripts --no-audit --no-fund | |
| fi | |
| npm test --prefix "$dir" | |
| fi | |
| done <<< "$SKILL_DIRS" | |
| # Deliberately ungated. The catalog is a registration surface for every | |
| # skill, so a SKILL.md or marketplace edit can break the site even when | |
| # nothing under site/ changed. Building on every run is the cheapest way | |
| # to keep catalog integrity a hard gate rather than a nightly surprise. | |
| - name: Build catalog site | |
| run: | | |
| npm ci --prefix site --ignore-scripts | |
| npm run build --prefix site | |
| # The dns-doctor cache engine resolves platform cache paths and refuses | |
| # redirected paths, and both behave differently per operating system. macOS | |
| # in particular puts os.tmpdir() under /var, which is itself a link to | |
| # /private/var, so a guard that compares a path to its own resolved form | |
| # fails there and nowhere else. That regression is only observable on this | |
| # matrix, so the suite runs on all three. | |
| dns-doctor-tests: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest, macos-latest] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: "24" | |
| cache: npm | |
| cache-dependency-path: skills/dns-doctor/package-lock.json | |
| - name: Install dependencies | |
| working-directory: skills/dns-doctor | |
| run: npm ci --ignore-scripts | |
| - name: Run tests and enforce coverage | |
| working-directory: skills/dns-doctor | |
| run: npm run test:coverage | |
| git-tidy-tests: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest, macos-latest] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: "24" | |
| cache: npm | |
| cache-dependency-path: skills/git-tidy/package-lock.json | |
| - name: Install dependencies | |
| working-directory: skills/git-tidy | |
| run: npm ci --ignore-scripts | |
| - name: Run tests and enforce coverage | |
| working-directory: skills/git-tidy | |
| run: npm run test:coverage |