stax 0.96.3

Fast stacked Git branches and PRs
Documentation
# learnings.md

- When changing the `stax co` UI, match the `stax ls` visual language (colors, tree/indentation) and confirm it visually. Do not ship a redesign without verifying the output looks like the `ls` tree and that selection emphasis is obvious.
- Ahead/behind label changes must cover both `stax co` and `stax ls` renderers; checkout and status format divergence labels separately, so verify both command outputs.
- When using `dialoguer::FuzzySelect`, keep item strings plain by default and let the picker own highlight styling; if rows must contain ANSI color, use a custom `Theme` with explicit active/inactive rendering and verify the selected row visually.
- Stack tree lane colors must come from `src/commands/stack_palette.rs`; do not duplicate per-command color arrays or `st ls`/`st co` will drift.
- Interactive prompts rendered on `stderr` must style item text against `stderr` and gate interactivity on `stdin` + `stderr`; shell integration can capture stdout for `--shell-output` while dialoguer prompts still run on stderr.
- TUI changes must be checked at standard terminal widths (around 80 columns); keep one-line summaries and footers compact, and prefer a single contextual action over listing every shortcut.
- If interactive lists scroll the terminal on navigation, clear and position the cursor before invoking the dialog to avoid rendering into the lower viewport.
- Bare commands that default to a TUI/dashboard must gate on both `stdin` and `stdout` being terminals and otherwise fall back to help or a non-interactive view; never assume `st`/`st wt` is launched from a full TTY.
- TUI/dashboard launch checks must also preflight the terminal input backend before entering the alternate screen; `isatty` alone is insufficient under constrained environments such as low file-descriptor limits.
- TUI/dashboard startup must render from cheap repo data first and defer expensive per-item Git or tmux inspection until after the first paint; do not block initial draw on full worktree/branch status scans.
- Read-only status/list commands (`st ls`, `st ll`, `st log`) must avoid broad Git subprocess scans for data available from libgit2 and must batch or parallelize unavoidable per-branch graph walks; first-run latency is part of the user-visible contract.
- Background TUI loaders must distinguish queued work from actively running work; only mark an item as `loading` once its worker starts, or selection changes can leave stale loading state behind.
- TUI/dashboard actions that shell back into `stax` must exit the alternate screen and drop live app/repo state before spawning the nested command; running child `stax` commands from inside an active dashboard can hit `EMFILE` in large repos.
- When adding or changing CLI commands/flags, update both `README.md` and `docs/` command references in the same change and verify parity against `stax --help` before marking docs complete.
- AI standup prompts that ask for `Today` must include concrete in-flight signals (recent branch work, opened PRs, or Jira tickets with PRs) and tell the model to carry unfinished work forward; otherwise the model fills missing context with generic review/cleanup/backlog bullets.
- Long-running reporting or AI commands must show progress for human-facing output while keeping `--json` and plain-text modes free of progress noise; use stderr for progress when stdout may be copied, piped, or parsed.
- Command completion footers must be printed after all foreground work has finished; if a best-effort metadata refresh or cache update can block, show it as a timed progress step and include it in the total.
- Sync metadata refreshes must scale with local tracked branches, not repo-wide forge state; avoid listing every open PR when the command only needs metadata for known PR numbers.
- When a feature has both command docs and workflow guides, keep one canonical docs page for the command surface and have workflow pages link back to it instead of duplicating subcommand semantics.
- Shell integration snippets that define common command names (`stax`, `st`, `sw`) must clear conflicting aliases first; zsh expands aliases while parsing function definitions and can abort shell startup otherwise.
- Shared bash/zsh shell snippets must use shell-specific command lookup (`whence -p` in zsh, `type -P` in bash) when they need the real binary path; bash-only flags in shared wrappers can break every `st`/`stax` invocation on zsh.
- Shared bash/zsh shell snippets must not use zsh-special parameter names like `path` for local scratch variables inside wrappers; localizing `path` in zsh also localizes `PATH` and breaks nested command lookup.
- `st setup` must replace legacy inline/eval shell integration blocks instead of appending a new source line, and later runs should refresh existing generated shell-setup files after upgrades; stale pasted wrappers or stale generated snippets both leave users with duplicate or broken shell behavior.
- Shell integration should special-case only commands that require shell-side directory changes (`wt go/create`, `lane`, and removing the current worktree); route all other commands straight to the binary without injecting `--shell-output`.
- Commands intended for first-run/setup flows (for example `setup`) must bypass repo initialization in `src/cli.rs`; otherwise running them outside a configured repo can accidentally trigger `init` and break shell startup or onboarding.
- Agent-launching commands must not blindly inherit the global `ai.model` setting across different CLIs; only an explicit per-command `--model` should override the selected agent's own default.
- When sync reparents children off merged branches, never clear `parent_branch_revision`; preserve the old-base boundary (or merged parent tip) so restack can run `git rebase --onto <new> <old>` and avoid replaying already-integrated commits.
- Branch-cleanup paths must compute the confirmed deletion set before mutating descendant metadata or PR bases; skip reparent/base updates for branches that are also being deleted in the same cleanup pass.
- Integration tests that shell out to `git`/`stax` must be hermetic: strip GitHub token env, user-specific `stax` env such as `STAX_SHELL_INTEGRATION`, and force `GIT_CONFIG_GLOBAL`/`GIT_CONFIG_SYSTEM` to null so contributor-specific git config and shell setup cannot change behavior or add large runtime overhead.
- Integration tests that shell out to the compiled `stax` binary must resolve a path that exists at runtime; prefer helper logic that falls back from `CARGO_BIN_EXE_stax` to the sibling `target/.../stax` binary so `nextest` and Docker runs stay stable.
- Tests that must run outside any Git repository must not use temp dirs rooted under `STAX_TEST_TMPDIR`/`TMPDIR` inside the workspace; `git discover` walks parent directories, so those fixtures can accidentally execute inside the repo during `make test-native`.
- For full-suite test runs, use `make test` (never `cargo test`); on macOS the default should use Docker for performance and consistency.
- Integration-test shims for external CLIs must track the option shapes used in production code; when tmux launch commands add flags such as `-c` or `new-window` flows, update the fake `tmux` script in the same change.
- When running native full-suite validation from inside tmux, unset `TMUX` and `TMUX_PANE`; worktree lane tests simulate both inside- and outside-tmux launches and inherit those variables through the test helper.
- Release automation that rewrites `CHANGELOG.md` must derive `Unreleased` notes from commits since the latest version tag and fail before publishing when that range is empty; never let `cargo release` cut an empty version section.
- Files generated for a release commit (for example `CHANGELOG.md`) must be produced inside `cargo release`'s `pre-release-hook`; wrapper scripts must not dirty the worktree before `cargo release` performs its clean-tree check.
- Amend-style commands must detect zero-ahead tracked branches and avoid rewriting inherited parent commits; when the user supplies a message, prefer creating the first branch-local commit instead of amending shared history.
- Commands that remove the current worktree must resolve any follow-up repo paths before deletion; once the active worktree directory is gone, later `git` subprocesses can fail with ENOENT if they still rely on the deleted cwd.
- Branch-cleanup paths that may encounter linked worktrees must verify refs with `git show-ref`, prefer `git branch -D` plus post-delete existence checks over libgit2-only deletion, and handle both sides of the handoff: the merged branch may still be checked out elsewhere, and the fallback branch you need to checkout may already be owned by another worktree.
- When a linked worktree blocks a destructive action, do not stop at a raw Git/libgit2 error or a generic "checked out elsewhere" note; print the owning worktree and concrete recovery commands that are actually runnable: use a unique `st wt rm <selector>` target (prefer the checked-out branch over a non-unique basename), and fall back to `git -C <path> switch --detach` whenever trunk is already checked out in another worktree.
- Branch-cleanup prompts and summaries must distinguish deleting branch refs from deleting live worktree directories; if `sync` will remove a safe linked worktree, say that up front, and if it keeps the lane, explain why and point to `st wt cleanup` / `st wt rm ... --delete-branch`.
- Dirty-worktree removal must use the same force/blocker policy across `wt rm`, `wt cleanup`, and `sync`: if the user confirmed "remove anyway" or passed `--force`, the eventual `git worktree remove` call must also be forced, and prompts must not promise a removal path that still falls through to a non-forced Git command.
- When routing to an existing worktree, preserve a unique selector such as the worktree path or checked-out branch; never round-trip through the display basename because different worktrees can share the same leaf directory name.
- Branch-switching commands in very large repos must drop live libgit2 repository handles before spawning `git checkout` or writing prev-branch refs; use path-based `git` subprocesses for the final handoff so checkout still works under file-descriptor pressure.
- When a command needs to reopen a repository after an FD-heavy libgit2 phase, reopen from a saved repo path (`git_dir`/worktree path) instead of rediscovering `.`; `Repository::discover(".")` can fail under `EMFILE` even when the repo path is already known.
- Stack/branch graph traversal in user-facing commands must be iterative and cycle-safe; do not recurse over metadata graphs that can be deep or corrupted by local refs.
- Stack-link sync must separate the PRs to update from the branch context rendered in each PR's comment/body; never reuse a trunk-wide `current_stack` as the displayed chain for every PR, because sibling stacks will be flattened into a fake linear series.
- When `Stack::load` normalizes corrupted metadata in memory, derive child lists from the normalized branch records only; do not also append synthetic child edges or status/log output can show duplicate branches.
- For stack-merge flows that delete merged branches, always rebase and retarget descendant branches/PR bases before cleanup; deleting a base branch first can auto-close descendant PRs on GitHub.
- Any descendant-rebase path (`merge`, `merge --when-ready`, `restack`, `upstack restack`, `sync --restack`) must preserve provenance boundaries (`parent_branch_revision` / old parent tip) and use provenance-aware rebase logic; plain `git rebase <trunk>` will replay already-integrated parent history after squash merges.
- Configure explicit connect/read/write timeouts for GitHub API clients (and other network clients); never rely on library defaults for long-running CLI flows where silent waits look like hangs.
- Submit/create flows must treat forge duplicate-PR responses for the same head branch as idempotent: look up the existing PR, persist metadata, and continue instead of surfacing a failed create.
- GitHub commit statuses are append-only events, not mutable runs; when showing CI timing or recording history for a status context, derive duration from the latest `pending` -> terminal sequence instead of a single status record's `created_at` / `updated_at`.
- Cross-branch CI ETA should be keyed by reusable check contexts, not branch names; branch names are one-off, so wall-clock estimates must be reconstructed from recent per-check history for the currently present check set.