@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.
Files changed (104) hide show
  1. package/README.md +60 -7
  2. package/dist/api-client.d.ts +251 -1
  3. package/dist/api-client.js +82 -3
  4. package/dist/handlers/blockers.js +9 -8
  5. package/dist/handlers/bodies-of-work.js +96 -63
  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 +112 -50
  10. package/dist/handlers/decisions.js +32 -19
  11. package/dist/handlers/deployment.js +144 -122
  12. package/dist/handlers/discovery.d.ts +7 -0
  13. package/dist/handlers/discovery.js +96 -7
  14. package/dist/handlers/fallback.js +29 -23
  15. package/dist/handlers/file-checkouts.d.ts +20 -0
  16. package/dist/handlers/file-checkouts.js +133 -0
  17. package/dist/handlers/findings.d.ts +6 -0
  18. package/dist/handlers/findings.js +96 -40
  19. package/dist/handlers/git-issues.js +40 -36
  20. package/dist/handlers/ideas.js +49 -31
  21. package/dist/handlers/index.d.ts +3 -0
  22. package/dist/handlers/index.js +9 -0
  23. package/dist/handlers/milestones.js +39 -32
  24. package/dist/handlers/organizations.js +99 -91
  25. package/dist/handlers/progress.js +24 -13
  26. package/dist/handlers/project.js +68 -28
  27. package/dist/handlers/requests.js +18 -14
  28. package/dist/handlers/roles.d.ts +18 -0
  29. package/dist/handlers/roles.js +130 -0
  30. package/dist/handlers/session.js +58 -17
  31. package/dist/handlers/sprints.js +93 -81
  32. package/dist/handlers/tasks.d.ts +2 -0
  33. package/dist/handlers/tasks.js +189 -91
  34. package/dist/handlers/types.d.ts +64 -2
  35. package/dist/handlers/types.js +48 -1
  36. package/dist/handlers/validation.js +21 -17
  37. package/dist/index.js +7 -2716
  38. package/dist/token-tracking.d.ts +74 -0
  39. package/dist/token-tracking.js +122 -0
  40. package/dist/tools.js +685 -9
  41. package/dist/utils.d.ts +5 -0
  42. package/dist/utils.js +17 -0
  43. package/docs/TOOLS.md +2053 -0
  44. package/package.json +4 -1
  45. package/scripts/generate-docs.ts +212 -0
  46. package/src/api-client.test.ts +718 -0
  47. package/src/api-client.ts +320 -6
  48. package/src/handlers/__test-setup__.ts +16 -0
  49. package/src/handlers/blockers.test.ts +31 -19
  50. package/src/handlers/blockers.ts +9 -8
  51. package/src/handlers/bodies-of-work.test.ts +55 -32
  52. package/src/handlers/bodies-of-work.ts +115 -115
  53. package/src/handlers/connectors.test.ts +834 -0
  54. package/src/handlers/connectors.ts +229 -0
  55. package/src/handlers/cost.test.ts +34 -44
  56. package/src/handlers/cost.ts +136 -85
  57. package/src/handlers/decisions.test.ts +37 -27
  58. package/src/handlers/decisions.ts +35 -30
  59. package/src/handlers/deployment.ts +180 -208
  60. package/src/handlers/discovery.test.ts +4 -5
  61. package/src/handlers/discovery.ts +98 -8
  62. package/src/handlers/fallback.test.ts +26 -22
  63. package/src/handlers/fallback.ts +36 -33
  64. package/src/handlers/file-checkouts.test.ts +670 -0
  65. package/src/handlers/file-checkouts.ts +165 -0
  66. package/src/handlers/findings.test.ts +178 -19
  67. package/src/handlers/findings.ts +112 -74
  68. package/src/handlers/git-issues.test.ts +51 -43
  69. package/src/handlers/git-issues.ts +44 -84
  70. package/src/handlers/ideas.test.ts +28 -23
  71. package/src/handlers/ideas.ts +61 -59
  72. package/src/handlers/index.ts +9 -0
  73. package/src/handlers/milestones.test.ts +33 -28
  74. package/src/handlers/milestones.ts +52 -50
  75. package/src/handlers/organizations.test.ts +104 -83
  76. package/src/handlers/organizations.ts +117 -142
  77. package/src/handlers/progress.test.ts +20 -14
  78. package/src/handlers/progress.ts +26 -24
  79. package/src/handlers/project.test.ts +34 -27
  80. package/src/handlers/project.ts +95 -63
  81. package/src/handlers/requests.test.ts +27 -18
  82. package/src/handlers/requests.ts +21 -17
  83. package/src/handlers/roles.test.ts +303 -0
  84. package/src/handlers/roles.ts +208 -0
  85. package/src/handlers/session.test.ts +47 -0
  86. package/src/handlers/session.ts +71 -26
  87. package/src/handlers/sprints.test.ts +71 -50
  88. package/src/handlers/sprints.ts +113 -146
  89. package/src/handlers/tasks.test.ts +77 -15
  90. package/src/handlers/tasks.ts +231 -156
  91. package/src/handlers/tool-categories.test.ts +66 -0
  92. package/src/handlers/types.ts +81 -2
  93. package/src/handlers/validation.test.ts +78 -45
  94. package/src/handlers/validation.ts +23 -25
  95. package/src/index.ts +12 -2732
  96. package/src/token-tracking.test.ts +453 -0
  97. package/src/token-tracking.ts +164 -0
  98. package/src/tools.ts +685 -9
  99. package/src/utils.test.ts +2 -2
  100. package/src/utils.ts +17 -0
  101. package/dist/config/tool-categories.d.ts +0 -31
  102. package/dist/config/tool-categories.js +0 -253
  103. package/dist/knowledge.d.ts +0 -6
  104. package/dist/knowledge.js +0 -218
