This file contains project-specific instructions for Claude Code (or any AI assistant) working on this project.
A Python CLI tool for scraping mountain peak data from PeakBagger.com. Uses Click for CLI, cloudscraper for Cloudflare bypass, BeautifulSoup for HTML parsing, and Rich for terminal output.
- Python: 3.12+ (use modern syntax:
X | Noneinstead ofOptional[X]) - Package Manager:
uv(NOT pip) - useuv run,uv sync,uv add - CLI Framework: Click
- Scraping: cloudscraper + BeautifulSoup4 + lxml
- Output: Rich (tables, colors) + JSON
- Linting/Formatting: Ruff (NOT Black, isort, or flake8)
- Version Management: python-semantic-release
- Pre-commit: Configured with multiple hooks
# First time setup - configure git hooks and commit template
bash scripts/setup-git-hooks.sh
# This will:
# - Install pre-commit hooks (prevents commits to main)
# - Configure commit message template with conventional commit format# Run CLI during development
uv run peakbagger peak search "Mount Rainier"
uv run peakbagger peak show 2296
uv run peakbagger peak ascents 2296
uv run peakbagger peak stats 2296
uv run peakbagger ascent show 12963
# Run with logging (useful for debugging)
uv run peakbagger --verbose peak search "Mount Rainier" # Show HTTP requests
uv run peakbagger --debug peak show 2296 # Show detailed operations
# Format and lint
uv run ruff format peakbagger tests
uv run ruff check --fix peakbagger tests
# Run pre-commit hooks
uv run pre-commit run --all-files
# Run tests
uv run pytest --cov=peakbagger
# Build package
uv build
# Makefile shortcuts (mirrors CI)
make check # lint + typecheck + complexity
make ci # check + coverage (85% threshold)
make coverage # tests with coverage report
make complexity # lizard complexity analysisCRITICAL: This project uses Conventional Commits for automated version management with python-semantic-release.
<type>: <description>
[optional body]
[optional footer]
Types that trigger releases:
fix:- Bug fix (PATCH: 0.1.0 → 0.1.1)feat:- New feature (MINOR: 0.1.0 → 0.2.0)perf:- Performance improvement (PATCH)- Footer with
BREAKING CHANGE:- Breaking change (MAJOR: 0.1.0 → 1.0.0)
Types that don't trigger releases:
docs:- Documentation only changesstyle:- Code style/formatting (no logic changes)refactor:- Code refactoring (no feature change)test:- Adding or updating testschore:- Maintenance, dependencies, toolingci:- CI/CD configurationbuild:- Build system changes
# Patch release
git commit -m "fix: handle missing elevation data in scraper"
# Minor release
git commit -m "feat: add --limit flag to search command"
# Major release
git commit -m "feat: redesign CLI commands
BREAKING CHANGE: renamed --full to --detailed for consistency"
# No release
git commit -m "docs: update README installation instructions"
git commit -m "chore: update dependencies"
git commit -m "refactor: extract parsing logic into helper methods"- First line: Imperative mood ("add" not "added" or "adds")
- First line: Lowercase after colon
- First line: No period at end
- First line: Max 50 characters (preferably)
- Body: Wrap at 72 characters
- Body: Explain why not what (the diff shows what changed)
git commit -m "feat: add support for batch peak lookup
Adds new --batch flag that accepts a file containing peak IDs.
Processes peaks with proper rate limiting and error handling.
Closes #42"- Type hints: Always use for function parameters and return values
- Docstrings: Required for all public functions and classes
- Modern Python: Use
X | None, notOptional[X] - Error handling: Use proper exception chaining (
raise ... from e) - Rich output: Use Rich for user-facing terminal output, not print()
- JSON output: Must be valid, parseable JSON (no Rich formatting in JSON mode)
- Logging: Use loguru logger for all logging (never use print() for debugging)
peakbagger/
├── __init__.py # Package initialization (version from metadata)
├── cli.py # Click commands (main entry point)
├── client.py # HTTP client with rate limiting
├── scraper.py # HTML parsing with BeautifulSoup
├── models.py # Data models (Peak, SearchResult)
├── formatters.py # Output formatting (Rich tables, JSON)
└── logging_config.py # Logging configuration with loguru
- Always respect the 2-second default rate limit
- Client has
rate_limit_secondsparameter (default: 2.0) - Never bypass or reduce rate limiting in production code
- Test parsing against real HTML saved locally
- PeakBagger.com structure may change - make parsing resilient
- Always handle missing data gracefully (return None, not crash)
- Use
get_text(strip=True)to clean whitespace
- Text mode: Use Rich tables with colors (default)
- JSON mode: Pure JSON, no Rich formatting, must be parseable
- Both modes must show the same data (just different format)
- Use cloudscraper, not plain requests
- Set clear User-Agent identifying the tool
- PeakBagger.com blocks curl and simple requests
- Use increased rate limits during development (
--rate-limit 5.0) - Test with well-known peaks (Mount Rainier: 2296, Denali: 271)
- Don't hammer the server - save sample HTML for testing
- Use loguru logger, NOT print() for debugging or informational output
- No status messages - CLI output is silent by default (except errors and data output)
- Log levels:
- INFO: HTTP requests (method, URL, status code, response time)
- DEBUG: Parsing details, rate-limiting waits, operational details
- ERROR: Errors and exceptions
- By default, no logs are shown (level set to CRITICAL)
- Users enable logging via
--verbose(INFO) flag --debugflag requires--verboseand adds DEBUG level logs- All logs go to stderr (allows easy redirection)
- Format:
- Verbose mode:
HH:MM:SS | LEVEL | message(clean, no file info) - Debug mode:
HH:MM:SS | LEVEL | file:line - message(includes source location)
- Verbose mode:
This project uses Release Please for automated version management and releases.
- Push conventional commits to
mainbranch - Release Please accumulates changes in an ongoing release PR
- Merge the release PR to cut a new version
- GitHub Actions builds and publishes to PyPI via OIDC trusted publishing
The workflow is defined in .github/workflows/release.yml.
- Version bumps follow conventional commit prefixes:
feat:= minor,fix:= patch,feat!:orBREAKING CHANGE:= major - Currently at v1.x (post-1.0 release)
- ❌ Using
pipinstead ofuv - ❌ Using
Optional[X]instead ofX | None - ❌ Using
print()instead of Rich Console - ❌ Committing without conventional commit format
- ❌ Using Black, isort, or flake8 (we use Ruff for everything)
- ❌ Reducing rate limits below 2 seconds
- ❌ Testing extensively against live website (save HTML samples)
When changing functionality:
- Update code in
peakbagger/ - Update relevant examples in
examples/if applicable - Update README.md if user-facing behavior changes
- Update CONTRIBUTING.md if development process changes
ALWAYS check and update documentation when modifying CLI output:
-
README.md - Check all "Sample Output" examples:
- Search command text output (line ~108)
- Info command text output (line ~150)
- Info command JSON output (line ~165)
-
CONTRIBUTING.md - Check command examples in:
- Testing section
- Development tips
- Release examples
-
CLAUDE.md - Update if new fields/formats are added
Output changes include:
- Adding/removing columns in tables
- Adding/removing fields in JSON
- Changing field names or formats
- Modifying table styles or layouts
- Less is more - only document project-specific information
- No generic Git/GitHub instructions (developers know this)
- Focus on what makes this project different
- Link to external docs rather than duplicating them
- click >= 8.0.0
- cloudscraper >= 1.2.0
- beautifulsoup4 >= 4.9.0
- lxml >= 4.6.0
- rich >= 10.0.0
- loguru >= 0.7.3
- pytest + pytest-cov
- ruff
- pre-commit
- ty
Always use uv add and uv add --dev to manage dependencies.
If instructions conflict or are unclear:
- Check CONTRIBUTING.md for release process details
- Check pyproject.toml for configuration
- Check this file for project-specific rules
- When in doubt, ask the user before proceeding