@vibescope/mcp-server 0.2.1 → 0.2.3

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 (93) hide show
  1. package/README.md +63 -38
  2. package/dist/api-client.d.ts +187 -0
  3. package/dist/api-client.js +53 -1
  4. package/dist/handlers/blockers.js +9 -8
  5. package/dist/handlers/bodies-of-work.js +14 -14
  6. package/dist/handlers/connectors.d.ts +45 -0
  7. package/dist/handlers/connectors.js +183 -0
  8. package/dist/handlers/cost.d.ts +10 -0
  9. package/dist/handlers/cost.js +54 -0
  10. package/dist/handlers/decisions.js +3 -3
  11. package/dist/handlers/deployment.js +35 -19
  12. package/dist/handlers/discovery.d.ts +7 -0
  13. package/dist/handlers/discovery.js +61 -2
  14. package/dist/handlers/fallback.js +5 -4
  15. package/dist/handlers/file-checkouts.d.ts +2 -0
  16. package/dist/handlers/file-checkouts.js +38 -6
  17. package/dist/handlers/findings.js +13 -12
  18. package/dist/handlers/git-issues.js +4 -4
  19. package/dist/handlers/ideas.js +5 -5
  20. package/dist/handlers/index.d.ts +1 -0
  21. package/dist/handlers/index.js +3 -0
  22. package/dist/handlers/milestones.js +5 -5
  23. package/dist/handlers/organizations.js +13 -13
  24. package/dist/handlers/progress.js +2 -2
  25. package/dist/handlers/project.js +6 -6
  26. package/dist/handlers/requests.js +3 -3
  27. package/dist/handlers/session.js +28 -9
  28. package/dist/handlers/sprints.js +17 -17
  29. package/dist/handlers/tasks.d.ts +2 -0
  30. package/dist/handlers/tasks.js +78 -20
  31. package/dist/handlers/types.d.ts +64 -2
  32. package/dist/handlers/types.js +48 -1
  33. package/dist/handlers/validation.js +3 -3
  34. package/dist/index.js +7 -2716
  35. package/dist/token-tracking.d.ts +74 -0
  36. package/dist/token-tracking.js +122 -0
  37. package/dist/tools.js +298 -9
  38. package/dist/utils.d.ts +5 -0
  39. package/dist/utils.js +17 -0
  40. package/docs/TOOLS.md +2053 -0
  41. package/package.json +4 -1
  42. package/scripts/generate-docs.ts +212 -0
  43. package/src/api-client.test.ts +723 -0
  44. package/src/api-client.ts +236 -1
  45. package/src/handlers/__test-setup__.ts +9 -0
  46. package/src/handlers/blockers.test.ts +31 -19
  47. package/src/handlers/blockers.ts +9 -8
  48. package/src/handlers/bodies-of-work.test.ts +55 -32
  49. package/src/handlers/bodies-of-work.ts +14 -14
  50. package/src/handlers/connectors.test.ts +834 -0
  51. package/src/handlers/connectors.ts +229 -0
  52. package/src/handlers/cost.ts +66 -0
  53. package/src/handlers/decisions.test.ts +34 -25
  54. package/src/handlers/decisions.ts +3 -3
  55. package/src/handlers/deployment.ts +39 -19
  56. package/src/handlers/discovery.ts +61 -2
  57. package/src/handlers/fallback.test.ts +26 -22
  58. package/src/handlers/fallback.ts +5 -4
  59. package/src/handlers/file-checkouts.test.ts +242 -49
  60. package/src/handlers/file-checkouts.ts +44 -6
  61. package/src/handlers/findings.test.ts +38 -24
  62. package/src/handlers/findings.ts +13 -12
  63. package/src/handlers/git-issues.test.ts +51 -43
  64. package/src/handlers/git-issues.ts +4 -4
  65. package/src/handlers/ideas.test.ts +28 -23
  66. package/src/handlers/ideas.ts +5 -5
  67. package/src/handlers/index.ts +3 -0
  68. package/src/handlers/milestones.test.ts +33 -28
  69. package/src/handlers/milestones.ts +5 -5
  70. package/src/handlers/organizations.test.ts +104 -83
  71. package/src/handlers/organizations.ts +13 -13
  72. package/src/handlers/progress.test.ts +20 -14
  73. package/src/handlers/progress.ts +2 -2
  74. package/src/handlers/project.test.ts +34 -27
  75. package/src/handlers/project.ts +6 -6
  76. package/src/handlers/requests.test.ts +27 -18
  77. package/src/handlers/requests.ts +3 -3
  78. package/src/handlers/session.test.ts +47 -0
  79. package/src/handlers/session.ts +26 -9
  80. package/src/handlers/sprints.test.ts +71 -50
  81. package/src/handlers/sprints.ts +17 -17
  82. package/src/handlers/tasks.test.ts +77 -15
  83. package/src/handlers/tasks.ts +90 -21
  84. package/src/handlers/tool-categories.test.ts +66 -0
  85. package/src/handlers/types.ts +81 -2
  86. package/src/handlers/validation.test.ts +78 -45
  87. package/src/handlers/validation.ts +3 -3
  88. package/src/index.ts +12 -2732
  89. package/src/token-tracking.test.ts +453 -0
  90. package/src/token-tracking.ts +164 -0
  91. package/src/tools.ts +298 -9
  92. package/src/utils.test.ts +2 -2
  93. package/src/utils.ts +17 -0
@@ -88,13 +88,13 @@ export const createSprint = async (args, ctx) => {
88
88
  const startDateObj = new Date(start_date);
89
89
  const endDateObj = new Date(end_date);
90
90
  if (isNaN(startDateObj.getTime())) {
91
- throw new Error('Invalid start_date format. Use YYYY-MM-DD');
91
+ return { result: { error: 'Invalid start_date format. Use YYYY-MM-DD' }, isError: true };
92
92
  }
93
93
  if (isNaN(endDateObj.getTime())) {
94
- throw new Error('Invalid end_date format. Use YYYY-MM-DD');
94
+ return { result: { error: 'Invalid end_date format. Use YYYY-MM-DD' }, isError: true };
95
95
  }
96
96
  if (endDateObj < startDateObj) {
97
- throw new Error('end_date must be on or after start_date');
97
+ return { result: { error: 'end_date must be on or after start_date' }, isError: true };
98
98
  }
99
99
  const { session } = ctx;
100
100
  const apiClient = getApiClient();
@@ -113,7 +113,7 @@ export const createSprint = async (args, ctx) => {
113
113
  instance_id: session.instanceId
114
114
  });
115
115
  if (!response.ok) {
116
- throw new Error(`Failed to create sprint: ${response.error}`);
116
+ return { result: { error: response.error || 'Failed to create sprint' }, isError: true };
117
117
  }
118
118
  return {
119
119
  result: {
@@ -134,13 +134,13 @@ export const updateSprint = async (args, ctx) => {
134
134
  if (start_date) {
135
135
  const startDateObj = new Date(start_date);
136
136
  if (isNaN(startDateObj.getTime())) {
137
- throw new Error('Invalid start_date format. Use YYYY-MM-DD');
137
+ return { result: { error: 'Invalid start_date format. Use YYYY-MM-DD' }, isError: true };
138
138
  }
139
139
  }
140
140
  if (end_date) {
141
141
  const endDateObj = new Date(end_date);
142
142
  if (isNaN(endDateObj.getTime())) {
143
- throw new Error('Invalid end_date format. Use YYYY-MM-DD');
143
+ return { result: { error: 'Invalid end_date format. Use YYYY-MM-DD' }, isError: true };
144
144
  }
145
145
  }
146
146
  const apiClient = getApiClient();
@@ -155,7 +155,7 @@ export const updateSprint = async (args, ctx) => {
155
155
  deploy_version_bump,
156
156
  });
157
157
  if (!response.ok) {
158
- throw new Error(`Failed to update sprint: ${response.error}`);
158
+ return { result: { error: response.error || 'Failed to update sprint' }, isError: true };
159
159
  }
160
160
  return { result: { success: true, sprint_id } };
161
161
  };
@@ -165,7 +165,7 @@ export const getSprint = async (args, ctx) => {
165
165
  // Response type varies based on summary_only
166
166
  const response = await apiClient.proxy('get_sprint', { sprint_id, summary_only });
167
167
  if (!response.ok) {
168
- throw new Error(`Failed to get sprint: ${response.error}`);
168
+ return { result: { error: response.error || 'Failed to get sprint' }, isError: true };
169
169
  }
170
170
  return { result: response.data };
171
171
  };
@@ -179,7 +179,7 @@ export const getSprints = async (args, ctx) => {
179
179
  offset,
180
180
  });
181
181
  if (!response.ok) {
182
- throw new Error(`Failed to fetch sprints: ${response.error}`);
182
+ return { result: { error: response.error || 'Failed to fetch sprints' }, isError: true };
183
183
  }
184
184
  return { result: response.data };
