Compare commits

...

6 Commits

Author SHA1 Message Date
Gordon Lam (SH)
a7aabac375 fix(skills): make Get-CycleStatus JsonOutput parseable 2026-02-05 10:03:41 -08:00
Gordon Lam (SH)
d37c37fa70 feat(skills): allow copilot model selection 2026-02-05 09:52:48 -08:00
Gordon Lam (SH)
1789b23cd8 fix(skills): improve cycle status signal reporting 2026-02-05 09:28:02 -08:00
Gordon Lam (SH)
e4d1325c5c docs(skills): move parallel examples into scripts 2026-02-05 08:47:02 -08:00
Gordon Lam (SH)
456fe207ca docs(skills/pr-fix): add batch processing guidance with PowerShell parallel 2026-02-05 07:45:38 -08:00
Gordon Lam (SH)
c32c5233ca docs(skills): add critical guidance for parallel execution and PR quality
- Add PowerShell 7 ForEach-Object -Parallel pattern for single terminal execution
- Document that spawning multiple terminals is NOT trackable by the agent
- Add PR quality requirements (no stub code, proper titles, full descriptions)
- Add quality gate checks for worktree validation
- Document the correct copilot CLI invocation pattern
2026-02-05 07:45:38 -08:00
12 changed files with 3217 additions and 0 deletions

220
.github/skills/issue-fix/SKILL.md vendored Normal file
View File

@@ -0,0 +1,220 @@
---
name: issue-fix
description: Automatically fix GitHub issues and create PRs. Use when asked to fix an issue, implement a feature from an issue, auto-fix an issue, apply implementation plan, create code changes for an issue, resolve a GitHub issue, or submit a PR for an issue. Creates isolated git worktree, applies AI-generated fixes, commits changes, and creates pull requests.
license: Complete terms in LICENSE.txt
---
# Issue Fix Skill
Automatically fix GitHub issues by creating isolated worktrees, applying AI-generated code changes, and creating pull requests - the complete issue-to-PR workflow.
## Skill Contents
This skill is **self-contained** with all required resources:
```
.github/skills/issue-fix/
├── SKILL.md # This file
├── LICENSE.txt # MIT License
├── scripts/
│ ├── Start-IssueAutoFix.ps1 # Main fix script (creates worktree, applies fix)
│ ├── Start-IssueFixParallel.ps1 # Parallel runner (single terminal)
│ ├── Get-WorktreeStatus.ps1 # Worktree status helper
│ ├── Submit-IssueFix.ps1 # Commit and create PR
│ └── IssueReviewLib.ps1 # Shared helpers
└── references/
├── fix-issue.prompt.md # AI prompt for fixing
├── create-commit-title.prompt.md # AI prompt for commit messages
├── create-pr-summary.prompt.md # AI prompt for PR descriptions
└── mcp-config.json # MCP configuration
```
## Output
- **Worktrees**: Created at drive root level `Q:/PowerToys-xxxx/`
- **PRs**: Created on GitHub linking to the original issue
- **Signal file**: `Generated Files/issueFix/<issue>/.signal`
## Signal File
On completion, a `.signal` file is created for orchestrator coordination:
```json
{
"status": "success",
"issueNumber": 45363,
"timestamp": "2026-02-04T10:05:23Z",
"worktreePath": "Q:/PowerToys-ab12"
}
```
Status values: `success`, `failure`
## When to Use This Skill
- Fix a specific GitHub issue automatically
- Implement a feature described in an issue
- Apply an existing implementation plan
- Create code changes and submit PR for an issue
- Auto-fix high-confidence issues end-to-end
## Prerequisites
- GitHub CLI (`gh`) installed and authenticated
- Issue must be reviewed first (use `issue-review` skill)
- PowerShell 7+ for running scripts
- Copilot CLI or Claude CLI installed
## Required Variables
| Variable | Description | Example |
|----------|-------------|---------|
| `{{IssueNumber}}` | GitHub issue number to fix | `44044` |
## Workflow
### Step 1: Ensure Issue is Reviewed
If not already reviewed, use the `issue-review` skill first.
### Step 2: Run Auto-Fix
```powershell
# Create worktree and apply fix
.github/skills/issue-fix/scripts/Start-IssueAutoFix.ps1 -IssueNumber {{IssueNumber}} -CLIType copilot -Force
```
This will:
1. Create a new git worktree with branch `issue/{{IssueNumber}}`
2. Copy the review files to the worktree
3. Launch Copilot CLI to implement the fix
4. Build and verify the changes
### Step 3: Submit PR
```powershell
# Commit changes and create PR
.github/skills/issue-fix/scripts/Submit-IssueFix.ps1 -IssueNumber {{IssueNumber}} -CLIType copilot -Force
```
This will:
1. Generate AI commit message
2. Commit all changes
3. Push to origin
4. Create PR with AI-generated description
5. Link PR to issue with "Fixes #{{IssueNumber}}"
### One-Step Alternative
To fix AND submit in one command:
```powershell
.github/skills/issue-fix/scripts/Start-IssueAutoFix.ps1 -IssueNumber {{IssueNumber}} -CLIType copilot -CreatePR -Force
```
## CLI Options
### Start-IssueAutoFix.ps1
| Parameter | Description | Default |
|-----------|-------------|---------|
| `-IssueNumber` | Issue to fix | Required |
| `-CLIType` | AI CLI: `copilot` or `claude` | `copilot` |
| `-Model` | Copilot model (e.g., `gpt-5.2-codex`) | (optional) |
| `-CreatePR` | Auto-create PR after fix | `false` |
| `-SkipWorktree` | Fix in current repo (no worktree) | `false` |
| `-Force` | Skip confirmation prompts | `false` |
### Submit-IssueFix.ps1
| Parameter | Description | Default |
|-----------|-------------|---------|
| `-IssueNumber` | Issue to submit | Required |
| `-CLIType` | AI CLI: `copilot`, `claude`, `manual` | `copilot` |
| `-Draft` | Create as draft PR | `false` |
| `-SkipCommit` | Skip commit (changes already committed) | `false` |
| `-Force` | Skip confirmation prompts | `false` |
## Batch Processing
Fix multiple issues:
```powershell
# Fix multiple issues (creates worktrees, applies fixes)
.github/skills/issue-fix/scripts/Start-IssueAutoFix.ps1 -IssueNumbers 44044, 32950 -CLIType copilot -Force
# Submit all fixed issues as PRs
.github/skills/issue-fix/scripts/Submit-IssueFix.ps1 -CLIType copilot -Force
```
## Parallel Execution (IMPORTANT)
**DO NOT** spawn separate terminals for each issue. Use the dedicated scripts:
```powershell
# Run fixes in parallel (single terminal)
.github/skills/issue-fix/scripts/Start-IssueFixParallel.ps1 -IssueNumbers 28726,13336,27507,3054,37800 -CLIType copilot -ThrottleLimit 5 -Force
# Check worktree status
.github/skills/issue-fix/scripts/Get-WorktreeStatus.ps1
```
This allows:
- Tracking all jobs in one place
- Waiting for completion with proper synchronization
- Controlling parallelism with `-ThrottleLimit`
- Combined output visibility
## Troubleshooting
| Problem | Solution |
|---------|----------|
| Worktree already exists | Use existing worktree or `git worktree remove <path>` |
| No implementation plan | Use `issue-review` skill first |
| Build failures | Check build logs, may need manual intervention |
| PR already exists | Script will skip, check existing PR |
| CLI not found | Install Copilot CLI |
## PR Creation Requirements (CRITICAL)
**NEVER create PRs with placeholder/stub code.** Every PR must have:
1. **Real implementation** - Actual working code that addresses the issue
2. **Proper title** - Follow `create-commit-title.prompt.md` (Conventional Commits)
3. **Full description** - Follow `create-pr-summary.prompt.md` based on actual diff
### PR Title Format (Conventional Commits)
```
feat(module): add feature description
fix(module): fix bug description
docs(module): update documentation
```
### PR Description Must Include
- Summary of changes (from actual diff)
- `Fixes #IssueNumber` link
- Checklist items marked appropriately
- Validation steps performed
**Example of BAD PR (never do this):**
```
Title: fix: address issue #12345
Body: Fixes #12345
Code: class Fix12345 { public void Apply() { } } // EMPTY STUB!
```
**Example of GOOD PR:**
```
Title: feat(peek): add symbolic link resolution for PDF/HTML files
Body: ## Summary
Adds SymlinkResolver helper to resolve symlinks...
[Full description based on create-pr-summary.prompt.md]
```
## Related Skills
| Skill | Purpose |
|-------|---------|
| `issue-review` | Review issues, generate implementation plans |
| `pr-review` | Review the created PR |
| `pr-fix` | Fix PR review comments |

View File

