Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
39 changes: 34 additions & 5 deletions .github/pr-review/pr-preflight.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
4. **Classify files** — separate fix files from test files, identify test type (UI / Device / Unit)
5. **Document edge cases** — from comments mentioning "what about...", "does this work with..."
6. **Record PR's fix** in Fix Candidates table (pending validation)
7. **Identify impacted UI test categories** — analyze which UI controls could be affected by this PR (see below)

```bash
# Fetch PR metadata
Expand All @@ -35,15 +36,43 @@ gh pr view XXXXX --json comments --jq '.comments[] | select(.body | contains("Fi

---

## Part B: Code Review (Step 7)
## Step 7: Identify Impacted UI Test Categories

After classifying files, determine which UI test categories could be affected by the PR changes. This enables targeted UI test runs instead of running the full matrix (~2h).

**How to identify categories:**
1. Look at the **controls modified** in the PR (e.g., changes to `Button` handler → `Button` category)
2. Consider **indirect impacts** (e.g., a layout change could affect `Layout`, `CollectionView`, `ListView`)
3. Check the **issue description** for mentions of specific controls
4. Consider **platform-specific impacts** (e.g., iOS SafeArea changes → `SafeAreaEdges`)

**Available categories** (from `UITestCategories.cs`):
`Accessibility`, `ActionSheet`, `ActivityIndicator`, `Animation`, `Border`, `BoxView`, `Brush`, `Button`, `CarouselView`, `Cells`, `CheckBox`, `CollectionView`, `ContextActions`, `DatePicker`, `Dispatcher`, `DisplayAlert`, `DragAndDrop`, `Editor`, `Effects`, `Entry`, `Essentials`, `FlyoutPage`, `Focus`, `Fonts`, `Frame`, `Gestures`, `GraphicsView`, `Image`, `ImageButton`, `IndicatorView`, `InputTransparent`, `IsEnabled`, `IsVisible`, `Label`, `Layout`, `Lifecycle`, `ListView`, `ManualReview`, `Maps`, `Navigation`, `Page`, `Performance`, `Picker`, `ProgressBar`, `RadioButton`, `RefreshView`, `SafeAreaEdges`, `ScrollView`, `SearchBar`, `Shadow`, `Shape`, `Shell`, `Slider`, `SoftInput`, `Stepper`, `Switch`, `SwipeView`, `TabbedPage`, `TableView`, `TimePicker`, `TitleView`, `ToolbarItem`, `Triggers`, `ViewBaseTests`, `VisualStateManager`, `WebView`, `Window`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't include this hardcoded list does this still work? Like if we add a link to the file with all the categories it seems like AI can just read through to that?


**Output file:**
```bash
mkdir -p CustomAgentLogsTmp/PRState/{PRNumber}/PRAgent/uitests
```

Write `ai-categories.md`:
```markdown
Button — PR modifies ButtonHandler click event logic
Layout — Changes to StackLayout could affect child arrangement
```

One category per line, followed by ` — ` and a brief justification. Write `NONE` if the PR has no UI impact (e.g., docs-only, build scripts, backend-only changes).

---

## Part B: Code Review (Step 8)

> **Purpose:** Perform deep code analysis using the `code-review` skill to surface correctness issues, safety concerns, and MAUI convention violations BEFORE Try-Fix explores alternatives. These findings guide Try-Fix models toward higher-quality fixes.

> **🚨 Independence-first requirement:** Step 7 MUST be invoked as a **separate sub-agent** (via the `task` tool with `agent_type: "general-purpose"`) so the code-review skill can form its assessment from the code BEFORE reading any PR narrative. The sub-agent receives ONLY the PR number — not the context gathered in Part A. This prevents anchoring bias.
> **🚨 Independence-first requirement:** Step 8 MUST be invoked as a **separate sub-agent** (via the `task` tool with `agent_type: "general-purpose"`) so the code-review skill can form its assessment from the code BEFORE reading any PR narrative. The sub-agent receives ONLY the PR number — not the context gathered in Part A. This prevents anchoring bias.
>
> **Validation constraint:** The Step 7 prompt MUST NOT contain issue titles, root-cause descriptions, bug summaries, or any Part A content — only `PR #XXXXX`. If you find yourself adding context "to help" the sub-agent, you are violating independence-first.
> **Validation constraint:** The Step 8 prompt MUST NOT contain issue titles, root-cause descriptions, bug summaries, or any Part A content — only `PR #XXXXX`. If you find yourself adding context "to help" the sub-agent, you are violating independence-first.

