Upgrade **all** outdated dependencies, opening one PR per dependency (and one grouped PR per coupled group). This is the on-demand equivalent of the weekly `.github/workflows/upgrade-deps.yml` job — it reuses the same `.github/scripts/check_outdated_deps.py` logic, so it respects `[tool.upgrade-deps]` `groups` and `pin-major` for free.

## Preconditions (check first, stop if any fail)

1. **Clean working tree.** Run `git status --porcelain`. If it is non-empty, STOP and tell me — uncommitted changes would be carried onto every upgrade branch. Do not stash or discard my work.
2. **On an up-to-date `master`.** `git checkout master && git pull --ff-only`.
3. **Tooling present.** The check script needs the `packaging` library, and the resolution gate needs `uv`. Both are expected locally; if a command fails because one is missing, report it rather than working around it.

## Steps

1. **Get the upgrade matrix.** Run `python .github/scripts/check_outdated_deps.py check` from the project root. Progress lines go to stderr; the only stdout is a JSON list. Parse it. Each item has: `kind` (`"single"` | `"group"`), `group`, `package`, `current_version`, `latest_version`, `members`, `branch`, `title`. If the list is `[]`, report that everything is up to date and STOP.

2. **Show the plan and confirm.** Present a table (Package, Current → Latest, kind) and state how many PRs this will open. Wait for my go-ahead before creating any branches.

3. **Process each matrix item in order.** Always start from a clean `master` so branches never accumulate each other's changes. For each item:

   a. **Skip if a PR already exists** (mirror the CI dedup):
      - `kind: group` → `gh pr list --state open --json headRefName --jq '[.[] | select(.headRefName | startswith("upgrade/<group>-group-"))] | length'`
      - `kind: single` → `gh pr list --state open --search "Upgrade <package> from in:title" --json number --jq 'length'`
      
      If the count is > 0, skip this item and note it.

   b. **Branch:** `git checkout master && git checkout -b "<branch>"` (use the `branch` field verbatim).

   c. **Apply the bump:**
      - `kind: group` → `python .github/scripts/check_outdated_deps.py apply-group "<group>"`
      - `kind: single` → `python .github/scripts/check_outdated_deps.py apply "<package>" "<current_version>" "<latest_version>"`

   d. **Bail on no-op.** If `git diff --quiet -- pyproject.toml` (nothing changed — e.g. a group whose members already resolve in range), abort this item: `git checkout master && git branch -D "<branch>"`, note it, continue to the next.

   e. **Resolution gate — never skip this.** Run `python .github/scripts/check_outdated_deps.py resolve`. If it exits non-zero, the upgrade does not resolve; abort this item (`git checkout master && git branch -D "<branch>"`), note that it likely needs to be added to a `[tool.upgrade-deps]` group, and continue. NEVER open a PR that fails to resolve.

   f. **Commit `pyproject.toml` only:** `git add pyproject.toml && git commit -m "<title>."` — **Do NOT bump the version.** The `bump-version.yml` Action bumps the patch version automatically when the merge lands on `master`; per CLAUDE.md a manual bump in the PR is wrong.

   g. **Push:** `git push -u origin "<branch>"`.

   h. **Open the PR** with base `master`. Write the body to a temp file and use `gh pr create --base master --title "<title>" --body-file <file>` (use `--body-file`, never an inline heredoc — package descriptions can contain apostrophes that break shell quoting). Body should state old → new (`~=<current>` → `~=<latest>`); for a `group`, add a note that it is a grouped upgrade and list `members`.

   i. Capture the PR URL.

4. **Return to `master`:** `git checkout master`.

5. **Report back.** List every PR opened (with URLs) and everything skipped, with the reason (existing PR / no-op / failed resolution). Remind me that for each PR both the **Build** workflow and the downstream **CCA** test workflow must pass before merging. Do not merge anything and do not bump the version.

## Guardrails

- Base branch is always `master`.
- One PR per outdated single dep; one grouped PR per outdated group. The matrix already encodes grouping and pin-major handling — just follow it, don't re-derive it.
- Never manually edit the version in `pyproject.toml`.
- Operate one branch at a time, returning to a clean `master` between items.
