worktrunk 0.1.13

A Git worktree manager for trunk-based development
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
# Worktrunk Development Guidelines

> **Note**: This CLAUDE.md is just getting started. More guidelines will be added as patterns emerge.

## Project Status

**This project was released very recently and has very few backward compatibility concerns.**

We are in **early release** mode:
- Breaking changes are generally acceptable
- Optimize for the best solution, not compatibility with previous versions
- No Rust library compatibility concerns (this is a CLI tool only)

**CLI deprecation policy:** When renaming or removing CLI options, retain the old option with a deprecation warning if there's no code complication cost. If supporting both old and new would add complexity, just make the breaking change.

When making decisions, prioritize:
1. **Best technical solution** over backward compatibility
2. **Clean design** over maintaining old patterns
3. **Modern conventions** over legacy approaches

Acceptable breaking changes: config locations, output formats, dependencies, codebase structure. CLI flag changes can be breaking if deprecation warnings would complicate the code.

## Code Quality

Claude commonly makes the mistake of adding `#[allow(dead_code)]` when writing code that isn't immediately used. Don't suppress the warning—either delete the code or add a TODO comment explaining when it will be used.

Example of escalating instead of suppressing:

```rust
// TODO(feature-name): Used by upcoming config validation
fn parse_config() { ... }
```

## Terminology

Use consistent terminology in documentation, help text, and code comments:

- **main worktree** — when referring to the primary worktree (the original git directory)
- **default branch** — when referring to the branch (main, master, etc.)

Avoid mixing: "main/default branch worktree" is confusing. Use "main worktree" for worktrees and "default branch" for branches.

## Testing

### Running Tests

```bash
# Unit tests (fast, ~210 tests)
cargo test --lib --bins

# Integration tests without shell tests (~370 tests, no external dependencies)
cargo test --test integration

# Integration tests WITH shell tests (~420 tests, requires bash/zsh/fish)
cargo test --test integration --features shell-integration-tests

# Run all tests via pre-merge hook (recommended before committing)
cargo run -- step pre-merge --force
```

The pre-merge hook runs the full test suite and is the recommended way to verify changes before committing.

**Shell integration tests** require bash, zsh, and fish. On Linux, run `./dev/setup-claude-code-web.sh` to install them.

### Claude Code Web Environment

When working in Claude Code web, run the setup script first:

```bash
./dev/setup-claude-code-web.sh
```

This installs required shells (zsh, fish) and builds the project. The permission tests (`test_permission_error_prevents_save`, `test_approval_prompt_permission_error`) automatically skip when running as root, which is common in containerized environments.

### CLI Flag Descriptions

Keep the first line of flag and argument descriptions brief—aim for 3-6 words. Use parenthetical defaults sparingly, only when the default isn't obvious from context.

**Good examples:**
- `/// Skip approval prompts`
- `/// Show CI, conflicts, and full diffs`
- `/// Target branch (defaults to default branch)`

**Bad examples (too verbose):**
- `/// Auto-approve project commands without saving approvals.`
- `/// Show CI status, conflict detection, and complete diff statistics`

The help text should be scannable. Users reading `wt switch --help` need to quickly understand what each flag does without parsing long sentences.

## CLI Output Formatting Standards

### User Message Principles

Output messages should acknowledge user-supplied arguments (flags, options, values) by reflecting those choices in the message text.

```rust
// User runs: wt switch --create feature --base=main
// ✅ GOOD - acknowledges the base branch
"Created new worktree for feature from main at /path/to/worktree"
// ❌ BAD - ignores the base argument
"Created new worktree for feature at /path/to/worktree"
```

**Avoid second-person ("you", "your"):** Messages should describe actions and state, not address the user directly.

```rust
// ❌ BAD - second-person
"Use 'wt merge' to rebase your changes onto main"
// ✅ GOOD - describes the action
"Use 'wt merge' to rebase onto main"

// ❌ BAD - second-person
"Add one line to your shell config"
// ✅ GOOD - refers to the thing
"Add one line to the shell config"
```

