nextjs-hackathon-stack 0.1.35 → 0.1.36

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 (36) hide show
  1. package/dist/index.js +33 -14
  2. package/package.json +1 -1
  3. package/template/.cursor/agents/backend.md +6 -3
  4. package/template/.cursor/agents/business-intelligence.md +5 -3
  5. package/template/.cursor/agents/code-reviewer.md +3 -3
  6. package/template/.cursor/agents/frontend.md +6 -3
  7. package/template/.cursor/agents/security-researcher.md +1 -1
  8. package/template/.cursor/agents/technical-lead.md +8 -4
  9. package/template/.cursor/agents/test-qa.md +17 -11
  10. package/template/.cursor/memory/architecture-snapshot.md +78 -1
  11. package/template/.cursor/rules/architecture.mdc +1 -0
  12. package/template/.cursor/rules/coding-standards.mdc +4 -4
  13. package/template/.cursor/rules/forms.mdc +2 -0
  14. package/template/.cursor/rules/general.mdc +2 -3
  15. package/template/.cursor/rules/supabase.mdc +1 -1
  16. package/template/.cursor/rules/testing.mdc +7 -5
  17. package/template/.cursor/skills/build-feature/SKILL.md +199 -0
  18. package/template/.cursor/skills/create-api-route/SKILL.md +1 -1
  19. package/template/.cursor/skills/discover-feature/SKILL.md +111 -0
  20. package/template/.cursor/skills/memory/SKILL.md +208 -0
  21. package/template/.cursor/skills/review-branch/SKILL.md +1 -1
  22. package/template/.cursor/skills/security-audit/SKILL.md +1 -1
  23. package/template/.cursor/skills/security-audit/references/audit-steps.md +1 -1
  24. package/template/AGENTS.md +29 -1
  25. package/template/CLAUDE.md +19 -7
  26. package/template/README.md +201 -2
  27. package/template/drizzle.config.ts +1 -1
  28. package/template/src/features/todos/components/todo-list.tsx +1 -1
  29. package/template/src/shared/__tests__/schema.test.ts +1 -1
  30. package/template/src/shared/db/index.ts +4 -1
  31. package/template/src/shared/db/profiles.schema.ts +15 -0
  32. package/template/src/shared/db/schema.ts +2 -29
  33. package/template/src/shared/db/todos.schema.ts +16 -0
  34. package/template/vitest.config.ts +4 -4
  35. package/template/.cursor/skills/create-feature/SKILL.md +0 -136
  36. /package/template/.cursor/skills/{create-feature → build-feature}/references/server-action-test-template.md +0 -0
