@claude-code-mastery/starter-kit 1.0.0

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 (70) hide show
  1. package/.claude/.starter-kit/profiles/clean.md +113 -0
  2. package/.claude/.starter-kit/profiles/go.md +458 -0
  3. package/.claude/.starter-kit/profiles/node.md +429 -0
  4. package/.claude/.starter-kit/profiles/python.md +475 -0
  5. package/.claude/.starter-kit/shared/analytics-rybbit.md +55 -0
  6. package/.claude/.starter-kit/shared/claude-md-base.md +93 -0
  7. package/.claude/.starter-kit/shared/deployment-dokploy.md +158 -0
  8. package/.claude/.starter-kit/shared/feature-manifest.md +43 -0
  9. package/.claude/.starter-kit/shared/mcp-and-pooler.md +38 -0
  10. package/.claude/.starter-kit/shared/mongo-setup.md +20 -0
  11. package/.claude/.starter-kit/shared/profile-config.md +65 -0
  12. package/.claude/.starter-kit/shared/seo.md +113 -0
  13. package/.claude/.starter-kit/shared/sql-setup.md +37 -0
  14. package/.claude/commands/add-feature.md +349 -0
  15. package/.claude/commands/add-project-setup.md +156 -0
  16. package/.claude/commands/architecture.md +27 -0
  17. package/.claude/commands/commit.md +61 -0
  18. package/.claude/commands/convert-project-to-starter-kit.md +508 -0
  19. package/.claude/commands/create-api.md +385 -0
  20. package/.claude/commands/create-e2e.md +230 -0
  21. package/.claude/commands/diagram.md +301 -0
  22. package/.claude/commands/help.md +120 -0
  23. package/.claude/commands/install-global.md +145 -0
  24. package/.claude/commands/new-project.md +244 -0
  25. package/.claude/commands/optimize-docker.md +352 -0
  26. package/.claude/commands/progress.md +61 -0
  27. package/.claude/commands/projects-created.md +79 -0
  28. package/.claude/commands/quickstart.md +105 -0
  29. package/.claude/commands/refactor.md +267 -0
  30. package/.claude/commands/remove-project.md +95 -0
  31. package/.claude/commands/review.md +59 -0
  32. package/.claude/commands/security-check.md +77 -0
  33. package/.claude/commands/set-project-profile-default.md +79 -0
  34. package/.claude/commands/setup.md +337 -0
  35. package/.claude/commands/show-user-guide.md +58 -0
  36. package/.claude/commands/starter-kit.md +90 -0
  37. package/.claude/commands/test-plan.md +118 -0
  38. package/.claude/commands/update-project.md +413 -0
  39. package/.claude/commands/what-is-my-ai-doing.md +42 -0
  40. package/.claude/commands/worktree.md +124 -0
  41. package/.claude/hooks/block-dangerous-bash.py +55 -0
  42. package/.claude/hooks/check-branch.sh +116 -0
  43. package/.claude/hooks/check-e2e.sh +71 -0
  44. package/.claude/hooks/check-env-sync.sh +41 -0
  45. package/.claude/hooks/check-file-length.py +47 -0
  46. package/.claude/hooks/check-ports.sh +59 -0
  47. package/.claude/hooks/check-rulecatch.sh +33 -0
  48. package/.claude/hooks/check-rybbit.sh +63 -0
  49. package/.claude/hooks/lint-on-save.sh +59 -0
  50. package/.claude/hooks/verify-no-secrets.sh +80 -0
  51. package/.claude/settings.json +34 -0
  52. package/.claude/skills/api-conventions/SKILL.md +34 -0
  53. package/.claude/skills/code-review/SKILL.md +87 -0
  54. package/.claude/skills/code-review/references/mongodb-checks.md +25 -0
  55. package/.claude/skills/code-review/references/project-checks.md +38 -0
  56. package/.claude/skills/create-service/SKILL.md +222 -0
  57. package/.claude/skills/debugger/SKILL.md +39 -0
  58. package/.claude/skills/dependency-vetting/SKILL.md +46 -0
  59. package/.claude/skills/design-review/SKILL.md +50 -0
  60. package/.claude/skills/mcp-builder/SKILL.md +57 -0
  61. package/.claude/skills/mongodb-rules/SKILL.md +62 -0
  62. package/.claude/skills/terminal-tui/SKILL.md +106 -0
  63. package/.claude/skills/test-writer/SKILL.md +78 -0
  64. package/LICENSE +21 -0
  65. package/README.md +2152 -0
  66. package/bin/cli.js +205 -0
  67. package/claude-mastery-project.conf +220 -0
  68. package/global-claude-md/CLAUDE.md +212 -0
  69. package/global-claude-md/settings.json +3 -0
  70. package/package.json +81 -0
