@vibescope/mcp-server 0.0.1

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 +98 -0
  2. package/dist/cli.d.ts +34 -0
  3. package/dist/cli.js +356 -0
  4. package/dist/cli.test.d.ts +1 -0
  5. package/dist/cli.test.js +367 -0
  6. package/dist/handlers/__test-utils__.d.ts +72 -0
  7. package/dist/handlers/__test-utils__.js +176 -0
  8. package/dist/handlers/blockers.d.ts +18 -0
  9. package/dist/handlers/blockers.js +81 -0
  10. package/dist/handlers/bodies-of-work.d.ts +34 -0
  11. package/dist/handlers/bodies-of-work.js +614 -0
  12. package/dist/handlers/checkouts.d.ts +37 -0
  13. package/dist/handlers/checkouts.js +377 -0
  14. package/dist/handlers/cost.d.ts +39 -0
  15. package/dist/handlers/cost.js +247 -0
  16. package/dist/handlers/decisions.d.ts +16 -0
  17. package/dist/handlers/decisions.js +64 -0
  18. package/dist/handlers/deployment.d.ts +36 -0
  19. package/dist/handlers/deployment.js +1062 -0
  20. package/dist/handlers/discovery.d.ts +14 -0
  21. package/dist/handlers/discovery.js +870 -0
  22. package/dist/handlers/fallback.d.ts +18 -0
  23. package/dist/handlers/fallback.js +216 -0
  24. package/dist/handlers/findings.d.ts +18 -0
  25. package/dist/handlers/findings.js +110 -0
  26. package/dist/handlers/git-issues.d.ts +22 -0
  27. package/dist/handlers/git-issues.js +247 -0
  28. package/dist/handlers/ideas.d.ts +19 -0
  29. package/dist/handlers/ideas.js +188 -0
  30. package/dist/handlers/index.d.ts +29 -0
  31. package/dist/handlers/index.js +65 -0
  32. package/dist/handlers/knowledge-query.d.ts +22 -0
  33. package/dist/handlers/knowledge-query.js +253 -0
  34. package/dist/handlers/knowledge.d.ts +12 -0
  35. package/dist/handlers/knowledge.js +108 -0
  36. package/dist/handlers/milestones.d.ts +20 -0
  37. package/dist/handlers/milestones.js +179 -0
  38. package/dist/handlers/organizations.d.ts +36 -0
  39. package/dist/handlers/organizations.js +428 -0
  40. package/dist/handlers/progress.d.ts +14 -0
  41. package/dist/handlers/progress.js +149 -0
  42. package/dist/handlers/project.d.ts +20 -0
  43. package/dist/handlers/project.js +278 -0
  44. package/dist/handlers/requests.d.ts +16 -0
  45. package/dist/handlers/requests.js +131 -0
  46. package/dist/handlers/roles.d.ts +30 -0
  47. package/dist/handlers/roles.js +281 -0
  48. package/dist/handlers/session.d.ts +20 -0
  49. package/dist/handlers/session.js +791 -0
  50. package/dist/handlers/tasks.d.ts +52 -0
  51. package/dist/handlers/tasks.js +1111 -0
  52. package/dist/handlers/tasks.test.d.ts +1 -0
  53. package/dist/handlers/tasks.test.js +431 -0
  54. package/dist/handlers/types.d.ts +94 -0
  55. package/dist/handlers/types.js +1 -0
  56. package/dist/handlers/validation.d.ts +16 -0
  57. package/dist/handlers/validation.js +188 -0
  58. package/dist/index.d.ts +2 -0
  59. package/dist/index.js +2707 -0
  60. package/dist/knowledge.d.ts +6 -0
  61. package/dist/knowledge.js +121 -0
  62. package/dist/tools.d.ts +2 -0
  63. package/dist/tools.js +2498 -0
  64. package/dist/utils.d.ts +149 -0
  65. package/dist/utils.js +317 -0
  66. package/dist/utils.test.d.ts +1 -0
  67. package/dist/utils.test.js +532 -0
  68. package/dist/validators.d.ts +35 -0
  69. package/dist/validators.js +111 -0
  70. package/dist/validators.test.d.ts +1 -0
  71. package/dist/validators.test.js +176 -0
  72. package/package.json +44 -0
  73. package/src/cli.test.ts +442 -0
  74. package/src/cli.ts +439 -0
  75. package/src/handlers/__test-utils__.ts +217 -0
  76. package/src/handlers/blockers.test.ts +390 -0
  77. package/src/handlers/blockers.ts +110 -0
  78. package/src/handlers/bodies-of-work.test.ts +1276 -0
  79. package/src/handlers/bodies-of-work.ts +783 -0
  80. package/src/handlers/cost.test.ts +436 -0
  81. package/src/handlers/cost.ts +322 -0
  82. package/src/handlers/decisions.test.ts +401 -0
  83. package/src/handlers/decisions.ts +86 -0
  84. package/src/handlers/deployment.test.ts +516 -0
  85. package/src/handlers/deployment.ts +1289 -0
  86. package/src/handlers/discovery.test.ts +254 -0
  87. package/src/handlers/discovery.ts +969 -0
  88. package/src/handlers/fallback.test.ts +687 -0
  89. package/src/handlers/fallback.ts +260 -0
  90. package/src/handlers/findings.test.ts +565 -0
  91. package/src/handlers/findings.ts +153 -0
  92. package/src/handlers/ideas.test.ts +753 -0
  93. package/src/handlers/ideas.ts +247 -0
  94. package/src/handlers/index.ts +69 -0
  95. package/src/handlers/milestones.test.ts +584 -0
  96. package/src/handlers/milestones.ts +217 -0
  97. package/src/handlers/organizations.test.ts +997 -0
  98. package/src/handlers/organizations.ts +550 -0
  99. package/src/handlers/progress.test.ts +369 -0
  100. package/src/handlers/progress.ts +188 -0
  101. package/src/handlers/project.test.ts +562 -0
  102. package/src/handlers/project.ts +352 -0
  103. package/src/handlers/requests.test.ts +531 -0
  104. package/src/handlers/requests.ts +150 -0
  105. package/src/handlers/session.test.ts +459 -0
  106. package/src/handlers/session.ts +912 -0
  107. package/src/handlers/tasks.test.ts +602 -0
  108. package/src/handlers/tasks.ts +1393 -0
  109. package/src/handlers/types.ts +88 -0
  110. package/src/handlers/validation.test.ts +880 -0
  111. package/src/handlers/validation.ts +223 -0
  112. package/src/index.ts +3205 -0
  113. package/src/knowledge.ts +132 -0
  114. package/src/tmpclaude-0078-cwd +1 -0
  115. package/src/tmpclaude-0ee1-cwd +1 -0
  116. package/src/tmpclaude-2dd5-cwd +1 -0
  117. package/src/tmpclaude-344c-cwd +1 -0
  118. package/src/tmpclaude-3860-cwd +1 -0
  119. package/src/tmpclaude-4b63-cwd +1 -0
  120. package/src/tmpclaude-5c73-cwd +1 -0
  121. package/src/tmpclaude-5ee3-cwd +1 -0
  122. package/src/tmpclaude-6795-cwd +1 -0
  123. package/src/tmpclaude-709e-cwd +1 -0
  124. package/src/tmpclaude-9839-cwd +1 -0
  125. package/src/tmpclaude-d829-cwd +1 -0
  126. package/src/tmpclaude-e072-cwd +1 -0
  127. package/src/tmpclaude-f6ee-cwd +1 -0
  128. package/src/utils.test.ts +681 -0
  129. package/src/utils.ts +375 -0
  130. package/src/validators.test.ts +223 -0
  131. package/src/validators.ts +122 -0
  132. package/tmpclaude-0439-cwd +1 -0
  133. package/tmpclaude-132f-cwd +1 -0
  134. package/tmpclaude-15bb-cwd +1 -0
  135. package/tmpclaude-165a-cwd +1 -0
  136. package/tmpclaude-1ba9-cwd +1 -0
  137. package/tmpclaude-21a3-cwd +1 -0
  138. package/tmpclaude-2a38-cwd +1 -0
  139. package/tmpclaude-2adf-cwd +1 -0
  140. package/tmpclaude-2f56-cwd +1 -0
  141. package/tmpclaude-3626-cwd +1 -0
  142. package/tmpclaude-3727-cwd +1 -0
  143. package/tmpclaude-40bc-cwd +1 -0
  144. package/tmpclaude-436f-cwd +1 -0
  145. package/tmpclaude-4783-cwd +1 -0
  146. package/tmpclaude-4b6d-cwd +1 -0
  147. package/tmpclaude-4ba4-cwd +1 -0
  148. package/tmpclaude-51e6-cwd +1 -0
  149. package/tmpclaude-5ecf-cwd +1 -0
  150. package/tmpclaude-6f97-cwd +1 -0
  151. package/tmpclaude-7fb2-cwd +1 -0
  152. package/tmpclaude-825c-cwd +1 -0
  153. package/tmpclaude-8baf-cwd +1 -0
  154. package/tmpclaude-8d9f-cwd +1 -0
  155. package/tmpclaude-975c-cwd +1 -0
  156. package/tmpclaude-9983-cwd +1 -0
  157. package/tmpclaude-a045-cwd +1 -0
  158. package/tmpclaude-ac4a-cwd +1 -0
  159. package/tmpclaude-b593-cwd +1 -0
  160. package/tmpclaude-b891-cwd +1 -0
  161. package/tmpclaude-c032-cwd +1 -0
  162. package/tmpclaude-cf43-cwd +1 -0
  163. package/tmpclaude-d040-cwd +1 -0
  164. package/tmpclaude-dcdd-cwd +1 -0
  165. package/tmpclaude-dcee-cwd +1 -0
  166. package/tmpclaude-e16b-cwd +1 -0
  167. package/tmpclaude-ecd2-cwd +1 -0
  168. package/tmpclaude-f48d-cwd +1 -0
  169. package/tsconfig.json +16 -0
  170. package/vitest.config.ts +13 -0
