@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/dist/handlers/tasks.js
CHANGED
|
@@ -15,8 +15,84 @@
|
|
|
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
|
+
worktree_path: { type: 'string' },
|
|
60
|
+
task_type: { type: 'string', validate: createEnumValidator(VALID_TASK_TYPES) },
|
|
61
|
+
skip_worktree_requirement: { type: 'boolean', default: false },
|
|
62
|
+
};
|
|
63
|
+
const completeTaskSchema = {
|
|
64
|
+
task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
65
|
+
summary: { type: 'string' },
|
|
66
|
+
};
|
|
67
|
+
const deleteTaskSchema = {
|
|
68
|
+
task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
69
|
+
};
|
|
70
|
+
const addTaskReferenceSchema = {
|
|
71
|
+
task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
72
|
+
url: { type: 'string', required: true },
|
|
73
|
+
label: { type: 'string' },
|
|
74
|
+
};
|
|
75
|
+
const removeTaskReferenceSchema = {
|
|
76
|
+
task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
77
|
+
url: { type: 'string', required: true },
|
|
78
|
+
};
|
|
79
|
+
const batchUpdateTasksSchema = {
|
|
80
|
+
updates: { type: 'array', required: true },
|
|
81
|
+
};
|
|
82
|
+
const batchCompleteTasksSchema = {
|
|
83
|
+
completions: { type: 'array', required: true },
|
|
84
|
+
};
|
|
85
|
+
const addSubtaskSchema = {
|
|
86
|
+
parent_task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
87
|
+
title: { type: 'string', required: true },
|
|
88
|
+
description: { type: 'string' },
|
|
89
|
+
priority: { type: 'number', validate: priorityValidator },
|
|
90
|
+
estimated_minutes: { type: 'number', validate: minutesValidator },
|
|
91
|
+
};
|
|
92
|
+
const getSubtasksSchema = {
|
|
93
|
+
parent_task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
94
|
+
status: { type: 'string', validate: taskStatusValidator },
|
|
95
|
+
};
|
|
20
96
|
function getTaskCompleteGitInstructions(gitWorkflow, gitMainBranch, gitDevelopBranch, taskBranch, taskTitle, taskId) {
|
|
21
97
|
if (gitWorkflow === 'none') {
|
|
22
98
|
return undefined;
|
|
@@ -68,21 +144,18 @@ export function getValidationApprovedGitInstructions(config, taskBranch) {
|
|
|
68
144
|
// Task Handlers - Using API Client
|
|
69
145
|
// ============================================================================
|
|
70
146
|
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);
|
|
147
|
+
const { project_id, status, limit, offset, search_query, include_subtasks, include_metadata } = parseArgs(args, getTasksSchema);
|
|
75
148
|
const api = getApiClient();
|
|
76
149
|
const response = await api.getTasks(project_id, {
|
|
77
150
|
status,
|
|
78
|
-
limit: Math.min(limit, 200),
|
|
151
|
+
limit: Math.min(limit ?? 50, 200),
|
|
79
152
|
offset,
|
|
80
153
|
include_subtasks,
|
|
81
154
|
search_query,
|
|
82
155
|
include_metadata,
|
|
83
156
|
});
|
|
84
157
|
if (!response.ok) {
|
|
85
|
-
|
|
158
|
+
return { result: { error: response.error || 'Failed to fetch tasks' }, isError: true };
|
|
86
159
|
}
|
|
87
160
|
return {
|
|
88
161
|
result: {
|
|
@@ -93,13 +166,11 @@ export const getTasks = async (args, ctx) => {
|
|
|
93
166
|
};
|
|
94
167
|
};
|
|
95
168
|
export const getNextTask = async (args, ctx) => {
|
|
96
|
-
const { project_id } = args;
|
|
97
|
-
validateRequired(project_id, 'project_id');
|
|
98
|
-
validateUUID(project_id, 'project_id');
|
|
169
|
+
const { project_id } = parseArgs(args, getNextTaskSchema);
|
|
99
170
|
const api = getApiClient();
|
|
100
171
|
const response = await api.getNextTask(project_id, ctx.session.currentSessionId || undefined);
|
|
101
172
|
if (!response.ok) {
|
|
102
|
-
|
|
173
|
+
return { result: { error: response.error || 'Failed to get next task' }, isError: true };
|
|
103
174
|
}
|
|
104
175
|
const data = response.data;
|
|
105
176
|
if (!data) {
|
|
@@ -138,12 +209,7 @@ export const getNextTask = async (args, ctx) => {
|
|
|
138
209
|
return { result };
|
|
139
210
|
};
|
|
140
211
|
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);
|
|
212
|
+
const { project_id, title, description, priority, estimated_minutes, blocking, task_type } = parseArgs(args, addTaskSchema);
|
|
147
213
|
const api = getApiClient();
|
|
148
214
|
const response = await api.createTask(project_id, {
|
|
149
215
|
title,
|
|
@@ -152,9 +218,10 @@ export const addTask = async (args, ctx) => {
|
|
|
152
218
|
estimated_minutes,
|
|
153
219
|
blocking,
|
|
154
220
|
session_id: ctx.session.currentSessionId || undefined,
|
|
221
|
+
task_type,
|
|
155
222
|
});
|
|
156
223
|
if (!response.ok) {
|
|
157
|
-
|
|
224
|
+
return { result: { error: response.error || 'Failed to add task' }, isError: true };
|
|
158
225
|
}
|
|
159
226
|
const data = response.data;
|
|
160
227
|
const result = {
|
|
@@ -169,13 +236,24 @@ export const addTask = async (args, ctx) => {
|
|
|
169
236
|
return { result };
|
|
170
237
|
};
|
|
171
238
|
export const updateTask = async (args, ctx) => {
|
|
172
|
-
const { task_id, progress_note,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
239
|
+
const { task_id, title, description, priority, status, progress_percentage, progress_note, estimated_minutes, git_branch, worktree_path, task_type, skip_worktree_requirement } = parseArgs(args, updateTaskSchema);
|
|
240
|
+
const updates = { title, description, priority, status, progress_percentage, estimated_minutes, git_branch, worktree_path, task_type };
|
|
241
|
+
// Enforce worktree creation: require git_branch when marking task as in_progress
|
|
242
|
+
// This ensures multi-agent collaboration works properly with isolated worktrees
|
|
243
|
+
if (status === 'in_progress' && !git_branch && !skip_worktree_requirement) {
|
|
244
|
+
return {
|
|
245
|
+
result: {
|
|
246
|
+
error: 'worktree_required',
|
|
247
|
+
message: 'git_branch is required when marking a task as in_progress. Create a worktree first and provide the branch name.',
|
|
248
|
+
hint: 'Create a worktree with: git worktree add ../PROJECT-task-TASKID -b feature/TASKID-description BASE_BRANCH, then call update_task with both status and git_branch parameters.',
|
|
249
|
+
worktree_example: {
|
|
250
|
+
command: `git worktree add ../worktree-${task_id.substring(0, 8)} -b feature/${task_id.substring(0, 8)}-task develop`,
|
|
251
|
+
then: `update_task(task_id: "${task_id}", status: "in_progress", git_branch: "feature/${task_id.substring(0, 8)}-task")`,
|
|
252
|
+
},
|
|
253
|
+
skip_option: 'If this project does not use git branching (trunk-based or no git workflow), pass skip_worktree_requirement: true',
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
179
257
|
const api = getApiClient();
|
|
180
258
|
const response = await api.updateTask(task_id, {
|
|
181
259
|
...updates,
|
|
@@ -208,7 +286,17 @@ export const updateTask = async (args, ctx) => {
|
|
|
208
286
|
},
|
|
209
287
|
};
|
|
210
288
|
}
|
|
211
|
-
|
|
289
|
+
if (response.error?.includes('branch_conflict')) {
|
|
290
|
+
return {
|
|
291
|
+
result: {
|
|
292
|
+
error: 'branch_conflict',
|
|
293
|
+
message: response.error,
|
|
294
|
+
conflicting_task_id: response.data?.conflicting_task_id,
|
|
295
|
+
conflicting_task_title: response.data?.conflicting_task_title,
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
return { result: { error: response.error || 'Failed to update task' }, isError: true };
|
|
212
300
|
}
|
|
213
301
|
// Build result - include git workflow info when transitioning to in_progress
|
|
214
302
|
const data = response.data;
|
|
@@ -222,28 +310,21 @@ export const updateTask = async (args, ctx) => {
|
|
|
222
310
|
if (data?.next_step) {
|
|
223
311
|
result.next_step = data.next_step;
|
|
224
312
|
}
|
|
225
|
-
// Warn if transitioning to in_progress without git_branch
|
|
226
|
-
if (updates.status === 'in_progress' && !updates.git_branch) {
|
|
227
|
-
result.warning = 'git_branch not set. For multi-agent collaboration, set git_branch when marking in_progress to track your worktree.';
|
|
228
|
-
result.hint = 'Call update_task again with git_branch parameter after creating your worktree.';
|
|
229
|
-
}
|
|
230
313
|
return { result };
|
|
231
314
|
};
|
|
232
315
|
export const completeTask = async (args, ctx) => {
|
|
233
|
-
const { task_id, summary } = args;
|
|
234
|
-
validateRequired(task_id, 'task_id');
|
|
235
|
-
validateUUID(task_id, 'task_id');
|
|
316
|
+
const { task_id, summary } = parseArgs(args, completeTaskSchema);
|
|
236
317
|
const api = getApiClient();
|
|
237
318
|
const response = await api.completeTask(task_id, {
|
|
238
319
|
summary,
|
|
239
320
|
session_id: ctx.session.currentSessionId || undefined,
|
|
240
321
|
});
|
|
241
322
|
if (!response.ok) {
|
|
242
|
-
|
|
323
|
+
return { result: { error: response.error || 'Failed to complete task' }, isError: true };
|
|
243
324
|
}
|
|
244
325
|
const data = response.data;
|
|
245
326
|
if (!data) {
|
|
246
|
-
|
|
327
|
+
return { result: { error: 'No response data from complete task' }, isError: true };
|
|
247
328
|
}
|
|
248
329
|
// Build result matching expected format
|
|
249
330
|
const result = {
|
|
@@ -266,28 +347,23 @@ export const completeTask = async (args, ctx) => {
|
|
|
266
347
|
return { result };
|
|
267
348
|
};
|
|
268
349
|
export const deleteTask = async (args, ctx) => {
|
|
269
|
-
const { task_id } = args;
|
|
270
|
-
validateRequired(task_id, 'task_id');
|
|
271
|
-
validateUUID(task_id, 'task_id');
|
|
350
|
+
const { task_id } = parseArgs(args, deleteTaskSchema);
|
|
272
351
|
const api = getApiClient();
|
|
273
352
|
const response = await api.deleteTask(task_id);
|
|
274
353
|
if (!response.ok) {
|
|
275
|
-
|
|
354
|
+
return { result: { error: response.error || 'Failed to delete task' }, isError: true };
|
|
276
355
|
}
|
|
277
356
|
return { result: { success: true, deleted_id: task_id } };
|
|
278
357
|
};
|
|
279
358
|
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');
|
|
359
|
+
const { task_id, url, label } = parseArgs(args, addTaskReferenceSchema);
|
|
284
360
|
const api = getApiClient();
|
|
285
361
|
const response = await api.addTaskReference(task_id, url, label);
|
|
286
362
|
if (!response.ok) {
|
|
287
363
|
if (response.error?.includes('already exists')) {
|
|
288
364
|
return { result: { success: false, error: 'Reference with this URL already exists' } };
|
|
289
365
|
}
|
|
290
|
-
|
|
366
|
+
return { result: { error: response.error || 'Failed to add reference' }, isError: true };
|
|
291
367
|
}
|
|
292
368
|
return {
|
|
293
369
|
result: {
|
|
@@ -297,86 +373,74 @@ export const addTaskReference = async (args, ctx) => {
|
|
|
297
373
|
};
|
|
298
374
|
};
|
|
299
375
|
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');
|
|
376
|
+
const { task_id, url } = parseArgs(args, removeTaskReferenceSchema);
|
|
304
377
|
const api = getApiClient();
|
|
305
378
|
const response = await api.removeTaskReference(task_id, url);
|
|
306
379
|
if (!response.ok) {
|
|
307
380
|
if (response.error?.includes('not found')) {
|
|
308
381
|
return { result: { success: false, error: 'Reference with this URL not found' } };
|
|
309
382
|
}
|
|
310
|
-
|
|
383
|
+
return { result: { error: response.error || 'Failed to remove reference' }, isError: true };
|
|
311
384
|
}
|
|
312
385
|
return { result: { success: true } };
|
|
313
386
|
};
|
|
314
387
|
export const batchUpdateTasks = async (args, ctx) => {
|
|
315
|
-
const { updates } = args;
|
|
316
|
-
|
|
388
|
+
const { updates } = parseArgs(args, batchUpdateTasksSchema);
|
|
389
|
+
const typedUpdates = updates;
|
|
390
|
+
if (!Array.isArray(typedUpdates) || typedUpdates.length === 0) {
|
|
317
391
|
throw new ValidationError('updates must be a non-empty array', {
|
|
318
392
|
field: 'updates',
|
|
319
393
|
hint: 'Provide an array of task updates with at least one item',
|
|
320
394
|
});
|
|
321
395
|
}
|
|
322
|
-
if (
|
|
396
|
+
if (typedUpdates.length > 50) {
|
|
323
397
|
throw new ValidationError('Too many updates. Maximum is 50 per batch.', {
|
|
324
398
|
field: 'updates',
|
|
325
399
|
hint: 'Split your updates into smaller batches',
|
|
326
400
|
});
|
|
327
401
|
}
|
|
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
|
-
}
|
|
402
|
+
// Individual item validation happens at API level
|
|
336
403
|
const api = getApiClient();
|
|
337
|
-
const response = await api.batchUpdateTasks(
|
|
404
|
+
const response = await api.batchUpdateTasks(typedUpdates);
|
|
338
405
|
if (!response.ok) {
|
|
339
|
-
|
|
406
|
+
return { result: { error: response.error || 'Failed to batch update tasks' }, isError: true };
|
|
340
407
|
}
|
|
341
408
|
return {
|
|
342
409
|
result: {
|
|
343
410
|
success: response.data?.success || false,
|
|
344
|
-
total:
|
|
411
|
+
total: typedUpdates.length,
|
|
345
412
|
succeeded: response.data?.updated_count || 0,
|
|
346
413
|
},
|
|
347
414
|
};
|
|
348
415
|
};
|
|
349
416
|
export const batchCompleteTasks = async (args, ctx) => {
|
|
350
|
-
const { completions } = args;
|
|
351
|
-
|
|
417
|
+
const { completions } = parseArgs(args, batchCompleteTasksSchema);
|
|
418
|
+
const typedCompletions = completions;
|
|
419
|
+
if (!Array.isArray(typedCompletions) || typedCompletions.length === 0) {
|
|
352
420
|
throw new ValidationError('completions must be a non-empty array', {
|
|
353
421
|
field: 'completions',
|
|
354
422
|
hint: 'Provide an array of task completions with at least one item',
|
|
355
423
|
});
|
|
356
424
|
}
|
|
357
|
-
if (
|
|
425
|
+
if (typedCompletions.length > 50) {
|
|
358
426
|
throw new ValidationError('Too many completions. Maximum is 50 per batch.', {
|
|
359
427
|
field: 'completions',
|
|
360
428
|
hint: 'Split your completions into smaller batches',
|
|
361
429
|
});
|
|
362
430
|
}
|
|
363
|
-
//
|
|
364
|
-
for (const completion of completions) {
|
|
365
|
-
validateRequired(completion.task_id, 'task_id');
|
|
366
|
-
validateUUID(completion.task_id, 'task_id');
|
|
367
|
-
}
|
|
431
|
+
// Individual item validation happens at API level
|
|
368
432
|
const api = getApiClient();
|
|
369
|
-
const response = await api.batchCompleteTasks(
|
|
433
|
+
const response = await api.batchCompleteTasks(typedCompletions);
|
|
370
434
|
if (!response.ok) {
|
|
371
|
-
|
|
435
|
+
return { result: { error: response.error || 'Failed to batch complete tasks' }, isError: true };
|
|
372
436
|
}
|
|
373
437
|
const data = response.data;
|
|
374
438
|
return {
|
|
375
439
|
result: {
|
|
376
440
|
success: data?.success || false,
|
|
377
|
-
total:
|
|
441
|
+
total: typedCompletions.length,
|
|
378
442
|
succeeded: data?.completed_count || 0,
|
|
379
|
-
failed:
|
|
443
|
+
failed: typedCompletions.length - (data?.completed_count || 0),
|
|
380
444
|
next_task: data?.next_task,
|
|
381
445
|
},
|
|
382
446
|
};
|
|
@@ -385,14 +449,7 @@ export const batchCompleteTasks = async (args, ctx) => {
|
|
|
385
449
|
// Subtask Handlers
|
|
386
450
|
// ============================================================================
|
|
387
451
|
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);
|
|
452
|
+
const { parent_task_id, title, description, priority, estimated_minutes } = parseArgs(args, addSubtaskSchema);
|
|
396
453
|
const api = getApiClient();
|
|
397
454
|
const response = await api.addSubtask(parent_task_id, {
|
|
398
455
|
title,
|
|
@@ -410,7 +467,7 @@ export const addSubtask = async (args, ctx) => {
|
|
|
410
467
|
},
|
|
411
468
|
};
|
|
412
469
|
}
|
|
413
|
-
|
|
470
|
+
return { result: { error: response.error || 'Failed to add subtask' }, isError: true };
|
|
414
471
|
}
|
|
415
472
|
return {
|
|
416
473
|
result: {
|
|
@@ -421,15 +478,11 @@ export const addSubtask = async (args, ctx) => {
|
|
|
421
478
|
};
|
|
422
479
|
};
|
|
423
480
|
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);
|
|
481
|
+
const { parent_task_id, status } = parseArgs(args, getSubtasksSchema);
|
|
429
482
|
const api = getApiClient();
|
|
430
483
|
const response = await api.getSubtasks(parent_task_id, status);
|
|
431
484
|
if (!response.ok) {
|
|
432
|
-
|
|
485
|
+
return { result: { error: response.error || 'Failed to fetch subtasks' }, isError: true };
|
|
433
486
|
}
|
|
434
487
|
return {
|
|
435
488
|
result: {
|
|
@@ -442,6 +495,48 @@ export const getSubtasks = async (args, ctx) => {
|
|
|
442
495
|
},
|
|
443
496
|
};
|
|
444
497
|
};
|
|
498
|
+
// ============================================================================
|
|
499
|
+
// Worktree Cleanup Handlers
|
|
500
|
+
// ============================================================================
|
|
501
|
+
const getStaleWorktreesSchema = {
|
|
502
|
+
project_id: { type: 'string', required: true, validate: uuidValidator },
|
|
503
|
+
};
|
|
504
|
+
const clearWorktreePathSchema = {
|
|
505
|
+
task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
506
|
+
};
|
|
507
|
+
export const getStaleWorktrees = async (args, ctx) => {
|
|
508
|
+
const { project_id } = parseArgs(args, getStaleWorktreesSchema);
|
|
509
|
+
const api = getApiClient();
|
|
510
|
+
const response = await api.getStaleWorktrees(project_id);
|
|
511
|
+
if (!response.ok) {
|
|
512
|
+
return { result: { error: response.error || 'Failed to get stale worktrees' }, isError: true };
|
|
513
|
+
}
|
|
514
|
+
const data = response.data;
|
|
515
|
+
return {
|
|
516
|
+
result: {
|
|
517
|
+
project_id: data?.project_id,
|
|
518
|
+
project_name: data?.project_name,
|
|
519
|
+
stale_worktrees: data?.stale_worktrees || [],
|
|
520
|
+
count: data?.count || 0,
|
|
521
|
+
cleanup_instructions: data?.cleanup_instructions,
|
|
522
|
+
},
|
|
523
|
+
};
|
|
524
|
+
};
|
|
525
|
+
export const clearWorktreePath = async (args, ctx) => {
|
|
526
|
+
const { task_id } = parseArgs(args, clearWorktreePathSchema);
|
|
527
|
+
const api = getApiClient();
|
|
528
|
+
const response = await api.clearWorktreePath(task_id);
|
|
529
|
+
if (!response.ok) {
|
|
530
|
+
return { result: { error: response.error || 'Failed to clear worktree path' }, isError: true };
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
result: {
|
|
534
|
+
success: true,
|
|
535
|
+
task_id,
|
|
536
|
+
message: 'Worktree path cleared. The worktree can now be safely removed if not already done.',
|
|
537
|
+
},
|
|
538
|
+
};
|
|
539
|
+
};
|
|
445
540
|
/**
|
|
446
541
|
* Task handlers registry
|
|
447
542
|
*/
|
|
@@ -459,4 +554,7 @@ export const taskHandlers = {
|
|
|
459
554
|
// Subtask handlers
|
|
460
555
|
add_subtask: addSubtask,
|
|
461
556
|
get_subtasks: getSubtasks,
|
|
557
|
+
// Worktree cleanup handlers
|
|
558
|
+
get_stale_worktrees: getStaleWorktrees,
|
|
559
|
+
clear_worktree_path: clearWorktreePath,
|
|
462
560
|
};
|
package/dist/handlers/types.d.ts
CHANGED
|
@@ -82,9 +82,40 @@ export interface HandlerContext {
|
|
|
82
82
|
extractProjectNameFromGitUrl?: (gitUrl: string) => string;
|
|
83
83
|
}
|
|
84
84
|
/**
|
|
85
|
-
*
|
|
85
|
+
* Success result with typed data
|
|
86
86
|
*/
|
|
87
|
-
export interface
|
|
87
|
+
export interface SuccessResult<T = unknown> {
|
|
88
|
+
result: T;
|
|
89
|
+
content?: Array<{
|
|
90
|
+
type: string;
|
|
91
|
+
text: string;
|
|
92
|
+
}>;
|
|
93
|
+
isError?: false;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Error result with error information
|
|
97
|
+
*/
|
|
98
|
+
export interface ErrorResult {
|
|
99
|
+
result: {
|
|
100
|
+
error: string;
|
|
101
|
+
[key: string]: unknown;
|
|
102
|
+
};
|
|
103
|
+
content?: Array<{
|
|
104
|
+
type: string;
|
|
105
|
+
text: string;
|
|
106
|
+
}>;
|
|
107
|
+
isError: true;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Result returned by handlers - discriminated union for type safety
|
|
111
|
+
* Use the helper functions success() and error() to create properly typed results.
|
|
112
|
+
*/
|
|
113
|
+
export type HandlerResult<T = unknown> = SuccessResult<T> | ErrorResult;
|
|
114
|
+
/**
|
|
115
|
+
* Legacy HandlerResult interface for backward compatibility
|
|
116
|
+
* @deprecated Use HandlerResult<T> discriminated union instead
|
|
117
|
+
*/
|
|
118
|
+
export interface LegacyHandlerResult {
|
|
88
119
|
result?: unknown;
|
|
89
120
|
content?: Array<{
|
|
90
121
|
type: string;
|
|
@@ -92,6 +123,37 @@ export interface HandlerResult {
|
|
|
92
123
|
}>;
|
|
93
124
|
isError?: boolean;
|
|
94
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Create a success result with typed data
|
|
128
|
+
* @example
|
|
129
|
+
* return success({ tasks: data, total_count: count });
|
|
130
|
+
*/
|
|
131
|
+
export declare function success<T>(data: T): SuccessResult<T>;
|
|
132
|
+
/**
|
|
133
|
+
* Create an error result
|
|
134
|
+
* @example
|
|
135
|
+
* return error('Task not found');
|
|
136
|
+
* return error('Validation failed', { field: 'title', reason: 'too long' });
|
|
137
|
+
*/
|
|
138
|
+
export declare function error(message: string, details?: Record<string, unknown>): ErrorResult;
|
|
139
|
+
/**
|
|
140
|
+
* Check if a handler result is a success (not an error)
|
|
141
|
+
* @example
|
|
142
|
+
* const result = await handler(args, ctx);
|
|
143
|
+
* if (isSuccess(result)) {
|
|
144
|
+
* console.log(result.result); // typed as T
|
|
145
|
+
* }
|
|
146
|
+
*/
|
|
147
|
+
export declare function isSuccess<T>(result: HandlerResult<T>): result is SuccessResult<T>;
|
|
148
|
+
/**
|
|
149
|
+
* Check if a handler result is an error
|
|
150
|
+
* @example
|
|
151
|
+
* const result = await handler(args, ctx);
|
|
152
|
+
* if (isError(result)) {
|
|
153
|
+
* console.log(result.result.error); // string
|
|
154
|
+
* }
|
|
155
|
+
*/
|
|
156
|
+
export declare function isError(result: HandlerResult<unknown>): result is ErrorResult;
|
|
95
157
|
/**
|
|
96
158
|
* Handler function type
|
|
97
159
|
*/
|
package/dist/handlers/types.js
CHANGED
|
@@ -1 +1,48 @@
|
|
|
1
|
-
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Result Factory Functions - use these for type-safe handler results
|
|
3
|
+
// ============================================================================
|
|
4
|
+
/**
|
|
5
|
+
* Create a success result with typed data
|
|
6
|
+
* @example
|
|
7
|
+
* return success({ tasks: data, total_count: count });
|
|
8
|
+
*/
|
|
9
|
+
export function success(data) {
|
|
10
|
+
return { result: data };
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Create an error result
|
|
14
|
+
* @example
|
|
15
|
+
* return error('Task not found');
|
|
16
|
+
* return error('Validation failed', { field: 'title', reason: 'too long' });
|
|
17
|
+
*/
|
|
18
|
+
export function error(message, details) {
|
|
19
|
+
return {
|
|
20
|
+
result: { error: message, ...details },
|
|
21
|
+
isError: true
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Type Predicates - use for runtime type narrowing
|
|
26
|
+
// ============================================================================
|
|
27
|
+
/**
|
|
28
|
+
* Check if a handler result is a success (not an error)
|
|
29
|
+
* @example
|
|
30
|
+
* const result = await handler(args, ctx);
|
|
31
|
+
* if (isSuccess(result)) {
|
|
32
|
+
* console.log(result.result); // typed as T
|
|
33
|
+
* }
|
|
34
|
+
*/
|
|
35
|
+
export function isSuccess(result) {
|
|
36
|
+
return !result.isError;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Check if a handler result is an error
|
|
40
|
+
* @example
|
|
41
|
+
* const result = await handler(args, ctx);
|
|
42
|
+
* if (isError(result)) {
|
|
43
|
+
* console.log(result.result.error); // string
|
|
44
|
+
* }
|
|
45
|
+
*/
|
|
46
|
+
export function isError(result) {
|
|
47
|
+
return result.isError === true;
|
|
48
|
+
}
|
|
@@ -6,39 +6,43 @@
|
|
|
6
6
|
* - claim_validation
|
|
7
7
|
* - validate_task
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { parseArgs, uuidValidator } from '../validators.js';
|
|
10
10
|
import { getApiClient } from '../api-client.js';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
// Argument schemas for type-safe parsing
|
|
12
|
+
const getTasksAwaitingValidationSchema = {
|
|
13
|
+
project_id: { type: 'string', required: true, validate: uuidValidator },
|
|
14
|
+
};
|
|
15
|
+
const claimValidationSchema = {
|
|
16
|
+
task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
17
|
+
};
|
|
18
|
+
const validateTaskSchema = {
|
|
19
|
+
task_id: { type: 'string', required: true, validate: uuidValidator },
|
|
20
|
+
approved: { type: 'boolean', required: true },
|
|
21
|
+
validation_notes: { type: 'string' },
|
|
22
|
+
skip_pr_check: { type: 'boolean' },
|
|
23
|
+
};
|
|
24
|
+
export const getTasksAwaitingValidation = async (args, _ctx) => {
|
|
25
|
+
const { project_id } = parseArgs(args, getTasksAwaitingValidationSchema);
|
|
15
26
|
const apiClient = getApiClient();
|
|
16
27
|
const response = await apiClient.getTasksAwaitingValidation(project_id);
|
|
17
28
|
if (!response.ok) {
|
|
18
|
-
|
|
29
|
+
return { result: { error: response.error || 'Failed to fetch tasks awaiting validation' }, isError: true };
|
|
19
30
|
}
|
|
20
31
|
return { result: response.data };
|
|
21
32
|
};
|
|
22
33
|
export const claimValidation = async (args, ctx) => {
|
|
23
|
-
const { task_id } = args;
|
|
24
|
-
validateRequired(task_id, 'task_id');
|
|
25
|
-
validateUUID(task_id, 'task_id');
|
|
34
|
+
const { task_id } = parseArgs(args, claimValidationSchema);
|
|
26
35
|
const { session } = ctx;
|
|
27
36
|
const currentSessionId = session.currentSessionId;
|
|
28
37
|
const apiClient = getApiClient();
|
|
29
38
|
const response = await apiClient.claimValidation(task_id, currentSessionId || undefined);
|
|
30
39
|
if (!response.ok) {
|
|
31
|
-
|
|
40
|
+
return { result: { error: response.error || 'Failed to claim task for validation' }, isError: true };
|
|
32
41
|
}
|
|
33
42
|
return { result: response.data };
|
|
34
43
|
};
|
|
35
44
|
export const validateTask = async (args, ctx) => {
|
|
36
|
-
const { task_id,
|
|
37
|
-
validateRequired(task_id, 'task_id');
|
|
38
|
-
validateUUID(task_id, 'task_id');
|
|
39
|
-
if (approved === undefined) {
|
|
40
|
-
throw new Error('approved is required');
|
|
41
|
-
}
|
|
45
|
+
const { task_id, approved, validation_notes, skip_pr_check } = parseArgs(args, validateTaskSchema);
|
|
42
46
|
const { session } = ctx;
|
|
43
47
|
const currentSessionId = session.currentSessionId;
|
|
44
48
|
const apiClient = getApiClient();
|
|
@@ -59,7 +63,7 @@ export const validateTask = async (args, ctx) => {
|
|
|
59
63
|
},
|
|
60
64
|
};
|
|
61
65
|
}
|
|
62
|
-
|
|
66
|
+
return { result: { error: response.error || 'Failed to validate task' }, isError: true };
|
|
63
67
|
}
|
|
64
68
|
return { result: response.data };
|
|
65
69
|
};
|