7. **Invoke the code-review skill as a sub-agent:**
8. **Invoke the code-review skill as a sub-agent:**

Use the `task` tool to launch a separate agent. The prompt MUST NOT contain issue titles, root-cause descriptions, or any Part A context — only the PR number.

Expand All @@ -69,7 +98,7 @@ gh pr view XXXXX --json comments --jq '.comments[] | select(.body | contains("Fi
5. Check CI status
6. Blast radius, failure-mode probing, and verdict

**If Step 7 fails, times out, or returns malformed output:**
**If Step 8 fails, times out, or returns malformed output:**
- Write `pre-flight/code-review.md` with: `## Code Review: SKIPPED\n\nReason: {failure description}`
- Set verdict to `SKIPPED` in the Code Review Summary section of `content.md`
- Omit `hints` from Try-Fix prompts (the `hints` field becomes optional when code review is unavailable)
Expand Down
157 changes: 140 additions & 17 deletions .github/scripts/Review-PR.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,51 @@ function Invoke-CopilotStep {
return $exitCode
}

# ═════════════════════════════════════════════════════════════════════════════
# STEP 0.5: DETECT UI Test Categories (detection only — no pipeline trigger)
# ═════════════════════════════════════════════════════════════════════════════

Write-Host ""
Write-Host "╔═══════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
Write-Host "║ STEP 0.5: DETECT UI TEST CATEGORIES ║" -ForegroundColor Cyan
Write-Host "╚═══════════════════════════════════════════════════════════╝" -ForegroundColor Cyan

$uitestCategories = ""

$detectScript = Join-Path $RepoRoot "eng/scripts/detect-ui-test-categories.ps1"
if (Test-Path $detectScript) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Moderate — 2/3 consensus | Variable name shadowing

$detectScript is defined here for detect-ui-test-categories.ps1, then reassigned at line ~499 to Detect-TestsInDiff.ps1. Both live in the same script scope. Future edits could accidentally reference the wrong script path.

Recommendation: Use distinct names — e.g., $uitestDetectScript (Step 0.5) and $diffDetectScript (Step 1).

