""" escrita_sd.py — regrava o SD.md preservando os comentários do frontmatter. Por que round-trip e não yaml.safe_dump: os comentários do frontmatter carregam decisão de contrato, não enfeite. O campo `estado` da P2·SD10 tem quatro linhas de correção datada ("CORREÇÃO 17/08/2026 (gestão do contrato): o protótipo navegável está EM CRIAÇÃO, não em produção como registrado em 11/08"). Um dump comum apagaria isso sem deixar rastro, e o histórico da decisão morreria num clique de "Salvar". Nada aqui grava sozinho: quem chama mostra o diff primeiro e pede confirmação. """ from __future__ import annotations import difflib import io import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts")) from caminhos import PROJETOS, RAIZ # noqa: E402 def _yaml_rt(): from ruamel.yaml import YAML y = YAML() y.preserve_quotes = True # Largura efetivamente infinita: o padrão do ruamel é 80 e requebraria # linhas longas que hoje estão inteiras, produzindo um diff cheio de ruído # em campos que ninguém tocou. y.width = 4096 return y def render_sd(dados, corpo: str) -> str: """Monta o texto completo do SD.md: frontmatter + prosa, verbatim.""" buf = io.StringIO() _yaml_rt().dump(dados, buf) frontmatter = buf.getvalue() if not frontmatter.endswith("\n"): frontmatter += "\n" corpo = corpo or "" if corpo and not corpo.startswith("\n"): corpo = "\n" + corpo if corpo and not corpo.endswith("\n"): corpo += "\n" return f"---\n{frontmatter}---\n{corpo}" def destino_provavel(sd_id: str) -> Path | None: """O SD.md do repositório cujo campo `sd:` casa com o da sessão. Só devolve caminho quando há UMA correspondência. Duas seria ambiguidade, e escolher sozinho o arquivo que vai ser sobrescrito não é decisão de código. """ if not sd_id: return None import yaml achados = [] for f in sorted(PROJETOS.glob("*/sds/*/SD.md")): try: linhas = f.read_text(encoding="utf-8").splitlines() if not linhas or linhas[0].strip() != "---": continue fim = next((i for i, l in enumerate(linhas[1:], 1) if l.strip() == "---"), None) if fim is None: continue fm = yaml.safe_load("\n".join(linhas[1:fim])) or {} except Exception: continue if str(fm.get("sd", "")).strip() == str(sd_id).strip(): achados.append(f) return achados[0] if len(achados) == 1 else None def diff_contra(destino: Path, texto_novo: str) -> str: atual = destino.read_text(encoding="utf-8") if destino.exists() else "" return "".join(difflib.unified_diff( atual.splitlines(keepends=True), texto_novo.splitlines(keepends=True), fromfile=f"a/{destino.name} (no disco)", tofile=f"b/{destino.name} (da sessão)", n=3, )) or "(sem diferenças)" def dentro_do_repositorio(destino: Path) -> bool: try: destino.resolve().relative_to(RAIZ) return True except ValueError: return False def gravar(destino: Path, texto: str) -> None: destino.parent.mkdir(parents=True, exist_ok=True) destino.write_text(texto, encoding="utf-8")