@girardmedia/bootspring 1.1.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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +255 -0
  3. package/agents/README.md +93 -0
  4. package/agents/api-expert/context.md +416 -0
  5. package/agents/architecture-expert/context.md +454 -0
  6. package/agents/backend-expert/context.md +483 -0
  7. package/agents/code-review-expert/context.md +365 -0
  8. package/agents/database-expert/context.md +250 -0
  9. package/agents/devops-expert/context.md +446 -0
  10. package/agents/frontend-expert/context.md +364 -0
  11. package/agents/index.js +140 -0
  12. package/agents/performance-expert/context.md +377 -0
  13. package/agents/security-expert/context.md +343 -0
  14. package/agents/testing-expert/context.md +414 -0
  15. package/agents/ui-ux-expert/context.md +448 -0
  16. package/agents/vercel-expert/context.md +426 -0
  17. package/bin/bootspring.js +310 -0
  18. package/cli/agent.js +337 -0
  19. package/cli/context.js +194 -0
  20. package/cli/dashboard.js +150 -0
  21. package/cli/generate.js +294 -0
  22. package/cli/init.js +410 -0
  23. package/cli/loop.js +421 -0
  24. package/cli/mcp.js +241 -0
  25. package/cli/memory.js +303 -0
  26. package/cli/orchestrator.js +400 -0
  27. package/cli/plugin.js +451 -0
  28. package/cli/quality.js +332 -0
  29. package/cli/skill.js +369 -0
  30. package/cli/task.js +628 -0
  31. package/cli/telemetry.js +114 -0
  32. package/cli/todo.js +614 -0
  33. package/cli/update.js +312 -0
  34. package/core/config.js +245 -0
  35. package/core/context.js +329 -0
  36. package/core/entitlements.js +209 -0
  37. package/core/index.js +43 -0
  38. package/core/policies.js +68 -0
  39. package/core/telemetry.js +247 -0
  40. package/core/utils.js +380 -0
  41. package/dashboard/server.js +818 -0
  42. package/docs/integrations/claude-code.md +42 -0
  43. package/docs/integrations/codex.md +42 -0
  44. package/docs/mcp-api-platform.md +102 -0
  45. package/generators/generate.js +598 -0
  46. package/generators/index.js +18 -0
  47. package/hooks/context-detector.js +177 -0
  48. package/hooks/index.js +35 -0
  49. package/hooks/prompt-enhancer.js +289 -0
  50. package/intelligence/git-memory.js +551 -0
  51. package/intelligence/index.js +59 -0
  52. package/intelligence/orchestrator.js +964 -0
  53. package/intelligence/prd.js +447 -0
  54. package/intelligence/recommendation-weights.json +18 -0
  55. package/intelligence/recommendations.js +234 -0
  56. package/mcp/capabilities.js +71 -0
  57. package/mcp/contracts/mcp-contract.v1.json +497 -0
  58. package/mcp/registry.js +213 -0
  59. package/mcp/response-formatter.js +462 -0
  60. package/mcp/server.js +99 -0
  61. package/mcp/tools/agent-tool.js +137 -0
  62. package/mcp/tools/capabilities-tool.js +54 -0
  63. package/mcp/tools/context-tool.js +49 -0
  64. package/mcp/tools/dashboard-tool.js +58 -0
  65. package/mcp/tools/generate-tool.js +46 -0
  66. package/mcp/tools/loop-tool.js +134 -0
  67. package/mcp/tools/memory-tool.js +180 -0
  68. package/mcp/tools/orchestrator-tool.js +232 -0
  69. package/mcp/tools/plugin-tool.js +76 -0
  70. package/mcp/tools/quality-tool.js +47 -0
  71. package/mcp/tools/skill-tool.js +233 -0
  72. package/mcp/tools/telemetry-tool.js +95 -0
  73. package/mcp/tools/todo-tool.js +133 -0
  74. package/package.json +98 -0
  75. package/plugins/index.js +141 -0
  76. package/quality/index.js +380 -0
  77. package/quality/lint-budgets.json +19 -0
  78. package/skills/index.js +787 -0
  79. package/skills/patterns/README.md +163 -0
  80. package/skills/patterns/api/route-handler.md +217 -0
  81. package/skills/patterns/api/server-action.md +249 -0
  82. package/skills/patterns/auth/clerk.md +132 -0
  83. package/skills/patterns/database/prisma.md +180 -0
  84. package/skills/patterns/payments/stripe.md +272 -0
  85. package/skills/patterns/security/validation.md +268 -0
  86. package/skills/patterns/testing/vitest.md +307 -0
  87. package/templates/bootspring.config.js +83 -0
  88. package/templates/mcp.json +9 -0
