Skip to content

Commit c1aa253

Browse files
feat(github): add Get-BotAuthors and Test-WorkflowRateLimit to GitHubHelpers (#492)
* feat(github): add centralized Get-BotAuthors and Test-WorkflowRateLimit Add two shared functions to GitHubHelpers module: 1. Get-BotAuthors (Closes #282): - Centralized bot author list for all workflows/scripts - Categories: reviewer, automation, repository, all - Eliminates duplication across 59 files 2. Test-WorkflowRateLimit (Closes #275): - Extracted from PRMaintenanceModule for reuse - Validates API rate limits before workflow execution - Returns structured results with summary markdown 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add null safety and PowerShell 5.1 compatibility to Test-WorkflowRateLimit Addresses review comments from @gemini-code-assist[bot] - Add null check for resource existence before property access (prevents crashes on API changes) - Wrap if expressions in script blocks for PowerShell 5.1 compatibility Comment-IDs: 2650298195, 2650298198 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test: add comprehensive unit tests for Get-BotAuthors and Test-WorkflowRateLimit Addresses qa Review CRITICAL_FAIL - Add 7 tests for Get-BotAuthors (all categories, sorting, type validation) - Add 7 tests for Test-WorkflowRateLimit (success/failure paths, API errors, missing resources, custom thresholds) - Mock gh CLI to avoid external dependencies - Test edge cases: missing resources, API failures, custom thresholds All critical paths now have test coverage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: rjmurillo[bot] <rjmurillo-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 2b83a3d commit c1aa253

File tree

2 files changed

+278
-1
lines changed

2 files changed

+278
-1
lines changed

.claude/skills/github/modules/GitHubHelpers.psm1

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,13 @@ Issue Comments (line ~380)
4242
Trusted Sources (line ~600)
4343
- Get-TrustedSourceComments Filter comments by trusted users
4444
45-
Formatting (line ~640)
45+
Bot Configuration (line ~670)
46+
- Get-BotAuthors Centralized bot author list
47+
48+
Rate Limit (line ~740)
49+
- Test-WorkflowRateLimit Check API rate limits before workflow execution
50+
51+
Formatting (line ~840)
4652
- Get-PriorityEmoji P0-P3 to emoji mapping
4753
- Get-ReactionEmoji Reaction type to emoji
4854
#>
@@ -670,6 +676,157 @@ function Get-TrustedSourceComments {
670676

671677
#endregion
672678

679+
#region Bot Configuration Functions
680+
681+
function Get-BotAuthors {
682+
<#
683+
.SYNOPSIS
684+
Returns the centralized list of known bot authors.
685+
686+
.DESCRIPTION
687+
Single source of truth for bot author identification across the repository.
688+
Used by workflows, scripts, and agents to distinguish bot vs. human activity.
689+
690+
.PARAMETER Category
691+
Optional. Filter by category: 'reviewer', 'automation', 'repository', 'all' (default).
692+
693+
.OUTPUTS
694+
String array of bot author login names.
695+
696+
.EXAMPLE
697+
$bots = Get-BotAuthors
698+
if ($comment.user.login -in $bots) { Write-Host "Bot comment" }
699+
700+
.NOTES
701+
See #282 for centralization rationale.
702+
#>
703+
[CmdletBinding()]
704+
[OutputType([string[]])]
705+
param(
706+
[Parameter()]
707+
[ValidateSet('reviewer', 'automation', 'repository', 'all')]
708+
[string]$Category = 'all'
709+
)
710+
711+
$bots = @{
712+
reviewer = @(
713+
'coderabbitai[bot]'
714+
'github-copilot[bot]'
715+
'gemini-code-assist[bot]'
716+
'cursor[bot]'
717+
)
718+
automation = @(
719+
'github-actions[bot]'
720+
'dependabot[bot]'
721+
)
722+
repository = @(
723+
'rjmurillo-bot'
724+
'copilot-swe-agent[bot]'
725+
)
726+
}
727+
728+
if ($Category -eq 'all') {
729+
return $bots.Values | ForEach-Object { $_ } | Sort-Object -Unique
730+
}
731+
732+
return $bots[$Category]
733+
}
734+
735+
#endregion
736+
737+
#region Rate Limit Functions
738+
739+
function Test-WorkflowRateLimit {
740+
<#
741+
.SYNOPSIS
742+
Checks GitHub API rate limits before workflow execution.
743+
744+
.DESCRIPTION
745+
Validates that all required API resource types have sufficient
746+
remaining quota. Returns structured results for workflow decisions.
747+
748+
.PARAMETER ResourceThresholds
749+
Hashtable of resource names to minimum remaining threshold.
750+
751+
.OUTPUTS
752+
PSCustomObject with Success, Resources, SummaryMarkdown, CoreRemaining.
753+
754+
.EXAMPLE
755+
$result = Test-WorkflowRateLimit
756+
if (-not $result.Success) { Write-Error "Rate limit too low"; exit 1 }
757+
758+
.NOTES
759+
Extracted from PRMaintenanceModule per #275.
760+
#>
761+
[CmdletBinding()]
762+
[OutputType([PSCustomObject])]
763+
param(
764+
[hashtable]$ResourceThresholds = @{
765+
'core' = 100
766+
'search' = 15
767+
'code_search' = 5
768+
'graphql' = 100
769+
}
770+
)
771+
772+
$rateLimitJson = gh api rate_limit 2>&1
773+
if ($LASTEXITCODE -ne 0) {
774+
throw "Failed to fetch rate limits: $rateLimitJson"
775+
}
776+
777+
$rateLimit = $rateLimitJson | ConvertFrom-Json
778+
$resources = @{}
779+
$allPassed = $true
780+
$summaryLines = @(
781+
"### API Rate Limit Status",
782+
"",
783+
"| Resource | Remaining | Threshold | Status |",
784+
"|----------|-----------|-----------|--------|"
785+
)
786+
787+
foreach ($resource in $ResourceThresholds.Keys) {
788+
# Check if resource exists in API response (null safety for API changes)
789+
$resourceData = $rateLimit.resources.$resource
790+
if ($null -eq $resourceData) {
791+
Write-Warning "Resource '$resource' not found in rate limit response"
792+
$allPassed = $false
793+
$summaryLines += "| $resource | N/A | $($ResourceThresholds[$resource]) | X MISSING |"
794+
continue
795+
}
796+
797+
$remaining = $resourceData.remaining
798+
$limit = $resourceData.limit
799+
$reset = $resourceData.reset
800+
$threshold = $ResourceThresholds[$resource]
801+
$passed = $remaining -ge $threshold
802+
803+
if (-not $passed) { $allPassed = $false }
804+
805+
# PowerShell 5.1 compatibility: wrap if expressions in script blocks
806+
$status = & { if ($passed) { "OK" } else { "TOO LOW" } }
807+
$statusIcon = & { if ($passed) { "+" } else { "X" } }
808+
809+
$resources[$resource] = @{
810+
Remaining = $remaining
811+
Limit = $limit
812+
Reset = $reset
813+
Threshold = $threshold
814+
Passed = $passed
815+
}
816+
817+
$summaryLines += "| $resource | $remaining | $threshold | $statusIcon $status |"
818+
}
819+
820+
return [PSCustomObject]@{
821+
Success = $allPassed
822+
Resources = $resources
823+
SummaryMarkdown = $summaryLines -join "`n"
824+
CoreRemaining = $rateLimit.resources.core.remaining
825+
}
826+
}
827+
828+
#endregion
829+
673830
#region Exit Codes
674831

675832
<#
@@ -706,6 +863,10 @@ Export-ModuleMember -Function @(
706863
'New-IssueComment'
707864
# Trusted sources
708865
'Get-TrustedSourceComments'
866+
# Bot configuration
867+
'Get-BotAuthors'
868+
# Rate limit
869+
'Test-WorkflowRateLimit'
709870
# Formatting
710871
'Get-PriorityEmoji'
711872
'Get-ReactionEmoji'

test/claude/skills/github/GitHubHelpers.Tests.ps1

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ Describe "GitHubHelpers Module" {
4545
It "Exports Get-ReactionEmoji function" {
4646
Get-Command -Module GitHubHelpers -Name Get-ReactionEmoji | Should -Not -BeNullOrEmpty
4747
}
48+
49+
It "Exports Get-BotAuthors function" {
50+
Get-Command -Module GitHubHelpers -Name Get-BotAuthors | Should -Not -BeNullOrEmpty
51+
}
52+
53+
It "Exports Test-WorkflowRateLimit function" {
54+
Get-Command -Module GitHubHelpers -Name Test-WorkflowRateLimit | Should -Not -BeNullOrEmpty
55+
}
4856
}
4957

5058
Context "Get-PriorityEmoji" {
@@ -117,6 +125,114 @@ Describe "GitHubHelpers Module" {
117125
$result | Should -BeOfType [bool]
118126
}
119127
}
128+
129+
Context "Get-BotAuthors" {
130+
It "Returns array of strings" {
131+
$result = Get-BotAuthors
132+
$result | Should -BeOfType [System.Object[]]
133+
$result | ForEach-Object { $_ | Should -BeOfType [string] }
134+
}
135+
136+
It "Returns all bots by default" {
137+
$result = Get-BotAuthors
138+
$result | Should -Contain "coderabbitai[bot]"
139+
$result | Should -Contain "github-actions[bot]"
140+
$result | Should -Contain "rjmurillo-bot"
141+
}
142+
143+
It "Returns only reviewer bots for Category 'reviewer'" {
144+
$result = Get-BotAuthors -Category 'reviewer'
145+
$result | Should -Contain "coderabbitai[bot]"
146+
$result | Should -Contain "github-copilot[bot]"
147+
$result | Should -Not -Contain "github-actions[bot]"
148+
$result | Should -Not -Contain "rjmurillo-bot"
149+
}
150+
151+
It "Returns only automation bots for Category 'automation'" {
152+
$result = Get-BotAuthors -Category 'automation'
153+
$result | Should -Contain "github-actions[bot]"
154+
$result | Should -Contain "dependabot[bot]"
155+
$result | Should -Not -Contain "coderabbitai[bot]"
156+
}
157+
158+
It "Returns only repository bots for Category 'repository'" {
159+
$result = Get-BotAuthors -Category 'repository'
160+
$result | Should -Contain "rjmurillo-bot"
161+
$result | Should -Contain "copilot-swe-agent[bot]"
162+
$result | Should -Not -Contain "github-actions[bot]"
163+
}
164+
165+
It "Returns all bots for Category 'all'" {
166+
$result = Get-BotAuthors -Category 'all'
167+
$result | Should -Contain "coderabbitai[bot]"
168+
$result | Should -Contain "github-actions[bot]"
169+
$result | Should -Contain "rjmurillo-bot"
170+
}
171+
172+
It "Returns sorted list" {
173+
$result = Get-BotAuthors
174+
$sorted = $result | Sort-Object
175+
$result | Should -Be $sorted
176+
}
177+
}
178+
179+
Context "Test-WorkflowRateLimit" {
180+
It "Returns PSCustomObject with required properties" {
181+
Mock gh { '{"resources":{"core":{"remaining":5000,"limit":5000,"reset":1234567890},"search":{"remaining":30,"limit":30,"reset":1234567890},"code_search":{"remaining":10,"limit":10,"reset":1234567890},"graphql":{"remaining":5000,"limit":5000,"reset":1234567890}}}' }
182+
183+
$result = Test-WorkflowRateLimit
184+
$result | Should -BeOfType [PSCustomObject]
185+
$result.Success | Should -BeOfType [bool]
186+
$result.Resources | Should -BeOfType [hashtable]
187+
$result.SummaryMarkdown | Should -BeOfType [string]
188+
$result.CoreRemaining | Should -BeOfType [int]
189+
}
190+
191+
It "Returns Success=true when all resources above threshold" {
192+
Mock gh { '{"resources":{"core":{"remaining":5000,"limit":5000,"reset":1234567890},"search":{"remaining":30,"limit":30,"reset":1234567890},"code_search":{"remaining":10,"limit":10,"reset":1234567890},"graphql":{"remaining":5000,"limit":5000,"reset":1234567890}}}' }
193+
194+
$result = Test-WorkflowRateLimit
195+
$result.Success | Should -Be $true
196+
}
197+
198+
It "Returns Success=false when any resource below threshold" {
199+
Mock gh { '{"resources":{"core":{"remaining":50,"limit":5000,"reset":1234567890},"search":{"remaining":5,"limit":30,"reset":1234567890},"code_search":{"remaining":2,"limit":10,"reset":1234567890},"graphql":{"remaining":50,"limit":5000,"reset":1234567890}}}' }
200+
201+
$result = Test-WorkflowRateLimit
202+
$result.Success | Should -Be $false
203+
}
204+
205+
It "Handles missing resource with warning" {
206+
Mock gh { '{"resources":{"core":{"remaining":5000,"limit":5000,"reset":1234567890},"search":{"remaining":30,"limit":30,"reset":1234567890},"graphql":{"remaining":5000,"limit":5000,"reset":1234567890}}}' }
207+
Mock Write-Warning { }
208+
209+
$result = Test-WorkflowRateLimit
210+
Should -Invoke Write-Warning -Times 1 -ParameterFilter { $Message -like "*code_search*" }
211+
$result.Success | Should -Be $false
212+
}
213+
214+
It "Throws when gh api fails" {
215+
Mock gh { $global:LASTEXITCODE = 1; "API error" }
216+
217+
{ Test-WorkflowRateLimit } | Should -Throw "*Failed to fetch rate limits*"
218+
}
219+
220+
It "Uses custom thresholds" {
221+
Mock gh { '{"resources":{"core":{"remaining":500,"limit":5000,"reset":1234567890}}}' }
222+
223+
$result = Test-WorkflowRateLimit -ResourceThresholds @{ 'core' = 100 }
224+
$result.Success | Should -Be $true
225+
}
226+
227+
It "Includes SummaryMarkdown with table" {
228+
Mock gh { '{"resources":{"core":{"remaining":5000,"limit":5000,"reset":1234567890}}}' }
229+
230+
$result = Test-WorkflowRateLimit -ResourceThresholds @{ 'core' = 100 }
231+
$result.SummaryMarkdown | Should -Match '### API Rate Limit Status'
232+
$result.SummaryMarkdown | Should -Match '\| Resource \| Remaining \| Threshold \| Status \|'
233+
$result.SummaryMarkdown | Should -Match '\| core \| 5000 \| 100 \|'
234+
}
235+
}
120236
}
121237

122238
Describe "Script Parameter Validation" {

0 commit comments

Comments
 (0)