Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
00b419e
first commit
venkywonka Jun 26, 2025
40a7385
add some more module-owners/modules
venkywonka Jun 27, 2025
fa9face
bug fix
venkywonka Jun 27, 2025
4786bd9
use default permissions in action
venkywonka Jun 27, 2025
6e84969
use new REVIEW_ASSIGNING_TOKEN secret
venkywonka Jun 30, 2025
8f61bb0
pull_request->pull_request_target to access secrets
venkywonka Jun 30, 2025
c32a374
dry run changes
venkywonka Jun 30, 2025
405f0d3
Enhance auto-reviewer assignment with duplicate prevention and better UX
venkywonka Jun 30, 2025
64438f5
Switch to pull_request_target for secret access
venkywonka Jun 30, 2025
d1a39b0
Add explicit permissions for pull request writes and secret access
venkywonka Jun 30, 2025
decd175
Switch from pull_request_target to pull_request trigger
venkywonka Jun 30, 2025
5c8b292
Add auto-assign feature in github actions for PRs
venkywonka Jun 26, 2025
e11ccf9
docs: add detailed auto-assign PR reviewer documentation
venkywonka Jul 1, 2025
9be3f69
add comprehensive testing, change to pull_request_target
venkywonka Jul 2, 2025
63339cd
fix: Major module mapping overhaul for auto-assign reviewers
venkywonka Jul 3, 2025
7829374
first commit
venkywonka Jun 26, 2025
7091eaf
add some more module-owners/modules
venkywonka Jun 27, 2025
52713d2
bug fix
venkywonka Jun 27, 2025
8435ff0
use default permissions in action
venkywonka Jun 27, 2025
e58fe23
use new REVIEW_ASSIGNING_TOKEN secret
venkywonka Jun 30, 2025
89322bf
pull_request->pull_request_target to access secrets
venkywonka Jun 30, 2025
9d78b23
dry run changes
venkywonka Jun 30, 2025
831501f
Enhance auto-reviewer assignment with duplicate prevention and better UX
venkywonka Jun 30, 2025
d5cdb65
Switch to pull_request_target for secret access
venkywonka Jun 30, 2025
a2814d3
Add explicit permissions for pull request writes and secret access
venkywonka Jun 30, 2025
26e4546
Switch from pull_request_target to pull_request trigger
venkywonka Jun 30, 2025
af43fd9
Merge fork-user/venky/auto-assign-pr-test into origin-user/venky/auto…
venkywonka Jul 3, 2025
192a3be
Fix CONTRIBUTING.md to match fork version completely
venkywonka Jul 3, 2025
c448fba
Complete merge with remote user/venky/auto-assign-pr-test changes
venkywonka Jul 3, 2025
18683e3
Enhanced assign_reviewers.py with detailed feedback for unmapped file…
venkywonka Jul 3, 2025
aff0d19
Merge updated fork changes into origin branch
venkywonka Jul 3, 2025
cfb8789
fix inconsistent CONTRIBUTING.md
venkywonka Jul 3, 2025
518c15b
feat: enhance auto-assign reviewer script with CODEOWNERS support
venkywonka Jul 3, 2025
1d127f9
Merge fork changes: enhance auto-assign reviewer script with CODEOWNE…
venkywonka Jul 3, 2025
2440352
Update auto-assign.yml to use .github/module-owners.json instead of
venkywonka Jul 4, 2025
b5d0d1f
Add auto-assign feature in github actions for PRs
venkywonka Jun 26, 2025
8ab5553
docs: add detailed auto-assign PR reviewer documentation
venkywonka Jul 1, 2025
eaef704
add comprehensive testing, change to pull_request_target
venkywonka Jul 2, 2025
c3ff1b4
fix: Major module mapping overhaul for auto-assign reviewers
venkywonka Jul 3, 2025
d3d110f
Enhanced assign_reviewers.py with detailed feedback for unmapped file…
venkywonka Jul 3, 2025
d2cc561
fix 'first-match-wins' bug and test it
venkywonka Jul 8, 2025
2597ad5
implement per-module reviewer assignment for better coverage, test it
venkywonka Jul 8, 2025
1f02d53
Merge changes from user/venky/auto-assign-pr-test to origin-user/venk…
venkywonka Jul 8, 2025
1887a26
add workflow
venkywonka Jul 14, 2025
8a4b365
[pr-checklist] fix path spec in workflow
venkywonka Jul 15, 2025
a943918
use pull_request_target
venkywonka Jul 15, 2025
947806e
add 👍 enforcement
venkywonka Jul 15, 2025
53d1941
Update CONTRIBUTING.md
venkywonka Jul 14, 2025
33dab6a
add 👍 enforcement
venkywonka Jul 15, 2025
cf7052a
Merge branch 'user/venky/auto-assign-pr-test' into test-fork
venkywonka Jul 15, 2025
0efbd83
enforce check instead of reaction
venkywonka Jul 15, 2025
3ad8e4b
Merge branch 'user/venky/auto-assign-pr-test' into test-fork
venkywonka Jul 15, 2025
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
39 changes: 39 additions & 0 deletions .github/module-owners.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"Generic Runtime": ["funatiq", "pcastonguay", "Shixiaowei02", "MartinMarciniszyn", "schetlur-nv", "dcampora"],
"Triton Backend": ["Tabrizian", "pcastonguay", "schetlur-nv"],
"LLM API/Workflow": ["Superjomn", "syuoni", "nv-guomingz", "litaotju", "QiJune"],
"KV-Cache Management":["thorjohnsen", "schetlur-nv"],
"Low Precision":["Tracin", "nv-guomingz", "Naveassaf"],
"Speculative Decoding":["yweng0828", "nekorobov", "lfr-0531"],
"Customized Kernels":["lowsfer", "PerkzZheng", "jdemouth-nvidia"],
"Performance": ["kaiyux", "jiahanc", "hypdeb"],
"Lora/P-tuning":["byshiue", "shaharmor98"],
"Disaggregated Serving":["Shixiaowei02", "joyang-nv", "chuangz0", "schetlur-nv"],
"Documentation":["nv-guomingz"],
"Sampling": ["dcampora", "lfr-0531", "Naveassaf", "syuoni", "yweng0828"],
"Memory": ["litaotju", "peaceh-nv"],
"Installation": ["hchings", "Superjomn", "nv-guomingz", "QiJune"],
"GitHub Configuration": ["tburt-nv", "niukuo"],
"Jenkins Pipelines": ["chzblych", "niukuo"],
"Test Configuration": ["niukuo", "syuoni", "LarryXFly"],
"Test Waive List": ["chzblych", "niukuo"],
"Integration Tests": ["LarryXFly", "niukuo"],
"Torch Framework": ["QiJune", "hlu1"],
"Torch Attention Backend": ["yuxianq", "hlu1"],
"Torch AutoDeploy": ["lucaslie", "suyoggupta"],
"Torch Compilation": ["litaotju", "yizhang-nv", "liji-nv"],
"Torch Custom Ops": ["yizhang-nv"],
"Torch Distributed": ["yilin-void", "yuxianq", "hyukn", "yizhang-nv", "hlu1"],
"Torch PyExecutor": ["dongxuy04", "funatiq", "dcampora", "HuiGao-NV"],
"Torch Speculative": ["lfr-0531", "mikeiovine"],
"Autotuner": ["hyukn", "litaotju"],
"Pipeline Interface": ["amukkara", "chang-l"],
"Torch Models": ["QiJune", "hlu1"],
"Torch Models DeepSeekV3": ["hlu1", "zongfeijing"],
"Torch Models Llama": ["chang-l", "mikeiovine"],
"Torch Modules": ["QiJune", "hlu1"],
"Torch Modules Attention": ["yuxianq", "hlu1"],
"Torch Modules Fused MOE": ["hlu1", "dongxuy04", "zongfeijing", "HuiGao-NV"],
"Torch Tests": ["QiJune", "hlu1"],
"PyTorch Examples": ["QiJune", "hlu1"]
}
33 changes: 33 additions & 0 deletions .github/module-paths.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"cpp/": "Generic Runtime",
"triton_backend/": "Triton Backend",
"tensorrt_llm/_torch/peft/": "Lora/P-tuning",
"tensorrt_llm/": "LLM API/Workflow",
"benchmarks/": "Performance",
"examples/disaggregated/": "Disaggregated Serving",
"docs/": "Documentation",
"docker/": "Installation",
".github/": "GitHub Configuration",
"jenkins/": "Jenkins Pipelines",
"tests/integration/test_lists/": "Test Configuration",
"tests/integration/test_lists/waives.txt": "Test Waive List",
"tests/integration/defs/": "Integration Tests",
"tensorrt_llm/_torch/": "Torch Framework",
"tensorrt_llm/_torch/attention_backend/": "Torch Attention Backend",
"tensorrt_llm/_torch/auto_deploy/": "Torch AutoDeploy",
"tensorrt_llm/_torch/compilation/": "Torch Compilation",
"tensorrt_llm/_torch/custom_ops/": "Torch Custom Ops",
"tensorrt_llm/_torch/distributed/": "Torch Distributed",
"tensorrt_llm/_torch/pyexecutor/": "Torch PyExecutor",
"tensorrt_llm/_torch/speculative/": "Torch Speculative",
"tensorrt_llm/autotuner.py": "Autotuner",
"tensorrt_llm/pipeline_interface.py": "Pipeline Interface",
"tensorrt_llm/_torch/models/": "Torch Models",
"tensorrt_llm/_torch/models/modeling_deepseekv3.py": "Torch Models DeepSeekV3",
"tensorrt_llm/_torch/models/modeling_llama.py": "Torch Models Llama",
"tensorrt_llm/_torch/modules/": "Torch Modules",
"tensorrt_llm/_torch/modules/attention.py": "Torch Modules Attention",
"tensorrt_llm/_torch/modules/fused_moe.py": "Torch Modules Fused MOE",
"tests/unittest/_torch/": "Torch Tests",
"examples/pytorch/": "PyTorch Examples"
}
12 changes: 12 additions & 0 deletions .github/pr-checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## 🚀 PR Checklist

