Skip to content

Commit 105ebee

Browse files
authored
Merge pull request #13 from wasabeef/refactor-app
Refactor app flow into usecases and adapters
2 parents 81023b0 + 1f895ea commit 105ebee

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+5593
-5170
lines changed

AGENTS.md

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
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

Comments
 (0)