# hooks/archive-check.ps1 # Check changed .md files and suggest related docs to update. # This script only outputs suggestions; it does NOT modify any files. # # Mechanism: # For every changed .md file, parse its "## 文档关联" section and extract # Markdown links. Those links are the documents that may need syncing. $ErrorActionPreference = "SilentlyContinue" $scriptPath = $MyInvocation.MyCommand.Path if (-not [System.IO.Path]::IsPathRooted($scriptPath)) { $scriptPath = Join-Path (Get-Location) $scriptPath } $scriptPath = [System.IO.Path]::GetFullPath($scriptPath) $scriptDir = Split-Path -Parent $scriptPath $projectRoot = [System.IO.Path]::GetFullPath((Join-Path $scriptDir "..")) Set-Location $projectRoot # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- function Resolve-RelativePath { param( [string]$BaseFile, [string]$LinkPath ) # Skip anchors, query strings, and external URLs if ($LinkPath -match '^[a-zA-Z][a-zA-Z0-9+.-]*://') { return $null } if ([string]::IsNullOrWhiteSpace($LinkPath)) { return $null } # Drop fragment $pathOnly = ($LinkPath -split '#')[0] if ([string]::IsNullOrWhiteSpace($pathOnly)) { return $null } $baseDir = Split-Path -Parent $BaseFile $combined = Join-Path $baseDir $pathOnly try { $resolved = [System.IO.Path]::GetFullPath($combined) } catch { return $null } # Convert to relative path from project root, normalizing separators $fullProjectRoot = [System.IO.Path]::GetFullPath($projectRoot) if ($resolved.StartsWith($fullProjectRoot, [System.StringComparison]::OrdinalIgnoreCase)) { $relative = $resolved.Substring($fullProjectRoot.Length).TrimStart('\', '/') return $relative -replace '\\', '/' } return $pathOnly -replace '\\', '/' } function Get-DocumentAssociations { param( [string]$MdFile ) $fullPath = Join-Path $projectRoot $MdFile if (-not (Test-Path $fullPath)) { return @() } $lines = [System.IO.File]::ReadAllLines($fullPath, [System.Text.Encoding]::UTF8) $associations = @() $inSection = $false $inCodeBlock = $false foreach ($line in $lines) { # Toggle fenced code block state (``` or ~~~) if ($line -match '^\s*```' -or $line -match '^\s*~~~') { $inCodeBlock = -not $inCodeBlock continue } if ($inCodeBlock) { continue } # Match exactly '## 文档关联' (not '## 文档关联机制' etc.). if ($line -match "^##\s+文档关联\s*$") { $inSection = $true continue } if ($inSection -and $line -match '^##\s+') { break } if ($inSection) { # Extract markdown links: [text](path) $regex = '\[(?[^\]]*)\]\((?[^\)]+)\)' $matches = [regex]::Matches($line, $regex) foreach ($match in $matches) { $linkPath = $match.Groups['path'].Value.Trim() $resolved = Resolve-RelativePath -BaseFile $MdFile -LinkPath $linkPath if ($resolved) { $associations += $resolved } } } } return $associations | Select-Object -Unique } function Test-WildcardMatch { param( [string]$Pattern, [string]$Path ) $regex = "^" + ($Pattern -replace "\.", "\." -replace "\*", ".*" -replace "\?", ".") + "$" return $Path -match $regex } # --------------------------------------------------------------------------- # Collect changed .md files (tracked modifications + untracked new files) # --------------------------------------------------------------------------- $changedFiles = @(cmd /c "git diff --name-only 2>nul") | Where-Object { $_ } if ($changedFiles.Count -eq 0) { $changedFiles = @(cmd /c "git diff --name-only --cached 2>nul") | Where-Object { $_ } } # Also include untracked .md files so new documents are checked too $untrackedFiles = @(cmd /c "git ls-files --others --exclude-standard 2>nul") | Where-Object { $_ } $allChangedFiles = @($changedFiles) + @($untrackedFiles) | Where-Object { $_ } | Select-Object -Unique if (-not $allChangedFiles) { Write-Output "[archive-check] No documentation changes detected (git diff is empty and no untracked .md files)." exit 0 } $changedMdFiles = $allChangedFiles | Where-Object { $_ -like "*.md" } | Select-Object -Unique if ($changedMdFiles.Count -eq 0) { Write-Output "[archive-check] No .md documentation changes detected." exit 0 } # --------------------------------------------------------------------------- # Build suggestions from each changed file's "## 文档关联" section # --------------------------------------------------------------------------- $suggestions = @{} foreach ($changed in $changedMdFiles) { $relatedDocs = Get-DocumentAssociations -MdFile $changed foreach ($related in $relatedDocs) { # Normalize related path $relatedNormalized = $related -replace '\\', '/' if (-not $suggestions.ContainsKey($relatedNormalized)) { $suggestions[$relatedNormalized] = @() } if ($suggestions[$relatedNormalized] -notcontains $changed) { $suggestions[$relatedNormalized] += $changed } } } # --------------------------------------------------------------------------- # Output results # --------------------------------------------------------------------------- Write-Output "[archive-check] Changed documentation files:" foreach ($changed in $changedMdFiles | Sort-Object) { Write-Output " - $changed" } if ($suggestions.Count -eq 0) { Write-Output "" Write-Output "[archive-check] No associations found in changed files' `"## 文档关联`" sections." exit 0 } Write-Output "" Write-Output "[archive-check] Related docs that may need updating:" foreach ($related in $suggestions.Keys | Sort-Object) { $sources = $suggestions[$related] | Sort-Object $sourceList = $sources -join ", " Write-Output " - $related (mentioned by: $sourceList)" } Write-Output "" Write-Output "[archive-check] Please confirm whether to sync-update the above docs." exit 0