@plazmodium/odin 0.3.2-beta → 0.3.4-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +82 -11
  2. package/builtin/ODIN.md +1045 -0
  3. package/builtin/agent-definitions/README.md +170 -0
  4. package/builtin/agent-definitions/_shared-context.md +377 -0
  5. package/builtin/agent-definitions/architect.md +627 -0
  6. package/builtin/agent-definitions/builder.md +716 -0
  7. package/builtin/agent-definitions/discovery.md +293 -0
  8. package/builtin/agent-definitions/documenter.md +238 -0
  9. package/builtin/agent-definitions/guardian.md +1049 -0
  10. package/builtin/agent-definitions/integrator.md +363 -0
  11. package/builtin/agent-definitions/planning.md +236 -0
  12. package/builtin/agent-definitions/product.md +405 -0
  13. package/builtin/agent-definitions/release.md +430 -0
  14. package/builtin/agent-definitions/reviewer.md +447 -0
  15. package/builtin/agent-definitions/watcher.md +402 -0
  16. package/builtin/skills/api/graphql/SKILL.md +548 -0
  17. package/builtin/skills/api/grpc/SKILL.md +554 -0
  18. package/builtin/skills/api/rest-api/SKILL.md +469 -0
  19. package/builtin/skills/api/trpc/SKILL.md +503 -0
  20. package/builtin/skills/architecture/clean-architecture/SKILL.md +141 -0
  21. package/builtin/skills/architecture/domain-driven-design/SKILL.md +129 -0
  22. package/builtin/skills/architecture/event-driven/SKILL.md +145 -0
  23. package/builtin/skills/architecture/microservices/SKILL.md +143 -0
  24. package/builtin/skills/architecture/tla-precheck/SKILL.md +171 -0
  25. package/builtin/skills/backend/golang-gin/SKILL.md +141 -0
  26. package/builtin/skills/backend/nodejs-express/SKILL.md +277 -0
  27. package/builtin/skills/backend/nodejs-fastify/SKILL.md +152 -0
  28. package/builtin/skills/backend/python-django/SKILL.md +128 -0
  29. package/builtin/skills/backend/python-fastapi/SKILL.md +140 -0
  30. package/builtin/skills/database/mongodb/SKILL.md +132 -0
  31. package/builtin/skills/database/postgresql/SKILL.md +120 -0
  32. package/builtin/skills/database/prisma-orm/SKILL.md +366 -0
  33. package/builtin/skills/database/redis/SKILL.md +140 -0
  34. package/builtin/skills/database/supabase/SKILL.md +416 -0
  35. package/builtin/skills/devops/aws/SKILL.md +382 -0
  36. package/builtin/skills/devops/docker/SKILL.md +359 -0
  37. package/builtin/skills/devops/github-actions/SKILL.md +435 -0
  38. package/builtin/skills/devops/kubernetes/SKILL.md +459 -0
  39. package/builtin/skills/devops/terraform/SKILL.md +453 -0
  40. package/builtin/skills/frontend/alpine-dev/SKILL.md +27 -0
  41. package/builtin/skills/frontend/angular-dev/SKILL.md +28 -0
  42. package/builtin/skills/frontend/astro-dev/SKILL.md +28 -0
  43. package/builtin/skills/frontend/htmx-dev/SKILL.md +28 -0
  44. package/builtin/skills/frontend/nextjs-dev/SKILL.md +470 -0
  45. package/builtin/skills/frontend/react-patterns/SKILL.md +166 -0
  46. package/builtin/skills/frontend/svelte-dev/SKILL.md +28 -0
  47. package/builtin/skills/frontend/tailwindcss/SKILL.md +131 -0
  48. package/builtin/skills/frontend/vuejs-dev/SKILL.md +28 -0
  49. package/builtin/skills/generic-dev/SKILL.md +307 -0
  50. package/builtin/skills/testing/cypress/SKILL.md +372 -0
  51. package/builtin/skills/testing/jest/SKILL.md +176 -0
  52. package/builtin/skills/testing/playwright/SKILL.md +341 -0
  53. package/builtin/skills/testing/unit-tests-eval-sdd/SKILL.md +73 -0
  54. package/builtin/skills/testing/unit-tests-sdd/SKILL.md +83 -0
  55. package/builtin/skills/testing/vitest/SKILL.md +249 -0
  56. package/dist/adapters/skills/filesystem.d.ts.map +1 -1
  57. package/dist/adapters/skills/filesystem.js +2 -18
  58. package/dist/adapters/skills/filesystem.js.map +1 -1
  59. package/dist/builtin-assets.d.ts +8 -0
  60. package/dist/builtin-assets.d.ts.map +1 -0
  61. package/dist/builtin-assets.js +90 -0
  62. package/dist/builtin-assets.js.map +1 -0
  63. package/dist/init.js +69 -11
  64. package/dist/init.js.map +1 -1
  65. package/dist/schemas.d.ts +1 -1
  66. package/dist/server.js +1 -1
  67. package/dist/server.js.map +1 -1
  68. package/dist/tools/prepare-phase-context.d.ts.map +1 -1
  69. package/dist/tools/prepare-phase-context.js +5 -0
  70. package/dist/tools/prepare-phase-context.js.map +1 -1
  71. package/dist/types.d.ts +3 -0
  72. package/dist/types.d.ts.map +1 -1
  73. package/package.json +5 -3
