Commit Graph

46 Commits

Author SHA1 Message Date
Maximilian Roos 0b27755726 fix(help): name the doc-entry-point command from clap, not an argv scan (#3762)
Three developer entry points — `--help-page`, `--help-description`, and
`--print-schema` — each carried its own copy of a scan that picked the
subcommand out of argv by rejecting entries that looked like the binary:

```rust
.filter(|a| *a != "--help-page" && !a.starts_with('-') && !a.ends_with("/wt"))
.find(|a| !a.contains("target/") && *a != "wt")
```

Neither `ends_with("/wt")` nor `contains("target/")` matches `wt.exe`
under a backslash path, so on every Windows install all three read the
binary's own path as the command. `--print-schema` exited 2; the other
two exited 0 with empty stdout and `Unknown command: C:\...\wt.exe` on
stderr. The scan also took the value of a value-carrying global as the
command, so `wt -C <path> list --print-schema` answered `No JSON schema
for '<path>'`.

clap had already parsed the same argv one call earlier.
`parse_early_globals` runs the real `Cli` definition with
`ignore_errors(true)` and reads `matches.subcommand()` to pick the
alias-splice context, and the comment above it already said that exists
"so the splice path in `augment_help` has no separate arg scanner". The
doc entry points just never asked it. It now carries the name in an
`EarlyGlobals` struct, and the three handlers take `Option<&str>`
instead of `&[String]`.

Error messages are unchanged: `src/cli/mod.rs` has
`#[command(external_subcommand)]` for user aliases, so a name the `Cli`
definition doesn't declare still arrives intact and can be named back in
`Unknown command: X`.

`--print-schema` now prints through `crate::output::print_json` rather
than `serde_json::to_string_pretty` plus std's `println!`. std's panics
with exit 101 when a consumer closes the pipe; anstream's drops the
`BrokenPipe`. This was the last JSON-to-stdout surface still panicking
after #3746 — the schema landed via #3747 while that PR was in flight,
and it lives outside `src/commands/`, where #3746's stdout guard doesn't
scan.

<details>
<summary>Why CI never caught this</summary>

`tests/integration_tests/help.rs` and
`tests/integration_tests/readme_sync.rs` are both
`#![cfg(not(windows))]`, and on Unix the test binary is always named
`wt` under `target/`, which is exactly the shape the scan was written
against. The new test drives all four cases through a symlink named
`wt.exe` — a symlink rather than a copy because the debug binary is
67MB. Verified to fail against the pre-change code.

</details>

`wt merge --help-page` and `wt merge --help-description` still panic on
a closed pipe. Those use std's `println!`/`print!` deliberately, to
preserve the ANSI codes anstream's would strip, so they need a different
mechanism than this one. Left for a follow-up.

> _This was written by Claude Code on behalf of max-sixty_
2026-08-06 23:13:17 -07:00
Maximilian Roos 7f8ac8e5d7 feat(list): publish a JSON Schema for the schema-2 envelope (#3747)
`wt list --format=json` schema 2 now has a published, machine-readable
contract at
[worktrunk.dev/schema/list-v2.json](https://worktrunk.dev/schema/list-v2.json).

The schema was already derived — `test_schema_generates` built one,
asserted it compiled, and threw it away, with a comment saying "until
the schema export ships." This ships it: `wt list --print-schema` prints
the document (a developer entry point alongside `--help-page`,
intercepted before clap), and a new step in `test_docs_are_in_sync`
commits it to `docs/static/schema/list-v2.json`, the same
generate-and-commit pattern as `llms.txt`. It shells out rather than
calling `schema_for!` because `JsonEnvelope` lives in the bin-only
`crate::commands` tree.

Two things had to be fixed for the document to be usable.

**The contract.** `schema_for!` generates under schemars' *deserialize*
contract, which marks a `skip_serializing_if` field required — nothing
supplies it on the way in. The first document I generated therefore
required `default_branch`, `upstream`, `pr`, `checks`, `summary` and
`vars` on every item, all of which the absence rule routinely omits, so
it rejected the output it documents. Generating under `for_serialize()`
fixes it.

**The vocabularies.** Four fields — `checks.status`, `display.state`,
`default_branch.integration.reason` and `worktree.operation` — were
`&'static str`, so the schema described them as bare strings. They are
now `JsonCheckStatus`, `JsonMainState`, `JsonIntegrationReason` and
`JsonOperation`, each converted from its domain enum by an exhaustive
match, so a new `CiStatus`, `MainState`, `IntegrationReason` or
`InProgressOperation` variant is a compile error rather than a value
silently missing from the published vocabulary. **The emitted JSON is
unchanged**; the existing envelope snapshot passes untouched.

<details>
<summary>Before and after, for one item</summary>

```json
// before — rejects its own output, and loses the vocabulary
"required": ["default_branch", "upstream", "pr", "checks", "summary", "vars", "display"],
"status": { "type": "string" }

// after
"required": ["branch", "head", "display"],
"status": { "enum": ["passed", "running", "failed"] }
```

</details>

## Testing

`test_schema_accepts_envelopes` validates a battery — every `CiStatus`
over both sources, every `MainState`, a populated worktree row, an
integrated row with an upstream and a dev server, plus the absent and
null arms of the absence rule — against the same document
`--print-schema` emits.

Validating proves nothing about a type the battery never instantiates,
so the test also pins every non-`Nullable_` type in the document to a
path that must carry a non-null value. A new `Json*` type fails until
the battery reaches it, and a row that stops populating one fails too —
the check reports the type names rather than leaving the gap to a
reader. This needed a `jsonschema` dev-dependency: schemars only
generates, and derives the document from the types without ever seeing
an envelope, so nothing otherwise tied the two together.

The test was confirmed to fail on the bug it exists for. Reverting to
`for_deserialize()` makes it report `pr`, `checks`, `summary`, `vars`
and `display.columns` as wrongly required.

The dependency is dev-only: `reqwest`, `rustls` and `async-trait` stay
unselected so no HTTP stack comes along, and `cargo tree --package
worktrunk --edges normal -i jsonschema` finds no path to it.

One direction it deliberately does not cover: a *loosening*. If a field
reverted to `&'static str` the schema would say `type: string` and
anything would validate. That direction is held by the compiler instead,
via the exhaustive matches.

## Notes for review

- `schema_document()` lives in `json_v2.rs` beside the types, not in
`help.rs`, so `--print-schema` and the test compile the same document
rather than two constructions that could drift on the contract setting.
- The lychee exclusion for `worktrunk.dev/schema/` follows the entry
directly above it: a generated link that 404s until the site deploys.

> _This was written by Claude Code on behalf of max-sixty_
2026-08-05 22:33:23 -07:00
Rene Leonhardt 79be81a4bb fix(merge): Accept merge-shaped fast-forward histories (#3509) 2026-07-18 00:32:56 -07:00
Zexin Yuan 6fc6b37b6b feat(step): add require-include option + CLI flags to copy-ignored (#3196)
`wt step copy-ignored` copies all gitignored files by default. Add a
`require-include` setting that gates the copy on a `.worktreeinclude`
file existing in the source worktree — matching Claude Code desktop,
where the file is required. Without `.worktreeinclude`, the command is a
no-op that reports why (text hint + JSON `reason`); with it, only
matching files copy.

Config (additive, defaults off; `Option<bool>` merged via `.or()` so an
explicit value overrides lower-priority config in either direction):

```toml
[step.copy-ignored]
require-include = true
```

CLI flags override config per run (precedence `CLI > --config-set >
project > user > default`, mirroring `wt merge` / `wt remove`:

```
--require-include      copy nothing unless .worktreeinclude exists
--no-require-include   copy all gitignored files this run
```

The gate short-circuits before `git ls-files` discovery. The recovery
hint names `--no-require-include` (correct whether the flag or config
triggered it, since the flag overrides both). `wt step promote` is
unaffected — it exchanges all gitignored artifacts by design.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 17:54:24 -07:00
Maximilian Roos 9425a22bd0 Add wt config state logs profile performance profiler (#3184)
## `wt config state logs profile`

A new subcommand that turns a `-vv` trace into a performance profile,
answering the three questions in `benches/CLAUDE.md` without leaving the
terminal: where time went, how parallel the run was, and where work was
wasted.

`-vv` already writes `[wt-trace]` records to `.git/wt/logs/trace.log`;
this parses them and renders:

- **BY COMMAND TYPE / SLOWEST CALLS** — subprocess time grouped by
command shape (`git status`, `gh pr list`), plus the slowest individual
jobs.
- **parallelism / peak concurrency** — Σ(subprocess time) ÷ wall span,
and the most subprocesses in flight at once.
- **CACHE** — commands re-run within the same context (a cache miss that
should have hit).
- **KEY INTERVALS / PHASES** — for a `wt list` or picker capture,
derived latencies (time to skeleton, time to first result) and a
collect-milestone timeline. These come from the
`worktrunk::trace::instant(…)` milestones, so they populate only for
those commands; every other command still gets the sections above.

`--format=json` serializes the same struct (durations as `*_us`
integers) for scripting, so the text and JSON views can't drift. The
`diagnostic.md` bug-report bundle now inlines a rendered profile beside
the raw trace, so any `-vv` report involving slowness shows where time
went at a glance.

### Navigating the diff

- `src/trace/profile.rs` — the analysis (`Profile` /
`CacheReport::from_entries`, pure data over `&[TraceEntry]`) and the
text renderer; the `Serialize` impl is the single canonical JSON source.
- `src/cli/config.rs`, `src/commands/config/state.rs` — CLI surface and
`handle_logs_profile` (reads a path arg, `-` for stdin, or the default
`.git/wt/logs/trace.log`).
- `src/diagnostic.rs` — inlines the rendered profile into the bundle.
- `tests/helpers/wt-perf/src/main.rs` — reuses the shared `CacheReport`.

### Testing

Well covered: the renderer across full / minimal /
collect-without-skeleton / truncation variants, the JSON shape, and the
CLI end-to-end (path, stdin, default, missing-file, no-records,
not-in-a-repo). The large text outputs are file snapshots, and an
end-to-end `wt -vv list` test guards the milestone strings against
drift. The diagnostic profile section is presence-checked (placeholdered
in the snapshot, since its timing is non-deterministic) with the
rendering logic unit-tested deterministically.

> _This was written by Claude Code on behalf of max_

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 18:30:15 -07:00
Maximilian Roos 3ea7392a65 fix: bound config-show's zsh probe and isolate tests from host env (#3165)
Bounds the `wt config show` zsh compinit probe with a 2s kill-on-timeout (the
only un-bounded interactive shell spawn in production), matching its twin
`shell::detect_zsh_compinit`. Also isolates several tests from host environment:
`TZ=UTC` in the statusline helper, `--no-config-file` on the nu probes, and the
`wt --help` / `git log` spawns routed through the isolated `wt_command()` /
`repo.git_command()` helpers.

The macOS PTY-test hang that prompted the audit is fixed structurally in #3161.

> _This was written by Claude Code on behalf of max_

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 17:54:55 -07:00
Maximilian Roos a8aad444ca feat(help): chop wt list example tables to width instead of wrapping (#3125)
In `wt list --help`, the captured `wt list` example tables word-wrapped
at narrow terminals, shearing their columns apart and making the
examples hard to read. This renders those captured-output blocks chopped
to terminal width with a dimmed ellipsis — matching how real `wt list`
truncates — while hand-authored command sessions (the `jq` examples,
`bash`/`sh` blocks) keep word-wrapping so every token stays readable.

The gutter formatter gains a `LineFit` mode (`Wrap`/`Chop`) and a
`format_bash_with_gutter_chopped` entry point, both built on the
existing `truncate_visible` (ANSI- and width-aware, so no new
dependency). The renderer gates chop on the `<!-- wt list … -->` marker,
which already denotes captured `wt list` output in the doc-sync layer;
everything else still wraps. The chopped ellipsis is re-dimmed to match
the block and the redundant trailing reset is dropped.

Tested by inline Chop-vs-Wrap unit snapshots in `format.rs` and the
`help_list_narrow_80` integration snapshot (rendered at `COLUMNS=80`),
which shows the three example tables chopping while the `jq` command
blocks still wrap.

> _This was written by Claude Code on behalf of max_

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 10:04:19 -07:00
Maximilian Roos ddf550a21c refactor(styling): return Option<usize> from terminal_width() (#3043)
`terminal_width()` signaled "can't detect width" by returning
`usize::MAX`, with a doc-comment contract that callers handle the
sentinel. Three consumers each re-implemented the check, and two misses
shipped as user-visible bugs fixed in #3040 (`wt list --help` panicked
with a capacity overflow when piped; piped diffstats truncated filenames
to ~10 chars).

The function now returns `Option<usize>`, so mishandling the no-width
case is a compile error rather than a convention. Help rendering and
`show_diffstat` pass the `Option` through naturally; the statusline
parent-TTY walk triggers on `None` instead of a sentinel comparison.
Consumers that genuinely want an unlimited width (statusline budget,
picker split math, list layout, gutter wrapping) now say
`.unwrap_or(usize::MAX)` explicitly at the use site, where the choice is
visible. The gutter formatters keep calling the wrap pipeline with that
unlimited width rather than skipping it, since `wrap_ansi`
post-processing affects output bytes even when nothing wraps.

No behavior change: output is byte-identical in every width context,
with zero snapshot or generated-doc churn, and the #3040 regression
tests (`test_help_without_detectable_width`,
`test_push_diffstat_without_detectable_width`) pass unchanged.

> _This was written by Claude Code on behalf of max_

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 16:13:20 -07:00
Maximilian Roos 4c2aeba95f refactor(cli): dedupe the config state --format flag; fix width-sentinel bugs (#3040)
The identical 10-line global `--format` declaration was repeated across
the five `wt config state` subcommands (`cache`, `logs`, `hints`,
`ci-status`, `marker`); this extracts it into a shared
`GlobalFormatFlag` args struct flattened into each, so the attributes
and help text can't drift apart. Help output is byte-identical: no
snapshot or generated-doc changes. Other repeated flag declarations were
reviewed and deliberately left inline: the switch/remove/merge
`--format` flags differ in their doc comments, and the remaining
identical pairs (two `--dry-run`s, two text-format and two table-format
flags) are too small to be worth the indirection of a wrapper struct.

Review of the change surfaced two pre-existing mishandlings of
`terminal_width()`'s `usize::MAX` "width unknown" sentinel, both fixed
here with regression tests:

- `wt list --help` panicked with a capacity overflow when neither stdout
nor stderr is a TTY and `COLUMNS` is unset (e.g. `wt list --help >
file`): the help renderer received the sentinel as a real width and
tried to render a `---` rule that wide. The sentinel now maps to the
renderer's `None`.
- The post-commit diffstat passed the sentinel to `git diff
--stat-width=`, making git truncate filenames to ~10 characters in piped
output; the flag is now omitted when no width is known.

> _This was written by Claude Code on behalf of max_

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 14:50:45 -07:00
Maximilian Roos 19aa6f0a28 feat(config-state): add cache subcommand, gate clear behind confirmation (#3027)
`wt config state` had a sprawl of per-category subcommands, several of
which only really existed to be cleared. This consolidates the
regenerable caches under one `cache` subcommand, makes the destructive
`clear` confirm first, and deprecates the now-redundant subcommands
without breaking them.

## What changed

**New `cache` subcommand** — the single home for everything regenerable:
CI status, summaries, git-commands (the SHA-keyed disk caches), hints,
and the `wt switch -` target. `wt config state cache` shows them
(`--format=json` too); `cache clear` drops them all with **no prompt**,
since clearing only forces recomputation. It re-shows one-time hints and
forgets the switch-back target — both repopulate on their own.

**`clear` now confirms** — `wt config state clear` still removes
*everything*, but it also wipes hand-authored markers and vars, so it
now prompts before doing so. `--yes` skips the prompt. Non-interactive
without `--yes` declines safely (no data loss) — the conservative
direction. This is the one behavior change to watch: a script doing `wt
config state clear` without `-y` now cancels instead of wiping.

**`ci-status` / `hints` / `previous-branch` deprecated** — hidden from
`--help` and emitting a one-line stderr deprecation notice, but still
fully functional. Existing scripts keep working (the notice is
stderr-only, so stdout parsing is unaffected); new users only discover
`cache`.

## Navigating the diff

- `src/commands/config/state.rs` — the bulk. `handle_state_clear_all` is
refactored to compose per-category `clear_*_reported` helpers + the
confirmation gate; `handle_cache_clear` reuses the regenerable subset.
`cache get` and the aggregate `state get` share extracted
`render_*_section` / `*_json` helpers, so the two views render each
category identically. The module docstring documents the
regenerable-vs-authoritative split and the preserved `get`↔`clear`
parity invariant.
- `src/cli/config.rs` — the `Cache` variant + `CacheAction`, `hide =
true` on the three deprecated subcommands, and the doc/help text.
- `src/main.rs` — dispatch: the `cache` arm, the deprecation warnings
(now uniformly bolding the replacement command across all four `wt …`
deprecation messages), and threading `--yes` into `clear`.

## Testing

Well covered: new integration tests for `cache get`/`cache clear` (empty
and populated), the `clear` prompt (accept and decline paths),
authoritative-state preservation under `cache clear`, and the
deprecation warnings — plus the existing aggregate `state get`/`clear`
suite, which exercises the shared helpers. Docs regenerated and
`test_docs_are_in_sync` passes. Full pre-merge gate (tests + clippy +
fmt) green.

> _This was written by Claude Code on behalf of max_

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 22:40:08 -07:00
Maximilian Roos 3f3c05bd49 test(help): route all help snapshots through one settings builder (#3009)
## Consolidate help-snapshot insta settings

Follow-up to #3006, which fixed one instance of this in
`test_help_list_narrow_terminal`.

### The bug

Insta's `info:` / `env:` block is metadata — written to the `.snap` file
but **not** part of insta's pass/fail comparison (only
`stdout`/`stderr`/`exit` are compared). Several help tests in
`tests/integration_tests/help.rs` constructed their own
`insta::Settings` inline and skipped `add_standard_env_redactions`, so
host-specific env baked into their committed snapshots and passed
forever on the machine that generated them. Three snapshots
(`help_md_root`, `help_md_merge`, `nested_subcommand_hook_pre_start`)
carried a developer's literal `LLVM_PROFILE_FILE` temp path
(`/var/folders/…/wt-test-profraw/…`); it only diffs when the snapshot is
regenerated elsewhere.

### The fix

One private `help_settings() -> Settings` builder (snapshot path +
`add_standard_env_redactions`); every help/version/usage snapshot test
now routes through it, so the redaction can't be forgotten. The seven
affected snapshots are force-regenerated — env blocks now uniformly
redacted, command-output bodies byte-identical.

This is latent-correctness hygiene: because the env block isn't part of
the comparison, it never fails CI; the payoff is that the next
regeneration on any machine won't reintroduce host churn.

> _This was written by Claude Code on behalf of max_

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 10:09:48 -07:00
Maximilian Roos 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>
2026-06-07 00:00:52 -07:00
Worktrunk Bot 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>
2026-05-21 16:03:03 +00:00
Maximilian Roos 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)
2026-05-20 19:31:50 -07:00
Maximilian Roos f522357e11 feat(commit): experimental project-level commit-message guidance (#2774)
## Summary

- Adds `[commit.generation] template-append` to **both** user config and
project config (`.config/wt.toml`). It *adds to* the commit/squash
prompt rather than replacing it (`template` still replaces). One field
covers both commit and squash.
- Each fragment is rendered as its own minijinja template with the same
variable context as the main template, then emitted in a
provenance-labeled block: `<user-guidance>` (the developer's own config
— no approval) followed by `<project-guidance>` (repo-supplied — gated).
Default templates render each block conditionally; `{{ user_guidance }}`
/ `{{ project_guidance }}` are exposed for custom user templates.
- The project fragment keeps the first-time approval gate — same
one-shot gate as project hooks. `wt merge` bundles it into the existing
hook-approval batch so the user sees one combined prompt. Declining is
non-fatal; the LLM runs with just the user fragment (if any). Skipped
automatically when no LLM command is configured.
- Marked experimental in CLI help, docs, and the user/project config
docstrings.

Discussion: #2758 (comment
[4454619614](https://github.com/max-sixty/worktrunk/issues/2758#issuecomment-4454619614))

## Test plan

- [x] Unit tests for `commit_template_append()` and the user-side
accessor (trim / empty / unset)
- [x] `Merge` test for user `template-append`
- [x] Snapshot tests confirming the default commit and squash templates
render the `<project-guidance>` block
- [x] Unit tests for user-side append: renders into `<user-guidance>`,
ordered before `<project-guidance>`, blank-is-unset, minijinja variable
expansion
- [x] PTY integration tests for the approval flow (accept / decline /
merge bundling), asserting `<project-guidance>`
- [x] `cargo run -- hook pre-merge --yes` — 3708 tests passing, clippy +
pre-commit clean

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-17 17:19:19 -07:00
Douglas Soares de Andrade 2c675e00f7 Add Codex support (#2512)
## What problem does this solve?

Worktrunk already has a Claude Code integration, but Codex users do not
have an equivalent first-class path for:

- discovering Worktrunk guidance inside the agent environment
- bundling hooks that can show active Codex work in `wt list`
- learning the Codex-specific worktree workflow and its differences from
Claude Code
- installing the integration through a Worktrunk CLI command

This PR adds Codex support while preserving the important semantic
difference between Claude Code and Codex: Claude Code can route
agent-created worktrees through `wt` lifecycle hooks, but Codex
currently needs users/agents to invoke `wt switch --create` and `wt
remove` directly.

## What changed?

### Codex plugin package

- Adds repo-local Codex plugin metadata in `.codex-plugin/plugin.json`.
- Adds a Codex marketplace entry in `.agents/plugins/marketplace.json`.
- Adds `hooks/hooks.json` for bundled activity hooks:
  - `SessionStart` sets the marker to `💬`
  - `UserPromptSubmit` sets the marker to `🤖`
  - `Stop` sets the marker back to `💬`
- Reuses the existing Worktrunk skill/reference docs as the plugin skill
payload.

### CLI integration

- Adds `wt config plugins codex install`.
  - Runs `codex plugin marketplace add max-sixty/worktrunk`.
- Clearly tells the user to open `/plugins` in Codex and install
Worktrunk from the marketplace.
- Adds `wt config plugins codex uninstall`.
  - Removes the Worktrunk marketplace entry.
- Intentionally leaves already-installed plugins and global Codex hook
feature flags unchanged, because users may have configured hook settings
for other hooks.
- Adds a `CODEX` section to `wt config show` when the Codex CLI is
available.
  - It reports Codex CLI availability.
- It avoids claiming the plugin is installed, because the CLI path only
detects the Codex binary.

### Documentation

- Adds a new Codex integration page with installation, bundled activity
hooks, worktree workflow, LLM commit setup, and a Claude Code
comparison.
- Updates README, overview docs, tips/patterns, FAQ, and generated skill
references so Codex is listed alongside Claude Code/OpenCode where
relevant.
- Keeps the Claude Code docs as the place for Claude-only worktree
lifecycle hook behavior.

## Why this shape?

The implementation keeps Codex separate from the existing Claude plugin
command instead of abstracting over both. That is intentional: Claude
installs a plugin directly, while the Codex flow configures a
marketplace and still requires the user to install from `/plugins`. A
shared abstraction would hide those differences and make the user-facing
behavior easier to misread.

The docs also avoid presenting Codex as feature-identical to Claude
Code. Codex gets skills and bundled activity hooks here, but not
automatic worktree lifecycle routing.

## Review guide

- Plugin packaging: `.codex-plugin/plugin.json`,
`.agents/plugins/marketplace.json`, `hooks/hooks.json`
- CLI behavior: `src/commands/config/codex.rs`, `src/cli/config.rs`,
`src/commands/config/show.rs`
- Test helpers and coverage: `src/testing/mod.rs`,
`tests/integration_tests/config_show.rs`,
`tests/integration_tests/help.rs`
- Primary docs: `docs/content/codex.md`
- Generated docs/snapshots: `skills/worktrunk/reference/codex.md`,
config/help snapshots

## Tests

- `cargo fmt --check`
- `cargo check --lib --bins`
- `cargo test --lib --bins`
- `cargo test --test integration test_docs_are_in_sync`
- `cargo test --test integration "test_help"`
- `cargo test --test integration config_show`
- `git diff --check HEAD`

## Local pre-merge note

`cargo run -- hook pre-merge --yes` could not complete locally because
this environment does not have `pre-commit` or `cargo-nextest`
installed. The hook's doctest/doc portions did run and passed.

---------

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>
2026-05-16 20:30:32 -07:00
Maximilian Roos 64e1b4efb9 feat(cli): move approvals subcommand from hook to config (#2282) 2026-04-18 00:46:27 -07:00
Maximilian Roos 5279d1f671 Route --help output to stdout (#2155)
Worktrunk sent all help output to stderr. This is an outlier versus most
CLIs (cargo, curl, python, `git <cmd> -h` — all stdout) and versus
`--version`, which moved to stdout in #2072 for the same reasons. Move
help to stdout and canonicalize the shared pager function so its other
callers (`wt config show`, `wt hook show`, `wt config state`) emit on
stdout too.

The prior rationale — "stdout is reserved for data output" so `wt config
show | jq` works — was already self-contradictory: `config show` went
through the same pager helper and was landing on stderr.

## What changed

- `src/help_pager.rs` — removed the `>&2` pager redirect and switched
fallback prints from `std::print!` to the anstream-aware
`worktrunk::styling::print`, so piped/redirected output is plain text
rather than raw ANSI. Simplified the TTY guard to check stdout only (the
`stdout OR stderr` check was a leftover from the redirect era).
- `src/help.rs` — DisplayHelp fallback prints via `styling::println`.
- `src/commands/config/{show,state}.rs`, `src/commands/hook_commands.rs`
— pager fallbacks print to stdout.
- Added `test_help_goes_to_stdout` and
`test_help_strips_ansi_when_piped` as regression anchors (parallel to
existing `test_version_goes_to_stdout`). Verified the ANSI test catches
the bug by reverting temporarily.
- Updated ~90 snapshots where content moved from the `----- stderr
-----` section to `----- stdout -----`.

## User-visible behavior change

Scripts that did `wt --help 2>/dev/null | x` previously discarded help
and piped nothing; they now pipe the help text. `wt --help > file`
captures help instead of leaving the file blank. Both changes align
Worktrunk with POSIX conventions. Terminal usage (pager on TTY) is
unchanged.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:30:26 -07:00
Worktrunk Bot 8742c3a648 fix: route wt --version output to stdout (#2073) 2026-04-11 07:12:58 -07:00
worktrunk-bot 683327a431 docs: improve merge command help text clarity (#1783) 2026-03-28 09:51:30 -07:00
Maximilian Roos efad9312db feat!: rationalize hooks — rename post-create, add post-commit, background post-merge (#1679)
Implements the hook rationalization from #1670, establishing a symmetric
`pre-` (blocking) / `post-` (background) pattern for every lifecycle
event.

## Changes

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

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

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

The hook table is now a clean symmetric grid:

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

## Testing

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

Closes #1670

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-23 00:33:30 -07:00
Maximilian Roos 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>
2026-02-22 13:58:34 -08:00
Maximilian Roos bc2ee2dcb6 Add experimental wt step promote command (#789)
## Summary

Adds `wt step promote` — an experimental command to exchange branches
between the main worktree and another worktree, including swapping
gitignored files (build artifacts, `.env`, `node_modules/`, etc.). This
addresses #738 where users want to move a branch from a worktree into
the main worktree.

This is useful when the main worktree has special significance (Docker
Compose configs, IDE settings, heavy build artifacts anchored to project
root) and hooks aren't yet set up to run on arbitrary worktrees.

**Key features:**
- Exchange branches between main worktree and any other worktree
- Swap gitignored files so build artifacts follow their branch
- Auto-restore: `wt step promote` from main worktree (no args) restores
default branch
- Shows mismatch state in `wt list` with ⚑ indicator
- Warns when creating mismatch, with hint to restore
- `.worktreeinclude` filtering respected per worktree

**Swap algorithm (stage → exchange → distribute):**
1. Discover gitignored entries before branch exchange (`.gitignore`
belongs to the branch)
2. Stage files to `.git/wt-promote-staging/` before `git switch`
(prevents tracked file collision)
3. Exchange branches with per-step rollback
4. Cross-distribute staged files to new worktrees
5. Best-effort restore on failure with recovery guidance

**Adversarial testing (4 rounds, 9 issues found and fixed):**
- Stage before switch to prevent tracked file collision
- Per-step rollback in `exchange_branches()`
- Bail on leftover staging (don't destroy previous run's files)
- Recovery guidance on all failure paths
- Dedicated `restore_staged()` for rollback semantics
- `ErrorKind::CrossesDevices` for cross-device fallback
- `Option<PathBuf>` instead of sentinel empty path

## Test plan

- [x] 25 integration tests covering: bidirectional swap, round-trip,
`.worktreeinclude` filtering, nested directories,
same-filename-different-content, tracked-vs-ignored separation, stale
staging detection, bare repo rejection, detached HEAD, dirty worktrees,
no-arg from main/linked, branch argument, already-in-main, mismatch
indicator in `wt list`
- [x] All 2576 tests pass
- [x] All lints clean
- [x] `copy-ignored` tests still pass after
`list_and_filter_ignored_entries` extraction

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

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-21 10:44:38 -08:00
Maximilian Roos 8587b66080 docs: update stale documentation and fix version snapshot filter (#1113)
## Summary

- Remove stale "Approved hook commands" subsection from config docs —
approvals moved to `approvals.toml` and no longer belong in the user
config section
- Fix version regex filters in test infra that failed to handle `-dirty`
suffix on bare hash versions (when `git describe` has no reachable
tags), making snapshots non-deterministic
- Add `commands.jsonl` documentation to `wt config state logs` help text
- Add "Documentation accuracy" checklist to PR review skill
- Remove deleted `test_clone_bare_repo_list_no_status_errors` (covered
by #1112's `BareRepoTest`)
- Minor cleanup: remove "select" from CLAUDE.md command list, remove
stale pre-release-replacements, update skill version marker

## Test plan

- [x] All 1084 tests pass locally
- [x] Pre-commit lints pass
- [x] Snapshot tests reviewed — version snap now shows `wt [VERSION]`
(not `wt [VERSION]-dirty`), config help snaps reflect removed approvals
section
- [x] Doc sync test passes
(`test_command_pages_and_skill_files_are_in_sync`)

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-19 10:02:40 -08:00
Maximilian Roos eaadf1314b Fix extra linebreak after demo comments in help output (#795)
Demo HTML comments like `<!-- demo: file.gif -->` had blank lines on both
sides in the source. When the comment was stripped for terminal display,
both blanks remained, creating a double blank line.

Fix by removing the trailing blank after demo comments in cli/mod.rs.
Add a check in snapshot_help() to catch this pattern in all help tests.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-21 22:55:51 -08:00
Maximilian Roos 16f7632143 refactor: simplify help snapshot tests and remove dead README sync code (#783)
- Remove ANSI reset filters from help tests; snapshots now show actual output
- Remove dead MarkerType::Snapshot path in readme_sync (README only has section markers)
- Remove unused OutputFormat::Readme, parse_snapshot_with_command, strip_code_block
- Use NO_COLOR=1 for help command discovery instead of stripping ANSI after

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-21 16:09:28 -08:00
Maximilian Roos 3fee985684 Remove redundant --help-page snapshot tests (#579)
The readme_sync test already verifies --help-page output against docs.
These snapshots duplicated that coverage.

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-12 20:59:29 -08:00
Maximilian Roos f88c40560e feat: suggest nested subcommands for unrecognized commands (#556)
* Add subcommand alias rewriting for nested commands

Add functionality to rewrite top-level subcommand aliases to their parent
commands. Users can now type `wt squash` instead of `wt step squash`, with
the CLI automatically expanding it to the full command path.

Implement `rewrite_subcommand_aliases()` to map nested subcommands (e.g.,
squash, unsquash under step; pre-push, post-merge under hook) to their
parents, handling global flags like `-C`, `--config`, and `-v` correctly.

Enable clap's `infer_subcommands` feature to support ambiguous subcommand
matching, and integrate alias rewriting into the main argument parsing flow.

* feat: suggest nested subcommands for unrecognized commands

When users type a nested subcommand at the top level (e.g., `wt squash`
instead of `wt step squash`), show a helpful suggestion pointing to
the correct command path.

- Add `suggest_nested_subcommand()` to check if unknown command matches
  a subcommand of `step` or `hook`
- Enhance error handler to show "did you mean wt step squash?" tip
- Add snapshot tests for step and hook subcommand suggestions

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-11 21:54:38 -08:00
Maximilian Roos 928b3017cd test: remove verbose assertion messages and doc comments (#458)
* Simplify assert messages by removing redundant descriptions

Remove verbose assertion messages that repeat the condition being tested.
Messages like "Should contain indicator" when asserting `contains("●")` are
redundant since the code is self-documenting. This reduces noise in tests
while maintaining clarity through meaningful assertion failures.

* test: remove verbose assertion messages and doc comments

Removed facile test verbosity across three categories:

1. **Doc comments** (~236 lines): Removed `/// Test X` comments that just
   restated test function names. Kept multi-line explanations that add context.

2. **Test names** (9 functions): Shortened overly verbose names by removing
   filler words while maintaining clarity:
   - test_merge_when_primary_not_on_default_but_default_has_worktree →
     test_merge_primary_not_on_default_with_default_worktree
   - test_complete_switch_option_prefix_shows_options_not_branches →
     test_complete_switch_option_prefix_shows_options

3. **Assertion messages** (19 instances): Removed messages that just restate
   the check:
   - assert!(path.exists(), "Path should exist") → assert!(path.exists())
   - assert!(x.is_file(), "Should be a file") → assert!(x.is_file())

Kept messages that add semantic meaning (e.g., "hook should have run") or
include debugging variables.

Total: 279 lines removed across 31 files. All 723 integration + 401 unit tests
passing.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* test: remove unreferenced snapshot files from renamed tests

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-06 16:51:44 -08:00
Maximilian Roos a0db586e68 Improve wt list output for docs site (#245)
* Add table wrapping in help output and expand JSON documentation

Tables in --help now wrap at terminal width with continuation lines
properly aligned. This replaces the previous approach of setting
term_width(200) to prevent clap from breaking tables.

Key changes:
- Add ```table code fence format for markdown tables
- Clap no longer wraps content (HELP_TERM_WIDTH = usize::MAX)
- Our markdown renderer handles all wrapping
- Expand wt list JSON documentation with Type column and nested
  object details (commit, working_tree, main, remote, worktree, ci)
- Fix ANSI reset codes appearing at start of wrapped lines
- Rename JSON field pr → ci for clarity

The table format uses a simpler pipe-delimited syntax that's easier
to maintain in source, while rendering with proper column alignment
and width constraints.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Replace custom table rendering with termimad

Use termimad for markdown table rendering in help text. This provides:
- Automatic column width calculation and balancing
- Cell content wrapping with proper alignment
- Simpler implementation (~100 lines removed)

Trade-offs:
- +99 KB binary size (0.8% increase)
- 9 new crate dependencies
- Backticks stripped from table cells
- 1-space column gaps instead of 2

The custom table rendering code is replaced with termimad's text()
function using a no_style skin with custom border characters for
our separator style (separate line segments under each column).

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Simplify md_help and standardize JSON boolean fields

- Remove ```table code fence format (use standard markdown tables)
- Deduplicate link stripping by having render_inline_formatting call strip_markdown_links
- Remove bare field from JSON output (worktrees are never bare by definition)
- Make boolean fields always present in JSON (is_current, is_previous, detached)
- Simplify HELP_TERM_WIDTH comment

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Wrap command reference at 80 chars for web docs

Add width parameter to get_help_reference() so web docs via --help-page
can request wrapped output. Terminal help uses default (no limit, our
markdown renderer handles it).

This prevents horizontal scroll in the command reference sections on
the docs site. Example terminal output tables still have internal
scroll (contained within their blocks), which is expected for wide
tabular output.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Reduce README example width to fit docs terminal blocks

Changed COLUMNS from 115 to 90 for README examples so the table
output fits within the docs terminal block width (~87 chars max)
without horizontal scroll.

Trade-off: Message column is now hidden in examples (shows
"1 column hidden" / "2 columns hidden"). All other columns
(Branch, Status, HEAD±, main↕, Path, Remote⇅, Commit, Age)
remain visible.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Add Message column to list output

Replace Path column with Message column by default. Message shows the last
commit message (truncated to fit available width). Path is now hidden unless
explicitly shown. Adjust minimum Message width from 20 to 10 chars, increase
README example terminal width from 90 to 98 columns, and update column
allocation logic to handle spacing after Gutter columns correctly.

* Tune list output width and rename truncation function

- Reduce README_COLUMNS from 105 to 98 to fit browser viewport
- Reduce MIN_MESSAGE from 20 to 10 to show Message in narrow layouts
- Rename truncate_at_word_boundary → truncate_to_width (now truncates
  mid-word for consistent column widths)
- Fix spacing calculation after Gutter column with needs_spacing helper
- Update tests that incorrectly assumed Path always visible

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix help output indentation by preserving leading whitespace

The clap library's next_line_help(true) option adds proper indentation
to help descriptions (2 spaces before flags, 10 spaces before descriptions).
However, wrap_ansi's default trim_whitespace(true) was stripping this
indentation.

Changes:
- Add next_line_help(true) to apply_help_template_recursive in cli.rs
- Remove term_width(usize::MAX) which was no longer needed
- Configure wrap_ansi with trim_whitespace(false) to preserve indentation

The fix ensures help output is properly indented:

  Commands:
    config
            Manage configuration and shell integration

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Refactor help template to remove next_line_help

* Add DEFAULT_HELP_WIDTH constant for consistent doc width

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

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-18 09:37:03 -08:00
Maximilian Roos 081087a171 Add tests for --version, --help-md, and --help-page flags (#220)
* Add tests for --version, --help-md, and --help-page flags

Tests cover previously uncovered main.rs code paths:
- --version flag output
- --help-md raw markdown output (for docs generation)
- --help-page documentation page generation (merge, switch commands)
- --help-page error cases (no subcommand, unknown command)

Coverage: 87.47% → 87.83%

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix version test filter to handle CI git hash format

The version output varies between environments:
- Local: wt v0.4.0-25-gc9bcf6c0
- CI: wt 7df940e (just short hash)

Updated filter regex to match both formats.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix formatting in help.rs

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

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-15 13:28:47 -08:00
Maximilian Roos 7c47e441c4 Add previous-branch state key and state clear command (#181)
Extends the `wt config state` command with:

- `previous-branch` key: get/set/clear the previous branch used by `wt switch -`
- `state clear` (no args): clears all stored state at once
- `state get` (renamed from `show`): shows all stored state

Also improves consistency across state operations:
- All clear operations show info message when nothing to clear
- Renamed "cached state" to "stored state" (more accurate term)
- `clear_default_branch_cache()` returns bool for proper messaging
- JSON field uses `previous_branch` (not `switch_previous`)

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-12 12:00:36 -08:00
Maximilian Roos 4ad3d4e00f Unify var and cache into state subcommand (#178)
Replace `wt config var` and `wt config cache` with a unified `wt config state`
subcommand that provides consistent get/set/clear semantics for all runtime
state.

New CLI structure uses key-first subcommands:
- `wt config state default-branch get [--refresh]`
- `wt config state default-branch set <branch>`
- `wt config state default-branch clear`
- `wt config state ci-status get [--refresh] [--branch=X]`
- `wt config state ci-status clear [--branch=X | --all]`
- `wt config state marker get [--branch=X]`
- `wt config state marker set <value> [--branch=X]`
- `wt config state marker clear [--branch=X | --all]`
- `wt config state logs get`
- `wt config state logs clear`
- `wt config state show`

Key changes:
- Added ability to manually set/clear default branch
- Each key is a subcommand with only its valid flags (type-safe at parse time)
- Removed runtime validation since clap handles invalid combinations
- Reuse format_relative_time_short for age formatting in logs

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

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-12 02:04:13 -08:00
Maximilian Roos 430a8aa331 Add test fixtures and refactor test infrastructure
Expand test fixtures with repo_with_remote_and_feature, repo_with_alternate_primary,
and repo_with_multi_commit_feature. Add TestRepo::add_feature() helper method and
refactor help tests to use rstest parametrization. Update test files to use new
fixtures, reducing boilerplate setup code.
2025-12-11 13:19:32 -08:00
Maximilian Roos 4ee9334d0f Add PowerShell directive tests and fix test file cfg placement (#125)
* Add PowerShell directive tests and fix test file cfg placement

- Add snapshot tests for PowerShell directives (switch, merge, remove)
- Fix misplaced #![cfg(unix)] attributes in test files
- Refactor shell config defaults for consistency

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Gate Unix-specific permission tests with #[cfg(unix)]

These tests use std::os::unix::fs::PermissionsExt which doesn't exist on
Windows. Gate them to only compile and run on Unix platforms.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Retrigger CI

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Normalize PowerShell remove test output for cross-platform compatibility

The removal message varies between platforms due to worktree/branch state
detection differences. Add filter to normalize the message in snapshots.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Normalize doubled ANSI codes in PowerShell remove test

Unix outputs [36m[36m (doubled), Windows outputs [36m (single).
Add filter to normalize this platform difference.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Skip PowerShell remove test on Windows due to ANSI escape code differences

Unix and Windows handle ANSI escape code output differently:
- Unix: ESC[36mESC[36m (doubled)
- Windows: ESC[36m (single)

This is a snapshot compatibility issue only - the actual Set-Location
directive works correctly on both platforms. Skip the test on Windows
to unblock CI while keeping test coverage on Unix.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Escape Windows paths in regex filters for snapshot tests

Windows paths contain backslashes which are interpreted as regex escape
sequences. Use regex::escape() to properly escape paths before using them
as filter patterns in insta snapshot settings.

Fixed in:
- tests/common/mod.rs: setup_snapshot_settings_with_home, setup_home_snapshot_settings
- tests/integration_tests/hook_show.rs: test_hook_show_with_both_configs
- tests/integration_tests/statusline.rs: claude_code_snapshot_settings

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Set USERPROFILE for Windows home directory detection in tests

The `home` crate uses USERPROFILE (not HOME) to detect the home
directory on Windows. Add USERPROFILE to set_temp_home_env() so
that format_path_for_display() correctly replaces paths with ~
on Windows.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Filter out Windows-specific PowerShell lines in snapshot tests

On Windows, PowerShell is detected as a shell to configure, adding extra
lines to config show output. Filter these out so snapshots remain compatible
across platforms.

Also removed duplicate Broken pipe filter.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Canonicalize worktree path for is_current comparison on Windows

On Windows, paths from `git worktree list --porcelain` may have different
formats than paths from `git rev-parse --show-toplevel` (which are
canonicalized). Canonicalize both paths before comparison to ensure
the current worktree is correctly identified.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Add forward-slash path filters for Windows JSON output

On Windows, paths in JSON output may have forward slashes (after the
backslash conversion filter). Add filters for both backslash and
forward-slash versions of repo and worktree paths to ensure proper
replacement regardless of filter application order.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Add wt.exe→wt filter for Windows help output

On Windows, clap shows 'wt.exe' instead of 'wt' in help output.
Add filter to normalize this for cross-platform snapshot consistency.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Add USERPROFILE env var to switch and post_start tests

On Windows, the `home` crate uses USERPROFILE instead of HOME.
Test helper functions that set HOME also need to set USERPROFILE
for path normalization (format_path_for_display) to work correctly.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Add USERPROFILE redaction for env metadata in snapshots

On Windows, tests that set USERPROFILE will have it captured in
snapshot metadata. Add redaction to normalize it to [TEST_HOME].

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Normalize trailing ANSI resets in help output for Windows

Windows terminal strips trailing \x1b[0m reset codes that Unix includes.
Add filters to remove these trailing resets for cross-platform consistency.
Update all help snapshots to not include trailing resets.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Add trailing ANSI reset filter to setup_snapshot_settings

Windows terminal strips trailing \x1b[0m reset codes that Unix includes.
Add the same filter used in help.rs to the central setup_snapshot_settings
function so all snapshot tests normalize trailing resets consistently.

Update 150+ snapshots to remove trailing ANSI reset codes.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix ANSI-to-HTML conversion for snapshots with stripped trailing resets

The trailing ANSI reset filter (for Windows compatibility) strips [0m codes
from line endings, causing the ansi-to-html library to produce malformed HTML
with tags spanning across lines.

Fix by adding reset codes back at line boundaries in readme_sync before HTML
conversion. This ensures each line gets proper HTML tag closure.

Also update inline snapshots in config_show.rs to match the new filter behavior.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Use dunce for cross-platform path canonicalization in tests

On Windows, std::fs::canonicalize() returns verbatim paths (\\?\C:\...)
which git cannot handle, causing 120 test failures with errors like:
  fatal: could not create leading directories of '//?/C:/...'

The dunce crate provides a drop-in replacement that strips the verbatim
prefix when safe, while on Unix it's a no-op wrapper. This fixes Windows
CI while preserving macOS symlink resolution (/var -> /private/var).

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix formatting

* Use dunce for path canonicalization in source code

The dunce crate strips Windows verbatim path prefixes (\\?\) which external
tools like git cannot handle. This complements the test utilities fix by
also updating the main source code:

- src/git/repository/mod.rs: git_common_dir, git_dir, worktree_root
- src/commands/worktree.rs: switch_to_existing helper
- src/commands/list/collect.rs: current worktree and path mismatch detection

On Unix, dunce::canonicalize is a no-op wrapper around std::fs::canonicalize,
so there's no behavioral change on macOS/Linux.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Add tilde-prefixed path filters and APPDATA env for Windows

On Windows, temp directories are under the user's home directory
(C:\Users\<user>\AppData\Local\Temp), so format_path_for_display()
converts them to tilde-prefixed paths (~/AppData/Local/Temp/...).

The existing snapshot filters work on absolute paths but miss these
tilde-prefixed paths. Add filters for:
- Tilde-prefixed repo paths
- Tilde-prefixed worktree paths

Also set APPDATA env var in set_temp_home_env() to redirect etcetera's
config_dir() (which uses %APPDATA% on Windows) to the temp home,
ensuring fish completions go to the expected ~/.config/ location.

Note: Canonicalize home_dir() as well, since on Windows home::home_dir()
may return a short path (C:\Users\RUNNER~1) while dunce::canonicalize
returns the long path (C:\Users\runneradmin), causing strip_prefix to fail.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix ANSI styling filter to be Windows-only

The previous filter converted ESC[2m (dim) to ESC[1m (bold) on all
platforms, breaking Unix tests that expected dim styling.

Changed to Windows-only filter that converts ESC[1m (bold) to ESC[2m
(dim), matching how clap renders backticked text differently per platform:
- Windows: bold (ESC[1m)
- Unix: dim (ESC[2m)

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Remove ANSI bold-to-dim filter that broke application styling

The filter was converting ALL bold (ESC[1m) to dim (ESC[2m), which broke
the application's intentional bold styling in warning messages and other
output - not just clap help text.

Reverting to see the actual Windows-only failures without this filter.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Filter out select command from Windows help tests

The select command is Unix-only (#[cfg(unix)]) because it uses TUI.
On Windows, help output doesn't include the select command, so filter
it out for cross-platform snapshot consistency.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix Windows help test styling and skip bare repo merge test

- Add filter to convert Windows backtick-quoted text to ANSI bold
  (clap uses backticks instead of ANSI codes on Windows)
- Skip test_bare_repo_merge_workflow on Windows due to file locking
  issues that prevent worktree removal during background cleanup

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Skip platform-specific tests and fix hardcoded paths on Windows

- help.rs: Skip entire module on Windows (clap markdown formatting differs)
- init.rs: Skip on Windows (shell scripts for bash/zsh/fish, CRLF issues)
- readme_sync.rs: Skip on Windows (depends on help output formatting)
- list.rs: Use std::env::temp_dir() instead of hardcoded /tmp
- remove.rs: Skip 3 tests on Windows (file locking prevents removal)
- user_hooks.rs: Skip pre-remove test on Windows (uses /tmp, file locking)

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Skip additional platform-specific tests on Windows

- merge.rs: Skip 4 README example tests (use Unix shell commands)
- post_start_commands.rs: Skip JSON stdin test (uses `cat` command)
- statusline.rs: Skip 3 Claude Code tests (JSON path escaping)
- switch.rs: Skip execute failure and hints tests (shell behavior)

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Allow dead_code for Windows-only unused test utilities

The test_env_vars method and ExponentialBackoff.timeout field are only
used by Unix PTY tests (approval_pty.rs, select.rs, progressive_output.rs).
On Windows, these items appear unused because the PTY tests are cfg-gated.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Update snapshots after merge from main

Syncs snapshot formatting with main branch ANSI code changes.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Extract canonicalize() to shared path module

- Move duplicated canonicalize() wrapper to src/path.rs
- Update imports in git/repository/mod.rs, commands/worktree.rs, commands/list/collect.rs
- Clarify comment in shell_exec.rs for bash path detection

The canonicalize() function wraps dunce::canonicalize() to handle Windows
verbatim paths (\\?\) that external tools like git cannot handle.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Reorder imports in worktree.rs

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Use dunce::canonicalize directly instead of wrapper

Remove the thin wrapper function and import dunce::canonicalize directly
at call sites. The wrapper provided no additional logic.

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

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-10 15:32:13 -08:00
Maximilian Roos b7b1b9e3c8 Move approvals command from config to hook
The `approvals` subcommand logically belongs under `wt hook` since it manages approval of hook commands. This reorganizes the CLI structure to group related functionality together. Users should now use `wt hook approvals add/clear` instead of `wt config approvals add/clear`.
2025-12-10 03:12:39 -08:00
Maximilian Roos 2da8c3304c feat(config): Introduce wt config var command
Renames `wt config status` to `wt config var`, allowing the management
of various runtime variables stored in git config.

This change:
- Renames `worktrunk.status.<branch>` to `worktrunk.marker.<branch>` for consistency.
- Extends the command to support `get` operations for `default-branch`, `marker`, and `ci-status`.
- Updates all references in documentation, plugin hooks, and internal code.
2025-12-01 13:58:05 -08:00
Maximilian Roos 41188f5f7e Move select and statusline out of beta
This change makes `select` a top-level command and `statusline` a subcommand of `list`.
The `beta` subcommand is removed.
2025-11-28 10:23:03 -08:00
Maximilian Roos 324f301ee2 Docs: Add explicit wt prefix in Usage lines for clarity 2025-11-27 12:16:09 -08:00
Maximilian Roos 59ab929513 Consolidate cache management under wt config cache
This change introduces a new `wt config cache` subcommand to manage all caching functionality, including:

- `wt config cache show`: Displays all cached data, including default branch and CI status.
- `wt config cache clear`: Clears specified or all caches.
- `wt config cache refresh`: Refreshes the default branch cache by querying the remote.

Previously, `wt config refresh-cache` only managed the default branch cache. This commit centralizes caching logic, updates documentation, and enhances user control over cached data.
2025-11-26 19:18:46 -08:00
Maximilian Roos 6620ccad49 Recognize implicit help requests to show help
This ensures `wt` shows its help message when run with no arguments.
2025-11-25 14:12:38 -08:00
Maximilian Roos c77f0fa62f refactor: Rename approvals ask to approvals add
Renames the `wt config approvals ask` subcommand and its associated handlers and tests to `wt config approvals add`.

The `add` verb better reflects the action of adding an approval to the configuration, aligning with `clear` for removing approvals. This change improves clarity and consistency in the CLI.
2025-11-24 20:15:43 -08:00
Maximilian Roos 73138958f6 feat: Rename config list to config show and enhance shell integration
Renamed the `wt config list` command to `wt config show` for better clarity.
This command now includes enhanced shell integration checks:
- Displays shell status for better visibility.
- Verifies `compinit` order for Zsh to ensure proper completion loading.
2025-11-24 18:03:22 -08:00
Maximilian Roos d6d371a34f Refactor help output to use inline markdown renderer
This commit updates the help output to use a custom, minimal markdown renderer instead of `termimad`. This allows for more consistent styling and control over how markdown is displayed in the terminal.

Changes include:
- Removing `termimad` dependency.
- Replacing `HELP_TEMPLATE_WITH_SUBCOMMANDS` with a single `HELP_TEMPLATE` for all commands.
- Updating `md_help.rs` to implement basic markdown rendering for headers, bold text, inline code, and code blocks.
- Modifying `cli.rs` to use the new markdown rendering in `after_long_help` and other documentation strings.
- Updating help snapshots to reflect the new rendering.
2025-11-22 22:45:47 -08:00
Maximilian Roos 7ca9f3596c refactor: Rename config init to config create 2025-11-21 03:36:47 -08:00
Maximilian Roos fdc1085a12 refactor: Use consistent help template and update CLI parsing 2025-11-20 19:28:00 -08:00