# Gza Code Review - Pre-Release Assessment

**Date:** 2026-02-12
**Reviewer:** Claude (automated code review)

## Executive Summary

The gza codebase demonstrates good overall quality with strong test coverage (399 tests passing), clear module boundaries, and consistent patterns. The codebase follows Python best practices with type hints, dataclasses, and proper error handling. Key areas for improvement include: filling test coverage gaps for `runner.py` and provider modules, addressing resource management concerns in some file operations, and standardizing logging approach (currently mixed `print` and `console.print`).

---

## 1. Test Coverage

### 1.1 Unit Tests

| Module | Test File | Coverage Assessment |
|--------|-----------|---------------------|
| `db.py` | `test_db.py` | **Good** - covers CRUD, queries, migrations, dependencies |
| `cli.py` | `test_cli.py` | **Good** - covers most commands, history, next, add, delete, retry, resume, merge, improve, review |
| `runner.py` | `test_runner.py` | **Partial** - only covers build_prompt, summary paths, review task generation, WIP functionality |
| `git.py` | (none) | **None** - no dedicated test file (tested indirectly via CLI tests) |
| `github.py` | (none) | **None** - no dedicated test file |
| `config.py` | (inline in test_cli.py) | **Partial** - validation tested via CLI validate command |
| `providers/base.py` | `test_providers.py` | **Good** - covers Docker config, docker commands, credential checks |
| `providers/claude.py` | `test_providers.py` | **Adequate** - covers routing, error extraction, tool logging |
| `providers/gemini.py` | `test_providers.py` | **Adequate** - covers pricing, credential checks |
| `workers.py` | `test_workers.py` | **Good** - covers registry, serialization, lifecycle |
| `tasks.py` | `test_tasks.py` | **Good** - covers serialization, YAML formatting |
| `importer.py` | `test_importer.py` | **Good** - covers YAML import, dependencies |
| `branch_naming.py` | `test_branch_naming.py` | **Good** - covers type inference, patterns, validation |
| `console.py` | (none) | **None** - no dedicated test file |
| `skills_utils.py` | (none) | **None** - no dedicated test file |

#### Well-Tested Areas
- Task database operations (CRUD, queries, migrations, dependency handling)
- CLI commands (history, next, add, delete, retry, resume, merge, improve, review, diff)
- Branch naming strategies and validation
- Worker registry operations
- Provider configuration and Docker command building
- Task serialization and YAML formatting

#### Under-Tested Areas
- **`runner.py`**: Core task execution logic lacks tests for:
  - `run()` function main execution path
  - `_run_non_code_task()` for explore/plan/review tasks
  - Worktree creation and cleanup
  - Branch checkout and commit logic
  - Error handling paths (GitError, KeyboardInterrupt)
- **`git.py`**: All 24 methods are untested directly (only tested via integration)
- **`github.py`**: All methods untested (`create_pr`, `pr_exists`, `add_pr_comment`)
- **`console.py`**: Helper functions untested
- **`skills_utils.py`**: Skill loading and validation untested

### 1.2 Functional Tests

| Workflow | Test Status | Notes |
|----------|-------------|-------|
| Task creation → execution | Partial | Tests create tasks but mock execution |
| PR creation workflow | **Not tested** | `gza pr` command path untested end-to-end |
| Review workflow | Partial | Tests task creation, not full execution |
| Improve workflow | Partial | Tests task chaining, not execution |
| Worker background execution | Partial | Tests spawning, not full lifecycle |
| Git worktree operations | **Not tested** | Worktree add/remove untested |

**Note:** No `tests_integration/` directory exists. Integration tests are embedded in `test_cli.py` using subprocess calls to `uv run gza`.

---

## 2. Code Duplication

### 2.1 Issues Found

1. **Duration formatting duplicated**
   - Location: `cli.py:35-42` (`_format_duration`) and `cli.py:1289-1297` (`_format_duration_verbose`)
   - Also in: `console.py:19-29` (`format_duration`)
   - **Severity: Medium**
   - Suggestion: Consolidate into single function in `console.py` with optional verbosity parameter

2. **Background worker spawning pattern**
   - Location: `cli.py:103-140` (`_spawn_background_worker`) and `cli.py:232-269` (`_spawn_resume_worker`)
   - **Severity: Low**
   - Suggestion: These are already extracted functions, minor differences are justified

