@@ -99,31 +99,61 @@ def map_modules(changed_files: list[str],
9999
100100
101101def gather_reviewers (
102- modules : set [str ],
103- module_owners : dict [str , list [str ]],
104- * ,
105- pr_author : str | None = None ,
106- existing_reviewers : set [str ] | None = None
107- ) -> tuple [list [str ], set [str ]]:
108- """Gather reviewers and return both reviewers and modules without owners"""
109- reviewers : set [str ] = set ()
102+ modules : set [str ],
103+ module_owners : dict [str , list [str ]],
104+ * ,
105+ pr_author : str | None = None ,
106+ existing_reviewers : set [str ] | None = None ,
107+ per_module_limit : int = 2
108+ ) -> tuple [list [str ], dict [str , list [str ]], set [str ]]:
109+ """
110+ Gather reviewers ensuring each module gets representation.
111+
112+ Args:
113+ modules: Set of module names that were touched
114+ module_owners: Dict mapping module names to lists of owners
115+ pr_author: PR author to exclude from reviewers
116+ existing_reviewers: Set of already assigned reviewers to exclude
117+ per_module_limit: Maximum reviewers to assign per module
118+
119+ Returns:
120+ - List of all unique reviewers to assign
121+ - Dict mapping modules to their assigned reviewers
122+ - Set of modules without owners
123+ """
124+ all_reviewers : set [str ] = set ()
125+ module_assignments : dict [str , list [str ]] = {}
110126 modules_without_owners : set [str ] = set ()
111127
112- for module in modules :
128+ for module in sorted ( modules ): # Sort for consistent ordering
113129 owners = module_owners .get (module , [])
114- if owners :
115- reviewers .update (owners )
116- else :
130+ if not owners :
117131 modules_without_owners .add (module )
132+ module_assignments [module ] = []
133+ continue
134+
135+ # Filter out PR author and existing reviewers
136+ eligible_owners = [
137+ o for o in owners if o != pr_author and (
138+ not existing_reviewers or o not in existing_reviewers )
139+ ]
140+
141+ if not eligible_owners :
142+ # All owners are excluded
143+ print (
144+ f" ⚠️ Module '{ module } ': All owners excluded (PR author or already assigned)"
145+ )
146+ module_assignments [module ] = []
147+ continue
118148
119- if pr_author :
120- reviewers .discard (pr_author )
149+ # Sample up to per_module_limit reviewers for this module
150+ num_to_select = min (len (eligible_owners ), per_module_limit )
151+ selected = random .sample (eligible_owners , num_to_select )
121152
122- # Remove existing reviewers to avoid duplicate assignments
123- if existing_reviewers :
124- reviewers -= existing_reviewers
153+ module_assignments [module ] = selected
154+ all_reviewers .update (selected )
125155
126- return sorted (reviewers ) , modules_without_owners
156+ return sorted (all_reviewers ), module_assignments , modules_without_owners
127157
128158
129159def main () -> None :
@@ -141,10 +171,11 @@ def main() -> None:
141171 args = parser .parse_args ()
142172
143173 pr_number = os .environ ["PR_NUMBER" ]
144- reviewer_limit = int (os .environ .get ("REVIEWER_LIMIT " , "0 " ))
174+ per_module_limit = int (os .environ .get ("PER_MODULE_REVIEWER_LIMIT " , "2 " ))
145175 pr_author = os .environ .get ("PR_AUTHOR" )
146176
147177 print (f"Testing PR #{ pr_number } with author: { pr_author } " )
178+ print (f"Per-module reviewer limit: { per_module_limit } " )
148179
149180 # Check existing reviewers
150181 existing_user_reviewers , existing_team_reviewers = get_existing_reviewers (
@@ -170,19 +201,26 @@ def main() -> None:
170201 module_owners = load_json (Path (".github" ) / "module-owners.json" )
171202
172203 modules , unmapped_files = map_modules (changed_files , module_paths )
173- reviewers , modules_without_owners = gather_reviewers (
204+ reviewers , module_assignments , modules_without_owners = gather_reviewers (
174205 modules ,
175206 module_owners ,
176207 pr_author = pr_author ,
177208 existing_reviewers =
178- existing_user_reviewers # Avoid re-assigning existing users
179- )
209+ existing_user_reviewers , # Avoid re-assigning existing users
210+ per_module_limit = per_module_limit )
180211
181- if reviewer_limit and len (reviewers ) > reviewer_limit :
182- reviewers = random .sample (reviewers , reviewer_limit )
212+ print (f"\n Changed modules: { sorted (modules )} " )
213+
214+ # Show module-specific assignments
215+ if module_assignments :
216+ print ("\n Module assignments:" )
217+ for module , assigned in sorted (module_assignments .items ()):
218+ if assigned :
219+ print (f" { module } : { assigned } " )
220+ else :
221+ print (f" { module } : No eligible reviewers" )
183222
184- print (f"Changed modules: { sorted (modules )} " )
185- print (f"Potential reviewers: { reviewers } " )
223+ print (f"\n Final reviewers to assign: { reviewers } " )
186224
187225 # Provide detailed feedback about coverage gaps
188226 if unmapped_files :
0 commit comments