Skip to content

fix: use ASCII hyphens in Safe Destroy Protocol guidance (#45) #98

fix: use ASCII hyphens in Safe Destroy Protocol guidance (#45)

fix: use ASCII hyphens in Safe Destroy Protocol guidance (#45) #98

Workflow file for this run

name: Validate Skill
on:
pull_request:
paths:
- 'skills/**'
- '.claude-plugin/**'
- '.codex-plugin/**'
- 'POWER.md'
- 'mcp.json'
- '.github/workflows/**'
- '.github/release/**'
push:
branches: [master, main]
paths:
- 'skills/**'
- '.claude-plugin/**'
- '.codex-plugin/**'
- 'POWER.md'
- 'mcp.json'
- '.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 POWER.md (Kiro) Sync
run: |
set -e
echo "🔍 Regenerating POWER.md and checking it is committed in sync..."
node .github/release/build-power.js --check
python3 << 'EOF'
import json, re, sys
import yaml
content = open('POWER.md').read()
if not content.startswith('---'):
print("❌ ERROR: POWER.md has no frontmatter")
sys.exit(1)
fm = yaml.safe_load(content.split('---', 2)[1])
required = {'name', 'displayName', 'description', 'keywords',
'author', 'version'}
missing = required - set(fm.keys())
if missing:
print(f"❌ ERROR: POWER.md frontmatter missing {missing}")
sys.exit(1)
if not isinstance(fm['keywords'], list) or not fm['keywords']:
print("❌ ERROR: POWER.md keywords must be a non-empty list")
sys.exit(1)
if not re.match(r'^\d+\.\d+\.\d+$', str(fm['version'])):
print(f"❌ ERROR: POWER.md version not semver: {fm['version']!r}")
sys.exit(1)
mcp = json.load(open('mcp.json'))
servers = list((mcp.get('mcpServers') or {}).keys())
if not servers:
print("❌ ERROR: mcp.json has no mcpServers")
sys.exit(1)
for s in servers:
if s not in content:
print(f"❌ ERROR: mcp.json server {s!r} not referenced "
f"in POWER.md")
sys.exit(1)
print(f"✅ POWER.md in sync (v{fm['version']}, "
f"{len(fm['keywords'])} keywords, MCP: {', '.join(servers)})")
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