prodigy 0.4.4

Turn ad-hoc Claude sessions into reproducible development pipelines with parallel AI agents
Documentation
## Configuration Precedence Rules

Prodigy loads configuration from multiple sources with a clear precedence hierarchy. Understanding how configuration is merged helps you control which settings take effect.

### Precedence Hierarchy

From highest to lowest priority:

1. **Project Config** (`.prodigy/config.yml`) - Highest priority
   - Project-specific settings in your repository
   - Located at `.prodigy/config.yml` in your project directory
   - Overrides all default values
   - Committed to version control (be careful with secrets)

2. **Defaults** (lowest priority)
   - Built-in default values defined in the code
   - Used when project config doesn't provide a value
   - See source: `src/config/mod.rs:88-100`

**Note**: Global config (`~/.prodigy/config.yml`), environment variables (`PRODIGY_*`), and CLI flag overrides are defined in the code but not currently loaded in production. Only project-level configuration and defaults are active.

### How Settings Are Loaded

When Prodigy starts, it builds the final configuration with this process:

1. **Initialize with defaults** - Create `GlobalConfig` with built-in defaults (source: `src/config/mod.rs:88-100`)
2. **Load project config** - Read `.prodigy/config.yml` from project directory (source: `src/config/loader.rs:85-104`)
3. **Merge at field level** - Project config values override defaults on a per-field basis (source: `src/config/mod.rs:133-154`)

Project config **overrides** default values at the individual field level. Settings not specified in project config inherit from defaults.

### Examples

#### Example 1: Using Defaults

```yaml
# No .prodigy/config.yml file exists
```

**Result**: Prodigy uses all default values:
- `log_level: "info"`
- `auto_commit: true`
- `max_concurrent_specs: 1`

(Source: `src/config/mod.rs:88-100`)

#### Example 2: Project Config Override

```yaml
# .prodigy/config.yml (project config)
name: my-project
claude_api_key: "sk-project-key"
auto_commit: false
```

**Result**:
- `claude_api_key: "sk-project-key"` (from project config)
- `auto_commit: false` (from project config)
- `log_level: "info"` (from defaults - not specified in project)
- `max_concurrent_specs: 1` (from defaults - not specified in project)

Field-level precedence is implemented via getter methods (source: `src/config/mod.rs:133-154`):
```rust
pub fn get_auto_commit(&self) -> bool {
    self.project
        .as_ref()
        .and_then(|p| p.auto_commit)
        .or(self.global.auto_commit)
        .unwrap_or(true)  // Default if neither provides value
}
```

#### Example 3: Partial Project Override

```yaml
# .prodigy/config.yml
name: my-project
claude_api_key: "sk-abc123"
# Other fields not specified
```

**Result**:
- `claude_api_key: "sk-abc123"` (from project config)
- `log_level: "info"` (from defaults)
- `auto_commit: true` (from defaults)
- `max_concurrent_specs: 1` (from defaults)

### Field-Level Precedence

Precedence is applied **per field**, not per file. Each configuration field is resolved independently using the precedence rules.

```yaml
# .prodigy/config.yml (project config)
name: my-project
auto_commit: false  # Only override auto_commit
# Other fields inherited from defaults
```

**Precedence Logic** (source: `src/config/mod.rs:133-154`):
1. Check if project config has the field → use it
2. Otherwise, check if global config has the field → use it
3. Otherwise, use the default value

This allows fine-grained configuration: override only what you need, inherit the rest.

### Configuration Loading Implementation

The configuration loading happens in these steps (source: `src/config/loader.rs`):

**Step 1: Initialize**
```rust
// ConfigLoader::new() - line 23
let config = Config::new();  // Creates Config with GlobalConfig defaults
```

**Step 2: Load Project Config** (optional)
```rust
// ConfigLoader::load_project() - line 85
let config_path = project_path.join(".prodigy").join("config.yml");
if config_path.exists() {
    let content = fs::read_to_string(&config_path).await?;
    let project_config = parse_project_config(&content)?;
    *config = merge_project_config(config.clone(), project_config);
}
```

**Step 3: Access with Precedence**
```rust
// Config::get_claude_api_key() - line 133
self.project
    .as_ref()
    .and_then(|p| p.claude_api_key.as_deref())  // Try project first
    .or(self.global.claude_api_key.as_deref())   // Fall back to global
```

### Default Values

Built-in defaults (source: `src/config/mod.rs:88-100`):

```rust
impl Default for GlobalConfig {
    fn default() -> Self {
        Self {
            prodigy_home: get_global_prodigy_dir()
                .unwrap_or_else(|_| PathBuf::from("~/.prodigy")),
            default_editor: None,
            log_level: Some("info".to_string()),
            claude_api_key: None,
            max_concurrent_specs: Some(1),
            auto_commit: Some(true),
            plugins: None,
        }
    }
}
```

### Future: Global Config and Environment Variables

The codebase includes infrastructure for additional configuration sources, but these are not currently loaded in production:

**Global Config** (`~/.prodigy/config.yml`):
- Mentioned in documentation (line 49: `src/config/mod.rs`)
- No loader implementation yet
- Would provide user-level defaults across all projects

**Environment Variables**:
- Defined in `Config::merge_env_vars()` (lines 111-131: `src/config/mod.rs`)
- Supports: `PRODIGY_CLAUDE_API_KEY`, `PRODIGY_LOG_LEVEL`, `PRODIGY_EDITOR`, `PRODIGY_AUTO_COMMIT`
- Only called in tests, not in production code
- Would override file-based configuration when implemented

**CLI Flag Overrides**:
- No implementation yet
- Would provide highest-priority overrides for individual runs

### Test Coverage

Configuration precedence behavior is validated through comprehensive tests (source: `src/config/loader.rs:113-334`):

**Test: Default Configuration**
```rust
// Line 120: test_new_creates_default_config
// Verifies GlobalConfig defaults are set correctly
assert_eq!(config.global.log_level, Some("info".to_string()));
assert_eq!(config.global.max_concurrent_specs, Some(1));
assert_eq!(config.global.auto_commit, Some(true));
```

**Test: Project Config Loading**
```rust
// Line 230: test_load_project_config
// Verifies .prodigy/config.yml is loaded and merged
let project = config.project.unwrap();
assert_eq!(project.name, "test-project");
assert_eq!(project.claude_api_key, Some("test-key".to_string()));
assert_eq!(project.auto_commit, Some(false));
```

**Test: Field-Level Override**
```rust
// src/config/mod.rs:471 - test shows project overrides global
config.project = Some(ProjectConfig {
    name: "test".into(),
    claude_api_key: Some("project-key".into()),
    // ... other fields
});
assert_eq!(config.get_claude_api_key(), Some("project-key"));
```