@ekkos/cli 0.2.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 (135) hide show
  1. package/dist/cache/LocalSessionStore.d.ts +129 -0
  2. package/dist/cache/LocalSessionStore.js +688 -0
  3. package/dist/cache/capture.d.ts +26 -0
  4. package/dist/cache/capture.js +461 -0
  5. package/dist/cache/index.d.ts +7 -0
  6. package/dist/cache/index.js +23 -0
  7. package/dist/cache/types.d.ts +147 -0
  8. package/dist/cache/types.js +40 -0
  9. package/dist/commands/init.d.ts +9 -0
  10. package/dist/commands/init.js +478 -0
  11. package/dist/commands/run.d.ts +12 -0
  12. package/dist/commands/run.js +829 -0
  13. package/dist/commands/setup.d.ts +6 -0
  14. package/dist/commands/setup.js +658 -0
  15. package/dist/commands/status.d.ts +1 -0
  16. package/dist/commands/status.js +109 -0
  17. package/dist/commands/test.d.ts +1 -0
  18. package/dist/commands/test.js +157 -0
  19. package/dist/deploy/agents.d.ts +15 -0
  20. package/dist/deploy/agents.js +72 -0
  21. package/dist/deploy/hooks.d.ts +16 -0
  22. package/dist/deploy/hooks.js +121 -0
  23. package/dist/deploy/index.d.ts +7 -0
  24. package/dist/deploy/index.js +24 -0
  25. package/dist/deploy/instructions.d.ts +12 -0
  26. package/dist/deploy/instructions.js +36 -0
  27. package/dist/deploy/mcp.d.ts +19 -0
  28. package/dist/deploy/mcp.js +109 -0
  29. package/dist/deploy/plugins.d.ts +19 -0
  30. package/dist/deploy/plugins.js +62 -0
  31. package/dist/deploy/settings.d.ts +8 -0
  32. package/dist/deploy/settings.js +84 -0
  33. package/dist/deploy/skills.d.ts +19 -0
  34. package/dist/deploy/skills.js +60 -0
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.js +71 -0
  37. package/dist/restore/RestoreOrchestrator.d.ts +48 -0
  38. package/dist/restore/RestoreOrchestrator.js +481 -0
  39. package/dist/restore/index.d.ts +4 -0
  40. package/dist/restore/index.js +20 -0
  41. package/dist/utils/platform.d.ts +29 -0
  42. package/dist/utils/platform.js +65 -0
  43. package/dist/utils/session-words.json +119 -0
  44. package/dist/utils/state.d.ts +57 -0
  45. package/dist/utils/state.js +186 -0
  46. package/dist/utils/templates.d.ts +24 -0
  47. package/dist/utils/templates.js +118 -0
  48. package/package.json +48 -0
  49. package/templates/CLAUDE.md +287 -0
  50. package/templates/README.md +378 -0
  51. package/templates/agents/README.md +182 -0
  52. package/templates/agents/code-reviewer.md +166 -0
  53. package/templates/agents/debug-detective.md +169 -0
  54. package/templates/agents/ekkOS_Vercel.md +99 -0
  55. package/templates/agents/extension-manager.md +229 -0
  56. package/templates/agents/git-companion.md +185 -0
  57. package/templates/agents/github-test-agent.md +321 -0
  58. package/templates/agents/railway-manager.md +179 -0
  59. package/templates/claude-plugins/PHASE2_COMPLETION.md +346 -0
  60. package/templates/claude-plugins/PLUGIN_PROPOSALS.md +1776 -0
  61. package/templates/claude-plugins/README.md +587 -0
  62. package/templates/claude-plugins/agents/code-reviewer.json +14 -0
  63. package/templates/claude-plugins/agents/debug-detective.json +15 -0
  64. package/templates/claude-plugins/agents/git-companion.json +14 -0
  65. package/templates/claude-plugins/blog-manager/.claude-plugin/plugin.json +8 -0
  66. package/templates/claude-plugins/blog-manager/commands/blog.md +691 -0
  67. package/templates/claude-plugins/golden-loop-monitor/.claude-plugin/plugin.json +8 -0
  68. package/templates/claude-plugins/golden-loop-monitor/commands/loop-status.md +434 -0
  69. package/templates/claude-plugins/learning-tracker/.claude-plugin/plugin.json +8 -0
  70. package/templates/claude-plugins/learning-tracker/commands/my-patterns.md +282 -0
  71. package/templates/claude-plugins/memory-lens/.claude-plugin/plugin.json +8 -0
  72. package/templates/claude-plugins/memory-lens/commands/memory-search.md +181 -0
  73. package/templates/claude-plugins/pattern-coach/.claude-plugin/plugin.json +8 -0
  74. package/templates/claude-plugins/pattern-coach/commands/forge.md +365 -0
  75. package/templates/claude-plugins/project-schema-validator/.claude-plugin/plugin.json +8 -0
  76. package/templates/claude-plugins/project-schema-validator/commands/validate-schema.md +582 -0
  77. package/templates/claude-plugins-admin/AGENT_TEAM_PROPOSALS.md +819 -0
  78. package/templates/claude-plugins-admin/README.md +446 -0
  79. package/templates/claude-plugins-admin/autonomous-admin-agent/.claude-plugin/plugin.json +8 -0
  80. package/templates/claude-plugins-admin/autonomous-admin-agent/commands/agent.md +595 -0
  81. package/templates/claude-plugins-admin/backend-agent/.claude-plugin/plugin.json +8 -0
  82. package/templates/claude-plugins-admin/backend-agent/commands/backend.md +798 -0
  83. package/templates/claude-plugins-admin/deploy-guardian/.claude-plugin/plugin.json +8 -0
  84. package/templates/claude-plugins-admin/deploy-guardian/commands/deploy.md +554 -0
  85. package/templates/claude-plugins-admin/frontend-agent/.claude-plugin/plugin.json +8 -0
  86. package/templates/claude-plugins-admin/frontend-agent/commands/frontend.md +881 -0
  87. package/templates/claude-plugins-admin/mcp-server-manager/.claude-plugin/plugin.json +8 -0
  88. package/templates/claude-plugins-admin/mcp-server-manager/commands/mcp.md +85 -0
  89. package/templates/claude-plugins-admin/memory-system-monitor/.claude-plugin/plugin.json +8 -0
  90. package/templates/claude-plugins-admin/memory-system-monitor/commands/memory-health.md +569 -0
  91. package/templates/claude-plugins-admin/qa-agent/.claude-plugin/plugin.json +8 -0
  92. package/templates/claude-plugins-admin/qa-agent/commands/qa.md +863 -0
  93. package/templates/claude-plugins-admin/tech-lead-agent/.claude-plugin/plugin.json +8 -0
  94. package/templates/claude-plugins-admin/tech-lead-agent/commands/lead.md +732 -0
  95. package/templates/commands/continue.md +47 -0
  96. package/templates/cursor-hooks/after-agent-response.sh +117 -0
  97. package/templates/cursor-hooks/before-submit-prompt.sh +419 -0
  98. package/templates/cursor-hooks/hooks.json +20 -0
  99. package/templates/cursor-hooks/lib/contract.sh +320 -0
  100. package/templates/cursor-hooks/stop.sh +75 -0
  101. package/templates/cursor-rules/ekkos-memory.md +187 -0
  102. package/templates/hooks/assistant-response.sh +96 -0
  103. package/templates/hooks/hooks.json +28 -0
  104. package/templates/hooks/lib/contract.sh +320 -0
  105. package/templates/hooks/lib/state.sh +158 -0
  106. package/templates/hooks/session-start.ps1 +41 -0
  107. package/templates/hooks/session-start.sh +318 -0
  108. package/templates/hooks/stop.ps1 +16 -0
  109. package/templates/hooks/stop.sh +989 -0
  110. package/templates/hooks/user-prompt-submit.ps1 +174 -0
  111. package/templates/hooks/user-prompt-submit.sh +587 -0
  112. package/templates/hooks-node/lib/state.js +187 -0
  113. package/templates/hooks-node/stop.js +416 -0
  114. package/templates/hooks-node/user-prompt-submit.js +337 -0
  115. package/templates/plan-template.md +306 -0
  116. package/templates/rules/00-hooks-contract.mdc +89 -0
  117. package/templates/rules/30-ekkos-core.mdc +188 -0
  118. package/templates/rules/31-ekkos-messages.mdc +78 -0
  119. package/templates/skills/continue/SKILL.md +169 -0
  120. package/templates/skills/ekkOS_Deep_Recall/Skill.md +282 -0
  121. package/templates/skills/ekkOS_Learn/Skill.md +265 -0
  122. package/templates/skills/ekkOS_Memory_First/Skill.md +206 -0
  123. package/templates/skills/ekkOS_Plan_Assist/Skill.md +302 -0
  124. package/templates/skills/ekkOS_Preferences/Skill.md +247 -0
  125. package/templates/skills/ekkOS_Reflect/Skill.md +257 -0
  126. package/templates/skills/ekkOS_Safety/Skill.md +265 -0
  127. package/templates/skills/ekkOS_Schema/Skill.md +251 -0
  128. package/templates/skills/ekkOS_Summary/Skill.md +257 -0
  129. package/templates/skills/ekkOS_Vault/Skill.md +287 -0
  130. package/templates/skills/permissions/Skill.md +322 -0
  131. package/templates/spec-template.md +159 -0
  132. package/templates/windsurf-hooks/before-submit-prompt.sh +238 -0
  133. package/templates/windsurf-hooks/hooks.json +10 -0
  134. package/templates/windsurf-hooks/lib/contract.sh +320 -0
  135. package/templates/windsurf-rules/ekkos-memory.md +129 -0
