diff --git a/.github/agents/learn-from-pr.md b/.github/agents/learn-from-pr.md
index ce0a506c3af3..759d1db88328 100644
--- a/.github/agents/learn-from-pr.md
+++ b/.github/agents/learn-from-pr.md
@@ -89,7 +89,6 @@ Present a summary:
| Situation | Action |
|-----------|--------|
| PR not found | Ask user to verify PR number |
-| No session markdown | Proceed with PR diff analysis only |
| Target file doesn't exist | Create if instruction/architecture doc, skip if code |
| Duplicate content exists | Skip, note in report |
| Unclear where to add | Ask user for guidance |
diff --git a/.github/agents/pr.md b/.github/agents/pr.md
index d5c55dbc80de..e4fdc4163c73 100644
--- a/.github/agents/pr.md
+++ b/.github/agents/pr.md
@@ -1,6 +1,6 @@
---
name: pr
-description: Sequential 4-phase workflow for GitHub issues - Pre-Flight, Gate, Fix, Report. Phases MUST complete in order. State tracked in CustomAgentLogsTmp/PRState/
+description: Sequential 4-phase workflow for GitHub issues - Pre-Flight, Gate, Fix, Report. Phases MUST complete in order.
---
# .NET MAUI Pull Request Agent
@@ -48,13 +48,13 @@ After Gate passes, read `.github/agents/pr/post-gate.md` for **Phases 3-4**.
- Follow Templates EXACTLY (no `open` attributes, no "improvements")
- No Direct Git Commands (use `gh pr diff/view`, let scripts handle files)
- Use Skills' Scripts (don't bypass with manual commands)
-- Stop on Environment Blockers (strict retry limits, report and ask user)
+- Stop on Environment Blockers (retry once, then skip and continue autonomously)
- Multi-Model Configuration (5 models for Phase 4)
- Platform Selection (must be affected AND available on host)
**Key points:**
- ❌ Never run `git checkout`, `git switch`, `git stash`, `git reset` - agent is always on correct branch
-- ❌ Never continue after environment blocker - STOP and ask user
+- ❌ Never stop and ask user - use best judgment to skip blocked phases and continue
- ❌ Never mark phase ✅ with [PENDING] fields remaining
Phase 3 uses a 5-model exploration workflow. See `post-gate.md` for detailed instructions after Gate passes.
@@ -65,8 +65,6 @@ Phase 3 uses a 5-model exploration workflow. See `post-gate.md` for detailed ins
> **⚠️ SCOPE**: Document only. No code analysis. No fix opinions. No running tests.
-**🚨 CRITICAL: Create the state file BEFORE doing anything else.**
-
### ❌ Pre-Flight Boundaries (What NOT To Do)
| ❌ Do NOT | Why | When to do it |
@@ -79,141 +77,11 @@ Phase 3 uses a 5-model exploration workflow. See `post-gate.md` for detailed ins
### ✅ What TO Do in Pre-Flight
-- Create/check state file
- Read issue description and comments
- Note platforms affected (from labels)
- Identify files changed (if PR exists)
- Document disagreements and edge cases from comments
-### Step 0: Check for Existing State File or Create New One
-
-**State file location**: `CustomAgentLogsTmp/PRState/pr-XXXXX.md`
-
-**Naming convention:**
-- If starting from **PR #12345** → Name file `pr-12345.md` (use PR number)
-- If starting from **Issue #33356** (no PR yet) → Name file `pr-33356.md` (use issue number as placeholder)
-- When PR is created later → Rename to use actual PR number
-
-```bash
-# Check if state file exists
-mkdir -p CustomAgentLogsTmp/PRState
-if [ -f "CustomAgentLogsTmp/PRState/pr-XXXXX.md" ]; then
- echo "State file exists - resuming session"
- cat CustomAgentLogsTmp/PRState/pr-XXXXX.md
-else
- echo "Creating new state file"
-fi
-```
-
-**If the file EXISTS**: Read it to determine your current phase and resume from there. Look for:
-- Which phase has `▶️ IN PROGRESS` status - that's where you left off
-- Which phases have `✅ PASSED` status - those are complete
-- Which phases have `⏳ PENDING` status - those haven't started
-
-**If the file does NOT exist**: Create it with the template structure:
-
-```markdown
-# PR Review: #XXXXX - [Issue Title TBD]
-
-**Date:** [TODAY] | **Issue:** [#XXXXX](https://github.com/dotnet/maui/issues/XXXXX) | **PR:** [#YYYYY](https://github.com/dotnet/maui/pull/YYYYY) or None
-
-## ⏳ Status: IN PROGRESS
-
-| Phase | Status |
-|-------|--------|
-| Pre-Flight | ▶️ IN PROGRESS |
-| 🚦 Gate | ⏳ PENDING |
-| 🔧 Fix | ⏳ PENDING |
-| 📋 Report | ⏳ PENDING |
-
----
-
-
-📋 Issue Summary
-
-[From issue body]
-
-**Steps to Reproduce:**
-1. [Step 1]
-2. [Step 2]
-
-**Platforms Affected:**
-- [ ] iOS
-- [ ] Android
-- [ ] Windows
-- [ ] MacCatalyst
-
-
-
-
-📁 Files Changed
-
-| File | Type | Changes |
-|------|------|---------|
-| `path/to/fix.cs` | Fix | +X lines |
-| `path/to/test.cs` | Test | +Y lines |
-
-
-
-
-💬 PR Discussion Summary
-
-**Key Comments:**
-- [Notable comments from issue/PR discussion]
-
-**Reviewer Feedback:**
-- [Key points from review comments]
-
-**Disagreements to Investigate:**
-| File:Line | Reviewer Says | Author Says | Status |
-|-----------|---------------|-------------|--------|
-
-**Author Uncertainty:**
-- [Areas where author expressed doubt]
-
-
-
-
-🚦 Gate - Test Verification
-
-**Status**: ⏳ PENDING
-
-- [ ] Tests FAIL (bug reproduced)
-
-**Result:** [PENDING]
-
-
-
-
-🔧 Fix Candidates
-
-**Status**: ⏳ PENDING
-
-| # | Source | Approach | Test Result | Files Changed | Notes |
-|---|--------|----------|-------------|---------------|-------|
-| PR | PR #XXXXX | [PR's approach - from Pre-Flight] | ⏳ PENDING (Gate) | [files] | Original PR - validated by Gate |
-
-**Note:** try-fix candidates (1, 2, 3...) are added during Phase 3. PR's fix is reference only.
-
-**Exhausted:** No
-**Selected Fix:** [PENDING]
-
-
-
----
-
-**Next Step:** After Gate passes, read `.github/agents/pr/post-gate.md` and continue with phases 3-4.
-```
-
-This file:
-- Serves as your TODO list for all phases
-- Tracks progress if interrupted
-- Must exist before you start gathering context
-- **Always include when saving changes** (to `CustomAgentLogsTmp/PRState/`)
-- **Phases 3-4 sections are added AFTER Gate passes** (see `pr/post-gate.md`)
-
-**Then gather context and update the file as you go.**
-
### Step 1: Gather Context (depends on starting point)
**If starting from a PR:**
@@ -258,11 +126,9 @@ gh pr view XXXXX --json comments --jq '.comments[] | select(.body | contains("Fi
- Contains structured analysis (Root Cause, Platform Comparison, etc.)
**If prior agent review found:**
-1. **Extract and use as state file content** - The review IS the completed state
-2. Parse the phase statuses to determine what's already done
-3. Import all findings (fix candidates, test results)
-4. Update your local state file with this content
-5. Resume from whichever phase is not yet complete (or report as done)
+1. Parse the phase statuses to determine what's already done
+2. Import all findings (fix candidates, test results)
+3. Resume from whichever phase is not yet complete (or report as done)
**Do NOT:**
- Start from scratch if a complete review already exists
@@ -271,8 +137,6 @@ gh pr view XXXXX --json comments --jq '.comments[] | select(.body | contains("Fi
### Step 3: Document Key Findings
-Update the state file `CustomAgentLogsTmp/PRState/pr-XXXXX.md`:
-
**If PR exists** - Document disagreements and reviewer feedback:
| File:Line | Reviewer Says | Author Says | Status |
|-----------|---------------|-------------|--------|
@@ -308,21 +172,12 @@ The test result will be updated to `✅ PASS (Gate)` after Gate passes.
### Step 5: Complete Pre-Flight
-**🚨 MANDATORY: Update state file**
-
-**Update state file** - Change Pre-Flight status and populate with gathered context:
-1. Change Pre-Flight status from `▶️ IN PROGRESS` to `✅ COMPLETE`
-2. Fill in issue summary, platforms affected, regression info
-3. Add edge cases and any disagreements (if PR exists)
-4. Change 🚦 Gate status to `▶️ IN PROGRESS`
-
-**Before marking ✅ COMPLETE, verify state file contains:**
-- [ ] Issue summary filled (not [PENDING])
-- [ ] Platform checkboxes marked
-- [ ] Files Changed table populated (if PR exists)
-- [ ] PR Discussion Summary documented (if PR exists)
-- [ ] All [PENDING] placeholders replaced
-- [ ] State file saved
+Verify the following before proceeding:
+- [ ] Issue summary captured
+- [ ] Platform information noted
+- [ ] Files changed identified (if PR exists)
+- [ ] PR discussion summarized (if PR exists)
+- [ ] **Write phase output to `CustomAgentLogsTmp/PRState/{PRNumber}/PRAgent/pre-flight/content.md`** (see SHARED-RULES.md "Phase Output Artifacts")
---
@@ -356,7 +211,7 @@ find src/Controls/tests -name "*XXXXX*" -type f 2>/dev/null
**🚨 CRITICAL: Choose a platform that is BOTH affected by the bug AND available on the current host.**
**Identify affected platforms** from Pre-Flight:
-- Check the "Platforms Affected" checkboxes in the state file
+- Check the platforms affected from Pre-Flight context
- Check issue labels (e.g., `platform/iOS`, `platform/Android`)
- Check which platform-specific files the PR modifies
@@ -416,18 +271,11 @@ See `.github/skills/verify-tests-fail-without-fix/SKILL.md` for full skill docum
### Complete 🚦 Gate
-**🚨 MANDATORY: Update state file**
-
-**Update state file**:
-1. Fill in **Result**: `PASSED ✅`
-2. Change 🚦 Gate status to `✅ PASSED`
-3. Proceed to Phase 3
-
-**Before marking ✅ PASSED, verify state file contains:**
-- [ ] Result shows PASSED ✅ or FAILED ❌
+Verify the following before proceeding:
+- [ ] Test result documented (PASSED ✅ or FAILED ❌)
- [ ] Test behavior documented
- [ ] Platform tested noted
-- [ ] State file saved
+- [ ] **Write phase output to `CustomAgentLogsTmp/PRState/{PRNumber}/PRAgent/gate/content.md`** (see SHARED-RULES.md "Phase Output Artifacts")
---
@@ -445,7 +293,6 @@ See `.github/skills/verify-tests-fail-without-fix/SKILL.md` for full skill docum
- ❌ **Looking at implementation code during Pre-Flight** - Just gather issue/PR context
- ❌ **Forming opinions on the fix during Pre-Flight** - That's Phase 3
- ❌ **Running tests during Pre-Flight** - That's Phase 2 (Gate)
-- ❌ **Not creating state file first** - ALWAYS create state file before gathering context
- ❌ **Skipping to Phase 3** - Gate MUST pass first
## Common Gate Mistakes
diff --git a/.github/agents/pr/PLAN-TEMPLATE.md b/.github/agents/pr/PLAN-TEMPLATE.md
index 6e9836d7d9fd..2cdeafd2ebd7 100644
--- a/.github/agents/pr/PLAN-TEMPLATE.md
+++ b/.github/agents/pr/PLAN-TEMPLATE.md
@@ -12,7 +12,7 @@
## 🚨 Critical Rules (Summary)
See `SHARED-RULES.md` for complete details. Key points:
-- **Environment Blockers**: STOP immediately, report, ask user (strict retry limits)
+- **Environment Blockers**: Skip blocked phase and continue autonomously (no human operator)
- **No Git Commands**: Never checkout/switch branches - agent is always on correct branch
- **Gate via Task Agent**: Never run inline (prevents fabrication)
- **Multi-Model try-fix**: 5 models, SEQUENTIAL only
@@ -23,7 +23,6 @@ See `SHARED-RULES.md` for complete details. Key points:
## Work Plan
### Phase 1: Pre-Flight
-- [ ] Create state file: `CustomAgentLogsTmp/PRState/pr-XXXXX.md`
- [ ] Gather PR metadata (title, body, labels, author)
- [ ] Fetch and read linked issue
- [ ] Fetch PR comments and review feedback
@@ -31,8 +30,7 @@ See `SHARED-RULES.md` for complete details. Key points:
- [ ] Document platforms affected
- [ ] Classify changed files (fix vs test)
- [ ] Document PR's fix approach in Fix Candidates table
-- [ ] Update state file: Pre-Flight → ✅ COMPLETE
-- [ ] Save state file
+- [ ] **Write `PRAgent/pre-flight/content.md`**
**Boundaries:** No code analysis, no fix opinions, no test running
@@ -46,11 +44,10 @@ See `SHARED-RULES.md` for complete details. Key points:
"Run verify-tests-fail-without-fix skill
Platform: [X], TestFilter: 'IssueXXXXX', RequireFullVerification: true"
```
-- [ ] ⛔ If environment blocker: STOP, report, ask user
+- [ ] ⛔ If environment blocker: retry once, then skip and document
- [ ] Verify: Tests FAIL without fix, PASS with fix
- [ ] If Gate fails: STOP, request test fixes
-- [ ] Update state file: Gate → ✅ PASSED
-- [ ] Save state file
+- [ ] **Write `PRAgent/gate/content.md`**
### Phase 3: Fix 🔧
*(Only if Gate ✅ PASSED)*
@@ -61,7 +58,7 @@ See `SHARED-RULES.md` for complete details. Key points:
- [ ] gpt-5.2
- [ ] gpt-5.3-codex
- [ ] gemini-3-pro-preview
-- [ ] ⛔ If blocker: STOP, report, ask user
+- [ ] ⛔ If blocker: retry once, skip remaining models, proceed to Report
- [ ] Record: approach, result, files, failure analysis
**Round 2+: Cross-Pollination (MANDATORY)**
@@ -75,25 +72,23 @@ See `SHARED-RULES.md` for complete details. Key points:
- [ ] Mark Exhausted: Yes
- [ ] Compare passing candidates with PR's fix
- [ ] Select best fix (results → simplicity → robustness)
-- [ ] Update state file: Fix → ✅ COMPLETE
-- [ ] Save state file
+- [ ] **Write `PRAgent/try-fix/content.md`**
### Phase 4: Report 📋
*(Only if Phases 1-3 complete)*
- [ ] Run `pr-finalize` skill
- [ ] Generate review: root cause, candidates, recommendation
-- [ ] Post AI Summary comment (PR phases + try-fix):
+- [ ] **Write `PRAgent/report/content.md`**
+- [ ] Post AI Summary comment (auto-loads from PRAgent/*/content.md):
```bash
- pwsh .github/skills/ai-summary-comment/scripts/post-ai-summary-comment.ps1 -PRNumber XXXXX -SkipValidation
+ pwsh .github/skills/ai-summary-comment/scripts/post-ai-summary-comment.ps1 -PRNumber XXXXX
pwsh .github/skills/ai-summary-comment/scripts/post-try-fix-comment.ps1 -IssueNumber XXXXX
```
- [ ] Post PR Finalization comment (separate):
```bash
- pwsh .github/skills/ai-summary-comment/scripts/post-pr-finalize-comment.ps1 -PRNumber XXXXX -SummaryFile CustomAgentLogsTmp/PRState/pr-XXXXX.md
+ pwsh .github/skills/ai-summary-comment/scripts/post-pr-finalize-comment.ps1 -PRNumber XXXXX -SummaryFile CustomAgentLogsTmp/PRState/XXXXX/PRAgent/pr-finalize/pr-finalize-summary.md
```
-- [ ] Update state file: Report → ✅ COMPLETE
-- [ ] Save final state file
---
@@ -101,11 +96,9 @@ See `SHARED-RULES.md` for complete details. Key points:
| Phase | Key Action | Blocker Response |
|-------|------------|------------------|
-| Pre-Flight | Create state file | N/A |
-| Gate | Task agent → verify script | ⛔ STOP, report, ask |
-| Fix | Multi-model try-fix | ⛔ STOP, report, ask |
-| Report | Post via skill | ⛔ STOP, report, ask |
+| Pre-Flight | Gather context | N/A |
+| Gate | Task agent → verify script | Skip, report incomplete |
+| Fix | Multi-model try-fix | Skip remaining, proceed to Report |
+| Report | Post via skill | Document what completed |
-**State file:** `CustomAgentLogsTmp/PRState/pr-XXXXX.md`
-
-**Never:** Mark BLOCKED and continue, claim success without tests, bypass scripts
+**Never:** Claim success without tests, bypass scripts, stop and ask user
diff --git a/.github/agents/pr/SHARED-RULES.md b/.github/agents/pr/SHARED-RULES.md
index 1347d92e659a..6f9b1a0827c2 100644
--- a/.github/agents/pr/SHARED-RULES.md
+++ b/.github/agents/pr/SHARED-RULES.md
@@ -8,24 +8,114 @@ This file contains critical rules that apply across all PR agent phases. Referen
**Before changing ANY phase status to ✅ COMPLETE:**
-1. **Read the state file section** for the phase you're completing
-2. **Find ALL ⏳ PENDING and [PENDING] fields** in that section
-3. **Fill in every field** with actual content
-4. **Verify no pending markers remain** in your section
-5. **Save the state file** (it's in gitignored `CustomAgentLogsTmp/`)
-6. **Then change status** to ✅ COMPLETE
+1. **Review the phase checklist** for the phase you're completing
+2. **Verify all required items** are addressed
+3. **Write the phase output to `content.md`** (see Phase Output Artifacts below)
+4. **Then mark the phase** as ✅ COMPLETE
-**Rule:** Status ✅ means "documentation complete", not "I finished thinking about it"
+**Rule:** Status ✅ means "work complete and verified", not "I finished thinking about it"
---
-## Follow Templates EXACTLY
+## Phase Output Artifacts
-When creating state files, use the EXACT format from the documentation:
-- **Do NOT add attributes** like `open` to `` tags
-- **Do NOT "improve"** the template format
-- **Do NOT deviate** from documented structure
-- Downstream scripts depend on exact formatting (regex patterns expect specific structure)
+**After completing EACH phase, write a `content.md` file to the phase's output directory.**
+
+This is MANDATORY. The comment scripts (`post-ai-summary-comment.ps1`) read from these files to build the PR comment.
+
+### Output Directory Structure
+
+```
+CustomAgentLogsTmp/PRState/{PRNumber}/PRAgent/
+├── pre-flight/
+│ └── content.md # Written after Phase 1
+├── gate/
+│ ├── content.md # Written after Phase 2
+│ └── verify-tests-fail/ # Script output from verify-tests-fail.ps1
+├── try-fix/
+│ ├── content.md # Written after Phase 3 (summary of all attempts)
+│ └── attempt-{N}/ # Individual attempt outputs from try-fix skill
+└── report/
+ └── content.md # Written after Phase 4
+```
+
+### What Goes in Each content.md
+
+Each `content.md` should contain the **formatted phase content** — the same content that would appear inside the collapsible `` section in the PR comment.
+
+**Pre-Flight example:**
+```markdown
+**Issue:** #XXXXX - [Title]
+**Platforms Affected:** iOS, Android
+**Files Changed:** 2 implementation files, 1 test file
+
+### Key Findings
+- [Finding 1]
+- [Finding 2]
+
+### Fix Candidates
+| # | Source | Approach | Test Result | Files Changed | Notes |
+|---|--------|----------|-------------|---------------|-------|
+| PR | PR #XXXXX | [approach] | ⏳ PENDING (Gate) | `file.cs` | Original PR |
+```
+
+**Gate example:**
+```markdown
+**Result:** ✅ PASSED
+**Platform:** android
+**Mode:** Full Verification
+
+- Tests FAIL without fix ✅
+- Tests PASS with fix ✅
+```
+
+**Fix (try-fix) example:**
+```markdown
+### Fix Candidates
+| # | Source | Approach | Test Result | Files Changed | Notes |
+|---|--------|----------|-------------|---------------|-------|
+| 1 | try-fix | [approach] | ❌ FAIL | 1 file | Why: [reason] |
+| 2 | try-fix | [approach] | ✅ PASS | 2 files | Works! |
+| PR | PR #XXXXX | [approach] | ✅ PASS (Gate) | 2 files | Original PR |
+
+**Exhausted:** Yes
+**Selected Fix:** PR's fix - [Reason]
+```
+
+**Report example:**
+```markdown
+## ✅ Final Recommendation: APPROVE
+
+### Summary
+[Brief summary of the review]
+
+### Root Cause
+[Root cause analysis]
+
+### Fix Quality
+[Assessment of the fix]
+```
+
+### How to Write the File
+
+```bash
+# Create the directory (idempotent)
+mkdir -p "CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/pre-flight"
+
+# Write content (use create tool or bash)
+cat > "CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/pre-flight/content.md" << 'EOF'
+[phase content here]
+EOF
+```
+
+### Timing
+
+| Phase | When to Write |
+|-------|---------------|
+| Pre-Flight | After all context gathered and documented |
+| Gate | After verification result received from task agent |
+| Fix (try-fix) | After all try-fix models explored and best fix selected |
+| Report | After final recommendation determined |
---
@@ -65,63 +155,58 @@ When a skill provides a PowerShell script:
If you encounter an environment or system setup blocker that prevents completing a phase:
-**STOP IMMEDIATELY. Do NOT continue to the next phase.**
+### 🚨 Autonomous Execution (Default)
-### Common Blockers
+When running via `Review-PR.ps1`, there is **NO human operator** to respond to questions.
-- Missing Appium drivers (Windows, iOS, Android)
-- WinAppDriver not installed or returning errors
-- Xcode/iOS simulators not available (on Windows)
-- Android emulator not running or not configured
-- Developer Mode not enabled
-- Port conflicts (e.g., 4723 in use)
-- Missing SDKs or tools
-- Server errors (500, timeout, "unknown error occurred")
+**NEVER stop and ask the user. NEVER present options and wait for a choice. Nobody will respond.**
-### Retry Limits (STRICT ENFORCEMENT)
-
-| Blocker Type | Max Retries | Then Do |
-|--------------|-------------|---------|
-| Missing tool/driver | 1 install attempt | STOP and ask user |
-| Server errors (500, timeout) | 0 | STOP immediately and report |
-| Port conflicts | 1 (kill process) | STOP and ask user |
-| Configuration issues | 1 fix attempt | STOP and ask user |
+Instead, use your best judgment to continue autonomously:
-### When Blocked
+1. **Try ONE retry** (install missing tool, kill conflicting process, etc.)
+2. **If still blocked after one retry**, SKIP the blocked phase and continue to the next phase
+3. **Document what was skipped and why** in your report
+4. **Always prefer continuing with partial results** over stopping completely
-1. **Stop all work** - Do not proceed to the next phase
-2. **Do NOT keep troubleshooting** - After the retry limit, STOP
-3. **Report the blocker** clearly (use template below)
-4. **Ask the user** how to proceed
-5. **Wait for user response** - Do not assume or work around
+**Autonomous decision guide:**
-### Blocker Report Template
+| Blocker Type | Max Retries | Then Do |
+|--------------|-------------|---------|
+| Missing tool/driver | 1 install attempt | Skip phase, continue |
+| Server errors (500, timeout) | 1 retry | Skip phase, continue |
+| Port conflicts | 1 (kill process) | Skip phase, continue |
+| Build failures in try-fix | 2 attempts | Skip remaining try-fix models, proceed to Report |
+| Configuration issues | 1 fix attempt | Skip phase, continue |
-```
-⛔ BLOCKED: Cannot complete [Phase Name]
+**Common autonomous decisions:**
+- Gate passes but Fix phase is blocked → **Skip Fix, proceed to Report** with Gate results only
+- try-fix builds fail for multiple models → **Stop try-fix exploration, proceed to Report**
+- A specific platform fails → **Try alternative platform ONCE**, then skip if still blocked
+- Gate fails due to environment → **Report as incomplete**, proceed to Report
-**What failed:** [Step/skill that failed]
-**Blocker:** [Tool/driver/error type]
-**Error:** "[Exact error message]"
+### Interactive Mode
-**What I tried:** [List retry attempts, max 1-2]
+When running with `-Interactive` flag, you MAY ask the user for guidance on blockers.
-**I am STOPPING here. Options:**
-1. [Option for user - e.g., investigate setup manually]
-2. [Alternative platform]
-3. [Skip with documented limitation]
+### Common Blockers
-Which would you like me to do?
-```
+- Missing Appium drivers (Windows, iOS, Android)
+- WinAppDriver not installed or returning errors
+- Xcode/iOS simulators not available (on Windows)
+- Android emulator not running or not configured
+- Developer Mode not enabled
+- Port conflicts (e.g., 4723 in use)
+- Missing SDKs or tools
+- Server errors (500, timeout, "unknown error occurred")
### Never Do
- ❌ Keep trying different fixes after retry limit exceeded
-- ❌ Mark a phase as ⚠️ BLOCKED and continue to the next phase
- ❌ Claim "verification passed" when tests couldn't actually run
-- ❌ Skip device/emulator testing and proceed with code review only
- ❌ Install multiple tools/drivers without asking between each
- ❌ Spend more than 2-3 tool calls troubleshooting the same blocker
+- ❌ **Stop and present options to the user** - choose the best option yourself
+- ❌ **Wait for user response** - nobody will respond
---
@@ -148,7 +233,6 @@ Phase 4 uses these 5 AI models for try-fix exploration (run SEQUENTIALLY):
**Choose a platform that is BOTH affected by the bug AND available on the current host.**
### Step 1: Identify affected platforms from Pre-Flight
-- Check the "Platforms Affected" checkboxes in the state file
- Check issue labels (e.g., `platform/iOS`, `platform/Android`)
- Check which platform-specific files the PR modifies
diff --git a/.github/agents/pr/post-gate.md b/.github/agents/pr/post-gate.md
index e5a149d070fb..55e4e876aa7b 100644
--- a/.github/agents/pr/post-gate.md
+++ b/.github/agents/pr/post-gate.md
@@ -1,6 +1,6 @@
# PR Agent: Post-Gate Phases (3-4)
-**⚠️ PREREQUISITE: Only read this file after 🚦 Gate shows `✅ PASSED` in your state file.**
+**⚠️ PREREQUISITE: Only read this file after 🚦 Gate shows `✅ PASSED`.**
If Gate is not passed, go back to `.github/agents/pr.md` and complete phases 1-2 first.
@@ -19,20 +19,27 @@ If Gate is not passed, go back to `.github/agents/pr.md` and complete phases 1-2
**All rules from `.github/agents/pr/SHARED-RULES.md` apply here**, including:
- Phase Completion Protocol (fill ALL pending fields before marking complete)
-- Stop on Environment Blockers (STOP and ask user, don't continue)
+- Stop on Environment Blockers (retry once, then skip and continue autonomously)
- Multi-Model Configuration (5 models, SEQUENTIAL only)
-If try-fix cannot run due to environment issues, **STOP and ask the user**. Do NOT mark attempts as "BLOCKED" and continue.
+If try-fix cannot run due to environment issues after one retry, **skip the remaining try-fix models and proceed to Report**. Do NOT stop and ask the user.
-### 🚨 CRITICAL: Stop on Environment Blockers (Applies to Phase 3)
+### 🚨 CRITICAL: Environment Blockers in Phase 3
-The same "Stop on Environment Blockers" rule from `pr.md` applies here. If try-fix cannot run due to:
+The default mode is **non-interactive** — no human can respond to questions.
+
+If try-fix cannot run due to:
- Missing Appium drivers
- Device/emulator not available
- WinAppDriver not installed
- Platform tools missing
+- Build failures unrelated to the fix
-**STOP and ask the user** before continuing. Do NOT mark try-fix attempts as "BLOCKED" and continue. Either fix the environment issue or get explicit user permission to skip.
+**Use your best judgment to continue autonomously:**
+1. Try ONE alternative (e.g., different platform, rebuild)
+2. If still blocked, **skip remaining try-fix models and proceed to Report**
+3. Document what was skipped and why in the Report phase
+4. The PR's fix was already validated by Gate - that's sufficient for a recommendation
---
@@ -40,7 +47,7 @@ The same "Stop on Environment Blockers" rule from `pr.md` applies here. If try-f
> **SCOPE**: Explore independent fix alternatives using `try-fix` skill, compare with PR's fix, select the best approach.
-**⚠️ Gate Check:** Verify 🚦 Gate is `✅ PASSED` in your state file before proceeding.
+**⚠️ Gate Check:** Verify 🚦 Gate is `✅ PASSED` before proceeding.
### 🚨 CRITICAL: try-fix is Independent of PR's Fix
@@ -73,7 +80,6 @@ Invoke the try-fix skill for PR #XXXXX:
- target_files:
- src/[area]/[likely-affected-file-1].cs
- src/[area]/[likely-affected-file-2].cs
-- state_file: CustomAgentLogsTmp/PRState/pr-XXXXX.md
Generate ONE independent fix idea. Review the PR's fix first to ensure your approach is DIFFERENT.
```
@@ -118,7 +124,7 @@ After Round 1, invoke EACH of the 5 models to ask for new ideas. **No shortcuts
Do you have any NEW fix ideas? Reply: 'NEW IDEA: [desc]' or 'NO NEW IDEAS'"
```
-3. **Record each model's response** in state file Cross-Pollination table
+3. **Record each model's response** in Cross-Pollination table
4. **For each new idea**: Run try-fix with that model (SEQUENTIAL, wait for completion)
@@ -127,13 +133,12 @@ After Round 1, invoke EACH of the 5 models to ask for new ideas. **No shortcuts
#### try-fix Behavior
Each `try-fix` invocation (run via task agent with specific model):
-1. Reads state file to learn from prior failed attempts
+1. Learns from prior failed attempts
2. Reverts PR's fix to get a broken baseline
3. Proposes ONE new independent fix idea
4. Implements and tests it
5. Records result (with failure analysis if it failed)
-6. **Updates state file** (appends row to Fix Candidates table)
-7. Reverts all changes (restores PR's fix)
+6. Reverts all changes (restores PR's fix)
See `.github/skills/try-fix/SKILL.md` for full details.
@@ -162,7 +167,7 @@ After the loop, review the **Fix Candidates** table:
3. **Most robust** - Handles edge cases, less likely to regress
4. **Matches codebase style** - Consistent with existing patterns
-Update the state file:
+Update the selected fix:
```markdown
**Exhausted:** Yes (or No if stopped early)
@@ -187,36 +192,17 @@ Update the state file:
### Complete 🔧 Fix
-**🚨 MANDATORY: Update state file**
-
-**Update state file**:
-1. Verify Fix Candidates table is complete with all attempts
-2. Verify failure analyses are documented for failed attempts
-3. Verify Selected Fix is documented with reasoning
-4. Change 🔧 Fix status to `✅ COMPLETE`
-5. Change 📋 Report status to `▶️ IN PROGRESS`
-
-**Before marking ✅ COMPLETE, verify state file contains:**
+Verify the following before proceeding:
- [ ] Round 1 completed: All 5 models ran try-fix
-- [ ] **Cross-pollination table exists** with responses from ALL 5 models:
- ```
- | Model | Round 2 Response |
- |-------|------------------|
- | claude-sonnet-4.6 | NO NEW IDEAS |
- | claude-opus-4.6 | NO NEW IDEAS |
- | gpt-5.2 | NO NEW IDEAS |
- | gpt-5.3-codex | NO NEW IDEAS |
- | gemini-3-pro-preview | NO NEW IDEAS |
- ```
+- [ ] Cross-pollination completed with responses from ALL 5 models
- [ ] Fix Candidates table has numbered rows for each try-fix attempt
- [ ] Each row has: approach, test result, files changed, notes
- [ ] "Exhausted" field set to Yes (all models confirmed no new ideas)
- [ ] "Selected Fix" populated with reasoning
-- [ ] Root cause analysis documented for the selected fix (to be surfaced in 📋 Report phase "### Root Cause" section)
-- [ ] No ⏳ PENDING markers remain in Fix section
-- [ ] State file saved
+- [ ] Root cause analysis documented for the selected fix
+- [ ] **Write phase output to `CustomAgentLogsTmp/PRState/{PRNumber}/PRAgent/try-fix/content.md`** (see SHARED-RULES.md "Phase Output Artifacts")
-**🚨 If cross-pollination table is missing, you skipped Round 2. Go back and invoke each model.**
+**🚨 If cross-pollination is missing, you skipped Round 2. Go back and invoke each model.**
---
@@ -224,7 +210,7 @@ Update the state file:
> **SCOPE**: Deliver the final result - either a PR review or a new PR.
-**⚠️ Gate Check:** Verify ALL phases 1-3 are `✅ COMPLETE` or `✅ PASSED` before proceeding.
+**⚠️ Gate Check:** Verify ALL phases 1-3 are complete before proceeding.
### Finalize Title and Description
@@ -256,8 +242,6 @@ If reviewing an existing PR, check if title/description need updates and include
**Do NOT run git commands. User handles commit/push/PR creation.**
-2. **Update state file** with PR link once user provides it
-
### If Starting from PR - Write Review
Determine your recommendation based on the Fix phase:
@@ -279,35 +263,14 @@ Determine your recommendation based on the Fix phase:
- Run the `pr-finalize` skill to verify title and description match implementation
- If discrepancies found, include suggested updates in review comments
-### Final State File Format
-
-Update the state file header:
-
-```markdown
-## ✅ Final Recommendation: APPROVE
-```
-or
-```markdown
-## ⚠️ Final Recommendation: REQUEST CHANGES
-```
-
-Update all phase statuses to complete.
-
### Complete 📋 Report
-**🚨 MANDATORY: Update state file**
-
-**Update state file**:
-1. Change header status to final recommendation
-2. Update all phases to `✅ COMPLETE` or `✅ PASSED`
-3. Present final result to user
-
-**Before marking ✅ COMPLETE, verify state file contains:**
-- [ ] Final recommendation (APPROVE/REQUEST_CHANGES/COMMENT)
-- [ ] Summary of findings
+Verify the following before finishing:
+- [ ] Final recommendation determined (APPROVE/REQUEST_CHANGES/COMMENT)
+- [ ] Summary of findings prepared
- [ ] Key technical insights documented
-- [ ] Overall status changed to final recommendation
-- [ ] State file saved
+- [ ] **Write phase output to `CustomAgentLogsTmp/PRState/{PRNumber}/PRAgent/report/content.md`** (see SHARED-RULES.md "Phase Output Artifacts")
+- [ ] Result presented to user
---
@@ -319,7 +282,7 @@ Update all phase statuses to complete.
- ❌ **Running try-fix in parallel** - SEQUENTIAL ONLY - they modify same files and use same device
- ❌ **Using explore/glob instead of invoking models** - Cross-pollination requires ACTUAL task agent invocations with each model, not code searches
- ❌ **Assuming "comprehensive coverage" = exhausted** - Only exhausted when all 5 models explicitly say "NO NEW IDEAS"
-- ❌ **Not recording cross-pollination responses** - State file must have table showing each model's Round 2 response
+- ❌ **Not recording cross-pollination responses** - Must track each model's Round 2 response
- ❌ **Not analyzing why fixes failed** - Record the flawed reasoning to help future attempts
- ❌ **Selecting a failing fix** - Only select from passing candidates
- ❌ **Forgetting to revert between attempts** - Each try-fix must start from broken baseline, end with PR restored
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index aaa22df4882e..b8f6e9a890a0 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -260,7 +260,7 @@ Skills are modular capabilities that can be invoked directly or used by agents.
- **Purpose**: Verifies PR title and description match actual implementation, AND performs code review for best practices before merge.
- **Trigger phrases**: "finalize PR #XXXXX", "check PR description for #XXXXX", "review commit message"
- **Used by**: Before merging any PR, when description may be stale
- - **Note**: Does NOT require agent involvement or session markdown - works on any PR
+ - **Note**: Works on any PR
- **🚨 CRITICAL**: NEVER use `--approve` or `--request-changes` - only post comments. Approval is a human decision.
4. **learn-from-pr** (`.github/skills/learn-from-pr/SKILL.md`)
@@ -302,7 +302,7 @@ Skills are modular capabilities that can be invoked directly or used by agents.
- **Purpose**: Proposes ONE independent fix approach, applies it, tests, records result with failure analysis, then reverts
- **Used by**: pr agent Phase 3 (Fix phase) - rarely invoked directly by users
- **Behavior**: Reads prior attempts to learn from failures. Max 5 attempts per session.
- - **Output**: Updates session markdown with attempt results and failure analysis
+ - **Output**: Reports attempt results and failure analysis
### Using Custom Agents
diff --git a/.github/scripts/BuildAndRunHostApp.ps1 b/.github/scripts/BuildAndRunHostApp.ps1
index 60d15963eb66..0702c73b015a 100644
--- a/.github/scripts/BuildAndRunHostApp.ps1
+++ b/.github/scripts/BuildAndRunHostApp.ps1
@@ -279,6 +279,11 @@ Write-Host ""
$env:DEVICE_UDID = $DeviceUdid
Write-Info "Set DEVICE_UDID environment variable: $DeviceUdid"
+# Set APPIUM_LOG_FILE so UITestBase saves screenshots/page-source to our log directory
+$appiumLogFile = Join-Path $HostAppLogsDir "appium.log"
+$env:APPIUM_LOG_FILE = $appiumLogFile
+Write-Info "Set APPIUM_LOG_FILE: $appiumLogFile (screenshots will be saved here)"
+
try {
# Run dotnet test and capture output
$testOutput = & dotnet test $TestProject --filter $effectiveFilter --logger "console;verbosity=detailed" 2>&1
@@ -313,6 +318,37 @@ try {
#endregion
+#region Collect Test Artifacts (screenshots, page source)
+
+Write-Step "Collecting test artifacts (screenshots, page source)..."
+
+# Collect any screenshots/page source from the test assembly output directory
+# UITestBase saves these via TestContext.AddTestAttachment to the assembly dir
+$testAssemblyDirs = @(
+ (Join-Path $RepoRoot "artifacts/bin/Controls.TestCases.Android.Tests/Debug/net10.0"),
+ (Join-Path $RepoRoot "artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0"),
+ (Join-Path $RepoRoot "artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0")
+)
+
+$copiedCount = 0
+foreach ($dir in $testAssemblyDirs) {
+ if (Test-Path $dir) {
+ $artifacts = Get-ChildItem -Path $dir -File -Include "*.png","*.txt" -ErrorAction SilentlyContinue |
+ Where-Object { $_.Name -match "ScreenShot|PageSource" }
+ foreach ($artifact in $artifacts) {
+ Copy-Item -Path $artifact.FullName -Destination $HostAppLogsDir -Force
+ $copiedCount++
+ }
+ }
+}
+
+# Also check the HostAppLogsDir itself for screenshots saved via APPIUM_LOG_FILE
+$screenshotCount = (Get-ChildItem -Path $HostAppLogsDir -Filter "*.png" -ErrorAction SilentlyContinue).Count
+$pageSourceCount = (Get-ChildItem -Path $HostAppLogsDir -Filter "*PageSource*" -ErrorAction SilentlyContinue).Count
+Write-Info "Test artifacts collected: $screenshotCount screenshot(s), $pageSourceCount page source(s) (copied $copiedCount from assembly dir)"
+
+#endregion
+
#region Capture Device Logs
Write-Step "Capturing device logs..."
diff --git a/.github/scripts/Review-PR.ps1 b/.github/scripts/Review-PR.ps1
index 1d8749d98aa6..0844fafb5893 100644
--- a/.github/scripts/Review-PR.ps1
+++ b/.github/scripts/Review-PR.ps1
@@ -236,6 +236,25 @@ if (-not (Test-Path $stateDir)) {
Write-Host " 📁 Created state directory: $stateDir" -ForegroundColor Gray
}
+# Create PRAgent phase directories for structured output
+$PRAgentDir = Join-Path $RepoRoot "CustomAgentLogsTmp/PRState/$PRNumber/PRAgent"
+$phaseSubdirs = @("pre-flight", "gate", "try-fix", "report")
+foreach ($subdir in $phaseSubdirs) {
+ $dirPath = Join-Path $PRAgentDir $subdir
+ if (-not (Test-Path $dirPath)) {
+ New-Item -ItemType Directory -Path $dirPath -Force | Out-Null
+ }
+}
+Write-Host " 📁 Created PRAgent phase directories: $PRAgentDir" -ForegroundColor Gray
+
+# Save the branch name and commit SHA BEFORE launching the agent.
+# The Copilot agent may change HEAD (e.g., via git checkout, gh pr checkout, git reset)
+# despite prompt instructions forbidding it. Using a pinned SHA for restoration
+# ensures we always recover to the correct state regardless of what the agent did.
+$savedBranch = git branch --show-current
+$savedHead = git rev-parse HEAD
+Write-Host " 📌 Pinned restore point: $savedBranch @ $($savedHead.Substring(0, 10))" -ForegroundColor Gray
+
# Step 4: Build the prompt for Copilot CLI
$planTemplatePath = ".github/agents/pr/PLAN-TEMPLATE.md"
@@ -255,19 +274,37 @@ $platformInstruction
- NEVER run ``git checkout``, ``git switch``, ``git fetch``, ``git stash``, or ``git reset``
- NEVER run ``git push`` - you do NOT have permission to push anything
- You are ALWAYS on the correct branch already - the script handles this
-- If the state file says "wrong branch", that's stale state - delete it and start fresh
- If you think you need to switch branches or push changes, you are WRONG - ask the user instead
+🚨 **CRITICAL - AUTONOMOUS EXECUTION:**
+- There is NO human operator to respond to questions
+- NEVER stop and ask the user for input - nobody will respond
+- NEVER present a list of options and wait for a choice
+- When you encounter an environment blocker, use your best judgment to choose the best path forward:
+ - If a phase is blocked (e.g., try-fix builds fail), SKIP that phase and proceed to the next one
+ - If Gate passes but Fix phase is blocked, proceed directly to Report phase with Gate results only
+ - If a specific platform fails, try an alternative platform ONCE, then skip if still blocked
+ - Always prefer CONTINUING with partial results over STOPPING completely
+- Document what was skipped and why in your report, but keep moving forward
+
+📁 **PHASE OUTPUT ARTIFACTS (MANDATORY):**
+- After completing EACH phase, write a ``content.md`` file to the phase output directory
+- Pre-Flight: ``CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/pre-flight/content.md``
+- Gate: ``CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/gate/content.md``
+- Fix: ``CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/try-fix/content.md``
+- Report: ``CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/report/content.md``
+- These files are read by the comment scripts to build the PR comment
+- See SHARED-RULES.md "Phase Output Artifacts" section for content format
+
**Instructions:**
1. Read the plan template at ``$planTemplatePath`` for the 4-phase workflow
2. Read ``.github/agents/pr.md`` for Phases 1-2 instructions
3. Follow ALL critical rules, especially:
- - STOP on environment blockers and ask before continuing
+ - On environment blockers: skip the blocked phase and continue autonomously (see AUTONOMOUS EXECUTION above)
- Use task agent for Gate verification
- Run multi-model try-fix in Phase 3
**Start with Phase 1: Pre-Flight**
-- Create state file: CustomAgentLogsTmp/PRState/pr-$PRNumber.md
- Gather context from PR #$PRNumber
- Proceed through all 4 phases
@@ -298,7 +335,6 @@ if ($DryRun) {
Write-Host "PR Review Context:" -ForegroundColor Cyan
Write-Host " PR_NUMBER: $PRNumber" -ForegroundColor White
Write-Host " PLATFORM: $(if ($Platform) { $Platform } else { '(agent will determine)' })" -ForegroundColor White
- Write-Host " STATE_FILE: CustomAgentLogsTmp/PRState/pr-$PRNumber.md" -ForegroundColor White
Write-Host " PLAN_TEMPLATE: $planTemplatePath" -ForegroundColor White
Write-Host " CURRENT_BRANCH: $(git branch --show-current)" -ForegroundColor White
Write-Host " PR_TITLE: $($prInfo.title)" -ForegroundColor White
@@ -330,7 +366,7 @@ if ($DryRun) {
# Branch switching prevention relies on agent instructions in pr.md only
# Create log directory for this PR
- $prLogDir = Join-Path $RepoRoot "CustomAgentLogsTmp/PRState/$PRNumber/copilot-logs"
+ $prLogDir = Join-Path $RepoRoot "CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/copilot-logs"
if (-not (Test-Path $prLogDir)) {
New-Item -ItemType Directory -Path $prLogDir -Force | Out-Null
}
@@ -369,14 +405,25 @@ if ($DryRun) {
# Restore tracked files to clean state before running post-completion skills.
# Phase 1 (PR Agent) may have left the working tree dirty from try-fix attempts,
- # which can cause skill files to be missing or modified in subsequent phases.
- # NOTE: State files in CustomAgentLogsTmp/ are .gitignore'd and untracked,
- # so this won't touch them. Using HEAD to also restore deleted files.
+ # or even switched branches despite instructions. We use the pinned SHA ($savedHead)
+ # instead of HEAD to guarantee restoration to the correct commit.
+ # NOTE: CustomAgentLogsTmp/ is .gitignore'd and untracked, so this won't touch them.
Write-Host ""
Write-Host "🧹 Restoring working tree to clean state between phases..." -ForegroundColor Yellow
git status --porcelain 2>$null | Set-Content "CustomAgentLogsTmp/PRState/phase1-exit-git-status.log" -ErrorAction SilentlyContinue
- git checkout HEAD -- . 2>&1 | Out-Null
- Write-Host " ✅ Working tree restored" -ForegroundColor Green
+
+ # Check if the agent moved HEAD or switched branches
+ $postAgentBranch = git branch --show-current
+ $postAgentHead = git rev-parse HEAD
+ if ($postAgentBranch -ne $savedBranch -or $postAgentHead -ne $savedHead) {
+ Write-Host " ⚠️ Agent changed git state! Branch: $postAgentBranch (expected: $savedBranch), HEAD: $($postAgentHead.Substring(0, 10)) (expected: $($savedHead.Substring(0, 10)))" -ForegroundColor Red
+ Write-Host " 🔄 Recovering to pinned restore point..." -ForegroundColor Yellow
+ git checkout $savedBranch 2>&1 | Out-Null
+ git reset --hard $savedHead 2>&1 | Out-Null
+ } else {
+ git checkout $savedHead -- . 2>&1 | Out-Null
+ }
+ Write-Host " ✅ Working tree restored (pinned SHA: $($savedHead.Substring(0, 10)))" -ForegroundColor Green
# Phase 2: Run pr-finalize skill if requested
if ($RunFinalize) {
@@ -387,12 +434,12 @@ if ($DryRun) {
Write-Host ""
# Ensure output directory exists for finalize results
- $finalizeDir = Join-Path $RepoRoot "CustomAgentLogsTmp/PRState/$PRNumber/pr-finalize"
+ $finalizeDir = Join-Path $RepoRoot "CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/pr-finalize"
if (-not (Test-Path $finalizeDir)) {
New-Item -ItemType Directory -Path $finalizeDir -Force | Out-Null
}
- $finalizePrompt = "Run the pr-finalize skill for PR #$PRNumber. Verify the PR title and description match the actual implementation. Do NOT post a comment. Write your findings to CustomAgentLogsTmp/PRState/$PRNumber/pr-finalize/pr-finalize-summary.md (NOT the main state file pr-$PRNumber.md which contains phase data that must not be overwritten). If you recommend a new description, also write it to CustomAgentLogsTmp/PRState/$PRNumber/pr-finalize/recommended-description.md. If you have code review findings, also write them to CustomAgentLogsTmp/PRState/$PRNumber/pr-finalize/code-review.md."
+ $finalizePrompt = "Run the pr-finalize skill for PR #$PRNumber. Verify the PR title and description match the actual implementation. Do NOT post a comment. Write your findings to CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/pr-finalize/pr-finalize-summary.md. If you recommend a new description, also write it to CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/pr-finalize/recommended-description.md. If you have code review findings, also write them to CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/pr-finalize/code-review.md."
$finalizeArgs = @(
"-p", $finalizePrompt,
@@ -422,17 +469,27 @@ if ($DryRun) {
Write-Host "╚═══════════════════════════════════════════════════════════╝" -ForegroundColor Magenta
Write-Host ""
- # Restore tracked files (including deleted ones) to clean state.
+ # Restore tracked files (including deleted ones) to clean state using pinned SHA.
Write-Host "🧹 Restoring working tree to clean state..." -ForegroundColor Yellow
git status --porcelain 2>$null | Set-Content "CustomAgentLogsTmp/PRState/phase2-exit-git-status.log" -ErrorAction SilentlyContinue
- git checkout HEAD -- . 2>&1 | Out-Null
- Write-Host " ✅ Working tree restored" -ForegroundColor Green
- # 3a: Post PR agent summary comment (from Phase 1 state file)
+ # Check if Phase 2 (pr-finalize) moved HEAD or switched branches
+ $postPhase2Branch = git branch --show-current
+ $postPhase2Head = git rev-parse HEAD
+ if ($postPhase2Branch -ne $savedBranch -or $postPhase2Head -ne $savedHead) {
+ Write-Host " ⚠️ Phase 2 changed git state! Recovering to pinned restore point..." -ForegroundColor Red
+ git checkout $savedBranch 2>&1 | Out-Null
+ git reset --hard $savedHead 2>&1 | Out-Null
+ } else {
+ git checkout $savedHead -- . 2>&1 | Out-Null
+ }
+ Write-Host " ✅ Working tree restored (pinned SHA: $($savedHead.Substring(0, 10)))" -ForegroundColor Green
+
+ # 3a: Post PR agent summary comment
$scriptPath = ".github/skills/ai-summary-comment/scripts/post-ai-summary-comment.ps1"
if (-not (Test-Path $scriptPath)) {
- Write-Host "⚠️ Script missing after checkout, attempting targeted recovery..." -ForegroundColor Yellow
- git checkout HEAD -- $scriptPath 2>&1 | Out-Null
+ Write-Host "⚠️ Script missing after restore, attempting targeted recovery..." -ForegroundColor Yellow
+ git checkout $savedHead -- $scriptPath 2>&1 | Out-Null
}
if (Test-Path $scriptPath) {
Write-Host "💬 Running post-ai-summary-comment.ps1 directly..." -ForegroundColor Yellow
@@ -455,11 +512,11 @@ if ($DryRun) {
$finalizeScriptPath = ".github/skills/ai-summary-comment/scripts/post-pr-finalize-comment.ps1"
if (-not (Test-Path $finalizeScriptPath)) {
Write-Host "⚠️ Finalize script missing, attempting targeted recovery..." -ForegroundColor Yellow
- git checkout HEAD -- $finalizeScriptPath 2>&1 | Out-Null
+ git checkout $savedHead -- $finalizeScriptPath 2>&1 | Out-Null
}
if (Test-Path $finalizeScriptPath) {
- Write-Host "💬 Running post-pr-finalize-comment.ps1 directly..." -ForegroundColor Yellow
- & $finalizeScriptPath -PRNumber $PRNumber
+ Write-Host "💬 Running post-pr-finalize-comment.ps1 directly (unified mode)..." -ForegroundColor Yellow
+ & $finalizeScriptPath -PRNumber $PRNumber -Unified
$finalizeCommentExit = $LASTEXITCODE
if ($finalizeCommentExit -eq 0) {
@@ -473,14 +530,14 @@ if ($DryRun) {
}
}
}
+
}
}
Write-Host ""
-Write-Host "📝 State file: CustomAgentLogsTmp/PRState/pr-$PRNumber.md" -ForegroundColor Gray
Write-Host "📋 Plan template: $planTemplatePath" -ForegroundColor Gray
if (-not $DryRun) {
- Write-Host "📁 Copilot logs: CustomAgentLogsTmp/PRState/$PRNumber/copilot-logs/" -ForegroundColor Gray
+ Write-Host "📁 Copilot logs: CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/copilot-logs/" -ForegroundColor Gray
if (-not $Interactive) {
Write-Host "📄 Session markdown: $sessionFile" -ForegroundColor Gray
}
diff --git a/.github/scripts/shared/Build-AndDeploy.ps1 b/.github/scripts/shared/Build-AndDeploy.ps1
index 8abdafe7b21c..f57d5c088094 100644
--- a/.github/scripts/shared/Build-AndDeploy.ps1
+++ b/.github/scripts/shared/Build-AndDeploy.ps1
@@ -136,6 +136,20 @@ if ($Platform -eq "android") {
# Deploy to iOS simulator
Write-Step "Deploying to iOS simulator..."
+
+ # Shutdown any OTHER booted simulators to avoid Appium connecting to the wrong device
+ $bootedSims = xcrun simctl list devices --json | ConvertFrom-Json
+ $otherBooted = $bootedSims.devices.PSObject.Properties.Value |
+ ForEach-Object { $_ } |
+ Where-Object { $_.state -eq "Booted" -and $_.udid -ne $DeviceUdid }
+
+ if ($otherBooted) {
+ foreach ($sim in $otherBooted) {
+ Write-Info "Shutting down other booted simulator: $($sim.name) ($($sim.udid))"
+ xcrun simctl shutdown $sim.udid 2>$null
+ }
+ }
+
Write-Info "Booting simulator (if not already running)..."
xcrun simctl boot $DeviceUdid 2>$null
diff --git a/.github/scripts/shared/Start-Emulator.ps1 b/.github/scripts/shared/Start-Emulator.ps1
index b842d840a6cc..a5d90f192530 100644
--- a/.github/scripts/shared/Start-Emulator.ps1
+++ b/.github/scripts/shared/Start-Emulator.ps1
@@ -339,7 +339,12 @@ if ($Platform -eq "android") {
exit 1
}
- # Get device UDID if not provided
+ # Get device UDID if not provided - check env var first
+ if (-not $DeviceUdid -and $env:DEVICE_UDID) {
+ Write-Info "Using DEVICE_UDID from environment: $($env:DEVICE_UDID)"
+ $DeviceUdid = $env:DEVICE_UDID
+ }
+
if (-not $DeviceUdid) {
Write-Info "Auto-detecting iOS simulator..."
$simList = xcrun simctl list devices available --json | ConvertFrom-Json
@@ -454,6 +459,19 @@ if ($Platform -eq "android") {
Write-Success "iOS simulator: $deviceName ($DeviceUdid)"
+ # Shutdown any OTHER booted simulators to avoid Appium connecting to the wrong device
+ $bootedSims = xcrun simctl list devices --json | ConvertFrom-Json
+ $otherBooted = $bootedSims.devices.PSObject.Properties.Value |
+ ForEach-Object { $_ } |
+ Where-Object { $_.state -eq "Booted" -and $_.udid -ne $DeviceUdid }
+
+ if ($otherBooted) {
+ foreach ($sim in $otherBooted) {
+ Write-Info "Shutting down other booted simulator: $($sim.name) ($($sim.udid))"
+ xcrun simctl shutdown $sim.udid 2>$null
+ }
+ }
+
# Boot simulator if not already booted
Write-Info "Booting simulator (if not already running)..."
xcrun simctl boot $DeviceUdid 2>$null
@@ -470,7 +488,7 @@ if ($Platform -eq "android") {
exit 1
}
- Write-Success "Simulator is booted and ready"
+ Write-Success "Simulator is booted and ready: $deviceName"
#endregion
}
diff --git a/.github/skills/ai-summary-comment/IMPROVEMENTS.md b/.github/skills/ai-summary-comment/IMPROVEMENTS.md
index 2de8dc008fe4..4c50133edaf9 100644
--- a/.github/skills/ai-summary-comment/IMPROVEMENTS.md
+++ b/.github/skills/ai-summary-comment/IMPROVEMENTS.md
@@ -10,7 +10,7 @@ The `post-ai-summary-comment.ps1` script has been significantly improved to make
**Before:** Script used hardcoded pattern matching with predefined title variations
-**After:** Script **automatically discovers ALL sections** from your state file and extracts them dynamically
+**After:** Script **automatically discovers ALL sections** from the provided content and extracts them dynamically
```powershell
# Extracts ALL TITLE
sections
@@ -25,13 +25,13 @@ $preFlightContent = Get-SectionByPattern -Sections $allSections -Patterns @(
**Benefits:**
- ✅ **No hardcoded titles** - works with ANY section header you use
-- ✅ **Automatically adapts** - add new sections without modifying the script
-- ✅ **Better debugging** - shows exactly which sections were found
-- ✅ **More maintainable** - less code, more flexible
+- ✅ Automatically adapts - add new sections without modifying the script
+- ✅ Better debugging - shows exactly which sections were found in the content
+- ✅ More maintainable - less code, more flexible
**Example debug output:**
```
-[DEBUG] Found 6 section(s) in state file
+[DEBUG] Found 6 section(s) in content
[DEBUG] Section: '📋 Issue Summary' (803 chars)
[DEBUG] Section: '🚦 Gate - Test Verification' (488 chars)
[DEBUG] Section: '🔧 Fix Candidates' (868 chars)
@@ -91,7 +91,7 @@ $DebugPreference = 'Continue'
```
**Shows:**
-- Which sections were found in the state file
+- Which sections were found in the content
- How many characters each section contains
- Which patterns matched which sections
- Why validation passed or failed
@@ -104,7 +104,7 @@ $DebugPreference = 'Continue'
```
⛔ VALIDATION FAILED
-💡 Fix these issues in the state file before posting.
+💡 Fix these issues in the content before posting.
Or use -SkipValidation to bypass these checks.
🐛 Debug tip: Run with $DebugPreference = 'Continue' for details
@@ -126,7 +126,7 @@ function Extract-AllSections {
}
```
-**Result:** Hashtable with ALL sections from your state file
+**Result:** Hashtable with ALL sections from your content
### Step 2: Map to Phases
@@ -208,12 +208,12 @@ Any title matching `'📋.*Report'` or `'Final Report'`:
**No changes needed!** The script is backward compatible.
-**Old state files** with exact headers like:
+**Old content** with exact headers like:
```markdown
📋 Phase 4: Report — Final Recommendation
```
-**New state files** with simpler headers like:
+**New content** with simpler headers like:
```markdown
📋 Final Report
```
@@ -236,7 +236,7 @@ Any title matching `'📋.*Report'` or `'Final Report'`:
## Common Issues & Solutions
-### Issue: "Phase X has NO content in state file"
+### Issue: "Phase X has NO content"
**Step 1:** Enable debug mode to see what was found
```powershell
@@ -245,7 +245,7 @@ pwsh -Command '$DebugPreference = "Continue"; ./post-ai-summary-comment.ps1 -PRN
**Look for:**
```
-[DEBUG] Found 7 section(s) in state file
+[DEBUG] Found 7 section(s) in content
[DEBUG] Section: 'Your Section Title' (XXX chars)
```
@@ -263,7 +263,7 @@ If your title is `"📋 Final Analysis"`, it won't match!
### Issue: Section extracted but content is empty
-**Cause:** State file structure issue (missing content between tags)
+**Cause:** Content structure issue (missing content between tags)
**Check your markdown:**
```markdown
@@ -336,7 +336,7 @@ Tested with:
**Debug output example:**
```
-[DEBUG] Found 6 section(s) in state file
+[DEBUG] Found 6 section(s) in content
[DEBUG] Section: '📋 Issue Summary' (803 chars)
[DEBUG] Section: '📁 Files Changed' (0 chars)
[DEBUG] Section: '💬 PR Discussion Summary' (0 chars)
@@ -425,16 +425,16 @@ PR #27340 provides a **correct and well-tested fix**...
```
⛔ VALIDATION FAILED
Found 1 validation error(s):
- - Report: Phase Report is marked as '✅ COMPLETE' but has NO content in state file
+ - Report: Phase Report is marked as '✅ COMPLETE' but has NO content
```
**After:**
```
⛔ VALIDATION FAILED
Found 1 validation error(s):
- - Report: Phase Report is marked as '✅ COMPLETE' but has NO content in state file
+ - Report: Phase Report is marked as '✅ COMPLETE' but has NO content
-💡 Fix these issues in the state file before posting the review comment.
+💡 Fix these issues in the content before posting the review comment.
Or use -SkipValidation to bypass these checks (not recommended).
🐛 Debug tip: Run with $DebugPreference = 'Continue' for detailed extraction info
@@ -517,7 +517,7 @@ Any of these variations will be recognized:
## Migration Guide
-**No changes needed!** The script is backward compatible. If you have existing state files with the old header format, they'll continue to work.
+**No changes needed!** The script is backward compatible. If you have existing content with the old header format, it will continue to work.
If you want to use the new flexibility:
- Just use simpler headers like `📋 Final Report` instead of `📋 Phase 4: Report — Final Recommendation`
@@ -527,11 +527,11 @@ If you want to use the new flexibility:
## Common Issues & Solutions
-### Issue: "Phase Report has NO content in state file"
+### Issue: "Phase Report has NO content"
-**Solution 1:** Check your state file structure
+**Solution 1:** Check your content structure
```bash
-grep -A 5 "📋.*Report" CustomAgentLogsTmp/PRState/pr-XXXXX.md
+grep -A 5 "📋.*Report" your-content-file.md
```
Make sure you have:
@@ -612,7 +612,7 @@ $reportContent = Extract-PhaseContent -StateContent $Content -PhaseTitles @(
## Future Improvements
Potential enhancements:
-- [ ] Auto-detect phase titles from state file (no hardcoded patterns)
+- [ ] Auto-detect phase titles from content (no hardcoded patterns)
- [ ] Support markdown headings (`##` / `###`) in addition to ``
- [ ] Validate links and references work
- [ ] Check that commit SHAs are valid
@@ -624,7 +624,7 @@ Potential enhancements:
The improvements have been tested with:
- ✅ PR #27340 (Entry/Editor keyboard issue)
-- ✅ State files with various header formats
+- ✅ Content with various header formats
- ✅ Dry run mode
- ✅ Debug mode
- ✅ Skip validation mode
@@ -636,5 +636,5 @@ The improvements have been tested with:
If you encounter issues or have suggestions, please:
1. Try debug mode first: `$DebugPreference = 'Continue'`
-2. Check the state file structure
+2. Check the content structure
3. Report the issue with debug output included
diff --git a/.github/skills/ai-summary-comment/NO-EXTERNAL-REFERENCES-RULE.md b/.github/skills/ai-summary-comment/NO-EXTERNAL-REFERENCES-RULE.md
index 84545c9fa546..70223059df16 100644
--- a/.github/skills/ai-summary-comment/NO-EXTERNAL-REFERENCES-RULE.md
+++ b/.github/skills/ai-summary-comment/NO-EXTERNAL-REFERENCES-RULE.md
@@ -87,17 +87,12 @@ Add an **Implementation** subsection:
### pr-finalize Skill
-When running `pr-finalize` skill, you create TWO outputs:
+When running `pr-finalize` skill, you create output:
1. **Summary file** (local reference)
- Location: `CustomAgentLogsTmp/PRState/XXXXX/pr-finalize/pr-finalize-summary.md`
- Purpose: Your detailed analysis and working notes
- Audience: You and local CLI users
-
-2. **State file Report section** (GitHub audience)
- - Location: `CustomAgentLogsTmp/PRState/pr-XXXXX.md` (Report phase)
- - Purpose: Final recommendations that get posted to GitHub
- - Audience: PR authors, reviewers, community
- **MUST be self-contained** - no external references
### PR Agent Phase 4 (Report)
@@ -184,9 +179,8 @@ When completing Phase 4, verify:
| What | Where | Audience | Self-Contained? |
|------|-------|----------|-----------------|
| Summary file | `CustomAgentLogsTmp/.../summary.md` | Local CLI | N/A (local only) |
-| State file | `CustomAgentLogsTmp/PRState/pr-XXXXX.md` | GitHub users | ✅ YES - REQUIRED |
| PR comment | GitHub PR page | Public | ✅ YES - REQUIRED |
-**Remember:** Anything that goes in the state file's `` sections will be posted to GitHub. Make it self-contained!
+**Remember:** Anything posted to GitHub must be self-contained. Never reference local files.
--
diff --git a/.github/skills/ai-summary-comment/SKILL.md b/.github/skills/ai-summary-comment/SKILL.md
index 9073d3bef37c..073fea7448fb 100644
--- a/.github/skills/ai-summary-comment/SKILL.md
+++ b/.github/skills/ai-summary-comment/SKILL.md
@@ -18,7 +18,6 @@ This skill posts automated progress comments to GitHub Pull Requests during the
- **Section-Based Updates**: Each script updates only its section, preserving others
- **Duplicate Prevention**: Finds existing `` comment and updates it
- **File-Based DryRun Preview**: Use `-DryRun` to preview changes in a local file before posting
-- **Auto-Loading State Files**: Automatically finds and loads state files from `CustomAgentLogsTmp/PRState/`
- **Simple Interface**: Just provide PR number - script handles everything else
## Comment Architecture
@@ -97,42 +96,22 @@ If an existing finalize comment exists, it will be replaced with the updated sec
## Usage
-### Simplest: Just provide PR number
+### Simplest: Just provide PR number (auto-loads from phase files)
```bash
-# Auto-loads CustomAgentLogsTmp/PRState/pr-27246.md
+# Auto-loads from CustomAgentLogsTmp/PRState/{PRNumber}/PRAgent/*/content.md
pwsh .github/skills/ai-summary-comment/scripts/post-ai-summary-comment.ps1 -PRNumber 27246
```
-### With explicit state file path
-
-```bash
-# PRNumber auto-extracted from filename (pr-27246.md → 27246)
-pwsh .github/skills/ai-summary-comment/scripts/post-ai-summary-comment.ps1 -StateFile CustomAgentLogsTmp/PRState/pr-27246.md
-```
-
-### Legacy: Provide content directly
-
-```bash
-pwsh .github/skills/ai-summary-comment/scripts/post-ai-summary-comment.ps1 -PRNumber 12345 -Content "$(cat CustomAgentLogsTmp/PRState/pr-12345.md)"
-```
-
### Parameters
| Parameter | Required | Description | Example |
|-----------|----------|-------------|---------|
-| `PRNumber` | No* | Pull request number | `12345` |
-| `StateFile` | No* | Path to state file (PRNumber auto-extracted from `pr-XXXXX.md` naming) | `CustomAgentLogsTmp/PRState/pr-27246.md` |
-| `Content` | No* | Full state file content (legacy, can be piped via stdin) | Content from state file |
+| `PRNumber` | Yes | Pull request number | `12345` |
| `DryRun` | No | Preview changes in local file instead of posting to GitHub | `-DryRun` |
| `PreviewFile` | No | Path to local preview file for DryRun mode (default: `CustomAgentLogsTmp/PRState/{PRNumber}/ai-summary-comment-preview.md`) | `-PreviewFile ./preview.md` |
| `SkipValidation` | No | Skip validation checks (not recommended) | `-SkipValidation` |
-*At least one of PRNumber, StateFile, or Content is required. The script will:
-- If `-PRNumber` provided: Auto-load `CustomAgentLogsTmp/PRState/pr-{PRNumber}.md`
-- If `-StateFile` provided: Load the file and extract PRNumber from `pr-XXXXX.md` filename
-- If `-Content` provided: Use content directly (legacy, requires `-PRNumber`)
-
## DryRun Preview Workflow
Use `-DryRun` to preview the combined comment before posting to GitHub. Each script updates the same preview file, mirroring how the actual GitHub comment is updated.
@@ -224,7 +203,7 @@ When the same PR is reviewed multiple times (e.g., after new commits), the scrip
The `post-try-fix-comment.ps1` script updates the `` section of the unified AI Summary comment. It aggregates all try-fix attempts into collapsible sections. Works for both issues and PRs (GitHub treats PR comments as issue comments).
-**✨ Auto-Loading from `CustomAgentLogsTmp`**: The script automatically discovers and aggregates ALL attempt directories from `CustomAgentLogsTmp/PRState/{IssueNumber}/try-fix/`.
+**✨ Auto-Loading from `CustomAgentLogsTmp`**: The script automatically discovers and aggregates ALL attempt directories from `CustomAgentLogsTmp/PRState/{IssueNumber}/PRAgent/try-fix/`.
### Usage
@@ -233,17 +212,17 @@ The `post-try-fix-comment.ps1` script updates the `` sec
```powershell
# All parameters auto-loaded from directory structure
pwsh .github/skills/ai-summary-comment/scripts/post-try-fix-comment.ps1 `
- -TryFixDir CustomAgentLogsTmp/PRState/27246/try-fix/attempt-1
+ -TryFixDir CustomAgentLogsTmp/PRState/27246/PRAgent/try-fix/attempt-1
```
#### Or just provide issue number
```powershell
-# Auto-discovers and posts latest attempt from CustomAgentLogsTmp/PRState/27246/try-fix/
+# Auto-discovers and posts latest attempt from CustomAgentLogsTmp/PRState/27246/PRAgent/try-fix/
pwsh .github/skills/ai-summary-comment/scripts/post-try-fix-comment.ps1 -IssueNumber 27246
```
-#### Legacy: Manual parameters
+#### Manual parameters
```powershell
pwsh .github/skills/ai-summary-comment/scripts/post-try-fix-comment.ps1 `
@@ -277,7 +256,7 @@ pwsh .github/skills/ai-summary-comment/scripts/post-try-fix-comment.ps1 `
### Expected Directory Structure
```
-CustomAgentLogsTmp/PRState/{IssueNumber}/try-fix/
+CustomAgentLogsTmp/PRState/{IssueNumber}/PRAgent/try-fix/
├── attempt-1/
│ ├── approach.md # Brief description of the approach (required)
│ ├── result.txt # "Pass", "Fail", or "Compiles" (required)
@@ -344,7 +323,7 @@ pwsh .github/skills/ai-summary-comment/scripts/post-verify-tests-comment.ps1 -PR
```powershell
pwsh .github/skills/ai-summary-comment/scripts/post-verify-tests-comment.ps1 `
-PRNumber 32891 `
- -ReportFile CustomAgentLogsTmp/PRState/32891/verify-tests-fail/verification-report.md
+ -ReportFile CustomAgentLogsTmp/PRState/32891/PRAgent/gate/verify-tests-fail/verification-report.md
```
### Parameters
@@ -362,7 +341,7 @@ pwsh .github/skills/ai-summary-comment/scripts/post-verify-tests-comment.ps1 `
### Expected Directory Structure
```
-CustomAgentLogsTmp/PRState/{PRNumber}/verify-tests-fail/
+CustomAgentLogsTmp/PRState/{PRNumber}/PRAgent/gate/verify-tests-fail/
├── verification-report.md # Full verification report (required)
├── verification-log.txt # Detailed log (optional)
├── test-without-fix.log # Test output without fix (optional)
@@ -394,7 +373,7 @@ pwsh .github/skills/ai-summary-comment/scripts/post-write-tests-comment.ps1 `
pwsh .github/skills/ai-summary-comment/scripts/post-write-tests-comment.ps1 -IssueNumber 27246
```
-#### Legacy: Manual parameters
+#### Manual parameters
```powershell
pwsh .github/skills/ai-summary-comment/scripts/post-write-tests-comment.ps1 `
@@ -539,15 +518,52 @@ pwsh .github/skills/ai-summary-comment/scripts/post-pr-finalize-comment.ps1 `
4. **Auto-parsing from summary file getting confused**
- When in doubt, provide explicit parameters instead of relying on auto-parsing
-### Expected Directory Structure for Auto-Loading
+### Expected Directory Structure
+
+The PR agent writes phase output files that comment scripts auto-load:
```
-CustomAgentLogsTmp/PRState/{PRNumber}/pr-finalize/
-├── pr-finalize-summary.md # Main summary (auto-parsed)
-├── recommended-description.md # Full recommended description (optional)
-└── code-review.md # Code review findings (optional)
+CustomAgentLogsTmp/PRState/{PRNumber}/
+├── PRAgent/
+│ ├── pre-flight/
+│ │ └── content.md # Phase 1 output (auto-loaded by post-ai-summary-comment.ps1)
+│ ├── gate/
+│ │ ├── content.md # Phase 2 output (auto-loaded by post-ai-summary-comment.ps1)
+│ │ └── verify-tests-fail/ # Script output from verify-tests-fail.ps1
+│ │ ├── verification-report.md
+│ │ ├── verification-log.txt
+│ │ ├── test-without-fix.log
+│ │ └── test-with-fix.log
+│ ├── try-fix/
+│ │ ├── content.md # Phase 3 summary (auto-loaded by post-ai-summary-comment.ps1)
+│ │ ├── attempt-1/ # Individual attempt outputs
+│ │ │ ├── approach.md
+│ │ │ ├── result.txt
+│ │ │ ├── fix.diff
+│ │ │ └── analysis.md
+│ │ └── attempt-2/
+│ │ └── ...
+│ └── report/
+│ └── content.md # Phase 4 output (auto-loaded by post-ai-summary-comment.ps1)
+├── pr-finalize/
+│ ├── pr-finalize-summary.md
+│ ├── recommended-description.md
+│ └── code-review.md
+└── copilot-logs/
+ ├── process-*.log
+ └── session-*.md
```
+### Auto-Loading Behavior
+
+When `post-ai-summary-comment.ps1` is called, it auto-discovers phase files:
+1. Checks `CustomAgentLogsTmp/PRState/{PRNumber}/PRAgent/*/content.md`
+2. Loads all available phase content files
+3. Builds the comment structure from the loaded files
+4. Posts/updates the unified AI Summary comment
+
+This eliminates the need to pass large content strings as parameters.
+
---
## Technical Details
diff --git a/.github/skills/ai-summary-comment/scripts/post-ai-summary-comment.ps1 b/.github/skills/ai-summary-comment/scripts/post-ai-summary-comment.ps1
index 46388f0a320f..9c1353794cbd 100644
--- a/.github/skills/ai-summary-comment/scripts/post-ai-summary-comment.ps1
+++ b/.github/skills/ai-summary-comment/scripts/post-ai-summary-comment.ps1
@@ -7,8 +7,10 @@
Creates ONE comment for the entire PR review with all phases wrapped in an expandable section.
Uses HTML marker for identification.
- **NEW: Validates that phases marked as COMPLETE actually have content.**
- **NEW: Auto-loads state file from CustomAgentLogsTmp/PRState/pr-XXXXX.md**
+ Content is always auto-loaded from PRAgent phase files
+ (CustomAgentLogsTmp/PRState//PRAgent/*/content.md).
+
+ **Validates that phases marked as COMPLETE actually have content.**
Format:
## 🤖 AI Summary — ✅ APPROVE
@@ -17,14 +19,7 @@
.PARAMETER PRNumber
- The pull request number (required unless -StateFile is provided with pr-XXXXX.md naming)
-
-.PARAMETER StateFile
- Path to state file (defaults to CustomAgentLogsTmp/PRState/pr-{PRNumber}.md)
- If provided with pr-XXXXX.md naming, PRNumber is auto-extracted
-
-.PARAMETER Content
- The full state file content (alternative to -StateFile)
+ The pull request number (required)
.PARAMETER DryRun
Print comment instead of posting
@@ -33,28 +28,16 @@
Skip validation checks (not recommended)
.EXAMPLE
- # Simplest: just provide PR number, state file auto-loaded
./post-ai-summary-comment.ps1 -PRNumber 12345
.EXAMPLE
- # Provide state file directly (PR number auto-extracted from filename)
- ./post-ai-summary-comment.ps1 -StateFile CustomAgentLogsTmp/PRState/pr-27246.md
-
-.EXAMPLE
- # Legacy: provide content directly
- ./post-ai-summary-comment.ps1 -PRNumber 12345 -Content "$(cat CustomAgentLogsTmp/PRState/pr-12345.md)"
+ ./post-ai-summary-comment.ps1 -PRNumber 12345 -DryRun
#>
param(
[Parameter(Mandatory=$false)]
[int]$PRNumber,
- [Parameter(Mandatory=$false)]
- [string]$StateFile,
-
- [Parameter(Mandatory=$false)]
- [string]$Content,
-
[Parameter(Mandatory=$false)]
[switch]$DryRun,
@@ -68,77 +51,141 @@ param(
$ErrorActionPreference = "Stop"
# ============================================================================
-# STATE FILE RESOLUTION
+# INPUT VALIDATION
# ============================================================================
-# Priority: 1) -Content, 2) -StateFile, 3) Auto-detect from PRNumber
-
-# If StateFile provided, extract PRNumber from filename if not already set
-if (-not [string]::IsNullOrWhiteSpace($StateFile)) {
- if ($StateFile -match 'pr-(\d+)\.md$') {
- $extractedPR = [int]$Matches[1]
- if ($PRNumber -eq 0) {
- $PRNumber = $extractedPR
- Write-Host "ℹ️ Auto-detected PRNumber: $PRNumber from state file name" -ForegroundColor Cyan
- } elseif ($PRNumber -ne $extractedPR) {
- Write-Host "⚠️ Warning: PRNumber ($PRNumber) differs from state file name (pr-$extractedPR.md)" -ForegroundColor Yellow
- }
+if ($PRNumber -eq 0) {
+ throw "PRNumber is required."
+}
+
+# Auto-load from PRAgent phase files
+Write-Host "ℹ️ Auto-loading from PRAgent phase files..." -ForegroundColor Cyan
+
+$PRAgentDir = "CustomAgentLogsTmp/PRState/$PRNumber/PRAgent"
+if (-not (Test-Path $PRAgentDir)) {
+ $repoRoot = git rev-parse --show-toplevel 2>$null
+ if ($repoRoot) {
+ $PRAgentDir = Join-Path $repoRoot "CustomAgentLogsTmp/PRState/$PRNumber/PRAgent"
}
-
- if (Test-Path $StateFile) {
- $Content = Get-Content $StateFile -Raw -Encoding UTF8
- Write-Host "ℹ️ Loaded state file: $StateFile" -ForegroundColor Cyan
+}
+
+if (-not (Test-Path $PRAgentDir)) {
+ Write-Host ""
+ Write-Host "╔═══════════════════════════════════════════════════════════╗" -ForegroundColor Red
+ Write-Host "║ ⛔ No PRAgent directory found ║" -ForegroundColor Red
+ Write-Host "╚═══════════════════════════════════════════════════════════╝" -ForegroundColor Red
+ Write-Host ""
+ Write-Host "Expected directory: $PRAgentDir" -ForegroundColor Yellow
+ Write-Host "Ensure PRAgent phase files exist." -ForegroundColor Yellow
+ throw "No PRAgent directory found. Ensure PRAgent/*/content.md files exist."
+}
+
+# Load each phase content file
+$phaseFiles = @{
+ "pre-flight" = Join-Path $PRAgentDir "pre-flight/content.md"
+ "gate" = Join-Path $PRAgentDir "gate/content.md"
+ "try-fix" = Join-Path $PRAgentDir "try-fix/content.md"
+ "report" = Join-Path $PRAgentDir "report/content.md"
+}
+
+$loadedPhases = @()
+$phaseContentMap = @{}
+
+foreach ($phase in $phaseFiles.GetEnumerator()) {
+ if (Test-Path $phase.Value) {
+ $phaseContentMap[$phase.Key] = Get-Content $phase.Value -Raw -Encoding UTF8
+ $loadedPhases += $phase.Key
+ Write-Host " ✅ Loaded: $($phase.Key) ($((Get-Item $phase.Value).Length) bytes)" -ForegroundColor Green
} else {
- throw "State file not found: $StateFile"
+ Write-Host " ⏭️ Skipped: $($phase.Key) (no content.md)" -ForegroundColor Gray
}
}
-# If no Content and no StateFile, try auto-detect from PRNumber
-if ([string]::IsNullOrWhiteSpace($Content) -and $PRNumber -gt 0) {
- $autoStateFile = "CustomAgentLogsTmp/PRState/pr-$PRNumber.md"
- if (Test-Path $autoStateFile) {
- $Content = Get-Content $autoStateFile -Raw -Encoding UTF8
- Write-Host "ℹ️ Auto-loaded state file: $autoStateFile" -ForegroundColor Cyan
+if ($loadedPhases.Count -eq 0) {
+ throw "No phase content files found in $PRAgentDir. Ensure at least one phase has a content.md file."
+}
+
+Write-Host " 📦 Loaded $($loadedPhases.Count) phase(s): $($loadedPhases -join ', ')" -ForegroundColor Cyan
+
+# Build synthetic Content from phase files in the expected format
+$syntheticParts = @()
+
+# Determine phase statuses based on which files exist and content
+$phaseStatusMap = @{}
+foreach ($phase in @("pre-flight", "gate", "try-fix", "report")) {
+ if ($phaseContentMap.ContainsKey($phase)) {
+ $phaseStatusMap[$phase] = "✅ COMPLETE"
} else {
- # Try relative to repo root
- $repoRoot = git rev-parse --show-toplevel 2>$null
- if ($repoRoot) {
- $autoStateFile = Join-Path $repoRoot "CustomAgentLogsTmp/PRState/pr-$PRNumber.md"
- if (Test-Path $autoStateFile) {
- $Content = Get-Content $autoStateFile -Raw -Encoding UTF8
- Write-Host "ℹ️ Auto-loaded state file: $autoStateFile" -ForegroundColor Cyan
- }
- }
+ $phaseStatusMap[$phase] = "⏳ PENDING"
}
}
-# If Content still not provided, try stdin (legacy support)
-if ([string]::IsNullOrWhiteSpace($Content)) {
- $Content = $input | Out-String
+# Build status table
+$statusTable = @"
+| Phase | Status |
+|-------|--------|
+| Pre-Flight | $($phaseStatusMap['pre-flight']) |
+| Gate | $($phaseStatusMap['gate']) |
+| Fix | $($phaseStatusMap['try-fix']) |
+| Report | $($phaseStatusMap['report']) |
+"@
+$syntheticParts += $statusTable
+
+# Build phase sections
+if ($phaseContentMap.ContainsKey('pre-flight')) {
+ $syntheticParts += @"
+📋 Pre-Flight — Issue Summary
+
+$($phaseContentMap['pre-flight'])
+
+
+"@
+}
+
+if ($phaseContentMap.ContainsKey('gate')) {
+ $syntheticParts += @"
+🚦 Gate — Test Verification
+
+$($phaseContentMap['gate'])
+
+
+"@
+}
+
+if ($phaseContentMap.ContainsKey('try-fix')) {
+ $syntheticParts += @"
+🔧 Fix — Analysis & Comparison
+
+$($phaseContentMap['try-fix'])
+
+
+"@
+}
+
+if ($phaseContentMap.ContainsKey('report')) {
+ $syntheticParts += @"
+📋 Report — Final Recommendation
+
+$($phaseContentMap['report'])
+
+
+"@
}
+$Content = $syntheticParts -join "`n`n---`n`n"
+Write-Host " ✅ Built synthetic content ($($Content.Length) chars)" -ForegroundColor Green
+
# Final validation
if ([string]::IsNullOrWhiteSpace($Content)) {
Write-Host ""
Write-Host "╔═══════════════════════════════════════════════════════════╗" -ForegroundColor Red
- Write-Host "║ ⛔ No state file content found ║" -ForegroundColor Red
+ Write-Host "║ ⛔ No content loaded from phase files ║" -ForegroundColor Red
Write-Host "╚═══════════════════════════════════════════════════════════╝" -ForegroundColor Red
Write-Host ""
- Write-Host "Usage options:" -ForegroundColor Yellow
- Write-Host " 1. ./post-ai-summary-comment.ps1 -PRNumber 12345" -ForegroundColor Gray
- Write-Host " (auto-loads CustomAgentLogsTmp/PRState/pr-12345.md)" -ForegroundColor Gray
- Write-Host ""
- Write-Host " 2. ./post-ai-summary-comment.ps1 -StateFile path/to/pr-12345.md" -ForegroundColor Gray
- Write-Host " (loads specified file, extracts PRNumber from name)" -ForegroundColor Gray
+ Write-Host "Usage:" -ForegroundColor Yellow
+ Write-Host " ./post-ai-summary-comment.ps1 -PRNumber 12345 # auto-loads from PRAgent/*/content.md" -ForegroundColor Gray
Write-Host ""
- Write-Host " 3. ./post-ai-summary-comment.ps1 -PRNumber 12345 -Content `"...`"" -ForegroundColor Gray
- Write-Host " (legacy: provide content directly)" -ForegroundColor Gray
- Write-Host ""
- throw "Content is required. See usage options above."
-}
-
-if ($PRNumber -eq 0) {
- throw "PRNumber is required. Provide via -PRNumber or use a state file named pr-XXXXX.md"
+ throw "No content loaded from PRAgent phase files."
}
Write-Host "╔═══════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
@@ -167,7 +214,7 @@ function Test-PhaseContentComplete {
# Check if content exists
if ([string]::IsNullOrWhiteSpace($PhaseContent)) {
- $validationErrors += "Phase $PhaseName is marked as '$PhaseStatus' but has NO content in state file"
+ $validationErrors += "Phase $PhaseName is marked as '$PhaseStatus' but has NO content"
if ($Debug) {
Write-Host " [DEBUG] Content is null or whitespace for phase: $PhaseName" -ForegroundColor DarkGray
}
@@ -179,8 +226,9 @@ function Test-PhaseContentComplete {
Write-Host " [DEBUG] First 100 chars: $($PhaseContent.Substring(0, [Math]::Min(100, $PhaseContent.Length)))" -ForegroundColor DarkGray
}
- # Check for PENDING markers
- $pendingMatches = [regex]::Matches($PhaseContent, '\[PENDING\]|⏳\s*PENDING')
+ # Check for PENDING markers - only match [PENDING] placeholder markers.
+ # ⏳ PENDING is a status indicator (redundant with phase table), not an unfilled placeholder.
+ $pendingMatches = [regex]::Matches($PhaseContent, '\[PENDING\]')
if ($pendingMatches.Count -gt 0) {
$validationErrors += "Phase $PhaseName is marked as '$PhaseStatus' but contains $($pendingMatches.Count) PENDING markers"
}
@@ -235,7 +283,7 @@ function Test-PhaseContentComplete {
# EXTRACTION FUNCTIONS
# ============================================================================
-# Extract recommendation from state file
+# Extract recommendation from content
$recommendation = "IN PROGRESS"
if ($Content -match '##\s+✅\s+Final Recommendation:\s+APPROVE') {
$recommendation = "✅ APPROVE"
@@ -247,7 +295,7 @@ if ($Content -match '##\s+✅\s+Final Recommendation:\s+APPROVE') {
$recommendation = "⚠️ REQUEST CHANGES"
}
-# Extract phase statuses from state file
+# Extract phase statuses from content
$phaseStatuses = @{
"Pre-Flight" = "⏳ PENDING"
"Gate" = "⏳ PENDING"
@@ -273,7 +321,7 @@ if ($Content -match '(?s)\|\s*Phase\s*\|\s*Status\s*\|.*?\n\|[\s-]+\|[\s-]+\|(.*
# DYNAMIC SECTION EXTRACTION
# ============================================================================
-# Extract ALL sections from state file dynamically
+# Extract ALL sections from content dynamically
function Extract-AllSections {
param(
[string]$StateContent,
@@ -288,7 +336,7 @@ function Extract-AllSections {
$matches = [regex]::Matches($StateContent, $pattern)
if ($Debug) {
- Write-Host " [DEBUG] Found $($matches.Count) section(s) in state file" -ForegroundColor Cyan
+ Write-Host " [DEBUG] Found $($matches.Count) section(s) in content" -ForegroundColor Cyan
}
foreach ($match in $matches) {
@@ -361,6 +409,27 @@ $reportContent = Get-SectionByPattern -Sections $allSections -Patterns @(
'Final Report'
) -Debug:$debugMode
+# Fallback: If Report content not found in blocks, look for
+# "## Final Recommendation" section directly in the markdown (agent sometimes
+# writes Report as a top-level heading instead of a block)
+if ([string]::IsNullOrWhiteSpace($reportContent)) {
+ # Look for "## Final Recommendation" heading - capture up to the first --- separator
+ # or block to avoid including content from other phases
+ if ($Content -match '(?s)##\s+[✅⚠️❌\uFE0F]*\s*Final Recommendation[:\s].+?(?=\n---|\n$null
if ($repoRoot) {
- $summaryPath = Join-Path $repoRoot "CustomAgentLogsTmp/PRState/$PRNumber/pr-finalize/pr-finalize-summary.md"
+ $summaryPath = Join-Path $repoRoot "CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/pr-finalize/pr-finalize-summary.md"
}
}
@@ -410,7 +413,14 @@ if ([string]::IsNullOrWhiteSpace($DescriptionStatus)) {
}
if ([string]::IsNullOrWhiteSpace($DescriptionAssessment)) {
- throw "DescriptionAssessment is required. Provide via -DescriptionAssessment or use -SummaryFile"
+ if (-not [string]::IsNullOrWhiteSpace($SummaryFile)) {
+ # We have a summary file but couldn't extract a specific assessment section.
+ # Use the full summary content as the assessment (the whole file IS the assessment).
+ $DescriptionAssessment = $content
+ Write-Host "ℹ️ Using full summary file content as DescriptionAssessment" -ForegroundColor Cyan
+ } else {
+ throw "DescriptionAssessment is required. Provide via -DescriptionAssessment or use -SummaryFile"
+ }
}
# Warn if description needs work but no recommended description is provided
@@ -573,10 +583,153 @@ if ($CodeReviewStatus -ne "Skipped" -or -not [string]::IsNullOrWhiteSpace($CodeR
}
# ============================================================================
-# STANDALONE COMMENT HANDLING
-# Posts as separate PR Finalization comment with marker
+# COMMENT POSTING
+# Two modes: -Unified (inject into AI Summary comment) or standalone (default)
# ============================================================================
+if ($Unified) {
+ # ========================================================================
+ # UNIFIED MODE: Inject into the comment
+ # ========================================================================
+
+ $MAIN_MARKER = ""
+ $SECTION_START = ""
+ $SECTION_END = ""
+ $LEGACY_MARKER = ""
+
+ # Build the finalize section wrapped in expandable details
+ $finalizeSection = @"
+$SECTION_START
+
+📋 Expand PR Finalization Review
+
+---
+
+$titleSection
+
+$descSection
+$codeReviewSection
+
+---
+
+
+$SECTION_END
+"@
+
+ Write-Host "`nUnified mode: injecting into AI Summary comment on #$PRNumber..." -ForegroundColor Yellow
+
+ $existingUnifiedComment = $null
+ $existingLegacyComment = $null
+
+ try {
+ $commentsJson = gh api "repos/dotnet/maui/issues/$PRNumber/comments?per_page=100" 2>$null
+ $comments = $commentsJson | ConvertFrom-Json
+
+ foreach ($comment in $comments) {
+ if ($comment.body -match [regex]::Escape($MAIN_MARKER)) {
+ $existingUnifiedComment = $comment
+ Write-Host "✓ Found unified AI Summary comment (ID: $($comment.id))" -ForegroundColor Green
+ }
+ if ($comment.body -match [regex]::Escape($LEGACY_MARKER)) {
+ $existingLegacyComment = $comment
+ }
+ }
+ } catch {
+ Write-Host "⚠️ Could not fetch comments: $_" -ForegroundColor Yellow
+ }
+
+ if ($DryRun) {
+ if ([string]::IsNullOrWhiteSpace($PreviewFile)) {
+ $PreviewFile = "CustomAgentLogsTmp/PRState/$PRNumber/ai-summary-comment-preview.md"
+ }
+
+ $previewDir = Split-Path $PreviewFile -Parent
+ if (-not (Test-Path $previewDir)) {
+ New-Item -ItemType Directory -Path $previewDir -Force | Out-Null
+ }
+
+ $existingPreview = ""
+ if (Test-Path $PreviewFile) {
+ $existingPreview = Get-Content $PreviewFile -Raw -Encoding UTF8
+ }
+
+ if ($existingPreview -match [regex]::Escape($SECTION_START)) {
+ $pattern = [regex]::Escape($SECTION_START) + "[\s\S]*?" + [regex]::Escape($SECTION_END)
+ $finalComment = $existingPreview -replace $pattern, $finalizeSection
+ } elseif (-not [string]::IsNullOrWhiteSpace($existingPreview)) {
+ $finalComment = $existingPreview.TrimEnd() + "`n`n" + $finalizeSection
+ } else {
+ $finalComment = @"
+$MAIN_MARKER
+
+## 🤖 AI Summary
+
+$finalizeSection
+"@
+ }
+
+ Set-Content -Path $PreviewFile -Value "$($finalComment.TrimEnd())`n" -Encoding UTF8 -NoNewline
+
+ Write-Host "`n=== COMMENT PREVIEW ===" -ForegroundColor Yellow
+ Write-Host $finalComment
+ Write-Host "`n=== END PREVIEW ===" -ForegroundColor Yellow
+ Write-Host "`n✅ Preview saved to: $PreviewFile" -ForegroundColor Green
+ exit 0
+ }
+
+ if ($existingUnifiedComment) {
+ $body = $existingUnifiedComment.body
+
+ if ($body -match [regex]::Escape($SECTION_START)) {
+ $pattern = [regex]::Escape($SECTION_START) + "[\s\S]*?" + [regex]::Escape($SECTION_END)
+ $newBody = $body -replace $pattern, $finalizeSection
+ } else {
+ $newBody = $body.TrimEnd() + "`n`n" + $finalizeSection
+ }
+
+ $newBody = $newBody -replace "`n{4,}", "`n`n`n"
+
+ Write-Host "Updating unified comment ID $($existingUnifiedComment.id) with PR finalize section..." -ForegroundColor Yellow
+ $tempFile = [System.IO.Path]::GetTempFileName()
+ @{ body = $newBody } | ConvertTo-Json -Depth 10 | Set-Content -Path $tempFile -Encoding UTF8
+
+ $result = gh api --method PATCH "repos/dotnet/maui/issues/comments/$($existingUnifiedComment.id)" --input $tempFile --jq '.html_url'
+ Remove-Item $tempFile
+ Write-Host "✅ PR finalize section added to unified comment: $result" -ForegroundColor Green
+ } else {
+ $commentBody = @"
+$MAIN_MARKER
+
+## 🤖 AI Summary
+
+$finalizeSection
+"@
+
+ Write-Host "Creating new unified comment with PR finalize section on PR #$PRNumber..." -ForegroundColor Yellow
+ $tempFile = [System.IO.Path]::GetTempFileName()
+ @{ body = $commentBody } | ConvertTo-Json -Depth 10 | Set-Content -Path $tempFile -Encoding UTF8
+
+ $result = gh api --method POST "repos/dotnet/maui/issues/$PRNumber/comments" --input $tempFile --jq '.html_url'
+ Remove-Item $tempFile
+ Write-Host "✅ Unified comment posted: $result" -ForegroundColor Green
+ }
+
+ # Clean up legacy standalone finalize comment if it exists
+ if ($existingLegacyComment) {
+ Write-Host "🧹 Removing legacy standalone PR Finalization comment (ID: $($existingLegacyComment.id))..." -ForegroundColor Yellow
+ gh api --method DELETE "repos/dotnet/maui/issues/comments/$($existingLegacyComment.id)" 2>$null | Out-Null
+ if ($LASTEXITCODE -eq 0) {
+ Write-Host " ✅ Legacy comment removed" -ForegroundColor Green
+ } else {
+ Write-Host " ⚠️ Could not remove legacy comment (non-fatal)" -ForegroundColor Yellow
+ }
+ }
+
+} else {
+ # ========================================================================
+ # STANDALONE MODE (default): Post as separate PR Finalization comment
+ # ========================================================================
+
$FINALIZE_MARKER = ""
Write-Host "`nChecking for existing PR Finalization comment on #$PRNumber..." -ForegroundColor Yellow
@@ -615,7 +768,7 @@ $codeReviewSection
if ($DryRun) {
# File-based DryRun: uses separate preview file for finalize (separate comment from unified)
if ([string]::IsNullOrWhiteSpace($PreviewFile)) {
- $PreviewFile = "CustomAgentLogsTmp/PRState/$PRNumber/pr-finalize-preview.md"
+ $PreviewFile = "CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/pr-finalize-preview.md"
}
# Ensure directory exists
@@ -651,3 +804,5 @@ if ($existingComment) {
}
Remove-Item $tempFile
+
+} # end standalone mode
diff --git a/.github/skills/ai-summary-comment/scripts/post-try-fix-comment.ps1 b/.github/skills/ai-summary-comment/scripts/post-try-fix-comment.ps1
index f5bcf49540c5..27bd4566bcb0 100644
--- a/.github/skills/ai-summary-comment/scripts/post-try-fix-comment.ps1
+++ b/.github/skills/ai-summary-comment/scripts/post-try-fix-comment.ps1
@@ -10,7 +10,7 @@
If an existing try-fix comment exists, it will be EDITED with the new attempt added.
Otherwise, a new comment will be created.
- **NEW: Auto-loads from CustomAgentLogsTmp/PRState/{PRNumber}/try-fix/**
+ **NEW: Auto-loads from CustomAgentLogsTmp/PRState/{PRNumber}/PRAgent/try-fix/**
Format:
## 🔧 Try-Fix Analysis for Issue #XXXXX
@@ -35,7 +35,7 @@
The attempt number (1, 2, 3, etc.) - auto-detected from TryFixDir if not specified
.PARAMETER TryFixDir
- Path to try-fix attempt directory (e.g., CustomAgentLogsTmp/PRState/27246/try-fix/attempt-1)
+ Path to try-fix attempt directory (e.g., CustomAgentLogsTmp/PRState/27246/PRAgent/try-fix/attempt-1)
If provided, all parameters are auto-loaded from files in this directory
.PARAMETER Approach
@@ -61,14 +61,14 @@
.EXAMPLE
# Simplest: Just provide attempt directory (all info auto-loaded)
- ./post-try-fix-comment.ps1 -TryFixDir CustomAgentLogsTmp/PRState/27246/try-fix/attempt-1
+ ./post-try-fix-comment.ps1 -TryFixDir CustomAgentLogsTmp/PRState/27246/PRAgent/try-fix/attempt-1
.EXAMPLE
# Post all attempts for an issue
./post-try-fix-comment.ps1 -IssueNumber 27246
.EXAMPLE
- # Manual parameters (legacy)
+ # Manual parameters
./post-try-fix-comment.ps1 -IssueNumber 19560 -AttemptNumber 1 `
-Approach "Change Shadow base class to StyleableElement" `
-RootCause "Shadow inherits from Element which lacks styling support" `
@@ -128,7 +128,7 @@ if (-not [string]::IsNullOrWhiteSpace($TryFixDir)) {
throw "Try-fix directory not found: $TryFixDir"
}
- # Extract IssueNumber from path (e.g., CustomAgentLogsTmp/PRState/27246/try-fix/attempt-1)
+ # Extract IssueNumber from path (e.g., CustomAgentLogsTmp/PRState/27246/PRAgent/try-fix/attempt-1)
if ($TryFixDir -match '[/\\](\d+)[/\\]try-fix') {
if ($IssueNumber -eq 0) {
$IssueNumber = [int]$Matches[1]
@@ -207,11 +207,11 @@ if (-not [string]::IsNullOrWhiteSpace($TryFixDir)) {
# If IssueNumber provided but no TryFixDir, try to find all attempts
if ($IssueNumber -gt 0 -and [string]::IsNullOrWhiteSpace($TryFixDir) -and [string]::IsNullOrWhiteSpace($Approach)) {
- $tryFixBase = "CustomAgentLogsTmp/PRState/$IssueNumber/try-fix"
+ $tryFixBase = "CustomAgentLogsTmp/PRState/$IssueNumber/PRAgent/try-fix"
if (-not (Test-Path $tryFixBase)) {
$repoRoot = git rev-parse --show-toplevel 2>$null
if ($repoRoot) {
- $tryFixBase = Join-Path $repoRoot "CustomAgentLogsTmp/PRState/$IssueNumber/try-fix"
+ $tryFixBase = Join-Path $repoRoot "CustomAgentLogsTmp/PRState/$IssueNumber/PRAgent/try-fix"
}
}
diff --git a/.github/skills/ai-summary-comment/scripts/post-verify-tests-comment.ps1 b/.github/skills/ai-summary-comment/scripts/post-verify-tests-comment.ps1
index 5cace91ca955..cc78274e74a2 100644
--- a/.github/skills/ai-summary-comment/scripts/post-verify-tests-comment.ps1
+++ b/.github/skills/ai-summary-comment/scripts/post-verify-tests-comment.ps1
@@ -85,11 +85,11 @@ Write-Host "╚═════════════════════
# If PRNumber provided but no ReportFile, try to find it
if ($PRNumber -gt 0 -and [string]::IsNullOrWhiteSpace($ReportFile)) {
- $reportPath = "CustomAgentLogsTmp/PRState/$PRNumber/verify-tests-fail/verification-report.md"
+ $reportPath = "CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/gate/verify-tests-fail/verification-report.md"
if (-not (Test-Path $reportPath)) {
$repoRoot = git rev-parse --show-toplevel 2>$null
if ($repoRoot) {
- $reportPath = Join-Path $repoRoot "CustomAgentLogsTmp/PRState/$PRNumber/verify-tests-fail/verification-report.md"
+ $reportPath = Join-Path $repoRoot "CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/gate/verify-tests-fail/verification-report.md"
}
}
diff --git a/.github/skills/ai-summary-comment/scripts/post-write-tests-comment.ps1 b/.github/skills/ai-summary-comment/scripts/post-write-tests-comment.ps1
index fb3125529c99..da3972cd92e4 100644
--- a/.github/skills/ai-summary-comment/scripts/post-write-tests-comment.ps1
+++ b/.github/skills/ai-summary-comment/scripts/post-write-tests-comment.ps1
@@ -74,7 +74,7 @@
./post-write-tests-comment.ps1 -IssueNumber 27246
.EXAMPLE
- # Manual parameters (legacy)
+ # Manual parameters
./post-write-tests-comment.ps1 -IssueNumber 33331 -AttemptNumber 1 `
-TestDescription "Verifies Picker.IsOpen property changes correctly" `
-HostAppFile "src/Controls/tests/TestCases.HostApp/Issues/Issue33331.cs" `
diff --git a/.github/skills/learn-from-pr/SKILL.md b/.github/skills/learn-from-pr/SKILL.md
index 18e3a0d3f700..2a21ef5dc0c1 100644
--- a/.github/skills/learn-from-pr/SKILL.md
+++ b/.github/skills/learn-from-pr/SKILL.md
@@ -16,7 +16,6 @@ Extracts lessons learned from a completed PR to improve repository documentation
| Input | Required | Source |
|-------|----------|--------|
| PR number or Issue number | Yes | User provides (e.g., "PR #33352" or "issue 33352") |
-| Session markdown | Optional | `CustomAgentLogsTmp/PRState/issue-XXXXX.md` or `pr-XXXXX.md` |
## Outputs
@@ -61,19 +60,10 @@ The skill is complete when you have:
# Required: Get PR info
gh pr view XXXXX --json title,body,files
gh pr diff XXXXX
-
-# Check for session markdown
-ls CustomAgentLogsTmp/PRState/issue-XXXXX.md CustomAgentLogsTmp/PRState/pr-XXXXX.md 2>/dev/null
```
-**If session markdown exists, extract:**
-- Fix Candidates table (what was tried)
-- Files each attempt targeted
-- Why attempts failed
-
-**Analyzing without session markdown:**
+**Analyze the PR to extract learning:**
-When no session file exists, you can still learn from:
1. **PR discussion** - Comments reveal what was tried
2. **Commit history** - Multiple commits may show iteration
3. **Code complexity** - Non-obvious fixes suggest learning opportunities
@@ -88,8 +78,6 @@ Focus on: "What would have helped an agent find this fix faster?"
```bash
# Where did final fix go?
gh pr view XXXXX --json files --jq '.files[].path' | grep -v test
-
-# If session markdown exists, compare to attempted files
```
| Scenario | Implication |
@@ -209,7 +197,6 @@ Present your analysis covering:
| Situation | Action |
|-----------|--------|
| PR not found | Ask user to verify PR number |
-| No session markdown | Analyze PR diff only, note limited context |
| No agent involvement evident | Ask user if they still want analysis |
| Can't determine failure mode | State "insufficient data" and what's missing |
diff --git a/.github/skills/try-fix/SKILL.md b/.github/skills/try-fix/SKILL.md
index 291283981e3c..d13690296403 100644
--- a/.github/skills/try-fix/SKILL.md
+++ b/.github/skills/try-fix/SKILL.md
@@ -47,7 +47,6 @@ All inputs are provided by the invoker (CI, agent, or user).
| Platform | Yes | Target platform (`android`, `ios`, `windows`, `maccatalyst`) |
| Hints | Optional | Suggested approaches, prior attempts, or areas to focus on |
| Baseline | Optional | Git ref or instructions for establishing broken state (default: current state) |
-| state_file | Optional | Path to PR agent state file (e.g., `CustomAgentLogsTmp/PRState/pr-12345.md`). If provided, try-fix will append its results to the Fix Candidates table. |
## Outputs
@@ -70,7 +69,7 @@ Results reported back to the invoker:
$IssueNumber = "" # Replace with actual number
# Find next attempt number
-$tryFixDir = "CustomAgentLogsTmp/PRState/$IssueNumber/try-fix"
+$tryFixDir = "CustomAgentLogsTmp/PRState/$IssueNumber/PRAgent/try-fix"
$existingAttempts = (Get-ChildItem "$tryFixDir/attempt-*" -Directory -ErrorAction SilentlyContinue).Count
$attemptNum = $existingAttempts + 1
@@ -163,9 +162,8 @@ The skill is complete when:
- Review what files were changed
- Read the actual code changes to understand the current fix approach
-2. **If state_file provided, review prior attempts:**
- - Read the Fix Candidates table
- - Note which approaches failed and WHY (the Notes column)
+2. **Review prior attempts if any are known:**
+ - Note which approaches failed and WHY
- Note which approaches partially succeeded
3. **Identify what makes your approach DIFFERENT:**
@@ -195,7 +193,7 @@ The skill is complete when:
pwsh .github/scripts/EstablishBrokenBaseline.ps1 *>&1 | Tee-Object -FilePath "$OUTPUT_DIR/baseline.log"
```
-The script auto-detects and reverts fix files to merge-base state while preserving test files. **Will fail fast if no fix files detected** - you must be on the actual PR branch. Optional flags: `-BaseBranch main`, `-DryRun`.
+The script auto-detects and reverts fix files to merge-base state while preserving test files. **Will fail fast if no fix files detected** - the PR's changes must be present in the current branch (the `Review-PR.ps1` script handles this by merging the PR before the agent runs). Optional flags: `-BaseBranch main`, `-DryRun`.
**Verify baseline was established:**
```powershell
@@ -203,7 +201,7 @@ The script auto-detects and reverts fix files to merge-base state while preservi
Select-String -Path "$OUTPUT_DIR/baseline.log" -Pattern "Baseline established"
```
-**If the script fails with "No fix files detected":** You're likely on the wrong branch. Checkout the actual PR branch with `gh pr checkout ` and try again.
+**If the script fails with "No fix files detected":** The PR changes are not present on the current branch. Report this as `Blocked` — do NOT switch branches.
**If something fails mid-attempt:** `pwsh .github/scripts/EstablishBrokenBaseline.ps1 -Restore`
@@ -349,29 +347,6 @@ Provide structured output to the invoker:
**Determining Status:** Set `Done` when you've completed testing this approach (whether it passed or failed). Set `NeedsRetry` only if you hit a transient error (network timeout, flaky test) and want to retry the same approach.
-### Step 10: Update State File (if provided)
-
-If `state_file` input was provided and file exists:
-
-1. **Read current Fix Candidates table** from state file
-2. **Determine next attempt number** (count existing try-fix rows + 1)
-3. **Append new row** with this attempt's results:
-
-| # | Source | Approach | Test Result | Files Changed | Notes |
-|---|--------|----------|-------------|---------------|-------|
-| N | try-fix #N | [approach] | ✅ PASS / ❌ FAIL | [files] | [analysis] |
-
-**If no state file provided:** Skip this step (results returned to invoker only).
-
-**⚠️ Do NOT `git add` or `git commit` the state file.** It lives in `CustomAgentLogsTmp/` which is `.gitignore`d. Committing it with `git add -f` would cause `git checkout HEAD -- .` (used between phases) to revert it, losing data.
-
-**⚠️ IMPORTANT: Do NOT set any "Exhausted" field.** Cross-pollination exhaustion is determined by the pr agent after invoking ALL 6 models and confirming none have new ideas. try-fix only reports its own attempt result.
-
-**Ownership rule:** try-fix updates its own row ONLY. Never modify:
-- Phase status fields
-- "Selected Fix" field
-- Other try-fix rows
-
## Error Handling
| Situation | Action |
diff --git a/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1 b/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1
index de3bad6fee15..5eacfed3135a 100644
--- a/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1
+++ b/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1
@@ -126,14 +126,14 @@ if (-not $PRNumber) {
}
if (-not $foundPR) {
- Write-Host "⚠️ Could not auto-detect PR number - using 'unknown' folder" -ForegroundColor Yellow
- $PRNumber = "unknown"
+ Write-Error "Could not auto-detect PR number. Please provide -PRNumber parameter."
+ exit 1
}
}
}
# Set output directory based on PR number
-$OutputDir = "CustomAgentLogsTmp/PRState/$PRNumber/verify-tests-fail"
+$OutputDir = "CustomAgentLogsTmp/PRState/$PRNumber/PRAgent/gate/verify-tests-fail"
Write-Host "📁 Output directory: $OutputDir" -ForegroundColor Cyan
# ============================================================
@@ -144,59 +144,6 @@ $BaselineScript = Join-Path $RepoRoot ".github/scripts/EstablishBrokenBaseline.p
# Import Test-IsTestFile and Find-MergeBase from shared script
. $BaselineScript
-# ============================================================
-# Label management for verification results
-# ============================================================
-$LabelConfirmed = "s/ai-reproduction-confirmed"
-$LabelFailed = "s/ai-reproduction-failed"
-
-function Update-VerificationLabels {
- param(
- [Parameter(Mandatory = $true)]
- [bool]$ReproductionConfirmed,
-
- [Parameter(Mandatory = $false)]
- [string]$PR = $PRNumber
- )
-
- if ($PR -eq "unknown" -or -not $PR) {
- Write-Host "⚠️ Cannot update labels: PR number not available" -ForegroundColor Yellow
- return
- }
-
- $labelToAdd = if ($ReproductionConfirmed) { $LabelConfirmed } else { $LabelFailed }
- $labelToRemove = if ($ReproductionConfirmed) { $LabelFailed } else { $LabelConfirmed }
-
- Write-Host ""
- Write-Host "🏷️ Updating verification labels on PR #$PR..." -ForegroundColor Cyan
-
- # Track success for both operations
- $removeSuccess = $true
-
- # Remove the opposite label if it exists (using REST API to avoid GraphQL deprecation issues)
- $existingLabels = gh pr view $PR --json labels --jq '.labels[].name' 2>$null
- if ($existingLabels -contains $labelToRemove) {
- Write-Host " Removing: $labelToRemove" -ForegroundColor Yellow
- gh api "repos/dotnet/maui/issues/$PR/labels/$labelToRemove" --method DELETE 2>$null | Out-Null
- if ($LASTEXITCODE -ne 0) {
- $removeSuccess = $false
- Write-Host " ⚠️ Failed to remove label: $labelToRemove" -ForegroundColor Yellow
- }
- }
-
- # Add the appropriate label (using REST API to avoid GraphQL deprecation issues)
- Write-Host " Adding: $labelToAdd" -ForegroundColor Green
- $result = gh api "repos/dotnet/maui/issues/$PR/labels" --method POST -f "labels[]=$labelToAdd" 2>&1
- $addSuccess = $LASTEXITCODE -eq 0
-
- if ($addSuccess -and $removeSuccess) {
- Write-Host "✅ Labels updated successfully" -ForegroundColor Green
- } elseif ($addSuccess) {
- Write-Host "⚠️ Label added but failed to remove old label" -ForegroundColor Yellow
- } else {
- Write-Host "⚠️ Failed to update labels: $result" -ForegroundColor Yellow
- }
-}
# ============================================================
# Auto-detect test filter from changed files
@@ -466,7 +413,6 @@ if ($DetectedFixFiles.Count -eq 0) {
Write-Host "╚═══════════════════════════════════════════════════════════╝" -ForegroundColor Green
Write-Host ""
Write-Host "Failed tests: $($testResult.FailCount)" -ForegroundColor Yellow
- Update-VerificationLabels -ReproductionConfirmed $true
exit 0
} else {
# Tests PASSED - this is bad!
@@ -487,7 +433,6 @@ if ($DetectedFixFiles.Count -eq 0) {
Write-Host "╚═══════════════════════════════════════════════════════════╝" -ForegroundColor Red
Write-Host ""
Write-Host "Passed tests: $($testResult.PassCount)" -ForegroundColor Yellow
- Update-VerificationLabels -ReproductionConfirmed $false
exit 1
}
}
@@ -882,9 +827,7 @@ if ($verificationPassed) {
Write-Host "╠═══════════════════════════════════════════════════════════╣" -ForegroundColor Green
Write-Host "║ Tests correctly detect the issue: ║" -ForegroundColor Green
Write-Host "║ - FAIL without fix (as expected) ║" -ForegroundColor Green
- Write-Host "║ - PASS with fix (as expected) ║" -ForegroundColor Green
Write-Host "╚═══════════════════════════════════════════════════════════╝" -ForegroundColor Green
- Update-VerificationLabels -ReproductionConfirmed $true
exit 0
} else {
Write-Host ""
@@ -904,8 +847,6 @@ if ($verificationPassed) {
Write-Host "║ 1. Wrong fix files specified ║" -ForegroundColor Red
Write-Host "║ 2. Tests don't actually test the fixed behavior ║" -ForegroundColor Red
Write-Host "║ 3. The issue was already fixed in base branch ║" -ForegroundColor Red
- Write-Host "║ 4. Build caching - try clean rebuild ║" -ForegroundColor Red
Write-Host "╚═══════════════════════════════════════════════════════════╝" -ForegroundColor Red
- Update-VerificationLabels -ReproductionConfirmed $false
exit 1
}
diff --git a/eng/pipelines/ci-copilot.yml b/eng/pipelines/ci-copilot.yml
new file mode 100644
index 000000000000..05e4d11a2796
--- /dev/null
+++ b/eng/pipelines/ci-copilot.yml
@@ -0,0 +1,736 @@
+# Pipeline for running GitHub Copilot PR Reviewer Agent
+# This pipeline installs the Copilot CLI and invokes the PR reviewer agent
+# to conduct automated code reviews on pull requests.
+#
+# For more information, see:
+# https://github.com/dotnet/maui/wiki/PR-Reviewer-Agent
+
+trigger: none # Manual trigger only
+
+pr: none # Not triggered by PRs
+
+parameters:
+ - name: PRNumber
+ displayName: 'Pull Request Number'
+ type: string
+ default: ''
+
+ - name: Platform
+ displayName: 'Target Platform'
+ type: string
+ default: 'android'
+ values:
+ - android
+ - ios
+
+ - name: pool
+ type: object
+ default:
+ name: AcesShared
+
+variables:
+ - template: /eng/pipelines/common/variables.yml@self
+ - name: Codeql.Enabled
+ value: false
+ - name: Codeql.SkipTaskAutoInjection
+ value: true
+
+stages:
+ - stage: ReviewPR
+ displayName: 'Review Pull Request'
+ jobs:
+ - job: CopilotReview
+ displayName: 'Run Copilot PR Reviewer Agent'
+ pool: ${{ parameters.pool }}
+ timeoutInMinutes: 180
+ steps:
+ - checkout: self
+ fetchDepth: 0
+ persistCredentials: true
+
+ - script: |
+ echo "Validating PR Number parameter..."
+ if [ -z "${{ parameters.PRNumber }}" ]; then
+ echo "##vso[task.logissue type=error]PRNumber parameter is required"
+ exit 1
+ fi
+ echo "PR Number: ${{ parameters.PRNumber }}"
+ displayName: 'Validate Parameters'
+
+ - script: |
+ echo "##vso[build.updatebuildnumber]PR ${{ parameters.PRNumber }} ${{ parameters.Platform }}"
+ displayName: 'Set Pipeline Run Title'
+
+ # Provision environment (Xcode, .NET SDK, Android SDK, etc.)
+ - template: common/provision.yml
+ parameters:
+ skipXcode: false
+ skipProvisionator: false
+ skipAndroidCommonSdks: ${{ eq(parameters.Platform, 'ios') }}
+ skipAndroidPlatformApis: ${{ eq(parameters.Platform, 'ios') }}
+ skipJdk: ${{ eq(parameters.Platform, 'ios') }}
+ skipSimulatorSetup: false
+ skipCertificates: true
+ # Android emulator setup (skip for iOS)
+ skipAndroidEmulatorImages: ${{ eq(parameters.Platform, 'ios') }}
+ skipAndroidCreateAvds: ${{ eq(parameters.Platform, 'ios') }}
+ androidEmulatorApiLevel: '30'
+
+ # Configure AVD for hardware acceleration (AcesShared ARM64 macOS agents have HVF)
+ # Match maui-pr Cake script: no GPU override on macOS (uses default hw GPU)
+ - script: |
+ echo "=== Configuring AVD for hardware-accelerated emulation ==="
+ AVD_CONFIG="$HOME/.android/avd/Emulator_30.avd/config.ini"
+ if [ -f "$AVD_CONFIG" ]; then
+ echo "Found AVD config: $AVD_CONFIG"
+ cat "$AVD_CONFIG"
+ else
+ echo "##vso[task.logissue type=warning]AVD config not found at: $AVD_CONFIG"
+ ls -la "$HOME/.android/avd/" 2>/dev/null || echo "AVD directory not found"
+ fi
+ displayName: 'Configure AVD'
+ condition: eq('${{ parameters.Platform }}', 'android')
+
+ # Set up Android SDK PATH (required on self-hosted agents)
+ - pwsh: |
+ if ($env:ANDROID_SDK_ROOT) {
+ $platformTools = Join-Path $env:ANDROID_SDK_ROOT "platform-tools"
+ $emulatorPath = Join-Path $env:ANDROID_SDK_ROOT "emulator"
+ $cmdlineTools = Join-Path $env:ANDROID_SDK_ROOT "cmdline-tools/latest/bin"
+
+ # Use Azure Pipelines' prependpath command to persist across steps
+ Write-Host "##vso[task.prependpath]$platformTools"
+ Write-Host "##vso[task.prependpath]$emulatorPath"
+ Write-Host "##vso[task.prependpath]$cmdlineTools"
+
+ Write-Host "Added to PATH (will apply to subsequent steps):"
+ Write-Host " platform-tools: $platformTools"
+ Write-Host " emulator: $emulatorPath"
+ Write-Host " cmdline-tools: $cmdlineTools"
+
+ # Verify the tools exist
+ if (Test-Path (Join-Path $platformTools "adb")) {
+ Write-Host "✅ adb found at: $platformTools/adb"
+ } else {
+ Write-Host "⚠️ adb NOT found at expected location"
+ }
+ if (Test-Path (Join-Path $emulatorPath "emulator")) {
+ Write-Host "✅ emulator found at: $emulatorPath/emulator"
+ } else {
+ Write-Host "⚠️ emulator NOT found at expected location"
+ }
+ } else {
+ Write-Host "##vso[task.logissue type=warning]ANDROID_SDK_ROOT not set, skipping PATH setup"
+ }
+
+ # Auto-detect and set JAVA_HOME if not already set
+ if (-not $env:JAVA_HOME) {
+ $jvmDir = "/Library/Java/JavaVirtualMachines"
+ $msJdk = Get-ChildItem "$jvmDir/microsoft-*.jdk" -ErrorAction SilentlyContinue | Select-Object -First 1
+ if ($msJdk) {
+ $javaHome = "$($msJdk.FullName)/Contents/Home"
+ Write-Host "##vso[task.setvariable variable=JAVA_HOME]$javaHome"
+ Write-Host "Set JAVA_HOME to: $javaHome"
+ }
+ }
+ displayName: 'Configure Android SDK PATH'
+ condition: eq('${{ parameters.Platform }}', 'android')
+ env:
+ ANDROID_SDK_ROOT: $(ANDROID_SDK_ROOT)
+
+ # Start Android Emulator EARLY (using Start-Emulator.ps1 script)
+ - pwsh: |
+ Write-Host "=== Starting Android Emulator ==="
+ Write-Host "Working directory: $(Get-Location)"
+ Write-Host "ANDROID_SDK_ROOT: $env:ANDROID_SDK_ROOT"
+ Write-Host "JAVA_HOME: $env:JAVA_HOME"
+ Write-Host "PATH (first 500 chars): $($env:PATH.Substring(0, [Math]::Min(500, $env:PATH.Length)))"
+
+ # Verify adb is in path
+ $adbPath = Get-Command adb -ErrorAction SilentlyContinue
+ if ($adbPath) {
+ Write-Host "adb found at: $($adbPath.Source)"
+ } else {
+ Write-Host "adb NOT in PATH, checking manually..."
+ $manualAdb = Join-Path $env:ANDROID_SDK_ROOT "platform-tools/adb"
+ if (Test-Path $manualAdb) {
+ Write-Host "adb exists at $manualAdb but not in PATH"
+ }
+ }
+
+ $scriptPath = ".github/scripts/shared/Start-Emulator.ps1"
+ Write-Host "Script path: $scriptPath"
+
+ if (-not (Test-Path $scriptPath)) {
+ Write-Host "##vso[task.logissue type=error]Script not found: $scriptPath"
+ exit 1
+ }
+ Write-Host "Script exists: OK"
+
+ $ErrorActionPreference = "Continue"
+ try {
+ Write-Host "Invoking Start-Emulator.ps1 -Platform android -DeviceUdid Emulator_30..."
+ & "./$scriptPath" -Platform android -DeviceUdid Emulator_30 2>&1 | ForEach-Object { Write-Host $_ }
+ $exitCode = $LASTEXITCODE
+ Write-Host "Script returned exit code: $exitCode"
+ } catch {
+ Write-Host "##vso[task.logissue type=error]Exception: $_"
+ Write-Host $_.ScriptStackTrace
+ $exitCode = 1
+ }
+
+ if ($exitCode -ne 0) {
+ Write-Host "##vso[task.logissue type=error]Start-Emulator failed with code $exitCode"
+ exit $exitCode
+ }
+
+ Write-Host "=== Android Emulator Started Successfully ==="
+ displayName: 'Start Android Emulator'
+ condition: eq('${{ parameters.Platform }}', 'android')
+ timeoutInMinutes: 35
+ env:
+ ANDROID_SDK_ROOT: $(ANDROID_SDK_ROOT)
+
+ # Install .NET and workloads via build.ps1
+ - pwsh: ./build.ps1 --target=dotnet --configuration="Release" --verbosity=diagnostic
+ displayName: 'Install .NET and workloads'
+ retryCountOnTaskFailure: 2
+ env:
+ DOTNET_TOKEN: $(dotnetbuilds-internal-container-read-token)
+ PRIVATE_BUILD: $(PrivateBuild)
+
+ - pwsh: echo "##vso[task.prependpath]$(DotNet.Dir)"
+ displayName: 'Add .NET to PATH'
+
+ # Build MSBuild tasks (required for MAUI builds)
+ - pwsh: ./build.ps1 --target=dotnet-buildtasks --configuration="Release" --verbosity=diagnostic
+ displayName: 'Build MSBuild Tasks'
+ retryCountOnTaskFailure: 1
+ env:
+ DOTNET_TOKEN: $(dotnetbuilds-internal-container-read-token)
+ PRIVATE_BUILD: $(PrivateBuild)
+
+ # Verify environment is ready
+ - script: |
+ echo "=== Verifying Build Environment ==="
+ ERRORS=""
+
+ # Check .NET SDK
+ echo "Checking .NET SDK..."
+ if ! dotnet --version; then
+ ERRORS="${ERRORS}\n- .NET SDK not available"
+ else
+ echo "✓ .NET SDK: $(dotnet --version)"
+ fi
+
+ # Check Android SDK (only for Android platform)
+ if [ "${{ parameters.Platform }}" = "android" ]; then
+ echo "Checking Android SDK..."
+ if [ -z "$ANDROID_HOME" ] && [ -z "$ANDROID_SDK_ROOT" ]; then
+ ERRORS="${ERRORS}\n- ANDROID_HOME/ANDROID_SDK_ROOT not set"
+ else
+ SDK_PATH="${ANDROID_HOME:-$ANDROID_SDK_ROOT}"
+ if [ ! -d "$SDK_PATH" ]; then
+ ERRORS="${ERRORS}\n- Android SDK directory not found: $SDK_PATH"
+ else
+ echo "✓ Android SDK: $SDK_PATH"
+ fi
+ fi
+ fi
+
+ # Check Xcode (macOS only)
+ if [ "$(uname)" = "Darwin" ]; then
+ echo "Checking Xcode..."
+ if ! xcodebuild -version; then
+ ERRORS="${ERRORS}\n- Xcode not available"
+ else
+ echo "✓ Xcode available"
+ fi
+
+ # Check iOS simulators
+ echo "Checking iOS simulators..."
+ if ! xcrun simctl list devices available | grep -q "iPhone"; then
+ ERRORS="${ERRORS}\n- No iOS simulators available"
+ else
+ echo "✓ iOS simulators available"
+ fi
+ fi
+
+ # Check Java/JDK (only for Android platform)
+ if [ "${{ parameters.Platform }}" = "android" ]; then
+ echo "Checking JDK..."
+ if ! java -version 2>&1; then
+ ERRORS="${ERRORS}\n- JDK not available"
+ else
+ echo "✓ JDK available"
+ fi
+ fi
+
+ # Report errors
+ if [ -n "$ERRORS" ]; then
+ echo ""
+ echo "##vso[task.logissue type=error]=== Environment Verification FAILED ==="
+ echo -e "Missing dependencies:$ERRORS"
+ echo "##vso[task.logissue type=error]Build environment is not properly configured. See above for details."
+ exit 1
+ fi
+
+ echo ""
+ echo "=== Environment Verification PASSED ==="
+ displayName: 'Verify Build Environment'
+
+
+ # Restore .NET tools (includes xharness)
+ - script: |
+ echo "Restoring .NET tools..."
+ dotnet tool restore
+ echo "Tools restored successfully"
+ displayName: 'Restore .NET Tools'
+
+ # Boot Android emulator for UI tests
+ - script: |
+ echo "=== Booting Android Emulator ==="
+
+ # Match maui-pr Cake script: on macOS use default GPU (hardware-accelerated),
+ # only use swiftshader on Linux. AcesShared agents are ARM64 macOS with HVF.
+ # Start emulator in background with logging for debugging
+ EMULATOR_LOG="/tmp/emulator-boot.log"
+ nohup $ANDROID_SDK_ROOT/emulator/emulator -avd Emulator_30 -no-window -no-snapshot -no-audio -no-boot-anim > "$EMULATOR_LOG" 2>&1 &
+ EMULATOR_PID=$!
+ echo "Emulator started with PID: $EMULATOR_PID"
+
+ # Give emulator a moment to start and verify process is running
+ sleep 3
+ if ! pgrep -f 'qemu-system' > /dev/null; then
+ echo "##vso[task.logissue type=error]Emulator process did not start"
+ echo "=== Emulator Log ==="
+ cat "$EMULATOR_LOG" 2>/dev/null || echo "No log available"
+ exit 1
+ fi
+ echo "Emulator process verified running"
+
+ # Wait for device to appear with timeout (don't use adb wait-for-device - can hang forever)
+ echo "Waiting for emulator device..."
+ device_timeout=60
+ device_waited=0
+ while ! adb devices | grep -q "emulator.*device"; do
+ sleep 2
+ device_waited=$((device_waited + 2))
+ if [ $device_waited -ge $device_timeout ]; then
+ echo "##vso[task.logissue type=error]Emulator device did not appear in time"
+ echo "=== Emulator Log (last 50 lines) ==="
+ tail -50 "$EMULATOR_LOG" 2>/dev/null || echo "No log available"
+ adb devices -l
+ exit 1
+ fi
+ done
+ echo "Emulator device detected"
+
+ # Wait for boot_completed
+ echo "Waiting for emulator to finish booting..."
+ timeout=600
+ waited=0
+ while [ "$(adb shell getprop sys.boot_completed 2>/dev/null)" != "1" ]; do
+ sleep 2
+ waited=$((waited + 2))
+ echo "Waiting for boot... ($waited/$timeout seconds)"
+ if [ $waited -ge $timeout ]; then
+ echo "##vso[task.logissue type=error]Emulator did not boot in time"
+ echo "=== Emulator Log (last 50 lines) ==="
+ tail -50 "$EMULATOR_LOG" 2>/dev/null || echo "No log available"
+ adb devices -l
+ exit 1
+ fi
+ done
+ echo "Boot completed flag set"
+
+ # Wait for package manager service to be available (critical for app installation)
+ echo "Waiting for package manager service..."
+ pm_timeout=120
+ pm_waited=0
+ while ! adb shell pm list packages 2>/dev/null | grep -q "package:"; do
+ sleep 3
+ pm_waited=$((pm_waited + 3))
+ echo "Waiting for package manager... ($pm_waited/$pm_timeout seconds)"
+ if [ $pm_waited -ge $pm_timeout ]; then
+ echo "##vso[task.logissue type=error]Package manager service did not start"
+ echo "=== Checking services ==="
+ adb shell service list 2>/dev/null | head -20 || echo "Cannot list services"
+ exit 1
+ fi
+ done
+ echo "Package manager service is ready"
+
+ echo "=== Emulator booted successfully! ==="
+ adb devices -l
+ displayName: 'Boot Android Emulator'
+ condition: eq('${{ parameters.Platform }}', 'android')
+ continueOnError: true
+ timeoutInMinutes: 15
+ env:
+ ANDROID_SDK_ROOT: $(ANDROID_SDK_ROOT)
+ JAVA_HOME: $(JAVA_HOME_17_X64)
+
+ - script: |
+ echo "Installing Node.js 22..."
+ brew install node@22
+ brew link --overwrite node@22
+ if ! node --version; then
+ echo "##vso[task.logissue type=error]Failed to install Node.js"
+ exit 1
+ fi
+ npm --version
+ echo "Node.js installed successfully"
+ displayName: 'Install Node.js'
+
+ - script: |
+ echo "Installing Appium and platform driver..."
+
+ # Get npm global bin directory and add to PATH
+ NPM_BIN=$(npm config get prefix)/bin
+ echo "NPM global bin directory: $NPM_BIN"
+ export PATH="$NPM_BIN:$PATH"
+
+ # Install Appium globally
+ npm install -g appium
+
+ # Install both drivers — the agent may switch platforms if the bug
+ # only affects one platform (e.g., iOS bug triggered via Android run)
+ echo "Installing UiAutomator2 driver for Android..."
+ appium driver install uiautomator2
+ echo "Installing XCUITest driver for iOS..."
+ appium driver install xcuitest
+
+ # Verify installation
+ if ! which appium; then
+ echo "##vso[task.logissue type=error]Failed to install Appium"
+ exit 1
+ fi
+
+ APPIUM_PATH=$(which appium)
+ echo "Appium path: $APPIUM_PATH"
+ echo "Appium version: $(appium --version)"
+ echo "Appium drivers installed:"
+ appium driver list --installed
+
+ # Export PATH for subsequent steps (Azure DevOps specific)
+ echo "##vso[task.prependpath]$NPM_BIN"
+
+ echo "Appium installed successfully"
+ displayName: 'Install Appium'
+
+ - script: |
+ echo "Installing GitHub CLI..."
+ brew install gh
+ if ! gh --version; then
+ echo "##vso[task.logissue type=error]Failed to install GitHub CLI"
+ exit 1
+ fi
+ echo "GitHub CLI installed successfully"
+ displayName: 'Install GitHub CLI'
+
+ - script: |
+ echo "Authenticating with GitHub CLI..."
+ if [ -z "$(GH_CLI_TOKEN)" ]; then
+ echo "##vso[task.logissue type=error]GH_CLI_TOKEN is not set. Please configure the pipeline variable."
+ exit 1
+ fi
+ echo "$(GH_CLI_TOKEN)" | gh auth login --with-token
+ if ! gh auth status; then
+ echo "##vso[task.logissue type=error]GitHub CLI authentication failed"
+ exit 1
+ fi
+ displayName: 'Authenticate GitHub CLI'
+ env:
+ GH_CLI_TOKEN: $(GH_CLI_TOKEN)
+
+ - script: |
+ echo "Installing GitHub Copilot CLI..."
+ npm install -g @github/copilot
+ if ! which copilot; then
+ echo "##vso[task.logissue type=error]Failed to install GitHub Copilot CLI"
+ exit 1
+ fi
+ echo "Copilot CLI installed successfully"
+ displayName: 'Install GitHub Copilot CLI'
+
+ # Restart Android emulator to ensure it's fresh before PR Reviewer runs
+ # The emulator can become unstable after running for a long time
+ - script: |
+ echo "=== Restarting Android Emulator for PR Reviewer ==="
+
+ # Kill any existing emulator
+ echo "Killing existing emulator..."
+ pkill -f 'qemu-system' 2>/dev/null || true
+ sleep 5
+
+ # Restart ADB server
+ echo "Restarting ADB server..."
+ adb kill-server
+ sleep 2
+ adb start-server
+ sleep 2
+
+ # Start fresh emulator
+ EMULATOR_LOG="/tmp/emulator-fresh.log"
+ echo "Starting fresh emulator..."
+ nohup $ANDROID_SDK_ROOT/emulator/emulator -avd Emulator_30 -no-window -no-snapshot -no-audio -no-boot-anim > "$EMULATOR_LOG" 2>&1 &
+ EMULATOR_PID=$!
+ echo "Emulator started with PID: $EMULATOR_PID"
+
+ # Wait for device to appear
+ echo "Waiting for emulator device..."
+ device_timeout=300
+ device_waited=0
+ while ! adb devices | grep -q "emulator.*device"; do
+ sleep 5
+ device_waited=$((device_waited + 5))
+ if [ $device_waited -ge $device_timeout ]; then
+ echo "##vso[task.logissue type=error]Emulator device did not appear in time"
+ cat "$EMULATOR_LOG" 2>/dev/null | tail -30
+ exit 1
+ fi
+ echo "Waiting for device... ($device_waited/$device_timeout seconds)"
+ done
+ echo "Emulator device detected"
+
+ # Wait for boot_completed
+ echo "Waiting for boot_completed..."
+ boot_timeout=600
+ boot_waited=0
+ while [ "$(adb shell getprop sys.boot_completed 2>/dev/null)" != "1" ]; do
+ sleep 5
+ boot_waited=$((boot_waited + 5))
+ if [ $boot_waited -ge $boot_timeout ]; then
+ echo "##vso[task.logissue type=error]Emulator did not boot in time"
+ exit 1
+ fi
+ done
+ echo "Boot completed"
+
+ # Wait for package manager
+ echo "Waiting for package manager service..."
+ pm_timeout=120
+ pm_waited=0
+ while ! adb shell pm list packages 2>/dev/null | grep -q "package:"; do
+ sleep 5
+ pm_waited=$((pm_waited + 5))
+ if [ $pm_waited -ge $pm_timeout ]; then
+ echo "##vso[task.logissue type=error]Package manager service did not start"
+ adb shell service list 2>/dev/null | head -20
+ exit 1
+ fi
+ echo "Waiting for package manager... ($pm_waited/$pm_timeout seconds)"
+ done
+
+ echo "=== Fresh Android Emulator Ready! ==="
+ adb devices -l
+ displayName: 'Restart Android Emulator (Fresh)'
+ condition: eq('${{ parameters.Platform }}', 'android')
+ timeoutInMinutes: 15
+ env:
+ ANDROID_SDK_ROOT: $(ANDROID_SDK_ROOT)
+
+ # Boot iOS Simulator (only for iOS platform)
+ # UI test baseline screenshots are captured on iPhone Xs - must use same device
+ - script: |
+ echo "=== Booting iOS Simulator ==="
+
+ # Find the latest stable iOS runtime (prefer 18.x, fallback to 17.x)
+ RUNTIME=$(xcrun simctl list runtimes available --json | jq -r '
+ [.runtimes[] | select(.name | test("iOS 18"))] | sort_by(.version) | last | .identifier // empty
+ ')
+ if [ -z "$RUNTIME" ]; then
+ RUNTIME=$(xcrun simctl list runtimes available --json | jq -r '
+ [.runtimes[] | select(.name | test("iOS 17"))] | sort_by(.version) | last | .identifier // empty
+ ')
+ fi
+ echo "Selected iOS runtime: $RUNTIME"
+
+ # Look for iPhone Xs (matches UI test baselines - required for snapshot tests)
+ UDID=$(xcrun simctl list devices available --json | jq -r --arg rt "$RUNTIME" '
+ .devices[$rt] // [] |
+ map(select(.name == "iPhone Xs")) |
+ .[0].udid // empty
+ ')
+
+ # If iPhone Xs doesn't exist, try iPhone 11 Pro (same 1125×2436 resolution)
+ if [ -z "$UDID" ]; then
+ UDID=$(xcrun simctl list devices available --json | jq -r --arg rt "$RUNTIME" '
+ .devices[$rt] // [] |
+ map(select(.name == "iPhone 11 Pro")) |
+ .[0].udid // empty
+ ')
+ if [ -n "$UDID" ]; then
+ echo "Found existing iPhone 11 Pro (same resolution as iPhone Xs): $UDID"
+ fi
+ else
+ echo "Found existing iPhone Xs: $UDID"
+ fi
+
+ # If neither exists, try to create them
+ if [ -z "$UDID" ]; then
+ echo "No matching device found - attempting to create one for runtime $RUNTIME..."
+
+ # Try iPhone Xs first
+ UDID=$(xcrun simctl create "iPhone Xs" com.apple.CoreSimulator.SimDeviceType.iPhone-Xs "$RUNTIME" 2>&1)
+ if [ $? -ne 0 ]; then
+ echo "iPhone Xs device type unavailable: $UDID"
+ # Try iPhone 11 Pro (same 1125×2436 resolution)
+ UDID=$(xcrun simctl create "iPhone 11 Pro" com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro "$RUNTIME" 2>&1)
+ if [ $? -ne 0 ]; then
+ echo "##vso[task.logissue type=warning]Failed to create iPhone 11 Pro: $UDID"
+ # Last resort: first available iPhone
+ UDID=$(xcrun simctl list devices available --json | jq -r '
+ .devices | to_entries |
+ map(.value) | flatten |
+ map(select(.name | test("iPhone"))) |
+ .[0].udid
+ ')
+ else
+ echo "Created iPhone 11 Pro simulator: $UDID"
+ fi
+ else
+ echo "Created iPhone Xs simulator: $UDID"
+ fi
+ fi
+
+ if [ -z "$UDID" ]; then
+ echo "##vso[task.logissue type=error]No iOS simulator found"
+ exit 1
+ fi
+
+ # Shutdown any other booted simulators to avoid Appium connecting to wrong device
+ xcrun simctl list devices booted --json | jq -r '
+ .devices | to_entries | map(.value) | flatten |
+ map(select(.state == "Booted" and .udid != "'"$UDID"'")) |
+ .[].udid
+ ' | while read OTHER_UDID; do
+ echo "Shutting down other simulator: $OTHER_UDID"
+ xcrun simctl shutdown "$OTHER_UDID" 2>/dev/null || true
+ done
+
+ echo "Booting simulator: $UDID"
+ xcrun simctl boot "$UDID" 2>/dev/null || echo "Simulator may already be booted"
+ sleep 10
+
+ echo "Booted simulators:"
+ xcrun simctl list devices booted
+
+ echo "##vso[task.setvariable variable=DEVICE_UDID]$UDID"
+ echo "iOS Simulator UDID: $UDID"
+ displayName: 'Boot iOS Simulator'
+ condition: eq('${{ parameters.Platform }}', 'ios')
+ timeoutInMinutes: 5
+
+ - script: |
+ echo "Running Copilot PR Reviewer Agent via Review-PR.ps1..."
+ echo "Reviewing PR #${{ parameters.PRNumber }}..."
+
+ # Configure git identity (required for merge operations on self-hosted agents)
+ git config user.email "copilot-ci@microsoft.com"
+ git config user.name "Copilot CI"
+ echo "Git identity configured"
+
+ # Create Directory.Build.Override.props to skip Xcode version check
+ # AcesShared agents may have a newer Xcode than the .NET iOS SDK expects
+ cp Directory.Build.Override.props.in Directory.Build.Override.props
+ # Insert ValidateXcodeVersion before closing tag
+ sed -i '' 's|| false\n|' Directory.Build.Override.props
+
+ # Create artifacts directory for Copilot outputs
+ mkdir -p $(Build.ArtifactStagingDirectory)/copilot-logs
+
+ # Invoke the PR reviewer using our PowerShell script
+ # The script will merge the PR into the current branch
+ # -PostSummaryComment and -RunFinalize handle posting comments
+ set +e
+ pwsh .github/scripts/Review-PR.ps1 -PRNumber ${{ parameters.PRNumber }} -Platform ${{ parameters.Platform }} -RunFinalize -PostSummaryComment -LogFile "$(Build.ArtifactStagingDirectory)/copilot-logs/copilot_review_output.md"
+ COPILOT_EXIT_CODE=$?
+ set -e
+
+ echo "Review-PR.ps1 exit code: $COPILOT_EXIT_CODE"
+
+ # Terminate any orphaned copilot CLI processes that could hold this step's
+ # stdout fd open and prevent the bash step from exiting.
+ # Only target processes whose command line includes the copilot CLI path.
+ echo "Cleaning up orphaned copilot processes..."
+ SELF_PID=$$
+ for proc in $(pgrep -f "[c]opilot" 2>/dev/null || true); do
+ if [ -n "$proc" ] && [ "$proc" != "$SELF_PID" ]; then
+ PROC_CMD=$(ps -p "$proc" -o args= 2>/dev/null || true)
+ if echo "$PROC_CMD" | grep -q "copilot"; then
+ echo " Stopping copilot process $proc: $PROC_CMD"
+ kill "$proc" 2>/dev/null || true
+ fi
+ fi
+ done
+
+ # Copy any Copilot session files
+ if [ -d "$HOME/.copilot" ]; then
+ echo "Copying Copilot session state..."
+ cp -r "$HOME/.copilot" $(Build.ArtifactStagingDirectory)/copilot-logs/copilot-session-state || true
+ fi
+
+ # Copy CustomAgentLogsTmp if it exists
+ if [ -d "CustomAgentLogsTmp" ]; then
+ echo "Copying CustomAgentLogsTmp..."
+ cp -r CustomAgentLogsTmp $(Build.ArtifactStagingDirectory)/copilot-logs/ || true
+ fi
+
+ # Copy any Review_Feedback files
+ find . -name "Review_Feedback_*.md" -type f -exec cp {} $(Build.ArtifactStagingDirectory)/copilot-logs/ \; 2>/dev/null || true
+
+ # Copy any .github/agent-pr-session files
+ if [ -d ".github/agent-pr-session" ]; then
+ echo "Copying agent-pr-session..."
+ cp -r .github/agent-pr-session $(Build.ArtifactStagingDirectory)/copilot-logs/ || true
+ fi
+
+ # Check for failure indicators in output
+ if [ $COPILOT_EXIT_CODE -ne 0 ]; then
+ echo "##vso[task.logissue type=error]Review-PR.ps1 exited with code $COPILOT_EXIT_CODE"
+ # Don't exit yet - let artifacts be published first
+ echo "##vso[task.setvariable variable=CopilotFailed]true"
+ fi
+
+ # Check output for common failure patterns
+ if grep -qi "error\|failed\|exception" $(Build.ArtifactStagingDirectory)/copilot-logs/copilot_review_output.md 2>/dev/null; then
+ if grep -qi "simulator.*not\|emulator.*not\|workload.*not\|sdk.*not found" $(Build.ArtifactStagingDirectory)/copilot-logs/copilot_review_output.md 2>/dev/null; then
+ echo "##vso[task.logissue type=warning]Copilot encountered environment issues. Check artifacts for details."
+ fi
+ fi
+
+ echo "Review output saved to $(Build.ArtifactStagingDirectory)/copilot-logs/"
+ displayName: 'Run PR Reviewer Agent'
+ env:
+ COPILOT_GITHUB_TOKEN: $(COPILOT_TOKEN)
+ GH_TOKEN: $(GH_COMMENT_TOKEN)
+ DEVICE_UDID: $(DEVICE_UDID)
+
+ # Publish Copilot logs and session artifacts
+ - task: PublishPipelineArtifact@1
+ displayName: 'Publish Copilot Logs'
+ inputs:
+ targetPath: '$(Build.ArtifactStagingDirectory)/copilot-logs'
+ artifact: 'CopilotLogs'
+ publishLocation: 'pipeline'
+ condition: succeededOrFailed()
+
+ # Publish build logs if they exist
+ - task: PublishPipelineArtifact@1
+ displayName: 'Publish Build Logs'
+ inputs:
+ targetPath: '$(LogDirectory)'
+ artifact: 'BuildLogs'
+ publishLocation: 'pipeline'
+ condition: and(succeededOrFailed(), ne(variables['LogDirectory'], ''))
+
+ # Fail the pipeline if Copilot failed
+ - script: |
+ if [ "$(CopilotFailed)" = "true" ]; then
+ echo "##vso[task.logissue type=error]Copilot PR review failed. Check CopilotLogs artifact for details."
+ exit 1
+ fi
+ displayName: 'Check Copilot Result'
+ condition: succeededOrFailed()
diff --git a/eng/pipelines/common/provision.yml b/eng/pipelines/common/provision.yml
index 527cd9885306..1f00c37d748e 100644
--- a/eng/pipelines/common/provision.yml
+++ b/eng/pipelines/common/provision.yml
@@ -32,6 +32,7 @@ parameters:
expiryInHours: 1
base64Encode: false
skipInternalFeeds: true
+ skipCertificates: false
steps:
diff --git a/eng/pipelines/common/variables.yml b/eng/pipelines/common/variables.yml
index ed84d69ca591..8dee742a2525 100644
--- a/eng/pipelines/common/variables.yml
+++ b/eng/pipelines/common/variables.yml
@@ -56,14 +56,7 @@ variables:
- group: MAUI # This is the main MAUI variable group that contains secrets for the apple certificate
-# Variable groups required for all builds
-- ${{ if and(ne(variables['Build.DefinitionName'], 'maui-pr'), ne(variables['Build.DefinitionName'], 'dotnet-maui'), ne(variables['Build.DefinitionName'], 'maui-pr-devicetests'), ne(variables['Build.DefinitionName'], 'maui-pr-uitests')) }}:
- - group: maui-provisionator # This is just needed for the provisionator
-
-
-- ${{ if or(eq(variables['System.TeamProject'], 'DevDiv'), eq(variables['Build.DefinitionName'], 'dotnet-maui'), eq(variables['Build.DefinitionName'], 'dotnet-maui-build')) }}:
- - name: internalProvisioning
- value: true
+- ${{ if or(eq(variables['Build.DefinitionName'], 'dotnet-maui'), eq(variables['Build.DefinitionName'], 'dotnet-maui-build')) }}:
- ${{ if notin(variables['Build.Reason'], 'PullRequest') }}:
- name: PrivateBuild
value: false
@@ -73,13 +66,11 @@ variables:
value: true
- name: _SignType
value: real
-
- - group: AzureDevOps-Artifact-Feeds-Pats
-
-- ${{ if eq(variables['Build.DefinitionName'], 'dotnet-maui') }}:
- - ${{ if notin(variables['Build.Reason'], 'PullRequest') }}:
- # Publish-Build-Assets provides: MaestroAccessToken, BotAccount-dotnet-maestro-bot-PAT
- # DotNet-HelixApi-Access provides: HelixApiAccessToken
- - group: Publish-Build-Assets
- group: DotNet-HelixApi-Access
- group: SDL_Settings
+ - group: AzureDevOps-Artifact-Feeds-Pats
+ - ${{ if eq(variables['Build.DefinitionName'], 'dotnet-maui') }}:
+ - group: Publish-Build-Assets # This variable group contains secrets to publis to BAR
+
+
+