129 lines
4.0 KiB
PowerShell
129 lines
4.0 KiB
PowerShell
# audit_flagger.ps1 — 判断 git 工作区是否存在高风险改动
|
||
# 兼容 Windows PowerShell 5.1(避免 try{} 内嵌套脚本块的解析器 bug)
|
||
|
||
$ErrorActionPreference = "SilentlyContinue"
|
||
|
||
function Get-TaipeiNow {
|
||
$tz = [TimeZoneInfo]::FindSystemTimeZoneById("Taipei Standard Time")
|
||
if ($tz) {
|
||
return [TimeZoneInfo]::ConvertTime([DateTimeOffset]::Now, $tz)
|
||
}
|
||
return [DateTimeOffset]::Now
|
||
}
|
||
|
||
function Sha1Hex([string]$s) {
|
||
$sha1 = [System.Security.Cryptography.SHA1]::Create()
|
||
$bytes = [System.Text.Encoding]::UTF8.GetBytes($s)
|
||
$hash = $sha1.ComputeHash($bytes)
|
||
return ([BitConverter]::ToString($hash) -replace "-", "").ToLowerInvariant()
|
||
}
|
||
|
||
function Get-ChangedFiles {
|
||
$status = git status --porcelain 2>$null
|
||
if ($LASTEXITCODE -ne 0) { return @() }
|
||
$result = @()
|
||
foreach ($line in $status) {
|
||
if ([string]::IsNullOrWhiteSpace($line)) { continue }
|
||
$pathPart = $line.Substring([Math]::Min(3, $line.Length - 1)).Trim()
|
||
if ($pathPart -match " -> ") { $pathPart = ($pathPart -split " -> ")[-1] }
|
||
if ([string]::IsNullOrWhiteSpace($pathPart)) { continue }
|
||
$result += $pathPart.Replace("\", "/").Trim()
|
||
}
|
||
return $result
|
||
}
|
||
|
||
function Test-NoiseFile([string]$f) {
|
||
if ($f -match "^docs/audit/") { return $true }
|
||
if ($f -match "^\.kiro/\.audit_state\.json$") { return $true }
|
||
if ($f -match "^\.kiro/\.last_prompt_id\.json$") { return $true }
|
||
if ($f -match "^\.kiro/scripts/") { return $true }
|
||
return $false
|
||
}
|
||
|
||
function Write-AuditState([string]$json) {
|
||
$null = New-Item -ItemType Directory -Force -Path ".kiro" 2>$null
|
||
Set-Content -Path ".kiro/.audit_state.json" -Value $json -Encoding UTF8
|
||
}
|
||
|
||
# --- 主逻辑 ---
|
||
|
||
# 非 git 仓库直接退出
|
||
$null = git rev-parse --is-inside-work-tree 2>$null
|
||
if ($LASTEXITCODE -ne 0) { exit 0 }
|
||
|
||
$allFiles = Get-ChangedFiles
|
||
# 过滤噪声
|
||
$files = @()
|
||
foreach ($f in $allFiles) {
|
||
if (-not (Test-NoiseFile $f)) { $files += $f }
|
||
}
|
||
$files = $files | Sort-Object -Unique
|
||
|
||
$now = Get-TaipeiNow
|
||
|
||
if ($files.Count -eq 0) {
|
||
$json = '{"audit_required":false,"db_docs_required":false,"reasons":[],"changed_files":[],"change_fingerprint":"","marked_at":"' + $now.ToString("o") + '","last_reminded_at":null}'
|
||
Write-AuditState $json
|
||
exit 0
|
||
}
|
||
|
||
# 高风险路径("regex|label" 格式,避免 @{} 哈希表在 PS 5.1 的解析问题)
|
||
$riskRules = @(
|
||
"^apps/etl/pipelines/feiqiu/(api|cli|config|database|loaders|models|orchestration|scd|tasks|utils|quality)/|etl",
|
||
"^apps/backend/app/|backend",
|
||
"^packages/shared/|shared",
|
||
"^db/|db"
|
||
)
|
||
|
||
$reasons = @()
|
||
$auditRequired = $false
|
||
$dbDocsRequired = $false
|
||
|
||
foreach ($f in $files) {
|
||
foreach ($rule in $riskRules) {
|
||
$idx = $rule.LastIndexOf("|")
|
||
$pat = $rule.Substring(0, $idx)
|
||
$lbl = $rule.Substring($idx + 1)
|
||
if ($f -match $pat) {
|
||
$auditRequired = $true
|
||
$tag = "dir:" + $lbl
|
||
if ($reasons -notcontains $tag) { $reasons += $tag }
|
||
}
|
||
}
|
||
if ($f -notmatch "/") {
|
||
$auditRequired = $true
|
||
if ($reasons -notcontains "root-file") { $reasons += "root-file" }
|
||
}
|
||
if ($f -match "^db/" -or $f -match "/migrations/" -or $f -match "\.sql$" -or $f -match "\.prisma$") {
|
||
$dbDocsRequired = $true
|
||
if ($reasons -notcontains "db-schema-change") { $reasons += "db-schema-change" }
|
||
}
|
||
}
|
||
|
||
$fp = Sha1Hex(($files -join "`n"))
|
||
|
||
# 读取已有状态以保留 last_reminded_at
|
||
$lastReminded = $null
|
||
if (Test-Path ".kiro/.audit_state.json") {
|
||
$raw = Get-Content ".kiro/.audit_state.json" -Raw 2>$null
|
||
if ($raw) {
|
||
$existing = $raw | ConvertFrom-Json 2>$null
|
||
if ($existing -and $existing.change_fingerprint -eq $fp) {
|
||
$lastReminded = $existing.last_reminded_at
|
||
}
|
||
}
|
||
}
|
||
|
||
$stateObj = [ordered]@{
|
||
audit_required = [bool]$auditRequired
|
||
db_docs_required = [bool]$dbDocsRequired
|
||
reasons = $reasons
|
||
changed_files = $files | Select-Object -First 50
|
||
change_fingerprint = $fp
|
||
marked_at = $now.ToString("o")
|
||
last_reminded_at = $lastReminded
|
||
}
|
||
|
||
Write-AuditState ($stateObj | ConvertTo-Json -Depth 6)
|
||
exit 0
|