Imported from wasabeef/git-workers (
AGENTS.md). Install upstream withnpx skills add wasabeef/git-workers. Copyright stays with the author.
AGENTS.md
This file provides guidance to coding agents when working with code in this repository.
Project Overview
Git Workers is an interactive CLI tool for managing Git worktrees, written in Rust. It provides a menu-driven interface for creating, deleting, switching, and renaming worktrees, with shell integration for automatic directory switching.
Development Commands
Build and Run
# Development build
cargo build
# Release build
cargo build --release
# Run directly (development)
cargo run
# Run the binary
./target/debug/gw
./target/release/gw
# Run tests
cargo test
# Run specific test
cargo test test_name
# Run tests single-threaded (for flaky tests)
cargo test -- --test-threads=1
# Run tests with output for debugging
cargo test test_name -- --nocapture
# Run with logging enabled
RUST_LOG=debug cargo run
RUST_LOG=git_workers=trace cargo run
Quality Checks
# Format check and apply
cargo fmt --check
cargo fmt
# Clippy (linter)
cargo clippy --all-features -- -D warnings
# Type check
cargo check --all-features
# Generate documentation
cargo doc --no-deps --open
# Run all checks (using bun if available)
bun run check
# Coverage report (requires cargo-llvm-cov)
cargo llvm-cov --html --lib --ignore-filename-regex '(tests/|src/main\.rs|src/bin/)' --open
Commit Conventions
- Follow
Conventional Commitsfor all commit messages - Format:
<type>(<scope>)?: <description> - Common types in this repository:
feat: user-facing feature additionsfix: bug fixes and behavior correctionsrefactor: structural changes without behavior changestest: test additions or test-only refactorsdocs: documentation-only changeschore: maintenance work with no product behavior impactci: CI or automation workflow changesbuild: build system or dependency management changes
- Keep the subject concise, imperative, and lowercase where natural
- Do not mix structural changes and behavior changes in the same commit
- Examples:
refactor(app): move menu dispatch into app modulefix(create): preserve selected tag when creating worktreetest(rename): cover cancel flow in rename prompt
Installation
# Install locally from source
cargo install --path .
# Setup shell integration
./setup.sh
# Or manually add to ~/.bashrc or ~/.zshrc:
source /path/to/git-workers/shell/gw.sh
Current Focus Areas
- Interactive worktree operations are driven from the
applayer and delegated intousecases - Existing public paths such as
commands,infrastructure, andrepository_infoare kept as compatibility facades - The project supports shell-assisted directory switching, lifecycle hooks, file copying, tag-based creation, and validated custom paths
- Current refactoring policy prioritizes preserving observable behavior over aggressively removing compatibility layers
Architecture
Core Module Structure
src/
├── main.rs # Thin CLI entry point (`--version` + app startup)
├── lib.rs # Public module exports and backward-compatible re-exports
├── app/ # Menu loop, action dispatch, presenter helpers
├── usecases/ # Main worktree operations (create/delete/list/rename/switch/search)
├── adapters/ # Config, shell, filesystem, Git, UI, and hook adapters
├── domain/ # Repository context and domain-level helpers
├── commands/ # Backward-compatible facades over usecases
├── config.rs # Configuration model and access helpers
├── repository_info.rs # Backward-compatible facade for repo context display
├── infrastructure/ # Backward-compatible exports for older module paths
├── core/ # Legacy core logic retained during migration
├── ui.rs # User interface abstraction used by prompts and tests
├── input_esc_raw.rs # ESC-aware input helpers
├── constants.rs # Centralized strings and formatting constants
├── support/ # Terminal and styling support utilities
└── utils.rs # Shared utilities and compatibility helpers
Dependency Direction
main->appapp->usecasesusecases->adapters,domain,ui,config,infrastructurecommandsandrepository_infoshould stay thin and delegate to the newer modules- Public compatibility paths are intentionally preserved unless a breaking change is explicitly planned
Technology Stack
- dialoguer + console: Interactive CLI (Select, Confirm, Input prompts)
- git2: Git repository operations (branch listing, commit info)
- std::process::Command: Git CLI invocation (worktree add/prune)
- colored: Terminal output coloring
- fuzzy-matcher: Worktree search functionality
- indicatif: Progress bar display
Shell Integration System
Automatic directory switching on worktree change requires special implementation due to Unix process restrictions:
- Binary writes path to file specified by
GW_SWITCH_FILEenv var - Shell function (
shell/gw.sh) reads the file and executescd - Legacy fallback:
SWITCH_TO:/pathmarker on stdout
Hook System Design
Define lifecycle hooks in .git-workers.toml:
[hooks]
post-create = ["npm install", "cp .env.example .env"]
pre-remove = ["rm -rf node_modules"]
post-switch = ["echo 'Switched to {{worktree_name}}'"]
Template variables:
{{worktree_name}}: The worktree name{{worktree_path}}: Absolute path to worktree
Worktree Patterns
First worktree creation offers two options:
- Same level as repository:
../worktree-name- Creates worktrees as siblings to the repository - Custom path: User specifies any relative path (e.g.,
main,branches/feature,worktrees/name)
For bare repositories with .bare pattern, use custom path to create worktrees inside the project directory:
- Custom path:
main→my-project/main/ - Custom path:
feature-1→my-project/feature-1/
Subsequent worktrees follow the established pattern automatically.
ESC Key Handling
All interactive prompts support ESC cancellation through custom input_esc_raw module:
input_esc_raw()returnsOption<String>(None on ESC)Select::interact_opt()for menu selectionsConfirm::interact_opt()for confirmations
Worktree Rename Implementation
Since Git lacks native rename functionality:
- Move directory with
fs::rename - Update
.git/worktrees/<name>metadata directory - Update gitdir files in both directions
- Optionally rename associated branch if it matches worktree name
CI/CD Configuration
- GitHub Actions:
.github/workflows/ci.yml(test, lint, build) - Release workflow:
.github/workflows/release.yml(automated releases) - Homebrew tap: Updates
wasabeef/homebrew-gw-tapon release - Pre-commit hooks:
lefthook.yml(format, clippy)
Testing Considerations
- The repository currently has 51 test files across
unit,integration,e2e, andperformance - The safest full verification command is
cargo test --all-features -- --test-threads=1 cargo fmt --checkandcargo clippy --all-features -- -D warningsare expected before shipping significant changes- Some tests remain sensitive to parallel execution because they manipulate Git repositories and process-wide state
- Use
--nocapturewhen debugging interactive or repository-context behavior
Common Error Patterns and Solutions
- "Permission denied" when running tests: Tests create temporary directories; ensure proper permissions
- "Repository not found" errors: Tests require git to be configured (
git config --global user.name/email) - Flaky test failures: Use
--test-threads=1to avoid race conditions in worktree operations - "Lock file exists" errors: Clean up
.git/git-workers-worktree.lockif tests are interrupted
String Formatting
-
ALWAYS use inline variable syntax in format! macros:
format!("{variable}")instead offormat!("{}", variable) -
This applies to ALL format-like macros:
format!,println!,eprintln!,log::info!,log::warn!,log::error!, etc. -
Examples:
// ✅ Correct format!("Device {name} created successfully") println!("Found {count} devices") log::info!("Starting device {identifier}") // ❌ Incorrect format!("Device {} created successfully", name) println!("Found {} devices", count) log::info!("Starting device {}", identifier) -
This rule is enforced by
clippy::uninlined_format_argswhich treats violations as errors in CI -
Apply this consistently across ALL files including main source, tests, examples, and binary targets
Important Constraints
- Only works within Git repositories
- Requires initial commit (bare repositories supported)
- Cannot rename current worktree
- Cannot rename worktrees with detached HEAD
- Shell integration supports Bash/Zsh only
- No Windows support (macOS and Linux only)
- The CLI is primarily interactive, with
--versionas the supported non-interactive flag
Configuration Loading Priority
Bare repositories:
- Check main/master worktree directories only
Non-bare repositories:
- Current directory (current worktree)
- Main/master worktree directories (fallback)
File Copy Behavior
Ignored files such as .env can be copied into new worktrees through .git-workers.toml.
[files]
copy = [".env", ".env.local", "config/local.json"]
# source = "path/to/source"
- Copy runs after worktree creation and before post-create hooks
- Missing source files warn but do not abort worktree creation
- Paths are validated to prevent traversal and invalid destinations
- File handling includes symlink checks, depth limits, and permission preservation where applicable
CI/CD and Tooling
.github/workflows/ci.ymlruns the main validation pipeline.github/workflows/release.ymlhandles release automationlefthook.ymlruns pre-commit checks such asfmtandclippypackage.jsonprovides helper scripts:bun run formatbun run lintbun run testbun run check
Key Implementation Patterns
Git Operations
The codebase uses two approaches for Git operations:
- git2 library: For read operations (listing branches, getting commit info)
- std::process::Command: For write operations (worktree add/remove) to ensure compatibility
Example pattern:
// Read operation using git2
let repo = Repository::open(".")?;
let branches = repo.branches(Some(BranchType::Local))?;
// Write operation using Command
Command::new("git")
.args(&["worktree", "add", path, branch])
.output()?;
Error Handling Philosophy
- Use
anyhow::Resultfor application-level errors - Provide context with
.context()for better error messages - Show user-friendly messages via
utils::display_error() - Never panic in production code; handle all error cases gracefully
UI Abstraction
The ui::UserInterface trait enables testing of interactive features:
- Mock implementations for tests
- Real implementation wraps dialoguer
- All user interactions go through this abstraction