@@ -0,0 +1,414 @@
1
+ # Testing Expert Agent
2
+
3
+ ## Role
4
+ Specialized in testing strategies, test frameworks (Vitest, Jest, Playwright), test patterns, and ensuring code quality through comprehensive test coverage.
5
+
6
+ ## Core Expertise
7
+
8
+ ### Vitest Setup & Configuration
9
+
10
+ ```typescript
11
+ // vitest.config.ts
12
+ import { defineConfig } from 'vitest/config';
13
+ import react from '@vitejs/plugin-react';
14
+ import tsconfigPaths from 'vite-tsconfig-paths';
15
+
16
+ export default defineConfig({
17
+ plugins: [react(), tsconfigPaths()],
18
+ test: {
19
+ environment: 'jsdom',
20
+ globals: true,
21
+ setupFiles: ['./tests/setup.ts'],
22
+ include: ['**/*.{test,spec}.{ts,tsx}'],
23
+ coverage: {
24
+ provider: 'v8',
25
+ reporter: ['text', 'json', 'html'],
26
+ exclude: ['node_modules', 'tests/setup.ts'],
27
+ },
28
+ },
29
+ });
30
+
31
+ // tests/setup.ts
32
+ import '@testing-library/jest-dom';
33
+ import { vi } from 'vitest';
34
+
35
+ // Mock Next.js router
36
+ vi.mock('next/navigation', () => ({
37
+ useRouter: () => ({
38
+ push: vi.fn(),
39
+ replace: vi.fn(),
40
+ back: vi.fn(),
41
+ }),
42
+ usePathname: () => '/',
43
+ useSearchParams: () => new URLSearchParams(),
44
+ }));
45
+ ```
46
+
47
+ ### Unit Testing
48
+
49
+ ```typescript
50
+ // lib/utils.test.ts
51
+ import { describe, it, expect, vi } from 'vitest';
52
+ import { formatCurrency, calculateDiscount, validateEmail } from './utils';
53
+
54
+ describe('formatCurrency', () => {
55
+ it('formats positive numbers correctly', () => {
56
+ expect(formatCurrency(1234.56)).toBe('$1,234.56');
57
+ });
58
+
59
+ it('handles zero', () => {
60
+ expect(formatCurrency(0)).toBe('$0.00');
61
+ });
62
+
63
+ it('formats negative numbers', () => {
64
+ expect(formatCurrency(-50)).toBe('-$50.00');
65
+ });
66
+ });
67
+
68
+ describe('calculateDiscount', () => {
69
+ it('applies percentage discount', () => {
70
+ expect(calculateDiscount(100, { type: 'percentage', value: 20 })).toBe(80);
71
+ });
72
+
73
+ it('applies fixed discount', () => {
74
+ expect(calculateDiscount(100, { type: 'fixed', value: 15 })).toBe(85);
75
+ });
76
+
77
+ it('does not go below zero', () => {
78
+ expect(calculateDiscount(10, { type: 'fixed', value: 20 })).toBe(0);
79
+ });
80
+ });
81
+
82
+ describe('validateEmail', () => {
83
+ it.each([
84
+ ['user@example.com', true],
85
+ ['user.name+tag@example.co.uk', true],
86
+ ['invalid', false],
87
+ ['missing@domain', false],
88
+ ['@nodomain.com', false],
89
+ ])('validates %s as %s', (email, expected) => {
90
+ expect(validateEmail(email)).toBe(expected);
91
+ });
92
+ });
93
+ ```
94
+
95
+ ### Component Testing
96
+
97
+ ```typescript
98
+ // components/Button.test.tsx
99
+ import { render, screen, fireEvent } from '@testing-library/react';
100
+ import userEvent from '@testing-library/user-event';
101
+ import { describe, it, expect, vi } from 'vitest';
102
+ import { Button } from './Button';
103
+
104
+ describe('Button', () => {
105
+ it('renders with children', () => {
106
+ render(<Button>Click me</Button>);
107
+ expect(screen.getByRole('button')).toHaveTextContent('Click me');
108
+ });
109
+
110
+ it('calls onClick when clicked', async () => {
111
+ const handleClick = vi.fn();
112
+ const user = userEvent.setup();
113
+
114
+ render(<Button onClick={handleClick}>Click me</Button>);
115
+ await user.click(screen.getByRole('button'));
116
+
117
+ expect(handleClick).toHaveBeenCalledOnce();
118
+ });
119
+
120
+ it('is disabled when loading', () => {
121
+ render(<Button isLoading>Submit</Button>);
122
+ expect(screen.getByRole('button')).toBeDisabled();
123
+ });
124
+
125
+ it('shows loading spinner when loading', () => {
126
+ render(<Button isLoading>Submit</Button>);
127
+ expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
128
+ });
129
+
130
+ it('applies variant classes', () => {
131
+ render(<Button variant="destructive">Delete</Button>);
132
+ expect(screen.getByRole('button')).toHaveClass('bg-destructive');
133
+ });
134
+ });
135
+ ```
136
+
137
+ ### Form Testing
138
+
139
+ ```typescript
140
+ // components/LoginForm.test.tsx
141
+ import { render, screen, waitFor } from '@testing-library/react';
142
+ import userEvent from '@testing-library/user-event';
143
+ import { describe, it, expect, vi } from 'vitest';
144
+ import { LoginForm } from './LoginForm';
145
+
146
+ describe('LoginForm', () => {
147
+ const mockOnSubmit = vi.fn();
148
+
149
+ beforeEach(() => {
150
+ mockOnSubmit.mockClear();
151
+ });
152
+
153
+ it('submits with valid data', async () => {
154
+ const user = userEvent.setup();
155
+ render(<LoginForm onSubmit={mockOnSubmit} />);
156
+
157
+ await user.type(screen.getByLabelText(/email/i), 'user@example.com');
158
+ await user.type(screen.getByLabelText(/password/i), 'password123');
159
+ await user.click(screen.getByRole('button', { name: /sign in/i }));
160
+
161
+ await waitFor(() => {
162
+ expect(mockOnSubmit).toHaveBeenCalledWith({
163
+ email: 'user@example.com',
164
+ password: 'password123',
165
+ });
166
+ });
167
+ });
168
+
169
+ it('shows validation errors for empty fields', async () => {
170
+ const user = userEvent.setup();
171
+ render(<LoginForm onSubmit={mockOnSubmit} />);
172
+
173
+ await user.click(screen.getByRole('button', { name: /sign in/i }));
174
+
175
+ await waitFor(() => {
176
+ expect(screen.getByText(/email is required/i)).toBeInTheDocument();
177
+ expect(screen.getByText(/password is required/i)).toBeInTheDocument();
178
+ });
179
+
180
+ expect(mockOnSubmit).not.toHaveBeenCalled();
181
+ });
182
+
183
+ it('shows error for invalid email', async () => {
184
+ const user = userEvent.setup();
185
+ render(<LoginForm onSubmit={mockOnSubmit} />);
186
+
187
+ await user.type(screen.getByLabelText(/email/i), 'invalid-email');
188
+ await user.type(screen.getByLabelText(/password/i), 'password123');
189
+ await user.click(screen.getByRole('button', { name: /sign in/i }));
190
+
191
+ await waitFor(() => {
192
+ expect(screen.getByText(/invalid email/i)).toBeInTheDocument();
193
+ });
194
+ });
195
+ });
196
+ ```
197
+
198
+ ### Mocking
199
+
200
+ ```typescript
201
+ // Mocking modules
202
+ vi.mock('@/lib/prisma', () => ({
203
+ prisma: {
204
+ user: {
205
+ findUnique: vi.fn(),
206
+ create: vi.fn(),
207
+ update: vi.fn(),
208
+ },
209
+ },
210
+ }));
211
+
212
+ // Mocking fetch
213
+ beforeEach(() => {
214
+ global.fetch = vi.fn();
215
+ });
216
+
217
+ it('fetches user data', async () => {
218
+ (global.fetch as Mock).mockResolvedValueOnce({
219
+ ok: true,
220
+ json: async () => ({ id: '1', name: 'Test User' }),
221
+ });
222
+
223
+ const user = await fetchUser('1');
224
+ expect(user.name).toBe('Test User');
225
+ });
226
+
227
+ // Mocking timers
228
+ it('debounces search', async () => {
229
+ vi.useFakeTimers();
230
+ const onSearch = vi.fn();
231
+ const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
232
+
233
+ render(<SearchInput onSearch={onSearch} debounceMs={300} />);
234
+
235
+ await user.type(screen.getByRole('textbox'), 'test');
236
+
237
+ expect(onSearch).not.toHaveBeenCalled();
238
+
239
+ vi.advanceTimersByTime(300);
240
+
241
+ expect(onSearch).toHaveBeenCalledWith('test');
242
+
243
+ vi.useRealTimers();
244
+ });
245
+ ```
246
+
247
+ ### API Route Testing
248
+
249
+ ```typescript
250
+ // app/api/users/route.test.ts
251
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
252
+ import { GET, POST } from './route';
253
+ import { prisma } from '@/lib/prisma';
254
+
255
+ vi.mock('@/lib/prisma');
256
+ vi.mock('@clerk/nextjs/server', () => ({
257
+ auth: vi.fn(() => ({ userId: 'user_123' })),
258
+ }));
259
+
260
+ describe('GET /api/users', () => {
261
+ beforeEach(() => {
262
+ vi.clearAllMocks();
263
+ });
264
+
265
+ it('returns paginated users', async () => {
266
+ const mockUsers = [
267
+ { id: '1', name: 'User 1' },
268
+ { id: '2', name: 'User 2' },
269
+ ];
270
+
271
+ (prisma.user.findMany as Mock).mockResolvedValue(mockUsers);
272
+ (prisma.user.count as Mock).mockResolvedValue(2);
273
+
274
+ const request = new Request('http://localhost/api/users?page=1&limit=10');
275
+ const response = await GET(request);
276
+ const data = await response.json();
277
+
278
+ expect(data.data).toHaveLength(2);
279
+ expect(data.pagination.total).toBe(2);
280
+ });
281
+ });
282
+
283
+ describe('POST /api/users', () => {
284
+ it('creates a user with valid data', async () => {
285
+ const newUser = { id: '1', email: 'new@example.com', name: 'New User' };
286
+ (prisma.user.create as Mock).mockResolvedValue(newUser);
287
+
288
+ const request = new Request('http://localhost/api/users', {
289
+ method: 'POST',
290
+ headers: { 'Content-Type': 'application/json' },
291
+ body: JSON.stringify({ email: 'new@example.com', name: 'New User' }),
292
+ });
293
+
294
+ const response = await POST(request);
295
+ expect(response.status).toBe(201);
296
+
297
+ const data = await response.json();
298
+ expect(data.email).toBe('new@example.com');
299
+ });
300
+
301
+ it('returns 400 for invalid data', async () => {
302
+ const request = new Request('http://localhost/api/users', {
303
+ method: 'POST',
304
+ headers: { 'Content-Type': 'application/json' },
305
+ body: JSON.stringify({ email: 'invalid' }),
306
+ });
307
+
308
+ const response = await POST(request);
309
+ expect(response.status).toBe(400);
310
+ });
311
+ });
312
+ ```
313
+
314
+ ### E2E Testing with Playwright
315
+
316
+ ```typescript
317
+ // e2e/auth.spec.ts
318
+ import { test, expect } from '@playwright/test';
319
+
320
+ test.describe('Authentication', () => {
321
+ test('user can sign in', async ({ page }) => {
322
+ await page.goto('/sign-in');
323
+
324
+ await page.fill('[name="email"]', 'test@example.com');
325
+ await page.fill('[name="password"]', 'password123');
326
+ await page.click('button[type="submit"]');
327
+
328
+ await expect(page).toHaveURL('/dashboard');
329
+ await expect(page.locator('text=Welcome')).toBeVisible();
330
+ });
331
+
332
+ test('shows error for invalid credentials', async ({ page }) => {
333
+ await page.goto('/sign-in');
334
+
335
+ await page.fill('[name="email"]', 'test@example.com');
336
+ await page.fill('[name="password"]', 'wrongpassword');
337
+ await page.click('button[type="submit"]');
338
+
339
+ await expect(page.locator('text=Invalid credentials')).toBeVisible();
340
+ });
341
+
342
+ test('redirects unauthenticated users', async ({ page }) => {
343
+ await page.goto('/dashboard');
344
+ await expect(page).toHaveURL('/sign-in');
345
+ });
346
+ });
347
+
348
+ // e2e/checkout.spec.ts
349
+ test.describe('Checkout', () => {
350
+ test.beforeEach(async ({ page }) => {
351
+ // Login before each test
352
+ await page.goto('/sign-in');
353
+ await page.fill('[name="email"]', 'test@example.com');
354
+ await page.fill('[name="password"]', 'password123');
355
+ await page.click('button[type="submit"]');
356
+ await page.waitForURL('/dashboard');
357
+ });
358
+
359
+ test('completes checkout flow', async ({ page }) => {
360
+ // Add item to cart
361
+ await page.goto('/products');
362
+ await page.click('text=Add to Cart');
363
+
364
+ // Go to checkout
365
+ await page.click('text=Checkout');
366
+ await expect(page).toHaveURL('/checkout');
367
+
368
+ // Fill shipping info
369
+ await page.fill('[name="address"]', '123 Test St');
370
+ await page.fill('[name="city"]', 'Test City');
371
+ await page.fill('[name="zip"]', '12345');
372
+
373
+ // Complete order
374
+ await page.click('text=Place Order');
375
+
376
+ await expect(page.locator('text=Order Confirmed')).toBeVisible();
377
+ });
378
+ });
379
+ ```
380
+
381
+ ### Test Coverage
382
+
383
+ ```bash
384
+ # Run tests with coverage
385
+ npm run test:coverage
386
+
387
+ # Coverage thresholds in vitest.config.ts
388
+ coverage: {
389
+ thresholds: {
390
+ global: {
391
+ branches: 80,
392
+ functions: 80,
393
+ lines: 80,
394
+ statements: 80,
395
+ },
396
+ },
397
+ }
398
+ ```
399
+
400
+ ## Testing Checklist
401
+
402
+ - [ ] Unit tests for utility functions
403
+ - [ ] Component tests for UI components
404
+ - [ ] Integration tests for forms
405
+ - [ ] API route tests
406
+ - [ ] E2E tests for critical paths
407
+ - [ ] Mocks properly isolated
408
+ - [ ] Tests are independent
409
+ - [ ] Coverage meets thresholds
410
+ - [ ] CI runs tests on every PR
411
+ - [ ] Flaky tests addressed
412
+
413
+ ## Trigger Keywords
414
+ test, spec, vitest, jest, playwright, coverage, mock, unit test, integration test, e2e, testing library, expect, describe, it, assertion, fixture