|
| 1 | +# AGENTS.md |
| 2 | + |
| 3 | +This file provides guidance to coding agents when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +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. |
| 8 | + |
| 9 | +## Development Commands |
| 10 | + |
| 11 | +### Build and Run |
| 12 | + |
| 13 | +```bash |
| 14 | +# Development build |
| 15 | +cargo build |
| 16 | + |
| 17 | +# Release build |
| 18 | +cargo build --release |
| 19 | + |
| 20 | +# Run directly (development) |
| 21 | +cargo run |
| 22 | + |
| 23 | +# Run the binary |
| 24 | +./target/debug/gw |
| 25 | +./target/release/gw |
| 26 | + |
| 27 | +# Run tests |
| 28 | +cargo test |
| 29 | + |
| 30 | +# Run specific test |
| 31 | +cargo test test_name |
| 32 | + |
| 33 | +# Run tests single-threaded (for flaky tests) |
| 34 | +cargo test -- --test-threads=1 |
| 35 | + |
| 36 | +# Run tests with output for debugging |
| 37 | +cargo test test_name -- --nocapture |
| 38 | + |
| 39 | +# Run with logging enabled |
| 40 | +RUST_LOG=debug cargo run |
| 41 | +RUST_LOG=git_workers=trace cargo run |
| 42 | +``` |
| 43 | + |
| 44 | +### Quality Checks |
| 45 | + |
| 46 | +```bash |
| 47 | +# Format check and apply |
| 48 | +cargo fmt --check |
| 49 | +cargo fmt |
| 50 | + |
| 51 | +# Clippy (linter) |
| 52 | +cargo clippy --all-features -- -D warnings |
| 53 | + |
| 54 | +# Type check |
| 55 | +cargo check --all-features |
| 56 | + |
| 57 | +# Generate documentation |
| 58 | +cargo doc --no-deps --open |
| 59 | + |
| 60 | +# Run all checks (using bun if available) |
| 61 | +bun run check |
| 62 | + |
| 63 | +# Coverage report (requires cargo-llvm-cov) |
| 64 | +cargo llvm-cov --html --lib --ignore-filename-regex '(tests/|src/main\.rs|src/bin/)' --open |
| 65 | +``` |
| 66 | + |
| 67 | +### Commit Conventions |
| 68 | + |
| 69 | +- Follow `Conventional Commits` for all commit messages |
| 70 | +- Format: `<type>(<scope>)?: <description>` |
| 71 | +- Common types in this repository: |
| 72 | + - `feat`: user-facing feature additions |
| 73 | + - `fix`: bug fixes and behavior corrections |
| 74 | + - `refactor`: structural changes without behavior changes |
| 75 | + - `test`: test additions or test-only refactors |
| 76 | + - `docs`: documentation-only changes |
| 77 | + - `chore`: maintenance work with no product behavior impact |
| 78 | + - `ci`: CI or automation workflow changes |
| 79 | + - `build`: build system or dependency management changes |
| 80 | +- Keep the subject concise, imperative, and lowercase where natural |
| 81 | +- Do not mix structural changes and behavior changes in the same commit |
| 82 | +- Examples: |
| 83 | + - `refactor(app): move menu dispatch into app module` |
| 84 | + - `fix(create): preserve selected tag when creating worktree` |
| 85 | + - `test(rename): cover cancel flow in rename prompt` |
| 86 | + |
| 87 | +### Installation |
| 88 | + |
| 89 | +```bash |
| 90 | +# Install locally from source |
| 91 | +cargo install --path . |
| 92 | + |
| 93 | +# Setup shell integration |
| 94 | +./setup.sh |
| 95 | + |
| 96 | +# Or manually add to ~/.bashrc or ~/.zshrc: |
| 97 | +source /path/to/git-workers/shell/gw.sh |
| 98 | +``` |
| 99 | + |
| 100 | +## Current Focus Areas |
| 101 | + |
| 102 | +- Interactive worktree operations are driven from the `app` layer and delegated into `usecases` |
| 103 | +- Existing public paths such as `commands`, `infrastructure`, and `repository_info` are kept as compatibility facades |
| 104 | +- The project supports shell-assisted directory switching, lifecycle hooks, file copying, tag-based creation, and validated custom paths |
| 105 | +- Current refactoring policy prioritizes preserving observable behavior over aggressively removing compatibility layers |
| 106 | + |
| 107 | +## Architecture |
| 108 | + |
| 109 | +### Core Module Structure |
| 110 | + |
| 111 | +``` |
| 112 | +src/ |
| 113 | +├── main.rs # Thin CLI entry point (`--version` + app startup) |
| 114 | +├── lib.rs # Public module exports and backward-compatible re-exports |
| 115 | +├── app/ # Menu loop, action dispatch, presenter helpers |
| 116 | +├── usecases/ # Main worktree operations (create/delete/list/rename/switch/search) |
| 117 | +├── adapters/ # Config, shell, filesystem, Git, UI, and hook adapters |
| 118 | +├── domain/ # Repository context and domain-level helpers |
| 119 | +├── commands/ # Backward-compatible facades over usecases |
| 120 | +├── config.rs # Configuration model and access helpers |
| 121 | +├── repository_info.rs # Backward-compatible facade for repo context display |
| 122 | +├── infrastructure/ # Backward-compatible exports for older module paths |
| 123 | +├── core/ # Legacy core logic retained during migration |
| 124 | +├── ui.rs # User interface abstraction used by prompts and tests |
| 125 | +├── input_esc_raw.rs # ESC-aware input helpers |
| 126 | +├── constants.rs # Centralized strings and formatting constants |
| 127 | +├── support/ # Terminal and styling support utilities |
| 128 | +└── utils.rs # Shared utilities and compatibility helpers |
| 129 | +``` |
| 130 | + |
| 131 | +### Dependency Direction |
| 132 | + |
| 133 | +- `main` -> `app` |
| 134 | +- `app` -> `usecases` |
| 135 | +- `usecases` -> `adapters`, `domain`, `ui`, `config`, `infrastructure` |
| 136 | +- `commands` and `repository_info` should stay thin and delegate to the newer modules |
| 137 | +- Public compatibility paths are intentionally preserved unless a breaking change is explicitly planned |
| 138 | + |
| 139 | +### Technology Stack |
| 140 | + |
| 141 | +- **dialoguer + console**: Interactive CLI (Select, Confirm, Input prompts) |
| 142 | +- **git2**: Git repository operations (branch listing, commit info) |
| 143 | +- **std::process::Command**: Git CLI invocation (worktree add/prune) |
| 144 | +- **colored**: Terminal output coloring |
| 145 | +- **fuzzy-matcher**: Worktree search functionality |
| 146 | +- **indicatif**: Progress bar display |
| 147 | + |
| 148 | +### Shell Integration System |
| 149 | + |
| 150 | +Automatic directory switching on worktree change requires special implementation due to Unix process restrictions: |
| 151 | + |
| 152 | +1. Binary writes path to file specified by `GW_SWITCH_FILE` env var |
| 153 | +2. Shell function (`shell/gw.sh`) reads the file and executes `cd` |
| 154 | +3. Legacy fallback: `SWITCH_TO:/path` marker on stdout |
| 155 | + |
| 156 | +### Hook System Design |
| 157 | + |
| 158 | +Define lifecycle hooks in `.git-workers.toml`: |
| 159 | + |
| 160 | +```toml |
| 161 | +[hooks] |
| 162 | +post-create = ["npm install", "cp .env.example .env"] |
| 163 | +pre-remove = ["rm -rf node_modules"] |
| 164 | +post-switch = ["echo 'Switched to {{worktree_name}}'"] |
| 165 | +``` |
| 166 | + |
| 167 | +Template variables: |
| 168 | + |
| 169 | +- `{{worktree_name}}`: The worktree name |
| 170 | +- `{{worktree_path}}`: Absolute path to worktree |
| 171 | + |
| 172 | +### Worktree Patterns |
| 173 | + |
| 174 | +First worktree creation offers two options: |
| 175 | + |
| 176 | +1. **Same level as repository**: `../worktree-name` - Creates worktrees as siblings to the repository |
| 177 | +2. **Custom path**: User specifies any relative path (e.g., `main`, `branches/feature`, `worktrees/name`) |
| 178 | + |
| 179 | +For bare repositories with `.bare` pattern, use custom path to create worktrees inside the project directory: |
| 180 | +- Custom path: `main` → `my-project/main/` |
| 181 | +- Custom path: `feature-1` → `my-project/feature-1/` |
| 182 | + |
| 183 | +Subsequent worktrees follow the established pattern automatically. |
| 184 | + |
| 185 | +### ESC Key Handling |
| 186 | + |
| 187 | +All interactive prompts support ESC cancellation through custom `input_esc_raw` module: |
| 188 | + |
| 189 | +- `input_esc_raw()` returns `Option<String>` (None on ESC) |
| 190 | +- `Select::interact_opt()` for menu selections |
| 191 | +- `Confirm::interact_opt()` for confirmations |
| 192 | + |
| 193 | +### Worktree Rename Implementation |
| 194 | + |
| 195 | +Since Git lacks native rename functionality: |
| 196 | + |
| 197 | +1. Move directory with `fs::rename` |
| 198 | +2. Update `.git/worktrees/<name>` metadata directory |
| 199 | +3. Update gitdir files in both directions |
| 200 | +4. Optionally rename associated branch if it matches worktree name |
| 201 | + |
| 202 | +### CI/CD Configuration |
| 203 | + |
| 204 | +- **GitHub Actions**: `.github/workflows/ci.yml` (test, lint, build) |
| 205 | +- **Release workflow**: `.github/workflows/release.yml` (automated releases) |
| 206 | +- **Homebrew tap**: Updates `wasabeef/homebrew-gw-tap` on release |
| 207 | +- **Pre-commit hooks**: `lefthook.yml` (format, clippy) |
| 208 | + |
| 209 | +### Testing Considerations |
| 210 | + |
| 211 | +- The repository currently has 51 test files across `unit`, `integration`, `e2e`, and `performance` |
| 212 | +- The safest full verification command is `cargo test --all-features -- --test-threads=1` |
| 213 | +- `cargo fmt --check` and `cargo clippy --all-features -- -D warnings` are expected before shipping significant changes |
| 214 | +- Some tests remain sensitive to parallel execution because they manipulate Git repositories and process-wide state |
| 215 | +- Use `--nocapture` when debugging interactive or repository-context behavior |
| 216 | + |
| 217 | +### Common Error Patterns and Solutions |
| 218 | + |
| 219 | +1. **"Permission denied" when running tests**: Tests create temporary directories; ensure proper permissions |
| 220 | +2. **"Repository not found" errors**: Tests require git to be configured (`git config --global user.name/email`) |
| 221 | +3. **Flaky test failures**: Use `--test-threads=1` to avoid race conditions in worktree operations |
| 222 | +4. **"Lock file exists" errors**: Clean up `.git/git-workers-worktree.lock` if tests are interrupted |
| 223 | + |
| 224 | +### String Formatting |
| 225 | + |
| 226 | +- **ALWAYS use inline variable syntax in format! macros**: `format!("{variable}")` instead of `format!("{}", variable)` |
| 227 | +- This applies to ALL format-like macros: `format!`, `println!`, `eprintln!`, `log::info!`, `log::warn!`, `log::error!`, etc. |
| 228 | +- Examples: |
| 229 | + |
| 230 | + ```rust |
| 231 | + // ✅ Correct |
| 232 | + format!("Device {name} created successfully") |
| 233 | + println!("Found {count} devices") |
| 234 | + log::info!("Starting device {identifier}") |
| 235 | + |
| 236 | + // ❌ Incorrect |
| 237 | + format!("Device {} created successfully", name) |
| 238 | + println!("Found {} devices", count) |
| 239 | + log::info!("Starting device {}", identifier) |
| 240 | + ``` |
| 241 | + |
| 242 | +- This rule is enforced by `clippy::uninlined_format_args` which treats violations as errors in CI |
| 243 | +- Apply this consistently across ALL files including main source, tests, examples, and binary targets |
| 244 | + |
| 245 | +### Important Constraints |
| 246 | + |
| 247 | +- Only works within Git repositories |
| 248 | +- Requires initial commit (bare repositories supported) |
| 249 | +- Cannot rename current worktree |
| 250 | +- Cannot rename worktrees with detached HEAD |
| 251 | +- Shell integration supports Bash/Zsh only |
| 252 | +- No Windows support (macOS and Linux only) |
| 253 | +- The CLI is primarily interactive, with `--version` as the supported non-interactive flag |
| 254 | + |
| 255 | +### Configuration Loading Priority |
| 256 | + |
| 257 | +**Bare repositories:** |
| 258 | + |
| 259 | +- Check main/master worktree directories only |
| 260 | + |
| 261 | +**Non-bare repositories:** |
| 262 | + |
| 263 | +1. Current directory (current worktree) |
| 264 | +2. Main/master worktree directories (fallback) |
| 265 | + |
| 266 | +### File Copy Behavior |
| 267 | + |
| 268 | +Ignored files such as `.env` can be copied into new worktrees through `.git-workers.toml`. |
| 269 | + |
| 270 | +```toml |
| 271 | +[files] |
| 272 | +copy = [".env", ".env.local", "config/local.json"] |
| 273 | +# source = "path/to/source" |
| 274 | +``` |
| 275 | + |
| 276 | +- Copy runs after worktree creation and before post-create hooks |
| 277 | +- Missing source files warn but do not abort worktree creation |
| 278 | +- Paths are validated to prevent traversal and invalid destinations |
| 279 | +- File handling includes symlink checks, depth limits, and permission preservation where applicable |
| 280 | + |
| 281 | +## CI/CD and Tooling |
| 282 | + |
| 283 | +- `.github/workflows/ci.yml` runs the main validation pipeline |
| 284 | +- `.github/workflows/release.yml` handles release automation |
| 285 | +- `lefthook.yml` runs pre-commit checks such as `fmt` and `clippy` |
| 286 | +- `package.json` provides helper scripts: |
| 287 | + - `bun run format` |
| 288 | + - `bun run lint` |
| 289 | + - `bun run test` |
| 290 | + - `bun run check` |
| 291 | + |
| 292 | +## Key Implementation Patterns |
| 293 | + |
| 294 | +### Git Operations |
| 295 | + |
| 296 | +The codebase uses two approaches for Git operations: |
| 297 | + |
| 298 | +1. **git2 library**: For read operations (listing branches, getting commit info) |
| 299 | +2. **std::process::Command**: For write operations (worktree add/remove) to ensure compatibility |
| 300 | + |
| 301 | +Example pattern: |
| 302 | + |
| 303 | +```rust |
| 304 | +// Read operation using git2 |
| 305 | +let repo = Repository::open(".")?; |
| 306 | +let branches = repo.branches(Some(BranchType::Local))?; |
| 307 | + |
| 308 | +// Write operation using Command |
| 309 | +Command::new("git") |
| 310 | + .args(&["worktree", "add", path, branch]) |
| 311 | + .output()?; |
| 312 | +``` |
| 313 | + |
| 314 | +### Error Handling Philosophy |
| 315 | + |
| 316 | +- Use `anyhow::Result` for application-level errors |
| 317 | +- Provide context with `.context()` for better error messages |
| 318 | +- Show user-friendly messages via `utils::display_error()` |
| 319 | +- Never panic in production code; handle all error cases gracefully |
| 320 | + |
| 321 | +### UI Abstraction |
| 322 | + |
| 323 | +The `ui::UserInterface` trait enables testing of interactive features: |
| 324 | + |
| 325 | +- Mock implementations for tests |
| 326 | +- Real implementation wraps dialoguer |
| 327 | +- All user interactions go through this abstraction |
0 commit comments