Skip to content

ci: lint skill frontmatter against Skills-API rules (DAT-563 follow-up) #26

ci: lint skill frontmatter against Skills-API rules (DAT-563 follow-up)

ci: lint skill frontmatter against Skills-API rules (DAT-563 follow-up) #26

Workflow file for this run

name: Validate plugin
# Catches what the marketplace-root check alone misses: `claude plugin validate .`
# validates ONLY the marketplace manifest and never descends into skills, so a
# broken SKILL.md frontmatter passes that check. Validating each plugin.json is a
# separate invocation — this workflow runs both, with --strict (warnings = failure).
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Claude Code CLI
# Pin a version (e.g. @anthropic-ai/claude-code@2.1.161) for reproducible
# builds; latest keeps the validator current with new --strict checks.
run: npm install -g @anthropic-ai/claude-code
- name: Show CLI version
run: claude --version
- name: Validate marketplace manifest
run: claude plugin validate . --strict
- name: Validate every plugin manifest (schema + all skills)
shell: bash
run: |
set -uo pipefail
status=0
while IFS= read -r manifest; do
echo "::group::validate $manifest"
if ! claude plugin validate "$manifest" --strict; then
status=1
echo "::error file=$manifest::plugin validation failed (see log group above)"
fi
echo "::endgroup::"
done < <(find . -path '*/.claude-plugin/plugin.json' -not -path '*/node_modules/*' | sort)
exit "$status"
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Validate Cursor plugin manifest (schema + assets)
# The Claude CLI only validates Claude manifests; this guards the Cursor
# manifest against schemas/cursor-plugin.schema.json and checks its assets.
shell: bash
run: |
set -euo pipefail
python -m pip install --quiet jsonschema
python scripts/validate_cursor_manifest.py
- name: Validate skill frontmatter (Skills-API rules)
# `claude plugin validate` only truncates over-long descriptions; the
# claude.ai / Cowork "Add marketplace" sync HARD-REJECTS them (and any XML
# tags) and fails the whole sync. This lints what the CLI misses: per skill,
# description non-empty + <=1024 chars/bytes + no XML tags, and name <=64,
# kebab-case, no reserved words. See DAT-563.
shell: bash
run: |
set -euo pipefail
python -m pip install --quiet pyyaml
python scripts/validate_skill_frontmatter.py
- name: Check manifest versions agree
shell: bash
# Assumes the current single-plugin marketplace (plugins[0], plugin.json
# at the repo root). Generalise this if a second plugin is ever added.
run: |
set -euo pipefail
meta=$(jq -r '.metadata.version' .claude-plugin/marketplace.json)
entry=$(jq -r '.plugins[0].version' .claude-plugin/marketplace.json)
pj=$(jq -r '.version' .claude-plugin/plugin.json)
cursor=$(jq -r '.version' .cursor-plugin/plugin.json)
echo "metadata=$meta marketplace-entry=$entry plugin.json=$pj cursor=$cursor"
if [ "$meta" != "$entry" ] || [ "$meta" != "$pj" ] || [ "$meta" != "$cursor" ]; then
echo "::error::version drift across manifests: metadata=$meta entry=$entry plugin.json=$pj cursor=$cursor"
exit 1
fi