@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,35 +14,38 @@
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
  import { validateRequired, validateUUID } from '../validators.js';
21
+ import { getApiClient } from '../api-client.js';
19
22
  export const createBodyOfWork = async (args, ctx) => {
20
23
  const { project_id, title, description, auto_deploy_on_completion, deploy_environment, deploy_version_bump, deploy_trigger, } = args;
21
24
  validateRequired(project_id, 'project_id');
22
25
  validateUUID(project_id, 'project_id');
23
26
  validateRequired(title, 'title');
24
- const { supabase, session } = ctx;
25
- const { data, error } = await supabase
26
- .from('bodies_of_work')
27
- .insert({
27
+ const { session } = ctx;
28
+ const apiClient = getApiClient();
29
+ const response = await apiClient.proxy('create_body_of_work', {
28
30
  project_id,
29
31
  title,
30
- description: description || null,
31
- auto_deploy_on_completion: auto_deploy_on_completion || false,
32
- deploy_environment: deploy_environment || 'production',
33
- deploy_version_bump: deploy_version_bump || 'minor',
34
- deploy_trigger: deploy_trigger || 'all_completed_validated',
35
- created_by: 'agent',
36
- created_by_session_id: session.currentSessionId,
37
- })
38
- .select('id')
39
- .single();
40
- if (error)
41
- throw new Error(`Failed to create body of work: ${error.message}`);
32
+ description,
33
+ auto_deploy_on_completion,
34
+ deploy_environment,
35
+ deploy_version_bump,
36
+ deploy_trigger
37
+ }, {
38
+ session_id: session.currentSessionId,
39
+ persona: session.currentPersona,
40
+ instance_id: session.instanceId
41
+ });
42
+ if (!response.ok) {
43
+ throw new Error(`Failed to create body of work: ${response.error}`);
44
+ }
42
45
  return {
43
46
  result: {
44
47
  success: true,
45
- body_of_work_id: data.id,
48
+ body_of_work_id: response.data?.body_of_work_id,
46
49
  title,
47
50
  status: 'draft',
48
51
  message: 'Body of work created. Add tasks with add_task_to_body_of_work, then activate with activate_body_of_work.',
@@ -53,172 +56,66 @@ export const updateBodyOfWork = async (args, ctx) => {
53
56
  const { body_of_work_id, title, description, auto_deploy_on_completion, deploy_environment, deploy_version_bump, deploy_trigger, } = args;
54
57
  validateRequired(body_of_work_id, 'body_of_work_id');
55
58
  validateUUID(body_of_work_id, 'body_of_work_id');
56
- const { supabase } = ctx;
57
- // Build update object
58
- const updates = {};
59
- if (title !== undefined)
60
- updates.title = title;
61
- if (description !== undefined)
62
- updates.description = description;
63
- if (auto_deploy_on_completion !== undefined)
64
- updates.auto_deploy_on_completion = auto_deploy_on_completion;
65
- if (deploy_environment !== undefined)
66
- updates.deploy_environment = deploy_environment;
67
- if (deploy_version_bump !== undefined)
68
- updates.deploy_version_bump = deploy_version_bump;
69
- if (deploy_trigger !== undefined)
70
- updates.deploy_trigger = deploy_trigger;
71
- if (Object.keys(updates).length === 0) {
59
+ // Check if any updates provided
60
+ if (title === undefined && description === undefined && auto_deploy_on_completion === undefined &&
61
+ deploy_environment === undefined && deploy_version_bump === undefined && deploy_trigger === undefined) {
72
62
  return { result: { success: true, message: 'No updates provided' } };
73
63
  }
74
- const { error } = await supabase
75
- .from('bodies_of_work')
76
- .update(updates)
77
- .eq('id', body_of_work_id);
78
- if (error)
79
- throw new Error(`Failed to update body of work: ${error.message}`);
64
+ const apiClient = getApiClient();
65
+ const response = await apiClient.proxy('update_body_of_work', {
66
+ body_of_work_id,
67
+ title,
68
+ description,
69
+ auto_deploy_on_completion,
70
+ deploy_environment,
71
+ deploy_version_bump,
72
+ deploy_trigger
73
+ });
74
+ if (!response.ok) {
75
+ throw new Error(`Failed to update body of work: ${response.error}`);
76
+ }
80
77
  return { result: { success: true, body_of_work_id } };
81
78
  };
82
79
  export const getBodyOfWork = async (args, ctx) => {
83
- const { body_of_work_id } = args;
80
+ const { body_of_work_id, summary_only = false } = args;
84
81
  validateRequired(body_of_work_id, 'body_of_work_id');
85
82
  validateUUID(body_of_work_id, 'body_of_work_id');
86
- const { supabase } = ctx;
87
- // Get body of work
88
- const { data: bow, error: bowError } = await supabase
89
- .from('bodies_of_work')
90
- .select('*')
91
- .eq('id', body_of_work_id)
92
- .single();
93
- if (bowError || !bow) {
94
- throw new Error(`Body of work not found: ${body_of_work_id}`);
83
+ const apiClient = getApiClient();
84
+ // Response type varies based on summary_only
85
+ const response = await apiClient.proxy('get_body_of_work', { body_of_work_id, summary_only });
86
+ if (!response.ok) {
87
+ throw new Error(`Failed to get body of work: ${response.error}`);
95
88
  }
96
- // Get tasks with their phases
97
- const { data: taskLinks, error: taskError } = await supabase
98
- .from('body_of_work_tasks')
99
- .select(`
100
- phase,
101
- order_index,
102
- tasks (
103
- id,
104
- title,
105
- status,
106
- priority,
107
- progress_percentage
108
- )
109
- `)
110
- .eq('body_of_work_id', body_of_work_id)
111
- .order('order_index');
112
- if (taskError)
113
- throw new Error(`Failed to fetch tasks: ${taskError.message}`);
114
- // Organize tasks by phase
115
- const preTasks = [];
116
- const coreTasks = [];
117
- const postTasks = [];
118
- for (const link of taskLinks || []) {
119
- const task = link.tasks;
120
- if (!task)
121
- continue;
122
- const taskWithPhase = { ...task, order_index: link.order_index };
123
- switch (link.phase) {
124
- case 'pre':
125
- preTasks.push(taskWithPhase);
126
- break;
127
- case 'core':
128
- coreTasks.push(taskWithPhase);
129
- break;
130
- case 'post':
131
- postTasks.push(taskWithPhase);
132
- break;
133
- }
134
- }
135
- return {
136
- result: {
137
- ...bow,
138
- pre_tasks: preTasks,
139
- core_tasks: coreTasks,
140
- post_tasks: postTasks,
141
- total_tasks: preTasks.length + coreTasks.length + postTasks.length,
142
- },
143
- };
89
+ return { result: response.data };
144
90
  };
145
91
  export const getBodiesOfWork = async (args, ctx) => {
146
- const { project_id, status } = args;
92
+ const { project_id, status, limit = 50, offset = 0, search_query } = args;
147
93
  validateRequired(project_id, 'project_id');
148
94
  validateUUID(project_id, 'project_id');
149
- const { supabase } = ctx;
150
- let query = supabase
151
- .from('bodies_of_work')
152
- .select(`
153
- id,
154
- title,
155
- description,
156
- status,
157
- progress_percentage,
158
- auto_deploy_on_completion,
159
- deploy_environment,
160
- deploy_version_bump,
161
- created_at,
162
- activated_at,
163
- completed_at
164
- `)
165
- .eq('project_id', project_id);
166
- if (status) {
167
- query = query.eq('status', status);
168
- }
169
- const { data, error } = await query.order('created_at', { ascending: false });
170
- if (error)
171
- throw new Error(`Failed to fetch bodies of work: ${error.message}`);
172
- const bodies = data || [];
173
- if (bodies.length === 0) {
174
- return { result: { bodies_of_work: [] } };
175
- }
176
- // Batch query: get all task links for all bodies of work in a single query
177
- const bodyIds = bodies.map((b) => b.id);
178
- const { data: allTaskLinks } = await supabase
179
- .from('body_of_work_tasks')
180
- .select('body_of_work_id, phase, tasks!inner(status)')
181
- .in('body_of_work_id', bodyIds);
182
- // Group task links by body_of_work_id
183
- const taskLinksByBody = new Map();
184
- for (const link of allTaskLinks || []) {
185
- const existing = taskLinksByBody.get(link.body_of_work_id) || [];
186
- existing.push(link);
187
- taskLinksByBody.set(link.body_of_work_id, existing);
188
- }
189
- // Build response with task counts
190
- const bodiesWithCounts = bodies.map((bow) => {
191
- const taskLinks = taskLinksByBody.get(bow.id) || [];
192
- const taskCounts = {
193
- pre: { total: 0, completed: 0 },
194
- core: { total: 0, completed: 0 },
195
- post: { total: 0, completed: 0 },
196
- };
197
- for (const link of taskLinks) {
198
- const phase = link.phase;
199
- taskCounts[phase].total++;
200
- const taskData = link.tasks;
201
- if (taskData?.status === 'completed') {
202
- taskCounts[phase].completed++;
203
- }
204
- }
205
- return {
206
- ...bow,
207
- task_counts: taskCounts,
208
- };
95
+ const apiClient = getApiClient();
96
+ const response = await apiClient.proxy('get_bodies_of_work', {
97
+ project_id,
98
+ status,
99
+ limit: Math.min(limit, 100),
100
+ offset,
101
+ search_query
209
102
  });
210
- return { result: { bodies_of_work: bodiesWithCounts } };
103
+ if (!response.ok) {
104
+ throw new Error(`Failed to fetch bodies of work: ${response.error}`);
105
+ }
106
+ return { result: response.data };
211
107
  };
212
108
  export const deleteBodyOfWork = async (args, ctx) => {
213
109
  const { body_of_work_id } = args;
214
110
  validateRequired(body_of_work_id, 'body_of_work_id');
215
111
  validateUUID(body_of_work_id, 'body_of_work_id');
216
- const { error } = await ctx.supabase
217
- .from('bodies_of_work')
218
- .delete()
219
- .eq('id', body_of_work_id);
220
- if (error)
221
- throw new Error(`Failed to delete body of work: ${error.message}`);
112
+ const apiClient = getApiClient();
113
+ const response = await apiClient.proxy('delete_body_of_work', {
114
+ body_of_work_id
115
+ });
116
+ if (!response.ok) {
117
+ throw new Error(`Failed to delete body of work: ${response.error}`);
118
+ }
222
119
  return { result: { success: true, message: 'Body of work deleted. Tasks are preserved.' } };
223
120
  };
224
121
  export const addTaskToBodyOfWork = async (args, ctx) => {
@@ -227,136 +124,39 @@ export const addTaskToBodyOfWork = async (args, ctx) => {
227
124
  validateUUID(body_of_work_id, 'body_of_work_id');
228
125
  validateRequired(task_id, 'task_id');
229
126
  validateUUID(task_id, 'task_id');
230
- const { supabase } = ctx;
231
- const taskPhase = phase || 'core';
232
- // Check if body of work exists and is in draft/active status
233
- const { data: bow, error: bowError } = await supabase
234
- .from('bodies_of_work')
235
- .select('status')
236
- .eq('id', body_of_work_id)
237
- .single();
238
- if (bowError || !bow) {
239
- throw new Error(`Body of work not found: ${body_of_work_id}`);
240
- }
241
- if (bow.status === 'completed' || bow.status === 'cancelled') {
242
- throw new Error(`Cannot add tasks to ${bow.status} body of work`);
243
- }
244
- // Check if task is already in a body of work
245
- const { data: existingLink } = await supabase
246
- .from('body_of_work_tasks')
247
- .select('body_of_work_id')
248
- .eq('task_id', task_id)
249
- .single();
250
- if (existingLink) {
251
- throw new Error('Task is already assigned to a body of work. Remove it first.');
252
- }
253
- // Get the next order index if not provided
254
- let finalOrderIndex = order_index;
255
- if (finalOrderIndex === undefined) {
256
- const { data: maxOrder } = await supabase
257
- .from('body_of_work_tasks')
258
- .select('order_index')
259
- .eq('body_of_work_id', body_of_work_id)
260
- .eq('phase', taskPhase)
261
- .order('order_index', { ascending: false })
262
- .limit(1)
263
- .single();
264
- finalOrderIndex = maxOrder ? maxOrder.order_index + 1 : 0;
265
- }
266
- const { error } = await supabase
267
- .from('body_of_work_tasks')
268
- .insert({
127
+ const apiClient = getApiClient();
128
+ const response = await apiClient.proxy('add_task_to_body_of_work', {
269
129
  body_of_work_id,
270
130
  task_id,
271
- phase: taskPhase,
272
- order_index: finalOrderIndex,
131
+ phase: phase || 'core',
132
+ order_index
273
133
  });
274
- if (error)
275
- throw new Error(`Failed to add task to body of work: ${error.message}`);
276
- return {
277
- result: {
278
- success: true,
279
- body_of_work_id,
280
- task_id,
281
- phase: taskPhase,
282
- order_index: finalOrderIndex,
283
- },
284
- };
134
+ if (!response.ok) {
135
+ throw new Error(`Failed to add task to body of work: ${response.error}`);
136
+ }
137
+ return { result: response.data };
285
138
  };
286
139
  export const removeTaskFromBodyOfWork = async (args, ctx) => {
287
140
  const { task_id } = args;
288
141
  validateRequired(task_id, 'task_id');
289
142
  validateUUID(task_id, 'task_id');
290
- const { supabase } = ctx;
291
- // Check if link exists
292
- const { data: link } = await supabase
293
- .from('body_of_work_tasks')
294
- .select('body_of_work_id')
295
- .eq('task_id', task_id)
296
- .single();
297
- if (!link) {
298
- return { result: { success: true, message: 'Task is not in any body of work' } };
299
- }
300
- // Check if body of work is completed
301
- const { data: bow } = await supabase
302
- .from('bodies_of_work')
303
- .select('status')
304
- .eq('id', link.body_of_work_id)
305
- .single();
306
- if (bow?.status === 'completed') {
307
- throw new Error('Cannot remove tasks from a completed body of work');
143
+ const apiClient = getApiClient();
144
+ const response = await apiClient.proxy('remove_task_from_body_of_work', { task_id });
145
+ if (!response.ok) {
146
+ throw new Error(`Failed to remove task from body of work: ${response.error}`);
308
147
  }
309
- const { error } = await supabase
310
- .from('body_of_work_tasks')
311
- .delete()
312
- .eq('task_id', task_id);
313
- if (error)
314
- throw new Error(`Failed to remove task from body of work: ${error.message}`);
315
- return { result: { success: true, body_of_work_id: link.body_of_work_id } };
148
+ return { result: response.data };
316
149
  };
317
150
  export const activateBodyOfWork = async (args, ctx) => {
318
151
  const { body_of_work_id } = args;
319
152
  validateRequired(body_of_work_id, 'body_of_work_id');
320
153
  validateUUID(body_of_work_id, 'body_of_work_id');
321
- const { supabase } = ctx;
322
- // Check current status
323
- const { data: bow, error: bowError } = await supabase
324
- .from('bodies_of_work')
325
- .select('status, title')
326
- .eq('id', body_of_work_id)
327
- .single();
328
- if (bowError || !bow) {
329
- throw new Error(`Body of work not found: ${body_of_work_id}`);
154
+ const apiClient = getApiClient();
155
+ const response = await apiClient.proxy('activate_body_of_work', { body_of_work_id });
156
+ if (!response.ok) {
157
+ throw new Error(`Failed to activate body of work: ${response.error}`);
330
158
  }
331
- if (bow.status !== 'draft') {
332
- throw new Error(`Can only activate draft bodies of work. Current status: ${bow.status}`);
333
- }
334
- // Check if there are any tasks
335
- const { count } = await supabase
336
- .from('body_of_work_tasks')
337
- .select('id', { count: 'exact', head: true })
338
- .eq('body_of_work_id', body_of_work_id);
339
- if (!count || count === 0) {
340
- throw new Error('Cannot activate body of work with no tasks. Add tasks first.');
341
- }
342
- const { error } = await supabase
343
- .from('bodies_of_work')
344
- .update({
345
- status: 'active',
346
- activated_at: new Date().toISOString(),
347
- })
348
- .eq('id', body_of_work_id);
349
- if (error)
350
- throw new Error(`Failed to activate body of work: ${error.message}`);
351
- return {
352
- result: {
353
- success: true,
354
- body_of_work_id,
355
- title: bow.title,
356
- status: 'active',
357
- message: 'Body of work activated. Tasks can now be worked on following phase order.',
358
- },
359
- };
159
+ return { result: response.data };
360
160
  };
361
161
  export const addTaskDependency = async (args, ctx) => {
362
162
  const { body_of_work_id, task_id, depends_on_task_id } = args;
@@ -369,63 +169,16 @@ export const addTaskDependency = async (args, ctx) => {
369
169
  if (task_id === depends_on_task_id) {
370
170
  throw new Error('A task cannot depend on itself');
371
171
  }
372
- const { supabase } = ctx;
373
- // Verify both tasks belong to the same body of work
374
- const { data: taskLinks, error: taskError } = await supabase
375
- .from('body_of_work_tasks')
376
- .select('task_id')
377
- .eq('body_of_work_id', body_of_work_id)
378
- .in('task_id', [task_id, depends_on_task_id]);
379
- if (taskError)
380
- throw new Error(`Failed to verify tasks: ${taskError.message}`);
381
- if (!taskLinks || taskLinks.length !== 2) {
382
- throw new Error('Both tasks must belong to the specified body of work');
383
- }
384
- // Check for circular dependencies by traversing the dependency graph
385
- const visited = new Set();
386
- const checkCircular = async (currentTaskId) => {
387
- if (currentTaskId === task_id)
388
- return true; // Found cycle
389
- if (visited.has(currentTaskId))
390
- return false;
391
- visited.add(currentTaskId);
392
- const { data: deps } = await supabase
393
- .from('body_of_work_task_dependencies')
394
- .select('depends_on_task_id')
395
- .eq('task_id', currentTaskId)
396
- .eq('body_of_work_id', body_of_work_id);
397
- for (const dep of deps || []) {
398
- if (await checkCircular(dep.depends_on_task_id)) {
399
- return true;
400
- }
401
- }
402
- return false;
403
- };
404
- if (await checkCircular(depends_on_task_id)) {
405
- throw new Error('Cannot add dependency: would create a circular dependency');
406
- }
407
- const { error } = await supabase
408
- .from('body_of_work_task_dependencies')
409
- .insert({
172
+ const apiClient = getApiClient();
173
+ const response = await apiClient.proxy('add_task_dependency', {
410
174
  body_of_work_id,
411
175
  task_id,
412
- depends_on_task_id,
176
+ depends_on_task_id
413
177
  });
414
- if (error) {
415
- if (error.message.includes('duplicate')) {
416
- throw new Error('This dependency already exists');
417
- }
418
- throw new Error(`Failed to add dependency: ${error.message}`);
178
+ if (!response.ok) {
179
+ throw new Error(`Failed to add task dependency: ${response.error}`);
419
180
  }
420
- return {
421
- result: {
422
- success: true,
423
- body_of_work_id,
424
- task_id,
425
- depends_on_task_id,
426
- message: `Task now depends on completion of the specified task`,
427
- },
428
- };
181
+ return { result: response.data };
429
182
  };
430
183
  export const removeTaskDependency = async (args, ctx) => {
431
184
  const { task_id, depends_on_task_id } = args;
@@ -433,15 +186,15 @@ export const removeTaskDependency = async (args, ctx) => {
433
186
  validateUUID(task_id, 'task_id');
434
187
  validateRequired(depends_on_task_id, 'depends_on_task_id');
435
188
  validateUUID(depends_on_task_id, 'depends_on_task_id');
436
- const { supabase } = ctx;
437
- const { error } = await supabase
438
- .from('body_of_work_task_dependencies')
439
- .delete()
440
- .eq('task_id', task_id)
441
- .eq('depends_on_task_id', depends_on_task_id);
442
- if (error)
443
- throw new Error(`Failed to remove dependency: ${error.message}`);
444
- return { result: { success: true, task_id, depends_on_task_id } };
189
+ const apiClient = getApiClient();
190
+ const response = await apiClient.proxy('remove_task_dependency', {
191
+ task_id,
192
+ depends_on_task_id
193
+ });
194
+ if (!response.ok) {
195
+ throw new Error(`Failed to remove task dependency: ${response.error}`);
196
+ }
197
+ return { result: response.data };
445
198
  };
446
199
  export const getTaskDependencies = async (args, ctx) => {
447
200
  const { body_of_work_id, task_id } = args;
@@ -452,148 +205,26 @@ export const getTaskDependencies = async (args, ctx) => {
452
205
  validateUUID(body_of_work_id, 'body_of_work_id');
453
206
  if (task_id)
454
207
  validateUUID(task_id, 'task_id');
455
- const { supabase } = ctx;
456
- let query = supabase
457
- .from('body_of_work_task_dependencies')
458
- .select(`
459
- id,
460
- task_id,
461
- depends_on_task_id,
462
- created_at
463
- `);
464
- if (body_of_work_id) {
465
- query = query.eq('body_of_work_id', body_of_work_id);
466
- }
467
- if (task_id) {
468
- query = query.eq('task_id', task_id);
208
+ const apiClient = getApiClient();
209
+ const response = await apiClient.proxy('get_task_dependencies', {
210
+ body_of_work_id,
211
+ task_id
212
+ });
213
+ if (!response.ok) {
214
+ throw new Error(`Failed to fetch task dependencies: ${response.error}`);
469
215
  }
470
- const { data, error } = await query;
471
- if (error)
472
- throw new Error(`Failed to fetch dependencies: ${error.message}`);
473
- return { result: { dependencies: data || [] } };
216
+ return { result: response.data };
474
217
  };
475
218
  export const getNextBodyOfWorkTask = async (args, ctx) => {
476
219
  const { body_of_work_id } = args;
477
220
  validateRequired(body_of_work_id, 'body_of_work_id');
478
221
  validateUUID(body_of_work_id, 'body_of_work_id');
479
- const { supabase } = ctx;
480
- // Get body of work status
481
- const { data: bow, error: bowError } = await supabase
482
- .from('bodies_of_work')
483
- .select('status, title')
484
- .eq('id', body_of_work_id)
485
- .single();
486
- if (bowError || !bow) {
487
- throw new Error(`Body of work not found: ${body_of_work_id}`);
488
- }
489
- if (bow.status !== 'active') {
490
- return {
491
- result: {
492
- next_task: null,
493
- message: `Body of work is ${bow.status}, not active`,
494
- },
495
- };
496
- }
497
- // Get all tasks with their status and phase
498
- const { data: taskLinks, error: taskError } = await supabase
499
- .from('body_of_work_tasks')
500
- .select(`
501
- task_id,
502
- phase,
503
- order_index,
504
- tasks (
505
- id,
506
- title,
507
- status,
508
- priority,
509
- claimed_by_session_id
510
- )
511
- `)
512
- .eq('body_of_work_id', body_of_work_id)
513
- .order('order_index');
514
- if (taskError)
515
- throw new Error(`Failed to fetch tasks: ${taskError.message}`);
516
- // Get all dependencies
517
- const { data: dependencies } = await supabase
518
- .from('body_of_work_task_dependencies')
519
- .select('task_id, depends_on_task_id')
520
- .eq('body_of_work_id', body_of_work_id);
521
- const depMap = new Map();
522
- for (const dep of dependencies || []) {
523
- const existing = depMap.get(dep.task_id) || [];
524
- existing.push(dep.depends_on_task_id);
525
- depMap.set(dep.task_id, existing);
526
- }
527
- // Build task status map
528
- const taskStatusMap = new Map();
529
- for (const link of taskLinks || []) {
530
- const task = link.tasks;
531
- if (task) {
532
- taskStatusMap.set(task.id, task.status);
533
- }
222
+ const apiClient = getApiClient();
223
+ const response = await apiClient.proxy('get_next_body_of_work_task', { body_of_work_id });
224
+ if (!response.ok) {
225
+ throw new Error(`Failed to get next body of work task: ${response.error}`);
534
226
  }
535
- // Check if all dependencies are completed
536
- const areDependenciesComplete = (taskId) => {
537
- const deps = depMap.get(taskId) || [];
538
- return deps.every((depId) => taskStatusMap.get(depId) === 'completed');
539
- };
540
- // Phase order: pre -> core -> post
541
- const phaseOrder = ['pre', 'core', 'post'];
542
- // Check if all tasks in a phase are completed
543
- const isPhaseComplete = (phase) => {
544
- return (taskLinks || [])
545
- .filter((link) => link.phase === phase)
546
- .every((link) => {
547
- const task = link.tasks;
548
- return task?.status === 'completed';
549
- });
550
- };
551
- // Find the next available task
552
- for (const phase of phaseOrder) {
553
- // Check if we can work on this phase
554
- const prevPhaseIndex = phaseOrder.indexOf(phase) - 1;
555
- if (prevPhaseIndex >= 0 && !isPhaseComplete(phaseOrder[prevPhaseIndex])) {
556
- continue; // Previous phase not complete, skip this phase
557
- }
558
- const phaseTasks = (taskLinks || [])
559
- .filter((link) => link.phase === phase)
560
- .sort((a, b) => a.order_index - b.order_index);
561
- for (const link of phaseTasks) {
562
- const task = link.tasks;
563
- if (!task)
564
- continue;
565
- // Skip completed, cancelled, or in-progress tasks
566
- if (task.status !== 'pending')
567
- continue;
568
- // Skip tasks claimed by another session
569
- if (task.claimed_by_session_id)
570
- continue;
571
- // Check dependencies
572
- if (!areDependenciesComplete(task.id))
573
- continue;
574
- return {
575
- result: {
576
- next_task: {
577
- id: task.id,
578
- title: task.title,
579
- phase: link.phase,
580
- priority: task.priority,
581
- },
582
- body_of_work: {
583
- id: body_of_work_id,
584
- title: bow.title,
585
- },
586
- },
587
- };
588
- }
589
- }
590
- // No available tasks
591
- return {
592
- result: {
593
- next_task: null,
594
- message: 'No available tasks - all are completed, in progress, or blocked by dependencies',
595
- },
596
- };
227
+ return { result: response.data };
597
228
  };
598
229
  /**
599
230
  * Bodies of Work handlers registry