stax 0.96.7

Fast stacked Git branches and PRs
Documentation
# Configuration

```bash
st config                     # show current configuration
st config --set-ai            # interactively pick AI agent/model
st config --reset-ai          # clear saved AI defaults and re-prompt
st config --reset-ai --no-prompt
```

Config is loaded as follows:

1. `STAX_CONFIG_DIR/config.toml` when `STAX_CONFIG_DIR` is set.
2. Otherwise, `~/.config/stax/config.toml` is loaded.
3. If present, `stax.toml` at the current git repository root overlays only the values it sets.

## Example config

```toml
[branch]
# format = "{user}/{date}/{message}"
# user = "cesar"
# date_format = "%m-%d"
# replacement = "-"
# stale_days = 30 # days without commits before `stax sweep` calls a branch stale

[git]
# rerere = true # auto-enable git rerere on `stax init`

[remote]
# name = "origin"
# base_url = "https://github.com"
# api_base_url = "https://github.company.com/api/v3"
# forge = "github" # "github" | "gitlab" | "gitea" — override auto-detection

[submit]
# stack_links = "comment" # "comment" | "body" | "both" | "off"
# single_stack = "on"     # "on" | "off" — when "off", skip stack-link sync while the stack has only one PR
# native_stack = "auto"   # "auto" | "off" | "link" — auto-register native GitHub Stacked PRs when available
# stack_links_when_native = "keep" # "keep" | "off" — keep stax body/comment links even when native registration succeeds

[ci]
# alert = false
# success_alert_sound = "/path/to/ci-success.wav"
# error_alert_sound = "/path/to/ci-error.wav"

[auth]
# use_gh_cli = true
# allow_github_token_env = false
# gh_hostname = "github.company.com"

[ui]
# tips = true

[restack]
# preflight_auto_repair = true # automatically use merge-base when stored parent
                               # boundary would replay a much larger range
# preflight_warn = true        # print a notice when that automatic repair happens

[ai]
# agent = "claude" # "codex" | "gemini" | "opencode" — global default
# model = "claude-sonnet-4-5-20250929"

# Per-feature overrides — optional, fall back to [ai] above
[ai.generate]   # st create --ai, st gen / st generate, st submit --ai
# agent = "codex"
# model = "o4-mini"

[ai.standup]    # st standup --ai
# agent = "gemini"
# model = "gemini-2.5-pro"

[ai.resolve]    # st resolve
# agent = "claude"
# model = "claude-opus-4-5"

[ai.lane]       # st lane / st worktree create --ai
# agent = "claude"
# (model is intentionally not inherited from [ai] for interactive lanes)

[worktree]
# root_dir = "" # default: ~/.stax/worktrees/<repo>
# reuse_slots = true
#   Warm-slot recycling. Removing a clean, merged-equivalent worktree parks it
#   (reset --hard trunk + `git clean -fd`, keeping gitignored deps like
#   node_modules / .venv) instead of deleting it, and the next create/lane adopts
#   that slot instead of a cold `git worktree add`. Set false to always
#   cold-create and real-remove (no pool manifest).
# max_idle_slots = 4
#   Maximum idle slots kept parked. Parking beyond the cap does a real remove;
#   `worktree cleanup` evicts the oldest excess slots.
# reconcile = "pnpm install"
#   Optional command run (non-fatally) inside a slot after it is adopted, to
#   re-sync dependencies. A missing or failing command only warns.

[worktree.hooks]
# post_create = "" # blocking hook run in a new worktree before launch
# post_start  = "" # background hook after creation
# post_go     = "" # background hook after entering an existing worktree
# pre_remove  = "" # blocking hook before removal
# post_remove = "" # background hook after removal
#
# Example — keep VS Code / Cursor aware of every lane:
#   post_start = "code --add ."
#   post_go    = "code --add ."
```

## AI configuration

### Set agent + model

Pick an agent and model for any feature (or the global default):

```bash
st config --set-ai
```

You're asked which feature to configure (`generate`, `standup`, `resolve`, `lane`, or global default), then prompted for agent and model. The choice is written to the appropriate `[ai.*]` section.

### First-use prompting

The first time you run an AI-powered command without a configured agent (e.g. `st standup --ai`), stax opens the picker automatically and persists the choice for future runs — no manual config editing required.

### Resolution order

For AI-powered commands, agent and model are resolved in this order:

| Priority | Source |
|---|---|
| 1 | CLI flag (`--agent`, `--model`) where the command exposes one |
| 2 | Per-feature config (`[ai.generate]`, `[ai.standup]`, …) |
| 3 | Global config (`[ai]`) |
| 4 | Interactive first-use prompt (persisted) |

> **Note:** `[ai.lane]` intentionally does not fall back to `[ai].model`. Interactive coding agents are a different workload from one-shot generation; a cheap model set for `st generate` should not silently apply to a long-running `st lane` session.

### "Using …" confirmation

When stax invokes an AI agent it prints a confirmation line to stderr:

```text
  Using claude with model claude-opus-4-5
  Using codex
```

### Reset saved defaults

```bash
st config --reset-ai              # clear + re-prompt
st config --reset-ai --no-prompt  # clear only
```

## CI watch alerts

```toml
[ci]
alert = true
# success_alert_sound = "/path/to/ci-success.wav"
# error_alert_sound = "/path/to/ci-error.wav"
```

