start-vibing 3.0.8 → 3.0.9
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/package.json +1 -1
- package/template/.claude/CLAUDE.md +18 -3
- package/template/.claude/skills/api-docs/SKILL.md +206 -0
- package/template/.claude/skills/claude-seo/SKILL.md +84 -0
- package/template/.claude/skills/mongoose-patterns/SKILL.md +188 -0
- package/template/.claude/skills/playwright-testing/SKILL.md +251 -0
- package/template/.claude/skills/skill-creator/SKILL.md +106 -0
- package/template/.claude/skills/test-infrastructure/SKILL.md +242 -0
- package/template/.claude/agents/_archive/01-orchestration/agent-selector.md +0 -130
- package/template/.claude/agents/_archive/01-orchestration/checkpoint-manager.md +0 -142
- package/template/.claude/agents/_archive/01-orchestration/context-manager.md +0 -138
- package/template/.claude/agents/_archive/01-orchestration/error-recovery.md +0 -182
- package/template/.claude/agents/_archive/01-orchestration/orchestrator.md +0 -114
- package/template/.claude/agents/_archive/01-orchestration/parallel-coordinator.md +0 -141
- package/template/.claude/agents/_archive/01-orchestration/task-decomposer.md +0 -121
- package/template/.claude/agents/_archive/01-orchestration/workflow-router.md +0 -119
- package/template/.claude/agents/_archive/02-typescript/bun-runtime-expert.md +0 -197
- package/template/.claude/agents/_archive/02-typescript/esm-resolver.md +0 -193
- package/template/.claude/agents/_archive/02-typescript/import-alias-enforcer.md +0 -158
- package/template/.claude/agents/_archive/02-typescript/ts-generics-helper.md +0 -183
- package/template/.claude/agents/_archive/02-typescript/ts-migration-helper.md +0 -238
- package/template/.claude/agents/_archive/02-typescript/ts-strict-checker.md +0 -180
- package/template/.claude/agents/_archive/02-typescript/ts-types-analyzer.md +0 -199
- package/template/.claude/agents/_archive/02-typescript/type-definition-writer.md +0 -187
- package/template/.claude/agents/_archive/02-typescript/zod-schema-designer.md +0 -212
- package/template/.claude/agents/_archive/02-typescript/zod-validator.md +0 -158
- package/template/.claude/agents/_archive/03-testing/playwright-assertions.md +0 -265
- package/template/.claude/agents/_archive/03-testing/playwright-e2e.md +0 -247
- package/template/.claude/agents/_archive/03-testing/playwright-fixtures.md +0 -234
- package/template/.claude/agents/_archive/03-testing/playwright-multi-viewport.md +0 -256
- package/template/.claude/agents/_archive/03-testing/playwright-page-objects.md +0 -247
- package/template/.claude/agents/_archive/03-testing/test-cleanup-manager.md +0 -248
- package/template/.claude/agents/_archive/03-testing/test-data-generator.md +0 -254
- package/template/.claude/agents/_archive/03-testing/tester-integration.md +0 -278
- package/template/.claude/agents/_archive/03-testing/tester-unit.md +0 -207
- package/template/.claude/agents/_archive/03-testing/vitest-config.md +0 -287
- package/template/.claude/agents/_archive/04-docker/container-health.md +0 -255
- package/template/.claude/agents/_archive/04-docker/deployment-validator.md +0 -225
- package/template/.claude/agents/_archive/04-docker/docker-compose-designer.md +0 -281
- package/template/.claude/agents/_archive/04-docker/docker-env-manager.md +0 -235
- package/template/.claude/agents/_archive/04-docker/docker-multi-stage.md +0 -241
- package/template/.claude/agents/_archive/04-docker/dockerfile-optimizer.md +0 -208
- package/template/.claude/agents/_archive/05-database/database-seeder.md +0 -273
- package/template/.claude/agents/_archive/05-database/mongodb-query-optimizer.md +0 -230
- package/template/.claude/agents/_archive/05-database/mongoose-aggregation.md +0 -306
- package/template/.claude/agents/_archive/05-database/mongoose-index-optimizer.md +0 -182
- package/template/.claude/agents/_archive/05-database/mongoose-schema-designer.md +0 -267
- package/template/.claude/agents/_archive/06-security/auth-session-validator.md +0 -68
- package/template/.claude/agents/_archive/06-security/input-sanitizer.md +0 -80
- package/template/.claude/agents/_archive/06-security/owasp-checker.md +0 -97
- package/template/.claude/agents/_archive/06-security/permission-auditor.md +0 -100
- package/template/.claude/agents/_archive/06-security/security-auditor.md +0 -84
- package/template/.claude/agents/_archive/06-security/sensitive-data-scanner.md +0 -83
- package/template/.claude/agents/_archive/07-documentation/api-documenter.md +0 -136
- package/template/.claude/agents/_archive/07-documentation/changelog-manager.md +0 -105
- package/template/.claude/agents/_archive/07-documentation/claude-md-compactor.md +0 -214
- package/template/.claude/agents/_archive/07-documentation/documenter.md +0 -184
- package/template/.claude/agents/_archive/07-documentation/domain-updater.md +0 -138
- package/template/.claude/agents/_archive/07-documentation/jsdoc-generator.md +0 -114
- package/template/.claude/agents/_archive/07-documentation/readme-generator.md +0 -135
- package/template/.claude/agents/_archive/08-git/branch-manager.md +0 -58
- package/template/.claude/agents/_archive/08-git/commit-manager.md +0 -78
- package/template/.claude/agents/_archive/09-quality/code-reviewer.md +0 -71
- package/template/.claude/agents/_archive/09-quality/quality-checker.md +0 -67
- package/template/.claude/agents/_archive/10-research/best-practices-finder.md +0 -89
- package/template/.claude/agents/_archive/10-research/competitor-analyzer.md +0 -106
- package/template/.claude/agents/_archive/10-research/pattern-researcher.md +0 -93
- package/template/.claude/agents/_archive/10-research/research-cache-manager.md +0 -76
- package/template/.claude/agents/_archive/10-research/research-web.md +0 -98
- package/template/.claude/agents/_archive/10-research/tech-evaluator.md +0 -101
- package/template/.claude/agents/_archive/11-ui-ux/accessibility-auditor.md +0 -136
- package/template/.claude/agents/_archive/11-ui-ux/design-system-enforcer.md +0 -125
- package/template/.claude/agents/_archive/11-ui-ux/skeleton-generator.md +0 -118
- package/template/.claude/agents/_archive/11-ui-ux/ui-desktop.md +0 -132
- package/template/.claude/agents/_archive/11-ui-ux/ui-mobile.md +0 -125
- package/template/.claude/agents/_archive/11-ui-ux/ui-tablet.md +0 -110
- package/template/.claude/agents/_archive/12-performance/api-latency-analyzer.md +0 -156
- package/template/.claude/agents/_archive/12-performance/bundle-analyzer.md +0 -113
- package/template/.claude/agents/_archive/12-performance/memory-leak-detector.md +0 -137
- package/template/.claude/agents/_archive/12-performance/performance-profiler.md +0 -115
- package/template/.claude/agents/_archive/12-performance/query-optimizer.md +0 -124
- package/template/.claude/agents/_archive/12-performance/render-optimizer.md +0 -154
- package/template/.claude/agents/_archive/_backup/analyzer.md +0 -134
- package/template/.claude/agents/_archive/_backup/code-reviewer.md +0 -279
- package/template/.claude/agents/_archive/_backup/commit-manager.md +0 -219
- package/template/.claude/agents/_archive/_backup/debugger.md +0 -280
- package/template/.claude/agents/_archive/_backup/documenter.md +0 -237
- package/template/.claude/agents/_archive/_backup/domain-updater.md +0 -197
- package/template/.claude/agents/_archive/_backup/final-validator.md +0 -169
- package/template/.claude/agents/_archive/_backup/orchestrator.md +0 -149
- package/template/.claude/agents/_archive/_backup/performance.md +0 -232
- package/template/.claude/agents/_archive/_backup/quality-checker.md +0 -240
- package/template/.claude/agents/_archive/_backup/research.md +0 -315
- package/template/.claude/agents/_archive/_backup/security-auditor.md +0 -192
- package/template/.claude/agents/_archive/_backup/tester.md +0 -566
- package/template/.claude/agents/_archive/_backup/ui-ux-reviewer.md +0 -247
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: playwright-testing
|
|
3
|
+
description: "ALWAYS invoke when creating or editing Playwright E2E tests. Do NOT write E2E tests without checking Page Object Model, fixture design, and multi-viewport patterns first."
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, Grep, Glob
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Playwright Testing
|
|
8
|
+
|
|
9
|
+
Architecture patterns for Playwright E2E tests: Page Objects, fixtures, assertions, and multi-viewport testing.
|
|
10
|
+
|
|
11
|
+
## Playwright Config
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// playwright.config.ts
|
|
15
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
16
|
+
|
|
17
|
+
export default defineConfig({
|
|
18
|
+
testDir: './tests/e2e',
|
|
19
|
+
fullyParallel: true,
|
|
20
|
+
forbidOnly: !!process.env['CI'],
|
|
21
|
+
retries: process.env['CI'] ? 2 : 0,
|
|
22
|
+
workers: process.env['CI'] ? 1 : undefined,
|
|
23
|
+
reporter: [['html'], ['list']],
|
|
24
|
+
use: {
|
|
25
|
+
baseURL: 'http://localhost:3000',
|
|
26
|
+
trace: 'on-first-retry',
|
|
27
|
+
screenshot: 'only-on-failure',
|
|
28
|
+
},
|
|
29
|
+
projects: [
|
|
30
|
+
{ name: 'Desktop', use: { ...devices['Desktop Chrome'] } },
|
|
31
|
+
{ name: 'Tablet', use: { ...devices['iPad Mini'] } },
|
|
32
|
+
{ name: 'Mobile', use: { ...devices['iPhone 14'] } },
|
|
33
|
+
],
|
|
34
|
+
webServer: {
|
|
35
|
+
command: 'bun run dev',
|
|
36
|
+
url: 'http://localhost:3000',
|
|
37
|
+
reuseExistingServer: !process.env['CI'],
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Page Object Model
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// tests/e2e/pages/base.page.ts
|
|
46
|
+
import { type Page, type Locator, expect } from '@playwright/test';
|
|
47
|
+
|
|
48
|
+
export abstract class BasePage {
|
|
49
|
+
constructor(protected page: Page) {}
|
|
50
|
+
|
|
51
|
+
abstract readonly url: string;
|
|
52
|
+
|
|
53
|
+
async goto() {
|
|
54
|
+
await this.page.goto(this.url);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async waitForReady() {
|
|
58
|
+
await this.page.waitForLoadState('networkidle');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Shared helpers
|
|
62
|
+
async getToast() {
|
|
63
|
+
return this.page.locator('[data-sonner-toast]');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// tests/e2e/pages/login.page.ts
|
|
70
|
+
export class LoginPage extends BasePage {
|
|
71
|
+
readonly url = '/login';
|
|
72
|
+
|
|
73
|
+
// Locators (prefer data-testid, role, text)
|
|
74
|
+
readonly emailInput = this.page.getByLabel('Email');
|
|
75
|
+
readonly passwordInput = this.page.getByLabel('Password');
|
|
76
|
+
readonly submitButton = this.page.getByRole('button', { name: 'Sign in' });
|
|
77
|
+
readonly errorMessage = this.page.getByRole('alert');
|
|
78
|
+
|
|
79
|
+
async login(email: string, password: string) {
|
|
80
|
+
await this.emailInput.fill(email);
|
|
81
|
+
await this.passwordInput.fill(password);
|
|
82
|
+
await this.submitButton.click();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Component Page Objects
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// tests/e2e/pages/components/data-table.component.ts
|
|
91
|
+
export class DataTableComponent {
|
|
92
|
+
constructor(private container: Locator) {}
|
|
93
|
+
|
|
94
|
+
get rows() { return this.container.getByRole('row'); }
|
|
95
|
+
get headers() { return this.container.getByRole('columnheader'); }
|
|
96
|
+
|
|
97
|
+
async sortBy(column: string) {
|
|
98
|
+
await this.container.getByRole('columnheader', { name: column }).click();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async getRowCount() {
|
|
102
|
+
return this.rows.count() - 1; // Minus header
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Fixture Architecture
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// tests/e2e/fixtures/index.ts
|
|
111
|
+
import { test as base } from '@playwright/test';
|
|
112
|
+
import { LoginPage } from '../pages/login.page';
|
|
113
|
+
import { DashboardPage } from '../pages/dashboard.page';
|
|
114
|
+
|
|
115
|
+
// Worker fixture — shared across tests in same worker
|
|
116
|
+
// Test fixture — fresh per test
|
|
117
|
+
type TestFixtures = {
|
|
118
|
+
loginPage: LoginPage;
|
|
119
|
+
dashboardPage: DashboardPage;
|
|
120
|
+
authenticatedPage: Page;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const test = base.extend<TestFixtures>({
|
|
124
|
+
loginPage: async ({ page }, use) => {
|
|
125
|
+
await use(new LoginPage(page));
|
|
126
|
+
},
|
|
127
|
+
dashboardPage: async ({ page }, use) => {
|
|
128
|
+
await use(new DashboardPage(page));
|
|
129
|
+
},
|
|
130
|
+
authenticatedPage: async ({ page }, use) => {
|
|
131
|
+
// Setup: login
|
|
132
|
+
await page.goto('/login');
|
|
133
|
+
await page.getByLabel('Email').fill('test@example.com');
|
|
134
|
+
await page.getByLabel('Password').fill('Password123!');
|
|
135
|
+
await page.getByRole('button', { name: 'Sign in' }).click();
|
|
136
|
+
await page.waitForURL('/dashboard');
|
|
137
|
+
// Use the authenticated page
|
|
138
|
+
await use(page);
|
|
139
|
+
// Teardown: cleanup (automatic)
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
export { expect } from '@playwright/test';
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Auto-fixtures (run before every test)
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
export const test = base.extend<{ autoCleanup: void }>({
|
|
150
|
+
autoCleanup: [async ({ page }, use) => {
|
|
151
|
+
await use(); // Test runs here
|
|
152
|
+
// Cleanup after test
|
|
153
|
+
await page.evaluate(() => localStorage.clear());
|
|
154
|
+
}, { auto: true }],
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Assertion Reference
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// Element assertions
|
|
162
|
+
await expect(locator).toBeVisible();
|
|
163
|
+
await expect(locator).toBeHidden();
|
|
164
|
+
await expect(locator).toBeEnabled();
|
|
165
|
+
await expect(locator).toBeDisabled();
|
|
166
|
+
await expect(locator).toBeChecked();
|
|
167
|
+
await expect(locator).toBeFocused();
|
|
168
|
+
await expect(locator).toHaveText('Expected text');
|
|
169
|
+
await expect(locator).toContainText('partial');
|
|
170
|
+
await expect(locator).toHaveValue('expected');
|
|
171
|
+
await expect(locator).toHaveAttribute('href', '/path');
|
|
172
|
+
await expect(locator).toHaveClass(/active/);
|
|
173
|
+
await expect(locator).toHaveCount(5);
|
|
174
|
+
await expect(locator).toHaveCSS('color', 'rgb(0, 0, 0)');
|
|
175
|
+
|
|
176
|
+
// Page assertions
|
|
177
|
+
await expect(page).toHaveURL('/expected-path');
|
|
178
|
+
await expect(page).toHaveURL(/\/users\/\d+/);
|
|
179
|
+
await expect(page).toHaveTitle('Page Title');
|
|
180
|
+
|
|
181
|
+
// Negation
|
|
182
|
+
await expect(locator).not.toBeVisible();
|
|
183
|
+
|
|
184
|
+
// Soft assertions (don't stop test)
|
|
185
|
+
await expect.soft(locator).toHaveText('text');
|
|
186
|
+
|
|
187
|
+
// Custom timeout
|
|
188
|
+
await expect(locator).toBeVisible({ timeout: 10000 });
|
|
189
|
+
|
|
190
|
+
// API response assertions
|
|
191
|
+
const response = await page.request.get('/api/users');
|
|
192
|
+
expect(response.ok()).toBeTruthy();
|
|
193
|
+
expect(response.status()).toBe(200);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Multi-Viewport Testing
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// tests/e2e/responsive/dashboard.spec.ts
|
|
200
|
+
import { test, expect } from '../fixtures';
|
|
201
|
+
|
|
202
|
+
const viewports = [
|
|
203
|
+
{ name: 'Mobile', width: 375, height: 812 },
|
|
204
|
+
{ name: 'Tablet', width: 768, height: 1024 },
|
|
205
|
+
{ name: 'Desktop', width: 1280, height: 720 },
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
for (const viewport of viewports) {
|
|
209
|
+
test.describe(`Dashboard - ${viewport.name}`, () => {
|
|
210
|
+
test.use({ viewport: { width: viewport.width, height: viewport.height } });
|
|
211
|
+
|
|
212
|
+
test('navigation is appropriate', async ({ page }) => {
|
|
213
|
+
await page.goto('/dashboard');
|
|
214
|
+
|
|
215
|
+
if (viewport.width < 768) {
|
|
216
|
+
// Mobile: bottom nav or hamburger
|
|
217
|
+
await expect(page.getByTestId('mobile-nav')).toBeVisible();
|
|
218
|
+
await expect(page.getByTestId('sidebar')).toBeHidden();
|
|
219
|
+
} else if (viewport.width < 1280) {
|
|
220
|
+
// Tablet: collapsible sidebar
|
|
221
|
+
await expect(page.getByTestId('sidebar')).toBeVisible();
|
|
222
|
+
} else {
|
|
223
|
+
// Desktop: full sidebar
|
|
224
|
+
await expect(page.getByTestId('sidebar')).toBeVisible();
|
|
225
|
+
await expect(page.getByTestId('search-bar')).toBeVisible();
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Selector Best Practices
|
|
233
|
+
|
|
234
|
+
| Priority | Selector | Example |
|
|
235
|
+
|----------|----------|---------|
|
|
236
|
+
| 1 | `getByRole` | `getByRole('button', { name: 'Submit' })` |
|
|
237
|
+
| 2 | `getByLabel` | `getByLabel('Email')` |
|
|
238
|
+
| 3 | `getByText` | `getByText('Welcome')` |
|
|
239
|
+
| 4 | `getByTestId` | `getByTestId('user-avatar')` |
|
|
240
|
+
| 5 | CSS (last resort) | `locator('.card:first-child')` |
|
|
241
|
+
|
|
242
|
+
## Critical Rules
|
|
243
|
+
|
|
244
|
+
1. **Page Object Model** — Every page/component gets a POM class extending BasePage
|
|
245
|
+
2. **Fixtures over beforeEach** — Use `test.extend` for reusable setup/teardown
|
|
246
|
+
3. **3 viewports** — Test Mobile (375), Tablet (768), Desktop (1280+) separately
|
|
247
|
+
4. **Role-based selectors** — Prefer `getByRole`/`getByLabel` over CSS selectors
|
|
248
|
+
5. **No hardcoded waits** — Use `waitForURL`, `waitForLoadState`, `expect` auto-retry
|
|
249
|
+
6. **Trace on failure** — `trace: 'on-first-retry'` for debugging
|
|
250
|
+
7. **Parallel by default** — `fullyParallel: true` unless tests share state
|
|
251
|
+
8. **Worker fixtures** — Share expensive setup (auth, DB) across tests in same worker
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skill-creator
|
|
3
|
+
description: "ALWAYS invoke when creating, editing, or optimizing Claude Code skills. Do NOT create skills without following the interview, draft, test, iterate loop first."
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, Grep, Glob
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Skill Creator
|
|
8
|
+
|
|
9
|
+
Meta-skill for creating new skills and iteratively improving them.
|
|
10
|
+
|
|
11
|
+
> Source: `anthropics/skills` (adapted for project conventions)
|
|
12
|
+
|
|
13
|
+
## Creation Loop
|
|
14
|
+
|
|
15
|
+
1. **Capture Intent** — What should this skill do? When should it trigger? What's the output format?
|
|
16
|
+
2. **Interview & Research** — Ask about edge cases, input/output formats, dependencies
|
|
17
|
+
3. **Write the SKILL.md** — See anatomy below
|
|
18
|
+
4. **Test** — Create 2-3 realistic test prompts, run with/without skill
|
|
19
|
+
5. **Evaluate** — Review outputs qualitatively and quantitatively
|
|
20
|
+
6. **Iterate** — Improve based on feedback, repeat until satisfied
|
|
21
|
+
7. **Optimize Description** — Generate trigger eval queries for better activation
|
|
22
|
+
|
|
23
|
+
## Skill Anatomy
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
skill-name/
|
|
27
|
+
├── SKILL.md (required)
|
|
28
|
+
│ ├── YAML frontmatter (name, description, allowed-tools)
|
|
29
|
+
│ └── Markdown instructions
|
|
30
|
+
└── Optional resources
|
|
31
|
+
├── scripts/ — Executable code
|
|
32
|
+
├── references/ — Docs loaded on demand
|
|
33
|
+
└── assets/ — Templates, icons
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Frontmatter (Project Convention)
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
---
|
|
40
|
+
name: my-skill
|
|
41
|
+
description: "ALWAYS invoke when [trigger]. Do NOT [negation]."
|
|
42
|
+
allowed-tools: Read, Write, Edit, Bash, Grep, Glob
|
|
43
|
+
---
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Description rules:**
|
|
47
|
+
- Start with `ALWAYS invoke when...`
|
|
48
|
+
- Include `Do NOT...` negation
|
|
49
|
+
- Keep under 200 chars
|
|
50
|
+
- Be slightly "pushy" — Claude tends to undertrigger skills
|
|
51
|
+
|
|
52
|
+
### Writing Guide
|
|
53
|
+
|
|
54
|
+
- Keep SKILL.md under 500 lines
|
|
55
|
+
- Use imperative form in instructions
|
|
56
|
+
- Explain **why** things are important (not heavy-handed MUSTs)
|
|
57
|
+
- Include examples for complex patterns
|
|
58
|
+
- Reference files from SKILL.md with guidance on when to read
|
|
59
|
+
- For large reference files (>300 lines), include a table of contents
|
|
60
|
+
|
|
61
|
+
## Testing Skills
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
// evals/evals.json
|
|
65
|
+
{
|
|
66
|
+
"skill_name": "example-skill",
|
|
67
|
+
"evals": [
|
|
68
|
+
{
|
|
69
|
+
"id": 1,
|
|
70
|
+
"prompt": "User's task prompt",
|
|
71
|
+
"expected_output": "Description of expected result",
|
|
72
|
+
"files": []
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
For each test case, run Claude with and without the skill, then compare outputs.
|
|
79
|
+
|
|
80
|
+
## Description Optimization
|
|
81
|
+
|
|
82
|
+
1. **Generate 20 eval queries** — Mix of should-trigger (8-10) and should-not-trigger (8-10)
|
|
83
|
+
2. **Make queries realistic** — Include file paths, personal context, casual speech, typos
|
|
84
|
+
3. **Focus negatives on near-misses** — Adjacent domains, ambiguous phrasing
|
|
85
|
+
4. **Test triggering** — Run queries against the description
|
|
86
|
+
5. **Iterate** — Improve description based on trigger accuracy
|
|
87
|
+
|
|
88
|
+
## Validation
|
|
89
|
+
|
|
90
|
+
After creating/modifying a skill:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
bash .claude/scripts/validate-skills.sh
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Checks: frontmatter, imperative pattern, negation, char budget < 16,000.
|
|
97
|
+
|
|
98
|
+
## Critical Rules
|
|
99
|
+
|
|
100
|
+
1. **Interview first** — Understand the user's intent before writing
|
|
101
|
+
2. **Imperative description** — `ALWAYS invoke when... Do NOT...`
|
|
102
|
+
3. **Under 200 chars** — Description must be concise
|
|
103
|
+
4. **Test with real prompts** — Not abstract requests
|
|
104
|
+
5. **Generalize from feedback** — Don't overfit to test cases
|
|
105
|
+
6. **Explain the why** — Theory of mind over rigid MUSTs
|
|
106
|
+
7. **Bundle repeated work** — If every test run writes the same helper, put it in scripts/
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-infrastructure
|
|
3
|
+
description: "ALWAYS invoke when setting up test configs, creating data factories, or writing integration tests. Do NOT configure Vitest or create test data without checking patterns first."
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, Grep, Glob
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Test Infrastructure
|
|
8
|
+
|
|
9
|
+
Patterns for Vitest configuration, data factories, integration tests, and test cleanup.
|
|
10
|
+
|
|
11
|
+
## Data Factory Pattern
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// tests/factories/user.factory.ts
|
|
15
|
+
import { ObjectId } from 'mongodb';
|
|
16
|
+
|
|
17
|
+
let counter = 0;
|
|
18
|
+
|
|
19
|
+
function nextId(): string {
|
|
20
|
+
return new ObjectId().toHexString();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function nextEmail(): string {
|
|
24
|
+
counter++;
|
|
25
|
+
return `user-${counter}-${Date.now()}@test.com`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createUser(overrides: Partial<IUser> = {}): IUser {
|
|
29
|
+
return {
|
|
30
|
+
_id: nextId(),
|
|
31
|
+
email: nextEmail(),
|
|
32
|
+
name: `Test User ${counter}`,
|
|
33
|
+
password: 'HashedPassword123!',
|
|
34
|
+
role: 'user',
|
|
35
|
+
isActive: true,
|
|
36
|
+
createdAt: new Date(),
|
|
37
|
+
updatedAt: new Date(),
|
|
38
|
+
...overrides,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createUsers(count: number, overrides: Partial<IUser> = {}): IUser[] {
|
|
43
|
+
return Array.from({ length: count }, () => createUser(overrides));
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Random Data Helpers (no faker dependency)
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// tests/helpers/random.ts
|
|
51
|
+
export const random = {
|
|
52
|
+
string: (length = 10) =>
|
|
53
|
+
Math.random().toString(36).substring(2, 2 + length),
|
|
54
|
+
number: (min: number, max: number) =>
|
|
55
|
+
Math.floor(Math.random() * (max - min + 1)) + min,
|
|
56
|
+
boolean: () => Math.random() > 0.5,
|
|
57
|
+
date: (daysBack = 30) => {
|
|
58
|
+
const d = new Date();
|
|
59
|
+
d.setDate(d.getDate() - Math.floor(Math.random() * daysBack));
|
|
60
|
+
return d;
|
|
61
|
+
},
|
|
62
|
+
pick: <T>(arr: T[]): T => arr[Math.floor(Math.random() * arr.length)]!,
|
|
63
|
+
email: () => `test-${Date.now()}-${Math.random().toString(36).slice(2, 6)}@test.com`,
|
|
64
|
+
};
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Vitest Configuration
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// vitest.config.ts
|
|
71
|
+
import { defineConfig } from 'vitest/config';
|
|
72
|
+
import path from 'path';
|
|
73
|
+
|
|
74
|
+
export default defineConfig({
|
|
75
|
+
test: {
|
|
76
|
+
globals: true,
|
|
77
|
+
environment: 'node',
|
|
78
|
+
include: ['tests/**/*.test.ts', 'tests/**/*.spec.ts'],
|
|
79
|
+
exclude: ['tests/e2e/**'],
|
|
80
|
+
coverage: {
|
|
81
|
+
provider: 'v8',
|
|
82
|
+
reporter: ['text', 'lcov'],
|
|
83
|
+
include: ['src/**/*.ts', 'server/**/*.ts'],
|
|
84
|
+
exclude: ['**/*.d.ts', '**/*.test.ts'],
|
|
85
|
+
thresholds: { statements: 80, branches: 75, functions: 80, lines: 80 },
|
|
86
|
+
},
|
|
87
|
+
setupFiles: ['tests/setup.ts'],
|
|
88
|
+
testTimeout: 10000,
|
|
89
|
+
},
|
|
90
|
+
resolve: {
|
|
91
|
+
alias: {
|
|
92
|
+
'$types': path.resolve(__dirname, './types'),
|
|
93
|
+
'@common': path.resolve(__dirname, './common/index.ts'),
|
|
94
|
+
'@db': path.resolve(__dirname, './common/db'),
|
|
95
|
+
'@': path.resolve(__dirname, './src'),
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Setup File
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// tests/setup.ts
|
|
105
|
+
import { beforeAll, afterAll, afterEach } from 'vitest';
|
|
106
|
+
import { MongoMemoryServer } from 'mongodb-memory-server';
|
|
107
|
+
import mongoose from 'mongoose';
|
|
108
|
+
|
|
109
|
+
let mongod: MongoMemoryServer;
|
|
110
|
+
|
|
111
|
+
beforeAll(async () => {
|
|
112
|
+
mongod = await MongoMemoryServer.create();
|
|
113
|
+
await mongoose.connect(mongod.getUri());
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
afterEach(async () => {
|
|
117
|
+
const collections = mongoose.connection.collections;
|
|
118
|
+
for (const key in collections) {
|
|
119
|
+
await collections[key]!.deleteMany({});
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
afterAll(async () => {
|
|
124
|
+
await mongoose.disconnect();
|
|
125
|
+
await mongod.stop();
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Integration Test Patterns
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// tests/integration/user.service.test.ts
|
|
133
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
134
|
+
import { UserService } from '@/services/user.service';
|
|
135
|
+
import { UserModel } from '@/models/user.model';
|
|
136
|
+
import { createUser } from '../factories/user.factory';
|
|
137
|
+
|
|
138
|
+
describe('UserService', () => {
|
|
139
|
+
let service: UserService;
|
|
140
|
+
|
|
141
|
+
beforeEach(() => {
|
|
142
|
+
service = new UserService();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('createUser', () => {
|
|
146
|
+
it('should create a user with hashed password', async () => {
|
|
147
|
+
const input = { email: 'new@test.com', password: 'Pass123!', name: 'Test' };
|
|
148
|
+
const user = await service.create(input);
|
|
149
|
+
|
|
150
|
+
expect(user.email).toBe('new@test.com');
|
|
151
|
+
expect(user.password).not.toBe('Pass123!'); // Hashed
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should reject duplicate emails', async () => {
|
|
155
|
+
const existing = createUser({ email: 'dup@test.com' });
|
|
156
|
+
await UserModel.create(existing);
|
|
157
|
+
|
|
158
|
+
await expect(
|
|
159
|
+
service.create({ email: 'dup@test.com', password: 'Pass123!', name: 'Test' })
|
|
160
|
+
).rejects.toThrow(/duplicate/i);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('findById', () => {
|
|
165
|
+
it('should return null for non-existent user', async () => {
|
|
166
|
+
const result = await service.findById('507f1f77bcf86cd799439011');
|
|
167
|
+
expect(result).toBeNull();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### API Route Testing
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// tests/integration/api/users.test.ts
|
|
177
|
+
import { describe, it, expect } from 'vitest';
|
|
178
|
+
import { createUser } from '../../factories/user.factory';
|
|
179
|
+
|
|
180
|
+
describe('POST /api/users', () => {
|
|
181
|
+
it('should validate required fields', async () => {
|
|
182
|
+
const response = await app.inject({
|
|
183
|
+
method: 'POST',
|
|
184
|
+
url: '/api/users',
|
|
185
|
+
payload: {},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(response.statusCode).toBe(400);
|
|
189
|
+
expect(response.json().errors).toBeDefined();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should create user with valid data', async () => {
|
|
193
|
+
const input = { email: 'new@test.com', password: 'Pass123!', name: 'Test' };
|
|
194
|
+
const response = await app.inject({
|
|
195
|
+
method: 'POST',
|
|
196
|
+
url: '/api/users',
|
|
197
|
+
payload: input,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
expect(response.statusCode).toBe(201);
|
|
201
|
+
expect(response.json().email).toBe('new@test.com');
|
|
202
|
+
expect(response.json().password).toBeUndefined(); // Not exposed
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Cleanup Strategy
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
// Delete children before parents to avoid foreign key issues
|
|
211
|
+
async function cleanupDatabase() {
|
|
212
|
+
const collections = ['orders', 'products', 'users', 'sessions'];
|
|
213
|
+
for (const name of collections) {
|
|
214
|
+
const collection = mongoose.connection.collection(name);
|
|
215
|
+
if (collection) await collection.deleteMany({});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Per-test isolation with transaction rollback
|
|
220
|
+
async function withTransaction<T>(fn: () => Promise<T>): Promise<T> {
|
|
221
|
+
const session = await mongoose.startSession();
|
|
222
|
+
session.startTransaction();
|
|
223
|
+
try {
|
|
224
|
+
const result = await fn();
|
|
225
|
+
await session.abortTransaction(); // Always rollback in tests
|
|
226
|
+
return result;
|
|
227
|
+
} finally {
|
|
228
|
+
session.endSession();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Critical Rules
|
|
234
|
+
|
|
235
|
+
1. **ObjectId + counter** — Use real ObjectIds, counter for unique emails
|
|
236
|
+
2. **No faker** — Use simple random helpers (no heavy dependency)
|
|
237
|
+
3. **MongoMemoryServer** — In-memory DB for integration tests
|
|
238
|
+
4. **Path aliases** — Mirror project aliases in vitest.config.ts
|
|
239
|
+
5. **Children before parents** — Delete dependent collections first
|
|
240
|
+
6. **afterEach cleanup** — Clear all collections between tests
|
|
241
|
+
7. **Factory pattern** — `createUser(overrides)` for flexible test data
|
|
242
|
+
8. **Separate E2E** — Exclude `tests/e2e/` from Vitest (Playwright handles those)
|