Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,16 @@ The repository includes specialized custom agents for specific tasks. These agen
- **Trigger phrases**: "test this PR", "validate PR #XXXXX in Sandbox", "reproduce issue #XXXXX", "try out in Sandbox"
- **Do NOT use for**: Code review (use pr agent), writing automated tests (use uitest-coding-agent)

### Reusable Skills

Skills are modular capabilities that can be invoked directly or used by agents. Located in `.github/skills/`:

1. **issue-triage** (`.github/skills/issue-triage/SKILL.md`)
- **Purpose**: Query and triage open issues that need milestones, labels, or investigation
- **Trigger phrases**: "find issues to triage", "show me old Android issues", "what issues need attention", "triage Android issues"
- **Scripts**: `init-triage-session.ps1`, `query-issues.ps1`, `record-triage.ps1`
- **Used by**: Any agent or direct invocation

### Using Custom Agents

**Delegation Policy**: When user request matches agent trigger phrases, **ALWAYS delegate to the appropriate agent immediately**. Do not ask for permission or explain alternatives unless the request is ambiguous.
Expand Down
162 changes: 162 additions & 0 deletions .github/skills/issue-triage/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
---
name: issue-triage
description: Queries and triages open GitHub issues that need attention. Helps identify issues needing milestones, labels, or investigation.
metadata:
author: dotnet-maui
version: "2.1"
compatibility: Requires GitHub CLI (gh) authenticated with access to dotnet/maui repository.
---

# Issue Triage Skill

This skill helps triage open GitHub issues in the dotnet/maui repository by:
1. Initializing a session with current milestones and labels
2. Loading a batch of issues into memory
3. Presenting issues ONE AT A TIME for triage decisions
4. Suggesting milestones based on issue characteristics
5. Tracking progress through a triage session

## When to Use

- "Find issues to triage"
- "Let's triage issues"
- "Grab me 10 issues to triage"
- "Triage Android issues"

## Triage Workflow

### Step 1: Initialize Session

Start by initializing a session to load current milestones and labels:

```bash
pwsh .github/skills/issue-triage/scripts/init-triage-session.ps1
```

### Step 2: Load Issues Into Memory

Load a batch of issues (e.g., 20) but DO NOT display them all. Store them for one-at-a-time presentation:

```bash
pwsh .github/skills/issue-triage/scripts/query-issues.ps1 -Limit 100 -OutputFormat triage
```

### Step 3: Present ONE Issue at a Time

**IMPORTANT**: When user asks to triage, present only ONE issue at a time in this format:

```markdown
## Issue #XXXXX

**[Title]**

🔗 [URL]

| Field | Value |
|-------|-------|
| **Author** | username (Syncfusion if applicable) |
| **Platform** | platform |
| **Area** | area |
| **Labels** | labels |
| **Linked PR** | PR info with milestone if available |
| **Regression** | Yes/No |
| **Comments** | count |

**Comment Summary** (if any):
- [Author] Comment preview...

**My Suggestion**: `Milestone` - Reason

---

What would you like to do with this issue?
```

### Step 4: Wait for User Decision

Wait for user to say:
- A milestone name (e.g., "Backlog", "current SR", "Servicing")
- "yes" to accept suggestion
- "skip" or "next" to move on without changes
- Specific instructions (e.g., "next SR and add regression label")

### Step 5: Apply Changes and Move to Next

After applying changes, automatically present the NEXT issue.

## Script Parameters

### query-issues.ps1

| Parameter | Values | Default | Description |
|-----------|--------|---------|-------------|
| `-Platform` | android, ios, windows, maccatalyst, all | all | Filter by platform |
| `-Area` | Any area label (e.g., collectionview, shell) | "" | Filter by area |
| `-Limit` | 1-1000 | 50 | Maximum issues to fetch |
| `-Skip` | 0+ | 0 | Skip first N issues (for pagination) |
| `-OutputFormat` | table, json, markdown, triage | table | Output format |
| `-RequireAreaLabel` | switch | false | Only return issues with area-* labels |
| `-SkipDetails` | switch | false | Skip fetching PRs/comments (faster) |

## Milestone Suggestion Logic

The script dynamically queries current milestones from dotnet/maui and suggests them based on issue characteristics:

| Condition | Suggested Milestone | Reason |
|-----------|---------------------|--------|
| Linked PR has milestone | PR's milestone | "PR already has milestone" |
| Has `i/regression` label | Current SR milestone (soonest due) | "Regression - current SR milestone" |
| Has open linked PR | Servicing milestone (or next SR) | "Has open PR" |
| Default | Backlog | "No PR, not a regression" |

