@@ -11,6 +11,10 @@ name: Issue Triage
1111 required : false
1212 default : ' true'
1313 type : boolean
14+ issue_number :
15+ description : ' Issue number to triage (leave empty to process all)'
16+ required : false
17+ type : string
1418
1519permissions :
1620 issues : write
8690 ${{ env.ISSUE_BODY }}
8791 ```
8892
89- **Output File Path**:
90- ```
91- ${{ env.GITHUB_ENV }}
92- ```
93-
9493 ## Steps
9594
9695 1. Review the issue title, issue body, and available labels
@@ -100,24 +99,26 @@ jobs:
10099 and choose all appropriate labels from the list of available
101100 labels.
102101
103- 3. Output the selected labels as a comma-separated list to the
104- file at the path specified in "Output File Path" using the
105- following format:
102+ 3. Return only the selected labels as a comma-separated list,
103+ with no additional text or explanation. For example:
106104 ```
107- SELECTED_LABELS= label1,label2,label3
105+ label1, label2, label3
108106 ```
109107
110108 - name : Apply labels
109+ if : steps.ai-triage.outputs.response != ''
111110 uses : actions/github-script@v7
111+ env :
112+ AI_RESPONSE : ${{ steps.ai-triage.outputs.response }}
112113 with :
113114 script : |
114- const selectedLabels = process.env.SELECTED_LABELS ;
115- if (!selectedLabels || selectedLabels .trim() === '') {
115+ const response = process.env.AI_RESPONSE ;
116+ if (!response || response .trim() === '') {
116117 console.log('No labels selected by AI');
117118 return;
118119 }
119120
120- const labels = selectedLabels .split(',')
121+ const labels = response .split(',')
121122 .map(l => l.trim())
122123 .filter(l => l.length > 0);
123124
@@ -135,28 +136,15 @@ jobs:
135136
136137 triage-unlabeled-issues :
137138 name : Triage Unlabeled Issues
138- if : github.event_name == 'workflow_dispatch'
139+ if : |
140+ github.event_name == 'workflow_dispatch' &&
141+ github.event.inputs.issue_number == ''
139142 runs-on : ubuntu-latest
140143 steps :
141- - name : Get available labels
142- id : get-labels
143- uses : actions/github-script@v7
144- with :
145- script : |
146- const labels = await github.rest.issues.listLabelsForRepo({
147- owner: context.repo.owner,
148- repo: context.repo.repo,
149- per_page: 100
150- });
151- const labelNames = labels.data.map(label => label.name);
152- return labelNames.join(', ');
153-
154- - name : Find and triage unlabeled issues
144+ - name : Find and dispatch triage for unlabeled issues
155145 uses : actions/github-script@v7
156146 with :
157147 script : |
158- const availableLabels = ${{ steps.get-labels.outputs.result }};
159-
160148 // Get all open issues
161149 const issues = await github.paginate(
162150 github.rest.issues.listForRepo,
@@ -184,16 +172,166 @@ jobs:
184172 return;
185173 }
186174
187- // For manual dispatch, we'll log that AI triage is not yet
188- // implemented for batch processing
189- console.log(
190- 'Note: Batch AI triage for unlabeled issues requires ' +
191- 'manual triggering of individual issue workflows or ' +
192- 'implementing batch AI processing in the future.'
193- );
194-
175+ // Dispatch triage workflow for each unlabeled issue
195176 for (const issue of unlabeledIssues) {
196177 console.log(
197- `Issue #${issue.number}: "${issue.title}" needs triage`
178+ `Dispatching triage for issue #${issue.number}: ` +
179+ `"${issue.title}"`
198180 );
181+
182+ try {
183+ await github.rest.actions.createWorkflowDispatch({
184+ owner: context.repo.owner,
185+ repo: context.repo.repo,
186+ workflow_id: 'issue-triage.yml',
187+ ref: context.ref || 'main',
188+ inputs: {
189+ process_unlabeled: 'false',
190+ issue_number: issue.number.toString()
191+ }
192+ });
193+ } catch (error) {
194+ console.error(
195+ `Failed to dispatch triage for issue #${issue.number}: ` +
196+ `${error.message}`
197+ );
198+ }
199+
200+ // Add a small delay to avoid rate limiting
201+ await new Promise(resolve => setTimeout(resolve, 100));
202+ }
203+
204+ console.log('Finished dispatching triage workflows');
205+
206+ triage-single-issue :
207+ name : Triage Single Issue
208+ if : |
209+ github.event_name == 'workflow_dispatch' &&
210+ github.event.inputs.issue_number != ''
211+ runs-on : ubuntu-latest
212+ steps :
213+ - name : Get available labels
214+ id : get-labels
215+ uses : actions/github-script@v7
216+ with :
217+ script : |
218+ const labels = await github.rest.issues.listLabelsForRepo({
219+ owner: context.repo.owner,
220+ repo: context.repo.repo,
221+ per_page: 100
222+ });
223+ const labelNames = labels.data.map(label => label.name);
224+ return labelNames.join(', ');
225+
226+ - name : Get issue details
227+ id : get-issue
228+ uses : actions/github-script@v7
229+ with :
230+ script : |
231+ const issue = await github.rest.issues.get({
232+ owner: context.repo.owner,
233+ repo: context.repo.repo,
234+ issue_number: parseInt('${{ github.event.inputs.issue_number }}')
235+ });
236+ return {
237+ title: issue.data.title,
238+ body: issue.data.body || ''
239+ };
240+
241+ - name : Set environment variables
242+ env :
243+ ISSUE_TITLE : ${{ fromJSON(steps.get-issue.outputs.result).title }}
244+ ISSUE_BODY : ${{ fromJSON(steps.get-issue.outputs.result).body }}
245+ LABELS_RESULT : ${{ steps.get-labels.outputs.result }}
246+ run : |
247+ {
248+ echo "AVAILABLE_LABELS=${LABELS_RESULT}"
249+ echo "ISSUE_TITLE=${ISSUE_TITLE}"
250+ echo "ISSUE_BODY<<EOF"
251+ echo "${ISSUE_BODY}"
252+ echo "EOF"
253+ } >> "$GITHUB_ENV"
254+
255+ - name : Analyze issue with AI
256+ id : ai-triage
257+ uses : actions/ai-inference@v1
258+ with :
259+ prompt : |
260+ ## Role
261+
262+ You are an issue triage assistant. Analyze the current GitHub
263+ issue and identify the most appropriate existing labels. Use the
264+ available tools to gather information; do not ask for information
265+ to be provided.
266+
267+ ## Guidelines
268+
269+ - Only use labels that are from the list of available labels.
270+ - You can choose multiple labels to apply.
271+ - When generating shell commands, you **MUST NOT** use command
272+ substitution with `$(...)`, `<(...)`, or `>(...)`. This is a
273+ security measure to prevent unintended command execution.
274+
275+ ## Input Data
276+
277+ **Available Labels** (comma-separated):
278+ ```
279+ ${{ env.AVAILABLE_LABELS }}
280+ ```
281+
282+ **Issue Title**:
283+ ```
284+ ${{ env.ISSUE_TITLE }}
285+ ```
286+
287+ **Issue Body**:
288+ ```
289+ ${{ env.ISSUE_BODY }}
290+ ```
291+
292+ ## Steps
293+
294+ 1. Review the issue title, issue body, and available labels
295+ provided above.
296+
297+ 2. Based on the issue title and issue body, classify the issue
298+ and choose all appropriate labels from the list of available
299+ labels.
300+
301+ 3. Return only the selected labels as a comma-separated list,
302+ with no additional text or explanation. For example:
303+ ```
304+ label1, label2, label3
305+ ```
306+
307+ - name : Apply labels
308+ if : steps.ai-triage.outputs.response != ''
309+ uses : actions/github-script@v7
310+ env :
311+ AI_RESPONSE : ${{ steps.ai-triage.outputs.response }}
312+ ISSUE_NUMBER : ${{ github.event.inputs.issue_number }}
313+ with :
314+ script : |
315+ const response = process.env.AI_RESPONSE;
316+ const issueNumber = parseInt(process.env.ISSUE_NUMBER);
317+
318+ if (!response || response.trim() === '') {
319+ console.log('No labels selected by AI');
320+ return;
321+ }
322+
323+ const labels = response.split(',')
324+ .map(l => l.trim())
325+ .filter(l => l.length > 0);
326+
327+ if (labels.length > 0) {
328+ console.log(`Applying labels: ${labels.join(', ')}`);
329+ await github.rest.issues.addLabels({
330+ owner: context.repo.owner,
331+ repo: context.repo.repo,
332+ issue_number: issueNumber,
333+ labels: labels
334+ });
335+ } else {
336+ console.log('No valid labels to apply');
199337 }
0 commit comments