55 branches : [main]
66 paths :
77 - ' skills/**'
8- - ' scripts/**'
98 - ' .github/workflows/validate-skills.yml'
109 pull_request :
1110 branches : [main]
1211 paths :
1312 - ' skills/**'
14- - ' scripts/**'
1513 - ' .github/workflows/validate-skills.yml'
1614 workflow_dispatch :
1715
@@ -21,19 +19,53 @@ jobs:
2119 steps :
2220 - uses : actions/checkout@v4
2321
24- - name : Set up Python
25- uses : actions/setup-python@v5
26- with :
27- python-version : ' 3.11'
22+ - name : Validate skills
23+ run : |
24+ python3 - <<'EOF'
25+ import sys, re, yaml
26+ from pathlib import Path
2827
29- - name : Install dependencies
30- run : pip install pyyaml
28+ ALLOWED = {'name', 'description', 'license', 'allowed-tools', 'metadata', 'compatibility'}
29+ skills = ['skills/harbor-task-creator', 'skills/harbor-adapter-creator', 'skills/harbor-cli']
30+ failed = False
3131
32- - name : Validate harbor-task-creator
33- run : python scripts/quick_validate.py ./skills/harbor-task-creator
32+ for skill_path in skills:
33+ path = Path(skill_path)
34+ skill_md = path / 'SKILL.md'
35+ if not skill_md.exists():
36+ print(f'FAIL {skill_path}: SKILL.md not found'); failed = True; continue
37+ content = skill_md.read_text()
38+ if not content.startswith('---'):
39+ print(f'FAIL {skill_path}: no YAML frontmatter'); failed = True; continue
40+ match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
41+ if not match:
42+ print(f'FAIL {skill_path}: invalid frontmatter'); failed = True; continue
43+ try:
44+ fm = yaml.safe_load(match.group(1))
45+ except yaml.YAMLError as e:
46+ print(f'FAIL {skill_path}: {e}'); failed = True; continue
47+ if not isinstance(fm, dict):
48+ print(f'FAIL {skill_path}: frontmatter must be a dict'); failed = True; continue
49+ extra = set(fm.keys()) - ALLOWED
50+ if extra:
51+ print(f'FAIL {skill_path}: unexpected keys: {extra}'); failed = True; continue
52+ for field in ('name', 'description'):
53+ if field not in fm:
54+ print(f'FAIL {skill_path}: missing {field!r}'); failed = True
55+ name = str(fm.get('name', '')).strip()
56+ if name:
57+ if not re.match(r'^[a-z0-9-]+$', name) or name.startswith('-') or name.endswith('-') or '--' in name:
58+ print(f'FAIL {skill_path}: invalid name {name!r}'); failed = True; continue
59+ if len(name) > 64:
60+ print(f'FAIL {skill_path}: name too long'); failed = True; continue
61+ desc = str(fm.get('description', '')).strip()
62+ if desc:
63+ if '<' in desc or '>' in desc:
64+ print(f'FAIL {skill_path}: description contains angle brackets'); failed = True; continue
65+ if len(desc) > 1024:
66+ print(f'FAIL {skill_path}: description too long'); failed = True; continue
67+ if not failed:
68+ print(f'OK {skill_path}')
3469
35- - name : Validate harbor-adapter-creator
36- run : python scripts/quick_validate.py ./skills/harbor-adapter-creator
37-
38- - name : Validate harbor-cli
39- run : python scripts/quick_validate.py ./skills/harbor-cli
70+ sys.exit(1 if failed else 0)
71+ EOF
0 commit comments