@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.
Files changed (65) hide show
  1. package/dist/api-client.d.ts +64 -1
  2. package/dist/api-client.js +34 -3
  3. package/dist/handlers/bodies-of-work.js +82 -49
  4. package/dist/handlers/cost.js +62 -54
  5. package/dist/handlers/decisions.js +29 -16
  6. package/dist/handlers/deployment.js +112 -106
  7. package/dist/handlers/discovery.js +35 -5
  8. package/dist/handlers/fallback.js +24 -19
  9. package/dist/handlers/file-checkouts.d.ts +18 -0
  10. package/dist/handlers/file-checkouts.js +101 -0
  11. package/dist/handlers/findings.d.ts +6 -0
  12. package/dist/handlers/findings.js +85 -30
  13. package/dist/handlers/git-issues.js +36 -32
  14. package/dist/handlers/ideas.js +44 -26
  15. package/dist/handlers/index.d.ts +2 -0
  16. package/dist/handlers/index.js +6 -0
  17. package/dist/handlers/milestones.js +34 -27
  18. package/dist/handlers/organizations.js +86 -78
  19. package/dist/handlers/progress.js +22 -11
  20. package/dist/handlers/project.js +62 -22
  21. package/dist/handlers/requests.js +15 -11
  22. package/dist/handlers/roles.d.ts +18 -0
  23. package/dist/handlers/roles.js +130 -0
  24. package/dist/handlers/session.js +30 -8
  25. package/dist/handlers/sprints.js +76 -64
  26. package/dist/handlers/tasks.js +113 -73
  27. package/dist/handlers/validation.js +18 -14
  28. package/dist/tools.js +387 -0
  29. package/package.json +1 -1
  30. package/src/api-client.ts +89 -6
  31. package/src/handlers/__test-setup__.ts +7 -0
  32. package/src/handlers/bodies-of-work.ts +101 -101
  33. package/src/handlers/cost.test.ts +34 -44
  34. package/src/handlers/cost.ts +77 -92
  35. package/src/handlers/decisions.test.ts +3 -2
  36. package/src/handlers/decisions.ts +32 -27
  37. package/src/handlers/deployment.ts +142 -190
  38. package/src/handlers/discovery.test.ts +4 -5
  39. package/src/handlers/discovery.ts +37 -6
  40. package/src/handlers/fallback.ts +31 -29
  41. package/src/handlers/file-checkouts.test.ts +477 -0
  42. package/src/handlers/file-checkouts.ts +127 -0
  43. package/src/handlers/findings.test.ts +145 -0
  44. package/src/handlers/findings.ts +101 -64
  45. package/src/handlers/git-issues.ts +40 -80
  46. package/src/handlers/ideas.ts +56 -54
  47. package/src/handlers/index.ts +6 -0
  48. package/src/handlers/milestones.test.ts +1 -1
  49. package/src/handlers/milestones.ts +47 -45
  50. package/src/handlers/organizations.ts +104 -129
  51. package/src/handlers/progress.ts +24 -22
  52. package/src/handlers/project.ts +89 -57
  53. package/src/handlers/requests.ts +18 -14
  54. package/src/handlers/roles.test.ts +303 -0
  55. package/src/handlers/roles.ts +208 -0
  56. package/src/handlers/session.ts +39 -17
  57. package/src/handlers/sprints.ts +96 -129
  58. package/src/handlers/tasks.ts +144 -138
  59. package/src/handlers/validation.test.ts +1 -1
  60. package/src/handlers/validation.ts +20 -22
  61. package/src/tools.ts +387 -0
  62. package/dist/config/tool-categories.d.ts +0 -31
  63. package/dist/config/tool-categories.js +0 -253
  64. package/dist/knowledge.d.ts +0 -6
  65. package/dist/knowledge.js +0 -218
@@ -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 = 'lite', model, role = 'developer' } = args;
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 && ['opus', 'sonnet', 'haiku'].includes(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) {
@@ -14,25 +14,76 @@
14
14
  * - get_sprint_backlog
15
15
  * - get_sprint_velocity
16
16
  */
17
- import { validateRequired, validateUUID, validateEnum } from '../validators.js';
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 = false } = args;
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 = 20, offset = 0 } = args;
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 = 10 } = args;
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}`);
@@ -15,8 +15,82 @@
15
15
  * - add_subtask
16
16
  * - get_subtasks
17
17
  */
18
- import { validateRequired, validateUUID, validateTaskStatus, validatePriority, validateProgressPercentage, validateEstimatedMinutes, ValidationError, } from '../validators.js';
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 = 50, offset = 0, search_query, include_subtasks = false, include_metadata = false } = args;
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 = 3, estimated_minutes, blocking = false, task_type } = args;
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, ...updates } = args;
173
- validateRequired(task_id, 'task_id');
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
- if (!updates || !Array.isArray(updates) || updates.length === 0) {
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 (updates.length > 50) {
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
- // Validate all inputs first
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(updates);
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: updates.length,
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
- if (!completions || !Array.isArray(completions) || completions.length === 0) {
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 (completions.length > 50) {
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
- // Validate all inputs first
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(completions);
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: completions.length,
428
+ total: typedCompletions.length,
378
429
  succeeded: data?.completed_count || 0,
379
- failed: completions.length - (data?.completed_count || 0),
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) {