feat(02-05): extend gen_ac_traceability.py deferred detection to match pending-phase-N

- Add _PENDING_RE = re.compile(r'pending-phase-\d+', re.IGNORECASE)
- Initialize AC entries with deferred_reason: None alongside deferred: False
- Update collect_acs_from_doc() to set deferred_reason='hardware' or the matched pending-phase-N token
- Update render_md() to show DEFERRED ({reason}) using actual reason string
- Update summary stat line to reflect both hardware and pending-phase deferrals
This commit is contained in:
Yuzviak
2026-05-11 18:29:25 +03:00
parent fcd4bb7b3e
commit a464697bfa
+17 -6
View File
@@ -31,6 +31,9 @@ OUT_MD = ROOT / ".planning" / "AC-TRACEABILITY.md"
_AC_HEADER_RE = re.compile(r"^\s*-\s*\*\*(AC-(?:\d+\.\d+[a-z]?|NEW-\d+))\*\*")
_DEFERRED_RE = re.compile(r"deferred-hardware", re.IGNORECASE)
# Phase 2 / AC-doc Status annotation: `pending-phase-3 (SAFE-01)`, `pending-phase-4 (FDR-02)`, etc.
# Tracks ACs whose test coverage is intentionally deferred to a later phase.
_PENDING_RE = re.compile(r"pending-phase-\d+", re.IGNORECASE)
def collect_acs_from_doc() -> dict[str, dict]:
@@ -51,10 +54,16 @@ def collect_acs_from_doc() -> dict[str, dict]:
m = _AC_HEADER_RE.match(line)
if m:
current_id = m.group(1)
acs[current_id] = {"deferred": False}
acs[current_id] = {"deferred": False, "deferred_reason": None}
continue
if current_id is not None and _DEFERRED_RE.search(line):
acs[current_id]["deferred"] = True
if current_id is not None:
if _DEFERRED_RE.search(line):
acs[current_id]["deferred"] = True
acs[current_id]["deferred_reason"] = "hardware"
elif _PENDING_RE.search(line):
acs[current_id]["deferred"] = True
pm = _PENDING_RE.search(line)
acs[current_id]["deferred_reason"] = pm.group(0)
return acs
@@ -98,7 +107,7 @@ def render_md(doc_acs: dict[str, dict], test_map: dict[str, list[str]]) -> str:
"",
f"**ACs declared in acceptance_criteria.md:** {len(declared)}",
f"**ACs covered by at least one test:** {len(declared_set & tested)}",
f"**ACs deferred to hardware:** {sum(1 for a in declared if doc_acs[a]['deferred'])}",
f"**ACs deferred (hardware or pending-phase):** {sum(1 for a in declared if doc_acs[a]['deferred'])}",
"",
"## AC -> Test mapping",
"",
@@ -107,8 +116,10 @@ def render_md(doc_acs: dict[str, dict], test_map: dict[str, list[str]]) -> str:
]
for ac_id in declared:
tests = test_map.get(ac_id, [])
if doc_acs[ac_id]["deferred"]:
status = "DEFERRED (hardware)"
meta = doc_acs[ac_id]
if meta["deferred"]:
reason = meta.get("deferred_reason") or "hardware"
status = f"DEFERRED ({reason})"
elif tests:
status = "OK"
else: