@vibescope/mcp-server 0.2.0 → 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 +251 -1
- package/dist/api-client.js +82 -3
- package/dist/handlers/blockers.js +9 -8
- package/dist/handlers/bodies-of-work.js +96 -63
- 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 +112 -50
- package/dist/handlers/decisions.js +32 -19
- package/dist/handlers/deployment.js +144 -122
- package/dist/handlers/discovery.d.ts +7 -0
- package/dist/handlers/discovery.js +96 -7
- package/dist/handlers/fallback.js +29 -23
- package/dist/handlers/file-checkouts.d.ts +20 -0
- package/dist/handlers/file-checkouts.js +133 -0
- package/dist/handlers/findings.d.ts +6 -0
- package/dist/handlers/findings.js +96 -40
- package/dist/handlers/git-issues.js +40 -36
- package/dist/handlers/ideas.js +49 -31
- package/dist/handlers/index.d.ts +3 -0
- package/dist/handlers/index.js +9 -0
- package/dist/handlers/milestones.js +39 -32
- package/dist/handlers/organizations.js +99 -91
- package/dist/handlers/progress.js +24 -13
- package/dist/handlers/project.js +68 -28
- package/dist/handlers/requests.js +18 -14
- package/dist/handlers/roles.d.ts +18 -0
- package/dist/handlers/roles.js +130 -0
- package/dist/handlers/session.js +58 -17
- package/dist/handlers/sprints.js +93 -81
- package/dist/handlers/tasks.d.ts +2 -0
- package/dist/handlers/tasks.js +189 -91
- package/dist/handlers/types.d.ts +64 -2
- package/dist/handlers/types.js +48 -1
- package/dist/handlers/validation.js +21 -17
- 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 +685 -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 +320 -6
- package/src/handlers/__test-setup__.ts +16 -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 +115 -115
- package/src/handlers/connectors.test.ts +834 -0
- package/src/handlers/connectors.ts +229 -0
- package/src/handlers/cost.test.ts +34 -44
- package/src/handlers/cost.ts +136 -85
- package/src/handlers/decisions.test.ts +37 -27
- package/src/handlers/decisions.ts +35 -30
- package/src/handlers/deployment.ts +180 -208
- package/src/handlers/discovery.test.ts +4 -5
- package/src/handlers/discovery.ts +98 -8
- package/src/handlers/fallback.test.ts +26 -22
- package/src/handlers/fallback.ts +36 -33
- package/src/handlers/file-checkouts.test.ts +670 -0
- package/src/handlers/file-checkouts.ts +165 -0
- package/src/handlers/findings.test.ts +178 -19
- package/src/handlers/findings.ts +112 -74
- package/src/handlers/git-issues.test.ts +51 -43
- package/src/handlers/git-issues.ts +44 -84
- package/src/handlers/ideas.test.ts +28 -23
- package/src/handlers/ideas.ts +61 -59
- package/src/handlers/index.ts +9 -0
- package/src/handlers/milestones.test.ts +33 -28
- package/src/handlers/milestones.ts +52 -50
- package/src/handlers/organizations.test.ts +104 -83
- package/src/handlers/organizations.ts +117 -142
- package/src/handlers/progress.test.ts +20 -14
- package/src/handlers/progress.ts +26 -24
- package/src/handlers/project.test.ts +34 -27
- package/src/handlers/project.ts +95 -63
- package/src/handlers/requests.test.ts +27 -18
- package/src/handlers/requests.ts +21 -17
- package/src/handlers/roles.test.ts +303 -0
- package/src/handlers/roles.ts +208 -0
- package/src/handlers/session.test.ts +47 -0
- package/src/handlers/session.ts +71 -26
- package/src/handlers/sprints.test.ts +71 -50
- package/src/handlers/sprints.ts +113 -146
- package/src/handlers/tasks.test.ts +77 -15
- package/src/handlers/tasks.ts +231 -156
- 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 +23 -25
- 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 +685 -9
- package/src/utils.test.ts +2 -2
- package/src/utils.ts +17 -0
- package/dist/config/tool-categories.d.ts +0 -31
- package/dist/config/tool-categories.js +0 -253
- package/dist/knowledge.d.ts +0 -6
- package/dist/knowledge.js +0 -218
package/src/handlers/sprints.ts
CHANGED
|
@@ -16,18 +16,92 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { Handler, HandlerRegistry } from './types.js';
|
|
19
|
-
import {
|
|
19
|
+
import { parseArgs, uuidValidator, createEnumValidator } from '../validators.js';
|
|
20
20
|
import { getApiClient } from '../api-client.js';
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
const SPRINT_STATUSES = ['planning', 'active', 'in_review', 'retrospective', 'completed', 'cancelled'] as const;
|
|
23
|
+
const TASK_PHASES = ['pre', 'core', 'post'] as const;
|
|
24
|
+
const DEPLOY_ENVIRONMENTS = ['development', 'staging', 'production'] as const;
|
|
25
|
+
const VERSION_BUMPS = ['patch', 'minor', 'major'] as const;
|
|
26
|
+
|
|
27
|
+
type SprintStatus = typeof SPRINT_STATUSES[number];
|
|
28
|
+
type TaskPhase = typeof TASK_PHASES[number];
|
|
29
|
+
type DeployEnvironment = typeof DEPLOY_ENVIRONMENTS[number];
|
|
30
|
+
type VersionBump = typeof VERSION_BUMPS[number];
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Argument Schemas
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
const createSprintSchema = {
|
|
37
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
38
|
+
title: { type: 'string' as const, required: true as const },
|
|
39
|
+
goal: { type: 'string' as const },
|
|
40
|
+
start_date: { type: 'string' as const, required: true as const },
|
|
41
|
+
end_date: { type: 'string' as const, required: true as const },
|
|
42
|
+
auto_deploy_on_completion: { type: 'boolean' as const },
|
|
43
|
+
deploy_environment: { type: 'string' as const, validate: createEnumValidator(DEPLOY_ENVIRONMENTS) },
|
|
44
|
+
deploy_version_bump: { type: 'string' as const, validate: createEnumValidator(VERSION_BUMPS) },
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const updateSprintSchema = {
|
|
48
|
+
sprint_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
49
|
+
title: { type: 'string' as const },
|
|
50
|
+
goal: { type: 'string' as const },
|
|
51
|
+
start_date: { type: 'string' as const },
|
|
52
|
+
end_date: { type: 'string' as const },
|
|
53
|
+
auto_deploy_on_completion: { type: 'boolean' as const },
|
|
54
|
+
deploy_environment: { type: 'string' as const, validate: createEnumValidator(DEPLOY_ENVIRONMENTS) },
|
|
55
|
+
deploy_version_bump: { type: 'string' as const, validate: createEnumValidator(VERSION_BUMPS) },
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const getSprintSchema = {
|
|
59
|
+
sprint_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
60
|
+
summary_only: { type: 'boolean' as const, default: false },
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const getSprintsSchema = {
|
|
64
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
65
|
+
status: { type: 'string' as const, validate: createEnumValidator(SPRINT_STATUSES) },
|
|
66
|
+
limit: { type: 'number' as const, default: 20 },
|
|
67
|
+
offset: { type: 'number' as const, default: 0 },
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const deleteSprintSchema = {
|
|
71
|
+
sprint_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const startSprintSchema = {
|
|
75
|
+
sprint_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const completeSprintSchema = {
|
|
79
|
+
sprint_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
80
|
+
retrospective_notes: { type: 'string' as const },
|
|
81
|
+
skip_retrospective: { type: 'boolean' as const },
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const addTaskToSprintSchema = {
|
|
85
|
+
sprint_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
86
|
+
task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
87
|
+
story_points: { type: 'number' as const },
|
|
88
|
+
phase: { type: 'string' as const, validate: createEnumValidator(TASK_PHASES) },
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const removeTaskFromSprintSchema = {
|
|
92
|
+
sprint_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
93
|
+
task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const getSprintBacklogSchema = {
|
|
97
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
98
|
+
sprint_id: { type: 'string' as const, validate: uuidValidator },
|
|
99
|
+
};
|
|
26
100
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
101
|
+
const getSprintVelocitySchema = {
|
|
102
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
103
|
+
limit: { type: 'number' as const, default: 10 },
|
|
104
|
+
};
|
|
31
105
|
|
|
32
106
|
export const createSprint: Handler = async (args, ctx) => {
|
|
33
107
|
const {
|
|
@@ -39,41 +113,19 @@ export const createSprint: Handler = async (args, ctx) => {
|
|
|
39
113
|
auto_deploy_on_completion,
|
|
40
114
|
deploy_environment,
|
|
41
115
|
deploy_version_bump,
|
|
42
|
-
} = args
|
|
43
|
-
project_id: string;
|
|
44
|
-
title: string;
|
|
45
|
-
goal?: string;
|
|
46
|
-
start_date: string;
|
|
47
|
-
end_date: string;
|
|
48
|
-
auto_deploy_on_completion?: boolean;
|
|
49
|
-
deploy_environment?: DeployEnvironment;
|
|
50
|
-
deploy_version_bump?: VersionBump;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
validateRequired(project_id, 'project_id');
|
|
54
|
-
validateUUID(project_id, 'project_id');
|
|
55
|
-
validateRequired(title, 'title');
|
|
56
|
-
validateRequired(start_date, 'start_date');
|
|
57
|
-
validateRequired(end_date, 'end_date');
|
|
58
|
-
|
|
59
|
-
if (deploy_environment) {
|
|
60
|
-
validateEnum(deploy_environment, DEPLOY_ENVIRONMENTS, 'deploy_environment');
|
|
61
|
-
}
|
|
62
|
-
if (deploy_version_bump) {
|
|
63
|
-
validateEnum(deploy_version_bump, VERSION_BUMPS, 'deploy_version_bump');
|
|
64
|
-
}
|
|
116
|
+
} = parseArgs(args, createSprintSchema);
|
|
65
117
|
|
|
66
118
|
// Validate date format and order
|
|
67
119
|
const startDateObj = new Date(start_date);
|
|
68
120
|
const endDateObj = new Date(end_date);
|
|
69
121
|
if (isNaN(startDateObj.getTime())) {
|
|
70
|
-
|
|
122
|
+
return { result: { error: 'Invalid start_date format. Use YYYY-MM-DD' }, isError: true };
|
|
71
123
|
}
|
|
72
124
|
if (isNaN(endDateObj.getTime())) {
|
|
73
|
-
|
|
125
|
+
return { result: { error: 'Invalid end_date format. Use YYYY-MM-DD' }, isError: true };
|
|
74
126
|
}
|
|
75
127
|
if (endDateObj < startDateObj) {
|
|
76
|
-
|
|
128
|
+
return { result: { error: 'end_date must be on or after start_date' }, isError: true };
|
|
77
129
|
}
|
|
78
130
|
|
|
79
131
|
const { session } = ctx;
|
|
@@ -99,7 +151,7 @@ export const createSprint: Handler = async (args, ctx) => {
|
|
|
99
151
|
});
|
|
100
152
|
|
|
101
153
|
if (!response.ok) {
|
|
102
|
-
|
|
154
|
+
return { result: { error: response.error || 'Failed to create sprint' }, isError: true };
|
|
103
155
|
}
|
|
104
156
|
|
|
105
157
|
return {
|
|
@@ -126,38 +178,19 @@ export const updateSprint: Handler = async (args, ctx) => {
|
|
|
126
178
|
auto_deploy_on_completion,
|
|
127
179
|
deploy_environment,
|
|
128
180
|
deploy_version_bump,
|
|
129
|
-
} = args
|
|
130
|
-
sprint_id: string;
|
|
131
|
-
title?: string;
|
|
132
|
-
goal?: string;
|
|
133
|
-
start_date?: string;
|
|
134
|
-
end_date?: string;
|
|
135
|
-
auto_deploy_on_completion?: boolean;
|
|
136
|
-
deploy_environment?: DeployEnvironment;
|
|
137
|
-
deploy_version_bump?: VersionBump;
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
141
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
142
|
-
|
|
143
|
-
if (deploy_environment) {
|
|
144
|
-
validateEnum(deploy_environment, DEPLOY_ENVIRONMENTS, 'deploy_environment');
|
|
145
|
-
}
|
|
146
|
-
if (deploy_version_bump) {
|
|
147
|
-
validateEnum(deploy_version_bump, VERSION_BUMPS, 'deploy_version_bump');
|
|
148
|
-
}
|
|
181
|
+
} = parseArgs(args, updateSprintSchema);
|
|
149
182
|
|
|
150
183
|
// Validate dates if provided
|
|
151
184
|
if (start_date) {
|
|
152
185
|
const startDateObj = new Date(start_date);
|
|
153
186
|
if (isNaN(startDateObj.getTime())) {
|
|
154
|
-
|
|
187
|
+
return { result: { error: 'Invalid start_date format. Use YYYY-MM-DD' }, isError: true };
|
|
155
188
|
}
|
|
156
189
|
}
|
|
157
190
|
if (end_date) {
|
|
158
191
|
const endDateObj = new Date(end_date);
|
|
159
192
|
if (isNaN(endDateObj.getTime())) {
|
|
160
|
-
|
|
193
|
+
return { result: { error: 'Invalid end_date format. Use YYYY-MM-DD' }, isError: true };
|
|
161
194
|
}
|
|
162
195
|
}
|
|
163
196
|
|
|
@@ -175,17 +208,14 @@ export const updateSprint: Handler = async (args, ctx) => {
|
|
|
175
208
|
});
|
|
176
209
|
|
|
177
210
|
if (!response.ok) {
|
|
178
|
-
|
|
211
|
+
return { result: { error: response.error || 'Failed to update sprint' }, isError: true };
|
|
179
212
|
}
|
|
180
213
|
|
|
181
214
|
return { result: { success: true, sprint_id } };
|
|
182
215
|
};
|
|
183
216
|
|
|
184
217
|
export const getSprint: Handler = async (args, ctx) => {
|
|
185
|
-
const { sprint_id, summary_only
|
|
186
|
-
|
|
187
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
188
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
218
|
+
const { sprint_id, summary_only } = parseArgs(args, getSprintSchema);
|
|
189
219
|
|
|
190
220
|
const apiClient = getApiClient();
|
|
191
221
|
|
|
@@ -220,26 +250,14 @@ export const getSprint: Handler = async (args, ctx) => {
|
|
|
220
250
|
}>('get_sprint', { sprint_id, summary_only });
|
|
221
251
|
|
|
222
252
|
if (!response.ok) {
|
|
223
|
-
|
|
253
|
+
return { result: { error: response.error || 'Failed to get sprint' }, isError: true };
|
|
224
254
|
}
|
|
225
255
|
|
|
226
256
|
return { result: response.data };
|
|
227
257
|
};
|
|
228
258
|
|
|
229
259
|
export const getSprints: Handler = async (args, ctx) => {
|
|
230
|
-
const { project_id, status, limit
|
|
231
|
-
project_id: string;
|
|
232
|
-
status?: SprintStatus;
|
|
233
|
-
limit?: number;
|
|
234
|
-
offset?: number;
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
validateRequired(project_id, 'project_id');
|
|
238
|
-
validateUUID(project_id, 'project_id');
|
|
239
|
-
|
|
240
|
-
if (status) {
|
|
241
|
-
validateEnum(status, SPRINT_STATUSES, 'status');
|
|
242
|
-
}
|
|
260
|
+
const { project_id, status, limit, offset } = parseArgs(args, getSprintsSchema);
|
|
243
261
|
|
|
244
262
|
const apiClient = getApiClient();
|
|
245
263
|
|
|
@@ -261,22 +279,19 @@ export const getSprints: Handler = async (args, ctx) => {
|
|
|
261
279
|
}>('get_sprints', {
|
|
262
280
|
project_id,
|
|
263
281
|
status,
|
|
264
|
-
limit: Math.min(limit, 100),
|
|
282
|
+
limit: Math.min(limit ?? 20, 100),
|
|
265
283
|
offset,
|
|
266
284
|
});
|
|
267
285
|
|
|
268
286
|
if (!response.ok) {
|
|
269
|
-
|
|
287
|
+
return { result: { error: response.error || 'Failed to fetch sprints' }, isError: true };
|
|
270
288
|
}
|
|
271
289
|
|
|
272
290
|
return { result: response.data };
|
|
273
291
|
};
|
|
274
292
|
|
|
275
293
|
export const deleteSprint: Handler = async (args, ctx) => {
|
|
276
|
-
const { sprint_id } = args
|
|
277
|
-
|
|
278
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
279
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
294
|
+
const { sprint_id } = parseArgs(args, deleteSprintSchema);
|
|
280
295
|
|
|
281
296
|
const apiClient = getApiClient();
|
|
282
297
|
|
|
@@ -285,17 +300,14 @@ export const deleteSprint: Handler = async (args, ctx) => {
|
|
|
285
300
|
});
|
|
286
301
|
|
|
287
302
|
if (!response.ok) {
|
|
288
|
-
|
|
303
|
+
return { result: { error: response.error || 'Failed to delete sprint' }, isError: true };
|
|
289
304
|
}
|
|
290
305
|
|
|
291
306
|
return { result: { success: true, message: 'Sprint deleted. Tasks are preserved.' } };
|
|
292
307
|
};
|
|
293
308
|
|
|
294
309
|
export const startSprint: Handler = async (args, ctx) => {
|
|
295
|
-
const { sprint_id } = args
|
|
296
|
-
|
|
297
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
298
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
310
|
+
const { sprint_id } = parseArgs(args, startSprintSchema);
|
|
299
311
|
|
|
300
312
|
const apiClient = getApiClient();
|
|
301
313
|
|
|
@@ -308,21 +320,14 @@ export const startSprint: Handler = async (args, ctx) => {
|
|
|
308
320
|
}>('start_sprint', { sprint_id });
|
|
309
321
|
|
|
310
322
|
if (!response.ok) {
|
|
311
|
-
|
|
323
|
+
return { result: { error: response.error || 'Failed to start sprint' }, isError: true };
|
|
312
324
|
}
|
|
313
325
|
|
|
314
326
|
return { result: response.data };
|
|
315
327
|
};
|
|
316
328
|
|
|
317
329
|
export const completeSprint: Handler = async (args, ctx) => {
|
|
318
|
-
const { sprint_id, retrospective_notes, skip_retrospective } = args
|
|
319
|
-
sprint_id: string;
|
|
320
|
-
retrospective_notes?: string;
|
|
321
|
-
skip_retrospective?: boolean;
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
325
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
330
|
+
const { sprint_id, retrospective_notes, skip_retrospective } = parseArgs(args, completeSprintSchema);
|
|
326
331
|
|
|
327
332
|
const apiClient = getApiClient();
|
|
328
333
|
|
|
@@ -341,31 +346,17 @@ export const completeSprint: Handler = async (args, ctx) => {
|
|
|
341
346
|
});
|
|
342
347
|
|
|
343
348
|
if (!response.ok) {
|
|
344
|
-
|
|
349
|
+
return { result: { error: response.error || 'Failed to complete sprint' }, isError: true };
|
|
345
350
|
}
|
|
346
351
|
|
|
347
352
|
return { result: response.data };
|
|
348
353
|
};
|
|
349
354
|
|
|
350
355
|
export const addTaskToSprint: Handler = async (args, ctx) => {
|
|
351
|
-
const { sprint_id, task_id, story_points, phase } = args
|
|
352
|
-
sprint_id: string;
|
|
353
|
-
task_id: string;
|
|
354
|
-
story_points?: number;
|
|
355
|
-
phase?: TaskPhase;
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
359
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
360
|
-
validateRequired(task_id, 'task_id');
|
|
361
|
-
validateUUID(task_id, 'task_id');
|
|
362
|
-
|
|
363
|
-
if (phase) {
|
|
364
|
-
validateEnum(phase, TASK_PHASES, 'phase');
|
|
365
|
-
}
|
|
356
|
+
const { sprint_id, task_id, story_points, phase } = parseArgs(args, addTaskToSprintSchema);
|
|
366
357
|
|
|
367
358
|
if (story_points !== undefined && (story_points < 0 || !Number.isInteger(story_points))) {
|
|
368
|
-
|
|
359
|
+
return { result: { error: 'story_points must be a non-negative integer' }, isError: true };
|
|
369
360
|
}
|
|
370
361
|
|
|
371
362
|
const apiClient = getApiClient();
|
|
@@ -385,22 +376,14 @@ export const addTaskToSprint: Handler = async (args, ctx) => {
|
|
|
385
376
|
});
|
|
386
377
|
|
|
387
378
|
if (!response.ok) {
|
|
388
|
-
|
|
379
|
+
return { result: { error: response.error || 'Failed to add task to sprint' }, isError: true };
|
|
389
380
|
}
|
|
390
381
|
|
|
391
382
|
return { result: response.data };
|
|
392
383
|
};
|
|
393
384
|
|
|
394
385
|
export const removeTaskFromSprint: Handler = async (args, ctx) => {
|
|
395
|
-
const { sprint_id, task_id } = args
|
|
396
|
-
sprint_id: string;
|
|
397
|
-
task_id: string;
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
401
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
402
|
-
validateRequired(task_id, 'task_id');
|
|
403
|
-
validateUUID(task_id, 'task_id');
|
|
386
|
+
const { sprint_id, task_id } = parseArgs(args, removeTaskFromSprintSchema);
|
|
404
387
|
|
|
405
388
|
const apiClient = getApiClient();
|
|
406
389
|
|
|
@@ -416,24 +399,14 @@ export const removeTaskFromSprint: Handler = async (args, ctx) => {
|
|
|
416
399
|
});
|
|
417
400
|
|
|
418
401
|
if (!response.ok) {
|
|
419
|
-
|
|
402
|
+
return { result: { error: response.error || 'Failed to remove task from sprint' }, isError: true };
|
|
420
403
|
}
|
|
421
404
|
|
|
422
405
|
return { result: response.data };
|
|
423
406
|
};
|
|
424
407
|
|
|
425
408
|
export const getSprintBacklog: Handler = async (args, ctx) => {
|
|
426
|
-
const { project_id, sprint_id } = args
|
|
427
|
-
project_id: string;
|
|
428
|
-
sprint_id?: string;
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
validateRequired(project_id, 'project_id');
|
|
432
|
-
validateUUID(project_id, 'project_id');
|
|
433
|
-
|
|
434
|
-
if (sprint_id) {
|
|
435
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
436
|
-
}
|
|
409
|
+
const { project_id, sprint_id } = parseArgs(args, getSprintBacklogSchema);
|
|
437
410
|
|
|
438
411
|
const apiClient = getApiClient();
|
|
439
412
|
|
|
@@ -453,20 +426,14 @@ export const getSprintBacklog: Handler = async (args, ctx) => {
|
|
|
453
426
|
});
|
|
454
427
|
|
|
455
428
|
if (!response.ok) {
|
|
456
|
-
|
|
429
|
+
return { result: { error: response.error || 'Failed to get sprint backlog' }, isError: true };
|
|
457
430
|
}
|
|
458
431
|
|
|
459
432
|
return { result: response.data };
|
|
460
433
|
};
|
|
461
434
|
|
|
462
435
|
export const getSprintVelocity: Handler = async (args, ctx) => {
|
|
463
|
-
const { project_id, limit
|
|
464
|
-
project_id: string;
|
|
465
|
-
limit?: number;
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
validateRequired(project_id, 'project_id');
|
|
469
|
-
validateUUID(project_id, 'project_id');
|
|
436
|
+
const { project_id, limit } = parseArgs(args, getSprintVelocitySchema);
|
|
470
437
|
|
|
471
438
|
const apiClient = getApiClient();
|
|
472
439
|
|
|
@@ -482,11 +449,11 @@ export const getSprintVelocity: Handler = async (args, ctx) => {
|
|
|
482
449
|
total_sprints: number;
|
|
483
450
|
}>('get_sprint_velocity', {
|
|
484
451
|
project_id,
|
|
485
|
-
limit: Math.min(limit, 50),
|
|
452
|
+
limit: Math.min(limit ?? 10, 50),
|
|
486
453
|
});
|
|
487
454
|
|
|
488
455
|
if (!response.ok) {
|
|
489
|
-
|
|
456
|
+
return { result: { error: response.error || 'Failed to get sprint velocity' }, isError: true };
|
|
490
457
|
}
|
|
491
458
|
|
|
492
459
|
return { result: response.data };
|
|
@@ -306,6 +306,7 @@ describe('updateTask', () => {
|
|
|
306
306
|
{
|
|
307
307
|
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
308
308
|
status: 'in_progress',
|
|
309
|
+
git_branch: 'feature/test-branch',
|
|
309
310
|
},
|
|
310
311
|
ctx
|
|
311
312
|
);
|
|
@@ -324,6 +325,7 @@ describe('updateTask', () => {
|
|
|
324
325
|
{
|
|
325
326
|
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
326
327
|
status: 'in_progress',
|
|
328
|
+
git_branch: 'feature/test-branch',
|
|
327
329
|
},
|
|
328
330
|
ctx
|
|
329
331
|
);
|
|
@@ -344,6 +346,7 @@ describe('updateTask', () => {
|
|
|
344
346
|
{
|
|
345
347
|
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
346
348
|
status: 'in_progress',
|
|
349
|
+
git_branch: 'feature/test-branch',
|
|
347
350
|
},
|
|
348
351
|
ctx
|
|
349
352
|
);
|
|
@@ -353,7 +356,42 @@ describe('updateTask', () => {
|
|
|
353
356
|
});
|
|
354
357
|
});
|
|
355
358
|
|
|
356
|
-
it('should
|
|
359
|
+
it('should return error when setting in_progress without git_branch', async () => {
|
|
360
|
+
const ctx = createMockContext();
|
|
361
|
+
const result = await updateTask(
|
|
362
|
+
{
|
|
363
|
+
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
364
|
+
status: 'in_progress',
|
|
365
|
+
},
|
|
366
|
+
ctx
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
expect(result.result).toMatchObject({
|
|
370
|
+
error: 'worktree_required',
|
|
371
|
+
message: expect.stringContaining('git_branch is required'),
|
|
372
|
+
hint: expect.stringContaining('Create a worktree'),
|
|
373
|
+
});
|
|
374
|
+
// Should NOT call the API when worktree requirement fails
|
|
375
|
+
expect(mockApiClient.updateTask).not.toHaveBeenCalled();
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('should return worktree_example in error response', async () => {
|
|
379
|
+
const ctx = createMockContext();
|
|
380
|
+
const result = await updateTask(
|
|
381
|
+
{
|
|
382
|
+
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
383
|
+
status: 'in_progress',
|
|
384
|
+
},
|
|
385
|
+
ctx
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
expect(result.result).toHaveProperty('worktree_example');
|
|
389
|
+
expect(result.result.worktree_example).toHaveProperty('command');
|
|
390
|
+
expect(result.result.worktree_example).toHaveProperty('then');
|
|
391
|
+
expect(result.result).toHaveProperty('skip_option');
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('should succeed when setting in_progress with git_branch', async () => {
|
|
357
395
|
mockApiClient.updateTask.mockResolvedValue({
|
|
358
396
|
ok: true,
|
|
359
397
|
data: { success: true },
|
|
@@ -364,18 +402,17 @@ describe('updateTask', () => {
|
|
|
364
402
|
{
|
|
365
403
|
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
366
404
|
status: 'in_progress',
|
|
405
|
+
git_branch: 'feature/my-task',
|
|
367
406
|
},
|
|
368
407
|
ctx
|
|
369
408
|
);
|
|
370
409
|
|
|
371
|
-
expect(result.result).toMatchObject({
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
hint: expect.stringContaining('update_task again'),
|
|
375
|
-
});
|
|
410
|
+
expect(result.result).toMatchObject({ success: true });
|
|
411
|
+
expect(result.result).not.toHaveProperty('error');
|
|
412
|
+
expect(mockApiClient.updateTask).toHaveBeenCalled();
|
|
376
413
|
});
|
|
377
414
|
|
|
378
|
-
it('should
|
|
415
|
+
it('should succeed when using skip_worktree_requirement without git_branch', async () => {
|
|
379
416
|
mockApiClient.updateTask.mockResolvedValue({
|
|
380
417
|
ok: true,
|
|
381
418
|
data: { success: true },
|
|
@@ -386,16 +423,17 @@ describe('updateTask', () => {
|
|
|
386
423
|
{
|
|
387
424
|
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
388
425
|
status: 'in_progress',
|
|
389
|
-
|
|
426
|
+
skip_worktree_requirement: true,
|
|
390
427
|
},
|
|
391
428
|
ctx
|
|
392
429
|
);
|
|
393
430
|
|
|
394
431
|
expect(result.result).toMatchObject({ success: true });
|
|
395
|
-
expect(result.result).not.toHaveProperty('
|
|
432
|
+
expect(result.result).not.toHaveProperty('error');
|
|
433
|
+
expect(mockApiClient.updateTask).toHaveBeenCalled();
|
|
396
434
|
});
|
|
397
435
|
|
|
398
|
-
it('should not
|
|
436
|
+
it('should not require git_branch when updating status other than in_progress', async () => {
|
|
399
437
|
mockApiClient.updateTask.mockResolvedValue({
|
|
400
438
|
ok: true,
|
|
401
439
|
data: { success: true },
|
|
@@ -411,7 +449,28 @@ describe('updateTask', () => {
|
|
|
411
449
|
);
|
|
412
450
|
|
|
413
451
|
expect(result.result).toMatchObject({ success: true });
|
|
414
|
-
expect(result.result).not.toHaveProperty('
|
|
452
|
+
expect(result.result).not.toHaveProperty('error');
|
|
453
|
+
expect(mockApiClient.updateTask).toHaveBeenCalled();
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('should not require git_branch when updating progress only', async () => {
|
|
457
|
+
mockApiClient.updateTask.mockResolvedValue({
|
|
458
|
+
ok: true,
|
|
459
|
+
data: { success: true },
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
const ctx = createMockContext();
|
|
463
|
+
const result = await updateTask(
|
|
464
|
+
{
|
|
465
|
+
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
466
|
+
progress_percentage: 50,
|
|
467
|
+
progress_note: 'Halfway done',
|
|
468
|
+
},
|
|
469
|
+
ctx
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
expect(result.result).toMatchObject({ success: true });
|
|
473
|
+
expect(mockApiClient.updateTask).toHaveBeenCalled();
|
|
415
474
|
});
|
|
416
475
|
});
|
|
417
476
|
|
|
@@ -480,16 +539,19 @@ describe('completeTask', () => {
|
|
|
480
539
|
);
|
|
481
540
|
});
|
|
482
541
|
|
|
483
|
-
it('should
|
|
542
|
+
it('should return error when API returns error', async () => {
|
|
484
543
|
mockApiClient.completeTask.mockResolvedValue({
|
|
485
544
|
ok: false,
|
|
486
545
|
error: 'Task not found',
|
|
487
546
|
});
|
|
488
547
|
|
|
489
548
|
const ctx = createMockContext();
|
|
490
|
-
await
|
|
491
|
-
|
|
492
|
-
).
|
|
549
|
+
const result = await completeTask({ task_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
550
|
+
|
|
551
|
+
expect(result.isError).toBe(true);
|
|
552
|
+
expect(result.result).toMatchObject({
|
|
553
|
+
error: 'Task not found',
|
|
554
|
+
});
|
|
493
555
|
});
|
|
494
556
|
});
|
|
495
557
|
|