@@ -0,0 +1,385 @@
1
+ ---
2
+ description: Scaffold a new API endpoint — route, handler, types, tests — wired into the server
3
+ scope: project
4
+ argument-hint: <resource-name> [--no-db]
5
+ allowed-tools: Read, Write, Edit, Grep, Glob, Bash, AskUserQuestion
6
+ ---
7
+
8
+ # Create API Endpoint
9
+
10
+ Scaffold a production-ready API endpoint for: **$ARGUMENTS**
11
+
12
+ ## Step 0 — Auto-Branch (if on main)
13
+
14
+ Before creating any files, check the current branch:
15
+
16
+ ```bash
17
+ git branch --show-current
18
+ ```
19
+
20
+ **Default behavior** (`auto_branch = true` in `claude-mastery-project.conf`):
21
+ - If on `main` or `master`: automatically create a feature branch and switch to it:
22
+ ```bash
23
+ git checkout -b feat/api-<resource-name>
24
+ ```
25
+ Report: "Created branch `feat/api-<resource>` — main stays untouched."
26
+ - If already on a feature branch: proceed
27
+ - If not a git repo: skip this check
28
+
29
+ **To disable:** Set `auto_branch = false` in `claude-mastery-project.conf`. When disabled, warn and ask the user before proceeding on main.
30
+
31
+ ## Step 1 — Gather Context
32
+
33
+ Before scaffolding, read the current project:
34
+
35
+ 1. **Read `src/server.ts`** (or `src/server.js`, `src/index.ts`) — understand the server setup
36
+ 2. **Scan `src/routes/`** — check for existing route patterns to follow
37
+ 3. **Scan `src/handlers/`** — check for existing handler patterns
38
+ 4. **Scan `src/types/`** — check for existing type patterns
39
+ 5. **Read `.env.example`** — check for database config
40
+
41
+ If `--no-db` is in the arguments, skip database integration. Otherwise, use StrictDB if it's installed, or the native MongoDB driver if not (always through the adapter, never Mongoose).
42
+
43
+ ## Step 2 — Create Files
44
+
45
+ Generate these files for the resource:
46
+
47
+ ### File 1: `src/types/<resource>.ts` — Types first (they're the spec)
48
+
49
+ ```typescript
50
+ /** Database document shape */
51
+ export interface <Resource>Doc {
52
+ _id: string;
53
+ // Add fields based on the resource
54
+ createdAt: Date;
55
+ updatedAt: Date;
56
+ }
57
+
58
+ /** API request body for creating a resource */
59
+ export interface Create<Resource>Body {
60
+ // Fields the client sends (NO _id, NO timestamps)
61
+ }
62
+
63
+ /** API request body for updating a resource */
64
+ export interface Update<Resource>Body {
65
+ // Partial fields the client can update
66
+ }
67
+
68
+ /** API response shape (what the client receives) */
69
+ export interface <Resource>Response {
70
+ id: string;
71
+ // Mapped from the doc — NEVER expose _id directly
72
+ createdAt: string;
73
+ updatedAt: string;
74
+ }
75
+ ```
76
+
77
+ ### File 2: `src/handlers/<resource>.ts` — Business logic
78
+
79
+ ```typescript
80
+ import type { StrictDB } from 'strictdb';
81
+ import type { <Resource>Doc, Create<Resource>Body, Update<Resource>Body, <Resource>Response } from '../types/<resource>.js';
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Schema + indexes — registered at startup via db.ensureIndexes()
85
+ // ---------------------------------------------------------------------------
86
+
87
+ // Called once at app startup with the shared StrictDB instance
88
+ export function register<Resource>Schema(db: StrictDB) {
89
+ db.registerCollection({
90
+ name: '<resources>',
91
+ indexes: [{ collection: '<resources>', fields: { createdAt: -1 } }],
92
+ });
93
+ }
94
+ // Add more indexes based on query patterns
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Helpers
98
+ // ---------------------------------------------------------------------------
99
+
100
+ const COLLECTION = '<resources>';
101
+
102
+ /** Map a database document to API response (never expose internals) */
103
+ function toResponse(doc: <Resource>Doc): <Resource>Response {
104
+ return {
105
+ id: String(doc._id),
106
+ // Map other fields
107
+ createdAt: doc.createdAt.toISOString(),
108
+ updatedAt: doc.updatedAt.toISOString(),
109
+ };
110
+ }
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // CRUD operations — all receive the shared StrictDB instance
114
+ // ---------------------------------------------------------------------------
115
+
116
+ export async function create<Resource>(db: StrictDB, body: Create<Resource>Body): Promise<<Resource>Response> {
117
+ const now = new Date();
118
+ const doc: Omit<<Resource>Doc, '_id'> = {
119
+ ...body,
120
+ createdAt: now,
121
+ updatedAt: now,
122
+ };
123
+ await db.insertOne(COLLECTION, doc);
124
+ const created = await db.queryOne<<Resource>Doc>(COLLECTION, { createdAt: now });
125
+ if (!created) throw new Error('Failed to create resource');
126
+ return toResponse(created);
127
+ }
128
+
129
+ export async function get<Resource>ById(db: StrictDB, id: string): Promise<<Resource>Response | null> {
130
+ const doc = await db.queryOne<<Resource>Doc>(COLLECTION, { _id: id });
131
+ return doc ? toResponse(doc) : null;
132
+ }
133
+
134
+ export async function list<Resource>s(db: StrictDB, options: {
135
+ page?: number;
136
+ limit?: number;
137
+ sort?: Record<string, 1 | -1>;
138
+ } = {}): Promise<{ data: <Resource>Response[]; total: number; page: number; limit: number }> {
139
+ const page = Math.max(1, options.page ?? 1);
140
+ const limit = Math.min(100, Math.max(1, options.limit ?? 20));
141
+ const sort = options.sort ?? { createdAt: -1 };
142
+
143
+ const [docs, total] = await Promise.all([
144
+ db.queryMany<<Resource>Doc>(COLLECTION, [
145
+ { $sort: sort },
146
+ { $skip: (page - 1) * limit },
147
+ { $limit: limit },
148
+ ]),
149
+ db.count(COLLECTION),
150
+ ]);
151
+
152
+ return { data: docs.map(toResponse), total, page, limit };
153
+ }
154
+
155
+ export async function update<Resource>(
156
+ db: StrictDB,
157
+ id: string,
158
+ body: Update<Resource>Body
159
+ ): Promise<<Resource>Response | null> {
160
+ const filter = { _id: id };
161
+ await db.updateOne(COLLECTION, filter, {
162
+ $set: { ...body, updatedAt: new Date() },
163
+ });
164
+ const updated = await db.queryOne<<Resource>Doc>(COLLECTION, filter);
165
+ return updated ? toResponse(updated) : null;
166
+ }
167
+
168
+ export async function delete<Resource>(db: StrictDB, id: string): Promise<boolean> {
169
+ await db.deleteOne(COLLECTION, { _id: id });
170
+ return true;
171
+ }
172
+ ```
173
+
174
+ ### File 3: `src/routes/v1/<resource>.ts` — Routes (thin, no logic)
175
+
176
+ ```typescript
177
+ import { Router } from 'express';
178
+ import type { Request, Response } from 'express';
179
+ import {
180
+ create<Resource>,
181
+ get<Resource>ById,
182
+ list<Resource>s,
183
+ update<Resource>,
184
+ delete<Resource>,
185
+ } from '../../handlers/<resource>.js';
186
+
187
+ const router = Router();
188
+
189
+ // POST /api/v1/<resources>
190
+ router.post('/', async (req: Request, res: Response) => {
191
+ try {
192
+ const result = await create<Resource>(req.body);
193
+ res.status(201).json(result);
194
+ } catch (err) {
195
+ console.error('Create <resource> failed:', err);
196
+ res.status(500).json({ error: 'Internal server error' });
197
+ }
198
+ });
199
+
200
+ // GET /api/v1/<resources>
201
+ router.get('/', async (req: Request, res: Response) => {
202
+ try {
203
+ const page = parseInt(req.query.page as string) || 1;
204
+ const limit = parseInt(req.query.limit as string) || 20;
205
+ const result = await list<Resource>s({ page, limit });
206
+ res.json(result);
207
+ } catch (err) {
208
+ console.error('List <resources> failed:', err);
209
+ res.status(500).json({ error: 'Internal server error' });
210
+ }
211
+ });
212
+
213
+ // GET /api/v1/<resources>/:id
214
+ router.get('/:id', async (req: Request, res: Response) => {
215
+ try {
216
+ const result = await get<Resource>ById(req.params.id);
217
+ if (!result) {
218
+ res.status(404).json({ error: '<Resource> not found' });
219
+ return;
220
+ }
221
+ res.json(result);
222
+ } catch (err) {
223
+ console.error('Get <resource> failed:', err);
224
+ res.status(500).json({ error: 'Internal server error' });
225
+ }
226
+ });
227
+
228
+ // PATCH /api/v1/<resources>/:id
229
+ router.patch('/:id', async (req: Request, res: Response) => {
230
+ try {
231
+ const result = await update<Resource>(req.params.id, req.body);
232
+ if (!result) {
233
+ res.status(404).json({ error: '<Resource> not found' });
234
+ return;
235
+ }
236
+ res.json(result);
237
+ } catch (err) {
238
+ console.error('Update <resource> failed:', err);
239
+ res.status(500).json({ error: 'Internal server error' });
240
+ }
241
+ });
242
+
243
+ // DELETE /api/v1/<resources>/:id
244
+ router.delete('/:id', async (req: Request, res: Response) => {
245
+ try {
246
+ const found = await delete<Resource>(req.params.id);
247
+ if (!found) {
248
+ res.status(404).json({ error: '<Resource> not found' });
249
+ return;
250
+ }
251
+ res.status(204).send();
252
+ } catch (err) {
253
+ console.error('Delete <resource> failed:', err);
254
+ res.status(500).json({ error: 'Internal server error' });
255
+ }
256
+ });
257
+
258
+ export default router;
259
+ ```
260
+
261
+ ### File 4: Wire into server — Add to `src/server.ts`
262
+
263
+ Add the import and route registration to the existing server:
264
+
265
+ ```typescript
266
+ // Add this import
267
+ import <resource>Routes from './routes/v1/<resource>.js';
268
+
269
+ // Add this route registration (with /api/v1/ prefix)
270
+ app.use('/api/v1/<resources>', <resource>Routes);
271
+ ```
272
+
273
+ ### File 5: `tests/unit/<resource>.test.ts` — Unit tests
274
+
275
+ ```typescript
276
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
277
+ // Test the handler functions directly, mock the db layer
278
+
279
+ describe('<Resource> Handlers', () => {
280
+ describe('create<Resource>', () => {
281
+ it('should create a new <resource> and return response', async () => {
282
+ // Arrange — mock db calls
283
+ // Act — call create<Resource>
284
+ // Assert — verify response shape, timestamps set
285
+ });
286
+ });
287
+
288
+ describe('get<Resource>ById', () => {
289
+ it('should return <resource> when found', async () => {
290
+ // Test happy path
291
+ });
292
+
293
+ it('should return null for invalid ObjectId', async () => {
294
+ // Test invalid id returns null
295
+ });
296
+
297
+ it('should return null when not found', async () => {
298
+ // Test missing doc returns null
299
+ });
300
+ });
301
+
302
+ describe('list<Resource>s', () => {
303
+ it('should return paginated results', async () => {
304
+ // Test pagination defaults
305
+ });
306
+
307
+ it('should cap limit at 100', async () => {
308
+ // Test max limit enforcement
309
+ });
310
+ });
311
+
312
+ describe('update<Resource>', () => {
313
+ it('should update and return updated doc', async () => {
314
+ // Test updatedAt is refreshed
315
+ });
316
+ });
317
+
318
+ describe('delete<Resource>', () => {
319
+ it('should return true when deleted', async () => {
320
+ // Test happy path
321
+ });
322
+
323
+ it('should return false for invalid ObjectId', async () => {
324
+ // Test invalid id
325
+ });
326
+ });
327
+ });
328
+ ```
329
+
330
+ ## Step 3 — Best Practices Enforced
331
+
332
+ Every generated endpoint MUST follow these rules:
333
+
334
+ ### Security
335
+ - All user input passes through StrictDB's built-in sanitization and guardrails
336
+ - NEVER trust `req.body` types at runtime — validate or use Zod schemas
337
+ - NEVER expose `_id` directly — always map to `id: string`
338
+ - NEVER expose internal error details to the client
339
+ - ALWAYS return generic "Internal server error" for unexpected errors
340
+
341
+ ### Performance
342
+ - Pagination on ALL list endpoints (default 20, max 100)
343
+ - Indexes registered for all query patterns (`registerCollection()`)
344
+ - Uses shared StrictDB instance (NEVER creates new connections)
345
+ - `$limit` enforced before `$lookup` in any join queries
346
+
347
+ ### Architecture
348
+ - Routes are THIN — no business logic, just parse and delegate
349
+ - Handlers contain ALL business logic
350
+ - Types defined FIRST — they're the contract
351
+ - One handler file per resource domain
352
+ - One route file per resource
353
+
354
+ ### Node.js Best Practices
355
+ - Async error handling with try/catch on every route
356
+ - Proper HTTP status codes (201 created, 204 no content, 404 not found)
357
+ - JSON responses on all endpoints (including errors)
358
+ - No callback-style code — async/await only
359
+ - Receives the shared StrictDB instance (never creates its own)
360
+
361
+ ## Step 4 — Verification Checklist
362
+
363
+ After generating, verify:
364
+
365
+ - [ ] Types file created at `src/types/<resource>.ts`
366
+ - [ ] Handler file created at `src/handlers/<resource>.ts`
367
+ - [ ] Route file created at `src/routes/v1/<resource>.ts`
368
+ - [ ] Routes wired into `src/server.ts` with `/api/v1/` prefix
369
+ - [ ] Test file created at `tests/unit/<resource>.test.ts`
370
+ - [ ] All CRUD operations: create, read (single + list), update, delete
371
+ - [ ] Pagination on list endpoint (default 20, max 100)
372
+ - [ ] Indexes registered with `registerCollection()`
373
+ - [ ] No `any` types
374
+ - [ ] No file exceeds 300 lines
375
+ - [ ] _id never exposed — mapped to id string
376
+ - [ ] All errors caught and logged
377
+ - [ ] Data access through the adapter — StrictDB if installed, otherwise the native driver; never Mongoose, never a raw driver in handlers
378
+
379
+ ## RuleCatch Report
380
+
381
+ After all files are created and verified, check RuleCatch:
382
+
383
+ - If the RuleCatch MCP server is available: query for violations in the new API files
384
+ - Report any violations found (type issues, missing assertions, security, etc.)
385
+ - If no MCP: remind the user — "Check your RuleCatch dashboard for any violations in the new endpoint files"
@@ -0,0 +1,230 @@
1
+ ---
2
+ description: Create a Playwright E2E test with explicit success criteria
3
+ scope: project
4
+ argument-hint: <feature-or-page-name>
5
+ allowed-tools: Read, Write, Grep, Glob, Bash, AskUserQuestion
6
+ ---
7
+
8
+ # Create E2E Test
9
+
10
+ Create a Playwright E2E test for: **$ARGUMENTS**
11
+
12
+ ## ABSOLUTE RULES — Read Before Writing a Single Line
13
+
14
+ ### 1. Every test MUST have explicit success criteria
15
+
16
+ "Page loads" is NOT a test. Every `test()` block MUST assert:
17
+ - **URL** — verify the page navigated to the correct URL
18
+ - **Visible elements** — verify key elements are present and visible
19
+ - **Correct data** — verify the right content is displayed
20
+ - **Error states** — verify error messages show when expected
21
+
22
+ ```typescript
23
+ // CORRECT — explicit success criteria
24
+ test('dashboard shows user data after login', async ({ page }) => {
25
+ await page.goto('/login');
26
+ await page.fill('[name="email"]', 'test@example.com');
27
+ await page.fill('[name="password"]', 'password123');
28
+ await page.click('button[type="submit"]');
29
+
30
+ // ✅ Verify URL changed
31
+ await expect(page).toHaveURL('/dashboard');
32
+ // ✅ Verify key element visible
33
+ await expect(page.locator('h1')).toContainText('Welcome');
34
+ // ✅ Verify correct data displayed
35
+ await expect(page.locator('[data-testid="user-email"]')).toContainText('test@example.com');
36
+ // ✅ Verify sidebar loaded
37
+ await expect(page.locator('nav.sidebar')).toBeVisible();
38
+ });
39
+
40
+ // WRONG — this passes even when completely broken
41
+ test('dashboard loads', async ({ page }) => {
42
+ await page.goto('/dashboard');
43
+ // no assertions!
44
+ });
45
+ ```
46
+
47
+ ### 2. A test is FINISHED when it has ALL of these
48
+
49
+ A test is NOT done until it has:
50
+ - [ ] At least one `await expect(page).toHaveURL()` assertion
51
+ - [ ] At least one `await expect(locator).toBeVisible()` assertion
52
+ - [ ] At least one content/data verification (`toContainText`, `toHaveValue`, etc.)
53
+ - [ ] Error case coverage (what happens when it fails?)
54
+ - [ ] No `// TODO` or placeholder assertions
55
+
56
+ If you cannot check ALL of these, the test is incomplete. Say so and explain what's missing.
57
+
58
+ ### 3. Test structure — ALWAYS follow this pattern
59
+
60
+ ```typescript
61
+ import { test, expect } from '@playwright/test';
62
+
63
+ test.describe('[Feature Name]', () => {
64
+ test.describe('happy path', () => {
65
+ test('should [specific behavior] when [specific condition]', async ({ page }) => {
66
+ // ARRANGE — navigate, set up state
67
+ // ACT — perform the user action
68
+ // ASSERT — verify SPECIFIC outcomes (URL, elements, data)
69
+ });
70
+ });
71
+
72
+ test.describe('error handling', () => {
73
+ test('should show error when [invalid input]', async ({ page }) => {
74
+ // Test the failure mode
75
+ });
76
+ });
77
+
78
+ test.describe('edge cases', () => {
79
+ test('should handle [empty state / max values / etc]', async ({ page }) => {
80
+ // Test boundaries
81
+ });
82
+ });
83
+ });
84
+ ```
85
+
86
+ ### 4. Port configuration — ALWAYS use test ports
87
+
88
+ E2E tests run on TEST ports, never dev ports:
89
+
90
+ | Service | Test Port | Base URL |
91
+ |-----------|-----------|----------------------------|
92
+ | Website | 4000 | http://localhost:4000 |
93
+ | API | 4010 | http://localhost:4010 |
94
+ | Dashboard | 4020 | http://localhost:4020 |
95
+
96
+ The `baseURL` is already set in `playwright.config.ts`. Use relative paths:
97
+
98
+ ```typescript
99
+ // CORRECT — uses baseURL from config
100
+ await page.goto('/dashboard');
101
+
102
+ // WRONG — hardcoded URL
103
+ await page.goto('http://localhost:3000/dashboard');
104
+ ```
105
+
106
+ ### 5. What to test for each page type
107
+
108
+ **For a page/route:**
109
+ - URL is correct after navigation
110
+ - Page title / heading is present
111
+ - Key UI elements are visible (nav, sidebar, footer, etc.)
112
+ - Dynamic data is loaded and displayed
113
+ - Links navigate to correct destinations
114
+ - Responsive behavior (if applicable)
115
+
116
+ **For a form:**
117
+ - Empty form shows proper validation messages
118
+ - Valid submission succeeds (verify success state)
119
+ - Invalid input shows specific error messages
120
+ - Submit button disabled during processing
121
+ - Form clears or redirects after success
122
+
123
+ **For an API endpoint:**
124
+ - Correct response status code
125
+ - Response body matches expected shape
126
+ - Error responses have proper status codes and messages
127
+ - Authentication/authorization is enforced
128
+
129
+ **For authentication:**
130
+ - Login with valid credentials succeeds
131
+ - Login with invalid credentials shows error
132
+ - Protected pages redirect to login
133
+ - Logout clears session
134
+ - Token expiry is handled
135
+
136
+ ### 6. Naming convention
137
+
138
+ File: `tests/e2e/[feature-name].spec.ts`
139
+
140
+ Examples:
141
+ - `tests/e2e/auth-login.spec.ts`
142
+ - `tests/e2e/dashboard-overview.spec.ts`
143
+ - `tests/e2e/api-users.spec.ts`
144
+ - `tests/e2e/settings-profile.spec.ts`
145
+
146
+ ## Step 0 — Auto-Branch (if on main)
147
+
148
+ Before creating any files, check the current branch:
149
+
150
+ ```bash
151
+ git branch --show-current
152
+ ```
153
+
154
+ **Default behavior** (`auto_branch = true` in `claude-mastery-project.conf`):
155
+ - If on `main` or `master`: automatically create a feature branch and switch to it:
156
+ ```bash
157
+ git checkout -b test/<feature-name>
158
+ ```
159
+ Report: "Created branch `test/<feature>` — main stays untouched."
160
+ - If already on a feature branch: proceed
161
+ - If not a git repo: skip this check
162
+
163
+ **To disable:** Set `auto_branch = false` in `claude-mastery-project.conf`. When disabled, warn and ask the user before proceeding on main.
164
+
165
+ ## Step 1 — Gather Information
166
+
167
+ Before writing the test:
168
+
169
+ 1. **Read the source code** for the feature/page being tested
170
+ 2. **Identify all assertions** — what URLs, elements, and data should be verified?
171
+ 3. **Identify error states** — what can go wrong? What should the user see?
172
+ 4. **Check for test data** — does the test need seeded data? Mock API responses?
173
+
174
+ ## Step 2 — Ask What to Verify (if not obvious)
175
+
176
+ If the feature has multiple possible success criteria, ask the user:
177
+
178
+ Use AskUserQuestion to clarify:
179
+ - "What specific elements should be visible on this page?"
180
+ - "What data should be displayed after this action?"
181
+ - "What error message should appear for invalid input?"
182
+
183
+ ## Step 3 — Write the Test
184
+
185
+ Create the test file at `tests/e2e/[feature-name].spec.ts` following ALL rules above.
186
+
187
+ Every test file MUST include:
188
+ 1. At least one happy-path test
189
+ 2. At least one error-case test
190
+ 3. Explicit assertions in every `test()` block
191
+
192
+ ## Step 4 — Verification Checklist
193
+
194
+ After writing, verify:
195
+
196
+ - [ ] File is at `tests/e2e/[name].spec.ts`
197
+ - [ ] Every `test()` has at least 3 assertions (URL, element, data)
198
+ - [ ] Error cases are covered
199
+ - [ ] No hardcoded ports (uses baseURL from config)
200
+ - [ ] No `// TODO` placeholders
201
+ - [ ] Test names describe behavior: "should [verb] when [condition]"
202
+ - [ ] No `any` types
203
+ - [ ] No `.only` left in the code
204
+
205
+ ## Running Tests
206
+
207
+ ```bash
208
+ # Run all E2E tests (spawns servers automatically on test ports)
209
+ pnpm test:e2e
210
+
211
+ # Run a specific test file
212
+ pnpm test:e2e tests/e2e/auth-login.spec.ts
213
+
214
+ # Run with UI mode (debug)
215
+ pnpm test:e2e:ui
216
+
217
+ # Run headed (see the browser)
218
+ pnpm test:e2e:headed
219
+
220
+ # View the last test report
221
+ pnpm test:e2e:report
222
+ ```
223
+
224
+ ## RuleCatch Report
225
+
226
+ After the test file is created and verified, check RuleCatch:
227
+
228
+ - If the RuleCatch MCP server is available: query for violations in the new test file
229
+ - Report any violations found (missing assertions, TypeScript issues, etc.)
230
+ - If no MCP: suggest checking the RuleCatch dashboard