From c89cae01301e45cdcdf60e37fa3b093b051f5106 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 10:37:32 +0200 Subject: [PATCH 1/6] Add missed-path coverage reporting upstream Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../actions/Get-PesterCodeCoverage/README.md | 6 ++ .../actions/Get-PesterCodeCoverage/action.yml | 5 ++ .../Get-PesterCodeCoverage/src/main.ps1 | 72 +++++++++++++++++++ .github/workflows/Get-CodeCoverage.yml | 8 +++ 4 files changed, 91 insertions(+) diff --git a/.github/actions/Get-PesterCodeCoverage/README.md b/.github/actions/Get-PesterCodeCoverage/README.md index 689d4452..2cda002b 100644 --- a/.github/actions/Get-PesterCodeCoverage/README.md +++ b/.github/actions/Get-PesterCodeCoverage/README.md @@ -11,6 +11,7 @@ This GitHub Action is a part of the [PSModule framework](https://github.com/PSMo - Combines multiple code coverage reports from parallel test runs - Generates markdown/HTML tables showing missed & executed commands - Displays analyzed files and coverage statistics +- Generates a generic missed-path report (markdown + json) - Configurable step summary sections - Threshold enforcement for minimum code coverage @@ -40,6 +41,10 @@ This GitHub Action is a part of the [PSModule framework](https://github.com/PSMo ## Outputs +| Name | Description | +| ---- | ----------- | +| `MissedPathReportPath` | Folder path containing `CodeCoverage-MissedPaths.md` and `CodeCoverage-MissedPaths.json` | + ### GitHub Step Summary The action generates a detailed summary visible in the GitHub Actions UI: @@ -53,6 +58,7 @@ The action generates a detailed summary visible in the GitHub Actions UI: - **Missed Commands**: HTML table with code snippets - **Executed Commands**: HTML table with code snippets - **Analyzed Files**: List of covered files + - **Missed Paths**: Aggregated missed-command counts grouped by file path Example summary: diff --git a/.github/actions/Get-PesterCodeCoverage/action.yml b/.github/actions/Get-PesterCodeCoverage/action.yml index ee1fc33b..29bc7496 100644 --- a/.github/actions/Get-PesterCodeCoverage/action.yml +++ b/.github/actions/Get-PesterCodeCoverage/action.yml @@ -35,6 +35,11 @@ inputs: description: The target for code coverage. required: false +outputs: + MissedPathReportPath: + description: Path to the generated missed-path report folder. + value: ${{ steps.Get-PesterCodeCoverage.outputs.MissedPathReportPath }} + runs: using: composite steps: diff --git a/.github/actions/Get-PesterCodeCoverage/src/main.ps1 b/.github/actions/Get-PesterCodeCoverage/src/main.ps1 index dc3fc9d6..b50201df 100644 --- a/.github/actions/Get-PesterCodeCoverage/src/main.ps1 +++ b/.github/actions/Get-PesterCodeCoverage/src/main.ps1 @@ -158,6 +158,66 @@ $success = $coveragePercent -ge $coveragePercentTarget $statusIcon = $success ? '✅' : '❌' $stats | Format-Table -AutoSize | Out-String +$missedPaths = $codeCoverage.CommandsMissed | + Where-Object { -not [string]::IsNullOrWhiteSpace($_.File) } | + Group-Object -Property File | + Sort-Object -Property @( + @{ Expression = 'Count'; Descending = $true }, + @{ Expression = 'Name'; Descending = $false } + ) | + ForEach-Object { + $lines = $_.Group | + Where-Object { $_.Line -is [ValueType] } | + ForEach-Object { [int]$_.Line } | + Sort-Object -Unique + [pscustomobject]@{ + Path = $_.Name + MissedCommands = [int]$_.Count + MissedLines = if ($lines.Count -eq 0) { '' } else { ($lines -join ', ') } + } + } + +$missedPathReportPath = 'CodeCoverage-MissedPaths' +$null = New-Item -Path $missedPathReportPath -ItemType Directory -Force + +$missedPathReport = [ordered]@{ + GeneratedAtUtc = [DateTime]::UtcNow.ToString('o') + CoveragePercent = [double]$coveragePercent + CoverageTarget = [double]$coveragePercentTarget + MissedPaths = @($missedPaths) +} + +$missedPathReportJsonPath = Join-Path -Path $missedPathReportPath -ChildPath 'CodeCoverage-MissedPaths.json' +$missedPathReportMarkdownPath = Join-Path -Path $missedPathReportPath -ChildPath 'CodeCoverage-MissedPaths.md' +$missedPathReport | ConvertTo-Json -Depth 10 | Set-Content -Path $missedPathReportJsonPath -Encoding utf8NoBOM + +$missedPathMarkdown = [System.Collections.Generic.List[string]]::new() +$null = $missedPathMarkdown.Add('# Code Coverage Missed Paths') +$null = $missedPathMarkdown.Add('') +$null = $missedPathMarkdown.Add("| Coverage | Target | Missed Paths | Missed Commands |") +$null = $missedPathMarkdown.Add("| --- | --- | --- | --- |") +$null = $missedPathMarkdown.Add("| $([Math]::Round($coveragePercent, 2))% | $([Math]::Round($coveragePercentTarget, 2))% | $($missedPaths.Count) | $($codeCoverage.CommandsMissedCount) |") +$null = $missedPathMarkdown.Add('') +$null = $missedPathMarkdown.Add('## Paths') +$null = $missedPathMarkdown.Add('| Path | Missed Commands | Missed Lines |') +$null = $missedPathMarkdown.Add('| --- | ---: | --- |') +if ($missedPaths.Count -eq 0) { + $null = $missedPathMarkdown.Add('| _None_ | 0 | |') +} else { + foreach ($pathEntry in $missedPaths) { + $safePath = ([string]$pathEntry.Path).Replace('|', '\|') + $safeLines = ([string]$pathEntry.MissedLines).Replace('|', '\|') + $null = $missedPathMarkdown.Add("| $safePath | $($pathEntry.MissedCommands) | $safeLines |") + } +} + +$missedPathMarkdown | Set-Content -Path $missedPathReportMarkdownPath -Encoding utf8NoBOM +Write-Output 'Missed path report generated:' +$missedPaths | Select-Object -First 20 | Format-Table -Property Path, MissedCommands, MissedLines -AutoSize | Out-String +if ($env:GITHUB_OUTPUT) { + "MissedPathReportPath=$missedPathReportPath" >> $env:GITHUB_OUTPUT +} + # Build HTML table for 'missed' commands $tableheader = @' @@ -300,6 +360,18 @@ LogGroup 'Step Summary - Set step summary' { } } } + + Details "Missed paths [$($missedPaths.Count)]" { + if ($missedPaths.Count -eq 0) { + Paragraph { + Write-Output 'No missed paths were detected.' + } + } else { + Table { + $missedPaths | Select-Object -First 100 + } + } + } } } diff --git a/.github/workflows/Get-CodeCoverage.yml b/.github/workflows/Get-CodeCoverage.yml index a0816e36..04008397 100644 --- a/.github/workflows/Get-CodeCoverage.yml +++ b/.github/workflows/Get-CodeCoverage.yml @@ -34,3 +34,11 @@ jobs: Prerelease: ${{ fromJson(inputs.Settings).Prerelease }} Verbose: ${{ fromJson(inputs.Settings).Verbose }} Version: ${{ fromJson(inputs.Settings).Version }} + + - name: Upload CodeCoverage-MissedPaths artifact + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: CodeCoverage-MissedPaths + path: CodeCoverage-MissedPaths + if-no-files-found: warn From 56d4d3a5493e106a00b68751691f354f6d6380ec Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 11:13:31 +0200 Subject: [PATCH 2/6] Fix lint findings for missed-path report Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/actions/Get-PesterCodeCoverage/README.md | 4 ++-- .../actions/Get-PesterCodeCoverage/src/main.ps1 | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/actions/Get-PesterCodeCoverage/README.md b/.github/actions/Get-PesterCodeCoverage/README.md index 2cda002b..38c3b64d 100644 --- a/.github/actions/Get-PesterCodeCoverage/README.md +++ b/.github/actions/Get-PesterCodeCoverage/README.md @@ -9,9 +9,9 @@ This GitHub Action is a part of the [PSModule framework](https://github.com/PSMo ## Features - Combines multiple code coverage reports from parallel test runs -- Generates markdown/HTML tables showing missed & executed commands +- Generates Markdown/HTML tables showing missed & executed commands - Displays analyzed files and coverage statistics -- Generates a generic missed-path report (markdown + json) +- Generates a generic missed-path report (Markdown + JSON) - Configurable step summary sections - Threshold enforcement for minimum code coverage diff --git a/.github/actions/Get-PesterCodeCoverage/src/main.ps1 b/.github/actions/Get-PesterCodeCoverage/src/main.ps1 index b50201df..9240db46 100644 --- a/.github/actions/Get-PesterCodeCoverage/src/main.ps1 +++ b/.github/actions/Get-PesterCodeCoverage/src/main.ps1 @@ -171,9 +171,13 @@ $missedPaths = $codeCoverage.CommandsMissed | ForEach-Object { [int]$_.Line } | Sort-Object -Unique [pscustomobject]@{ - Path = $_.Name + Path = $_.Name MissedCommands = [int]$_.Count - MissedLines = if ($lines.Count -eq 0) { '' } else { ($lines -join ', ') } + MissedLines = if ($lines.Count -eq 0) { + '' + } else { + $lines -join ', ' + } } } @@ -196,7 +200,12 @@ $null = $missedPathMarkdown.Add('# Code Coverage Missed Paths') $null = $missedPathMarkdown.Add('') $null = $missedPathMarkdown.Add("| Coverage | Target | Missed Paths | Missed Commands |") $null = $missedPathMarkdown.Add("| --- | --- | --- | --- |") -$null = $missedPathMarkdown.Add("| $([Math]::Round($coveragePercent, 2))% | $([Math]::Round($coveragePercentTarget, 2))% | $($missedPaths.Count) | $($codeCoverage.CommandsMissedCount) |") +$coverageSummaryRow = '| {0}% | {1}% | {2} | {3} |' -f ( + [Math]::Round($coveragePercent, 2) +), ( + [Math]::Round($coveragePercentTarget, 2) +), $missedPaths.Count, $codeCoverage.CommandsMissedCount +$null = $missedPathMarkdown.Add($coverageSummaryRow) $null = $missedPathMarkdown.Add('') $null = $missedPathMarkdown.Add('## Paths') $null = $missedPathMarkdown.Add('| Path | Missed Commands | Missed Lines |') From 45b6f9c35853ed9c75f7e1cc571ec4c569764019 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 11:57:28 +0200 Subject: [PATCH 3/6] Add repo AGENTS guidance standard Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..408274fa --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,35 @@ +# Agents + +## Purpose + +Use this file to understand how agents should gather context and execute work in +this repository. + +## Core operating standard + +1. Treat repository controls (linters, security checks, policies) as correct by + default. +2. Align implementation with controls before considering exceptions. +3. If an exception is needed, make it explicit, minimal, and documented in code + and PR context. Never bypass controls silently. + +## Canonical context resolution + +Determine repository identity from Git metadata (for example, `origin` remote and +repository settings). Do not hardcode a specific owner or host unless the task +explicitly requires it. + +Process context in this order: + +| Order | Source | What to read first | Why | +| --- | --- | --- | --- | +| 1 | Local repository | `README.md`, `CONTRIBUTING.md`, local docs index/start page | Local behavior and contribution rules are authoritative for this repo. | +| 2 | Project/framework guidance | Framework or shared project docs referenced by this repo | Align with shared implementation patterns used by sibling repos. | +| 3 | Central organization guidance | Organization-level docs and standards | Apply global policy after local/project specifics are known. | + +## Documentation standards for commands and repos + +1. Use canonical repository URLs in docs (full URLs such as + `https://github.com/owner/repo`) instead of ambiguous short names. +2. For install and synchronization instructions, provide equivalent examples for + native shells on Windows, macOS, and Linux. From 78e5f76d302c211bf20e2884f784940af926e05a Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 11:58:50 +0200 Subject: [PATCH 4/6] Revert "Add repo AGENTS guidance standard" This reverts commit 45b6f9c35853ed9c75f7e1cc571ec4c569764019. --- AGENTS.md | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 408274fa..00000000 --- a/AGENTS.md +++ /dev/null @@ -1,35 +0,0 @@ -# Agents - -## Purpose - -Use this file to understand how agents should gather context and execute work in -this repository. - -## Core operating standard - -1. Treat repository controls (linters, security checks, policies) as correct by - default. -2. Align implementation with controls before considering exceptions. -3. If an exception is needed, make it explicit, minimal, and documented in code - and PR context. Never bypass controls silently. - -## Canonical context resolution - -Determine repository identity from Git metadata (for example, `origin` remote and -repository settings). Do not hardcode a specific owner or host unless the task -explicitly requires it. - -Process context in this order: - -| Order | Source | What to read first | Why | -| --- | --- | --- | --- | -| 1 | Local repository | `README.md`, `CONTRIBUTING.md`, local docs index/start page | Local behavior and contribution rules are authoritative for this repo. | -| 2 | Project/framework guidance | Framework or shared project docs referenced by this repo | Align with shared implementation patterns used by sibling repos. | -| 3 | Central organization guidance | Organization-level docs and standards | Apply global policy after local/project specifics are known. | - -## Documentation standards for commands and repos - -1. Use canonical repository URLs in docs (full URLs such as - `https://github.com/owner/repo`) instead of ambiguous short names. -2. For install and synchronization instructions, provide equivalent examples for - native shells on Windows, macOS, and Linux. From 746506a62be4a295983c2ce21acd6cbf7dbb564c Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 13:19:03 +0200 Subject: [PATCH 5/6] Align hashtable assignments for lint Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/actions/Get-PesterCodeCoverage/src/main.ps1 | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/actions/Get-PesterCodeCoverage/src/main.ps1 b/.github/actions/Get-PesterCodeCoverage/src/main.ps1 index 9240db46..9d1e9e28 100644 --- a/.github/actions/Get-PesterCodeCoverage/src/main.ps1 +++ b/.github/actions/Get-PesterCodeCoverage/src/main.ps1 @@ -171,13 +171,9 @@ $missedPaths = $codeCoverage.CommandsMissed | ForEach-Object { [int]$_.Line } | Sort-Object -Unique [pscustomobject]@{ - Path = $_.Name + Path = $_.Name MissedCommands = [int]$_.Count - MissedLines = if ($lines.Count -eq 0) { - '' - } else { - $lines -join ', ' - } + MissedLines = if ($lines.Count -eq 0) { '' } else { $lines -join ', ' } } } From 00cd9d1fa520cf863329a38c0b137a680c7c9d68 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 26 Jul 2026 15:17:54 +0200 Subject: [PATCH 6/6] Refactor missed-path grouping for lint compliance Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Get-PesterCodeCoverage/src/main.ps1 | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/actions/Get-PesterCodeCoverage/src/main.ps1 b/.github/actions/Get-PesterCodeCoverage/src/main.ps1 index 9d1e9e28..995a5825 100644 --- a/.github/actions/Get-PesterCodeCoverage/src/main.ps1 +++ b/.github/actions/Get-PesterCodeCoverage/src/main.ps1 @@ -158,24 +158,26 @@ $success = $coveragePercent -ge $coveragePercentTarget $statusIcon = $success ? '✅' : '❌' $stats | Format-Table -AutoSize | Out-String -$missedPaths = $codeCoverage.CommandsMissed | +$missedPathGroups = $codeCoverage.CommandsMissed | Where-Object { -not [string]::IsNullOrWhiteSpace($_.File) } | Group-Object -Property File | Sort-Object -Property @( @{ Expression = 'Count'; Descending = $true }, @{ Expression = 'Name'; Descending = $false } - ) | - ForEach-Object { - $lines = $_.Group | - Where-Object { $_.Line -is [ValueType] } | - ForEach-Object { [int]$_.Line } | - Sort-Object -Unique - [pscustomobject]@{ - Path = $_.Name - MissedCommands = [int]$_.Count - MissedLines = if ($lines.Count -eq 0) { '' } else { $lines -join ', ' } - } + ) + +$missedPaths = foreach ($group in $missedPathGroups) { + $lines = $group.Group | + Where-Object { $_.Line -is [ValueType] } | + ForEach-Object { [int]$_.Line } | + Sort-Object -Unique + $missedLines = if ($lines.Count -eq 0) { '' } else { $lines -join ', ' } + [pscustomobject]@{ + Path = $group.Name + MissedCommands = [int]$group.Count + MissedLines = $missedLines } +} $missedPathReportPath = 'CodeCoverage-MissedPaths' $null = New-Item -Path $missedPathReportPath -ItemType Directory -Force