# 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