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.
- package/dist/index.js +6 -3
- package/package.json +1 -1
- package/template/.cursor/agents/backend.md +10 -59
- package/template/.cursor/agents/business-intelligence.md +43 -15
- package/template/.cursor/agents/code-reviewer.md +2 -2
- package/template/.cursor/agents/frontend.md +6 -10
- package/template/.cursor/agents/security-researcher.md +8 -1
- package/template/.cursor/agents/technical-lead.md +47 -18
- package/template/.cursor/agents/test-qa.md +9 -49
- package/template/.cursor/rules/architecture.mdc +5 -2
- package/template/.cursor/rules/coding-standards.mdc +53 -3
- package/template/.cursor/rules/components.mdc +7 -0
- package/template/.cursor/rules/data-fetching.mdc +56 -5
- package/template/.cursor/rules/forms.mdc +4 -0
- package/template/.cursor/rules/general.mdc +8 -5
- package/template/.cursor/rules/nextjs.mdc +27 -3
- package/template/.cursor/rules/security.mdc +56 -3
- package/template/.cursor/rules/supabase.mdc +19 -3
- package/template/.cursor/rules/testing.mdc +21 -4
- package/template/.cursor/skills/create-feature/SKILL.md +29 -8
- package/template/.cursor/skills/create-feature/references/server-action-test-template.md +51 -0
- package/template/.cursor/skills/review-branch/SKILL.md +2 -22
- package/template/.cursor/skills/review-branch/references/review-checklist.md +36 -0
- package/template/.cursor/skills/security-audit/SKILL.md +8 -39
- package/template/.cursor/skills/security-audit/references/audit-steps.md +41 -0
- package/template/CLAUDE.md +43 -10
- package/template/eslint.config.ts +7 -1
- package/template/next.config.ts +5 -1
- package/template/src/app/(protected)/page.tsx +2 -4
- package/template/src/app/__tests__/auth-callback.test.ts +14 -0
- package/template/src/app/__tests__/protected-page.test.tsx +11 -13
- package/template/src/app/api/auth/callback/route.ts +2 -1
- package/template/src/e2e/login.spec.ts +4 -4
- package/template/src/features/todos/__tests__/todos.action.test.ts +78 -44
- package/template/src/features/todos/__tests__/todos.queries.test.ts +101 -0
- package/template/src/features/todos/actions/todos.action.ts +41 -27
- package/template/src/features/todos/queries/todos.queries.ts +16 -0
- package/template/src/shared/lib/supabase/middleware.ts +0 -1
- package/template/src/shared/lib/supabase/server.ts +0 -1
- package/template/tailwind.css +14 -14
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Stack overview and project conventions.
|
|
3
|
-
alwaysApply:
|
|
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: `
|
|
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
|
|
3
|
-
|
|
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
|
|
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
|
-
// ✅
|
|
27
|
-
|
|
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
|
-
-
|
|
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.
|
|
84
|
-
await page.
|
|
85
|
-
await page.
|
|
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
|
|
12
|
-
- If
|
|
13
|
-
-
|
|
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.
|
|
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
|
-
###
|
|
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
|
-
###
|
|
60
|
+
### 6. Refactor
|
|
40
61
|
Clean up while keeping tests green.
|
|
41
62
|
|
|
42
|
-
###
|
|
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
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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`
|
package/template/CLAUDE.md
CHANGED
|
@@ -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
|
-
#
|
|
6
|
+
# Context-Specific Rules
|
|
4
7
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
+
This project uses specialist agents in `.claude/agents/`. Follow the technical-lead workflow:
|
|
13
21
|
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
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
|
-
|
|
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
|
);
|
package/template/next.config.ts
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
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
|
|
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">
|