param( [string]$Stamp = (Get-Date).ToString("yyyy-MM-dd") ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" function Get-NormalizedRepoId { param( [string]$RepoUrl, [string]$FallbackName ) if ($RepoUrl) { $trimmed = $RepoUrl.Trim() if ($trimmed -match '^(?:https?|ssh)://[^/]+/(?.+?)(?:\.git)?/?$') { return $Matches["id"] } if ($trimmed -match '^[^:]+:(?.+?)(?:\.git)?/?$') { return $Matches["id"] } } if ($FallbackName) { return ($FallbackName -replace '\s*-\s*', '/' -replace '\s+', '-') } return "unknown" } function Get-GitRoot { param([string]$Path) try { $root = (git -C $Path rev-parse --show-toplevel 2>$null) if ($LASTEXITCODE -eq 0 -and $root) { return $root.Trim() } } catch { } return $null } function Get-RemoteOriginUrl { param([string]$Path) try { $remote = (git -C $Path config --get remote.origin.url 2>$null) if ($LASTEXITCODE -eq 0 -and $remote) { return $remote.Trim() } } catch { } return "" } function Test-StandaloneGitRoot { param( [string]$Path, [string]$GitRoot ) if (-not $GitRoot) { return $false } return ([System.IO.Path]::GetFullPath($GitRoot).TrimEnd('\') -eq [System.IO.Path]::GetFullPath($Path).TrimEnd('\')) } function Get-RootFilesByPattern { param( [string]$Path, [string]$Pattern ) return @(Get-ChildItem -LiteralPath $Path -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -match $Pattern }) } function Get-LicenseSignalFromText { param( [string]$FileName, [string]$Text ) $name = $FileName.ToUpperInvariant() if ($name -match 'MIT' -or (($Text -match 'Permission is hereby granted, free of charge, to any person obtaining a copy') -and ($Text -match 'THE SOFTWARE IS PROVIDED "AS IS"'))) { return "MIT" } if ($name -match 'APACHE' -or $Text -match 'Apache License' -or $Text -match 'Apache-2\.0') { return "Apache-2.0" } if ($name -match 'MPL' -or $Text -match 'Mozilla Public License' -or $Text -match 'MPL-2\.0') { return "MPL-2.0" } $trimmedText = $Text.TrimStart() if ($name -match 'AGPL' -or $trimmedText -match '^[#\s]*GNU AFFERO GENERAL PUBLIC LICENSE') { return "AGPL-3.0" } if ($name -match 'GPL' -or $trimmedText -match '^[#\s]*GNU GENERAL PUBLIC LICENSE') { return "GPL-3.0-or-later" } if ($name -match 'BSD' -or $Text -match 'Redistribution and use in source and binary forms') { return "BSD-family" } if ($name -match 'ISC' -or $Text -match 'ISC License') { return "ISC" } if ($name -match 'POLYFORM' -or $Text -match 'PolyForm') { return "PolyForm-family" } if ($name -match 'BUSINESS' -or $Text -match 'Business Source License' -or $Text -match '\bBSL\b') { return "BSL" } if ($Text -match 'Custom broad-use license') { return "custom-broad-use" } return "other_root_license_file" } function Resolve-RootLicenseSignal { param([array]$LicenseFileRows) $signals = @($LicenseFileRows | Select-Object -ExpandProperty Signal -Unique) if (@($signals).Count -eq 0) { return "" } if (($signals -contains "MPL-2.0") -and @($signals | Where-Object { $_ -like "GPL*" -or $_ -like "AGPL*" }).Count -gt 0) { return "MPL-2.0 OR GPL-3.0-or-later" } if (($signals -contains "MIT") -and ($signals -contains "Apache-2.0")) { return "MIT OR Apache-2.0" } if (($signals -contains "MIT") -and @($signals | Where-Object { $_ -like "GPL*" -or $_ -like "AGPL*" }).Count -gt 0) { return "MIT with GPL-family companion file" } return ($signals -join " + ") } function Get-MetadataSignal { param( [string]$Path, [string]$ManifestLicense ) $candidates = @("package.json", "Cargo.toml", "pyproject.toml", "setup.cfg", "setup.py", "composer.json") foreach ($candidate in $candidates) { $fullPath = Join-Path $Path $candidate if (-not (Test-Path -LiteralPath $fullPath)) { continue } $text = Get-Content -LiteralPath $fullPath -Raw -Encoding UTF8 -ErrorAction SilentlyContinue if (-not $text) { continue } if ($candidate -eq "package.json") { try { $packageJson = $text | ConvertFrom-Json -ErrorAction Stop $packageLicense = [string]$packageJson.license if ($packageLicense) { $packageLicense = $packageLicense.Trim() if ($packageLicense -and $packageLicense -notmatch '^SEE LICENSE IN\b' -and ((-not $ManifestLicense) -or ($ManifestLicense -match [regex]::Escape($packageLicense)) -or ($packageLicense -match [regex]::Escape($ManifestLicense)))) { return @{ Signal = $packageLicense Path = $candidate } } } } catch { } } if ($ManifestLicense -match 'MIT' -and $text -match '\bMIT\b') { return @{ Signal = "MIT" Path = $candidate } } } return $null } function Get-CopyrightLine { param([string]$Text) foreach ($line in ($Text -split "`r?`n")) { if ($line -match 'copyright') { return $line.Trim() } } return "" } function Get-MITTextSource { param([array]$LicenseFileRows) foreach ($row in $LicenseFileRows) { if ($row.Signal -eq "MIT") { return $row } } foreach ($row in $LicenseFileRows) { if ($row.Text -match 'MIT License') { return $row } } return $null } function Get-SafeFileStem { param([string]$RepoId) $stem = $RepoId -replace '[\\/:\*\?"<>\| ]', '__' return $stem.Trim('_') } function Write-Utf8NoBomTextFile { param( [string]$Path, [string]$Content ) $encoding = [System.Text.UTF8Encoding]::new($false) [System.IO.File]::WriteAllText($Path, $Content, $encoding) } $repoRoot = Split-Path -Parent $PSScriptRoot $docsRoot = Join-Path $repoRoot "docs" $generatedRoot = Join-Path $docsRoot "generated\\license_audit" $manifestPath = "C:\\Workspaces\\HyperTwist\\repos.manifest.json" $legacyMirrorsRoot = Join-Path $repoRoot "mirrors" $csvPath = Join-Path $generatedRoot "HYPERTWIST_REPO_LICENSE_EVIDENCE_AUDIT_$Stamp.csv" $markdownPath = Join-Path $docsRoot "HYPERTWIST_REPO_LICENSE_EVIDENCE_AUDIT_$Stamp.md" $mitTextDir = Join-Path $generatedRoot "MIT_LICENSE_TEXTS_$Stamp" $thirdPartyPath = Join-Path $generatedRoot "THIRD_PARTY_NOTICES_MIT_DRAFT_$Stamp.txt" $canonicalMitText = @" MIT License [README-only or metadata-only MIT signal captured by HyperTwist legal audit. No standalone root MIT LICENSE file was present in the repo-local mirror at the recorded commit. Preserve the evidence path, source URL, and commit hash from the paired audit ledger and third-party notices draft.] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. "@ New-Item -ItemType Directory -Path $generatedRoot -Force | Out-Null New-Item -ItemType Directory -Path $mitTextDir -Force | Out-Null Get-ChildItem -LiteralPath $mitTextDir -File -ErrorAction SilentlyContinue | Remove-Item -Force $manifest = Get-Content $manifestPath -Raw -Encoding UTF8 | ConvertFrom-Json $entries = New-Object System.Collections.Generic.List[object] foreach ($repo in $manifest.repositories) { if ($repo.localPath -like 'C:\Workspaces\HyperTwist\mirrors\*') { $entries.Add([pscustomobject]@{ RepoId = Get-NormalizedRepoId -RepoUrl $repo.repoUrl -FallbackName $repo.name RepoUrl = $repo.repoUrl PrimaryPath = $repo.localPath AllLocalPaths = @($repo.localPath) CurrentManifestMirrorClass = $repo.mirrorClass CurrentManifestLicense = $repo.license CurrentManifestLicenseStatus = $repo.licenseStatus Source = "manifest" }) } } if (Test-Path -LiteralPath $legacyMirrorsRoot) { $legacyDirs = @(Get-ChildItem -LiteralPath $legacyMirrorsRoot -Directory -ErrorAction SilentlyContinue) foreach ($legacyDir in $legacyDirs) { $gitRoot = Get-GitRoot -Path $legacyDir.FullName if (-not (Test-StandaloneGitRoot -Path $legacyDir.FullName -GitRoot $gitRoot)) { continue } $repoUrl = Get-RemoteOriginUrl -Path $legacyDir.FullName $repoId = Get-NormalizedRepoId -RepoUrl $repoUrl -FallbackName $legacyDir.Name if (@($entries | Where-Object { $_.PrimaryPath -eq $legacyDir.FullName -or $_.RepoId -eq $repoId }).Count -gt 0) { continue } $entries.Add([pscustomobject]@{ RepoId = $repoId RepoUrl = $repoUrl PrimaryPath = $legacyDir.FullName AllLocalPaths = @($legacyDir.FullName) CurrentManifestMirrorClass = "legacy" CurrentManifestLicense = "" CurrentManifestLicenseStatus = "" Source = "legacy" }) } } $auditRows = New-Object System.Collections.Generic.List[object] $excludedRows = New-Object System.Collections.Generic.List[object] foreach ($entry in $entries) { $path = $entry.PrimaryPath $exists = Test-Path -LiteralPath $path if (-not $exists) { $missingReason = if ($entry.CurrentManifestLicenseStatus -eq "historical-alias-unavailable") { "historical_alias_not_expected_to_be_mirrored" } elseif ($entry.CurrentManifestLicenseStatus -eq "upstream-unavailable") { "upstream_unavailable_or_not_publicly_resolvable" } else { "missing_local_path" } $excludedRows.Add([pscustomobject]@{ RepoId = $entry.RepoId RepoUrl = $entry.RepoUrl Path = $path GitRoot = "" Reason = $missingReason }) continue } $gitRoot = Get-GitRoot -Path $path if (-not (Test-StandaloneGitRoot -Path $path -GitRoot $gitRoot)) { $reason = "not_a_git_root" if ($gitRoot) { $rootRepoId = Get-NormalizedRepoId -RepoUrl (Get-RemoteOriginUrl -Path $gitRoot) -FallbackName (Split-Path $gitRoot -Leaf) if ($rootRepoId -eq $entry.RepoId) { $reason = "package_subpath_under_parent_root_authority" } else { $reason = "non_standalone_git_subpath" } } $excludedRows.Add([pscustomobject]@{ RepoId = $entry.RepoId RepoUrl = $entry.RepoUrl Path = $path GitRoot = $gitRoot Reason = $reason }) continue } $licenseFiles = Get-RootFilesByPattern -Path $path -Pattern '^(LICENSE|LICEN[CS]E|COPYING)([-_.].+)?(\..+)?$' $noticeFiles = Get-RootFilesByPattern -Path $path -Pattern '^NOTICE(\..+)?$' $readmeFiles = Get-RootFilesByPattern -Path $path -Pattern '^README([-.].+)?(\..+)?$' $licenseFileRows = foreach ($licenseFile in $licenseFiles) { $text = Get-Content -LiteralPath $licenseFile.FullName -Raw -Encoding UTF8 -ErrorAction SilentlyContinue [pscustomobject]@{ Name = $licenseFile.Name Signal = Get-LicenseSignalFromText -FileName $licenseFile.Name -Text $text Text = $text } } $resolvedLicenseSignal = Resolve-RootLicenseSignal -LicenseFileRows $licenseFileRows $evidenceTier = "" $mitBucket = "" $evidencePaths = "" $readmeSignal = "" $readmeSignalPath = "" $metadataSignal = "" $metadataSignalPath = "" $note = "" $copyrightLine = "" if ($resolvedLicenseSignal) { $evidenceTier = "clear_repo_license_file" $evidencePaths = ($licenseFiles.Name -join "; ") if ($resolvedLicenseSignal -match 'MIT') { $mitBucket = "mit_clear_root_license_file" $mitSource = Get-MITTextSource -LicenseFileRows $licenseFileRows if ($mitSource) { $copyrightLine = Get-CopyrightLine -Text $mitSource.Text } } else { foreach ($licenseFileRow in $licenseFileRows) { $copyrightLine = Get-CopyrightLine -Text $licenseFileRow.Text if ($copyrightLine) { break } } } } else { foreach ($readmeFile in $readmeFiles) { $readmeText = Get-Content -LiteralPath $readmeFile.FullName -Raw -Encoding UTF8 -ErrorAction SilentlyContinue if ($entry.CurrentManifestLicense -match 'MIT' -and $readmeText -match '\bMIT\b') { $resolvedLicenseSignal = "MIT" $evidenceTier = "readme_only" $mitBucket = "mit_readme_only" $evidencePaths = $readmeFile.Name $readmeSignal = "MIT" $readmeSignalPath = $readmeFile.Name break } } if (-not $resolvedLicenseSignal) { $metadataResult = Get-MetadataSignal -Path $path -ManifestLicense $entry.CurrentManifestLicense if ($metadataResult) { $resolvedLicenseSignal = $metadataResult.Signal $evidenceTier = "metadata_only" if ($resolvedLicenseSignal -eq "MIT") { $mitBucket = "mit_metadata_only" } $evidencePaths = $metadataResult.Path $metadataSignal = $metadataResult.Signal $metadataSignalPath = $metadataResult.Path } } } if (-not $resolvedLicenseSignal) { $resolvedLicenseSignal = "no_clear_repo_local_license_signal" } $repoHead = (git -C $path rev-parse HEAD).Trim() if ($entry.CurrentManifestLicense -and $resolvedLicenseSignal -ne "no_clear_repo_local_license_signal") { if ($entry.CurrentManifestLicense -notmatch [regex]::Escape($resolvedLicenseSignal)) { $note = "Manifest/tracker posture is narrower or more contextual than the root license signal." } } elseif ($entry.CurrentManifestLicense -and $resolvedLicenseSignal -eq "no_clear_repo_local_license_signal") { $note = "Manifest/tracker carries the current legal call, but no standalone repo-local license signal was captured at the mirror root." } $licenseFileNames = @($licenseFiles | ForEach-Object { $_.Name }) $noticeFileNames = @($noticeFiles | ForEach-Object { $_.Name }) $readmeFileNames = @($readmeFiles | ForEach-Object { $_.Name }) $auditRows.Add([pscustomobject]@{ repo_id = $entry.RepoId repo_url = $entry.RepoUrl primary_path = $path all_local_paths = ($entry.AllLocalPaths -join "; ") current_manifest_mirror_class = $entry.CurrentManifestMirrorClass current_manifest_license = $entry.CurrentManifestLicense current_manifest_license_status = $entry.CurrentManifestLicenseStatus license_file_count = @($licenseFiles).Count license_files = ($licenseFileNames -join "; ") notice_files = ($noticeFileNames -join "; ") readme_files = ($readmeFileNames -join "; ") resolved_license_signal = $resolvedLicenseSignal evidence_tier = $evidenceTier mit_bucket = $mitBucket evidence_paths = $evidencePaths readme_signal = $readmeSignal readme_signal_path = $readmeSignalPath metadata_signal = $metadataSignal metadata_signal_path = $metadataSignalPath copyright_line = $copyrightLine repo_head = $repoHead note = $note }) } $orderedAuditRows = @($auditRows | Sort-Object repo_id) $orderedAuditRows | Export-Csv -LiteralPath $csvPath -NoTypeInformation -Encoding UTF8 $mitNoticeBlocks = New-Object System.Collections.Generic.List[string] foreach ($row in $orderedAuditRows | Where-Object { $_.mit_bucket }) { $mitText = "" if ($row.mit_bucket -eq "mit_clear_root_license_file") { $licenseCandidates = ($row.license_files -split '; ' | Where-Object { $_ }) foreach ($candidate in $licenseCandidates) { $fullPath = Join-Path $row.primary_path $candidate $candidateText = Get-Content -LiteralPath $fullPath -Raw -Encoding UTF8 -ErrorAction SilentlyContinue if ((Get-LicenseSignalFromText -FileName $candidate -Text $candidateText) -eq "MIT") { $mitText = $candidateText break } } } if (-not $mitText) { $mitText = $canonicalMitText } $mitFilePath = Join-Path $mitTextDir ("{0}.LICENSE.txt" -f (Get-SafeFileStem -RepoId $row.repo_id)) Write-Utf8NoBomTextFile -Path $mitFilePath -Content ($mitText.TrimEnd() + "`r`n`r`n") $block = @( "Component: $($row.repo_id)", "Source: $($row.repo_url)", "Version: $($row.repo_head)", "Evidence tier: $($row.evidence_tier)", "Evidence path(s): $($row.evidence_paths)" ) if ($row.readme_signal_path) { $block += "README evidence: $($row.readme_signal_path)" } if ($row.metadata_signal_path) { $block += "Metadata evidence: $($row.metadata_signal_path)" } $block += "" if ($row.copyright_line) { $block += $row.copyright_line $block += "" } $block += "Licensed under the MIT License:" $block += "" $block += $mitText.TrimEnd() $block += "" $block += "-----" $mitNoticeBlocks.Add(($block -join "`r`n")) } Write-Utf8NoBomTextFile -Path $thirdPartyPath -Content (($mitNoticeBlocks -join "`r`n`r`n").TrimEnd() + "`r`n`r`n") $scannedCount = @($orderedAuditRows).Count $clearRootCount = @($orderedAuditRows | Where-Object { $_.evidence_tier -eq "clear_repo_license_file" }).Count $mitClearCount = @($orderedAuditRows | Where-Object { $_.mit_bucket -eq "mit_clear_root_license_file" }).Count $mitReadmeCount = @($orderedAuditRows | Where-Object { $_.mit_bucket -eq "mit_readme_only" }).Count $mitMetadataCount = @($orderedAuditRows | Where-Object { $_.mit_bucket -eq "mit_metadata_only" }).Count $nonMitMetadataCount = @($orderedAuditRows | Where-Object { $_.evidence_tier -eq "metadata_only" -and -not $_.mit_bucket }).Count $noClearCount = @($orderedAuditRows | Where-Object { $_.resolved_license_signal -eq "no_clear_repo_local_license_signal" }).Count $noClearWithCanonCount = @($orderedAuditRows | Where-Object { $_.resolved_license_signal -eq "no_clear_repo_local_license_signal" -and $_.current_manifest_license }).Count $normalizedSubpathCount = @($excludedRows | Where-Object { $_.Reason -eq "package_subpath_under_parent_root_authority" }).Count $historicalAliasCount = @($excludedRows | Where-Object { $_.Reason -eq "historical_alias_not_expected_to_be_mirrored" }).Count $remainingExcludedCount = @($excludedRows | Where-Object { $_.Reason -ne "package_subpath_under_parent_root_authority" -and $_.Reason -ne "historical_alias_not_expected_to_be_mirrored" }).Count $excludedCount = $excludedRows.Count $markdown = New-Object System.Collections.Generic.List[string] $markdown.Add("# HyperTwist Repo License Evidence Audit - $Stamp") $markdown.Add("") $markdown.Add('Scope: deduped local mirror custody across `C:\Workspaces\HyperTwist\mirrors\{permissive,restrictive}` plus standalone legacy mirrors under `C:\HyperTwist\mirrors`.') $markdown.Add("") $markdown.Add("Boundary:") $markdown.Add("- this is the repo-local legal-evidence pass for mirrored HyperTwist custody, not a rewrite of the broader historical workbook universe") $markdown.Add("- only standalone git roots count in the live audit") $markdown.Add("- package-subpath rows that live inside a mirrored monorepo and resolve to the same repo URL are normalized under the parent git-root authority instead of being treated as standalone repo roots") $markdown.Add("- historical alias rows whose standalone repo surfaces are gone but whose maintained public source surfaces now live elsewhere are kept visible as historical references, not as unresolved mirror-restoration work") $markdown.Add("- upstream-unavailable rows, unresolved missing mirror paths, and genuinely unrelated non-standalone paths stay visible outside the standalone count instead of being silently discarded") $markdown.Add('- when later repos are mirrored or their root-license evidence changes, rerun `C:\HyperTwist\scripts\Write-HyperTwistRepoLicenseEvidenceAudit.ps1` in the same pass that updates the legal tracker') $markdown.Add("") $markdown.Add("Counts:") $markdown.Add("- total deduped repos scanned: $scannedCount") $markdown.Add("- clear repo-local license file or explicit mixed root-license declaration: $clearRootCount") $markdown.Add("- MIT with clear root license file: $mitClearCount") $markdown.Add("- MIT from README only, no root license file: $mitReadmeCount") $markdown.Add("- MIT from repo metadata only, no root license file: $mitMetadataCount") $markdown.Add("- non-MIT license from repo metadata only, no root license file: $nonMitMetadataCount") $markdown.Add("- no clear repo-local license signal: $noClearCount") $markdown.Add("- no-clear bucket where current HyperTwist manifest already carries a legal call: $noClearWithCanonCount") $markdown.Add("- package-subpath rows normalized under parent root authority: $normalizedSubpathCount") $markdown.Add("- historical alias rows outside active mirror expectation: $historicalAliasCount") $markdown.Add("- unresolved upstream-unavailable, missing, or unrelated non-standalone rows outside the live standalone count: $remainingExcludedCount") $markdown.Add("- total rows outside the standalone live count: $excludedCount") $markdown.Add("") $markdown.Add("Artifacts:") $markdown.Add('- audit CSV: `docs/generated/license_audit/HYPERTWIST_REPO_LICENSE_EVIDENCE_AUDIT_' + $Stamp + '.csv`') $markdown.Add('- MIT license text capture folder: `docs/generated/license_audit/MIT_LICENSE_TEXTS_' + $Stamp + '/`') $markdown.Add('- MIT third-party notices draft: `docs/generated/license_audit/THIRD_PARTY_NOTICES_MIT_DRAFT_' + $Stamp + '.txt`') $markdown.Add("") $readmeOnlyRows = @($orderedAuditRows | Where-Object { $_.mit_bucket -eq "mit_readme_only" }) $markdown.Add("MIT without root license file - README only:") if ($readmeOnlyRows.Count -eq 0) { $markdown.Add("- none") } else { foreach ($row in $readmeOnlyRows) { $markdown.Add("- $($row.repo_id)") } } $markdown.Add("") $metadataOnlyRows = @($orderedAuditRows | Where-Object { $_.mit_bucket -eq "mit_metadata_only" }) $markdown.Add("MIT without root license file - metadata only:") if ($metadataOnlyRows.Count -eq 0) { $markdown.Add("- none") } else { foreach ($row in $metadataOnlyRows) { $markdown.Add("- $($row.repo_id)") } } $markdown.Add("") $nonMitMetadataRows = @($orderedAuditRows | Where-Object { $_.evidence_tier -eq "metadata_only" -and -not $_.mit_bucket }) $markdown.Add("Non-MIT license from repo metadata only, no root license file:") if ($nonMitMetadataRows.Count -eq 0) { $markdown.Add("- none") } else { foreach ($row in $nonMitMetadataRows) { $detail = if ($row.metadata_signal_path) { "$($row.resolved_license_signal) via ``$($row.metadata_signal_path)``" } else { $row.resolved_license_signal } $markdown.Add("- $($row.repo_id) - signal: $detail") } } $markdown.Add("") $noClearRows = @($orderedAuditRows | Where-Object { $_.resolved_license_signal -eq "no_clear_repo_local_license_signal" }) $markdown.Add("No clear repo-local license signal:") if ($noClearRows.Count -eq 0) { $markdown.Add("- none") } else { foreach ($row in $noClearRows) { if ($row.current_manifest_license) { $markdown.Add("- $($row.repo_id) - current manifest call: ``$($row.current_manifest_license)``") } else { $markdown.Add("- $($row.repo_id)") } } } $markdown.Add("") $normalizedRows = @($excludedRows | Where-Object { $_.Reason -eq "package_subpath_under_parent_root_authority" } | Sort-Object RepoId, Path) $markdown.Add("Package-subpath rows normalized under parent root authority:") if ($normalizedRows.Count -eq 0) { $markdown.Add("- none") } else { foreach ($row in $normalizedRows) { $markdown.Add("- $($row.Path) -> $($row.GitRoot) (``$($row.Reason)``)") } } $markdown.Add("") $historicalAliasRows = @($excludedRows | Where-Object { $_.Reason -eq "historical_alias_not_expected_to_be_mirrored" } | Sort-Object RepoId, Path) $markdown.Add("Historical alias rows not expected to be mirrored:") if ($historicalAliasRows.Count -eq 0) { $markdown.Add("- none") } else { foreach ($row in $historicalAliasRows) { $markdown.Add("- $($row.Path) (``$($row.Reason)``)") } } $markdown.Add("") $remainingExcludedRows = @($excludedRows | Where-Object { $_.Reason -ne "package_subpath_under_parent_root_authority" -and $_.Reason -ne "historical_alias_not_expected_to_be_mirrored" } | Sort-Object RepoId, Path) $markdown.Add("Excluded from the live standalone-git-root count:") if ($remainingExcludedRows.Count -eq 0) { $markdown.Add("- none") } else { foreach ($row in $remainingExcludedRows) { if ($row.GitRoot) { $markdown.Add("- $($row.Path) -> $($row.GitRoot) (``$($row.Reason)``)") } else { $markdown.Add("- $($row.Path) (``$($row.Reason)``)") } } } $markdown.Add("") Write-Utf8NoBomTextFile -Path $markdownPath -Content (($markdown -join "`r`n").TrimEnd() + "`r`n`r`n") Write-Output "Wrote:" Write-Output $csvPath Write-Output $markdownPath Write-Output $thirdPartyPath Write-Output $mitTextDir