@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
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
  addFinding,
6
4
  getFindings,
@@ -8,109 +6,8 @@ import {
8
6
  deleteFinding,
9
7
  } from './findings.js';
10
8
  import { ValidationError } from '../validators.js';
11
-
12
- // ============================================================================
13
- // Test Utilities
14
- // ============================================================================
15
-
16
- function createMockSupabase(overrides: {
17
- selectResult?: { data: unknown; error: unknown };
18
- insertResult?: { data: unknown; error: unknown };
19
- updateResult?: { data: unknown; error: unknown };
20
- deleteResult?: { data: unknown; error: unknown };
21
- } = {}) {
22
- const defaultResult = { data: null, error: null };
23
- let currentOperation = 'select';
24
- let insertThenSelect = false;
25
-
26
- const mock = {
27
- from: vi.fn().mockReturnThis(),
28
- select: vi.fn(() => {
29
- if (currentOperation === 'insert') {
30
- insertThenSelect = true;
31
- } else {
32
- currentOperation = 'select';
33
- insertThenSelect = false;
34
- }
35
- return mock;
36
- }),
37
- insert: vi.fn(() => {
38
- currentOperation = 'insert';
39
- insertThenSelect = false;
40
- return mock;
41
- }),
42
- update: vi.fn(() => {
43
- currentOperation = 'update';
44
- insertThenSelect = false;
45
- return mock;
46
- }),
47
- delete: vi.fn(() => {
48
- currentOperation = 'delete';
49
- insertThenSelect = false;
50
- return mock;
51
- }),
52
- eq: vi.fn().mockReturnThis(),
53
- neq: vi.fn().mockReturnThis(),
54
- in: vi.fn().mockReturnThis(),
55
- is: vi.fn().mockReturnThis(),
56
- order: vi.fn().mockReturnThis(),
57
- limit: vi.fn().mockReturnThis(),
58
- single: vi.fn(() => {
59
- if (currentOperation === 'insert' || insertThenSelect) {
60
- return Promise.resolve(overrides.insertResult ?? defaultResult);
61
- }
62
- if (currentOperation === 'select') {
63
- return Promise.resolve(overrides.selectResult ?? defaultResult);
64
- }
65
- if (currentOperation === 'update') {
66
- return Promise.resolve(overrides.updateResult ?? defaultResult);
67
- }
68
- return Promise.resolve(defaultResult);
69
- }),
70
- then: vi.fn((resolve: (value: unknown) => void) => {
71
- if (currentOperation === 'insert' || insertThenSelect) {
72
- return Promise.resolve(overrides.insertResult ?? defaultResult).then(resolve);
73
- }
74
- if (currentOperation === 'select') {
75
- return Promise.resolve(overrides.selectResult ?? defaultResult).then(resolve);
76
- }
77
- if (currentOperation === 'update') {
78
- return Promise.resolve(overrides.updateResult ?? defaultResult).then(resolve);
79
- }
80
- if (currentOperation === 'delete') {
81
- return Promise.resolve(overrides.deleteResult ?? defaultResult).then(resolve);
82
- }
83
- return Promise.resolve(defaultResult).then(resolve);
84
- }),
85
- };
86
-
87
- return mock as unknown as SupabaseClient;
88
- }
89
-
90
- function createMockContext(
91
- supabase: SupabaseClient,
92
- options: { sessionId?: string | null } = {}
93
- ): HandlerContext {
94
- const defaultTokenUsage: TokenUsage = {
95
- callCount: 5,
96
- totalTokens: 2500,
97
- byTool: {},
98
- };
99
-
100
- const sessionId = 'sessionId' in options ? options.sessionId : 'session-123';
101
-
102
- return {
103
- supabase,
104
- auth: { userId: 'user-123', apiKeyId: 'api-key-123' },
105
- session: {
106
- instanceId: 'instance-abc',
107
- currentSessionId: sessionId,
108
- currentPersona: 'Wave',
109
- tokenUsage: defaultTokenUsage,
110
- },
111
- updateSession: vi.fn(),
112
- };
113
- }
9
+ import { createMockContext } from './__test-utils__.js';
10
+ import { mockApiClient } from './__test-setup__.js';
114
11
 
115
12
  const VALID_UUID = '123e4567-e89b-12d3-a456-426614174000';
