SignPath Foundation's OSS program requires the attribution notice, and a
route to the code signing policy, on a project's home page and
download/release pages. Worktrunk had both only on the policy page
itself — nothing on the README or the docs landing page. This puts the
notice in the install section's Windows block, beside the artifacts it
actually describes:
> Free code signing provided by [SignPath.io](https://signpath.io/),
certificate by [SignPath Foundation](https://signpath.org/) —
[policy](https://worktrunk.dev/code-signing/).
The edit is one line in `docs/content/worktrunk.md`; the README's
Install→Further reading block is generated from it, so it propagates
there and to both skill mirrors.
The policy page leaves the docs navigation in the same change, so this
is the single place it's linked from. `hide_from_nav = true` in a page's
`[extra]` skips it in all three loops over `docs_section.pages`: the
desktop TOC (`macros.html`), the mobile menu (`base.html`), and the
prev/next flow nav (`page.html`). The page stays published and reachable
at `/code-signing/` — unlisted, not removed.
In the nav loops the skip wraps the whole per-page body, so a hidden
page can't emit a stray group heading. In the prev/next loop it guards
only the *candidate* assignments — the current-page test stays
unguarded, because viewing a hidden page directly must still flip
`found_current` or its own neighbours compute against the wrong page.
Verified both directions: FAQ ends at `← Tips & Patterns` with no
forward link, and the policy page keeps `← FAQ` back out into the docs.
<details><summary>Why this came up, and the placement tradeoff</summary>
Found while debugging why the `release-signing` signing policy shows
INVALID in the SignPath console. That turned out to be unrelated and not
fixable here — its certificate ("Release certificate 2026", subject
`CN=SignPath Foundation`, on SignPath's HSM) is in `CSR PENDING` with no
validity dates, awaiting CA issuance. The policy's own configuration is
complete and correct. Worktrunk currently signs with the test
certificate, which is VALID.
The attribution gap was the one thing found on our side. Whether it
bears on the pending review is unknown — the console exposes no
application status.
On placement: the notice sits inside the collapsed `<details>`, so it
isn't visible until a reader expands "Windows & other". That's
deliberate — the signing is Windows-specific and the notice reads better
next to it than in the page chrome — but it is the least prominent
placement that still counts as a link, and the terms ask for the notice
*on* the home and download pages. Worth knowing if placement is ever
queried during review.
</details>
> _This was written by Claude Code on behalf of max-sixty_
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nightly maintenance: the README status blockquote opens with a
month+year stamp that should track the current month (per the README
date check in the `running-tend` skill). It read **July 2026**; today is
2026-08-01, so this refreshes it to **August 2026**.
No test — this is a one-word documentation refresh with no behavioral
surface. The blockquote month isn't asserted by any snapshot
(`readme_example_list_branches` covers the `wt list` example output
further down, not this line).
---------
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
The Commit cell sliced `&head[..8]` while `--format=json`'s `short_sha`
carried git's `%h`, so one commit read `1b9f1d96` in the table and
`1b9f1d9` in JSON, and `core.abbrev` reached only the JSON. #3675 gave a
detached row's Branch cell the same slice, so the disagreement showed up
twice on one row.
`ListItem::short_sha` becomes the only abbreviation of `head` anywhere:
the Commit cell, a detached row's Branch cell, the statusline, and JSON
all render it. `abbreviated_head()` is gone.
## Column widths
`COMMIT_HASH_WIDTH = 8` is gone. The Commit column and the Branch
column's detached budget both measure the SHAs they will render, so
`core.abbrev = 12` no longer truncates mid-hash and the default 7 stops
reserving a column nothing fills — the freed character goes to Message.
## Latency
`collect()` folds `%h` onto the rows before layout instead of after the
skeleton. The batch carrying it already gates the skeleton for `%ct`
sort order, so this is a map lookup rather than new I/O, and both cells
are identity columns with no placeholder — they still paint in the first
frame.
Measured on a 40-worktree / 400-branch fixture: git subprocess counts
are identical (5 pre-skeleton, 108 for the full run). Pre-skeleton wall
time is unchanged; running both binaries in each order, the sign of the
difference follows run order rather than the binary (+1.5 ms with this
branch second, −0.5 ms with it first), so the residual sits inside
drift.
## Behavior change
Where the commit-details batch fails, the Commit cell is now empty
rather than a slice of a SHA git refused. Age and Message already report
that failure the same way, under the same warning, and two snapshots
show it. `render_text_cell` also stops styling empty text, so a blank
cell no longer emits an escape pair around nothing.
## Reading the diff
160 files, but the hand-written part is +91/−79 in `src/commands/list/`
plus a +71 test. The rest is generated. The docs mirrors and help
snapshots are symmetric. Of the snapshot lines, content is +799/−799 —
every changed line a 1-for-1 hash swap — while +1396 is insta `env:`
metadata refreshing on the 128 snapshots this happens to touch.
`test_list_abbreviated_sha_follows_git` pins the invariant: the table's
hash equals JSON's `short_sha` at git's default and at `core.abbrev =
12`, and a longer prefix is ruled out. It fails against the old fixed
slice.
> _This was written by Claude Code on behalf of max-sixty_
Follow-up to the [`wt remove <path>` discussion on
#3480](https://github.com/max-sixty/worktrunk/pull/3480#issuecomment-5039137116),
widened from that one command to the whole surface. #3480 has since
landed and is merged in here — its duplicate-checkout warning composes
with this: the warning names the shadowed worktrees, and a path is how
you then address one.
## Audit
Verified against the built binary. wt had three answers to "what does
this token mean?":
| Route | `@` `-` `^` | worktree path | `pr:N` |
|---|---|---|---|
| `wt switch` (`resolve_switch_target`) | yes | only if absolute or ≥2
components, and not `--create` | yes |
| `wt remove` (`resolve_worktree_arg`) | yes | any token | no |
| everything else (raw `worktree_for_branch`) | **no** | **no** | no |
Same token, same cwd, two answers:
```console
$ wt remove inner # ✓ Removed innerbranch worktree & branch
$ wt switch inner # ✗ No branch named inner
```
And outside switch/remove the shortcuts didn't work at all — `wt step
diff --branch @` was `✗ Branch @ has no worktree`, while `wt config
state marker set --branch @` silently wrote state under the literal key
`@`. Separately, wt prints paths as `~/…` but wouldn't accept that form
back.
## Change
One canonicalizer in the lib, `Repository::resolve_worktree`, absorbing
the path fallback that lived in the bin crate's `resolve_worktree_arg`
(now deleted). Resolution order is documented once, on that function:
`@`, then `-`/`^`, then a branch with a worktree, then a path naming a
registered worktree, then the branch alone.
**Branch-first, everywhere.** A directory never shadows a branch that
shares its name; a path answers only what a branch cannot — a detached
worktree, or one of two checkouts of the same branch (#3480's case). The
`looks_like_path` shape gate is gone, so a single-component path
resolves like any other.
Two shapes cover what callers need: `require_worktree` for commands that
need a worktree to operate in, `require_selected_branch` for arguments
that key by branch. The merge/rebase target validators fall through to
the same path lookup, so a target can be named by the worktree it's
checked out in.
Routed through it: `switch` (including `--base`), `remove`, `step commit
--branch`, `step diff --branch` and its target, `step copy-ignored
--from`/`--to`, `step promote`, `step relocate`, `config state --branch`
(9 sites), and `merge` / `step rebase` / `step squash` / `step push`
targets.
`resolve_input_path` — already documented as the one resolution point
for user-supplied paths — now expands a leading `~`, so the tilde form
worktrunk prints is a form it reads back. `~user` stays literal; wt
doesn't reimplement that shell feature.
## Documentation
A path is an alias, not a second addressing scheme, so it is stated once
rather than on every argument: one paragraph in `wt switch`'s help and
one sentence on the addressing line in `worktrunk.md`. Argument
descriptions still read as branches. The two exceptions are the
arguments whose descriptions are already catalogues of accepted forms —
`wt switch`'s (`Branch, worktree path, shortcut, or PR/MR URL`) and `wt
remove`'s, which has named the path since before this branch. The
Worktree Model section of `CLAUDE.md` records which way to document it,
so the next argument doesn't grow its own copy.
## Two silent no-ops fixed along the way
- `wt step relocate <unmatched>` matched arguments against branch names
by string equality, so a typo filtered everything out and the empty
result rendered as `○ All worktrees are at expected paths` — a success
message for work that never happened. Every way an argument can fail to
land on a relocatable worktree now errors, including the detached and
prunable cases the new path route makes reachable.
- A selector matching nothing was reported as a branch without a
worktree, hinting `wt switch <token>` — which creates a worktree only
when the branch exists, so for a mistyped path it would just fail again.
`WorktreeSelectorNotFound` now says `No branch or worktree named X`; a
branch that genuinely exists without a checkout keeps the create hint.
## Testing
Full gate green: 4596 tests, lints, docs sync, `--features
shell-integration-tests` clippy. `codecov/patch` is 99.25% of diff hit
against a 97.93% target. New coverage:
- Unit: branch-and-path equivalence, branch-beats-same-named-directory,
detached-by-path (and its `require_selected_branch` refusal), shortcuts
never treated as paths, branch-only fallthrough, and the two distinct
not-found errors. Plus `expand_tilde` round-tripping
`format_path_for_display`.
- Integration: `switch` by relative/single-component/absolute/tilde
path, `--base` by path, `step diff --branch` by path and `@` (asserted
equal to the by-branch output), `config state --branch` set via `@` and
read via the worktree path, and both new relocate errors.
`wt remove`'s resolution is unchanged — it already had this rule; it now
shares the implementation. The 106-test `remove::` suite is untouched
and green.
- Integration: `wt step push <worktree-path>` (the
`require_target_branch` half of the target fallback), and `wt step
relocate` against a prunable worktree.
One diff line is unhit: `expand_tilde`'s fallback when `home_dir()`
returns `None`, which has no deterministic trigger. The `@`-resolution
backstop in `resolve_worktree` is untested for the same reason — no CLI
route reaches it — so it kept its original `match` arm rather than being
re-indented into the diff.
## Left out
`wt config state default-branch set` and `previous-branch set` take a
branch name as a *value to store* rather than a selector, so they still
take it literally.
> _This was written by Claude Code on behalf of Maximilian Roos_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Move the `main…±` branch-diff column (line diffs since the merge-base) into the default `wt list` view — it's pure local git backed by a persistent content-addressed cache, so the original blocking-walk concern no longer applies. `--full` now gates only the two off-machine columns: CI status (network) and LLM branch summaries. The interactive picker (`wt switch`) follows suit and is effectively `wt list --full`; on narrow terminals with the preview shown, CI clips past the split and alt-p reveals it.
Also adds a `.typos.toml` ignore rule for truncated word fragments glued to the … ellipsis, so the narrower Message column's truncated quickstart embed doesn't get spell-"corrected" by pre-commit.ci.
Per @max-sixty's [direction in
#2838](https://github.com/max-sixty/worktrunk/issues/2838#issuecomment-4509447593):
revert the docs portion of #2840 and keep the code. Docs continue to
recommend `pre-start`/`post-start`; both names work in code so anyone
who already followed the briefly-changed docs (e.g. @EcksDy) isn't
stranded once a release ships these aliases.
## User-visible — back to `pre-start`/`post-start`
- README, docs site, skill mirrors, `dev/*.example.toml`,
`plugins/worktrunk/README.md`, `flake.nix`, `.config/wt.toml`
- `src/cli/mod.rs` / `src/cli/config.rs` / `src/cli/step.rs` /
`src/help.rs` after_long_help and example snippets — and the auto-synced
`docs/content/` and `skills/worktrunk/reference/` mirrors
- `wt hook --help` canonical subcommand names; completion advertises
`-start` only
- `HookType` Display via strum, serde `rename`, and clap `ValueEnum`
name — all `pre-start`/`post-start`. The Rust variant identifiers stay
`PreCreate`/`PostCreate` (internal; we already paid for that rename in
#2840, and now the eventual flip is a Display-only change)
- `HooksConfig` serde canonical fields
## `*-create` still works (kept code)
- `wt hook pre-create` / `post-create` — CLI alias on the canonical
subcommand
- `pre-create` / `post-create` in config: top-level, `[hooks.*]`, and
per-project, in string, `[table]`, and `[[array-of-tables]]` form.
Mechanism: serde `alias = ...` on the field, plus a silent in-memory
rename in `migrate_content()` so the round-trip in `unknown_tree`
doesn't flag table forms as schema-unknown.
- The pre-0.32.0 `post-create` fatal-load-error machinery stays removed
— the name is reclaimed, and both forms load without error.
## Smaller bits
- `valid_user_config_keys()` / `valid_project_config_keys()` append
`pre-create` / `post-create` so the unknown-field round-trip skips them.
`test_valid_*_keys_all_deserialize` skips both aliases (they can't sit
alongside the canonical without a duplicate-field error).
- `DEPRECATED_SECTION_KEYS` drops the `pre-start`/`post-start` entries
#2840 added — `pre-start`/`post-start` are canonical again.
- `find_pre_start_from_doc` / `find_post_start_from_doc` /
`find_renamed_hook_key` / `is_non_empty_item` /
`migrate_start_hooks_doc` and their tests are removed; the migration
direction flips via a new `migrate_create_hooks_doc` (silent, mirrors
the prior shape).
- Test files `e2e_shell_post_create.rs` and `post_create_commands.rs`
rename back to `_post_start_` (via `git mv`, so the rename shows as a
rename).
## Testing
`cargo run -- hook pre-merge --yes` — 3806 tests pass; the 10 failures
are all `case_4` of `shell_wrapper::unix_tests::*` (nu-shell case; `nu`
isn't installed in this runner; same failures occur on `main`).
Also manually verified that a fresh `wt switch --create` against a
project config with `[post-create]` loads cleanly with no unknown-field
warning and the hook fires as `post-start`.
## Follow-up
Per @max-sixty: in a couple of weeks, once a release with
both-names-work is out and users have had a chance to upgrade, the docs
flip is straightforward (most of it is in `src/cli/mod.rs`'s
`after_long_help` and the doc-sync test propagates).
Re #2838.
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Phase 1 of the staged hook rename tracked in #2838: the worktree-creation hooks `pre-start`/`post-start` become `pre-create`/`post-create`. The old names keep working with no deprecation warning yet (Phase 2, months out, adds the warning).
## What changes
- `pre-create`/`post-create` are canonical everywhere: the `HookType` enum, the `HooksConfig` serde fields, the `wt hook` CLI, completion, and all docs.
- Old names keep working: `migrate_content()` rewrites `pre-start`/`post-start` config keys to `-create` before serde, and `parse_hook_type` accepts the old CLI names as silent aliases. `wt config update` rewrites them on disk; `wt config show` shows the migration diff. `wt hook <type>` execution and `wt hook show` both accept the old names; completion and `--help` advertise only the canonical names.
- `detect_deprecations()` flags the old keys so `update`/`show` act on them, but `format_deprecation_warnings()` stays silent (Phase 2 adds the warning). A new empty-warnings guard in `check_and_migrate` keeps a `-start`-only config from emitting a stray hint.
- The dead pre-0.32.0 `post-create` machinery is removed: the fatal `POST_CREATE_REMOVED_MSG` load error, the vestigial `HooksConfig.post_create` merge-fold, and `find_post_create_from_doc`. `post-create` is reclaimed as the canonical background creation hook.
## Semantic flip
Before v0.32.0, the key `post-create` named a *blocking* hook. It now names the *background* one.
Since v0.44.0, a pre-0.32.0 `post-create` config is a fatal load error on the `check_and_migrate` paths: `ProjectConfig::load` and user/system config loading, which fire on essentially every `wt` command. A repo carrying one has been unusable ever since. The one path that skips that check is `project_config_at_ref` (the base-ref read behind `wt switch --create`), which applies only structural migration. A pre-0.32.0 `post-create` surviving solely on a base ref, never checked out into a worktree, would now load as a background hook rather than folding into the blocking `pre-start`. That edge case is accepted: once `post-create` is valid again, reclaiming the name and detecting the dead key are mutually exclusive.
## Reviewing this diff
205 files, but the substance is ~36 files under `src/`. The rest is regenerated snapshots and auto-synced doc mirrors. Start with:
- `src/config/deprecation.rs` — detection (`find_renamed_hook_key`), migration (`rename_hook_key`), removal of the fatal block, the empty-warnings guard, and the `DEPRECATED_SECTION_KEYS` entries that stop unknown-field detection from flagging the migrated keys.
- `src/config/hooks.rs`, `src/git/mod.rs` — the serde field and enum renames.
- `src/config/project.rs` — `ProjectConfig::load` deserializes `check_and_migrate`'s migrated content, so a current-worktree config using the old keys loads into the canonical fields.
- `src/cli/hook.rs`, `src/commands/hook_commands.rs`, `src/completion.rs`, `src/main.rs` — the CLI alias layer; `wt hook show` accepts the old type names as hidden value-parser aliases.
- `src/cli/mod.rs` — the `wt hook` docs, including the soft-deprecation note linking #2838.
The ~93 modified snapshots also pick up deterministic env-block lines (`GIT_*: ""`, `LLVM_PROFILE_FILE`) that pre-existing snapshots already carry. That is stale-snapshot drift surfaced by the regeneration, not a behavior change.
## Testing
Full suite green (3799 tests). New coverage: `snapshot_migrate_start_to_create` (migration preserves value shape and position), `test_deprecated_start_hook_key_runs_silently` and `test_standalone_hook_start_alias_runs_silently` (old config and CLI names run with no warning), `test_config_show_displays_start_hook_migration` (`config show` reveals the diff without an "unknown field" warning), and `test_hook_show_accepts_deprecated_start_hooks` (a current-worktree config using the old keys loads, and `wt hook show` takes both the canonical and the deprecated type arguments).
Part of #2838.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The line was a generic LLM-flavored flourish ("scaling," "becomes
trivial") that also overclaimed: it sat above the core-commands GIF,
whose own caption honestly says "Listing worktrees, switching, cleaning
up." The parallel-agents demo lives further down the page.
Drop the slop sentence above the basics demo, and move the zoom-out
claim down to the second demo's lead-in, where the GIF actually
substantiates it: `"Multiple parallel agents, same simple commands:"` —
ties back to the "as easy as branches" framing in the intro.
Co-authored-by: Claude <noreply@anthropic.com>
## Problem
The README install section lists Homebrew, Cargo, winget, and pacman,
but does not mention the conda-forge package — which has been published
since 0.32.0 and is currently at 0.44.0 with builds for `linux-64`,
`linux-aarch64`, `osx-64`, `osx-arm64`, and `win-64`
([anaconda.org/conda-forge/worktrunk](https://anaconda.org/conda-forge/worktrunk)).
Reported in #2424.
## Solution
Adds a "Conda / Pixi" entry to the install section in
`docs/content/worktrunk.md`, between Cargo and the Windows expandable.
README and the worktrunk skill reference are auto-synced from this
source.
## Testing
`cargo test --test integration test_docs_are_in_sync` passes.
---
Closes#2424 — automated triage
---------
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
Adds static command-output blocks to the docs pages dominated by GIFs
(addresses #2403). The blocks are **driven from insta snapshots** so
they stay in lockstep with what `wt` actually prints — one source flows
to all three surfaces:
- terminal `wt <cmd> --help` (plain text, gutter-formatted)
- `docs/content/*.md` (colorized `{% terminal(cmd="...") %}` shortcode)
- `skills/worktrunk/reference/*.md` (plain `$ cmd\noutput\n` block)
## What changed
- **Six scripted snapshot tests** produce realistic output (`cargo
nextest run`, `flyctl scale count 0`, LLM-generated commit messages):
- `test_docs_merge_pre_merge_hook` — `wt merge` with pre-merge hook
- `test_docs_step_commit_llm` — `wt step commit` with LLM
- `test_docs_step_squash_llm` — three-commit squash with LLM
- `test_docs_merge_squash_llm` — `wt merge` (squash + LLM + merge) for
`llm-commits.md`
- `test_docs_remove_pre_remove_hook` — `wt remove` with pre-remove hook
- `test_docs_hook_pre_merge` — `wt hook pre-merge` direct invocation
- **Sync pipeline extension** in
`tests/integration_tests/readme_sync.rs`:
- New write-back pass `sync_cli_mod_example_bodies` fills the
```console``` body in each `<!-- wt <cmd> (docs-example) -->`
placeholder in `src/cli/mod.rs` from the registered snapshot. Runs
before `--help-page` reads the file.
- `COMMAND_PLACEHOLDER_PATTERN` extended to match three forms
(```bash```, `{{ terminal() }}` self-closing, `{% terminal %} body {%
end %}`). This **fixes a pre-existing bug** in `docs/content/list.md`
where the HTML-mode expansion was silently broken.
- Stripped trailing `|||` corruption that arises when blank lines in
snapshot bodies are interpreted as empty commands by
`convert_dollar_console_to_terminal`.
- **Docs page migration**:
- `src/cli/mod.rs` — replaced four hand-written ```console``` blocks
(merge, step, remove, hook) with `<!-- wt <cmd> (docs-example) -->`
markers.
- `docs/content/llm-commits.md` — replaced three hand-crafted HTML
blocks with `<!-- ⚠️ AUTO-GENERATED-HTML from X.snap -->` markers.
- **Refactor follow-up** in a separate commit:
- `BADGE_EXPERIMENTAL_HTML`, `SUBDOC_MARKER_PREFIX`,
`DEMO_MARKER_PREFIX` hoisted to `worktrunk::docs` so producer and
consumer stay in lockstep.
- `normalize_clap_help_fences()` consolidates the `text→` +
`console→bash` replacement pair shared by `--help-md` and
`help_reference_inner`.
- **CLAUDE.md note** documenting the `.gitattributes`
`linguist-generated=false` exemption requirement when adding skill-only
files (carryover from #2409).
## Test plan
- [x] `cargo test --test integration readme_sync` — all 13 tests pass,
idempotent
- [x] `cargo test --test integration test_help` — help snapshots updated
- [x] `cargo run -- {merge,remove,step,hook} --help` — clean gutter
formatting, no visible HTML comments
- [x] `cargo run -- hook pre-merge --yes` — full project gate green
(3355 tests)
- [ ] Visual check on dev site (maintainer — terminal shortcodes now
render with colors via ANSI→HTML; dark/light variants both expected to
look like the GIFs they replace)
🤖 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.7 <noreply@anthropic.com>
Co-authored-by: Maximilian Roos <m@maxroos.com>
The section mostly restated content already covered by the "Hook Types"
table and the hook purpose table above it. Removing it and folding the
load-bearing guidance ("prefer `post-start` unless a later step needs
the work completed first") into the existing "most common starting
point" sentence near the top.
> _This was written by Claude Code on behalf of Maximilian_
---------
Co-authored-by: Claude <noreply@anthropic.com>
The Dev servers and Databases sections in `hook.md` duplicated nearly
identical TOML and narration from the corresponding recipes in
`tips-patterns.md`. Replaces them with two bullets under a new "More
recipes" section that link to the canonical recipes, and redirects
inbound links (`README.md`, `worktrunk.md` feature list) from
`/hook/#dev-servers` to `/tips-patterns/#dev-server-per-worktree`.
The bullets use bare URLs in `cli.rs` so `wt hook --help` shows
terminal-auto-linkable `https://...` rather than stripped markdown link
text; `post_process_for_html` rewrites them to inline markdown links for
the web docs. Same pattern as the existing "Open an issue at ..."
transform.
Also tightens the extending.md note on operation-context variables to
"aren't auto-populated" — they can still be bound via `--KEY=VALUE` on
the CLI.
Net −156 lines.
> _This was written by Claude Code on behalf of Maximilian_
Co-authored-by: Claude <noreply@anthropic.com>
`wt deploy` now resolves `deploy` against configured aliases before
falling through to a `wt-deploy` PATH binary. Built-ins still win (clap
matches before alias dispatch ever runs), and `wt step <name>` keeps
working at runtime — only the docs cut over to the new form.
## Why
`wt deploy` reads better than `wt step deploy`, and aliases as
first-class commands lower friction for using them as everyday
shortcuts.
## Precedence
built-in (clap) → alias (user/project config, merged) → `wt-<name>` PATH
binary → "unrecognized subcommand" error.
User config wins over PATH binaries because aliases are how users
customize wt — same model as git, where `[alias]` entries shadow
`git-foo` externals.
## Navigating the diff
- `src/commands/alias.rs` — refactored `step_alias` to share `run_alias`
with the new `try_alias(name, rest) -> Result<Option<()>>`. Returns
`Ok(None)` when the name isn't a configured alias or when not in a git
repo; propagates config-load errors so a broken `wt.toml` fails loudly
instead of silently turning into "unrecognized subcommand". Argument
parsing is gated on alias-membership, so unrelated args meant for an
external binary don't surface as alias parse errors. New
`alias_names_for_suggestions()` mixes alias names into "did you mean"
hints. `HelpContext` enum lets the help splice annotate "(shadowed by
built-in)" against the right level (top-level builtins for `wt --help`,
step builtins for `wt step --help`). The user-facing "shadow warning"
was removed entirely — under the new model an alias named `commit` runs
fine via `wt commit`, only `wt step commit` is shadowed.
- `src/commands/external.rs` — `handle_external_command` calls
`try_alias` first, then PATH lookup, then unrecognized-subcommand error.
Suggestions include alias names. Non-UTF-8 args bypass alias dispatch
(alias parser requires UTF-8; binary subcommands get raw `OsStr`).
- `src/help.rs` + `src/main.rs` — early-parse pass returns
`Option<HelpContext>`; help splice fires for both `wt --help` and `wt
step --help`.
- `src/completion.rs` — aliases injected at the top level in addition to
`step`.
- `src/cli/mod.rs` — long Aliases section moved out of
`Step::after_long_help` into hand-authored `docs/content/extending.md`.
New sync test `test_top_level_builtins_match_clap` keeps the
`TOP_LEVEL_BUILTINS` constant aligned with the `Cli` enum.
## Tests
3221 tests pass, lints clean. New integration tests:
`test_top_level_alias_dispatch`,
`test_top_level_alias_with_step_builtin_name`,
`test_top_level_alias_did_you_mean`. Removed
`test_step_alias_shadows_builtin_plural` (warning gone). Reframed
`test_step_alias_shadows_builtin` to verify shadow filtering of typo
suggestions instead. Completion tests now isolate user config via
`WORKTRUNK_CONFIG_PATH=/dev/null` — project config isolation is a noted
gap (commented inline).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to #2079. Adds cross-links from the landing page and adds the
`up` alias example to the extending page.
- worktrunk.md "Next steps": link to extending.md
- extending.md: add `wt step up` multi-line alias example, remove
redundant TOML file-path comments
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
Both features were hard to find from the docs entry points: the homepage
workflow showcase didn't mention them, and tips-patterns used them
inside larger recipes without dedicated anchors.
**Homepage (\`worktrunk.md\`):** Add an "Aliases & per-branch variables"
bullet to the Workflow automation list, as the final item right before
"lots more".
**Tips & patterns:**
- Add two new dedicated sections near the top: \`wt\` aliases and
Per-branch variables. Both are kept brief — only the recipe-specific
content (composing with \`hash_port\`, parametrizing via \`vars\`,
sticky per-branch environment) — with pointers to the reference docs for
breadth.
- Rename the existing shell-alias section to "Shell alias for new
worktree + agent" to disambiguate from \`wt\` aliases.
- Reorder so the template-variable recipes (dev server, database per
worktree) appear before "Eliminate cold starts", grouping related
content.
No changes to reference docs or CLI source this round — purely homepage
and tips-patterns.
Co-authored-by: Claude <noreply@anthropic.com>
Two small follow-ups to the link audit in #2036:
- `post-start hooks` in the "Parallel agents" paragraph now points at
`#pre-start-vs-post-start` so readers land on the specific subsection.
- `Set up project hooks` in the Next steps list is now `Set up hooks` —
the narrower phrasing was misleading because the link covers both user
and project hooks. Target URL is unchanged.
> _This was written by Claude Code on behalf of Maximilian_
Co-authored-by: Claude <noreply@anthropic.com>
The "Copy build caches" bullet in the feature list under "Workflow
automation" pointed to the whole `wt step` page (covers 10 subcommands)
instead of the specific `copy-ignored` section. Anchor it so readers
land on the relevant docs.
Audited the other anchored links in the same list (Interactive picker,
`wt list --full`, CI status, LLM summaries, PR checkout, Dev server per
worktree) — all resolve to valid anchors in their target pages.
> _This was written by Claude Code on behalf of Maximilian_
Co-authored-by: Claude <noreply@anthropic.com>
Update the README blockquote date from March to April 2026, and add a
README date check to the `running-tend` skill so daily maintenance
catches stale months going forward.
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
Shell code blocks on the docs site had inconsistent syntax highlighting.
Blocks with `$ ` prompt prefixes rendered as a single flat color because
Syntect's bash grammar treats `$` as variable expansion. This PR adds `$
` prompts to all shell commands while preserving full Syntect
highlighting by routing through terminal shortcodes.
## Approach
All shell commands in `console` blocks use `$ ` prefix.
`convert_dollar_console_to_terminal()` (new library function in
`src/docs.rs`) detects `$ ` lines and emits Zola terminal shortcodes:
- **Single or multi-command blocks** (no `{{ }}`): Uses `cmd` parameter
with `|||` delimiter. The shortcode template splits, highlights each
line individually through Syntect, and wraps commands in `<span
class="cmd">` (CSS `::before` adds `$ `). Comment lines (`#`) are
highlighted as comments without a prompt.
- **Blocks with `{{ }}` template syntax**: Falls back to body approach
with `<span class="cmd">` (accent color only, since Tera would interpret
`{{ }}` in the `cmd` parameter).
The function runs in both the `--help-page` generator (CLI source →
docs) and the doc sync test (hand-written docs → terminal shortcodes).
Hand-written docs can use plain `console` fences with `$ ` and get
auto-converted.
## Key files
- `src/docs.rs` — New library module with
`convert_dollar_console_to_terminal()` and unit tests
- `docs/templates/shortcodes/terminal.html` — Template enhanced to loop
over `|||`-delimited commands, highlighting each through Syntect.
Supports self-closing `{{ }}` syntax for bodyless blocks.
- `src/help.rs` — Uses library function, updated pipeline docs
- `tests/integration_tests/readme_sync.rs` — Sync test runs conversion
on all docs (not just CLI-generated). Updated skill transformation to
handle both body and self-closing terminal shortcodes.
- All `src/cli/*.rs` — `$ ` added to all console blocks
- All `docs/content/*.md` — Auto-converted to terminal shortcodes (zero
`bash` blocks remain)
> _This was written by Claude Code on behalf of @max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: worktrunk-bot <w@worktrunk.dev>
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
The quick start `wt list` example was sparse — only staged additions
(+53), no removed lines, no commits ahead of main, and both branches
showing the same "Initial commit" hash. Now feature-auth has a committed
change plus staged WIP, so the output showcases more columns at a
glance:
```
Branch Status HEAD± main↕ Remote⇅ Commit Age Message
@ feature-auth + ↑ +27 -8 ↑1 4bc72dc9 2h Add authentication module
^ main ^⇡ ⇡1 0e631add 1d Initial commit
```
The test creates a two-phase setup: commit auth.rs (1 commit ahead of
main), then stage WIP changes that extend auth.rs and restructure lib.rs
(producing both additions and deletions in HEAD±). The explanatory text
was updated to mention `↑1`.
> _This was written by Claude Code on behalf of @max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
## Summary
Three fixes found during nightly code quality survey:
- **Shell operator precedence in `build_remove_command`**
(`process.rs`): The `|| true` for fsmonitor stop had incorrect
precedence — it could swallow failures from the entire `sleep 1 && git
...` chain. Wrapped in `{ ...; }` brace group to scope correctly.
- **Missing shell escaping in `WorktreePathOccupied` hint**
(`error.rs`): Branch name and path in the suggested `cd ... && git
switch ...` command were not shell-escaped, unlike other error hints
that use `shell_escape::escape()`.
- **README license badge drift**: Badge said "MIT" but `Cargo.toml`
declares "MIT OR Apache-2.0".
Also opened issues for items that need more design consideration:
- #1579 — `wt step push` missing `--no-ff` flag
- #1580 — `get_config()` swallows all git errors
- #1582 — `get_*` naming convention violations
## Test plan
- [x] Unit tests pass (`test_build_remove_command`,
`snapshot_worktree_path_occupied`)
- [x] `cargo fmt` and `cargo clippy` 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>
## 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>
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>
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>
## Summary
- Add interactive picker and copy build caches to home page "Workflow
automation" section
- Update omnibus demo to showcase the picker (TAB 4 uses `wt switch`
with no args, filters to "au", selects auth)
- Fix demo infrastructure: `CLAUDECODE=""` for nested sessions, model →
Opus 4.6, approvals.toml migration, comprehensive tip suppression
## Test plan
- [x] Demo builds and validates (`./docs/demos/build docs --only
wt-zellij-omnibus`)
- [x] Assets published to worktrunk-assets repo
- [x] Doc sync test passes
- [ ] CI green
> _This was written by Claude Code on behalf of @max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
The quick start section claimed `↕` means unpushed commits and `+` means
uncommitted changes. Both were wrong: `↕` means diverged from default
branch (the symbol for unpushed commits is `⇡`), and `+` means staged
changes specifically (modified is `!`, untracked is `?`).
Co-authored-by: Claude <noreply@anthropic.com>
The two sections had inconsistent ordering — "Core commands" had the
blockquote first, while "Workflow automation" had the heading first.
Swap the second to match.
Co-authored-by: Claude <noreply@anthropic.com>
* feat(switch): integrate interactive picker into `wt switch` (#890)
`wt switch` without arguments now opens the interactive picker (previously
`wt select`). This simplifies the mental model: one command for all switching.
- `wt switch` → opens interactive picker (Unix) or shows error (Windows)
- `wt switch --branches/--remotes` → customizes picker
- `wt select` → deprecated hidden alias with warning
Removes the separate `wt select` docs page since the functionality is now
documented under `wt switch`. The `[select]` config section is preserved
for backward compatibility with a TODO to rename once migration is confirmed.
Closes#890
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(test): make switch TTY test Unix-only
The test expects the Unix error message ("Interactive picker requires
an interactive terminal") but Windows shows a different message
("Interactive picker is not available on Windows").
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: trigger CI
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(switch): require branch when using --create, --base, --execute, or --clobber
These flags only make sense when switching to a specific branch, not when
opening the interactive picker. Previously, running `wt switch --create`
would silently ignore the flag and open the picker instead.
Now clap properly enforces that these flags require a branch argument:
$ wt switch --create
error: the following required arguments were not provided:
<BRANCH>
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(test): update snapshot for interactive picker error message
The snapshot was stale after integrating `wt select` into `wt switch`.
Now `wt switch` without args shows the picker error, not clap's missing
argument message.
Co-Authored-By: Claude <noreply@anthropic.com>
* docs(switch): move interactive picker section after shortcuts
The picker is a navigation method (like shortcuts), not an afterthought.
New order: Shortcuts → Interactive picker → GitHub/GitLab PRs → Troubleshooting
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(test): update help snapshot after doc reorg
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* docs: use tool-agnostic terminology for LLM commit messages
Stop implying `llm` binary is the default/primary tool for commit
message generation. The feature now supports Claude Code, Codex,
llm, and aichat equally.
- Remove `via [llm](...)` links from feature descriptions
- Use Claude Code as the example in config docs (first in setup list)
- Generalize troubleshooting to cover all supported tools
- Link to llm-commits.md as single source of truth for commands
- Rename sections to "Commit Message Generation" for clarity
Co-Authored-By: Claude <noreply@anthropic.com>
* test: update help snapshot for Claude Code config example
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* docs: add Quick Start section to front page
Shows the basic workflow with clear directory paths at each stage:
- Create worktree with wt switch --create
- Check status with wt list
- Two merge paths: PR workflow (push + gh pr create + wt remove)
and local merge (wt merge)
- Parallel agents example with -x flag
Co-Authored-By: Claude <noreply@anthropic.com>
* docs: improve Quick Start with realistic examples and snapshot sync
- Add snapshot-based output sync for quickstart examples (switch, list, merge)
- Make examples more realistic: 53 lines of auth module changes instead of 9
- Show uncommitted changes in wt list demo (WIP state, not already committed)
- Add wt step commit to PR workflow since changes are now uncommitted
- Remove redundant git push from PR workflow (gh pr create auto-pushes)
- Suppress worktree-path hint in quickstart tests for cleaner output
- Add transform_zola_to_github() for converting HTML terminal markers to plain code blocks
Co-Authored-By: Claude <noreply@anthropic.com>
* Simplify code block formatting in README and tests
Convert bash code blocks to console format with command prompts and output
in a single block. Update regex pattern to optionally strip redundant bash
preamble when converting AUTO-GENERATED-HTML terminal markers.
* docs: remove redundant bash blocks before terminal examples
The terminal shortcodes already include the command with $ prefix,
so separate bash blocks showing just the command were redundant.
Also fix broken anchor link in config.md.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(docs): make terminal prompt non-copyable via CSS
Use CSS ::before pseudo-element to generate the $ prompt, making it
structurally non-copyable. This works with both manual text selection
and copy buttons.
Changes:
- Add .cmd::before { content: "$ "; } to generate prompt via CSS
- Remove explicit <span class="prompt">$</span> from HTML output
- Extract command from snapshot YAML header instead of parsing HTML
- Update expand_command_placeholders for command pages (list.md, etc.)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(docs): show commit step in quick start merge example
The merge example now shows staged changes being committed as part of
the wt merge workflow, reflecting a realistic user experience where
code is staged but not yet committed before merging.
Co-Authored-By: Claude <noreply@anthropic.com>
* style: fix cargo fmt formatting
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(tests): use cross-platform mock LLM for quickstart_merge test
The quickstart_merge test was using a shell script for the mock LLM
which doesn't work on Windows. Now uses the mock-stub system which
creates a cross-platform mock binary.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(test): use to_slash_lossy for Windows path compatibility
Windows paths with backslashes trigger shell metacharacter handling,
which wraps the command in `sh -c`. Bash can't parse Windows paths
like `C:\Users\...`. Converting to forward slashes with
to_slash_lossy() makes the path bash-compatible.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* chore: update brew install to use core tap
Signed-off-by: Rui Chen <rui@chenrui.dev>
* docs: restore shell install step in brew instructions
The previous commit removed `&& wt config shell install` from the
Homebrew install command. This restores it so users get shell
integration set up automatically.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Signed-off-by: Rui Chen <rui@chenrui.dev>
Co-authored-by: Maximilian Roos <m@maxroos.com>
Co-authored-by: Claude <noreply@anthropic.com>
* Add Arch Linux install via AUR
* Alias PKGBUILD source to include version
* Add Maintained at line
* Add depends to PKGBUILD
* docs: move Arch Linux install below Windows
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Evan Sosenko <razorx@evansosenko.com>
Co-authored-by: Claude <noreply@anthropic.com>
Add expandable Windows section to Install with:
- Winget as recommended install (ships git-wt by default)
- Alternative: disable Windows Terminal's wt alias
Remove redundant FAQ entry about the wt conflict since Install
now covers it. The "Does Worktrunk work on Windows?" FAQ remains
for feature support details.
Closes#133
Co-authored-by: Claude <noreply@anthropic.com>
* Redesign social cards with horizontal layout
- New horizontal layout: logo left, text right
- Warm radial glow emanating from logo
- Refined typography: 88px title with tight tracking, 28px tagline
- Darker title color (#1a1817) for better hierarchy
- Move generated PNGs to assets repo (same pattern as demos)
- Add build script with automatic font downloading
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix deploy workflow to include social cards
The assets repo now has both demos/ and social/ directories.
Co-Authored-By: Claude <noreply@anthropic.com>
* Simplify assets structure: single path copy
Restructured worktrunk-assets repo to have all content under assets/
so deploy workflow can use a single `cp -r` command.
Co-Authored-By: Claude <noreply@anthropic.com>
* Address review feedback
- Fix typo: "ONGs" → "PNGs"
- Fix stale comment: "Google Fonts" → "GitHub releases"
- Add -f flag to curl for better error handling
- Document purpose of each social card size
Co-Authored-By: Claude <noreply@anthropic.com>
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Fix asset URL in README for restructured repo
The worktrunk-assets repo was restructured from demos/ to assets/.
Update the URL transform in readme_sync.rs to match.
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix README demo links after assets repo restructure
- Update wt-core.gif URL from demos/ to assets/ path
- Add PNGs exception to typos config (was incorrectly flagged)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>