spec-lite 1.1.6 → 1.1.7

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.
@@ -0,0 +1,153 @@
1
+ # Performance Checklist
2
+
3
+ Quick reference checklist for web application performance. Use alongside the `performance-optimization` skill.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Core Web Vitals Targets](#core-web-vitals-targets)
8
+ - [TTFB Diagnosis](#ttfb-diagnosis)
9
+ - [Frontend Checklist](#frontend-checklist)
10
+ - [Backend Checklist](#backend-checklist)
11
+ - [Measurement Commands](#measurement-commands)
12
+ - [Common Anti-Patterns](#common-anti-patterns)
13
+
14
+ ## Core Web Vitals Targets
15
+
16
+ | Metric | Good | Needs Work | Poor |
17
+ |--------|------|------------|------|
18
+ | LCP (Largest Contentful Paint) | ≤ 2.5s | ≤ 4.0s | > 4.0s |
19
+ | INP (Interaction to Next Paint) | ≤ 200ms | ≤ 500ms | > 500ms |
20
+ | CLS (Cumulative Layout Shift) | ≤ 0.1 | ≤ 0.25 | > 0.25 |
21
+
22
+ ## TTFB Diagnosis
23
+
24
+ When TTFB is slow (> 800ms), check each component in DevTools Network waterfall:
25
+
26
+ - [ ] **DNS resolution** slow → add `<link rel="dns-prefetch">` or `<link rel="preconnect">` for known origins
27
+ - [ ] **TCP/TLS handshake** slow → enable HTTP/2, consider edge deployment, verify keep-alive
28
+ - [ ] **Server processing** slow → profile backend, check slow queries, add caching
29
+
30
+ ## Frontend Checklist
31
+
32
+ ### Images
33
+ - [ ] Images use modern formats (WebP, AVIF)
34
+ - [ ] Images are responsively sized (`srcset` and `sizes`)
35
+ - [ ] Images and `<source>` elements have explicit `width` and `height` (prevents CLS in art direction)
36
+ - [ ] Below-the-fold images use `loading="lazy"` and `decoding="async"`
37
+ - [ ] Hero/LCP images use `fetchpriority="high"` and no lazy loading
38
+
39
+ ### JavaScript
40
+ - [ ] Bundle size under 200KB gzipped (initial load)
41
+ - [ ] Code splitting with dynamic `import()` for routes and heavy features
42
+ - [ ] Tree shaking enabled (verify dependency ships ESM and marks `sideEffects: false`)
43
+ - [ ] No blocking JavaScript in `<head>` (use `defer` or `async`)
44
+ - [ ] Heavy computation offloaded to Web Workers (if applicable)
45
+ - [ ] `React.memo()` on expensive components that re-render with same props
46
+ - [ ] `useMemo()` / `useCallback()` only where profiling shows benefit
47
+ - [ ] Long tasks (> 50ms) broken up to keep the main thread available — main lever for INP
48
+ - [ ] `yieldToMain` pattern used inside long-running loops so input events can run between chunks
49
+ - [ ] Modern scheduling APIs used where available: `scheduler.yield()` (preferred), `scheduler.postTask()` with priorities, `isInputPending()` to yield only when needed
50
+ - [ ] `requestIdleCallback` for deferrable, non-urgent work (analytics flush, prefetch, warmup)
51
+ - [ ] Non-critical work deferred out of event handlers (e.g. analytics, logging) so the response to the interaction is not delayed
52
+ - [ ] Third-party scripts loaded with `async` / `defer`, audited for size, and fronted by a facade when heavy (chat widgets, embeds)
53
+
54
+ ### CSS
55
+ - [ ] Critical CSS inlined or preloaded
56
+ - [ ] No render-blocking CSS for non-critical styles
57
+ - [ ] No CSS-in-JS runtime cost in production (use extraction)
58
+
59
+ ### Fonts
60
+ - [ ] Limited to 2–3 font families, 2–3 weights each (every additional weight is another request)
61
+ - [ ] WOFF2 format only (smallest, universal support — skip WOFF/TTF/EOT)
62
+ - [ ] Self-hosted when possible (third-party font CDNs add DNS + TCP + TLS round-trips)
63
+ - [ ] LCP-critical fonts preloaded: `<link rel="preload" as="font" type="font/woff2" crossorigin>`
64
+ - [ ] `font-display: swap` (or `optional` for non-critical) to avoid FOIT blocking render
65
+ - [ ] Subsetted via `unicode-range` to ship only the glyphs each page needs
66
+ - [ ] Variable fonts considered when multiple weights/styles are required (one file replaces many)
67
+ - [ ] Fallback font metrics adjusted with `size-adjust`, `ascent-override`, `descent-override` to reduce CLS on font swap
68
+ - [ ] System font stack considered before any custom font
69
+
70
+ ### Network
71
+ - [ ] Static assets cached with long `max-age` + content hashing
72
+ - [ ] API responses cached where appropriate (`Cache-Control`)
73
+ - [ ] HTTP/2 or HTTP/3 enabled
74
+ - [ ] Resources preconnected (`<link rel="preconnect">`) for known origins
75
+ - [ ] `fetchpriority` used on critical non-image resources (e.g., key `<link rel="preload">`, above-the-fold `<script>`) — not only on `<img>`
76
+ - [ ] No unnecessary redirects
77
+
78
+ ### Rendering
79
+ - [ ] No layout thrashing (forced synchronous layouts)
80
+ - [ ] Animations use `transform` and `opacity` (GPU-accelerated)
81
+ - [ ] Long lists use virtualization (e.g., `react-window`)
82
+ - [ ] No unnecessary full-page re-renders
83
+ - [ ] Off-screen sections use `content-visibility: auto` with `contain-intrinsic-size` to skip layout/paint of non-visible areas
84
+ - [ ] No `unload` event handlers and no `Cache-Control: no-store` on HTML responses — preserves back/forward cache (bfcache) eligibility
85
+
86
+ ## Backend Checklist
87
+
88
+ ### Database
89
+ - [ ] No N+1 query patterns (use eager loading / joins)
90
+ - [ ] Queries have appropriate indexes
91
+ - [ ] List endpoints paginated (never `SELECT * FROM table`)
92
+ - [ ] Connection pooling configured
93
+ - [ ] Slow query logging enabled
94
+
95
+ ### API
96
+ - [ ] Response times < 200ms (p95)
97
+ - [ ] No synchronous heavy computation in request handlers
98
+ - [ ] Bulk operations instead of loops of individual calls
99
+ - [ ] Response compression (gzip/brotli)
100
+ - [ ] Appropriate caching (in-memory, Redis, CDN)
101
+
102
+ ### Infrastructure
103
+ - [ ] CDN for static assets
104
+ - [ ] Server located close to users (or edge deployment)
105
+ - [ ] Horizontal scaling configured (if needed)
106
+ - [ ] Health check endpoint for load balancer
107
+
108
+ ## Measurement Commands
109
+
110
+ ### INP field data and DevTools workflow
111
+
112
+ 1. **Field data first** — check [CrUX Vis](https://developer.chrome.com/docs/crux/vis) or your RUM tool for real-user INP before optimising
113
+ 2. **Identify slow interactions** — open DevTools → Performance panel → record while interacting; look for long tasks triggered by clicks/keystrokes
114
+ 3. **Test on mid-range Android** — INP issues often only surface on slower hardware; use a real device or DevTools CPU throttling (4×–6× slowdown)
115
+
116
+ ```bash
117
+ # Lighthouse CLI
118
+ npx lighthouse https://localhost:3000 --output json --output-path ./report.json
119
+
120
+ # Bundle analysis
121
+ npx webpack-bundle-analyzer stats.json
122
+ # or for Vite:
123
+ npx vite-bundle-visualizer
124
+
125
+ # Check bundle size
126
+ npx bundlesize
127
+
128
+ # Web Vitals in code
129
+ import { onLCP, onINP, onCLS } from 'web-vitals';
130
+ onLCP(console.log);
131
+ onINP(console.log);
132
+ onCLS(console.log);
133
+
134
+ # INP with interaction-level detail (attribution build)
135
+ import { onINP } from 'web-vitals/attribution';
136
+ onINP(({ value, attribution }) => {
137
+ const { interactionTarget, inputDelay, processingDuration, presentationDelay } = attribution;
138
+ console.log({ value, interactionTarget, inputDelay, processingDuration, presentationDelay });
139
+ });
140
+ ```
141
+
142
+ ## Common Anti-Patterns
143
+
144
+ | Anti-Pattern | Impact | Fix |
145
+ |---|---|---|
146
+ | N+1 queries | Linear DB load growth | Use joins, includes, or batch loading |
147
+ | Unbounded queries | Memory exhaustion, timeouts | Always paginate, add LIMIT |
148
+ | Missing indexes | Slow reads as data grows | Add indexes for filtered/sorted columns |
149
+ | Layout thrashing | Jank, dropped frames | Batch DOM reads, then batch writes |
150
+ | Unoptimized images | Slow LCP, wasted bandwidth | Use WebP, responsive sizes, lazy load |
151
+ | Large bundles | Slow Time to Interactive | Code split, tree shake, audit deps |
152
+ | Blocking main thread | Poor INP, unresponsive UI | Chunk long tasks with `scheduler.yield()` / `yieldToMain`, offload to Web Workers |
153
+ | Memory leaks | Growing memory, eventual crash | Clean up listeners, intervals, refs |
@@ -0,0 +1,134 @@
1
+ # Security Checklist
2
+
3
+ Quick reference for web application security. Use alongside the `security-and-hardening` skill.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Pre-Commit Checks](#pre-commit-checks)
8
+ - [Authentication](#authentication)
9
+ - [Authorization](#authorization)
10
+ - [Input Validation](#input-validation)
11
+ - [Security Headers](#security-headers)
12
+ - [CORS Configuration](#cors-configuration)
13
+ - [Data Protection](#data-protection)
14
+ - [Dependency Security](#dependency-security)
15
+ - [Error Handling](#error-handling)
16
+ - [OWASP Top 10 Quick Reference](#owasp-top-10-quick-reference)
17
+
18
+ ## Pre-Commit Checks
19
+
20
+ - [ ] No secrets in code (`git diff --cached | grep -i "password\|secret\|api_key\|token"`)
21
+ - [ ] `.gitignore` covers: `.env`, `.env.local`, `*.pem`, `*.key`
22
+ - [ ] `.env.example` uses placeholder values (not real secrets)
23
+
24
+ ## Authentication
25
+
26
+ - [ ] Passwords hashed with bcrypt (≥12 rounds), scrypt, or argon2
27
+ - [ ] Session cookies: `httpOnly`, `secure`, `sameSite: 'lax'`
28
+ - [ ] Session expiration configured (reasonable max-age)
29
+ - [ ] Rate limiting on login endpoint (≤10 attempts per 15 minutes)
30
+ - [ ] Password reset tokens: time-limited (≤1 hour), single-use
31
+ - [ ] Account lockout after repeated failures (optional, with notification)
32
+ - [ ] MFA supported for sensitive operations (optional but recommended)
33
+
34
+ ## Authorization
35
+
36
+ - [ ] Every protected endpoint checks authentication
37
+ - [ ] Every resource access checks ownership/role (prevents IDOR)
38
+ - [ ] Admin endpoints require admin role verification
39
+ - [ ] API keys scoped to minimum necessary permissions
40
+ - [ ] JWT tokens validated (signature, expiration, issuer)
41
+
42
+ ## Input Validation
43
+
44
+ - [ ] All user input validated at system boundaries (API routes, form handlers)
45
+ - [ ] Validation uses allowlists (not denylists)
46
+ - [ ] String lengths constrained (min/max)
47
+ - [ ] Numeric ranges validated
48
+ - [ ] Email, URL, and date formats validated with proper libraries
49
+ - [ ] File uploads: type restricted, size limited, content verified
50
+ - [ ] SQL queries parameterized (no string concatenation)
51
+ - [ ] HTML output encoded (use framework auto-escaping)
52
+ - [ ] URLs validated before redirect (prevent open redirect)
53
+
54
+ ## Security Headers
55
+
56
+ ```
57
+ Content-Security-Policy: default-src 'self'; script-src 'self'
58
+ Strict-Transport-Security: max-age=31536000; includeSubDomains
59
+ X-Content-Type-Options: nosniff
60
+ X-Frame-Options: DENY
61
+ X-XSS-Protection: 0 (disabled, rely on CSP)
62
+ Referrer-Policy: strict-origin-when-cross-origin
63
+ Permissions-Policy: camera=(), microphone=(), geolocation=()
64
+ ```
65
+
66
+ ## CORS Configuration
67
+
68
+ ```typescript
69
+ // Restrictive (recommended)
70
+ cors({
71
+ origin: ['https://yourdomain.com', 'https://app.yourdomain.com'],
72
+ credentials: true,
73
+ methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
74
+ allowedHeaders: ['Content-Type', 'Authorization'],
75
+ })
76
+
77
+ // NEVER use in production:
78
+ cors({ origin: '*' }) // Allows any origin
79
+ ```
80
+
81
+ ## Data Protection
82
+
83
+ - [ ] Sensitive fields excluded from API responses (`passwordHash`, `resetToken`, etc.)
84
+ - [ ] Sensitive data not logged (passwords, tokens, full CC numbers)
85
+ - [ ] PII encrypted at rest (if required by regulation)
86
+ - [ ] HTTPS for all external communication
87
+ - [ ] Database backups encrypted
88
+
89
+ ## Dependency Security
90
+
91
+ ```bash
92
+ # Audit dependencies
93
+ npm audit
94
+
95
+ # Fix automatically where possible
96
+ npm audit fix
97
+
98
+ # Check for critical vulnerabilities
99
+ npm audit --audit-level=critical
100
+
101
+ # Keep dependencies updated
102
+ npx npm-check-updates
103
+ ```
104
+
105
+ ## Error Handling
106
+
107
+ ```typescript
108
+ // Production: generic error, no internals
109
+ res.status(500).json({
110
+ error: { code: 'INTERNAL_ERROR', message: 'Something went wrong' }
111
+ });
112
+
113
+ // NEVER in production:
114
+ res.status(500).json({
115
+ error: err.message,
116
+ stack: err.stack, // Exposes internals
117
+ query: err.sql, // Exposes database details
118
+ });
119
+ ```
120
+
121
+ ## OWASP Top 10 Quick Reference
122
+
123
+ | # | Vulnerability | Prevention |
124
+ |---|---|---|
125
+ | 1 | Broken Access Control | Auth checks on every endpoint, ownership verification |
126
+ | 2 | Cryptographic Failures | HTTPS, strong hashing, no secrets in code |
127
+ | 3 | Injection | Parameterized queries, input validation |
128
+ | 4 | Insecure Design | Threat modeling, spec-driven development |
129
+ | 5 | Security Misconfiguration | Security headers, minimal permissions, audit deps |
130
+ | 6 | Vulnerable Components | `npm audit`, keep deps updated, minimal deps |
131
+ | 7 | Auth Failures | Strong passwords, rate limiting, session management |
132
+ | 8 | Data Integrity Failures | Verify updates/dependencies, signed artifacts |
133
+ | 9 | Logging Failures | Log security events, don't log secrets |
134
+ | 10 | SSRF | Validate/allowlist URLs, restrict outbound requests |
@@ -0,0 +1,236 @@
1
+ # Testing Patterns Reference
2
+
3
+ Quick reference for common testing patterns across the stack. Use alongside the `test-driven-development` skill.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Test Structure (Arrange-Act-Assert)](#test-structure-arrange-act-assert)
8
+ - [Test Naming Conventions](#test-naming-conventions)
9
+ - [Common Assertions](#common-assertions)
10
+ - [Mocking Patterns](#mocking-patterns)
11
+ - [React/Component Testing](#reactcomponent-testing)
12
+ - [API / Integration Testing](#api--integration-testing)
13
+ - [E2E Testing (Playwright)](#e2e-testing-playwright)
14
+ - [Test Anti-Patterns](#test-anti-patterns)
15
+
16
+ ## Test Structure (Arrange-Act-Assert)
17
+
18
+ ```typescript
19
+ it('describes expected behavior', () => {
20
+ // Arrange: Set up test data and preconditions
21
+ const input = { title: 'Test Task', priority: 'high' };
22
+
23
+ // Act: Perform the action being tested
24
+ const result = createTask(input);
25
+
26
+ // Assert: Verify the outcome
27
+ expect(result.title).toBe('Test Task');
28
+ expect(result.priority).toBe('high');
29
+ expect(result.status).toBe('pending');
30
+ });
31
+ ```
32
+
33
+ ## Test Naming Conventions
34
+
35
+ ```typescript
36
+ // Pattern: [unit] [expected behavior] [condition]
37
+ describe('TaskService.createTask', () => {
38
+ it('creates a task with default pending status', () => {});
39
+ it('throws ValidationError when title is empty', () => {});
40
+ it('trims whitespace from title', () => {});
41
+ it('generates a unique ID for each task', () => {});
42
+ });
43
+ ```
44
+
45
+ ## Common Assertions
46
+
47
+ ```typescript
48
+ // Equality
49
+ expect(result).toBe(expected); // Strict equality (===)
50
+ expect(result).toEqual(expected); // Deep equality (objects/arrays)
51
+ expect(result).toStrictEqual(expected); // Deep equality + type matching
52
+
53
+ // Truthiness
54
+ expect(result).toBeTruthy();
55
+ expect(result).toBeFalsy();
56
+ expect(result).toBeNull();
57
+ expect(result).toBeDefined();
58
+ expect(result).toBeUndefined();
59
+
60
+ // Numbers
61
+ expect(result).toBeGreaterThan(5);
62
+ expect(result).toBeLessThanOrEqual(10);
63
+ expect(result).toBeCloseTo(0.3, 5); // Floating point
64
+
65
+ // Strings
66
+ expect(result).toMatch(/pattern/);
67
+ expect(result).toContain('substring');
68
+
69
+ // Arrays / Objects
70
+ expect(array).toContain(item);
71
+ expect(array).toHaveLength(3);
72
+ expect(object).toHaveProperty('key', 'value');
73
+
74
+ // Errors
75
+ expect(() => fn()).toThrow();
76
+ expect(() => fn()).toThrow(ValidationError);
77
+ expect(() => fn()).toThrow('specific message');
78
+
79
+ // Async
80
+ await expect(asyncFn()).resolves.toBe(value);
81
+ await expect(asyncFn()).rejects.toThrow(Error);
82
+ ```
83
+
84
+ ## Mocking Patterns
85
+
86
+ ### Mock Functions
87
+
88
+ ```typescript
89
+ const mockFn = jest.fn();
90
+ mockFn.mockReturnValue(42);
91
+ mockFn.mockResolvedValue({ data: 'test' });
92
+ mockFn.mockImplementation((x) => x * 2);
93
+
94
+ expect(mockFn).toHaveBeenCalled();
95
+ expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
96
+ expect(mockFn).toHaveBeenCalledTimes(3);
97
+ ```
98
+
99
+ ### Mock Modules
100
+
101
+ ```typescript
102
+ // Mock an entire module
103
+ jest.mock('./database', () => ({
104
+ query: jest.fn().mockResolvedValue([{ id: 1, title: 'Test' }]),
105
+ }));
106
+
107
+ // Mock specific exports
108
+ jest.mock('./utils', () => ({
109
+ ...jest.requireActual('./utils'),
110
+ generateId: jest.fn().mockReturnValue('test-id'),
111
+ }));
112
+ ```
113
+
114
+ ### Mock at Boundaries Only
115
+
116
+ ```
117
+ Mock these: Don't mock these:
118
+ ├── Database calls ├── Internal utility functions
119
+ ├── HTTP requests ├── Business logic
120
+ ├── File system operations ├── Data transformations
121
+ ├── External API calls ├── Validation functions
122
+ └── Time/Date (when needed) └── Pure functions
123
+ ```
124
+
125
+ ## React/Component Testing
126
+
127
+ ```tsx
128
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
129
+
130
+ describe('TaskForm', () => {
131
+ it('submits the form with entered data', async () => {
132
+ const onSubmit = jest.fn();
133
+ render(<TaskForm onSubmit={onSubmit} />);
134
+
135
+ // Find elements by accessible role/label (not test IDs)
136
+ await screen.findByRole('textbox', { name: /title/i });
137
+ fireEvent.change(screen.getByRole('textbox', { name: /title/i }), {
138
+ target: { value: 'New Task' },
139
+ });
140
+ fireEvent.click(screen.getByRole('button', { name: /create/i }));
141
+
142
+ await waitFor(() => {
143
+ expect(onSubmit).toHaveBeenCalledWith({ title: 'New Task' });
144
+ });
145
+ });
146
+
147
+ it('shows validation error for empty title', async () => {
148
+ render(<TaskForm onSubmit={jest.fn()} />);
149
+
150
+ fireEvent.click(screen.getByRole('button', { name: /create/i }));
151
+
152
+ expect(await screen.findByText(/title is required/i)).toBeInTheDocument();
153
+ });
154
+ });
155
+ ```
156
+
157
+ ## API / Integration Testing
158
+
159
+ ```typescript
160
+ import request from 'supertest';
161
+ import { app } from '../src/app';
162
+
163
+ describe('POST /api/tasks', () => {
164
+ it('creates a task and returns 201', async () => {
165
+ const response = await request(app)
166
+ .post('/api/tasks')
167
+ .send({ title: 'Test Task' })
168
+ .set('Authorization', `Bearer ${testToken}`)
169
+ .expect(201);
170
+
171
+ expect(response.body).toMatchObject({
172
+ id: expect.any(String),
173
+ title: 'Test Task',
174
+ status: 'pending',
175
+ });
176
+ });
177
+
178
+ it('returns 422 for invalid input', async () => {
179
+ const response = await request(app)
180
+ .post('/api/tasks')
181
+ .send({ title: '' })
182
+ .set('Authorization', `Bearer ${testToken}`)
183
+ .expect(422);
184
+
185
+ expect(response.body.error.code).toBe('VALIDATION_ERROR');
186
+ });
187
+
188
+ it('returns 401 without authentication', async () => {
189
+ await request(app)
190
+ .post('/api/tasks')
191
+ .send({ title: 'Test' })
192
+ .expect(401);
193
+ });
194
+ });
195
+ ```
196
+
197
+ ## E2E Testing (Playwright)
198
+
199
+ ```typescript
200
+ import { test, expect } from '@playwright/test';
201
+
202
+ test('user can create and complete a task', async ({ page }) => {
203
+ // Navigate and authenticate
204
+ await page.goto('/');
205
+ await page.fill('[name="email"]', 'test@example.com');
206
+ await page.fill('[name="password"]', 'testpass123');
207
+ await page.click('button:has-text("Log in")');
208
+
209
+ // Create a task
210
+ await page.click('button:has-text("New Task")');
211
+ await page.fill('[name="title"]', 'Buy groceries');
212
+ await page.click('button:has-text("Create")');
213
+
214
+ // Verify task appears
215
+ await expect(page.locator('text=Buy groceries')).toBeVisible();
216
+
217
+ // Complete the task
218
+ await page.click('[aria-label="Complete Buy groceries"]');
219
+ await expect(page.locator('text=Buy groceries')).toHaveCSS(
220
+ 'text-decoration-line', 'line-through'
221
+ );
222
+ });
223
+ ```
224
+
225
+ ## Test Anti-Patterns
226
+
227
+ | Anti-Pattern | Problem | Better Approach |
228
+ |---|---|---|
229
+ | Testing implementation details | Breaks on refactor | Test inputs/outputs |
230
+ | Snapshot everything | No one reviews snapshot diffs | Assert specific values |
231
+ | Shared mutable state | Tests pollute each other | Setup/teardown per test |
232
+ | Testing third-party code | Wastes time, not your bug | Mock the boundary |
233
+ | Skipping tests to pass CI | Hides real bugs | Fix or delete the test |
234
+ | Using `test.skip` permanently | Dead code | Remove or fix it |
235
+ | Overly broad assertions | Doesn't catch regressions | Be specific |
236
+ | No async error handling | Swallowed errors, false passes | Always `await` async tests |