3. **Prompt display truncation pattern**
   - Locations: `cli.py:131`, `cli.py:260`, `cli.py:508`, `cli.py:521`, `cli.py:603`, `cli.py:682`, `cli.py:2458`
   - Pattern: `prompt[:N] + "..." if len(prompt) > N else prompt` with varying N (50, 60, 72)
   - **Severity: Low**
   - Suggestion: Extract helper function `truncate(text, max_len)` to `console.py`

4. **Task validation pattern in CLI commands**
   - Multiple commands repeat: check task exists → check task status → check dependencies
   - Locations: `gza run`, `gza retry`, `gza resume`, `gza merge`
   - **Severity: Low**
   - Suggestion: Pattern is contextual, some duplication acceptable

### 2.2 Single Code Path Violations

**No significant violations found.** The codebase follows the single code path principle well:
- Database operations go through `SqliteTaskStore`
- Git operations go through `Git` class
- GitHub operations go through `GitHub` class
- Provider execution routes through `Provider.run()`

---

## 3. Error Handling

### 3.1 Consistency Assessment

The codebase uses a consistent pattern:
- Custom exceptions defined: `GitError`, `GitHubError`, `ConfigError`
- Functions raise exceptions for actual errors
- CLI commands catch and display errors with exit codes

### 3.2 Issues Found

| Location | Issue | Suggestion |
|----------|-------|------------|
| `db.py:447` | Bare `except Exception:` in `claim_next_pending_task` | Catch `sqlite3.Error` specifically |
| `skills_utils.py:84` | Bare `except Exception:` suppresses all errors | Log the error before returning None |
| `skills_utils.py:173` | `except Exception as e:` catches too broadly | Catch specific exceptions (IOError, yaml.YAMLError) |
| `skills_utils.py:188` | Bare `except Exception:` in cleanup | Consider logging the failure |
| `cli.py:1078` | `except Exception:` hides errors in PR generation | At minimum log the exception |

### 3.3 Silent Failure Risk

- `git.py:154`: `except GitError: pass` in `worktree_add` - This is documented behavior (network unavailable is OK)
- `runner.py:669`: `except GitError: pass` - Fetching origin may fail offline, acceptable
- Several places use `check=False` with subprocess, which is intentional for probing

---

## 4. API/Interface Consistency

### 4.1 Naming Conventions

**Consistent patterns observed:**
- `get_*` for retrieving items (e.g., `get_next_pending`, `get_history`, `get_by_group`)
- `mark_*` for state changes (e.g., `mark_completed`, `mark_failed`, `mark_in_progress`)
- `is_*` for boolean checks (e.g., `is_blocked`, `is_merged`, `is_running`)
- `_run` for internal subprocess execution in wrappers

### 4.2 Issues Found

1. **Inconsistent return for not-found**
   - `store.get(id)` returns `None` for not found ✓
   - `store.get_by_task_id(slug)` returns `None` ✓
   - Consistent across the codebase

2. **Parameter ordering inconsistency**
   - Most functions take `config` first when needed
   - `build_prompt(task, config, store, ...)` takes task first - this is appropriate as task is the primary subject

3. **Functions with many parameters**
   - `mark_completed(task, branch, log_file, output_content, has_commits, stats)` - 6 parameters
   - Suggestion: Consider a `CompletionInfo` dataclass to group parameters

### 4.3 Public Interface Assessment

- Modules use underscore prefix (`_`) for private functions consistently
- `__all__` exports are not defined (not required but would clarify public API)

---

## 5. Configuration and Hardcoding

### 5.1 Configuration Constants

Configuration defaults are well-defined in `config.py`:
- `DEFAULT_TIMEOUT_MINUTES = 10`
- `DEFAULT_MAX_TURNS = 50`
- `DEFAULT_USE_DOCKER = True`
- `DEFAULT_BRANCH_MODE = "multi"`
- `DEFAULT_WORK_COUNT = 1`
- `DEFAULT_WORKTREE_DIR = f"/tmp/{APP_NAME}-worktrees"`

All support environment variable overrides (`GZA_*`).

### 5.2 Magic Numbers Found

| Value | Location | Suggestion |
|-------|----------|------------|
| 60 | `cli.py:131,260` | Display truncation - consider constant `MAX_PROMPT_DISPLAY` |
| 50 | `cli.py:603,682,2458` | Different truncation length - should be consistent |
| 72 | `cli.py:807,1120,1167,1184` | PR title max length - make constant `MAX_PR_TITLE_LENGTH` |
| 500 | `cli.py:1171,1188,1260` | PR body truncation - make constant |
| 100 | `cli.py:623` | History limit - already has `--limit` flag |
| 30 | `cli.py:1407` | Purge days default - should be constant |

