$ErrorActionPreference = "Stop" function Get-TaipeiNow { try { $tz = [TimeZoneInfo]::FindSystemTimeZoneById("Taipei Standard Time") return [TimeZoneInfo]::ConvertTime([DateTimeOffset]::Now, $tz) } catch { return [DateTimeOffset]::Now } } try { $statePath = ".kiro/.audit_state.json" if (-not (Test-Path $statePath)) { exit 0 } $state = $null try { $state = Get-Content $statePath -Raw | ConvertFrom-Json } catch { exit 0 } if (-not $state) { exit 0 } # If no pending audit, do nothing if (-not $state.audit_required) { exit 0 } # If working tree is clean (ignoring logs/state), stop reminding $null = git rev-parse --is-inside-work-tree 2>$null if ($LASTEXITCODE -ne 0) { exit 0 } $status = git status --porcelain $files = @() 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] } $p = $pathPart.Replace("\", "/").Trim() if ($p -and $p -notmatch "^docs/audit/" -and $p -notmatch "^\.kiro/") { $files += $p } } if (($files | Sort-Object -Unique).Count -eq 0) { $state.audit_required = $false $state.reasons = @() $state.changed_files = @() $state.last_reminded_at = $null ($state | ConvertTo-Json -Depth 6) | Set-Content -Path $statePath -Encoding UTF8 exit 0 } $now = Get-TaipeiNow $minInterval = [TimeSpan]::FromMinutes(15) $last = $null if ($state.last_reminded_at) { try { $last = [DateTimeOffset]::Parse($state.last_reminded_at) } catch { $last = $null } } $shouldRemind = $true if ($last) { $elapsed = $now - $last if ($elapsed -lt $minInterval) { $shouldRemind = $false } } if (-not $shouldRemind) { exit 0 } # Update last_reminded_at (persist even if user ignores) $state.last_reminded_at = $now.ToString("o") ($state | ConvertTo-Json -Depth 6) | Set-Content -Path $statePath -Encoding UTF8 $reasons = @() if ($state.reasons) { $reasons = @($state.reasons) } $reasonText = if ($reasons.Count -gt 0) { ($reasons -join ", ") } else { "high-risk paths changed" } [Console]::Error.WriteLine("[AUDIT REMINDER] Pending audit detected ($reasonText). Run /audit (Manual: Run /audit hook) to sync docs & write audit artifacts. (rate limit: 15min)") exit 1 } catch { exit 0 }