@@ -0,0 +1,390 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import {
3
+ addBlocker,
4
+ resolveBlocker,
5
+ getBlockers,
6
+ deleteBlocker,
7
+ } from './blockers.js';
8
+ import { ValidationError } from '../validators.js';
9
+ import { createMockSupabase, createMockContext } from './__test-utils__.js';
10
+
11
+ // ============================================================================
12
+ // addBlocker Tests
13
+ // ============================================================================
14
+
15
+ describe('addBlocker', () => {
16
+ beforeEach(() => vi.clearAllMocks());
17
+
18
+ it('should throw error for missing project_id', async () => {
19
+ const supabase = createMockSupabase();
20
+ const ctx = createMockContext(supabase);
21
+
22
+ await expect(
23
+ addBlocker({ description: 'Some blocker' }, ctx)
24
+ ).rejects.toThrow(ValidationError);
25
+ });
26
+
27
+ it('should throw error for invalid project_id UUID', async () => {
28
+ const supabase = createMockSupabase();
29
+ const ctx = createMockContext(supabase);
30
+
31
+ await expect(
32
+ addBlocker({ project_id: 'invalid', description: 'Some blocker' }, ctx)
33
+ ).rejects.toThrow(ValidationError);
34
+ });
35
+
36
+ it('should throw error for missing description', async () => {
37
+ const supabase = createMockSupabase();
38
+ const ctx = createMockContext(supabase);
39
+
40
+ await expect(
41
+ addBlocker({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
42
+ ).rejects.toThrow(ValidationError);
43
+ });
44
+
45
+ it('should add blocker successfully', async () => {
46
+ const supabase = createMockSupabase({
47
+ insertResult: { data: { id: 'blocker-1' }, error: null },
48
+ });
49
+ const ctx = createMockContext(supabase);
50
+
51
+ const result = await addBlocker(
52
+ {
53
+ project_id: '123e4567-e89b-12d3-a456-426614174000',
54
+ description: 'API is down',
55
+ },
56
+ ctx
57
+ );
58
+
59
+ expect(result.result).toMatchObject({
60
+ success: true,
61
+ blocker_id: 'blocker-1',
62
+ });
63
+ });
64
+
65
+ it('should include session_id in insert', async () => {
66
+ const supabase = createMockSupabase({
67
+ insertResult: { data: { id: 'blocker-1' }, error: null },
68
+ });
69
+ const ctx = createMockContext(supabase, { sessionId: 'my-session' });
70
+
71
+ await addBlocker(
72
+ {
73
+ project_id: '123e4567-e89b-12d3-a456-426614174000',
74
+ description: 'Blocked by dependency',
75
+ },
76
+ ctx
77
+ );
78
+
79
+ expect(supabase.insert).toHaveBeenCalledWith(
80
+ expect.objectContaining({
81
+ created_by: 'agent',
82
+ created_by_session_id: 'my-session',
83
+ })
84
+ );
85
+ });
86
+
87
+ it('should throw error when insert fails', async () => {
88
+ const supabase = createMockSupabase({
89
+ insertResult: { data: null, error: { message: 'Insert failed' } },
90
+ });
91
+ const ctx = createMockContext(supabase);
92
+
93
+ await expect(
94
+ addBlocker({
95
+ project_id: '123e4567-e89b-12d3-a456-426614174000',
96
+ description: 'Test blocker',
97
+ }, ctx)
98
+ ).rejects.toThrow('Failed to add blocker: Insert failed');
99
+ });
100
+ });
101
+
102
+ // ============================================================================
103
+ // resolveBlocker Tests
104
+ // ============================================================================
105
+
106
+ describe('resolveBlocker', () => {
107
+ beforeEach(() => vi.clearAllMocks());
108
+
109
+ it('should throw error for missing blocker_id', async () => {
110
+ const supabase = createMockSupabase();
111
+ const ctx = createMockContext(supabase);
112
+
113
+ await expect(resolveBlocker({}, ctx)).rejects.toThrow(ValidationError);
114
+ });
115
+
116
+ it('should throw error for invalid blocker_id UUID', async () => {
117
+ const supabase = createMockSupabase();
118
+ const ctx = createMockContext(supabase);
119
+
120
+ await expect(
121
+ resolveBlocker({ blocker_id: 'invalid' }, ctx)
122
+ ).rejects.toThrow(ValidationError);
123
+ });
124
+
125
+ it('should resolve blocker successfully', async () => {
126
+ const supabase = createMockSupabase({
127
+ updateResult: { data: null, error: null },
128
+ });
129
+ const ctx = createMockContext(supabase);
130
+
131
+ const result = await resolveBlocker(
132
+ { blocker_id: '123e4567-e89b-12d3-a456-426614174000' },
133
+ ctx
134
+ );
135
+
136
+ expect(result.result).toMatchObject({
137
+ success: true,
138
+ blocker_id: '123e4567-e89b-12d3-a456-426614174000',
139
+ });
140
+ });
141
+
142
+ it('should resolve blocker with resolution note', async () => {
143
+ const supabase = createMockSupabase({
144
+ updateResult: { data: null, error: null },
145
+ });
146
+ const ctx = createMockContext(supabase);
147
+
148
+ await resolveBlocker(
149
+ {
150
+ blocker_id: '123e4567-e89b-12d3-a456-426614174000',
151
+ resolution_note: 'Fixed by upgrading dependency',
152
+ },
153
+ ctx
154
+ );
155
+
156
+ expect(supabase.update).toHaveBeenCalledWith(
157
+ expect.objectContaining({
158
+ status: 'resolved',
159
+ resolution_note: 'Fixed by upgrading dependency',
160
+ })
161
+ );
162
+ });
163
+
164
+ it('should set resolved_at timestamp', async () => {
165
+ const supabase = createMockSupabase({
166
+ updateResult: { data: null, error: null },
167
+ });
168
+ const ctx = createMockContext(supabase);
169
+
170
+ await resolveBlocker(
171
+ { blocker_id: '123e4567-e89b-12d3-a456-426614174000' },
172
+ ctx
173
+ );
174
+
175
+ expect(supabase.update).toHaveBeenCalledWith(
176
+ expect.objectContaining({
177
+ resolved_at: expect.any(String),
178
+ })
179
+ );
180
+ });
181
+
182
+ it('should throw error when update fails', async () => {
183
+ const supabase = createMockSupabase();
184
+ const ctx = createMockContext(supabase);
185
+
186
+ // Override update to return error
187
+ vi.mocked(supabase.from('').update).mockReturnValue({
188
+ ...supabase,
189
+ eq: vi.fn().mockReturnValue({
190
+ then: (resolve: (val: unknown) => void) =>
191
+ Promise.resolve({ data: null, error: { message: 'Update failed' } }).then(resolve),
192
+ }),
193
+ } as unknown as ReturnType<SupabaseClient['from']>);
194
+
195
+ await expect(
196
+ resolveBlocker({ blocker_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
197
+ ).rejects.toThrow('Failed to resolve blocker');
198
+ });
199
+ });
200
+
201
+ // ============================================================================
202
+ // getBlockers Tests
203
+ // ============================================================================
204
+
205
+ describe('getBlockers', () => {
206
+ beforeEach(() => vi.clearAllMocks());
207
+
208
+ it('should throw error for missing project_id', async () => {
209
+ const supabase = createMockSupabase();
210
+ const ctx = createMockContext(supabase);
211
+
212
+ await expect(getBlockers({}, ctx)).rejects.toThrow(ValidationError);
213
+ });
214
+
215
+ it('should throw error for invalid project_id UUID', async () => {
216
+ const supabase = createMockSupabase();
217
+ const ctx = createMockContext(supabase);
218
+
219
+ await expect(
220
+ getBlockers({ project_id: 'invalid' }, ctx)
221
+ ).rejects.toThrow(ValidationError);
222
+ });
223
+
224
+ it('should return empty list when no blockers', async () => {
225
+ const supabase = createMockSupabase({
226
+ selectResult: { data: [], error: null },
227
+ });
228
+ const ctx = createMockContext(supabase);
229
+
230
+ const result = await getBlockers(
231
+ { project_id: '123e4567-e89b-12d3-a456-426614174000' },
232
+ ctx
233
+ );
234
+
235
+ expect(result.result).toMatchObject({
236
+ blockers: [],
237
+ });
238
+ });
239
+
240
+ it('should return blockers list', async () => {
241
+ const mockBlockers = [
242
+ { id: 'b1', description: 'Blocker 1', status: 'open', created_at: '2025-01-14T10:00:00Z' },
243
+ { id: 'b2', description: 'Blocker 2', status: 'open', created_at: '2025-01-14T11:00:00Z' },
244
+ ];
245
+
246
+ const supabase = createMockSupabase({
247
+ selectResult: { data: mockBlockers, error: null },
248
+ });
249
+ const ctx = createMockContext(supabase);
250
+
251
+ const result = await getBlockers(
252
+ { project_id: '123e4567-e89b-12d3-a456-426614174000' },
253
+ ctx
254
+ );
255
+
256
+ expect((result.result as { blockers: unknown[] }).blockers).toHaveLength(2);
257
+ });
258
+
259
+ it('should filter by status (default: open)', async () => {
260
+ const supabase = createMockSupabase({
261
+ selectResult: { data: [], error: null },
262
+ });
263
+ const ctx = createMockContext(supabase);
264
+
265
+ await getBlockers(
266
+ { project_id: '123e4567-e89b-12d3-a456-426614174000' },
267
+ ctx
268
+ );
269
+
270
+ expect(supabase.eq).toHaveBeenCalledWith('status', 'open');
271
+ });
272
+
273
+ it('should filter by custom status', async () => {
274
+ const supabase = createMockSupabase({
275
+ selectResult: { data: [], error: null },
276
+ });
277
+ const ctx = createMockContext(supabase);
278
+
279
+ await getBlockers(
280
+ { project_id: '123e4567-e89b-12d3-a456-426614174000', status: 'resolved' },
281
+ ctx
282
+ );
283
+
284
+ expect(supabase.eq).toHaveBeenCalledWith('status', 'resolved');
285
+ });
286
+
287
+ it('should query blockers table', async () => {
288
+ const supabase = createMockSupabase({
289
+ selectResult: { data: [], error: null },
290
+ });
291
+ const ctx = createMockContext(supabase);
292
+
293
+ await getBlockers(
294
+ { project_id: '123e4567-e89b-12d3-a456-426614174000' },
295
+ ctx
296
+ );
297
+
298
+ expect(supabase.from).toHaveBeenCalledWith('blockers');
299
+ });
300
+
301
+ it('should throw error when query fails', async () => {
302
+ const supabase = createMockSupabase();
303
+ const ctx = createMockContext(supabase);
304
+
305
+ // Override to return error
306
+ vi.mocked(supabase.from('').select).mockReturnValue({
307
+ ...supabase,
308
+ then: (resolve: (val: unknown) => void) =>
309
+ Promise.resolve({ data: null, error: { message: 'Query failed' } }).then(resolve),
310
+ } as unknown as ReturnType<SupabaseClient['from']>);
311
+
312
+ await expect(
313
+ getBlockers({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
314
+ ).rejects.toThrow('Failed to fetch blockers');
315
+ });
316
+ });
317
+
318
+ // ============================================================================
319
+ // deleteBlocker Tests
320
+ // ============================================================================
321
+
322
+ describe('deleteBlocker', () => {
323
+ beforeEach(() => vi.clearAllMocks());
324
+
325
+ it('should throw error for missing blocker_id', async () => {
326
+ const supabase = createMockSupabase();
327
+ const ctx = createMockContext(supabase);
328
+
329
+ await expect(deleteBlocker({}, ctx)).rejects.toThrow(ValidationError);
330
+ });
331
+
332
+ it('should throw error for invalid blocker_id UUID', async () => {
333
+ const supabase = createMockSupabase();
334
+ const ctx = createMockContext(supabase);
335
+
336
+ await expect(
337
+ deleteBlocker({ blocker_id: 'invalid' }, ctx)
338
+ ).rejects.toThrow(ValidationError);
339
+ });
340
+
341
+ it('should delete blocker successfully', async () => {
342
+ const supabase = createMockSupabase({
343
+ deleteResult: { data: null, error: null },
344
+ });
345
+ const ctx = createMockContext(supabase);
346
+
347
+ const result = await deleteBlocker(
348
+ { blocker_id: '123e4567-e89b-12d3-a456-426614174000' },
349
+ ctx
350
+ );
351
+
352
+ expect(result.result).toMatchObject({
353
+ success: true,
354
+ });
355
+ });
356
+
357
+ it('should call delete on blockers table', async () => {
358
+ const supabase = createMockSupabase({
359
+ deleteResult: { data: null, error: null },
360
+ });
361
+ const ctx = createMockContext(supabase);
362
+
363
+ await deleteBlocker(
364
+ { blocker_id: '123e4567-e89b-12d3-a456-426614174000' },
365
+ ctx
366
+ );
367
+
368
+ expect(supabase.from).toHaveBeenCalledWith('blockers');
369
+ expect(supabase.delete).toHaveBeenCalled();
370
+ expect(supabase.eq).toHaveBeenCalledWith('id', '123e4567-e89b-12d3-a456-426614174000');
371
+ });
372
+
373
+ it('should throw error when delete fails', async () => {
374
+ const supabase = createMockSupabase();
375
+ const ctx = createMockContext(supabase);
376
+
377
+ // Override delete to return error
378
+ vi.mocked(supabase.from('').delete).mockReturnValue({
379
+ ...supabase,
380
+ eq: vi.fn().mockReturnValue({
381
+ then: (resolve: (val: unknown) => void) =>
382
+ Promise.resolve({ data: null, error: { message: 'Delete failed' } }).then(resolve),
383
+ }),
384
+ } as unknown as ReturnType<SupabaseClient['from']>);
385
+
386
+ await expect(
387
+ deleteBlocker({ blocker_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
388
+ ).rejects.toThrow('Failed to delete blocker');
389
+ });
390
+ });
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Blockers Handlers
3
+ *
4
+ * Handles blocker management:
5
+ * - add_blocker
6
+ * - resolve_blocker
7
+ * - get_blockers
8
+ * - delete_blocker
9
+ */
10
+
11
+ import type { Handler, HandlerRegistry } from './types.js';
12
+ import { validateRequired, validateUUID } from '../validators.js';
13
+
14
+ export const addBlocker: Handler = async (args, ctx) => {
15
+ const { project_id, description } = args as {
16
+ project_id: string;
17
+ description: string;
18
+ };
19
+
20
+ validateRequired(project_id, 'project_id');
21
+ validateUUID(project_id, 'project_id');
22
+ validateRequired(description, 'description');
23
+
24
+ const { supabase, session } = ctx;
25
+
26
+ const { data, error } = await supabase
27
+ .from('blockers')
28
+ .insert({
29
+ project_id,
30
+ description,
31
+ created_by: 'agent',
32
+ created_by_session_id: session.currentSessionId,
33
+ })
34
+ .select('id')
35
+ .single();
36
+
37
+ if (error) throw new Error(`Failed to add blocker: ${error.message}`);
38
+
39
+ return { result: { success: true, blocker_id: data.id } };
40
+ };
41
+
42
+ export const resolveBlocker: Handler = async (args, ctx) => {
43
+ const { blocker_id, resolution_note } = args as {
44
+ blocker_id: string;
45
+ resolution_note?: string;
46
+ };
47
+
48
+ validateRequired(blocker_id, 'blocker_id');
49
+ validateUUID(blocker_id, 'blocker_id');
50
+
51
+ const { error } = await ctx.supabase
52
+ .from('blockers')
53
+ .update({
54
+ status: 'resolved',
55
+ resolved_at: new Date().toISOString(),
56
+ resolution_note: resolution_note || null,
57
+ })
58
+ .eq('id', blocker_id);
59
+
60
+ if (error) throw new Error(`Failed to resolve blocker: ${error.message}`);
61
+
62
+ return { result: { success: true, blocker_id } };
63
+ };
64
+
65
+ export const getBlockers: Handler = async (args, ctx) => {
66
+ const { project_id, status = 'open' } = args as {
67
+ project_id: string;
68
+ status?: string;
69
+ };
70
+
71
+ validateRequired(project_id, 'project_id');
72
+ validateUUID(project_id, 'project_id');
73
+
74
+ const { data, error } = await ctx.supabase
75
+ .from('blockers')
76
+ .select('id, description, status, created_at, resolved_at, resolution_note')
77
+ .eq('project_id', project_id)
78
+ .eq('status', status)
79
+ .order('created_at', { ascending: false });
80
+
81
+ if (error) throw new Error(`Failed to fetch blockers: ${error.message}`);
82
+
83
+ return { result: { blockers: data || [] } };
84
+ };
85
+
86
+ export const deleteBlocker: Handler = async (args, ctx) => {
87
+ const { blocker_id } = args as { blocker_id: string };
88
+
89
+ validateRequired(blocker_id, 'blocker_id');
90
+ validateUUID(blocker_id, 'blocker_id');
91
+
92
+ const { error } = await ctx.supabase
93
+ .from('blockers')
94
+ .delete()
95
+ .eq('id', blocker_id);
96
+
97
+ if (error) throw new Error(`Failed to delete blocker: ${error.message}`);
98
+
99
+ return { result: { success: true } };
100
+ };
101
+
102
+ /**
103
+ * Blockers handlers registry
104
+ */
105
+ export const blockerHandlers: HandlerRegistry = {
106
+ add_blocker: addBlocker,
107
+ resolve_blocker: resolveBlocker,
108
+ get_blockers: getBlockers,
109
+ delete_blocker: deleteBlocker,
110
+ };