diff --git a/.github/allowed-pr-authors.txt b/.github/allowed-pr-authors.txt new file mode 100644 index 00000000..2e97b411 --- /dev/null +++ b/.github/allowed-pr-authors.txt @@ -0,0 +1,3 @@ +# GitHub usernames allowed to open pull requests directly. +mdecimus +mdyring diff --git a/.github/workflows/auto-close-prs.yml b/.github/workflows/auto-close-prs.yml new file mode 100644 index 00000000..b2cfa344 --- /dev/null +++ b/.github/workflows/auto-close-prs.yml @@ -0,0 +1,116 @@ +name: Auto-close PRs from non-allowed authors + +on: + pull_request_target: + types: [opened, reopened] + +permissions: + pull-requests: write + issues: write + +jobs: + auto-close: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + sparse-checkout: .github/allowed-pr-authors.txt + sparse-checkout-cone-mode: false + + - name: Close PRs from non-allowed authors + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + + let allowedAuthors = []; + try { + allowedAuthors = fs.readFileSync('.github/allowed-pr-authors.txt', 'utf8') + .split('\n') + .map(line => line.trim()) + .filter(line => line && !line.startsWith('#')) + .map(line => line.toLowerCase()); + } catch (err) { + core.warning(`Could not read allowed-pr-authors.txt: ${err.message}`); + } + + const pr = context.payload.pull_request; + const author = (pr.user && pr.user.login) || ''; + const login = author.toLowerCase(); + + if (author.endsWith('[bot]')) { + core.info(`PR #${pr.number} opened by bot '${author}'. Skipping.`); + return; + } + + if (allowedAuthors.includes(login)) { + core.info(`PR #${pr.number} opened by allowed author '${author}'. Skipping.`); + return; + } + + try { + const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: author, + }); + if (perm.permission === 'admin' || perm.permission === 'write') { + core.info(`PR #${pr.number} author '${author}' is a collaborator (${perm.permission}). Skipping.`); + return; + } + } catch (err) { + core.info(`Could not resolve collaborator permission for '${author}': ${err.message}`); + } + + const contributingUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/blob/HEAD/CONTRIBUTING.md`; + + const haystack = `${pr.title || ''}\n${pr.body || ''}`; + const aiPatterns = [ + /[—―]/, + ]; + const looksAiGenerated = aiPatterns.some(re => re.test(haystack)); + + const aiMessage = [ + `Hi @${author}, thanks for your interest in contributing.`, + ``, + `This pull request is being **automatically closed and locked**. The description contains strong indicators of AI-generated content, and this project does not accept AI-generated code or unsolicited machine-authored contributions.`, + ``, + `Please read [CONTRIBUTING.md](${contributingUrl}) to learn what kinds of contributions are currently accepted. If this is a genuine hand-written change that fits those guidelines, please open a discussion at **[support.stalw.art](https://support.stalw.art)** before submitting.`, + ].join('\n'); + + const standardMessage = [ + `Hi @${author}, thanks for taking the time to open this pull request.`, + ``, + `This PR is being **automatically closed** because it was submitted by an author who is not on the list of approved contributors. This policy helps us keep review capacity focused and filter out unsolicited or low-quality contributions.`, + ``, + `Please read [CONTRIBUTING.md](${contributingUrl}) to learn what kinds of contributions are currently accepted. If your change fits those guidelines, please first discuss it at our support portal: **[support.stalw.art](https://support.stalw.art)**. You can sign in with your existing GitHub account.`, + ``, + `Thank you for understanding.`, + ].join('\n'); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + body: looksAiGenerated ? aiMessage : standardMessage, + }); + + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.number, + state: 'closed', + }); + + if (looksAiGenerated) { + try { + await github.rest.issues.lock({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + lock_reason: 'spam', + }); + } catch (err) { + core.warning(`Could not lock PR #${pr.number}: ${err.message}`); + } + } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 17c5bcd3..5081afab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,22 +1,48 @@ # Contributing -## Contributions are Temporarily Limited +Thank you for your interest in contributing to Stalwart. We appreciate the support and enthusiasm of the open-source community. To keep the project maintainable and the review process sustainable, contributions are subject to the policies described below. Please read them in full before opening a pull request. -Thank you for your interest in contributing to Stalwart. We appreciate the support and enthusiasm of the open-source community. However, at this stage of the project, we are **limiting the scope of external contributions**. +## Vouched Contributors Only -Stalwart is currently **not accepting external contributions**, except for bug fixes and small, well-scoped changes. The project is approaching version 1.0, and as we move toward this milestone, development is progressing rapidly. The architecture of Stalwart is still evolving, and many internal components are subject to change. +Due to the high volume of low-quality, AI-generated submissions, pull requests are limited to a list of vouched contributors. Pull requests opened by anyone who is not on this list are closed automatically. -Due to these ongoing changes and the fast pace of development, we do not have the time or resources to thoroughly review and integrate most pull requests. Accepting broad contributions at this time could lead to confusion and unnecessary rework for both contributors and maintainers. +To be added as a vouched contributor, post a message at [support.stalw.art](https://support.stalw.art) explaining the code changes you would like to submit, and include a link to the proposed change (a branch, diff, or draft). Once a maintainer has reviewed your request and vouched for you, you will be able to open pull requests directly. -While we are not accepting most code contributions, you can still support the project in meaningful ways. Reporting bugs, providing feedback, and helping test the software are all valuable forms of participation. If you encounter an issue, please open a detailed report that includes steps to reproduce the problem and any relevant logs or context. We also welcome thoughtful suggestions and questions through our issue tracker or discussion channels. +This policy lets us focus limited review capacity on contributions from people who have taken the time to understand the codebase and discuss their changes first. -We plan to open the project to broader contributions once we reach a stable 1.0 release. At that point, with a more mature architecture and clearer development roadmap, we will be better positioned to collaborate with the community. We will update this policy accordingly when the time comes. +## What Contributions Are Accepted -Thank you for your understanding and continued support. We’re excited about the future of Stalwart and look forward to working with the community in the near future. +At this stage of the project we accept a narrow set of contributions: + +- **Bug fixes.** Corrections to existing, incorrect behavior are welcome. Please include steps to reproduce the bug and describe the fix. +- **Translations.** Additions and corrections to existing translations are welcome. + +New features are generally **not** accepted, unless they involve only a few lines of code. Larger features fall outside the scope of what we can review and integrate while the architecture is still evolving. + +If you would like to see a new feature, please request it at [support.stalw.art](https://support.stalw.art) under the **Feature Ideas** category rather than opening a pull request. This lets the community discuss and prioritize ideas before any code is written. + +## No AI-Generated Code + +AI-generated code is not accepted in this project. + +Even the most advanced models write inefficient Rust code. Beyond raw performance, AI creates technical debt by generating large amounts of code that not even the authors who submitted it can fully understand or maintain. Reviewing and untangling such contributions costs the maintainers far more time than it saves. + +Using AI as a fancy autocomplete is perfectly fine. What matters is that every line generated by a model is read, understood, and reviewed by a human before it is submitted. You are responsible for every line in your pull request, regardless of how it was produced. If you cannot explain why a change is written the way it is, it is not ready to be submitted. + +## Pull Request Process + +Once you are a vouched contributor: + +1. Keep each pull request small and focused on a single logical change. +2. Match the style and conventions of the surrounding code. +3. Make sure the project builds and the test suite passes before opening the pull request. +4. In the pull request description, explain what the change does and why, and link to the [support.stalw.art](https://support.stalw.art) discussion where the change was vouched. ## Code of Conduct -Please note we have a code of conduct, please follow it in all your interactions with the project. +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +You can read the full Code of Conduct [here](https://github.com/stalwartlabs/.github/blob/main/CODE_OF_CONDUCT.md). ## Licensing @@ -32,26 +58,4 @@ Key points of the FLA: - Protects the project from potential copyright issues - Includes a reversion clause: if the fiduciary violates Free Software principles, rights revert to the original contributors -For more details about FLA, please refer to the [FLA FAQ](https://fsfe.org/activities/fla/fla.en.html). - -## Pull Request Process - -1. Ensure any install or build dependencies are removed before the end of the layer when doing a - build. -2. Update the README.md with details of changes to the interface, this includes new environment - variables, exposed ports, useful file locations and container parameters. -3. Increase the version numbers in any examples files and the README.md to the new version that this - Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). -4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you - do not have permission to do that, you may request the second reviewer to merge it for you. - -## Code of Conduct - -We as members, contributors, and leaders pledge to make participation in our community a harassment-free -experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex -characteristics, gender identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity and orientation. -We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, -and healthy community. - -You can read the full Code of Conduct [here](https://github.com/stalwartlabs/.github/blob/main/CODE_OF_CONDUCT.md). +For more details about the FLA, please refer to the [FLA FAQ](https://fsfe.org/activities/fla/fla.en.html). diff --git a/README.md b/README.md index 1805ca1c..b8491e93 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,11 @@ All documentation is available at [stalw.art/docs](https://stalw.art/docs/instal If you are having problems running Stalwart, found a bug, or just have a question, please head to the [Stalwart Support Portal](https://support.stalw.art) at [support.stalw.art](https://support.stalw.art). Additionally, you may purchase an [Enterprise License](https://stalw.art/enterprise) to obtain priority support from Stalwart Labs LLC, including response-time commitments and a private Priority Support area on the portal. +## Contributing + +We welcome contributions, but to keep the project maintainable there are a few things to know before opening a pull request. Because of the high volume of low-quality, AI-generated submissions, pull requests are limited to a list of vouched contributors; to be added, post at [support.stalw.art](https://support.stalw.art) describing the change you would like to submit, together with a link to the proposed change. At this stage only bug fixes and translations are accepted, and new features are not, unless they involve just a few lines of code. +For the full guidelines, please read [CONTRIBUTING.md](CONTRIBUTING.md). + ## Roadmap Stalwart has reached an exciting point in its journey, it’s now **feature complete**. All the core functionality and open standard email and collaboration protocols that we set out to support are in place. In other words, Stalwart already does everything you’d expect from a modern, standards-compliant mail and collaboration platform.