WikiLLM 快照:2026-08-02 以来编译工作固化(期货/股票/宏观/估值 raw 资料、wiki 65 页、审计与反思工具)+ 2026-08-07 反思整理(Glossary 术语去重、8 月焦煤回顾与登记、16 页定位区块、清理空文件)
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
[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
|
||||
@@ -0,0 +1,156 @@
|
||||
[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
|
||||
Reference in New Issue
Block a user