Skip to content

Commit 56e571e

Browse files
committed
feat: add 2 new rules (27 total) - verify-before-use, context-management + major upgrades to ai-collaboration and error-handling
- verify-before-use.mdc: Forces type checking, file re-reading, schema verification (inspired by Claude Code's skeptical retrieval) - context-management.mdc: Prevents context bloat in long sessions, sub-task splitting, structured summarization - ai-collaboration.mdc: Major rewrite with ActionResponse pattern, verification checklist, skeptical retrieval - error-handling.mdc: Added ActionResponse type, explicit no-leak rule, Supabase error patterns - Updated all counts: 25 -> 27 across README.md, llms.txt
1 parent 4b50e58 commit 56e571e

6 files changed

Lines changed: 343 additions & 39 deletions

File tree

.cursor/rules/ai-collaboration.mdc

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,105 @@
11
---
22
description: Instructions for AI agent code generation workflow
33
globs: ["*"]
4+
alwaysApply: true
45
---
56

67
# AI Collaboration Guidelines
78

8-
The 3-stage agentic loop:
9+
## The 3-Stage Agentic Loop
910

10-
1. **PLAN:**
11-
"Analyze the current codebase and plan how to implement [feature]. List the files you'll modify and why. Don't write code yet."
11+
Every code generation task MUST follow this loop:
1212

13-
2. **IMPLEMENT:**
14-
"Implement the plan. Follow all `.cursor/rules/` guidelines. If you're unsure about auth or RLS, check `supabase-auth-security.mdc`."
13+
### Stage 1: PLAN (gather context first)
1514

16-
3. **REVIEW:**
17-
"Review what you just generated. Check for:
18-
- `getSession()` usage (MUST be `getUser()`)
19-
- Missing error boundaries or `loading.tsx`
20-
- Next.js 15 async layout/page params
21-
- TypeScript `any` types (use `unknown` or define interface)
22-
Report any issues before I run it."
15+
Before writing ANY code, the AI MUST:
16+
1. Read the relevant source files to verify current state — do NOT rely on memory
17+
2. Identify which `.cursor/rules/` files apply to this task
18+
3. List the files to modify and explain why
19+
4. Do NOT write code yet
20+
21+
Prompt template:
22+
```
23+
Analyze the current codebase and plan how to implement [feature].
24+
List the files you'll modify and why. Don't write code yet.
25+
```
26+
27+
### Stage 2: IMPLEMENT (follow constraints)
28+
29+
When generating code:
30+
- Follow ALL active `.cursor/rules/` constraints
31+
- If unsure about auth patterns, re-read `supabase-auth-security.mdc`
32+
- If unsure about params/layouts, re-read `nextjs15-params.mdc`
33+
- Use `unknown` + Zod validation instead of `any` for external data
34+
- Return `ActionResponse<T>` from all Server Actions (see below)
35+
36+
### Stage 3: VERIFY (confirm before declaring done)
37+
38+
After generating code, the AI MUST self-check:
39+
- [ ] No `getSession()` on the server (MUST be `getUser()`)
40+
- [ ] No synchronous `params` or `searchParams` access
41+
- [ ] No `@supabase/auth-helpers-nextjs` imports
42+
- [ ] No `any` types — use `unknown` + type narrowing
43+
- [ ] Every API route has Zod validation
44+
- [ ] Every error is caught and returns `{ error: string }`
45+
- [ ] New tables have RLS enabled
46+
- [ ] `loading.tsx` and `error.tsx` exist for data-fetching pages
47+
48+
Report issues BEFORE the developer runs the code.
49+
50+
## Skeptical Retrieval Rule
51+
52+
NEVER trust stored knowledge about the codebase. Before modifying a file:
53+
1. Re-read the file to verify its current contents
54+
2. Check imports and exports to ensure they match expectations
55+
3. Verify that function signatures haven't changed
56+
57+
This prevents generating code that references stale function signatures,
58+
deleted files, or renamed exports.
59+
60+
## Standard Patterns
61+
62+
### ActionResponse Type (use for all Server Actions)
63+
```typescript
64+
type ActionResponse<T = void> =
65+
| { success: true; data: T }
66+
| { success: false; error: string }
67+
```
68+
69+
### Standard Server Action Structure
70+
```typescript
71+
'use server'
72+
import { createClient } from '@/lib/supabase/server'
73+
import { z } from 'zod'
74+
75+
const Schema = z.object({ /* ... */ })
76+
77+
export async function myAction(input: unknown): Promise<ActionResponse<MyType>> {
78+
// 1. Auth
79+
const supabase = await createClient()
80+
const { data: { user } } = await supabase.auth.getUser()
81+
if (!user) return { success: false, error: 'Unauthorized' }
82+
83+
// 2. Validate
84+
const result = Schema.safeParse(input)
85+
if (!result.success) return { success: false, error: 'Invalid input' }
86+
87+
// 3. Execute
88+
try {
89+
const { data, error } = await supabase.from('table').insert(result.data).select().single()
90+
if (error) throw error
91+
return { success: true, data }
92+
} catch {
93+
return { success: false, error: 'Operation failed' }
94+
}
95+
}
96+
```
97+
98+
## Task Constraints
99+
100+
- NEVER give the AI a task longer than 200 words
101+
- NEVER let the AI implement more than 3-4 files in one turn
102+
- NEVER let the AI skip the verification checklist
103+
- ALWAYS review the Git diff before committing
104+
- When uncertain, ask the developer — do NOT guess
23105

24-
Constraints:
25-
- NEVER give the AI a task longer than 200 words.
26-
- NEVER let the AI implement more than 3-4 files in one turn.
27-
- ALWAYS review the Git diff before committing.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
description: Context window management — prevents AI from losing track during long sessions
3+
globs: ["*"]
4+
alwaysApply: false
5+
---
6+
7+
# Context Management Rules
8+
9+
## RULE 1: Break Large Tasks Into Sub-Tasks
10+
11+
If a feature requires changes to more than 4 files, break it into sequential sub-tasks.
12+
Complete and verify each sub-task before starting the next.
13+
14+
Example — "Add a blog feature with CRUD":
15+
1. Sub-task 1: Database migration + RLS policies
16+
2. Sub-task 2: Server Actions (create, read, update, delete)
17+
3. Sub-task 3: UI components (list, detail, form)
18+
4. Sub-task 4: Route handlers + API endpoints
19+
5. Sub-task 5: Integration testing
20+
21+
## RULE 2: Summarize Before Continuing
22+
23+
If the conversation has been going for more than 5 turns, summarize
24+
what has been done and what remains before generating more code.
25+
26+
Template:
27+
```
28+
Before continuing, let me summarize:
29+
✅ Completed: [list of completed items]
30+
🔄 In progress: [current item]
31+
📋 Remaining: [list of remaining items]
32+
```
33+
34+
## RULE 3: Reference Files By Full Path
35+
36+
When discussing code, ALWAYS use the full path relative to the project root.
37+
This prevents confusion between similarly named files.
38+
39+
✅ CORRECT: "Update `src/app/dashboard/page.tsx`"
40+
❌ WRONG: "Update the dashboard page"
41+
42+
✅ CORRECT: "Import from `@/lib/supabase/server`"
43+
❌ WRONG: "Import the Supabase client"
44+
45+
## RULE 4: One Concern Per Message
46+
47+
Each AI message should address ONE concern clearly:
48+
- ONE file modification with before/after
49+
- ONE bug fix with explanation
50+
- ONE new feature with complete implementation
51+
52+
Do NOT mix unrelated changes in a single response. This makes
53+
review harder and increases the chance of introducing bugs.
54+
55+
## RULE 5: Keep Rule Files Under 150 Lines
56+
57+
Individual `.mdc` rule files should stay under 150 lines.
58+
If a rule file grows beyond that, split it into focused sub-rules.
59+
60+
This mirrors the principle from production AI harnesses:
61+
keep the index lightweight, put details in separate files.

.cursor/rules/error-handling.mdc

Lines changed: 108 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,135 @@
11
---
2-
description: Error boundaries and loading states guidelines
2+
description: Error boundaries, loading states, and server error handling
33
globs: ["**/app/**"]
44
---
55

66
# Error Handling and Loading States
77

8-
EVERY page in `app/` router MUST have sibling files if it does data fetching:
9-
- `loading.tsx` (Suspense boundary)
8+
## RULE 1: Every Data-Fetching Page Needs Sibling Files
9+
10+
EVERY page in `app/` router that does data fetching MUST have:
11+
- `loading.tsx` (Suspense boundary — shown during streaming)
1012
- `error.tsx` (Error boundary — must be `'use client'`)
1113

1214
`loading.tsx` template:
1315
```tsx
1416
export default function Loading() {
15-
return <div className="animate-pulse">Loading...</div>
17+
return (
18+
<div className="flex items-center justify-center min-h-[200px]">
19+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
20+
</div>
21+
)
1622
}
1723
```
1824

1925
`error.tsx` template:
2026
```tsx
2127
'use client'
22-
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
28+
29+
export default function Error({
30+
error,
31+
reset
32+
}: {
33+
error: Error & { digest?: string }
34+
reset: () => void
35+
}) {
2336
return (
24-
<div>
25-
<h2>Something went wrong</h2>
26-
<button onClick={reset}>Try again</button>
37+
<div className="flex flex-col items-center justify-center min-h-[200px] gap-4">
38+
<h2 className="text-xl font-semibold">Something went wrong</h2>
39+
<p className="text-muted-foreground">An unexpected error occurred.</p>
40+
<button
41+
onClick={reset}
42+
className="px-4 py-2 bg-primary text-primary-foreground rounded-md"
43+
>
44+
Try again
45+
</button>
2746
</div>
2847
)
2948
}
3049
```
3150

32-
Server Action error pattern:
51+
## RULE 2: NEVER Expose Internal Errors to Clients
52+
53+
Server errors, database errors, and stack traces MUST stay server-side.
54+
Return generic messages to the client. Log the real error.
55+
56+
✅ CORRECT:
57+
```typescript
58+
try {
59+
const { data, error } = await supabase.from('posts').select()
60+
if (error) throw error
61+
return data
62+
} catch (error) {
63+
console.error('[POSTS_FETCH_ERROR]', error) // Detailed log server-side
64+
return NextResponse.json(
65+
{ error: 'Failed to fetch posts' }, // Generic message to client
66+
{ status: 500 }
67+
)
68+
}
69+
```
70+
71+
❌ WRONG:
72+
```typescript
73+
} catch (error) {
74+
return NextResponse.json(
75+
{ error: error.message }, // Leaks: "relation 'posts' does not exist"
76+
{ status: 500 }
77+
)
78+
}
79+
```
80+
81+
## RULE 3: Use ActionResponse for All Server Actions
82+
83+
Every Server Action MUST return a typed response — never throw.
84+
3385
```typescript
34-
export async function createItem(formData: FormData) {
35-
try {
36-
// ... operation
37-
return { success: true }
38-
} catch (error) {
39-
return { error: 'Failed to create item' }
86+
type ActionResponse<T = void> =
87+
| { success: true; data: T }
88+
| { success: false; error: string }
89+
90+
// Usage:
91+
export async function createPost(input: unknown): Promise<ActionResponse<Post>> {
92+
const supabase = await createClient()
93+
const { data: { user } } = await supabase.auth.getUser()
94+
if (!user) return { success: false, error: 'Unauthorized' }
95+
96+
const parsed = PostSchema.safeParse(input)
97+
if (!parsed.success) return { success: false, error: 'Invalid input' }
98+
99+
const { data, error } = await supabase
100+
.from('posts')
101+
.insert({ ...parsed.data, user_id: user.id })
102+
.select()
103+
.single()
104+
105+
if (error) {
106+
console.error('[CREATE_POST_ERROR]', error)
107+
return { success: false, error: 'Failed to create post' }
40108
}
109+
110+
revalidatePath('/dashboard')
111+
return { success: true, data }
41112
}
42113
```
114+
115+
## RULE 4: Handle Supabase Errors Explicitly
116+
117+
NEVER ignore the `error` field from Supabase queries.
118+
119+
✅ CORRECT:
120+
```typescript
121+
const { data, error } = await supabase.from('posts').select()
122+
if (error) {
123+
console.error('[SUPABASE_ERROR]', error.message, error.code)
124+
throw new Error('Database query failed')
125+
}
126+
// data is now safely typed as non-null
127+
```
128+
129+
❌ WRONG:
130+
```typescript
131+
const { data } = await supabase.from('posts').select()
132+
// data could be null if query failed — using it causes runtime crash
133+
return data.map(post => post.title) // TypeError: Cannot read property 'map' of null
134+
```
135+

0 commit comments

Comments
 (0)