# ado.py — Capability Reference

Read/write Azure DevOps CLI + Python library (REST api-version 7.1). JSON on
stdout (pipe-friendly), warnings on stderr, non-zero exit on error. Auth:
Entra service principal or PAT via `devops.json` / environment variables —
see README.

Run every command as: `python ado.py <command> [options]`

---

## Identity & org

| Command | What it's for |
|---|---|
| `whoami [--check]` | Confirm which org and auth mode you're operating as before writing anything. Reports org, default project, auth mode (`entra-sp` / `pat` / `none`). `--check` round-trips to ADO and returns the authenticated identity — first stop when auth misbehaves. |
| `projects` | List the organisation's projects (id, name, state, description). |
| `create-project <name> [--description] [--process <name>]` | Create a project with a named work-item process (resolved against the org's process list; the error names available processes if yours doesn't match). Waits until the project is `wellFormed` so follow-up calls don't race. |

## Work items

| Command | What it's for |
|---|---|
| `work-items --project <name>\|all [--type] [--state <s>\|open] [--assigned-to] [--tag] [--top 200]` | The board query. `--project all` sweeps **every project in one process** — a whole-org WIP check in seconds. `--state open` matches anything not Closed/Done/Removed/Resolved. `--tag` filters on `System.Tags`. Output rows carry a `project` column. Truncation warns on stderr. |
| `get-work-item <id> [--raw]` | One item **in full by default**: raw fields, parent and children resolved to readable rows (not bare relation URLs), and **all comments** — a complete "catch up on this thread" in one call. `--raw` skips the extra lookups for scripting. |
| `create-work-item --title … [--type] [--project] [--description] [--assigned-to] [--tags] [--parent <id>] [--field K=V …] [--sess <id>]` | Create any work item type. `--parent` links it under an epic/feature in the same call. `--field` sets any raw field (repeatable). `--sess` appends a `sess:<id>` tag — handy for stamping which automation/session made the change. |
| `update-work-item <id> [--state] [--title] [--assigned-to] [--add-tags] [--remove-tags] [--field …] [--sess]` | State moves and field edits. `--add-tags`/`--remove-tags` **read-merge-write** — ADO has no tag-append operation, so a naive field write clobbers the whole tag string; this never does. Cross-project move: `--field System.TeamProject=X --field System.AreaPath=X --field System.IterationPath=X`. |
| `add-parent <child> <parent>` | Link an existing item under a parent (epic/feature/issue). |
| `remove-parent <child>` | Drop the parent link. `remove-parent` + `add-parent` = re-parenting. |
| `comment <id> "<text>" [--sess]` | Add a comment (progress notes, handovers). Derives the item's project automatically, so it works across projects without flags. |
| `comments <id> [--top 50]` | Read an item's comments, newest first. |
| `wiql "<query>" [--project] [--top 200] [--org-wide] [--expand]` | Raw WIQL for anything the filters can't express. **Queries without a `[System.TeamProject]` filter are rejected** — an unscoped flat query silently spans the entire organisation, which is rarely what you meant; `--org-wide` is the explicit override. `--expand` returns shaped work items instead of bare ids. |

## Repositories

| Command | What it's for |
|---|---|
| `repos [--project]` | List a project's git repositories (name, default branch, URL). |
| `create-repo <name> --project <p>` | Create a repository — returns the remote URL ready for `git remote add`. |
| `delete-repo <name> --project <p> --yes` | Soft-delete to the project **recycle bin** (restorable ~30 days). Gated behind `--yes`. The safe way to complete a repo move (create in target → push → delete source). |
| `archive-repo <name> --project <p> [--restore]` | **Reversible** disable for legacy repos — preserved in place, restorable any time with `--restore`. Prefer this over delete for retiring repos. |

## Wikis

| Command | What it's for |
|---|---|
| `wikis [--project]` | List a project's wikis. |
| `wiki-page <path> --project <p>` | Read a page (content + immediate sub-pages). |
| `wiki-put <path> --project <p> (--content "…" \| --file f.md)` | Create or update a page. Handles the ETag/version dance on updates, auto-provisions the project wiki if none exists yet, and accepts paths with or without the leading slash (Git Bash on Windows mangles leading-slash arguments). Ideal for publishing generated markdown. |

## Pull requests, pipelines, builds

| Command | What it's for |
|---|---|
| `pull-requests [--status active] [--top 25] [--project]` | List pull requests (title, creator, repo, source/target). |
| `create-pr --repo R --source refs/heads/X --target refs/heads/main --title "…" [--description]` | Open a pull request. |
| `pipelines [--project]` | List build definitions. |
| `builds [--top 20] [--project]` | Recent builds with status/result/finish time. |
| `queue-build <definition_id> [--project]` | Queue a build of a definition. |

Not implemented (by design, add if needed): PR review operations
(approve/complete/comment threads) and work-item attachments.

---

## Safety rails (always on)

- **WIQL scoping guard** — unscoped org-wide queries rejected unless explicitly
  flagged; `work-items` requires `--project` or the explicit `all` sweep.
- **Retry with backoff** — 429/5xx retried automatically; 400/408 retried on
  writes only (ADO throws transient 400s under bursts of writes); honours
  `Retry-After` (clamped 0.5–60s). Network-level errors retried for reads only,
  since a write may already have been applied.
- **No silent truncation** — clipped listings warn on stderr; stdout stays pure
  JSON.
- **Tag safety** — tag edits merge case-insensitively, never clobber.
- **Destructive gates** — `delete-repo` needs `--yes` and is recycle-bin only;
  `archive-repo` is the reversible default.
- **Credential hygiene** — credentials go into outbound request headers only;
  never echoed in output, errors, or `whoami`.

## Library use

```python
import ado

ado.list_work_items(project="MyProject", state="open", tag="triage")
ado.create_work_item("Title", wtype="Task", project="MyProject", parent=100)
ado.update_work_item(123, state="Done", add_tags=["reviewed"])
ado.get_work_item(123, full=True)          # fields + hierarchy + comments
ado.run_wiql("SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject]='X'")
ado.put_wiki_page("Docs/Generated", markdown, project="MyProject")
ado.get("build/builds", project="MyProject", params={"$top": 5})   # any REST path
```

All calls share the same auth, retry, and scoping behaviour as the CLI.

## Everyday patterns

```bash
# Morning: what's in flight across the whole org
python ado.py work-items --project all --state Doing

# Triage: everything tagged 'blocked' in one project
python ado.py work-items --project MyProject --tag blocked

# Full context on one item before a standup
python ado.py get-work-item 1234

# Raise + parent + tag in one call
python ado.py create-work-item --project MyProject --type Task \
  --title "Rotate expiring certs" --parent 900 --tags "ops"

# Close out with a note
python ado.py comment 1234 "verified in prod, closing"
python ado.py update-work-item 1234 --state Done

# Publish generated docs to the wiki
python ado.py wiki-put "Team/Runbook" --project MyProject --file runbook.md
```
