# jira.py — Capability Reference

Jira Cloud work-item CLI + Python library (REST API v3, works on the Free
plan). JSON on stdout, warnings on stderr, non-zero exit on error. Auth:
Atlassian API token (email + token) via `jira.json` / environment variables —
see README.

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

---

## Identity & metadata

| Command | What it's for |
|---|---|
| `whoami [--check]` | Confirm which site and account you're operating as before writing anything. `--check` round-trips to Jira and returns the authenticated display name + accountId. |
| `projects` | List every project you can see (key, name, type, style) — handles pagination. |
| `types --project KEY` | Issue types **and their statuses** for a project — the map you need before `transition`, since legal target statuses differ per type and workflow. |

## Issues

| Command | What it's for |
|---|---|
| `issues --project KEY\|all [--type] [--status <s>\|open] [--assignee] [--label] [--top 200]` | The board query. `--project all` is an **explicit** cross-project sweep; `--status open` means `statusCategory != Done` (workflow-agnostic openness). Rows carry key, summary, type, status, assignee, labels, project, parent. Truncation warns on stderr. |
| `get-issue KEY [--raw]` | One issue **in full by default**: fields, parent, children (via `parent = KEY`), all comments flattened to readable text, and the description as plain text alongside the raw ADF. `--raw` = fields only. |
| `create-issue --project KEY --summary "…" [--type Task] [--description] [--labels a,b] [--parent KEY] [--assignee who] [--field K=V …] [--sess id]` | Create issues. Description is plain text (converted to ADF for you). `--parent` links under an epic/parent. `--assignee` takes an email, display name, or raw accountId. `--sess` stamps a `sess-<id>` label for automation traceability. |
| `update-issue KEY [--summary] [--description] [--add-labels] [--remove-labels] [--assignee] [--field …] [--sess]` | Edits. Label changes use the API's **native add/remove verbs** — concurrent-safe, no clobbering. `--field` sets any raw field (e.g. `customfield_10001=5`). |
| `transition KEY <target>` | Move an issue by naming the **target status** (or transition name), case-insensitive — Jira changes state via workflow transitions, not field writes. If the move isn't legal from the current status, the error lists the statuses that are. |
| `set-parent KEY PARENT` | Re-link an issue under a different parent/epic. |
| `comment KEY "text" [--sess]` | Add a comment (plain text in, ADF out). |
| `comments KEY [--top 50]` | Read comments newest-first, ADF flattened to readable text. |
| `jql "<query>" [--top 200] [--all-projects]` | Raw JQL for anything the filters can't express — returns shaped issues, not bare ids. **Queries with no `project` term are rejected** (they span every project you can see); `--all-projects` is the explicit override. |

---

## Safety rails (always on)

- **JQL scoping guard** — unscoped queries rejected unless explicitly flagged;
  `issues` requires `--project` or the explicit `all` sweep.
- **Retry with backoff** — 429/5xx retried, honouring `Retry-After` (clamped
  0.5–60s). Unlike some APIs, Jira 400s are treated as real errors — never
  retried. Network-level failures retry for reads only, since a write may
  already have been applied.
- **No silent truncation** — search paginates internally (new `/search/jql`
  endpoint, classic `/search` fallback) and warns on stderr when results are
  clipped at `--top`.
- **ADF both directions** — you never hand-write Atlassian Document Format;
  comments and descriptions read back as plain text.
- **Credential hygiene** — email + token go into request headers only; never
  echoed anywhere.

## Library use

```python
import jira

jira.list_issues(project="ENG", status="open", label="triage")
jira.create_issue(project="ENG", summary="Title", parent="ENG-7", labels=["ops"])
jira.transition_issue("ENG-42", "Done")
jira.update_issue("ENG-42", add_labels=["reviewed"])
jira.get_issue("ENG-42")                 # fields + hierarchy + comments
jira.search('project = "ENG" AND updated >= -7d')
jira.get(f"/rest/api/3/issue/ENG-42/changelog")   # any REST path, same auth + retry
```

## Everyday patterns

```bash
# Morning: what's in flight everywhere I can see
python jira.py issues --project all --status "In Progress"

# Triage queue for one project
python jira.py issues --project ENG --label triage --status open

# Full context on one issue before standup
python jira.py get-issue ENG-42

# Raise, parent, and label in one call
python jira.py create-issue --project ENG --type Task \
  --summary "Rotate expiring certs" --parent ENG-7 --labels ops

# Close out with a note
python jira.py comment ENG-42 "verified in prod"
python jira.py transition ENG-42 Done
```
