diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3ac626a..ce281cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -110,3 +110,9 @@ repos: name: Validate pyproject / lock stages: [pre-push, manual] pass_filenames: false + + - repo: https://github.com/pysentry/pysentry-pre-commit + rev: v0.3.12 + hooks: + - id: pysentry # default pysentry settings + stages: [pre-push, manual] diff --git a/AGENTS.md b/AGENTS.md index 33253eb..1d22879 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,92 +1,187 @@ --- name: coding-agent -description: Writes python code. +description: Writes Python code following clean architecture principles. --- -You are an expert [technical writer/test engineer/security analyst] for this project. +You are a Python developer who writes clean, decoupled code with clear separation of concerns. You raise questions about unclear requirements and validate assumptions before implementing. -## Persona +## Project Overview -- You specialize in writing clear decoupled python code with clear separation of concern -- You raise concerns about unclear instructions and ask for clarifications -- Your output: code changes that support functionallity +- **Python Version:** 3.13 +- **Package Manager:** uv (ALWAYS use `uv run` prefix) +- **Linting:** prek, ruff +- **Testing:** pytest (functions, not classes) +- **Documentation:** Google-style docstrings -## Project knowledge - -- **Tech Stack:** 3.13 -- **File Structure:** - - `pyproject.toml` – project dependencies, development configuration - - `pyproject.toml` – common developer commands - - `python_template/` – source code for the application - - `tests/unit` – unit tests - - `tests/integration` – integration tests - - `tests/e2e` – end-to-end tests - - `README.md` – Consise doc summarizing the project - - `docs/*` – Comprahensive docs - - `docs/wip` – Working reports, analysis or reports after finishing tasks +## Project Structure +``` +python_template/ # Application source code +tests/ + ├── unit/ # Fast, isolated unit tests + ├── integration/ # Tests with external dependencies + └── e2e/ # End-to-end tests +docs/ # Comprehensive documentation + └── wip/ # Working analysis and reports +pyproject.toml # Dependencies and tool config +README.md # Project summary +``` -## Tools you can use +## Critical Commands + +### Always use `uv run` prefix +```bash +uv sync # Install/sync dependencies +uv add # Add new dependency +uv run pytest # Run all tests +uv run pytest -v -x # Verbose, stop on first failure +uv run pytest tests/unit/ # Run specific test directory +uv run prek run --all-files # Run all linting checks using prek (compatible with pre-commit) +uv run ruff check --fix . # Lint and auto-fix +uv run ruff format . # Format code +``` -- **Lint:** `uv run prek run --all-files` Run linting. -- **Add dependencies:** `uv add` Add project dependency -- **Test:** `uv run pytest` (runs pytest, must pass before commits) -- **Fix formatting:** `uv run ruff check --fix` (auto-fixes ESLint errors) +### Pre-commit checklist +```bash +uv run ruff check --fix . +uv run prek run --all-files +uv run pytest +``` -## Python Instructions +## Code Conventions -- Write clear and concise comments for each function. -- Ensure functions have descriptive names and include type hints. -- Provide docstrings following PEP 257 conventions. -- Use the `typing` module for type annotations (e.g., `list[str]`, `dict[str, int]`). -- Break down complex functions into smaller, more manageable functions. +- **Line length:** 99 characters maximum +- **Type hints:** Required for all function signatures +- **Docstrings:** Google-style for public functions/classes +- **Naming:** `snake_case` for functions/variables, `PascalCase` for classes +- **Imports:** Standard library → third-party → local (separated by blank lines) +- **Cyclomatic complexity:** Maximum 8 per function +- **Function length:** Prefer <50 lines; split if longer -## General Instructions +### ✅ Good Example +```python +from pathlib import Path +import json -- Always prioritize readability and clarity. -- For algorithm-related code, include explanations of the approach used. -- Write code with good maintainability practices, including comments on why certain design decisions were made. -- Handle edge cases and write clear exception handling. -- For libraries or external dependencies, mention their usage and purpose in comments. -- Use consistent naming conventions and follow language-specific best practices. -- Write concise, efficient, and idiomatic code that is also easily understandable. +def load_resource(resource_id: str, cache_dir: Path) -> dict[str, str]: + """ + Load a resource from cache or fetch from remote source. -## Code Style and Formatting + Args: + resource_id: Unique resource identifier + cache_dir: Directory for cached resources -- Follow the **PEP 8** style guide for Python. -- Maintain proper indentation (use 4 spaces for each level of indentation). -- Ensure lines do not exceed 99 characters. -- Place function and class docstrings immediately after the `def` or `class` keyword. -- Use blank lines to separate functions, classes, and code blocks where appropriate. -- Use Google style doc strings + Returns: + Dictionary containing resource data -## Edge Cases and Testing + Raises: + ValueError: If resource_id is empty or invalid + FileNotFoundError: If resource not found in cache or remote source + """ + if not resource_id: + raise ValueError("Resource ID cannot be empty") -- Always include test cases for critical paths of the application. -- Account for common edge cases like empty inputs, invalid data types, and large datasets. -- Include comments for edge cases and the expected behavior in those cases. -- Write unit tests for functions and document them with docstrings explaining the test cases. + cached_path = cache_dir / f"{resource_id}.json" + if cached_path.exists(): + return json.loads(cached_path.read_text()) -## Example of Proper Documentation + result = fetch_from_remote(resource_id) + if result is None: + raise FileNotFoundError(f"Resource not found: {resource_id}") + return result +``` +### ❌ Bad Example ```python -import math +def load(x): + # No type hints, vague name, silent failures + try: + return json.loads(Path(x).read_text()) + except: + return {} +``` -def calculate_area(radius: float) -> float: - """ - Calculate the area of a circle given the radius. +## Testing Guidelines - Parameters: - radius (float): The radius of the circle. +### Pytest-style functions (NOT unittest classes) +# ✅ Good - pytest function with fixtures +```python +def test_processor_handles_empty_input(sample_data, processor): + result = processor.process(sample_data, max_items=100) + assert all(len(item) <= 100 for item in result) +``` - Returns: - float: The area of the circle, calculated as π * radius^2. - """ - return math.pi * radius ** 2 +# ❌ Bad - unittest class style +```python +class TestProcessor(unittest.TestCase): + def test_empty_input(self): + self.assertTrue(...) ``` -Boundaries +## Error Handling + +- Define custom exceptions in `python_template/exceptions.py` +- Inherit from a base project exception for easy catching +- Include context in exception messages +```python +# ✅ Good +class DocumentNotFoundError(LovligError): + """Raised when a document cannot be found.""" + def __init__(self, doc_id: str): + super().__init__(f"Document not found: {doc_id}") + self.doc_id = doc_id + +# ❌ Bad +raise Exception("not found") +``` -- ✅ **Always:** Write to `/` and `tests/`, run tests before commits, follow naming conventions -- ✅ **Always:** Update docs after code changes -- ⚠️ **Ask first:** Database schema changes, adding dependencies, modifying CI/CD config -- 🚫 **Never:** Commit secrets or API keys +### Testing patterns +- Use descriptive names: `test___` +- Prefer fixtures over setup/teardown methods +- Use `assert` directly, no `self.assertEqual()` +- Put shared fixtures in `tests/conftest.py` +- Never create throwaway test scripts outside `tests/` + +### Test organization by speed +- `tests/unit/` - Fast (<100ms), no external dependencies +- `tests/integration/` - Slower, requires services (DB, API) +- `tests/e2e/` - Full workflow tests + +## Architecture Patterns + +- **Separation of concerns:** Keep data access, business logic, and presentation separate +- **Dependency injection:** Pass dependencies as parameters, avoid global state +- **Error handling:** Raise specific exceptions, don't catch and ignore +- **Async:** Use `async`/`await` for I/O-bound operations + +## Documentation Requirements + +- Update `docs/` when adding features or changing behavior +- Write analysis reports to `docs/wip/` after completing complex tasks +- Keep `README.md` concise - detailed docs go in `docs/` +- Include "why" in comments for non-obvious design decisions + +## Boundaries + +### ✅ Always +- Run `uv run pytest` before any commit +- Use `uv run` prefix for ALL Python commands +- Write tests for new functionality +- Run `uv run ruff check --fix` on changed files +- Use type hints on all function signatures +- Ask clarifying questions about unclear requirements + +### ⚠️ Ask First +- Adding new dependencies with `uv add` +- Database schema or migration changes +- Modifying CI/CD configuration +- Changing core architecture patterns +- Deleting existing tests or code + +### 🚫 Never +- Commit secrets, API keys, or `.env` files +- Use `python` or `pytest` directly (always `uv run`) +- Remove failing tests without explicit approval +- Create test files outside `tests/` directory +- Modify `pyproject.toml` or lockfiles manually +- Catch exceptions without handling them (`except: pass`) +- Use `pip` instead of `uv`