Files
my_wiki/tools/wiki-audit.ps1
T

171 lines
6.3 KiB
PowerShell

[CmdletBinding()]
param(
[string]$Root = (Join-Path $PSScriptRoot '..'),
[switch]$FailOnWarning,
[string[]]$LongPageAllowlist = @(
'Glossary.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