@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,85 +1,24 @@
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 { logProgress, getActivityFeed } from './progress.js';
5
3
  import { ValidationError } from '../validators.js';
6
-
7
- // ============================================================================
8
- // Test Utilities
9
- // ============================================================================
10
-
11
- function createMockSupabase(overrides: {
12
- selectResult?: { data: unknown; error: unknown };
13
- insertResult?: { data: unknown; error: unknown };
14
- } = {}) {
15
- const defaultResult = { data: null, error: null };
16
- let currentOperation = 'select';
17
-
18
- const mock = {
19
- from: vi.fn().mockReturnThis(),
20
- select: vi.fn(() => {
21
- currentOperation = 'select';
22
- return mock;
23
- }),
24
- insert: vi.fn(() => {
25
- currentOperation = 'insert';
26
- return mock;
27
- }),
28
- eq: vi.fn().mockReturnThis(),
29
- gt: vi.fn().mockReturnThis(),
30
- order: vi.fn().mockReturnThis(),
31
- limit: vi.fn().mockReturnThis(),
32
- then: vi.fn((resolve: (value: unknown) => void) => {
33
- if (currentOperation === 'insert') {
34
- return Promise.resolve(overrides.insertResult ?? defaultResult).then(resolve);
35
- }
36
- if (currentOperation === 'select') {
37
- return Promise.resolve(overrides.selectResult ?? defaultResult).then(resolve);
38
- }
39
- return Promise.resolve(defaultResult).then(resolve);
40
- }),
41
- };
42
-
43
- return mock as unknown as SupabaseClient;
44
- }
45
-
46
- function createMockContext(
47
- supabase: SupabaseClient,
48
- options: { sessionId?: string | null } = {}
49
- ): HandlerContext {
50
- const defaultTokenUsage: TokenUsage = {
51
- callCount: 5,
52
- totalTokens: 2500,
53
- byTool: {},
54
- };
55
-
56
- const sessionId = 'sessionId' in options ? options.sessionId : 'session-123';
57
-
58
- return {
59
- supabase,
60
- auth: { userId: 'user-123', apiKeyId: 'api-key-123' },
61
- session: {
62
- instanceId: 'instance-abc',
63
- currentSessionId: sessionId,
64
- currentPersona: 'Wave',
65
- tokenUsage: defaultTokenUsage,
66
- },
67
- updateSession: vi.fn(),
68
- };
69
- }
4
+ import { createMockContext } from './__test-utils__.js';
5
+ import { mockApiClient } from './__test-setup__.js';
70
6
 
71
7
  // ============================================================================
72
8
  // logProgress Tests
73
9
  // ============================================================================
74
10
 
