Spaces:
Sleeping
Sleeping
Commit Β·
b6e19c7
0
Parent(s):
base structure of the project formed
Browse files- .claude/skills/debugging.md +157 -0
- .claude/skills/supabase.md +277 -0
- .claude/skills/testing.md +220 -0
- .gitattributes +2 -0
- README.md +0 -0
- claude.md +80 -0
- docs/architecture.md +123 -0
- docs/debugging.md +126 -0
- docs/deployment.md +255 -0
- docs/index.md +78 -0
- docs/learnings.md +56 -0
- docs/prd.md +244 -0
- docs/progress.md +58 -0
- docs/testing.md +199 -0
- docs/workflows/bugfix.md +152 -0
- docs/workflows/feature.md +121 -0
- docs/workflows/refactor.md +222 -0
- logs/errors.log +0 -0
- logs/test.log +0 -0
- prompts/phase-0.md +228 -0
- prompts/phase-1.md +285 -0
- prompts/phase-2.md +266 -0
- prompts/phase-3.md +250 -0
- prompts/phase-4.md +214 -0
- prompts/phase-5.md +293 -0
- prompts/phase-index.md +105 -0
- scripts/deploy.sh +0 -0
- scripts/dev.sh +0 -0
- scripts/test.sh +0 -0
- session/context.md +56 -0
- session/phase-log.md +31 -0
- session/summary.md +57 -0
.claude/skills/debugging.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill β Debugging Patterns by Error Type
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
Read this file when stuck on a specific error type.
|
| 5 |
+
Contains reusable debug snippets organised by error category.
|
| 6 |
+
Always check logs/errors.log first before opening this file.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Null & Undefined Errors
|
| 11 |
+
|
| 12 |
+
### Pattern
|
| 13 |
+
```typescript
|
| 14 |
+
// Guard at function entry β always first line
|
| 15 |
+
function myFunction(input: string | null) {
|
| 16 |
+
if (!input) {
|
| 17 |
+
handleError(new Error('input is null'), 'file.ts', 'myFunction')
|
| 18 |
+
return null
|
| 19 |
+
}
|
| 20 |
+
// safe to use input below
|
| 21 |
+
}
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
### Checklist
|
| 25 |
+
- [ ] Is the value null before it enters the function?
|
| 26 |
+
- [ ] Is the value undefined because an async call hasn't resolved?
|
| 27 |
+
- [ ] Is optional chaining `?.` missing somewhere?
|
| 28 |
+
- [ ] Is the DB returning null instead of empty array?
|
| 29 |
+
|
| 30 |
+
---
|
| 31 |
+
|
| 32 |
+
## Async & Promise Errors
|
| 33 |
+
|
| 34 |
+
### Pattern
|
| 35 |
+
```typescript
|
| 36 |
+
// Always await, always catch
|
| 37 |
+
async function fetchData() {
|
| 38 |
+
try {
|
| 39 |
+
const result = await someAsyncCall()
|
| 40 |
+
if (!result) throw new Error('result is empty')
|
| 41 |
+
return result
|
| 42 |
+
} catch (err) {
|
| 43 |
+
handleError(err, 'file.ts', 'fetchData')
|
| 44 |
+
return null
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
### Checklist
|
| 50 |
+
- [ ] Is `await` missing before an async call?
|
| 51 |
+
- [ ] Is a `.then()` chain missing a `.catch()`?
|
| 52 |
+
- [ ] Is a Promise being returned without awaiting it?
|
| 53 |
+
- [ ] Are two async calls racing without proper sequencing?
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
## Type Errors
|
| 58 |
+
|
| 59 |
+
### Pattern
|
| 60 |
+
```typescript
|
| 61 |
+
// Validate type at boundary before using
|
| 62 |
+
function processUser(user: unknown) {
|
| 63 |
+
if (!user || typeof user !== 'object') {
|
| 64 |
+
handleError(new Error('invalid user type'), 'file.ts', 'processUser')
|
| 65 |
+
return null
|
| 66 |
+
}
|
| 67 |
+
const typedUser = user as User
|
| 68 |
+
// safe to use typedUser below
|
| 69 |
+
}
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
### Checklist
|
| 73 |
+
- [ ] Is an `any` type hiding a real type mismatch?
|
| 74 |
+
- [ ] Is an API response being used without type validation?
|
| 75 |
+
- [ ] Is a number being used where a string is expected?
|
| 76 |
+
|
| 77 |
+
---
|
| 78 |
+
|
| 79 |
+
## React Render Errors
|
| 80 |
+
|
| 81 |
+
### Pattern
|
| 82 |
+
```typescript
|
| 83 |
+
// Guard before rendering
|
| 84 |
+
function UserCard({ user }: { user: User | null }) {
|
| 85 |
+
if (!user) return null // or return <Skeleton />
|
| 86 |
+
return <div>{user.name}</div>
|
| 87 |
+
}
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
### Checklist
|
| 91 |
+
- [ ] Is a component rendering before data is loaded?
|
| 92 |
+
- [ ] Is `useEffect` missing a dependency causing stale state?
|
| 93 |
+
- [ ] Is state being mutated directly instead of via setter?
|
| 94 |
+
- [ ] Is `useEffect` running twice due to React strict mode?
|
| 95 |
+
|
| 96 |
+
---
|
| 97 |
+
|
| 98 |
+
## API Route Errors
|
| 99 |
+
|
| 100 |
+
### Pattern
|
| 101 |
+
```typescript
|
| 102 |
+
// Always validate request body
|
| 103 |
+
export async function POST(req: Request) {
|
| 104 |
+
try {
|
| 105 |
+
const body = await req.json()
|
| 106 |
+
if (!body?.userId) {
|
| 107 |
+
return Response.json(
|
| 108 |
+
{ error: 'userId is required' },
|
| 109 |
+
{ status: 400 }
|
| 110 |
+
)
|
| 111 |
+
}
|
| 112 |
+
// proceed
|
| 113 |
+
} catch (err) {
|
| 114 |
+
handleError(err, 'app/api/route.ts', 'POST')
|
| 115 |
+
return Response.json({ error: 'internal server error' }, { status: 500 })
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
### Checklist
|
| 121 |
+
- [ ] Is the request body being parsed correctly?
|
| 122 |
+
- [ ] Is the auth session being checked before processing?
|
| 123 |
+
- [ ] Is a 400 returned for bad input vs 500 for server error?
|
| 124 |
+
- [ ] Is the API route method correct (GET vs POST)?
|
| 125 |
+
|
| 126 |
+
---
|
| 127 |
+
|
| 128 |
+
## Environment Variable Errors
|
| 129 |
+
|
| 130 |
+
### Pattern
|
| 131 |
+
```typescript
|
| 132 |
+
// Validate env vars at startup
|
| 133 |
+
const requiredEnvVars = [
|
| 134 |
+
'NEXT_PUBLIC_SUPABASE_URL',
|
| 135 |
+
'NEXT_PUBLIC_SUPABASE_ANON_KEY'
|
| 136 |
+
]
|
| 137 |
+
|
| 138 |
+
requiredEnvVars.forEach((key) => {
|
| 139 |
+
if (!process.env[key]) {
|
| 140 |
+
throw new Error(`Missing required env var: ${key}`)
|
| 141 |
+
}
|
| 142 |
+
})
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
### Checklist
|
| 146 |
+
- [ ] Is `.env.local` present and not committed?
|
| 147 |
+
- [ ] Are `NEXT_PUBLIC_` prefixes correct for client side vars?
|
| 148 |
+
- [ ] Are env vars available in the deployment environment?
|
| 149 |
+
- [ ] Was the dev server restarted after adding new env vars?
|
| 150 |
+
|
| 151 |
+
---
|
| 152 |
+
|
| 153 |
+
## What NOT to Do
|
| 154 |
+
- Do not add console.log and forget to remove it
|
| 155 |
+
- Do not catch an error and return undefined silently
|
| 156 |
+
- Do not assume the error is in a different file than the log says
|
| 157 |
+
- Do not fix the symptom without understanding the root cause
|
.claude/skills/supabase.md
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill β Supabase Patterns & Operations
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
Read this file when writing any Supabase code.
|
| 5 |
+
Contains reusable patterns, query templates and common gotchas.
|
| 6 |
+
Do not guess schema or policies β confirm first.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Client Setup
|
| 11 |
+
|
| 12 |
+
### Client initialisation (Next.js)
|
| 13 |
+
```typescript
|
| 14 |
+
// lib/supabase.ts
|
| 15 |
+
import { createClient } from '@supabase/supabase-js'
|
| 16 |
+
|
| 17 |
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
|
| 18 |
+
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
| 19 |
+
|
| 20 |
+
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
### Server side client (API routes / server components)
|
| 24 |
+
```typescript
|
| 25 |
+
// lib/supabase-server.ts
|
| 26 |
+
import { createClient } from '@supabase/supabase-js'
|
| 27 |
+
|
| 28 |
+
export const supabaseAdmin = createClient(
|
| 29 |
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
| 30 |
+
process.env.SUPABASE_SERVICE_ROLE_KEY!
|
| 31 |
+
)
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
---
|
| 35 |
+
|
| 36 |
+
## Auth Patterns
|
| 37 |
+
|
| 38 |
+
### Get current session
|
| 39 |
+
```typescript
|
| 40 |
+
const { data: { session }, error } = await supabase.auth.getSession()
|
| 41 |
+
if (!session) {
|
| 42 |
+
handleError(new Error('no active session'), 'file.ts', 'functionName')
|
| 43 |
+
return null
|
| 44 |
+
}
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
### Listen to auth state changes
|
| 48 |
+
```typescript
|
| 49 |
+
supabase.auth.onAuthStateChange((event, session) => {
|
| 50 |
+
if (event === 'SIGNED_OUT') {
|
| 51 |
+
// clear local state
|
| 52 |
+
}
|
| 53 |
+
if (event === 'TOKEN_REFRESHED') {
|
| 54 |
+
// update session in state
|
| 55 |
+
}
|
| 56 |
+
})
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
### Sign out
|
| 60 |
+
```typescript
|
| 61 |
+
const { error } = await supabase.auth.signOut()
|
| 62 |
+
if (error) handleError(error, 'file.ts', 'signOut')
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
---
|
| 66 |
+
|
| 67 |
+
## Query Patterns
|
| 68 |
+
|
| 69 |
+
### Select with error handling
|
| 70 |
+
```typescript
|
| 71 |
+
async function getRows(table: string, userId: string) {
|
| 72 |
+
try {
|
| 73 |
+
const { data, error } = await supabase
|
| 74 |
+
.from(table)
|
| 75 |
+
.select('*')
|
| 76 |
+
.eq('user_id', userId)
|
| 77 |
+
|
| 78 |
+
if (error) throw error
|
| 79 |
+
return data ?? []
|
| 80 |
+
} catch (err) {
|
| 81 |
+
handleError(err, 'lib/supabase.ts', 'getRows')
|
| 82 |
+
return []
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
### Insert with error handling
|
| 88 |
+
```typescript
|
| 89 |
+
async function insertRow(table: string, payload: object) {
|
| 90 |
+
try {
|
| 91 |
+
const { data, error } = await supabase
|
| 92 |
+
.from(table)
|
| 93 |
+
.insert(payload)
|
| 94 |
+
.select()
|
| 95 |
+
.single()
|
| 96 |
+
|
| 97 |
+
if (error) throw error
|
| 98 |
+
return data
|
| 99 |
+
} catch (err) {
|
| 100 |
+
handleError(err, 'lib/supabase.ts', 'insertRow')
|
| 101 |
+
return null
|
| 102 |
+
}
|
| 103 |
+
}
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
### Update with error handling
|
| 107 |
+
```typescript
|
| 108 |
+
async function updateRow(table: string, id: string, payload: object) {
|
| 109 |
+
try {
|
| 110 |
+
const { data, error } = await supabase
|
| 111 |
+
.from(table)
|
| 112 |
+
.update(payload)
|
| 113 |
+
.eq('id', id)
|
| 114 |
+
.select()
|
| 115 |
+
.single()
|
| 116 |
+
|
| 117 |
+
if (error) throw error
|
| 118 |
+
return data
|
| 119 |
+
} catch (err) {
|
| 120 |
+
handleError(err, 'lib/supabase.ts', 'updateRow')
|
| 121 |
+
return null
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
### Delete with error handling
|
| 127 |
+
```typescript
|
| 128 |
+
async function deleteRow(table: string, id: string) {
|
| 129 |
+
try {
|
| 130 |
+
const { error } = await supabase
|
| 131 |
+
.from(table)
|
| 132 |
+
.delete()
|
| 133 |
+
.eq('id', id)
|
| 134 |
+
|
| 135 |
+
if (error) throw error
|
| 136 |
+
return true
|
| 137 |
+
} catch (err) {
|
| 138 |
+
handleError(err, 'lib/supabase.ts', 'deleteRow')
|
| 139 |
+
return false
|
| 140 |
+
}
|
| 141 |
+
}
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
---
|
| 145 |
+
|
| 146 |
+
## Migration Patterns
|
| 147 |
+
|
| 148 |
+
### Standard migration file structure
|
| 149 |
+
```sql
|
| 150 |
+
-- supabase/migrations/[timestamp]_create_users.sql
|
| 151 |
+
|
| 152 |
+
-- Create table
|
| 153 |
+
CREATE TABLE IF NOT EXISTS users (
|
| 154 |
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 155 |
+
email TEXT NOT NULL UNIQUE,
|
| 156 |
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
| 157 |
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
| 158 |
+
);
|
| 159 |
+
|
| 160 |
+
-- Enable RLS
|
| 161 |
+
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
| 162 |
+
|
| 163 |
+
-- Policies
|
| 164 |
+
CREATE POLICY "Users can view own record"
|
| 165 |
+
ON users FOR SELECT
|
| 166 |
+
USING (auth.uid() = id);
|
| 167 |
+
|
| 168 |
+
CREATE POLICY "Users can update own record"
|
| 169 |
+
ON users FOR UPDATE
|
| 170 |
+
USING (auth.uid() = id);
|
| 171 |
+
```
|
| 172 |
+
|
| 173 |
+
### Add column migration
|
| 174 |
+
```sql
|
| 175 |
+
ALTER TABLE users ADD COLUMN IF NOT EXISTS display_name TEXT;
|
| 176 |
+
```
|
| 177 |
+
|
| 178 |
+
### Add index migration
|
| 179 |
+
```sql
|
| 180 |
+
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
| 181 |
+
```
|
| 182 |
+
|
| 183 |
+
---
|
| 184 |
+
|
| 185 |
+
## RLS Policy Templates
|
| 186 |
+
|
| 187 |
+
### Full CRUD for owner
|
| 188 |
+
```sql
|
| 189 |
+
CREATE POLICY "owner select" ON table_name FOR SELECT USING (auth.uid() = user_id);
|
| 190 |
+
CREATE POLICY "owner insert" ON table_name FOR INSERT WITH CHECK (auth.uid() = user_id);
|
| 191 |
+
CREATE POLICY "owner update" ON table_name FOR UPDATE USING (auth.uid() = user_id);
|
| 192 |
+
CREATE POLICY "owner delete" ON table_name FOR DELETE USING (auth.uid() = user_id);
|
| 193 |
+
```
|
| 194 |
+
|
| 195 |
+
### Public read, owner write
|
| 196 |
+
```sql
|
| 197 |
+
CREATE POLICY "public read" ON table_name FOR SELECT USING (true);
|
| 198 |
+
CREATE POLICY "owner insert" ON table_name FOR INSERT WITH CHECK (auth.uid() = user_id);
|
| 199 |
+
CREATE POLICY "owner update" ON table_name FOR UPDATE USING (auth.uid() = user_id);
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
### Service role bypass (admin operations)
|
| 203 |
+
```sql
|
| 204 |
+
-- Use supabaseAdmin client (service role) β bypasses RLS
|
| 205 |
+
-- Never expose service role key client side
|
| 206 |
+
```
|
| 207 |
+
|
| 208 |
+
---
|
| 209 |
+
|
| 210 |
+
## Realtime Patterns
|
| 211 |
+
|
| 212 |
+
### Subscribe to table changes
|
| 213 |
+
```typescript
|
| 214 |
+
const channel = supabase
|
| 215 |
+
.channel('table-changes')
|
| 216 |
+
.on('postgres_changes',
|
| 217 |
+
{ event: '*', schema: 'public', table: 'your_table' },
|
| 218 |
+
(payload) => {
|
| 219 |
+
console.info(`[INFO] realtime event: ${payload.eventType}`)
|
| 220 |
+
}
|
| 221 |
+
)
|
| 222 |
+
.subscribe()
|
| 223 |
+
|
| 224 |
+
// Cleanup
|
| 225 |
+
channel.unsubscribe()
|
| 226 |
+
```
|
| 227 |
+
|
| 228 |
+
---
|
| 229 |
+
|
| 230 |
+
## Storage Patterns
|
| 231 |
+
|
| 232 |
+
### Upload file
|
| 233 |
+
```typescript
|
| 234 |
+
async function uploadFile(bucket: string, path: string, file: File) {
|
| 235 |
+
try {
|
| 236 |
+
const { data, error } = await supabase.storage
|
| 237 |
+
.from(bucket)
|
| 238 |
+
.upload(path, file, { upsert: true })
|
| 239 |
+
|
| 240 |
+
if (error) throw error
|
| 241 |
+
return data
|
| 242 |
+
} catch (err) {
|
| 243 |
+
handleError(err, 'lib/storage.ts', 'uploadFile')
|
| 244 |
+
return null
|
| 245 |
+
}
|
| 246 |
+
}
|
| 247 |
+
```
|
| 248 |
+
|
| 249 |
+
### Get public URL
|
| 250 |
+
```typescript
|
| 251 |
+
function getPublicUrl(bucket: string, path: string): string {
|
| 252 |
+
const { data } = supabase.storage.from(bucket).getPublicUrl(path)
|
| 253 |
+
return data.publicUrl
|
| 254 |
+
}
|
| 255 |
+
```
|
| 256 |
+
|
| 257 |
+
---
|
| 258 |
+
|
| 259 |
+
## Common Gotchas
|
| 260 |
+
|
| 261 |
+
| Gotcha | Rule |
|
| 262 |
+
|--------|------|
|
| 263 |
+
| RLS blocks everything by default | Always add policies after enabling RLS |
|
| 264 |
+
| `.single()` throws if no row found | Use `.maybeSingle()` if row may not exist |
|
| 265 |
+
| Service role bypasses RLS | Never use service role client on frontend |
|
| 266 |
+
| Auth session expires | Always check session before DB operations |
|
| 267 |
+
| Migration order matters | Never edit existing migration files |
|
| 268 |
+
| `.select()` after insert needed | Add `.select().single()` to get inserted row back |
|
| 269 |
+
|
| 270 |
+
---
|
| 271 |
+
|
| 272 |
+
## What NOT to Do
|
| 273 |
+
- Do not edit existing migration files β create new ones
|
| 274 |
+
- Do not disable RLS on any table
|
| 275 |
+
- Do not use service role key on client side
|
| 276 |
+
- Do not call DB without checking session first
|
| 277 |
+
- Do not use `.single()` when row might not exist
|
.claude/skills/testing.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skill β Reusable Test Patterns by Feature Type
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
Read this file when writing tests for a specific feature type.
|
| 5 |
+
Contains reusable test templates organised by category.
|
| 6 |
+
Always follow naming conventions from docs/testing.md.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Auth Feature Tests
|
| 11 |
+
|
| 12 |
+
```typescript
|
| 13 |
+
// tests/unit/auth/session.test.ts
|
| 14 |
+
describe('session handling', () => {
|
| 15 |
+
it('returns null when no session exists', async () => {
|
| 16 |
+
mockSupabase.auth.getSession.mockResolvedValueOnce({
|
| 17 |
+
data: { session: null }, error: null
|
| 18 |
+
})
|
| 19 |
+
const result = await getSession()
|
| 20 |
+
expect(result).toBeNull()
|
| 21 |
+
})
|
| 22 |
+
|
| 23 |
+
it('returns session when authenticated', async () => {
|
| 24 |
+
mockSupabase.auth.getSession.mockResolvedValueOnce({
|
| 25 |
+
data: { session: mockSession }, error: null
|
| 26 |
+
})
|
| 27 |
+
const result = await getSession()
|
| 28 |
+
expect(result).toEqual(mockSession)
|
| 29 |
+
})
|
| 30 |
+
|
| 31 |
+
it('handles auth error gracefully', async () => {
|
| 32 |
+
mockSupabase.auth.getSession.mockResolvedValueOnce({
|
| 33 |
+
data: { session: null }, error: new Error('auth failed')
|
| 34 |
+
})
|
| 35 |
+
const result = await getSession()
|
| 36 |
+
expect(result).toBeNull()
|
| 37 |
+
})
|
| 38 |
+
})
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
---
|
| 42 |
+
|
| 43 |
+
## DB Query Tests
|
| 44 |
+
|
| 45 |
+
```typescript
|
| 46 |
+
// tests/unit/lib/users.test.ts
|
| 47 |
+
describe('fetchUser', () => {
|
| 48 |
+
it('returns user for valid id', async () => {
|
| 49 |
+
mockSupabase.from().select().eq.mockResolvedValueOnce({
|
| 50 |
+
data: mockUser, error: null
|
| 51 |
+
})
|
| 52 |
+
const result = await fetchUser('valid-uuid')
|
| 53 |
+
expect(result).toEqual(mockUser)
|
| 54 |
+
})
|
| 55 |
+
|
| 56 |
+
it('returns null for null id', async () => {
|
| 57 |
+
const result = await fetchUser(null as any)
|
| 58 |
+
expect(result).toBeNull()
|
| 59 |
+
})
|
| 60 |
+
|
| 61 |
+
it('returns null for empty string id', async () => {
|
| 62 |
+
const result = await fetchUser('')
|
| 63 |
+
expect(result).toBeNull()
|
| 64 |
+
})
|
| 65 |
+
|
| 66 |
+
it('returns null on DB error', async () => {
|
| 67 |
+
mockSupabase.from().select().eq.mockResolvedValueOnce({
|
| 68 |
+
data: null, error: new Error('DB error')
|
| 69 |
+
})
|
| 70 |
+
const result = await fetchUser('valid-uuid')
|
| 71 |
+
expect(result).toBeNull()
|
| 72 |
+
})
|
| 73 |
+
|
| 74 |
+
it('returns null when DB returns null data', async () => {
|
| 75 |
+
mockSupabase.from().select().eq.mockResolvedValueOnce({
|
| 76 |
+
data: null, error: null
|
| 77 |
+
})
|
| 78 |
+
const result = await fetchUser('valid-uuid')
|
| 79 |
+
expect(result).toBeNull()
|
| 80 |
+
})
|
| 81 |
+
})
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
---
|
| 85 |
+
|
| 86 |
+
## API Route Tests
|
| 87 |
+
|
| 88 |
+
```typescript
|
| 89 |
+
// tests/integration/api/users.integration.test.ts
|
| 90 |
+
describe('POST /api/users', () => {
|
| 91 |
+
it('returns 400 when userId is missing', async () => {
|
| 92 |
+
const res = await fetch('/api/users', {
|
| 93 |
+
method: 'POST',
|
| 94 |
+
body: JSON.stringify({})
|
| 95 |
+
})
|
| 96 |
+
expect(res.status).toBe(400)
|
| 97 |
+
})
|
| 98 |
+
|
| 99 |
+
it('returns 401 when not authenticated', async () => {
|
| 100 |
+
mockGetSession.mockResolvedValueOnce(null)
|
| 101 |
+
const res = await fetch('/api/users', {
|
| 102 |
+
method: 'POST',
|
| 103 |
+
body: JSON.stringify({ userId: 'uuid' })
|
| 104 |
+
})
|
| 105 |
+
expect(res.status).toBe(401)
|
| 106 |
+
})
|
| 107 |
+
|
| 108 |
+
it('returns 200 with valid input and session', async () => {
|
| 109 |
+
mockGetSession.mockResolvedValueOnce(mockSession)
|
| 110 |
+
const res = await fetch('/api/users', {
|
| 111 |
+
method: 'POST',
|
| 112 |
+
body: JSON.stringify({ userId: 'uuid' })
|
| 113 |
+
})
|
| 114 |
+
expect(res.status).toBe(200)
|
| 115 |
+
})
|
| 116 |
+
|
| 117 |
+
it('returns 500 on unexpected server error', async () => {
|
| 118 |
+
mockGetSession.mockRejectedValueOnce(new Error('unexpected'))
|
| 119 |
+
const res = await fetch('/api/users', {
|
| 120 |
+
method: 'POST',
|
| 121 |
+
body: JSON.stringify({ userId: 'uuid' })
|
| 122 |
+
})
|
| 123 |
+
expect(res.status).toBe(500)
|
| 124 |
+
})
|
| 125 |
+
})
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
---
|
| 129 |
+
|
| 130 |
+
## React Component Tests
|
| 131 |
+
|
| 132 |
+
```typescript
|
| 133 |
+
// tests/components/UserCard.test.tsx
|
| 134 |
+
import { render, screen } from '@testing-library/react'
|
| 135 |
+
import UserCard from '@/components/UserCard'
|
| 136 |
+
|
| 137 |
+
describe('UserCard', () => {
|
| 138 |
+
it('renders user name when user exists', () => {
|
| 139 |
+
render(<UserCard user={mockUser} />)
|
| 140 |
+
expect(screen.getByText(mockUser.name)).toBeInTheDocument()
|
| 141 |
+
})
|
| 142 |
+
|
| 143 |
+
it('renders nothing when user is null', () => {
|
| 144 |
+
const { container } = render(<UserCard user={null} />)
|
| 145 |
+
expect(container.firstChild).toBeNull()
|
| 146 |
+
})
|
| 147 |
+
|
| 148 |
+
it('renders loading state when isLoading is true', () => {
|
| 149 |
+
render(<UserCard user={null} isLoading={true} />)
|
| 150 |
+
expect(screen.getByTestId('skeleton')).toBeInTheDocument()
|
| 151 |
+
})
|
| 152 |
+
})
|
| 153 |
+
```
|
| 154 |
+
|
| 155 |
+
---
|
| 156 |
+
|
| 157 |
+
## Utility Function Tests
|
| 158 |
+
|
| 159 |
+
```typescript
|
| 160 |
+
// tests/unit/utils/validation.test.ts
|
| 161 |
+
describe('isValidId', () => {
|
| 162 |
+
it('returns true for valid uuid', () => {
|
| 163 |
+
expect(isValidId('550e8400-e29b-41d4-a716-446655440000')).toBe(true)
|
| 164 |
+
})
|
| 165 |
+
|
| 166 |
+
it('returns false for null', () => {
|
| 167 |
+
expect(isValidId(null)).toBe(false)
|
| 168 |
+
})
|
| 169 |
+
|
| 170 |
+
it('returns false for empty string', () => {
|
| 171 |
+
expect(isValidId('')).toBe(false)
|
| 172 |
+
})
|
| 173 |
+
|
| 174 |
+
it('returns false for whitespace only', () => {
|
| 175 |
+
expect(isValidId(' ')).toBe(false)
|
| 176 |
+
})
|
| 177 |
+
|
| 178 |
+
it('returns false for undefined', () => {
|
| 179 |
+
expect(isValidId(undefined)).toBe(false)
|
| 180 |
+
})
|
| 181 |
+
})
|
| 182 |
+
```
|
| 183 |
+
|
| 184 |
+
---
|
| 185 |
+
|
| 186 |
+
## Mock Templates
|
| 187 |
+
|
| 188 |
+
### Mock user
|
| 189 |
+
```typescript
|
| 190 |
+
export const mockUser = {
|
| 191 |
+
id: '550e8400-e29b-41d4-a716-446655440000',
|
| 192 |
+
email: 'test@example.com',
|
| 193 |
+
created_at: '2025-01-01T00:00:00Z'
|
| 194 |
+
}
|
| 195 |
+
```
|
| 196 |
+
|
| 197 |
+
### Mock session
|
| 198 |
+
```typescript
|
| 199 |
+
export const mockSession = {
|
| 200 |
+
access_token: 'mock-token',
|
| 201 |
+
user: mockUser,
|
| 202 |
+
expires_at: Date.now() + 3600
|
| 203 |
+
}
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
### Reset mocks between tests
|
| 207 |
+
```typescript
|
| 208 |
+
beforeEach(() => {
|
| 209 |
+
jest.clearAllMocks()
|
| 210 |
+
})
|
| 211 |
+
```
|
| 212 |
+
|
| 213 |
+
---
|
| 214 |
+
|
| 215 |
+
## What NOT to Do
|
| 216 |
+
- Do not write tests that always pass regardless of logic
|
| 217 |
+
- Do not skip null and undefined test cases
|
| 218 |
+
- Do not forget to reset mocks between tests
|
| 219 |
+
- Do not test implementation details β test behaviour
|
| 220 |
+
- Do not mock everything β let utility functions run real logic
|
.gitattributes
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
File without changes
|
claude.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Claude.md β Global Instructions
|
| 2 |
+
|
| 3 |
+
## Identity
|
| 4 |
+
You are a focused, phase-gated AI engineer on this project.
|
| 5 |
+
Read only what is needed. Act only on the current phase prompt.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## On Every Session Start
|
| 10 |
+
1. Read `docs/index.md` to orient yourself
|
| 11 |
+
2. Read `session/phase-log.md` to know current progress
|
| 12 |
+
3. Read `session/context.md` for any carry-over from last session
|
| 13 |
+
4. Load the current phase prompt from `prompts/phase-X.md`
|
| 14 |
+
5. Do NOT read all docs at once β load on demand only
|
| 15 |
+
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
## Mandatory Rules (Non-Negotiable)
|
| 19 |
+
|
| 20 |
+
### Phase Gating
|
| 21 |
+
- Work only on the current phase prompt
|
| 22 |
+
- Do not proceed to next phase without explicit user confirmation
|
| 23 |
+
- Confirm all tests pass before marking a phase done
|
| 24 |
+
|
| 25 |
+
### Error Handling
|
| 26 |
+
- Follow `docs/debugging.md` for all error handling patterns
|
| 27 |
+
- Every error must log: file name, function, reason, fix hint
|
| 28 |
+
- When debugging: check the specific file first, nothing else
|
| 29 |
+
|
| 30 |
+
### Testing
|
| 31 |
+
- Follow `docs/testing.md` for all test patterns
|
| 32 |
+
- Update test files after every feature, not at project end
|
| 33 |
+
- Provide CLI commands to run tests with every implementation
|
| 34 |
+
|
| 35 |
+
### Supabase
|
| 36 |
+
- Follow `.claude/skills/supabase.md` for all DB operations
|
| 37 |
+
- Always provide CLI command first, dashboard steps if CLI not possible
|
| 38 |
+
|
| 39 |
+
### Code Quality
|
| 40 |
+
- Handle all edge cases inline
|
| 41 |
+
- No infinite loops, no redundant API calls
|
| 42 |
+
- Fail fast, log clearly
|
| 43 |
+
|
| 44 |
+
---
|
| 45 |
+
|
| 46 |
+
## On Every Session End
|
| 47 |
+
1. Give a one-liner git commit message for every change made
|
| 48 |
+
2. Do NOT push to GitHub
|
| 49 |
+
3. Update `session/summary.md` with what was done
|
| 50 |
+
4. Append one line to `session/phase-log.md`
|
| 51 |
+
5. Update `docs/progress.md` with completed items
|
| 52 |
+
6. If something failed or a workaround was used β add to `docs/learnings.md`
|
| 53 |
+
|
| 54 |
+
---
|
| 55 |
+
|
| 56 |
+
## Reference Map (Load on Demand)
|
| 57 |
+
|
| 58 |
+
| Need | File |
|
| 59 |
+
|------|------|
|
| 60 |
+
| What does each file do | `docs/index.md` |
|
| 61 |
+
| Requirements + phases | `docs/prd.md` |
|
| 62 |
+
| Current progress | `docs/progress.md` |
|
| 63 |
+
| Past failures + fixes | `docs/learnings.md` |
|
| 64 |
+
| System architecture | `docs/architecture.md` |
|
| 65 |
+
| Error handling patterns | `docs/debugging.md` |
|
| 66 |
+
| Test strategy + commands | `docs/testing.md` |
|
| 67 |
+
| Supabase operations | `.claude/skills/supabase.md` |
|
| 68 |
+
| Deploy steps | `docs/deployment.md` |
|
| 69 |
+
| Feature workflow | `docs/workflows/feature.md` |
|
| 70 |
+
| Bug fix workflow | `docs/workflows/bugfix.md` |
|
| 71 |
+
| Refactor workflow | `docs/workflows/refactor.md` |
|
| 72 |
+
|
| 73 |
+
---
|
| 74 |
+
|
| 75 |
+
## Never Do
|
| 76 |
+
- Do not read all `.md` files at session start
|
| 77 |
+
- Do not push to GitHub
|
| 78 |
+
- Do not skip writing tests for a feature
|
| 79 |
+
- Do not proceed to next phase without user confirmation
|
| 80 |
+
- Do not guess Supabase schema β always confirm before migrating
|
docs/architecture.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Architecture β Stack, Structure & Data Models
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
Read this file when building or modifying any structural part
|
| 5 |
+
of the project. Update this file when architecture changes.
|
| 6 |
+
Do not guess the stack β confirm here first.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Tech Stack
|
| 11 |
+
Frontend: [e.g. Next.js 14, React, TypeScript]
|
| 12 |
+
Styling: [e.g. Tailwind CSS, shadcn/ui]
|
| 13 |
+
Backend: [e.g. Next.js API Routes / Edge Functions]
|
| 14 |
+
Database: Supabase (PostgreSQL)
|
| 15 |
+
Auth: Supabase Auth
|
| 16 |
+
Storage: Supabase Storage
|
| 17 |
+
Deployment: [e.g. Vercel / Railway]
|
| 18 |
+
Testing: Jest, ts-jest, React Testing Library
|
| 19 |
+
Package Mgr: [e.g. npm / pnpm]
|
| 20 |
+
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
## Folder Structure
|
| 24 |
+
project-root/
|
| 25 |
+
βββ src/
|
| 26 |
+
β βββ app/ β Next.js app router pages
|
| 27 |
+
β β βββ (auth)/ β auth route group
|
| 28 |
+
β β βββ (dashboard)/ β protected route group
|
| 29 |
+
β β βββ api/ β API routes
|
| 30 |
+
β βββ components/ β reusable UI components
|
| 31 |
+
β β βββ ui/ β base components (shadcn)
|
| 32 |
+
β β βββ [feature]/ β feature specific components
|
| 33 |
+
β βββ lib/ β shared utilities and clients
|
| 34 |
+
β β βββ supabase.ts β supabase client
|
| 35 |
+
β β βββ supabase-server.tsβ server side supabase client
|
| 36 |
+
β β βββ utils.ts β shared utility functions
|
| 37 |
+
β βββ hooks/ β custom React hooks
|
| 38 |
+
β βββ types/ β TypeScript type definitions
|
| 39 |
+
β βββ constants/ β app wide constants
|
| 40 |
+
βββ supabase/
|
| 41 |
+
β βββ migrations/ β all DB migration files
|
| 42 |
+
βββ tests/ β all test files
|
| 43 |
+
βββ docs/ β project documentation
|
| 44 |
+
βββ scripts/ β shell scripts
|
| 45 |
+
βββ logs/ β runtime and test logs
|
| 46 |
+
|
| 47 |
+
---
|
| 48 |
+
|
| 49 |
+
## Database Schema
|
| 50 |
+
|
| 51 |
+
### Tables
|
| 52 |
+
[Update this section as tables are created]
|
| 53 |
+
users
|
| 54 |
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
| 55 |
+
email TEXT NOT NULL UNIQUE
|
| 56 |
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
| 57 |
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
| 58 |
+
|
| 59 |
+
### Relationships
|
| 60 |
+
[Document foreign keys and relationships here]
|
| 61 |
+
users.id β referenced by [table].[column]
|
| 62 |
+
|
| 63 |
+
### RLS Summary
|
| 64 |
+
[Document which tables have RLS enabled and policy types]
|
| 65 |
+
Table RLS Policies
|
| 66 |
+
|
| 67 |
+
users YES owner CRUD
|
| 68 |
+
|
| 69 |
+
---
|
| 70 |
+
|
| 71 |
+
## Auth Flow
|
| 72 |
+
|
| 73 |
+
User lands on /login
|
| 74 |
+
Supabase Auth handles email/OAuth
|
| 75 |
+
On success β session stored in cookie
|
| 76 |
+
Protected routes check session via middleware
|
| 77 |
+
API routes validate session server side
|
| 78 |
+
On signout β session cleared, redirect to /login
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
---
|
| 82 |
+
|
| 83 |
+
## API Routes
|
| 84 |
+
[Document API routes as they are created]
|
| 85 |
+
POST /api/auth/login β handle login
|
| 86 |
+
POST /api/auth/logout β handle logout
|
| 87 |
+
GET /api/user β get current user
|
| 88 |
+
|
| 89 |
+
---
|
| 90 |
+
|
| 91 |
+
## Environment Variables
|
| 92 |
+
NEXT_PUBLIC_SUPABASE_URL β supabase project URL
|
| 93 |
+
NEXT_PUBLIC_SUPABASE_ANON_KEY β supabase anon key
|
| 94 |
+
SUPABASE_SERVICE_ROLE_KEY β server only, never expose
|
| 95 |
+
|
| 96 |
+
---
|
| 97 |
+
|
| 98 |
+
## Key Architectural Decisions
|
| 99 |
+
[Document WHY decisions were made as project grows]
|
| 100 |
+
[YYYY-MM-DD] β Used app router over pages router for better
|
| 101 |
+
server component support
|
| 102 |
+
[YYYY-MM-DD] β Used Supabase RLS over API-level auth checks
|
| 103 |
+
for defence in depth
|
| 104 |
+
|
| 105 |
+
---
|
| 106 |
+
|
| 107 |
+
## Constraints
|
| 108 |
+
[Document hard technical constraints]
|
| 109 |
+
|
| 110 |
+
No direct DB access from client side components
|
| 111 |
+
Service role key only used in server side code
|
| 112 |
+
All DB changes must go through migration files
|
| 113 |
+
RLS must be enabled on every table
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
---
|
| 117 |
+
|
| 118 |
+
## Rules for This File
|
| 119 |
+
- Update when a new table is added
|
| 120 |
+
- Update when a new API route is created
|
| 121 |
+
- Update when a key architectural decision is made
|
| 122 |
+
- Keep schema section in sync with actual migrations
|
| 123 |
+
- Do not document implementation details β only structure
|
docs/debugging.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Debugging β Error Handling, Logging & Debug Strategy
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
Read this file when an error occurs or when implementing error
|
| 5 |
+
handling for any feature. Do not guess β follow this exactly.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Core Principle
|
| 10 |
+
**Check the specific file where the error occurred. Nothing else.**
|
| 11 |
+
Do not scan the entire codebase. Logs must tell you:
|
| 12 |
+
- WHERE it broke (file + function)
|
| 13 |
+
- WHY it broke (reason)
|
| 14 |
+
- HOW to fix it (hint)
|
| 15 |
+
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
## Standard Error Log Format
|
| 19 |
+
|
| 20 |
+
Every error logged must follow this exact structure:
|
| 21 |
+
|
| 22 |
+
[ERROR] [timestamp] [file:function] β reason β fix hint
|
| 23 |
+
|
| 24 |
+
**Example:**
|
| 25 |
+
[ERROR] [2025-01-15T10:32:00Z] [lib/supabase.ts:fetchUser] β user_id is null β check auth session before calling fetchUser
|
| 26 |
+
|
| 27 |
+
---
|
| 28 |
+
|
| 29 |
+
## Error Handler Template
|
| 30 |
+
|
| 31 |
+
### TypeScript / Next.js
|
| 32 |
+
```typescript
|
| 33 |
+
function handleError(error: unknown, file: string, fn: string): void {
|
| 34 |
+
const timestamp = new Date().toISOString()
|
| 35 |
+
const reason = error instanceof Error ? error.message : String(error)
|
| 36 |
+
const hint = getFixHint(reason)
|
| 37 |
+
|
| 38 |
+
console.error(`[ERROR] [${timestamp}] [${file}:${fn}] β ${reason} β ${hint}`)
|
| 39 |
+
|
| 40 |
+
// Write to logs/errors.log in dev
|
| 41 |
+
if (process.env.NODE_ENV === 'development') {
|
| 42 |
+
appendToLog('logs/errors.log', `[ERROR] [${timestamp}] [${file}:${fn}] β ${reason} β ${hint}`)
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
function getFixHint(reason: string): string {
|
| 47 |
+
if (reason.includes('null')) return 'check for null before using this value'
|
| 48 |
+
if (reason.includes('undefined')) return 'confirm the value exists before accessing'
|
| 49 |
+
if (reason.includes('network')) return 'check network connection and API endpoint'
|
| 50 |
+
if (reason.includes('permission')) return 'verify Supabase RLS policies'
|
| 51 |
+
if (reason.includes('timeout')) return 'increase timeout or check slow query'
|
| 52 |
+
return 'check function inputs and dependencies'
|
| 53 |
+
}
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
### Usage in any function
|
| 57 |
+
```typescript
|
| 58 |
+
// file: lib/supabase.ts
|
| 59 |
+
async function fetchUser(userId: string) {
|
| 60 |
+
try {
|
| 61 |
+
if (!userId) throw new Error('user_id is null')
|
| 62 |
+
const { data, error } = await supabase.from('users').select('*').eq('id', userId)
|
| 63 |
+
if (error) throw error
|
| 64 |
+
return data
|
| 65 |
+
} catch (err) {
|
| 66 |
+
handleError(err, 'lib/supabase.ts', 'fetchUser')
|
| 67 |
+
return null
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
---
|
| 73 |
+
|
| 74 |
+
## Logging Levels
|
| 75 |
+
|
| 76 |
+
| Level | When to Use |
|
| 77 |
+
|-------|------------|
|
| 78 |
+
| `[ERROR]` | Something broke, feature cannot continue |
|
| 79 |
+
| `[WARN]` | Something unexpected but recoverable |
|
| 80 |
+
| `[INFO]` | Key state changes, successful operations |
|
| 81 |
+
| `[DEBUG]` | Verbose, dev-only, remove before production |
|
| 82 |
+
|
| 83 |
+
---
|
| 84 |
+
|
| 85 |
+
## Debug Steps β When Something Breaks
|
| 86 |
+
|
| 87 |
+
1. Read `logs/errors.log` β find the exact `[file:function]`
|
| 88 |
+
2. Open only that file
|
| 89 |
+
3. Check the function mentioned in the log
|
| 90 |
+
4. Verify inputs to that function
|
| 91 |
+
5. Check for null/undefined before the failure point
|
| 92 |
+
6. Fix inline, do not refactor adjacent code
|
| 93 |
+
7. Re-run the specific test for that function only
|
| 94 |
+
|
| 95 |
+
**Do not open other files unless the log explicitly points to them.**
|
| 96 |
+
|
| 97 |
+
---
|
| 98 |
+
|
| 99 |
+
## Supabase Specific Errors
|
| 100 |
+
|
| 101 |
+
| Error | Likely Cause | Fix |
|
| 102 |
+
|-------|-------------|-----|
|
| 103 |
+
| `permission denied` | RLS policy blocking query | Check policy in Supabase dashboard β Auth β Policies |
|
| 104 |
+
| `relation does not exist` | Migration not applied | Run `supabase db push` |
|
| 105 |
+
| `violates foreign key` | Referenced row missing | Insert parent record first |
|
| 106 |
+
| `JWT expired` | Auth token stale | Refresh session with `supabase.auth.refreshSession()` |
|
| 107 |
+
| `null value in column` | Missing required field | Validate inputs before insert |
|
| 108 |
+
|
| 109 |
+
---
|
| 110 |
+
|
| 111 |
+
## Edge Case Checklist
|
| 112 |
+
Before shipping any function, verify:
|
| 113 |
+
- [ ] What happens if input is null or undefined?
|
| 114 |
+
- [ ] What happens if the DB returns empty array?
|
| 115 |
+
- [ ] What happens if the API call times out?
|
| 116 |
+
- [ ] What happens if the user is not authenticated?
|
| 117 |
+
- [ ] What happens if this runs twice simultaneously?
|
| 118 |
+
|
| 119 |
+
---
|
| 120 |
+
|
| 121 |
+
## What NOT to Do
|
| 122 |
+
- Do not use `console.log` for errors β use `console.error` with the format above
|
| 123 |
+
- Do not catch an error and do nothing with it
|
| 124 |
+
- Do not log sensitive data (passwords, tokens, PII)
|
| 125 |
+
- Do not open unrelated files to debug an error
|
| 126 |
+
- Do not refactor while debugging β fix first, refactor later
|
docs/deployment.md
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Deployment β Supabase CLI & Dashboard Instructions
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
Read this file when deploying any change to Supabase or your
|
| 5 |
+
environment. Always try CLI first. Use dashboard only if CLI
|
| 6 |
+
is not possible β dashboard steps will be clearly marked.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Core Principle
|
| 11 |
+
**CLI first. Dashboard only when CLI cannot do it.**
|
| 12 |
+
Every deployment step must be logged and reversible.
|
| 13 |
+
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
## Prerequisites
|
| 17 |
+
```bash
|
| 18 |
+
# Install Supabase CLI
|
| 19 |
+
npm install -g supabase
|
| 20 |
+
|
| 21 |
+
# Login
|
| 22 |
+
supabase login
|
| 23 |
+
|
| 24 |
+
# Link to your project (run once per project)
|
| 25 |
+
supabase link --project-ref YOUR_PROJECT_REF
|
| 26 |
+
|
| 27 |
+
# Confirm link
|
| 28 |
+
supabase status
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
---
|
| 32 |
+
|
| 33 |
+
## Environment Variables
|
| 34 |
+
```bash
|
| 35 |
+
# .env.local (never commit this file)
|
| 36 |
+
NEXT_PUBLIC_SUPABASE_URL=your_project_url
|
| 37 |
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
|
| 38 |
+
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
**Where to find these:**
|
| 42 |
+
Dashboard β Project Settings β API β Project URL + Keys
|
| 43 |
+
|
| 44 |
+
---
|
| 45 |
+
|
| 46 |
+
## Database Migrations
|
| 47 |
+
|
| 48 |
+
### Create a new migration
|
| 49 |
+
```bash
|
| 50 |
+
supabase migration new migration_name
|
| 51 |
+
# Creates: supabase/migrations/[timestamp]_migration_name.sql
|
| 52 |
+
# Write your SQL inside this file, then push
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
### Push migration to remote
|
| 56 |
+
```bash
|
| 57 |
+
supabase db push
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
### Check migration status
|
| 61 |
+
```bash
|
| 62 |
+
supabase migration list
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
### Reset local DB (dev only)
|
| 66 |
+
```bash
|
| 67 |
+
supabase db reset
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
### Pull remote schema to local
|
| 71 |
+
```bash
|
| 72 |
+
supabase db pull
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
---
|
| 76 |
+
|
| 77 |
+
## Local Development
|
| 78 |
+
|
| 79 |
+
### Start local Supabase
|
| 80 |
+
```bash
|
| 81 |
+
supabase start
|
| 82 |
+
# Gives you local URL, anon key, service role key
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
### Stop local Supabase
|
| 86 |
+
```bash
|
| 87 |
+
supabase stop
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
### View local DB in browser
|
| 91 |
+
```bash
|
| 92 |
+
supabase studio
|
| 93 |
+
# Opens Supabase Studio at localhost:54323
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
---
|
| 97 |
+
|
| 98 |
+
## Edge Functions
|
| 99 |
+
|
| 100 |
+
### Create a new edge function
|
| 101 |
+
```bash
|
| 102 |
+
supabase functions new function-name
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
### Serve locally
|
| 106 |
+
```bash
|
| 107 |
+
supabase functions serve function-name --env-file .env.local
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
### Deploy edge function
|
| 111 |
+
```bash
|
| 112 |
+
supabase functions deploy function-name
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
### Set environment secret for edge function
|
| 116 |
+
```bash
|
| 117 |
+
supabase secrets set KEY=value
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
### List secrets
|
| 121 |
+
```bash
|
| 122 |
+
supabase secrets list
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
---
|
| 126 |
+
|
| 127 |
+
## Storage
|
| 128 |
+
|
| 129 |
+
### CLI β not fully supported for bucket creation
|
| 130 |
+
**β Use Dashboard for bucket creation**
|
| 131 |
+
|
| 132 |
+
#### Dashboard Steps β Create Storage Bucket
|
| 133 |
+
|
| 134 |
+
Go to Supabase Dashboard β Storage
|
| 135 |
+
Click "New bucket"
|
| 136 |
+
Enter bucket name
|
| 137 |
+
Toggle public/private
|
| 138 |
+
Click "Create bucket"
|
| 139 |
+
|
| 140 |
+
### Upload file via CLI
|
| 141 |
+
```bash
|
| 142 |
+
supabase storage cp ./local-file.png ss:///bucket-name/path/file.png
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
---
|
| 146 |
+
|
| 147 |
+
## Row Level Security (RLS)
|
| 148 |
+
|
| 149 |
+
### CLI β enable RLS on a table
|
| 150 |
+
```sql
|
| 151 |
+
-- Inside a migration file
|
| 152 |
+
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
|
| 153 |
+
```
|
| 154 |
+
|
| 155 |
+
### CLI β create a policy via migration
|
| 156 |
+
```sql
|
| 157 |
+
CREATE POLICY "Users can view own data"
|
| 158 |
+
ON your_table
|
| 159 |
+
FOR SELECT
|
| 160 |
+
USING (auth.uid() = user_id);
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
### Dashboard Steps β RLS Policy (if migration not possible)
|
| 164 |
+
|
| 165 |
+
Go to Supabase Dashboard β Authentication β Policies
|
| 166 |
+
Select your table
|
| 167 |
+
Click "New Policy"
|
| 168 |
+
Choose template or write custom
|
| 169 |
+
Click "Review" then "Save Policy"
|
| 170 |
+
|
| 171 |
+
---
|
| 172 |
+
|
| 173 |
+
## Auth Configuration
|
| 174 |
+
|
| 175 |
+
### CLI β not supported for OAuth provider setup
|
| 176 |
+
**β Use Dashboard for OAuth setup**
|
| 177 |
+
|
| 178 |
+
#### Dashboard Steps β Enable OAuth Provider
|
| 179 |
+
|
| 180 |
+
Go to Dashboard β Authentication β Providers
|
| 181 |
+
Select provider (Google, GitHub, etc.)
|
| 182 |
+
Enter Client ID and Secret
|
| 183 |
+
Copy callback URL β paste into provider's OAuth app
|
| 184 |
+
Click "Save"
|
| 185 |
+
|
| 186 |
+
### Set auth email templates
|
| 187 |
+
|
| 188 |
+
Dashboard β Authentication β Email Templates
|
| 189 |
+
β Edit confirm signup / reset password templates
|
| 190 |
+
|
| 191 |
+
---
|
| 192 |
+
|
| 193 |
+
## scripts/deploy.sh
|
| 194 |
+
```bash
|
| 195 |
+
#!/bin/bash
|
| 196 |
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Starting deployment..." >> logs/errors.log
|
| 197 |
+
|
| 198 |
+
# Run tests first β abort if they fail
|
| 199 |
+
bash scripts/test.sh
|
| 200 |
+
if [ $? -ne 0 ]; then
|
| 201 |
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Deployment aborted β tests failed" >> logs/errors.log
|
| 202 |
+
exit 1
|
| 203 |
+
fi
|
| 204 |
+
|
| 205 |
+
# Push DB migrations
|
| 206 |
+
echo "Pushing DB migrations..."
|
| 207 |
+
supabase db push
|
| 208 |
+
if [ $? -ne 0 ]; then
|
| 209 |
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Migration failed β check supabase logs" >> logs/errors.log
|
| 210 |
+
exit 1
|
| 211 |
+
fi
|
| 212 |
+
|
| 213 |
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Deployment complete" >> logs/errors.log
|
| 214 |
+
```
|
| 215 |
+
|
| 216 |
+
---
|
| 217 |
+
|
| 218 |
+
## Rollback
|
| 219 |
+
|
| 220 |
+
### Rollback last migration
|
| 221 |
+
```bash
|
| 222 |
+
# Supabase does not have auto-rollback
|
| 223 |
+
# Write a reverse migration manually
|
| 224 |
+
|
| 225 |
+
supabase migration new rollback_migration_name
|
| 226 |
+
# Write reverse SQL (DROP TABLE, ALTER, etc.)
|
| 227 |
+
supabase db push
|
| 228 |
+
```
|
| 229 |
+
|
| 230 |
+
### Dashboard Steps β Rollback (if CLI fails)
|
| 231 |
+
|
| 232 |
+
Go to Dashboard β Database β Migrations
|
| 233 |
+
Identify the migration to reverse
|
| 234 |
+
Go to Dashboard β SQL Editor
|
| 235 |
+
Write and run reverse SQL manually
|
| 236 |
+
|
| 237 |
+
---
|
| 238 |
+
|
| 239 |
+
## Deployment Checklist
|
| 240 |
+
- [ ] All tests pass (`bash scripts/test.sh`)
|
| 241 |
+
- [ ] `.env.local` is not committed
|
| 242 |
+
- [ ] Migration file created for every schema change
|
| 243 |
+
- [ ] RLS enabled on every new table
|
| 244 |
+
- [ ] Edge functions tested locally before deploy
|
| 245 |
+
- [ ] `supabase db push` confirms no errors
|
| 246 |
+
- [ ] One-liner git commit message written
|
| 247 |
+
|
| 248 |
+
---
|
| 249 |
+
|
| 250 |
+
## What NOT to Do
|
| 251 |
+
- Do not edit schema directly in Dashboard without a migration file
|
| 252 |
+
- Do not push migrations without running tests first
|
| 253 |
+
- Do not commit `.env.local` or any file with keys
|
| 254 |
+
- Do not disable RLS on any table in production
|
| 255 |
+
- Do not deploy edge functions without local testing first
|
docs/index.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Index β What Every File Does & When to Read It
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
This is your orientation file. Read this once at session start.
|
| 5 |
+
It tells you what exists, what it does, and when to load it.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Core Files
|
| 10 |
+
|
| 11 |
+
| File | Purpose | When to Read |
|
| 12 |
+
|------|---------|--------------|
|
| 13 |
+
| `claude.md` | Global rules, session start/end checklist | Every session start |
|
| 14 |
+
| `docs/index.md` | This file β master map | Every session start |
|
| 15 |
+
| `session/phase-log.md` | One-liner per phase, current status | Every session start |
|
| 16 |
+
| `session/context.md` | Carry-over notes from last session | Every session start |
|
| 17 |
+
| `session/summary.md` | What was done in last session | Only if context is unclear |
|
| 18 |
+
|
| 19 |
+
---
|
| 20 |
+
|
| 21 |
+
## Project State Files
|
| 22 |
+
|
| 23 |
+
| File | Purpose | When to Read |
|
| 24 |
+
|------|---------|--------------|
|
| 25 |
+
| `docs/prd.md` | Full PRD, all 8 phases, requirements | Phase start or on demand |
|
| 26 |
+
| `docs/progress.md` | One-liner per feature, pass/fail status | When checking what's done |
|
| 27 |
+
| `docs/learnings.md` | Past failures, workarounds, gotchas | When hitting a repeated error |
|
| 28 |
+
| `docs/architecture.md` | Stack, data models, folder structure | When building or modifying structure |
|
| 29 |
+
|
| 30 |
+
---
|
| 31 |
+
|
| 32 |
+
## How-To Reference Files
|
| 33 |
+
|
| 34 |
+
| File | Purpose | When to Read |
|
| 35 |
+
|------|---------|--------------|
|
| 36 |
+
| `docs/debugging.md` | Error format, log structure, debug steps | When an error occurs |
|
| 37 |
+
| `docs/testing.md` | Test strategy, CLI commands, log format | When writing or running tests |
|
| 38 |
+
| `docs/deployment.md` | Supabase CLI + dashboard deploy steps | When deploying any change |
|
| 39 |
+
| `.claude/skills/supabase.md` | Supabase patterns, queries, migrations | When writing DB code |
|
| 40 |
+
| `.claude/skills/debugging.md` | Reusable debug snippets by error type | When stuck on a specific error |
|
| 41 |
+
| `.claude/skills/testing.md` | Reusable test patterns by feature type | When writing tests |
|
| 42 |
+
|
| 43 |
+
---
|
| 44 |
+
|
| 45 |
+
## Workflow Files
|
| 46 |
+
|
| 47 |
+
| File | Purpose | When to Read |
|
| 48 |
+
|------|---------|--------------|
|
| 49 |
+
| `docs/workflows/feature.md` | Step-by-step for building a new feature | Every new feature |
|
| 50 |
+
| `docs/workflows/bugfix.md` | Step-by-step for fixing a bug | Every bug fix |
|
| 51 |
+
| `docs/workflows/refactor.md` | Step-by-step for refactoring code | Every refactor |
|
| 52 |
+
|
| 53 |
+
---
|
| 54 |
+
|
| 55 |
+
## Phase Prompts
|
| 56 |
+
|
| 57 |
+
| File | Purpose | When to Read |
|
| 58 |
+
|------|---------|--------------|
|
| 59 |
+
| `prompts/phase-1.md` through `phase-8.md` | Individual phase instructions | Only the current phase |
|
| 60 |
+
|
| 61 |
+
---
|
| 62 |
+
|
| 63 |
+
## Scripts & Logs
|
| 64 |
+
|
| 65 |
+
| File | Purpose | When to Read |
|
| 66 |
+
|------|---------|--------------|
|
| 67 |
+
| `scripts/test.sh` | Run all tests with logging | When running tests |
|
| 68 |
+
| `scripts/dev.sh` | Start dev environment | When starting dev |
|
| 69 |
+
| `scripts/deploy.sh` | Deploy to Supabase + environment | When deploying |
|
| 70 |
+
| `logs/errors.log` | Runtime error output | When debugging a failure |
|
| 71 |
+
| `logs/test.log` | Test run output | When a test fails |
|
| 72 |
+
|
| 73 |
+
---
|
| 74 |
+
|
| 75 |
+
## Rules for This File
|
| 76 |
+
- Do not add implementation details here
|
| 77 |
+
- Do not add code here
|
| 78 |
+
- Only update this file if a new reference file is added to the project
|
docs/learnings.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Learnings β Failures, Workarounds & Time Savers
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
Read this file when hitting a repeated error or unexpected behaviour.
|
| 5 |
+
Each bullet is a one-liner under 15 words.
|
| 6 |
+
No explanations. Only things that save time in future sessions.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Rules for This File
|
| 11 |
+
- One line per learning, under 15 words
|
| 12 |
+
- Add immediately when a failure or workaround is found
|
| 13 |
+
- Never delete a line β they compound over time
|
| 14 |
+
- Group by category as list grows
|
| 15 |
+
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
## Supabase
|
| 19 |
+
- [ ] Add learnings here as they are discovered
|
| 20 |
+
|
| 21 |
+
## Auth
|
| 22 |
+
- [ ] Add learnings here as they are discovered
|
| 23 |
+
|
| 24 |
+
## Testing
|
| 25 |
+
- [ ] Add learnings here as they are discovered
|
| 26 |
+
|
| 27 |
+
## Next.js
|
| 28 |
+
- [ ] Add learnings here as they are discovered
|
| 29 |
+
|
| 30 |
+
## Deployment
|
| 31 |
+
- [ ] Add learnings here as they are discovered
|
| 32 |
+
|
| 33 |
+
## General
|
| 34 |
+
- [ ] Add learnings here as they are discovered
|
| 35 |
+
|
| 36 |
+
---
|
| 37 |
+
|
| 38 |
+
## Example Format (remove when real entries exist)
|
| 39 |
+
```
|
| 40 |
+
## Supabase
|
| 41 |
+
- RLS blocks all queries by default β always add select policy first
|
| 42 |
+
- supabase db push fails silently if migration has syntax error
|
| 43 |
+
- Foreign key inserts require parent row to exist first
|
| 44 |
+
|
| 45 |
+
## Auth
|
| 46 |
+
- JWT expires after 1hr β always call refreshSession before DB ops
|
| 47 |
+
- getSession returns null on hard refresh β use onAuthStateChange instead
|
| 48 |
+
|
| 49 |
+
## Testing
|
| 50 |
+
- Jest mock must be reset between tests or state bleeds across cases
|
| 51 |
+
- Supabase client must be mocked at module level not inside test
|
| 52 |
+
|
| 53 |
+
## Next.js
|
| 54 |
+
- useEffect runs twice in strict mode β guard with cleanup function
|
| 55 |
+
- API routes do not have access to cookies without explicit parsing
|
| 56 |
+
```
|
docs/prd.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# PRD β Product Requirements Document
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
Read this file at phase start to understand requirements.
|
| 5 |
+
Contains full product vision, all phases and acceptance criteria.
|
| 6 |
+
Do not implement anything not listed here without user confirmation.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Product Overview
|
| 11 |
+
Product Name: [name]
|
| 12 |
+
Description: [one paragraph max]
|
| 13 |
+
Primary User: [who is this for]
|
| 14 |
+
Core Problem: [what problem does this solve]
|
| 15 |
+
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
## Success Metrics
|
| 19 |
+
|
| 20 |
+
[metric one β measurable]
|
| 21 |
+
[metric two β measurable]
|
| 22 |
+
[metric three β measurable]
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
---
|
| 26 |
+
|
| 27 |
+
## Technical Constraints
|
| 28 |
+
|
| 29 |
+
[constraint one]
|
| 30 |
+
[constraint two]
|
| 31 |
+
[constraint three]
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
---
|
| 35 |
+
|
| 36 |
+
## Phase Overview
|
| 37 |
+
Phase 1 β [name] β [one liner]
|
| 38 |
+
Phase 2 β [name] β [one liner]
|
| 39 |
+
Phase 3 β [name] β [one liner]
|
| 40 |
+
Phase 4 β [name] β [one liner]
|
| 41 |
+
Phase 5 β [name] β [one liner]
|
| 42 |
+
Phase 6 β [name] β [one liner]
|
| 43 |
+
Phase 7 β [name] β [one liner]
|
| 44 |
+
Phase 8 β [name] β [one liner]
|
| 45 |
+
|
| 46 |
+
---
|
| 47 |
+
|
| 48 |
+
## Phase 1 β [Phase Name]
|
| 49 |
+
|
| 50 |
+
### Goal
|
| 51 |
+
[One paragraph β what this phase achieves]
|
| 52 |
+
|
| 53 |
+
### Features
|
| 54 |
+
|
| 55 |
+
[feature name] β [one liner description]
|
| 56 |
+
[feature name] β [one liner description]
|
| 57 |
+
[feature name] β [one liner description]
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
### Acceptance Criteria
|
| 61 |
+
|
| 62 |
+
[specific testable criterion]
|
| 63 |
+
[specific testable criterion]
|
| 64 |
+
[specific testable criterion]
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
### Out of Scope for This Phase
|
| 68 |
+
|
| 69 |
+
[what is explicitly NOT included]
|
| 70 |
+
[what is explicitly NOT included]
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
---
|
| 74 |
+
|
| 75 |
+
## Phase 2 β [Phase Name]
|
| 76 |
+
|
| 77 |
+
### Goal
|
| 78 |
+
[One paragraph]
|
| 79 |
+
|
| 80 |
+
### Features
|
| 81 |
+
|
| 82 |
+
[feature name] β [one liner]
|
| 83 |
+
[feature name] β [one liner]
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
### Acceptance Criteria
|
| 87 |
+
|
| 88 |
+
[criterion]
|
| 89 |
+
[criterion]
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
### Out of Scope for This Phase
|
| 93 |
+
|
| 94 |
+
[excluded item]
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
---
|
| 98 |
+
|
| 99 |
+
## Phase 3 β [Phase Name]
|
| 100 |
+
|
| 101 |
+
### Goal
|
| 102 |
+
[One paragraph]
|
| 103 |
+
|
| 104 |
+
### Features
|
| 105 |
+
|
| 106 |
+
[feature name] β [one liner]
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
### Acceptance Criteria
|
| 110 |
+
|
| 111 |
+
[criterion]
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
### Out of Scope for This Phase
|
| 115 |
+
|
| 116 |
+
[excluded item]
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
---
|
| 120 |
+
|
| 121 |
+
## Phase 4 β [Phase Name]
|
| 122 |
+
|
| 123 |
+
### Goal
|
| 124 |
+
[One paragraph]
|
| 125 |
+
|
| 126 |
+
### Features
|
| 127 |
+
|
| 128 |
+
[feature name] β [one liner]
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
### Acceptance Criteria
|
| 132 |
+
|
| 133 |
+
[criterion]
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
### Out of Scope for This Phase
|
| 137 |
+
|
| 138 |
+
[excluded item]
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
---
|
| 142 |
+
|
| 143 |
+
## Phase 5 β [Phase Name]
|
| 144 |
+
|
| 145 |
+
### Goal
|
| 146 |
+
[One paragraph]
|
| 147 |
+
|
| 148 |
+
### Features
|
| 149 |
+
|
| 150 |
+
[feature name] β [one liner]
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
### Acceptance Criteria
|
| 154 |
+
|
| 155 |
+
[criterion]
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
### Out of Scope for This Phase
|
| 159 |
+
|
| 160 |
+
[excluded item]
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
---
|
| 164 |
+
|
| 165 |
+
## Phase 6 β [Phase Name]
|
| 166 |
+
|
| 167 |
+
### Goal
|
| 168 |
+
[One paragraph]
|
| 169 |
+
|
| 170 |
+
### Features
|
| 171 |
+
|
| 172 |
+
[feature name] β [one liner]
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
### Acceptance Criteria
|
| 176 |
+
|
| 177 |
+
[criterion]
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
### Out of Scope for This Phase
|
| 181 |
+
|
| 182 |
+
[excluded item]
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
---
|
| 186 |
+
|
| 187 |
+
## Phase 7 β [Phase Name]
|
| 188 |
+
|
| 189 |
+
### Goal
|
| 190 |
+
[One paragraph]
|
| 191 |
+
|
| 192 |
+
### Features
|
| 193 |
+
|
| 194 |
+
[feature name] β [one liner]
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
### Acceptance Criteria
|
| 198 |
+
|
| 199 |
+
[criterion]
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
### Out of Scope for This Phase
|
| 203 |
+
|
| 204 |
+
[excluded item]
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
---
|
| 208 |
+
|
| 209 |
+
## Phase 8 β [Phase Name]
|
| 210 |
+
|
| 211 |
+
### Goal
|
| 212 |
+
[One paragraph]
|
| 213 |
+
|
| 214 |
+
### Features
|
| 215 |
+
|
| 216 |
+
[feature name] β [one liner]
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
### Acceptance Criteria
|
| 220 |
+
|
| 221 |
+
[criterion]
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
### Out of Scope for This Phase
|
| 225 |
+
|
| 226 |
+
[excluded item]
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
---
|
| 230 |
+
|
| 231 |
+
## Phase Gate Rules
|
| 232 |
+
- Do not start a phase without user confirmation
|
| 233 |
+
- Do not proceed to next phase if current tests are failing
|
| 234 |
+
- Do not implement features outside current phase scope
|
| 235 |
+
- Mark acceptance criteria as complete before closing phase
|
| 236 |
+
- All criteria must be checked before phase is marked COMPLETE
|
| 237 |
+
|
| 238 |
+
---
|
| 239 |
+
|
| 240 |
+
## Rules for This File
|
| 241 |
+
- Fill in placeholders before starting Phase 1
|
| 242 |
+
- Do not modify acceptance criteria mid phase
|
| 243 |
+
- Do not add features to a phase that is in progress
|
| 244 |
+
- New features go into the next available phase or a new phase
|
docs/progress.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Progress β Feature & Phase Status
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
Read this file to know exactly what is done, in progress, or pending.
|
| 5 |
+
One liner per feature. Update after every feature implementation.
|
| 6 |
+
Do not read entire codebase to understand progress β read this file.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Status Legend
|
| 11 |
+
|
| 12 |
+
β
done β implemented, tested, committed
|
| 13 |
+
π in progress β currently being worked on
|
| 14 |
+
β³ pending β not started yet
|
| 15 |
+
β blocked β cannot proceed, reason noted
|
| 16 |
+
π bug β implemented but has known failing test
|
| 17 |
+
|
| 18 |
+
---
|
| 19 |
+
|
| 20 |
+
## Phase 1 β [Phase Name]
|
| 21 |
+
β³ [feature name] β [one line description]
|
| 22 |
+
β³ [feature name] β [one line description]
|
| 23 |
+
β³ [feature name] β [one line description]
|
| 24 |
+
|
| 25 |
+
## Phase 2 β [Phase Name]
|
| 26 |
+
β³ [feature name] β [one line description]
|
| 27 |
+
β³ [feature name] β [one line description]
|
| 28 |
+
|
| 29 |
+
## Phase 3 β [Phase Name]
|
| 30 |
+
β³ [feature name] β [one line description]
|
| 31 |
+
|
| 32 |
+
## Phase 4 β [Phase Name]
|
| 33 |
+
β³ [feature name] β [one line description]
|
| 34 |
+
|
| 35 |
+
## Phase 5 β [Phase Name]
|
| 36 |
+
β³ [feature name] β [one line description]
|
| 37 |
+
|
| 38 |
+
## Phase 6 β [Phase Name]
|
| 39 |
+
β³ [feature name] β [one line description]
|
| 40 |
+
|
| 41 |
+
## Phase 7 β [Phase Name]
|
| 42 |
+
β³ [feature name] β [one line description]
|
| 43 |
+
|
| 44 |
+
## Phase 8 β [Phase Name]
|
| 45 |
+
β³ [feature name] β [one line description]
|
| 46 |
+
|
| 47 |
+
---
|
| 48 |
+
|
| 49 |
+
## Blocked Items
|
| 50 |
+
β [feature name] β blocked by: [reason]
|
| 51 |
+
|
| 52 |
+
---
|
| 53 |
+
|
| 54 |
+
## Rules for This File
|
| 55 |
+
- One line per feature, no paragraphs
|
| 56 |
+
- Update status after every feature, not at end of phase
|
| 57 |
+
- Never delete a line β only update its status
|
| 58 |
+
- If blocked, note the reason inline
|
docs/testing.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Testing β Strategy, Patterns & CLI Commands
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
Read this file when writing tests or running them.
|
| 5 |
+
Follow this exactly for every feature implementation.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Core Principle
|
| 10 |
+
**Write tests after every feature. Never at the end of the project.**
|
| 11 |
+
Every test failure must tell you:
|
| 12 |
+
- WHICH test failed (test name + file)
|
| 13 |
+
- WHY it failed (expected vs received)
|
| 14 |
+
- WHERE it failed (line number)
|
| 15 |
+
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
## Testing Stack
|
| 19 |
+
- **Unit + Integration:** Jest + ts-jest
|
| 20 |
+
- **API Routes:** Supertest
|
| 21 |
+
- **UI Components:** React Testing Library
|
| 22 |
+
- **DB (Supabase):** Mocked with jest.mock or Supabase local instance
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
## Folder Structure
|
| 27 |
+
|
| 28 |
+
tests/
|
| 29 |
+
βββ unit/
|
| 30 |
+
β βββ lib/
|
| 31 |
+
β βββ utils/
|
| 32 |
+
βββ integration/
|
| 33 |
+
β βββ api/
|
| 34 |
+
β βββ db/
|
| 35 |
+
βββ components/
|
| 36 |
+
βββ setup.ts
|
| 37 |
+
|
| 38 |
+
---
|
| 39 |
+
|
| 40 |
+
## Test File Naming Convention
|
| 41 |
+
[feature-name].test.ts β unit test
|
| 42 |
+
[feature-name].integration.test.ts β integration test
|
| 43 |
+
[component-name].test.tsx β component test
|
| 44 |
+
|
| 45 |
+
---
|
| 46 |
+
|
| 47 |
+
## Standard Test Template
|
| 48 |
+
|
| 49 |
+
```typescript
|
| 50 |
+
// tests/unit/lib/fetchUser.test.ts
|
| 51 |
+
import { fetchUser } from '@/lib/supabase'
|
| 52 |
+
|
| 53 |
+
describe('fetchUser', () => {
|
| 54 |
+
it('returns user when valid userId is provided', async () => {
|
| 55 |
+
const result = await fetchUser('valid-uuid')
|
| 56 |
+
expect(result).not.toBeNull()
|
| 57 |
+
expect(result).toHaveProperty('id')
|
| 58 |
+
})
|
| 59 |
+
|
| 60 |
+
it('returns null when userId is null', async () => {
|
| 61 |
+
const result = await fetchUser(null as any)
|
| 62 |
+
expect(result).toBeNull()
|
| 63 |
+
})
|
| 64 |
+
|
| 65 |
+
it('returns null when userId is empty string', async () => {
|
| 66 |
+
const result = await fetchUser('')
|
| 67 |
+
expect(result).toBeNull()
|
| 68 |
+
})
|
| 69 |
+
|
| 70 |
+
it('handles DB error gracefully', async () => {
|
| 71 |
+
// mock supabase to throw
|
| 72 |
+
jest.spyOn(supabase, 'from').mockImplementationOnce(() => {
|
| 73 |
+
throw new Error('DB connection failed')
|
| 74 |
+
})
|
| 75 |
+
const result = await fetchUser('valid-uuid')
|
| 76 |
+
expect(result).toBeNull()
|
| 77 |
+
})
|
| 78 |
+
})
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
---
|
| 82 |
+
|
| 83 |
+
## Edge Cases to Test for Every Feature
|
| 84 |
+
|
| 85 |
+
| Scenario | What to Test |
|
| 86 |
+
|----------|-------------|
|
| 87 |
+
| Empty input | null, undefined, empty string |
|
| 88 |
+
| Auth state | unauthenticated user, expired token |
|
| 89 |
+
| DB response | empty array, null, malformed data |
|
| 90 |
+
| Network | timeout, connection failure |
|
| 91 |
+
| Duplicates | calling same function twice simultaneously |
|
| 92 |
+
| Boundary | max length strings, zero values, negative numbers |
|
| 93 |
+
|
| 94 |
+
---
|
| 95 |
+
|
| 96 |
+
## CLI Commands
|
| 97 |
+
|
| 98 |
+
### Run all tests
|
| 99 |
+
```bash
|
| 100 |
+
npm test 2>&1 | tee logs/test.log
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
### Run a specific test file
|
| 104 |
+
```bash
|
| 105 |
+
npm test -- tests/unit/lib/fetchUser.test.ts 2>&1 | tee logs/test.log
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
### Run tests in watch mode
|
| 109 |
+
```bash
|
| 110 |
+
npm test -- --watch
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
### Run tests with coverage
|
| 114 |
+
```bash
|
| 115 |
+
npm test -- --coverage 2>&1 | tee logs/test.log
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
### Run only failed tests
|
| 119 |
+
```bash
|
| 120 |
+
npm test -- --onlyFailures 2>&1 | tee logs/test.log
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
---
|
| 124 |
+
|
| 125 |
+
## Test Log Format
|
| 126 |
+
All test output pipes to `logs/test.log`.
|
| 127 |
+
When a test fails, the log will show:
|
| 128 |
+
|
| 129 |
+
FAIL tests/unit/lib/fetchUser.test.ts
|
| 130 |
+
β fetchUser βΊ returns null when userId is null
|
| 131 |
+
|
| 132 |
+
expect(received).toBeNull()
|
| 133 |
+
|
| 134 |
+
Received: { id: 'abc', name: 'test' }
|
| 135 |
+
|
| 136 |
+
14 | it('returns null when userId is null', async () => {
|
| 137 |
+
15 | const result = await fetchUser(null as any)
|
| 138 |
+
> 16 | expect(result).toBeNull()
|
| 139 |
+
| ^
|
| 140 |
+
17 | })
|
| 141 |
+
|
| 142 |
+
at Object.<anonymous> (tests/unit/lib/fetchUser.test.ts:16:20)
|
| 143 |
+
|
| 144 |
+
---
|
| 145 |
+
|
| 146 |
+
## scripts/test.sh
|
| 147 |
+
```bash
|
| 148 |
+
#!/bin/bash
|
| 149 |
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Starting test run..." >> logs/test.log
|
| 150 |
+
npm test 2>&1 | tee -a logs/test.log
|
| 151 |
+
EXIT_CODE=${PIPESTATUS[0]}
|
| 152 |
+
if [ $EXIT_CODE -ne 0 ]; then
|
| 153 |
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] TESTS FAILED β see above for details" >> logs/test.log
|
| 154 |
+
else
|
| 155 |
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ALL TESTS PASSED" >> logs/test.log
|
| 156 |
+
fi
|
| 157 |
+
exit $EXIT_CODE
|
| 158 |
+
```
|
| 159 |
+
|
| 160 |
+
---
|
| 161 |
+
|
| 162 |
+
## Test Update Rules
|
| 163 |
+
- [ ] Write tests immediately after each feature is implemented
|
| 164 |
+
- [ ] Never delete existing tests β only add or update
|
| 165 |
+
- [ ] If a test is skipped, add a comment explaining why
|
| 166 |
+
- [ ] All tests must pass before marking a phase complete
|
| 167 |
+
- [ ] Run full test suite before every deployment
|
| 168 |
+
|
| 169 |
+
---
|
| 170 |
+
|
| 171 |
+
## Mocking Supabase
|
| 172 |
+
```typescript
|
| 173 |
+
// tests/setup.ts
|
| 174 |
+
jest.mock('@/lib/supabase', () => ({
|
| 175 |
+
supabase: {
|
| 176 |
+
from: jest.fn().mockReturnValue({
|
| 177 |
+
select: jest.fn().mockReturnValue({
|
| 178 |
+
eq: jest.fn().mockResolvedValue({ data: [], error: null })
|
| 179 |
+
}),
|
| 180 |
+
insert: jest.fn().mockResolvedValue({ data: null, error: null }),
|
| 181 |
+
update: jest.fn().mockResolvedValue({ data: null, error: null }),
|
| 182 |
+
delete: jest.fn().mockResolvedValue({ data: null, error: null })
|
| 183 |
+
}),
|
| 184 |
+
auth: {
|
| 185 |
+
getSession: jest.fn().mockResolvedValue({ data: { session: null }, error: null }),
|
| 186 |
+
refreshSession: jest.fn().mockResolvedValue({ data: null, error: null })
|
| 187 |
+
}
|
| 188 |
+
}
|
| 189 |
+
}))
|
| 190 |
+
```
|
| 191 |
+
|
| 192 |
+
---
|
| 193 |
+
|
| 194 |
+
## What NOT to Do
|
| 195 |
+
- Do not write tests after the entire project is done
|
| 196 |
+
- Do not mock everything β integration tests must hit real logic
|
| 197 |
+
- Do not skip edge case tests to save time
|
| 198 |
+
- Do not ignore a failing test β fix it before moving on
|
| 199 |
+
- Do not write tests that always pass regardless of logic
|
docs/workflows/bugfix.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Workflow β Fixing a Bug
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
Read this file every time you need to fix a bug.
|
| 5 |
+
Follow steps in order. Do not open files not mentioned in the log.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Core Principle
|
| 10 |
+
**The log tells you where to look. Trust the log. Check one file.**
|
| 11 |
+
Do not scan the codebase. Do not refactor while fixing.
|
| 12 |
+
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
## Step 1 β Read the Error First
|
| 16 |
+
Before touching any code:
|
| 17 |
+
- [ ] Read `logs/errors.log` β find the exact error entry
|
| 18 |
+
- [ ] Identify: file, function, reason, fix hint from the log
|
| 19 |
+
- [ ] Read `docs/learnings.md` β has this failed before?
|
| 20 |
+
- [ ] If yes β apply the known fix, skip to Step 5
|
| 21 |
+
|
| 22 |
+
Error log format to look for:
|
| 23 |
+
|
| 24 |
+
[ERROR] [timestamp] [file:function] β reason β fix hint
|
| 25 |
+
|
| 26 |
+
---
|
| 27 |
+
|
| 28 |
+
## Step 2 β Isolate Before Fixing
|
| 29 |
+
State this out loud before touching code:
|
| 30 |
+
Error in: [file:function]
|
| 31 |
+
Reason: [from log]
|
| 32 |
+
Fix hint: [from log]
|
| 33 |
+
Only file I will open: [file]
|
| 34 |
+
|
| 35 |
+
- [ ] Open only the file named in the log
|
| 36 |
+
- [ ] Find only the function named in the log
|
| 37 |
+
- [ ] Do not open any other file unless the log explicitly points there
|
| 38 |
+
|
| 39 |
+
---
|
| 40 |
+
|
| 41 |
+
## Step 3 β Understand the Failure
|
| 42 |
+
Inside the identified function:
|
| 43 |
+
- [ ] Check what input is coming in
|
| 44 |
+
- [ ] Check where exactly it breaks (line from log)
|
| 45 |
+
- [ ] Check if it is a null/undefined issue
|
| 46 |
+
- [ ] Check if it is an auth/session issue
|
| 47 |
+
- [ ] Check if it is a Supabase RLS issue
|
| 48 |
+
- [ ] Reference `docs/debugging.md` β Supabase Specific Errors table
|
| 49 |
+
|
| 50 |
+
---
|
| 51 |
+
|
| 52 |
+
## Step 4 β Write the Fix
|
| 53 |
+
- [ ] Fix only the broken logic
|
| 54 |
+
- [ ] Do not rename variables or restructure the function
|
| 55 |
+
- [ ] Do not fix adjacent code that looks messy
|
| 56 |
+
- [ ] Add or improve error handling inline if it was missing
|
| 57 |
+
- [ ] Confirm fix hint from log is addressed
|
| 58 |
+
|
| 59 |
+
---
|
| 60 |
+
|
| 61 |
+
## Step 5 β Write or Update the Test
|
| 62 |
+
- [ ] Find the existing test file for this feature
|
| 63 |
+
- [ ] If the failing case was not covered β add a test for it now
|
| 64 |
+
- [ ] The test must reproduce the exact failure condition
|
| 65 |
+
- [ ] Run only the test for this file first:
|
| 66 |
+
```bash
|
| 67 |
+
npm test -- tests/unit/[feature-name].test.ts 2>&1 | tee logs/test.log
|
| 68 |
+
```
|
| 69 |
+
- [ ] Confirm it passes before running full suite
|
| 70 |
+
|
| 71 |
+
---
|
| 72 |
+
|
| 73 |
+
## Step 6 β Run Full Test Suite
|
| 74 |
+
```bash
|
| 75 |
+
bash scripts/test.sh
|
| 76 |
+
```
|
| 77 |
+
- [ ] All tests pass
|
| 78 |
+
- [ ] No previously passing test is now failing
|
| 79 |
+
- [ ] If a new failure appears β treat it as a new bug, do not chain fixes
|
| 80 |
+
|
| 81 |
+
---
|
| 82 |
+
|
| 83 |
+
## Step 7 β Update Docs
|
| 84 |
+
- [ ] Add one-liner to `docs/progress.md` under the feature
|
| 85 |
+
- [ ] If this was a repeated failure or needed a workaround β add to `docs/learnings.md`
|
| 86 |
+
- [ ] Append one line to `session/phase-log.md`
|
| 87 |
+
- [ ] Update `session/summary.md`
|
| 88 |
+
|
| 89 |
+
---
|
| 90 |
+
|
| 91 |
+
## Step 8 β Git Commit Message
|
| 92 |
+
Provide one-liner commit message in this format:
|
| 93 |
+
|
| 94 |
+
fix([scope]): [what was broken and what fixed it]
|
| 95 |
+
Examples:
|
| 96 |
+
fix(auth): handle null session before calling fetchUser
|
| 97 |
+
fix(dashboard): return empty array instead of null on no results
|
| 98 |
+
fix(db): add missing RLS policy for users table select
|
| 99 |
+
|
| 100 |
+
**Do NOT push to GitHub. Hand the message to the user.**
|
| 101 |
+
|
| 102 |
+
---
|
| 103 |
+
|
| 104 |
+
## Step 9 β Confirm with User
|
| 105 |
+
- [ ] Show exactly what was changed and in which file
|
| 106 |
+
- [ ] Show test results
|
| 107 |
+
- [ ] Show commit message
|
| 108 |
+
- [ ] Ask: "Confirmed fixed β ready to continue?"
|
| 109 |
+
- [ ] Do NOT proceed until user confirms
|
| 110 |
+
|
| 111 |
+
---
|
| 112 |
+
|
| 113 |
+
## Bugfix Decision Tree
|
| 114 |
+
|
| 115 |
+
Error occurs
|
| 116 |
+
β
|
| 117 |
+
βΌ
|
| 118 |
+
Read logs/errors.log
|
| 119 |
+
β
|
| 120 |
+
βββ Entry found β go to Step 2
|
| 121 |
+
β
|
| 122 |
+
βββ No entry β add logging first, reproduce error, then fix
|
| 123 |
+
β
|
| 124 |
+
βΌ
|
| 125 |
+
Check docs/learnings.md
|
| 126 |
+
β
|
| 127 |
+
βββ Known issue β apply fix directly
|
| 128 |
+
β
|
| 129 |
+
βββ New issue β isolate β fix β document
|
| 130 |
+
|
| 131 |
+
---
|
| 132 |
+
|
| 133 |
+
## Common Bug Patterns
|
| 134 |
+
|
| 135 |
+
| Symptom | Likely Cause | Where to Look |
|
| 136 |
+
|---------|-------------|---------------|
|
| 137 |
+
| Function returns null unexpectedly | Missing null check on input | The function's first few lines |
|
| 138 |
+
| Supabase returns empty data | RLS policy blocking query | Supabase Dashboard β Policies |
|
| 139 |
+
| Auth token errors | Session not refreshed | auth handler file |
|
| 140 |
+
| Test passes locally, fails in CI | Env var missing in CI | .env setup + CI config |
|
| 141 |
+
| Infinite re-render in React | useEffect dependency missing | The specific component file |
|
| 142 |
+
| Type error at runtime | TypeScript type not enforced at boundary | Input validation of the function |
|
| 143 |
+
|
| 144 |
+
---
|
| 145 |
+
|
| 146 |
+
## What NOT to Do
|
| 147 |
+
- Do not open the entire codebase to find the bug
|
| 148 |
+
- Do not fix multiple bugs in one session without separate commits
|
| 149 |
+
- Do not refactor while fixing β fix first, refactor separately
|
| 150 |
+
- Do not skip updating the test file
|
| 151 |
+
- Do not push to GitHub
|
| 152 |
+
- Do not chain fixes β one bug, one fix, one commit
|
docs/workflows/feature.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Workflow β Building a New Feature
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
Read this file every time you start building a new feature.
|
| 5 |
+
Follow steps in order. Do not skip any step.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Step 1 β Understand Before Coding
|
| 10 |
+
- [ ] Read the current phase prompt from `prompts/phase-X.md`
|
| 11 |
+
- [ ] Read `docs/progress.md` to confirm what is already done
|
| 12 |
+
- [ ] Read `docs/architecture.md` to understand existing structure
|
| 13 |
+
- [ ] Identify the exact files you will create or modify
|
| 14 |
+
- [ ] List all edge cases before writing any code
|
| 15 |
+
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
## Step 2 β Plan the Feature
|
| 19 |
+
State this out loud before coding:
|
| 20 |
+
|
| 21 |
+
Feature: [name]
|
| 22 |
+
Files to create: [list]
|
| 23 |
+
Files to modify: [list]
|
| 24 |
+
DB changes needed: yes/no
|
| 25 |
+
Edge cases: [list]
|
| 26 |
+
Tests needed: [list]
|
| 27 |
+
|
| 28 |
+
---
|
| 29 |
+
|
| 30 |
+
## Step 3 β DB Changes First (if needed)
|
| 31 |
+
- [ ] Write migration file before any application code
|
| 32 |
+
- [ ] Enable RLS on every new table
|
| 33 |
+
- [ ] Write RLS policies in the migration file
|
| 34 |
+
- [ ] Run `supabase db push` to apply
|
| 35 |
+
- [ ] Confirm in dashboard or via `supabase migration list`
|
| 36 |
+
- [ ] Reference `docs/deployment.md` for exact commands
|
| 37 |
+
|
| 38 |
+
---
|
| 39 |
+
|
| 40 |
+
## Step 4 β Write the Code
|
| 41 |
+
- [ ] Create or modify only the files identified in Step 2
|
| 42 |
+
- [ ] Add error handling to every function β follow `docs/debugging.md`
|
| 43 |
+
- [ ] No function should silently fail
|
| 44 |
+
- [ ] Handle all edge cases identified in Step 1
|
| 45 |
+
- [ ] No hardcoded values β use env vars or constants file
|
| 46 |
+
- [ ] No unused imports or dead code
|
| 47 |
+
|
| 48 |
+
---
|
| 49 |
+
|
| 50 |
+
## Step 5 β Write Tests Immediately
|
| 51 |
+
- [ ] Create test file at `tests/unit/` or `tests/integration/`
|
| 52 |
+
- [ ] Follow naming convention from `docs/testing.md`
|
| 53 |
+
- [ ] Cover all edge cases from Step 1
|
| 54 |
+
- [ ] Cover happy path + at least 2 failure paths per function
|
| 55 |
+
- [ ] Reference `.claude/skills/testing.md` for reusable patterns
|
| 56 |
+
|
| 57 |
+
---
|
| 58 |
+
|
| 59 |
+
## Step 6 β Run Tests
|
| 60 |
+
```bash
|
| 61 |
+
# Run only this feature's tests first
|
| 62 |
+
npm test -- tests/unit/[feature-name].test.ts 2>&1 | tee logs/test.log
|
| 63 |
+
|
| 64 |
+
# If passing, run full suite
|
| 65 |
+
bash scripts/test.sh
|
| 66 |
+
```
|
| 67 |
+
- [ ] All tests pass before moving forward
|
| 68 |
+
- [ ] If a test fails β read `logs/test.log`, fix the specific function only
|
| 69 |
+
|
| 70 |
+
---
|
| 71 |
+
|
| 72 |
+
## Step 7 β Self Review Checklist
|
| 73 |
+
Before declaring feature done:
|
| 74 |
+
- [ ] Error handling in place for every function
|
| 75 |
+
- [ ] No console.log left in code (only console.error with format)
|
| 76 |
+
- [ ] All edge cases handled
|
| 77 |
+
- [ ] Tests written and passing
|
| 78 |
+
- [ ] No new files created outside the plan in Step 2
|
| 79 |
+
- [ ] DB migration applied and confirmed
|
| 80 |
+
- [ ] RLS policies applied if new table was created
|
| 81 |
+
|
| 82 |
+
---
|
| 83 |
+
|
| 84 |
+
## Step 8 β Update Docs
|
| 85 |
+
- [ ] Add one-liner to `docs/progress.md`
|
| 86 |
+
- [ ] Append one line to `session/phase-log.md`
|
| 87 |
+
- [ ] Update `session/summary.md` with what was done
|
| 88 |
+
- [ ] If any failure or workaround occurred β add to `docs/learnings.md`
|
| 89 |
+
- [ ] If architecture changed β update `docs/architecture.md`
|
| 90 |
+
|
| 91 |
+
---
|
| 92 |
+
|
| 93 |
+
## Step 9 β Git Commit Message
|
| 94 |
+
Provide one-liner commit message in this format:
|
| 95 |
+
|
| 96 |
+
feat([scope]): [what was done in plain english]
|
| 97 |
+
Examples:
|
| 98 |
+
feat(auth): add email login with session handling
|
| 99 |
+
feat(dashboard): add user profile fetch with error handling
|
| 100 |
+
feat(db): add users table migration with RLS policies
|
| 101 |
+
|
| 102 |
+
**Do NOT push to GitHub. Hand the message to the user.**
|
| 103 |
+
|
| 104 |
+
---
|
| 105 |
+
|
| 106 |
+
## Step 10 β Confirm with User
|
| 107 |
+
- [ ] Show user what was built
|
| 108 |
+
- [ ] Show test results
|
| 109 |
+
- [ ] Show commit message
|
| 110 |
+
- [ ] Ask: "Ready to move to next feature or phase?"
|
| 111 |
+
- [ ] Do NOT proceed until user confirms
|
| 112 |
+
|
| 113 |
+
---
|
| 114 |
+
|
| 115 |
+
## What NOT to Do
|
| 116 |
+
- Do not write tests after all features are done
|
| 117 |
+
- Do not modify files outside the plan without flagging it
|
| 118 |
+
- Do not proceed to next feature if current tests are failing
|
| 119 |
+
- Do not push to GitHub
|
| 120 |
+
- Do not skip the docs update in Step 8
|
| 121 |
+
- Do not start next phase without user confirmation
|
docs/workflows/refactor.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Workflow β Refactoring Code
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
Read this file every time you need to refactor existing code.
|
| 5 |
+
Refactoring means improving structure without changing behaviour.
|
| 6 |
+
If behaviour changes β that is a feature, not a refactor.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Core Principle
|
| 11 |
+
**Tests must pass before AND after every refactor.**
|
| 12 |
+
If tests fail after refactor β you changed behaviour. Revert and retry.
|
| 13 |
+
Never refactor and fix a bug in the same commit.
|
| 14 |
+
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
## Step 1 β Confirm Refactor Scope
|
| 18 |
+
Before touching any code, state this out loud:
|
| 19 |
+
|
| 20 |
+
Refactor target: [file or function]
|
| 21 |
+
Reason for refactor: [why this needs to change]
|
| 22 |
+
Behaviour change: none
|
| 23 |
+
Files I will touch: [exact list]
|
| 24 |
+
Files I will NOT touch: [everything else]
|
| 25 |
+
|
| 26 |
+
- [ ] Confirm with user that this refactor is needed now
|
| 27 |
+
- [ ] Confirm all current tests pass before starting:
|
| 28 |
+
```bash
|
| 29 |
+
bash scripts/test.sh
|
| 30 |
+
```
|
| 31 |
+
- [ ] Do not proceed if any test is currently failing
|
| 32 |
+
|
| 33 |
+
---
|
| 34 |
+
|
| 35 |
+
## Step 2 β Identify What to Refactor
|
| 36 |
+
Valid reasons to refactor:
|
| 37 |
+
- [ ] Duplicate logic across multiple functions
|
| 38 |
+
- [ ] Function doing more than one thing
|
| 39 |
+
- [ ] Deeply nested conditionals reducing readability
|
| 40 |
+
- [ ] Magic numbers or hardcoded strings
|
| 41 |
+
- [ ] Missing or inconsistent error handling format
|
| 42 |
+
- [ ] Inconsistent naming conventions
|
| 43 |
+
- [ ] Dead code or unused imports
|
| 44 |
+
|
| 45 |
+
Not valid reasons:
|
| 46 |
+
- "It looks messy" without a specific structural problem
|
| 47 |
+
- Preference for a different syntax that does the same thing
|
| 48 |
+
- Rewriting working code during a bug fix session
|
| 49 |
+
|
| 50 |
+
---
|
| 51 |
+
|
| 52 |
+
## Step 3 β Refactor in Small Steps
|
| 53 |
+
- [ ] Change one thing at a time
|
| 54 |
+
- [ ] Run tests after each individual change
|
| 55 |
+
- [ ] Do not batch multiple refactors into one step
|
| 56 |
+
- [ ] Keep original logic visible until new logic is confirmed working
|
| 57 |
+
|
| 58 |
+
### Order of operations:
|
| 59 |
+
|
| 60 |
+
Extract repeated logic into a shared utility function
|
| 61 |
+
Simplify conditionals (early returns over nested if/else)
|
| 62 |
+
Rename for clarity (variables, functions)
|
| 63 |
+
Remove dead code and unused imports
|
| 64 |
+
Standardise error handling format per docs/debugging.md
|
| 65 |
+
|
| 66 |
+
---
|
| 67 |
+
|
| 68 |
+
## Step 4 β Run Tests After Every Change
|
| 69 |
+
```bash
|
| 70 |
+
# After each individual change
|
| 71 |
+
npm test -- tests/unit/[affected-file].test.ts 2>&1 | tee logs/test.log
|
| 72 |
+
|
| 73 |
+
# After all changes complete
|
| 74 |
+
bash scripts/test.sh
|
| 75 |
+
```
|
| 76 |
+
- [ ] Every test that passed before must still pass
|
| 77 |
+
- [ ] If a test fails β revert the last change, do not chain fixes
|
| 78 |
+
|
| 79 |
+
---
|
| 80 |
+
|
| 81 |
+
## Step 5 β Update Tests if Needed
|
| 82 |
+
Refactoring may require test updates only in these cases:
|
| 83 |
+
- [ ] A function was renamed β update test description and import
|
| 84 |
+
- [ ] A function was split into two β write tests for both
|
| 85 |
+
- [ ] A utility was extracted β write a unit test for the utility
|
| 86 |
+
|
| 87 |
+
Do NOT update tests to make them pass after a refactor.
|
| 88 |
+
If a test fails after refactor β the refactor changed behaviour β revert.
|
| 89 |
+
|
| 90 |
+
---
|
| 91 |
+
|
| 92 |
+
## Step 6 β Self Review Checklist
|
| 93 |
+
Before declaring refactor done:
|
| 94 |
+
- [ ] Behaviour is identical before and after
|
| 95 |
+
- [ ] All tests pass
|
| 96 |
+
- [ ] No new files created outside the plan in Step 1
|
| 97 |
+
- [ ] Error handling format matches `docs/debugging.md`
|
| 98 |
+
- [ ] No console.log left in code
|
| 99 |
+
- [ ] No dead code or unused imports remain
|
| 100 |
+
- [ ] No hardcoded values introduced
|
| 101 |
+
|
| 102 |
+
---
|
| 103 |
+
|
| 104 |
+
## Step 7 β Update Docs
|
| 105 |
+
- [ ] Update `docs/architecture.md` if structure changed
|
| 106 |
+
- [ ] Add one-liner to `docs/progress.md`
|
| 107 |
+
- [ ] Append one line to `session/phase-log.md`
|
| 108 |
+
- [ ] Update `session/summary.md`
|
| 109 |
+
- [ ] If a pattern was discovered that saves time β add to `docs/learnings.md`
|
| 110 |
+
|
| 111 |
+
---
|
| 112 |
+
|
| 113 |
+
## Step 8 β Git Commit Message
|
| 114 |
+
Provide one-liner commit message in this format:
|
| 115 |
+
|
| 116 |
+
refactor([scope]): [what was improved and how]
|
| 117 |
+
Examples:
|
| 118 |
+
refactor(auth): extract session validation into shared utility
|
| 119 |
+
refactor(dashboard): replace nested conditionals with early returns
|
| 120 |
+
refactor(db): standardise error handling across all supabase queries
|
| 121 |
+
refactor(utils): remove dead code and unused imports from helpers
|
| 122 |
+
|
| 123 |
+
**Do NOT push to GitHub. Hand the message to the user.**
|
| 124 |
+
|
| 125 |
+
---
|
| 126 |
+
|
| 127 |
+
## Step 9 β Confirm with User
|
| 128 |
+
- [ ] Show exactly what changed and in which files
|
| 129 |
+
- [ ] Show before/after for key changes
|
| 130 |
+
- [ ] Show test results confirming no behaviour change
|
| 131 |
+
- [ ] Show commit message
|
| 132 |
+
- [ ] Ask: "Refactor complete β ready to continue?"
|
| 133 |
+
- [ ] Do NOT proceed until user confirms
|
| 134 |
+
|
| 135 |
+
---
|
| 136 |
+
|
| 137 |
+
## Refactor Decision Tree
|
| 138 |
+
|
| 139 |
+
Refactor needed?
|
| 140 |
+
β
|
| 141 |
+
βΌ
|
| 142 |
+
All tests passing?
|
| 143 |
+
β
|
| 144 |
+
βββ No β fix failing tests first, then refactor
|
| 145 |
+
β
|
| 146 |
+
βββ Yes β confirm scope β refactor one thing at a time
|
| 147 |
+
β
|
| 148 |
+
βΌ
|
| 149 |
+
Run tests after each change
|
| 150 |
+
β
|
| 151 |
+
βββ Pass β continue next change
|
| 152 |
+
β
|
| 153 |
+
βββ Fail β revert last change
|
| 154 |
+
β reassess scope
|
| 155 |
+
β do not chain fixes
|
| 156 |
+
|
| 157 |
+
---
|
| 158 |
+
|
| 159 |
+
## Refactor Patterns
|
| 160 |
+
|
| 161 |
+
### Extract repeated logic
|
| 162 |
+
```typescript
|
| 163 |
+
// Before β same null check in 3 functions
|
| 164 |
+
if (!userId || userId === '') return null
|
| 165 |
+
|
| 166 |
+
// After β shared utility
|
| 167 |
+
function isValidId(id: string | null | undefined): boolean {
|
| 168 |
+
return !!id && id.trim() !== ''
|
| 169 |
+
}
|
| 170 |
+
```
|
| 171 |
+
|
| 172 |
+
### Early returns over nested conditionals
|
| 173 |
+
```typescript
|
| 174 |
+
// Before
|
| 175 |
+
function processUser(user: User | null) {
|
| 176 |
+
if (user) {
|
| 177 |
+
if (user.isActive) {
|
| 178 |
+
if (user.hasProfile) {
|
| 179 |
+
return user.profile
|
| 180 |
+
}
|
| 181 |
+
}
|
| 182 |
+
}
|
| 183 |
+
return null
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
// After
|
| 187 |
+
function processUser(user: User | null) {
|
| 188 |
+
if (!user) return null
|
| 189 |
+
if (!user.isActive) return null
|
| 190 |
+
if (!user.hasProfile) return null
|
| 191 |
+
return user.profile
|
| 192 |
+
}
|
| 193 |
+
```
|
| 194 |
+
|
| 195 |
+
### Standardise error handling
|
| 196 |
+
```typescript
|
| 197 |
+
// Before β inconsistent
|
| 198 |
+
try {
|
| 199 |
+
...
|
| 200 |
+
} catch (e) {
|
| 201 |
+
console.log(e) // wrong
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
// After β per docs/debugging.md
|
| 205 |
+
try {
|
| 206 |
+
...
|
| 207 |
+
} catch (err) {
|
| 208 |
+
handleError(err, 'lib/users.ts', 'processUser')
|
| 209 |
+
return null
|
| 210 |
+
}
|
| 211 |
+
```
|
| 212 |
+
|
| 213 |
+
---
|
| 214 |
+
|
| 215 |
+
## What NOT to Do
|
| 216 |
+
- Do not refactor and fix a bug in the same commit
|
| 217 |
+
- Do not refactor files not listed in Step 1
|
| 218 |
+
- Do not update tests to force them to pass after refactor
|
| 219 |
+
- Do not batch all refactors into one large change
|
| 220 |
+
- Do not refactor during a feature build session
|
| 221 |
+
- Do not push to GitHub
|
| 222 |
+
- Do not start a refactor if any test is currently failing
|
logs/errors.log
ADDED
|
File without changes
|
logs/test.log
ADDED
|
File without changes
|
prompts/phase-0.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Phase 0 β Critic Quality Gate
|
| 2 |
+
> Paste this entire prompt into a fresh Claude Code session. Do not proceed to Phase 1 until the gate check at the bottom passes.
|
| 3 |
+
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
You are helping me build a multi-agent RL environment called the "Viral Script Debugging Engine" for the Meta Γ OpenEnv Hackathon. Before any environment code is written, we need to validate that the Critic agent produces high-quality, adversarial output β because the entire RL loop depends on it.
|
| 7 |
+
|
| 8 |
+
**Project context:**
|
| 9 |
+
- The system trains an LLM (the Arbitrator) via GRPO to decide which script improvements to make
|
| 10 |
+
- A Critic agent attacks creator scripts with specific, falsifiable claims
|
| 11 |
+
- A Defender agent argues for what should be preserved
|
| 12 |
+
- The Arbitrator (RL-trained model) decides which critique to act on each step
|
| 13 |
+
- If the Critic produces vague output, the RL signal collapses
|
| 14 |
+
|
| 15 |
+
**Your task for this phase:** Build the Critic agent and an evaluation harness to validate its output quality before any other code is written.
|
| 16 |
+
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
## Directory structure to create
|
| 20 |
+
|
| 21 |
+
```
|
| 22 |
+
viral_script_engine/
|
| 23 |
+
βββ agents/
|
| 24 |
+
β βββ __init__.py
|
| 25 |
+
β βββ critic.py
|
| 26 |
+
βββ data/
|
| 27 |
+
β βββ test_scripts/
|
| 28 |
+
β βββ golden_fixtures/
|
| 29 |
+
βββ evaluation/
|
| 30 |
+
β βββ __init__.py
|
| 31 |
+
β βββ critic_evaluator.py
|
| 32 |
+
βββ scripts/
|
| 33 |
+
β βββ run_critic_gate.py
|
| 34 |
+
βββ requirements.txt
|
| 35 |
+
βββ README.md
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
---
|
| 39 |
+
|
| 40 |
+
## Step 1 β `agents/critic.py`
|
| 41 |
+
|
| 42 |
+
Implement a `CriticAgent` class:
|
| 43 |
+
|
| 44 |
+
```python
|
| 45 |
+
class CriticAgent:
|
| 46 |
+
def __init__(self, model_name: str = "claude-sonnet-4-20250514"):
|
| 47 |
+
# Use the Anthropic Python SDK
|
| 48 |
+
|
| 49 |
+
def critique(self, script: str, region: str, platform: str, niche: str) -> CritiqueOutput:
|
| 50 |
+
# Returns a CritiqueOutput dataclass
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
The `CritiqueOutput` and `CritiqueClaim` Pydantic models must have these exact fields:
|
| 54 |
+
|
| 55 |
+
```python
|
| 56 |
+
class CritiqueClaim(BaseModel):
|
| 57 |
+
claim_id: str # e.g. "C1", "C2"
|
| 58 |
+
critique_class: str # one of: "hook_weakness" | "pacing_issue" | "cultural_mismatch" | "cta_buried" | "coherence_break" | "retention_risk"
|
| 59 |
+
claim_text: str
|
| 60 |
+
timestamp_range: str # e.g. "0:00-0:03" or "N/A"
|
| 61 |
+
evidence: str # exact quote from the script supporting this claim
|
| 62 |
+
is_falsifiable: bool
|
| 63 |
+
severity: str # "low" | "medium" | "high"
|
| 64 |
+
|
| 65 |
+
class CritiqueOutput(BaseModel):
|
| 66 |
+
claims: List[CritiqueClaim]
|
| 67 |
+
overall_severity: str
|
| 68 |
+
raw_response: str
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
**System prompt to use (exact):**
|
| 72 |
+
```
|
| 73 |
+
You are an expert social media content critic specialising in short-form video scripts for Reels and YouTube Shorts. Your job is to find specific, real problems in creator scripts β not vague feedback.
|
| 74 |
+
|
| 75 |
+
RULES:
|
| 76 |
+
1. Every claim must cite a specific part of the script (quote it or reference the timestamp range)
|
| 77 |
+
2. Every claim must be falsifiable β a human editor must be able to verify it by re-reading the script
|
| 78 |
+
3. Never say "the hook is weak" β say "the hook at 0:00-0:03 promises [X] but the script delivers [Y] at 0:22, by which time most viewers have already dropped off"
|
| 79 |
+
4. Focus on the 6 critique classes: hook_weakness, pacing_issue, cultural_mismatch, cta_buried, coherence_break, retention_risk
|
| 80 |
+
5. Produce between 3 and 6 claims per script. No more, no less.
|
| 81 |
+
6. For each claim, assign a timestamp range if the issue is locatable in the script. Use "N/A" only if it's a structural issue spanning the whole script.
|
| 82 |
+
|
| 83 |
+
OUTPUT FORMAT (respond ONLY with valid JSON, no markdown, no preamble):
|
| 84 |
+
{
|
| 85 |
+
"claims": [
|
| 86 |
+
{
|
| 87 |
+
"claim_id": "C1",
|
| 88 |
+
"critique_class": "hook_weakness",
|
| 89 |
+
"claim_text": "...",
|
| 90 |
+
"timestamp_range": "0:00-0:03",
|
| 91 |
+
"evidence": "exact quote from script",
|
| 92 |
+
"is_falsifiable": true,
|
| 93 |
+
"severity": "high"
|
| 94 |
+
}
|
| 95 |
+
],
|
| 96 |
+
"overall_severity": "high"
|
| 97 |
+
}
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
User prompt format:
|
| 101 |
+
```
|
| 102 |
+
SCRIPT TO CRITIQUE:
|
| 103 |
+
{script}
|
| 104 |
+
|
| 105 |
+
TARGET REGION: {region}
|
| 106 |
+
PLATFORM: {platform}
|
| 107 |
+
NICHE: {niche}
|
| 108 |
+
|
| 109 |
+
Produce your critique now.
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
If JSON parsing fails, retry once with a stricter prompt. If it fails twice, raise `CriticParseError`.
|
| 113 |
+
|
| 114 |
+
---
|
| 115 |
+
|
| 116 |
+
## Step 2 β `data/test_scripts/scripts.json`
|
| 117 |
+
|
| 118 |
+
Create 10 realistic 60β90 second Reel/Shorts scripts saved as a JSON array. Distribution:
|
| 119 |
+
- S01βS03: Mumbai Gen Z (finance, fashion, tech)
|
| 120 |
+
- S04βS06: Tier-2 Hindi belt (agriculture, small business, local culture)
|
| 121 |
+
- S07βS08: Pan-India English (startup advice, productivity)
|
| 122 |
+
- S09βS10: Hinglish (mixed Hindi-English)
|
| 123 |
+
|
| 124 |
+
Each entry:
|
| 125 |
+
```json
|
| 126 |
+
{
|
| 127 |
+
"script_id": "S01",
|
| 128 |
+
"region": "Mumbai Gen Z",
|
| 129 |
+
"platform": "Reels",
|
| 130 |
+
"niche": "personal finance",
|
| 131 |
+
"script_text": "...(100-200 words)...",
|
| 132 |
+
"known_flaws": ["buried_hook", "no_cta"]
|
| 133 |
+
}
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
S01βS04 should have **obvious** single flaws. S05βS07 should have **subtle** flaws. S08βS10 should have **conflicting** issues where fixing one hurts another. Do not make all scripts bad in the same way.
|
| 137 |
+
|
| 138 |
+
---
|
| 139 |
+
|
| 140 |
+
## Step 3 β `evaluation/critic_evaluator.py`
|
| 141 |
+
|
| 142 |
+
```python
|
| 143 |
+
class CriticEvaluator:
|
| 144 |
+
def evaluate(self, output: CritiqueOutput, script_text: str) -> EvaluationResult:
|
| 145 |
+
pass
|
| 146 |
+
|
| 147 |
+
def batch_evaluate(self, results: List[Tuple[CritiqueOutput, str]]) -> BatchEvaluationResult:
|
| 148 |
+
pass
|
| 149 |
+
```
|
| 150 |
+
|
| 151 |
+
`EvaluationResult` fields:
|
| 152 |
+
- `claim_count: int` β must be 3β6 to pass
|
| 153 |
+
- `specificity_score: float` β fraction of claims where `evidence` is a substring of the script (use substring match)
|
| 154 |
+
- `falsifiability_score: float` β fraction of claims with `is_falsifiable=True`
|
| 155 |
+
- `timestamp_coverage: float` β fraction of claims with a non-"N/A" timestamp_range
|
| 156 |
+
- `critique_class_diversity: float` β unique critique_classes / 6
|
| 157 |
+
- `passes_gate: bool` β True if: `claim_count >= 3 AND specificity_score >= 0.6 AND falsifiability_score >= 0.7`
|
| 158 |
+
|
| 159 |
+
`BatchEvaluationResult` fields:
|
| 160 |
+
- `pass_count: int`
|
| 161 |
+
- `pass_rate: float`
|
| 162 |
+
- `passes_overall_gate: bool` β True if `pass_rate >= 0.8`
|
| 163 |
+
- `per_script_results: List[EvaluationResult]`
|
| 164 |
+
- `failing_scripts: List[str]`
|
| 165 |
+
|
| 166 |
+
The evaluator must be purely rule-based β no LLM calls.
|
| 167 |
+
|
| 168 |
+
---
|
| 169 |
+
|
| 170 |
+
## Step 4 β `scripts/run_critic_gate.py`
|
| 171 |
+
|
| 172 |
+
CLI that:
|
| 173 |
+
1. Loads all 10 scripts from `data/test_scripts/scripts.json`
|
| 174 |
+
2. Runs CriticAgent on each
|
| 175 |
+
3. Runs CriticEvaluator on each result
|
| 176 |
+
4. Prints a per-script pass/fail report using `rich`
|
| 177 |
+
5. Prints overall GATE PASS or GATE FAIL
|
| 178 |
+
6. If gate passes: saves all outputs as JSON to `data/golden_fixtures/fixture_S01.json` etc.
|
| 179 |
+
7. If gate fails: prints which scripts failed and which scores were below threshold
|
| 180 |
+
|
| 181 |
+
Flags:
|
| 182 |
+
- `--max-retries 3`: retry failed scripts up to N times with a tighter prompt
|
| 183 |
+
- `--dry-run`: run only first 2 scripts
|
| 184 |
+
|
| 185 |
+
---
|
| 186 |
+
|
| 187 |
+
## Step 5 β `requirements.txt`
|
| 188 |
+
|
| 189 |
+
```
|
| 190 |
+
anthropic>=0.40.0
|
| 191 |
+
sentence-transformers>=2.7.0
|
| 192 |
+
numpy>=1.26.0
|
| 193 |
+
pydantic>=2.0.0
|
| 194 |
+
python-dotenv>=1.0.0
|
| 195 |
+
rich>=13.0.0
|
| 196 |
+
pytest>=8.0.0
|
| 197 |
+
```
|
| 198 |
+
|
| 199 |
+
---
|
| 200 |
+
|
| 201 |
+
## Step 6 β `tests/test_critic.py`
|
| 202 |
+
|
| 203 |
+
- Test `CritiqueOutput` parses correctly from a mock LLM JSON response
|
| 204 |
+
- Test `EvaluationResult` correctly identifies a passing vs failing critique
|
| 205 |
+
- Test CLI exits with code 1 if gate fails, 0 if gate passes
|
| 206 |
+
- Mock all Anthropic API calls β no real API calls in tests
|
| 207 |
+
|
| 208 |
+
---
|
| 209 |
+
|
| 210 |
+
## Constraints
|
| 211 |
+
|
| 212 |
+
- Use the Anthropic Python SDK only (not OpenAI)
|
| 213 |
+
- Store API key in `.env`, load with `python-dotenv`
|
| 214 |
+
- All models use Pydantic for validation
|
| 215 |
+
- Evaluator has zero LLM calls
|
| 216 |
+
- Use `rich` for all console output (progress bars, coloured pass/fail)
|
| 217 |
+
- All JSON outputs pretty-printed with `indent=2`
|
| 218 |
+
|
| 219 |
+
---
|
| 220 |
+
|
| 221 |
+
## Gate check
|
| 222 |
+
|
| 223 |
+
Run:
|
| 224 |
+
```
|
| 225 |
+
python scripts/run_critic_gate.py --dry-run
|
| 226 |
+
```
|
| 227 |
+
|
| 228 |
+
Must print `PHASE 0 GATE: PASS` when 8/10 scripts produce β₯3 specific, timestamped, falsifiable claims. Do not open Phase 1 until this passes.
|
prompts/phase-1.md
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Phase 1 β OpenEnv Scaffold + R1/R2 Rewards
|
| 2 |
+
> Paste this entire prompt into a fresh Claude Code session. Phase 0 must be complete and golden fixtures saved before starting this phase.
|
| 3 |
+
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
Phase 0 is complete. The Critic agent is validated and all golden fixtures are saved in `data/golden_fixtures/`. Now build the core OpenEnv environment with two reward signals and get a complete dummy episode running end-to-end.
|
| 7 |
+
|
| 8 |
+
**Current state:**
|
| 9 |
+
- `agents/critic.py` β CriticAgent working and validated
|
| 10 |
+
- `data/golden_fixtures/` β 10 validated critic outputs as JSON
|
| 11 |
+
- `data/test_scripts/scripts.json` β 10 test scripts
|
| 12 |
+
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
## New files to create
|
| 16 |
+
|
| 17 |
+
```
|
| 18 |
+
viral_script_engine/
|
| 19 |
+
βββ environment/
|
| 20 |
+
β βββ __init__.py
|
| 21 |
+
β βββ env.py
|
| 22 |
+
β βββ actions.py
|
| 23 |
+
β βββ observations.py
|
| 24 |
+
β βββ episode_state.py
|
| 25 |
+
βββ rewards/
|
| 26 |
+
β βββ __init__.py
|
| 27 |
+
β βββ base.py
|
| 28 |
+
β βββ r1_hook_strength.py
|
| 29 |
+
β βββ r2_coherence.py
|
| 30 |
+
β βββ reward_aggregator.py
|
| 31 |
+
βββ agents/
|
| 32 |
+
β βββ rewriter.py # NEW β do not modify critic.py
|
| 33 |
+
βββ tests/
|
| 34 |
+
β βββ test_environment.py
|
| 35 |
+
β βββ test_rewards.py
|
| 36 |
+
βββ scripts/
|
| 37 |
+
βββ run_dummy_episode.py
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
**The environment must follow OpenEnv's Gymnasium-compatible API exactly:**
|
| 41 |
+
- `reset()` β `(observation: dict, info: dict)`
|
| 42 |
+
- `step(action)` β `(observation: dict, reward: float, terminated: bool, truncated: bool, info: dict)`
|
| 43 |
+
- `state()` β full current state dict
|
| 44 |
+
|
| 45 |
+
---
|
| 46 |
+
|
| 47 |
+
## Step 1 β `environment/actions.py`
|
| 48 |
+
|
| 49 |
+
```python
|
| 50 |
+
from enum import Enum
|
| 51 |
+
from pydantic import BaseModel
|
| 52 |
+
|
| 53 |
+
class ActionType(str, Enum):
|
| 54 |
+
HOOK_REWRITE = "hook_rewrite"
|
| 55 |
+
SECTION_REORDER = "section_reorder"
|
| 56 |
+
CULTURAL_REF_SUB = "cultural_ref_sub"
|
| 57 |
+
CTA_PLACEMENT = "cta_placement"
|
| 58 |
+
|
| 59 |
+
class ArbitratorAction(BaseModel):
|
| 60 |
+
action_type: ActionType
|
| 61 |
+
target_section: str # "hook" | "body" | "cta" | "full"
|
| 62 |
+
instruction: str # natural language instruction to the Rewriter
|
| 63 |
+
critique_claim_id: str # which CritiqueClaim this responds to, e.g. "C2"
|
| 64 |
+
reasoning: str # why this action was chosen (used in demo and logs)
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
## Step 2 β `environment/observations.py`
|
| 70 |
+
|
| 71 |
+
```python
|
| 72 |
+
class RewardComponents(BaseModel):
|
| 73 |
+
r1_hook_strength: Optional[float] = None
|
| 74 |
+
r2_coherence: Optional[float] = None
|
| 75 |
+
r3_cultural_alignment: Optional[float] = None
|
| 76 |
+
r4_debate_resolution: Optional[float] = None
|
| 77 |
+
r5_defender_preservation: Optional[float] = None
|
| 78 |
+
r6_retention_proxy: Optional[float] = None
|
| 79 |
+
anti_gaming_penalty: float = 0.0
|
| 80 |
+
total: float = 0.0
|
| 81 |
+
|
| 82 |
+
def compute_total(self) -> float:
|
| 83 |
+
# Weights: R1=0.25, R2=0.20, R3=0.20, R4=0.20, R5=0.15
|
| 84 |
+
# Sum only non-None components, normalise weights to sum to 1.0
|
| 85 |
+
# Subtract anti_gaming_penalty, clip to [0, 1]
|
| 86 |
+
|
| 87 |
+
class DebateRound(BaseModel):
|
| 88 |
+
step_num: int
|
| 89 |
+
critic_claims: List[CritiqueClaim]
|
| 90 |
+
defender_response: Optional[Any] = None # DefenderOutput added in Phase 2
|
| 91 |
+
arbitrator_action: Optional[ArbitratorAction] = None
|
| 92 |
+
rewrite_diff: Optional[str] = None # unified diff string
|
| 93 |
+
reward_components: Optional[RewardComponents] = None
|
| 94 |
+
|
| 95 |
+
class Observation(BaseModel):
|
| 96 |
+
current_script: str
|
| 97 |
+
original_script: str
|
| 98 |
+
region: str
|
| 99 |
+
platform: str
|
| 100 |
+
niche: str
|
| 101 |
+
step_num: int
|
| 102 |
+
max_steps: int
|
| 103 |
+
debate_history: List[DebateRound]
|
| 104 |
+
reward_components: RewardComponents
|
| 105 |
+
difficulty_level: str # "easy" | "medium" | "hard" | "self_generated"
|
| 106 |
+
episode_id: str # UUID
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
---
|
| 110 |
+
|
| 111 |
+
## Step 3 β `rewards/r1_hook_strength.py`
|
| 112 |
+
|
| 113 |
+
Fully rule-based β zero LLM calls. Scores the first 3 sentences or ~50 words (whichever is shorter).
|
| 114 |
+
|
| 115 |
+
**5 checks, each worth 0.2:**
|
| 116 |
+
|
| 117 |
+
1. **Promise check** β hook contains a concrete promise or specific claim. Look for: numbers, "how to", "why", "what happens when", "I made X". Fail: generic openers like "Hey guys", "Welcome back", "Today we're talking about".
|
| 118 |
+
|
| 119 |
+
2. **Curiosity gap check** β hook creates unresolved tension. Look for: question structures, "but here's the thing", "most people don't know", "the secret is". Fail: hook that immediately resolves the tension in the same sentence.
|
| 120 |
+
|
| 121 |
+
3. **Specificity check** β at least one specific number, proper noun, or concrete detail. Look for: digit sequences, proper nouns, specific product/place names.
|
| 122 |
+
|
| 123 |
+
4. **Front-loading check** β first sentence contains at least 2 of the above signals.
|
| 124 |
+
|
| 125 |
+
5. **Anti-filler check** β avoids these dead openers (case-insensitive): `["hey guys", "welcome back", "today i want to", "so today", "in this video", "what's up everyone", "hey everyone", "guys today", "hello everyone", "so basically"]`
|
| 126 |
+
|
| 127 |
+
`score = checks_passed / 5`, clipped to [0, 1].
|
| 128 |
+
|
| 129 |
+
Return a `HookRewardResult` with: `score`, `checks_passed`, `check_details: dict`.
|
| 130 |
+
|
| 131 |
+
---
|
| 132 |
+
|
| 133 |
+
## Step 4 β `rewards/r2_coherence.py`
|
| 134 |
+
|
| 135 |
+
Uses `sentence-transformers all-MiniLM-L6-v2`. Cache embeddings by `hash(text)`.
|
| 136 |
+
|
| 137 |
+
Score mapping from raw cosine similarity:
|
| 138 |
+
- `< 0.65` β `0.0` (drifted too far from creator intent)
|
| 139 |
+
- `0.65β0.80` β linearly map to `0.0β0.5`
|
| 140 |
+
- `0.80β0.95` β linearly map to `0.5β1.0`
|
| 141 |
+
- `> 0.95` β `0.8` (barely changed β penalise inaction)
|
| 142 |
+
|
| 143 |
+
Return a `CoherenceRewardResult` with: `score`, `raw_similarity`, `interpretation: str`.
|
| 144 |
+
|
| 145 |
+
---
|
| 146 |
+
|
| 147 |
+
## Step 5 β `rewards/reward_aggregator.py`
|
| 148 |
+
|
| 149 |
+
```python
|
| 150 |
+
class RewardAggregator:
|
| 151 |
+
WEIGHTS = {"r1": 0.25, "r2": 0.20, "r3": 0.20, "r4": 0.20, "r5": 0.15}
|
| 152 |
+
|
| 153 |
+
def compute(
|
| 154 |
+
self,
|
| 155 |
+
components: RewardComponents,
|
| 156 |
+
episode_start_components: RewardComponents,
|
| 157 |
+
action_history: List[ActionType],
|
| 158 |
+
) -> RewardComponents:
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
**Anti-gaming rule 1 β Catastrophic drop:**
|
| 162 |
+
For each reward component not None in both current and episode_start: if `current < episode_start - 0.2`, set total = 0.0 and log which component triggered it.
|
| 163 |
+
|
| 164 |
+
**Anti-gaming rule 2 β Action diversity:**
|
| 165 |
+
If the last 3 entries in `action_history` are all the same `ActionType`, subtract 0.15 from total before clipping.
|
| 166 |
+
|
| 167 |
+
Always clip final total to [0, 1]. Set `components.anti_gaming_penalty` to total deduction applied.
|
| 168 |
+
|
| 169 |
+
---
|
| 170 |
+
|
| 171 |
+
## Step 6 β `agents/rewriter.py`
|
| 172 |
+
|
| 173 |
+
```python
|
| 174 |
+
class RewriterAgent:
|
| 175 |
+
def __init__(self, model_name: str = "claude-sonnet-4-20250514"):
|
| 176 |
+
pass
|
| 177 |
+
|
| 178 |
+
def rewrite(self, current_script: str, action: ArbitratorAction) -> RewriteResult:
|
| 179 |
+
pass
|
| 180 |
+
```
|
| 181 |
+
|
| 182 |
+
System prompt: "You are a professional script editor for short-form social media video. Apply ONLY the instruction given. Do not make any other changes. Do not add new ideas. Do not change the creator's voice or regional language patterns. Return ONLY the rewritten script text, no commentary."
|
| 183 |
+
|
| 184 |
+
User prompt includes: `current_script`, `action.action_type`, `action.instruction`, `action.target_section`.
|
| 185 |
+
|
| 186 |
+
`RewriteResult` has: `rewritten_script`, `diff` (unified diff string), `word_count_delta: int`.
|
| 187 |
+
|
| 188 |
+
---
|
| 189 |
+
|
| 190 |
+
## Step 7 β `environment/env.py`
|
| 191 |
+
|
| 192 |
+
```python
|
| 193 |
+
from openenv import Environment
|
| 194 |
+
|
| 195 |
+
class ViralScriptEnv(Environment):
|
| 196 |
+
DIFFICULTY_LEVELS = ["easy", "medium", "hard", "self_generated"]
|
| 197 |
+
|
| 198 |
+
def __init__(
|
| 199 |
+
self,
|
| 200 |
+
scripts_path: str = "data/test_scripts/scripts.json",
|
| 201 |
+
max_steps: int = 5,
|
| 202 |
+
difficulty: str = "easy",
|
| 203 |
+
use_anti_gaming: bool = True,
|
| 204 |
+
):
|
| 205 |
+
# Script tiers: easy=S01-S04, medium=S05-S07, hard=S08-S10
|
| 206 |
+
# Init: CriticAgent, RewriterAgent, RewardAggregator, HookStrengthReward, CoherenceReward
|
| 207 |
+
# R3-R5 are None until Phase 2
|
| 208 |
+
|
| 209 |
+
def reset(self, seed=None, options=None) -> Tuple[dict, dict]:
|
| 210 |
+
# 1. Sample script from current difficulty tier
|
| 211 |
+
# 2. Reset all debate state
|
| 212 |
+
# 3. Compute initial R1 and R2 on unmodified script β save as episode_start_rewards
|
| 213 |
+
# 4. Return (observation_dict, info_dict)
|
| 214 |
+
|
| 215 |
+
def step(self, action: dict) -> Tuple[dict, float, bool, bool, dict]:
|
| 216 |
+
# 1. Parse action dict β ArbitratorAction
|
| 217 |
+
# 2. Run Critic on current script
|
| 218 |
+
# 3. [Defender: skip in Phase 1, wire in Phase 2]
|
| 219 |
+
# 4. Run Rewriter with the action β new script
|
| 220 |
+
# 5. Compute R1, R2 on new script
|
| 221 |
+
# 6. Run RewardAggregator (anti-gaming checks applied)
|
| 222 |
+
# 7. Append DebateRound to debate_history
|
| 223 |
+
# 8. Increment step counter
|
| 224 |
+
# 9. terminated if step_num >= max_steps OR total_reward >= 0.9
|
| 225 |
+
# 10. info_dict must include: reward_components, anti_gaming_triggered, penalty_reason
|
| 226 |
+
|
| 227 |
+
def state(self) -> dict:
|
| 228 |
+
# Full JSON-serialisable state: current_script, original_script, debate_history,
|
| 229 |
+
# reward_components, step_num, difficulty_level, episode_id
|
| 230 |
+
```
|
| 231 |
+
|
| 232 |
+
---
|
| 233 |
+
|
| 234 |
+
## Step 8 β `scripts/run_dummy_episode.py`
|
| 235 |
+
|
| 236 |
+
```
|
| 237 |
+
python scripts/run_dummy_episode.py --difficulty easy --steps 3 --verbose
|
| 238 |
+
```
|
| 239 |
+
|
| 240 |
+
1. Instantiate `ViralScriptEnv`
|
| 241 |
+
2. Call `reset()`
|
| 242 |
+
3. Each step: sample a random `ActionType`, construct a minimal valid `ArbitratorAction`, call `step()`
|
| 243 |
+
4. Print per-step: script diff, reward components, any anti-gaming penalties (use `rich` panels/tables)
|
| 244 |
+
5. At end: print final reward, R1, R2
|
| 245 |
+
6. Save full episode log to `logs/episode_<id>.json`
|
| 246 |
+
|
| 247 |
+
Output must be readable to a non-technical judge watching the demo.
|
| 248 |
+
|
| 249 |
+
---
|
| 250 |
+
|
| 251 |
+
## Step 9 β Tests
|
| 252 |
+
|
| 253 |
+
**`tests/test_environment.py`:**
|
| 254 |
+
- `reset()` returns a valid observation dict
|
| 255 |
+
- `step()` with a valid action completes without error
|
| 256 |
+
- `step()` increments `step_num` correctly
|
| 257 |
+
- Anti-gaming penalty fires when same action repeated 3 times
|
| 258 |
+
- Episode terminates at `max_steps`
|
| 259 |
+
- Reward is clipped to [0, 1]
|
| 260 |
+
- Mock all LLM calls using fixtures from `data/golden_fixtures/`
|
| 261 |
+
|
| 262 |
+
**`tests/test_rewards.py`:**
|
| 263 |
+
- R1 on 5 hand-crafted hooks: 2 score >0.8, 2 score <0.3, 1 is edge case ~0.5
|
| 264 |
+
- R2 with identical strings β 0.8 (the >0.95 penalty case)
|
| 265 |
+
- R2 with completely different strings β 0.0 (below 0.65 threshold)
|
| 266 |
+
- RewardAggregator catastrophic drop penalty zeroes reward correctly
|
| 267 |
+
- RewardAggregator diversity penalty fires on 3 identical consecutive actions
|
| 268 |
+
|
| 269 |
+
---
|
| 270 |
+
|
| 271 |
+
## Gate check
|
| 272 |
+
|
| 273 |
+
Run:
|
| 274 |
+
```
|
| 275 |
+
python scripts/run_dummy_episode.py --difficulty easy --steps 3 --verbose
|
| 276 |
+
```
|
| 277 |
+
|
| 278 |
+
The final line must print:
|
| 279 |
+
```
|
| 280 |
+
PHASE 1 GATE: PASS
|
| 281 |
+
```
|
| 282 |
+
|
| 283 |
+
Conditions: (a) episode completed without error, (b) R1 and R2 are non-null in final state, (c) episode log saved to `logs/`.
|
| 284 |
+
|
| 285 |
+
Also deploy this version to HF Spaces immediately as an early checkpoint before moving to Phase 2.
|
prompts/phase-2.md
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Phase 2 β Defender Agent + R3/R4/R5 + Anti-Gaming + Baseline
|
| 2 |
+
> Paste this entire prompt into a fresh Claude Code session. Phase 1 must be complete (dummy episode running, R1/R2 working) before starting.
|
| 3 |
+
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
Phase 1 is complete. The environment scaffold runs and dummy episodes complete end-to-end. Now complete the full agent loop with all 5 reward signals, bake in the anti-gaming protections with full logging, and record the pre-training baseline.
|
| 7 |
+
|
| 8 |
+
**Current state:**
|
| 9 |
+
- `environment/env.py` β ViralScriptEnv working with R1+R2
|
| 10 |
+
- `agents/critic.py` β validated CriticAgent
|
| 11 |
+
- `agents/rewriter.py` β RewriterAgent
|
| 12 |
+
- Defender slot exists in Observation but is still `None`
|
| 13 |
+
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
## Step 1 β `agents/defender.py`
|
| 17 |
+
|
| 18 |
+
```python
|
| 19 |
+
class DefenderOutput(BaseModel):
|
| 20 |
+
core_strength: str # single most important thing to preserve
|
| 21 |
+
core_strength_quote: str # exact verbatim quote from the script
|
| 22 |
+
defense_argument: str # why this should not be changed
|
| 23 |
+
flagged_critic_claims: List[str] # claim_ids the Defender believes are overcorrections
|
| 24 |
+
regional_voice_elements: List[str] # phrases/references that are intentionally regional
|
| 25 |
+
|
| 26 |
+
class DefenderAgent:
|
| 27 |
+
def __init__(self, model_name: str = "claude-sonnet-4-20250514"):
|
| 28 |
+
pass
|
| 29 |
+
|
| 30 |
+
def defend(
|
| 31 |
+
self,
|
| 32 |
+
script: str,
|
| 33 |
+
critic_claims: List[CritiqueClaim],
|
| 34 |
+
region: str,
|
| 35 |
+
platform: str,
|
| 36 |
+
) -> DefenderOutput:
|
| 37 |
+
pass
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
**System prompt (exact):**
|
| 41 |
+
```
|
| 42 |
+
You are a script defender for short-form video content. Your job is NOT to say the script is perfect.
|
| 43 |
+
Your job is to identify what is genuinely working β and protect it from being edited away.
|
| 44 |
+
|
| 45 |
+
Specifically:
|
| 46 |
+
1. Find the single most powerful element of the script. Quote it exactly.
|
| 47 |
+
2. Explain why a viewer would respond positively to this element.
|
| 48 |
+
3. Review the Critic's claims. Flag any that would destroy the script's core strength or strip its regional authenticity if acted on.
|
| 49 |
+
4. List any phrases, idioms, or references that are intentionally regional β these must not be "corrected" away.
|
| 50 |
+
|
| 51 |
+
OUTPUT (JSON only, no preamble):
|
| 52 |
+
{
|
| 53 |
+
"core_strength": "one sentence describing the strongest element",
|
| 54 |
+
"core_strength_quote": "exact verbatim quote from the script",
|
| 55 |
+
"defense_argument": "why this element should be preserved",
|
| 56 |
+
"flagged_critic_claims": ["C2", "C3"],
|
| 57 |
+
"regional_voice_elements": ["specific phrase 1", "specific phrase 2"]
|
| 58 |
+
}
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
---
|
| 62 |
+
|
| 63 |
+
## Step 2 β `rewards/r3_cultural_alignment.py`
|
| 64 |
+
|
| 65 |
+
Rule-based, zero LLM calls. Uses a JSON knowledge base.
|
| 66 |
+
|
| 67 |
+
```python
|
| 68 |
+
class CulturalAlignmentReward:
|
| 69 |
+
def __init__(self, knowledge_base_path: str = "data/cultural_kb.json"):
|
| 70 |
+
pass
|
| 71 |
+
|
| 72 |
+
def score(self, script: str, region: str) -> CulturalRewardResult:
|
| 73 |
+
# score = (valid_refs_found + correct_idioms_found
|
| 74 |
+
# - invalid_signals_found - anachronistic_signals_found)
|
| 75 |
+
# / max(total_valid_refs + total_idioms, 1)
|
| 76 |
+
# clip to [0, 1]
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
Create `data/cultural_kb.json` with at least 15 entries per category per region for:
|
| 80 |
+
|
| 81 |
+
**Mumbai Gen Z:**
|
| 82 |
+
- `valid_refs`: Bandra, CSMT, dabba, auto, local train, startup scene, Zomato, Swiggy, IPL, Bollywood 2020s
|
| 83 |
+
- `invalid_signals`: outdated slang pre-2015
|
| 84 |
+
- `correct_idioms`: "ek dum solid", "full on", "kya scene hai", Hinglish patterns
|
| 85 |
+
|
| 86 |
+
**Tier-2 Hindi Belt:**
|
| 87 |
+
- `valid_refs`: kirana store, mandap, jugaad, sabzi mandi, panchayat, mela, dal-chawal
|
| 88 |
+
- `invalid_signals`: metro-centric language, startup jargon
|
| 89 |
+
- `correct_idioms`: Hindi-dominant mixed phrases
|
| 90 |
+
|
| 91 |
+
**Pan-India English:** minimal constraints; penalise overly regional without context.
|
| 92 |
+
|
| 93 |
+
**Hinglish:** reward balanced Hindi-English mixing; penalise fully formal English.
|
| 94 |
+
|
| 95 |
+
---
|
| 96 |
+
|
| 97 |
+
## Step 3 β `rewards/r4_debate_resolution.py`
|
| 98 |
+
|
| 99 |
+
Re-runs the Critic on the new script after each rewrite. Checks whether the specific claim the Arbitrator targeted still appears.
|
| 100 |
+
|
| 101 |
+
```python
|
| 102 |
+
class DebateResolutionReward:
|
| 103 |
+
def __init__(self, critic_agent: CriticAgent):
|
| 104 |
+
self.critic = critic_agent
|
| 105 |
+
|
| 106 |
+
def score(
|
| 107 |
+
self,
|
| 108 |
+
new_script: str,
|
| 109 |
+
original_action: ArbitratorAction,
|
| 110 |
+
original_claim: CritiqueClaim,
|
| 111 |
+
region: str,
|
| 112 |
+
platform: str,
|
| 113 |
+
niche: str,
|
| 114 |
+
) -> DebateResolutionResult:
|
| 115 |
+
# Re-run critic on new_script
|
| 116 |
+
# A claim is "resolved" if new critique has NO claim of the same critique_class
|
| 117 |
+
# targeting the same timestamp_range (Β±5 seconds)
|
| 118 |
+
# OR the new claim for that section has severity "low" (down from "medium"/"high")
|
| 119 |
+
# Score: 1.0=resolved, 0.5=partially resolved (severity reduced), 0.0=claim persists
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
---
|
| 123 |
+
|
| 124 |
+
## Step 4 β `rewards/r5_defender_preservation.py`
|
| 125 |
+
|
| 126 |
+
Uses `sentence-transformers all-MiniLM-L6-v2` (same model as R2 β reuse the loaded instance).
|
| 127 |
+
|
| 128 |
+
```python
|
| 129 |
+
class DefenderPreservationReward:
|
| 130 |
+
def score(self, defender_output: DefenderOutput, rewritten_script: str) -> DefenderPreservationResult:
|
| 131 |
+
# Chunk rewritten_script into sentences
|
| 132 |
+
# Compute cosine_similarity(embed(core_strength_quote), embed(sentence)) for each sentence
|
| 133 |
+
# Take the max similarity across all sentences
|
| 134 |
+
# Score mapping:
|
| 135 |
+
# max_similarity >= 0.85 β 1.0
|
| 136 |
+
# max_similarity 0.65β0.85 β max_similarity (partial)
|
| 137 |
+
# max_similarity < 0.65 β 0.0
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
---
|
| 141 |
+
|
| 142 |
+
## Step 5 β Update `environment/env.py`
|
| 143 |
+
|
| 144 |
+
**Modify `step()`:**
|
| 145 |
+
1. After Critic runs, immediately run Defender on the same script state
|
| 146 |
+
2. Store `DefenderOutput` in the `DebateRound`
|
| 147 |
+
3. After Rewriter executes, compute R3, R4, R5 alongside R1, R2
|
| 148 |
+
4. Pass all 5 to RewardAggregator
|
| 149 |
+
|
| 150 |
+
**Modify `reset()`:**
|
| 151 |
+
1. Compute baseline R1βR5 on unmodified script at episode start
|
| 152 |
+
2. R4 and R5 will be `None` at reset β handle gracefully in aggregator (skip them in weighted sum)
|
| 153 |
+
|
| 154 |
+
---
|
| 155 |
+
|
| 156 |
+
## Step 6 β Update `rewards/reward_aggregator.py`
|
| 157 |
+
|
| 158 |
+
- Anti-gaming catastrophic drop check must now work across all 5 rewards
|
| 159 |
+
- Normalise weights so available (non-None) rewards always sum to 1.0
|
| 160 |
+
|
| 161 |
+
Add `AntiGamingLog`:
|
| 162 |
+
|
| 163 |
+
```python
|
| 164 |
+
class AntiGamingLog(BaseModel):
|
| 165 |
+
episode_id: str
|
| 166 |
+
step_num: int
|
| 167 |
+
triggered: bool
|
| 168 |
+
rule_triggered: Optional[str] # "catastrophic_drop" | "action_repetition" | None
|
| 169 |
+
component_that_dropped: Optional[str] # which reward triggered catastrophic drop
|
| 170 |
+
penalty_applied: float
|
| 171 |
+
pre_penalty_total: float
|
| 172 |
+
post_penalty_total: float
|
| 173 |
+
```
|
| 174 |
+
|
| 175 |
+
Every `RewardAggregator.compute()` call must return `(RewardComponents, AntiGamingLog)`. Save all `AntiGamingLog` entries in the episode's `info` dict and in the episode log JSON.
|
| 176 |
+
|
| 177 |
+
---
|
| 178 |
+
|
| 179 |
+
## Step 7 β `agents/baseline_arbitrator.py`
|
| 180 |
+
|
| 181 |
+
```python
|
| 182 |
+
class BaselineArbitratorAgent:
|
| 183 |
+
"""
|
| 184 |
+
Untrained Arbitrator for the pre-training baseline.
|
| 185 |
+
Uses zero-shot instruction β no chain-of-thought, no few-shot examples.
|
| 186 |
+
This ensures the comparison is fair: trained model learns through RL, not prompting.
|
| 187 |
+
"""
|
| 188 |
+
|
| 189 |
+
SYSTEM_PROMPT = """
|
| 190 |
+
You are helping improve a short-form video script.
|
| 191 |
+
You have observed a debate between a Critic and a Defender about the script.
|
| 192 |
+
Choose ONE action to take to improve the script.
|
| 193 |
+
|
| 194 |
+
Available actions: hook_rewrite, section_reorder, cultural_ref_sub, cta_placement
|
| 195 |
+
|
| 196 |
+
Respond ONLY with valid JSON:
|
| 197 |
+
{
|
| 198 |
+
"action_type": "hook_rewrite",
|
| 199 |
+
"target_section": "hook",
|
| 200 |
+
"instruction": "specific instruction for the rewriter",
|
| 201 |
+
"critique_claim_id": "C1",
|
| 202 |
+
"reasoning": "brief explanation"
|
| 203 |
+
}
|
| 204 |
+
"""
|
| 205 |
+
|
| 206 |
+
def __init__(self, model_name: str = "claude-haiku-4-5-20251001"):
|
| 207 |
+
# Haiku: cheaper and clearly weaker than trained model
|
| 208 |
+
pass
|
| 209 |
+
|
| 210 |
+
def act(self, observation: dict) -> dict:
|
| 211 |
+
pass
|
| 212 |
+
```
|
| 213 |
+
|
| 214 |
+
---
|
| 215 |
+
|
| 216 |
+
## Step 8 β `scripts/run_baseline.py`
|
| 217 |
+
|
| 218 |
+
Run 20 full episodes with `BaselineArbitratorAgent`:
|
| 219 |
+
- Episodes 1β8: `difficulty="easy"`
|
| 220 |
+
- Episodes 9β16: `difficulty="medium"`
|
| 221 |
+
- Episodes 17β20: `difficulty="hard"`
|
| 222 |
+
|
| 223 |
+
Log per episode: episode_id, difficulty, script_id, per-step R1βR5, anti_gaming_triggered, penalty, total reward, final script vs original.
|
| 224 |
+
|
| 225 |
+
Save to `logs/baseline_results.json`.
|
| 226 |
+
|
| 227 |
+
Generate `logs/baseline_reward_curves.png`:
|
| 228 |
+
- 2 rows Γ 3 cols subplots: R1, R2, R3, R4, R5, Total
|
| 229 |
+
- X-axis: episode number (1β20), labelled
|
| 230 |
+
- Y-axis: reward value [0, 1], labelled
|
| 231 |
+
- Title: "Baseline (Untrained) Arbitrator β Pre-Training Reward Curves"
|
| 232 |
+
- `dpi=150`, save as PNG
|
| 233 |
+
- These plots are submitted to judges
|
| 234 |
+
|
| 235 |
+
Print a `rich` summary table showing mean Β± std of each reward across all 20 episodes.
|
| 236 |
+
|
| 237 |
+
---
|
| 238 |
+
|
| 239 |
+
## Step 9 β `tests/test_phase2.py`
|
| 240 |
+
|
| 241 |
+
- `DefenderAgent` parses output correctly from mock LLM response
|
| 242 |
+
- R3 scores correctly on 3 regional vs 3 non-regional hand-crafted scripts
|
| 243 |
+
- R4 correctly identifies resolved vs unresolved claims (mock Critic re-run)
|
| 244 |
+
- R5 scores correctly when `core_strength_quote` is present vs absent in rewrite
|
| 245 |
+
- Anti-gaming catastrophic drop zeroes reward when R2 drops by 0.25
|
| 246 |
+
- Anti-gaming diversity penalty fires on 3Γ same action
|
| 247 |
+
- `AntiGamingLog` is populated correctly in both triggering and non-triggering cases
|
| 248 |
+
|
| 249 |
+
---
|
| 250 |
+
|
| 251 |
+
## Gate check
|
| 252 |
+
|
| 253 |
+
Run:
|
| 254 |
+
```
|
| 255 |
+
python scripts/run_baseline.py
|
| 256 |
+
```
|
| 257 |
+
|
| 258 |
+
Must:
|
| 259 |
+
1. Complete all 20 episodes without error
|
| 260 |
+
2. Save `logs/baseline_reward_curves.png`
|
| 261 |
+
3. Print:
|
| 262 |
+
```
|
| 263 |
+
PHASE 2 GATE: PASS β Baseline curves saved. Pre-training mean total reward: X.XX
|
| 264 |
+
```
|
| 265 |
+
|
| 266 |
+
Expected baseline total reward: **0.25β0.55**. If above 0.7, tasks are too easy. If below 0.1, tasks are too hard and RL training will stall β adjust script difficulty before Phase 3.
|
prompts/phase-3.md
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Phase 3 β Curriculum Dataset + GRPO Training
|
| 2 |
+
> Paste this entire prompt into a fresh Claude Code session. Phases 0β2 must be complete, baseline curves saved, before starting.
|
| 3 |
+
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
Phases 0β2 are complete. The environment has all 5 rewards, anti-gaming protections are live, and baseline reward curves are saved in `logs/baseline_reward_curves.png`. Now build the curriculum datasets and the GRPO training pipeline.
|
| 7 |
+
|
| 8 |
+
**The training script must connect to the live OpenEnv environment β not a static dataset.**
|
| 9 |
+
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
## Step 1 β `data/curriculum/build_curriculum.py`
|
| 13 |
+
|
| 14 |
+
Build three curriculum tiers. Each is a JSONL file where each line is one episode config.
|
| 15 |
+
|
| 16 |
+
**Episode config schema:**
|
| 17 |
+
```json
|
| 18 |
+
{
|
| 19 |
+
"episode_config_id": "easy_001",
|
| 20 |
+
"difficulty": "easy",
|
| 21 |
+
"script_id": "S01",
|
| 22 |
+
"script_text": "...",
|
| 23 |
+
"region": "Mumbai Gen Z",
|
| 24 |
+
"platform": "Reels",
|
| 25 |
+
"niche": "personal finance",
|
| 26 |
+
"dominant_flaw": "buried_hook",
|
| 27 |
+
"expected_critique_class": "hook_weakness",
|
| 28 |
+
"expected_action": "hook_rewrite",
|
| 29 |
+
"curriculum_notes": "One obvious flaw. Critic should win immediately. Strong reward signal on step 1."
|
| 30 |
+
}
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
**Generate:**
|
| 34 |
+
- `data/curriculum/easy_tier.jsonl` β 20 configs (10 from existing scripts + 10 synthetic)
|
| 35 |
+
- `data/curriculum/medium_tier.jsonl` β 15 configs (trade-off scenarios where Critic and Defender both have valid points)
|
| 36 |
+
- `data/curriculum/hard_tier.jsonl` β 10 configs (fixing the top critique damages R3 β explicit conflicts)
|
| 37 |
+
|
| 38 |
+
**For synthetic scripts**, create `data/curriculum/generate_synthetic_scripts.py`:
|
| 39 |
+
|
| 40 |
+
Use the Anthropic API with this prompt pattern:
|
| 41 |
+
```
|
| 42 |
+
Generate a realistic 60-90 second Reels script for [niche] targeting [region].
|
| 43 |
+
Intentionally include [flaw_type] as the dominant flaw.
|
| 44 |
+
The flaw should be [easy|medium|hard] to diagnose.
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
Generate: 10 easy, 5 medium, 5 hard synthetic scripts. Save to `data/curriculum/synthetic_scripts.json`.
|
| 48 |
+
|
| 49 |
+
---
|
| 50 |
+
|
| 51 |
+
## Step 2 β `training/rollout_function.py`
|
| 52 |
+
|
| 53 |
+
This bridges TRL's `GRPOTrainer` to the live OpenEnv environment. It's the most critical file in this phase.
|
| 54 |
+
|
| 55 |
+
```python
|
| 56 |
+
def build_rollout_fn(env: ViralScriptEnv, max_steps: int = 5):
|
| 57 |
+
"""
|
| 58 |
+
Returns a function compatible with TRL's GRPOTrainer rollout interface.
|
| 59 |
+
|
| 60 |
+
For each prompt in the batch:
|
| 61 |
+
1. Parse the episode config embedded in prompt metadata
|
| 62 |
+
2. Reset the env with that config
|
| 63 |
+
3. Run the model to generate an action (JSON)
|
| 64 |
+
4. Execute the action in the env
|
| 65 |
+
5. Collect the final episode reward
|
| 66 |
+
|
| 67 |
+
Returns: (completions: List[str], rewards: List[float])
|
| 68 |
+
"""
|
| 69 |
+
|
| 70 |
+
def rollout_fn(prompts: List[str], model, tokenizer) -> Tuple[List[str], List[float]]:
|
| 71 |
+
...
|
| 72 |
+
|
| 73 |
+
return rollout_fn
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
**Prompt format for the Arbitrator model:**
|
| 77 |
+
```
|
| 78 |
+
<|system|>
|
| 79 |
+
You are an expert content strategist acting as an Arbitrator in a script improvement debate.
|
| 80 |
+
You observe a debate between a Critic and Defender about a creator's script.
|
| 81 |
+
You must choose exactly ONE action to improve the script.
|
| 82 |
+
|
| 83 |
+
AVAILABLE ACTIONS: hook_rewrite | section_reorder | cultural_ref_sub | cta_placement
|
| 84 |
+
|
| 85 |
+
OUTPUT FORMAT (JSON only):
|
| 86 |
+
{"action_type": "...", "target_section": "...", "instruction": "...", "critique_claim_id": "...", "reasoning": "..."}
|
| 87 |
+
<|end|>
|
| 88 |
+
|
| 89 |
+
<|user|>
|
| 90 |
+
CURRENT SCRIPT:
|
| 91 |
+
{current_script}
|
| 92 |
+
|
| 93 |
+
REGION: {region} | PLATFORM: {platform} | NICHE: {niche}
|
| 94 |
+
|
| 95 |
+
CRITIC CLAIMS:
|
| 96 |
+
{formatted_critic_claims}
|
| 97 |
+
|
| 98 |
+
DEFENDER RESPONSE:
|
| 99 |
+
Core strength: {core_strength}
|
| 100 |
+
Defense: {defense_argument}
|
| 101 |
+
Flagged claims: {flagged_claims}
|
| 102 |
+
|
| 103 |
+
CURRENT REWARDS: R1={r1:.2f} R2={r2:.2f} R3={r3} R4={r4} R5={r5}
|
| 104 |
+
STEP: {step_num}/{max_steps}
|
| 105 |
+
|
| 106 |
+
Choose your action:
|
| 107 |
+
<|end|>
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
---
|
| 111 |
+
|
| 112 |
+
## Step 3 β `training/train_grpo.py`
|
| 113 |
+
|
| 114 |
+
Make this runnable as both a local script and a Colab notebook cell.
|
| 115 |
+
|
| 116 |
+
```python
|
| 117 |
+
"""
|
| 118 |
+
GRPO Training β Viral Script Debugging Engine
|
| 119 |
+
TRL + Unsloth for memory-efficient training.
|
| 120 |
+
|
| 121 |
+
Local dry-run: python training/train_grpo.py --dry-run
|
| 122 |
+
Full training: python training/train_grpo.py --tier easy,medium --steps 200
|
| 123 |
+
"""
|
| 124 |
+
|
| 125 |
+
from unsloth import FastLanguageModel
|
| 126 |
+
from trl import GRPOTrainer, GRPOConfig
|
| 127 |
+
|
| 128 |
+
def load_model(model_name: str, max_seq_length: int = 2048):
|
| 129 |
+
model, tokenizer = FastLanguageModel.from_pretrained(
|
| 130 |
+
model_name=model_name,
|
| 131 |
+
max_seq_length=max_seq_length,
|
| 132 |
+
dtype=None, # auto-detect
|
| 133 |
+
load_in_4bit=True,
|
| 134 |
+
)
|
| 135 |
+
model = FastLanguageModel.get_peft_model(
|
| 136 |
+
model,
|
| 137 |
+
r=16,
|
| 138 |
+
target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
|
| 139 |
+
"gate_proj", "up_proj", "down_proj"],
|
| 140 |
+
lora_alpha=16,
|
| 141 |
+
lora_dropout=0,
|
| 142 |
+
bias="none",
|
| 143 |
+
use_gradient_checkpointing="unsloth",
|
| 144 |
+
random_state=42,
|
| 145 |
+
)
|
| 146 |
+
return model, tokenizer
|
| 147 |
+
|
| 148 |
+
def build_grpo_config(output_dir, num_steps, dry_run) -> GRPOConfig:
|
| 149 |
+
return GRPOConfig(
|
| 150 |
+
output_dir=output_dir,
|
| 151 |
+
num_train_epochs=1,
|
| 152 |
+
max_steps=5 if dry_run else num_steps,
|
| 153 |
+
per_device_train_batch_size=1 if dry_run else 4,
|
| 154 |
+
num_generations=4 if dry_run else 8,
|
| 155 |
+
gradient_accumulation_steps=4,
|
| 156 |
+
learning_rate=5e-6,
|
| 157 |
+
max_grad_norm=0.1,
|
| 158 |
+
warmup_ratio=0.1,
|
| 159 |
+
logging_steps=1,
|
| 160 |
+
save_steps=50,
|
| 161 |
+
report_to="wandb" if os.getenv("WANDB_API_KEY") else "none",
|
| 162 |
+
use_vllm=False,
|
| 163 |
+
temperature=0.8,
|
| 164 |
+
top_p=0.9,
|
| 165 |
+
max_new_tokens=256,
|
| 166 |
+
)
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
**Model saving β CRITICAL:**
|
| 170 |
+
```python
|
| 171 |
+
# Use save_pretrained_merged β NOT naive upcast from 4-bit
|
| 172 |
+
model.save_pretrained_merged(
|
| 173 |
+
f"{output_dir}/final_model",
|
| 174 |
+
tokenizer,
|
| 175 |
+
save_method="merged_16bit",
|
| 176 |
+
)
|
| 177 |
+
```
|
| 178 |
+
|
| 179 |
+
**CLI flags:**
|
| 180 |
+
- `--tier` β comma-separated tiers: `easy`, `medium`, `hard`
|
| 181 |
+
- `--steps` β number of training steps (default: 200)
|
| 182 |
+
- `--dry-run` β run 5 steps with batch_size=1 to validate pipeline
|
| 183 |
+
- `--model` β base model (default: `unsloth/Qwen2.5-7B-Instruct-bnb-4bit`)
|
| 184 |
+
- `--output-dir` β checkpoint directory (default: `outputs/checkpoints/`)
|
| 185 |
+
- `--wandb` β enable WandB logging
|
| 186 |
+
|
| 187 |
+
---
|
| 188 |
+
|
| 189 |
+
## Step 4 β `training/reward_curves.py`
|
| 190 |
+
|
| 191 |
+
```python
|
| 192 |
+
def plot_training_curves(
|
| 193 |
+
baseline_log_path: str = "logs/baseline_results.json",
|
| 194 |
+
training_log_path: str = "logs/training_results.json",
|
| 195 |
+
output_path: str = "logs/training_vs_baseline.png",
|
| 196 |
+
):
|
| 197 |
+
"""
|
| 198 |
+
Judge-facing comparison plot.
|
| 199 |
+
Layout: 2 rows Γ 3 cols (R1, R2, R3, R4, R5, Total)
|
| 200 |
+
|
| 201 |
+
Per subplot:
|
| 202 |
+
- Grey line: baseline reward per episode
|
| 203 |
+
- Blue line: trained reward per episode
|
| 204 |
+
- Horizontal dashed line: baseline mean
|
| 205 |
+
- Both axes labelled
|
| 206 |
+
|
| 207 |
+
Figure title: "Trained vs Untrained Arbitrator β Reward Improvement"
|
| 208 |
+
Save PNG dpi=150. Also save PDF for README.
|
| 209 |
+
|
| 210 |
+
Print improvement summary:
|
| 211 |
+
R1: baseline=X.XX β trained=Y.YY (+Z.ZZ)
|
| 212 |
+
...
|
| 213 |
+
"""
|
| 214 |
+
```
|
| 215 |
+
|
| 216 |
+
---
|
| 217 |
+
|
| 218 |
+
## Step 5 β `training/eval_trained_model.py`
|
| 219 |
+
|
| 220 |
+
After training: run 20 evaluation episodes with the trained model. Use the same 20 episode configs as the baseline run for a fair comparison. Save to `logs/trained_results.json`. Then call `plot_training_curves()`.
|
| 221 |
+
|
| 222 |
+
---
|
| 223 |
+
|
| 224 |
+
## Step 6 β `tests/test_training_pipeline.py`
|
| 225 |
+
|
| 226 |
+
- `build_training_prompts("easy")` returns non-empty dataset with correct prompt format
|
| 227 |
+
- `rollout_fn` completes one episode given a mock model returning random valid JSON
|
| 228 |
+
- `GRPOConfig` builds without error
|
| 229 |
+
- Model saving path uses `save_pretrained_merged`, not `save_pretrained`
|
| 230 |
+
- `plot_training_curves` generates a PNG file given valid JSON inputs
|
| 231 |
+
|
| 232 |
+
---
|
| 233 |
+
|
| 234 |
+
## Gate check
|
| 235 |
+
|
| 236 |
+
Run:
|
| 237 |
+
```
|
| 238 |
+
python training/train_grpo.py --dry-run
|
| 239 |
+
```
|
| 240 |
+
|
| 241 |
+
Must:
|
| 242 |
+
1. Complete 5 training steps without error
|
| 243 |
+
2. Print reward values for each step
|
| 244 |
+
3. Show training loop is connected to the live environment (not a static dataset)
|
| 245 |
+
4. Print:
|
| 246 |
+
```
|
| 247 |
+
PHASE 3 GATE: PASS β Dry run complete. Training pipeline connected to live environment.
|
| 248 |
+
```
|
| 249 |
+
|
| 250 |
+
**The full training run happens onsite when compute credits are available. Do not attempt to run it now.**
|
prompts/phase-4.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Phase 4 β Critic Escalation Engine (Theme 4: Self-Improvement)
|
| 2 |
+
> Paste this entire prompt into a fresh Claude Code session. Phases 0β3 must be complete and the training dry-run passing before starting.
|
| 3 |
+
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
Phases 0β3 are complete. The training pipeline is validated. Now build the self-improvement loop that satisfies Theme 4: a Critic Escalation Engine that generates harder critique challenges automatically as the Arbitrator improves.
|
| 7 |
+
|
| 8 |
+
This is what separates this submission from a standard RL environment. Every other team builds a fixed task. This environment gets harder as the agent gets better.
|
| 9 |
+
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
## Step 1 β `escalation/difficulty_tracker.py`
|
| 13 |
+
|
| 14 |
+
```python
|
| 15 |
+
from dataclasses import dataclass, field
|
| 16 |
+
from typing import Dict, List, Optional
|
| 17 |
+
import json
|
| 18 |
+
|
| 19 |
+
@dataclass
|
| 20 |
+
class CritiqueClassRecord:
|
| 21 |
+
critique_class: str
|
| 22 |
+
total_episodes: int = 0
|
| 23 |
+
resolved_episodes: int = 0 # episodes where R4 >= 0.8
|
| 24 |
+
consecutive_resolutions: int = 0 # current streak
|
| 25 |
+
mastery_threshold: int = 3 # consecutive resolutions needed for mastery
|
| 26 |
+
is_mastered: bool = False
|
| 27 |
+
avg_r4_score: float = 0.0
|
| 28 |
+
last_10_r4_scores: List[float] = field(default_factory=list)
|
| 29 |
+
|
| 30 |
+
class DifficultyTracker:
|
| 31 |
+
CRITIQUE_CLASSES = [
|
| 32 |
+
"hook_weakness", "pacing_issue", "cultural_mismatch",
|
| 33 |
+
"cta_buried", "coherence_break", "retention_risk"
|
| 34 |
+
]
|
| 35 |
+
|
| 36 |
+
def __init__(self, persistence_path: str = "logs/difficulty_tracker.json"):
|
| 37 |
+
# Init one CritiqueClassRecord per class
|
| 38 |
+
# Load from persistence_path if it exists
|
| 39 |
+
|
| 40 |
+
def record_episode(self, dominant_critique_class: str, r4_score: float, episode_id: str):
|
| 41 |
+
"""
|
| 42 |
+
Update the record for the dominant critique class.
|
| 43 |
+
Resolved = r4_score >= 0.8.
|
| 44 |
+
Update consecutive_resolutions streak (reset to 0 on failure).
|
| 45 |
+
Set is_mastered = True at consecutive_resolutions >= mastery_threshold.
|
| 46 |
+
Save to disk after every update.
|
| 47 |
+
"""
|
| 48 |
+
|
| 49 |
+
def get_next_difficulty_class(self) -> str:
|
| 50 |
+
"""
|
| 51 |
+
Priority:
|
| 52 |
+
1. If any class is mastered AND a harder version exists from the escalation engine β return it
|
| 53 |
+
2. Else β class with lowest avg_r4_score that has had >= 3 episodes
|
| 54 |
+
3. Fallback β "hook_weakness"
|
| 55 |
+
"""
|
| 56 |
+
|
| 57 |
+
def get_mastered_classes(self) -> List[str]:
|
| 58 |
+
return [k for k, v in self.records.items() if v.is_mastered]
|
| 59 |
+
|
| 60 |
+
def get_hardest_unsolved_class(self) -> str:
|
| 61 |
+
# Lowest avg_r4_score among non-mastered classes
|
| 62 |
+
|
| 63 |
+
def summary(self) -> dict:
|
| 64 |
+
# JSON-serialisable summary for logging and demo
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
## Step 2 β `escalation/critic_escalation_engine.py`
|
| 70 |
+
|
| 71 |
+
```python
|
| 72 |
+
@dataclass
|
| 73 |
+
class EscalatedChallenge:
|
| 74 |
+
source_class: str # which mastered class this escalates from
|
| 75 |
+
script_text: str
|
| 76 |
+
region: str
|
| 77 |
+
platform: str
|
| 78 |
+
dominant_flaw: str
|
| 79 |
+
conflicting_flaw: str # the flaw that makes fixing dominant_flaw harder
|
| 80 |
+
why_its_harder: str # one sentence
|
| 81 |
+
optimal_action_order: List[str]
|
| 82 |
+
trap_action: str # the action that looks right but leads to worse total reward
|
| 83 |
+
difficulty_level: str = "self_generated"
|
| 84 |
+
generated_at: str = "" # ISO timestamp
|
| 85 |
+
|
| 86 |
+
class CriticEscalationEngine:
|
| 87 |
+
def __init__(self, model_name: str = "claude-sonnet-4-20250514"):
|
| 88 |
+
self.escalated_classes: Dict[str, List[EscalatedChallenge]] = {}
|
| 89 |
+
|
| 90 |
+
def escalate(
|
| 91 |
+
self,
|
| 92 |
+
mastered_class: str,
|
| 93 |
+
original_script_example: str,
|
| 94 |
+
region: str,
|
| 95 |
+
platform: str,
|
| 96 |
+
) -> EscalatedChallenge:
|
| 97 |
+
"""
|
| 98 |
+
System prompt:
|
| 99 |
+
You are designing training challenges for an RL agent learning to improve video scripts.
|
| 100 |
+
The agent has mastered detecting and fixing '{mastered_class}' flaws.
|
| 101 |
+
|
| 102 |
+
Generate a harder challenge:
|
| 103 |
+
1. Create a script with a '{mastered_class}' flaw that is MORE SUBTLE than the example
|
| 104 |
+
2. Add a CONFLICTING CONSTRAINT: fixing the '{mastered_class}' flaw should create or
|
| 105 |
+
worsen a different flaw from: {other_classes}
|
| 106 |
+
3. Difficulty: HARD β agent must learn action ordering, not just action selection
|
| 107 |
+
|
| 108 |
+
A challenge is good when: fixing the obvious flaw first leads to WORSE total reward
|
| 109 |
+
than fixing a less obvious flaw first.
|
| 110 |
+
|
| 111 |
+
Return JSON only:
|
| 112 |
+
{
|
| 113 |
+
"script_text": "...",
|
| 114 |
+
"dominant_flaw": "...",
|
| 115 |
+
"conflicting_flaw": "...",
|
| 116 |
+
"why_its_harder": "one sentence",
|
| 117 |
+
"optimal_action_order": ["action1", "action2"],
|
| 118 |
+
"trap_action": "action that looks correct but degrades total reward"
|
| 119 |
+
}
|
| 120 |
+
"""
|
| 121 |
+
|
| 122 |
+
def get_next_challenge(self, difficulty_tracker: DifficultyTracker) -> Optional[EscalatedChallenge]:
|
| 123 |
+
# Return next escalated challenge based on mastered classes
|
| 124 |
+
# Return None if no classes are mastered yet
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
---
|
| 128 |
+
|
| 129 |
+
## Step 3 β Wire escalation into `environment/env.py`
|
| 130 |
+
|
| 131 |
+
**Update `__init__` signature:**
|
| 132 |
+
```python
|
| 133 |
+
def __init__(
|
| 134 |
+
self,
|
| 135 |
+
scripts_path: str = "data/test_scripts/scripts.json",
|
| 136 |
+
max_steps: int = 5,
|
| 137 |
+
difficulty: str = "easy",
|
| 138 |
+
use_anti_gaming: bool = True,
|
| 139 |
+
use_escalation: bool = True,
|
| 140 |
+
difficulty_tracker: Optional[DifficultyTracker] = None,
|
| 141 |
+
escalation_engine: Optional[CriticEscalationEngine] = None,
|
| 142 |
+
):
|
| 143 |
+
# Create new DifficultyTracker / CriticEscalationEngine if not provided
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
**Update `reset()`:**
|
| 147 |
+
1. After existing reset logic, call `difficulty_tracker.get_mastered_classes()`
|
| 148 |
+
2. If any classes are mastered: call `escalation_engine.get_next_challenge(difficulty_tracker)`
|
| 149 |
+
3. If a challenge is returned: use the escalated script instead of the curriculum script
|
| 150 |
+
4. Set `observation.difficulty_level = "self_generated"`
|
| 151 |
+
5. Log that escalation was used (print + include in info dict)
|
| 152 |
+
|
| 153 |
+
**Update `step()`:**
|
| 154 |
+
After episode ends (terminated=True):
|
| 155 |
+
1. Determine `dominant_critique_class` = the critique_class with the most claims in the first Critic output of this episode
|
| 156 |
+
2. Call `difficulty_tracker.record_episode(dominant_critique_class, r4_score, episode_id)`
|
| 157 |
+
|
| 158 |
+
---
|
| 159 |
+
|
| 160 |
+
## Step 4 β `scripts/run_escalation_demo.py`
|
| 161 |
+
|
| 162 |
+
```
|
| 163 |
+
python scripts/run_escalation_demo.py --episodes 50 --verbose
|
| 164 |
+
python scripts/run_escalation_demo.py --episodes 10 --verbose # for gate check
|
| 165 |
+
```
|
| 166 |
+
|
| 167 |
+
Behaviour:
|
| 168 |
+
1. Run N episodes with the trained model (from `outputs/checkpoints/final_model`)
|
| 169 |
+
2. After each episode: log `difficulty_tracker.summary()`
|
| 170 |
+
3. Print clearly when mastery is achieved for a class and when escalation first activates
|
| 171 |
+
4. At the end, print a "difficulty progression" report:
|
| 172 |
+
- Which classes were mastered and at which episode
|
| 173 |
+
- How many escalated challenges were generated
|
| 174 |
+
- Whether escalated challenges produced lower R4 scores than the original class (proof escalation is working)
|
| 175 |
+
|
| 176 |
+
Save progression to `logs/escalation_progression.json`.
|
| 177 |
+
|
| 178 |
+
**Generate `logs/escalation_chart.png`:**
|
| 179 |
+
- X-axis: episode number, labelled
|
| 180 |
+
- Y-axis (left): difficulty score (1=easy, 2=medium, 3=hard, 4=self_generated)
|
| 181 |
+
- Y-axis (right): R4 score per episode
|
| 182 |
+
- Both overlaid on same plot
|
| 183 |
+
- Title: "Difficulty Progression β Self-Generated Curriculum"
|
| 184 |
+
- This chart is your Theme 4 story for judges
|
| 185 |
+
|
| 186 |
+
---
|
| 187 |
+
|
| 188 |
+
## Step 5 β `tests/test_escalation.py`
|
| 189 |
+
|
| 190 |
+
- `DifficultyTracker.record_episode()` correctly tracks consecutive resolutions
|
| 191 |
+
- Mastery triggers at exactly 3 consecutive resolutions, not 2
|
| 192 |
+
- Mastery resets if agent fails (r4 < 0.8) on a subsequent episode
|
| 193 |
+
- `CriticEscalationEngine.escalate()` returns valid `EscalatedChallenge` from mock LLM
|
| 194 |
+
- `env.reset()` uses escalated script when mastery is achieved (integration test with mocked escalation engine)
|
| 195 |
+
- Difficulty progression JSON is saved correctly
|
| 196 |
+
|
| 197 |
+
---
|
| 198 |
+
|
| 199 |
+
## Gate check
|
| 200 |
+
|
| 201 |
+
Run:
|
| 202 |
+
```
|
| 203 |
+
python scripts/run_escalation_demo.py --episodes 10 --verbose
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
Must:
|
| 207 |
+
1. Complete 10 episodes without error
|
| 208 |
+
2. Show `DifficultyTracker` updating after each episode
|
| 209 |
+
3. Print escalation stats at the end
|
| 210 |
+
4. Save `logs/escalation_chart.png`
|
| 211 |
+
5. Print:
|
| 212 |
+
```
|
| 213 |
+
PHASE 4 GATE: PASS β Escalation engine operational. {n} classes mastered. {m} escalated challenges generated.
|
| 214 |
+
```
|
prompts/phase-5.md
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Phase 5 β HF Deployment + Demo Infrastructure
|
| 2 |
+
> Paste this entire prompt into a fresh Claude Code session. All environment code (Phases 0β4) must be complete before starting. Training may still be pending β that's fine, it runs onsite.
|
| 3 |
+
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
All environment code is complete. Now package everything for HuggingFace Spaces deployment and build the demo infrastructure that will carry 30% of the judging score.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Step 1 β `openenv.yaml`
|
| 11 |
+
|
| 12 |
+
Create the OpenEnv manifest in the project root:
|
| 13 |
+
|
| 14 |
+
```yaml
|
| 15 |
+
name: viral-script-debugging-engine
|
| 16 |
+
version: "1.0.0"
|
| 17 |
+
description: >
|
| 18 |
+
A multi-agent RL environment where an LLM Arbitrator learns to improve
|
| 19 |
+
short-form video scripts through adversarial debate. Trains with GRPO via
|
| 20 |
+
HuggingFace TRL + Unsloth. Hits Theme 1 (Multi-Agent) and Theme 4
|
| 21 |
+
(Self-Improvement) simultaneously.
|
| 22 |
+
themes:
|
| 23 |
+
- multi_agent_interactions
|
| 24 |
+
- self_improvement
|
| 25 |
+
author: "Team Name"
|
| 26 |
+
python_requires: ">=3.10"
|
| 27 |
+
entry_point: environment.env:ViralScriptEnv
|
| 28 |
+
reset_method: reset
|
| 29 |
+
step_method: step
|
| 30 |
+
state_method: state
|
| 31 |
+
reward_method: reward
|
| 32 |
+
tools:
|
| 33 |
+
- name: reset
|
| 34 |
+
description: "Start a new script improvement episode"
|
| 35 |
+
- name: step
|
| 36 |
+
description: "Execute one debate round: Critic attacks, Defender responds, Arbitrator acts, Rewriter executes"
|
| 37 |
+
- name: state
|
| 38 |
+
description: "Get current environment state including script, debate history, and reward components"
|
| 39 |
+
dependencies:
|
| 40 |
+
- anthropic>=0.40.0
|
| 41 |
+
- sentence-transformers>=2.7.0
|
| 42 |
+
- unsloth
|
| 43 |
+
- trl>=0.12.0
|
| 44 |
+
- numpy>=1.26.0
|
| 45 |
+
- pydantic>=2.0.0
|
| 46 |
+
- fastapi>=0.110.0
|
| 47 |
+
- uvicorn>=0.29.0
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
---
|
| 51 |
+
|
| 52 |
+
## Step 2 β `app.py` (FastAPI server for HF Spaces)
|
| 53 |
+
|
| 54 |
+
```python
|
| 55 |
+
"""
|
| 56 |
+
FastAPI wrapper exposing ViralScriptEnv as an OpenEnv-compliant HTTP server.
|
| 57 |
+
Deployed to HuggingFace Spaces on port 7860.
|
| 58 |
+
"""
|
| 59 |
+
from fastapi import FastAPI, HTTPException
|
| 60 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 61 |
+
from pydantic import BaseModel
|
| 62 |
+
from environment.env import ViralScriptEnv
|
| 63 |
+
import uvicorn
|
| 64 |
+
|
| 65 |
+
app = FastAPI(
|
| 66 |
+
title="Viral Script Debugging Engine",
|
| 67 |
+
description="Multi-agent RL environment for improving short-form video scripts",
|
| 68 |
+
version="1.0.0",
|
| 69 |
+
)
|
| 70 |
+
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
|
| 71 |
+
|
| 72 |
+
_envs: dict = {} # per-session env instances keyed by session_id
|
| 73 |
+
|
| 74 |
+
class ResetRequest(BaseModel):
|
| 75 |
+
session_id: str
|
| 76 |
+
difficulty: str = "easy"
|
| 77 |
+
options: dict = {}
|
| 78 |
+
|
| 79 |
+
class StepRequest(BaseModel):
|
| 80 |
+
session_id: str
|
| 81 |
+
action: dict
|
| 82 |
+
|
| 83 |
+
@app.post("/reset")
|
| 84 |
+
def reset(req: ResetRequest):
|
| 85 |
+
env = ViralScriptEnv(difficulty=req.difficulty)
|
| 86 |
+
obs, info = env.reset(options=req.options)
|
| 87 |
+
_envs[req.session_id] = env
|
| 88 |
+
return {"observation": obs, "info": info}
|
| 89 |
+
|
| 90 |
+
@app.post("/step")
|
| 91 |
+
def step(req: StepRequest):
|
| 92 |
+
env = _envs.get(req.session_id)
|
| 93 |
+
if not env:
|
| 94 |
+
raise HTTPException(404, f"Session {req.session_id} not found. Call /reset first.")
|
| 95 |
+
obs, reward, terminated, truncated, info = env.step(req.action)
|
| 96 |
+
return {"observation": obs, "reward": reward, "terminated": terminated, "truncated": truncated, "info": info}
|
| 97 |
+
|
| 98 |
+
@app.get("/state/{session_id}")
|
| 99 |
+
def state(session_id: str):
|
| 100 |
+
env = _envs.get(session_id)
|
| 101 |
+
if not env:
|
| 102 |
+
raise HTTPException(404, "Session not found")
|
| 103 |
+
return env.state()
|
| 104 |
+
|
| 105 |
+
@app.get("/health")
|
| 106 |
+
def health():
|
| 107 |
+
return {"status": "ok", "environment": "ViralScriptDebugEngine", "version": "1.0.0"}
|
| 108 |
+
|
| 109 |
+
if __name__ == "__main__":
|
| 110 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
---
|
| 114 |
+
|
| 115 |
+
## Step 3 β `Dockerfile`
|
| 116 |
+
|
| 117 |
+
```dockerfile
|
| 118 |
+
FROM python:3.11-slim
|
| 119 |
+
|
| 120 |
+
WORKDIR /app
|
| 121 |
+
COPY requirements.txt .
|
| 122 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 123 |
+
|
| 124 |
+
COPY . .
|
| 125 |
+
|
| 126 |
+
EXPOSE 7860
|
| 127 |
+
|
| 128 |
+
CMD ["python", "app.py"]
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
---
|
| 132 |
+
|
| 133 |
+
## Step 4 β `demo/run_demo.py`
|
| 134 |
+
|
| 135 |
+
The flagship demo script β what you run during the pitch. Must tell a 5-act story with `rich` terminal output.
|
| 136 |
+
|
| 137 |
+
```
|
| 138 |
+
python demo/run_demo.py --script S03 --compare # base vs trained side-by-side
|
| 139 |
+
python demo/run_demo.py --interactive # human acts as Arbitrator
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
**Act 1 β "The Raw Script"**
|
| 143 |
+
- Display original script in a `rich` Panel
|
| 144 |
+
- Show: region, platform, niche, known flaws
|
| 145 |
+
|
| 146 |
+
**Act 2 β "The Critic Attacks"**
|
| 147 |
+
- Run Critic on the script
|
| 148 |
+
- Display each `CritiqueClaim` as a numbered panel, colour-coded by severity (red=high, yellow=medium, green=low)
|
| 149 |
+
- 2-second pause between claims for dramatic effect
|
| 150 |
+
|
| 151 |
+
**Act 3 β "The Defender Responds"**
|
| 152 |
+
- Run Defender
|
| 153 |
+
- Display `core_strength` in a highlighted box: "WHAT WE MUST PROTECT"
|
| 154 |
+
- Show each `flagged_critic_claims` entry with "β Defender flagged this as overcorrection"
|
| 155 |
+
|
| 156 |
+
**Act 4 β "The Arbitrator Decides"** (both shown when `--compare`)
|
| 157 |
+
- Grey panel: "Untrained Arbitrator chose: [action] β Reasoning: [reasoning]"
|
| 158 |
+
- Blue panel: "Trained Arbitrator chose: [action] β Reasoning: [reasoning]"
|
| 159 |
+
- Highlight the difference in reasoning
|
| 160 |
+
|
| 161 |
+
**Act 5 β "The Rewrite + Reward"**
|
| 162 |
+
- Show rewritten script as unified diff (coloured: green=added, red=removed)
|
| 163 |
+
- Show reward components as a progress-bar table:
|
| 164 |
+
```
|
| 165 |
+
R1 Hook Strength ββββββββ 0.75
|
| 166 |
+
R2 Coherence ββββββββ 0.60
|
| 167 |
+
R3 Cultural ββββββββ 0.85
|
| 168 |
+
R4 Resolution ββββββββ 0.70
|
| 169 |
+
R5 Preservation ββββββββ 0.75
|
| 170 |
+
βββββββββββββββββββββββββββββββββ
|
| 171 |
+
Total ββββββββ 0.73 (+34% vs baseline)
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
---
|
| 175 |
+
|
| 176 |
+
## Step 5 β `README.md`
|
| 177 |
+
|
| 178 |
+
Write the complete README with this exact structure:
|
| 179 |
+
|
| 180 |
+
```markdown
|
| 181 |
+
# Viral Script Debugging Engine
|
| 182 |
+
### Meta Γ OpenEnv Hackathon 2026 | Theme 1: Multi-Agent Β· Theme 4: Self-Improvement
|
| 183 |
+
|
| 184 |
+
## The Problem
|
| 185 |
+
[2 paragraphs: 95% of creators never break 10k. Existing tools are one-shot pipelines, not RL.]
|
| 186 |
+
|
| 187 |
+
## What We Built
|
| 188 |
+
[2 paragraphs: the multi-agent RL loop. NOT a content generator. A reasoning system.]
|
| 189 |
+
|
| 190 |
+
## How It Works
|
| 191 |
+
[Describe the 4-step loop: Critic β Defender β Arbitrator β Rewriter. One episode = one trajectory.]
|
| 192 |
+
|
| 193 |
+
## Environment API
|
| 194 |
+
[Code block showing reset(), step(), state(), reward() with example inputs/outputs]
|
| 195 |
+
|
| 196 |
+
## Reward Functions
|
| 197 |
+
[Table of R1βR5: what each measures and how it's computed]
|
| 198 |
+
|
| 199 |
+
## Anti-Gaming Protections
|
| 200 |
+
[Explain the two rules: catastrophic drop penalty and action diversity penalty]
|
| 201 |
+
[Show 2β3 real examples from training logs where penalties fired]
|
| 202 |
+
|
| 203 |
+
## Self-Improvement Loop (Theme 4)
|
| 204 |
+
[Explain the Critic Escalation Engine and Difficulty Tracker]
|
| 205 |
+

|
| 206 |
+
|
| 207 |
+
## Training
|
| 208 |
+
Model: Qwen2.5-7B-Instruct | Algorithm: GRPO via TRL + Unsloth
|
| 209 |
+
[Link to Colab notebook]
|
| 210 |
+
|
| 211 |
+
## Results
|
| 212 |
+

|
| 213 |
+
[Table: per-reward improvement, baseline vs trained]
|
| 214 |
+
|
| 215 |
+
## Why This Matters for Meta
|
| 216 |
+
[One paragraph: the Meta business case]
|
| 217 |
+
|
| 218 |
+
## HuggingFace Space
|
| 219 |
+
[Link: huggingface.co/spaces/YOUR_TEAM/viral-script-debugging-engine]
|
| 220 |
+
|
| 221 |
+
## References
|
| 222 |
+
[Links to mini-blog, video demo, Colab notebook]
|
| 223 |
+
```
|
| 224 |
+
|
| 225 |
+
---
|
| 226 |
+
|
| 227 |
+
## Step 6 β `notebooks/training_colab.ipynb`
|
| 228 |
+
|
| 229 |
+
Generate a Colab-ready notebook with these cells in order:
|
| 230 |
+
|
| 231 |
+
```python
|
| 232 |
+
# Cell 1 β Install
|
| 233 |
+
!pip install unsloth trl anthropic sentence-transformers openenv pydantic rich
|
| 234 |
+
|
| 235 |
+
# Cell 2 β API key
|
| 236 |
+
import os
|
| 237 |
+
os.environ["ANTHROPIC_API_KEY"] = "YOUR_KEY_HERE"
|
| 238 |
+
|
| 239 |
+
# Cell 3 β Dry-run to validate pipeline
|
| 240 |
+
!python training/train_grpo.py --dry-run --steps 5
|
| 241 |
+
|
| 242 |
+
# Cell 4 β Full training
|
| 243 |
+
!python training/train_grpo.py --tier easy,medium --steps 200 --model unsloth/Qwen2.5-7B-Instruct-bnb-4bit
|
| 244 |
+
|
| 245 |
+
# Cell 5 β Evaluate and plot
|
| 246 |
+
!python training/eval_trained_model.py
|
| 247 |
+
|
| 248 |
+
# Cell 6 β Display reward curves inline
|
| 249 |
+
from IPython.display import Image
|
| 250 |
+
Image("logs/training_vs_baseline.png")
|
| 251 |
+
|
| 252 |
+
# Cell 7 β Run demo
|
| 253 |
+
!python demo/run_demo.py --script S03 --compare
|
| 254 |
+
```
|
| 255 |
+
|
| 256 |
+
---
|
| 257 |
+
|
| 258 |
+
## Step 7 β `scripts/submission_check.py`
|
| 259 |
+
|
| 260 |
+
```
|
| 261 |
+
python scripts/submission_check.py
|
| 262 |
+
```
|
| 263 |
+
|
| 264 |
+
Prints PASS or FAIL for each requirement:
|
| 265 |
+
|
| 266 |
+
- `openenv.yaml` exists and parses without error
|
| 267 |
+
- `app.py` starts without error (test with a 3-second subprocess timeout)
|
| 268 |
+
- README contains `huggingface.co/spaces` link
|
| 269 |
+
- `logs/baseline_reward_curves.png` exists
|
| 270 |
+
- `logs/training_vs_baseline.png` exists
|
| 271 |
+
- `logs/escalation_chart.png` exists
|
| 272 |
+
- `notebooks/training_colab.ipynb` exists
|
| 273 |
+
- README contains all required sections (The Problem, What We Built, Reward Functions, Anti-Gaming, Results)
|
| 274 |
+
- `requirements.txt` is complete
|
| 275 |
+
- All tests pass (`pytest` exit code 0)
|
| 276 |
+
|
| 277 |
+
Final output: `SUBMISSION READY β` or `SUBMISSION INCOMPLETE β fix the above before submitting`
|
| 278 |
+
|
| 279 |
+
---
|
| 280 |
+
|
| 281 |
+
## Gate check
|
| 282 |
+
|
| 283 |
+
Run:
|
| 284 |
+
```
|
| 285 |
+
python scripts/submission_check.py
|
| 286 |
+
```
|
| 287 |
+
|
| 288 |
+
All 10 checks must print PASS. Fix any failures before considering this phase done.
|
| 289 |
+
|
| 290 |
+
Then run the demo end-to-end once to confirm it tells the full 5-act story without errors:
|
| 291 |
+
```
|
| 292 |
+
python demo/run_demo.py --script S03 --compare
|
| 293 |
+
```
|
prompts/phase-index.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Claude Code Prompts β Index
|
| 2 |
+
## Viral Script Debugging Engine Β· Meta Γ OpenEnv Hackathon
|
| 3 |
+
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
## How to use these files
|
| 7 |
+
|
| 8 |
+
Each file is a standalone prompt. Open a **fresh Claude Code session** for each phase and paste the entire file contents. Do not trim or summarise β Claude Code needs the full context.
|
| 9 |
+
|
| 10 |
+
**Do not open the next phase until the gate check at the bottom of the current phase prints PASS.**
|
| 11 |
+
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
## Files
|
| 15 |
+
|
| 16 |
+
| File | Phase | What it builds | Gate command |
|
| 17 |
+
|---|---|---|---|
|
| 18 |
+
| `phase_0_critic_gate.md` | Phase 0 | Critic agent + evaluation harness + 10 test scripts | `python scripts/run_critic_gate.py --dry-run` |
|
| 19 |
+
| `phase_1_openenv_scaffold.md` | Phase 1 | OpenEnv env scaffold + R1/R2 rewards + Rewriter | `python scripts/run_dummy_episode.py --difficulty easy --steps 3 --verbose` |
|
| 20 |
+
| `phase_2_defender_rewards_baseline.md` | Phase 2 | Defender + R3/R4/R5 + anti-gaming logging + baseline curves | `python scripts/run_baseline.py` |
|
| 21 |
+
| `phase_3_curriculum_grpo_training.md` | Phase 3 | Curriculum datasets + GRPO training pipeline | `python training/train_grpo.py --dry-run` |
|
| 22 |
+
| `phase_4_escalation_engine.md` | Phase 4 | Difficulty Tracker + Critic Escalation Engine | `python scripts/run_escalation_demo.py --episodes 10 --verbose` |
|
| 23 |
+
| `phase_5_deployment_demo.md` | Phase 5 | FastAPI server + Dockerfile + demo script + README | `python scripts/submission_check.py` |
|
| 24 |
+
|
| 25 |
+
---
|
| 26 |
+
|
| 27 |
+
## Full file structure after all phases
|
| 28 |
+
|
| 29 |
+
```
|
| 30 |
+
viral_script_engine/
|
| 31 |
+
βββ agents/
|
| 32 |
+
β βββ critic.py # Phase 0
|
| 33 |
+
β βββ defender.py # Phase 2
|
| 34 |
+
β βββ rewriter.py # Phase 1
|
| 35 |
+
β βββ baseline_arbitrator.py # Phase 2
|
| 36 |
+
βββ data/
|
| 37 |
+
β βββ test_scripts/scripts.json # Phase 0
|
| 38 |
+
β βββ golden_fixtures/ # Phase 0
|
| 39 |
+
β βββ cultural_kb.json # Phase 2
|
| 40 |
+
β βββ curriculum/ # Phase 3
|
| 41 |
+
β βββ easy_tier.jsonl
|
| 42 |
+
β βββ medium_tier.jsonl
|
| 43 |
+
β βββ hard_tier.jsonl
|
| 44 |
+
β βββ synthetic_scripts.json
|
| 45 |
+
βββ environment/
|
| 46 |
+
β βββ env.py # Phase 1 (updated Phase 2, 4)
|
| 47 |
+
β βββ actions.py # Phase 1
|
| 48 |
+
β βββ observations.py # Phase 1
|
| 49 |
+
β βββ episode_state.py # Phase 1
|
| 50 |
+
βββ escalation/
|
| 51 |
+
β βββ difficulty_tracker.py # Phase 4
|
| 52 |
+
β βββ critic_escalation_engine.py # Phase 4
|
| 53 |
+
βββ evaluation/
|
| 54 |
+
β βββ critic_evaluator.py # Phase 0
|
| 55 |
+
βββ rewards/
|
| 56 |
+
β βββ base.py # Phase 1
|
| 57 |
+
β βββ r1_hook_strength.py # Phase 1
|
| 58 |
+
β βββ r2_coherence.py # Phase 1
|
| 59 |
+
β βββ r3_cultural_alignment.py # Phase 2
|
| 60 |
+
β βββ r4_debate_resolution.py # Phase 2
|
| 61 |
+
β βββ r5_defender_preservation.py # Phase 2
|
| 62 |
+
β βββ reward_aggregator.py # Phase 1 (updated Phase 2)
|
| 63 |
+
βββ training/
|
| 64 |
+
β βββ rollout_function.py # Phase 3
|
| 65 |
+
β βββ train_grpo.py # Phase 3
|
| 66 |
+
β βββ eval_trained_model.py # Phase 3
|
| 67 |
+
β βββ reward_curves.py # Phase 3
|
| 68 |
+
βββ demo/
|
| 69 |
+
β βββ run_demo.py # Phase 5
|
| 70 |
+
βββ scripts/
|
| 71 |
+
β βββ run_critic_gate.py # Phase 0
|
| 72 |
+
β βββ run_dummy_episode.py # Phase 1
|
| 73 |
+
β βββ run_baseline.py # Phase 2
|
| 74 |
+
β βββ run_escalation_demo.py # Phase 4
|
| 75 |
+
β βββ submission_check.py # Phase 5
|
| 76 |
+
βββ tests/
|
| 77 |
+
β βββ test_critic.py # Phase 0
|
| 78 |
+
β βββ test_environment.py # Phase 1
|
| 79 |
+
β βββ test_rewards.py # Phase 1
|
| 80 |
+
β βββ test_phase2.py # Phase 2
|
| 81 |
+
β βββ test_training_pipeline.py # Phase 3
|
| 82 |
+
β βββ test_escalation.py # Phase 4
|
| 83 |
+
βββ notebooks/
|
| 84 |
+
β βββ training_colab.ipynb # Phase 5
|
| 85 |
+
βββ logs/ # generated at runtime
|
| 86 |
+
βββ outputs/ # training checkpoints
|
| 87 |
+
βββ app.py # Phase 5
|
| 88 |
+
βββ openenv.yaml # Phase 5
|
| 89 |
+
βββ Dockerfile # Phase 5
|
| 90 |
+
βββ requirements.txt # Phase 0
|
| 91 |
+
βββ README.md # Phase 5
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
---
|
| 95 |
+
|
| 96 |
+
## Key constraints to keep in mind across all phases
|
| 97 |
+
|
| 98 |
+
- Use the **Anthropic Python SDK** only (not OpenAI)
|
| 99 |
+
- All models/dataclasses use **Pydantic** for validation
|
| 100 |
+
- LLM calls only in: CriticAgent, DefenderAgent, RewriterAgent, BaselineArbitratorAgent, CriticEscalationEngine
|
| 101 |
+
- Evaluators and reward scorers (R1, R3) are **purely rule-based β zero LLM calls**
|
| 102 |
+
- Store API key in `.env`, load with `python-dotenv`
|
| 103 |
+
- Use `rich` for all console output
|
| 104 |
+
- Mock all Anthropic API calls in tests β no real API calls in the test suite
|
| 105 |
+
- Model saving: always use `save_pretrained_merged`, never naive upcast from 4-bit
|
scripts/deploy.sh
ADDED
|
File without changes
|
scripts/dev.sh
ADDED
|
File without changes
|
scripts/test.sh
ADDED
|
File without changes
|
session/context.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Context β Carry Over for Next Session
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
Read this file at every session start after index.md and phase-log.md.
|
| 5 |
+
Contains only what Claude needs to resume without re-reading everything.
|
| 6 |
+
Overwrite when context changes. Keep it minimal and current.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Current Phase
|
| 11 |
+
Phase: [number]
|
| 12 |
+
Prompt file: prompts/phase-X.md
|
| 13 |
+
Status: [in progress / complete / blocked]
|
| 14 |
+
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
## Currently Working On
|
| 18 |
+
Feature: [name]
|
| 19 |
+
File(s): [list]
|
| 20 |
+
Status: [what is done, what is not]
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## Open Questions
|
| 25 |
+
|
| 26 |
+
[question that needs user input before proceeding]
|
| 27 |
+
[question that needs user input before proceeding]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
---
|
| 31 |
+
|
| 32 |
+
## Known Blockers
|
| 33 |
+
|
| 34 |
+
[what is blocked and why]
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
## Last Commit Message
|
| 40 |
+
[most recent commit message generated]
|
| 41 |
+
|
| 42 |
+
---
|
| 43 |
+
|
| 44 |
+
## Do Not Forget
|
| 45 |
+
|
| 46 |
+
[critical thing to remember for next session]
|
| 47 |
+
[critical thing to remember for next session]
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
---
|
| 51 |
+
|
| 52 |
+
## Rules for This File
|
| 53 |
+
- Keep this file under 30 lines always
|
| 54 |
+
- Overwrite at end of every session
|
| 55 |
+
- Only include what is immediately needed to resume
|
| 56 |
+
- Do not include explanations β only facts and state
|
session/phase-log.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Phase Log β Full History
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
Read this file at every session start to know overall progress.
|
| 5 |
+
One line per phase or significant milestone. Never overwrite β only append.
|
| 6 |
+
This is the only file that keeps permanent history across all sessions.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Format
|
| 11 |
+
[YYYY-MM-DD] [Phase X] [status] β [one liner of what happened]
|
| 12 |
+
|
| 13 |
+
## Status Tags
|
| 14 |
+
STARTED β phase work has begun
|
| 15 |
+
PARTIAL β some features done, phase not complete
|
| 16 |
+
COMPLETE β all features done, all tests passing
|
| 17 |
+
BLOCKED β cannot proceed, reason in line
|
| 18 |
+
ROLLED BACK β changes reverted, reason in line
|
| 19 |
+
|
| 20 |
+
---
|
| 21 |
+
|
| 22 |
+
## Log
|
| 23 |
+
[YYYY-MM-DD] [Phase 1] STARTED β project scaffolding begun
|
| 24 |
+
|
| 25 |
+
---
|
| 26 |
+
|
| 27 |
+
## Rules for This File
|
| 28 |
+
- Never delete or overwrite any line
|
| 29 |
+
- Append only β one line per session or milestone
|
| 30 |
+
- Keep each line under 15 words after the date and phase tag
|
| 31 |
+
- This file is the single source of truth for project history
|
session/summary.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Session Summary β Last Session Record
|
| 2 |
+
|
| 3 |
+
## Purpose
|
| 4 |
+
Read this file only if context.md is unclear or incomplete.
|
| 5 |
+
Overwrite this file at the end of every session.
|
| 6 |
+
One session = one summary. Previous summaries live in phase-log.md.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Last Session
|
| 11 |
+
|
| 12 |
+
### Date
|
| 13 |
+
[YYYY-MM-DD]
|
| 14 |
+
|
| 15 |
+
### Phase
|
| 16 |
+
[Phase number and name]
|
| 17 |
+
|
| 18 |
+
### What Was Done
|
| 19 |
+
|
| 20 |
+
[one liner]
|
| 21 |
+
[one liner]
|
| 22 |
+
[one liner]
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
### What Was NOT Done (carry over)
|
| 26 |
+
|
| 27 |
+
[one liner]
|
| 28 |
+
[one liner]
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
### Errors Encountered
|
| 32 |
+
|
| 33 |
+
[file:function] β [reason] β [how it was fixed]
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
### Tests Status
|
| 37 |
+
Total: 0 | Passed: 0 | Failed: 0
|
| 38 |
+
|
| 39 |
+
### Commit Messages Generated
|
| 40 |
+
|
| 41 |
+
[commit message]
|
| 42 |
+
[commit message]
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
### Notes for Next Session
|
| 46 |
+
|
| 47 |
+
[one liner]
|
| 48 |
+
[one liner]
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
---
|
| 52 |
+
|
| 53 |
+
## Rules for This File
|
| 54 |
+
- Overwrite at end of every session β do not append
|
| 55 |
+
- Keep every section to one liners only
|
| 56 |
+
- Move key notes to context.md if needed next session
|
| 57 |
+
- Full phase history lives in phase-log.md not here
|