@@ -0,0 +1,863 @@
1
+ # QA Agent
2
+
3
+ **ADMIN ONLY** - AI QA Engineer specialized in testing, quality assurance, test automation, coverage analysis, and ensuring production-ready code quality.
4
+
5
+ ## Overview
6
+
7
+ The QA Agent is your testing expert. It:
8
+ - Writes comprehensive test suites (unit, integration, E2E)
9
+ - Ensures high test coverage (90%+ target)
10
+ - Tests edge cases and error conditions
11
+ - Performs regression testing
12
+ - Reviews code quality and identifies bugs
13
+ - Automates testing workflows
14
+
15
+ ## Commands
16
+
17
+ ### `/qa write`
18
+
19
+ Write tests for a feature, component, or API.
20
+
21
+ ```bash
22
+ /qa write "Test target description"
23
+
24
+ # Examples
25
+ /qa write "Teams API endpoints"
26
+ /qa write "Teams dashboard component"
27
+ /qa write "Pattern search functionality"
28
+ ```
29
+
30
+ **What happens:**
31
+
32
+ ```
33
+ 🧪 QA Agent: Writing Tests for "Teams API"
34
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
35
+
36
+ 📊 Test Planning
37
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
38
+
39
+ Analyzing target...
40
+ ✓ Found 5 API endpoints in apps/web/app/api/teams/
41
+ ✓ Checked input validation schemas
42
+ ✓ Reviewed RLS policies
43
+ ✓ Identified edge cases
44
+
45
+ Test Types:
46
+ • Unit tests (validation, utilities)
47
+ • Integration tests (API endpoints)
48
+ • E2E tests (user workflows)
49
+
50
+ Test Cases: 28 total
51
+ • Happy path: 8
52
+ • Error handling: 12
53
+ • Edge cases: 6
54
+ • Security: 2
55
+
56
+ Coverage Goal: 95%
57
+
58
+ Stack:
59
+ • Vitest for unit/integration tests
60
+ • Playwright for E2E tests
61
+ • Testing Library for React components
62
+ • Supertest for API testing
63
+
64
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
65
+ 📝 Writing Tests
66
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
67
+
68
+ Creating apps/web/app/api/teams/__tests__/route.test.ts...
69
+
70
+ ```typescript
71
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
72
+ import { NextRequest } from 'next/server';
73
+ import { POST, GET } from '../route';
74
+ import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
75
+
76
+ // Mock Supabase
77
+ vi.mock('@supabase/auth-helpers-nextjs', () => ({
78
+ createRouteHandlerClient: vi.fn(),
79
+ }));
80
+
81
+ vi.mock('next/headers', () => ({
82
+ cookies: vi.fn(() => ({})),
83
+ }));
84
+
85
+ describe('POST /api/teams', () => {
86
+ let mockSupabase: any;
87
+
88
+ beforeEach(() => {
89
+ mockSupabase = {
90
+ auth: {
91
+ getUser: vi.fn(),
92
+ },
93
+ from: vi.fn(() => mockSupabase),
94
+ insert: vi.fn(() => mockSupabase),
95
+ select: vi.fn(() => mockSupabase),
96
+ single: vi.fn(),
97
+ };
98
+
99
+ vi.mocked(createRouteHandlerClient).mockReturnValue(mockSupabase);
100
+ });
101
+
102
+ describe('Happy Path', () => {
103
+ it('should create a team with valid data', async () => {
104
+ // Arrange
105
+ const mockUser = { id: 'user-123' };
106
+ const mockTeam = {
107
+ id: 'team-123',
108
+ name: 'Engineering Team',
109
+ slug: 'engineering-team',
110
+ owner_id: 'user-123',
111
+ created_at: new Date().toISOString(),
112
+ };
113
+
114
+ mockSupabase.auth.getUser.mockResolvedValue({
115
+ data: { user: mockUser },
116
+ error: null,
117
+ });
118
+
119
+ mockSupabase.single.mockResolvedValue({
120
+ data: mockTeam,
121
+ error: null,
122
+ });
123
+
124
+ const request = new NextRequest('http://localhost/api/teams', {
125
+ method: 'POST',
126
+ headers: { 'Content-Type': 'application/json' },
127
+ body: JSON.stringify({
128
+ name: 'Engineering Team',
129
+ description: 'Our amazing eng team',
130
+ }),
131
+ });
132
+
133
+ // Act
134
+ const response = await POST(request);
135
+ const data = await response.json();
136
+
137
+ // Assert
138
+ expect(response.status).toBe(201);
139
+ expect(data).toEqual(mockTeam);
140
+ expect(mockSupabase.from).toHaveBeenCalledWith('teams');
141
+ expect(mockSupabase.insert).toHaveBeenCalledWith(
142
+ expect.objectContaining({
143
+ name: 'Engineering Team',
144
+ slug: 'engineering-team',
145
+ owner_id: 'user-123',
146
+ })
147
+ );
148
+ });
149
+
150
+ it('should create team without optional fields', async () => {
151
+ const mockUser = { id: 'user-123' };
152
+ mockSupabase.auth.getUser.mockResolvedValue({
153
+ data: { user: mockUser },
154
+ error: null,
155
+ });
156
+
157
+ mockSupabase.single.mockResolvedValue({
158
+ data: { id: 'team-123', name: 'Team', slug: 'team', owner_id: 'user-123' },
159
+ error: null,
160
+ });
161
+
162
+ const request = new NextRequest('http://localhost/api/teams', {
163
+ method: 'POST',
164
+ body: JSON.stringify({ name: 'Team' }),
165
+ });
166
+
167
+ const response = await POST(request);
168
+
169
+ expect(response.status).toBe(201);
170
+ });
171
+ });
172
+
173
+ describe('Authentication', () => {
174
+ it('should return 401 if user not authenticated', async () => {
175
+ mockSupabase.auth.getUser.mockResolvedValue({
176
+ data: { user: null },
177
+ error: { message: 'Not authenticated' },
178
+ });
179
+
180
+ const request = new NextRequest('http://localhost/api/teams', {
181
+ method: 'POST',
182
+ body: JSON.stringify({ name: 'Team' }),
183
+ });
184
+
185
+ const response = await POST(request);
186
+ const data = await response.json();
187
+
188
+ expect(response.status).toBe(401);
189
+ expect(data.error).toBe('Unauthorized');
190
+ });
191
+
192
+ it('should return 401 if auth token expired', async () => {
193
+ mockSupabase.auth.getUser.mockResolvedValue({
194
+ data: { user: null },
195
+ error: { message: 'Token expired' },
196
+ });
197
+
198
+ const request = new NextRequest('http://localhost/api/teams', {
199
+ method: 'POST',
200
+ body: JSON.stringify({ name: 'Team' }),
201
+ });
202
+
203
+ const response = await POST(request);
204
+
205
+ expect(response.status).toBe(401);
206
+ });
207
+ });
208
+
209
+ describe('Validation', () => {
210
+ beforeEach(() => {
211
+ mockSupabase.auth.getUser.mockResolvedValue({
212
+ data: { user: { id: 'user-123' } },
213
+ error: null,
214
+ });
215
+ });
216
+
217
+ it('should reject team name shorter than 3 characters', async () => {
218
+ const request = new NextRequest('http://localhost/api/teams', {
219
+ method: 'POST',
220
+ body: JSON.stringify({ name: 'AB' }),
221
+ });
222
+
223
+ const response = await POST(request);
224
+ const data = await response.json();
225
+
226
+ expect(response.status).toBe(400);
227
+ expect(data.error).toBe('Validation failed');
228
+ expect(data.details).toBeDefined();
229
+ });
230
+
231
+ it('should reject team name longer than 50 characters', async () => {
232
+ const longName = 'A'.repeat(51);
233
+ const request = new NextRequest('http://localhost/api/teams', {
234
+ method: 'POST',
235
+ body: JSON.stringify({ name: longName }),
236
+ });
237
+
238
+ const response = await POST(request);
239
+
240
+ expect(response.status).toBe(400);
241
+ });
242
+
243
+ it('should reject team name with invalid characters', async () => {
244
+ const request = new NextRequest('http://localhost/api/teams', {
245
+ method: 'POST',
246
+ body: JSON.stringify({ name: 'Team@#$%' }),
247
+ });
248
+
249
+ const response = await POST(request);
250
+
251
+ expect(response.status).toBe(400);
252
+ });
253
+
254
+ it('should reject invalid avatar URL', async () => {
255
+ const request = new NextRequest('http://localhost/api/teams', {
256
+ method: 'POST',
257
+ body: JSON.stringify({
258
+ name: 'Engineering Team',
259
+ avatar_url: 'not-a-url',
260
+ }),
261
+ });
262
+
263
+ const response = await POST(request);
264
+
265
+ expect(response.status).toBe(400);
266
+ });
267
+
268
+ it('should reject empty request body', async () => {
269
+ const request = new NextRequest('http://localhost/api/teams', {
270
+ method: 'POST',
271
+ body: JSON.stringify({}),
272
+ });
273
+
274
+ const response = await POST(request);
275
+
276
+ expect(response.status).toBe(400);
277
+ });
278
+ });
279
+
280
+ describe('Edge Cases', () => {
281
+ beforeEach(() => {
282
+ mockSupabase.auth.getUser.mockResolvedValue({
283
+ data: { user: { id: 'user-123' } },
284
+ error: null,
285
+ });
286
+ });
287
+
288
+ it('should handle duplicate team name (409)', async () => {
289
+ mockSupabase.single.mockResolvedValue({
290
+ data: null,
291
+ error: { code: '23505', message: 'Duplicate key' },
292
+ });
293
+
294
+ const request = new NextRequest('http://localhost/api/teams', {
295
+ method: 'POST',
296
+ body: JSON.stringify({ name: 'Engineering Team' }),
297
+ });
298
+
299
+ const response = await POST(request);
300
+ const data = await response.json();
301
+
302
+ expect(response.status).toBe(409);
303
+ expect(data.error).toContain('already exists');
304
+ });
305
+
306
+ it('should generate slug from name with special characters', async () => {
307
+ mockSupabase.single.mockResolvedValue({
308
+ data: {
309
+ id: 'team-123',
310
+ name: 'Team - Engineering & Design',
311
+ slug: 'team-engineering-design',
312
+ owner_id: 'user-123',
313
+ },
314
+ error: null,
315
+ });
316
+
317
+ const request = new NextRequest('http://localhost/api/teams', {
318
+ method: 'POST',
319
+ body: JSON.stringify({ name: 'Team - Engineering & Design' }),
320
+ });
321
+
322
+ const response = await POST(request);
323
+
324
+ expect(response.status).toBe(201);
325
+ expect(mockSupabase.insert).toHaveBeenCalledWith(
326
+ expect.objectContaining({
327
+ slug: 'team-engineering-design',
328
+ })
329
+ );
330
+ });
331
+
332
+ it('should handle database connection failure', async () => {
333
+ mockSupabase.single.mockResolvedValue({
334
+ data: null,
335
+ error: { message: 'Connection timeout', code: 'CONNECTION_ERROR' },
336
+ });
337
+
338
+ const request = new NextRequest('http://localhost/api/teams', {
339
+ method: 'POST',
340
+ body: JSON.stringify({ name: 'Team' }),
341
+ });
342
+
343
+ const response = await POST(request);
344
+
345
+ expect(response.status).toBe(500);
346
+ });
347
+ });
348
+
349
+ describe('Security', () => {
350
+ it('should prevent SQL injection in team name', async () => {
351
+ mockSupabase.auth.getUser.mockResolvedValue({
352
+ data: { user: { id: 'user-123' } },
353
+ error: null,
354
+ });
355
+
356
+ const request = new NextRequest('http://localhost/api/teams', {
357
+ method: 'POST',
358
+ body: JSON.stringify({
359
+ name: "Team'; DROP TABLE teams; --",
360
+ }),
361
+ });
362
+
363
+ const response = await POST(request);
364
+
365
+ // Should either validate or safely handle
366
+ expect([400, 201]).toContain(response.status);
367
+ // If 201, verify Supabase client escaped the input
368
+ });
369
+
370
+ it('should prevent XSS in team description', async () => {
371
+ mockSupabase.auth.getUser.mockResolvedValue({
372
+ data: { user: { id: 'user-123' } },
373
+ error: null,
374
+ });
375
+
376
+ mockSupabase.single.mockResolvedValue({
377
+ data: { id: 'team-123', name: 'Team', description: '<script>alert("xss")</script>' },
378
+ error: null,
379
+ });
380
+
381
+ const request = new NextRequest('http://localhost/api/teams', {
382
+ method: 'POST',
383
+ body: JSON.stringify({
384
+ name: 'Team',
385
+ description: '<script>alert("xss")</script>',
386
+ }),
387
+ });
388
+
389
+ const response = await POST(request);
390
+
391
+ expect(response.status).toBe(201);
392
+ // Note: Frontend should sanitize when displaying
393
+ });
394
+ });
395
+ });
396
+
397
+ describe('GET /api/teams', () => {
398
+ // ... similar comprehensive tests for GET endpoint
399
+ // (abbreviated for space)
400
+ });
401
+ ```
402
+
403
+ Creating apps/web/app/api/teams/__tests__/integration.test.ts...
404
+
405
+ ```typescript
406
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
407
+ import { createClient } from '@supabase/supabase-js';
408
+
409
+ // Integration tests with real Supabase (test database)
410
+ const supabase = createClient(
411
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
412
+ process.env.SUPABASE_SERVICE_ROLE_KEY!
413
+ );
414
+
415
+ describe('Teams API Integration', () => {
416
+ let testUserId: string;
417
+ let testTeamId: string;
418
+ let authToken: string;
419
+
420
+ beforeAll(async () => {
421
+ // Create test user
422
+ const { data: { user } } = await supabase.auth.admin.createUser({
423
+ email: 'test@example.com',
424
+ password: 'test-password-123',
425
+ email_confirm: true,
426
+ });
427
+ testUserId = user!.id;
428
+
429
+ // Sign in to get auth token
430
+ const { data: { session } } = await supabase.auth.signInWithPassword({
431
+ email: 'test@example.com',
432
+ password: 'test-password-123',
433
+ });
434
+ authToken = session!.access_token;
435
+ });
436
+
437
+ afterAll(async () => {
438
+ // Cleanup: delete test data
439
+ if (testTeamId) {
440
+ await supabase.from('teams').delete().eq('id', testTeamId);
441
+ }
442
+ await supabase.auth.admin.deleteUser(testUserId);
443
+ });
444
+
445
+ it('should create, read, update, and delete a team (CRUD)', async () => {
446
+ // CREATE
447
+ const createResponse = await fetch('http://localhost:3000/api/teams', {
448
+ method: 'POST',
449
+ headers: {
450
+ 'Content-Type': 'application/json',
451
+ 'Authorization': `Bearer ${authToken}`,
452
+ },
453
+ body: JSON.stringify({
454
+ name: 'Integration Test Team',
455
+ description: 'Created by integration test',
456
+ }),
457
+ });
458
+
459
+ expect(createResponse.status).toBe(201);
460
+ const createdTeam = await createResponse.json();
461
+ testTeamId = createdTeam.id;
462
+
463
+ expect(createdTeam.name).toBe('Integration Test Team');
464
+ expect(createdTeam.owner_id).toBe(testUserId);
465
+
466
+ // READ (list)
467
+ const listResponse = await fetch('http://localhost:3000/api/teams', {
468
+ headers: { 'Authorization': `Bearer ${authToken}` },
469
+ });
470
+
471
+ expect(listResponse.status).toBe(200);
472
+ const teams = await listResponse.json();
473
+ expect(teams).toContainEqual(expect.objectContaining({ id: testTeamId }));
474
+
475
+ // READ (single)
476
+ const getResponse = await fetch(`http://localhost:3000/api/teams/${testTeamId}`, {
477
+ headers: { 'Authorization': `Bearer ${authToken}` },
478
+ });
479
+
480
+ expect(getResponse.status).toBe(200);
481
+ const team = await getResponse.json();
482
+ expect(team.id).toBe(testTeamId);
483
+
484
+ // UPDATE
485
+ const updateResponse = await fetch(`http://localhost:3000/api/teams/${testTeamId}`, {
486
+ method: 'PATCH',
487
+ headers: {
488
+ 'Content-Type': 'application/json',
489
+ 'Authorization': `Bearer ${authToken}`,
490
+ },
491
+ body: JSON.stringify({
492
+ name: 'Updated Team Name',
493
+ }),
494
+ });
495
+
496
+ expect(updateResponse.status).toBe(200);
497
+ const updatedTeam = await updateResponse.json();
498
+ expect(updatedTeam.name).toBe('Updated Team Name');
499
+
500
+ // DELETE
501
+ const deleteResponse = await fetch(`http://localhost:3000/api/teams/${testTeamId}`, {
502
+ method: 'DELETE',
503
+ headers: { 'Authorization': `Bearer ${authToken}` },
504
+ });
505
+
506
+ expect(deleteResponse.status).toBe(200);
507
+
508
+ // Verify soft delete
509
+ const { data: deletedTeam } = await supabase
510
+ .from('teams')
511
+ .select('deleted_at')
512
+ .eq('id', testTeamId)
513
+ .single();
514
+
515
+ expect(deletedTeam.deleted_at).not.toBeNull();
516
+ });
517
+
518
+ it('should enforce RLS policies (no access to other users teams)', async () => {
519
+ // Create second user
520
+ const { data: { user: user2 } } = await supabase.auth.admin.createUser({
521
+ email: 'test2@example.com',
522
+ password: 'test-password-456',
523
+ email_confirm: true,
524
+ });
525
+
526
+ const { data: { session: session2 } } = await supabase.auth.signInWithPassword({
527
+ email: 'test2@example.com',
528
+ password: 'test-password-456',
529
+ });
530
+
531
+ // Try to access user1's team with user2's token
532
+ const response = await fetch(`http://localhost:3000/api/teams/${testTeamId}`, {
533
+ headers: { 'Authorization': `Bearer ${session2!.access_token}` },
534
+ });
535
+
536
+ expect(response.status).toBe(404); // RLS blocks access
537
+
538
+ // Cleanup user2
539
+ await supabase.auth.admin.deleteUser(user2!.id);
540
+ });
541
+ });
542
+ ```
543
+
544
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
545
+ ✅ Tests Complete
546
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
547
+
548
+ Test Files Created: 2
549
+ ✓ route.test.ts (unit tests)
550
+ ✓ integration.test.ts (integration tests)
551
+
552
+ Test Cases Written: 28
553
+ ✓ Happy path: 8 tests
554
+ ✓ Authentication: 2 tests
555
+ ✓ Validation: 5 tests
556
+ ✓ Edge cases: 3 tests
557
+ ✓ Security: 2 tests
558
+ ✓ Integration: 8 tests
559
+
560
+ Coverage: 96% (exceeds 90% goal) ✅
561
+
562
+ Running tests...
563
+
564
+ ```bash
565
+ $ npm run test apps/web/app/api/teams
566
+
567
+ ✓ apps/web/app/api/teams/__tests__/route.test.ts (28 tests)
568
+ ✓ POST /api/teams (18 tests) 1245ms
569
+ ✓ Happy Path (2 tests) 145ms
570
+ ✓ Authentication (2 tests) 89ms
571
+ ✓ Validation (5 tests) 234ms
572
+ ✓ Edge Cases (3 tests) 178ms
573
+ ✓ Security (2 tests) 123ms
574
+ ✓ GET /api/teams (10 tests) 567ms
575
+
576
+ ✓ apps/web/app/api/teams/__tests__/integration.test.ts (2 tests) 3456ms
577
+ ✓ Teams API Integration (2 tests) 3456ms
578
+
579
+ Test Files: 2 passed (2)
580
+ Tests: 30 passed (30)
581
+ Duration: 4.8s
582
+
583
+ Coverage:
584
+ apps/web/app/api/teams/route.ts: 96.42% (54/56 lines)
585
+ apps/web/app/api/teams/[id]/route.ts: 95.83% (46/48 lines)
586
+ ```
587
+
588
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
589
+
590
+ Next Steps:
591
+ • Run full test suite: npm run test
592
+ • Add E2E tests: /qa e2e "Teams workflow"
593
+ • Review code quality: /lead review all
594
+ ```
595
+
596
+ ### `/qa test`
597
+
598
+ Run existing tests and analyze results.
599
+
600
+ ```bash
601
+ /qa test
602
+
603
+ # Run specific tests
604
+ /qa test "teams"
605
+
606
+ # Run with coverage
607
+ /qa test --coverage
608
+
609
+ # Run integration tests only
610
+ /qa test --integration
611
+ ```
612
+
613
+ **What happens:**
614
+
615
+ ```
616
+ 🧪 QA Agent: Running Tests
617
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
618
+
619
+ Running test suite...
620
+
621
+ ✓ apps/web/app/api/teams (30 tests) 4821ms
622
+ ✓ apps/web/app/api/patterns (45 tests) 5234ms
623
+ ✓ apps/web/components/teams (18 tests) 2345ms
624
+ ✓ apps/memory/lib/brain (67 tests) 8934ms
625
+
626
+ Test Files: 12 passed (12)
627
+ Tests: 412 passed (412)
628
+ Duration: 32.4s
629
+
630
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
631
+ 📊 Coverage Report
632
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
633
+
634
+ Overall Coverage: 94.2% ✅ (meets 90% threshold)
635
+
636
+ By Directory:
637
+ apps/web/app/api/ 96.8% ✅
638
+ apps/web/components/ 92.3% ✅
639
+ apps/memory/lib/brain/ 95.1% ✅
640
+ packages/shared/ 89.4% 🟡 (below threshold)
641
+
642
+ Uncovered Lines: 24
643
+ Most critical:
644
+ • apps/web/lib/auth/refresh.ts:45-48 (error recovery)
645
+ • packages/shared/utils/retry.ts:67-70 (timeout handling)
646
+
647
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
648
+ ✅ All Tests Passed
649
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
650
+
651
+ Status: Ready for deployment ✅
652
+
653
+ Recommendations:
654
+ • Increase coverage in packages/shared (currently 89.4%)
655
+ • Add tests for error recovery in auth/refresh.ts
656
+ • Consider adding performance tests for memory/lib/brain
657
+ ```
658
+
659
+ ### `/qa regression`
660
+
661
+ Run regression tests to ensure no existing functionality broke.
662
+
663
+ ```bash
664
+ /qa regression
665
+
666
+ # Test specific area
667
+ /qa regression "authentication"
668
+ ```
669
+
670
+ ### `/qa e2e`
671
+
672
+ Write and run end-to-end tests for user workflows.
673
+
674
+ ```bash
675
+ /qa e2e "User workflow description"
676
+
677
+ # Examples
678
+ /qa e2e "Create team and invite member"
679
+ /qa e2e "Sign up, forge pattern, verify in dashboard"
680
+ ```
681
+
682
+ **What happens:**
683
+
684
+ ```
685
+ 🧪 QA Agent: E2E Test for "Create Team Workflow"
686
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
687
+
688
+ Creating e2e/teams/create-team.spec.ts...
689
+
690
+ ```typescript
691
+ import { test, expect } from '@playwright/test';
692
+
693
+ test.describe('Create Team Workflow', () => {
694
+ test.beforeEach(async ({ page }) => {
695
+ // Sign in
696
+ await page.goto('http://localhost:3000/login');
697
+ await page.fill('[name="email"]', 'test@example.com');
698
+ await page.fill('[name="password"]', 'test-password');
699
+ await page.click('button[type="submit"]');
700
+
701
+ await expect(page).toHaveURL('/dashboard');
702
+ });
703
+
704
+ test('should create a team successfully', async ({ page }) => {
705
+ // Navigate to teams
706
+ await page.goto('http://localhost:3000/teams');
707
+
708
+ // Click create team button
709
+ await page.click('button:has-text("Create Team")');
710
+
711
+ // Fill form
712
+ await page.fill('[name="name"]', 'E2E Test Team');
713
+ await page.fill('[name="description"]', 'Created by E2E test');
714
+
715
+ // Submit
716
+ await page.click('button[type="submit"]:has-text("Create Team")');
717
+
718
+ // Verify success
719
+ await expect(page.locator('text=Team created successfully')).toBeVisible();
720
+ await expect(page.locator('text=E2E Test Team')).toBeVisible();
721
+
722
+ // Verify in database
723
+ const teamName = await page.locator('[data-testid="team-name"]').textContent();
724
+ expect(teamName).toBe('E2E Test Team');
725
+ });
726
+
727
+ test('should show validation error for short name', async ({ page }) => {
728
+ await page.goto('http://localhost:3000/teams');
729
+ await page.click('button:has-text("Create Team")');
730
+
731
+ await page.fill('[name="name"]', 'AB'); // Too short
732
+ await page.click('button[type="submit"]');
733
+
734
+ await expect(page.locator('text=Team name must be at least 3 characters')).toBeVisible();
735
+ });
736
+
737
+ test('should close modal on cancel', async ({ page }) => {
738
+ await page.goto('http://localhost:3000/teams');
739
+ await page.click('button:has-text("Create Team")');
740
+
741
+ await expect(page.locator('role=dialog')).toBeVisible();
742
+
743
+ await page.click('button:has-text("Cancel")');
744
+
745
+ await expect(page.locator('role=dialog')).not.toBeVisible();
746
+ });
747
+ });
748
+ ```
749
+
750
+ Running E2E tests with Playwright...
751
+
752
+ ```bash
753
+ $ npx playwright test e2e/teams/create-team.spec.ts
754
+
755
+ Running 3 tests using 1 worker
756
+
757
+ ✓ Create Team Workflow > should create a team successfully (2.3s)
758
+ ✓ Create Team Workflow > should show validation error for short name (1.1s)
759
+ ✓ Create Team Workflow > should close modal on cancel (0.8s)
760
+
761
+ 3 passed (4.2s)
762
+ ```
763
+
764
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
765
+ ✅ E2E Tests Complete
766
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
767
+
768
+ Test File: e2e/teams/create-team.spec.ts
769
+ Tests: 3 passed (3)
770
+ Duration: 4.2s
771
+
772
+ All user workflows verified ✅
773
+ ```
774
+
775
+ ## MCP Tools Used
776
+
777
+ The QA Agent uses:
778
+
779
+ - `ekkOS_Search` - Find similar test patterns
780
+ - `ekkOS_Codebase` - Search code to understand what to test
781
+ - `ekkOS_Forge` - Save testing patterns
782
+ - `Read` - Read code to test
783
+ - `Write` - Write test files
784
+ - `Bash` - Run test commands (npm test, playwright)
785
+ - `supabase_execute_sql` - Setup test data
786
+
787
+ ## Best Practices
788
+
789
+ ### Test the Unhappy Path
790
+
791
+ ```typescript
792
+ // ✅ Good: Tests both happy and unhappy paths
793
+ it('should create team with valid data', async () => { /* ... */ });
794
+ it('should reject invalid data', async () => { /* ... */ });
795
+ it('should handle database errors', async () => { /* ... */ });
796
+
797
+ // ❌ Bad: Only tests happy path
798
+ it('should create team', async () => { /* ... */ });
799
+ ```
800
+
801
+ ### Use Descriptive Test Names
802
+
803
+ ```typescript
804
+ // ✅ Good: Clear what is being tested
805
+ it('should return 401 if user not authenticated', async () => { /* ... */ });
806
+
807
+ // ❌ Bad: Unclear
808
+ it('should work', async () => { /* ... */ });
809
+ ```
810
+
811
+ ### Test Edge Cases
812
+
813
+ ```typescript
814
+ // ✅ Good: Tests edge cases
815
+ it('should handle duplicate team name', async () => { /* ... */ });
816
+ it('should handle database connection timeout', async () => { /* ... */ });
817
+ it('should prevent SQL injection', async () => { /* ... */ });
818
+
819
+ // ❌ Bad: Only basic scenarios
820
+ ```
821
+
822
+ ## Integration with Other Agents
823
+
824
+ QA Agent works closely with:
825
+
826
+ - **Frontend Agent** - Tests UI components
827
+ - **Backend Agent** - Tests APIs and database
828
+ - **Tech Lead** - Reports test results for reviews
829
+
830
+ ## Troubleshooting
831
+
832
+ ### Tests Failing
833
+
834
+ **Problem:** Tests failing unexpectedly
835
+ **Check:** `/qa test` to see failures
836
+ **Fix:** Agent analyzes failures and suggests fixes
837
+
838
+ ### Low Coverage
839
+
840
+ **Problem:** Coverage below 90% threshold
841
+ **Check:** Coverage report
842
+ **Fix:** Agent writes tests for uncovered lines
843
+
844
+ ---
845
+
846
+ ## Summary
847
+
848
+ The QA Agent is your testing expert that:
849
+
850
+ ✅ **Writes** - Comprehensive test suites
851
+ ✅ **Runs** - Tests with coverage analysis
852
+ ✅ **Validates** - Edge cases and security
853
+ ✅ **Reports** - Quality metrics and issues
854
+
855
+ **Ship with confidence.**
856
+
857
+ ```bash
858
+ /qa write "Your feature here"
859
+ ```
860
+
861
+ ---
862
+
863
+ **Test early. Test often. Ship quality.** 🧪