Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7d87222
[CI] Refactor ci-copilot pipeline: scope env vars per task
Copilot May 22, 2026
29b1518
Fix review findings: persist regression data across phases
Copilot May 22, 2026
647786a
Fix detectedCategories routing: coalesce RunReview with RunGate
Copilot May 22, 2026
3fd884a
Fix null crash in Post/CopilotReview phases + add sentinel check
Copilot May 22, 2026
485e865
Add GH_TOKEN to Gate phase for PR metadata fetches
Copilot May 22, 2026
a22749d
[CI] Plug GH service-connection token leaks in copilot pipeline
T-Gro May 26, 2026
ca5ce6d
[CI] Strip GH_TOKEN from PR-code subprocesses; trust eng/scripts copy
T-Gro May 26, 2026
0d1acf3
[CI] Add security instructions for ci-copilot pipeline surface
T-Gro May 26, 2026
07565bb
Clean up stale MauiBot PR comments
Copilot May 26, 2026
63c4689
Merge remote-tracking branch 'origin/feature/refactor-copilot-yml' in…
Copilot May 26, 2026
d3bcebd
[CI] Compact ci-copilot security instructions
T-Gro May 26, 2026
7ade47d
[CI] Use YAML list + brace alternation for applyTo
T-Gro May 26, 2026
0e3ce9d
[CI] Use officially-documented applyTo syntax + drop scare line
T-Gro May 26, 2026
888febc
[CI] Fix Gate: move token-strip wrap inside verify-tests-fail.ps1
T-Gro May 26, 2026
d8ff0a1
Fix AI summary session replacement
Copilot May 27, 2026
3650971
Remove duplicate full-category UI test run from ReviewPR stage
Copilot May 28, 2026
d4deda6
Fix deep UI setup failure reporting
Copilot May 28, 2026
bbd6055
Hide no-op AI summary sections
Copilot May 28, 2026
d3069ce
Enhance MauiBot review posting
Copilot May 31, 2026
d461a5b
Update MauiBot AI summary layout
Copilot May 31, 2026
8fcd355
Merge try-fix guidance into AI summary review
Copilot May 31, 2026
5f3beaa
Run Windows device tests without VSTest
Copilot May 31, 2026
4b8e6dc
Add deterministic review rerun gate
Copilot May 31, 2026
19812a0
Verify rerun label application
Copilot May 31, 2026
87c03f0
Use JSON when adding agent labels
Copilot May 31, 2026
4148e72
Allow rerun workflow to label PRs
Copilot May 31, 2026
84421cf
Remove PR finalization from AI review flow
Copilot May 31, 2026
f1d8b1c
Include rerun activity in pre-flight context
Copilot Jun 1, 2026
16321bd
Move rerun implementation out of review UI PR
Copilot Jun 1, 2026
22636b9
Commit squashed PR changes before gate
Copilot Jun 1, 2026
3791860
Reset review branch before gate retries
Copilot Jun 1, 2026
a80f23e
Fix BlazorWebView unit test detection
Copilot Jun 1, 2026
9b313c1
Merge origin/main into feature/enhanced-reviewer
Copilot Jun 3, 2026
9fb6a04
Add Copilot token usage artifacts
Copilot Jun 5, 2026
8559242
Capture Copilot OTel token metrics
Copilot Jun 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .github/docs/agent-labels.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,8 @@ Review-PR.ps1
│ ├── Validate → writes content.md
│ ├── Fix → writes content.md
│ └── Report → writes content.md
├── Phase 2: PR Finalize (optional)
├── Phase 3: Post Comments (optional)
└── Phase 4: Apply Labels ← labels are applied here
├── Phase 2: Post Comments (optional)
└── Phase 3: Apply Labels ← labels are applied here
├── Parse content.md files
├── Determine outcome + signal labels
├── Apply via GitHub REST API
Expand Down
90 changes: 90 additions & 0 deletions .github/scripts/Aggregate-CopilotTokenUsage.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env pwsh
#Requires -Modules Pester
<#
.SYNOPSIS
Pester tests for Aggregate-CopilotTokenUsage.ps1.
#>