185
185
  };
@@ -190,7 +190,7 @@ export const deleteSprint = async (args, ctx) => {
190
190
  sprint_id
191
191
  });
192
192
  if (!response.ok) {
193
- throw new Error(`Failed to delete sprint: ${response.error}`);
193
+ return { result: { error: response.error || 'Failed to delete sprint' }, isError: true };
194
194
  }
195
195
  return { result: { success: true, message: 'Sprint deleted. Tasks are preserved.' } };
196
196
  };
@@ -199,7 +199,7 @@ export const startSprint = async (args, ctx) => {
199
199
  const apiClient = getApiClient();
200
200
  const response = await apiClient.proxy('start_sprint', { sprint_id });
201
201
  if (!response.ok) {
202
- throw new Error(`Failed to start sprint: ${response.error}`);
202
+ return { result: { error: response.error || 'Failed to start sprint' }, isError: true };
203
203
  }
204
204
  return { result: response.data };
205
205
  };
@@ -212,14 +212,14 @@ export const completeSprint = async (args, ctx) => {
212
212
  skip_retrospective,
213
213
  });
214
214
  if (!response.ok) {
215
- throw new Error(`Failed to complete sprint: ${response.error}`);
215
+ return { result: { error: response.error || 'Failed to complete sprint' }, isError: true };
216
216
  }
217
217
  return { result: response.data };
218
218
  };
219
219
  export const addTaskToSprint = async (args, ctx) => {
220
220
  const { sprint_id, task_id, story_points, phase } = parseArgs(args, addTaskToSprintSchema);
221
221
  if (story_points !== undefined && (story_points < 0 || !Number.isInteger(story_points))) {
222
- throw new Error('story_points must be a non-negative integer');
222
+ return { result: { error: 'story_points must be a non-negative integer' }, isError: true };
223
223
  }
224
224
  const apiClient = getApiClient();
225
225
  const response = await apiClient.proxy('add_task_to_sprint', {
@@ -229,7 +229,7 @@ export const addTaskToSprint = async (args, ctx) => {
229
229
  phase: phase || 'core',
230
230
  });
231
231
  if (!response.ok) {
232
- throw new Error(`Failed to add task to sprint: ${response.error}`);
232
+ return { result: { error: response.error || 'Failed to add task to sprint' }, isError: true };
233
233
  }
234
234
  return { result: response.data };
235
235
  };
@@ -241,7 +241,7 @@ export const removeTaskFromSprint = async (args, ctx) => {
241
241
  task_id,
242
242
  });
243
243
  if (!response.ok) {
244
- throw new Error(`Failed to remove task from sprint: ${response.error}`);
244
+ return { result: { error: response.error || 'Failed to remove task from sprint' }, isError: true };
245
245
  }
246
246
  return { result: response.data };
247
247
  };
@@ -253,7 +253,7 @@ export const getSprintBacklog = async (args, ctx) => {
253
253
  sprint_id,
254
254
  });
255
255
  if (!response.ok) {
256
- throw new Error(`Failed to get sprint backlog: ${response.error}`);
256
+ return { result: { error: response.error || 'Failed to get sprint backlog' }, isError: true };
257
257
  }
258
258
  return { result: response.data };
259
259
  };
@@ -265,7 +265,7 @@ export const getSprintVelocity = async (args, ctx) => {
265
265
  limit: Math.min(limit ?? 10, 50),
266
266
  });
267
267
  if (!response.ok) {
268
- throw new Error(`Failed to get sprint velocity: ${response.error}`);
268
+ return { result: { error: response.error || 'Failed to get sprint velocity' }, isError: true };
269
269
  }
270
270
  return { result: response.data };
271
271
  };
@@ -42,6 +42,8 @@ export declare const batchUpdateTasks: Handler;
42
42
  export declare const batchCompleteTasks: Handler;
43
43
  export declare const addSubtask: Handler;
44
44
  export declare const getSubtasks: Handler;
45
+ export declare const getStaleWorktrees: Handler;
46
+ export declare const clearWorktreePath: Handler;
45
47
  /**
46
48
  * Task handlers registry
47
49
  */
@@ -56,7 +56,9 @@ const updateTaskSchema = {
56
56
  progress_note: { type: 'string' },
57
57
  estimated_minutes: { type: 'number', validate: minutesValidator },
58
58
  git_branch: { type: 'string' },
59
+ worktree_path: { type: 'string' },
59
60
  task_type: { type: 'string', validate: createEnumValidator(VALID_TASK_TYPES) },
61
+ skip_worktree_requirement: { type: 'boolean', default: false },
60
62
  };