**Note**: SR milestones are sorted by due date, so the soonest SR is suggested for regressions. Milestone names change monthly, so they are queried dynamically rather than hardcoded.

## Applying Triage Decisions

```bash
# Set milestone only
gh issue edit ISSUE_NUMBER --repo dotnet/maui --milestone "Backlog"

# Set milestone and add labels
gh issue edit ISSUE_NUMBER --repo dotnet/maui --milestone "MILESTONE_NAME" --add-label "i/regression"

# Set milestone on both issue AND linked PR
gh issue edit ISSUE_NUMBER --repo dotnet/maui --milestone "MILESTONE_NAME"
gh pr edit PR_NUMBER --repo dotnet/maui --milestone "MILESTONE_NAME"
```

## Common Milestone Types

| Milestone Type | Use When |
|----------------|----------|
| Current SR (e.g., SR3) | Regressions, critical bugs with PRs ready |
| Next SR (e.g., SR4) | Important bugs, regressions being investigated |
| Servicing | General fixes with PRs, non-urgent improvements |
| Backlog | No PR, not a regression, nice-to-have fixes |

**Note**: Use `init-triage-session.ps1` to see current milestone names.

## Label Quick Reference

**Regression Labels:**
- `i/regression` - Confirmed regression
- `regressed-in-10.0.0` - Specific version

**Priority Labels:**
- `p/0` - Critical
- `p/1` - High
- `p/2` - Medium
- `p/3` - Low

**iOS 26 / macOS 26:**
- `version/iOS-26` - iOS 26 specific issue

## Session Tracking (Optional)

```bash
# Record triaged issue
pwsh .github/skills/issue-triage/scripts/record-triage.ps1 -IssueNumber 33272 -Milestone "Backlog"

# View session stats
cat CustomAgentLogsTmp/Triage/triage-*.json | jq '.Stats'
```
203 changes: 203 additions & 0 deletions .github/skills/issue-triage/scripts/init-triage-session.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
#!/usr/bin/env pwsh
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

The shebang line has incorrect spacing. PowerShell shebangs should be '#!/usr/bin/env pwsh' without extra spaces. The current format with spaces after '#' may not be recognized correctly by some systems.

Copilot uses AI. Check for mistakes.
<#
.SYNOPSIS
Initializes a triage session by loading current milestones, labels, and creating a session tracker.

.DESCRIPTION
This script prepares for an issue triage session by:
1. Querying all open milestones from dotnet/maui
2. Loading common labels for quick reference
3. Creating a session file to track triaged issues

Run this at the start of a triage session to have current milestone/label data available.

.PARAMETER SessionName
Optional name for the triage session (default: timestamp-based)

.PARAMETER OutputDir
Directory to store session files (default: CustomAgentLogsTmp/Triage)

.EXAMPLE
./init-triage-session.ps1
# Initializes a new triage session with defaults

.EXAMPLE
./init-triage-session.ps1 -SessionName "android-triage"
# Creates a named session for Android issue triage
#>

param(
[Parameter(Mandatory = $false)]
[string]$SessionName = "",

[Parameter(Mandatory = $false)]
[string]$OutputDir = "CustomAgentLogsTmp/Triage"
)

$ErrorActionPreference = "Stop"

Write-Host "╔═══════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
Write-Host "║ Initializing Triage Session ║" -ForegroundColor Cyan
Write-Host "╚═══════════════════════════════════════════════════════════╝" -ForegroundColor Cyan

# Create output directory
if (-not (Test-Path $OutputDir)) {
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
}

# Generate session name if not provided
if ($SessionName -eq "") {
$SessionName = "triage-$(Get-Date -Format 'yyyy-MM-dd-HHmm')"
}

$sessionFile = Join-Path $OutputDir "$SessionName.json"

Write-Host ""
Write-Host "Session: $SessionName" -ForegroundColor Green
Write-Host "Output: $sessionFile" -ForegroundColor DarkGray

# Query open milestones
Write-Host ""
Write-Host "Fetching open milestones..." -ForegroundColor Cyan

