diff --git a/scripts/validate_pr_body.py b/scripts/validate_pr_body.py index 42609fb..f0e1891 100644 --- a/scripts/validate_pr_body.py +++ b/scripts/validate_pr_body.py @@ -50,11 +50,13 @@ def validate_pr_body_text(text: str, *, check_governance: bool = True) -> list[s if not LIST_ITEM_PATTERN.search(text): errors.append("body is missing markdown list items") if check_governance: - if not REQ_ID_PATTERN.search(text): + # Check governance IDs against code-stripped text so IDs hidden in code + # blocks or inline code are not counted (prevents spoof via code fences). + if not REQ_ID_PATTERN.search(searchable): errors.append("body is missing REQ-ID traceability (e.g. REQ-OPS-001)") - if not TASK_ID_PATTERN.search(text): + if not TASK_ID_PATTERN.search(searchable): errors.append("body is missing TASK-ID traceability (e.g. TASK-OPS-001)") - if not TEST_ID_PATTERN.search(text): + if not TEST_ID_PATTERN.search(searchable): errors.append("body is missing TEST-ID traceability (e.g. TEST-OPS-001)") return errors diff --git a/tests/test_validate_pr_body.py b/tests/test_validate_pr_body.py index 908995c..b02bddc 100644 --- a/tests/test_validate_pr_body.py +++ b/tests/test_validate_pr_body.py @@ -103,6 +103,26 @@ def test_validate_pr_body_text_skips_governance_when_disabled() -> None: assert not any("REQ-ID" in err or "TASK-ID" in err or "TEST-ID" in err for err in errors) +def test_validate_pr_body_text_rejects_governance_ids_in_code_block_only() -> None: + """Regression for review comment: IDs inside code fences must not count.""" + module = _load_module() + text = "\n".join( + [ + "## Summary", + "- no governance IDs in narrative text", + "```text", + "REQ-FAKE-999", + "TASK-FAKE-999", + "TEST-FAKE-999", + "```", + ] + ) + errors = module.validate_pr_body_text(text) + assert any("REQ-ID" in err for err in errors) + assert any("TASK-ID" in err for err in errors) + assert any("TEST-ID" in err for err in errors) + + def test_fetch_pr_body_reads_body_from_tea_api(monkeypatch) -> None: module = _load_module()