mirror of
https://github.com/exo-explore/exo.git
synced 2025-12-23 14:17:58 -05:00
Add RULES.md and .cursorrules
This commit is contained in:
64
.cursorrules
Normal file
64
.cursorrules
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# follow **every** rule exactly; report any violation instead of silently fixing it.
|
||||||
|
|
||||||
|
You must prioritize straightforward code semantics, well-named types, clear function signatures, and robust, carefully-chosen abstractions. Think about how your decisions might impact these aspects of code quality before proposing any changes.
|
||||||
|
|
||||||
|
You can use the advanced features of `typing`. You have access to all of the new features from Python 3.13, 3.12, 3.11...
|
||||||
|
|
||||||
|
**When you're done making your changes, remove any redundant comments that you may have left; the comments that remain should only apply to complex segments of code, adding relevant context.**
|
||||||
|
|
||||||
|
## 1. Code Discipline
|
||||||
|
|
||||||
|
* Eliminate superfluous `try` / `catch` and `if` branches through strict typing and static analysis.
|
||||||
|
* Use pure functions unless you must mutate fixed state—then wrap that state in a class.
|
||||||
|
* Every function is **referentially transparent**: same inputs ⇒ same outputs, no hidden state, no unintended I/O.
|
||||||
|
* Put side-effects in injectable “effect handlers”; keep core logic pure.
|
||||||
|
|
||||||
|
## 2. Naming
|
||||||
|
|
||||||
|
* Choose descriptive, non-abbreviated names—no 3-letter acronyms or non-standard contractions.
|
||||||
|
* Anyone reading a function’s type signature alone should grasp its purpose without extra context.
|
||||||
|
|
||||||
|
## 3. Typing
|
||||||
|
|
||||||
|
* Maintain **strict, exhaustive** typing; never bypass the type-checker.
|
||||||
|
* Default to `Literal[...]` when an enum-like set is needed.
|
||||||
|
* Prefer built-in types; when two values share structure but differ in meaning, enforce separation:
|
||||||
|
* Use `typing.NewType` for primitives (zero runtime cost).
|
||||||
|
* For serialisable objects, add a `type: str` field that states the object’s identity.
|
||||||
|
|
||||||
|
## 4. Pydantic
|
||||||
|
|
||||||
|
* Read, respect, and rely on Pydantic docs.
|
||||||
|
* Centralise a common `ConfigDict` with `frozen=True` and `strict=True` (or stricter) and reuse it everywhere.
|
||||||
|
* For hierarchies of `BaseModel` variants, declare a discriminated union with `typing.Annotated[Base, Field(discriminator='variant')]`; publish a single `TypeAdapter[Base]` so all variants share one strict validator.
|
||||||
|
|
||||||
|
## 5. IDs & UUIDs
|
||||||
|
|
||||||
|
* Subclass Pydantic’s `UUID4` for custom ID types.
|
||||||
|
* Generate fresh IDs with `uuid.uuid4()`.
|
||||||
|
* Create idempotency keys by hashing *persisted* state plus a **function-specific salt** to avoid collisions after crashes.
|
||||||
|
|
||||||
|
## 6. Error Handling
|
||||||
|
|
||||||
|
* Catch an exception **only** where you can handle or transform it meaningfully.
|
||||||
|
* State in the docstring **where** each exception is expected to be handled and **why**.
|
||||||
|
|
||||||
|
## 7. Dependencies
|
||||||
|
|
||||||
|
* Introduce new external dependencies only after approval.
|
||||||
|
* Request only libraries common in production environments.
|
||||||
|
|
||||||
|
## 8. Use of `@final` & Freezing
|
||||||
|
|
||||||
|
* Mark classes, methods, and variables as `@final` or otherwise immutable wherever applicable.
|
||||||
|
|
||||||
|
## 9. Repository Workflow
|
||||||
|
|
||||||
|
If you spot a rule violation within code that you've not been asked to work on directly, inform the user rather than patching it ad-hoc.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### One-Sentence Summary
|
||||||
|
|
||||||
|
Write strictly-typed, pure, self-describing Python that uses Pydantic, well-scoped side-effects, immutable state, approved dependencies, and explicit error handling
|
||||||
66
RULES.md
66
RULES.md
@@ -1,8 +1,72 @@
|
|||||||
# Repository Rules
|
# Repository Rules
|
||||||
|
|
||||||
|
* if you see any code that violates these rules, raise it with me directly rather than trying to fix.
|
||||||
|
* where applicable, file a GitHub Issue.
|
||||||
|
* adhere to these rules strictly.
|
||||||
|
|
||||||
## General Rules
|
## General Rules
|
||||||
|
|
||||||
* do not bypass the type-checker.
|
* if its possible to eliminate an extra try-catch or if-statement at runtime using type-level discipline, do it!
|
||||||
|
* name your types, functions, and classes appropriately.
|
||||||
|
* no three-letter acronyms.
|
||||||
|
* no non-standard contractions.
|
||||||
|
* each data type has a meaning, pick a name which is accurate and descriptive.
|
||||||
|
* the average layman should be able to easily understand what your function does using the function signature alone!
|
||||||
|
* sometimes, there will be exceptions. eg, when you're using specific technical terms that are well understood (saga, event, etc).
|
||||||
|
* usually, you'll think that your code is an exception to the rules, but it won't be.
|
||||||
|
|
||||||
|
## State, Functions and Classes
|
||||||
|
|
||||||
|
* every function, given the same inputs, should produce the same outputs. ie, no hidden state.
|
||||||
|
* use classes to prevent fixed state from being mutated arbitrarily (unsafely); methods provide a safe way of interfacing with state.
|
||||||
|
* if your logic doesn't mutate fixed state, it probably belongs in a standalone function rather than a class.
|
||||||
|
* functions shouldn't usually produce side-effects (they should be computationally pure).
|
||||||
|
* if, for example, you're updating a state using an event (computationally pure), and you want to trigger a saga (computational side-effect), store the logic for triggering the saga into an effect handler (a function, capable of producing side-effects, that you pass into an otherwise computationally pure function, so that it may trigger side-effects safely).
|
||||||
|
|
||||||
|
## Pydantic
|
||||||
|
|
||||||
|
* read the Pydantic docs.
|
||||||
|
* respect the Pydantic docs.
|
||||||
|
* pydantic is all you need.
|
||||||
|
* declare and re-use a central `ConfigDict` for your use-case, you'll usually want `frozen` and `strict` to be `True`.
|
||||||
|
|
||||||
|
## Unique ID (UUID) Generation
|
||||||
|
|
||||||
|
* inherit from Pydantic's `UUID4` class to create your own UUID class.
|
||||||
|
* use `uuid.uuid4()` to initialize your class with a fresh UUID where possible.
|
||||||
|
* ensure that idempotency tags are generated by taking the salted hash of persisted state.
|
||||||
|
* rationale: if a node crashes and resumes from an older state, it should not accidentally re-publish the same event twice under different idempotency tags.
|
||||||
|
* every distinct function should feature a unique salt, so that there are no accidental collisions in idempotency tags.
|
||||||
|
|
||||||
|
## Type Wrappers
|
||||||
|
|
||||||
|
* reuse types that already exist in the Python standard library.
|
||||||
|
* when two distinct data types are structurally identical (for example, different IDs which are both UUIDs but shouldn't never mixed up), make sure they can't be conflated by the type system.
|
||||||
|
* if you're working with a primitive data type (`str`, `int`, etc), use `NewType` (it has zero runtime overhead).
|
||||||
|
* if you're working with serializable data objects, consider adding a field (type `str`) that states its type.
|
||||||
|
|
||||||
|
## Type Discipline
|
||||||
|
|
||||||
|
* do not bypass the type-checker, preserve strict typing by any means necessary.
|
||||||
|
* by default, use literal types (like `Literal['one', 'two']`) where an enum seems appropriate.
|
||||||
|
|
||||||
|
pro-tip: Python's type system is quite complex and feature-rich, so reading the documentation is often advisable; Matt discovered that Python `typing` library allows you to check that you've implemented a `match` exhaustively using `Literal` and `get_args(type)` after reading the docs.
|
||||||
|
|
||||||
|
## Use of `@final`, Freezing
|
||||||
|
|
||||||
|
* use wherever applicable.
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
* don't try-catch for no reason.
|
||||||
|
* make sure that you always know where and when the exceptions your code produces are meant to be handled, so that it's never a nasty surprise.
|
||||||
|
* always write the rationale for your error-handling down in the docstring!
|
||||||
|
* communicate the details to your colleagues when appropriate.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
* don't introduce any new dependencies without asking.
|
||||||
|
* don't ask for any dependencies that aren't ubiquitous within production environments.
|
||||||
|
|
||||||
## Commit Messages
|
## Commit Messages
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user