From 8116535cb49026bdf998c364488051190f4f0b7b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 14:27:06 +0000 Subject: [PATCH 1/9] Initial plan From 4182ada4e762af2e8135b546a43af5defc6d8f2f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 14:38:24 +0000 Subject: [PATCH 2/9] Add issue triage workflow with automatic labeling Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- .github/workflows/issue-triage.yml | 185 +++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 .github/workflows/issue-triage.yml diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml new file mode 100644 index 0000000..79b8e5a --- /dev/null +++ b/.github/workflows/issue-triage.yml @@ -0,0 +1,185 @@ +--- +name: Issue Triage + +'on': + issues: + types: [opened] + workflow_dispatch: + inputs: + process_unlabeled: + description: 'Process all open issues without labels' + required: false + default: 'true' + type: boolean + +permissions: + issues: write + contents: read + +jobs: + triage-new-issue: + name: Triage New Issue + if: github.event_name == 'issues' + runs-on: ubuntu-latest + steps: + - name: Analyze issue and apply labels + uses: actions/github-script@v7 + with: + script: | + const issue = context.payload.issue; + const issueBody = issue.body || ''; + const issueTitle = issue.title || ''; + const content = (issueTitle + ' ' + issueBody).toLowerCase(); + + // Determine appropriate labels based on content analysis + const labels = []; + + // Check if it matches bug report patterns + if (content.includes('bug report') || + content.includes('buggy behavior') || + content.includes('error') || + content.includes('broken') || + content.includes('not working') || + content.includes('issue') || + content.includes('problem')) { + labels.push('i: bug'); + } + + // Check if it matches feature request patterns + if (content.includes('feature request') || + content.includes('enhancement') || + content.includes('suggestion') || + content.includes('would like') || + content.includes('could you') || + content.includes('add support for')) { + labels.push('i: enhancement'); + } + + // Check if it matches support request patterns + if (content.includes('support request') || + content.includes('how to') || + content.includes('question') || + content.includes('help needed') || + (content.includes('how do') && + content.includes('?'))) { + labels.push('i: state:unsupported'); + } + + // Apply labels if any were determined + if (labels.length > 0) { + console.log(`Applying labels: ${labels.join(', ')}`); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: labels + }); + } else { + console.log( + 'No appropriate labels determined for this issue' + ); + } + + triage-unlabeled-issues: + name: Triage Unlabeled Issues + if: github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + steps: + - name: Find and label unlabeled issues + uses: actions/github-script@v7 + with: + script: | + // Get all open issues + const issues = await github.paginate( + github.rest.issues.listForRepo, + { + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page: 100 + } + ); + + console.log(`Found ${issues.length} open issues`); + + // Filter issues without labels + const unlabeledIssues = issues.filter(issue => + !issue.pull_request && issue.labels.length === 0 + ); + + console.log( + `Found ${unlabeledIssues.length} unlabeled issues` + ); + + // Process each unlabeled issue + for (const issue of unlabeledIssues) { + const issueBody = issue.body || ''; + const issueTitle = issue.title || ''; + const content = ( + issueTitle + ' ' + issueBody + ).toLowerCase(); + + const labels = []; + + // Check if it matches bug report patterns + if (content.includes('bug report') || + content.includes('buggy behavior') || + content.includes('error') || + content.includes('broken') || + content.includes('not working') || + content.includes('issue') || + content.includes('problem')) { + labels.push('i: bug'); + } + + // Check if it matches feature request patterns + if (content.includes('feature request') || + content.includes('enhancement') || + content.includes('suggestion') || + content.includes('would like') || + content.includes('could you') || + content.includes('add support for')) { + labels.push('i: enhancement'); + } + + // Check if it matches support request patterns + if (content.includes('support request') || + content.includes('how to') || + content.includes('question') || + content.includes('help needed') || + (content.includes('how do') && + content.includes('?'))) { + labels.push('i: state:unsupported'); + } + + // Apply labels if any were determined + if (labels.length > 0) { + const labelList = labels.join(', '); + console.log( + `Issue #${issue.number}: Applying labels: ${labelList}` + ); + try { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: labels + }); + } catch (error) { + console.error( + `Failed to label issue #${issue.number}: ` + + `${error.message}` + ); + } + } else { + console.log( + `Issue #${issue.number}: ` + + `No appropriate labels determined` + ); + } + + // Add a small delay to avoid rate limiting + await new Promise(resolve => setTimeout(resolve, 100)); + } + + console.log('Finished processing unlabeled issues'); From 774bd2d6384a1ccc7b985aa8f2f32198bdc2ea68 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 14:41:58 +0000 Subject: [PATCH 3/9] Add issue-triage workflow to sync list Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- .github/workflows/sync-workflows.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sync-workflows.yml b/.github/workflows/sync-workflows.yml index f7083ee..75502ac 100644 --- a/.github/workflows/sync-workflows.yml +++ b/.github/workflows/sync-workflows.yml @@ -24,6 +24,7 @@ jobs: ^.editorconfig ^.github/workflows/code-quality.yml ^.github/workflows/regenerate-readme.yml + ^.github/workflows/issue-triage.yml TARGET_REPOS: | wp-cli/admin-command wp-cli/cache-command From 6245837d9fc55705661de6dc70eecfd51f19f43a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 14:44:02 +0000 Subject: [PATCH 4/9] Refactor issue triage workflow to reduce duplication and improve accuracy Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- .github/workflows/issue-triage.yml | 147 ++++++++++++++++------------- 1 file changed, 81 insertions(+), 66 deletions(-) diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml index 79b8e5a..f3017c3 100644 --- a/.github/workflows/issue-triage.yml +++ b/.github/workflows/issue-triage.yml @@ -26,44 +26,52 @@ jobs: uses: actions/github-script@v7 with: script: | + // Function to determine labels based on content + function determineLabels(title, body) { + const content = (title + ' ' + body).toLowerCase(); + const labels = []; + + // Check if it matches bug report patterns + if (content.includes('bug report') || + content.includes('buggy behavior') || + content.includes('error') || + content.includes('broken') || + content.includes('not working') || + content.includes('fails to') || + content.includes('crash') || + content.includes('problem')) { + labels.push('i: bug'); + } + + // Check if it matches feature request patterns + if (content.includes('feature request') || + content.includes('enhancement') || + content.includes('suggestion') || + content.includes('would like') || + content.includes('could you') || + content.includes('add support for') || + content.includes('it would be nice')) { + labels.push('i: enhancement'); + } + + // Check if it matches support request patterns + if (content.includes('support request') || + content.includes('how to') || + content.includes('question') || + content.includes('help needed') || + (content.includes('how do') && + content.includes('?'))) { + labels.push('i: state:unsupported'); + } + + return labels; + } + const issue = context.payload.issue; const issueBody = issue.body || ''; const issueTitle = issue.title || ''; - const content = (issueTitle + ' ' + issueBody).toLowerCase(); - - // Determine appropriate labels based on content analysis - const labels = []; - - // Check if it matches bug report patterns - if (content.includes('bug report') || - content.includes('buggy behavior') || - content.includes('error') || - content.includes('broken') || - content.includes('not working') || - content.includes('issue') || - content.includes('problem')) { - labels.push('i: bug'); - } - // Check if it matches feature request patterns - if (content.includes('feature request') || - content.includes('enhancement') || - content.includes('suggestion') || - content.includes('would like') || - content.includes('could you') || - content.includes('add support for')) { - labels.push('i: enhancement'); - } - - // Check if it matches support request patterns - if (content.includes('support request') || - content.includes('how to') || - content.includes('question') || - content.includes('help needed') || - (content.includes('how do') && - content.includes('?'))) { - labels.push('i: state:unsupported'); - } + const labels = determineLabels(issueTitle, issueBody); // Apply labels if any were determined if (labels.length > 0) { @@ -89,36 +97,9 @@ jobs: uses: actions/github-script@v7 with: script: | - // Get all open issues - const issues = await github.paginate( - github.rest.issues.listForRepo, - { - owner: context.repo.owner, - repo: context.repo.repo, - state: 'open', - per_page: 100 - } - ); - - console.log(`Found ${issues.length} open issues`); - - // Filter issues without labels - const unlabeledIssues = issues.filter(issue => - !issue.pull_request && issue.labels.length === 0 - ); - - console.log( - `Found ${unlabeledIssues.length} unlabeled issues` - ); - - // Process each unlabeled issue - for (const issue of unlabeledIssues) { - const issueBody = issue.body || ''; - const issueTitle = issue.title || ''; - const content = ( - issueTitle + ' ' + issueBody - ).toLowerCase(); - + // Function to determine labels based on content + function determineLabels(title, body) { + const content = (title + ' ' + body).toLowerCase(); const labels = []; // Check if it matches bug report patterns @@ -127,7 +108,8 @@ jobs: content.includes('error') || content.includes('broken') || content.includes('not working') || - content.includes('issue') || + content.includes('fails to') || + content.includes('crash') || content.includes('problem')) { labels.push('i: bug'); } @@ -138,7 +120,8 @@ jobs: content.includes('suggestion') || content.includes('would like') || content.includes('could you') || - content.includes('add support for')) { + content.includes('add support for') || + content.includes('it would be nice')) { labels.push('i: enhancement'); } @@ -152,6 +135,38 @@ jobs: labels.push('i: state:unsupported'); } + return labels; + } + + // Get all open issues + const issues = await github.paginate( + github.rest.issues.listForRepo, + { + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page: 100 + } + ); + + console.log(`Found ${issues.length} open issues`); + + // Filter issues without labels + const unlabeledIssues = issues.filter(issue => + !issue.pull_request && issue.labels.length === 0 + ); + + console.log( + `Found ${unlabeledIssues.length} unlabeled issues` + ); + + // Process each unlabeled issue + for (const issue of unlabeledIssues) { + const issueBody = issue.body || ''; + const issueTitle = issue.title || ''; + + const labels = determineLabels(issueTitle, issueBody); + // Apply labels if any were determined if (labels.length > 0) { const labelList = labels.join(', '); From 64acef421ddf8404d765fe0bf235bb51ca6e7cbc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:01:49 +0000 Subject: [PATCH 5/9] Reimplement issue triage using AI inference action Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- .github/workflows/issue-triage.yml | 247 ++++++++++++++--------------- 1 file changed, 123 insertions(+), 124 deletions(-) diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml index f3017c3..07982d3 100644 --- a/.github/workflows/issue-triage.yml +++ b/.github/workflows/issue-triage.yml @@ -22,70 +22,115 @@ jobs: if: github.event_name == 'issues' runs-on: ubuntu-latest steps: - - name: Analyze issue and apply labels + - name: Get available labels + id: get-labels uses: actions/github-script@v7 with: script: | - // Function to determine labels based on content - function determineLabels(title, body) { - const content = (title + ' ' + body).toLowerCase(); - const labels = []; - - // Check if it matches bug report patterns - if (content.includes('bug report') || - content.includes('buggy behavior') || - content.includes('error') || - content.includes('broken') || - content.includes('not working') || - content.includes('fails to') || - content.includes('crash') || - content.includes('problem')) { - labels.push('i: bug'); - } + const labels = await github.rest.issues.listLabelsForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100 + }); + const labelNames = labels.data.map(label => label.name); + return labelNames.join(', '); + + - name: Set environment variables + env: + ISSUE_TITLE: ${{ github.event.issue.title }} + ISSUE_BODY: ${{ github.event.issue.body }} + LABELS_RESULT: ${{ steps.get-labels.outputs.result }} + run: | + { + echo "AVAILABLE_LABELS=${LABELS_RESULT}" + echo "ISSUE_TITLE=${ISSUE_TITLE}" + echo "ISSUE_BODY<> "$GITHUB_ENV" + + - name: Analyze issue with AI + id: ai-triage + uses: actions/ai-inference@v1 + with: + prompt: | + ## Role - // Check if it matches feature request patterns - if (content.includes('feature request') || - content.includes('enhancement') || - content.includes('suggestion') || - content.includes('would like') || - content.includes('could you') || - content.includes('add support for') || - content.includes('it would be nice')) { - labels.push('i: enhancement'); - } + You are an issue triage assistant. Analyze the current GitHub + issue and identify the most appropriate existing labels. Use the + available tools to gather information; do not ask for information + to be provided. - // Check if it matches support request patterns - if (content.includes('support request') || - content.includes('how to') || - content.includes('question') || - content.includes('help needed') || - (content.includes('how do') && - content.includes('?'))) { - labels.push('i: state:unsupported'); - } + ## Guidelines - return labels; - } + - Only use labels that are from the list of available labels. + - You can choose multiple labels to apply. + - When generating shell commands, you **MUST NOT** use command + substitution with `$(...)`, `<(...)`, or `>(...)`. This is a + security measure to prevent unintended command execution. + + ## Input Data + + **Available Labels** (comma-separated): + ``` + ${{ env.AVAILABLE_LABELS }} + ``` + + **Issue Title**: + ``` + ${{ env.ISSUE_TITLE }} + ``` + + **Issue Body**: + ``` + ${{ env.ISSUE_BODY }} + ``` + + **Output File Path**: + ``` + ${{ env.GITHUB_ENV }} + ``` - const issue = context.payload.issue; - const issueBody = issue.body || ''; - const issueTitle = issue.title || ''; + ## Steps - const labels = determineLabels(issueTitle, issueBody); + 1. Review the issue title, issue body, and available labels + provided above. + + 2. Based on the issue title and issue body, classify the issue + and choose all appropriate labels from the list of available + labels. + + 3. Output the selected labels as a comma-separated list to the + file at the path specified in "Output File Path" using the + following format: + ``` + SELECTED_LABELS=label1,label2,label3 + ``` + + - name: Apply labels + uses: actions/github-script@v7 + with: + script: | + const selectedLabels = process.env.SELECTED_LABELS; + if (!selectedLabels || selectedLabels.trim() === '') { + console.log('No labels selected by AI'); + return; + } + + const labels = selectedLabels.split(',') + .map(l => l.trim()) + .filter(l => l.length > 0); - // Apply labels if any were determined if (labels.length > 0) { console.log(`Applying labels: ${labels.join(', ')}`); await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, - issue_number: issue.number, + issue_number: context.issue.number, labels: labels }); } else { - console.log( - 'No appropriate labels determined for this issue' - ); + console.log('No valid labels to apply'); } triage-unlabeled-issues: @@ -93,50 +138,24 @@ jobs: if: github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest steps: - - name: Find and label unlabeled issues + - name: Get available labels + id: get-labels uses: actions/github-script@v7 with: script: | - // Function to determine labels based on content - function determineLabels(title, body) { - const content = (title + ' ' + body).toLowerCase(); - const labels = []; - - // Check if it matches bug report patterns - if (content.includes('bug report') || - content.includes('buggy behavior') || - content.includes('error') || - content.includes('broken') || - content.includes('not working') || - content.includes('fails to') || - content.includes('crash') || - content.includes('problem')) { - labels.push('i: bug'); - } - - // Check if it matches feature request patterns - if (content.includes('feature request') || - content.includes('enhancement') || - content.includes('suggestion') || - content.includes('would like') || - content.includes('could you') || - content.includes('add support for') || - content.includes('it would be nice')) { - labels.push('i: enhancement'); - } - - // Check if it matches support request patterns - if (content.includes('support request') || - content.includes('how to') || - content.includes('question') || - content.includes('help needed') || - (content.includes('how do') && - content.includes('?'))) { - labels.push('i: state:unsupported'); - } - - return labels; - } + const labels = await github.rest.issues.listLabelsForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100 + }); + const labelNames = labels.data.map(label => label.name); + return labelNames.join(', '); + + - name: Find and triage unlabeled issues + uses: actions/github-script@v7 + with: + script: | + const availableLabels = ${{ steps.get-labels.outputs.result }}; // Get all open issues const issues = await github.paginate( @@ -160,41 +179,21 @@ jobs: `Found ${unlabeledIssues.length} unlabeled issues` ); - // Process each unlabeled issue - for (const issue of unlabeledIssues) { - const issueBody = issue.body || ''; - const issueTitle = issue.title || ''; - - const labels = determineLabels(issueTitle, issueBody); - - // Apply labels if any were determined - if (labels.length > 0) { - const labelList = labels.join(', '); - console.log( - `Issue #${issue.number}: Applying labels: ${labelList}` - ); - try { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - labels: labels - }); - } catch (error) { - console.error( - `Failed to label issue #${issue.number}: ` + - `${error.message}` - ); - } - } else { - console.log( - `Issue #${issue.number}: ` + - `No appropriate labels determined` - ); - } - - // Add a small delay to avoid rate limiting - await new Promise(resolve => setTimeout(resolve, 100)); + if (unlabeledIssues.length === 0) { + console.log('No unlabeled issues to process'); + return; } - console.log('Finished processing unlabeled issues'); + // For manual dispatch, we'll log that AI triage is not yet + // implemented for batch processing + console.log( + 'Note: Batch AI triage for unlabeled issues requires ' + + 'manual triggering of individual issue workflows or ' + + 'implementing batch AI processing in the future.' + ); + + for (const issue of unlabeledIssues) { + console.log( + `Issue #${issue.number}: "${issue.title}" needs triage` + ); + } From 7b6dc58398ae15e23d1855608379b8a2b80b58bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:36:35 +0000 Subject: [PATCH 6/9] Fix AI inference output handling and implement batch triage via workflow dispatch Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- .github/workflows/issue-triage.yml | 214 ++++++++++++++++++++++++----- 1 file changed, 176 insertions(+), 38 deletions(-) diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml index 07982d3..c505ada 100644 --- a/.github/workflows/issue-triage.yml +++ b/.github/workflows/issue-triage.yml @@ -11,6 +11,10 @@ name: Issue Triage required: false default: 'true' type: boolean + issue_number: + description: 'Issue number to triage (leave empty to process all)' + required: false + type: string permissions: issues: write @@ -86,11 +90,6 @@ jobs: ${{ env.ISSUE_BODY }} ``` - **Output File Path**: - ``` - ${{ env.GITHUB_ENV }} - ``` - ## Steps 1. Review the issue title, issue body, and available labels @@ -100,24 +99,26 @@ jobs: and choose all appropriate labels from the list of available labels. - 3. Output the selected labels as a comma-separated list to the - file at the path specified in "Output File Path" using the - following format: + 3. Return only the selected labels as a comma-separated list, + with no additional text or explanation. For example: ``` - SELECTED_LABELS=label1,label2,label3 + label1, label2, label3 ``` - name: Apply labels + if: steps.ai-triage.outputs.response != '' uses: actions/github-script@v7 + env: + AI_RESPONSE: ${{ steps.ai-triage.outputs.response }} with: script: | - const selectedLabels = process.env.SELECTED_LABELS; - if (!selectedLabels || selectedLabels.trim() === '') { + const response = process.env.AI_RESPONSE; + if (!response || response.trim() === '') { console.log('No labels selected by AI'); return; } - const labels = selectedLabels.split(',') + const labels = response.split(',') .map(l => l.trim()) .filter(l => l.length > 0); @@ -135,28 +136,15 @@ jobs: triage-unlabeled-issues: name: Triage Unlabeled Issues - if: github.event_name == 'workflow_dispatch' + if: | + github.event_name == 'workflow_dispatch' && + github.event.inputs.issue_number == '' runs-on: ubuntu-latest steps: - - name: Get available labels - id: get-labels - uses: actions/github-script@v7 - with: - script: | - const labels = await github.rest.issues.listLabelsForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - per_page: 100 - }); - const labelNames = labels.data.map(label => label.name); - return labelNames.join(', '); - - - name: Find and triage unlabeled issues + - name: Find and dispatch triage for unlabeled issues uses: actions/github-script@v7 with: script: | - const availableLabels = ${{ steps.get-labels.outputs.result }}; - // Get all open issues const issues = await github.paginate( github.rest.issues.listForRepo, @@ -184,16 +172,166 @@ jobs: return; } - // For manual dispatch, we'll log that AI triage is not yet - // implemented for batch processing - console.log( - 'Note: Batch AI triage for unlabeled issues requires ' + - 'manual triggering of individual issue workflows or ' + - 'implementing batch AI processing in the future.' - ); - + // Dispatch triage workflow for each unlabeled issue for (const issue of unlabeledIssues) { console.log( - `Issue #${issue.number}: "${issue.title}" needs triage` + `Dispatching triage for issue #${issue.number}: ` + + `"${issue.title}"` ); + + try { + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'issue-triage.yml', + ref: context.ref || 'main', + inputs: { + process_unlabeled: 'false', + issue_number: issue.number.toString() + } + }); + } catch (error) { + console.error( + `Failed to dispatch triage for issue #${issue.number}: ` + + `${error.message}` + ); + } + + // Add a small delay to avoid rate limiting + await new Promise(resolve => setTimeout(resolve, 100)); + } + + console.log('Finished dispatching triage workflows'); + + triage-single-issue: + name: Triage Single Issue + if: | + github.event_name == 'workflow_dispatch' && + github.event.inputs.issue_number != '' + runs-on: ubuntu-latest + steps: + - name: Get available labels + id: get-labels + uses: actions/github-script@v7 + with: + script: | + const labels = await github.rest.issues.listLabelsForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100 + }); + const labelNames = labels.data.map(label => label.name); + return labelNames.join(', '); + + - name: Get issue details + id: get-issue + uses: actions/github-script@v7 + with: + script: | + const issue = await github.rest.issues.get({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: parseInt('${{ github.event.inputs.issue_number }}') + }); + return { + title: issue.data.title, + body: issue.data.body || '' + }; + + - name: Set environment variables + env: + ISSUE_TITLE: ${{ fromJSON(steps.get-issue.outputs.result).title }} + ISSUE_BODY: ${{ fromJSON(steps.get-issue.outputs.result).body }} + LABELS_RESULT: ${{ steps.get-labels.outputs.result }} + run: | + { + echo "AVAILABLE_LABELS=${LABELS_RESULT}" + echo "ISSUE_TITLE=${ISSUE_TITLE}" + echo "ISSUE_BODY<> "$GITHUB_ENV" + + - name: Analyze issue with AI + id: ai-triage + uses: actions/ai-inference@v1 + with: + prompt: | + ## Role + + You are an issue triage assistant. Analyze the current GitHub + issue and identify the most appropriate existing labels. Use the + available tools to gather information; do not ask for information + to be provided. + + ## Guidelines + + - Only use labels that are from the list of available labels. + - You can choose multiple labels to apply. + - When generating shell commands, you **MUST NOT** use command + substitution with `$(...)`, `<(...)`, or `>(...)`. This is a + security measure to prevent unintended command execution. + + ## Input Data + + **Available Labels** (comma-separated): + ``` + ${{ env.AVAILABLE_LABELS }} + ``` + + **Issue Title**: + ``` + ${{ env.ISSUE_TITLE }} + ``` + + **Issue Body**: + ``` + ${{ env.ISSUE_BODY }} + ``` + + ## Steps + + 1. Review the issue title, issue body, and available labels + provided above. + + 2. Based on the issue title and issue body, classify the issue + and choose all appropriate labels from the list of available + labels. + + 3. Return only the selected labels as a comma-separated list, + with no additional text or explanation. For example: + ``` + label1, label2, label3 + ``` + + - name: Apply labels + if: steps.ai-triage.outputs.response != '' + uses: actions/github-script@v7 + env: + AI_RESPONSE: ${{ steps.ai-triage.outputs.response }} + ISSUE_NUMBER: ${{ github.event.inputs.issue_number }} + with: + script: | + const response = process.env.AI_RESPONSE; + const issueNumber = parseInt(process.env.ISSUE_NUMBER); + + if (!response || response.trim() === '') { + console.log('No labels selected by AI'); + return; + } + + const labels = response.split(',') + .map(l => l.trim()) + .filter(l => l.length > 0); + + if (labels.length > 0) { + console.log(`Applying labels: ${labels.join(', ')}`); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + labels: labels + }); + } else { + console.log('No valid labels to apply'); } From ea561128e3a070dbfefc5d9086fd1ba39322e446 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 16:58:01 +0000 Subject: [PATCH 7/9] Split workflow into reusable and calling workflows Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- .github/workflows/issue-triage.yml | 322 +------------------ .github/workflows/reusable-issue-triage.yml | 330 ++++++++++++++++++++ 2 files changed, 334 insertions(+), 318 deletions(-) create mode 100644 .github/workflows/reusable-issue-triage.yml diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml index c505ada..b87c89e 100644 --- a/.github/workflows/issue-triage.yml +++ b/.github/workflows/issue-triage.yml @@ -16,322 +16,8 @@ name: Issue Triage required: false type: string -permissions: - issues: write - contents: read - jobs: - triage-new-issue: - name: Triage New Issue - if: github.event_name == 'issues' - runs-on: ubuntu-latest - steps: - - name: Get available labels - id: get-labels - uses: actions/github-script@v7 - with: - script: | - const labels = await github.rest.issues.listLabelsForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - per_page: 100 - }); - const labelNames = labels.data.map(label => label.name); - return labelNames.join(', '); - - - name: Set environment variables - env: - ISSUE_TITLE: ${{ github.event.issue.title }} - ISSUE_BODY: ${{ github.event.issue.body }} - LABELS_RESULT: ${{ steps.get-labels.outputs.result }} - run: | - { - echo "AVAILABLE_LABELS=${LABELS_RESULT}" - echo "ISSUE_TITLE=${ISSUE_TITLE}" - echo "ISSUE_BODY<> "$GITHUB_ENV" - - - name: Analyze issue with AI - id: ai-triage - uses: actions/ai-inference@v1 - with: - prompt: | - ## Role - - You are an issue triage assistant. Analyze the current GitHub - issue and identify the most appropriate existing labels. Use the - available tools to gather information; do not ask for information - to be provided. - - ## Guidelines - - - Only use labels that are from the list of available labels. - - You can choose multiple labels to apply. - - When generating shell commands, you **MUST NOT** use command - substitution with `$(...)`, `<(...)`, or `>(...)`. This is a - security measure to prevent unintended command execution. - - ## Input Data - - **Available Labels** (comma-separated): - ``` - ${{ env.AVAILABLE_LABELS }} - ``` - - **Issue Title**: - ``` - ${{ env.ISSUE_TITLE }} - ``` - - **Issue Body**: - ``` - ${{ env.ISSUE_BODY }} - ``` - - ## Steps - - 1. Review the issue title, issue body, and available labels - provided above. - - 2. Based on the issue title and issue body, classify the issue - and choose all appropriate labels from the list of available - labels. - - 3. Return only the selected labels as a comma-separated list, - with no additional text or explanation. For example: - ``` - label1, label2, label3 - ``` - - - name: Apply labels - if: steps.ai-triage.outputs.response != '' - uses: actions/github-script@v7 - env: - AI_RESPONSE: ${{ steps.ai-triage.outputs.response }} - with: - script: | - const response = process.env.AI_RESPONSE; - if (!response || response.trim() === '') { - console.log('No labels selected by AI'); - return; - } - - const labels = response.split(',') - .map(l => l.trim()) - .filter(l => l.length > 0); - - if (labels.length > 0) { - console.log(`Applying labels: ${labels.join(', ')}`); - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - labels: labels - }); - } else { - console.log('No valid labels to apply'); - } - - triage-unlabeled-issues: - name: Triage Unlabeled Issues - if: | - github.event_name == 'workflow_dispatch' && - github.event.inputs.issue_number == '' - runs-on: ubuntu-latest - steps: - - name: Find and dispatch triage for unlabeled issues - uses: actions/github-script@v7 - with: - script: | - // Get all open issues - const issues = await github.paginate( - github.rest.issues.listForRepo, - { - owner: context.repo.owner, - repo: context.repo.repo, - state: 'open', - per_page: 100 - } - ); - - console.log(`Found ${issues.length} open issues`); - - // Filter issues without labels - const unlabeledIssues = issues.filter(issue => - !issue.pull_request && issue.labels.length === 0 - ); - - console.log( - `Found ${unlabeledIssues.length} unlabeled issues` - ); - - if (unlabeledIssues.length === 0) { - console.log('No unlabeled issues to process'); - return; - } - - // Dispatch triage workflow for each unlabeled issue - for (const issue of unlabeledIssues) { - console.log( - `Dispatching triage for issue #${issue.number}: ` + - `"${issue.title}"` - ); - - try { - await github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: 'issue-triage.yml', - ref: context.ref || 'main', - inputs: { - process_unlabeled: 'false', - issue_number: issue.number.toString() - } - }); - } catch (error) { - console.error( - `Failed to dispatch triage for issue #${issue.number}: ` + - `${error.message}` - ); - } - - // Add a small delay to avoid rate limiting - await new Promise(resolve => setTimeout(resolve, 100)); - } - - console.log('Finished dispatching triage workflows'); - - triage-single-issue: - name: Triage Single Issue - if: | - github.event_name == 'workflow_dispatch' && - github.event.inputs.issue_number != '' - runs-on: ubuntu-latest - steps: - - name: Get available labels - id: get-labels - uses: actions/github-script@v7 - with: - script: | - const labels = await github.rest.issues.listLabelsForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - per_page: 100 - }); - const labelNames = labels.data.map(label => label.name); - return labelNames.join(', '); - - - name: Get issue details - id: get-issue - uses: actions/github-script@v7 - with: - script: | - const issue = await github.rest.issues.get({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: parseInt('${{ github.event.inputs.issue_number }}') - }); - return { - title: issue.data.title, - body: issue.data.body || '' - }; - - - name: Set environment variables - env: - ISSUE_TITLE: ${{ fromJSON(steps.get-issue.outputs.result).title }} - ISSUE_BODY: ${{ fromJSON(steps.get-issue.outputs.result).body }} - LABELS_RESULT: ${{ steps.get-labels.outputs.result }} - run: | - { - echo "AVAILABLE_LABELS=${LABELS_RESULT}" - echo "ISSUE_TITLE=${ISSUE_TITLE}" - echo "ISSUE_BODY<> "$GITHUB_ENV" - - - name: Analyze issue with AI - id: ai-triage - uses: actions/ai-inference@v1 - with: - prompt: | - ## Role - - You are an issue triage assistant. Analyze the current GitHub - issue and identify the most appropriate existing labels. Use the - available tools to gather information; do not ask for information - to be provided. - - ## Guidelines - - - Only use labels that are from the list of available labels. - - You can choose multiple labels to apply. - - When generating shell commands, you **MUST NOT** use command - substitution with `$(...)`, `<(...)`, or `>(...)`. This is a - security measure to prevent unintended command execution. - - ## Input Data - - **Available Labels** (comma-separated): - ``` - ${{ env.AVAILABLE_LABELS }} - ``` - - **Issue Title**: - ``` - ${{ env.ISSUE_TITLE }} - ``` - - **Issue Body**: - ``` - ${{ env.ISSUE_BODY }} - ``` - - ## Steps - - 1. Review the issue title, issue body, and available labels - provided above. - - 2. Based on the issue title and issue body, classify the issue - and choose all appropriate labels from the list of available - labels. - - 3. Return only the selected labels as a comma-separated list, - with no additional text or explanation. For example: - ``` - label1, label2, label3 - ``` - - - name: Apply labels - if: steps.ai-triage.outputs.response != '' - uses: actions/github-script@v7 - env: - AI_RESPONSE: ${{ steps.ai-triage.outputs.response }} - ISSUE_NUMBER: ${{ github.event.inputs.issue_number }} - with: - script: | - const response = process.env.AI_RESPONSE; - const issueNumber = parseInt(process.env.ISSUE_NUMBER); - - if (!response || response.trim() === '') { - console.log('No labels selected by AI'); - return; - } - - const labels = response.split(',') - .map(l => l.trim()) - .filter(l => l.length > 0); - - if (labels.length > 0) { - console.log(`Applying labels: ${labels.join(', ')}`); - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueNumber, - labels: labels - }); - } else { - console.log('No valid labels to apply'); - } + issue-triage: + uses: wp-cli/.github/.github/workflows/reusable-issue-triage.yml@main + with: + issue_number: ${{ inputs.issue_number }} diff --git a/.github/workflows/reusable-issue-triage.yml b/.github/workflows/reusable-issue-triage.yml new file mode 100644 index 0000000..e9781b6 --- /dev/null +++ b/.github/workflows/reusable-issue-triage.yml @@ -0,0 +1,330 @@ +--- +name: Issue Triage + +'on': + workflow_call: + inputs: + issue_number: + description: 'Issue number to triage (leave empty to process all)' + required: false + type: string + +permissions: + issues: write + contents: read + +jobs: + triage-new-issue: + name: Triage New Issue + if: github.event_name == 'issues' + runs-on: ubuntu-latest + steps: + - name: Get available labels + id: get-labels + uses: actions/github-script@v7 + with: + script: | + const labels = await github.rest.issues.listLabelsForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100 + }); + const labelNames = labels.data.map(label => label.name); + return labelNames.join(', '); + + - name: Set environment variables + env: + ISSUE_TITLE: ${{ github.event.issue.title }} + ISSUE_BODY: ${{ github.event.issue.body }} + LABELS_RESULT: ${{ steps.get-labels.outputs.result }} + run: | + { + echo "AVAILABLE_LABELS=${LABELS_RESULT}" + echo "ISSUE_TITLE=${ISSUE_TITLE}" + echo "ISSUE_BODY<> "$GITHUB_ENV" + + - name: Analyze issue with AI + id: ai-triage + uses: actions/ai-inference@v1 + with: + prompt: | + ## Role + + You are an issue triage assistant. Analyze the current GitHub + issue and identify the most appropriate existing labels. Use the + available tools to gather information; do not ask for information + to be provided. + + ## Guidelines + + - Only use labels that are from the list of available labels. + - You can choose multiple labels to apply. + - When generating shell commands, you **MUST NOT** use command + substitution with `$(...)`, `<(...)`, or `>(...)`. This is a + security measure to prevent unintended command execution. + + ## Input Data + + **Available Labels** (comma-separated): + ``` + ${{ env.AVAILABLE_LABELS }} + ``` + + **Issue Title**: + ``` + ${{ env.ISSUE_TITLE }} + ``` + + **Issue Body**: + ``` + ${{ env.ISSUE_BODY }} + ``` + + ## Steps + + 1. Review the issue title, issue body, and available labels + provided above. + + 2. Based on the issue title and issue body, classify the issue + and choose all appropriate labels from the list of available + labels. + + 3. Return only the selected labels as a comma-separated list, + with no additional text or explanation. For example: + ``` + label1, label2, label3 + ``` + + - name: Apply labels + if: steps.ai-triage.outputs.response != '' + uses: actions/github-script@v7 + env: + AI_RESPONSE: ${{ steps.ai-triage.outputs.response }} + with: + script: | + const response = process.env.AI_RESPONSE; + if (!response || response.trim() === '') { + console.log('No labels selected by AI'); + return; + } + + const labels = response.split(',') + .map(l => l.trim()) + .filter(l => l.length > 0); + + if (labels.length > 0) { + console.log(`Applying labels: ${labels.join(', ')}`); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: labels + }); + } else { + console.log('No valid labels to apply'); + } + + triage-unlabeled-issues: + name: Triage Unlabeled Issues + if: | + github.event_name == 'workflow_dispatch' && + inputs.issue_number == '' + runs-on: ubuntu-latest + steps: + - name: Find and dispatch triage for unlabeled issues + uses: actions/github-script@v7 + with: + script: | + // Get all open issues + const issues = await github.paginate( + github.rest.issues.listForRepo, + { + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page: 100 + } + ); + + console.log(`Found ${issues.length} open issues`); + + // Filter issues without labels + const unlabeledIssues = issues.filter(issue => + !issue.pull_request && issue.labels.length === 0 + ); + + console.log( + `Found ${unlabeledIssues.length} unlabeled issues` + ); + + if (unlabeledIssues.length === 0) { + console.log('No unlabeled issues to process'); + return; + } + + // Dispatch triage workflow for each unlabeled issue + for (const issue of unlabeledIssues) { + console.log( + `Dispatching triage for issue #${issue.number}: ` + + `"${issue.title}"` + ); + + try { + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'issue-triage.yml', + ref: context.ref || 'main', + inputs: { + process_unlabeled: 'false', + issue_number: issue.number.toString() + } + }); + } catch (error) { + console.error( + `Failed to dispatch triage for issue #${issue.number}: ` + + `${error.message}` + ); + } + + // Add a small delay to avoid rate limiting + await new Promise(resolve => setTimeout(resolve, 100)); + } + + console.log('Finished dispatching triage workflows'); + + triage-single-issue: + name: Triage Single Issue + if: | + github.event_name == 'workflow_dispatch' && + inputs.issue_number != '' + runs-on: ubuntu-latest + steps: + - name: Get available labels + id: get-labels + uses: actions/github-script@v7 + with: + script: | + const labels = await github.rest.issues.listLabelsForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100 + }); + const labelNames = labels.data.map(label => label.name); + return labelNames.join(', '); + + - name: Get issue details + id: get-issue + uses: actions/github-script@v7 + with: + script: | + const issue = await github.rest.issues.get({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: parseInt('${{ inputs.issue_number }}') + }); + return { + title: issue.data.title, + body: issue.data.body || '' + }; + + - name: Set environment variables + env: + ISSUE_TITLE: ${{ fromJSON(steps.get-issue.outputs.result).title }} + ISSUE_BODY: ${{ fromJSON(steps.get-issue.outputs.result).body }} + LABELS_RESULT: ${{ steps.get-labels.outputs.result }} + run: | + { + echo "AVAILABLE_LABELS=${LABELS_RESULT}" + echo "ISSUE_TITLE=${ISSUE_TITLE}" + echo "ISSUE_BODY<> "$GITHUB_ENV" + + - name: Analyze issue with AI + id: ai-triage + uses: actions/ai-inference@v1 + with: + prompt: | + ## Role + + You are an issue triage assistant. Analyze the current GitHub + issue and identify the most appropriate existing labels. Use the + available tools to gather information; do not ask for information + to be provided. + + ## Guidelines + + - Only use labels that are from the list of available labels. + - You can choose multiple labels to apply. + - When generating shell commands, you **MUST NOT** use command + substitution with `$(...)`, `<(...)`, or `>(...)`. This is a + security measure to prevent unintended command execution. + + ## Input Data + + **Available Labels** (comma-separated): + ``` + ${{ env.AVAILABLE_LABELS }} + ``` + + **Issue Title**: + ``` + ${{ env.ISSUE_TITLE }} + ``` + + **Issue Body**: + ``` + ${{ env.ISSUE_BODY }} + ``` + + ## Steps + + 1. Review the issue title, issue body, and available labels + provided above. + + 2. Based on the issue title and issue body, classify the issue + and choose all appropriate labels from the list of available + labels. + + 3. Return only the selected labels as a comma-separated list, + with no additional text or explanation. For example: + ``` + label1, label2, label3 + ``` + + - name: Apply labels + if: steps.ai-triage.outputs.response != '' + uses: actions/github-script@v7 + env: + AI_RESPONSE: ${{ steps.ai-triage.outputs.response }} + ISSUE_NUMBER: ${{ inputs.issue_number }} + with: + script: | + const response = process.env.AI_RESPONSE; + const issueNumber = parseInt(process.env.ISSUE_NUMBER); + + if (!response || response.trim() === '') { + console.log('No labels selected by AI'); + return; + } + + const labels = response.split(',') + .map(l => l.trim()) + .filter(l => l.length > 0); + + if (labels.length > 0) { + console.log(`Applying labels: ${labels.join(', ')}`); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + labels: labels + }); + } else { + console.log('No valid labels to apply'); + } From 46601c2d5a76a6bcabdee213e99f1b11e8aacdcb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 20:38:23 +0000 Subject: [PATCH 8/9] Remove unused process_unlabeled input parameter Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- .github/workflows/issue-triage.yml | 5 ----- .github/workflows/reusable-issue-triage.yml | 1 - 2 files changed, 6 deletions(-) diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml index b87c89e..1d046f9 100644 --- a/.github/workflows/issue-triage.yml +++ b/.github/workflows/issue-triage.yml @@ -6,11 +6,6 @@ name: Issue Triage types: [opened] workflow_dispatch: inputs: - process_unlabeled: - description: 'Process all open issues without labels' - required: false - default: 'true' - type: boolean issue_number: description: 'Issue number to triage (leave empty to process all)' required: false diff --git a/.github/workflows/reusable-issue-triage.yml b/.github/workflows/reusable-issue-triage.yml index e9781b6..bab5892 100644 --- a/.github/workflows/reusable-issue-triage.yml +++ b/.github/workflows/reusable-issue-triage.yml @@ -179,7 +179,6 @@ jobs: workflow_id: 'issue-triage.yml', ref: context.ref || 'main', inputs: { - process_unlabeled: 'false', issue_number: issue.number.toString() } }); From 4f7c000e3cf03dd4fcf5cb6e7a87225a06c4058f Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 3 Nov 2025 21:46:58 +0100 Subject: [PATCH 9/9] Apply suggestions Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/issue-triage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml index 1d046f9..634607e 100644 --- a/.github/workflows/issue-triage.yml +++ b/.github/workflows/issue-triage.yml @@ -15,4 +15,4 @@ jobs: issue-triage: uses: wp-cli/.github/.github/workflows/reusable-issue-triage.yml@main with: - issue_number: ${{ inputs.issue_number }} + issue_number: ${{ github.event_name == 'workflow_dispatch' && inputs.issue_number || github.event.issue.number }}