# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
podpull is a Rust CLI tool for downloading and synchronizing podcasts from RSS feeds. It's designed for local backup with no external services, accounts, or databases — the output directory itself serves as the state.
**CLI**: `podpull [OPTIONS] <feed> <output-dir>`
- `feed` - RSS feed URL or local file path
- `output_dir` - Directory for downloaded episodes
- `-c, --concurrent <N>` - Max concurrent downloads (default: 3)
- `-l, --limit <N>` - Download only N most recent undownloaded episodes
- `-q, --quiet` - Suppress progress output
## Development Commands
```bash
cargo build # Build debug version
cargo build --release # Build optimized release version
cargo run -- <args> # Run with arguments
cargo test # Run all tests
cargo clippy # Run linter
cargo fmt # Format code
```
## Code Architecture
```
src/
├── main.rs # CLI entry, argument parsing, progress UI (indicatif)
├── lib.rs # Public API exports
├── error.rs # Error types: FeedError, DownloadError, MetadataError, StateError, SyncError
├── sync.rs # Main orchestration: fetch → parse → scan → plan → download
├── state.rs # Output directory scanning, sync planning, episode sorting
├── http.rs # HttpClient trait + reqwest implementation
├── progress.rs # ProgressEvent enum + ProgressReporter trait
├── feed/
│ ├── fetch.rs # Fetch from URL or local file
│ └── parse.rs # RSS parsing → Podcast/Episode structs
├── episode/
│ ├── download.rs # Stream to .partial file, SHA-256 hash, atomic rename
│ └── filename.rs # Generate filenames from metadata
└── metadata/
├── episode.rs # Per-episode JSON metadata
└── podcast.rs # Feed-level podcast.json
```
### Key Design Patterns
- **Stateless**: No database; state derived from output directory files
- **Trait abstraction**: `HttpClient` and `ProgressReporter` traits enable testing with mocks
- **Atomic downloads**: Write to `.partial` file, rename on completion
- **Content integrity**: SHA-256 hash stored in episode metadata
- **GUID deduplication**: Track episodes by RSS GUID (fallback to URL if missing)
- **Concurrent slots**: Downloads run in parallel but respect `--limit` ordering
### Output Structure
```
output-dir/
├── podcast.json # Feed metadata
├── 2024-01-15-episode-title.mp3 # Audio file
└── 2024-01-15-episode-title.json # Episode metadata (guid, content_hash, downloaded_at)
```
## Testing
Tests use `tempfile::tempdir()` for isolation and mock `HttpClient` implementations.
```bash
cargo test # Run all tests
cargo test sync::tests # Run sync module tests
cargo test state::tests # Run state module tests
```
Key test files: `sync.rs` (3 tests), `state.rs` (8 tests), `feed/parse.rs`, `episode/download.rs`
## Code Quality
Before committing:
1. `cargo fmt` - Format code
2. `cargo clippy` - Check for lint issues
3. `cargo test` - Ensure tests pass
## Architecture Decision Records (ADRs)
ADRs are stored in `doc/adr/` (16 ADRs documenting key decisions). Use the `adrs` tool:
**CRITICAL: Always set `EDITOR=true` to prevent hanging:**
```bash
EDITOR=true adrs new "Title of decision"
EDITOR=true adrs list
EDITOR=true adrs link <source> <link-type> <target>
```
## Commit Style
- **Atomic commits**: One logical change per commit
- **Working state**: Every commit must build and pass tests
- **No AI attribution**: No mentions of AI/Claude in commit messages, no `Co-Authored-By` lines
- **Message format**: Imperative subject line, optional body explaining "why"
Example:
```
Add retry logic for failed downloads
Transient network errors are common with podcast feeds. Retrying
up to 3 times with exponential backoff improves reliability.
```
## Versioning and Changelog
- **Semantic Versioning**: Follow [semver](https://semver.org/) for version numbers in `Cargo.toml`
- MAJOR: Breaking changes
- MINOR: New features, backward compatible
- PATCH: Bug fixes, backward compatible
- **Changelog**: Maintain `CHANGELOG.md` using [Keep a Changelog](https://keepachangelog.com/) format
- **User-focused entries**: Document what changed for users, not implementation details
- Good: "Directory scanning displays a progress bar"
- Bad: "Renamed `FeedParsed` event to `SyncPlanReady`"
- Implementation details belong in commits and ADRs, not the changelog