@@ -81,10 +81,16 @@ def parse_front_matter(path: str) -> dict | None:
8181 return None
8282 yaml_block = content [3 :end ]
8383 try :
84- return yaml .safe_load (yaml_block ) or {}
84+ payload = yaml .safe_load (yaml_block )
8585 except yaml .YAMLError :
8686 fail ("YAML front matter is invalid" , path )
8787 return None
88+ if payload is None :
89+ return {}
90+ if not isinstance (payload , dict ):
91+ fail ("YAML front matter must be a mapping" , path )
92+ return None
93+ return payload
8894
8995
9096def assert_front_matter_field (fm : dict , field : str , context : str ) -> None :
@@ -107,6 +113,22 @@ def assert_front_matter_field_exists(fm: dict, field: str, context: str) -> None
107113 ok (f"Front matter field '{ field } ' declared in { context } " )
108114
109115
116+ def assert_front_matter_field_type (fm : dict , field : str , expected_type : type | tuple [type , ...], context : str , * , allow_empty : bool = False ) -> None :
117+ if field not in fm :
118+ return
119+
120+ value = fm [field ]
121+ if not isinstance (value , expected_type ):
122+ fail (f"Front matter field '{ field } ' must be { expected_type } " , context )
123+ return
124+
125+ if not allow_empty and (value == "" or value == []):
126+ fail (f"Front matter field '{ field } ' must not be empty" , context )
127+ return
128+
129+ ok (f"Front matter field '{ field } ' has expected type in { context } " )
130+
131+
110132def assert_markdown_section (path : str , section : str ) -> None :
111133 """Assert that '## section' is present in a Markdown file."""
112134 content = read_file (path )
@@ -258,7 +280,9 @@ def known_skill_slugs() -> list[str]:
258280 assert_front_matter_field (fm , "name" , ctx )
259281 assert_front_matter_field (fm , "description" , ctx )
260282 assert_front_matter_field (fm , "primary-skill" , ctx )
283+ assert_front_matter_field_type (fm , "primary-skill" , str , ctx )
261284 assert_front_matter_field_exists (fm , "supporting-skills" , ctx )
285+ assert_front_matter_field_type (fm , "supporting-skills" , list , ctx )
262286
263287 # 'name' slug must match the slug derived from the filename.
264288 expected_slug = re .sub (r"^fast-forward-" , "" , os .path .splitext (filename )[0 ])
@@ -508,7 +532,11 @@ def known_skill_slugs() -> list[str]:
508532 continue
509533
510534 fm = parse_front_matter (path )
511- if fm is None or not isinstance (fm .get ("primary-skill" ), str ):
535+ if fm is None :
536+ continue
537+
538+ assert_front_matter_field_type (fm , "primary-skill" , str , filename )
539+ if not isinstance (fm .get ("primary-skill" ), str ):
512540 continue
513541
514542 primary = fm ["primary-skill" ]
@@ -533,6 +561,15 @@ def known_skill_slugs() -> list[str]:
533561 continue
534562
535563 supporting = fm ["supporting-skills" ]
564+ if not isinstance (supporting , list ):
565+ fail (f"supporting-skills must be a list" , filename )
566+ continue
567+
568+ for idx , slug in enumerate (supporting ):
569+ if not isinstance (slug , str ):
570+ fail (f"supporting-skills[{ idx } ] must be a string" , filename )
571+ continue
572+
536573 if not supporting :
537574 ok (f"No supporting-skills declared (empty list OK) in { filename } " )
538575 continue
0 commit comments