nextjs-hackathon-stack 0.1.30 → 0.1.32

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 (40) hide show
  1. package/dist/index.js +6 -3
  2. package/package.json +1 -1
  3. package/template/.cursor/agents/backend.md +10 -59
  4. package/template/.cursor/agents/business-intelligence.md +43 -15
  5. package/template/.cursor/agents/code-reviewer.md +2 -2
  6. package/template/.cursor/agents/frontend.md +6 -10
  7. package/template/.cursor/agents/security-researcher.md +8 -1
  8. package/template/.cursor/agents/technical-lead.md +47 -18
  9. package/template/.cursor/agents/test-qa.md +9 -49
  10. package/template/.cursor/rules/architecture.mdc +5 -2
  11. package/template/.cursor/rules/coding-standards.mdc +53 -3
  12. package/template/.cursor/rules/components.mdc +7 -0
  13. package/template/.cursor/rules/data-fetching.mdc +56 -5
  14. package/template/.cursor/rules/forms.mdc +4 -0
  15. package/template/.cursor/rules/general.mdc +8 -5
  16. package/template/.cursor/rules/nextjs.mdc +27 -3
  17. package/template/.cursor/rules/security.mdc +56 -3
  18. package/template/.cursor/rules/supabase.mdc +19 -3
  19. package/template/.cursor/rules/testing.mdc +21 -4
  20. package/template/.cursor/skills/create-feature/SKILL.md +29 -8
  21. package/template/.cursor/skills/create-feature/references/server-action-test-template.md +51 -0
  22. package/template/.cursor/skills/review-branch/SKILL.md +2 -22
  23. package/template/.cursor/skills/review-branch/references/review-checklist.md +36 -0
  24. package/template/.cursor/skills/security-audit/SKILL.md +8 -39
  25. package/template/.cursor/skills/security-audit/references/audit-steps.md +41 -0
  26. package/template/CLAUDE.md +43 -10
  27. package/template/eslint.config.ts +7 -1
  28. package/template/next.config.ts +5 -1
  29. package/template/src/app/(protected)/page.tsx +2 -4
  30. package/template/src/app/__tests__/auth-callback.test.ts +14 -0
  31. package/template/src/app/__tests__/protected-page.test.tsx +11 -13
  32. package/template/src/app/api/auth/callback/route.ts +2 -1
  33. package/template/src/e2e/login.spec.ts +4 -4
  34. package/template/src/features/todos/__tests__/todos.action.test.ts +78 -44
  35. package/template/src/features/todos/__tests__/todos.queries.test.ts +101 -0
  36. package/template/src/features/todos/actions/todos.action.ts +41 -27
  37. package/template/src/features/todos/queries/todos.queries.ts +16 -0
  38. package/template/src/shared/lib/supabase/middleware.ts +0 -1
  39. package/template/src/shared/lib/supabase/server.ts +0 -1
  40. package/template/tailwind.css +14 -14
@@ -1,12 +1,13 @@
1
1
  ---
2
- description: Stack overview and project conventions. Always applies.
3
- alwaysApply: true
2
+ description: Stack overview and project conventions. Applies to all source files.
3
+ alwaysApply: false
4
+ globs: ["src/**"]
4
5
  ---
5
6
 
6
7
  # Stack Overview
7
8
 
8
9
  ## Technology Stack
9
- - **Framework**: Next.js 16 (App Router)
10
+ - **Framework**: Next.js 16.2 (App Router)
10
11
  - **Database**: Supabase (PostgreSQL) with Drizzle ORM for schema/migrations
11
12
  - **Auth**: Supabase Auth via `@supabase/ssr`
12
13
  - **ORM**: Drizzle + `drizzle-zod` (schema + migrations ONLY — no runtime queries)
@@ -14,7 +15,7 @@ alwaysApply: true
14
15
  - **Client State**: TanStack Query v5
15
16
  - **Validation**: Zod (auto-generated via `drizzle-zod`)
16
17
  - **Forms**: React Hook Form + Zod resolver
17
- - **UI**: shadcn/ui + Tailwind CSS v4
18
+ - **UI**: shadcn/ui + Tailwind CSS v4 (shadcn skill installed for AI context)
18
19
  - **AI**: Vercel AI SDK + Google Gemini 2.0 Flash via AI Gateway