@@ -12,22 +12,55 @@
12
12
  */
13
13
 
14
14
  import type { Handler, HandlerRegistry } from './types.js';
15
- import { validateRequired, validateUUID, validatePriority, validateEstimatedMinutes } from '../validators.js';
15
+ import {
16
+ parseArgs,
17
+ uuidValidator,
18
+ priorityValidator,
19
+ minutesValidator,
20
+ createEnumValidator,
21
+ } from '../validators.js';
16
22
  import { getApiClient } from '../api-client.js';
17
23
 
18
- type IdeaStatus = 'raw' | 'exploring' | 'planned' | 'in_development' | 'shipped';
24
+ const VALID_IDEA_STATUSES = ['raw', 'exploring', 'planned', 'in_development', 'shipped'] as const;
25
+ type IdeaStatus = typeof VALID_IDEA_STATUSES[number];
19
26
 
20
- export const addIdea: Handler = async (args, ctx) => {
21
- const { project_id, title, description, status } = args as {
22
- project_id: string;
23
- title: string;
24
- description?: string;
25
- status?: IdeaStatus;
26
- };
27
+ // Argument schemas for type-safe parsing
28
+ const addIdeaSchema = {
29
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
30
+ title: { type: 'string' as const, required: true as const },
31
+ description: { type: 'string' as const },
32
+ status: { type: 'string' as const, validate: createEnumValidator(VALID_IDEA_STATUSES) },
33
+ };
34
+
35
+ const updateIdeaSchema = {
36
+ idea_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
37
+ title: { type: 'string' as const },
38
+ description: { type: 'string' as const },
39
+ status: { type: 'string' as const, validate: createEnumValidator(VALID_IDEA_STATUSES) },
40
+ doc_url: { type: 'string' as const },
41
+ };
27
42
 
28
- validateRequired(project_id, 'project_id');
29
- validateUUID(project_id, 'project_id');
30
- validateRequired(title, 'title');
43
+ const getIdeasSchema = {
44
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
45
+ status: { type: 'string' as const, validate: createEnumValidator(VALID_IDEA_STATUSES) },
46
+ limit: { type: 'number' as const, default: 50 },
47
+ offset: { type: 'number' as const, default: 0 },
48
+ search_query: { type: 'string' as const },
49
+ };
50
+
51
+ const deleteIdeaSchema = {
52
+ idea_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
53
+ };
54
+
55
+ const convertIdeaToTaskSchema = {
56
+ idea_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
57
+ priority: { type: 'number' as const, default: 3, validate: priorityValidator },
58
+ estimated_minutes: { type: 'number' as const, validate: minutesValidator },
59
+ update_status: { type: 'boolean' as const, default: true },
60
+ };
61
+
62
+ export const addIdea: Handler = async (args, ctx) => {
63
+ const { project_id, title, description, status } = parseArgs(args, addIdeaSchema);
31
64
 
32
65
  const { session } = ctx;
33
66
  const apiClient = getApiClient();
@@ -35,67 +68,49 @@ export const addIdea: Handler = async (args, ctx) => {
35
68
  const response = await apiClient.addIdea(project_id, {
36
69
  title,
37
70
  description,
38
- status
71
+ status: status as IdeaStatus | undefined
39
72
  }, session.currentSessionId || undefined);
40
73
 
41
74
  if (!response.ok) {
42
- throw new Error(`Failed to add idea: ${response.error}`);
75
+ return { result: { error: response.error || 'Failed to add idea' }, isError: true };
43
76
  }
44
77
 
45
78
  return { result: { success: true, idea_id: response.data?.idea_id, title } };
46
79
  };
47
80
 
48
- export const updateIdea: Handler = async (args, ctx) => {
49
- const { idea_id, title, description, status, doc_url } = args as {
50
- idea_id: string;
51
- title?: string;
52
- description?: string;
53
- status?: IdeaStatus;
54
- doc_url?: string;
55
- };
56
-
57
- validateRequired(idea_id, 'idea_id');
58
- validateUUID(idea_id, 'idea_id');
81
+ export const updateIdea: Handler = async (args, _ctx) => {
82
+ const { idea_id, title, description, status, doc_url } = parseArgs(args, updateIdeaSchema);
59
83
 
60
84
  const apiClient = getApiClient();
61
85
 
62
86
  const response = await apiClient.updateIdea(idea_id, {
63
87
  title,
64
88
  description,
65
- status,
89
+ status: status as IdeaStatus | undefined,
66
90
  doc_url
67
91
  });
68
92
 
69
93
  if (!response.ok) {
70
- throw new Error(`Failed to update idea: ${response.error}`);
94
+ return { result: { error: response.error || 'Failed to update idea' }, isError: true };
71
95
  }
72
96
 
73
97
  return { result: { success: true, idea_id } };
74
98
  };
75
99
 
76
- export const getIdeas: Handler = async (args, ctx) => {
77
- const { project_id, status, limit = 50, offset = 0, search_query } = args as {
78
- project_id: string;
79
- status?: IdeaStatus;
80
- limit?: number;
81
- offset?: number;
82
- search_query?: string;
83
- };
84
-
85
- validateRequired(project_id, 'project_id');
86
- validateUUID(project_id, 'project_id');
100
+ export const getIdeas: Handler = async (args, _ctx) => {
101
+ const { project_id, status, limit, offset, search_query } = parseArgs(args, getIdeasSchema);
87
102
 
88
103
  const apiClient = getApiClient();
89
104
 
90
105
  const response = await apiClient.getIdeas(project_id, {
91
- status,
106
+ status: status as IdeaStatus | undefined,
92
107
  limit,
93
108
  offset,
94
109
  search_query
95
110
  });
96
111
 
97
112
  if (!response.ok) {
98
- throw new Error(`Failed to fetch ideas: ${response.error}`);
113
+ return { result: { error: response.error || 'Failed to fetch ideas' }, isError: true };
99
114
  }
100
115
 
101
116
  return {
@@ -105,35 +120,22 @@ export const getIdeas: Handler = async (args, ctx) => {
105
120
  };
106
121
  };
107
122
 
108
- export const deleteIdea: Handler = async (args, ctx) => {
109
- const { idea_id } = args as { idea_id: string };
110
-
111
- validateRequired(idea_id, 'idea_id');
112
- validateUUID(idea_id, 'idea_id');
123
+ export const deleteIdea: Handler = async (args, _ctx) => {
124
+ const { idea_id } = parseArgs(args, deleteIdeaSchema);
113
125
 
114
126
  const apiClient = getApiClient();
115
127
 
116
128
  const response = await apiClient.deleteIdea(idea_id);
117
129
 
118
130
  if (!response.ok) {
119
- throw new Error(`Failed to delete idea: ${response.error}`);
131
+ return { result: { error: response.error || 'Failed to delete idea' }, isError: true };
120
132
  }
121
133
 
122
134
  return { result: { success: true } };
123
135
  };
124
136
 
125
- export const convertIdeaToTask: Handler = async (args, ctx) => {
126
- const { idea_id, priority = 3, estimated_minutes, update_status = true } = args as {
127
- idea_id: string;
128
- priority?: number;
129
- estimated_minutes?: number;
130
- update_status?: boolean;
131
- };
132
-
133
- validateRequired(idea_id, 'idea_id');
134
- validateUUID(idea_id, 'idea_id');
135
- validatePriority(priority);
136
- validateEstimatedMinutes(estimated_minutes);
137
+ export const convertIdeaToTask: Handler = async (args, _ctx) => {
138
+ const { idea_id, priority, estimated_minutes, update_status } = parseArgs(args, convertIdeaToTaskSchema);
137
139
 
138
140
  const apiClient = getApiClient();
139
141
 
@@ -155,7 +157,7 @@ export const convertIdeaToTask: Handler = async (args, ctx) => {
155
157
  });
156
158
 
157
159
  if (!response.ok) {
158
- throw new Error(`Failed to convert idea: ${response.error}`);
160
+ return { result: { error: response.error || 'Failed to convert idea' }, isError: true };
159
161
  }
160
162
 
161
163
  return { result: response.data };
@@ -25,6 +25,9 @@ export * from './organizations.js';
25
25
  export * from './cost.js';
26
26
  export * from './git-issues.js';
27
27
  export * from './sprints.js';
28
+ export * from './file-checkouts.js';
29
+ export * from './roles.js';
30
+ export * from './connectors.js';
28
31
 
29
32
  import type { HandlerRegistry } from './types.js';
30
33
  import { milestoneHandlers } from './milestones.js';
@@ -46,6 +49,9 @@ import { organizationHandlers } from './organizations.js';
46
49
  import { costHandlers } from './cost.js';
47
50
  import { gitIssueHandlers } from './git-issues.js';
48
51
  import { sprintHandlers } from './sprints.js';
52
+ import { fileCheckoutHandlers } from './file-checkouts.js';
53
+ import { roleHandlers } from './roles.js';
54
+ import { connectorHandlers } from './connectors.js';
49
55
 
50
56
  /**
51
57
  * Build the complete handler registry from all modules
@@ -71,5 +77,8 @@ export function buildHandlerRegistry(): HandlerRegistry {
71
77
  ...costHandlers,
72
78
  ...gitIssueHandlers,
73
79
  ...sprintHandlers,
80
+ ...fileCheckoutHandlers,
81
+ ...roleHandlers,
82
+ ...connectorHandlers,
74
83
  };
75
84
  }
@@ -90,19 +90,20 @@ describe('addMilestone', () => {
90
90
  );
91
91
  });
92
92
 
93
- it('should throw error when API call fails', async () => {
93
+ it('should return error when API call fails', async () => {
94
94
  mockApiClient.addMilestone.mockResolvedValue({
95
95
  ok: false,
96
96
  error: 'Task not found',
97
97
  });
98
98
  const ctx = createMockContext();
99
99
 
100
- await expect(
101
- addMilestone({
102
- task_id: '123e4567-e89b-12d3-a456-426614174000',
103
- title: 'Milestone 1',
104
- }, ctx)
105
- ).rejects.toThrow('Failed to add milestone: Task not found');
100
+ const result = await addMilestone({
101
+ task_id: '123e4567-e89b-12d3-a456-426614174000',
102
+ title: 'Milestone 1',
103
+ }, ctx);
104
+
105
+ expect(result.isError).toBe(true);
106
+ expect(result.result).toMatchObject({ error: 'Task not found' });
106
107
  });
107
108
  });
108
109
 
@@ -132,7 +133,7 @@ describe('updateMilestone', () => {
132
133
 
133
134
  await expect(
134
135
  updateMilestone({ milestone_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
135
- ).rejects.toThrow('At least one field to update is required');
136
+ ).rejects.toThrow(ValidationError);
136
137
  });
137
138
 
138
139
  it('should throw error for invalid status', async () => {
@@ -143,7 +144,7 @@ describe('updateMilestone', () => {
143
144
  milestone_id: '123e4567-e89b-12d3-a456-426614174000',
144
145
  status: 'invalid_status',
145
146
  }, ctx)
146
- ).rejects.toThrow('status must be pending, in_progress, or completed');
147
+ ).rejects.toThrow(ValidationError);
147
148
  });
148
149
 
149
150
  it('should update title successfully', async () => {
@@ -192,19 +193,20 @@ describe('updateMilestone', () => {
192
193
  );
193
194
  });
194
195
 
195
- it('should throw error when API call fails', async () => {
196
+ it('should return error when API call fails', async () => {
196
197
  mockApiClient.updateMilestone.mockResolvedValue({
197
198
  ok: false,
198
199
  error: 'Milestone not found',
199
200
  });
200
201
  const ctx = createMockContext();
201
202
 
202
- await expect(
203
- updateMilestone({
204
- milestone_id: '123e4567-e89b-12d3-a456-426614174000',
205
- title: 'New Title',
206
- }, ctx)
207
- ).rejects.toThrow('Failed to update milestone: Milestone not found');
203
+ const result = await updateMilestone({
204
+ milestone_id: '123e4567-e89b-12d3-a456-426614174000',
205
+ title: 'New Title',
206
+ }, ctx);
207
+
208
+ expect(result.isError).toBe(true);
209
+ expect(result.result).toMatchObject({ error: 'Milestone not found' });
208
210
  });
209
211
  });
210
212
 
@@ -263,16 +265,17 @@ describe('completeMilestone', () => {
263
265
  );
264
266
  });
265
267
 
266
- it('should throw error when API call fails', async () => {
268
+ it('should return error when API call fails', async () => {
267
269
  mockApiClient.completeMilestone.mockResolvedValue({
268
270
  ok: false,
269
271
  error: 'Milestone not found',
270
272
  });
271
273
  const ctx = createMockContext();
272
274
 
273
- await expect(
274
- completeMilestone({ milestone_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
275
- ).rejects.toThrow('Failed to complete milestone: Milestone not found');
275
+ const result = await completeMilestone({ milestone_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
276
+
277
+ expect(result.isError).toBe(true);
278
+ expect(result.result).toMatchObject({ error: 'Milestone not found' });
276
279
  });
277
280
  });
278
281
 
@@ -332,16 +335,17 @@ describe('deleteMilestone', () => {
332
335
  );
333
336
  });
334
337
 
335
- it('should throw error when API call fails', async () => {
338
+ it('should return error when API call fails', async () => {
336
339
  mockApiClient.deleteMilestone.mockResolvedValue({
337
340
  ok: false,
338
341
  error: 'Milestone not found',
339
342
  });
340
343
  const ctx = createMockContext();
341
344
 
342
- await expect(
343
- deleteMilestone({ milestone_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
344
- ).rejects.toThrow('Failed to delete milestone: Milestone not found');
345
+ const result = await deleteMilestone({ milestone_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
346
+
347
+ expect(result.isError).toBe(true);
348
+ expect(result.result).toMatchObject({ error: 'Milestone not found' });
345
349
  });
346
350
  });
347
351
 
@@ -456,15 +460,16 @@ describe('getMilestones', () => {
456
460
  );
457
461
  });
458
462
 
459
- it('should throw error when API call fails', async () => {
463
+ it('should return error when API call fails', async () => {
460
464
  mockApiClient.getMilestones.mockResolvedValue({
461
465
  ok: false,
462
466
  error: 'Task not found',
463
467
  });
464
468
  const ctx = createMockContext();
465
469
 
466
- await expect(
467
- getMilestones({ task_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
468
- ).rejects.toThrow('Failed to get milestones: Task not found');
470
+ const result = await getMilestones({ task_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
471
+
472
+ expect(result.isError).toBe(true);
473
+ expect(result.result).toMatchObject({ error: 'Task not found' });
469
474
  });
470
475
  });
@@ -12,20 +12,47 @@
12
12
  */
13
13
 
14
14
  import type { Handler, HandlerRegistry } from './types.js';
15
- import { ValidationError, validateRequired, validateUUID } from '../validators.js';
15
+ import {
16
+ parseArgs,
17
+ uuidValidator,
18
+ createEnumValidator,
19
+ ValidationError,
20
+ } from '../validators.js';
16
21
  import { getApiClient } from '../api-client.js';
17
22
 
18
- export const addMilestone: Handler = async (args, ctx) => {
19
- const { task_id, title, description, order_index } = args as {
20
- task_id: string;
21
- title: string;
22
- description?: string;
23
- order_index?: number;
24
- };
23
+ const VALID_MILESTONE_STATUSES = ['pending', 'in_progress', 'completed'] as const;
24
+ type MilestoneStatus = typeof VALID_MILESTONE_STATUSES[number];
25
+
26
+ // Argument schemas for type-safe parsing
27
+ const addMilestoneSchema = {
28
+ task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
29
+ title: { type: 'string' as const, required: true as const },
30
+ description: { type: 'string' as const },
31
+ order_index: { type: 'number' as const },
32
+ };
33
+
34
+ const updateMilestoneSchema = {
35
+ milestone_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
36
+ title: { type: 'string' as const },
37
+ description: { type: 'string' as const },
38
+ status: { type: 'string' as const, validate: createEnumValidator(VALID_MILESTONE_STATUSES) },
39
+ order_index: { type: 'number' as const },
40
+ };
41
+
42
+ const completeMilestoneSchema = {
43
+ milestone_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
44
+ };
45
+
46
+ const deleteMilestoneSchema = {
47
+ milestone_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
48
+ };
25
49
 
26
- validateRequired(task_id, 'task_id');
27
- validateUUID(task_id, 'task_id');
28
- validateRequired(title, 'title');
50
+ const getMilestonesSchema = {
51
+ task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
52
+ };
53
+
54
+ export const addMilestone: Handler = async (args, ctx) => {
55
+ const { task_id, title, description, order_index } = parseArgs(args, addMilestoneSchema);
29
56
 
30
57
  const { session } = ctx;
31
58
  const apiClient = getApiClient();
@@ -37,7 +64,7 @@ export const addMilestone: Handler = async (args, ctx) => {
37
64
  }, session.currentSessionId || undefined);
38
65
 
39
66
  if (!response.ok) {
40
- throw new Error(`Failed to add milestone: ${response.error}`);
67
+ return { result: { error: response.error || 'Failed to add milestone' }, isError: true };
41
68
  }
42
69
 
43
70
  return {
@@ -48,24 +75,8 @@ export const addMilestone: Handler = async (args, ctx) => {
48
75
  };
49
76
  };
50
77
 
51
- export const updateMilestone: Handler = async (args, ctx) => {
52
- const { milestone_id, title, description, status, order_index } = args as {
53
- milestone_id: string;
54
- title?: string;
55
- description?: string;
56
- status?: string;
57
- order_index?: number;
58
- };
59
-
60
- validateRequired(milestone_id, 'milestone_id');
61
- validateUUID(milestone_id, 'milestone_id');
62
-
63
- // Validate status if provided
64
- if (status !== undefined) {
65
- if (!['pending', 'in_progress', 'completed'].includes(status)) {
66
- throw new ValidationError('status must be pending, in_progress, or completed');
67
- }
68
- }
78
+ export const updateMilestone: Handler = async (args, _ctx) => {
79
+ const { milestone_id, title, description, status, order_index } = parseArgs(args, updateMilestoneSchema);
69
80
 
70
81
  // Check that at least one field is provided
71
82
  if (title === undefined && description === undefined && status === undefined && order_index === undefined) {
@@ -77,12 +88,12 @@ export const updateMilestone: Handler = async (args, ctx) => {
77
88
  const response = await apiClient.updateMilestone(milestone_id, {
78
89
  title,
79
90
  description,
80
- status: status as 'pending' | 'in_progress' | 'completed' | undefined,
91
+ status: status as MilestoneStatus | undefined,
81
92
  order_index
82
93
  });
83
94
 
84
95
  if (!response.ok) {
85
- throw new Error(`Failed to update milestone: ${response.error}`);
96
+ return { result: { error: response.error || 'Failed to update milestone' }, isError: true };
86
97
  }
87
98
 
88
99
  return {
@@ -93,18 +104,15 @@ export const updateMilestone: Handler = async (args, ctx) => {
93
104
  };
94
105
  };
95
106
 
96
- export const completeMilestone: Handler = async (args, ctx) => {
97
- const { milestone_id } = args as { milestone_id: string };
98
-
99
- validateRequired(milestone_id, 'milestone_id');
100
- validateUUID(milestone_id, 'milestone_id');
107
+ export const completeMilestone: Handler = async (args, _ctx) => {
108
+ const { milestone_id } = parseArgs(args, completeMilestoneSchema);
101
109
 
102
110
  const apiClient = getApiClient();
103
111
 
104
112
  const response = await apiClient.completeMilestone(milestone_id);
105
113
 
106
114
  if (!response.ok) {
107
- throw new Error(`Failed to complete milestone: ${response.error}`);
115
+ return { result: { error: response.error || 'Failed to complete milestone' }, isError: true };
108
116
  }
109
117
 
110
118
  return {
@@ -115,18 +123,15 @@ export const completeMilestone: Handler = async (args, ctx) => {
115
123
  };
116
124
  };
117
125
 
118
- export const deleteMilestone: Handler = async (args, ctx) => {
119
- const { milestone_id } = args as { milestone_id: string };
120
-
121
- validateRequired(milestone_id, 'milestone_id');
122
- validateUUID(milestone_id, 'milestone_id');
126
+ export const deleteMilestone: Handler = async (args, _ctx) => {
127
+ const { milestone_id } = parseArgs(args, deleteMilestoneSchema);
123
128
 
124
129
  const apiClient = getApiClient();
125
130
 
126
131
  const response = await apiClient.deleteMilestone(milestone_id);
127
132
 
128
133
  if (!response.ok) {
129
- throw new Error(`Failed to delete milestone: ${response.error}`);
134
+ return { result: { error: response.error || 'Failed to delete milestone' }, isError: true };
130
135
  }
131
136
 
132
137
  return {
@@ -137,18 +142,15 @@ export const deleteMilestone: Handler = async (args, ctx) => {
137
142
  };
138
143
  };
139
144
 
140
- export const getMilestones: Handler = async (args, ctx) => {
141
- const { task_id } = args as { task_id: string };
142
-
143
- validateRequired(task_id, 'task_id');
144
- validateUUID(task_id, 'task_id');
145
+ export const getMilestones: Handler = async (args, _ctx) => {
146
+ const { task_id } = parseArgs(args, getMilestonesSchema);
145
147
 
146
148
  const apiClient = getApiClient();
147
149
 
148
150
  const response = await apiClient.getMilestones(task_id);
149
151
 
150
152
  if (!response.ok) {
151
- throw new Error(`Failed to get milestones: ${response.error}`);
153
+ return { result: { error: response.error || 'Failed to get milestones' }, isError: true };
152
154
  }
153
155
 
154
156
  // Stats are calculated server-side now