@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,10 @@
14
14
  * - get_deployment_requirements
15
15
  */
16
16
  import { ValidationError, validateRequired, validateUUID, validateEnvironment, } from '../validators.js';
17
+ import { getApiClient } from '../api-client.js';
17
18
  export const requestDeployment = async (args, ctx) => {
18
19
  const { project_id, environment = 'production', version_bump = 'patch', notes, git_ref } = args;
19
- const { supabase, session } = ctx;
20
- const currentSessionId = session.currentSessionId;
20
+ const { session } = ctx;
21
21
  validateRequired(project_id, 'project_id');
22
22
  validateUUID(project_id, 'project_id');
23
23
  validateEnvironment(environment);
@@ -28,204 +28,33 @@ export const requestDeployment = async (args, ctx) => {
28
28
  hint: 'Must be one of: patch, minor, major',
29
29
  });
30
30
  }
31
- // Check for existing active deployment
32
- const { data: existingDeployment } = await supabase
33
- .from('deployments')
34
- .select('id, status')
35
- .eq('project_id', project_id)
36
- .not('status', 'in', '("deployed","failed")')
37
- .single();
38
- if (existingDeployment) {
39
- return {
40
- result: {
41
- success: false,
42
- error: 'A deployment is already in progress',
43
- existing_deployment_id: existingDeployment.id,
44
- existing_status: existingDeployment.status,
45
- hint: 'Wait for the current deployment to complete or cancel it first',
46
- },
47
- };
48
- }
49
- // Check for unvalidated completed tasks
50
- const { data: unvalidatedTasks } = await supabase
51
- .from('tasks')
52
- .select('id, title, completed_at, completed_by_session_id')
53
- .eq('project_id', project_id)
54
- .eq('status', 'completed')
55
- .is('validated_at', null)
56
- .order('completed_at', { ascending: true });
57
- if (unvalidatedTasks && unvalidatedTasks.length > 0) {
58
- return {
59
- result: {
60
- success: false,
61
- error: 'Cannot deploy: There are unvalidated completed tasks',
62
- unvalidated_tasks: unvalidatedTasks.map(t => ({
63
- id: t.id,
64
- title: t.title,
65
- completed_at: t.completed_at,
66
- })),
67
- unvalidated_count: unvalidatedTasks.length,
68
- hint: 'All completed tasks must be validated before deployment. Use validate_task to review each task.',
69
- action: `Call validate_task(task_id: "${unvalidatedTasks[0].id}", approved: true/false, validation_notes: "...")`,
70
- },
71
- };
72
- }
73
- // Get current version from project
74
- const { data: project } = await supabase
75
- .from('projects')
76
- .select('current_version')
77
- .eq('id', project_id)
78
- .single();
79
- const currentVersion = project?.current_version || '0.0.0';
80
- // Create new deployment
81
- const { data: deployment, error } = await supabase
82
- .from('deployments')
83
- .insert({
84
- project_id,
31
+ const apiClient = getApiClient();
32
+ const response = await apiClient.requestDeployment(project_id, {
85
33
  environment,
86
34
  version_bump,
87
35
  notes,
88
- git_ref,
89
- requested_by: 'agent',
90
- requesting_agent_session_id: currentSessionId,
91
- })
92
- .select()
93
- .single();
94
- if (error)
95
- throw error;
96
- // Auto-convert pending deployment requirements to tasks
97
- const { data: pendingRequirements } = await supabase
98
- .from('deployment_requirements')
99
- .select('id, type, title, description, stage, blocking')
100
- .eq('project_id', project_id)
101
- .eq('status', 'pending')
102
- .is('converted_task_id', null);
103
- const convertedTasks = [];
104
- if (pendingRequirements && pendingRequirements.length > 0) {
105
- for (const req of pendingRequirements) {
106
- const isDeployStage = req.stage === 'deployment';
107
- const isBlocking = req.blocking ?? isDeployStage;
108
- const titlePrefix = isBlocking
109
- ? 'DEPLOY:'
110
- : isDeployStage
111
- ? 'DEPLOY:'
112
- : req.stage === 'verification'
113
- ? 'VERIFY:'
114
- : 'PREP:';
115
- // Create linked task
116
- const { data: newTask } = await supabase
117
- .from('tasks')
118
- .insert({
119
- project_id,
120
- title: `${titlePrefix} ${req.title}`,
121
- description: `[${req.type}] ${req.description || req.title}`,
122
- priority: 1,
123
- status: 'pending',
124
- blocking: isBlocking,
125
- created_by: 'agent',
126
- created_by_session_id: currentSessionId,
127
- })
128
- .select('id')
129
- .single();
130
- if (newTask) {
131
- // Link task to requirement WITHOUT changing status
132
- // This keeps the requirement visible in the deployment steps list (permanent)
133
- await supabase
134
- .from('deployment_requirements')
135
- .update({
136
- converted_task_id: newTask.id,
137
- })
138
- .eq('id', req.id);
139
- convertedTasks.push({
140
- task_id: newTask.id,
141
- requirement_id: req.id,
142
- title: `${titlePrefix} ${req.title}`,
143
- });
144
- }
145
- }
146
- }
147
- // Log progress
148
- const convertedMsg = convertedTasks.length > 0
149
- ? ` (${convertedTasks.length} requirements converted to tasks)`
150
- : '';
151
- await supabase.from('progress_logs').insert({
152
- project_id,
153
- summary: `Deployment requested for ${environment} (${version_bump} bump from ${currentVersion})${convertedMsg}`,
154
- details: notes || undefined,
155
- created_by: 'agent',
156
- created_by_session_id: currentSessionId,
36
+ git_ref
157
37
  });
158
- return {
159
- result: {
160
- success: true,
161
- deployment_id: deployment.id,
162
- status: deployment.status,
163
- environment: deployment.environment,
164
- version_bump,
165
- current_version: currentVersion,
166
- converted_requirements: convertedTasks.length,
167
- converted_tasks: convertedTasks.length > 0 ? convertedTasks : undefined,
168
- message: convertedTasks.length > 0
169
- ? `Deployment created. ${convertedTasks.length} requirements converted to tasks. Run build/tests then call claim_deployment_validation.`
170
- : 'Deployment created. Run build/tests then call claim_deployment_validation.',
171
- },
172
- };
38
+ if (!response.ok) {
39
+ throw new Error(response.error || 'Failed to request deployment');
40
+ }
41
+ return { result: response.data };
173
42
  };
174
43
  export const claimDeploymentValidation = async (args, ctx) => {
175
44
  const { project_id } = args;
176
- const { supabase, session } = ctx;
177
- const currentSessionId = session.currentSessionId;
45
+ const { session } = ctx;
178
46
  validateRequired(project_id, 'project_id');
179
47
  validateUUID(project_id, 'project_id');
180
- // Find pending deployment
181
- const { data: deployment, error: fetchError } = await supabase
182
- .from('deployments')
183
- .select('*')
184
- .eq('project_id', project_id)
185
- .eq('status', 'pending')
186
- .single();
187
- if (fetchError || !deployment) {
188
- return {
189
- result: {
190
- success: false,
191
- error: 'No pending deployment found',
192
- hint: 'Use request_deployment to create a deployment first, or check_deployment_status to see current state',
193
- },
194
- };
48
+ const apiClient = getApiClient();
49
+ const response = await apiClient.claimDeploymentValidation(project_id, session.currentSessionId || undefined);
50
+ if (!response.ok) {
51
+ throw new Error(response.error || 'Failed to claim deployment validation');
195
52
  }
196
- // Claim validation
197
- const { data: updated, error: updateError } = await supabase
198
- .from('deployments')
199
- .update({
200
- status: 'validating',
201
- validation_agent_session_id: currentSessionId,
202
- validation_started_at: new Date().toISOString(),
203
- })
204
- .eq('id', deployment.id)
205
- .eq('status', 'pending')
206
- .select()
207
- .single();
208
- if (updateError || !updated) {
209
- return {
210
- result: {
211
- success: false,
212
- error: 'Failed to claim validation - deployment may have been claimed by another agent',
213
- },
214
- };
215
- }
216
- return {
217
- result: {
218
- success: true,
219
- deployment_id: deployment.id,
220
- status: 'validating',
221
- message: 'Validation claimed. Run build and tests, then call report_validation with results.',
222
- },
223
- };
53
+ return { result: response.data };
224
54
  };
225
55
  export const reportValidation = async (args, ctx) => {
226
56
  const { project_id, build_passed, tests_passed, error_message } = args;
227
- const { supabase, session } = ctx;
228
- const currentSessionId = session.currentSessionId;
57
+ const { session } = ctx;
229
58
  validateRequired(project_id, 'project_id');
230
59
  validateUUID(project_id, 'project_id');
231
60
  if (build_passed === undefined) {
@@ -234,211 +63,43 @@ export const reportValidation = async (args, ctx) => {
234
63
  hint: 'Set to true if the build succeeded, false otherwise',
235
64
  });
236
65
  }
237
- // Find validating deployment
238
- const { data: deployment, error: fetchError } = await supabase
239
- .from('deployments')
240
- .select('id')
241
- .eq('project_id', project_id)
242
- .eq('status', 'validating')
243
- .single();
244
- if (fetchError || !deployment) {
245
- return {
246
- result: {
247
- success: false,
248
- error: 'No deployment being validated. Use claim_deployment_validation first.',
249
- },
250
- };
251
- }
252
- const validationPassed = build_passed && (tests_passed !== false);
253
- const newStatus = validationPassed ? 'ready' : 'failed';
254
- const { error: updateError } = await supabase
255
- .from('deployments')
256
- .update({
257
- status: newStatus,
66
+ const apiClient = getApiClient();
67
+ const response = await apiClient.reportValidation(project_id, {
258
68
  build_passed,
259
- tests_passed: tests_passed ?? null,
260
- validation_completed_at: new Date().toISOString(),
261
- validation_error: error_message || null,
262
- })
263
- .eq('id', deployment.id);
264
- if (updateError)
265
- throw updateError;
266
- // Log result
267
- await supabase.from('progress_logs').insert({
268
- project_id,
269
- summary: validationPassed
270
- ? `Deployment validation passed - ready to deploy`
271
- : `Deployment validation failed: ${error_message || 'build/tests failed'}`,
272
- details: `Build: ${build_passed ? 'passed' : 'failed'}, Tests: ${tests_passed === undefined ? 'skipped' : tests_passed ? 'passed' : 'failed'}`,
273
- created_by: 'agent',
274
- created_by_session_id: currentSessionId,
69
+ tests_passed: tests_passed ?? true,
70
+ error_message
275
71
  });
276
- // Auto-create task for failed validation
277
- let createdTaskId = null;
278
- if (!validationPassed) {
279
- const failureType = !build_passed ? 'build' : 'test';
280
- const { data: newTask } = await supabase
281
- .from('tasks')
282
- .insert({
283
- project_id,
284
- title: `Fix ${failureType} failure`,
285
- description: error_message || `${failureType} failed during deployment validation`,
286
- priority: 1,
287
- status: 'pending',
288
- created_by: 'agent',
289
- created_by_session_id: currentSessionId,
290
- estimated_minutes: 30,
291
- })
292
- .select('id')
293
- .single();
294
- createdTaskId = newTask?.id || null;
72
+ if (!response.ok) {
73
+ throw new Error(response.error || 'Failed to report validation');
295
74
  }
296
- return {
297
- result: {
298
- success: true,
299
- status: newStatus,
300
- passed: validationPassed,
301
- ...(createdTaskId && { fix_task_id: createdTaskId }),
302
- },
303
- };
75
+ return { result: response.data };
304
76
  };
305
77
  export const checkDeploymentStatus = async (args, ctx) => {
306
78
  const { project_id } = args;
307
- const { supabase } = ctx;
308
79
  validateRequired(project_id, 'project_id');
309
80
  validateUUID(project_id, 'project_id');
310
- // Get most recent deployment
311
- const { data: deployment, error } = await supabase
312
- .from('deployments')
313
- .select('*')
314
- .eq('project_id', project_id)
315
- .order('created_at', { ascending: false })
316
- .limit(1)
317
- .single();
318
- if (error || !deployment) {
319
- return {
320
- result: {
321
- has_deployment: false,
322
- message: 'No deployments found for this project',
323
- },
324
- };
325
- }
326
- // Auto-timeout stale deployments
327
- const DEPLOYMENT_TIMEOUT_MS = {
328
- pending: 30 * 60 * 1000,
329
- validating: 15 * 60 * 1000,
330
- ready: 30 * 60 * 1000,
331
- deploying: 10 * 60 * 1000,
332
- };
333
- if (!['deployed', 'failed'].includes(deployment.status)) {
334
- const timeout = DEPLOYMENT_TIMEOUT_MS[deployment.status];
335
- if (timeout) {
336
- const startTime = deployment.status === 'deploying'
337
- ? deployment.deployment_started_at
338
- : deployment.status === 'validating'
339
- ? deployment.validation_started_at
340
- : deployment.created_at;
341
- if (startTime && Date.now() - new Date(startTime).getTime() > timeout) {
342
- const timeoutError = `Timed out: deployment was stuck in '${deployment.status}' state for too long`;
343
- await supabase
344
- .from('deployments')
345
- .update({
346
- status: 'failed',
347
- deployment_error: timeoutError,
348
- deployment_completed_at: new Date().toISOString(),
349
- })
350
- .eq('id', deployment.id);
351
- deployment.status = 'failed';
352
- deployment.deployment_error = timeoutError;
353
- }
354
- }
81
+ const apiClient = getApiClient();
82
+ const response = await apiClient.checkDeploymentStatus(project_id);
83
+ if (!response.ok) {
84
+ throw new Error(response.error || 'Failed to check deployment status');
355
85
  }
356
- return {
357
- result: {
358
- has_deployment: true,
359
- deployment: {
360
- id: deployment.id,
361
- status: deployment.status,
362
- environment: deployment.environment,
363
- requested_by: deployment.requested_by,
364
- build_passed: deployment.build_passed,
365
- tests_passed: deployment.tests_passed,
366
- validation_error: deployment.validation_error,
367
- deployment_error: deployment.deployment_error,
368
- deployment_summary: deployment.deployment_summary,
369
- notes: deployment.notes,
370
- git_ref: deployment.git_ref,
371
- created_at: deployment.created_at,
372
- validation_started_at: deployment.validation_started_at,
373
- validation_completed_at: deployment.validation_completed_at,
374
- deployment_started_at: deployment.deployment_started_at,
375
- deployment_completed_at: deployment.deployment_completed_at,
376
- },
377
- },
378
- };
86
+ return { result: response.data };
379
87
  };
380
88
  export const startDeployment = async (args, ctx) => {
381
89
  const { project_id } = args;
382
- const { supabase, session } = ctx;
383
- const currentSessionId = session.currentSessionId;
90
+ const { session } = ctx;
384
91
  validateRequired(project_id, 'project_id');
385
92
  validateUUID(project_id, 'project_id');
386
- // Find ready deployment and project deployment instructions
387
- const [deploymentResult, projectResult] = await Promise.all([
388
- supabase
389
- .from('deployments')
390
- .select('id, environment')
391
- .eq('project_id', project_id)
392
- .eq('status', 'ready')
393
- .single(),
394
- supabase
395
- .from('projects')
396
- .select('deployment_instructions, git_main_branch')
397
- .eq('id', project_id)
398
- .single(),
399
- ]);
400
- if (deploymentResult.error || !deploymentResult.data) {
401
- return {
402
- result: {
403
- success: false,
404
- error: 'No deployment ready. Must pass validation first.',
405
- },
406
- };
93
+ const apiClient = getApiClient();
94
+ const response = await apiClient.startDeployment(project_id, session.currentSessionId || undefined);
95
+ if (!response.ok) {
96
+ throw new Error(response.error || 'Failed to start deployment');
407
97
  }
408
- const deployment = deploymentResult.data;
409
- const project = projectResult.data;
410
- const { error: updateError } = await supabase
411
- .from('deployments')
412
- .update({
413
- status: 'deploying',
414
- deployment_started_at: new Date().toISOString(),
415
- })
416
- .eq('id', deployment.id);
417
- if (updateError)
418
- throw updateError;
419
- await supabase.from('progress_logs').insert({
420
- project_id,
421
- summary: `Deployment to ${deployment.environment} started`,
422
- created_by: 'agent',
423
- created_by_session_id: currentSessionId,
424
- });
425
- const result = {
426
- success: true,
427
- status: 'deploying',
428
- env: deployment.environment,
429
- };
430
- if (project?.deployment_instructions) {
431
- result.instructions = project.deployment_instructions;
432
- }
433
- else {
434
- result.instructions = `No deployment instructions configured. Common steps:\n1. Push to ${project?.git_main_branch || 'main'} branch\n2. Or run your deploy command (e.g., fly deploy, vercel deploy)\n3. Call complete_deployment when done`;
435
- }
436
- return { result };
98
+ return { result: response.data };
437
99
  };
438
100
  export const completeDeployment = async (args, ctx) => {
439
101
  const { project_id, success, summary } = args;
440
- const { supabase, session } = ctx;
441
- const currentSessionId = session.currentSessionId;
102
+ const { session } = ctx;
442
103
  validateRequired(project_id, 'project_id');
443
104
  validateUUID(project_id, 'project_id');
444
105
  if (success === undefined) {
@@ -447,117 +108,29 @@ export const completeDeployment = async (args, ctx) => {
447
108
  hint: 'Set to true if deployment succeeded, false otherwise',
448
109
  });
449
110
  }
450
- // Find deploying deployment
451
- const { data: deployment, error: fetchError } = await supabase
452
- .from('deployments')
453
- .select('id, environment, version_bump')
454
- .eq('project_id', project_id)
455
- .eq('status', 'deploying')
456
- .single();
457
- if (fetchError || !deployment) {
458
- return {
459
- result: {
460
- success: false,
461
- error: 'No deployment in progress. Use start_deployment first.',
462
- },
463
- };
464
- }
465
- const newStatus = success ? 'deployed' : 'failed';
466
- let newVersion = null;
467
- // If successful, calculate and store new version
468
- if (success) {
469
- const { data: project } = await supabase
470
- .from('projects')
471
- .select('current_version')
472
- .eq('id', project_id)
473
- .single();
474
- const currentVersion = project?.current_version || '0.0.0';
475
- const versionBump = deployment.version_bump || 'patch';
476
- const parts = currentVersion.split('.').map((p) => parseInt(p, 10) || 0);
477
- let [major, minor, patch] = [parts[0] || 0, parts[1] || 0, parts[2] || 0];
478
- switch (versionBump) {
479
- case 'major':
480
- major += 1;
481
- minor = 0;
482
- patch = 0;
483
- break;
484
- case 'minor':
485
- minor += 1;
486
- patch = 0;
487
- break;
488
- default: patch += 1;
489
- }
490
- newVersion = `${major}.${minor}.${patch}`;
491
- await supabase
492
- .from('projects')
493
- .update({ current_version: newVersion })
494
- .eq('id', project_id);
495
- }
496
- const { error: updateError } = await supabase
497
- .from('deployments')
498
- .update({
499
- status: newStatus,
500
- version: newVersion,
501
- deployment_completed_at: new Date().toISOString(),
502
- deployment_summary: summary || null,
503
- })
504
- .eq('id', deployment.id);
505
- if (updateError)
506
- throw updateError;
507
- await supabase.from('progress_logs').insert({
508
- project_id,
509
- summary: success
510
- ? `Deployed to ${deployment.environment}${newVersion ? ` v${newVersion}` : ''}`
511
- : `Deployment failed`,
512
- details: summary || undefined,
513
- created_by: 'agent',
514
- created_by_session_id: currentSessionId,
111
+ const apiClient = getApiClient();
112
+ const response = await apiClient.completeDeployment(project_id, {
113
+ success,
114
+ summary
515
115
  });
516
- return {
517
- result: {
518
- success: true,
519
- status: newStatus,
520
- ...(newVersion && { version: newVersion }),
521
- },
522
- };
116
+ if (!response.ok) {
117
+ throw new Error(response.error || 'Failed to complete deployment');
118
+ }
119
+ return { result: response.data };
523
120
  };
524
121
  export const cancelDeployment = async (args, ctx) => {
525
122
  const { project_id, reason } = args;
526
- const { supabase, session } = ctx;
527
- const currentSessionId = session.currentSessionId;
528
123
  validateRequired(project_id, 'project_id');
529
124
  validateUUID(project_id, 'project_id');
530
- const { data: deployment, error: fetchError } = await supabase
531
- .from('deployments')
532
- .select('id')
533
- .eq('project_id', project_id)
534
- .not('status', 'in', '("deployed","failed")')
535
- .single();
536
- if (fetchError || !deployment) {
537
- return { result: { success: false, error: 'No active deployment' } };
125
+ const apiClient = getApiClient();
126
+ const response = await apiClient.cancelDeployment(project_id, reason);
127
+ if (!response.ok) {
128
+ throw new Error(response.error || 'Failed to cancel deployment');
538
129
  }
539
- const { error: updateError } = await supabase
540
- .from('deployments')
541
- .update({
542
- status: 'failed',
543
- deployment_error: `Cancelled: ${reason || 'unspecified'}`,
544
- deployment_completed_at: new Date().toISOString(),
545
- })
546
- .eq('id', deployment.id);
547
- if (updateError)
548
- throw updateError;
549
- await supabase.from('progress_logs').insert({
550
- project_id,
551
- summary: `Deployment cancelled${reason ? `: ${reason}` : ''}`,
552
- created_by: 'agent',
553
- created_by_session_id: currentSessionId,
554
- });
555
- return { result: { success: true } };
130
+ return { result: response.data };
556
131
  };
557
132
  export const addDeploymentRequirement = async (args, ctx) => {
558
- const { project_id, type, title, description, file_path, stage = 'preparation', blocking = false } = args;
559
- const { supabase, session } = ctx;
560
- const currentSessionId = session.currentSessionId;
133
+ const { project_id, type, title, description, file_path, stage = 'preparation', blocking = false, recurring = false } = args;
561
134
  validateRequired(project_id, 'project_id');
562
135
  validateUUID(project_id, 'project_id');
563
136
  validateRequired(type, 'type');
@@ -570,124 +143,51 @@ export const addDeploymentRequirement = async (args, ctx) => {
570
143
  if (!validStages.includes(stage)) {
571
144
  throw new ValidationError(`stage must be one of: ${validStages.join(', ')}`);
572
145
  }
573
- const { data: requirement, error } = await supabase
574
- .from('deployment_requirements')
575
- .insert({
576
- project_id,
577
- type,
146
+ const apiClient = getApiClient();
147
+ const response = await apiClient.addDeploymentRequirement(project_id, {
148
+ type: type,
578
149
  title,
579
- description: description || null,
580
- file_path: file_path || null,
581
- stage,
150
+ description,
151
+ file_path,
152
+ stage: stage,
582
153
  blocking,
583
- created_by_session_id: currentSessionId,
584
- })
585
- .select('id, type, title, stage, blocking')
586
- .single();
587
- if (error)
588
- throw new Error(`Failed to add requirement: ${error.message}`);
589
- const blockingText = blocking ? ' (BLOCKING)' : '';
590
- await supabase.from('progress_logs').insert({
591
- project_id,
592
- summary: `Added ${stage} deployment requirement${blockingText}: ${title}`,
593
- details: `Type: ${type}, Stage: ${stage}${blocking ? ', Blocking: true' : ''}${file_path ? `, File: ${file_path}` : ''}`,
594
- created_by: 'agent',
595
- created_by_session_id: currentSessionId,
154
+ recurring
596
155
  });
597
- const stageMessage = blocking
598
- ? 'Will block all other work when converted to task.'
599
- : stage === 'deployment'
600
- ? 'Will run during deployment.'
601
- : stage === 'verification'
602
- ? 'Will run after deployment for verification.'
603
- : 'Will run during preparation phase.';
604
- return {
605
- result: {
606
- success: true,
607
- requirement_id: requirement.id,
608
- stage: requirement.stage,
609
- message: `Added ${type} requirement. ${stageMessage}`,
610
- },
611
- };
156
+ if (!response.ok) {
157
+ throw new Error(response.error || 'Failed to add deployment requirement');
158
+ }
159
+ return { result: response.data };
612
160
  };
613
161
  export const completeDeploymentRequirement = async (args, ctx) => {
614
162
  const { requirement_id } = args;
615
- const { supabase, session } = ctx;
616
- const currentSessionId = session.currentSessionId;
617
163
  validateRequired(requirement_id, 'requirement_id');
618
164
  validateUUID(requirement_id, 'requirement_id');
619
- const { data: requirement, error: fetchError } = await supabase
620
- .from('deployment_requirements')
621
- .select('id, title, status')
622
- .eq('id', requirement_id)
623
- .single();
624
- if (fetchError || !requirement) {
625
- throw new Error('Requirement not found');
165
+ const apiClient = getApiClient();
166
+ const response = await apiClient.completeDeploymentRequirement(requirement_id);
167
+ if (!response.ok) {
168
+ throw new Error(response.error || 'Failed to complete deployment requirement');
626
169
  }
627
- if (requirement.status !== 'pending') {
628
- return {
629
- result: {
630
- success: false,
631
- error: `Requirement is already ${requirement.status}`,
632
- },
633
- };
634
- }
635
- const { error: updateError } = await supabase
636
- .from('deployment_requirements')
637
- .update({
638
- status: 'completed',
639
- completed_at: new Date().toISOString(),
640
- completed_by: currentSessionId || 'agent',
641
- })
642
- .eq('id', requirement_id);
643
- if (updateError)
644
- throw updateError;
645
- return {
646
- result: {
647
- success: true,
648
- requirement_id,
649
- title: requirement.title,
650
- },
651
- };
170
+ return { result: response.data };
652
171
  };
653
172
  export const getDeploymentRequirements = async (args, ctx) => {
654
173
  const { project_id, status = 'pending', stage } = args;
655
- const { supabase } = ctx;
656
174
  validateRequired(project_id, 'project_id');
657
175
  validateUUID(project_id, 'project_id');
658
- let query = supabase
659
- .from('deployment_requirements')
660
- .select('id, type, title, description, file_path, status, stage, blocking, created_at, completed_at')
661
- .eq('project_id', project_id)
662
- .order('stage', { ascending: true })
663
- .order('created_at', { ascending: false });
664
- if (status !== 'all') {
665
- query = query.eq('status', status);
666
- }
667
- if (stage && stage !== 'all') {
668
- query = query.eq('stage', stage);
176
+ const apiClient = getApiClient();
177
+ const response = await apiClient.getDeploymentRequirements(project_id, {
178
+ status: status,
179
+ stage: stage
180
+ });
181
+ if (!response.ok) {
182
+ throw new Error(response.error || 'Failed to get deployment requirements');
669
183
  }
670
- const { data: requirements, error } = await query;
671
- if (error)
672
- throw new Error(`Failed to fetch requirements: ${error.message}`);
673
- const preparationPending = requirements?.filter(r => r.status === 'pending' && r.stage === 'preparation').length || 0;
674
- const deploymentPending = requirements?.filter(r => r.status === 'pending' && r.stage === 'deployment').length || 0;
675
- return {
676
- result: {
677
- requirements: requirements || [],
678
- preparation_pending: preparationPending,
679
- deployment_pending: deploymentPending,
680
- deployment_blocked: preparationPending > 0 || deploymentPending > 0,
681
- },
682
- };
184
+ return { result: response.data };
683
185
  };
684
186
  // ============================================================================
685
187
  // Scheduled Deployments
686
188
  // ============================================================================
687
189
  export const scheduleDeployment = async (args, ctx) => {
688
190
  const { project_id, environment = 'production', version_bump = 'patch', schedule_type = 'once', scheduled_at, auto_trigger = true, notes, git_ref, } = args;
689
- const { supabase, session } = ctx;
690
- const currentSessionId = session.currentSessionId;
691
191
  validateRequired(project_id, 'project_id');
692
192
  validateUUID(project_id, 'project_id');
693
193
  validateRequired(scheduled_at, 'scheduled_at');
@@ -717,82 +217,34 @@ export const scheduleDeployment = async (args, ctx) => {
717
217
  field: 'scheduled_at',
718
218
  });
719
219
  }
720
- // Create scheduled deployment
721
- const { data: schedule, error } = await supabase
722
- .from('scheduled_deployments')
723
- .insert({
724
- project_id,
725
- environment,
726
- version_bump,
727
- schedule_type,
220
+ const apiClient = getApiClient();
221
+ const response = await apiClient.scheduleDeployment(project_id, {
222
+ environment: environment,
223
+ version_bump: version_bump,
224
+ schedule_type: schedule_type,
728
225
  scheduled_at: scheduledDate.toISOString(),
729
226
  auto_trigger,
730
- notes: notes || null,
731
- git_ref: git_ref || null,
732
- created_by: 'agent',
733
- created_by_session_id: currentSessionId,
734
- })
735
- .select('id, scheduled_at, schedule_type')
736
- .single();
737
- if (error)
738
- throw new Error(`Failed to create schedule: ${error.message}`);
739
- // Log progress
740
- await supabase.from('progress_logs').insert({
741
- project_id,
742
- summary: `Scheduled ${schedule_type} deployment to ${environment} for ${scheduledDate.toISOString()}`,
743
- details: `Auto-trigger: ${auto_trigger}, Version bump: ${version_bump}`,
744
- created_by: 'agent',
745
- created_by_session_id: currentSessionId,
227
+ notes,
228
+ git_ref
746
229
  });
747
- return {
748
- result: {
749
- success: true,
750
- schedule_id: schedule.id,
751
- scheduled_at: schedule.scheduled_at,
752
- schedule_type: schedule.schedule_type,
753
- auto_trigger,
754
- message: auto_trigger
755
- ? 'Deployment scheduled. Will trigger automatically when time arrives.'
756
- : 'Deployment scheduled. Manual trigger required from dashboard.',
757
- },
758
- };
230
+ if (!response.ok) {
231
+ throw new Error(response.error || 'Failed to schedule deployment');
232
+ }
233
+ return { result: response.data };
759
234
  };
760
235
  export const getScheduledDeployments = async (args, ctx) => {
761
236
  const { project_id, include_disabled = false } = args;
762
- const { supabase } = ctx;
763
237
  validateRequired(project_id, 'project_id');
764
238
  validateUUID(project_id, 'project_id');
765
- let query = supabase
766
- .from('scheduled_deployments')
767
- .select('*')
768
- .eq('project_id', project_id)
769
- .order('scheduled_at', { ascending: true });
770
- if (!include_disabled) {
771
- query = query.eq('enabled', true);
239
+ const apiClient = getApiClient();
240
+ const response = await apiClient.getScheduledDeployments(project_id, include_disabled);
241
+ if (!response.ok) {
242
+ throw new Error(response.error || 'Failed to get scheduled deployments');
772
243
  }
773
- const { data: schedules, error } = await query;
774
- if (error)
775
- throw new Error(`Failed to fetch schedules: ${error.message}`);
776
- const now = new Date();
777
- const schedulesWithStatus = (schedules || []).map(s => ({
778
- ...s,
779
- is_due: s.enabled && new Date(s.scheduled_at) <= now,
780
- }));
781
- const dueCount = schedulesWithStatus.filter(s => s.is_due && s.auto_trigger).length;
782
- return {
783
- result: {
784
- schedules: schedulesWithStatus,
785
- count: schedulesWithStatus.length,
786
- due_count: dueCount,
787
- ...(dueCount > 0 && {
788
- hint: 'There are due schedules. Call trigger_scheduled_deployment to execute.',
789
- }),
790
- },
791
- };
244
+ return { result: response.data };
792
245
  };
793
246
  export const updateScheduledDeployment = async (args, ctx) => {
794
247
  const { schedule_id, environment, version_bump, schedule_type, scheduled_at, auto_trigger, enabled, notes, git_ref, } = args;
795
- const { supabase } = ctx;
796
248
  validateRequired(schedule_id, 'schedule_id');
797
249
  validateUUID(schedule_id, 'schedule_id');
798
250
  const updates = {};
@@ -830,213 +282,46 @@ export const updateScheduledDeployment = async (args, ctx) => {
830
282
  if (Object.keys(updates).length === 0) {
831
283
  return { result: { success: false, error: 'No updates provided' } };
832
284
  }
833
- const { error } = await supabase
834
- .from('scheduled_deployments')
835
- .update(updates)
836
- .eq('id', schedule_id);
837
- if (error)
838
- throw new Error(`Failed to update schedule: ${error.message}`);
839
- return { result: { success: true, schedule_id } };
285
+ const apiClient = getApiClient();
286
+ const response = await apiClient.updateScheduledDeployment(schedule_id, updates);
287
+ if (!response.ok) {
288
+ throw new Error(response.error || 'Failed to update scheduled deployment');
289
+ }
290
+ return { result: response.data };
840
291
  };
841
292
  export const deleteScheduledDeployment = async (args, ctx) => {
842
293
  const { schedule_id } = args;
843
- const { supabase } = ctx;
844
294
  validateRequired(schedule_id, 'schedule_id');
845
295
  validateUUID(schedule_id, 'schedule_id');
846
- const { error } = await supabase
847
- .from('scheduled_deployments')
848
- .delete()
849
- .eq('id', schedule_id);
850
- if (error)
851
- throw new Error(`Failed to delete schedule: ${error.message}`);
852
- return { result: { success: true } };
296
+ const apiClient = getApiClient();
297
+ const response = await apiClient.deleteScheduledDeployment(schedule_id);
298
+ if (!response.ok) {
299
+ throw new Error(response.error || 'Failed to delete scheduled deployment');
300
+ }
301
+ return { result: response.data };
853
302
  };
854
303
  export const triggerScheduledDeployment = async (args, ctx) => {
855
304
  const { schedule_id } = args;
856
- const { supabase, session } = ctx;
857
- const currentSessionId = session.currentSessionId;
305
+ const { session } = ctx;
858
306
  validateRequired(schedule_id, 'schedule_id');
859
307
  validateUUID(schedule_id, 'schedule_id');
860
- // Get the schedule
861
- const { data: schedule, error: fetchError } = await supabase
862
- .from('scheduled_deployments')
863
- .select('*')
864
- .eq('id', schedule_id)
865
- .single();
866
- if (fetchError || !schedule) {
867
- return { result: { success: false, error: 'Schedule not found' } };
308
+ const apiClient = getApiClient();
309
+ const response = await apiClient.triggerScheduledDeployment(schedule_id, session.currentSessionId || undefined);
310
+ if (!response.ok) {
311
+ throw new Error(response.error || 'Failed to trigger scheduled deployment');
868
312
  }
869
- if (!schedule.enabled) {
870
- return { result: { success: false, error: 'Schedule is disabled' } };
871
- }
872
- // Check for existing active deployment
873
- const { data: existingDeployment } = await supabase
874
- .from('deployments')
875
- .select('id, status')
876
- .eq('project_id', schedule.project_id)
877
- .not('status', 'in', '("deployed","failed")')
878
- .single();
879
- if (existingDeployment) {
880
- return {
881
- result: {
882
- success: false,
883
- error: 'A deployment is already in progress',
884
- existing_deployment_id: existingDeployment.id,
885
- hint: 'Wait for current deployment to complete or cancel it first',
886
- },
887
- };
888
- }
889
- // Create the deployment (similar to request_deployment)
890
- const { data: deployment, error: deployError } = await supabase
891
- .from('deployments')
892
- .insert({
893
- project_id: schedule.project_id,
894
- environment: schedule.environment,
895
- version_bump: schedule.version_bump,
896
- notes: schedule.notes,
897
- git_ref: schedule.git_ref,
898
- requested_by: 'agent',
899
- requesting_agent_session_id: currentSessionId,
900
- })
901
- .select('id, status')
902
- .single();
903
- if (deployError)
904
- throw new Error(`Failed to create deployment: ${deployError.message}`);
905
- // Auto-convert pending deployment requirements to tasks
906
- const { data: pendingRequirements } = await supabase
907
- .from('deployment_requirements')
908
- .select('id, type, title, description, stage, blocking')
909
- .eq('project_id', schedule.project_id)
910
- .eq('status', 'pending')
911
- .is('converted_task_id', null);
912
- const convertedTasks = [];
913
- if (pendingRequirements && pendingRequirements.length > 0) {
914
- for (const req of pendingRequirements) {
915
- const isDeployStage = req.stage === 'deployment';
916
- const isBlocking = req.blocking ?? isDeployStage;
917
- const titlePrefix = isBlocking
918
- ? 'DEPLOY:'
919
- : isDeployStage
920
- ? 'DEPLOY:'
921
- : req.stage === 'verification'
922
- ? 'VERIFY:'
923
- : 'PREP:';
924
- // Create linked task
925
- const { data: newTask } = await supabase
926
- .from('tasks')
927
- .insert({
928
- project_id: schedule.project_id,
929
- title: `${titlePrefix} ${req.title}`,
930
- description: `[${req.type}] ${req.description || req.title}`,
931
- priority: 1,
932
- status: 'pending',
933
- blocking: isBlocking,
934
- created_by: 'agent',
935
- created_by_session_id: currentSessionId,
936
- })
937
- .select('id')
938
- .single();
939
- if (newTask) {
940
- // Link task to requirement WITHOUT changing status
941
- // This keeps the requirement visible in the deployment steps list (permanent)
942
- await supabase
943
- .from('deployment_requirements')
944
- .update({
945
- converted_task_id: newTask.id,
946
- })
947
- .eq('id', req.id);
948
- convertedTasks.push({
949
- task_id: newTask.id,
950
- requirement_id: req.id,
951
- title: `${titlePrefix} ${req.title}`,
952
- });
953
- }
954
- }
955
- }
956
- // Update the schedule
957
- const scheduleUpdates = {
958
- last_triggered_at: new Date().toISOString(),
959
- last_deployment_id: deployment.id,
960
- trigger_count: schedule.trigger_count + 1,
961
- };
962
- // For recurring schedules, calculate next run time
963
- if (schedule.schedule_type !== 'once') {
964
- const currentScheduledAt = new Date(schedule.scheduled_at);
965
- let nextScheduledAt;
966
- switch (schedule.schedule_type) {
967
- case 'daily':
968
- nextScheduledAt = new Date(currentScheduledAt.getTime() + 24 * 60 * 60 * 1000);
969
- break;
970
- case 'weekly':
971
- nextScheduledAt = new Date(currentScheduledAt.getTime() + 7 * 24 * 60 * 60 * 1000);
972
- break;
973
- case 'monthly':
974
- nextScheduledAt = new Date(currentScheduledAt.getTime() + 30 * 24 * 60 * 60 * 1000);
975
- break;
976
- default:
977
- nextScheduledAt = currentScheduledAt;
978
- }
979
- scheduleUpdates.scheduled_at = nextScheduledAt.toISOString();
980
- }
981
- else {
982
- // One-time schedule, disable it
983
- scheduleUpdates.enabled = false;
984
- }
985
- await supabase
986
- .from('scheduled_deployments')
987
- .update(scheduleUpdates)
988
- .eq('id', schedule_id);
989
- // Log progress
990
- const convertedMsg = convertedTasks.length > 0
991
- ? `, ${convertedTasks.length} requirements converted to tasks`
992
- : '';
993
- await supabase.from('progress_logs').insert({
994
- project_id: schedule.project_id,
995
- summary: `Triggered scheduled deployment to ${schedule.environment}${convertedMsg}`,
996
- details: `Schedule: ${schedule.schedule_type}, Trigger #${schedule.trigger_count + 1}`,
997
- created_by: 'agent',
998
- created_by_session_id: currentSessionId,
999
- });
1000
- return {
1001
- result: {
1002
- success: true,
1003
- deployment_id: deployment.id,
1004
- schedule_id,
1005
- schedule_type: schedule.schedule_type,
1006
- next_scheduled_at: schedule.schedule_type !== 'once' ? scheduleUpdates.scheduled_at : null,
1007
- converted_requirements: convertedTasks.length,
1008
- converted_tasks: convertedTasks.length > 0 ? convertedTasks : undefined,
1009
- message: convertedTasks.length > 0
1010
- ? `Deployment created from schedule. ${convertedTasks.length} requirements converted to tasks. Run validation then deploy.`
1011
- : 'Deployment created from schedule. Run validation then deploy.',
1012
- },
1013
- };
313
+ return { result: response.data };
1014
314
  };
1015
315
  export const checkDueDeployments = async (args, ctx) => {
1016
316
  const { project_id } = args;
1017
- const { supabase } = ctx;
1018
317
  validateRequired(project_id, 'project_id');
1019
318
  validateUUID(project_id, 'project_id');
1020
- // Find schedules that are due (enabled, auto_trigger, and scheduled_at <= now)
1021
- const { data: dueSchedules, error } = await supabase
1022
- .from('scheduled_deployments')
1023
- .select('id, environment, version_bump, schedule_type, scheduled_at')
1024
- .eq('project_id', project_id)
1025
- .eq('enabled', true)
1026
- .eq('auto_trigger', true)
1027
- .lte('scheduled_at', new Date().toISOString())
1028
- .order('scheduled_at', { ascending: true });
1029
- if (error)
1030
- throw new Error(`Failed to check schedules: ${error.message}`);
1031
- return {
1032
- result: {
1033
- due_schedules: dueSchedules || [],
1034
- count: dueSchedules?.length || 0,
1035
- ...(dueSchedules && dueSchedules.length > 0 && {
1036
- hint: `Call trigger_scheduled_deployment(schedule_id: "${dueSchedules[0].id}") to trigger the first due deployment`,
1037
- }),
1038
- },
1039
- };
319
+ const apiClient = getApiClient();
320
+ const response = await apiClient.checkDueDeployments(project_id);
321
+ if (!response.ok) {
322
+ throw new Error(response.error || 'Failed to check due deployments');
323
+ }
324
+ return { result: response.data };
1040
325
  };
1041
326
  /**
1042
327
  * Deployment handlers registry