---
name: ado
description: Azure DevOps work items, PR reviews, pipelines, and repos via az CLI + REST API
metadata:
  activation:
    keywords: [ado, azure devops, work item, boards, sprint, backlog, iteration, wiql]
    patterns:
      - "(?i)\\bdev\\.azure\\.com\\b"
      - "(?i)\\bpullrequest/\\d+"
      - "(?i)\\baz\\s+(boards|repos|pipelines|devops)\\b"
      - "(?i)\\bazure\\s+devops\\b"
      - "(?i)\\bwork\\s*items?\\b"
      - "(?i)\\bado\\s+(pr|pipeline|repos?)\\b"
      - "(?i)\\b(create|open|list|show|close|merge|review)\\s+(\\w+\\s+)?(pr|pull\\s*request)s?\\b"
    context:
      git-remote: ["dev\\.azure\\.com", "visualstudio\\.com"]
      files: ["azure-pipelines.yml", ".azure-pipelines"]
  requires:
    bins: [az]
  superseded-by:
    tools: [MCP_ado]
---
## Azure DevOps Operations

Use the `shell` tool with `az devops` CLI or `az rest`/`Invoke-RestMethod` for operations the CLI doesn't cover (PR threads, file diffs, voting).

### Authentication

Two auth methods — use whichever works:

Method 1 — az devops CLI (needs PAT or `az login` + devops extension):
```
az extension add --name azure-devops
az devops configure --defaults organization=https://dev.azure.com/{ORG} project={PROJECT}
```

Method 2 — Bearer token via AAD (works when CLI auth fails — preferred for REST API):
```powershell
$token = az account get-access-token --resource "499b84ac-1321-427f-aa17-267ca6975798" --query accessToken -o tsv
$headers = @{Authorization = "Bearer $token"; "Content-Type" = "application/json"}
```

### Work Items
```
az boards work-item show --id {ID} -o json
az boards work-item create --type "User Story" --title "..." --assigned-to "user@example.com" -o json
az boards work-item update --id {ID} --state "Active" -o json
az boards query --wiql "SELECT [System.Id],[System.Title],[System.State] FROM WorkItems WHERE [System.AssignedTo] = @Me AND [System.State] <> 'Closed' ORDER BY [System.ChangedDate] DESC" -o json
```

### Pull Requests — Basic
```
az repos pr show --id {ID} --org https://dev.azure.com/{ORG} -o json
az repos pr list --status active -o json
az repos pr create --repository {REPO} --source-branch {SRC} --target-branch main --title "..." -o json
```

### Pull Requests — Review Workflow (REST API)

Extract org, project, repoId, and commit SHAs from `az repos pr show` output first.

Get changed files:
```powershell
$url = "https://dev.azure.com/{ORG}/{PROJECT}/_apis/git/repositories/{REPO_ID}/pullRequests/{PR_ID}/iterations/1/changes?api-version=7.1"
Invoke-RestMethod -Uri $url -Headers $headers  # → .changeEntries[].item.path
```

Get file content at a specific commit:
```powershell
$url = "https://dev.azure.com/{ORG}/{PROJECT}/_apis/git/repositories/{REPO_ID}/items?path={FILE_PATH}&versionDescriptor.version={COMMIT_SHA}&versionDescriptor.versionType=commit&api-version=7.1"
Invoke-RestMethod -Uri $url -Headers $headers
```

Post a review comment (inline on file):
```powershell
$body = @{
  comments = @(@{ parentCommentId = 0; content = "Review comment text"; commentType = 1 })
  status = 1  # 1=active, 2=fixed, 3=won't fix, 4=closed, 5=by design
  threadContext = @{
    filePath = "/src/path/to/file.tsx"
    rightFileStart = @{ line = 10; offset = 1 }
    rightFileEnd = @{ line = 15; offset = 1 }
  }
} | ConvertTo-Json -Depth 5
$url = "https://dev.azure.com/{ORG}/{PROJECT}/_apis/git/repositories/{REPO_ID}/pullRequests/{PR_ID}/threads?api-version=7.1"
Invoke-RestMethod -Uri $url -Headers $headers -Method Post -Body ([System.Text.Encoding]::UTF8.GetBytes($body))
```

Post a general comment (no file context):
```powershell
$body = @{ comments = @(@{ parentCommentId = 0; content = "Overall review summary"; commentType = 1 }); status = 1 } | ConvertTo-Json -Depth 5
$threadsUrl = "https://dev.azure.com/{ORG}/{PROJECT}/_apis/git/repositories/{REPO_ID}/pullRequests/{PR_ID}/threads?api-version=7.1"
Invoke-RestMethod -Uri $threadsUrl -Headers $headers -Method Post -Body ([System.Text.Encoding]::UTF8.GetBytes($body))
```

Set vote on PR:
```powershell
# Votes: 10=approve, 5=approve-with-suggestions, 0=no vote, -5=wait-for-author, -10=reject
$voteUrl = "https://dev.azure.com/{ORG}/{PROJECT}/_apis/git/repositories/{REPO_ID}/pullRequests/{PR_ID}/reviewers/{REVIEWER_ID}?api-version=7.1-preview"
$body = @{ vote = 5 } | ConvertTo-Json
Invoke-RestMethod -Uri $voteUrl -Headers $headers -Method Put -Body ([System.Text.Encoding]::UTF8.GetBytes($body))
```

### Pipelines
```
az pipelines list -o json
az pipelines show --id {ID} -o json
az pipelines run --id {ID} --branch main -o json
az pipelines runs list --pipeline-id {ID} -o json
az pipelines runs show --id {RUN_ID} -o json
```

### Repos
```
az repos list -o json
az repos show --repository {NAME} -o json
```

### Boards & Sprints
```
az boards iteration team list-work-items --team {TEAM} --iteration {PATH} -o json
```

### Guidelines
- Always use `-o json` for structured output from CLI commands
- Use `--query` (JMESPath) to filter large results: `--query "[].{id:id, title:fields.\"System.Title\"}"`
- For REST API calls, always get a fresh bearer token — tokens expire after ~1 hour
- Use `7.1-preview` API version for vote/reviewer endpoints; `7.1` for everything else
- Write operations (create, update, vote, comment) are medium-risk — confirm with user first
- When posting JSON bodies in PowerShell, wrap with `[System.Text.Encoding]::UTF8.GetBytes()` to avoid encoding issues
- WIQL queries use SQL-like syntax specific to Azure DevOps, not standard SQL