116
13
  const VALID_UUID_2 = '223e4567-e89b-12d3-a456-426614174001';
@@ -123,15 +20,13 @@ describe('addFinding', () => {
123
20
  beforeEach(() => vi.clearAllMocks());
124
21
 
125
22
  it('should throw error for missing project_id', async () => {
126
- const supabase = createMockSupabase();
127
- const ctx = createMockContext(supabase);
23
+ const ctx = createMockContext();
128
24
 
129
25
  await expect(addFinding({ title: 'Test Finding' }, ctx)).rejects.toThrow(ValidationError);
130
26
  });
131
27
 
132
28
  it('should throw error for invalid project_id UUID', async () => {
133
- const supabase = createMockSupabase();
134
- const ctx = createMockContext(supabase);
29
+ const ctx = createMockContext();
135
30
 
136
31
  await expect(
137
32
  addFinding({ project_id: 'invalid', title: 'Test' }, ctx)
@@ -139,8 +34,7 @@ describe('addFinding', () => {
139
34
  });
140
35
 
141
36
  it('should throw error for missing title', async () => {
142
- const supabase = createMockSupabase();
143
- const ctx = createMockContext(supabase);
37
+ const ctx = createMockContext();
144
38
 
145
39
  await expect(
146
40
  addFinding({ project_id: VALID_UUID }, ctx)
@@ -148,19 +42,19 @@ describe('addFinding', () => {
148
42
  });
149
43
 
150
44
  it('should throw error for invalid related_task_id UUID', async () => {
151
- const supabase = createMockSupabase();
152
- const ctx = createMockContext(supabase);
45
+ const ctx = createMockContext();
153
46
 
154
47
  await expect(
155
48
  addFinding({ project_id: VALID_UUID, title: 'Test', related_task_id: 'invalid' }, ctx)
156
49
  ).rejects.toThrow(ValidationError);
157
50
  });
158
51
 
159
- it('should create finding with required fields and defaults', async () => {
160
- const supabase = createMockSupabase({
161
- insertResult: { data: { id: 'finding-1' }, error: null },
52
+ it('should create finding with required fields', async () => {
53
+ mockApiClient.addFinding.mockResolvedValue({
54
+ ok: true,
55
+ data: { success: true, finding_id: 'finding-1', title: 'Performance issue' },
162
56
  });
163
- const ctx = createMockContext(supabase);
57
+ const ctx = createMockContext();
164
58
 
165
59
  const result = await addFinding(
166
60
  { project_id: VALID_UUID, title: 'Performance issue' },
@@ -172,27 +66,14 @@ describe('addFinding', () => {
172
66
  finding_id: 'finding-1',
173
67
  title: 'Performance issue',
174
68
  });
175
- expect(supabase.from).toHaveBeenCalledWith('findings');
176
- expect(supabase.insert).toHaveBeenCalledWith(
177
- expect.objectContaining({
178
- project_id: VALID_UUID,
179
- title: 'Performance issue',
180
- category: 'other',
181
- severity: 'info',
182
- description: null,
183
- file_path: null,
184
- line_number: null,
185
- related_task_id: null,
186
- created_by: 'agent',
187
- })
188
- );
189
69
  });
190
70
 
191
- it('should create finding with all optional fields', async () => {
192
- const supabase = createMockSupabase({
193
- insertResult: { data: { id: 'finding-2' }, error: null },
71
+ it('should call API client with all parameters', async () => {
72
+ mockApiClient.addFinding.mockResolvedValue({
73
+ ok: true,
74
+ data: { success: true, finding_id: 'finding-2' },
194
75
  });
195
- const ctx = createMockContext(supabase);
76
+ const ctx = createMockContext({ sessionId: 'my-session' });
196
77
 
197
78
  await addFinding(
198
79
  {
@@ -208,8 +89,9 @@ describe('addFinding', () => {
208
89
  ctx
209
90
  );
210
91
 
211
- expect(supabase.insert).toHaveBeenCalledWith(
212
- expect.objectContaining({
92
+ expect(mockApiClient.addFinding).toHaveBeenCalledWith(
93
+ VALID_UUID,
94
+ {
213
95
  title: 'SQL Injection vulnerability',
214
96
  description: 'User input not sanitized',
215
97
  category: 'security',
@@ -217,19 +99,21 @@ describe('addFinding', () => {
217
99
  file_path: 'src/api/users.ts',
218
100
  line_number: 42,
219
101
  related_task_id: VALID_UUID_2,
220
- })
102
+ },
103
+ 'my-session'
221
104
  );
222
105
  });
223
106
 
224
- it('should throw error when database insert fails', async () => {
225
- const supabase = createMockSupabase({
226
- insertResult: { data: null, error: { message: 'Insert failed' } },
107
+ it('should throw error when API call fails', async () => {
108
+ mockApiClient.addFinding.mockResolvedValue({
109
+ ok: false,
110
+ error: 'Insert failed',
227
111
  });
228
- const ctx = createMockContext(supabase);
112
+ const ctx = createMockContext();
229
113
 
230
114
  await expect(
231
115
  addFinding({ project_id: VALID_UUID, title: 'Test' }, ctx)
232
- ).rejects.toThrow('Failed to add finding');
116
+ ).rejects.toThrow('Insert failed');
233
117
  });
234
118
  });
235
119
 
@@ -241,15 +125,13 @@ describe('getFindings', () => {
241
125
  beforeEach(() => vi.clearAllMocks());
242
126
 
243
127
  it('should throw error for missing project_id', async () => {
244
- const supabase = createMockSupabase();
245
- const ctx = createMockContext(supabase);
128
+ const ctx = createMockContext();
246
129
 
247
130
  await expect(getFindings({}, ctx)).rejects.toThrow(ValidationError);
248
131
  });
249
132
 
250
133
  it('should throw error for invalid project_id UUID', async () => {
251
- const supabase = createMockSupabase();
252
- const ctx = createMockContext(supabase);
134
+ const ctx = createMockContext();
253
135
 
254
136
  await expect(
255
137
  getFindings({ project_id: 'invalid' }, ctx)
@@ -261,83 +143,68 @@ describe('getFindings', () => {
261
143
  { id: 'f1', title: 'Finding 1', category: 'security', severity: 'high', status: 'open', file_path: null, created_at: '2026-01-14' },
262
144
  { id: 'f2', title: 'Finding 2', category: 'performance', severity: 'medium', status: 'addressed', file_path: 'src/app.ts', created_at: '2026-01-13' },
263
145
  ];
264
-
265
- const supabase = createMockSupabase({
266
- selectResult: { data: mockFindings, error: null },
146
+ mockApiClient.getFindings.mockResolvedValue({
147
+ ok: true,
148
+ data: { findings: mockFindings },
267
149
  });
268
- const ctx = createMockContext(supabase);
150
+ const ctx = createMockContext();
269
151
 
270
152
  const result = await getFindings({ project_id: VALID_UUID }, ctx);
271
153
 
272
154
  expect(result.result).toMatchObject({ findings: mockFindings });
273
- expect(supabase.from).toHaveBeenCalledWith('findings');
274
- expect(supabase.eq).toHaveBeenCalledWith('project_id', VALID_UUID);
275
- });
276
-
277
- it('should filter by category when provided', async () => {
278
- const supabase = createMockSupabase({
279
- selectResult: { data: [], error: null },
280
- });
281
- const ctx = createMockContext(supabase);
282
-
283
- await getFindings({ project_id: VALID_UUID, category: 'security' }, ctx);
284
-
285
- expect(supabase.eq).toHaveBeenCalledWith('category', 'security');
286
- });
287
-
288
- it('should filter by severity when provided', async () => {
289
- const supabase = createMockSupabase({
290
- selectResult: { data: [], error: null },
291
- });
292
- const ctx = createMockContext(supabase);
293
-
294
- await getFindings({ project_id: VALID_UUID, severity: 'critical' }, ctx);
295
-
296
- expect(supabase.eq).toHaveBeenCalledWith('severity', 'critical');
297
155
  });
298
156
 
299
- it('should filter by status when provided', async () => {
300
- const supabase = createMockSupabase({
301
- selectResult: { data: [], error: null },
157
+ it('should pass filters to API client', async () => {
158
+ mockApiClient.getFindings.mockResolvedValue({
159
+ ok: true,
160
+ data: { findings: [] },
302
161
  });
303
- const ctx = createMockContext(supabase);
304
-
305
- await getFindings({ project_id: VALID_UUID, status: 'open' }, ctx);
306
-
307
- expect(supabase.eq).toHaveBeenCalledWith('status', 'open');
308
- });
309
-
310
- it('should use custom limit when provided', async () => {
311
- const supabase = createMockSupabase({
312
- selectResult: { data: [], error: null },
313
- });
314
- const ctx = createMockContext(supabase);
315
-
316
- await getFindings({ project_id: VALID_UUID, limit: 10 }, ctx);
317
-
318
- expect(supabase.limit).toHaveBeenCalledWith(10);
162
+ const ctx = createMockContext();
163
+
164
+ await getFindings({
165
+ project_id: VALID_UUID,
166
+ category: 'security',
167
+ severity: 'critical',
168
+ status: 'open',
169
+ limit: 10
170
+ }, ctx);
171
+
172
+ expect(mockApiClient.getFindings).toHaveBeenCalledWith(
173
+ VALID_UUID,
174
+ {
175
+ category: 'security',
176
+ severity: 'critical',
177
+ status: 'open',
178
+ limit: 10,
179
+ }
180
+ );
319
181
  });
320
182
 
321
183
  it('should use default limit of 50', async () => {
322
- const supabase = createMockSupabase({
323
- selectResult: { data: [], error: null },
184
+ mockApiClient.getFindings.mockResolvedValue({
185
+ ok: true,
186
+ data: { findings: [] },
324
187
  });
325
- const ctx = createMockContext(supabase);
188
+ const ctx = createMockContext();
326
189
 
327
190
  await getFindings({ project_id: VALID_UUID }, ctx);
328
191
 
329
- expect(supabase.limit).toHaveBeenCalledWith(50);
192
+ expect(mockApiClient.getFindings).toHaveBeenCalledWith(
193
+ VALID_UUID,
194
+ expect.objectContaining({ limit: 50 })
195
+ );
330
196
  });
331
197
 
332
- it('should throw error when database query fails', async () => {
333
- const supabase = createMockSupabase({
334
- selectResult: { data: null, error: { message: 'Query failed' } },
198
+ it('should throw error when API call fails', async () => {
199
+ mockApiClient.getFindings.mockResolvedValue({
200
+ ok: false,
201
+ error: 'Query failed',
335
202
  });
336
- const ctx = createMockContext(supabase);
203
+ const ctx = createMockContext();
337
204
 
338
205
  await expect(
339
206
  getFindings({ project_id: VALID_UUID }, ctx)
340
- ).rejects.toThrow('Failed to get findings');
207
+ ).rejects.toThrow('Query failed');
341
208
  });
342
209
  });
343
210
 
@@ -349,15 +216,13 @@ describe('updateFinding', () => {
349
216
  beforeEach(() => vi.clearAllMocks());
350
217
 
351
218
  it('should throw error for missing finding_id', async () => {
352
- const supabase = createMockSupabase();
353
- const ctx = createMockContext(supabase);
219
+ const ctx = createMockContext();
354
220
 
355
221
  await expect(updateFinding({}, ctx)).rejects.toThrow(ValidationError);
356
222
  });
357
223
 
358
224
  it('should throw error for invalid finding_id UUID', async () => {
359
- const supabase = createMockSupabase();
360
- const ctx = createMockContext(supabase);
225
+ const ctx = createMockContext();
361
226
 
362
227
  await expect(
363
228
  updateFinding({ finding_id: 'invalid' }, ctx)
@@ -365,10 +230,11 @@ describe('updateFinding', () => {
365
230
  });
366
231
 
367
232
  it('should update title', async () => {
368
- const supabase = createMockSupabase({
369
- updateResult: { data: null, error: null },
233
+ mockApiClient.updateFinding.mockResolvedValue({
234
+ ok: true,
235
+ data: { success: true, finding_id: VALID_UUID },
370
236
  });
371
- const ctx = createMockContext(supabase);
237
+ const ctx = createMockContext();
372
238
 
373
239
  const result = await updateFinding(
374
240
  { finding_id: VALID_UUID, title: 'Updated Title' },
@@ -376,142 +242,49 @@ describe('updateFinding', () => {
376
242
  );
377
243
 
378
244
  expect(result.result).toMatchObject({ success: true, finding_id: VALID_UUID });
379
- expect(supabase.update).toHaveBeenCalledWith(
380
- expect.objectContaining({ title: 'Updated Title' })
381
- );
382
245
  });
383
246
 
384
- it('should update severity', async () => {
385
- const supabase = createMockSupabase({
386
- updateResult: { data: null, error: null },
247
+ it('should call API client with all update fields', async () => {
248
+ mockApiClient.updateFinding.mockResolvedValue({
249
+ ok: true,
250
+ data: { success: true },
387
251
  });
388
- const ctx = createMockContext(supabase);
252
+ const ctx = createMockContext();
389
253
 
390
254
  await updateFinding(
391
- { finding_id: VALID_UUID, severity: 'high' },
392
- ctx
393
- );
394
-
395
- expect(supabase.update).toHaveBeenCalledWith(
396
- expect.objectContaining({ severity: 'high' })
397
- );
398
- });
399
-
400
- it('should set addressed_at when status changed to addressed', async () => {
401
- const supabase = createMockSupabase({
402
- updateResult: { data: null, error: null },
403
- });
404
- const ctx = createMockContext(supabase, { sessionId: 'resolver-session' });
405
-
406
- await updateFinding(
407
- { finding_id: VALID_UUID, status: 'addressed' },
408
- ctx
409
- );
410
-
411
- expect(supabase.update).toHaveBeenCalledWith(
412
- expect.objectContaining({
255
+ {
256
+ finding_id: VALID_UUID,
257
+ title: 'New Title',
258
+ description: 'New description',
259
+ severity: 'high',
413
260
  status: 'addressed',
414
- addressed_at: expect.any(String),
415
- addressed_by_session_id: 'resolver-session',
416
- })
417
- );
418
- });
419
-
420
- it('should set addressed_at when status changed to dismissed', async () => {
421
- const supabase = createMockSupabase({
422
- updateResult: { data: null, error: null },
423
- });
424
- const ctx = createMockContext(supabase);
425
-
426
- await updateFinding(
427
- { finding_id: VALID_UUID, status: 'dismissed' },
428
- ctx
429
- );
430
-
431
- expect(supabase.update).toHaveBeenCalledWith(
432
- expect.objectContaining({
433
- status: 'dismissed',
434
- addressed_at: expect.any(String),
435
- })
436
- );
437
- });
438
-
439
- it('should set addressed_at when status changed to wontfix', async () => {
440
- const supabase = createMockSupabase({
441
- updateResult: { data: null, error: null },
442
- });
443
- const ctx = createMockContext(supabase);
444
-
445
- await updateFinding(
446
- { finding_id: VALID_UUID, status: 'wontfix' },
447
- ctx
448
- );
449
-
450
- expect(supabase.update).toHaveBeenCalledWith(
451
- expect.objectContaining({
452
- status: 'wontfix',
453
- addressed_at: expect.any(String),
454
- })
455
- );
456
- });
457
-
458
- it('should not set addressed_at when status changed to open', async () => {
459
- const supabase = createMockSupabase({
460
- updateResult: { data: null, error: null },
461
- });
462
- const ctx = createMockContext(supabase);
463
-
464
- await updateFinding(
465
- { finding_id: VALID_UUID, status: 'open' },
466
- ctx
467
- );
468
-
469
- const updateCall = vi.mocked(supabase.update).mock.calls[0][0] as Record<string, unknown>;
470
- expect(updateCall.status).toBe('open');
471
- expect(updateCall.addressed_at).toBeUndefined();
472
- });
473
-
474
- it('should update resolution_note', async () => {
475
- const supabase = createMockSupabase({
476
- updateResult: { data: null, error: null },
477
- });
478
- const ctx = createMockContext(supabase);
479
-
480
- await updateFinding(
481
- { finding_id: VALID_UUID, resolution_note: 'Fixed by sanitizing input' },
482
- ctx
483
- );
484
-
485
- expect(supabase.update).toHaveBeenCalledWith(
486
- expect.objectContaining({ resolution_note: 'Fixed by sanitizing input' })
487
- );
488
- });
489
-
490
- it('should always update updated_at timestamp', async () => {
491
- const supabase = createMockSupabase({
492
- updateResult: { data: null, error: null },
493
- });
494
- const ctx = createMockContext(supabase);
495
-
496
- await updateFinding(
497
- { finding_id: VALID_UUID, title: 'Test' },
261
+ resolution_note: 'Fixed by sanitizing input'
262
+ },
498
263
  ctx
499
264
  );
500
265
 
501
- expect(supabase.update).toHaveBeenCalledWith(
502
- expect.objectContaining({ updated_at: expect.any(String) })
266
+ expect(mockApiClient.updateFinding).toHaveBeenCalledWith(
267
+ VALID_UUID,
268
+ {
269
+ title: 'New Title',
270
+ description: 'New description',
271
+ severity: 'high',
272
+ status: 'addressed',
273
+ resolution_note: 'Fixed by sanitizing input',
274
+ }
503
275
  );
504
276
  });
505
277
 
506
- it('should throw error when database update fails', async () => {
507
- const supabase = createMockSupabase({
508
- updateResult: { data: null, error: { message: 'Update failed' } },
278
+ it('should throw error when API call fails', async () => {
279
+ mockApiClient.updateFinding.mockResolvedValue({
280
+ ok: false,
281
+ error: 'Update failed',
509
282
  });
510
- const ctx = createMockContext(supabase);
283
+ const ctx = createMockContext();
511
284
 
512
285
  await expect(
513
286
  updateFinding({ finding_id: VALID_UUID, title: 'Test' }, ctx)
514
- ).rejects.toThrow('Failed to update finding');
287
+ ).rejects.toThrow('Update failed');
515
288
  });
516
289
  });
517
290
 
@@ -523,15 +296,13 @@ describe('deleteFinding', () => {
523
296
  beforeEach(() => vi.clearAllMocks());
524
297
 
525
298
  it('should throw error for missing finding_id', async () => {
526
- const supabase = createMockSupabase();
527
- const ctx = createMockContext(supabase);
299
+ const ctx = createMockContext();
528
300
 
529
301
  await expect(deleteFinding({}, ctx)).rejects.toThrow(ValidationError);
530
302
  });
531
303
 
532
304
  it('should throw error for invalid finding_id UUID', async () => {
533
- const supabase = createMockSupabase();
534
- const ctx = createMockContext(supabase);
305
+ const ctx = createMockContext();
535
306
 
536
307
  await expect(
537
308
  deleteFinding({ finding_id: 'invalid' }, ctx)
@@ -539,27 +310,38 @@ describe('deleteFinding', () => {
539
310
  });
540
311
 
541
312
  it('should delete finding successfully', async () => {
542
- const supabase = createMockSupabase({
543
- deleteResult: { data: null, error: null },
313
+ mockApiClient.deleteFinding.mockResolvedValue({
314
+ ok: true,
315
+ data: { success: true },
544
316
  });
545
- const ctx = createMockContext(supabase);
317
+ const ctx = createMockContext();
546
318
 
547
319
  const result = await deleteFinding({ finding_id: VALID_UUID }, ctx);
548
320
 
549
321
  expect(result.result).toMatchObject({ success: true });
550
- expect(supabase.from).toHaveBeenCalledWith('findings');
551
- expect(supabase.delete).toHaveBeenCalled();
552
- expect(supabase.eq).toHaveBeenCalledWith('id', VALID_UUID);
553
322
  });
554
323
 
555
- it('should throw error when database delete fails', async () => {
556
- const supabase = createMockSupabase({
557
- deleteResult: { data: null, error: { message: 'Delete failed' } },
324
+ it('should call API client deleteFinding', async () => {
325
+ mockApiClient.deleteFinding.mockResolvedValue({
326
+ ok: true,
327
+ data: { success: true },
328
+ });
329
+ const ctx = createMockContext();
330
+
331
+ await deleteFinding({ finding_id: VALID_UUID }, ctx);
332
+
333
+ expect(mockApiClient.deleteFinding).toHaveBeenCalledWith(VALID_UUID);
334
+ });
335
+
336
+ it('should throw error when API call fails', async () => {
337
+ mockApiClient.deleteFinding.mockResolvedValue({
338
+ ok: false,
339
+ error: 'Delete failed',
558
340
  });
559
- const ctx = createMockContext(supabase);
341
+ const ctx = createMockContext();
560
342
 
561
343
  await expect(
562
344
  deleteFinding({ finding_id: VALID_UUID }, ctx)
563
- ).rejects.toThrow('Failed to delete finding');
345
+ ).rejects.toThrow('Delete failed');
564
346
  });
565
347
  });