chore(claude-plugin): update marketplace.json source field #7
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: Validate Skill | |
| on: | |
| pull_request: | |
| paths: | |
| - 'SKILL.md' | |
| - 'references/**/*.md' | |
| - '.claude-plugin/**' | |
| push: | |
| branches: [master, main] | |
| paths: | |
| - 'SKILL.md' | |
| - 'references/**/*.md' | |
| - '.claude-plugin/**' | |
| workflow_dispatch: | |
| jobs: | |
| validate: | |
| name: Validate Skill Files | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.x' | |
| - name: Install Dependencies | |
| run: pip install pyyaml | |
| - name: Check SKILL.md Frontmatter | |
| run: | | |
| python3 << 'EOF' | |
| import yaml | |
| import sys | |
| import re | |
| print("🔍 Validating SKILL.md frontmatter...") | |
| with open('SKILL.md', 'r') as f: | |
| content = f.read() | |
| if not content.startswith('---'): | |
| print("❌ ERROR: No frontmatter found") | |
| sys.exit(1) | |
| parts = content.split('---', 2) | |
| if len(parts) < 3: | |
| print("❌ ERROR: Invalid frontmatter format") | |
| sys.exit(1) | |
| frontmatter = yaml.safe_load(parts[1]) | |
| if 'name' not in frontmatter or 'description' not in frontmatter: | |
| print("❌ ERROR: Missing required fields") | |
| sys.exit(1) | |
| allowed = {'name', 'description'} | |
| extra = set(frontmatter.keys()) - allowed | |
| if extra: | |
| print(f"❌ ERROR: Extra fields: {extra}") | |
| print(" Only 'name' and 'description' allowed") | |
| sys.exit(1) | |
| name = frontmatter['name'] | |
| if not re.match(r'^[a-zA-Z0-9-]+$', name): | |
| print(f"❌ ERROR: Invalid name: {name}") | |
| sys.exit(1) | |
| desc_len = len(frontmatter['description']) | |
| if desc_len > 1024: | |
| print(f"❌ ERROR: Description too long: {desc_len} chars") | |
| sys.exit(1) | |
| print(f"✅ Frontmatter valid ({desc_len} chars)") | |
| EOF | |
| - name: Check File Size | |
| run: | | |
| LINES=$(wc -l < SKILL.md) | |
| WORDS=$(wc -w < SKILL.md) | |
| echo "📊 SKILL.md: $LINES lines, $WORDS words" | |
| if [ $LINES -gt 500 ]; then | |
| echo "⚠️ WARNING: $LINES lines (guideline: <500)" | |
| else | |
| echo "✅ Size OK" | |
| fi | |
| - name: Validate marketplace.json | |
| run: | | |
| python3 << 'EOF' | |
| import json | |
| import sys | |
| import re | |
| print("🔍 Validating marketplace.json...") | |
| with open('.claude-plugin/marketplace.json', 'r') as f: | |
| marketplace = json.load(f) | |
| # Validate marketplace-level fields | |
| required_marketplace = ['name', 'owner', 'version', 'plugins'] | |
| missing = [f for f in required_marketplace if f not in marketplace] | |
| if missing: | |
| print(f"❌ ERROR: Missing marketplace fields: {missing}") | |
| sys.exit(1) | |
| # Validate owner structure | |
| if 'name' not in marketplace['owner'] or 'email' not in marketplace['owner']: | |
| print("❌ ERROR: owner must have 'name' and 'email'") | |
| sys.exit(1) | |
| # Validate version format | |
| version = marketplace['version'] | |
| if not re.match(r'^\d+\.\d+\.\d+$', version): | |
| print(f"❌ ERROR: Invalid marketplace version: {version}") | |
| sys.exit(1) | |
| # Validate plugins array | |
| if not isinstance(marketplace['plugins'], list) or len(marketplace['plugins']) == 0: | |
| print("❌ ERROR: 'plugins' must be a non-empty array") | |
| sys.exit(1) | |
| # Validate each plugin | |
| for idx, plugin in enumerate(marketplace['plugins']): | |
| required_plugin = ['name', 'version', 'description', 'source'] | |
| missing = [f for f in required_plugin if f not in plugin] | |
| if missing: | |
| print(f"❌ ERROR: Plugin {idx} missing fields: {missing}") | |
| sys.exit(1) | |
| # Validate plugin version | |
| plugin_version = plugin['version'] | |
| if not re.match(r'^\d+\.\d+\.\d+$', plugin_version): | |
| print(f"❌ ERROR: Invalid plugin version: {plugin_version}") | |
| sys.exit(1) | |
| # Validate source structure | |
| source = plugin['source'] | |
| if 'type' not in source: | |
| print(f"❌ ERROR: Plugin {idx} source missing 'type'") | |
| sys.exit(1) | |
| source_type = source['type'] | |
| if source_type == 'github': | |
| if 'repo' not in source or 'ref' not in source: | |
| print(f"❌ ERROR: GitHub source must have 'repo' and 'ref'") | |
| sys.exit(1) | |
| # Validate repo format (owner/repo) | |
| if not re.match(r'^[^/]+/[^/]+$', source['repo']): | |
| print(f"❌ ERROR: Invalid repo format: {source['repo']}") | |
| sys.exit(1) | |
| elif source_type == 'local': | |
| if 'path' not in source: | |
| print(f"❌ ERROR: Local source must have 'path'") | |
| sys.exit(1) | |
| else: | |
| print(f"❌ ERROR: Unsupported source type: {source_type}") | |
| sys.exit(1) | |
| # Validate version sync | |
| if plugin_version != version: | |
| print(f"⚠️ WARNING: Plugin version {plugin_version} != marketplace version {version}") | |
| # Validate ref sync (if github source) | |
| if source_type == 'github': | |
| expected_ref = f"v{version}" | |
| if source['ref'] != expected_ref: | |
| print(f"⚠️ WARNING: Source ref {source['ref']} != expected {expected_ref}") | |
| print(f"✅ marketplace.json valid (v{version}, {len(marketplace['plugins'])} plugin(s))") | |
| EOF | |
| - name: Check for Broken Links | |
| run: | | |
| echo "🔍 Checking internal links..." | |
| if grep -oP '\[.*?\]\(references/.*?\.md.*?\)' SKILL.md references/*.md 2>/dev/null | \ | |
| sed 's/.*(//' | sed 's/).*//' | sed 's/#.*//' | \ | |
| while read -r link; do | |
| if [ ! -f "$link" ]; then | |
| echo "❌ ERROR: Broken link: $link" | |
| exit 1 | |
| fi | |
| done | |
| then | |
| echo "✅ No broken links" | |
| fi | |
| - name: Lint Markdown | |
| uses: DavidAnson/markdownlint-cli2-action@v16 | |
| with: | |
| globs: | | |
| SKILL.md | |
| references/**/*.md | |
| README.md | |
| CONTRIBUTING.md | |
| continue-on-error: true | |
| - name: Summary | |
| if: success() | |
| run: | | |
| echo "## ✅ Validation Passed" >> $GITHUB_STEP_SUMMARY | |
| echo "All skill validation checks passed." >> $GITHUB_STEP_SUMMARY |