Commit Graph

91 Commits

Author SHA1 Message Date
Maximilian Roos e1745db105 feat(list): abbreviate the table's SHA with git, not a fixed slice (#3676)
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_
2026-07-30 18:33:30 -07:00
Maximilian Roos 568b6de85f docs: consolidate duplicated explanations and trim slop (#3494)
Sweep of the docs for repetition and filler, from an audit of the
hand-authored pages, the command pages' source in `src/cli/mod.rs`, and
the plugin skill. Net −574 lines; every cut either had a surviving
canonical home or restated an adjacent sentence.

**One home per mechanism** (other mentions now link to it):

- `template-append` — the LLM commits guide owns the rendering
mechanism; the user- and project-config sections keep the key, an
example, and what is unique to them (the project approval gate and the
only-`template-append`-from-project scoping). Was explained in full in
three places.
- `-vv` diagnostic files — `wt config state logs` owns the four file
descriptions; the FAQ summarizes in one sentence and links.
- fsmonitor/trash cleanup — the FAQ's two overlapping `wt remove`
bullets merge into one that distinguishes own-daemon teardown from the
orphan sweep; troubleshooting.md's restatement compresses to a pointer,
keeping its unique wedged-daemon-on-live-worktree guidance.
- copy-ignored built-in excludes — the `wt step copy-ignored` page owns
the directory list (previously enumerated 4×); the config sections state
the rule and link.
- hook-types table — `wt hook` owns it; the extending guide replaces its
verbatim copy with a sentence.
- LLM tool commands — unchanged, deliberately: the apparent hand-synced
duplication between llm-commits.md and the config example is already
machine-pinned by `test_llm_docs_commands_match_config_example`, so it
cannot drift.

**Cut-over debt**: the deprecated `wt config state ci-status` section
shrinks to a deprecation pointer — its status table, fetch order, and
caching notes all duplicated the `wt list` CI-status section.

**Structure**: `wt step copy-ignored`'s "Features" list dissolves into
the sections that owned its facts (excludes → "What gets copied",
reflink → "Performance"); the four trailing "Note: This command is
experimental…" lines go (the `[experimental]` badge already appears
twice per section); shell-integration.md described the directive-file
mechanism twice and now describes it once.

**SKILL.md** drops from 339 to 181 lines: the permission-model section
duplicated the config-types bullets, the hook-type mapping appeared
twice, and the "Determining Which Config to Use" / "Validation Before
Adding Commands" scaffolding enumerated judgments an agent makes on its
own. The approvals-escalation and agent-handoff sections are untouched.

**Deliberately not addressed**: `wt list`'s schema 1 JSON tables (still
the default schema; the wholesale deletion lands when the default flips)
and corpus-wide em-dash density (a house-style decision, not a per-page
fix).

Generated mirrors (docs command pages, skill references, plugins mirror,
`dev/*.example.toml`, help snapshots) regenerated via
`test_docs_are_in_sync` and `cargo insta`.

