diff --git a/.github/workflows/pr-metadata-check.yml b/.github/workflows/pr-metadata-check.yml new file mode 100644 index 0000000000..dd6971a544 --- /dev/null +++ b/.github/workflows/pr-metadata-check.yml @@ -0,0 +1,105 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +name: "CI: Enforce label/milestone on PRs" + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + - labeled + - unlabeled + - reopened + - ready_for_review + +jobs: + check-metadata: + name: PR has labels and milestone + if: github.repository_owner == 'NVIDIA' + runs-on: ubuntu-latest + steps: + - name: Check for labels and milestone + env: + LABELS: ${{ toJson(github.event.pull_request.labels) }} + MILESTONE: ${{ github.event.pull_request.milestone && github.event.pull_request.milestone.title || '' }} + PR_URL: ${{ github.event.pull_request.html_url }} + IS_BOT: ${{ github.actor == 'dependabot[bot]' || github.actor == 'pre-commit-ci[bot]' || github.actor == 'copy-pr-bot[bot]' }} + IS_DRAFT: ${{ github.event.pull_request.draft }} + run: | + if [ "$IS_BOT" = "true" ] || [ "$IS_DRAFT" = "true" ]; then + echo "Skipping check for bot or draft PR." + exit 0 + fi + + LABEL_NAMES=$(echo "$LABELS" | jq -r '.[].name') + ERRORS="" + + # Module labels identify which package the PR touches. + MODULE_LABELS="cuda.bindings cuda.core cuda.pathfinder" + HAS_MODULE=false + for label in $LABEL_NAMES; do + for mod in $MODULE_LABELS; do + if [ "$label" = "$mod" ]; then + HAS_MODULE=true + break 2 + fi + done + done + + if [ "$HAS_MODULE" = "false" ]; then + ERRORS="${ERRORS}- **Missing module label**: add at least one of: \`cuda.bindings\`, \`cuda.core\`, \`cuda.pathfinder\`.\n" + fi + + # Type labels categorize the kind of change. + TYPE_LABELS="bug enhancement feature documentation test example CI/CD packaging dependencies performance experiment RFC support P0 P1 P2" + HAS_TYPE=false + for label in $LABEL_NAMES; do + for typ in $TYPE_LABELS; do + if [ "$label" = "$typ" ]; then + HAS_TYPE=true + break 2 + fi + done + done + + if [ "$HAS_TYPE" = "false" ]; then + ERRORS="${ERRORS}- **Missing type label**: add at least one of: \`bug\`, \`enhancement\`, \`feature\`, \`documentation\`, \`test\`, \`example\`, \`CI/CD\`, \`packaging\`, \`dependencies\`, \`performance\`, \`experiment\`, \`RFC\`, \`support\`, \`P0\`, \`P1\`, \`P2\`.\n" + fi + + if [ -z "$MILESTONE" ]; then + ERRORS="${ERRORS}- **Missing milestone**: assign a milestone to this PR.\n" + fi + + # Block PRs with labels that indicate they are not ready to merge. + BLOCKED_PATTERNS="blocked DO NOT MERGE do not merge" + for label in $LABEL_NAMES; do + for pattern in $BLOCKED_PATTERNS; do + if echo "$label" | grep -qi "$pattern"; then + ERRORS="${ERRORS}- **Blocked label detected**: label \`$label\` prevents merging. Remove it when the PR is ready.\n" + break + fi + done + done + + if [ -n "$ERRORS" ]; then + echo "::error::This PR is missing required metadata. See the job summary for details." + { + echo "## PR Metadata Check Failed" + echo "" + printf "$ERRORS" + echo "" + echo "Please update the PR at: $PR_URL" + } >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi + + LABEL_LIST=$(echo "$LABEL_NAMES" | paste -sd ', ' -) + { + echo "## PR Metadata Check Passed" + echo "" + echo "- **Labels**: $LABEL_LIST" + echo "- **Milestone**: $MILESTONE" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/AGENTS.md b/AGENTS.md index dd62ad5a95..a4450fbc66 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,6 +12,22 @@ guide for package-specific conventions and workflows. - `cuda_core/`: High-level Pythonic CUDA APIs built on top of bindings. - `cuda_python/`: Metapackage and docs aggregation. +# Pull requests + +When creating pull requests with `gh pr create`, always assign at least one +label and a milestone. CI enforces this via the `pr-metadata-check` workflow +and will block PRs that are missing labels or a milestone. Use `--label` and +`--milestone` flags, for example: + +``` +gh pr create --title "..." --body "..." --label "bug" --milestone "v1.0" +``` + +If you are unsure which label or milestone to use, check the existing labels +and milestones on the repository with `gh label list` and `gh api +repos/{owner}/{repo}/milestones --jq '.[].title'`, and pick the best match. + + # General - When searching for text or files, prefer using `rg` or `rg --files`