### 5.3 Path Handling

**Good practices observed:**
- Uses `pathlib.Path` throughout
- `Path.home()` for home directory
- Proper path joining with `/` operator

**No string concatenation for paths found** - all use pathlib correctly.

---

## 6. Logging and Observability

### 6.1 Logging Approach

The codebase uses `print()` statements for user-facing output rather than Python's `logging` module. This is intentional for a CLI tool where output goes to console.

**Output methods used:**
- `print()` - 200+ occurrences in `cli.py` for direct user output
- `console.print()` (rich) - for styled output in `runner.py`, `providers/claude.py`
- `sys.stderr` - for warnings/errors in `config.py`, `tasks.py`

### 6.2 Traceability Assessment

- Task execution can be traced via log files (`.gza/logs/<task-id>.log`)
- Each task has a unique `task_id` in YYYYMMDD-slug format
- Worker IDs provide traceability for background execution
- Session IDs enable resume capability

### 6.3 Issues Found

1. **Mixed output styles**
   - Some places use `print()`, others use `console.print()` with rich formatting
   - Suggestion: Standardize on `console.print()` for styled output, plain `print()` for data

2. **Missing operation logging**
   - Git operations don't log what they're doing (silent unless error)
   - Worktree creation/removal not logged
   - Suggestion: Add debug-level logging for git operations

3. **No structured logging**
   - All output is human-readable, no JSON logging option
   - For background workers, this is fine as output goes to log files

### 6.4 Sensitive Data

**No exposure risks found:**
- API keys read from environment, not logged
- Credentials path checked but not printed
- Session IDs are opaque UUIDs

---

## 7. Resource Management

### 7.1 File Handling

| Location | Issue | Severity |
|----------|-------|----------|
| `cli.py:2018` | `open(log_path)` without context manager | Medium |
| `runner.py:36` | `with open(home_env)` ✓ | OK |
| `runner.py:48` | `with open(project_env)` ✓ | OK |
| `workers.py:93,107,123,138` | All use `with open()` ✓ | OK |
| `config.py:178,341` | All use `with open()` ✓ | OK |
| `providers/base.py:322` | `with open(log_file)` ✓ | OK |

**Issue:** `cli.py:2018`:
```python
last_line_count = sum(1 for _ in open(log_path))
```
This opens a file without context manager. Should be:
```python
with open(log_path) as f:
    last_line_count = sum(1 for _ in f)
```

### 7.2 Database Connections

- `SqliteTaskStore._connect()` creates connections with `isolation_level=None` (autocommit)
- Connections are used within `with` statements for automatic cleanup
- No connection pooling needed for SQLite
- **No issues found**

### 7.3 Subprocess Management

- `subprocess.Popen` used in `cli.py` for background workers - properly managed with PID tracking
- `subprocess.run` used everywhere else with `capture_output=True` - properly waits
- Workers track PIDs and clean up on completion
- **No zombie process risk**

### 7.4 Temp File Cleanup

- `db.py:790` uses `tempfile` for edit prompts - should ensure cleanup
- Worktrees in `/tmp/gza-worktrees/` may accumulate - user must clean manually
- Suggestion: Add `gza cleanup` command for worktree cleanup

---

## 8. Type Safety

### 8.1 Type Hint Coverage

**Good coverage observed:**
- All public functions have type hints
- Dataclasses used for structured data (`Task`, `Config`, `RunResult`, etc.)
- `TYPE_CHECKING` guard used for import cycles

### 8.2 Common Type Patterns

```python
# Return None for not found
def get(self, task_id: int) -> Task | None:

# Optional parameters with defaults
def get_history(self, limit: int | None = 10, status: str | None = None) -> list[Task]:

# Union types for flexibility
def _run(self, *args: str, check: bool = True, stdin: bytes | None = None) -> subprocess.CompletedProcess:
```

### 8.3 Potential Issues

1. **Optional field access without None checks**
   - `task.branch` is `str | None` - most code checks before use ✓
   - `task.session_id` is `str | None` - checked in resume logic ✓

2. **dict.get() returns Optional**
   - Usage is generally safe, defaults provided where needed

