scripts/quick_validate.py
scripts/quick_validate.pyBrowse 9 files
968 tokens
4,249 bytes
Token encoding: o200k_base
Snapshot 18d7ace
← Back to SKILL.md
1#!/usr/bin/env python32"""3Quick validation script for skills - minimal version4"""5 6import re7import sys8from pathlib import Path9 10import yaml11 12MAX_SKILL_NAME_LENGTH = 6413 14 15def validate_skill(skill_path):16 """Basic validation of a skill"""17 skill_path = Path(skill_path)18 19 skill_md = skill_path / "SKILL.md"20 if not skill_md.exists():21 return False, "SKILL.md not found"22 23 content = skill_md.read_text()24 if not content.startswith("---"):25 return False, "No YAML frontmatter found"26 27 match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)28 if not match:29 return False, "Invalid frontmatter format"30 31 frontmatter_text = match.group(1)32 33 try:34 frontmatter = yaml.safe_load(frontmatter_text)35 if not isinstance(frontmatter, dict):36 return False, "Frontmatter must be a YAML dictionary"37 except yaml.YAMLError as e:38 return False, f"Invalid YAML in frontmatter: {e}"39 40 allowed_properties = {"name", "description", "license", "allowed-tools", "metadata"}41 42 unexpected_keys = set(frontmatter.keys()) - allowed_properties43 if unexpected_keys:44 allowed = ", ".join(sorted(allowed_properties))45 unexpected = ", ".join(sorted(unexpected_keys))46 return (47 False,48 f"Unexpected key(s) in SKILL.md frontmatter: {unexpected}. Allowed properties are: {allowed}",49 )50 51 if "name" not in frontmatter:52 return False, "Missing 'name' in frontmatter"53 if "description" not in frontmatter:54 return False, "Missing 'description' in frontmatter"55 56 name = frontmatter.get("name", "")57 if not isinstance(name, str):58 return False, f"Name must be a string, got {type(name).__name__}"59 name = name.strip()60 if name:61 if not re.match(r"^[a-z0-9-]+$", name):62 return (63 False,64 f"Name '{name}' should be hyphen-case (lowercase letters, digits, and hyphens only)",65 )66 if name.startswith("-") or name.endswith("-") or "--" in name:67 return (68 False,69 f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens",70 )71 if len(name) > MAX_SKILL_NAME_LENGTH:72 return (73 False,74 f"Name is too long ({len(name)} characters). "75 f"Maximum is {MAX_SKILL_NAME_LENGTH} characters.",76 )77 78 description = frontmatter.get("description", "")79 if not isinstance(description, str):80 return False, f"Description must be a string, got {type(description).__name__}"81 description = description.strip()82 if description.startswith("[TODO:"):83 return False, "Description contains an unfinished TODO placeholder"84 if description:85 if "<" in description or ">" in description:86 return False, "Description cannot contain angle brackets (< or >)"87 if len(description) > 1024:88 return (89 False,90 f"Description is too long ({len(description)} characters). Maximum is 1024 characters.",91 )92 93 body = content[match.end() :]94 fence_marker = None95 fence_length = 096 for line in body.splitlines():97 fence = re.match(r"^[ \t]*(?:(?:[-+*]|\d+[.)])[ \t]+)?(`{3,}|~{3,})(.*)$", line)98 if fence:99 marker = fence.group(1)100 if fence_marker is None:101 fence_marker = marker[0]102 fence_length = len(marker)103 elif (104 marker[0] == fence_marker105 and len(marker) >= fence_length106 and not fence.group(2).strip()107 ):108 fence_marker = None109 fence_length = 0110 continue111 112 if fence_marker is None and re.fullmatch(113 r"[ ]{0,3}\[TODO:[^\n]*\][ \t]*", line114 ):115 return False, "Skill instructions contain an unfinished TODO placeholder"116 117 return True, "Skill is valid!"118 119 120if __name__ == "__main__":121 if len(sys.argv) != 2:122 print("Usage: python quick_validate.py <skill_directory>")123 sys.exit(1)124 125 valid, message = validate_skill(sys.argv[1])126 print(message)127 sys.exit(0 if valid else 1)128 