@@ -0,0 +1,22 @@
<#
.SYNOPSIS
Show commit/uncommitted status for issue/* worktrees.
#>
[CmdletBinding()]
param()
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..\..\..')
Set-Location $repoRoot
git worktree list | Select-String "issue/" | ForEach-Object {
$path = ($_ -split "\s+")[0]
$branch = ($_ -split "\s+")[2] -replace "\[|\]",""
$ahead = (git -C $path rev-list main..HEAD --count 2>$null)
$uncommitted = (git -C $path status --porcelain 2>$null | Measure-Object).Count
[pscustomobject]@{
Branch = $branch
CommitsAhead = $ahead
Uncommitted = $uncommitted
Path = $path
}
}

View File

@@ -0,0 +1,562 @@
<#!
.SYNOPSIS
Auto-fix high-confidence issues using worktrees and AI CLI.
.DESCRIPTION
Finds issues with high confidence scores from the review results, creates worktrees
for each, copies the Generated Files, and kicks off the FixIssue agent to implement fixes.
.PARAMETER IssueNumber
Specific issue number to fix. If not specified, finds high-confidence issues automatically.
.PARAMETER MinFeasibilityScore
Minimum Technical Feasibility score (0-100). Default: 70.
.PARAMETER MinClarityScore
Minimum Requirement Clarity score (0-100). Default: 60.
.PARAMETER MaxEffortDays
Maximum effort estimate in days. Default: 2 (Small fixes).
.PARAMETER MaxParallel
Maximum parallel fix jobs. Default: 5 (worktrees are resource-intensive).
.PARAMETER CLIType
AI CLI to use: claude, gh-copilot, or vscode. Auto-detected if not specified.
.PARAMETER Model
Copilot CLI model to use (e.g., gpt-5.2-codex).
.PARAMETER DryRun
List issues without starting fixes.
.PARAMETER SkipWorktree
Fix in the current repository instead of creating worktrees (useful for single issue).
.PARAMETER VSCodeProfile
VS Code profile to use when opening worktrees. Default: Default.
.PARAMETER AutoCommit
Automatically commit changes after successful fix.
.PARAMETER CreatePR
Automatically create a pull request after successful fix.
.EXAMPLE
# Fix a specific issue
./Start-IssueAutoFix.ps1 -IssueNumber 12345
.EXAMPLE
# Find and fix all high-confidence issues (dry run)
./Start-IssueAutoFix.ps1 -DryRun
.EXAMPLE
# Fix issues with very high confidence
./Start-IssueAutoFix.ps1 -MinFeasibilityScore 80 -MinClarityScore 70 -MaxEffortDays 1
.EXAMPLE
# Fix single issue in current repo (no worktree)
./Start-IssueAutoFix.ps1 -IssueNumber 12345 -SkipWorktree
.NOTES
Prerequisites:
- Run Start-BulkIssueReview.ps1 first to generate review files
- GitHub CLI (gh) authenticated
- Claude Code CLI or VS Code with Copilot
Results:
- Worktrees created at ../<RepoName>-<hash>/
- Generated Files copied to each worktree
- Fix agent invoked in each worktree
#>
[CmdletBinding()]
param(
[int]$IssueNumber,
[int]$MinFeasibilityScore = 70,
[int]$MinClarityScore = 60,
[int]$MaxEffortDays = 2,
[int]$MaxParallel = 5,
[ValidateSet('claude', 'copilot', 'gh-copilot', 'vscode', 'auto')]
[string]$CLIType = 'auto',
[string]$Model,
[switch]$DryRun,
[switch]$SkipWorktree,
[Alias('Profile')]
[string]$VSCodeProfile = 'Default',
[switch]$AutoCommit,
[switch]$CreatePR,
[switch]$Force,
[switch]$Help
)
# Load libraries
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
. "$scriptDir/IssueReviewLib.ps1"
# Load worktree library from tools/build
$repoRoot = Get-RepoRoot
$worktreeLib = Join-Path $repoRoot 'tools/build/WorktreeLib.ps1'
if (Test-Path $worktreeLib) {
. $worktreeLib
}
# Show help
if ($Help) {
Get-Help $MyInvocation.MyCommand.Path -Full
return
}
function Start-IssueFixInWorktree {
<#
.SYNOPSIS
Analyze implementation plan and either take action or create worktree for fix.
.DESCRIPTION
First analyzes the implementation plan to determine if:
- Issue is already resolved (close it)
- Issue needs clarification (add comment)
- Issue is a duplicate (close as duplicate)
- Issue is ready to implement (create worktree and fix)
#>
param(
[Parameter(Mandatory)]
[int]$IssueNumber,
[Parameter(Mandatory)]
[string]$SourceRepoRoot,
[string]$CLIType = 'claude',
[string]$Model,
[string]$VSCodeProfile = 'Default',
[switch]$SkipWorktree,
[switch]$DryRun
)
$issueReviewPath = Get-IssueReviewPath -RepoRoot $SourceRepoRoot -IssueNumber $IssueNumber
$overviewPath = Join-Path $issueReviewPath 'overview.md'
$implPlanPath = Join-Path $issueReviewPath 'implementation-plan.md'
# Verify review files exist
if (-not (Test-Path $overviewPath)) {
throw "No overview.md found for issue #$IssueNumber. Run Start-BulkIssueReview.ps1 first."
}
if (-not (Test-Path $implPlanPath)) {
throw "No implementation-plan.md found for issue #$IssueNumber. Run Start-BulkIssueReview.ps1 first."
}
# =====================================
# STEP 1: Analyze the implementation plan
# =====================================
Info "Analyzing implementation plan for issue #$IssueNumber..."
$planStatus = Get-ImplementationPlanStatus -ImplementationPlanPath $implPlanPath
# =====================================
# STEP 2: Execute the recommended action
# =====================================
$actionResult = Invoke-ImplementationPlanAction -IssueNumber $IssueNumber -PlanStatus $planStatus -DryRun:$DryRun
# If we shouldn't proceed with fix, return early
if (-not $actionResult.ShouldProceedWithFix) {
return @{
IssueNumber = $IssueNumber
WorktreePath = $null
Success = $actionResult.Success
ActionTaken = $actionResult.ActionTaken
SkippedCodeFix = $true
}
}
# =====================================
# STEP 3: Proceed with code fix
# =====================================
$workingDir = $SourceRepoRoot
if (-not $SkipWorktree) {
# Use the simplified New-WorktreeFromIssue.cmd which only needs issue number
$worktreeCmd = Join-Path $SourceRepoRoot 'tools/build/New-WorktreeFromIssue.cmd'
Info "Creating worktree for issue #$IssueNumber..."
# Call the cmd script with issue number and -NoVSCode for automation
& cmd /c $worktreeCmd $IssueNumber -NoVSCode
if ($LASTEXITCODE -ne 0) {
throw "Failed to create worktree for issue #$IssueNumber"
}
# Find the created worktree
$entries = Get-WorktreeEntries
$worktreeEntry = $entries | Where-Object { $_.Branch -like "issue/$IssueNumber*" } | Select-Object -First 1
if (-not $worktreeEntry) {
throw "Failed to find worktree for issue #$IssueNumber"
}
$workingDir = $worktreeEntry.Path
Info "Worktree created at: $workingDir"
# Copy Generated Files to worktree
Info "Copying review files to worktree..."
$destReviewPath = Copy-IssueReviewToWorktree -IssueNumber $IssueNumber -SourceRepoRoot $SourceRepoRoot -WorktreePath $workingDir
Info "Review files copied to: $destReviewPath"
# Copy .github/skills folder to worktree (needed for MCP config)
$sourceSkillsPath = Join-Path $SourceRepoRoot '.github/skills'
$destSkillsPath = Join-Path $workingDir '.github/skills'
if (Test-Path $sourceSkillsPath) {
$destGithubPath = Join-Path $workingDir '.github'
if (-not (Test-Path $destGithubPath)) {
New-Item -ItemType Directory -Path $destGithubPath -Force | Out-Null
}
Copy-Item -Path $sourceSkillsPath -Destination $destGithubPath -Recurse -Force
Info "Copied .github/skills to worktree"
}
}
# Build the prompt for the fix agent
$prompt = @"
You are the FixIssue agent. Fix GitHub issue #$IssueNumber.
The implementation plan is at: Generated Files/issueReview/$IssueNumber/implementation-plan.md
The overview is at: Generated Files/issueReview/$IssueNumber/overview.md
Follow the implementation plan exactly. Build and verify after each change.
"@
# Start the fix agent
Info "Starting fix agent for issue #$IssueNumber in $workingDir..."
# MCP config for github-artifacts tools (relative to repo root)
$mcpConfig = '@.github/skills/issue-fix/references/mcp-config.json'
switch ($CLIType) {
'copilot' {
# GitHub Copilot CLI (standalone copilot command)
# -p: Non-interactive prompt mode (exits after completion)
# --yolo: Enable all permissions for automated execution
# -s: Silent mode - output only agent response
# --additional-mcp-config: Load github-artifacts MCP for image/attachment analysis
$copilotArgs = @(
'--additional-mcp-config', $mcpConfig,
'-p', $prompt,
'--yolo',
'-s'
)
if ($Model) {
$copilotArgs += @('--model', $Model)
}
Info "Running: copilot $($copilotArgs -join ' ')"
Push-Location $workingDir
try {
& copilot @copilotArgs
if ($LASTEXITCODE -ne 0) {
Warn "Copilot exited with code $LASTEXITCODE"
}
} finally {
Pop-Location
}
}
'claude' {
$claudeArgs = @(
'--print',
'--dangerously-skip-permissions',
'--prompt', $prompt
)
Start-Process -FilePath 'claude' -ArgumentList $claudeArgs -WorkingDirectory $workingDir -Wait -NoNewWindow
}
'gh-copilot' {
# Use GitHub Copilot CLI via gh extension
# gh copilot suggest requires interactive mode, so we open VS Code with the prompt
Info "GitHub Copilot CLI detected. Opening VS Code with prompt..."
# Create a prompt file in the worktree for easy access
$promptFile = Join-Path $workingDir "Generated Files/issueReview/$IssueNumber/fix-prompt.md"
$promptContent = @"
# Fix Issue #$IssueNumber
## Instructions
$prompt
## Quick Start
1. Read the implementation plan: ``Generated Files/issueReview/$IssueNumber/implementation-plan.md``
2. Read the overview: ``Generated Files/issueReview/$IssueNumber/overview.md``
3. Follow the plan step by step
4. Build and test after each change
"@
Set-Content -Path $promptFile -Value $promptContent -Force
# Open VS Code with the worktree
code --new-window $workingDir --profile $VSCodeProfile
Info "VS Code opened at $workingDir"
Info "Prompt file created at: $promptFile"
Info "Use GitHub Copilot in VS Code to implement the fix."
}
'vscode' {
# Open VS Code and let user manually trigger the fix
code --new-window $workingDir --profile $VSCodeProfile
Info "VS Code opened at $workingDir. Use Copilot to implement the fix."
}
default {
Warn "CLI type '$CLIType' not fully supported for auto-fix. Opening VS Code..."
code --new-window $workingDir --profile $VSCodeProfile
}
}
# Check if any changes were actually made
$hasChanges = $false
Push-Location $workingDir
try {
$uncommitted = git status --porcelain 2>$null
$commitsAhead = git rev-list main..HEAD --count 2>$null
if ($uncommitted -or ($commitsAhead -gt 0)) {
$hasChanges = $true
}
} finally {
Pop-Location
}
return @{
IssueNumber = $IssueNumber
WorktreePath = $workingDir
Success = $true
ActionTaken = 'CodeFixAttempted'
SkippedCodeFix = $false
HasChanges = $hasChanges
}
}
#region Main Script
try {
Info "Repository root: $repoRoot"
# Detect or validate CLI
if ($CLIType -eq 'auto') {
$cli = Get-AvailableCLI
if ($cli) {
$CLIType = $cli.Type
Info "Auto-detected CLI: $($cli.Name)"
} else {
$CLIType = 'vscode'
Info "No CLI detected, will use VS Code"
}
}
# Find issues to fix
$issuesToFix = @()
if ($IssueNumber) {
# Single issue specified
$reviewResult = Get-IssueReviewResult -IssueNumber $IssueNumber -RepoRoot $repoRoot
if (-not $reviewResult.HasOverview -or -not $reviewResult.HasImplementationPlan) {
throw "Issue #$IssueNumber does not have review files. Run Start-BulkIssueReview.ps1 first."
}
$issuesToFix += @{
IssueNumber = $IssueNumber
OverviewPath = $reviewResult.OverviewPath
ImplementationPlanPath = $reviewResult.ImplementationPlanPath
}
} else {
# Find high-confidence issues
Info "`nSearching for high-confidence issues..."
Info " Min Feasibility Score: $MinFeasibilityScore"
Info " Min Clarity Score: $MinClarityScore"
Info " Max Effort: $MaxEffortDays days"
$highConfidence = Get-HighConfidenceIssues `
-RepoRoot $repoRoot `
-MinFeasibilityScore $MinFeasibilityScore `
-MinClarityScore $MinClarityScore `
-MaxEffortDays $MaxEffortDays
if ($highConfidence.Count -eq 0) {
Warn "No high-confidence issues found matching criteria."
Info "Try lowering the score thresholds or increasing MaxEffortDays."
return
}
$issuesToFix = $highConfidence
}
Info "`nIssues ready for auto-fix: $($issuesToFix.Count)"
Info ("-" * 80)
foreach ($issue in $issuesToFix) {
$scores = ""
if ($issue.FeasibilityScore) {
$scores = " [Feasibility: $($issue.FeasibilityScore), Clarity: $($issue.ClarityScore), Effort: $($issue.EffortDays)d]"
}
Info ("#{0,-6}{1}" -f $issue.IssueNumber, $scores)
}
Info ("-" * 80)
# In DryRun mode, still analyze plans but don't take action
if ($DryRun) {
Info "`nAnalyzing implementation plans (dry run)..."
foreach ($issue in $issuesToFix) {
$implPlanPath = Join-Path (Get-IssueReviewPath -RepoRoot $repoRoot -IssueNumber $issue.IssueNumber) 'implementation-plan.md'
if (Test-Path $implPlanPath) {
$planStatus = Get-ImplementationPlanStatus -ImplementationPlanPath $implPlanPath
$color = switch ($planStatus.Action) {
'ImplementFix' { 'Green' }
'CloseIssue' { 'Yellow' }
'AddComment' { 'Cyan' }
'LinkDuplicate' { 'Magenta' }
default { 'Gray' }
}
Write-Host (" #{0,-6} [{1,-20}] -> {2}" -f $issue.IssueNumber, $planStatus.Status, $planStatus.Action) -ForegroundColor $color
if ($planStatus.RelatedPR) {
$prInfo = "PR #$($planStatus.RelatedPR)"
if ($planStatus.ReleasedIn) {
$prInfo += " (released in $($planStatus.ReleasedIn))"
} elseif ($planStatus.Status -eq 'FixedButUnreleased') {
$prInfo += " (merged, awaiting release)"
}
Write-Host " $prInfo" -ForegroundColor DarkGray
}
if ($planStatus.DuplicateOf) {
Write-Host " Duplicate of #$($planStatus.DuplicateOf)" -ForegroundColor DarkGray
}
}
}
Warn "`nDry run mode - no actions taken."
return
}
# Confirm before proceeding (skip if -Force)
if (-not $Force) {
$confirm = Read-Host "`nProceed with fixing $($issuesToFix.Count) issues? (y/N)"
if ($confirm -notmatch '^[yY]') {
Info "Cancelled."
return
}
}
# Process issues
$results = @{
Succeeded = @()
Failed = @()
AlreadyResolved = @()
AwaitingRelease = @()
NeedsClarification = @()
Duplicates = @()
NoChanges = @()
}
foreach ($issue in $issuesToFix) {
try {
Info "`n" + ("=" * 60)
Info "PROCESSING ISSUE #$($issue.IssueNumber)"
Info ("=" * 60)
$result = Start-IssueFixInWorktree `
-IssueNumber $issue.IssueNumber `
-SourceRepoRoot $repoRoot `
-CLIType $CLIType `
-Model $Model `
-VSCodeProfile $VSCodeProfile `
-SkipWorktree:$SkipWorktree `
-DryRun:$DryRun
if ($result.SkippedCodeFix) {
# Action was taken but no code fix (e.g., closed issue, added comment)
switch -Wildcard ($result.ActionTaken) {
'*Closing*' { $results.AlreadyResolved += $issue.IssueNumber }
'*clarification*' { $results.NeedsClarification += $issue.IssueNumber }
'*duplicate*' { $results.Duplicates += $issue.IssueNumber }
'*merged*awaiting*' { $results.AwaitingRelease += $issue.IssueNumber }
'*merged but not yet released*' { $results.AwaitingRelease += $issue.IssueNumber }
default { $results.Succeeded += $issue.IssueNumber }
}
Success "✓ Issue #$($issue.IssueNumber) handled: $($result.ActionTaken)"
}
elseif ($result.HasChanges) {
$results.Succeeded += $issue.IssueNumber
Success "✓ Issue #$($issue.IssueNumber) fix completed with changes"
}
else {
$results.NoChanges += $issue.IssueNumber
Warn "⚠ Issue #$($issue.IssueNumber) fix ran but no code changes were made"
}
}
catch {
Err "✗ Issue #$($issue.IssueNumber) failed: $($_.Exception.Message)"
$results.Failed += $issue.IssueNumber
}
}
# Summary
Info "`n" + ("=" * 80)
Info "AUTO-FIX COMPLETE"
Info ("=" * 80)
Info "Total issues: $($issuesToFix.Count)"
if ($results.Succeeded.Count -gt 0) {
Success "Code fixes: $($results.Succeeded.Count)"
}
if ($results.AlreadyResolved.Count -gt 0) {
Success "Already resolved: $($results.AlreadyResolved.Count) (issues closed)"
}
if ($results.AwaitingRelease.Count -gt 0) {
Info "Awaiting release: $($results.AwaitingRelease.Count) (fix merged, pending release)"
}
if ($results.NeedsClarification.Count -gt 0) {
Warn "Need clarification: $($results.NeedsClarification.Count) (comments added)"
}
if ($results.Duplicates.Count -gt 0) {
Warn "Duplicates: $($results.Duplicates.Count) (issues closed)"
}
if ($results.NoChanges.Count -gt 0) {
Warn "No changes made: $($results.NoChanges.Count)"
}
if ($results.Failed.Count -gt 0) {
Err "Failed: $($results.Failed.Count)"
Err "Failed issues: $($results.Failed -join ', ')"
}
Info ("=" * 80)
if (-not $SkipWorktree -and ($results.Succeeded.Count -gt 0 -or $results.NoChanges.Count -gt 0)) {
Info "`nWorktrees created. Use 'git worktree list' to see all worktrees."
Info "To clean up: Delete-Worktree.ps1 -Branch issue/<number>"
}
# Write signal files for orchestrator
$genFiles = Get-GeneratedFilesPath -RepoRoot $repoRoot
foreach ($issueNum in $results.Succeeded) {
$signalDir = Join-Path $genFiles "issueFix/$issueNum"
if (-not (Test-Path $signalDir)) { New-Item -ItemType Directory -Path $signalDir -Force | Out-Null }
@{
status = "success"
issueNumber = $issueNum
timestamp = (Get-Date).ToString("o")
worktreePath = (git worktree list --porcelain | Select-String "worktree.*issue.$issueNum" | ForEach-Object { $_.Line -replace 'worktree ', '' })
} | ConvertTo-Json | Set-Content "$signalDir/.signal" -Force
}
foreach ($issueNum in $results.Failed) {
$signalDir = Join-Path $genFiles "issueFix/$issueNum"
if (-not (Test-Path $signalDir)) { New-Item -ItemType Directory -Path $signalDir -Force | Out-Null }
@{
status = "failure"
issueNumber = $issueNum
timestamp = (Get-Date).ToString("o")
} | ConvertTo-Json | Set-Content "$signalDir/.signal" -Force
}
return $results
}
catch {
Err "Error: $($_.Exception.Message)"
exit 1
}
#endregion

View File

@@ -0,0 +1,73 @@
<#
.SYNOPSIS
Run issue-fix in parallel from a single terminal.
.PARAMETER IssueNumbers
Issue numbers to fix.
.PARAMETER ThrottleLimit
Maximum parallel tasks.
.PARAMETER CLIType
AI CLI type (copilot/claude/gh-copilot/vscode/auto).
.PARAMETER Model
Copilot CLI model to use (e.g., gpt-5.2-codex).
.PARAMETER Force
Skip confirmation prompts in Start-IssueAutoFix.ps1.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[int[]]$IssueNumbers,
[int]$ThrottleLimit = 5,
[ValidateSet('claude', 'copilot', 'gh-copilot', 'vscode', 'auto')]
[string]$CLIType = 'copilot',
[string]$Model,
[switch]$Force
)
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..\..\..')
$scriptPath = Join-Path $repoRoot '.github\skills\issue-fix\scripts\Start-IssueAutoFix.ps1'
$results = $IssueNumbers | ForEach-Object -Parallel {
param($issue)
$repoRoot = $using:repoRoot
$scriptPath = $using:scriptPath
$cliType = $using:CLIType
$model = $using:Model
$force = $using:Force
Set-Location $repoRoot
$args = @('-IssueNumber', $issue, '-CLIType', $cliType)
if ($model) {
$args += @('-Model', $model)
}
if ($force) {
$args += '-Force'
}
try {
& $scriptPath @args | Out-Default
[pscustomobject]@{
IssueNumber = $issue
ExitCode = $LASTEXITCODE
}
}
catch {
[pscustomobject]@{
IssueNumber = $issue
ExitCode = 1
Error = $_.Exception.Message
}
}
} -ThrottleLimit $ThrottleLimit
$results

View File

@@ -0,0 +1,252 @@
---
name: issue-to-pr-cycle
description: End-to-end orchestration from issue analysis to PR creation and review. This skill is the ORCHESTRATION BRAIN that invokes other skills via CLI and performs VS Code MCP operations directly.
license: Complete terms in LICENSE.txt
---
# Issue-to-PR Full Cycle Skill
**ORCHESTRATION BRAIN** - coordinates other skills and performs VS Code MCP operations.
## Skill Contents
```
.github/skills/issue-to-pr-cycle/
├── SKILL.md # This file (orchestration brain)
├── LICENSE.txt # MIT License
└── scripts/
├── Get-CycleStatus.ps1 # Check status of issues/PRs
├── IssueReviewLib.ps1 # Shared helpers
└── Start-FullIssueCycle.ps1 # Legacy script (phases A-C)
```
**Orchestrates these skills:**
| Skill | Purpose |
|-------|---------|
| `issue-review` | Analyze issues, generate implementation plans |
| `issue-fix` | Create worktrees, apply fixes, create PRs |
| `pr-review` | Comprehensive PR review (13 steps) |
| `pr-fix` | Fix review comments, resolve threads |
## Prerequisites
- GitHub CLI (`gh`) installed and authenticated
- Copilot CLI or Claude CLI installed
- PowerShell 7+
- VS Code with MCP tools (for write operations)
## Required Variables
| Variable | Description | Example |
|----------|-------------|---------|
| `{{IssueNumbers}}` | Issue numbers to process | `45363, 45364` |
| (or) `{{PRNumbers}}` | PR numbers for review/fix loop | `45365, 45366` |
## How This Skill Works
The orchestrator:
1. **Invokes skills via CLI** - kicks off `copilot` CLI (not `gh copilot`) to run each skill
2. **Runs in parallel** - use PowerShell 7 `ForEach-Object -Parallel` in SINGLE terminal
3. **Waits for signals** - polls for `.signal` files indicating completion
4. **Performs VS Code MCP directly** - for operations that require write access (request reviewer, resolve threads)
## Quality Gates (CRITICAL)
**Every PR must pass these quality checks before creation:**
1. **Real Implementation** - NO placeholder/stub code
- Files must contain actual working code
- Empty classes like `class FixXXX { }` are FORBIDDEN
2. **Proper PR Title** - Follow Conventional Commits
- Use `.github/prompts/create-commit-title.prompt.md`
- Format: `feat(module): description` or `fix(module): description`
- NEVER use generic titles like "fix: address issue #12345"
3. **Full PR Description** - Based on actual diff
- Use `.github/prompts/create-pr-summary.prompt.md`
- Run `git diff main...HEAD` to analyze changes
- Fill PR template with real information
4. **Build Verification** - Code must compile
- Run `tools/build/build.cmd` in worktree
- Exit code 0 = success
### Checking Worktree Quality
```powershell
# Check if worktree has real implementation (not stubs)
$files = git diff main --name-only
foreach ($file in $files) {
if ($file -match "src/common/fixes/Fix\d+\.cs") {
Write-Error "STUB FILE DETECTED: $file - Need real implementation"
}
}
```
## Signal Files
Each skill produces a `.signal` file when complete:
| Skill | Signal Location | Status Values |
|-------|-----------------|---------------|
| `issue-review` | `Generated Files/issueReview/<issue>/.signal` | `success`, `failure` |
| `issue-fix` | `Generated Files/issueFix/<issue>/.signal` | `success`, `failure` |
| `pr-review` | `Generated Files/prReview/<pr>/.signal` | `success`, `failure` |
| `pr-fix` | `Generated Files/prFix/<pr>/.signal` | `success`, `partial`, `failure` |
Signal format:
```json
{
"status": "success",
"issueNumber": 45363,
"timestamp": "2026-02-04T10:05:23Z"
}
```
## Architecture
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ ORCHESTRATOR (this skill, VS Code agent) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ issue-review│ │ issue-fix │ │ pr-review │ │ pr-fix │ │
│ │ (CLI) │ │ (CLI) │ │ (CLI) │ │ (CLI) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Signal Files (Generated Files/*/.signal) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ VS Code MCP Operations (orchestrator executes directly): │
│ - mcp_github_request_copilot_review │
│ - gh api graphql (resolve threads) │
│ - Post review comments │
└─────────────────────────────────────────────────────────────────────────────┘
```
## Workflow
### Phase A: Issue Review
Use the orchestration script instead of inline commands:
```powershell
.github/skills/issue-to-pr-cycle/scripts/Start-FullIssueCycle.ps1 -IssueNumbers 45363,45364
```
### Phase B: Issue Fix
Use the parallel runner script:
```powershell
.github/skills/issue-fix/scripts/Start-IssueFixParallel.ps1 -IssueNumbers 45363,45364 -CLIType copilot -ThrottleLimit 5 -Force
```
### Phase C: PR Review
Use the pr-review script for each PR, or run the full cycle script to orchestrate:
```powershell
.github/skills/pr-review/scripts/Start-PRReviewWorkflow.ps1 -PRNumber 45392
```
### Phase D: Review/Fix Loop (VS Code Agent Orchestrated)
This phase requires the VS Code agent to:
**D1: Request Copilot review (VS Code MCP)**
```
mcp_github_request_copilot_review:
owner: microsoft
repo: PowerToys
pullNumber: {{PRNumber}}
```
**D2: Invoke pr-review skill (CLI, parallel)**
```powershell
gh copilot -p "Run skill pr-review for PR #{{PRNumber}}"
# Wait for: Generated Files/prReview/{{PRNumber}}/.signal
```
**D3: Check results**
- Read `Generated Files/prReview/{{PRNumber}}/00-OVERVIEW.md`
- Query unresolved threads via GraphQL
**D4: Post comments (VS Code MCP) - if medium+ severity**
**D5: Invoke pr-fix skill in WORKTREE (CLI)**
```powershell
# Find worktree for this PR's branch
$branch = (gh pr view {{PRNumber}} --json headRefName -q .headRefName)
$worktree = git worktree list --porcelain | Select-String "worktree.*$branch" | ...
# Run fix in worktree
cd $worktreePath
gh copilot -p "Run skill pr-fix for PR #{{PRNumber}}"
# Wait for: Generated Files/prFix/{{PRNumber}}/.signal
```
**D6: Resolve threads (VS Code MCP)**
```powershell
# Get thread IDs
gh api graphql -f query='query { repository(owner:"microsoft",name:"PowerToys") {
pullRequest(number:{{PRNumber}}) { reviewThreads(first:50) { nodes { id isResolved } } }
} }'
# Resolve each (VS Code agent executes this)
gh api graphql -f query='mutation { resolveReviewThread(input:{threadId:"{{ID}}"}) { thread { isResolved } } }'
```
**D7: Loop**
- If unresolved issues remain → go to D2
- If all clear → done
## Timeout Handling
Default timeout: 10 minutes per skill invocation.
If no signal file appears within timeout:
1. Check if the skill process is still running
2. If hung, terminate and mark as `timeout`
3. Log failure and continue with other items
## Parallel Execution (CRITICAL)
**DO NOT spawn separate terminals for each operation.** Use the dedicated scripts to run parallel work from a single terminal:
```powershell
# Issue fixes in parallel
.github/skills/issue-fix/scripts/Start-IssueFixParallel.ps1 -IssueNumbers 28726,13336,27507,3054,37800 -CLIType copilot -Model gpt-5.2-codex -ThrottleLimit 5 -Force
# PR fixes in parallel
.github/skills/pr-fix/scripts/Start-PRFixParallel.ps1 -PRNumbers 45256,45257,45285,45286 -CLIType copilot -Model gpt-5.2-codex -ThrottleLimit 3 -Force
```
## Worktree Mapping
The orchestrator must track which worktree belongs to which issue/PR:
```powershell
# Get all worktrees
$worktrees = git worktree list --porcelain | Select-String "worktree|branch" |
ForEach-Object { $_.Line }
# Parse into mapping
# Q:\PowerToys-ab12 → issue/44044
# Q:\PowerToys-cd34 → issue/32950
# Find worktree for issue
$issueNum = 45363
$worktreeLine = git worktree list | Select-String "issue/$issueNum"
$worktreePath = ($worktreeLine -split '\s+')[0]
```
## When to Use This Skill
- Process multiple issues end-to-end
- Automate the full issue → PR → review → fix cycle
- Batch process high-confidence issues
- Run continuous review/fix loops until clean

View File

@@ -0,0 +1,349 @@
<#
.SYNOPSIS
Get the current status of issues/PRs in the issue-to-PR cycle.
.DESCRIPTION
Checks the status of:
- Issue review completion (has overview.md + implementation-plan.md)
- Issue fix completion (has worktree + commits)
- PR creation status (has open PR)
- PR review status (has review files)
- PR active comments count
.PARAMETER IssueNumbers
Array of issue numbers to check status for.
.PARAMETER PRNumbers
Array of PR numbers to check status for.
.PARAMETER CheckAll
Check all issues with review data and all open PRs with issue/* branches.
.EXAMPLE
./Get-CycleStatus.ps1 -IssueNumbers 44044, 32950
.EXAMPLE
./Get-CycleStatus.ps1 -PRNumbers 45234, 45235
.EXAMPLE
./Get-CycleStatus.ps1 -CheckAll
#>
[CmdletBinding()]
param(
[int[]]$IssueNumbers = @(),
[int[]]$PRNumbers = @(),
[switch]$CheckAll,
[switch]$JsonOutput
)
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
. (Join-Path $scriptDir 'IssueReviewLib.ps1')
$repoRoot = Get-RepoRoot
$genFiles = Get-GeneratedFilesPath -RepoRoot $repoRoot
$worktreeLib = Join-Path $repoRoot 'tools/build/WorktreeLib.ps1'
if (Test-Path $worktreeLib) {
. $worktreeLib
}
function Get-IssueStatus {
param([int]$IssueNumber)
$status = @{
IssueNumber = $IssueNumber
HasReview = $false
HasImplementationPlan = $false
FeasibilityScore = 0
ClarityScore = 0
EffortDays = 0
HasWorktree = $false
WorktreePath = $null
HasCommits = $false
CommitCount = 0
HasPR = $false
PRNumber = 0
PRState = $null
PRUrl = $null
ReviewSignalStatus = $null
ReviewSignalTimestamp = $null
FixSignalStatus = $null
FixSignalTimestamp = $null
}
# Check review status
$reviewDir = Join-Path $genFiles "issueReview/$IssueNumber"
$overviewPath = Join-Path $reviewDir 'overview.md'
$implPlanPath = Join-Path $reviewDir 'implementation-plan.md'
if (Test-Path $overviewPath) {
$status.HasReview = $true
$overview = Get-Content $overviewPath -Raw
if ($overview -match 'Technical Feasibility[^\d]*(\d+)/100') {
$status.FeasibilityScore = [int]$Matches[1]
}
if ($overview -match 'Requirement Clarity[^\d]*(\d+)/100') {
$status.ClarityScore = [int]$Matches[1]
}
if ($overview -match 'Effort Estimate[^|]*\|\s*[\d.]+(?:-(\d+))?\s*days?') {
$status.EffortDays = if ($Matches[1]) { [int]$Matches[1] } else { 1 }
}
}
if (Test-Path $implPlanPath) {
$status.HasImplementationPlan = $true
}
# Check review signal
$reviewSignalPath = Join-Path $reviewDir '.signal'
if (Test-Path $reviewSignalPath) {
try {
$reviewSignal = Get-Content $reviewSignalPath -Raw | ConvertFrom-Json
$status.ReviewSignalStatus = $reviewSignal.status
$status.ReviewSignalTimestamp = $reviewSignal.timestamp
}
catch {}
}
# Check worktree status
$worktrees = Get-WorktreeEntries | Where-Object { $_.Branch -like "issue/$IssueNumber*" }
if ($worktrees) {
$status.HasWorktree = $true
$status.WorktreePath = $worktrees[0].Path
# Check for commits
Push-Location $status.WorktreePath
try {
$commits = git log --oneline "main..HEAD" 2>$null
if ($commits) {
$status.HasCommits = $true
$status.CommitCount = @($commits).Count
}
}
finally {
Pop-Location
}
}
# Check fix signal
$fixSignalPath = Join-Path $genFiles "issueFix/$IssueNumber/.signal"
if (Test-Path $fixSignalPath) {
try {
$fixSignal = Get-Content $fixSignalPath -Raw | ConvertFrom-Json
$status.FixSignalStatus = $fixSignal.status
$status.FixSignalTimestamp = $fixSignal.timestamp
}
catch {}
}
# Check PR status
$prs = gh pr list --head "issue/$IssueNumber" --state all --json number,url,state 2>$null | ConvertFrom-Json
if (-not $prs -or $prs.Count -eq 0) {
# Try searching by issue reference
$prs = gh pr list --search "fixes #$IssueNumber OR closes #$IssueNumber" --state all --json number,url,state --limit 1 2>$null | ConvertFrom-Json
}
if ($prs -and $prs.Count -gt 0) {
$status.HasPR = $true
$status.PRNumber = $prs[0].number
$status.PRState = $prs[0].state
$status.PRUrl = $prs[0].url
}
return $status
}
function Get-PRStatus {
param([int]$PRNumber)
$status = @{
PRNumber = $PRNumber
State = $null
IssueNumber = 0
Branch = $null
HasReviewFiles = $false
ReviewStepCount = 0
HighSeverityCount = 0
MediumSeverityCount = 0
ActiveCommentCount = 0
UnresolvedThreadCount = 0
CopilotReviewRequested = $false
ReviewSignalStatus = $null
ReviewSignalTimestamp = $null
FixSignalStatus = $null
FixSignalTimestamp = $null
}
# Get PR info
$prInfo = gh pr view $PRNumber --json state,headRefName,number 2>$null | ConvertFrom-Json
if (-not $prInfo) {
return $status
}
$status.State = $prInfo.state
$status.Branch = $prInfo.headRefName
# Extract issue number from branch
if ($status.Branch -match 'issue/(\d+)') {
$status.IssueNumber = [int]$Matches[1]
}
# Check review files
$reviewDir = Join-Path $genFiles "prReview/$PRNumber"
if (Test-Path $reviewDir) {
$status.HasReviewFiles = $true
$stepFiles = Get-ChildItem -Path $reviewDir -Filter "*.md" -ErrorAction SilentlyContinue |
Where-Object { $_.Name -match '^\d{2}-' }
$status.ReviewStepCount = $stepFiles.Count
# Count severity issues
foreach ($stepFile in $stepFiles) {
$content = Get-Content $stepFile.FullName -Raw -ErrorAction SilentlyContinue
if ($content) {
$status.HighSeverityCount += ([regex]::Matches($content, '\*\*Severity:\s*high\*\*', 'IgnoreCase')).Count
$status.HighSeverityCount += ([regex]::Matches($content, '🔴\s*High', 'IgnoreCase')).Count
$status.MediumSeverityCount += ([regex]::Matches($content, '\*\*Severity:\s*medium\*\*', 'IgnoreCase')).Count
$status.MediumSeverityCount += ([regex]::Matches($content, '🟡\s*Medium', 'IgnoreCase')).Count
}
}
}
# Check review signal
$reviewSignalPath = Join-Path $reviewDir '.signal'
if (Test-Path $reviewSignalPath) {
try {
$reviewSignal = Get-Content $reviewSignalPath -Raw | ConvertFrom-Json
$status.ReviewSignalStatus = $reviewSignal.status
$status.ReviewSignalTimestamp = $reviewSignal.timestamp
}
catch {}
}
# Check fix signal
$fixSignalPath = Join-Path $genFiles "prFix/$PRNumber/.signal"
if (Test-Path $fixSignalPath) {
try {
$fixSignal = Get-Content $fixSignalPath -Raw | ConvertFrom-Json
$status.FixSignalStatus = $fixSignal.status
$status.FixSignalTimestamp = $fixSignal.timestamp
}
catch {}
}
# Get active comments (not in reply to another comment)
try {
$commentCount = gh api "repos/microsoft/PowerToys/pulls/$PRNumber/comments" --jq '[.[] | select(.in_reply_to_id == null)] | length' 2>$null
$status.ActiveCommentCount = [int]$commentCount
}
catch {
$status.ActiveCommentCount = 0
}
# Get unresolved thread count
try {
$threads = gh api graphql -f query="query { repository(owner: `"microsoft`", name: `"PowerToys`") { pullRequest(number: $PRNumber) { reviewThreads(first: 100) { nodes { isResolved } } } } }" --jq '.data.repository.pullRequest.reviewThreads.nodes | map(select(.isResolved == false)) | length' 2>$null
$status.UnresolvedThreadCount = [int]$threads
}
catch {
$status.UnresolvedThreadCount = 0
}
# Check if Copilot review was requested
try {
$reviewers = gh pr view $PRNumber --json reviewRequests --jq '.reviewRequests[].login' 2>$null
if ($reviewers -contains 'copilot' -or $reviewers -contains 'github-copilot') {
$status.CopilotReviewRequested = $true
}
}
catch {}
return $status
}
# Main execution
$results = @{
Issues = @()
PRs = @()
Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
}
# Gather issue numbers to check
$issuesToCheck = @()
$prsToCheck = @()
if ($CheckAll) {
# Get all reviewed issues
$reviewDir = Join-Path $genFiles 'issueReview'
if (Test-Path $reviewDir) {
$issuesToCheck = Get-ChildItem -Path $reviewDir -Directory |
Where-Object { $_.Name -match '^\d+$' } |
ForEach-Object { [int]$_.Name }
}
# Get all open PRs with issue/* branches
$openPRs = gh pr list --state open --json number,headRefName 2>$null | ConvertFrom-Json |
Where-Object { $_.headRefName -like 'issue/*' }
$prsToCheck = @($openPRs | ForEach-Object { $_.number })
}
else {
$issuesToCheck = $IssueNumbers
$prsToCheck = $PRNumbers
}
# Get issue statuses
foreach ($issueNum in $issuesToCheck) {
$status = Get-IssueStatus -IssueNumber $issueNum
$results.Issues += $status
}
# Get PR statuses
foreach ($prNum in $prsToCheck) {
$status = Get-PRStatus -PRNumber $prNum
$results.PRs += $status
}
# Output
if ($JsonOutput) {
$results | ConvertTo-Json -Depth 5
return $results
}
else {
if ($results.Issues.Count -gt 0) {
Write-Host "`n=== ISSUE STATUS ===" -ForegroundColor Cyan
Write-Host ("-" * 100)
Write-Host ("{0,-8} {1,-8} {2,-8} {3,-5} {4,-5} {5,-10} {6,-8} {7,-8} {8,-8} {9,-8}" -f "Issue", "Review", "Plan", "Feas", "Clar", "Worktree", "Commits", "PR", "RevSig", "FixSig")
Write-Host ("-" * 100)
foreach ($issue in $results.Issues | Sort-Object IssueNumber) {
$reviewMark = if ($issue.HasReview) { "" } else { "-" }
$planMark = if ($issue.HasImplementationPlan) { "" } else { "-" }
$wtMark = if ($issue.HasWorktree) { "" } else { "-" }
$commitMark = if ($issue.HasCommits) { $issue.CommitCount } else { "-" }
$prMark = if ($issue.HasPR) { "#$($issue.PRNumber) ($($issue.PRState))" } else { "-" }
$reviewSignalMark = if ($issue.ReviewSignalStatus) { $issue.ReviewSignalStatus } else { "-" }
$fixSignalMark = if ($issue.FixSignalStatus) { $issue.FixSignalStatus } else { "-" }
Write-Host ("{0,-8} {1,-8} {2,-8} {3,-5} {4,-5} {5,-10} {6,-8} {7,-8} {8,-8} {9,-8}" -f
"#$($issue.IssueNumber)", $reviewMark, $planMark, $issue.FeasibilityScore, $issue.ClarityScore, $wtMark, $commitMark, $prMark, $reviewSignalMark, $fixSignalMark)
}
}
if ($results.PRs.Count -gt 0) {
Write-Host "`n=== PR STATUS ===" -ForegroundColor Cyan
Write-Host ("-" * 120)
Write-Host ("{0,-8} {1,-10} {2,-10} {3,-8} {4,-8} {5,-10} {6,-12} {7,-10} {8,-8} {9,-8}" -f "PR", "State", "Issue", "Reviews", "High", "Medium", "Comments", "Unresolved", "RevSig", "FixSig")
Write-Host ("-" * 120)
foreach ($pr in $results.PRs | Sort-Object PRNumber) {
$reviewMark = if ($pr.HasReviewFiles) { "$($pr.ReviewStepCount) steps" } else { "-" }
$issueMark = if ($pr.IssueNumber -gt 0) { "#$($pr.IssueNumber)" } else { "-" }
$reviewSignalMark = if ($pr.ReviewSignalStatus) { $pr.ReviewSignalStatus } else { "-" }
$fixSignalMark = if ($pr.FixSignalStatus) { $pr.FixSignalStatus } else { "-" }
Write-Host ("{0,-8} {1,-10} {2,-10} {3,-8} {4,-8} {5,-10} {6,-12} {7,-10} {8,-8} {9,-8}" -f
"#$($pr.PRNumber)", $pr.State, $issueMark, $reviewMark, $pr.HighSeverityCount, $pr.MediumSeverityCount, $pr.ActiveCommentCount, $pr.UnresolvedThreadCount, $reviewSignalMark, $fixSignalMark)
}
}
Write-Host "`nTimestamp: $($results.Timestamp)" -ForegroundColor Gray
}
return $results

