Commit Graph

1003 Commits

Author SHA1 Message Date
Maximilian Roos 0afc50d677 feat: add wt config plugins claude install/uninstall (#1830)
Adds a `wt config plugins` subcommand group for managing AI tool
plugins, starting with Claude Code:

- `wt config plugins claude install [--yes]` — runs `claude plugin
marketplace add` + `claude plugin install`
- `wt config plugins claude uninstall [--yes]` — runs `claude plugin
uninstall`
- Handles already-installed, not-installed, and CLI-not-found cases
gracefully
- Updates `wt config show` hint to suggest `wt config plugins claude
install` instead of raw claude CLI commands
- The `plugins` namespace is extensible for future tool integrations

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-30 21:15:48 -07:00
Maximilian Roos b33cc8e9d6 Simplify forge detection with [forge] config section (#1826)
SSH host aliases (`git@work-ssh:owner/repo`) corrupt the hostname in
remote URLs but never the path. The previous forge detection code tried
to work around this by searching all remotes, auto-detecting GHE
hostnames, and maintaining multiple resolution strategies. This PR
replaces all of that with a simpler design: derive owner/repo from URL
paths, and let users set `forge.platform` / `forge.hostname` for corner
cases.

## Key changes

**New `[forge]` config section** with `platform` and `hostname` fields.
`ci.platform` is deprecated with migration support. Example configs and
inline help examples updated.

**`fetch_pr_info`** now builds literal API paths from the primary
remote's raw URL instead of relying on `gh`'s placeholder resolution.
`--hostname` only passed when explicitly configured.

**`platform_for_repo`** checks config > branch's remote > primary
remote. No longer searches all remotes. Loads config internally instead
of taking a `platform_override` parameter.

**`find_remote`** matches by owner/repo only (no host required),
handling SSH aliases where the local hostname differs from the API
hostname.

**`CiBranchName::from_branch_ref`** uses `split_once('/')` instead of
iterating `all_remote_urls()`, removing a git call per branch during `wt
list`.

## Deleted

- `resolve_gh_hostname` (GHE auto-detection heuristic)
- `find_forge_remote` (search all remotes by predicate)
- `all_remote_urls()` loop in branch name parsing
- `platform_override` parameter threading through callers

The spec in `src/git/repository/remotes.rs` documents the full design.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-30 21:10:32 -07:00
Maximilian Roos d8292f9a8b fix: use terminal shortcode for installation commands (#1828)
The installation block on the Claude Code docs page used raw `{%
terminal() %}` with manual `<span>` tags, bypassing the `cmd` parameter
path — no Syntect syntax highlighting and no `$ ` prompts, unlike every
other command block on the site.

Switched to the `{{ terminal(cmd="...") }}` shortcode format via the
standard `$ ` console block convention.

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

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

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

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

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

## Test plan

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

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-30 14:37:04 -07:00
worktrunk-bot 9160162af9 feat: add --branch flag to wt step commit (#1750) 2026-03-30 06:12:06 -07:00
Maximilian Roos 4047079fb2 Wrap prompt openers in <task> tags and reword squash template (#1794)
Wraps the opening instruction line in all three LLM prompt templates
(commit, squash, summary) in `<task>` XML tags — the only content that
wasn't already in XML tags. Also rewords the squash template opener from
"Combine these commits into a single commit message" to "Write a commit
message for the combined effect of these commits", shifting the framing
from mechanical concatenation to synthesis.

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-29 16:01:12 -07:00
Maximilian Roos bd9b9fe293 Syntect highlighting for template expression blocks (#1792)
Commands containing `{{ }}` template expressions (e.g., `wt step eval
'{{ branch | hash_port }}'`) were the only blocks that didn't get full
Syntect highlighting on the docs site — they fell back to accent-only
color because Tera would interpret `{{ }}` in the `cmd` parameter as
template expressions.

This uses text placeholders (`__WT_OPEN2__`, `__WT_CLOSE2__`) that pass
through Tera safely. The terminal shortcode template replaces them back
to real braces before Syntect processes them. Also fixes double-encoding
of `"` in cmd parameters (the old `&quot;` was getting double-encoded by
Syntect to `&amp;quot;`), using a `__WT_QUOT__` placeholder for the same
reason — Tera has no backslash-escape mechanism for string literals.

The skill file generator (`transform_docs_for_skill`) was updated to
handle the new format: extracting `cmd` parameter values and `|||`
delimiters into `$ `-prefixed bash blocks, converting legacy `<span
class="cmd">` body tags, and fixing the `[^)]*` regex that broke on `)`
inside cmd values.

Net effect: all code blocks on the docs site now have consistent
multi-color Syntect highlighting, and skill reference files have clean
`$ command` blocks instead of raw HTML.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-29 15:04:35 -07:00
Maximilian Roos 33b87f0b95 Widen help text wrapping from 80 to 100 chars for web docs (#1786)
The web docs content area fits ~101 monospace characters on desktop. The
previous 80-char wrap caused unnecessary line breaks in option
descriptions — e.g., enum variant descriptions wrapping mid-phrase like
"Stage everything: untracked files + unstaged tracked / changes".

Widens the two `help_reference()` call sites in `src/help.rs` from
`Some(80)` to `Some(100)`. Also fixes a pre-existing issue where clap's
line wrapping would break bold (`<b>`) spans across lines —
`ensure_line_resets` now tracks active SGR styles and re-opens them on
continuation lines.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-29 12:38:00 -07:00
worktrunk-bot 37192edf04 docs: improve hook command help text (#1785)
## Problem

The hook command's `--help` text had several issues (#1784):

1. The intro sentence included full config paths
(`~/.config/worktrunk/config.toml`, `.config/wt.toml`) — unnecessary
detail for an overview
2. Background log output referenced a raw path
(`.git/wt/logs/{branch}-{source}-{hook}-{name}.log`) instead of linking
to the `wt config state logs` command that manages them
3. "Approvals are saved to user config
(`~/.config/worktrunk/config.toml`)" was incorrect — approvals are saved
to `approvals.toml`
4. Per-hook-type examples were verbose, each with its own code block
interleaved with one-line descriptions

## Solution

- Removed inline paths from the intro sentence
- Replaced the raw log path with a link to `wt config state logs`
- Fixed the approvals path to `~/.config/worktrunk/approvals.toml`
- Consolidated per-hook-type examples into a single TOML block at the
bottom, keeping the descriptions inline as a compact reference section

## Testing

- `cargo test --test integration
test_command_pages_and_skill_files_are_in_sync` — passes, docs synced
- `cargo insta test --accept -- --test integration "test_help"` — no
snapshot changes needed
- `pre-commit run --all-files` — all checks pass (except lychee not
installed)

---
Closes #1784 — automated triage

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-28 13:44:02 -07:00
Maximilian Roos 29d762a05f Convert stray bash block to console convention in config docs (#1788)
Follow-up to #1777. The `wt config state default-branch` example in
`src/cli/config.rs` was introduced on main after the code block
convention conversion and used the old ```bash format. Converts to
```console with `$ ` prefix so the sync test generates a terminal
shortcode.

Also verified: zero ```bash and zero ```console blocks remain in
docs/content/ — all are terminal shortcodes now.

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-28 12:58:23 -07:00
Maximilian Roos b6a6744380 Consistent code block convention for syntax highlighting (#1777)
Shell code blocks on the docs site had inconsistent syntax highlighting.
Blocks with `$ ` prompt prefixes rendered as a single flat color because
Syntect's bash grammar treats `$` as variable expansion. This PR adds `$
` prompts to all shell commands while preserving full Syntect
highlighting by routing through terminal shortcodes.

## Approach

All shell commands in `console` blocks use `$ ` prefix.
`convert_dollar_console_to_terminal()` (new library function in
`src/docs.rs`) detects `$ ` lines and emits Zola terminal shortcodes:

- **Single or multi-command blocks** (no `{{ }}`): Uses `cmd` parameter
with `|||` delimiter. The shortcode template splits, highlights each
line individually through Syntect, and wraps commands in `<span
class="cmd">` (CSS `::before` adds `$ `). Comment lines (`#`) are
highlighted as comments without a prompt.
- **Blocks with `{{ }}` template syntax**: Falls back to body approach
with `<span class="cmd">` (accent color only, since Tera would interpret
`{{ }}` in the `cmd` parameter).

The function runs in both the `--help-page` generator (CLI source →
docs) and the doc sync test (hand-written docs → terminal shortcodes).
Hand-written docs can use plain `console` fences with `$ ` and get
auto-converted.

## Key files

- `src/docs.rs` — New library module with
`convert_dollar_console_to_terminal()` and unit tests
- `docs/templates/shortcodes/terminal.html` — Template enhanced to loop
over `|||`-delimited commands, highlighting each through Syntect.
Supports self-closing `{{ }}` syntax for bodyless blocks.
- `src/help.rs` — Uses library function, updated pipeline docs
- `tests/integration_tests/readme_sync.rs` — Sync test runs conversion
on all docs (not just CLI-generated). Updated skill transformation to
handle both body and self-closing terminal shortcodes.
- All `src/cli/*.rs` — `$ ` added to all console blocks
- All `docs/content/*.md` — Auto-converted to terminal shortcodes (zero
`bash` blocks remain)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: worktrunk-bot <w@worktrunk.dev>
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
2026-03-28 12:18:48 -07:00
worktrunk-bot 683327a431 docs: improve merge command help text clarity (#1783) 2026-03-28 09:51:30 -07:00
worktrunk-bot e14e80c0ce docs: clean up switch command help text (#1782) 2026-03-28 09:50:12 -07:00
Maximilian Roos d78de49444 demos: batch frame extraction in OCR validation (#1779)
Single ffmpeg call per checkpoint instead of ~20 separate calls. Builds
a `select` filter expression for the full frame range and extracts all
sampled frames in one pass with `-vsync vfr`. OCR still short-circuits
on first match.

> _This was written by Claude Code on behalf of maximilian_

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-27 23:56:43 -07:00
Maximilian Roos 38307dfad4 demos: frame-range OCR validation and build script improvements (#1778)
OCR validation checked a single exact frame number, which broke whenever
Claude Code's UI shifted timing slightly. Now uses a `Checkpoint`
dataclass with frame ranges — scans every 10th frame across a ~200-frame
window and passes if any frame matches. Resilient to normal timing
shifts without losing validation rigor.

Build script now automatically runs TUI demos sequentially (Zellij
conflicts when parallel were the root cause of previous failures), while
non-TUI demos continue running in parallel. Adds per-demo and per-target
timing summaries.

> _This was written by Claude Code on behalf of maximilian_

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-27 23:35:18 -07:00
Maximilian Roos 5c1672932f docs: remove deprecated post-create from documentation (#1776)
Replace all `post-create` references with `pre-start` across
documentation, skills, example config, and test names. The Rust
deprecation handling code (migration, alias, config parsing) remains
intact for users with existing configs.

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

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-27 17:38:57 -07:00
worktrunk-bot 82d02239f3 fix: correct --full example text and add removable worktree to docs (#1775) 2026-03-27 16:14:53 -07:00
Maximilian Roos 7efd4f7d9a Unify approval example to pre-start and add drift test (#1770)
Follows up on #1766. Two changes:

1. **Unify hook type**: faq.md approval prompt used `post-create` while
hook.md used `pre-start`. Both now use `pre-start` for consistency.

2. **Drift detection**: Added `test_approval_prompt_styled_in_hook_page`
— runs `wt hook --help-page` and asserts the output contains the styled
terminal shortcode. If the cli/mod.rs approval example changes without
updating the `post_process_for_html()` replacement in help.rs, this test
fails with an actionable error message.

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

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

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

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-27 00:46:25 -07:00
Maximilian Roos 6613ffcd90 Add fish syntax highlighting, delta diffs, and website-matched demo theme (#1768)
Three improvements to demo GIF recordings:

- **Fish syntax highlighting** — commands render in amber matching the
website's `.cmd` accent. Params/args use the default foreground.
Starship prompt uses craft-brown (`#8b7355`) to differentiate from
commands. Two delivery mechanisms: `fish_variables` for
`--shell`/`--snapshot` mode, `colors.fish` sourced in VHS hidden section
(VHS applies `Env HOME` after fish starts).
- **Delta diff viewer** — `GIT_PAGER` set per-theme (`delta
--paging=never --light` for light, `delta --paging=never` for dark).
Empty for text/snapshot recordings.
- **Light theme aligned to website** — terminal colors updated to match
`_variables.html`. Cyan and green desaturated to avoid garish bold
rendering.

Also: build script accepts multiple targets (`./build docs social`),
kills stale Zellij processes before recording, and requires delta only
for GIF mode.

> _This was written by Claude Code on behalf of maximilian_

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-27 00:41:07 -07:00
Maximilian Roos 8c6a0149dc docs: add colored approval prompt to online docs (#1766)
The approval prompt examples on the hook and FAQ pages were plain code
blocks with no styling, while the CLI renders them with colored symbols
and syntax-highlighted commands. The `wt merge` example on the LLM
commits page had the same issue.

Adds yellow ▲, dim ○, cyan ❯/◎, gutter bars on command lines, and bash
syntax highlighting (blue commands, green strings, cyan
flags/operators). Also replaces echo placeholders with real commands and
fixes the "3 commands" header that previously only showed 1 command.

Changes: `post_process_for_html()` in help.rs for auto-generated
hook.md, direct edits for manually-maintained faq.md and llm-commits.md,
`.y` and `.b` CSS classes.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-27 00:24:51 -07:00
Maximilian Roos eda8d9b53b docs: replace background-process note with pipeline ordering link (#1767)
The "background processes outlive the worktree" note in pre-start vs
post-start was about process lifecycle, not hook selection — and the Dev
servers/Databases examples already demonstrate the pattern. Replace with
a link to pipeline ordering for users who need finer execution control.

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-27 00:03:58 -07:00
Maximilian Roos a4fe57b510 docs: tighten remove and hook help text (#1765)
Clarify branch cleanup description, remove migration notes, and clean up
small wording issues in remove and hook docs.

- Reword branch deletion condition to "when they would add no changes to
the default branch if merged"
- Add "empty working trees" qualifier to the dimming condition
- Use long flags consistently in prose (`--force-delete` not `-D`)
- Remove parenthetical examples from force flag table
- Remove `wt switch /path/to/worktree also works` from detached HEAD
section
- Remove hook migration section (legacy — should have been in changelog
only)
- Reorder pre/post-start table to lifecycle order

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-26 23:10:01 -07:00
Maximilian Roos acff4cd03b Replace parentheticals with prose in after_long_help docs (#1764)
Rewrites parenthetical asides in `after_long_help` text across switch,
list, merge, step, hook, and config docs. Qualifiers and conditions
become em-dashes or semicolons; examples and analogies stay in parens.

Changes: `src/cli/mod.rs` only (docs, skills, and snapshots are
auto-synced).

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-26 22:54:26 -07:00
Maximilian Roos d3b63690a1 Reorder hook types from lifecycle to paired-by-event (#1763)
The hook subcommands in `wt hook --help` were ordered by a lifecycle
narrative (pre-switch, pre-start, post-start, post-switch, ...) that
claimed to represent execution order but didn't — post-switch and
post-start actually run concurrently, and in `wt merge`, post-merge runs
after post-remove.

Reorders to paired-by-event (pre-switch, post-switch, pre-start,
post-start, ...) which is easier to scan and matches how users think
about hooks. Also fixes the switch docs to accurately describe
post-start and post-switch as concurrent rather than sequential, and
replaces parenthetical annotations with prose.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-26 21:05:19 -07:00
Maximilian Roos 550ce8a70f Enrich quick start wt list output (#1760)
The quick start `wt list` example was sparse — only staged additions
(+53), no removed lines, no commits ahead of main, and both branches
showing the same "Initial commit" hash. Now feature-auth has a committed
change plus staged WIP, so the output showcases more columns at a
glance:

```
  Branch        Status        HEAD±    main↕  Remote⇅  Commit    Age   Message
@ feature-auth  +   ↑      +27  -8   ↑1               4bc72dc9  2h    Add authentication module
^ main              ^⇡                         ⇡1      0e631add  1d    Initial commit
```

The test creates a two-phase setup: commit auth.rs (1 commit ahead of
main), then stage WIP changes that extend auth.rs and restructure lib.rs
(producing both additions and deletions in HEAD±). The explanatory text
was updated to mention `↑1`.

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-26 19:59:27 -07:00
Maximilian Roos d738ff1c39 docs: rewrite pipeline ordering with progressive examples (#1756)
Restructured the pipeline ordering docs to lead with three progressive
examples (string → map → list) that map 1:1 to a compact reference list.
Each example builds on the last, with inline TOML comments showing
execution order in the pipeline example.

Also: noted that anonymous strings in pipeline lists are intentionally
undocumented (simplifies the docs without removing the feature), and
fixed the `[hooks]` section header in the old example which wasn't valid
config syntax.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-26 14:01:09 -07:00
Maximilian Roos 83df75b196 fix: preserve leading spaces in terminal table headers on docs site (#1755)
Zola trims leading whitespace from shortcode bodies, stripping the
two-space gutter that aligns `wt list` table headers with data rows. On
the web, columns drifted ~17px (2 characters) from where they should be.

`encode_leading_spaces()` in the snapshot-to-docs pipeline converts
leading spaces on the first line to `&#32;` HTML entities, which survive
Zola's trim and render as spaces in `<pre>` blocks.

Closes #1753

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-26 12:54:27 -07:00
Maximilian Roos 270f7a4b17 Release v0.33.0
Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-26 12:35:31 -07:00
worktrunk-bot 7d663ea97d feat: add .well-known/agent-skills/ for web-based skill discovery (#1751) 2026-03-26 08:03:36 -07:00
Maximilian Roos 57e8518f2a refactor: remove experimental() shortcode, use badge span directly (#1746)
The shortcode had one remaining usage in faq.md. Replace with the raw
`<span class="badge-experimental"></span>` used everywhere else, then
delete the unused shortcode template. Updated module doc comments in
help.rs that referenced it.

> _This was written by Claude Code on behalf of maximilian_

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 20:14:00 -07:00
Maximilian Roos 5cf6c0eac3 fix: place experimental badge outside heading anchor links (#1742)
The badge was inside Zola's `<a class="zola-anchor">` wrapper because it
was on the heading line in markdown. Now generated on a separate line
after the heading, with CSS `:has()` selectors collapsing the badge's
`<p>` wrapper to flow inline with the heading. No JS needed.

Two generation paths updated:
- `post_process_for_html()` — new `move_experimental_from_headings()`
splits heading-line badges to separate lines (terminal help unchanged)
- `format_subcommand_section()` — emits badge span after heading, not on
it

Also: `vertical-align: 0.15em` for better vertical centering (was
`middle`, which aligned too low).

Includes the heading-position fix from #1734 (squash-merged earlier).

> _This was written by Claude Code on behalf of maximilian_

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 18:44:14 -07:00
Maximilian Roos 848f83a7a0 docs: remove redundant after_long_help openers in step subcommands (#1737)
\`combine_command_docs()\` concatenates \`about\` + \`subtitle\` +
\`after_long_help\` for web docs. When \`after_long_help\` restated the
definition, the website showed back-to-back paraphrases of the same
sentence.

Removed redundant openers from 6 step subcommands:
- **relocate**: Dropped entire opening line (pure triple repetition)
- **commit/squash**: Replaced restatement with cross-reference to
LLM-commits doc
- **copy-ignored**: Dropped orphaned background sentence; now opens with
Setup
- **eval**: Dropped restated stdout/piping clause; now opens with
template variables link
- **for-each**: Replaced near-verbatim restatement with only-new-info
("summary at the end")

Added CLAUDE.md guidance ("Help text authoring") about checking help
text in all three rendering contexts (terminal, web docs, skill
reference) and the no-restatement rule.

> _This was written by Claude Code on behalf of maximilian_

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-25 16:40:27 -07:00
Maximilian Roos f02c5aa363 fix: move [experimental] badge to Aliases heading for consistency (#1734)
The `[experimental]` badge in the Aliases section was inline in the
paragraph body, while every other experimental section puts the badge on
the heading itself. Moves it to the `## Aliases` heading for
consistency.

> _This was written by Claude Code on behalf of maximilian_

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 15:47:57 -07:00
Shun Kakinoki 439d79b614 feat(config): add copy-ignored exclude patterns (#1667)
Add configurable exclusions to `wt step copy-ignored`, and make the
command safer by skipping known tool-state directories by default.

- `wt step copy-ignored` always skips built-in excluded directories (VCS
metadata like `.jj/`, `.hg/` and tool-state like `.conductor/`,
`.worktrees/`)
- Users can add more excludes with `[step.copy-ignored] exclude = [...]`
in user config, per-project user overrides, or project config
(`.config/wt.toml`)
- All exclusion sources are combined; scalar config values replace,
everything else appends (global first)

Closes #1653

> _This was written by Claude Code on behalf of maximilian_

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Maximilian Roos <m@maxroos.com>
2026-03-25 14:30:10 -07:00
Maximilian Roos 8ae8fbde14 feat: structure-driven hook execution ordering (#1713)
Post-* hooks all run concurrently in the background. When one hook
depends on another (e.g., `npm run build` needs `npm install` to finish
first), there's no way to express ordering today.

Rather than per-command flags, the TOML data structure itself determines
execution order:

- **String** — single command (unchanged)
- **Map** (table) — concurrent commands (unchanged)
- **List** (array) — serial pipeline, steps execute in order

```toml
[hooks]
post-start = [
    { install = "npm install" },
    { build = "npm run build", lint = "npm run lint" }
]
```

`install` runs first. After it completes, `build` and `lint` run
concurrently. The entire pipeline runs in the background as one detached
compound shell command — the user sees a summary line and nothing else.

**Key files:** `src/config/commands.rs` (HookStep enum, 3-form
deserialization), `src/commands/hooks.rs` (compound shell command
building, pipeline spawning), `src/commands/command_executor.rs`
(PreparedStep).

**Design decisions:**
- Backward compatible — existing string/map configs route through the
unchanged flat path. Pipeline path only activates for multi-step list
configs.
- User and project pipelines run independently (user first, project
second). No cross-source merging of pipeline structure.
- Pre-* hooks unchanged — always serial, fail-fast regardless of
structure.
- `commands()` returns `impl Iterator` for zero-allocation flat access.
`steps()` returns `&[HookStep]` for pipeline-aware execution.
- Compound shell commands wrap each step in `{ ...; }` to prevent
operator precedence issues between steps.

**Testing:** Unit tests cover all deserialization forms, serialization
round-trips, and flattening. Integration tests cover project pipelines,
template variable expansion in pipelines, mixed user-pipeline +
project-flat configs, pipeline serial ordering (marker file
verification), and pipeline failure propagation.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-25 14:28:28 -07:00
Maximilian Roos fc0ee3a5f7 fix: render [experimental] badge in Aliases section of step docs (#1729)
\`post_process_for_html()\` only ran on content before the first \`<!--
subdoc: -->\` marker. The Aliases section lives after the last marker,
so its \`[experimental]\` was never converted to a badge \`<span>\`.
Apply the transform to the subdoc region before expanding markers.

> _This was written by Claude Code on behalf of maximilian_

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-25 13:12:32 -07:00
Maximilian Roos 9ad642f874 feat: use append semantics for alias merging (#1724)
Per-project alias merging changes from "override on collision" to
"append" semantics, matching how hooks merge. When both global and
per-project user configs define the same alias name, both commands run
in sequence (global first, then per-project).

The alias value type changes from `String` to `CommandConfig` — the same
type hooks use. This stores each command separately and enables the
named-table TOML format for multi-command aliases. Key changes:

- `merge_alias_maps()` and `UserConfig::aliases()` use
`CommandConfig::merge_append()` on collision
- `step_alias()` iterates over `CommandConfig.commands()`, running each
command separately (fail-fast on first failure)
- `approve_alias_commands()` approves each project-config command
individually via the existing batch approval system
- Project-vs-user merge stays as override (user wins) for security —
only the global-vs-per-project *user* merge uses append

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-25 11:34:57 -07:00
worktrunk-bot f8b80afd94 fix: align all pre-* hooks to FailFast — no exceptions (#1708) 2026-03-24 07:51:04 -07:00
Maximilian Roos 85a58fa62a fix: hide alt-r remove from picker legend and docs (#1696)
The alt-r keybinding for in-place worktree removal has poor UX — cursor
resets to the top after each removal due to a skim 0.20 limitation
(#1695). This hides the feature from the picker legend and documentation
so users don't discover it until fixed. The keybinding itself remains
functional for power users who already know about it.

Ref #1695

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-23 13:00:07 -07:00
Maximilian Roos efad9312db feat!: rationalize hooks — rename post-create, add post-commit, background post-merge (#1679)
Implements the hook rationalization from #1670, establishing a symmetric
`pre-` (blocking) / `post-` (background) pattern for every lifecycle
event.

## Changes

**Rename `post-create` → `pre-start`**: The old name suggested it ran
*after* creation, but it actually runs *before* `post-start` as a
blocking dependency step. Both names accepted for one release cycle —
config deprecation detection, migration file generation, `wt config
update` support, and CLI alias all in place. `merge_with` folds old-name
hooks into new-name so cross-config combinations don't silently drop
hooks.

**Add `post-commit` hook**: New background hook firing after successful
commits (including squash commits), completing the commit lifecycle
pair. Included in approval batches for `wt step commit`, `wt step
squash`, and `wt merge`.

**Change `post-merge` to background**: Was blocking with `Warn`
strategy, now runs in background like all other `post-` hooks.
`--foreground` flag available for debugging.

The hook table is now a clean symmetric grid:

| Event | `pre-` (blocking) | `post-` (background) |
|-------|-------------------|---------------------|
| start | `pre-start` | `post-start` |
| switch | `pre-switch` | `post-switch` |
| commit | `pre-commit` | `post-commit` |
| merge | `pre-merge` | `post-merge` |
| remove | `pre-remove` | `post-remove` |

## Testing

868 lib + 483 bin + 1227 integration tests pass, all 13 lint checks
pass. The deprecation has 23 dedicated unit tests covering detection at
all three config scopes, migration, empty table filtering, cross-config
merge safety, and integration with `wt config show` / `wt config
update`.

Closes #1670

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-23 00:33:30 -07:00
Maximilian Roos e1e47a3b04 feat: support path-based switch for detached worktrees (#1680)
Extends path-based worktree resolution to `wt switch`, matching the
path-based removal from #1665. Both the CLI (`wt switch
/path/to/worktree`) and the picker (pressing Enter on a detached
worktree) now work.

## Changes

- `plan_switch`: path-based fallback (Phase 2b) after branch lookup
returns `None` — tries the argument as an absolute or multi-component
relative path
- Picker: passes the worktree path instead of `"(detached)"` for
detached items when switching
- CLI arg description stays "Branch name" — path support is documented
in the remove page's "Detached HEAD worktrees" section, per user
guidance
- Reverts "Branch name or path" arg description from #1665 back to
"Branch name"

## Testing

- New test: `test_switch_detached_worktree_by_path` — verifies `wt
switch /path/to/worktree` works for detached worktrees
- All existing tests pass

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-22 23:12:11 -07:00
worktrunk-bot 47e6090502 fix: allow removing detached HEAD worktrees from picker (#1665)
## Problem

The switch picker (`wt switch` TUI) fails to remove detached HEAD
worktrees when pressing `alt-r`. All detached worktrees resolve to the
identifier `"(detached)"`, which is then passed to branch-based removal
— causing the error:

```
✗ No branch named (detached)
```

Additionally, there was no CLI way to remove a detached worktree from
outside it (`wt remove @` only works from within the worktree).

## Solution

Add path-based worktree resolution as a fallback in
`resolve_worktree_arg` for the `Remove` context. When branch-name lookup
fails, the argument is tried as a filesystem path. This means:

- **CLI**: `wt remove /path/to/detached-worktree` now works
- **Picker**: Uses `handle_remove_path()` — the same codepath as the CLI

The picker doesn't do anything the CLI can't do.

## Changes

- `resolve_worktree_arg`: path-based fallback for `Remove` context
(absolute or relative paths)
- `handle_remove_path`: new function for path-based worktree removal
- `validate_remove_targets`: handles `branch: None` (detached worktrees)
via path-based removal
- Picker uses `handle_remove_path` for detached worktrees instead of
directly calling `prepare_worktree_removal`
- CLI help text updated: arg description says "Branch name or path",
detached HEAD section added to `--help`

## Testing

- Existing test: `wt remove (detached)` → fails with "No branch named
(detached)"
- New test: `wt remove /path/to/worktree` → successfully removes
detached worktree
- All existing tests pass

Thanks to @mjakl for reporting in #1661

---
Closes #1661

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

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Maximilian Roos <m@maxroos.com>
2026-03-22 22:05:21 -07:00
Maximilian Roos 38bf4abdc5 docs: changelog and migration note for template variable redesign (#1666)
Adds changelog entry for the template variable redesign (breaking +
improved) and a migration section in the hook docs explaining what
changed and how to update existing hooks.

Ref #1633

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-22 16:50:31 -07:00
Maximilian Roos af8a6022ad feat: complete template variable redesign follow-ups (#1663)
Follow-up fixes and documentation for the template variable redesign
(#1655, #1660).

**Code fixes:**
- `commit` derivation now uses `rev-parse <branch>` instead of
`rev-parse HEAD`, so `{{ commit }}` follows the Active branch
- `base` / `base_worktree_path` now available in post-switch for
existing switches (previously only creates)
- post-remove `{{ target }}` now correctly resolves to the destination
branch, not the removed branch

**Documentation:**
- Restructured template variable table — Active vars grouped first,
directional vars second, repo-level vars last
- Added arrive/depart table explaining what bare vars, `base`, and
`target` point to per operation
- Documented when `cwd` differs from `worktree_path` (pre-switch
existing, post-remove, post-merge with removal)

**Tests:** 4 new integration tests for directional var semantics.

Ref #1633

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

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

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

## Test plan

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

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-22 16:07:41 -07:00
Maximilian Roos 113a2dd940 feat: add directional template vars to all two-worktree hooks (#1655)
Phase 1 of the template variable redesign discussed in #1633. Adds
directional vars (`base`, `target`, `target_worktree_path`, `cwd`) to
all two-worktree hooks — non-breaking additions only, no existing
behavior changes.

## New variables

- **`cwd`** — execution directory, always exists on disk (all hooks).
Enables the Active model in Phase 2 where `worktree_path` becomes
identity rather than execution context.
- **`target_worktree_path`** — target worktree path, added alongside
existing `target` for merge hooks (pre-merge, post-merge) and remove
hooks (pre-remove, post-remove).
- **`base` / `base_worktree_path`** — source branch/worktree, added to
pre-switch hooks. For existing destinations, `target` /
`target_worktree_path` are also provided.

These follow the "Active" model where each two-worktree operation has a
base (source/origin) and target (destination) side:
- **Arriving ops** (switch, create): Active = destination. `base` =
where you came from.
- **Departing ops** (merge, remove): Active = source. `target` = where
it's going.

Mnemonic: *"Are you going there, or is it going away?"*

Phase 2 (separate PR) will change bare var semantics so `worktree_path`
consistently points to the Active worktree, with `cwd` as the
execution-context escape hatch.

Ref #1633

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-22 13:44:42 -07:00
worktrunk-bot eabf4aac27 fix: protect default branch worktree from removal in bare repos (#1620) 2026-03-19 08:08:16 -07:00
worktrunk-bot 5eb522cf42 docs: add pnpm post-create example for copy-ignored (#1581) 2026-03-19 00:00:03 -07:00
worktrunk-bot ce9ea1a6b6 refactor: consolidate .git/wt-* paths under .git/wt/ and stage removed worktrees in trash (#1583)
## Summary

- Moves the rename-based staging directory from a visible sibling path
(`project.wt-removing-<timestamp>`) into `.git/wt/trash/`, hiding it
from the user's workspace
- Consolidates all worktrunk-managed `.git/wt-*` directories under a
single `.git/wt/` parent
- Adds `Repository::wt_dir()` accessor as the single root for all
worktrunk state
- Renames `wt-relocate-tmp` to `wt/relocate-staging` for consistency
with `wt/promote-staging`
- Falls back to legacy `git worktree remove` if the trash directory
can't be created
- The `.git/` directory is always on the same filesystem as worktrees,
so the instant rename guarantee is preserved

## Path migration

| Before | After |
|--------|-------|
| `.git/wt-logs/` | `.git/wt/logs/` |
| `.git/wt-cache/summaries/` | `.git/wt/cache/summaries/` |
| `.git/wt-cache/ci-status/` | `.git/wt/cache/ci-status/` |
| `.git/wt-promote-staging/` | `.git/wt/promote-staging/` |
| `.git/wt-relocate-tmp/` | `.git/wt/relocate-staging/` |
| (new) `.git/wt/trash/` | Staging for background removal |

## Context

Users reported confusion when seeing `.wt-removing-*` directories in
their workspace after `wt remove` (#1572). By staging in
`.git/wt/trash/` instead, the directory is completely hidden — even if
the background `rm -rf` is slow or gets interrupted.

The `.git/wt-*` sibling directories were also consolidated into
`.git/wt/` for tidiness per review feedback.

## Test plan

- [x] Unit tests for `generate_removing_path` and
`build_remove_command_staged` updated and passing
- [x] All 105 remove-related integration tests passing
- [x] `test_remove_background_path_gone_immediately` — verifies instant
removal still works
- [x] `test_remove_background_fallback_on_rename_failure` — verifies
fallback when staging path is blocked
- [x] `test_remove_stale_staging_dir_from_crashed_removal` — verifies
stale dirs land inside `.git/`
- [x] All 2496 tests pass (lib + bin + integration)
- [x] Help snapshots, doc sync, and lint checks all pass

Closes #1572

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

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Maximilian Roos <5635139+max-sixty@users.noreply.github.com>
2026-03-17 13:30:49 -07:00