diff --git a/.github/workflows/docs-from-code.yml b/.github/workflows/docs-from-code.yml new file mode 100644 index 00000000..1e8b89a9 --- /dev/null +++ b/.github/workflows/docs-from-code.yml @@ -0,0 +1,121 @@ +name: Process Docs from Code Issues + +on: + issues: + types: [labeled] + +permissions: + contents: read + issues: write + +jobs: + assign-to-copilot: + name: Assign to Copilot Agent + runs-on: ubuntu-latest + if: github.event.action == 'labeled' && github.event.label.name == 'docs-from-code' + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Extract SME from issue body + id: extract-sme + uses: actions/github-script@v7 + with: + script: | + const issueBody = context.payload.issue.body || ''; + + // Look for PR author pattern in issue body + // Expected format from dotnet/aspire workflow: "PR Author: @username" or similar + const authorPatterns = [ + /PR\s*Author:\s*@?([\w-]+)/i, + /Author:\s*@?([\w-]+)/i, + /SME:\s*@?([\w-]+)/i, + /Original\s*PR\s*by\s*@?([\w-]+)/i, + /Created\s*by\s*@([\w-]+)/i + ]; + + let smeUsername = null; + + for (const pattern of authorPatterns) { + const match = issueBody.match(pattern); + if (match) { + smeUsername = match[1]; + break; + } + } + + // Also try to extract PR URL if present + const prUrlPattern = /https:\/\/github\.com\/dotnet\/aspire\/pull\/(\d+)/; + const prUrlMatch = issueBody.match(prUrlPattern); + + if (!smeUsername && prUrlMatch) { + // If we found a PR URL but no author, we'll need to fetch it + const prNumber = prUrlMatch[1]; + try { + const { data: pr } = await github.rest.pulls.get({ + owner: 'dotnet', + repo: 'aspire', + pull_number: parseInt(prNumber) + }); + smeUsername = pr.user.login; + console.log(`Extracted SME from PR #${prNumber}: ${smeUsername}`); + } catch (error) { + console.log(`Could not fetch PR details: ${error.message}`); + } + } + + core.setOutput('sme', smeUsername || ''); + core.setOutput('found', smeUsername ? 'true' : 'false'); + + console.log(`SME Username: ${smeUsername || 'not found'}`); + + - name: Assign issue to Copilot + uses: actions/github-script@v7 + with: + script: | + const issueNumber = context.payload.issue.number; + + // Assign the issue to copilot + try { + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + assignees: ['copilot'] + }); + console.log(`Successfully assigned issue #${issueNumber} to copilot`); + } catch (error) { + console.log(`Could not assign to copilot: ${error.message}`); + // Continue even if assignment fails + } + + - name: Add comment mentioning SME + uses: actions/github-script@v7 + with: + script: | + const issueNumber = context.payload.issue.number; + const smeUsername = '${{ steps.extract-sme.outputs.sme }}'; + const smeFound = '${{ steps.extract-sme.outputs.found }}' === 'true'; + + let commentBody = '## 🤖 Copilot Agent Assigned\n\n'; + commentBody += 'This issue has been assigned to GitHub Copilot to draft a pull request based on the description provided.\n\n'; + + if (smeFound) { + commentBody += '### 📋 SME Review Required\n\n'; + commentBody += `@${smeUsername} - You have been identified as the subject matter expert (SME) for this documentation update based on the originating PR in the [dotnet/aspire](https://github.com/dotnet/aspire) repository. Please review the draft PR once Copilot has completed its work.\n`; + } else { + commentBody += '### ⚠️ SME Not Found\n\n'; + commentBody += 'Could not automatically identify the SME from the issue body. Please manually tag the appropriate reviewer once the draft PR is created.\n'; + } + + commentBody += '\n---\n*This comment was automatically generated by the docs-from-code workflow.*'; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + body: commentBody + }); + + console.log(`Added comment to issue #${issueNumber}`);