From 8a6db8cfa3886f78fd8e575858c16c8e396d7cf9 Mon Sep 17 00:00:00 2001 From: Aryan Gorwade Date: Wed, 4 Nov 2026 19:46:50 -0800 Subject: [PATCH] Introduce triage policy Signed-off-by: Aryan Gorwade --- .github/ISSUE_TEMPLATE/bug_report.md | 24 ++++++ .github/ISSUE_TEMPLATE/config.yml | 5 ++ .github/ISSUE_TEMPLATE/feature_request.md | 14 ++++ .github/PULL_REQUEST_TEMPLATE.md | 7 ++ .github/workflows/assign.yaml | 98 +++++++++++++++++++++++ .github/workflows/issue-state-labels.yaml | 62 ++++++++++++++ .github/workflows/issue-triage.yaml | 45 +++++++++++ .github/workflows/stale.yaml | 17 +++- AGENTS.md | 11 +++ CONTRIBUTING.md | 53 +++++++++++- GOVERNANCE.md | 7 +- README.md | 4 +- 12 files changed, 339 insertions(+), 8 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/assign.yaml create mode 100644 .github/workflows/issue-state-labels.yaml create mode 100644 .github/workflows/issue-triage.yaml create mode 100644 AGENTS.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..2a09fc6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,24 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '[Bug]: ' +labels: ["bug", "needs-triage"] + +--- + + + +**Motivation** + + +**Check here if you would like to work on this issue** +- [ ] + +**Describe the bug** + + +**To Reproduce** + + +**Expected behavior** + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..bc4389a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Community Slack (#gpu-operator) + url: https://inviter.co/dsx-ai-factory + about: Chat with the GPU Operator team and community diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..a3908c0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,14 @@ +--- +name: Feature request +about: Submit a feature request +title: "[Feature]: " +labels: ["feature", "needs-triage"] +--- +**Motivation** + + +**Check here if you would like to work on this issue** +- [ ] + +**Describe the feature** + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..fa222c5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +## Issue + +Fixes # + +## Description + + diff --git a/.github/workflows/assign.yaml b/.github/workflows/assign.yaml new file mode 100644 index 0000000..c50e428 --- /dev/null +++ b/.github/workflows/assign.yaml @@ -0,0 +1,98 @@ +# Copyright NVIDIA CORPORATION +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Assign + +on: + issue_comment: + types: [created] + +permissions: {} + +jobs: + assign: + name: Assign issue from /assign comment + if: ${{ !github.event.issue.pull_request && startsWith(github.event.comment.body, '/assign') }} + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + issues: write + steps: + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const { owner, repo } = context.repo; + const { comment, issue } = context.payload; + const react = (content) => + github.rest.reactions.createForIssueComment({ owner, repo, comment_id: comment.id, content }); + + const firstLine = comment.body.split(/\r?\n/, 1)[0].trim(); + if (!/^\/assign(\s|$)/.test(firstLine)) { + return; + } + + // Matches GitHub's own rule: assigning requires the triage role or higher. + const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({ + owner, + repo, + username: comment.user.login, + }); + const canAssign = ['admin', 'maintain', 'write', 'triage'].includes(perm.role_name) + || ['admin', 'write'].includes(perm.permission); + if (!canAssign) { + return react('-1'); + } + + const match = firstLine.match(/^\/assign(?:\s+@?([A-Za-z0-9-]+))?$/); + if (!match || !issue.labels.some((label) => label.name === 'accepted')) { + return react('confused'); + } + + const assignee = match[1] ?? comment.user.login; + const { data: updated } = await github.rest.issues.addAssignees({ + owner, + repo, + issue_number: issue.number, + assignees: [assignee], + }); + // GitHub silently skips users it cannot assign, such as people who have not commented on the issue. + if (!updated.assignees.some((a) => a.login.toLowerCase() === assignee.toLowerCase())) { + return react('confused'); + } + + // The PR gatekeeper accepts any assignee, so a new claimant must replace the previous ones. + // Maintainers stay assigned because the gatekeeper does not apply to them. + const previousClaimants = []; + for (const { login } of updated.assignees) { + if (login.toLowerCase() === assignee.toLowerCase()) { + continue; + } + const { data: level } = await github.rest.repos.getCollaboratorPermissionLevel({ + owner, + repo, + username: login, + }); + if (!['admin', 'maintain', 'write'].includes(level.permission)) { + previousClaimants.push(login); + } + } + if (previousClaimants.length > 0) { + await github.rest.issues.removeAssignees({ + owner, + repo, + issue_number: issue.number, + assignees: previousClaimants, + }); + } + return react('+1'); diff --git a/.github/workflows/issue-state-labels.yaml b/.github/workflows/issue-state-labels.yaml new file mode 100644 index 0000000..b034465 --- /dev/null +++ b/.github/workflows/issue-state-labels.yaml @@ -0,0 +1,62 @@ +# Copyright NVIDIA CORPORATION +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Issue State Labels + +on: + issues: + types: [labeled] + +permissions: {} + +concurrency: + group: issue-state-${{ github.event.issue.number }} + +jobs: + single-state: + name: Keep one state label per issue + if: contains(fromJSON('["needs-triage","needs-more-info","accepted"]'), github.event.label.name) + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + issues: write + steps: + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const stateLabels = ['needs-triage', 'needs-more-info', 'accepted']; + const { owner, repo } = context.repo; + const issue_number = context.payload.issue.number; + const added = context.payload.label.name; + if (!stateLabels.includes(added)) { + return; + } + + const { data: labels } = await github.rest.issues.listLabelsOnIssue({ owner, repo, issue_number }); + const current = labels.map((label) => label.name); + // Adding two state labels at once starts two runs; a later run must not undo an earlier one. + if (!current.includes(added)) { + return; + } + + for (const name of current.filter((label) => stateLabels.includes(label) && label !== added)) { + try { + await github.rest.issues.removeLabel({ owner, repo, issue_number, name }); + core.info(`Removed ${name}: ${added} replaces it.`); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } + } diff --git a/.github/workflows/issue-triage.yaml b/.github/workflows/issue-triage.yaml new file mode 100644 index 0000000..93b683f --- /dev/null +++ b/.github/workflows/issue-triage.yaml @@ -0,0 +1,45 @@ +# Copyright NVIDIA CORPORATION +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Issue Triage + +on: + issues: + types: [opened] + +permissions: {} + +jobs: + label: + name: Label new issues for triage + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + issues: write + steps: + # Issue templates apply needs-triage, but issues created through the API skip templates. + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const stateLabels = ['needs-triage', 'needs-more-info', 'accepted']; + const labels = context.payload.issue.labels.map((label) => label.name); + if (labels.some((label) => stateLabels.includes(label))) { + return; + } + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + labels: ['needs-triage'], + }); diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 25a4b22..88f2567 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -17,9 +17,24 @@ jobs: with: stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. This issue will be closed in 30 days unless new comments are made or the stale label is removed. To skip these checks, apply the "feature, enhancement or lifecycle/frozen" labels.' stale-issue-label: 'lifecycle/stale' - exempt-issue-labels: 'lifecycle/frozen,feature,enhancement' + exempt-issue-labels: 'lifecycle/frozen,feature,enhancement,accepted' days-before-stale: 90 close-issue-message: 'This issue was automatically closed due to inactivity.' days-before-issue-close: 30 remove-stale-when-updated: true operations-per-run: 300 + - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11 + with: + only-issue-labels: 'needs-more-info' + # Triage policy start date. + start-date: '2026-10-27T00:00:00Z' + stale-issue-message: 'This issue has been waiting for more information for 23 days with no activity. It will be closed in 7 days unless the requested information is provided.' + stale-issue-label: 'lifecycle/stale' + exempt-issue-labels: 'lifecycle/frozen' + days-before-issue-stale: 23 + close-issue-message: 'This issue was automatically closed because the requested information was not provided within 30 days.' + days-before-issue-close: 7 + days-before-pr-stale: -1 + days-before-pr-close: -1 + remove-stale-when-updated: true + operations-per-run: 1000 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c20f581 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,11 @@ +## Contribution process (see `CONTRIBUTING.md`) + +- For any change, an issue should exist describing the problem/proposal before implementation begins. Additionally, the contributor should be approved to work on said issue — ensure the PR is linked to an issue the contributor has been assigned to. +- Keep PR titles short and imperative. +- Issue or PR descriptions drafted with AI must be thoroughly reviewed and verified by the user before they are submitted. +- Replies to maintainers in review comments or issue discussions may not be AI generated. +- All commits must be signed off (DCO): `git commit -s`, producing a trailing + `Signed-off-by: Name ` line. Do not fabricate a sign-off identity — use the configured + git user's identity. +- Do not open, push to, or comment on GitHub issues/PRs without explicit user confirmation. +- Keep all changes as minimal as possible. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2f792a0..7abc5d5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,10 +1,59 @@ # Contribute to the NVIDIA `go-gpuallocator` Project -Want to contribute to the NVIDIA `go-gpuallocator` Project? Awesome! -We only require you to sign your work, the below section describes this! +To file feature requests or bugs, submit an issue at https://github.com/NVIDIA/go-gpuallocator/issues. For questions, ask in the `#gpu-operator` channel in our community Slack at https://inviter.co/dsx-ai-factory. + +All external contributions start with an issue. External contributors must have an accepted issue that is assigned to them before they open a pull request. See [Contribution workflow](#contribution-workflow). Contributions do not require explicit contributor license agreements (CLA), but we expect contributors to [sign](#sign-your-work) their work. + +PR approval and decision-making for major changes are documented in [GOVERNANCE.md](GOVERNANCE.md). All contributions must adhere to the [Code of Conduct](CODE_OF_CONDUCT.md). +### AI Usage +AI-assisted contributions are welcome, however, **you must understand your code**. If you can't explain what your changes do and how they interact with the greater system without the aid of AI tools, your contribution may not be accepted. AI should assist your effort, not replace it or shift the burden of understanding, testing, and reviewing onto maintainers. + +The human contributor is accountable for all code, regardless of how much was AI-generated. Each contribution must name the human behind the change via a [DCO sign-off](#sign-your-work). + +Maintainers may close issues and PRs that are unproductive or low-quality, without explanation, regardless of whether AI tools were used or not. If a contributor repeatedly opens unproductive issues or PRs, they may be blocked from contributing to the project because it is disruptive and disrespectful of the maintainers' time. + +Below are some unacceptable usages of AI that will likely result in your contribution being closed: + +- Submitting AI-generated issues or PRs without thorough review and verification +- Submitting AI-generated code that you cannot explain or answer questions about during review +- Using AI to generate replies to review comments or issue discussions +- Submitting AI-generated code that contains unnecessary refactoring or unrelated changes not scoped to the problem the PR is solving + +### Contribution workflow + +**To propose a new issue:** start at step 1.\ +**To work on an existing issue:** skip to step 3 and [claim an `accepted` issue](https://github.com/NVIDIA/go-gpuallocator/issues?q=is%3Aopen+label%3Aaccepted+no%3Aassignee) with no assignees. + +1. **Open an issue.** Use the bug report template to report a defect, or the feature request template to propose new behavior or a [major change](GOVERNANCE.md#core-decisions). Indicate if you want to work on the issue. Each new issue gets the `needs-triage` label. +2. **Wait for triage.** A maintainer reviews the issue against the [triage criteria](#triage-criteria). The maintainer then labels the issue `accepted`, `needs-more-info`, or closes it. +3. **Claim the issue.** When an issue is `accepted`, comment on it expressing interest and summarizing your proposed solution. Issue authors have priority if they indicate they would like to work on it. Other claims are first-come, first-served. A maintainer discusses your plan with you and assigns the issue to you when the plan is ready. +4. **Open a pull request.** File a pull request at https://github.com/NVIDIA/go-gpuallocator/pulls and link the issue in the description (for example, `Fixes #1234`). Maintainers close pull requests that do not link an issue assigned to the author, and pull requests that are duplicates of existing ones. + +### Issue labels + +Each open issue has one state label and one category label. For example, `needs-triage` and `bug` together identify a bug report that no maintainer has reviewed yet. + +| Label | Type | Meaning | +| ----- | ---- | ------- | +| `needs-triage` | State | No maintainer has reviewed the issue yet. | +| `needs-more-info` | State | Maintainers are waiting for more information from the author. If the issue has no activity for 30 days, it is closed. | +| `accepted` | State | The issue is approved for work. Contributors can claim it. | +| `bug` | Category | A defect in existing behavior. | +| `feature` | Category | A request for new behavior. | + +### Triage criteria + +Maintainers use these questions to triage an issue: + +- Is the problem clear and reproducible? +- Is the issue a duplicate, or does other work already address it? +- Does the issue fit the supported use cases and the project priorities? +- Is the proposed approach appropriate? +- Does the issue align with the product roadmap? + ## Sign your work The sign-off is a simple line at the end of the description for the patch. Your diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 4605c70..ddcf84d 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -48,9 +48,8 @@ report issues, and engage in discussions. ### Routine Decisions -Routine decisions — bug fixes, minor features, documentation, dependency updates — proceed -via a PR. At least one maintainer approval is required for technical acceptance; a maintainer -then merges after verifying project requirements are met. +Routine decisions — bug fixes, minor features, documentation, dependency updates — proceed via a PR. External contributions first follow the contribution workflow referenced in [CONTRIBUTING.md](CONTRIBUTING.md), which requires maintainer approval on an issue before implementation begins. +A maintainer then approves and merges PRs after verifying project requirements are met. ### Core Decisions @@ -68,7 +67,7 @@ considered, and the impact on existing contributors and users. Core decisions are made by the maintainers. For contributors who want to propose and contribute a major change, the process is as follows: -- A proposal for a major change will be submitted as an issue. +- A proposal for a major change will be submitted as a [feature request](.github/ISSUE_TEMPLATE/feature_request.md). - The maintainers will discuss the proposal and seek feedback from the community. - Once the discussion is concluded, maintainers will make the final decision. diff --git a/README.md b/README.md index 28d8656..e59cfc3 100644 --- a/README.md +++ b/README.md @@ -119,5 +119,7 @@ func main() { ## Contributions [Read the document on contributions](CONTRIBUTING.md). -You can contribute by opening a [pull request](https://help.github.com/en/articles/about-pull-requests). Contributions must adhere to the [Code of Conduct](CODE_OF_CONDUCT.md). + +## Support and Getting Help +You can join our [community Slack](https://inviter.co/dsx-ai-factory). Your feedback is appreciated.