19
20
  - **Testing**: Vitest + React Testing Library + Playwright
20
21
 
@@ -26,8 +27,10 @@ src/
26
27
  │ └── <feature>/
27
28
  │ ├── components/
28
29
  │ ├── actions/
30
+ │ ├── queries/
29
31
  │ ├── hooks/
30
32
  │ ├── api/
33
+ │ ├── lib/
31
34
  │ └── __tests__/
32
35
  ├── shared/ # Shared across features
33
36
  │ ├── components/ui/ # shadcn/ui components
@@ -40,7 +43,7 @@ src/
40
43
  - Files: `kebab-case.ts`
41
44
  - Components: `PascalCase`
42
45
  - Hooks: `useCamelCase`
43
- - Server Actions: `camelCase.action.ts`
46
+ - Server Actions: `kebab-case.action.ts`
44
47
  - API Routes: `route.ts` (in feature `api/` folder)
45
48
  - Tests: `*.test.ts` or `*.test.tsx`
46
49
  - E2e: `*.spec.ts`
@@ -1,9 +1,9 @@
1
1
  ---
2
- description: Next.js 16 App Router patterns.
2
+ description: Next.js 16.2 App Router patterns.
3
3
  globs: ["src/app/**"]
4
4
  ---
5
5
 
6
- # Next.js 16 Rules
6
+ # Next.js 16.2 Rules
7
7
 
8
8
  ## App Router Conventions
9
9
  - `page.tsx` — public route page component
@@ -11,7 +11,7 @@ globs: ["src/app/**"]
11
11
  - `loading.tsx` — Suspense boundary UI
12
12
  - `error.tsx` — Error boundary UI
13
13
  - `route.ts` — API route handler
14
- - `proxy.ts` — Request proxy (project root, Node.js runtime)
14
+ - `proxy.ts` — Request proxy (project root, Node.js runtime). Intercepts and rewrites requests before they reach route handlers (e.g., session validation, redirects).
15
15
 
16
16
  ## Route Groups
17
17
  - `(auth)` — unauthenticated routes
@@ -44,3 +44,27 @@ export const metadata: Metadata = {
44
44
  description: "Page description",
45
45
  };
46
46
  ```
47
+
48
+ ## Browser Log Forwarding
49
+
50
+ Next.js 16.2 can forward browser logs to the terminal. Configure in `next.config.ts`:
51
+
52
+ ```typescript
53
+ const nextConfig: NextConfig = {
54
+ logging: {
55
+ browserToTerminal: 'error', // 'error' (default) | 'warn' | true (all) | false (disabled)
56
+ },
57
+ };
58
+ ```
59
+
60
+ - `'error'` — forwards browser errors only (default)
61
+ - `'warn'` — forwards errors and warnings
62
+ - `true` — forwards all console output
63
+ - `false` — disabled
64
+
65
+ ## Dev Server Lock File
66
+
67
+ Next.js 16.2 writes `.next/dev/lock` when `next dev` starts. The file contains the PID, port, and URL of the running dev server.
68
+
69
+ - Agents and scripts must check `.next/dev/lock` before starting `next dev` or `next build` to avoid duplicate processes.
70
+ - If the lock file exists and the PID is still alive, attach to the existing server instead of starting a new one.
@@ -1,6 +1,6 @@
1
1
  ---
2
- description: Security rules and OWASP guidelines. Always applies.
3
- alwaysApply: true
2
+ description: Security rules (OWASP, auth, RLS, XSS, CSP). Applied to server code, API routes, actions, auth, and config.
3
+ globs: ["src/**/actions/**", "src/**/api/**", "src/**/lib/supabase/**", "src/app/**/route.ts", "src/app/**/proxy.ts", "*.config.*"]
4
4
  ---
5
5
 
6
6
  # Security Rules
@@ -28,7 +28,10 @@ Never use `NEXT_PUBLIC_` for secret keys.
28
28
  - Write explicit allow/deny policies
29
29
  - Test RLS policies — never assume they work
30
30
 
31
- ## Auth in API Routes
31
+ ## Auth in API Routes and Server Actions
32
+
33
+ Every Server Action and API route that accesses data must verify auth via `getUser()` before any data access. RLS provides row-level enforcement, but `getUser()` at the action level is required as defense-in-depth — never rely on RLS alone.
34
+
32
35
  ```typescript