try {
$detectOutput = & pwsh -NoProfile -File $detectScript -PrNumber "$PRNumber" 2>&1

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MODERATE — Tier 3 AI categories is dead code in this workflow (3/3 after dispute)

Step 0.5 calls the detect script with only -PrNumber, but the script's -AiCategories parameter (Tier 3, detect script lines 365-371) is never populated. The AI pre-flight runs later in Step 2 and writes ai-categories.md (per pr-preflight.md Step 7), but nothing reads that file and re-invokes detection.

The detection script is correctly wired for AzDO pipeline use (where -AiCategories could be passed), but in the Review-PR.ps1 local workflow, Tier 3 never executes.

Fix: Either (a) move Step 0.5 after Step 2 and read ai-categories.md, or (b) document that -AiCategories is reserved for pipeline use and remove the Step 7 instruction from pr-preflight.md to avoid confusion.

Comment on lines +451 to +454

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 CRITICAL — Step 0.5 leaves repo in detached HEAD, corrupting Step 1 gate (3/3 reviewers)

The detect script is called with -PrNumber, which triggers its manual-PR-test path. That path does git checkout --quiet $headSha (detect script line 127), leaving the working tree in detached HEAD at the PR's raw commit. There is no git checkout $reviewBranch between Step 0.5 (line 483) and Step 1 (line 486). The next branch restore is at line 670, after the gate has completed.

Since the review branch is a squash-merge onto main (different SHA), Step 1's gate runs against the wrong tree state.

Fix: Add git checkout $reviewBranch 2>$null | Out-Null immediately after the Step 0.5 try/catch block (after line 483).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 CRITICAL — Step 0.5 leaves repo in detached HEAD, corrupting Step 1 gate (Flagged by: 3/3 reviewers)

The detect script is called with -PrNumber, which triggers its manual-PR-test path. That path does git checkout --quiet $headSha (detect script line 127), leaving the working tree in detached HEAD at the PR's raw commit. There is no git checkout $reviewBranch between Step 0.5 (line 483) and Step 1 (line 486). The next branch restore is at line 670, after the gate has completed.

Since the review branch is a squash-merge onto main (different SHA), Step 1's gate runs against the wrong tree state.

Fix: Add git checkout $reviewBranch 2>$null | Out-Null immediately after the Step 0.5 try/catch block (after line 483).

$detectOutput | ForEach-Object { Write-Host " $_" }

foreach ($line in $detectOutput) {
$lineStr = $line.ToString()
if ($lineStr -match 'UITestCategoryList;isOutput=true\](.+)$') {
$uitestCategories = $Matches[1]
}
}

if ([string]::IsNullOrWhiteSpace($uitestCategories) -or $uitestCategories -eq 'NONE') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Warning — Empty output conflated with "NONE" (3/3 consensus)

This condition treats IsNullOrWhiteSpace($uitestCategories) identically to $uitestCategories -eq 'NONE', but they mean different things:

  • Empty/null: The detect script returned without emitting UITestCategoryList — this means "run the full matrix" (e.g., $touchesControls is true but no specific categories mapped, or a non-PR build, or label run-all-uitests)
  • NONE: The script explicitly determined no UI tests are needed

Both currently produce "No UI test categories detected" which misleads reviewers — a PR touching src/Controls/ would be reported as needing no UI tests when the intent is the opposite.

Recommendation: Distinguish the two cases:

if ($uitestCategories -eq 'NONE') {
    Write-Host "  i️ No UI test categories needed" -ForegroundColor DarkGray
} elseif ([string]::IsNullOrWhiteSpace($uitestCategories)) {
    Write-Host "  i️ Full UI test matrix (no specific categories detected)" -ForegroundColor DarkGray
} else {
    Write-Host "  🎯 Detected categories: $uitestCategories" -ForegroundColor Green
}

Write-Host " ℹ️ No UI test categories detected" -ForegroundColor DarkGray
} else {
Write-Host " 🎯 Detected categories: $uitestCategories" -ForegroundColor Green

Copilot AI Apr 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

detect-ui-test-categories.ps1 uses an empty UITestCategoryList output to mean “run the full matrix” (it returns without setting the variable). Here, an empty result is treated as “no UI test categories detected”, which is misleading in the common fallback-to-all case. Treat empty as “ALL/full matrix” and reserve NONE for “skip all UI tests”.

Copilot uses AI. Check for mistakes.
}

# Write detection result for AI summary
$uitestOutputDir = Join-Path $RepoRoot "CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/uitests"
New-Item -ItemType Directory -Force -Path $uitestOutputDir | Out-Null
if ([string]::IsNullOrWhiteSpace($uitestCategories) -or $uitestCategories -eq 'NONE') {
"No UI test categories detected for this PR." | Set-Content (Join-Path $uitestOutputDir "content.md") -Encoding UTF8
} else {
"**Detected UI test categories:** ``$uitestCategories``" | Set-Content (Join-Path $uitestOutputDir "content.md") -Encoding UTF8
}
} catch {
Write-Host " ⚠️ Category detection failed (non-fatal): $_" -ForegroundColor Yellow
}
} else {
Write-Host " ⚠️ detect-ui-test-categories.ps1 not found" -ForegroundColor Yellow
}

# ═════════════════════════════════════════════════════════════════════════════
# STEP 1: Gate - Test Before and After Fix (script, no copilot agent)
# ═════════════════════════════════════════════════════════════════════════════
Expand All @@ -452,16 +497,61 @@ New-Item -ItemType Directory -Force -Path $gateOutputDir | Out-Null
# Detect tests in PR
Write-Host " 🔍 Detecting tests in PR #$PRNumber..." -ForegroundColor Cyan
$detectScript = Join-Path $PSScriptRoot "shared/Detect-TestsInDiff.ps1"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Suggestion — $detectScript variable shadowed (2/3 consensus)

$detectScript was already assigned at line 451 (pointing to eng/scripts/detect-ui-test-categories.ps1). Here it's reassigned to shared/Detect-TestsInDiff.ps1. While PowerShell scoping makes this work, reusing the same variable name for semantically different scripts creates confusion when reading or debugging.

Recommendation: Use a distinct name like $testDetectScript or $detectTestsScript for clarity.

