# CLAUDE.md

**PySentry** - A fast, reliable security vulnerability scanner for Python projects, written in Rust.

## Critical Rules - Read First

These override defaults. Violations cause production bugs, panics, or security issues.

**Scope Discipline:**
- NEVER add files, abstractions, dependencies, or features not explicitly requested
- NEVER refactor surrounding code when fixing a bug - fix only what is broken
- NEVER add docstrings, comments, or type annotations to code you did not change
- When adding/removing `Commands`/`ConfigCommands` variants or changing handler signatures in `src/cli.rs`, MUST update the match arms in `src/python.rs` - it's a thin PyO3 shim that mirrors `src/main.rs` through shared types

**Resolver Isolation (CRITICAL):**
- Resolvers MUST run in isolated temp directories - shared state causes cross-project contamination
- ALWAYS force cache to temp dir (`UV_CACHE_DIR`, `PIP_CACHE_DIR`) - NEVER use project cache
- Reference: `UvResolver::create_isolated_temp_dir()`, `PipToolsResolver::create_isolated_temp_dir()`
- Default timeout: 5 minutes (hardcoded) - may fail on slow networks/large dependency trees

**Error Handling:**
- MUST use `?` operator. NEVER use `.unwrap()` or `.expect()` in production code - panics crash the binary for all users
- Replace `.unwrap()` with `.ok_or_else()` or `.context()` for propagation
- Exception: `let _ = self.cached_*.set()` is intentional - OnceLock set failure is benign
- All errors MUST propagate with context or be logged at WARN level
- NEVER use `let _ =` on fallible operations without logging at WARN level

**Safety:**
- MUST use `.get()` over `[]` indexing - `[]` panics on out-of-bounds
- MUST use `Option` and `Result` extensively - NEVER bypass with unwrap
- All public APIs MUST return `Result<T>` for fallible operations

## Codebase Map

**Architecture:** Dual interface (Rust binary `pysentry` + Python package `pysentry-rs`)

**Key Modules:**
- `src/main.rs` - CLI entry point
- `src/lib.rs` - `AuditEngine` API (high-level audit orchestrator)
- `src/cache/` - Multi-tier caching
  - `audit.rs` - `AuditCache` interface
  - `storage.rs` - `CacheEntry` abstraction
- `src/dependency/` - Dependency scanning + external resolvers
  - `scanner.rs` - `DependencyScanner` (main orchestration)
  - `resolvers/uv.rs` - UV resolver (preferred, Rust-based)
  - `resolvers/pip_tools.rs` - pip-compile fallback
- `src/parsers/` - 7 parsers with priority system
  - Priority 1: `lock.rs` (uv.lock), `poetry_lock.rs`, `pipfile_lock.rs`, `pylock.rs`
  - Priority 3: `pyproject.rs` (requires external resolver)
  - Priority 4: `pipfile.rs` (requires external resolver)
  - Priority 5: `requirements.rs` (requires external resolver)
  - See `ParserTrait::priority()` for implementation details
- `src/providers/` - PyPA, PyPI, OSV.dev integrations
- `src/vulnerability/` - Matching engine + database
- `src/output/` - Human, JSON, SARIF, markdown reports
- `src/config.rs` - Hierarchical TOML config

## Parser Rules

**Priority System:**
- Lower number = higher priority (1–5 scale)
- Lock files (priority 1) ALWAYS take precedence over manifest files (priority 3–5)
- New parsers MUST return priority via `ParserTrait::priority()`

**Known Limitations:**
- Path dependencies NOT extracted from lock files - virtual/editable installs skipped
- Poetry 2.x uses different marker format than 1.x - custom deserializer handles this
- Virtual packages and editable installs are excluded from vulnerability scans

## Cache Safety

- Atomic writes via temp-file + rename pattern - see `CacheEntry::write_atomic_sync`
- Idempotent deletes - `NotFound` errors treated as success
- Write failures only logged at WARN level, NEVER retried - retry storms corrupt the cache
- Resolution cache: 24h TTL, content-based keys (requirements + resolver + Python version)
- Vuln DB cache: 24h TTL

**Cache Locations:** `~/.cache/pysentry/vulnerability-db/`, `~/.cache/pysentry/dependency-resolution/`

## Rust Code Style

- MUST use `module_name.rs` over `mod.rs` - Rust 2024 convention, avoids ambiguous `mod.rs` in nested directories
- Only "why" comments, NEVER organizational/summary comments - code structure MUST be self-documenting
- Full words for variable names (`package_name` not `pkg`) - reduces cognitive load when scanning unfamiliar code. Exception: standard idioms like `i` for index

## Config & Runtime

**Config Discovery:** `.pysentry.toml` (project) → `~/.config/pysentry/config.toml` (user) → `/etc/pysentry/config.toml` (system)
**Override Env Vars:** `PYSENTRY_CONFIG` (path override), `PYSENTRY_NO_CONFIG` (disable all config)

**CLI Structure:** See `src/cli.rs` and `src/main.rs`
- Main: `pysentry [options] [path]`
- Subcommands: `resolvers`, `check-version`, `config {init|show|validate}`

## Development Commands

```bash
# Build
cargo build --release

# Test
cargo test                                             # all tests
cargo test -- --nocapture                              # with output
cargo test test_name                                   # specific test

# Code quality
cargo fmt --all                                        # format
cargo fmt --all -- --check                             # CI format check
cargo clippy --all-targets --all-features              # lint
cargo clippy --all-targets --all-features -- -D warnings  # CI lint (warnings = errors)
cargo check --all-targets --all-features               # type check

# Python bindings (requires maturin)
maturin develop                                        # dev build
maturin build --release                                # release wheel

# Tools
cargo audit                                            # security audit
cd benchmarks && python main.py                        # benchmarks
pre-commit run --all-files                             # hooks
gh pr view <number>                                    # GitHub CLI (READ-ONLY)
gh issue view <number>
```
