Harden SKILL.md parser — error logging, flexible indent, CRLF support, type validation
This commit is contained in:
@@ -67,13 +67,22 @@ except FileNotFoundError:
|
||||
|
||||
|
||||
def parse_skill_md(path):
|
||||
"""Parse a SKILL.md frontmatter into a tool definition."""
|
||||
with open(path) as f:
|
||||
content = f.read()
|
||||
"""Parse a SKILL.md frontmatter into a tool definition.
|
||||
Returns tool definition dict or None on failure."""
|
||||
try:
|
||||
with open(path) as f:
|
||||
content = f.read()
|
||||
except Exception as e:
|
||||
log(f"Cannot read {path}: {e}")
|
||||
return None
|
||||
|
||||
# Normalize line endings
|
||||
content = content.replace("\r\n", "\n")
|
||||
|
||||
# Extract YAML frontmatter between ---
|
||||
match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
|
||||
if not match:
|
||||
log(f"No frontmatter in {path}")
|
||||
return None
|
||||
|
||||
# Simple YAML-like parser (no pyyaml dependency)
|
||||
@@ -87,21 +96,24 @@ def parse_skill_md(path):
|
||||
if not stripped or stripped.startswith("#"):
|
||||
continue
|
||||
|
||||
if line.startswith(" ") and current_key == "parameters":
|
||||
# Inside parameters block
|
||||
if line.startswith(" ") and current_param:
|
||||
# Detect indent level (flexible — 2+ spaces counts as nested)
|
||||
indent = len(line) - len(line.lstrip())
|
||||
|
||||
if indent >= 2 and current_key == "parameters":
|
||||
if indent >= 4 and current_param:
|
||||
# Parameter property
|
||||
k, _, v = stripped.partition(":")
|
||||
k = k.strip()
|
||||
v = v.strip().strip('"').strip("'")
|
||||
if k.strip() == "required":
|
||||
v = v.lower() == "true"
|
||||
params[current_param][k.strip()] = v
|
||||
if k == "required":
|
||||
v = v.lower() in ("true", "yes", "1")
|
||||
params[current_param][k] = v
|
||||
elif ":" in stripped:
|
||||
# New parameter
|
||||
# New parameter name
|
||||
param_name = stripped.rstrip(":").strip()
|
||||
current_param = param_name
|
||||
params[param_name] = {}
|
||||
elif ":" in line and not line.startswith(" "):
|
||||
elif ":" in line and indent == 0:
|
||||
k, _, v = line.partition(":")
|
||||
k = k.strip()
|
||||
v = v.strip().strip('"').strip("'")
|
||||
@@ -111,14 +123,21 @@ def parse_skill_md(path):
|
||||
current_param = None
|
||||
|
||||
if "name" not in fm:
|
||||
log(f"No 'name' field in {path}")
|
||||
return None
|
||||
|
||||
if "description" not in fm:
|
||||
log(f"Warning: no 'description' in {path}")
|
||||
|
||||
# Build Ollama tool definition
|
||||
properties = {}
|
||||
required = []
|
||||
for pname, pdata in params.items():
|
||||
ptype = pdata.get("type", "string")
|
||||
if ptype not in ("string", "integer", "number", "boolean", "array", "object"):
|
||||
log(f"Warning: unknown type '{ptype}' for param '{pname}' in {path}")
|
||||
properties[pname] = {
|
||||
"type": pdata.get("type", "string"),
|
||||
"type": ptype,
|
||||
"description": pdata.get("description", ""),
|
||||
}
|
||||
if pdata.get("required", False):
|
||||
|
||||
Reference in New Issue
Block a user