Files

151 lines
5.7 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<#
.SYNOPSIS
Verify that a code-review report carries a line-coverage gate (fail-closed).
.DESCRIPTION
Enforces the "three-piece suite" guarantee defined in project-review/SKILL.md
Step 8.7: whole-project / feature review reports MUST contain a line coverage
(xingfugai) field in their coverage section, and its value MUST be >= the
configured target (default 95%). A missing field means the review never ran
coverage_tool(gate="both+line") (or forgot the three-piece data) and the
report would otherwise look green with no line coverage.
Scope handling: only whole-project and feature reviews are gated; other
scopes (e.g. change-level) are skipped.
Exit codes: 0 = line coverage present and >= target (or scope not gated);
1 = blocking violation (missing line coverage or below target).
.PARAMETER Repo
Repository root. Defaults to the current directory.
.PARAMETER Report
Optional relative path (under docs/reviews/) to a specific report file.
Defaults to the newest *.md report in docs/reviews/.
.PARAMETER Target
Minimum line-coverage percentage. Default 95.
.EXAMPLE
powershell -File verify-line-coverage.ps1 -Repo D:\AuraSpace
powershell -File verify-line-coverage.ps1 -Repo D:\AuraSpace -Report full-project-review-2026-08-17-101500.md
#>
param(
[string]$Repo = "",
[string]$Report = "",
[double]$Target = 95.0
)
$ErrorActionPreference = "Stop"
function Write-Step([string]$msg) { Write-Host " $msg" -ForegroundColor DarkGray }
function Write-Ok([string]$msg) { Write-Host " [OK] $msg" -ForegroundColor Green }
function Write-Bad([string]$msg) { Write-Host " [!!] $msg" -ForegroundColor Red }
function Write-Warn([string]$msg) { Write-Host " [!] $msg" -ForegroundColor Yellow }
# Resolve repo root ---------------------------------------------------------
if ([string]::IsNullOrWhiteSpace($Repo)) { $Repo = (Get-Location).Path }
$root = (Resolve-Path -LiteralPath $Repo).Path
$reviewsDir = Join-Path $root "docs\reviews"
Write-Host "verify-line-coverage: repo = $root (target $Target%)"
Write-Host ""
# Locate the report --------------------------------------------------------
$reportPath = ""
if (-not [string]::IsNullOrWhiteSpace($Report)) {
$reportPath = Join-Path $reviewsDir $Report
if (-not (Test-Path -LiteralPath $reportPath)) {
Write-Bad "report not found: $reportPath"
exit 1
}
} else {
if (-not (Test-Path -LiteralPath $reviewsDir)) {
Write-Warn "docs/reviews/ does not exist - no report to verify"
Write-Host "RESULT: PASS (no report)" -ForegroundColor Green
exit 0
}
$reportPath = Get-ChildItem -LiteralPath $reviewsDir -File |
Where-Object { $_.Extension -eq ".md" } |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1 -ExpandProperty FullName
if (-not $reportPath) {
Write-Warn "no .md report in docs/reviews/ - nothing to verify"
Write-Host "RESULT: PASS (no report)" -ForegroundColor Green
exit 0
}
}
Write-Step "checking report: $reportPath"
# Parse scope from header (line like: 档位:standard · 范围:whole-project · 基线:...)
$content = [System.IO.File]::ReadAllText($reportPath, [System.Text.Encoding]::UTF8)
$scope = ""
$scopeLine = ($content -split "`n" | Where-Object { $_ -match "范围" } | Select-Object -First 1)
if ($scopeLine -and $scopeLine -match "范围\**[:]\s*([^\s·*]+)") {
$scope = $Matches[1].Trim()
}
# Scope gate: only change-level reviews are skipped. whole-project / feature
# AND any custom scope value (e.g. scope="evm") that still produced a coverage
# section must be checked for line coverage.
if ($scope -eq "change-level") {
Write-Warn "scope = '$scope' is not gated on line coverage (change-level only)"
Write-Host "RESULT: PASS (scope not gated)" -ForegroundColor Green
exit 0
}
Write-Step "scope = '$scope' (gated)"
# Parse ## 覆盖度 section
$covSection = ""
$lines = $content -split "`r?`n"
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($lines[$i] -match "^##\s*\**覆盖度\**") {
$j = $i + 1
$buf = @()
while ($j -lt $lines.Count -and -not ($lines[$j] -match "^##\s")) {
$buf += $lines[$j]; $j++
}
$covSection = $buf -join "`n"
break
}
}
if (-not $covSection) {
Write-Bad "no coverage section found - line coverage cannot be verified"
exit 1
}
# Line-coverage field present?
$lineLine = ($covSection -split "`n" | Where-Object { $_ -match "行覆盖" } | Select-Object -First 1)
if (-not $lineLine) {
Write-Bad "coverage section has NO line-coverage field - gate='both+line' was not run (or three-piece data missing)"
Write-Step "coverage section:"
foreach ($l in ($covSection -split "`n" | Where-Object { $_.Trim() })) { Write-Step " $($l.Trim())" }
exit 1
}
# "未执行" marker means gate never ran
if ($lineLine -match "未执行") {
Write-Bad "line coverage = NOT RUN (gate='both+line' not run or three-piece data missing)"
Write-Step $lineLine.Trim()
exit 1
}
# Extract percentage (行覆盖:99.8% — 目标 95.0%(缺口 0 文件)✅)
if ($lineLine -match "行覆盖\**[:]\s*([0-9.]+)\s*%") {
$pct = [double]$Matches[1]
if ($pct -lt $Target) {
Write-Bad "line coverage $pct% < target $Target%"
Write-Step $lineLine.Trim()
exit 1
}
Write-Ok "line coverage = $pct% (>= $Target%)"
if ($lineLine -match "缺口\s*([0-9]+)\s*文件" -and [int]$Matches[1] -gt 0) {
Write-Warn "line-coverage gap files present: $($Matches[1])"
}
Write-Host "RESULT: PASS - line coverage verified" -ForegroundColor Green
exit 0
}
Write-Bad "unable to parse line-coverage percentage from: $($lineLine.Trim())"
exit 1