@vibescope/mcp-server 0.0.1 → 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 (173) hide show
  1. package/README.md +113 -98
  2. package/dist/api-client.d.ts +1169 -0
  3. package/dist/api-client.js +713 -0
  4. package/dist/cli.d.ts +1 -6
  5. package/dist/cli.js +39 -240
  6. package/dist/config/tool-categories.d.ts +31 -0
  7. package/dist/config/tool-categories.js +253 -0
  8. package/dist/handlers/blockers.js +57 -58
  9. package/dist/handlers/bodies-of-work.d.ts +2 -0
  10. package/dist/handlers/bodies-of-work.js +108 -477
  11. package/dist/handlers/cost.d.ts +1 -0
  12. package/dist/handlers/cost.js +35 -113
  13. package/dist/handlers/decisions.d.ts +2 -0
  14. package/dist/handlers/decisions.js +28 -27
  15. package/dist/handlers/deployment.js +113 -828
  16. package/dist/handlers/discovery.d.ts +3 -0
  17. package/dist/handlers/discovery.js +26 -627
  18. package/dist/handlers/fallback.d.ts +2 -0
  19. package/dist/handlers/fallback.js +56 -142
  20. package/dist/handlers/findings.d.ts +8 -1
  21. package/dist/handlers/findings.js +65 -68
  22. package/dist/handlers/git-issues.d.ts +9 -13
  23. package/dist/handlers/git-issues.js +80 -225
  24. package/dist/handlers/ideas.d.ts +3 -0
  25. package/dist/handlers/ideas.js +53 -134
  26. package/dist/handlers/index.d.ts +2 -0
  27. package/dist/handlers/index.js +6 -0
  28. package/dist/handlers/milestones.d.ts +2 -0
  29. package/dist/handlers/milestones.js +51 -98
  30. package/dist/handlers/organizations.js +79 -275
  31. package/dist/handlers/progress.d.ts +2 -0
  32. package/dist/handlers/progress.js +25 -123
  33. package/dist/handlers/project.js +42 -221
  34. package/dist/handlers/requests.d.ts +2 -0
  35. package/dist/handlers/requests.js +23 -83
  36. package/dist/handlers/session.js +119 -590
  37. package/dist/handlers/sprints.d.ts +32 -0
  38. package/dist/handlers/sprints.js +275 -0
  39. package/dist/handlers/tasks.d.ts +7 -10
  40. package/dist/handlers/tasks.js +245 -894
  41. package/dist/handlers/tool-docs.d.ts +9 -0
  42. package/dist/handlers/tool-docs.js +904 -0
  43. package/dist/handlers/types.d.ts +11 -3
  44. package/dist/handlers/validation.d.ts +1 -1
  45. package/dist/handlers/validation.js +38 -153
  46. package/dist/index.js +493 -162
  47. package/dist/knowledge.js +106 -9
  48. package/dist/tools.js +34 -4
  49. package/dist/validators.d.ts +21 -0
  50. package/dist/validators.js +91 -0
  51. package/package.json +2 -3
  52. package/src/api-client.ts +1822 -0
  53. package/src/cli.test.ts +128 -302
  54. package/src/cli.ts +41 -285
  55. package/src/handlers/__test-setup__.ts +215 -0
  56. package/src/handlers/__test-utils__.ts +4 -134
  57. package/src/handlers/blockers.test.ts +114 -124
  58. package/src/handlers/blockers.ts +68 -70
  59. package/src/handlers/bodies-of-work.test.ts +236 -831
  60. package/src/handlers/bodies-of-work.ts +210 -525
  61. package/src/handlers/cost.test.ts +149 -113
  62. package/src/handlers/cost.ts +44 -132
  63. package/src/handlers/decisions.test.ts +111 -209
  64. package/src/handlers/decisions.ts +35 -27
  65. package/src/handlers/deployment.test.ts +193 -239
  66. package/src/handlers/deployment.ts +143 -896
  67. package/src/handlers/discovery.test.ts +20 -67
  68. package/src/handlers/discovery.ts +29 -714
  69. package/src/handlers/fallback.test.ts +206 -361
  70. package/src/handlers/fallback.ts +81 -156
  71. package/src/handlers/findings.test.ts +229 -320
  72. package/src/handlers/findings.ts +76 -64
  73. package/src/handlers/git-issues.test.ts +623 -0
  74. package/src/handlers/git-issues.ts +174 -0
  75. package/src/handlers/ideas.test.ts +229 -343
  76. package/src/handlers/ideas.ts +69 -143
  77. package/src/handlers/index.ts +6 -0
  78. package/src/handlers/milestones.test.ts +167 -281
  79. package/src/handlers/milestones.ts +54 -93
  80. package/src/handlers/organizations.test.ts +275 -467
  81. package/src/handlers/organizations.ts +84 -294
  82. package/src/handlers/progress.test.ts +112 -218
  83. package/src/handlers/progress.ts +29 -142
  84. package/src/handlers/project.test.ts +203 -226
  85. package/src/handlers/project.ts +48 -238
  86. package/src/handlers/requests.test.ts +74 -342
  87. package/src/handlers/requests.ts +25 -83
  88. package/src/handlers/session.test.ts +276 -206
  89. package/src/handlers/session.ts +136 -662
  90. package/src/handlers/sprints.test.ts +711 -0
  91. package/src/handlers/sprints.ts +510 -0
  92. package/src/handlers/tasks.test.ts +669 -353
  93. package/src/handlers/tasks.ts +263 -1015
  94. package/src/handlers/tool-docs.ts +1024 -0
  95. package/src/handlers/types.ts +12 -4
  96. package/src/handlers/validation.test.ts +237 -568
  97. package/src/handlers/validation.ts +43 -167
  98. package/src/index.ts +493 -186
  99. package/src/tools.ts +2532 -0
  100. package/src/validators.test.ts +223 -223
  101. package/src/validators.ts +127 -0
  102. package/tsconfig.json +1 -1
  103. package/vitest.config.ts +14 -13
  104. package/dist/cli.test.d.ts +0 -1
  105. package/dist/cli.test.js +0 -367
  106. package/dist/handlers/__test-utils__.d.ts +0 -72
  107. package/dist/handlers/__test-utils__.js +0 -176
  108. package/dist/handlers/checkouts.d.ts +0 -37
  109. package/dist/handlers/checkouts.js +0 -377
  110. package/dist/handlers/knowledge-query.d.ts +0 -22
  111. package/dist/handlers/knowledge-query.js +0 -253
  112. package/dist/handlers/knowledge.d.ts +0 -12
  113. package/dist/handlers/knowledge.js +0 -108
  114. package/dist/handlers/roles.d.ts +0 -30
  115. package/dist/handlers/roles.js +0 -281
  116. package/dist/handlers/tasks.test.d.ts +0 -1
  117. package/dist/handlers/tasks.test.js +0 -431
  118. package/dist/utils.test.d.ts +0 -1
  119. package/dist/utils.test.js +0 -532
  120. package/dist/validators.test.d.ts +0 -1
  121. package/dist/validators.test.js +0 -176
  122. package/src/knowledge.ts +0 -132
  123. package/src/tmpclaude-0078-cwd +0 -1
  124. package/src/tmpclaude-0ee1-cwd +0 -1
  125. package/src/tmpclaude-2dd5-cwd +0 -1
  126. package/src/tmpclaude-344c-cwd +0 -1
  127. package/src/tmpclaude-3860-cwd +0 -1
  128. package/src/tmpclaude-4b63-cwd +0 -1
  129. package/src/tmpclaude-5c73-cwd +0 -1
  130. package/src/tmpclaude-5ee3-cwd +0 -1
  131. package/src/tmpclaude-6795-cwd +0 -1
  132. package/src/tmpclaude-709e-cwd +0 -1
  133. package/src/tmpclaude-9839-cwd +0 -1
  134. package/src/tmpclaude-d829-cwd +0 -1
  135. package/src/tmpclaude-e072-cwd +0 -1
  136. package/src/tmpclaude-f6ee-cwd +0 -1
  137. package/tmpclaude-0439-cwd +0 -1
  138. package/tmpclaude-132f-cwd +0 -1
  139. package/tmpclaude-15bb-cwd +0 -1
  140. package/tmpclaude-165a-cwd +0 -1
  141. package/tmpclaude-1ba9-cwd +0 -1
  142. package/tmpclaude-21a3-cwd +0 -1
  143. package/tmpclaude-2a38-cwd +0 -1
  144. package/tmpclaude-2adf-cwd +0 -1
  145. package/tmpclaude-2f56-cwd +0 -1
  146. package/tmpclaude-3626-cwd +0 -1
  147. package/tmpclaude-3727-cwd +0 -1
  148. package/tmpclaude-40bc-cwd +0 -1
  149. package/tmpclaude-436f-cwd +0 -1
  150. package/tmpclaude-4783-cwd +0 -1
  151. package/tmpclaude-4b6d-cwd +0 -1
  152. package/tmpclaude-4ba4-cwd +0 -1
  153. package/tmpclaude-51e6-cwd +0 -1
  154. package/tmpclaude-5ecf-cwd +0 -1
  155. package/tmpclaude-6f97-cwd +0 -1
  156. package/tmpclaude-7fb2-cwd +0 -1
  157. package/tmpclaude-825c-cwd +0 -1
  158. package/tmpclaude-8baf-cwd +0 -1
  159. package/tmpclaude-8d9f-cwd +0 -1
  160. package/tmpclaude-975c-cwd +0 -1
  161. package/tmpclaude-9983-cwd +0 -1
  162. package/tmpclaude-a045-cwd +0 -1
  163. package/tmpclaude-ac4a-cwd +0 -1
  164. package/tmpclaude-b593-cwd +0 -1
  165. package/tmpclaude-b891-cwd +0 -1
  166. package/tmpclaude-c032-cwd +0 -1
  167. package/tmpclaude-cf43-cwd +0 -1
  168. package/tmpclaude-d040-cwd +0 -1
  169. package/tmpclaude-dcdd-cwd +0 -1
  170. package/tmpclaude-dcee-cwd +0 -1
  171. package/tmpclaude-e16b-cwd +0 -1
  172. package/tmpclaude-ecd2-cwd +0 -1
  173. package/tmpclaude-f48d-cwd +0 -1
