@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
@@ -21,6 +21,7 @@ import {
21
21
  validateUUID,
22
22
  validateEnvironment,
23
23
  } from '../validators.js';
24
+ import { getApiClient } from '../api-client.js';
24
25
 
25
26
  export const requestDeployment: Handler = async (args, ctx) => {
26
27
  const { project_id, environment = 'production', version_bump = 'patch', notes, git_ref } = args as {
@@ -31,8 +32,7 @@ export const requestDeployment: Handler = async (args, ctx) => {
31
32
  git_ref?: string;
32
33
  };
33
34
 
34
- const { supabase, session } = ctx;
35
- const currentSessionId = session.currentSessionId;
35
+ const { session } = ctx;
36
36
 
37
37
  validateRequired(project_id, 'project_id');
38
38
  validateUUID(project_id, 'project_id');
@@ -46,220 +46,39 @@ export const requestDeployment: Handler = async (args, ctx) => {
46
46
  });
47
47
  }
48
48
 
49
- // Check for existing active deployment
50
- const { data: existingDeployment } = await supabase
51
- .from('deployments')
52
- .select('id, status')
53
- .eq('project_id', project_id)
54
- .not('status', 'in', '("deployed","failed")')
55
- .single();
56
-
57
- if (existingDeployment) {
58
- return {
59
- result: {
60
- success: false,
61
- error: 'A deployment is already in progress',
62
- existing_deployment_id: existingDeployment.id,
63
- existing_status: existingDeployment.status,
64
- hint: 'Wait for the current deployment to complete or cancel it first',
65
- },
66
- };
67
- }
68
-
69
- // Check for unvalidated completed tasks
70
- const { data: unvalidatedTasks } = await supabase
71
- .from('tasks')
72
- .select('id, title, completed_at, completed_by_session_id')
73
- .eq('project_id', project_id)
74
- .eq('status', 'completed')
75
- .is('validated_at', null)
76
- .order('completed_at', { ascending: true });
77
-
78
- if (unvalidatedTasks && unvalidatedTasks.length > 0) {
79
- return {
80
- result: {
81
- success: false,
82
- error: 'Cannot deploy: There are unvalidated completed tasks',
83
- unvalidated_tasks: unvalidatedTasks.map(t => ({
84
- id: t.id,
85
- title: t.title,
86
- completed_at: t.completed_at,
87
- })),
88
- unvalidated_count: unvalidatedTasks.length,
89
- hint: 'All completed tasks must be validated before deployment. Use validate_task to review each task.',
90
- action: `Call validate_task(task_id: "${unvalidatedTasks[0].id}", approved: true/false, validation_notes: "...")`,
91
- },
92
- };
93
- }
49
+ const apiClient = getApiClient();
50
+ const response = await apiClient.requestDeployment(project_id, {
51
+ environment,
52
+ version_bump,
53
+ notes,
54
+ git_ref
55
+ });
94
56
 
95
- // Get current version from project
96
- const { data: project } = await supabase
97
- .from('projects')
98
- .select('current_version')
99
- .eq('id', project_id)
100
- .single();
101
-
102
- const currentVersion = project?.current_version || '0.0.0';
103
-
104
- // Create new deployment
105
- const { data: deployment, error } = await supabase
106
- .from('deployments')
107
- .insert({
108
- project_id,
109
- environment,
110
- version_bump,
111
- notes,
112
- git_ref,
113
- requested_by: 'agent',
114
- requesting_agent_session_id: currentSessionId,
115
- })
116
- .select()
117
- .single();
118
-
119
- if (error) throw error;
120
-
121
- // Auto-convert pending deployment requirements to tasks
122
- const { data: pendingRequirements } = await supabase
123
- .from('deployment_requirements')
124
- .select('id, type, title, description, stage, blocking')
125
- .eq('project_id', project_id)
126
- .eq('status', 'pending')
127
- .is('converted_task_id', null);
128
-
129
- const convertedTasks: Array<{ task_id: string; requirement_id: string; title: string }> = [];
130
-
131
- if (pendingRequirements && pendingRequirements.length > 0) {
132
- for (const req of pendingRequirements) {
133
- const isDeployStage = req.stage === 'deployment';
134
- const isBlocking = req.blocking ?? isDeployStage;
135
- const titlePrefix = isBlocking
136
- ? 'DEPLOY:'
137
- : isDeployStage
138
- ? 'DEPLOY:'
139
- : req.stage === 'verification'
140
- ? 'VERIFY:'
141
- : 'PREP:';
142
-
143
- // Create linked task
144
- const { data: newTask } = await supabase
145
- .from('tasks')
146
- .insert({
147
- project_id,
148
- title: `${titlePrefix} ${req.title}`,
149
- description: `[${req.type}] ${req.description || req.title}`,
150
- priority: 1,
151
- status: 'pending',
152
- blocking: isBlocking,
153
- created_by: 'agent',
154
- created_by_session_id: currentSessionId,
155
- })
156
- .select('id')
157
- .single();
158
-
159
- if (newTask) {
160
- // Link task to requirement WITHOUT changing status
161
- // This keeps the requirement visible in the deployment steps list (permanent)
162
- await supabase
163
- .from('deployment_requirements')
164
- .update({
165
- converted_task_id: newTask.id,
166
- })
167
- .eq('id', req.id);
168
-
169
- convertedTasks.push({
170
- task_id: newTask.id,
171
- requirement_id: req.id,
172
- title: `${titlePrefix} ${req.title}`,
173
- });
174
- }
175
- }
57
+ if (!response.ok) {
58
+ throw new Error(response.error || 'Failed to request deployment');
176
59
  }
177
60
 
178
- // Log progress
179
- const convertedMsg = convertedTasks.length > 0
180
- ? ` (${convertedTasks.length} requirements converted to tasks)`
181
- : '';
182
- await supabase.from('progress_logs').insert({
183
- project_id,
184
- summary: `Deployment requested for ${environment} (${version_bump} bump from ${currentVersion})${convertedMsg}`,
185
- details: notes || undefined,
186
- created_by: 'agent',
187
- created_by_session_id: currentSessionId,
188
- });
189
-
190
- return {
191
- result: {
192
- success: true,
193
- deployment_id: deployment.id,
194
- status: deployment.status,
195
- environment: deployment.environment,
196
- version_bump,
197
- current_version: currentVersion,
198
- converted_requirements: convertedTasks.length,
199
- converted_tasks: convertedTasks.length > 0 ? convertedTasks : undefined,
200
- message: convertedTasks.length > 0
201
- ? `Deployment created. ${convertedTasks.length} requirements converted to tasks. Run build/tests then call claim_deployment_validation.`
202
- : 'Deployment created. Run build/tests then call claim_deployment_validation.',
203
- },
204
- };
61
+ return { result: response.data };
205
62
  };
