141 lines
5.1 KiB
PowerShell
141 lines
5.1 KiB
PowerShell
# PowerShell Lint Script for LLM Knowledge Base
|
|
# This script scans the knowledge/ directory for broken WikiLinks, orphan pages, and unlinked concepts/entities.
|
|
|
|
$knowledgeDir = Resolve-Path "knowledge"
|
|
$files = Get-ChildItem -Path $knowledgeDir -Recurse -Filter *.md
|
|
|
|
# Maps to keep track of files and links
|
|
$existingFiles = @{} # file name/path -> absolute path
|
|
$fileIncomingLinks = @{} # relative path -> list of incoming files
|
|
|
|
# Exclude list for orphan check (master files)
|
|
$excludeOrphans = @(
|
|
"knowledge\index.md",
|
|
"knowledge\log.md",
|
|
"knowledge\feynman_problems.md"
|
|
)
|
|
|
|
# Initialize files
|
|
foreach ($file in $files) {
|
|
# Normalize relative path (e.g. "knowledge/concepts/accidental-dba.md")
|
|
$relPath = (Resolve-Path $file.FullName -Relative) -replace "^\.\\", "" -replace "\\", "/"
|
|
$existingFiles[$relPath] = $file.FullName
|
|
|
|
# Also index by base name (without extension) for simple links, e.g. [[log]] -> "knowledge/log.md"
|
|
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($file.Name)
|
|
$existingFiles[$baseName] = $file.FullName
|
|
$existingFiles[$relPath -replace "\.md$", ""] = $file.FullName
|
|
|
|
# Initialize incoming links list
|
|
$fileIncomingLinks[$relPath] = [System.Collections.Generic.List[string]]::new()
|
|
}
|
|
|
|
$brokenLinks = [System.Collections.Generic.List[PSCustomObject]]::new()
|
|
|
|
# Scan each file for links
|
|
foreach ($file in $files) {
|
|
$relPath = (Resolve-Path $file.FullName -Relative) -replace "^\.\\", "" -replace "\\", "/"
|
|
$content = Get-Content -Path $file.FullName -Raw
|
|
|
|
# Match [[WikiLinks]] (can include aliases, e.g. [[link|alias]] or [[link]])
|
|
# Regex matches anything inside [[ and ]] except newlines or brackets
|
|
$matches = [regex]::Matches($content, "\[\[([^\]|]+)(?:\|[^\]]*)?\]\]")
|
|
|
|
foreach ($m in $matches) {
|
|
$linkTarget = $m.Groups[1].Value.Trim()
|
|
|
|
# Normalize target path
|
|
# Try resolving directly, with .md, or prefixing with "knowledge/"
|
|
$resolvedPath = $null
|
|
|
|
$candidates = @(
|
|
$linkTarget,
|
|
"$linkTarget.md",
|
|
"knowledge/$linkTarget",
|
|
"knowledge/$linkTarget.md"
|
|
)
|
|
|
|
foreach ($c in $candidates) {
|
|
$normalizedC = $c -replace "\\", "/"
|
|
if ($existingFiles.ContainsKey($normalizedC)) {
|
|
$resolvedPath = $existingFiles[$normalizedC]
|
|
break
|
|
}
|
|
}
|
|
|
|
if ($resolvedPath) {
|
|
# Link is valid! Log incoming link
|
|
$resolvedRelPath = (Resolve-Path $resolvedPath -Relative) -replace "^\.\\", "" -replace "\\", "/"
|
|
if ($resolvedRelPath -ne $relPath) { # ignore self-links
|
|
$fileIncomingLinks[$resolvedRelPath].Add($relPath)
|
|
}
|
|
} else {
|
|
# Link is broken!
|
|
$brokenLinks.Add([PSCustomObject]@{
|
|
SourceFile = $relPath
|
|
LinkTarget = $linkTarget
|
|
MatchText = $m.Value
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
# Find Orphans
|
|
$orphans = [System.Collections.Generic.List[string]]::new()
|
|
foreach ($key in $fileIncomingLinks.Keys) {
|
|
# Check if file is in exclude orphans list
|
|
$isExcluded = $false
|
|
foreach ($ex in $excludeOrphans) {
|
|
$normalizedEx = $ex -replace "\\", "/"
|
|
if ($key -eq $normalizedEx -or $key -like "knowledge/indices/*") {
|
|
$isExcluded = $true
|
|
break
|
|
}
|
|
}
|
|
|
|
# An orphan is a file that is not master index/indices and has 0 incoming links (except from index files themselves)
|
|
if (-not $isExcluded) {
|
|
$incoming = $fileIncomingLinks[$key]
|
|
|
|
# Filter out incoming links that are indices or master index
|
|
$realIncomingCount = 0
|
|
foreach ($inc in $incoming) {
|
|
if ($inc -ne "knowledge/index.md" -and $inc -notlike "knowledge/indices/*") {
|
|
$realIncomingCount++
|
|
}
|
|
}
|
|
|
|
if ($realIncomingCount -eq 0) {
|
|
$orphans.Add($key)
|
|
}
|
|
}
|
|
}
|
|
|
|
# Print Beautiful Report
|
|
Write-Host "==========================================" -ForegroundColor Green
|
|
Write-Host " KNOWLEDGE BASE LINT REPORT" -ForegroundColor Green
|
|
Write-Host "==========================================" -ForegroundColor Green
|
|
Write-Host "Total Files Scanned: $($files.Count)" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
if ($brokenLinks.Count -eq 0) {
|
|
Write-Host "[OK] No broken WikiLinks found!" -ForegroundColor Green
|
|
} else {
|
|
Write-Host "[WARNING] Found $($brokenLinks.Count) broken WikiLink(s):" -ForegroundColor Yellow
|
|
foreach ($bl in $brokenLinks) {
|
|
Write-Host " - In '$($bl.SourceFile)': Link '$($bl.LinkTarget)' is broken (Match: '$($bl.MatchText)')" -ForegroundColor Red
|
|
}
|
|
}
|
|
Write-Host ""
|
|
|
|
if ($orphans.Count -eq 0) {
|
|
Write-Host "[OK] No orphan pages found!" -ForegroundColor Green
|
|
} else {
|
|
Write-Host "[INFO] Found $($orphans.Count) orphan page(s) (no incoming links from other content pages):" -ForegroundColor Yellow
|
|
foreach ($o in $orphans) {
|
|
Write-Host " - $o" -ForegroundColor DarkYellow
|
|
}
|
|
}
|
|
Write-Host ""
|
|
Write-Host "Lint completed successfully." -ForegroundColor Green
|