@@ -1,119 +1,18 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import type { SupabaseClient } from '@supabase/supabase-js';
3
- import type { HandlerContext, TokenUsage } from './types.js';
4
2
  import {
5
3
  getTasksAwaitingValidation,
6
4
  claimValidation,
7
5
  validateTask,
8
6
  } from './validation.js';
9
7
  import { ValidationError } from '../validators.js';
8
+ import { createMockContext } from './__test-utils__.js';
9
+ import { mockApiClient } from './__test-setup__.js';
10
10
 
11
11
  // ============================================================================
12
- // Test Utilities
12
+ // Test Constants
13
13
  // ============================================================================
14
14
 
15
- function createMockSupabase(overrides: {
16
- selectResult?: { data: unknown; error: unknown };
17
- insertResult?: { data: unknown; error: unknown };
18
- updateResult?: { data: unknown; error: unknown };
19
- } = {}) {
20
- const defaultResult = { data: null, error: null };
21
- let currentOperation = 'select';
22
- let insertThenSelect = false;
23
-
24
- const mock = {
25
- from: vi.fn().mockReturnThis(),
26
- select: vi.fn(() => {
27
- if (currentOperation === 'insert') {
28
- insertThenSelect = true;
29
- } else {
30
- currentOperation = 'select';
31
- insertThenSelect = false;
32
- }
33
- return mock;
34
- }),
35
- insert: vi.fn(() => {
36
- currentOperation = 'insert';
37
- insertThenSelect = false;
38
- return mock;
39
- }),
40
- update: vi.fn(() => {
41
- currentOperation = 'update';
42
- insertThenSelect = false;
43
- return mock;
44
- }),
45
- delete: vi.fn(() => {
46
- currentOperation = 'delete';
47
- insertThenSelect = false;
48
- return mock;
49
- }),
50
- eq: vi.fn().mockReturnThis(),
51
- neq: vi.fn().mockReturnThis(),
52
- in: vi.fn().mockReturnThis(),
53
- is: vi.fn().mockReturnThis(),
54
- not: vi.fn().mockReturnThis(),
55
- or: vi.fn().mockReturnThis(),
56
- gte: vi.fn().mockReturnThis(),
57
- lte: vi.fn().mockReturnThis(),
58
- lt: vi.fn().mockReturnThis(),
59
- order: vi.fn().mockReturnThis(),
60
- limit: vi.fn().mockReturnThis(),
61
- single: vi.fn(() => {
62
- if (currentOperation === 'insert' || insertThenSelect) {
63
- return Promise.resolve(overrides.insertResult ?? defaultResult);
64
- }
65
- if (currentOperation === 'select') {
66
- return Promise.resolve(overrides.selectResult ?? defaultResult);
67
- }
68
- if (currentOperation === 'update') {
69
- return Promise.resolve(overrides.updateResult ?? defaultResult);
70
- }
71
- return Promise.resolve(defaultResult);
72
- }),
73
- maybeSingle: vi.fn(() => {
74
- return Promise.resolve(overrides.selectResult ?? defaultResult);
75
- }),
76
- then: vi.fn((resolve: (value: unknown) => void) => {
77
- if (currentOperation === 'insert' || insertThenSelect) {
78
- return Promise.resolve(overrides.insertResult ?? defaultResult).then(resolve);
79
- }
80
- if (currentOperation === 'select') {
81
- return Promise.resolve(overrides.selectResult ?? defaultResult).then(resolve);
82
- }
83
- if (currentOperation === 'update') {
84
- return Promise.resolve(overrides.updateResult ?? defaultResult).then(resolve);
85
- }
86
- return Promise.resolve(defaultResult).then(resolve);
87
- }),
88
- };
89
-
90
- return mock as unknown as SupabaseClient;
91
- }
92
-
93
- function createMockContext(
94
- supabase: SupabaseClient,
95
- options: { sessionId?: string | null } = {}
96
- ): HandlerContext {
97
- const defaultTokenUsage: TokenUsage = {
98
- callCount: 5,
99
- totalTokens: 2500,
100
- byTool: {},
101
- };
102
-
103
- const sessionId = 'sessionId' in options ? options.sessionId : 'session-123';
104
-
105
- return {
106
- supabase,
107
- auth: { userId: 'user-123', apiKeyId: 'api-key-123' },
108
- session: {
109
- instanceId: 'instance-abc',
110
- currentSessionId: sessionId,
111
- currentPersona: 'Wave',
112
- tokenUsage: defaultTokenUsage,
113
- },
114
- updateSession: vi.fn(),
115
- };
116
- }
15
+ const VALID_UUID = '123e4567-e89b-12d3-a456-426614174000';
117
16
 
