Commit Graph

1008 Commits

Author SHA1 Message Date
Maximilian Roos 7c779b864b docs: use git rev-parse --verify -q in up alias example (#2082)
Use `git rev-parse --verify -q` to suppress the error message natively,
plus `>/dev/null` to hide the SHA printed on success. Replaces the
`>/dev/null 2>&1` redirect.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-11 13:16:15 -07:00
Maximilian Roos 000613ccfd docs(faq): qualify "no background processes" claim (#2080)
Hooks and worktree removal spawn short-lived detached processes, so the
blanket "no background processes" was inaccurate. Adds "long-running"
qualifier.

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-11 12:53:39 -07:00
Maximilian Roos a9f3803452 fix(docs): use quote placeholders in terminal shortcode (#2081)
Tera has no backslash-escape for string literals, so `\"` inside a `cmd`
parameter closes the string prematurely. The `git worktree lock` example
in the FAQ was rendering as literal `{{ terminal(...) }}` text instead
of a styled terminal block.

Switched to the `__WT_QUOT__` placeholder that the shortcode template
already handles (replacing back to `"` before Syntect highlighting).

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-11 12:48:36 -07:00
Maximilian Roos e2fff694a5 docs: add Extending Worktrunk page (#2079)
New Reference page covering the three extension mechanisms — hooks,
aliases, and external subcommands — with a comparison table and enough
detail to orient readers before linking to the full references
(`hook.md`, `step.md#aliases`).

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

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-11 12:35:40 -07:00
Maximilian Roos 8c515ac723 docs: add wt step up as example multi-line alias (#2078)
Adds a real-world alias example to the step aliases documentation,
showing multi-line syntax and composition with `wt step for-each`. The
`up` alias fetches all remotes and rebases each worktree onto its
upstream.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-11 12:27:52 -07:00
Worktrunk Bot d889738258 fix: pass directive file through wt step aliases (#2077) 2026-04-11 11:24:20 -07:00
Maximilian Roos c68d0075af Release v0.36.0 2026-04-10 20:11:57 -07:00
Worktrunk Bot 5709eb50d4 Add git-style external subcommand dispatch (wt-<name>) (#2054)
## Summary

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

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

## Implementation

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

## Test plan

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

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 13:07:19 -07:00
Maximilian Roos 0089747d06 fix(readme-sync): don't leak placeholder disambiguation suffix into web docs prompt (#2058)
## Summary

`docs/content/config.md` was rendering `$ wt list (markers)` as its
prompt line. The HTML branch of `expand_command_placeholders` used the
placeholder id (the snapshot-lookup key, e.g. `wt list (markers)`) for
the `cmd=` parameter of the generated terminal shortcode, so
disambiguation suffixes leaked into the rendered prompt. This swaps it
for `display_cmd` (the inner command from the source code block),
mirroring what `ExpandMode::Plain` already does and the fix that landed
for the plain pipeline in #2049.

After rebasing on top of #2057 (which unified the HTML/plain functions
behind `ExpandMode`), the fix is a one-line change:
`cmd=\"{placeholder_id}\"` → `cmd=\"{display_cmd}\"`, plus a comment
update explaining why. The `docs/content/config.md` line is the
auto-regenerated artifact.

## Test plan

- [x] `cargo test --test integration
test_command_pages_and_skill_files_are_in_sync` — passes;
`docs/content/config.md:856` reads `cmd="wt list"` with no `(markers)`
suffix
- [x] `cargo run -- hook pre-merge --yes` — all tests, lints, doctests
pass
- [x] `docs/content/list.md` unchanged
- [x] `skills/worktrunk/reference/config.md` unchanged (plain pipeline
was already correct via #2049)

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-10 11:47:09 -07:00
Gregg Donovan 3e8799ae27 Add {{ owner }} template variable for worktree paths (#2051) 2026-04-10 13:28:18 +00:00
Maximilian Roos 31c80feee5 cli: order VarsAction reads before writes (#2044)
`wt config state vars` now lists subcommands as `get → list → set →
clear` instead of `get → set → list → clear`, grouping all reads ahead
of all writes. The policy comment on `VarsAction` is updated to match.

Follow-up to #2043 — was noted there as a deferred tightening.

> _This was written by Claude Code on behalf of Maximilian_

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 19:47:27 -07:00
Maximilian Roos 254d7aa14c cli: document subcommand ordering policy and align step/state (#2043)
Adds per-enum ordering policy comments to every clap `Subcommand` enum
so future edits have a clear rule for where new variants go. Two enums
are reordered to match their newly documented policy.

Each `Subcommand` enum now has a `// Ordering: ...` comment immediately
above its `#[derive(Subcommand)]`. The comments use one of five
principles, picked per group:

- **Pipeline / lifecycle order** — `Commands`, `StepCommand`,
`HookCommand` (steps follow execution order; hooks follow worktree
lifecycle phases with `pre-`/`post-` paired)
- **CRUD** — `*Action` enums (`get`, `set`, `clear`, with `list` where
present)
- **Action + inverse adjacency** — `install`/`uninstall`, `add`/`clear`
- **User journey / core-ness** — `ConfigCommand`, `StateCommand`,
top-level `Commands`
- **Alphabetical** — `ConfigPluginsCommand` (equal-weight siblings)

Two enums are reordered to match these policies:

- **`StepCommand`**: swap `Push` and `Rebase` so the enum matches the
actual `wt merge` pipeline order (`commit` → `squash` → `rebase` →
`push`). The bullet list and `wt merge` prose were already in this
order; only the enum was out of step.
- **`StateCommand`**: lead with the aggregate operations (`get`,
`clear`), then primary state managed by wt (`default-branch`,
`previous-branch`, `logs`, `hints`), then per-branch display annotations
(`ci-status`, `marker`), then experimental keys (`vars`). Previously the
keys came first and `get`/`clear` were buried at the end.

The auto-generated `docs/content/{config,step}.md` and
`skills/worktrunk/reference/{config,step}.md` files are regenerated to
reflect the new orderings, and `help_config_state` /
`help_step_{long,short}` snapshots are updated.

Top-level `Commands` ordering keeps `remove` before `merge` because
`remove` is a primitive worktree operation while `merge` is a composite
that wraps it — "more core" wins, not "lifecycle order".

> _This was written by Claude Code on behalf of Maximilian_

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 19:22:40 -07:00
Maximilian Roos f8f291b372 refactor(logs): nest hook output by branch/source/hook-type/name (#2041)
Flattens categorization of `.git/wt/logs/` by making the filesystem
encode what the old scheme crammed into filenames.

**Layout:** top-level *files* are shared logs (`commands.jsonl*`,
`verbose.log`, `diagnostic.md`); top-level *directories* are per-branch
log trees — `{branch}/{source}/{hook-type}/{name}.log` for hook output,
`{branch}/internal/remove.log` for background removal,
`wt/internal/trash-sweep.log` for the trash sweeper. Categorization
becomes a trivial file-vs-directory check, eliminating the exclusion
rule `ends_with(".log") && !is_diagnostic_file(name)`.

**Wins:** per-branch listing/clearing is now O(that branch) instead of
O(all logs); orphan cleanup for a removed branch is a single
`remove_dir_all`; filenames drop the joined-tuple collision hashes they
only needed to disambiguate flat keys.

**Transition:** `clear_logs` keeps a self-healing sweep of legacy
top-level `.log` files so users transition without an explicit
migration. A pinning test
(`test_state_clear_logs_sweeps_legacy_flat_files`) guards that behavior.

**Observable change:** `logs get --format=json` now puts relative paths
in the `file` field (e.g. `main/user/post-start/server.log`). Log
locations are listed as "flexible" in `CLAUDE.md`, so this is in scope.

**Reviewer orientation:**
- `src/commands/process.rs` — `HookLog::path()` rewritten; `suffix()` /
`filename()` deleted.
- `src/commands/config/state.rs` — new `walk_hook_output_files` /
`walk_branch_dir` / `HookOutputEntry`; `clear_logs` handles legacy
sweep; `partition_log_files_json` + `render_*` split along the top-level
vs hook-output seam; module docstring pins the invariant.
- `src/testing/mod.rs` — `wait_for_file_count` walks recursively.
- Test fixtures in `tests/integration_tests/config_state.rs` use new
`hook_log_rel_path` / `internal_log_rel_path` / `write_log_at` helpers.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
2026-04-09 16:47:35 -07:00
Maximilian Roos 4a7219b230 Sweep stale .git/wt/trash entries on wt remove (#2039)
## Summary

Background `wt remove` renames worktrees into
`.git/wt/trash/<name>-<timestamp>` before firing a detached `rm -rf`. If
that cleanup is interrupted (SIGKILL, reboot, disk full), the directory
is orphaned and nothing reclaims the space.

After `wt remove` prints its primary output, it now sweeps trash entries
older than 24 hours via a single detached `rm -rf`. Age is parsed from
the filename suffix, so the sweep is deterministic under
`WORKTRUNK_TEST_EPOCH`.

## Notes

- Runs **after** `handle_remove_output` so it never delays
time-to-first-output. `.claude/skills/writing-user-outputs/SKILL.md`
documents the general rule for future handlers.
- Uses existing `spawn_detached` infrastructure and a new
`InternalOp::TrashSweep` log name (`.git/wt/logs/wt-trash-sweep.log`).
- Unparseable entries are left alone — the sweep only touches names
worktrunk produced.
- Integration test seeds stale + fresh entries, runs `wt remove`, and
polls for the stale entry to disappear while the fresh one stays.

Ref #1974 — related safety improvement (data lingers in trash briefly
before deletion), but doesn't resolve the prune confirmation prompt
discussion there.

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-09 13:54:41 -07:00
Maximilian Roos 71499df45b docs: surface vars & aliases on homepage and tips-patterns (#2038)
Both features were hard to find from the docs entry points: the homepage
workflow showcase didn't mention them, and tips-patterns used them
inside larger recipes without dedicated anchors.

**Homepage (\`worktrunk.md\`):** Add an "Aliases & per-branch variables"
bullet to the Workflow automation list, as the final item right before
"lots more".

**Tips & patterns:**
- Add two new dedicated sections near the top: \`wt\` aliases and
Per-branch variables. Both are kept brief — only the recipe-specific
content (composing with \`hash_port\`, parametrizing via \`vars\`,
sticky per-branch environment) — with pointers to the reference docs for
breadth.
- Rename the existing shell-alias section to "Shell alias for new
worktree + agent" to disambiguate from \`wt\` aliases.
- Reorder so the template-variable recipes (dev server, database per
worktree) appear before "Eliminate cold starts", grouping related
content.

No changes to reference docs or CLI source this round — purely homepage
and tips-patterns.

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-09 13:47:23 -07:00
Maximilian Roos c329cb23b2 docs: tighten hook links in worktrunk landing page (#2037)
Two small follow-ups to the link audit in #2036:

- `post-start hooks` in the "Parallel agents" paragraph now points at
`#pre-start-vs-post-start` so readers land on the specific subsection.
- `Set up project hooks` in the Next steps list is now `Set up hooks` —
the narrower phrasing was misleading because the link covers both user
and project hooks. Target URL is unchanged.

> _This was written by Claude Code on behalf of Maximilian_

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-09 11:37:11 -07:00
Maximilian Roos 5e3c992ae3 docs: link Copy build caches to wt-step-copy-ignored anchor (#2036)
The "Copy build caches" bullet in the feature list under "Workflow
automation" pointed to the whole `wt step` page (covers 10 subcommands)
instead of the specific `copy-ignored` section. Anchor it so readers
land on the relevant docs.

Audited the other anchored links in the same list (Interactive picker,
`wt list --full`, CI status, LLM summaries, PR checkout, Dev server per
worktree) — all resolve to valid anchors in their target pages.

> _This was written by Claude Code on behalf of Maximilian_

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-09 09:12:21 -07:00
Maximilian Roos 760ca92748 docs: cross-link state keys to dedicated docs (#2035)
Mirrors the `vars` pattern from #2034: wraps each state key's
description in a link to its subdoc section on `config.md`. Covers
`default-branch`, `ci-status`, `marker`, and `logs`.

`previous-branch` has no `<!-- subdoc: -->` marker in the parent
`after_long_help`, so there's no `#wt-config-state-previous-branch`
anchor to link to — left as plain text.

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-09 09:11:14 -07:00
Maximilian Roos ebf421f5c5 Release v0.35.3 2026-04-09 09:02:29 -07:00
Maximilian Roos d0bee7b260 docs: cross-link vars references to dedicated docs (#2034)
The vars feature is documented in three places (hook template variable
table, config state vars page, tips-patterns recipes), but these pages
didn't cross-reference each other. Readers encountering vars in one
place had no clear path to the other.

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

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

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

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-09 08:41:01 -07:00
Worktrunk Bot b6ddf736d7 docs(config): improve project config intro and template variable heading (#2032) 2026-04-08 23:39:14 -07:00
Maximilian Roos bb383f8c2f Release v0.35.2 2026-04-08 15:50:27 -07:00
Worktrunk Bot 446e206ed6 feat: accept multiple NAME filters in hook subcommands (#2013)
## Problem

All hook subcommands (`pre-switch`, `post-switch`, `pre-merge`, etc.)
accept at most one `NAME` filter, so running a subset of hooks requires
chaining separate invocations:

```
wt hook pre-merge --yes insta && wt hook pre-merge --yes doctest && wt hook pre-merge --yes doc
```

## Solution

Changed `name: Option<String>` to `name: Vec<String>` across all 10 hook
subcommands, threading the multi-filter through the entire hook
execution pipeline:

- `src/cli/hook.rs` — positional arg becomes `Vec<String>`
- `src/commands/hooks.rs` — `HookCommandSpec`, `filter_by_name`,
`check_name_filter_matched` accept slices
- `src/commands/hook_commands.rs` — `run_hook`, `run_filtered_hook`,
`run_post_hook` signatures updated
- `src/main.rs` — dispatch helpers updated

A command matches if **any** filter matches it. Empty list (no names
given) still runs all hooks. Source prefixes (`user:`, `project:`) work
per-filter.

```
wt hook pre-merge --yes insta doctest doc
```

## Testing

- All 1390 integration tests pass
- All 495 unit tests pass
- Clippy clean
- Backward compatible — single name still works identically

---
Closes #2012 — automated triage

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-08 14:55:47 -07:00
Maximilian Roos d5314f3ed6 Release v0.35.1
Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-08 12:39:58 -07:00
Maximilian Roos e428c2ac9e Release v0.35.0
Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-07 20:12:20 -07:00
worktrunk-bot 7f8b58d7a2 docs: comment explaining why Alt-r is omitted from picker keybindings (#1996)
## Summary

- Adds an HTML comment next to the picker keybinding table in
`src/cli/mod.rs` explaining why `Alt-r` is intentionally omitted — the
skim `reload` action resets the cursor (#1695), so the UX isn't polished
enough to document yet.
- Comment auto-syncs to `docs/content/switch.md` and
`skills/worktrunk/reference/switch.md`.

Closes #1881

## Test plan

- [x] `test_help` snapshots pass (HTML comment is stripped from rendered
help)
- [x] `test_command_pages_and_skill_files_are_in_sync` passes

🤖 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>
2026-04-07 19:27:12 -07:00
Maximilian Roos 045fd25941 docs: fix inaccurate logs documentation (#1986)
The `wt config state logs` documentation had several factual errors
discovered during an audit:

- **Wrong scope label**: "Background operation logs" was the subcommand
definition, but the directory also contains diagnostic files
(`verbose.log`, `diagnostic.md`) from `-vv` — not background operations.
Changed to "Operation and debug logs".
- **Wrong behavior for `commands.jsonl`**: The "Behavior" section said
"Overwrites — Same operation on same branch overwrites previous log" but
`commands.jsonl` appends (with rotation at 1MB). Removed the blanket
section, moved accurate behavior inline to each subsection where it
applies.
- **FAQ table overlap**: `*.log` glob matched `verbose.log`,
contradicting the explicit `verbose.log` row two lines below. Narrowed
to `{branch}-*.log`.
- **Undocumented JSONL fields**: Docs said "timestamp, command, exit
code, and duration" but the actual fields are `ts`, `wt`, `label`,
`cmd`, `exit`, `dur_ms` — the `wt` and `label` fields were missing.
Added a field table since the docs encourage `jq` queries.
- **Incomplete `clear` description**: FAQ said `wt config state clear`
"removes all worktrunk keys from `.git/config`, deletes CI cache, clears
logs, and removes stale trash" — omitting markers, hints, variables, and
previous-branch.
- **Inaccurate hook output filename pattern**: Table showed
`{branch}-{source}-post-start-{name}.log` but actual filenames include
hash suffixes from `sanitize_for_filename()` and cover all `post-*` hook
types, not just post-start.
- **Dead code cleanup**: Removed unreachable `"logs"` arm from
`handle_state_get` — `StateCommand::Logs` routes directly to
`handle_logs_get`.

Also includes the `state-logs-docs` branch changes:
`is_diagnostic_file()` filter so `logs get` and `logs clear` handle
`diagnostic.md`, `render_log_section()` generic helper, and three-way
partition in the JSON output.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-07 15:37:27 -07:00
Maximilian Roos 85c3dadf17 feat: add --format=json to config state subcommands (#1969)
Add `--format=json` to 10 commands across two commits.

## Config state subcommands (commit 1)

- `logs get` — `{command_log: [...], hook_output: [...]}` with
file/size/modified_at per entry
- `ci-status get` — `{status, source, stale, url}` (richer than the text
mode's bare status string)
- `marker get` — `{branch, marker, set_at}` or `null`
- `vars list` — `{key: value, ...}` object
- `hints get` — `["hint-name", ...]` array

Also extracts a shared `log_entry_to_json` helper, deduplicating the
DirEntry→JSON conversion.

## Top-level commands (commit 2)

- `config show` — serialized user/project/system config with paths and
existence flags
- `step prune --dry-run` — array of candidates with branch, path, kind,
reason, target
- `remove` — array of removed worktrees with branch, path, deletion
status
- `merge` — summary with branch, target, committed, squashed, rebased,
removed
- `step for-each` — per-worktree results with branch, path, exit_code,
success

For action commands (remove, merge, for-each), progress goes to stderr
as usual; JSON summary goes to stdout at the end — same pattern as `wt
switch --format=json`.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-07 14:41:21 -07:00
Maximilian Roos ed883d5747 fix: document and categorize diagnostic files in state logs (#1981)
`verbose.log` and `diagnostic.md` (created by `-vv`) were undocumented
in the `wt config state logs` help text and FAQ. `verbose.log` was
miscategorized as hook output in `logs get`, and `diagnostic.md` was
invisible to both `logs get` and `logs clear` (not matched by
`is_wt_log_file`).

Adds a DIAGNOSTIC section to all output paths (human table, JSON, `logs
get`, `state get logs`), updates help text ("two kinds" → "three
kinds"), and updates the FAQ file inventory.

Also extracts the three identical per-section render functions into a
single `render_log_section` parameterized by heading and filter
predicate — `render_all_log_sections` composes them and is called from
all three display paths.

Fixes a test filter bug where `\s*$` consumed trailing newlines,
collapsing blank line separators between populated sections. Changed to
`[ \t]*$` to match only horizontal whitespace.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-07 13:03:54 -07:00
suyua9 d3fda9a8c1 docs: clarify plugin install command (#1906)
## Summary
- document `wt config plugins claude install` as the recommended plugin
install path
- keep the existing Claude marketplace commands as the manual equivalent
- align the docs page with the shipped CLI help and plugin installer
behavior

## Testing
- compared the docs change against `src/cli/config.rs`, which already
advertises `wt config plugins claude install`
- verified the diff stays limited to `docs/content/claude-code.md`

## Notes
- docs-only change

---------

Co-authored-by: ming <silverchris@foxmail.com>
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 19:49:26 +00:00
Maximilian Roos 53e11fa168 docs: fix marker position and use snapshot-backed example (#1970)
The marker help text incorrectly stated markers appear at the "start" of
the Status column with examples like `🚧↑`. The rendering code
(`status_symbols.rs:300`) places markers at the end, after all git
symbols (`↑🚧`).

Replaced the hand-written example with the `<!-- wt list (markers) -->`
placeholder pattern, backed by a new `test_readme_example_list_marker`
snapshot test. In terminal `--help`, this shows `wt list` as a command
hint; in web docs, it expands to the actual colored table output.

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-06 21:25:46 -07:00
Maximilian Roos e94da8d1e7 docs: trim wt config state help text (#1965)
Remove the "Use wt config show to view file-based configuration"
cross-reference (discoverable from `wt config --help`) and simplify the
marker example from `"🚧 WIP"` to just `🚧`.

Also fixes a pre-existing nondeterministic test failure in
`test_prune_removes_integrated_detached` by adding `RAYON_NUM_THREADS=1`
(matching the pattern used by `test_prune_removes_multiple_detached`).

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-06 20:42:30 -07:00
Maximilian Roos d6e84ed258 feat: add --format=json to wt switch and CC worktree hooks (#1959)
Adds `--format=json` to `wt switch` and WorktreeCreate/WorktreeRemove
hooks to the Claude Code plugin, so agent-created worktrees go through
`wt` instead of raw `git`.

**`wt switch --format=json`** prints structured JSON to stdout
(`action`, `branch`, `path`, etc.) without changing any behavior —
hooks, `--execute`, shell integration, and cd all run normally
regardless of format. A `SwitchFormat` enum (`text`/`json`) keeps the
help text clean (only valid values shown).

**Plugin hooks** are inline one-liners in `hooks.json`:
- `WorktreeCreate` — pipes CC's JSON through `jq` to extract the branch,
calls `wt switch --create --format=json`, extracts the path
- `WorktreeRemove` — extracts the worktree path from CC's JSON, passes
it directly to `wt remove -D --foreground`

Approval prompts still run — the hooks don't pass `--yes`, so `wt`'s
non-interactive detection applies normally.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-06 19:17:03 -07:00
Maximilian Roos 850961366d feat: clean up stale trash in wt config state clear (#1960)
Worktree removal renames directories into `.git/wt/trash/` for instant
UX, then deletes them in a background `rm -rf`. If the background
process fails or is killed, entries accumulate indefinitely — discovered
22 GB of stale trash from two entries whose background cleanup never
completed.

Adds trash cleanup to `wt config state clear`, which already handles CI
cache, logs, markers, hints, and vars. Also updates the FAQ to document
`.git/wt/trash/` in the "What files does Worktrunk create?" table and
the "Other cleanup" description.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-06 19:09:52 -07:00
Maximilian Roos 679fe539fe Deprecate --no-verify in favor of --no-hooks (#1932)
`--no-hooks` describes what the flag does — skip hooks. `--no-verify`
was inherited from git's naming but doesn't match worktrunk's semantics
(there's no "verification" step being skipped).

`--no-verify` remains as a hidden alias that emits a deprecation
warning, retained for at least one release cycle per the project's
deprecation policy.

Changes across switch, remove, merge, step commit, and step squash:
- `--no-hooks` is the canonical visible flag
- `--no-verify` hidden, emits `▲ --no-verify is deprecated; use
--no-hooks instead`
- Error hints (`↳ To skip pre-merge hooks, re-run with --no-hooks`),
info messages, help text, docs, and config examples all updated
- `resolve_verify()` helper in main.rs deduplicates the deprecation
logic
- Backward-compatibility test verifies `--no-verify` still works

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-06 15:01:18 -07:00
Maximilian Roos 44531f50b7 Release v0.34.2 2026-04-06 11:48:00 -07:00
Axel H. 4ef64c47ad feat(opencode): add OpenCode integration (activity tracking, plugin, config) (#1807)
This PR adds OpenCode integration: activity tracking markers in `wt
list`, plugin installation via CLI, `wt config show` diagnostics, and
LLM commit generation detection.

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

## What's included

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

## Docs approach

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

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

---------

Co-authored-by: Maximilian Roos <m@maxroos.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-05 18:44:41 -07:00
Maximilian Roos 7bc0805e7e feat: renice copy-ignored to lowest OS priority (#1916)
`wt step copy-ignored` now calls `renice -n 19` on its own process
before starting work, so large reflink copies yield to interactive
foreground tasks. The call is in `step_copy_ignored()` rather than the
shared `COPY_POOL` init, so other callers (like `handle_promote`'s
cross-device fallback) keep normal priority. Non-fatal if `renice` is
missing or fails.

Avoids `libc::nice()` directly because of `forbid(unsafe_code)` —
`renice` is a one-time subprocess spawn during pool init.

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-05 18:25:30 -07:00
Maximilian Roos 9e612a11e6 Release v0.34.1
Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-03 21:58:54 -07:00
Maximilian Roos 2ab8fe2a94 Release v0.34.0
Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-03 07:18:18 -07:00
Maximilian Roos 5913e3d56b feat: Rust-orchestrated pipeline execution with stdin spec passing (#1893)
Background pipelines no longer compile to compound shell strings with
`__WT_TPL_*` env vars and `eval "$(wt step eval --shell-escape ...)"`
wrapping. Instead, the parent `wt` process serializes a `PipelineSpec`
to JSON and spawns `wt hook run-pipeline` as a detached background
process, piping the spec to stdin. The background runner expands
templates just-in-time and spawns shell children per step.

Key changes:

- **New `spawn_detached_exec`** in `process.rs` — spawns a binary
directly (no intermediate shell), pipes data to stdin, with proper
detachment on both Unix (`process_group(0)`) and Windows
(`CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS`). Shared log setup
extracted into `create_detach_log` helper.
- **New `pipeline_spec.rs`** — serde types for the JSON spec
(`PipelineSpec`, `PipelineStepSpec`, `PipelineCommandSpec`) with
roundtrip test.
- **New `run_pipeline.rs`** — the background orchestrator. Reads spec
from stdin, walks steps in order (serial abort-on-failure, concurrent
spawn-then-wait-for-all), expands templates with `shell_escape=true`,
pipes context JSON to each shell child's stdin. Module docstring
specifies the full execution model.
- **`wt hook run-pipeline`** — hidden subcommand (not in `--help` or
autocomplete) replacing the old top-level `_run-pipeline`.
- **Deleted**: `build_pipeline_command`, `format_cmd`, `--shell-escape`
flag, `__WT_TPL_N` env vars, `extra_env` parameter on `spawn_detached`,
7 unit tests for the old shell builder, 2 integration tests + snapshots
for `--shell-escape`.
- **Updated docs** — "How it works" sections in CLI help,
`docs/content/hook.md`, and skill reference no longer show the compound
shell command example. Test comments updated to describe the new
execution model.
- **New tests** — concurrent group execution (both commands run),
concurrent partial failure (sibling completes, later steps abort), and
shell escaping of metacharacters (spaces, quotes, `$`).

> _This was written by Claude Code on behalf of maximilian_

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-02 19:04:37 -07:00
worktrunk-bot 4972fd8b02 docs: fix FAQ reference to approvals storage location (#1886) 2026-04-02 09:31:24 -07:00
worktrunk-bot f6e89009e5 fix: detect squash-merged branches when merge-tree conflicts (#1820)
## Problem

`wt step prune` (and `wt remove`) fail to detect squash-merged branches
when the default branch has since modified the same files that the
branch touched. This is because `git merge-tree --write-tree` reports
conflicts when both sides changed the same files, and the code
conservatively treated conflicts as "not integrated."

The same issue affects `wt list` — the `WouldMergeAdd` task calls the
same function, so the integration symbol `⊂` is not shown for these
branches.

## Solution

When `git merge-tree --write-tree` conflicts, fall back to **patch-id
matching**: compute the branch's squashed patch-id (`git diff-tree -p
merge-base..branch | git patch-id --verbatim`) and check if any commit
on the target has a matching patch-id. This detects squash merges
because the squash-merge commit on the target has the exact same content
changes as the branch.

Uses `--verbatim` (not `--stable`) to avoid false positives from
whitespace normalization — `--stable` strips whitespace, so
tabs-vs-spaces would produce matching patch-ids even though file content
differs.

The fallback is only triggered when merge-tree conflicts — the happy
path (no conflicts) is unchanged.

The merge-tree → patch-id sequence is extracted into
`Repository::merge_integration_probe()`, a shared method used by both
`wt list` (parallel tasks) and `wt remove`/`wt merge` (sequential path).
This fixes a drift where the two call sites handled patch-id errors
differently.

## Known limitation

The patch-id fallback checks if the branch's diff was **ever** applied
to the target, not whether the effect **persists**. If a squash merge is
later reverted on the target and then the same files are modified
(causing merge-tree conflicts), the historical squash-merge commit's
patch-id still matches. This is documented in #1818. A follow-up could
add a revert-detection check after finding a match.

## Testing

- `test_remove_squash_merged_then_same_files_modified` — reproduces the
exact scenario from #1818 (branch modifies file, squash-merged, then
target modifies same file)
- `test_prune_squash_merged_same_files_modified` — verifies `wt step
prune --dry-run` detects the branch
- All existing squash-merge tests continue to pass (5 remove + 22 prune
tests)

Closes #1818

> _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 <5635139+max-sixty@users.noreply.github.com>
Co-authored-by: Maximilian Roos <m@maxroos.com>
2026-04-01 19:05:54 -07:00
ctrl-q 62ed7e37d2 Use official Arch package in installation instructions (#1872)
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 15:35:52 -07:00
Maximilian Roos 36aec6b090 Document hooks in user config, fix config doc comment style (#1861)
Adds a `## Hooks` section to user config docs — the config supported
hooks but didn't document them. Also fixes comment style issues and
improves cross-references.

- Added `## Hooks` section to user config with the three formats
(string, named table, pipeline) and user-vs-project differentiation
- Used plain text descriptions before code blocks instead of TOML
comments inside them, avoiding `# #` double-commenting in
`dev/config.example.toml`
- Replaced the duplicate TOML block in project config hooks with a
minimal example and cross-reference to `wt hook` docs
- Both hooks sections now link to `wt hook` docs as the canonical
reference
- Added CLAUDE.md guidance on avoiding TOML comments inside code blocks
in config docs

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-31 22:13:51 -07:00
Maximilian Roos e89884b934 refactor: rename switch config no-cd to cd with reversed polarity (#1860)
Follow-up to #1856 (`no-ff` → `ff`). Renames the last negative-sense
config field so all boolean config fields use positive names defaulting
to true: `squash`, `commit`, `rebase`, `remove`, `verify`, `ff`, and now
`cd`.

The old `no-cd` field is kept for backward-compatible deserialization
with a deprecation warning guiding users to `cd = false`.

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-31 21:44:51 -07:00
Maximilian Roos b459b7a7e0 refactor: rename merge config no-ff to ff with reversed polarity (#1856)
`ff = true` (default) is more natural than `no-ff = false` — now
consistent with all other merge config fields which are positive-sense
defaulting to true.

CLI flags `--no-ff` and `--ff` are unchanged. The old `no-ff` config key
still works via serde deserialization + polarity inversion in
`normalize_deprecated_fields()`, with a deprecation warning.

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

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-31 20:16:17 -07:00
worktrunk-bot 09332057f1 fix: add hook examples back to project config docs (#1853)
## Problem

CI failed on main after #1845 (491108a). The
`test_project_config_docs_include_all_sections` test requires at least
one hook key (e.g. `pre-start`, `pre-merge`) to appear as a bare key in
the project config docs section of `src/cli/mod.rs`. The hooks
deduplication in #1845 replaced all hook examples with a
cross-reference, leaving no hook keys in the section.

## Solution

Added a brief TOML example showing three common hooks (`pre-start`,
`post-start`, `pre-merge`) below the cross-reference text. This
preserves the deduplication intent while satisfying the test constraint.

## Testing

- `test_project_config_docs_include_all_sections` — passes
- `test_command_pages_and_skill_files_are_in_sync` — passes (auto-synced
docs and skills)
- Help snapshots — no changes needed

---
Automated fix for [failed
run](https://github.com/max-sixty/worktrunk/actions/runs/23827619802)

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-31 19:51:22 -07:00
Maximilian Roos 491108a782 Document hooks in user config (#1845)
The user config (`~/.config/worktrunk/config.toml`) supports hooks via
`OverridableConfig.hooks`, but the user config documentation didn't
mention them. The project config docs had a hooks section with the full
TOML format reference — this adds a comparable section to user config
and deduplicates the project config side.

- Added `### Hooks` section to user config docs with the three formats
(string, named table, pipeline) and user-vs-project differentiation
- Replaced the duplicate TOML block in project config hooks with a
cross-reference to the user config section

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

---------

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

> _This was written by Claude Code on behalf of maximilian_

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-31 18:27:35 -07:00