Describe 'Aggregate-CopilotTokenUsage.ps1' {
BeforeEach {
$script:fixtureRoot = Join-Path ([System.IO.Path]::GetTempPath()) "token-usage-fixtures-$([guid]::NewGuid())"
$script:inputRoot = Join-Path $script:fixtureRoot 'input'
$script:outputRoot = Join-Path $script:fixtureRoot 'output'
New-Item -ItemType Directory -Path $script:inputRoot -Force | Out-Null
}

AfterEach {
Remove-Item -Path $script:fixtureRoot -Recurse -Force -ErrorAction SilentlyContinue
}

It 'writes raw and summarized artifacts with zero rows for stages without Copilot invocations' {
$nested = Join-Path $script:inputRoot 'CopilotLogs/copilot-token-usage/raw'
New-Item -ItemType Directory -Path $nested -Force | Out-Null

[ordered]@{
schemaVersion = 1
prNumber = 35677
pipeline = [ordered]@{ stageName = 'ReviewPR' }
scriptPhase = 'CopilotReview'
copilotStep = 'STEP 5a: TRY-FIX'
model = 'gpt-5.5'
durationMs = 5000
apiDurationMs = 2000
turnCount = 2
toolCount = 3
cliUsage = [ordered]@{
aicUsed = 7.5
contextWindow = 1100000
contextWindowRaw = '1.1M'
}
normalizedTokens = [ordered]@{
inputTokens = 100
outputTokens = 40
cachedInputTokens = 10
totalTokens = 140
}
} | ConvertTo-Json -Depth 10 | Set-Content (Join-Path $nested 'copilot-token-usage-a.json') -Encoding UTF8

$scriptPath = Join-Path $PSScriptRoot 'shared/Aggregate-CopilotTokenUsage.ps1'
& $scriptPath `
-InputRoot $script:inputRoot `
-OutputDir $script:outputRoot `
-PRNumber '35677' `
-ExpectedStages @('ReviewPR', 'RunDeepUITests', 'UpdateAISummaryComment', 'AnalyzeCopilotTokenUsage')

Test-Path (Join-Path $script:outputRoot 'token-usage-raw.jsonl') | Should -Be $true
Test-Path (Join-Path $script:outputRoot 'token-usage-summary.md') | Should -Be $true
Test-Path (Join-Path $script:outputRoot 'token-usage-by-step.csv') | Should -Be $true

$summary = Get-Content (Join-Path $script:outputRoot 'token-usage-summary.json') -Raw | ConvertFrom-Json
$summary.recordCount | Should -Be 1
$summary.totals.inputTokens | Should -Be 100
$summary.totals.outputTokens | Should -Be 40
$summary.totals.totalTokens | Should -Be 140
$summary.totals.aicUsed | Should -Be 7.5

$reviewStage = $summary.stages | Where-Object { $_.stageName -eq 'ReviewPR' }
$reviewStage.invocationCount | Should -Be 1
$reviewStage.totalTokens | Should -Be 140
$reviewStage.aicUsed | Should -Be 7.5

$deepStage = $summary.stages | Where-Object { $_.stageName -eq 'RunDeepUITests' }
$deepStage.invocationCount | Should -Be 0
$deepStage.totalTokens | Should -Be 0
$deepStage.aicUsed | Should -Be 0
$deepStage.note | Should -Be 'No Copilot invocation observed in this stage.'
}

It 'emits a no-record summary when the input artifact is missing' {
$scriptPath = Join-Path $PSScriptRoot 'shared/Aggregate-CopilotTokenUsage.ps1'
& $scriptPath `
-InputRoot (Join-Path $script:fixtureRoot 'missing') `
-OutputDir $script:outputRoot `
-PRNumber '35677'

$summary = Get-Content (Join-Path $script:outputRoot 'token-usage-summary.json') -Raw | ConvertFrom-Json
$summary.recordCount | Should -Be 0
($summary.stages | Where-Object { $_.stageName -eq 'ReviewPR' }).invocationCount | Should -Be 0
Test-Path (Join-Path $script:outputRoot 'token-usage-by-step.csv') | Should -Be $true
}
}
4 changes: 2 additions & 2 deletions .github/scripts/Find-RegressionRisks.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
added → 🔴 REVERT. Same file but no line match → 🟡 OVERLAP. Otherwise → 🟢 CLEAN.

Outputs (when -OutputDir is provided):
- content.md Markdown summary suitable for the wall-of-text PR comment.
- content.md Markdown summary suitable for the wall-of-text PR review.
- risks.json Structured findings for downstream agents.
- result.txt One token: CLEAN | OVERLAP | REVERT (used by Review-PR.ps1
for branching).
Expand Down Expand Up @@ -726,7 +726,7 @@ if ($OutputDir) {
} | ConvertTo-Json -Depth 6
$payload | Set-Content (Join-Path $OutputDir 'risks.json') -Encoding UTF8

# content.md — markdown summary for the wall-of-text PR comment
# content.md — markdown summary for the wall-of-text PR review
$md = New-Object System.Text.StringBuilder
[void]$md.AppendLine("## 🔍 Regression Cross-Reference")
[void]$md.AppendLine()
Expand Down
119 changes: 111 additions & 8 deletions .github/scripts/Post-AISummaryComment.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,24 @@ BeforeAll {
throw ($parseErrors | ForEach-Object { $_.Message }) -join [Environment]::NewLine
}

$function = $ast.Find({
$args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] -and
$args[0].Name -eq 'Test-PhaseContentIsNoOp'
}, $true)
foreach ($functionName in @(
'Test-PhaseContentIsNoOp',
'Get-AIReviewEvent',
'Test-HasNonPRWinner',
'Get-AIReviewEventForRun',
'New-FutureActionSection'
)) {
$function = $ast.Find({
$args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] -and
$args[0].Name -eq $functionName
}, $true)

if (-not $function) {
throw "Function 'Test-PhaseContentIsNoOp' not found"
}
if (-not $function) {
throw "Function '$functionName' not found"
}

Invoke-Expression $function.Extent.Text
Invoke-Expression $function.Extent.Text
}
}

Describe 'Test-PhaseContentIsNoOp' {
Expand Down Expand Up @@ -73,3 +81,98 @@ Describe 'Test-PhaseContentIsNoOp' {
Should -BeFalse
}
}

Describe 'Get-AIReviewEvent' {
It 'maps an exact approve recommendation to APPROVE' {
Get-AIReviewEvent -ReportContent "## ✅ Final Recommendation: APPROVE`n`nLooks good." |
Should -Be 'APPROVE'
}

It 'maps an exact request-changes recommendation to REQUEST_CHANGES' {
Get-AIReviewEvent -ReportContent "## ⚠️ Final Recommendation: REQUEST CHANGES`n`nNeeds the try-fix candidate." |
Should -Be 'REQUEST_CHANGES'
}

It 'falls back to COMMENT when the recommendation is missing or ambiguous' {
Get-AIReviewEvent -ReportContent '' | Should -Be 'COMMENT'
Get-AIReviewEvent -ReportContent 'Recommendation: APPROVE after manual review' | Should -Be 'COMMENT'
}
}

Describe 'Get-AIReviewEventForRun' {
BeforeEach {
$script:testDir = Join-Path ([System.IO.Path]::GetTempPath()) "ai-summary-tests-$([guid]::NewGuid())"
New-Item -ItemType Directory -Path $script:testDir -Force | Out-Null
}

AfterEach {
Remove-Item -LiteralPath $script:testDir -Recurse -Force -ErrorAction SilentlyContinue
}

It 'requests changes when a non-PR try-fix candidate wins and the report is otherwise comment-only' {
@{
winner = 'try-fix-1'
isPRFix = $false
candidateDiff = 'diff --git a/file.cs b/file.cs'
summary = 'Candidate fixes the issue more directly.'
} | ConvertTo-Json -Depth 5 | Set-Content (Join-Path $script:testDir 'winner.json') -Encoding UTF8

Get-AIReviewEventForRun -ReportContent 'Report still in progress.' -PRAgentDir $script:testDir |
Should -Be 'REQUEST_CHANGES'
}

It 'does not override an exact approve recommendation' {
@{
winner = 'try-fix-1'
isPRFix = $false
candidateDiff = 'diff --git a/file.cs b/file.cs'
} | ConvertTo-Json -Depth 5 | Set-Content (Join-Path $script:testDir 'winner.json') -Encoding UTF8

Get-AIReviewEventForRun -ReportContent 'Final Recommendation: APPROVE' -PRAgentDir $script:testDir |
Should -Be 'APPROVE'
}

It 'does not force changes for missing, malformed, or PR-fix winner files' {
Get-AIReviewEventForRun -ReportContent '' -PRAgentDir $script:testDir |
Should -Be 'COMMENT'

'not json' | Set-Content (Join-Path $script:testDir 'winner.json') -Encoding UTF8
Get-AIReviewEventForRun -ReportContent '' -PRAgentDir $script:testDir |
Should -Be 'COMMENT'

@{
winner = 'pr'
isPRFix = $true
} | ConvertTo-Json -Depth 5 | Set-Content (Join-Path $script:testDir 'winner.json') -Encoding UTF8
Get-AIReviewEventForRun -ReportContent '' -PRAgentDir $script:testDir |
Should -Be 'COMMENT'
}
}

Describe 'New-FutureActionSection' {
BeforeEach {
$script:testDir = Join-Path ([System.IO.Path]::GetTempPath()) "future-action-tests-$([guid]::NewGuid())"
New-Item -ItemType Directory -Path $script:testDir -Force | Out-Null
}

AfterEach {
Remove-Item -LiteralPath $script:testDir -Recurse -Force -ErrorAction SilentlyContinue
}

It 'renders selected try-fix candidate guidance in the AI Summary Future Action section' {
@{
winner = 'try-fix-2'
isPRFix = $false
summary = 'Candidate avoids the regression.'
candidateDiff = "diff --git a/file.cs b/file.cs`n+fixed"
} | ConvertTo-Json -Depth 5 | Set-Content (Join-Path $script:testDir 'winner.json') -Encoding UTF8

$section = New-FutureActionSection -PRAgentDir $script:testDir

$section | Should -Match '<strong>Future Action</strong>'
$section | Should -Match 'alternative fix proposed'
$section | Should -Match 'try-fix-2'
$section | Should -Match 'Candidate avoids the regression'
$section | Should -Match 'diff --git a/file.cs b/file.cs'
}
}
76 changes: 76 additions & 0 deletions .github/scripts/Remove-StaleMauiBotComments.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env pwsh
#Requires -Modules Pester
<#
.SYNOPSIS
Pester tests for stale MauiBot artifact helper pure functions.
#>