227
.github/skills/pr-fix/SKILL.md vendored Normal file
View File

@@ -0,0 +1,227 @@
---
name: pr-fix
description: Fix active PR review comments and resolve threads. Use when asked to fix PR comments, address review feedback, resolve review threads, implement PR fixes, or handle review iterations. Works with VS Code MCP tools to resolve GitHub threads after fixes are applied.
license: Complete terms in LICENSE.txt
---
# PR Fix Skill
Fix active pull request review comments and resolve threads. This skill handles the **fix** part of the PR review cycle, separate from the review itself.
## ⚠️ Critical Architecture
This skill requires **both** CLI scripts AND VS Code MCP tools:
| Operation | Execution Method |
|-----------|------------------|
| Apply code fixes | Copilot/Claude CLI via script |
| Resolve review threads | **VS Code Agent** via `gh api graphql` |
| Check status | Script (read-only) |
**WHY**: Copilot CLI's MCP is **read-only**. Only VS Code can resolve threads.
## Skill Contents
```
.github/skills/pr-fix/
├── SKILL.md # This file
├── LICENSE.txt # MIT License
├── references/
│ ├── fix-pr-comments.prompt.md # AI prompt for fixing comments
│ └── mcp-config.json # MCP configuration
└── scripts/
├── Start-PRFix.ps1 # Main fix script
├── Start-PRFixParallel.ps1 # Parallel runner (single terminal)
├── Resolve-PRThreads.ps1 # Resolve threads helper
├── Get-UnresolvedThreads.ps1 # Get threads needing resolution
└── IssueReviewLib.ps1 # Shared helpers
```
## Output
- **Code changes**: Applied in the PR's worktree
- **Signal file**: `Generated Files/prFix/<pr>/.signal`
## Signal File
On completion, a `.signal` file is created for orchestrator coordination:
```json
{
"status": "success",
"prNumber": 45365,
"timestamp": "2026-02-04T10:05:23Z",
"unresolvedBefore": 3,
"unresolvedAfter": 0
}
```
Status values: `success`, `partial` (some threads remain), `failure`
## When to Use This Skill
- Fix active review comments on a PR
- Address reviewer feedback
- Resolve review threads after fixing
- Run the fix portion of review/fix loop
- Implement changes requested in PR reviews
## Prerequisites
- GitHub CLI (`gh`) installed and authenticated
- Copilot CLI or Claude CLI installed
- PowerShell 7+
- PR has active review comments to fix
## Required Variables
| Variable | Description | Example |
|----------|-------------|---------|
| `{{PRNumber}}` | Pull request number to fix | `45286` |
## Workflow
### Step 1: Check Unresolved Threads
```powershell
# See what needs to be fixed
.github/skills/pr-fix/scripts/Get-UnresolvedThreads.ps1 -PRNumber {{PRNumber}}
```
### Step 2: Run Fix (CLI Script)
```powershell
# Apply AI-generated fixes to address comments
.github/skills/pr-fix/scripts/Start-PRFix.ps1 -PRNumber {{PRNumber}} -CLIType copilot -Force
```
### Step 3: Resolve Threads (VS Code Agent)
After fixes are pushed, **you (the VS Code agent) must resolve threads**:
```powershell
# Get unresolved thread IDs
gh api graphql -f query='
query {
repository(owner: "microsoft", name: "PowerToys") {
pullRequest(number: {{PRNumber}}) {
reviewThreads(first: 50) {
nodes { id isResolved path line }
}
}
}
}
' --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)'
```
```powershell
# Resolve each thread
gh api graphql -f query='
mutation {
resolveReviewThread(input: {threadId: "{{threadId}}"}) {
thread { isResolved }
}
}
'
```
### Step 4: Verify All Resolved
```powershell
# Confirm no unresolved threads remain
.github/skills/pr-fix/scripts/Get-UnresolvedThreads.ps1 -PRNumber {{PRNumber}}
```
## CLI Options
| Parameter | Description | Default |
|-----------|-------------|---------|
| `-PRNumber` | PR number to fix | Required |
| `-CLIType` | AI CLI: `copilot` or `claude` | `copilot` |
| `-Model` | Copilot model (e.g., `gpt-5.2-codex`) | (optional) |
| `-Force` | Skip confirmation prompts | `false` |
| `-DryRun` | Show what would be done | `false` |
## Review/Fix Loop Integration
This skill is typically used with `pr-review` in a loop:
```
┌─────────────────┐
│ pr-review │ ← Generate review, post comments
└────────┬────────┘
┌─────────────────┐
│ pr-fix │ ← Fix comments, resolve threads
└────────┬────────┘
┌─────────────────┐
│ Check status │ ← Any threads unresolved?
└────────┬────────┘
┌────┴────┐
│ YES │ NO
▼ ▼
(loop) ✓ Done
```
## VS Code Agent Operations
These operations **must** be done by the VS Code agent (not scripts):
| Operation | Method |
|-----------|--------|
| Resolve thread | `gh api graphql` with `resolveReviewThread` mutation |
| Unresolve thread | `gh api graphql` with `unresolveReviewThread` mutation |
### Batch Resolve All Threads
```powershell
# Get all unresolved thread IDs and resolve them
$threads = gh api graphql -f query='
query {
repository(owner: "microsoft", name: "PowerToys") {
pullRequest(number: {{PRNumber}}) {
reviewThreads(first: 100) {
nodes { id isResolved }
}
}
}
}
' --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false) | .id'
foreach ($threadId in $threads) {
gh api graphql -f query="mutation { resolveReviewThread(input: {threadId: `"$threadId`"}) { thread { isResolved } } }"
}
```
## Troubleshooting
| Problem | Solution |
|---------|----------|
| "Cannot resolve thread" | Use VS Code agent, not Copilot CLI |
| Fix not applied | Check worktree is on correct branch |
| Thread ID not found | Re-fetch threads, ID may have changed |
| Fix pushed but thread unresolved | Must explicitly resolve via GraphQL |
## Batch Processing Multiple PRs (CRITICAL)
**DO NOT spawn separate terminals for each PR.** Use the dedicated scripts:
```powershell
# Run fixes in parallel (single terminal)
.github/skills/pr-fix/scripts/Start-PRFixParallel.ps1 -PRNumbers 45256,45257,45285,45286 -CLIType copilot -ThrottleLimit 3 -Force
# Resolve threads (VS Code agent)
.github/skills/pr-fix/scripts/Resolve-PRThreads.ps1 -PRNumber 45256
```
## Related Skills
| Skill | Purpose |
|-------|---------|
| `pr-review` | Review PR, generate findings, post comments |
| `issue-fix` | Fix issues and create PRs |
| `issue-to-pr-cycle` | Full orchestration |

View File

@@ -0,0 +1,29 @@
<#
.SYNOPSIS
Resolve all unresolved review threads for a PR.
.PARAMETER PRNumber
PR number to resolve.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[int]$PRNumber
)
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..\..\..')
Set-Location $repoRoot
$threads = gh api graphql -f query="query { repository(owner:\"microsoft\",name:\"PowerToys\") { pullRequest(number:$PRNumber) { reviewThreads(first:100) { nodes { id isResolved } } } } }" --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false) | .id'
foreach ($threadId in $threads) {
gh api graphql -f query="mutation { resolveReviewThread(input:{threadId:\"$threadId\"}) { thread { isResolved } } }" | Out-Null
}
$threadsAfter = gh api graphql -f query="query { repository(owner:\"microsoft\",name:\"PowerToys\") { pullRequest(number:$PRNumber) { reviewThreads(first:100) { nodes { id isResolved } } } } }" --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false) | .id'
if ($threadsAfter) {
Write-Warning "Unresolved threads remain for PR #$PRNumber"
} else {
Write-Host "All threads resolved for PR #$PRNumber"
}

View File

@@ -0,0 +1,298 @@
<#
.SYNOPSIS
Fix active PR review comments using AI CLI.
.DESCRIPTION
Kicks off Copilot/Claude CLI to address active review comments on a PR.
Does NOT resolve threads - that must be done by VS Code agent via GraphQL.
.PARAMETER PRNumber
PR number to fix.
.PARAMETER CLIType
AI CLI to use: copilot or claude. Default: copilot.
.PARAMETER Model
Copilot CLI model to use (e.g., gpt-5.2-codex).
.PARAMETER WorktreePath
Path to the worktree containing the PR branch. Auto-detected if not specified.
.PARAMETER DryRun
Show what would be done without executing.
.PARAMETER Force
Skip confirmation prompts.
.EXAMPLE
./Start-PRFix.ps1 -PRNumber 45286 -CLIType copilot -Force
.NOTES
After this script completes, use VS Code agent to resolve threads via GraphQL.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[int]$PRNumber,
[ValidateSet('copilot', 'claude')]
[string]$CLIType = 'copilot',
[string]$Model,
[string]$WorktreePath,
[switch]$DryRun,
[switch]$Force,
[switch]$Help
)
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
. (Join-Path $scriptDir 'IssueReviewLib.ps1')
$repoRoot = Get-RepoRoot
$worktreeLib = Join-Path $repoRoot 'tools/build/WorktreeLib.ps1'
if (Test-Path $worktreeLib) {
. $worktreeLib
}
if ($Help) {
Get-Help $MyInvocation.MyCommand.Path -Full
return
}
function Get-PRBranch {
param([int]$PRNumber)
$prInfo = gh pr view $PRNumber --json headRefName 2>$null | ConvertFrom-Json
if ($prInfo) {
return $prInfo.headRefName
}
return $null
}
function Find-WorktreeForPR {
param([int]$PRNumber)
$branch = Get-PRBranch -PRNumber $PRNumber
if (-not $branch) {
return $null
}
$worktrees = Get-WorktreeEntries
$wt = $worktrees | Where-Object { $_.Branch -eq $branch } | Select-Object -First 1
if ($wt) {
return $wt.Path
}
# If no dedicated worktree, check if we're on that branch in main repo
Push-Location $repoRoot
try {
$currentBranch = git branch --show-current 2>$null
if ($currentBranch -eq $branch) {
return $repoRoot
}
}
finally {
Pop-Location
}
return $null
}
function Get-ActiveComments {
param([int]$PRNumber)
try {
$comments = gh api "repos/microsoft/PowerToys/pulls/$PRNumber/comments" 2>$null | ConvertFrom-Json
# Filter to root comments (not replies)
$rootComments = $comments | Where-Object { $null -eq $_.in_reply_to_id }
return $rootComments
}
catch {
return @()
}
}
function Get-UnresolvedThreadCount {
param([int]$PRNumber)
try {
$result = gh api graphql -f query="query { repository(owner: `"microsoft`", name: `"PowerToys`") { pullRequest(number: $PRNumber) { reviewThreads(first: 100) { nodes { isResolved } } } } }" 2>$null | ConvertFrom-Json
$threads = $result.data.repository.pullRequest.reviewThreads.nodes
$unresolved = $threads | Where-Object { -not $_.isResolved }
return @($unresolved).Count
}
catch {
return 0
}
}
#region Main
try {
Info "=" * 60
Info "PR FIX - PR #$PRNumber"
Info "=" * 60
# Get PR info
$prInfo = gh pr view $PRNumber --json state,headRefName,url 2>$null | ConvertFrom-Json
if (-not $prInfo) {
throw "PR #$PRNumber not found"
}
if ($prInfo.state -ne 'OPEN') {
Warn "PR #$PRNumber is $($prInfo.state), not OPEN"
return
}
Info "PR URL: $($prInfo.url)"
Info "Branch: $($prInfo.headRefName)"
Info "CLI: $CLIType"
# Find worktree
if (-not $WorktreePath) {
$WorktreePath = Find-WorktreeForPR -PRNumber $PRNumber
}
if (-not $WorktreePath -or -not (Test-Path $WorktreePath)) {
Warn "No worktree found for PR #$PRNumber"
Warn "Using main repo root. Make sure the PR branch is checked out."
$WorktreePath = $repoRoot
}
Info "Working directory: $WorktreePath"
# Check for active comments
$comments = Get-ActiveComments -PRNumber $PRNumber
$unresolvedCount = Get-UnresolvedThreadCount -PRNumber $PRNumber
Info ""
Info "Active review comments: $($comments.Count)"
Info "Unresolved threads: $unresolvedCount"
if ($comments.Count -eq 0 -and $unresolvedCount -eq 0) {
Success "No active comments or unresolved threads to fix!"
return @{ PRNumber = $PRNumber; Status = 'NothingToFix' }
}
if ($DryRun) {
Info ""
Warn "[DRY RUN] Would run AI CLI to fix comments"
Info "Comments to address:"
foreach ($c in $comments | Select-Object -First 5) {
Info " - $($c.path):$($c.line) - $($c.body.Substring(0, [Math]::Min(80, $c.body.Length)))..."
}
return @{ PRNumber = $PRNumber; Status = 'DryRun' }
}
# Confirm
if (-not $Force) {
$confirm = Read-Host "Fix $($comments.Count) comments on PR #$PRNumber? (y/N)"
if ($confirm -notmatch '^[yY]') {
Info "Cancelled."
return
}
}
# Build prompt
$prompt = @"
You are fixing review comments on PR #$PRNumber.
Read the active review comments using GitHub tools and address each one:
1. Fetch the PR review comments
2. For each comment, understand what change is requested
3. Make the code changes to address the feedback
4. Build and verify your changes work
Focus on the reviewer's feedback and make targeted fixes.
"@
# MCP config
$mcpConfig = '@.github/skills/pr-fix/references/mcp-config.json'
Info ""
Info "Starting AI fix..."
Push-Location $WorktreePath
try {
switch ($CLIType) {
'copilot' {
$copilotArgs = @('--additional-mcp-config', $mcpConfig, '-p', $prompt, '--yolo')
if ($Model) {
$copilotArgs += @('--model', $Model)
}
$output = & copilot @copilotArgs 2>&1
# Log output
$logPath = Join-Path $repoRoot "Generated Files/prReview/$PRNumber"
if (-not (Test-Path $logPath)) {
New-Item -ItemType Directory -Path $logPath -Force | Out-Null
}
$output | Out-File -FilePath (Join-Path $logPath "_fix.log") -Force
}
'claude' {
$output = & claude --print --dangerously-skip-permissions --prompt $prompt 2>&1
$logPath = Join-Path $repoRoot "Generated Files/prReview/$PRNumber"
if (-not (Test-Path $logPath)) {
New-Item -ItemType Directory -Path $logPath -Force | Out-Null
}
$output | Out-File -FilePath (Join-Path $logPath "_fix.log") -Force
}
}
}
finally {
Pop-Location
}
# Check results
$newUnresolvedCount = Get-UnresolvedThreadCount -PRNumber $PRNumber
Info ""
Info "Fix complete."
Info "Unresolved threads before: $unresolvedCount"
Info "Unresolved threads after: $newUnresolvedCount"
if ($newUnresolvedCount -gt 0) {
Warn ""
Warn "⚠️ $newUnresolvedCount threads still unresolved."
Warn "Use VS Code agent to resolve them via GraphQL:"
Warn " gh api graphql -f query='mutation { resolveReviewThread(input: {threadId: \"THREAD_ID\"}) { thread { isResolved } } }'"
}
else {
Success "✓ All threads resolved!"
}
# Write signal file
$signalDir = Join-Path $repoRoot "Generated Files/prFix/$PRNumber"
if (-not (Test-Path $signalDir)) { New-Item -ItemType Directory -Path $signalDir -Force | Out-Null }
@{
status = if ($newUnresolvedCount -eq 0) { "success" } else { "partial" }
prNumber = $PRNumber
timestamp = (Get-Date).ToString("o")
unresolvedBefore = $unresolvedCount
unresolvedAfter = $newUnresolvedCount
} | ConvertTo-Json | Set-Content "$signalDir/.signal" -Force
return @{
PRNumber = $PRNumber
Status = 'FixApplied'
UnresolvedBefore = $unresolvedCount
UnresolvedAfter = $newUnresolvedCount
}
}
catch {
Err "Error: $($_.Exception.Message)"
# Write failure signal
$signalDir = Join-Path $repoRoot "Generated Files/prFix/$PRNumber"
if (-not (Test-Path $signalDir)) { New-Item -ItemType Directory -Path $signalDir -Force | Out-Null }
@{
status = "failure"
prNumber = $PRNumber
timestamp = (Get-Date).ToString("o")
error = $_.Exception.Message
} | ConvertTo-Json | Set-Content "$signalDir/.signal" -Force

View File

@@ -0,0 +1,86 @@
<#
.SYNOPSIS
Run pr-fix in parallel from a single terminal.
.PARAMETER PRNumbers
PR numbers to fix.
.PARAMETER ThrottleLimit
Maximum parallel tasks.
.PARAMETER CLIType
AI CLI type (copilot/claude).
.PARAMETER Model
Copilot CLI model to use (e.g., gpt-5.2-codex).
.PARAMETER Force
Skip confirmation prompts in Start-PRFix.ps1.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[int[]]$PRNumbers,
[int]$ThrottleLimit = 3,
[ValidateSet('claude', 'copilot')]
[string]$CLIType = 'copilot',
[string]$Model,
[switch]$Force
)
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..\..\..\..')
$scriptPath = Join-Path $repoRoot '.github\skills\pr-fix\scripts\Start-PRFix.ps1'
$results = $PRNumbers | ForEach-Object -Parallel {
param($pr)
$repoRoot = $using:repoRoot
$scriptPath = $using:scriptPath
$cliType = $using:CLIType
$model = $using:Model
$force = $using:Force
Set-Location $repoRoot
$branch = (gh pr view $pr --json headRefName -q .headRefName)
$worktree = (git worktree list | Select-String $branch | ForEach-Object { ($_ -split '\s+')[0] })
if (-not $worktree) {
return [pscustomobject]@{
PRNumber = $pr
ExitCode = 1
Error = "Worktree not found for branch $branch"
}
}
Set-Location $worktree
$args = @('-PRNumber', $pr, '-CLIType', $cliType)
if ($model) {
$args += @('-Model', $model)
}
if ($force) {
$args += '-Force'
}
try {
& $scriptPath @args | Out-Default
[pscustomobject]@{
PRNumber = $pr
ExitCode = $LASTEXITCODE
}
}
catch {
[pscustomobject]@{
PRNumber = $pr
ExitCode = 1
Error = $_.Exception.Message
}
}
} -ThrottleLimit $ThrottleLimit
$results