$milestones = @()
try {
$msResult = gh api repos/dotnet/maui/milestones --jq '.[] | {number, title, due_on, open_issues}' 2>&1
$msLines = $msResult -split "`n" | Where-Object { $_ -match "^\{" }

foreach ($line in $msLines) {
$ms = $line | ConvertFrom-Json
$milestones += [PSCustomObject]@{
Number = $ms.number
Title = $ms.title
DueOn = $ms.due_on
OpenIssues = $ms.open_issues
}
}

# Sort by title for easy reference
$milestones = $milestones | Sort-Object Title

Write-Host " Found $($milestones.Count) open milestones:" -ForegroundColor Green

# Group milestones by type
$srMilestones = $milestones | Where-Object { $_.Title -match "SR\d|Servicing" }
$backlog = $milestones | Where-Object { $_.Title -eq "Backlog" }
$otherMs = $milestones | Where-Object { $_.Title -notmatch "SR\d|Servicing" -and $_.Title -ne "Backlog" }

Write-Host ""
Write-Host " Servicing Releases:" -ForegroundColor Yellow
foreach ($ms in $srMilestones) {
$dueInfo = ""
if ($ms.DueOn -and $ms.DueOn -is [string] -and $ms.DueOn.Length -ge 10) {
$dueInfo = " (due: $($ms.DueOn.Substring(0, 10)))"
}
Write-Host " - $($ms.Title)$dueInfo [$($ms.OpenIssues) open]"
}

if ($backlog) {
Write-Host ""
Write-Host " Backlog:" -ForegroundColor Yellow
Write-Host " - $($backlog.Title) [$($backlog.OpenIssues) open]"
}

if ($otherMs.Count -gt 0) {
Write-Host ""
Write-Host " Other:" -ForegroundColor Yellow
foreach ($ms in $otherMs | Select-Object -First 5) {
Write-Host " - $($ms.Title) [$($ms.OpenIssues) open]"
}
if ($otherMs.Count -gt 5) {
Write-Host " ... and $($otherMs.Count - 5) more"
}
}
}
catch {
Write-Host " Failed to fetch milestones: $_" -ForegroundColor Red
}

# Query common labels
Write-Host ""
Write-Host "Fetching labels..." -ForegroundColor Cyan

$labels = @{
Platforms = @()
Areas = @()
Status = @()
Priority = @()
Regression = @()
Other = @()
}

try {
$labelResult = gh api repos/dotnet/maui/labels --paginate --jq '.[].name' 2>&1
$allLabels = $labelResult -split "`n" | Where-Object { $_ -ne "" }

foreach ($label in $allLabels) {
if ($label -match "^platform/") {
$labels.Platforms += $label
}
elseif ($label -match "^area-") {
$labels.Areas += $label
}
elseif ($label -match "^s/") {
$labels.Status += $label
}
elseif ($label -match "^p/") {
$labels.Priority += $label
}
elseif ($label -match "regression|regressed") {
$labels.Regression += $label
}
}

Write-Host " Platforms: $($labels.Platforms.Count) labels"
Write-Host " Areas: $($labels.Areas.Count) labels"
Write-Host " Status: $($labels.Status.Count) labels"
Write-Host " Priority: $($labels.Priority.Count) labels"
Write-Host " Regression: $($labels.Regression.Count) labels"
}
catch {
Write-Host " Failed to fetch labels: $_" -ForegroundColor Red
}

# Create session object
$session = [PSCustomObject]@{
Name = $SessionName
StartedAt = (Get-Date).ToString("o")
Milestones = $milestones
Labels = $labels
TriagedIssues = @()
Stats = @{
Total = 0
Backlog = 0
Servicing = 0
SR = 0
Skipped = 0
}
Comment on lines +171 to +177
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

The Stats object is accessed as a hashtable with '@{}' syntax but accessed with dot notation later. PowerShell PSCustomObject properties should use consistent syntax. Consider using a PSCustomObject instead of a hashtable for the Stats property to match how it's accessed elsewhere, or ensure consistent bracket notation access.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

PowerShell hashtables support dot notation access, so this works correctly. While using [PSCustomObject] would be more consistent stylistically, the current code functions as expected.

}

# Save session file
$session | ConvertTo-Json -Depth 10 | Out-File -FilePath $sessionFile -Encoding UTF8

Write-Host ""
Write-Host "═══════════════════════════════════════════════════════════" -ForegroundColor Cyan
Write-Host ""
Write-Host "Session initialized! Quick reference:" -ForegroundColor Green
Write-Host ""
Write-Host " Common Milestones:" -ForegroundColor Yellow
$srMilestones | ForEach-Object { Write-Host " $($_.Title)" }
Write-Host " Backlog"
Write-Host ""
Write-Host " Priority Labels:" -ForegroundColor Yellow
$labels.Priority | ForEach-Object { Write-Host " $_" }
Write-Host ""
Write-Host " Regression Labels:" -ForegroundColor Yellow
$labels.Regression | Select-Object -First 5 | ForEach-Object { Write-Host " $_" }
Write-Host ""
Write-Host " Session file: $sessionFile" -ForegroundColor DarkGray
Write-Host ""
Write-Host "Ready to triage! Run query-issues.ps1 to load issues." -ForegroundColor Green

# Return session for pipeline usage
return $session
Loading