Skip to content

Commit 3256382

Browse files
author
Nick Sullivan
committed
📝 Migrate from .cursorrules to structured .cursor/rules/
Replaces single monolithic .cursorrules file with organized rule modules for better maintainability. Separates code style, Python conventions, and testing standards into focused files that can be selectively applied based on context and file types.
1 parent c61822a commit 3256382

File tree

4 files changed

+581
-131
lines changed

4 files changed

+581
-131
lines changed

.cursor/rules/code-style.mdc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
description:
3+
globs:
4+
alwaysApply: true
5+
---
6+
7+
# 💅 Code Style Guidelines
8+
9+
## Line Length
10+
11+
- Maximum line length: 88 characters (per pyproject.toml Ruff configuration)
12+
- Break long docstring lines during generation
13+
- Never generate lines longer than 88 characters
14+
15+
## Comments
16+
17+
- Focus on explaining the "why" behind the code rather than the "what"
18+
- File headers: Provide a brief overview of the file's purpose (1-2 sentences)
19+
- Function documentation: Include 1-3 sentences explaining intent and non-obvious logic
20+
- Use section dividers for logical organization
21+
- Use inline comments sparingly - only for complex logic or business rules
22+
- Avoid commenting the obvious, especially when code is self-documenting
23+
- Emojis encouraged in comments when they add clarity
24+
- Occasional clever puns/jokes welcome. You should aim to create delight for the people
25+
reading the code. :)
26+
27+
## Zen of Python
28+
29+
1. **Readability is the number 1 code quality metric**.
30+
2. Beautiful is better than ugly.
31+
3. Explicit is better than implicit.
32+
4. Simple is better than complex.
33+
5. Complex is better than complicated.
34+
6. Flat is better than nested.
35+
7. Sparse is better than dense.
36+
8. Special cases aren't special enough to break the rules.
37+
- Although practicality beats purity.
38+
9. Errors should never pass silently.
39+
- Unless explicitly silenced.
40+
10. In the face of ambiguity, refuse the temptation to guess.
41+
11. There should be one -- and preferably only one -- obvious way to do it.
42+
12. Now is better than never.

