mirror of
https://github.com/antonbabenko/terraform-skill.git
synced 2026-09-18 20:07:05 +08:00
cc78c5a434
Co-authored-by: Anton Babenko <anton@antonbabenko.com>
185 lines
5.8 KiB
YAML
185 lines
5.8 KiB
YAML
name: Validate Skill
|
||
|
||
on:
|
||
pull_request:
|
||
paths:
|
||
- 'skills/**'
|
||
- '.claude-plugin/**'
|
||
- '.codex-plugin/**'
|
||
- '.github/workflows/**'
|
||
- '.github/release/**'
|
||
push:
|
||
branches: [master, main]
|
||
paths:
|
||
- 'skills/**'
|
||
- '.claude-plugin/**'
|
||
- '.codex-plugin/**'
|
||
- '.github/workflows/**'
|
||
- '.github/release/**'
|
||
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('skills/terraform-skill/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])
|
||
|
||
# Check required fields (but allow additional optional fields)
|
||
required = {'name', 'description'}
|
||
missing = required - set(frontmatter.keys())
|
||
|
||
if missing:
|
||
print(f"❌ ERROR: Missing required fields: {missing}")
|
||
sys.exit(1)
|
||
|
||
# Log optional fields if present (informational only)
|
||
optional_fields = set(frontmatter.keys()) - required
|
||
if optional_fields:
|
||
print(f"📋 Optional fields present: {optional_fields}")
|
||
|
||
if 'license' in frontmatter:
|
||
print(f" - license: {frontmatter['license']}")
|
||
|
||
if 'metadata' in frontmatter:
|
||
metadata = frontmatter['metadata']
|
||
if isinstance(metadata, dict):
|
||
if 'version' in metadata:
|
||
print(f" - metadata.version: {metadata['version']}")
|
||
if 'author' in metadata:
|
||
print(f" - metadata.author: {metadata['author']}")
|
||
|
||
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 Codex Manifest Version Sync
|
||
run: |
|
||
python3 << 'EOF'
|
||
import json
|
||
import os
|
||
import re
|
||
import sys
|
||
import yaml
|
||
|
||
SEMVER = re.compile(r'^\d+\.\d+\.\d+$')
|
||
|
||
manifest_path = '.codex-plugin/plugin.json'
|
||
if not os.path.exists(manifest_path):
|
||
print("ℹ️ No .codex-plugin/plugin.json; skipping sync check")
|
||
sys.exit(0)
|
||
|
||
with open('skills/terraform-skill/SKILL.md') as f:
|
||
fm = yaml.safe_load(f.read().split('---', 2)[1])
|
||
skill_version = (fm.get('metadata') or {}).get('version')
|
||
|
||
with open(manifest_path) as f:
|
||
manifest_version = json.load(f).get('version')
|
||
|
||
# Both must exist and be real semver, not merely equal: equality
|
||
# alone passes when both are missing/empty.
|
||
for label, value in (
|
||
('SKILL.md metadata.version', skill_version),
|
||
(f'{manifest_path} version', manifest_version),
|
||
):
|
||
if not (isinstance(value, str) and SEMVER.match(value)):
|
||
print(
|
||
f"❌ ERROR: {label} is not a valid semver: {value!r}. "
|
||
f"CI owns these; do not hand-edit (see CLAUDE.md).")
|
||
sys.exit(1)
|
||
|
||
if skill_version != manifest_version:
|
||
print(
|
||
f"❌ ERROR: version mismatch - SKILL.md metadata.version="
|
||
f"{skill_version!r} vs {manifest_path}={manifest_version!r}. "
|
||
f"CI owns these; do not hand-edit (see CLAUDE.md).")
|
||
sys.exit(1)
|
||
|
||
print(f"✅ Codex manifest version in sync ({manifest_version})")
|
||
EOF
|
||
|
||
- name: Check File Size
|
||
run: |
|
||
LINES=$(wc -l < skills/terraform-skill/SKILL.md)
|
||
WORDS=$(wc -w < skills/terraform-skill/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: Check for Broken Links
|
||
run: |
|
||
echo "🔍 Checking internal links..."
|
||
cd skills/terraform-skill
|
||
broken=0
|
||
while read -r link; do
|
||
if [ ! -f "$link" ]; then
|
||
echo "❌ ERROR: Broken link: $link"
|
||
broken=1
|
||
fi
|
||
done < <(grep -oP '\[.*?\]\(references/.*?\.md.*?\)' SKILL.md references/*.md 2>/dev/null | \
|
||
sed 's/.*(//' | sed 's/).*//' | sed 's/#.*//')
|
||
if [ "$broken" -ne 0 ]; then
|
||
exit 1
|
||
fi
|
||
echo "✅ No broken links"
|
||
|
||
- name: Lint Markdown
|
||
uses: DavidAnson/markdownlint-cli2-action@v16
|
||
with:
|
||
globs: |
|
||
skills/**/*.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
|