diff --git a/.config/lychee.toml b/.config/lychee.toml index 712275e22..0a49fcd69 100644 --- a/.config/lychee.toml +++ b/.config/lychee.toml @@ -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/", ] diff --git a/.gitattributes b/.gitattributes index 9cd43ecf3..1189cdeda 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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 diff --git a/CLAUDE.md b/CLAUDE.md index a72067596..c4aee5745 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/README.md b/README.md index 47087814a..4520253af 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/dev/config.example.toml b/dev/config.example.toml index 94d6e8c09..bb13b969f 100644 --- a/dev/config.example.toml +++ b/dev/config.example.toml @@ -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 `. 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. diff --git a/dev/wt.example.toml b/dev/wt.example.toml index 2b73802d2..744dc3356 100644 --- a/dev/wt.example.toml +++ b/dev/wt.example.toml @@ -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 `. 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. diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md index 6c07401bd..0088a062d 100644 --- a/docs/CLAUDE.md +++ b/docs/CLAUDE.md @@ -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. diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index cc079b07b..44506ba59 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -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', diff --git a/docs/public/.well-known/agent-skills/index.json b/docs/public/.well-known/agent-skills/index.json index 4842f820b..037aefb0c 100644 --- a/docs/public/.well-known/agent-skills/index.json +++ b/docs/public/.well-known/agent-skills/index.json @@ -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 ` 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" } ] } diff --git a/docs/public/llms.txt b/docs/public/llms.txt index 2754b4aaf..c8dc3aaa4 100644 --- a/docs/public/llms.txt +++ b/docs/public/llms.txt @@ -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. diff --git a/docs/public/shell-integration.md b/docs/public/shell-integration.md new file mode 120000 index 000000000..215f477d0 --- /dev/null +++ b/docs/public/shell-integration.md @@ -0,0 +1 @@ +../../skills/worktrunk/reference/shell-integration.md \ No newline at end of file diff --git a/docs/src/components/Footer.astro b/docs/src/components/Footer.astro new file mode 100644 index 000000000..f34cc90c9 --- /dev/null +++ b/docs/src/components/Footer.astro @@ -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'); +--- + + + + + + diff --git a/docs/src/content/docs/claude-code.md b/docs/src/content/docs/claude-code.md index 4d0451771..4a87911be 100644 --- a/docs/src/content/docs/claude-code.md +++ b/docs/src/content/docs/claude-code.md @@ -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 [] [] [-- ]` 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 Claude Code statusline demo +
Switching to a worktree, then launching Claude Code with the statusline configured
Add to `~/.claude/settings.json`: diff --git a/docs/src/content/docs/code-signing.md b/docs/src/content/docs/code-signing.md index e31e10a8b..08f0ff703 100644 --- a/docs/src/content/docs/code-signing.md +++ b/docs/src/content/docs/code-signing.md @@ -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. diff --git a/docs/src/content/docs/config.md b/docs/src/content/docs/config.md index 3b85274f2..d9cfc8a00 100644 --- a/docs/src/content/docs/config.md +++ b/docs/src/content/docs/config.md @@ -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 `. 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. # 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 `. 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. # Shell Integration @@ -789,24 +789,6 @@ Output: [default: text] [possible values: text, json] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 `. 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 Output format (text, json) [default: text] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 Output format (text, json) [default: text] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 Output format (text, json) [default: text] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 Output format (text, json) [default: text] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 ``` diff --git a/docs/src/content/docs/extending.md b/docs/src/content/docs/extending.md index 4d16da16f..0719c53e9 100644 --- a/docs/src/content/docs/extending.md +++ b/docs/src/content/docs/extending.md @@ -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" ``` diff --git a/docs/src/content/docs/faq.md b/docs/src/content/docs/faq.md index b4a616acb..e9e765d74 100644 --- a/docs/src/content/docs/faq.md +++ b/docs/src/content/docs/faq.md @@ -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 ` when switching to a branch that doesn't have a worktree. Use `wt switch --create ` to create a new branch. Default location is `../.` (sibling to main repo), configurable via `worktree-path` in user config. +Created by `wt switch ` when switching to a branch that doesn't have a worktree. Use `wt switch --create ` to create a new branch. Default location is `../.` (sibling to the main worktree), configurable via `worktree-path` in user config. **To remove:** `wt remove ` 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** : 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 `/vendor/autoload` (now `/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 `: 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. diff --git a/docs/src/content/docs/list.md b/docs/src/content/docs/list.md index 9501b49b2..224993677 100644 --- a/docs/src/content/docs/list.md +++ b/docs/src/content/docs/list.md @@ -15,6 +15,7 @@ Shows uncommitted changes, divergence from the default branch and remote, and op wt list demo +
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. @@ -623,24 +624,6 @@ Options: -h, --help Print help (see a summary with '-h') - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 ``` diff --git a/docs/src/content/docs/llm-commits.md b/docs/src/content/docs/llm-commits.md index a478fea67..b399cb11e 100644 --- a/docs/src/content/docs/llm-commits.md +++ b/docs/src/content/docs/llm-commits.md @@ -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 LLM commit message generation demo +
Reviewing the staged diff, generating a message, and committing
## 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: diff --git a/docs/src/content/docs/merge.md b/docs/src/content/docs/merge.md index 5ba802035..6ec75f9c3 100644 --- a/docs/src/content/docs/merge.md +++ b/docs/src/content/docs/merge.md @@ -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 --- @@ -15,6 +15,7 @@ Unlike `git merge`, this merges the current branch into the target branch — no wt merge demo +
Creating a worktree, committing in it, and merging it away
## Examples diff --git a/docs/src/content/docs/remove.md b/docs/src/content/docs/remove.md index 653d0d577..7b874a033 100644 --- a/docs/src/content/docs/remove.md +++ b/docs/src/content/docs/remove.md @@ -2,7 +2,7 @@ title: "wt remove" description: "Remove worktree; delete branch if merged. Defaults to the current worktree." sidebar: - order: 12 + order: 13 --- diff --git a/docs/src/content/docs/shell-integration.md b/docs/src/content/docs/shell-integration.md new file mode 100644 index 000000000..f0e3918c3 --- /dev/null +++ b/docs/src/content/docs/shell-integration.md @@ -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** : 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) diff --git a/docs/src/content/docs/step.md b/docs/src/content/docs/step.md index af2bbe93a..d7c1ff340 100644 --- a/docs/src/content/docs/step.md +++ b/docs/src/content/docs/step.md @@ -188,24 +188,6 @@ Automation: [default: text] [possible values: text, json] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 ``` diff --git a/docs/src/content/docs/switch.md b/docs/src/content/docs/switch.md index b96bc91f5..efcb3c6c7 100644 --- a/docs/src/content/docs/switch.md +++ b/docs/src/content/docs/switch.md @@ -15,6 +15,7 @@ Worktrees are addressed by branch name; paths are computed from a configurable t wt switch demo +
Switching to a worktree, creating one, and creating one that launches an agent
## Examples @@ -87,6 +88,7 @@ The CI column shows each row's PR/MR CI and review status, the same as [`wt list wt switch picker demo +
Browsing the picker, scrolling a preview, filtering, and selecting
**Keybindings:** diff --git a/docs/src/content/docs/tips-patterns.md b/docs/src/content/docs/tips-patterns.md index e0bd005a5..b7f6e7ed3 100644 --- a/docs/src/content/docs/tips-patterns.md +++ b/docs/src/content/docs/tips-patterns.md @@ -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 `/.git` puts all worktrees under one directory: + +```bash +git clone --bare 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 ` 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`: + + +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 - +## 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 ''`. -[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 `/.git` puts all worktrees under one directory: - -```bash -git clone --bare 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 ` 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. diff --git a/docs/src/content/docs/worktrunk.md b/docs/src/content/docs/worktrunk.md index 2319a8b73..d50b3ceb7 100644 --- a/docs/src/content/docs/worktrunk.md +++ b/docs/src/content/docs/worktrunk.md @@ -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:
- Worktrunk omnibus demo: multiple Claude agents in Zellij tabs with hooks, LLM commits, and merge workflow + Worktrunk omnibus demo: multiple Claude agents in Zellij tabs with hooks, LLM commits, and merge workflow
Multiple Claude agents in parallel with interactive picker, hooks, LLM commits, and merge
diff --git a/docs/src/plugins/worktrunk-terminal.mjs b/docs/src/plugins/worktrunk-terminal.mjs index ae5a5c5e0..94b1da77a 100644 --- a/docs/src/plugins/worktrunk-terminal.mjs +++ b/docs/src/plugins/worktrunk-terminal.mjs @@ -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; diff --git a/docs/src/site-navigation.mjs b/docs/src/site-navigation.mjs index e1dd79fda..aa4fdc9da 100644 --- a/docs/src/site-navigation.mjs +++ b/docs/src/site-navigation.mjs @@ -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/' }, ], }, diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css index 7f9e861e0..0778da5d7 100644 --- a/docs/src/styles/custom.css +++ b/docs/src/styles/custom.css @@ -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)); + } +} diff --git a/docs/tests/built-site.test.mjs b/docs/tests/built-site.test.mjs index 053bf60e2..aec684c98 100644 --- a/docs/tests/built-site.test.mjs +++ b/docs/tests/built-site.test.mjs @@ -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(/
([\s\S]*?)<\/figure>/g)) { const frame = match[1]; const lines = [...frame.matchAll( - /
([\s\S]*?)<\/div><\/div>/g, + /
([\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(/]*\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( + /
[\s\S]*?]*\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( + /
[\s\S]*?]*\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( + /
([\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 () => { diff --git a/plugins/worktrunk/CLAUDE.md b/plugins/worktrunk/CLAUDE.md index 8b3c8371d..cea1180e5 100644 --- a/plugins/worktrunk/CLAUDE.md +++ b/plugins/worktrunk/CLAUDE.md @@ -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 diff --git a/plugins/worktrunk/README.md b/plugins/worktrunk/README.md index 10750f012..048de3364 100644 --- a/plugins/worktrunk/README.md +++ b/plugins/worktrunk/README.md @@ -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 (`.fix-auth/`), switches the session into it, and starts the task there. The branch name is optional (`/wt-switch-create -- `). 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 (`.fix-auth/`), switches the session into it, and starts the task there. The branch name is optional (`/wt-switch-create -- `). The worktree persists after the session — merge or remove it with `wt merge` / `wt remove` like any other. diff --git a/plugins/worktrunk/hooks/wt.sh b/plugins/worktrunk/hooks/wt.sh index 0d1cc3ed3..3be4697d4 100755 --- a/plugins/worktrunk/hooks/wt.sh +++ b/plugins/worktrunk/hooks/wt.sh @@ -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 diff --git a/plugins/worktrunk/skills/worktrunk/SKILL.md b/plugins/worktrunk/skills/worktrunk/SKILL.md index ad7705c98..cac788b99 100644 --- a/plugins/worktrunk/skills/worktrunk/SKILL.md +++ b/plugins/worktrunk/skills/worktrunk/SKILL.md @@ -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 `` 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 "wt switch --create -x -- ''" -``` - -**Zellij** (check `$ZELLIJ` env var): -```bash -zellij run -- wt switch --create -x -- '' -``` +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 --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.` on branch ``. +You are working in `/abs/path/to/myproject.` on 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-` 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-` 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. diff --git a/plugins/worktrunk/skills/worktrunk/reference/README.md b/plugins/worktrunk/skills/worktrunk/reference/README.md deleted file mode 100644 index 47087814a..000000000 --- a/plugins/worktrunk/skills/worktrunk/reference/README.md +++ /dev/null @@ -1,262 +0,0 @@ - - -

Worktrunk logo  Worktrunk

- - -[![Docs](https://img.shields.io/badge/docs-worktrunk.dev-blue?style=for-the-badge&logo=gitbook)](https://worktrunk.dev) -[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue?style=for-the-badge)](https://opensource.org/licenses/MIT) -[![CI](https://img.shields.io/github/actions/workflow/status/max-sixty/worktrunk/ci.yaml?event=push&branch=main&style=for-the-badge&logo=github)](https://github.com/max-sixty/worktrunk/actions?query=branch%3Amain+workflow%3Aci) -[![Codecov](https://img.shields.io/codecov/c/github/max-sixty/worktrunk?style=for-the-badge&logo=codecov)](https://codecov.io/gh/max-sixty/worktrunk) -[![Stars](https://img.shields.io/github/stars/max-sixty/worktrunk?style=for-the-badge&logo=github)](https://github.com/max-sixty/worktrunk/stargazers) -[![maintained with tend](https://img.shields.io/badge/maintained_with-tend-bba580?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwxNikgc2NhbGUoMC4wMTI1LC0wLjAxMjUpIiBmaWxsPSIjZmZmIiBzdHJva2U9Im5vbmUiPjxwYXRoIGQ9Ik02ODAgMTEyOCBjNjIgLTk2IDY5IC0xNzggMjAgLTI0MSAtMTcgLTIyIC0yMCAtNDAgLTIwIC0xMzQgbDEgLTEwOCAyMSAyOCBjMTEgMTYgMzAgNDcgNDIgNzAgMTIgMjIgMzIgNDkgNDYgNTkgMzcgMjcgMTE0IDM4IDE4NCAyNyA5MyAtMTUgOTQgLTE4IDQ0IC03OSAtNzIgLTg4IC0xMDkgLTExMyAtMTc2IC0xMTcgLTMxIC0yIC02NCAxIC03MiA2IC0yMyAxNSAyMSA1NiAxMDcgOTggNDAgMjAgNzEgMzggNjkgNDAgLTYgNyAtODggLTE3IC0xMjYgLTM3IC00OSAtMjUgLTEwMCAtNzggLTEyMSAtMTI1IC0xNSAtMzMgLTE5IC02NiAtMTkgLTE4OCAwIC0xNTcgOCAtMTk1IDUwIC0yMzIgMTcgLTE2IDM2IC0yMCA4NSAtMTkgNjIgMSA2MyAxIDczIC0zMiA5IC0zMiA5IC0zMyAtMjIgLTQwIC01MCAtMTIgLTEzMiAtNyAtMTY0IDEwIC00MCAyMSAtNzkgNjkgLTkyIDExNCAtNSAyMCAtMTAgMTAyIC0xMCAxODIgMCA4MCAtNSAxNjIgLTExIDE4NCAtMjIgNzkgLTEzNSAxNjYgLTIzNCAxODEgLTM3IDYgLTM1IDMgMzAgLTI4IDc4IC0zOSAxNDQgLTkxIDEzMiAtMTA0IC01IC00IC0zNyAtOCAtNzEgLTggLTc3IDAgLTExNyAyNCAtMTgyIDEwOSAtNTIgNjggLTUxIDcwIDQyIDg1IDcxIDExIDE0MyAwIDE4MyAtMjkgMTYgLTExIDQwIC00MyA1NCAtNzMgMTMgLTI5IDMyIC01OSA0MSAtNjYgMTQgLTEyIDE2IC03IDE2IDU4IDAgNTkgNCA3NyAyMyAxMDIgMTkgMjYgMjMgNDYgMjUgMTMwIDMgNjcgMCA5OSAtNyA5OSAtNyAwIC0xMSAtMjMgLTEyIC01NyAwIC0zMiAtNiAtNzYgLTEyIC05NyBsLTEyIC00MCAtMjcgMzIgYy0zNCA0MSAtNDMgOTYgLTI0IDE1MSAxNCA0MSA3NSAxNDEgODYgMTQxIDMgMCAyMSAtMjQgNDAgLTUyeiIvPjwvZz48L3N2Zz4K)](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: - -![Worktrunk Demo](https://cdn.jsdelivr.net/gh/max-sixty/worktrunk-assets@main/assets/docs/light/wt-core.gif) - -> ### 📚 Full documentation at [worktrunk.dev](https://worktrunk.dev) 📚 - - - -## 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. - -

Start with the core commands

- -**Core commands:** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TaskWorktrunkPlain git
Switch worktreeswt switch featcd ../repo.feat
Create + start Claudewt switch -c -x claude feat
git worktree add -b feat ../repo.feat && \
-cd ../repo.feat && \
-claude
Clean upwt remove
cd ../repo && \
-git worktree remove ../repo.feat && \
-git branch -d feat
List with statuswt listgit worktree list (paths only)
- -

Expand into the more advanced commands as needed

- -

Workflow automation:

- -- **[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 ` commands and branch-scoped state for hook templates -- ...and **[lots more](#next-steps)** - -Multiple parallel agents, same simple commands: - -![Worktrunk omnibus demo: multiple Claude agents in Zellij tabs with hooks, LLM commits, and merge workflow](https://raw.githubusercontent.com/max-sixty/worktrunk-assets/main/assets/docs/light/wt-zellij-omnibus.gif) - - - - - -## 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 -``` - -
-Windows & other - -**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`. - -
- -## 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 --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 - - - -## 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 - - - - Star History Chart - diff --git a/plugins/worktrunk/skills/worktrunk/reference/claude-code.md b/plugins/worktrunk/skills/worktrunk/reference/claude-code.md index 964eeb4ed..bf30e02fe 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/claude-code.md +++ b/plugins/worktrunk/skills/worktrunk/reference/claude-code.md @@ -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 [] [] [-- ]` 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`. diff --git a/plugins/worktrunk/skills/worktrunk/reference/config.md b/plugins/worktrunk/skills/worktrunk/reference/config.md index 3c570027f..4ea1746bf 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/config.md +++ b/plugins/worktrunk/skills/worktrunk/reference/config.md @@ -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 `. 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. # 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 `. 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. # Shell Integration @@ -781,24 +781,6 @@ Output: [default: text] [possible values: text, json] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 `. 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 Output format (text, json) [default: text] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 Output format (text, json) [default: text] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 Output format (text, json) [default: text] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 Output format (text, json) [default: text] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 ``` diff --git a/plugins/worktrunk/skills/worktrunk/reference/extending.md b/plugins/worktrunk/skills/worktrunk/reference/extending.md index e07d479a5..bef10ddf2 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/extending.md +++ b/plugins/worktrunk/skills/worktrunk/reference/extending.md @@ -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" ``` diff --git a/plugins/worktrunk/skills/worktrunk/reference/faq.md b/plugins/worktrunk/skills/worktrunk/reference/faq.md index 3b668bd0d..38d7dbfd5 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/faq.md +++ b/plugins/worktrunk/skills/worktrunk/reference/faq.md @@ -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 ` when switching to a branch that doesn't have a worktree. Use `wt switch --create ` to create a new branch. Default location is `../.` (sibling to main repo), configurable via `worktree-path` in user config. +Created by `wt switch ` when switching to a branch that doesn't have a worktree. Use `wt switch --create ` to create a new branch. Default location is `../.` (sibling to the main worktree), configurable via `worktree-path` in user config. **To remove:** `wt remove ` 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 `/vendor/autoload` (now `/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 `: 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. diff --git a/plugins/worktrunk/skills/worktrunk/reference/list.md b/plugins/worktrunk/skills/worktrunk/reference/list.md index 63dd975ae..6e7c8a403 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/list.md +++ b/plugins/worktrunk/skills/worktrunk/reference/list.md @@ -604,22 +604,4 @@ Options: -h, --help Print help (see a summary with '-h') - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 ``` diff --git a/plugins/worktrunk/skills/worktrunk/reference/llm-commits.md b/plugins/worktrunk/skills/worktrunk/reference/llm-commits.md index ae777e127..cc451bb17 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/llm-commits.md +++ b/plugins/worktrunk/skills/worktrunk/reference/llm-commits.md @@ -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: diff --git a/plugins/worktrunk/skills/worktrunk/reference/shell-integration.md b/plugins/worktrunk/skills/worktrunk/reference/shell-integration.md index 1f4600db1..7c72d3890 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/shell-integration.md +++ b/plugins/worktrunk/skills/worktrunk/reference/shell-integration.md @@ -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 diff --git a/plugins/worktrunk/skills/worktrunk/reference/step.md b/plugins/worktrunk/skills/worktrunk/reference/step.md index 2e4e128f5..1faee0d55 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/step.md +++ b/plugins/worktrunk/skills/worktrunk/reference/step.md @@ -176,24 +176,6 @@ Automation: [default: text] [possible values: text, json] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 ``` diff --git a/plugins/worktrunk/skills/worktrunk/reference/tips-patterns.md b/plugins/worktrunk/skills/worktrunk/reference/tips-patterns.md index 54af9b20f..67b11c953 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/tips-patterns.md +++ b/plugins/worktrunk/skills/worktrunk/reference/tips-patterns.md @@ -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 `/.git` puts all worktrees under one directory: + +```bash +git clone --bare 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 ` 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`: + + +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 - +## 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 ''`. -[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 `/.git` puts all worktrees under one directory: - -```bash -git clone --bare 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 ` 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. diff --git a/plugins/worktrunk/skills/worktrunk/reference/troubleshooting.md b/plugins/worktrunk/skills/worktrunk/reference/troubleshooting.md index 7c4c8e4c5..82527b3ec 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/troubleshooting.md +++ b/plugins/worktrunk/skills/worktrunk/reference/troubleshooting.md @@ -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 ` 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: : working-tree-diff, working-tree-conflicts ``` diff --git a/plugins/worktrunk/skills/worktrunk/reference/worktrunk.md b/plugins/worktrunk/skills/worktrunk/reference/worktrunk.md index 5bc616028..c23836451 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/worktrunk.md +++ b/plugins/worktrunk/skills/worktrunk/reference/worktrunk.md @@ -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 diff --git a/skills/worktrunk/SKILL.md b/skills/worktrunk/SKILL.md index ad7705c98..cac788b99 100644 --- a/skills/worktrunk/SKILL.md +++ b/skills/worktrunk/SKILL.md @@ -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 `` 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 "wt switch --create -x -- ''" -``` - -**Zellij** (check `$ZELLIJ` env var): -```bash -zellij run -- wt switch --create -x -- '' -``` +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 --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.` on branch ``. +You are working in `/abs/path/to/myproject.` on 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-` 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-` 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. diff --git a/skills/worktrunk/reference/README.md b/skills/worktrunk/reference/README.md deleted file mode 120000 index 8a33348c7..000000000 --- a/skills/worktrunk/reference/README.md +++ /dev/null @@ -1 +0,0 @@ -../../../README.md \ No newline at end of file diff --git a/skills/worktrunk/reference/claude-code.md b/skills/worktrunk/reference/claude-code.md index 964eeb4ed..bf30e02fe 100644 --- a/skills/worktrunk/reference/claude-code.md +++ b/skills/worktrunk/reference/claude-code.md @@ -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 [] [] [-- ]` 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`. diff --git a/skills/worktrunk/reference/config.md b/skills/worktrunk/reference/config.md index 3c570027f..4ea1746bf 100644 --- a/skills/worktrunk/reference/config.md +++ b/skills/worktrunk/reference/config.md @@ -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 `. 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. # 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 `. 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. # Shell Integration @@ -781,24 +781,6 @@ Output: [default: text] [possible values: text, json] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 `. 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 Output format (text, json) [default: text] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 Output format (text, json) [default: text] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 Output format (text, json) [default: text] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 Output format (text, json) [default: text] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 ``` diff --git a/skills/worktrunk/reference/extending.md b/skills/worktrunk/reference/extending.md index e07d479a5..bef10ddf2 100644 --- a/skills/worktrunk/reference/extending.md +++ b/skills/worktrunk/reference/extending.md @@ -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" ``` diff --git a/skills/worktrunk/reference/faq.md b/skills/worktrunk/reference/faq.md index 3b668bd0d..38d7dbfd5 100644 --- a/skills/worktrunk/reference/faq.md +++ b/skills/worktrunk/reference/faq.md @@ -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 ` when switching to a branch that doesn't have a worktree. Use `wt switch --create ` to create a new branch. Default location is `../.` (sibling to main repo), configurable via `worktree-path` in user config. +Created by `wt switch ` when switching to a branch that doesn't have a worktree. Use `wt switch --create ` to create a new branch. Default location is `../.` (sibling to the main worktree), configurable via `worktree-path` in user config. **To remove:** `wt remove ` 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 `/vendor/autoload` (now `/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 `: 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. diff --git a/skills/worktrunk/reference/list.md b/skills/worktrunk/reference/list.md index 63dd975ae..6e7c8a403 100644 --- a/skills/worktrunk/reference/list.md +++ b/skills/worktrunk/reference/list.md @@ -604,22 +604,4 @@ Options: -h, --help Print help (see a summary with '-h') - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 ``` diff --git a/skills/worktrunk/reference/llm-commits.md b/skills/worktrunk/reference/llm-commits.md index ae777e127..cc451bb17 100644 --- a/skills/worktrunk/reference/llm-commits.md +++ b/skills/worktrunk/reference/llm-commits.md @@ -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: diff --git a/skills/worktrunk/reference/shell-integration.md b/skills/worktrunk/reference/shell-integration.md index 1f4600db1..7c72d3890 100644 --- a/skills/worktrunk/reference/shell-integration.md +++ b/skills/worktrunk/reference/shell-integration.md @@ -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 diff --git a/skills/worktrunk/reference/step.md b/skills/worktrunk/reference/step.md index 2e4e128f5..1faee0d55 100644 --- a/skills/worktrunk/reference/step.md +++ b/skills/worktrunk/reference/step.md @@ -176,24 +176,6 @@ Automation: [default: text] [possible values: text, json] - -Global Options: - -C - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 - Working directory for this command - - --config - User config file path - - --config-set - 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 ``` diff --git a/skills/worktrunk/reference/tips-patterns.md b/skills/worktrunk/reference/tips-patterns.md index 54af9b20f..67b11c953 100644 --- a/skills/worktrunk/reference/tips-patterns.md +++ b/skills/worktrunk/reference/tips-patterns.md @@ -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 `/.git` puts all worktrees under one directory: + +```bash +git clone --bare 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 ` 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`: + + +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 - +## 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 ''`. -[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 `/.git` puts all worktrees under one directory: - -```bash -git clone --bare 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 ` 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. diff --git a/skills/worktrunk/reference/troubleshooting.md b/skills/worktrunk/reference/troubleshooting.md index 7c4c8e4c5..82527b3ec 100644 --- a/skills/worktrunk/reference/troubleshooting.md +++ b/skills/worktrunk/reference/troubleshooting.md @@ -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 ` 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: : working-tree-diff, working-tree-conflicts ``` diff --git a/skills/worktrunk/reference/worktrunk.md b/skills/worktrunk/reference/worktrunk.md index 5bc616028..c23836451 100644 --- a/skills/worktrunk/reference/worktrunk.md +++ b/skills/worktrunk/reference/worktrunk.md @@ -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 diff --git a/src/cli/config.rs b/src/cli/config.rs index d94de2a38..3021ecc9c 100644 --- a/src/cli/config.rs +++ b/src/cli/config.rs @@ -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 `. 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 diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 6473c3a2c..f909d5a26 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -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. - + ## 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/). - + **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. - + 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. - + ## 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 `. 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. # 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 `. 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. # Shell Integration diff --git a/src/cli/step.rs b/src/cli/step.rs index 1844d2321..8bb961036 100644 --- a/src/cli/step.rs +++ b/src/cli/step.rs @@ -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 diff --git a/src/help.rs b/src/help.rs index 799f8dce9..3f1fe15e5 100644 --- a/src/help.rs +++ b/src/help.rs @@ -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!( "", @@ -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: `` +/// Supports optional dimensions and a caption, in that order: +/// ``. The +/// caption becomes the figure's `
`, 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
{caption}
") + }; + // Use figure.demo class for proper mobile styling (no shrink, horizontal scroll) // Generate 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!( - "
\n\n \n \"{alt_text}\n\n
\n" + "
\n\n \n \"{alt_text}\n{figcaption}\n
\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 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 `
` rather than an empty element. + #[test] + fn demo_placeholder_without_a_caption_renders_no_figcaption() { + let expanded = expand_demo_placeholders(""); + + assert!( + !expanded.contains("figcaption"), + "no caption means no element: {expanded}" + ); + assert!(expanded.contains("width=\"1600\" height=\"900\"")); + } + + /// A caption after `|` becomes the figure's `
`. + #[test] + fn demo_placeholder_caption_becomes_a_figcaption() { + let expanded = + expand_demo_placeholders(""); + + assert!( + expanded.contains("
Switching worktrees
"), + "{expanded}" + ); + } +} diff --git a/tests/integration_tests/readme_sync.rs b/tests/integration_tests/readme_sync.rs index 82b0a9296..8c514745d 100644 --- a/tests/integration_tests/readme_sync.rs +++ b/tests/integration_tests/readme_sync.rs @@ -123,14 +123,27 @@ static RUST_RAW_STRING_PATTERN: LazyLock = 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 = - LazyLock::new(|| Regex::new(r"\[((?:`[^`]*`|[^\]`])+)\]\(/([^)/]+)/(#[^)]*)?\)").unwrap()); +static SITE_LINK_PATTERN: LazyLock = 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 = @@ -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 { 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, Vec) { @@ -2651,6 +2656,23 @@ fn sync_llms_txt(project_root: &Path) -> (Vec, Vec) { let out = format!("{}\n", out.trim_end()); + // Every page llms.txt names is served as `.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 = 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. diff --git a/tests/snapshots/integration__integration_tests__help__help_config_approvals.snap b/tests/snapshots/integration__integration_tests__help__help_config_approvals.snap index aa57b17fd..01adbcfda 100644 --- a/tests/snapshots/integration__integration_tests__help__help_config_approvals.snap +++ b/tests/snapshots/integration__integration_tests__help__help_config_approvals.snap @@ -74,7 +74,7 @@ Usage: wt config approvals [OPTIONS]  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:   wt config approvals list diff --git a/tests/snapshots/integration__integration_tests__help__help_config_create.snap b/tests/snapshots/integration__integration_tests__help__help_config_create.snap index 2e408d850..a9a8df4df 100644 --- a/tests/snapshots/integration__integration_tests__help__help_config_create.snap +++ b/tests/snapshots/integration__integration_tests__help__help_config_create.snap @@ -297,7 +297,7 @@ Creates ~/.config/worktrunk/config.toml with the following content:   #   # 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 `. See the Extending Worktrunk guide (https://worktrunk.dev/extending/#aliases) for usage and flags.   # @@ -305,7 +305,7 @@ Creates ~/.config/worktrunk/config.toml with the following content:   # 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   # @@ -488,9 +488,9 @@ Creates ~/.config/worktrunk/config.toml with the following content:   #   # 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. Project config @@ -502,7 +502,7 @@ With --project, creates .config/wt.toml in the current repositor   #   # 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.   # @@ -549,7 +549,7 @@ With --project, creates .config/wt.toml in the current repositor   #   # 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 `. See the Extending Worktrunk guide (https://worktrunk.dev/extending/#aliases) for usage and flags.   # @@ -557,6 +557,6 @@ With --project, creates .config/wt.toml in the current repositor   # 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. ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__help__help_config_long.snap b/tests/snapshots/integration__integration_tests__help__help_config_long.snap index ba57884e6..222feaacb 100644 --- a/tests/snapshots/integration__integration_tests__help__help_config_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_config_long.snap @@ -345,7 +345,7 @@ Persistent flag values for wt remove. Override on command line as needed Built-in excludes (VCS metadata and tool-state directories) always apply; the wt step copy-ignored docs list them. User config and project config exclusions are combined. -Aliases +User aliases Command templates that run as wt . 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. -Hooks +User hooks See wt hook for hook types, execution order, template variables, and examples. User hooks apply to all projects; project hooks apply only to that repository. PROJECT CONFIGURATION @@ -541,7 +541,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 for hook types, execution order, and examples. @@ -587,7 +587,7 @@ Additional excludes for wt step copy-ignored: Built-in excludes (VCS metadata and tool-state directories) always apply; the wt step copy-ignored docs list them. User config and project config exclusions are combined. -Aliases +Project aliases Command templates that run as wt . See the Extending Worktrunk guide for usage and flags. diff --git a/tests/snapshots/integration__integration_tests__help__help_config_state.snap b/tests/snapshots/integration__integration_tests__help__help_config_state.snap index 481553df7..47c5dddeb 100644 --- a/tests/snapshots/integration__integration_tests__help__help_config_state.snap +++ b/tests/snapshots/integration__integration_tests__help__help_config_state.snap @@ -86,7 +86,7 @@ State is stored in .git/ (config entries and log files), separate from c - vars: Custom variables per branch - logs: Operation and debug logs -Examples +State examples Get the default branch:   wt config state default-branch diff --git a/tests/snapshots/integration__integration_tests__help__help_config_state_cache.snap b/tests/snapshots/integration__integration_tests__help__help_config_state_cache.snap index 2b416189c..11b711e33 100644 --- a/tests/snapshots/integration__integration_tests__help__help_config_state_cache.snap +++ b/tests/snapshots/integration__integration_tests__help__help_config_state_cache.snap @@ -90,7 +90,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:   wt config state cache diff --git a/tests/snapshots/integration__integration_tests__help__help_config_state_logs.snap b/tests/snapshots/integration__integration_tests__help__help_config_state_logs.snap index 4ba55b992..ba41bc828 100644 --- a/tests/snapshots/integration__integration_tests__help__help_config_state_logs.snap +++ b/tests/snapshots/integration__integration_tests__help__help_config_state_logs.snap @@ -129,7 +129,7 @@ All logs are stored in .git/wt/logs/ (in the main worktree's git directo 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:   wt config state logs diff --git a/tests/snapshots/integration__integration_tests__help__help_md_merge.snap b/tests/snapshots/integration__integration_tests__help__help_md_merge.snap index 96d1c9f0e..86ed51091 100644 --- a/tests/snapshots/integration__integration_tests__help__help_md_merge.snap +++ b/tests/snapshots/integration__integration_tests__help__help_md_merge.snap @@ -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. - + ## Examples Merge to the default branch: