@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
@@ -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,185 +108,116 @@ 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();
118
+
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
+ });
123
128
 
124
- if (error) throw new Error(`Failed to update body of work: ${error.message}`);
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
  };
128
135
 
129
136
  export const getBodyOfWork: Handler = async (args, ctx) => {
130
- const { body_of_work_id } = args as { body_of_work_id: string };
137
+ const { body_of_work_id, summary_only = false } = args as { body_of_work_id: string; summary_only?: boolean };
131
138
 
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;
142
+ const apiClient = getApiClient();
136
143
 
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
- }
144
+ // Response type varies based on summary_only
145
+ const response = await apiClient.proxy<{
146
+ body_of_work: {
147
+ id: string;
148
+ title: string;
149
+ description?: string;
150
+ status: string;
151
+ progress_percentage: number;
152
+ };
153
+ // Full response includes tasks grouped by phase
154
+ tasks?: {
155
+ pre: unknown[];
156
+ core: unknown[];
157
+ post: unknown[];
158
+ };
159
+ // Summary response includes counts and next task
160
+ task_counts?: {
161
+ pre: { total: number; completed: number };
162
+ core: { total: number; completed: number };
163
+ post: { total: number; completed: number };
164
+ total: number;
165
+ completed: number;
166
+ in_progress: number;
167
+ };
168
+ current_task?: { id: string; title: string } | null;
169
+ next_task?: { id: string; title: string; priority: number } | null;
170
+ }>('get_body_of_work', { body_of_work_id, summary_only });
147
171
 
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
- }
172
+ if (!response.ok) {
173
+ throw new Error(`Failed to get body of work: ${response.error}`);
189
174
  }
190
175
 
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
- };
176
+ return { result: response.data };
200
177
  };
201
178
 
202
179
  export const getBodiesOfWork: Handler = async (args, ctx) => {
203
- const { project_id, status } = args as {
180
+ const { project_id, status, limit = 50, offset = 0, search_query } = args as {
204
181
  project_id: string;
205
182
  status?: BodyOfWorkStatus;
183
+ limit?: number;
184
+ offset?: number;
185
+ search_query?: string;
206
186
  };
207
187
 
208
188
  validateRequired(project_id, 'project_id');
209
189
  validateUUID(project_id, 'project_id');
210
190
 
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
- }
191
+ const apiClient = getApiClient();
192
+
193
+ const response = await apiClient.proxy<{
194
+ bodies_of_work: Array<{
195
+ id: string;
196
+ title: string;
197
+ description?: string;
198
+ status: string;
199
+ progress_percentage: number;
200
+ task_counts: {
201
+ pre: { total: number; completed: number };
202
+ core: { total: number; completed: number };
203
+ post: { total: number; completed: number };
204
+ };
205
+ }>;
206
+ total_count: number;
207
+ has_more: boolean;
208
+ }>('get_bodies_of_work', {
209
+ project_id,
210
+ status,
211
+ limit: Math.min(limit, 100),
212
+ offset,
213
+ search_query
214
+ });
242
215
 
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);
216
+ if (!response.ok) {
217
+ throw new Error(`Failed to fetch bodies of work: ${response.error}`);
256
218
  }
257
219
 
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 } };
220
+ return { result: response.data };
283
221
  };
284
222
 
