mirror of
https://github.com/max-sixty/worktrunk.git
synced 2026-09-14 20:00:38 +08:00
codex/remove-codex-cloud-specific-tests
75 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5c42c5b7d5 |
feat: machine-readable approval state and branch-removal outcomes (#3710)
Two of the five machine-readable-output requests NathanaelRea opened (#3696–#3700), reviewed as a set and implemented where the gap was real. ## `wt config approvals list --format=json` (#3698) The command already computed the four distinctions an orchestrator needs — no commands, approved, approval-required, and stale — read-only, without prompting or writing. It had no `--format` flag, so the only way to learn that a non-interactive run would stop for approval was to run the operation and catch `NotInteractive`, or to pass `--yes` and approve whatever was there. ```json { "state": "approval_required", "commands": [ {"phase": "post-start", "name": "dev", "template": "npm run dev", "approved": false}, {"phase": "pre-merge", "template": "cargo test", "approved": true} ], "stale": ["some removed command"] } ``` `state` is what a caller branches on. `stale` stays a separate list rather than a fourth `state`, because it co-occurs with all three — and those are the approvals `--yes` would silently re-approve after their command template changed, which is exactly what an orchestrator preserving the approval model needs to see. A flag on the existing read command rather than a new `status` verb, matching `wt config show`, `wt config state get`, `wt config state logs`, and `wt list`. Closes #3698. ## `branch_outcome` on removal (#3700, partly) `wt remove --format=json` reported the branch as one boolean, collapsing five internal outcomes into two values: | Internal outcome | `branch_deleted` was | |---|---| | `Deleted` | `true` | | `Deferred` — handed to a detached process, result never observed | `true` | | `NotAttempted` — no branch, or `--no-delete-branch` | `false` | | `Retained` — a sibling worktree has it checked out | `false` | | `Retained` — **the CAS refused; the ref moved under us** | `false` | The last row is the exact race #3700 asks for protection against. Worktrunk already deletes with `git update-ref -d <ref> <oid>` and already fails closed when the ref has moved — then reported it as the same `false` that means "you asked me not to". And `Deferred` reported `true` on intent. `branch_outcome` names it instead: `deleted`, `deferred`, `not_attempted`, `retained_unmerged`, `retained_checked_out`, `retained_raced`, `retained_failed`. A caller that sees `retained_raced` knows to re-read the ref and retry, which is what the guard detects it for. **This does not close #3700.** That issue asks for an *input* — a caller-supplied expected OID that makes `wt` fail closed against the orchestrator's own observation. This is an *output*. They land in the same place on the default path, because the integration check already refuses to delete unintegrated content, so the caller was never going to lose commits — they just couldn't classify the refusal. Where the gap is real is `--force-delete` / `-D`, which takes the early return in `delete_branch_if_safe` and runs `git branch -D` with no integration check and no CAS. If an `--expected-oid` flag lands, it has to gate that path. ## Notes - **Output-format break.** `branch_deleted` is replaced, not supplemented, on `wt remove --format=json` and on `wt step prune --format=json`'s live path. Per CLAUDE.md, output formatting is on the flexible side of the interface line; flagging it here so the release changelog picks it up. - **`wt step prune --dry-run` keeps `branch_deleted`.** A dry run predicts; it runs nothing to have an outcome. Different thing, different name, documented as such. - **`retained_raced` and `retained_checked_out` have no deterministic CLI trigger.** Both come from windows between `wt`'s own fresh read and the ref mutation, which no hook can be scheduled inside. They're covered at the unit level (`branch_fate_from_result_mapping`, `branch_fate_json_outcome_is_distinct_per_fate`, and `cas_rejects_delete_when_branch_advances` in `src/git/remove.rs`, which drives the race with a stale snapshot). The integration tests cover the two reachable contrasts: `retained_unmerged` via a `pre-remove` hook that commits, and `not_attempted` via `--no-delete-branch`. - **`print_json` lives under `src/commands/list/`** and now has a third caller from outside that module. Worth a more central home; not moved here. ## The other three Reviewed but not implemented: - **#3696** — already possible. `wt --config-set 'list.json-schema = 2' list --format=json` pins the schema per invocation above every config layer, as does `WORKTRUNK_LIST__JSON_SCHEMA`. Answered on the issue; what's left is a docs gap and making an out-of-range value fail rather than degrade in JSON mode. - **#3697** — the machine-readable error channel. A real gap and the one policy call in the set; not started. - **#3699** — aimed at `wt config state logs --format=json`, which is a directory listing reconstructed from paths, under a model that overwrites. The append-only run record it wants is `commands.jsonl`. ## Testing `cargo run -- hook pre-merge --yes` green: 4533 tests, clippy, fmt, doctests, rustdoc, docs sync. > _This was written by Claude Code on behalf of max-sixty_ --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4af04c2260 |
fix(remove, prune, merge): target the named worktree, retain shared branches (#3533)
Follow-up to the discussion on #3480: [@max-sixty asked](https://github.com/max-sixty/worktrunk/pull/3480#issuecomment-5039137116) to have `wt remove` honor an explicit path generally. #3607 has since landed the resolution half, `Repository::resolve_worktree`, which turns a path into a worktree. This is the removal half, and it is the part resolution cannot decide: which worktree to act on is a naming question, whether the branch may be deleted is not. ## Problem `wt remove` threw the resolved answer away. For any non-current worktree it re-targeted by branch name, and `prepare_worktree_removal` mapped that branch back to git's *first-listed* worktree. Two failures followed, both silent. **The wrong worktree is removed.** With `feature` checked out twice: ```console $ git worktree list …/dup [feature] …/first [feature] $ wt remove …/first ◎ Removing feature worktree & branch in background (same commit as main, _) ``` `…/dup` is gone. `…/first`, the one named, is still there. **The survivor is left broken.** The shared branch is deleted with it. Worktrunk deletes branches with `git update-ref -d`, git's compare-and-swap primitive, which unlike `git branch -d` does not refuse a ref that is checked out somewhere: ```console $ git worktree list …/first 0000000 [feature] $ git -C …/first rev-parse HEAD fatal: ambiguous argument 'HEAD': unknown revision or path not in the working tree. ``` `wt step prune` reached the same deletion unattended, and `wt merge` reached it with a freshly integrated branch, so nothing else declined. A branch gets a second worktree only through `git worktree add --force`; worktrunk never does it itself. ## Fix **Remove the worktree that was named.** `wt remove` drops non-current worktrees via `RemoveTarget::Path`. `wt step prune` does the same: its candidates already carry a path, and targeting a *stale* entry by branch name resolved to a live worktree that the same prune had just skipped as too young, then removed it. **Retain a branch another worktree holds.** One predicate, `live_sibling_checkout`, answers "would deleting this ref orphan a checkout?", and every path that can delete a branch asks it: `prepare_worktree_removal`'s worktree and pruned-branch-only arms (covering `wt remove`, `wt step prune`, and the picker, which already targeted by path) and `wt merge`'s finish. A hit forces `BranchDeletionMode::Keep`, the single chokepoint every deletion path honors, and names the surviving checkout: ```console $ wt remove …/dup ◎ Removing feature worktree in background ○ Branch feature retained; still checked out @ …/first ``` A sibling whose *directory* is already gone is stale metadata, not a checkout with anything to lose, so it does not retain: removing the last live checkout still deletes the branch. **`-D` is refused out loud.** Everywhere else `-D` is the override that wins, so one that cannot be honored warns rather than passing quietly: ```console $ wt remove …/dup -D ◎ Removing feature worktree in background ▲ Branch feature retained despite -D; still checked out @ …/first ``` The ordinary single-checkout case is unchanged, and a retained branch skips the integration check entirely rather than computing a verdict it would discard. #3480's duplicate-checkout hint now points at `wt remove <path>`, which this makes the safe answer. ## Testing Full gate green. New coverage, each case asserting the survivor still resolves `HEAD`, which is the corruption in question: - `remove`: by path, by name, refused `-D`, the pruned-directory fallback, and the mirror case where a stale sibling must *not* retain. - `step prune`: a stale entry whose branch is live in an age-skipped worktree. This test is what surfaced the wrong-worktree bug in prune. - `merge`: merging a branch that a `--force` duplicate also holds. ## Not addressed `wt step prune`'s summary counts candidates rather than outcomes, so a retained branch still reports `✓ Pruned 1 branch`. The per-item line above it already says the branch was retained. Fixing the count means threading removal outcomes back through prune's accounting, which is a separate change. > _This was written by Claude Code on behalf of Maximilian Roos_ --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Maximilian Roos <m@maxroos.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f593438e7c |
feat(remove): add experimental --reap to kill worktree processes (#3396)
Implements the design agreed in #3365: an opt-in, experimental flag on `wt remove` that reaps processes left running in the worktree. ## The name `--reap` — it's the term the whole thread and the issue title already use, it's concise (matching worktrunk's flag style), and the user-facing messages read naturally (`◎ Reaping 2 processes under feature worktree`). ## What it does ```console $ wt remove --reap feature ◎ Reaping 2 processes under feature worktree ┃ 51234 node ┃ 51240 esbuild ✓ Reaped 2 processes ◎ Removing feature worktree & branch in background (same commit as main, _) ``` Processes are discovered by working directory (`lsof -d cwd`): any process whose cwd is at or under the worktree path. Termination reuses the existing `SIGTERM`→wait→`SIGKILL` escalation (`escalate_terminate`, shared with the fsmonitor sweep). ## Data-safety posture Killing a process the user didn't mean to kill — a terminal editor with unsaved buffers — is exactly the silent loss-of-work the project refuses without consent, so two guards keep `--reap` conservative: - **Controlling-terminal exclusion.** A process holding a controlling terminal (an interactive shell, or `vim`/`nvim`/`emacs -nw`) is never reaped (`ps -o tty=`). Only detached processes — the dev servers and watchers this issue is about — remain candidates. This also spares the shell `wt remove` was run from. - **Self-exclusion.** The current `wt` process is never a candidate. The flag itself is the explicit opt-in; the list is printed before signalling for transparency. ## Scope / limitations (matching the #3365 discussion) - **Under-inclusive by design.** cwd discovery misses a daemon that forked and `chdir`'d away, or one that reparented to `init` — they no longer report a cwd under the path. Those are what [`wt step tether`](https://worktrunk.dev/step/#wt-step-tether) is built to reap (whole process group). `--reap` and `tether` cover different gaps and are complementary, not substitutes — the docs say so. - **Ordering.** Reaping runs before the worktree directory is staged/renamed (cwd matching needs the directory in place), so it's independent of foreground/background removal, trash-vs-delete, and `--force`. - **Unix only.** Windows has no cheap per-process cwd; `--reap` is rejected there with a clear error. ## Tests - Pure parsers for `lsof`/`ps` output (`parse_lsof_cwd`, `parse_ps_tty`). - End-to-end against the real `lsof`/`ps`: spawns a child with a cwd under a tempdir, asserts `processes_under` discovers it, asserts the controlling-terminal guard keeps-or-drops it in agreement with the child's *actual* TTY state (so the test is host-independent — CI has no TTY, a dev box does), then reaps it and confirms `SIGTERM`. - CLI snapshot (`test_remove_reap_no_processes`) covering the no-candidates path, deterministic whether or not `lsof` is installed on the runner. Help text, `docs/content/remove.md`, and the skill reference mirror are regenerated and in sync. One thing worth a maintainer's eye: I chose to print-then-signal rather than add an interactive confirm/`--dry-run` in this first cut — the opt-in flag + TTY exclusion + printed list felt like enough for an experimental flag, and a confirm step is easy to layer on if you'd prefer it. Closes #3365. --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
771739f6b7 |
perf(integration): bound patch-id squash-merge scan to 500 commits (#2752)
## Summary
`is_squash_merged_via_patch_id` runs `git log -p {merge-base}..{target}
| git patch-id` over the *entire* target-side history. The range is
unbounded: on a fast-moving repo with an old branch — tens of thousands
of commits since divergence — a single integration check takes seconds
to tens of seconds, which surfaces as `wt step prune` (and `wt list`)
going visibly silent on the parallel check tail in #1888.
This caps the scan at `PATCH_ID_SCAN_MAX_COMMITS = 500` commits via a
cheap graph-only `git rev-list --count` pre-flight. Above the cap, the
check returns `Ok(false)` — the safe direction (branch kept, not wrongly
deleted); `wt remove -D` still removes branches that fall past the cap.
## Why 500
Count is a rough proxy — per-commit cost scales with `changed_files ×
changed_lines`, not just count. Working back from "keep one check under
a few seconds":
- typical repo (~5-20 KB patches): 500 ≈ well under 1s
- heavy monorepo (~50-100 KB patches): 500 ≈ 2-5s
Branches merged within a normal review-and-cleanup cycle sit well inside
this. Anything older is `-D` territory.
## Scope
The cap is in the shared probe, so it applies to `wt list`'s status
column, `wt remove`, `wt merge`, and `wt step prune` — anywhere
`integration_reason` is called. Behavior change only when a branch was
squash-merged *and* `git merge-tree` conflicts (the same files were
modified again after the squash) *and* the default branch has advanced >
500 commits since the merge point. Narrow.
## Test plan
- Two new unit tests in `patch_id_cap_tests` using `git fast-import`
(501-commit history built in milliseconds): under-cap finds the squash,
over-cap bails despite the squash being in range. The pair pins the
difference to the cap, not the topology.
- Existing squash-merge integration tests
(`test_remove_squash_merged_*`, `test_prune_squash_merged_*`,
`test_list_integrated_when_squash_merged_*`) keep small histories well
under the cap and continue to pass — regression guard for the wiring.
Refs #1888.
@ortonomy — would you mind running this against the repo from #1888? The
simplest check is `cargo install --git
https://github.com/max-sixty/worktrunk --branch prune-speed wt` (or
build locally), clear the disk cache to force a cold run (`rm -rf
.git/wt/cache`), then time `wt step prune`. Most interested in whether
the silent stretch is gone; if it isn't, `wt -vv step prune` writes
`.git/wt/logs/trace.log` showing which `git` invocations are slow.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.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> |
||
|
|
66a5becc25 |
refactor(tests): consolidate three sync tests; drop dead inner snapshot wrappers (#2419)
## Summary Two follow-ups from #2418 review. - **Test brittleness fix.** Three previously-separate sync tests had data dependencies that nextest could interleave: `test_readme_examples_are_in_sync` reads `docs/content/*.md`, which `test_docs_quickstart_examples_are_in_sync` generates from snapshots. Under parallelism, README sync could see stale docs and produce content that required a second run to converge. Collapsed both into the existing `test_command_pages_and_skill_files_are_in_sync` pipeline, renamed to `test_docs_are_in_sync`. Steps run sequentially in dependency order; single pass converges from a clean working tree. Each step's errors and updated-file list are tagged with the pipeline stage (`[command pages]`, `[standalone docs]`, `[README]`, etc.) so a failure tells a developer which stage broke without reading the test source. README failure also now reports `(N of M section(s) updated)`. - **Dead inner snapshot wrappers.** `expand_command_placeholders` wrapped each terminal shortcode in command pages with `<!-- ⚠️ AUTO-GENERATED from <snap> --> ... <!-- END AUTO-GENERATED -->` markers. Command pages regenerate wholesale from `--help-page` each sync, so the inner wrapper served no in-place-refresh purpose — it was dead weight nested inside the outer help-page region's markers. Stripped from `expand_command_placeholders`'s HTML branch; net -32 lines across six command-page docs files. The outer help-page region's mirrored close stays — it's load-bearing whenever any nested `AUTO-GENERATED` marker appears in the body. Comment in `src/help.rs:emit_footer` updated to explain the role. (My follow-up note that the mirrored close was redundant turned out to be a misanalysis — non-greedy `.*?` only safely pairs the open with the right close when there are no nested closes; once the inner snapshot wrappers were gone, the mirror became technically unnecessary, but keeping it survives any future reintroduction of nesting at no cost.) Renamed the test in `CLAUDE.md` (4 sites) and `docs/CLAUDE.md` (2 sites) so the documented `cargo test` invocations resolve. Net diff: −54 lines. ## Test plan - [x] `cargo test --test integration readme_sync` — 11 sync tests pass (was 13; minus the two collapsed) - [x] `cargo test --test integration` — full integration suite (1557 tests) pass - [x] **Single-pass convergence verified**: `git checkout docs/ README.md && 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: \`/worktrunk/\`, \`/llm-commits/\`, \`/claude-code/\`, \`/tips-patterns/\`, \`/merge/\`, \`/step/\`, \`/remove/\`, \`/hook/\`, \`/list/\` all render terminal blocks cleanly with no leaked marker strings - [x] Adversarial review of the consolidation (subagent /popper-style): all 4 actionable findings (stale doc references, lost README count, missing stage tags, etc.) addressed - [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> |
||
|
|
5f126d21fe |
docs(pages): add static command output to sections dominated by GIFs (#2405)
## Summary Adds static command-output blocks to the docs pages dominated by GIFs (addresses #2403). The blocks are **driven from insta snapshots** so they stay in lockstep with what `wt` actually prints — one source flows to all three surfaces: - terminal `wt <cmd> --help` (plain text, gutter-formatted) - `docs/content/*.md` (colorized `{% terminal(cmd="...") %}` shortcode) - `skills/worktrunk/reference/*.md` (plain `$ cmd\noutput\n` block) ## What changed - **Six scripted snapshot tests** produce realistic output (`cargo nextest run`, `flyctl scale count 0`, LLM-generated commit messages): - `test_docs_merge_pre_merge_hook` — `wt merge` with pre-merge hook - `test_docs_step_commit_llm` — `wt step commit` with LLM - `test_docs_step_squash_llm` — three-commit squash with LLM - `test_docs_merge_squash_llm` — `wt merge` (squash + LLM + merge) for `llm-commits.md` - `test_docs_remove_pre_remove_hook` — `wt remove` with pre-remove hook - `test_docs_hook_pre_merge` — `wt hook pre-merge` direct invocation - **Sync pipeline extension** in `tests/integration_tests/readme_sync.rs`: - New write-back pass `sync_cli_mod_example_bodies` fills the ```console``` body in each `<!-- wt <cmd> (docs-example) -->` placeholder in `src/cli/mod.rs` from the registered snapshot. Runs before `--help-page` reads the file. - `COMMAND_PLACEHOLDER_PATTERN` extended to match three forms (```bash```, `{{ terminal() }}` self-closing, `{% terminal %} body {% end %}`). This **fixes a pre-existing bug** in `docs/content/list.md` where the HTML-mode expansion was silently broken. - Stripped trailing `|||` corruption that arises when blank lines in snapshot bodies are interpreted as empty commands by `convert_dollar_console_to_terminal`. - **Docs page migration**: - `src/cli/mod.rs` — replaced four hand-written ```console``` blocks (merge, step, remove, hook) with `<!-- wt <cmd> (docs-example) -->` markers. - `docs/content/llm-commits.md` — replaced three hand-crafted HTML blocks with `<!-- ⚠️ AUTO-GENERATED-HTML from X.snap -->` markers. - **Refactor follow-up** in a separate commit: - `BADGE_EXPERIMENTAL_HTML`, `SUBDOC_MARKER_PREFIX`, `DEMO_MARKER_PREFIX` hoisted to `worktrunk::docs` so producer and consumer stay in lockstep. - `normalize_clap_help_fences()` consolidates the `text→` + `console→bash` replacement pair shared by `--help-md` and `help_reference_inner`. - **CLAUDE.md note** documenting the `.gitattributes` `linguist-generated=false` exemption requirement when adding skill-only files (carryover from #2409). ## Test plan - [x] `cargo test --test integration readme_sync` — all 13 tests pass, idempotent - [x] `cargo test --test integration test_help` — help snapshots updated - [x] `cargo run -- {merge,remove,step,hook} --help` — clean gutter formatting, no visible HTML comments - [x] `cargo run -- hook pre-merge --yes` — full project gate green (3355 tests) - [ ] Visual check on dev site (maintainer — terminal shortcodes now render with colors via ANSI→HTML; dark/light variants both expected to look like the GIFs they replace) 🤖 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.7 <noreply@anthropic.com> Co-authored-by: Maximilian Roos <m@maxroos.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> |
||
|
|
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> |
||
|
|
c16150afc8 |
Skip hash suffix for already-safe log filenames (#2157)
`sanitize_for_filename` previously always appended a 3-char hash, giving logs like `main-vfz/project/post-merge/clippy-vif.log`. Clean names now pass through unchanged, so the common case reads `main/project/post-merge/clippy.log`. Inputs that actually require sanitization (path separators, invalid chars, empty input) still get the hash suffix so they can't collide with an already-safe name. Also adds a TODO noting that the trash-sweep log shouldn't be branch-scoped — it piggybacks on `HookLog` with a fake `"wt"` pseudo-branch, so its actual on-disk path is the awkward `.git/wt/logs/wt/internal/trash-sweep.log`. Cleaner would be a top-level `internal/trash-sweep.log` alongside the other shared logs (`commands.jsonl`, `verbose.log`, `diagnostic.md`). Snapshot tests updated to reflect the new pass-through behavior. > _This was written by Claude Code on behalf of Maximilian_ Co-authored-by: Claude Opus 4.6 (1M context) <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>
|
||
|
|
f8f291b372 |
refactor(logs): nest hook output by branch/source/hook-type/name (#2041)
Flattens categorization of `.git/wt/logs/` by making the filesystem
encode what the old scheme crammed into filenames.
**Layout:** top-level *files* are shared logs (`commands.jsonl*`,
`verbose.log`, `diagnostic.md`); top-level *directories* are per-branch
log trees — `{branch}/{source}/{hook-type}/{name}.log` for hook output,
`{branch}/internal/remove.log` for background removal,
`wt/internal/trash-sweep.log` for the trash sweeper. Categorization
becomes a trivial file-vs-directory check, eliminating the exclusion
rule `ends_with(".log") && !is_diagnostic_file(name)`.
**Wins:** per-branch listing/clearing is now O(that branch) instead of
O(all logs); orphan cleanup for a removed branch is a single
`remove_dir_all`; filenames drop the joined-tuple collision hashes they
only needed to disambiguate flat keys.
**Transition:** `clear_logs` keeps a self-healing sweep of legacy
top-level `.log` files so users transition without an explicit
migration. A pinning test
(`test_state_clear_logs_sweeps_legacy_flat_files`) guards that behavior.
**Observable change:** `logs get --format=json` now puts relative paths
in the `file` field (e.g. `main/user/post-start/server.log`). Log
locations are listed as "flexible" in `CLAUDE.md`, so this is in scope.
**Reviewer orientation:**
- `src/commands/process.rs` — `HookLog::path()` rewritten; `suffix()` /
`filename()` deleted.
- `src/commands/config/state.rs` — new `walk_hook_output_files` /
`walk_branch_dir` / `HookOutputEntry`; `clear_logs` handles legacy
sweep; `partition_log_files_json` + `render_*` split along the top-level
vs hook-output seam; module docstring pins the invariant.
- `src/testing/mod.rs` — `wait_for_file_count` walks recursively.
- Test fixtures in `tests/integration_tests/config_state.rs` use new
`hook_log_rel_path` / `internal_log_rel_path` / `write_log_at` helpers.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
|
||
|
|
4a7219b230 |
Sweep stale .git/wt/trash entries on wt remove (#2039)
## Summary Background `wt remove` renames worktrees into `.git/wt/trash/<name>-<timestamp>` before firing a detached `rm -rf`. If that cleanup is interrupted (SIGKILL, reboot, disk full), the directory is orphaned and nothing reclaims the space. After `wt remove` prints its primary output, it now sweeps trash entries older than 24 hours via a single detached `rm -rf`. Age is parsed from the filename suffix, so the sweep is deterministic under `WORKTRUNK_TEST_EPOCH`. ## Notes - Runs **after** `handle_remove_output` so it never delays time-to-first-output. `.claude/skills/writing-user-outputs/SKILL.md` documents the general rule for future handlers. - Uses existing `spawn_detached` infrastructure and a new `InternalOp::TrashSweep` log name (`.git/wt/logs/wt-trash-sweep.log`). - Unparseable entries are left alone — the sweep only touches names worktrunk produced. - Integration test seeds stale + fresh entries, runs `wt remove`, and polls for the stale entry to disappear while the fresh one stays. Ref #1974 — related safety improvement (data lingers in trash briefly before deletion), but doesn't resolve the prune confirmation prompt discussion there. > _This was written by Claude Code on behalf of max-sixty_ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
85c3dadf17 |
feat: add --format=json to config state subcommands (#1969)
Add `--format=json` to 10 commands across two commits.
## Config state subcommands (commit 1)
- `logs get` — `{command_log: [...], hook_output: [...]}` with
file/size/modified_at per entry
- `ci-status get` — `{status, source, stale, url}` (richer than the text
mode's bare status string)
- `marker get` — `{branch, marker, set_at}` or `null`
- `vars list` — `{key: value, ...}` object
- `hints get` — `["hint-name", ...]` array
Also extracts a shared `log_entry_to_json` helper, deduplicating the
DirEntry→JSON conversion.
## Top-level commands (commit 2)
- `config show` — serialized user/project/system config with paths and
existence flags
- `step prune --dry-run` — array of candidates with branch, path, kind,
reason, target
- `remove` — array of removed worktrees with branch, path, deletion
status
- `merge` — summary with branch, target, committed, squashed, rebased,
removed
- `step for-each` — per-worktree results with branch, path, exit_code,
success
For action commands (remove, merge, for-each), progress goes to stderr
as usual; JSON summary goes to stdout at the end — same pattern as `wt
switch --format=json`.
> _This was written by Claude Code on behalf of @max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
f6e89009e5 |
fix: detect squash-merged branches when merge-tree conflicts (#1820)
## Problem `wt step prune` (and `wt remove`) fail to detect squash-merged branches when the default branch has since modified the same files that the branch touched. This is because `git merge-tree --write-tree` reports conflicts when both sides changed the same files, and the code conservatively treated conflicts as "not integrated." The same issue affects `wt list` — the `WouldMergeAdd` task calls the same function, so the integration symbol `⊂` is not shown for these branches. ## Solution When `git merge-tree --write-tree` conflicts, fall back to **patch-id matching**: compute the branch's squashed patch-id (`git diff-tree -p merge-base..branch | git patch-id --verbatim`) and check if any commit on the target has a matching patch-id. This detects squash merges because the squash-merge commit on the target has the exact same content changes as the branch. Uses `--verbatim` (not `--stable`) to avoid false positives from whitespace normalization — `--stable` strips whitespace, so tabs-vs-spaces would produce matching patch-ids even though file content differs. The fallback is only triggered when merge-tree conflicts — the happy path (no conflicts) is unchanged. The merge-tree → patch-id sequence is extracted into `Repository::merge_integration_probe()`, a shared method used by both `wt list` (parallel tasks) and `wt remove`/`wt merge` (sequential path). This fixes a drift where the two call sites handled patch-id errors differently. ## Known limitation The patch-id fallback checks if the branch's diff was **ever** applied to the target, not whether the effect **persists**. If a squash merge is later reverted on the target and then the same files are modified (causing merge-tree conflicts), the historical squash-merge commit's patch-id still matches. This is documented in #1818. A follow-up could add a revert-detection check after finding a match. ## Testing - `test_remove_squash_merged_then_same_files_modified` — reproduces the exact scenario from #1818 (branch modifies file, squash-merged, then target modifies same file) - `test_prune_squash_merged_same_files_modified` — verifies `wt step prune --dry-run` detects the branch - All existing squash-merge tests continue to pass (5 remove + 22 prune tests) Closes #1818 > _This was written by Claude Code on behalf of @max-sixty_ --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Maximilian Roos <5635139+max-sixty@users.noreply.github.com> Co-authored-by: Maximilian Roos <m@maxroos.com> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
a4fe57b510 |
docs: tighten remove and hook help text (#1765)
Clarify branch cleanup description, remove migration notes, and clean up small wording issues in remove and hook docs. - Reword branch deletion condition to "when they would add no changes to the default branch if merged" - Add "empty working trees" qualifier to the dimming condition - Use long flags consistently in prose (`--force-delete` not `-D`) - Remove parenthetical examples from force flag table - Remove `wt switch /path/to/worktree also works` from detached HEAD section - Remove hook migration section (legacy — should have been in changelog only) - Reorder pre/post-start table to lifecycle order > _This was written by Claude Code on behalf of @max-sixty_ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
e1e47a3b04 |
feat: support path-based switch for detached worktrees (#1680)
Extends path-based worktree resolution to `wt switch`, matching the path-based removal from #1665. Both the CLI (`wt switch /path/to/worktree`) and the picker (pressing Enter on a detached worktree) now work. ## Changes - `plan_switch`: path-based fallback (Phase 2b) after branch lookup returns `None` — tries the argument as an absolute or multi-component relative path - Picker: passes the worktree path instead of `"(detached)"` for detached items when switching - CLI arg description stays "Branch name" — path support is documented in the remove page's "Detached HEAD worktrees" section, per user guidance - Reverts "Branch name or path" arg description from #1665 back to "Branch name" ## Testing - New test: `test_switch_detached_worktree_by_path` — verifies `wt switch /path/to/worktree` works for detached worktrees - All existing tests pass > _This was written by Claude Code on behalf of max-sixty_ Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
47e6090502 |
fix: allow removing detached HEAD worktrees from picker (#1665)
## Problem The switch picker (`wt switch` TUI) fails to remove detached HEAD worktrees when pressing `alt-r`. All detached worktrees resolve to the identifier `"(detached)"`, which is then passed to branch-based removal — causing the error: ``` ✗ No branch named (detached) ``` Additionally, there was no CLI way to remove a detached worktree from outside it (`wt remove @` only works from within the worktree). ## Solution Add path-based worktree resolution as a fallback in `resolve_worktree_arg` for the `Remove` context. When branch-name lookup fails, the argument is tried as a filesystem path. This means: - **CLI**: `wt remove /path/to/detached-worktree` now works - **Picker**: Uses `handle_remove_path()` — the same codepath as the CLI The picker doesn't do anything the CLI can't do. ## Changes - `resolve_worktree_arg`: path-based fallback for `Remove` context (absolute or relative paths) - `handle_remove_path`: new function for path-based worktree removal - `validate_remove_targets`: handles `branch: None` (detached worktrees) via path-based removal - Picker uses `handle_remove_path` for detached worktrees instead of directly calling `prepare_worktree_removal` - CLI help text updated: arg description says "Branch name or path", detached HEAD section added to `--help` ## Testing - Existing test: `wt remove (detached)` → fails with "No branch named (detached)" - New test: `wt remove /path/to/worktree` → successfully removes detached worktree - All existing tests pass Thanks to @mjakl for reporting in #1661 --- Closes #1661 > _This was written by Claude Code on behalf of max-sixty_ --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Maximilian Roos <m@maxroos.com> |
||
|
|
ce9ea1a6b6 |
refactor: consolidate .git/wt-* paths under .git/wt/ and stage removed worktrees in trash (#1583)
## Summary - Moves the rename-based staging directory from a visible sibling path (`project.wt-removing-<timestamp>`) into `.git/wt/trash/`, hiding it from the user's workspace - Consolidates all worktrunk-managed `.git/wt-*` directories under a single `.git/wt/` parent - Adds `Repository::wt_dir()` accessor as the single root for all worktrunk state - Renames `wt-relocate-tmp` to `wt/relocate-staging` for consistency with `wt/promote-staging` - Falls back to legacy `git worktree remove` if the trash directory can't be created - The `.git/` directory is always on the same filesystem as worktrees, so the instant rename guarantee is preserved ## Path migration | Before | After | |--------|-------| | `.git/wt-logs/` | `.git/wt/logs/` | | `.git/wt-cache/summaries/` | `.git/wt/cache/summaries/` | | `.git/wt-cache/ci-status/` | `.git/wt/cache/ci-status/` | | `.git/wt-promote-staging/` | `.git/wt/promote-staging/` | | `.git/wt-relocate-tmp/` | `.git/wt/relocate-staging/` | | (new) `.git/wt/trash/` | Staging for background removal | ## Context Users reported confusion when seeing `.wt-removing-*` directories in their workspace after `wt remove` (#1572). By staging in `.git/wt/trash/` instead, the directory is completely hidden — even if the background `rm -rf` is slow or gets interrupted. The `.git/wt-*` sibling directories were also consolidated into `.git/wt/` for tidiness per review feedback. ## Test plan - [x] Unit tests for `generate_removing_path` and `build_remove_command_staged` updated and passing - [x] All 105 remove-related integration tests passing - [x] `test_remove_background_path_gone_immediately` — verifies instant removal still works - [x] `test_remove_background_fallback_on_rename_failure` — verifies fallback when staging path is blocked - [x] `test_remove_stale_staging_dir_from_crashed_removal` — verifies stale dirs land inside `.git/` - [x] All 2496 tests pass (lib + bin + integration) - [x] Help snapshots, doc sync, and lint checks all pass Closes #1572 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Maximilian Roos <5635139+max-sixty@users.noreply.github.com> |
||
|
|
3080eb0922 |
docs(remove): update example heading to mention branches (#1449)
## Summary - Updates example heading from "Remove specific worktrees:" to "Remove specific worktrees / branches:" to clarify that `wt remove` also works on branches Ref #1415 ## Test plan - [x] Snapshot test updated and passing - [x] Doc sync test 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> |
||
|
|
2bca89a886 |
Group help options into Picker and Automation headings (#1355)
Group `--branches`/`--remotes` under "Picker Options" and `--yes`/`--no-verify` under "Automation" in `wt switch`, `wt remove`, and `wt merge` help output, using clap's `help_heading`. > _This was written by Claude Code on behalf of @max-sixty_ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
1cf2c8203f |
Add page metadata, canonical URLs, and structured data to docs (#1167)
## Summary - Add per-page `<meta name="description">` to all doc pages — command pages auto-generated from CLI `about`/`long_about` via new `--help-description` flag, non-command pages manually written - Add `<link rel="canonical">` URLs and JSON-LD structured data (WebSite + SoftwareApplication) on the homepage - Add custom `sitemap.xml` template with `<lastmod>` dates and descriptive homepage `<title>` - Extract shared `extract_about_and_subtitle()` helper, eliminating duplicated subtitle logic between `handle_help_description` and `combine_command_docs` - Fix broken anchor in faq.md (`#picker-summaries` → `#branch-summaries-experimental`) ## Test plan - [x] Full test suite passes (2713 tests via `wt hook pre-merge --yes`) - [x] All lints clean (pre-commit, clippy, cargo fmt) - [x] Doc sync test confirms auto-generated descriptions match CLI help - [x] Zola build succeeds with all template changes > _This was written by Claude Code on behalf of @max-sixty_ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
1f361b6f45 |
docs: improve wt remove help text (#792)
- Change subdefinition from "For finished feature branches. Removes the current worktree by default." to "Defaults to the current worktree." - Add Hooks section documenting pre-remove and post-remove hooks Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
fb011c37dd |
feat: reduce background hook output verbosity (#740)
* feat: reduce background hook output verbosity (#690) Background hooks (post-start, post-switch) now show a single-line summary by default instead of verbose per-hook output with command details: ◎ Running post-start hooks @ repo.feature: user:bg, project Use `-v` to see detailed per-hook output with expanded commands. Co-Authored-By: Claude <noreply@anthropic.com> * fix: update comment to match actual output format Co-Authored-By: Claude <noreply@anthropic.com> * test: add coverage for verbose background hook output Add test_post_start_verbose_shows_per_hook_output to verify that -v shows detailed per-hook output with command in gutter format. Co-Authored-By: Claude <noreply@anthropic.com> * docs: document -v flag for background hook verbosity Adds a note in the hook types section explaining that background hooks show a single-line summary by default, with -v for expanded details. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
f64c7863b6 |
refactor: require -vv for debug logging, reserve -v for future use (#702)
Change verbosity threshold from -v to -vv for enabling debug logging and diagnostic file generation. This frees up -v for other purposes. Behavior change: - -v: no effect (reserved for future use) - -vv: debug logging + verbose.log + diagnostic.md (unchanged) Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
d70860afbb |
docs: improve command documentation structure (#643)
* feat: include command definition at top of doc pages The first `///` doc comment line (the "about" / definition) now appears at the top of each command's documentation page, before the subtitle and after_long_help content. Previously, this definition only appeared in the Command Reference section at the bottom. Now the page structure is: 1. Definition (short about) 2. Subtitle (long about, if present) 3. Conceptual documentation (after_long_help) 4. Command reference Co-Authored-By: Claude <noreply@anthropic.com> * docs: improve command documentation structure - Add definition to top of web doc pages (was missing) - Combine definition + subdefinition into single lead paragraph on web - Remove duplication between definition and after_long_help openers - Fix em-dash spacing (spaced per style guide) - Fix pronoun clarity ("Creates one" vs "Creates it") - Use indicative mood instead of second person ("For finished feature branches" not "Use when you're done") - Add documentation guidelines to docs/CLAUDE.md Commands updated: switch, list, select, remove, merge, step, hook, config Co-Authored-By: Claude <noreply@anthropic.com> * fix: update switch help snapshot for pronoun change Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
6ecc51feef |
docs: clarify --force vs -D flags in wt remove (#565)
Add a "Force flags" section to explain the difference between: - `--force` (`-f`) for worktree removal with untracked files - `--force-delete` (`-D`) for deleting unmerged branches This addresses user confusion reported in #564 where the `--force` flag for worktrees was only documented in the command reference, not the prose section. Also removes the Shortcuts section (not relevant to remove) and uses consistent single-quote styling for 'target' and 'same commit'. Closes #564 Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
6665c128d4 |
feat(diagnostic): add -vv flag for diagnostic report generation (#472)
* feat(diagnostic): add -vv flag for diagnostic report generation Add verbosity levels to the CLI: - `-v` enables debug logging (existing behavior) - `-vv` also writes a diagnostic report to .git/wt-logs/diagnostic.md The diagnostic report includes: - Command that was run and result - Environment (wt version, OS, git version, shell integration) - Worktree list - User and project config contents - Verbose log (if available) When gh CLI is installed, shows a hint with the full `gh issue create` command for easy bug reporting. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(test): make version check more robust in diagnostic test The version string can be either "v0.9.5" (from git describe) or "0.9.5" (from cargo), so check for "wt " instead of "wt v". 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(test): handle Windows path separators in diagnostic snapshot The project config path uses backslashes on Windows (.config\wt.toml) but forward slashes on Unix (.config/wt.toml). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * test(diagnostic): add unit tests for config formatting functions Add tests for: - format_config_section: file not found, empty file, content, truncation - strip_ansi_codes: ANSI code removal - truncate_log: small and large content 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
75e1e039f7 |
feat(hook): add --foreground flag for debugging background hooks (#470)
* feat(hook): add --no-background flag for post-start/post-switch debugging Allows running background hooks (post-start, post-switch) in the foreground to see their output directly, useful for debugging. Usage: wt hook post-start --no-background Follows the existing --no-background pattern from wt remove. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor(cli): rename --no-background to --foreground across all commands Rename the flag for running operations in foreground mode from --no-background to --foreground for better UX. The old flag remains as a hidden deprecated alias with a warning message. Affected commands: - wt remove --foreground - wt hook post-start --foreground - wt hook post-switch --foreground 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * test: add tests for deprecated --no-background flag Add tests to verify the deprecated --no-background flag still works and shows the deprecation warning. This covers the code paths for: - wt remove --no-background - wt hook post-start --no-background - wt hook post-switch --no-background These tests ensure the deprecation warnings are emitted and the old flag continues to function as expected. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
92abd99656 |
Format help text command references with backticks
Wrap command references in backticks (e.g., `wt merge`) in help text and documentation to render them as code. Updates both source documentation files and CLI help strings, with corresponding snapshot updates. |
||
|
|
0340924d47 |
refactor: improve CLI help text and documentation
Clarify Push command documentation and improve help text descriptions for select, squash, and merge commands. Update test snapshots to reflect these documentation changes. |
||
|
|
1154ff0744 |
Clarify branch cleanup target comparison logic
Reword the explanation of how the "same commit" check differs from other checks in determining the target branch for comparison. |
||
|
|
143ec2a497 |
Clarify remove command help text
Update the `wt remove` command help to explicitly mention that branches are only deleted if merged, and that the command defaults to removing the current worktree. Adjust wording in branch cleanup section to use "By default" for consistency. |
||
|
|
9e27a51d15 |
Standardize worktree/branch terminology in CLI and docs (#316)
Align with spec: "Worktrees are addressed by branch name." CLI changes: - Switch: "Branch or worktree name" → "Branch name" - Remove: argument renamed from `worktrees` to `branches` - Remove: "Worktree or branch" → "Branch name [default: current]" Internal changes: - GitError::UncommittedChanges field: `worktree` → `branch` - Repository::ensure_clean_working_tree param: `worktree` → `branch` Documentation: - Added explanation: "Each worktree has exactly one branch, so Worktrunk uses branch names to address worktrees. The path is derived automatically." - Appears in worktrunk.md (landing page) and `wt switch --help` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
1a9ce24f36 |
Separate --force into --yes (prompts) and --force (removal) (#311)
* Add --force flag to wt remove for worktrees with untracked files The --force flag now also passes --force to git worktree remove, allowing removal of worktrees containing untracked files like build artifacts (.vite/, node_modules/, etc). - Extended existing --force flag semantics (was: skip approval prompts) - Added -f short form for convenience - wt merge now always forces worktree removal (build artifacts unneeded post-merge) Fixes #301 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Separate --force into --yes (prompts) and --force (removal) The --force flag on wt remove had two meanings: skipping approval prompts AND forcing worktree removal with untracked files. This was confusing and potentially dangerous. Now: - --yes/-y: Skip approval prompts (all commands with prompts) - --force/-f: Force worktree removal with untracked files (wt remove only) This follows CLI conventions (apt, npm, etc.) where --yes skips prompts and --force overrides safety checks. Breaking change acceptable per project guidelines (early release mode). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix unit test for NotInteractive error message Update test to check for --yes instead of --force. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
7e5057b1ef |
Add --force flag to wt remove for worktrees with untracked files (#310)
The --force flag now also passes --force to git worktree remove, allowing removal of worktrees containing untracked files like build artifacts (.vite/, node_modules/, etc). - Extended existing --force flag semantics (was: skip approval prompts) - Added -f short form for convenience - wt merge now always forces worktree removal (build artifacts unneeded post-merge) Fixes #301 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com> |