Add migrate-dotnet9-to-dotnet10 skill #58
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Evaluation workflow for fork PRs. | |
| # | |
| # Fork PRs cannot access repository secrets via the `pull_request` trigger. | |
| # This workflow uses `issue_comment` so that a maintainer can trigger evaluation | |
| # by posting "/evaluate" on a fork PR. The workflow YAML always comes from the | |
| # default branch (enforced by the issue_comment trigger). | |
| # | |
| # Security model: | |
| # - Workflow YAML: always from the default branch (enforced by issue_comment) | |
| # - Validator binary: built from the default branch checkout (trusted) | |
| # - Skill/test content: checked out from the fork PR (untrusted data, read-only) | |
| # - Secret access: only users with write+ permission can trigger evaluation | |
| name: evaluation (fork PRs) | |
| on: | |
| issue_comment: | |
| types: [created] | |
| concurrency: | |
| group: eval-fork-${{ github.event.issue.number }} | |
| cancel-in-progress: true | |
| # pull-requests: write is required at the workflow level because the reusable | |
| # workflow's comment-on-pr job needs it, and reusable workflow jobs cannot | |
| # escalate beyond the caller's permissions. Individual jobs restrict their own | |
| # permissions to contents: read. | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| jobs: | |
| # Validate the trigger: must be a /evaluate comment on a fork PR from a user | |
| # with write+ permissions. | |
| gate: | |
| if: >- | |
| github.event.issue.pull_request && | |
| startsWith(github.event.comment.body, '/evaluate') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| statuses: write | |
| outputs: | |
| head_sha: ${{ steps.pr.outputs.head_sha }} | |
| base_sha: ${{ steps.pr.outputs.base_sha }} | |
| pr_number: ${{ steps.pr.outputs.pr_number }} | |
| steps: | |
| - name: Check commenter permissions | |
| id: perms | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| PERMISSION=$(gh api "repos/${{ github.repository }}/collaborators/${{ github.event.comment.user.login }}/permission" --jq '.permission') | |
| echo "Commenter ${{ github.event.comment.user.login }} has permission: $PERMISSION" | |
| if [[ "$PERMISSION" != "admin" && "$PERMISSION" != "write" && "$PERMISSION" != "maintain" ]]; then | |
| echo "::error::User does not have write access" | |
| exit 1 | |
| fi | |
| - name: Get PR details | |
| id: pr | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| PR_NUMBER=${{ github.event.issue.number }} | |
| PR_DATA=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUMBER}") | |
| HEAD_SHA=$(echo "$PR_DATA" | jq -r '.head.sha') | |
| HEAD_REPO=$(echo "$PR_DATA" | jq -r '.head.repo.full_name') | |
| BASE_REPO=$(echo "$PR_DATA" | jq -r '.base.repo.full_name') | |
| BASE_SHA=$(echo "$PR_DATA" | jq -r '.base.sha') | |
| if [[ "$HEAD_REPO" == "$BASE_REPO" ]]; then | |
| echo "::error::This command is only for fork PRs. Same-repo PRs are evaluated automatically." | |
| exit 1 | |
| fi | |
| echo "PR #${PR_NUMBER}: head=${HEAD_SHA} base=${BASE_SHA}" | |
| echo "head_sha=${HEAD_SHA}" >> $GITHUB_OUTPUT | |
| echo "base_sha=${BASE_SHA}" >> $GITHUB_OUTPUT | |
| echo "pr_number=${PR_NUMBER}" >> $GITHUB_OUTPUT | |
| - name: Add reaction to comment | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| gh api "repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions" \ | |
| -X POST -f content='eyes' || true | |
| - name: Set pending commit status | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| gh api "repos/${{ github.repository }}/statuses/${{ steps.pr.outputs.head_sha }}" \ | |
| -f state=pending \ | |
| -f context="evaluation (fork PRs)" \ | |
| -f description="Evaluation in progress..." \ | |
| -f target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| discover: | |
| needs: gate | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| entries: ${{ steps.find.outputs.entries }} | |
| has_entries: ${{ steps.find.outputs.has_entries }} | |
| steps: | |
| - name: Checkout base branch | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Fetch PR head | |
| run: git fetch origin +refs/pull/${{ needs.gate.outputs.pr_number }}/head:refs/remotes/origin/pr-head | |
| - name: Find skills to evaluate | |
| id: find | |
| run: | | |
| $base = "${{ needs.gate.outputs.base_sha }}" | |
| $head = (git rev-parse origin/pr-head) | |
| $changedFiles = git diff --name-only --diff-filter=ACMR $base $head | |
| # We need the PR content to check for SKILL.md files — use a worktree | |
| git worktree add /tmp/pr-content origin/pr-head 2>$null | |
| # Extract unique plugin/skill pairs from changed files | |
| $changedPairs = @($changedFiles | | |
| Where-Object { $_ -match '^(?:plugins/([^/]+)/skills|tests/([^/]+))/([^/]+)/' } | | |
| ForEach-Object { | |
| $p = if ($Matches[1]) { $Matches[1] } else { $Matches[2] } | |
| "$p/$($Matches[3])" | |
| } | | |
| Sort-Object -Unique) | |
| # Filter to skills that have a SKILL.md and a tests directory (check in PR content) | |
| $entries = @($changedPairs | ForEach-Object { | |
| $parts = $_ -split '/' | |
| $plugin = $parts[0] | |
| $skill = $parts[1] | |
| $skillMd = Join-Path "/tmp/pr-content" "plugins" $plugin "skills" $skill "SKILL.md" | |
| $testsDir = Join-Path "/tmp/pr-content" "tests" $plugin | |
| if ((Test-Path $skillMd) -and (Test-Path $testsDir)) { | |
| @{ | |
| name = "$plugin--$skill" | |
| plugin = $plugin | |
| skills_path = "plugins/$plugin/skills/$skill" | |
| } | |
| } | |
| } | Where-Object { $_ }) | |
| git worktree remove /tmp/pr-content --force 2>$null | |
| if (-not $entries -or $entries.Count -eq 0) { | |
| Write-Host "No skills to evaluate" | |
| echo "entries=[]" >> $env:GITHUB_OUTPUT | |
| echo "has_entries=false" >> $env:GITHUB_OUTPUT | |
| } else { | |
| $json = $entries | ConvertTo-Json -Compress -AsArray | |
| Write-Host "Entries to evaluate: $json" | |
| echo "entries=$json" >> $env:GITHUB_OUTPUT | |
| echo "has_entries=true" >> $env:GITHUB_OUTPUT | |
| } | |
| shell: pwsh | |
| run-evaluation: | |
| needs: [gate, discover] | |
| if: needs.discover.outputs.has_entries == 'true' | |
| uses: ./.github/workflows/evaluation-run.yml | |
| with: | |
| entries: ${{ needs.discover.outputs.entries }} | |
| checkout-ref: ${{ needs.gate.outputs.head_sha }} | |
| pr-number: ${{ needs.gate.outputs.pr_number }} | |
| secrets: | |
| COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} | |
| COPILOT_GITHUB_TOKEN_2: ${{ secrets.COPILOT_GITHUB_TOKEN_2 }} | |
| COPILOT_GITHUB_TOKEN_3: ${{ secrets.COPILOT_GITHUB_TOKEN_3 }} | |
| COPILOT_GITHUB_TOKEN_4: ${{ secrets.COPILOT_GITHUB_TOKEN_4 }} | |
| COPILOT_GITHUB_TOKEN_5: ${{ secrets.COPILOT_GITHUB_TOKEN_5 }} | |
| COPILOT_GITHUB_TOKEN_6: ${{ secrets.COPILOT_GITHUB_TOKEN_6 }} | |
| COPILOT_GITHUB_TOKEN_7: ${{ secrets.COPILOT_GITHUB_TOKEN_7 }} | |
| COPILOT_GITHUB_TOKEN_8: ${{ secrets.COPILOT_GITHUB_TOKEN_8 }} | |
| report-status: | |
| needs: [gate, discover, run-evaluation] | |
| if: always() && needs.gate.result == 'success' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| statuses: write | |
| steps: | |
| - name: Set final commit status | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| if [[ "${{ needs.run-evaluation.result }}" == "success" ]]; then | |
| STATE="success" | |
| DESC="Evaluation passed" | |
| elif [[ "${{ needs.run-evaluation.result }}" == "skipped" && "${{ needs.discover.outputs.has_entries }}" != "true" ]]; then | |
| STATE="success" | |
| DESC="No skills to evaluate" | |
| elif [[ "${{ needs.run-evaluation.result }}" == "failure" ]]; then | |
| STATE="failure" | |
| DESC="Evaluation failed" | |
| else | |
| STATE="error" | |
| DESC="Evaluation did not complete (${{ needs.run-evaluation.result }})" | |
| fi | |
| gh api "repos/${{ github.repository }}/statuses/${{ needs.gate.outputs.head_sha }}" \ | |
| -f state="$STATE" \ | |
| -f context="evaluation (fork PRs)" \ | |
| -f description="$DESC" \ | |
| -f target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" |