- [ ] PR title follows format: `[type] Description`
- [ ] PR description explains both **what** you're doing and **why**
- [ ] Code conforms to coding conventions (see `CODING_GUIDELINES.md`)
- [ ] Test cases added for new code
- [ ] All existing tests pass
- [ ] PR and commit messages cleaned up via `git rebase -i`

---
**Please ✅ check the below item to confirm you've reviewed the checklist when ready for review!.**
- [ ] **I have reviewed the above checklist and addressed all applicable items**
295 changes: 295 additions & 0 deletions .github/scripts/assign_reviewers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
import argparse
import json
import os
import random
import subprocess
import sys
from pathlib import Path


def get_pr_changed_files(pr_number: str) -> list[str]:
"""Get files changed in PR using GitHub CLI (more reliable than git diff)"""
result = subprocess.run(
[
"gh", "pr", "view", pr_number, "--json", "files", "--jq",
".files[].path"
],
capture_output=True,
text=True,
check=True,
)
return [line.strip() for line in result.stdout.splitlines() if line.strip()]


def get_existing_reviewers(pr_number: str) -> tuple[set[str], set[str]]:
"""Get currently assigned reviewers (users and teams) for a PR"""
try:
# Get user reviewers
user_result = subprocess.run(
[
"gh", "pr", "view", pr_number, "--json", "reviewRequests",
"--jq",
"(.reviewRequests // []) | .[] | select(.login) | .login"
],
capture_output=True,
text=True,
check=True,
)
user_reviewers = {
line.strip()
for line in user_result.stdout.splitlines() if line.strip()
}