118
17
  // ============================================================================
119
18
  // getTasksAwaitingValidation Tests
@@ -123,15 +22,13 @@ describe('getTasksAwaitingValidation', () => {
123
22
  beforeEach(() => vi.clearAllMocks());
124
23
 
125
24
  it('should throw error for missing project_id', async () => {
126
- const supabase = createMockSupabase();
127
- const ctx = createMockContext(supabase);
25
+ const ctx = createMockContext();
128
26
 
129
27
  await expect(getTasksAwaitingValidation({}, ctx)).rejects.toThrow(ValidationError);
130
28
  });
131
29
 
132
30
  it('should throw error for invalid project_id UUID', async () => {
133
- const supabase = createMockSupabase();
134
- const ctx = createMockContext(supabase);
31
+ const ctx = createMockContext();
135
32
 
136
33
  await expect(
137
34
  getTasksAwaitingValidation({ project_id: 'invalid' }, ctx)
@@ -139,26 +36,19 @@ describe('getTasksAwaitingValidation', () => {
139
36
  });
140
37
 
141
38
  it('should return empty list when no tasks awaiting validation', async () => {
142
- const supabase = createMockSupabase({
143
- selectResult: { data: [], error: null },
39
+ mockApiClient.getTasksAwaitingValidation.mockResolvedValue({
40
+ ok: true,
41
+ data: { tasks: [], count: 0 },
144
42
  });
145
- const ctx = createMockContext(supabase);
146
-
147
- // Override to return array result
148
- vi.mocked(supabase.from('').select).mockReturnValue({
149
- ...supabase,
150
- then: (resolve: (val: unknown) => void) =>
151
- Promise.resolve({ data: [], error: null }).then(resolve),
152
- } as unknown as ReturnType<SupabaseClient['from']>);
43
+ const ctx = createMockContext();
153
44
 
154
45
  const result = await getTasksAwaitingValidation(
155
- { project_id: '123e4567-e89b-12d3-a456-426614174000' },
46
+ { project_id: VALID_UUID },
156
47
  ctx
157
48
  );
158
49
 
159
50
  expect(result.result).toHaveProperty('tasks');
160
51
  expect((result.result as { tasks: unknown[] }).tasks).toEqual([]);
161
- expect((result.result as { hint?: string }).hint).toBeUndefined();
162
52
  });
163
53
 
164
54
  it('should return tasks with review status info', async () => {
@@ -168,33 +58,31 @@ describe('getTasksAwaitingValidation', () => {
168
58
  title: 'Implement feature A',
169
59
  completed_at: '2025-01-14T10:00:00Z',
170
60
  completed_by_session_id: 'other-session',
171
- reviewing_by_session_id: null,
172
- reviewing_started_at: null,
61
+ being_reviewed: false,
62
+ review_minutes: null,
173
63
  },
174
64
  {
175
65
  id: 'task-2',
176
66
  title: 'Implement feature B',
177
67
  completed_at: '2025-01-14T11:00:00Z',
178
68
  completed_by_session_id: 'other-session',
179
- reviewing_by_session_id: 'reviewer-session',
180
- reviewing_started_at: new Date(Date.now() - 5 * 60000).toISOString(), // 5 minutes ago
69
+ being_reviewed: true,
70
+ review_minutes: 5,
181
71
  },
182
72
  ];
183
73
 
184
- const supabase = createMockSupabase({
185
- selectResult: { data: mockTasks, error: null },
74
+ mockApiClient.getTasksAwaitingValidation.mockResolvedValue({
75
+ ok: true,
76
+ data: {
77
+ tasks: mockTasks,
78
+ count: 2,
79
+ hint: 'Use claim_validation to claim a task for review',
80
+ },
186
81
  });
187
- const ctx = createMockContext(supabase);
188
-
189
- // Override to return array result
190
- vi.mocked(supabase.from('').select).mockReturnValue({
191
- ...supabase,
192
- then: (resolve: (val: unknown) => void) =>
193
- Promise.resolve({ data: mockTasks, error: null }).then(resolve),
194
- } as unknown as ReturnType<SupabaseClient['from']>);
82
+ const ctx = createMockContext();
195
83
 
196
84
  const result = await getTasksAwaitingValidation(
197
- { project_id: '123e4567-e89b-12d3-a456-426614174000' },
85
+ { project_id: VALID_UUID },
198
86
  ctx
199
87
  );
200
88
 
@@ -207,46 +95,37 @@ describe('getTasksAwaitingValidation', () => {
207
95
 
208
96
  // Second task being reviewed for ~5 minutes
209
97
  expect(tasks[1].being_reviewed).toBe(true);
210
- expect(tasks[1].review_minutes).toBeGreaterThanOrEqual(4);
211
- expect(tasks[1].review_minutes).toBeLessThanOrEqual(6);
98
+ expect(tasks[1].review_minutes).toBe(5);
212
99
 
213
100
  // Should have hint when tasks are present
214
101
  expect((result.result as { hint?: string }).hint).toContain('claim_validation');
215
102
  });
216
103
 
217
- it('should query correct table with filters', async () => {
218
- const supabase = createMockSupabase({
219
- selectResult: { data: [], error: null },
104
+ it('should call API client with correct project_id', async () => {
105
+ mockApiClient.getTasksAwaitingValidation.mockResolvedValue({
106
+ ok: true,
107
+ data: { tasks: [], count: 0 },
220
108
  });
221
- const ctx = createMockContext(supabase);
109
+ const ctx = createMockContext();
222
110
 
223
111
  await getTasksAwaitingValidation(
224
- { project_id: '123e4567-e89b-12d3-a456-426614174000' },
112
+ { project_id: VALID_UUID },
225
113
  ctx
226
114
  );
227
115
 
228
- expect(supabase.from).toHaveBeenCalledWith('tasks');
229
- expect(supabase.eq).toHaveBeenCalledWith('project_id', '123e4567-e89b-12d3-a456-426614174000');
230
- expect(supabase.eq).toHaveBeenCalledWith('status', 'completed');
231
- expect(supabase.is).toHaveBeenCalledWith('validated_at', null);
116
+ expect(mockApiClient.getTasksAwaitingValidation).toHaveBeenCalledWith(VALID_UUID);
232
117
  });
233
118
 
234
- it('should throw error when database query fails', async () => {
235
- const supabase = createMockSupabase({
236
- selectResult: { data: null, error: { message: 'Database error' } },
119
+ it('should throw error when API call fails', async () => {
120
+ mockApiClient.getTasksAwaitingValidation.mockResolvedValue({
121
+ ok: false,
122
+ error: 'Failed to fetch tasks awaiting validation',
237
123
  });
238
- const ctx = createMockContext(supabase);
239
-
240
- // Override to return error
241
- vi.mocked(supabase.from('').select).mockReturnValue({
242
- ...supabase,
243
- then: (resolve: (val: unknown) => void) =>
244
- Promise.resolve({ data: null, error: { message: 'Database error' } }).then(resolve),
245
- } as unknown as ReturnType<SupabaseClient['from']>);
124
+ const ctx = createMockContext();
246
125
 
247
126
  await expect(
248
- getTasksAwaitingValidation({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
249
- ).rejects.toThrow('Failed to fetch tasks');
127
+ getTasksAwaitingValidation({ project_id: VALID_UUID }, ctx)
128
+ ).rejects.toThrow('Failed to fetch tasks awaiting validation');
250
129
  });
251
130
  });
252
131
 
@@ -258,15 +137,13 @@ describe('claimValidation', () => {
258
137
  beforeEach(() => vi.clearAllMocks());
259
138
 
260
139
  it('should throw error for missing task_id', async () => {
261
- const supabase = createMockSupabase();
262
- const ctx = createMockContext(supabase);
140
+ const ctx = createMockContext();
263
141
 
264
142
  await expect(claimValidation({}, ctx)).rejects.toThrow(ValidationError);
265
143
  });
266
144
 
267
145
  it('should throw error for invalid task_id UUID', async () => {
268
- const supabase = createMockSupabase();
269
- const ctx = createMockContext(supabase);
146
+ const ctx = createMockContext();
270
147
 
271
148
  await expect(
272
149
  claimValidation({ task_id: 'not-a-uuid' }, ctx)
@@ -274,194 +151,133 @@ describe('claimValidation', () => {
274
151
  });
275
152
 
276
153
  it('should throw error when task not found', async () => {
277
- const supabase = createMockSupabase({
278
- selectResult: { data: null, error: { message: 'Not found' } },
154
+ mockApiClient.claimValidation.mockResolvedValue({
155
+ ok: false,
156
+ error: 'Task not found',
279
157
  });
280
- const ctx = createMockContext(supabase);
158
+ const ctx = createMockContext();
281
159
 
282
160
  await expect(
283
- claimValidation({ task_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
161
+ claimValidation({ task_id: VALID_UUID }, ctx)
284
162
  ).rejects.toThrow('Task not found');
285
163
  });
286
164
 
287
165
  it('should throw error when task is not completed', async () => {
288
- const supabase = createMockSupabase({
289
- selectResult: {
290
- data: {
291
- id: 'task-1',
292
- title: 'Test Task',
293
- status: 'in_progress',
294
- validated_at: null,
295
- completed_by_session_id: 'other-session',
296
- reviewing_by_session_id: null,
297
- },
298
- error: null,
299
- },
166
+ mockApiClient.claimValidation.mockResolvedValue({
167
+ ok: false,
168
+ error: 'Can only claim completed tasks for review',
300
169
  });
301
- const ctx = createMockContext(supabase);
170
+ const ctx = createMockContext();
302
171
 
303
172
  await expect(
304
- claimValidation({ task_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
173
+ claimValidation({ task_id: VALID_UUID }, ctx)
305
174
  ).rejects.toThrow('Can only claim completed tasks for review');
306
175
  });
307
176
 
308
177
  it('should throw error when task is already validated', async () => {
309
- const supabase = createMockSupabase({
310
- selectResult: {
311
- data: {
312
- id: 'task-1',
313
- title: 'Test Task',
314
- status: 'completed',
315
- validated_at: '2025-01-14T12:00:00Z',
316
- completed_by_session_id: 'other-session',
317
- reviewing_by_session_id: null,
318
- },
319
- error: null,
320
- },
178
+ mockApiClient.claimValidation.mockResolvedValue({
179
+ ok: false,
180
+ error: 'Task has already been validated',
321
181
  });
322
- const ctx = createMockContext(supabase);
182
+ const ctx = createMockContext();
323
183
 
324
184
  await expect(
325
- claimValidation({ task_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
185
+ claimValidation({ task_id: VALID_UUID }, ctx)
326
186
  ).rejects.toThrow('Task has already been validated');
327
187
  });
328
188
 
329
189
  it('should throw error when task is being reviewed by another agent', async () => {
330
- const supabase = createMockSupabase({
331
- selectResult: {
332
- data: {
333
- id: 'task-1',
334
- title: 'Test Task',
335
- status: 'completed',
336
- validated_at: null,
337
- completed_by_session_id: 'other-session',
338
- reviewing_by_session_id: 'different-reviewer',
339
- },
340
- error: null,
341
- },
190
+ mockApiClient.claimValidation.mockResolvedValue({
191
+ ok: false,
192
+ error: 'Task is already being reviewed by another agent',
342
193
  });
343
- const ctx = createMockContext(supabase, { sessionId: 'session-123' });
194
+ const ctx = createMockContext({ sessionId: 'session-123' });
344
195
 
345
196
  await expect(
346
- claimValidation({ task_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
197
+ claimValidation({ task_id: VALID_UUID }, ctx)
347
198
  ).rejects.toThrow('Task is already being reviewed by another agent');
348
199
  });
349
200
 
350
201
  it('should allow same agent to re-claim their own review', async () => {
351
- const supabase = createMockSupabase({
352
- selectResult: {
353
- data: {
354
- id: 'task-1',
355
- title: 'Test Task',
356
- status: 'completed',
357
- validated_at: null,
358
- completed_by_session_id: 'other-session',
359
- reviewing_by_session_id: 'session-123', // Same as current session
360
- },
361
- error: null,
202
+ mockApiClient.claimValidation.mockResolvedValue({
203
+ ok: true,
204
+ data: {
205
+ success: true,
206
+ task_id: VALID_UUID,
207
+ title: 'Test Task',
208
+ message: 'Re-claimed task for review',
362
209
  },
363
- updateResult: { data: null, error: null },
364
210
  });
365
- const ctx = createMockContext(supabase, { sessionId: 'session-123' });
211
+ const ctx = createMockContext({ sessionId: 'session-123' });
366
212
 
367
213
  const result = await claimValidation(
368
- { task_id: '123e4567-e89b-12d3-a456-426614174000' },
214
+ { task_id: VALID_UUID },
369
215
  ctx
370
216
  );
371
217
 
372
218
  expect(result.result).toMatchObject({
373
219
  success: true,
374
- task_id: '123e4567-e89b-12d3-a456-426614174000',
220
+ task_id: VALID_UUID,
375
221
  title: 'Test Task',
376
222
  });
377
223
  });
378
224
 
379
225
  it('should claim task successfully', async () => {
380
- const supabase = createMockSupabase({
381
- selectResult: {
382
- data: {
383
- id: 'task-1',
384
- title: 'Test Task',
385
- status: 'completed',
386
- validated_at: null,
387
- completed_by_session_id: 'other-session',
388
- reviewing_by_session_id: null,
389
- },
390
- error: null,
226
+ mockApiClient.claimValidation.mockResolvedValue({
227
+ ok: true,
228
+ data: {
229
+ success: true,
230
+ task_id: VALID_UUID,
231
+ title: 'Test Task',
232
+ message: 'Task claimed for review. Dashboard updated.',
233
+ next_step: 'Run tests and verify implementation. Call validate_task when done.',
391
234
  },
392
- updateResult: { data: null, error: null },
393
235
  });
394
- const ctx = createMockContext(supabase);
236
+ const ctx = createMockContext();
395
237
 
396
238
  const result = await claimValidation(
397
- { task_id: '123e4567-e89b-12d3-a456-426614174000' },
239
+ { task_id: VALID_UUID },
398
240
  ctx
399
241
  );
400
242
 
401
243
  expect(result.result).toMatchObject({
402
244
  success: true,
403
- task_id: '123e4567-e89b-12d3-a456-426614174000',
245
+ task_id: VALID_UUID,
404
246
  title: 'Test Task',
405
247
  message: expect.stringContaining('Dashboard'),
406
248
  });
407
249
  expect((result.result as { next_step: string }).next_step).toContain('validate_task');
408
250
  });
409
251
 
410
- it('should update task with reviewing session info', async () => {
411
- const supabase = createMockSupabase({
412
- selectResult: {
413
- data: {
414
- id: 'task-1',
415
- title: 'Test Task',
416
- status: 'completed',
417
- validated_at: null,
418
- completed_by_session_id: 'other-session',
419
- reviewing_by_session_id: null,
420
- },
421
- error: null,
252
+ it('should call API client with session_id', async () => {
253
+ mockApiClient.claimValidation.mockResolvedValue({
254
+ ok: true,
255
+ data: {
256
+ success: true,
257
+ task_id: VALID_UUID,
258
+ title: 'Test Task',
422
259
  },
423
- updateResult: { data: null, error: null },
424
260
  });
425
- const ctx = createMockContext(supabase, { sessionId: 'reviewer-session' });
261
+ const ctx = createMockContext({ sessionId: 'reviewer-session' });
426
262
 
427
- await claimValidation({ task_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
263
+ await claimValidation({ task_id: VALID_UUID }, ctx);
428
264
 
429
- expect(supabase.update).toHaveBeenCalledWith(
430
- expect.objectContaining({
431
- reviewing_by_session_id: 'reviewer-session',
432
- reviewing_started_at: expect.any(String),
433
- })
265
+ expect(mockApiClient.claimValidation).toHaveBeenCalledWith(
266
+ VALID_UUID,
267
+ 'reviewer-session'
434
268
  );
435
269
  });
436
270
 
437
- it('should throw error when update fails', async () => {
438
- const supabase = createMockSupabase({
439
- selectResult: {
440
- data: {
441
- id: 'task-1',
442
- title: 'Test Task',
443
- status: 'completed',
444
- validated_at: null,
445
- completed_by_session_id: 'other-session',
446
- reviewing_by_session_id: null,
447
- },
448
- error: null,
449
- },
271
+ it('should throw error when claim fails', async () => {
272
+ mockApiClient.claimValidation.mockResolvedValue({
273
+ ok: false,
274
+ error: 'Failed to claim task for validation',
450
275
  });
451
- const ctx = createMockContext(supabase);
452
-
453
- // Override update to return error
454
- vi.mocked(supabase.from('').update).mockReturnValue({
455
- ...supabase,
456
- eq: vi.fn().mockReturnValue({
457
- then: (resolve: (val: unknown) => void) =>
458
- Promise.resolve({ data: null, error: { message: 'Update failed' } }).then(resolve),
459
- }),
460
- } as unknown as ReturnType<SupabaseClient['from']>);
276
+ const ctx = createMockContext();
461
277
 
462
278
  await expect(
463
- claimValidation({ task_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
464
- ).rejects.toThrow('Failed to claim task');
279
+ claimValidation({ task_id: VALID_UUID }, ctx)
280
+ ).rejects.toThrow('Failed to claim task for validation');
465
281
  });
466
282
  });
467
283
 
@@ -473,8 +289,7 @@ describe('validateTask', () => {
473
289
  beforeEach(() => vi.clearAllMocks());
474
290
 
475
291
  it('should throw error for missing task_id', async () => {
476
- const supabase = createMockSupabase();
477
- const ctx = createMockContext(supabase);
292
+ const ctx = createMockContext();
478
293
 
479
294
  await expect(
480
295
  validateTask({ approved: true }, ctx)
@@ -482,122 +297,94 @@ describe('validateTask', () => {
482
297
  });
483
298
 
484
299
  it('should throw error for invalid task_id UUID', async () => {
485
- const supabase = createMockSupabase();
486
- const ctx = createMockContext(supabase);
300
+ const ctx = createMockContext();
487
301
 
488
302
  await expect(
489
303
  validateTask({ task_id: 'invalid', approved: true }, ctx)
490
304
  ).rejects.toThrow(ValidationError);
491
305
  });
492
306
 
307
+ it('should throw error when approved is missing', async () => {
308
+ const ctx = createMockContext();
309
+
310
+ await expect(
311
+ validateTask({ task_id: VALID_UUID }, ctx)
312
+ ).rejects.toThrow('approved is required');
313
+ });
314
+
493
315
  it('should throw error when task not found', async () => {
494
- const supabase = createMockSupabase({
495
- selectResult: { data: null, error: { message: 'Not found' } },
316
+ mockApiClient.validateTask.mockResolvedValue({
317
+ ok: false,
318
+ error: 'Task not found',
496
319
  });
497
- const ctx = createMockContext(supabase);
320
+ const ctx = createMockContext();
498
321
 
499
322
  await expect(
500
- validateTask({ task_id: '123e4567-e89b-12d3-a456-426614174000', approved: true }, ctx)
323
+ validateTask({ task_id: VALID_UUID, approved: true }, ctx)
501
324
  ).rejects.toThrow('Task not found');
502
325
  });
503
326
 
504
327
  it('should throw error when task is not completed', async () => {
505
- const supabase = createMockSupabase({
506
- selectResult: {
507
- data: {
508
- id: 'task-1',
509
- title: 'Test Task',
510
- status: 'in_progress',
511
- validated_at: null,
512
- completed_by_session_id: 'other-session',
513
- project_id: 'proj-1',
514
- git_branch: null,
515
- },
516
- error: null,
517
- },
328
+ mockApiClient.validateTask.mockResolvedValue({
329
+ ok: false,
330
+ error: 'Can only validate completed tasks',
518
331
  });
519
- const ctx = createMockContext(supabase);
332
+ const ctx = createMockContext();
520
333
 
521
334
  await expect(
522
- validateTask({ task_id: '123e4567-e89b-12d3-a456-426614174000', approved: true }, ctx)
335
+ validateTask({ task_id: VALID_UUID, approved: true }, ctx)
523
336
  ).rejects.toThrow('Can only validate completed tasks');
524
337
  });
525
338
 
526
339
  it('should throw error when task already validated', async () => {
527
- const supabase = createMockSupabase({
528
- selectResult: {
529
- data: {
530
- id: 'task-1',
531
- title: 'Test Task',
532
- status: 'completed',
533
- validated_at: '2025-01-14T12:00:00Z',
534
- completed_by_session_id: 'other-session',
535
- project_id: 'proj-1',
536
- git_branch: null,
537
- },
538
- error: null,
539
- },
340
+ mockApiClient.validateTask.mockResolvedValue({
341
+ ok: false,
342
+ error: 'Task has already been validated',
540
343
  });
541
- const ctx = createMockContext(supabase);
344
+ const ctx = createMockContext();
542
345
 
543
346
  await expect(
544
- validateTask({ task_id: '123e4567-e89b-12d3-a456-426614174000', approved: true }, ctx)
347
+ validateTask({ task_id: VALID_UUID, approved: true }, ctx)
545
348
  ).rejects.toThrow('Task has already been validated');
546
349
  });
547
350
 
548
351
  describe('when approved', () => {
549
352
  it('should mark task as validated', async () => {
550
- const supabase = createMockSupabase({
551
- selectResult: {
552
- data: {
553
- id: 'task-1',
554
- title: 'Test Task',
555
- status: 'completed',
556
- validated_at: null,
557
- completed_by_session_id: 'other-session',
558
- project_id: 'proj-1',
559
- git_branch: null,
560
- },
561
- error: null,
353
+ mockApiClient.validateTask.mockResolvedValue({
354
+ ok: true,
355
+ data: {
356
+ success: true,
357
+ validated_task_id: VALID_UUID,
358
+ self_validated: false,
562
359
  },
563
- updateResult: { data: null, error: null },
564
- insertResult: { data: null, error: null },
565
360
  });
566
- const ctx = createMockContext(supabase, { sessionId: 'validator-session' });
361
+ const ctx = createMockContext({ sessionId: 'validator-session' });
567
362
 
568
363
  const result = await validateTask(
569
- { task_id: '123e4567-e89b-12d3-a456-426614174000', approved: true },
364
+ { task_id: VALID_UUID, approved: true },
570
365
  ctx
571
366
  );
572
367
 
573
368
  expect(result.result).toMatchObject({
574
369
  success: true,
575
- validated_task_id: '123e4567-e89b-12d3-a456-426614174000',
370
+ validated_task_id: VALID_UUID,
576
371
  self_validated: false,
577
372
  });
578
373
  });
579
374
 
580
375
  it('should detect self-validation', async () => {
581
- const supabase = createMockSupabase({
582
- selectResult: {
583
- data: {
584
- id: 'task-1',
585
- title: 'Test Task',
586
- status: 'completed',
587
- validated_at: null,
588
- completed_by_session_id: 'session-123', // Same as validator
589
- project_id: 'proj-1',
590
- git_branch: null,
591
- },
592
- error: null,
376
+ mockApiClient.validateTask.mockResolvedValue({
377
+ ok: true,
378
+ data: {
379
+ success: true,
380
+ validated_task_id: VALID_UUID,
381
+ self_validated: true,
593
382
  },
594
- updateResult: { data: null, error: null },
595
- insertResult: { data: null, error: null },
596
383
  });
597
- const ctx = createMockContext(supabase, { sessionId: 'session-123' });
384
+ const ctx = createMockContext({ sessionId: 'session-123' });
598
385
 
599
386
  const result = await validateTask(
600
- { task_id: '123e4567-e89b-12d3-a456-426614174000', approved: true },
387
+ { task_id: VALID_UUID, approved: true },
601
388
  ctx
602
389
  );
603
390
 
@@ -607,274 +394,156 @@ describe('validateTask', () => {
607
394
  });
608
395
  });
609
396
 
610
- it('should clear reviewing status on approval', async () => {
611
- const supabase = createMockSupabase({
612
- selectResult: {
613
- data: {
614
- id: 'task-1',
615
- title: 'Test Task',
616
- status: 'completed',
617
- validated_at: null,
618
- completed_by_session_id: 'other-session',
619
- project_id: 'proj-1',
620
- git_branch: null,
621
- },
622
- error: null,
623
- },
624
- updateResult: { data: null, error: null },
625
- insertResult: { data: null, error: null },
626
- });
627
- const ctx = createMockContext(supabase);
628
-
629
- await validateTask(
630
- { task_id: '123e4567-e89b-12d3-a456-426614174000', approved: true },
631
- ctx
632
- );
633
-
634
- expect(supabase.update).toHaveBeenCalledWith(
635
- expect.objectContaining({
636
- validated_at: expect.any(String),
637
- reviewing_by_session_id: null,
638
- reviewing_started_at: null,
639
- })
640
- );
641
- });
642
-
643
- it('should log validation in progress_logs', async () => {
644
- const supabase = createMockSupabase({
645
- selectResult: {
646
- data: {
647
- id: 'task-1',
648
- title: 'Test Feature',
649
- status: 'completed',
650
- validated_at: null,
651
- completed_by_session_id: 'other-session',
652
- project_id: 'proj-123',
653
- git_branch: null,
654
- },
655
- error: null,
397
+ it('should pass validation_notes to API', async () => {
398
+ mockApiClient.validateTask.mockResolvedValue({
399
+ ok: true,
400
+ data: {
401
+ success: true,
402
+ validated_task_id: VALID_UUID,
403
+ self_validated: false,
656
404
  },
657
- updateResult: { data: null, error: null },
658
- insertResult: { data: null, error: null },
659
405
  });
660
- const ctx = createMockContext(supabase, { sessionId: 'validator-session' });
406
+ const ctx = createMockContext({ sessionId: 'validator-session' });
661
407
 
662
408
  await validateTask(
663
409
  {
664
- task_id: '123e4567-e89b-12d3-a456-426614174000',
410
+ task_id: VALID_UUID,
665
411
  approved: true,
666
412
  validation_notes: 'Looks good!',
667
413
  },
668
414
  ctx
669
415
  );
670
416
 
671
- expect(supabase.from).toHaveBeenCalledWith('progress_logs');
672
- expect(supabase.insert).toHaveBeenCalledWith(
673
- expect.objectContaining({
674
- project_id: 'proj-123',
675
- task_id: '123e4567-e89b-12d3-a456-426614174000',
676
- summary: expect.stringContaining('Validated'),
677
- details: 'Looks good!',
678
- created_by: 'agent',
679
- })
417
+ expect(mockApiClient.validateTask).toHaveBeenCalledWith(
418
+ VALID_UUID,
419
+ { approved: true, validation_notes: 'Looks good!' },
420
+ 'validator-session'
680
421
  );
681
422
  });
682
423
  });
683
424
 
684
425
  describe('when rejected', () => {
685
426
  it('should reopen task', async () => {
686
- const supabase = createMockSupabase({
687
- selectResult: {
688
- data: {
689
- id: 'task-1',
690
- title: 'Test Task',
691
- status: 'completed',
692
- validated_at: null,
693
- completed_by_session_id: 'other-session',
694
- project_id: 'proj-1',
695
- git_branch: null,
696
- },
697
- error: null,
427
+ mockApiClient.validateTask.mockResolvedValue({
428
+ ok: true,
429
+ data: {
430
+ success: true,
431
+ reopened_task_id: VALID_UUID,
698
432
  },
699
- updateResult: { data: null, error: null },
700
- insertResult: { data: null, error: null },
701
433
  });
702
- const ctx = createMockContext(supabase);
434
+ const ctx = createMockContext();
703
435
 
704
436
  const result = await validateTask(
705
- { task_id: '123e4567-e89b-12d3-a456-426614174000', approved: false },
437
+ { task_id: VALID_UUID, approved: false },
706
438
  ctx
707
439
  );
708
440
 
709
441
  expect(result.result).toMatchObject({
710
442
  success: true,
711
- reopened_task_id: '123e4567-e89b-12d3-a456-426614174000',
443
+ reopened_task_id: VALID_UUID,
712
444
  });
713
445
  });
714
446
 
715
- it('should set task to in_progress with 80% progress', async () => {
716
- const supabase = createMockSupabase({
717
- selectResult: {
718
- data: {
719
- id: 'task-1',
720
- title: 'Test Task',
721
- status: 'completed',
722
- validated_at: null,
723
- completed_by_session_id: 'other-session',
724
- project_id: 'proj-1',
725
- git_branch: null,
726
- },
727
- error: null,
728
- },
729
- updateResult: { data: null, error: null },
730
- insertResult: { data: null, error: null },
731
- });
732
- const ctx = createMockContext(supabase);
733
-
734
- await validateTask(
735
- { task_id: '123e4567-e89b-12d3-a456-426614174000', approved: false },
736
- ctx
737
- );
738
-
739
- expect(supabase.update).toHaveBeenCalledWith(
740
- expect.objectContaining({
741
- status: 'in_progress',
742
- completed_at: null,
743
- progress_percentage: 80,
744
- reviewing_by_session_id: null,
745
- reviewing_started_at: null,
746
- })
747
- );
748
- });
749
-
750
- it('should log rejection in progress_logs', async () => {
751
- const supabase = createMockSupabase({
752
- selectResult: {
753
- data: {
754
- id: 'task-1',
755
- title: 'Broken Feature',
756
- status: 'completed',
757
- validated_at: null,
758
- completed_by_session_id: 'other-session',
759
- project_id: 'proj-123',
760
- git_branch: null,
761
- },
762
- error: null,
447
+ it('should pass rejection notes to API', async () => {
448
+ mockApiClient.validateTask.mockResolvedValue({
449
+ ok: true,
450
+ data: {
451
+ success: true,
452
+ reopened_task_id: VALID_UUID,
763
453
  },
764
- updateResult: { data: null, error: null },
765
- insertResult: { data: null, error: null },
766
454
  });
767
- const ctx = createMockContext(supabase);
455
+ const ctx = createMockContext();
768
456
 
769
457
  await validateTask(
770
458
  {
771
- task_id: '123e4567-e89b-12d3-a456-426614174000',
459
+ task_id: VALID_UUID,
772
460
  approved: false,
773
461
  validation_notes: 'Tests failing',
774
462
  },
775
463
  ctx
776
464
  );
777
465
 
778
- expect(supabase.from).toHaveBeenCalledWith('progress_logs');
779
- expect(supabase.insert).toHaveBeenCalledWith(
780
- expect.objectContaining({
781
- summary: expect.stringContaining('Validation failed'),
782
- details: 'Tests failing',
783
- })
466
+ expect(mockApiClient.validateTask).toHaveBeenCalledWith(
467
+ VALID_UUID,
468
+ { approved: false, validation_notes: 'Tests failing' },
469
+ 'session-123'
784
470
  );
785
471
  });
472
+ });
786
473
 
787
- it('should use default message when no validation_notes provided', async () => {
788
- const supabase = createMockSupabase({
789
- selectResult: {
790
- data: {
791
- id: 'task-1',
792
- title: 'Test Task',
793
- status: 'completed',
794
- validated_at: null,
795
- completed_by_session_id: 'other-session',
796
- project_id: 'proj-123',
797
- git_branch: null,
798
- },
799
- error: null,
800
- },
801
- updateResult: { data: null, error: null },
802
- insertResult: { data: null, error: null },
803
- });
804
- const ctx = createMockContext(supabase);
805
-
806
- await validateTask(
807
- { task_id: '123e4567-e89b-12d3-a456-426614174000', approved: false },
808
- ctx
809
- );
810
-
811
- expect(supabase.insert).toHaveBeenCalledWith(
812
- expect.objectContaining({
813
- details: 'Needs more work',
814
- })
815
- );
474
+ it('should throw error when validation fails on approval', async () => {
475
+ mockApiClient.validateTask.mockResolvedValue({
476
+ ok: false,
477
+ error: 'Failed to validate task',
816
478
  });
479
+ const ctx = createMockContext();
480
+
481
+ await expect(
482
+ validateTask({ task_id: VALID_UUID, approved: true }, ctx)
483
+ ).rejects.toThrow('Failed to validate task');
817
484
  });
818
485
 
819
- it('should throw error when update fails on approval', async () => {
820
- const supabase = createMockSupabase({
821
- selectResult: {
822
- data: {
823
- id: 'task-1',
824
- title: 'Test Task',
825
- status: 'completed',
826
- validated_at: null,
827
- completed_by_session_id: 'other-session',
828
- project_id: 'proj-1',
829
- git_branch: null,
830
- },
831
- error: null,
832
- },
486
+ it('should throw error when validation fails on rejection', async () => {
487
+ mockApiClient.validateTask.mockResolvedValue({
488
+ ok: false,
489
+ error: 'Failed to validate task',
833
490
  });
834
- const ctx = createMockContext(supabase);
835
-
836
- // Override update to return error
837
- vi.mocked(supabase.from('').update).mockReturnValue({
838
- ...supabase,
839
- eq: vi.fn().mockReturnValue({
840
- then: (resolve: (val: unknown) => void) =>
841
- Promise.resolve({ data: null, error: { message: 'Update failed' } }).then(resolve),
842
- }),
843
- } as unknown as ReturnType<SupabaseClient['from']>);
491
+ const ctx = createMockContext();
844
492
 
845
493
  await expect(
846
- validateTask({ task_id: '123e4567-e89b-12d3-a456-426614174000', approved: true }, ctx)
494
+ validateTask({ task_id: VALID_UUID, approved: false }, ctx)
847
495
  ).rejects.toThrow('Failed to validate task');
848
496
  });
849
497
 
850
- it('should throw error when update fails on rejection', async () => {
851
- const supabase = createMockSupabase({
852
- selectResult: {
498
+ describe('PR requirement', () => {
499
+ it('should return error when PR is required but not present', async () => {
500
+ mockApiClient.validateTask.mockResolvedValue({
501
+ ok: false,
502
+ error: 'pr_required',
853
503
  data: {
854
- id: 'task-1',
855
- title: 'Test Task',
856
- status: 'completed',
857
- validated_at: null,
858
- completed_by_session_id: 'other-session',
859
- project_id: 'proj-1',
860
- git_branch: null,
504
+ message: 'This project uses git-flow workflow which requires a Pull Request before validation approval.',
505
+ workflow: 'git-flow',
861
506
  },
862
- error: null,
863
- },
507
+ });
508
+ const ctx = createMockContext();
509
+
510
+ const result = await validateTask(
511
+ { task_id: VALID_UUID, approved: true },
512
+ ctx
513
+ );
514
+
515
+ expect(result.result).toMatchObject({
516
+ error: 'pr_required',
517
+ workflow: 'git-flow',
518
+ action_required: expect.stringContaining('add_task_reference'),
519
+ });
864
520
  });
865
- const ctx = createMockContext(supabase);
866
521
 
867
- // Override update to return error
868
- vi.mocked(supabase.from('').update).mockReturnValue({
869
- ...supabase,
870
- eq: vi.fn().mockReturnValue({
871
- then: (resolve: (val: unknown) => void) =>
872
- Promise.resolve({ data: null, error: { message: 'Update failed' } }).then(resolve),
873
- }),
874
- } as unknown as ReturnType<SupabaseClient['from']>);
522
+ it('should pass skip_pr_check to API', async () => {
523
+ mockApiClient.validateTask.mockResolvedValue({
524
+ ok: true,
525
+ data: {
526
+ success: true,
527
+ validated_task_id: VALID_UUID,
528
+ self_validated: false,
529
+ },
530
+ });
531
+ const ctx = createMockContext({ sessionId: 'validator-session' });
532
+
533
+ await validateTask(
534
+ {
535
+ task_id: VALID_UUID,
536
+ approved: true,
537
+ skip_pr_check: true,
538
+ },
539
+ ctx
540
+ );
875
541
 
876
- await expect(
877
- validateTask({ task_id: '123e4567-e89b-12d3-a456-426614174000', approved: false }, ctx)
878
- ).rejects.toThrow('Failed to reopen task');
542
+ expect(mockApiClient.validateTask).toHaveBeenCalledWith(
543
+ VALID_UUID,
544
+ { approved: true, skip_pr_check: true },
545
+ 'validator-session'
546
+ );
547
+ });
879
548
  });
880
549
  });