BeforeAll {
$scriptPath = Join-Path $PSScriptRoot 'shared/Remove-StaleMauiBotComments.ps1'
$tokens = $null
$parseErrors = $null
$ast = [System.Management.Automation.Language.Parser]::ParseFile($scriptPath, [ref]$tokens, [ref]$parseErrors)
if ($parseErrors -and $parseErrors.Count -gt 0) {
throw ($parseErrors | ForEach-Object { $_.Message }) -join [Environment]::NewLine
}

. $scriptPath
}

Describe 'MauiBot artifact marker detection' {
It 'detects AI Summary artifacts by marker' {
Test-IsAISummaryCommentBody -Body "<!-- AI Summary -->`n## AI Summary" |
Should -BeTrue
}

It 'detects try-fix artifacts by current and legacy text markers' {
Test-IsTryFixCommentBody -Body "<!-- MAUI_BOT_TRY_FIX -->`nBody" |
Should -BeTrue

Test-IsTryFixCommentBody -Body 'Automated review — alternative fix proposed' |
Should -BeTrue
}

It 'does not treat the merged AI Summary Future Action section as a standalone try-fix artifact' {
$body = @'
<!-- AI Summary -->

<details>
<summary><strong>Future Action</strong> — alternative fix proposed (<code>try-fix-1</code>)</summary>

**Automated review — alternative fix proposed**

<details><summary>Candidate diff (<code>try-fix-1</code>)</summary>
</details>
</details>
'@

Test-IsTryFixCommentBody -Body $body |
Should -BeFalse
}
}

Describe 'Test-ShouldPreserveMauiBotArtifact' {
It 'preserves artifacts by node id or REST id' {
$artifact = [pscustomobject]@{
id = 123
node_id = 'PRR_test'
}

Test-ShouldPreserveMauiBotArtifact -Artifact $artifact -PreserveNodeIds @('PRR_test') |
Should -BeTrue

Test-ShouldPreserveMauiBotArtifact -Artifact $artifact -PreserveIds @('123') |
Should -BeTrue
}

It 'does not preserve unmatched artifacts' {
$artifact = [pscustomobject]@{
id = 123
node_id = 'PRR_test'
}

Test-ShouldPreserveMauiBotArtifact -Artifact $artifact -PreserveNodeIds @('other') -PreserveIds @('456') |
Should -BeFalse
}
}
Loading
Loading