**Avoid redundant parenthesized content:** Parenthesized text should add new information, not restate what's already said.

```rust
// ❌ BAD - parentheses restate "no changes"
"No changes after squashing 3 commits (commits resulted in no net changes)"
// ✅ GOOD - clear and concise
"No changes after squashing 3 commits"
// ✅ GOOD - parentheses add supplementary info
"Committing with default message... (3 files, +45, -12)"
```

**Avoid pronouns with cross-message referents:** Hints appear as separate messages from errors. Don't use pronouns like "it" that refer to something mentioned in the error message.

```rust
// ❌ BAD - "it" refers to branch name in error message
// Error: "Branch 'feature' not found"
// Hint:  "Use --create to create it"
// ✅ GOOD - self-contained hint
// Error: "Branch 'feature' not found"
// Hint:  "Use --create to create a new branch"
```

### Message Consistency Patterns

Use consistent punctuation and structure for related messages:

**Semicolon for qualifiers:** Separate the action from a qualifier/reason:
```rust
// Action; qualifier (flag)
"Removing feature worktree in background; retaining branch (--no-delete-branch)"
"Commands approved; not saved (--force)"
```

**Ampersand for conjunctions:** Use `&` for combined actions:
```rust
// Action & additional action
"Removing feature worktree & branch in background"
"Commands approved & saved to config"
```

**Explicit flag acknowledgment:** Show flags in parentheses when they change behavior:
```rust
// ✅ GOOD - shows the flag explicitly
"Removing feature worktree in background; retaining branch (--no-delete-branch)"
// ❌ BAD - doesn't acknowledge user's explicit choice
"Removing feature worktree in background; retaining branch"
```

**Parallel structure:** Related messages should follow the same pattern:
```rust
// ✅ GOOD - parallel structure with integration reason explaining branch deletion
// Both wt merge and wt remove show integration reason when branch is deleted
"Removing feature worktree & branch in background (already in main)"       // Branch is integrated (ancestor)
"Removing feature worktree & branch in background (files match main)"      // Branch is integrated (squash/rebase)
"Removing feature worktree & branch in background (all changes in main)"   // Branch is integrated (squash + main advanced)
"Removing feature worktree in background; retaining unmerged branch"        // Unmerged (system keeps)
"Removing feature worktree in background; retaining branch (--no-delete-branch)"  // User flag (user keeps)
```

**Compute decisions once:** For background operations, check conditions upfront, show the message, then pass the decision explicitly rather than re-checking in background scripts:
```rust
// ✅ GOOD - check once, pass decision
let should_delete = check_if_merged();
show_message_based_on(should_delete);
spawn_background(build_command(should_delete));

// ❌ BAD - check twice (once for message, again in background script)
let is_merged = check_if_merged();
show_message_based_on(is_merged);
spawn_background(build_command_that_checks_merge_again());  // Duplicate check!
```

### Message Types

Seven canonical message patterns with their emojis:

1. **Progress**: 🔄 (operations in progress)
2. **Success**: ✅ (successful completion)
3. **Errors**: ❌ (failures, invalid states)
4. **Warnings**: 🟡 (non-blocking issues)
5. **Hints**: 💡 (actionable suggestions, tips for user)
6. **Info**: ⚪ (neutral status, system feedback, metadata)
7. **Prompts**: ❓ (questions requiring user input)

**Output functions automatically add emoji AND semantic color.** Callers provide content with optional inner styling (like `<bold>`):

```rust
// Simple message - function adds emoji + color
output::success("Created worktree")?;
output::hint("Run 'wt config' to configure")?;

// With inner styling - use cformat! for bold/dim within the message
output::success(cformat!("Created worktree for <bold>{branch}</>"))?;
output::warning(cformat!("Branch <bold>{name}</> not found"))?;
```

**Semantic colors added automatically:**
- `success()` → green
- `progress()` → cyan
- `hint()` → dimmed
- `warning()` → yellow
- `info()` → no color (neutral status)

