Skip to content

Commit 32bc9af

Browse files
committed
Update run-evals.py
1 parent 8480f54 commit 32bc9af

1 file changed

Lines changed: 103 additions & 8 deletions

File tree

scripts/run-evals.py

Lines changed: 103 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from __future__ import annotations
77

88
import json
9+
import re
910
import subprocess
1011
import sys
1112
from pathlib import Path
@@ -54,17 +55,111 @@
5455
"evals/evals.json",
5556
]
5657

58+
MAX_SKILL_NAME_LENGTH = 64
59+
60+
61+
def parse_frontmatter_minimal(frontmatter_text: str) -> tuple[dict | None, str | None]:
62+
parsed: dict[str, object] = {}
63+
64+
for raw_line in frontmatter_text.splitlines():
65+
if not raw_line.strip():
66+
continue
67+
if raw_line.startswith(" ") or raw_line.startswith("\t"):
68+
continue
69+
if ":" not in raw_line:
70+
return None, f"Unsupported frontmatter line: {raw_line}"
71+
72+
key, value = raw_line.split(":", 1)
73+
key = key.strip()
74+
value = value.strip()
75+
76+
if not key:
77+
return None, f"Invalid frontmatter key in line: {raw_line}"
78+
79+
if value.startswith('"') and value.endswith('"') and len(value) >= 2:
80+
parsed[key] = value[1:-1]
81+
elif value.startswith("'") and value.endswith("'") and len(value) >= 2:
82+
parsed[key] = value[1:-1]
83+
elif value == "":
84+
parsed[key] = None
85+
else:
86+
parsed[key] = value
87+
88+
return parsed, None
89+
90+
91+
def local_quick_validate(skill_dir: Path) -> tuple[bool, str]:
92+
skill_md = skill_dir / "SKILL.md"
93+
if not skill_md.exists():
94+
return False, "SKILL.md not found"
95+
96+
content = skill_md.read_text()
97+
if not content.startswith("---"):
98+
return False, "No YAML frontmatter found"
99+
100+
match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
101+
if not match:
102+
return False, "Invalid frontmatter format"
103+
104+
frontmatter_text = match.group(1)
105+
106+
frontmatter, parse_error = parse_frontmatter_minimal(frontmatter_text)
107+
if parse_error:
108+
return False, parse_error
109+
if not isinstance(frontmatter, dict):
110+
return False, "Frontmatter must be a YAML dictionary"
111+
112+
allowed_properties = {"name", "description", "license", "allowed-tools", "metadata"}
113+
unexpected_keys = set(frontmatter.keys()) - allowed_properties
114+
if unexpected_keys:
115+
unexpected = ", ".join(sorted(unexpected_keys))
116+
allowed = ", ".join(sorted(allowed_properties))
117+
return (
118+
False,
119+
f"Unexpected key(s) in SKILL.md frontmatter: {unexpected}. Allowed properties are: {allowed}",
120+
)
121+
122+
if "name" not in frontmatter:
123+
return False, "Missing 'name' in frontmatter"
124+
if "description" not in frontmatter:
125+
return False, "Missing 'description' in frontmatter"
126+
127+
name = frontmatter.get("name", "")
128+
if not isinstance(name, str):
129+
return False, f"Name must be a string, got {type(name).__name__}"
130+
name = name.strip()
131+
if not re.match(r"^[a-z0-9-]+$", name):
132+
return False, f"Name '{name}' should be hyphen-case"
133+
if name.startswith("-") or name.endswith("-") or "--" in name:
134+
return False, f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens"
135+
if len(name) > MAX_SKILL_NAME_LENGTH:
136+
return False, f"Name is too long ({len(name)} characters). Maximum is {MAX_SKILL_NAME_LENGTH}."
137+
138+
description = frontmatter.get("description", "")
139+
if not isinstance(description, str):
140+
return False, f"Description must be a string, got {type(description).__name__}"
141+
description = description.strip()
142+
if "<" in description or ">" in description:
143+
return False, "Description cannot contain angle brackets (< or >)"
144+
if len(description) > 1024:
145+
return False, f"Description is too long ({len(description)} characters). Maximum is 1024."
146+
147+
return True, "Local fallback validation passed."
148+
57149

58150
def run_quick_validate(skill_dir: Path) -> tuple[bool, str]:
59151
validator = Path.home() / ".codex/skills/.system/skill-creator/scripts/quick_validate.py"
60-
proc = subprocess.run(
61-
[sys.executable, str(validator), str(skill_dir)],
62-
capture_output=True,
63-
text=True,
64-
check=False,
65-
)
66-
output = (proc.stdout + proc.stderr).strip()
67-
return proc.returncode == 0, output
152+
if validator.exists():
153+
proc = subprocess.run(
154+
[sys.executable, str(validator), str(skill_dir)],
155+
capture_output=True,
156+
text=True,
157+
check=False,
158+
)
159+
output = (proc.stdout + proc.stderr).strip()
160+
return proc.returncode == 0, output
161+
162+
return local_quick_validate(skill_dir)
68163

69164

70165
def check_file_exists(skill_dir: Path) -> tuple[bool, list[str]]:

0 commit comments

Comments
 (0)