@@ -47,27 +47,64 @@ def _iter_release_blocks(lines: list[str]) -> list[tuple[str, list[str]]]:
4747 return blocks
4848
4949
50+ def _collapse_bullets (block_lines : list [str ]) -> list [tuple [str , str ]]:
51+ """Collapse multi-line bullets into (first_line, full_text) pairs.
52+
53+ python-semantic-release places commit links on indented continuation
54+ lines. We join them so the reference check sees the complete bullet.
55+ """
56+ bullets : list [tuple [str , str ]] = []
57+ current_first : str | None = None
58+ current_parts : list [str ] = []
59+
60+ for line in block_lines :
61+ if line .startswith ("- " ):
62+ if current_first is not None :
63+ bullets .append ((current_first , " " .join (current_parts )))
64+ current_first = line
65+ current_parts = [line [2 :].strip ()]
66+ elif current_first is not None and line .startswith (" " ):
67+ current_parts .append (line .strip ())
68+ else :
69+ if current_first is not None :
70+ bullets .append ((current_first , " " .join (current_parts )))
71+ current_first = None
72+ current_parts = []
73+
74+ if current_first is not None :
75+ bullets .append ((current_first , " " .join (current_parts )))
76+
77+ return bullets
78+
79+
5080def _validate_release_block (title : str , block_lines : list [str ]) -> list [str ]:
5181 errors : list [str ] = []
5282 sections_seen : set [str ] = set ()
5383 current_section : str | None = None
84+ section_lines : list [str ] = []
85+
86+ def _check_section_bullets () -> None :
87+ for first_line , full_text in _collapse_bullets (section_lines ):
88+ if full_text .lower () == "none." :
89+ continue
90+ if not RE_ISSUE_REF .search (full_text ) and not RE_COMMIT_LINK .search (full_text ):
91+ errors .append (f"{ title } : bullet missing issue/PR reference -> '{ first_line } '" )
5492
5593 for line in block_lines :
5694 section = RE_SECTION .match (line )
5795 if section :
96+ _check_section_bullets ()
5897 current_section = section .group ("section" )
5998 sections_seen .add (current_section )
99+ section_lines = []
60100 continue
61101
62102 if current_section is None :
63103 continue
64104
65- if line .startswith ("- " ):
66- item = line [2 :].strip ()
67- if item .lower () == "none." :
68- continue
69- if not RE_ISSUE_REF .search (item ) and not RE_COMMIT_LINK .search (item ):
70- errors .append (f"{ title } : bullet missing issue/PR reference -> '{ line } '" )
105+ section_lines .append (line )
106+
107+ _check_section_bullets ()
71108
72109 if not sections_seen and not any (line .startswith ("**Detailed Changes**:" ) for line in block_lines ):
73110 errors .append (f"{ title } : missing at least one '### <section>' subsection" )
0 commit comments