3. **Mypy not configured**
   - `pyproject.toml` doesn't include mypy configuration
   - Suggestion: Add mypy to dev dependencies and CI

---

## 9. Component Interactions

### 9.1 Module Dependency Graph

```
                    ┌─────────────┐
                    │   cli.py    │ (entry point)
                    └──────┬──────┘
                           │
         ┌─────────────────┼─────────────────┐
         │                 │                 │
         ▼                 ▼                 ▼
    ┌─────────┐      ┌──────────┐      ┌──────────┐
    │ runner  │      │    db    │      │  config  │
    └────┬────┘      └──────────┘      └──────────┘
         │
    ┌────┴────┬──────────┬──────────┐
    │         │          │          │
    ▼         ▼          ▼          ▼
┌───────┐ ┌───────┐ ┌─────────┐ ┌─────────────┐
│  git  │ │github │ │providers│ │   console   │
└───────┘ └───────┘ └────┬────┘ └─────────────┘
                         │
                    ┌────┴────┐
                    │         │
                    ▼         ▼
              ┌─────────┐ ┌─────────┐
              │ claude  │ │ gemini  │
              └─────────┘ └─────────┘
```

### 9.2 Clear Patterns

- **cli.py** handles user input, delegates to runner/db
- **runner.py** orchestrates task execution, coordinates git/providers
- **db.py** is pure data access, no external dependencies
- **providers/** abstract AI provider differences

### 9.3 Potential Improvements

1. **runner.py is large (1200+ lines)**
   - Consider extracting `_run_non_code_task` to separate module
   - Consider extracting WIP save/restore logic

2. **cli.py is very large (3800+ lines)**
   - Most commands are self-contained, acceptable for CLI
   - Could extract command handlers to separate modules if it grows further

---

## 10. Recommendations

### High Priority

1. **Add tests for `runner.py` core execution paths**
   - The `run()` function is critical and currently untested
   - Add mock-based tests for worktree operations, branch handling

2. **Add tests for `git.py` operations**
   - All git operations are infrastructure-critical
   - Consider integration tests with a test repository

3. **Fix file handle leak in `cli.py:2018`**
   - Use context manager for file open

### Medium Priority

4. **Consolidate duration formatting functions**
   - Three versions exist (`_format_duration`, `_format_duration_verbose`, `format_duration`)
   - Unify into single function with format options

5. **Add mypy to CI pipeline**
   - Type hints exist but aren't validated
   - Would catch type errors early

6. **Standardize output method**
   - Choose between `print()` and `console.print()` consistently
   - Use `console.print()` for styled output, `print()` for plain data

### Low Priority

7. **Extract display constants**
   - `MAX_PROMPT_DISPLAY = 60`
   - `MAX_PR_TITLE = 72`
   - etc.

8. **Add `gza cleanup` command**
   - Clean up stale worktrees in `/tmp/gza-worktrees/`
   - Clean up old log files

9. **Consider `CompletionInfo` dataclass**
   - Group completion parameters to reduce function argument count

---

## Appendix: Test File Mapping

| Source File | Test File(s) |
|-------------|--------------|
| `__init__.py` | (none needed) |
| `__main__.py` | (none needed) |
| `_version.py` | (none needed) |
| `branch_naming.py` | `test_branch_naming.py` |
| `cli.py` | `test_cli.py` |
| `config.py` | (tested via test_cli.py) |
| `console.py` | (none) |
| `db.py` | `test_db.py` |
| `git.py` | (none) |
| `github.py` | (none) |
| `importer.py` | `test_importer.py` |
| `runner.py` | `test_runner.py` |
| `skills_utils.py` | `test_claude_install_skills.py` (partial) |
| `tasks.py` | `test_tasks.py` |
| `workers.py` | `test_workers.py` |
| `providers/base.py` | `test_providers.py` |
| `providers/claude.py` | `test_providers.py` |
| `providers/gemini.py` | `test_providers.py` |

---

## Summary Statistics

- **Total source files:** 17 (including providers/)
- **Total test files:** 13
- **Total tests:** 399 (all passing)
- **Code coverage gaps:** `runner.py` execution paths, `git.py`, `github.py`
- **Critical issues:** 1 (file handle leak)
- **Medium issues:** 5 (duplication, standardization)
- **Low issues:** 4 (constants, cleanup command)

The codebase is in good shape for release with the high-priority items addressed.
