mirror of
https://github.com/max-sixty/worktrunk.git
synced 2026-09-14 20:00:38 +08:00
codex/remove-codex-cloud-specific-tests
115 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
667c6efaf0 |
docs(switch): note that Alt-x never forces (#3811)
Requested by @max-sixty in [#3809](https://github.com/max-sixty/worktrunk/issues/3809#issuecomment-5273484502) — a couple of words clarifying that `Alt-x`'s removal is safe-only. The keybinding table read `Remove selected worktree/branch`, which doesn't say the removal never forces; that's what sent the reporter looking for a force-remove that isn't there. The picker hardcodes the safe path — [`prepare_removal`](https://github.com/max-sixty/worktrunk/blob/7a2a3e003e7eed138ff5f2dcd2296f6bbd8e86d4/src/commands/picker/mod.rs#L323-L330) passes `BranchDeletionMode::SafeDelete` and `force_worktree: false`. Deliberately scoped to the table cell, per the "(only)" in the request. The bigger question — whether `Alt-x` should ever pass `-D` — is still open on the issue and isn't touched here. Primary source is `after_long_help` in `src/cli/mod.rs`; the three mirrors and the `--help` snapshot are regenerated. Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> |
||
|
|
6d09125b7b |
test: converge suite on semantic boundaries (#3663)
This follows the first test-simplification tranche by converging the remaining suite around distinct semantic and pragmatic contracts rather than raw case count. The branch removes false-confidence tests, invalid setup variants, repetitive snapshots, and expensive PTY overlap while strengthening the retained route, precondition, and interaction proofs. ## What changed - Replace obsolete CI-status integration mocks and blank snapshots with direct provider semantics, mixed-priority cases, and strict GitHub/GitLab route assertions. - Remove free-riding merge, push, remove, list, security, config, and switch cases whose setup never reached the named behavior; consolidate repetitive direct cases into labeled tables. - Reduce the switch picker from 42 PTYs to 19 distinct terminal contracts, using causal release gates for asynchronous loading and repaint behavior. - Add a cached main-only picker fixture, eliminating 138 unnecessary Git subprocesses across the retained PTYs, and integrate it with main's generated hermetic standard fixture. - Tighten test guidance around proving setup preconditions and mock invocation routes, and correct the comments-tab help text and generated mirrors. ## Reviewer map - `tests/integration_tests/ci_status.rs` and `src/commands/list/ci_status/`: provider semantics and route coverage. - `tests/integration_tests/switch_picker.rs`, `src/commands/picker/`, and `src/testing/`: retained PTY contracts, causal mocks, and fixture design. - `tests/integration_tests/config_show.rs`, `src/config/deprecation.rs`, `src/config/expansion.rs`, and worktree type/resolve tests: direct-boundary consolidation. - `tests/CLAUDE.md`: the testing rules extracted from the false-confidence cases found during the survey. The measured loop removed 98 tests, 65 snapshots, and 23 picker PTYs. Controlled warm Nextest execution improved from a 79.593-second mean to 72.574 seconds (8.8%), while comparable production-line coverage moved from 97.32% to 97.23%. The tracked PR diff is a net deletion of more than 5,700 lines. ## Validation - `cargo run -- hook pre-merge --yes` after syncing current `main`: 4,468 passed, one configured skip; docs, doctests, clippy, formatting, policy checks, and snapshots green. - `task coverage` on the completed change before the base sync: 4,465 passed, one configured skip; 97.23% comparable production-line coverage. - Three independent final audits found no remaining lost beliefs, fixture hazards, or safe PTY consolidations. > _This was written by Claude Code on behalf of max_. |
||
|
|
14580de79c |
feat(worktree): accept a worktree path wherever a branch is accepted (#3607)
Follow-up to the [`wt remove <path>` discussion on #3480](https://github.com/max-sixty/worktrunk/pull/3480#issuecomment-5039137116), widened from that one command to the whole surface. #3480 has since landed and is merged in here — its duplicate-checkout warning composes with this: the warning names the shadowed worktrees, and a path is how you then address one. ## Audit Verified against the built binary. wt had three answers to "what does this token mean?": | Route | `@` `-` `^` | worktree path | `pr:N` | |---|---|---|---| | `wt switch` (`resolve_switch_target`) | yes | only if absolute or ≥2 components, and not `--create` | yes | | `wt remove` (`resolve_worktree_arg`) | yes | any token | no | | everything else (raw `worktree_for_branch`) | **no** | **no** | no | Same token, same cwd, two answers: ```console $ wt remove inner # ✓ Removed innerbranch worktree & branch $ wt switch inner # ✗ No branch named inner ``` And outside switch/remove the shortcuts didn't work at all — `wt step diff --branch @` was `✗ Branch @ has no worktree`, while `wt config state marker set --branch @` silently wrote state under the literal key `@`. Separately, wt prints paths as `~/…` but wouldn't accept that form back. ## Change One canonicalizer in the lib, `Repository::resolve_worktree`, absorbing the path fallback that lived in the bin crate's `resolve_worktree_arg` (now deleted). Resolution order is documented once, on that function: `@`, then `-`/`^`, then a branch with a worktree, then a path naming a registered worktree, then the branch alone. **Branch-first, everywhere.** A directory never shadows a branch that shares its name; a path answers only what a branch cannot — a detached worktree, or one of two checkouts of the same branch (#3480's case). The `looks_like_path` shape gate is gone, so a single-component path resolves like any other. Two shapes cover what callers need: `require_worktree` for commands that need a worktree to operate in, `require_selected_branch` for arguments that key by branch. The merge/rebase target validators fall through to the same path lookup, so a target can be named by the worktree it's checked out in. Routed through it: `switch` (including `--base`), `remove`, `step commit --branch`, `step diff --branch` and its target, `step copy-ignored --from`/`--to`, `step promote`, `step relocate`, `config state --branch` (9 sites), and `merge` / `step rebase` / `step squash` / `step push` targets. `resolve_input_path` — already documented as the one resolution point for user-supplied paths — now expands a leading `~`, so the tilde form worktrunk prints is a form it reads back. `~user` stays literal; wt doesn't reimplement that shell feature. ## Documentation A path is an alias, not a second addressing scheme, so it is stated once rather than on every argument: one paragraph in `wt switch`'s help and one sentence on the addressing line in `worktrunk.md`. Argument descriptions still read as branches. The two exceptions are the arguments whose descriptions are already catalogues of accepted forms — `wt switch`'s (`Branch, worktree path, shortcut, or PR/MR URL`) and `wt remove`'s, which has named the path since before this branch. The Worktree Model section of `CLAUDE.md` records which way to document it, so the next argument doesn't grow its own copy. ## Two silent no-ops fixed along the way - `wt step relocate <unmatched>` matched arguments against branch names by string equality, so a typo filtered everything out and the empty result rendered as `○ All worktrees are at expected paths` — a success message for work that never happened. Every way an argument can fail to land on a relocatable worktree now errors, including the detached and prunable cases the new path route makes reachable. - A selector matching nothing was reported as a branch without a worktree, hinting `wt switch <token>` — which creates a worktree only when the branch exists, so for a mistyped path it would just fail again. `WorktreeSelectorNotFound` now says `No branch or worktree named X`; a branch that genuinely exists without a checkout keeps the create hint. ## Testing Full gate green: 4596 tests, lints, docs sync, `--features shell-integration-tests` clippy. `codecov/patch` is 99.25% of diff hit against a 97.93% target. New coverage: - Unit: branch-and-path equivalence, branch-beats-same-named-directory, detached-by-path (and its `require_selected_branch` refusal), shortcuts never treated as paths, branch-only fallthrough, and the two distinct not-found errors. Plus `expand_tilde` round-tripping `format_path_for_display`. - Integration: `switch` by relative/single-component/absolute/tilde path, `--base` by path, `step diff --branch` by path and `@` (asserted equal to the by-branch output), `config state --branch` set via `@` and read via the worktree path, and both new relocate errors. `wt remove`'s resolution is unchanged — it already had this rule; it now shares the implementation. The 106-test `remove::` suite is untouched and green. - Integration: `wt step push <worktree-path>` (the `require_target_branch` half of the target fallback), and `wt step relocate` against a prunable worktree. One diff line is unhit: `expand_tilde`'s fallback when `home_dir()` returns `None`, which has no deterministic trigger. The `@`-resolution backstop in `resolve_worktree` is untested for the same reason — no CLI route reaches it — so it kept its original `match` arm rather than being re-indented into the diff. ## Left out `wt config state default-branch set` and `previous-branch set` take a branch name as a *value to store* rather than a selector, so they still take it literally. > _This was written by Claude Code on behalf of Maximilian Roos_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
bd504f0998 | feat(switch): run --execute against the picked worktree (#3394) | ||
|
|
8e2ab04c7d |
feat(switch): rework the alt-x picker removal — morph in place, decline current worktree, identity cursor (#3262)
Overhauls the `wt switch` picker's `alt-x` (remove) so a removal updates the selected row in place instead of re-collecting the whole list, declines removals that can't safely happen with an explanation, and lands the cursor on the right row afterward — including under an active filter. ## What changes for the user `alt-x` on a row now behaves by what the removal does: - **Unmerged worktree** → the row morphs in place to a `/ branch` row (worktree gone, branch kept); the worktree removal runs in the background. No flicker, cursor stays put. - **Merged worktree** → the row drops; the cursor lands on the row that slid up into its slot. - **Current worktree (`@`), main, dirty, locked** → kept, with the same diagnostic `wt remove` prints (drained to stderr on picker exit) instead of a silent dead keypress or a disruptive cd-home. Before, `alt-x` re-collected the entire list (a visible flicker, cursor reset to top), and removing the current worktree forced a `cd` elsewhere mid-render. ## The cursor fix (last commit) The post-removal reposition scrolled to the removed row's index in the full `shared_items` list. Under a fuzzy query, skim's `item_list` is filtered and reordered, so that index pointed at the wrong row — the cursor jumped +N rows down (N = filtered-out rows above it), compounding on a second removal. Reposition is now by **row identity**: it walks `item_list` to the target row's `output()` token. The drop's landing row (the display-neighbor) is captured before the reload via a native `alt-x` binding (`[capture, reload]`); keep/morph/restore land on their own row's token. ## Reviewing The bulk is `src/commands/picker/mod.rs`. Key pieces: - `invoke` dispatch (`removal_targets_current_worktree` → `worktree_removal_keeps_branch` → `removal_will_remove_target`) decides keep / morph / drop. - `morph_and_remove_in_background` does the in-place row morph (optimistic, reverted if the worktree unexpectedly survives). - `reposition_cursor_action` / `install_remove_keybinding` are the identity-based cursor landing. ## Testing Picker unit tests plus PTY integration tests through real skim, including the new `test_switch_picker_alt_x_lands_on_neighbor_under_filter` (removes a row with filtered-out decoys above it — confirmed to fail on the old index-based reposition and pass on identity). Multi-row cursor tests cover both drop and keep paths. > _This was written by Claude Code on behalf of max_ --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a725a2d786 |
Unify wt switch and --prs: differ only by the extra PR rows (#3252)
Full filter parity (branch, path, PR number/title/author) across worktree and --prs rows via a shared token builder; --prs dedups already-shown branches; worktree match text folds in PR data live as the forge fetch lands. Author plumbed through both forges. |
||
|
|
de97744718 |
feat(switch): picker shortcuts — copy, open, refresh, remap remove to alt-x (#3233)
Adds four keyboard shortcuts to the interactive `wt switch` picker: `alt-y` (copy the selected branch), `alt-o` (open the row's PR/MR), `alt-r` (refresh the list), and `alt-x` (remove, remapped from `alt-r`). `alt-y`/`alt-o` are native skim `Action::Custom` callbacks reading the selected row off `App.item_list` (no reload, so the cursor stays put and `--prs` rows aren't dropped); the row → branch/URL lookup is extracted into `resolve_shortcut_branch`/`resolve_shortcut_url` and unit-tested. `alt-r` refresh re-enters the collect pipeline via a new `PipelineFactory`. `alt-y` no-ops on a detached worktree (no branch). New cross-platform deps: `arboard` (clipboard) and `open` (browser), gated behind the `cli` feature. Merged over `codecov/patch` (89.76% vs 97.61% target) with explicit approval: the uncovered residual is the picker's clipboard/browser/thread-spawn and skim-`App`-bound closures, which a headless CI runner can't exercise. All required jobs green; reviewed and approved by worktrunk-bot. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8f17cb4a91 |
test(switch): run the interactive picker tests on Windows (#3221)
PR #3217 made `wt switch`'s interactive picker run on Windows, but its tests stayed `#[cfg(unix)]` — so the Windows CI leg proved the picker *compiled* without ever exercising its runtime. This ungates them so Windows CI drives the real thing. **What's ungated** (the test infra — `mock_commands`, the ConPTY handling in `tests/common/pty.rs` — was already cross-platform, so this is largely a gate flip): - `switch_picker_dry_run.rs` — the headless dry-run pipeline (collect → render → preview cache → summary spawn → warning drain). - The `--prs` dry-run tests in `switch.rs` — forge fetch + row render via the cross-platform mock `gh`/`glab`. - The two requires-tty snapshots — the no-TTY bail is uniform across platforms. - `switch_picker.rs` — the PTY/vt100 TUI snapshot tests. **Test-quality fix folded in:** the four `--prs` dry-run tests asserted only `output.status.success()`, but the fetch path is fail-soft (any error → stashed warning → exit 0). On a platform where the mock didn't resolve they would pass vacuously. They now assert the fetched row actually rendered (a `pr:42`/`mr:7` preview-cache entry, or the empty-list warning). **Also:** `/bin/cat` → `cat` in the summary test (resolves via the platform shell on Unix and Git Bash alike); removed the stale "picker is Unix-only" line that #3217 left in the `wt switch` docs (and regenerated the help snapshot). **Windows results (first full run):** of the ~50 ungated tests, 46 passed on Windows on the first run — including every PTY/vt100 snapshot test (skim renders byte-identically under ConPTY), the accept-path drain tests, and the `cat`-via-Git-Bash summary test. Two issues surfaced and are handled here: - The three interactive `--prs` picker tests failed because the test helper `mock_forge_env` joined mock-bin onto `PATH` with a hardcoded `:` separator — malformed on Windows (`;`), so the mock `gh.exe`/`glab.exe` was never found. Fixed to use `std::env::join_paths` (OS-aware), matching the dry-run path's helper. - `test_switch_picker_pre_switch_hook_requires_approval` exits 1 instead of 0 on Windows: declining the hook returns `Ok` on both platforms, so the failure is in the interactive approval prompt's stdin handoff after skim releases the ConPTY. Not reproducible locally — `#[cfg_attr(windows, ignore)]` with a TODO until a Windows box can debug it (still compiles on Windows, runs on Unix). This is a genuine picker-on-Windows gap surfaced by this work, tracked for follow-up. > _This was written by Claude Code on behalf of max_ --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b3e1fc06b4 |
feat(switch): log + comments preview tabs for --prs picker rows (#3169)
Adds background-loaded `log` and `comments` preview tabs to `wt switch --prs` picker rows, with a width-aware tab bar that compacts to fit the 7th tab. The log tab uses a local `git log` when the head commit is present, else a forge-fetched list. |
||
|
|
81f9260a46 |
feat(switch): browse open PRs/MRs in the interactive picker (#3128)
Adds `wt switch --prs`: browse the repository's open PRs (GitHub) / MRs (GitLab) in the interactive picker, with a live CI column (same data as `wt list --full`), a `pr` preview tab showing the markdown-rendered description, and `#`-gutter-sigil filtering. Selection routes through the existing `pr:`/`mr:` fetch-and-switch path, so there's no new switch logic. |
||
|
|
3d0b99e072 |
Add WORKTRUNK_VERBOSE env var equivalent to -v/-vv (#3166)
Shell tab-completion runs the `wt` binary as its own subprocess (the shell sets `COMPLETE=<shell>`), and that path returns from `parse_cli` before `main` ever reaches `logging::init`. So when a tab-completion is slow, there's no flag that turns on logging for it — `-v`/`-vv` never run, and `RUST_LOG` only sets a level, not the `-vv` file sinks. There was no way to profile a slow completion. This adds `WORKTRUNK_VERBOSE=0|1|2` as the env-var equivalent of the `-v`/`-vv` flag count. It's read everywhere — including the completion path, which no flag can reach — and combined with the flag via `max`, so the env sets a baseline the flag can raise but never lower. Completion behaves *identically* to a flagged command at the same level: at level 2 it writes the same `trace.log`/`subprocess.log`/`diagnostic.md` under `.git/wt/logs/`, so a slow tab-completion can be profiled with: ```console $ WORKTRUNK_VERBOSE=2 COMPLETE=fish wt -- wt switch '' ``` then reading `trace.log`. (Set it inline like that, or as a one-off, rather than `export`-ing it — an exported value makes *every* TAB run as `-vv`, printing the "Writing to…" banner above your prompt and re-truncating the shared trace files on each keystroke. That's just normal `-vv` shared-sink behavior, but it's noisy interactively.) The one place completion deliberately diverges: it strips `WORKTRUNK_VERBOSE` from the environment of any forwarded `wt-*` custom-subcommand child, so the child doesn't re-run `logging::init` and clobber the trace files the parent completion just wrote (its stderr is discarded anyway). ### Testing Integration tests cover: `WORKTRUNK_VERBOSE=2` opens the trace files like `-vv` while `=1` does not; flag `-vv` combined with env `0` still writes (the `max`); and completion at level 2 writes `[wt-trace]`/`$ git` records to `trace.log` while candidates still go to stdout. A unit test pins the lossy parse (empty/garbage/out-of-range → `0`, never an error) so a stray value can't corrupt the completion candidate list. Docs (faq, config, the env-var table) and help snapshots are synced. > _This was written by Claude Code on behalf of max_ Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c5c2742058 |
feat(switch): make the picker filter text match the gutter sigil (#3143)
Typing into the `wt switch` picker filtered only on branch name + path, so typing a gutter sigil like `+` matched nothing. Each row now folds its gutter glyph into the fuzzy-search text via a canonical `ItemKind::gutter_glyph` (the single source shared with the rendered Gutter column, replacing `BranchScope::gutter_sigil`), so `+` filters to linked worktrees and `@` to the current worktree. The glyph is a skeleton-time fact (`is_current`/`is_main` set at construction, `BranchScope` structural), so folding it in keeps fuzzy ranks stable across the picker's progressive column updates, and the rendered Gutter column stays byte-identical. `^` and `|` collide with skim's prefix-anchor and OR query operators (so `^` matches everything and `|` nothing), and `/` over-matches because every path contains it; the picker help documents which sigils filter cleanly, and an engine-level test pins the operator semantics against skim 4.8's default stack. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a17fc22904 |
Add --config-set for inline TOML config overrides (#3138)
A global, repeatable `--config-set <toml>` flag that overrides any user-config key for a single invocation, layered above config files and `WORKTRUNK_*` env vars. The value is a real TOML fragment, so arrays and tables work natively — no bespoke `key=value` grammar. ## Behavior - **Precedence**: config files → `WORKTRUNK_*` env vars → `--config-set` (highest). - **Merge semantics**: a later override replaces an earlier one for the same key; scalars and arrays replace the lower-layer value; nested tables deep-merge, so `--config-set list.full=true` leaves sibling `list.*` keys untouched. - **Global**: works in any position (`wt --config-set … list` or `wt list --config-set …`), like `-v` / `-y` / `--config`. - **Graceful degradation**: a malformed, ill-typed, or invalid override drops the whole `--config-set` layer with an attributed warning (`▲ Ignoring --config-set overrides: …`) and preserves the lower layers — consistent with how the existing `WORKTRUNK_*` env overlay degrades. ## Why This is the foundation for a follow-up that parameterizes `wt list`'s column set without baking it into config: e.g. an alias `list-fast = "wt --config-set list.columns=[...] list"` renders a smaller view while plain `wt list` stays full. Doing it as a generic config-override lever (rather than a `list`-specific flag) keeps one canonical path and composes with aliases and any future config key. ## Implementation `Cli.config_override` (`--config-set`, global, `Vec<String>`) is stashed in a process-global `OnceLock` (mirroring `set_config_path`) and applied in `UserConfig::load_with_warnings` via `apply_cli_overrides` as the top layer. New `LoadError::CliOverride` variant carries the raw values for attribution; the warning is rendered in `emit_user_config_warnings`. ## Testing 8 unit tests on `apply_cli_overrides` (sets, deep-merge-preserves-siblings, last-wins, array-replace, malformed, type-mismatch, validation-failure, empty) and 3 integration tests (overrides-the-file, malformed-warns-attributed, works-after-subcommand). Full suite green: lib + 1828 integration, clippy clean, docs in sync. > _This was written by Claude Code on behalf of max_ --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a7e29f79c7 |
feat(list): custom template columns and cached PR numbers in the picker (#3073)
Two display features for `wt list` and the interactive picker, developed together because they share the column-layout and progressive-rendering machinery. ## Custom columns (`[list.custom-columns]`) Each `[list.custom-columns.<Header>]` entry in user config adds a `wt list` column: a minijinja template rendered per row over `branch`, `worktree_path`, `worktree_name`, and `vars.*`, with optional `width` and drop priority. Values expand before the skeleton renders, from in-memory data only — `vars` come from the bulk git-config snapshot, so no subprocess runs per cell. Widths are measured from content like the Branch and Path columns; a column that is empty on every row is dropped. Unknown variables and misspelled filters abort `wt list` with the available-variables hint; undefined values render as empty cells (the intended sparse-column shape). `wt list --format json` gains a `columns` map per item, and its `vars` field now reads from the snapshot too (the previous `--get-regexp` line-parse truncated multiline values). The picker shares the row renderer, so the columns appear there as well; a broken definition degrades to no columns plus a stashed warning, since collect runs while skim owns the terminal. The key is `[list.custom-columns]`, not `[list.columns]`, to avoid colliding with the column-visibility toggles in #3065 (which claims `[list.columns]` as a flat map of built-in-column bools — a mutually exclusive serde shape for the same protected key). Namespacing here lets both land independently. Ref #1982 — the custom-columns proposal lives in that thread. The issue's own title is a separate directory-naming request, so this doesn't close it. ## Cached PR/MR numbers in the picker The picker skips the networked CiStatus task, so until now it had no CI column at all. Cached statuses are local data, though: collect now fills rows from `.git/wt/cache/ci-status/` when the task is skipped under a progressive handler, so PR/MR numbers fetched by earlier `wt list --full` or statusline runs render in the picker — aligned with the same `MaxPrNumber` ratchet width `wt list` uses, and with zero network access. A valid cache entry renders as-is. An entry whose TTL passed or whose branch head moved keeps its PR/MR number dimmed: the number still identifies the PR when the pipeline color may be outdated. Expired entries without a number are dropped. The CI column is allocated only when some row had a usable entry, and rows the cache can't fill resolve to blank rather than a pending placeholder, since no task repaints them. ## Key files - `src/config/expansion.rs`, `src/config/user/sections.rs`, `src/git/repository/config.rs` — column resolution, the template environment, and the bulk git-config snapshot. - `src/commands/list/layout.rs`, `src/commands/list/render.rs` — column width allocation and cell rendering. - `src/commands/list/ci_status/mod.rs` — `populate_from_cache`, the cache-only fill. - `src/commands/picker/mod.rs` — the dry-run dump (`WORKTRUNK_PICKER_DRY_RUN`) that makes picker row content assertable in tests. ## Testing Integration tests cover both features: custom columns (table render, JSON output, empty-column drop, invalid-template error) and the picker (cached PR numbers appear in the dry-run dump, uncached branches stay blank). Unit tests cover the cache-population logic (valid, expired-with-number, head-moved, dropped). Verified against the full `cargo run -- hook pre-merge --yes` gate locally. > _This was written by Claude Code on behalf of max_ --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
7d3c923ef0 | feat(picker): free digit keys for typing; preview tabs to Alt + Tab/Shift-Tab (#3079) | ||
|
|
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>
|
||
|
|
2dd5d60902 | docs(switch): give PR links equal billing with pr:N (#2970) | ||
|
|
456c8506ea |
feat(switch): accept forge PR/MR URLs anywhere pr:N / mr:N works (#2898)
|
||
|
|
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> |
||
|
|
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) |
||
|
|
3848a9da7a | chore: bump the patch group with 3 updates (#2792) | ||
|
|
1ca73ab52c |
feat: experimental Azure DevOps support (#1256)
## Summary
Adds **experimental Azure DevOps support** alongside the existing GitHub and GitLab integrations:
- `wt switch pr:<N>` resolves Azure DevOps PRs via the `az` CLI (auto-detected from `dev.azure.com` / `ssh.dev.azure.com` / `*.visualstudio.com` remotes, or pinned via `[forge] platform = "azure-devops"`).
- `wt list --full` surfaces Azure DevOps PR and pipeline CI status.
- `wt config show --full` reports `az` install/auth state when Azure DevOps is the detected platform.
GitHub still wins in mixed-remote setups; `forge.platform` is the override. Requires the `azure-devops` CLI extension (`az extension add --name azure-devops`).
## Context
Originally proposed by @mikeyroush; reimplemented against current `main` to pick up the `[forge]` config section, `url.insteadOf` fallback, the `handle_switch` consolidation, and the new Gitea provider that all landed after the original branch was opened. The dispatch path (`choose_pr_provider`) is shared with GitHub/Gitea — Azure is just a fourth provider in the same priority chain.
Implementation notes worth a reviewer's attention:
- Azure DevOps URLs don't fit the standard `host/owner/repo` shape (`dev.azure.com/{org}/{project}/_git/{repo}`), so `Repository::find_remote_for_azure` matches on `org` + `project` + `repo` instead of the owner-based path used for the other forges.
- Pipeline/PR web URLs are constructed from `org/project/build-id`, not the API's `url` field (which is a REST endpoint).
- `*.visualstudio.com` legacy hosts encode the org in the hostname; the URL helpers handle both shapes.
Fixes #1144
## Test plan
- `cargo run -- hook pre-merge --yes` — 3631 tests pass, clippy + fmt clean
- Unit tests cover the host-aware URL helpers, `find_remote_for_azure` (all URL shapes + the same-org/different-project collision case), and `choose_pr_provider` dispatch
- Integration tests bring Azure to parity with the other forges (see Coverage):
- 13 `test_switch_pr_azure_*` tests mirroring the Gitea suite — same-repo, fork, `*.visualstudio.com` host, create/base conflicts, not-found, az-not-installed, `forge.platform` override, invalid JSON, generic server error, auth error, missing `azure-devops` extension, undeterminable org/host
- 9 `test_list_full_with_azure_*` tests covering `detect_azure_pr` (conflicts, queued, stale, retriable error) and `detect_azure_pipeline` (passed/failed/running, stale, no runs, retriable error)
- Manual validation: `wt switch pr:<N>` and `wt list --full` against an Azure DevOps repo
## Coverage
The `az`-shelling code (`fetch_pr_info`, `detect_azure_pr`, `detect_azure_pipeline`) is now exercised by integration tests via new `setup_mock_az*` helpers (modeled on `setup_mock_gh` / `setup_mock_glab`) — covering the happy paths plus the not-found / auth / extension-missing / generic-error / retriable-error branches. The non-`az` parts (URL parsing, provider dispatch, remote matching) remain unit-tested.
|
||
|
|
7fca05441e |
feat(switch): experimental Gitea PR support via pr: shortcut (#1320)
Add experimental Gitea PR support to `wt switch pr:<number>`.
The `pr:` syntax already resolved GitHub PRs; this teaches it to also
resolve Gitea PRs via the `tea` CLI. GitLab continues to use `mr:`.
## Dispatch
`pr:N` now goes through `choose_pr_provider`:
1. `[forge] platform` in `.config/wt.toml` if set (`github` / `gitea` /
`gitlab`)
2. Primary remote URL detection (host contains `github` / `gitea` /
`gitlab`)
3. CLI auth lookup: if `tea` is configured for this host (per
`~/.config/tea/config.yml`) but `gh` is not (per `gh auth token
--hostname <host>`), pick Gitea
4. Default to GitHub
There is no longer an "ambiguous" fallback that tries both providers and
wraps both errors — users on self-hosted Gitea instances either run `tea
login add <host>` (auto-detected) or set `[forge] platform = "gitea"`.
## New code
- `src/git/remote_ref/gitea.rs` — `GiteaProvider` implementing
`RemoteRefProvider` via `tea api repos/<owner>/<repo>/pulls/<n>`; reuses
the shared `cli_api_error` / `run_cli_api` helpers.
- `src/git/remote_ref/info.rs` — `PlatformData::Gitea { host,
head_owner, head_repo, base_owner, base_repo }`, wired into
`source_ref()`, `prefixed_local_branch_name()`, and `find_remote()`.
- `src/git/url.rs` — `GitRemoteUrl::is_gitea()`.
Shared helpers introduced in this PR:
- `mod.rs::extract_host_from_html_url()` (used by github + gitea;
identical 7-line chains collapsed).
- `github::is_authed_for()` (wraps `gh auth token --hostname`).
- `gitea::is_authed_for()` (reads tea's config.yml; never invokes `tea`
to avoid OAuth refresh on lookup).
## Docs
User-facing copy says "GitHub PR" by default; one paragraph in `wt
switch --help` mentions Gitea support, marked experimental.
## Tests
- 13 new integration tests covering Gitea same-repo, fork, error
responses (401/403/404/5xx/malformed JSON/deleted fork/no source
branch), `tea` not installed, `forge.platform` overrides,
GitLab-remote-with-`pr:` bail, self-hosted defaults-to-GitHub, and
self-hosted-with-`tea`-login routes-to-Gitea.
- Unit tests for `extract_source_branch` edge cases and the tea config
parser.
## Compatibility
No CLI flag or config file changes. The `tea` CLI is only required for
Gitea PRs; GitHub-only users see no change.
---------
Co-authored-by: worktrunk-bot <noreply@anthropic.com>
Co-authored-by: Maximilian Roos <m@maxroos.com>
|
||
|
|
a77c92e25b | docs(help): point banner at the actual cli source path (#2665) | ||
|
|
4cbc8ca5fd |
refactor(docs): drop mirrored help-page close; align convert_console_blocks_in_docs error channel (#2422)
## Summary Three follow-ups from #2419 review. - **Bare close everywhere.** With inner snapshot wrappers gone from command pages (#2419), no `AUTO-GENERATED` markers nest inside the help-page region anywhere in `docs/content/*.md`. The mirrored close (`<!-- END AUTO-GENERATED from \`wt cmd --help-page\` -->`) was the only remaining variant of the close form; collapsed to bare `MARKER_CLOSE`. - `src/help.rs::PageMode::emit_footer()` no longer takes `subcommand`. - The help-page regex in `readme_sync.rs` matches via non-greedy `.*?` to bare `MARKER_CLOSE`, with a comment pointing at the new invariant test. - `AUTO_GENERATED_MARKER_PATTERN` strip regex built from the constants. Added **`test_no_nested_auto_generated_markers`** — walks `docs/content/*.md` and fails if any `AUTO-GENERATED` open ever appears inside an already-open region. This is the explicit invariant that bare-close pairing depends on; if a future change tries to re-introduce nesting (e.g., restore an inner snapshot wrapper around terminal shortcodes), the test catches it before the subtle "regex chops region at first inner close" failure mode lands. - **Aligned error channel.** `convert_console_blocks_in_docs` now returns `(Vec<String>, Vec<String>)` like its sibling sync steps, with per-file error capture for `read_dir`, dir entries, and `read_to_string`. The caller passes errors through the same `tag()` aggregation as everything else, so a transient I/O failure on one file no longer aborts the whole pipeline silently. Also fixes the matching clippy warning (`is_some_and(|e| e == \"md\")` → `is_none_or(|e| e != \"md\")`). - **Docs alignment.** `docs/CLAUDE.md` updated in two places where the prose still documented the mirrored close as the canonical form. Visual check via curl across 12 dev-server pages confirmed no leakage of any prior marker form. Adversarial review (subagent /popper-style) caught the stale prose; otherwise no regressions found. Net diff: +113/-49 (the growth is the new invariant test and explicit per-file error handling; structural simplification shows as -49). ## Test plan - [x] `cargo test --test integration readme_sync` — 12 sync tests pass (was 11; +1 for the new invariant test) - [x] `cargo test --test integration` — full integration suite (1558 tests) pass - [x] `cargo test --test integration test_no_nested_auto_generated_markers` — new guard test passes; manually verified it fires on synthetic nesting - [x] Single-pass convergence verified: `git checkout docs/ && cargo test --test integration test_docs_are_in_sync && git diff --stat` produces an empty diff after the first run - [x] Visual check via local Zola dev server: 12 docs pages (\`/worktrunk/\`, \`/llm-commits/\`, \`/claude-code/\`, \`/tips-patterns/\`, \`/merge/\`, \`/step/\`, \`/remove/\`, \`/hook/\`, \`/list/\`, \`/switch/\`, \`/config/\`, \`/faq/\`) — only HTML comments contain marker text, no rendered leakage - [x] `cargo clippy --all-targets --all-features` — clean - [x] `cargo fmt --check` — clean 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
0253260503 |
Extend -v variable dump to aliases + help-table drift test (#2324)
Follow-ups from #2316. ## What's in here 1. **Alias `-v` variable dump.** Added `format_alias_variables(ctx)` alongside the existing `format_hook_variables(hook_type, ctx)`, with a private `format_variables_table` helper sharing the alignment + `(unset)` logic. Wired into `run_alias` before the announcement, symmetric with the foreground hook path. 2. **Help-table drift test.** `test_template_variables_table_matches_constants` parses the `## Template variables` table out of `src/cli/mod.rs`, extracts `(kind, var_name)` pairs, and asserts presence + group placement against `ACTIVE_VARS` / `REPO_VARS` / `EXEC_BASE_VARS` / `ALIAS_ARGS_KEY` / union of `vars_available_in(Hook(*))`. Uses public API only — no leak of the private `hook_extras` helper. Adding a var to the constants without updating the table (or vice versa) fails the test. Descriptions stay free-form. 3. **Shorter `-v` help text.** `Verbose output (-v: info logs + hook/alias template variable & output; ...)`. ## Example ``` \$ wt -v greet world ○ template variables: branch = feature worktree_path = _REPO_.feature worktree_name = repo.feature … args = ["world"] repo = repo … cwd = _REPO_.feature ◎ Running alias greet ○ Expanding greet echo hello {{ args }} → echo hello world hello world ``` > _This was written by Claude Code on behalf of @max-sixty_ --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
eec3b24883 |
Show resolved template variables under -v for hooks (#2316)
Closes #2309. When a hook fires under `-v`, `wt` now prints a `template variables:` block listing every variable in scope for that hook type and the value it resolved to for this specific invocation. Vars that are in scope but not populated render as `(unset)` — which is exactly how `target_worktree_path` surfaces during `wt switch -`, the thing the issue reporter hand-rolled an echo-hook to discover. The block prints *before* the `◎ Running …` announce line so it describes what the hook is about to see, not what already ran. Works in both paths: the foreground path (`announce_command`, one block per command) and the background path (`announce_and_spawn_background_hooks`, one block per distinct hook type in the pipeline batch). ## Key files - `src/config/expansion.rs` — `format_hook_variables(hook_type, ctx)` emits the aligned `name = value` block ordered per the docs table (active → operation → repo → exec). `BASE_VARS` is split into `ACTIVE_VARS` / `REPO_VARS` / `EXEC_BASE_VARS` with a `base_vars()` helper, so `vars_available_in` and the printer share one source of truth. - `src/commands/command_executor.rs` — foreground wiring in `announce_command`, gated on `verbosity() >= 1`. - `src/commands/hooks.rs` — background wiring in `announce_and_spawn_background_hooks`; prints one table per distinct hook type (since within a pipeline only `hook_name` varies). - `src/cli/mod.rs` — updates the global `-v` help text and adds a pointer under `## Template variables` in the hook help. ## Testing - Unit: `test_format_hook_variables_groups_and_unset` snapshots a pre-switch context with `target_worktree_path` omitted so `(unset)` is covered; `test_format_hook_variables_scope_filters_operation` confirms pre-commit's narrower operation scope. - Integration: `test_hook_verbose_prints_variable_table` covers the foreground path; the existing `test_post_start_verbose_shows_per_hook_output` now also exercises the background path. No new CLI surface, no new config keys — only behavior added behind the existing `-v` flag. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
1a4eea3c03 |
feat(cli): promote --yes to a global flag (#2279)
## Summary `-y, --yes` is now a top-level global clap flag. It moves off every subcommand's clap args and lives once on `Cli`, so `wt -y <anything>`, `wt <anything> --yes`, and `wt --yes <anything>` all skip any approval or confirmation prompt for that invocation. ## Why Follow-up to the top-level alias dispatch (#2266). The long-term plan is to unify approval-bypass behavior — `-y` should be a true global rather than duplicated across switch, remove, merge, commit, squash, prune, all ten hook subcommands, shell install/uninstall, plugin install/uninstall, and config update. A single canonical flag removes ~50 lines of duplicated clap definitions and one source of drift. ## Call-site survey Approval and confirmation prompt sources, as surfaced by `rg -l 'approve_|requires_approval|confirm_'`: - `approve_hooks` / `approve_hooks_filtered` — reads `ctx.yes`, already threaded via `CommandContext::new(..., yes)`. No change needed; the global now feeds those call sites from `handle_*_command` in `main.rs`. - `approve_command_batch` (merge, config `hook approvals add`) — takes `yes: bool`. Receives the global. - `approve_alias_commands` — takes `yes: bool`. Receives `global_yes || opts.yes` (see "Alias compat" below). - `handle_configure_shell` / `handle_unconfigure_shell` / `handle_config_update` / `handle_claude_install[_statusline]` / `handle_claude_uninstall` / `handle_opencode_install` / `handle_opencode_uninstall` — take `yes: bool` for confirmation-prompt bypass. All receive the global. ## Alias compat `AliasOptions` is hand-rolled (not clap) so it doesn't conflict with the clap global. Its post-alias `--yes` parsing is intentionally preserved here — `run_alias` does `let skip_approval = global_yes || opts.yes;` so both `wt -y deploy` and `wt deploy --yes` work unchanged. Removing the post-alias form is a separate cleanup, tracked by the user's long-term simplification plan. ## Navigating the diff - `src/cli/mod.rs` — new `yes: bool` on `Cli` with `global = true`, `short = 'y'`, `help_heading = "Global Options"`, `display_order = 103` (slots after `-v`). Removed the per-command `yes` field from `SwitchArgs`, `RemoveArgs`, `MergeArgs`. - `src/cli/step.rs` — removed `yes` from `CommitArgs`, `SquashArgs`, and `StepCommand::Prune`. Also dropped `help_heading = "Automation"` from the four step subcommands where the group was left with a single flag (`commit`, `squash`, `for-each`, `prune`); those flags now render under default Options instead of a single-item group. - `src/cli/hook.rs` — removed `yes` from all ten hook subcommands (`pre-switch`, `post-switch`, `pre-start`, `post-start`, `pre-commit`, `post-commit`, `pre-merge`, `post-merge`, `pre-remove`, `post-remove`). `--yes` stays in `KNOWN_HOOK_LONG_FLAGS` so the shorthand rewriter still recognizes it as a real flag rather than a template variable. - `src/cli/config.rs` — removed `yes` from `ConfigShellCommand::Install/Uninstall`, `ConfigCommand::Update` (and its now-redundant `conflicts_with = "yes"` on `--print`), `ConfigPluginsOpencodeCommand::Install/Uninstall`, `ConfigPluginsClaudeCommand::Install/Uninstall/InstallStatusline`. - `src/main.rs` — `dispatch_command` takes `yes: bool`; each `handle_*_command` accepts and threads it. `Cli` destructure adds `yes`. - `src/commands/alias.rs` — `try_alias`, `step_alias`, `run_alias` take `global_yes: bool`. `run_alias` OR's with `opts.yes` before calling `approve_alias_commands`. - `src/commands/custom.rs` — `handle_custom_command` accepts and passes the global to `try_alias`. - `docs/content/` + `skills/worktrunk/reference/` — auto-generated from `--help-page`; the global appears under "Global Options" on every subcommand. - `tests/integration_tests/approval_ui.rs` — six new tests: `test_global_yes_before_subcommand`, `test_global_yes_for_hook`, `test_global_yes_for_alias`, `test_post_alias_yes_still_works`, `test_global_yes_for_step_alias`, `test_global_yes_on_command_without_approval`. ## Notes - Does not remove `AliasOptions::yes` — deferred cleanup per the task brief. - Snapshot churn (~27 help files) is the expected fallout of adding a global flag; each subcommand's `--help` now shows `-y, --yes` under Global Options. ## Test plan - [x] 3242 tests pass (`cargo run -- hook pre-merge --yes`) - [x] Lints clean (clippy, cargo fmt, pre-commit) - [x] Doc sync test passes after regeneration - [x] New approval_ui tests cover before-subcommand, after-subcommand, alias, post-alias, step-alias, and no-approval positions > _This was written by Claude Code on behalf of Maximilian_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> |
||
|
|
1d01a25e90 |
feat(switch): accept pr:N/mr:N in --base (#2263)
## Summary Route `--base` through the same `pr:N`/`mr:N` resolution used for the positional branch argument, so these two commands now work symmetrically: ``` wt switch -c feat-x pr:42 # already worked wt switch -c feat-x --base pr:42 # new ``` Same-repo PRs/MRs fetch the source branch and use the branch name as the base, so the resulting worktree tracks it naturally. Fork PRs/MRs fetch `refs/pull/N/head` (GitHub) or `refs/merge-requests/N/head` (GitLab) and use the resolved commit SHA as the base — avoiding polluting the local branch namespace with a tracking branch for a fork contributor's branch. Closes #2261 ## Test plan - [x] `test_switch_base_pr_same_repo` — same-repo PR resolves to source branch name - [x] `test_switch_base_pr_fork` — fork PR resolves to commit SHA, no tracking branch created - [x] `test_switch_base_mr_same_repo` — same-repo GitLab MR - [x] `test_switch_base_pr_without_create` — warning (no fetch) when `--create` absent - [x] `cargo test --test integration` (1478 tests pass) - [x] `cargo clippy --all-targets --all-features -- -D warnings` - [x] `cargo fmt --all -- --check` --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Maximilian Roos <m@maxroos.com> |
||
|
|
3e1b351ac1 |
feat(log): split -vv output into trace.log + output.log, drop -vvv (#2201)
## Motivation At `-vv`, raw subprocess bodies were bounded (200 lines / 64 KB) on both stderr and `verbose.log`, with full output only available by rerunning at `-vvv`. Large captures (notably `git log -p` piped into `patch-id` during `wt list`) would routinely flood stderr with elision markers and force a second run just to see what was elided. ## Change The verbosity map is now `-v` (info) and `-vv` (debug); any `-v` count above 2 collapses to `-vv`. Captured subprocess stdout/stderr fan out through two log targets in `src/shell_exec.rs`: - `SUBPROCESS_TERMINAL_TARGET` → bounded preview on stderr, mirrored to `.git/wt/logs/trace.log` (new, replaces `verbose.log`) - `SUBPROCESS_FULL_TARGET` → uncapped body to `.git/wt/logs/output.log` (new), never stderr `src/log_files.rs` (renamed from `src/verbose_log.rs`) owns both file sinks behind a `LogSink` type and a `route(target)` helper that is the single source of truth for sink selection. `src/main.rs`'s env_logger format closure matches on the `Route` enum and emits once per sink. Diagnostic reports embed `trace.log` and reference `output.log` by path — multi-MB raw bodies would swamp a bug report. ## Fallback path When `RUST_LOG=debug` is set without `-vv`, neither sink is active. `FULL` records drop and the `TERMINAL` preview reaches stderr as before — preserving the bounded-stderr guarantee. The elision marker phrases its hint based on whether `output.log` was opened, so users in the fallback path see `rerun with -vv for full output` rather than a pointer to a file that doesn't exist. ## Key files - `src/log_files.rs` — new module; `LogSink`, `TRACE`, `OUTPUT`, `route`. - `src/shell_exec.rs` — two `pub const` targets, `log_output` emits on both, elision hint switches on `OUTPUT_LOG_AVAILABLE`. - `src/main.rs` — verbosity map + format closure. - `src/diagnostic.rs` — template splits inlined `trace.log` from referenced `output.log`. - `src/commands/config/state.rs` — diagnostic file recognition for the new names. ## Testing - `test_vv_splits_full_and_bounded_output` — `[wt-trace]` in `trace.log`, raw stdout in `output.log`, no trace records in `output.log`. - `test_vv_bounded_on_stderr_full_in_output_log` — 250-ref packed-refs trip the elision cap; asserts the marker on stderr + `trace.log`, full content in `output.log` without elision. - `test_rust_log_debug_fallback_without_vv` — no log files created at `-v 0 + RUST_LOG=debug`; bounded preview reaches stderr. > _This was written by Claude Code on behalf of max-sixty_ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
2a6389a092 |
Centralise [wt-trace] emitter and fix -vv log verbosity (#2146)
## Summary
Three changes to worktrunk's logging conventions, all motivated by `wt
list -vv` dumping raw `git diff-tree -p` bodies into the log stream at
debug level:
**1. `src/trace/emit.rs` owns the `[wt-trace]` grammar.** Previously the
grammar was emitted via ad-hoc `log::debug!("[wt-trace] ...")` format
strings in `shell_exec.rs`, with `src/trace/parse.rs` silently defining
it by how it parsed. `trace_instant`, `log_command_result`, the
`TRACE_EPOCH` static, and `thread_id_number` moved into the new emitter
module so `parse.rs` and the producer share one source. Wire format
byte-identical; `wt-perf` parsing unchanged.
**2. Level discipline.** `-v` → Info, `-vv` → Debug, `-vvv` → Trace.
Previously `-v` didn't touch the `log` crate at all and `-vvv` didn't
exist. The LLM prompt dump (`src/llm.rs`) and captured subprocess
stdout/stderr (`log_output` in `shell_exec.rs`) moved to `log::trace!`,
so `-vv` stops spilling thousand-line diff bodies and full LLM prompts.
**3. Bounded `log_output`.** At Debug, each stream caps at 200 lines /
64 KB with `… (N more lines, M bytes elided — use -vvv for full
output)`. At Trace, uncapped.
## Reviewer navigation
- **New file**: `src/trace/emit.rs` — the single-source emitter.
`command_completed`, `command_errored`, `instant` plus `trace_epoch` /
`now_us` / `thread_id` helpers.
- **Delete/delegate**: `src/shell_exec.rs` loses its duplicate
`TRACE_EPOCH` + `trace_epoch` + `thread_id_number`, and the four
`log::debug!("[wt-trace] …")` branches collapse into two calls into
`trace::emit`. `log_output` gains `log_stream_full` (Trace) and
`log_stream_bounded` (Debug) helpers.
- **Level map**: `src/main.rs:init_logging` has the new match on
`verbose_level`.
- **Help + test snapshots**: the `--verbose` help text change in
`src/cli/mod.rs:249` drives the large auto-synced snapshot / docs /
skill-reference diff.
- **Test rename**: `tests/integration_tests/diagnostic.rs` —
`test_v_does_not_enable_logging` → `test_v_does_not_write_log_files`
(the old name became inaccurate now that `-v` enables Info logging on
stderr).
## Testing
- 969 library unit tests pass.
- Integration tests for `diagnostic`, `step_alias`, `test_help` pass (82
tests).
- Lints clean.
> _This was written by Claude Code on behalf of max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
33b87f0b95 |
Widen help text wrapping from 80 to 100 chars for web docs (#1786)
The web docs content area fits ~101 monospace characters on desktop. The previous 80-char wrap caused unnecessary line breaks in option descriptions — e.g., enum variant descriptions wrapping mid-phrase like "Stage everything: untracked files + unstaged tracked / changes". Widens the two `help_reference()` call sites in `src/help.rs` from `Some(80)` to `Some(100)`. Also fixes a pre-existing issue where clap's line wrapping would break bold (`<b>`) spans across lines — `ensure_line_resets` now tracks active SGR styles and re-opens them on continuation lines. > _This was written by Claude Code on behalf of @max-sixty_ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
37192edf04 |
docs: improve hook command help text (#1785)
## Problem The hook command's `--help` text had several issues (#1784): 1. The intro sentence included full config paths (`~/.config/worktrunk/config.toml`, `.config/wt.toml`) — unnecessary detail for an overview 2. Background log output referenced a raw path (`.git/wt/logs/{branch}-{source}-{hook}-{name}.log`) instead of linking to the `wt config state logs` command that manages them 3. "Approvals are saved to user config (`~/.config/worktrunk/config.toml`)" was incorrect — approvals are saved to `approvals.toml` 4. Per-hook-type examples were verbose, each with its own code block interleaved with one-line descriptions ## Solution - Removed inline paths from the intro sentence - Replaced the raw log path with a link to `wt config state logs` - Fixed the approvals path to `~/.config/worktrunk/approvals.toml` - Consolidated per-hook-type examples into a single TOML block at the bottom, keeping the descriptions inline as a compact reference section ## Testing - `cargo test --test integration test_command_pages_and_skill_files_are_in_sync` — passes, docs synced - `cargo insta test --accept -- --test integration "test_help"` — no snapshot changes needed - `pre-commit run --all-files` — all checks pass (except lychee not installed) --- Closes #1784 — automated triage --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
b6a6744380 |
Consistent code block convention for syntax highlighting (#1777)
Shell code blocks on the docs site had inconsistent syntax highlighting.
Blocks with `$ ` prompt prefixes rendered as a single flat color because
Syntect's bash grammar treats `$` as variable expansion. This PR adds `$
` prompts to all shell commands while preserving full Syntect
highlighting by routing through terminal shortcodes.
## Approach
All shell commands in `console` blocks use `$ ` prefix.
`convert_dollar_console_to_terminal()` (new library function in
`src/docs.rs`) detects `$ ` lines and emits Zola terminal shortcodes:
- **Single or multi-command blocks** (no `{{ }}`): Uses `cmd` parameter
with `|||` delimiter. The shortcode template splits, highlights each
line individually through Syntect, and wraps commands in `<span
class="cmd">` (CSS `::before` adds `$ `). Comment lines (`#`) are
highlighted as comments without a prompt.
- **Blocks with `{{ }}` template syntax**: Falls back to body approach
with `<span class="cmd">` (accent color only, since Tera would interpret
`{{ }}` in the `cmd` parameter).
The function runs in both the `--help-page` generator (CLI source →
docs) and the doc sync test (hand-written docs → terminal shortcodes).
Hand-written docs can use plain `console` fences with `$ ` and get
auto-converted.
## Key files
- `src/docs.rs` — New library module with
`convert_dollar_console_to_terminal()` and unit tests
- `docs/templates/shortcodes/terminal.html` — Template enhanced to loop
over `|||`-delimited commands, highlighting each through Syntect.
Supports self-closing `{{ }}` syntax for bodyless blocks.
- `src/help.rs` — Uses library function, updated pipeline docs
- `tests/integration_tests/readme_sync.rs` — Sync test runs conversion
on all docs (not just CLI-generated). Updated skill transformation to
handle both body and self-closing terminal shortcodes.
- All `src/cli/*.rs` — `$ ` added to all console blocks
- All `docs/content/*.md` — Auto-converted to terminal shortcodes (zero
`bash` blocks remain)
> _This was written by Claude Code on behalf of @max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: worktrunk-bot <w@worktrunk.dev>
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
|
||
|
|
e14e80c0ce | docs: clean up switch command help text (#1782) | ||
|
|
acff4cd03b |
Replace parentheticals with prose in after_long_help docs (#1764)
Rewrites parenthetical asides in `after_long_help` text across switch, list, merge, step, hook, and config docs. Qualifiers and conditions become em-dashes or semicolons; examples and analogies stay in parens. Changes: `src/cli/mod.rs` only (docs, skills, and snapshots are auto-synced). > _This was written by Claude Code on behalf of max-sixty_ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
d3b63690a1 |
Reorder hook types from lifecycle to paired-by-event (#1763)
The hook subcommands in `wt hook --help` were ordered by a lifecycle narrative (pre-switch, pre-start, post-start, post-switch, ...) that claimed to represent execution order but didn't — post-switch and post-start actually run concurrently, and in `wt merge`, post-merge runs after post-remove. Reorders to paired-by-event (pre-switch, post-switch, pre-start, post-start, ...) which is easier to scan and matches how users think about hooks. Also fixes the switch docs to accurately describe post-start and post-switch as concurrent rather than sequential, and replaces parenthetical annotations with prose. > _This was written by Claude Code on behalf of max-sixty_ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
85a58fa62a |
fix: hide alt-r remove from picker legend and docs (#1696)
The alt-r keybinding for in-place worktree removal has poor UX — cursor resets to the top after each removal due to a skim 0.20 limitation (#1695). This hides the feature from the picker legend and documentation so users don't discover it until fixed. The keybinding itself remains functional for power users who already know about it. Ref #1695 > _This was written by Claude Code on behalf of @max-sixty_ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
efad9312db |
feat!: rationalize hooks — rename post-create, add post-commit, background post-merge (#1679)
Implements the hook rationalization from #1670, establishing a symmetric `pre-` (blocking) / `post-` (background) pattern for every lifecycle event. ## Changes **Rename `post-create` → `pre-start`**: The old name suggested it ran *after* creation, but it actually runs *before* `post-start` as a blocking dependency step. Both names accepted for one release cycle — config deprecation detection, migration file generation, `wt config update` support, and CLI alias all in place. `merge_with` folds old-name hooks into new-name so cross-config combinations don't silently drop hooks. **Add `post-commit` hook**: New background hook firing after successful commits (including squash commits), completing the commit lifecycle pair. Included in approval batches for `wt step commit`, `wt step squash`, and `wt merge`. **Change `post-merge` to background**: Was blocking with `Warn` strategy, now runs in background like all other `post-` hooks. `--foreground` flag available for debugging. The hook table is now a clean symmetric grid: | Event | `pre-` (blocking) | `post-` (background) | |-------|-------------------|---------------------| | start | `pre-start` | `post-start` | | switch | `pre-switch` | `post-switch` | | commit | `pre-commit` | `post-commit` | | merge | `pre-merge` | `post-merge` | | remove | `pre-remove` | `post-remove` | ## Testing 868 lib + 483 bin + 1227 integration tests pass, all 13 lint checks pass. The deprecation has 23 dedicated unit tests covering detection at all three config scopes, migration, empty table filtering, cross-config merge safety, and integration with `wt config show` / `wt config update`. Closes #1670 > _This was written by Claude Code on behalf of max-sixty_ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
df54d28c3a |
docs(switch): trim upstream tracking, add missing hooks, combine PR/MR sections (#1521)
## Summary - **Trimmed upstream tracking paragraph** — `--create` doesn't configure upstream tracking, but that's standard `git switch -c` behavior. Removed the prominent explanation and folded the remote-branch note into the existing sentence. - **Added missing hooks to creation lifecycle** — The numbered list now includes `pre-switch` (step 1) and `post-switch` (step 6), matching the actual execution order in `handle_switch.rs`. - **Combined GitHub/GitLab sections** — Merged two near-identical sections into a single "Pull requests and merge requests" section, reducing repetition. Closes #1518 ## Test plan - [x] `test_command_pages_and_skill_files_are_in_sync` passes (docs auto-synced) - [x] Unit tests pass - [ ] CI green 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Maximilian Roos <5635139+max-sixty@users.noreply.github.com> |
||
|
|
00eb04a042 | feat(switch): add no-cd config option (#1401) | ||
|
|
54f34bb19c |
feat(switch): make --no-cd print-only in picker mode (#1445)
## Summary Draft implementation per @max-sixty's [suggestion](https://github.com/max-sixty/worktrunk/pull/1404#issuecomment-4041170944): reuse `--no-cd` to make the picker print-only instead of adding a separate `--print` flag. - When `wt switch --no-cd` opens the interactive picker (no branch argument), selecting a branch prints its name to stdout and exits — no switching, no cd directive, no hooks - When `wt switch --no-cd <branch>` is used with an explicit branch, behavior is unchanged (switches without cd) - `alt-r` (remove) is blocked in print mode since it's a read-only operation This avoids adding a new CLI flag while giving the same scripting capability that #1404 proposed. ## Test plan - [x] Unit tests for `resolve_print_identifier` (switch, create, remove cases) - [x] PTY integration test: `--no-cd` picker prints branch name, emits no cd directive - [x] Existing `--no-cd` tests still pass (directive suppression, hooks, execute) - [x] Help snapshots and doc sync updated 🤖 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> |