mirror of
https://github.com/max-sixty/worktrunk.git
synced 2026-09-14 20:00:38 +08:00
Publish shell integration, add a footer, dedupe global options (#4000)
Guides and site presentation: one home per topic, a shell-integration
page, a footer, and three rendering fixes.
**Global options were emitted once per command reference**
clap repeats the same ~20-line `Global Options:` block in every
reference it renders, so a page assembled from subdocs stacked 11 copies
on `/config/` and 13 on `/step/`. That padded the pages and gave site
search that many near-identical hits — "squash" returned both
`#command-reference` and `#command-reference-2`. `take_global_options`
cuts each reference at the heading as it is built, keeping only the
first; one `kept` flag threads through the subdoc expansion and the page
streams out rather than accumulating. Terminal `--help` renders through
clap directly and is unchanged.
The config page also carried colliding anchors — two "Hooks" (`#hooks`,
`#hooks-1`), two "Aliases", and seven "Examples" (`#examples` …
`#examples-6`) — now qualified at their source in `src/cli/config.rs`:
User/Project hooks, User/Project aliases, and
Approval/Alias/State/Cache/Log/Variable examples.
`/step/` still has its own set (eight "Examples", two "Options", two
"Arguments", plus "Staging" and "Dry run" pairs). Qualifying those moves
existing `/step/#examples-N` anchors, so it wants a pass of its own with
the inbound links audited; the deduplication above already removes 13
Global Options blocks from that page.
**Shell integration has a page**
Shell-integration debugging was skill-only: five named warning messages,
a PowerShell checklist, and the wrapper mechanism, with no site page —
while the FAQ's answer to "`wt switch` didn't cd" was to install the
Claude Code plugin. It is now `/shell-integration/`, offered first, with
the plugin as the second route. The `llms.txt` listing serves every page
as `/<slug>.md` from a hand-created symlink, so a new page was a 404 the
listing still advertised; the symlink is added and the sync now fails
when a listed page has none.
**Presentation**
- A site footer carries the version (read from `Cargo.toml` at build
time), releases, changelog and license. No page named any of them, and
`/code-signing/` was reachable only from inside a collapsed block on the
homepage. Starlight's `Footer` is wrapped rather than replaced.
- `wt list --full` renders 1157px inside an 800px content column, so 40%
of it sat behind a horizontal scrollbar with the pane beside the column
empty. A terminal frame now takes the whole pane where there is slack,
measured with a query container rather than recomputed from Starlight's
layout formula.
- The `wt-command-reference` frames offered a copy button for 3,877
characters of generated help text; they now expose no copy control. A
console block listing several commands is as often a menu of
alternatives as a recipe, and nothing in the markup tells them apart, so
every command line in such a block carries its own copy control
alongside the block's.
- The four command demos and the two hand-written figures get captions;
the 2.33 MB homepage GIF below the fold loads lazily.
**Sidebar order is pinned**
`site-navigation.mjs` told readers a
`test_sidebar_matches_frontmatter_order` would fail when the authored
sidebar and the pages' `sidebar.order` disagreed. No such test existed,
and the disagreement it describes is exactly what the survey found:
`remove` listed before `merge`, Agent integration ahead of
lower-numbered pages. The test is written, so the sidebar and the
`llms.txt` ordering derived from the frontmatter can't drift apart
again.
<details>
<summary>Guide corrections</summary>
- Tips & patterns was 26 flat H2 recipes in no order, all 26 in the
sidebar. They group under five H2s — setup and layout, aliases and
hooks, per-worktree services, working with agents, status/commits/logs —
with each recipe demoted to H3. Anchors are level-independent, so
existing `/tips-patterns/#…` fragments still resolve.
- `-x 'opencode run'` has been broken since 0.75.0 made `-x` a literal
program: it is `-x opencode -- run '<task>'`.
- The branch-summary preview moved from tab 5 to 6 when the unified-diff
tab landed; the recipe names the `summary` tab instead of a number.
- The Caddy recipe claimed `feature-auth` hashes to port 16460 — that is
`fix-auth`'s port. It is 18283.
- `_` in `wt list` is same-commit *and clean*; the
same-commit-with-changes glyph is `–`, which is not safe to delete.
- `wt step prune` removes branches with no worktree too, and the min-age
guard ages a worktree by its creation time and a bare branch by its
oldest reflog entry.
- `wt step eval -v` prints fifteen variables; the example showed two
under a lead calling them "the available template variables".
- A filter applied to `{{ vars.<key> }}` acts on the placeholder the
preview substitutes, so `{{ vars.port | default('8080') }}` previews as
`{{ vars.port }}`, filter gone.
- The `.git/wt/cache/` table was missing `picker-preview`, and `wt
config state clear` prompts unless `--yes`.
- `skills/worktrunk/reference/README.md` was a symlink to the repo
README that `SKILL.md` never referenced, and the plugin mirror
dereferenced it into a 262-line copy carrying the star-history token,
share links, and a logo path resolving nowhere. Nothing generated it, so
deleting the symlink is the whole fix.
- One home per topic: agent handoffs stay in tips-patterns, activity
markers in `claude-code.md`, alias-template deferral in `extending.md`,
and the `codename` filter's two `worktree-path` recipes give way to the
config page that owns path templates. The FAQ's "Running tests" and "How
can I contribute?" duplicated the README's Contributing block down to
the share URLs.
- The FAQ linked `/worktrunk/#install`, the `noindex` compatibility
route; the plugin hook shim's Windows Terminal hint pointed there too.
Both use `/#install`, where the new sidebar Install entry goes.
- Example names settle on `myproject` / `feature-auth`; "sibling to main
repo" becomes "sibling to the main worktree", and `wt remove`'s "target
worktree" becomes "the worktree being removed" per the project's own
terminology rule.
</details>
UX survey items: `#36`, `#37`, `#38`, `#39`, `#45`, `#47`, `#48`, `#49`,
`#50`, `#51`, `#52`, `#61`, `#94`, `#95`, `#96`, `#97`, `#99`, `#100`.
Reviewable files: the hand-written pages under `docs/src/content/docs/`
(notably the new `shell-integration.md`, `tips-patterns.md`, `faq.md`),
`docs/src/components/Footer.astro`,
`docs/src/plugins/worktrunk-terminal.mjs`,
`docs/src/site-navigation.mjs`, `docs/tests/*.mjs`, `src/help.rs`,
`plugins/worktrunk/hooks/wt.sh`. Generated mirrors and snapshots are
regenerated.
> _This was written by Claude Code on behalf of max-sixty_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+2
-7
@@ -88,12 +88,7 @@ exclude = [
|
||||
exclude_path = [
|
||||
# Generated plugin-skills mirror: a byte-identical copy of `skills/`, synced
|
||||
# by `test_docs_are_in_sync`. Every file here is already link-checked at its
|
||||
# authored `skills/` source, where `reference/README.md` is a symlink to the
|
||||
# repo-root README and its relative `docs/public/logo.png` resolves. The sync
|
||||
# dereferences that symlink into a real file, so under the mirror the
|
||||
# repo-root-relative image path resolves to a nonexistent
|
||||
# `plugins/worktrunk/skills/worktrunk/reference/docs/public/logo.png`.
|
||||
# Excluding the redundant copy avoids that false positive without losing
|
||||
# coverage (the authored source is still checked).
|
||||
# authored `skills/` source, so checking the copy costs time and finds
|
||||
# nothing new.
|
||||
"plugins/worktrunk/skills/",
|
||||
]
|
||||
|
||||
+2
-5
@@ -3,11 +3,8 @@
|
||||
# belong in the primary source (src/cli/mod.rs for command pages,
|
||||
# docs/src/content/docs/*.md for everything else).
|
||||
skills/worktrunk/reference/*.md linguist-generated=true
|
||||
# Skill-only files and the top-level README symlink are primary sources — edited
|
||||
# directly, not regenerated from docs/src/content/docs/. Exempt them so real edits stay
|
||||
# visible in PR diffs.
|
||||
skills/worktrunk/reference/README.md linguist-generated=false
|
||||
skills/worktrunk/reference/shell-integration.md linguist-generated=false
|
||||
# Skill-only files are primary sources — edited directly, not regenerated from
|
||||
# docs/src/content/docs/. Exempt them so real edits stay visible in PR diffs.
|
||||
skills/worktrunk/reference/troubleshooting.md linguist-generated=false
|
||||
# The plugin skills tree is a real-file mirror of repo-root skills/, rewritten by
|
||||
# `test_docs_are_in_sync`'s `sync_plugin_skills_mirror` stage. Nothing here is
|
||||
|
||||
@@ -46,7 +46,7 @@ Load relevant skills before starting; reload when scope changes mid-session. Pro
|
||||
|
||||
## Documentation
|
||||
|
||||
Behavior changes require doc updates. `src/cli/mod.rs` (`after_long_help` plus clap attributes) is the PRIMARY SOURCE for command pages; their rendered mirrors in `docs/src/content/docs/` and `skills/worktrunk/reference/` are generated, as is all of `plugins/worktrunk/skills/` — but both directories also hold hand-edited primaries (non-command docs in `docs/src/content/docs/`, skill-only pages like `shell-integration.md` in the reference dir), so check which file is primary in the sync taxonomy before editing. Ask: "does `--help` still describe what the code does?" `cargo test --test integration test_docs_are_in_sync` checks doc sync; editing help text (`after_long_help`, `about`, arg docs) also changes the rendered `--help` snapshots, which that test leaves untouched — `cargo insta test --accept --test integration -- test_help` regenerates them (the pre-merge hook runs both). Sync taxonomy, help-text authoring (three render contexts, link text, config-TOML blocks): `docs/CLAUDE.md`.
|
||||
Behavior changes require doc updates. `src/cli/mod.rs` (`after_long_help` plus clap attributes) is the PRIMARY SOURCE for command pages; their rendered mirrors in `docs/src/content/docs/` and `skills/worktrunk/reference/` are generated, as is all of `plugins/worktrunk/skills/` — but both directories also hold hand-edited primaries (non-command docs in `docs/src/content/docs/`, skill-only pages like `troubleshooting.md` in the reference dir), so check which file is primary in the sync taxonomy before editing. Ask: "does `--help` still describe what the code does?" `cargo test --test integration test_docs_are_in_sync` checks doc sync; editing help text (`after_long_help`, `about`, arg docs) also changes the rendered `--help` snapshots, which that test leaves untouched — `cargo insta test --accept --test integration -- test_help` regenerates them (the pre-merge hook runs both). Sync taxonomy, help-text authoring (three render contexts, link text, config-TOML blocks): `docs/CLAUDE.md`.
|
||||
|
||||
## Plugin Layout
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ A quick demo:
|
||||
|
||||
AI agents like Claude Code and Codex can handle longer tasks without
|
||||
supervision, such that it's possible to manage 5-10+ in parallel. Git's native
|
||||
worktree feature give each agent its own working directory, so they don't step
|
||||
worktree feature gives each agent its own working directory, so they don't step
|
||||
on each other's changes.
|
||||
|
||||
But the git worktree UX is clunky. Even a task as small as starting a new
|
||||
@@ -238,6 +238,18 @@ The `-x` flag runs a command after switching; arguments after `--` are passed to
|
||||
- [Open an issue](https://github.com/max-sixty/worktrunk/issues/new?title=&body=%23%23%20Description%0A%0A%3C!--%20Describe%20the%20bug%20or%20feature%20request%20--%3E%0A%0A%23%23%20Context%0A%0A%3C!--%20Any%20relevant%20context%3A%20your%20workflow%2C%20what%20you%20were%20trying%20to%20do%2C%20etc.%20--%3E) — feedback, feature requests, even a small friction or imperfect user message, or [a worktree pain not yet solved](https://github.com/max-sixty/worktrunk/issues/new?title=Worktree%20friction%3A%20&body=%23%23%20The%20friction%0A%0A%3C!--%20What%20worktree-related%20task%20is%20still%20painful%3F%20--%3E%0A%0A%23%23%20Current%20workaround%0A%0A%3C!--%20How%20do%20you%20handle%20this%20today%3F%20--%3E%0A%0A%23%23%20Ideal%20solution%0A%0A%3C!--%20What%20would%20make%20this%20easier%3F%20--%3E)
|
||||
- Share: [X](https://twitter.com/intent/tweet?text=Worktrunk%20%E2%80%94%20CLI%20for%20git%20worktree%20management&url=https%3A%2F%2Fworktrunk.dev) · [Reddit](https://www.reddit.com/submit?url=https%3A%2F%2Fworktrunk.dev&title=Worktrunk%20%E2%80%94%20CLI%20for%20git%20worktree%20management) · [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fworktrunk.dev)
|
||||
|
||||
### Running the tests
|
||||
|
||||
```bash
|
||||
cargo test
|
||||
```
|
||||
|
||||
The shell integration tests need bash, zsh, fish, nushell, and pwsh, plus `jq`:
|
||||
|
||||
```bash
|
||||
cargo test --test integration --features shell-integration-tests
|
||||
```
|
||||
|
||||
> ### 📚 Full documentation at [worktrunk.dev](https://worktrunk.dev) 📚
|
||||
|
||||
### Star history
|
||||
|
||||
Generated
+4
-4
@@ -221,7 +221,7 @@
|
||||
#
|
||||
# Built-in excludes (VCS metadata and tool-state directories) always apply; the `wt step copy-ignored` docs (https://worktrunk.dev/step/#wt-step-copy-ignored) list them. User config and project config exclusions are combined.
|
||||
#
|
||||
# ### Aliases
|
||||
# ### User aliases
|
||||
#
|
||||
# Command templates that run as `wt <name>`. See the Extending Worktrunk guide (https://worktrunk.dev/extending/#aliases) for usage and flags.
|
||||
#
|
||||
@@ -229,7 +229,7 @@
|
||||
# greet = "echo Hello from {{ branch }}"
|
||||
# url = "echo http://localhost:{{ branch | hash_port }}"
|
||||
#
|
||||
# Aliases defined here apply to all projects. For project-specific aliases, use the project config (https://worktrunk.dev/config/#project-configuration) `[aliases]` section instead.
|
||||
# Aliases defined here apply to all projects. For project-specific aliases, use the project config (https://worktrunk.dev/config/#project-aliases) `[aliases]` section instead.
|
||||
#
|
||||
# ### User project-specific settings
|
||||
#
|
||||
@@ -411,6 +411,6 @@
|
||||
#
|
||||
# How the fragment renders, and the project-config counterpart: the LLM commits guide (https://worktrunk.dev/llm-commits/#appending-to-the-prompt).
|
||||
#
|
||||
# ## Hooks
|
||||
# ## User hooks
|
||||
#
|
||||
# See `wt hook` (https://worktrunk.dev/hook/) for hook types, execution order, template variables, and examples. User hooks apply to all projects; project hooks (https://worktrunk.dev/config/#project-configuration) apply only to that repository.
|
||||
# See `wt hook` (https://worktrunk.dev/hook/) for hook types, execution order, template variables, and examples. User hooks apply to all projects; project hooks (https://worktrunk.dev/config/#project-hooks) apply only to that repository.
|
||||
|
||||
Generated
+3
-3
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# To create a starter file with commented-out examples, run `wt config create --project`.
|
||||
#
|
||||
# ## Hooks
|
||||
# ## Project hooks
|
||||
#
|
||||
# Project hooks apply to this repository only. See `wt hook` (https://worktrunk.dev/hook/) for hook types, execution order, and examples.
|
||||
#
|
||||
@@ -50,7 +50,7 @@
|
||||
#
|
||||
# Built-in excludes (VCS metadata and tool-state directories) always apply; the `wt step copy-ignored` docs (https://worktrunk.dev/step/#wt-step-copy-ignored) list them. User config and project config exclusions are combined.
|
||||
#
|
||||
# ## Aliases
|
||||
# ## Project aliases
|
||||
#
|
||||
# Command templates that run as `wt <name>`. See the Extending Worktrunk guide (https://worktrunk.dev/extending/#aliases) for usage and flags.
|
||||
#
|
||||
@@ -58,4 +58,4 @@
|
||||
# deploy = "make deploy BRANCH={{ branch }}"
|
||||
# url = "echo http://localhost:{{ branch | hash_port }}"
|
||||
#
|
||||
# Aliases defined here are shared with teammates. For personal aliases, use the user config (https://worktrunk.dev/config/#aliases) `[aliases]` section instead.
|
||||
# Aliases defined here are shared with teammates. For personal aliases, use the user config (https://worktrunk.dev/config/#user-aliases) `[aliases]` section instead.
|
||||
|
||||
+6
-6
@@ -114,12 +114,12 @@ There are three source categories:
|
||||
region in `docs/src/content/docs/{command}.md`, then generates the matching
|
||||
`skills/worktrunk/reference/` page.
|
||||
2. **Non-command pages**: files such as `claude-code.md`, `extending.md`,
|
||||
`faq.md`, `llm-commits.md`, `tips-patterns.md`, and `worktrunk.md` are primary
|
||||
in `docs/src/content/docs/`. The sync test derives the skill copy.
|
||||
3. **Skill-only pages**: files such as `shell-integration.md` and
|
||||
`troubleshooting.md` are primary in `skills/worktrunk/reference/` and have no
|
||||
site page. When adding one, add a `linguist-generated=false` entry to
|
||||
`.gitattributes`.
|
||||
`faq.md`, `llm-commits.md`, `shell-integration.md`, `tips-patterns.md`, and
|
||||
`worktrunk.md` are primary in `docs/src/content/docs/`. The sync test derives
|
||||
the skill copy.
|
||||
3. **Skill-only pages**: `troubleshooting.md` is primary in
|
||||
`skills/worktrunk/reference/` and has no site page. When adding one, add a
|
||||
`linguist-generated=false` entry to `.gitattributes`.
|
||||
|
||||
Never hand-edit a generated mirror.
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ export default defineConfig({
|
||||
},
|
||||
customCss: ['./src/styles/custom.css'],
|
||||
components: {
|
||||
Footer: './src/components/Footer.astro',
|
||||
Head: './src/components/Head.astro',
|
||||
PageTitle: './src/components/PageTitle.astro',
|
||||
Pagination: './src/components/Pagination.astro',
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
"type": "skill-md",
|
||||
"description": "Guidance for Worktrunk (the `wt` CLI) — git worktree management, hooks, and config. Load when working out which worktree a `wt` command will act on, or reaching for the global `-C <path>` to target one; editing .config/wt.toml or ~/.config/worktrunk/config.toml; adding, modifying, or debugging hooks (post-merge, post-start, pre-commit, pre-merge, post-switch, etc.); configuring commit message generation or command aliases; or troubleshooting wt behavior. Also answers general worktrunk/wt questions.",
|
||||
"url": "./worktrunk/SKILL.md",
|
||||
"digest": "sha256:69ebaa585f37a3cbe825028129409f4f22e11549afc4ab5cc872603af4459a90"
|
||||
"digest": "sha256:f8102f3e9c1909fd75f57599f8a84696c635cf41edd747d728c54abe7d585272"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Generated
+4
-3
@@ -13,17 +13,18 @@ with many parallel changes, including hooks to automate local workflows.
|
||||
|
||||
- [wt switch](https://worktrunk.dev/switch.md): Switch to a worktree; create if needed.
|
||||
- [wt list](https://worktrunk.dev/list.md): List worktrees and their status.
|
||||
- [wt remove](https://worktrunk.dev/remove.md): Remove worktree; delete branch if merged. Defaults to the current worktree.
|
||||
- [wt merge](https://worktrunk.dev/merge.md): Merge current branch into the target branch. Squash & rebase, fast-forward the target branch, remove the worktree.
|
||||
- [wt remove](https://worktrunk.dev/remove.md): Remove worktree; delete branch if merged. Defaults to the current worktree.
|
||||
- [wt config](https://worktrunk.dev/config.md): Manage user & project configs. Includes shell integration, hooks, and saved state.
|
||||
- [wt step](https://worktrunk.dev/step.md): Run individual operations. The building blocks of wt merge — commit, squash, rebase, push — plus standalone utilities.
|
||||
- [wt hook](https://worktrunk.dev/hook.md): Run configured hooks.
|
||||
|
||||
## Reference
|
||||
|
||||
- [Agent Integration](https://worktrunk.dev/claude-code.md): Worktrunk plugins for Claude Code, Codex, OpenCode, Pi, and Gemini CLI: a configuration skill, wt list activity tracking, and Claude-only worktree isolation.
|
||||
- [Extending Worktrunk](https://worktrunk.dev/extending.md): Three ways to add custom behavior: hooks for lifecycle automation, aliases for reusable commands, and custom subcommands for standalone tools.
|
||||
- [LLM Commit Messages](https://worktrunk.dev/llm-commits.md): Generate commit messages from diffs using any LLM. Integrates with wt merge, wt step commit, and wt step squash.
|
||||
- [Agent Integration](https://worktrunk.dev/claude-code.md): Worktrunk plugins for Claude Code, Codex, OpenCode, Pi, and Gemini CLI: a configuration skill, wt list activity tracking, and Claude-only worktree isolation.
|
||||
- [Tips & Patterns](https://worktrunk.dev/tips-patterns.md): Practical recipes for Worktrunk workflows: aliases, shell integration, Zellij layouts, and parallel agent patterns.
|
||||
- [Tips & Patterns](https://worktrunk.dev/tips-patterns.md): Practical recipes for Worktrunk workflows: repo layout, aliases and hooks, per-worktree dev servers and databases, agent handoffs, and branch status.
|
||||
- [Shell integration](https://worktrunk.dev/shell-integration.md): How Worktrunk's shell wrapper changes the parent shell's directory, which files each shell gets, and how to debug integration that isn't working.
|
||||
- [FAQ](https://worktrunk.dev/faq.md): Common questions about Worktrunk: comparison to git worktree and branch switching, bare repos, TUI support, and more.
|
||||
- [Code Signing Policy](https://worktrunk.dev/code-signing.md): How Worktrunk's Windows release binaries are code-signed: certificate provenance, the build and signing pipeline, project roles, and per-release approval.
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../skills/worktrunk/reference/shell-integration.md
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
// Starlight's own footer carries only the edit link, so the site had nowhere
|
||||
// to name its version, its releases, or its license, and /code-signing/ was
|
||||
// reachable only from a collapsed block on the homepage. This wraps the
|
||||
// default rather than replacing it, keeping the upstream merge surface to the
|
||||
// one element added below.
|
||||
import Default from '@astrojs/starlight/components/Footer.astro';
|
||||
|
||||
// The published version is whatever Cargo.toml says at build time; a second
|
||||
// copy here would go stale on the next release. `?raw` resolves at build time
|
||||
// relative to this file, unlike a runtime path in the bundled output.
|
||||
import cargoToml from '../../../Cargo.toml?raw';
|
||||
|
||||
const repository = 'https://github.com/max-sixty/worktrunk';
|
||||
|
||||
const version = cargoToml.match(/^\[package\][\s\S]*?^version = "([^"]+)"$/m)?.[1];
|
||||
if (!version) throw new Error('No [package] version in Cargo.toml');
|
||||
---
|
||||
|
||||
<Default />
|
||||
|
||||
<footer class="wt-site-footer">
|
||||
<a href={`${repository}/releases/tag/v${version}`}>v{version}</a>
|
||||
<a href={`${repository}/releases`}>Releases</a>
|
||||
<a href={`${repository}/blob/main/CHANGELOG.md`}>Changelog</a>
|
||||
<a href="/code-signing/">Code signing</a>
|
||||
<a href={`${repository}/blob/main/LICENSE`}>MIT OR Apache-2.0</a>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
.wt-site-footer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem 1.25rem;
|
||||
margin-top: 2.5rem;
|
||||
border-top: 1px solid var(--sl-color-hairline);
|
||||
padding-top: 1rem;
|
||||
color: var(--sl-color-gray-3);
|
||||
font-size: var(--sl-text-xs);
|
||||
}
|
||||
|
||||
.wt-site-footer a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.wt-site-footer a:hover {
|
||||
color: var(--wt-copper);
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
title: "Agent Integration"
|
||||
description: "Worktrunk plugins for Claude Code, Codex, OpenCode, Pi, and Gemini CLI: a configuration skill, wt list activity tracking, and Claude-only worktree isolation."
|
||||
sidebar:
|
||||
order: 23
|
||||
order: 21
|
||||
---
|
||||
Worktrunk ships a plugin for each supported agent CLI. What a plugin provides depends on the hooks that CLI exposes:
|
||||
|
||||
@@ -11,9 +11,11 @@ Worktrunk ships a plugin for each supported agent CLI. What a plugin provides de
|
||||
| Configuration skill | ✓ | ✓ | | | ✓ |
|
||||
| Activity tracking (🤖/💬 in `wt list`) | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Worktree isolation | ✓ | | | | |
|
||||
| `/wt-switch-create` command | ✓ | | | | |
|
||||
| `/wt-switch-create` skill | ✓ | ✓\* | | | ✓\* |
|
||||
|
||||
The configuration skill is documentation the agent reads to help set up LLM commits, hooks, and troubleshooting. Activity tracking shows which worktrees have running sessions. Worktree isolation needs worktree-lifecycle hooks and `/wt-switch-create` needs session working-directory switching — both Claude Code-only, so Codex, OpenCode, Pi, and Gemini users invoke `wt switch --create` and `wt remove` directly. Codex tracks activity through its own `Stop` and `SessionEnd` hooks.
|
||||
\* Codex and Gemini load the `/wt-switch-create` skill from the same shared skill set, but neither lets a skill change the session's working directory, so it does nothing there.
|
||||
|
||||
The configuration skill is documentation the agent reads to help set up LLM commits, hooks, and troubleshooting. Activity tracking shows which worktrees have running sessions. Worktree isolation needs worktree-lifecycle hooks, which only Claude Code exposes, so Codex, OpenCode, Pi, and Gemini users invoke `wt switch --create` and `wt remove` directly. Codex tracks activity through its own `Stop` and `SessionEnd` hooks.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -136,7 +138,7 @@ Three things to get right:
|
||||
|
||||
Claude Code agents can run in isolated worktrees (`isolation: "worktree"`). By default, Claude Code creates these with `git worktree add`. The plugin's `WorktreeCreate` and `WorktreeRemove` hooks route this through `wt switch --create` and `wt remove` instead, so worktrees created by agents get worktrunk's naming conventions, hooks, and lifecycle management.
|
||||
|
||||
## `/wt-switch-create` command (Claude Code only)
|
||||
## `/wt-switch-create` skill (Claude Code only)
|
||||
|
||||
`/wt-switch-create [<branch>] [<repo>] [-- <task>]` starts a task in a fresh worktree without leaving the session: it creates the worktree, switches into it, and runs the task (all arguments optional). The worktree shows up in `wt list`; merge or remove it with `wt merge` / `wt remove`.
|
||||
|
||||
@@ -153,6 +155,7 @@ Worktree state comes from the same cells [`wt list`](/list/) renders; Claude Cod
|
||||
<source srcset="/assets/docs/dark/wt-statusline.gif" media="(prefers-color-scheme: dark)">
|
||||
<img src="/assets/docs/light/wt-statusline.gif" alt="Claude Code statusline demo" width="1600" height="900">
|
||||
</picture>
|
||||
<figcaption>Switching to a worktree, then launching Claude Code with the statusline configured</figcaption>
|
||||
</figure>
|
||||
|
||||
Add to `~/.claude/settings.json`:
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
title: "Code Signing Policy"
|
||||
description: "How Worktrunk's Windows release binaries are code-signed: certificate provenance, the build and signing pipeline, project roles, and per-release approval."
|
||||
sidebar:
|
||||
order: 26
|
||||
hidden: true
|
||||
order: 27
|
||||
---
|
||||
This page is Worktrunk's **code signing policy**. It describes what gets signed, which certificate is used, how the signing pipeline works, and who authorizes each release. It exists both to document the process for users and to satisfy the transparency requirements of the [SignPath Foundation](https://signpath.org/) open-source code signing program.
|
||||
|
||||
|
||||
+13
-211
@@ -330,7 +330,7 @@ exclude = [] # Additional excludes (e.g., [".cache/", ".turbo/"])
|
||||
|
||||
Built-in excludes (VCS metadata and tool-state directories) always apply; [the `wt step copy-ignored` docs](/step/#wt-step-copy-ignored) list them. User config and project config exclusions are combined.
|
||||
|
||||
### Aliases
|
||||
### User aliases
|
||||
|
||||
Command templates that run as `wt <name>`. See the [Extending Worktrunk guide](/extending/#aliases) for usage and flags.
|
||||
|
||||
@@ -340,7 +340,7 @@ greet = "echo Hello from {{ branch }}"
|
||||
url = "echo http://localhost:{{ branch | hash_port }}"
|
||||
```
|
||||
|
||||
Aliases defined here apply to all projects. For project-specific aliases, use the [project config](/config/#project-configuration) `[aliases]` section instead.
|
||||
Aliases defined here apply to all projects. For project-specific aliases, use the [project config](/config/#project-aliases) `[aliases]` section instead.
|
||||
|
||||
### User project-specific settings
|
||||
|
||||
@@ -536,9 +536,9 @@ template-append = """
|
||||
|
||||
How the fragment renders, and the project-config counterpart: [the LLM commits guide](/llm-commits/#appending-to-the-prompt).
|
||||
|
||||
## Hooks
|
||||
## User hooks
|
||||
|
||||
See [`wt hook`](/hook/) for hook types, execution order, template variables, and examples. User hooks apply to all projects; [project hooks](/config/#project-configuration) apply only to that repository.
|
||||
See [`wt hook`](/hook/) for hook types, execution order, template variables, and examples. User hooks apply to all projects; [project hooks](/config/#project-hooks) apply only to that repository.
|
||||
<!-- USER_CONFIG_END -->
|
||||
<!-- PROJECT_CONFIG_START -->
|
||||
# Project Configuration
|
||||
@@ -547,7 +547,7 @@ Project configuration lets teams share repository-specific settings — hooks, d
|
||||
|
||||
To create a starter file with commented-out examples, run `wt config create --project`.
|
||||
|
||||
## Hooks
|
||||
## Project hooks
|
||||
|
||||
Project hooks apply to this repository only. See [`wt hook`](/hook/) for hook types, execution order, and examples.
|
||||
|
||||
@@ -603,7 +603,7 @@ exclude = [".cache/", ".turbo/"]
|
||||
|
||||
Built-in excludes (VCS metadata and tool-state directories) always apply; [the `wt step copy-ignored` docs](/step/#wt-step-copy-ignored) list them. User config and project config exclusions are combined.
|
||||
|
||||
## Aliases
|
||||
## Project aliases
|
||||
|
||||
Command templates that run as `wt <name>`. See the [Extending Worktrunk guide](/extending/#aliases) for usage and flags.
|
||||
|
||||
@@ -613,7 +613,7 @@ deploy = "make deploy BRANCH={{ branch }}"
|
||||
url = "echo http://localhost:{{ branch | hash_port }}"
|
||||
```
|
||||
|
||||
Aliases defined here are shared with teammates. For personal aliases, use the [user config](/config/#aliases) `[aliases]` section instead.
|
||||
Aliases defined here are shared with teammates. For personal aliases, use the [user config](/config/#user-aliases) `[aliases]` section instead.
|
||||
<!-- PROJECT_CONFIG_END -->
|
||||
|
||||
# Shell Integration
|
||||
@@ -789,24 +789,6 @@ Output:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config update
|
||||
@@ -861,24 +843,6 @@ Options:
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config approvals
|
||||
@@ -887,7 +851,7 @@ Manage command approvals.
|
||||
|
||||
Project hooks and project aliases prompt for approval on first run to prevent untrusted projects from running arbitrary commands. Approvals from both flows are stored together.
|
||||
|
||||
### Examples
|
||||
### Approval examples
|
||||
|
||||
List commands and their approval status for current project:
|
||||
```console
|
||||
@@ -964,24 +928,6 @@ Commands:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config alias
|
||||
@@ -990,7 +936,7 @@ Inspect and preview aliases.
|
||||
|
||||
Aliases are command templates configured in user (`~/.config/worktrunk/config.toml`) or project (`.config/wt.toml`) config and run as `wt <name>`. See the [Extending Worktrunk guide](/extending/#aliases) for the configuration format.
|
||||
|
||||
### Examples
|
||||
### Alias examples
|
||||
|
||||
Show every configured alias's template:
|
||||
```console
|
||||
@@ -1022,24 +968,6 @@ Commands:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state
|
||||
@@ -1056,7 +984,7 @@ State is stored in `.git/` (config entries and log files), separate from configu
|
||||
- **vars**: [Custom variables per branch](/config/#wt-config-state-vars)
|
||||
- **logs**: [Operation and debug logs](/config/#wt-config-state-logs)
|
||||
|
||||
### Examples
|
||||
### State examples
|
||||
|
||||
Get the default branch:
|
||||
```console
|
||||
@@ -1112,24 +1040,6 @@ Commands:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state cache
|
||||
@@ -1150,7 +1060,7 @@ View or drop worktrunk's regenerable caches in one place. Everything here is reb
|
||||
|
||||
Without a subcommand, runs `get`.
|
||||
|
||||
### Examples
|
||||
### Cache examples
|
||||
|
||||
Show cache contents:
|
||||
```console
|
||||
@@ -1180,24 +1090,6 @@ Options:
|
||||
Output:
|
||||
--format <FORMAT>
|
||||
Output format (text, json) [default: text]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state default-branch
|
||||
@@ -1252,24 +1144,6 @@ Commands:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state logs
|
||||
@@ -1327,7 +1201,7 @@ All logs are stored in `.git/wt/logs/` (in the main worktree's git directory). A
|
||||
|
||||
`wt config state logs --format=json` emits three arrays — `command_log`, `hook_output`, `diagnostic`. Each entry carries a `file` (relative), `path` (absolute), `size`, and `modified_at` (unix seconds). Hook-output entries additionally expose `branch`, `source` (`user` / `project` / `internal`), `hook_type` (the `post-*` kind, or `null` for internal ops), and `name`. Filter with `jq` to pick out a specific entry.
|
||||
|
||||
### Examples
|
||||
### Log examples
|
||||
|
||||
List all log files:
|
||||
```console
|
||||
@@ -1373,24 +1247,6 @@ Options:
|
||||
Output:
|
||||
--format <FORMAT>
|
||||
Output format (text, json) [default: text]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state ci-status
|
||||
@@ -1421,24 +1277,6 @@ Options:
|
||||
Output:
|
||||
--format <FORMAT>
|
||||
Output format (text, json) [default: text]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state marker
|
||||
@@ -1497,24 +1335,6 @@ Options:
|
||||
Output:
|
||||
--format <FORMAT>
|
||||
Output format (text, json) [default: text]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state vars
|
||||
@@ -1523,7 +1343,7 @@ Custom variables per branch.
|
||||
|
||||
Store custom variables per branch. Values are stored as-is — plain strings or JSON.
|
||||
|
||||
### Examples
|
||||
### Variable examples
|
||||
|
||||
Set and get values:
|
||||
```console
|
||||
@@ -1585,24 +1405,6 @@ Commands:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
<!-- END AUTO-GENERATED -->
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: "Extending Worktrunk"
|
||||
description: "Three ways to add custom behavior: hooks for lifecycle automation, aliases for reusable commands, and custom subcommands for standalone tools."
|
||||
sidebar:
|
||||
order: 21
|
||||
order: 22
|
||||
---
|
||||
Worktrunk has three extension mechanisms.
|
||||
|
||||
@@ -45,7 +45,7 @@ Aliases are configured under `[aliases]`:
|
||||
|
||||
```toml
|
||||
[aliases]
|
||||
deploy = "fly deploy --config=fly.{{ env }}.toml --app=myapp-{{ branch }}"
|
||||
deploy = "fly deploy --config=fly.{{ env }}.toml --app=myproject-{{ branch }}"
|
||||
open = "open http://localhost:{{ branch | hash_port }}"
|
||||
since-main = "git log --oneline {{ default_branch }}..HEAD"
|
||||
```
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: "FAQ"
|
||||
description: "Common questions about Worktrunk: comparison to git worktree and branch switching, bare repos, TUI support, and more."
|
||||
sidebar:
|
||||
order: 25
|
||||
order: 26
|
||||
---
|
||||
## How does Worktrunk compare to alternatives?
|
||||
|
||||
@@ -16,19 +16,19 @@ Git's built-in worktree commands work but require manual lifecycle management:
|
||||
|
||||
```console
|
||||
# Plain git worktree workflow
|
||||
$ git worktree add -b feature-branch ../myapp-feature main
|
||||
$ cd ../myapp-feature
|
||||
$ git worktree add -b feature-auth ../myproject.feature-auth main
|
||||
$ cd ../myproject.feature-auth
|
||||
# ...work, commit, push...
|
||||
$ cd ../myapp
|
||||
$ git merge feature-branch
|
||||
$ git worktree remove ../myapp-feature
|
||||
$ git branch -d feature-branch
|
||||
$ cd ../myproject
|
||||
$ git merge feature-auth
|
||||
$ git worktree remove ../myproject.feature-auth
|
||||
$ git branch -d feature-auth
|
||||
```
|
||||
|
||||
Worktrunk automates the full lifecycle:
|
||||
|
||||
```console
|
||||
$ wt switch --create feature-branch # Creates worktree, runs setup hooks
|
||||
$ wt switch --create feature-auth # Creates worktree, runs setup hooks
|
||||
# ...work...
|
||||
$ wt merge # Merges into default branch, cleans up
|
||||
```
|
||||
@@ -81,14 +81,11 @@ The stash lives in the shared `.git` directory, so it's reachable from the new w
|
||||
|
||||
## There's an issue with my shell setup
|
||||
|
||||
If shell integration isn't working (auto-cd not happening, completions missing, `wt` not found as a function), the fastest path to a fix is using Claude Code with the Worktrunk plugin:
|
||||
If shell integration isn't working (auto-cd not happening, completions missing, `wt` not found as a function), work through the [shell integration](/shell-integration/#debugging-checklist) debugging checklist — it covers each warning `wt switch` prints and what to check for every shell.
|
||||
|
||||
1. Install the [Worktrunk plugin](/claude-code/) in Claude Code
|
||||
2. Ask Claude to debug the Worktrunk shell integration
|
||||
Or hand it to an agent: install the [Worktrunk plugin](/claude-code/) in Claude Code and ask it to debug the Worktrunk shell integration. It runs `wt config show`, inspects the shell config files, and identifies the issue.
|
||||
|
||||
Claude will run `wt config show`, inspect the shell config files, and identify the issue.
|
||||
|
||||
If Claude can't fix it, please [open an issue](https://github.com/max-sixty/worktrunk/issues/new?title=Shell%20setup%20issue&body=%23%23%20Shell%20and%20OS%0A%0A-%20Shell%3A%20%0A-%20OS%3A%20%0A%0A%23%23%20Output%20of%20%60wt%20config%20show%60%0A%0A%60%60%60%0A%0A%60%60%60%0A%0A%23%23%20What%20Claude%20found%20%28if%20available%29%0A%0A) with the output of `wt config show`, the shell (bash/zsh/fish), and OS. (And even if it fixes the problem, feel free to open an issue: non-standard success cases are useful for ensuring Worktrunk is easy to set up for others.)
|
||||
If neither settles it, please [open an issue](https://github.com/max-sixty/worktrunk/issues/new?title=Shell%20setup%20issue&body=%23%23%20Shell%20and%20OS%0A%0A-%20Shell%3A%20%0A-%20OS%3A%20%0A%0A%23%23%20Output%20of%20%60wt%20config%20show%60%0A%0A%60%60%60%0A%0A%60%60%60%0A%0A%23%23%20What%20Claude%20found%20%28if%20available%29%0A%0A) with the output of `wt config show`, the shell (bash/zsh/fish), and OS. (And even if it fixes the problem, feel free to open an issue: non-standard success cases are useful for ensuring Worktrunk is easy to set up for others.)
|
||||
|
||||
## What does `-v` / `-vv` do?
|
||||
|
||||
@@ -112,7 +109,7 @@ The flags only reach a command you type; shell completion runs as its own proces
|
||||
|
||||
### 1. Worktree directories
|
||||
|
||||
Created by `wt switch <branch>` when switching to a branch that doesn't have a worktree. Use `wt switch --create <branch>` to create a new branch. Default location is `../<repo>.<branch>` (sibling to main repo), configurable via `worktree-path` in user config.
|
||||
Created by `wt switch <branch>` when switching to a branch that doesn't have a worktree. Use `wt switch --create <branch>` to create a new branch. Default location is `../<repo>.<branch>` (sibling to the main worktree), configurable via `worktree-path` in user config.
|
||||
|
||||
**To remove:** `wt remove <branch>` removes the worktree directory and deletes the branch.
|
||||
|
||||
@@ -130,19 +127,7 @@ User config location: `$XDG_CONFIG_HOME/worktrunk/` (or `~/.config/worktrunk/`)
|
||||
|
||||
### 3. Shell integration
|
||||
|
||||
Created by `wt config shell install`:
|
||||
|
||||
- **Bash**: adds line to `~/.bashrc`
|
||||
- **Zsh**: adds line to `~/.zshrc` (or `$ZDOTDIR/.zshrc`)
|
||||
- **Fish**: creates `~/.config/fish/functions/wt.fish` and `~/.config/fish/completions/wt.fish`
|
||||
- **Nushell** <span class="badge-experimental"></span>: creates `wt.nu` in Nushell's user vendor-autoload directory — the last entry of `$nu.vendor-autoload-dirs`, under `$nu.data-dir` (typically `~/.local/share/nushell/vendor/autoload` on Linux, `~/Library/Application Support/nushell/vendor/autoload` on macOS)
|
||||
- **PowerShell** (Windows): creates both profile files if they don't exist:
|
||||
- `Documents/PowerShell/Microsoft.PowerShell_profile.ps1` (PowerShell 7+)
|
||||
- `Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1` (Windows PowerShell 5.1)
|
||||
|
||||
Fish and Nushell wrappers live at a path named after the command, so install writes that file whole, replacing an existing `functions/wt.fish`, `completions/wt.fish`, or `wt.nu`. Bash, zsh, and PowerShell rc files hold the rest of a shell's setup, so install only appends a line to those.
|
||||
|
||||
**PowerShell detection on Windows:** When running from cmd.exe or PowerShell, both PowerShell profile files are created automatically. When running from Git Bash or MSYS2, PowerShell is skipped (use `wt config shell install powershell` to create the profiles explicitly).
|
||||
`wt config shell install` appends a line to the bash, zsh, and PowerShell rc files, and writes worktrunk's own wrapper and completion files whole for fish and Nushell. [Shell integration](/shell-integration/#files-created) names the file each shell gets.
|
||||
|
||||
**To remove:** `wt config shell uninstall`.
|
||||
|
||||
@@ -155,6 +140,7 @@ Worktrunk stores repository state, caches, and logs under `.git/`:
|
||||
| `git config worktrunk.*` | Cached default branch, switch history, branch markers, custom variables | Various commands |
|
||||
| `.git/wt/cache/{kind}/*.json` | Cached CI status, the largest PR/MR number seen (sizes the `wt list` CI column), and git command results (merge-tree, integration probes, diff stats, ancestry checks, ahead/behind counts, merge bases) | `wt list`, `wt merge`, `wt remove` |
|
||||
| `.git/wt/cache/summary/{branch}/{hash}.json` | Cached LLM branch summaries, content-addressed by diff hash | `wt list --full`, `wt switch` (when `[list] summary = true`) |
|
||||
| `.git/wt/cache/picker-preview/*.json` | Rendered preview panes for the interactive picker | `wt switch` |
|
||||
| `.git/wt/logs/{branch}/**/*.log` | Background hook output (nested per branch) | Hooks, background `wt remove` |
|
||||
| `.git/wt/logs/commands.jsonl` | Command audit log (~2MB max) | Hooks, LLM commands |
|
||||
| `.git/wt/logs/trace.log` | Human debug trace for issue reporting | Running with `-vv` |
|
||||
@@ -165,7 +151,7 @@ Worktrunk stores repository state, caches, and logs under `.git/`:
|
||||
|
||||
None of this is tracked by git or pushed to remotes.
|
||||
|
||||
**To remove:** `wt config state clear` removes all repository data: config keys, caches, markers, hints, variables, logs, and stale trash.
|
||||
**To remove:** `wt config state clear` removes all repository data: config keys, caches, markers, hints, variables, logs, and stale trash. It prompts before removing anything worktrunk can't recompute, unless you pass `--yes`.
|
||||
|
||||
### 5. Agent integrations
|
||||
|
||||
@@ -207,14 +193,14 @@ Removal also refuses, `--force` included, when the directory at a registered pat
|
||||
To protect a worktree from removal entirely (say it holds a local database), lock it:
|
||||
|
||||
```bash
|
||||
git worktree lock ../myproject.feature --reason "Contains local database"
|
||||
git worktree lock ../myproject.feature-auth --reason "Contains local database"
|
||||
```
|
||||
|
||||
Locked worktrees show `⊞` in `wt list`. Neither `git worktree remove` nor `wt remove` (even with `--force`) will delete them. Unlock with `git worktree unlock`.
|
||||
|
||||
### Branch deletion
|
||||
|
||||
By default, `wt remove` only deletes branches whose content is already in the default branch. Branches showing `_` (same commit) or `⊂` (integrated) in `wt list` are safe to delete.
|
||||
By default, `wt remove` only deletes branches whose content is already in the default branch. Branches showing `_` (same commit, clean) or `⊂` (integrated) in `wt list` are safe to delete.
|
||||
|
||||
For the full algorithm, see [Branch cleanup](/remove/#branch-cleanup) — it handles squash-merge and rebase workflows where commit history differs but file changes match.
|
||||
|
||||
@@ -225,7 +211,7 @@ A branch checked out in a second worktree is retained regardless, `-D` included.
|
||||
### Other cleanup
|
||||
|
||||
- `wt merge` / `wt step push` — the target branch's checked-out worktree is updated to the merged commits, so a file those commits delete disappears from it, and an ignored file at a path they track is overwritten — the same result a `git merge` run in that worktree would produce. Uncommitted changes at paths the merge doesn't touch stay in place, staged or not; one at a path it does touch refuses the merge upfront, naming the file
|
||||
- `wt remove` — besides the target worktree, two cleanup mechanisms run. The removed worktree's own `git fsmonitor--daemon` (git's per-worktree filesystem watcher under `core.fsmonitor=true`, which would leak once its worktree is gone) is sent `git fsmonitor--daemon stop`, then force-terminated (`SIGTERM`, then `SIGKILL`) via the PID resolved from its IPC socket if it didn't exit. A background sweep then deletes `.git/wt/trash/` entries older than 24 hours (directories orphaned when a previous background removal was interrupted) and terminates fsmonitor daemons whose worktree no longer exists (orphans from `git worktree remove`, `rm -rf`, or a crashed `wt`)
|
||||
- `wt remove` — besides the worktree being removed, two cleanup mechanisms run. The removed worktree's own `git fsmonitor--daemon` (git's per-worktree filesystem watcher under `core.fsmonitor=true`, which would leak once its worktree is gone) is sent `git fsmonitor--daemon stop`, then force-terminated (`SIGTERM`, then `SIGKILL`) via the PID resolved from its IPC socket if it didn't exit. A background sweep then deletes `.git/wt/trash/` entries older than 24 hours (directories orphaned when a previous background removal was interrupted) and terminates fsmonitor daemons whose worktree no longer exists (orphans from `git worktree remove`, `rm -rf`, or a crashed `wt`)
|
||||
- `wt config state clear` — removes all worktrunk data from `.git/` (config keys, caches, markers, hints, variables, logs, stale trash)
|
||||
- `wt config shell install` — when migrating an integration to a new location, removes the file left at the old one: fish `conf.d/wt.fish` (now `functions/wt.fish`) and nushell wrappers stranded under `<config-dir>/vendor/autoload` (now `<data-dir>/vendor/autoload`). The old path is where worktrunk's own wrapper lived and is named after the command being installed, so it's taken back whole without reading it — a `conf.d/wt.fish` left in place would be sourced at startup and shadow the new wrapper anyway. Only that exact filename is touched, and each removal is printed
|
||||
- `wt config shell uninstall` — removes integration lines from bash/zsh/PowerShell rc files, and deletes worktrunk's wrapper and completion files (fish `functions/`, `conf.d/`, and `completions/`; nushell `vendor/autoload`). Uninstall takes no command name, so it lists those directories and recognizes files by worktrunk's own content markers, whatever binary name they were installed under; files without the markers are left alone. An rc file belongs to the user, so a line qualifies only where it runs the init command: one that merely mentions it, inside a comment, an `echo`, or an alias body, stays. Every line uninstall does take is printed, before removal and again after
|
||||
@@ -279,7 +265,7 @@ Clear with `wt config state logs clear`.
|
||||
|
||||
## Does Worktrunk work on Windows?
|
||||
|
||||
Yes. Core commands, shell integration, and tab completion work in both Git Bash and PowerShell. See [installation](/worktrunk/#install) for setup details, including avoiding the Windows Terminal `wt` conflict.
|
||||
Yes. Core commands, shell integration, and tab completion work in both Git Bash and PowerShell. See [installation](/#install) for setup details, including avoiding the Windows Terminal `wt` conflict.
|
||||
|
||||
**Git for Windows required** — Hooks use bash syntax and execute via Git Bash, so [Git for Windows](https://gitforwindows.org/) must be installed even when PowerShell is the interactive shell.
|
||||
|
||||
@@ -295,11 +281,7 @@ For full details on the detection mechanism, see `wt config state default-branch
|
||||
|
||||
## My `for-each` or `--execute` alias prints the same value in every worktree
|
||||
|
||||
An alias body renders once at dispatch, in the invoking worktree's context, so a per-worktree variable like `{{ branch }}` is baked to that one worktree's value before the nested `wt` command iterates. Every worktree then sees the same value.
|
||||
|
||||
Confirm it with `wt config alias dry-run <name>`: if the value is already substituted (e.g. `… echo branch=main`), it was baked at dispatch.
|
||||
|
||||
To defer a variable to the nested command, wrap it as `{% raw %}{{ branch }}{% endraw %}`; for `wt step for-each`, also keep it inside a quoted `sh -c '…'` so the alias's shell doesn't word-split it. See [deferring expansion in an alias](/extending/#deferring-expansion-to-a-nested-wt-command). A repo-level variable like `{{ default_branch }}` is unaffected — it is identical in every worktree.
|
||||
The alias body rendered once at dispatch, baking the variable to the invoking worktree's value before the nested `wt` command iterated. See [deferring expansion to a nested `wt` command](/extending/#deferring-expansion-to-a-nested-wt-command) for how to confirm it and how to defer the variable.
|
||||
|
||||
## What system dependencies are required?
|
||||
|
||||
@@ -313,26 +295,6 @@ cargo install worktrunk --no-default-features --features cli
|
||||
|
||||
This disables bash syntax highlighting in command output but keeps all core functionality. The syntax highlighting feature requires C99 compiler support and can fail on older systems or minimal Docker images.
|
||||
|
||||
## Running tests (for contributors)
|
||||
|
||||
### Quick tests
|
||||
|
||||
```bash
|
||||
cargo test
|
||||
```
|
||||
|
||||
### Full integration tests
|
||||
|
||||
Shell integration tests require bash, zsh, fish, nushell, and pwsh, plus `jq`:
|
||||
|
||||
```bash
|
||||
cargo test --test integration --features shell-integration-tests
|
||||
```
|
||||
|
||||
## How can I contribute?
|
||||
|
||||
- Star the repo
|
||||
- Try it out and [open an issue](https://github.com/max-sixty/worktrunk/issues) with feedback — even small annoyances
|
||||
- What worktree friction does Worktrunk not yet solve? [Tell us](https://github.com/max-sixty/worktrunk/issues)
|
||||
- Send to a friend
|
||||
- Post about it on [X](https://twitter.com/intent/tweet?text=Worktrunk%20%E2%80%94%20CLI%20for%20git%20worktree%20management&url=https%3A%2F%2Fworktrunk.dev), [Reddit](https://www.reddit.com/submit?url=https%3A%2F%2Fworktrunk.dev&title=Worktrunk%20%E2%80%94%20CLI%20for%20git%20worktree%20management), or [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fworktrunk.dev)
|
||||
See [Contributing](https://github.com/max-sixty/worktrunk#contributing) in the README — feedback, share links, and how to run the test suite.
|
||||
|
||||
@@ -15,6 +15,7 @@ Shows uncommitted changes, divergence from the default branch and remote, and op
|
||||
<source srcset="/assets/docs/dark/wt-list.gif" media="(prefers-color-scheme: dark)">
|
||||
<img src="/assets/docs/light/wt-list.gif" alt="wt list demo" width="1600" height="900">
|
||||
</picture>
|
||||
<figcaption>Progressive rendering, then --full and --branches</figcaption>
|
||||
</figure>
|
||||
|
||||
The table renders progressively: branch names, paths, and commit hashes appear immediately, then status, divergence, and other columns fill in as background git operations complete.
|
||||
@@ -623,24 +624,6 @@ Options:
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
<!-- END AUTO-GENERATED -->
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: "LLM Commit Messages"
|
||||
description: "Generate commit messages from diffs using any LLM. Integrates with wt merge, wt step commit, and wt step squash."
|
||||
sidebar:
|
||||
order: 22
|
||||
order: 23
|
||||
---
|
||||
Worktrunk generates commit messages by building a templated prompt and piping it to an external command. This integrates with `wt merge`, `wt step commit`, and `wt step squash`.
|
||||
|
||||
@@ -11,6 +11,7 @@ Worktrunk generates commit messages by building a templated prompt and piping it
|
||||
<source srcset="/assets/docs/dark/wt-commit.gif" media="(prefers-color-scheme: dark)">
|
||||
<img src="/assets/docs/light/wt-commit.gif" alt="LLM commit message generation demo" width="1600" height="900">
|
||||
</picture>
|
||||
<figcaption>Reviewing the staged diff, generating a message, and committing</figcaption>
|
||||
</figure>
|
||||
|
||||
## Setup
|
||||
@@ -124,7 +125,7 @@ With `summary = true` and a `[commit.generation] command` configured, Worktrunk
|
||||
|
||||
Summaries appear in:
|
||||
|
||||
- **`wt switch`** [interactive picker](/switch/#interactive-picker) — preview tab 5
|
||||
- **`wt switch`** [interactive picker](/switch/#interactive-picker) — the `summary` preview tab
|
||||
- **`wt list --full`** — the Summary column (see [`wt list`](/list/#llm-summaries))
|
||||
|
||||
Enable in user config:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: "wt merge"
|
||||
description: "Merge current branch into the target branch. Squash & rebase, fast-forward the target branch, remove the worktree."
|
||||
sidebar:
|
||||
order: 13
|
||||
order: 12
|
||||
---
|
||||
<!-- ⚠️ AUTO-GENERATED from `wt merge --help-page` — edit src/cli/mod.rs to update -->
|
||||
|
||||
@@ -15,6 +15,7 @@ Unlike `git merge`, this merges the current branch into the target branch — no
|
||||
<source srcset="/assets/docs/dark/wt-merge.gif" media="(prefers-color-scheme: dark)">
|
||||
<img src="/assets/docs/light/wt-merge.gif" alt="wt merge demo" width="1600" height="900">
|
||||
</picture>
|
||||
<figcaption>Creating a worktree, committing in it, and merging it away</figcaption>
|
||||
</figure>
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: "wt remove"
|
||||
description: "Remove worktree; delete branch if merged. Defaults to the current worktree."
|
||||
sidebar:
|
||||
order: 12
|
||||
order: 13
|
||||
---
|
||||
<!-- ⚠️ AUTO-GENERATED from `wt remove --help-page` — edit src/cli/mod.rs to update -->
|
||||
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
---
|
||||
title: "Shell integration"
|
||||
description: "How Worktrunk's shell wrapper changes the parent shell's directory, which files each shell gets, and how to debug integration that isn't working."
|
||||
sidebar:
|
||||
order: 25
|
||||
---
|
||||
Shell integration is what lets `wt switch` change your shell's directory. This page covers how it works, what it installs, and how to fix it when it doesn't.
|
||||
|
||||
## Why shell integration exists
|
||||
|
||||
A subprocess cannot change its parent shell's directory. When `wt switch feature` runs, the `wt` binary is a child process and cannot `cd` the terminal.
|
||||
|
||||
Worktrunk solves this with a file directive: the shell wrapper creates one temp file, `wt` writes the target directory to it, and the wrapper changes directory after `wt` exits. `--execute` runs directly inside `wt`. See [How the shell wrapper works](#how-the-shell-wrapper-works) for the steps and a simplified implementation.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Auto-install for all shells (bash, zsh, fish, nushell (experimental), PowerShell)
|
||||
wt config shell install
|
||||
|
||||
# Or manual installation - add to the shell config:
|
||||
# bash (~/.bashrc):
|
||||
eval "$(wt config shell init bash)"
|
||||
|
||||
# zsh (~/.zshrc):
|
||||
eval "$(wt config shell init zsh)"
|
||||
|
||||
# fish (~/.config/fish/config.fish):
|
||||
wt config shell init fish | source
|
||||
|
||||
# nushell (experimental) — save to vendor autoload directory:
|
||||
wt config shell init nu | save -f ($nu.vendor-autoload-dirs | last | path join wt.nu)
|
||||
|
||||
# PowerShell ($PROFILE):
|
||||
Invoke-Expression (& wt config shell init powershell | Out-String)
|
||||
```
|
||||
|
||||
## Files created
|
||||
|
||||
`wt config shell install` writes:
|
||||
|
||||
- **Bash**: adds a line to `~/.bashrc`
|
||||
- **Zsh**: adds a line to `~/.zshrc` (or `$ZDOTDIR/.zshrc`)
|
||||
- **Fish**: creates `~/.config/fish/functions/wt.fish` and `~/.config/fish/completions/wt.fish`
|
||||
- **Nushell** <span class="badge-experimental"></span>: creates `wt.nu` in Nushell's user vendor-autoload directory — the last entry of `$nu.vendor-autoload-dirs`, under `$nu.data-dir` (typically `~/.local/share/nushell/vendor/autoload` on Linux, `~/Library/Application Support/nushell/vendor/autoload` on macOS)
|
||||
- **PowerShell** (Windows): creates both profile files if they don't exist:
|
||||
- `Documents/PowerShell/Microsoft.PowerShell_profile.ps1` (PowerShell 7+)
|
||||
- `Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1` (Windows PowerShell 5.1)
|
||||
|
||||
Fish and Nushell wrappers live at a path named after the command, so install writes that file whole, replacing an existing `functions/wt.fish`, `completions/wt.fish`, or `wt.nu`. Bash, zsh, and PowerShell rc files hold the rest of a shell's setup, so install only appends a line to those.
|
||||
|
||||
**PowerShell detection on Windows:** When running from cmd.exe or PowerShell, both PowerShell profile files are created automatically. When running from Git Bash or MSYS2, PowerShell is skipped (use `wt config shell install powershell` to create the profiles explicitly).
|
||||
|
||||
**To remove:** `wt config shell uninstall`.
|
||||
|
||||
## Checking status
|
||||
|
||||
```bash
|
||||
# Show shell integration status
|
||||
wt config show
|
||||
```
|
||||
|
||||
The RUNTIME section shows whether shell integration is active for the current session.
|
||||
|
||||
## Warning messages
|
||||
|
||||
When shell integration isn't working, `wt switch` shows a warning explaining why.
|
||||
|
||||
### "shell wrapper is out of date"
|
||||
|
||||
**Meaning**: The active shell still has a retired wrapper loaded. Current versions no longer write to that wrapper's single directive file, so the parent shell cannot follow a directory change.
|
||||
|
||||
**Fix**: Run `wt config shell install`, then restart the shell (or reload its config) to activate the current wrapper.
|
||||
|
||||
### "shell integration not installed"
|
||||
|
||||
**Meaning**: The current shell's config file doesn't have the `eval "$(wt config shell init ...)"` line. The current shell is detected from the process tree (falling back to `$SHELL`), so this refers to the shell `wt` was actually invoked from, not necessarily the login shell.
|
||||
|
||||
**Fix**: Run `wt config shell install` or add the line manually.
|
||||
|
||||
### "shell integration installed but not active"
|
||||
|
||||
**Meaning**: Shell integration is configured for the current shell, but the shell function isn't loaded in this session — usually because the session was started before installation.
|
||||
|
||||
**Fix**: Start a new terminal or run `source ~/.bashrc` (or equivalent). If the message persists after a restart, `wt config show` reports the detected shell, `$SHELL`, and per-shell integration status.
|
||||
|
||||
### "ran ./path/to/wt; shell integration wraps wt"
|
||||
|
||||
**Meaning**: The binary was invoked with an explicit path (like `./target/debug/wt` or `/usr/local/bin/wt`) instead of just `wt`. The shell wrapper only intercepts the bare command `wt`.
|
||||
|
||||
**Fix**: Use `wt` without a path. For testing dev builds, set `WORKTRUNK_BIN`:
|
||||
|
||||
```bash
|
||||
export WORKTRUNK_BIN=./target/debug/wt
|
||||
wt switch feature # Now uses the dev build with shell integration
|
||||
```
|
||||
|
||||
### "ran git wt; running through git prevents cd"
|
||||
|
||||
**Meaning**: `git wt` (git alias) was used instead of `wt`. Git runs worktrunk as a subprocess, bypassing the shell wrapper.
|
||||
|
||||
**Fix**: Use `wt` directly instead of `git wt` when directory switching is needed.
|
||||
|
||||
### "Alias bypasses shell integration"
|
||||
|
||||
**Meaning**: An alias like `alias gwt="/usr/bin/wt"` or `alias gwt="wt.exe"` points directly to the binary instead of the shell function.
|
||||
|
||||
When shell integration is installed, it creates a shell function named `wt` (or `git-wt`). If the alias points to the binary path, it bypasses this function and shell integration won't work.
|
||||
|
||||
**Examples that bypass** (won't auto-cd):
|
||||
|
||||
```bash
|
||||
alias gwt="/usr/bin/wt"
|
||||
alias gwt="wt.exe"
|
||||
alias wt="/path/to/wt"
|
||||
```
|
||||
|
||||
**Fix**: Change the alias to point to the function name instead of the binary:
|
||||
|
||||
```bash
|
||||
alias gwt="wt" # Good - uses the shell function
|
||||
alias gwt="git-wt" # Good - uses the shell function
|
||||
```
|
||||
|
||||
`wt config show` detects these problematic aliases and shows a warning with the suggested fix.
|
||||
|
||||
## How the shell wrapper works
|
||||
|
||||
The shell wrapper (installed by `wt config shell install`) defines a shell function that:
|
||||
|
||||
1. Creates a temp file
|
||||
2. Sets `WORKTRUNK_DIRECTIVE_CD_FILE`
|
||||
3. Runs the real `wt` binary
|
||||
4. Reads the CD file with `cd -- "$(< file)"` (raw path, no shell parsing)
|
||||
5. Cleans up the temp file
|
||||
|
||||
Simplified example (the actual wrapper also handles completions and edge cases):
|
||||
|
||||
```bash
|
||||
wt() {
|
||||
local cd_file exit_code=0
|
||||
cd_file="$(mktemp)"
|
||||
|
||||
WORKTRUNK_DIRECTIVE_CD_FILE="$cd_file" \
|
||||
command wt "$@" || exit_code=$?
|
||||
|
||||
if [[ -s "$cd_file" ]]; then
|
||||
cd -- "$(<"$cd_file")"
|
||||
fi
|
||||
rm -f "$cd_file"
|
||||
return "$exit_code"
|
||||
}
|
||||
```
|
||||
|
||||
## Debugging checklist
|
||||
|
||||
### 1. Check whether the wrapper is loaded
|
||||
|
||||
```bash
|
||||
# Should show a shell function, not a binary path
|
||||
type wt
|
||||
|
||||
# Expected output (bash/zsh):
|
||||
# wt is a function
|
||||
# wt () { ... }
|
||||
|
||||
# If it shows a path like /usr/local/bin/wt, the wrapper isn't loaded
|
||||
```
|
||||
|
||||
### 2. Check whether the wrapper is loaded (PowerShell)
|
||||
|
||||
```powershell
|
||||
# PowerShell: should show Function, not just Application
|
||||
Get-Command wt -All
|
||||
|
||||
# Expected output when the wrapper is loaded:
|
||||
# CommandType Name Source
|
||||
# ----------- ---- ------
|
||||
# Function wt
|
||||
# Application wt C:\Users\...\wt.exe
|
||||
|
||||
# If only Application appears, the wrapper isn't loaded (restart the shell)
|
||||
# If Function appears but integration is still "not active", check the body:
|
||||
(Get-Command wt -CommandType Function).ScriptBlock | Select-String WORKTRUNK
|
||||
```
|
||||
|
||||
### 3. Check the shell config file
|
||||
|
||||
```bash
|
||||
# bash
|
||||
grep -n "wt config shell init" ~/.bashrc
|
||||
|
||||
# zsh
|
||||
grep -n "wt config shell init" ~/.zshrc
|
||||
|
||||
# fish
|
||||
grep -n "wt config shell init" ~/.config/fish/config.fish
|
||||
```
|
||||
|
||||
This should show the `eval` line with its line number.
|
||||
|
||||
### 4. Check whether directive files are set
|
||||
|
||||
```bash
|
||||
# After running any wt command, this should be unset (the temp file is deleted)
|
||||
echo $WORKTRUNK_DIRECTIVE_CD_FILE
|
||||
|
||||
# During wt execution, this is set to a temp file path
|
||||
```
|
||||
|
||||
### 5. Test directive files manually
|
||||
|
||||
```bash
|
||||
# Create the temp file and test
|
||||
export WORKTRUNK_DIRECTIVE_CD_FILE=$(mktemp)
|
||||
command wt switch feature
|
||||
cat $WORKTRUNK_DIRECTIVE_CD_FILE # Should contain: /path/to/worktree (raw path)
|
||||
cd -- "$(<$WORKTRUNK_DIRECTIVE_CD_FILE)" # Should cd you there
|
||||
rm -f $WORKTRUNK_DIRECTIVE_CD_FILE
|
||||
```
|
||||
|
||||
## Common issues
|
||||
|
||||
### Shell integration works in the terminal but not in an IDE terminal
|
||||
|
||||
IDE terminals may use different shell configs. Check:
|
||||
|
||||
- VS Code: Settings → Terminal → Integrated → Shell Args
|
||||
- The IDE terminal might source a different profile
|
||||
|
||||
### Completions not working
|
||||
|
||||
Completions are installed alongside shell integration. If they're missing:
|
||||
|
||||
```bash
|
||||
# Reinstall (forces regeneration)
|
||||
wt config shell install
|
||||
|
||||
# For zsh, you may need compinit before the wt line:
|
||||
autoload -Uz compinit && compinit
|
||||
eval "$(wt config shell init zsh)"
|
||||
```
|
||||
|
||||
### Windows Git Bash issues
|
||||
|
||||
Git Bash uses MSYS2, which automatically converts POSIX paths in environment variables. The directive file path is handled correctly without manual conversion.
|
||||
|
||||
If you see path issues, make sure you're on a recent Git for Windows version.
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `WORKTRUNK_DIRECTIVE_CD_FILE` | Set by the shell wrapper; `wt` writes a raw path, the wrapper `cd`s to it |
|
||||
| `WORKTRUNK_BIN` | Override the binary path (for testing dev builds) |
|
||||
| `WORKTRUNK_COMPLETE_NAME` | Set by the bash, zsh, and PowerShell wrappers when they load completions; names the command the registration binds to, so `--cmd` integrations complete |
|
||||
|
||||
## See also
|
||||
|
||||
- [`wt config`](/config/#shell-integration) — shell integration commands
|
||||
- [FAQ: What files does Worktrunk create?](/faq/#what-files-does-worktrunk-create)
|
||||
@@ -188,24 +188,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step squash
|
||||
@@ -292,24 +274,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step rebase
|
||||
@@ -371,24 +335,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step push
|
||||
@@ -441,24 +387,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step diff
|
||||
@@ -529,24 +457,6 @@ Options:
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step copy-ignored
|
||||
@@ -698,24 +608,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step eval
|
||||
@@ -795,24 +687,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step for-each
|
||||
@@ -877,24 +751,6 @@ Options:
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step promote
|
||||
@@ -971,24 +827,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step prune
|
||||
@@ -1003,11 +841,11 @@ Locked worktrees and the main worktree are always skipped. The current worktree
|
||||
|
||||
### Min-age guard
|
||||
|
||||
Worktrees and branches younger than `--min-age` (default: 1 day) are skipped. This prevents removing a worktree just created from the default branch — it looks "merged" because its branch points at the same commit.
|
||||
Candidates younger than `--min-age` (default: 1 day) are skipped. A worktree's age comes from its creation time, and a branch with no worktree takes its age from its oldest reflog entry. This prevents removing a worktree just created from the default branch: it looks "merged" because its branch points at the same commit.
|
||||
|
||||
```console
|
||||
$ wt step prune --min-age=0s # no age guard
|
||||
$ wt step prune --min-age=2d # skip worktrees younger than 2 days
|
||||
$ wt step prune --min-age=2d # skip candidates younger than 2 days
|
||||
```
|
||||
|
||||
### JSON output
|
||||
@@ -1055,24 +893,6 @@ Options:
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step relocate
|
||||
@@ -1176,24 +996,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step tether
|
||||
@@ -1261,24 +1063,6 @@ Arguments:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
<!-- END AUTO-GENERATED -->
|
||||
|
||||
@@ -15,6 +15,7 @@ Worktrees are addressed by branch name; paths are computed from a configurable t
|
||||
<source srcset="/assets/docs/dark/wt-switch.gif" media="(prefers-color-scheme: dark)">
|
||||
<img src="/assets/docs/light/wt-switch.gif" alt="wt switch demo" width="1600" height="900">
|
||||
</picture>
|
||||
<figcaption>Switching to a worktree, creating one, and creating one that launches an agent</figcaption>
|
||||
</figure>
|
||||
|
||||
## Examples
|
||||
@@ -87,6 +88,7 @@ The CI column shows each row's PR/MR CI and review status, the same as [`wt list
|
||||
<source srcset="/assets/docs/dark/wt-switch-picker.gif" media="(prefers-color-scheme: dark)">
|
||||
<img src="/assets/docs/light/wt-switch-picker.gif" alt="wt switch picker demo" width="1600" height="800">
|
||||
</picture>
|
||||
<figcaption>Browsing the picker, scrolling a preview, filtering, and selecting</figcaption>
|
||||
</figure>
|
||||
|
||||
**Keybindings:**
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
---
|
||||
title: "Tips & Patterns"
|
||||
description: "Practical recipes for Worktrunk workflows: aliases, shell integration, Zellij layouts, and parallel agent patterns."
|
||||
description: "Practical recipes for Worktrunk workflows: repo layout, aliases and hooks, per-worktree dev servers and databases, agent handoffs, and branch status."
|
||||
sidebar:
|
||||
order: 24
|
||||
---
|
||||
Practical recipes for common Worktrunk workflows.
|
||||
|
||||
## Shell alias for new worktree + agent
|
||||
## Setup and layout
|
||||
|
||||
### Shell alias for new worktree + agent
|
||||
|
||||
Create a worktree and launch Claude in one command:
|
||||
|
||||
@@ -16,7 +18,106 @@ wsc new-feature # Creates worktree, runs hooks, launches C
|
||||
wsc feature -- 'Fix GH #322' # Runs `claude 'Fix GH #322'`
|
||||
```
|
||||
|
||||
## `wt` aliases
|
||||
### Shortcuts
|
||||
|
||||
Special arguments work across all commands—see [`wt switch`](/switch/#shortcuts) for the full list.
|
||||
|
||||
```bash
|
||||
wt switch --create hotfix --base=@ # Branch from current HEAD
|
||||
wt switch - # Switch to previous worktree
|
||||
wt remove @ # Remove current worktree
|
||||
```
|
||||
|
||||
### Stacked branches
|
||||
|
||||
Branch from current HEAD instead of the default branch:
|
||||
|
||||
```bash
|
||||
wt switch --create feature-part2 --base=@
|
||||
```
|
||||
|
||||
### Reuse `default-branch`
|
||||
|
||||
Default branch [detection](/config/#wt-config-state-default-branch) means scripts work on any repo — no need to hardcode `main` or `master`:
|
||||
|
||||
```bash
|
||||
git rebase $(wt config state default-branch)
|
||||
```
|
||||
|
||||
In hooks and aliases, the same value is the `{{ default_branch }}` [template variable](/hook/#template-variables); reserve this command for plain shell scripts.
|
||||
|
||||
### Override `default-branch` for one clone
|
||||
|
||||
When the integration branch differs from the remote's `HEAD`, set a [clone-local override](/config/#wt-config-state-default-branch):
|
||||
|
||||
```bash
|
||||
wt config state default-branch set integration
|
||||
```
|
||||
|
||||
### Bare repository layout
|
||||
|
||||
A [bare repository](https://git-scm.com/docs/gitrepository-layout) has no working tree, so all branches — including the default — are [linked worktrees](https://git-scm.com/docs/git-worktree) at equal paths. No branch gets special treatment.
|
||||
|
||||
Cloning a bare repo into `<project>/.git` puts all worktrees under one directory:
|
||||
|
||||
```bash
|
||||
git clone --bare <url> myproject/.git
|
||||
cd myproject
|
||||
```
|
||||
|
||||
With `worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}"`, worktrees become subdirectories of `myproject/`:
|
||||
|
||||
```
|
||||
myproject/
|
||||
├── .git/ # bare repository
|
||||
├── main/ # default branch worktree
|
||||
├── feature/ # feature branch worktree
|
||||
└── bugfix/ # bugfix branch worktree
|
||||
```
|
||||
|
||||
#### Configure the worktree path
|
||||
|
||||
On first `wt switch` in a bare repo at a hidden path (`.git`, `.bare`), worktrunk detects that the default template would produce broken paths like `myproject/.git.main` and offers a fix:
|
||||
|
||||
```
|
||||
▲ Bare repo at myproject/.git — worktrees will be at myproject/.git.main
|
||||
◎ Configure worktree-path to place worktrees at myproject/main? [y/N/?]
|
||||
```
|
||||
|
||||
Accepting writes a project-scoped entry to user config:
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[projects."github.com/myorg/myrepo"]
|
||||
worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}"
|
||||
```
|
||||
|
||||
Run `wt config show` from inside any worktree to find the project identifier (`Identifier: …` in the PROJECT CONFIG section). Set it globally with `worktree-path = "..."` at the top level if this layout is preferred for all bare repos.
|
||||
|
||||
#### Create the first worktree
|
||||
|
||||
```bash
|
||||
wt switch main
|
||||
```
|
||||
|
||||
For a freshly cloned bare repo the default branch already exists, so `wt switch main` (without `--create`) is enough. Use `wt switch --create <branch>` for new branches.
|
||||
|
||||
Now `wt switch --create feature` creates `myproject/feature/`.
|
||||
|
||||
#### Set up the project config
|
||||
|
||||
The project config (`.config/wt.toml`) must live inside a worktree — the bare `.git` directory has no tracked files. Once the first worktree exists, create it from there:
|
||||
|
||||
```bash
|
||||
cd myproject/main
|
||||
wt config create --project
|
||||
```
|
||||
|
||||
Commit the file and it will appear in every worktree automatically.
|
||||
|
||||
## Aliases and hooks
|
||||
|
||||
### `wt` aliases
|
||||
|
||||
Compose with template filters and [vars](/tips-patterns/#per-branch-variables):
|
||||
|
||||
@@ -35,7 +136,7 @@ pick = "wt switch --format=json | jq -r '.branch'"
|
||||
|
||||
See [Aliases](/extending/#aliases) for scoping, approval, and reference.
|
||||
|
||||
## Per-branch variables
|
||||
### Per-branch variables
|
||||
|
||||
`wt config state vars` holds state per branch, accessible from templates (`{{ vars.key }}`) and the CLI. Some uses:
|
||||
|
||||
@@ -45,7 +146,53 @@ See [Aliases](/extending/#aliases) for scoping, approval, and reference.
|
||||
|
||||
See [`wt config state vars`](/config/#wt-config-state-vars) for storage format, JSON support, and reference.
|
||||
|
||||
## Dev server per worktree
|
||||
### Task runners in hooks
|
||||
|
||||
Reference Taskfile/Justfile/Makefile in hooks:
|
||||
|
||||
```toml
|
||||
[pre-start]
|
||||
"setup" = "task install"
|
||||
|
||||
[pre-merge]
|
||||
"validate" = "just test lint"
|
||||
```
|
||||
|
||||
### Progressive validation
|
||||
|
||||
Split checks across hook types — quick feedback before each commit, expensive suites before merge:
|
||||
|
||||
```toml
|
||||
[[pre-commit]]
|
||||
lint = "npm run lint"
|
||||
typecheck = "npm run typecheck"
|
||||
|
||||
[[pre-merge]]
|
||||
test = "npm test"
|
||||
build = "npm run build"
|
||||
```
|
||||
|
||||
`pre-commit` runs during `wt merge`, before the squash commit; `pre-merge` runs once per merge after the rebase, so it's the right place for the slow tests.
|
||||
|
||||
### Target-specific hooks
|
||||
|
||||
Branch on `{{ target }}` to vary behavior per merge destination — for example, deploying to production from `main` and staging from a release branch:
|
||||
|
||||
```toml
|
||||
post-merge = """
|
||||
if [ {{ target }} = main ]; then
|
||||
npm run deploy:production
|
||||
elif [ {{ target }} = staging ]; then
|
||||
npm run deploy:staging
|
||||
fi
|
||||
"""
|
||||
```
|
||||
|
||||
`{{ target }}` is the branch being merged into. `post-merge` runs in the target's worktree (or the primary worktree if target has none), so deploy commands see the merged code.
|
||||
|
||||
## Per-worktree services
|
||||
|
||||
### Dev server per worktree
|
||||
|
||||
Each worktree runs its own dev server on a deterministic port. The `hash_port` filter generates a stable port (10000-19999) from the branch name:
|
||||
|
||||
@@ -79,7 +226,7 @@ $ wt list
|
||||
|
||||
`fix-auth` always gets port 16460, on any machine. The URL dims if the server isn't running.
|
||||
|
||||
## Database per worktree
|
||||
### Database per worktree
|
||||
|
||||
Each worktree can have its own isolated database. A pipeline sets up names and ports as [vars](/config/#wt-config-state-vars), then later steps and hooks reference them:
|
||||
|
||||
@@ -116,7 +263,7 @@ The connection string is accessible anywhere — not just in hooks:
|
||||
DATABASE_URL=$(wt config state vars get db_url) npm start
|
||||
```
|
||||
|
||||
## Per-worktree env vars
|
||||
### Per-worktree env vars
|
||||
|
||||
To scope environment variables to a worktree — a tool's package path, a profile, an API endpoint — use a directory environment manager like [direnv](https://direnv.net) or [mise](https://mise.jdx.dev). Both hook the shell prompt, so they activate on the `cd` that `wt switch` already performs — no worktrunk configuration needed. Commit the config at the repo root and every worktree gets its own copy, with paths resolving relative to that worktree.
|
||||
|
||||
@@ -139,7 +286,7 @@ MY_PACKAGES_PATH = "{{ config_root }}/.packages"
|
||||
|
||||
Both set real environment variables in the shell session, so every child process inherits them — hooks, build tools, subshells — without the `--execute` workaround. Each new worktree is a new path, so it needs its own one-time trust step (`direnv allow` / `mise trust`); worktrunk deliberately doesn't bypass that prompt, the same safety reasoning behind [disabling `--execute` in project alias and hook bodies](https://github.com/max-sixty/worktrunk/issues/2101).
|
||||
|
||||
## Eliminate cold starts
|
||||
### Eliminate cold starts
|
||||
|
||||
Use [`wt step copy-ignored`](/step/#wt-step-copy-ignored) to copy gitignored files (caches, dependencies, `.env`) between worktrees:
|
||||
|
||||
@@ -162,184 +309,43 @@ Use `pre-start` instead when an `--execute` command needs the copied files immed
|
||||
|
||||
All gitignored files are copied by default. To limit what gets copied, create `.worktreeinclude` with patterns — files must be both gitignored and listed. See [`wt step copy-ignored`](/step/#wt-step-copy-ignored) for details.
|
||||
|
||||
## Manual commit messages
|
||||
### Subdomain routing with Caddy
|
||||
|
||||
The `commit.generation.command` receives the rendered prompt on stdin and returns the commit message on stdout. To write commit messages by hand instead of using an LLM, point it at `$EDITOR`:
|
||||
<!-- Hand-tested 2026-03-07 -->
|
||||
|
||||
Clean URLs like `http://feature-auth.myproject.localhost` without port numbers. Useful for cookies, CORS, and matching production URL structure.
|
||||
|
||||
**Prerequisites:** [Caddy](https://caddyserver.com/docs/install) (`brew install caddy`)
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[commit.generation]
|
||||
command = '''f=$(mktemp); printf '\n\n' > "$f"; sed 's/^/# /' >> "$f"; ${EDITOR:-vi} "$f" < /dev/tty > /dev/tty; grep -v '^#' "$f"'''
|
||||
```
|
||||
|
||||
This comments out the rendered prompt (diff, branch name, stats) with `#` prefixes, opens your editor, and strips comment lines on save. A couple of blank lines at the top give you space to type; the prompt context is visible below for reference.
|
||||
|
||||
To keep the LLM as default but use the editor for a specific merge, add a [worktrunk alias](/extending/#aliases):
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[aliases]
|
||||
mc = '''WORKTRUNK_COMMIT__GENERATION__COMMAND='f=$(mktemp); printf "\n\n" > "$f"; sed "s/^/# /" >> "$f"; ${EDITOR:-vi} "$f" < /dev/tty > /dev/tty; grep -v "^#" "$f"' wt merge'''
|
||||
```
|
||||
|
||||
Then `wt mc` opens an editor for the commit message while plain `wt merge` continues to use the LLM.
|
||||
|
||||
## Track agent status
|
||||
|
||||
Custom emoji markers show agent state in `wt list`. The [Claude Code](/claude-code/) plugin and [OpenCode plugin](https://github.com/max-sixty/worktrunk/tree/main/dev/opencode-plugin.ts) set these automatically:
|
||||
|
||||
```
|
||||
+ feature-api ↑ 🤖 ↑1 ./repo.feature-api
|
||||
+ review-ui ? ↑ 💬 ↑1 ./repo.review-ui
|
||||
```
|
||||
|
||||
- `🤖` — Agent is working
|
||||
- `💬` — Agent is waiting for input
|
||||
|
||||
Set status manually for any workflow:
|
||||
|
||||
```bash
|
||||
wt config state marker set "🚧" # Current branch
|
||||
wt config state marker set "✅" --branch feature # Specific branch
|
||||
git config worktrunk.state.feature.marker '{"marker":"💬","set_at":0}' # Direct
|
||||
```
|
||||
|
||||
See [Claude Code Integration](/claude-code/#installation) for plugin installation.
|
||||
|
||||
## Monitor CI across branches
|
||||
|
||||
```bash
|
||||
wt list --full --branches
|
||||
```
|
||||
|
||||
Shows PR/CI status for all branches, including those without worktrees. CI indicators are clickable links to the PR page.
|
||||
|
||||
## LLM branch summaries
|
||||
|
||||
With `summary = true` and [`commit.generation`](/config/#commit) configured, `wt list --full` shows an LLM-generated one-line summary for each branch. The same summaries appear in the `wt switch` picker (tab 5).
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[list]
|
||||
summary = true
|
||||
```
|
||||
|
||||
See [LLM Commits](/llm-commits/#branch-summaries) for details.
|
||||
|
||||
## JSON API
|
||||
|
||||
```bash
|
||||
wt list --format=json
|
||||
```
|
||||
|
||||
Structured output for dashboards, statuslines, and scripts. See [`wt list`](/list/) for query examples.
|
||||
|
||||
## Reuse `default-branch`
|
||||
|
||||
Default branch [detection](/config/#wt-config-state-default-branch) means scripts work on any repo — no need to hardcode `main` or `master`:
|
||||
|
||||
```bash
|
||||
git rebase $(wt config state default-branch)
|
||||
```
|
||||
|
||||
In hooks and aliases, the same value is the `{{ default_branch }}` [template variable](/hook/#template-variables); reserve this command for plain shell scripts.
|
||||
|
||||
## Override `default-branch` for one clone
|
||||
|
||||
When the integration branch differs from the remote's `HEAD`, set a [clone-local override](/config/#wt-config-state-default-branch):
|
||||
|
||||
```bash
|
||||
wt config state default-branch set integration
|
||||
```
|
||||
|
||||
## Task runners in hooks
|
||||
|
||||
Reference Taskfile/Justfile/Makefile in hooks:
|
||||
|
||||
```toml
|
||||
[pre-start]
|
||||
"setup" = "task install"
|
||||
|
||||
[pre-merge]
|
||||
"validate" = "just test lint"
|
||||
```
|
||||
|
||||
## Progressive validation
|
||||
|
||||
Split checks across hook types — quick feedback before each commit, expensive suites before merge:
|
||||
|
||||
```toml
|
||||
[[pre-commit]]
|
||||
lint = "npm run lint"
|
||||
typecheck = "npm run typecheck"
|
||||
|
||||
[[pre-merge]]
|
||||
test = "npm test"
|
||||
build = "npm run build"
|
||||
```
|
||||
|
||||
`pre-commit` runs during `wt merge`, before the squash commit; `pre-merge` runs once per merge after the rebase, so it's the right place for the slow tests.
|
||||
|
||||
## Target-specific hooks
|
||||
|
||||
Branch on `{{ target }}` to vary behavior per merge destination — for example, deploying to production from `main` and staging from a release branch:
|
||||
|
||||
```toml
|
||||
post-merge = """
|
||||
if [ {{ target }} = main ]; then
|
||||
npm run deploy:production
|
||||
elif [ {{ target }} = staging ]; then
|
||||
npm run deploy:staging
|
||||
fi
|
||||
# .config/wt.toml
|
||||
[post-start]
|
||||
server = "wt step tether -- npm run dev -- --port {{ branch | hash_port }}"
|
||||
proxy = """
|
||||
curl -sf --max-time 0.5 http://localhost:2019/config/ || caddy start
|
||||
curl -sf http://localhost:2019/config/apps/http/servers/wt || \
|
||||
curl -sfX PUT http://localhost:2019/config/apps/http/servers/wt -H 'Content-Type: application/json' \
|
||||
-d '{"listen":[":8080"],"automatic_https":{"disable":true},"routes":[]}'
|
||||
curl -sf -X DELETE http://localhost:2019/id/wt:{{ repo }}:{{ branch | sanitize }} || true
|
||||
curl -sfX PUT http://localhost:2019/config/apps/http/servers/wt/routes/0 -H 'Content-Type: application/json' \
|
||||
-d '{"@id":"wt:{{ repo }}:{{ branch | sanitize }}","match":[{"host":["{{ branch | sanitize }}.{{ repo }}.localhost"]}],"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"127.0.0.1:{{ branch | hash_port }}"}]}]}'
|
||||
"""
|
||||
|
||||
[pre-remove]
|
||||
proxy = "curl -sf -X DELETE http://localhost:2019/id/wt:{{ repo }}:{{ branch | sanitize }} || true"
|
||||
|
||||
[list]
|
||||
url = "http://{{ branch | sanitize }}.{{ repo }}.localhost:8080"
|
||||
```
|
||||
|
||||
`{{ target }}` is the branch being merged into. `post-merge` runs in the target's worktree (or the primary worktree if target has none), so deploy commands see the merged code.
|
||||
**How it works:**
|
||||
|
||||
## Shortcuts
|
||||
1. `wt switch --create feature-auth` runs the `post-start` hook, starting the dev server on a deterministic port (`{{ branch | hash_port }}` → 18283)
|
||||
2. The hook starts Caddy if needed and registers a route using the same port: `feature-auth.myproject` → `localhost:18283`
|
||||
3. `*.localhost` resolves to `127.0.0.1` via the OS
|
||||
4. Visiting `http://feature-auth.myproject.localhost:8080`: Caddy matches the subdomain and proxies to the dev server
|
||||
|
||||
Special arguments work across all commands—see [`wt switch`](/switch/#shortcuts) for the full list.
|
||||
|
||||
```bash
|
||||
wt switch --create hotfix --base=@ # Branch from current HEAD
|
||||
wt switch - # Switch to previous worktree
|
||||
wt remove @ # Remove current worktree
|
||||
```
|
||||
|
||||
## Stacked branches
|
||||
|
||||
Branch from current HEAD instead of the default branch:
|
||||
|
||||
```bash
|
||||
wt switch --create feature-part2 --base=@
|
||||
```
|
||||
|
||||
## Agent handoffs
|
||||
|
||||
Spawn a worktree with an agent CLI running in the background. Examples below use `claude`; for OpenCode, replace `claude` with `'opencode run'`.
|
||||
|
||||
**tmux** (new detached session):
|
||||
```bash
|
||||
tmux new-session -d -s fix-auth-bug "wt switch --create fix-auth-bug -x claude -- \
|
||||
'The login session expires after 5 minutes. Find the session timeout config and extend it to 24 hours.'"
|
||||
```
|
||||
|
||||
**Zellij** (new pane in current session):
|
||||
```bash
|
||||
zellij run -- wt switch --create fix-auth-bug -x claude -- \
|
||||
'The login session expires after 5 minutes. Find the session timeout config and extend it to 24 hours.'
|
||||
```
|
||||
|
||||
This lets one agent session hand off work to another that runs in the background. Hooks run inside the multiplexer session/pane.
|
||||
|
||||
The [worktrunk skill](/claude-code/) includes guidance for Claude Code (and other agent CLIs that load it) to execute this pattern. To enable it, request it explicitly ("spawn a parallel worktree for...") or add to your project instructions (`CLAUDE.md` or `AGENTS.md`):
|
||||
|
||||
```markdown
|
||||
When I ask you to spawn parallel worktrees, use the agent handoff pattern
|
||||
from the worktrunk skill.
|
||||
```
|
||||
|
||||
## Tmux session per worktree
|
||||
### Tmux session per worktree
|
||||
|
||||
Each worktree gets its own tmux session with a multi-pane layout.
|
||||
|
||||
@@ -375,7 +381,7 @@ To create a worktree and immediately attach:
|
||||
$ wt switch --create feature -x tmux -- attach -t '{{ branch | sanitize }}'
|
||||
```
|
||||
|
||||
## cmux workspace per worktree
|
||||
### cmux workspace per worktree
|
||||
|
||||
Each worktree gets its own [cmux](https://cmux.com) workspace. Switching worktrees switches workspaces; removing a worktree closes its workspace. Configuration contributed by [@endigma](https://github.com/endigma) ([#2796](https://github.com/max-sixty/worktrunk/issues/2796)).
|
||||
|
||||
@@ -410,7 +416,7 @@ WS=$(cmux --json list-workspaces 2>/dev/null \\
|
||||
|
||||
**Why `pre-*` instead of `post-*`?** cmux restricts socket access to processes spawned inside a cmux terminal. `post-*` hooks run as detached background processes, breaking the process ancestry chain. `pre-*` hooks run in the foreground and inherit the terminal's process lineage.
|
||||
|
||||
## Xcode DerivedData cleanup
|
||||
### Xcode DerivedData cleanup
|
||||
|
||||
Clean up Xcode's DerivedData when removing a worktree. Each DerivedData directory contains an `info.plist` recording its project path — grep for the worktree path to find and remove the matching build cache:
|
||||
|
||||
@@ -428,42 +434,90 @@ clean-derived = """
|
||||
"""
|
||||
```
|
||||
|
||||
## Subdomain routing with Caddy
|
||||
<!-- Hand-tested 2026-03-07 -->
|
||||
## Working with agents
|
||||
|
||||
Clean URLs like `http://feature-auth.myproject.localhost` without port numbers. Useful for cookies, CORS, and matching production URL structure.
|
||||
### Track agent status
|
||||
|
||||
**Prerequisites:** [Caddy](https://caddyserver.com/docs/install) (`brew install caddy`)
|
||||
The agent plugins mark each worktree 🤖 (working) or 💬 (waiting) in `wt list`, and `wt config state marker set` sets a marker by hand for any other workflow. See [Activity tracking](/claude-code/#activity-tracking).
|
||||
|
||||
```toml
|
||||
# .config/wt.toml
|
||||
[post-start]
|
||||
server = "wt step tether -- npm run dev -- --port {{ branch | hash_port }}"
|
||||
proxy = """
|
||||
curl -sf --max-time 0.5 http://localhost:2019/config/ || caddy start
|
||||
curl -sf http://localhost:2019/config/apps/http/servers/wt || \
|
||||
curl -sfX PUT http://localhost:2019/config/apps/http/servers/wt -H 'Content-Type: application/json' \
|
||||
-d '{"listen":[":8080"],"automatic_https":{"disable":true},"routes":[]}'
|
||||
curl -sf -X DELETE http://localhost:2019/id/wt:{{ repo }}:{{ branch | sanitize }} || true
|
||||
curl -sfX PUT http://localhost:2019/config/apps/http/servers/wt/routes/0 -H 'Content-Type: application/json' \
|
||||
-d '{"@id":"wt:{{ repo }}:{{ branch | sanitize }}","match":[{"host":["{{ branch | sanitize }}.{{ repo }}.localhost"]}],"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"127.0.0.1:{{ branch | hash_port }}"}]}]}'
|
||||
"""
|
||||
### Agent handoffs
|
||||
|
||||
[pre-remove]
|
||||
proxy = "curl -sf -X DELETE http://localhost:2019/id/wt:{{ repo }}:{{ branch | sanitize }} || true"
|
||||
Spawn a worktree with an agent CLI running in the background. `-x` names the program to run and everything after `--` is passed to it, so OpenCode's subcommand goes after the `--`: `-x opencode -- run '<task>'`.
|
||||
|
||||
[list]
|
||||
url = "http://{{ branch | sanitize }}.{{ repo }}.localhost:8080"
|
||||
**tmux** (new detached session):
|
||||
```bash
|
||||
tmux new-session -d -s fix-auth-bug "wt switch --create fix-auth-bug -x claude -- \
|
||||
'The login session expires after 5 minutes. Find the session timeout config and extend it to 24 hours.'"
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
**Zellij** (new pane in current session):
|
||||
```bash
|
||||
zellij run -- wt switch --create fix-auth-bug -x claude -- \
|
||||
'The login session expires after 5 minutes. Find the session timeout config and extend it to 24 hours.'
|
||||
```
|
||||
|
||||
1. `wt switch --create feature-auth` runs the `post-start` hook, starting the dev server on a deterministic port (`{{ branch | hash_port }}` → 16460)
|
||||
2. The hook starts Caddy if needed and registers a route using the same port: `feature-auth.myproject` → `localhost:16460`
|
||||
3. `*.localhost` resolves to `127.0.0.1` via the OS
|
||||
4. Visiting `http://feature-auth.myproject.localhost:8080`: Caddy matches the subdomain and proxies to the dev server
|
||||
This lets one agent session hand off work to another that runs in the background. Hooks run inside the multiplexer session/pane.
|
||||
|
||||
## Monitor hook logs
|
||||
The [worktrunk skill](/claude-code/) includes guidance for Claude Code (and other agent CLIs that load it) to execute this pattern. To enable it, request it explicitly ("spawn a parallel worktree for...") or add to your project instructions (`CLAUDE.md` or `AGENTS.md`):
|
||||
|
||||
```markdown
|
||||
When I ask you to spawn parallel worktrees, use the agent handoff pattern
|
||||
from the worktrunk skill.
|
||||
```
|
||||
|
||||
## Status, commits, and logs
|
||||
|
||||
### Monitor CI across branches
|
||||
|
||||
```bash
|
||||
wt list --full --branches
|
||||
```
|
||||
|
||||
Shows PR/CI status for all branches, including those without worktrees. CI indicators are clickable links to the PR page.
|
||||
|
||||
### LLM branch summaries
|
||||
|
||||
With `summary = true` and [`commit.generation`](/config/#commit) configured, `wt list --full` shows an LLM-generated one-line summary for each branch. The same summaries appear in the [`wt switch` picker](/switch/#interactive-picker)'s `summary` tab.
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[list]
|
||||
summary = true
|
||||
```
|
||||
|
||||
See [LLM Commits](/llm-commits/#branch-summaries) for details.
|
||||
|
||||
### JSON API
|
||||
|
||||
```bash
|
||||
wt list --format=json
|
||||
```
|
||||
|
||||
Structured output for dashboards, statuslines, and scripts. See [`wt list`](/list/) for query examples.
|
||||
|
||||
### Manual commit messages
|
||||
|
||||
The `commit.generation.command` receives the rendered prompt on stdin and returns the commit message on stdout. To write commit messages by hand instead of using an LLM, point it at `$EDITOR`:
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[commit.generation]
|
||||
command = '''f=$(mktemp); printf '\n\n' > "$f"; sed 's/^/# /' >> "$f"; ${EDITOR:-vi} "$f" < /dev/tty > /dev/tty; grep -v '^#' "$f"'''
|
||||
```
|
||||
|
||||
This comments out the rendered prompt (diff, branch name, stats) with `#` prefixes, opens your editor, and strips comment lines on save. A couple of blank lines at the top give you space to type; the prompt context is visible below for reference.
|
||||
|
||||
To keep the LLM as default but use the editor for a specific merge, add a [worktrunk alias](/extending/#aliases):
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[aliases]
|
||||
mc = '''WORKTRUNK_COMMIT__GENERATION__COMMAND='f=$(mktemp); printf "\n\n" > "$f"; sed "s/^/# /" >> "$f"; ${EDITOR:-vi} "$f" < /dev/tty > /dev/tty; grep -v "^#" "$f"' wt merge'''
|
||||
```
|
||||
|
||||
Then `wt mc` opens an editor for the commit message while plain `wt merge` continues to use the LLM.
|
||||
|
||||
### Monitor hook logs
|
||||
|
||||
Follow background hook output:
|
||||
|
||||
@@ -478,64 +532,3 @@ Create an alias for frequent use:
|
||||
```bash
|
||||
alias wtlog='f() { tail -f "$(wt config state logs get --hook="$1")"; }; f'
|
||||
```
|
||||
|
||||
## Bare repository layout
|
||||
|
||||
A [bare repository](https://git-scm.com/docs/gitrepository-layout) has no working tree, so all branches — including the default — are [linked worktrees](https://git-scm.com/docs/git-worktree) at equal paths. No branch gets special treatment.
|
||||
|
||||
Cloning a bare repo into `<project>/.git` puts all worktrees under one directory:
|
||||
|
||||
```bash
|
||||
git clone --bare <url> myproject/.git
|
||||
cd myproject
|
||||
```
|
||||
|
||||
With `worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}"`, worktrees become subdirectories of `myproject/`:
|
||||
|
||||
```
|
||||
myproject/
|
||||
├── .git/ # bare repository
|
||||
├── main/ # default branch worktree
|
||||
├── feature/ # feature branch worktree
|
||||
└── bugfix/ # bugfix branch worktree
|
||||
```
|
||||
|
||||
### Configure the worktree path
|
||||
|
||||
On first `wt switch` in a bare repo at a hidden path (`.git`, `.bare`), worktrunk detects that the default template would produce broken paths like `myproject/.git.main` and offers a fix:
|
||||
|
||||
```
|
||||
▲ Bare repo at myproject/.git — worktrees will be at myproject/.git.main
|
||||
◎ Configure worktree-path to place worktrees at myproject/main? [y/N/?]
|
||||
```
|
||||
|
||||
Accepting writes a project-scoped entry to user config:
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[projects."github.com/myorg/myrepo"]
|
||||
worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}"
|
||||
```
|
||||
|
||||
Run `wt config show` from inside any worktree to find the project identifier (`Identifier: …` in the PROJECT CONFIG section). Set it globally with `worktree-path = "..."` at the top level if this layout is preferred for all bare repos.
|
||||
|
||||
### Create the first worktree
|
||||
|
||||
```bash
|
||||
wt switch main
|
||||
```
|
||||
|
||||
For a freshly cloned bare repo the default branch already exists, so `wt switch main` (without `--create`) is enough. Use `wt switch --create <branch>` for new branches.
|
||||
|
||||
Now `wt switch --create feature` creates `myproject/feature/`.
|
||||
|
||||
### Set up the project config
|
||||
|
||||
The project config (`.config/wt.toml`) must live inside a worktree — the bare `.git` directory has no tracked files. Once the first worktree exists, create it from there:
|
||||
|
||||
```bash
|
||||
cd myproject/main
|
||||
wt config create --project
|
||||
```
|
||||
|
||||
Commit the file and it will appear in every worktree automatically.
|
||||
|
||||
@@ -41,7 +41,7 @@ A quick demo:
|
||||
|
||||
AI agents like Claude Code and Codex can handle longer tasks without
|
||||
supervision, such that it's possible to manage 5-10+ in parallel. Git's native
|
||||
worktree feature give each agent its own working directory, so they don't step
|
||||
worktree feature gives each agent its own working directory, so they don't step
|
||||
on each other's changes.
|
||||
|
||||
But the git worktree UX is clunky. Even a task as small as starting a new
|
||||
@@ -112,7 +112,7 @@ Multiple parallel agents, same simple commands:
|
||||
<figure class="demo">
|
||||
<picture>
|
||||
<source srcset="/assets/docs/dark/wt-zellij-omnibus.gif" media="(prefers-color-scheme: dark)">
|
||||
<img src="/assets/docs/light/wt-zellij-omnibus.gif" alt="Worktrunk omnibus demo: multiple Claude agents in Zellij tabs with hooks, LLM commits, and merge workflow" width="1600" height="900">
|
||||
<img src="/assets/docs/light/wt-zellij-omnibus.gif" alt="Worktrunk omnibus demo: multiple Claude agents in Zellij tabs with hooks, LLM commits, and merge workflow" width="1600" height="900" loading="lazy">
|
||||
</picture>
|
||||
<figcaption>Multiple Claude agents in parallel with interactive picker, hooks, LLM commits, and merge</figcaption>
|
||||
</figure>
|
||||
|
||||
@@ -61,6 +61,26 @@ function setCopyText(blockAst, copyText) {
|
||||
button.properties['data-code'] = copyText;
|
||||
}
|
||||
|
||||
/// Expressive Code's copy script binds every `.expressive-code .copy button`
|
||||
/// and reads `data-code` (newlines encoded as U+007F), so a control built to
|
||||
/// the same shape works wherever it is placed inside the frame.
|
||||
function copyControl(text, className) {
|
||||
return {
|
||||
type: 'element',
|
||||
tagName: 'div',
|
||||
properties: { className: ['copy', className] },
|
||||
children: [
|
||||
{ type: 'element', tagName: 'div', properties: { 'aria-live': 'polite' }, children: [] },
|
||||
{
|
||||
type: 'element',
|
||||
tagName: 'button',
|
||||
properties: { title: 'Copy this line', 'data-copied': 'Copied!', 'data-code': text },
|
||||
children: [{ type: 'element', tagName: 'div', properties: {}, children: [] }],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function removeTitlelessHeader(blockAst) {
|
||||
blockAst.children = blockAst.children.filter((child) => {
|
||||
if (
|
||||
@@ -420,6 +440,28 @@ export function pluginWorktrunkTerminal() {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 0.14em;
|
||||
}
|
||||
.expressive-code .frame.wt-line-copies .ec-line.wt-command {
|
||||
position: relative;
|
||||
}
|
||||
.expressive-code .frame.wt-line-copies .ec-line.wt-command .code {
|
||||
padding-inline-end: calc(2rem + var(--ec-codePadInl));
|
||||
}
|
||||
.expressive-code .frame .wt-line-copy {
|
||||
inset-block-start: 0;
|
||||
}
|
||||
@media (hover: hover) {
|
||||
.expressive-code .frame:hover .wt-line-copy button:not(:hover) {
|
||||
opacity: 0;
|
||||
}
|
||||
.expressive-code .frame .ec-line:hover .wt-line-copy button:not(:hover) {
|
||||
opacity: 0.75;
|
||||
}
|
||||
/* The block control and the first line's sit in the same corner, so
|
||||
hand the corner to whichever line the pointer is on. */
|
||||
.expressive-code .frame.wt-line-copies:has(.ec-line:hover) > .copy button:not(:hover) {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.expressive-code .frame.wt-command-reference .wt-help-heading {
|
||||
color: var(--wt-copper);
|
||||
font-weight: 650;
|
||||
@@ -520,6 +562,13 @@ export function pluginWorktrunkTerminal() {
|
||||
: 'wt-output';
|
||||
addClass(renderData.lineAst, className);
|
||||
const text = line?.text ?? codeBlock.getLines()[lineIndex].text;
|
||||
// A block listing several commands is as often a menu of alternatives
|
||||
// as a recipe, and nothing in the markup tells them apart — so each
|
||||
// command line gets its own control alongside the block's.
|
||||
if (className === 'wt-command' && terminal.commandLines.size > 1) {
|
||||
renderData.lineAst.children ??= [];
|
||||
renderData.lineAst.children.push(copyControl(text, 'wt-line-copy'));
|
||||
}
|
||||
if (className === 'wt-output') {
|
||||
const rendered = terminal.recordedByLine.has(lineIndex)
|
||||
&& renderRecordedOutput(renderData.lineAst, terminal.recordedByLine.get(lineIndex));
|
||||
@@ -530,6 +579,10 @@ export function pluginWorktrunkTerminal() {
|
||||
removeTitlelessHeader(renderData.blockAst);
|
||||
if (commandReferenceBlocks.has(codeBlock)) {
|
||||
addClass(renderData.blockAst, 'wt-command-reference');
|
||||
// Generated `--help` output is reference material — its copy button
|
||||
// offered thousands of characters nobody pastes anywhere.
|
||||
removeCopyControl(renderData.blockAst);
|
||||
return;
|
||||
}
|
||||
const terminal = terminalBlocks.get(codeBlock);
|
||||
if (!terminal) {
|
||||
@@ -539,6 +592,7 @@ export function pluginWorktrunkTerminal() {
|
||||
return;
|
||||
}
|
||||
if (!terminal.hasOutput) addClass(renderData.blockAst, 'wt-commands-only');
|
||||
if (terminal.commandLines.size > 1) addClass(renderData.blockAst, 'wt-line-copies');
|
||||
if (terminal.commandLines.size === 0 && terminal.copyableLines.size === 0) {
|
||||
removeCopyControl(renderData.blockAst);
|
||||
return;
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
// Sidebar order is authored here. Each page's `sidebar.order` frontmatter must
|
||||
// agree with it: the docs sync test reads that value to order
|
||||
// `docs/public/llms.txt`, and `test_sidebar_matches_frontmatter_order` fails
|
||||
// when the two disagree.
|
||||
export const sidebar = [
|
||||
{ label: 'Overview', link: '/' },
|
||||
{ label: 'Install', link: '/#install' },
|
||||
{
|
||||
label: 'Commands',
|
||||
items: [
|
||||
{ label: 'wt switch', link: '/switch/' },
|
||||
{ label: 'wt list', link: '/list/' },
|
||||
{ label: 'wt remove', link: '/remove/' },
|
||||
{ label: 'wt merge', link: '/merge/' },
|
||||
{ label: 'wt remove', link: '/remove/' },
|
||||
{ label: 'wt config', link: '/config/' },
|
||||
{ label: 'wt step', link: '/step/' },
|
||||
{ label: 'wt hook', link: '/hook/' },
|
||||
@@ -19,6 +24,7 @@ export const sidebar = [
|
||||
{ label: 'Extending Worktrunk', link: '/extending/' },
|
||||
{ label: 'LLM commit messages', link: '/llm-commits/' },
|
||||
{ label: 'Tips & patterns', link: '/tips-patterns/' },
|
||||
{ label: 'Shell integration', link: '/shell-integration/' },
|
||||
{ label: 'FAQ', link: '/faq/' },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -625,7 +625,7 @@ figure.demo {
|
||||
margin-block-start: 0.75rem;
|
||||
}
|
||||
|
||||
.wt-home figure.demo figcaption {
|
||||
figure.demo figcaption {
|
||||
margin-block-start: 0.65rem;
|
||||
color: var(--wt-ink-muted);
|
||||
font-family: var(--wt-font-display);
|
||||
@@ -754,3 +754,22 @@ figure.demo img {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Terminal output is the site's widest material — `wt list --full` renders far
|
||||
past the prose column and disappears behind a horizontal scrollbar while the
|
||||
pane beside the column sits empty. On viewports with that slack, let a
|
||||
terminal frame take the whole pane. `.content-panel` becomes the query
|
||||
container so the room available is measured rather than recomputed from
|
||||
Starlight's main-pane formula: `100cqi` is the pane's content box, and the
|
||||
frame's own `100%` is the content column it would otherwise be trapped in. */
|
||||
@media (min-width: 72rem) {
|
||||
.content-panel {
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
.sl-markdown-content .expressive-code:has(.frame.is-terminal),
|
||||
.sl-markdown-content .expressive-code:has(.frame.wt-command-reference) {
|
||||
width: 100cqi;
|
||||
margin-inline-start: min(0px, calc(100% - 100cqi));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,12 +375,13 @@ test('output-only console blocks do not expose copy controls', async () => {
|
||||
|
||||
test('command-bearing console blocks emit command-only copy payloads', async () => {
|
||||
let commandBearingBlocks = 0;
|
||||
let perLineBlocks = 0;
|
||||
for (const page of renderedPages) {
|
||||
const html = await readFile(page, 'utf8');
|
||||
for (const match of html.matchAll(/<figure class="frame is-terminal[^"]*">([\s\S]*?)<\/figure>/g)) {
|
||||
const frame = match[1];
|
||||
const lines = [...frame.matchAll(
|
||||
/<div class="ec-line wt-(command|copyable|output)"><div class="code">([\s\S]*?)<\/div><\/div>/g,
|
||||
/<div class="ec-line wt-(command|copyable|output)"><div class="code">([\s\S]*?)<\/div>/g,
|
||||
)];
|
||||
const expected = lines
|
||||
.filter((line) => line[1] !== 'output')
|
||||
@@ -388,12 +389,51 @@ test('command-bearing console blocks emit command-only copy payloads', async ()
|
||||
if (expected.length === 0) continue;
|
||||
|
||||
commandBearingBlocks += 1;
|
||||
const encodedPayload = frame.match(/<button\b[^>]*\bdata-code="([^"]*)"/)?.[1];
|
||||
// The block control carries the bare `copy` class; per-line controls add
|
||||
// `wt-line-copy`, so this anchors on the block's own payload.
|
||||
const encodedPayload = frame.match(
|
||||
/<div class="copy">[\s\S]*?<button\b[^>]*\bdata-code="([^"]*)"/,
|
||||
)?.[1];
|
||||
assert.notEqual(encodedPayload, undefined, `${page} is missing a terminal copy payload`);
|
||||
assert.equal(renderedText(encodedPayload), expected.join('\u007f'), `${page} copies captured output`);
|
||||
|
||||
// A block listing several commands is as often a menu of alternatives as
|
||||
// a recipe, so each command line offers its own payload alongside the
|
||||
// block's.
|
||||
const commands = lines
|
||||
.filter((line) => line[1] === 'command')
|
||||
.map((line) => renderedText(line[2]).replace(/\n$/u, ''));
|
||||
const perLine = [...frame.matchAll(
|
||||
/<div class="copy wt-line-copy">[\s\S]*?<button\b[^>]*\bdata-code="([^"]*)"/g,
|
||||
)].map((line) => renderedText(line[1]));
|
||||
if (commands.length > 1) {
|
||||
perLineBlocks += 1;
|
||||
assert.deepEqual(perLine, commands, `${page} per-line copy payloads do not match its commands`);
|
||||
} else {
|
||||
assert.deepEqual(perLine, [], `${page} adds per-line copy to a single-command block`);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.ok(commandBearingBlocks > 0, 'expected command-bearing console blocks');
|
||||
assert.ok(perLineBlocks > 0, 'expected blocks listing several commands');
|
||||
});
|
||||
|
||||
test('generated command references expose no copy control', async () => {
|
||||
let references = 0;
|
||||
for (const page of renderedPages) {
|
||||
const html = await readFile(page, 'utf8');
|
||||
for (const match of html.matchAll(
|
||||
/<figure class="frame[^"]*\bwt-command-reference\b[^"]*">([\s\S]*?)<\/figure>/g,
|
||||
)) {
|
||||
references += 1;
|
||||
assert.doesNotMatch(
|
||||
match[1],
|
||||
/class="copy/,
|
||||
`${page} offers to copy a page of generated help text`,
|
||||
);
|
||||
}
|
||||
}
|
||||
assert.ok(references > 0, 'expected generated command references');
|
||||
});
|
||||
|
||||
test('titleless code frames do not render decorative headers', async () => {
|
||||
|
||||
@@ -108,8 +108,8 @@ end-to-end against codex-cli 0.144.1 (scratch marketplaces through
|
||||
that handles only regular files and directories, silently skipping symlink
|
||||
entries (`copy_dir_recursive` in `codex-rs/core-plugins/src/store.rs`), and
|
||||
sessions load from that cache copy. A symlink anywhere in the tree — a
|
||||
top-level `skills` link or a nested one like `reference/README.md` — ships
|
||||
no content. No manifest value can bridge it: manifest paths must stay within
|
||||
top-level `skills` link or one nested under `reference/` — ships no
|
||||
content. No manifest value can bridge it: manifest paths must stay within
|
||||
the plugin root (`..` and absolute paths are rejected,
|
||||
`resolve_manifest_path` in `codex-rs/core-plugins/src/manifest.rs`).
|
||||
- Codex's convention scan (`default_skill_roots`, the empty-`skills` branch of
|
||||
|
||||
@@ -29,4 +29,4 @@ The skill configures `.config/wt.toml` with project hooks. Pre-start hooks run w
|
||||
|
||||
**Start work in a fresh worktree**
|
||||
|
||||
`/wt-switch-create fix-auth Investigate the 5-minute session timeout` creates a `fix-auth` worktree in worktrunk's normal sibling layout (`<repo>.fix-auth/`), switches the session into it, and starts the task there. The branch name is optional (`/wt-switch-create -- <task>`). The worktree persists after the session — merge or remove it with `wt merge` / `wt remove` like any other.
|
||||
`/wt-switch-create fix-auth -- Investigate the 5-minute session timeout` creates a `fix-auth` worktree in worktrunk's normal sibling layout (`<repo>.fix-auth/`), switches the session into it, and starts the task there. The branch name is optional (`/wt-switch-create -- <task>`). The worktree persists after the session — merge or remove it with `wt merge` / `wt remove` like any other.
|
||||
|
||||
@@ -26,7 +26,7 @@ elif [[ "$(uname -o 2>/dev/null)" =~ ^(Msys|Cygwin)$ ]]; then
|
||||
elif command -v wt >/dev/null 2>&1; then
|
||||
# reject wt if it's the Windows Terminal alias
|
||||
if [[ "$(command -v wt)" == *WindowsApps* ]]; then
|
||||
echo "worktrunk: 'wt' resolves to Windows Terminal; install worktrunk as git-wt.exe or remove the Windows Terminal alias. See https://worktrunk.dev/worktrunk/#install" >&2
|
||||
echo "worktrunk: 'wt' resolves to Windows Terminal; install worktrunk as git-wt.exe or remove the Windows Terminal alias. See https://worktrunk.dev/#install" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
+6
-37
@@ -59,16 +59,7 @@ Detect which tools are installed (`which claude codex llm aichat`); if none, rec
|
||||
|
||||
### Configuring project hooks
|
||||
|
||||
Pick the hook type by when the command should run and whether it may block (10 types: 5 events × pre/post — full reference in `reference/hook.md`):
|
||||
|
||||
- Dependencies and env files a later step needs → `pre-start` (blocks creation)
|
||||
- Dev servers, long builds, cache copying → `post-start` (background)
|
||||
- Formatters, linters, type checks → `pre-commit`
|
||||
- Tests that must pass before merging → `pre-merge`
|
||||
- CI triggers, notifications → `post-commit`
|
||||
- Deployment → `post-merge`
|
||||
- Setup before branch resolution / terminal-IDE updates → `pre-switch` / `post-switch`
|
||||
- Cleanup before/after removal (save artifacts; stop servers, remove containers) → `pre-remove` / `post-remove`
|
||||
Pick the hook type by when the command should run and whether it may block — `reference/hook.md` maps all ten (5 events × pre/post) to their timing and typical uses.
|
||||
|
||||
Derive the commands from the project itself (`package.json` scripts, `Cargo.toml`, `pyproject.toml`) and verify they run before adding them.
|
||||
|
||||
@@ -97,7 +88,7 @@ Test with `wt switch --create test-hooks`.
|
||||
- Customize worktree paths → `reference/config.md#worktree-path-template`
|
||||
- Custom commit templates → `reference/llm-commits.md#prompt-templates`
|
||||
- Configure command defaults → `reference/config.md#command-config`
|
||||
- Set up personal hooks → `reference/config.md#hooks`
|
||||
- Set up personal hooks → `reference/config.md#user-hooks`
|
||||
|
||||
### Project config tasks
|
||||
- Set up hooks for new project → `reference/hook.md`
|
||||
@@ -146,37 +137,15 @@ The resolution is for the user to make the trust decision themselves:
|
||||
|
||||
## Advanced: agent handoffs
|
||||
|
||||
When the user requests spawning a worktree with an agent in a background session ("spawn a worktree for...", "hand off to another agent"), use the appropriate pattern for their terminal multiplexer. Substitute `<agent-cli>` with the CLI you are running as: `claude` for Claude Code, `'opencode run'` for OpenCode.
|
||||
|
||||
**tmux** (check `$TMUX` env var):
|
||||
```bash
|
||||
tmux new-session -d -s <branch-name> "wt switch --create <branch-name> -x <agent-cli> -- '<task description>'"
|
||||
```
|
||||
|
||||
**Zellij** (check `$ZELLIJ` env var):
|
||||
```bash
|
||||
zellij run -- wt switch --create <branch-name> -x <agent-cli> -- '<task description>'
|
||||
```
|
||||
When the user requests spawning a worktree with an agent in a background session ("spawn a worktree for...", "hand off to another agent"), use the tmux or Zellij command from `reference/tips-patterns.md#agent-handoffs`, substituting the CLI you are running as for `claude`.
|
||||
|
||||
**Requirements** (all must be true):
|
||||
- User explicitly requests spawning/handoff
|
||||
- User is in a supported multiplexer (tmux or Zellij)
|
||||
- User is in a supported multiplexer (check `$TMUX` / `$ZELLIJ`)
|
||||
- The user's project instructions (`CLAUDE.md` or `AGENTS.md`) or an explicit prompt authorize this pattern
|
||||
|
||||
**Do not use this pattern** for normal worktree operations.
|
||||
|
||||
Example (tmux, Claude Code):
|
||||
```bash
|
||||
tmux new-session -d -s fix-auth-bug "wt switch --create fix-auth-bug -x claude -- \
|
||||
'The login session expires after 5 minutes. Find the session timeout config and extend it to 24 hours.'"
|
||||
```
|
||||
|
||||
Example (Zellij, OpenCode):
|
||||
```bash
|
||||
zellij run -- wt switch --create fix-auth-bug -x 'opencode run' -- \
|
||||
'The login session expires after 5 minutes. Find the session timeout config and extend it to 24 hours.'
|
||||
```
|
||||
|
||||
### Parallel sub-Agents (single Claude Code session)
|
||||
|
||||
To spawn multiple sub-Agents that each work in their own worktree from one Claude Code session — no terminal multiplexer, no human in the other pane — pre-start each worktree from the parent and pass the path into the sub-Agent prompt:
|
||||
@@ -188,10 +157,10 @@ wt switch --create <branch> --no-cd --no-hooks
|
||||
Then call the `Agent` tool **without** `isolation: "worktree"`, naming the path in the prompt:
|
||||
|
||||
```
|
||||
You are working in `/abs/path/to/worktrunk.<branch>` on branch `<branch>`.
|
||||
You are working in `/abs/path/to/myproject.<branch>` on branch `<branch>`.
|
||||
All edits must stay in that worktree.
|
||||
```
|
||||
|
||||
`--no-cd` skips the shell-integration cd script the parent can't consume; `--no-hooks` is appropriate when each sub-Agent will run its own build/test step (e.g. `cargo run -- hook pre-merge --yes`) and you don't need post-start setup repeated per worktree.
|
||||
|
||||
**Do not** use `Agent { isolation: "worktree" }` for this. Claude Code passes its internal agent ID as `name` to the `WorktreeCreate` hook, so `wt` creates the worktree as `worktrunk.agent-<id>` on a throwaway branch. If the sub-Agent then creates a feature branch on top, you end up with non-canonical paths, orphan branches, and post-start hooks fired against the wrong branch. Pre-creating with `wt switch --create` keeps path, branch, and hook target aligned.
|
||||
**Do not** use `Agent { isolation: "worktree" }` for this. Claude Code passes its internal agent ID as `name` to the `WorktreeCreate` hook, so `wt` creates the worktree as `myproject.agent-<id>` on a throwaway branch. If the sub-Agent then creates a feature branch on top, you end up with non-canonical paths, orphan branches, and post-start hooks fired against the wrong branch. Pre-creating with `wt switch --create` keeps path, branch, and hook target aligned.
|
||||
|
||||
-262
@@ -1,262 +0,0 @@
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
|
||||
<h1><img src="docs/public/logo.png" alt="Worktrunk logo" width="50" align="absmiddle"> Worktrunk</h1>
|
||||
|
||||
<!-- Crates.io badge below disabled while shields.io is rate-limited by crates.io
|
||||
(renders "CRATES.IO: INVALID"). Tracking: badges/shields#11879. Restore once fixed.
|
||||
[](https://crates.io/crates/worktrunk)
|
||||
-->
|
||||
[](https://worktrunk.dev)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://github.com/max-sixty/worktrunk/actions?query=branch%3Amain+workflow%3Aci)
|
||||
[](https://codecov.io/gh/max-sixty/worktrunk)
|
||||
[](https://github.com/max-sixty/worktrunk/stargazers)
|
||||
[](https://github.com/max-sixty/tend)
|
||||
|
||||
> **September 2026**: Worktrunk was [released](https://x.com/max_sixty/status/2006077845391724739?s=20) at the start of the year, and has quickly become the most popular git worktree manager. It's built with love (there's no slop!). Please let me know any frictions at all; I'm intensely focused on continuing to make Worktrunk excellent, and the biggest help is folks posting problems they perceive.
|
||||
|
||||
Worktrunk is a CLI for git worktree management, designed for running AI agents in parallel.
|
||||
|
||||
Worktrunk's three core commands make worktrees as easy as branches. Plus, Worktrunk has a bunch of quality-of-life features to simplify working with many parallel changes, including hooks to automate local workflows.
|
||||
|
||||
A quick demo:
|
||||
|
||||

|
||||
|
||||
> ### 📚 Full documentation at [worktrunk.dev](https://worktrunk.dev) 📚
|
||||
|
||||
<!-- ⚠️ AUTO-GENERATED from docs/src/content/docs/worktrunk.md#context-git-worktrees..worktrunk-makes-git-worktrees-as-easy-as-branches — edit source to update -->
|
||||
|
||||
## Context: git worktrees
|
||||
|
||||
AI agents like Claude Code and Codex can handle longer tasks without
|
||||
supervision, such that it's possible to manage 5-10+ in parallel. Git's native
|
||||
worktree feature give each agent its own working directory, so they don't step
|
||||
on each other's changes.
|
||||
|
||||
But the git worktree UX is clunky. Even a task as small as starting a new
|
||||
worktree requires typing the branch name three times: `git worktree add -b feat
|
||||
../repo.feat`, then `cd ../repo.feat`.
|
||||
|
||||
## Worktrunk makes git worktrees as easy as branches
|
||||
|
||||
Worktrees are addressed by branch name; paths are computed from a configurable template. Commands that take a branch also accept the path of the worktree it is checked out in.
|
||||
|
||||
<p class="workflow-stage">Start with the core commands</p>
|
||||
|
||||
**Core commands:**
|
||||
|
||||
<table class="cmd-compare">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Task</th>
|
||||
<th>Worktrunk</th>
|
||||
<th>Plain git</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Switch worktrees</td>
|
||||
<td data-label="Worktrunk"><code>wt switch feat</code></td>
|
||||
<td data-label="Plain git"><code>cd ../repo.feat</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Create + start Claude</td>
|
||||
<td data-label="Worktrunk"><code>wt switch -c -x claude feat</code></td>
|
||||
<td data-label="Plain git"><pre><code>git worktree add -b feat ../repo.feat && \
|
||||
cd ../repo.feat && \
|
||||
claude</code></pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Clean up</td>
|
||||
<td data-label="Worktrunk"><code>wt remove</code></td>
|
||||
<td data-label="Plain git"><pre><code>cd ../repo && \
|
||||
git worktree remove ../repo.feat && \
|
||||
git branch -d feat</code></pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>List with status</td>
|
||||
<td data-label="Worktrunk"><code>wt list</code></td>
|
||||
<td data-label="Plain git"><span class="cmd-compare-value"><code>git worktree list</code> (paths only)</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p class="workflow-stage">Expand into the more advanced commands as needed</p>
|
||||
|
||||
<p class="workflow-heading"><strong>Workflow automation:</strong></p>
|
||||
|
||||
- **[Hooks](https://worktrunk.dev/hook/)** — run commands on create, pre-merge, post-merge, etc
|
||||
- **[LLM commit messages](https://worktrunk.dev/llm-commits/)** — generate commit messages from diffs
|
||||
- **[Merge workflow](https://worktrunk.dev/merge/)** — squash, rebase, merge, clean up in one command
|
||||
- **[Interactive picker](https://worktrunk.dev/switch/#interactive-picker)** — browse worktrees with live diff and log previews
|
||||
- **[Share build caches](https://worktrunk.dev/step/#wt-step-copy-ignored)** — ten worktrees get `target/`, `node_modules/`, etc without building or copying them (on APFS, btrfs, and XFS)
|
||||
- **[`wt list --full`](https://worktrunk.dev/list/#full-mode)** — [CI status](https://worktrunk.dev/list/#ci-status) and [AI-generated summaries](https://worktrunk.dev/list/#llm-summaries) per branch
|
||||
- **[PR checkout](https://worktrunk.dev/switch/#pull-requests-and-merge-requests)** — `wt switch pr:123` to jump straight to a PR's branch
|
||||
- **[Dev server per worktree](https://worktrunk.dev/tips-patterns/#dev-server-per-worktree)** — `hash_port` template filter gives each worktree a unique port
|
||||
- **[Aliases](https://worktrunk.dev/extending/#aliases) & [per-branch variables](https://worktrunk.dev/config/#wt-config-state-vars)** — custom `wt <name>` commands and branch-scoped state for hook templates
|
||||
- ...and **[lots more](#next-steps)**
|
||||
|
||||
Multiple parallel agents, same simple commands:
|
||||
|
||||

|
||||
|
||||
<!-- END AUTO-GENERATED -->
|
||||
|
||||
<!-- ⚠️ AUTO-GENERATED from docs/src/content/docs/worktrunk.md#install..further-reading — edit source to update -->
|
||||
|
||||
## Install
|
||||
|
||||
**Homebrew (macOS & Linux):**
|
||||
|
||||
```bash
|
||||
brew install worktrunk && wt config shell install
|
||||
```
|
||||
|
||||
Shell integration allows commands to change directories.
|
||||
|
||||
**Cargo:**
|
||||
|
||||
```bash
|
||||
cargo install worktrunk && wt config shell install
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary><strong>Windows & other</strong></summary>
|
||||
|
||||
**Windows.** `wt` defaults to Windows Terminal's command, so Winget additionally installs Worktrunk as `git-wt` to avoid the conflict:
|
||||
|
||||
```bash
|
||||
winget install max-sixty.worktrunk
|
||||
git-wt config shell install
|
||||
```
|
||||
|
||||
Alternatively, disable Windows Terminal's alias (Settings → Apps → Advanced app settings → App execution aliases → "Terminal"/"Terminal Preview") to use `wt` directly.
|
||||
|
||||
> Free code signing provided by [SignPath.io](https://signpath.io/), certificate by [SignPath Foundation](https://signpath.org/) — [policy](https://worktrunk.dev/code-signing/).
|
||||
|
||||
**Arch Linux:**
|
||||
|
||||
```bash
|
||||
sudo pacman -S worktrunk && wt config shell install
|
||||
```
|
||||
|
||||
**Conda / Pixi** (community-maintained [feedstock](https://github.com/conda-forge/worktrunk-feedstock)):
|
||||
|
||||
```bash
|
||||
conda install -c conda-forge worktrunk && wt config shell install
|
||||
```
|
||||
|
||||
Or with [Pixi](https://pixi.sh): `pixi global install worktrunk && wt config shell install`.
|
||||
|
||||
</details>
|
||||
|
||||
## Quick start
|
||||
|
||||
Create a worktree for a new feature:
|
||||
|
||||
```console
|
||||
$ wt switch --create feature-auth
|
||||
✓ Created branch feature-auth from main and worktree @ ~/repo.feature-auth
|
||||
```
|
||||
|
||||
This creates a new branch and worktree, then switches to it. Do your work, then check all worktrees with [`wt list`](https://worktrunk.dev/list/):
|
||||
|
||||
```console
|
||||
$ wt list
|
||||
Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message
|
||||
@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati…
|
||||
^ main ^⇡ ⇡1 0e631ad 1d Initial commit
|
||||
|
||||
○ Showing 2 worktrees, 1 with changes, 1 ahead, hidden: Path
|
||||
```
|
||||
|
||||
The `@` marks the current worktree. `+` means staged changes, `↑1` means 1 commit ahead of main, `⇡` means unpushed commits.
|
||||
|
||||
When done, either:
|
||||
|
||||
**PR workflow** — commit, push, open a PR, merge via GitHub/GitLab, then clean up:
|
||||
|
||||
```bash
|
||||
wt step commit # commit staged changes
|
||||
gh pr create # or glab mr create
|
||||
wt remove # after PR is merged
|
||||
```
|
||||
|
||||
**Local merge** — squash, rebase onto main, fast-forward merge, clean up:
|
||||
|
||||
```console
|
||||
$ wt merge main
|
||||
◎ Generating commit message and committing changes... (2 files, +53, no squashing needed)
|
||||
Add authentication module
|
||||
✓ Committed changes @ a1b2c3d
|
||||
◎ Merging 1 commit to main @ a1b2c3d (no rebase needed)
|
||||
* a1b2c3d Add authentication module
|
||||
auth.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
lib.rs | 2 ++
|
||||
2 files changed, 53 insertions(+)
|
||||
✓ Merged to main (1 commit, 2 files, +53)
|
||||
◎ Removing feature-auth worktree & branch in background (same commit as main, _)
|
||||
○ Switched to worktree for main @ ~/repo
|
||||
```
|
||||
|
||||
For parallel agents, create multiple worktrees and launch an agent in each:
|
||||
|
||||
```bash
|
||||
wt switch -x claude -c feature-a -- 'Add user authentication'
|
||||
wt switch -x claude -c feature-b -- 'Fix the pagination bug'
|
||||
wt switch -x claude -c feature-c -- 'Write tests for the API'
|
||||
```
|
||||
|
||||
The `-x` flag runs a command after switching; arguments after `--` are passed to it. Configure [post-start hooks](https://worktrunk.dev/hook/#hook-types) to automate setup (install deps, start dev servers).
|
||||
|
||||
## Next steps
|
||||
|
||||
- Learn the core commands: [`wt switch`](https://worktrunk.dev/switch/), [`wt list`](https://worktrunk.dev/list/), [`wt merge`](https://worktrunk.dev/merge/), [`wt remove`](https://worktrunk.dev/remove/)
|
||||
- Set up [hooks](https://worktrunk.dev/hook/) for automated setup
|
||||
- Explore [LLM commit messages](https://worktrunk.dev/llm-commits/), [interactive
|
||||
picker](https://worktrunk.dev/switch/#interactive-picker), [Claude Code integration](https://worktrunk.dev/claude-code/), [CI
|
||||
status & PR links](https://worktrunk.dev/list/#ci-status)
|
||||
- Browse [tips & patterns](https://worktrunk.dev/tips-patterns/) for recipes: aliases, dev servers, databases, agent handoffs, and more
|
||||
- [Extending Worktrunk](https://worktrunk.dev/extending/) — customize workflows with hooks & aliases
|
||||
- Run `wt --help` or `wt <command> --help` for quick CLI reference
|
||||
|
||||
## Further reading
|
||||
|
||||
- [Claude Code: Best practices for agentic coding](https://www.anthropic.com/engineering/claude-code-best-practices) — Anthropic's official guide, including the worktree pattern
|
||||
- [Shipping faster with Claude Code and Git Worktrees](https://incident.io/blog/shipping-faster-with-claude-code-and-git-worktrees) — incident.io's workflow for parallel agents
|
||||
- [Git worktree pattern discussion](https://github.com/anthropics/claude-code/issues/1052) — Community discussion in the Claude Code repo
|
||||
- [@DevOpsToolbox's video on Worktrunk](https://youtu.be/WBQiqr6LevQ?t=345)
|
||||
- [git-worktree documentation](https://git-scm.com/docs/git-worktree) — Official git reference
|
||||
|
||||
<!-- END AUTO-GENERATED -->
|
||||
|
||||
## Contributing
|
||||
|
||||
- ⭐ Star the repo
|
||||
- Tell a friend about Worktrunk
|
||||
- [Open an issue](https://github.com/max-sixty/worktrunk/issues/new?title=&body=%23%23%20Description%0A%0A%3C!--%20Describe%20the%20bug%20or%20feature%20request%20--%3E%0A%0A%23%23%20Context%0A%0A%3C!--%20Any%20relevant%20context%3A%20your%20workflow%2C%20what%20you%20were%20trying%20to%20do%2C%20etc.%20--%3E) — feedback, feature requests, even a small friction or imperfect user message, or [a worktree pain not yet solved](https://github.com/max-sixty/worktrunk/issues/new?title=Worktree%20friction%3A%20&body=%23%23%20The%20friction%0A%0A%3C!--%20What%20worktree-related%20task%20is%20still%20painful%3F%20--%3E%0A%0A%23%23%20Current%20workaround%0A%0A%3C!--%20How%20do%20you%20handle%20this%20today%3F%20--%3E%0A%0A%23%23%20Ideal%20solution%0A%0A%3C!--%20What%20would%20make%20this%20easier%3F%20--%3E)
|
||||
- Share: [X](https://twitter.com/intent/tweet?text=Worktrunk%20%E2%80%94%20CLI%20for%20git%20worktree%20management&url=https%3A%2F%2Fworktrunk.dev) · [Reddit](https://www.reddit.com/submit?url=https%3A%2F%2Fworktrunk.dev&title=Worktrunk%20%E2%80%94%20CLI%20for%20git%20worktree%20management) · [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fworktrunk.dev)
|
||||
|
||||
> ### 📚 Full documentation at [worktrunk.dev](https://worktrunk.dev) 📚
|
||||
|
||||
### Star history
|
||||
|
||||
<!-- `sealed_token` is a GitHub token of ours encrypted with star-history's public
|
||||
key, from "Generate embed code" on star-history.com. Without one the chart
|
||||
renders a "GitHub restricted access to star data" placeholder: GitHub limits
|
||||
stargazer data to a repo's admins and collaborators, and their servers are
|
||||
neither.
|
||||
|
||||
The token is fine-grained and reaches only this repo. Its Contents permission
|
||||
has to be read *and write* — write access is what GitHub accepts as proof of
|
||||
collaborator status, so rotating to a read-only token brings the placeholder
|
||||
straight back. Publishing the ciphertext is safe on its own; what the scope
|
||||
buys is a bound on star-history, which decrypts it and so holds a credential
|
||||
that can push here until it expires 2027-08-16 — at which point the chart
|
||||
reverts to the placeholder with nothing else to signal it. star-history
|
||||
reports GitHub is working on restoring access, so drop this parameter once
|
||||
the plain URL renders a chart again. -->
|
||||
<a href="https://star-history.com/#max-sixty/worktrunk&Date">
|
||||
<img src="https://api.star-history.com/svg?repos=max-sixty/worktrunk&type=Date&sealed_token=2ySbQiVbkVrGmwgDsJya-xr4ApbVVvR0siYI46d22Xj_1kPCcgA9X0YpUGc3__aMuZ0ZAWzG4NBhJtqepYjlkoYrVwmKbgaPmGpNZTCfSyVp8EDA_IXaOOYW2whsOXDAi6g7HD9ezsnqSl58n7AqW2_4IQ4hY2p3h7tNv_3k4am5ASq1NdpbfrOUazCl" width="500" alt="Star History Chart">
|
||||
</a>
|
||||
+5
-3
@@ -7,9 +7,11 @@ Worktrunk ships a plugin for each supported agent CLI. What a plugin provides de
|
||||
| Configuration skill | ✓ | ✓ | | | ✓ |
|
||||
| Activity tracking (🤖/💬 in `wt list`) | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Worktree isolation | ✓ | | | | |
|
||||
| `/wt-switch-create` command | ✓ | | | | |
|
||||
| `/wt-switch-create` skill | ✓ | ✓\* | | | ✓\* |
|
||||
|
||||
The configuration skill is documentation the agent reads to help set up LLM commits, hooks, and troubleshooting. Activity tracking shows which worktrees have running sessions. Worktree isolation needs worktree-lifecycle hooks and `/wt-switch-create` needs session working-directory switching — both Claude Code-only, so Codex, OpenCode, Pi, and Gemini users invoke `wt switch --create` and `wt remove` directly. Codex tracks activity through its own `Stop` and `SessionEnd` hooks.
|
||||
\* Codex and Gemini load the `/wt-switch-create` skill from the same shared skill set, but neither lets a skill change the session's working directory, so it does nothing there.
|
||||
|
||||
The configuration skill is documentation the agent reads to help set up LLM commits, hooks, and troubleshooting. Activity tracking shows which worktrees have running sessions. Worktree isolation needs worktree-lifecycle hooks, which only Claude Code exposes, so Codex, OpenCode, Pi, and Gemini users invoke `wt switch --create` and `wt remove` directly. Codex tracks activity through its own `Stop` and `SessionEnd` hooks.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -128,7 +130,7 @@ Three things to get right:
|
||||
|
||||
Claude Code agents can run in isolated worktrees (`isolation: "worktree"`). By default, Claude Code creates these with `git worktree add`. The plugin's `WorktreeCreate` and `WorktreeRemove` hooks route this through `wt switch --create` and `wt remove` instead, so worktrees created by agents get worktrunk's naming conventions, hooks, and lifecycle management.
|
||||
|
||||
## `/wt-switch-create` command (Claude Code only)
|
||||
## `/wt-switch-create` skill (Claude Code only)
|
||||
|
||||
`/wt-switch-create [<branch>] [<repo>] [-- <task>]` starts a task in a fresh worktree without leaving the session: it creates the worktree, switches into it, and runs the task (all arguments optional). The worktree shows up in `wt list`; merge or remove it with `wt merge` / `wt remove`.
|
||||
|
||||
|
||||
+13
-211
@@ -322,7 +322,7 @@ exclude = [] # Additional excludes (e.g., [".cache/", ".turbo/"])
|
||||
|
||||
Built-in excludes (VCS metadata and tool-state directories) always apply; [the `wt step copy-ignored` docs](https://worktrunk.dev/step/#wt-step-copy-ignored) list them. User config and project config exclusions are combined.
|
||||
|
||||
### Aliases
|
||||
### User aliases
|
||||
|
||||
Command templates that run as `wt <name>`. See the [Extending Worktrunk guide](https://worktrunk.dev/extending/#aliases) for usage and flags.
|
||||
|
||||
@@ -332,7 +332,7 @@ greet = "echo Hello from {{ branch }}"
|
||||
url = "echo http://localhost:{{ branch | hash_port }}"
|
||||
```
|
||||
|
||||
Aliases defined here apply to all projects. For project-specific aliases, use the [project config](https://worktrunk.dev/config/#project-configuration) `[aliases]` section instead.
|
||||
Aliases defined here apply to all projects. For project-specific aliases, use the [project config](https://worktrunk.dev/config/#project-aliases) `[aliases]` section instead.
|
||||
|
||||
### User project-specific settings
|
||||
|
||||
@@ -528,9 +528,9 @@ template-append = """
|
||||
|
||||
How the fragment renders, and the project-config counterpart: [the LLM commits guide](https://worktrunk.dev/llm-commits/#appending-to-the-prompt).
|
||||
|
||||
## Hooks
|
||||
## User hooks
|
||||
|
||||
See [`wt hook`](https://worktrunk.dev/hook/) for hook types, execution order, template variables, and examples. User hooks apply to all projects; [project hooks](https://worktrunk.dev/config/#project-configuration) apply only to that repository.
|
||||
See [`wt hook`](https://worktrunk.dev/hook/) for hook types, execution order, template variables, and examples. User hooks apply to all projects; [project hooks](https://worktrunk.dev/config/#project-hooks) apply only to that repository.
|
||||
<!-- USER_CONFIG_END -->
|
||||
<!-- PROJECT_CONFIG_START -->
|
||||
# Project Configuration
|
||||
@@ -539,7 +539,7 @@ Project configuration lets teams share repository-specific settings — hooks, d
|
||||
|
||||
To create a starter file with commented-out examples, run `wt config create --project`.
|
||||
|
||||
## Hooks
|
||||
## Project hooks
|
||||
|
||||
Project hooks apply to this repository only. See [`wt hook`](https://worktrunk.dev/hook/) for hook types, execution order, and examples.
|
||||
|
||||
@@ -595,7 +595,7 @@ exclude = [".cache/", ".turbo/"]
|
||||
|
||||
Built-in excludes (VCS metadata and tool-state directories) always apply; [the `wt step copy-ignored` docs](https://worktrunk.dev/step/#wt-step-copy-ignored) list them. User config and project config exclusions are combined.
|
||||
|
||||
## Aliases
|
||||
## Project aliases
|
||||
|
||||
Command templates that run as `wt <name>`. See the [Extending Worktrunk guide](https://worktrunk.dev/extending/#aliases) for usage and flags.
|
||||
|
||||
@@ -605,7 +605,7 @@ deploy = "make deploy BRANCH={{ branch }}"
|
||||
url = "echo http://localhost:{{ branch | hash_port }}"
|
||||
```
|
||||
|
||||
Aliases defined here are shared with teammates. For personal aliases, use the [user config](https://worktrunk.dev/config/#aliases) `[aliases]` section instead.
|
||||
Aliases defined here are shared with teammates. For personal aliases, use the [user config](https://worktrunk.dev/config/#user-aliases) `[aliases]` section instead.
|
||||
<!-- PROJECT_CONFIG_END -->
|
||||
|
||||
# Shell Integration
|
||||
@@ -781,24 +781,6 @@ Output:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config update
|
||||
@@ -853,24 +835,6 @@ Options:
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config approvals
|
||||
@@ -879,7 +843,7 @@ Manage command approvals.
|
||||
|
||||
Project hooks and project aliases prompt for approval on first run to prevent untrusted projects from running arbitrary commands. Approvals from both flows are stored together.
|
||||
|
||||
### Examples
|
||||
### Approval examples
|
||||
|
||||
List commands and their approval status for current project:
|
||||
```console
|
||||
@@ -956,24 +920,6 @@ Commands:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config alias
|
||||
@@ -982,7 +928,7 @@ Inspect and preview aliases.
|
||||
|
||||
Aliases are command templates configured in user (`~/.config/worktrunk/config.toml`) or project (`.config/wt.toml`) config and run as `wt <name>`. See the [Extending Worktrunk guide](https://worktrunk.dev/extending/#aliases) for the configuration format.
|
||||
|
||||
### Examples
|
||||
### Alias examples
|
||||
|
||||
Show every configured alias's template:
|
||||
```console
|
||||
@@ -1014,24 +960,6 @@ Commands:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state
|
||||
@@ -1048,7 +976,7 @@ State is stored in `.git/` (config entries and log files), separate from configu
|
||||
- **vars**: [Custom variables per branch](https://worktrunk.dev/config/#wt-config-state-vars)
|
||||
- **logs**: [Operation and debug logs](https://worktrunk.dev/config/#wt-config-state-logs)
|
||||
|
||||
### Examples
|
||||
### State examples
|
||||
|
||||
Get the default branch:
|
||||
```console
|
||||
@@ -1104,24 +1032,6 @@ Commands:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state cache
|
||||
@@ -1142,7 +1052,7 @@ View or drop worktrunk's regenerable caches in one place. Everything here is reb
|
||||
|
||||
Without a subcommand, runs `get`.
|
||||
|
||||
### Examples
|
||||
### Cache examples
|
||||
|
||||
Show cache contents:
|
||||
```console
|
||||
@@ -1172,24 +1082,6 @@ Options:
|
||||
Output:
|
||||
--format <FORMAT>
|
||||
Output format (text, json) [default: text]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state default-branch
|
||||
@@ -1244,24 +1136,6 @@ Commands:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state logs
|
||||
@@ -1319,7 +1193,7 @@ All logs are stored in `.git/wt/logs/` (in the main worktree's git directory). A
|
||||
|
||||
`wt config state logs --format=json` emits three arrays — `command_log`, `hook_output`, `diagnostic`. Each entry carries a `file` (relative), `path` (absolute), `size`, and `modified_at` (unix seconds). Hook-output entries additionally expose `branch`, `source` (`user` / `project` / `internal`), `hook_type` (the `post-*` kind, or `null` for internal ops), and `name`. Filter with `jq` to pick out a specific entry.
|
||||
|
||||
### Examples
|
||||
### Log examples
|
||||
|
||||
List all log files:
|
||||
```console
|
||||
@@ -1365,24 +1239,6 @@ Options:
|
||||
Output:
|
||||
--format <FORMAT>
|
||||
Output format (text, json) [default: text]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state ci-status
|
||||
@@ -1413,24 +1269,6 @@ Options:
|
||||
Output:
|
||||
--format <FORMAT>
|
||||
Output format (text, json) [default: text]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state marker
|
||||
@@ -1489,24 +1327,6 @@ Options:
|
||||
Output:
|
||||
--format <FORMAT>
|
||||
Output format (text, json) [default: text]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state vars
|
||||
@@ -1515,7 +1335,7 @@ Custom variables per branch.
|
||||
|
||||
Store custom variables per branch. Values are stored as-is — plain strings or JSON.
|
||||
|
||||
### Examples
|
||||
### Variable examples
|
||||
|
||||
Set and get values:
|
||||
```console
|
||||
@@ -1577,22 +1397,4 @@ Commands:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ Aliases are configured under `[aliases]`:
|
||||
|
||||
```toml
|
||||
[aliases]
|
||||
deploy = "fly deploy --config=fly.{{ env }}.toml --app=myapp-{{ branch }}"
|
||||
deploy = "fly deploy --config=fly.{{ env }}.toml --app=myproject-{{ branch }}"
|
||||
open = "open http://localhost:{{ branch | hash_port }}"
|
||||
since-main = "git log --oneline {{ default_branch }}..HEAD"
|
||||
```
|
||||
|
||||
+20
-58
@@ -12,19 +12,19 @@ Git's built-in worktree commands work but require manual lifecycle management:
|
||||
|
||||
```console
|
||||
# Plain git worktree workflow
|
||||
$ git worktree add -b feature-branch ../myapp-feature main
|
||||
$ cd ../myapp-feature
|
||||
$ git worktree add -b feature-auth ../myproject.feature-auth main
|
||||
$ cd ../myproject.feature-auth
|
||||
# ...work, commit, push...
|
||||
$ cd ../myapp
|
||||
$ git merge feature-branch
|
||||
$ git worktree remove ../myapp-feature
|
||||
$ git branch -d feature-branch
|
||||
$ cd ../myproject
|
||||
$ git merge feature-auth
|
||||
$ git worktree remove ../myproject.feature-auth
|
||||
$ git branch -d feature-auth
|
||||
```
|
||||
|
||||
Worktrunk automates the full lifecycle:
|
||||
|
||||
```console
|
||||
$ wt switch --create feature-branch # Creates worktree, runs setup hooks
|
||||
$ wt switch --create feature-auth # Creates worktree, runs setup hooks
|
||||
# ...work...
|
||||
$ wt merge # Merges into default branch, cleans up
|
||||
```
|
||||
@@ -77,14 +77,11 @@ The stash lives in the shared `.git` directory, so it's reachable from the new w
|
||||
|
||||
## There's an issue with my shell setup
|
||||
|
||||
If shell integration isn't working (auto-cd not happening, completions missing, `wt` not found as a function), the fastest path to a fix is using Claude Code with the Worktrunk plugin:
|
||||
If shell integration isn't working (auto-cd not happening, completions missing, `wt` not found as a function), work through the [shell integration](https://worktrunk.dev/shell-integration/#debugging-checklist) debugging checklist — it covers each warning `wt switch` prints and what to check for every shell.
|
||||
|
||||
1. Install the [Worktrunk plugin](https://worktrunk.dev/claude-code/) in Claude Code
|
||||
2. Ask Claude to debug the Worktrunk shell integration
|
||||
Or hand it to an agent: install the [Worktrunk plugin](https://worktrunk.dev/claude-code/) in Claude Code and ask it to debug the Worktrunk shell integration. It runs `wt config show`, inspects the shell config files, and identifies the issue.
|
||||
|
||||
Claude will run `wt config show`, inspect the shell config files, and identify the issue.
|
||||
|
||||
If Claude can't fix it, please [open an issue](https://github.com/max-sixty/worktrunk/issues/new?title=Shell%20setup%20issue&body=%23%23%20Shell%20and%20OS%0A%0A-%20Shell%3A%20%0A-%20OS%3A%20%0A%0A%23%23%20Output%20of%20%60wt%20config%20show%60%0A%0A%60%60%60%0A%0A%60%60%60%0A%0A%23%23%20What%20Claude%20found%20%28if%20available%29%0A%0A) with the output of `wt config show`, the shell (bash/zsh/fish), and OS. (And even if it fixes the problem, feel free to open an issue: non-standard success cases are useful for ensuring Worktrunk is easy to set up for others.)
|
||||
If neither settles it, please [open an issue](https://github.com/max-sixty/worktrunk/issues/new?title=Shell%20setup%20issue&body=%23%23%20Shell%20and%20OS%0A%0A-%20Shell%3A%20%0A-%20OS%3A%20%0A%0A%23%23%20Output%20of%20%60wt%20config%20show%60%0A%0A%60%60%60%0A%0A%60%60%60%0A%0A%23%23%20What%20Claude%20found%20%28if%20available%29%0A%0A) with the output of `wt config show`, the shell (bash/zsh/fish), and OS. (And even if it fixes the problem, feel free to open an issue: non-standard success cases are useful for ensuring Worktrunk is easy to set up for others.)
|
||||
|
||||
## What does `-v` / `-vv` do?
|
||||
|
||||
@@ -108,7 +105,7 @@ The flags only reach a command you type; shell completion runs as its own proces
|
||||
|
||||
### 1. Worktree directories
|
||||
|
||||
Created by `wt switch <branch>` when switching to a branch that doesn't have a worktree. Use `wt switch --create <branch>` to create a new branch. Default location is `../<repo>.<branch>` (sibling to main repo), configurable via `worktree-path` in user config.
|
||||
Created by `wt switch <branch>` when switching to a branch that doesn't have a worktree. Use `wt switch --create <branch>` to create a new branch. Default location is `../<repo>.<branch>` (sibling to the main worktree), configurable via `worktree-path` in user config.
|
||||
|
||||
**To remove:** `wt remove <branch>` removes the worktree directory and deletes the branch.
|
||||
|
||||
@@ -126,19 +123,7 @@ User config location: `$XDG_CONFIG_HOME/worktrunk/` (or `~/.config/worktrunk/`)
|
||||
|
||||
### 3. Shell integration
|
||||
|
||||
Created by `wt config shell install`:
|
||||
|
||||
- **Bash**: adds line to `~/.bashrc`
|
||||
- **Zsh**: adds line to `~/.zshrc` (or `$ZDOTDIR/.zshrc`)
|
||||
- **Fish**: creates `~/.config/fish/functions/wt.fish` and `~/.config/fish/completions/wt.fish`
|
||||
- **Nushell** [experimental]: creates `wt.nu` in Nushell's user vendor-autoload directory — the last entry of `$nu.vendor-autoload-dirs`, under `$nu.data-dir` (typically `~/.local/share/nushell/vendor/autoload` on Linux, `~/Library/Application Support/nushell/vendor/autoload` on macOS)
|
||||
- **PowerShell** (Windows): creates both profile files if they don't exist:
|
||||
- `Documents/PowerShell/Microsoft.PowerShell_profile.ps1` (PowerShell 7+)
|
||||
- `Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1` (Windows PowerShell 5.1)
|
||||
|
||||
Fish and Nushell wrappers live at a path named after the command, so install writes that file whole, replacing an existing `functions/wt.fish`, `completions/wt.fish`, or `wt.nu`. Bash, zsh, and PowerShell rc files hold the rest of a shell's setup, so install only appends a line to those.
|
||||
|
||||
**PowerShell detection on Windows:** When running from cmd.exe or PowerShell, both PowerShell profile files are created automatically. When running from Git Bash or MSYS2, PowerShell is skipped (use `wt config shell install powershell` to create the profiles explicitly).
|
||||
`wt config shell install` appends a line to the bash, zsh, and PowerShell rc files, and writes worktrunk's own wrapper and completion files whole for fish and Nushell. [Shell integration](https://worktrunk.dev/shell-integration/#files-created) names the file each shell gets.
|
||||
|
||||
**To remove:** `wt config shell uninstall`.
|
||||
|
||||
@@ -151,6 +136,7 @@ Worktrunk stores repository state, caches, and logs under `.git/`:
|
||||
| `git config worktrunk.*` | Cached default branch, switch history, branch markers, custom variables | Various commands |
|
||||
| `.git/wt/cache/{kind}/*.json` | Cached CI status, the largest PR/MR number seen (sizes the `wt list` CI column), and git command results (merge-tree, integration probes, diff stats, ancestry checks, ahead/behind counts, merge bases) | `wt list`, `wt merge`, `wt remove` |
|
||||
| `.git/wt/cache/summary/{branch}/{hash}.json` | Cached LLM branch summaries, content-addressed by diff hash | `wt list --full`, `wt switch` (when `[list] summary = true`) |
|
||||
| `.git/wt/cache/picker-preview/*.json` | Rendered preview panes for the interactive picker | `wt switch` |
|
||||
| `.git/wt/logs/{branch}/**/*.log` | Background hook output (nested per branch) | Hooks, background `wt remove` |
|
||||
| `.git/wt/logs/commands.jsonl` | Command audit log (~2MB max) | Hooks, LLM commands |
|
||||
| `.git/wt/logs/trace.log` | Human debug trace for issue reporting | Running with `-vv` |
|
||||
@@ -161,7 +147,7 @@ Worktrunk stores repository state, caches, and logs under `.git/`:
|
||||
|
||||
None of this is tracked by git or pushed to remotes.
|
||||
|
||||
**To remove:** `wt config state clear` removes all repository data: config keys, caches, markers, hints, variables, logs, and stale trash.
|
||||
**To remove:** `wt config state clear` removes all repository data: config keys, caches, markers, hints, variables, logs, and stale trash. It prompts before removing anything worktrunk can't recompute, unless you pass `--yes`.
|
||||
|
||||
### 5. Agent integrations
|
||||
|
||||
@@ -203,14 +189,14 @@ Removal also refuses, `--force` included, when the directory at a registered pat
|
||||
To protect a worktree from removal entirely (say it holds a local database), lock it:
|
||||
|
||||
```bash
|
||||
git worktree lock ../myproject.feature --reason "Contains local database"
|
||||
git worktree lock ../myproject.feature-auth --reason "Contains local database"
|
||||
```
|
||||
|
||||
Locked worktrees show `⊞` in `wt list`. Neither `git worktree remove` nor `wt remove` (even with `--force`) will delete them. Unlock with `git worktree unlock`.
|
||||
|
||||
### Branch deletion
|
||||
|
||||
By default, `wt remove` only deletes branches whose content is already in the default branch. Branches showing `_` (same commit) or `⊂` (integrated) in `wt list` are safe to delete.
|
||||
By default, `wt remove` only deletes branches whose content is already in the default branch. Branches showing `_` (same commit, clean) or `⊂` (integrated) in `wt list` are safe to delete.
|
||||
|
||||
For the full algorithm, see [Branch cleanup](https://worktrunk.dev/remove/#branch-cleanup) — it handles squash-merge and rebase workflows where commit history differs but file changes match.
|
||||
|
||||
@@ -221,7 +207,7 @@ A branch checked out in a second worktree is retained regardless, `-D` included.
|
||||
### Other cleanup
|
||||
|
||||
- `wt merge` / `wt step push` — the target branch's checked-out worktree is updated to the merged commits, so a file those commits delete disappears from it, and an ignored file at a path they track is overwritten — the same result a `git merge` run in that worktree would produce. Uncommitted changes at paths the merge doesn't touch stay in place, staged or not; one at a path it does touch refuses the merge upfront, naming the file
|
||||
- `wt remove` — besides the target worktree, two cleanup mechanisms run. The removed worktree's own `git fsmonitor--daemon` (git's per-worktree filesystem watcher under `core.fsmonitor=true`, which would leak once its worktree is gone) is sent `git fsmonitor--daemon stop`, then force-terminated (`SIGTERM`, then `SIGKILL`) via the PID resolved from its IPC socket if it didn't exit. A background sweep then deletes `.git/wt/trash/` entries older than 24 hours (directories orphaned when a previous background removal was interrupted) and terminates fsmonitor daemons whose worktree no longer exists (orphans from `git worktree remove`, `rm -rf`, or a crashed `wt`)
|
||||
- `wt remove` — besides the worktree being removed, two cleanup mechanisms run. The removed worktree's own `git fsmonitor--daemon` (git's per-worktree filesystem watcher under `core.fsmonitor=true`, which would leak once its worktree is gone) is sent `git fsmonitor--daemon stop`, then force-terminated (`SIGTERM`, then `SIGKILL`) via the PID resolved from its IPC socket if it didn't exit. A background sweep then deletes `.git/wt/trash/` entries older than 24 hours (directories orphaned when a previous background removal was interrupted) and terminates fsmonitor daemons whose worktree no longer exists (orphans from `git worktree remove`, `rm -rf`, or a crashed `wt`)
|
||||
- `wt config state clear` — removes all worktrunk data from `.git/` (config keys, caches, markers, hints, variables, logs, stale trash)
|
||||
- `wt config shell install` — when migrating an integration to a new location, removes the file left at the old one: fish `conf.d/wt.fish` (now `functions/wt.fish`) and nushell wrappers stranded under `<config-dir>/vendor/autoload` (now `<data-dir>/vendor/autoload`). The old path is where worktrunk's own wrapper lived and is named after the command being installed, so it's taken back whole without reading it — a `conf.d/wt.fish` left in place would be sourced at startup and shadow the new wrapper anyway. Only that exact filename is touched, and each removal is printed
|
||||
- `wt config shell uninstall` — removes integration lines from bash/zsh/PowerShell rc files, and deletes worktrunk's wrapper and completion files (fish `functions/`, `conf.d/`, and `completions/`; nushell `vendor/autoload`). Uninstall takes no command name, so it lists those directories and recognizes files by worktrunk's own content markers, whatever binary name they were installed under; files without the markers are left alone. An rc file belongs to the user, so a line qualifies only where it runs the init command: one that merely mentions it, inside a comment, an `echo`, or an alias body, stays. Every line uninstall does take is printed, before removal and again after
|
||||
@@ -275,7 +261,7 @@ Clear with `wt config state logs clear`.
|
||||
|
||||
## Does Worktrunk work on Windows?
|
||||
|
||||
Yes. Core commands, shell integration, and tab completion work in both Git Bash and PowerShell. See [installation](https://worktrunk.dev/worktrunk/#install) for setup details, including avoiding the Windows Terminal `wt` conflict.
|
||||
Yes. Core commands, shell integration, and tab completion work in both Git Bash and PowerShell. See [installation](https://worktrunk.dev/#install) for setup details, including avoiding the Windows Terminal `wt` conflict.
|
||||
|
||||
**Git for Windows required** — Hooks use bash syntax and execute via Git Bash, so [Git for Windows](https://gitforwindows.org/) must be installed even when PowerShell is the interactive shell.
|
||||
|
||||
@@ -291,11 +277,7 @@ For full details on the detection mechanism, see `wt config state default-branch
|
||||
|
||||
## My `for-each` or `--execute` alias prints the same value in every worktree
|
||||
|
||||
An alias body renders once at dispatch, in the invoking worktree's context, so a per-worktree variable like `{{ branch }}` is baked to that one worktree's value before the nested `wt` command iterates. Every worktree then sees the same value.
|
||||
|
||||
Confirm it with `wt config alias dry-run <name>`: if the value is already substituted (e.g. `… echo branch=main`), it was baked at dispatch.
|
||||
|
||||
To defer a variable to the nested command, wrap it as `{% raw %}{{ branch }}{% endraw %}`; for `wt step for-each`, also keep it inside a quoted `sh -c '…'` so the alias's shell doesn't word-split it. See [deferring expansion in an alias](https://worktrunk.dev/extending/#deferring-expansion-to-a-nested-wt-command). A repo-level variable like `{{ default_branch }}` is unaffected — it is identical in every worktree.
|
||||
The alias body rendered once at dispatch, baking the variable to the invoking worktree's value before the nested `wt` command iterated. See [deferring expansion to a nested `wt` command](https://worktrunk.dev/extending/#deferring-expansion-to-a-nested-wt-command) for how to confirm it and how to defer the variable.
|
||||
|
||||
## What system dependencies are required?
|
||||
|
||||
@@ -309,26 +291,6 @@ cargo install worktrunk --no-default-features --features cli
|
||||
|
||||
This disables bash syntax highlighting in command output but keeps all core functionality. The syntax highlighting feature requires C99 compiler support and can fail on older systems or minimal Docker images.
|
||||
|
||||
## Running tests (for contributors)
|
||||
|
||||
### Quick tests
|
||||
|
||||
```bash
|
||||
cargo test
|
||||
```
|
||||
|
||||
### Full integration tests
|
||||
|
||||
Shell integration tests require bash, zsh, fish, nushell, and pwsh, plus `jq`:
|
||||
|
||||
```bash
|
||||
cargo test --test integration --features shell-integration-tests
|
||||
```
|
||||
|
||||
## How can I contribute?
|
||||
|
||||
- Star the repo
|
||||
- Try it out and [open an issue](https://github.com/max-sixty/worktrunk/issues) with feedback — even small annoyances
|
||||
- What worktree friction does Worktrunk not yet solve? [Tell us](https://github.com/max-sixty/worktrunk/issues)
|
||||
- Send to a friend
|
||||
- Post about it on [X](https://twitter.com/intent/tweet?text=Worktrunk%20%E2%80%94%20CLI%20for%20git%20worktree%20management&url=https%3A%2F%2Fworktrunk.dev), [Reddit](https://www.reddit.com/submit?url=https%3A%2F%2Fworktrunk.dev&title=Worktrunk%20%E2%80%94%20CLI%20for%20git%20worktree%20management), or [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fworktrunk.dev)
|
||||
See [Contributing](https://github.com/max-sixty/worktrunk#contributing) in the README — feedback, share links, and how to run the test suite.
|
||||
|
||||
@@ -604,22 +604,4 @@ Options:
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
+1
-1
@@ -101,7 +101,7 @@ With `summary = true` and a `[commit.generation] command` configured, Worktrunk
|
||||
|
||||
Summaries appear in:
|
||||
|
||||
- **`wt switch`** [interactive picker](https://worktrunk.dev/switch/#interactive-picker) — preview tab 5
|
||||
- **`wt switch`** [interactive picker](https://worktrunk.dev/switch/#interactive-picker) — the `summary` preview tab
|
||||
- **`wt list --full`** — the Summary column (see [`wt list`](https://worktrunk.dev/list/#llm-summaries))
|
||||
|
||||
Enable in user config:
|
||||
|
||||
+66
-74
@@ -1,18 +1,12 @@
|
||||
# Shell Integration Reference
|
||||
# Shell integration
|
||||
|
||||
How Worktrunk's shell integration works and how to debug issues.
|
||||
Shell integration is what lets `wt switch` change your shell's directory. This page covers how it works, what it installs, and how to fix it when it doesn't.
|
||||
|
||||
## Why Shell Integration Exists
|
||||
## Why shell integration exists
|
||||
|
||||
Subprocesses cannot change the parent shell's current directory. When
|
||||
`wt switch feature` runs, the `wt` binary runs as a child process and cannot
|
||||
`cd` the terminal.
|
||||
A subprocess cannot change its parent shell's directory. When `wt switch feature` runs, the `wt` binary is a child process and cannot `cd` the terminal.
|
||||
|
||||
Worktrunk solves this with a file directive: the shell wrapper creates one temp
|
||||
file, `wt` writes the target directory to it, and the wrapper changes directory
|
||||
after `wt` exits. `--execute` runs directly inside wt. The wrapper's steps and a
|
||||
simplified implementation: [How the Shell Wrapper
|
||||
Works](#how-the-shell-wrapper-works).
|
||||
Worktrunk solves this with a file directive: the shell wrapper creates one temp file, `wt` writes the target directory to it, and the wrapper changes directory after `wt` exits. `--execute` runs directly inside `wt`. See [How the shell wrapper works](#how-the-shell-wrapper-works) for the steps and a simplified implementation.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -37,55 +31,61 @@ wt config shell init nu | save -f ($nu.vendor-autoload-dirs | last | path join w
|
||||
Invoke-Expression (& wt config shell init powershell | Out-String)
|
||||
```
|
||||
|
||||
## Checking Status
|
||||
## Files created
|
||||
|
||||
`wt config shell install` writes:
|
||||
|
||||
- **Bash**: adds a line to `~/.bashrc`
|
||||
- **Zsh**: adds a line to `~/.zshrc` (or `$ZDOTDIR/.zshrc`)
|
||||
- **Fish**: creates `~/.config/fish/functions/wt.fish` and `~/.config/fish/completions/wt.fish`
|
||||
- **Nushell** [experimental]: creates `wt.nu` in Nushell's user vendor-autoload directory — the last entry of `$nu.vendor-autoload-dirs`, under `$nu.data-dir` (typically `~/.local/share/nushell/vendor/autoload` on Linux, `~/Library/Application Support/nushell/vendor/autoload` on macOS)
|
||||
- **PowerShell** (Windows): creates both profile files if they don't exist:
|
||||
- `Documents/PowerShell/Microsoft.PowerShell_profile.ps1` (PowerShell 7+)
|
||||
- `Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1` (Windows PowerShell 5.1)
|
||||
|
||||
Fish and Nushell wrappers live at a path named after the command, so install writes that file whole, replacing an existing `functions/wt.fish`, `completions/wt.fish`, or `wt.nu`. Bash, zsh, and PowerShell rc files hold the rest of a shell's setup, so install only appends a line to those.
|
||||
|
||||
**PowerShell detection on Windows:** When running from cmd.exe or PowerShell, both PowerShell profile files are created automatically. When running from Git Bash or MSYS2, PowerShell is skipped (use `wt config shell install powershell` to create the profiles explicitly).
|
||||
|
||||
**To remove:** `wt config shell uninstall`.
|
||||
|
||||
## Checking status
|
||||
|
||||
```bash
|
||||
# Show shell integration status
|
||||
wt config show
|
||||
```
|
||||
|
||||
The RUNTIME section shows whether shell integration is active for the current
|
||||
session.
|
||||
The RUNTIME section shows whether shell integration is active for the current session.
|
||||
|
||||
## Warning Messages
|
||||
## Warning messages
|
||||
|
||||
When shell integration isn't working, `wt switch` shows warnings explaining why.
|
||||
When shell integration isn't working, `wt switch` shows a warning explaining why.
|
||||
|
||||
### "shell wrapper is out of date"
|
||||
|
||||
**Meaning**: The active shell still has a retired wrapper loaded. Current
|
||||
versions no longer write to that wrapper's single directive file, so the
|
||||
parent shell cannot follow a directory change.
|
||||
**Meaning**: The active shell still has a retired wrapper loaded. Current versions no longer write to that wrapper's single directive file, so the parent shell cannot follow a directory change.
|
||||
|
||||
**Fix**: Run `wt config shell install`, then restart the shell (or reload its
|
||||
config) to activate the current wrapper.
|
||||
**Fix**: Run `wt config shell install`, then restart the shell (or reload its config) to activate the current wrapper.
|
||||
|
||||
### "shell integration not installed"
|
||||
|
||||
**Meaning**: The current shell's config file doesn't have the
|
||||
`eval "$(wt config shell init ...)"` line. The current shell is detected from
|
||||
the process tree (falling back to `$SHELL`), so this refers to the shell wt
|
||||
was actually invoked from, not necessarily the login shell.
|
||||
**Meaning**: The current shell's config file doesn't have the `eval "$(wt config shell init ...)"` line. The current shell is detected from the process tree (falling back to `$SHELL`), so this refers to the shell `wt` was actually invoked from, not necessarily the login shell.
|
||||
|
||||
**Fix**: Run `wt config shell install` or add the line manually.
|
||||
|
||||
### "shell integration installed but not active"
|
||||
|
||||
**Meaning**: Shell integration is configured for the current shell, but the
|
||||
shell function isn't loaded in this session — usually because the session was
|
||||
started before installation.
|
||||
**Meaning**: Shell integration is configured for the current shell, but the shell function isn't loaded in this session — usually because the session was started before installation.
|
||||
|
||||
**Fix**: Start a new terminal or run `source ~/.bashrc` (or equivalent). If
|
||||
the message persists after a restart, `wt config show` reports the detected
|
||||
shell, `$SHELL`, and per-shell integration status.
|
||||
**Fix**: Start a new terminal or run `source ~/.bashrc` (or equivalent). If the message persists after a restart, `wt config show` reports the detected shell, `$SHELL`, and per-shell integration status.
|
||||
|
||||
### "ran ./path/to/wt; shell integration wraps wt"
|
||||
|
||||
**Meaning**: The binary was invoked with an explicit path (like `./target/debug/wt`
|
||||
or `/usr/local/bin/wt`) instead of just `wt`. The shell wrapper only intercepts
|
||||
the bare command `wt`.
|
||||
**Meaning**: The binary was invoked with an explicit path (like `./target/debug/wt` or `/usr/local/bin/wt`) instead of just `wt`. The shell wrapper only intercepts the bare command `wt`.
|
||||
|
||||
**Fix**: Use `wt` without a path. For testing dev builds, set `WORKTRUNK_BIN`:
|
||||
|
||||
```bash
|
||||
export WORKTRUNK_BIN=./target/debug/wt
|
||||
wt switch feature # Now uses the dev build with shell integration
|
||||
@@ -93,21 +93,18 @@ wt switch feature # Now uses the dev build with shell integration
|
||||
|
||||
### "ran git wt; running through git prevents cd"
|
||||
|
||||
**Meaning**: `git wt` (git alias) was used instead of `wt`. Git runs worktrunk as
|
||||
a subprocess, bypassing the shell wrapper.
|
||||
**Meaning**: `git wt` (git alias) was used instead of `wt`. Git runs worktrunk as a subprocess, bypassing the shell wrapper.
|
||||
|
||||
**Fix**: Use `wt` directly instead of `git wt` when directory switching is needed.
|
||||
|
||||
### "Alias bypasses shell integration"
|
||||
|
||||
**Meaning**: An alias like `alias gwt="/usr/bin/wt"` or `alias gwt="wt.exe"`
|
||||
points directly to the binary instead of the shell function.
|
||||
**Meaning**: An alias like `alias gwt="/usr/bin/wt"` or `alias gwt="wt.exe"` points directly to the binary instead of the shell function.
|
||||
|
||||
When shell integration is installed, it creates a shell function named `wt` (or
|
||||
`git-wt`). If the alias points to the binary path, it bypasses this function
|
||||
and shell integration won't work.
|
||||
When shell integration is installed, it creates a shell function named `wt` (or `git-wt`). If the alias points to the binary path, it bypasses this function and shell integration won't work.
|
||||
|
||||
**Examples that bypass** (won't auto-cd):
|
||||
|
||||
```bash
|
||||
alias gwt="/usr/bin/wt"
|
||||
alias gwt="wt.exe"
|
||||
@@ -115,18 +112,17 @@ alias wt="/path/to/wt"
|
||||
```
|
||||
|
||||
**Fix**: Change the alias to point to the function name instead of the binary:
|
||||
|
||||
```bash
|
||||
alias gwt="wt" # Good - uses the shell function
|
||||
alias gwt="git-wt" # Good - uses the shell function
|
||||
```
|
||||
|
||||
`wt config show` detects these problematic aliases and shows a warning with the
|
||||
suggested fix.
|
||||
`wt config show` detects these problematic aliases and shows a warning with the suggested fix.
|
||||
|
||||
## How the Shell Wrapper Works
|
||||
## How the shell wrapper works
|
||||
|
||||
The shell wrapper (installed by `wt config shell install`) defines a shell
|
||||
function that:
|
||||
The shell wrapper (installed by `wt config shell install`) defines a shell function that:
|
||||
|
||||
1. Creates a temp file
|
||||
2. Sets `WORKTRUNK_DIRECTIVE_CD_FILE`
|
||||
@@ -134,7 +130,8 @@ function that:
|
||||
4. Reads the CD file with `cd -- "$(< file)"` (raw path, no shell parsing)
|
||||
5. Cleans up the temp file
|
||||
|
||||
Simplified example (actual wrapper handles completions and edge cases):
|
||||
Simplified example (the actual wrapper also handles completions and edge cases):
|
||||
|
||||
```bash
|
||||
wt() {
|
||||
local cd_file exit_code=0
|
||||
@@ -151,39 +148,39 @@ wt() {
|
||||
}
|
||||
```
|
||||
|
||||
## Debugging Checklist
|
||||
## Debugging checklist
|
||||
|
||||
### 1. Check if wrapper is installed
|
||||
### 1. Check whether the wrapper is loaded
|
||||
|
||||
```bash
|
||||
# Should show shell function, not binary path
|
||||
# Should show a shell function, not a binary path
|
||||
type wt
|
||||
|
||||
# Expected output (bash/zsh):
|
||||
# wt is a function
|
||||
# wt () { ... }
|
||||
|
||||
# If it shows a path like /usr/local/bin/wt, wrapper isn't loaded
|
||||
# If it shows a path like /usr/local/bin/wt, the wrapper isn't loaded
|
||||
```
|
||||
|
||||
### 1b. Check if wrapper is installed (PowerShell)
|
||||
### 2. Check whether the wrapper is loaded (PowerShell)
|
||||
|
||||
```powershell
|
||||
# PowerShell: should show Function, not just Application
|
||||
Get-Command wt -All
|
||||
|
||||
# Expected output when wrapper is loaded:
|
||||
# Expected output when the wrapper is loaded:
|
||||
# CommandType Name Source
|
||||
# ----------- ---- ------
|
||||
# Function wt
|
||||
# Application wt C:\Users\...\wt.exe
|
||||
|
||||
# If only Application appears, wrapper isn't loaded (restart shell)
|
||||
# If only Application appears, the wrapper isn't loaded (restart the shell)
|
||||
# If Function appears but integration is still "not active", check the body:
|
||||
(Get-Command wt -CommandType Function).ScriptBlock | Select-String WORKTRUNK
|
||||
```
|
||||
|
||||
### 2. Check shell config file
|
||||
### 3. Check the shell config file
|
||||
|
||||
```bash
|
||||
# bash
|
||||
@@ -196,21 +193,21 @@ grep -n "wt config shell init" ~/.zshrc
|
||||
grep -n "wt config shell init" ~/.config/fish/config.fish
|
||||
```
|
||||
|
||||
Should show the `eval` line with line number.
|
||||
This should show the `eval` line with its line number.
|
||||
|
||||
### 3. Check if directive files are set
|
||||
### 4. Check whether directive files are set
|
||||
|
||||
```bash
|
||||
# After running any wt command, this should be unset (temp file deleted)
|
||||
# After running any wt command, this should be unset (the temp file is deleted)
|
||||
echo $WORKTRUNK_DIRECTIVE_CD_FILE
|
||||
|
||||
# During wt execution, these would be set to temp file paths
|
||||
# During wt execution, this is set to a temp file path
|
||||
```
|
||||
|
||||
### 4. Test directive files manually
|
||||
### 5. Test directive files manually
|
||||
|
||||
```bash
|
||||
# Create temp files and test
|
||||
# Create the temp file and test
|
||||
export WORKTRUNK_DIRECTIVE_CD_FILE=$(mktemp)
|
||||
command wt switch feature
|
||||
cat $WORKTRUNK_DIRECTIVE_CD_FILE # Should contain: /path/to/worktree (raw path)
|
||||
@@ -218,11 +215,12 @@ cd -- "$(<$WORKTRUNK_DIRECTIVE_CD_FILE)" # Should cd you there
|
||||
rm -f $WORKTRUNK_DIRECTIVE_CD_FILE
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
## Common issues
|
||||
|
||||
### Shell integration works in terminal but not in IDE terminal
|
||||
### Shell integration works in the terminal but not in an IDE terminal
|
||||
|
||||
IDE terminals may use different shell configs. Check:
|
||||
|
||||
- VS Code: Settings → Terminal → Integrated → Shell Args
|
||||
- The IDE terminal might source a different profile
|
||||
|
||||
@@ -241,20 +239,14 @@ eval "$(wt config shell init zsh)"
|
||||
|
||||
### Windows Git Bash issues
|
||||
|
||||
Git Bash uses MSYS2, which automatically converts POSIX paths in environment
|
||||
variables. The directive file path is handled correctly without manual conversion.
|
||||
Git Bash uses MSYS2, which automatically converts POSIX paths in environment variables. The directive file path is handled correctly without manual conversion.
|
||||
|
||||
If you see path issues, ensure you're using a recent Git for Windows version.
|
||||
If you see path issues, make sure you're on a recent Git for Windows version.
|
||||
|
||||
## Environment Variables
|
||||
## Environment variables
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `WORKTRUNK_DIRECTIVE_CD_FILE` | Set by shell wrapper; wt writes a raw path, wrapper `cd`s to it |
|
||||
| `WORKTRUNK_BIN` | Override binary path (for testing dev builds) |
|
||||
| `WORKTRUNK_DIRECTIVE_CD_FILE` | Set by the shell wrapper; `wt` writes a raw path, the wrapper `cd`s to it |
|
||||
| `WORKTRUNK_BIN` | Override the binary path (for testing dev builds) |
|
||||
| `WORKTRUNK_COMPLETE_NAME` | Set by the bash, zsh, and PowerShell wrappers when they load completions; names the command the registration binds to, so `--cmd` integrations complete |
|
||||
|
||||
## See Also
|
||||
|
||||
- `wt config shell --help` — Shell integration commands
|
||||
- `wt config show` — View current configuration and status
|
||||
|
||||
+2
-218
@@ -176,24 +176,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step squash
|
||||
@@ -280,24 +262,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step rebase
|
||||
@@ -359,24 +323,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step push
|
||||
@@ -429,24 +375,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step diff
|
||||
@@ -517,24 +445,6 @@ Options:
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step copy-ignored
|
||||
@@ -686,24 +596,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step eval
|
||||
@@ -783,24 +675,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step for-each
|
||||
@@ -865,24 +739,6 @@ Options:
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step promote
|
||||
@@ -959,24 +815,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step prune
|
||||
@@ -991,11 +829,11 @@ Locked worktrees and the main worktree are always skipped. The current worktree
|
||||
|
||||
### Min-age guard
|
||||
|
||||
Worktrees and branches younger than `--min-age` (default: 1 day) are skipped. This prevents removing a worktree just created from the default branch — it looks "merged" because its branch points at the same commit.
|
||||
Candidates younger than `--min-age` (default: 1 day) are skipped. A worktree's age comes from its creation time, and a branch with no worktree takes its age from its oldest reflog entry. This prevents removing a worktree just created from the default branch: it looks "merged" because its branch points at the same commit.
|
||||
|
||||
```console
|
||||
$ wt step prune --min-age=0s # no age guard
|
||||
$ wt step prune --min-age=2d # skip worktrees younger than 2 days
|
||||
$ wt step prune --min-age=2d # skip candidates younger than 2 days
|
||||
```
|
||||
|
||||
### JSON output
|
||||
@@ -1043,24 +881,6 @@ Options:
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step relocate
|
||||
@@ -1164,24 +984,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step tether
|
||||
@@ -1249,22 +1051,4 @@ Arguments:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
+260
-267
@@ -2,7 +2,9 @@
|
||||
|
||||
Practical recipes for common Worktrunk workflows.
|
||||
|
||||
## Shell alias for new worktree + agent
|
||||
## Setup and layout
|
||||
|
||||
### Shell alias for new worktree + agent
|
||||
|
||||
Create a worktree and launch Claude in one command:
|
||||
|
||||
@@ -12,7 +14,106 @@ wsc new-feature # Creates worktree, runs hooks, launches C
|
||||
wsc feature -- 'Fix GH #322' # Runs `claude 'Fix GH #322'`
|
||||
```
|
||||
|
||||
## `wt` aliases
|
||||
### Shortcuts
|
||||
|
||||
Special arguments work across all commands—see [`wt switch`](https://worktrunk.dev/switch/#shortcuts) for the full list.
|
||||
|
||||
```bash
|
||||
wt switch --create hotfix --base=@ # Branch from current HEAD
|
||||
wt switch - # Switch to previous worktree
|
||||
wt remove @ # Remove current worktree
|
||||
```
|
||||
|
||||
### Stacked branches
|
||||
|
||||
Branch from current HEAD instead of the default branch:
|
||||
|
||||
```bash
|
||||
wt switch --create feature-part2 --base=@
|
||||
```
|
||||
|
||||
### Reuse `default-branch`
|
||||
|
||||
Default branch [detection](https://worktrunk.dev/config/#wt-config-state-default-branch) means scripts work on any repo — no need to hardcode `main` or `master`:
|
||||
|
||||
```bash
|
||||
git rebase $(wt config state default-branch)
|
||||
```
|
||||
|
||||
In hooks and aliases, the same value is the `{{ default_branch }}` [template variable](https://worktrunk.dev/hook/#template-variables); reserve this command for plain shell scripts.
|
||||
|
||||
### Override `default-branch` for one clone
|
||||
|
||||
When the integration branch differs from the remote's `HEAD`, set a [clone-local override](https://worktrunk.dev/config/#wt-config-state-default-branch):
|
||||
|
||||
```bash
|
||||
wt config state default-branch set integration
|
||||
```
|
||||
|
||||
### Bare repository layout
|
||||
|
||||
A [bare repository](https://git-scm.com/docs/gitrepository-layout) has no working tree, so all branches — including the default — are [linked worktrees](https://git-scm.com/docs/git-worktree) at equal paths. No branch gets special treatment.
|
||||
|
||||
Cloning a bare repo into `<project>/.git` puts all worktrees under one directory:
|
||||
|
||||
```bash
|
||||
git clone --bare <url> myproject/.git
|
||||
cd myproject
|
||||
```
|
||||
|
||||
With `worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}"`, worktrees become subdirectories of `myproject/`:
|
||||
|
||||
```
|
||||
myproject/
|
||||
├── .git/ # bare repository
|
||||
├── main/ # default branch worktree
|
||||
├── feature/ # feature branch worktree
|
||||
└── bugfix/ # bugfix branch worktree
|
||||
```
|
||||
|
||||
#### Configure the worktree path
|
||||
|
||||
On first `wt switch` in a bare repo at a hidden path (`.git`, `.bare`), worktrunk detects that the default template would produce broken paths like `myproject/.git.main` and offers a fix:
|
||||
|
||||
```
|
||||
▲ Bare repo at myproject/.git — worktrees will be at myproject/.git.main
|
||||
◎ Configure worktree-path to place worktrees at myproject/main? [y/N/?]
|
||||
```
|
||||
|
||||
Accepting writes a project-scoped entry to user config:
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[projects."github.com/myorg/myrepo"]
|
||||
worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}"
|
||||
```
|
||||
|
||||
Run `wt config show` from inside any worktree to find the project identifier (`Identifier: …` in the PROJECT CONFIG section). Set it globally with `worktree-path = "..."` at the top level if this layout is preferred for all bare repos.
|
||||
|
||||
#### Create the first worktree
|
||||
|
||||
```bash
|
||||
wt switch main
|
||||
```
|
||||
|
||||
For a freshly cloned bare repo the default branch already exists, so `wt switch main` (without `--create`) is enough. Use `wt switch --create <branch>` for new branches.
|
||||
|
||||
Now `wt switch --create feature` creates `myproject/feature/`.
|
||||
|
||||
#### Set up the project config
|
||||
|
||||
The project config (`.config/wt.toml`) must live inside a worktree — the bare `.git` directory has no tracked files. Once the first worktree exists, create it from there:
|
||||
|
||||
```bash
|
||||
cd myproject/main
|
||||
wt config create --project
|
||||
```
|
||||
|
||||
Commit the file and it will appear in every worktree automatically.
|
||||
|
||||
## Aliases and hooks
|
||||
|
||||
### `wt` aliases
|
||||
|
||||
Compose with template filters and [vars](https://worktrunk.dev/tips-patterns/#per-branch-variables):
|
||||
|
||||
@@ -31,7 +132,7 @@ pick = "wt switch --format=json | jq -r '.branch'"
|
||||
|
||||
See [Aliases](https://worktrunk.dev/extending/#aliases) for scoping, approval, and reference.
|
||||
|
||||
## Per-branch variables
|
||||
### Per-branch variables
|
||||
|
||||
`wt config state vars` holds state per branch, accessible from templates (`{{ vars.key }}`) and the CLI. Some uses:
|
||||
|
||||
@@ -41,7 +142,53 @@ See [Aliases](https://worktrunk.dev/extending/#aliases) for scoping, approval, a
|
||||
|
||||
See [`wt config state vars`](https://worktrunk.dev/config/#wt-config-state-vars) for storage format, JSON support, and reference.
|
||||
|
||||
## Dev server per worktree
|
||||
### Task runners in hooks
|
||||
|
||||
Reference Taskfile/Justfile/Makefile in hooks:
|
||||
|
||||
```toml
|
||||
[pre-start]
|
||||
"setup" = "task install"
|
||||
|
||||
[pre-merge]
|
||||
"validate" = "just test lint"
|
||||
```
|
||||
|
||||
### Progressive validation
|
||||
|
||||
Split checks across hook types — quick feedback before each commit, expensive suites before merge:
|
||||
|
||||
```toml
|
||||
[[pre-commit]]
|
||||
lint = "npm run lint"
|
||||
typecheck = "npm run typecheck"
|
||||
|
||||
[[pre-merge]]
|
||||
test = "npm test"
|
||||
build = "npm run build"
|
||||
```
|
||||
|
||||
`pre-commit` runs during `wt merge`, before the squash commit; `pre-merge` runs once per merge after the rebase, so it's the right place for the slow tests.
|
||||
|
||||
### Target-specific hooks
|
||||
|
||||
Branch on `{{ target }}` to vary behavior per merge destination — for example, deploying to production from `main` and staging from a release branch:
|
||||
|
||||
```toml
|
||||
post-merge = """
|
||||
if [ {{ target }} = main ]; then
|
||||
npm run deploy:production
|
||||
elif [ {{ target }} = staging ]; then
|
||||
npm run deploy:staging
|
||||
fi
|
||||
"""
|
||||
```
|
||||
|
||||
`{{ target }}` is the branch being merged into. `post-merge` runs in the target's worktree (or the primary worktree if target has none), so deploy commands see the merged code.
|
||||
|
||||
## Per-worktree services
|
||||
|
||||
### Dev server per worktree
|
||||
|
||||
Each worktree runs its own dev server on a deterministic port. The `hash_port` filter generates a stable port (10000-19999) from the branch name:
|
||||
|
||||
@@ -71,7 +218,7 @@ $ wt list
|
||||
|
||||
`fix-auth` always gets port 16460, on any machine. The URL dims if the server isn't running.
|
||||
|
||||
## Database per worktree
|
||||
### Database per worktree
|
||||
|
||||
Each worktree can have its own isolated database. A pipeline sets up names and ports as [vars](https://worktrunk.dev/config/#wt-config-state-vars), then later steps and hooks reference them:
|
||||
|
||||
@@ -108,7 +255,7 @@ The connection string is accessible anywhere — not just in hooks:
|
||||
DATABASE_URL=$(wt config state vars get db_url) npm start
|
||||
```
|
||||
|
||||
## Per-worktree env vars
|
||||
### Per-worktree env vars
|
||||
|
||||
To scope environment variables to a worktree — a tool's package path, a profile, an API endpoint — use a directory environment manager like [direnv](https://direnv.net) or [mise](https://mise.jdx.dev). Both hook the shell prompt, so they activate on the `cd` that `wt switch` already performs — no worktrunk configuration needed. Commit the config at the repo root and every worktree gets its own copy, with paths resolving relative to that worktree.
|
||||
|
||||
@@ -131,7 +278,7 @@ MY_PACKAGES_PATH = "{{ config_root }}/.packages"
|
||||
|
||||
Both set real environment variables in the shell session, so every child process inherits them — hooks, build tools, subshells — without the `--execute` workaround. Each new worktree is a new path, so it needs its own one-time trust step (`direnv allow` / `mise trust`); worktrunk deliberately doesn't bypass that prompt, the same safety reasoning behind [disabling `--execute` in project alias and hook bodies](https://github.com/max-sixty/worktrunk/issues/2101).
|
||||
|
||||
## Eliminate cold starts
|
||||
### Eliminate cold starts
|
||||
|
||||
Use [`wt step copy-ignored`](https://worktrunk.dev/step/#wt-step-copy-ignored) to copy gitignored files (caches, dependencies, `.env`) between worktrees:
|
||||
|
||||
@@ -154,184 +301,43 @@ Use `pre-start` instead when an `--execute` command needs the copied files immed
|
||||
|
||||
All gitignored files are copied by default. To limit what gets copied, create `.worktreeinclude` with patterns — files must be both gitignored and listed. See [`wt step copy-ignored`](https://worktrunk.dev/step/#wt-step-copy-ignored) for details.
|
||||
|
||||
## Manual commit messages
|
||||
### Subdomain routing with Caddy
|
||||
|
||||
The `commit.generation.command` receives the rendered prompt on stdin and returns the commit message on stdout. To write commit messages by hand instead of using an LLM, point it at `$EDITOR`:
|
||||
<!-- Hand-tested 2026-03-07 -->
|
||||
|
||||
Clean URLs like `http://feature-auth.myproject.localhost` without port numbers. Useful for cookies, CORS, and matching production URL structure.
|
||||
|
||||
**Prerequisites:** [Caddy](https://caddyserver.com/docs/install) (`brew install caddy`)
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[commit.generation]
|
||||
command = '''f=$(mktemp); printf '\n\n' > "$f"; sed 's/^/# /' >> "$f"; ${EDITOR:-vi} "$f" < /dev/tty > /dev/tty; grep -v '^#' "$f"'''
|
||||
```
|
||||
|
||||
This comments out the rendered prompt (diff, branch name, stats) with `#` prefixes, opens your editor, and strips comment lines on save. A couple of blank lines at the top give you space to type; the prompt context is visible below for reference.
|
||||
|
||||
To keep the LLM as default but use the editor for a specific merge, add a [worktrunk alias](https://worktrunk.dev/extending/#aliases):
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[aliases]
|
||||
mc = '''WORKTRUNK_COMMIT__GENERATION__COMMAND='f=$(mktemp); printf "\n\n" > "$f"; sed "s/^/# /" >> "$f"; ${EDITOR:-vi} "$f" < /dev/tty > /dev/tty; grep -v "^#" "$f"' wt merge'''
|
||||
```
|
||||
|
||||
Then `wt mc` opens an editor for the commit message while plain `wt merge` continues to use the LLM.
|
||||
|
||||
## Track agent status
|
||||
|
||||
Custom emoji markers show agent state in `wt list`. The [Claude Code](https://worktrunk.dev/claude-code/) plugin and [OpenCode plugin](https://github.com/max-sixty/worktrunk/tree/main/dev/opencode-plugin.ts) set these automatically:
|
||||
|
||||
```
|
||||
+ feature-api ↑ 🤖 ↑1 ./repo.feature-api
|
||||
+ review-ui ? ↑ 💬 ↑1 ./repo.review-ui
|
||||
```
|
||||
|
||||
- `🤖` — Agent is working
|
||||
- `💬` — Agent is waiting for input
|
||||
|
||||
Set status manually for any workflow:
|
||||
|
||||
```bash
|
||||
wt config state marker set "🚧" # Current branch
|
||||
wt config state marker set "✅" --branch feature # Specific branch
|
||||
git config worktrunk.state.feature.marker '{"marker":"💬","set_at":0}' # Direct
|
||||
```
|
||||
|
||||
See [Claude Code Integration](https://worktrunk.dev/claude-code/#installation) for plugin installation.
|
||||
|
||||
## Monitor CI across branches
|
||||
|
||||
```bash
|
||||
wt list --full --branches
|
||||
```
|
||||
|
||||
Shows PR/CI status for all branches, including those without worktrees. CI indicators are clickable links to the PR page.
|
||||
|
||||
## LLM branch summaries
|
||||
|
||||
With `summary = true` and [`commit.generation`](https://worktrunk.dev/config/#commit) configured, `wt list --full` shows an LLM-generated one-line summary for each branch. The same summaries appear in the `wt switch` picker (tab 5).
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[list]
|
||||
summary = true
|
||||
```
|
||||
|
||||
See [LLM Commits](https://worktrunk.dev/llm-commits/#branch-summaries) for details.
|
||||
|
||||
## JSON API
|
||||
|
||||
```bash
|
||||
wt list --format=json
|
||||
```
|
||||
|
||||
Structured output for dashboards, statuslines, and scripts. See [`wt list`](https://worktrunk.dev/list/) for query examples.
|
||||
|
||||
## Reuse `default-branch`
|
||||
|
||||
Default branch [detection](https://worktrunk.dev/config/#wt-config-state-default-branch) means scripts work on any repo — no need to hardcode `main` or `master`:
|
||||
|
||||
```bash
|
||||
git rebase $(wt config state default-branch)
|
||||
```
|
||||
|
||||
In hooks and aliases, the same value is the `{{ default_branch }}` [template variable](https://worktrunk.dev/hook/#template-variables); reserve this command for plain shell scripts.
|
||||
|
||||
## Override `default-branch` for one clone
|
||||
|
||||
When the integration branch differs from the remote's `HEAD`, set a [clone-local override](https://worktrunk.dev/config/#wt-config-state-default-branch):
|
||||
|
||||
```bash
|
||||
wt config state default-branch set integration
|
||||
```
|
||||
|
||||
## Task runners in hooks
|
||||
|
||||
Reference Taskfile/Justfile/Makefile in hooks:
|
||||
|
||||
```toml
|
||||
[pre-start]
|
||||
"setup" = "task install"
|
||||
|
||||
[pre-merge]
|
||||
"validate" = "just test lint"
|
||||
```
|
||||
|
||||
## Progressive validation
|
||||
|
||||
Split checks across hook types — quick feedback before each commit, expensive suites before merge:
|
||||
|
||||
```toml
|
||||
[[pre-commit]]
|
||||
lint = "npm run lint"
|
||||
typecheck = "npm run typecheck"
|
||||
|
||||
[[pre-merge]]
|
||||
test = "npm test"
|
||||
build = "npm run build"
|
||||
```
|
||||
|
||||
`pre-commit` runs during `wt merge`, before the squash commit; `pre-merge` runs once per merge after the rebase, so it's the right place for the slow tests.
|
||||
|
||||
## Target-specific hooks
|
||||
|
||||
Branch on `{{ target }}` to vary behavior per merge destination — for example, deploying to production from `main` and staging from a release branch:
|
||||
|
||||
```toml
|
||||
post-merge = """
|
||||
if [ {{ target }} = main ]; then
|
||||
npm run deploy:production
|
||||
elif [ {{ target }} = staging ]; then
|
||||
npm run deploy:staging
|
||||
fi
|
||||
# .config/wt.toml
|
||||
[post-start]
|
||||
server = "wt step tether -- npm run dev -- --port {{ branch | hash_port }}"
|
||||
proxy = """
|
||||
curl -sf --max-time 0.5 http://localhost:2019/config/ || caddy start
|
||||
curl -sf http://localhost:2019/config/apps/http/servers/wt || \
|
||||
curl -sfX PUT http://localhost:2019/config/apps/http/servers/wt -H 'Content-Type: application/json' \
|
||||
-d '{"listen":[":8080"],"automatic_https":{"disable":true},"routes":[]}'
|
||||
curl -sf -X DELETE http://localhost:2019/id/wt:{{ repo }}:{{ branch | sanitize }} || true
|
||||
curl -sfX PUT http://localhost:2019/config/apps/http/servers/wt/routes/0 -H 'Content-Type: application/json' \
|
||||
-d '{"@id":"wt:{{ repo }}:{{ branch | sanitize }}","match":[{"host":["{{ branch | sanitize }}.{{ repo }}.localhost"]}],"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"127.0.0.1:{{ branch | hash_port }}"}]}]}'
|
||||
"""
|
||||
|
||||
[pre-remove]
|
||||
proxy = "curl -sf -X DELETE http://localhost:2019/id/wt:{{ repo }}:{{ branch | sanitize }} || true"
|
||||
|
||||
[list]
|
||||
url = "http://{{ branch | sanitize }}.{{ repo }}.localhost:8080"
|
||||
```
|
||||
|
||||
`{{ target }}` is the branch being merged into. `post-merge` runs in the target's worktree (or the primary worktree if target has none), so deploy commands see the merged code.
|
||||
**How it works:**
|
||||
|
||||
## Shortcuts
|
||||
1. `wt switch --create feature-auth` runs the `post-start` hook, starting the dev server on a deterministic port (`{{ branch | hash_port }}` → 18283)
|
||||
2. The hook starts Caddy if needed and registers a route using the same port: `feature-auth.myproject` → `localhost:18283`
|
||||
3. `*.localhost` resolves to `127.0.0.1` via the OS
|
||||
4. Visiting `http://feature-auth.myproject.localhost:8080`: Caddy matches the subdomain and proxies to the dev server
|
||||
|
||||
Special arguments work across all commands—see [`wt switch`](https://worktrunk.dev/switch/#shortcuts) for the full list.
|
||||
|
||||
```bash
|
||||
wt switch --create hotfix --base=@ # Branch from current HEAD
|
||||
wt switch - # Switch to previous worktree
|
||||
wt remove @ # Remove current worktree
|
||||
```
|
||||
|
||||
## Stacked branches
|
||||
|
||||
Branch from current HEAD instead of the default branch:
|
||||
|
||||
```bash
|
||||
wt switch --create feature-part2 --base=@
|
||||
```
|
||||
|
||||
## Agent handoffs
|
||||
|
||||
Spawn a worktree with an agent CLI running in the background. Examples below use `claude`; for OpenCode, replace `claude` with `'opencode run'`.
|
||||
|
||||
**tmux** (new detached session):
|
||||
```bash
|
||||
tmux new-session -d -s fix-auth-bug "wt switch --create fix-auth-bug -x claude -- \
|
||||
'The login session expires after 5 minutes. Find the session timeout config and extend it to 24 hours.'"
|
||||
```
|
||||
|
||||
**Zellij** (new pane in current session):
|
||||
```bash
|
||||
zellij run -- wt switch --create fix-auth-bug -x claude -- \
|
||||
'The login session expires after 5 minutes. Find the session timeout config and extend it to 24 hours.'
|
||||
```
|
||||
|
||||
This lets one agent session hand off work to another that runs in the background. Hooks run inside the multiplexer session/pane.
|
||||
|
||||
The [worktrunk skill](https://worktrunk.dev/claude-code/) includes guidance for Claude Code (and other agent CLIs that load it) to execute this pattern. To enable it, request it explicitly ("spawn a parallel worktree for...") or add to your project instructions (`CLAUDE.md` or `AGENTS.md`):
|
||||
|
||||
```markdown
|
||||
When I ask you to spawn parallel worktrees, use the agent handoff pattern
|
||||
from the worktrunk skill.
|
||||
```
|
||||
|
||||
## Tmux session per worktree
|
||||
### Tmux session per worktree
|
||||
|
||||
Each worktree gets its own tmux session with a multi-pane layout.
|
||||
|
||||
@@ -367,7 +373,7 @@ To create a worktree and immediately attach:
|
||||
$ wt switch --create feature -x tmux -- attach -t '{{ branch | sanitize }}'
|
||||
```
|
||||
|
||||
## cmux workspace per worktree
|
||||
### cmux workspace per worktree
|
||||
|
||||
Each worktree gets its own [cmux](https://cmux.com) workspace. Switching worktrees switches workspaces; removing a worktree closes its workspace. Configuration contributed by [@endigma](https://github.com/endigma) ([#2796](https://github.com/max-sixty/worktrunk/issues/2796)).
|
||||
|
||||
@@ -402,7 +408,7 @@ WS=$(cmux --json list-workspaces 2>/dev/null \\
|
||||
|
||||
**Why `pre-*` instead of `post-*`?** cmux restricts socket access to processes spawned inside a cmux terminal. `post-*` hooks run as detached background processes, breaking the process ancestry chain. `pre-*` hooks run in the foreground and inherit the terminal's process lineage.
|
||||
|
||||
## Xcode DerivedData cleanup
|
||||
### Xcode DerivedData cleanup
|
||||
|
||||
Clean up Xcode's DerivedData when removing a worktree. Each DerivedData directory contains an `info.plist` recording its project path — grep for the worktree path to find and remove the matching build cache:
|
||||
|
||||
@@ -420,42 +426,90 @@ clean-derived = """
|
||||
"""
|
||||
```
|
||||
|
||||
## Subdomain routing with Caddy
|
||||
<!-- Hand-tested 2026-03-07 -->
|
||||
## Working with agents
|
||||
|
||||
Clean URLs like `http://feature-auth.myproject.localhost` without port numbers. Useful for cookies, CORS, and matching production URL structure.
|
||||
### Track agent status
|
||||
|
||||
**Prerequisites:** [Caddy](https://caddyserver.com/docs/install) (`brew install caddy`)
|
||||
The agent plugins mark each worktree 🤖 (working) or 💬 (waiting) in `wt list`, and `wt config state marker set` sets a marker by hand for any other workflow. See [Activity tracking](https://worktrunk.dev/claude-code/#activity-tracking).
|
||||
|
||||
```toml
|
||||
# .config/wt.toml
|
||||
[post-start]
|
||||
server = "wt step tether -- npm run dev -- --port {{ branch | hash_port }}"
|
||||
proxy = """
|
||||
curl -sf --max-time 0.5 http://localhost:2019/config/ || caddy start
|
||||
curl -sf http://localhost:2019/config/apps/http/servers/wt || \
|
||||
curl -sfX PUT http://localhost:2019/config/apps/http/servers/wt -H 'Content-Type: application/json' \
|
||||
-d '{"listen":[":8080"],"automatic_https":{"disable":true},"routes":[]}'
|
||||
curl -sf -X DELETE http://localhost:2019/id/wt:{{ repo }}:{{ branch | sanitize }} || true
|
||||
curl -sfX PUT http://localhost:2019/config/apps/http/servers/wt/routes/0 -H 'Content-Type: application/json' \
|
||||
-d '{"@id":"wt:{{ repo }}:{{ branch | sanitize }}","match":[{"host":["{{ branch | sanitize }}.{{ repo }}.localhost"]}],"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"127.0.0.1:{{ branch | hash_port }}"}]}]}'
|
||||
"""
|
||||
### Agent handoffs
|
||||
|
||||
[pre-remove]
|
||||
proxy = "curl -sf -X DELETE http://localhost:2019/id/wt:{{ repo }}:{{ branch | sanitize }} || true"
|
||||
Spawn a worktree with an agent CLI running in the background. `-x` names the program to run and everything after `--` is passed to it, so OpenCode's subcommand goes after the `--`: `-x opencode -- run '<task>'`.
|
||||
|
||||
[list]
|
||||
url = "http://{{ branch | sanitize }}.{{ repo }}.localhost:8080"
|
||||
**tmux** (new detached session):
|
||||
```bash
|
||||
tmux new-session -d -s fix-auth-bug "wt switch --create fix-auth-bug -x claude -- \
|
||||
'The login session expires after 5 minutes. Find the session timeout config and extend it to 24 hours.'"
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
**Zellij** (new pane in current session):
|
||||
```bash
|
||||
zellij run -- wt switch --create fix-auth-bug -x claude -- \
|
||||
'The login session expires after 5 minutes. Find the session timeout config and extend it to 24 hours.'
|
||||
```
|
||||
|
||||
1. `wt switch --create feature-auth` runs the `post-start` hook, starting the dev server on a deterministic port (`{{ branch | hash_port }}` → 16460)
|
||||
2. The hook starts Caddy if needed and registers a route using the same port: `feature-auth.myproject` → `localhost:16460`
|
||||
3. `*.localhost` resolves to `127.0.0.1` via the OS
|
||||
4. Visiting `http://feature-auth.myproject.localhost:8080`: Caddy matches the subdomain and proxies to the dev server
|
||||
This lets one agent session hand off work to another that runs in the background. Hooks run inside the multiplexer session/pane.
|
||||
|
||||
## Monitor hook logs
|
||||
The [worktrunk skill](https://worktrunk.dev/claude-code/) includes guidance for Claude Code (and other agent CLIs that load it) to execute this pattern. To enable it, request it explicitly ("spawn a parallel worktree for...") or add to your project instructions (`CLAUDE.md` or `AGENTS.md`):
|
||||
|
||||
```markdown
|
||||
When I ask you to spawn parallel worktrees, use the agent handoff pattern
|
||||
from the worktrunk skill.
|
||||
```
|
||||
|
||||
## Status, commits, and logs
|
||||
|
||||
### Monitor CI across branches
|
||||
|
||||
```bash
|
||||
wt list --full --branches
|
||||
```
|
||||
|
||||
Shows PR/CI status for all branches, including those without worktrees. CI indicators are clickable links to the PR page.
|
||||
|
||||
### LLM branch summaries
|
||||
|
||||
With `summary = true` and [`commit.generation`](https://worktrunk.dev/config/#commit) configured, `wt list --full` shows an LLM-generated one-line summary for each branch. The same summaries appear in the [`wt switch` picker](https://worktrunk.dev/switch/#interactive-picker)'s `summary` tab.
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[list]
|
||||
summary = true
|
||||
```
|
||||
|
||||
See [LLM Commits](https://worktrunk.dev/llm-commits/#branch-summaries) for details.
|
||||
|
||||
### JSON API
|
||||
|
||||
```bash
|
||||
wt list --format=json
|
||||
```
|
||||
|
||||
Structured output for dashboards, statuslines, and scripts. See [`wt list`](https://worktrunk.dev/list/) for query examples.
|
||||
|
||||
### Manual commit messages
|
||||
|
||||
The `commit.generation.command` receives the rendered prompt on stdin and returns the commit message on stdout. To write commit messages by hand instead of using an LLM, point it at `$EDITOR`:
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[commit.generation]
|
||||
command = '''f=$(mktemp); printf '\n\n' > "$f"; sed 's/^/# /' >> "$f"; ${EDITOR:-vi} "$f" < /dev/tty > /dev/tty; grep -v '^#' "$f"'''
|
||||
```
|
||||
|
||||
This comments out the rendered prompt (diff, branch name, stats) with `#` prefixes, opens your editor, and strips comment lines on save. A couple of blank lines at the top give you space to type; the prompt context is visible below for reference.
|
||||
|
||||
To keep the LLM as default but use the editor for a specific merge, add a [worktrunk alias](https://worktrunk.dev/extending/#aliases):
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[aliases]
|
||||
mc = '''WORKTRUNK_COMMIT__GENERATION__COMMAND='f=$(mktemp); printf "\n\n" > "$f"; sed "s/^/# /" >> "$f"; ${EDITOR:-vi} "$f" < /dev/tty > /dev/tty; grep -v "^#" "$f"' wt merge'''
|
||||
```
|
||||
|
||||
Then `wt mc` opens an editor for the commit message while plain `wt merge` continues to use the LLM.
|
||||
|
||||
### Monitor hook logs
|
||||
|
||||
Follow background hook output:
|
||||
|
||||
@@ -470,64 +524,3 @@ Create an alias for frequent use:
|
||||
```bash
|
||||
alias wtlog='f() { tail -f "$(wt config state logs get --hook="$1")"; }; f'
|
||||
```
|
||||
|
||||
## Bare repository layout
|
||||
|
||||
A [bare repository](https://git-scm.com/docs/gitrepository-layout) has no working tree, so all branches — including the default — are [linked worktrees](https://git-scm.com/docs/git-worktree) at equal paths. No branch gets special treatment.
|
||||
|
||||
Cloning a bare repo into `<project>/.git` puts all worktrees under one directory:
|
||||
|
||||
```bash
|
||||
git clone --bare <url> myproject/.git
|
||||
cd myproject
|
||||
```
|
||||
|
||||
With `worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}"`, worktrees become subdirectories of `myproject/`:
|
||||
|
||||
```
|
||||
myproject/
|
||||
├── .git/ # bare repository
|
||||
├── main/ # default branch worktree
|
||||
├── feature/ # feature branch worktree
|
||||
└── bugfix/ # bugfix branch worktree
|
||||
```
|
||||
|
||||
### Configure the worktree path
|
||||
|
||||
On first `wt switch` in a bare repo at a hidden path (`.git`, `.bare`), worktrunk detects that the default template would produce broken paths like `myproject/.git.main` and offers a fix:
|
||||
|
||||
```
|
||||
▲ Bare repo at myproject/.git — worktrees will be at myproject/.git.main
|
||||
◎ Configure worktree-path to place worktrees at myproject/main? [y/N/?]
|
||||
```
|
||||
|
||||
Accepting writes a project-scoped entry to user config:
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[projects."github.com/myorg/myrepo"]
|
||||
worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}"
|
||||
```
|
||||
|
||||
Run `wt config show` from inside any worktree to find the project identifier (`Identifier: …` in the PROJECT CONFIG section). Set it globally with `worktree-path = "..."` at the top level if this layout is preferred for all bare repos.
|
||||
|
||||
### Create the first worktree
|
||||
|
||||
```bash
|
||||
wt switch main
|
||||
```
|
||||
|
||||
For a freshly cloned bare repo the default branch already exists, so `wt switch main` (without `--create`) is enough. Use `wt switch --create <branch>` for new branches.
|
||||
|
||||
Now `wt switch --create feature` creates `myproject/feature/`.
|
||||
|
||||
### Set up the project config
|
||||
|
||||
The project config (`.config/wt.toml`) must live inside a worktree — the bare `.git` directory has no tracked files. Once the first worktree exists, create it from there:
|
||||
|
||||
```bash
|
||||
cd myproject/main
|
||||
wt config create --project
|
||||
```
|
||||
|
||||
Commit the file and it will appear in every worktree automatically.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Claude-specific troubleshooting guidance for common worktrunk issues.
|
||||
|
||||
## Commit Message Generation
|
||||
## Commit message generation
|
||||
|
||||
### Command not found
|
||||
|
||||
@@ -81,7 +81,7 @@ post-start = "npm run build"
|
||||
|
||||
### A `for-each` or `--execute` alias uses the same value in every worktree
|
||||
|
||||
The alias body renders once at dispatch, in the invoking worktree, so a per-worktree variable like `{{ branch }}` is baked before the nested `wt` command iterates. If `wt config alias dry-run <name>` shows a single substituted value (e.g. `… echo branch=main`), it was baked at that first pass. Defer it with `{% raw %}{{ branch }}{% endraw %}`, and for `for-each` keep it inside a quoted `sh -c '...'` so the alias's shell doesn't word-split it. Repo-level variables like `{{ default_branch }}` are unaffected — they are identical in every worktree. See `reference/extending.md#deferring-expansion-to-a-nested-wt-command`.
|
||||
The alias body rendered once at dispatch, baking the variable to the invoking worktree's value before the nested `wt` command iterated. See `reference/extending.md#deferring-expansion-to-a-nested-wt-command` for how to confirm it and how to defer the variable.
|
||||
|
||||
## List
|
||||
|
||||
@@ -90,7 +90,7 @@ The alias body renders once at dispatch, in the invoking worktree, so a per-work
|
||||
The timeout warning names the tasks that didn't finish:
|
||||
|
||||
```
|
||||
wt list timed out after 120s (170 results received); blocked tasks:
|
||||
Listing worktrees timed out after 120s (170 results received); blocked tasks:
|
||||
<branch>: working-tree-diff, working-tree-conflicts
|
||||
```
|
||||
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ A quick demo:
|
||||
|
||||
AI agents like Claude Code and Codex can handle longer tasks without
|
||||
supervision, such that it's possible to manage 5-10+ in parallel. Git's native
|
||||
worktree feature give each agent its own working directory, so they don't step
|
||||
worktree feature gives each agent its own working directory, so they don't step
|
||||
on each other's changes.
|
||||
|
||||
But the git worktree UX is clunky. Even a task as small as starting a new
|
||||
|
||||
@@ -59,16 +59,7 @@ Detect which tools are installed (`which claude codex llm aichat`); if none, rec
|
||||
|
||||
### Configuring project hooks
|
||||
|
||||
Pick the hook type by when the command should run and whether it may block (10 types: 5 events × pre/post — full reference in `reference/hook.md`):
|
||||
|
||||
- Dependencies and env files a later step needs → `pre-start` (blocks creation)
|
||||
- Dev servers, long builds, cache copying → `post-start` (background)
|
||||
- Formatters, linters, type checks → `pre-commit`
|
||||
- Tests that must pass before merging → `pre-merge`
|
||||
- CI triggers, notifications → `post-commit`
|
||||
- Deployment → `post-merge`
|
||||
- Setup before branch resolution / terminal-IDE updates → `pre-switch` / `post-switch`
|
||||
- Cleanup before/after removal (save artifacts; stop servers, remove containers) → `pre-remove` / `post-remove`
|
||||
Pick the hook type by when the command should run and whether it may block — `reference/hook.md` maps all ten (5 events × pre/post) to their timing and typical uses.
|
||||
|
||||
Derive the commands from the project itself (`package.json` scripts, `Cargo.toml`, `pyproject.toml`) and verify they run before adding them.
|
||||
|
||||
@@ -97,7 +88,7 @@ Test with `wt switch --create test-hooks`.
|
||||
- Customize worktree paths → `reference/config.md#worktree-path-template`
|
||||
- Custom commit templates → `reference/llm-commits.md#prompt-templates`
|
||||
- Configure command defaults → `reference/config.md#command-config`
|
||||
- Set up personal hooks → `reference/config.md#hooks`
|
||||
- Set up personal hooks → `reference/config.md#user-hooks`
|
||||
|
||||
### Project config tasks
|
||||
- Set up hooks for new project → `reference/hook.md`
|
||||
@@ -146,37 +137,15 @@ The resolution is for the user to make the trust decision themselves:
|
||||
|
||||
## Advanced: agent handoffs
|
||||
|
||||
When the user requests spawning a worktree with an agent in a background session ("spawn a worktree for...", "hand off to another agent"), use the appropriate pattern for their terminal multiplexer. Substitute `<agent-cli>` with the CLI you are running as: `claude` for Claude Code, `'opencode run'` for OpenCode.
|
||||
|
||||
**tmux** (check `$TMUX` env var):
|
||||
```bash
|
||||
tmux new-session -d -s <branch-name> "wt switch --create <branch-name> -x <agent-cli> -- '<task description>'"
|
||||
```
|
||||
|
||||
**Zellij** (check `$ZELLIJ` env var):
|
||||
```bash
|
||||
zellij run -- wt switch --create <branch-name> -x <agent-cli> -- '<task description>'
|
||||
```
|
||||
When the user requests spawning a worktree with an agent in a background session ("spawn a worktree for...", "hand off to another agent"), use the tmux or Zellij command from `reference/tips-patterns.md#agent-handoffs`, substituting the CLI you are running as for `claude`.
|
||||
|
||||
**Requirements** (all must be true):
|
||||
- User explicitly requests spawning/handoff
|
||||
- User is in a supported multiplexer (tmux or Zellij)
|
||||
- User is in a supported multiplexer (check `$TMUX` / `$ZELLIJ`)
|
||||
- The user's project instructions (`CLAUDE.md` or `AGENTS.md`) or an explicit prompt authorize this pattern
|
||||
|
||||
**Do not use this pattern** for normal worktree operations.
|
||||
|
||||
Example (tmux, Claude Code):
|
||||
```bash
|
||||
tmux new-session -d -s fix-auth-bug "wt switch --create fix-auth-bug -x claude -- \
|
||||
'The login session expires after 5 minutes. Find the session timeout config and extend it to 24 hours.'"
|
||||
```
|
||||
|
||||
Example (Zellij, OpenCode):
|
||||
```bash
|
||||
zellij run -- wt switch --create fix-auth-bug -x 'opencode run' -- \
|
||||
'The login session expires after 5 minutes. Find the session timeout config and extend it to 24 hours.'
|
||||
```
|
||||
|
||||
### Parallel sub-Agents (single Claude Code session)
|
||||
|
||||
To spawn multiple sub-Agents that each work in their own worktree from one Claude Code session — no terminal multiplexer, no human in the other pane — pre-start each worktree from the parent and pass the path into the sub-Agent prompt:
|
||||
@@ -188,10 +157,10 @@ wt switch --create <branch> --no-cd --no-hooks
|
||||
Then call the `Agent` tool **without** `isolation: "worktree"`, naming the path in the prompt:
|
||||
|
||||
```
|
||||
You are working in `/abs/path/to/worktrunk.<branch>` on branch `<branch>`.
|
||||
You are working in `/abs/path/to/myproject.<branch>` on branch `<branch>`.
|
||||
All edits must stay in that worktree.
|
||||
```
|
||||
|
||||
`--no-cd` skips the shell-integration cd script the parent can't consume; `--no-hooks` is appropriate when each sub-Agent will run its own build/test step (e.g. `cargo run -- hook pre-merge --yes`) and you don't need post-start setup repeated per worktree.
|
||||
|
||||
**Do not** use `Agent { isolation: "worktree" }` for this. Claude Code passes its internal agent ID as `name` to the `WorktreeCreate` hook, so `wt` creates the worktree as `worktrunk.agent-<id>` on a throwaway branch. If the sub-Agent then creates a feature branch on top, you end up with non-canonical paths, orphan branches, and post-start hooks fired against the wrong branch. Pre-creating with `wt switch --create` keeps path, branch, and hook target aligned.
|
||||
**Do not** use `Agent { isolation: "worktree" }` for this. Claude Code passes its internal agent ID as `name` to the `WorktreeCreate` hook, so `wt` creates the worktree as `myproject.agent-<id>` on a throwaway branch. If the sub-Agent then creates a feature branch on top, you end up with non-canonical paths, orphan branches, and post-start hooks fired against the wrong branch. Pre-creating with `wt switch --create` keeps path, branch, and hook target aligned.
|
||||
|
||||
Generated
-1
@@ -1 +0,0 @@
|
||||
../../../README.md
|
||||
+5
-3
@@ -7,9 +7,11 @@ Worktrunk ships a plugin for each supported agent CLI. What a plugin provides de
|
||||
| Configuration skill | ✓ | ✓ | | | ✓ |
|
||||
| Activity tracking (🤖/💬 in `wt list`) | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Worktree isolation | ✓ | | | | |
|
||||
| `/wt-switch-create` command | ✓ | | | | |
|
||||
| `/wt-switch-create` skill | ✓ | ✓\* | | | ✓\* |
|
||||
|
||||
The configuration skill is documentation the agent reads to help set up LLM commits, hooks, and troubleshooting. Activity tracking shows which worktrees have running sessions. Worktree isolation needs worktree-lifecycle hooks and `/wt-switch-create` needs session working-directory switching — both Claude Code-only, so Codex, OpenCode, Pi, and Gemini users invoke `wt switch --create` and `wt remove` directly. Codex tracks activity through its own `Stop` and `SessionEnd` hooks.
|
||||
\* Codex and Gemini load the `/wt-switch-create` skill from the same shared skill set, but neither lets a skill change the session's working directory, so it does nothing there.
|
||||
|
||||
The configuration skill is documentation the agent reads to help set up LLM commits, hooks, and troubleshooting. Activity tracking shows which worktrees have running sessions. Worktree isolation needs worktree-lifecycle hooks, which only Claude Code exposes, so Codex, OpenCode, Pi, and Gemini users invoke `wt switch --create` and `wt remove` directly. Codex tracks activity through its own `Stop` and `SessionEnd` hooks.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -128,7 +130,7 @@ Three things to get right:
|
||||
|
||||
Claude Code agents can run in isolated worktrees (`isolation: "worktree"`). By default, Claude Code creates these with `git worktree add`. The plugin's `WorktreeCreate` and `WorktreeRemove` hooks route this through `wt switch --create` and `wt remove` instead, so worktrees created by agents get worktrunk's naming conventions, hooks, and lifecycle management.
|
||||
|
||||
## `/wt-switch-create` command (Claude Code only)
|
||||
## `/wt-switch-create` skill (Claude Code only)
|
||||
|
||||
`/wt-switch-create [<branch>] [<repo>] [-- <task>]` starts a task in a fresh worktree without leaving the session: it creates the worktree, switches into it, and runs the task (all arguments optional). The worktree shows up in `wt list`; merge or remove it with `wt merge` / `wt remove`.
|
||||
|
||||
|
||||
Generated
+13
-211
@@ -322,7 +322,7 @@ exclude = [] # Additional excludes (e.g., [".cache/", ".turbo/"])
|
||||
|
||||
Built-in excludes (VCS metadata and tool-state directories) always apply; [the `wt step copy-ignored` docs](https://worktrunk.dev/step/#wt-step-copy-ignored) list them. User config and project config exclusions are combined.
|
||||
|
||||
### Aliases
|
||||
### User aliases
|
||||
|
||||
Command templates that run as `wt <name>`. See the [Extending Worktrunk guide](https://worktrunk.dev/extending/#aliases) for usage and flags.
|
||||
|
||||
@@ -332,7 +332,7 @@ greet = "echo Hello from {{ branch }}"
|
||||
url = "echo http://localhost:{{ branch | hash_port }}"
|
||||
```
|
||||
|
||||
Aliases defined here apply to all projects. For project-specific aliases, use the [project config](https://worktrunk.dev/config/#project-configuration) `[aliases]` section instead.
|
||||
Aliases defined here apply to all projects. For project-specific aliases, use the [project config](https://worktrunk.dev/config/#project-aliases) `[aliases]` section instead.
|
||||
|
||||
### User project-specific settings
|
||||
|
||||
@@ -528,9 +528,9 @@ template-append = """
|
||||
|
||||
How the fragment renders, and the project-config counterpart: [the LLM commits guide](https://worktrunk.dev/llm-commits/#appending-to-the-prompt).
|
||||
|
||||
## Hooks
|
||||
## User hooks
|
||||
|
||||
See [`wt hook`](https://worktrunk.dev/hook/) for hook types, execution order, template variables, and examples. User hooks apply to all projects; [project hooks](https://worktrunk.dev/config/#project-configuration) apply only to that repository.
|
||||
See [`wt hook`](https://worktrunk.dev/hook/) for hook types, execution order, template variables, and examples. User hooks apply to all projects; [project hooks](https://worktrunk.dev/config/#project-hooks) apply only to that repository.
|
||||
<!-- USER_CONFIG_END -->
|
||||
<!-- PROJECT_CONFIG_START -->
|
||||
# Project Configuration
|
||||
@@ -539,7 +539,7 @@ Project configuration lets teams share repository-specific settings — hooks, d
|
||||
|
||||
To create a starter file with commented-out examples, run `wt config create --project`.
|
||||
|
||||
## Hooks
|
||||
## Project hooks
|
||||
|
||||
Project hooks apply to this repository only. See [`wt hook`](https://worktrunk.dev/hook/) for hook types, execution order, and examples.
|
||||
|
||||
@@ -595,7 +595,7 @@ exclude = [".cache/", ".turbo/"]
|
||||
|
||||
Built-in excludes (VCS metadata and tool-state directories) always apply; [the `wt step copy-ignored` docs](https://worktrunk.dev/step/#wt-step-copy-ignored) list them. User config and project config exclusions are combined.
|
||||
|
||||
## Aliases
|
||||
## Project aliases
|
||||
|
||||
Command templates that run as `wt <name>`. See the [Extending Worktrunk guide](https://worktrunk.dev/extending/#aliases) for usage and flags.
|
||||
|
||||
@@ -605,7 +605,7 @@ deploy = "make deploy BRANCH={{ branch }}"
|
||||
url = "echo http://localhost:{{ branch | hash_port }}"
|
||||
```
|
||||
|
||||
Aliases defined here are shared with teammates. For personal aliases, use the [user config](https://worktrunk.dev/config/#aliases) `[aliases]` section instead.
|
||||
Aliases defined here are shared with teammates. For personal aliases, use the [user config](https://worktrunk.dev/config/#user-aliases) `[aliases]` section instead.
|
||||
<!-- PROJECT_CONFIG_END -->
|
||||
|
||||
# Shell Integration
|
||||
@@ -781,24 +781,6 @@ Output:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config update
|
||||
@@ -853,24 +835,6 @@ Options:
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config approvals
|
||||
@@ -879,7 +843,7 @@ Manage command approvals.
|
||||
|
||||
Project hooks and project aliases prompt for approval on first run to prevent untrusted projects from running arbitrary commands. Approvals from both flows are stored together.
|
||||
|
||||
### Examples
|
||||
### Approval examples
|
||||
|
||||
List commands and their approval status for current project:
|
||||
```console
|
||||
@@ -956,24 +920,6 @@ Commands:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config alias
|
||||
@@ -982,7 +928,7 @@ Inspect and preview aliases.
|
||||
|
||||
Aliases are command templates configured in user (`~/.config/worktrunk/config.toml`) or project (`.config/wt.toml`) config and run as `wt <name>`. See the [Extending Worktrunk guide](https://worktrunk.dev/extending/#aliases) for the configuration format.
|
||||
|
||||
### Examples
|
||||
### Alias examples
|
||||
|
||||
Show every configured alias's template:
|
||||
```console
|
||||
@@ -1014,24 +960,6 @@ Commands:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state
|
||||
@@ -1048,7 +976,7 @@ State is stored in `.git/` (config entries and log files), separate from configu
|
||||
- **vars**: [Custom variables per branch](https://worktrunk.dev/config/#wt-config-state-vars)
|
||||
- **logs**: [Operation and debug logs](https://worktrunk.dev/config/#wt-config-state-logs)
|
||||
|
||||
### Examples
|
||||
### State examples
|
||||
|
||||
Get the default branch:
|
||||
```console
|
||||
@@ -1104,24 +1032,6 @@ Commands:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state cache
|
||||
@@ -1142,7 +1052,7 @@ View or drop worktrunk's regenerable caches in one place. Everything here is reb
|
||||
|
||||
Without a subcommand, runs `get`.
|
||||
|
||||
### Examples
|
||||
### Cache examples
|
||||
|
||||
Show cache contents:
|
||||
```console
|
||||
@@ -1172,24 +1082,6 @@ Options:
|
||||
Output:
|
||||
--format <FORMAT>
|
||||
Output format (text, json) [default: text]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state default-branch
|
||||
@@ -1244,24 +1136,6 @@ Commands:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state logs
|
||||
@@ -1319,7 +1193,7 @@ All logs are stored in `.git/wt/logs/` (in the main worktree's git directory). A
|
||||
|
||||
`wt config state logs --format=json` emits three arrays — `command_log`, `hook_output`, `diagnostic`. Each entry carries a `file` (relative), `path` (absolute), `size`, and `modified_at` (unix seconds). Hook-output entries additionally expose `branch`, `source` (`user` / `project` / `internal`), `hook_type` (the `post-*` kind, or `null` for internal ops), and `name`. Filter with `jq` to pick out a specific entry.
|
||||
|
||||
### Examples
|
||||
### Log examples
|
||||
|
||||
List all log files:
|
||||
```console
|
||||
@@ -1365,24 +1239,6 @@ Options:
|
||||
Output:
|
||||
--format <FORMAT>
|
||||
Output format (text, json) [default: text]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state ci-status
|
||||
@@ -1413,24 +1269,6 @@ Options:
|
||||
Output:
|
||||
--format <FORMAT>
|
||||
Output format (text, json) [default: text]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state marker
|
||||
@@ -1489,24 +1327,6 @@ Options:
|
||||
Output:
|
||||
--format <FORMAT>
|
||||
Output format (text, json) [default: text]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt config state vars
|
||||
@@ -1515,7 +1335,7 @@ Custom variables per branch.
|
||||
|
||||
Store custom variables per branch. Values are stored as-is — plain strings or JSON.
|
||||
|
||||
### Examples
|
||||
### Variable examples
|
||||
|
||||
Set and get values:
|
||||
```console
|
||||
@@ -1577,22 +1397,4 @@ Commands:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
Generated
+1
-1
@@ -41,7 +41,7 @@ Aliases are configured under `[aliases]`:
|
||||
|
||||
```toml
|
||||
[aliases]
|
||||
deploy = "fly deploy --config=fly.{{ env }}.toml --app=myapp-{{ branch }}"
|
||||
deploy = "fly deploy --config=fly.{{ env }}.toml --app=myproject-{{ branch }}"
|
||||
open = "open http://localhost:{{ branch | hash_port }}"
|
||||
since-main = "git log --oneline {{ default_branch }}..HEAD"
|
||||
```
|
||||
|
||||
Generated
+20
-58
@@ -12,19 +12,19 @@ Git's built-in worktree commands work but require manual lifecycle management:
|
||||
|
||||
```console
|
||||
# Plain git worktree workflow
|
||||
$ git worktree add -b feature-branch ../myapp-feature main
|
||||
$ cd ../myapp-feature
|
||||
$ git worktree add -b feature-auth ../myproject.feature-auth main
|
||||
$ cd ../myproject.feature-auth
|
||||
# ...work, commit, push...
|
||||
$ cd ../myapp
|
||||
$ git merge feature-branch
|
||||
$ git worktree remove ../myapp-feature
|
||||
$ git branch -d feature-branch
|
||||
$ cd ../myproject
|
||||
$ git merge feature-auth
|
||||
$ git worktree remove ../myproject.feature-auth
|
||||
$ git branch -d feature-auth
|
||||
```
|
||||
|
||||
Worktrunk automates the full lifecycle:
|
||||
|
||||
```console
|
||||
$ wt switch --create feature-branch # Creates worktree, runs setup hooks
|
||||
$ wt switch --create feature-auth # Creates worktree, runs setup hooks
|
||||
# ...work...
|
||||
$ wt merge # Merges into default branch, cleans up
|
||||
```
|
||||
@@ -77,14 +77,11 @@ The stash lives in the shared `.git` directory, so it's reachable from the new w
|
||||
|
||||
## There's an issue with my shell setup
|
||||
|
||||
If shell integration isn't working (auto-cd not happening, completions missing, `wt` not found as a function), the fastest path to a fix is using Claude Code with the Worktrunk plugin:
|
||||
If shell integration isn't working (auto-cd not happening, completions missing, `wt` not found as a function), work through the [shell integration](https://worktrunk.dev/shell-integration/#debugging-checklist) debugging checklist — it covers each warning `wt switch` prints and what to check for every shell.
|
||||
|
||||
1. Install the [Worktrunk plugin](https://worktrunk.dev/claude-code/) in Claude Code
|
||||
2. Ask Claude to debug the Worktrunk shell integration
|
||||
Or hand it to an agent: install the [Worktrunk plugin](https://worktrunk.dev/claude-code/) in Claude Code and ask it to debug the Worktrunk shell integration. It runs `wt config show`, inspects the shell config files, and identifies the issue.
|
||||
|
||||
Claude will run `wt config show`, inspect the shell config files, and identify the issue.
|
||||
|
||||
If Claude can't fix it, please [open an issue](https://github.com/max-sixty/worktrunk/issues/new?title=Shell%20setup%20issue&body=%23%23%20Shell%20and%20OS%0A%0A-%20Shell%3A%20%0A-%20OS%3A%20%0A%0A%23%23%20Output%20of%20%60wt%20config%20show%60%0A%0A%60%60%60%0A%0A%60%60%60%0A%0A%23%23%20What%20Claude%20found%20%28if%20available%29%0A%0A) with the output of `wt config show`, the shell (bash/zsh/fish), and OS. (And even if it fixes the problem, feel free to open an issue: non-standard success cases are useful for ensuring Worktrunk is easy to set up for others.)
|
||||
If neither settles it, please [open an issue](https://github.com/max-sixty/worktrunk/issues/new?title=Shell%20setup%20issue&body=%23%23%20Shell%20and%20OS%0A%0A-%20Shell%3A%20%0A-%20OS%3A%20%0A%0A%23%23%20Output%20of%20%60wt%20config%20show%60%0A%0A%60%60%60%0A%0A%60%60%60%0A%0A%23%23%20What%20Claude%20found%20%28if%20available%29%0A%0A) with the output of `wt config show`, the shell (bash/zsh/fish), and OS. (And even if it fixes the problem, feel free to open an issue: non-standard success cases are useful for ensuring Worktrunk is easy to set up for others.)
|
||||
|
||||
## What does `-v` / `-vv` do?
|
||||
|
||||
@@ -108,7 +105,7 @@ The flags only reach a command you type; shell completion runs as its own proces
|
||||
|
||||
### 1. Worktree directories
|
||||
|
||||
Created by `wt switch <branch>` when switching to a branch that doesn't have a worktree. Use `wt switch --create <branch>` to create a new branch. Default location is `../<repo>.<branch>` (sibling to main repo), configurable via `worktree-path` in user config.
|
||||
Created by `wt switch <branch>` when switching to a branch that doesn't have a worktree. Use `wt switch --create <branch>` to create a new branch. Default location is `../<repo>.<branch>` (sibling to the main worktree), configurable via `worktree-path` in user config.
|
||||
|
||||
**To remove:** `wt remove <branch>` removes the worktree directory and deletes the branch.
|
||||
|
||||
@@ -126,19 +123,7 @@ User config location: `$XDG_CONFIG_HOME/worktrunk/` (or `~/.config/worktrunk/`)
|
||||
|
||||
### 3. Shell integration
|
||||
|
||||
Created by `wt config shell install`:
|
||||
|
||||
- **Bash**: adds line to `~/.bashrc`
|
||||
- **Zsh**: adds line to `~/.zshrc` (or `$ZDOTDIR/.zshrc`)
|
||||
- **Fish**: creates `~/.config/fish/functions/wt.fish` and `~/.config/fish/completions/wt.fish`
|
||||
- **Nushell** [experimental]: creates `wt.nu` in Nushell's user vendor-autoload directory — the last entry of `$nu.vendor-autoload-dirs`, under `$nu.data-dir` (typically `~/.local/share/nushell/vendor/autoload` on Linux, `~/Library/Application Support/nushell/vendor/autoload` on macOS)
|
||||
- **PowerShell** (Windows): creates both profile files if they don't exist:
|
||||
- `Documents/PowerShell/Microsoft.PowerShell_profile.ps1` (PowerShell 7+)
|
||||
- `Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1` (Windows PowerShell 5.1)
|
||||
|
||||
Fish and Nushell wrappers live at a path named after the command, so install writes that file whole, replacing an existing `functions/wt.fish`, `completions/wt.fish`, or `wt.nu`. Bash, zsh, and PowerShell rc files hold the rest of a shell's setup, so install only appends a line to those.
|
||||
|
||||
**PowerShell detection on Windows:** When running from cmd.exe or PowerShell, both PowerShell profile files are created automatically. When running from Git Bash or MSYS2, PowerShell is skipped (use `wt config shell install powershell` to create the profiles explicitly).
|
||||
`wt config shell install` appends a line to the bash, zsh, and PowerShell rc files, and writes worktrunk's own wrapper and completion files whole for fish and Nushell. [Shell integration](https://worktrunk.dev/shell-integration/#files-created) names the file each shell gets.
|
||||
|
||||
**To remove:** `wt config shell uninstall`.
|
||||
|
||||
@@ -151,6 +136,7 @@ Worktrunk stores repository state, caches, and logs under `.git/`:
|
||||
| `git config worktrunk.*` | Cached default branch, switch history, branch markers, custom variables | Various commands |
|
||||
| `.git/wt/cache/{kind}/*.json` | Cached CI status, the largest PR/MR number seen (sizes the `wt list` CI column), and git command results (merge-tree, integration probes, diff stats, ancestry checks, ahead/behind counts, merge bases) | `wt list`, `wt merge`, `wt remove` |
|
||||
| `.git/wt/cache/summary/{branch}/{hash}.json` | Cached LLM branch summaries, content-addressed by diff hash | `wt list --full`, `wt switch` (when `[list] summary = true`) |
|
||||
| `.git/wt/cache/picker-preview/*.json` | Rendered preview panes for the interactive picker | `wt switch` |
|
||||
| `.git/wt/logs/{branch}/**/*.log` | Background hook output (nested per branch) | Hooks, background `wt remove` |
|
||||
| `.git/wt/logs/commands.jsonl` | Command audit log (~2MB max) | Hooks, LLM commands |
|
||||
| `.git/wt/logs/trace.log` | Human debug trace for issue reporting | Running with `-vv` |
|
||||
@@ -161,7 +147,7 @@ Worktrunk stores repository state, caches, and logs under `.git/`:
|
||||
|
||||
None of this is tracked by git or pushed to remotes.
|
||||
|
||||
**To remove:** `wt config state clear` removes all repository data: config keys, caches, markers, hints, variables, logs, and stale trash.
|
||||
**To remove:** `wt config state clear` removes all repository data: config keys, caches, markers, hints, variables, logs, and stale trash. It prompts before removing anything worktrunk can't recompute, unless you pass `--yes`.
|
||||
|
||||
### 5. Agent integrations
|
||||
|
||||
@@ -203,14 +189,14 @@ Removal also refuses, `--force` included, when the directory at a registered pat
|
||||
To protect a worktree from removal entirely (say it holds a local database), lock it:
|
||||
|
||||
```bash
|
||||
git worktree lock ../myproject.feature --reason "Contains local database"
|
||||
git worktree lock ../myproject.feature-auth --reason "Contains local database"
|
||||
```
|
||||
|
||||
Locked worktrees show `⊞` in `wt list`. Neither `git worktree remove` nor `wt remove` (even with `--force`) will delete them. Unlock with `git worktree unlock`.
|
||||
|
||||
### Branch deletion
|
||||
|
||||
By default, `wt remove` only deletes branches whose content is already in the default branch. Branches showing `_` (same commit) or `⊂` (integrated) in `wt list` are safe to delete.
|
||||
By default, `wt remove` only deletes branches whose content is already in the default branch. Branches showing `_` (same commit, clean) or `⊂` (integrated) in `wt list` are safe to delete.
|
||||
|
||||
For the full algorithm, see [Branch cleanup](https://worktrunk.dev/remove/#branch-cleanup) — it handles squash-merge and rebase workflows where commit history differs but file changes match.
|
||||
|
||||
@@ -221,7 +207,7 @@ A branch checked out in a second worktree is retained regardless, `-D` included.
|
||||
### Other cleanup
|
||||
|
||||
- `wt merge` / `wt step push` — the target branch's checked-out worktree is updated to the merged commits, so a file those commits delete disappears from it, and an ignored file at a path they track is overwritten — the same result a `git merge` run in that worktree would produce. Uncommitted changes at paths the merge doesn't touch stay in place, staged or not; one at a path it does touch refuses the merge upfront, naming the file
|
||||
- `wt remove` — besides the target worktree, two cleanup mechanisms run. The removed worktree's own `git fsmonitor--daemon` (git's per-worktree filesystem watcher under `core.fsmonitor=true`, which would leak once its worktree is gone) is sent `git fsmonitor--daemon stop`, then force-terminated (`SIGTERM`, then `SIGKILL`) via the PID resolved from its IPC socket if it didn't exit. A background sweep then deletes `.git/wt/trash/` entries older than 24 hours (directories orphaned when a previous background removal was interrupted) and terminates fsmonitor daemons whose worktree no longer exists (orphans from `git worktree remove`, `rm -rf`, or a crashed `wt`)
|
||||
- `wt remove` — besides the worktree being removed, two cleanup mechanisms run. The removed worktree's own `git fsmonitor--daemon` (git's per-worktree filesystem watcher under `core.fsmonitor=true`, which would leak once its worktree is gone) is sent `git fsmonitor--daemon stop`, then force-terminated (`SIGTERM`, then `SIGKILL`) via the PID resolved from its IPC socket if it didn't exit. A background sweep then deletes `.git/wt/trash/` entries older than 24 hours (directories orphaned when a previous background removal was interrupted) and terminates fsmonitor daemons whose worktree no longer exists (orphans from `git worktree remove`, `rm -rf`, or a crashed `wt`)
|
||||
- `wt config state clear` — removes all worktrunk data from `.git/` (config keys, caches, markers, hints, variables, logs, stale trash)
|
||||
- `wt config shell install` — when migrating an integration to a new location, removes the file left at the old one: fish `conf.d/wt.fish` (now `functions/wt.fish`) and nushell wrappers stranded under `<config-dir>/vendor/autoload` (now `<data-dir>/vendor/autoload`). The old path is where worktrunk's own wrapper lived and is named after the command being installed, so it's taken back whole without reading it — a `conf.d/wt.fish` left in place would be sourced at startup and shadow the new wrapper anyway. Only that exact filename is touched, and each removal is printed
|
||||
- `wt config shell uninstall` — removes integration lines from bash/zsh/PowerShell rc files, and deletes worktrunk's wrapper and completion files (fish `functions/`, `conf.d/`, and `completions/`; nushell `vendor/autoload`). Uninstall takes no command name, so it lists those directories and recognizes files by worktrunk's own content markers, whatever binary name they were installed under; files without the markers are left alone. An rc file belongs to the user, so a line qualifies only where it runs the init command: one that merely mentions it, inside a comment, an `echo`, or an alias body, stays. Every line uninstall does take is printed, before removal and again after
|
||||
@@ -275,7 +261,7 @@ Clear with `wt config state logs clear`.
|
||||
|
||||
## Does Worktrunk work on Windows?
|
||||
|
||||
Yes. Core commands, shell integration, and tab completion work in both Git Bash and PowerShell. See [installation](https://worktrunk.dev/worktrunk/#install) for setup details, including avoiding the Windows Terminal `wt` conflict.
|
||||
Yes. Core commands, shell integration, and tab completion work in both Git Bash and PowerShell. See [installation](https://worktrunk.dev/#install) for setup details, including avoiding the Windows Terminal `wt` conflict.
|
||||
|
||||
**Git for Windows required** — Hooks use bash syntax and execute via Git Bash, so [Git for Windows](https://gitforwindows.org/) must be installed even when PowerShell is the interactive shell.
|
||||
|
||||
@@ -291,11 +277,7 @@ For full details on the detection mechanism, see `wt config state default-branch
|
||||
|
||||
## My `for-each` or `--execute` alias prints the same value in every worktree
|
||||
|
||||
An alias body renders once at dispatch, in the invoking worktree's context, so a per-worktree variable like `{{ branch }}` is baked to that one worktree's value before the nested `wt` command iterates. Every worktree then sees the same value.
|
||||
|
||||
Confirm it with `wt config alias dry-run <name>`: if the value is already substituted (e.g. `… echo branch=main`), it was baked at dispatch.
|
||||
|
||||
To defer a variable to the nested command, wrap it as `{% raw %}{{ branch }}{% endraw %}`; for `wt step for-each`, also keep it inside a quoted `sh -c '…'` so the alias's shell doesn't word-split it. See [deferring expansion in an alias](https://worktrunk.dev/extending/#deferring-expansion-to-a-nested-wt-command). A repo-level variable like `{{ default_branch }}` is unaffected — it is identical in every worktree.
|
||||
The alias body rendered once at dispatch, baking the variable to the invoking worktree's value before the nested `wt` command iterated. See [deferring expansion to a nested `wt` command](https://worktrunk.dev/extending/#deferring-expansion-to-a-nested-wt-command) for how to confirm it and how to defer the variable.
|
||||
|
||||
## What system dependencies are required?
|
||||
|
||||
@@ -309,26 +291,6 @@ cargo install worktrunk --no-default-features --features cli
|
||||
|
||||
This disables bash syntax highlighting in command output but keeps all core functionality. The syntax highlighting feature requires C99 compiler support and can fail on older systems or minimal Docker images.
|
||||
|
||||
## Running tests (for contributors)
|
||||
|
||||
### Quick tests
|
||||
|
||||
```bash
|
||||
cargo test
|
||||
```
|
||||
|
||||
### Full integration tests
|
||||
|
||||
Shell integration tests require bash, zsh, fish, nushell, and pwsh, plus `jq`:
|
||||
|
||||
```bash
|
||||
cargo test --test integration --features shell-integration-tests
|
||||
```
|
||||
|
||||
## How can I contribute?
|
||||
|
||||
- Star the repo
|
||||
- Try it out and [open an issue](https://github.com/max-sixty/worktrunk/issues) with feedback — even small annoyances
|
||||
- What worktree friction does Worktrunk not yet solve? [Tell us](https://github.com/max-sixty/worktrunk/issues)
|
||||
- Send to a friend
|
||||
- Post about it on [X](https://twitter.com/intent/tweet?text=Worktrunk%20%E2%80%94%20CLI%20for%20git%20worktree%20management&url=https%3A%2F%2Fworktrunk.dev), [Reddit](https://www.reddit.com/submit?url=https%3A%2F%2Fworktrunk.dev&title=Worktrunk%20%E2%80%94%20CLI%20for%20git%20worktree%20management), or [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fworktrunk.dev)
|
||||
See [Contributing](https://github.com/max-sixty/worktrunk#contributing) in the README — feedback, share links, and how to run the test suite.
|
||||
|
||||
Generated
-18
@@ -604,22 +604,4 @@ Options:
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
+1
-1
@@ -101,7 +101,7 @@ With `summary = true` and a `[commit.generation] command` configured, Worktrunk
|
||||
|
||||
Summaries appear in:
|
||||
|
||||
- **`wt switch`** [interactive picker](https://worktrunk.dev/switch/#interactive-picker) — preview tab 5
|
||||
- **`wt switch`** [interactive picker](https://worktrunk.dev/switch/#interactive-picker) — the `summary` preview tab
|
||||
- **`wt list --full`** — the Summary column (see [`wt list`](https://worktrunk.dev/list/#llm-summaries))
|
||||
|
||||
Enable in user config:
|
||||
|
||||
+66
-74
@@ -1,18 +1,12 @@
|
||||
# Shell Integration Reference
|
||||
# Shell integration
|
||||
|
||||
How Worktrunk's shell integration works and how to debug issues.
|
||||
Shell integration is what lets `wt switch` change your shell's directory. This page covers how it works, what it installs, and how to fix it when it doesn't.
|
||||
|
||||
## Why Shell Integration Exists
|
||||
## Why shell integration exists
|
||||
|
||||
Subprocesses cannot change the parent shell's current directory. When
|
||||
`wt switch feature` runs, the `wt` binary runs as a child process and cannot
|
||||
`cd` the terminal.
|
||||
A subprocess cannot change its parent shell's directory. When `wt switch feature` runs, the `wt` binary is a child process and cannot `cd` the terminal.
|
||||
|
||||
Worktrunk solves this with a file directive: the shell wrapper creates one temp
|
||||
file, `wt` writes the target directory to it, and the wrapper changes directory
|
||||
after `wt` exits. `--execute` runs directly inside wt. The wrapper's steps and a
|
||||
simplified implementation: [How the Shell Wrapper
|
||||
Works](#how-the-shell-wrapper-works).
|
||||
Worktrunk solves this with a file directive: the shell wrapper creates one temp file, `wt` writes the target directory to it, and the wrapper changes directory after `wt` exits. `--execute` runs directly inside `wt`. See [How the shell wrapper works](#how-the-shell-wrapper-works) for the steps and a simplified implementation.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -37,55 +31,61 @@ wt config shell init nu | save -f ($nu.vendor-autoload-dirs | last | path join w
|
||||
Invoke-Expression (& wt config shell init powershell | Out-String)
|
||||
```
|
||||
|
||||
## Checking Status
|
||||
## Files created
|
||||
|
||||
`wt config shell install` writes:
|
||||
|
||||
- **Bash**: adds a line to `~/.bashrc`
|
||||
- **Zsh**: adds a line to `~/.zshrc` (or `$ZDOTDIR/.zshrc`)
|
||||
- **Fish**: creates `~/.config/fish/functions/wt.fish` and `~/.config/fish/completions/wt.fish`
|
||||
- **Nushell** [experimental]: creates `wt.nu` in Nushell's user vendor-autoload directory — the last entry of `$nu.vendor-autoload-dirs`, under `$nu.data-dir` (typically `~/.local/share/nushell/vendor/autoload` on Linux, `~/Library/Application Support/nushell/vendor/autoload` on macOS)
|
||||
- **PowerShell** (Windows): creates both profile files if they don't exist:
|
||||
- `Documents/PowerShell/Microsoft.PowerShell_profile.ps1` (PowerShell 7+)
|
||||
- `Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1` (Windows PowerShell 5.1)
|
||||
|
||||
Fish and Nushell wrappers live at a path named after the command, so install writes that file whole, replacing an existing `functions/wt.fish`, `completions/wt.fish`, or `wt.nu`. Bash, zsh, and PowerShell rc files hold the rest of a shell's setup, so install only appends a line to those.
|
||||
|
||||
**PowerShell detection on Windows:** When running from cmd.exe or PowerShell, both PowerShell profile files are created automatically. When running from Git Bash or MSYS2, PowerShell is skipped (use `wt config shell install powershell` to create the profiles explicitly).
|
||||
|
||||
**To remove:** `wt config shell uninstall`.
|
||||
|
||||
## Checking status
|
||||
|
||||
```bash
|
||||
# Show shell integration status
|
||||
wt config show
|
||||
```
|
||||
|
||||
The RUNTIME section shows whether shell integration is active for the current
|
||||
session.
|
||||
The RUNTIME section shows whether shell integration is active for the current session.
|
||||
|
||||
## Warning Messages
|
||||
## Warning messages
|
||||
|
||||
When shell integration isn't working, `wt switch` shows warnings explaining why.
|
||||
When shell integration isn't working, `wt switch` shows a warning explaining why.
|
||||
|
||||
### "shell wrapper is out of date"
|
||||
|
||||
**Meaning**: The active shell still has a retired wrapper loaded. Current
|
||||
versions no longer write to that wrapper's single directive file, so the
|
||||
parent shell cannot follow a directory change.
|
||||
**Meaning**: The active shell still has a retired wrapper loaded. Current versions no longer write to that wrapper's single directive file, so the parent shell cannot follow a directory change.
|
||||
|
||||
**Fix**: Run `wt config shell install`, then restart the shell (or reload its
|
||||
config) to activate the current wrapper.
|
||||
**Fix**: Run `wt config shell install`, then restart the shell (or reload its config) to activate the current wrapper.
|
||||
|
||||
### "shell integration not installed"
|
||||
|
||||
**Meaning**: The current shell's config file doesn't have the
|
||||
`eval "$(wt config shell init ...)"` line. The current shell is detected from
|
||||
the process tree (falling back to `$SHELL`), so this refers to the shell wt
|
||||
was actually invoked from, not necessarily the login shell.
|
||||
**Meaning**: The current shell's config file doesn't have the `eval "$(wt config shell init ...)"` line. The current shell is detected from the process tree (falling back to `$SHELL`), so this refers to the shell `wt` was actually invoked from, not necessarily the login shell.
|
||||
|
||||
**Fix**: Run `wt config shell install` or add the line manually.
|
||||
|
||||
### "shell integration installed but not active"
|
||||
|
||||
**Meaning**: Shell integration is configured for the current shell, but the
|
||||
shell function isn't loaded in this session — usually because the session was
|
||||
started before installation.
|
||||
**Meaning**: Shell integration is configured for the current shell, but the shell function isn't loaded in this session — usually because the session was started before installation.
|
||||
|
||||
**Fix**: Start a new terminal or run `source ~/.bashrc` (or equivalent). If
|
||||
the message persists after a restart, `wt config show` reports the detected
|
||||
shell, `$SHELL`, and per-shell integration status.
|
||||
**Fix**: Start a new terminal or run `source ~/.bashrc` (or equivalent). If the message persists after a restart, `wt config show` reports the detected shell, `$SHELL`, and per-shell integration status.
|
||||
|
||||
### "ran ./path/to/wt; shell integration wraps wt"
|
||||
|
||||
**Meaning**: The binary was invoked with an explicit path (like `./target/debug/wt`
|
||||
or `/usr/local/bin/wt`) instead of just `wt`. The shell wrapper only intercepts
|
||||
the bare command `wt`.
|
||||
**Meaning**: The binary was invoked with an explicit path (like `./target/debug/wt` or `/usr/local/bin/wt`) instead of just `wt`. The shell wrapper only intercepts the bare command `wt`.
|
||||
|
||||
**Fix**: Use `wt` without a path. For testing dev builds, set `WORKTRUNK_BIN`:
|
||||
|
||||
```bash
|
||||
export WORKTRUNK_BIN=./target/debug/wt
|
||||
wt switch feature # Now uses the dev build with shell integration
|
||||
@@ -93,21 +93,18 @@ wt switch feature # Now uses the dev build with shell integration
|
||||
|
||||
### "ran git wt; running through git prevents cd"
|
||||
|
||||
**Meaning**: `git wt` (git alias) was used instead of `wt`. Git runs worktrunk as
|
||||
a subprocess, bypassing the shell wrapper.
|
||||
**Meaning**: `git wt` (git alias) was used instead of `wt`. Git runs worktrunk as a subprocess, bypassing the shell wrapper.
|
||||
|
||||
**Fix**: Use `wt` directly instead of `git wt` when directory switching is needed.
|
||||
|
||||
### "Alias bypasses shell integration"
|
||||
|
||||
**Meaning**: An alias like `alias gwt="/usr/bin/wt"` or `alias gwt="wt.exe"`
|
||||
points directly to the binary instead of the shell function.
|
||||
**Meaning**: An alias like `alias gwt="/usr/bin/wt"` or `alias gwt="wt.exe"` points directly to the binary instead of the shell function.
|
||||
|
||||
When shell integration is installed, it creates a shell function named `wt` (or
|
||||
`git-wt`). If the alias points to the binary path, it bypasses this function
|
||||
and shell integration won't work.
|
||||
When shell integration is installed, it creates a shell function named `wt` (or `git-wt`). If the alias points to the binary path, it bypasses this function and shell integration won't work.
|
||||
|
||||
**Examples that bypass** (won't auto-cd):
|
||||
|
||||
```bash
|
||||
alias gwt="/usr/bin/wt"
|
||||
alias gwt="wt.exe"
|
||||
@@ -115,18 +112,17 @@ alias wt="/path/to/wt"
|
||||
```
|
||||
|
||||
**Fix**: Change the alias to point to the function name instead of the binary:
|
||||
|
||||
```bash
|
||||
alias gwt="wt" # Good - uses the shell function
|
||||
alias gwt="git-wt" # Good - uses the shell function
|
||||
```
|
||||
|
||||
`wt config show` detects these problematic aliases and shows a warning with the
|
||||
suggested fix.
|
||||
`wt config show` detects these problematic aliases and shows a warning with the suggested fix.
|
||||
|
||||
## How the Shell Wrapper Works
|
||||
## How the shell wrapper works
|
||||
|
||||
The shell wrapper (installed by `wt config shell install`) defines a shell
|
||||
function that:
|
||||
The shell wrapper (installed by `wt config shell install`) defines a shell function that:
|
||||
|
||||
1. Creates a temp file
|
||||
2. Sets `WORKTRUNK_DIRECTIVE_CD_FILE`
|
||||
@@ -134,7 +130,8 @@ function that:
|
||||
4. Reads the CD file with `cd -- "$(< file)"` (raw path, no shell parsing)
|
||||
5. Cleans up the temp file
|
||||
|
||||
Simplified example (actual wrapper handles completions and edge cases):
|
||||
Simplified example (the actual wrapper also handles completions and edge cases):
|
||||
|
||||
```bash
|
||||
wt() {
|
||||
local cd_file exit_code=0
|
||||
@@ -151,39 +148,39 @@ wt() {
|
||||
}
|
||||
```
|
||||
|
||||
## Debugging Checklist
|
||||
## Debugging checklist
|
||||
|
||||
### 1. Check if wrapper is installed
|
||||
### 1. Check whether the wrapper is loaded
|
||||
|
||||
```bash
|
||||
# Should show shell function, not binary path
|
||||
# Should show a shell function, not a binary path
|
||||
type wt
|
||||
|
||||
# Expected output (bash/zsh):
|
||||
# wt is a function
|
||||
# wt () { ... }
|
||||
|
||||
# If it shows a path like /usr/local/bin/wt, wrapper isn't loaded
|
||||
# If it shows a path like /usr/local/bin/wt, the wrapper isn't loaded
|
||||
```
|
||||
|
||||
### 1b. Check if wrapper is installed (PowerShell)
|
||||
### 2. Check whether the wrapper is loaded (PowerShell)
|
||||
|
||||
```powershell
|
||||
# PowerShell: should show Function, not just Application
|
||||
Get-Command wt -All
|
||||
|
||||
# Expected output when wrapper is loaded:
|
||||
# Expected output when the wrapper is loaded:
|
||||
# CommandType Name Source
|
||||
# ----------- ---- ------
|
||||
# Function wt
|
||||
# Application wt C:\Users\...\wt.exe
|
||||
|
||||
# If only Application appears, wrapper isn't loaded (restart shell)
|
||||
# If only Application appears, the wrapper isn't loaded (restart the shell)
|
||||
# If Function appears but integration is still "not active", check the body:
|
||||
(Get-Command wt -CommandType Function).ScriptBlock | Select-String WORKTRUNK
|
||||
```
|
||||
|
||||
### 2. Check shell config file
|
||||
### 3. Check the shell config file
|
||||
|
||||
```bash
|
||||
# bash
|
||||
@@ -196,21 +193,21 @@ grep -n "wt config shell init" ~/.zshrc
|
||||
grep -n "wt config shell init" ~/.config/fish/config.fish
|
||||
```
|
||||
|
||||
Should show the `eval` line with line number.
|
||||
This should show the `eval` line with its line number.
|
||||
|
||||
### 3. Check if directive files are set
|
||||
### 4. Check whether directive files are set
|
||||
|
||||
```bash
|
||||
# After running any wt command, this should be unset (temp file deleted)
|
||||
# After running any wt command, this should be unset (the temp file is deleted)
|
||||
echo $WORKTRUNK_DIRECTIVE_CD_FILE
|
||||
|
||||
# During wt execution, these would be set to temp file paths
|
||||
# During wt execution, this is set to a temp file path
|
||||
```
|
||||
|
||||
### 4. Test directive files manually
|
||||
### 5. Test directive files manually
|
||||
|
||||
```bash
|
||||
# Create temp files and test
|
||||
# Create the temp file and test
|
||||
export WORKTRUNK_DIRECTIVE_CD_FILE=$(mktemp)
|
||||
command wt switch feature
|
||||
cat $WORKTRUNK_DIRECTIVE_CD_FILE # Should contain: /path/to/worktree (raw path)
|
||||
@@ -218,11 +215,12 @@ cd -- "$(<$WORKTRUNK_DIRECTIVE_CD_FILE)" # Should cd you there
|
||||
rm -f $WORKTRUNK_DIRECTIVE_CD_FILE
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
## Common issues
|
||||
|
||||
### Shell integration works in terminal but not in IDE terminal
|
||||
### Shell integration works in the terminal but not in an IDE terminal
|
||||
|
||||
IDE terminals may use different shell configs. Check:
|
||||
|
||||
- VS Code: Settings → Terminal → Integrated → Shell Args
|
||||
- The IDE terminal might source a different profile
|
||||
|
||||
@@ -241,20 +239,14 @@ eval "$(wt config shell init zsh)"
|
||||
|
||||
### Windows Git Bash issues
|
||||
|
||||
Git Bash uses MSYS2, which automatically converts POSIX paths in environment
|
||||
variables. The directive file path is handled correctly without manual conversion.
|
||||
Git Bash uses MSYS2, which automatically converts POSIX paths in environment variables. The directive file path is handled correctly without manual conversion.
|
||||
|
||||
If you see path issues, ensure you're using a recent Git for Windows version.
|
||||
If you see path issues, make sure you're on a recent Git for Windows version.
|
||||
|
||||
## Environment Variables
|
||||
## Environment variables
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `WORKTRUNK_DIRECTIVE_CD_FILE` | Set by shell wrapper; wt writes a raw path, wrapper `cd`s to it |
|
||||
| `WORKTRUNK_BIN` | Override binary path (for testing dev builds) |
|
||||
| `WORKTRUNK_DIRECTIVE_CD_FILE` | Set by the shell wrapper; `wt` writes a raw path, the wrapper `cd`s to it |
|
||||
| `WORKTRUNK_BIN` | Override the binary path (for testing dev builds) |
|
||||
| `WORKTRUNK_COMPLETE_NAME` | Set by the bash, zsh, and PowerShell wrappers when they load completions; names the command the registration binds to, so `--cmd` integrations complete |
|
||||
|
||||
## See Also
|
||||
|
||||
- `wt config shell --help` — Shell integration commands
|
||||
- `wt config show` — View current configuration and status
|
||||
|
||||
Generated
+2
-218
@@ -176,24 +176,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step squash
|
||||
@@ -280,24 +262,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step rebase
|
||||
@@ -359,24 +323,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step push
|
||||
@@ -429,24 +375,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step diff
|
||||
@@ -517,24 +445,6 @@ Options:
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step copy-ignored
|
||||
@@ -686,24 +596,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step eval
|
||||
@@ -783,24 +675,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step for-each
|
||||
@@ -865,24 +739,6 @@ Options:
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step promote
|
||||
@@ -959,24 +815,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step prune
|
||||
@@ -991,11 +829,11 @@ Locked worktrees and the main worktree are always skipped. The current worktree
|
||||
|
||||
### Min-age guard
|
||||
|
||||
Worktrees and branches younger than `--min-age` (default: 1 day) are skipped. This prevents removing a worktree just created from the default branch — it looks "merged" because its branch points at the same commit.
|
||||
Candidates younger than `--min-age` (default: 1 day) are skipped. A worktree's age comes from its creation time, and a branch with no worktree takes its age from its oldest reflog entry. This prevents removing a worktree just created from the default branch: it looks "merged" because its branch points at the same commit.
|
||||
|
||||
```console
|
||||
$ wt step prune --min-age=0s # no age guard
|
||||
$ wt step prune --min-age=2d # skip worktrees younger than 2 days
|
||||
$ wt step prune --min-age=2d # skip candidates younger than 2 days
|
||||
```
|
||||
|
||||
### JSON output
|
||||
@@ -1043,24 +881,6 @@ Options:
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step relocate
|
||||
@@ -1164,24 +984,6 @@ Automation:
|
||||
|
||||
[default: text]
|
||||
[possible values: text, json]
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
## wt step tether
|
||||
@@ -1249,22 +1051,4 @@ Arguments:
|
||||
Options:
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Global Options:
|
||||
-C <path>
|
||||
Working directory for this command
|
||||
|
||||
--config <path>
|
||||
User config file path
|
||||
|
||||
--config-set <toml>
|
||||
Override config with inline TOML, e.g. --config-set list.full=true (repeatable)
|
||||
|
||||
-v, --verbose...
|
||||
Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug
|
||||
logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to
|
||||
apply the same level everywhere — including shell completion, which no flag can reach
|
||||
|
||||
-y, --yes
|
||||
Skip approval prompts
|
||||
```
|
||||
|
||||
+260
-267
@@ -2,7 +2,9 @@
|
||||
|
||||
Practical recipes for common Worktrunk workflows.
|
||||
|
||||
## Shell alias for new worktree + agent
|
||||
## Setup and layout
|
||||
|
||||
### Shell alias for new worktree + agent
|
||||
|
||||
Create a worktree and launch Claude in one command:
|
||||
|
||||
@@ -12,7 +14,106 @@ wsc new-feature # Creates worktree, runs hooks, launches C
|
||||
wsc feature -- 'Fix GH #322' # Runs `claude 'Fix GH #322'`
|
||||
```
|
||||
|
||||
## `wt` aliases
|
||||
### Shortcuts
|
||||
|
||||
Special arguments work across all commands—see [`wt switch`](https://worktrunk.dev/switch/#shortcuts) for the full list.
|
||||
|
||||
```bash
|
||||
wt switch --create hotfix --base=@ # Branch from current HEAD
|
||||
wt switch - # Switch to previous worktree
|
||||
wt remove @ # Remove current worktree
|
||||
```
|
||||
|
||||
### Stacked branches
|
||||
|
||||
Branch from current HEAD instead of the default branch:
|
||||
|
||||
```bash
|
||||
wt switch --create feature-part2 --base=@
|
||||
```
|
||||
|
||||
### Reuse `default-branch`
|
||||
|
||||
Default branch [detection](https://worktrunk.dev/config/#wt-config-state-default-branch) means scripts work on any repo — no need to hardcode `main` or `master`:
|
||||
|
||||
```bash
|
||||
git rebase $(wt config state default-branch)
|
||||
```
|
||||
|
||||
In hooks and aliases, the same value is the `{{ default_branch }}` [template variable](https://worktrunk.dev/hook/#template-variables); reserve this command for plain shell scripts.
|
||||
|
||||
### Override `default-branch` for one clone
|
||||
|
||||
When the integration branch differs from the remote's `HEAD`, set a [clone-local override](https://worktrunk.dev/config/#wt-config-state-default-branch):
|
||||
|
||||
```bash
|
||||
wt config state default-branch set integration
|
||||
```
|
||||
|
||||
### Bare repository layout
|
||||
|
||||
A [bare repository](https://git-scm.com/docs/gitrepository-layout) has no working tree, so all branches — including the default — are [linked worktrees](https://git-scm.com/docs/git-worktree) at equal paths. No branch gets special treatment.
|
||||
|
||||
Cloning a bare repo into `<project>/.git` puts all worktrees under one directory:
|
||||
|
||||
```bash
|
||||
git clone --bare <url> myproject/.git
|
||||
cd myproject
|
||||
```
|
||||
|
||||
With `worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}"`, worktrees become subdirectories of `myproject/`:
|
||||
|
||||
```
|
||||
myproject/
|
||||
├── .git/ # bare repository
|
||||
├── main/ # default branch worktree
|
||||
├── feature/ # feature branch worktree
|
||||
└── bugfix/ # bugfix branch worktree
|
||||
```
|
||||
|
||||
#### Configure the worktree path
|
||||
|
||||
On first `wt switch` in a bare repo at a hidden path (`.git`, `.bare`), worktrunk detects that the default template would produce broken paths like `myproject/.git.main` and offers a fix:
|
||||
|
||||
```
|
||||
▲ Bare repo at myproject/.git — worktrees will be at myproject/.git.main
|
||||
◎ Configure worktree-path to place worktrees at myproject/main? [y/N/?]
|
||||
```
|
||||
|
||||
Accepting writes a project-scoped entry to user config:
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[projects."github.com/myorg/myrepo"]
|
||||
worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}"
|
||||
```
|
||||
|
||||
Run `wt config show` from inside any worktree to find the project identifier (`Identifier: …` in the PROJECT CONFIG section). Set it globally with `worktree-path = "..."` at the top level if this layout is preferred for all bare repos.
|
||||
|
||||
#### Create the first worktree
|
||||
|
||||
```bash
|
||||
wt switch main
|
||||
```
|
||||
|
||||
For a freshly cloned bare repo the default branch already exists, so `wt switch main` (without `--create`) is enough. Use `wt switch --create <branch>` for new branches.
|
||||
|
||||
Now `wt switch --create feature` creates `myproject/feature/`.
|
||||
|
||||
#### Set up the project config
|
||||
|
||||
The project config (`.config/wt.toml`) must live inside a worktree — the bare `.git` directory has no tracked files. Once the first worktree exists, create it from there:
|
||||
|
||||
```bash
|
||||
cd myproject/main
|
||||
wt config create --project
|
||||
```
|
||||
|
||||
Commit the file and it will appear in every worktree automatically.
|
||||
|
||||
## Aliases and hooks
|
||||
|
||||
### `wt` aliases
|
||||
|
||||
Compose with template filters and [vars](https://worktrunk.dev/tips-patterns/#per-branch-variables):
|
||||
|
||||
@@ -31,7 +132,7 @@ pick = "wt switch --format=json | jq -r '.branch'"
|
||||
|
||||
See [Aliases](https://worktrunk.dev/extending/#aliases) for scoping, approval, and reference.
|
||||
|
||||
## Per-branch variables
|
||||
### Per-branch variables
|
||||
|
||||
`wt config state vars` holds state per branch, accessible from templates (`{{ vars.key }}`) and the CLI. Some uses:
|
||||
|
||||
@@ -41,7 +142,53 @@ See [Aliases](https://worktrunk.dev/extending/#aliases) for scoping, approval, a
|
||||
|
||||
See [`wt config state vars`](https://worktrunk.dev/config/#wt-config-state-vars) for storage format, JSON support, and reference.
|
||||
|
||||
## Dev server per worktree
|
||||
### Task runners in hooks
|
||||
|
||||
Reference Taskfile/Justfile/Makefile in hooks:
|
||||
|
||||
```toml
|
||||
[pre-start]
|
||||
"setup" = "task install"
|
||||
|
||||
[pre-merge]
|
||||
"validate" = "just test lint"
|
||||
```
|
||||
|
||||
### Progressive validation
|
||||
|
||||
Split checks across hook types — quick feedback before each commit, expensive suites before merge:
|
||||
|
||||
```toml
|
||||
[[pre-commit]]
|
||||
lint = "npm run lint"
|
||||
typecheck = "npm run typecheck"
|
||||
|
||||
[[pre-merge]]
|
||||
test = "npm test"
|
||||
build = "npm run build"
|
||||
```
|
||||
|
||||
`pre-commit` runs during `wt merge`, before the squash commit; `pre-merge` runs once per merge after the rebase, so it's the right place for the slow tests.
|
||||
|
||||
### Target-specific hooks
|
||||
|
||||
Branch on `{{ target }}` to vary behavior per merge destination — for example, deploying to production from `main` and staging from a release branch:
|
||||
|
||||
```toml
|
||||
post-merge = """
|
||||
if [ {{ target }} = main ]; then
|
||||
npm run deploy:production
|
||||
elif [ {{ target }} = staging ]; then
|
||||
npm run deploy:staging
|
||||
fi
|
||||
"""
|
||||
```
|
||||
|
||||
`{{ target }}` is the branch being merged into. `post-merge` runs in the target's worktree (or the primary worktree if target has none), so deploy commands see the merged code.
|
||||
|
||||
## Per-worktree services
|
||||
|
||||
### Dev server per worktree
|
||||
|
||||
Each worktree runs its own dev server on a deterministic port. The `hash_port` filter generates a stable port (10000-19999) from the branch name:
|
||||
|
||||
@@ -71,7 +218,7 @@ $ wt list
|
||||
|
||||
`fix-auth` always gets port 16460, on any machine. The URL dims if the server isn't running.
|
||||
|
||||
## Database per worktree
|
||||
### Database per worktree
|
||||
|
||||
Each worktree can have its own isolated database. A pipeline sets up names and ports as [vars](https://worktrunk.dev/config/#wt-config-state-vars), then later steps and hooks reference them:
|
||||
|
||||
@@ -108,7 +255,7 @@ The connection string is accessible anywhere — not just in hooks:
|
||||
DATABASE_URL=$(wt config state vars get db_url) npm start
|
||||
```
|
||||
|
||||
## Per-worktree env vars
|
||||
### Per-worktree env vars
|
||||
|
||||
To scope environment variables to a worktree — a tool's package path, a profile, an API endpoint — use a directory environment manager like [direnv](https://direnv.net) or [mise](https://mise.jdx.dev). Both hook the shell prompt, so they activate on the `cd` that `wt switch` already performs — no worktrunk configuration needed. Commit the config at the repo root and every worktree gets its own copy, with paths resolving relative to that worktree.
|
||||
|
||||
@@ -131,7 +278,7 @@ MY_PACKAGES_PATH = "{{ config_root }}/.packages"
|
||||
|
||||
Both set real environment variables in the shell session, so every child process inherits them — hooks, build tools, subshells — without the `--execute` workaround. Each new worktree is a new path, so it needs its own one-time trust step (`direnv allow` / `mise trust`); worktrunk deliberately doesn't bypass that prompt, the same safety reasoning behind [disabling `--execute` in project alias and hook bodies](https://github.com/max-sixty/worktrunk/issues/2101).
|
||||
|
||||
## Eliminate cold starts
|
||||
### Eliminate cold starts
|
||||
|
||||
Use [`wt step copy-ignored`](https://worktrunk.dev/step/#wt-step-copy-ignored) to copy gitignored files (caches, dependencies, `.env`) between worktrees:
|
||||
|
||||
@@ -154,184 +301,43 @@ Use `pre-start` instead when an `--execute` command needs the copied files immed
|
||||
|
||||
All gitignored files are copied by default. To limit what gets copied, create `.worktreeinclude` with patterns — files must be both gitignored and listed. See [`wt step copy-ignored`](https://worktrunk.dev/step/#wt-step-copy-ignored) for details.
|
||||
|
||||
## Manual commit messages
|
||||
### Subdomain routing with Caddy
|
||||
|
||||
The `commit.generation.command` receives the rendered prompt on stdin and returns the commit message on stdout. To write commit messages by hand instead of using an LLM, point it at `$EDITOR`:
|
||||
<!-- Hand-tested 2026-03-07 -->
|
||||
|
||||
Clean URLs like `http://feature-auth.myproject.localhost` without port numbers. Useful for cookies, CORS, and matching production URL structure.
|
||||
|
||||
**Prerequisites:** [Caddy](https://caddyserver.com/docs/install) (`brew install caddy`)
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[commit.generation]
|
||||
command = '''f=$(mktemp); printf '\n\n' > "$f"; sed 's/^/# /' >> "$f"; ${EDITOR:-vi} "$f" < /dev/tty > /dev/tty; grep -v '^#' "$f"'''
|
||||
```
|
||||
|
||||
This comments out the rendered prompt (diff, branch name, stats) with `#` prefixes, opens your editor, and strips comment lines on save. A couple of blank lines at the top give you space to type; the prompt context is visible below for reference.
|
||||
|
||||
To keep the LLM as default but use the editor for a specific merge, add a [worktrunk alias](https://worktrunk.dev/extending/#aliases):
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[aliases]
|
||||
mc = '''WORKTRUNK_COMMIT__GENERATION__COMMAND='f=$(mktemp); printf "\n\n" > "$f"; sed "s/^/# /" >> "$f"; ${EDITOR:-vi} "$f" < /dev/tty > /dev/tty; grep -v "^#" "$f"' wt merge'''
|
||||
```
|
||||
|
||||
Then `wt mc` opens an editor for the commit message while plain `wt merge` continues to use the LLM.
|
||||
|
||||
## Track agent status
|
||||
|
||||
Custom emoji markers show agent state in `wt list`. The [Claude Code](https://worktrunk.dev/claude-code/) plugin and [OpenCode plugin](https://github.com/max-sixty/worktrunk/tree/main/dev/opencode-plugin.ts) set these automatically:
|
||||
|
||||
```
|
||||
+ feature-api ↑ 🤖 ↑1 ./repo.feature-api
|
||||
+ review-ui ? ↑ 💬 ↑1 ./repo.review-ui
|
||||
```
|
||||
|
||||
- `🤖` — Agent is working
|
||||
- `💬` — Agent is waiting for input
|
||||
|
||||
Set status manually for any workflow:
|
||||
|
||||
```bash
|
||||
wt config state marker set "🚧" # Current branch
|
||||
wt config state marker set "✅" --branch feature # Specific branch
|
||||
git config worktrunk.state.feature.marker '{"marker":"💬","set_at":0}' # Direct
|
||||
```
|
||||
|
||||
See [Claude Code Integration](https://worktrunk.dev/claude-code/#installation) for plugin installation.
|
||||
|
||||
## Monitor CI across branches
|
||||
|
||||
```bash
|
||||
wt list --full --branches
|
||||
```
|
||||
|
||||
Shows PR/CI status for all branches, including those without worktrees. CI indicators are clickable links to the PR page.
|
||||
|
||||
## LLM branch summaries
|
||||
|
||||
With `summary = true` and [`commit.generation`](https://worktrunk.dev/config/#commit) configured, `wt list --full` shows an LLM-generated one-line summary for each branch. The same summaries appear in the `wt switch` picker (tab 5).
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[list]
|
||||
summary = true
|
||||
```
|
||||
|
||||
See [LLM Commits](https://worktrunk.dev/llm-commits/#branch-summaries) for details.
|
||||
|
||||
## JSON API
|
||||
|
||||
```bash
|
||||
wt list --format=json
|
||||
```
|
||||
|
||||
Structured output for dashboards, statuslines, and scripts. See [`wt list`](https://worktrunk.dev/list/) for query examples.
|
||||
|
||||
## Reuse `default-branch`
|
||||
|
||||
Default branch [detection](https://worktrunk.dev/config/#wt-config-state-default-branch) means scripts work on any repo — no need to hardcode `main` or `master`:
|
||||
|
||||
```bash
|
||||
git rebase $(wt config state default-branch)
|
||||
```
|
||||
|
||||
In hooks and aliases, the same value is the `{{ default_branch }}` [template variable](https://worktrunk.dev/hook/#template-variables); reserve this command for plain shell scripts.
|
||||
|
||||
## Override `default-branch` for one clone
|
||||
|
||||
When the integration branch differs from the remote's `HEAD`, set a [clone-local override](https://worktrunk.dev/config/#wt-config-state-default-branch):
|
||||
|
||||
```bash
|
||||
wt config state default-branch set integration
|
||||
```
|
||||
|
||||
## Task runners in hooks
|
||||
|
||||
Reference Taskfile/Justfile/Makefile in hooks:
|
||||
|
||||
```toml
|
||||
[pre-start]
|
||||
"setup" = "task install"
|
||||
|
||||
[pre-merge]
|
||||
"validate" = "just test lint"
|
||||
```
|
||||
|
||||
## Progressive validation
|
||||
|
||||
Split checks across hook types — quick feedback before each commit, expensive suites before merge:
|
||||
|
||||
```toml
|
||||
[[pre-commit]]
|
||||
lint = "npm run lint"
|
||||
typecheck = "npm run typecheck"
|
||||
|
||||
[[pre-merge]]
|
||||
test = "npm test"
|
||||
build = "npm run build"
|
||||
```
|
||||
|
||||
`pre-commit` runs during `wt merge`, before the squash commit; `pre-merge` runs once per merge after the rebase, so it's the right place for the slow tests.
|
||||
|
||||
## Target-specific hooks
|
||||
|
||||
Branch on `{{ target }}` to vary behavior per merge destination — for example, deploying to production from `main` and staging from a release branch:
|
||||
|
||||
```toml
|
||||
post-merge = """
|
||||
if [ {{ target }} = main ]; then
|
||||
npm run deploy:production
|
||||
elif [ {{ target }} = staging ]; then
|
||||
npm run deploy:staging
|
||||
fi
|
||||
# .config/wt.toml
|
||||
[post-start]
|
||||
server = "wt step tether -- npm run dev -- --port {{ branch | hash_port }}"
|
||||
proxy = """
|
||||
curl -sf --max-time 0.5 http://localhost:2019/config/ || caddy start
|
||||
curl -sf http://localhost:2019/config/apps/http/servers/wt || \
|
||||
curl -sfX PUT http://localhost:2019/config/apps/http/servers/wt -H 'Content-Type: application/json' \
|
||||
-d '{"listen":[":8080"],"automatic_https":{"disable":true},"routes":[]}'
|
||||
curl -sf -X DELETE http://localhost:2019/id/wt:{{ repo }}:{{ branch | sanitize }} || true
|
||||
curl -sfX PUT http://localhost:2019/config/apps/http/servers/wt/routes/0 -H 'Content-Type: application/json' \
|
||||
-d '{"@id":"wt:{{ repo }}:{{ branch | sanitize }}","match":[{"host":["{{ branch | sanitize }}.{{ repo }}.localhost"]}],"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"127.0.0.1:{{ branch | hash_port }}"}]}]}'
|
||||
"""
|
||||
|
||||
[pre-remove]
|
||||
proxy = "curl -sf -X DELETE http://localhost:2019/id/wt:{{ repo }}:{{ branch | sanitize }} || true"
|
||||
|
||||
[list]
|
||||
url = "http://{{ branch | sanitize }}.{{ repo }}.localhost:8080"
|
||||
```
|
||||
|
||||
`{{ target }}` is the branch being merged into. `post-merge` runs in the target's worktree (or the primary worktree if target has none), so deploy commands see the merged code.
|
||||
**How it works:**
|
||||
|
||||
## Shortcuts
|
||||
1. `wt switch --create feature-auth` runs the `post-start` hook, starting the dev server on a deterministic port (`{{ branch | hash_port }}` → 18283)
|
||||
2. The hook starts Caddy if needed and registers a route using the same port: `feature-auth.myproject` → `localhost:18283`
|
||||
3. `*.localhost` resolves to `127.0.0.1` via the OS
|
||||
4. Visiting `http://feature-auth.myproject.localhost:8080`: Caddy matches the subdomain and proxies to the dev server
|
||||
|
||||
Special arguments work across all commands—see [`wt switch`](https://worktrunk.dev/switch/#shortcuts) for the full list.
|
||||
|
||||
```bash
|
||||
wt switch --create hotfix --base=@ # Branch from current HEAD
|
||||
wt switch - # Switch to previous worktree
|
||||
wt remove @ # Remove current worktree
|
||||
```
|
||||
|
||||
## Stacked branches
|
||||
|
||||
Branch from current HEAD instead of the default branch:
|
||||
|
||||
```bash
|
||||
wt switch --create feature-part2 --base=@
|
||||
```
|
||||
|
||||
## Agent handoffs
|
||||
|
||||
Spawn a worktree with an agent CLI running in the background. Examples below use `claude`; for OpenCode, replace `claude` with `'opencode run'`.
|
||||
|
||||
**tmux** (new detached session):
|
||||
```bash
|
||||
tmux new-session -d -s fix-auth-bug "wt switch --create fix-auth-bug -x claude -- \
|
||||
'The login session expires after 5 minutes. Find the session timeout config and extend it to 24 hours.'"
|
||||
```
|
||||
|
||||
**Zellij** (new pane in current session):
|
||||
```bash
|
||||
zellij run -- wt switch --create fix-auth-bug -x claude -- \
|
||||
'The login session expires after 5 minutes. Find the session timeout config and extend it to 24 hours.'
|
||||
```
|
||||
|
||||
This lets one agent session hand off work to another that runs in the background. Hooks run inside the multiplexer session/pane.
|
||||
|
||||
The [worktrunk skill](https://worktrunk.dev/claude-code/) includes guidance for Claude Code (and other agent CLIs that load it) to execute this pattern. To enable it, request it explicitly ("spawn a parallel worktree for...") or add to your project instructions (`CLAUDE.md` or `AGENTS.md`):
|
||||
|
||||
```markdown
|
||||
When I ask you to spawn parallel worktrees, use the agent handoff pattern
|
||||
from the worktrunk skill.
|
||||
```
|
||||
|
||||
## Tmux session per worktree
|
||||
### Tmux session per worktree
|
||||
|
||||
Each worktree gets its own tmux session with a multi-pane layout.
|
||||
|
||||
@@ -367,7 +373,7 @@ To create a worktree and immediately attach:
|
||||
$ wt switch --create feature -x tmux -- attach -t '{{ branch | sanitize }}'
|
||||
```
|
||||
|
||||
## cmux workspace per worktree
|
||||
### cmux workspace per worktree
|
||||
|
||||
Each worktree gets its own [cmux](https://cmux.com) workspace. Switching worktrees switches workspaces; removing a worktree closes its workspace. Configuration contributed by [@endigma](https://github.com/endigma) ([#2796](https://github.com/max-sixty/worktrunk/issues/2796)).
|
||||
|
||||
@@ -402,7 +408,7 @@ WS=$(cmux --json list-workspaces 2>/dev/null \\
|
||||
|
||||
**Why `pre-*` instead of `post-*`?** cmux restricts socket access to processes spawned inside a cmux terminal. `post-*` hooks run as detached background processes, breaking the process ancestry chain. `pre-*` hooks run in the foreground and inherit the terminal's process lineage.
|
||||
|
||||
## Xcode DerivedData cleanup
|
||||
### Xcode DerivedData cleanup
|
||||
|
||||
Clean up Xcode's DerivedData when removing a worktree. Each DerivedData directory contains an `info.plist` recording its project path — grep for the worktree path to find and remove the matching build cache:
|
||||
|
||||
@@ -420,42 +426,90 @@ clean-derived = """
|
||||
"""
|
||||
```
|
||||
|
||||
## Subdomain routing with Caddy
|
||||
<!-- Hand-tested 2026-03-07 -->
|
||||
## Working with agents
|
||||
|
||||
Clean URLs like `http://feature-auth.myproject.localhost` without port numbers. Useful for cookies, CORS, and matching production URL structure.
|
||||
### Track agent status
|
||||
|
||||
**Prerequisites:** [Caddy](https://caddyserver.com/docs/install) (`brew install caddy`)
|
||||
The agent plugins mark each worktree 🤖 (working) or 💬 (waiting) in `wt list`, and `wt config state marker set` sets a marker by hand for any other workflow. See [Activity tracking](https://worktrunk.dev/claude-code/#activity-tracking).
|
||||
|
||||
```toml
|
||||
# .config/wt.toml
|
||||
[post-start]
|
||||
server = "wt step tether -- npm run dev -- --port {{ branch | hash_port }}"
|
||||
proxy = """
|
||||
curl -sf --max-time 0.5 http://localhost:2019/config/ || caddy start
|
||||
curl -sf http://localhost:2019/config/apps/http/servers/wt || \
|
||||
curl -sfX PUT http://localhost:2019/config/apps/http/servers/wt -H 'Content-Type: application/json' \
|
||||
-d '{"listen":[":8080"],"automatic_https":{"disable":true},"routes":[]}'
|
||||
curl -sf -X DELETE http://localhost:2019/id/wt:{{ repo }}:{{ branch | sanitize }} || true
|
||||
curl -sfX PUT http://localhost:2019/config/apps/http/servers/wt/routes/0 -H 'Content-Type: application/json' \
|
||||
-d '{"@id":"wt:{{ repo }}:{{ branch | sanitize }}","match":[{"host":["{{ branch | sanitize }}.{{ repo }}.localhost"]}],"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"127.0.0.1:{{ branch | hash_port }}"}]}]}'
|
||||
"""
|
||||
### Agent handoffs
|
||||
|
||||
[pre-remove]
|
||||
proxy = "curl -sf -X DELETE http://localhost:2019/id/wt:{{ repo }}:{{ branch | sanitize }} || true"
|
||||
Spawn a worktree with an agent CLI running in the background. `-x` names the program to run and everything after `--` is passed to it, so OpenCode's subcommand goes after the `--`: `-x opencode -- run '<task>'`.
|
||||
|
||||
[list]
|
||||
url = "http://{{ branch | sanitize }}.{{ repo }}.localhost:8080"
|
||||
**tmux** (new detached session):
|
||||
```bash
|
||||
tmux new-session -d -s fix-auth-bug "wt switch --create fix-auth-bug -x claude -- \
|
||||
'The login session expires after 5 minutes. Find the session timeout config and extend it to 24 hours.'"
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
**Zellij** (new pane in current session):
|
||||
```bash
|
||||
zellij run -- wt switch --create fix-auth-bug -x claude -- \
|
||||
'The login session expires after 5 minutes. Find the session timeout config and extend it to 24 hours.'
|
||||
```
|
||||
|
||||
1. `wt switch --create feature-auth` runs the `post-start` hook, starting the dev server on a deterministic port (`{{ branch | hash_port }}` → 16460)
|
||||
2. The hook starts Caddy if needed and registers a route using the same port: `feature-auth.myproject` → `localhost:16460`
|
||||
3. `*.localhost` resolves to `127.0.0.1` via the OS
|
||||
4. Visiting `http://feature-auth.myproject.localhost:8080`: Caddy matches the subdomain and proxies to the dev server
|
||||
This lets one agent session hand off work to another that runs in the background. Hooks run inside the multiplexer session/pane.
|
||||
|
||||
## Monitor hook logs
|
||||
The [worktrunk skill](https://worktrunk.dev/claude-code/) includes guidance for Claude Code (and other agent CLIs that load it) to execute this pattern. To enable it, request it explicitly ("spawn a parallel worktree for...") or add to your project instructions (`CLAUDE.md` or `AGENTS.md`):
|
||||
|
||||
```markdown
|
||||
When I ask you to spawn parallel worktrees, use the agent handoff pattern
|
||||
from the worktrunk skill.
|
||||
```
|
||||
|
||||
## Status, commits, and logs
|
||||
|
||||
### Monitor CI across branches
|
||||
|
||||
```bash
|
||||
wt list --full --branches
|
||||
```
|
||||
|
||||
Shows PR/CI status for all branches, including those without worktrees. CI indicators are clickable links to the PR page.
|
||||
|
||||
### LLM branch summaries
|
||||
|
||||
With `summary = true` and [`commit.generation`](https://worktrunk.dev/config/#commit) configured, `wt list --full` shows an LLM-generated one-line summary for each branch. The same summaries appear in the [`wt switch` picker](https://worktrunk.dev/switch/#interactive-picker)'s `summary` tab.
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[list]
|
||||
summary = true
|
||||
```
|
||||
|
||||
See [LLM Commits](https://worktrunk.dev/llm-commits/#branch-summaries) for details.
|
||||
|
||||
### JSON API
|
||||
|
||||
```bash
|
||||
wt list --format=json
|
||||
```
|
||||
|
||||
Structured output for dashboards, statuslines, and scripts. See [`wt list`](https://worktrunk.dev/list/) for query examples.
|
||||
|
||||
### Manual commit messages
|
||||
|
||||
The `commit.generation.command` receives the rendered prompt on stdin and returns the commit message on stdout. To write commit messages by hand instead of using an LLM, point it at `$EDITOR`:
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[commit.generation]
|
||||
command = '''f=$(mktemp); printf '\n\n' > "$f"; sed 's/^/# /' >> "$f"; ${EDITOR:-vi} "$f" < /dev/tty > /dev/tty; grep -v '^#' "$f"'''
|
||||
```
|
||||
|
||||
This comments out the rendered prompt (diff, branch name, stats) with `#` prefixes, opens your editor, and strips comment lines on save. A couple of blank lines at the top give you space to type; the prompt context is visible below for reference.
|
||||
|
||||
To keep the LLM as default but use the editor for a specific merge, add a [worktrunk alias](https://worktrunk.dev/extending/#aliases):
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[aliases]
|
||||
mc = '''WORKTRUNK_COMMIT__GENERATION__COMMAND='f=$(mktemp); printf "\n\n" > "$f"; sed "s/^/# /" >> "$f"; ${EDITOR:-vi} "$f" < /dev/tty > /dev/tty; grep -v "^#" "$f"' wt merge'''
|
||||
```
|
||||
|
||||
Then `wt mc` opens an editor for the commit message while plain `wt merge` continues to use the LLM.
|
||||
|
||||
### Monitor hook logs
|
||||
|
||||
Follow background hook output:
|
||||
|
||||
@@ -470,64 +524,3 @@ Create an alias for frequent use:
|
||||
```bash
|
||||
alias wtlog='f() { tail -f "$(wt config state logs get --hook="$1")"; }; f'
|
||||
```
|
||||
|
||||
## Bare repository layout
|
||||
|
||||
A [bare repository](https://git-scm.com/docs/gitrepository-layout) has no working tree, so all branches — including the default — are [linked worktrees](https://git-scm.com/docs/git-worktree) at equal paths. No branch gets special treatment.
|
||||
|
||||
Cloning a bare repo into `<project>/.git` puts all worktrees under one directory:
|
||||
|
||||
```bash
|
||||
git clone --bare <url> myproject/.git
|
||||
cd myproject
|
||||
```
|
||||
|
||||
With `worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}"`, worktrees become subdirectories of `myproject/`:
|
||||
|
||||
```
|
||||
myproject/
|
||||
├── .git/ # bare repository
|
||||
├── main/ # default branch worktree
|
||||
├── feature/ # feature branch worktree
|
||||
└── bugfix/ # bugfix branch worktree
|
||||
```
|
||||
|
||||
### Configure the worktree path
|
||||
|
||||
On first `wt switch` in a bare repo at a hidden path (`.git`, `.bare`), worktrunk detects that the default template would produce broken paths like `myproject/.git.main` and offers a fix:
|
||||
|
||||
```
|
||||
▲ Bare repo at myproject/.git — worktrees will be at myproject/.git.main
|
||||
◎ Configure worktree-path to place worktrees at myproject/main? [y/N/?]
|
||||
```
|
||||
|
||||
Accepting writes a project-scoped entry to user config:
|
||||
|
||||
```toml
|
||||
# ~/.config/worktrunk/config.toml
|
||||
[projects."github.com/myorg/myrepo"]
|
||||
worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}"
|
||||
```
|
||||
|
||||
Run `wt config show` from inside any worktree to find the project identifier (`Identifier: …` in the PROJECT CONFIG section). Set it globally with `worktree-path = "..."` at the top level if this layout is preferred for all bare repos.
|
||||
|
||||
### Create the first worktree
|
||||
|
||||
```bash
|
||||
wt switch main
|
||||
```
|
||||
|
||||
For a freshly cloned bare repo the default branch already exists, so `wt switch main` (without `--create`) is enough. Use `wt switch --create <branch>` for new branches.
|
||||
|
||||
Now `wt switch --create feature` creates `myproject/feature/`.
|
||||
|
||||
### Set up the project config
|
||||
|
||||
The project config (`.config/wt.toml`) must live inside a worktree — the bare `.git` directory has no tracked files. Once the first worktree exists, create it from there:
|
||||
|
||||
```bash
|
||||
cd myproject/main
|
||||
wt config create --project
|
||||
```
|
||||
|
||||
Commit the file and it will appear in every worktree automatically.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Claude-specific troubleshooting guidance for common worktrunk issues.
|
||||
|
||||
## Commit Message Generation
|
||||
## Commit message generation
|
||||
|
||||
### Command not found
|
||||
|
||||
@@ -81,7 +81,7 @@ post-start = "npm run build"
|
||||
|
||||
### A `for-each` or `--execute` alias uses the same value in every worktree
|
||||
|
||||
The alias body renders once at dispatch, in the invoking worktree, so a per-worktree variable like `{{ branch }}` is baked before the nested `wt` command iterates. If `wt config alias dry-run <name>` shows a single substituted value (e.g. `… echo branch=main`), it was baked at that first pass. Defer it with `{% raw %}{{ branch }}{% endraw %}`, and for `for-each` keep it inside a quoted `sh -c '...'` so the alias's shell doesn't word-split it. Repo-level variables like `{{ default_branch }}` are unaffected — they are identical in every worktree. See `reference/extending.md#deferring-expansion-to-a-nested-wt-command`.
|
||||
The alias body rendered once at dispatch, baking the variable to the invoking worktree's value before the nested `wt` command iterated. See `reference/extending.md#deferring-expansion-to-a-nested-wt-command` for how to confirm it and how to defer the variable.
|
||||
|
||||
## List
|
||||
|
||||
@@ -90,7 +90,7 @@ The alias body renders once at dispatch, in the invoking worktree, so a per-work
|
||||
The timeout warning names the tasks that didn't finish:
|
||||
|
||||
```
|
||||
wt list timed out after 120s (170 results received); blocked tasks:
|
||||
Listing worktrees timed out after 120s (170 results received); blocked tasks:
|
||||
<branch>: working-tree-diff, working-tree-conflicts
|
||||
```
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -13,7 +13,7 @@ A quick demo:
|
||||
|
||||
AI agents like Claude Code and Codex can handle longer tasks without
|
||||
supervision, such that it's possible to manage 5-10+ in parallel. Git's native
|
||||
worktree feature give each agent its own working directory, so they don't step
|
||||
worktree feature gives each agent its own working directory, so they don't step
|
||||
on each other's changes.
|
||||
|
||||
But the git worktree UX is clunky. Even a task as small as starting a new
|
||||
|
||||
+6
-6
@@ -638,7 +638,7 @@ $ wt config update --output=-
|
||||
#[command(
|
||||
after_long_help = r#"Project hooks and project aliases prompt for approval on first run to prevent untrusted projects from running arbitrary commands. Approvals from both flows are stored together.
|
||||
|
||||
## Examples
|
||||
## Approval examples
|
||||
|
||||
List commands and their approval status for current project:
|
||||
```console
|
||||
@@ -709,7 +709,7 @@ Approved commands are saved to `~/.config/worktrunk/approvals.toml`. Re-approval
|
||||
#[command(
|
||||
after_long_help = r#"Aliases are command templates configured in user (`~/.config/worktrunk/config.toml`) or project (`.config/wt.toml`) config and run as `wt <name>`. See the [Extending Worktrunk guide](/extending/#aliases) for the configuration format.
|
||||
|
||||
## Examples
|
||||
## Alias examples
|
||||
|
||||
Show every configured alias's template:
|
||||
```console
|
||||
@@ -769,7 +769,7 @@ $ wt config plugins pi install
|
||||
- **vars**: [Custom variables per branch](/config/#wt-config-state-vars)
|
||||
- **logs**: [Operation and debug logs](/config/#wt-config-state-logs)
|
||||
|
||||
## Examples
|
||||
## State examples
|
||||
|
||||
Get the default branch:
|
||||
```console
|
||||
@@ -889,7 +889,7 @@ untouched."#)]
|
||||
|
||||
Without a subcommand, runs `get`.
|
||||
|
||||
## Examples
|
||||
## Cache examples
|
||||
|
||||
Show cache contents:
|
||||
```console
|
||||
@@ -1022,7 +1022,7 @@ All logs are stored in `.git/wt/logs/` (in the main worktree's git directory). A
|
||||
|
||||
`wt config state logs --format=json` emits three arrays — `command_log`, `hook_output`, `diagnostic`. Each entry carries a `file` (relative), `path` (absolute), `size`, and `modified_at` (unix seconds). Hook-output entries additionally expose `branch`, `source` (`user` / `project` / `internal`), `hook_type` (the `post-*` kind, or `null` for internal ops), and `name`. Filter with `jq` to pick out a specific entry.
|
||||
|
||||
## Examples
|
||||
## Log examples
|
||||
|
||||
List all log files:
|
||||
```console
|
||||
@@ -1148,7 +1148,7 @@ Without a subcommand, runs `get` for the current branch. For `--branch`, use `ge
|
||||
name = "vars",
|
||||
after_long_help = r#"Store custom variables per branch. Values are stored as-is — plain strings or JSON.
|
||||
|
||||
## Examples
|
||||
## Variable examples
|
||||
|
||||
Set and get values:
|
||||
```console
|
||||
|
||||
+11
-11
@@ -625,7 +625,7 @@ pub(crate) enum Commands {
|
||||
#[command(
|
||||
after_long_help = r#"Worktrees are addressed by branch name; paths are computed from a configurable template. Unlike `git switch`, this navigates between worktrees rather than changing branches in place.
|
||||
|
||||
<!-- demo: wt-switch.gif 1600x900 -->
|
||||
<!-- demo: wt-switch.gif 1600x900 | Switching to a worktree, creating one, and creating one that launches an agent -->
|
||||
## Examples
|
||||
|
||||
```console
|
||||
@@ -691,7 +691,7 @@ When called without arguments, `wt switch` opens an interactive picker to browse
|
||||
|
||||
The CI column shows each row's PR/MR CI and review status, the same as [`wt list --full`](/list/).
|
||||
|
||||
<!-- demo: wt-switch-picker.gif 1600x800 -->
|
||||
<!-- demo: wt-switch-picker.gif 1600x800 | Browsing the picker, scrolling a preview, filtering, and selecting -->
|
||||
**Keybindings:**
|
||||
|
||||
| Key | Action |
|
||||
@@ -781,7 +781,7 @@ To change which branch a worktree is on, use `git switch` inside that worktree.
|
||||
#[command(
|
||||
after_long_help = r#"Shows uncommitted changes, divergence from the default branch and remote, and optional CI status and LLM summaries.
|
||||
|
||||
<!-- demo: wt-list.gif 1600x900 -->
|
||||
<!-- demo: wt-list.gif 1600x900 | Progressive rendering, then --full and --branches -->
|
||||
The table renders progressively: branch names, paths, and commit hashes appear immediately, then status, divergence, and other columns fill in as background git operations complete.
|
||||
|
||||
## Full mode
|
||||
@@ -1426,7 +1426,7 @@ Detached worktrees have no branch name. Pass the worktree path instead: `wt remo
|
||||
#[command(
|
||||
after_long_help = r#"Unlike `git merge`, this merges the current branch into the target branch — not the target into current. Similar to clicking "Merge pull request" on GitHub, but locally. The target defaults to the default branch.
|
||||
|
||||
<!-- demo: wt-merge.gif 1600x900 -->
|
||||
<!-- demo: wt-merge.gif 1600x900 | Creating a worktree, committing in it, and merging it away -->
|
||||
## Examples
|
||||
|
||||
Merge to the default branch:
|
||||
@@ -2267,7 +2267,7 @@ exclude = [] # Additional excludes (e.g., [".cache/", ".turbo/"])
|
||||
|
||||
Built-in excludes (VCS metadata and tool-state directories) always apply; [the `wt step copy-ignored` docs](/step/#wt-step-copy-ignored) list them. User config and project config exclusions are combined.
|
||||
|
||||
### Aliases
|
||||
### User aliases
|
||||
|
||||
Command templates that run as `wt <name>`. See the [Extending Worktrunk guide](/extending/#aliases) for usage and flags.
|
||||
|
||||
@@ -2277,7 +2277,7 @@ greet = "echo Hello from {{ branch }}"
|
||||
url = "echo http://localhost:{{ branch | hash_port }}"
|
||||
```
|
||||
|
||||
Aliases defined here apply to all projects. For project-specific aliases, use the [project config](/config/#project-configuration) `[aliases]` section instead.
|
||||
Aliases defined here apply to all projects. For project-specific aliases, use the [project config](/config/#project-aliases) `[aliases]` section instead.
|
||||
|
||||
### User project-specific settings
|
||||
|
||||
@@ -2473,9 +2473,9 @@ template-append = """
|
||||
|
||||
How the fragment renders, and the project-config counterpart: [the LLM commits guide](/llm-commits/#appending-to-the-prompt).
|
||||
|
||||
## Hooks
|
||||
## User hooks
|
||||
|
||||
See [`wt hook`](/hook/) for hook types, execution order, template variables, and examples. User hooks apply to all projects; [project hooks](/config/#project-configuration) apply only to that repository.
|
||||
See [`wt hook`](/hook/) for hook types, execution order, template variables, and examples. User hooks apply to all projects; [project hooks](/config/#project-hooks) apply only to that repository.
|
||||
<!-- USER_CONFIG_END -->
|
||||
<!-- PROJECT_CONFIG_START -->
|
||||
# Project Configuration
|
||||
@@ -2484,7 +2484,7 @@ Project configuration lets teams share repository-specific settings — hooks, d
|
||||
|
||||
To create a starter file with commented-out examples, run `wt config create --project`.
|
||||
|
||||
## Hooks
|
||||
## Project hooks
|
||||
|
||||
Project hooks apply to this repository only. See [`wt hook`](/hook/) for hook types, execution order, and examples.
|
||||
|
||||
@@ -2540,7 +2540,7 @@ exclude = [".cache/", ".turbo/"]
|
||||
|
||||
Built-in excludes (VCS metadata and tool-state directories) always apply; [the `wt step copy-ignored` docs](/step/#wt-step-copy-ignored) list them. User config and project config exclusions are combined.
|
||||
|
||||
## Aliases
|
||||
## Project aliases
|
||||
|
||||
Command templates that run as `wt <name>`. See the [Extending Worktrunk guide](/extending/#aliases) for usage and flags.
|
||||
|
||||
@@ -2550,7 +2550,7 @@ deploy = "make deploy BRANCH={{ branch }}"
|
||||
url = "echo http://localhost:{{ branch | hash_port }}"
|
||||
```
|
||||
|
||||
Aliases defined here are shared with teammates. For personal aliases, use the [user config](/config/#aliases) `[aliases]` section instead.
|
||||
Aliases defined here are shared with teammates. For personal aliases, use the [user config](/config/#user-aliases) `[aliases]` section instead.
|
||||
<!-- PROJECT_CONFIG_END -->
|
||||
|
||||
# Shell Integration
|
||||
|
||||
+2
-2
@@ -643,11 +643,11 @@ Locked worktrees and the main worktree are always skipped. The current worktree
|
||||
|
||||
## Min-age guard
|
||||
|
||||
Worktrees and branches younger than `--min-age` (default: 1 day) are skipped. This prevents removing a worktree just created from the default branch — it looks "merged" because its branch points at the same commit.
|
||||
Candidates younger than `--min-age` (default: 1 day) are skipped. A worktree's age comes from its creation time, and a branch with no worktree takes its age from its oldest reflog entry. This prevents removing a worktree just created from the default branch: it looks "merged" because its branch points at the same commit.
|
||||
|
||||
```console
|
||||
$ wt step prune --min-age=0s # no age guard
|
||||
$ wt step prune --min-age=2d # skip worktrees younger than 2 days
|
||||
$ wt step prune --min-age=2d # skip candidates younger than 2 days
|
||||
```
|
||||
|
||||
## JSON output
|
||||
|
||||
+136
-7
@@ -122,6 +122,46 @@ impl PageMode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Keep the global-options section in the first command reference on a page,
|
||||
/// and cut it from the rest.
|
||||
///
|
||||
/// Clap repeats the same ~20-line `Global Options:` list in every reference it
|
||||
/// renders, so a page built from subdocs stacks one copy per subcommand — 11 on
|
||||
/// `wt config`, 13 on `wt step`. That pads both the site page and the skill
|
||||
/// mirror, and gives the site's search that many near-identical hits. `kept`
|
||||
/// carries the state for one page; a reference with no such section leaves it
|
||||
/// alone, so the page's first *real* reference is always the one that keeps it.
|
||||
///
|
||||
/// Terminal `--help` renders through clap directly and still prints the section
|
||||
/// for every command.
|
||||
///
|
||||
/// The section is last in the block: `help_reference_inner` cuts
|
||||
/// `after_long_help` off at `find_after_help_start`, which is the first
|
||||
/// non-indented line after it.
|
||||
fn take_global_options(reference: &str, kept: &mut bool) -> String {
|
||||
// Clap renders references with color escapes even under NO_COLOR, so match
|
||||
// on the stripped text.
|
||||
let heading = |line: &str| line.ansi_strip().trim_end() == "Global Options:";
|
||||
let Some(start) = reference
|
||||
.lines()
|
||||
.scan(0, |offset, line| {
|
||||
let at = *offset;
|
||||
*offset += line.len() + 1;
|
||||
Some((at, line))
|
||||
})
|
||||
.find_map(|(at, line)| heading(line).then_some(at))
|
||||
else {
|
||||
return reference.to_string();
|
||||
};
|
||||
|
||||
if *kept {
|
||||
reference[..start].trim_end().to_string()
|
||||
} else {
|
||||
*kept = true;
|
||||
reference.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom help handling for pager support and markdown rendering.
|
||||
///
|
||||
/// We intercept help requests to provide:
|
||||
@@ -485,7 +525,11 @@ Commands with pages: merge, switch, remove, list"
|
||||
};
|
||||
|
||||
let main_help = mode.process_body(main_content);
|
||||
// One flag for the whole page: the first reference below keeps its global
|
||||
// options and every later one drops them.
|
||||
let mut kept_global_options = false;
|
||||
let reference_block = help_reference(&[subcommand], Some(100));
|
||||
let reference_block = take_global_options(&reference_block, &mut kept_global_options);
|
||||
|
||||
mode.emit_header(subcommand);
|
||||
println!("{}", main_help.trim());
|
||||
@@ -504,7 +548,8 @@ Commands with pages: merge, switch, remove, list"
|
||||
// marker) before expansion — each subdoc's own body is post-processed
|
||||
// inside format_subcommand_section, so re-running would double-convert.
|
||||
let subdocs = mode.process_subdoc_trailing(subdocs);
|
||||
let subdocs_expanded = expand_subdoc_placeholders(&subdocs, sub, &parent_name, mode);
|
||||
let subdocs_expanded =
|
||||
expand_subdoc_placeholders(&subdocs, sub, &parent_name, mode, &mut kept_global_options);
|
||||
println!();
|
||||
println!("# Subcommands");
|
||||
println!();
|
||||
@@ -647,6 +692,7 @@ fn expand_subdoc_placeholders(
|
||||
parent_cmd: &clap::Command,
|
||||
parent_name: &str,
|
||||
mode: PageMode,
|
||||
kept_global_options: &mut bool,
|
||||
) -> String {
|
||||
const SUFFIX: &str = " -->";
|
||||
|
||||
@@ -662,7 +708,13 @@ fn expand_subdoc_placeholders(
|
||||
.get_subcommands()
|
||||
.find(|s| s.get_name() == subcommand_name)
|
||||
{
|
||||
format_subcommand_section(sub, parent_name, subcommand_name, mode)
|
||||
format_subcommand_section(
|
||||
sub,
|
||||
parent_name,
|
||||
subcommand_name,
|
||||
mode,
|
||||
kept_global_options,
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"<!-- subdoc error: subcommand '{}' not found -->",
|
||||
@@ -711,6 +763,7 @@ fn format_subcommand_section(
|
||||
parent_name: &str,
|
||||
subcommand_name: &str,
|
||||
mode: PageMode,
|
||||
kept_global_options: &mut bool,
|
||||
) -> String {
|
||||
// parent_name is "wt config", subcommand_name is "create"
|
||||
// full_command is "wt config create"
|
||||
@@ -743,6 +796,7 @@ fn format_subcommand_section(
|
||||
.collect();
|
||||
|
||||
let reference_block = help_reference(&command_path, Some(100));
|
||||
let reference_block = take_global_options(&reference_block, kept_global_options);
|
||||
|
||||
// Format the section: heading, badge (outside heading), main content, command reference
|
||||
let mut section = format!("## {full_command}\n\n");
|
||||
@@ -766,7 +820,8 @@ fn format_subcommand_section(
|
||||
// Expand nested subdocs after the command reference.
|
||||
if let Some(subdocs) = subdoc_content {
|
||||
let subdocs = mode.process_subdoc_trailing(subdocs);
|
||||
let subdocs_expanded = expand_subdoc_placeholders(&subdocs, sub, &full_command, mode);
|
||||
let subdocs_expanded =
|
||||
expand_subdoc_placeholders(&subdocs, sub, &full_command, mode, kept_global_options);
|
||||
section.push('\n');
|
||||
section.push_str(subdocs_expanded.trim());
|
||||
section.push('\n');
|
||||
@@ -784,7 +839,10 @@ fn format_subcommand_section(
|
||||
/// The placeholder should be on its own line without surrounding blank lines in the source.
|
||||
/// This function adds blank lines around the figure for proper markdown paragraph separation.
|
||||
///
|
||||
/// Supports optional dimensions: `<!-- demo: filename.gif 1600x900 -->`
|
||||
/// Supports optional dimensions and a caption, in that order:
|
||||
/// `<!-- demo: filename.gif 1600x900 | What the recording shows -->`. The
|
||||
/// caption becomes the figure's `<figcaption>`, the same treatment the
|
||||
/// hand-written homepage figures get; without one the figure has no caption.
|
||||
fn expand_demo_placeholders(text: &str) -> String {
|
||||
const SUFFIX: &str = " -->";
|
||||
|
||||
@@ -793,8 +851,12 @@ fn expand_demo_placeholders(text: &str) -> String {
|
||||
let after_prefix = start + DEMO_MARKER_PREFIX.len();
|
||||
if let Some(end_offset) = result[after_prefix..].find(SUFFIX) {
|
||||
let content = &result[after_prefix..after_prefix + end_offset];
|
||||
// Parse "filename.gif" or "filename.gif 1600x900"
|
||||
let mut parts = content.split_whitespace();
|
||||
// Split "filename.gif 1600x900" from an optional "| caption"
|
||||
let (spec, caption) = match content.split_once('|') {
|
||||
Some((spec, caption)) => (spec, caption.trim()),
|
||||
None => (content, ""),
|
||||
};
|
||||
let mut parts = spec.split_whitespace();
|
||||
let filename = parts.next().unwrap_or("");
|
||||
let dimensions = parts.next(); // Optional "WIDTHxHEIGHT"
|
||||
|
||||
@@ -807,12 +869,18 @@ fn expand_demo_placeholders(text: &str) -> String {
|
||||
.map(|(w, h)| format!(" width=\"{w}\" height=\"{h}\""))
|
||||
.unwrap_or_default();
|
||||
|
||||
let figcaption = if caption.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("\n<figcaption>{caption}</figcaption>")
|
||||
};
|
||||
|
||||
// Use figure.demo class for proper mobile styling (no shrink, horizontal scroll)
|
||||
// Generate <picture> element for light/dark theme switching
|
||||
// Assets are organized as: /assets/docs/{light,dark}/filename.gif
|
||||
// Add trailing newline for markdown paragraph separation after the figure
|
||||
let replacement = format!(
|
||||
"<figure class=\"demo\">\n<picture>\n <source srcset=\"/assets/docs/dark/{filename}\" media=\"(prefers-color-scheme: dark)\">\n <img src=\"/assets/docs/light/{filename}\" alt=\"{alt_text} demo\"{dim_attrs}>\n</picture>\n</figure>\n"
|
||||
"<figure class=\"demo\">\n<picture>\n <source srcset=\"/assets/docs/dark/{filename}\" media=\"(prefers-color-scheme: dark)\">\n <img src=\"/assets/docs/light/{filename}\" alt=\"{alt_text} demo\"{dim_attrs}>\n</picture>{figcaption}\n</figure>\n"
|
||||
);
|
||||
let end = after_prefix + end_offset + SUFFIX.len();
|
||||
result.replace_range(start..end, &replacement);
|
||||
@@ -848,3 +916,64 @@ fn strip_demo_placeholders(text: &str) -> String {
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A reference with no `Global Options:` heading is returned whole, and
|
||||
/// leaves `kept` alone so the first reference that does carry one still
|
||||
/// keeps it. Every clap-rendered reference has the section today, so this
|
||||
/// is the path that runs if clap renames or drops the heading: the page
|
||||
/// loses nothing rather than being truncated at a heading that isn't there.
|
||||
#[test]
|
||||
fn take_global_options_passes_through_a_reference_without_the_section() {
|
||||
let reference = "Usage: wt list\n\nOptions:\n -h, --help Print help\n";
|
||||
let mut kept = false;
|
||||
|
||||
assert_eq!(take_global_options(reference, &mut kept), reference);
|
||||
assert!(!kept, "a reference with no section must not claim the slot");
|
||||
}
|
||||
|
||||
/// The first reference keeps the section; every later one ends before the
|
||||
/// heading. This is what stops a subdoc-assembled page from stacking a
|
||||
/// dozen copies of the same twenty lines.
|
||||
#[test]
|
||||
fn take_global_options_keeps_only_the_first_section() {
|
||||
let reference = "Usage: wt list\n\nGlobal Options:\n -C <path> Working directory\n";
|
||||
let mut kept = false;
|
||||
|
||||
assert_eq!(take_global_options(reference, &mut kept), reference);
|
||||
assert!(kept);
|
||||
assert_eq!(
|
||||
take_global_options(reference, &mut kept),
|
||||
"Usage: wt list",
|
||||
"a later reference ends before the heading"
|
||||
);
|
||||
}
|
||||
|
||||
/// The caption is optional: a placeholder without one renders a figure with
|
||||
/// no `<figcaption>` rather than an empty element.
|
||||
#[test]
|
||||
fn demo_placeholder_without_a_caption_renders_no_figcaption() {
|
||||
let expanded = expand_demo_placeholders("<!-- demo: wt-switch.gif 1600x900 -->");
|
||||
|
||||
assert!(
|
||||
!expanded.contains("figcaption"),
|
||||
"no caption means no element: {expanded}"
|
||||
);
|
||||
assert!(expanded.contains("width=\"1600\" height=\"900\""));
|
||||
}
|
||||
|
||||
/// A caption after `|` becomes the figure's `<figcaption>`.
|
||||
#[test]
|
||||
fn demo_placeholder_caption_becomes_a_figcaption() {
|
||||
let expanded =
|
||||
expand_demo_placeholders("<!-- demo: wt-switch.gif | Switching worktrees -->");
|
||||
|
||||
assert!(
|
||||
expanded.contains("<figcaption>Switching worktrees</figcaption>"),
|
||||
"{expanded}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,14 +123,27 @@ static RUST_RAW_STRING_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
|
||||
});
|
||||
|
||||
/// Regex to convert site-root documentation links to full URLs.
|
||||
/// Matches: [text](/page/) or [text](/page/#anchor).
|
||||
/// Matches: [text](/page/), [text](/page/#anchor), and the homepage forms
|
||||
/// [text](/) and [text](/#anchor) — the page segment is optional because the
|
||||
/// homepage has none.
|
||||
///
|
||||
/// Link text tolerates `]` characters when they appear inside a backticked
|
||||
/// code span (e.g. `[[block]]`), alternating "a `...` code span" with "any
|
||||
/// non-`]`-non-backtick char". Bare backticks are forbidden so the regex
|
||||
/// can't bridge across two unrelated code spans on the same line.
|
||||
static SITE_LINK_PATTERN: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"\[((?:`[^`]*`|[^\]`])+)\]\(/([^)/]+)/(#[^)]*)?\)").unwrap());
|
||||
static SITE_LINK_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"\[((?:`[^`]*`|[^\]`])+)\]\(/(?:([^)/#]+)/)?(#[^)]*)?\)").unwrap()
|
||||
});
|
||||
|
||||
/// Expand one `SITE_LINK_PATTERN` capture into an absolute worktrunk.dev link.
|
||||
fn expand_site_link(caps: ®ex::Captures) -> String {
|
||||
let text = caps.get(1).unwrap().as_str();
|
||||
let page = caps
|
||||
.get(2)
|
||||
.map_or(String::new(), |m| format!("{}/", m.as_str()));
|
||||
let anchor = caps.get(3).map_or("", |m| m.as_str());
|
||||
format!("[{text}](https://worktrunk.dev/{page}{anchor})")
|
||||
}
|
||||
|
||||
/// Guardrail for root-relative or legacy Zola links on generated non-site surfaces.
|
||||
static UNTRANSFORMED_SITE_LINK_PATTERN: LazyLock<Regex> =
|
||||
@@ -948,12 +961,7 @@ fn heading_to_anchor(heading: &str) -> String {
|
||||
fn transform_docs_to_github(content: &str) -> String {
|
||||
// Transform internal links
|
||||
let content = SITE_LINK_PATTERN
|
||||
.replace_all(content, |caps: ®ex::Captures| {
|
||||
let text = caps.get(1).unwrap().as_str();
|
||||
let page = caps.get(2).unwrap().as_str();
|
||||
let anchor = caps.get(3).map_or("", |m| m.as_str());
|
||||
format!("[{text}](https://worktrunk.dev/{page}/{anchor})")
|
||||
})
|
||||
.replace_all(content, expand_site_link)
|
||||
.into_owned();
|
||||
let content = AUTO_GENERATED_MARKER_PATTERN
|
||||
.replace_all(&content, "")
|
||||
@@ -1204,8 +1212,8 @@ fn test_config_markdown_links_convert_to_plain_text() {
|
||||
// `[[projects."…".post-start]]` pipelines, so a link naming one is the
|
||||
// next form to arrive; the code-span class covers it.
|
||||
(
|
||||
"see [`[[projects.\"…\".post-start]]` hooks](/config/#hooks) for the pipeline form",
|
||||
"see `[[projects.\"…\".post-start]]` hooks (https://worktrunk.dev/config/#hooks) for the pipeline form",
|
||||
"see [`[[projects.\"…\".post-start]]` hooks](/config/#project-hooks) for the pipeline form",
|
||||
"see `[[projects.\"…\".post-start]]` hooks (https://worktrunk.dev/config/#project-hooks) for the pipeline form",
|
||||
),
|
||||
// Two links on one line still both convert.
|
||||
(
|
||||
@@ -1241,7 +1249,7 @@ fn test_config_markdown_links_convert_to_plain_text() {
|
||||
fn test_untransformed_site_link_fails_the_config_transform() {
|
||||
// An unbalanced backtick in the link text: the code-span alternative can't
|
||||
// close, so the rewrite declines and the raw target would survive.
|
||||
transform_config_source_to_toml("See [a `broken span](/config/#hooks) here");
|
||||
transform_config_source_to_toml("See [a `broken span](/config/#project-hooks) here");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1437,11 +1445,13 @@ fn test_project_config_docs_include_all_sections() {
|
||||
}
|
||||
|
||||
// Hooks section should exist (individual hook keys are documented in user config
|
||||
// and cross-referenced from project config)
|
||||
// and cross-referenced from project config). The heading names its config
|
||||
// kind because both kinds land on /config/ and would otherwise share an
|
||||
// anchor.
|
||||
assert!(
|
||||
project_config_content.contains("## Hooks"),
|
||||
project_config_content.contains("## Project hooks"),
|
||||
"Hooks section heading missing from project config docs.\n\
|
||||
Expected `## Hooks` between PROJECT_CONFIG_START/END markers."
|
||||
Expected `## Project hooks` between PROJECT_CONFIG_START/END markers."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2094,12 +2104,7 @@ fn generate_skill_from_help(cmd: &str, project_root: &Path) -> Result<String, St
|
||||
/// Site-root links → full URLs, remove "See also", and collapse blank lines.
|
||||
fn finalize_skill_content(content: &str) -> String {
|
||||
let content = SITE_LINK_PATTERN
|
||||
.replace_all(content, |caps: ®ex::Captures| {
|
||||
let text = caps.get(1).unwrap().as_str();
|
||||
let page = caps.get(2).unwrap().as_str();
|
||||
let anchor = caps.get(3).map_or("", |m| m.as_str());
|
||||
format!("[{text}](https://worktrunk.dev/{page}/{anchor})")
|
||||
})
|
||||
.replace_all(content, expand_site_link)
|
||||
.into_owned();
|
||||
|
||||
// Installed skills don't have the site's root URL as a resolution base.
|
||||
@@ -2130,8 +2135,8 @@ fn finalize_skill_content(content: &str) -> String {
|
||||
/// only: Codex's plugin installer copies the plugin root with a copier that
|
||||
/// silently skips symlink entries (`copy_dir_recursive` in codex-rs
|
||||
/// core-plugins), so a symlink anywhere in the tree — a `skills` link at the
|
||||
/// top or a nested one like `reference/README.md` — ships no content, and a
|
||||
/// symlink also materializes as a plain text file on Windows checkouts.
|
||||
/// top or one nested under `reference/` — ships no content, and a symlink
|
||||
/// also materializes as a plain text file on Windows checkouts.
|
||||
/// Repo-root `skills/` stays the authored home: Gemini reads it directly, and
|
||||
/// the earlier sync stages write into it.
|
||||
fn sync_plugin_skills_mirror(project_root: &Path) -> (Vec<String>, Vec<String>) {
|
||||
@@ -2651,6 +2656,23 @@ fn sync_llms_txt(project_root: &Path) -> (Vec<String>, Vec<String>) {
|
||||
|
||||
let out = format!("{}\n", out.trim_end());
|
||||
|
||||
// Every page llms.txt names is served as `<slug>.md` from a hand-created
|
||||
// symlink into the skill reference. A new page reaches llms.txt from its
|
||||
// frontmatter alone, so without this the listing links a 404.
|
||||
for name in docs_content_page_names(&docs_dir) {
|
||||
let served = project_root.join("docs/public").join(&name);
|
||||
if !served.exists() {
|
||||
errors.push(format!(
|
||||
"docs/public/{name} is missing — add the symlink \
|
||||
`ln -s ../../skills/worktrunk/reference/{name} docs/public/{name}` \
|
||||
so llms.txt's https://worktrunk.dev/{name} resolves"
|
||||
));
|
||||
}
|
||||
}
|
||||
if !errors.is_empty() {
|
||||
return (errors, updated);
|
||||
}
|
||||
|
||||
let dst = project_root.join("docs/public/llms.txt");
|
||||
let current = fs::read_to_string(&dst).unwrap_or_default();
|
||||
if current != out {
|
||||
@@ -2835,6 +2857,82 @@ fn test_no_nested_auto_generated_markers() {
|
||||
);
|
||||
}
|
||||
|
||||
/// The authored sidebar in `docs/src/site-navigation.mjs` and each page's
|
||||
/// `sidebar.order` frontmatter are two orderings of the same pages, and only
|
||||
/// the frontmatter reaches `docs/public/llms.txt`. When they disagree, the
|
||||
/// sidebar a reader browses and the index an agent reads put the pages in
|
||||
/// different orders.
|
||||
///
|
||||
/// Only pages the sidebar names are checked. A page may carry an order without
|
||||
/// a sidebar entry (`code-signing.md`); the order still places it in llms.txt.
|
||||
#[test]
|
||||
fn test_sidebar_matches_frontmatter_order() {
|
||||
let project_root = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
let nav_path = project_root.join("docs/src/site-navigation.mjs");
|
||||
let nav = fs::read_to_string(&nav_path).unwrap();
|
||||
|
||||
// `link: '/switch/'` names the page `switch.md`; `link: '/'` is the
|
||||
// homepage, `worktrunk.md`. A link carrying a fragment (`/#install`) points
|
||||
// into a page rather than at one, so it has no order of its own.
|
||||
let link_pattern = Regex::new(r"link: '([^']+)'").unwrap();
|
||||
let slugs: Vec<String> = link_pattern
|
||||
.captures_iter(&nav)
|
||||
.map(|captures| captures[1].to_string())
|
||||
.filter(|link| !link.contains('#'))
|
||||
.map(|link| match link.trim_matches('/') {
|
||||
"" => "worktrunk".to_string(),
|
||||
slug => slug.to_string(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
slugs.len() > 5,
|
||||
"found only {} sidebar links in {} — the `link:` shape changed and this \
|
||||
test would pass on an empty list",
|
||||
slugs.len(),
|
||||
nav_path.display()
|
||||
);
|
||||
|
||||
let mut violations = Vec::new();
|
||||
let mut previous: Option<(String, i64)> = None;
|
||||
for slug in slugs {
|
||||
let path = project_root.join(format!("docs/src/content/docs/{slug}.md"));
|
||||
let content = fs::read_to_string(&path).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"sidebar names {slug}, but reading {} failed: {e}",
|
||||
path.display()
|
||||
)
|
||||
});
|
||||
let frontmatter = YAML_FRONTMATTER_PATTERN
|
||||
.captures(&content)
|
||||
.unwrap_or_else(|| panic!("no YAML frontmatter in {}", path.display()));
|
||||
let order: i64 = frontmatter
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.lines()
|
||||
.find_map(|line| line.trim().strip_prefix("order:"))
|
||||
.and_then(|value| value.trim().parse().ok())
|
||||
.unwrap_or_else(|| panic!("no numeric sidebar.order in {}", path.display()));
|
||||
|
||||
if let Some((previous_slug, previous_order)) = &previous
|
||||
&& order <= *previous_order
|
||||
{
|
||||
violations.push(format!(
|
||||
"{slug} (order {order}) is listed after {previous_slug} (order {previous_order})"
|
||||
));
|
||||
}
|
||||
previous = Some((slug, order));
|
||||
}
|
||||
|
||||
assert!(
|
||||
violations.is_empty(),
|
||||
"site-navigation.mjs lists pages out of `sidebar.order`, so the sidebar and \
|
||||
docs/public/llms.txt would order them differently. Reconcile the two:\n\n{}",
|
||||
violations.join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
/// The hand-authored `## Template variables` table in `src/cli/mod.rs` must
|
||||
/// match the variable constants in `src/config/expansion.rs`. Drift means the
|
||||
/// help docs lie about which vars hooks and aliases can reference.
|
||||
|
||||
@@ -74,7 +74,7 @@ Usage: [1m[36mwt config approvals[0m [36m[OPTIONS][0m [36m<COMMAND>[0m
|
||||
|
||||
Project hooks and project aliases prompt for approval on first run to prevent untrusted projects from running arbitrary commands. Approvals from both flows are stored together.
|
||||
|
||||
[1m[32mExamples[0m
|
||||
[1m[32mApproval examples[0m
|
||||
|
||||
List commands and their approval status for current project:
|
||||
[107m [0m [2m[0m[2m[34mwt[0m[2m config approvals list[0m
|
||||
|
||||
@@ -297,7 +297,7 @@ Creates [2m~/.config/worktrunk/config.toml[0m with the following content:
|
||||
[107m [0m [2m#[0m
|
||||
[107m [0m [2m# Built-in excludes (VCS metadata and tool-state directories) always apply; the `wt step copy-ignored` docs (https://worktrunk.dev/step/#wt-step-copy-ignored) list them. User config and project config exclusions are combined.[0m
|
||||
[107m [0m [2m#[0m
|
||||
[107m [0m [2m# ### Aliases[0m
|
||||
[107m [0m [2m# ### User aliases[0m
|
||||
[107m [0m [2m#[0m
|
||||
[107m [0m [2m# Command templates that run as `wt <name>`. See the Extending Worktrunk guide (https://worktrunk.dev/extending/#aliases) for usage and flags.[0m
|
||||
[107m [0m [2m#[0m
|
||||
@@ -305,7 +305,7 @@ Creates [2m~/.config/worktrunk/config.toml[0m with the following content:
|
||||
[107m [0m [2m# greet = "echo Hello from {{ branch }}"[0m
|
||||
[107m [0m [2m# url = "echo http://localhost:{{ branch | hash_port }}"[0m
|
||||
[107m [0m [2m#[0m
|
||||
[107m [0m [2m# Aliases defined here apply to all projects. For project-specific aliases, use the project config (https://worktrunk.dev/config/#project-configuration) `[aliases]` section instead.[0m
|
||||
[107m [0m [2m# Aliases defined here apply to all projects. For project-specific aliases, use the project config (https://worktrunk.dev/config/#project-aliases) `[aliases]` section instead.[0m
|
||||
[107m [0m [2m#[0m
|
||||
[107m [0m [2m# ### User project-specific settings[0m
|
||||
[107m [0m [2m#[0m
|
||||
@@ -488,9 +488,9 @@ Creates [2m~/.config/worktrunk/config.toml[0m with the following content:
|
||||
[107m [0m [2m#[0m
|
||||
[107m [0m [2m# How the fragment renders, and the project-config counterpart: the LLM commits guide (https://worktrunk.dev/llm-commits/#appending-to-the-prompt).[0m
|
||||
[107m [0m [2m#[0m
|
||||
[107m [0m [2m# ## Hooks[0m
|
||||
[107m [0m [2m# ## User hooks[0m
|
||||
[107m [0m [2m#[0m
|
||||
[107m [0m [2m# See `wt hook` (https://worktrunk.dev/hook/) for hook types, execution order, template variables, and examples. User hooks apply to all projects; project hooks (https://worktrunk.dev/config/#project-configuration) apply only to that repository.[0m
|
||||
[107m [0m [2m# See `wt hook` (https://worktrunk.dev/hook/) for hook types, execution order, template variables, and examples. User hooks apply to all projects; project hooks (https://worktrunk.dev/config/#project-hooks) apply only to that repository.[0m
|
||||
|
||||
[1m[32mProject config[0m
|
||||
|
||||
@@ -502,7 +502,7 @@ With [2m--project[0m, creates [2m.config/wt.toml[0m in the current repositor
|
||||
[107m [0m [2m#[0m
|
||||
[107m [0m [2m# To create a starter file with commented-out examples, run `wt config create --project`.[0m
|
||||
[107m [0m [2m#[0m
|
||||
[107m [0m [2m# ## Hooks[0m
|
||||
[107m [0m [2m# ## Project hooks[0m
|
||||
[107m [0m [2m#[0m
|
||||
[107m [0m [2m# Project hooks apply to this repository only. See `wt hook` (https://worktrunk.dev/hook/) for hook types, execution order, and examples.[0m
|
||||
[107m [0m [2m#[0m
|
||||
@@ -549,7 +549,7 @@ With [2m--project[0m, creates [2m.config/wt.toml[0m in the current repositor
|
||||
[107m [0m [2m#[0m
|
||||
[107m [0m [2m# Built-in excludes (VCS metadata and tool-state directories) always apply; the `wt step copy-ignored` docs (https://worktrunk.dev/step/#wt-step-copy-ignored) list them. User config and project config exclusions are combined.[0m
|
||||
[107m [0m [2m#[0m
|
||||
[107m [0m [2m# ## Aliases[0m
|
||||
[107m [0m [2m# ## Project aliases[0m
|
||||
[107m [0m [2m#[0m
|
||||
[107m [0m [2m# Command templates that run as `wt <name>`. See the Extending Worktrunk guide (https://worktrunk.dev/extending/#aliases) for usage and flags.[0m
|
||||
[107m [0m [2m#[0m
|
||||
@@ -557,6 +557,6 @@ With [2m--project[0m, creates [2m.config/wt.toml[0m in the current repositor
|
||||
[107m [0m [2m# deploy = "make deploy BRANCH={{ branch }}"[0m
|
||||
[107m [0m [2m# url = "echo http://localhost:{{ branch | hash_port }}"[0m
|
||||
[107m [0m [2m#[0m
|
||||
[107m [0m [2m# Aliases defined here are shared with teammates. For personal aliases, use the user config (https://worktrunk.dev/config/#aliases) `[aliases]` section instead.[0m
|
||||
[107m [0m [2m# Aliases defined here are shared with teammates. For personal aliases, use the user config (https://worktrunk.dev/config/#user-aliases) `[aliases]` section instead.[0m
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -345,7 +345,7 @@ Persistent flag values for [2mwt remove[0m. Override on command line as needed
|
||||
|
||||
Built-in excludes (VCS metadata and tool-state directories) always apply; the [2mwt step copy-ignored[0m docs list them. User config and project config exclusions are combined.
|
||||
|
||||
[32mAliases[0m
|
||||
[32mUser aliases[0m
|
||||
|
||||
Command templates that run as [2mwt <name>[0m. See the Extending Worktrunk guide for usage and flags.
|
||||
|
||||
@@ -532,7 +532,7 @@ Default template:
|
||||
|
||||
How the fragment renders, and the project-config counterpart: the LLM commits guide.
|
||||
|
||||
[1m[32mHooks[0m
|
||||
[1m[32mUser hooks[0m
|
||||
|
||||
See [2mwt hook[0m for hook types, execution order, template variables, and examples. User hooks apply to all projects; project hooks apply only to that repository.
|
||||
[32mPROJECT CONFIGURATION[0m
|
||||
@@ -541,7 +541,7 @@ Project configuration lets teams share repository-specific settings — hooks, d
|
||||
|
||||
To create a starter file with commented-out examples, run [2mwt config create --project[0m.
|
||||
|
||||
[1m[32mHooks[0m
|
||||
[1m[32mProject hooks[0m
|
||||
|
||||
Project hooks apply to this repository only. See [2mwt hook[0m for hook types, execution order, and examples.
|
||||
|
||||
@@ -587,7 +587,7 @@ Additional excludes for [2mwt step copy-ignored[0m:
|
||||
|
||||
Built-in excludes (VCS metadata and tool-state directories) always apply; the [2mwt step copy-ignored[0m docs list them. User config and project config exclusions are combined.
|
||||
|
||||
[1m[32mAliases[0m
|
||||
[1m[32mProject aliases[0m
|
||||
|
||||
Command templates that run as [2mwt <name>[0m. See the Extending Worktrunk guide for usage and flags.
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ State is stored in [2m.git/[0m (config entries and log files), separate from c
|
||||
- [1mvars[0m: Custom variables per branch
|
||||
- [1mlogs[0m: Operation and debug logs
|
||||
|
||||
[1m[32mExamples[0m
|
||||
[1m[32mState examples[0m
|
||||
|
||||
Get the default branch:
|
||||
[107m [0m [2m[0m[2m[34mwt[0m[2m config state default-branch[0m
|
||||
|
||||
@@ -90,7 +90,7 @@ View or drop worktrunk's regenerable caches in one place. Everything here is reb
|
||||
|
||||
Without a subcommand, runs [2mget[0m.
|
||||
|
||||
[1m[32mExamples[0m
|
||||
[1m[32mCache examples[0m
|
||||
|
||||
Show cache contents:
|
||||
[107m [0m [2m[0m[2m[34mwt[0m[2m config state cache[0m
|
||||
|
||||
@@ -129,7 +129,7 @@ All logs are stored in [2m.git/wt/logs/[0m (in the main worktree's git directo
|
||||
|
||||
[2mwt config state logs --format=json[0m emits three arrays — [2mcommand_log[0m, [2mhook_output[0m, [2mdiagnostic[0m. Each entry carries a [2mfile[0m (relative), [2mpath[0m (absolute), [2msize[0m, and [2mmodified_at[0m (unix seconds). Hook-output entries additionally expose [2mbranch[0m, [2msource[0m ([2muser[0m / [2mproject[0m / [2minternal[0m), [2mhook_type[0m (the [2mpost-*[0m kind, or [2mnull[0m for internal ops), and [2mname[0m. Filter with [2mjq[0m to pick out a specific entry.
|
||||
|
||||
[1m[32mExamples[0m
|
||||
[1m[32mLog examples[0m
|
||||
|
||||
List all log files:
|
||||
[107m [0m [2m[0m[2m[34mwt[0m[2m config state logs[0m
|
||||
|
||||
@@ -111,7 +111,7 @@ Global Options:
|
||||
|
||||
Unlike `git merge`, this merges the current branch into the target branch — not the target into current. Similar to clicking "Merge pull request" on GitHub, but locally. The target defaults to the default branch.
|
||||
|
||||
<!-- demo: wt-merge.gif 1600x900 -->
|
||||
<!-- demo: wt-merge.gif 1600x900 | Creating a worktree, committing in it, and merging it away -->
|
||||
## Examples
|
||||
|
||||
Merge to the default branch:
|
||||
|
||||
Reference in New Issue
Block a user