206
63
 
207
64
  export const claimDeploymentValidation: Handler = async (args, ctx) => {
208
65
  const { project_id } = args as { project_id: string };
209
- const { supabase, session } = ctx;
210
- const currentSessionId = session.currentSessionId;
66
+ const { session } = ctx;
211
67
 
212
68
  validateRequired(project_id, 'project_id');
213
69
  validateUUID(project_id, 'project_id');
214
70
 
215
- // Find pending deployment
216
- const { data: deployment, error: fetchError } = await supabase
217
- .from('deployments')
218
- .select('*')
219
- .eq('project_id', project_id)
220
- .eq('status', 'pending')
221
- .single();
222
-
223
- if (fetchError || !deployment) {
224
- return {
225
- result: {
226
- success: false,
227
- error: 'No pending deployment found',
228
- hint: 'Use request_deployment to create a deployment first, or check_deployment_status to see current state',
229
- },
230
- };
231
- }
71
+ const apiClient = getApiClient();
72
+ const response = await apiClient.claimDeploymentValidation(
73
+ project_id,
74
+ session.currentSessionId || undefined
75
+ );
232
76
 
233
- // Claim validation
234
- const { data: updated, error: updateError } = await supabase
235
- .from('deployments')
236
- .update({
237
- status: 'validating',
238
- validation_agent_session_id: currentSessionId,
239
- validation_started_at: new Date().toISOString(),
240
- })
241
- .eq('id', deployment.id)
242
- .eq('status', 'pending')
243
- .select()
244
- .single();
245
-
246
- if (updateError || !updated) {
247
- return {
248
- result: {
249
- success: false,
250
- error: 'Failed to claim validation - deployment may have been claimed by another agent',
251
- },
252
- };
77
+ if (!response.ok) {
78
+ throw new Error(response.error || 'Failed to claim deployment validation');
253
79
  }
254
80
 
255
- return {
256
- result: {
257
- success: true,
258
- deployment_id: deployment.id,
259
- status: 'validating',
260
- message: 'Validation claimed. Run build and tests, then call report_validation with results.',
261
- },
262
- };
81
+ return { result: response.data };
263
82
  };
264
83
 
265
84
  export const reportValidation: Handler = async (args, ctx) => {
@@ -270,8 +89,7 @@ export const reportValidation: Handler = async (args, ctx) => {
270
89
  error_message?: string;
271
90
  };
272
91
 
273
- const { supabase, session } = ctx;
274
- const currentSessionId = session.currentSessionId;
92
+ const { session } = ctx;
275
93
 
276
94
  validateRequired(project_id, 'project_id');
277
95
  validateUUID(project_id, 'project_id');
@@ -282,232 +100,54 @@ export const reportValidation: Handler = async (args, ctx) => {
282
100
  });
283
101
  }
284
102
 
