# Code Style
## Type hints
- Annotate all function parameters and return types without exception.
- Use modern built-in type syntax: `list[str]`, `dict[str, Any]`, `str | None` — never `Optional`, `Union`, `List`, `Dict`, or `Tuple` from `typing`. `Any` is acceptable when the type is genuinely dynamic.
## Docstrings — Google style
- Multi-line format: short imperative summary sentence, blank line, then sections.
- `Args:` — one line per parameter, no type (already in the signature).
- `Returns:` — `ReturnType: Description`. For union returns with distinct cases, use a bullet list under the type.
- `Raises:` — `ExceptionType: Condition that triggers it.`
- One-liner docstring acceptable for trivial or self-evident methods.
- Class docstring covers the class purpose; `__init__` docstring covers non-obvious constructor parameters.
## No inline comments
- Write no `#` comments. Name things so the code reads without annotation.
## Strings
- Double quotes throughout.
## Imports
- Order: stdlib → third-party → local. One blank line between groups.
## Naming
- `snake_case` — functions, methods, variables, module names.
- `PascalCase` — classes.
- `UPPER_CASE` — module-level constants.
- `_prefix` — private methods and attributes.
## Formatting
- 4-space indentation. Not Black-formatted. No enforced line length; keep lines readable.
- Multi-line expressions use standard 4-space continuation indent.
## Logging
- Library modules: `logger = logging.getLogger(__name__)` plus an explicit `logger.setLevel(...)`, both at module level.
- Entry-point scripts: `logging.basicConfig(level=..., format="%(levelname)s: %(message)s")` at module level.
- `print()` acceptable in deploy and setup scripts for user-facing progress output.
## Error handling
- Catch specific exceptions, never bare `except`.
- Re-raise with context: `raise XError("...") from e`.
- `RuntimeError` for internal invariant violations; `EnvironmentError` for missing env configuration.
## Patterns
- Use `Path` from `pathlib` for all filesystem operations.
- Apply `@staticmethod` to any method that does not access `self` or `cls`.
- Use `@dataclass(frozen=True)` for configuration containers — immutability turns accidental mutation into a TypeError rather than a silent bug.
- Use context managers (`with`) for all file I/O.
- Use `@abstractmethod` on every method a subclass must override — turns a runtime `AttributeError` into a clear error at class definition time.
- Use `Protocol` (from `typing`) for structural typing when callers should not be required to inherit from a base class.
- Use `@classmethod` for alternative constructors instead of overloading `__init__` with optional parameters.
- Use `functools.cached_property` for expensive computed attributes that are derived purely from `self` state and read more than once.
- Define `__all__` in every `__init__.py` to declare the public API explicitly.
## Idioms
- Prefer early returns (guard clauses) over nested conditionals — exit on invalid/edge cases at the top of the function.
- Build collections with comprehensions, not imperative loops. Use generator expressions when the full list is not needed.
- Use f-strings for all string interpolation — never `.format()` or `%`-style.
- Use list comprehension