Public/Show-GraphOverview.ps1
|
# Copyright (c) 2026 Jeffrey Snover. All rights reserved. # Licensed under the MIT License. See LICENSE file in the project root. function Show-GraphOverview { <# .SYNOPSIS Displays a structural overview of the taxonomy graph. .DESCRIPTION Computes and displays graph statistics: node/edge counts by POV, edge type distribution, connected components, density, and orphan/hub nodes. Works directly from JSON files (no Neo4j required). .PARAMETER StatusFilter Only count edges with this approval status. Default: approved. Use 'all' to include all edges. .PARAMETER RepoRoot Path to the repository root. .EXAMPLE Show-GraphOverview .EXAMPLE Show-GraphOverview -StatusFilter all #> [CmdletBinding()] param( [ValidateSet('proposed', 'approved', 'rejected', 'all')] [string]$StatusFilter = 'approved', [string]$RepoRoot = $script:RepoRoot ) Set-StrictMode -Version Latest $TaxDir = Get-TaxonomyDir # ── Load nodes ── $AllNodes = @{} $NodePovMap = @{} $PovCounts = [ordered]@{} foreach ($PovKey in @('accelerationist', 'safetyist', 'skeptic', 'cross-cutting')) { $FilePath = Join-Path $TaxDir "$PovKey.json" if (-not (Test-Path $FilePath)) { continue } try { $FileData = Get-Content -Raw -Path $FilePath | ConvertFrom-Json } catch { Write-Warn "Failed to load $PovKey.json — $($_.Exception.Message)" continue } $PovCounts[$PovKey] = $FileData.nodes.Count foreach ($Node in $FileData.nodes) { $AllNodes[$Node.id] = $Node $NodePovMap[$Node.id] = $PovKey } } # ── Load edges ── $EdgesPath = Join-Path $TaxDir 'edges.json' $Edges = @() if (Test-Path $EdgesPath) { try { $EdgesData = Get-Content -Raw -Path $EdgesPath | ConvertFrom-Json } catch { Write-Warn "Failed to load edges.json — $($_.Exception.Message)" $EdgesData = [PSCustomObject]@{ edges = @() } } if ($StatusFilter -eq 'all') { $Edges = @($EdgesData.edges) } else { $Edges = @($EdgesData.edges | Where-Object { $_.status -eq $StatusFilter }) } } # ── Compute statistics ── $NodeCount = $AllNodes.Count $EdgeCount = $Edges.Count $MaxPossibleEdges = $NodeCount * ($NodeCount - 1) $Density = if ($MaxPossibleEdges -gt 0) { [Math]::Round($EdgeCount / $MaxPossibleEdges, 4) } else { 0 } # Edge type distribution $TypeCounts = [ordered]@{} foreach ($Edge in $Edges) { $T = $Edge.type if (-not $TypeCounts.Contains($T)) { $TypeCounts[$T] = 0 } $TypeCounts[$T]++ } # Status distribution (always from all edges) $StatusCounts = [ordered]@{} if (Test-Path $EdgesPath) { foreach ($Edge in $EdgesData.edges) { $S = $Edge.status if (-not $StatusCounts.Contains($S)) { $StatusCounts[$S] = 0 } $StatusCounts[$S]++ } } # Cross-POV edges $CrossPovCount = 0 $SamePovCount = 0 foreach ($Edge in $Edges) { $SourcePov = $NodePovMap[$Edge.source] $TargetPov = $NodePovMap[$Edge.target] if ($SourcePov -and $TargetPov) { if ($SourcePov -eq $TargetPov) { $SamePovCount++ } else { $CrossPovCount++ } } } # Degree distribution $InDegree = @{} $OutDegree = @{} foreach ($Edge in $Edges) { if (-not $OutDegree.Contains($Edge.source)) { $OutDegree[$Edge.source] = 0 } if (-not $InDegree.Contains($Edge.target)) { $InDegree[$Edge.target] = 0 } $OutDegree[$Edge.source]++ $InDegree[$Edge.target]++ } # Orphans (no edges at all) $Orphans = @(foreach ($NId in $AllNodes.Keys) { $In = if ($InDegree.Contains($NId)) { $InDegree[$NId] } else { 0 } $Out = if ($OutDegree.Contains($NId)) { $OutDegree[$NId] } else { 0 } if ($In -eq 0 -and $Out -eq 0) { $NId } }) # Hubs (top 5 by total degree) $TotalDegree = @{} foreach ($NId in $AllNodes.Keys) { $In = if ($InDegree.Contains($NId)) { $InDegree[$NId] } else { 0 } $Out = if ($OutDegree.Contains($NId)) { $OutDegree[$NId] } else { 0 } $TotalDegree[$NId] = $In + $Out } $Hubs = $TotalDegree.GetEnumerator() | Sort-Object Value -Descending | Select-Object -First 5 # Confidence distribution $ConfBuckets = [ordered]@{ '0.5-0.6' = 0; '0.6-0.7' = 0; '0.7-0.8' = 0; '0.8-0.9' = 0; '0.9-1.0' = 0 } foreach ($Edge in $Edges) { $C = [double]$Edge.confidence if ($C -lt 0.6) { $ConfBuckets['0.5-0.6']++ } elseif ($C -lt 0.7) { $ConfBuckets['0.6-0.7']++ } elseif ($C -lt 0.8) { $ConfBuckets['0.7-0.8']++ } elseif ($C -lt 0.9) { $ConfBuckets['0.8-0.9']++ } else { $ConfBuckets['0.9-1.0']++ } } # ── Display ── Write-Host '' Write-Host '══════════════════════════════════════════════════════════════' -ForegroundColor Cyan Write-Host ' Graph Overview' -ForegroundColor White Write-Host '══════════════════════════════════════════════════════════════' -ForegroundColor Cyan Write-Host '' Write-Host ' Nodes:' -ForegroundColor Cyan foreach ($PovKey in $PovCounts.Keys) { $PovColor = switch ($PovKey) { 'accelerationist' { 'Blue' } 'safetyist' { 'Green' } 'skeptic' { 'Yellow' } 'cross-cutting' { 'Magenta' } } Write-Host " $($PovKey.PadRight(18)) $($PovCounts[$PovKey])" -ForegroundColor $PovColor } Write-Host " $('Total'.PadRight(18)) $NodeCount" -ForegroundColor White Write-Host '' Write-Host " Edges ($StatusFilter):" -ForegroundColor Cyan Write-Host " Total: $EdgeCount" -ForegroundColor White Write-Host " Cross-POV: $CrossPovCount" -ForegroundColor White Write-Host " Same-POV: $SamePovCount" -ForegroundColor White Write-Host " Density: $Density" -ForegroundColor White Write-Host '' Write-Host ' Edge Types:' -ForegroundColor Cyan foreach ($T in $TypeCounts.Keys) { Write-Host " $($T.PadRight(18)) $($TypeCounts[$T])" -ForegroundColor White } Write-Host '' Write-Host ' Status (all edges):' -ForegroundColor Cyan foreach ($S in $StatusCounts.Keys) { $SColor = switch ($S) { 'approved' { 'Green' } 'rejected' { 'Red' } default { 'Yellow' } } Write-Host " $($S.PadRight(18)) $($StatusCounts[$S])" -ForegroundColor $SColor } Write-Host '' Write-Host ' Confidence Distribution:' -ForegroundColor Cyan foreach ($Bucket in $ConfBuckets.Keys) { $Bar = '*' * [Math]::Min($ConfBuckets[$Bucket], 50) Write-Host " $($Bucket.PadRight(10)) $($ConfBuckets[$Bucket].ToString().PadLeft(4)) $Bar" -ForegroundColor White } Write-Host '' Write-Host ' Hub Nodes (top 5 by degree):' -ForegroundColor Cyan foreach ($Hub in $Hubs) { $NId = $Hub.Key $Label = $AllNodes[$NId].label $Pov = $NodePovMap[$NId] Write-Host " $($NId.PadRight(20)) degree=$($Hub.Value) [$Pov] $Label" -ForegroundColor White } Write-Host '' if ($Orphans.Count -gt 0) { Write-Host " Orphan Nodes ($($Orphans.Count) with no edges):" -ForegroundColor Yellow foreach ($OId in @($Orphans) | Select-Object -First 10) { Write-Host " $OId — $($AllNodes[$OId].label)" -ForegroundColor DarkGray } if ($Orphans.Count -gt 10) { Write-Host " ... and $($Orphans.Count - 10) more" -ForegroundColor DarkGray } } else { Write-Host ' No orphan nodes — all nodes have at least one edge.' -ForegroundColor Green } Write-Host '' Write-Host '══════════════════════════════════════════════════════════════' -ForegroundColor Cyan Write-Host '' } |