"""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('' + snippet + '') 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()