& pwsh -NoProfile -Command "& '$detectScript' -PRNumber $PRNumber" 2>&1 | ForEach-Object { Write-Host " $_" }
if (Test-Path $detectScript) {
$detectScript = (Resolve-Path $detectScript).Path
& pwsh -NoProfile -File $detectScript -PRNumber $PRNumber 2>&1 | ForEach-Object { Write-Host " $_" }
} else {
Write-Host " ⚠️ Detect-TestsInDiff.ps1 not found at $detectScript" -ForegroundColor Yellow
}

# Determine platform for gate
$gatePlatform = if ($Platform) { $Platform } else { "android" }
Write-Host " 🧪 Running gate on platform: $gatePlatform" -ForegroundColor Cyan

$verifyScript = Join-Path $PSScriptRoot "../skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1"
$gateOutput = & pwsh -NoProfile -File "$verifyScript" -Platform $gatePlatform -PRNumber $PRNumber -RequireFullVerification 2>&1
$gateExitCode = $LASTEXITCODE
$gateOutput | ForEach-Object { Write-Host " $_" }
$verifyScript = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot "../skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1"))
if (-not (Test-Path $verifyScript)) {
Write-Host " ❌ verify-tests-fail.ps1 not found at: $verifyScript" -ForegroundColor Red
$gateResult = "FAILED"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Suggestion — Dead $gateResult assignment (2/3 consensus)

$gateResult = "FAILED" is assigned here in the "script not found" branch, but it's immediately overwritten by the switch ($gateExitCode) at line 559 (which falls through to the default { "FAILED" } case anyway). The explicit assignment has no effect.

Recommendation: Either remove this line, or restructure so the "not found" path skips the switch entirely (e.g., by using an early return or placing the switch inside the else block).

$gateExitCode = 1
$gateOutput = @("verify-tests-fail.ps1 not found at: $verifyScript")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MODERATE — Gate retry only triggers if report file already exists (Flagged by: 2/3 reviewers)

The ENV ERROR check reads from verification-report.md. If the verify script fails before writing the report (e.g., emulator fails to start, ADB crash during setup), Test-Path $gateContentFile returns false, $isEnvError stays false, and the loop breaks — no retry.

Fix: Also treat "nonzero exit + missing report file" as a retryable environment failure:

if ($gateExitCode -ne 0) {
    if ((Test-Path $gateContentFile) -and (Get-Content $gateContentFile -Raw) -match 'ENV ERROR') {
        $isEnvError = $true
    } elseif (-not (Test-Path $gateContentFile)) {
        $isEnvError = $true  # No report = likely infra failure
    }
}

} else {

$maxGateAttempts = 3
$gateExitCode = 1
$gateOutput = @()

for ($gateAttempt = 1; $gateAttempt -le $maxGateAttempts; $gateAttempt++) {
if ($gateAttempt -gt 1) {
Write-Host " 🔄 Retry $gateAttempt/$maxGateAttempts — previous attempt hit environment error" -ForegroundColor Yellow
}
$gateOutput = & pwsh -NoProfile -File "$verifyScript" -Platform $gatePlatform -PRNumber $PRNumber -RequireFullVerification 2>&1
$gateExitCode = $LASTEXITCODE
$gateOutput | ForEach-Object { Write-Host " $_" }

# Check if this was an ENV ERROR (emulator timeout, ADB failure, etc.)
$gateContentFile = Join-Path $gateOutputDir "verify-tests-fail/verification-report.md"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Moderate — 2/3 consensus | Stale report file across retries

$gateContentFile (verification-report.md) is read each retry iteration to detect ENV ERROR, but is never cleared between attempts. If attempt 1 writes an ENV ERROR report and attempt 2 crashes before overwriting it, the stale file causes misclassification of the second attempt.

Recommendation: Delete or rename $gateContentFile at the start of each loop iteration before invoking the verify script:

if (Test-Path $gateContentFile) { Remove-Item $gateContentFile -Force }

$isEnvError = $false
if ($gateExitCode -ne 0 -and (Test-Path $gateContentFile)) {
$gateContent = Get-Content $gateContentFile -Raw -ErrorAction SilentlyContinue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Warning — Stale report file not cleared between retry attempts (3/3 consensus)

The retry loop resets $isEnvError = $false at line 533 (semantically correct), but never deletes $gateContentFile (verification-report.md) before re-invoking the verify script. If attempt N crashes before writing a new report, Test-Path $gateContentFile still returns $true from attempt N-1's file, the old "ENV ERROR" content is re-read, and an unnecessary retry is triggered.

Recommendation: Delete the report file at the top of each loop iteration:

for ($gateAttempt = 1; $gateAttempt -le $maxGateAttempts; $gateAttempt++) {
    # Clear previous attempt's report to avoid stale classification
    $gateContentFile = Join-Path $gateOutputDir "verify-tests-fail/verification-report.md"
    Remove-Item $gateContentFile -ErrorAction SilentlyContinue
    # ... rest of loop
}

if ($gateContent -match 'ENV ERROR') {
$isEnvError = $true
Write-Host " ⚠️ Environment error detected (attempt $gateAttempt/$maxGateAttempts)" -ForegroundColor Yellow
}
}

if ($gateExitCode -eq 0 -or -not $isEnvError) {
break # Real pass or real failure — don't retry
}
if ($gateAttempt -lt $maxGateAttempts) {
Write-Host " ⏳ Waiting 30s before retry..." -ForegroundColor DarkGray
Start-Sleep -Seconds 30
}
}
if ($isEnvError) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Moderate — 2/3 consensus | $isEnvError post-loop invariant is non-obvious

The check if ($isEnvError) after the loop is technically correct (it can only be $true here if ALL iterations were env errors, since non-env-error iterations break). However, the variable name suggests "any" rather than "all", and if $maxGateAttempts is ever 0, $isEnvError would be undefined ($null → falsy, so safe but fragile).

Recommendation: Either add a clarifying comment explaining the invariant, or use a counter: if ($envErrorCount -eq $maxGateAttempts) { ... }.

Write-Host " ⚠️ All $maxGateAttempts gate attempts hit environment errors" -ForegroundColor Yellow
}

} # end else (verify script exists)

