@vibescope/mcp-server 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +1 -1
  2. package/dist/api-client.d.ts +120 -2
  3. package/dist/api-client.js +51 -5
  4. package/dist/handlers/bodies-of-work.js +84 -50
  5. package/dist/handlers/cost.js +62 -54
  6. package/dist/handlers/decisions.js +29 -16
  7. package/dist/handlers/deployment.js +114 -107
  8. package/dist/handlers/discovery.d.ts +3 -0
  9. package/dist/handlers/discovery.js +55 -657
  10. package/dist/handlers/fallback.js +42 -28
  11. package/dist/handlers/file-checkouts.d.ts +18 -0
  12. package/dist/handlers/file-checkouts.js +101 -0
  13. package/dist/handlers/findings.d.ts +14 -1
  14. package/dist/handlers/findings.js +104 -28
  15. package/dist/handlers/git-issues.js +36 -32
  16. package/dist/handlers/ideas.js +44 -26
  17. package/dist/handlers/index.d.ts +2 -0
  18. package/dist/handlers/index.js +6 -0
  19. package/dist/handlers/milestones.js +34 -27
  20. package/dist/handlers/organizations.js +86 -78
  21. package/dist/handlers/progress.js +22 -11
  22. package/dist/handlers/project.js +62 -22
  23. package/dist/handlers/requests.js +15 -11
  24. package/dist/handlers/roles.d.ts +18 -0
  25. package/dist/handlers/roles.js +130 -0
  26. package/dist/handlers/session.js +52 -15
  27. package/dist/handlers/sprints.js +78 -65
  28. package/dist/handlers/tasks.js +135 -74
  29. package/dist/handlers/tool-docs.d.ts +4 -3
  30. package/dist/handlers/tool-docs.js +252 -5
  31. package/dist/handlers/validation.js +30 -14
  32. package/dist/index.js +25 -7
  33. package/dist/tools.js +417 -4
  34. package/package.json +1 -1
  35. package/src/api-client.ts +161 -8
  36. package/src/handlers/__test-setup__.ts +12 -0
  37. package/src/handlers/bodies-of-work.ts +127 -111
  38. package/src/handlers/cost.test.ts +34 -44
  39. package/src/handlers/cost.ts +77 -92
  40. package/src/handlers/decisions.test.ts +3 -2
  41. package/src/handlers/decisions.ts +32 -27
  42. package/src/handlers/deployment.ts +144 -190
  43. package/src/handlers/discovery.test.ts +4 -5
  44. package/src/handlers/discovery.ts +60 -746
  45. package/src/handlers/fallback.test.ts +78 -0
  46. package/src/handlers/fallback.ts +51 -38
  47. package/src/handlers/file-checkouts.test.ts +477 -0
  48. package/src/handlers/file-checkouts.ts +127 -0
  49. package/src/handlers/findings.test.ts +274 -2
  50. package/src/handlers/findings.ts +123 -57
  51. package/src/handlers/git-issues.ts +40 -80
  52. package/src/handlers/ideas.ts +56 -54
  53. package/src/handlers/index.ts +6 -0
  54. package/src/handlers/milestones.test.ts +1 -1
  55. package/src/handlers/milestones.ts +47 -45
  56. package/src/handlers/organizations.ts +104 -129
  57. package/src/handlers/progress.ts +24 -22
  58. package/src/handlers/project.ts +89 -57
  59. package/src/handlers/requests.ts +18 -14
  60. package/src/handlers/roles.test.ts +303 -0
  61. package/src/handlers/roles.ts +208 -0
  62. package/src/handlers/session.test.ts +37 -2
  63. package/src/handlers/session.ts +64 -21
  64. package/src/handlers/sprints.ts +114 -134
  65. package/src/handlers/tasks.test.ts +61 -0
  66. package/src/handlers/tasks.ts +170 -139
  67. package/src/handlers/tool-docs.ts +1024 -0
  68. package/src/handlers/validation.test.ts +53 -1
  69. package/src/handlers/validation.ts +32 -21
  70. package/src/index.ts +25 -7
  71. package/src/tools.ts +417 -4
  72. package/dist/config/tool-categories.d.ts +0 -31
  73. package/dist/config/tool-categories.js +0 -253
  74. package/dist/knowledge.d.ts +0 -6
  75. package/dist/knowledge.js +0 -218
  76. package/src/knowledge.ts +0 -230