# Get team reviewers
team_result = subprocess.run(
[
"gh", "pr", "view", pr_number, "--json", "reviewRequests",
"--jq", "(.reviewRequests // []) | .[] | select(.name) | .name"
],
capture_output=True,
text=True,
check=True,
)
team_reviewers = {
line.strip()
for line in team_result.stdout.splitlines() if line.strip()
}

return user_reviewers, team_reviewers
except subprocess.CalledProcessError as e:
print(f"Warning: Could not fetch existing reviewers: {e}")
return set(), set()


def load_json(path: str):
with open(path, "r", encoding="utf-8") as f:
return json.load(f)


def map_modules(changed_files: list[str],
module_paths: dict[str, str]) -> tuple[set[str], list[str]]:
"""Map changed files to modules using MOST SPECIFIC (longest) prefix match"""
modules: set[str] = set()
unmapped_files: list[str] = []

for file in changed_files:
# Find ALL matching prefixes
matches = []
for prefix, module in module_paths.items():
if file.startswith(prefix):
matches.append((len(prefix), prefix, module))

if matches:
# Sort by prefix length (descending) to get most specific first
matches.sort(reverse=True)
most_specific_module = matches[0][2]
modules.add(most_specific_module)

# Log if there were multiple matches (for debugging)
if len(matches) > 1:
matches[0][1]
print(f" File '{file}' has overlapping mappings:")
for _, prefix, module in matches:
marker = "→" if module == most_specific_module else " "
print(f" {marker} {prefix} -> {module}")
else:
unmapped_files.append(file)

return modules, unmapped_files


def gather_reviewers(
modules: set[str],
module_owners: dict[str, list[str]],
*,
pr_author: str | None = None,
existing_reviewers: set[str] | None = None,
per_module_limit: int = 2
) -> tuple[list[str], dict[str, list[str]], set[str]]:
"""
Gather reviewers ensuring each module gets representation.

Args:
modules: Set of module names that were touched
module_owners: Dict mapping module names to lists of owners
pr_author: PR author to exclude from reviewers
existing_reviewers: Set of already assigned reviewers to exclude
per_module_limit: Maximum reviewers to assign per module

Returns:
- List of all unique reviewers to assign
- Dict mapping modules to their assigned reviewers
- Set of modules without owners
"""
all_reviewers: set[str] = set()
module_assignments: dict[str, list[str]] = {}
modules_without_owners: set[str] = set()

for module in sorted(modules): # Sort for consistent ordering
owners = module_owners.get(module, [])
if not owners:
modules_without_owners.add(module)
module_assignments[module] = []
continue

