@vibescope/mcp-server 0.0.1 → 0.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 (170) hide show
  1. package/README.md +113 -98
  2. package/dist/api-client.d.ts +1114 -0
  3. package/dist/api-client.js +698 -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 +106 -476
  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 +112 -828
  16. package/dist/handlers/discovery.js +31 -0
  17. package/dist/handlers/fallback.d.ts +2 -0
  18. package/dist/handlers/fallback.js +39 -134
  19. package/dist/handlers/findings.js +43 -67
  20. package/dist/handlers/git-issues.d.ts +9 -13
  21. package/dist/handlers/git-issues.js +80 -225
  22. package/dist/handlers/ideas.d.ts +3 -0
  23. package/dist/handlers/ideas.js +53 -134
  24. package/dist/handlers/index.d.ts +2 -0
  25. package/dist/handlers/index.js +6 -0
  26. package/dist/handlers/milestones.d.ts +2 -0
  27. package/dist/handlers/milestones.js +51 -98
  28. package/dist/handlers/organizations.js +79 -275
  29. package/dist/handlers/progress.d.ts +2 -0
  30. package/dist/handlers/progress.js +25 -123
  31. package/dist/handlers/project.js +42 -221
  32. package/dist/handlers/requests.d.ts +2 -0
  33. package/dist/handlers/requests.js +23 -83
  34. package/dist/handlers/session.js +99 -585
  35. package/dist/handlers/sprints.d.ts +32 -0
  36. package/dist/handlers/sprints.js +274 -0
  37. package/dist/handlers/tasks.d.ts +7 -10
  38. package/dist/handlers/tasks.js +230 -900
  39. package/dist/handlers/tool-docs.d.ts +8 -0
  40. package/dist/handlers/tool-docs.js +657 -0
  41. package/dist/handlers/types.d.ts +11 -3
  42. package/dist/handlers/validation.d.ts +1 -1
  43. package/dist/handlers/validation.js +26 -153
  44. package/dist/index.js +473 -160
  45. package/dist/knowledge.js +106 -9
  46. package/dist/tools.js +4 -0
  47. package/dist/validators.d.ts +21 -0
  48. package/dist/validators.js +91 -0
  49. package/package.json +2 -3
  50. package/src/api-client.ts +1752 -0
  51. package/src/cli.test.ts +128 -302
  52. package/src/cli.ts +41 -285
  53. package/src/handlers/__test-setup__.ts +210 -0
  54. package/src/handlers/__test-utils__.ts +4 -134
  55. package/src/handlers/blockers.test.ts +114 -124
  56. package/src/handlers/blockers.ts +68 -70
  57. package/src/handlers/bodies-of-work.test.ts +236 -831
  58. package/src/handlers/bodies-of-work.ts +194 -525
  59. package/src/handlers/cost.test.ts +149 -113
  60. package/src/handlers/cost.ts +44 -132
  61. package/src/handlers/decisions.test.ts +111 -209
  62. package/src/handlers/decisions.ts +35 -27
  63. package/src/handlers/deployment.test.ts +193 -239
  64. package/src/handlers/deployment.ts +140 -895
  65. package/src/handlers/discovery.test.ts +20 -67
  66. package/src/handlers/discovery.ts +32 -0
  67. package/src/handlers/fallback.test.ts +128 -361
  68. package/src/handlers/fallback.ts +62 -148
  69. package/src/handlers/findings.test.ts +127 -345
  70. package/src/handlers/findings.ts +49 -66
  71. package/src/handlers/git-issues.test.ts +623 -0
  72. package/src/handlers/git-issues.ts +174 -0
  73. package/src/handlers/ideas.test.ts +229 -343
  74. package/src/handlers/ideas.ts +69 -143
  75. package/src/handlers/index.ts +6 -0
  76. package/src/handlers/milestones.test.ts +167 -281
  77. package/src/handlers/milestones.ts +54 -93
  78. package/src/handlers/organizations.test.ts +275 -467
  79. package/src/handlers/organizations.ts +84 -294
  80. package/src/handlers/progress.test.ts +112 -218
  81. package/src/handlers/progress.ts +29 -142
  82. package/src/handlers/project.test.ts +203 -226
  83. package/src/handlers/project.ts +48 -238
  84. package/src/handlers/requests.test.ts +74 -342
  85. package/src/handlers/requests.ts +25 -83
  86. package/src/handlers/session.test.ts +241 -206
  87. package/src/handlers/session.ts +110 -657
  88. package/src/handlers/sprints.test.ts +711 -0
  89. package/src/handlers/sprints.ts +497 -0
  90. package/src/handlers/tasks.test.ts +608 -353
  91. package/src/handlers/tasks.ts +248 -1025
  92. package/src/handlers/types.ts +12 -4
  93. package/src/handlers/validation.test.ts +189 -572
  94. package/src/handlers/validation.ts +29 -166
  95. package/src/index.ts +473 -184
  96. package/src/knowledge.ts +107 -9
  97. package/src/tools.ts +2506 -0
  98. package/src/validators.test.ts +223 -223
  99. package/src/validators.ts +127 -0
  100. package/tsconfig.json +1 -1
  101. package/vitest.config.ts +14 -13
  102. package/dist/cli.test.d.ts +0 -1
  103. package/dist/cli.test.js +0 -367
  104. package/dist/handlers/__test-utils__.d.ts +0 -72
  105. package/dist/handlers/__test-utils__.js +0 -176
  106. package/dist/handlers/checkouts.d.ts +0 -37
  107. package/dist/handlers/checkouts.js +0 -377
  108. package/dist/handlers/knowledge-query.d.ts +0 -22
  109. package/dist/handlers/knowledge-query.js +0 -253
  110. package/dist/handlers/knowledge.d.ts +0 -12
  111. package/dist/handlers/knowledge.js +0 -108
  112. package/dist/handlers/roles.d.ts +0 -30
  113. package/dist/handlers/roles.js +0 -281
  114. package/dist/handlers/tasks.test.d.ts +0 -1
  115. package/dist/handlers/tasks.test.js +0 -431
  116. package/dist/utils.test.d.ts +0 -1
  117. package/dist/utils.test.js +0 -532
  118. package/dist/validators.test.d.ts +0 -1
  119. package/dist/validators.test.js +0 -176
  120. package/src/tmpclaude-0078-cwd +0 -1
  121. package/src/tmpclaude-0ee1-cwd +0 -1
  122. package/src/tmpclaude-2dd5-cwd +0 -1
  123. package/src/tmpclaude-344c-cwd +0 -1
  124. package/src/tmpclaude-3860-cwd +0 -1
  125. package/src/tmpclaude-4b63-cwd +0 -1
  126. package/src/tmpclaude-5c73-cwd +0 -1
  127. package/src/tmpclaude-5ee3-cwd +0 -1
  128. package/src/tmpclaude-6795-cwd +0 -1
  129. package/src/tmpclaude-709e-cwd +0 -1
  130. package/src/tmpclaude-9839-cwd +0 -1
  131. package/src/tmpclaude-d829-cwd +0 -1
  132. package/src/tmpclaude-e072-cwd +0 -1
  133. package/src/tmpclaude-f6ee-cwd +0 -1
  134. package/tmpclaude-0439-cwd +0 -1
  135. package/tmpclaude-132f-cwd +0 -1
  136. package/tmpclaude-15bb-cwd +0 -1
  137. package/tmpclaude-165a-cwd +0 -1
  138. package/tmpclaude-1ba9-cwd +0 -1
  139. package/tmpclaude-21a3-cwd +0 -1
  140. package/tmpclaude-2a38-cwd +0 -1
  141. package/tmpclaude-2adf-cwd +0 -1
  142. package/tmpclaude-2f56-cwd +0 -1
  143. package/tmpclaude-3626-cwd +0 -1
  144. package/tmpclaude-3727-cwd +0 -1
  145. package/tmpclaude-40bc-cwd +0 -1
  146. package/tmpclaude-436f-cwd +0 -1
  147. package/tmpclaude-4783-cwd +0 -1
  148. package/tmpclaude-4b6d-cwd +0 -1
  149. package/tmpclaude-4ba4-cwd +0 -1
  150. package/tmpclaude-51e6-cwd +0 -1
  151. package/tmpclaude-5ecf-cwd +0 -1
  152. package/tmpclaude-6f97-cwd +0 -1
  153. package/tmpclaude-7fb2-cwd +0 -1
  154. package/tmpclaude-825c-cwd +0 -1
  155. package/tmpclaude-8baf-cwd +0 -1
  156. package/tmpclaude-8d9f-cwd +0 -1
  157. package/tmpclaude-975c-cwd +0 -1
  158. package/tmpclaude-9983-cwd +0 -1
  159. package/tmpclaude-a045-cwd +0 -1
  160. package/tmpclaude-ac4a-cwd +0 -1
  161. package/tmpclaude-b593-cwd +0 -1
  162. package/tmpclaude-b891-cwd +0 -1
  163. package/tmpclaude-c032-cwd +0 -1
  164. package/tmpclaude-cf43-cwd +0 -1
  165. package/tmpclaude-d040-cwd +0 -1
  166. package/tmpclaude-dcdd-cwd +0 -1
  167. package/tmpclaude-dcee-cwd +0 -1
  168. package/tmpclaude-e16b-cwd +0 -1
  169. package/tmpclaude-ecd2-cwd +0 -1
  170. package/tmpclaude-f48d-cwd +0 -1
