<# .SYNOPSIS NOVA UI 规范护栏:禁止新增裸 HEX 颜色值。 .DESCRIPTION 对应 .kiro/steering/ui-design.md 第 6 节「编码约束 · 禁止裸 HEX」。 颜色必须统一走 Themes/NovaColors.xaml 中的令牌(NovaXxxBrush / NovaXxxColor),不允许在页面或代码里直接写十六进制色值。 仓库里历史遗留的裸 HEX 还有大量未迁移(P3 阶段尚在推进,详见 ui-design.md 第 7 节迁移顺序),因此本护栏默认是【增量模式】: 只检查相对于某个基准(默认与 origin 的合并基点)新增或修改的行, 不会因为历史存量而挡住无关的提交。要看全量现状用 -Mode Full。 范围限定为 XplorePlane 主项目(原生 WPF 面 + 代码侧),与 ui-design.md 第 4 节记录的范围边界一致: - 排除 Themes/Nova*.xaml 本身(它是唯一允许出现裸 HEX 的地方) - 排除 obj/bin 生成目录 - 不扫描跨模块项目(XP.Common、XP.Hardware.* 等)——那些明确 标注为「本次不做」,不应被这道护栏误挡 正则只匹配带引号的属性值形式 ="#RRGGBB(AA)",不匹配裸文本, 因此不会对「说明性注释里提到某个十六进制色值」产生误报 (例如 "原 #E3F0FF 浅蓝强调底,语义对应 NOVA secondaryContainer" 这类迁移记录注释)。 .PARAMETER Mode Diff(默认):只检查增量(git diff 新增行)。 Full:扫描全部现存文件,用于生成现状报告,不建议作为提交门禁。 .PARAMETER Base Diff 模式下的比较基准。默认自动探测 origin/主分支的合并基点; 在本地无网络/无远程的场景可显式传入一个 commit/分支名。 .PARAMETER Path 要扫描的项目根目录。默认是仓库中的 XplorePlane 主项目目录。 .EXAMPLE pwsh tools/Check-NovaColorTokens.ps1 提交前跑:只检查本次改动是否新增了裸 HEX。 .EXAMPLE pwsh tools/Check-NovaColorTokens.ps1 -Mode Full 生成全量现状报告(预期会有大量历史命中,仅供参考)。 .NOTES 退出码:0 = 未发现违规;1 = 发现违规。 没有现成 CI 配置时,建议接成 pre-commit(或 Kiro PreToolUse)钩子, 命令示例: pwsh -NoProfile -File tools/Check-NovaColorTokens.ps1 一旦引入正式 CI 流水线,直接调用本脚本(Diff 模式)作为一个步骤即可。 #> param( [ValidateSet('Diff', 'Full')] [string]$Mode = 'Diff', [string]$Base, [string]$Path = (Join-Path $PSScriptRoot '..\XplorePlane') ) $ErrorActionPreference = 'Stop' $RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path $Path = (Resolve-Path $Path).Path # 带引号的属性值形式:Background="#FFFFFF"、Value="#1A2B3C"、 # "#2E7D32"(C# 字符串字面量)等。不匹配裸文本,避免命中注释里的说明文字。 $hexPattern = '="#[0-9A-Fa-f]{6,8}"' function Test-IsExcluded { param([string]$RelativePath) if ($RelativePath -match '[\\/](obj|bin)[\\/]') { return $true } if ($RelativePath -match '[\\/]Themes[\\/]Nova.*\.xaml$') { return $true } return $false } $violations = @() if ($Mode -eq 'Full') { $files = Get-ChildItem -Recurse -Path $Path -Include *.xaml, *.cs -File | Where-Object { -not (Test-IsExcluded $_.FullName.Substring($RepoRoot.Length + 1)) } foreach ($file in $files) { $matches = Select-String -Path $file.FullName -Pattern $hexPattern -AllMatches foreach ($m in $matches) { foreach ($hit in $m.Matches) { $violations += [pscustomobject]@{ File = $file.FullName.Substring($Path.Length + 1) Line = $m.LineNumber Snippet = $m.Line.Trim() Hex = $hit.Value } } } } } else { # Diff 模式:只看 git diff 里新增的行(+ 开头,排除 +++ 文件头), # 且限定在 XplorePlane 目录下的 .xaml/.cs 文件。 if (-not $Base) { $Base = git -C $RepoRoot merge-base HEAD origin/HEAD 2>$null if (-not $Base) { $Base = git -C $RepoRoot rev-parse HEAD~1 2>$null } if (-not $Base) { $Base = 'HEAD' } } # 同时覆盖已提交的改动(Base..HEAD)与尚未提交的工作区改动, # 这样在提交前本地跑一次就能拦住,不必依赖已经 commit。 $diffArgs = @('-C', $RepoRoot, 'diff', '--unified=0', "$Base", '--', 'XplorePlane/*.xaml', 'XplorePlane/*.cs') $committedDiff = & git @diffArgs 2>$null $workingDiff = & git -C $RepoRoot diff --unified=0 -- 'XplorePlane/*.xaml' 'XplorePlane/*.cs' 2>$null $stagedDiff = & git -C $RepoRoot diff --unified=0 --cached -- 'XplorePlane/*.xaml' 'XplorePlane/*.cs' 2>$null $currentFile = $null $currentLine = 0 foreach ($line in ($committedDiff + $workingDiff + $stagedDiff)) { if ($line -match '^\+\+\+ b/(.+)$') { $currentFile = $matches[1] continue } if ($line -match '^@@ -\d+(?:,\d+)? \+(\d+)') { $currentLine = [int]$matches[1] continue } if ($line -match '^\+(?!\+\+)(.*)$') { $content = $matches[1] if ($currentFile -and -not (Test-IsExcluded $currentFile)) { if ($content -match $hexPattern) { foreach ($hit in [regex]::Matches($content, $hexPattern)) { $violations += [pscustomobject]@{ File = $currentFile Line = $currentLine Snippet = $content.Trim() Hex = $hit.Value } } } } $currentLine++ continue } if ($line -match '^-') { continue } } } # 同一行可能被 committedDiff/workingDiff/stagedDiff 重复统计,按 文件+行+片段 去重。 $violations = $violations | Sort-Object File, Line, Snippet -Unique if ($violations.Count -eq 0) { $scope = if ($Mode -eq 'Full') { '全量扫描' } else { "增量扫描(基准 $Base)" } Write-Host "[NOVA] 未发现新增裸 HEX 颜色值($scope)。" -ForegroundColor Green exit 0 } Write-Host "[NOVA] 发现 $($violations.Count) 处裸 HEX 颜色值,须改为引用 Themes/NovaColors.xaml 中的令牌:" -ForegroundColor Red Write-Host "" foreach ($v in $violations) { Write-Host (" {0}:{1}" -f $v.File, $v.Line) -ForegroundColor Yellow Write-Host (" {0}" -f $v.Snippet) } Write-Host "" Write-Host "参见 .kiro/steering/ui-design.md 第 6 节「禁止裸 HEX」。" -ForegroundColor DarkYellow exit 1