-
Notifications
You must be signed in to change notification settings - Fork 0
Add automated PR management system #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e02a21d
99c7535
65a10fb
8bb1f5c
e738c7c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,312 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # PR Handler - Comprehensive Pull Request Management | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Handles incoming PRs with intelligent triage, labeling, and status tracking | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: PR Handler | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pull_request: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| types: [opened, edited, synchronize, reopened, ready_for_review] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pull_request_review: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| types: [submitted] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| issue_comment: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| types: [created] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| workflow_dispatch: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| inputs: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pr_number: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: 'PR number to handle' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| required: false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| permissions: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| contents: read | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pull-requests: write | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| issues: write | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| analyze-pr: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: Analyze PR | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| outputs: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is_wip: ${{ steps.analyze.outputs.is_wip }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pr_type: ${{ steps.analyze.outputs.pr_type }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| needs_review: ${{ steps.analyze.outputs.needs_review }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| can_auto_merge: ${{ steps.analyze.outputs.can_auto_merge }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| org_scope: ${{ steps.analyze.outputs.org_scope }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fetch-depth: 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Analyze PR metadata | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: analyze | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uses: actions/github-script@v7 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| script: | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const pr = context.payload.pull_request || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await github.rest.pulls.get({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| owner: context.repo.owner, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| repo: context.repo.repo, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pull_number: context.payload.inputs?.pr_number || context.issue.number | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }).then(r => r.data); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+46
to
+51
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if WIP | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isWIP = pr.title.includes('[WIP]') || pr.title.includes('WIP:') || pr.draft; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| core.setOutput('is_wip', isWIP); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Determine PR type based on title and files | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let prType = 'other'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const title = pr.title.toLowerCase(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+53
to
+59
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if WIP | |
| const isWIP = pr.title.includes('[WIP]') || pr.title.includes('WIP:') || pr.draft; | |
| core.setOutput('is_wip', isWIP); | |
| // Determine PR type based on title and files | |
| let prType = 'other'; | |
| const title = pr.title.toLowerCase(); | |
| // Check if WIP (case-insensitive) | |
| const lowerTitle = pr.title.toLowerCase(); | |
| const isWIP = lowerTitle.includes('[wip]') || lowerTitle.includes('wip:') || pr.draft; | |
| core.setOutput('is_wip', isWIP); | |
| // Determine PR type based on title and files | |
| let prType = 'other'; | |
| const title = lowerTitle; |
Copilot
AI
Feb 15, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR type detection checks title.includes('ci') before title.includes('ci/cd'), so a title containing “ci/cd” will always be categorized as workflow instead of testing. Reorder the checks or use more specific matching to avoid this misclassification.
Copilot
AI
Feb 15, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Org scope detection won’t match because title is lowercased but orgPatterns contain mixed-case strings, and body.includes(org) is case-sensitive. Normalize both title/body and patterns to the same case before matching so org_scope labels are applied correctly.
| const orgsFound = orgPatterns.filter(org => | |
| title.includes(org) || body.includes(org) | |
| ); | |
| const bodyLower = body.toLowerCase(); | |
| const orgPatternsLower = orgPatterns.map(org => org.toLowerCase()); | |
| const orgsFound = orgPatterns.filter((org, index) => { | |
| const orgLower = orgPatternsLower[index]; | |
| return title.includes(orgLower) || bodyLower.includes(orgLower); | |
| }); |
Copilot
AI
Feb 15, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When no orgs are detected, the workflow sets org_scope to all and applies no scope label. The docs/label taxonomy mention an all-orgs label; if that label is part of the intended system, consider emitting/applying it explicitly so “no specific org detected” is still represented consistently.
Copilot
AI
Feb 15, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The workflow_dispatch path will fail because this job uses context.issue.number, which is undefined for workflow_dispatch events. Consider setting an explicit PR number output in analyze-pr (e.g., pr_number) and using that for issue_number/pull_number across label/comment/reviewer steps when github.event_name == 'workflow_dispatch'.
| // Apply labels | |
| if (labels.length > 0) { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| // Determine PR number (supports both pull_request and workflow_dispatch) | |
| const prNumber = context.eventName === 'workflow_dispatch' | |
| ? Number(context.payload && context.payload.inputs && context.payload.inputs.pr_number) | |
| : (context.issue && context.issue.number); | |
| // Apply labels | |
| if (labels.length > 0 && prNumber) { | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, |
Copilot
AI
Feb 15, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Applying labels will fail the step if any label doesn’t already exist in the repo (GitHub API returns 422). Since org labels are dynamically generated (e.g., org:foundation, org:enterprises), consider either creating missing labels first or wrapping addLabels in try/catch and only applying labels that exist.
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| labels: labels | |
| }); | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| // Fetch all existing labels in the repo to avoid 422 on non-existent labels | |
| const existingLabels = await github.paginate( | |
| github.rest.issues.listLabelsForRepo, | |
| { owner, repo } | |
| ); | |
| const existingLabelNames = new Set(existingLabels.map(l => l.name)); | |
| const validLabels = labels.filter(label => existingLabelNames.has(label)); | |
| if (validLabels.length > 0) { | |
| await github.rest.issues.addLabels({ | |
| owner, | |
| repo, | |
| issue_number: context.issue.number, | |
| labels: validLabels | |
| }); | |
| } |
Copilot
AI
Feb 15, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This job assumes context.payload.pull_request exists, but it will be undefined for workflow_dispatch runs even if can_auto_merge is true, causing a runtime error. Also, check_runs.every(...) returns true when there are zero checks, which could incorrectly report “ready to merge”; ensure at least one relevant check run exists (or query required checks) before declaring success.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The workflow is triggered on pull_request_review and issue_comment, but analyze-pr is gated to only pull_request/workflow_dispatch events, so review submissions won’t update labels/status (e.g., “approved”) despite the docs claiming approval/status tracking. Either handle pull_request_review events (fetch PR by pull_request.number) or remove unsupported triggers to avoid confusion and unnecessary runs.