# Filter out PR author and existing reviewers
eligible_owners = [
o for o in owners if o != pr_author and (
not existing_reviewers or o not in existing_reviewers)
]

if not eligible_owners:
# All owners are excluded
print(
f" ⚠️ Module '{module}': All owners excluded (PR author or already assigned)"
)
module_assignments[module] = []
continue

# Sample up to per_module_limit reviewers for this module
num_to_select = min(len(eligible_owners), per_module_limit)
selected = random.sample(eligible_owners, num_to_select)

module_assignments[module] = selected
all_reviewers.update(selected)

return sorted(all_reviewers), module_assignments, modules_without_owners


def main() -> None:
parser = argparse.ArgumentParser(
description="Assign reviewers based on changed modules")
parser.add_argument("--dry-run",
action="store_true",
help="Print the gh command instead of executing")
parser.add_argument(
"--force-assign",
action="store_true",
help=
"Assign reviewers even if some already exist (default: only assign if no reviewers)"
)
args = parser.parse_args()

pr_number = os.environ["PR_NUMBER"]
per_module_limit = int(os.environ.get("PER_MODULE_REVIEWER_LIMIT", "2"))
pr_author = os.environ.get("PR_AUTHOR")

print(f"Testing PR #{pr_number} with author: {pr_author}")
print(f"Per-module reviewer limit: {per_module_limit}")

# Check existing reviewers
existing_user_reviewers, existing_team_reviewers = get_existing_reviewers(
pr_number)
total_existing = len(existing_user_reviewers) + len(existing_team_reviewers)

print(f"Existing user reviewers: {sorted(existing_user_reviewers)}")
print(f"Existing team reviewers: {sorted(existing_team_reviewers)}")

# Skip assignment if reviewers already exist (unless forced)
if total_existing > 0 and not args.force_assign:
print(
f"✅ PR already has {total_existing} reviewer(s) assigned. Skipping auto-assignment."
)
print(" Use --force-assign to assign additional reviewers.")
return

try:
changed_files = get_pr_changed_files(pr_number)
print(f"Changed files: {changed_files}")

module_paths = load_json(Path(".github") / "module-paths.json")
module_owners = load_json(Path(".github") / "module-owners.json")

modules, unmapped_files = map_modules(changed_files, module_paths)
reviewers, module_assignments, modules_without_owners = gather_reviewers(
modules,
module_owners,
pr_author=pr_author,
existing_reviewers=
existing_user_reviewers, # Avoid re-assigning existing users
per_module_limit=per_module_limit)

print(f"\nChanged modules: {sorted(modules)}")

# Show module-specific assignments
if module_assignments:
print("\nModule assignments:")
for module, assigned in sorted(module_assignments.items()):
if assigned:
print(f" {module}: {assigned}")
else:
print(f" {module}: No eligible reviewers")

print(f"\nFinal reviewers to assign: {reviewers}")

# Provide detailed feedback about coverage gaps
if unmapped_files:
print(f"⚠️ Files with no module mapping: {unmapped_files}")
print(
f" These files are not covered in .github/module-paths.json")
print(
f" Consider adding appropriate module mappings for these paths."
)

if modules_without_owners:
print(
f"⚠️ Modules with no owners: {sorted(modules_without_owners)}")
print(
f" These modules exist in module-paths.json but have no owners in module-owners.json"
)
print(f" Consider adding owner assignments for these modules.")

if reviewers:
cmd = ["gh", "pr", "edit", pr_number]
for reviewer in reviewers:
cmd.extend(["--add-reviewer", reviewer])

if args.dry_run:
print(f"🔍 DRY RUN: {' '.join(cmd)}")
else:
try:
subprocess.run(cmd, check=True)
print(
f"✅ Successfully assigned {len(reviewers)} new reviewer(s)"
)
except subprocess.CalledProcessError as e:
print(f"❌ Failed to add reviewers: {e}", file=sys.stderr)
print(
" This might be due to permissions or invalid usernames"
)
sys.exit(1)
else:
print("✅ No new reviewers to assign")

# Explain why no reviewers were assigned
if not modules and not unmapped_files:
print(" Reason: No files were changed in this PR")
elif not modules and unmapped_files:
print(
" Reason: All changed files are unmapped (no module coverage)"
)
print(
" ➜ Action needed: Add module mappings to .github/module-paths.json"
)
elif modules and not reviewers:
if modules_without_owners:
print(" Reason: Matched modules have no assigned owners")
print(
" ➜ Action needed: Add owner assignments to .github/module-owners.json"
)
else:
print(
" Reason: All potential reviewers are already assigned or excluded"
)
else:
print(
" Reason: Complex combination of mapping/ownership issues (see warnings above)"
)

except subprocess.CalledProcessError as e:
print(f"❌ Error processing PR: {e}", file=sys.stderr)
sys.exit(1)


if __name__ == "__main__":
main()
Loading