1+ const SUPPORTED_GFI_REPOS = [
2+ 'hiero-sdk-cpp' ,
3+ 'hiero-sdk-swift' ,
4+ 'hiero-sdk-python' ,
5+ 'hiero-website' ,
6+ ] ;
7+
18module . exports = async ( { github, context, core } ) => {
29 const { payload } = context ;
3-
10+
411 // Get PR information from automatic pull_request_target trigger
512 let prNumber = payload . pull_request ?. number ;
613 let prBody = payload . pull_request ?. body || '' ;
7-
14+
815 // Manual workflow_dispatch is no longer supported - inputs were removed
916 // Only automatic triggers from merged PRs will work
1017 const repoOwner = context . repo . owner ;
1118 const repoName = context . repo . repo ;
12-
19+
1320 if ( ! prNumber ) {
1421 core . info ( 'No PR number found, skipping' ) ;
1522 return ;
1623 }
17-
24+
1825 core . info ( `Processing PR #${ prNumber } ` ) ;
19-
26+
2027 // Parse PR body to find linked issues
2128 const MAX_PR_BODY_LENGTH = 50000 ; // Reasonable limit for PR body
2229 if ( prBody . length > MAX_PR_BODY_LENGTH ) {
@@ -25,54 +32,53 @@ module.exports = async ({ github, context, core }) => {
2532 }
2633 const issueRegex = / ( f i x e s | c l o s e s | r e s o l v e s | f i x | c l o s e | r e s o l v e ) \s + (?: [ \w - ] + \/ [ \w - ] + ) ? # ( \d + ) / gi;
2734 const matches = [ ...prBody . matchAll ( issueRegex ) ] ;
28-
35+
2936 if ( matches . length === 0 ) {
3037 core . info ( 'No linked issues found in PR body' ) ;
3138 return ;
3239 }
33-
40+
3441 // Get the first linked issue number
3542 const issueNumber = parseInt ( matches [ 0 ] [ 2 ] ) ;
3643 core . info ( `Found linked issue #${ issueNumber } ` ) ;
37-
44+
3845 try {
3946 // Fetch issue details
4047 const { data : issue } = await github . rest . issues . get ( {
4148 owner : repoOwner ,
4249 repo : repoName ,
4350 issue_number : issueNumber ,
4451 } ) ;
45-
52+
4653 // Normalize and check issue labels (case-insensitive)
4754 const labelNames = issue . labels . map ( label => label . name . toLowerCase ( ) ) ;
4855 const labelSet = new Set ( labelNames ) ;
4956 core . info ( `Issue labels: ${ labelNames . join ( ', ' ) } ` ) ;
50-
57+
5158 // Determine issue difficulty level
5259 const difficultyLevels = {
5360 beginner : labelSet . has ( 'beginner' ) ,
5461 goodFirstIssue : labelSet . has ( 'good first issue' ) ,
5562 intermediate : labelSet . has ( 'intermediate' ) ,
5663 advanced : labelSet . has ( 'advanced' ) ,
5764 } ;
58-
65+
5966 // Skip if intermediate or advanced
6067 if ( difficultyLevels . intermediate || difficultyLevels . advanced ) {
6168 core . info ( 'Issue is intermediate or advanced level, skipping recommendation' ) ;
6269 return ;
6370 }
64-
71+
6572 // Only proceed for Good First Issue or beginner issues
6673 if ( ! difficultyLevels . goodFirstIssue && ! difficultyLevels . beginner ) {
6774 core . info ( 'Issue is not a Good First Issue or beginner issue, skipping' ) ;
6875 return ;
6976 }
70-
77+
7178 let recommendedIssues = [ ] ;
7279 let recommendedLabel = null ;
7380 let isFallback = false ;
74- let recommendationScope = 'repo' ;
75-
81+
7682 recommendedIssues = await searchIssues ( github , core , repoOwner , repoName , 'beginner' ) ;
7783 recommendedLabel = 'Beginner' ;
7884
@@ -82,29 +88,20 @@ module.exports = async ({ github, context, core }) => {
8288 recommendedLabel = 'Good First Issue' ;
8389 }
8490
85- if ( recommendedIssues . length === 0 ) {
86- recommendationScope = 'org' ;
87- recommendedLabel = 'Good First Issue' ;
88- recommendedIssues = await github . rest . search . issuesAndPullRequests ( {
89- q : `org:hiero-ledger type:issue state:open label:"good first issue" no:assignee` ,
90- per_page : 6 ,
91- } ) . then ( res => res . data . items ) ;
92- }
9391
9492 // Remove the issue they just solved
9593 recommendedIssues = recommendedIssues . filter ( i => i . number !== issueNumber ) ;
96-
94+
9795 // Generate and post comment
9896 const completedLabel = difficultyLevels . goodFirstIssue ? 'Good First Issue' : 'Beginner' ;
9997 const completedLabelText = completedLabel === 'Beginner' ? 'Beginner issue' : completedLabel ;
10098 const recommendationMeta = {
10199 completedLabelText,
102100 recommendedLabel,
103101 isFallback,
104- recommendationScope,
105102 } ;
106103 await generateAndPostComment ( github , context , core , prNumber , recommendedIssues , recommendationMeta ) ;
107-
104+
108105 } catch ( error ) {
109106 core . setFailed ( `Error processing issue #${ issueNumber } : ${ error . message } ` ) ;
110107 }
@@ -114,12 +111,12 @@ async function searchIssues(github, core, owner, repo, label) {
114111 try {
115112 const query = `repo:${ owner } /${ repo } type:issue state:open label:"${ label } " no:assignee` ;
116113 core . info ( `Searching for issues with query: ${ query } ` ) ;
117-
114+
118115 const { data : searchResult } = await github . rest . search . issuesAndPullRequests ( {
119116 q : query ,
120117 per_page : 6 ,
121118 } ) ;
122-
119+
123120 core . info ( `Found ${ searchResult . items . length } issues with label "${ label } "` ) ;
124121 return searchResult . items ;
125122 } catch ( error ) {
@@ -128,22 +125,21 @@ async function searchIssues(github, core, owner, repo, label) {
128125 }
129126}
130127
131- async function generateAndPostComment ( github , context , core , prNumber , recommendedIssues , { completedLabelText, recommendedLabel, isFallback, recommendationScope } ) {
128+ async function generateAndPostComment ( github , context , core , prNumber , recommendedIssues , { completedLabelText, recommendedLabel, isFallback} ) {
132129 const marker = '<!-- next-issue-bot-marker -->' ;
133-
130+
134131 // Build comment content
135132 let comment = `${ marker } \n\n🎉 **Nice work completing a ${ completedLabelText } !**\n\n` ;
136133 comment += `Thank you for your contribution to the Hiero Python SDK! We're excited to have you as part of our community.\n\n` ;
137-
134+
138135 if ( recommendedIssues . length > 0 ) {
139- if ( recommendationScope === 'org' ) {
140- comment += `Here are some **Good First Issues across the Hiero organization** you might be interested in working on next:\n\n` ;
141- } else if ( isFallback ) {
136+
137+ if ( isFallback ) {
142138 comment += `Here are some **${ recommendedLabel } ** issues at a similar level you might be interested in working on next:\n\n` ;
143139 } else {
144140 comment += `Here are some issues labeled **${ recommendedLabel } ** you might be interested in working on next:\n\n` ;
145141 }
146-
142+
147143 // Sanitize title: escape markdown link syntax and special characters
148144 const sanitizeTitle = ( title ) => title
149145 . replace ( / \[ / g, '\\[' )
@@ -168,37 +164,51 @@ async function generateAndPostComment(github, context, core, prNumber, recommend
168164 } ) ;
169165 } else {
170166 comment += `There are currently no open issues available at or near the ${ completedLabelText } level in this repository.\n\n` ;
171- const orgLabel = recommendedLabel === 'Beginner' ? 'beginner' : 'good first issue' ;
172- const orgLabelQuery = encodeURIComponent ( `label:"${ orgLabel } "` ) ;
173- comment += `You can check out ${ recommendedLabel . toLowerCase ( ) } issues across the entire Hiero organization: ` +
174- `[Hiero ${ recommendedLabel } Issues](https://github.com/issues?q=org%3Ahiero-ledger+type%3Aissue+state%3Aopen+${ orgLabelQuery } )\n\n` ;
167+ comment += `You can check out **Good First Issues** in other Hiero repositories:\n\n` ;
168+ const repoQuery = SUPPORTED_GFI_REPOS
169+ . map ( repo => `repo:${ context . repo . owner } /${ repo } ` )
170+ . join ( ' OR ' ) ;
171+
172+ const gfiSearchQuery = [
173+ 'is:open' ,
174+ 'is:issue' ,
175+ `org:${ context . repo . owner } ` ,
176+ 'archived:false' ,
177+ 'no:assignee' ,
178+ '(label:"good first issue" OR label:"skill: good first issue")' ,
179+ `(${ repoQuery } )` ,
180+ ] . join ( ' ' ) ;
181+
182+ const gfiQuery = `https://github.com/issues?q=${ encodeURIComponent ( gfiSearchQuery ) } ` ;
183+
184+ comment += `[View Good First Issues across supported Hiero repositories](${ gfiQuery } )\n\n` ;
175185 }
176-
186+
177187 comment += `🌟 **Stay connected with the project:**\n` ;
178188 comment += `- ⭐ [Star this repository](https://github.com/${ context . repo . owner } /${ context . repo . repo } ) to show your support\n` ;
179189 comment += `- 👀 [Watch this repository](https://github.com/${ context . repo . owner } /${ context . repo . repo } /watchers) to get notified of new issues and releases\n\n` ;
180-
190+
181191 comment += `We look forward to seeing more contributions from you! If you have any questions, feel free to ask in our [Discord community](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/docs/discord.md).\n\n` ;
182192 comment += `From the Hiero Python SDK Team 🚀` ;
183-
193+
184194 // Check for existing comment
185195 try {
186196 const { data : comments } = await github . rest . issues . listComments ( {
187197 owner : context . repo . owner ,
188198 repo : context . repo . repo ,
189199 issue_number : prNumber ,
190200 } ) ;
191-
192- const existingComment = comments . find ( comment => comment . body . includes ( marker ) ) ;
193-
201+
202+ const existingComment = comments . find ( c => c . body . includes ( marker ) ) ;
203+
194204 if ( existingComment ) {
195205 core . info ( 'Comment already exists, skipping' ) ;
196206 return ;
197207 }
198208 } catch ( error ) {
199209 core . warning ( `Error checking existing comments: ${ error . message } ` ) ;
200210 }
201-
211+
202212 // Post the comment
203213 try {
204214 await github . rest . issues . createComment ( {
@@ -207,7 +217,7 @@ async function generateAndPostComment(github, context, core, prNumber, recommend
207217 issue_number : prNumber ,
208218 body : comment ,
209219 } ) ;
210-
220+
211221 core . info ( `Successfully posted comment to PR #${ prNumber } ` ) ;
212222 } catch ( error ) {
213223 core . setFailed ( `Error posting comment: ${ error . message } ` ) ;
0 commit comments