diff --git a/docs/tools/README.md b/docs/tools/README.md index d5fd92c..bd149c5 100644 --- a/docs/tools/README.md +++ b/docs/tools/README.md @@ -50,7 +50,7 @@ These packages are declared in `.chezmoidata/packages.yaml` but have no config f | `wget` | HTTP downloads | Not referenced by any config. | | `zsh-syntax-highlighting` | zsh highlighting | Sourced by the zsh setup. See [Shell](../shell/README.md). | | `ttf-victor-mono-nerd` / `font-victor-mono-nerd-font` | Nerd Font | Terminal font. See [Terminal](../terminal/README.md). | -| `suvadu`, `mandible`, `worktrunk` | Declared under `packages.shared.cargo` | Installed by [`15-tools`](../chezmoi/scripts.md#15-tools), which installs rustup first when `cargo` is missing. `mandible` backs the tmux-palette "mandible" item via `~/.local/bin/mandible-pick`. `worktrunk` is the `wt` git-worktree CLI for parallel agent workflows. | +| `suvadu`, `mandible`, `worktrunk` | Declared under `packages.shared.cargo` | Installed by [`15-tools`](../chezmoi/scripts.md#15-tools), which installs rustup first when `cargo` is missing. `mandible` backs the tmux-palette "mandible" item via `~/.local/bin/mandible-pick`. `worktrunk` is the `wt` git-worktree CLI for parallel agent workflows; `dot_config/worktrunk/private_config.toml` sets `worktree-path = "~/Work/{{ repo }}-{{ branch \| sanitize }}"`, so worktrees of ghq clones in `~/Code` land in `~/Work/-`. [lazygit](lazygit.md)'s worktree keys call it. | Tools with their own pages in other sections: [fish](../shell/fish.md), [starship](../shell/starship.md), and [atuin](../shell/atuin.md) under Shell; [kitty](../terminal/kitty.md) and [tmux](../terminal/tmux.md) under Terminal. diff --git a/docs/tools/lazygit.md b/docs/tools/lazygit.md index 82e1990..2fcfea5 100644 --- a/docs/tools/lazygit.md +++ b/docs/tools/lazygit.md @@ -132,6 +132,10 @@ Colors come from the CherryBlossom palette, shared with Ghostty, tmux, and omp: | o | localBranches | `gh pr create --fill -w` ("start ai pull request") | | | C | localBranches | `gh repo sync && gh clean-branches --force` | log | | O | commits | `hub browse -- "commit/{{.SelectedLocalCommit.Hash}}"` | | +| n | worktrees | Prompts for a branch name, then `wt switch --create --no-cd` ("New branch + worktree in ~/Work") | terminal | +| w | localBranches | `wt switch --no-cd` ("Worktree for branch in ~/Work") | terminal | + +Worktrees go to `~/Work/-`, from worktrunk's `worktree-path` (`dot_config/worktrunk/private_config.toml`). Clones stay under ghq's `~/Code`. lazygit's own worktree flow always names the directory after the branch alone, so the two worktree keys above replace the built-in ones and hand off to `wt`. `worktree.defaultPath: ~/Work` makes the built-in menus that remain, such as detached worktrees from commits and tags, offer `~/Work` as the parent directory. `ghostty-tmux` puts `~/.cargo/bin` on the tmux server's PATH so `wt` resolves in the `prefix g` popup. `gh commit`, `gh clean-branches`, and `gh dash` are managed gh extensions (see [GitHub CLI](github-cli.md)). `gitv`, `gwm`, `wrkflw`, `github`, `livediff`, and `hub` are not installed by this repo. diff --git a/dot_config/lazygit/config.yml b/dot_config/lazygit/config.yml index 6bfbd26..f46632c 100644 --- a/dot_config/lazygit/config.yml +++ b/dot_config/lazygit/config.yml @@ -437,7 +437,29 @@ keybinding: bulkMenu: b commitMessage: commitMenu: +# Worktrees live in ~/Work/- (worktrunk's worktree-path, see +# ~/.config/worktrunk/config.toml); clones stay under ghq's ~/Code. lazygit's +# own flow always names the directory after the branch alone, so the two keys +# that create worktrees hand off to `wt switch` instead (custom commands win +# over built-ins in the same context). The built-in menus left elsewhere +# (detached worktrees from commits/tags) still offer ~/Work as the parent. +worktree: + defaultPath: ~/Work customCommands: + - key: "n" + context: "worktrees" + description: "New branch + worktree in ~/Work (wt)" + prompts: + - type: "input" + title: "New branch (worktree: ~/Work/-)" + key: "Branch" + command: "wt switch --create {{.Form.Branch | quote}} --no-cd" + output: terminal + - key: "w" + context: "localBranches" + description: "Worktree for branch in ~/Work (wt)" + command: "wt switch {{.SelectedLocalBranch.Name | quote}} --no-cd" + output: terminal - key: "" command: "gitv {{.Repository.Owner}}/{{.Repository.Name}}" context: "global" diff --git a/dot_config/worktrunk/private_config.toml b/dot_config/worktrunk/private_config.toml new file mode 100644 index 0000000..0821835 --- /dev/null +++ b/dot_config/worktrunk/private_config.toml @@ -0,0 +1,421 @@ +# Repositories are cloned by ghq under ~/Code/// (GHQ_ROOT); +# their worktrees go to ~/Work/-, e.g. ~/Work/omarchy-feature-auth. +# lazygit's worktree keys call `wt switch`, so they land here too. +worktree-path = "~/Work/{{ repo }}-{{ branch | sanitize }}" + +# # User Configuration +# +# Create with `wt config create`. Values shown are defaults unless noted otherwise. +# +# Location: +# +# - macOS/Linux: `~/.config/worktrunk/config.toml` (or `$XDG_CONFIG_HOME` if set) +# - Windows: `%APPDATA%\worktrunk\config.toml` +# +# ## Worktree path template +# +# Controls where new worktrees are created. +# +# **Available template variables:** +# +# - `{{ repo_path }}` — absolute path to the repository root (e.g., `/Users/me/code/myproject`. Or for bare repos, the bare directory itself) +# - `{{ repo }}` — repository directory name (e.g., `myproject`) +# - `{{ owner }}` — primary remote owner path (may include subgroups like `group/subgroup`) +# - `{{ remote_repo }}` — repository name in the primary remote URL, without `.git` (e.g., `myproject`); differs from `{{ repo }}`, the directory on disk, when a clone was renamed +# - `{{ branch }}` — raw branch name (e.g., `feature/auth`) +# - `{{ branch | sanitize }}` — filesystem-safe: `/` and `\` become `-` (e.g., `feature-auth`) +# - `{{ branch | sanitize_db }}` — database-safe: lowercase, underscores, hash suffix (e.g., `feature_auth_x7k`) +# - `{{ branch | codename(2) }}` — deterministic friendly name from a ~1.26M-combo pool (e.g., `malleable-opah`) +# +# This is a smaller set than the variables hooks and aliases get (https://worktrunk.dev/hook/#template-variables). +# +# **Examples** for repo at `~/code/myproject`, branch `feature/auth`: +# +# Default — sibling directory (`~/code/myproject.feature-auth`): +# +# worktree-path = "{{ repo_path }}/../{{ repo }}.{{ branch | sanitize }}" +# +# Inside the repository (`~/code/myproject/.worktrees/feature-auth`): +# +# worktree-path = "{{ repo_path }}/.worktrees/{{ branch | sanitize }}" +# +# Friendly branch-derived names (`~/code/myproject.malleable-opah`): +# +# worktree-path = "{{ repo_path }}/../{{ repo }}.{{ branch | codename(2) }}" +# +# Friendly names with branch identity in a parent directory (`~/code/worktrees/feature-auth/malleable-opah`): +# +# worktree-path = "{{ repo_path }}/../worktrees/{{ branch | sanitize }}/{{ branch | codename(2) }}" +# +# Centralized worktrees directory (`~/worktrees/myproject/feature-auth`): +# +# worktree-path = "~/worktrees/{{ repo }}/{{ branch | sanitize }}" +# +# By remote owner path (`~/development/max-sixty/myproject/feature/auth`): +# +# worktree-path = "~/development/{{ owner }}/{{ repo }}/{{ branch }}" +# +# Bare repository cloned to `~/code/myproject/.git` (`~/code/myproject/feature-auth`): +# +# worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}" +# +# `~` expands to the home directory. Relative paths resolve from `repo_path`. +# +# ## LLM commit messages +# +# Generate commit messages automatically during merge. Requires an external CLI tool. +# +# ### Claude Code +# +# [commit.generation] +# command = "MAX_THINKING_TOKENS=0 claude -p --no-session-persistence --model=haiku --tools='' --safe-mode --setting-sources='user' --system-prompt=''" +# +# ### Codex +# +# Create `~/.codex/worktrunk-commit-instructions.txt` containing just `.` (no newline). Accepting Worktrunk's first-run Codex setup creates the file for you. +# +# [commit.generation] +# command = "codex exec -m gpt-6-luna -c model_reasoning_effort='none' -c project_doc_max_bytes=0 -c skills.max_context_tokens=1 -c agents.enabled=false -c features.goals=false -c web_search=disabled -c 'model_instructions_file=\"~/.codex/worktrunk-commit-instructions.txt\"' -c features.shell_tool=false -c features.unified_exec=false -c features.apps=false -c features.plugins=false --ephemeral --sandbox=read-only --json - | jq -sr '[.[] | select(.item.type? == \"agent_message\")] | last.item.text'" +# +# ### OpenCode +# +# [commit.generation] +# command = "opencode run -m anthropic/claude-haiku-5-5 --variant fast" +# +# ### llm +# +# [commit.generation] +# command = "llm -m claude-haiku-4.5" +# +# ### aichat +# +# [commit.generation] +# command = "aichat -m claude:claude-haiku-4.5 --code" +# +# See LLM commits docs (https://worktrunk.dev/llm-commits/) for setup and Custom prompt templates (#custom-prompt-templates) for template customization. +# +# ## Command config +# +# ### List +# +# Persistent flag values for `wt list`. Override on command line as needed. +# +# [list] +# summary = false # Enable LLM branch summaries (requires [commit.generation]) +# +# full = false # Show CI status and LLM summaries (--full) +# branches = false # Include branches without worktrees (--branches) +# remotes = false # Include remote-only branches (--remotes) +# +# json-schema = 2 # JSON output schema: 2 (envelope, default) or 1 (bare array) +# +# columns = ["branch", "status", "ci", "path"] # Columns to show, in order — built-ins or custom headers (omit for the default set) +# +# timeout-ms = 0 # Wall-clock budget for the entire collect phase; 0 disables +# +# `columns` selects and orders the columns the `wt list` table and the `wt switch` +# picker render; `--format json` ignores it and always emits every field. Omit it +# for the default set. It is meant to drive a per-invocation +# alias (https://worktrunk.dev/extending/#aliases) (`wt --config-set 'list.columns=[…]' list`), +# giving a named view without disturbing the default `wt list`. A static setting +# works but pins one layout over a table that otherwise adapts to `--full` and +# terminal width. +# +# Valid built-in names: +# +# - `branch` — The branch name +# - `status` — Git status symbols, plus any user-defined status +# - `working-diff` — Uncommitted line changes against `HEAD`, including untracked files (header `HEAD±`) +# - `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`) +# - `branch-diff` — Line changes against the default branch (header `main…±`) +# - `summary` — An LLM-generated summary of the branch +# - `upstream` — Commits ahead of and behind the upstream tracking branch (header `Remote⇅`) +# - `ci` — CI status of the head commit +# - `path` — The worktree's path +# - `url` — Dev-server URL from the `[list] url` template +# - `commit` — The head commit's short hash +# - `age` — Time since the last commit +# - `message` — The head commit's subject +# +# A selection mixes built-ins with custom columns (#custom-columns), each named +# by its `[list.custom-columns]` header (`columns = ["branch", "Ticket", "ci"]`), +# and is exhaustive: only the listed columns render. Omit `columns` to keep the +# default set, where custom columns append automatically. +# +# Listing a column forces it on, space permitting: `ci` shows without `--full`. A +# column whose data source is missing still stays hidden — `summary` needs an LLM +# command (`[commit.generation]`), `url` needs a `[list] url` template. +# +# #### Custom columns [experimental] +# +# Custom columns add per-branch context to the `wt list` table. Each +# `[list.custom-columns]` entry is a column: the key is the header, the template +# renders each row's cell. +# +# [list.custom-columns.Ticket] +# template = "{{ vars.ticket }}" # Required; the result is the cell text +# width = 20 # Optional max display width (default: 40) +# priority = 9 # Optional drop order when the terminal narrows; +# # lower = kept longer (default: 9, the URL band) +# +# Templates may reference `{{ branch }}`, `{{ worktree_path }}`, +# `{{ worktree_name }}` (empty for branch-only rows), and two per-branch +# namespaces: +# +# - `{{ vars.* }}` — values stored with +# `wt config state vars set` (https://worktrunk.dev/config/#wt-config-state-vars). +# - `{{ git.branch.* }}` — the branch's own git config under `branch..*`, +# read straight from `git config` (e.g. `{{ git.branch.jira }}` for a key you +# set yourself, or the git-native `description`). Git lowercases config variable +# names, so `branch..nvciShelf` reads as `{{ git.branch.nvcishelf }}`. +# +# All standard filters work (`sanitize`, `hash_port`, `codename`, …). A row +# where the template renders empty (e.g. a branch without the key) shows an +# empty cell; a column that is empty for every row is dropped from the table. +# `wt list --format json` includes the rendered values under +# `items[].display.columns` in schema 2, or under `columns` in schema 1. +# +# A `Jira` column reading a key kept in git config, and a `Summary` column +# showing just the first line of the git-native branch description: +# +# [list.custom-columns.Jira] +# template = "{{ git.branch.jira }}" +# +# [list.custom-columns.Summary] +# template = "{{ git.branch.description | lines | first }}" +# +# ### Commit +# +# Shared by `wt step commit`, `wt step squash`, and `wt merge`. +# +# [commit] +# stage = "all" # What to stage before commit: "all", "tracked", or "none" +# +# ### Merge +# +# Most flags are on by default. Set to false to change default behavior. +# +# [merge] +# squash = true # Squash commits into one (--no-squash to preserve history) +# commit = true # Commit uncommitted changes first (--no-commit to skip) +# rebase = true # Rebase onto target before merge (--no-rebase to skip) +# remove = true # Remove worktree after merge (--no-remove to keep) +# verify = true # Run project hooks (--no-hooks to skip) +# ff = true # Fast-forward merge (--no-ff to create a merge commit instead) +# +# ### Remove +# +# Persistent flag values for `wt remove`. Override on command line as needed. +# +# [remove] +# delete-branch = true # Delete branch after removal (--no-delete-branch to keep) +# +# ### Switch +# +# [switch] +# cd = true # Change directory after switching (--no-cd to skip) +# +# [switch.picker] +# pager = "delta --paging=never" # Example: override git's core.pager for diff preview +# preview = true # Show the preview panel on open (Alt-p toggles) +# +# ### Step +# +# [step.copy-ignored] +# 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. +# +# ### User aliases +# +# Command templates that run as `wt `. See the Extending Worktrunk guide (https://worktrunk.dev/extending/#aliases) for usage and flags. +# +# [aliases] +# 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-aliases) `[aliases]` section instead. +# +# ### User project-specific settings +# +# User config can include a `[projects]` table for project-specific settings — worktree layout, setting overrides, anything else — separate from the project config (https://worktrunk.dev/config/#project-configuration) shared with teammates. +# +# Entries are keyed by project identifier — `//` derived from the primary remote URL (no `.git` suffix; an explicit port other than SSH's default 22 stays with the host, as in `gitlab.example.com:2222/team/project`), or the canonical repo path when there is no remote. Run `wt config show` inside the repo to see the identifier for the current project; it appears in the `PROJECT CONFIG` section as `Identifier: …`. +# +# Scalar values (like `worktree-path`) replace the global value; everything else (hooks, aliases, etc.) appends, global first. See how the layers rank (https://worktrunk.dev/config/#precedence). +# +# [projects."github.com/user/repo"] +# worktree-path = ".worktrees/{{ branch | sanitize }}" +# list.full = true +# merge.squash = false +# remove.delete-branch = false +# pre-start.env = "cp .env.example .env" +# step.copy-ignored.exclude = [".repo-local-cache/"] +# aliases.deploy = "make deploy BRANCH={{ branch }}" +# +# #### Matching several repositories with one entry +# +# A key containing `*` matches any run of characters, `/` included, so one entry covers a whole host or namespace — including nested groups. `*` is the only wildcard; every other character, `.` among them, is literal. +# +# # Every repository on a self-hosted forge whose hostname carries no brand +# [projects."git.company.example/*"] +# forge.platform = "gitlab" +# +# # Everything under one namespace shares a layout +# [projects."git.company.example/platform/*"] +# worktree-path = ".worktrees/{{ branch | sanitize }}" +# +# Every matching entry applies, least- to most-specific, following the rule above: a more specific entry — `git.company.example/platform/*` over `git.company.example/*` — wins where both set the same setting, while hooks and aliases from every matching entry all run, least-specific first. A literal key is the most specific of all; specificity is the count of non-`*` characters in the key. End a host-wide key with `/*` — a bare `git.company.example*` also covers hosts whose names merely start with that string. A remote on a non-default SSH port carries the port in its identifier, so its host-wide key is `git.company.example:2222/*`. +# +# Entries in `approvals.toml` match the same way, so a hand-written pattern entry approves its commands for every repository it covers. +# +# #### Forge platform and hostname +# +# `forge` names the forge for the matched repositories — the user-level counterpart of the project config's forge platform (https://worktrunk.dev/config/#forge-platform) block, for a self-hosted host whose name carries no `github`, `gitlab`, or `gitea` for detection to read. +# +# [projects."git.company.example/*"] +# forge.platform = "gitlab" # or "github", "gitea" (experimental), "azure-devops" (experimental) +# forge.hostname = "api.git.company.example" # API host, when the remote's own host isn't it +# +# A repository's own `[forge]` block still wins over any entry here, field by field. +# +# Hooks support all three hook forms (https://worktrunk.dev/hook/#hook-forms). A table runs multiple commands concurrently; an array-of-tables pipeline runs steps in sequence: +# +# # Single command +# [projects."github.com/user/repo"] +# post-start = "mise trust" +# +# # Multiple commands, running concurrently +# [projects."github.com/user/repo".post-start] +# mise = "mise trust" +# server = "npm run dev" +# +# # Pipeline: steps run in sequence +# [[projects."github.com/user/repo".post-start]] +# install = "npm ci" +# +# [[projects."github.com/user/repo".post-start]] +# build = "npm run build" +# server = "npm run dev" +# +# ### Custom prompt templates +# +# Templates use minijinja (https://docs.rs/minijinja/) syntax. +# +# #### Commit template +# +# Available variables: +# +# - `{{ git_diff }}`, `{{ git_diff_stat }}` — diff content +# - `{{ branch }}`, `{{ repo }}` — context +# - `{{ recent_commits }}` — recent commit messages +# - `{{ user_guidance }}`, `{{ project_guidance }}` — rendered append fragments (see Appending to the prompt (https://worktrunk.dev/config/#appending-to-the-prompt)) +# +# Default template: +# +# +# [commit.generation] +# template = """ +# Write a commit message for the staged changes below. +# +# +# - Subject line under 50 chars +# - For material changes, add a blank line then a body paragraph explaining the change +# - Output only the commit message, no quotes or code blocks +# +# +# +# {% if user_guidance %} +# +# {{ user_guidance }} +# +# {% endif %}{% if project_guidance %} +# +# {{ project_guidance }} +# +# {% endif %} +# +# {{ git_diff_stat }} +# +# +# +# {{ git_diff }} +# +# +# +# Branch: {{ branch }} +# {% if recent_commits %} +# {% for commit in recent_commits %}- {{ commit }} +# {% endfor %}{% endif %} +# +# +# """ +# +# +# #### Squash template +# +# Available variables (in addition to commit template variables): +# +# - `{{ commit_details }}` — list of commits being squashed; each renders as its subject and exposes `.subject` / `.body` +# - `{{ target_branch }}` — merge target branch +# +# Default template: +# +# +# [commit.generation] +# squash-template = """ +# Write a commit message for the change in , which is everything the squash will record. lists what it folds in. +# +# +# - Subject line under 50 chars +# - For material changes, add a blank line then a body paragraph explaining the change +# - Output only the commit message, no quotes or code blocks +# +# +# +# {% if user_guidance %} +# +# {{ user_guidance }} +# +# {% endif %}{% if project_guidance %} +# +# {{ project_guidance }} +# +# {% endif %} +# +# {% for detail in commit_details %}- {{ detail.subject }} +# {% endfor %} +# +# +# {{ git_diff_stat }} +# +# +# +# {{ git_diff }} +# +# +# """ +# +# +# #### Appending to the prompt +# +# `template-append` adds personal conventions to the commit and squash prompts without restating the whole template: +# +# [commit.generation] +# template-append = """ +# - Explain the rationale in the body, not just the change +# """ +# +# See the LLM commits guide (https://worktrunk.dev/llm-commits/#appending-to-the-prompt) for how the fragment renders and its project-config counterpart. +# +# ## 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-hooks) apply only to that repository. diff --git a/dot_local/bin/executable_ghostty-tmux b/dot_local/bin/executable_ghostty-tmux index 9169c71..5429c9d 100755 --- a/dot_local/bin/executable_ghostty-tmux +++ b/dot_local/bin/executable_ghostty-tmux @@ -12,8 +12,10 @@ # A server started here inherits this PATH. Ghostty launched from the desktop # has no shell init, so add mise's shims for tools tmux plugins shell out to -# (bun for tmux-palette, fzf, zoxide). +# (bun for tmux-palette, fzf, zoxide) and cargo's bin for the cargo-installed +# ones (wt, which lazygit's worktree keys call from the prefix g popup). [[ -d $HOME/.local/share/mise/shims ]] && PATH="$HOME/.local/share/mise/shims:$PATH" +[[ -d $HOME/.cargo/bin ]] && PATH="$HOME/.cargo/bin:$PATH" oldest=$(tmux list-sessions -f '#{&&:#{==:#{session_attached},0},#{!:#{@pj_task}}}' \ -F '#{session_created} #{session_id}' 2>/dev/null | sort -n | head -1 | cut -d' ' -f2)