@@ -73,7 +73,49 @@ function barnacleContext(
7373 } ;
7474}
7575
76- function barnacleGithub ( files : ReturnType < typeof file > [ ] ) {
76+ function barnacleIssueContext (
77+ issue : Record < string , unknown > ,
78+ labels : string [ ] = [ ] ,
79+ options : Record < string , unknown > = { } ,
80+ ) {
81+ return {
82+ repo : {
83+ owner : "openclaw" ,
84+ repo : "openclaw" ,
85+ } ,
86+ payload : {
87+ action : options . action ?? "opened" ,
88+ label : options . label ,
89+ sender : options . sender ,
90+ issue : {
91+ number : 456 ,
92+ title : "OpenClaw issue" ,
93+ body : "" ,
94+ author_association : "CONTRIBUTOR" ,
95+ user : {
96+ login : "contributor" ,
97+ } ,
98+ labels : labels . map ( ( name ) => ( { name } ) ) ,
99+ ...issue ,
100+ } ,
101+ comment : options . comment ,
102+ } ,
103+ } ;
104+ }
105+
106+ function barnacleGithub (
107+ files : ReturnType < typeof file > [ ] ,
108+ options : { maintainerLogins ?: string [ ] ; repositoryRoles ?: Record < string , string > } = { } ,
109+ ) {
110+ const maintainerLogins = new Set (
111+ ( options . maintainerLogins ?? [ ] ) . map ( ( login ) => login . toLowerCase ( ) ) ,
112+ ) ;
113+ const repositoryRoles = Object . fromEntries (
114+ Object . entries ( options . repositoryRoles ?? { } ) . map ( ( [ login , role ] ) => [
115+ login . toLowerCase ( ) ,
116+ role ,
117+ ] ) ,
118+ ) ;
77119 const calls = {
78120 addLabels : [ ] as Array < { issue_number : number ; labels : string [ ] } > ,
79121 createComment : [ ] as Array < { issue_number : number ; body : string } > ,
@@ -115,14 +157,25 @@ function barnacleGithub(files: ReturnType<typeof file>[]) {
115157 listFiles : async ( ) => files ,
116158 } ,
117159 repos : {
118- getCollaboratorPermissionLevel : async ( ) => ( {
119- data : {
120- role_name : "read" ,
121- } ,
122- } ) ,
160+ getCollaboratorPermissionLevel : async ( { username } : { username : string } ) => {
161+ const role = repositoryRoles [ username . toLowerCase ( ) ] ?? "read" ;
162+ return {
163+ data : {
164+ permission : role ,
165+ role_name : role ,
166+ } ,
167+ } ;
168+ } ,
123169 } ,
124170 teams : {
125- getMembershipForUserInOrg : async ( ) => {
171+ getMembershipForUserInOrg : async ( { username } : { username : string } ) => {
172+ if ( maintainerLogins . has ( username . toLowerCase ( ) ) ) {
173+ return {
174+ data : {
175+ state : "active" ,
176+ } ,
177+ } ;
178+ }
126179 const error = new Error ( "not found" ) as Error & { status : number } ;
127180 error . status = 404 ;
128181 throw error ;
@@ -234,7 +287,7 @@ describe("barnacle-auto-response", () => {
234287 expect ( labels ) . not . toContain ( candidateLabels . externalPluginCandidate ) ;
235288 } ) ;
236289
237- it ( "does not add candidate labels to maintainer-authored PRs" , async ( ) => {
290+ it ( "does not mutate maintainer-authored PRs" , async ( ) => {
238291 const { calls, github } = barnacleGithub ( [
239292 file ( "ui/src/app.ts" ) ,
240293 file ( "src/gateway/server.ts" ) ,
@@ -256,9 +309,12 @@ describe("barnacle-auto-response", () => {
256309 } ) ;
257310
258311 expect ( calls . addLabels ) . toEqual ( [ ] ) ;
312+ expect ( calls . createComment ) . toEqual ( [ ] ) ;
313+ expect ( calls . removeLabel ) . toEqual ( [ ] ) ;
314+ expect ( calls . update ) . toEqual ( [ ] ) ;
259315 } ) ;
260316
261- it ( "removes stale Barnacle candidate and PR-limit labels from maintainer-authored PRs" , async ( ) => {
317+ it ( "leaves stale Barnacle labels alone on maintainer-authored PRs" , async ( ) => {
262318 const { calls, github } = barnacleGithub ( [
263319 file ( "ui/src/app.ts" ) ,
264320 file ( "src/gateway/server.ts" ) ,
@@ -282,12 +338,93 @@ describe("barnacle-auto-response", () => {
282338 } ,
283339 } ) ;
284340
285- expect ( calls . removeLabel ) . toEqual (
286- expect . arrayContaining ( [
287- expect . objectContaining ( { name : candidateLabels . dirtyCandidate } ) ,
288- expect . objectContaining ( { name : "r: too-many-prs" } ) ,
289- ] ) ,
290- ) ;
341+ expect ( calls . addLabels ) . toEqual ( [ ] ) ;
342+ expect ( calls . createComment ) . toEqual ( [ ] ) ;
343+ expect ( calls . removeLabel ) . toEqual ( [ ] ) ;
344+ expect ( calls . update ) . toEqual ( [ ] ) ;
345+ } ) ;
346+
347+ it ( "does not mutate maintainer-authored issues" , async ( ) => {
348+ const { calls, github } = barnacleGithub ( [ ] ) ;
349+
350+ await runBarnacleAutoResponse ( {
351+ github,
352+ context : barnacleIssueContext ( {
353+ title : "TestFlight access" ,
354+ author_association : "OWNER" ,
355+ user : {
356+ login : "maintainer" ,
357+ } ,
358+ } ) ,
359+ core : {
360+ info : ( ) => undefined ,
361+ } ,
362+ } ) ;
363+
364+ expect ( calls . addLabels ) . toEqual ( [ ] ) ;
365+ expect ( calls . createComment ) . toEqual ( [ ] ) ;
366+ expect ( calls . update ) . toEqual ( [ ] ) ;
367+ } ) ;
368+
369+ it ( "does not action close labels on maintainer-authored issues" , async ( ) => {
370+ const { calls, github } = barnacleGithub ( [ ] ) ;
371+
372+ await runBarnacleAutoResponse ( {
373+ github,
374+ context : barnacleIssueContext (
375+ {
376+ title : "Need help with setup" ,
377+ author_association : "MEMBER" ,
378+ user : {
379+ login : "maintainer" ,
380+ } ,
381+ } ,
382+ [ "r: support" ] ,
383+ {
384+ action : "labeled" ,
385+ label : { name : "r: support" } ,
386+ } ,
387+ ) ,
388+ core : {
389+ info : ( ) => undefined ,
390+ } ,
391+ } ) ;
392+
393+ expect ( calls . createComment ) . toEqual ( [ ] ) ;
394+ expect ( calls . update ) . toEqual ( [ ] ) ;
395+ } ) ;
396+
397+ it ( "does not respond to maintainer comments on contributor items" , async ( ) => {
398+ const { calls, github } = barnacleGithub ( [ ] , { maintainerLogins : [ "maintainer" ] } ) ;
399+
400+ await runBarnacleAutoResponse ( {
401+ github,
402+ context : barnacleIssueContext (
403+ {
404+ title : "Contributor issue" ,
405+ user : {
406+ login : "contributor" ,
407+ } ,
408+ } ,
409+ [ ] ,
410+ {
411+ action : "created" ,
412+ comment : {
413+ body : "testflight" ,
414+ user : {
415+ login : "maintainer" ,
416+ type : "User" ,
417+ } ,
418+ } ,
419+ } ,
420+ ) ,
421+ core : {
422+ info : ( ) => undefined ,
423+ } ,
424+ } ) ;
425+
426+ expect ( calls . createComment ) . toEqual ( [ ] ) ;
427+ expect ( calls . update ) . toEqual ( [ ] ) ;
291428 } ) ;
292429
293430 it ( "does not close automation PRs for the active PR limit" , async ( ) => {
0 commit comments