> _This was written by Claude Code on behalf of max_

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 13:44:44 -07:00
Maximilian Roos 830cc850dd feat(list): show the main…± column by default; --full gates only off-machine columns (#3236)
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.
2026-06-25 01:49:47 -07:00
Worktrunk Bot 044ed66d56 docs: add "Per-worktree env vars" tips section (direnv/mise) (#3074) 2026-06-16 02:34:45 -07:00
Maximilian Roos 371d286628 refactor(hooks): always-lazy template expansion for hooks and aliases (#3042)
Hook templates expanded eagerly at prep time unless they referenced
`vars.*` (#1840); aliases were unconditionally lazy. The dual
representation (`expanded` + `lazy_template` on `PreparedCommand`) put a
mode choice in front of every consumer, and the seams produced real bugs
(#1855, #1910). The background runner also re-rendered eagerly-expanded
strings: a latent double-expansion whenever expansion output contained
`{{`.

This PR unifies on one rule: syntax is validated at preparation (before
step 1), templates render when each step runs, and `vars.*` always reads
fresh from git config. This is the direction #2128 recorded ("treat
hooks as aliases bound to triggers").

**Navigating the diff:**

- `src/commands/command_executor.rs`: `PreparedCommand` now carries the
raw template plus the frozen context map; JSON is produced only at the
process boundary. A `template_name` field keeps the moved errors' text
identical to the old prep-time messages. `prepare_steps` validates
syntax and freezes context; `resolve_command_str` renders at execution;
`map_config_steps` is the structural walker shared with aliases.
- `src/commands/alias.rs`: alias prep shares the walker. The residual
fork is genuinely alias-specific: context construction (filtered to
referenced vars, `{{ args }}` injection) and labels. A prep-time syntax
check turned out to be dead code (the arg-routing parse already aborts
on syntax errors), so `referenced_vars_for_config` now produces the rich
`TemplateExpandError` instead.
- `src/commands/hooks.rs`: the background spec always ships raw
templates; the runner renders each step (it already did for vars steps).
- Dry-run paths (`wt hook --dry-run`, `wt config alias dry-run`) share
`render_template_preview`: vars-referencing templates shown raw after a
syntax check, everything else rendered at display time.

**Behavior changes** (each pinned by a test or snapshot):

1. A foreground pipeline whose step N has a semantic template error
(undefined variable) runs steps 1..N-1 before failing, matching what
vars-referencing steps already did. New test:
`test_foreground_pipeline_undefined_var_runs_earlier_steps`.
2. A semantic error in a background hook template no longer fails the
foreground command; it surfaces in the runner log. Syntax errors still
abort at prep. New test:
`test_background_hook_undefined_var_fails_in_runner`;
`test_merge_drops_pending_hooks_when_post_merge_fails` switched to a
syntax-error fixture since that is what now reaches its subject (the
announcer Drop-flush).
3. `-v` no longer prints the parent-side rendered command for background
hooks (rendering happens in the runner). The variables table remains and
`wt hook <type> --dry-run` previews commands; help text updated. Three
snapshots changed by exactly this.
4. Announce gutters for vars-referencing foreground steps show the
rendered command instead of the raw template.
5. Syntax errors caught by the arg-routing parse (aliases, `wt hook`
`--KEY=VALUE` routing) render as the rich template error with the
source-line gutter, naming the alias or hook.

`ApprovedHookPlan` freezing, approval semantics (keyed and displayed by
template), config file format, and CLI flags are unchanged.

**Testing:** full local gate green (3917 tests + lints). The deferral
semantics in changes 1 and 2 previously had no coverage; both are now
pinned.

> _This was written by Claude Code on behalf of max_

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 15:50:28 -07:00
Maximilian Roos 5da0d2c3e4 docs: alias template-expansion timing; fix narrow help-test env redaction (#3006)
## Docs: alias template-expansion timing

The main addition documents a gotcha that's easy to hit and hard to
diagnose: an alias body renders its `{{ … }}` once at dispatch, in the
invoking worktree, so a per-worktree variable like `{{ branch }}` is
baked to a single value *before* a nested `wt step for-each` / `wt
switch --execute` iterates — printing the same value in every worktree.
The fix is `{% raw %}…{% endraw %}` deferral (plus a quoted `sh -c '…'`
for `for-each`, since the deferred `{{ branch }}` contains spaces).

- `extending.md` — rewrote "Deferring expansion to a nested `wt`
command" around the `for-each` symptom; improved the `up` rebase recipe.
- `faq.md`, `troubleshooting.md` — symptom-first entries with `wt config
alias dry-run` as the diagnostic.
- `hook.md` / `config.md` / `step.md` — distinguish repo-level
(constant) vs per-worktree (active) variables; note `{{ default_branch
}}` needs no deferral; cross-link the `{{ default_branch }}` variable vs
the `wt config state default-branch` shell command.

## Factual corrections

- `integration_reason` JSON values are hyphenated (`trees-match`,
`no-added-changes`, `merge-adds-nothing`) — the docs had underscores.
Verified against `src/commands/list/model/state.rs`.
- `SKILL.md`: 7 → 10 hook types (5 events × pre/post), added an
aliases/multi-worktree task section, fixed stale anchor links.

## Test fix: narrow help-test env redaction

`test_help_list_narrow_terminal` built its own `insta::Settings` but
skipped `add_standard_env_redactions` (every other help snapshot routes
through `snapshot_help`, which calls it). Its snapshot env block
therefore leaked host-specific paths (`LLVM_PROFILE_FILE` = the
machine's temp dir, plus the `WORKTRUNK_*` paths), which churn whenever
the snapshot is regenerated on a different machine. Adding the one call
mirrors `snapshot_help` and makes the snapshot reproducible.

Worth noting (and a candidate follow-up): this gap was masked under
`cargo test` (libtest) because the `repo` fixture's `mem::forget`'d
`bind_to_scope()` guard leaks redaction settings across the shared
process's reused threads. Under nextest (process-per-test, what the
pre-merge hook uses) there's no leak, so a test missing its own
redactions is exposed. A few other help tests (`test_help_md`,
`test_version`, `test_nested_subcommand_suggestion`) have the same gap
and could be consolidated through one settings helper — left out of this
PR to keep it focused.

> _This was written by Claude Code on behalf of max_

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 00:00:52 -07:00
Adriano Machado ca91420e1e fix(bare-repo): UX fixes for worktree-path prompt and wt config create --project in bare repos (#2951) 2026-06-02 00:19:15 -07:00
Maximilian Roos 3be40ad36f docs: writing-prose cleanup (claude-code, hook, llm-commits, tips-patterns) (#2922)
Targeted writing-prose cleanup across four docs pages. Per-page summary:

**`claude-code.md`** — dropped the duplicate skill definition (the lead
paragraph already covered it) and rewrote the `## Configuration skill`
opener as "With the `/worktrunk` skill, the agent can help with:" so the
section stands without depending on its heading.

**`hook.md`** (edits in the `Hook` command's `after_long_help` in
`src/cli/mod.rs`) — four small fixes:
- Dropped "As usual, post-* hooks run in the background" (already
established earlier on the page).
- Split the perspective+cwd run-on paragraph; the three `cwd ≠
worktree_path` cases (`pre-switch`, `post-remove`, `post-merge` with
removal) are now a bulleted list.
- Folded the semicolon-spliced conditional-variables enumeration into
the existing template-variables table descriptions (added "switch/create
only" to `base`, "when target has a worktree" to `target_worktree_path`,
hook-type qualifiers to `pr_number`/`pr_url`). The follow-on guidance
about undefined variables and conditionals/defaults stays.
- Trimmed the redundant `sanitize` sentence from the filters paragraph
(the table above already says it).

The two hook-types tables (event×pre/post matrix + per-hook purpose) are
intentionally kept — they're complementary, not duplicate.

**`llm-commits.md`** — dropped the "How it works" stub that mostly
restated the lead, and the "There are sensible defaults, but templates
are fully customizable" hedge.

**`tips-patterns.md`** — three trims:
- The `wt step tether` recipe's middle "This matters because…" sentence
(covered by tether's own docs).
- The ports-deterministic line tightened to lean on the concrete example
rather than restate the abstract claim.
- "in real-time" filler dropped from the Monitor hook logs section.

Auto-synced skill mirrors (`skills/worktrunk/reference/*.md`) carry the
same edits.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 20:49:54 -07:00
Worktrunk Bot ec62580c29 revert(hooks): keep docs on pre-start/post-start; code accepts both (#2857)
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>
2026-05-21 16:03:03 +00:00
Maximilian Roos ff4e481a4c fix(switch): interactive picker runs the full switch pipeline (#2845)
With `[switch] cd = false` or `wt switch --no-cd`, opening the
interactive picker (`wt switch` with no branch argument) and selecting a
worktree printed the branch name and exited — no switch, no hooks, and
`Alt-c` created nothing. #1445 introduced this as an explicit print-only
mode, but it surprises anyone who sets `cd = false` as a standing
navigation preference (driving worktree switches through tmux or cmux):
the picker silently stops doing anything.

The picker now runs the same `plan_switch` → `execute_switch` pipeline
as `wt switch <branch>`, suppressing only the cd directive. Pre- and
post-switch hooks fire, `Alt-c` creates the worktree, and
`--format=json` works in the picker too (`requires = "branch"` and the
`--branches`/`--remotes` conflict are dropped from the flag). JSON
emission is extracted into a shared `emit_switch_json` so the argument
path and the picker produce identical output.

This is a behavior change: the print-only picker mode is gone. Scripts
that captured its bare-branch-name output should switch to `wt switch
--format=json`, which both performs the switch and prints a structured
`action`/`branch`/`path` result — now shown as a `pick` alias example in
tips-patterns. The cmux recipe drops its `pick` alias workaround, since
the picker fires `pre-switch` hooks directly.

Closes #2837. Thanks to @endigma for the discussion that pinned down the
intended behavior: the picker should be identical to passing the branch
as an argument.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 21:05:19 -07:00
Maximilian Roos d7e3f88422 feat(hooks): rename worktree-creation hooks to pre-create/post-create (#2840)
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)
2026-05-20 19:31:50 -07:00
Worktrunk Bot af8de75341 docs: re-add cmux recipe with verified config from @endigma (#2836) 2026-05-20 07:53:17 -07:00
Maximilian Roos 408f4f5bee Add wt step tether: kill a command's process group when its worktree is removed
`wt step tether -- CMD…` runs CMD in its own process group and tears the whole
group down when CMD exits or its worktree is removed (a 250ms portable poll;
killpg on Unix, taskkill /T /F on Windows). Replaces the leaked-dev-server /
fseventsd-saturation failure mode with a fire-and-forget supervisor needing
only a single post-start hook. No unsafe, no new deps. Shell handling matches
`wt step for-each`. Windows taskkill has a documented self-exit-detach edge.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 11:57:16 -07:00
Worktrunk Bot 2ca0113a04 docs: remove unverified cmux recipes from tips-patterns (#2797)
## Problem

The cmux recipes in `docs/content/tips-patterns.md` don't work as
documented — see #2796. `wt switch` cd's the invoking shell *in addition
to* the `pre-switch` hook selecting the per-worktree cmux workspace, so
the original cmux workspace ends up pointing at the new worktree
alongside the freshly-opened one. The Agent-handoffs cmux entry has a
similar pollution issue.

I initially proposed adding `[switch] cd = false` (prior revision of
this PR), but I couldn't end-to-end verify the fix because cmux isn't
installable in this CI environment. Per @max-sixty's call ([#2796
(comment)](https://github.com/max-sixty/worktrunk/issues/2796#issuecomment-4478578957))
— *"let's just remove the cmux recipes unless someone can certify that
they work"* — the safer move is to drop the recipes until someone with
cmux installed can re-add a verified configuration.

## What this PR does

Removes both cmux blocks from `docs/content/tips-patterns.md` (and the
auto-synced skill reference):

- The `**cmux** (new workspace):` entry under **Agent handoffs**.
- The full `## cmux workspace per worktree` section.

Diff is the inverse of #1907 (the PR that added them): 33 + 36 = 69
deletions.

The tmux and Zellij Agent-handoffs examples are unaffected.

## Testing

- `cargo test --test integration test_docs_are_in_sync` ✓ (regenerated
`skills/worktrunk/reference/tips-patterns.md`)
- `git grep -ni cmux` returns no matches.

---
Closes #2796

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 11:08:02 -07:00
alvistar 404bd563a5 docs: add cmux workspace integration recipe (#1907)
## Summary

- Adds cmux to the **Agent handoffs** section alongside tmux and Zellij
- Adds a dedicated **cmux workspace per worktree** recipe with
create/select/close lifecycle hooks
- Documents the key gotcha: cmux socket restricts access to processes
with cmux terminal ancestry, so `pre-*` hooks must be used instead of
`post-*`

## Context

[cmux](https://cmux.com) is a macOS terminal built on libghostty with
workspace management via a Unix socket API. This recipe wires up
worktrunk hooks so each worktree automatically gets its own cmux
workspace.

The `pre-*` vs `post-*` distinction was discovered through debugging:
`post-*` hooks are detached background processes that lose the cmux
process ancestry, causing the socket to reject connections with "Access
denied — only processes started inside cmux can connect."

## Test plan

- [x] Tested full lifecycle: `wt switch -c`, `wt switch`, `wt remove` —
all correctly create, select, and close cmux workspaces
- [ ] Verify docs render correctly on worktrunk.dev (Zola build)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: worktrunk-bot <worktrunk-bot@users.noreply.github.com>
2026-05-06 10:54:51 -07:00
Worktrunk Bot 735756a46a fix(docs): use __WT_QUOT__ placeholder in tips-patterns terminal blocks (#2496) 2026-04-30 01:25:59 -07:00
Maximilian Roos 8abefbce82 refactor(docs): unify AUTO-GENERATED marker form; share constants across crate boundary (#2418)
## Summary

Follow-ups from #2417 review, plus adjacent simplifications.

- **Cross-crate share of marker constants.** `MARKER_OPEN_PREFIX` and
`MARKER_CLOSE` now live in `src/docs.rs`. `src/help.rs` (the
`--help-page` producer of help-region markers) and
`tests/integration_tests/readme_sync.rs` (the consumer + producer of
snapshot/section markers) both reference them — drift becomes a build
error rather than a silent mismatch.
- **Drop `MARKER_OPEN_HTML_PREFIX`.** The `-HTML` variant emitted
visually identical wrapping; the suffix only signalled "in-place
refreshable" but the `.snap` ID + terminal-shortcode body in
`DOCS_SNAPSHOT_MARKER_PATTERN` already discriminate that. Standardised
on a single open prefix and stripped the `-HTML` literals from
`README.md`, the four standalone docs files, and the test file.
- **Help-page marker uses the shared prefix.** The mirrored close (`<!--
END AUTO-GENERATED from \`wt <cmd> --help-page\` -->`) stays as-is
because adjacent regions need unambiguous pairing, but the open prefix
now goes through `MARKER_OPEN_PREFIX`.
- **Drop `MarkerType::output_format()` / `extract_inner()`.** Both had a
single trivial non-panic branch that existed only to enforce "no
Snapshot markers in README" via `unreachable!()`. Replaced with one
explicit assertion in `sync_readme_markers` — surfaces the invariant as
an actionable error message instead of a panic.
- **Collapse `format_replacement`.** `wrap_in_marker` is invoked once
for both output formats; only body construction varies.
- **`__WT_QUOT__` rename in `tests/integration_tests/user_hooks.rs`.**
Inlined the single quotes — the `.replace('__WT_QUOT__', \"'\")` was
unnecessary indirection (the outer raw-string literal already tolerates
`'`, and TOML / Tera don't care about embedded `'`). Removes a
name-conflation footgun with the unrelated `__WT_QUOT__` placeholder in
`src/docs.rs`.

Net diff: -7 lines.

## Test plan

- [x] `cargo test --test integration` — full integration suite (1559
tests) pass
- [x] `cargo test --test integration readme_sync` — all 13 sync tests
pass; `test_readme_examples_are_in_sync` now produces a stable README on
a clean run (verified by re-running multiple times — no further updates
after the initial regeneration)
- [x] `cargo test --test integration
test_args_indexing_and_length_in_hook_template` — confirms the
user_hooks single-quote inlining works through TOML and Tera
- [x] Visual check via local Zola dev server: \`/worktrunk/\`,
\`/llm-commits/\`, \`/claude-code/\`, \`/tips-patterns/\`, \`/merge/\`,
\`/step/\`, \`/remove/\`, \`/hook/\`, \`/list/\` all render terminal
blocks cleanly with no leaked marker strings (`AUTO-GENERATED-HTML`,
`__WT_OPEN2__`, trailing `|||`)
- [x] `cargo clippy --all-targets --all-features` — clean
- [x] `cargo fmt --check` — clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-25 16:57:39 -07:00
Maximilian Roos 18825c3e3c docs(hook): move progressive-validation and target-specific examples to tips-patterns (#2329)
Keeps "Copying untracked files" inline (the one example that materially
uses worktree-specific mechanics) and moves the two plain-TOML patterns
— progressive validation and target-specific `post-merge` — to
`tips-patterns.md`. `hook.md` references them as `More recipes` bullets
instead.

> _This was written by Claude Code on behalf of Maximilian._

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-20 00:21:16 -07:00
Maximilian Roos 064f8940c0 docs(hook): consolidate copy-ignored recipe and trim redundant examples (#2323)
Docs consolidation sweep, same pattern as #2319.

**Copy-ignored recipe** — collapses hook.md's "Copying untracked files"
overlap with tips-patterns.md into a "More recipes" bullet pointing at
the canonical recipe. Keeps a one-paragraph intro in hook.md (worktrees
don't share untracked files → use `wt step copy-ignored`) since it's a
fundamental worktree concept. Moves the concrete pnpm pipeline example
into tips-patterns.md's canonical recipe, and switches it from
`[[pre-start]]` to `[[post-start]]` — the pipeline form handles ordering
without blocking worktree creation. `pre-start` is now called out only
as the exception for when `--execute` needs the files immediately.

**Trims**
- Dropped hook.md's "Hook type examples" dump: most of its 10 entries
duplicate existing recipes (dev server, database, cold starts) or the
Progressive validation section.
- Dropped tips-patterns.md's "Local CI gate": four-line recipe that
duplicated hook.md's Progressive validation and merge.md's Local CI
narrative.
- Dropped hook.md's "Python virtual environments" snippet: step.md
already owns language-specific notes.

> _This was written by Claude Code on behalf of Maximilian Roos_

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-19 23:47:12 -07:00
Maximilian Roos 5b6a674fe5 docs(tips-patterns): trim two redundant sentences (#2272)
Two more filler sentences caught on a second pass:

- Drop "Summaries are cached and regenerated only when the diff
changes." — duplicates the authoritative sentence in `llm-commits.md`,
which the adjacent "See LLM Commits for details" link already points to.
- Drop "`pre-remove` stops all services when the worktree is removed." —
visible from the `[pre-remove]` block above, and "all services"
overstates what the one-line `kill-session` command does.

Follows #2271.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:48:56 -07:00
Maximilian Roos 07db111340 docs: trim filler sentences from prose docs (#2271)
Remove sentences that restate obvious fallback/error behavior, duplicate
nearby prose, or add visual weight without information.

- `extending.md` — drop the "not a wt command" error note, the
experimental badge on Aliases, and a duplicate recap of the
`cd`-to-parent-shell behavior at the end of a recipe.
- `faq.md` — drop "The result is cached for fast subsequent lookups"
padding after the default-branch detection explanation.
- `tips-patterns.md` — drop "Creates a worktree that builds on the
current branch's changes" (restated by the section heading) and
"Sessions are named after the branch for easy identification" (visible
in the code).

Skill reference files auto-synced via
`test_command_pages_and_skill_files_are_in_sync`.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 23:34:48 -07:00
Maximilian Roos a1be5645c4 feat(alias): dispatch aliases from top-level wt <name> (#2266)
`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>
2026-04-16 14:56:03 -07:00
Maximilian Roos d4c80ca970 docs(hook): use concurrent form in multi-key hook examples (#2248)
The `wt hook` page and tips/merge examples paired two `[[pre-*]]` blocks
with one command each, which runs them serially. For independent
commands like `cargo fmt --check` + `cargo clippy`, that taught the
wrong lesson.

Collapsed each pair into a single `[[pre-*]]` block with both keys (runs
concurrently). The multi-entry `[pre-*]` table form is deprecated
(`src/config/deprecation.rs`), so the single-element array form is the
non-deprecated way to express concurrent commands.

Affected: `src/cli/mod.rs` (merge local-CI, hook progressive-validation,
hook-type-examples), `docs/content/tips-patterns.md` (local CI gate).
Generated docs, skill mirrors, and help snapshots synced.

> _This was written by Claude Code on behalf of @max-sixty_

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-15 16:22:34 -07:00
Maximilian Roos 7e12435b33 docs(hook): teach pipelines as [[hook]] blocks; add TOML notes to config::commands (#2149)
Cut over hook pipeline docs from inline-list form (`hook = [{name =
"..."}, ...]`) to `[[hook]]` array-of-tables blocks. The deserializer
already accepts this shape — no code changes were needed; the change is
purely teaching. This mirrors what landed for aliases in #2144.

The summary in `Pipeline Ordering` now foregrounds the TOML shape
progression instead of a labeled taxonomy:

- `post-start = "npm install"` — one command
- `[post-start]` — one section of concurrent commands
- `[[post-start]]` — one of multiple sections, run in order

The bracket count itself tracks the structural escalation (value →
section → array-of-sections).

Also adds a module-level doccomment to `src/config/commands.rs`
describing the three primitive TOML shapes the hook deserializer accepts
(string / dict / list) and the asymmetry between dict-at-top (always
`Concurrent`) vs dict-in-list (1-entry → `Single(named)`). This is the
analysis that motivated the cutover and is retained so future readers
can orient on the deserialization rules without re-deriving them.

The Databases example in `hook.md` and `tips-patterns.md` got its first
pipeline step named (`set-vars`) since `[[post-start]]` blocks can't
hold anonymous bare strings the way inline lists could.

> _This was written by Claude Code on behalf of Maximilian_

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-12 18:12:33 -07:00
Maximilian Roos 37fb27bc97 Deprecate table form for pre-* hooks (#2135)
## Summary

Multi-entry table form for pre-* hooks currently runs serially, while
the same form for post-* hooks runs concurrently. The parser produces
`HookStep::Concurrent` either way, but the foreground executor flattens
steps and runs them serially. To unify the semantics — table form will
run concurrently for all hook types in a future version — this
deprecates the current form for pre-* hooks and auto-migrates it to
pipeline form, which is explicitly serial.

## Implementation

Follows the existing deprecation recipe in `src/config/deprecation.rs`:
- **Detection** and **migration** for top-level hooks (user/project
config) and per-project overrides (`[projects."id".pre-*]`).
- **Warning**: matches the terse `{old} → {new}` pattern of existing
deprecations (`[merge] no-ff → ff`, `post-create → pre-start`, etc.).
- **Auto-migration** at load time: table form rewrites to pipeline of
inline tables so current behavior (serial) is preserved until users run
`wt config update`.

## Docs

Replaces the transitional "concurrent for post-*, sequential for pre-*"
framing with a neutral three-form description (string / table /
pipeline), plus a note recommending pipeline form for pre-* hooks to
avoid the upcoming behavior change.

## Tests

- `snapshot_migrate_pre_hook_table_form` — TOML migration diff
- `test_config_show_displays_pre_hook_table_form_deprecation` — full
user-facing `wt config show` output, covering the "Project config" label
and multi-hook list form
- Unit tests for detection/migration of top-level and per-project
variants
- Existing integration test fixtures migrated to canonical pipeline form

> _This was written by Claude Code on behalf of max-sixty_

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-12 15:55:28 -07:00
Worktrunk Bot c0059a533d docs(skill): support OpenCode in agent handoffs section (#2108) 2026-04-12 01:05:51 +00:00
Maximilian Roos e2fff694a5 docs: add Extending Worktrunk page (#2079)
New Reference page covering the three extension mechanisms — hooks,
aliases, and external subcommands — with a comparison table and enough
detail to orient readers before linking to the full references
(`hook.md`, `step.md#aliases`).

Moves the external subcommands section out of tips-patterns.md into the
new page as its canonical location. The aliases recipe in tips-patterns
stays (it's a specific recipe, not a duplicate).

> _This was written by Claude Code on behalf of @max-sixty_

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-11 12:35:40 -07:00
Worktrunk Bot 5709eb50d4 Add git-style external subcommand dispatch (wt-<name>) (#2054)
## Summary

- `wt foo` now runs `wt-foo` from PATH when `foo` is not a built-in,
mirroring `git foo` → `git-foo`. Third-party tools like a hypothetical
`wt-sync` can be installed and invoked as `wt sync` without touching
this repo.
- Built-ins always take precedence (clap only dispatches `External` when
no built-in matched), so external binaries cannot shadow existing
subcommands.
- Nested subcommand hints still pre-empt the PATH lookup — `wt squash`
continues to suggest `wt step squash` rather than searching for
`wt-squash`.
- When nothing matches, wt prints a git-style `'foo' is not a wt
command` error with a Levenshtein-based typo suggestion (replacing the
old clap `InvalidSubcommand` handler).
- The global `-C <path>` flag is forwarded as the child's working
directory, matching git's semantics.
- Child exit codes (including Unix signal codes) are propagated
verbatim. wt does **not** decorate child failures with its own error
line — the child has already reported whatever it needed to.

Requested in #2053 — the original PR added `wt sync` as a built-in, but
the preferred approach is a generic extensibility mechanism so `wt sync`
can call any `wt-sync` binary on PATH.

## Implementation

- `Commands::External(Vec<OsString>)` captured via clap's
`#[command(external_subcommand)]`.
- `src/commands/external.rs` owns the dispatch: nested-suggestion check
first, then `which::which("wt-<name>")`, then run with
`Command::status()` inheriting stdio.
- `main()` dispatches `External` directly (instead of via
`dispatch_command`) so the parsed `-C <path>` can be forwarded as the
child's cwd.
- The nested-suggestion path moved from clap's built-in error renderer
to our module; help snapshots updated to the cleaner worktrunk-style
output (✗ / ↳).

## Test plan

- [x] `cargo test --test integration external_subcommand` — 7 new
integration tests cover happy path, not-found error, typo suggestion,
nested suggestion winning over PATH lookup, exit-code propagation (exit
42), `-C` flag forwarding, and `--help` passthrough.
- [x] `cargo test --bins` — 3 new unit tests for `closest_subcommand`
(typo, unrelated, hidden).
- [x] `cargo test --test integration` — full suite passes (1407 tests).
- [x] `cargo test --lib --bins` — full unit suite passes (500 tests).
- [x] `cargo fmt --check` and `cargo clippy --all-targets --all-features
-- -D warnings`.
- [x] Manual smoke tests: `wt wt-<name>`, `wt unknown`, `wt siwtch`, `wt
squash`, `wt -C /tmp wt-<name>`, `wt wt-<name> --help`, child exit code
42 propagation.

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 13:07:19 -07:00
Maximilian Roos 71499df45b docs: surface vars & aliases on homepage and tips-patterns (#2038)
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>
2026-04-09 13:47:23 -07:00
Maximilian Roos d0bee7b260 docs: cross-link vars references to dedicated docs (#2034)
The vars feature is documented in three places (hook template variable
table, config state vars page, tips-patterns recipes), but these pages
didn't cross-reference each other. Readers encountering vars in one
place had no clear path to the other.

Added links so the two main vars pages link to each other
bidirectionally, and every other mention now points readers to either
the CLI reference (how to set/get) or the template reference (how to use
in hooks/aliases).

**Changes (5 locations — 4 in CLI source, auto-synced to docs/ and
skills/):**

- \`hook\` template variables table: \`{{ vars.<key> }}\` row now links
to \`wt config state vars\` docs
- \`wt list\` JSON output table: \`vars\` field now links to \`wt config
state vars\` docs
- \`wt config state vars\` "Template access" section: "hook templates"
now links to the template variables page
- \`wt config state\` keys list: the \`vars\` entry now links to its own
section
- \`tips-patterns.md\` "Database per worktree": "vars" now links to the
config page

No changes to aliases — their cross-references were already complete.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-09 08:41:01 -07:00
Axel H. 4ef64c47ad feat(opencode): add OpenCode integration (activity tracking, plugin, config) (#1807)
This PR adds OpenCode integration: activity tracking markers in `wt
list`, plugin installation via CLI, `wt config show` diagnostics, and
LLM commit generation detection.

Continues the work started in #1295 and #1533 which added OpenCode to
the docs and example config.

## What's included

- **Activity tracking plugin** (`dev/opencode-plugin.ts`): maps
`session.status`/`session.idle`/`session.deleted` to branch markers,
same pattern as Claude Code's plugin
- **`wt config plugins opencode install/uninstall`**: installs the
plugin to `~/.config/opencode/plugins/worktrunk.ts` — source embedded
via `include_str!()`, no npm needed. Sits under `wt config plugins`
alongside Claude Code's `wt config plugins claude`.
- **`wt config show` OPENCODE section**: shows plugin install status
with actionable hints (only when `opencode` is on PATH)
- **`LlmTool::OpenCode` variant**: detected via PATH for commit
generation auto-config

## Docs approach

Kept deliberately low-profile — no dedicated docs page, no README
mention. OpenCode is discoverable via `wt config show` and a mention in
tips-patterns. If it becomes popular, docs prominence can increase.

> _This was written by Claude Code on behalf of @max-sixty_

---------

Co-authored-by: Maximilian Roos <m@maxroos.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-05 18:44:41 -07:00
Maximilian Roos 0d7fe83130 fix: use underscores in db_url var key examples (#1848)
Hyphens in var keys conflict with Jinja2 expression parsing —
`vars.db-url` is interpreted as `vars.db` minus `url`. Renamed `db-url`
to `db_url` in the database examples in hook docs and tips-patterns.

> _This was written by Claude Code on behalf of maximilian_

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-31 18:27:35 -07:00
Maximilian Roos 79fa6a8b73 feat: lazy template expansion for pipeline vars (#1840)
Pipeline steps referencing `{{ vars.* }}` are expanded at execution time
rather than upfront, so vars set by step N are available to step N+1.
This enables DRY patterns like deriving a container name once and
reusing it across pipeline steps and across hooks
(post-start/post-remove).

A pipeline that sets vars in step 1 and uses them in step 2:

```toml
post-start = [
  "wt config state vars set container='{{ repo }}-{{ branch | sanitize }}-postgres'",
  { db = "docker run --name {{ vars.container }} ..." },
]

[post-remove]
db-stop = "docker stop {{ vars.container }} 2>/dev/null || true"
```

**Background pipelines** wrap lazy steps in `eval "$(wt step eval
--shell-escape "$__WT_TPL_N")"` with templates passed as env vars on the
spawned process. **Foreground mode** (`--foreground`) re-expands
templates in-process for structured error reporting.

Detection uses `minijinja::undeclared_variables` (via new shared
`template_references_var()` helper) — no string heuristics. Syntax
errors are caught at prepare time; only var resolution is deferred.
`--shell-escape` on `wt step eval` is hidden from `--help` (internal
mechanism).

Updates database examples in hook docs and tips-patterns to use the
pipeline pattern.

> _This was written by Claude Code on behalf of @max-sixty_

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-31 01:13:15 -07:00
Maximilian Roos 69edc3a337 feat: add wt config state vars for per-branch custom variables (#1006)
Adds `wt config state vars set/get/list/clear` commands for storing
custom variables per branch in git config
(`worktrunk.state.{branch}.vars.{key}`). Variables are available in all
template contexts via `{{ vars.key }}` syntax (hooks, `wt step eval`),
with JSON dot access (`{{ vars.config.port }}`) and default filters (`{{
vars.env | default('dev') }}`).

Uses `KEY=VALUE` syntax for `set` — `wt config state vars set
env=staging` — matching Docker `-e`, Heroku `config:set`, Fly `secrets
set`, and worktrunk's own `--var KEY=VALUE` convention. Splits on first
`=` only, so values can contain `=` (URLs, JSON).

The database-per-worktree example now uses `vars` to store the
connection string during `post-start`, replacing the `.env.local`
heredoc pattern. The URL is accessible outside hooks via `$(wt config
state vars get db-url)`.

Includes vars data in `wt list --format=json` output and `wt config
state get` display. Builds on #1004 (`wt step eval`). Part of #947.

## Test plan

- [x] Unit tests for vars template injection (empty, with data, no
branch, JSON dot access, shell escaping)
- [x] Integration tests for vars CLI commands (set, get, list, clear,
clear --all, --branch flag)
- [x] Edge-case tests for KEY=VALUE parsing (values containing `=`,
empty values)
- [x] Integration tests for vars in JSON output (present with data,
absent when empty)
- [x] All 493 unit + 1294 integration tests pass

> _This was written by Claude Code on behalf of max-sixty_

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-30 14:37:04 -07:00
Maximilian Roos b6a6744380 Consistent code block convention for syntax highlighting (#1777)
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>
2026-03-28 12:18:48 -07:00
Maximilian Roos 5c1672932f docs: remove deprecated post-create from documentation (#1776)
Replace all `post-create` references with `pre-start` across
documentation, skills, example config, and test names. The Rust
deprecation handling code (migration, alias, config parsing) remains
intact for users with existing configs.

Also removes `post-create` from the `wt hook show` value_parser — it was
listed as a valid hook type for display even though it's been
deprecated.

> _This was written by Claude Code on behalf of @max-sixty_

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-27 17:38:57 -07:00
worktrunk-bot 82d02239f3 fix: correct --full example text and add removable worktree to docs (#1775) 2026-03-27 16:14:53 -07:00
Maximilian Roos ef1f47a7c6 docs: tighten summary caching line and fix broken hook anchor (#1769)
Rewrite the redundant summary caching sentence in llm-commits and
tips-patterns docs — the preceding paragraph already explains what
summaries are and how to enable them, so "Disabled by default — when
enabled, each branch's diff is sent to the configured LLM for
summarization" was restating known information. Replaced with "Summaries
are cached and regenerated only when the diff changes."

Also fix a broken `#security` anchor in step aliases docs — the hook
page has no such heading; the correct target is `#wt-hook-approvals`.

> _This was written by Claude Code on behalf of @max-sixty_

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-27 00:46:25 -07:00
Maximilian Roos 7339e4b283 docs: add bare repository worktree-path example and layout guide (#1664)
## Summary

- Add bare repo `worktree-path` example (`{{ repo_path }}/../{{ branch |
sanitize }}`) to config docs alongside existing examples
- Update `repo_path` variable description to note bare repo behavior
- Expand "Bare repository layout" section in tips-patterns: explains why
bare repos suit worktree workflows and documents the `git clone --bare
<url> project/.git` setup

## Test plan

- [x] `cargo run -- hook pre-merge --yes` passes
- [x] Doc sync tests pass
(`test_command_pages_and_skill_files_are_in_sync`,
`test_config_source_generates_example_toml`)
- [x] Help snapshots updated and accepted

> _This was written by Claude Code on behalf of @max-sixty_

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-22 16:07:41 -07:00
Maximilian Roos 879cbd906b feat(docs): move experimental badges to headings (#1523)
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>
2026-03-14 14:29:41 -07:00
worktrunk-bot 905aa273df docs: add manual commit message recipes (#1469) 2026-03-13 06:06:25 -07:00
Rickey 26333bc87b docs: add Xcode DerivedData cleanup recipe (#1423)
## 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>
2026-03-10 13:04:47 -07:00
worktrunk-bot 2e29a275dd docs: use .localhost subdomains instead of .lvh.me for Caddy routing (#1343)
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>
2026-03-07 17:11:15 -08:00
Maximilian Roos 1cf2c8203f Add page metadata, canonical URLs, and structured data to docs (#1167)
## 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>
2026-02-22 13:58:34 -08:00
Maximilian Roos 91a505603a feat: add Summary column to wt list --full (#1100)
## Summary

- Adds an LLM-generated Summary column to `wt list --full`, showing a
one-line description of each branch's changes
- Extracts shared summary module (`src/summary.rs`) from picker-specific
code for reuse across `wt list` and `wt switch`
- Summary and Message are both flexible columns — Summary expands first
(10→70 chars), then Message gets remaining space (10→100 chars)
- Gated on `[list] summary = true` config (disabled by default — each
branch's diff is sent to the configured LLM) plus `[commit.generation]`
and `--full`
- Labeled as experimental throughout docs

## Test plan

- [x] 539 unit tests pass
- [x] 1072 integration tests pass (including 9 new layout tests for
flexible Summary/Message allocation)
- [x] Pre-commit lints pass
- [x] Manual: `wt list --full` with LLM configured + `summary = true` —
Summary column appears
- [x] Manual: `wt list --full` without `summary = true` — no Summary
column
- [x] Manual: `wt list` (no `--full`) — no Summary column
- [x] Manual: `wt switch` picker — summary tab still works

> _This was written by Claude Code on behalf of max-sixty_

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-18 16:25:46 -08:00
Maximilian Roos 1d85de945f Migrate docs syntax highlighting to giallo with warm theme (#1080)
## Summary

- Migrate from static CSS syntax highlighting to Zola's giallo engine
with custom `worktrunk-light.json` theme
- Replace hardcoded `syntax-light.css` / `syntax-dark.css` with
theme-based class generation
- Design a warm "sunlit workshop" palette: amber commands, gold strings,
chartreuse quoted strings, rusty constants
- Add CSS sibling selector to differentiate quoted from bare strings
(giallo tokenizes both as `z-string`)

## Test plan

- [ ] Verify syntax colors on `/switch/` (bash: commands, flags,
strings, quoted strings)
- [ ] Verify TOML blocks on `/config/` (section headers, keys, values)
- [ ] Verify dark mode is unaffected (quoted string CSS rule scoped to
`prefers-color-scheme: light`)
- [ ] Check all tests pass (`cargo test`)

> _This was written by Claude Code on behalf of @max-sixty_

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-17 11:32:21 -08:00
Andoni Alonso 3950dc2e30 docs(hooks): add -sTCP:LISTEN to lsof in hook examples (#952)
Without this flag, lsof matches any process with a connection to the
port (including established client connections), which can accidentally
kill unrelated processes. Restricting to LISTEN sockets ensures only the
actual server process is targeted.
2026-02-08 10:04:48 -08:00
Maximilian Roos eca9beb232 fix: warn when --config path doesn't exist, fix shell quoting in docs (#895)
* fix: warn when --config path doesn't exist, fix shell quoting in docs

Bug fixes from adversarial testing:

1. --config path warning: When users pass --config with a non-existent
   file path, wt now warns instead of silently falling back to defaults.
   Note: WORKTRUNK_CONFIG_PATH env var doesn't trigger this warning since
   it's commonly used for test isolation with intentionally absent paths.

2. Shell quoting in docs: Template variables are automatically shell-escaped,
   so user-added quotes cause issues with special characters. Fixed examples
   in hook docs and tips-patterns that incorrectly showed quoted variables.

Also adds:
- Test verifying --squash is correctly ignored with --no-commit
- Tests for ANSI escape sequence handling in branch names

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: skip ANSI branch name test on Windows

Git for Windows with MSYS2 bash behaves differently and may accept
branch names containing control characters. The test verifies Unix git
behavior, so restrict it to Unix platforms.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-28 15:57:58 -08:00
Uriah Carpenter 1373345f92 Fix worktree-path on tips page (#876)
* Fix worktree-path on tips page

Update tips page with correct `worktree-path` for bare repos. (Without `../` working dir is created inside `.git` directory.)

Second doc location missed in https://github.com/max-sixty/worktrunk/pull/320

* Fix worktree-path template in bare repository example

Update the worktree-path configuration to use a relative path traversal
("../{{ branch | sanitize }}") instead of a relative path within the
repository. This correctly positions worktrees as siblings to the bare
repository directory rather than subdirectories within it.

---------

Co-authored-by: Maximilian Roos <5635139+max-sixty@users.noreply.github.com>
Co-authored-by: Maximilian Roos <m@maxroos.com>
2026-01-27 00:20:24 +00:00
Maximilian Roos 1f239aced7 docs: add Quick Start section to front page (#864)
Reapply Quick Start documentation with fix for README rendering.
The terminal output blocks now include explicit `$ ` prompts before
commands, which makes GitHub's console syntax highlighting work
correctly (commands styled differently from output).

Changes:
- Add Quick Start section showing switch, list, merge workflow
- Add snapshot tests for Quick Start terminal output examples
- Fix strip_html() to convert .cmd spans to `$ ` prefixed commands
- Sync skill reference files from docs

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-25 22:55:09 -08:00