My Cursor Rules and Context Files
As a hopefully helpful starter, I am sharing my rules and context files in their entirety.
Cursor Rules
Note: these are global cursor rules, I do not use .cursorrules
files, because I don’t need project-specific rules at the moment.
You are a an eager, but thoughtful and collaborataive engineer, ready to learn from collaborations.
You never immediately start writing code; in fact, you wait until you are told to write code.
Your main goal is learning, discussion, and collaboration.
You keep a strict process to collaborate with me, even reminding me when I try to circumvent it.
1. Problem statement: we always start with discussing and agreeing on a problem statement. This should capture the main value and requirements of the effort.
2. Design Document: you always think about your solution before you begin implementation and share your plan so we can discuss and iterate on it. Use the @context/design_doc.md template. Store this under `design/`.
3. Implementation and testing: you always conduct implementation and testing in a separate branch, and do it in a phased approach so that not too much is done at once.
4. Pull Request: you always create a pull request so that I can review and merge your changes.
When writing code, you always keep the following in mind:
- Type hints: you use type hints wherever possible to make the code more readable and maintainable.
- Docstrings: you use docstrings to explain the code and make it more readable and maintainable.
- Logging: you use loggers to make sure code is properly instrumented for later debugging.
- Environment and dependency management: you use uv to manage dependencies and run scripts: `uv run SCRIPT ARGS` and `uv add DEPENDENCY` instead of `python SCRIPT ARGS` and `pip install DEPENDENCY`.
- Testing: you write tests for your code, and use pytest to run them: `uv run pytest tests/TEST_FILE.py`.
You always keep @context/coding_style.md and @context/writing_good_interfaces.md in mind when writing your code.
Context File: Design Document Template
# [Feature/Component Name] Design Document
## Current Context
- Brief overview of the existing system
- Key components and their relationships
- Pain points or gaps being addressed
## Requirements
### Functional Requirements
- List of must-have functionality
- Expected behaviors
- Integration points
### Non-Functional Requirements
- Performance expectations
- Scalability needs
- Observability requirements
- Security considerations
## Design Decisions
### 1. [Major Decision Area]
Will implement/choose [approach] because:
- Rationale 1
- Rationale 2
- Trade-offs considered
### 2. [Another Decision Area]
Will implement/choose [approach] because:
- Rationale 1
- Rationale 2
- Alternatives considered
## Technical Design
### 1. Core Components
```python
# Key interfaces/classes with type hints
class MainComponent:
"""Core documentation"""
pass
```
### 2. Data Models
```python
# Key data models with type hints
class DataModel:
"""Model documentation"""
pass
```
### 3. Integration Points
- How this interfaces with other systems
- API contracts
- Data flow diagrams if needed
## Implementation Plan
1. Phase 1: [Initial Implementation]
- Task 1
- Task 2
- Expected timeline
2. Phase 2: [Enhancement Phase]
- Task 1
- Task 2
- Expected timeline
3. Phase 3: [Production Readiness]
- Task 1
- Task 2
- Expected timeline
## Testing Strategy
### Unit Tests
- Key test cases
- Mock strategies
- Coverage expectations
### Integration Tests
- Test scenarios
- Environment needs
- Data requirements
## Observability
### Logging
- Key logging points
- Log levels
- Structured logging format
### Metrics
- Key metrics to track
- Collection method
- Alert thresholds
## Future Considerations
### Potential Enhancements
- Future feature ideas
- Scalability improvements
- Performance optimizations
### Known Limitations
- Current constraints
- Technical debt
- Areas needing future attention
## Dependencies
### Runtime Dependencies
- Required libraries
- External services
- Version constraints
### Development Dependencies
- Build tools
- Test frameworks
- Development utilities
## Security Considerations
- Authentication/Authorization
- Data protection
- Compliance requirements
## Rollout Strategy
1. Development phase
2. Testing phase
3. Staging deployment
4. Production deployment
5. Monitoring period
## References
- Related design documents
- External documentation
- Relevant standards
Context File: Coding Style Guide
## **Overview**
This guide is written in the spirit of [Google Style Guides](https://github.com/google/styleguide), especially the most well written ones like for [Obj-C](https://github.com/google/styleguide/blob/gh-pages/objcguide.md).
Coding style guides are meant to help everyone who contributes to a project to forget about how code feels and easily understand the logic.
These are guidelines with rationales for all rules. If the rationale doesn't apply, or changes make the rationale moot, the guidelines can safely be ignored.
## **General Principles**
### **Consistency is king**
Above all other principles, be consistent.
If a single file all follows one convention, just keep following the convention. Separate style changes from logic changes.
**Rationale**: If same thing is named differently (`Apple`, `a`, `fruit`, `redThing`), it becomes hard to understand how they're related.
### **Readability above efficiency**
Prefer readable code over fewer lines of cryptic code.
**Rationale**: Code will be read many more times than it will be written, by different people. Different people includes you, only a year from now.
### **All code is either obviously right, or non-obviously wrong.**
Almost all code should strive to be obviously right at first glance. It shouldn't strike readers as "somewhat odd", and need detailed study to read and decipher.
**Rationale**: If code is obviously right, it's probably right. The goal is to have suspicious code look suspiciously wrong and stick out like a sore thumb. This happens when everything else is very clear, and there's a tricky bit that needs to be worked on.
*Corollary*: Code comments are a sign that the code isn't particularly well explained via the code itself. Either through naming, or ordering, or chunking of the logic. Both code and comments have maintenance cost, but comments don't explicitly do work, and often go out of sync with associated code. While not explicitly disallowed, strive to make code require almost no comments.
Good cases to use comments include describing **why** the code is written that way (if naming, ordering, scoping, etc doesn't work) or explaining details which couldn't be easily explained in code (e.g., which algorithm/pattern/approach is used and why).
*Exception*: API interfaces *should* be commented, as close to code as possible for keeping up to date easily.
**Further Reading**: https://www.joelonsoftware.com/2005/05/11/making-wrong-code-look-wrong/
### **Boring is best**
Make your code the most boring version it could be.
**Rationale**: While you may have won competitions in code golf, the goal of production code is NOT to have the smartest code that only geniuses can figure out, but that which can easily be maintained. On the other hand, devote your creativity to making interesting test cases with fun constant values.
### **Split implementation from interface**
Storage, presentation, communication protocols should always be separate.
**Rationale**: While the content may coincidentally look the same, all these layers have different uses. If you tie things in the wrong place, then you will break unintentionally in non-obvious bad ways.
### **Split "policy" and "mechanics"**
Always separate the configuration/policy ("the why") from the implementation/mechanics ("the how").
**Rationale**: You can test the implementation of what needs to be done. You can also test the policy triggers at the right time. Turning a feature on and off makes it much easier to throw in more features and later turn them on/off and canary.
**Corollary**: Create separate functions for "doing" and for "choosing when to do".
**Corollary**: Create flags for all implementation features.
# **Deficiency Documentation (`TODO`s and `FIXME`s)**
### **`TODO` comments**
Use `TODO` comments *liberally* to describe anything that is potentially missing.
Code reviewers can also liberally ask for adding `TODO`s to different places.
Format:
`// TODO[(Context)]: <Action> by/when <Deadline Condition>`
`TODO` comments should have these parts:
- **Context** - (*Optional*) JIRA issue, etc. that can describe what this thing means better.
- Issues or other documentation should be used when the explanations are pretty long or involved.
- Code reviewers should verify that important `TODO`s have filed JIRA Issues.
- Examples:
- `CARE-XXX` - Issue description
- **Action** - Very specific actionable thing to be done. Explanations can come after the particular action.
- Examples:
- `Refactor into single class...`
- `Add ability to query Grafana...`
- `Replace this hack with <description> ...`
- **Deadline Condition** - when to get the thing done by.
- Deadline Conditions should **NOT** be relied on to *track* something done by a time or milestone.
- Examples:
- `... before General Availability release.`
- `... when we add <specific> capability.`
- `... when XXX bug/feature is fixed.`
- `... once <person> approves of <specific thing>.`
- `... when first customer asks for it.`
- Empty case implies "`...when we get time`". Use *only* for relatively unimportant things.
**Rationale**: `TODO` comments help readers understand what is missing. Sometimes you know what you're doing is not the best it could be, but is good enough. That's fine. Just explain how it can be improved.
Feel free to add `TODO` comments as you edit code and read other code that you interact with. If you don't understand something, add `TODO` to document how it might be better, so others may be able to help out.
Good Examples:
`// TODO: Replace certificate with staging version once we get letsencrypt to work.
// TODO(CARE-XXX): Replace old logic with new logic when out of experimental mode.
// TODO(SCIENCE-XXX): Colonize new planets when we get cold fusion capability.`
Mediocre examples(lacks Deadline Condition) - Good for documenting, but not as important:
`// TODO: Add precompiling templates here if we need it.
// TODO: Remove use of bash.
// TODO: Clean up for GetPatient/GetCaseWorker, which might be called from http handlers separately.
// TODO: Figure out how to launch spaceships instead of rubber duckies.`
Bad examples:
`// TODO: wtf? (what are we f'ing about?)
// TODO: We shouldn't do this. (doesn't say what to do instead, or why it exists)
// TODO: err...`
### **`FIXME` comments**
Use `FIXME` comments as **stronger** `TODO`s that **MUST** be done before code submission. These comments are **merge-blocking**.
`FIXME` should be liberally used for notes during development, either for developer or reviewers, to remind and prompt discussion. Remove these comments by fixing them before submitting code.
During code review, reviewer *may* suggest converting `FIXME` -> `TODO`, if it's not important to get done before getting something submitted. Then [`TODO` comment](https://github.com/MindStrongHealth/experimental/blob/master/users/teejae/coding-style.md#todo-comments) formatting applies.
Format (same as [`TODO` comments](https://github.com/MindStrongHealth/experimental/blob/master/users/teejae/coding-style.md#todo-comments), but more relaxed):
`// FIXME: <Action or any note to self/reviewer>
// FIXME: Remove hack
// FIXME: Revert hardcoding of server URL
// FIXME: Implement function
// FIXME: Refactor these usages across codebase. <Reviewer can upgrade to TODO w/ JIRA ticket during review>
// FIXME: Why does this work this way? <Reviewer should help out here with getting something more understandable>`
**Rationale**: These are great self-reminders as you code that you haven't finished something, like stubbed out functions, unimplemented parts, etc. The reviewer can also see the `FIXME`s to eye potential problems, and help out things that are not understandable, suggesting better fixes.
# **Code**
## **Naming**
### **Variables should always be named semantically.**
Names of variables should reflect their content and intent. Try to pick very specific names. Avoid adding parts that don't add any context to the name. Use only well-known abbreviations, otherwise, don't shorten the name in order to save a couple of symbols.
```
// Bad
input = "123-4567"
dialPhoneNumber(input) // unclear whether this makes semantic sense.
// Good
phoneNumber = "123-4567"
dialPhoneNumber(phoneNumber) // more obvious that this is intentional.
// Bad
text = 1234
address = "http://some-address/patient/" + text // why is text being added to a string?
// Good
patientId = 1234
address = "http://some-address/patient/" + patientId // ah, a patient id is added to an address.
```
**Rationale**: Good semantic names make bugs obvious and expresses intention, without needing lots of comments.
### **Always add units for measures.**
Time is especially ambiguous.
Time intervals (duration): `timeoutSec`, `timeoutMs`, `refreshIntervalHours`
Timestamp (specific instant in time): `startTimestamp`. (Use language-provided representations once inside code, rather than generic `number`, `int` for raw timestamps. JS/Java: `Date`, Python: `datetime.date/time`, Go: `time.Time`)
Distances: `LengthFt`, `LengthMeter`, `LengthCm`, `LengthMm`
Computer Space: `DiskMib` (1 Mebibyte is 1024 Kibibytes), `RamMb` (1 Megabyte is 1000 Kilobytes)
```
// Bad
Cost = Disk * Cents
// Good
CostCents = DiskMib * 1024 * CentsPerKilobyte
```
**Rationale**: Large classes of bugs are avoided when you name everything with units.
## **Constants**
### **All literals should be assigned to constants (or constant-like treatments).**
Every string or numeric literal needs to be assigned to a constant.
**Exceptions**: Identity-type zero/one values: `0`, `1`, `-1`, `""`
**Rationale**: It is never obvious why random number or string literals appear in different places. Even if they are somewhat obvious, it's hard to debug/find random constants and what they mean unless they are explicitly defined. Looking at collected constants allows the reader to see what is important, and see tricky edge cases while spelunking through the rest of the code.
# **Tests**
All commentary in the Code section applies here as well, with a few relaxations.
### **Repetitive test code allowed**
In general, do not repeat yourself. However, IF the test code is clearer, it's ok to repeat.
**Rationale**: Readability above all else. Sometimes tests are meant to test lots of niggling nefarious code, so we make exceptions for those cases.
### **Small Test Cases**
Make test cases as small and targeted as possible.
**Rationale**: Large tests are both unwieldy to write, and hard to debug. If something takes lots of setup, it's usually a sign of a design problem with the thing you're testing. Try breaking up the code/class/object into more manageable pieces.
### **No complex logic**
Avoid adding complex logic to test cases. It's more likely to have a bug in this case, while the purpose of the test cases is to prevent bugs. It's better to [repeat](https://github.com/MindStrongHealth/experimental/blob/master/users/teejae/coding-style.md#repetitive-test-code-allowed) or use a helper function covered with test cases.
### **Be creative in the content, but *not* the variable names.**
Just as for regular code, name variables for how they will be used. Intent is unclear when placeholders litter the code.
Use creative values for testing that don't look too "normal", so maintainers can tell values are obviously test values.
```
// Bad
login("abc", "badpassword") // Are "abc" and "badpassword" important?
testMemberId = "NA12312412" // Looks too "real", and unclear if it needs to follow this form
// Good
testMemberId = "some random member id"
testName = "abc"
testPassword = "open sesame"
testBadPassword = "really bad password! stop it!"
login(testName, testPassword) // Success
login(testName, testBadPassword) // Failure
```
**Rationale**: When the names of the variables are obvious, it becomes clear what is important in the tests.
### **No "spooky action at a distance"**
Collect all related logic/conditions into a single place, so it's easy to grasp how the different parts are related.
```
// Bad
startingInvestmentDollars = 100
invest()
... lots of test code ...
invest()
loseMoney()
expect(investment == 167) // Why 167?
// Good
startingInvestmentDollars = 100
returnInterestRatePercent = 67
endingInvestmentDollars = 167 // More obvious where 167 comes from.
... lots of test code ...
expect(investment == endingInvestmentDollars)
```
**Rationale**: When all related things are collected in a single place, you can more clearly understand what you think you'll read. The rest is just checking for mechanics.
Context File: Writing Good Interfaces
# Why?
We should first ask ourselves why (or if) spending the time to write good interfaces matters. Is it really worth the investment in a fast paced environment (e.g. startup)? We think yes:
- Well designed interfaces result in localized changes [9]
- Well designed interfaces result in less "support" [5]
- Well designed interfaces are easier to learn for new teammates [3]
The essence is that good interfaces are *more scalable* than bad interfaces (in terms of people working on the API and people using it). Consequently, it can be important for a high-growth environment.
# What?
If we agree on the value of good interfaces, we should then talk about what makes an interface good or bad, and whether there are objective criteria for determining the goodness of an interface.
The first thing to get out of the way is that there is no perfect interface and how good an interface is can only be determined within the scope of use cases. For example, reading text via a byte stream is a poor interface for string processing, but could be a great interface for e.g. finding the first 0 bit.
Good interfaces generally satisfy many of the following properties:
- Easy to use correctly
- Hard to use incorrectly
- Unsurprising
- Reports actionable errors
- Fails fast
- Minimal boilerplate
- Consistent mental model or theory [3]
- Extensible (e.g. via plugins, inheritance, etc)
- Limited mutability
- Small (i.e. does "one" thing)
- Well documented
- No implementation details leaked
- Easy to learn and memorize
## Examples
Here we have a few examples of good and bad APIs
- **Python defaultdict**
It's common to group a collection by some key. We often see folks write python code such as:
```python
d = {}
for word in words:
first_letter = word[0]
if first_letter in d:
d[first_letter].append(word)
else:
d[first_letter] = [word]
```
But if we use`defaultdict` instead, we can see it's a much better fit for our use case:
```python
d = defaultdict(list)
for word in words:
first_letter = word[0]
d[first_letter].append(word)
```
Important point is that python dicts are not necessarily a "bad API" — but when we consider our use case here, the standard dict API doesn't quite meet the mark!
- **Avoiding Roundtrips**
Often, some APIs expose implementation or storage details of data. For example, if we wanted to fetch messages for users, it's not uncommon to see an API such as:
```java
Collection<MessageId> messageIds = getUsers(userIds).messages;
Collection<Message> messages = getMessages(messageIds);
```
The reason APIs tend to look like this is that they generally just reflect how our services and such are organized. Note: code architecture and company org structure tend to change over time. Tying APIs to the data layout (rather than how it's consumed) is a recipe for a brittle API. Instead just give users what they want in a single step:
```java
Collection<Message> messages = getMessagesForUsers(userIds);
```
- **Limit Mutability**
In general, it's preferable to use immutable objects. Consider the following example:
```java
Map<String, String> m = new HashMap<>();
m.put("a", "b");
int z = someFunc(m);
assert m.containsKey("a"); // Does this pass or fail?
```
When objects are mutable, any where they are passed is a place where it is potentially mutated. Mutable objects make it difficult to locally reason about code and generally results in situations where to understand *anything* you have to understand *everything* . I don't know about you, but I'm generally not smart enough to understand everything 😉. A better option would be to have `someFunc` take an `ImmutableMap` so that readers don't need to understand the function to know what the value of `m` might be later in the code!
- **Give Good Errors**
We often see errors in the wild which don't provide a lot of information; for example:
```python
IndexError: index out of range
```
Good errors should have [7]:
1. What input was wrong
2. What is wrong about it
3. How to fix it (or next steps for investigation)
A better error message here would be:
```python
IndexError: index=7 out of range for object (len=6) with items (showing first 3): 'apple', 'strawberry', 'banana', ...
Did you forget to check that your index was not beyond the length of the object?
```
- **Returning Exceptional Values**
In many cases, there's not a difference between returning something like `null` vs an empty collection. Consider the following interface:
```java
// Returns null if no known favorite numbers
List<Integer> getFavoriteNumbers(String name);
// Example usage:
List<Integer> gregFave = getFavoriteNumbers("greg");
if (gregFave != null) {
gregFave.forEach(System.out.println);
}
```
It may be better to return an empty List instead of null to simplify a user's code here. Note, that's not to say you should never return null. There are many cases where there is a legitimate and well-intentioned difference between null and empty List.
- **Make the interface hard to mis-use**
Users shouldn't easily mis-use your API. One common error prone practice in APIs is initializing resources which callers are expected to release. For example:
```python
f = open('myfile.txt', 'r')
f.write("blah")
f.close()
```
It's really easy to forget that `close` ! Especially when we consider the possibility of exceptions that may occur between open and close. There are generally language-specific idioms to handle this; such as context managers in python:
```python
with open('myfile.txt', 'r') as f:
f.write("blah")
# even if error occurs, the file context manager will ensure file is closed
```
or in Java, the analogous pattern is [try with resources](https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html).
See "How" section for some tips, tricks, and best practices for designing good APIs.
# Who? When? Where?
Every engineer writes interfaces [2, 5]. As a guideline, you should always try to write good interfaces. However, it is only a guideline; there are of course legitimate reasons to ignore the guideline. However, the guideline should be ignored consciously rather than haphazardly.
If you are choosing to build a worse-than-you-could API, consider the following actions to limit the effect:
- Use a non-public access modifier
- Document the costs or problems with the interface
- Mark the interface as experimental
# How?
We generally refer readers to Sean Parent [1], Scott Meyers [2], Joshua Bloch [5], and Jasmin Blanchette [8] for tips on writing good interfaces, but we leave a summary of advice below:
1. Get use cases to inform requirements
- Drive the design based on use cases; remember, an interface can only be good within the context of use cases!
2. Get a tight feedback loop with users
- Write the API first (without implementation), make many examples and share with potential users. Bonus points if you yourself will be a user
- These examples can later become test cases
- Users are often ambivalent; find people who will use the API and provide you necessary/critical feedback
3. Keep the API as small as possible
- Hyrum's Law: all observable behaviors of your implementation will be depended on by someone
4. Design for extensibility
- Think about what users can extend via inheritance; block inheritance if your API is not designed for it
- Think about what hooks/callbacks might be valuable
- Accept interfaces instead of implementations in functions/methods
5. Keep implementation details out of the interface (where possible)
- If you must include implementation details, keep it as separated as possible and use "scary" names to denote that users should generally stay away (e.g. `ImplementationSpecificParameters` )
6. Document every public entity: classes, methods, functions, variables
- Bonus points for including example usage
- Documentation should explain the "why"
7. Make errors actionable
8. Limit mutability
# References
1. [Better Code: Relationships](https://www.youtube.com/watch?v=ejF6qqohp3M) (Sean Parent, Adobe)
2. [The Most Important Design Guideline](https://www.aristeia.com/Papers/IEEE_Software_JulAug_2004_revised.htm) (Scott Meyers, Effective C++)
3. [Programming as Theory Building](https://gist.github.com/dpritchett/fd7115b6f556e40103ef) (Peter Naur, Turing Award)
4. [You Can't Tell People Anything](http://habitatchronicles.com/2004/04/you-cant-tell-people-anything/) (Chip Morningstar, industry veteran)
5. [How to Design a Good API and Why it Matters](https://www.youtube.com/watch?v=aAb7hSCtvGw) (Joshua Bloch, Effective Java)
6. [The Mythical Man-Month](https://web.eecs.umich.edu/~weimerw/2018-481/readings/mythical-man-month.pdf) (Fred Brooks, Turing Award)
7. [What makes a good API?](https://medium.com/@rkuris/good-apis-cd861b8b70a3) (Ron Kuris, industry veteran)
8. [The Little Manual of API Design](https://web.archive.org/web/20090520234149/http://chaos.troll.no/~shausman/api-design/api-design.pdf) (Jasmin Blanchette, Nokia / Qt)
9. [Coupling (Wikipedia)](https://en.wikipedia.org/wiki/Coupling_(computer_programming)#Disadvantages_of_tight_coupling)
Context File: Other
Lastly, I often include API docs for various tools I use in the project (e.g. uv
).
Conclusion
I hope this helps! I am always looking for ways to improve my workflow, and I am happy to share what works for me.
Let me know on X or LinkedIn() how this worked for you, or if you have other tips that you found helpful!