@@ -18,16 +18,105 @@
18
18
 
19
19
  import type { Handler, HandlerRegistry } from './types.js';
20
20
  import {
21
- validateRequired,
22
- validateUUID,
23
- validateTaskStatus,
24
- validatePriority,
25
- validateProgressPercentage,
26
- validateEstimatedMinutes,
21
+ parseArgs,
22
+ uuidValidator,
23
+ taskStatusValidator,
24
+ priorityValidator,
25
+ progressValidator,
26
+ minutesValidator,
27
+ createEnumValidator,
27
28
  ValidationError,
28
29
  } from '../validators.js';
29
30
  import { getApiClient } from '../api-client.js';
30
31
 
32
+ // Valid task types
33
+ const VALID_TASK_TYPES = [
34
+ 'frontend', 'backend', 'database', 'feature', 'bugfix',
35
+ 'design', 'mcp', 'testing', 'docs', 'infra', 'other'
36
+ ] as const;
37
+
38
+ // ============================================================================
39
+ // Argument Schemas
40
+ // ============================================================================
41
+
42
+ const getTasksSchema = {
43
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
44
+ status: { type: 'string' as const, validate: taskStatusValidator },
45
+ limit: { type: 'number' as const, default: 50 },
46
+ offset: { type: 'number' as const, default: 0 },
47
+ search_query: { type: 'string' as const },
48
+ include_subtasks: { type: 'boolean' as const, default: false },
49
+ include_metadata: { type: 'boolean' as const, default: false },
50
+ };
51
+
52
+ const getNextTaskSchema = {
53
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
54
+ };
55
+
56
+ const addTaskSchema = {
57
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
58
+ title: { type: 'string' as const, required: true as const },
59
+ description: { type: 'string' as const },
60
+ priority: { type: 'number' as const, default: 3, validate: priorityValidator },
61
+ estimated_minutes: { type: 'number' as const, validate: minutesValidator },
62
+ blocking: { type: 'boolean' as const, default: false },
63
+ task_type: { type: 'string' as const, validate: createEnumValidator(VALID_TASK_TYPES) },
64
+ };
65
+
66
+ const updateTaskSchema = {
67
+ task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
68
+ title: { type: 'string' as const },
69
+ description: { type: 'string' as const },
70
+ priority: { type: 'number' as const, validate: priorityValidator },
71
+ status: { type: 'string' as const, validate: taskStatusValidator },
72
+ progress_percentage: { type: 'number' as const, validate: progressValidator },
73
+ progress_note: { type: 'string' as const },
74
+ estimated_minutes: { type: 'number' as const, validate: minutesValidator },
75
+ git_branch: { type: 'string' as const },
76
+ task_type: { type: 'string' as const, validate: createEnumValidator(VALID_TASK_TYPES) },
77
+ };
78
+
79
+ const completeTaskSchema = {
80
+ task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
81
+ summary: { type: 'string' as const },
82
+ };
83
+
84
+ const deleteTaskSchema = {
85
+ task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
86
+ };
87
+
88
+ const addTaskReferenceSchema = {
89
+ task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
90
+ url: { type: 'string' as const, required: true as const },
91
+ label: { type: 'string' as const },
92
+ };
93
+
94
+ const removeTaskReferenceSchema = {
95
+ task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
96
+ url: { type: 'string' as const, required: true as const },
97
+ };
98
+
99
+ const batchUpdateTasksSchema = {
100
+ updates: { type: 'array' as const, required: true as const },
101
+ };
102
+
103
+ const batchCompleteTasksSchema = {
104
+ completions: { type: 'array' as const, required: true as const },
105
+ };
106
+
107
+ const addSubtaskSchema = {
108
+ parent_task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
109
+ title: { type: 'string' as const, required: true as const },
110
+ description: { type: 'string' as const },
111
+ priority: { type: 'number' as const, validate: priorityValidator },
112
+ estimated_minutes: { type: 'number' as const, validate: minutesValidator },
113
+ };
114
+
115
+ const getSubtasksSchema = {
116
+ parent_task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
117
+ status: { type: 'string' as const, validate: taskStatusValidator },
118
+ };
119
+
31
120
  // ============================================================================
