#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ patch_layouts_c5c.py — Chantier C5 lot 3 (catalogue : agenda, pyramid) ====================================================================== Append sécurisé à layouts_v2.yaml (mécanique verrouillée C4/C5b). Après ce patch, propager : python3 prompt_injection_v2.py && python3 build_gallery.py. Usage (dossier du pipeline, single-line) : python3 patch_layouts_c5c.py """ import shutil import sys from pathlib import Path import yaml TARGET = Path("layouts_v2.yaml") NEW_KEYS = ("agenda", "pyramid") FRAGMENT = """ # ── Chantier C5 lot 3 — retours v1 restylés ─────────────────────── agenda: id: L40 famille: Structure mode: light champs: [titre, sections] champs_requis: [titre, sections] agent_hint: >- Sommaire du deck : à placer en slide 2 pour toute présentation de plus de 15 minutes. sections = liste de {label, numero?, duree?, actif?}, 2 à 8 entrées. actif: true met la section en corail (utile pour les rappels d'agenda en cours de deck). duree (ex "10 min") s'affiche à droite en discret. pyramid: id: L41 famille: Concept mode: light champs: [titre, niveaux] champs_requis: [titre, niveaux] agent_hint: >- Argumentation pyramidale : la conclusion au sommet, les fondations à la base. niveaux = liste de {label, description?}, EXACTEMENT 3 ou 4 niveaux, du sommet vers la base. Largeurs d'étages fixes gérées par le moteur. Idéal pour : message clé et ses appuis, hiérarchie stratégie/tactiques/moyens. """ def fail(msg): print(" ! %s" % msg) sys.exit(1) def main(): if not TARGET.exists(): fail("%s introuvable — lancer depuis le dossier du pipeline." % TARGET) raw = TARGET.read_text(encoding="utf-8") try: data = yaml.safe_load(raw) except yaml.YAMLError as e: fail("layouts_v2.yaml actuel illisible : %s" % e) layouts = (data or {}).get("layouts") if not isinstance(layouts, dict): fail("Bloc layouts: introuvable dans le fichier.") for key in NEW_KEYS: if key in layouts: fail("'%s' déjà présent — rien à faire." % key) last = next((l for l in reversed(raw.splitlines()) if l.strip() and not l.strip().startswith("#")), "") if not last.startswith(" "): fail("Le fichier ne se termine pas dans le bloc layouts: — " "fusion manuelle requise.") shutil.copy2(TARGET, str(TARGET) + ".bak-c5c") print(" + Sauvegarde : %s.bak-c5c" % TARGET) with open(TARGET, "a", encoding="utf-8") as f: if not raw.endswith("\n"): f.write("\n") f.write(FRAGMENT) try: check = yaml.safe_load(TARGET.read_text(encoding="utf-8")) nl = check["layouts"] assert all(k in nl for k in NEW_KEYS) assert len(nl) == len(layouts) + len(NEW_KEYS) except Exception as e: shutil.copy2(str(TARGET) + ".bak-c5c", TARGET) fail("Contrôle post-append échoué (%s) — fichier restauré." % e) print(" + agenda (L40) et pyramid (L41) ajoutés (%d layouts au " "total)." % (len(layouts) + len(NEW_KEYS))) print("\n Propager : python3 prompt_injection_v2.py && python3 " "build_gallery.py") if __name__ == "__main__": main()