[CmdletBinding()] param( [string]$Root = (Join-Path $PSScriptRoot '..'), [switch]$FailOnWarning, [string[]]$LongPageAllowlist = @( 'Glossary.md', 'INDEX.md', 'concepts/Harness-Engineering-Complete-Guide.md', 'concepts/Meta-Harness.md', 'practices/OpenAI-Codex-Harness-Engineering.md', 'queries/What-is-Harness-Engineering-in-Simple-Terms.md' ) ) $ErrorActionPreference = 'Stop' $Root = [IO.Path]::GetFullPath($Root) $Wiki = Join-Path $Root 'wiki' $Index = Join-Path $Wiki 'INDEX.md' $State = Join-Path $Wiki 'compile-results.tsv' $errors = [Collections.Generic.List[string]]::new() $warnings = [Collections.Generic.List[string]]::new() $infos = [Collections.Generic.List[string]]::new() $longPageAllowlistSet = @{} foreach ($relativePath in $LongPageAllowlist) { $normalizedPath = $relativePath.Replace('\', '/') $longPageAllowlistSet[$normalizedPath] = $true } function Add-Error([string]$Message) { $script:errors.Add("ERROR $Message") } function Add-Warning([string]$Message) { $script:warnings.Add("WARNING $Message") } function Add-Info([string]$Message) { $script:infos.Add("INFO $Message") } if (-not (Test-Path -LiteralPath $Wiki -PathType Container)) { Add-Error "wiki directory does not exist: $Wiki" } if (-not (Test-Path -LiteralPath $Index -PathType Leaf)) { Add-Error "wiki index does not exist: $Index" } $pages = @() if (Test-Path -LiteralPath $Wiki -PathType Container) { $pages = @(Get-ChildItem -LiteralPath $Wiki -Recurse -File -Filter '*.md' | Where-Object { $_.DirectoryName -ne (Join-Path $Wiki '_meta') }) } $byBaseName = @{} foreach ($page in $pages) { if ($byBaseName.ContainsKey($page.BaseName)) { Add-Error "duplicate Wiki basename: $($page.BaseName)" } else { $byBaseName[$page.BaseName] = $page.FullName } } foreach ($page in $pages) { $firstLine = Get-Content -LiteralPath $page.FullName -TotalCount 1 if ($firstLine -ne '---') { Add-Error "missing frontmatter: $($page.FullName)" } $lineCount = @(Get-Content -LiteralPath $page.FullName).Count if ($lineCount -gt 200) { $relativeWikiPath = [IO.Path]::GetRelativePath($Wiki, $page.FullName).Replace('\', '/') if (-not $longPageAllowlistSet.ContainsKey($relativeWikiPath)) { Add-Warning "long page ($lineCount lines): $($page.FullName)" } } $text = [IO.File]::ReadAllText($page.FullName) foreach ($match in [regex]::Matches($text, '\[\[([^\]|#]+)(?:#[^\]|]+)?(?:\|[^\]]+)?\]\]')) { $target = $match.Groups[1].Value.Trim() if (-not $byBaseName.ContainsKey($target)) { Add-Error "broken Wiki link in $($page.FullName): $target" } } foreach ($match in [regex]::Matches($text, '!\[[^\]]*\]\(([^)]+)\)')) { $reference = $match.Groups[1].Value.Trim() if ($reference -match '^https?://' -or $reference -match '^data:') { continue } $candidate = [IO.Path]::GetFullPath([IO.Path]::Combine($page.DirectoryName, $reference)) if (-not (Test-Path -LiteralPath $candidate -PathType Leaf)) { Add-Error "missing image in $($page.FullName): $reference" } } foreach ($match in [regex]::Matches($text, '(?m)^\s*- path:\s*["'']?([^"''\r\n]+)')) { $sourcePath = $match.Groups[1].Value.Trim() if ($sourcePath -match '^https?://') { continue } $candidate = Join-Path $Root $sourcePath if (-not (Test-Path -LiteralPath $candidate -PathType Leaf)) { Add-Warning "missing raw_sources path in $($page.FullName): $sourcePath" } } } if (Test-Path -LiteralPath $Index -PathType Leaf) { $indexText = [IO.File]::ReadAllText($Index) foreach ($page in ($pages | Where-Object { $_.Name -notin @('INDEX.md', 'Glossary.md', 'sources.md') })) { if ($indexText -notmatch [regex]::Escape($page.BaseName)) { Add-Error "page is not indexed by INDEX.md: $($page.FullName)" } } } if (Test-Path -LiteralPath $State -PathType Leaf) { try { $rows = @(Import-Csv -LiteralPath $State -Delimiter ([char]9)) $seenPaths = @{} foreach ($row in $rows) { if ([string]::IsNullOrWhiteSpace($row.raw_path) -or [string]::IsNullOrWhiteSpace($row.hash)) { Add-Error "compile-results.tsv contains an incomplete row" continue } if ($seenPaths.ContainsKey($row.raw_path)) { Add-Error "duplicate compile-results raw_path: $($row.raw_path)" } $seenPaths[$row.raw_path] = $true if ($row.hash -match 'initial|pending|to-be-computed|placeholder') { Add-Error "placeholder hash in compile-results.tsv: $($row.raw_path)" } $candidate = Join-Path $Root $row.raw_path if (-not (Test-Path -LiteralPath $candidate -PathType Leaf)) { Add-Warning "compile row points to missing raw file: $($row.raw_path)" } } Add-Info "compile rows: $($rows.Count)" } catch { Add-Error "compile-results.tsv cannot be parsed: $($_.Exception.Message)" } } else { Add-Error "compile-results.tsv does not exist: $State" } $allTextFiles = @(Get-ChildItem -LiteralPath $Root -Recurse -File -Include '*.md', '*.tsv', '*.log' -ErrorAction SilentlyContinue) $deletedSourceRefs = @() foreach ($file in $allTextFiles) { $text = [IO.File]::ReadAllText($file.FullName) if ($text -match 'MiniMax-M27-Self-Evolution|raw/MiniMax M2\.7|MiniMax M2\.7 开启模型的自我进化') { if ($text -notmatch '用户已删除|同步移除|delete \|') { $deletedSourceRefs += $file.FullName } } } foreach ($file in ($deletedSourceRefs | Sort-Object -Unique)) { Add-Warning "possible stale MiniMax source/page reference: $file" } $linkCount = 0 foreach ($page in $pages) { $linkCount += ([regex]::Matches([IO.File]::ReadAllText($page.FullName), '\[\[')).Count } Add-Info "Wiki Markdown pages: $($pages.Count)" Add-Info "Wiki links scanned: $linkCount" Add-Info "long-page allowlist entries: $($longPageAllowlistSet.Count)" Add-Info "errors: $($errors.Count)" Add-Info "warnings: $($warnings.Count)" $errors $warnings $infos if ($errors.Count -gt 0 -or ($FailOnWarning -and $warnings.Count -gt 0)) { exit 1 } exit 0