[DTP-Worker] Sprint 8 · buffer L75 · Traçabilité non vérifiée : les n° de ligne roadmap_line de la matrice d'acceptation pouvaient pointer à côté en silence (roadmap éditée) → INV11 ancrage roadmap (dérive 8+7 du fichier · #6) + parse_roadmap_anchors + 6 tests ; régénéré 558→564

INV10 ne garantissait que « roadmap_line est un entier positif ». Ajout de
parse_roadmap_anchors (deps) qui DÉRIVE la structure réelle de la roadmap, et
d'INV11 qui exige que chaque roadmap_line pointe RÉELLEMENT son bullet
(DELIVERABLE du sprint SX · k-ième bullet métrique) et que le « 8 + 7 » soit
dérivé du fichier, pas figé. Morsure prouvée sur le spec réel (S1=999, M3=200).
Régénéré consommateurs : regression 558→564 (run/plan/MANIFEST), quality_report
(acceptance 31→37 méthodes, 100/100 inchangé), fiches QA + Backend, README
acceptance (10→11 invariants). 7 gates verts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code DTP Worker
2026-07-31 06:12:15 +00:00
parent 11f1250b29
commit 25541c365d
11 changed files with 273 additions and 22 deletions
@@ -22,11 +22,14 @@ from __future__ import annotations
import json
import os
import re
import sys
_HERE = os.path.dirname(os.path.abspath(__file__))
# qa/acceptance/acclib → 05_deliverables_mvp
_DELIVERABLES = os.path.normpath(os.path.join(_HERE, "..", "..", ".."))
# 05_deliverables_mvp → racine du dépôt (où vit 04_roadmap/).
_REPO_ROOT = os.path.normpath(os.path.join(_DELIVERABLES, ".."))
_PUB = os.path.join(_DELIVERABLES, "publiciste")
_AUDIT = os.path.join(_DELIVERABLES, "qa", "audit_4big")
@@ -55,6 +58,54 @@ def audit_sprints() -> dict:
return {m["path"]: m["sprint"] for m in spec["modules"]}
# Ancres de traçabilité de la roadmap (structure, jamais des n° saisis à la main).
_SPRINT_HEAD = re.compile(r"##\s+SPRINT\s+\d+.*?·\s*(S\d+)\s*\(")
_METRIC_HEAD = re.compile(r"##\s+M[ée]triques\s+succ")
_DELIVERABLE = re.compile(r"\*\*DELIVERABLE")
_METRIC_BULLET = re.compile(r"^-\s*✅")
def parse_roadmap_anchors(roadmap_ref: str) -> dict:
"""DÉRIVE les ancres de traçabilité depuis la roadmap réelle (#6 · jamais une
liste de n° de ligne saisie à la main).
Renvoie :
- ``deliverables`` : {sprint SX -> n° de ligne du bullet ``**DELIVERABLE**``
appartenant au sprint SX} (le SX est LU dans le titre ``## SPRINT n … · SX (…``) ;
- ``metrics`` : [n° de ligne] des bullets ``- ✅`` sous la section
``## Métriques succès MVP``, en ordre de fichier ;
- ``path`` : le chemin résolu (pour messages).
``roadmap_ref`` absolu est utilisé tel quel ; relatif est résolu depuis la
racine du dépôt. Lève ``OSError`` si la roadmap est absente/illisible — une
référence de traçabilité vers un fichier fantôme est un mensonge (red honnête)."""
path = roadmap_ref if os.path.isabs(roadmap_ref) \
else os.path.join(_REPO_ROOT, roadmap_ref)
with open(path, encoding="utf-8") as fh:
lines = fh.read().splitlines()
deliverables: dict[str, int] = {}
metrics: list[int] = []
current_sprint: str | None = None
in_metrics = False
for i, ln in enumerate(lines, 1):
mh = _SPRINT_HEAD.match(ln)
if mh:
current_sprint, in_metrics = mh.group(1), False
continue
if _METRIC_HEAD.match(ln):
current_sprint, in_metrics = None, True
continue
if ln.startswith("## "): # toute autre section clôt le contexte
current_sprint, in_metrics = None, False
continue
if current_sprint and _DELIVERABLE.search(ln):
deliverables[current_sprint] = i
if in_metrics and _METRIC_BULLET.match(ln):
metrics.append(i)
return {"deliverables": deliverables, "metrics": metrics, "path": path}
def artifact_exists(rel_path: str) -> bool:
"""Vrai ssi l'artefact cité (chemin relatif à 05_deliverables_mvp) existe.
@@ -67,6 +118,7 @@ __all__ = [
"validate",
"parse_ci",
"audit_sprints",
"parse_roadmap_anchors",
"artifact_exists",
"DELIVERABLES_ROOT",
]