Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 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
d6aed2d
Add deterministic review rerun gate
Copilot Jun 1, 2026
b20223d
Verify rerun label application
Copilot May 31, 2026
6706df5
Use JSON when adding agent labels
Copilot May 31, 2026
69238a9
Allow rerun workflow to label PRs
Copilot May 31, 2026
766c909
Include rerun activity in pre-flight context
Copilot Jun 1, 2026
9465d1c
Add gh-aw rerun review scanner
Copilot Jun 1, 2026
798f837
Make rerun helper importable
Copilot Jun 1, 2026
03a490e
Allow fork dry-runs for rerun scanner
Copilot Jun 1, 2026
b84cc7f
Clarify rerun scanner concurrency
Copilot Jun 2, 2026
a3412bc
Add review rerun in-progress lock
Copilot Jun 2, 2026
1dcdb74
Preserve rerun scanner target repository
Copilot Jun 2, 2026
8e71f44
Preserve review rerun platform and branch
Copilot Jun 2, 2026
530a7c5
Restrict review rerun to contributors
Copilot Jun 2, 2026
7f447e2
Address rerun review scanner feedback
Copilot Jun 4, 2026
5378fc4
Merge remote-tracking branch 'origin/main' into feature/rerun-review-…
Copilot Jun 4, 2026
7325fba
Use current repo for rerun scanner query
Copilot Jun 4, 2026
12cea3a
Allow non-first-time PR authors to rerun reviews
Copilot Jun 4, 2026
606c4e3
Add rerun scanner abuse limits
Copilot Jun 4, 2026
80da010
Harden rerun review scanner handler
Copilot Jun 4, 2026
29f62fb
Refine rerun handler input handling and edge cases
PureWeen Jun 4, 2026
3484700
Always check per-comment author_association in Test-ReviewCommandOpti…
PureWeen 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
31 changes: 28 additions & 3 deletions .github/aw/actions-lock.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
{
"entries": {
"actions/checkout@v4": {
"repo": "actions/checkout",
"version": "v4",
"sha": "34e114876b0b11c390a56381ad16ebd13914f8d5"
},
"actions/checkout@v6.0.2": {
"repo": "actions/checkout",
"version": "v6.0.2",
"sha": "de0fac2e4500dabe0009e67214ff5f5447ce83dd"
},
"actions/download-artifact@v8.0.1": {
"repo": "actions/download-artifact",
"version": "v8.0.1",
"sha": "3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c"
},
"actions/github-script@v8": {
"repo": "actions/github-script",
"version": "v8",
Expand All @@ -10,10 +25,20 @@
"version": "v9.0.0",
"sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3"
},
"github/gh-aw-actions/setup@v0.72.1": {
"actions/setup-node@v6.4.0": {
"repo": "actions/setup-node",
"version": "v6.4.0",
"sha": "48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e"
},
"actions/upload-artifact@v7.0.1": {
"repo": "actions/upload-artifact",
"version": "v7.0.1",
"sha": "043fb46d1a93c77aae656e7c1c64a875d1fc6a0a"
},
"github/gh-aw-actions/setup@v0.77.5": {
"repo": "github/gh-aw-actions/setup",
"version": "v0.72.1",
"sha": "bc56a0cad2f450c562810785ef38649c04db812a"
"version": "v0.77.5",
"sha": "3ea13c02d765410340d533515cb31a7eef2baaf0"
},
"github/gh-aw/actions/setup@v0.43.19": {
"repo": "github/gh-aw/actions/setup",
Expand Down
20 changes: 14 additions & 6 deletions .github/docs/agent-labels.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,15 @@ Always applied on every completed agent run.
|-------|-------|-------------|--------------|
| `s/agent-reviewed` | 🔵 `#1565C0` | PR was reviewed by AI agent workflow (full 4-phase review) | Every completed agent run |

### Manual Label
### Manual / Queue Labels

Applied by MAUI maintainers, not by automation.
Manual labels are applied by MAUI maintainers. Queue labels are applied by deterministic automation, not by AI.

| Label | Color | Description | Applied When |
|-------|-------|-------------|--------------|
| `s/agent-fix-implemented` | 🟣 `#7B1FA2` | PR author implemented the agent's suggested fix | Maintainer applies when PR author adopts agent's recommendation |
| `s/agent-ready-for-rerun` | 🟣 `#5319E7` | AI review has new PR activity and is ready for rerun | `/review rerun` finds new comments or commits after the latest AI Summary / previous rerun request |
| `s/agent-review-in-progress` | 🟡 `#FBCA04` | AI review is currently running for this PR | Applied before triggering the async AzDO review pipeline and removed by pipeline cleanup; stale locks can be recovered after a conservative timeout |

---

Expand All @@ -62,16 +64,15 @@ 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
└── Non-fatal: errors warn but don't fail the workflow
```

Labels are applied exclusively from `Review-PR.ps1` Phase 4. No other script applies agent labels. This single-source design avoids label conflicts and simplifies debugging.
Most review outcome labels are applied from `Review-PR.ps1` Phase 4. The exceptions are queue/lock labels: `s/agent-ready-for-rerun` is applied by the deterministic `/review rerun` GitHub Action path after checking for new comments or commits, and `s/agent-review-in-progress` is applied before triggering the async AzDO review pipeline. The rerun path does not use AI to decide whether these labels apply. The lock label normally clears in the AzDO cleanup stage; trigger paths treat very old locks as stale so a cancelled pipeline does not permanently block reviews.

### How Labels Are Parsed

Expand Down Expand Up @@ -140,6 +141,10 @@ is:pr label:s/agent-reviewed
|------|---------|
| `.github/scripts/shared/Update-AgentLabels.ps1` | Label helper module (all label logic) |
| `.github/scripts/Review-PR.ps1` | Orchestrator that calls `Apply-AgentLabels` in Phase 4 |
| `.github/scripts/Resolve-RerunEligibility.ps1` | Deterministic `/review rerun` checker that can apply `s/agent-ready-for-rerun` |
| `.github/scripts/Invoke-RerunReviewTrigger.ps1` | Safe-output handler that applies `s/agent-review-in-progress` before triggering AzDO reruns |
| `.github/workflows/review-trigger.yml` | Manual `/review` trigger that applies `s/agent-review-in-progress` before triggering AzDO reviews |
| `eng/pipelines/ci-copilot.yml` | AzDO review pipeline that removes `s/agent-review-in-progress` in final cleanup |
| `.github/skills/pr-review/SKILL.md` | Documents label system for the pr-review skill |

### Key Functions
Expand All @@ -151,6 +156,9 @@ is:pr label:s/agent-reviewed
| `Update-AgentOutcomeLabel` | Applies one outcome label, removes conflicting ones |
| `Update-AgentSignalLabels` | Adds/removes validate and fix signal labels |
| `Update-AgentReviewedLabel` | Ensures tracking label is present |
| `Set-AgentReviewInProgress` | Applies the async review lock label |
| `Clear-AgentReviewInProgress` | Removes the async review lock label |
| `Test-AgentReviewInProgressIsStale` | Checks whether a lock label is old enough to recover |
| `Ensure-LabelExists` | Creates or updates a label in the repository |

### Design Principles
Expand Down
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
221 changes: 221 additions & 0 deletions .github/scripts/Invoke-RerunReviewTrigger.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
#!/usr/bin/env pwsh
#Requires -Modules Pester

BeforeAll {
$scriptPath = Join-Path $PSScriptRoot 'Invoke-RerunReviewTrigger.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
}

$script:ReviewTriggerCooldownMinutes = 60
$script:ReviewTriggerWindowHours = 24
$script:MaxReviewTriggersPerWindow = 3

foreach ($functionName in @('Get-ReviewTriggerRateLimitStatus', 'ConvertTo-SafeLogValue', 'Get-MatchingCandidate', 'Normalize-PipelineRef', 'Get-PlatformFromLabels')) {
$function = $ast.Find({
$args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] -and
$args[0].Name -eq $functionName
}, $true)

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

Invoke-Expression $function.Extent.Text
}

function New-TestCandidate {
param(
[int]$PRNumber = 123,
[string]$HeadSha = 'abc123def',
[string]$Platform = 'android',
[string]$PipelineRef = 'main',
[Int64]$RerunCommentId = 9001
)

[pscustomobject]@{
prNumber = $PRNumber
headSha = $HeadSha
platform = $Platform
pipelineRef = $PipelineRef
rerunCommentId = $RerunCommentId
}
}
}

Describe 'ConvertTo-SafeLogValue' {
It 'removes newlines and breaks workflow command tokens' {
$safe = ConvertTo-SafeLogValue "ok`n::stop-commands::x"

$safe | Should -Be 'ok : :stop-commands: :x'
$safe | Should -Not -Match '[\r\n]'
$safe | Should -Not -Match '::'
}

It 'caps long log values' {
$safe = ConvertTo-SafeLogValue ('a' * 200) -MaxLength 20

$safe.Length | Should -Be 20
$safe | Should -Match '\.\.\.$'
}

It 'sanitizes embedded workflow commands in error-shaped strings' {
$exceptionText = "Cannot convert value `"99`n::stop-commands::pwn`" to type System.Int32"

$safe = ConvertTo-SafeLogValue $exceptionText

$safe | Should -Not -Match '[\r\n]'
$safe | Should -Not -Match '::'
$safe | Should -Match 'stop-commands'
}
}

Describe 'Get-MatchingCandidate' {
It 'matches only PRs in the deterministic candidate set' {
$candidates = @(
[pscustomobject]@{ prNumber = 123; headSha = 'abc' }
[pscustomobject]@{ prNumber = 456; headSha = 'def' }
)

(Get-MatchingCandidate -Candidates $candidates -PRNumber 456).headSha | Should -Be 'def'
Get-MatchingCandidate -Candidates $candidates -PRNumber 789 | Should -BeNullOrEmpty
}
}

Describe 'Normalize-PipelineRef' {
It 'strips refs/heads/ prefix' {
Normalize-PipelineRef -Value 'refs/heads/feature/x' | Should -Be 'feature/x'
}

It 'returns fallback for empty input' {
Normalize-PipelineRef -Value '' -Fallback 'main' | Should -Be 'main'
}

It 'returns fallback when traversal or anchor patterns are present' {
Normalize-PipelineRef -Value '../etc/passwd' -Fallback 'main' | Should -Be 'main'
Normalize-PipelineRef -Value '/feature' -Fallback 'main' | Should -Be 'main'
Normalize-PipelineRef -Value 'feature/' -Fallback 'main' | Should -Be 'main'
}

It 'strips characters outside the safe set and returns fallback when result ends with slash' {
Normalize-PipelineRef -Value 'feature/x; rm -rf /' -Fallback 'main' | Should -Be 'main'
}

It 'strips invalid characters while keeping a valid ref intact' {
Normalize-PipelineRef -Value 'feat ure/x' -Fallback 'main' | Should -Be 'feature/x'
}
}

Describe 'Get-PlatformFromLabels' {
It 'prefers a valid fallback over labels' {
Get-PlatformFromLabels -Labels @('platform/ios') -Fallback 'android' | Should -Be 'android'
}

It 'ignores an invalid fallback and falls back to labels' {
Get-PlatformFromLabels -Labels @('platform/ios') -Fallback '../etc/passwd' | Should -Be 'ios'
}

It 'maps platform/macos to catalyst' {
Get-PlatformFromLabels -Labels @('platform/macos') | Should -Be 'catalyst'
}

It 'defaults to android when no platform label is present' {
Get-PlatformFromLabels -Labels @('area-controls') | Should -Be 'android'
}
}

Describe 'Candidate-sourced values' {
It 'produces the rerun comment id from candidate data, not the agent emission' {
$candidate = New-TestCandidate -RerunCommentId 4242

# The handler reads $candidate.rerunCommentId via:
# $rerunCommentId = if ($candidate.rerunCommentId) { [Int64]$candidate.rerunCommentId } else { [Int64]0 }
$rerunCommentId = if ($candidate.rerunCommentId) { [Int64]$candidate.rerunCommentId } else { [Int64]0 }

$rerunCommentId | Should -Be 4242
}

It 'falls back to zero when candidate has no rerun comment id' {
$candidate = New-TestCandidate -RerunCommentId 0
$rerunCommentId = if ($candidate.rerunCommentId) { [Int64]$candidate.rerunCommentId } else { [Int64]0 }

$rerunCommentId | Should -Be 0
}

It 'normalizes the candidate pipeline ref against the configured default' {
$candidate = New-TestCandidate -PipelineRef 'refs/heads/feature/x'

Normalize-PipelineRef -Value ([string]$candidate.pipelineRef) -Fallback 'main' | Should -Be 'feature/x'
}

It 'falls back to the configured default when candidate pipeline ref is unsafe' {
$candidate = New-TestCandidate -PipelineRef '../escape'

Normalize-PipelineRef -Value ([string]$candidate.pipelineRef) -Fallback 'main' | Should -Be 'main'
}

It 'lets label-derived platform override an invalid candidate platform' {
$candidate = New-TestCandidate -Platform ''

Get-PlatformFromLabels -Labels @('platform/ios') -Fallback ([string]$candidate.platform) | Should -Be 'ios'
}
}

Describe 'Get-ReviewTriggerRateLimitStatus' {
It 'allows a PR with no recent rerun triggers' {
$now = [datetimeoffset]'2026-06-04T12:00:00Z'

$result = Get-ReviewTriggerRateLimitStatus -TriggeredAt @() -Now $now

$result.Allowed | Should -BeTrue
$result.Reason | Should -Be 'allowed'
$result.RecentCount | Should -Be 0
}

It 'blocks rerun triggers inside the cooldown window' {
$now = [datetimeoffset]'2026-06-04T12:00:00Z'

$result = Get-ReviewTriggerRateLimitStatus `
-TriggeredAt @([datetimeoffset]'2026-06-04T11:30:00Z') `
-Now $now

$result.Allowed | Should -BeFalse
$result.Reason | Should -Be 'cooldown-active'
$result.RecentCount | Should -Be 1
}

It 'blocks when the 24 hour quota is exhausted' {
$now = [datetimeoffset]'2026-06-04T12:00:00Z'

$result = Get-ReviewTriggerRateLimitStatus `
-TriggeredAt @(
[datetimeoffset]'2026-06-04T10:00:00Z',
[datetimeoffset]'2026-06-04T08:00:00Z',
[datetimeoffset]'2026-06-03T13:00:00Z'
) `
-Now $now

$result.Allowed | Should -BeFalse
$result.Reason | Should -Be 'rerun-quota-exhausted'
$result.RecentCount | Should -Be 3
}

It 'ignores trigger history older than the window' {
$now = [datetimeoffset]'2026-06-04T12:00:00Z'

$result = Get-ReviewTriggerRateLimitStatus `
-TriggeredAt @(
[datetimeoffset]'2026-06-04T10:00:00Z',
[datetimeoffset]'2026-06-03T11:00:00Z',
[datetimeoffset]'2026-06-02T12:00:00Z'
) `
-Now $now

$result.Allowed | Should -BeTrue
$result.Reason | Should -Be 'allowed'
$result.RecentCount | Should -Be 1
}
}
Loading
Loading