285
223
  export const deleteBodyOfWork: Handler = async (args, ctx) => {
@@ -288,12 +226,15 @@ export const deleteBodyOfWork: Handler = async (args, ctx) => {
288
226
  validateRequired(body_of_work_id, 'body_of_work_id');
289
227
  validateUUID(body_of_work_id, 'body_of_work_id');
290
228
 
291
- const { error } = await ctx.supabase
292
- .from('bodies_of_work')
293
- .delete()
294
- .eq('id', body_of_work_id);
229
+ const apiClient = getApiClient();
230
+
231
+ const response = await apiClient.proxy<{ success: boolean }>('delete_body_of_work', {
232
+ body_of_work_id
233
+ });
295
234
 
296
- if (error) throw new Error(`Failed to delete body of work: ${error.message}`);
235
+ if (!response.ok) {
236
+ throw new Error(`Failed to delete body of work: ${response.error}`);
237
+ }
297
238
 
298
239
  return { result: { success: true, message: 'Body of work deleted. Tasks are preserved.' } };
299
240
  };
@@ -311,70 +252,26 @@ export const addTaskToBodyOfWork: Handler = async (args, ctx) => {
311
252
  validateRequired(task_id, 'task_id');
312
253
  validateUUID(task_id, 'task_id');
313
254
 
314
- const { supabase } = ctx;
315
- const taskPhase = phase || 'core';
255
+ const apiClient = getApiClient();
316
256
 
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
- }
257
+ const response = await apiClient.proxy<{
258
+ success: boolean;
259
+ body_of_work_id: string;
260
+ task_id: string;
261
+ phase: string;
262
+ order_index: number;
263
+ }>('add_task_to_body_of_work', {
264
+ body_of_work_id,
265
+ task_id,
266
+ phase: phase || 'core',
267
+ order_index
268
+ });
342
269
 
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;
270
+ if (!response.ok) {
271
+ throw new Error(`Failed to add task to body of work: ${response.error}`);
356
272
  }
357
273
 
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
- };
274
+ return { result: response.data };
378
275
  };
379
276
 
380
277
  export const removeTaskFromBodyOfWork: Handler = async (args, ctx) => {
@@ -383,38 +280,19 @@ export const removeTaskFromBodyOfWork: Handler = async (args, ctx) => {
383
280
  validateRequired(task_id, 'task_id');
384
281
  validateUUID(task_id, 'task_id');
385
282
 
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
- }
283
+ const apiClient = getApiClient();
398
284
 
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();
285
+ const response = await apiClient.proxy<{
286
+ success: boolean;
287
+ body_of_work_id?: string;
288
+ message?: string;
289
+ }>('remove_task_from_body_of_work', { task_id });
405
290
 
406
- if (bow?.status === 'completed') {
407
- throw new Error('Cannot remove tasks from a completed body of work');
291
+ if (!response.ok) {
292
+ throw new Error(`Failed to remove task from body of work: ${response.error}`);
408
293
  }
409
294
 
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 } };
295
+ return { result: response.data };
418
296
  };
419
297
 
420
298
  export const activateBodyOfWork: Handler = async (args, ctx) => {
@@ -423,52 +301,21 @@ export const activateBodyOfWork: Handler = async (args, ctx) => {
423
301
  validateRequired(body_of_work_id, 'body_of_work_id');
424
302
  validateUUID(body_of_work_id, 'body_of_work_id');
425
303
 
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();
304
+ const apiClient = getApiClient();
434
305
 
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);
306
+ const response = await apiClient.proxy<{
307
+ success: boolean;
308
+ body_of_work_id: string;
309
+ title: string;
310
+ status: string;
311
+ message: string;
312
+ }>('activate_body_of_work', { body_of_work_id });
448
313
 
449
- if (!count || count === 0) {
450
- throw new Error('Cannot activate body of work with no tasks. Add tasks first.');
314
+ if (!response.ok) {
315
+ throw new Error(`Failed to activate body of work: ${response.error}`);
451
316
  }
452
317
 
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
- };
318
+ return { result: response.data };
472
319
  };
473
320
 
474
321
  export const addTaskDependency: Handler = async (args, ctx) => {
@@ -489,70 +336,25 @@ export const addTaskDependency: Handler = async (args, ctx) => {
489
336
  throw new Error('A task cannot depend on itself');
490
337
  }
491
338
 
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
- };
339
+ const apiClient = getApiClient();
527
340
 
