fix(learning_graph): guard non-dict metadata so /journey can't crash

parse_frontmatter's malformed-YAML fallback stores every value as a string,
so a skill's `metadata` can be a str. `_category`/`_related` chained
`.get("metadata", {}).get("hermes", {})` and blew up with `'str' object has
no attribute 'get'`, taking down `build_learning_graph()` (and thus /journey
and `hermes journey`) whenever any installed skill had bad frontmatter.

Extract a `_hermes_meta()` helper that returns the nested dict only when it
really is one. Fixes the whole class, not just the two call sites.
This commit is contained in:
Brooklyn Nicholson 2026-07-01 16:25:48 -05:00
parent 76a468e513
commit ec319e4e3e
2 changed files with 34 additions and 2 deletions

View file

@ -48,8 +48,16 @@ def _frontmatter(text: str) -> dict[str, Any]:
return {}
def _hermes_meta(fm: dict[str, Any]) -> dict[str, Any]:
"""``metadata.hermes`` as a dict, tolerant of the string-valued frontmatter
that ``parse_frontmatter``'s malformed-YAML fallback produces."""
meta = fm.get("metadata")
hermes = meta.get("hermes") if isinstance(meta, dict) else None
return hermes if isinstance(hermes, dict) else {}
def _related(fm: dict[str, Any]) -> list[str]:
raw = fm.get("related_skills") or (fm.get("metadata", {}).get("hermes", {}) or {}).get("related_skills")
raw = fm.get("related_skills") or _hermes_meta(fm).get("related_skills")
if isinstance(raw, list):
return [str(r).strip() for r in raw if str(r).strip()]
if isinstance(raw, str):
@ -58,7 +66,7 @@ def _related(fm: dict[str, Any]) -> list[str]:
def _category(fm: dict[str, Any], skill_md: Path) -> str:
cat = fm.get("category") or (fm.get("metadata", {}).get("hermes", {}) or {}).get("category")
cat = fm.get("category") or _hermes_meta(fm).get("category")
if cat:
return str(cat)
# …/skills/<category>/<skill>/SKILL.md