292
.github/skills/pr-review/SKILL.md vendored Normal file
View File

@@ -0,0 +1,292 @@
---
name: pr-review
description: Comprehensive pull request review with multi-step analysis and comment posting. Use when asked to review a PR, analyze pull request changes, check PR for issues, post review comments, validate PR quality, run code review on a PR, or audit pull request. Generates 13 review step files covering functionality, security, performance, accessibility, and more. For FIXING PR comments, use the pr-fix skill instead.
license: Complete terms in LICENSE.txt
---
# PR Review Skill
Perform comprehensive pull request reviews with multi-step analysis covering functionality, security, performance, accessibility, localization, and more.
**Note**: This skill is for **reviewing** PRs only. To **fix** review comments, use the `pr-fix` skill.
## Critical Guidelines
### Load-on-Demand Architecture
Step prompt files are loaded **only when that step is executed** to minimize context usage:
- Read `references/review-pr.prompt.md` first for orchestration
- Load each `references/0X-*.prompt.md` only when executing that step
- Skip steps based on smart filtering (see review-pr.prompt.md)
### Mandatory External Reference Research
**Each step prompt includes an `## External references (MUST research)` section.** Before completing any step, you **MUST**:
1. **Fetch the referenced URLs** using `fetch_webpage` or equivalent
2. **Analyze PR changes against those authoritative sources**
3. **Include a `## References consulted` section** in the output file listing:
- Which guidelines were checked
- Any violations found with specific IDs (e.g., WCAG 1.4.3, OWASP A03, CWE-79)
| Step | Key External References |
|------|------------------------|
| 04 Accessibility | WCAG 2.1, Windows Accessibility Guidelines |
| 05 Security | OWASP Top 10, CWE Top 25, Microsoft SDL |
| 06 Localization | .NET Localization, Microsoft Style Guide |
| 07 Globalization | Unicode TR9 (BiDi), ICU Guidelines |
| 09 SOLID Design | .NET Architecture Guidelines, Design Patterns |
**Failure to research external references is a review quality violation.**
## Skill Contents
This skill is **self-contained** with all required resources:
```
.github/skills/pr-review/
├── SKILL.md # This file
├── LICENSE.txt # MIT License
├── scripts/
│ ├── Start-PRReviewWorkflow.ps1 # Main review script
│ ├── Post-ReviewComments.ps1 # Post comments to GitHub
│ ├── Get-GitHubPrFilePatch.ps1 # Fetch PR file diffs
│ ├── Get-GitHubRawFile.ps1 # Download repo files
│ ├── Get-PrIncrementalChanges.ps1 # Detect incremental changes
│ └── Test-IncrementalReview.ps1 # Test incremental detection
└── references/
├── review-pr.prompt.md # Orchestration prompt (load first)
├── 01-functionality.prompt.md # Step 01 detailed checks
├── 02-compatibility.prompt.md # Step 02 detailed checks
├── 03-performance.prompt.md # Step 03 detailed checks
├── 04-accessibility.prompt.md # Step 04 detailed checks
├── 05-security.prompt.md # Step 05 detailed checks
├── 06-localization.prompt.md # Step 06 detailed checks
├── 07-globalization.prompt.md # Step 07 detailed checks
├── 08-extensibility.prompt.md # Step 08 detailed checks
├── 09-solid-design.prompt.md # Step 09 detailed checks
├── 10-repo-patterns.prompt.md # Step 10 detailed checks
├── 11-docs-automation.prompt.md # Step 11 detailed checks
├── 12-code-comments.prompt.md # Step 12 detailed checks
└── 13-copilot-guidance.prompt.md # Step 13 (conditional)
```
## Output Directory
All generated artifacts are placed under `Generated Files/prReview/<pr-number>/` at the repository root (gitignored).
```
Generated Files/prReview/
└── <pr-number>/
├── 00-OVERVIEW.md # Summary with all findings
├── 01-functionality.md # Functional correctness
├── 02-compatibility.md # Breaking changes, versioning
├── 03-performance.md # Performance implications
├── 04-accessibility.md # A11y compliance
├── 05-security.md # Security concerns
├── 06-localization.md # L10n readiness
├── 07-globalization.md # G11n considerations
├── 08-extensibility.md # API/extension points
├── 09-solid-design.md # SOLID principles
├── 10-repo-patterns.md # PowerToys conventions
├── 11-docs-automation.md # Documentation coverage
├── 12-code-comments.md # Code comment quality
├── 13-copilot-guidance.md # (if applicable)
└── .signal # Completion signal for orchestrator
```
## Signal File
On completion, a `.signal` file is created for orchestrator coordination:
```json
{
"status": "success",
"prNumber": 45365,
"timestamp": "2026-02-04T10:05:23Z"
}
```
Status values: `success`, `failure`
## When to Use This Skill
- Review a specific pull request
- Analyze PR changes for quality issues
- Post review comments on a PR
- Validate PR against PowerToys standards
- Run comprehensive code review
## Prerequisites
- GitHub CLI (`gh`) installed and authenticated
- PowerShell 7+ for running scripts
- GitHub MCP configured (for posting comments)
## Required Variables
⚠️ **For single PR review**, confirm `{{PRNumber}}` with the user. For batch modes, see "Batch Review Modes" below.
| Variable | Description | Example |
|----------|-------------|---------|
| `{{PRNumber}}` | Pull request number to review | `45234` |
## Workflow
### Single PR Review
Execute the review workflow for a specific PR:
```powershell
# From repo root
.github/skills/pr-review/scripts/Start-PRReviewWorkflow.ps1 -PRNumbers {{PRNumber}} -CLIType copilot -SkipAssign -SkipFix -Force
```
### Batch Review Modes
Review multiple PRs with a single command:
```powershell
# Review ALL open non-draft PRs in the repository
.github/skills/pr-review/scripts/Start-PRReviewWorkflow.ps1 -AllOpen -SkipAssign -SkipFix -Force
# Review only PRs assigned to me
.github/skills/pr-review/scripts/Start-PRReviewWorkflow.ps1 -Assigned -SkipAssign -SkipFix -Force
# Review ALL open PRs, skip those already reviewed
.github/skills/pr-review/scripts/Start-PRReviewWorkflow.ps1 -AllOpen -SkipExisting -SkipAssign -SkipFix -Force
# Limit batch size
.github/skills/pr-review/scripts/Start-PRReviewWorkflow.ps1 -AllOpen -Limit 50 -SkipExisting -Force
```
### Background Batch Review (Recommended for Large Batches)
For reviewing many PRs, generate a standalone batch script and run it in background:
```powershell
# Step 1: Generate the batch script
.github/skills/pr-review/scripts/Start-PRReviewWorkflow.ps1 -AllOpen -SkipExisting -GenerateBatchScript -Force
# Step 2: Run in background (minimized window)
Start-Process pwsh -ArgumentList '-File', 'Generated Files/prReview/_batch-review.ps1' -WindowStyle Minimized
# Or run interactively to see progress
pwsh -File "Generated Files/prReview/_batch-review.ps1"
```
The batch script:
- Processes PRs sequentially (more reliable than parallel)
- Skips already-reviewed PRs automatically
- Shows progress as `[N/Total] PR #XXXXX`
- Logs copilot output to `_copilot.log` in each PR folder
- Reports failed PRs at the end
### Step 2: Review Output
Check the generated files at `Generated Files/prReview/{{PRNumber}}/`
## CLI Options
| Parameter | Description | Default |
|-----------|-------------|---------|
| `-PRNumbers` | PR number(s) to review | From worktrees |
| `-AllOpen` | Review ALL open non-draft PRs | `false` |
| `-Assigned` | Review PRs assigned to current user | `false` |
| `-Limit` | Max PRs to fetch for batch modes | `100` |
| `-SkipExisting` | Skip PRs with completed reviews | `false` |
| `-GenerateBatchScript` | Generate standalone script for background execution | `false` |
| `-CLIType` | AI CLI to use: `copilot` or `claude` | `copilot` |
| `-Model` | Copilot model (e.g., `gpt-5.2-codex`) | (optional) |
| `-MinSeverity` | Min severity to post: `high`, `medium`, `low`, `info` | `medium` |
| `-SkipAssign` | Skip assigning Copilot as reviewer | `false` |
| `-SkipReview` | Skip the review step | `false` |
| `-SkipFix` | Skip fix step (recommended - use `pr-fix` skill instead) | `false` |
| `-MaxParallel` | Maximum parallel jobs | `3` |
| `-Force` | Skip confirmation prompts | `false` |
**Note**: The `-SkipFix` option is kept for backward compatibility. For fixing PR comments, use the dedicated `pr-fix` skill which provides better control over the fix/resolve loop.
## AI Prompt References
### Orchestration (load first)
- `references/review-pr.prompt.md` - Main orchestration with PR selection, iteration management, smart filtering
### Step Prompts (load on-demand per step)
Each step prompt contains:
- Detailed checklist of concerns (15-25 items)
- PowerToys-specific checks
- Severity guidelines
- Output file template
- **External references (MUST research)** section
| Step | Prompt File | External References |
|------|-------------|---------------------|
| 01 | `01-functionality.prompt.md` | C# Guidelines, .NET API Design |
| 02 | `02-compatibility.prompt.md` | Windows Versions, .NET Breaking Changes |
| 03 | `03-performance.prompt.md` | .NET Performance, Async Best Practices |
| 04 | `04-accessibility.prompt.md` | **WCAG 2.1**, Windows Accessibility |
| 05 | `05-security.prompt.md` | **OWASP Top 10**, **CWE Top 25**, SDL |
| 06 | `06-localization.prompt.md` | .NET Localization, MS Style Guide |
| 07 | `07-globalization.prompt.md` | Unicode BiDi, ICU, Date/Time Formatting |
| 08 | `08-extensibility.prompt.md` | Plugin Architecture, SemVer |
| 09 | `09-solid-design.prompt.md` | SOLID Principles, Clean Architecture |
| 10 | `10-repo-patterns.prompt.md` | PowerToys docs (architecture, style, logging) |
| 11 | `11-docs-automation.prompt.md` | MS Writing Style, XML Docs |
| 12 | `12-code-comments.prompt.md` | XML Documentation, Comment Conventions |
| 13 | `13-copilot-guidance.prompt.md` | Agent Skills Spec, Prompt Engineering |
### Fix Prompt
- `references/fix-pr-active-comments.prompt.md` - Address active review comments
## Troubleshooting
| Problem | Solution |
|---------|----------|
| PR not found | Verify PR number: `gh pr view {{PRNumber}}` |
| Review incomplete | Check `_copilot-review.log` for errors |
| Comments not posted | Use VS Code MCP tools (Copilot CLI is read-only) |
| Missing `## References consulted` | Re-run step with external reference research |
| Cannot resolve comments | Use `gh api graphql` with resolveReviewThread mutation |
## ⚠️ VS Code Agent Operations
**Copilot CLI's MCP is read-only.** These operations require VS Code MCP tools:
| Operation | VS Code MCP Tool |
|-----------|------------------|
| Assign Copilot reviewer | `mcp_github_request_copilot_review` |
| Post review comments | `mcp_github_pull_request_review_write` |
| Add line-specific comments | `mcp_github_add_comment_to_pending_review` |
| Resolve threads | `gh api graphql` with `resolveReviewThread` |
### Resolve Review Thread Example
```powershell
# Get unresolved threads
gh api graphql -f query='
query {
repository(owner: "microsoft", name: "PowerToys") {
pullRequest(number: {{PRNumber}}) {
reviewThreads(first: 50) {
nodes { id isResolved path line }
}
}
}
}
' --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)'
# Resolve a specific thread
gh api graphql -f query='
mutation {
resolveReviewThread(input: {threadId: "{{threadId}}"}) {
thread { isResolved }
}
}
'
```
## Related Skills
| Skill | Purpose |
|-------|---------|
| `pr-fix` | Fix review comments after this skill identifies issues |
| `issue-to-pr-cycle` | Full orchestration (review → fix loop) |

