mirror of
https://github.com/max-sixty/worktrunk.git
synced 2026-09-14 20:00:38 +08:00
worktree-path-not-found-error
1003 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
68dae3a2fc |
docs(cli): drop redundant --format value parenthetical where clap lists them (#3112)
Follow-up to #3108. That PR made clap render `--format`'s values inline as `[possible values: …]`, but five flags still carried a hand-written value parenthetical in their doc comment, so the list rendered twice — three times in `wt list statusline -h`: ``` --format <FORMAT> Output format (table, json, claude-code) [default: table] [possible values: table, json, claude-code] ``` This drops the parenthetical (→ bare `/// Output format`) from every `--format` flag that does **not** hide possible-values, leaving clap's inline list as the single source: `wt list statusline`, `wt step for-each`, `wt step prune`, `wt config show`, `wt config state vars list`. The deliberate hiders are left untouched — they set `hide_possible_values = true`, so their parenthetical is the *only* value list: `wt list`, `wt config state get` (both suppress the `claude-code` value, which is meaningless for them), and the shared `GlobalFormatFlag` for `config state cache`/`logs`/`hints`/`ci-status`/`marker`. The rule: if clap prints the list, drop the hand-written one; if clap's list is suppressed, keep it. For `wt list statusline`, `claude-code`'s non-obvious behavior stays visible in the long-help possible-values block (`- claude-code: Claude Code statusline mode (reads context from stdin)`) and the dedicated "Claude Code mode" section. This also regenerates a stale mirror: `wt step eval --format`'s `step.md` section was out of sync with the binary (expanded block vs. terse) because #3106 added that flag with the old `SwitchFormat` while #3108 trimmed it — their merge order left `test_docs_are_in_sync` failing on `main`. This PR fixes that. > _This was written by Claude Code on behalf of max_ Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ea126f1466 | docs(step): regen wt step eval --format mirror (#3109) | ||
|
|
41d5ef7349 |
docs(cli): render --format help as terse inline possible-values (#3108)
Every `--format` flag — `wt step commit`/`squash`/`rebase`/`push`/`for-each`/`copy-ignored`, `wt remove`, `wt switch`, `wt merge`, `wt config show` — rendered a verbose `Possible values: - text: … - json: …` block in long help and the generated doc pages. clap auto-generates that block from the per-variant doc comments on the shared `SwitchFormat` enum. Dropping those variant doc comments makes clap render the terser inline `[possible values: text, json]` instead, slimming every `--format` flag's long help in one place. The variant names are self-describing, so the descriptions added nothing. The sibling `OutputFormat` enum gets the same trim for `Table`/`Json`, but `claude-code` keeps its description — "reads context from stdin" conveys real, non-obvious behavior the bare name doesn't. That one surfaces in `wt list statusline --help`. Doc mirrors (`docs/content/`, `skills/worktrunk/reference/`) and help snapshots are regenerated. > _This was written by Claude Code on behalf of max_ Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c9cfc1ef16 |
feat(eval): add --format=json for structured template output (#3106)
Adds `--format=json` to `wt step eval`, the structured counterpart to its `-v` view.
`wt step eval` printed only the bare rendered result to stdout; the template's name, source, and rendered result were reachable solely from the `-v` human lane on stderr, with no machine-readable form. `--format=json` emits `{name, template, result}` to stdout — the structured analog of the `-v` expansion view, and consistent with `wt hook show --format=json` and the other `wt step` JSON lanes.
Text mode is unchanged (bare result to stdout). The two lanes compose: `--format=json -v` keeps JSON on stdout and the human expansion view on stderr, so `wt step eval --format=json -v … 2>/dev/null` yields clean JSON.
Implementation mirrors the sibling `wt step` commands: a `SwitchFormat` flag under the `Automation` heading, an inline `serde_json::json!` payload, and snapshot tests covering the JSON lane and the JSON + `-v` composition.
> _This was written by Claude Code on behalf of max_
|
||
|
|
6f63a5bc54 |
docs(switch): refine wt switch --help text (#3096)
## Summary Picks up the four `wt switch --help` refinements from #3095: 1. Use backtick emphasis for shortcut symbols (`^`, `-`, `@`, `pr:{N}`, `mr:{N}`) instead of single quotes — matches the existing style on `--base`. 2. Surface PR/MR URL acceptance in the `[BRANCH]` argument summary ("Branch name, shortcut, or PR/MR URL"), so the short help mirrors what the long help already documents. 3. Rewrite the Forks paragraph as prose, opening with *"If the PR or MR is on a fork…"* and using descriptive mood ("requires renaming") instead of imperative ("rename it first"). Drops the `(e.g., feature-fix)` example. 4. Consolidate the per-forge notes — the Requires line, the standalone Gitea (experimental) paragraph, and the standalone Azure DevOps (experimental) paragraph — into a single footer pointing at [forge platform](https://worktrunk.dev/config/#forge-platform) for the platform-specific setup. ## Before / after — `Pull requests and merge requests` section Before: > Both work anywhere a branch is accepted, including `--base`. > > Requires `gh` (GitHub) or `glab` (GitLab) CLI to be installed and authenticated. The `--create` flag cannot be used with a PR/MR reference since the branch already exists. > > **Forks:** The local branch uses the PR/MR's branch name directly (e.g., `feature-fix`), so `git push` works normally. If a local branch with that name already exists tracking something else, rename it first. > > **Gitea (experimental):** `pr:` is also compatible with Gitea via the `tea` CLI. Set `[forge] platform = "gitea"` in `.config/wt.toml` to opt in; worktrunk also auto-detects Gitea when the remote host contains `gitea` or when `tea login add` has been run for the host. > > **Azure DevOps (experimental):** `pr:` is also compatible with Azure DevOps via the `az` CLI (with the `azure-devops` extension). Set `[forge] platform = "azure-devops"` in `.config/wt.toml` to opt in; worktrunk also auto-detects Azure DevOps from `dev.azure.com` and `*.visualstudio.com` remotes. After: > Both work anywhere a branch is accepted, including `--base`. The `--create` flag cannot be used with a PR/MR reference since the branch already exists. > > If the PR or MR is on a fork, the local branch uses its branch name directly, so `git push` works normally. A pre-existing local branch with that name tracking something else requires renaming first. > > Requires `gh` (GitHub), `glab` (GitLab), or an equivalent CLI installed and authenticated; see [forge platform](@/config.md#forge-platform) for Gitea, Azure DevOps, and other supported platforms. ## Testing - `cargo test --test integration test_docs_are_in_sync` — generated docs mirrors regenerated and now in sync. - `cargo test --test integration test_help` — short and long `switch --help` snapshots updated. - `cargo build` — clean compile. - Inspected the rendered `wt switch --help` output to confirm the shortcuts render with bold emphasis and the consolidated footer reads cleanly. ## Notes for review - I did not sweep other `--help` pages for parenthesized "e.g." examples — the issue mentioned looking for more, but I scoped this PR to the switch help and left the broader pass for a maintainer call. One other in-scope candidate at `src/cli/mod.rs:569` (`Switching to a remote branch (e.g., wt switch feature when only origin/feature exists) creates a local tracking branch.`) felt like a useful clarification of "remote branch" rather than a redundant example, so I left it alone. - The forge-platform link uses the `@/config.md#forge-platform` form that the rest of `after_long_help` uses for cross-doc references. --- Closes #3095 — automated triage Co-authored-by: worktrunk-bot <worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
01242177d3 |
refactor(expand): label -v template expansion as source/result blocks (#3099)
Reworks the `-v` template-expansion view. It used to stack the template,
a lone `→`, and the result in one shared gutter; the arrow on its own
line read awkwardly and the shared gutter blurred input vs output.
Now each side is its own labeled block — an info header above a bash
gutter:
```
○ eval source
{{ branch | hash_port }}
○ eval result
12107
```
The two headers carry the input/output distinction, so both blocks keep
the same gutter with content at column 2 (no marker glyph, no in-gutter
indent). Multi-line templates and results follow the same shape, one
gutter line per source line.
`expand_template` emits no trailing blank. The standalone `wt step eval`
adds one after the view; in a command pipeline the executor already
separates the block from the next output (an unconditional trailing
blank there would trip the no-double-blank guard).
This view also surfaces in `wt switch -v`, hooks, aliases, and `wt -v
list`'s deprecation preview — all snapshots regenerated. Known rough
edge: `wt -v list` previews a deprecated `worktree-path` once per
worktree, and those previews now sit back-to-back without a separating
blank.
> _This was written by Claude Code on behalf of max_
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
044ed66d56 | docs: add "Per-worktree env vars" tips section (direnv/mise) (#3074) | ||
|
|
7d3c923ef0 | feat(picker): free digit keys for typing; preview tabs to Alt + Tab/Shift-Tab (#3079) | ||
|
|
a01a9020ca |
refactor(eval): inspect template variables via -v, not --dry-run (#3078)
`--dry-run` previews a mutation a command would perform. `wt step eval`
mutates nothing — it expands a template and prints the result — so its
`--dry-run` was a category error: it dumped the full variable context as
raw `key=value` text, and was the only `--dry-run` in the CLI not using
the gutter house style.
This moves variable discovery to the verbose lane. `wt step eval -v` now
lists the available template variables on stderr in the gutter style,
above the `{{ template }} → result` expansion view that `-v` already
rendered:
```console
$ wt step eval -v '{{ branch }}'
○ Available template variables
branch = feature/auth-oauth2
worktree_path = /home/user/projects/myapp-feature-auth-oauth2
…
○ Expanding eval
{{ branch }}
→
feature/auth-oauth2
feature/auth-oauth2
```
The result still goes to stdout, so `$(wt step eval …)` is unchanged.
`eval` is experimental, so `--dry-run` is removed outright rather than
deprecated — it now errors loudly instead of breaking silently.
The convention is documented in `src/commands/CLAUDE.md`: `--dry-run`
previews a mutation, so a command that changes nothing carries none, and
inspection belongs to `-v`. The other nine `--dry-run` commands already
conform (`config alias dry-run` and `hook --dry-run` use `info_message`
+ the gutter), so eval was the only outlier.
> _This was written by Claude Code on behalf of max_
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
0d9b9b8082 |
feat(commit-generation): deprecate commits squash template variable (#2985)
|
||
|
|
11129d3811 |
fix(plugin): fail worktree hooks before side effects; document path args (#3060)
Hardens the plugin's worktree-lifecycle hooks against malformed
payloads, and documents two things they rely on.
**Hooks** (`plugins/worktrunk/hooks/hooks.json`): in the pipeline form,
`jq -r .name | xargs … wt switch --create {} …` runs `wt` with whatever
jq printed — so a payload missing `.name` minted a real branch named
`null`, and `set -o pipefail` could only report the failure after the
side effect (verified under `/bin/sh`). Both hooks now validate the
field in a command substitution before `wt` runs (`name=$(jq -er .name)
|| exit 1; …`), which fails with nothing created and stays
whitespace-safe via quoted variables. The `bash -c` wrapper remains —
hook commands must parse under fish/zsh/bash and fish rejects
`name=$(…)` — but `set -o pipefail` is gone: the only remaining pipe
ends in `jq -er .path`, whose exit is the pipeline's. Exercised under
`sh -c` against the shipped JSON: missing field → exit 1, no branch;
fresh create → path on stdout, exit 0; existing branch → wt's real
error, nonzero; remove by path → removed.
**Docs**: `wt remove`'s positional also accepts worktree paths
(`resolve_worktree_arg` tries branches first, then paths) and the
`WorktreeRemove` hook passes a path — the help line now reads "Branch
name or worktree path". The plugin README lists `jq` as a hook
dependency, and the skill's branch-naming step asks for names consistent
with the repo's existing worktrees.
> _This was written by Claude Code on behalf of max_
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
fd3ae5bff4 |
feat(plugin): optional branch name and single-route flow for /wt-switch-create (#3058)
The `/wt-switch-create` branch argument becomes optional (a name is
picked from the task when omitted), and the skill's workflow is rebuilt
from a guard-heavy three-route flow into one route plus one error-driven
fallback.
**The new flow**: pick a branch name if none was given, create the
worktree with `wt -C <repo> switch --create <branch> --no-cd
--format=json` in Bash (rerunning without `--create` when the user named
an existing branch), re-root the session with `EnterWorktree({path})`,
and on a graceful rejection (different repo, pinned cwd, nested session)
work in the worktree via absolute paths instead.
**Why the rewrite**: the old flow's guards encoded hypotheses about
Claude Code that turned out wrong or untested. The load-bearing
discoveries, each verified by binary inspection, official docs, or live
tests and documented in the new `skills/wt-switch-create/rationale.md`:
`EnterWorktree({path})` accepts worktrunk's sibling layout on first
entry; every rejection it can raise is side-effect-free, so
try-then-fallback replaces prediction; the previous
`EnterWorktree({name})`/hook route silently auto-removes a clean
worktree at session exit, which is wrong for durable worktrunk worktrees
(path-entered worktrees persist); and the "pinned cwd" the old guards
defended against was a misdiagnosis of the harness resetting `cd` that
leaves the session's working directories.
**Hook fix**: both `WorktreeCreate` and `WorktreeRemove` pipelines are
wrapped in `bash -c 'set -o pipefail; ...'`. Without it the trailing
`jq` exits 0 on empty input, so a `wt` failure surfaced as a
"successful" hook with an empty path. The wrapper is explicit `bash`
because hooks spawn via `/bin/sh -c`, where dash rejects `set -o
pipefail` fatally. `xargs -I{}` also fixes worktree paths containing
spaces. Tested end-to-end in both directions (success exits 0 with the
path; failure exits 1 with empty stdout).
Docs, README, and plugin CLAUDE.md are aligned with the new mechanism.
The agent-isolation use of the `WorktreeCreate` hook is unchanged.
Review trail: the design survived an evidence audit (every rationale
claim independently re-verified), a code review (bare directory-named
tokens no longer parse as the repo; mid-session moves now stash
uncommitted work across), and a level check that considered and rejected
fixing this in `wt` itself (an idempotent `--create` cannot encode who
chose the branch name, which determines the correct recovery).
> _This was written by Claude Code on behalf of max_
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
8f44e98636 |
feat(statusline): show used % instead of pace above 90% of the rate limit (#3057)
When the statusline rate-limit segment shows and the binding window is above 90% used, it now displays the used percentage instead of the pace ratio — `95%(8:30am–1:30pm)` rather than `1.1×(8:30am–1:30pm)`. Near the cap, how much is left matters more than how fast it's going; the pace form is unchanged below 90%. The show condition is untouched (P(over) ≥ 0.5 via the existing Bayesian gate), so this only changes which number renders once the segment is already visible. Strictly greater-than 90: a window at exactly 90% still shows pace. Unit test pins the switch and the boundary; the 95%- and 100%-used integration snapshots now render the percent form, and the help text and claude-code docs describe it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) > _This was written by Claude Code on behalf of max_ Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
22768d425f |
feat(hooks)!: table-form pre-* hooks run concurrently — remove the serial path (#3052)
## Summary Lands the cutover announced in v0.37.0 (#2135): a multi-entry table hook now runs its commands concurrently for every hook type. `[pre-merge]` with several keys behaves like `[post-start]` with several keys and like a single `[[pre-merge]]` block — one execution semantic for `Concurrent` steps. The serial behavior was not implemented where one might expect. The executor's serial branch (`ForegroundStep.concurrent` / `SourcedStep.is_pipeline` / `CommandConfig::is_pipeline()`) was mostly dead: the deprecation shipped with a load-time TOML migration that rewrote multi-entry pre-* tables into serial pipeline form in memory on every config load, so affected configs never reached the executor as `Concurrent` steps. Removing that migration (now a `DEPRECATION_RULES` row, after #3045) is the actual behavior change; the executor flags come out with it. ## Why no replacement runtime warning Affected configs have printed a warning on every `wt` invocation for twenty minor releases (v0.37.0, April 12 → v0.57.0), with `wt config update` offering a one-command migration that preserved serial behavior explicitly ("migrate now to keep the current serial behavior once the table form is repurposed"). A post-cutover warning has no coherent shape: table form is now legitimate concurrent config, so it would nag users who want exactly that behavior with no way to silence it, and the `wt config update` rewrite would no longer be behavior-preserving. ## Parse normalization A one-entry top-level table previously parsed as a `Concurrent` group of one; with the serial branch gone it would have picked up `name │` output prefixes. One-entry maps now parse as `Single(named)` everywhere, matching one-entry maps inside pipeline lists (removing the documented dict-at-top vs dict-in-list asymmetry), and a new `Serialize` arm keeps one-step named configs round-tripping as named tables. Single-entry table hooks render exactly as before. ## Behavior changes 1. **Multi-entry table-form pre-* hooks: serial → concurrent.** The announced change. 2. **`wt hook <post-type>` foreground runs of multi-entry table hooks: serial → concurrent.** Previously an undocumented inconsistency — the same config already ran concurrently via the background path. 3. **Single-entry table aliases (`[aliases.x]` with one key): prefixed-stderr → stdout passthrough.** Now matches the string and `[[aliases.x]]` spellings, and makes `wt <alias> | …` work for this spelling too. (2) and (3) were never deprecation-warned; all three belong in the release notes. ## Tests - `test_pre_merge_deprecated_table_runs_serially` → rewritten as `test_pre_merge_table_form_runs_concurrently`; the single-`[[pre-merge]]`-block test is deleted (both forms now parse identically, so it duplicated the rewritten test). - Fixtures that genuinely need serial ordering (`>`/`>>` chains, the signal-abort "second must not run" assertion) converted to pipeline form. - `test_user_hooks_preserve_toml_order` (#737) and `test_post_start_named_commands` keep table form and pin ordering via `WORKTRUNK_TEST_SERIAL_CONCURRENT=1`, so insertion order is still asserted through the real concurrent input ordering. - New: one-entry-table parse/round-trip unit test; `test_alias_single_entry_table_writes_to_stdout` pinning change (3). - Regenerated snapshots drop a stale `RUST_LOG: warn` env line (the harness stopped setting it in #2901). ## Known follow-up Concurrent-group announcements expose a pre-existing ANSI dim-bleed: `format_bash_with_gutter` output ends without closing the dim attribute, so back-to-back `◎ Running …` lines render dim (visible today on pipeline-form hooks and `--execute` verbose). Now that this rendering is the table-form default it's worth fixing, but the fix lives in the gutter formatter and churns every bash-gutter snapshot — separate PR. > _This was written by Claude Code on behalf of max_ --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ce7c7c4861 |
fix(statusline): drop the word "pace" from the rate-limit segment (#3053)
The Claude Code statusline's rate-limit segment read `2.9×pace(Tue–Tue
5pm)`. The word "pace" made the longest segment longer without earning
the space, so the segment now renders as `2.9×(Tue–Tue 5pm)` and `wt
list statusline --help` explains the reading instead: 2.9× the pace that
would exactly fill the window, plus a one-line gesture at why the
segment doesn't show on every over-pace reading ("Likely" is a Bayesian
forecast; early-window bursts don't trigger it).
While there, the help page's structure had drifted: the `claude-code`
bullet under "Output formats" and the "Claude Code mode" section both
described the stdin context and the added segments. The segment list now
lives in the bullet (parallel with the `table` bullet) and the section
carries the input fields and the pace explanation. Ranges use en-dashes
(`0–100`), and the hard-wrapped paragraphs are unwrapped to match
`after_long_help` convention elsewhere in the CLI.
`docs/content/claude-code.md` and its skill mirror drop the word from
the example line and segment description. CHANGELOG.md keeps the old
spelling as a historical record.
> _This was written by Claude Code on behalf of max_
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
c511d3fa2b |
feat(list): show PR/MR number in the CI column (#3041)
The CI column in `wt list --full` (and the statusline) previously showed a single colored dot. It now shows the branch's open PR/MR reference — `#3035` on GitHub/Gitea/Azure DevOps, `!3035` on GitLab — colored by CI status, dimmed when stale, and hyperlinked to the PR. When no number is available (branch workflows without a PR/MR, pre-number cache entries, or a number wider than the allocated column), the cell shows a bare `#` in the same colors. Fetch errors always render `⚠`, even when a number is known — Error and Conflicts share yellow, so a yellow `#3035` would read as a conflicted PR. The branch merges main's review-state feature (#3044): review colors (magenta/cyan) and draft dimming apply to the number cells exactly as they did to the dot, and the `--help` legend shows colored `#` samples for all seven states (the interim version had dropped the colored samples from the legend entirely). ## The width problem `wt list` renders skeleton-first: column widths are fixed before any CI data arrives, and the table never resizes mid-render. The PR number's width therefore has to be known up front. The solution is a repo-level ratchet cache (`.git/wt/cache/pr-number/max.json`) holding the largest PR number any fetch has seen — PR numbers are monotonic per repo, so the value needs no invalidation. Pre-skeleton, `collect` reads that one file and sizes the column exactly; on a cold cache the estimate is 5 chars (`#9999`). A number that outgrows the estimate renders as the bare `#` for that run and sizes correctly on the next run once the ratchet records it. The ratchet is deliberately separate from the per-branch `ci-status/` entries so the width hint isn't coupled to branch-entry retention, and `detect` re-ratchets on cache hits too, so a deleted or racily regressed `max.json` heals from locally cached numbers instead of waiting out the TTL. ## Reviewer's map - `src/commands/list/ci_status/mod.rs` — `PrRef` (number + forge sigil, `PrRef::pr`/`PrRef::mr` constructors), `PrStatus.number` (serde-default so pre-existing cache entries still deserialize, rendering `#` until their 30–60s TTL expires), `format_cell` width-aware renderer with the Error guard, ratchet in `detect` (both cache-hit and fetch paths) - `src/commands/list/ci_status/cache.rs` — `MaxPrNumber` ratchet (read/ratchet/clear) - `src/commands/list/ci_status/{github,gitlab,gitea,azure}.rs` — each fetcher populates the number (`gh --json number`, `iid`, Gitea `number`, `pullRequestId`); GitLab's mr-view-failure path carries the iid/URL/review state into the error status so the `⚠` stays clickable - `src/commands/list/layout.rs`, `collect/mod.rs` — width estimate threading - `src/commands/list/render.rs`, `model/item.rs` — table cell and statusline both go through `format_cell` - `src/commands/list/json_output.rs` — `ci.number` field - `src/commands/config/state.rs` — ratchet shown by `state get`/`cache get` (table + JSON) and swept with the CI cache category, including the deprecated `ci-status clear --all` path - `src/md_help.rs`, `src/help.rs` — legend colorization rules rewritten from `●` to `#` (terminal + website) Most of the diff is snapshot churn from the column width and glyph changes plus regenerated docs mirrors. Known trade-offs: concurrent statusline ratchet writes can transiently lose an update (monotonic, re-learns on the next render, documented at the write site); one anomalously high PR number widens the column until `wt config state cache clear`; an open Azure DevOps PR still shows gray `NoCI` instead of its pipeline status — a pre-existing gap, now marked `TODO(azure-pr-pipeline)`. Testing: unit tests for `format_cell` (including the Error-with-number and oversized-number link cases)/`pr_ref_width`/ratchet/width estimates; integration coverage for all four forges with real numbers (the Gitea mocks now exercise the number path too), review-state × number composition, the GitLab mr-view-failure `⚠` and branch-pipeline success paths, cache-TTL expiry → refetch, the statusline number view, and the `wt config state` surfaces. > _This was written by Claude Code on behalf of max_ --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
214d82aeda |
Add PR/MR review state to wt list CI status (#3044)
Adds PR/MR review state to `wt list`'s CI status, merged into the existing CI dot color. ## What - New `ReviewState` (`approved` / `changes_requested` / `pending` / `draft`) on `PrStatus`, absent when the forge reports no review signal — branches with no review activity render exactly as before. - **GitHub**: `reviewDecision,isDraft` join the existing single `gh pr list --json` call (no extra API cost). Draft wins over the decision; an empty `reviewDecision` (no required reviewers, no reviews) maps to absent rather than `pending`, so solo repos don't show a perpetual waiting state. - **GitLab**: `draft` + `detailed_merge_status == "not_approved"` → `draft`/`pending`; MR list data carries no approved/changes-requested signal. Gitea/Azure: none. - **Display**: review state merges into the CI dot in `PrStatus::color()` — conflicts (yellow) > changes-requested (**magenta**) > running (blue) > failed (red) > review-required (**cyan**) > base CI color; draft dims like staleness. Cool colors mean waiting (blue: on CI, cyan: on a human), warm mean act; changes-requested outranks running because waiting can't clear it. Magenta was the one color not already carrying a meaning in the list row; cyan is reused from the working-tree symbols column, where the differing glyph disambiguates. - **JSON**: `ci.review_state` in `wt list --format=json`, vocabulary matching Claude Code's statusline `pr.review_state` so the two surfaces agree on names. ## Notes for review - The merge lives in `PrStatus::color()`/`style()` — `CiStatus` stays pure CI (cache values and JSON `status` strings unchanged). Both the table and the statusline render through `format_indicator` → `style()`, so there's a single chokepoint. - Old CI cache files deserialize unchanged (the new field is an `Option`). - `docs/content/list.md` and `skills/worktrunk/reference/list.md` are regenerated from `src/cli/mod.rs`. The HTML and terminal help colorizers learned magenta/cyan (they previously mapped only the five existing colors). Also fixed a pre-existing missing `repo_url` row in the ci-object doc table. - Testing: unit tables for the color merge and both forge mappings; integration snapshots (mocked `gh`) for changes-requested / review-required / approved / draft verifying the exact ANSI codes; the JSON field name is pinned by an inline snapshot. If this change is bad, it's most likely because the dot now encodes two axes (CI health and review attention) in one color — a consumer who reads green as "CI passed" still gets that, but red-vs-magenta now distinguishes who rejected the branch, which is more vocabulary to learn. The `--format=json` output keeps the axes separate for anyone who wants their own logic. Ref #2950 > _This was written by Claude Code on behalf of max_ Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
371d286628 |
refactor(hooks): always-lazy template expansion for hooks and aliases (#3042)
Hook templates expanded eagerly at prep time unless they referenced `vars.*` (#1840); aliases were unconditionally lazy. The dual representation (`expanded` + `lazy_template` on `PreparedCommand`) put a mode choice in front of every consumer, and the seams produced real bugs (#1855, #1910). The background runner also re-rendered eagerly-expanded strings: a latent double-expansion whenever expansion output contained `{{`. This PR unifies on one rule: syntax is validated at preparation (before step 1), templates render when each step runs, and `vars.*` always reads fresh from git config. This is the direction #2128 recorded ("treat hooks as aliases bound to triggers"). **Navigating the diff:** - `src/commands/command_executor.rs`: `PreparedCommand` now carries the raw template plus the frozen context map; JSON is produced only at the process boundary. A `template_name` field keeps the moved errors' text identical to the old prep-time messages. `prepare_steps` validates syntax and freezes context; `resolve_command_str` renders at execution; `map_config_steps` is the structural walker shared with aliases. - `src/commands/alias.rs`: alias prep shares the walker. The residual fork is genuinely alias-specific: context construction (filtered to referenced vars, `{{ args }}` injection) and labels. A prep-time syntax check turned out to be dead code (the arg-routing parse already aborts on syntax errors), so `referenced_vars_for_config` now produces the rich `TemplateExpandError` instead. - `src/commands/hooks.rs`: the background spec always ships raw templates; the runner renders each step (it already did for vars steps). - Dry-run paths (`wt hook --dry-run`, `wt config alias dry-run`) share `render_template_preview`: vars-referencing templates shown raw after a syntax check, everything else rendered at display time. **Behavior changes** (each pinned by a test or snapshot): 1. A foreground pipeline whose step N has a semantic template error (undefined variable) runs steps 1..N-1 before failing, matching what vars-referencing steps already did. New test: `test_foreground_pipeline_undefined_var_runs_earlier_steps`. 2. A semantic error in a background hook template no longer fails the foreground command; it surfaces in the runner log. Syntax errors still abort at prep. New test: `test_background_hook_undefined_var_fails_in_runner`; `test_merge_drops_pending_hooks_when_post_merge_fails` switched to a syntax-error fixture since that is what now reaches its subject (the announcer Drop-flush). 3. `-v` no longer prints the parent-side rendered command for background hooks (rendering happens in the runner). The variables table remains and `wt hook <type> --dry-run` previews commands; help text updated. Three snapshots changed by exactly this. 4. Announce gutters for vars-referencing foreground steps show the rendered command instead of the raw template. 5. Syntax errors caught by the arg-routing parse (aliases, `wt hook` `--KEY=VALUE` routing) render as the rich template error with the source-line gutter, naming the alias or hook. `ApprovedHookPlan` freezing, approval semantics (keyed and displayed by template), config file format, and CLI flags are unchanged. **Testing:** full local gate green (3917 tests + lints). The deferral semantics in changes 1 and 2 previously had no coverage; both are now pinned. > _This was written by Claude Code on behalf of max_ Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
19aa6f0a28 |
feat(config-state): add cache subcommand, gate clear behind confirmation (#3027)
`wt config state` had a sprawl of per-category subcommands, several of which only really existed to be cleared. This consolidates the regenerable caches under one `cache` subcommand, makes the destructive `clear` confirm first, and deprecates the now-redundant subcommands without breaking them. ## What changed **New `cache` subcommand** — the single home for everything regenerable: CI status, summaries, git-commands (the SHA-keyed disk caches), hints, and the `wt switch -` target. `wt config state cache` shows them (`--format=json` too); `cache clear` drops them all with **no prompt**, since clearing only forces recomputation. It re-shows one-time hints and forgets the switch-back target — both repopulate on their own. **`clear` now confirms** — `wt config state clear` still removes *everything*, but it also wipes hand-authored markers and vars, so it now prompts before doing so. `--yes` skips the prompt. Non-interactive without `--yes` declines safely (no data loss) — the conservative direction. This is the one behavior change to watch: a script doing `wt config state clear` without `-y` now cancels instead of wiping. **`ci-status` / `hints` / `previous-branch` deprecated** — hidden from `--help` and emitting a one-line stderr deprecation notice, but still fully functional. Existing scripts keep working (the notice is stderr-only, so stdout parsing is unaffected); new users only discover `cache`. ## Navigating the diff - `src/commands/config/state.rs` — the bulk. `handle_state_clear_all` is refactored to compose per-category `clear_*_reported` helpers + the confirmation gate; `handle_cache_clear` reuses the regenerable subset. `cache get` and the aggregate `state get` share extracted `render_*_section` / `*_json` helpers, so the two views render each category identically. The module docstring documents the regenerable-vs-authoritative split and the preserved `get`↔`clear` parity invariant. - `src/cli/config.rs` — the `Cache` variant + `CacheAction`, `hide = true` on the three deprecated subcommands, and the doc/help text. - `src/main.rs` — dispatch: the `cache` arm, the deprecation warnings (now uniformly bolding the replacement command across all four `wt …` deprecation messages), and threading `--yes` into `clear`. ## Testing Well covered: new integration tests for `cache get`/`cache clear` (empty and populated), the `clear` prompt (accept and decline paths), authoritative-state preservation under `cache clear`, and the deprecation warnings — plus the existing aggregate `state get`/`clear` suite, which exercises the shared helpers. Docs regenerated and `test_docs_are_in_sync` passes. Full pre-merge gate (tests + clippy + fmt) green. > _This was written by Claude Code on behalf of max_ --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6df9623e23 |
docs(state): distinguish reading the cache from resolving it (#3028)
Documentation follow-up to #3024 (which made `wt config state get` read the default-branch cache read-only). After that change the dump shows `DEFAULT BRANCH (none)` right after a `clear`, while `wt config state default-branch get` still returns the resolved branch. That contrast can read as inconsistent. Two small additions make the model explicit: - The `default-branch` `--help` gains one line: `default-branch get` resolves and caches; the aggregate `wt config state get` only reports the cache, so it can show `(none)` until something populates it. - The `state` module spec gains a "Reading vs resolving" section recording the invariant for future developers: the aggregate `get` inspects read-only (`cached_default_branch()`, `CachedCiStatus::list_all`), while a per-key `get` for a derived value resolves it (`default_branch()`, `PrStatus::detect`). No behavior change. The `docs/` and `skills/` mirrors and the `--help` snapshot are regenerated from the source. > _This was written by Claude Code on behalf of max_ Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5da0d2c3e4 |
docs: alias template-expansion timing; fix narrow help-test env redaction (#3006)
## Docs: alias template-expansion timing
The main addition documents a gotcha that's easy to hit and hard to
diagnose: an alias body renders its `{{ … }}` once at dispatch, in the
invoking worktree, so a per-worktree variable like `{{ branch }}` is
baked to a single value *before* a nested `wt step for-each` / `wt
switch --execute` iterates — printing the same value in every worktree.
The fix is `{% raw %}…{% endraw %}` deferral (plus a quoted `sh -c '…'`
for `for-each`, since the deferred `{{ branch }}` contains spaces).
- `extending.md` — rewrote "Deferring expansion to a nested `wt`
command" around the `for-each` symptom; improved the `up` rebase recipe.
- `faq.md`, `troubleshooting.md` — symptom-first entries with `wt config
alias dry-run` as the diagnostic.
- `hook.md` / `config.md` / `step.md` — distinguish repo-level
(constant) vs per-worktree (active) variables; note `{{ default_branch
}}` needs no deferral; cross-link the `{{ default_branch }}` variable vs
the `wt config state default-branch` shell command.
## Factual corrections
- `integration_reason` JSON values are hyphenated (`trees-match`,
`no-added-changes`, `merge-adds-nothing`) — the docs had underscores.
Verified against `src/commands/list/model/state.rs`.
- `SKILL.md`: 7 → 10 hook types (5 events × pre/post), added an
aliases/multi-worktree task section, fixed stale anchor links.
## Test fix: narrow help-test env redaction
`test_help_list_narrow_terminal` built its own `insta::Settings` but
skipped `add_standard_env_redactions` (every other help snapshot routes
through `snapshot_help`, which calls it). Its snapshot env block
therefore leaked host-specific paths (`LLVM_PROFILE_FILE` = the
machine's temp dir, plus the `WORKTRUNK_*` paths), which churn whenever
the snapshot is regenerated on a different machine. Adding the one call
mirrors `snapshot_help` and makes the snapshot reproducible.
Worth noting (and a candidate follow-up): this gap was masked under
`cargo test` (libtest) because the `repo` fixture's `mem::forget`'d
`bind_to_scope()` guard leaks redaction settings across the shared
process's reused threads. Under nextest (process-per-test, what the
pre-merge hook uses) there's no leak, so a test missing its own
redactions is exposed. A few other help tests (`test_help_md`,
`test_version`, `test_nested_subcommand_suggestion`) have the same gap
and could be consolidated through one settings helper — left out of this
PR to keep it focused.
> _This was written by Claude Code on behalf of max_
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
77f2f5a405 |
fix(help): render inline code in --help section headings (#3003)
Terminal `--help` applied heading styling to the raw header text without
stripping inline-code markers, so a heading authored as `` ### `--stage`
`` rendered with literal backticks. Header rendering now reduces inline
`code` and links to plain text, letting the heading's single uniform
style (color/weight) cover the whole line. A dim ANSI reset inside a
colored heading would otherwise terminate the heading color partway
through — visible on the `wt config state logs` heading `Command log
(`commands.jsonl`)`, where the trailing `)` follows the code span. That
heading is now fixed too.
Separately, renamed the `--stage` / `--dry-run` subsection headings in
`wt step commit` and `wt step squash` to sentence case ("Staging", "Dry
run"). They were the only flag-literal, inline-code headings in the
entire help system; every other heading uses sentence-case topic
wording, which the `writing-user-outputs` skill also prescribes. The
flag name is still surfaced by the clap `Options:` block and each
section's usage example.
The markdown source feeds three render contexts (terminal `--help`, web
docs, skill mirrors); only the terminal path was wrong. Doc mirrors
regenerated via `test_docs_are_in_sync`.
> _This was written by Claude Code on behalf of max_
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
293b44245c | docs(faq): add entry on moving uncommitted changes to a new worktree (#3002) | ||
|
|
b41e210f82 |
feat(step): add --branch arg to wt step diff (#2995)
## Problem `wt step diff` could only diff the current worktree. #2994 (part of the effort to extend `--branch` to more commands) asks for a `--branch` flag so the diff can target another worktree's branch without leaving the current one. ## Solution Add `-b/--branch` to `wt step diff`, mirroring the existing flag on `wt step commit`. When provided, the repo is rooted at that branch's worktree (via `worktree_for_branch` + `Repository::at`) so both the diff and its target/merge-base resolution operate there. The branch must have a checked-out worktree; otherwise it errors with `no worktree for branch '<b>'`. ## Testing Two integration tests in `tests/integration_tests/step_diff.rs`: - `test_step_diff_branch_arg` — runs `wt step diff --branch feature` from the main worktree and asserts the output matches `test_step_diff_committed_changes` (the same diff run from inside the feature worktree). - `test_step_diff_branch_no_worktree` — asserts the error path for a branch without a worktree. Docs regenerated via `test_docs_are_in_sync`; `cargo clippy --all-targets` and `cargo fmt --check` are clean. The only failing tests locally are the `case_4` nushell shell-wrapper tests, which fail because `nu` isn't installed in this sandbox — unrelated to this change. --- Closes #2994 — automated triage --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
b75b3cf1a7 |
fix(shell): install nushell wrapper to vendor-autoload dir (#2878) (#2992)
Fixes #2878. Implements the approach agreed in the issue: query `$nu.vendor-autoload-dirs` directly and install to its last (user-writable) entry, with cross-platform tests and stranded-file cleanup on install/uninstall. ## The bug The nushell integration wrote `wt.nu` to `$nu.default-config-dir/vendor/autoload`, which Nushell **never autoloads**. It happened to work on macOS/Windows because there `$nu.default-config-dir == $nu.data-dir`, so the wrong subpath resolved to the real vendor dir; on Linux the wrapper was written but silently never loaded — so `wt` was never wrapped. Verified against `nu` 0.113.1: `$nu.vendor-autoload-dirs | last` is `<data-dir>/vendor/autoload`, and `$nu.default-config-dir/vendor/autoload` is in neither `$nu.vendor-autoload-dirs` nor `$nu.user-autoload-dirs`. ## The fix - **Resolve the install target from `$nu.vendor-autoload-dirs | last`** (one memoised `nu` spawn that also reads `$nu.default-config-dir` for legacy cleanup). - **Fallback when `nu` isn't on PATH** mirrors nushell's own `nu_path::data_dir`: `XDG_DATA_HOME` (when absolute) wins on every platform, otherwise `dirs::data_dir()` — matching nushell on Linux/macOS/Windows. - **Wrapper-based shells now always write to the canonical location** (`paths.first()`), so a wrapper stranded at a legacy path can't become the write target. (No-op for fish, which has a single canonical path.) - **Stranded-file cleanup**: install writes the correct path and removes any worktrunk-managed wrapper left under the legacy `<config-dir>/vendor/autoload` locations; uninstall already iterates every candidate, which now includes those legacy paths. Legacy files are only removed when they carry the `# worktrunk shell integration for nushell` header, so a user's own `wt.nu` is never touched. - `config_line`, the `wt config shell init` manual-setup help, and the FAQ/shell-integration docs now use `$nu.vendor-autoload-dirs | last | path join wt.nu`. ## Tests - New `WORKTRUNK_TEST_NU_VENDOR_AUTOLOAD_DIR` override pins the install dir so the install/uninstall/cleanup tests are **deterministic on Linux, macOS, and Windows** and don't depend on `nu` being installed. - New `test_nushell_install_target_is_a_vendor_autoload_dir` (gated on `shell-integration-tests`, where CI has `nu`) runs the **real `nu` flow** and asserts the wrapper worktrunk wrote is a member of `$nu.vendor-autoload-dirs`. This fails against `main` (the old config-dir path is in neither autoload list) and passes with the fix. - New `test_nushell_install_cleans_stranded_legacy` and a reworked `test_uninstall_nushell_cleans_all_candidate_locations` cover the migration cleanup. - Existing nushell tests updated to the data-dir path; full suite green (`cargo test --lib` + `cargo test --test integration`, and the nushell subset with real `nu` under `--features shell-integration-tests`). ## Not in scope "Not best practice" cleanup flagged in the issue (the overlap between the candidate-building helpers, and the dubious Windows branch) — the helpers here are restructured around the autoload model, but a broader tidy can be a follow-up. 🤖 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.8 <noreply@anthropic.com> |
||
|
|
3a4ae84715 | Expose commit bodies to squash templates (#2983) | ||
|
|
3287052b5b |
chore(demos): drop the obsolete CLAUDECODE= env from the shared tape (#2980)
Follow-up to #2979. The shared demo tape set `Env CLAUDECODE ""` so that a `claude` invoked while recording would not be rejected as a nested session. Claude Code no longer blocks nested `claude -p`, and #2979 dropped the matching workaround in commit generation, so the override is unneeded. It is not part of the recorded output, so the GIFs are unchanged; no re-record. > _This was written by Claude Code on behalf of max_ Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7d16e63fad |
refactor(commit-generation): drop the CLAUDECODE= nesting workaround (#2979)
worktrunk stripped `CLAUDECODE` before spawning the
`[commit.generation]` LLM command, and the recommended Claude Code
command carried a leading `CLAUDECODE=`, both to get past Claude Code's
nested-session check that rejected `claude -p` launched from inside
another Claude Code session.
That check is gone. It's absent from the `claude` binary across every
current build (2.1.157 through 2.1.162), and `CLAUDECODE=1 claude -p …`
runs cleanly (exit 0, empty stderr). So the workaround is no longer
needed.
This drops it: `src/llm.rs` no longer calls `.env_remove("CLAUDECODE")`,
and the recommended command loses the `CLAUDECODE=` prefix in the source
of truth (`dev/config.example.toml`), the `wt config --help` text, and
the Taskfile bench command. Docs, the skill reference, and the config
help snapshots are regenerated to match.
Caveat: there's no published guarantee the nested-session check stays
gone, and a Claude Code old enough to still have it (older than roughly
late May 2026) would again block nested commit generation. Since the
check is absent from the implementation across all current builds, that
risk is low.
Tests: 1236 lib + 693 integration pass; fmt and clippy clean.
> _This was written by Claude Code on behalf of max_
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
2dd5d60902 | docs(switch): give PR links equal billing with pr:N (#2970) | ||
|
|
d65d5028fa | docs(commit-generation): bump Codex model to gpt-5.4-mini (#2949) | ||
|
|
ca91420e1e |
fix(bare-repo): UX fixes for worktree-path prompt and wt config create --project in bare repos (#2951)
|
||
|
|
51e32f4425 |
fix(statusline): space the context gauge emoji from its percent (#2944)
The context gauge in the Claude Code statusline rendered the moon-phase emoji flush against the percent (`🌕42%`). Most terminals draw emoji as double-width and bleed the glyph into the cell to its right, so the moon visually collided with the digits. This adds a trailing space — `🌕 42%`. The flush-packing convention still holds for ASCII and narrow markers (`@+1`, `↓1`, `●`); emoji are the exception. That distinction is now documented in the `writing-user-outputs` skill so future output keeps it consistent. Tests, the three inline statusline snapshots, nine rate-limit `.snap` files, and the hand-authored docs example (auto-synced to the skill reference) are all updated. > _This was written by Claude Code on behalf of max_ Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2617348b22 |
docs: writing-prose cleanup (faq, config, list, remove) (#2925)
Continues the writing-prose pass on the remaining doc-site pages — the items deferred at the end of #2922. **`faq.md`** — dropped "Worktrunk creates files in four categories." scaffolding; the four H3s below (Worktree directories, Config files, Shell integration, Metadata in .git) make the count self-evident. **`config.md`** (edits in the `Config` command's `after_long_help` in `src/cli/mod.rs`) — four small fixes: - Replaced the "For context:" three-bullet preamble in *User project-specific settings* with a direct lead sentence. Side benefit: fixes the "User configs _also_ has" grammar bug. The new sentence avoids second-person ("for you") and third-person addressing ("for the user") per the writing-prose indicative-mood rule. - Dropped "also" from the system-config sentence — it was the only signal the sentence was an orphan footnote relative to the table above. - Dropped "Similarly," before the first-commit-prompt sentence; the parallel "On first run … On first commit …" structure carries the relation. - Dropped "Note the single underscore after `WORKTRUNK` and double underscores between nested keys." that restated what the env-var table already showed. **`list.md`** (edits in the `List` command's `after_long_help`) — folded the three-dot diff detail into the `main…±` column description; tightened the remaining footnote to just the label-stays-main point. **`remove.md`** (edits in the `Remove` command's `after_long_help`) — extracted the cap-detail appendix from the "Patch-id match" bullet, which had four sentences while the surrounding five bullets averaged one or two; it now sits as its own paragraph. Also dropped the two sentences in *Force flags* that inverted the force-flags table just above; only the new `--no-delete-branch` note remains. **Skipped** (re-reviewed and judged not worth changing): - `switch.md` fork material — mechanism + naming-rule, not duplication. - `step.md` mixed-shape operations list — the asymmetry signals which subcommands have subdoc sections. - `step.md` "How it works" subsections — useful reference content, not internal commentary. - `step.md` `wt step promote` opener — opinionated voice framing. - `step.md` `wt step tether` "Why" — now the canonical place for the leakage rationale (the tips-patterns dup was already removed in #2922). Auto-synced skill mirrors (`skills/worktrunk/reference/`) and regenerated help snapshots carry the same edits. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
456c8506ea |
feat(switch): accept forge PR/MR URLs anywhere pr:N / mr:N works (#2898)
|
||
|
|
3be40ad36f |
docs: writing-prose cleanup (claude-code, hook, llm-commits, tips-patterns) (#2922)
Targeted writing-prose cleanup across four docs pages. Per-page summary: **`claude-code.md`** — dropped the duplicate skill definition (the lead paragraph already covered it) and rewrote the `## Configuration skill` opener as "With the `/worktrunk` skill, the agent can help with:" so the section stands without depending on its heading. **`hook.md`** (edits in the `Hook` command's `after_long_help` in `src/cli/mod.rs`) — four small fixes: - Dropped "As usual, post-* hooks run in the background" (already established earlier on the page). - Split the perspective+cwd run-on paragraph; the three `cwd ≠ worktree_path` cases (`pre-switch`, `post-remove`, `post-merge` with removal) are now a bulleted list. - Folded the semicolon-spliced conditional-variables enumeration into the existing template-variables table descriptions (added "switch/create only" to `base`, "when target has a worktree" to `target_worktree_path`, hook-type qualifiers to `pr_number`/`pr_url`). The follow-on guidance about undefined variables and conditionals/defaults stays. - Trimmed the redundant `sanitize` sentence from the filters paragraph (the table above already says it). The two hook-types tables (event×pre/post matrix + per-hook purpose) are intentionally kept — they're complementary, not duplicate. **`llm-commits.md`** — dropped the "How it works" stub that mostly restated the lead, and the "There are sensible defaults, but templates are fully customizable" hedge. **`tips-patterns.md`** — three trims: - The `wt step tether` recipe's middle "This matters because…" sentence (covered by tether's own docs). - The ports-deterministic line tightened to lean on the concrete example rather than restate the abstract claim. - "in real-time" filler dropped from the Monitor hook logs section. Auto-synced skill mirrors (`skills/worktrunk/reference/*.md`) carry the same edits. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
21e0b27e49 |
refactor(verbose): rename output.log → subprocess.log; -vv keeps Info on stderr (#2913)
## Motivation The `-v` / `-vv` UX had three small issues that compounded: 1. **`output.log` is misnamed.** It holds the *uncapped raw stdout/stderr of every subprocess `wt` spawns* — multi-MB possible (`git log -p`, patch-id pipelines, etc.). "output" reads as "stuff `wt` printed" — the small thing — when it's actually the big thing. Easy to misread. 2. **`-vv` went fully dark on stderr.** PR #2892 moved the noisy debug pipeline to files at `-vv`; in the process, the stderr layer was disabled entirely. Users running `-vv` to see hook output (info-level, which `-v` shows on stderr) suddenly couldn't. 3. **`-v` help text was a 150-char one-liner** packed into a parenthetical, and the surrounding docs leaned on a "stderr stays readable / `log::*` pipeline" framing that was Rust-jargon-flavored and implied stderr-quiet at `-vv` — which is no longer true after change #2. ## Change - **Rename `output.log` → `subprocess.log`.** Filename now matches content. `OUTPUT` static → `SUBPROCESS`, plus the related `OutputMakeWriter` / `OutputFileFormat` / `build_output_layer` symbol renames. - **`-vv` keeps the Info baseline on stderr.** `build_stderr_layer` no longer returns `None` at `-vv`; debug-level records still route to file layers only, so the terminal stays readable while info-level status (hook output, template variables, the `Tracing to ...` pointer) shows the same as at `-v`. - **`-v` help text rewritten** to describe both levels cleanly without a wall of detail. - **`docs/content/faq.md` gets a "What does -v / -vv do?" section** with a three-level table. - **Docs cleanup**: drop "stderr stays readable" / `log::*` jargon / "but not subprocess.log" negative framing from user-facing prose. ## Notes for review - The only `log::info!` site in the codebase is `commands/picker/mod.rs:389` (a single picker error message), so making `-vv` show info-level on stderr doesn't add meaningful noise. - `test_vv_log_pipeline_silent_on_stderr` is renamed to `test_vv_debug_pipeline_silent_on_stderr` — its assertions only check debug-level records stay out of stderr (they do); the old name implied the whole `log::*` pipeline was silent, which was never quite true (direct `eprintln!` always showed) and is less true now (info-level routes to stderr). - 67 of the 69 changed files are snapshot updates (help text and one diagnostic snapshot) and auto-synced doc/skill mirrors. `git diff --stat -- 'tests/snapshots/*' 'docs/content/*' 'skills/worktrunk/reference/*' | tail -1` separates them. - CHANGELOG: not touched. The historical entry that introduced `output.log` (`#2201`) stays accurate for its release; this rename gets a new line in the next release. ## Tests 3870 tests pass. Re-snapshotted all `test_help_*` snapshots, three `step_alias` snapshots that quote the global help, and the diagnostic file format snapshot. > _This was written by Claude Code on behalf of max-sixty_ Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
1efbcdd718 |
docs(extending): writing-prose cleanup (#2912)
Two-pass writing-prose cleanup of `docs/content/extending.md`. **First pass** (em-dashes, mostly mechanical): replaced 16 em-dashes with colons, parens, or sentence breaks; flattened a few nested asides; reworked the interface-differences table cells that relied on inner em-dashes. **Second pass** (concision and consistency): - Made the three intro paragraphs parallel — each says what the thing is and how it's defined. Hooks and aliases both end with "Defined in TOML"; custom subcommands describe the `wt-foo`-on-`PATH` mechanism. The previous text said TOML for hooks, omitted it for aliases, and called out "No configuration needed" for subcommands. - Dropped the `[[block]]` pipeline syntax mention from the shared-features paragraph. It's a sub-feature, not orientation-level content, and the reader hasn't seen a hook yet. Block pipelines are still covered in the Multi-step pipelines section where they're actually relevant. - Trimmed the Hooks section opener so it doesn't restate the intro's "Hooks are shell commands…". - Cut "TOML forms, template variables and filters" from the `wt hook` link blurb (covered elsewhere on this page and on the linked page). - Cut the four `same / same / same / same` repetitions from the Templates list. - Dropped the duplicate resolution-order rule and the template-variables sentence from the Custom subcommands section (both already in the comparison table). - Collapsed the Reference intro paragraph to "Aside from the differences below, hooks and aliases behave the same." The skill mirror (`skills/worktrunk/reference/extending.md`) regenerates from this via the existing sync test. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a2e95dbf6b |
refactor(prune): stream removals, skip unapproved hooks instead of prompting (#2908)
## Summary Three changes to \`wt step prune\`, all pointing the same way — let the scan run end-to-end and react to each result as it arrives. - **Removability uses the same gate as \`wt remove\`.** Dirty / locked / primary worktrees flunk \`prepare_worktree_removal\` and drop out silently, before the age check. A young + dirty + integrated worktree (HEAD == default branch, working tree modified) no longer surfaces as \`Skipped (younger than 1d)\` — the dirty tree would have blocked removal anyway. The \`(younger than X)\` message now fires only when the worktree would actually have been pruned. - **Scanning streams instead of batching.** One parallel pass per check item bundles integration + removability + age in a single rayon worker. Results flow through a channel; the main thread calls \`try_remove\` immediately for positives instead of waiting for the full scan to finish. Removals overlap with continued scanning (brief check-lock pause per removal for the Windows \`.git/config\` race). - **Hook approval moves out of the way.** Prune never prompts inline — that would deadlock against the streaming structure. With \`--yes\`, every project command is auto-approved. Without \`--yes\`, already-approved commands run; a candidate whose hooks include any unapproved project command is SKIPPED with \`(approval required)\` plus a hint pointing at \`wt config approvals add\` (or \`wt -C <wt> remove\`). Unapproved hooks never run silently. New \`HookPlan::unapproved_project_commands\` exposes the templates that gate the approval decision. ## Test plan - [x] \`cargo run -- hook pre-merge --yes\` (3867 tests, lints, all green) - [x] \`test_prune_pre_remove_needs_approval\` updated to assert skip-with-hint (not abort) - [x] \`test_prune_unmerged_pre_remove_is_not_approved\` still passes — unmerged worktrees are silently filtered before the approval check, no spurious prompts - [x] \`test_prune_non_current_removal_does_not_approve_post_switch\` still passes — only Current candidates check post-switch - [x] \`test_prune_runs_pre_remove_hook\` (\`--yes\`) still passes — auto-approval path runs hooks in full - [x] Removed \`test_approval_prompt_prune_decline\` (PTY-based, asserted the old prompt-and-decline path that no longer exists) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
e22ed71282 |
feat(statusline): add Claude Code rate-limit pace segment (#2899)
## Motivation
Claude Code exposes
`rate_limits.{five_hour,seven_day}.{used_percentage,resets_at}` on its
statusLine JSON. Without UI for it, users only learn they're over-pacing
once Claude throttles them mid-task. We want a single-glance warning
that surfaces *before* throttling, and only when continuing at the
current pace would actually blow the cap — not whenever raw `u/t > 1`,
which fires constantly on early-window bursts.
## Change
A new yellow segment `1.3×pace(10am–3pm)` appears in the
`--format=claude-code` statusline when either rate-limit window is
meaningfully likely to be hit before reset. For the weekly window it
formats as `1.9×pace(Mon–Mon 3pm)`. Only the worse-projected of the two
windows is shown; the segment is hidden entirely when both are safe.
The trigger is a Bayesian forecast on `P(final ≥ 100%)`: cumulative
consumption modelled as Brownian-with-drift, prior on the long-run rate
pulled toward "most windows finish under" (`m₀ = 0.8`), posterior
predictive Gaussian. Segment appears only when `P > 50%`. Early-window
noise (e.g., 5% used at 3% elapsed → naive `u/t = 1.67` but P(over) =
42%) stays correctly hidden.
The *displayed* number is the naive `u/t` ratio (one decimal), not the
Bayesian posterior — the user sees an honest measurement of "what you've
actually done over the elapsed window," while the smoothing stays
internal where it decides visibility. Window bounds in parentheses
locate which 5h or weekly window the pace was measured over.
Adjacent delimiter cleanup in the same file, so the new segment fits the
existing line:
- Drop the vestigial `| ` prefix on the model segment — the two-space
inter-segment separator already does the job
- Drop the space inside `🌕 42%` → `🌕42%`, matching the other prefix-char
segments (`@+1`, `↓1`, `?^|`)
- En-dash inside the parenthetical (`10am–3pm`, not `10am-3pm`) —
typographically correct for a range, visually distinct from the ASCII
`-3` used for line deletions
## Tests
Eight file snapshot tests cover hidden/well-paced, 5h binding, the `:mm`
time branch, 5h at-limit edge, 7d binding (day+time form),
both-windows-pick-worse, dirty-worktree combined state, and narrow-width
segment drop. Time and timezone are pinned via the existing
`WORKTRUNK_TEST_EPOCH` env hook plus a per-subprocess `TZ=UTC`.
23 new unit tests: erf reference-value accuracy, standard-normal CDF,
`p_over` boundaries + monotonicity + six Python-prototype calibration
points, `format_clock`, `format_window_bounds` for both window kinds,
`select_binding_window` selection logic across five scenarios,
`format_rate_limit_segment` shape, and `parse_rate_limits` JSON parsing
across four cases.
|
||
|
|
a50785f70a |
refactor(log): route -vv log pipeline to trace.log instead of stderr (#2892)
## Motivation `wt … -vv` produced ~15K lines of stderr per invocation — unreadable in scrollback, forced redirect to a file, at which point the parallel `trace.log` mirror introduced by #2201 was redundant noise. The earlier split (#2201) only file-routed the *uncapped raw subprocess bodies* (`SUBPROCESS_FULL_TARGET`); everything else — `$ cmd` headers, `[wt-trace]` spans, bounded subprocess previews — stayed on stderr. ## Change At `-vv`, the `log::*` pipeline routes to `.git/wt/logs/trace.log` instead of stderr. A one-line startup pointer on stderr tells the user where it went: ``` ○ Tracing to ~/.../trace.log (raw subprocess output @ ~/.../output.log) ``` User-facing `eprintln!` output (status messages, template expansions, hints) is **unaffected** — it stays on stderr at every verbosity level. This change governs only the `log::*` macro pipeline. `-v` is unchanged (Info on stderr, no file). `RUST_LOG=debug` without `-vv` is unchanged (Debug on stderr, no file — the documented fallback from #2201). Result on `wt list -vv`: stderr 15K → 3 lines. `trace.log` gets the full ~1K-line debug trace as before. ## Implementation notes for review - `src/log_files.rs::route()` is now the only place that picks the sink. Non-`FULL` targets go to `File(&TRACE)` when `TRACE.is_active()`, else `Stderr`. Format closure in `src/main.rs` simplified to a `match route` — no more "mirror to TRACE then write to stderr" branch. - `Repository::current()` is primed *before* `env_logger.init()` so the rev-parse fired by `log_files::init` is a memory-cache hit. Records emitted during priming go to a not-yet-installed logger and are dropped — robust against future `Repository::current` emissions. A `Ctrl-C` during the ~5ms priming window can leak one `$ git rev-parse` line; documented inline, cheaper than violating "all commands through `Cmd`". - `announce_trace_destination()` handles the rare split-init case where `output.log` open fails (path-type mismatch, fs quota) but `trace.log` succeeds — partial pointer + "output.log unavailable" hint. New regression test `test_vv_pointer_handles_split_init` reproduces the failure with a pre-existing directory at the `output.log` path. - `SUBPROCESS_TERMINAL_TARGET` → `SUBPROCESS_BOUNDED_TARGET`. The "terminal-safe" framing no longer fits now that the bounded preview lives in `trace.log` rather than stderr. - `diagnostic.md` doc cutover: three surfaces (`src/diagnostic.rs`, `src/cli/config.rs`, `docs/content/faq.md`) still claimed "written when warnings occur" — the code has no warning gate and writes on every `-vv`. Docs cut over to match reality. ## Deferred follow-ups Surfaced during review but out of scope here: - **Unify `trace.log` + `output.log`** — the bounded preview at `-vv` duplicates a subset of `output.log`'s uncapped bytes. The dual-file design was explicitly approved for this PR; a follow-up could collapse them with `diagnostic.md` doing the bounding at extraction time. - **`RUST_LOG` precedence at `-v`** — pre-existing: `-v 0` honors `RUST_LOG`, `-v` and `-vv` hardcode the level. Needs a policy decision (always-honor / always-ignore / merge) more than a cleanup. - **`tracing` crate migration** — deserves its own dedicated PR with `[wt-trace]` migration as the headline. ## Tests 3822 tests pass (+1 regression). Notable changes: - `test_vv_bounded_on_stderr_full_in_output_log` → `test_vv_log_pipeline_silent_on_stderr` — inverted assertions (marker must NOT appear on stderr; must appear in `trace.log`). Added a stderr-pointer presence check. - `test_vv_pointer_handles_split_init` — new; reproduces the split-init asymmetry by creating a directory at `output.log`'s path, asserts the partial pointer fires and `trace.log` still works. > _This was written by Claude Code on behalf of max-sixty_ --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
818aef5c5c |
feat(prune): bump default --min-age from 1h to 1d (#2886)
## Summary - Raise the default `--min-age` guard in `wt step prune` from `1h` to `1d`. - A worktree just created from the default branch looks "merged" because its branch still points at the same commit; a one-day floor keeps an unattended prune from sweeping it up before its owner has a chance to start work. - Explicit `--min-age=0s` or any other value is unchanged. ## Test plan - [x] `cargo run -- hook pre-merge --yes` (3821 tests + lints green) - [x] `cargo run -- step prune --help` shows `[default: 1d]` - [x] Docs synced: `docs/content/step.md`, `skills/worktrunk/reference/step.md` |
||
|
|
1716ecab37 |
fix(hooks): resolve all hook config from the invoking worktree (#2873)
Worktrunk resolved each hook's `.config/wt.toml` from a different worktree depending on the hook — `post-merge` from the merge target, `post-switch` from the destination, `pre-remove`/`post-remove` from each removed worktree, `wt step prune` from each prunable worktree, and `wt switch --create` from the base ref's *committed* config via `git show`. That last one is the bug behind #2856 and #2818: an uncommitted or branch-local `.config/wt.toml` silently failed to fire creation hooks, and `wt config show` (which reads the working tree) disagreed with what actually ran. This replaces all of it with one rule: **every hook resolves its commands from the `.config/wt.toml` of the worktree `wt` ran in** — the invoking worktree, read from its working tree, the same file `wt config show` displays. ## Behavior changes - `wt switch --create` / `pr:` / `mr:` creation hooks read the invoking worktree's config, so an uncommitted `.config/wt.toml` fires them; the base ref's or PR's committed config is no longer consulted. - `post-merge` runs the feature worktree's config, not the merge target's. - `post-switch` into an existing worktree uses the source, not the destination. - `wt remove <other-branch>` and `wt step prune` use the invoking worktree's config, not each removed worktree's. In the common case — a committed, repo-wide `.config/wt.toml` — these are identical; they diverge only when a branch carries its own working-tree edits. ## For reviewers The module docstring in `src/commands/hooks.rs` is the spec — its per-hook config-source table collapsed to one rule. The change is concentrated in five approval gates that now call `repo.load_project_config()` once instead of `Repository::at(<other-worktree>)`: `merge::approve_merge_plan`, `main.rs`'s `approve_remove`, `step::prune::approve_prune_hooks`, `picker::approved_removal_plan`, and `worktree::switch`. The `switch_hook_project_config` helper and the `base_ref_for_create` / `project_config_at_ref` `git show` machinery are deleted. The *anchor* — the worktree a hook runs in, the executor's plan-lookup key — is unchanged; only the config *source* unifies. The frozen `ApprovedHookPlan` still closes the approval-boundary TOCTOU. ## Testing Hook config-resolution tests across `switch`, `merge`, `remove`, and `step_prune` were rewritten to assert the new rule, each also checking that the non-invoking worktree's config is ignored. `test_post_merge_hook_from_rebased_in_config_does_not_run` is the TOCTOU regression: a `post-merge` that enters the invoking worktree's config only via the rebase, after the gate froze the plan, must not run. Ref #2856, #2818. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9e8c028476 |
fix(relocate): atomic no-overwrite rename for --clobber backup (#2865)
`wt switch --clobber` and `wt step relocate --clobber` each had their own logic for backing up a path blocking the target, and the two had diverged. relocate used an `exists()` check followed by `std::fs::rename` — a time-of-check/time-of-use race, since `std::fs::rename` silently replaces an existing file or empty directory on Unix and could destroy a just-created backup. switch (#2849) instead used `renamore::rename_exclusive`, an atomic no-overwrite rename, and counted up through suffixes (`…-2`, `…-3`, …) on a name collision rather than failing. This PR replaces both with one shared helper in `commands::backup`. `back_up_clobbered_path_now` computes the timestamped name and delegates to the suffix-iterating `back_up_clobbered_path`, which moves the blocker with `renamore::rename_exclusive` — atomic and no-overwrite, so an existing backup is never clobbered; a name collision just lands on the next free `-N` name. relocate gains the suffix fallback (previously it failed closed on `AlreadyExists`), the timestamp computation is no longer duplicated across the two call sites, and `renamore::rename_exclusive` now has a single caller. `renamore` is already a direct dependency and keeps the platform FFI and its `unsafe` inside that crate, so worktrunk stays `unsafe_code = "forbid"`. relocate's backup name changes from `.bak-<timestamp>` to the extension-aware `.bak.<timestamp>` form used by switch (e.g. `repo.feature` → `repo.feature.bak.<timestamp>`). The `test_relocate_clobber_error_backup_exists` regression test asserted the old fail-closed behavior and is rewritten as `test_relocate_clobber_falls_back_when_backup_taken`, mirroring the switch test. CLI help and the generated doc mirrors are updated for the new naming and fallback. The shared helper carries the suffix-iteration, collision, and missing-source unit tests (moved from `resolve.rs`); the relocate `--clobber` path keeps its integration coverage. > _This was written by Claude Code on behalf of Maximilian Roos_ --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
61f4efc1e4 |
refactor(cli): consolidate --no-verify deprecation into one path (#2868)
The deprecated `--no-verify` flag was declared as a clap arg five times — across `SwitchArgs`, `RemoveArgs`, `CommitArgs`, `SquashArgs`, and `MergeArgs` — under two field names, resolved through two helpers, with the deprecation-warning string hand-duplicated in two places. This collapses the four bool-resolving commands (`switch`, `remove`, `step commit`, `step squash`) onto a single flattened `HookFlags` args struct with one `resolve()` method, and routes every `--no-verify` deprecation warning — merge's included — through one emitter, so the warning text exists in exactly one place. `wt merge` keeps its own flags: its hooks flag is genuinely tri-state (`Option<bool>`, so config `[merge] verify` still applies) with a positive `--verify` override, which `HookFlags`'s `SetFalse`/default-true shape cannot express. It shares only the warning emitter — forcing it into the struct would need a special case. Behavior is unchanged: `--no-verify` still works as a deprecated alias and emits the same warning under the same conditions. One visible `--help` change, a direct consequence of the shared struct: `--no-hooks` for `step commit`/`step squash` moves under the `Automation` heading, matching where `switch`/`remove`/`merge` already place it. Doc mirrors regenerated. > _This was written by Claude Code on behalf of Maximilian Roos_ Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
89ec4f70fa |
docs(remove): state that --force discards all uncommitted changes (#2869)
The FAQ and the `wt remove --force` help text said `--force` overrides the untracked-files check "for build artifacts". `--force` actually removes a dirty worktree including staged and modified *tracked* files, not just untracked ones (`test_remove_force_with_modified_files`, `test_remove_force_with_staged_files`). On a destructive command, that wording can lead users to consent to more data loss than they expected. Updates the `--force` arg help, the "Force flags" table and example in `src/cli/mod.rs`, and the FAQ to say `--force` discards staged, modified, and untracked files; the `help_remove_long` snapshot and the `remove.md` / `faq.md` doc and skill mirrors are regenerated to match. > _This was written by Claude Code on behalf of Maximilian Roos_ Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
93b441c8f0 |
feat(switch): deprecate shell command lines in --execute (#2852)
## What
A future release will switch `wt switch --execute` (`-x`) to an argv
input model: a single program name, with arguments after `--` passed
verbatim, run via `execvp` with no implicit shell. That is a breaking
change to a protected CLI interface, so it ships as a two-release
deprecation — **this PR is the warn phase.**
`validate_switch_templates` now emits a deprecation warning when the
`-x` value is not a single program token — it contains shell syntax,
multiple words, or `{{ }}` template markup. The hint shows the concrete,
copy-pasteable migration:
```
▲ --execute will change in a future release: it will run a single program,
with arguments after --, not a shell command line
↳ To run this command line unchanged, pass it to a shell:
--execute sh -- -c 'echo hi && ls'
```
`sh` is itself a single program token, so the suggested form works
today, does not warn, and survives the cutover. A single program name —
including a path — stays silent.
The `-x` help examples in `src/cli/mod.rs` move to the argv-compatible
form (`-x code -- '{{ worktree_path }}'`) so the docs no longer
recommend a form that warns.
## Why no PATH check
The classifier is purely structural — it decides whether the value is
one bare program token, nothing more. It deliberately does not check
whether a bare name resolves to a real executable. An earlier iteration
did, to also warn on `-x my-alias`, but a PATH lookup at pre-flight is
environment-sensitive, runs in the source worktree rather than where
`-x` will execute, and cannot distinguish a shell alias from a typo or
an uninstalled tool. A bare alias/function `-x` is left to fail loudly
(`execvp` → `ENOENT`) at the cutover rather than guessed at here. The
warning still catches every multi-word / shell-syntax form, which is
what users actually write.
## Testing
`cargo run -- hook pre-merge --yes` — 3799 tests pass; clippy, fmt, and
pre-commit hooks green. New: a unit test for the token classifier and an
integration test covering the warn and no-warn cases. Most of the
32-file diff is snapshot regeneration — the warning is new stderr output
on existing `-x` tests.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
ec62580c29 |
revert(hooks): keep docs on pre-start/post-start; code accepts both (#2857)
Per @max-sixty's [direction in #2838](https://github.com/max-sixty/worktrunk/issues/2838#issuecomment-4509447593): revert the docs portion of #2840 and keep the code. Docs continue to recommend `pre-start`/`post-start`; both names work in code so anyone who already followed the briefly-changed docs (e.g. @EcksDy) isn't stranded once a release ships these aliases. ## User-visible — back to `pre-start`/`post-start` - README, docs site, skill mirrors, `dev/*.example.toml`, `plugins/worktrunk/README.md`, `flake.nix`, `.config/wt.toml` - `src/cli/mod.rs` / `src/cli/config.rs` / `src/cli/step.rs` / `src/help.rs` after_long_help and example snippets — and the auto-synced `docs/content/` and `skills/worktrunk/reference/` mirrors - `wt hook --help` canonical subcommand names; completion advertises `-start` only - `HookType` Display via strum, serde `rename`, and clap `ValueEnum` name — all `pre-start`/`post-start`. The Rust variant identifiers stay `PreCreate`/`PostCreate` (internal; we already paid for that rename in #2840, and now the eventual flip is a Display-only change) - `HooksConfig` serde canonical fields ## `*-create` still works (kept code) - `wt hook pre-create` / `post-create` — CLI alias on the canonical subcommand - `pre-create` / `post-create` in config: top-level, `[hooks.*]`, and per-project, in string, `[table]`, and `[[array-of-tables]]` form. Mechanism: serde `alias = ...` on the field, plus a silent in-memory rename in `migrate_content()` so the round-trip in `unknown_tree` doesn't flag table forms as schema-unknown. - The pre-0.32.0 `post-create` fatal-load-error machinery stays removed — the name is reclaimed, and both forms load without error. ## Smaller bits - `valid_user_config_keys()` / `valid_project_config_keys()` append `pre-create` / `post-create` so the unknown-field round-trip skips them. `test_valid_*_keys_all_deserialize` skips both aliases (they can't sit alongside the canonical without a duplicate-field error). - `DEPRECATED_SECTION_KEYS` drops the `pre-start`/`post-start` entries #2840 added — `pre-start`/`post-start` are canonical again. - `find_pre_start_from_doc` / `find_post_start_from_doc` / `find_renamed_hook_key` / `is_non_empty_item` / `migrate_start_hooks_doc` and their tests are removed; the migration direction flips via a new `migrate_create_hooks_doc` (silent, mirrors the prior shape). - Test files `e2e_shell_post_create.rs` and `post_create_commands.rs` rename back to `_post_start_` (via `git mv`, so the rename shows as a rename). ## Testing `cargo run -- hook pre-merge --yes` — 3806 tests pass; the 10 failures are all `case_4` of `shell_wrapper::unix_tests::*` (nu-shell case; `nu` isn't installed in this runner; same failures occur on `main`). Also manually verified that a fresh `wt switch --create` against a project config with `[post-create]` loads cleanly with no unknown-field warning and the hook fires as `post-start`. ## Follow-up Per @max-sixty: in a couple of weeks, once a release with both-names-work is out and users have had a chance to upgrade, the docs flip is straightforward (most of it is in `src/cli/mod.rs`'s `after_long_help` and the doc-sync test propagates). Re #2838. Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
ff4e481a4c |
fix(switch): interactive picker runs the full switch pipeline (#2845)
With `[switch] cd = false` or `wt switch --no-cd`, opening the interactive picker (`wt switch` with no branch argument) and selecting a worktree printed the branch name and exited — no switch, no hooks, and `Alt-c` created nothing. #1445 introduced this as an explicit print-only mode, but it surprises anyone who sets `cd = false` as a standing navigation preference (driving worktree switches through tmux or cmux): the picker silently stops doing anything. The picker now runs the same `plan_switch` → `execute_switch` pipeline as `wt switch <branch>`, suppressing only the cd directive. Pre- and post-switch hooks fire, `Alt-c` creates the worktree, and `--format=json` works in the picker too (`requires = "branch"` and the `--branches`/`--remotes` conflict are dropped from the flag). JSON emission is extracted into a shared `emit_switch_json` so the argument path and the picker produce identical output. This is a behavior change: the print-only picker mode is gone. Scripts that captured its bare-branch-name output should switch to `wt switch --format=json`, which both performs the switch and prints a structured `action`/`branch`/`path` result — now shown as a `pick` alias example in tips-patterns. The cmux recipe drops its `pick` alias workaround, since the picker fires `pre-switch` hooks directly. Closes #2837. Thanks to @endigma for the discussion that pinned down the intended behavior: the picker should be identical to passing the branch as an argument. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d7e3f88422 |
feat(hooks): rename worktree-creation hooks to pre-create/post-create (#2840)
Phase 1 of the staged hook rename tracked in #2838: the worktree-creation hooks `pre-start`/`post-start` become `pre-create`/`post-create`. The old names keep working with no deprecation warning yet (Phase 2, months out, adds the warning). ## What changes - `pre-create`/`post-create` are canonical everywhere: the `HookType` enum, the `HooksConfig` serde fields, the `wt hook` CLI, completion, and all docs. - Old names keep working: `migrate_content()` rewrites `pre-start`/`post-start` config keys to `-create` before serde, and `parse_hook_type` accepts the old CLI names as silent aliases. `wt config update` rewrites them on disk; `wt config show` shows the migration diff. `wt hook <type>` execution and `wt hook show` both accept the old names; completion and `--help` advertise only the canonical names. - `detect_deprecations()` flags the old keys so `update`/`show` act on them, but `format_deprecation_warnings()` stays silent (Phase 2 adds the warning). A new empty-warnings guard in `check_and_migrate` keeps a `-start`-only config from emitting a stray hint. - The dead pre-0.32.0 `post-create` machinery is removed: the fatal `POST_CREATE_REMOVED_MSG` load error, the vestigial `HooksConfig.post_create` merge-fold, and `find_post_create_from_doc`. `post-create` is reclaimed as the canonical background creation hook. ## Semantic flip Before v0.32.0, the key `post-create` named a *blocking* hook. It now names the *background* one. Since v0.44.0, a pre-0.32.0 `post-create` config is a fatal load error on the `check_and_migrate` paths: `ProjectConfig::load` and user/system config loading, which fire on essentially every `wt` command. A repo carrying one has been unusable ever since. The one path that skips that check is `project_config_at_ref` (the base-ref read behind `wt switch --create`), which applies only structural migration. A pre-0.32.0 `post-create` surviving solely on a base ref, never checked out into a worktree, would now load as a background hook rather than folding into the blocking `pre-start`. That edge case is accepted: once `post-create` is valid again, reclaiming the name and detecting the dead key are mutually exclusive. ## Reviewing this diff 205 files, but the substance is ~36 files under `src/`. The rest is regenerated snapshots and auto-synced doc mirrors. Start with: - `src/config/deprecation.rs` — detection (`find_renamed_hook_key`), migration (`rename_hook_key`), removal of the fatal block, the empty-warnings guard, and the `DEPRECATED_SECTION_KEYS` entries that stop unknown-field detection from flagging the migrated keys. - `src/config/hooks.rs`, `src/git/mod.rs` — the serde field and enum renames. - `src/config/project.rs` — `ProjectConfig::load` deserializes `check_and_migrate`'s migrated content, so a current-worktree config using the old keys loads into the canonical fields. - `src/cli/hook.rs`, `src/commands/hook_commands.rs`, `src/completion.rs`, `src/main.rs` — the CLI alias layer; `wt hook show` accepts the old type names as hidden value-parser aliases. - `src/cli/mod.rs` — the `wt hook` docs, including the soft-deprecation note linking #2838. The ~93 modified snapshots also pick up deterministic env-block lines (`GIT_*: ""`, `LLVM_PROFILE_FILE`) that pre-existing snapshots already carry. That is stale-snapshot drift surfaced by the regeneration, not a behavior change. ## Testing Full suite green (3799 tests). New coverage: `snapshot_migrate_start_to_create` (migration preserves value shape and position), `test_deprecated_start_hook_key_runs_silently` and `test_standalone_hook_start_alias_runs_silently` (old config and CLI names run with no warning), `test_config_show_displays_start_hook_migration` (`config show` reveals the diff without an "unknown field" warning), and `test_hook_show_accepts_deprecated_start_hooks` (a current-worktree config using the old keys loads, and `wt hook show` takes both the canonical and the deprecated type arguments). Part of #2838. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
af8de75341 | docs: re-add cmux recipe with verified config from @endigma (#2836) |