[DTP-Worker] Sprint 7/8 · Livrable démo : prompteur Markdown (out/run_sheet.md)

Le seul livrable démo (demo/scenarios) ne produisait qu'un run-sheet JSON
machine — aucun support lisible par un présentateur, alors que le Sprint 7 vise
un scénario « prêt à jouer ». Rendu Markdown in-repo depuis le JSON = même
pattern que faisabilite (rend des .md), zéro écriture VPS.

- scenlib/render.py : render_markdown() PUR/déterministe, ne lit que le
  run-sheet (déjà anti-inventé), aucun chiffre nouveau (#6).
- build émet out/run_sheet.md (prompteur : par beat, table « À dire | Chiffre |
  Source (preuve) » traçant le pointeur RFC 6901 amont).
- Compat vérifiée : HANDOFF 4Big ne json.load que les .json (ignore .md) ;
  check_artifacts diffe tout fichier build → md commité + reproductible.
- Consommateurs régénérés : régression 551→558 (22 suites PASS) ;
  quality_report.json (README 5011→5575 o · 32→39 test_*) PASS 22/22 ;
  03_agents/qa/AGENT.md 551→558 ; README démo auto-score 96→97.
- 7 gates verts · 39 tests démo · 34 tests audit · arbre propre.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-07-31 04:09:23 +00:00
parent c858e193e0
commit aebe9b7ba9
11 changed files with 364 additions and 27 deletions
@@ -19,7 +19,7 @@ _MOD = os.path.normpath(os.path.join(_HERE, ".."))
sys.path.insert(0, _MOD)
import demo_scenario_gen as gen # noqa: E402
from scenlib import builder, evidence # noqa: E402
from scenlib import builder, evidence, render # noqa: E402
def _fresh():
@@ -214,6 +214,51 @@ class TestInjectionsNegatives(unittest.TestCase):
self.assertTrue(gen.deps.validate(mf, gen._load_schema()))
class TestRenderMarkdown(unittest.TestCase):
"""Le prompteur Markdown : déterministe et sans donnée nouvelle (#6)."""
def setUp(self):
_, self.run_sheet, _ = _fresh()
self.md = render.render_markdown(self.run_sheet)
def test_deterministe(self):
self.assertEqual(self.md, render.render_markdown(self.run_sheet))
def test_termine_par_un_saut_de_ligne(self):
self.assertTrue(self.md.endswith("\n"))
self.assertFalse(self.md.endswith("\n\n"))
def test_titres_scenarios_presents(self):
for sc in self.run_sheet["scenarios"]:
self.assertIn(sc["titre"], self.md)
self.assertIn(sc["id"], self.md)
def test_chaque_beat_rendu(self):
for sc in self.run_sheet["scenarios"]:
for b in sc["beats"]:
self.assertIn(b["titre"], self.md)
self.assertIn(b["role_narratif"], self.md)
def test_valeurs_proviennent_du_run_sheet(self):
# Anti-invention : chaque valeur affichée est celle, formatée, d'une
# citation — jamais un chiffre nouveau. On vérifie la présence verbatim.
for sc in self.run_sheet["scenarios"]:
for b in sc["beats"]:
for c in b["evidence"]:
self.assertIn(render._fmt(c["valeur"]), self.md)
def test_fmt_bool_liste_none(self):
self.assertEqual(render._fmt(True), "oui")
self.assertEqual(render._fmt(False), "non")
self.assertEqual(render._fmt(["fr", "en"]), "fr, en")
self.assertEqual(render._fmt(50), "50")
self.assertEqual(render._fmt(None), "`—`")
def test_placeholder_non_promu_en_chiffre(self):
# Un placeholder survivant est rendu entre back-ticks, jamais nu.
self.assertEqual(render._fmt("{{m:f#/p}}"), "`{{m:f#/p}}`")
class TestCLI(unittest.TestCase):
def test_validate_retourne_zero(self):
self.assertEqual(gen.main(["validate"]), 0)
@@ -222,7 +267,7 @@ class TestCLI(unittest.TestCase):
import tempfile
with tempfile.TemporaryDirectory() as d:
self.assertEqual(gen.main(["build", "-o", d]), 0)
for name in ("run_sheet.json", "MANIFEST.json"):
for name in ("run_sheet.json", "MANIFEST.json", "run_sheet.md"):
self.assertTrue(os.path.isfile(os.path.join(d, name)))