diff --git a/.github/workflows/enforce-draft-pr.yml b/.github/workflows/enforce-draft-pr.yml new file mode 100644 index 0000000000..6a5da12272 --- /dev/null +++ b/.github/workflows/enforce-draft-pr.yml @@ -0,0 +1,75 @@ +name: Enforce Draft PR + +on: + pull_request_target: + types: [opened, reopened] + +permissions: + pull-requests: write + +jobs: + enforce-draft: + name: Enforce Draft PR + runs-on: ubuntu-24.04 + if: github.event.pull_request.draft == false + steps: + - name: Convert PR to draft + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const pullRequest = context.payload.pull_request; + const repo = context.repo; + + // Convert to draft via GraphQL (REST API doesn't support this) + try { + await github.graphql(` + mutation($pullRequestId: ID!) { + convertPullRequestToDraft(input: { pullRequestId: $pullRequestId }) { + pullRequest { + isDraft + } + } + } + `, { + pullRequestId: pullRequest.node_id + }); + } catch (error) { + core.warning(`Failed to convert PR to draft: ${error.message}`); + return; + } + + // Label the PR so maintainers can filter/track violations + await github.rest.issues.addLabels({ + ...repo, + issue_number: pullRequest.number, + labels: ['converted-to-draft'], + }); + + // Check for existing bot comment to avoid duplicates on reopen + const comments = await github.rest.issues.listComments({ + ...repo, + issue_number: pullRequest.number, + }); + const botComment = comments.data.find(c => + c.user.type === 'Bot' && + c.body.includes('automatically converted to draft') + ); + if (botComment) { + core.info('Bot comment already exists, skipping.'); + return; + } + + const contributingUrl = `https://github.com/${repo.owner}/${repo.repo}/blob/master/CONTRIBUTING.md`; + + await github.rest.issues.createComment({ + ...repo, + issue_number: pullRequest.number, + body: [ + `This PR has been automatically converted to draft. All PRs must start as drafts per our [contributing guidelines](${contributingUrl}).`, + '', + '**Next steps:**', + '1. Ensure CI passes', + '2. Fill in the PR description completely', + '3. Mark as "Ready for review" when you\'re done' + ].join('\n') + }); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f9831d8a01..60105c8bac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,6 +21,18 @@ We will review your pull request as soon as possible. Thank you for contributing You are welcome to use whatever tools you prefer for making a contribution. However, any changes you propose have to be reviewed and tested by you, a human, first, before you submit a pull request with them for the Sentry team to review. If we feel like that didn't happen, we will close the PR outright. For example, we won't review visibly AI-generated PRs from an agent instructed to look for and "fix" open issues in the repo. +## Pull Requests + +All PRs must be created as **drafts**. Non-draft PRs will be automatically converted to draft. Mark your PR as "Ready for review" once: + +- CI passes +- The PR description is complete (what, why, and links to relevant issues) +- You've personally reviewed your own changes + +A PR should do one thing well. Don't mix functional changes with unrelated refactors or cleanup. Smaller, focused PRs are easier to review, reason about, and revert if needed. + +For the full set of PR standards, see the [code submission standard](https://develop.sentry.dev/sdk/getting-started/standards/code-submission/#pull-requests). + ## Development Environment ### Set up Python