285
- // Find validating deployment
286
- const { data: deployment, error: fetchError } = await supabase
287
- .from('deployments')
288
- .select('id')
289
- .eq('project_id', project_id)
290
- .eq('status', 'validating')
291
- .single();
292
-
293
- if (fetchError || !deployment) {
294
- return {
295
- result: {
296
- success: false,
297
- error: 'No deployment being validated. Use claim_deployment_validation first.',
298
- },
299
- };
300
- }
301
-
302
- const validationPassed = build_passed && (tests_passed !== false);
303
- const newStatus = validationPassed ? 'ready' : 'failed';
304
-
305
- const { error: updateError } = await supabase
306
- .from('deployments')
307
- .update({
308
- status: newStatus,
309
- build_passed,
310
- tests_passed: tests_passed ?? null,
311
- validation_completed_at: new Date().toISOString(),
312
- validation_error: error_message || null,
313
- })
314
- .eq('id', deployment.id);
315
-
316
- if (updateError) throw updateError;
317
-
318
- // Log result
319
- await supabase.from('progress_logs').insert({
320
- project_id,
321
- summary: validationPassed
322
- ? `Deployment validation passed - ready to deploy`
323
- : `Deployment validation failed: ${error_message || 'build/tests failed'}`,
324
- details: `Build: ${build_passed ? 'passed' : 'failed'}, Tests: ${tests_passed === undefined ? 'skipped' : tests_passed ? 'passed' : 'failed'}`,
325
- created_by: 'agent',
326
- created_by_session_id: currentSessionId,
103
+ const apiClient = getApiClient();
104
+ const response = await apiClient.reportValidation(project_id, {
105
+ build_passed,
106
+ tests_passed: tests_passed ?? true,
107
+ error_message
327
108
  });
328
109
 
329
- // Auto-create task for failed validation
330
- let createdTaskId: string | null = null;
331
- if (!validationPassed) {
332
- const failureType = !build_passed ? 'build' : 'test';
333
- const { data: newTask } = await supabase
334
- .from('tasks')
335
- .insert({
336
- project_id,
337
- title: `Fix ${failureType} failure`,
338
- description: error_message || `${failureType} failed during deployment validation`,
339
- priority: 1,
340
- status: 'pending',
341
- created_by: 'agent',
342
- created_by_session_id: currentSessionId,
343
- estimated_minutes: 30,
344
- })
345
- .select('id')
346
- .single();
347
-
348
- createdTaskId = newTask?.id || null;
110
+ if (!response.ok) {
111
+ throw new Error(response.error || 'Failed to report validation');
349
112
  }
350
113
 
351
- return {
352
- result: {
353
- success: true,
354
- status: newStatus,
355
- passed: validationPassed,
356
- ...(createdTaskId && { fix_task_id: createdTaskId }),
357
- },
358
- };
114
+ return { result: response.data };
359
115
  };
360
116
 
361
117
  export const checkDeploymentStatus: Handler = async (args, ctx) => {
362
118
  const { project_id } = args as { project_id: string };
363
- const { supabase } = ctx;
364
119
 
365
120
  validateRequired(project_id, 'project_id');
366
121
  validateUUID(project_id, 'project_id');
367
122
 
368
- // Get most recent deployment
369
- const { data: deployment, error } = await supabase
370
- .from('deployments')
371
- .select('*')
372
- .eq('project_id', project_id)
373
- .order('created_at', { ascending: false })
374
- .limit(1)
375
- .single();
376
-
377
- if (error || !deployment) {
378
- return {
379
- result: {
380
- has_deployment: false,
381
- message: 'No deployments found for this project',
382
- },
383
- };
384
- }
385
-
386
- // Auto-timeout stale deployments
387
- const DEPLOYMENT_TIMEOUT_MS: Record<string, number> = {
388
- pending: 30 * 60 * 1000,
389
- validating: 15 * 60 * 1000,
390
- ready: 30 * 60 * 1000,
391
- deploying: 10 * 60 * 1000,
392
- };
123
+ const apiClient = getApiClient();
124
+ const response = await apiClient.checkDeploymentStatus(project_id);
393
125
 
394
- if (!['deployed', 'failed'].includes(deployment.status)) {
395
- const timeout = DEPLOYMENT_TIMEOUT_MS[deployment.status];
396
- if (timeout) {
397
- const startTime =
398
- deployment.status === 'deploying'
399
- ? deployment.deployment_started_at
400
- : deployment.status === 'validating'
401
- ? deployment.validation_started_at
402
- : deployment.created_at;
403
-
404
- if (startTime && Date.now() - new Date(startTime).getTime() > timeout) {
405
- const timeoutError = `Timed out: deployment was stuck in '${deployment.status}' state for too long`;
406
- await supabase
407
- .from('deployments')
408
- .update({
409
- status: 'failed',
410
- deployment_error: timeoutError,
411
- deployment_completed_at: new Date().toISOString(),
412
- })
413
- .eq('id', deployment.id);
414
-
415
- deployment.status = 'failed';
416
- deployment.deployment_error = timeoutError;
417
- }
418
- }
126
+ if (!response.ok) {
127
+ throw new Error(response.error || 'Failed to check deployment status');
419
128
  }
420
129
 
421
- return {
422
- result: {
423
- has_deployment: true,
424
- deployment: {
425
- id: deployment.id,
426
- status: deployment.status,
427
- environment: deployment.environment,
428
- requested_by: deployment.requested_by,
429
- build_passed: deployment.build_passed,
430
- tests_passed: deployment.tests_passed,
431
- validation_error: deployment.validation_error,
432
- deployment_error: deployment.deployment_error,
433
- deployment_summary: deployment.deployment_summary,
434
- notes: deployment.notes,
435
- git_ref: deployment.git_ref,
436
- created_at: deployment.created_at,
437
- validation_started_at: deployment.validation_started_at,
438
- validation_completed_at: deployment.validation_completed_at,
439
- deployment_started_at: deployment.deployment_started_at,
440
- deployment_completed_at: deployment.deployment_completed_at,
441
- },
442
- },
443
- };
130
+ return { result: response.data };
444
131
  };
445
132
 
446
133
  export const startDeployment: Handler = async (args, ctx) => {
447
134
  const { project_id } = args as { project_id: string };
448
- const { supabase, session } = ctx;
449
- const currentSessionId = session.currentSessionId;
135
+ const { session } = ctx;
450
136
 
451
137
  validateRequired(project_id, 'project_id');
452
138
  validateUUID(project_id, 'project_id');
453
139
 
454
- // Find ready deployment and project deployment instructions
455
- const [deploymentResult, projectResult] = await Promise.all([
456
- supabase
457
- .from('deployments')
458
- .select('id, environment')
459
- .eq('project_id', project_id)
460
- .eq('status', 'ready')
461
- .single(),
462
- supabase
463
- .from('projects')
464
- .select('deployment_instructions, git_main_branch')
465
- .eq('id', project_id)
466
- .single(),
467
- ]);
468
-
469
- if (deploymentResult.error || !deploymentResult.data) {
470
- return {
471
- result: {
472
- success: false,
473
- error: 'No deployment ready. Must pass validation first.',
474
- },
475
- };
476
- }
477
-
478
- const deployment = deploymentResult.data;
479
- const project = projectResult.data;
480
-
481
- const { error: updateError } = await supabase
482
- .from('deployments')
483
- .update({
484
- status: 'deploying',
485
- deployment_started_at: new Date().toISOString(),
486
- })
487
- .eq('id', deployment.id);
488
-
489
- if (updateError) throw updateError;
490
-
491
- await supabase.from('progress_logs').insert({
140
+ const apiClient = getApiClient();
141
+ const response = await apiClient.startDeployment(
492
142
  project_id,
493
- summary: `Deployment to ${deployment.environment} started`,
494
- created_by: 'agent',
495
- created_by_session_id: currentSessionId,
496
- });
497
-
498
- const result: Record<string, unknown> = {
499
- success: true,
500
- status: 'deploying',
501
- env: deployment.environment,
502
- };
143
+ session.currentSessionId || undefined
144
+ );
503
145
 
504
- if (project?.deployment_instructions) {
505
- result.instructions = project.deployment_instructions;
506
- } else {
507
- 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`;
146
+ if (!response.ok) {
147
+ throw new Error(response.error || 'Failed to start deployment');
508
148
  }
509
149
 
510
- return { result };
150
+ return { result: response.data };
511
151
  };
512
152
 
513
153
  export const completeDeployment: Handler = async (args, ctx) => {
@@ -517,8 +157,7 @@ export const completeDeployment: Handler = async (args, ctx) => {
517
157
  summary?: string;
518
158
  };
519
159
 
520
- const { supabase, session } = ctx;
521
- const currentSessionId = session.currentSessionId;
160
+ const { session } = ctx;
522
161
 
523
162
  validateRequired(project_id, 'project_id');
524
163
  validateUUID(project_id, 'project_id');
@@ -529,126 +168,37 @@ export const completeDeployment: Handler = async (args, ctx) => {
529
168
  });
530
169
  }
531
170
 
532
- // Find deploying deployment
533
- const { data: deployment, error: fetchError } = await supabase
534
- .from('deployments')
535
- .select('id, environment, version_bump')
536
- .eq('project_id', project_id)
537
- .eq('status', 'deploying')
538
- .single();
539
-
540
- if (fetchError || !deployment) {
541
- return {
542
- result: {
543
- success: false,
544
- error: 'No deployment in progress. Use start_deployment first.',
545
- },
546
- };
547
- }
548
-
549
- const newStatus = success ? 'deployed' : 'failed';
550
- let newVersion: string | null = null;
551
-
552
- // If successful, calculate and store new version
553
- if (success) {
554
- const { data: project } = await supabase
555
- .from('projects')
556
- .select('current_version')
557
- .eq('id', project_id)
558
- .single();
559
-
560
- const currentVersion = project?.current_version || '0.0.0';
561
- const versionBump = deployment.version_bump || 'patch';
562
- const parts = currentVersion.split('.').map((p: string) => parseInt(p, 10) || 0);
563
- let [major, minor, patch] = [parts[0] || 0, parts[1] || 0, parts[2] || 0];
564
-
565
- switch (versionBump) {
566
- case 'major': major += 1; minor = 0; patch = 0; break;
567
- case 'minor': minor += 1; patch = 0; break;
568
- default: patch += 1;
569
- }
570
-
571
- newVersion = `${major}.${minor}.${patch}`;
171
+ const apiClient = getApiClient();
172
+ const response = await apiClient.completeDeployment(project_id, {
173
+ success,
174
+ summary
175
+ });
572
176
 
573
- await supabase
574
- .from('projects')
575
- .update({ current_version: newVersion })
576
- .eq('id', project_id);
177
+ if (!response.ok) {
178
+ throw new Error(response.error || 'Failed to complete deployment');
577
179
  }
578
180
 
579
- const { error: updateError } = await supabase
580
- .from('deployments')
581
- .update({
582
- status: newStatus,
583
- version: newVersion,
584
- deployment_completed_at: new Date().toISOString(),
585
- deployment_summary: summary || null,
586
- })
587
- .eq('id', deployment.id);
588
-
589
- if (updateError) throw updateError;
590
-
591
- await supabase.from('progress_logs').insert({
592
- project_id,
593
- summary: success
594
- ? `Deployed to ${deployment.environment}${newVersion ? ` v${newVersion}` : ''}`
595
- : `Deployment failed`,
596
- details: summary || undefined,
597
- created_by: 'agent',
598
- created_by_session_id: currentSessionId,
599
- });
600
-
601
- return {
602
- result: {
603
- success: true,
604
- status: newStatus,
605
- ...(newVersion && { version: newVersion }),
606
- },
607
- };
181
+ return { result: response.data };
608
182
  };
609
183
 
610
184
  export const cancelDeployment: Handler = async (args, ctx) => {
611
185
  const { project_id, reason } = args as { project_id: string; reason?: string };
612
- const { supabase, session } = ctx;
613
- const currentSessionId = session.currentSessionId;
614
186
 
615
187
  validateRequired(project_id, 'project_id');
616
188
  validateUUID(project_id, 'project_id');
617
189
 
618
- const { data: deployment, error: fetchError } = await supabase
619
- .from('deployments')
620
- .select('id')
621
- .eq('project_id', project_id)
622
- .not('status', 'in', '("deployed","failed")')
623
- .single();
190
+ const apiClient = getApiClient();
191
+ const response = await apiClient.cancelDeployment(project_id, reason);
624
192
 
625
- if (fetchError || !deployment) {
626
- return { result: { success: false, error: 'No active deployment' } };
193
+ if (!response.ok) {
194
+ throw new Error(response.error || 'Failed to cancel deployment');
627
195
  }
628
196
 
629
- const { error: updateError } = await supabase
630
- .from('deployments')
631
- .update({
632
- status: 'failed',
633
- deployment_error: `Cancelled: ${reason || 'unspecified'}`,
634
- deployment_completed_at: new Date().toISOString(),
635
- })
636
- .eq('id', deployment.id);
637
-
638
- if (updateError) throw updateError;
639
-
640
- await supabase.from('progress_logs').insert({
641
- project_id,
642
- summary: `Deployment cancelled${reason ? `: ${reason}` : ''}`,
643
- created_by: 'agent',
644
- created_by_session_id: currentSessionId,
645
- });
646
-
647
- return { result: { success: true } };
197
+ return { result: response.data };
648
198
  };
649
199
 
650
200
  export const addDeploymentRequirement: Handler = async (args, ctx) => {
651
- const { project_id, type, title, description, file_path, stage = 'preparation', blocking = false } = args as {
201
+ const { project_id, type, title, description, file_path, stage = 'preparation', blocking = false, recurring = false } = args as {
652
202
  project_id: string;
653
203
  type: string;
654
204
  title: string;
@@ -656,11 +206,9 @@ export const addDeploymentRequirement: Handler = async (args, ctx) => {
656
206
  file_path?: string;
657
207
  stage?: string;
658
208
  blocking?: boolean;
209
+ recurring?: boolean;
659
210
  };
660
211
 
661
- const { supabase, session } = ctx;
662
- const currentSessionId = session.currentSessionId;
663
-
664
212
  validateRequired(project_id, 'project_id');
665
213
  validateUUID(project_id, 'project_id');
666
214
  validateRequired(type, 'type');
@@ -676,95 +224,38 @@ export const addDeploymentRequirement: Handler = async (args, ctx) => {
676
224
  throw new ValidationError(`stage must be one of: ${validStages.join(', ')}`);
677
225
  }
678
226
 
679
- const { data: requirement, error } = await supabase
680
- .from('deployment_requirements')
681
- .insert({
682
- project_id,
683
- type,
684
- title,
685
- description: description || null,
686
- file_path: file_path || null,
687
- stage,
688
- blocking,
689
- created_by_session_id: currentSessionId,
690
- })
691
- .select('id, type, title, stage, blocking')
692
- .single();
693
-
694
- if (error) throw new Error(`Failed to add requirement: ${error.message}`);
695
-
696
- const blockingText = blocking ? ' (BLOCKING)' : '';
697
- await supabase.from('progress_logs').insert({
698
- project_id,
699
- summary: `Added ${stage} deployment requirement${blockingText}: ${title}`,
700
- details: `Type: ${type}, Stage: ${stage}${blocking ? ', Blocking: true' : ''}${file_path ? `, File: ${file_path}` : ''}`,
701
- created_by: 'agent',
702
- created_by_session_id: currentSessionId,
227
+ const apiClient = getApiClient();
228
+ const response = await apiClient.addDeploymentRequirement(project_id, {
229
+ type: type as 'migration' | 'env_var' | 'config' | 'manual' | 'breaking_change' | 'agent_task',
230
+ title,
231
+ description,
232
+ file_path,
233
+ stage: stage as 'preparation' | 'deployment' | 'verification',
234
+ blocking,
235
+ recurring
703
236
  });
704
237
 
705
- const stageMessage = blocking
706
- ? 'Will block all other work when converted to task.'
707
- : stage === 'deployment'
708
- ? 'Will run during deployment.'
709
- : stage === 'verification'
710
- ? 'Will run after deployment for verification.'
711
- : 'Will run during preparation phase.';
712
-
713
- return {
714
- result: {
715
- success: true,
716
- requirement_id: requirement.id,
717
- stage: requirement.stage,
718
- message: `Added ${type} requirement. ${stageMessage}`,
719
- },
720
- };
238
+ if (!response.ok) {
239
+ throw new Error(response.error || 'Failed to add deployment requirement');
240
+ }
241
+
242
+ return { result: response.data };
721
243
  };
722
244
 
723
245
  export const completeDeploymentRequirement: Handler = async (args, ctx) => {
724
246
  const { requirement_id } = args as { requirement_id: string };
725
- const { supabase, session } = ctx;
726
- const currentSessionId = session.currentSessionId;
727
247
 
728
248
  validateRequired(requirement_id, 'requirement_id');
729
249
  validateUUID(requirement_id, 'requirement_id');
730
250
 
731
- const { data: requirement, error: fetchError } = await supabase
732
- .from('deployment_requirements')
733
- .select('id, title, status')
734
- .eq('id', requirement_id)
735
- .single();
251
+ const apiClient = getApiClient();
252
+ const response = await apiClient.completeDeploymentRequirement(requirement_id);
736
253
 
737
- if (fetchError || !requirement) {
738
- throw new Error('Requirement not found');
254
+ if (!response.ok) {
255
+ throw new Error(response.error || 'Failed to complete deployment requirement');
739
256
  }
740
257
 
741
- if (requirement.status !== 'pending') {
742
- return {
743
- result: {
744
- success: false,
745
- error: `Requirement is already ${requirement.status}`,
746
- },
747
- };
748
- }
749
-
750
- const { error: updateError } = await supabase
751
- .from('deployment_requirements')
752
- .update({
753
- status: 'completed',
754
- completed_at: new Date().toISOString(),
755
- completed_by: currentSessionId || 'agent',
756
- })
757
- .eq('id', requirement_id);
758
-
759
- if (updateError) throw updateError;
760
-
761
- return {
762
- result: {
763
- success: true,
764
- requirement_id,
765
- title: requirement.title,
766
- },
767
- };
258
+ return { result: response.data };
768
259
  };
769
260
 
770
261
  export const getDeploymentRequirements: Handler = async (args, ctx) => {
@@ -774,41 +265,20 @@ export const getDeploymentRequirements: Handler = async (args, ctx) => {
774
265
  stage?: string;
775
266
  };
776
267
 
777
- const { supabase } = ctx;
778
-
779
268
  validateRequired(project_id, 'project_id');
780
269
  validateUUID(project_id, 'project_id');
781
270
 
782
- let query = supabase
783
- .from('deployment_requirements')
784
- .select('id, type, title, description, file_path, status, stage, blocking, created_at, completed_at')
785
- .eq('project_id', project_id)
786
- .order('stage', { ascending: true })
787
- .order('created_at', { ascending: false });
788
-
789
- if (status !== 'all') {
790
- query = query.eq('status', status);
791
- }
271
+ const apiClient = getApiClient();
272
+ const response = await apiClient.getDeploymentRequirements(project_id, {
273
+ status: status as 'pending' | 'completed' | 'converted_to_task' | 'all',
274
+ stage: stage as 'preparation' | 'deployment' | 'verification' | 'all' | undefined
275
+ });
792
276
 
793
- if (stage && stage !== 'all') {
794
- query = query.eq('stage', stage);
277
+ if (!response.ok) {
278
+ throw new Error(response.error || 'Failed to get deployment requirements');
795
279
  }
796
280
 
797
- const { data: requirements, error } = await query;
798
-
799
- if (error) throw new Error(`Failed to fetch requirements: ${error.message}`);
800
-
801
- const preparationPending = requirements?.filter(r => r.status === 'pending' && r.stage === 'preparation').length || 0;
802
- const deploymentPending = requirements?.filter(r => r.status === 'pending' && r.stage === 'deployment').length || 0;
803
-
804
- return {
805
- result: {
806
- requirements: requirements || [],
807
- preparation_pending: preparationPending,
808
- deployment_pending: deploymentPending,
809
- deployment_blocked: preparationPending > 0 || deploymentPending > 0,
810
- },
811
- };
281
+ return { result: response.data };
812
282
  };
813
283
 
814
284
  // ============================================================================
@@ -836,9 +306,6 @@ export const scheduleDeployment: Handler = async (args, ctx) => {
836
306
  git_ref?: string;
837
307
  };
838
308
 
839
- const { supabase, session } = ctx;
840
- const currentSessionId = session.currentSessionId;
841
-
842
309
  validateRequired(project_id, 'project_id');
843
310
  validateUUID(project_id, 'project_id');
844
311
  validateRequired(scheduled_at, 'scheduled_at');
@@ -873,47 +340,22 @@ export const scheduleDeployment: Handler = async (args, ctx) => {
873
340
  });
874
341
  }
875
342
 
876
- // Create scheduled deployment
877
- const { data: schedule, error } = await supabase
878
- .from('scheduled_deployments')
879
- .insert({
880
- project_id,
881
- environment,
882
- version_bump,
883
- schedule_type,
884
- scheduled_at: scheduledDate.toISOString(),
885
- auto_trigger,
886
- notes: notes || null,
887
- git_ref: git_ref || null,
888
- created_by: 'agent',
889
- created_by_session_id: currentSessionId,
890
- })
891
- .select('id, scheduled_at, schedule_type')
892
- .single();
893
-
894
- if (error) throw new Error(`Failed to create schedule: ${error.message}`);
895
-
896
- // Log progress
897
- await supabase.from('progress_logs').insert({
898
- project_id,
899
- summary: `Scheduled ${schedule_type} deployment to ${environment} for ${scheduledDate.toISOString()}`,
900
- details: `Auto-trigger: ${auto_trigger}, Version bump: ${version_bump}`,
901
- created_by: 'agent',
902
- created_by_session_id: currentSessionId,
343
+ const apiClient = getApiClient();
344
+ const response = await apiClient.scheduleDeployment(project_id, {
345
+ environment: environment as 'development' | 'staging' | 'production',
346
+ version_bump: version_bump as 'patch' | 'minor' | 'major',
347
+ schedule_type: schedule_type as 'once' | 'daily' | 'weekly' | 'monthly',
348
+ scheduled_at: scheduledDate.toISOString(),
349
+ auto_trigger,
350
+ notes,
351
+ git_ref
903
352
  });
904
353
 
905
- return {
906
- result: {
907
- success: true,
908
- schedule_id: schedule.id,
909
- scheduled_at: schedule.scheduled_at,
910
- schedule_type: schedule.schedule_type,
911
- auto_trigger,
912
- message: auto_trigger
913
- ? 'Deployment scheduled. Will trigger automatically when time arrives.'
914
- : 'Deployment scheduled. Manual trigger required from dashboard.',
915
- },
916
- };
354
+ if (!response.ok) {
355
+ throw new Error(response.error || 'Failed to schedule deployment');
356
+ }
357
+
358
+ return { result: response.data };
917
359
  };
918
360
 
919
361
  export const getScheduledDeployments: Handler = async (args, ctx) => {
@@ -922,43 +364,17 @@ export const getScheduledDeployments: Handler = async (args, ctx) => {
922
364
  include_disabled?: boolean;
923
365
  };
924
366
 
925
- const { supabase } = ctx;
926
-
927
367
  validateRequired(project_id, 'project_id');
928
368
  validateUUID(project_id, 'project_id');
929
369
 
930
- let query = supabase
931
- .from('scheduled_deployments')
932
- .select('*')
933
- .eq('project_id', project_id)
934
- .order('scheduled_at', { ascending: true });
370
+ const apiClient = getApiClient();
371
+ const response = await apiClient.getScheduledDeployments(project_id, include_disabled);
935
372
 
936
- if (!include_disabled) {
937
- query = query.eq('enabled', true);
373
+ if (!response.ok) {
374
+ throw new Error(response.error || 'Failed to get scheduled deployments');
938
375
  }
939
376
 
940
- const { data: schedules, error } = await query;
941
-
942
- if (error) throw new Error(`Failed to fetch schedules: ${error.message}`);
943
-
944
- const now = new Date();
945
- const schedulesWithStatus = (schedules || []).map(s => ({
946
- ...s,
947
- is_due: s.enabled && new Date(s.scheduled_at) <= now,
948
- }));
949
-
950
- const dueCount = schedulesWithStatus.filter(s => s.is_due && s.auto_trigger).length;
951
-
952
- return {
953
- result: {
954
- schedules: schedulesWithStatus,
955
- count: schedulesWithStatus.length,
956
- due_count: dueCount,
957
- ...(dueCount > 0 && {
958
- hint: 'There are due schedules. Call trigger_scheduled_deployment to execute.',
959
- }),
960
- },
961
- };
377
+ return { result: response.data };
962
378
  };
963
379
 
964
380
  export const updateScheduledDeployment: Handler = async (args, ctx) => {
@@ -984,8 +400,6 @@ export const updateScheduledDeployment: Handler = async (args, ctx) => {
984
400
  git_ref?: string;
985
401
  };
986
402
 
987
- const { supabase } = ctx;
988
-
989
403
  validateRequired(schedule_id, 'schedule_id');
990
404
  validateUUID(schedule_id, 'schedule_id');
991
405
 
@@ -1027,242 +441,75 @@ export const updateScheduledDeployment: Handler = async (args, ctx) => {
1027
441
  return { result: { success: false, error: 'No updates provided' } };
1028
442
  }
1029
443
 
1030
- const { error } = await supabase
1031
- .from('scheduled_deployments')
1032
- .update(updates)
1033
- .eq('id', schedule_id);
444
+ const apiClient = getApiClient();
445
+ const response = await apiClient.updateScheduledDeployment(schedule_id, updates as {
446
+ environment?: 'development' | 'staging' | 'production';
447
+ version_bump?: 'patch' | 'minor' | 'major';
448
+ schedule_type?: 'once' | 'daily' | 'weekly' | 'monthly';
449
+ scheduled_at?: string;
450
+ auto_trigger?: boolean;
451
+ enabled?: boolean;
452
+ notes?: string;
453
+ git_ref?: string;
454
+ });
1034
455
 
1035
- if (error) throw new Error(`Failed to update schedule: ${error.message}`);
456
+ if (!response.ok) {
457
+ throw new Error(response.error || 'Failed to update scheduled deployment');
458
+ }
1036
459
 
1037
- return { result: { success: true, schedule_id } };
460
+ return { result: response.data };
1038
461
  };
1039
462
 
1040
463
  export const deleteScheduledDeployment: Handler = async (args, ctx) => {
1041
464
  const { schedule_id } = args as { schedule_id: string };
1042
- const { supabase } = ctx;
1043
465
 
1044
466
  validateRequired(schedule_id, 'schedule_id');
1045
467
  validateUUID(schedule_id, 'schedule_id');
1046
468
 
1047
- const { error } = await supabase
1048
- .from('scheduled_deployments')
1049
- .delete()
1050
- .eq('id', schedule_id);
469
+ const apiClient = getApiClient();
470
+ const response = await apiClient.deleteScheduledDeployment(schedule_id);
1051
471
 
1052
- if (error) throw new Error(`Failed to delete schedule: ${error.message}`);
472
+ if (!response.ok) {
473
+ throw new Error(response.error || 'Failed to delete scheduled deployment');
474
+ }
1053
475
 
1054
- return { result: { success: true } };
476
+ return { result: response.data };
1055
477
  };
1056
478
 
1057
479
  export const triggerScheduledDeployment: Handler = async (args, ctx) => {
1058
480
  const { schedule_id } = args as { schedule_id: string };
1059
- const { supabase, session } = ctx;
1060
- const currentSessionId = session.currentSessionId;
481
+ const { session } = ctx;
1061
482
 
1062
483
  validateRequired(schedule_id, 'schedule_id');
1063
484
  validateUUID(schedule_id, 'schedule_id');
1064
485
 
1065
- // Get the schedule
1066
- const { data: schedule, error: fetchError } = await supabase
1067
- .from('scheduled_deployments')
1068
- .select('*')
1069
- .eq('id', schedule_id)
1070
- .single();
1071
-
1072
- if (fetchError || !schedule) {
1073
- return { result: { success: false, error: 'Schedule not found' } };
1074
- }
1075
-
1076
- if (!schedule.enabled) {
1077
- return { result: { success: false, error: 'Schedule is disabled' } };
1078
- }
1079
-
1080
- // Check for existing active deployment
1081
- const { data: existingDeployment } = await supabase
1082
- .from('deployments')
1083
- .select('id, status')
1084
- .eq('project_id', schedule.project_id)
1085
- .not('status', 'in', '("deployed","failed")')
1086
- .single();
1087
-
1088
- if (existingDeployment) {
1089
- return {
1090
- result: {
1091
- success: false,
1092
- error: 'A deployment is already in progress',
1093
- existing_deployment_id: existingDeployment.id,
1094
- hint: 'Wait for current deployment to complete or cancel it first',
1095
- },
1096
- };
1097
- }
1098
-
1099
- // Create the deployment (similar to request_deployment)
1100
- const { data: deployment, error: deployError } = await supabase
1101
- .from('deployments')
1102
- .insert({
1103
- project_id: schedule.project_id,
1104
- environment: schedule.environment,
1105
- version_bump: schedule.version_bump,
1106
- notes: schedule.notes,
1107
- git_ref: schedule.git_ref,
1108
- requested_by: 'agent',
1109
- requesting_agent_session_id: currentSessionId,
1110
- })
1111
- .select('id, status')
1112
- .single();
1113
-
1114
- if (deployError) throw new Error(`Failed to create deployment: ${deployError.message}`);
1115
-
1116
- // Auto-convert pending deployment requirements to tasks
1117
- const { data: pendingRequirements } = await supabase
1118
- .from('deployment_requirements')
1119
- .select('id, type, title, description, stage, blocking')
1120
- .eq('project_id', schedule.project_id)
1121
- .eq('status', 'pending')
1122
- .is('converted_task_id', null);
1123
-
1124
- const convertedTasks: Array<{ task_id: string; requirement_id: string; title: string }> = [];
1125
-
1126
- if (pendingRequirements && pendingRequirements.length > 0) {
1127
- for (const req of pendingRequirements) {
1128
- const isDeployStage = req.stage === 'deployment';
1129
- const isBlocking = req.blocking ?? isDeployStage;
1130
- const titlePrefix = isBlocking
1131
- ? 'DEPLOY:'
1132
- : isDeployStage
1133
- ? 'DEPLOY:'
1134
- : req.stage === 'verification'
1135
- ? 'VERIFY:'
1136
- : 'PREP:';
1137
-
1138
- // Create linked task
1139
- const { data: newTask } = await supabase
1140
- .from('tasks')
1141
- .insert({
1142
- project_id: schedule.project_id,
1143
- title: `${titlePrefix} ${req.title}`,
1144
- description: `[${req.type}] ${req.description || req.title}`,
1145
- priority: 1,
1146
- status: 'pending',
1147
- blocking: isBlocking,
1148
- created_by: 'agent',
1149
- created_by_session_id: currentSessionId,
1150
- })
1151
- .select('id')
1152
- .single();
1153
-
1154
- if (newTask) {
1155
- // Link task to requirement WITHOUT changing status
1156
- // This keeps the requirement visible in the deployment steps list (permanent)
1157
- await supabase
1158
- .from('deployment_requirements')
1159
- .update({
1160
- converted_task_id: newTask.id,
1161
- })
1162
- .eq('id', req.id);
1163
-
1164
- convertedTasks.push({
1165
- task_id: newTask.id,
1166
- requirement_id: req.id,
1167
- title: `${titlePrefix} ${req.title}`,
1168
- });
1169
- }
1170
- }
1171
- }
1172
-
1173
- // Update the schedule
1174
- const scheduleUpdates: Record<string, unknown> = {
1175
- last_triggered_at: new Date().toISOString(),
1176
- last_deployment_id: deployment.id,
1177
- trigger_count: schedule.trigger_count + 1,
1178
- };
1179
-
1180
- // For recurring schedules, calculate next run time
1181
- if (schedule.schedule_type !== 'once') {
1182
- const currentScheduledAt = new Date(schedule.scheduled_at);
1183
- let nextScheduledAt: Date;
1184
-
1185
- switch (schedule.schedule_type) {
1186
- case 'daily':
1187
- nextScheduledAt = new Date(currentScheduledAt.getTime() + 24 * 60 * 60 * 1000);
1188
- break;
1189
- case 'weekly':
1190
- nextScheduledAt = new Date(currentScheduledAt.getTime() + 7 * 24 * 60 * 60 * 1000);
1191
- break;
1192
- case 'monthly':
1193
- nextScheduledAt = new Date(currentScheduledAt.getTime() + 30 * 24 * 60 * 60 * 1000);
1194
- break;
1195
- default:
1196
- nextScheduledAt = currentScheduledAt;
1197
- }
486
+ const apiClient = getApiClient();
487
+ const response = await apiClient.triggerScheduledDeployment(
488
+ schedule_id,
489
+ session.currentSessionId || undefined
490
+ );
1198
491
 
1199
- scheduleUpdates.scheduled_at = nextScheduledAt.toISOString();
1200
- } else {
1201
- // One-time schedule, disable it
1202
- scheduleUpdates.enabled = false;
492
+ if (!response.ok) {
493
+ throw new Error(response.error || 'Failed to trigger scheduled deployment');
1203
494
  }
1204
495
 
1205
- await supabase
1206
- .from('scheduled_deployments')
1207
- .update(scheduleUpdates)
1208
- .eq('id', schedule_id);
1209
-
1210
- // Log progress
1211
- const convertedMsg = convertedTasks.length > 0
1212
- ? `, ${convertedTasks.length} requirements converted to tasks`
1213
- : '';
1214
- await supabase.from('progress_logs').insert({
1215
- project_id: schedule.project_id,
1216
- summary: `Triggered scheduled deployment to ${schedule.environment}${convertedMsg}`,
1217
- details: `Schedule: ${schedule.schedule_type}, Trigger #${schedule.trigger_count + 1}`,
1218
- created_by: 'agent',
1219
- created_by_session_id: currentSessionId,
1220
- });
1221
-
1222
- return {
1223
- result: {
1224
- success: true,
1225
- deployment_id: deployment.id,
1226
- schedule_id,
1227
- schedule_type: schedule.schedule_type,
1228
- next_scheduled_at: schedule.schedule_type !== 'once' ? scheduleUpdates.scheduled_at : null,
1229
- converted_requirements: convertedTasks.length,
1230
- converted_tasks: convertedTasks.length > 0 ? convertedTasks : undefined,
1231
- message: convertedTasks.length > 0
1232
- ? `Deployment created from schedule. ${convertedTasks.length} requirements converted to tasks. Run validation then deploy.`
1233
- : 'Deployment created from schedule. Run validation then deploy.',
1234
- },
1235
- };
496
+ return { result: response.data };
1236
497
  };
1237
498
 
1238
499
  export const checkDueDeployments: Handler = async (args, ctx) => {
1239
500
  const { project_id } = args as { project_id: string };
1240
- const { supabase } = ctx;
1241
501
 
1242
502
  validateRequired(project_id, 'project_id');
1243
503
  validateUUID(project_id, 'project_id');
1244
504
 
1245
- // Find schedules that are due (enabled, auto_trigger, and scheduled_at <= now)
1246
- const { data: dueSchedules, error } = await supabase
1247
- .from('scheduled_deployments')
1248
- .select('id, environment, version_bump, schedule_type, scheduled_at')
1249
- .eq('project_id', project_id)
1250
- .eq('enabled', true)
1251
- .eq('auto_trigger', true)
1252
- .lte('scheduled_at', new Date().toISOString())
1253
- .order('scheduled_at', { ascending: true });
1254
-
1255
- if (error) throw new Error(`Failed to check schedules: ${error.message}`);
1256
-
1257
- return {
1258
- result: {
1259
- due_schedules: dueSchedules || [],
1260
- count: dueSchedules?.length || 0,
1261
- ...(dueSchedules && dueSchedules.length > 0 && {
1262
- hint: `Call trigger_scheduled_deployment(schedule_id: "${dueSchedules[0].id}") to trigger the first due deployment`,
1263
- }),
1264
- },
1265
- };
505
+ const apiClient = getApiClient();
506
+ const response = await apiClient.checkDueDeployments(project_id);
507
+
508
+ if (!response.ok) {
509
+ throw new Error(response.error || 'Failed to check due deployments');
510
+ }
511
+
512
+ return { result: response.data };
1266
513
  };
1267
514
 
1268
515
  /**