This consolidates cross-cutting models that had accumulated parallel
representations, while preserving the CLI and config interfaces.
## What changed
- Forge identity now flows through one `ForgeKind`, with boundary-safe
network-host classification shared by CI, remote references, and
structured repository metadata. Branded SSH aliases such as
`github-personal` remain outside provider dispatch and receive an
actionable `forge.platform` diagnostic when forge data is requested.
- Worktree removal now uses one owned target type and rechecks uncached
worktree topology immediately before compare-and-swap branch deletion,
retaining branches that gained a live or locked checkout.
- Shell integration no longer implements the retired single-file
directive writer. Stale wrappers receive repair guidance, execution
fails closed, and child processes cannot inherit the retired
sourceable-file capability.
- Zsh and Git-version probes are shared, while dead mocks, redundant
dependency edges, serializer detours, pass-through types, and duplicate
tests are removed.
The removal guard deliberately distinguishes live, stale-prunable, and
locked registrations. The detached cleanup path performs the same
record-aware check before deleting a branch ref.
## Testing
`cargo run -- hook pre-merge --yes` passed after merging current
`origin/main`: 4,489 tests, Clippy, formatting, lockfile checks, docs,
doctests, and snapshot review.
> _This was written by Claude Code on behalf of max_.
This follows the first test-simplification tranche by converging the
remaining suite around distinct semantic and pragmatic contracts rather
than raw case count. The branch removes false-confidence tests, invalid
setup variants, repetitive snapshots, and expensive PTY overlap while
strengthening the retained route, precondition, and interaction proofs.
## What changed
- Replace obsolete CI-status integration mocks and blank snapshots with
direct provider semantics, mixed-priority cases, and strict
GitHub/GitLab route assertions.
- Remove free-riding merge, push, remove, list, security, config, and
switch cases whose setup never reached the named behavior; consolidate
repetitive direct cases into labeled tables.
- Reduce the switch picker from 42 PTYs to 19 distinct terminal
contracts, using causal release gates for asynchronous loading and
repaint behavior.
- Add a cached main-only picker fixture, eliminating 138 unnecessary Git
subprocesses across the retained PTYs, and integrate it with main's
generated hermetic standard fixture.
- Tighten test guidance around proving setup preconditions and mock
invocation routes, and correct the comments-tab help text and generated
mirrors.
## Reviewer map
- `tests/integration_tests/ci_status.rs` and
`src/commands/list/ci_status/`: provider semantics and route coverage.
- `tests/integration_tests/switch_picker.rs`, `src/commands/picker/`,
and `src/testing/`: retained PTY contracts, causal mocks, and fixture
design.
- `tests/integration_tests/config_show.rs`, `src/config/deprecation.rs`,
`src/config/expansion.rs`, and worktree type/resolve tests:
direct-boundary consolidation.
- `tests/CLAUDE.md`: the testing rules extracted from the
false-confidence cases found during the survey.
The measured loop removed 98 tests, 65 snapshots, and 23 picker PTYs.
Controlled warm Nextest execution improved from a 79.593-second mean to
72.574 seconds (8.8%), while comparable production-line coverage moved
from 97.32% to 97.23%. The tracked PR diff is a net deletion of more
than 5,700 lines.
## Validation
- `cargo run -- hook pre-merge --yes` after syncing current `main`:
4,468 passed, one configured skip; docs, doctests, clippy, formatting,
policy checks, and snapshots green.
- `task coverage` on the completed change before the base sync: 4,465
passed, one configured skip; 97.23% comparable production-line coverage.
- Three independent final audits found no remaining lost beliefs,
fixture hazards, or safe PTY consolidations.
> _This was written by Claude Code on behalf of max_.
Converts ~29 string literals across 16 files from escaped form
(`"\\d+"`, `"{\"name\": 1}"`) to raw-string form (`r"\d+"`, `r#"{"name":
1}"#`) where possible. Strings containing control escapes (`\n`, `\t`,
`\u{1b}`, etc.) are left alone — raw strings can't represent those.
No behavior change; `cargo check/clippy -D warnings/fmt/test` all clean
locally.
> _This was written by Claude Code on behalf of max-sixty_
Co-authored-by: Claude <noreply@anthropic.com>
The shell wrapper previously used a single `WORKTRUNK_DIRECTIVE_FILE`
where wt wrote shell commands (`cd '/path'`, arbitrary `--execute`
payloads). This meant the cd path went through shell parsing — any
content wt wrote was sourced as shell.
This splits the protocol into two files with different trust levels:
- **`WORKTRUNK_DIRECTIVE_CD_FILE`** — raw path, read with `cd -- "$(<
file)"`. No shell parsing, no escaping, no injection surface. Safe to
pass through to alias/hook child processes.
- **`WORKTRUNK_DIRECTIVE_EXEC_FILE`** — arbitrary shell (from
`--execute`), sourced by the wrapper. Scrubbed from alias/hook child
environments so hook bodies cannot inject shell into the parent session.
When a nested `wt` inside an alias body tries `--execute` without the
EXEC file, the command is dropped with a warning linking to #2101 for
user feedback.
The old `WORKTRUNK_DIRECTIVE_FILE` is silently honored for one release
(users who upgrade wt without restarting their shell). Bash, zsh, fish,
and PowerShell self-update on restart; nushell requires `wt config shell
install`.
Closes#2101
> _This was written by Claude Code on behalf of @max-sixty_
---------
Co-authored-by: Claude <noreply@anthropic.com>
The test infrastructure's `git_command()` returned
`std::process::Command`, so test git commands bypassed `Cmd`'s debug
logging (`$ git status [ctx]`) and timing traces (`[wt-trace]`). This
changes it to return `worktrunk::shell_exec::Cmd`.
## Approach
Added `configure_git_env(Cmd, &Path) -> Cmd` alongside the existing
`configure_git_cmd(&mut Command)`. The `Command` version is kept because
`configure_wt_cmd` (which configures wt binary commands) still needs it
— wt commands go through `Command`, not `Cmd`.
Key changes in `tests/common/mod.rs`:
- `TestRepoBase::git_command()` and `TestRepo::git_command()` now return
`Cmd`
- All wrapper methods (`run_git`, `git_output`, `head_sha`, etc.) use
`.run()` instead of `.output()`
- `BareRepoTest::new()` and `NestedBareRepoTest::new()` use
`Cmd::new("git")` for init
247 call sites across 25 files converted: `.output()` → `.run()` and
`.status()` → `.run()` on git command chains. Zero `Command::new("git")`
remaining in the test directory.
Follows #1714 and #1716 which converted raw `Cmd::new("git")` and
`Command::new("git")` in test bodies to `repo.run_command()`.
> _This was written by Claude Code on behalf of maximilian_
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: warn when --config path doesn't exist, fix shell quoting in docs
Bug fixes from adversarial testing:
1. --config path warning: When users pass --config with a non-existent
file path, wt now warns instead of silently falling back to defaults.
Note: WORKTRUNK_CONFIG_PATH env var doesn't trigger this warning since
it's commonly used for test isolation with intentionally absent paths.
2. Shell quoting in docs: Template variables are automatically shell-escaped,
so user-added quotes cause issues with special characters. Fixed examples
in hook docs and tips-patterns that incorrectly showed quoted variables.
Also adds:
- Test verifying --squash is correctly ignored with --no-commit
- Tests for ANSI escape sequence handling in branch names
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: skip ANSI branch name test on Windows
Git for Windows with MSYS2 bash behaves differently and may accept
branch names containing control characters. The test verifies Unix git
behavior, so restrict it to Unix platforms.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* 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>
* Consolidate test git commands using helper methods
Migrate ~180 instances of manual `Command::new("git")` calls to use
TestRepo helper methods for better consistency and reduced boilerplate:
- `repo.run_git(&["..."])` - run in repo root, panic on failure
- `repo.run_git_in(&path, &["..."])` - run in specific dir, panic on failure
- `repo.git_command()` - get configured Command for output capture
Changes:
- Add `run_git_in()` to `TestRepoBase` trait so BareRepoTest and
NestedBareRepoTest also get this method
- Migrate tests in switch.rs, remove.rs, merge.rs, list.rs, completion.rs,
shell_wrapper.rs, push.rs, security.rs, list_config.rs, list_column_alignment.rs
- Migrate internal TestRepo methods to use git_command() with chained env vars
- Remove unused `use std::process::Command` imports where no longer needed
Remaining intentional uses of Command::new("git"):
- TestRepoBase::git_command() and TestRepo::git_command() definitions
- BareRepoTest constructors for `git init --bare` (can't use git_command
because init takes path as argument, not current_dir)
- Standalone bare repo test in remove.rs (to be refactored separately)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Move BareRepoTest to common module and refactor bare repo test
- Move `BareRepoTest` from bare_repository.rs to common/mod.rs for reuse
- Add `wt_command()` helper method to BareRepoTest
- Refactor `test_remove_default_branch_no_tautology` to use shared BareRepoTest
- Remove unused imports (canonicalize, Command, TempDir) from remove.rs
This reduces the standalone bare repo test from 70+ lines to ~15 lines.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Move commit_in method to TestRepoBase trait
Consolidate BareRepoTest::commit_in_worktree into the shared
TestRepoBase trait as commit_in(). This allows all test harnesses
to share this method without duplication.
- Add commit_in() to TestRepoBase trait (tests/common/mod.rs:619)
- Remove duplicate commit_in_worktree from BareRepoTest
- Update 16 callers in bare_repository.rs
- Update 1 caller in remove.rs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Tests that spawn `wt` must be isolated from the host environment to
prevent directive leakage (test commands writing to user's shell
directive file), config pollution, and git interference.
Changes:
- Add `TestRepo::wt_command()` returning pre-configured Command
- Rename `clean_cli_env` → `configure_wt_cmd` (parallel to git)
- Make `git_command()` consistent (no args, chain `.args()`)
- Fix approval_ui.rs and readme_sync.rs missing isolation
- Add `#[must_use]` to command builders
- Document pattern in tests/CLAUDE.md
The public API is now 3 symmetric methods:
- `repo.wt_command()` - for wt commands with TestRepo
- `repo.git_command()` - for git commands
- `wt_command()` - for wt without TestRepo (help tests)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
* Replace --internal flag with WT_DIRECTIVE_FILE environment variable
* Rename directive file environment variable to WORKTRUNK_DIRECTIVE_FILE
* Update stale comments referencing directive scripts
Codex review identified comments that still referenced "directive scripts"
after migrating to file-based directive passing. Updated to reference
"data output" which is the current terminology.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Address documentation review feedback
- Add WORKTRUNK_DIRECTIVE_FILE to env var tables (cli.rs, config.md)
- Update stale "directive mode" terminology in test comments to use
"shell integration" or "data output" as appropriate
- Regenerate help snapshot
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Refactor progressive table output to use output module
Remove finalize_non_tty method and simplify non-TTY output path to use
output::table() directly, consistent with buffered mode. Add WORKTRUNK_SHELL
environment variable support for PowerShell-specific path escaping. Update
documentation and templates accordingly.
* Preserve directive script exit code in PowerShell
Add logic to use the directive script's exit code when wt succeeds, and
add redaction for WORKTRUNK_DIRECTIVE_FILE in test snapshots.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Add temp_home fixture for tests that need custom home directory
Tests that manipulate shell config files or other home directory content were creating `TempDir::new()` manually. Convert these to use an `#[rstest]` fixture for consistency and cleaner test code.
The `temp_home` fixture:
- Creates a temporary directory for use as a fake HOME
- Automatically cleans up when dropped
- Makes test intent clearer by appearing in function signature
This reduces boilerplate across multiple test files (config_init, config_show, configure_shell, hook_show, list_config) and enables better test composition.
* Refactor tests to use repo_with_remote fixture
Replace manual setup_remote() calls with #[from(repo_with_remote)]
rstest fixture parameter to reduce boilerplate in test functions.
Also normalize import ordering across test files.
Replace `git init` with pre-initialized fixture template to save ~10ms per test.
The fixture at tests/fixtures/template-repo/ contains a minimal git repo with
one deterministic commit (hash 23e5a15, timestamp 2025-01-01T00:00:00Z).
Changes:
- TestRepo::new() now copies fixture instead of running git init
- TestRepo::empty() added for tests needing uninitialized repos
- Removed ~320 redundant `repo.commit("Initial commit")` calls from tests
- Reduced fixture from 32 files to 9 (removed sample hooks, logs, etc.)
- Fixed path canonicalization in TestRepo::empty() for Windows compatibility
- Added typos exception for commit hash substring 'ede'
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This reverts a previous commit that removed SHA redaction from test snapshots.
The SHA values can be non-deterministic due to various factors (e.g., commit timestamp, environment variables), causing tests to fail when they change unexpectedly.
By re-introducing SHA redaction, we ensure that snapshot tests remain stable and only capture the relevant structural output, preventing unnecessary failures due to transient SHA differences.
Removes obsolete filters and rewords comments to reflect the simplified
shell integration protocol, which no longer uses NUL terminators
for synchronization or directives.
Updates the `--internal` flag's output format from NUL-terminated directives
to a shell script emitted on stdout at the end of execution. User-facing
messages now stream exclusively to stderr in real-time.
This change:
- Eliminates the need for FIFOs and background processes in shell wrappers, simplifying implementation.
- Ensures all user messages stream directly to the terminal, improving real-time feedback.
- Uses standard POSIX single-quote escaping for paths, making it more robust against injection.
- Aligns with proven patterns like direnv (stderr for logs, stdout for script).
Removes support for Elvish, Nushell, PowerShell, and Xonsh shells due to their
complexities in implementing the previous streaming NUL-byte directive protocol.
Bash, Zsh, and Fish are now simpler and more reliably supported.
Updates documentation, code comments, and integration tests to reflect the new protocol.
Replaces most uses of `.expect(...)` with `.unwrap()` in test code. This is a common pattern in Rust tests for operations that are expected to succeed, simplifying the code.
* feat: Move diffstat display to `handle_push`
This ensures that the diffstat is consistently displayed after a push operation,
rather than after every commit or squash. This makes the output more logical.
* feat: Standardize snapshot settings and redact more volatile env vars
Standardize `insta` snapshot settings to remove more volatile environment variables that cause diff churn.
Also canonicalize worktree paths to handle macOS symlinks (e.g., /var -> /private/var), ensuring consistent snapshot outputs across different environments.
* fix: Fix CI failures on Windows and Ubuntu
- Wrap BetaCommand import with #[cfg(unix)] to fix Windows clippy error
- Use /bin/echo instead of echo in LLM tests to fix Ubuntu PATH issues
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
The main worktree now displays a `^` symbol in the `main↕` column to clearly distinguish it from other worktrees and branches. This adds clarity when quickly scanning the list output.
This refactoring introduces `wt_command` and `configure_cli_command` helper functions in `tests/common/mod.rs` to centralize the setup of `wt` CLI commands in integration tests.
The new helpers ensure all test commands:
- Clear host `GIT_*` and `WORKTRUNK_*` environment variables.
- Force color output (`CLICOLOR_FORCE=1`) for consistent snapshot testing.
- Set a default terminal width of 150 columns (`COLUMNS=150`) if not already specified.
This change reduces duplication across integration tests by moving common command environment configuration into reusable functions. Existing calls to `Command::new(get_cargo_bin("wt"))` have been replaced with `wt_command()`, and manual environment variable settings have been replaced with calls to `configure_cli_command`.
A new helper `set_temp_home_env` is also introduced to simplify setting `HOME` and `XDG_CONFIG_HOME` for commands requiring an isolated home directory.
- Add Windows to CI matrix (ubuntu, macos, windows)
- Configure Windows CI to use Git Bash
- Add 10min timeout for test suite
- Mark Unix-specific tests with #[cfg(unix)]
- Make file permissions tests cross-platform
- Escape Windows paths in snapshot regex filters
- Disable flaky bash PTY test with timing race
- Add git author identity to bare_repository tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit introduces a new integration test suite focused on preventing
shell directive injection through user-controlled strings.
The tests ensure that malicious content in branch names, commit messages,
file paths, and error output cannot be misinterpreted as internal
`__WORKTRUNK_EXEC__` or `__WORKTRUNK_CD__` directives.
A new `TestRepo::commit_with_message` helper is added to facilitate
creating commits with custom, potentially malicious, messages.
These tests verify that various attack vectors, including NUL byte
injection (where prevented by Git/Rust), newline injection, and direct
directive strings, do not lead to arbitrary command execution when
processed or displayed by worktrunk.