package/dist/index.js CHANGED
@@ -72,6 +72,25 @@ function getTemplateDir() {
72
72
  function processTemplate(content, vars) {
73
73
  return content.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`);
74
74
  }
75
+ function createClaudeDir(targetDir) {
76
+ const cursorDir = join(targetDir, ".cursor");
77
+ const claudeDir = join(targetDir, ".claude");
78
+ if (!existsSync(cursorDir)) return;
79
+ const mirrorDirs = ["agents", "rules", "skills"];
80
+ for (const dir of mirrorDirs) {
81
+ const srcDir = join(cursorDir, dir);
82
+ if (!existsSync(srcDir)) continue;
83
+ const destDir = join(claudeDir, dir);
84
+ mkdirSync(destDir, { recursive: true });
85
+ for (const entry of readdirSync(srcDir)) {
86
+ const relTarget = `../../.cursor/${dir}/${entry}`;
87
+ const destPath = join(destDir, entry);
88
+ if (!existsSync(destPath)) {
89
+ symlinkSync(relTarget, destPath);
90
+ }
91
+ }
92
+ }
93
+ }
75
94
  function copyDir(src, dest, vars, skip = /* @__PURE__ */ new Set(), templateRoot = src) {
76
95
  mkdirSync(dest, { recursive: true });
77
96
  for (const entry of readdirSync(src)) {
@@ -133,21 +152,18 @@ export default async function HomePage() {
133
152
  );
134
153
  }
135
154
  `;
136
- var EMPTY_APP_SCHEMA = `import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
137
- import { createInsertSchema, createSelectSchema } from "drizzle-zod";
155
+ var EMPTY_APP_BARREL = `export * from "./profiles.schema";
156
+ `;
157
+ var EMPTY_APP_DB_INDEX = `import { drizzle } from "drizzle-orm/postgres-js";
158
+ import postgres from "postgres";
138
159
 
139
- export const profiles = pgTable("profiles", {
140
- id: uuid("id").primaryKey(),
141
- email: text("email").notNull().unique(),
142
- createdAt: timestamp("created_at").notNull().defaultNow(),
143
- updatedAt: timestamp("updated_at").notNull().defaultNow(),
144
- });
160
+ import * as profiles from "./profiles.schema";
161
+
162
+ const schema = { ...profiles };
145
163
 
146
- export const insertProfileSchema = createInsertSchema(profiles);
147
- export const selectProfileSchema = createSelectSchema(profiles);
164
+ const client = postgres(process.env.DATABASE_URL ?? "");
148
165
 
149
- export type InsertProfile = typeof profiles.$inferInsert;
150
- export type SelectProfile = typeof profiles.$inferSelect;
166
+ export const db = drizzle(client, { schema });
151
167
  `;
152
168
  var EMPTY_APP_PAGE_TEST = `import { describe, it, expect, vi, beforeEach } from "vitest";
153
169
 
@@ -196,7 +212,8 @@ describe("HomePage (protected)", () => {
196
212
  function buildSkipSet() {
197
213
  return /* @__PURE__ */ new Set([
198
214
  "src/features/todos",
199
- "src/e2e/todos.spec.ts"
215
+ "src/e2e/todos.spec.ts",
216
+ "src/shared/db/todos.schema.ts"
200
217
  ]);
201
218
  }
202
219
  async function scaffold(projectName, skipInstall, skipMcp = false, template = "example") {
@@ -211,11 +228,13 @@ async function scaffold(projectName, skipInstall, skipMcp = false, template = "e
211
228
  const spinner2 = p3.spinner();
212
229
  spinner2.start("Copying template files");
213
230
  copyDir(templateDir, targetDir, vars, skip, templateDir);
231
+ createClaudeDir(targetDir);
214
232
  if (template === "empty") {
215
233
  const pageDir = join(targetDir, "src/app/(protected)");
216
234
  mkdirSync(pageDir, { recursive: true });
217
235
  writeFileSync(join(pageDir, "page.tsx"), EMPTY_APP_PAGE);
218
- writeFileSync(join(targetDir, "src/shared/db/schema.ts"), EMPTY_APP_SCHEMA);
236
+ writeFileSync(join(targetDir, "src/shared/db/schema.ts"), EMPTY_APP_BARREL);
237
+ writeFileSync(join(targetDir, "src/shared/db/index.ts"), EMPTY_APP_DB_INDEX);
219
238
  const testDir = join(targetDir, "src/app/__tests__");
220
239
  mkdirSync(testDir, { recursive: true });
221
240
  writeFileSync(join(testDir, "protected-page.test.tsx"), EMPTY_APP_PAGE_TEST);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextjs-hackathon-stack",
3
- "version": "0.1.35",
3
+ "version": "0.1.36",
4
4
  "description": "Scaffold a full-stack Next.js hackathon starter",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,15 +1,18 @@
1
1
  ---
2
2
  name: backend
3
- description: Backend specialist for Node.js, Next.js server-side, Supabase RLS policies, and Drizzle ORM schema/migrations.
3
+ description: Backend specialist for Node.js, Next.js server-side, Supabase RLS policies, and Drizzle ORM schema/migrations. Triggers: database changes, Server Actions, API routes, auth flows, schema definitions, RLS policies, migrations.
4
4
  model: inherit
5
5
  readonly: false
6
6
  ---
7
7
 
8
8
  # Backend Agent
9
9
 
10
- ## First Step
10
+ ## First Step — Load Context via MCP Memory
11
11
 
12
- Read `.cursor/memory/architecture-snapshot.md` to understand the existing schema, features, and proven Server Action patterns before writing new code. Use the canonical pattern references to copy the correct structure.
12
+ 1. Read `package.json` to get the project name (`<project-name>`)
13
+ 2. Call `search_memory` with `tags: ["project:<project-name>", "domain:database"]` — existing tables and their columns
14
+ 3. Call `search_memory` with `tags: ["project:<project-name>", "domain:patterns"]` and `query: "server action"` — canonical Server Action pattern to copy
15
+ 4. **Fallback**: if the memory service is unavailable or returns no results, read `.cursor/memory/architecture-snapshot.md` directly
13
16
 
14
17
  ## Responsibilities
15
18
  - Build Next.js Server Actions, API routes, and middleware
@@ -1,15 +1,17 @@
1
1
  ---
2
2
  name: business-intelligence
3
- description: Requirements analyst. Use when defining features, writing user stories, validating acceptance criteria, or creating requirements documentation.
3
+ description: Requirements analyst (readonly). Discovers requirements through questions before defining anything. Triggers: new feature definition, writing requirements, user stories, acceptance criteria, requirements documentation, feature scope clarification.
4
4
  model: inherit
5
5
  readonly: true
6
6
  ---
7
7
 
8
8
  # Business Intelligence Agent
9
9
 
10
- ## First Step
10
+ ## First Step — Load Context via MCP Memory
11
11
 
12
- Read `.cursor/memory/architecture-snapshot.md` to understand existing features before asking discovery questions. Use it to identify relationships with existing data and patterns.
12
+ 1. Read `package.json` to get the project name (`<project-name>`)
13
+ 2. Call `search_memory` with `tags: ["project:<project-name>", "domain:features"]` — existing features and their descriptions, to identify relationships with new requirements
14
+ 3. **Fallback**: if the memory service is unavailable or returns no results, read `.cursor/memory/architecture-snapshot.md` directly
13
15
 
14
16
  ## Responsibilities
15
17
  - Discover requirements through user questions before defining anything
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: code-reviewer
3
- description: Code review specialist. Use when reviewing branch changes, PRs, or MRs. Supports GitHub (gh) and GitLab (glab).
3
+ description: Code review specialist (readonly, fast model). Reviews branch diffs and PRs against the project quality checklist. Supports GitHub (gh) and GitLab (glab). Triggers: review my branch, review PR, code review, check my changes.
4
4
  model: fast
5
5
  readonly: true
6
6
  ---
@@ -34,8 +34,8 @@ glab --version 2>/dev/null && echo "GitLab"
34
34
 
35
35
  ### Testing
36
36
  - [ ] Tests written BEFORE implementation (TDD)
37
- - [ ] 100% coverage on new/changed files
38
- - [ ] 100% BRANCH coverage (every if/else/ternary/catch)
37
+ - [ ] ≥95% statement/function/line coverage on new/changed files
38
+ - [ ] ≥90% branch coverage (every if/else/ternary/catch)
39
39
  - [ ] Behavior tested, not implementation
40
40
  - [ ] AAA pattern with labeled comments on every test
41
41
  - [ ] Tests NOT weakened (no removed assertions, no loosened matchers, no .skip)
@@ -1,15 +1,18 @@
1
1
  ---
2
2
  name: frontend
3
- description: UI specialist. Use when building components, pages, layouts, or any styling with Tailwind CSS v4 and shadcn/ui.
3
+ description: UI specialist for React components, pages, layouts, Tailwind CSS v4, shadcn/ui, and accessibility. Triggers: building UI, styling, component creation, page layouts, client-side interactions, form UI, empty states, loading states.
4
4
  model: inherit
5
5
  readonly: false
6
6
  ---
7
7
 
8
8
  # Frontend Agent
9
9
 
10
- ## First Step
10
+ ## First Step — Load Context via MCP Memory
11
11
 
12
- Read `.cursor/memory/architecture-snapshot.md` to see which shadcn/ui components are already installed before running `shadcn add`. Use the canonical component test reference to follow the established testing pattern.
12
+ 1. Read `package.json` to get the project name (`<project-name>`)
13
+ 2. Call `search_memory` with `tags: ["project:<project-name>", "domain:ui", "category:components"]` — installed shadcn/ui components (check before running `shadcn add`)
14
+ 3. Call `search_memory` with `tags: ["project:<project-name>", "domain:patterns"]` and `query: "component test"` — canonical component test reference to follow
15
+ 4. **Fallback**: if the memory service is unavailable or returns no results, read `.cursor/memory/architecture-snapshot.md` directly
13
16
 
14
17
  ## Responsibilities
15
18
  - Build accessible React components with shadcn/ui and Tailwind v4
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: security-researcher
3
- description: Security auditor. Use when implementing auth, handling sensitive data, reviewing API routes, or auditing the codebase for vulnerabilities.
3
+ description: Security auditor (readonly). Checks OWASP vulnerabilities, RLS gaps, exposed secrets, and missing auth checks. Triggers: security audit, check vulnerabilities, review auth, audit dependencies, check for secrets, RLS review.
4
4
  model: inherit
5
5
  readonly: true
6
6
  ---
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: technical-lead
3
- description: Orchestrator and architecture owner. Receives all requests, delegates to specialist agents, and reviews architectural decisions.
3
+ description: Orchestrator and architecture owner. Default entry point for all requests — breaks down tasks, delegates to specialists, and validates architecture. Triggers: new feature planning, architecture questions, task breakdown, multi-agent coordination, refactor planning.
4
4
  model: inherit
5
5
  # Note: `model: inherit` means this agent uses whatever model the user selected.
6
6
  # For security-critical reviews, consider switching to a more capable model explicitly.
@@ -9,9 +9,13 @@ readonly: false
9
9
 
10
10
  # Technical Lead Agent
11
11
 
12
- ## First Step
12
+ ## First Step — Load Context via MCP Memory
13
13
 
14
- Read `.cursor/memory/architecture-snapshot.md` to understand the current project state before planning. Use it to identify which patterns to reuse and what already exists.
14
+ 1. Read `package.json` to get the project name (`<project-name>`)
15
+ 2. Call `search_memory` with `tags: ["project:<project-name>", "domain:features"]` — existing features and their paths
16
+ 3. Call `search_memory` with `tags: ["project:<project-name>", "domain:patterns"]` — canonical patterns to reuse
17
+ 4. Call `search_memory` with `tags: ["project:<project-name>", "domain:database"]` — current DB schema
18
+ 5. **Fallback**: if the memory service is unavailable or returns no results, read `.cursor/memory/architecture-snapshot.md` directly
15
19
 
16
20
  ## Responsibilities
17
21
  - Act as the default entry point for all requests
@@ -47,7 +51,7 @@ Delegate to the appropriate specialist based on domain:
47
51
  ## Workflow Sequences
48
52
 
49
53
  ### New feature
50
- @business-intelligence (functional issue) → @technical-lead (task breakdown + test plan) @test-qa (RED) **@backend + @frontend in parallel** (GREEN) **@code-reviewer + @security-researcher in parallel**
54
+ `/discover-feature` (Conversation 1) → `/build-feature` (Conversation 2, fresh) planning, TDD, review, and memory sync are all automated inside `/build-feature`
51
55
 
52
56
  ### Bug fix
53
57
  @test-qa (reproduce with failing test) → @backend/@frontend (fix) → @code-reviewer
@@ -1,15 +1,19 @@
1
1
  ---
2
2
  name: test-qa
3
- description: Testing specialist. Use proactively when code changes to write tests and ensure 100% coverage. Always write tests BEFORE implementation.
3
+ description: Testing specialist. Use when writing tests, enforcing TDD workflow, fixing coverage gaps, or debugging test failures. Always write tests BEFORE implementation. Triggers: writing tests, TDD workflow, coverage gaps, test failures, mock setup.
4
4
  model: inherit
5
5
  readonly: false
6
6
  ---
7
7
 
8
8
  # Test & QA Agent
9
9
 
10
- ## First Step
10
+ ## First Step — Load Context via MCP Memory
11
11
 
12
- Read `.cursor/memory/architecture-snapshot.md` to find the canonical test references. Import `makeChain` and `makeSupabaseMock` from `@/shared/test-utils/supabase-mock` — never recreate the mock chain from scratch.
12
+ 1. Read `package.json` to get the project name (`<project-name>`)
13
+ 2. Call `search_memory` with `tags: ["project:<project-name>", "domain:patterns"]` and `query: "test mock supabase"` — canonical test references and mock setup (`makeChain`, `makeSupabaseMock`)
14
+ 3. **Fallback**: if the memory service is unavailable or returns no results, read `.cursor/memory/architecture-snapshot.md` directly
15
+
16
+ Import `makeChain` and `makeSupabaseMock` from `@/shared/test-utils/supabase-mock` — never recreate the mock chain from scratch.
13
17
 
14
18
  ## TDD Workflow
15
19
 
@@ -26,7 +30,7 @@ Each phase requires pasting the actual terminal output before proceeding:
26
30
  |------|---------|-----------------|
27
31
  | After RED | `pnpm test:unit` | All new tests **FAIL** (red) |
28
32
  | After GREEN | `pnpm test:unit` | All tests **PASS** (green) |
29
- | After VERIFY | `pnpm test:coverage` | 100% across all metrics |
33
+ | After VERIFY | `pnpm test:coverage` | ≥95% statements/functions/lines, ≥90% branches |
30
34
 
31
35
  **Never say "tests pass" or "tests fail" without showing the actual output.**
32
36
  **Do NOT write any implementation code until the RED gate output confirms failures.**
@@ -37,16 +41,18 @@ Each phase requires pasting the actual terminal output before proceeding:
37
41
 
38
42
  ## Coverage Requirement
39
43
  ```
40
- branches: 100%
41
- functions: 100%
42
- lines: 100%
43
- statements: 100%
44
+ branches: 90%
45
+ functions: 95%
46
+ lines: 95%
47
+ statements: 95%
44
48
  ```
45
49
 
50
+ Use `/* v8 ignore next */` for genuinely unreachable defensive branches only.
51
+
46
52
  After every change:
47
53
  1. Run `pnpm test:coverage`
48
- 2. If below 100%, add missing tests
49
- 3. Never mark work complete without full coverage
54
+ 2. If below thresholds, add missing tests
55
+ 3. Never mark work complete without meeting thresholds
50
56
 
51
57
  ## Supabase Mock Pattern
52
58
 
@@ -73,7 +79,7 @@ When the Technical Lead provides a test plan, follow it:
73
79
  - Flag missing edge cases to the TL **before** adding unplanned tests — do not expand scope unilaterally
74
80
  - If the plan is ambiguous or incomplete, ask for clarification rather than guessing
75
81
 
76
- For the full Server Action test template, see `create-feature` skill references.
82
+ For the full Server Action test template, see `build-feature` skill references (`references/server-action-test-template.md`).
77
83
 
78
84
  ## Guardrails
79
85
  - Never write implementation without a failing test first
@@ -47,4 +47,81 @@ Read these files to understand the project's proven patterns before writing new
47
47
  - Toast feedback required for every mutation (success + error) via `sonner`
48
48
  - RLS must be enabled on every table before a feature is considered complete
49
49
  - UI text: Spanish | Code-level text: English (test descriptions, variable names)
50
- - 100% test coverageno exceptions
50
+ - Coverage threshold: 95% statements/functions/lines, 90% branches use `/* v8 ignore next */` for genuinely unreachable defensive branches
51
+
52
+ ## Shared Utilities
53
+
54
+ Reuse these before writing new helpers:
55
+
56
+ | Utility | Location | Purpose |
57
+ |---------|----------|---------|
58
+ | `formFieldText(formData, key)` | `src/shared/lib/form-utils.ts` | Safe `FormData` text extraction — avoids `no-base-to-string` lint error |
59
+ | `firstZodIssueMessage(error)` | `src/shared/lib/zod-utils.ts` | Extract first Zod validation error message |
60
+ | `readUploadFileBuffer(file)` | `src/shared/lib/upload-utils.ts` | Read a `File`/`Blob` upload to `ArrayBuffer` safely |
61
+
62
+ ## Strict Rules Reference
63
+
64
+ Read this before writing any code. These are the rules that most often cause post-hoc fix cycles.
65
+
66
+ ### TypeScript Compiler (`tsconfig.json`)
67
+
68
+ `"strict": true` plus additional flags:
69
+
70
+ | Flag | What it enforces | How to comply |
71
+ |------|-----------------|---------------|
72
+ | `noUncheckedIndexedAccess` | `arr[i]` / `obj[key]` return `T \| undefined` | Guard: `const val = arr[0]; if (val) { ... }` |
73
+ | `exactOptionalPropertyTypes` | Cannot assign `undefined` to optional props | Omit the key entirely; never write `{ prop: undefined }` |
74
+ | `noImplicitOverride` | Subclass overrides need `override` keyword | `override render() {}` |
75
+ | `noFallthroughCasesInSwitch` | Every `case` needs `break`/`return`/`throw` | Add `break` to every case |
76
+ | `noImplicitReturns` | Every code path must return | Add explicit `return` in all branches |
77
+ | `noUnusedLocals` / `noUnusedParameters` | No unused vars/params | Prefix unused params with `_` |
78
+ | `useUnknownInCatchVariables` | `catch (e)` → `e` is `unknown` | Narrow: `if (e instanceof Error)` before `.message` |
79
+
80
+ ### ESLint — Rules That Most Often Trip AI Agents
81
+
82
+ Preset: `tseslint.configs.strictTypeChecked` + `stylisticTypeChecked`. Runs with `--max-warnings 0` (warnings = errors).
83
+
84
+ **Unsafe-any family (all "error"):**
85
+ - `no-unsafe-assignment`, `no-unsafe-member-access`, `no-unsafe-call`, `no-unsafe-return`, `no-unsafe-argument`
86
+ - Never let `any` flow through. Cast `JSON.parse()` results. Type all mock returns explicitly.
87
+
88
+ **Promise handling:**
89
+ - `no-floating-promises`: Every Promise must be `await`ed, returned, or `void`-prefixed
90
+ - `no-misused-promises`: Async functions in React event handlers need a `void` wrapper: `onClick={() => void handleSubmit()}`
91
+ - `require-await`: `async` functions must contain `await`; remove `async` if not needed
92
+ - `return-await`: Must use `return await` inside `try/catch`
93
+
94
+ **Type safety:**
95
+ - `no-explicit-any`: Use `unknown` and narrow; never use `any`
96
+ - `no-non-null-assertion`: Cannot use `!` postfix; narrow with conditionals
97
+ - `no-base-to-string`: Objects without `toString()` cannot appear in template literals or string concatenation — use `formFieldText()` for `FormData` values
98
+ - `restrict-template-expressions`: Template literals only accept `string | number | boolean`
99
+ - `no-unnecessary-condition`: Don't check conditions that are always truthy/falsy per types
100
+ - `unbound-method`: Don't detach methods from objects; bind or use arrow functions
101
+ - `only-throw-error`: Only throw `Error` instances, never strings or plain objects
102
+ - `use-unknown-in-catch-callback-variable`: `.catch(err => ...)` — `err` must be `unknown`
103
+
104
+ **Imports & style:**
105
+ - `consistent-type-imports`: Use `import type { X }` for type-only imports
106
+ - `import-x/order`: Imports grouped (builtin → external → internal → parent → sibling → index), alphabetized, blank lines between groups
107
+ - `no-inferrable-types`: Don't annotate types TS can infer (`const count: number = 0` → `const count = 0`)
108
+ - `prefer-optional-chain`: Use `?.` instead of `&&` chains
109
+ - `prefer-nullish-coalescing`: Use `??` instead of `||` for nullable defaults
110
+ - `consistent-type-definitions`: Prefer `interface` over `type` for object shapes
111
+ - `array-type`: Use `T[]` not `Array<T>`
112
+
113
+ **React-specific:**
114
+ - `react-hooks/rules-of-hooks`: Hooks only at top level of components/custom hooks
115
+ - `react-hooks/exhaustive-deps`: All reactive values must be in hook dependency arrays
116
+
117
+ **Console:**
118
+ - `no-console`: warn — but `--max-warnings 0` makes it an error. Only allowed in `**/error.tsx`.
119
+
120
+ **Test files (`.test.ts`, `.test.tsx`):**
121
+ - `vitest/expect-expect`: Every test must have an assertion
122
+ - `vitest/no-identical-title`: Test titles must be unique
123
+ - `vitest/no-conditional-expect`: No `expect()` inside conditionals
124
+
125
+ ### File-specific exceptions
126
+ - `**/error.tsx`: `no-console` disabled (Next.js error boundaries)
127
+ - `src/shared/lib/supabase/server.ts`, `middleware.ts`: `no-deprecated` disabled (Supabase SSR)
@@ -45,3 +45,4 @@ If two features share logic, move it to `shared/`.
45
45
  - Return typed result objects, use `redirect()` for navigation
46
46
  - Actions that mutate data must return `ActionResult` from `@/shared/lib/action-result` — never return `void`
47
47
  - The calling component is responsible for showing `toast.success()` / `toast.error()` based on the result
48
+ - NEVER import or call client-only APIs inside server actions (`toast` from sonner, React hooks, `window`, `document`). Server actions run on the server where these APIs do not exist and will throw at runtime.
@@ -41,7 +41,7 @@ Follow the RED → GREEN → REFACTOR cycle. See `testing.mdc` for the full rule
41
41
  - Always handle Promise rejections
42
42
 
43
43
  ## Types
44
- - Prefer `type` over `interface` for data shapes
44
+ - Prefer `interface` for object shapes (enforced by ESLint `consistent-type-definitions`). Use `type` for unions, intersections, and utility types.
45
45
  - Use `satisfies` for type-checked literals
46
46
  - Use `as const` for immutable literals
47
47
  - **Never use `as` type assertions for data from external sources** (DB, API, user input). Use Zod `.parse()` or `.safeParse()` to validate and type runtime data. `as const` is acceptable for literals.
@@ -76,7 +76,7 @@ it("retorna error si el candidato no existe", async () => { ... });
76
76
  ## Coverage Exclusions (configure upfront, not reactively)
77
77
 
78
78
  Before writing tests, add exclusions to `vitest.config.ts` for:
79
- - Drizzle schema definition files (`src/shared/db/schema.ts`)
79
+ - Drizzle schema definition files (`src/shared/db/*.schema.ts`)
80
80
  - Pure type files (files with only `type`/`interface` exports, no runtime logic)
81
81
  - Third-party UI wrapper components that rely on portals or browser APIs not supported in jsdom (e.g., `sonner.tsx`, toast providers)
82
82
 
@@ -86,7 +86,7 @@ Configure these exclusions **before writing tests** to avoid reactive coverage f
86
86
  // vitest.config.ts
87
87
  coverage: {
88
88
  exclude: [
89
- "src/shared/db/schema.ts",
89
+ "src/shared/db/*.schema.ts",
90
90
  "src/shared/components/ui/sonner.tsx",
91
91
  // add other portal/type-only files here
92
92
  ],
@@ -96,6 +96,6 @@ coverage: {
96
96
  ## Pre-commit Quality Gates
97
97
  Every commit must pass two sequential gates:
98
98
  1. **lint-staged** — runs `eslint --max-warnings 0` on staged `.ts/.tsx` files. Any lint warning or error blocks the commit immediately (before the slower test suite runs).
99
- 2. **Full test suite** — runs `vitest run --coverage` with 100% branch/function/line/statement coverage required.
99
+ 2. **Full test suite** — runs `vitest run --coverage` with 95% statement/function/line and 90% branch coverage required.
100
100
 
101
101
  Code that fails either gate cannot be committed.
@@ -64,6 +64,8 @@ Share the Zod schema between client and server from a single source (e.g., `lib/
64
64
 
65
65
  ## Toast Feedback
66
66
 
67
+ > **⚠️ Toast calls belong in client components ONLY.** Never import `sonner` or call `toast()` inside a `"use server"` file. The server action returns `ActionResult`; the component reads the result and shows the toast.
68
+
67
69
  Every action that affects the database MUST show a toast to the user. No mutation should complete silently.
68
70
 
69
71
  - Use `toast.success(message)` for successful operations
@@ -1,7 +1,6 @@
1
1
  ---
2
- description: Stack overview and project conventions. Applies to all source files.
3
- alwaysApply: false
4
- globs: ["src/**"]
2
+ description: Stack overview and project conventions. Always loaded defines the technology stack, project structure, and naming conventions for this project.
3
+ alwaysApply: true
5
4
  ---
6
5
 
7
6
  # Stack Overview
@@ -61,7 +61,7 @@ Files in `src/shared/db/migrations/` are **auto-generated** by Drizzle CLI — n
61
61
  | `pnpm db:migrate` | Apply pending migrations to the database |
62
62
  | `pnpm db:push` | Push schema directly to DB (dev only, no migration file) |
63
63
 
64
- Workflow: edit `src/shared/db/schema.ts` → `pnpm db:generate` → review SQL → `pnpm db:migrate`
64
+ Workflow: edit `src/shared/db/<table>.schema.ts` → `pnpm db:generate` → review SQL → `pnpm db:migrate`
65
65
 
66
66
  ## Environment Variables
67
67
  - `NEXT_PUBLIC_SUPABASE_URL` + `NEXT_PUBLIC_SUPABASE_ANON_KEY` — client-safe, used in browser + server
@@ -8,7 +8,7 @@ globs: ["**/*.test.*", "**/e2e/**"]
8
8
  ## Iron Rules (Non-Negotiable)
9
9
  1. **NEVER modify a test to make it pass** — always fix the implementation code. Tests define the contract. If a test fails, the code is wrong, not the test.
10
10
  2. **Tests are immutable during GREEN phase** — only modify tests in RED phase (writing new ones) or REFACTOR phase (improving structure, never weakening assertions)
11
- 3. **100% branch coverage** — every `if`, `else`, `switch`, `??`, `?.`, ternary, and `catch` must be exercised
11
+ 3. **Coverage thresholds** — 95% statements/functions/lines, 90% branches. Use `/* v8 ignore next */` only for genuinely unreachable defensive branches
12
12
  4. **Every edge case gets a test** — null/undefined, empty string, empty array, boundary values, error responses, timeout, concurrent calls
13
13
  5. **AAA is mandatory** — every test uses Arrange-Act-Assert with labeled comments
14
14
 
@@ -124,10 +124,12 @@ jsdom does not implement all browser APIs. Hitting these in tests causes failure
124
124
  // vitest.config.ts
125
125
  coverage: {
126
126
  thresholds: {
127
- branches: 100,
128
- functions: 100,
129
- lines: 100,
130
- statements: 100,
127
+ branches: 90,
128
+ functions: 95,
129
+ lines: 95,
130
+ statements: 95,
131
131
  },
132
132
  }
133
133
  ```
134
+
135
+ Use `/* v8 ignore next */` for genuinely unreachable defensive branches (e.g., exhaustive `switch` default arms, type narrowing guards that cannot be hit at runtime).