Files
2026-10-27 09:12:40 +08:00

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()