From 01e4e0f43fedb50b32ff4c427cc5cda33c7de71d Mon Sep 17 00:00:00 2001 From: agentson Date: Wed, 4 Mar 2026 23:37:10 +0900 Subject: [PATCH] fix: governance ID check must ignore code blocks (review #415) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit REQ/TASK/TEST ID 패턴 매칭을 _strip_code_segments() 결과에 적용하여 코드 펜스/인라인 코드 안에만 ID를 넣어 검증을 우회하는 케이스를 차단. 회귀 테스트 추가: test_validate_pr_body_text_rejects_governance_ids_in_code_block_only Co-Authored-By: Claude Sonnet 4.6 --- scripts/validate_pr_body.py | 8 +++++--- tests/test_validate_pr_body.py | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) 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()