**Every user-facing message requires either an emoji or a gutter** for consistent visual separation.

### Blank Line Principles

- **No leading/trailing blanks** - Start immediately, end cleanly
- **One blank after blocks** - Separate multi-line content (gutter blocks, sections)
- **One blank after prompts** - Separate user input from results
- **Never double blanks** - One blank line maximum between elements

### stdout vs stderr

**Both modes write all messages to stderr.** stdout is reserved for structured data:

- **stdout**: JSON output (`--format=json`), shell scripts (directive mode)
- **stderr**: All user-facing messages (progress, success, errors, hints, gutter, etc.)

**Directive mode** additionally emits a shell script to stdout at the end.

Use the output system (`output::success()`, `output::progress()`, etc.) to handle both modes automatically. Never write directly to stdout/stderr in command code.

```rust
// ✅ GOOD - use output system (handles both modes)
output::success("Branch created")?;

// ❌ BAD - direct writes bypass output system
println!("Branch created");
```

Interactive prompts must flush stderr before blocking on stdin:
```rust
eprint!("❓ Allow and remember? [y/N] ");
stderr().flush()?;
io::stdin().read_line(&mut response)?;
```

### Temporal Locality: Output Should Be Close to Operations

Output should appear immediately adjacent to the operations it describes. Progress messages apply only to slow operations (>400ms): git operations, network requests, builds.

Sequential operations should show immediate feedback:
```rust
for item in items {
    output::progress(format!("Removing {item}..."))?;
    perform_operation(item)?;
    output::success(format!("Removed {item}"))?;  // Immediate feedback
}
```

Bad example (output decoupled from operations):
```
🔄 Removing worktree for feature...
🔄 Removing worktree for bugfix...
                                    ← Long delay, no feedback
Removed worktree for feature        ← All output at the end
Removed worktree for bugfix
```

Signs of poor temporal locality: collecting messages in a buffer, single success message for batch operations, no progress before slow operations.

### Information Display: Show Once, Not Twice

