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.
Rich-backed CLI output with --json across every list command
Replaces the hand-rolled ANSI styler (`output.py`) with `rich` for both
human-readable tables and machine-readable JSON, and adds consistent
`--json` support to every list-style command (`pm pool ls`, `pm project
ls/status`, `pm repo ls/pull`, `pm check`, `pm stacker config --list`,
`pm pool add`, `pm pool gc-ops`, the `wt attach/detach/create/delete`
family, `pm project delete --dry-run`). Grouped commands render a
single aligned table with a leading group column whose value appears
only on the first row of each group; horizontal rules under the header
and between groups make the structure visible at a glance.
New `project_manager/render.py` is the single output layer:
- `Column[T]` — per-cell value accessor + optional per-row styling.
Accepts attr names or callables for value and style so domain logic
(dirty→red, Kind→per-status colors, `ahead 3` derivation) lives in
exactly one place colocated with the dataclass.
- `Section[T]` + `GroupColumn` — grouped output drives a single table
with the group value prepended; only the first row of each group
shows the label.
- `JsonShape` — `{group_key: title, items_key: [raw_row, ...]}` layout
so machine consumers get structured data (ints stay ints, enums
become `.value`, paths become strings) instead of parsing display
strings like `"ahead 3"`.
- `__pm_json__` protocol lets types with nested Finding/Path/enum
structure (`Finding`, `StatusRow`, `DetachPlan`, `DeletePlan`)
override the default dataclass serialization.
- `emit_rows` / `emit_sections` / `emit_markup` / `emit_json` are the
only public emit primitives — CLI files shrink to one-line dispatch.
Domain modules carry their own `COLUMNS` spec next to the dataclass
(pool/project/repo/check/attach/detach/create/pull/config). This
removes the 38 hand-rolled `print(f"{row.x}\t{row.y}...")` call sites
and centralizes column titles, value derivations, and styling.
The stacker tree keeps its bespoke composition logic — `fmt.style()`
now emits rich markup instead of raw ANSI, auto-escaping leaf text so
literal tokens like `[LOCAL]` / `[MERGED]` and PR URLs stay literal.
Merged-row dim/strike wrapping constructs markup directly (it wraps
pre-styled fragments; escaping them would break the inner tags).
`output.py` is deleted: rich honors `NO_COLOR` / `FORCE_COLOR` / tty
detection natively via `Console`, so pm's color shim and the
`CLICOLOR_FORCE` hook are no longer needed. `FORCE_COLOR=1 pm pool ls
| cat` replaces the old `CLICOLOR_FORCE=1` path. The `--color-mode`
option on `pm stacker ls` keeps its user-facing semantics but no
longer auto-clamps to `off` on non-TTY — rich strips markup anyway.
Tests: new `tests/test_render.py` covers `emit_rows` /
`emit_sections` / `emit_markup` / JSON / enum+Path coercion / empty
substitution in both pipe and forced-TTY modes. Stacker `ls` color
assertions move from raw ANSI byte patterns to rich-markup regexes.
The old tab-separated surface for `pm pool ls` / `pm project ls/status`
/ `pm repo ls` / `pm check` is intentionally broken — consumers
should use `--json`.
Promotes `rich>=14,<15` from transitive (via cyclopts) to direct dep.
Split stacker service.py into ops/pr/cherry_pick/render/commands subpackages
Breaks the 2,025-LOC StackerService god class into focused modules
following gitstack's CommandCtx pattern:
- ctx.py StackerCtx bundles db/paths/progress/pr_backend/git
- ops/ init, track, remove, reparent, split, rename, sync, push,
continue_abort, guard, worktree — one file per operation
- pr/ config, resolve, find, lineage, create_update, stack_block
- cherry_pick/ driver, resume, empty (local cherry-pick state machine)
- render/ status, log, ls, graph, format
- commands/ one-file-per-CLI-command argparse wiring
- gc.py gc_ops moved out of cli.py (re-exported for back-compat)
service.py shrinks to a 248-LOC public facade (no private passthroughs)
and cli.py to a 9-LOC dispatcher. git.py gains a GitClient Protocol +
SubprocessGitClient impl, symmetric with the existing PRBackend seam.
Introduces LsOptions, PrContext, DriveHandle, GraphPos dataclasses so
every function stays under the 5-arg limit; drops redundant repo_name
from drive_local/finalize_local_op (use op.repo_name).
Test impact: 5 files rewritten to import public module functions instead
of poking service._foo; 3 files switched monkeypatches to target
ops.worktree.run_single_pp at module scope; 1 file updated for
_resolve_create_slot / _resolve_repo / _resolve_branch relocations.
CLI help output is byte-identical to pre-refactor.
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.