stacker: full Emacs UI + --json on every stacker command
Build out interactive stacker support in the pm Emacs integration,
backed by structured CLI output.
Python:
- pm project status --json now carries the structured stacker tree per
repo (StackerRow.branches / current_branch / operation), reusing the
same branch selection the text arm renders (ls.ls_structure +
arm_branch_set). Text mode is unchanged; --json skips the text render
to stay at one prefetch per repo.
- Every `pm stacker` subcommand gains --json via a uniform envelope
(_common.emit_result): {ok, command, repo, branch, message, operation}.
message is the raw markup result; operation surfaces a paused/
conflicted op so a client can offer continue/abort. ls/config keep
their existing richer JSON.
Emacs (new pm-stacker.el + integration):
- *pm-stacker: <project>* — a foldable, multi-repo branch tree with
CLI-matching status icons, commit-group markers (count/needs-sync/
ahead), current-branch + merged styling, and a paused-op banner.
- Branch-scoped action transient (RET) covering the full verb set with
infix flags; direct keys for the common verbs. Every action runs
`pm stacker <verb> --json` and refreshes; destructive verbs confirm.
- The status buffer's Stacker section reuses the same renderer, so it's
now the same interactive tree (closes the old plain-face gap).
- Branch rows use a dedicated section class (pm-stacker-branch-section)
whose highlight covers only the heading line — so a repo heading
highlights its whole block (table-style) while a branch row (even a
parent) highlights as a single line, with folding intact.
- Faces: pm-faces-stacker-status (icon+face per status mirroring
graph.py), pm-stacker-current, pm-stacker-merged, strike markup token.
Tests: status structured-tree + --json envelope (Python); stacker tree
render, faces, RET scope, wrapper argv (Emacs). README documents the
buffer, transient, and the --json envelope.
Align pm stacker ls output with gitstack
Rewrites the `ls` renderer to match `git stack ls` in output fidelity,
and splits PR-state metadata into its own `pr_state` table so the
branch row stops carrying PR fields.
Rendering changes (text):
- Always-on `[MERGED]/[<pr_url>]/[REMOTE]/[LOCAL]` suffix replacing
the old `[synced]/[unsynced]` word tokens.
- Compact `(!N↑)` commit group combining sync + ahead-of-remote.
- Tree connectors pick up `├─✗ / └─✗` on unsynced rows and `■┄┆` on
merged rows.
- `> / (current)` marker for the cwd's pm slot.
- Status icons `· ○ ● ■` offline, upgraded to `▼ ⚑ ✓ ◼` when `--online`
is set (default true) via a new bulk `gh graphql` call.
- Dim/strikethrough styling for merged rows; new `--color-mode` gradient
`off | icon | title | full` (full renders as title pending follow-up).
- New flags: `-c`, `--icons`, `--merged`, `--merged-style`, `--color-mode`.
`ls` default scope flipped to `all` to match gitstack.
Rendering changes (JSON):
- Flat list replaced by `{current_branch, branches: [root...children]}`.
- Tiered fields by `--details`: `status`, `needs_sync`, `ahead_of_remote`,
`commit_count` populated per level; `ahead_of_remote` is an int
(not a bool like gitstack) so scripts get an exact count.
Data model:
- New `pr_state` table keyed on `(repo_name, branch)` holds pr_url,
pr_number, state, is_draft, merged, merged_at, is_approved,
has_open_comments, fetched_at. `tracked_branches.pr_url` is migrated
into it on first boot and then the column is dropped.
- `pr/find::record_pr` and the `create_or_update` path now write full
PR state (not just the URL) so `ls` can render merge status offline.
- Rename paths explicitly move the `pr_state` row so cache survives.
Online mode:
- One GraphQL call per (owner, repo) group via `gh.batch_pr_review`
fetches review state in bulk before rendering.
Plumbing:
- `output.style` grows `dim` + `strikethrough` kwargs.
- `resolve_color_mode` honours `NO_COLOR` / non-TTY.
- `LsOptions` / `RenderOptions` dataclasses collect the ls knobs.
- `ls_text` takes `LsOptions` directly to stay within the arg-count budget.
316 tests passing. No new ruff/ty suppressions.