@vibescope/mcp-server 0.2.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/dist/api-client.d.ts +64 -1
- package/dist/api-client.js +34 -3
- package/dist/handlers/bodies-of-work.js +82 -49
- package/dist/handlers/cost.js +62 -54
- package/dist/handlers/decisions.js +29 -16
- package/dist/handlers/deployment.js +112 -106
- package/dist/handlers/discovery.js +35 -5
- package/dist/handlers/fallback.js +24 -19
- package/dist/handlers/file-checkouts.d.ts +18 -0
- package/dist/handlers/file-checkouts.js +101 -0
- package/dist/handlers/findings.d.ts +6 -0
- package/dist/handlers/findings.js +85 -30
- 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 +30 -8
- package/dist/handlers/sprints.js +76 -64
- package/dist/handlers/tasks.js +113 -73
- package/dist/handlers/validation.js +18 -14
- package/dist/tools.js +387 -0
- package/package.json +1 -1
- package/src/api-client.ts +89 -6
- package/src/handlers/__test-setup__.ts +7 -0
- package/src/handlers/bodies-of-work.ts +101 -101
- 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 +142 -190
- package/src/handlers/discovery.test.ts +4 -5
- package/src/handlers/discovery.ts +37 -6
- package/src/handlers/fallback.ts +31 -29
- package/src/handlers/file-checkouts.test.ts +477 -0
- package/src/handlers/file-checkouts.ts +127 -0
- package/src/handlers/findings.test.ts +145 -0
- package/src/handlers/findings.ts +101 -64
- 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.ts +39 -17
- package/src/handlers/sprints.ts +96 -129
- package/src/handlers/tasks.ts +144 -138
- package/src/handlers/validation.test.ts +1 -1
- package/src/handlers/validation.ts +20 -22
- package/src/tools.ts +387 -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/dist/handlers/session.js
CHANGED
|
@@ -8,13 +8,35 @@
|
|
|
8
8
|
* - get_help
|
|
9
9
|
* - get_token_usage
|
|
10
10
|
*/
|
|
11
|
+
import { parseArgs, createEnumValidator } from '../validators.js';
|
|
11
12
|
import { getApiClient } from '../api-client.js';
|
|
13
|
+
const VALID_MODES = ['lite', 'full'];
|
|
14
|
+
const VALID_MODELS = ['opus', 'sonnet', 'haiku'];
|
|
15
|
+
const VALID_ROLES = ['developer', 'validator', 'deployer', 'reviewer', 'maintainer'];
|
|
16
|
+
// Argument schemas for type-safe parsing
|
|
17
|
+
const startWorkSessionSchema = {
|
|
18
|
+
project_id: { type: 'string' },
|
|
19
|
+
git_url: { type: 'string' },
|
|
20
|
+
mode: { type: 'string', default: 'lite', validate: createEnumValidator(VALID_MODES) },
|
|
21
|
+
model: { type: 'string', validate: createEnumValidator(VALID_MODELS) },
|
|
22
|
+
role: { type: 'string', default: 'developer', validate: createEnumValidator(VALID_ROLES) },
|
|
23
|
+
};
|
|
24
|
+
const heartbeatSchema = {
|
|
25
|
+
session_id: { type: 'string' },
|
|
26
|
+
current_worktree_path: { type: 'string' },
|
|
27
|
+
};
|
|
28
|
+
const endWorkSessionSchema = {
|
|
29
|
+
session_id: { type: 'string' },
|
|
30
|
+
};
|
|
31
|
+
const getHelpSchema = {
|
|
32
|
+
topic: { type: 'string', required: true },
|
|
33
|
+
};
|
|
12
34
|
export const startWorkSession = async (args, ctx) => {
|
|
13
|
-
const { project_id, git_url, mode
|
|
35
|
+
const { project_id, git_url, mode, model, role } = parseArgs(args, startWorkSessionSchema);
|
|
14
36
|
const { session, updateSession } = ctx;
|
|
15
37
|
// Reset token tracking for new session with model info
|
|
16
38
|
const normalizedModel = model ? model.toLowerCase().replace(/^claude[- ]*/i, '') : null;
|
|
17
|
-
const validModel = normalizedModel &&
|
|
39
|
+
const validModel = normalizedModel && VALID_MODELS.includes(normalizedModel)
|
|
18
40
|
? normalizedModel
|
|
19
41
|
: null;
|
|
20
42
|
updateSession({
|
|
@@ -38,9 +60,9 @@ export const startWorkSession = async (args, ctx) => {
|
|
|
38
60
|
const response = await apiClient.startSession({
|
|
39
61
|
project_id,
|
|
40
62
|
git_url,
|
|
41
|
-
mode,
|
|
42
|
-
model,
|
|
43
|
-
role
|
|
63
|
+
mode: mode,
|
|
64
|
+
model: model,
|
|
65
|
+
role: role
|
|
44
66
|
});
|
|
45
67
|
if (!response.ok) {
|
|
46
68
|
return {
|
|
@@ -113,7 +135,7 @@ export const startWorkSession = async (args, ctx) => {
|
|
|
113
135
|
return { result };
|
|
114
136
|
};
|
|
115
137
|
export const heartbeat = async (args, ctx) => {
|
|
116
|
-
const { session_id, current_worktree_path } = args;
|
|
138
|
+
const { session_id, current_worktree_path } = parseArgs(args, heartbeatSchema);
|
|
117
139
|
const { session } = ctx;
|
|
118
140
|
const targetSession = session_id || session.currentSessionId;
|
|
119
141
|
if (!targetSession) {
|
|
@@ -150,7 +172,7 @@ export const heartbeat = async (args, ctx) => {
|
|
|
150
172
|
};
|
|
151
173
|
};
|
|
152
174
|
export const endWorkSession = async (args, ctx) => {
|
|
153
|
-
const { session_id } = args;
|
|
175
|
+
const { session_id } = parseArgs(args, endWorkSessionSchema);
|
|
154
176
|
const { session, updateSession } = ctx;
|
|
155
177
|
const targetSession = session_id || session.currentSessionId;
|
|
156
178
|
if (!targetSession) {
|
|
@@ -205,7 +227,7 @@ export const endWorkSession = async (args, ctx) => {
|
|
|
205
227
|
};
|
|
206
228
|
};
|
|
207
229
|
export const getHelp = async (args, _ctx) => {
|
|
208
|
-
const { topic } = args;
|
|
230
|
+
const { topic } = parseArgs(args, getHelpSchema);
|
|
209
231
|
const apiClient = getApiClient();
|
|
210
232
|
const response = await apiClient.getHelpTopic(topic);
|
|
211
233
|
if (!response.ok) {
|
package/dist/handlers/sprints.js
CHANGED
|
@@ -14,25 +14,76 @@
|
|
|
14
14
|
* - get_sprint_backlog
|
|
15
15
|
* - get_sprint_velocity
|
|
16
16
|
*/
|
|
17
|
-
import {
|
|
17
|
+
import { parseArgs, uuidValidator, createEnumValidator } from '../validators.js';
|
|
18
18
|
import { getApiClient } from '../api-client.js';
|
|
19
19
|
const SPRINT_STATUSES = ['planning', 'active', 'in_review', 'retrospective', 'completed', 'cancelled'];
|
|
20
20
|
const TASK_PHASES = ['pre', 'core', 'post'];
|
|
21
21
|
const DEPLOY_ENVIRONMENTS = ['development', 'staging', 'production'];
|
|
22
22
|
const VERSION_BUMPS = ['patch', 'minor', 'major'];
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Argument Schemas
|
|
25
|
+
// ============================================================================
|
|
26
|
+
const createSprintSchema = {
|
|
27
|
+
project_id: { type: 'string', required: true, validate: uuidValidator },
|
|
28
|
+
title: { type: 'string', required: true },
|
|
29
|
+
goal: { type: 'string' },
|
|
30
|
+
start_date: { type: 'string', required: true },
|
|
31
|
+
end_date: { type: 'string', required: true },
|
|
32
|
+
auto_deploy_on_completion: { type: 'boolean' },
|
|
33
|
+
deploy_environment: { type: 'string', validate: createEnumValidator(DEPLOY_ENVIRONMENTS) },
|
|
34
|
+
deploy_version_bump: { type: 'string', validate: createEnumValidator(VERSION_BUMPS) },
|
|
35
|
+
};
|
|
36
|
+
const updateSprintSchema = {
|
|
37
|
+
sprint_id: { type: 'string', required: true, validate: uuidValidator },
|
|
38
|
+
title: { type: 'string' },
|
|
39
|
+
goal: { type: 'string' },
|
|
40
|
+
start_date: { type: 'string' },
|
|
41
|
+
end_date: { type: 'string' },
|
|
42
|
+
auto_deploy_on_completion: { type: 'boolean' },
|
|
43
|
+
deploy_environment: { type: 'string', validate: createEnumValidator(DEPLOY_ENVIRONMENTS) },
|
|
44
|
+
deploy_version_bump: { type: 'string', validate: createEnumValidator(VERSION_BUMPS) },
|
|
45
|
+
};
|
|
46
|
+
const getSprintSchema = {
|
|
47
|
+
sprint_id: { type: 'string', required: true, validate: uuidValidator },
|
|
48
|
+
summary_only: { type: 'boolean', default: false },
|
|
49
|
+
};
|
|
50
|
+
const getSprintsSchema = {
|
|
51
|
+
project_id: { type: 'string', required: true, validate: uuidValidator },
|
|
52
|
+
status: { type: 'string', validate: createEnumValidator(SPRINT_STATUSES) },
|
|
53
|
+
limit: { type: 'number', default: 20 },
|
|
54
|
+
offset: { type: 'number', default: 0 },
|
|
55
|
+
};
|
|
56
|
+
const deleteSprintSchema = {
|
|
57
|
+
sprint_id: { type: 'string', required: true, validate: uuidValidator },
|
|
58
|
+
};
|
|
59
|
+
const startSprintSchema = {
|
|
60
|
+
sprint_id: { type: 'string', required: true, validate: uuidValidator },
|
|
61
|
+
};
|
|
62
|
+
const completeSprintSchema = {
|
|
63
|
+
sprint_id: { type: 'string', required: true, validate: uuidValidator },
|
|
64
|
+
retrospective_notes: { type: 'string' },
|
|
65
|
+
skip_retrospective: { type: 'boolean' },
|
|
66
|
+
};
|
|
67
|
+
const addTaskToSprintSchema = {
|
|
68
|
+
sprint_id: { type: 'string', required: true, validate: uuidValidator },
|
|
69
|
+
task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
70
|
+
story_points: { type: 'number' },
|
|
71
|
+
phase: { type: 'string', validate: createEnumValidator(TASK_PHASES) },
|
|
72
|
+
};
|
|
73
|
+
const removeTaskFromSprintSchema = {
|
|
74
|
+
sprint_id: { type: 'string', required: true, validate: uuidValidator },
|
|
75
|
+
task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
76
|
+
};
|
|
77
|
+
const getSprintBacklogSchema = {
|
|
78
|
+
project_id: { type: 'string', required: true, validate: uuidValidator },
|
|
79
|
+
sprint_id: { type: 'string', validate: uuidValidator },
|
|
80
|
+
};
|
|
81
|
+
const getSprintVelocitySchema = {
|
|
82
|
+
project_id: { type: 'string', required: true, validate: uuidValidator },
|
|
83
|
+
limit: { type: 'number', default: 10 },
|
|
84
|
+
};
|
|
23
85
|
export const createSprint = async (args, ctx) => {
|
|
24
|
-
const { project_id, title, goal, start_date, end_date, auto_deploy_on_completion, deploy_environment, deploy_version_bump, } = args;
|
|
25
|
-
validateRequired(project_id, 'project_id');
|
|
26
|
-
validateUUID(project_id, 'project_id');
|
|
27
|
-
validateRequired(title, 'title');
|
|
28
|
-
validateRequired(start_date, 'start_date');
|
|
29
|
-
validateRequired(end_date, 'end_date');
|
|
30
|
-
if (deploy_environment) {
|
|
31
|
-
validateEnum(deploy_environment, DEPLOY_ENVIRONMENTS, 'deploy_environment');
|
|
32
|
-
}
|
|
33
|
-
if (deploy_version_bump) {
|
|
34
|
-
validateEnum(deploy_version_bump, VERSION_BUMPS, 'deploy_version_bump');
|
|
35
|
-
}
|
|
86
|
+
const { project_id, title, goal, start_date, end_date, auto_deploy_on_completion, deploy_environment, deploy_version_bump, } = parseArgs(args, createSprintSchema);
|
|
36
87
|
// Validate date format and order
|
|
37
88
|
const startDateObj = new Date(start_date);
|
|
38
89
|
const endDateObj = new Date(end_date);
|
|
@@ -78,15 +129,7 @@ export const createSprint = async (args, ctx) => {
|
|
|
78
129
|
};
|
|
79
130
|
};
|
|
80
131
|
export const updateSprint = async (args, ctx) => {
|
|
81
|
-
const { sprint_id, title, goal, start_date, end_date, auto_deploy_on_completion, deploy_environment, deploy_version_bump, } = args;
|
|
82
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
83
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
84
|
-
if (deploy_environment) {
|
|
85
|
-
validateEnum(deploy_environment, DEPLOY_ENVIRONMENTS, 'deploy_environment');
|
|
86
|
-
}
|
|
87
|
-
if (deploy_version_bump) {
|
|
88
|
-
validateEnum(deploy_version_bump, VERSION_BUMPS, 'deploy_version_bump');
|
|
89
|
-
}
|
|
132
|
+
const { sprint_id, title, goal, start_date, end_date, auto_deploy_on_completion, deploy_environment, deploy_version_bump, } = parseArgs(args, updateSprintSchema);
|
|
90
133
|
// Validate dates if provided
|
|
91
134
|
if (start_date) {
|
|
92
135
|
const startDateObj = new Date(start_date);
|
|
@@ -117,9 +160,7 @@ export const updateSprint = async (args, ctx) => {
|
|
|
117
160
|
return { result: { success: true, sprint_id } };
|
|
118
161
|
};
|
|
119
162
|
export const getSprint = async (args, ctx) => {
|
|
120
|
-
const { sprint_id, summary_only
|
|
121
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
122
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
163
|
+
const { sprint_id, summary_only } = parseArgs(args, getSprintSchema);
|
|
123
164
|
const apiClient = getApiClient();
|
|
124
165
|
// Response type varies based on summary_only
|
|
125
166
|
const response = await apiClient.proxy('get_sprint', { sprint_id, summary_only });
|
|
@@ -129,17 +170,12 @@ export const getSprint = async (args, ctx) => {
|
|
|
129
170
|
return { result: response.data };
|
|
130
171
|
};
|
|
131
172
|
export const getSprints = async (args, ctx) => {
|
|
132
|
-
const { project_id, status, limit
|
|
133
|
-
validateRequired(project_id, 'project_id');
|
|
134
|
-
validateUUID(project_id, 'project_id');
|
|
135
|
-
if (status) {
|
|
136
|
-
validateEnum(status, SPRINT_STATUSES, 'status');
|
|
137
|
-
}
|
|
173
|
+
const { project_id, status, limit, offset } = parseArgs(args, getSprintsSchema);
|
|
138
174
|
const apiClient = getApiClient();
|
|
139
175
|
const response = await apiClient.proxy('get_sprints', {
|
|
140
176
|
project_id,
|
|
141
177
|
status,
|
|
142
|
-
limit: Math.min(limit, 100),
|
|
178
|
+
limit: Math.min(limit ?? 20, 100),
|
|
143
179
|
offset,
|
|
144
180
|
});
|
|
145
181
|
if (!response.ok) {
|
|
@@ -148,9 +184,7 @@ export const getSprints = async (args, ctx) => {
|
|
|
148
184
|
return { result: response.data };
|
|
149
185
|
};
|
|
150
186
|
export const deleteSprint = async (args, ctx) => {
|
|
151
|
-
const { sprint_id } = args;
|
|
152
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
153
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
187
|
+
const { sprint_id } = parseArgs(args, deleteSprintSchema);
|
|
154
188
|
const apiClient = getApiClient();
|
|
155
189
|
const response = await apiClient.proxy('delete_sprint', {
|
|
156
190
|
sprint_id
|
|
@@ -161,9 +195,7 @@ export const deleteSprint = async (args, ctx) => {
|
|
|
161
195
|
return { result: { success: true, message: 'Sprint deleted. Tasks are preserved.' } };
|
|
162
196
|
};
|
|
163
197
|
export const startSprint = async (args, ctx) => {
|
|
164
|
-
const { sprint_id } = args;
|
|
165
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
166
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
198
|
+
const { sprint_id } = parseArgs(args, startSprintSchema);
|
|
167
199
|
const apiClient = getApiClient();
|
|
168
200
|
const response = await apiClient.proxy('start_sprint', { sprint_id });
|
|
169
201
|
if (!response.ok) {
|
|
@@ -172,9 +204,7 @@ export const startSprint = async (args, ctx) => {
|
|
|
172
204
|
return { result: response.data };
|
|
173
205
|
};
|
|
174
206
|
export const completeSprint = async (args, ctx) => {
|
|
175
|
-
const { sprint_id, retrospective_notes, skip_retrospective } = args;
|
|
176
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
177
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
207
|
+
const { sprint_id, retrospective_notes, skip_retrospective } = parseArgs(args, completeSprintSchema);
|
|
178
208
|
const apiClient = getApiClient();
|
|
179
209
|
const response = await apiClient.proxy('complete_sprint', {
|
|
180
210
|
sprint_id,
|
|
@@ -187,14 +217,7 @@ export const completeSprint = async (args, ctx) => {
|
|
|
187
217
|
return { result: response.data };
|
|
188
218
|
};
|
|
189
219
|
export const addTaskToSprint = async (args, ctx) => {
|
|
190
|
-
const { sprint_id, task_id, story_points, phase } = args;
|
|
191
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
192
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
193
|
-
validateRequired(task_id, 'task_id');
|
|
194
|
-
validateUUID(task_id, 'task_id');
|
|
195
|
-
if (phase) {
|
|
196
|
-
validateEnum(phase, TASK_PHASES, 'phase');
|
|
197
|
-
}
|
|
220
|
+
const { sprint_id, task_id, story_points, phase } = parseArgs(args, addTaskToSprintSchema);
|
|
198
221
|
if (story_points !== undefined && (story_points < 0 || !Number.isInteger(story_points))) {
|
|
199
222
|
throw new Error('story_points must be a non-negative integer');
|
|
200
223
|
}
|
|
@@ -211,11 +234,7 @@ export const addTaskToSprint = async (args, ctx) => {
|
|
|
211
234
|
return { result: response.data };
|
|
212
235
|
};
|
|
213
236
|
export const removeTaskFromSprint = async (args, ctx) => {
|
|
214
|
-
const { sprint_id, task_id } = args;
|
|
215
|
-
validateRequired(sprint_id, 'sprint_id');
|
|
216
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
217
|
-
validateRequired(task_id, 'task_id');
|
|
218
|
-
validateUUID(task_id, 'task_id');
|
|
237
|
+
const { sprint_id, task_id } = parseArgs(args, removeTaskFromSprintSchema);
|
|
219
238
|
const apiClient = getApiClient();
|
|
220
239
|
const response = await apiClient.proxy('remove_task_from_sprint', {
|
|
221
240
|
sprint_id,
|
|
@@ -227,12 +246,7 @@ export const removeTaskFromSprint = async (args, ctx) => {
|
|
|
227
246
|
return { result: response.data };
|
|
228
247
|
};
|
|
229
248
|
export const getSprintBacklog = async (args, ctx) => {
|
|
230
|
-
const { project_id, sprint_id } = args;
|
|
231
|
-
validateRequired(project_id, 'project_id');
|
|
232
|
-
validateUUID(project_id, 'project_id');
|
|
233
|
-
if (sprint_id) {
|
|
234
|
-
validateUUID(sprint_id, 'sprint_id');
|
|
235
|
-
}
|
|
249
|
+
const { project_id, sprint_id } = parseArgs(args, getSprintBacklogSchema);
|
|
236
250
|
const apiClient = getApiClient();
|
|
237
251
|
const response = await apiClient.proxy('get_sprint_backlog', {
|
|
238
252
|
project_id,
|
|
@@ -244,13 +258,11 @@ export const getSprintBacklog = async (args, ctx) => {
|
|
|
244
258
|
return { result: response.data };
|
|
245
259
|
};
|
|
246
260
|
export const getSprintVelocity = async (args, ctx) => {
|
|
247
|
-
const { project_id, limit
|
|
248
|
-
validateRequired(project_id, 'project_id');
|
|
249
|
-
validateUUID(project_id, 'project_id');
|
|
261
|
+
const { project_id, limit } = parseArgs(args, getSprintVelocitySchema);
|
|
250
262
|
const apiClient = getApiClient();
|
|
251
263
|
const response = await apiClient.proxy('get_sprint_velocity', {
|
|
252
264
|
project_id,
|
|
253
|
-
limit: Math.min(limit, 50),
|
|
265
|
+
limit: Math.min(limit ?? 10, 50),
|
|
254
266
|
});
|
|
255
267
|
if (!response.ok) {
|
|
256
268
|
throw new Error(`Failed to get sprint velocity: ${response.error}`);
|
package/dist/handlers/tasks.js
CHANGED
|
@@ -15,8 +15,82 @@
|
|
|
15
15
|
* - add_subtask
|
|
16
16
|
* - get_subtasks
|
|
17
17
|
*/
|
|
18
|
-
import {
|
|
18
|
+
import { parseArgs, uuidValidator, taskStatusValidator, priorityValidator, progressValidator, minutesValidator, createEnumValidator, ValidationError, } from '../validators.js';
|
|
19
19
|
import { getApiClient } from '../api-client.js';
|
|
20
|
+
// Valid task types
|
|
21
|
+
const VALID_TASK_TYPES = [
|
|
22
|
+
'frontend', 'backend', 'database', 'feature', 'bugfix',
|
|
23
|
+
'design', 'mcp', 'testing', 'docs', 'infra', 'other'
|
|
24
|
+
];
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// Argument Schemas
|
|
27
|
+
// ============================================================================
|
|
28
|
+
const getTasksSchema = {
|
|
29
|
+
project_id: { type: 'string', required: true, validate: uuidValidator },
|
|
30
|
+
status: { type: 'string', validate: taskStatusValidator },
|
|
31
|
+
limit: { type: 'number', default: 50 },
|
|
32
|
+
offset: { type: 'number', default: 0 },
|
|
33
|
+
search_query: { type: 'string' },
|
|
34
|
+
include_subtasks: { type: 'boolean', default: false },
|
|
35
|
+
include_metadata: { type: 'boolean', default: false },
|
|
36
|
+
};
|
|
37
|
+
const getNextTaskSchema = {
|
|
38
|
+
project_id: { type: 'string', required: true, validate: uuidValidator },
|
|
39
|
+
};
|
|
40
|
+
const addTaskSchema = {
|
|
41
|
+
project_id: { type: 'string', required: true, validate: uuidValidator },
|
|
42
|
+
title: { type: 'string', required: true },
|
|
43
|
+
description: { type: 'string' },
|
|
44
|
+
priority: { type: 'number', default: 3, validate: priorityValidator },
|
|
45
|
+
estimated_minutes: { type: 'number', validate: minutesValidator },
|
|
46
|
+
blocking: { type: 'boolean', default: false },
|
|
47
|
+
task_type: { type: 'string', validate: createEnumValidator(VALID_TASK_TYPES) },
|
|
48
|
+
};
|
|
49
|
+
const updateTaskSchema = {
|
|
50
|
+
task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
51
|
+
title: { type: 'string' },
|
|
52
|
+
description: { type: 'string' },
|
|
53
|
+
priority: { type: 'number', validate: priorityValidator },
|
|
54
|
+
status: { type: 'string', validate: taskStatusValidator },
|
|
55
|
+
progress_percentage: { type: 'number', validate: progressValidator },
|
|
56
|
+
progress_note: { type: 'string' },
|
|
57
|
+
estimated_minutes: { type: 'number', validate: minutesValidator },
|
|
58
|
+
git_branch: { type: 'string' },
|
|
59
|
+
task_type: { type: 'string', validate: createEnumValidator(VALID_TASK_TYPES) },
|
|
60
|
+
};
|
|
61
|
+
const completeTaskSchema = {
|
|
62
|
+
task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
63
|
+
summary: { type: 'string' },
|
|
64
|
+
};
|
|
65
|
+
const deleteTaskSchema = {
|
|
66
|
+
task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
67
|
+
};
|
|
68
|
+
const addTaskReferenceSchema = {
|
|
69
|
+
task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
70
|
+
url: { type: 'string', required: true },
|
|
71
|
+
label: { type: 'string' },
|
|
72
|
+
};
|
|
73
|
+
const removeTaskReferenceSchema = {
|
|
74
|
+
task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
75
|
+
url: { type: 'string', required: true },
|
|
76
|
+
};
|
|
77
|
+
const batchUpdateTasksSchema = {
|
|
78
|
+
updates: { type: 'array', required: true },
|
|
79
|
+
};
|
|
80
|
+
const batchCompleteTasksSchema = {
|
|
81
|
+
completions: { type: 'array', required: true },
|
|
82
|
+
};
|
|
83
|
+
const addSubtaskSchema = {
|
|
84
|
+
parent_task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
85
|
+
title: { type: 'string', required: true },
|
|
86
|
+
description: { type: 'string' },
|
|
87
|
+
priority: { type: 'number', validate: priorityValidator },
|
|
88
|
+
estimated_minutes: { type: 'number', validate: minutesValidator },
|
|
89
|
+
};
|
|
90
|
+
const getSubtasksSchema = {
|
|
91
|
+
parent_task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
92
|
+
status: { type: 'string', validate: taskStatusValidator },
|
|
93
|
+
};
|
|
20
94
|
function getTaskCompleteGitInstructions(gitWorkflow, gitMainBranch, gitDevelopBranch, taskBranch, taskTitle, taskId) {
|
|
21
95
|
if (gitWorkflow === 'none') {
|
|
22
96
|
return undefined;
|
|
@@ -68,14 +142,11 @@ export function getValidationApprovedGitInstructions(config, taskBranch) {
|
|
|
68
142
|
// Task Handlers - Using API Client
|
|
69
143
|
// ============================================================================
|
|
70
144
|
export const getTasks = async (args, ctx) => {
|
|
71
|
-
const { project_id, status, limit
|
|
72
|
-
validateRequired(project_id, 'project_id');
|
|
73
|
-
validateUUID(project_id, 'project_id');
|
|
74
|
-
validateTaskStatus(status);
|
|
145
|
+
const { project_id, status, limit, offset, search_query, include_subtasks, include_metadata } = parseArgs(args, getTasksSchema);
|
|
75
146
|
const api = getApiClient();
|
|
76
147
|
const response = await api.getTasks(project_id, {
|
|
77
148
|
status,
|
|
78
|
-
limit: Math.min(limit, 200),
|
|
149
|
+
limit: Math.min(limit ?? 50, 200),
|
|
79
150
|
offset,
|
|
80
151
|
include_subtasks,
|
|
81
152
|
search_query,
|
|
@@ -93,9 +164,7 @@ export const getTasks = async (args, ctx) => {
|
|
|
93
164
|
};
|
|
94
165
|
};
|
|
95
166
|
export const getNextTask = async (args, ctx) => {
|
|
96
|
-
const { project_id } = args;
|
|
97
|
-
validateRequired(project_id, 'project_id');
|
|
98
|
-
validateUUID(project_id, 'project_id');
|
|
167
|
+
const { project_id } = parseArgs(args, getNextTaskSchema);
|
|
99
168
|
const api = getApiClient();
|
|
100
169
|
const response = await api.getNextTask(project_id, ctx.session.currentSessionId || undefined);
|
|
101
170
|
if (!response.ok) {
|
|
@@ -138,12 +207,7 @@ export const getNextTask = async (args, ctx) => {
|
|
|
138
207
|
return { result };
|
|
139
208
|
};
|
|
140
209
|
export const addTask = async (args, ctx) => {
|
|
141
|
-
const { project_id, title, description, priority
|
|
142
|
-
validateRequired(project_id, 'project_id');
|
|
143
|
-
validateRequired(title, 'title');
|
|
144
|
-
validateUUID(project_id, 'project_id');
|
|
145
|
-
validatePriority(priority);
|
|
146
|
-
validateEstimatedMinutes(estimated_minutes);
|
|
210
|
+
const { project_id, title, description, priority, estimated_minutes, blocking, task_type } = parseArgs(args, addTaskSchema);
|
|
147
211
|
const api = getApiClient();
|
|
148
212
|
const response = await api.createTask(project_id, {
|
|
149
213
|
title,
|
|
@@ -152,6 +216,7 @@ export const addTask = async (args, ctx) => {
|
|
|
152
216
|
estimated_minutes,
|
|
153
217
|
blocking,
|
|
154
218
|
session_id: ctx.session.currentSessionId || undefined,
|
|
219
|
+
task_type,
|
|
155
220
|
});
|
|
156
221
|
if (!response.ok) {
|
|
157
222
|
throw new Error(`Failed to add task: ${response.error}`);
|
|
@@ -169,13 +234,8 @@ export const addTask = async (args, ctx) => {
|
|
|
169
234
|
return { result };
|
|
170
235
|
};
|
|
171
236
|
export const updateTask = async (args, ctx) => {
|
|
172
|
-
const { task_id, progress_note,
|
|
173
|
-
|
|
174
|
-
validateUUID(task_id, 'task_id');
|
|
175
|
-
validateTaskStatus(updates.status);
|
|
176
|
-
validatePriority(updates.priority);
|
|
177
|
-
validateProgressPercentage(updates.progress_percentage);
|
|
178
|
-
validateEstimatedMinutes(updates.estimated_minutes);
|
|
237
|
+
const { task_id, title, description, priority, status, progress_percentage, progress_note, estimated_minutes, git_branch, task_type } = parseArgs(args, updateTaskSchema);
|
|
238
|
+
const updates = { title, description, priority, status, progress_percentage, estimated_minutes, git_branch, task_type };
|
|
179
239
|
const api = getApiClient();
|
|
180
240
|
const response = await api.updateTask(task_id, {
|
|
181
241
|
...updates,
|
|
@@ -208,6 +268,16 @@ export const updateTask = async (args, ctx) => {
|
|
|
208
268
|
},
|
|
209
269
|
};
|
|
210
270
|
}
|
|
271
|
+
if (response.error?.includes('branch_conflict')) {
|
|
272
|
+
return {
|
|
273
|
+
result: {
|
|
274
|
+
error: 'branch_conflict',
|
|
275
|
+
message: response.error,
|
|
276
|
+
conflicting_task_id: response.data?.conflicting_task_id,
|
|
277
|
+
conflicting_task_title: response.data?.conflicting_task_title,
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
}
|
|
211
281
|
throw new Error(`Failed to update task: ${response.error}`);
|
|
212
282
|
}
|
|
213
283
|
// Build result - include git workflow info when transitioning to in_progress
|
|
@@ -230,9 +300,7 @@ export const updateTask = async (args, ctx) => {
|
|
|
230
300
|
return { result };
|
|
231
301
|
};
|
|
232
302
|
export const completeTask = async (args, ctx) => {
|
|
233
|
-
const { task_id, summary } = args;
|
|
234
|
-
validateRequired(task_id, 'task_id');
|
|
235
|
-
validateUUID(task_id, 'task_id');
|
|
303
|
+
const { task_id, summary } = parseArgs(args, completeTaskSchema);
|
|
236
304
|
const api = getApiClient();
|
|
237
305
|
const response = await api.completeTask(task_id, {
|
|
238
306
|
summary,
|
|
@@ -266,9 +334,7 @@ export const completeTask = async (args, ctx) => {
|
|
|
266
334
|
return { result };
|
|
267
335
|
};
|
|
268
336
|
export const deleteTask = async (args, ctx) => {
|
|
269
|
-
const { task_id } = args;
|
|
270
|
-
validateRequired(task_id, 'task_id');
|
|
271
|
-
validateUUID(task_id, 'task_id');
|
|
337
|
+
const { task_id } = parseArgs(args, deleteTaskSchema);
|
|
272
338
|
const api = getApiClient();
|
|
273
339
|
const response = await api.deleteTask(task_id);
|
|
274
340
|
if (!response.ok) {
|
|
@@ -277,10 +343,7 @@ export const deleteTask = async (args, ctx) => {
|
|
|
277
343
|
return { result: { success: true, deleted_id: task_id } };
|
|
278
344
|
};
|
|
279
345
|
export const addTaskReference = async (args, ctx) => {
|
|
280
|
-
const { task_id, url, label } = args;
|
|
281
|
-
validateRequired(task_id, 'task_id');
|
|
282
|
-
validateUUID(task_id, 'task_id');
|
|
283
|
-
validateRequired(url, 'url');
|
|
346
|
+
const { task_id, url, label } = parseArgs(args, addTaskReferenceSchema);
|
|
284
347
|
const api = getApiClient();
|
|
285
348
|
const response = await api.addTaskReference(task_id, url, label);
|
|
286
349
|
if (!response.ok) {
|
|
@@ -297,10 +360,7 @@ export const addTaskReference = async (args, ctx) => {
|
|
|
297
360
|
};
|
|
298
361
|
};
|
|
299
362
|
export const removeTaskReference = async (args, ctx) => {
|
|
300
|
-
const { task_id, url } = args;
|
|
301
|
-
validateRequired(task_id, 'task_id');
|
|
302
|
-
validateUUID(task_id, 'task_id');
|
|
303
|
-
validateRequired(url, 'url');
|
|
363
|
+
const { task_id, url } = parseArgs(args, removeTaskReferenceSchema);
|
|
304
364
|
const api = getApiClient();
|
|
305
365
|
const response = await api.removeTaskReference(task_id, url);
|
|
306
366
|
if (!response.ok) {
|
|
@@ -312,61 +372,52 @@ export const removeTaskReference = async (args, ctx) => {
|
|
|
312
372
|
return { result: { success: true } };
|
|
313
373
|
};
|
|
314
374
|
export const batchUpdateTasks = async (args, ctx) => {
|
|
315
|
-
const { updates } = args;
|
|
316
|
-
|
|
375
|
+
const { updates } = parseArgs(args, batchUpdateTasksSchema);
|
|
376
|
+
const typedUpdates = updates;
|
|
377
|
+
if (!Array.isArray(typedUpdates) || typedUpdates.length === 0) {
|
|
317
378
|
throw new ValidationError('updates must be a non-empty array', {
|
|
318
379
|
field: 'updates',
|
|
319
380
|
hint: 'Provide an array of task updates with at least one item',
|
|
320
381
|
});
|
|
321
382
|
}
|
|
322
|
-
if (
|
|
383
|
+
if (typedUpdates.length > 50) {
|
|
323
384
|
throw new ValidationError('Too many updates. Maximum is 50 per batch.', {
|
|
324
385
|
field: 'updates',
|
|
325
386
|
hint: 'Split your updates into smaller batches',
|
|
326
387
|
});
|
|
327
388
|
}
|
|
328
|
-
//
|
|
329
|
-
for (const update of updates) {
|
|
330
|
-
validateRequired(update.task_id, 'task_id');
|
|
331
|
-
validateUUID(update.task_id, 'task_id');
|
|
332
|
-
validateTaskStatus(update.status);
|
|
333
|
-
validatePriority(update.priority);
|
|
334
|
-
validateProgressPercentage(update.progress_percentage);
|
|
335
|
-
}
|
|
389
|
+
// Individual item validation happens at API level
|
|
336
390
|
const api = getApiClient();
|
|
337
|
-
const response = await api.batchUpdateTasks(
|
|
391
|
+
const response = await api.batchUpdateTasks(typedUpdates);
|
|
338
392
|
if (!response.ok) {
|
|
339
393
|
throw new Error(`Failed to batch update tasks: ${response.error}`);
|
|
340
394
|
}
|
|
341
395
|
return {
|
|
342
396
|
result: {
|
|
343
397
|
success: response.data?.success || false,
|
|
344
|
-
total:
|
|
398
|
+
total: typedUpdates.length,
|
|
345
399
|
succeeded: response.data?.updated_count || 0,
|
|
346
400
|
},
|
|
347
401
|
};
|
|
348
402
|
};
|
|
349
403
|
export const batchCompleteTasks = async (args, ctx) => {
|
|
350
|
-
const { completions } = args;
|
|
351
|
-
|
|
404
|
+
const { completions } = parseArgs(args, batchCompleteTasksSchema);
|
|
405
|
+
const typedCompletions = completions;
|
|
406
|
+
if (!Array.isArray(typedCompletions) || typedCompletions.length === 0) {
|
|
352
407
|
throw new ValidationError('completions must be a non-empty array', {
|
|
353
408
|
field: 'completions',
|
|
354
409
|
hint: 'Provide an array of task completions with at least one item',
|
|
355
410
|
});
|
|
356
411
|
}
|
|
357
|
-
if (
|
|
412
|
+
if (typedCompletions.length > 50) {
|
|
358
413
|
throw new ValidationError('Too many completions. Maximum is 50 per batch.', {
|
|
359
414
|
field: 'completions',
|
|
360
415
|
hint: 'Split your completions into smaller batches',
|
|
361
416
|
});
|
|
362
417
|
}
|
|
363
|
-
//
|
|
364
|
-
for (const completion of completions) {
|
|
365
|
-
validateRequired(completion.task_id, 'task_id');
|
|
366
|
-
validateUUID(completion.task_id, 'task_id');
|
|
367
|
-
}
|
|
418
|
+
// Individual item validation happens at API level
|
|
368
419
|
const api = getApiClient();
|
|
369
|
-
const response = await api.batchCompleteTasks(
|
|
420
|
+
const response = await api.batchCompleteTasks(typedCompletions);
|
|
370
421
|
if (!response.ok) {
|
|
371
422
|
throw new Error(`Failed to batch complete tasks: ${response.error}`);
|
|
372
423
|
}
|
|
@@ -374,9 +425,9 @@ export const batchCompleteTasks = async (args, ctx) => {
|
|
|
374
425
|
return {
|
|
375
426
|
result: {
|
|
376
427
|
success: data?.success || false,
|
|
377
|
-
total:
|
|
428
|
+
total: typedCompletions.length,
|
|
378
429
|
succeeded: data?.completed_count || 0,
|
|
379
|
-
failed:
|
|
430
|
+
failed: typedCompletions.length - (data?.completed_count || 0),
|
|
380
431
|
next_task: data?.next_task,
|
|
381
432
|
},
|
|
382
433
|
};
|
|
@@ -385,14 +436,7 @@ export const batchCompleteTasks = async (args, ctx) => {
|
|
|
385
436
|
// Subtask Handlers
|
|
386
437
|
// ============================================================================
|
|
387
438
|
export const addSubtask = async (args, ctx) => {
|
|
388
|
-
const { parent_task_id, title, description, priority, estimated_minutes } = args;
|
|
389
|
-
validateRequired(parent_task_id, 'parent_task_id');
|
|
390
|
-
validateUUID(parent_task_id, 'parent_task_id');
|
|
391
|
-
validateRequired(title, 'title');
|
|
392
|
-
if (priority !== undefined)
|
|
393
|
-
validatePriority(priority);
|
|
394
|
-
if (estimated_minutes !== undefined)
|
|
395
|
-
validateEstimatedMinutes(estimated_minutes);
|
|
439
|
+
const { parent_task_id, title, description, priority, estimated_minutes } = parseArgs(args, addSubtaskSchema);
|
|
396
440
|
const api = getApiClient();
|
|
397
441
|
const response = await api.addSubtask(parent_task_id, {
|
|
398
442
|
title,
|
|
@@ -421,11 +465,7 @@ export const addSubtask = async (args, ctx) => {
|
|
|
421
465
|
};
|
|
422
466
|
};
|
|
423
467
|
export const getSubtasks = async (args, ctx) => {
|
|
424
|
-
const { parent_task_id, status } = args;
|
|
425
|
-
validateRequired(parent_task_id, 'parent_task_id');
|
|
426
|
-
validateUUID(parent_task_id, 'parent_task_id');
|
|
427
|
-
if (status)
|
|
428
|
-
validateTaskStatus(status);
|
|
468
|
+
const { parent_task_id, status } = parseArgs(args, getSubtasksSchema);
|
|
429
469
|
const api = getApiClient();
|
|
430
470
|
const response = await api.getSubtasks(parent_task_id, status);
|
|
431
471
|
if (!response.ok) {
|