Progress messages should include all relevant details (what's being done, counts, stats, context). Success messages should be minimal, confirming completion with reference info (hash, path).

```rust
// ✅ GOOD - detailed progress, minimal success
output::progress("Squashing 3 commits & working tree changes into a single commit (5 files, +60)...")?;
perform_squash()?;
output::success("Squashed @ a1b2c3d")?;
```

### Style Constants

Style constants in `src/styling/constants.rs` (minimal set for programmatic styling):
- `ADDITION`: Green (diffs, additions) - used in table rendering
- `DELETION`: Red (diffs, deletions) - used in table rendering
- `GUTTER`: BrightWhite background (quoted content)

Emoji constants: `PROGRESS_EMOJI` (🔄), `SUCCESS_EMOJI` (✅), `ERROR_EMOJI` (❌), `WARNING_EMOJI` (🟡), `HINT_EMOJI` (💡), `INFO_EMOJI` (⚪), `PROMPT_EMOJI` (❓)

For all other styling, use color-print tags in `cformat!`: `<red>`, `<green>`, `<yellow>`, `<cyan>`, `<dim>`, `<bold>`, `<bright-black>`

### Styling in Command Code

Use `output::` functions with `cformat!` for styled content. The output function adds the emoji + semantic color, and `cformat!` handles inner styling:

```rust
// ✅ GOOD - output:: handles emoji + outer color, cformat! handles inner styling
output::success(cformat!("Created <bold>{branch}</> from <bold>{base}</>"))?;
output::warning(cformat!("Branch <bold>{name}</> has <dim>uncommitted changes</>"))?;
output::hint(cformat!("Run <bright-black>wt merge</> to continue"))?;

// ✅ GOOD - plain strings work too (no inner styling needed)
output::progress("Rebasing onto main...")?;
output::hint("No changes to commit")?;
```

**Available color-print tags:** `<bold>`, `<dim>`, `<bright-black>`, `<red>`, `<green>`, `<yellow>`, `<cyan>`, `<magenta>`

**Emoji constants in cformat!:** Use `{ERROR_EMOJI}`, `{HINT_EMOJI}`, etc. for messages that bypass output:: functions (e.g., GitError Display impl):

```rust
cformat!("{ERROR_EMOJI} <red>Branch <bold>{branch}</> not found</>")
```

Branch names in messages should be bolded. Tables (`wt list`) use `StyledLine` with conditional styling for branch names.

### Commands and Branches in Messages

Never quote commands or branch names. Use styling to make them stand out:

- **In normal font context**: Use `<bold>` for commands and branches
- **In hints**: Use `<bright-black>` for commands (hint() already applies dimming—no explicit `<dim>` needed)

```rust
// ✅ GOOD - bold in normal context
output::info(cformat!("Use <bold>wt merge</> to continue"))?;

// ✅ GOOD - bright-black for commands in hints
output::hint(cformat!("Run <bright-black>wt list</> to see worktrees"))?;

// ✅ GOOD - plain hint without commands
output::hint("No changes to commit")?;

// ❌ BAD - quoted commands
output::hint("Run 'wt list' to see worktrees")?;
```

### Color Detection

Colors automatically adjust based on environment (NO_COLOR, CLICOLOR_FORCE, TTY detection). When using `output::` functions, this is handled automatically.

For direct terminal I/O (rare - mainly internal output system code), import print macros from `worktrunk::styling`:

```rust
use worktrunk::styling::eprintln;  // Auto-detects color support
```

### Design Principles

- **color-print for all styling** - Use `cformat!` with HTML-like tags (`<green>`, `<bold>`, etc.). Only use anstyle for `StyledLine` table rendering.
- **output:: functions over direct printing** - Use output:: for user messages, which auto-adds emoji + semantic color
- **cformat! for inner styling** - Use `<bold>`, `<dim>` tags within output:: calls
- **Never manual escape codes** - No `\x1b[...` in code
- **YAGNI for presentation** - Most output needs no styling
- **Unicode-aware** - Width calculations respect emoji and CJK characters (via `StyledLine`)
- **Graceful degradation** - Must work without color support

### StyledLine API

For complex table formatting with proper width calculations, use `StyledLine`:

```rust
use worktrunk::styling::StyledLine;
use anstyle::{AnsiColor, Color, Style};

let mut line = StyledLine::new();
line.push_styled("Branch", Style::new().dimmed());
line.push_raw("  ");
line.push_styled("main", Style::new().fg_color(Some(Color::Ansi(AnsiColor::Cyan))));
println!("{}", line.render());
```

See `src/commands/list/render.rs` for advanced usage.

### Gutter Formatting for Quoted Content

Use `format_with_gutter()` for quoted content. Gutter content displays external output (git errors, command output) in a visually distinct block.

```rust
// Show warning message, then external error in gutter
output::warning(cformat!("Could not delete branch <bold>{branch_name}</>"))?;
output::gutter(format_with_gutter(&e.to_string(), "", None))?;
```

**Linebreaks:** Gutter content requires a single newline before it, never double newlines. Output functions (`progress()`, `success()`, etc.) use `println!()` internally, adding a trailing newline. Messages passed to these functions should not include `\n`:

```rust
// ✅ GOOD - no trailing \n
output::progress("Merging...")?;
output::gutter(format_with_gutter(&log, "", None))?;

// ❌ BAD - trailing \n creates blank line
output::progress("Merging...\n")?;
```

### Error Message Formatting

**Single-line errors with variables are fine:**
```rust
// ✅ GOOD - single-line with path variable
.map_err(|e| format!("Failed to read {}: {}", format_path_for_display(path), e))?

// ✅ GOOD - using .context() for simple errors
std::fs::read_to_string(&path).context("Failed to read config")?
```

**Multi-line external output needs gutter formatting:**

When external commands (git, npm, LLM tools, hooks) produce multi-line stderr, use gutter formatting:
1. **Show the command that was run** - Include the full command with arguments so users can debug
2. **Put multi-line output in a gutter** - Don't embed raw stderr inline in the message

Use the `format_error_block` helper in `src/git/error.rs` or follow its pattern:

```rust
// ✅ GOOD - command shown in header, multi-line error in gutter
❌ Commit generation command 'llm --model claude' failed
   ┃ Error: [Errno 8] nodename nor servname provided, or not known

// ❌ BAD - multi-line error embedded inline
❌ Commit generation command 'llm' failed: LLM command failed: Error: [Errno 8]...
```

**Implementation:** See `format_error_block()` in `src/git/error.rs` for the pattern, and `LlmCommandFailed` variant for an example.

### Table Column Alignment

**Principle: Headers and values align consistently within each column type.**

Column alignment follows standard tabular data conventions:

1. **Text columns** (Branch, Path, Message, Commit):
   - Headers: Left-aligned
   - Values: Left-aligned

2. **Diff/numeric columns** (HEAD±, main↕, main…±, Remote⇅):
   - Headers: Right-aligned
   - Values: Right-aligned

**Why:** Right-aligning numeric data allows visual scanning by magnitude (rightmost digits align vertically). Left-aligning text data prioritizes readability from the start. Matching header and value alignment within each column creates a consistent visual grid.

**Implementation:** Headers are positioned within their column width using the same alignment strategy as their values (render.rs).

### Snapshot Testing Requirement

Every command output must have a snapshot test (`tests/integration_tests/`). See `tests/integration_tests/remove.rs` for the standard pattern using `setup_snapshot_settings()`, `make_snapshot_cmd()`, and `assert_cmd_snapshot!()`.

Cover success/error states, with/without data, and flag variations.

## Output System Architecture

### Two Output Modes

Worktrunk supports two output modes, selected once at program startup:
1. **Interactive Mode** - Human-friendly output with colors, emojis, and hints
2. **Directive Mode** - Shell script on stdout (at end), user messages on stderr

Both modes write all messages to stderr. stdout is reserved for structured data (JSON, shell scripts).

The mode is determined at initialization in `main()` and never changes during execution.

### The Cardinal Rule: Never Check Mode in Command Code

Command code must never check which output mode is active. The output system uses enum dispatch - commands call output functions without knowing the mode.

```rust
// ❌ NEVER DO THIS
if mode == OutputMode::Interactive {
    println!("✅ Success!");
}

// ✅ ALWAYS DO THIS
output::success("Success!")?;
```

Decide once at the edge (`main()`), initialize globally, trust internally:

```rust
// In main.rs - the only place that knows about modes
output::initialize(if internal { OutputMode::Directive } else { OutputMode::Interactive });

// Everywhere else - just use the output functions
output::success("Created worktree")?;
output::change_directory(&path)?;
```

### Available Output Functions

The output module (`src/output/global.rs`) provides:

- `success(message)` - Successful completion (✅, both modes)
- `progress(message)` - Operations in progress (🔄, both modes)
- `info(message)` - Neutral status/metadata (⚪, both modes)
- `warning(message)` - Non-blocking issues (🟡, both modes)
- `hint(message)` - Actionable suggestions (💡, both modes)
- `print(message)` - Write message as-is (caller formats, both modes)
- `shell_integration_hint(message)` - Shell integration hints (💡, suppressed in directive)
- `gutter(content)` - Gutter-formatted content (use with `format_with_gutter()`)
- `blank()` - Blank line for visual separation
- `data(content)` - Structured data output without emoji (JSON, for piping)
- `table(content)` - Table/UI output to stderr
- `change_directory(path)` - Request directory change
- `execute(command)` - Execute command or buffer for shell script
- `flush()` - Flush output buffers
- `flush_for_stderr_prompt()` - Flush before interactive prompts
- `terminate_output()` - Emit shell script in directive mode (no-op in interactive)

For the complete API, see `src/output/global.rs`.

### Adding New Output Functions

Add the function to both handlers, add dispatch in `global.rs`, never add mode parameters. This maintains one canonical path: commands have ONE code path that works for both modes.

### Architectural Constraint: --internal Commands Must Use Output System

Commands supporting `--internal` must never use direct print macros - use output system functions to prevent directive leaks. Enforced by `tests/output_system_guard.rs`.

## Command Execution Principles

### Real-time Output Streaming

Command output must stream in real-time. Never buffer external command output.

```rust
// ✅ GOOD - streaming
for line in reader.lines() {
    println!("{}", line);
    stdout().flush();
}
// ❌ BAD - buffering
let lines: Vec<_> = reader.lines().collect();
```

## Background Operation Logs

### Unified Logging Location

All background operation logs are centralized in `.git/wt-logs/` (main worktree's git directory):

- **Post-start commands**: `{branch}-post-start-{command}.log`
- **Background removal**: `{branch}-remove.log`

Examples (where command names are from config):
- `feature-post-start-npm.log`
- `bugfix-remove.log`

### Log Behavior

- **Centralized**: All logs go to main worktree's `.git/wt-logs/`, shared across all worktrees
- **Overwrites**: Same operation on same branch overwrites previous log (prevents accumulation)
- **Not tracked**: Logs are in `.git/` directory, which git doesn't track
- **Manual cleanup**: Stale logs (from deleted branches) persist but are bounded by branch count

Users can clean up old logs manually or use a git hook. No automatic cleanup is provided.

## Testing Guidelines

### Timing Tests: Long Timeouts with Fast Polling

**Core principle:** Use long timeouts (5+ seconds) for reliability on slow CI, but poll frequently (10-50ms) so tests complete quickly when things work.

This achieves both goals:
- **No flaky failures** on slow machines - generous timeout accommodates worst-case
- **Fast tests** on normal machines - frequent polling means no unnecessary waiting

```rust
// ✅ GOOD: Long timeout, fast polling
let timeout = Duration::from_secs(5);
let poll_interval = Duration::from_millis(10);
let start = Instant::now();
while start.elapsed() < timeout {
    if condition_met() { break; }
    thread::sleep(poll_interval);
}

// ❌ BAD: Fixed sleep (always slow, might still fail)
thread::sleep(Duration::from_millis(500));
assert!(condition_met());

// ❌ BAD: Short timeout (flaky on slow CI)
let timeout = Duration::from_millis(100);
```

Use the helpers in `tests/common/mod.rs`:

```rust
use crate::common::{wait_for_file, wait_for_file_count};

// ✅ Poll for file existence with 5+ second timeout
wait_for_file(&log_file, Duration::from_secs(5));

// ✅ Poll for multiple files
wait_for_file_count(&log_dir, "log", 3, Duration::from_secs(5));
```

These use exponential backoff (10ms → 500ms cap) for fast initial checks that back off on slow CI.

**Exception - testing absence:** When verifying something did NOT happen, polling doesn't work. Use a fixed 500ms+ sleep:

```rust
thread::sleep(Duration::from_millis(500));
assert!(!marker_file.exists(), "Command should NOT have run");
```

### Testing with --execute Commands

Use `--force` to skip interactive prompts in tests. Don't pipe input to stdin.

## Benchmarks

For detailed benchmark documentation, see `benches/CLAUDE.md`.

### Quick Start

```bash
# Run fast synthetic benchmarks (skip slow ones)
cargo bench --bench list -- --skip cold --skip real

# Run specific benchmark
cargo bench --bench list bench_list_by_worktree_count
cargo bench --bench completion
```

Real repo benchmarks clone rust-lang/rust (~2-5 min first run, cached thereafter). Skip during normal development with `--skip real`.

## JSON Output Format

Use `wt list --format=json` for structured data access. See `wt list --help` for complete field documentation, status variants, and query examples.

## Worktree Model

- Worktrees are **addressed by branch name**, not by filesystem path.
- Each worktree should map to **exactly one branch**.
- We **never retarget an existing worktree** to a different branch; instead create/switch/remove worktrees.