@vibescope/mcp-server 0.2.1 → 0.2.2
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.
- package/README.md +60 -7
- package/dist/api-client.d.ts +187 -0
- package/dist/api-client.js +48 -0
- package/dist/handlers/blockers.js +9 -8
- package/dist/handlers/bodies-of-work.js +14 -14
- package/dist/handlers/connectors.d.ts +45 -0
- package/dist/handlers/connectors.js +183 -0
- package/dist/handlers/cost.d.ts +10 -0
- package/dist/handlers/cost.js +54 -0
- package/dist/handlers/decisions.js +3 -3
- package/dist/handlers/deployment.js +35 -19
- package/dist/handlers/discovery.d.ts +7 -0
- package/dist/handlers/discovery.js +61 -2
- package/dist/handlers/fallback.js +5 -4
- package/dist/handlers/file-checkouts.d.ts +2 -0
- package/dist/handlers/file-checkouts.js +38 -6
- package/dist/handlers/findings.js +13 -12
- package/dist/handlers/git-issues.js +4 -4
- package/dist/handlers/ideas.js +5 -5
- package/dist/handlers/index.d.ts +1 -0
- package/dist/handlers/index.js +3 -0
- package/dist/handlers/milestones.js +5 -5
- package/dist/handlers/organizations.js +13 -13
- package/dist/handlers/progress.js +2 -2
- package/dist/handlers/project.js +6 -6
- package/dist/handlers/requests.js +3 -3
- package/dist/handlers/session.js +28 -9
- package/dist/handlers/sprints.js +17 -17
- package/dist/handlers/tasks.d.ts +2 -0
- package/dist/handlers/tasks.js +78 -20
- package/dist/handlers/types.d.ts +64 -2
- package/dist/handlers/types.js +48 -1
- package/dist/handlers/validation.js +3 -3
- package/dist/index.js +7 -2716
- package/dist/token-tracking.d.ts +74 -0
- package/dist/token-tracking.js +122 -0
- package/dist/tools.js +298 -9
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +17 -0
- package/docs/TOOLS.md +2053 -0
- package/package.json +4 -1
- package/scripts/generate-docs.ts +212 -0
- package/src/api-client.test.ts +718 -0
- package/src/api-client.ts +231 -0
- package/src/handlers/__test-setup__.ts +9 -0
- package/src/handlers/blockers.test.ts +31 -19
- package/src/handlers/blockers.ts +9 -8
- package/src/handlers/bodies-of-work.test.ts +55 -32
- package/src/handlers/bodies-of-work.ts +14 -14
- package/src/handlers/connectors.test.ts +834 -0
- package/src/handlers/connectors.ts +229 -0
- package/src/handlers/cost.ts +66 -0
- package/src/handlers/decisions.test.ts +34 -25
- package/src/handlers/decisions.ts +3 -3
- package/src/handlers/deployment.ts +39 -19
- package/src/handlers/discovery.ts +61 -2
- package/src/handlers/fallback.test.ts +26 -22
- package/src/handlers/fallback.ts +5 -4
- package/src/handlers/file-checkouts.test.ts +242 -49
- package/src/handlers/file-checkouts.ts +44 -6
- package/src/handlers/findings.test.ts +38 -24
- package/src/handlers/findings.ts +13 -12
- package/src/handlers/git-issues.test.ts +51 -43
- package/src/handlers/git-issues.ts +4 -4
- package/src/handlers/ideas.test.ts +28 -23
- package/src/handlers/ideas.ts +5 -5
- package/src/handlers/index.ts +3 -0
- package/src/handlers/milestones.test.ts +33 -28
- package/src/handlers/milestones.ts +5 -5
- package/src/handlers/organizations.test.ts +104 -83
- package/src/handlers/organizations.ts +13 -13
- package/src/handlers/progress.test.ts +20 -14
- package/src/handlers/progress.ts +2 -2
- package/src/handlers/project.test.ts +34 -27
- package/src/handlers/project.ts +6 -6
- package/src/handlers/requests.test.ts +27 -18
- package/src/handlers/requests.ts +3 -3
- package/src/handlers/session.test.ts +47 -0
- package/src/handlers/session.ts +32 -9
- package/src/handlers/sprints.test.ts +71 -50
- package/src/handlers/sprints.ts +17 -17
- package/src/handlers/tasks.test.ts +77 -15
- package/src/handlers/tasks.ts +90 -21
- package/src/handlers/tool-categories.test.ts +66 -0
- package/src/handlers/types.ts +81 -2
- package/src/handlers/validation.test.ts +78 -45
- package/src/handlers/validation.ts +3 -3
- package/src/index.ts +12 -2732
- package/src/token-tracking.test.ts +453 -0
- package/src/token-tracking.ts +164 -0
- package/src/tools.ts +298 -9
- package/src/utils.test.ts +2 -2
- package/src/utils.ts +17 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connectors Handlers
|
|
3
|
+
*
|
|
4
|
+
* Handles external integration management:
|
|
5
|
+
* - get_connectors
|
|
6
|
+
* - get_connector
|
|
7
|
+
* - add_connector
|
|
8
|
+
* - update_connector
|
|
9
|
+
* - delete_connector
|
|
10
|
+
* - test_connector
|
|
11
|
+
* - get_connector_events
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { Handler, HandlerRegistry } from './types.js';
|
|
15
|
+
import { success, error } from './types.js';
|
|
16
|
+
import {
|
|
17
|
+
parseArgs,
|
|
18
|
+
uuidValidator,
|
|
19
|
+
createEnumValidator,
|
|
20
|
+
} from '../validators.js';
|
|
21
|
+
import { getApiClient } from '../api-client.js';
|
|
22
|
+
|
|
23
|
+
// Valid connector types
|
|
24
|
+
const VALID_CONNECTOR_TYPES = ['webhook', 'slack', 'discord', 'github', 'custom'] as const;
|
|
25
|
+
|
|
26
|
+
// Valid connector statuses
|
|
27
|
+
const VALID_CONNECTOR_STATUSES = ['active', 'disabled'] as const;
|
|
28
|
+
|
|
29
|
+
// Valid event statuses
|
|
30
|
+
const VALID_EVENT_STATUSES = ['pending', 'sent', 'failed', 'retrying'] as const;
|
|
31
|
+
|
|
32
|
+
// Argument schemas for type-safe parsing
|
|
33
|
+
const getConnectorsSchema = {
|
|
34
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
35
|
+
type: { type: 'string' as const, validate: createEnumValidator(VALID_CONNECTOR_TYPES) },
|
|
36
|
+
status: { type: 'string' as const, validate: createEnumValidator(VALID_CONNECTOR_STATUSES) },
|
|
37
|
+
limit: { type: 'number' as const, default: 50 },
|
|
38
|
+
offset: { type: 'number' as const, default: 0 },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const getConnectorSchema = {
|
|
42
|
+
connector_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const addConnectorSchema = {
|
|
46
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
47
|
+
name: { type: 'string' as const, required: true as const },
|
|
48
|
+
type: { type: 'string' as const, required: true as const, validate: createEnumValidator(VALID_CONNECTOR_TYPES) },
|
|
49
|
+
description: { type: 'string' as const },
|
|
50
|
+
config: { type: 'object' as const },
|
|
51
|
+
events: { type: 'object' as const },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const updateConnectorSchema = {
|
|
55
|
+
connector_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
56
|
+
name: { type: 'string' as const },
|
|
57
|
+
description: { type: 'string' as const },
|
|
58
|
+
config: { type: 'object' as const },
|
|
59
|
+
events: { type: 'object' as const },
|
|
60
|
+
status: { type: 'string' as const, validate: createEnumValidator(VALID_CONNECTOR_STATUSES) },
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const deleteConnectorSchema = {
|
|
64
|
+
connector_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const testConnectorSchema = {
|
|
68
|
+
connector_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const getConnectorEventsSchema = {
|
|
72
|
+
connector_id: { type: 'string' as const, validate: uuidValidator },
|
|
73
|
+
project_id: { type: 'string' as const, validate: uuidValidator },
|
|
74
|
+
status: { type: 'string' as const, validate: createEnumValidator(VALID_EVENT_STATUSES) },
|
|
75
|
+
limit: { type: 'number' as const, default: 50 },
|
|
76
|
+
offset: { type: 'number' as const, default: 0 },
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get all connectors for a project
|
|
81
|
+
*/
|
|
82
|
+
export const getConnectors: Handler = async (args, _ctx) => {
|
|
83
|
+
const { project_id, type, status, limit, offset } = parseArgs(args, getConnectorsSchema);
|
|
84
|
+
|
|
85
|
+
const apiClient = getApiClient();
|
|
86
|
+
const response = await apiClient.getConnectors(project_id, {
|
|
87
|
+
type,
|
|
88
|
+
status,
|
|
89
|
+
limit,
|
|
90
|
+
offset
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
return error(response.error || 'Failed to fetch connectors');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return success(response.data);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get a single connector with full details
|
|
102
|
+
*/
|
|
103
|
+
export const getConnector: Handler = async (args, _ctx) => {
|
|
104
|
+
const { connector_id } = parseArgs(args, getConnectorSchema);
|
|
105
|
+
|
|
106
|
+
const apiClient = getApiClient();
|
|
107
|
+
const response = await apiClient.getConnector(connector_id);
|
|
108
|
+
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
return error(response.error || 'Failed to fetch connector');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return success(response.data);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Add a new connector
|
|
118
|
+
*/
|
|
119
|
+
export const addConnector: Handler = async (args, _ctx) => {
|
|
120
|
+
const { project_id, name, type, description, config, events } = parseArgs(args, addConnectorSchema);
|
|
121
|
+
|
|
122
|
+
const apiClient = getApiClient();
|
|
123
|
+
const response = await apiClient.addConnector(project_id, {
|
|
124
|
+
name,
|
|
125
|
+
type,
|
|
126
|
+
description,
|
|
127
|
+
config: config as Record<string, unknown> | undefined,
|
|
128
|
+
events: events as Record<string, boolean> | undefined
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
if (!response.ok) {
|
|
132
|
+
return error(response.error || 'Failed to create connector');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return success(response.data);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Update a connector
|
|
140
|
+
*/
|
|
141
|
+
export const updateConnector: Handler = async (args, _ctx) => {
|
|
142
|
+
const { connector_id, name, description, config, events, status } = parseArgs(args, updateConnectorSchema);
|
|
143
|
+
|
|
144
|
+
const apiClient = getApiClient();
|
|
145
|
+
const response = await apiClient.updateConnector(connector_id, {
|
|
146
|
+
name,
|
|
147
|
+
description,
|
|
148
|
+
config: config as Record<string, unknown> | undefined,
|
|
149
|
+
events: events as Record<string, boolean> | undefined,
|
|
150
|
+
status
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
return error(response.error || 'Failed to update connector');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return success(response.data);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Delete a connector
|
|
162
|
+
*/
|
|
163
|
+
export const deleteConnector: Handler = async (args, _ctx) => {
|
|
164
|
+
const { connector_id } = parseArgs(args, deleteConnectorSchema);
|
|
165
|
+
|
|
166
|
+
const apiClient = getApiClient();
|
|
167
|
+
const response = await apiClient.deleteConnector(connector_id);
|
|
168
|
+
|
|
169
|
+
if (!response.ok) {
|
|
170
|
+
return error(response.error || 'Failed to delete connector');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return success(response.data);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Test a connector by sending a test event
|
|
178
|
+
*/
|
|
179
|
+
export const testConnector: Handler = async (args, _ctx) => {
|
|
180
|
+
const { connector_id } = parseArgs(args, testConnectorSchema);
|
|
181
|
+
|
|
182
|
+
const apiClient = getApiClient();
|
|
183
|
+
const response = await apiClient.testConnector(connector_id);
|
|
184
|
+
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
return error(response.error || 'Failed to test connector');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return success(response.data);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get connector event history
|
|
194
|
+
*/
|
|
195
|
+
export const getConnectorEvents: Handler = async (args, _ctx) => {
|
|
196
|
+
const { connector_id, project_id, status, limit, offset } = parseArgs(args, getConnectorEventsSchema);
|
|
197
|
+
|
|
198
|
+
if (!connector_id && !project_id) {
|
|
199
|
+
return error('Either connector_id or project_id is required');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const apiClient = getApiClient();
|
|
203
|
+
const response = await apiClient.getConnectorEvents({
|
|
204
|
+
connector_id,
|
|
205
|
+
project_id,
|
|
206
|
+
status,
|
|
207
|
+
limit,
|
|
208
|
+
offset
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
if (!response.ok) {
|
|
212
|
+
return error(response.error || 'Failed to fetch connector events');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return success(response.data);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Connectors handlers registry
|
|
220
|
+
*/
|
|
221
|
+
export const connectorHandlers: HandlerRegistry = {
|
|
222
|
+
get_connectors: getConnectors,
|
|
223
|
+
get_connector: getConnector,
|
|
224
|
+
add_connector: addConnector,
|
|
225
|
+
update_connector: updateConnector,
|
|
226
|
+
delete_connector: deleteConnector,
|
|
227
|
+
test_connector: testConnector,
|
|
228
|
+
get_connector_events: getConnectorEvents,
|
|
229
|
+
};
|
package/src/handlers/cost.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* - update_cost_alert
|
|
9
9
|
* - delete_cost_alert
|
|
10
10
|
* - get_task_costs
|
|
11
|
+
* - get_body_of_work_costs
|
|
12
|
+
* - get_sprint_costs
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
import type { Handler, HandlerRegistry } from './types.js';
|
|
@@ -55,6 +57,16 @@ const getTaskCostsSchema = {
|
|
|
55
57
|
limit: { type: 'number' as const, default: 20 },
|
|
56
58
|
};
|
|
57
59
|
|
|
60
|
+
const getBodyOfWorkCostsSchema = {
|
|
61
|
+
body_of_work_id: { type: 'string' as const, validate: uuidValidator },
|
|
62
|
+
project_id: { type: 'string' as const, validate: uuidValidator },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const getSprintCostsSchema = {
|
|
66
|
+
sprint_id: { type: 'string' as const, validate: uuidValidator },
|
|
67
|
+
project_id: { type: 'string' as const, validate: uuidValidator },
|
|
68
|
+
};
|
|
69
|
+
|
|
58
70
|
// Custom validator for positive numbers
|
|
59
71
|
function validatePositiveNumber(value: number | undefined, fieldName: string): void {
|
|
60
72
|
if (value !== undefined && value <= 0) {
|
|
@@ -206,6 +218,58 @@ export const getTaskCosts: Handler = async (args, _ctx) => {
|
|
|
206
218
|
return { result: response.data };
|
|
207
219
|
};
|
|
208
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Get body of work costs with phase breakdown
|
|
223
|
+
*/
|
|
224
|
+
export const getBodyOfWorkCosts: Handler = async (args, _ctx) => {
|
|
225
|
+
const { body_of_work_id, project_id } = parseArgs(args, getBodyOfWorkCostsSchema);
|
|
226
|
+
|
|
227
|
+
if (!body_of_work_id && !project_id) {
|
|
228
|
+
return {
|
|
229
|
+
result: { error: 'Either body_of_work_id or project_id is required' },
|
|
230
|
+
isError: true,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const apiClient = getApiClient();
|
|
235
|
+
const response = await apiClient.getBodyOfWorkCosts({ body_of_work_id, project_id });
|
|
236
|
+
|
|
237
|
+
if (!response.ok) {
|
|
238
|
+
return {
|
|
239
|
+
result: { error: response.error || 'Failed to get body of work costs' },
|
|
240
|
+
isError: true,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return { result: response.data };
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Get sprint costs with velocity metrics
|
|
249
|
+
*/
|
|
250
|
+
export const getSprintCosts: Handler = async (args, _ctx) => {
|
|
251
|
+
const { sprint_id, project_id } = parseArgs(args, getSprintCostsSchema);
|
|
252
|
+
|
|
253
|
+
if (!sprint_id && !project_id) {
|
|
254
|
+
return {
|
|
255
|
+
result: { error: 'Either sprint_id or project_id is required' },
|
|
256
|
+
isError: true,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const apiClient = getApiClient();
|
|
261
|
+
const response = await apiClient.getSprintCosts({ sprint_id, project_id });
|
|
262
|
+
|
|
263
|
+
if (!response.ok) {
|
|
264
|
+
return {
|
|
265
|
+
result: { error: response.error || 'Failed to get sprint costs' },
|
|
266
|
+
isError: true,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return { result: response.data };
|
|
271
|
+
};
|
|
272
|
+
|
|
209
273
|
/**
|
|
210
274
|
* Cost handlers registry
|
|
211
275
|
*/
|
|
@@ -216,4 +280,6 @@ export const costHandlers: HandlerRegistry = {
|
|
|
216
280
|
update_cost_alert: updateCostAlert,
|
|
217
281
|
delete_cost_alert: deleteCostAlert,
|
|
218
282
|
get_task_costs: getTaskCosts,
|
|
283
|
+
get_body_of_work_costs: getBodyOfWorkCosts,
|
|
284
|
+
get_sprint_costs: getSprintCosts,
|
|
219
285
|
};
|
|
@@ -119,23 +119,26 @@ describe('logDecision', () => {
|
|
|
119
119
|
);
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
-
it('should
|
|
122
|
+
it('should return error when API call fails', async () => {
|
|
123
123
|
mockApiClient.logDecision.mockResolvedValue({
|
|
124
124
|
ok: false,
|
|
125
125
|
error: 'Insert failed',
|
|
126
126
|
});
|
|
127
127
|
const ctx = createMockContext();
|
|
128
128
|
|
|
129
|
-
await
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
).
|
|
129
|
+
const result = await logDecision(
|
|
130
|
+
{
|
|
131
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
132
|
+
title: 'Test',
|
|
133
|
+
description: 'Test',
|
|
134
|
+
},
|
|
135
|
+
ctx
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
expect(result.isError).toBe(true);
|
|
139
|
+
expect(result.result).toMatchObject({
|
|
140
|
+
error: 'Insert failed',
|
|
141
|
+
});
|
|
139
142
|
});
|
|
140
143
|
});
|
|
141
144
|
|
|
@@ -216,19 +219,22 @@ describe('getDecisions', () => {
|
|
|
216
219
|
);
|
|
217
220
|
});
|
|
218
221
|
|
|
219
|
-
it('should
|
|
222
|
+
it('should return error when query fails', async () => {
|
|
220
223
|
mockApiClient.getDecisions.mockResolvedValue({
|
|
221
224
|
ok: false,
|
|
222
225
|
error: 'Query failed',
|
|
223
226
|
});
|
|
224
227
|
const ctx = createMockContext();
|
|
225
228
|
|
|
226
|
-
await
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
).
|
|
229
|
+
const result = await getDecisions(
|
|
230
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
231
|
+
ctx
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
expect(result.isError).toBe(true);
|
|
235
|
+
expect(result.result).toMatchObject({
|
|
236
|
+
error: 'Query failed',
|
|
237
|
+
});
|
|
232
238
|
});
|
|
233
239
|
});
|
|
234
240
|
|
|
@@ -287,18 +293,21 @@ describe('deleteDecision', () => {
|
|
|
287
293
|
);
|
|
288
294
|
});
|
|
289
295
|
|
|
290
|
-
it('should
|
|
296
|
+
it('should return error when delete fails', async () => {
|
|
291
297
|
mockApiClient.deleteDecision.mockResolvedValue({
|
|
292
298
|
ok: false,
|
|
293
299
|
error: 'Delete failed',
|
|
294
300
|
});
|
|
295
301
|
const ctx = createMockContext();
|
|
296
302
|
|
|
297
|
-
await
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
).
|
|
303
|
+
const result = await deleteDecision(
|
|
304
|
+
{ decision_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
305
|
+
ctx
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
expect(result.isError).toBe(true);
|
|
309
|
+
expect(result.result).toMatchObject({
|
|
310
|
+
error: 'Delete failed',
|
|
311
|
+
});
|
|
303
312
|
});
|
|
304
313
|
});
|
|
@@ -47,7 +47,7 @@ export const logDecision: Handler = async (args, ctx) => {
|
|
|
47
47
|
}, session.currentSessionId || undefined);
|
|
48
48
|
|
|
49
49
|
if (!response.ok) {
|
|
50
|
-
|
|
50
|
+
return { result: { error: response.error || 'Failed to log decision' }, isError: true };
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
return { result: { success: true, title, decision_id: response.data?.decision_id } };
|
|
@@ -65,7 +65,7 @@ export const getDecisions: Handler = async (args, _ctx) => {
|
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
if (!response.ok) {
|
|
68
|
-
|
|
68
|
+
return { result: { error: response.error || 'Failed to fetch decisions' }, isError: true };
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
return {
|
|
@@ -83,7 +83,7 @@ export const deleteDecision: Handler = async (args, _ctx) => {
|
|
|
83
83
|
const response = await apiClient.deleteDecision(decision_id);
|
|
84
84
|
|
|
85
85
|
if (!response.ok) {
|
|
86
|
-
|
|
86
|
+
return { result: { error: response.error || 'Failed to delete decision' }, isError: true };
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
return { result: { success: true } };
|
|
@@ -28,7 +28,7 @@ const VERSION_BUMPS = ['patch', 'minor', 'major'] as const;
|
|
|
28
28
|
const REQUIREMENT_TYPES = ['migration', 'env_var', 'config', 'manual', 'breaking_change', 'agent_task'] as const;
|
|
29
29
|
const REQUIREMENT_STAGES = ['preparation', 'deployment', 'verification'] as const;
|
|
30
30
|
const REQUIREMENT_STATUSES = ['pending', 'completed', 'converted_to_task', 'all'] as const;
|
|
31
|
-
const SCHEDULE_TYPES = ['once', 'daily', 'weekly', 'monthly'] as const;
|
|
31
|
+
const SCHEDULE_TYPES = ['once', 'hourly', 'daily', 'weekly', 'monthly'] as const;
|
|
32
32
|
|
|
33
33
|
type Environment = typeof ENVIRONMENTS[number];
|
|
34
34
|
type VersionBump = typeof VERSION_BUMPS[number];
|
|
@@ -107,6 +107,7 @@ const scheduleDeploymentSchema = {
|
|
|
107
107
|
schedule_type: { type: 'string' as const, default: 'once', validate: createEnumValidator(SCHEDULE_TYPES) },
|
|
108
108
|
scheduled_at: { type: 'string' as const, required: true as const },
|
|
109
109
|
auto_trigger: { type: 'boolean' as const, default: true },
|
|
110
|
+
hours_interval: { type: 'number' as const, default: 1 },
|
|
110
111
|
notes: { type: 'string' as const },
|
|
111
112
|
git_ref: { type: 'string' as const },
|
|
112
113
|
};
|
|
@@ -123,6 +124,7 @@ const updateScheduledDeploymentSchema = {
|
|
|
123
124
|
schedule_type: { type: 'string' as const, validate: createEnumValidator(SCHEDULE_TYPES) },
|
|
124
125
|
scheduled_at: { type: 'string' as const },
|
|
125
126
|
auto_trigger: { type: 'boolean' as const },
|
|
127
|
+
hours_interval: { type: 'number' as const },
|
|
126
128
|
enabled: { type: 'boolean' as const },
|
|
127
129
|
notes: { type: 'string' as const },
|
|
128
130
|
git_ref: { type: 'string' as const },
|
|
@@ -153,7 +155,7 @@ export const requestDeployment: Handler = async (args, ctx) => {
|
|
|
153
155
|
});
|
|
154
156
|
|
|
155
157
|
if (!response.ok) {
|
|
156
|
-
|
|
158
|
+
return { result: { error: response.error || 'Failed to request deployment' }, isError: true };
|
|
157
159
|
}
|
|
158
160
|
|
|
159
161
|
return { result: response.data };
|
|
@@ -170,7 +172,7 @@ export const claimDeploymentValidation: Handler = async (args, ctx) => {
|
|
|
170
172
|
);
|
|
171
173
|
|
|
172
174
|
if (!response.ok) {
|
|
173
|
-
|
|
175
|
+
return { result: { error: response.error || 'Failed to claim deployment validation' }, isError: true };
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
return { result: response.data };
|
|
@@ -188,7 +190,7 @@ export const reportValidation: Handler = async (args, ctx) => {
|
|
|
188
190
|
});
|
|
189
191
|
|
|
190
192
|
if (!response.ok) {
|
|
191
|
-
|
|
193
|
+
return { result: { error: response.error || 'Failed to report validation' }, isError: true };
|
|
192
194
|
}
|
|
193
195
|
|
|
194
196
|
return { result: response.data };
|
|
@@ -201,7 +203,7 @@ export const checkDeploymentStatus: Handler = async (args, ctx) => {
|
|
|
201
203
|
const response = await apiClient.checkDeploymentStatus(project_id);
|
|
202
204
|
|
|
203
205
|
if (!response.ok) {
|
|
204
|
-
|
|
206
|
+
return { result: { error: response.error || 'Failed to check deployment status' }, isError: true };
|
|
205
207
|
}
|
|
206
208
|
|
|
207
209
|
return { result: response.data };
|
|
@@ -218,7 +220,7 @@ export const startDeployment: Handler = async (args, ctx) => {
|
|
|
218
220
|
);
|
|
219
221
|
|
|
220
222
|
if (!response.ok) {
|
|
221
|
-
|
|
223
|
+
return { result: { error: response.error || 'Failed to start deployment' }, isError: true };
|
|
222
224
|
}
|
|
223
225
|
|
|
224
226
|
return { result: response.data };
|
|
@@ -235,7 +237,7 @@ export const completeDeployment: Handler = async (args, ctx) => {
|
|
|
235
237
|
});
|
|
236
238
|
|
|
237
239
|
if (!response.ok) {
|
|
238
|
-
|
|
240
|
+
return { result: { error: response.error || 'Failed to complete deployment' }, isError: true };
|
|
239
241
|
}
|
|
240
242
|
|
|
241
243
|
return { result: response.data };
|
|
@@ -248,7 +250,7 @@ export const cancelDeployment: Handler = async (args, ctx) => {
|
|
|
248
250
|
const response = await apiClient.cancelDeployment(project_id, reason);
|
|
249
251
|
|
|
250
252
|
if (!response.ok) {
|
|
251
|
-
|
|
253
|
+
return { result: { error: response.error || 'Failed to cancel deployment' }, isError: true };
|
|
252
254
|
}
|
|
253
255
|
|
|
254
256
|
return { result: response.data };
|
|
@@ -269,7 +271,7 @@ export const addDeploymentRequirement: Handler = async (args, ctx) => {
|
|
|
269
271
|
});
|
|
270
272
|
|
|
271
273
|
if (!response.ok) {
|
|
272
|
-
|
|
274
|
+
return { result: { error: response.error || 'Failed to add deployment requirement' }, isError: true };
|
|
273
275
|
}
|
|
274
276
|
|
|
275
277
|
return { result: response.data };
|
|
@@ -282,7 +284,7 @@ export const completeDeploymentRequirement: Handler = async (args, ctx) => {
|
|
|
282
284
|
const response = await apiClient.completeDeploymentRequirement(requirement_id);
|
|
283
285
|
|
|
284
286
|
if (!response.ok) {
|
|
285
|
-
|
|
287
|
+
return { result: { error: response.error || 'Failed to complete deployment requirement' }, isError: true };
|
|
286
288
|
}
|
|
287
289
|
|
|
288
290
|
return { result: response.data };
|
|
@@ -298,7 +300,7 @@ export const getDeploymentRequirements: Handler = async (args, ctx) => {
|
|
|
298
300
|
});
|
|
299
301
|
|
|
300
302
|
if (!response.ok) {
|
|
301
|
-
|
|
303
|
+
return { result: { error: response.error || 'Failed to get deployment requirements' }, isError: true };
|
|
302
304
|
}
|
|
303
305
|
|
|
304
306
|
return { result: response.data };
|
|
@@ -316,6 +318,7 @@ export const scheduleDeployment: Handler = async (args, ctx) => {
|
|
|
316
318
|
schedule_type,
|
|
317
319
|
scheduled_at,
|
|
318
320
|
auto_trigger,
|
|
321
|
+
hours_interval,
|
|
319
322
|
notes,
|
|
320
323
|
git_ref,
|
|
321
324
|
} = parseArgs(args, scheduleDeploymentSchema);
|
|
@@ -335,19 +338,28 @@ export const scheduleDeployment: Handler = async (args, ctx) => {
|
|
|
335
338
|
});
|
|
336
339
|
}
|
|
337
340
|
|
|
341
|
+
// Validate hours_interval for hourly schedule type (default is 1)
|
|
342
|
+
const hoursInterval = hours_interval ?? 1;
|
|
343
|
+
if (schedule_type === 'hourly' && (hoursInterval < 1 || hoursInterval > 24)) {
|
|
344
|
+
throw new ValidationError('hours_interval must be between 1 and 24', {
|
|
345
|
+
field: 'hours_interval',
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
338
349
|
const apiClient = getApiClient();
|
|
339
350
|
const response = await apiClient.scheduleDeployment(project_id, {
|
|
340
351
|
environment: environment as 'development' | 'staging' | 'production',
|
|
341
352
|
version_bump: version_bump as 'patch' | 'minor' | 'major',
|
|
342
|
-
schedule_type: schedule_type as 'once' | 'daily' | 'weekly' | 'monthly',
|
|
353
|
+
schedule_type: schedule_type as 'once' | 'hourly' | 'daily' | 'weekly' | 'monthly',
|
|
343
354
|
scheduled_at: scheduledDate.toISOString(),
|
|
344
355
|
auto_trigger,
|
|
356
|
+
hours_interval: hoursInterval,
|
|
345
357
|
notes,
|
|
346
358
|
git_ref
|
|
347
359
|
});
|
|
348
360
|
|
|
349
361
|
if (!response.ok) {
|
|
350
|
-
|
|
362
|
+
return { result: { error: response.error || 'Failed to schedule deployment' }, isError: true };
|
|
351
363
|
}
|
|
352
364
|
|
|
353
365
|
return { result: response.data };
|
|
@@ -360,7 +372,7 @@ export const getScheduledDeployments: Handler = async (args, ctx) => {
|
|
|
360
372
|
const response = await apiClient.getScheduledDeployments(project_id, include_disabled);
|
|
361
373
|
|
|
362
374
|
if (!response.ok) {
|
|
363
|
-
|
|
375
|
+
return { result: { error: response.error || 'Failed to get scheduled deployments' }, isError: true };
|
|
364
376
|
}
|
|
365
377
|
|
|
366
378
|
return { result: response.data };
|
|
@@ -374,6 +386,7 @@ export const updateScheduledDeployment: Handler = async (args, ctx) => {
|
|
|
374
386
|
schedule_type,
|
|
375
387
|
scheduled_at,
|
|
376
388
|
auto_trigger,
|
|
389
|
+
hours_interval,
|
|
377
390
|
enabled,
|
|
378
391
|
notes,
|
|
379
392
|
git_ref,
|
|
@@ -394,6 +407,12 @@ export const updateScheduledDeployment: Handler = async (args, ctx) => {
|
|
|
394
407
|
}
|
|
395
408
|
|
|
396
409
|
if (auto_trigger !== undefined) updates.auto_trigger = auto_trigger;
|
|
410
|
+
if (hours_interval !== undefined) {
|
|
411
|
+
if (hours_interval < 1 || hours_interval > 24) {
|
|
412
|
+
throw new ValidationError('hours_interval must be between 1 and 24');
|
|
413
|
+
}
|
|
414
|
+
updates.hours_interval = hours_interval;
|
|
415
|
+
}
|
|
397
416
|
if (enabled !== undefined) updates.enabled = enabled;
|
|
398
417
|
if (notes !== undefined) updates.notes = notes;
|
|
399
418
|
if (git_ref !== undefined) updates.git_ref = git_ref;
|
|
@@ -406,16 +425,17 @@ export const updateScheduledDeployment: Handler = async (args, ctx) => {
|
|
|
406
425
|
const response = await apiClient.updateScheduledDeployment(schedule_id, updates as {
|
|
407
426
|
environment?: 'development' | 'staging' | 'production';
|
|
408
427
|
version_bump?: 'patch' | 'minor' | 'major';
|
|
409
|
-
schedule_type?: 'once' | 'daily' | 'weekly' | 'monthly';
|
|
428
|
+
schedule_type?: 'once' | 'hourly' | 'daily' | 'weekly' | 'monthly';
|
|
410
429
|
scheduled_at?: string;
|
|
411
430
|
auto_trigger?: boolean;
|
|
431
|
+
hours_interval?: number;
|
|
412
432
|
enabled?: boolean;
|
|
413
433
|
notes?: string;
|
|
414
434
|
git_ref?: string;
|
|
415
435
|
});
|
|
416
436
|
|
|
417
437
|
if (!response.ok) {
|
|
418
|
-
|
|
438
|
+
return { result: { error: response.error || 'Failed to update scheduled deployment' }, isError: true };
|
|
419
439
|
}
|
|
420
440
|
|
|
421
441
|
return { result: response.data };
|
|
@@ -428,7 +448,7 @@ export const deleteScheduledDeployment: Handler = async (args, ctx) => {
|
|
|
428
448
|
const response = await apiClient.deleteScheduledDeployment(schedule_id);
|
|
429
449
|
|
|
430
450
|
if (!response.ok) {
|
|
431
|
-
|
|
451
|
+
return { result: { error: response.error || 'Failed to delete scheduled deployment' }, isError: true };
|
|
432
452
|
}
|
|
433
453
|
|
|
434
454
|
return { result: response.data };
|
|
@@ -445,7 +465,7 @@ export const triggerScheduledDeployment: Handler = async (args, ctx) => {
|
|
|
445
465
|
);
|
|
446
466
|
|
|
447
467
|
if (!response.ok) {
|
|
448
|
-
|
|
468
|
+
return { result: { error: response.error || 'Failed to trigger scheduled deployment' }, isError: true };
|
|
449
469
|
}
|
|
450
470
|
|
|
451
471
|
return { result: response.data };
|
|
@@ -458,7 +478,7 @@ export const checkDueDeployments: Handler = async (args, ctx) => {
|
|
|
458
478
|
const response = await apiClient.checkDueDeployments(project_id);
|
|
459
479
|
|
|
460
480
|
if (!response.ok) {
|
|
461
|
-
|
|
481
|
+
return { result: { error: response.error || 'Failed to check due deployments' }, isError: true };
|
|
462
482
|
}
|
|
463
483
|
|
|
464
484
|
return { result: response.data };
|