.cursor/rules/python.mdc

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
---
2+
alwaysApply: true
3+
---
4+
5+
## Python
6+
7+
- **Imports ALWAYS go to the top of the file - NEVER import within functions, unless it
8+
would create circular import** ⚠️
9+
- Use Decimal for financial calculations - never use float for money/prices
10+
- Don't abbreviate packages (use "pandas" not "pd", "numpy" not "np")
11+
12+
## Modern python
13+
14+
We use python 3.13+ and follow modern best practices, including:
15+
16+
- Use `Path` lib for files instead of `open`
17+
- Use `var!s` instead of `str(var)`
18+
- Prefer walrus operator (:=) to reduce repetition in code
19+
- Use modern union syntax `X | Y` instead of `Union[X, Y]` or `(X, Y)` in isinstance
20+
calls
21+
22+
## Libraries to use
23+
24+
- Use `arrow` for datetime handling, and `dateparse` for parsing human supplied dates
25+
- Use `rich` for making command line applications more beautiful and more user friendly
26+
- We are migrating from `requests` to `httpx` so new code should use `httpx`
27+
28+
## Exceptions - READ THIS CAREFULLY 🚨
29+
30+
### ⚠️ CRITICAL: try/except is FORBIDDEN except in these EXACT 2 scenarios:
31+
32+
**SCENARIO 1: Handling a SPECIFIC exception type with actual handling logic**
33+
34+
```python
35+
# ✅ ALLOWED: Specific exception + real handling + honeybadger notification
36+
try:
37+
result = process_transaction(data)
38+
except InvalidTransactionError as e:
39+
honeybadger.notify(e, context={"transaction_id": tx_id, "error_type": "validation"})
40+
return {"status": "failed", "reason": "validation_error"}
41+
```
42+
43+
**SCENARIO 2: Processing loop where you want to continue on errors**
44+
45+
```python
46+
# ✅ ALLOWED: Loop processing where individual failures shouldn't stop the loop
47+
for item in items:
48+
try:
49+
process_item(item)
50+
except ProcessingError as e:
51+
honeybadger.notify(e, context={"item_id": item.id, "batch_id": batch.id})
52+
continue # Continue processing other items
53+
```
54+
55+
### 🚫 ABSOLUTELY FORBIDDEN - NEVER EVER DO THESE:
56+
57+
```python
58+
# 🚫 NEVER: Generic exception catching
59+
try:
60+
some_function()
61+
except Exception as e:
62+
logger.error("Something went wrong") # This hides errors from us!
63+
64+
# 🚫 NEVER: Log and continue without specific handling
65+
try:
66+
some_function()
67+
except SomeError as e:
68+
logger.error(f"Error: {e}") # Just logging is NOT handling!
69+
70+
# 🚫 NEVER: Swallowing exceptions
71+
try:
72+
some_function()
73+
except Exception:
74+
pass # Absolutely forbidden!
75+
```
76+
77+
### ✅ DEFAULT APPROACH - 99% of the time, do this:
78+
79+
```python
80+
# ✅ PREFERRED: Let exceptions bubble up to HoneyBadger
81+
result = process_transaction(data) # No try/except at all!
82+
user = User.objects.get(id=user_id) # Let it crash if user doesn't exist!
83+
api_response = requests.get(url) # Let network errors bubble up!
84+
```
85+
86+
### 🎯 REMEMBER:
87+
88+
- **Exceptions are NOT errors to be hidden - they're signals something needs attention**
89+
- **HoneyBadger catches everything that bubbles up - that's what we want**
90+
- **If you're tempted to use try/except, ask: "Am I actually HANDLING this or just
91+
HIDING it?"**
92+
- **Logging an error is NOT the same as handling an error**
93+
94+
### 🚨 AI INSTRUCTION COMPLIANCE:
95+
96+
If you are an AI reading this and you write try/except code that doesn't fit EXACTLY
97+
into scenarios 1 or 2 above, you are directly violating explicit instructions. The human
98+
has been crystal clear about this requirement. Do not rationalize, do not make
99+
exceptions, do not "be helpful" by catching errors. Follow the rules.
100+
101+
## 🚫 Defensive Programming - hasattr/getattr
102+
103+
**NEVER use hasattr() or getattr() unless there's a REALLY good reason.**
104+
105+
### ❌ BAD: Defensive programming bullshit
106+
107+
```python
108+
# 🚫 NEVER: Checking if methods exist in your own class
109+
if hasattr(self, "create_trade_from_fill") and callable(self.create_trade_from_fill):
110+
self.create_trade_from_fill(position, fill, trade_type)
111+
112+
# 🚫 NEVER: Using getattr with defaults for config
113+
value = getattr(config, "SOME_SETTING", "default")
114+
```
115+
116+
### ✅ GOOD: Trust your code
117+
118+
```python
119+
# ✅ PREFERRED: Just call the method - it exists!
120+
self.create_trade_from_fill(position, fill, trade_type)
121+
122+
# ✅ PREFERRED: Config entries exist - don't use defaults
123+
value = config.SOME_SETTING
124+
```
125+
126+
### 🎯 PHILOSOPHY:
127+
128+
- **Trust Django ORM** - Model fields and methods exist
129+
- **Trust your own classes** - Methods you defined exist
130+
- **Fail fast** - If something doesn't exist, let it crash
131+
- **Broken is better than wrong** - Don't hide missing attributes with defaults
132+
133+
### 🤏 RARE EXCEPTIONS:
134+
135+
Only use hasattr/getattr when:
136+
137+
- Dynamically handling external APIs with unknown schemas
138+
- Plugin systems where attributes genuinely might not exist
139+
- Legacy compatibility layers (temporarily)
140+
141+
**But 99% of the time - just trust your code and let it fail explicitly.**
142+
143+
## 🚫 Defensive Programming - .get() with defaults
144+
145+
**NEVER use dict.get() with default values for critical data that SHOULD exist.**
146+
147+
### ❌ BAD: Silent failures that hide bugs
148+
149+
```python
150+
# 🚫 NEVER: Using .get() with defaults for critical API data
151+
filled_size = Decimal(str(order_data.get("filledSz", "0"))) # Hides missing data!
152+
position_size = position_data.get("szi", "0") # Could hide real positions!
153+
error_msg = result.get("error", "") # Wrong error message passed to handler!
154+
155+
# 🚫 NEVER: Using .get() for required fields
156+
if fill.get("coin") == symbol and fill.get("time", 0) > cutoff: # Wrong filter logic!
157+
```
158+
159+
### ✅ GOOD: Fail fast on missing data
160+
161+
```python
162+
# ✅ PREFERRED: Critical fields must exist or crash immediately
163+
filled_size = Decimal(str(order_data["filledSz"])) # Crashes if data is malformed
164+
position_size = position_data["szi"] # Crashes if position data is incomplete
165+
error_msg = result["error_msg"] # Crashes if error handling is broken
166+
167+
# ✅ PREFERRED: Required fields for filtering/logic
168+
if fill["coin"] == symbol and fill["time"] > cutoff: # Crashes if data is incomplete
169+
```
170+
171+
### 🎯 WHEN TO USE .get() WITH DEFAULTS:
172+
173+
Only use `.get()` with defaults when:
174+
175+
1. **Optional configuration** - `config.get("optional_setting", "default")`
176+
2. **Optional metadata** - `metadata.get("description", "")`
177+
3. **Legitimate optional fields** - `kwargs.get("timeout", 30)`
178+
4. **External API responses with genuinely optional fields** -
179+
`api_response.get("optional_field")`
180+
181+
### 🚨 THE DANGER:
182+
183+
Using `.get()` with defaults for critical data creates **silent failures** that:
184+
185+
- **Hide API schema changes** - If Hyperliquid changes field names, you get 0 instead of
186+
an error
187+
- **Corrupt financial data** - Missing position sizes default to 0, hiding real
188+
positions
189+
- **Break error handling** - Wrong error messages passed to exception mappers
190+
- **Create logic bugs** - Filters work on wrong data, missing critical records
191+
- **Cause hours of debugging** - Bugs are hidden instead of failing loudly
192+
193+
### 💡 PHILOSOPHY:
194+
195+
**"Broken is better than wrong"** - If the data structure isn't what we expect, crash
196+
immediately and alert us through HoneyBadger rather than silently continuing with
197+
potentially wrong default values that could lead to financial losses.
198+
199+
## 🚨 Database Transactions
200+
201+
**NEVER use Django's `transaction.atomic()`** - neither as decorator nor context
202+
manager.
203+
204+
Instead:
205+
206+
- Use individual atomic operations (update_or_create, etc.)
207+
- Design operations to be idempotent
208+
- Use database constraints for consistency
209+
210+
## Logging
211+
212+
Do not use built in logger. Use our logger which is loguru, from `helpers.logger` Use
213+
emojis when they are helpful. Use other log levels from loguru when helpful, such as
214+
logger.success() when a command completes. Add logging that is helpful not only for
215+
humans, but for AI to troubleshoot. Always use logger.exception in an exception catch,
216+
not logger.error()
217+
218+
```python
219+
from helpers.logger import logger
220+
logger.info("Launch sequence initiated", extra={"mission_id": mission.id})
221+
```
222+
223+
## 🎨 Formatting Helpers
224+
225+
We have a set of consistent formatting helpers in `helpers.formatting` for displaying
226+
values across the app. Always use these instead of creating custom formatting logic.
227+
228+
[formatting.py](mdc:helpers/formatting.py)
229+
230+
For json formatting, see [json_utils.py](mdc:helpers/json_utils.py)

0 commit comments

Comments
 (0)