diff --git a/lib/frontmatter.py b/lib/frontmatter.py index 2286f2b..5d82d72 100644 --- a/lib/frontmatter.py +++ b/lib/frontmatter.py @@ -9,6 +9,15 @@ Extracted from merge.py Phase 6 of decomposition (Ganymede-approved plan). import yaml +def _yaml_quote(value: str) -> str: + """Quote a YAML list value if it contains characters that would break parsing.""" + s = str(value) + if ":" in s or s.startswith(("{", "[", "'", '"', "*", "&", "!", "|", ">")): + escaped = s.replace('"', '\\"') + return f'"{escaped}"' + return s + + # Edge field names recognized in claim frontmatter. # Order matters: serialize_edge_fields writes them in this order when appending new fields. REWEAVE_EDGE_FIELDS = ("supports", "challenges", "challenged_by", "depends_on", "related", "reweave_edges") @@ -101,7 +110,7 @@ def serialize_edge_fields(raw_fm_text: str, merged_edges: dict[str, list]) -> st if edges: result_lines.append(f"{matched_field}:") for edge in edges: - result_lines.append(f"- {edge}") + result_lines.append(f"- {_yaml_quote(edge)}") # Don't increment i — it's already past the old field continue else: @@ -115,7 +124,7 @@ def serialize_edge_fields(raw_fm_text: str, merged_edges: dict[str, list]) -> st if edges: result_lines.append(f"{field}:") for edge in edges: - result_lines.append(f"- {edge}") + result_lines.append(f"- {_yaml_quote(edge)}") return "\n".join(result_lines) diff --git a/lib/merge.py b/lib/merge.py index 1331ca1..1d54bf2 100644 --- a/lib/merge.py +++ b/lib/merge.py @@ -508,9 +508,8 @@ async def _merge_reweave_pr(branch: str) -> tuple[bool, str]: branch_fm, _branch_raw_fm, branch_body = parse_yaml_frontmatter(branch_content) if main_fm is None or branch_fm is None: - # Parse failure = something unexpected. Fail the merge, don't fallback - # to cherry-pick. (Theseus: loud failure, not silent retry) - return False, f"frontmatter parse failed on {fpath} — manual review needed" + logger.warning("Reweave merge: frontmatter parse failed on %s — skipping file, continuing PR", fpath) + continue # Superset assertion + merge in one pass. # Reweave only adds edges. If branch is missing an edge that main has, diff --git a/reweave.py b/reweave.py index b351f6f..cf671bc 100644 --- a/reweave.py +++ b/reweave.py @@ -535,8 +535,9 @@ def _write_edge_regex(neighbor_path: Path, fm_text: str, body_text: str, field_re = re.compile(rf"^{edge_type}:\s*$", re.MULTILINE) inline_re = re.compile(rf'^{edge_type}:\s*\[', re.MULTILINE) - entry_line = f'- {orphan_title}' - rw_line = f'- {orphan_title}|{edge_type}|{date_str}' + from lib.frontmatter import _yaml_quote + entry_line = f'- {_yaml_quote(orphan_title)}' + rw_line = f'- {_yaml_quote(orphan_title + "|" + edge_type + "|" + date_str)}' if field_re.search(fm_text): # Multi-line list exists — find end of list, append