44from __future__ import annotations
55
66import argparse
7+ import re
78from pathlib import Path
89
910
11+ PLAIN_SCALAR_MAPPING_VALUE = re .compile (r":(?:\s|$)" )
12+
13+
14+ def strip_matching_quotes (value : str ) -> str :
15+ if len (value ) >= 2 and value [0 ] == value [- 1 ] and value [0 ] in {'"' , "'" }:
16+ return value [1 :- 1 ]
17+ return value
18+
19+
20+ def validate_plain_scalar (path : Path , line_number : int , key : str , value : str ) -> None :
21+ """Catch invalid plain-scalar YAML that Codex rejects while loading skills."""
22+ stripped = value .strip ()
23+ if not stripped or stripped [0 ] in {'"' , "'" } or stripped in {"|" , ">" , "|-" , ">-" , "|+" , ">+" }:
24+ return
25+ if PLAIN_SCALAR_MAPPING_VALUE .search (stripped ):
26+ raise SystemExit (
27+ f"{ path } :{ line_number } : invalid YAML frontmatter for { key !r} : "
28+ "unquoted ':' followed by whitespace; quote the value"
29+ )
30+
31+
1032def parse_frontmatter (path : Path ) -> dict [str , str ]:
1133 """Extract top-level frontmatter keys from a Markdown file.
1234
@@ -15,22 +37,26 @@ def parse_frontmatter(path: Path) -> dict[str, str]:
1537 skipped, so nested blocks (a schema note's `schema:`/`settings:` children) can't
1638 overwrite a top-level key like `type` or `entity` via last-write-wins. It does
1739 not interpret block scalars or multi-line values; callers rely on single-line
18- top-level fields (name, description, type, entity).
40+ top-level fields (name, description, type, entity). Keep the Codex-facing YAML
41+ guard here dependency-free so package checks work under bare `python3`.
1942 """
2043 lines = path .read_text ().splitlines ()
2144 if not lines or lines [0 ] != "---" :
2245 raise SystemExit (f"{ path } : missing YAML frontmatter" )
2346
2447 frontmatter : dict [str , str ] = {}
25- for line in lines [1 :]:
48+ for line_number , line in enumerate ( lines [1 :], start = 2 ) :
2649 if line == "---" :
2750 break
2851 if line [:1 ] in (" " , "\t " ): # nested key — not a top-level field
2952 continue
3053 if ":" not in line :
3154 continue
3255 key , value = line .split (":" , 1 )
33- frontmatter [key .strip ()] = value .strip ().strip ('"' )
56+ key = key .strip ()
57+ value = value .strip ()
58+ validate_plain_scalar (path , line_number , key , value )
59+ frontmatter [key ] = strip_matching_quotes (value )
3460 else :
3561 raise SystemExit (f"{ path } : unclosed YAML frontmatter" )
3662
0 commit comments