568 lines
21 KiB
PowerShell
568 lines
21 KiB
PowerShell
# ----------------------------------------------------------------
|
|
# Scenario 2: Code Quality Gatekeeper
|
|
# Flow: PR submit -> AI Review -> Check CI -> Auto-merge if pass
|
|
#
|
|
# Commands chained:
|
|
# 1. pr +list -- list open PRs
|
|
# 2. pr +view -- get PR details
|
|
# 3. pr +files -- get changed files
|
|
# 4. pr +diff -- get diff content
|
|
# 5. gitlink-code-review -- AI code review (claude CLI or keyword fallback)
|
|
# 6. api POST .../reviews -- post review comment
|
|
# 7. ci +builds -- check CI status
|
|
# 8. pr +merge -- auto-merge if quality threshold met
|
|
# ----------------------------------------------------------------
|
|
#Requires -Version 5.1
|
|
|
|
param(
|
|
[string]$Owner = "",
|
|
[string]$Repo = "",
|
|
[string]$PrId = "",
|
|
[int]$Threshold = 80,
|
|
[switch]$DryRun,
|
|
[switch]$Help
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
Import-Module "$PSScriptRoot/lib/common.psm1" -Force
|
|
|
|
if ($Help) {
|
|
Write-Host "Usage: powershell 02-code-quality-gatekeeper.ps1 -Owner OWNER -Repo REPO [-PrId ID] [-Threshold SCORE] [-DryRun]"
|
|
Write-Host ""
|
|
Write-Host " -Owner OWNER Repository owner"
|
|
Write-Host " -Repo REPO Repository name"
|
|
Write-Host " -PrId ID Specific PR to review (default: all open PRs)"
|
|
Write-Host " -Threshold SCORE Min quality score to auto-merge (default: 80)"
|
|
Write-Host " -DryRun Preview actions without executing"
|
|
exit 0
|
|
}
|
|
|
|
Check-Auth
|
|
$r = Resolve-OwnerRepo $Owner $Repo
|
|
$Owner = $r.Owner; $Repo = $r.Repo
|
|
|
|
# ----------------------------------------------------------------
|
|
# Default fallback strings (defined at script scope to avoid indented here-string issue)
|
|
$DefaultSkillDimensions = "1. 代码质量: 复杂度、命名、注释、格式`n2. 安全性: SQL注入、XSS、敏感信息、认证、输入验证`n3. 性能: 循环效率、资源泄漏、N+1查询、内存`n4. 可维护性: 代码重复、职责单一、依赖耦合、测试覆盖"
|
|
|
|
$DefaultReviewPrompt = @'
|
|
你是代码审查专家。请按 gitlink-code-review skill 的审查维度分析以下 PR。
|
|
|
|
## 审查维度与检查项
|
|
|
|
{SKILL_DIMENSIONS}
|
|
|
|
## 评分标准
|
|
- 90-100: 优秀,可直接合并
|
|
- 75-89: 良好,建议合并
|
|
- 60-74: 一般,需要改进
|
|
- <60: 较差,不建议合并
|
|
|
|
## 问题严重级别
|
|
- CRITICAL: 阻止合并
|
|
- HIGH: 强烈建议修复
|
|
- MEDIUM: 建议修复
|
|
- LOW: 可选修复
|
|
|
|
## PR 数据
|
|
|
|
PR 标题: {PR_TITLE}
|
|
变更文件:
|
|
{FILE_LIST}
|
|
代码差异:
|
|
{DIFF_CONTENT}
|
|
|
|
## 输出要求
|
|
|
|
请严格按以下 JSON 格式输出,不要输出其他内容:
|
|
{"total": <0-100>, "quality": <0-25>, "security": <0-25>, "performance": <0-25>, "maintainability": <0-25>, "issues": [{"severity": "HIGH/MEDIUM/LOW", "category": "quality/security/performance/maintainability", "file": "文件路径", "rule": "规则名", "description": "问题描述", "suggestion": "修复建议"}], "positive_notes": [{"description": "优秀实践描述"}], "recommendations": ["改进建议1"], "verdict": "PASS或FAIL"}
|
|
'@
|
|
|
|
function Review-PR {
|
|
param([string]$PrId)
|
|
|
|
Log-Title "Reviewing PR #$PrId"
|
|
|
|
# -- Step 1: Get PR details --
|
|
Log-Step "Fetching PR details..."
|
|
$prJson = Invoke-GLCheck pr,+view,--owner,$Owner,--repo,$Repo,--id,$PrId
|
|
if (-not $prJson) {
|
|
Log-Warn "Failed to fetch PR #$PrId, skipping"
|
|
return
|
|
}
|
|
$prData = $prJson.data
|
|
$prTitle = if ($prData.title) { $prData.title }
|
|
elseif ($prData.subject) { $prData.subject }
|
|
elseif ($prData.issue.subject) { $prData.issue.subject }
|
|
else { "N/A" }
|
|
$prState = if ($prData.state) { $prData.state }
|
|
elseif ($prData.status) { $prData.status }
|
|
else { "N/A" }
|
|
$prAuthor = if ($prData.author.login) { $prData.author.login }
|
|
elseif ($prData.author.username) { $prData.author.username }
|
|
elseif ($prData.issue.author.login) { $prData.issue.author.login }
|
|
else { "N/A" }
|
|
Log-Ok "PR #${PrId}: `"$prTitle`" by @$prAuthor (state: $prState)"
|
|
|
|
# -- Step 2: Get changed files --
|
|
Log-Step "Fetching changed files..."
|
|
$filesJson = Invoke-GL pr,+files,--owner,$Owner,--repo,$Repo,--id,$PrId
|
|
$fileNames = @()
|
|
$fileCount = 0
|
|
if ($filesJson) {
|
|
try {
|
|
$filesData = $filesJson | ConvertFrom-Json
|
|
if ($filesData.data.files) {
|
|
$fileNames = @($filesData.data.files | ForEach-Object {
|
|
if ($_.name) { $_.name } elseif ($_.filename) { $_.filename } else { "unknown" }
|
|
})
|
|
$fileCount = $fileNames.Count
|
|
}
|
|
} catch { }
|
|
}
|
|
Log-Ok "Changed files: $fileCount"
|
|
foreach ($f in $fileNames) { Write-Host " $f" }
|
|
|
|
# -- Step 3: Get diff --
|
|
Log-Step "Fetching diff..."
|
|
$diffContent = ""
|
|
$diffLines = 0
|
|
$diffJson = Invoke-GL pr,+diff,--owner,$Owner,--repo,$Repo,--id,$PrId
|
|
if ($diffJson) {
|
|
try {
|
|
$diffData = $diffJson | ConvertFrom-Json
|
|
$diffLines_arr = @()
|
|
if ($diffData.data.files) {
|
|
foreach ($f in $diffData.data.files) {
|
|
if ($f.sections) {
|
|
foreach ($s in $f.sections) {
|
|
if ($s.lines) {
|
|
foreach ($l in $s.lines) {
|
|
if ($l.content) { $diffLines_arr += $l.content }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$diffContent = ($diffLines_arr -join "`n")
|
|
if ($diffContent.Length -gt 5000) { $diffContent = $diffContent.Substring(0, 5000) }
|
|
$diffLines = $diffLines_arr.Count
|
|
} catch { }
|
|
}
|
|
Log-Ok "Diff: $diffLines lines"
|
|
|
|
# -- Step 4: AI-powered code review --
|
|
Log-Step "AI analyzing code quality..."
|
|
|
|
$fileListText = ""
|
|
foreach ($f in $fileNames) { $fileListText += "- $f`n" }
|
|
|
|
$diffTruncated = $diffContent
|
|
if ($diffTruncated.Length -gt 4000) { $diffTruncated = $diffTruncated.Substring(0, 4000) }
|
|
|
|
# Load skill dimensions from SKILL.md
|
|
$skillDir = "$PSScriptRoot/../skills/gitlink-code-review"
|
|
$skillDimensions = ""
|
|
if (Test-Path "$skillDir/SKILL.md") {
|
|
$skillMd = Get-Content "$skillDir/SKILL.md" -Raw -Encoding UTF8
|
|
if ($skillMd -match '(?s)## 📊 审查维度(.+?)## 🔧 使用方式') {
|
|
$section = $Matches[1]
|
|
$dimLines = ($section -split "`n" | Where-Object { $_ -match '^- \*\*' } | Select-Object -First 20)
|
|
$skillDimensions = $dimLines -join "`n"
|
|
}
|
|
}
|
|
|
|
if (-not $skillDimensions) {
|
|
$skillDimensions = $DefaultSkillDimensions
|
|
}
|
|
|
|
$reviewPrompt = $DefaultReviewPrompt -replace '\{PR_TITLE\}', $prTitle -replace '\{FILE_LIST\}', $fileListText -replace '\{DIFF_CONTENT\}', $diffTruncated -replace '\{SKILL_DIMENSIONS\}', $skillDimensions
|
|
|
|
$aiAvailable = $false
|
|
$totalScore = 0
|
|
$scoreQuality = 25
|
|
$scoreSecurity = 25
|
|
$scorePerformance = 25
|
|
$scoreMaintainability = 25
|
|
$issuesFound = @()
|
|
$aiPositive = @()
|
|
$aiRecommendations = @()
|
|
$aiVerdict = "PASS"
|
|
|
|
$claudePath = (Get-Command claude -ErrorAction SilentlyContinue).Source
|
|
if ($claudePath) {
|
|
Log-Info "Calling AI agent for code review (may take 30-60s)..."
|
|
|
|
try {
|
|
$promptFile = [System.IO.Path]::GetTempFileName()
|
|
$outFile = [System.IO.Path]::GetTempFileName()
|
|
[System.IO.File]::WriteAllText($promptFile, $reviewPrompt, [System.Text.Encoding]::UTF8)
|
|
|
|
$aiResult = $null
|
|
$proc = Start-Process -FilePath $claudePath `
|
|
-ArgumentList @("-p", "--output-format", "json") `
|
|
-RedirectStandardInput $promptFile `
|
|
-RedirectStandardOutput $outFile `
|
|
-NoNewWindow -Wait -PassThru
|
|
|
|
if ($proc.ExitCode -eq 0 -and (Test-Path $outFile)) {
|
|
$aiOutput = [System.IO.File]::ReadAllText($outFile, [System.Text.Encoding]::UTF8)
|
|
try {
|
|
$aiOutputJson = $aiOutput | ConvertFrom-Json
|
|
$aiResult = $aiOutputJson.result
|
|
} catch {
|
|
$aiResult = $aiOutput
|
|
}
|
|
}
|
|
|
|
Remove-Item $promptFile -Force -ErrorAction SilentlyContinue
|
|
Remove-Item $outFile -Force -ErrorAction SilentlyContinue
|
|
|
|
if ($aiResult) {
|
|
# Extract JSON from AI response (may contain markdown wrapping)
|
|
$aiJson = $null
|
|
$jsonCandidate = Extract-JsonBlock $aiResult
|
|
if ($jsonCandidate) {
|
|
try {
|
|
$testJson = $jsonCandidate | ConvertFrom-Json
|
|
if ($testJson.total -ne $null -and $testJson.verdict) {
|
|
$aiJson = $testJson
|
|
}
|
|
} catch { }
|
|
}
|
|
# Fallback: try direct parse
|
|
if (-not $aiJson) {
|
|
try {
|
|
$testJson = $aiResult | ConvertFrom-Json
|
|
if ($testJson.total -ne $null -and $testJson.verdict) {
|
|
$aiJson = $testJson
|
|
}
|
|
} catch { }
|
|
}
|
|
|
|
if ($aiJson) {
|
|
$totalScore = [int]($aiJson.total -as [int])
|
|
$scoreQuality = [int]($aiJson.quality -as [int])
|
|
$scoreSecurity = [int]($aiJson.security -as [int])
|
|
$scorePerformance = [int]($aiJson.performance -as [int])
|
|
$scoreMaintainability = [int]($aiJson.maintainability -as [int])
|
|
$aiVerdict = if ($aiJson.verdict) { $aiJson.verdict } else { "PASS" }
|
|
|
|
if ($aiJson.issues) {
|
|
foreach ($issue in $aiJson.issues) {
|
|
if ($issue -is [string]) {
|
|
$issuesFound += $issue
|
|
} else {
|
|
$sev = if ($issue.severity) { $issue.severity } else { "?" }
|
|
$cat = if ($issue.category) { $issue.category } else { "?" }
|
|
$desc = if ($issue.description) { $issue.description }
|
|
elseif ($issue.rule) { $issue.rule } else { "unknown" }
|
|
$file = if ($issue.file) { " ($($issue.file))" } else { "" }
|
|
$sug = if ($issue.suggestion) { " -> $($issue.suggestion)" } else { "" }
|
|
$issuesFound += "[$sev] ${cat}: $desc${file}${sug}"
|
|
}
|
|
}
|
|
}
|
|
if ($aiJson.positive_notes) {
|
|
foreach ($note in $aiJson.positive_notes) {
|
|
if ($note.description) { $aiPositive += $note.description }
|
|
elseif ($note -is [string]) { $aiPositive += $note }
|
|
}
|
|
}
|
|
if ($aiJson.recommendations) {
|
|
foreach ($rec in $aiJson.recommendations) {
|
|
if ($rec -is [string]) { $aiRecommendations += $rec }
|
|
}
|
|
}
|
|
|
|
$aiAvailable = $true
|
|
Log-Ok "AI review complete (verdict: $aiVerdict)"
|
|
} else {
|
|
Log-Warn "Could not parse AI response JSON, falling back to keyword-based"
|
|
}
|
|
}
|
|
} catch {
|
|
Log-Warn "AI call failed: $_"
|
|
}
|
|
}
|
|
|
|
# -- Fallback: keyword-based heuristics --
|
|
if (-not $aiAvailable) {
|
|
Log-Warn "AI not available, falling back to keyword-based analysis"
|
|
|
|
$scoreQuality = 25
|
|
$scoreSecurity = 25
|
|
$scorePerformance = 25
|
|
$scoreMaintainability = 25
|
|
$issuesFound = @()
|
|
|
|
if ($diffContent -match '(?i)password|secret|token|api_key|apikey|private_key') {
|
|
$scoreSecurity -= 15
|
|
$issuesFound += "SECURITY: 检测到可能的硬编码凭证"
|
|
}
|
|
if ($diffContent -match '(?i)eval\(|exec\(|system\(|shell_exec|os\.system|subprocess\.call') {
|
|
$scoreSecurity -= 10
|
|
$issuesFound += "SECURITY: 检测到危险函数调用"
|
|
}
|
|
if ($diffContent -match '(?i)TODO|FIXME|HACK|XXX') {
|
|
$scoreQuality -= 5
|
|
$issuesFound += "QUALITY: 存在 TODO/FIXME/HACK 注释"
|
|
}
|
|
if ($diffContent -match '(?i)SELECT \*|\.findAll\(\)|\.all\(\)') {
|
|
$scorePerformance -= 10
|
|
$issuesFound += "PERFORMANCE: 可能的全表查询"
|
|
}
|
|
if ($diffContent -match '(?i)sleep\(|time\.sleep|Thread\.sleep') {
|
|
$scorePerformance -= 5
|
|
$issuesFound += "PERFORMANCE: 检测到阻塞式 sleep"
|
|
}
|
|
if ($fileCount -gt 20) {
|
|
$scoreMaintainability -= 10
|
|
$issuesFound += "MAINTAINABILITY: 变更文件数量过多 ($fileCount)"
|
|
}
|
|
|
|
$totalScore = [Math]::Max(0, $scoreQuality + $scoreSecurity + $scorePerformance + $scoreMaintainability)
|
|
}
|
|
|
|
# -- Print review report --
|
|
Divider
|
|
if ($aiAvailable) {
|
|
Log-Info "AI Review Report for PR #$PrId"
|
|
} else {
|
|
Log-Info "Review Report for PR #$PrId (keyword-based)"
|
|
}
|
|
Write-Host ""
|
|
Write-Host " Overall Score: $totalScore / 100"
|
|
Write-Host " Code Quality: $scoreQuality / 25"
|
|
Write-Host " Security: $scoreSecurity / 25"
|
|
Write-Host " Performance: $scorePerformance / 25"
|
|
Write-Host " Maintainability: $scoreMaintainability / 25"
|
|
Write-Host ""
|
|
|
|
if ($issuesFound.Count -gt 0) {
|
|
Write-Host " Issues Found:"
|
|
foreach ($issue in $issuesFound) {
|
|
Write-Host " - $issue"
|
|
}
|
|
Write-Host ""
|
|
}
|
|
|
|
if ($aiPositive.Count -gt 0) {
|
|
Write-Host " Positive Notes:"
|
|
foreach ($note in $aiPositive) {
|
|
Write-Host " + $note"
|
|
}
|
|
Write-Host ""
|
|
}
|
|
|
|
if ($aiRecommendations.Count -gt 0) {
|
|
Write-Host " Recommendations:"
|
|
foreach ($rec in $aiRecommendations) {
|
|
Write-Host " > $rec"
|
|
}
|
|
Write-Host ""
|
|
}
|
|
|
|
# -- Step 5: Post review comment --
|
|
if ($aiAvailable) {
|
|
$reviewHeader = "## AI Code Quality Review - PR #$PrId"
|
|
} else {
|
|
$reviewHeader = "## Code Quality Review - PR #$PrId (keyword-based)"
|
|
}
|
|
|
|
$reviewBody = "$reviewHeader`n`n### Scores`n"
|
|
$reviewBody += "| Dimension | Score | Max |`n"
|
|
$reviewBody += "|-----------|-------|-----|`n"
|
|
$reviewBody += "| Code Quality | $scoreQuality | 25 |`n"
|
|
$reviewBody += "| Security | $scoreSecurity | 25 |`n"
|
|
$reviewBody += "| Performance | $scorePerformance | 25 |`n"
|
|
$reviewBody += "| Maintainability | $scoreMaintainability | 25 |`n"
|
|
$reviewBody += "| **Total** | **$totalScore** | **100** |`n`n"
|
|
$reviewBody += "### Issues Found`n"
|
|
|
|
if ($issuesFound.Count -gt 0) {
|
|
foreach ($issue in $issuesFound) {
|
|
$reviewBody += "- $issue`n"
|
|
}
|
|
} else {
|
|
$reviewBody += "No issues found.`n"
|
|
}
|
|
|
|
if ($aiPositive.Count -gt 0) {
|
|
$reviewBody += "`n### Positive Notes`n"
|
|
foreach ($note in $aiPositive) {
|
|
$reviewBody += "- $note`n"
|
|
}
|
|
}
|
|
|
|
if ($aiRecommendations.Count -gt 0) {
|
|
$reviewBody += "`n### Recommendations`n"
|
|
foreach ($rec in $aiRecommendations) {
|
|
$reviewBody += "- $rec`n"
|
|
}
|
|
}
|
|
|
|
$verdictText = if ($totalScore -ge $Threshold) {
|
|
"**PASS** - Score $totalScore >= threshold $Threshold. Ready to merge."
|
|
} else {
|
|
"**FAIL** - Score $totalScore < threshold $Threshold. Please address the issues above."
|
|
}
|
|
$reviewBody += "`n### Verdict`n${verdictText}`n`n---`n*Auto-reviewed by gitlink-cli code-quality-gatekeeper workflow (skill: gitlink-code-review)*"
|
|
|
|
Log-Step "Posting review comment..."
|
|
$reviewEvent = if ($totalScore -ge $Threshold) { "APPROVE" } else { "COMMENT" }
|
|
$reviewPayload = @{
|
|
body = $reviewBody
|
|
event = $reviewEvent
|
|
} | ConvertTo-Json -Compress
|
|
|
|
$reviewResult = Invoke-GL api,POST,"/$Owner/$Repo/pulls/$PrId/reviews",--body,$reviewPayload
|
|
if ($reviewResult) {
|
|
try {
|
|
$reviewOk = (($reviewResult | ConvertFrom-Json).ok -eq $true)
|
|
} catch { $reviewOk = $false }
|
|
if ($reviewOk) {
|
|
Log-Ok "Review posted"
|
|
} else {
|
|
Log-Warn "Review post may have failed (review API might not be available)"
|
|
}
|
|
} else {
|
|
Log-Warn "Review post may have failed"
|
|
}
|
|
|
|
# -- Step 6: Check CI --
|
|
Log-Step "Checking CI build status..."
|
|
$ciPresent = $false
|
|
$ciPassed = $true
|
|
$ciJson = Invoke-GL ci,+builds,--owner,$Owner,--repo,$Repo
|
|
if ($ciJson) {
|
|
try {
|
|
$ciData = $ciJson | ConvertFrom-Json
|
|
$builds = @()
|
|
if ($ciData.data.builds) { $builds = @($ciData.data.builds) }
|
|
elseif ($ciData.data -is [array]) { $builds = @($ciData.data) }
|
|
if ($builds.Count -gt 0) {
|
|
$ciPresent = $true
|
|
foreach ($b in $builds) {
|
|
$status = if ($b.status) { $b.status } elseif ($b.state) { $b.state } else { "unknown" }
|
|
$name = if ($b.name) { $b.name } else { "build" }
|
|
if ($status -notin @("success", "passed", "completed")) {
|
|
$ciPassed = $false
|
|
Log-Warn "CI '$name' status: $status"
|
|
} else {
|
|
Log-Ok "CI '$name' status: $status"
|
|
}
|
|
}
|
|
}
|
|
} catch { }
|
|
}
|
|
if (-not $ciPresent) { Log-Info "No CI builds found" }
|
|
|
|
# -- Step 7: Auto-merge --
|
|
if ($totalScore -ge $Threshold -and $ciPassed) {
|
|
Log-Step "Quality score $totalScore >= $Threshold and CI passed"
|
|
if ($DryRun) {
|
|
Log-Warn "[DRY RUN] Would auto-merge PR #$PrId"
|
|
} else {
|
|
Log-Step "Auto-merging PR #$PrId..."
|
|
$mergeResult = Invoke-GL pr,+merge,--owner,$Owner,--repo,$Repo,--id,$PrId,--method,merge
|
|
if ($mergeResult) {
|
|
try {
|
|
$mergeOk = (($mergeResult | ConvertFrom-Json).ok -eq $true)
|
|
} catch { $mergeOk = $false }
|
|
if ($mergeOk) {
|
|
Log-Ok "PR #$PrId merged successfully!"
|
|
} else {
|
|
Log-Err "Auto-merge failed"
|
|
}
|
|
} else {
|
|
Log-Err "Auto-merge failed"
|
|
}
|
|
}
|
|
} else {
|
|
Log-Warn "PR #$PrId not auto-merged (score: $totalScore, threshold: $Threshold, CI passed: $ciPassed)"
|
|
}
|
|
|
|
Write-Host ""
|
|
}
|
|
|
|
# ----------------------------------------------------------------
|
|
function Extract-JsonBlock {
|
|
param([string]$Text)
|
|
# Find JSON by balanced brace matching
|
|
$depth = 0
|
|
$start = -1
|
|
$results = @()
|
|
for ($i = 0; $i -lt $Text.Length; $i++) {
|
|
$c = $Text[$i]
|
|
if ($c -eq '{') {
|
|
if ($depth -eq 0) { $start = $i }
|
|
$depth++
|
|
} elseif ($c -eq '}') {
|
|
$depth--
|
|
if ($depth -eq 0 -and $start -ge 0) {
|
|
$results += $Text.Substring($start, $i - $start + 1)
|
|
$start = -1
|
|
}
|
|
}
|
|
}
|
|
# Return the last valid JSON object (usually the most complete)
|
|
for ($i = $results.Count - 1; $i -ge 0; $i--) {
|
|
try {
|
|
$obj = $results[$i] | ConvertFrom-Json
|
|
if ($obj.total -ne $null -and $obj.verdict) {
|
|
return $results[$i]
|
|
}
|
|
} catch { }
|
|
}
|
|
return $null
|
|
}
|
|
|
|
# ================================================================
|
|
# Main
|
|
# ================================================================
|
|
Log-Title "Code Quality Gatekeeper"
|
|
|
|
if ($PrId) {
|
|
Review-PR $PrId
|
|
} else {
|
|
Log-Step "Fetching open PRs..."
|
|
$prsResult = Invoke-GL pr,+list,--owner,$Owner,--repo,$Repo,--state,open,--limit,50
|
|
$prList = @()
|
|
if ($prsResult) {
|
|
try {
|
|
$prsJson = $prsResult | ConvertFrom-Json
|
|
$prData = $prsJson.data
|
|
if ($prData.issues) { $prList = @($prData.issues) }
|
|
elseif ($prData.pulls) { $prList = @($prData.pulls) }
|
|
elseif ($prData -is [array]) { $prList = $prData }
|
|
} catch { }
|
|
}
|
|
$prCount = $prList.Count
|
|
Log-Ok "Found $prCount open PRs"
|
|
|
|
if ($prCount -eq 0) {
|
|
Log-Info "No open PRs to review"
|
|
exit 0
|
|
}
|
|
|
|
$reviewed = 0
|
|
$passed = 0
|
|
$failed = 0
|
|
|
|
foreach ($pr in $prList) {
|
|
$prNum = if ($pr.pull_request_number) { $pr.pull_request_number }
|
|
elseif ($pr.number) { $pr.number }
|
|
elseif ($pr.id) { $pr.id }
|
|
else { $null }
|
|
if (-not $prNum) { continue }
|
|
Review-PR $prNum
|
|
$reviewed++
|
|
}
|
|
|
|
Log-Title "Gatekeeper Summary"
|
|
Write-Host " PRs Reviewed: $reviewed" -ForegroundColor Green
|
|
Write-Host " Threshold: $Threshold" -ForegroundColor Green
|
|
}
|