Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
Tradeoff: These guidelines bias toward caution over speed. For trivial tasks, use judgment.
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 overcomplicated?" 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 pre-existing 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 multi-step tasks, state a brief plan:
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
These guidelines are working if: fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
- Target macOS 15.2 or later.
- Swift 6.2 or later, using modern Swift concurrency.
- SwiftUI backed up by
@Observableclasses for shared data. - Do not introduce third-party frameworks without asking first.
- Avoid UIKit unless requested.
- Always mark
@Observableclasses with@MainActor. - Assume strict Swift concurrency rules are being applied.
- Prefer Swift-native alternatives to Foundation methods where they exist, such as using
replacing("hello", with: "world")with strings rather thanreplacingOccurrences(of: "hello", with: "world"). - Prefer modern Foundation API, for example
URL.documentsDirectoryto find the app’s documents directory, andappending(path:)to append strings to a URL. - Never use C-style number formatting such as
Text(String(format: "%.2f", abs(myNumber))); always useText(abs(change), format: .number.precision(.fractionLength(2)))instead. - Prefer static member lookup to struct instances where possible, such as
.circlerather thanCircle(), and.borderedProminentrather thanBorderedProminentButtonStyle(). - Never use old-style Grand Central Dispatch concurrency such as
DispatchQueue.main.async(). If behavior like this is needed, always use modern Swift concurrency. - Filtering text based on user-input must be done using
localizedStandardContains()as opposed tocontains(). - Avoid force unwraps and force
tryunless it is unrecoverable.
- Always use
foregroundStyle()instead offoregroundColor(). - Always use
clipShape(.rect(cornerRadius:))instead ofcornerRadius(). - Always use the
TabAPI instead oftabItem(). - Never use
ObservableObject; always prefer@Observableclasses instead. - Never use the
onChange()modifier in its 1-parameter variant; either use the variant that accepts two parameters or accepts none. - Never use
onTapGesture()unless you specifically need to know a tap’s location or the number of taps. All other usages should useButton. - Never use
Task.sleep(nanoseconds:); always useTask.sleep(for:)instead. - Never use
UIScreen.main.boundsto read the size of the available space. - Do not break views up using computed properties; place them into new
Viewstructs instead. - Do not force specific font sizes; prefer using Dynamic Type instead.
- Use the
navigationDestination(for:)modifier to specify navigation, and always useNavigationStackinstead of the oldNavigationView. - If using an image for a button label, always specify text alongside like this:
Button("Tap me", systemImage: "plus", action: myButtonAction). - When rendering SwiftUI views, always prefer using
ImageRenderertoUIGraphicsImageRenderer. - Don’t apply the
fontWeight()modifier unless there is good reason. If you want to make some text bold, always usebold()instead offontWeight(.bold). - Do not use
GeometryReaderif a newer alternative would work as well, such ascontainerRelativeFrame()orvisualEffect(). - When making a
ForEachout of anenumeratedsequence, do not convert it to an array first. So, preferForEach(x.enumerated(), id: \.element.id)instead ofForEach(Array(x.enumerated()), id: \.element.id). - When hiding scroll view indicators, use the
.scrollIndicators(.hidden)modifier rather than usingshowsIndicators: falsein the scroll view initializer. - Place view logic into view models or similar, so it can be tested.
- Avoid
AnyViewunless it is absolutely required. - Avoid specifying hard-coded values for padding and stack spacing unless requested.
- Avoid using UIKit colors in SwiftUI code.
If SwiftData is configured to use CloudKit:
- Never use
@Attribute(.unique). - Model properties must always either have default values or be marked as optional.
- All relationships must be marked optional.
- Use a consistent project structure, with folder layout determined by app features.
- Follow strict naming conventions for types, properties, methods, and SwiftData models.
- Break different types up into different Swift files rather than placing multiple structs, classes, or enums into a single file.
- Write unit tests for core application logic.
- Only write UI tests if unit tests are not possible.
- Add code comments and documentation comments as needed.
- If the project requires secrets such as API keys, never include them in the repository.
- When working on a GitHub issue, use
gh issue develop <issue-number> --checkoutto create and checkout a branch with consistent naming. - Use conventional commit format for all commits:
type(scope): subject- Types:
feat,fix,docs,style,refactor,test,chore - Example:
feat(menu-bar): add open output folder button - Keep subject line under 50 characters when possible
- Use present tense ("add" not "added")
- Types:
- If installed, make sure SwiftLint returns no warnings or errors before committing.