@vibescope/mcp-server 0.1.0 → 0.2.1
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 +1 -1
- package/dist/api-client.d.ts +120 -2
- package/dist/api-client.js +51 -5
- package/dist/handlers/bodies-of-work.js +84 -50
- package/dist/handlers/cost.js +62 -54
- package/dist/handlers/decisions.js +29 -16
- package/dist/handlers/deployment.js +114 -107
- package/dist/handlers/discovery.d.ts +3 -0
- package/dist/handlers/discovery.js +55 -657
- package/dist/handlers/fallback.js +42 -28
- package/dist/handlers/file-checkouts.d.ts +18 -0
- package/dist/handlers/file-checkouts.js +101 -0
- package/dist/handlers/findings.d.ts +14 -1
- package/dist/handlers/findings.js +104 -28
- package/dist/handlers/git-issues.js +36 -32
- package/dist/handlers/ideas.js +44 -26
- package/dist/handlers/index.d.ts +2 -0
- package/dist/handlers/index.js +6 -0
- package/dist/handlers/milestones.js +34 -27
- package/dist/handlers/organizations.js +86 -78
- package/dist/handlers/progress.js +22 -11
- package/dist/handlers/project.js +62 -22
- package/dist/handlers/requests.js +15 -11
- package/dist/handlers/roles.d.ts +18 -0
- package/dist/handlers/roles.js +130 -0
- package/dist/handlers/session.js +52 -15
- package/dist/handlers/sprints.js +78 -65
- package/dist/handlers/tasks.js +135 -74
- package/dist/handlers/tool-docs.d.ts +4 -3
- package/dist/handlers/tool-docs.js +252 -5
- package/dist/handlers/validation.js +30 -14
- package/dist/index.js +25 -7
- package/dist/tools.js +417 -4
- package/package.json +1 -1
- package/src/api-client.ts +161 -8
- package/src/handlers/__test-setup__.ts +12 -0
- package/src/handlers/bodies-of-work.ts +127 -111
- package/src/handlers/cost.test.ts +34 -44
- package/src/handlers/cost.ts +77 -92
- package/src/handlers/decisions.test.ts +3 -2
- package/src/handlers/decisions.ts +32 -27
- package/src/handlers/deployment.ts +144 -190
- package/src/handlers/discovery.test.ts +4 -5
- package/src/handlers/discovery.ts +60 -746
- package/src/handlers/fallback.test.ts +78 -0
- package/src/handlers/fallback.ts +51 -38
- package/src/handlers/file-checkouts.test.ts +477 -0
- package/src/handlers/file-checkouts.ts +127 -0
- package/src/handlers/findings.test.ts +274 -2
- package/src/handlers/findings.ts +123 -57
- package/src/handlers/git-issues.ts +40 -80
- package/src/handlers/ideas.ts +56 -54
- package/src/handlers/index.ts +6 -0
- package/src/handlers/milestones.test.ts +1 -1
- package/src/handlers/milestones.ts +47 -45
- package/src/handlers/organizations.ts +104 -129
- package/src/handlers/progress.ts +24 -22
- package/src/handlers/project.ts +89 -57
- package/src/handlers/requests.ts +18 -14
- package/src/handlers/roles.test.ts +303 -0
- package/src/handlers/roles.ts +208 -0
- package/src/handlers/session.test.ts +37 -2
- package/src/handlers/session.ts +64 -21
- package/src/handlers/sprints.ts +114 -134
- package/src/handlers/tasks.test.ts +61 -0
- package/src/handlers/tasks.ts +170 -139
- package/src/handlers/tool-docs.ts +1024 -0
- package/src/handlers/validation.test.ts +53 -1
- package/src/handlers/validation.ts +32 -21
- package/src/index.ts +25 -7
- package/src/tools.ts +417 -4
- 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/knowledge.ts +0 -230
package/src/handlers/session.ts
CHANGED
|
@@ -10,23 +10,47 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { Handler, HandlerRegistry, TokenUsage } from './types.js';
|
|
13
|
-
import {
|
|
13
|
+
import { parseArgs, createEnumValidator } from '../validators.js';
|
|
14
14
|
import { getApiClient } from '../api-client.js';
|
|
15
15
|
|
|
16
|
+
const VALID_MODES = ['lite', 'full'] as const;
|
|
17
|
+
const VALID_MODELS = ['opus', 'sonnet', 'haiku'] as const;
|
|
18
|
+
const VALID_ROLES = ['developer', 'validator', 'deployer', 'reviewer', 'maintainer'] as const;
|
|
19
|
+
|
|
20
|
+
type SessionMode = typeof VALID_MODES[number];
|
|
21
|
+
type SessionModel = typeof VALID_MODELS[number];
|
|
22
|
+
type SessionRole = typeof VALID_ROLES[number];
|
|
23
|
+
|
|
24
|
+
// Argument schemas for type-safe parsing
|
|
25
|
+
const startWorkSessionSchema = {
|
|
26
|
+
project_id: { type: 'string' as const },
|
|
27
|
+
git_url: { type: 'string' as const },
|
|
28
|
+
mode: { type: 'string' as const, default: 'lite', validate: createEnumValidator(VALID_MODES) },
|
|
29
|
+
model: { type: 'string' as const, validate: createEnumValidator(VALID_MODELS) },
|
|
30
|
+
role: { type: 'string' as const, default: 'developer', validate: createEnumValidator(VALID_ROLES) },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const heartbeatSchema = {
|
|
34
|
+
session_id: { type: 'string' as const },
|
|
35
|
+
current_worktree_path: { type: 'string' as const },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const endWorkSessionSchema = {
|
|
39
|
+
session_id: { type: 'string' as const },
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const getHelpSchema = {
|
|
43
|
+
topic: { type: 'string' as const, required: true as const },
|
|
44
|
+
};
|
|
45
|
+
|
|
16
46
|
export const startWorkSession: Handler = async (args, ctx) => {
|
|
17
|
-
const { project_id, git_url, mode
|
|
18
|
-
project_id?: string;
|
|
19
|
-
git_url?: string;
|
|
20
|
-
mode?: 'lite' | 'full';
|
|
21
|
-
model?: 'opus' | 'sonnet' | 'haiku';
|
|
22
|
-
role?: 'developer' | 'validator' | 'deployer' | 'reviewer' | 'maintainer';
|
|
23
|
-
};
|
|
47
|
+
const { project_id, git_url, mode, model, role } = parseArgs(args, startWorkSessionSchema);
|
|
24
48
|
|
|
25
49
|
const { session, updateSession } = ctx;
|
|
26
50
|
|
|
27
51
|
// Reset token tracking for new session with model info
|
|
28
52
|
const normalizedModel = model ? model.toLowerCase().replace(/^claude[- ]*/i, '') : null;
|
|
29
|
-
const validModel = normalizedModel &&
|
|
53
|
+
const validModel = normalizedModel && VALID_MODELS.includes(normalizedModel as SessionModel)
|
|
30
54
|
? normalizedModel
|
|
31
55
|
: null;
|
|
32
56
|
|
|
@@ -53,9 +77,9 @@ export const startWorkSession: Handler = async (args, ctx) => {
|
|
|
53
77
|
const response = await apiClient.startSession({
|
|
54
78
|
project_id,
|
|
55
79
|
git_url,
|
|
56
|
-
mode,
|
|
57
|
-
model,
|
|
58
|
-
role
|
|
80
|
+
mode: mode as SessionMode,
|
|
81
|
+
model: model as SessionModel | undefined,
|
|
82
|
+
role: role as SessionRole
|
|
59
83
|
});
|
|
60
84
|
|
|
61
85
|
if (!response.ok) {
|
|
@@ -140,7 +164,7 @@ export const startWorkSession: Handler = async (args, ctx) => {
|
|
|
140
164
|
};
|
|
141
165
|
|
|
142
166
|
export const heartbeat: Handler = async (args, ctx) => {
|
|
143
|
-
const { session_id } = args
|
|
167
|
+
const { session_id, current_worktree_path } = parseArgs(args, heartbeatSchema);
|
|
144
168
|
const { session } = ctx;
|
|
145
169
|
const targetSession = session_id || session.currentSessionId;
|
|
146
170
|
|
|
@@ -154,8 +178,10 @@ export const heartbeat: Handler = async (args, ctx) => {
|
|
|
154
178
|
|
|
155
179
|
const apiClient = getApiClient();
|
|
156
180
|
|
|
157
|
-
// Send heartbeat
|
|
158
|
-
const heartbeatResponse = await apiClient.heartbeat(targetSession
|
|
181
|
+
// Send heartbeat with optional worktree path
|
|
182
|
+
const heartbeatResponse = await apiClient.heartbeat(targetSession, {
|
|
183
|
+
current_worktree_path,
|
|
184
|
+
});
|
|
159
185
|
|
|
160
186
|
if (!heartbeatResponse.ok) {
|
|
161
187
|
return {
|
|
@@ -182,7 +208,7 @@ export const heartbeat: Handler = async (args, ctx) => {
|
|
|
182
208
|
};
|
|
183
209
|
|
|
184
210
|
export const endWorkSession: Handler = async (args, ctx) => {
|
|
185
|
-
const { session_id } = args
|
|
211
|
+
const { session_id } = parseArgs(args, endWorkSessionSchema);
|
|
186
212
|
const { session, updateSession } = ctx;
|
|
187
213
|
const targetSession = session_id || session.currentSessionId;
|
|
188
214
|
|
|
@@ -247,19 +273,36 @@ export const endWorkSession: Handler = async (args, ctx) => {
|
|
|
247
273
|
};
|
|
248
274
|
|
|
249
275
|
export const getHelp: Handler = async (args, _ctx) => {
|
|
250
|
-
const { topic } = args
|
|
276
|
+
const { topic } = parseArgs(args, getHelpSchema);
|
|
277
|
+
|
|
278
|
+
const apiClient = getApiClient();
|
|
279
|
+
const response = await apiClient.getHelpTopic(topic);
|
|
280
|
+
|
|
281
|
+
if (!response.ok) {
|
|
282
|
+
// If database fetch fails, return error
|
|
283
|
+
return {
|
|
284
|
+
result: {
|
|
285
|
+
error: response.error || `Failed to fetch help topic: ${topic}`,
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!response.data) {
|
|
291
|
+
// Topic not found - fetch available topics
|
|
292
|
+
const topicsResponse = await apiClient.getHelpTopics();
|
|
293
|
+
const available = topicsResponse.ok && topicsResponse.data
|
|
294
|
+
? topicsResponse.data.map(t => t.slug)
|
|
295
|
+
: ['getting_started', 'tasks', 'validation', 'deployment', 'git', 'blockers', 'milestones', 'fallback', 'session', 'tokens', 'sprints', 'topics'];
|
|
251
296
|
|
|
252
|
-
const content = KNOWLEDGE_BASE[topic];
|
|
253
|
-
if (!content) {
|
|
254
297
|
return {
|
|
255
298
|
result: {
|
|
256
299
|
error: `Unknown topic: ${topic}`,
|
|
257
|
-
available
|
|
300
|
+
available,
|
|
258
301
|
},
|
|
259
302
|
};
|
|
260
303
|
}
|
|
261
304
|
|
|
262
|
-
return { result: { topic, content } };
|
|
305
|
+
return { result: { topic, content: response.data.content } };
|
|
263
306
|
};
|
|
264
307
|
|
|
265
308
|
// Model pricing rates (USD per 1M tokens)
|
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,29 +113,7 @@ 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);
|
|
@@ -126,26 +178,7 @@ 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) {
|
|
@@ -182,13 +215,11 @@ export const updateSprint: Handler = async (args, ctx) => {
|
|
|
182
215
|
};
|
|
183
216
|
|
|
184
217
|
export const getSprint: Handler = async (args, ctx) => {
|
|
185
|
-
const { sprint_id } = args
|
|
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
|
|
|
222
|
+
// Response type varies based on summary_only
|
|
192
223
|
const response = await apiClient.proxy<{
|
|
193
224
|
id: string;
|
|
194
225
|
title: string;
|
|
@@ -200,11 +231,23 @@ export const getSprint: Handler = async (args, ctx) => {
|
|
|
200
231
|
progress_percentage: number;
|
|
201
232
|
velocity_points: number;
|
|
202
233
|
committed_points: number;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
234
|
+
// Full response includes task arrays
|
|
235
|
+
pre_tasks?: unknown[];
|
|
236
|
+
core_tasks?: unknown[];
|
|
237
|
+
post_tasks?: unknown[];
|
|
238
|
+
total_tasks?: number;
|
|
239
|
+
// Summary response includes counts and next task
|
|
240
|
+
task_counts?: {
|
|
241
|
+
pre: { total: number; completed: number };
|
|
242
|
+
core: { total: number; completed: number };
|
|
243
|
+
post: { total: number; completed: number };
|
|
244
|
+
total: number;
|
|
245
|
+
completed: number;
|
|
246
|
+
in_progress: number;
|
|
247
|
+
};
|
|
248
|
+
current_task?: { id: string; title: string } | null;
|
|
249
|
+
next_task?: { id: string; title: string; priority: number } | null;
|
|
250
|
+
}>('get_sprint', { sprint_id, summary_only });
|
|
208
251
|
|
|
209
252
|
if (!response.ok) {
|
|
210
253
|
throw new Error(`Failed to get sprint: ${response.error}`);
|
|
@@ -214,19 +257,7 @@ export const getSprint: Handler = async (args, ctx) => {
|
|
|
214
257
|
};
|
|
215
258
|
|
|
216
259
|
export const getSprints: Handler = async (args, ctx) => {
|
|
217
|
-
const { project_id, status, limit
|
|
218
|
-
project_id: string;
|
|
219
|
-
status?: SprintStatus;
|
|
220
|
-
limit?: number;
|
|
221
|
-
offset?: number;
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
validateRequired(project_id, 'project_id');
|
|
225
|
-
validateUUID(project_id, 'project_id');
|
|
226
|
-
|
|
227
|
-
if (status) {
|
|
228
|
-
validateEnum(status, SPRINT_STATUSES, 'status');
|
|
229
|
-
}
|
|
260
|
+
const { project_id, status, limit, offset } = parseArgs(args, getSprintsSchema);
|
|
230
261
|
|
|
231
262
|
const apiClient = getApiClient();
|
|
232
263
|
|
|
@@ -248,7 +279,7 @@ export const getSprints: Handler = async (args, ctx) => {
|
|
|
248
279
|
}>('get_sprints', {
|
|
249
280
|
project_id,
|
|
250
281
|
status,
|
|
251
|
-
limit: Math.min(limit, 100),
|
|
282
|
+
limit: Math.min(limit ?? 20, 100),
|
|
252
283
|
offset,
|
|
253
284
|
});
|
|
254
285
|
|
|
@@ -260,10 +291,7 @@ export const getSprints: Handler = async (args, ctx) => {
|
|
|
260
291
|
};
|
|
261
292
|
|
|
262
293
|
export const deleteSprint: Handler = async (args, ctx) => {
|
|
263
|
-
const { sprint_id } = args
|
|
264
|
-
|
|
265
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
266
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
294
|
+
const { sprint_id } = parseArgs(args, deleteSprintSchema);
|
|
267
295
|
|
|
268
296
|
const apiClient = getApiClient();
|
|
269
297
|
|
|
@@ -279,10 +307,7 @@ export const deleteSprint: Handler = async (args, ctx) => {
|
|
|
279
307
|
};
|
|
280
308
|
|
|
281
309
|
export const startSprint: Handler = async (args, ctx) => {
|
|
282
|
-
const { sprint_id } = args
|
|
283
|
-
|
|
284
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
285
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
310
|
+
const { sprint_id } = parseArgs(args, startSprintSchema);
|
|
286
311
|
|
|
287
312
|
const apiClient = getApiClient();
|
|
288
313
|
|
|
@@ -302,14 +327,7 @@ export const startSprint: Handler = async (args, ctx) => {
|
|
|
302
327
|
};
|
|
303
328
|
|
|
304
329
|
export const completeSprint: Handler = async (args, ctx) => {
|
|
305
|
-
const { sprint_id, retrospective_notes, skip_retrospective } = args
|
|
306
|
-
sprint_id: string;
|
|
307
|
-
retrospective_notes?: string;
|
|
308
|
-
skip_retrospective?: boolean;
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
312
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
330
|
+
const { sprint_id, retrospective_notes, skip_retrospective } = parseArgs(args, completeSprintSchema);
|
|
313
331
|
|
|
314
332
|
const apiClient = getApiClient();
|
|
315
333
|
|
|
@@ -335,21 +353,7 @@ export const completeSprint: Handler = async (args, ctx) => {
|
|
|
335
353
|
};
|
|
336
354
|
|
|
337
355
|
export const addTaskToSprint: Handler = async (args, ctx) => {
|
|
338
|
-
const { sprint_id, task_id, story_points, phase } = args
|
|
339
|
-
sprint_id: string;
|
|
340
|
-
task_id: string;
|
|
341
|
-
story_points?: number;
|
|
342
|
-
phase?: TaskPhase;
|
|
343
|
-
};
|
|
344
|
-
|
|
345
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
346
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
347
|
-
validateRequired(task_id, 'task_id');
|
|
348
|
-
validateUUID(task_id, 'task_id');
|
|
349
|
-
|
|
350
|
-
if (phase) {
|
|
351
|
-
validateEnum(phase, TASK_PHASES, 'phase');
|
|
352
|
-
}
|
|
356
|
+
const { sprint_id, task_id, story_points, phase } = parseArgs(args, addTaskToSprintSchema);
|
|
353
357
|
|
|
354
358
|
if (story_points !== undefined && (story_points < 0 || !Number.isInteger(story_points))) {
|
|
355
359
|
throw new Error('story_points must be a non-negative integer');
|
|
@@ -379,15 +383,7 @@ export const addTaskToSprint: Handler = async (args, ctx) => {
|
|
|
379
383
|
};
|
|
380
384
|
|
|
381
385
|
export const removeTaskFromSprint: Handler = async (args, ctx) => {
|
|
382
|
-
const { sprint_id, task_id } = args
|
|
383
|
-
sprint_id: string;
|
|
384
|
-
task_id: string;
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
388
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
389
|
-
validateRequired(task_id, 'task_id');
|
|
390
|
-
validateUUID(task_id, 'task_id');
|
|
386
|
+
const { sprint_id, task_id } = parseArgs(args, removeTaskFromSprintSchema);
|
|
391
387
|
|
|
392
388
|
const apiClient = getApiClient();
|
|
393
389
|
|
|
@@ -410,17 +406,7 @@ export const removeTaskFromSprint: Handler = async (args, ctx) => {
|
|
|
410
406
|
};
|
|
411
407
|
|
|
412
408
|
export const getSprintBacklog: Handler = async (args, ctx) => {
|
|
413
|
-
const { project_id, sprint_id } = args
|
|
414
|
-
project_id: string;
|
|
415
|
-
sprint_id?: string;
|
|
416
|
-
};
|
|
417
|
-
|
|
418
|
-
validateRequired(project_id, 'project_id');
|
|
419
|
-
validateUUID(project_id, 'project_id');
|
|
420
|
-
|
|
421
|
-
if (sprint_id) {
|
|
422
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
423
|
-
}
|
|
409
|
+
const { project_id, sprint_id } = parseArgs(args, getSprintBacklogSchema);
|
|
424
410
|
|
|
425
411
|
const apiClient = getApiClient();
|
|
426
412
|
|
|
@@ -447,13 +433,7 @@ export const getSprintBacklog: Handler = async (args, ctx) => {
|
|
|
447
433
|
};
|
|
448
434
|
|
|
449
435
|
export const getSprintVelocity: Handler = async (args, ctx) => {
|
|
450
|
-
const { project_id, limit
|
|
451
|
-
project_id: string;
|
|
452
|
-
limit?: number;
|
|
453
|
-
};
|
|
454
|
-
|
|
455
|
-
validateRequired(project_id, 'project_id');
|
|
456
|
-
validateUUID(project_id, 'project_id');
|
|
436
|
+
const { project_id, limit } = parseArgs(args, getSprintVelocitySchema);
|
|
457
437
|
|
|
458
438
|
const apiClient = getApiClient();
|
|
459
439
|
|
|
@@ -469,7 +449,7 @@ export const getSprintVelocity: Handler = async (args, ctx) => {
|
|
|
469
449
|
total_sprints: number;
|
|
470
450
|
}>('get_sprint_velocity', {
|
|
471
451
|
project_id,
|
|
472
|
-
limit: Math.min(limit, 50),
|
|
452
|
+
limit: Math.min(limit ?? 10, 50),
|
|
473
453
|
});
|
|
474
454
|
|
|
475
455
|
if (!response.ok) {
|
|
@@ -352,6 +352,67 @@ describe('updateTask', () => {
|
|
|
352
352
|
error: 'task_claimed',
|
|
353
353
|
});
|
|
354
354
|
});
|
|
355
|
+
|
|
356
|
+
it('should warn when setting in_progress without git_branch', async () => {
|
|
357
|
+
mockApiClient.updateTask.mockResolvedValue({
|
|
358
|
+
ok: true,
|
|
359
|
+
data: { success: true },
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
const ctx = createMockContext();
|
|
363
|
+
const result = await updateTask(
|
|
364
|
+
{
|
|
365
|
+
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
366
|
+
status: 'in_progress',
|
|
367
|
+
},
|
|
368
|
+
ctx
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
expect(result.result).toMatchObject({
|
|
372
|
+
success: true,
|
|
373
|
+
warning: expect.stringContaining('git_branch not set'),
|
|
374
|
+
hint: expect.stringContaining('update_task again'),
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('should not warn when setting in_progress with git_branch', async () => {
|
|
379
|
+
mockApiClient.updateTask.mockResolvedValue({
|
|
380
|
+
ok: true,
|
|
381
|
+
data: { success: true },
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const ctx = createMockContext();
|
|
385
|
+
const result = await updateTask(
|
|
386
|
+
{
|
|
387
|
+
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
388
|
+
status: 'in_progress',
|
|
389
|
+
git_branch: 'feature/my-task',
|
|
390
|
+
},
|
|
391
|
+
ctx
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
expect(result.result).toMatchObject({ success: true });
|
|
395
|
+
expect(result.result).not.toHaveProperty('warning');
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('should not warn when updating status other than in_progress', async () => {
|
|
399
|
+
mockApiClient.updateTask.mockResolvedValue({
|
|
400
|
+
ok: true,
|
|
401
|
+
data: { success: true },
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
const ctx = createMockContext();
|
|
405
|
+
const result = await updateTask(
|
|
406
|
+
{
|
|
407
|
+
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
408
|
+
status: 'completed',
|
|
409
|
+
},
|
|
410
|
+
ctx
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
expect(result.result).toMatchObject({ success: true });
|
|
414
|
+
expect(result.result).not.toHaveProperty('warning');
|
|
415
|
+
});
|
|
355
416
|
});
|
|
356
417
|
|
|
357
418
|
// ============================================================================
|