528
- if (await checkCircular(depends_on_task_id)) {
529
- throw new Error('Cannot add dependency: would create a circular dependency');
530
- }
341
+ const response = await apiClient.proxy<{
342
+ success: boolean;
343
+ body_of_work_id: string;
344
+ task_id: string;
345
+ depends_on_task_id: string;
346
+ message: string;
347
+ }>('add_task_dependency', {
348
+ body_of_work_id,
349
+ task_id,
350
+ depends_on_task_id
351
+ });
531
352
 
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}`);
353
+ if (!response.ok) {
354
+ throw new Error(`Failed to add task dependency: ${response.error}`);
545
355
  }
546
356
 
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
- };
357
+ return { result: response.data };
556
358
  };
557
359
 
558
360
  export const removeTaskDependency: Handler = async (args, ctx) => {
@@ -566,17 +368,22 @@ export const removeTaskDependency: Handler = async (args, ctx) => {
566
368
  validateRequired(depends_on_task_id, 'depends_on_task_id');
567
369
  validateUUID(depends_on_task_id, 'depends_on_task_id');
568
370
 
569
- const { supabase } = ctx;
371
+ const apiClient = getApiClient();
570
372
 
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);
373
+ const response = await apiClient.proxy<{
374
+ success: boolean;
375
+ task_id: string;
376
+ depends_on_task_id: string;
377
+ }>('remove_task_dependency', {
378
+ task_id,
379
+ depends_on_task_id
380
+ });
576
381
 
577
- if (error) throw new Error(`Failed to remove dependency: ${error.message}`);
382
+ if (!response.ok) {
383
+ throw new Error(`Failed to remove task dependency: ${response.error}`);
384
+ }
578
385
 
579
- return { result: { success: true, task_id, depends_on_task_id } };
386
+ return { result: response.data };
580
387
  };
581
388
 
582
389
  export const getTaskDependencies: Handler = async (args, ctx) => {
@@ -592,29 +399,25 @@ export const getTaskDependencies: Handler = async (args, ctx) => {
592
399
  if (body_of_work_id) validateUUID(body_of_work_id, 'body_of_work_id');
593
400
  if (task_id) validateUUID(task_id, 'task_id');
594
401
 
595
- const { supabase } = ctx;
402
+ const apiClient = getApiClient();
596
403
 
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
- `);
404
+ const response = await apiClient.proxy<{
405
+ dependencies: Array<{
406
+ id: string;
407
+ task_id: string;
408
+ depends_on_task_id: string;
409
+ created_at: string;
410
+ }>;
411
+ }>('get_task_dependencies', {
412
+ body_of_work_id,
413
+ task_id
414
+ });
605
415
 
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);
416
+ if (!response.ok) {
417
+ throw new Error(`Failed to fetch task dependencies: ${response.error}`);
611
418
  }
612
419
 
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 || [] } };
420
+ return { result: response.data };
618
421
  };
619
422
 
620
423
  export const getNextBodyOfWorkTask: Handler = async (args, ctx) => {
@@ -623,145 +426,27 @@ export const getNextBodyOfWorkTask: Handler = async (args, ctx) => {
623
426
  validateRequired(body_of_work_id, 'body_of_work_id');
624
427
  validateUUID(body_of_work_id, 'body_of_work_id');
625
428
 
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
- },
429
+ const apiClient = getApiClient();
430
+
431
+ const response = await apiClient.proxy<{
432
+ next_task: {
433
+ id: string;
434
+ title: string;
435
+ phase: string;
436
+ priority: number;
437
+ } | null;
438
+ body_of_work?: {
439
+ id: string;
440
+ title: string;
645
441
  };
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
- };
442
+ message?: string;
443
+ }>('get_next_body_of_work_task', { body_of_work_id });
708
444
 
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
- }
445
+ if (!response.ok) {
446
+ throw new Error(`Failed to get next body of work task: ${response.error}`);
756
447
  }
757
448
 
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
- };
449
+ return { result: response.data };
765
450
  };
766
451
 
767
452
  /**