Files
my_wiki/tools/wiki-reflect.ps1
T

157 lines
8.3 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[CmdletBinding()]
param(
[string]$Root = (Join-Path $PSScriptRoot '..'),
[string]$Output = '',
[switch]$FailOnFindings
)
$ErrorActionPreference = 'Stop'
$Root = [IO.Path]::GetFullPath($Root)
$Wiki = Join-Path $Root 'wiki'
if ([string]::IsNullOrWhiteSpace($Output)) {
$Output = Join-Path $Wiki '_meta\wiki-review.md'
} else {
$Output = [IO.Path]::GetFullPath($Output)
}
$pages = @(Get-ChildItem -LiteralPath $Wiki -Recurse -File -Filter '*.md' |
Where-Object { $_.DirectoryName -ne (Join-Path $Wiki '_meta') })
$byBaseName = @{}
foreach ($page in $pages) { $byBaseName[$page.BaseName] = $page }
$lowConfidence = [Collections.Generic.List[object]]::new()
$contested = [Collections.Generic.List[object]]::new()
$longPages = [Collections.Generic.List[object]]::new()
$missingRole = [Collections.Generic.List[object]]::new()
$missingSources = [Collections.Generic.List[object]]::new()
$inbound = @{}
foreach ($page in $pages) { $inbound[$page.BaseName] = 0 }
foreach ($page in $pages) {
$text = [IO.File]::ReadAllText($page.FullName)
$lines = @($text -split "`r?`n").Count
$relative = $page.FullName.Substring($Root.Length + 1).Replace('\', '/')
$title = if ($text -match '(?m)^title:\s*["'']?([^"''\r\n]+)') { $Matches[1].Trim() } else { $page.BaseName }
$type = if ($text -match '(?m)^type:\s*["'']?([^"''\r\n]+)') { $Matches[1].Trim() } else { '' }
if ($text -match '(?m)^confidence:\s*(low)\s*$') {
$lowConfidence.Add([pscustomobject]@{ Path = $relative; Title = $title; Type = $type })
}
if ($text -match '(?m)^contested:\s*true\s*$') {
$contested.Add([pscustomobject]@{ Path = $relative; Title = $title; Type = $type })
}
if ($lines -gt 200) {
$longPages.Add([pscustomobject]@{ Path = $relative; Title = $title; Lines = $lines })
}
if ($page.Name -notin @('INDEX.md', 'Glossary.md', 'sources.md') -and
$text -notmatch '(?m)^## 页面定位|^## 页面定位与证据边界|^## 页面职责') {
$missingRole.Add([pscustomobject]@{ Path = $relative; Title = $title })
}
if ($page.Name -notin @('INDEX.md', 'Glossary.md', 'sources.md') -and
$text -notmatch '(?m)^\s*- path:\s*') {
$missingSources.Add([pscustomobject]@{ Path = $relative; Title = $title })
}
foreach ($match in [regex]::Matches($text, '\[\[([^\]|#]+)(?:#[^\]|]+)?(?:\|[^\]]+)?\]\]')) {
$target = $match.Groups[1].Value.Trim()
if ($inbound.ContainsKey($target)) { $inbound[$target]++ }
}
}
$orphans = @($pages | Where-Object {
$_.Name -notin @('INDEX.md', 'Glossary.md', 'sources.md') -and $inbound[$_.BaseName] -eq 0
} | ForEach-Object {
[pscustomobject]@{ Path = $_.FullName.Substring($Root.Length + 1).Replace('\', '/'); Title = $_.BaseName }
})
$glossary = Join-Path $Wiki 'Glossary.md'
$duplicateTerms = [Collections.Generic.List[string]]::new()
if (Test-Path -LiteralPath $glossary -PathType Leaf) {
$termCounts = @{}
foreach ($match in [regex]::Matches([IO.File]::ReadAllText($glossary), '(?m)^###\s+(.+)$')) {
$term = $match.Groups[1].Value.Trim()
if ($termCounts.ContainsKey($term)) { $termCounts[$term]++ } else { $termCounts[$term] = 1 }
}
foreach ($term in ($termCounts.Keys | Sort-Object)) {
if ($termCounts[$term] -gt 1) { $duplicateTerms.Add("$term$($termCounts[$term]) 次)") }
}
}
$indexDuplicates = [Collections.Generic.List[string]]::new()
$indexPath = Join-Path $Wiki 'INDEX.md'
if (Test-Path -LiteralPath $indexPath -PathType Leaf) {
$indexCounts = @{}
foreach ($match in [regex]::Matches([IO.File]::ReadAllText($indexPath), '\[\[([^\]|#]+)')) {
$target = $match.Groups[1].Value.Trim()
if ($indexCounts.ContainsKey($target)) { $indexCounts[$target]++ } else { $indexCounts[$target] = 1 }
}
foreach ($target in ($indexCounts.Keys | Sort-Object)) {
if ($indexCounts[$target] -gt 1) { $indexDuplicates.Add("$target$($indexCounts[$target]) 次)") }
}
}
$today = Get-Date -Format 'yyyy-MM-dd'
$sb = [Text.StringBuilder]::new()
[void]$sb.AppendLine('---')
[void]$sb.AppendLine('title: "Wiki 自我反思报告"')
[void]$sb.AppendLine("generated: $today")
[void]$sb.AppendLine('type: review')
[void]$sb.AppendLine('status: generated')
[void]$sb.AppendLine('---')
[void]$sb.AppendLine('')
[void]$sb.AppendLine('# Wiki 自我反思报告')
[void]$sb.AppendLine('')
[void]$sb.AppendLine('本报告由 `tools/wiki-reflect.ps1` 生成,只读扫描 Wiki 并覆盖写入本文件。它提出审阅任务,不自动修改知识正文。')
[void]$sb.AppendLine('')
[void]$sb.AppendLine("生成日期:$today;页面数:$($pages.Count)")
[void]$sb.AppendLine('')
[void]$sb.AppendLine('## 反思规则')
[void]$sb.AppendLine('')
[void]$sb.AppendLine('1. **保留价值**:页面是否有独立检索价值,是否保留了公式、单位、机制、失败模式和证据边界。')
[void]$sb.AppendLine('2. **职责清晰**:页面是否明确自己是总览、概念、实践、案例还是来源摘编,是否与相邻页面重复。')
[void]$sb.AppendLine('3. **证据可靠**:低置信度、争议、历史规则和来源方主张是否明确标注,是否仍有可访问 raw 来源。')
[void]$sb.AppendLine('4. **可发现性**:页面是否进入 INDEX,是否有入链,术语和导航是否重复。')
[void]$sb.AppendLine('5. **可维护性**:超长页面是否真的需要拆分,还是应保留为完整案例并增加专题入口。')
[void]$sb.AppendLine('')
function Add-Section([string]$Heading, [string[]]$Rows, [string]$Empty = '无') {
[void]$sb.AppendLine("## $Heading")
[void]$sb.AppendLine('')
if ($Rows.Count -eq 0) { [void]$sb.AppendLine($Empty) } else { foreach ($row in $Rows) { [void]$sb.AppendLine("- $row") } }
[void]$sb.AppendLine('')
}
$lowRows = @($lowConfidence | ForEach-Object { ('`{0}`:{1};建议复核主张、样本和失效条件' -f $_.Path, $_.Title) })
$contestedRows = @($contested | ForEach-Object { ('`{0}`:{1};建议确认来源冲突、版本日期和当前适用性' -f $_.Path, $_.Title) })
$longRows = @($longPages | Sort-Object Lines -Descending | ForEach-Object { ('`{0}`:{1} 行;先判断是否应按职责拆分,不以行数为唯一删除依据' -f $_.Path, $_.Lines) })
$roleRows = @($missingRole | ForEach-Object { ('`{0}`:建议补充页面负责/不负责内容及深入阅读入口' -f $_.Path) })
$sourceRows = @($missingSources | ForEach-Object { ('`{0}`:确认它是否为无需原始来源的导航、Q&A 或方法页;否则补充来源' -f $_.Path) })
$orphanRows = @($orphans | ForEach-Object { ('`{0}`:没有被其他 Wiki 页面链接;确认是否应由 INDEX 或主题入口挂载' -f $_.Path) })
Add-Section '低置信度页面' $lowRows
Add-Section '争议或历史边界页面' $contestedRows
Add-Section '超长页面' $longRows
Add-Section '缺少页面定位区块' $roleRows
Add-Section '缺少 raw_sources' $sourceRows
Add-Section '可能的 Wiki 孤岛' $orphanRows
Add-Section '术语重复' @($duplicateTerms)
Add-Section 'INDEX 重复入口' @($indexDuplicates)
[void]$sb.AppendLine('## 建议执行顺序')
[void]$sb.AppendLine('')
[void]$sb.AppendLine('1. 先处理 ERROR 和缺失来源,不要先改写低风险页面。')
[void]$sb.AppendLine('2. 对低置信度/争议页面逐页确认“保留、降级、补证据、归档”四选一。')
[void]$sb.AppendLine('3. 对职责缺失页面补充定位,而不是直接合并页面。')
[void]$sb.AppendLine('4. 只有重复定义和重复导航确认后才去重。')
[void]$sb.AppendLine('5. 运行 `pwsh -File .\\tools\\wiki-audit.ps1` 验证,再在 `wiki/compile.log` 记录实际变更。')
$parent = Split-Path -Parent $Output
New-Item -ItemType Directory -Path $parent -Force | Out-Null
[IO.File]::WriteAllText($Output, $sb.ToString(), [Text.UTF8Encoding]::new($false))
Write-Output "REPORT $Output"
Write-Output "FINDINGS low_confidence=$($lowConfidence.Count) contested=$($contested.Count) long_pages=$($longPages.Count) missing_role=$($missingRole.Count) missing_sources=$($missingSources.Count) orphans=$($orphans.Count) duplicate_terms=$($duplicateTerms.Count) duplicate_index=$($indexDuplicates.Count)"
$findingCount = $lowConfidence.Count + $contested.Count + $longPages.Count + $missingRole.Count + $missingSources.Count + $orphans.Count + $duplicateTerms.Count + $indexDuplicates.Count
if ($FailOnFindings -and $findingCount -gt 0) { exit 1 }
exit 0