Commit da134e8559f6

Vincent Demeester <vincent@sbr.pm>
2025-12-05 11:09:16
feat(skills): Add comprehensive Python skill with modern tooling
- Enable modern Python development with uv, ruff, mypy, and pytest - Support PEP 723 inline script metadata for standalone scripts - Provide 8 workflows covering projects, testing, and packaging Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent 222852c
dots/.config/claude/skills/Python/tools/python-check
@@ -0,0 +1,99 @@
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+#     "ruff>=0.8.0",
+#     "mypy>=1.13.0",
+#     "pytest>=8.0.0",
+#     "pytest-cov>=6.0.0",
+# ]
+# ///
+"""Run all Python quality checks: lint, type check, and tests.
+
+This comprehensive check runs:
+1. ruff check (linting)
+2. ruff format --check (formatting)
+3. mypy (type checking)
+4. pytest with coverage (testing)
+"""
+
+import subprocess
+import sys
+from pathlib import Path
+
+
+def run_check(name: str, cmd: list[str]) -> tuple[bool, int]:
+    """Run a check command.
+
+    Args:
+        name: Name of the check
+        cmd: Command and arguments to run
+
+    Returns:
+        Tuple of (success, exit_code)
+    """
+    print(f"\n{'='*60}")
+    print(f"Running: {name}")
+    print(f"{'='*60}")
+    print(f"Command: {' '.join(cmd)}\n")
+
+    result = subprocess.run(cmd, check=False)
+    success = result.returncode == 0
+
+    if success:
+        print(f"\n✓ {name} passed")
+    else:
+        print(f"\n✗ {name} failed")
+
+    return success, result.returncode
+
+
+def main() -> int:
+    """Run all quality checks.
+
+    Returns:
+        Exit code (0 if all checks pass, 1 if any fail)
+    """
+    target = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(".")
+
+    if not target.exists():
+        print(f"Error: {target} does not exist", file=sys.stderr)
+        return 1
+
+    checks: list[tuple[str, list[str]]] = [
+        ("Linting (ruff check)", ["ruff", "check", str(target)]),
+        ("Formatting (ruff format)", ["ruff", "format", "--check", str(target)]),
+        ("Type checking (mypy)", ["mypy", str(target)]),
+        ("Tests (pytest)", ["pytest", "--cov", str(target)]),
+    ]
+
+    results: list[tuple[str, bool, int]] = []
+
+    for name, cmd in checks:
+        success, code = run_check(name, cmd)
+        results.append((name, success, code))
+
+    # Print summary
+    print(f"\n{'='*60}")
+    print("Summary")
+    print(f"{'='*60}")
+
+    all_passed = True
+    for name, success, code in results:
+        status = "✓ PASS" if success else "✗ FAIL"
+        print(f"{status} - {name}")
+        if not success:
+            all_passed = False
+
+    print(f"{'='*60}\n")
+
+    if all_passed:
+        print("✓ All checks passed!")
+        return 0
+    else:
+        print("✗ Some checks failed")
+        return 1
+
+
+if __name__ == "__main__":
+    sys.exit(main())
dots/.config/claude/skills/Python/tools/python-lint
@@ -0,0 +1,61 @@
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+#     "ruff>=0.8.0",
+# ]
+# ///
+"""Run ruff linter and formatter on Python code.
+
+This script runs both ruff check and ruff format on the specified directory
+or the current directory if none is provided.
+"""
+
+import subprocess
+import sys
+from pathlib import Path
+
+
+def run_command(cmd: list[str]) -> int:
+    """Run a command and return its exit code.
+
+    Args:
+        cmd: Command and arguments to run
+
+    Returns:
+        Exit code from the command
+    """
+    print(f"Running: {' '.join(cmd)}")
+    result = subprocess.run(cmd, check=False)
+    return result.returncode
+
+
+def main() -> int:
+    """Run linter and formatter.
+
+    Returns:
+        Exit code (0 for success, non-zero for errors)
+    """
+    # Get target directory from args or use current directory
+    target = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(".")
+
+    if not target.exists():
+        print(f"Error: {target} does not exist", file=sys.stderr)
+        return 1
+
+    # Run ruff check
+    check_result = run_command(["ruff", "check", "--fix", str(target)])
+
+    # Run ruff format
+    format_result = run_command(["ruff", "format", str(target)])
+
+    # Return non-zero if either failed
+    if check_result != 0 or format_result != 0:
+        return 1
+
+    print("\n✓ Linting and formatting complete!")
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())
dots/.config/claude/skills/Python/workflows/Deps.md
@@ -0,0 +1,138 @@
+# Dependencies Workflow
+
+Manage Python dependencies with uv.
+
+## When to Use
+
+- "add python dependency"
+- "update dependencies"
+- "manage requirements"
+- "uv lock"
+
+## Quick Commands
+
+```bash
+# Add dependency
+uv add requests
+
+# Add dev dependency
+uv add --dev pytest
+
+# Add with version constraint
+uv add 'requests>=2.31.0,<3.0.0'
+
+# Remove dependency
+uv remove requests
+
+# Update all dependencies
+uv lock --upgrade
+
+# Sync environment with lock file
+uv sync
+
+# Export to requirements.txt
+uv pip compile pyproject.toml -o requirements.txt
+```
+
+## Managing Dependencies
+
+### Add Dependencies
+
+```bash
+# Runtime dependency
+uv add httpx pydantic rich
+
+# Development tools
+uv add --dev pytest ruff mypy pytest-cov
+
+# Optional dependency group
+uv add --optional docs sphinx
+
+# From git
+uv add git+https://github.com/user/repo.git
+
+# From git with branch/tag
+uv add git+https://github.com/user/repo.git@main
+uv add git+https://github.com/user/repo.git@v1.0.0
+
+# From local path (editable)
+uv add --editable ./local-package
+```
+
+### Update Dependencies
+
+```bash
+# Update specific package
+uv lock --upgrade-package requests
+
+# Update all packages
+uv lock --upgrade
+
+# Update and sync
+uv lock --upgrade && uv sync
+```
+
+## Lock File
+
+The `uv.lock` file ensures reproducible builds:
+
+```bash
+# Generate lock file
+uv lock
+
+# Sync from lock file
+uv sync
+
+# Include optional dependencies
+uv sync --extra docs
+
+# Include all optional dependencies
+uv sync --all-extras
+```
+
+**Always commit `uv.lock` to version control.**
+
+## Dependency Groups
+
+### pyproject.toml
+
+```toml
+[project]
+dependencies = [
+    "requests>=2.31.0",
+    "pydantic>=2.0.0",
+]
+
+[project.optional-dependencies]
+dev = [
+    "pytest>=8.0.0",
+    "ruff>=0.8.0",
+    "mypy>=1.13.0",
+]
+docs = [
+    "sphinx>=7.0.0",
+    "sphinx-rtd-theme>=2.0.0",
+]
+```
+
+### Install Groups
+
+```bash
+# Install with dev dependencies
+uv sync --group dev
+
+# Install with docs dependencies
+uv sync --extra docs
+
+# Install all groups
+uv sync --all-extras
+```
+
+## Best Practices
+
+1. **Lock dependencies** - Always maintain `uv.lock`
+2. **Pin versions** - Use version constraints
+3. **Separate dev deps** - Use `--dev` for tools
+4. **Regular updates** - Update dependencies monthly
+5. **Security audit** - Check for vulnerabilities
+6. **Minimal deps** - Only add what you need
dots/.config/claude/skills/Python/workflows/Lint.md
@@ -0,0 +1,271 @@
+# Lint Workflow
+
+Lint and format Python code with ruff - the fast, modern linter and formatter.
+
+## When to Use
+
+- "lint python code"
+- "format python"
+- "ruff check"
+- "ruff format"
+- "fix code style"
+
+## Quick Commands
+
+### Linting
+
+```bash
+# Check for issues
+uv run ruff check .
+
+# Auto-fix issues
+uv run ruff check --fix .
+
+# Show fixes that would be applied (dry run)
+uv run ruff check --fix --diff .
+
+# Check specific file
+uv run ruff check src/myproject/main.py
+
+# Output as JSON
+uv run ruff check --output-format=json .
+```
+
+### Formatting
+
+```bash
+# Format code
+uv run ruff format .
+
+# Check if code would be reformatted (dry run)
+uv run ruff format --check .
+
+# Show diff of what would change
+uv run ruff format --diff .
+
+# Format specific file
+uv run ruff format src/myproject/main.py
+```
+
+### Combined
+
+```bash
+# Lint and format in one go
+uv run ruff check --fix . && uv run ruff format .
+```
+
+## Configuration
+
+### pyproject.toml
+
+```toml
+[tool.ruff]
+# Set line length (default: 88, Black compatible)
+line-length = 100
+
+# Target Python version
+target-version = "py311"
+
+# Source code directories
+src = ["src", "tests"]
+
+# Files to exclude
+extend-exclude = [
+    ".git",
+    "__pycache__",
+    "dist",
+    "build",
+    ".venv",
+]
+
+[tool.ruff.lint]
+# Enable rule sets
+select = [
+    "E",      # pycodestyle errors
+    "W",      # pycodestyle warnings
+    "F",      # Pyflakes
+    "I",      # isort
+    "N",      # pep8-naming
+    "UP",     # pyupgrade
+    "ANN",    # flake8-annotations
+    "ASYNC",  # flake8-async
+    "S",      # flake8-bandit
+    "B",      # flake8-bugbear
+    "A",      # flake8-builtins
+    "C4",     # flake8-comprehensions
+    "DTZ",    # flake8-datetimez
+    "T10",    # flake8-debugger
+    "EM",     # flake8-errmsg
+    "ISC",    # flake8-implicit-str-concat
+    "ICN",    # flake8-import-conventions
+    "G",      # flake8-logging-format
+    "PIE",    # flake8-pie
+    "T20",    # flake8-print
+    "PT",     # flake8-pytest-style
+    "Q",      # flake8-quotes
+    "RSE",    # flake8-raise
+    "RET",    # flake8-return
+    "SIM",    # flake8-simplify
+    "TID",    # flake8-tidy-imports
+    "ARG",    # flake8-unused-arguments
+    "PTH",    # flake8-use-pathlib
+    "PL",     # Pylint
+    "TRY",    # tryceratops
+    "RUF",    # Ruff-specific rules
+]
+
+# Rules to ignore
+ignore = [
+    "ANN101",  # Missing type annotation for self
+    "ANN102",  # Missing type annotation for cls
+    "D203",    # 1 blank line required before class docstring
+    "D213",    # Multi-line docstring summary should start at the second line
+]
+
+# Allow fix for all enabled rules (when `--fix` is used)
+fixable = ["ALL"]
+unfixable = []
+
+# Per-file ignores
+[tool.ruff.lint.per-file-ignores]
+"tests/*" = [
+    "S101",    # Use of assert
+    "ARG",     # Unused arguments
+    "PLR2004", # Magic value used in comparison
+]
+"__init__.py" = [
+    "F401",    # Unused imports
+]
+
+# isort settings
+[tool.ruff.lint.isort]
+known-first-party = ["myproject"]
+force-single-line = false
+lines-after-imports = 2
+
+# flake8-quotes settings
+[tool.ruff.lint.flake8-quotes]
+docstring-quotes = "double"
+inline-quotes = "double"
+
+# pylint settings
+[tool.ruff.lint.pylint]
+max-args = 5
+max-branches = 12
+max-returns = 6
+max-statements = 50
+```
+
+## Common Rules
+
+### Import Organization (I)
+```python
+# Ruff automatically organizes imports
+import os
+import sys
+from pathlib import Path
+
+import requests
+import httpx
+
+from myproject import utils
+from myproject.core import MyClass
+```
+
+### Type Annotations (ANN)
+```python
+# Enforces type hints
+def greet(name: str) -> str:
+    return f"Hello, {name}!"
+
+def process_data(items: list[int]) -> dict[str, int]:
+    return {"count": len(items), "sum": sum(items)}
+```
+
+### Pathlib Usage (PTH)
+```python
+# Bad: Using os.path
+import os
+path = os.path.join("data", "file.txt")
+
+# Good: Using pathlib
+from pathlib import Path
+path = Path("data") / "file.txt"
+```
+
+### Simplifications (SIM)
+```python
+# Bad: Unnecessary comprehension
+squares = [x ** 2 for x in numbers if x ** 2 > 10]
+
+# Good: Direct filtering
+squares = [x ** 2 for x in numbers if x > 3]
+```
+
+## Ignoring Rules
+
+### Inline Ignore
+
+```python
+# Ignore specific rule for one line
+x = eval(user_input)  # noqa: S307
+
+# Ignore multiple rules
+result = func()  # noqa: ARG001, ANN201
+
+# Ignore all rules (use sparingly!)
+dangerous_code()  # noqa
+```
+
+### File-level Ignore
+
+```python
+# At top of file
+# ruff: noqa: ANN
+```
+
+## Integration with Pre-commit
+
+```yaml
+# .pre-commit-config.yaml
+repos:
+  - repo: https://github.com/astral-sh/ruff-pre-commit
+    rev: v0.8.0
+    hooks:
+      # Run linter
+      - id: ruff
+        args: [--fix]
+      # Run formatter
+      - id: ruff-format
+```
+
+## CI/CD Integration
+
+```yaml
+# .github/workflows/lint.yml
+name: Lint
+
+on: [push, pull_request]
+
+jobs:
+  lint:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: astral-sh/setup-uv@v1
+      - name: Lint
+        run: uv run ruff check .
+      - name: Format check
+        run: uv run ruff format --check .
+```
+
+## Best Practices
+
+1. **Run ruff before committing** - Use pre-commit hooks
+2. **Fix automatically** - Use `--fix` flag liberally
+3. **Configure in pyproject.toml** - Keep all config in one place
+4. **Use per-file ignores** - Different rules for tests vs src
+5. **Don't ignore too much** - Only ignore rules when necessary
+6. **Format before linting** - Format first, then lint
+7. **CI enforcement** - Run in CI to catch issues
+8. **Editor integration** - Configure your editor to run ruff on save
dots/.config/claude/skills/Python/workflows/Package.md
@@ -0,0 +1,139 @@
+# Package Workflow
+
+Build and publish Python packages.
+
+## When to Use
+
+- "build python package"
+- "publish to pypi"
+- "create wheel"
+- "distribute package"
+
+## Quick Commands
+
+```bash
+# Build package
+uv build
+
+# Build wheel only
+uv build --wheel
+
+# Build source distribution only
+uv build --sdist
+
+# Publish to PyPI
+uv publish
+
+# Publish to test PyPI
+uv publish --publish-url https://test.pypi.org/legacy/
+```
+
+## Package Configuration
+
+### pyproject.toml
+
+```toml
+[project]
+name = "mypackage"
+version = "0.1.0"
+description = "My awesome package"
+readme = "README.md"
+requires-python = ">=3.11"
+license = {text = "MIT"}
+authors = [
+    {name = "Your Name", email = "you@example.com"}
+]
+keywords = ["example", "package"]
+classifiers = [
+    "Development Status :: 3 - Alpha",
+    "Intended Audience :: Developers",
+    "License :: OSI Approved :: MIT License",
+    "Programming Language :: Python :: 3.11",
+]
+
+dependencies = [
+    "requests>=2.31.0",
+]
+
+[project.urls]
+Homepage = "https://github.com/user/mypackage"
+Repository = "https://github.com/user/mypackage"
+Documentation = "https://mypackage.readthedocs.io"
+Changelog = "https://github.com/user/mypackage/blob/main/CHANGELOG.md"
+
+[project.scripts]
+mypackage = "mypackage.cli:main"
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[tool.hatch.build.targets.wheel]
+packages = ["src/mypackage"]
+```
+
+## Building
+
+```bash
+# Clean previous builds
+rm -rf dist/
+
+# Build distributions
+uv build
+
+# Outputs:
+# dist/mypackage-0.1.0-py3-none-any.whl  (wheel)
+# dist/mypackage-0.1.0.tar.gz            (source)
+```
+
+## Publishing
+
+### Setup PyPI Credentials
+
+```bash
+# Set token (recommended)
+export UV_PUBLISH_TOKEN=pypi-...
+
+# Or use ~/.pypirc
+cat > ~/.pypirc << EOF
+[pypi]
+username = __token__
+password = pypi-...
+EOF
+```
+
+### Publish
+
+```bash
+# Publish to PyPI
+uv publish
+
+# Publish to Test PyPI
+uv publish --publish-url https://test.pypi.org/legacy/
+
+# Dry run
+uv publish --dry-run
+```
+
+## Version Management
+
+Update version in `pyproject.toml`, then:
+
+```bash
+# Tag release
+git tag v0.1.0
+git push --tags
+
+# Build and publish
+uv build
+uv publish
+```
+
+## Best Practices
+
+1. **Use src layout** - `src/package/`
+2. **Semantic versioning** - MAJOR.MINOR.PATCH
+3. **Include README** - And CHANGELOG
+4. **Test on Test PyPI** - Before real PyPI
+5. **Use build backend** - hatchling, setuptools, etc.
+6. **Add classifiers** - Help users find your package
dots/.config/claude/skills/Python/workflows/Project.md
@@ -0,0 +1,485 @@
+# Project Workflow
+
+Create and manage Python projects with uv, following modern best practices.
+
+## When to Use
+
+- "create python project"
+- "new python project"
+- "initialize python app"
+- "uv init"
+- "setup python package"
+
+## Quick Start
+
+### Create New Project
+
+```bash
+# Create new project with default structure
+uv init myproject
+
+# Create library project
+uv init --lib myproject
+
+# Create app project (default)
+uv init --app myproject
+
+# Create in existing directory
+cd myproject
+uv init
+```
+
+### Project Structure Created
+
+```
+myproject/
+├── .python-version          # Python version (e.g., 3.11)
+├── README.md
+├── pyproject.toml          # Project configuration
+├── hello.py                # Entry point (for apps)
+└── src/
+    └── myproject/
+        └── __init__.py     # For libraries
+```
+
+## Configure Project
+
+### Edit pyproject.toml
+
+```toml
+[project]
+name = "myproject"
+version = "0.1.0"
+description = "Description of my awesome project"
+readme = "README.md"
+requires-python = ">=3.11"
+authors = [
+    { name = "Your Name", email = "your.email@example.com" }
+]
+license = { text = "MIT" }
+classifiers = [
+    "Development Status :: 3 - Alpha",
+    "Intended Audience :: Developers",
+    "Programming Language :: Python :: 3.11",
+]
+
+dependencies = [
+    # Runtime dependencies
+]
+
+[project.optional-dependencies]
+dev = [
+    "pytest>=8.0.0",
+    "pytest-cov>=6.0.0",
+    "ruff>=0.8.0",
+    "mypy>=1.13.0",
+]
+
+[project.scripts]
+myproject = "myproject.main:main"
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+# Ruff configuration
+[tool.ruff]
+line-length = 100
+target-version = "py311"
+src = ["src", "tests"]
+
+[tool.ruff.lint]
+select = [
+    "E",   # pycodestyle errors
+    "W",   # pycodestyle warnings
+    "F",   # pyflakes
+    "I",   # isort
+    "N",   # pep8-naming
+    "B",   # flake8-bugbear
+    "Q",   # flake8-quotes
+    "UP",  # pyupgrade
+    "ANN", # flake8-annotations
+    "S",   # flake8-bandit
+    "C4",  # flake8-comprehensions
+]
+ignore = [
+    "ANN101",  # Missing type annotation for self
+    "ANN102",  # Missing type annotation for cls
+]
+
+[tool.ruff.lint.per-file-ignores]
+"tests/*" = ["S101"]  # Allow assert in tests
+
+# mypy configuration
+[tool.mypy]
+python_version = "3.11"
+strict = true
+warn_return_any = true
+warn_unused_configs = true
+disallow_untyped_defs = true
+disallow_any_generics = true
+check_untyped_defs = true
+no_implicit_optional = true
+warn_redundant_casts = true
+warn_unused_ignores = true
+
+# pytest configuration
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+python_files = "test_*.py"
+python_functions = "test_*"
+addopts = [
+    "--strict-markers",
+    "--strict-config",
+    "--cov=src",
+    "--cov-report=term-missing",
+    "--cov-report=html",
+]
+
+# Coverage configuration
+[tool.coverage.run]
+source = ["src"]
+omit = ["tests/*", "**/__pycache__/*"]
+
+[tool.coverage.report]
+precision = 2
+show_missing = true
+skip_covered = false
+exclude_lines = [
+    "pragma: no cover",
+    "def __repr__",
+    "raise AssertionError",
+    "raise NotImplementedError",
+    "if __name__ == .__main__.:",
+    "if TYPE_CHECKING:",
+]
+```
+
+## Add Dependencies
+
+### Runtime Dependencies
+
+```bash
+# Add single package
+uv add requests
+
+# Add multiple packages
+uv add requests httpx rich
+
+# Add with version constraint
+uv add 'requests>=2.31.0,<3.0.0'
+
+# Add from git
+uv add git+https://github.com/user/repo.git
+
+# Add from local path
+uv add --editable ./local-package
+```
+
+### Development Dependencies
+
+```bash
+# Add dev dependencies
+uv add --dev pytest pytest-cov ruff mypy
+
+# Add testing tools
+uv add --dev pytest pytest-asyncio pytest-mock
+
+# Add type stubs
+uv add --dev types-requests types-pyyaml
+```
+
+### Optional Dependencies
+
+```bash
+# Add optional dependency group
+uv add --optional docs sphinx sphinx-rtd-theme
+
+# Install with optional group
+uv sync --extra docs
+```
+
+## Project Setup Workflow
+
+### 1. Initialize Project
+
+```bash
+uv init myproject
+cd myproject
+```
+
+### 2. Set Python Version
+
+```bash
+# Use specific Python version
+echo "3.11" > .python-version
+
+# Or let uv detect/install
+uv python pin 3.11
+```
+
+### 3. Add Dependencies
+
+```bash
+# Add core dependencies
+uv add requests pydantic
+
+# Add development tools
+uv add --dev pytest ruff mypy pytest-cov
+```
+
+### 4. Create Project Structure
+
+```bash
+# For applications
+mkdir -p src/myproject tests docs
+touch src/myproject/{__init__.py,main.py}
+touch tests/{__init__.py,test_main.py}
+
+# For libraries
+mkdir -p src/myproject tests docs examples
+touch src/myproject/{__init__.py,core.py}
+touch tests/{__init__.py,test_core.py}
+touch examples/basic_usage.py
+```
+
+### 5. Initialize Git
+
+```bash
+git init
+cat > .gitignore << 'EOF'
+# Python
+__pycache__/
+*.py[cod]
+*$py.class
+*.so
+.Python
+env/
+venv/
+.venv/
+ENV/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# Testing
+.pytest_cache/
+.coverage
+htmlcov/
+.tox/
+
+# Type checking
+.mypy_cache/
+.dmypy.json
+
+# IDEs
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# uv
+uv.lock
+EOF
+
+git add .
+git commit -m "Initial commit"
+```
+
+### 6. Setup Pre-commit (Optional)
+
+```bash
+# Add pre-commit
+uv add --dev pre-commit
+
+# Create .pre-commit-config.yaml
+cat > .pre-commit-config.yaml << 'EOF'
+repos:
+  - repo: https://github.com/astral-sh/ruff-pre-commit
+    rev: v0.8.0
+    hooks:
+      - id: ruff
+        args: [--fix]
+      - id: ruff-format
+
+  - repo: https://github.com/pre-commit/mirrors-mypy
+    rev: v1.13.0
+    hooks:
+      - id: mypy
+        additional_dependencies: []
+EOF
+
+# Install hooks
+uv run pre-commit install
+```
+
+## Project Templates
+
+### CLI Application
+
+```python
+# src/myproject/main.py
+"""Main entry point for myproject CLI."""
+import argparse
+import sys
+from typing import Sequence
+
+def main(argv: Sequence[str] | None = None) -> int:
+    """Run the CLI application.
+
+    Args:
+        argv: Command line arguments (defaults to sys.argv)
+
+    Returns:
+        Exit code (0 for success, non-zero for errors)
+    """
+    parser = argparse.ArgumentParser(description="My awesome CLI")
+    parser.add_argument("--version", action="version", version="0.1.0")
+    parser.add_argument("input", help="Input file path")
+    parser.add_argument("-o", "--output", help="Output file path")
+
+    args = parser.parse_args(argv)
+
+    try:
+        # Your logic here
+        print(f"Processing {args.input}")
+        return 0
+    except Exception as e:
+        print(f"Error: {e}", file=sys.stderr)
+        return 1
+
+if __name__ == "__main__":
+    sys.exit(main())
+```
+
+### Library Package
+
+```python
+# src/myproject/__init__.py
+"""MyProject - A Python library for awesome things."""
+from myproject.core import MyClass, my_function
+
+__version__ = "0.1.0"
+__all__ = ["MyClass", "my_function"]
+
+# src/myproject/core.py
+"""Core functionality for myproject."""
+from typing import Any
+
+class MyClass:
+    """Main class for myproject."""
+
+    def __init__(self, name: str) -> None:
+        """Initialize MyClass.
+
+        Args:
+            name: Name of the instance
+        """
+        self.name = name
+
+    def process(self, data: dict[str, Any]) -> dict[str, Any]:
+        """Process data.
+
+        Args:
+            data: Input data dictionary
+
+        Returns:
+            Processed data dictionary
+        """
+        return {"name": self.name, **data}
+
+def my_function(value: int) -> int:
+    """Perform calculation on value.
+
+    Args:
+        value: Input integer
+
+    Returns:
+        Calculated result
+    """
+    return value * 2
+```
+
+## Run Project
+
+### Development
+
+```bash
+# Run module
+uv run python -m myproject
+
+# Run script entry point
+uv run myproject
+
+# Run tests
+uv run pytest
+
+# Run with coverage
+uv run pytest --cov
+
+# Lint and format
+uv run ruff check .
+uv run ruff format .
+
+# Type check
+uv run mypy .
+```
+
+### Build and Install
+
+```bash
+# Build distribution
+uv build
+
+# Install locally
+uv pip install -e .
+
+# Install from built wheel
+uv pip install dist/*.whl
+```
+
+## Common Project Commands
+
+```bash
+# Show project info
+uv tree                    # Show dependency tree
+cat pyproject.toml         # View configuration
+
+# Update dependencies
+uv lock --upgrade          # Update lock file
+uv sync                    # Sync environment
+
+# Clean project
+rm -rf .venv               # Remove virtual environment
+rm -rf dist build *.egg-info  # Remove build artifacts
+rm -rf .pytest_cache .mypy_cache __pycache__  # Remove caches
+
+# Rebuild environment
+rm -rf .venv && uv sync
+```
+
+## Best Practices
+
+1. **Always use pyproject.toml** - Single source of configuration
+2. **Pin Python version** - Use `.python-version` file
+3. **Lock dependencies** - Commit `uv.lock` to git
+4. **Separate dev deps** - Use `--dev` for development tools
+5. **Configure tools** - Add ruff, mypy, pytest config to pyproject.toml
+6. **Use src layout** - Place code in `src/package/` for better testing
+7. **Add type hints** - Enable mypy strict mode
+8. **Write tests** - Aim for >80% coverage
+9. **Use entry points** - Define CLI commands in `[project.scripts]`
+10. **Document** - Add docstrings and README
dots/.config/claude/skills/Python/workflows/Script.md
@@ -0,0 +1,621 @@
+# Script Workflow
+
+Create and run single-file Python scripts with inline dependencies using PEP 723 and uv.
+
+## When to Use
+
+- "create python script"
+- "single file script"
+- "uv run"
+- "PEP 723"
+- "standalone script"
+
+## Quick Start
+
+### Basic Script with Dependencies
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+#     "requests>=2.31.0",
+#     "rich>=13.0.0",
+# ]
+# ///
+"""Fetch and display data from an API."""
+
+import requests
+from rich.console import Console
+from rich.table import Table
+
+def main() -> None:
+    """Fetch data and display in a table."""
+    console = Console()
+
+    with console.status("[bold green]Fetching data..."):
+        response = requests.get("https://api.github.com/users/github")
+        response.raise_for_status()
+        data = response.json()
+
+    table = Table(title="GitHub User Info")
+    table.add_column("Field", style="cyan")
+    table.add_column("Value", style="magenta")
+
+    table.add_row("Name", data.get("name", "N/A"))
+    table.add_row("Location", data.get("location", "N/A"))
+    table.add_row("Public Repos", str(data.get("public_repos", 0)))
+
+    console.print(table)
+
+if __name__ == "__main__":
+    main()
+```
+
+### Run the Script
+
+```bash
+# Make executable
+chmod +x script.py
+
+# Run directly (uv handles dependencies automatically)
+./script.py
+
+# Or run with uv explicitly
+uv run script.py
+```
+
+## PEP 723 Inline Metadata
+
+### Metadata Block Format
+
+The metadata must be:
+- In a comment block starting with `# /// script`
+- Followed by TOML-formatted metadata
+- Closed with `# ///`
+- Placed before any imports
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+#     "requests>=2.31.0",
+#     "rich>=13.0.0",
+# ]
+# ///
+```
+
+### Shebang Line
+
+**Required format:**
+```python
+#!/usr/bin/env -S uv run --script
+```
+
+**Important:** Must use `--script` flag with `uv run` in shebang.
+
+### Dependencies Syntax
+
+```python
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+#     # Simple dependency
+#     "requests",
+#
+#     # With version constraint
+#     "rich>=13.0.0",
+#
+#     # Version range
+#     "httpx>=0.24.0,<1.0.0",
+#
+#     # Git dependency
+#     "mypackage @ git+https://github.com/user/repo.git",
+#
+#     # Git with tag/branch
+#     "mypackage @ git+https://github.com/user/repo.git@v1.0.0",
+# ]
+# ///
+```
+
+## Script Templates
+
+### Data Processing Script
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+#     "pandas>=2.0.0",
+#     "rich>=13.0.0",
+# ]
+# ///
+"""Process CSV data and generate report."""
+
+import sys
+from pathlib import Path
+from typing import NoReturn
+
+import pandas as pd
+from rich.console import Console
+from rich.table import Table
+
+console = Console()
+
+def process_csv(input_path: Path, output_path: Path) -> None:
+    """Process CSV file and generate summary.
+
+    Args:
+        input_path: Path to input CSV file
+        output_path: Path to output summary file
+
+    Raises:
+        FileNotFoundError: If input file doesn't exist
+        ValueError: If CSV is invalid or empty
+    """
+    if not input_path.exists():
+        raise FileNotFoundError(f"Input file not found: {input_path}")
+
+    # Read and process
+    df = pd.read_csv(input_path)
+
+    if df.empty:
+        raise ValueError("CSV file is empty")
+
+    # Generate summary
+    summary = df.describe()
+
+    # Save output
+    summary.to_csv(output_path)
+
+    # Display
+    table = Table(title=f"Summary: {input_path.name}")
+    for col in summary.columns:
+        table.add_column(col)
+
+    for idx, row in summary.iterrows():
+        table.add_row(str(idx), *[f"{val:.2f}" for val in row])
+
+    console.print(table)
+    console.print(f"\n[green]Summary saved to:[/green] {output_path}")
+
+def main() -> int:
+    """Run the data processing script.
+
+    Returns:
+        Exit code (0 for success, 1 for error)
+    """
+    if len(sys.argv) != 3:
+        console.print("[red]Usage:[/red] script.py <input.csv> <output.csv>")
+        return 1
+
+    input_path = Path(sys.argv[1])
+    output_path = Path(sys.argv[2])
+
+    try:
+        process_csv(input_path, output_path)
+        return 0
+    except Exception as e:
+        console.print(f"[red]Error:[/red] {e}")
+        return 1
+
+if __name__ == "__main__":
+    sys.exit(main())
+```
+
+### API Client Script
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+#     "httpx>=0.24.0",
+#     "pydantic>=2.0.0",
+#     "rich>=13.0.0",
+# ]
+# ///
+"""Fetch and validate data from REST API."""
+
+import sys
+from typing import Any
+
+import httpx
+from pydantic import BaseModel, Field, ValidationError
+from rich.console import Console
+from rich.pretty import pprint
+
+console = Console()
+
+class User(BaseModel):
+    """User data model."""
+
+    id: int
+    name: str
+    email: str
+    company: dict[str, Any] = Field(default_factory=dict)
+
+    class Config:
+        """Pydantic configuration."""
+
+        frozen = True
+
+async def fetch_user(user_id: int) -> User:
+    """Fetch user from API.
+
+    Args:
+        user_id: ID of user to fetch
+
+    Returns:
+        User object with validated data
+
+    Raises:
+        httpx.HTTPError: If request fails
+        ValidationError: If response data is invalid
+    """
+    async with httpx.AsyncClient() as client:
+        response = await client.get(
+            f"https://jsonplaceholder.typicode.com/users/{user_id}"
+        )
+        response.raise_for_status()
+        data = response.json()
+
+    return User(**data)
+
+async def main() -> int:
+    """Run the API client.
+
+    Returns:
+        Exit code (0 for success, 1 for error)
+    """
+    if len(sys.argv) != 2:
+        console.print("[red]Usage:[/red] script.py <user_id>")
+        return 1
+
+    try:
+        user_id = int(sys.argv[1])
+    except ValueError:
+        console.print("[red]Error:[/red] user_id must be an integer")
+        return 1
+
+    try:
+        with console.status(f"[bold green]Fetching user {user_id}..."):
+            user = await fetch_user(user_id)
+
+        console.print("\n[bold green]User data:[/bold green]")
+        pprint(user.model_dump())
+        return 0
+
+    except httpx.HTTPError as e:
+        console.print(f"[red]HTTP Error:[/red] {e}")
+        return 1
+    except ValidationError as e:
+        console.print(f"[red]Validation Error:[/red] {e}")
+        return 1
+    except Exception as e:
+        console.print(f"[red]Error:[/red] {e}")
+        return 1
+
+if __name__ == "__main__":
+    import asyncio
+    sys.exit(asyncio.run(main()))
+```
+
+### CLI Tool Script
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+#     "click>=8.0.0",
+#     "rich>=13.0.0",
+# ]
+# ///
+"""Example CLI tool with multiple commands."""
+
+import click
+from rich.console import Console
+
+console = Console()
+
+@click.group()
+@click.version_option(version="1.0.0")
+def cli() -> None:
+    """My awesome CLI tool."""
+
+@cli.command()
+@click.argument("name")
+@click.option("--greeting", default="Hello", help="Greeting to use")
+def greet(name: str, greeting: str) -> None:
+    """Greet someone by name.
+
+    Args:
+        name: Name to greet
+        greeting: Greeting message
+    """
+    console.print(f"[bold green]{greeting}, {name}![/bold green]")
+
+@cli.command()
+@click.argument("numbers", nargs=-1, type=int)
+@click.option("--operation", type=click.Choice(["sum", "product"]), default="sum")
+def calculate(numbers: tuple[int, ...], operation: str) -> None:
+    """Perform calculation on numbers.
+
+    Args:
+        numbers: Numbers to calculate
+        operation: Operation to perform (sum or product)
+    """
+    if not numbers:
+        console.print("[red]Error:[/red] No numbers provided")
+        return
+
+    if operation == "sum":
+        result = sum(numbers)
+        console.print(f"Sum: [bold]{result}[/bold]")
+    else:
+        result = 1
+        for num in numbers:
+            result *= num
+        console.print(f"Product: [bold]{result}[/bold]")
+
+if __name__ == "__main__":
+    cli()
+```
+
+### File Processing Script
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+#     "pathlib",
+# ]
+# ///
+"""Process files in a directory."""
+
+import sys
+from pathlib import Path
+from typing import Iterator
+
+def find_python_files(directory: Path) -> Iterator[Path]:
+    """Find all Python files in directory.
+
+    Args:
+        directory: Directory to search
+
+    Yields:
+        Path objects for Python files
+    """
+    for path in directory.rglob("*.py"):
+        if path.is_file() and not path.name.startswith("."):
+            yield path
+
+def count_lines(file_path: Path) -> int:
+    """Count lines in file.
+
+    Args:
+        file_path: Path to file
+
+    Returns:
+        Number of lines in file
+    """
+    return len(file_path.read_text().splitlines())
+
+def main() -> int:
+    """Process Python files and count lines.
+
+    Returns:
+        Exit code (0 for success, 1 for error)
+    """
+    if len(sys.argv) != 2:
+        print("Usage: script.py <directory>")
+        return 1
+
+    directory = Path(sys.argv[1])
+
+    if not directory.is_dir():
+        print(f"Error: {directory} is not a directory")
+        return 1
+
+    total_lines = 0
+    file_count = 0
+
+    for file_path in find_python_files(directory):
+        lines = count_lines(file_path)
+        total_lines += lines
+        file_count += 1
+        print(f"{file_path.relative_to(directory)}: {lines} lines")
+
+    print(f"\nTotal: {file_count} files, {total_lines} lines")
+    return 0
+
+if __name__ == "__main__":
+    sys.exit(main())
+```
+
+## Managing Script Dependencies
+
+### Add Dependencies to Existing Script
+
+```bash
+# Add dependency to script
+uv add --script myscript.py requests
+
+# Add multiple dependencies
+uv add --script myscript.py requests rich httpx
+```
+
+This updates the inline metadata in the script automatically.
+
+### Remove Dependencies
+
+Manually edit the script's metadata block to remove dependencies.
+
+## Testing Scripts
+
+### Inline Tests
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+#     "pytest>=8.0.0",
+# ]
+# ///
+"""Script with inline tests."""
+
+def add(a: int, b: int) -> int:
+    """Add two numbers."""
+    return a + b
+
+def test_add() -> None:
+    """Test add function."""
+    assert add(2, 3) == 5
+    assert add(0, 0) == 0
+    assert add(-1, 1) == 0
+
+if __name__ == "__main__":
+    # Run main logic
+    result = add(10, 20)
+    print(f"Result: {result}")
+
+    # To run tests: pytest script.py
+```
+
+```bash
+# Run the script normally
+./script.py
+
+# Run tests
+uv run pytest script.py
+```
+
+## Script Organization
+
+### Multiple Related Scripts
+
+For related scripts, consider this structure:
+
+```
+scripts/
+├── common.py              # Shared utilities (no shebang)
+├── fetch_data.py          # Script 1 (with shebang + metadata)
+├── process_data.py        # Script 2 (with shebang + metadata)
+└── generate_report.py     # Script 3 (with shebang + metadata)
+```
+
+**Shared utilities (common.py):**
+```python
+"""Shared utilities for scripts."""
+from pathlib import Path
+
+def ensure_dir(path: Path) -> None:
+    """Ensure directory exists."""
+    path.mkdir(parents=True, exist_ok=True)
+```
+
+**Script using shared code:**
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = []
+# ///
+"""Script using shared utilities."""
+
+import sys
+from pathlib import Path
+
+# Import from adjacent file
+sys.path.insert(0, str(Path(__file__).parent))
+from common import ensure_dir
+
+def main() -> int:
+    ensure_dir(Path("output"))
+    return 0
+
+if __name__ == "__main__":
+    sys.exit(main())
+```
+
+## Best Practices
+
+1. **Always use shebang** - `#!/usr/bin/env -S uv run --script`
+2. **Always use metadata block** - Even if no dependencies
+3. **Pin Python version** - Use `requires-python`
+4. **Use version constraints** - Pin dependency versions
+5. **Add docstrings** - Document what the script does
+6. **Add type hints** - Make code more maintainable
+7. **Handle errors** - Use try/except and return error codes
+8. **Make executable** - `chmod +x script.py`
+9. **Use pathlib** - Use `Path` objects instead of strings
+10. **Return exit codes** - Return 0 for success, non-zero for errors
+
+## Common Patterns
+
+### Read from stdin, write to stdout
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = []
+# ///
+"""Process data from stdin to stdout."""
+
+import sys
+
+def main() -> int:
+    """Read from stdin, process, write to stdout."""
+    for line in sys.stdin:
+        processed = line.strip().upper()
+        print(processed)
+    return 0
+
+if __name__ == "__main__":
+    sys.exit(main())
+```
+
+```bash
+# Usage
+echo "hello world" | ./script.py
+cat input.txt | ./script.py > output.txt
+```
+
+### Configuration from environment
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = []
+# ///
+"""Script using environment variables."""
+
+import os
+import sys
+
+def main() -> int:
+    """Run script with config from environment."""
+    api_key = os.getenv("API_KEY")
+    if not api_key:
+        print("Error: API_KEY environment variable not set", file=sys.stderr)
+        return 1
+
+    # Use api_key...
+    print(f"Using API key: {api_key[:4]}...")
+    return 0
+
+if __name__ == "__main__":
+    sys.exit(main())
+```
+
+```bash
+# Usage
+API_KEY=secret ./script.py
+```
dots/.config/claude/skills/Python/workflows/Test.md
@@ -0,0 +1,567 @@
+# Test Workflow
+
+Run Python tests with pytest, coverage, and various testing patterns.
+
+## When to Use
+
+- "run python tests"
+- "pytest"
+- "test coverage"
+- "run specific test"
+- "test with fixtures"
+
+## Quick Commands
+
+### Basic Testing
+
+```bash
+# Run all tests
+uv run pytest
+
+# Verbose output
+uv run pytest -v
+
+# Very verbose (show test names as they run)
+uv run pytest -vv
+
+# Run specific test file
+uv run pytest tests/test_auth.py
+
+# Run specific test function
+uv run pytest tests/test_auth.py::test_login
+
+# Run specific test class
+uv run pytest tests/test_auth.py::TestAuth
+
+# Run specific test method
+uv run pytest tests/test_auth.py::TestAuth::test_login
+
+# Run tests matching pattern
+uv run pytest -k "auth"
+uv run pytest -k "test_login or test_logout"
+```
+
+### Test Coverage
+
+```bash
+# Run tests with coverage
+uv run pytest --cov
+
+# Coverage for specific module
+uv run pytest --cov=src/myproject
+
+# Show missing lines
+uv run pytest --cov --cov-report=term-missing
+
+# Generate HTML coverage report
+uv run pytest --cov --cov-report=html
+# Open htmlcov/index.html in browser
+
+# Generate XML coverage (for CI)
+uv run pytest --cov --cov-report=xml
+
+# Fail if coverage below threshold
+uv run pytest --cov --cov-fail-under=80
+```
+
+### Test Output Control
+
+```bash
+# Show print statements
+uv run pytest -s
+
+# Stop at first failure
+uv run pytest -x
+
+# Stop after N failures
+uv run pytest --maxfail=3
+
+# Show local variables in traceback
+uv run pytest -l
+
+# Show summary of all test outcomes
+uv run pytest -ra
+```
+
+### Test Selection
+
+```bash
+# Run only failed tests from last run
+uv run pytest --lf
+
+# Run failed tests first, then others
+uv run pytest --ff
+
+# Run tests in random order (requires pytest-random-order)
+uv run pytest --random-order
+```
+
+## Test Structure
+
+### Basic Test File
+
+```python
+# tests/test_calculator.py
+"""Tests for calculator module."""
+import pytest
+from myproject.calculator import Calculator
+
+class TestCalculator:
+    """Test suite for Calculator class."""
+
+    def test_add(self) -> None:
+        """Test addition operation."""
+        calc = Calculator()
+        result = calc.add(2, 3)
+        assert result == 5
+
+    def test_subtract(self) -> None:
+        """Test subtraction operation."""
+        calc = Calculator()
+        result = calc.subtract(5, 3)
+        assert result == 2
+
+    def test_multiply(self) -> None:
+        """Test multiplication operation."""
+        calc = Calculator()
+        result = calc.multiply(2, 3)
+        assert result == 6
+
+    def test_divide(self) -> None:
+        """Test division operation."""
+        calc = Calculator()
+        result = calc.divide(6, 3)
+        assert result == 2.0
+
+    def test_divide_by_zero(self) -> None:
+        """Test division by zero raises error."""
+        calc = Calculator()
+        with pytest.raises(ValueError, match="Cannot divide by zero"):
+            calc.divide(10, 0)
+```
+
+### Using Fixtures
+
+```python
+# tests/test_database.py
+"""Tests for database operations."""
+import pytest
+from myproject.database import Database
+
+@pytest.fixture
+def db() -> Database:
+    """Provide a test database instance."""
+    database = Database(":memory:")  # SQLite in-memory
+    database.create_tables()
+    yield database
+    database.close()
+
+@pytest.fixture
+def sample_data() -> dict[str, str]:
+    """Provide sample test data."""
+    return {
+        "name": "John Doe",
+        "email": "john@example.com",
+    }
+
+def test_insert_user(db: Database, sample_data: dict[str, str]) -> None:
+    """Test inserting a user into database."""
+    user_id = db.insert_user(sample_data)
+    assert user_id > 0
+
+def test_get_user(db: Database, sample_data: dict[str, str]) -> None:
+    """Test retrieving a user from database."""
+    user_id = db.insert_user(sample_data)
+    user = db.get_user(user_id)
+    assert user["name"] == sample_data["name"]
+    assert user["email"] == sample_data["email"]
+```
+
+### Parametrized Tests
+
+```python
+# tests/test_validation.py
+"""Tests for validation functions."""
+import pytest
+from myproject.validation import validate_email, validate_phone
+
+@pytest.mark.parametrize("email,expected", [
+    ("user@example.com", True),
+    ("user.name@example.co.uk", True),
+    ("user+tag@example.com", True),
+    ("invalid.email", False),
+    ("@example.com", False),
+    ("user@", False),
+    ("", False),
+])
+def test_validate_email(email: str, expected: bool) -> None:
+    """Test email validation with various inputs."""
+    assert validate_email(email) == expected
+
+@pytest.mark.parametrize("phone,expected", [
+    ("+1-555-123-4567", True),
+    ("555-123-4567", True),
+    ("5551234567", True),
+    ("invalid", False),
+    ("", False),
+])
+def test_validate_phone(phone: str, expected: bool) -> None:
+    """Test phone validation with various inputs."""
+    assert validate_phone(phone) == expected
+
+# Test with multiple parameters
+@pytest.mark.parametrize("a,b,expected", [
+    (2, 3, 5),
+    (0, 0, 0),
+    (-1, 1, 0),
+    (10, -5, 5),
+    (100, 200, 300),
+])
+def test_addition(a: int, b: int, expected: int) -> None:
+    """Test addition with multiple input combinations."""
+    assert a + b == expected
+```
+
+### Fixture Scopes
+
+```python
+# tests/conftest.py
+"""Shared fixtures for all tests."""
+import pytest
+from myproject.app import create_app
+from myproject.database import Database
+
+@pytest.fixture(scope="session")
+def app():
+    """Provide application instance for entire test session."""
+    app = create_app({"TESTING": True})
+    yield app
+
+@pytest.fixture(scope="module")
+def db():
+    """Provide database for all tests in a module."""
+    database = Database(":memory:")
+    database.create_tables()
+    yield database
+    database.close()
+
+@pytest.fixture(scope="function")
+def clean_db(db: Database):
+    """Provide clean database for each test function."""
+    yield db
+    db.clear_all_tables()  # Clean up after each test
+
+@pytest.fixture
+def client(app):
+    """Provide test client for making requests."""
+    return app.test_client()
+```
+
+## Advanced Testing Patterns
+
+### Testing Exceptions
+
+```python
+def test_exception_raised() -> None:
+    """Test that exception is raised."""
+    with pytest.raises(ValueError):
+        my_function(-1)
+
+def test_exception_with_message() -> None:
+    """Test exception message matches pattern."""
+    with pytest.raises(ValueError, match="must be positive"):
+        my_function(-1)
+
+def test_exception_attributes() -> None:
+    """Test exception has expected attributes."""
+    with pytest.raises(ValueError) as exc_info:
+        my_function(-1)
+
+    assert exc_info.value.args[0] == "Value must be positive"
+    assert exc_info.type is ValueError
+```
+
+### Testing Async Code
+
+```python
+import pytest
+
+@pytest.mark.asyncio
+async def test_async_function() -> None:
+    """Test async function."""
+    result = await fetch_data()
+    assert result is not None
+
+@pytest.mark.asyncio
+async def test_async_with_fixture(async_client) -> None:
+    """Test async code with fixture."""
+    response = await async_client.get("/api/data")
+    assert response.status_code == 200
+```
+
+### Mocking and Patching
+
+```python
+from unittest.mock import Mock, patch, MagicMock
+import pytest
+
+def test_with_mock() -> None:
+    """Test using mock objects."""
+    # Create a mock
+    mock_db = Mock()
+    mock_db.get_user.return_value = {"id": 1, "name": "John"}
+
+    # Use the mock
+    user = mock_db.get_user(1)
+    assert user["name"] == "John"
+
+    # Verify mock was called
+    mock_db.get_user.assert_called_once_with(1)
+
+@patch("myproject.api.requests.get")
+def test_with_patch(mock_get) -> None:
+    """Test using patch decorator."""
+    # Configure mock return value
+    mock_response = Mock()
+    mock_response.json.return_value = {"status": "ok"}
+    mock_response.status_code = 200
+    mock_get.return_value = mock_response
+
+    # Call function that uses requests.get
+    result = fetch_api_data()
+
+    # Verify
+    assert result["status"] == "ok"
+    mock_get.assert_called_once()
+
+def test_with_pytest_mock(mocker) -> None:
+    """Test using pytest-mock plugin."""
+    # Mock a method
+    mock = mocker.patch("myproject.api.requests.get")
+    mock.return_value.json.return_value = {"data": "test"}
+
+    result = fetch_api_data()
+    assert result["data"] == "test"
+```
+
+### Testing with Temporary Files
+
+```python
+import pytest
+from pathlib import Path
+
+def test_with_tmp_path(tmp_path: Path) -> None:
+    """Test using temporary directory.
+
+    tmp_path is a pytest fixture that provides a temporary directory
+    unique to the test invocation.
+    """
+    # Create a file in temp directory
+    test_file = tmp_path / "test.txt"
+    test_file.write_text("test content")
+
+    # Use the file
+    result = process_file(test_file)
+
+    assert result is not None
+    assert test_file.exists()
+
+def test_with_tmp_path_factory(tmp_path_factory) -> None:
+    """Test using temporary directory factory.
+
+    Useful for session-scoped fixtures.
+    """
+    temp_dir = tmp_path_factory.mktemp("data")
+    test_file = temp_dir / "test.txt"
+    test_file.write_text("content")
+
+    assert test_file.read_text() == "content"
+```
+
+## Test Configuration
+
+### pyproject.toml
+
+```toml
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+python_files = "test_*.py"
+python_classes = "Test*"
+python_functions = "test_*"
+
+# Add options
+addopts = [
+    "--strict-markers",     # Fail on unknown markers
+    "--strict-config",      # Fail on config errors
+    "--verbose",            # Verbose output
+    "--cov=src",           # Coverage for src directory
+    "--cov-report=term-missing",  # Show missing lines
+    "--cov-report=html",   # Generate HTML report
+    "--cov-fail-under=80", # Fail if coverage < 80%
+]
+
+# Markers for categorizing tests
+markers = [
+    "slow: marks tests as slow (deselect with '-m \"not slow\"')",
+    "integration: marks tests as integration tests",
+    "unit: marks tests as unit tests",
+]
+
+# Filter warnings
+filterwarnings = [
+    "error",  # Treat warnings as errors
+    "ignore::DeprecationWarning",  # Ignore deprecation warnings
+]
+```
+
+### Coverage Configuration
+
+```toml
+[tool.coverage.run]
+source = ["src"]
+omit = [
+    "tests/*",
+    "**/__pycache__/*",
+    "**/site-packages/*",
+]
+branch = true  # Measure branch coverage
+
+[tool.coverage.report]
+precision = 2
+show_missing = true
+skip_covered = false
+exclude_lines = [
+    "pragma: no cover",
+    "def __repr__",
+    "raise AssertionError",
+    "raise NotImplementedError",
+    "if __name__ == .__main__.:",
+    "if TYPE_CHECKING:",
+    "@abstractmethod",
+]
+
+[tool.coverage.html]
+directory = "htmlcov"
+```
+
+## Test Markers
+
+### Using Markers
+
+```python
+import pytest
+
+@pytest.mark.slow
+def test_slow_operation() -> None:
+    """Test that takes a long time to run."""
+    # ... slow test ...
+
+@pytest.mark.integration
+def test_api_integration() -> None:
+    """Integration test with external API."""
+    # ... integration test ...
+
+@pytest.mark.unit
+def test_unit_calculation() -> None:
+    """Fast unit test."""
+    # ... unit test ...
+
+@pytest.mark.skip(reason="Not implemented yet")
+def test_future_feature() -> None:
+    """Test for future feature."""
+
+@pytest.mark.skipif(sys.platform == "win32", reason="Unix only")
+def test_unix_specific() -> None:
+    """Test that only runs on Unix."""
+
+@pytest.mark.xfail(reason="Known bug #123")
+def test_known_failure() -> None:
+    """Test expected to fail due to known bug."""
+```
+
+### Running Tests by Marker
+
+```bash
+# Run only unit tests
+uv run pytest -m unit
+
+# Run everything except slow tests
+uv run pytest -m "not slow"
+
+# Run integration tests only
+uv run pytest -m integration
+
+# Combine markers
+uv run pytest -m "unit and not slow"
+```
+
+## Continuous Testing
+
+### Watch Mode (with pytest-watch)
+
+```bash
+# Install pytest-watch
+uv add --dev pytest-watch
+
+# Watch for changes and re-run tests
+uv run ptw
+
+# Watch specific directory
+uv run ptw tests/
+
+# Watch with coverage
+uv run ptw -- --cov
+```
+
+### Pre-commit Hook
+
+```yaml
+# .pre-commit-config.yaml
+repos:
+  - repo: local
+    hooks:
+      - id: pytest
+        name: pytest
+        entry: uv run pytest
+        language: system
+        pass_filenames: false
+        always_run: true
+```
+
+## Common Test Commands
+
+```bash
+# Run tests with coverage and generate HTML report
+uv run pytest --cov --cov-report=html
+
+# Run only tests that failed last time
+uv run pytest --lf
+
+# Run tests in parallel (requires pytest-xdist)
+uv add --dev pytest-xdist
+uv run pytest -n auto
+
+# Generate JUnit XML report (for CI)
+uv run pytest --junit-xml=report.xml
+
+# Profile slow tests (requires pytest-profiling)
+uv add --dev pytest-profiling
+uv run pytest --profile
+
+# Debug tests with pdb
+uv run pytest --pdb  # Drop into debugger on failure
+uv run pytest -x --pdb  # Stop at first failure and debug
+```
+
+## Best Practices
+
+1. **Organize tests by module** - Mirror your src structure in tests
+2. **Use descriptive names** - Test names should describe what they test
+3. **One assertion per test** - Keep tests focused (when practical)
+4. **Use fixtures** - Share setup code across tests
+5. **Parametrize** - Test multiple inputs with one test function
+6. **Test edge cases** - Empty inputs, None, negative numbers, etc.
+7. **Mock external dependencies** - Don't hit real APIs or databases
+8. **Aim for >80% coverage** - But 100% coverage doesn't mean bug-free
+9. **Keep tests fast** - Mark slow tests and run them separately
+10. **Use markers** - Categorize tests for selective running
dots/.config/claude/skills/Python/workflows/Type.md
@@ -0,0 +1,304 @@
+# Type Checking Workflow
+
+Type check Python code with mypy for static type verification.
+
+## When to Use
+
+- "type check python"
+- "mypy"
+- "check type hints"
+- "static type checking"
+
+## Quick Commands
+
+```bash
+# Type check all files
+uv run mypy .
+
+# Type check specific file/directory
+uv run mypy src/myproject
+
+# Strict mode
+uv run mypy --strict .
+
+# Show error codes
+uv run mypy --show-error-codes .
+
+# Generate HTML report
+uv run mypy --html-report mypy-report .
+```
+
+## Configuration
+
+### pyproject.toml
+
+```toml
+[tool.mypy]
+python_version = "3.11"
+strict = true
+
+# Import discovery
+files = ["src", "tests"]
+namespace_packages = true
+explicit_package_bases = true
+
+# Warnings
+warn_return_any = true
+warn_unused_configs = true
+warn_redundant_casts = true
+warn_unused_ignores = true
+warn_no_return = true
+warn_unreachable = true
+
+# Strictness
+disallow_untyped_defs = true
+disallow_any_generics = true
+disallow_subclassing_any = true
+disallow_untyped_calls = true
+disallow_incomplete_defs = true
+check_untyped_defs = true
+disallow_untyped_decorators = true
+no_implicit_optional = true
+strict_optional = true
+strict_equality = true
+
+# Error messages
+show_error_context = true
+show_column_numbers = true
+show_error_codes = true
+pretty = true
+
+# Per-module options
+[[tool.mypy.overrides]]
+module = "tests.*"
+disallow_untyped_defs = false
+
+[[tool.mypy.overrides]]
+module = "setuptools.*"
+ignore_missing_imports = true
+```
+
+## Type Hints Examples
+
+### Basic Types
+
+```python
+from typing import Any
+
+# Simple types
+name: str = "John"
+age: int = 30
+height: float = 5.9
+is_active: bool = True
+
+# Collections
+names: list[str] = ["John", "Jane"]
+scores: dict[str, int] = {"math": 90, "english": 85}
+coordinates: tuple[float, float] = (1.0, 2.0)
+unique_ids: set[int] = {1, 2, 3}
+
+# Optional
+optional_value: str | None = None
+```
+
+### Function Annotations
+
+```python
+def greet(name: str, age: int = 0) -> str:
+    """Greet a person."""
+    return f"Hello {name}, age {age}"
+
+def process_items(items: list[int]) -> dict[str, int]:
+    """Process list of items."""
+    return {"count": len(items), "sum": sum(items)}
+
+# No return value
+def log_message(msg: str) -> None:
+    """Log a message."""
+    print(msg)
+```
+
+### Type Aliases
+
+```python
+from typing import TypeAlias
+
+# Simple alias
+UserId: TypeAlias = int
+Username: TypeAlias = str
+
+# Complex alias
+UserData: TypeAlias = dict[str, str | int]
+ProcessingResult: TypeAlias = tuple[bool, str, list[int]]
+
+def get_user(user_id: UserId) -> UserData:
+    """Get user data."""
+    return {"id": user_id, "name": "John"}
+```
+
+### Generics
+
+```python
+from typing import TypeVar, Generic
+
+T = TypeVar("T")
+
+class Box(Generic[T]):
+    """Generic box container."""
+
+    def __init__(self, value: T) -> None:
+        self.value = value
+
+    def get(self) -> T:
+        return self.value
+
+# Usage
+int_box: Box[int] = Box(42)
+str_box: Box[str] = Box("hello")
+```
+
+### Protocols
+
+```python
+from typing import Protocol
+
+class Drawable(Protocol):
+    """Protocol for drawable objects."""
+
+    def draw(self) -> str:
+        """Draw the object."""
+        ...
+
+class Circle:
+    """Circle that implements Drawable protocol."""
+
+    def draw(self) -> str:
+        return "Drawing circle"
+
+def render(obj: Drawable) -> None:
+    """Render any drawable object."""
+    print(obj.draw())
+```
+
+### Callable Types
+
+```python
+from typing import Callable
+
+# Function that takes int, returns str
+Transformer: TypeAlias = Callable[[int], str]
+
+def apply_transform(
+    value: int,
+    transform: Transformer,
+) -> str:
+    """Apply transformation function."""
+    return transform(value)
+
+# Usage
+def int_to_str(x: int) -> str:
+    return str(x)
+
+result = apply_transform(42, int_to_str)
+```
+
+## Advanced Patterns
+
+### Literal Types
+
+```python
+from typing import Literal
+
+def set_mode(mode: Literal["read", "write", "append"]) -> None:
+    """Set file mode."""
+    pass
+
+# Only accepts these exact strings
+set_mode("read")   # OK
+set_mode("delete") # Error!
+```
+
+### TypedDict
+
+```python
+from typing import TypedDict
+
+class User(TypedDict):
+    """User data structure."""
+    id: int
+    name: str
+    email: str
+    age: int | None  # Optional field
+
+def create_user(user: User) -> None:
+    """Create a user."""
+    print(user["name"])
+
+# Usage
+user: User = {
+    "id": 1,
+    "name": "John",
+    "email": "john@example.com",
+    "age": None,
+}
+```
+
+### Union Types
+
+```python
+def process(value: int | str | None) -> str:
+    """Process value of multiple types."""
+    if value is None:
+        return "empty"
+    if isinstance(value, int):
+        return f"number: {value}"
+    return f"string: {value}"
+```
+
+## Type Checking Best Practices
+
+1. **Start with `--strict`** - Catches most issues
+2. **Add type hints everywhere** - Functions, variables, attributes
+3. **Use type aliases** - For complex types
+4. **Leverage protocols** - For structural subtyping
+5. **Check CI** - Run mypy in continuous integration
+6. **Fix incrementally** - Use per-file overrides if needed
+7. **Type stubs** - Install type stubs for third-party libraries
+
+```bash
+# Install type stubs
+uv add --dev types-requests types-pyyaml
+```
+
+## Common Issues
+
+### Ignoring Errors
+
+```python
+# Ignore error on specific line
+result = unsafe_function()  # type: ignore
+
+# Ignore specific error code
+result = unsafe_function()  # type: ignore[arg-type]
+
+# File-level ignore (use sparingly)
+# mypy: ignore-errors
+```
+
+### Dealing with `Any`
+
+```python
+from typing import Any, cast
+
+# Avoid Any when possible
+def bad(data: Any) -> Any:
+    return data
+
+# Better: Be specific
+def good(data: dict[str, str]) -> dict[str, str]:
+    return data
+
+# If Any is unavoidable, cast to specific type
+raw_data: Any = get_external_data()
+user_data: dict[str, str] = cast(dict[str, str], raw_data)
+```
dots/.config/claude/skills/Python/workflows/Workspace.md
@@ -0,0 +1,91 @@
+# Workspace Workflow
+
+Manage multiple Python packages in a monorepo with uv workspaces.
+
+## When to Use
+
+- "python monorepo"
+- "multiple packages"
+- "workspace"
+- "manage related packages"
+
+## Quick Setup
+
+### Create Workspace Structure
+
+```
+myworkspace/
+├── pyproject.toml          # Workspace root
+├── packages/
+│   ├── core/
+│   │   ├── pyproject.toml
+│   │   └── src/core/
+│   ├── api/
+│   │   ├── pyproject.toml
+│   │   └── src/api/
+│   └── cli/
+│       ├── pyproject.toml
+│       └── src/cli/
+└── uv.lock                 # Shared lock file
+```
+
+### Root pyproject.toml
+
+```toml
+[tool.uv.workspace]
+members = ["packages/*"]
+
+[tool.uv.sources]
+# Workspace dependencies
+core = { workspace = true }
+api = { workspace = true }
+cli = { workspace = true }
+```
+
+### Package pyproject.toml
+
+```toml
+# packages/api/pyproject.toml
+[project]
+name = "myworkspace-api"
+version = "0.1.0"
+dependencies = [
+    "fastapi>=0.104.0",
+]
+
+[tool.uv.sources]
+core = { workspace = true }  # Depend on workspace package
+```
+
+## Commands
+
+```bash
+# Sync all workspace packages
+uv sync
+
+# Build specific package
+cd packages/api
+uv build
+
+# Run from specific package
+cd packages/cli
+uv run myworkspace-cli
+
+# Add dependency to specific package
+cd packages/api
+uv add httpx
+```
+
+## Benefits
+
+1. **Single lock file** - Consistent versions across packages
+2. **Shared dependencies** - Avoid duplication
+3. **Local development** - Easy cross-package development
+4. **Atomic updates** - Update all packages together
+
+## Best Practices
+
+1. **Shared version** - Use same Python version
+2. **Consistent naming** - `workspace-package` pattern
+3. **Clear boundaries** - Each package has clear purpose
+4. **Minimal coupling** - Avoid circular dependencies
dots/.config/claude/skills/Python/README.md
@@ -0,0 +1,130 @@
+# Python Skill
+
+Modern Python development guidance using uv, ruff, mypy, and pytest.
+
+## Overview
+
+This skill provides comprehensive guidance for Python development following 2025 best practices with modern tooling:
+
+- **uv**: Fast, all-in-one package manager (replaces pip, poetry, pyenv, virtualenv)
+- **ruff**: Lightning-fast linter and formatter (replaces Black, isort, flake8, pylint)
+- **mypy**: Static type checker for type safety
+- **pytest**: Feature-rich testing framework
+
+## Workflows
+
+### Project Management
+- **[Project](workflows/Project.md)**: Create and configure Python projects
+- **[Script](workflows/Script.md)**: Single-file scripts with PEP 723 inline metadata
+- **[Workspace](workflows/Workspace.md)**: Manage monorepos with multiple packages
+
+### Code Quality
+- **[Lint](workflows/Lint.md)**: Lint and format code with ruff
+- **[Type](workflows/Type.md)**: Type check with mypy
+- **[Test](workflows/Test.md)**: Run tests with pytest and coverage
+
+### Dependencies
+- **[Deps](workflows/Deps.md)**: Manage dependencies with uv
+- **[Package](workflows/Package.md)**: Build and publish packages
+
+## Tools
+
+### python-lint
+Run ruff linter and formatter on your code:
+```bash
+./tools/python-lint [directory]
+```
+
+### python-check
+Run all quality checks (lint, type check, tests):
+```bash
+./tools/python-check [directory]
+```
+
+## Quick Start
+
+### New Project
+```bash
+# Create project
+uv init myproject
+cd myproject
+
+# Add dependencies
+uv add requests pydantic rich
+uv add --dev pytest ruff mypy
+
+# Run checks
+uv run ruff check --fix .
+uv run ruff format .
+uv run mypy .
+uv run pytest
+```
+
+### Single Script
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+#     "requests>=2.31.0",
+# ]
+# ///
+import requests
+
+def main() -> int:
+    response = requests.get("https://api.github.com")
+    print(response.json())
+    return 0
+
+if __name__ == "__main__":
+    import sys
+    sys.exit(main())
+```
+
+```bash
+chmod +x script.py
+./script.py  # uv handles dependencies automatically!
+```
+
+## Key Features
+
+### PEP 723 Support
+Inline script metadata allows standalone scripts to declare dependencies directly in the file. No separate requirements.txt needed!
+
+### Modern Type Hints
+Full support for Python 3.10+ type syntax:
+```python
+def process(items: list[str], config: dict[str, int] | None = None) -> list[int]:
+    """Process items with optional config."""
+    pass
+```
+
+### Fast Tooling
+- uv is 10-100x faster than pip
+- ruff is 10-100x faster than traditional linters
+- Both written in Rust for maximum performance
+
+## Best Practices
+
+1. **Use uv for everything** - Package management, venv, running scripts
+2. **Type hints everywhere** - Enable mypy strict mode
+3. **Lint with ruff** - Auto-fix before committing
+4. **Test with pytest** - Aim for >80% coverage
+5. **Lock dependencies** - Commit uv.lock to git
+6. **Use PEP 723** - For single-file scripts
+7. **Follow src layout** - Place code in `src/package/`
+
+## Resources
+
+### Documentation
+- [uv Documentation](https://docs.astral.sh/uv/)
+- [PEP 723 - Inline Script Metadata](https://peps.python.org/pep-0723/)
+- [Ruff Documentation](https://docs.astral.sh/ruff/)
+- [mypy Documentation](https://mypy.readthedocs.io/)
+- [pytest Documentation](https://docs.pytest.org/)
+
+### Articles
+- [Python UV: The Ultimate Guide (DataCamp)](https://www.datacamp.com/tutorial/python-uv)
+- [Managing Python Projects With uv (Real Python)](https://realpython.com/python-uv/)
+- [Share Python Scripts Like a Pro: uv and PEP 723](https://thisdavej.com/share-python-scripts-like-a-pro-uv-and-pep-723-for-easy-deployment/)
+- [Python Testing Best Practices](https://pytest-with-eric.com/introduction/python-unit-testing-best-practices/)
dots/.config/claude/skills/Python/SKILL.md
@@ -0,0 +1,528 @@
+---
+name: Python
+description: Python development best practices and modern tooling with uv. USE WHEN writing Python code, managing dependencies, testing, type checking, or working with Python projects.
+---
+
+# Python Development Best Practices
+
+## Purpose
+Guide Python development using modern tooling (uv, ruff, mypy, pytest) following Python Enhancement Proposals (PEPs), type hints best practices, and community standards for 2025.
+
+### Context Detection
+
+**This skill activates when:**
+- Current directory contains `pyproject.toml`, `requirements.txt`, or `uv.lock`
+- Git repository contains Python files (`.py`, `.pyi`)
+- User is working with Python code or virtual environments
+- Commands like `uv`, `pytest`, `ruff`, or `mypy` are mentioned
+- User explicitly asks about Python development
+
+## Workflow Routing
+
+**When executing a workflow, output this notification directly:**
+
+```
+Running the **WorkflowName** workflow from the **Python** skill...
+```
+
+| Workflow | Trigger | File |
+|----------|---------|------|
+| **Project** | "create python project", "new project", "uv init" | `workflows/Project.md` |
+| **Script** | "python script", "single file", "uv run", "PEP 723" | `workflows/Script.md` |
+| **Test** | "run tests", "pytest", "test coverage", "tox" | `workflows/Test.md` |
+| **Lint** | "lint", "format", "ruff check", "ruff format" | `workflows/Lint.md` |
+| **Type** | "type check", "mypy", "type hints", "pyright" | `workflows/Type.md` |
+| **Deps** | "dependencies", "uv add", "requirements", "lock file" | `workflows/Deps.md` |
+| **Package** | "build package", "publish", "pypi", "wheel" | `workflows/Package.md` |
+| **Workspace** | "monorepo", "workspace", "multiple packages" | `workflows/Workspace.md` |
+
+## Core Principles
+
+1. **Use uv for everything**: uv is the modern, fast, all-in-one tool replacing pip, pip-tools, poetry, pyenv, virtualenv, and more (10-100x faster)
+2. **Type hints everywhere**: Use type annotations for all functions and classes; run mypy in strict mode
+3. **Ruff for quality**: Use ruff for both linting and formatting (replaces Black, isort, flake8, and more)
+4. **Test with pytest**: Write comprehensive tests using pytest with fixtures and parametrization
+5. **Lock dependencies**: Always maintain `uv.lock` for reproducible builds
+6. **PEP 723 for scripts**: Use inline script metadata for single-file Python scripts
+
+## Modern Python Toolchain (2025)
+
+### Package Management: uv
+
+**Why uv?**
+- Written in Rust, 10-100x faster than pip
+- All-in-one: replaces pip, virtualenv, poetry, pyenv, pipx
+- Lockfile support for reproducible builds
+- Automatic Python version management
+- First-class support for PEP 723 inline script metadata
+
+**Installation:**
+```bash
+# On NixOS (preferred in this repo)
+# Already available via home-manager or system packages
+
+# Standalone (if needed elsewhere)
+curl -LsSf https://astral.sh/uv/install.sh | sh
+```
+
+### Code Quality: ruff
+
+**Why ruff?**
+- Extremely fast (10-100x faster than traditional tools)
+- Replaces: Black, isort, flake8, pylint, pyupgrade, autoflake
+- Single tool for linting and formatting
+- Compatible with Black formatting
+
+**Usage:**
+```bash
+# Check for issues
+ruff check .
+
+# Auto-fix issues
+ruff check --fix .
+
+# Format code
+ruff format .
+```
+
+### Type Checking: mypy
+
+**Why mypy?**
+- Industry standard for static type checking
+- Catches bugs before runtime
+- Improves code documentation and IDE support
+- Works alongside ruff (not replaced by it)
+
+**Usage:**
+```bash
+# Type check entire project
+mypy .
+
+# Strict mode
+mypy --strict .
+
+# With specific Python version
+mypy --python-executable .venv/bin/python .
+```
+
+### Testing: pytest
+
+**Why pytest?**
+- Most popular testing framework
+- Simple syntax, powerful features
+- Excellent fixture system
+- Parametrization for data-driven tests
+- Rich plugin ecosystem
+
+**Usage:**
+```bash
+# Run all tests
+pytest
+
+# With coverage
+pytest --cov
+
+# Specific test
+pytest tests/test_auth.py::test_login
+```
+
+## Project Structure
+
+### Standard Layout
+```
+myproject/
+├── src/
+│   └── myproject/
+│       ├── __init__.py
+│       ├── main.py
+│       └── utils.py
+├── tests/
+│   ├── __init__.py
+│   ├── test_main.py
+│   └── test_utils.py
+├── docs/
+│   └── README.md
+├── .gitignore
+├── .python-version          # Python version (managed by uv)
+├── pyproject.toml          # Project configuration
+├── uv.lock                 # Locked dependencies
+└── README.md
+```
+
+### Key Files
+
+**`pyproject.toml`** - Project configuration (PEP 621)
+```toml
+[project]
+name = "myproject"
+version = "0.1.0"
+description = "Description of my project"
+readme = "README.md"
+requires-python = ">=3.11"
+dependencies = [
+    "requests>=2.31.0",
+]
+
+[project.optional-dependencies]
+dev = [
+    "pytest>=8.0.0",
+    "ruff>=0.8.0",
+    "mypy>=1.13.0",
+]
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[tool.ruff]
+line-length = 100
+target-version = "py311"
+
+[tool.ruff.lint]
+select = ["E", "F", "I", "N", "W", "B", "Q"]
+
+[tool.mypy]
+python_version = "3.11"
+strict = true
+warn_return_any = true
+warn_unused_configs = true
+
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+python_files = "test_*.py"
+python_functions = "test_*"
+```
+
+**`.python-version`** - Pin Python version
+```
+3.11
+```
+
+## Writing Python Code
+
+### Type Hints
+
+**Always use type hints:**
+```python
+from typing import List, Dict, Optional, Union
+from pathlib import Path
+
+def process_data(
+    items: list[str],
+    config: dict[str, int],
+    output_path: Path | None = None,
+) -> list[int]:
+    """Process items according to configuration.
+
+    Args:
+        items: List of items to process
+        config: Configuration dictionary
+        output_path: Optional path to write results
+
+    Returns:
+        List of processed integers
+
+    Raises:
+        ValueError: If items is empty
+    """
+    if not items:
+        raise ValueError("items cannot be empty")
+
+    results: list[int] = []
+    for item in items:
+        value = config.get(item, 0)
+        results.append(value)
+
+    if output_path is not None:
+        output_path.write_text(str(results))
+
+    return results
+```
+
+**Modern type syntax (Python 3.10+):**
+```python
+# Use | instead of Union
+def foo(x: int | str) -> str: ...
+
+# Use list, dict, tuple directly (no need for typing.List, etc.)
+def bar(items: list[int]) -> dict[str, list[int]]: ...
+```
+
+### Error Handling
+
+**Be explicit about errors:**
+```python
+from pathlib import Path
+import logging
+
+logger = logging.getLogger(__name__)
+
+def read_config(path: Path) -> dict[str, str]:
+    """Read configuration from file.
+
+    Args:
+        path: Path to configuration file
+
+    Returns:
+        Configuration dictionary
+
+    Raises:
+        FileNotFoundError: If config file doesn't exist
+        ValueError: If config file is invalid
+    """
+    if not path.exists():
+        raise FileNotFoundError(f"Config file not found: {path}")
+
+    try:
+        content = path.read_text()
+        # Parse content...
+        config = parse_config(content)
+    except Exception as e:
+        logger.error(f"Failed to parse config: {e}")
+        raise ValueError(f"Invalid config file: {path}") from e
+
+    return config
+```
+
+### Docstrings
+
+**Use Google or NumPy style docstrings:**
+```python
+def calculate_metrics(
+    data: list[float],
+    method: str = "mean",
+) -> dict[str, float]:
+    """Calculate statistical metrics for data.
+
+    Args:
+        data: List of numeric values
+        method: Calculation method ("mean", "median", "mode")
+
+    Returns:
+        Dictionary containing calculated metrics with keys:
+        - value: The calculated metric
+        - confidence: Confidence interval
+
+    Raises:
+        ValueError: If method is not supported or data is empty
+
+    Examples:
+        >>> calculate_metrics([1, 2, 3], method="mean")
+        {'value': 2.0, 'confidence': 0.95}
+    """
+    if not data:
+        raise ValueError("data cannot be empty")
+
+    # Implementation...
+```
+
+## Single-File Scripts (PEP 723)
+
+**Use uv with inline script metadata for standalone scripts:**
+
+```python
+#!/usr/bin/env -S uv run --script
+# /// script
+# requires-python = ">=3.11"
+# dependencies = [
+#     "requests>=2.31.0",
+#     "rich>=13.0.0",
+# ]
+# ///
+"""Script to fetch and display API data."""
+
+import requests
+from rich.console import Console
+
+console = Console()
+
+def main() -> None:
+    """Fetch and display data from API."""
+    response = requests.get("https://api.example.com/data")
+    response.raise_for_status()
+
+    data = response.json()
+    console.print(data)
+
+if __name__ == "__main__":
+    main()
+```
+
+**Make it executable:**
+```bash
+chmod +x script.py
+./script.py  # uv automatically handles dependencies!
+```
+
+## Testing with pytest
+
+### Test Structure
+
+```python
+# tests/test_calculator.py
+import pytest
+from myproject.calculator import Calculator
+
+class TestCalculator:
+    """Test suite for Calculator class."""
+
+    @pytest.fixture
+    def calc(self) -> Calculator:
+        """Provide a Calculator instance."""
+        return Calculator()
+
+    def test_add(self, calc: Calculator) -> None:
+        """Test addition operation."""
+        result = calc.add(2, 3)
+        assert result == 5
+
+    @pytest.mark.parametrize("a,b,expected", [
+        (2, 3, 5),
+        (0, 0, 0),
+        (-1, 1, 0),
+        (10, -5, 5),
+    ])
+    def test_add_parametrized(
+        self,
+        calc: Calculator,
+        a: int,
+        b: int,
+        expected: int,
+    ) -> None:
+        """Test addition with multiple inputs."""
+        assert calc.add(a, b) == expected
+
+    def test_divide_by_zero(self, calc: Calculator) -> None:
+        """Test division by zero raises error."""
+        with pytest.raises(ValueError, match="Cannot divide by zero"):
+            calc.divide(10, 0)
+```
+
+### Coverage Configuration
+
+Add to `pyproject.toml`:
+```toml
+[tool.coverage.run]
+source = ["src"]
+omit = ["tests/*", "**/__pycache__/*"]
+
+[tool.coverage.report]
+precision = 2
+show_missing = true
+skip_covered = false
+
+[tool.coverage.html]
+directory = "htmlcov"
+```
+
+## Common Workflows
+
+### New Project
+```bash
+# Create new project
+uv init myproject
+cd myproject
+
+# Add dependencies
+uv add requests rich
+
+# Add dev dependencies
+uv add --dev pytest ruff mypy
+
+# Run tests
+uv run pytest
+```
+
+### Existing Project
+```bash
+# Sync environment with lock file
+uv sync
+
+# Run app
+uv run python -m myproject
+
+# Run tests
+uv run pytest
+
+# Lint and format
+uv run ruff check --fix .
+uv run ruff format .
+
+# Type check
+uv run mypy .
+```
+
+### Scripts
+```bash
+# Run script (with automatic dependency installation)
+uv run script.py
+
+# Add dependencies to existing script
+uv add --script script.py requests
+```
+
+## Integration with NixOS
+
+This repository uses NixOS with home-manager. Python tools are configured via:
+
+**System/Home packages:**
+```nix
+# home/common/dev/python.nix
+{ pkgs, ... }: {
+  home.packages = with pkgs; [
+    uv          # Package manager
+    ruff        # Linter/formatter
+    mypy        # Type checker
+    python311   # Python interpreter
+  ];
+}
+```
+
+**Shell environment:**
+```bash
+# uv manages virtual environments automatically
+# No need for manual venv activation
+
+# Tools available system-wide
+uv --version
+ruff --version
+mypy --version
+```
+
+## Best Practices Checklist
+
+- [ ] Use `uv` for all package management
+- [ ] Add type hints to all functions
+- [ ] Run `mypy --strict` with no errors
+- [ ] Use `ruff` for linting and formatting
+- [ ] Write tests with `pytest` (aim for >80% coverage)
+- [ ] Use PEP 723 for single-file scripts
+- [ ] Pin Python version in `.python-version`
+- [ ] Lock dependencies in `uv.lock`
+- [ ] Document functions with docstrings
+- [ ] Handle errors explicitly
+- [ ] Use `pathlib.Path` instead of string paths
+- [ ] Follow PEP 8 naming conventions
+- [ ] Keep functions focused and small
+- [ ] Use descriptive variable names
+
+## Resources
+
+### Official Documentation
+- [uv Documentation](https://docs.astral.sh/uv/)
+- [PEP 723 - Inline Script Metadata](https://peps.python.org/pep-0723/)
+- [Ruff Documentation](https://docs.astral.sh/ruff/)
+- [mypy Documentation](https://mypy.readthedocs.io/)
+- [pytest Documentation](https://docs.pytest.org/)
+
+### Best Practices
+- [Python UV: The Ultimate Guide (DataCamp)](https://www.datacamp.com/tutorial/python-uv)
+- [uv: An In-Depth Guide](https://www.saaspegasus.com/guides/uv-deep-dive/)
+- [Managing Python Projects With uv (Real Python)](https://realpython.com/python-uv/)
+- [Python Testing Best Practices](https://pytest-with-eric.com/introduction/python-unit-testing-best-practices/)
+
+### References
+- Python Enhancement Proposals (PEPs)
+- Google Python Style Guide
+- Type Hints Best Practices
+- pytest Good Integration Practices