Thank you for your interest in contributing to Prodigy! This document provides guidelines and best practices for contributing to the project.
- Code of Conduct
- Getting Started
- Development Setup
- Error Handling Guidelines
- Testing
- Pull Request Process
- Code Style
This project adheres to a code of conduct. By participating, you are expected to uphold this code. Please be respectful and constructive in all interactions.
- Fork the repository
- Clone your fork:
git clone https://github.com/your-username/prodigy.git - Create a new branch:
git checkout -b feature/your-feature-name - Make your changes
- Run tests:
cargo test - Commit your changes with clear messages
- Push to your fork and submit a pull request
- Rust 1.75 or later
- Git
- Claude CLI (for testing workflows)
cargo build --release# Run all tests
cargo test
# Run tests with output
cargo test -- --nocapture
# Run specific test
cargo test test_nameProper error handling is critical for production reliability. This project follows strict error handling practices to prevent panics and ensure graceful failure recovery.
- No panic!() in production code - All panic!() calls must be eliminated from production code paths
- No unwrap() in critical modules - Replace with proper error handling using
?operator orok_or_else() - Use Result types - All fallible operations should return
Result<T, E> - Provide context - Use
anyhow::Contextto add helpful error messages - Graceful degradation - Systems should fail gracefully with clear error messages
// Good - propagates errors properly
pub async fn read_file(path: &Path) -> Result<String> {
let content = fs::read_to_string(path)
.await
.context("Failed to read file")?;
Ok(content)
}
// Bad - will panic if file doesn't exist
pub async fn read_file(path: &Path) -> String {
fs::read_to_string(path).await.unwrap()
}// Good - provides meaningful error
let value = map.get("key")
.ok_or_else(|| anyhow!("Key 'key' not found in configuration"))?;
// Bad - will panic if key doesn't exist
let value = map.get("key").unwrap();// Good - handles both cases explicitly
match optional_value {
Some(val) => process_value(val),
None => {
log::warn!("Value not present, using default");
use_default()
}
}
// Bad - assumes value exists
if optional_value.is_some() {
process_value(optional_value.unwrap())
}// Good - compile-time error for unsupported platforms
#[cfg(not(unix))]
compile_error!("Only Unix-like systems are supported");
// Bad - runtime panic
#[cfg(not(unix))]
panic!("Only Unix-like systems are supported");The following modules are considered critical and must have zero unwrap() or panic!() calls:
-
Analytics Engine (
src/analytics/)- Session tracking
- Cost calculation
- Performance metrics
-
Cook Orchestrator (
src/cook/orchestrator.rs)- Workflow execution
- Command coordination
- Error recovery
-
Git Operations (
src/git/)- Repository management
- Worktree operations
- Commit tracking
-
Session Tracking (
src/cook/session/)- State management
- Progress tracking
- Checkpointing
-
Resume Logic (
src/resume_logic.rs)- Workflow resumption
- State recovery
- Checkpoint validation
All error handling code must be tested:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_on_missing_file() {
let result = read_file(Path::new("/nonexistent"));
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Failed to read"));
}
#[test]
fn test_graceful_recovery() {
let mut state = WorkflowState::new();
let result = state.recover_from_error(test_error());
assert!(result.is_ok());
assert_eq!(state.status(), Status::Recovered);
}
}All functions that return Result must document:
/// Reads the configuration file from the specified path.
///
/// # Arguments
/// * `path` - Path to the configuration file
///
/// # Returns
/// * `Ok(Config)` - Successfully parsed configuration
/// * `Err` - If file doesn't exist or parsing fails
///
/// # Errors
/// This function will return an error if:
/// * The file cannot be read
/// * The file contents are not valid YAML
/// * Required configuration fields are missing
pub fn read_config(path: &Path) -> Result<Config> {
// implementation
}- Unit Tests: Test individual functions and modules
- Integration Tests: Test component interactions
- Error Path Tests: Specifically test error handling
- Scenario Tests: Test complete workflows
- Test both success and failure paths
- Use descriptive test names
- Include edge cases
- Test error messages and context
- Ensure all tests pass:
cargo test - Run linter:
cargo clippy - Format code:
cargo fmt - Update documentation if needed
- Add tests for new functionality
- Ensure no new unwrap() or panic!() calls in production code
- Update CHANGELOG.md with your changes
- Submit PR with clear description
- Follow standard Rust naming conventions
- Use
cargo fmtfor formatting - Use
cargo clippyfor linting - Prefer explicit error handling over panics
- Document public APIs
- Keep functions focused and small
- Use meaningful variable names
- Use present tense ("Add feature" not "Added feature")
- Keep first line under 50 characters
- Reference issues and pull requests
- Include context for why changes were made
Example:
fix: replace panic!() with proper error handling
- Eliminated all panic!() calls from production code
- Replaced unwrap() with ? operator in critical modules
- Added comprehensive error context using anyhow
- Fixes #123
If you have questions about contributing, please open an issue for discussion.
Thank you for contributing to Prodigy!