33
36
  export async function POST(request: Request) {
34
37
  const supabase = await createClient();
@@ -38,6 +41,36 @@ export async function POST(request: Request) {
38
41
  }
39
42
  ```
40
43
 
44
+ Every mutation Server Action must:
45
+ 1. Call `getUser()` and return an error result if no user
46
+ 2. Scope all queries to `user.id` (even with RLS active)
47
+
48
+ ```typescript
49
+ export async function deleteItemAction(id: string): Promise<ActionResult> {
50
+ const supabase = await createClient();
51
+ const { data: { user } } = await supabase.auth.getUser();
52
+ if (!user) return { status: "error", message: "No autenticado" };
53
+
54
+ const { error } = await supabase.from("items").delete().eq("id", id).eq("user_id", user.id);
55
+ if (error) return { status: "error", message: "Error al eliminar" };
56
+
57
+ revalidatePath("/");
58
+ return { status: "success", message: "Eliminado" };
59
+ }
60
+ ```
61
+
62
+ ## Open Redirect Prevention
63
+
64
+ Validate all redirect targets — ensure `next`/`redirect` params start with `/` and not `//`. Never redirect to user-controlled URLs without validation.
65
+
66
+ ```typescript
67
+ // ✅ Safe redirect validation
68
+ const next = rawNext.startsWith("/") && !rawNext.startsWith("//") ? rawNext : "/";
69
+
70
+ // ❌ Unsafe — allows protocol-relative redirects (//evil.com)
71
+ const next = searchParams.get("next") ?? "/";
72
+ ```
73
+
41
74
  ## Input Validation
42
75
  - Validate ALL external input with Zod at the boundary
43
76
  - Validate in Server Actions AND API routes
@@ -48,6 +81,26 @@ export async function POST(request: Request) {
48
81
  - Apply rate limiting to auth endpoints
49
82
  - Use Vercel's built-in rate limiting or `@upstash/ratelimit`
50
83
 
84
+ ## Content Security Policy
85
+
86
+ `script-src` must not include `'unsafe-eval'`. Avoid `'unsafe-inline'` for scripts — prefer nonce-based CSP. `'unsafe-inline'` is acceptable for `style-src` only.
87
+
88
+ ```typescript
89
+ // ✅ Ideal — nonce-based CSP (no unsafe-inline for scripts)
90
+ `script-src 'self' 'nonce-${nonce}'`,
91
+ "style-src 'self' 'unsafe-inline'",
92
+
93
+ // ✅ Acceptable intermediate — add a TODO to migrate to nonce-based
94
+ "script-src 'self' 'unsafe-inline'", // TODO: replace with nonce-based CSP
95
+ "style-src 'self' 'unsafe-inline'",
96
+
97
+ // ✅ Dev-only — unsafe-eval for React debugging features (silences installHook.js warning)
98
+ `script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ""}`,
99
+
100
+ // ❌ Never — unsafe-eval unconditionally in production
101
+ "script-src 'self' 'unsafe-eval' 'unsafe-inline'",
102
+ ```
103
+
51
104
  ## Cookies
52
105
  - `httpOnly: true` for auth tokens (Supabase handles this)
53
106
  - `secure: true` in production
@@ -21,10 +21,26 @@ export const selectUserSchema = createSelectSchema(users);
21
21
  const userSchema = z.object({ id: z.string(), email: z.string() }); // WRONG
22
22
  ```
23
23
 
24
- ## Runtime Queries: supabase-js (RLS active)
24
+ ## Runtime Reads: Repository Pattern (RLS active)
25
+
26
+ All reads go through the feature's `queries/` module — never call `supabase.from().select()` inline in pages or actions.
27
+
25
28
  ```typescript
26
- // ✅ Runtime queries via supabase-js
27
- const { data, error } = await supabase.from("users").select("*").eq("id", userId);
29
+ // ✅ Read via repository function
30
+ import { getUserById } from "@/features/users/queries/users.queries";
31
+ const { data, error } = await getUserById(supabase, userId);
32
+
33
+ // ✅ Repository module — receives supabase client as parameter (DI)
34
+ // src/features/users/queries/users.queries.ts
35
+ import type { createClient } from "@/shared/lib/supabase/server";
36
+ type Client = Awaited<ReturnType<typeof createClient>>;
37
+
38
+ export async function getUserById(supabase: Client, userId: string) {
39
+ return supabase.from("users").select("*").eq("id", userId).single();
40
+ }
41
+
42
+ // ❌ Never inline a SELECT in a page or action
43
+ const { data } = await supabase.from("users").select("*").eq("id", userId); // WRONG in page/action
28
44
 
29
45
  // ❌ Never use Drizzle for runtime queries
30
46
  const users = await db.select().from(usersTable); // WRONG — bypasses RLS
@@ -70,19 +70,28 @@ it("calls signInWithPassword with correct args", () => { ... });
70
70
  ```
71
71
 
72
72
  ## Playwright (E2E)
73
- - Use `data-testid` attributes for stable selectors
73
+ - **Always use `data-testid` attributes for element selection** — never use visible text or translated labels. Text-based selectors break when copy or i18n changes.
74
74
  - Page Object Pattern for complex flows
75
75
  - Every user flow needs an e2e test
76
76
 
77
+ ```typescript
78
+ // ✅ Stable — decoupled from i18n
79
+ await page.getByTestId("submit-button").click();
80
+
81
+ // ❌ Brittle — breaks if language changes or copy is updated
82
+ await page.getByRole("button", { name: /sign in/i }).click();
83
+ await page.getByLabel(/email/i).fill("...");
84
+ ```
85
+
77
86
  ```typescript
78
87
  test("user can log in", async ({ page }) => {
79
88
  // Arrange
80
89
  await page.goto("/login");
81
90
 
82
91
  // Act
83
- await page.getByLabel(/email/i).fill("user@example.com");
84
- await page.getByLabel(/password/i).fill("password123");
85
- await page.getByRole("button", { name: /sign in/i }).click();
92
+ await page.getByTestId("email-input").fill("user@example.com");
93
+ await page.getByTestId("password-input").fill("password123");
94
+ await page.getByTestId("sign-in-button").click();
86
95
 
87
96
  // Assert
88
97
  await expect(page).toHaveURL("/");
@@ -102,6 +111,14 @@ For every function, ask: what happens with...
102
111
 
103
112
  If any of these apply, write a test for it. No exceptions.
104
113
 
114
+ ## jsdom Known Limitations (avoid rework cycles)
115
+
116
+ jsdom does not implement all browser APIs. Hitting these in tests causes failures that require full component rewrites — avoid them upfront:
117
+
118
+ - **No `HTMLDialogElement` methods** — `dialog.showModal()` and `dialog.close()` throw in jsdom. Use state-controlled visibility (`isOpen` prop / conditional render) instead of the native `<dialog>` API.
119
+ - **No portal-based components in unit tests** — components like `<Toaster>` (sonner), `<Tooltip>`, `<Popover>` render outside the React tree and are unreliable in jsdom. Exclude their wrapper files from coverage; test toast behavior indirectly via the action's `ActionResult`.
120
+ - **Run `pnpm lint` before finishing** — do not leave lint errors to a separate pass. Fix inline as you go.
121
+
105
122
  ## Coverage Thresholds
106
123
  ```typescript
107
124
  // vitest.config.ts
@@ -8,38 +8,59 @@ description: Scaffold a new feature following TDD and project conventions. Use w
8
8
  ## Process
9
9
 
10
10
  ### 1. Requirements First
11
- - Check `.requirements/` for existing spec
12
- - If none exists, ask the user to describe the feature
13
- - Document in `.requirements/<feature-name>.md` using the Given/When/Then template
11
+ - Check `.requirements/` for an existing functional issue
12
+ - If a spec exists, read it and proceed to Step 2
13
+ - **If no spec exists, call @business-intelligence** to run the discovery process and produce a functional issue in `.requirements/<feature-name>.md`
14
+ - Wait for the functional issue to be written and confirmed by the user before proceeding to Step 2
14
15
 
15
16
  ### 2. Create Feature Structure
16
17
  ```
17
18
  src/features/<feature-name>/
18
19
  ├── components/
19
20
  ├── actions/
21
+ ├── queries/
20
22
  ├── hooks/
21
23
  ├── api/
24
+ ├── lib/
22
25
  └── __tests__/
23
26
  ```
24
27
 
25
- ### 3. TDD: RED Phase
28
+ ### 3. Pre-Test Setup (do this before writing any tests)
29
+
30
+ Update `vitest.config.ts` to exclude files that cannot be meaningfully tested in jsdom:
31
+ - Drizzle schema file(s) (`src/shared/db/schema.ts`)
32
+ - Pure type-only files (no runtime logic)
33
+ - Portal/browser-API-dependent UI wrappers (e.g., `src/shared/components/ui/sonner.tsx`)
34
+
35
+ ```typescript
36
+ // vitest.config.ts — add to coverage.exclude before writing tests
37
+ exclude: [
38
+ "src/shared/db/schema.ts",
39
+ "src/shared/components/ui/sonner.tsx",
40
+ ]
41
+ ```
42
+
43
+ Configuring exclusions upfront prevents reactive coverage fixes mid-implementation.
44
+
45
+ ### 4. TDD: RED Phase
26
46
  Write ALL test files first:
27
47
  - `__tests__/<component>.test.tsx` — component tests
28
48
  - `__tests__/use-<feature>.test.ts` — hook tests
29
- - `__tests__/<action>.test.ts` — action tests
49
+ - `__tests__/<action>.test.ts` — action tests (see `references/server-action-test-template.md`)
50
+ - `__tests__/<feature>.queries.test.ts` — query tests
30
51
 
31
52
  Run `pnpm test:unit` — all tests must FAIL (RED).
32
53
 
33
- ### 4. TDD: GREEN Phase
54
+ ### 5. TDD: GREEN Phase
34
55
  Implement minimum code to pass each test:
35
56
  - Components → actions → hooks → API routes
36
57
 
37
58
  Run `pnpm test:unit` — all tests must PASS (GREEN).
38
59
 
39
- ### 5. Refactor
60
+ ### 6. Refactor
40
61
  Clean up while keeping tests green.
41
62
 
42
- ### 6. Verify
63
+ ### 7. Verify
43
64
  ```bash
44
65
  pnpm test:coverage # Must show 100%
45
66
  pnpm lint # Must pass with 0 warnings
@@ -0,0 +1,51 @@
1
+ # Server Action Testing Pattern
2
+
3
+ Test Server Actions by mocking `createClient`, asserting on the returned `ActionResult`, and verifying side effects.
4
+
5
+ ```typescript
6
+ import { vi, it, expect, describe } from "vitest";
7
+ import { myAction } from "@/features/example/actions/my.action";
8
+
9
+ vi.mock("@/shared/lib/supabase/server", () => ({
10
+ createClient: vi.fn(),
11
+ }));
12
+ vi.mock("next/cache", () => ({ revalidatePath: vi.fn() }));
13
+
14
+ describe("myAction", () => {
15
+ it("returns error when user is not authenticated", async () => {
16
+ // Arrange
17
+ const mockSupabase = {
18
+ auth: { getUser: vi.fn().mockResolvedValue({ data: { user: null } }) },
19
+ };
20
+ vi.mocked(createClient).mockResolvedValue(mockSupabase as never);
21
+ const formData = new FormData();
22
+
23
+ // Act
24
+ const result = await myAction(formData);
25
+
26
+ // Assert
27
+ expect(result).toEqual({ status: "error", message: "No autenticado" });
28
+ expect(revalidatePath).not.toHaveBeenCalled();
29
+ });
30
+
31
+ it("returns success and revalidates on valid input", async () => {
32
+ // Arrange
33
+ const mockUser = { id: "user-1" };
34
+ const mockSupabase = {
35
+ auth: { getUser: vi.fn().mockResolvedValue({ data: { user: mockUser } }) },
36
+ from: vi.fn().mockReturnThis(),
37
+ insert: vi.fn().mockResolvedValue({ error: null }),
38
+ };
39
+ vi.mocked(createClient).mockResolvedValue(mockSupabase as never);
40
+ const formData = new FormData();
41
+ formData.set("name", "Test Item");
42
+
43
+ // Act
44
+ const result = await myAction(formData);
45
+
46
+ // Assert
47
+ expect(result).toEqual({ status: "success", message: expect.any(String) });
48
+ expect(revalidatePath).toHaveBeenCalledWith("/");
49
+ });
50
+ });
51
+ ```
@@ -17,28 +17,8 @@ Or for a specific branch:
17
17
  git diff develop...<branch-name>
18
18
  ```
19
19
 
20
- ### 2. Check Each Changed File Against:
21
-
22
- #### Code Quality
23
- - Zero `any` types
24
- - Zero comments
25
- - Functions ≤ 20 lines, files ≤ 200 lines
26
- - No magic numbers/strings
27
-
28
- #### Tests
29
- - Test files exist for every changed source file
30
- - Tests cover new code paths
31
- - No implementation-detail testing
32
-
33
- #### Architecture
34
- - Correct imports (features → shared only)
35
- - Server Actions for mutations
36
- - Edge runtime on AI routes
37
-
38
- #### Security
39
- - Input validation at boundaries
40
- - Auth checks present
41
- - No exposed secrets
20
+ ### 2. Check Each Changed File
21
+ Apply the full checklist from `references/review-checklist.md` — covers code quality, tests, architecture, security, performance, and accessibility.
42
22
 
43
23
  ### 3. Generate Report
44
24
  ```
@@ -0,0 +1,36 @@
1
+ # Review Checklist
2
+
3
+ ## Code Quality
4
+ - Zero `any` types
5
+ - Zero comments (excluding test AAA labels: `// Arrange`, `// Act`, `// Assert`)
6
+ - Functions ≤ 20 lines
7
+ - Files ≤ 200 lines
8
+ - No magic numbers/strings
9
+ - Proper error handling
10
+
11
+ ## Tests
12
+ - Tests written BEFORE implementation (TDD)
13
+ - 100% coverage on new/changed files
14
+ - 100% BRANCH coverage (every if/else/ternary/catch)
15
+ - Behavior tested, not implementation
16
+ - AAA pattern with labeled comments on every test
17
+ - Tests NOT weakened (no removed assertions, no loosened matchers, no .skip)
18
+ - Edge cases covered: null, empty, boundaries, errors, auth expired
19
+
20
+ ## Architecture
21
+ - Correct layer (features → shared only)
22
+ - Server Actions for mutations (not TanStack)
23
+ - Edge runtime on AI routes
24
+
25
+ ## Security
26
+ - Input validation at boundaries
27
+ - Auth checks in protected routes
28
+ - No exposed secrets
29
+
30
+ ## Performance
31
+ - No N+1 query patterns
32
+ - No unnecessary re-renders
33
+
34
+ ## Accessibility
35
+ - Semantic HTML
36
+ - ARIA labels where needed
@@ -8,45 +8,14 @@ disable-model-invocation: true
8
8
 
9
9
  ## Process
10
10
 
11
- ### 1. Dependency Audit
12
- ```bash
13
- pnpm audit
14
- ```
15
- Categorize findings by: Critical / High / Medium / Low
16
-
17
- ### 2. Secret Scan
18
- Search for hardcoded secrets:
19
- ```bash
20
- grep -r "sk_" src/ --include="*.ts"
21
- grep -r "apiKey\s*=" src/ --include="*.ts"
22
- grep -r "password\s*=" src/ --include="*.ts"
23
- ```
24
- Check `.env.example` has no real values.
25
-
26
- ### 3. RLS Verification
27
- For each table in `src/shared/db/schema.ts`:
28
- - Confirm RLS is enabled in Supabase dashboard
29
- - Confirm explicit policies exist
30
-
31
- ### 4. Auth Coverage
32
- - Verify `proxy.ts` protects all non-public routes
33
- - Check every `route.ts` in protected features has auth check
34
- - Verify `app/(protected)/layout.tsx` has server-side auth check
35
-
36
- ### 5. Input Validation
37
- - Every `route.ts` has Zod schema validation
38
- - Every `.action.ts` has Zod schema validation
39
-
40
- ### 6. XSS Check
41
- ```bash
42
- grep -r "dangerouslySetInnerHTML" src/ --include="*.tsx"
43
- ```
44
-
45
- ### 7. Security Headers
46
- Verify in `next.config.ts`:
47
- - `Content-Security-Policy`
48
- - `Strict-Transport-Security`
49
- - `X-Frame-Options`
11
+ Execute all 7 audit steps from `references/audit-steps.md`:
12
+ 1. Dependency audit (`pnpm audit`)
13
+ 2. Secret scan (hardcoded keys)
14
+ 3. RLS verification (all tables)
15
+ 4. Auth coverage (routes + actions)
16
+ 5. Input validation (Zod at boundaries)
17
+ 6. XSS check (`dangerouslySetInnerHTML`)
18
+ 7. Security headers (`next.config.ts`)
50
19
 
51
20
  ## Output Format
52
21
  ```
@@ -0,0 +1,41 @@
1
+ # Security Audit Steps
2
+
3
+ ### 1. Dependency Audit
4
+ ```bash
5
+ pnpm audit
6
+ ```
7
+ Categorize findings by: Critical / High / Medium / Low
8
+
9
+ ### 2. Secret Scan
10
+ Search for hardcoded secrets:
11
+ ```bash
12
+ grep -r "sk_" src/ --include="*.ts"
13
+ grep -r "apiKey\s*=" src/ --include="*.ts"
14
+ grep -r "password\s*=" src/ --include="*.ts"
15
+ ```
16
+ Check `.env.example` has no real values.
17
+
18
+ ### 3. RLS Verification
19
+ For each table in `src/shared/db/schema.ts`:
20
+ - Confirm RLS is enabled in Supabase dashboard
21
+ - Confirm explicit policies exist
22
+
23
+ ### 4. Auth Coverage
24
+ - Verify `proxy.ts` protects all non-public routes
25
+ - Check every `route.ts` in protected features has auth check
26
+ - Verify `app/(protected)/layout.tsx` has server-side auth check
27
+
28
+ ### 5. Input Validation
29
+ - Every `route.ts` has Zod schema validation
30
+ - Every `.action.ts` has Zod schema validation
31
+
32
+ ### 6. XSS Check
33
+ ```bash
34
+ grep -r "dangerouslySetInnerHTML" src/ --include="*.tsx"
35
+ ```
36
+
37
+ ### 7. Security Headers
38
+ Verify in `next.config.ts`:
39
+ - `Content-Security-Policy`
40
+ - `Strict-Transport-Security`
41
+ - `X-Frame-Options`
@@ -1,16 +1,49 @@
1
1
  @AGENTS.md
2
+ @.claude/rules/general.mdc
3
+ @.claude/rules/architecture.mdc
4
+ @.claude/rules/coding-standards.mdc
2
5
 
3
- # Architecture
6
+ # Context-Specific Rules
4
7
 
5
- Feature-based structure: `src/features/* src/shared/*` (never reverse).
8
+ Read these rules when working on related files:
9
+ - Data fetching (components, queries, hooks, actions): `.claude/rules/data-fetching.mdc`
10
+ - Security (actions, API routes, auth, config): `.claude/rules/security.mdc`
11
+ - Components (UI, shadcn/ui, Tailwind): `.claude/rules/components.mdc`
12
+ - Forms: `.claude/rules/forms.mdc`
13
+ - Migrations: `.claude/rules/migrations.mdc`
14
+ - Next.js specifics: `.claude/rules/nextjs.mdc`
15
+ - Supabase (Drizzle, RLS, repository pattern): `.claude/rules/supabase.mdc`
16
+ - Testing: `.claude/rules/testing.mdc`
6
17
 
7
- - **Drizzle** = schema + migrations ONLY. Never use Drizzle for runtime queries.
8
- - **supabase-js** = all runtime queries. RLS is always active.
9
- - **Zod schemas** for DB types: auto-generate via `drizzle-zod`, never write manually.
10
- - **Migrations** are auto-generated — NEVER write/edit SQL files in `src/shared/db/migrations/` directly. Use `pnpm db:generate` + `pnpm db:migrate`.
18
+ # Agent Workflow
11
19
 
12
- # Testing
20
+ This project uses specialist agents in `.claude/agents/`. Follow the technical-lead workflow:
13
21
 
14
- - 100% coverage required `pnpm test:coverage` must pass
15
- - AAA pattern (Arrange / Act / Assert) in every test
16
- - Mock only external boundaries (Supabase, HTTP, DB) never mock internal code
22
+ - **New feature**: requirements (`business-intelligence`) → task breakdown (`technical-lead`) → tests first (`test-qa`) implementation (`backend` + `frontend` **in parallel**) → review (`code-reviewer` + `security-researcher` **in parallel**)
23
+ - **Bug fix**: reproduce with failing test (`test-qa`) fix (`backend`/`frontend`) → review (`code-reviewer`)
24
+ - **Refactor**: plan (`technical-lead`) implement (`backend`/`frontend`) verify (`test-qa`) review (`code-reviewer`)
25
+
26
+ ## Recommended: 2-Conversation Workflow for New Features
27
+
28
+ Split new feature work across two conversations to avoid context exhaustion (hitting >80% context mid-implementation):
29
+
30
+ **Conversation 1 — Requirements** (low context, can be discarded after)
31
+ ```
32
+ @business-intelligence → write .requirements/<feature-name>.md → confirm with user
33
+ ```
34
+
35
+ **Conversation 2 — Implementation** (fresh context, reads spec from file)
36
+ ```
37
+ /create-feature → reads .requirements/<feature-name>.md → full TDD pipeline
38
+ ```
39
+
40
+ The `/create-feature` skill reads the requirements file directly, so the BI conversation history is not needed during implementation. This keeps each conversation well under 50% context usage.
41
+
42
+ # File Naming
43
+
44
+ - Server Actions: `name.action.ts` with `"use server"` at top
45
+ - React hooks: `use-name.ts`
46
+ - Components: PascalCase `.tsx`
47
+ - Tests: colocated in `__tests__/`, named `name.test.ts`
48
+
49
+ # Maintenance Notes
@@ -65,6 +65,12 @@ export default tseslint.config(
65
65
  rules: { "no-console": "off" },
66
66
  },
67
67
  {
68
- ignores: [".next/**", "node_modules/**", "dist/**", "eslint.config.ts", "next-env.d.ts", "next.config.ts", "playwright.config.ts", "drizzle.config.ts", "vitest.config.ts", "postcss.config.mjs"],
68
+ // TODO: @supabase/ssr exports createServerClient as deprecated while the replacement
69
+ // API is not yet stable. Remove this override once the package provides a non-deprecated alternative.
70
+ files: ["src/shared/lib/supabase/server.ts", "src/shared/lib/supabase/middleware.ts"],
71
+ rules: { "@typescript-eslint/no-deprecated": "off" },
72
+ },
73
+ {
74
+ ignores: [".next/**", "node_modules/**", "dist/**", "coverage/**", "eslint.config.ts", "next-env.d.ts", "next.config.ts", "playwright.config.ts", "drizzle.config.ts", "vitest.config.ts", "postcss.config.mjs"],
69
75
  }
70
76
  );
@@ -1,5 +1,7 @@
1
1
  import type { NextConfig } from "next";
2
2
 
3
+ const isDev = process.env.NODE_ENV !== "production";
4
+
3
5
  const nextConfig: NextConfig = {
4
6
  typedRoutes: true,
5
7
  logging: {
@@ -20,7 +22,9 @@ const nextConfig: NextConfig = {
20
22
  key: "Content-Security-Policy",
21
23
  value: [
22
24
  "default-src 'self'",
23
- "script-src 'self' 'unsafe-eval' 'unsafe-inline'",
25
+ // TODO: replace 'unsafe-inline' with nonce-based CSP
26
+ // 'unsafe-eval' is added in dev only for React's debugging features (never in production)
27
+ `script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ""}`,
24
28
  "style-src 'self' 'unsafe-inline'",
25
29
  "img-src 'self' data: blob: https:",
26
30
  "font-src 'self'",
@@ -1,11 +1,9 @@
1
- import { eq } from "drizzle-orm";
2
1
  import { redirect } from "next/navigation";
3
2
 
4
3
  import { logoutAction } from "@/features/auth/actions/logout.action";
5
4
  import { AddTodoForm } from "@/features/todos/components/add-todo-form";
6
5
  import { TodoList } from "@/features/todos/components/todo-list";
7
- import { db } from "@/shared/db";
8
- import { todos } from "@/shared/db/schema";
6
+ import { getTodosByUserId } from "@/features/todos/queries/todos.queries";
9
7
  import { createClient } from "@/shared/lib/supabase/server";
10
8
 
11
9
  export default async function HomePage() {
@@ -14,7 +12,7 @@ export default async function HomePage() {
14
12
  data: { user },
15
13
  } = await supabase.auth.getUser();
16
14
  if (!user) redirect("/login");
17
- const items = await db.select().from(todos).where(eq(todos.userId, user.id));
15
+ const { data: items } = await getTodosByUserId(supabase, user.id);
18
16
 
19
17
  return (
20
18
  <main className="container mx-auto max-w-2xl p-8">