@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,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,65 @@ 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
80
  const { body_of_work_id } = 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}`);
95
- }
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
- }
83
+ const apiClient = getApiClient();
84
+ const response = await apiClient.proxy('get_body_of_work', { body_of_work_id });
85
+ if (!response.ok) {
86
+ throw new Error(`Failed to get body of work: ${response.error}`);
134
87
  }
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
- };
88
+ return { result: response.data };
144
89
  };
145
90
  export const getBodiesOfWork = async (args, ctx) => {
146
- const { project_id, status } = args;
91
+ const { project_id, status, limit = 50, offset = 0, search_query } = args;
147
92
  validateRequired(project_id, 'project_id');
148
93
  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
- };
94
+ const apiClient = getApiClient();
95
+ const response = await apiClient.proxy('get_bodies_of_work', {
96
+ project_id,
97
+ status,
98
+ limit: Math.min(limit, 100),
99
+ offset,
100
+ search_query
209
101
  });
210
- return { result: { bodies_of_work: bodiesWithCounts } };
102
+ if (!response.ok) {
103
+ throw new Error(`Failed to fetch bodies of work: ${response.error}`);
104
+ }
105
+ return { result: response.data };
211
106
  };
212
107
  export const deleteBodyOfWork = async (args, ctx) => {
213
108
  const { body_of_work_id } = args;
214
109
  validateRequired(body_of_work_id, 'body_of_work_id');
215
110
  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}`);
111
+ const apiClient = getApiClient();
112
+ const response = await apiClient.proxy('delete_body_of_work', {
113
+ body_of_work_id
114
+ });
115
+ if (!response.ok) {
116
+ throw new Error(`Failed to delete body of work: ${response.error}`);
117
+ }
222
118
  return { result: { success: true, message: 'Body of work deleted. Tasks are preserved.' } };
223
119
  };
224
120
  export const addTaskToBodyOfWork = async (args, ctx) => {
@@ -227,136 +123,39 @@ export const addTaskToBodyOfWork = async (args, ctx) => {
227
123
  validateUUID(body_of_work_id, 'body_of_work_id');
228
124
  validateRequired(task_id, 'task_id');
229
125
  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({
126
+ const apiClient = getApiClient();
127
+ const response = await apiClient.proxy('add_task_to_body_of_work', {
269
128
  body_of_work_id,
270
129
  task_id,
271
- phase: taskPhase,
272
- order_index: finalOrderIndex,
130
+ phase: phase || 'core',
131
+ order_index
273
132
  });
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
- };
133
+ if (!response.ok) {
134
+ throw new Error(`Failed to add task to body of work: ${response.error}`);
135
+ }
136
+ return { result: response.data };
285
137
  };
286
138
  export const removeTaskFromBodyOfWork = async (args, ctx) => {
287
139
  const { task_id } = args;
288
140
  validateRequired(task_id, 'task_id');
289
141
  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');
142
+ const apiClient = getApiClient();
143
+ const response = await apiClient.proxy('remove_task_from_body_of_work', { task_id });
144
+ if (!response.ok) {
145
+ throw new Error(`Failed to remove task from body of work: ${response.error}`);
308
146
  }
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 } };
147
+ return { result: response.data };
316
148
  };
317
149
  export const activateBodyOfWork = async (args, ctx) => {
318
150
  const { body_of_work_id } = args;
319
151
  validateRequired(body_of_work_id, 'body_of_work_id');
320
152
  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}`);
330
- }
331
- if (bow.status !== 'draft') {
332
- throw new Error(`Can only activate draft bodies of work. Current status: ${bow.status}`);
153
+ const apiClient = getApiClient();
154
+ const response = await apiClient.proxy('activate_body_of_work', { body_of_work_id });
155
+ if (!response.ok) {
156
+ throw new Error(`Failed to activate body of work: ${response.error}`);
333
157
  }
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
- };
158
+ return { result: response.data };
360
159
  };
361
160
  export const addTaskDependency = async (args, ctx) => {
362
161
  const { body_of_work_id, task_id, depends_on_task_id } = args;
@@ -369,63 +168,16 @@ export const addTaskDependency = async (args, ctx) => {
369
168
  if (task_id === depends_on_task_id) {
370
169
  throw new Error('A task cannot depend on itself');
371
170
  }
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({
171
+ const apiClient = getApiClient();
172
+ const response = await apiClient.proxy('add_task_dependency', {
410
173
  body_of_work_id,
411
174
  task_id,
412
- depends_on_task_id,
175
+ depends_on_task_id
413
176
  });
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}`);
177
+ if (!response.ok) {
178
+ throw new Error(`Failed to add task dependency: ${response.error}`);
419
179
  }
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
- };
180
+ return { result: response.data };
429
181
  };
430
182
  export const removeTaskDependency = async (args, ctx) => {
431
183
  const { task_id, depends_on_task_id } = args;
@@ -433,15 +185,15 @@ export const removeTaskDependency = async (args, ctx) => {
433
185
  validateUUID(task_id, 'task_id');
434
186
  validateRequired(depends_on_task_id, 'depends_on_task_id');
435
187
  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 } };
188
+ const apiClient = getApiClient();
189
+ const response = await apiClient.proxy('remove_task_dependency', {
190
+ task_id,
191
+ depends_on_task_id
192
+ });
193
+ if (!response.ok) {
194
+ throw new Error(`Failed to remove task dependency: ${response.error}`);
195
+ }
196
+ return { result: response.data };
445
197
  };
446
198
  export const getTaskDependencies = async (args, ctx) => {
447
199
  const { body_of_work_id, task_id } = args;
@@ -452,148 +204,26 @@ export const getTaskDependencies = async (args, ctx) => {
452
204
  validateUUID(body_of_work_id, 'body_of_work_id');
453
205
  if (task_id)
454
206
  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);
207
+ const apiClient = getApiClient();
208
+ const response = await apiClient.proxy('get_task_dependencies', {
209
+ body_of_work_id,
210
+ task_id
211
+ });
212
+ if (!response.ok) {
213
+ throw new Error(`Failed to fetch task dependencies: ${response.error}`);
469
214
  }
470
- const { data, error } = await query;
471
- if (error)
472
- throw new Error(`Failed to fetch dependencies: ${error.message}`);
473
- return { result: { dependencies: data || [] } };
215
+ return { result: response.data };
474
216
  };
475
217
  export const getNextBodyOfWorkTask = async (args, ctx) => {
476
218
  const { body_of_work_id } = args;
477
219
  validateRequired(body_of_work_id, 'body_of_work_id');
478
220
  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
- }
221
+ const apiClient = getApiClient();
222
+ const response = await apiClient.proxy('get_next_body_of_work_task', { body_of_work_id });
223
+ if (!response.ok) {
224
+ throw new Error(`Failed to get next body of work task: ${response.error}`);
534
225
  }
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
- };
226
+ return { result: response.data };
597
227
  };
598
228
  /**
599
229
  * Bodies of Work handlers registry
@@ -7,6 +7,7 @@
7
7
  * - add_cost_alert
8
8
  * - update_cost_alert
9
9
  * - delete_cost_alert
10
+ * - get_task_costs
10
11
  */
11
12
  import type { Handler, HandlerRegistry } from './types.js';
12
13
  /**