75
11
  describe('logProgress', () => {
76
- beforeEach(() => vi.clearAllMocks());
12
+ beforeEach(() => {
13
+ vi.clearAllMocks();
14
+ });
77
15
 
78
16
  it('should log progress successfully', async () => {
79
- const supabase = createMockSupabase({
80
- insertResult: { data: null, error: null },
17
+ mockApiClient.logProgress.mockResolvedValue({
18
+ ok: true,
19
+ data: { progress_id: 'prog-123' },
81
20
  });
82
- const ctx = createMockContext(supabase);
21
+ const ctx = createMockContext();
83
22
 
84
23
  const result = await logProgress(
85
24
  {
@@ -91,14 +30,16 @@ describe('logProgress', () => {
91
30
 
92
31
  expect(result.result).toMatchObject({
93
32
  success: true,
33
+ progress_id: 'prog-123',
94
34
  });
95
35
  });
96
36
 
97
37
  it('should log progress with task_id and details', async () => {
98
- const supabase = createMockSupabase({
99
- insertResult: { data: null, error: null },
38
+ mockApiClient.logProgress.mockResolvedValue({
39
+ ok: true,
40
+ data: { progress_id: 'prog-456' },
100
41
  });
101
- const ctx = createMockContext(supabase);
42
+ const ctx = createMockContext();
102
43
 
103
44
  await logProgress(
104
45
  {
@@ -110,23 +51,23 @@ describe('logProgress', () => {
110
51
  ctx
111
52
  );
112
53
 
113
- expect(supabase.insert).toHaveBeenCalledWith(
114
- expect.objectContaining({
115
- project_id: '123e4567-e89b-12d3-a456-426614174000',
116
- task_id: '456e4567-e89b-12d3-a456-426614174000',
54
+ expect(mockApiClient.logProgress).toHaveBeenCalledWith(
55
+ '123e4567-e89b-12d3-a456-426614174000',
56
+ {
117
57
  summary: 'Working on feature',
118
58
  details: 'Added the main component and started styling',
119
- created_by: 'agent',
120
- created_by_session_id: 'session-123',
121
- })
59
+ task_id: '456e4567-e89b-12d3-a456-426614174000',
60
+ session_id: 'session-123',
61
+ }
122
62
  );
123
63
  });
124
64
 
125
- it('should include session_id in insert', async () => {
126
- const supabase = createMockSupabase({
127
- insertResult: { data: null, error: null },
65
+ it('should include session_id in request', async () => {
66
+ mockApiClient.logProgress.mockResolvedValue({
67
+ ok: true,
68
+ data: { progress_id: 'prog-789' },
128
69
  });
129
- const ctx = createMockContext(supabase, { sessionId: 'my-session' });
70
+ const ctx = createMockContext({ sessionId: 'my-session' });
130
71
 
131
72
  await logProgress(
132
73
  {
@@ -136,19 +77,20 @@ describe('logProgress', () => {
136
77
  ctx
137
78
  );
138
79
 
139
- expect(supabase.insert).toHaveBeenCalledWith(
80
+ expect(mockApiClient.logProgress).toHaveBeenCalledWith(
81
+ '123e4567-e89b-12d3-a456-426614174000',
140
82
  expect.objectContaining({
141
- created_by: 'agent',
142
- created_by_session_id: 'my-session',
83
+ session_id: 'my-session',
143
84
  })
144
85
  );
145
86
  });
146
87
 
147
- it('should query progress_logs table', async () => {
148
- const supabase = createMockSupabase({
149
- insertResult: { data: null, error: null },
88
+ it('should call API client logProgress', async () => {
89
+ mockApiClient.logProgress.mockResolvedValue({
90
+ ok: true,
91
+ data: { progress_id: 'prog-abc' },
150
92
  });
151
- const ctx = createMockContext(supabase);
93
+ const ctx = createMockContext();
152
94
 
153
95
  await logProgress(
154
96
  {
@@ -158,14 +100,15 @@ describe('logProgress', () => {
158
100
  ctx
159
101
  );
160
102
 
161
- expect(supabase.from).toHaveBeenCalledWith('progress_logs');
103
+ expect(mockApiClient.logProgress).toHaveBeenCalled();
162
104
  });
163
105
 
164
- it('should throw error when insert fails', async () => {
165
- const supabase = createMockSupabase({
166
- insertResult: { data: null, error: { message: 'Insert failed' } },
106
+ it('should throw error when API call fails', async () => {
107
+ mockApiClient.logProgress.mockResolvedValue({
108
+ ok: false,
109
+ error: 'Insert failed',
167
110
  });
168
- const ctx = createMockContext(supabase);
111
+ const ctx = createMockContext();
169
112
 
170
113
  await expect(
171
114
  logProgress({
@@ -174,6 +117,30 @@ describe('logProgress', () => {
174
117
  }, ctx)
175
118
  ).rejects.toThrow('Failed to log progress: Insert failed');
176
119
  });
120
+
121
+ it('should throw error for missing project_id', async () => {
122
+ const ctx = createMockContext();
123
+
124
+ await expect(
125
+ logProgress({ summary: 'Progress' }, ctx)
126
+ ).rejects.toThrow(ValidationError);
127
+ });
128
+
129
+ it('should throw error for invalid project_id UUID', async () => {
130
+ const ctx = createMockContext();
131
+
132
+ await expect(
133
+ logProgress({ project_id: 'invalid', summary: 'Progress' }, ctx)
134
+ ).rejects.toThrow(ValidationError);
135
+ });
136
+
137
+ it('should throw error for missing summary', async () => {
138
+ const ctx = createMockContext();
139
+
140
+ await expect(
141
+ logProgress({ project_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
142
+ ).rejects.toThrow(ValidationError);
143
+ });
177
144
  });
178
145
 
179
146
  // ============================================================================
@@ -181,18 +148,18 @@ describe('logProgress', () => {
181
148
  // ============================================================================
182
149
 
183
150
  describe('getActivityFeed', () => {
184
- beforeEach(() => vi.clearAllMocks());
151
+ beforeEach(() => {
152
+ vi.clearAllMocks();
153
+ });
185
154
 
186
155
  it('should throw error for missing project_id', async () => {
187
- const supabase = createMockSupabase();
188
- const ctx = createMockContext(supabase);
156
+ const ctx = createMockContext();
189
157
 
190
158
  await expect(getActivityFeed({}, ctx)).rejects.toThrow(ValidationError);
191
159
  });
192
160
 
193
161
  it('should throw error for invalid project_id UUID', async () => {
194
- const supabase = createMockSupabase();
195
- const ctx = createMockContext(supabase);
162
+ const ctx = createMockContext();
196
163
 
197
164
  await expect(
198
165
  getActivityFeed({ project_id: 'invalid' }, ctx)
@@ -200,10 +167,11 @@ describe('getActivityFeed', () => {
200
167
  });
201
168
 
202
169
  it('should return empty activities when no data', async () => {
203
- const supabase = createMockSupabase({
204
- selectResult: { data: [], error: null },
170
+ mockApiClient.getActivityFeed.mockResolvedValue({
171
+ ok: true,
172
+ data: { activities: [] },
205
173
  });
206
- const ctx = createMockContext(supabase);
174
+ const ctx = createMockContext();
207
175
 
208
176
  const result = await getActivityFeed(
209
177
  { project_id: '123e4567-e89b-12d3-a456-426614174000' },
@@ -215,50 +183,33 @@ describe('getActivityFeed', () => {
215
183
  });
216
184
  });
217
185
 
218
- it('should query all activity types by default', async () => {
219
- const supabase = createMockSupabase({
220
- selectResult: { data: [], error: null },
186
+ it('should return activities from API', async () => {
187
+ const mockActivities = [
188
+ { id: 'a1', type: 'task', created_at: '2025-01-14T10:00:00Z' },
189
+ { id: 'a2', type: 'progress', created_at: '2025-01-14T11:00:00Z' },
190
+ ];
191
+ mockApiClient.getActivityFeed.mockResolvedValue({
192
+ ok: true,
193
+ data: { activities: mockActivities },
221
194
  });
222
- const ctx = createMockContext(supabase);
195
+ const ctx = createMockContext();
223
196
 
224
- await getActivityFeed(
197
+ const result = await getActivityFeed(
225
198
  { project_id: '123e4567-e89b-12d3-a456-426614174000' },
226
199
  ctx
227
200
  );
228
201
 
229
- // Should query tasks, progress_logs, blockers, decisions
230
- expect(supabase.from).toHaveBeenCalledWith('tasks');
231
- expect(supabase.from).toHaveBeenCalledWith('progress_logs');
232
- expect(supabase.from).toHaveBeenCalledWith('blockers');
233
- expect(supabase.from).toHaveBeenCalledWith('decisions');
234
- });
235
-
236
- it('should filter by specific types', async () => {
237
- const supabase = createMockSupabase({
238
- selectResult: { data: [], error: null },
202
+ expect(result.result).toMatchObject({
203
+ activities: mockActivities,
239
204
  });
240
- const ctx = createMockContext(supabase);
241
-
242
- await getActivityFeed(
243
- {
244
- project_id: '123e4567-e89b-12d3-a456-426614174000',
245
- types: ['task', 'blocker'],
246
- },
247
- ctx
248
- );
249
-
250
- // Should only query tasks and blockers
251
- expect(supabase.from).toHaveBeenCalledWith('tasks');
252
- expect(supabase.from).toHaveBeenCalledWith('blockers');
253
- expect(supabase.from).not.toHaveBeenCalledWith('progress_logs');
254
- expect(supabase.from).not.toHaveBeenCalledWith('decisions');
255
205
  });
256
206
 
257
- it('should respect limit parameter', async () => {
258
- const supabase = createMockSupabase({
259
- selectResult: { data: [], error: null },
207
+ it('should pass limit parameter to API', async () => {
208
+ mockApiClient.getActivityFeed.mockResolvedValue({
209
+ ok: true,
210
+ data: { activities: [] },
260
211
  });
261
- const ctx = createMockContext(supabase);
212
+ const ctx = createMockContext();
262
213
 
263
214
  await getActivityFeed(
264
215
  {
@@ -268,16 +219,20 @@ describe('getActivityFeed', () => {
268
219
  ctx
269
220
  );
270
221
 
271
- expect(supabase.limit).toHaveBeenCalled();
222
+ expect(mockApiClient.getActivityFeed).toHaveBeenCalledWith(
223
+ '123e4567-e89b-12d3-a456-426614174000',
224
+ expect.objectContaining({ limit: 10 })
225
+ );
272
226
  });
273
227
 
274
228
  it('should cap limit at 200', async () => {
275
- const supabase = createMockSupabase({
276
- selectResult: { data: [], error: null },
229
+ mockApiClient.getActivityFeed.mockResolvedValue({
230
+ ok: true,
231
+ data: { activities: [] },
277
232
  });
278
- const ctx = createMockContext(supabase);
233
+ const ctx = createMockContext();
279
234
 
280
- const result = await getActivityFeed(
235
+ await getActivityFeed(
281
236
  {
282
237
  project_id: '123e4567-e89b-12d3-a456-426614174000',
283
238
  limit: 500,
@@ -285,85 +240,24 @@ describe('getActivityFeed', () => {
285
240
  ctx
286
241
  );
287
242
 
288
- // Should return at most 200 items
289
- expect((result.result as { activities: unknown[] }).activities.length).toBeLessThanOrEqual(200);
290
- });
291
-
292
- it('should sort activities by created_at descending', async () => {
293
- const mockTasks = [
294
- { id: 't1', title: 'Task 1', status: 'pending', created_by: 'agent', created_at: '2025-01-14T10:00:00Z' },
295
- ];
296
- const mockProgress = [
297
- { id: 'p1', summary: 'Progress 1', created_by: 'agent', created_at: '2025-01-14T11:00:00Z' },
298
- ];
299
-
300
- let callCount = 0;
301
- const supabase = {
302
- from: vi.fn().mockReturnThis(),
303
- select: vi.fn().mockReturnThis(),
304
- eq: vi.fn().mockReturnThis(),
305
- gt: vi.fn().mockReturnThis(),
306
- order: vi.fn().mockReturnThis(),
307
- limit: vi.fn().mockReturnThis(),
308
- then: vi.fn((resolve: (val: unknown) => void) => {
309
- callCount++;
310
- if (callCount === 1) {
311
- return Promise.resolve({ data: mockTasks, error: null }).then(resolve);
312
- }
313
- if (callCount === 2) {
314
- return Promise.resolve({ data: mockProgress, error: null }).then(resolve);
315
- }
316
- return Promise.resolve({ data: [], error: null }).then(resolve);
317
- }),
318
- } as unknown as SupabaseClient;
319
-
320
- const ctx = createMockContext(supabase);
321
-
322
- const result = await getActivityFeed(
323
- { project_id: '123e4567-e89b-12d3-a456-426614174000' },
324
- ctx
243
+ expect(mockApiClient.getActivityFeed).toHaveBeenCalledWith(
244
+ '123e4567-e89b-12d3-a456-426614174000',
245
+ expect.objectContaining({ limit: 200 })
325
246
  );
326
-
327
- const activities = (result.result as { activities: { created_at: string }[] }).activities;
328
- // Should be sorted by date descending (most recent first)
329
- for (let i = 1; i < activities.length; i++) {
330
- const prevDate = new Date(activities[i - 1].created_at).getTime();
331
- const currDate = new Date(activities[i].created_at).getTime();
332
- expect(prevDate).toBeGreaterThanOrEqual(currDate);
333
- }
334
247
  });
335
248
 
336
- it('should filter by created_by', async () => {
337
- const supabase = createMockSupabase({
338
- selectResult: { data: [], error: null },
249
+ it('should throw error when API call fails', async () => {
250
+ mockApiClient.getActivityFeed.mockResolvedValue({
251
+ ok: false,
252
+ error: 'Query failed',
339
253
  });
340
- const ctx = createMockContext(supabase);
254
+ const ctx = createMockContext();
341
255
 
342
- await getActivityFeed(
343
- {
344
- project_id: '123e4567-e89b-12d3-a456-426614174000',
345
- created_by: 'agent',
346
- },
347
- ctx
348
- );
349
-
350
- expect(supabase.eq).toHaveBeenCalledWith('created_by', 'agent');
351
- });
352
-
353
- it('should filter by since parameter', async () => {
354
- const supabase = createMockSupabase({
355
- selectResult: { data: [], error: null },
356
- });
357
- const ctx = createMockContext(supabase);
358
-
359
- await getActivityFeed(
360
- {
361
- project_id: '123e4567-e89b-12d3-a456-426614174000',
362
- since: '2025-01-14T00:00:00Z',
363
- },
364
- ctx
365
- );
366
-
367
- expect(supabase.gt).toHaveBeenCalledWith('created_at', '2025-01-14T00:00:00Z');
256
+ await expect(
257
+ getActivityFeed(
258
+ { project_id: '123e4567-e89b-12d3-a456-426614174000' },
259
+ ctx
260
+ )
261
+ ).rejects.toThrow('Failed to fetch activity feed: Query failed');
368
262
  });
369
263
  });
@@ -4,10 +4,13 @@
4
4
  * Handles progress logging and activity feed:
5
5
  * - log_progress
6
6
  * - get_activity_feed
7
+ *
8
+ * MIGRATED: Uses Vibescope API client instead of direct Supabase
7
9
  */
8
10
 
9
11
  import type { Handler, HandlerRegistry } from './types.js';
10
12
  import { validateRequired, validateUUID } from '../validators.js';
13
+ import { getApiClient } from '../api-client.js';
11
14
 
12
15
  export const logProgress: Handler = async (args, ctx) => {
13
16
  const { project_id, task_id, summary, details } = args as {
@@ -17,166 +20,50 @@ export const logProgress: Handler = async (args, ctx) => {
17
20
  details?: string;
18
21
  };
19
22
 
20
- const { supabase, session } = ctx;
23
+ validateRequired(project_id, 'project_id');
24
+ validateUUID(project_id, 'project_id');
25
+ validateRequired(summary, 'summary');
26
+
27
+ const { session } = ctx;
28
+ const apiClient = getApiClient();
21
29
 
22
- const { error } = await supabase
23
- .from('progress_logs')
24
- .insert({
25
- project_id,
26
- task_id: task_id || null,
27
- summary,
28
- details: details || null,
29
- created_by: 'agent',
30
- created_by_session_id: session.currentSessionId,
31
- });
30
+ const response = await apiClient.logProgress(project_id, {
31
+ summary,
32
+ details,
33
+ task_id,
34
+ session_id: session.currentSessionId || undefined
35
+ });
32
36
 
33
- if (error) throw new Error(`Failed to log progress: ${error.message}`);
37
+ if (!response.ok) {
38
+ throw new Error(`Failed to log progress: ${response.error}`);
39
+ }
34
40
 
35
- return { result: { success: true } };
41
+ return { result: { success: true, progress_id: response.data?.progress_id } };
36
42
  };
37
43
 
38
44
  export const getActivityFeed: Handler = async (args, ctx) => {
39
- const { project_id, types, created_by, since, limit = 50 } = args as {
45
+ const { project_id, limit = 50, since } = args as {
40
46
  project_id: string;
41
- types?: ('task' | 'progress' | 'blocker' | 'decision')[];
42
- created_by?: 'agent' | 'user';
43
- since?: string;
44
47
  limit?: number;
48
+ since?: string;
45
49
  };
46
50
 
47
51
  validateRequired(project_id, 'project_id');
48
52
  validateUUID(project_id, 'project_id');
49
53
 
50
- const { supabase } = ctx;
54
+ const apiClient = getApiClient();
51
55
  const effectiveLimit = Math.min(limit, 200);
52
- const includeTypes = types || ['task', 'progress', 'blocker', 'decision'];
53
- // Fetch slightly more per type to account for interleaving, but not full limit
54
- const perTypeLimit = Math.min(Math.ceil(effectiveLimit / includeTypes.length) * 2, effectiveLimit);
55
-
56
- interface ActivityItem {
57
- type: string;
58
- id: string;
59
- title?: string;
60
- summary?: string;
61
- description?: string;
62
- status?: string;
63
- created_by: string;
64
- created_at: string;
65
- }
66
-
67
- const activities: ActivityItem[] = [];
68
-
69
- // Fetch tasks if included
70
- if (includeTypes.includes('task')) {
71
- let query = supabase
72
- .from('tasks')
73
- .select('id, title, status, created_by, created_at, completed_at')
74
- .eq('project_id', project_id)
75
- .order('created_at', { ascending: false })
76
- .limit(perTypeLimit);
77
-
78
- if (created_by) query = query.eq('created_by', created_by);
79
- if (since) query = query.gt('created_at', since);
80
56
 
81
- const { data: tasks } = await query;
82
- if (tasks) {
83
- for (const task of tasks) {
84
- activities.push({
85
- type: 'task',
86
- id: task.id,
87
- title: task.title,
88
- status: task.status,
89
- created_by: task.created_by,
90
- created_at: task.created_at,
91
- });
92
- }
93
- }
94
- }
95
-
96
- // Fetch progress logs if included
97
- if (includeTypes.includes('progress')) {
98
- let query = supabase
99
- .from('progress_logs')
100
- .select('id, summary, created_by, created_at')
101
- .eq('project_id', project_id)
102
- .order('created_at', { ascending: false })
103
- .limit(perTypeLimit);
104
-
105
- if (created_by) query = query.eq('created_by', created_by);
106
- if (since) query = query.gt('created_at', since);
107
-
108
- const { data: logs } = await query;
109
- if (logs) {
110
- for (const log of logs) {
111
- activities.push({
112
- type: 'progress',
113
- id: log.id,
114
- summary: log.summary,
115
- created_by: log.created_by,
116
- created_at: log.created_at,
117
- });
118
- }
119
- }
120
- }
121
-
122
- // Fetch blockers if included
123
- if (includeTypes.includes('blocker')) {
124
- let query = supabase
125
- .from('blockers')
126
- .select('id, description, status, created_by, created_at')
127
- .eq('project_id', project_id)
128
- .order('created_at', { ascending: false })
129
- .limit(perTypeLimit);
57
+ const response = await apiClient.getActivityFeed(project_id, {
58
+ limit: effectiveLimit,
59
+ since
60
+ });
130
61
 
131
- if (created_by) query = query.eq('created_by', created_by);
132
- if (since) query = query.gt('created_at', since);
133
-
134
- const { data: blockers } = await query;
135
- if (blockers) {
136
- for (const blocker of blockers) {
137
- activities.push({
138
- type: 'blocker',
139
- id: blocker.id,
140
- description: blocker.description,
141
- status: blocker.status,
142
- created_by: blocker.created_by,
143
- created_at: blocker.created_at,
144
- });
145
- }
146
- }
147
- }
148
-
149
- // Fetch decisions if included
150
- if (includeTypes.includes('decision')) {
151
- let query = supabase
152
- .from('decisions')
153
- .select('id, title, created_by, created_at')
154
- .eq('project_id', project_id)
155
- .order('created_at', { ascending: false })
156
- .limit(perTypeLimit);
157
-
158
- if (created_by) query = query.eq('created_by', created_by);
159
- if (since) query = query.gt('created_at', since);
160
-
161
- const { data: decisions } = await query;
162
- if (decisions) {
163
- for (const decision of decisions) {
164
- activities.push({
165
- type: 'decision',
166
- id: decision.id,
167
- title: decision.title,
168
- created_by: decision.created_by,
169
- created_at: decision.created_at,
170
- });
171
- }
172
- }
62
+ if (!response.ok) {
63
+ throw new Error(`Failed to fetch activity feed: ${response.error}`);
173
64
  }
174
65
 
175
- // Sort by created_at descending and limit
176
- activities.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
177
- const limitedActivities = activities.slice(0, effectiveLimit);
178
-
179
- return { result: { activities: limitedActivities } };
66
+ return { result: { activities: response.data?.activities || [] } };
180
67
  };
181
68
 
182
69
  /**