22 * Core maintainer team lookup
33 */
44
5- import type { Logger } from ' pino' ;
6- import type { Config } from ' ../config.js' ;
7- import type { GitHubClient } from ' ../github/client.js' ;
5+ import type { Logger } from " pino" ;
6+ import type { Config } from " ../config.js" ;
7+ import type { GitHubClient } from " ../github/client.js" ;
88
99/**
10- * Subteams of steering-committee whose members can sponsor SEPs.
10+ * The root team whose members (including all subteam members) can sponsor SEPs.
1111 */
12- const SPONSOR_TEAMS = [
13- 'core-maintainers' ,
14- 'moderators' ,
15- 'working-groups' ,
16- 'interest-groups' ,
17- 'sdk-maintainers' ,
18- 'inspector-maintainers' ,
19- 'mcpb-maintainers' ,
20- 'docs-maintainers' ,
21- 'lead-maintainers' ,
22- ] ;
23-
24- /**
25- * Fallback list of allowed sponsors.
26- * Used when the GitHub Teams API is not accessible.
27- * Keep this in sync with the steering-committee subteams.
28- *
29- * Generated from: steering-committee subteams
30- * Last updated: 2026-01-16
31- */
32- const FALLBACK_SPONSORS = new Set ( [
33- // core-maintainers
34- 'jspahrsummers' , 'pcarleton' , 'CaitieM20' , 'pwwpche' , 'kurtisvg' ,
35- 'localden' , 'nickcoai' , '000-000-000-000-000' , 'dsp-ant' , 'bhosmer-ant' ,
36- // moderators
37- 'jonathanhefner' , 'cliffhall' , 'evalstate' , 'tadasant' , 'maheshmurag' ,
38- 'olaservo' , 'jerome3o-anthropic' ,
39- // working-groups
40- 'toby' , 'aaronpk' , 'felixweinberger' , 'domdomegg' , 'rdimitrov' ,
41- 'an-dustin' , 'LucaButBoring' , 'D-McAdams' , 'jenn-newton' , 'og-ant' , 'petery-ant' ,
42- // interest-groups
43- 'sambhav' , 'PederHP' ,
44- // sdk-maintainers
45- 'mattt' , 'koic' , 'michaelneale' , 'fabpot' , 'atesgoral' , 'halter73' ,
46- 'nicolas-grekas' , 'markpollack' , 'ochafik' , 'stallent' , 'ignatov' ,
47- 'alexhancock' , 'KKonstantinov' , 'ansaba' , 'pronskiy' , 'Nyholm' ,
48- 'tzolov' , 'kpavlov' , 'topherbullock' , 'movetz' , 'chemicL' , 'stephentoub' ,
49- 'eiriktsarpalis' , 'chr-hertel' , 'maciej-kisiel' , 'e5l' , 'jamadeo' ,
50- // inspector-maintainers (KKonstantinov, cliffhall, olaservo already listed)
51- // mcpb-maintainers
52- 'felixrieseberg' , 'MarshallOfSound' , 'asklar' , 'joan-anthropic' ,
53- // docs-maintainers
54- 'ihrpr' , 'a-akimov' ,
55- // lead-maintainers (dsp-ant already listed)
56- ] ) ;
12+ const SPONSOR_ROOT_TEAM = "steering-committee" ;
5713
5814export class MaintainerResolver {
5915 private readonly config : Config ;
@@ -69,59 +25,70 @@ export class MaintainerResolver {
6925 }
7026
7127 /**
72- * Load allowed sponsors from the API (all steering-committee subteams),
73- * falling back to static list on error .
28+ * Load allowed sponsors from the API by recursively traversing
29+ * all subteams of steering-committee .
7430 */
7531 private async ensureSponsorsLoaded ( ) : Promise < Set < string > > {
7632 if ( this . maintainerSet ) {
7733 return this . maintainerSet ;
7834 }
7935
8036 if ( this . loadAttempted ) {
81- // Already tried and failed, use fallback
82- return FALLBACK_SPONSORS ;
37+ // Already tried and failed, return empty set
38+ return new Set ( ) ;
8339 }
8440
8541 this . loadAttempted = true ;
8642
8743 try {
44+ // Discover all teams recursively from the root team
45+ const allTeams = await this . github . getAllDescendantTeams (
46+ this . config . targetOwner ,
47+ SPONSOR_ROOT_TEAM ,
48+ ) ;
49+
50+ this . logger ?. debug (
51+ { teams : allTeams } ,
52+ "Discovered sponsor teams from steering-committee" ,
53+ ) ;
54+
8855 const allMembers = new Set < string > ( ) ;
8956
90- // Fetch members from all sponsor teams
91- for ( const team of SPONSOR_TEAMS ) {
57+ // Fetch members from all discovered teams
58+ for ( const team of allTeams ) {
9259 try {
9360 const members = await this . github . getTeamMembers (
9461 this . config . targetOwner ,
95- team
62+ team ,
9663 ) ;
9764 for ( const member of members ) {
9865 allMembers . add ( member ) ;
9966 }
10067 } catch ( error ) {
10168 this . logger ?. debug (
10269 { team, error : String ( error ) } ,
103- ' Failed to load team, continuing with others'
70+ " Failed to load team members , continuing with others" ,
10471 ) ;
10572 }
10673 }
10774
10875 if ( allMembers . size > 0 ) {
10976 this . maintainerSet = allMembers ;
11077 this . logger ?. info (
111- { count : allMembers . size } ,
112- ' Loaded allowed sponsors from API'
78+ { count : allMembers . size , teamCount : allTeams . length } ,
79+ " Loaded allowed sponsors from API" ,
11380 ) ;
11481 return this . maintainerSet ;
11582 }
11683
117- // All teams failed, use fallback
118- throw new Error ( 'No team members loaded from any team' ) ;
84+ throw new Error ( "No team members loaded from any team" ) ;
11985 } catch ( error ) {
120- this . logger ?. warn (
86+ this . logger ?. error (
12187 { error : String ( error ) } ,
122- ' Failed to load sponsors from API, using fallback list'
88+ " Failed to load sponsors from API" ,
12389 ) ;
124- this . maintainerSet = FALLBACK_SPONSORS ;
90+ // No fallback - return empty set
91+ this . maintainerSet = new Set ( ) ;
12592 return this . maintainerSet ;
12693 }
12794 }
@@ -135,13 +102,6 @@ export class MaintainerResolver {
135102 return sponsors . has ( username ) ;
136103 }
137104
138- /**
139- * Alias for canSponsor (for backward compatibility)
140- */
141- async isCoreMaintainer ( username : string ) : Promise < boolean > {
142- return this . canSponsor ( username ) ;
143- }
144-
145105 /**
146106 * Get the sponsor (allowed assignee) for a SEP
147107 */
0 commit comments