# Exit code: 0 = passed, 1 = verification failed, 2 = no tests detected
$gateResult = switch ($gateExitCode) {
Expand All @@ -472,30 +562,61 @@ $gateResult = switch ($gateExitCode) {
$gateColor = switch ($gateResult) { "PASSED" { "Green" } "SKIPPED" { "Yellow" } default { "Red" } }
Write-Host " 📁 Gate result: $gateResult" -ForegroundColor $gateColor

# Copy the verification report to gate/content.md if it exists
# Copy the verification report to gate/content.md (always overwrite — the report is the source of truth)
$verificationReport = Join-Path $gateOutputDir "verify-tests-fail/verification-report.md"
# Capture last meaningful lines from gate output for fallback diagnostics
$gateLogTail = @($gateOutput | ForEach-Object { $_.ToString() } | Where-Object { $_ -match '\S' } | Select-Object -Last 20) -join "`n"

if (Test-Path $verificationReport) {
Copy-Item $verificationReport (Join-Path $gateOutputDir "content.md") -Force
$reportContent = Get-Content $verificationReport -Raw -ErrorAction SilentlyContinue
if ($reportContent) {
# Strip broken "Test Summary" blocks with empty values (from old verify script format)
$reportContent = $reportContent -replace '(?s)\*\*Test Summary:\*\*\s*\n- Total:\s*\n- Passed:\s*(True|False)\s*\n- Failed:\s*\n- Skipped:\s*\n?', ''
$reportContent | Set-Content (Join-Path $gateOutputDir "content.md") -Encoding UTF8
} else {
# Report exists but has bad format — generate fallback with logs
Write-Host " ⚠️ Verification report has invalid format — using fallback" -ForegroundColor Yellow
$resultIcon = switch ($gateResult) { "PASSED" { "✅" } "SKIPPED" { "⚠️" } default { "❌" } }
@"
### Gate Result: $resultIcon $gateResult

**Platform:** $($gatePlatform.ToUpper())

<details>
<summary>Gate output log</summary>

``````
$gateLogTail
``````

</details>
"@ | Set-Content (Join-Path $gateOutputDir "content.md") -Encoding UTF8
}
} elseif (-not (Test-Path (Join-Path $gateOutputDir "content.md"))) {
# Create gate content based on result
if ($gateResult -eq "SKIPPED") {
$skipContent = @"
@"
### Gate Result: ⚠️ SKIPPED

No tests were detected in this PR.

**Recommendation:** Add tests to verify the fix using the ``write-tests-agent``:
**Recommendation:** Add tests to verify the fix using the ``write-tests-agent``.
"@ | Set-Content (Join-Path $gateOutputDir "content.md") -Encoding UTF8
} else {
$resultIcon = switch ($gateResult) { "PASSED" { "✅" } default { "❌" } }
@"
### Gate Result: $resultIcon $gateResult

**Platform:** $($gatePlatform.ToUpper())

<details>
<summary>Gate output log</summary>

``````
@copilot write tests for this PR
$gateLogTail
``````

The agent will analyze the issue, determine the appropriate test type (UI test, device test, unit test, or XAML test), and create tests that verify the fix.
"@
$skipContent | Set-Content (Join-Path $gateOutputDir "content.md")
} else {
"### Gate Result: $(if ($gateExitCode -eq 0) { '✅ PASSED' } else { '❌ FAILED' })`n`n**Platform:** $gatePlatform" |
Set-Content (Join-Path $gateOutputDir "content.md")
</details>
"@ | Set-Content (Join-Path $gateOutputDir "content.md") -Encoding UTF8
}
}

Expand Down Expand Up @@ -566,8 +687,10 @@ $autonomousRules

**Gate result (already completed in a prior step):** $gateStatusForPrompt
Do NOT re-run gate verification. The gate phase is handled separately.
⚠️ Do NOT create or overwrite ``gate/content.md`` — it is already generated by the gate script with detailed test output.

📁 Write phase output to ``CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/{phase}/content.md``
(phases: pre-flight, try-fix, report — NOT gate)
"@

Invoke-CopilotStep -StepName "STEP 2: PR REVIEW" -Prompt $step2Prompt | Out-Null
Expand Down
1 change: 1 addition & 0 deletions .github/scripts/post-ai-summary-comment.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ if (-not (Test-Path $PRAgentDir)) {
}

$phases = [ordered]@{
"uitests" = @{ File = "uitests/content.md"; Icon = "🧪"; Title = "UI Tests — Category Detection" }
"pre-flight" = @{ File = "pre-flight/content.md"; Icon = "🔍"; Title = "Pre-Flight — Context & Validation" }
"code-review" = @{ File = "pre-flight/code-review.md"; Icon = "🔬"; Title = "Code Review — Deep Analysis" }
"try-fix" = @{ File = "try-fix/content.md"; Icon = "🔧"; Title = "Fix — Analysis & Comparison" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1259,7 +1259,7 @@ function Write-MarkdownReport {
$lines += "</details>"
}

# ── Failure details (only if something went wrong) ──
# ── Failure details (shown directly — not collapsed) ──
$failureLines = @()
foreach ($r in $WithoutFixResultsList) {
if ($r.Passed) {
Expand All @@ -1272,7 +1272,7 @@ function Write-MarkdownReport {
$failureLines += "- ❌ **$($r.TestName)** FAILED with fix (should pass)"
if ($r.FailureReason) { $failureLines += " - ``$($r.FailureReason)``" }
if ($r.FailureMessage) {
$msg = if ($r.FailureMessage.Length -gt 200) { $r.FailureMessage.Substring(0, 200) + "..." } else { $r.FailureMessage }
$msg = if ($r.FailureMessage.Length -gt 300) { $r.FailureMessage.Substring(0, 300) + "..." } else { $r.FailureMessage }
$failureLines += " - ``$msg``"
}
}
Expand All @@ -1281,12 +1281,9 @@ function Write-MarkdownReport {

if ($failureLines.Count -gt 0) {
$lines += ""
$lines += "<details>"
$lines += "<summary>⚠️ Issues found</summary>"
$lines += "#### ⚠️ Failure Details"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Suggestion — Failure details no longer collapsible (2/3 consensus)

Removing <details>/<summary> makes failure information always visible, which improves discoverability. However, when many tests fail simultaneously, this section can become quite long and push the gate result summary off-screen in PR comments.

Consideration: A middle ground could be collapsing only when $failureLines.Count exceeds a threshold (e.g., 5 tests):

if ($failureLines.Count -gt 5) {
    $lines += "<details>"
    $lines += "<summary>⚠️ Failure Details ($($failureLines.Count) tests)</summary>"
}
$lines += ""
$lines += ($failureLines -join "`n")
if ($failureLines.Count -gt 5) { $lines += "</details>" }

$lines += ""
$lines += ($failureLines -join "`n")
$lines += ""
$lines += "</details>"
}

# ── Fix files (collapsible) ──
Expand Down
Loading
Loading