@@ -1,6 +1,4 @@
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';
1
+ import { describe, it, expect, beforeEach } from 'vitest';
4
2
  import {
5
3
  addMilestone,
6
4
  updateMilestone,
@@ -9,129 +7,18 @@ import {
9
7
  getMilestones,
10
8
  } from './milestones.js';
11
9
  import { ValidationError } from '../validators.js';
12
-
13
- // ============================================================================
14
- // Test Utilities
15
- // ============================================================================
16
-
17
- function createMockSupabase(overrides: {
18
- selectResult?: { data: unknown; error: unknown };
19
- insertResult?: { data: unknown; error: unknown };
20
- updateResult?: { data: unknown; error: unknown };
21
- deleteResult?: { data: unknown; error: unknown };
22
- } = {}) {
23
- const defaultResult = { data: null, error: null };
24
- let currentOperation = 'select';
25
- let insertThenSelect = false;
26
-
27
- const mock = {
28
- from: vi.fn().mockReturnThis(),
29
- select: vi.fn(() => {
30
- if (currentOperation === 'insert' || currentOperation === 'update') {
31
- insertThenSelect = true;
32
- } else {
33
- currentOperation = 'select';
34
- insertThenSelect = false;
35
- }
36
- return mock;
37
- }),
38
- insert: vi.fn(() => {
39
- currentOperation = 'insert';
40
- insertThenSelect = false;
41
- return mock;
42
- }),
43
- update: vi.fn(() => {
44
- currentOperation = 'update';
45
- insertThenSelect = false;
46
- return mock;
47
- }),
48
- delete: vi.fn(() => {
49
- currentOperation = 'delete';
50
- insertThenSelect = false;
51
- return mock;
52
- }),
53
- eq: vi.fn().mockReturnThis(),
54
- neq: vi.fn().mockReturnThis(),
55
- in: vi.fn().mockReturnThis(),
56
- is: vi.fn().mockReturnThis(),
57
- not: vi.fn().mockReturnThis(),
58
- or: vi.fn().mockReturnThis(),
59
- gt: vi.fn().mockReturnThis(),
60
- gte: vi.fn().mockReturnThis(),
61
- lte: vi.fn().mockReturnThis(),
62
- lt: vi.fn().mockReturnThis(),
63
- order: vi.fn().mockReturnThis(),
64
- limit: vi.fn().mockReturnThis(),
65
- single: vi.fn(() => {
66
- if (currentOperation === 'insert' || insertThenSelect) {
67
- return Promise.resolve(overrides.insertResult ?? defaultResult);
68
- }
69
- if (currentOperation === 'select') {
70
- return Promise.resolve(overrides.selectResult ?? defaultResult);
71
- }
72
- if (currentOperation === 'update') {
73
- return Promise.resolve(overrides.updateResult ?? defaultResult);
74
- }
75
- return Promise.resolve(defaultResult);
76
- }),
77
- maybeSingle: vi.fn(() => {
78
- return Promise.resolve(overrides.selectResult ?? defaultResult);
79
- }),
80
- then: vi.fn((resolve: (value: unknown) => void) => {
81
- if (currentOperation === 'insert' || insertThenSelect) {
82
- return Promise.resolve(overrides.insertResult ?? defaultResult).then(resolve);
83
- }
84
- if (currentOperation === 'select') {
85
- return Promise.resolve(overrides.selectResult ?? defaultResult).then(resolve);
86
- }
87
- if (currentOperation === 'update') {
88
- return Promise.resolve(overrides.updateResult ?? defaultResult).then(resolve);
89
- }
90
- if (currentOperation === 'delete') {
91
- return Promise.resolve(overrides.deleteResult ?? defaultResult).then(resolve);
92
- }
93
- return Promise.resolve(defaultResult).then(resolve);
94
- }),
95
- };
96
-
97
- return mock as unknown as SupabaseClient;
98
- }
99
-
100
- function createMockContext(
101
- supabase: SupabaseClient,
102
- options: { sessionId?: string | null } = {}
103
- ): HandlerContext {
104
- const defaultTokenUsage: TokenUsage = {
105
- callCount: 5,
106
- totalTokens: 2500,
107
- byTool: {},
108
- };
109
-
110
- const sessionId = 'sessionId' in options ? options.sessionId : 'session-123';
111
-
112
- return {
113
- supabase,
114
- auth: { userId: 'user-123', apiKeyId: 'api-key-123' },
115
- session: {
116
- instanceId: 'instance-abc',
117
- currentSessionId: sessionId,
118
- currentPersona: 'Wave',
119
- tokenUsage: defaultTokenUsage,
120
- },
121
- updateSession: vi.fn(),
122
- };
123
- }
10
+ import { createMockContext } from './__test-utils__.js';
11
+ import { mockApiClient } from './__test-setup__.js';
124
12
 
125
13
  // ============================================================================
126
14
  // addMilestone Tests
127
15
  // ============================================================================
128
16
 
129
17
  describe('addMilestone', () => {
130
- beforeEach(() => vi.clearAllMocks());
18
+ beforeEach(() => mockApiClient.addMilestone.mockReset());
131
19
 
132
20
  it('should throw error for missing task_id', async () => {
133
- const supabase = createMockSupabase();
134
- const ctx = createMockContext(supabase);
21
+ const ctx = createMockContext();
135
22
 
136
23
  await expect(
137
24
  addMilestone({ title: 'Milestone 1' }, ctx)
@@ -139,8 +26,7 @@ describe('addMilestone', () => {
139
26
  });
140
27
 
141
28
  it('should throw error for invalid task_id UUID', async () => {
142
- const supabase = createMockSupabase();
143
- const ctx = createMockContext(supabase);
29
+ const ctx = createMockContext();
144
30
 
145
31
  await expect(
146
32
  addMilestone({ task_id: 'invalid', title: 'Milestone 1' }, ctx)
@@ -148,42 +34,19 @@ describe('addMilestone', () => {
148
34
  });
149
35
 
150
36
  it('should throw error for missing title', async () => {
151
- const supabase = createMockSupabase();
152
- const ctx = createMockContext(supabase);
37
+ const ctx = createMockContext();
153
38
 
154
39
  await expect(
155
40
  addMilestone({ task_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
156
41
  ).rejects.toThrow(ValidationError);
157
42
  });
158
43
 
159
- it('should throw error when task not found', async () => {
160
- const supabase = createMockSupabase({
161
- selectResult: { data: null, error: { message: 'Not found' } },
162
- });
163
- const ctx = createMockContext(supabase);
164
-
165
- await expect(
166
- addMilestone({
167
- task_id: '123e4567-e89b-12d3-a456-426614174000',
168
- title: 'Milestone 1',
169
- }, ctx)
170
- ).rejects.toThrow('Task not found');
171
- });
172
-
173
44
  it('should add milestone successfully', async () => {
174
- const mockMilestone = {
175
- id: 'milestone-1',
176
- task_id: '123e4567-e89b-12d3-a456-426614174000',
177
- title: 'First Milestone',
178
- order_index: 0,
179
- status: 'pending',
180
- };
181
-
182
- const supabase = createMockSupabase({
183
- selectResult: { data: { id: 'task-1', project_id: 'proj-1' }, error: null },
184
- insertResult: { data: mockMilestone, error: null },
45
+ mockApiClient.addMilestone.mockResolvedValue({
46
+ ok: true,
47
+ data: { milestone_id: 'milestone-1' },
185
48
  });
186
- const ctx = createMockContext(supabase);
49
+ const ctx = createMockContext();
187
50
 
188
51
  const result = await addMilestone(
189
52
  {
@@ -195,54 +58,51 @@ describe('addMilestone', () => {
195
58
 
196
59
  expect(result.result).toMatchObject({
197
60
  success: true,
198
- milestone: mockMilestone,
61
+ milestone_id: 'milestone-1',
199
62
  });
200
63
  });
201
64
 
202
- it('should use provided order_index', async () => {
203
- const supabase = createMockSupabase({
204
- selectResult: { data: { id: 'task-1', project_id: 'proj-1' }, error: null },
205
- insertResult: { data: { id: 'milestone-1', order_index: 5 }, error: null },
65
+ it('should call API client with correct parameters', async () => {
66
+ mockApiClient.addMilestone.mockResolvedValue({
67
+ ok: true,
68
+ data: { milestone_id: 'milestone-1' },
206
69
  });
207
- const ctx = createMockContext(supabase);
70
+ const ctx = createMockContext();
208
71
 
209
72
  await addMilestone(
210
73
  {
211
74
  task_id: '123e4567-e89b-12d3-a456-426614174000',
212
75
  title: 'Test Milestone',
76
+ description: 'A test milestone',
213
77
  order_index: 5,
214
78
  },
215
79
  ctx
216
80
  );
217
81
 
218
- expect(supabase.insert).toHaveBeenCalledWith(
219
- expect.objectContaining({
82
+ expect(mockApiClient.addMilestone).toHaveBeenCalledWith(
83
+ '123e4567-e89b-12d3-a456-426614174000',
84
+ {
85
+ title: 'Test Milestone',
86
+ description: 'A test milestone',
220
87
  order_index: 5,
221
- })
88
+ },
89
+ 'session-123'
222
90
  );
223
91
  });
224
92
 
225
- it('should include session_id in insert', async () => {
226
- const supabase = createMockSupabase({
227
- selectResult: { data: { id: 'task-1', project_id: 'proj-1' }, error: null },
228
- insertResult: { data: { id: 'milestone-1' }, error: null },
93
+ it('should throw error when API call fails', async () => {
94
+ mockApiClient.addMilestone.mockResolvedValue({
95
+ ok: false,
96
+ error: 'Task not found',
229
97
  });
230
- const ctx = createMockContext(supabase, { sessionId: 'my-session' });
98
+ const ctx = createMockContext();
231
99
 
232
- await addMilestone(
233
- {
100
+ await expect(
101
+ addMilestone({
234
102
  task_id: '123e4567-e89b-12d3-a456-426614174000',
235
- title: 'Test',
236
- },
237
- ctx
238
- );
239
-
240
- expect(supabase.insert).toHaveBeenCalledWith(
241
- expect.objectContaining({
242
- created_by: 'agent',
243
- created_by_session_id: 'my-session',
244
- })
245
- );
103
+ title: 'Milestone 1',
104
+ }, ctx)
105
+ ).rejects.toThrow('Failed to add milestone: Task not found');
246
106
  });
247
107
  });
248
108
 
@@ -251,18 +111,16 @@ describe('addMilestone', () => {
251
111
  // ============================================================================
252
112
 
253
113
  describe('updateMilestone', () => {
254
- beforeEach(() => vi.clearAllMocks());
114
+ beforeEach(() => mockApiClient.updateMilestone.mockReset());
255
115
 
256
116
  it('should throw error for missing milestone_id', async () => {
257
- const supabase = createMockSupabase();
258
- const ctx = createMockContext(supabase);
117
+ const ctx = createMockContext();
259
118
 
260
119
  await expect(updateMilestone({}, ctx)).rejects.toThrow(ValidationError);
261
120
  });
262
121
 
263
122
  it('should throw error for invalid milestone_id UUID', async () => {
264
- const supabase = createMockSupabase();
265
- const ctx = createMockContext(supabase);
123
+ const ctx = createMockContext();
266
124
 
267
125
  await expect(
268
126
  updateMilestone({ milestone_id: 'invalid', title: 'New Title' }, ctx)
@@ -270,8 +128,7 @@ describe('updateMilestone', () => {
270
128
  });
271
129
 
272
130
  it('should throw error when no fields to update', async () => {
273
- const supabase = createMockSupabase();
274
- const ctx = createMockContext(supabase);
131
+ const ctx = createMockContext();
275
132
 
276
133
  await expect(
277
134
  updateMilestone({ milestone_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
@@ -279,8 +136,7 @@ describe('updateMilestone', () => {
279
136
  });
280
137
 
281
138
  it('should throw error for invalid status', async () => {
282
- const supabase = createMockSupabase();
283
- const ctx = createMockContext(supabase);
139
+ const ctx = createMockContext();
284
140
 
285
141
  await expect(
286
142
  updateMilestone({
@@ -291,10 +147,11 @@ describe('updateMilestone', () => {
291
147
  });
292
148
 
293
149
  it('should update title successfully', async () => {
294
- const supabase = createMockSupabase({
295
- updateResult: { data: { id: 'milestone-1', title: 'Updated Title' }, error: null },
150
+ mockApiClient.updateMilestone.mockResolvedValue({
151
+ ok: true,
152
+ data: { milestone: { id: 'milestone-1', title: 'Updated Title' } },
296
153
  });
297
- const ctx = createMockContext(supabase);
154
+ const ctx = createMockContext();
298
155
 
299
156
  const result = await updateMilestone(
300
157
  {
@@ -309,11 +166,12 @@ describe('updateMilestone', () => {
309
166
  });
310
167
  });
311
168
 
312
- it('should set completed_at when status is completed', async () => {
313
- const supabase = createMockSupabase({
314
- updateResult: { data: { id: 'milestone-1', status: 'completed' }, error: null },
169
+ it('should call API client with correct parameters', async () => {
170
+ mockApiClient.updateMilestone.mockResolvedValue({
171
+ ok: true,
172
+ data: { milestone: { id: 'milestone-1', status: 'completed' } },
315
173
  });
316
- const ctx = createMockContext(supabase);
174
+ const ctx = createMockContext();
317
175
 
318
176
  await updateMilestone(
319
177
  {
@@ -323,34 +181,30 @@ describe('updateMilestone', () => {
323
181
  ctx
324
182
  );
325
183
 
326
- expect(supabase.update).toHaveBeenCalledWith(
327
- expect.objectContaining({
184
+ expect(mockApiClient.updateMilestone).toHaveBeenCalledWith(
185
+ '123e4567-e89b-12d3-a456-426614174000',
186
+ {
187
+ title: undefined,
188
+ description: undefined,
328
189
  status: 'completed',
329
- completed_at: expect.any(String),
330
- })
190
+ order_index: undefined,
191
+ }
331
192
  );
332
193
  });
333
194
 
334
- it('should clear completed_at when status is not completed', async () => {
335
- const supabase = createMockSupabase({
336
- updateResult: { data: { id: 'milestone-1', status: 'in_progress' }, error: null },
195
+ it('should throw error when API call fails', async () => {
196
+ mockApiClient.updateMilestone.mockResolvedValue({
197
+ ok: false,
198
+ error: 'Milestone not found',
337
199
  });
338
- const ctx = createMockContext(supabase);
200
+ const ctx = createMockContext();
339
201
 
340
- await updateMilestone(
341
- {
202
+ await expect(
203
+ updateMilestone({
342
204
  milestone_id: '123e4567-e89b-12d3-a456-426614174000',
343
- status: 'in_progress',
344
- },
345
- ctx
346
- );
347
-
348
- expect(supabase.update).toHaveBeenCalledWith(
349
- expect.objectContaining({
350
- status: 'in_progress',
351
- completed_at: null,
352
- })
353
- );
205
+ title: 'New Title',
206
+ }, ctx)
207
+ ).rejects.toThrow('Failed to update milestone: Milestone not found');
354
208
  });
355
209
  });
356
210
 
@@ -359,18 +213,16 @@ describe('updateMilestone', () => {
359
213
  // ============================================================================
360
214
 
361
215
  describe('completeMilestone', () => {
362
- beforeEach(() => vi.clearAllMocks());
216
+ beforeEach(() => mockApiClient.completeMilestone.mockReset());
363
217
 
364
218
  it('should throw error for missing milestone_id', async () => {
365
- const supabase = createMockSupabase();
366
- const ctx = createMockContext(supabase);
219
+ const ctx = createMockContext();
367
220
 
368
221
  await expect(completeMilestone({}, ctx)).rejects.toThrow(ValidationError);
369
222
  });
370
223
 
371
224
  it('should throw error for invalid milestone_id UUID', async () => {
372
- const supabase = createMockSupabase();
373
- const ctx = createMockContext(supabase);
225
+ const ctx = createMockContext();
374
226
 
375
227
  await expect(
376
228
  completeMilestone({ milestone_id: 'invalid' }, ctx)
@@ -378,13 +230,11 @@ describe('completeMilestone', () => {
378
230
  });
379
231
 
380
232
  it('should complete milestone successfully', async () => {
381
- const supabase = createMockSupabase({
382
- updateResult: {
383
- data: { id: 'milestone-1', status: 'completed', completed_at: '2025-01-14T12:00:00Z' },
384
- error: null,
385
- },
233
+ mockApiClient.completeMilestone.mockResolvedValue({
234
+ ok: true,
235
+ data: { milestone: { id: 'milestone-1', status: 'completed' } },
386
236
  });
387
- const ctx = createMockContext(supabase);
237
+ const ctx = createMockContext();
388
238
 
389
239
  const result = await completeMilestone(
390
240
  { milestone_id: '123e4567-e89b-12d3-a456-426614174000' },
@@ -396,24 +246,34 @@ describe('completeMilestone', () => {
396
246
  });
397
247
  });
398
248
 
399
- it('should set status to completed with timestamp', async () => {
400
- const supabase = createMockSupabase({
401
- updateResult: { data: { id: 'milestone-1' }, error: null },
249
+ it('should call API client completeMilestone', async () => {
250
+ mockApiClient.completeMilestone.mockResolvedValue({
251
+ ok: true,
252
+ data: { milestone: { id: 'milestone-1' } },
402
253
  });
403
- const ctx = createMockContext(supabase);
254
+ const ctx = createMockContext();
404
255
 
405
256
  await completeMilestone(
406
257
  { milestone_id: '123e4567-e89b-12d3-a456-426614174000' },
407
258
  ctx
408
259
  );
409
260
 
410
- expect(supabase.update).toHaveBeenCalledWith(
411
- expect.objectContaining({
412
- status: 'completed',
413
- completed_at: expect.any(String),
414
- })
261
+ expect(mockApiClient.completeMilestone).toHaveBeenCalledWith(
262
+ '123e4567-e89b-12d3-a456-426614174000'
415
263
  );
416
264
  });
265
+
266
+ it('should throw error when API call fails', async () => {
267
+ mockApiClient.completeMilestone.mockResolvedValue({
268
+ ok: false,
269
+ error: 'Milestone not found',
270
+ });
271
+ const ctx = createMockContext();
272
+
273
+ await expect(
274
+ completeMilestone({ milestone_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
275
+ ).rejects.toThrow('Failed to complete milestone: Milestone not found');
276
+ });
417
277
  });
418
278
 
419
279
  // ============================================================================
@@ -421,18 +281,16 @@ describe('completeMilestone', () => {
421
281
  // ============================================================================
422
282
 
423
283
  describe('deleteMilestone', () => {
424
- beforeEach(() => vi.clearAllMocks());
284
+ beforeEach(() => mockApiClient.deleteMilestone.mockReset());
425
285
 
426
286
  it('should throw error for missing milestone_id', async () => {
427
- const supabase = createMockSupabase();
428
- const ctx = createMockContext(supabase);
287
+ const ctx = createMockContext();
429
288
 
430
289
  await expect(deleteMilestone({}, ctx)).rejects.toThrow(ValidationError);
431
290
  });
432
291
 
433
292
  it('should throw error for invalid milestone_id UUID', async () => {
434
- const supabase = createMockSupabase();
435
- const ctx = createMockContext(supabase);
293
+ const ctx = createMockContext();
436
294
 
437
295
  await expect(
438
296
  deleteMilestone({ milestone_id: 'invalid' }, ctx)
@@ -440,10 +298,11 @@ describe('deleteMilestone', () => {
440
298
  });
441
299
 
442
300
  it('should delete milestone successfully', async () => {
443
- const supabase = createMockSupabase({
444
- deleteResult: { data: null, error: null },
301
+ mockApiClient.deleteMilestone.mockResolvedValue({
302
+ ok: true,
303
+ data: { success: true },
445
304
  });
446
- const ctx = createMockContext(supabase);
305
+ const ctx = createMockContext();
447
306
 
448
307
  const result = await deleteMilestone(
449
308
  { milestone_id: '123e4567-e89b-12d3-a456-426614174000' },
@@ -456,19 +315,33 @@ describe('deleteMilestone', () => {
456
315
  });
457
316
  });
458
317
 
459
- it('should call delete on task_milestones table', async () => {
460
- const supabase = createMockSupabase({
461
- deleteResult: { data: null, error: null },
318
+ it('should call API client deleteMilestone', async () => {
319
+ mockApiClient.deleteMilestone.mockResolvedValue({
320
+ ok: true,
321
+ data: { success: true },
462
322
  });
463
- const ctx = createMockContext(supabase);
323
+ const ctx = createMockContext();
464
324
 
465
325
  await deleteMilestone(
466
326
  { milestone_id: '123e4567-e89b-12d3-a456-426614174000' },
467
327
  ctx
468
328
  );
469
329
 
470
- expect(supabase.from).toHaveBeenCalledWith('task_milestones');
471
- expect(supabase.delete).toHaveBeenCalled();
330
+ expect(mockApiClient.deleteMilestone).toHaveBeenCalledWith(
331
+ '123e4567-e89b-12d3-a456-426614174000'
332
+ );
333
+ });
334
+
335
+ it('should throw error when API call fails', async () => {
336
+ mockApiClient.deleteMilestone.mockResolvedValue({
337
+ ok: false,
338
+ error: 'Milestone not found',
339
+ });
340
+ const ctx = createMockContext();
341
+
342
+ await expect(
343
+ deleteMilestone({ milestone_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
344
+ ).rejects.toThrow('Failed to delete milestone: Milestone not found');
472
345
  });
473
346
  });
474
347
 
@@ -477,18 +350,16 @@ describe('deleteMilestone', () => {
477
350
  // ============================================================================
478
351
 
479
352
  describe('getMilestones', () => {
480
- beforeEach(() => vi.clearAllMocks());
353
+ beforeEach(() => mockApiClient.getMilestones.mockReset());
481
354
 
482
355
  it('should throw error for missing task_id', async () => {
483
- const supabase = createMockSupabase();
484
- const ctx = createMockContext(supabase);
356
+ const ctx = createMockContext();
485
357
 
486
358
  await expect(getMilestones({}, ctx)).rejects.toThrow(ValidationError);
487
359
  });
488
360
 
489
361
  it('should throw error for invalid task_id UUID', async () => {
490
- const supabase = createMockSupabase();
491
- const ctx = createMockContext(supabase);
362
+ const ctx = createMockContext();
492
363
 
493
364
  await expect(
494
365
  getMilestones({ task_id: 'invalid' }, ctx)
@@ -496,10 +367,20 @@ describe('getMilestones', () => {
496
367
  });
497
368
 
498
369
  it('should return empty list with zero stats', async () => {
499
- const supabase = createMockSupabase({
500
- selectResult: { data: [], error: null },
370
+ mockApiClient.getMilestones.mockResolvedValue({
371
+ ok: true,
372
+ data: {
373
+ milestones: [],
374
+ stats: {
375
+ total: 0,
376
+ completed: 0,
377
+ in_progress: 0,
378
+ pending: 0,
379
+ progress_percentage: 0,
380
+ },
381
+ },
501
382
  });
502
- const ctx = createMockContext(supabase);
383
+ const ctx = createMockContext();
503
384
 
504
385
  const result = await getMilestones(
505
386
  { task_id: '123e4567-e89b-12d3-a456-426614174000' },
@@ -518,7 +399,7 @@ describe('getMilestones', () => {
518
399
  });
519
400
  });
520
401
 
521
- it('should return milestones with stats', async () => {
402
+ it('should return milestones with stats from API', async () => {
522
403
  const mockMilestones = [
523
404
  { id: 'm1', title: 'Step 1', status: 'completed', order_index: 0 },
524
405
  { id: 'm2', title: 'Step 2', status: 'in_progress', order_index: 1 },
@@ -526,10 +407,20 @@ describe('getMilestones', () => {
526
407
  { id: 'm4', title: 'Step 4', status: 'pending', order_index: 3 },
527
408
  ];
528
409
 
529
- const supabase = createMockSupabase({
530
- selectResult: { data: mockMilestones, error: null },
410
+ mockApiClient.getMilestones.mockResolvedValue({
411
+ ok: true,
412
+ data: {
413
+ milestones: mockMilestones,
414
+ stats: {
415
+ total: 4,
416
+ completed: 1,
417
+ in_progress: 1,
418
+ pending: 2,
419
+ progress_percentage: 25,
420
+ },
421
+ },
531
422
  });
532
- const ctx = createMockContext(supabase);
423
+ const ctx = createMockContext();
533
424
 
534
425
  const result = await getMilestones(
535
426
  { task_id: '123e4567-e89b-12d3-a456-426614174000' },
@@ -543,42 +434,37 @@ describe('getMilestones', () => {
543
434
  completed: 1,
544
435
  in_progress: 1,
545
436
  pending: 2,
546
- progress_percentage: 25, // 1/4 = 25%
437
+ progress_percentage: 25,
547
438
  },
548
439
  });
549
440
  });
550
441
 
551
- it('should calculate 100% when all completed', async () => {
552
- const mockMilestones = [
553
- { id: 'm1', status: 'completed' },
554
- { id: 'm2', status: 'completed' },
555
- ];
556
-
557
- const supabase = createMockSupabase({
558
- selectResult: { data: mockMilestones, error: null },
442
+ it('should call API client getMilestones', async () => {
443
+ mockApiClient.getMilestones.mockResolvedValue({
444
+ ok: true,
445
+ data: { milestones: [], stats: {} },
559
446
  });
560
- const ctx = createMockContext(supabase);
447
+ const ctx = createMockContext();
561
448
 
562
- const result = await getMilestones(
449
+ await getMilestones(
563
450
  { task_id: '123e4567-e89b-12d3-a456-426614174000' },
564
451
  ctx
565
452
  );
566
453
 
567
- expect((result.result as { stats: { progress_percentage: number } }).stats.progress_percentage).toBe(100);
454
+ expect(mockApiClient.getMilestones).toHaveBeenCalledWith(
455
+ '123e4567-e89b-12d3-a456-426614174000'
456
+ );
568
457
  });
569
458
 
570
- it('should query task_milestones table ordered by order_index', async () => {
571
- const supabase = createMockSupabase({
572
- selectResult: { data: [], error: null },
459
+ it('should throw error when API call fails', async () => {
460
+ mockApiClient.getMilestones.mockResolvedValue({
461
+ ok: false,
462
+ error: 'Task not found',
573
463
  });
574
- const ctx = createMockContext(supabase);
464
+ const ctx = createMockContext();
575
465
 
576
- await getMilestones(
577
- { task_id: '123e4567-e89b-12d3-a456-426614174000' },
578
- ctx
579
- );
580
-
581
- expect(supabase.from).toHaveBeenCalledWith('task_milestones');
582
- expect(supabase.order).toHaveBeenCalledWith('order_index', { ascending: true });
466
+ await expect(
467
+ getMilestones({ task_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
468
+ ).rejects.toThrow('Failed to get milestones: Task not found');
583
469
  });
584
470
  });