@@ -22,29 +22,34 @@ var (
2222 kindRE = regexp .MustCompile (`(?im)^/kind\s+([a-z0-9_/-]+)` )
2323 // releaseNoteRE captures the first fenced code block with the word "release-note" in it.
2424 releaseNoteRE = regexp .MustCompile ("(?s)```release-note\\ s*(.*?)\\ s*```" )
25+ // descriptionRE captures content under the # Description heading until the next level-1 heading or end of string.
26+ // Only stops at # followed by space (level-1), not ## or ### (level-2+)
27+ descriptionRE = regexp .MustCompile (`(?sm)^#[ \t]*Description[ \t]*\n(.*?)(?:^#[ \t]|\z)` )
2528)
2629
2730// labeler handles PR labeling operations.
2831type labeler struct {
29- client * github.Client
30- owner string
31- repo string
32- prNum int
33- labelsToAdd map [string ]bool
34- labelsToRemove map [string ]bool
35- currentMap map [string ]bool
32+ client * github.Client
33+ owner string
34+ repo string
35+ prNum int
36+ labelsToAdd map [string ]bool
37+ labelsToRemove map [string ]bool
38+ currentMap map [string ]bool
39+ enforceDescription bool
3640}
3741
3842// New creates a new Labeler instance.
39- func New (client * github.Client , owner , repo string , prNum int ) * labeler {
43+ func New (client * github.Client , owner , repo string , prNum int , enforceDescription bool ) * labeler {
4044 return & labeler {
41- client : client ,
42- owner : owner ,
43- repo : repo ,
44- prNum : prNum ,
45- labelsToAdd : map [string ]bool {},
46- labelsToRemove : map [string ]bool {},
47- currentMap : map [string ]bool {},
45+ client : client ,
46+ owner : owner ,
47+ repo : repo ,
48+ prNum : prNum ,
49+ labelsToAdd : map [string ]bool {},
50+ labelsToRemove : map [string ]bool {},
51+ currentMap : map [string ]bool {},
52+ enforceDescription : enforceDescription ,
4853 }
4954}
5055
@@ -54,6 +59,8 @@ func (l *labeler) ProcessPR(ctx context.Context, body string, syncLabels bool) e
5459 if err := l .fetchLabels (ctx ); err != nil {
5560 return err
5661 }
62+ // normalize line endings to \n (GitHub returns \r\n)
63+ body = strings .ReplaceAll (body , "\r \n " , "\n " )
5764 // strip HTML comments to make the body easier to parse.
5865 sanitizedBody := commentRE .ReplaceAllString (body , "" )
5966
@@ -64,6 +71,11 @@ func (l *labeler) ProcessPR(ctx context.Context, body string, syncLabels bool) e
6471 if err := l .processReleaseNotes (sanitizedBody ); err != nil {
6572 errs = append (errs , err )
6673 }
74+ if l .enforceDescription {
75+ if err := l .processDescription (sanitizedBody ); err != nil {
76+ errs = append (errs , err )
77+ }
78+ }
6779 if syncLabels {
6880 if err := l .syncLabels (ctx ); err != nil {
6981 errs = append (errs , err )
@@ -228,6 +240,31 @@ func (l *labeler) processReleaseNotes(body string) error {
228240 return nil
229241}
230242
243+ // processDescription handles the description validation and labeling
244+ func (l * labeler ) processDescription (body string ) error {
245+ // validate the description block is present
246+ match := descriptionRE .FindStringSubmatch (body )
247+ if len (match ) < 2 {
248+ if ! l .currentMap [labels .InvalidDescriptionLabel ] {
249+ l .labelsToAdd [labels .InvalidDescriptionLabel ] = true
250+ }
251+ return fmt .Errorf ("missing # Description section in PR body; please add a description explaining the changes" )
252+ }
253+ // check if the description content is meaningful (not empty or just whitespace)
254+ descriptionContent := strings .TrimSpace (match [1 ])
255+ if descriptionContent == "" {
256+ if ! l .currentMap [labels .InvalidDescriptionLabel ] {
257+ l .labelsToAdd [labels .InvalidDescriptionLabel ] = true
258+ }
259+ return fmt .Errorf ("empty # Description section in PR body; please add a meaningful description explaining the changes" )
260+ }
261+ // description is valid, remove the invalid label if present
262+ if l .currentMap [labels .InvalidDescriptionLabel ] {
263+ l .labelsToRemove [labels .InvalidDescriptionLabel ] = true
264+ }
265+ return nil
266+ }
267+
231268func (l * labeler ) syncLabels (ctx context.Context ) error {
232269 var errs []error
233270 labelsToAdd := make ([]string , 0 , len (l .labelsToAdd ))
0 commit comments