@@ -0,0 +1,416 @@
1
+ ---
2
+ name: supabase
3
+ description: Supabase expertise for PostgreSQL database, authentication, storage, real-time subscriptions, and edge functions
4
+ category: database
5
+ version: "2.x"
6
+ depends_on:
7
+ - postgresql
8
+ compatible_with:
9
+ - nextjs-dev
10
+ - nodejs-express
11
+ - prisma-orm
12
+ ---
13
+
14
+ # Supabase Development
15
+
16
+ ## Overview
17
+
18
+ Supabase is an open-source Firebase alternative providing PostgreSQL database, authentication, instant APIs, real-time subscriptions, storage, and edge functions.
19
+
20
+ ## Client Setup
21
+
22
+ ### JavaScript/TypeScript
23
+
24
+ ```typescript
25
+ // src/lib/supabase.ts
26
+ import { createClient } from '@supabase/supabase-js';
27
+ import type { Database } from '@/types/supabase';
28
+
29
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
30
+ const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
31
+
32
+ export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey);
33
+
34
+ // For server-side with secret key (admin access, bypasses RLS)
35
+ // Find in: Supabase Dashboard → Settings → API → Secret keys
36
+ export const supabaseAdmin = createClient<Database>(
37
+ supabaseUrl,
38
+ process.env.SUPABASE_SECRET_KEY!,
39
+ {
40
+ auth: { persistSession: false },
41
+ global: {
42
+ // CRITICAL for Next.js 14: Bypass fetch cache to prevent stale data.
43
+ // Next.js patches global fetch with cache: 'force-cache' by default.
44
+ fetch: (input, init) =>
45
+ fetch(input, { ...init, cache: 'no-store' }),
46
+ },
47
+ }
48
+ );
49
+ ```
50
+
51
+ ### Generate Types
52
+
53
+ ```bash
54
+ npx supabase gen types typescript --project-id YOUR_PROJECT_ID > types/supabase.ts
55
+ ```
56
+
57
+ ## Database Queries
58
+
59
+ ### Basic CRUD
60
+
61
+ ```typescript
62
+ // Select
63
+ const { data, error } = await supabase
64
+ .from('users')
65
+ .select('id, email, name')
66
+ .eq('is_archived', false);
67
+
68
+ // Select with relations
69
+ const { data, error } = await supabase
70
+ .from('posts')
71
+ .select(`
72
+ id,
73
+ title,
74
+ content,
75
+ author:users(id, name, email)
76
+ `)
77
+ .eq('published', true);
78
+
79
+ // Insert
80
+ const { data, error } = await supabase
81
+ .from('users')
82
+ .insert({ email: 'user@example.com', name: 'John' })
83
+ .select()
84
+ .single();
85
+
86
+ // Update
87
+ const { data, error } = await supabase
88
+ .from('users')
89
+ .update({ name: 'Jane' })
90
+ .eq('id', userId)
91
+ .select()
92
+ .single();
93
+
94
+ // Archive (not delete!)
95
+ const { error } = await supabase
96
+ .from('users')
97
+ .update({ is_archived: true, archived_at: new Date().toISOString() })
98
+ .eq('id', userId);
99
+
100
+ // Actual delete (use sparingly)
101
+ const { error } = await supabase
102
+ .from('users')
103
+ .delete()
104
+ .eq('id', userId);
105
+ ```
106
+
107
+ ### Filtering
108
+
109
+ ```typescript
110
+ // Multiple conditions
111
+ const { data } = await supabase
112
+ .from('posts')
113
+ .select('*')
114
+ .eq('published', true)
115
+ .eq('is_archived', false)
116
+ .gte('created_at', '2024-01-01')
117
+ .order('created_at', { ascending: false })
118
+ .limit(10);
119
+
120
+ // OR conditions
121
+ const { data } = await supabase
122
+ .from('users')
123
+ .select('*')
124
+ .or('role.eq.admin,role.eq.moderator');
125
+
126
+ // Text search
127
+ const { data } = await supabase
128
+ .from('posts')
129
+ .select('*')
130
+ .textSearch('title', 'search query');
131
+
132
+ // In array
133
+ const { data } = await supabase
134
+ .from('users')
135
+ .select('*')
136
+ .in('id', [id1, id2, id3]);
137
+ ```
138
+
139
+ ### Pagination
140
+
141
+ ```typescript
142
+ const page = 1;
143
+ const pageSize = 20;
144
+
145
+ const { data, count } = await supabase
146
+ .from('posts')
147
+ .select('*', { count: 'exact' })
148
+ .eq('is_archived', false)
149
+ .range((page - 1) * pageSize, page * pageSize - 1)
150
+ .order('created_at', { ascending: false });
151
+
152
+ const totalPages = Math.ceil((count || 0) / pageSize);
153
+ ```
154
+
155
+ ## Authentication
156
+
157
+ ### Sign Up / Sign In
158
+
159
+ ```typescript
160
+ // Sign up with email
161
+ const { data, error } = await supabase.auth.signUp({
162
+ email: 'user@example.com',
163
+ password: 'securepassword',
164
+ options: {
165
+ data: { name: 'John Doe' }, // Custom user metadata
166
+ },
167
+ });
168
+
169
+ // Sign in with email
170
+ const { data, error } = await supabase.auth.signInWithPassword({
171
+ email: 'user@example.com',
172
+ password: 'securepassword',
173
+ });
174
+
175
+ // Sign in with OAuth
176
+ const { data, error } = await supabase.auth.signInWithOAuth({
177
+ provider: 'google',
178
+ options: { redirectTo: `${origin}/auth/callback` },
179
+ });
180
+
181
+ // Sign out
182
+ await supabase.auth.signOut();
183
+ ```
184
+
185
+ ### Session Management
186
+
187
+ ```typescript
188
+ // Get current session
189
+ const { data: { session } } = await supabase.auth.getSession();
190
+
191
+ // Get current user
192
+ const { data: { user } } = await supabase.auth.getUser();
193
+
194
+ // Listen to auth changes
195
+ supabase.auth.onAuthStateChange((event, session) => {
196
+ if (event === 'SIGNED_IN') {
197
+ // Handle sign in
198
+ } else if (event === 'SIGNED_OUT') {
199
+ // Handle sign out
200
+ }
201
+ });
202
+ ```
203
+
204
+ ### Password Reset
205
+
206
+ ```typescript
207
+ // Send reset email
208
+ const { error } = await supabase.auth.resetPasswordForEmail(email, {
209
+ redirectTo: `${origin}/auth/reset-password`,
210
+ });
211
+
212
+ // Update password (after redirect)
213
+ const { error } = await supabase.auth.updateUser({
214
+ password: newPassword,
215
+ });
216
+ ```
217
+
218
+ ## Row Level Security (RLS)
219
+
220
+ ### Enable RLS
221
+
222
+ ```sql
223
+ -- Always enable RLS on tables with user data
224
+ ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
225
+
226
+ -- Policy: Users can read published posts
227
+ CREATE POLICY "Public posts are viewable by everyone"
228
+ ON posts FOR SELECT
229
+ USING (published = true AND is_archived = false);
230
+
231
+ -- Policy: Users can only edit their own posts
232
+ CREATE POLICY "Users can edit own posts"
233
+ ON posts FOR UPDATE
234
+ USING (auth.uid() = author_id);
235
+
236
+ -- Policy: Users can only insert as themselves
237
+ CREATE POLICY "Users can create own posts"
238
+ ON posts FOR INSERT
239
+ WITH CHECK (auth.uid() = author_id);
240
+ ```
241
+
242
+ ### Common RLS Patterns
243
+
244
+ ```sql
245
+ -- Authenticated users only
246
+ CREATE POLICY "Authenticated access"
247
+ ON table_name FOR ALL
248
+ USING (auth.role() = 'authenticated');
249
+
250
+ -- Owner access
251
+ CREATE POLICY "Owner access"
252
+ ON table_name FOR ALL
253
+ USING (auth.uid() = user_id);
254
+
255
+ -- Role-based access
256
+ CREATE POLICY "Admin access"
257
+ ON table_name FOR ALL
258
+ USING (
259
+ EXISTS (
260
+ SELECT 1 FROM users
261
+ WHERE users.id = auth.uid()
262
+ AND users.role = 'admin'
263
+ )
264
+ );
265
+ ```
266
+
267
+ ## Real-time Subscriptions
268
+
269
+ ### Prerequisites (CRITICAL)
270
+
271
+ Supabase Realtime requires **three layers** of configuration:
272
+
273
+ 1. **Database layer** — Add tables to the `supabase_realtime` publication:
274
+ ```sql
275
+ ALTER PUBLICATION supabase_realtime ADD TABLE your_table;
276
+ ```
277
+
278
+ 2. **RLS layer** — The subscribing role (usually `anon`) needs SELECT policies:
279
+ ```sql
280
+ CREATE POLICY "Anon read access" ON your_table
281
+ FOR SELECT TO anon USING (true);
282
+ ```
283
+
284
+ 3. **Infrastructure layer** — The Realtime service must recognize your project as a "tenant". This is configured through the **Supabase Dashboard → Database → Replication**, NOT via SQL.
285
+
286
+ > **GOTCHA: TenantNotFound Error**
287
+ >
288
+ > If you only configure layers 1 and 2 via SQL but skip layer 3 (Dashboard), WebSocket connections will fail with `TenantNotFound: Tenant not found: <project-ref>`. The database side looks correct (publication exists, RLS policies in place, `wal_level = logical`), but the Realtime Elixir service doesn't have your project registered.
289
+ >
290
+ > **Diagnosis**: Check Supabase Realtime logs for `error_code: "TenantNotFound"`. You may also see Cloudflare Error 1101 ("Worker threw exception") when hitting the `/realtime/v1/` endpoint.
291
+ >
292
+ > **As of 2026-02**: Database → Replication is in **alpha** and requires paid onboarding on some plans. If you can't enable it, use **polling** instead (see Next.js skill for the pattern).
293
+
294
+ ### Subscription Code (when Realtime IS available)
295
+
296
+ ```typescript
297
+ // Subscribe to changes
298
+ const channel = supabase
299
+ .channel('posts-changes')
300
+ .on(
301
+ 'postgres_changes',
302
+ {
303
+ event: '*', // INSERT, UPDATE, DELETE, or *
304
+ schema: 'public',
305
+ table: 'posts',
306
+ filter: 'author_id=eq.' + userId,
307
+ },
308
+ (payload) => {
309
+ console.log('Change:', payload);
310
+ }
311
+ )
312
+ .subscribe();
313
+
314
+ // Cleanup
315
+ channel.unsubscribe();
316
+ ```
317
+
318
+ ### Verification Checklist
319
+
320
+ Before debugging Realtime issues, verify ALL three layers:
321
+
322
+ ```sql
323
+ -- 1. Check publication has your tables
324
+ SELECT tablename FROM pg_publication_tables WHERE pubname = 'supabase_realtime';
325
+
326
+ -- 2. Check anon SELECT policies exist
327
+ SELECT tablename, policyname FROM pg_policies
328
+ WHERE schemaname = 'public' AND roles::text LIKE '%anon%' AND cmd = 'SELECT';
329
+
330
+ -- 3. Check wal_level (must be 'logical')
331
+ SHOW wal_level;
332
+ ```
333
+
334
+ Then check the **Supabase Dashboard → Database → Replication** to confirm the Realtime service is enabled for your project. If all three SQL checks pass but WebSocket still fails, the issue is layer 3.
335
+
336
+ ## Storage
337
+
338
+ ```typescript
339
+ // Upload file
340
+ const { data, error } = await supabase.storage
341
+ .from('avatars')
342
+ .upload(`${userId}/avatar.png`, file, {
343
+ cacheControl: '3600',
344
+ upsert: true,
345
+ });
346
+
347
+ // Get public URL
348
+ const { data } = supabase.storage
349
+ .from('avatars')
350
+ .getPublicUrl(`${userId}/avatar.png`);
351
+
352
+ // Download file
353
+ const { data, error } = await supabase.storage
354
+ .from('documents')
355
+ .download('path/to/file.pdf');
356
+
357
+ // Delete file
358
+ const { error } = await supabase.storage
359
+ .from('avatars')
360
+ .remove([`${userId}/avatar.png`]);
361
+ ```
362
+
363
+ ## Edge Functions
364
+
365
+ ```typescript
366
+ // Invoke edge function
367
+ const { data, error } = await supabase.functions.invoke('function-name', {
368
+ body: { key: 'value' },
369
+ });
370
+ ```
371
+
372
+ ## Best Practices
373
+
374
+ 1. **Always enable RLS** - Never expose tables without row-level security
375
+ 2. **Use secret key server-side only** - Never expose the Supabase secret key to client
376
+ 3. **Generate types** - Use `supabase gen types` for type safety
377
+ 4. **Archive, don't delete** - Use `is_archived` pattern for recoverable data
378
+ 5. **Filter archived records** - Always add `.eq('is_archived', false)` to queries
379
+ 6. **Handle errors** - Always check for `error` in responses
380
+ 7. **Use transactions** - Use `rpc` for complex multi-table operations
381
+
382
+ ## Gotchas & Pitfalls
383
+
384
+ - **RLS blocks service role by default** - Use `SECURITY DEFINER` functions or check policies
385
+ - **Real-time has 3 config layers** - SQL publication + RLS policies + Dashboard Replication toggle. Missing any one causes silent failures
386
+ - **Real-time TenantNotFound** - If WebSocket fails with this error, the Dashboard Replication feature isn't enabled. SQL-only setup is NOT sufficient
387
+ - **Real-time requires paid onboarding** - As of 2026-02, Database → Replication is in alpha on some plans. Use polling as fallback
388
+ - **Key naming changed** - "Service Role Key" is now "Secret key", "Anon Key" is now "Publishable key" in the Dashboard
389
+ - **Next.js 14 caches Supabase responses** - The Supabase JS client uses `fetch` internally, and Next.js 14 patches `fetch` with `cache: 'force-cache'` by default. This causes stale data even with `force-dynamic` on pages. **Fix**: Pass `global: { fetch: (input, init) => fetch(input, { ...init, cache: 'no-store' }) }` in the client constructor. See frontend/nextjs-dev skill for full details.
390
+ - **Storage policies separate** - Storage has its own policy system
391
+ - **Type generation** - Re-run after schema changes
392
+ - **Connection limits** - Use connection pooling for serverless
393
+ - **Null handling** - PostgreSQL nulls require explicit handling
394
+
395
+ ## Integration with Next.js
396
+
397
+ ```typescript
398
+ // app/api/posts/route.ts
399
+ import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
400
+ import { cookies } from 'next/headers';
401
+
402
+ export async function GET() {
403
+ const supabase = createRouteHandlerClient({ cookies });
404
+
405
+ const { data, error } = await supabase
406
+ .from('posts')
407
+ .select('*')
408
+ .eq('is_archived', false);
409
+
410
+ if (error) {
411
+ return Response.json({ error: error.message }, { status: 500 });
412
+ }
413
+
414
+ return Response.json(data);
415
+ }
416
+ ```