Add claude.yml workflow for @claude mentions; restore truncated auto-merge.yml #59
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
| name: auto-merge | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, labeled] | |
| permissions: | |
| pull-requests: write | |
| contents: write | |
| checks: read | |
| statuses: read | |
| jobs: | |
| auto-merge: | |
| # Skip fork PRs | |
| if: github.event.pull_request.head.repo.full_name == github.repository | |
| runs-on: ubuntu-latest | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| steps: | |
| - name: Wait for CI checks to initialize | |
| run: sleep 300 | |
| - name: Wait for checks to complete | |
| uses: actions/github-script@v7.0.1 | |
| id: wait-checks | |
| with: | |
| script: | | |
| const pr = context.payload.pull_request; | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| core.setOutput('head_sha', context.sha); | |
| } | |
| // Final verification after the loop | |
| const { data: { check_runs } } = await github.rest.checks.listForRef({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: context.sha | |
| }); | |
| const incomplete = check_runs.filter(run => run.status !== 'completed' || run.conclusion !== 'success'); | |
| if (incomplete.length > 0) { | |
| throw new Error(`Auto-merge aborted: Some checks did not pass: ${incomplete.map(r => r.name).join(', ')}`); | |
| } | |
| const { data: checkRuns } = await github.rest.checks.listForRef({ | |
| owner, repo, | |
| ref: pr.head.sha, | |
| }); | |
| // Filter out this workflow's own check | |
| const externalChecks = checkRuns.check_runs.filter( | |
| c => c.name !== 'auto-merge' | |
| ); | |
| if (externalChecks.length === 0) continue; | |
| const pending = externalChecks.filter( | |
| c => !['completed'].includes(c.status) | |
| ); | |
| if (pending.length === 0) break; | |
| } | |
| - name: Check CI status and reviews | |
| uses: actions/github-script@v7.0.1 | |
| id: check-status | |
| with: | |
| script: | | |
| const pr = context.payload.pull_request; | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const prNumber = pr.number; | |
| const expectedHeadSha = '${{ steps.wait-checks.outputs.head_sha }}'; | |
| // Re-fetch PR to get current state | |
| const { data: currentPR } = await github.rest.pulls.get({ | |
| owner, repo, pull_number: prNumber | |
| }); | |
| if (currentPR.state !== 'open') { | |
| core.setOutput('should_merge', 'false'); | |
| core.setOutput('reason', 'PR is no longer open'); | |
| return; | |
| } | |
| if (expectedHeadSha && currentPR.head.sha !== expectedHeadSha) { | |
| core.setOutput('should_merge', 'false'); | |
| core.setOutput('reason', 'head_sha_changed'); | |
| return; | |
| } | |
| // Check all CI checks pass | |
| const { data: checkRuns } = await github.rest.checks.listForRef({ | |
| owner, repo, ref: expectedHeadSha || currentPR.head.sha | |
| }); | |
| const externalChecks = checkRuns.check_runs.filter( | |
| c => c.name !== 'auto-merge' | |
| ); | |
| if (externalChecks.length === 0) { | |
| await github.rest.issues.createComment({ | |
| owner, repo, issue_number: prNumber, | |
| body: '⚠️ **Auto-merge skipped**: No CI checks detected. Please ensure CI is configured.\n\n[Workflow run](https://github.com/' + owner + '/' + repo + '/actions/runs/' + context.runId + ')' | |
| }); | |
| core.setOutput('should_merge', 'false'); | |
| core.setOutput('reason', 'no_checks'); | |
| return; | |
| } | |
| const pendingChecks = externalChecks.filter(c => c.status !== 'completed'); | |
| if (pendingChecks.length > 0) { | |
| const pendingNames = pendingChecks.map(c => c.name).join(', '); | |
| await github.rest.issues.createComment({ | |
| owner, repo, issue_number: prNumber, | |
| body: '⏳ **Auto-merge pending**: CI checks are still running: ' + pendingNames + '\n\n[Workflow run](https://github.com/' + owner + '/' + repo + '/actions/runs/' + context.runId + ')' | |
| }); | |
| core.setOutput('should_merge', 'false'); | |
| core.setOutput('reason', 'checks_pending'); | |
| return; | |
| } | |
| const failedChecks = externalChecks.filter( | |
| c => !['success', 'skipped', 'neutral'].includes(c.conclusion) | |
| ); | |
| if (failedChecks.length > 0) { | |
| const failedNames = failedChecks.map(c => c.name).join(', '); | |
| await github.rest.issues.createComment({ | |
| owner, repo, issue_number: prNumber, | |
| body: '❌ **Auto-merge failed**: The following checks did not pass: ' + failedNames + '\n\n[Workflow run](https://github.com/' + owner + '/' + repo + '/actions/runs/' + context.runId + ')' | |
| }); | |
| core.setOutput('should_merge', 'false'); | |
| core.setOutput('reason', 'checks_failed'); | |
| return; | |
| } | |
| // Detect AI code reviews | |
| const { data: reviews } = await github.rest.pulls.listReviews({ | |
| owner, repo, pull_number: prNumber | |
| }); | |
| const { data: reviewComments } = await github.rest.pulls.listReviewComments({ | |
| owner, repo, pull_number: prNumber | |
| }); | |
| const hasReviews = reviews.length > 0 || reviewComments.length > 0; | |
| // Check for 'reviewed' label | |
| const labels = currentPR.labels.map(l => l.name); | |
| const hasReviewedLabel = labels.includes('reviewed'); | |
| if (!hasReviews) { | |
| await github.rest.issues.createComment({ | |
| owner, repo, issue_number: prNumber, | |
| body: '⏳ **Auto-merge pending**: No reviews detected yet. Waiting for AI code review feedback.\n\n[Workflow run](https://github.com/' + owner + '/' + repo + '/actions/runs/' + context.runId + ')' | |
| }); | |
| core.setOutput('should_merge', 'false'); | |
| core.setOutput('reason', 'pending_reviews'); | |
| return; | |
| } | |
| if (!hasReviewedLabel) { | |
| const reviewerNames = [...new Set([ | |
| ...reviews.map(r => r.user.login), | |
| ...reviewComments.map(r => r.user.login) | |
| ])]; | |
| await github.rest.issues.createComment({ | |
| owner, repo, issue_number: prNumber, | |
| body: '👀 **Reviews detected but awaiting confirmation**: ' + reviewerNames.length + ' reviewer(s) (' + reviewerNames.join(', ') + ') have provided feedback. Add the `reviewed` label to confirm review acceptance and trigger auto-merge.\n\n[Workflow run](https://github.com/' + owner + '/' + repo + '/actions/runs/' + context.runId + ')' | |
| }); | |
| core.setOutput('should_merge', 'false'); | |
| core.setOutput('reason', 'awaiting_label'); | |
| return; | |
| } | |
| // All conditions met | |
| core.setOutput('should_merge', 'true'); | |
| const reviewerNames = [...new Set([ | |
| ...reviews.map(r => r.user.login), | |
| ...reviewComments.map(r => r.user.login) | |
| ])]; | |
| core.setOutput('reviewer_count', reviewerNames.length.toString()); | |
| core.setOutput('reviewer_names', reviewerNames.join(', ')); | |
| - name: Wait for GitHub state consistency | |
| if: steps.check-status.outputs.should_merge == 'true' | |
| run: sleep 60 | |
| - name: Squash merge | |
| if: steps.check-status.outputs.should_merge == 'true' | |
| uses: actions/github-script@v7.0.1 | |
| with: | |
| script: | | |
| const pr = context.payload.pull_request; | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const prNumber = pr.number; | |
| const reviewerCount = '${{ steps.check-status.outputs.reviewer_count }}'; | |
| const reviewerNames = '${{ steps.check-status.outputs.reviewer_names }}'; | |
| try { | |
| // Pin the merge to the head SHA that was validated above. If new | |
| // commits landed during the run, GitHub rejects the merge (409) | |
| // and the catch block reports it, avoiding merging an unchecked commit. | |
| await github.rest.pulls.merge({ | |
| owner, repo, pull_number: prNumber, | |
| sha: pr.head.sha, | |
| merge_method: 'squash' | |
| }); | |
| await github.rest.issues.createComment({ | |
| owner, repo, issue_number: prNumber, | |
| body: '✅ **Auto-merged** via squash merge. ' + reviewerCount + ' reviewer(s) (' + reviewerNames + ') provided feedback.\n\n[Workflow run](https://github.com/' + owner + '/' + repo + '/actions/runs/' + context.runId + ')' | |
| }); | |
| } catch (error) { | |
| await github.rest.issues.createComment({ | |
| owner, repo, issue_number: prNumber, | |
| body: '❌ **Auto-merge failed**: ' + error.message + '\n\n[Workflow run](https://github.com/' + owner + '/' + repo + '/actions/runs/' + context.runId + ')' | |
| }); | |
| } |