View File

@@ -0,0 +1,807 @@
<#!
.SYNOPSIS
Review and fix PRs in parallel using GitHub Copilot and MCP.
.DESCRIPTION
For each PR (from worktrees, specified, or fetched from repo), runs:
1. Assigns GitHub Copilot as reviewer via GitHub MCP
2. Runs review-pr.prompt.md to generate review and post comments
3. Runs fix-pr-active-comments.prompt.md to fix issues
.PARAMETER PRNumbers
Array of PR numbers to process. If not specified, finds PRs from issue worktrees.
.PARAMETER AllOpen
Fetch and process ALL open non-draft PRs from the repository.
.PARAMETER Assigned
Fetch and process PRs assigned to the current user.
.PARAMETER Limit
Maximum number of PRs to fetch when using -AllOpen or -Assigned. Default: 100.
.PARAMETER SkipExisting
Skip PRs that already have a completed review (00-OVERVIEW.md exists).
.PARAMETER SkipAssign
Skip assigning Copilot as reviewer.
.PARAMETER SkipReview
Skip the review step.
.PARAMETER SkipFix
Skip the fix step.
.PARAMETER MinSeverity
Minimum severity to post as PR comments: high, medium, low, info. Default: medium.
.PARAMETER MaxParallel
Maximum parallel jobs. Default: 3.
.PARAMETER DryRun
Show what would be done without executing.
.PARAMETER GenerateBatchScript
Instead of running reviews, generate a standalone batch script that can be run
in background. The script will be saved to Generated Files/prReview/_batch-review.ps1.
.PARAMETER CLIType
AI CLI to use: copilot or claude. Default: copilot.
.PARAMETER Model
Copilot CLI model to use (e.g., gpt-5.2-codex).
.EXAMPLE
# Process all PRs from issue worktrees
./Start-PRReviewWorkflow.ps1
.EXAMPLE
# Process specific PRs
./Start-PRReviewWorkflow.ps1 -PRNumbers 45234, 45235
.EXAMPLE
# Review ALL open PRs in the repo
./Start-PRReviewWorkflow.ps1 -AllOpen -SkipFix -SkipAssign
.EXAMPLE
# Review PRs assigned to me, skip already reviewed
./Start-PRReviewWorkflow.ps1 -Assigned -SkipExisting
.EXAMPLE
# Generate a batch script for background execution
./Start-PRReviewWorkflow.ps1 -AllOpen -SkipExisting -GenerateBatchScript
.EXAMPLE
# Only review, don't fix
./Start-PRReviewWorkflow.ps1 -SkipFix
.EXAMPLE
# Dry run
./Start-PRReviewWorkflow.ps1 -DryRun
.NOTES
Prerequisites:
- GitHub CLI (gh) authenticated
- Copilot CLI installed
- GitHub MCP configured for posting comments
#>
[CmdletBinding()]
param(
[int[]]$PRNumbers,
[switch]$AllOpen,
[switch]$Assigned,
[int]$Limit = 100,
[switch]$SkipExisting,
[switch]$SkipAssign,
[switch]$SkipReview,
[switch]$SkipFix,
[ValidateSet('high', 'medium', 'low', 'info')]
[string]$MinSeverity = 'medium',
[int]$MaxParallel = 3,
[switch]$DryRun,
[switch]$GenerateBatchScript,
[ValidateSet('copilot', 'claude')]
[string]$CLIType = 'copilot',
[string]$Model,
[switch]$Force,
[switch]$Help
)
# Load libraries
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
. "$scriptDir/IssueReviewLib.ps1"
# Load worktree library
$repoRoot = Get-RepoRoot
$worktreeLib = Join-Path $repoRoot 'tools/build/WorktreeLib.ps1'
if (Test-Path $worktreeLib) {
. $worktreeLib
}
if ($Help) {
Get-Help $MyInvocation.MyCommand.Path -Full
return
}
function Get-AllOpenPRs {
<#
.SYNOPSIS
Get all open non-draft PRs from the repository.
#>
param(
[int]$Limit = 100
)
Info "Fetching all open PRs (limit: $Limit)..."
$prList = gh pr list --state open --json number,url,headRefName,isDraft --limit $Limit 2>$null | ConvertFrom-Json
if (-not $prList) {
return @()
}
# Filter out drafts
$prs = @()
foreach ($pr in $prList | Where-Object { -not $_.isDraft }) {
$prs += @{
PRNumber = $pr.number
PRUrl = $pr.url
Branch = $pr.headRefName
WorktreePath = $repoRoot
}
}
Info "Found $($prs.Count) non-draft open PRs"
return $prs
}
function Get-AssignedPRs {
<#
.SYNOPSIS
Get PRs assigned to the current user.
#>
param(
[int]$Limit = 100
)
Info "Fetching PRs assigned to @me (limit: $Limit)..."
$prList = gh pr list --assignee @me --state open --json number,url,headRefName,isDraft --limit $Limit 2>$null | ConvertFrom-Json
if (-not $prList) {
return @()
}
$prs = @()
foreach ($pr in $prList | Where-Object { -not $_.isDraft }) {
$prs += @{
PRNumber = $pr.number
PRUrl = $pr.url
Branch = $pr.headRefName
WorktreePath = $repoRoot
}
}
Info "Found $($prs.Count) assigned PRs"
return $prs
}
function Test-ReviewExists {
<#
.SYNOPSIS
Check if a PR review already exists (has 00-OVERVIEW.md).
#>
param(
[int]$PRNumber
)
$reviewPath = Join-Path $repoRoot "Generated Files/prReview/$PRNumber/00-OVERVIEW.md"
return Test-Path $reviewPath
}
function Get-PRsFromWorktrees {
<#
.SYNOPSIS
Get PR numbers from issue worktrees by checking for open PRs on each branch.
#>
$worktrees = Get-WorktreeEntries | Where-Object { $_.Branch -like 'issue/*' }
$prs = @()
foreach ($wt in $worktrees) {
$prInfo = gh pr list --head $wt.Branch --json number,url --state open 2>$null | ConvertFrom-Json
if ($prInfo -and $prInfo.Count -gt 0) {
$prs += @{
PRNumber = $prInfo[0].number
PRUrl = $prInfo[0].url
Branch = $wt.Branch
WorktreePath = $wt.Path
}
}
}
return $prs
}
function Invoke-AssignCopilotReviewer {
<#
.SYNOPSIS
Assign GitHub Copilot as a reviewer to the PR using GitHub MCP.
#>
param(
[Parameter(Mandatory)]
[int]$PRNumber,
[string]$CLIType = 'copilot',
[string]$Model,
[switch]$DryRun
)
if ($DryRun) {
Info " [DRY RUN] Would request Copilot review for PR #$PRNumber"
return $true
}
# Use a prompt that instructs Copilot to use GitHub MCP to assign Copilot as reviewer
$prompt = @"
Use the GitHub MCP to request a review from GitHub Copilot for PR #$PRNumber.
Steps:
1. Use the GitHub MCP tool to add "Copilot" as a reviewer to pull request #$PRNumber in the microsoft/PowerToys repository
2. This should add Copilot to the "Reviewers" section of the PR
If GitHub MCP is not available, report that and skip this step.
"@
# MCP config for github-artifacts tools - use absolute path from main repo
$mcpConfigPath = Join-Path $repoRoot '.github/skills/pr-review/references/mcp-config.json'
$mcpConfig = "@$mcpConfigPath"
try {
Info " Requesting Copilot review via GitHub MCP..."
switch ($CLIType) {
'copilot' {
$copilotArgs = @('--additional-mcp-config', $mcpConfig, '-p', $prompt, '--yolo', '-s')
if ($Model) {
$copilotArgs += @('--model', $Model)
}
& copilot @copilotArgs 2>&1 | Out-Null
}
'claude' {
& claude --print --dangerously-skip-permissions --prompt $prompt 2>&1 | Out-Null
}
}
return $true
}
catch {
Warn " Could not assign Copilot reviewer: $($_.Exception.Message)"
return $false
}
}
function Invoke-PRReview {
<#
.SYNOPSIS
Run review-pr.prompt.md using Copilot CLI.
#>
param(
[Parameter(Mandatory)]
[int]$PRNumber,
[string]$CLIType = 'copilot',
[string]$Model,
[string]$MinSeverity = 'medium',
[switch]$DryRun
)
# Simple prompt - let the prompt file define all the details
$prompt = @"
Follow exactly what at .github/prompts/review-pr.prompt.md to do with PR #$PRNumber.
Post findings with severity >= $MinSeverity as PR review comments via GitHub MCP.
"@
if ($DryRun) {
Info " [DRY RUN] Would run PR review for #$PRNumber"
return @{ Success = $true; ReviewPath = "Generated Files/prReview/$PRNumber" }
}
$reviewPath = Join-Path $repoRoot "Generated Files/prReview/$PRNumber"
# Ensure the review directory exists
if (-not (Test-Path $reviewPath)) {
New-Item -ItemType Directory -Path $reviewPath -Force | Out-Null
}
# MCP config for github-artifacts tools - use absolute path from main repo
$mcpConfigPath = Join-Path $repoRoot '.github/skills/pr-review/references/mcp-config.json'
$mcpConfig = "@$mcpConfigPath"
Push-Location $repoRoot
try {
switch ($CLIType) {
'copilot' {
Info " Running Copilot review (this may take several minutes)..."
$copilotArgs = @('--additional-mcp-config', $mcpConfig, '-p', $prompt, '--yolo')
if ($Model) {
$copilotArgs += @('--model', $Model)
}
$output = & copilot @copilotArgs 2>&1
# Log output for debugging
$logFile = Join-Path $reviewPath "_copilot-review.log"
$output | Out-File -FilePath $logFile -Force
}
'claude' {
Info " Running Claude review (this may take several minutes)..."
$output = & claude --print --dangerously-skip-permissions --prompt $prompt 2>&1
$logFile = Join-Path $reviewPath "_claude-review.log"
$output | Out-File -FilePath $logFile -Force
}
}
# Check if review files were created (at minimum, check for multiple step files)
$overviewPath = Join-Path $reviewPath '00-OVERVIEW.md'
$stepFiles = Get-ChildItem -Path $reviewPath -Filter "*.md" -ErrorAction SilentlyContinue
$stepCount = ($stepFiles | Where-Object { $_.Name -match '^\d{2}-' }).Count
if ($stepCount -ge 5) {
return @{ Success = $true; ReviewPath = $reviewPath; StepFilesCreated = $stepCount }
} elseif (Test-Path $overviewPath) {
Warn " Only overview created, step files may be incomplete ($stepCount step files)"
return @{ Success = $true; ReviewPath = $reviewPath; StepFilesCreated = $stepCount; Partial = $true }
} else {
return @{ Success = $false; Error = "Review files not created (found $stepCount step files)" }
}
}
catch {
return @{ Success = $false; Error = $_.Exception.Message }
}
finally {
Pop-Location
}
}
function Invoke-FixPRComments {
<#
.SYNOPSIS
Run fix-pr-active-comments.prompt.md to fix issues.
#>
param(
[Parameter(Mandatory)]
[int]$PRNumber,
[string]$WorktreePath,
[string]$CLIType = 'copilot',
[string]$Model,
[switch]$DryRun
)
# Simple prompt - let the prompt file define all the details
$prompt = "Follow .github/prompts/fix-pr-active-comments.prompt.md for PR #$PRNumber."
if ($DryRun) {
Info " [DRY RUN] Would fix PR comments for #$PRNumber"
return @{ Success = $true }
}
$workDir = if ($WorktreePath -and (Test-Path $WorktreePath)) { $WorktreePath } else { $repoRoot }
# MCP config for github-artifacts tools - use absolute path from main repo
# This is needed because worktrees don't have .github folder
$mcpConfigPath = Join-Path $repoRoot '.github/skills/pr-review/references/mcp-config.json'
$mcpConfig = "@$mcpConfigPath"
Push-Location $workDir
try {
switch ($CLIType) {
'copilot' {
Info " Running Copilot to fix comments..."
$copilotArgs = @('--additional-mcp-config', $mcpConfig, '-p', $prompt, '--yolo')
if ($Model) {
$copilotArgs += @('--model', $Model)
}
$output = & copilot @copilotArgs 2>&1
# Log output for debugging
$logPath = Join-Path $repoRoot "Generated Files/prReview/$PRNumber"
if (-not (Test-Path $logPath)) {
New-Item -ItemType Directory -Path $logPath -Force | Out-Null
}
$logFile = Join-Path $logPath "_copilot-fix.log"
$output | Out-File -FilePath $logFile -Force
}
'claude' {
Info " Running Claude to fix comments..."
$output = & claude --print --dangerously-skip-permissions --prompt $prompt 2>&1
$logPath = Join-Path $repoRoot "Generated Files/prReview/$PRNumber"
if (-not (Test-Path $logPath)) {
New-Item -ItemType Directory -Path $logPath -Force | Out-Null
}
$logFile = Join-Path $logPath "_claude-fix.log"
$output | Out-File -FilePath $logFile -Force
}
}
return @{ Success = $true }
}
catch {
return @{ Success = $false; Error = $_.Exception.Message }
}
finally {
Pop-Location
}
}
function Start-PRWorkflowJob {
<#
.SYNOPSIS
Process a single PR through the workflow.
#>
param(
[Parameter(Mandatory)]
[int]$PRNumber,
[string]$WorktreePath,
[string]$CLIType = 'copilot',
[string]$Model,
[string]$MinSeverity = 'medium',
[switch]$SkipAssign,
[switch]$SkipReview,
[switch]$SkipFix,
[switch]$DryRun
)
$result = @{
PRNumber = $PRNumber
AssignResult = $null
ReviewResult = $null
FixResult = $null
Success = $true
}
# Step 1: Assign Copilot as reviewer
if (-not $SkipAssign) {
Info " Step 1: Assigning Copilot reviewer..."
$result.AssignResult = Invoke-AssignCopilotReviewer -PRNumber $PRNumber -CLIType $CLIType -Model $Model -DryRun:$DryRun
if (-not $result.AssignResult) {
Warn " Assignment step had issues (continuing...)"
}
} else {
Info " Step 1: Skipped (assign)"
}
# Step 2: Run PR review
if (-not $SkipReview) {
Info " Step 2: Running PR review..."
$result.ReviewResult = Invoke-PRReview -PRNumber $PRNumber -CLIType $CLIType -Model $Model -MinSeverity $MinSeverity -DryRun:$DryRun
if (-not $result.ReviewResult.Success) {
Warn " Review step failed: $($result.ReviewResult.Error)"
$result.Success = $false
} else {
$stepInfo = if ($result.ReviewResult.StepFilesCreated) { " ($($result.ReviewResult.StepFilesCreated) step files)" } else { "" }
$partialInfo = if ($result.ReviewResult.Partial) { " [PARTIAL]" } else { "" }
Success " Review completed: $($result.ReviewResult.ReviewPath)$stepInfo$partialInfo"
}
} else {
Info " Step 2: Skipped (review)"
}
# Step 3: Fix PR comments
if (-not $SkipFix) {
Info " Step 3: Fixing PR comments..."
$result.FixResult = Invoke-FixPRComments -PRNumber $PRNumber -WorktreePath $WorktreePath -CLIType $CLIType -Model $Model -DryRun:$DryRun
if (-not $result.FixResult.Success) {
Warn " Fix step failed: $($result.FixResult.Error)"
$result.Success = $false
} else {
Success " Fix step completed"
}
} else {
Info " Step 3: Skipped (fix)"
}
return $result
}
#region Main Script
try {
Info "Repository root: $repoRoot"
Info "CLI type: $CLIType"
Info "Min severity for comments: $MinSeverity"
Info "Max parallel: $MaxParallel"
# Determine PRs to process
$prsToProcess = @()
if ($PRNumbers -and $PRNumbers.Count -gt 0) {
# Use specified PR numbers
foreach ($prNum in $PRNumbers) {
$prInfo = gh pr view $prNum --json number,url,headRefName 2>$null | ConvertFrom-Json
if ($prInfo) {
# Try to find matching worktree
$wt = Get-WorktreeEntries | Where-Object { $_.Branch -eq $prInfo.headRefName } | Select-Object -First 1
$prsToProcess += @{
PRNumber = $prInfo.number
PRUrl = $prInfo.url
Branch = $prInfo.headRefName
WorktreePath = if ($wt) { $wt.Path } else { $repoRoot }
}
} else {
Warn "PR #$prNum not found"
}
}
} elseif ($AllOpen) {
# Fetch all open PRs from repository
$prsToProcess = Get-AllOpenPRs -Limit $Limit
} elseif ($Assigned) {
# Fetch PRs assigned to current user
$prsToProcess = Get-AssignedPRs -Limit $Limit
} else {
# Get PRs from worktrees
Info "`nFinding PRs from issue worktrees..."
$prsToProcess = Get-PRsFromWorktrees
}
# Filter out already reviewed PRs if requested
if ($SkipExisting -and $prsToProcess.Count -gt 0) {
$beforeCount = $prsToProcess.Count
$prsToProcess = $prsToProcess | Where-Object { -not (Test-ReviewExists -PRNumber $_.PRNumber) }
$skippedCount = $beforeCount - $prsToProcess.Count
if ($skippedCount -gt 0) {
Info "Skipped $skippedCount PRs with existing reviews"
}
}
if ($prsToProcess.Count -eq 0) {
Warn "No PRs found to process."
return
}
# Display PRs
Info "`nPRs to process:"
Info ("-" * 80)
foreach ($pr in $prsToProcess) {
Info (" #{0,-6} {1}" -f $pr.PRNumber, $pr.PRUrl)
}
Info ("-" * 80)
# Generate batch script mode - creates a standalone script for background execution
if ($GenerateBatchScript) {
$batchPath = Join-Path $repoRoot "Generated Files/prReview/_batch-review.ps1"
$prNumbers = $prsToProcess | ForEach-Object { $_.PRNumber }
$batchContent = @"
# Auto-generated batch review script
# Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
# PRs to review: $($prNumbers.Count)
#
# Run this script in a PowerShell terminal to review all PRs sequentially.
# Each review takes 2-5 minutes. Total estimated time: $([math]::Ceiling($prNumbers.Count * 3)) minutes.
#
# Usage: pwsh -File "$batchPath"
`$ErrorActionPreference = 'Continue'
`$repoRoot = '$repoRoot'
Set-Location `$repoRoot
`$prNumbers = @($($prNumbers -join ', '))
`$total = `$prNumbers.Count
`$completed = 0
`$failed = @()
Write-Host "Starting batch review of `$total PRs" -ForegroundColor Cyan
Write-Host "Estimated time: $([math]::Ceiling($prNumbers.Count * 3)) minutes" -ForegroundColor Yellow
Write-Host ""
foreach (`$pr in `$prNumbers) {
`$completed++
`$reviewPath = Join-Path `$repoRoot "Generated Files/prReview/`$pr"
# Skip if already reviewed
if (Test-Path (Join-Path `$reviewPath "00-OVERVIEW.md")) {
Write-Host "[`$completed/`$total] PR #`$pr - Already reviewed, skipping" -ForegroundColor DarkGray
continue
}
Write-Host "[`$completed/`$total] PR #`$pr - Starting review..." -ForegroundColor Cyan
try {
# Create output directory
if (-not (Test-Path `$reviewPath)) {
New-Item -ItemType Directory -Path `$reviewPath -Force | Out-Null
}
# Run copilot review
`$prompt = "Follow exactly what at .github/skills/pr-review/references/review-pr.prompt.md to do with PR #`$pr. Write output to Generated Files/prReview/`$pr/. Do not post comments to GitHub."
& copilot -p `$prompt --yolo 2>&1 | Out-File -FilePath (Join-Path `$reviewPath "_copilot.log") -Force
# Check if review completed
if (Test-Path (Join-Path `$reviewPath "00-OVERVIEW.md")) {
Write-Host "[`$completed/`$total] PR #`$pr - Review completed" -ForegroundColor Green
} else {
Write-Host "[`$completed/`$total] PR #`$pr - Review may be incomplete (no overview file)" -ForegroundColor Yellow
`$failed += `$pr
}
}
catch {
Write-Host "[`$completed/`$total] PR #`$pr - FAILED: `$(`$_.Exception.Message)" -ForegroundColor Red
`$failed += `$pr
}
}
Write-Host ""
Write-Host "======================================" -ForegroundColor Cyan
Write-Host "Batch review complete!" -ForegroundColor Cyan
Write-Host "Total: `$total | Completed: `$(`$total - `$failed.Count) | Failed: `$(`$failed.Count)" -ForegroundColor Cyan
if (`$failed.Count -gt 0) {
Write-Host "Failed PRs: `$(`$failed -join ', ')" -ForegroundColor Red
}
Write-Host "======================================" -ForegroundColor Cyan
"@
$batchContent | Out-File -FilePath $batchPath -Encoding UTF8 -Force
Success "`nBatch script generated: $batchPath"
Info "PRs included: $($prNumbers.Count)"
Info ""
Info "To run the batch review in background:"
Info " Start-Process pwsh -ArgumentList '-File',`"$batchPath`" -WindowStyle Minimized"
Info ""
Info "Or run interactively to see progress:"
Info " pwsh -File `"$batchPath`""
return @{
BatchScript = $batchPath
PRCount = $prNumbers.Count
PRNumbers = $prNumbers
}
}
if ($DryRun) {
Warn "`nDry run mode - no changes will be made."
}
# Confirm
if (-not $Force -and -not $DryRun) {
$stepsDesc = @()
if (-not $SkipAssign) { $stepsDesc += "assign Copilot" }
if (-not $SkipReview) { $stepsDesc += "review" }
if (-not $SkipFix) { $stepsDesc += "fix comments" }
$confirm = Read-Host "`nProceed with $($prsToProcess.Count) PRs ($($stepsDesc -join ', '))? (y/N)"
if ($confirm -notmatch '^[yY]') {
Info "Cancelled."
return
}
}
# Process PRs (using jobs for parallelization)
$results = @{
Success = @()
Failed = @()
}
if ($MaxParallel -gt 1 -and $prsToProcess.Count -gt 1) {
# Parallel processing using PowerShell jobs
Info "`nStarting parallel processing (max $MaxParallel concurrent)..."
$jobs = @()
$prQueue = [System.Collections.Queue]::new($prsToProcess)
while ($prQueue.Count -gt 0 -or $jobs.Count -gt 0) {
# Start new jobs up to MaxParallel
while ($jobs.Count -lt $MaxParallel -and $prQueue.Count -gt 0) {
$pr = $prQueue.Dequeue()
Info "`n" + ("=" * 60)
Info "PROCESSING PR #$($pr.PRNumber)"
Info ("=" * 60)
# For simplicity, process sequentially within each PR but PRs in parallel
# Since copilot CLI might have issues with true parallel execution
$jobResult = Start-PRWorkflowJob `
-PRNumber $pr.PRNumber `
-WorktreePath $pr.WorktreePath `
-CLIType $CLIType `
-Model $Model `
-MinSeverity $MinSeverity `
-SkipAssign:$SkipAssign `
-SkipReview:$SkipReview `
-SkipFix:$SkipFix `
-DryRun:$DryRun
if ($jobResult.Success) {
$results.Success += $jobResult
Success "✓ PR #$($pr.PRNumber) workflow completed"
} else {
$results.Failed += $jobResult
Err "✗ PR #$($pr.PRNumber) workflow had failures"
}
}
}
} else {
# Sequential processing
foreach ($pr in $prsToProcess) {
Info "`n" + ("=" * 60)
Info "PROCESSING PR #$($pr.PRNumber)"
Info ("=" * 60)
$jobResult = Start-PRWorkflowJob `
-PRNumber $pr.PRNumber `
-WorktreePath $pr.WorktreePath `
-CLIType $CLIType `
-Model $Model `
-MinSeverity $MinSeverity `
-SkipAssign:$SkipAssign `
-SkipReview:$SkipReview `
-SkipFix:$SkipFix `
-DryRun:$DryRun
if ($jobResult.Success) {
$results.Success += $jobResult
Success "✓ PR #$($pr.PRNumber) workflow completed"
} else {
$results.Failed += $jobResult
Err "✗ PR #$($pr.PRNumber) workflow had failures"
}
}
}
# Summary
Info "`n" + ("=" * 80)
Info "PR REVIEW WORKFLOW COMPLETE"
Info ("=" * 80)
Info "Total PRs: $($prsToProcess.Count)"
if ($results.Success.Count -gt 0) {
Success "Succeeded: $($results.Success.Count)"
foreach ($r in $results.Success) {
Success " PR #$($r.PRNumber)"
}
}
if ($results.Failed.Count -gt 0) {
Err "Had issues: $($results.Failed.Count)"
foreach ($r in $results.Failed) {
Err " PR #$($r.PRNumber)"
}
}
Info "`nReview files location: Generated Files/prReview/<PR_NUMBER>/"
Info ("=" * 80)
# Write signal files for orchestrator
foreach ($r in $results.Success) {
$signalPath = Join-Path $repoRoot "Generated Files/prReview/$($r.PRNumber)/.signal"
@{
status = "success"
prNumber = $r.PRNumber
timestamp = (Get-Date).ToString("o")
} | ConvertTo-Json | Set-Content $signalPath -Force
}
foreach ($r in $results.Failed) {
$signalPath = Join-Path $repoRoot "Generated Files/prReview/$($r.PRNumber)/.signal"
@{
status = "failure"
prNumber = $r.PRNumber
timestamp = (Get-Date).ToString("o")
} | ConvertTo-Json | Set-Content $signalPath -Force
}
return $results
}
catch {
Err "Error: $($_.Exception.Message)"
exit 1
}
#endregion