Don't assume. Don't hide confusion. Surface tradeoffs.
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
Minimum code that solves the problem. Nothing speculative.
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is over complicated?" If yes, simplify.
Touch only what you must. Clean up only your own mess.
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove preexisting dead code unless asked.
The test: Every changed line should trace directly to the user's request.
Define success criteria. Loop until verified.
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multistep tasks, state a brief plan:
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
- The package manger for the project is uv
- Make
uv addfor core dependencies,uv add --devfor developer dependencies, and add optional features to groups - It is also possible to remove packages using
uv remove
- The targets python versions greater than or equal to 3.12
- Given the project targets a more modern python, use functionality such as:
- Classes and data structures:
- Use
@dataclass(fromdataclasses) instead of manually defining__init__for data-holding classes - Consider using
slots=Truefor memory efficiency and attribute access protection - Use
kw_only=Trueto require keyword arguments for better readability at call sites - Use
frozen=Truefor immutable data structures - Example:
@dataclass(slots=True, kw_only=True, frozen=True) - When NOT to use dataclass:
- Inheriting from non-dataclass parents (can cause MRO and initialization issues)
- Need for
__new__method (for singleton patterns, custom object creation) - Complex property logic with getters/setters that transform data
- Need for
__init_subclass__or metaclass customization - Classes with significant behavior/methods (prefer traditional classes for these)
- When to use dataclass:
- Simple data containers with minimal logic
- Configuration objects, DTOs (Data Transfer Objects), result types
- Immutable value objects (use
frozen=True) - When you want automatic
__eq__,__repr__,__hash__implementations
- Use
- Prefer importing using
from x import yinstead ofimport x - Import local modules using the full path (ex:
from my_project.my_module import MyClass) - Don't use docstrings, instead add inline comments only in places where there is complex or easily breakable logic
- For type aliases, prefer Python's modern syntax:
type MyAlias = SomeType(PEP 695 style), especially in new code. - URL construction:
- Use
urllib.parsemethods for URL manipulation (don't use string concatenation or f-strings for query params) - Use
urlencode()for query parameters - Use
urlparse()andurlunparse()for URL composition - Example:
urlunparse((parsed.scheme, parsed.netloc, parsed.path, "", urlencode(params), "")) - This ensures proper encoding and avoids common URL injection vulnerabilities
- Use
Note
This project targets python >=3.14,<3.15.
- Control Flow Assignment: Prefer the walrus operator assigment for control statments (i.e.
if,while, etc.).
# Do
if (x := f()) > 10: ...
# Don't
x = f()
if x > 10: ...- Ambiguous Types: Avoid using
object,typing.cast, andAnyanywhere in the codebase. In 99%+ of cases, their usage is required due to poor design choices. If there is a requirement due to an upstream dependency and/or a lack of maturity around python's typing, add a brief note using an inline comment. - Modern Templating: As of
python>=3.12,PEP 695added support for a more modern type paramter syntax. Prefer that for type parameters overTypeVar.
# Do
class A[T: B]:
def __init__(self, c: B) -> None ...
# Don't
T = TypeVar("T", bound=B)
class A(Generic[T]):
def __init__(self, c: T) -> None ...# Do
def fn[T: B](c: B) -> B: ...
# Don't
T = TypeVar("T", bound=B)
def fn(c: T) -> T: ...- Self References: The
Selftype for return types (from typing import Self) instead of importfrom __future__ import annotationsand using the class name directly (note: there is no need to annotateselfparameters since the typing is implicit).
# Do
class MyClass:
def fn(self, x: Self) -> Self: ...
# Don't
class MyClass:
def fn(self, x: MyClass) -> MyClass: ...