61
63
  const completeTaskSchema = {
62
64
  task_id: { type: 'string', required: true, validate: uuidValidator },
@@ -153,7 +155,7 @@ export const getTasks = async (args, ctx) => {
153
155
  include_metadata,
154
156
  });
155
157
  if (!response.ok) {
156
- throw new Error(`Failed to fetch tasks: ${response.error}`);
158
+ return { result: { error: response.error || 'Failed to fetch tasks' }, isError: true };
157
159
  }
158
160
  return {
159
161
  result: {
@@ -168,7 +170,7 @@ export const getNextTask = async (args, ctx) => {
168
170
  const api = getApiClient();
169
171
  const response = await api.getNextTask(project_id, ctx.session.currentSessionId || undefined);
170
172
  if (!response.ok) {
171
- throw new Error(`Failed to get next task: ${response.error}`);
173
+ return { result: { error: response.error || 'Failed to get next task' }, isError: true };
172
174
  }
173
175
  const data = response.data;
174
176
  if (!data) {
@@ -219,7 +221,7 @@ export const addTask = async (args, ctx) => {
219
221
  task_type,
220
222
  });
221
223
  if (!response.ok) {
222
- throw new Error(`Failed to add task: ${response.error}`);
224
+ return { result: { error: response.error || 'Failed to add task' }, isError: true };
223
225
  }
224
226
  const data = response.data;
225
227
  const result = {
@@ -234,8 +236,24 @@ export const addTask = async (args, ctx) => {
234
236
  return { result };
235
237
  };
236
238
  export const updateTask = async (args, ctx) => {
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 };
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
+ }
239
257
  const api = getApiClient();
240
258
  const response = await api.updateTask(task_id, {
241
259
  ...updates,
@@ -278,7 +296,7 @@ export const updateTask = async (args, ctx) => {
278
296
  },
279
297
  };
280
298
  }
281
- throw new Error(`Failed to update task: ${response.error}`);
299
+ return { result: { error: response.error || 'Failed to update task' }, isError: true };
282
300
  }
283
301
  // Build result - include git workflow info when transitioning to in_progress
284
302
  const data = response.data;
@@ -292,11 +310,6 @@ export const updateTask = async (args, ctx) => {
292
310
  if (data?.next_step) {
293
311
  result.next_step = data.next_step;
294
312
  }
295
- // Warn if transitioning to in_progress without git_branch
296
- if (updates.status === 'in_progress' && !updates.git_branch) {
297
- result.warning = 'git_branch not set. For multi-agent collaboration, set git_branch when marking in_progress to track your worktree.';
298
- result.hint = 'Call update_task again with git_branch parameter after creating your worktree.';
299
- }
300
313
  return { result };
301
314
  };
302
315
  export const completeTask = async (args, ctx) => {
@@ -307,11 +320,11 @@ export const completeTask = async (args, ctx) => {
307
320
  session_id: ctx.session.currentSessionId || undefined,
308
321
  });
309
322
  if (!response.ok) {
310
- throw new Error(`Failed to complete task: ${response.error}`);
323
+ return { result: { error: response.error || 'Failed to complete task' }, isError: true };
311
324
  }
312
325
  const data = response.data;
313
326
  if (!data) {
314
- throw new Error('No response data from complete task');
327
+ return { result: { error: 'No response data from complete task' }, isError: true };
315
328
  }
316
329
  // Build result matching expected format
317
330
  const result = {
@@ -338,7 +351,7 @@ export const deleteTask = async (args, ctx) => {
338
351
  const api = getApiClient();
339
352
  const response = await api.deleteTask(task_id);
340
353
  if (!response.ok) {
341
- throw new Error(`Failed to delete task: ${response.error}`);
354
+ return { result: { error: response.error || 'Failed to delete task' }, isError: true };
342
355
  }
343
356
  return { result: { success: true, deleted_id: task_id } };
344
357
  };
@@ -350,7 +363,7 @@ export const addTaskReference = async (args, ctx) => {
350
363
  if (response.error?.includes('already exists')) {
351
364
  return { result: { success: false, error: 'Reference with this URL already exists' } };
352
365
  }
353
- throw new Error(`Failed to add reference: ${response.error}`);
366
+ return { result: { error: response.error || 'Failed to add reference' }, isError: true };
354
367
  }
355
368
  return {
356
369
  result: {
@@ -367,7 +380,7 @@ export const removeTaskReference = async (args, ctx) => {
367
380
  if (response.error?.includes('not found')) {
368
381
  return { result: { success: false, error: 'Reference with this URL not found' } };
369
382
  }
370
- throw new Error(`Failed to remove reference: ${response.error}`);
383
+ return { result: { error: response.error || 'Failed to remove reference' }, isError: true };
371
384
  }
372
385
  return { result: { success: true } };
373
386
  };
@@ -390,7 +403,7 @@ export const batchUpdateTasks = async (args, ctx) => {
390
403
  const api = getApiClient();
391
404
  const response = await api.batchUpdateTasks(typedUpdates);
392
405
  if (!response.ok) {
393
- throw new Error(`Failed to batch update tasks: ${response.error}`);
406
+ return { result: { error: response.error || 'Failed to batch update tasks' }, isError: true };
394
407
  }
395
408
  return {
396
409
  result: {
@@ -419,7 +432,7 @@ export const batchCompleteTasks = async (args, ctx) => {
419
432
  const api = getApiClient();
420
433
  const response = await api.batchCompleteTasks(typedCompletions);
421
434
  if (!response.ok) {
422
- throw new Error(`Failed to batch complete tasks: ${response.error}`);
435
+ return { result: { error: response.error || 'Failed to batch complete tasks' }, isError: true };
423
436
  }
424
437
  const data = response.data;
425
438
  return {
@@ -454,7 +467,7 @@ export const addSubtask = async (args, ctx) => {
454
467
  },
455
468
  };
456
469
  }
457
- throw new Error(`Failed to add subtask: ${response.error}`);
470
+ return { result: { error: response.error || 'Failed to add subtask' }, isError: true };
458
471
  }
459
472
  return {
460
473
  result: {
@@ -469,7 +482,7 @@ export const getSubtasks = async (args, ctx) => {
469
482
  const api = getApiClient();
470
483
  const response = await api.getSubtasks(parent_task_id, status);
471
484
  if (!response.ok) {
472
- throw new Error(`Failed to fetch subtasks: ${response.error}`);
485
+ return { result: { error: response.error || 'Failed to fetch subtasks' }, isError: true };
473
486
  }
474
487
  return {
475
488
  result: {
@@ -482,6 +495,48 @@ export const getSubtasks = async (args, ctx) => {
482
495
  },
483
496
  };
484
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
+ };
485
540
  /**
486
541
  * Task handlers registry
487
542
  */
@@ -499,4 +554,7 @@ export const taskHandlers = {
499
554
  // Subtask handlers
500
555
  add_subtask: addSubtask,
501
556
  get_subtasks: getSubtasks,
557
+ // Worktree cleanup handlers
558
+ get_stale_worktrees: getStaleWorktrees,
559
+ clear_worktree_path: clearWorktreePath,
502
560
  };
@@ -82,9 +82,40 @@ export interface HandlerContext {
82
82
  extractProjectNameFromGitUrl?: (gitUrl: string) => string;
83
83
  }
84
84
  /**
85
- * Result returned by handlers
85
+ * Success result with typed data
86
86
  */
87
- export interface HandlerResult {
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
  */
@@ -1 +1,48 @@
1
- export {};
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
+ }
@@ -26,7 +26,7 @@ export const getTasksAwaitingValidation = async (args, _ctx) => {
26
26
  const apiClient = getApiClient();
27
27
  const response = await apiClient.getTasksAwaitingValidation(project_id);
28
28
  if (!response.ok) {
29
- throw new Error(response.error || 'Failed to fetch tasks awaiting validation');
29
+ return { result: { error: response.error || 'Failed to fetch tasks awaiting validation' }, isError: true };
30
30
  }
31
31
  return { result: response.data };
32
32
  };
@@ -37,7 +37,7 @@ export const claimValidation = async (args, ctx) => {
37
37
  const apiClient = getApiClient();
38
38
  const response = await apiClient.claimValidation(task_id, currentSessionId || undefined);
39
39
  if (!response.ok) {
40
- throw new Error(response.error || 'Failed to claim task for validation');
40
+ return { result: { error: response.error || 'Failed to claim task for validation' }, isError: true };
41
41
  }
42
42
  return { result: response.data };
43
43
  };
@@ -63,7 +63,7 @@ export const validateTask = async (args, ctx) => {
63
63
  },
64
64
  };
65
65
  }
66
- throw new Error(response.error || 'Failed to validate task');
66
+ return { result: { error: response.error || 'Failed to validate task' }, isError: true };
67
67
  }
68
68
  return { result: response.data };
69
69
  };