## Summary
- Add experimental `wt step eval` subcommand that evaluates a minijinja
template in the current worktree context and prints the result to stdout
- Reuses existing hook template infrastructure — all variables
(`branch`, `repo`, `worktree_path`, etc.) and filters (`hash_port`,
`sanitize`, `sanitize_db`) are available
- Output goes to stdout with no decoration or shell escaping, designed
for scripting and shell substitution
```
$ wt step eval '{{ branch | hash_port }}'
16066
$ curl http://localhost:$(wt step eval '{{ branch | hash_port }}')/health
```
This is the first step from the
[IDEAS.md](https://github.com/max-sixty/worktrunk/blob/arbitrary-data/IDEAS.md)
design doc, directly addressing the user request in #947 — "found no way
to run it on a single branch."
## Test plan
- [x] 6 integration tests: basic branch, hash_port, multiple values,
sanitize_db, conditionals, template error
- [x] Help text and completion snapshots updated
- [x] stdout guard allowlisted
- [x] Doc pages auto-synced
- [x] All lints pass
- [x] 2360 tests pass (5 pre-existing shell_wrapper failures excluded)
Ref #947
> _This was written by Claude Code on behalf of @max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: worktrunk-bot <w@worktrunk.dev>
Follow-up to #1531. These three tools were only documented in
`llm-commits.md` — adding them as double-commented entries in the config
example makes them discoverable via `wt config create` and keeps all
tool commands in one place, verified by the existing sync test.
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- **Trimmed upstream tracking paragraph** — `--create` doesn't configure
upstream tracking, but that's standard `git switch -c` behavior. Removed
the prominent explanation and folded the remote-branch note into the
existing sentence.
- **Added missing hooks to creation lifecycle** — The numbered list now
includes `pre-switch` (step 1) and `post-switch` (step 6), matching the
actual execution order in `handle_switch.rs`.
- **Combined GitHub/GitLab sections** — Merged two near-identical
sections into a single "Pull requests and merge requests" section,
reducing repetition.
Closes#1518
## Test plan
- [x] `test_command_pages_and_skill_files_are_in_sync` passes (docs
auto-synced)
- [x] Unit tests pass
- [ ] CI green
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Maximilian Roos <5635139+max-sixty@users.noreply.github.com>
## Summary
- Moves branch resolution (`plan_switch`) before `pre-switch` hook
execution, so `{{ branch }}` in pre-switch hooks is the **destination**
branch rather than the source
- Applies to both `wt switch` and the interactive picker (`wt switch`
without args)
- Adds a test verifying `{{ branch }}` expands to the destination
Discussed in #1494 — `{{ branch }}` was chosen over `{{ target }}` per
[this
comment](https://github.com/max-sixty/worktrunk/issues/1494#issuecomment-4059068313).
## Test plan
- [x] All 5 pre-switch hook tests pass
- [x] All 62 user hook tests pass
- [ ] CI passes on all platforms
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The Claude/Codex recommended commands were duplicated across 4 locations
(Rust code, llm-commits.md, config.md, Taskfile) with no automated sync
— they were already drifting (missing `CLAUDECODE=` prefix, missing `-c
system_prompt=''`).
Now the double-commented entries in `config.example.toml` are the single
source of truth. `recommended_config()` parses them at runtime via
`include_str!` + `LazyLock`, and a new sync test verifies
`llm-commits.md` matches. Also fixes the existing drift in all copies.
> _This was written by Claude Code on behalf of @max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
Add the `[experimental]` → empty `<span>` mapping to the post-processing
table in docs CLAUDE.md, which was missing after the badge-to-heading
change in #1523.
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
Move experimental badges from the start of description paragraphs to
after the heading text in web docs.
Uses empty `<span>` elements with CSS `::after` for badge text, so the
span doesn't affect Zola's heading slug generation or page TOC entries.
This avoids the need for `{#slug}` anchor overrides and keeps sidebar
TOC entries clean (no "experimental" suffix).
Before: `## wt step relocate` / `EXPERIMENTAL Move worktrees to expected
paths.`
After: `## wt step relocate EXPERIMENTAL` / `Move worktrees to expected
paths.`
> _This was written by Claude Code on behalf of @max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
\`colorize_ci_status_for_html\` was renamed to \`post_process_for_html\`
and moved from \`src/main.rs\` to \`src/help.rs\` in #1499. The docs
CLAUDE.md reference was stale.
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
The picker had its own hardcoded 200ms per-command timeout default plus
a `WORKTRUNK_PICKER_BUDGET_MS` env var escape hatch, while `wt list` had
a separate experimental `[list] timeout-ms` for per-task timeouts. This
consolidates into a shared config model with clear separation between
per-task and wall-clock timeouts.
**New config model:**
- `[list] task-timeout-ms` — per-task command timeout, shared by both
list and picker (default: disabled)
- `[list] timeout-ms` — wall-clock budget for list's collect phase
(default: disabled)
- `[switch.picker] timeout-ms` — wall-clock budget for picker (default:
500ms, was 200ms per-command)
The picker's old per-command timeout goes away — the wall-clock budget
is sufficient. `collect_deadline` moves from a `collect()` parameter
into `ShowConfig::Resolved` where it belongs. Removes the
`WORKTRUNK_PICKER_BUDGET_MS` env var and a dead benchmark that tested a
non-functional env var.
Note: `[list] timeout-ms` changes semantics from per-task to wall-clock
budget. The old field was marked "(Experimental)" and not shown in
user-facing docs.
> _This was written by Claude Code on behalf of max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
Replaces plain `[experimental]` text in web docs with styled pill badges
— small uppercase labels with a subtle border that read as metadata
rather than emphasis.
Canonicalizes all experimental markers to `[experimental]` (was a mix of
`[experimental]`, `(experimental)`, `(Experimental)`). One marker format
in cli.rs, one `.replace()` in the post-processing.
Also renames `colorize_ci_status_for_html` → `post_process_for_html` (it
handles badges and URLs too), adds a module docstring documenting the
full `--help-page` pipeline, and fixes a latent double-application of
the post-processor on subdoc content.
> _This was written by Claude Code on behalf of @max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
The cross-device rename happens when the worktree is on a different
filesystem from `.git/`, not "if `.git/` is on a different filesystem."
Minor accuracy fix.
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
Remove the TODO ("promote this feature more prominently once it's been
tested in the wild") since LLM summaries are now on the homepage.
Tighten the subsection — the prerequisites are already in the Columns
table above, so the text now focuses on the mechanism (reuses
`commit.generation`), how to enable (`summary = true`), and caching
behavior.
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
The promote command swaps branches *and* gitignored files between
worktrees — the old definition ("Put a branch into the main worktree")
undersold what it does. Updated to "Swap a branch into the main
worktree" with a subdefinition that mentions the gitignored file
exchange.
Added promote as a subdoc section on the step page (it was the only step
command with substantial `after_long_help` content that lacked one).
Expanded the gitignored files documentation to mention the shared
discovery mechanism with `copy-ignored`, `.worktreeinclude` filtering,
and the `rename()`-based swap approach.
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
Add three new entries to the "Workflow automation" feature list on the
homepage: `wt list --full` (CI status & AI summaries), PR checkout (`wt
switch pr:123`), and dev server per worktree (`hash_port`). Also add a
"Full mode" section to the list docs as a link target, and link tips &
patterns from next steps.
> _This was written by Claude Code on behalf of @max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
The Aliases section was sandwiched between the Operations list and See
Also — disconnected from the subcommand documentation below. Every other
step operation (commit, squash, copy-ignored, etc.) gets its own section
after the command reference, but aliases didn't.
Moved aliases after the last subdoc marker so it appears alongside the
other subcommand sections. Also added `[experimental]` prefix to match
the operations list entry, and fixed the `console → bash` code block
transform to apply before the subdoc split (it was only applied to main
content, so anything after subdoc markers missed it).
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
Adds a legend for the `⋯` (loading) and `·` (skipped) placeholder
symbols to `wt list --help`, under the existing Status symbols section.
These symbols appear across all columns during progressive table
rendering but weren't documented. The `·` symbol was introduced
alongside the picker budget feature — it indicates data collection was
truncated (timed out or branch too stale), as opposed to `⋯` which means
data is still arriving.
> _This was written by Claude Code on behalf of @max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
Link subcommands from the Operations index to their detailed sections
below. Previously all items were plain text; now commit, squash, diff,
copy-ignored, for-each, prune, relocate, and alias are clickable
anchors.
Also adds `diff` as a subdoc expansion (it had `after_long_help` content
but wasn't included as a subdoc), and fixes the repetitive opener in
diff's `after_long_help` — the definition+subdefinition already listed
what's included, so the opener now just adds the `wt merge` context.
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
Draft implementation per @max-sixty's
[suggestion](https://github.com/max-sixty/worktrunk/pull/1404#issuecomment-4041170944):
reuse `--no-cd` to make the picker print-only instead of adding a
separate `--print` flag.
- When `wt switch --no-cd` opens the interactive picker (no branch
argument), selecting a branch prints its name to stdout and exits — no
switching, no cd directive, no hooks
- When `wt switch --no-cd <branch>` is used with an explicit branch,
behavior is unchanged (switches without cd)
- `alt-r` (remove) is blocked in print mode since it's a read-only
operation
This avoids adding a new CLI flag while giving the same scripting
capability that #1404 proposed.
## Test plan
- [x] Unit tests for `resolve_print_identifier` (switch, create, remove
cases)
- [x] PTY integration test: `--no-cd` picker prints branch name, emits
no cd directive
- [x] Existing `--no-cd` tests still pass (directive suppression, hooks,
execute)
- [x] Help snapshots and doc sync updated
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Without this flag, `claude -p` saves the commit generation conversation
to session history. When the user later runs `claude --continue`, it
resumes that ephemeral commit session instead of their previous working
session.
> _This was written by Claude Code on behalf of @max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Updates example heading from "Remove specific worktrees:" to "Remove
specific worktrees / branches:" to clarify that `wt remove` also works
on branches
Ref #1415
## Test plan
- [x] Snapshot test updated and passing
- [x] Doc sync test passes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
For iOS developers, this PR add a `post-remove` hook recipe to Tips &
Patterns that cleans up Xcode's DerivedData when removing a worktree.
## Context
Xcode's DerivedData directory names are hashed from the `.xcodeproj`
path, making them hard to match by name alone. Each DerivedData
directory contains an `info.plist` with a `WorkspacePath` field that
records the original project path. This recipe leverages that to find
and remove only the matching cache.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The JSON context section had two issues:
- It described hook_type and hook_name as extras "plus" the template
variables, but they're regular template variables already in the table.
Fixed to say "all template variables".
- It showed a floating Python script without the toml hook definition
that calls it. Added the toml entry so the reader sees how JSON stdin
gets wired up.
Also trimmed the example (removed bridge text and filename comment) to
keep it concise.
> _This was written by Claude Code on behalf of @max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
The `@/hook.md#command-approval` anchor in step.md doesn't exist,
causing
the `publish-docs` workflow to fail (`zola build` rejects broken
internal
anchors). The approval docs live under the `# Security` heading, so the
correct anchor is `#security`.
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
Removes the deprecated `--no-background` flag (replaced by
`--foreground`) from
`remove`, `hook post-start`, `hook post-switch`, and `hook post-remove`.
Removes
all supporting deprecation infrastructure: warning message,
`is_foreground_mode`
helper, `DEPRECATED_ARGS` completion filtering.
Promotes `--no-progressive` from deprecated to hidden flag with inline
mention
in `--progressive`'s help text, using `flag_pair()` for consistent
handling.
Carries `foreground: bool` through the entire call chain instead of
negating to
`background` at call sites. Refactors `print_message` to use
`success_message()`/`progress_message()` helpers instead of manual color
construction, adding `FormattedMessage::append()` for integration
symbols that
need native styling outside the message's color span.
> _This was written by Claude Code on behalf of maximilian_
---------
Co-authored-by: Claude <noreply@anthropic.com>
Group `--branches`/`--remotes` under "Picker Options" and
`--yes`/`--no-verify` under "Automation" in `wt switch`, `wt remove`,
and `wt merge` help output, using clap's `help_heading`.
> _This was written by Claude Code on behalf of @max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
These were already included in the JSON context piped to stdin but
couldn't be used as `{{ hook_type }}` or `{{ hook_name }}` in templates
— the values were inserted into `cmd_context` *after* template
expansion.
Now both code paths (`expand_commands` for execution,
`expand_command_template` for `wt hook show --expanded`) include them
before rendering. Also added to `TEMPLATE_VARS` so `--var hook_type=...`
works for testing.
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
Promotes major sections of `wt hook --help` to H1 headings (rendered as
uppercase bold green), matching the pattern from `wt config --help`.
Subsections become H2.
Before: flat `##`/`###` headings made the long help text hard to scan.
After: five landmark sections — Hook Types, Security, Configuration,
Running Hooks Manually, Designing Effective Hooks.
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
The home page (`/`) and `/worktrunk/` serve identical content (the index
template renders `worktrunk.content` directly), but each declared its
own
canonical URL. Google saw two competing pages and wasn't indexing either
as
the home page result.
- Set canonical + og:url on `/worktrunk/` to point to `/`
- Fix home page og:title/twitter:title — was just "Worktrunk", now uses
the
full tagline from `config.extra.site_description`
- Align `worktrunk.md` description with `site_description`
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
The Blocking column's "No (background)" was overspecified — the column
header
and footer definition already explain what "No" means.
> _This was written by Claude Code on behalf of max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
Move `--branches` and `--remotes` into a "Picker Options" section in `wt
switch --help`, since they only apply to the interactive picker mode and
conflict with all direct-switch flags.
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Adds `wt step <alias-name>` for running user-defined command templates
configured in `[aliases]` sections of user or project config
- Aliases support the same template variables as hooks (`{{ branch }}`,
`{{ worktree }}`, etc.) plus custom `--var KEY=VALUE` variables
- Project-config aliases require the same command approval flow as
project hooks; user-config aliases are trusted. `--dry-run` skips
approval since it's a read-only preview
- Alias names matching built-in step commands are filtered from the
"available" list in error messages (shadowed by the built-in)
- Introduces `Phase` enum (`Hook(HookType)` | `Alias`) replacing the old
`hook_type` + `phase_override` pattern in the approval system
- Renames `HookCommand` to `ApprovableCommand` since it now covers both
hooks and aliases
## Test plan
- [x] Unit tests for `AliasOptions::parse` (name-only, --dry-run, --yes,
--var, empty key rejection, positional arg rejection)
- [x] Integration tests for alias execution, dry-run (without --yes),
exit code propagation
- [x] Integration tests for shadowing (filtered from available list)
- [x] Integration tests for user/project config merging
- [x] Integration tests for approval flow (project prompts, user skips,
override skips, already-approved, --yes bypass, decline)
- [x] Unit tests for `merge_alias_maps` coverage
- [x] Sync test: `BUILTIN_STEP_COMMANDS` matches actual `StepCommand`
clap variants
> _This was written by Claude Code on behalf of max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
Replace `.lvh.me` with `.localhost` in all Caddy subdomain routing
examples
(docs, CLI help, tests). `.localhost` resolves to 127.0.0.1 via the OS
on
macOS and Linux with systemd-resolved — no external DNS dependency.
Also fixes a flaky PTY test: `WORKTRUNK_TEST_DELAYED_STREAM_MS` was
missing
from `STANDARD_TEST_ENV` in shell_wrapper.rs, causing `git worktree add`
output to appear non-deterministically under heavy parallel load.
Closes#1334
> _This was written by Claude Code on behalf of maximilian_
---------
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Maximilian Roos <m@maxroos.com>
The `summary` field was wired up in the table UI but never added to
`JsonItem`, so `wt list --format=json` never included LLM summaries even
when configured. Also improves doc comments to mention all three
requirements for summary generation.
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Change the `pre-switch` hook example from `git fetch` to `git pull`
- `git fetch` only updates remote tracking refs without advancing the
local branch, so a new worktree created after fetch is still based on
the old local HEAD — not the latest remote
Reported by @jdb8 in #1085.
## Test plan
- [x] Doc sync test passes (docs and skill reference updated
automatically)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
Adds an `alt-d` keybinding to the `wt switch` interactive picker that
removes the highlighted worktree directly, without needing to type the
branch name or switch first.
- **Safe defaults**: no `-D` (force-delete unmerged branches), no `-f`
(force-remove dirty worktrees) — matches the safety behavior requested
in #1251
- Removes the worktree and its merged branch in foreground (immediate
feedback)
- Shows standard `wt remove` success/failure output after picker exits
- Controls line in the preview pane updated to show `alt-d: delete`
Closes#1251
## Test plan
- [x] All 554 unit tests pass (`cargo test --lib --bins`)
- [x] Clippy clean
- [x] Doc sync test passes
- [ ] Manual testing: open `wt switch`, highlight a worktree, press
`alt-d` — verify it removes the worktree and shows the success message
- [ ] Manual testing: try `alt-d` on main worktree — verify it shows
"Cannot remove the main working tree" error
- [ ] Manual testing: try `alt-d` on a worktree with uncommitted changes
— verify it shows the dirty worktree error
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
## Summary
- Add opencode as a supported tool for LLM commit message generation
- Condense opencode, llm, and aichat into a single "Other tools" section
with just recommended commands
- Claude Code and Codex retain their own detailed sections
- Auto-sync skill reference file
Closes#911
## Feedback welcome
@viicslen @gabimoncha — does `opencode run -m anthropic/claude-haiku-4.5
--variant fast` work correctly? Happy to adjust the flags.
---------
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
- Add early `check_ffmpeg_libass()` at demo build startup — exits with
install
instructions when ffmpeg lacks libass (required for keystroke overlay
ASS
subtitles)
- Add output verification in `record_vhs()` — catches silent failures
where VHS
exits 0 but produces no GIF
- Document the Homebrew API-bottle vs tap-formula issue in demos
CLAUDE.md
Homebrew's API-sourced formula strips `libass`; the tap formula includes
it.
The fix is `HOMEBREW_NO_INSTALL_FROM_API=1 brew install
--build-from-source ffmpeg`.
## Test plan
- [x] Verified `check_ffmpeg_libass()` passes with correct ffmpeg
- [x] Verified full demo build succeeds with `--only wt-switch-picker`
- [x] Lints pass (`pre-commit run --all-files`)
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Mark `wt step prune` as `[experimental]`
- Remove candidates inline as they're discovered instead of
scan-then-remove — each worktree is pruned immediately, with the current
worktree deferred to last
- Handle per-candidate errors gracefully (warn and skip) rather than
aborting the entire prune — dirty worktrees are caught by the same
`ensure_clean` check that `wt remove` uses
- Dry-run lists candidates with `○` lines; actual removal only shows `◎
Removing` output
- Drop redundant help text ("uncommitted changes are skipped" is
inherited from `wt remove`; "Note: experimental" is redundant with
`[experimental]` tag)
- Add test for dirty worktree handling
## Test plan
- [x] All 2740 tests pass (`wt hook pre-merge --yes`)
- [x] Snapshot tests reviewed for all prune scenarios (dry-run,
multiple, dirty, locked, stale, orphan branches, current worktree,
detached, min-age)
- [ ] Manual test: run `wt step prune --dry-run` on a repo with merged
worktrees
> _This was written by Claude Code on behalf of @max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Add per-page `<meta name="description">` to all doc pages — command
pages auto-generated from CLI `about`/`long_about` via new
`--help-description` flag, non-command pages manually written
- Add `<link rel="canonical">` URLs and JSON-LD structured data (WebSite
+ SoftwareApplication) on the homepage
- Add custom `sitemap.xml` template with `<lastmod>` dates and
descriptive homepage `<title>`
- Extract shared `extract_about_and_subtitle()` helper, eliminating
duplicated subtitle logic between `handle_help_description` and
`combine_command_docs`
- Fix broken anchor in faq.md (`#picker-summaries` →
`#branch-summaries-experimental`)
## Test plan
- [x] Full test suite passes (2713 tests via `wt hook pre-merge --yes`)
- [x] All lints clean (pre-commit, clippy, cargo fmt)
- [x] Doc sync test confirms auto-generated descriptions match CLI help
- [x] Zola build succeeds with all template changes
> _This was written by Claude Code on behalf of @max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
Adds `wt step promote` — an experimental command to exchange branches
between the main worktree and another worktree, including swapping
gitignored files (build artifacts, `.env`, `node_modules/`, etc.). This
addresses #738 where users want to move a branch from a worktree into
the main worktree.
This is useful when the main worktree has special significance (Docker
Compose configs, IDE settings, heavy build artifacts anchored to project
root) and hooks aren't yet set up to run on arbitrary worktrees.
**Key features:**
- Exchange branches between main worktree and any other worktree
- Swap gitignored files so build artifacts follow their branch
- Auto-restore: `wt step promote` from main worktree (no args) restores
default branch
- Shows mismatch state in `wt list` with ⚑ indicator
- Warns when creating mismatch, with hint to restore
- `.worktreeinclude` filtering respected per worktree
**Swap algorithm (stage → exchange → distribute):**
1. Discover gitignored entries before branch exchange (`.gitignore`
belongs to the branch)
2. Stage files to `.git/wt-promote-staging/` before `git switch`
(prevents tracked file collision)
3. Exchange branches with per-step rollback
4. Cross-distribute staged files to new worktrees
5. Best-effort restore on failure with recovery guidance
**Adversarial testing (4 rounds, 9 issues found and fixed):**
- Stage before switch to prevent tracked file collision
- Per-step rollback in `exchange_branches()`
- Bail on leftover staging (don't destroy previous run's files)
- Recovery guidance on all failure paths
- Dedicated `restore_staged()` for rollback semantics
- `ErrorKind::CrossesDevices` for cross-device fallback
- `Option<PathBuf>` instead of sentinel empty path
## Test plan
- [x] 25 integration tests covering: bidirectional swap, round-trip,
`.worktreeinclude` filtering, nested directories,
same-filename-different-content, tracked-vs-ignored separation, stale
staging detection, bare repo rejection, detached HEAD, dirty worktrees,
no-arg from main/linked, branch argument, already-in-main, mismatch
indicator in `wt list`
- [x] All 2576 tests pass
- [x] All lints clean
- [x] `copy-ignored` tests still pass after
`list_and_filter_ignored_entries` extraction
🤖 Generated with [Claude Code](https://claude.com/claude-code)
> _This was written by Claude Code on behalf of max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- Replace `<bold>` with `<bright-black>` inside `hint_message()` calls —
bold's close tag (`[22m`) resets both bold and dim, breaking hint
styling
- Use `format_path_for_display()` instead of raw `.display()` in
user-facing messages (config warning, relocate errors, step_commands
output)
- Remove "you/your" pronouns from help text and rephrase typo
suggestions
- Reorder hint phrasing to canonical "To X, run/use Y" pattern
## Test plan
- [x] All 1084 integration tests pass
- [x] All lints pass (`pre-commit run --all-files`)
- [x] Snapshot tests updated to reflect new wording
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>