When `alert` is true, `st ci --watch` plays bundled success/error sounds after CI completes. Set either path to override one outcome while keeping the other bundled default.

## Branch naming format

```toml
[branch]
format = "{user}/{date}/{message}"
user = "cesar"
date_format = "%m-%d"
```

The legacy `prefix` field still works when `format` is unset.

## Stale-branch threshold

```toml
[branch]
stale_days = 60
```

`stale_days` is the number of days without new commits before [`stax sweep`](../commands/sweep.md) classifies a branch as `stale` (default: `30`). The `stax sweep --stale-days <N>` flag overrides this per run.

## Git rerere

```toml
[git]
rerere = false
```

When `rerere` is `true` (the default), `stax init` enables Git's [rerere](https://git-scm.com/docs/git-rerere) ("reuse recorded resolution") so previously resolved merge conflicts are replayed automatically during restacks. Set it to `false` to leave your global Git configuration untouched on init.

## Stack-links placement

Where `st submit` writes the stack graph for a PR:

```toml
[submit]
stack_links = "body"   # "comment" | "body" | "both" | "off"
single_stack = "on"    # "on" | "off"
```

When body output is enabled, stax appends a managed block to the bottom of the PR body and only rewrites that managed block on future submits.

Stack-link entries use compact PR/MR references and mark the PR being rendered with `👈`. On GitHub, stax keeps native `#123` PR references so GitHub renders its standard linked issue/PR styling; other forges use direct markdown links. The intro text is relative to the PR being rendered, so an imported base PR is described as an imported reference, while a local PR calls out any imported downstack context. Imported branches remain read-only for push and PR metadata updates, but their existing PRs still receive the managed stack links when they are part of the displayed stack.

`single_stack` controls whether stack links are written when the stack contains only one PR. With the default `"on"`, links are always synced per `stack_links`. With `"off"`, stax skips link sync — and removes any stale links left over from a previous `"on"` setting — while the stack has a single PR. As soon as a second PR is submitted on the same stack, links populate on every PR (including the original) automatically.

## Native GitHub Stacked PRs

```toml
[submit]
native_stack = "auto"              # "auto" | "off" | "link"
stack_links_when_native = "keep"   # "keep" | "off"
```

`native_stack = "auto"` is the default. On GitHub remotes, stax checks for the `github/gh-stack` extension and tries to register submitted multi-PR stacks with GitHub's native Stacked PRs feature. If the extension is missing, the repo does not have private-preview access, or the remote is not GitHub, submit silently keeps the existing stax behavior.

Use `native_stack = "off"` to disable native registration. Use `"link"` to force an attempt even when the repo's feature cache is unknown or disabled. Per run, `st submit --native-stack` forces an attempt and `st submit --no-native-stack` skips it.

`stack_links_when_native = "keep"` preserves stax's body/comment stack links when native registration succeeds. Set it to `"off"` only if you want the GitHub native stack map without stax-managed PR body/comment links.

`st doctor` reports whether `gh-stack` is installed and `st doctor --fix` can install it with `gh extension install github/gh-stack` after confirmation.

## Forge type override

By default stax detects the forge from the remote hostname. If your self-hosted instance has a generic hostname like `git.mycompany.com`, override it:

```toml
[remote]
base_url = "https://git.mycompany.com"
forge = "gitlab"
```

Accepted values: `"github"`, `"gitlab"`, `"gitea"`, `"forgejo"` (Forgejo is treated as Gitea).

Auto-detection fallback: hostnames containing `gitlab` → GitLab, `gitea`/`forgejo` → Gitea, otherwise → GitHub.

### Automatic CI hydration trust

The TUI and desktop app may refresh CI automatically after opening a repository.
For those credential-bearing requests, repository-local `stax.toml` may select
only `remote.name`. The following values are accepted only from global
`~/.config/stax/config.toml`: `remote.base_url`, `remote.api_base_url`,
`remote.forge`, and all `[auth]` settings.

GitHub.com, GitLab.com, and Gitea.com use built-in trusted API mappings.
Self-hosted or enterprise remotes must set a matching global
`remote.base_url`; if the API uses a different hostname, set the relationship
explicitly with global `remote.api_base_url`. For GitHub Enterprise,
`auth.gh_hostname` must match the Git remote hostname. Automatic hydration
rejects mismatches before looking up a token or making a request.

## Auth tokens by forge

| Forge | Auth sources (checked in order) |
|---|---|
| GitHub | `STAX_GITHUB_TOKEN`, credentials file, `gh` CLI, `GITHUB_TOKEN` |
| GitLab | `STAX_GITLAB_TOKEN`, `GITLAB_TOKEN`, `STAX_FORGE_TOKEN`, credentials file |
| Gitea | `STAX_GITEA_TOKEN`, `GITEA_TOKEN`, `STAX_FORGE_TOKEN`, credentials file |

`stax auth` writes `~/.config/stax/.credentials` (mode `600`). That shared token is reused for GitHub, GitLab, and Gitea when forge-specific env vars are not set.

### GitHub resolution order

1. `STAX_GITHUB_TOKEN`
2. `~/.config/stax/.credentials`
3. `gh auth token` (`auth.use_gh_cli = true`)
4. `GITHUB_TOKEN` (only when `auth.allow_github_token_env = true`)

```bash
st auth status
```