Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ Please explain the issue and the solution in short.
Please list clearly what are the relevant test(s) that can safeguard the changes in the PR. This helps us to ensure we have sufficient test coverage for the PR.
-->

## PR Checklist

Please review the following before submitting your PR:
- PR description clearly explains what and why. If using CodeRabbit's summary, please make sure it makes sense.
- PR Follows [TRT-LLM CODING GUIDELINES](https://github.com/NVIDIA/TensorRT-LLM/blob/main/CODING_GUIDELINES.md) to the best of your knowledge.
- Test cases are provided for new code paths (see [test instructions](https://github.com/NVIDIA/TensorRT-LLM/tree/main/tests#1-how-does-the-ci-work))
- Any new dependencies have been scanned for license and vulnerabilities
- [CODEOWNERS](https://github.com/NVIDIA/TensorRT-LLM/blob/main/.github/CODEOWNERS) updated if ownership changes
- Documentation updated as needed
- The reviewers assigned automatically/manually are appropriate for the PR.


- [ ] Please check this after reviewing the above items as appropriate for this PR.

## GitHub Bot Help

`/bot [-h] ['run', 'kill', 'skip', 'reuse-pipeline'] ...`
Expand Down
120 changes: 120 additions & 0 deletions .github/scripts/pr_checklist_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# 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.

import os
import re
import sys
from typing import List

# Matches a Markdown checklist item in the PR body.
# Expected format: "- [ ] Task description" or "* [x] Task description"
# Group 1 captures the checkbox state: ' ' (unchecked), 'x' or 'X' (checked).
# Group 2 captures the task content (the description of the checklist item).
TASK_PATTERN = re.compile(r'^\s*[-*]\s+\[( |x|X)\]\s*(.*)')


def find_all_tasks(pr_body: str) -> List[str]:
"""Return list of all task list items (both resolved and unresolved)."""
tasks: List[str] = []
for line in pr_body.splitlines():
match = TASK_PATTERN.match(line)
if match:
tasks.append(match.group(0).strip())
return tasks


def find_unresolved_tasks(pr_body: str) -> List[str]:
"""Return list of unresolved task list items.

A task is considered resolved if it is checked (``[x]`` or ``[X]``)
or if its text is struck through using ``~~`` markers.
"""
unresolved: List[str] = []
for line in pr_body.splitlines():
match = TASK_PATTERN.match(line)
if not match:
continue
state, content = match.groups()
if state.lower() == 'x':
continue
# Check if the entire content is struck through
if content.strip().startswith('~~') and content.strip().endswith('~~'):
continue
unresolved.append(match.group(0).strip())
return unresolved


def check_pr_checklist_section(pr_body: str) -> tuple[bool, str]:
"""Check if the PR Checklist section exists with the required final checkbox.

Returns:
tuple: (is_valid, error_message)
"""
# Check if "## PR Checklist" header exists
pr_checklist_pattern = re.compile(r'^##\s+PR\s+Checklist',
re.IGNORECASE | re.MULTILINE)
if not pr_checklist_pattern.search(pr_body):
return False, "Missing '## PR Checklist' header. Please ensure you haven't removed the PR template section."

# Check if the final checkbox exists (the one users must check)
final_checkbox_pattern = re.compile(
r'^\s*[-*]\s+\[( |x|X)\]\s+Please check this after reviewing the above items',
re.MULTILINE)
if not final_checkbox_pattern.search(pr_body):
return False, "Missing the required final checkbox '- [ ] Please check this after reviewing the above items as appropriate for this PR.' Please ensure you haven't removed this from the PR template."

return True, ""


def main() -> None:
pr_body = os.environ.get("PR_BODY", "")
enforce_checklist = os.environ.get("ENFORCE_PR_HAS_CHECKLIST",
"false").lower() == "true"

# Always check for PR Checklist section when enforcement is enabled
if enforce_checklist:
is_valid, error_msg = check_pr_checklist_section(pr_body)
if not is_valid:
print(f"Error: {error_msg}")
sys.exit(1)

all_tasks = find_all_tasks(pr_body)
unresolved = find_unresolved_tasks(pr_body)

# Check if we need to enforce the presence of at least one checklist item
if enforce_checklist and not all_tasks:
print(
"Error: PR body must contain at least one checklist item when ENFORCE_PR_HAS_CHECKLIST is enabled."
)
print(
"Expected format: - [ ] Task description or * [ ] Task description")
sys.exit(1)

# If we have tasks, check if any are unresolved
if unresolved:
print("Unresolved checklist items found:")
for item in unresolved:
print(f"{item}")
sys.exit(1)

if all_tasks:
print("All checklist items resolved.")
else:
print("No checklist items found in PR body.")


if __name__ == "__main__":
main()
18 changes: 18 additions & 0 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,21 @@ jobs:
echo " - [#1234][doc] Update documentation"
echo " - [None][chore] Minor clean-up"
exit 1

check-pr-body-checklist:
name: Check PR Checklist Resolution
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'

- name: Validate PR Checklist
env:
PR_BODY: ${{ github.event.pull_request.body }}
ENFORCE_PR_HAS_CHECKLIST: true
run: python .github/scripts/pr_checklist_check.py