60 lines
2.8 KiB
Python
60 lines
2.8 KiB
Python
"""Validate generated skill structure, local/source links and XML snippet syntax."""
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from xml.etree import ElementTree as ET
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
SKILLS = ROOT / 'skills'
|
|
|
|
|
|
def main():
|
|
subprocess.run([sys.executable, str(SKILLS / 'scripts/build_skills.py'), '--check'], check=True)
|
|
manifest = json.loads((SKILLS / 'catalog.json').read_text(encoding='utf-8'))['skills']
|
|
errors = []
|
|
snippets = 0
|
|
for name in manifest:
|
|
folder = SKILLS / name
|
|
text = (folder / 'SKILL.md').read_text(encoding='utf-8')
|
|
front = re.match(r'---\nname: ([a-z0-9-]+)\ndescription: (.+)\n---\n', text)
|
|
if not front or front[1] != name or len(name) > 64:
|
|
errors.append(f'{name}: invalid frontmatter')
|
|
continue
|
|
description = json.loads(front[2])
|
|
if not 1 <= len(description) <= 1024 or '<' in description or '>' in description:
|
|
errors.append(f'{name}: invalid description')
|
|
agent = folder / 'agents/openai.yaml'
|
|
if f'${name}' not in agent.read_text(encoding='utf-8'):
|
|
errors.append(f'{name}: missing invocation prompt')
|
|
for path in folder.rglob('*.md'):
|
|
content = path.read_text(encoding='utf-8')
|
|
for target in re.findall(r'\]\(([^)]+)\)', content):
|
|
if target.startswith('https://github.com/HandyOrg/HandyControl/blob/master/'):
|
|
local = ROOT / target.split('/blob/master/', 1)[1]
|
|
if not local.is_file():
|
|
errors.append(f'{path}: missing source {target}')
|
|
elif '://' not in target and not target.startswith('#'):
|
|
local = path.parent / target.split('#')[0]
|
|
if not local.is_file():
|
|
errors.append(f'{path}: missing reference {target}')
|
|
for snippet in re.findall(r'```xml\n(.*?)```', content, re.S):
|
|
# Namespace declaration is intentionally just an attribute, not an element.
|
|
if snippet.lstrip().startswith('xmlns:'):
|
|
continue
|
|
try:
|
|
ET.fromstring('<Root xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" '
|
|
'xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" '
|
|
'xmlns:hc="https://handyorg.github.io/handycontrol">' + snippet + '</Root>')
|
|
snippets += 1
|
|
except ET.ParseError as exc:
|
|
errors.append(f'{path}: invalid XML: {exc}')
|
|
if errors:
|
|
raise SystemExit('\n'.join(errors))
|
|
print(f'Validated {len(manifest)} skills; {snippets} XML fragments; all local/source links resolve')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|