32
121
  // Git workflow helpers (used by complete_task response)
33
122
  // ============================================================================
@@ -126,24 +215,12 @@ export function getValidationApprovedGitInstructions(
126
215
  // ============================================================================
127
216
 
128
217
  export const getTasks: Handler = async (args, ctx) => {
129
- const { project_id, status, limit = 50, offset = 0, search_query, include_subtasks = false, include_metadata = false } = args as {
130
- project_id: string;
131
- status?: string;
132
- limit?: number;
133
- offset?: number;
134
- search_query?: string;
135
- include_subtasks?: boolean;
136
- include_metadata?: boolean; // When true, returns all task fields; when false (default), only id/title/priority/status
137
- };
138
-
139
- validateRequired(project_id, 'project_id');
140
- validateUUID(project_id, 'project_id');
141
- validateTaskStatus(status);
218
+ const { project_id, status, limit, offset, search_query, include_subtasks, include_metadata } = parseArgs(args, getTasksSchema);
142
219
 
143
220
  const api = getApiClient();
144
221
  const response = await api.getTasks(project_id, {
145
222
  status,
146
- limit: Math.min(limit, 200),
223
+ limit: Math.min(limit ?? 50, 200),
147
224
  offset,
148
225
  include_subtasks,
149
226
  search_query,
@@ -164,10 +241,7 @@ export const getTasks: Handler = async (args, ctx) => {
164
241
  };
165
242
 
166
243
  export const getNextTask: Handler = async (args, ctx) => {
167
- const { project_id } = args as { project_id: string };
168
-
169
- validateRequired(project_id, 'project_id');
170
- validateUUID(project_id, 'project_id');
244
+ const { project_id } = parseArgs(args, getNextTaskSchema);
171
245
 
172
246
  const api = getApiClient();
173
247
  const response = await api.getNextTask(project_id, ctx.session.currentSessionId || undefined);
@@ -211,21 +285,7 @@ export const getNextTask: Handler = async (args, ctx) => {
211
285
  };
212
286
 
213
287
  export const addTask: Handler = async (args, ctx) => {
214
- const { project_id, title, description, priority = 3, estimated_minutes, blocking = false, task_type } = args as {
215
- project_id: string;
216
- title: string;
217
- description?: string;
218
- priority?: number;
219
- estimated_minutes?: number;
220
- blocking?: boolean;
221
- task_type?: string;
222
- };
223
-
224
- validateRequired(project_id, 'project_id');
225
- validateRequired(title, 'title');
226
- validateUUID(project_id, 'project_id');
227
- validatePriority(priority);
228
- validateEstimatedMinutes(estimated_minutes);
288
+ const { project_id, title, description, priority, estimated_minutes, blocking, task_type } = parseArgs(args, addTaskSchema);
229
289
 
230
290
  const api = getApiClient();
231
291
  const response = await api.createTask(project_id, {
@@ -235,6 +295,7 @@ export const addTask: Handler = async (args, ctx) => {
235
295
  estimated_minutes,
236
296
  blocking,
237
297
  session_id: ctx.session.currentSessionId || undefined,
298
+ task_type,
238
299
  });
239
300
 
240
301
  if (!response.ok) {
@@ -257,25 +318,8 @@ export const addTask: Handler = async (args, ctx) => {
257
318
  };
258
319
 
259
320
  export const updateTask: Handler = async (args, ctx) => {
260
- const { task_id, progress_note, ...updates } = args as {
261
- task_id: string;
262
- title?: string;
263
- description?: string;
264
- priority?: number;
265
- status?: string;
266
- progress_percentage?: number;
267
- progress_note?: string;
268
- estimated_minutes?: number;
269
- git_branch?: string;
270
- task_type?: string;
271
- };
272
-
273
- validateRequired(task_id, 'task_id');
274
- validateUUID(task_id, 'task_id');
275
- validateTaskStatus(updates.status);
276
- validatePriority(updates.priority);
277
- validateProgressPercentage(updates.progress_percentage);
278
- validateEstimatedMinutes(updates.estimated_minutes);
321
+ const { task_id, title, description, priority, status, progress_percentage, progress_note, estimated_minutes, git_branch, task_type } = parseArgs(args, updateTaskSchema);
322
+ const updates = { title, description, priority, status, progress_percentage, estimated_minutes, git_branch, task_type };
279
323
 
280
324
  const api = getApiClient();
281
325
  const response = await api.updateTask(task_id, {
@@ -310,20 +354,44 @@ export const updateTask: Handler = async (args, ctx) => {
310
354
  },
311
355
  };
312
356
  }
357
+ if (response.error?.includes('branch_conflict')) {
358
+ return {
359
+ result: {
360
+ error: 'branch_conflict',
361
+ message: response.error,
362
+ conflicting_task_id: (response.data as { conflicting_task_id?: string })?.conflicting_task_id,
363
+ conflicting_task_title: (response.data as { conflicting_task_title?: string })?.conflicting_task_title,
364
+ },
365
+ };
366
+ }
313
367
  throw new Error(`Failed to update task: ${response.error}`);
314
368
  }
315
369
 
316
- return { result: { success: true, task_id } };
370
+ // Build result - include git workflow info when transitioning to in_progress
371
+ const data = response.data;
372
+ const result: Record<string, unknown> = { success: true, task_id };
373
+
374
+ if (data?.git_workflow) {
375
+ result.git_workflow = data.git_workflow;
376
+ }
377
+ if (data?.worktree_setup) {
378
+ result.worktree_setup = data.worktree_setup;
379
+ }
380
+ if (data?.next_step) {
381
+ result.next_step = data.next_step;
382
+ }
383
+
384
+ // Warn if transitioning to in_progress without git_branch
385
+ if (updates.status === 'in_progress' && !updates.git_branch) {
386
+ result.warning = 'git_branch not set. For multi-agent collaboration, set git_branch when marking in_progress to track your worktree.';
387
+ result.hint = 'Call update_task again with git_branch parameter after creating your worktree.';
388
+ }
389
+
390
+ return { result };
317
391
  };
318
392
 
319
393
  export const completeTask: Handler = async (args, ctx) => {
320
- const { task_id, summary } = args as {
321
- task_id: string;
322
- summary?: string;
323
- };
324
-
325
- validateRequired(task_id, 'task_id');
326
- validateUUID(task_id, 'task_id');
394
+ const { task_id, summary } = parseArgs(args, completeTaskSchema);
327
395
 
328
396
  const api = getApiClient();
329
397
  const response = await api.completeTask(task_id, {
@@ -353,6 +421,11 @@ export const completeTask: Handler = async (args, ctx) => {
353
421
  result.context = data.context;
354
422
  }
355
423
 
424
+ // Pass through warnings (e.g., missing git_branch)
425
+ if (data.warnings) {
426
+ result.warnings = data.warnings;
427
+ }
428
+
356
429
  // Git workflow instructions are already in API response but we need to fetch
357
430
  // task details if we want to include them (API should return these)
358
431
  result.next_action = data.next_action;
@@ -361,10 +434,7 @@ export const completeTask: Handler = async (args, ctx) => {
361
434
  };
362
435
 
363
436
  export const deleteTask: Handler = async (args, ctx) => {
364
- const { task_id } = args as { task_id: string };
365
-
366
- validateRequired(task_id, 'task_id');
367
- validateUUID(task_id, 'task_id');
437
+ const { task_id } = parseArgs(args, deleteTaskSchema);
368
438
 
369
439
  const api = getApiClient();
370
440
  const response = await api.deleteTask(task_id);
@@ -377,11 +447,7 @@ export const deleteTask: Handler = async (args, ctx) => {
377
447
  };
378
448
 
379
449
  export const addTaskReference: Handler = async (args, ctx) => {
380
- const { task_id, url, label } = args as { task_id: string; url: string; label?: string };
381
-
382
- validateRequired(task_id, 'task_id');
383
- validateUUID(task_id, 'task_id');
384
- validateRequired(url, 'url');
450
+ const { task_id, url, label } = parseArgs(args, addTaskReferenceSchema);
385
451
 
386
452
  const api = getApiClient();
387
453
  const response = await api.addTaskReference(task_id, url, label);
@@ -402,11 +468,7 @@ export const addTaskReference: Handler = async (args, ctx) => {
402
468
  };
403
469
 
404
470
  export const removeTaskReference: Handler = async (args, ctx) => {
405
- const { task_id, url } = args as { task_id: string; url: string };
406
-
407
- validateRequired(task_id, 'task_id');
408
- validateUUID(task_id, 'task_id');
409
- validateRequired(url, 'url');
471
+ const { task_id, url } = parseArgs(args, removeTaskReferenceSchema);
410
472
 
411
473
  const api = getApiClient();
412
474
  const response = await api.removeTaskReference(task_id, url);
@@ -422,41 +484,33 @@ export const removeTaskReference: Handler = async (args, ctx) => {
422
484
  };
423
485
 
424
486
  export const batchUpdateTasks: Handler = async (args, ctx) => {
425
- const { updates } = args as {
426
- updates: Array<{
427
- task_id: string;
428
- status?: string;
429
- progress_percentage?: number;
430
- progress_note?: string;
431
- priority?: number;
432
- }>;
433
- };
487
+ const { updates } = parseArgs(args, batchUpdateTasksSchema);
434
488
 
435
- if (!updates || !Array.isArray(updates) || updates.length === 0) {
489
+ const typedUpdates = updates as Array<{
490
+ task_id: string;
491
+ status?: string;
492
+ progress_percentage?: number;
493
+ progress_note?: string;
494
+ priority?: number;
495
+ }>;
496
+
497
+ if (!Array.isArray(typedUpdates) || typedUpdates.length === 0) {
436
498
  throw new ValidationError('updates must be a non-empty array', {
437
499
  field: 'updates',
438
500
  hint: 'Provide an array of task updates with at least one item',
439
501
  });
440
502
  }
441
503
 
442
- if (updates.length > 50) {
504
+ if (typedUpdates.length > 50) {
443
505
  throw new ValidationError('Too many updates. Maximum is 50 per batch.', {
444
506
  field: 'updates',
445
507
  hint: 'Split your updates into smaller batches',
446
508
  });
447
509
  }
448
510
 
449
- // Validate all inputs first
450
- for (const update of updates) {
451
- validateRequired(update.task_id, 'task_id');
452
- validateUUID(update.task_id, 'task_id');
453
- validateTaskStatus(update.status);
454
- validatePriority(update.priority);
455
- validateProgressPercentage(update.progress_percentage);
456
- }
457
-
511
+ // Individual item validation happens at API level
458
512
  const api = getApiClient();
459
- const response = await api.batchUpdateTasks(updates);
513
+ const response = await api.batchUpdateTasks(typedUpdates);
460
514
 
461
515
  if (!response.ok) {
462
516
  throw new Error(`Failed to batch update tasks: ${response.error}`);
@@ -465,42 +519,38 @@ export const batchUpdateTasks: Handler = async (args, ctx) => {
465
519
  return {
466
520
  result: {
467
521
  success: response.data?.success || false,
468
- total: updates.length,
522
+ total: typedUpdates.length,
469
523
  succeeded: response.data?.updated_count || 0,
470
524
  },
471
525
  };
472
526
  };
473
527
 
474
528
  export const batchCompleteTasks: Handler = async (args, ctx) => {
475
- const { completions } = args as {
476
- completions: Array<{
477
- task_id: string;
478
- summary?: string;
479
- }>;
480
- };
529
+ const { completions } = parseArgs(args, batchCompleteTasksSchema);
481
530
 
482
- if (!completions || !Array.isArray(completions) || completions.length === 0) {
531
+ const typedCompletions = completions as Array<{
532
+ task_id: string;
533
+ summary?: string;
534
+ }>;
535
+
536
+ if (!Array.isArray(typedCompletions) || typedCompletions.length === 0) {
483
537
  throw new ValidationError('completions must be a non-empty array', {
484
538
  field: 'completions',
485
539
  hint: 'Provide an array of task completions with at least one item',
486
540
  });
487
541
  }
488
542
 
489
- if (completions.length > 50) {
543
+ if (typedCompletions.length > 50) {
490
544
  throw new ValidationError('Too many completions. Maximum is 50 per batch.', {
491
545
  field: 'completions',
492
546
  hint: 'Split your completions into smaller batches',
493
547
  });
494
548
  }
495
549
 
496
- // Validate all inputs first
497
- for (const completion of completions) {
498
- validateRequired(completion.task_id, 'task_id');
499
- validateUUID(completion.task_id, 'task_id');
500
- }
550
+ // Individual item validation happens at API level
501
551
 
502
552
  const api = getApiClient();
503
- const response = await api.batchCompleteTasks(completions);
553
+ const response = await api.batchCompleteTasks(typedCompletions);
504
554
 
505
555
  if (!response.ok) {
506
556
  throw new Error(`Failed to batch complete tasks: ${response.error}`);
@@ -510,9 +560,9 @@ export const batchCompleteTasks: Handler = async (args, ctx) => {
510
560
  return {
511
561
  result: {
512
562
  success: data?.success || false,
513
- total: completions.length,
563
+ total: typedCompletions.length,
514
564
  succeeded: data?.completed_count || 0,
515
- failed: completions.length - (data?.completed_count || 0),
565
+ failed: typedCompletions.length - (data?.completed_count || 0),
516
566
  next_task: data?.next_task,
517
567
  },
518
568
  };
@@ -523,19 +573,7 @@ export const batchCompleteTasks: Handler = async (args, ctx) => {
523
573
  // ============================================================================
524
574
 
525
575
  export const addSubtask: Handler = async (args, ctx) => {
526
- const { parent_task_id, title, description, priority, estimated_minutes } = args as {
527
- parent_task_id: string;
528
- title: string;
529
- description?: string;
530
- priority?: number;
531
- estimated_minutes?: number;
532
- };
533
-
534
- validateRequired(parent_task_id, 'parent_task_id');
535
- validateUUID(parent_task_id, 'parent_task_id');
536
- validateRequired(title, 'title');
537
- if (priority !== undefined) validatePriority(priority);
538
- if (estimated_minutes !== undefined) validateEstimatedMinutes(estimated_minutes);
576
+ const { parent_task_id, title, description, priority, estimated_minutes } = parseArgs(args, addSubtaskSchema);
539
577
 
540
578
  const api = getApiClient();
541
579
  const response = await api.addSubtask(parent_task_id, {
@@ -568,14 +606,7 @@ export const addSubtask: Handler = async (args, ctx) => {
568
606
  };
569
607
 
570
608
  export const getSubtasks: Handler = async (args, ctx) => {
571
- const { parent_task_id, status } = args as {
572
- parent_task_id: string;
573
- status?: string;
574
- };
575
-
576
- validateRequired(parent_task_id, 'parent_task_id');
577
- validateUUID(parent_task_id, 'parent_task_id');
578
- if (status) validateTaskStatus(status);
609
+ const { parent_task_id, status } = parseArgs(args, getSubtasksSchema);
579
610
 
580
611
  const api = getApiClient();
581
612
  const response = await api.getSubtasks(parent_task_id, status);