@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
@@ -14,10 +14,13 @@
14
14
  * - remove_task_dependency
15
15
  * - get_task_dependencies
16
16
  * - get_next_body_of_work_task
17
+ *
18
+ * MIGRATED: Uses Vibescope API client instead of direct Supabase
17
19
  */
18
20
 
19
21
  import type { Handler, HandlerRegistry } from './types.js';
20
22
  import { validateRequired, validateUUID } from '../validators.js';
23
+ import { getApiClient } from '../api-client.js';
21
24
 
22
25
  type BodyOfWorkStatus = 'draft' | 'active' | 'completed' | 'cancelled';
23
26
  type TaskPhase = 'pre' | 'core' | 'post';
@@ -48,30 +51,34 @@ export const createBodyOfWork: Handler = async (args, ctx) => {
48
51
  validateUUID(project_id, 'project_id');
49
52
  validateRequired(title, 'title');
50
53
 
51
- const { supabase, session } = ctx;
54
+ const { session } = ctx;
55
+ const apiClient = getApiClient();
52
56
 
53
- const { data, error } = await supabase
54
- .from('bodies_of_work')
55
- .insert({
56
- project_id,
57
- title,
58
- description: description || null,
59
- auto_deploy_on_completion: auto_deploy_on_completion || false,
60
- deploy_environment: deploy_environment || 'production',
61
- deploy_version_bump: deploy_version_bump || 'minor',
62
- deploy_trigger: deploy_trigger || 'all_completed_validated',
63
- created_by: 'agent',
64
- created_by_session_id: session.currentSessionId,
65
- })
66
- .select('id')
67
- .single();
68
-
69
- if (error) throw new Error(`Failed to create body of work: ${error.message}`);
57
+ const response = await apiClient.proxy<{
58
+ success: boolean;
59
+ body_of_work_id: string;
60
+ }>('create_body_of_work', {
61
+ project_id,
62
+ title,
63
+ description,
64
+ auto_deploy_on_completion,
65
+ deploy_environment,
66
+ deploy_version_bump,
67
+ deploy_trigger
68
+ }, {
69
+ session_id: session.currentSessionId,
70
+ persona: session.currentPersona,
71
+ instance_id: session.instanceId
72
+ });
73
+
74
+ if (!response.ok) {
75
+ throw new Error(`Failed to create body of work: ${response.error}`);
76
+ }
70
77
 
71
78
  return {
72
79
  result: {
73
80
  success: true,
74
- body_of_work_id: data.id,
81
+ body_of_work_id: response.data?.body_of_work_id,
75
82
  title,
76
83
  status: 'draft',
77
84
  message: 'Body of work created. Add tasks with add_task_to_body_of_work, then activate with activate_body_of_work.',
@@ -101,27 +108,27 @@ export const updateBodyOfWork: Handler = async (args, ctx) => {
101
108
  validateRequired(body_of_work_id, 'body_of_work_id');
102
109
  validateUUID(body_of_work_id, 'body_of_work_id');
103
110
 
104
- const { supabase } = ctx;
105
-
106
- // Build update object
107
- const updates: Record<string, unknown> = {};
108
- if (title !== undefined) updates.title = title;
109
- if (description !== undefined) updates.description = description;
110
- if (auto_deploy_on_completion !== undefined) updates.auto_deploy_on_completion = auto_deploy_on_completion;
111
- if (deploy_environment !== undefined) updates.deploy_environment = deploy_environment;
112
- if (deploy_version_bump !== undefined) updates.deploy_version_bump = deploy_version_bump;
113
- if (deploy_trigger !== undefined) updates.deploy_trigger = deploy_trigger;
114
-
115
- if (Object.keys(updates).length === 0) {
111
+ // Check if any updates provided
112
+ if (title === undefined && description === undefined && auto_deploy_on_completion === undefined &&
113
+ deploy_environment === undefined && deploy_version_bump === undefined && deploy_trigger === undefined) {
116
114
  return { result: { success: true, message: 'No updates provided' } };
117
115
  }
118
116
 
119
- const { error } = await supabase
120
- .from('bodies_of_work')
121
- .update(updates)
122
- .eq('id', body_of_work_id);
117
+ const apiClient = getApiClient();
123
118
 
124
- if (error) throw new Error(`Failed to update body of work: ${error.message}`);
119
+ const response = await apiClient.proxy<{ success: boolean }>('update_body_of_work', {
120
+ body_of_work_id,
121
+ title,
122
+ description,
123
+ auto_deploy_on_completion,
124
+ deploy_environment,
125
+ deploy_version_bump,
126
+ deploy_trigger
127
+ });
128
+
129
+ if (!response.ok) {
130
+ throw new Error(`Failed to update body of work: ${response.error}`);
131
+ }
125
132
 
126
133
  return { result: { success: true, body_of_work_id } };
127
134
  };
@@ -132,154 +139,69 @@ export const getBodyOfWork: Handler = async (args, ctx) => {
132
139
  validateRequired(body_of_work_id, 'body_of_work_id');
133
140
  validateUUID(body_of_work_id, 'body_of_work_id');
134
141
 
135
- const { supabase } = ctx;
136
-
137
- // Get body of work
138
- const { data: bow, error: bowError } = await supabase
139
- .from('bodies_of_work')
140
- .select('*')
141
- .eq('id', body_of_work_id)
142
- .single();
143
-
144
- if (bowError || !bow) {
145
- throw new Error(`Body of work not found: ${body_of_work_id}`);
146
- }
142
+ const apiClient = getApiClient();
147
143
 
148
- // Get tasks with their phases
149
- const { data: taskLinks, error: taskError } = await supabase
150
- .from('body_of_work_tasks')
151
- .select(`
152
- phase,
153
- order_index,
154
- tasks (
155
- id,
156
- title,
157
- status,
158
- priority,
159
- progress_percentage
160
- )
161
- `)
162
- .eq('body_of_work_id', body_of_work_id)
163
- .order('order_index');
164
-
165
- if (taskError) throw new Error(`Failed to fetch tasks: ${taskError.message}`);
166
-
167
- // Organize tasks by phase
168
- const preTasks: unknown[] = [];
169
- const coreTasks: unknown[] = [];
170
- const postTasks: unknown[] = [];
171
-
172
- for (const link of taskLinks || []) {
173
- const task = link.tasks;
174
- if (!task) continue;
175
-
176
- const taskWithPhase = { ...task, order_index: link.order_index };
177
-
178
- switch (link.phase) {
179
- case 'pre':
180
- preTasks.push(taskWithPhase);
181
- break;
182
- case 'core':
183
- coreTasks.push(taskWithPhase);
184
- break;
185
- case 'post':
186
- postTasks.push(taskWithPhase);
187
- break;
188
- }
144
+ const response = await apiClient.proxy<{
145
+ id: string;
146
+ title: string;
147
+ description?: string;
148
+ status: string;
149
+ progress_percentage: number;
150
+ pre_tasks: unknown[];
151
+ core_tasks: unknown[];
152
+ post_tasks: unknown[];
153
+ total_tasks: number;
154
+ }>('get_body_of_work', { body_of_work_id });
155
+
156
+ if (!response.ok) {
157
+ throw new Error(`Failed to get body of work: ${response.error}`);
189
158
  }
190
159
 
191
- return {
192
- result: {
193
- ...bow,
194
- pre_tasks: preTasks,
195
- core_tasks: coreTasks,
196
- post_tasks: postTasks,
197
- total_tasks: preTasks.length + coreTasks.length + postTasks.length,
198
- },
199
- };
160
+ return { result: response.data };
200
161
  };
201
162
 
202
163
  export const getBodiesOfWork: Handler = async (args, ctx) => {
203
- const { project_id, status } = args as {
164
+ const { project_id, status, limit = 50, offset = 0, search_query } = args as {
204
165
  project_id: string;
205
166
  status?: BodyOfWorkStatus;
167
+ limit?: number;
168
+ offset?: number;
169
+ search_query?: string;
206
170
  };
207
171
 
208
172
  validateRequired(project_id, 'project_id');
209
173
  validateUUID(project_id, 'project_id');
210
174
 
211
- const { supabase } = ctx;
212
-
213
- let query = supabase
214
- .from('bodies_of_work')
215
- .select(`
216
- id,
217
- title,
218
- description,
219
- status,
220
- progress_percentage,
221
- auto_deploy_on_completion,
222
- deploy_environment,
223
- deploy_version_bump,
224
- created_at,
225
- activated_at,
226
- completed_at
227
- `)
228
- .eq('project_id', project_id);
229
-
230
- if (status) {
231
- query = query.eq('status', status);
232
- }
233
-
234
- const { data, error } = await query.order('created_at', { ascending: false });
235
-
236
- if (error) throw new Error(`Failed to fetch bodies of work: ${error.message}`);
237
-
238
- const bodies = data || [];
239
- if (bodies.length === 0) {
240
- return { result: { bodies_of_work: [] } };
241
- }
175
+ const apiClient = getApiClient();
176
+
177
+ const response = await apiClient.proxy<{
178
+ bodies_of_work: Array<{
179
+ id: string;
180
+ title: string;
181
+ description?: string;
182
+ status: string;
183
+ progress_percentage: number;
184
+ task_counts: {
185
+ pre: { total: number; completed: number };
186
+ core: { total: number; completed: number };
187
+ post: { total: number; completed: number };
188
+ };
189
+ }>;
190
+ total_count: number;
191
+ has_more: boolean;
192
+ }>('get_bodies_of_work', {
193
+ project_id,
194
+ status,
195
+ limit: Math.min(limit, 100),
196
+ offset,
197
+ search_query
198
+ });
242
199
 
243
- // Batch query: get all task links for all bodies of work in a single query
244
- const bodyIds = bodies.map((b) => b.id);
245
- const { data: allTaskLinks } = await supabase
246
- .from('body_of_work_tasks')
247
- .select('body_of_work_id, phase, tasks!inner(status)')
248
- .in('body_of_work_id', bodyIds);
249
-
250
- // Group task links by body_of_work_id
251
- const taskLinksByBody = new Map<string, typeof allTaskLinks>();
252
- for (const link of allTaskLinks || []) {
253
- const existing = taskLinksByBody.get(link.body_of_work_id) || [];
254
- existing.push(link);
255
- taskLinksByBody.set(link.body_of_work_id, existing);
200
+ if (!response.ok) {
201
+ throw new Error(`Failed to fetch bodies of work: ${response.error}`);
256
202
  }
257
203
 
258
- // Build response with task counts
259
- const bodiesWithCounts = bodies.map((bow) => {
260
- const taskLinks = taskLinksByBody.get(bow.id) || [];
261
- const taskCounts = {
262
- pre: { total: 0, completed: 0 },
263
- core: { total: 0, completed: 0 },
264
- post: { total: 0, completed: 0 },
265
- };
266
-
267
- for (const link of taskLinks) {
268
- const phase = link.phase as TaskPhase;
269
- taskCounts[phase].total++;
270
- const taskData = link.tasks as unknown as { status: string } | null;
271
- if (taskData?.status === 'completed') {
272
- taskCounts[phase].completed++;
273
- }
274
- }
275
-
276
- return {
277
- ...bow,
278
- task_counts: taskCounts,
279
- };
280
- });
281
-
282
- return { result: { bodies_of_work: bodiesWithCounts } };
204
+ return { result: response.data };
283
205
  };
284
206
 
285
207
  export const deleteBodyOfWork: Handler = async (args, ctx) => {
@@ -288,12 +210,15 @@ export const deleteBodyOfWork: Handler = async (args, ctx) => {
288
210
  validateRequired(body_of_work_id, 'body_of_work_id');
289
211
  validateUUID(body_of_work_id, 'body_of_work_id');
290
212
 
291
- const { error } = await ctx.supabase
292
- .from('bodies_of_work')
293
- .delete()
294
- .eq('id', body_of_work_id);
213
+ const apiClient = getApiClient();
214
+
215
+ const response = await apiClient.proxy<{ success: boolean }>('delete_body_of_work', {
216
+ body_of_work_id
217
+ });
295
218
 
296
- if (error) throw new Error(`Failed to delete body of work: ${error.message}`);
219
+ if (!response.ok) {
220
+ throw new Error(`Failed to delete body of work: ${response.error}`);
221
+ }
297
222
 
298
223
  return { result: { success: true, message: 'Body of work deleted. Tasks are preserved.' } };
299
224
  };
@@ -311,70 +236,26 @@ export const addTaskToBodyOfWork: Handler = async (args, ctx) => {
311
236
  validateRequired(task_id, 'task_id');
312
237
  validateUUID(task_id, 'task_id');
313
238
 
314
- const { supabase } = ctx;
315
- const taskPhase = phase || 'core';
239
+ const apiClient = getApiClient();
316
240
 
317
- // Check if body of work exists and is in draft/active status
318
- const { data: bow, error: bowError } = await supabase
319
- .from('bodies_of_work')
320
- .select('status')
321
- .eq('id', body_of_work_id)
322
- .single();
323
-
324
- if (bowError || !bow) {
325
- throw new Error(`Body of work not found: ${body_of_work_id}`);
326
- }
327
-
328
- if (bow.status === 'completed' || bow.status === 'cancelled') {
329
- throw new Error(`Cannot add tasks to ${bow.status} body of work`);
330
- }
331
-
332
- // Check if task is already in a body of work
333
- const { data: existingLink } = await supabase
334
- .from('body_of_work_tasks')
335
- .select('body_of_work_id')
336
- .eq('task_id', task_id)
337
- .single();
338
-
339
- if (existingLink) {
340
- throw new Error('Task is already assigned to a body of work. Remove it first.');
341
- }
241
+ const response = await apiClient.proxy<{
242
+ success: boolean;
243
+ body_of_work_id: string;
244
+ task_id: string;
245
+ phase: string;
246
+ order_index: number;
247
+ }>('add_task_to_body_of_work', {
248
+ body_of_work_id,
249
+ task_id,
250
+ phase: phase || 'core',
251
+ order_index
252
+ });
342
253
 
343
- // Get the next order index if not provided
344
- let finalOrderIndex = order_index;
345
- if (finalOrderIndex === undefined) {
346
- const { data: maxOrder } = await supabase
347
- .from('body_of_work_tasks')
348
- .select('order_index')
349
- .eq('body_of_work_id', body_of_work_id)
350
- .eq('phase', taskPhase)
351
- .order('order_index', { ascending: false })
352
- .limit(1)
353
- .single();
354
-
355
- finalOrderIndex = maxOrder ? maxOrder.order_index + 1 : 0;
254
+ if (!response.ok) {
255
+ throw new Error(`Failed to add task to body of work: ${response.error}`);
356
256
  }
357
257
 
358
- const { error } = await supabase
359
- .from('body_of_work_tasks')
360
- .insert({
361
- body_of_work_id,
362
- task_id,
363
- phase: taskPhase,
364
- order_index: finalOrderIndex,
365
- });
366
-
367
- if (error) throw new Error(`Failed to add task to body of work: ${error.message}`);
368
-
369
- return {
370
- result: {
371
- success: true,
372
- body_of_work_id,
373
- task_id,
374
- phase: taskPhase,
375
- order_index: finalOrderIndex,
376
- },
377
- };
258
+ return { result: response.data };
378
259
  };
379
260
 
380
261
  export const removeTaskFromBodyOfWork: Handler = async (args, ctx) => {
@@ -383,38 +264,19 @@ export const removeTaskFromBodyOfWork: Handler = async (args, ctx) => {
383
264
  validateRequired(task_id, 'task_id');
384
265
  validateUUID(task_id, 'task_id');
385
266
 
386
- const { supabase } = ctx;
387
-
388
- // Check if link exists
389
- const { data: link } = await supabase
390
- .from('body_of_work_tasks')
391
- .select('body_of_work_id')
392
- .eq('task_id', task_id)
393
- .single();
394
-
395
- if (!link) {
396
- return { result: { success: true, message: 'Task is not in any body of work' } };
397
- }
267
+ const apiClient = getApiClient();
398
268
 
399
- // Check if body of work is completed
400
- const { data: bow } = await supabase
401
- .from('bodies_of_work')
402
- .select('status')
403
- .eq('id', link.body_of_work_id)
404
- .single();
269
+ const response = await apiClient.proxy<{
270
+ success: boolean;
271
+ body_of_work_id?: string;
272
+ message?: string;
273
+ }>('remove_task_from_body_of_work', { task_id });
405
274
 
406
- if (bow?.status === 'completed') {
407
- throw new Error('Cannot remove tasks from a completed body of work');
275
+ if (!response.ok) {
276
+ throw new Error(`Failed to remove task from body of work: ${response.error}`);
408
277
  }
409
278
 
410
- const { error } = await supabase
411
- .from('body_of_work_tasks')
412
- .delete()
413
- .eq('task_id', task_id);
414
-
415
- if (error) throw new Error(`Failed to remove task from body of work: ${error.message}`);
416
-
417
- return { result: { success: true, body_of_work_id: link.body_of_work_id } };
279
+ return { result: response.data };
418
280
  };
419
281
 
420
282
  export const activateBodyOfWork: Handler = async (args, ctx) => {
@@ -423,52 +285,21 @@ export const activateBodyOfWork: Handler = async (args, ctx) => {
423
285
  validateRequired(body_of_work_id, 'body_of_work_id');
424
286
  validateUUID(body_of_work_id, 'body_of_work_id');
425
287
 
426
- const { supabase } = ctx;
427
-
428
- // Check current status
429
- const { data: bow, error: bowError } = await supabase
430
- .from('bodies_of_work')
431
- .select('status, title')
432
- .eq('id', body_of_work_id)
433
- .single();
288
+ const apiClient = getApiClient();
434
289
 
435
- if (bowError || !bow) {
436
- throw new Error(`Body of work not found: ${body_of_work_id}`);
437
- }
438
-
439
- if (bow.status !== 'draft') {
440
- throw new Error(`Can only activate draft bodies of work. Current status: ${bow.status}`);
441
- }
442
-
443
- // Check if there are any tasks
444
- const { count } = await supabase
445
- .from('body_of_work_tasks')
446
- .select('id', { count: 'exact', head: true })
447
- .eq('body_of_work_id', body_of_work_id);
290
+ const response = await apiClient.proxy<{
291
+ success: boolean;
292
+ body_of_work_id: string;
293
+ title: string;
294
+ status: string;
295
+ message: string;
296
+ }>('activate_body_of_work', { body_of_work_id });
448
297
 
449
- if (!count || count === 0) {
450
- throw new Error('Cannot activate body of work with no tasks. Add tasks first.');
298
+ if (!response.ok) {
299
+ throw new Error(`Failed to activate body of work: ${response.error}`);
451
300
  }
452
301
 
453
- const { error } = await supabase
454
- .from('bodies_of_work')
455
- .update({
456
- status: 'active',
457
- activated_at: new Date().toISOString(),
458
- })
459
- .eq('id', body_of_work_id);
460
-
461
- if (error) throw new Error(`Failed to activate body of work: ${error.message}`);
462
-
463
- return {
464
- result: {
465
- success: true,
466
- body_of_work_id,
467
- title: bow.title,
468
- status: 'active',
469
- message: 'Body of work activated. Tasks can now be worked on following phase order.',
470
- },
471
- };
302
+ return { result: response.data };
472
303
  };
473
304
 
474
305
  export const addTaskDependency: Handler = async (args, ctx) => {
@@ -489,70 +320,25 @@ export const addTaskDependency: Handler = async (args, ctx) => {
489
320
  throw new Error('A task cannot depend on itself');
490
321
  }
491
322
 
492
- const { supabase } = ctx;
493
-
494
- // Verify both tasks belong to the same body of work
495
- const { data: taskLinks, error: taskError } = await supabase
496
- .from('body_of_work_tasks')
497
- .select('task_id')
498
- .eq('body_of_work_id', body_of_work_id)
499
- .in('task_id', [task_id, depends_on_task_id]);
500
-
501
- if (taskError) throw new Error(`Failed to verify tasks: ${taskError.message}`);
502
-
503
- if (!taskLinks || taskLinks.length !== 2) {
504
- throw new Error('Both tasks must belong to the specified body of work');
505
- }
506
-
507
- // Check for circular dependencies by traversing the dependency graph
508
- const visited = new Set<string>();
509
- const checkCircular = async (currentTaskId: string): Promise<boolean> => {
510
- if (currentTaskId === task_id) return true; // Found cycle
511
- if (visited.has(currentTaskId)) return false;
512
- visited.add(currentTaskId);
513
-
514
- const { data: deps } = await supabase
515
- .from('body_of_work_task_dependencies')
516
- .select('depends_on_task_id')
517
- .eq('task_id', currentTaskId)
518
- .eq('body_of_work_id', body_of_work_id);
519
-
520
- for (const dep of deps || []) {
521
- if (await checkCircular(dep.depends_on_task_id)) {
522
- return true;
523
- }
524
- }
525
- return false;
526
- };
323
+ const apiClient = getApiClient();
527
324
 
528
- if (await checkCircular(depends_on_task_id)) {
529
- throw new Error('Cannot add dependency: would create a circular dependency');
530
- }
325
+ const response = await apiClient.proxy<{
326
+ success: boolean;
327
+ body_of_work_id: string;
328
+ task_id: string;
329
+ depends_on_task_id: string;
330
+ message: string;
331
+ }>('add_task_dependency', {
332
+ body_of_work_id,
333
+ task_id,
334
+ depends_on_task_id
335
+ });
531
336
 
532
- const { error } = await supabase
533
- .from('body_of_work_task_dependencies')
534
- .insert({
535
- body_of_work_id,
536
- task_id,
537
- depends_on_task_id,
538
- });
539
-
540
- if (error) {
541
- if (error.message.includes('duplicate')) {
542
- throw new Error('This dependency already exists');
543
- }
544
- throw new Error(`Failed to add dependency: ${error.message}`);
337
+ if (!response.ok) {
338
+ throw new Error(`Failed to add task dependency: ${response.error}`);
545
339
  }
546
340
 
547
- return {
548
- result: {
549
- success: true,
550
- body_of_work_id,
551
- task_id,
552
- depends_on_task_id,
553
- message: `Task now depends on completion of the specified task`,
554
- },
555
- };
341
+ return { result: response.data };
556
342
  };
557
343
 
558
344
  export const removeTaskDependency: Handler = async (args, ctx) => {
@@ -566,17 +352,22 @@ export const removeTaskDependency: Handler = async (args, ctx) => {
566
352
  validateRequired(depends_on_task_id, 'depends_on_task_id');
567
353
  validateUUID(depends_on_task_id, 'depends_on_task_id');
568
354
 
569
- const { supabase } = ctx;
355
+ const apiClient = getApiClient();
570
356
 
571
- const { error } = await supabase
572
- .from('body_of_work_task_dependencies')
573
- .delete()
574
- .eq('task_id', task_id)
575
- .eq('depends_on_task_id', depends_on_task_id);
357
+ const response = await apiClient.proxy<{
358
+ success: boolean;
359
+ task_id: string;
360
+ depends_on_task_id: string;
361
+ }>('remove_task_dependency', {
362
+ task_id,
363
+ depends_on_task_id
364
+ });
576
365
 
577
- if (error) throw new Error(`Failed to remove dependency: ${error.message}`);
366
+ if (!response.ok) {
367
+ throw new Error(`Failed to remove task dependency: ${response.error}`);
368
+ }
578
369
 
579
- return { result: { success: true, task_id, depends_on_task_id } };
370
+ return { result: response.data };
580
371
  };
581
372
 
582
373
  export const getTaskDependencies: Handler = async (args, ctx) => {
@@ -592,29 +383,25 @@ export const getTaskDependencies: Handler = async (args, ctx) => {
592
383
  if (body_of_work_id) validateUUID(body_of_work_id, 'body_of_work_id');
593
384
  if (task_id) validateUUID(task_id, 'task_id');
594
385
 
595
- const { supabase } = ctx;
386
+ const apiClient = getApiClient();
596
387
 
597
- let query = supabase
598
- .from('body_of_work_task_dependencies')
599
- .select(`
600
- id,
601
- task_id,
602
- depends_on_task_id,
603
- created_at
604
- `);
388
+ const response = await apiClient.proxy<{
389
+ dependencies: Array<{
390
+ id: string;
391
+ task_id: string;
392
+ depends_on_task_id: string;
393
+ created_at: string;
394
+ }>;
395
+ }>('get_task_dependencies', {
396
+ body_of_work_id,
397
+ task_id
398
+ });
605
399
 
606
- if (body_of_work_id) {
607
- query = query.eq('body_of_work_id', body_of_work_id);
608
- }
609
- if (task_id) {
610
- query = query.eq('task_id', task_id);
400
+ if (!response.ok) {
401
+ throw new Error(`Failed to fetch task dependencies: ${response.error}`);
611
402
  }
612
403
 
613
- const { data, error } = await query;
614
-
615
- if (error) throw new Error(`Failed to fetch dependencies: ${error.message}`);
616
-
617
- return { result: { dependencies: data || [] } };
404
+ return { result: response.data };
618
405
  };
619
406
 
620
407
  export const getNextBodyOfWorkTask: Handler = async (args, ctx) => {
@@ -623,145 +410,27 @@ export const getNextBodyOfWorkTask: Handler = async (args, ctx) => {
623
410
  validateRequired(body_of_work_id, 'body_of_work_id');
624
411
  validateUUID(body_of_work_id, 'body_of_work_id');
625
412
 
626
- const { supabase } = ctx;
627
-
628
- // Get body of work status
629
- const { data: bow, error: bowError } = await supabase
630
- .from('bodies_of_work')
631
- .select('status, title')
632
- .eq('id', body_of_work_id)
633
- .single();
634
-
635
- if (bowError || !bow) {
636
- throw new Error(`Body of work not found: ${body_of_work_id}`);
637
- }
638
-
639
- if (bow.status !== 'active') {
640
- return {
641
- result: {
642
- next_task: null,
643
- message: `Body of work is ${bow.status}, not active`,
644
- },
413
+ const apiClient = getApiClient();
414
+
415
+ const response = await apiClient.proxy<{
416
+ next_task: {
417
+ id: string;
418
+ title: string;
419
+ phase: string;
420
+ priority: number;
421
+ } | null;
422
+ body_of_work?: {
423
+ id: string;
424
+ title: string;
645
425
  };
646
- }
647
-
648
- // Get all tasks with their status and phase
649
- const { data: taskLinks, error: taskError } = await supabase
650
- .from('body_of_work_tasks')
651
- .select(`
652
- task_id,
653
- phase,
654
- order_index,
655
- tasks (
656
- id,
657
- title,
658
- status,
659
- priority,
660
- claimed_by_session_id
661
- )
662
- `)
663
- .eq('body_of_work_id', body_of_work_id)
664
- .order('order_index');
665
-
666
- if (taskError) throw new Error(`Failed to fetch tasks: ${taskError.message}`);
667
-
668
- // Get all dependencies
669
- const { data: dependencies } = await supabase
670
- .from('body_of_work_task_dependencies')
671
- .select('task_id, depends_on_task_id')
672
- .eq('body_of_work_id', body_of_work_id);
673
-
674
- const depMap = new Map<string, string[]>();
675
- for (const dep of dependencies || []) {
676
- const existing = depMap.get(dep.task_id) || [];
677
- existing.push(dep.depends_on_task_id);
678
- depMap.set(dep.task_id, existing);
679
- }
680
-
681
- // Build task status map
682
- const taskStatusMap = new Map<string, string>();
683
- for (const link of taskLinks || []) {
684
- const task = link.tasks as unknown as { id: string; status: string } | null;
685
- if (task) {
686
- taskStatusMap.set(task.id, task.status);
687
- }
688
- }
689
-
690
- // Check if all dependencies are completed
691
- const areDependenciesComplete = (taskId: string): boolean => {
692
- const deps = depMap.get(taskId) || [];
693
- return deps.every((depId) => taskStatusMap.get(depId) === 'completed');
694
- };
695
-
696
- // Phase order: pre -> core -> post
697
- const phaseOrder = ['pre', 'core', 'post'];
698
-
699
- // Check if all tasks in a phase are completed
700
- const isPhaseComplete = (phase: string): boolean => {
701
- return (taskLinks || [])
702
- .filter((link) => link.phase === phase)
703
- .every((link) => {
704
- const task = link.tasks as unknown as { status: string } | null;
705
- return task?.status === 'completed';
706
- });
707
- };
426
+ message?: string;
427
+ }>('get_next_body_of_work_task', { body_of_work_id });
708
428
 
709
- // Find the next available task
710
- for (const phase of phaseOrder) {
711
- // Check if we can work on this phase
712
- const prevPhaseIndex = phaseOrder.indexOf(phase) - 1;
713
- if (prevPhaseIndex >= 0 && !isPhaseComplete(phaseOrder[prevPhaseIndex])) {
714
- continue; // Previous phase not complete, skip this phase
715
- }
716
-
717
- const phaseTasks = (taskLinks || [])
718
- .filter((link) => link.phase === phase)
719
- .sort((a, b) => a.order_index - b.order_index);
720
-
721
- for (const link of phaseTasks) {
722
- const task = link.tasks as unknown as {
723
- id: string;
724
- title: string;
725
- status: string;
726
- priority: number;
727
- claimed_by_session_id: string | null;
728
- } | null;
729
-
730
- if (!task) continue;
731
-
732
- // Skip completed, cancelled, or in-progress tasks
733
- if (task.status !== 'pending') continue;
734
-
735
- // Skip tasks claimed by another session
736
- if (task.claimed_by_session_id) continue;
737
-
738
- // Check dependencies
739
- if (!areDependenciesComplete(task.id)) continue;
740
-
741
- return {
742
- result: {
743
- next_task: {
744
- id: task.id,
745
- title: task.title,
746
- phase: link.phase,
747
- priority: task.priority,
748
- },
749
- body_of_work: {
750
- id: body_of_work_id,
751
- title: bow.title,
752
- },
753
- },
754
- };
755
- }
429
+ if (!response.ok) {
430
+ throw new Error(`Failed to get next body of work task: ${response.error}`);
756
431
  }
757
432
 
758
- // No available tasks
759
- return {
760
- result: {
761
- next_task: null,
762
- message: 'No available tasks - all are completed, in progress, or blocked by dependencies',
763
- },
764
- };
433
+ return { result: response.data };
765
434
  };
766
435
 
767
436
  /**