nextjs-hackathon-stack 0.1.35 → 0.1.37
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 +33 -14
- package/package.json +1 -1
- package/template/.cursor/agents/backend.md +6 -3
- package/template/.cursor/agents/business-intelligence.md +5 -3
- package/template/.cursor/agents/code-reviewer.md +3 -3
- package/template/.cursor/agents/frontend.md +6 -3
- package/template/.cursor/agents/security-researcher.md +1 -1
- package/template/.cursor/agents/technical-lead.md +8 -4
- package/template/.cursor/agents/test-qa.md +17 -11
- package/template/.cursor/memory/architecture-snapshot.md +78 -1
- package/template/.cursor/rules/architecture.mdc +1 -0
- package/template/.cursor/rules/coding-standards.mdc +4 -4
- package/template/.cursor/rules/forms.mdc +2 -0
- package/template/.cursor/rules/general.mdc +2 -3
- package/template/.cursor/rules/supabase.mdc +1 -1
- package/template/.cursor/rules/testing.mdc +7 -5
- package/template/.cursor/skills/build-feature/SKILL.md +199 -0
- package/template/.cursor/skills/create-api-route/SKILL.md +1 -1
- package/template/.cursor/skills/discover-feature/SKILL.md +111 -0
- package/template/.cursor/skills/memory/SKILL.md +208 -0
- package/template/.cursor/skills/review-branch/SKILL.md +1 -1
- package/template/.cursor/skills/security-audit/SKILL.md +1 -1
- package/template/.cursor/skills/security-audit/references/audit-steps.md +1 -1
- package/template/AGENTS.md +29 -1
- package/template/CLAUDE.md +31 -9
- package/template/README.md +216 -2
- package/template/drizzle.config.ts +1 -1
- package/template/src/features/todos/components/todo-list.tsx +1 -1
- package/template/src/shared/__tests__/schema.test.ts +1 -1
- package/template/src/shared/db/index.ts +4 -1
- package/template/src/shared/db/profiles.schema.ts +15 -0
- package/template/src/shared/db/schema.ts +2 -29
- package/template/src/shared/db/todos.schema.ts +16 -0
- package/template/vitest.config.ts +4 -4
- package/template/.cursor/skills/create-feature/SKILL.md +0 -136
- /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
|
|
137
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
147
|
-
export const selectProfileSchema = createSelectSchema(profiles);
|
|
164
|
+
const client = postgres(process.env.DATABASE_URL ?? "");
|
|
148
165
|
|
|
149
|
-
export
|
|
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"),
|
|
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,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
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
- [ ]
|
|
38
|
-
- [ ]
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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` |
|
|
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:
|
|
41
|
-
functions:
|
|
42
|
-
lines:
|
|
43
|
-
statements:
|
|
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
|
|
49
|
-
3. Never mark work complete without
|
|
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 `
|
|
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
|
-
-
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
3
|
-
alwaysApply:
|
|
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
|
|
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. **
|
|
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:
|
|
128
|
-
functions:
|
|
129
|
-
lines:
|
|
130
|
-
statements:
|
|
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).
|