@vibescope/mcp-server 0.2.1 → 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 (93) hide show
  1. package/README.md +60 -7
  2. package/dist/api-client.d.ts +187 -0
  3. package/dist/api-client.js +48 -0
  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 +718 -0
  44. package/src/api-client.ts +231 -0
  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 +32 -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
@@ -120,14 +120,17 @@ describe('getProjectContext', () => {
120
120
  expect(result.result).toHaveProperty('active_tasks');
121
121
  });
122
122
 
123
- it('should throw error when API call fails', async () => {
123
+ it('should return error when API call fails', async () => {
124
124
  mockApiClient.listProjects.mockResolvedValue({
125
125
  ok: false,
126
126
  error: 'Database error',
127
127
  });
128
128
  const ctx = createMockContext();
129
129
 
130
- await expect(getProjectContext({}, ctx)).rejects.toThrow('Database error');
130
+ const result = await getProjectContext({}, ctx);
131
+
132
+ expect(result.isError).toBe(true);
133
+ expect(result.result).toMatchObject({ error: 'Database error' });
131
134
  });
132
135
  });
133
136
 
@@ -237,16 +240,17 @@ describe('getGitWorkflow', () => {
237
240
  expect((result.result as { instructions: string[] }).instructions[0]).toContain('No git workflow configured');
238
241
  });
239
242
 
240
- it('should throw error when project not found', async () => {
243
+ it('should return error when project not found', async () => {
241
244
  mockApiClient.getGitWorkflow.mockResolvedValue({
242
245
  ok: false,
243
246
  error: 'Project not found',
244
247
  });
245
248
  const ctx = createMockContext();
246
249
 
247
- await expect(
248
- getGitWorkflow({ project_id: VALID_UUID }, ctx)
249
- ).rejects.toThrow('Project not found');
250
+ const result = await getGitWorkflow({ project_id: VALID_UUID }, ctx);
251
+
252
+ expect(result.isError).toBe(true);
253
+ expect(result.result).toMatchObject({ error: 'Project not found' });
250
254
  });
251
255
  });
252
256
 
@@ -327,16 +331,17 @@ describe('createProject', () => {
327
331
  });
328
332
  });
329
333
 
330
- it('should throw error when insert fails', async () => {
334
+ it('should return error when insert fails', async () => {
331
335
  mockApiClient.createProject.mockResolvedValue({
332
336
  ok: false,
333
- error: 'Failed to create project: Insert failed',
337
+ error: 'Failed to create project',
334
338
  });
335
339
  const ctx = createMockContext();
336
340
 
337
- await expect(
338
- createProject({ name: 'Test Project' }, ctx)
339
- ).rejects.toThrow('Failed to create project: Insert failed');
341
+ const result = await createProject({ name: 'Test Project' }, ctx);
342
+
343
+ expect(result.isError).toBe(true);
344
+ expect(result.result).toMatchObject({ error: 'Failed to create project' });
340
345
  });
341
346
 
342
347
  it('should throw error when name is missing', async () => {
@@ -445,19 +450,20 @@ describe('updateProject', () => {
445
450
  );
446
451
  });
447
452
 
448
- it('should throw error when update fails', async () => {
453
+ it('should return error when update fails', async () => {
449
454
  mockApiClient.updateProject.mockResolvedValue({
450
455
  ok: false,
451
- error: 'Failed to update project: Update failed',
456
+ error: 'Failed to update project',
452
457
  });
453
458
  const ctx = createMockContext();
454
459
 
455
- await expect(
456
- updateProject({
457
- project_id: VALID_UUID,
458
- name: 'Test',
459
- }, ctx)
460
- ).rejects.toThrow('Failed to update project: Update failed');
460
+ const result = await updateProject({
461
+ project_id: VALID_UUID,
462
+ name: 'Test',
463
+ }, ctx);
464
+
465
+ expect(result.isError).toBe(true);
466
+ expect(result.result).toMatchObject({ error: 'Failed to update project' });
461
467
  });
462
468
  });
463
469
 
@@ -522,18 +528,19 @@ describe('updateProjectReadme', () => {
522
528
  );
523
529
  });
524
530
 
525
- it('should throw error when update fails', async () => {
531
+ it('should return error when update fails', async () => {
526
532
  mockApiClient.updateProjectReadme.mockResolvedValue({
527
533
  ok: false,
528
- error: 'Failed to update README: Update failed',
534
+ error: 'Failed to update README',
529
535
  });
530
536
  const ctx = createMockContext();
531
537
 
532
- await expect(
533
- updateProjectReadme({
534
- project_id: VALID_UUID,
535
- readme_content: '# README',
536
- }, ctx)
537
- ).rejects.toThrow('Failed to update README: Update failed');
538
+ const result = await updateProjectReadme({
539
+ project_id: VALID_UUID,
540
+ readme_content: '# README',
541
+ }, ctx);
542
+
543
+ expect(result.isError).toBe(true);
544
+ expect(result.result).toMatchObject({ error: 'Failed to update README' });
538
545
  });
539
546
  });
@@ -72,7 +72,7 @@ export const getProjectContext: Handler = async (args, _ctx) => {
72
72
  const response = await apiClient.listProjects();
73
73
 
74
74
  if (!response.ok) {
75
- throw new Error(response.error || 'Failed to fetch projects');
75
+ return { result: { error: response.error || 'Failed to fetch projects' }, isError: true };
76
76
  }
77
77
 
78
78
  return { result: { projects: response.data?.projects || [] } };
@@ -82,7 +82,7 @@ export const getProjectContext: Handler = async (args, _ctx) => {
82
82
  const response = await apiClient.getProject(project_id || 'by-git-url', git_url);
83
83
 
84
84
  if (!response.ok) {
85
- throw new Error(response.error || 'Failed to fetch project');
85
+ return { result: { error: response.error || 'Failed to fetch project' }, isError: true };
86
86
  }
87
87
 
88
88
  if (!response.data?.found) {
@@ -104,7 +104,7 @@ export const getGitWorkflow: Handler = async (args, _ctx) => {
104
104
  const response = await apiClient.getGitWorkflow(project_id, task_id);
105
105
 
106
106
  if (!response.ok) {
107
- throw new Error(response.error || 'Failed to get git workflow');
107
+ return { result: { error: response.error || 'Failed to get git workflow' }, isError: true };
108
108
  }
109
109
 
110
110
  return { result: response.data };
@@ -123,7 +123,7 @@ export const createProject: Handler = async (args, _ctx) => {
123
123
  });
124
124
 
125
125
  if (!response.ok) {
126
- throw new Error(response.error || 'Failed to create project');
126
+ return { result: { error: response.error || 'Failed to create project' }, isError: true };
127
127
  }
128
128
 
129
129
  return { result: response.data };
@@ -163,7 +163,7 @@ export const updateProject: Handler = async (args, _ctx) => {
163
163
  });
164
164
 
165
165
  if (!response.ok) {
166
- throw new Error(response.error || 'Failed to update project');
166
+ return { result: { error: response.error || 'Failed to update project' }, isError: true };
167
167
  }
168
168
 
169
169
  return { result: response.data };
@@ -176,7 +176,7 @@ export const updateProjectReadme: Handler = async (args, _ctx) => {
176
176
  const response = await apiClient.updateProjectReadme(project_id, readme_content);
177
177
 
178
178
  if (!response.ok) {
179
- throw new Error(response.error || 'Failed to update README');
179
+ return { result: { error: response.error || 'Failed to update README' }, isError: true };
180
180
  }
181
181
 
182
182
  return { result: response.data };
@@ -87,19 +87,22 @@ describe('getPendingRequests', () => {
87
87
  );
88
88
  });
89
89
 
90
- it('should throw error when API call fails', async () => {
90
+ it('should return error when API call fails', async () => {
91
91
  mockApiClient.getPendingRequests.mockResolvedValue({
92
92
  ok: false,
93
93
  error: 'Query failed',
94
94
  });
95
95
  const ctx = createMockContext();
96
96
 
97
- await expect(
98
- getPendingRequests(
99
- { project_id: '123e4567-e89b-12d3-a456-426614174000' },
100
- ctx
101
- )
102
- ).rejects.toThrow('Failed to get pending requests: Query failed');
97
+ const result = await getPendingRequests(
98
+ { project_id: '123e4567-e89b-12d3-a456-426614174000' },
99
+ ctx
100
+ );
101
+
102
+ expect(result.isError).toBe(true);
103
+ expect(result.result).toMatchObject({
104
+ error: 'Query failed',
105
+ });
103
106
  });
104
107
  });
105
108
 
@@ -159,16 +162,19 @@ describe('acknowledgeRequest', () => {
159
162
  );
160
163
  });
161
164
 
162
- it('should throw error when API call fails', async () => {
165
+ it('should return error when API call fails', async () => {
163
166
  mockApiClient.acknowledgeRequest.mockResolvedValue({
164
167
  ok: false,
165
168
  error: 'Update failed',
166
169
  });
167
170
  const ctx = createMockContext();
168
171
 
169
- await expect(
170
- acknowledgeRequest({ request_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
171
- ).rejects.toThrow('Failed to acknowledge request: Update failed');
172
+ const result = await acknowledgeRequest({ request_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
173
+
174
+ expect(result.isError).toBe(true);
175
+ expect(result.result).toMatchObject({
176
+ error: 'Update failed',
177
+ });
172
178
  });
173
179
  });
174
180
 
@@ -246,18 +252,21 @@ describe('answerQuestion', () => {
246
252
  );
247
253
  });
248
254
 
249
- it('should throw error when API call fails', async () => {
255
+ it('should return error when API call fails', async () => {
250
256
  mockApiClient.answerQuestion.mockResolvedValue({
251
257
  ok: false,
252
258
  error: 'Update failed',
253
259
  });
254
260
  const ctx = createMockContext();
255
261
 
256
- await expect(
257
- answerQuestion({
258
- request_id: '123e4567-e89b-12d3-a456-426614174000',
259
- answer: 'Answer',
260
- }, ctx)
261
- ).rejects.toThrow('Failed to answer question: Update failed');
262
+ const result = await answerQuestion({
263
+ request_id: '123e4567-e89b-12d3-a456-426614174000',
264
+ answer: 'Answer',
265
+ }, ctx);
266
+
267
+ expect(result.isError).toBe(true);
268
+ expect(result.result).toMatchObject({
269
+ error: 'Update failed',
270
+ });
262
271
  });
263
272
  });
@@ -36,7 +36,7 @@ export const getPendingRequests: Handler = async (args, ctx) => {
36
36
  const response = await apiClient.getPendingRequests(project_id, session.currentSessionId || undefined);
37
37
 
38
38
  if (!response.ok) {
39
- throw new Error(`Failed to get pending requests: ${response.error}`);
39
+ return { result: { error: response.error || 'Failed to get pending requests' }, isError: true };
40
40
  }
41
41
 
42
42
  return {
@@ -56,7 +56,7 @@ export const acknowledgeRequest: Handler = async (args, ctx) => {
56
56
  const response = await apiClient.acknowledgeRequest(request_id, session.currentSessionId || undefined);
57
57
 
58
58
  if (!response.ok) {
59
- throw new Error(`Failed to acknowledge request: ${response.error}`);
59
+ return { result: { error: response.error || 'Failed to acknowledge request' }, isError: true };
60
60
  }
61
61
 
62
62
  return {
@@ -75,7 +75,7 @@ export const answerQuestion: Handler = async (args, ctx) => {
75
75
  const response = await apiClient.answerQuestion(request_id, answer, session.currentSessionId || undefined);
76
76
 
77
77
  if (!response.ok) {
78
- throw new Error(`Failed to answer question: ${response.error}`);
78
+ return { result: { error: response.error || 'Failed to answer question' }, isError: true };
79
79
  }
80
80
 
81
81
  return {
@@ -526,4 +526,51 @@ describe('startWorkSession', () => {
526
526
  expect(result.result).toHaveProperty('next_task');
527
527
  expect((result.result as { next_task: { id: string } }).next_task.id).toBe('task-1');
528
528
  });
529
+
530
+ it('should include pending_requests when available in full mode', async () => {
531
+ const ctx = createMockContext({ sessionId: null });
532
+ const mockRequests = [
533
+ { id: 'req-1', request_type: 'question', message: 'What is the status?', created_at: '2026-01-14T10:00:00Z' },
534
+ { id: 'req-2', request_type: 'instruction', message: 'Please prioritize task X', created_at: '2026-01-14T11:00:00Z' },
535
+ ];
536
+ mockApiClient.startSession.mockResolvedValue({
537
+ ok: true,
538
+ data: {
539
+ session_started: true,
540
+ session_id: 'new-session-123',
541
+ persona: 'Wave',
542
+ role: 'developer',
543
+ project: { id: 'project-123', name: 'Test Project' },
544
+ pending_requests: mockRequests,
545
+ },
546
+ });
547
+
548
+ const result = await startWorkSession({ project_id: 'project-123', mode: 'full' }, ctx);
549
+
550
+ expect(result.result).toHaveProperty('pending_requests');
551
+ expect(result.result).toHaveProperty('pending_requests_count', 2);
552
+ const pendingRequests = (result.result as { pending_requests: typeof mockRequests }).pending_requests;
553
+ expect(pendingRequests.length).toBe(2);
554
+ expect(pendingRequests[0].request_type).toBe('question');
555
+ });
556
+
557
+ it('should not include pending_requests when empty', async () => {
558
+ const ctx = createMockContext({ sessionId: null });
559
+ mockApiClient.startSession.mockResolvedValue({
560
+ ok: true,
561
+ data: {
562
+ session_started: true,
563
+ session_id: 'new-session-123',
564
+ persona: 'Wave',
565
+ role: 'developer',
566
+ project: { id: 'project-123', name: 'Test Project' },
567
+ pending_requests: [],
568
+ },
569
+ });
570
+
571
+ const result = await startWorkSession({ project_id: 'project-123', mode: 'full' }, ctx);
572
+
573
+ expect(result.result).not.toHaveProperty('pending_requests');
574
+ expect(result.result).not.toHaveProperty('pending_requests_count');
575
+ });
529
576
  });
@@ -105,22 +105,40 @@ export const startWorkSession: Handler = async (args, ctx) => {
105
105
  });
106
106
  }
107
107
 
108
- // Build result with directive at top for visibility
108
+ // Check for urgent questions - these MUST be handled first
109
+ const hasUrgentQuestions = data.URGENT_QUESTIONS || (data.pending_requests && data.pending_requests.length > 0);
110
+
111
+ // Build result - URGENT_QUESTIONS at absolute top for maximum visibility
109
112
  const result: Record<string, unknown> = {
110
113
  session_started: true,
111
- directive: data.directive || 'ACTION_REQUIRED: Start working immediately.',
112
- auto_continue: true,
113
- session_id: data.session_id,
114
- persona: data.persona,
115
- role: data.role,
116
- project: data.project,
117
114
  };
118
115
 
116
+ // URGENT_QUESTIONS must be the FIRST thing the agent sees
117
+ if (data.URGENT_QUESTIONS) {
118
+ result.URGENT_QUESTIONS = data.URGENT_QUESTIONS;
119
+ }
120
+
121
+ // Directive comes right after urgent questions
122
+ result.directive = data.directive || 'ACTION_REQUIRED: Start working immediately.';
123
+ result.auto_continue = true;
124
+
125
+ // Session info
126
+ result.session_id = data.session_id;
127
+ result.persona = data.persona;
128
+ result.role = data.role;
129
+ result.project = data.project;
130
+
119
131
  // Add task data
120
132
  if (data.next_task) {
121
133
  result.next_task = data.next_task;
122
134
  }
123
135
 
136
+ // Add pending requests (questions from user) - these take priority
137
+ if (data.pending_requests && data.pending_requests.length > 0) {
138
+ result.pending_requests = data.pending_requests;
139
+ result.pending_requests_count = data.pending_requests.length;
140
+ }
141
+
124
142
  // Add active tasks for full mode
125
143
  if (data.active_tasks) {
126
144
  result.active_tasks = data.active_tasks;
@@ -153,8 +171,13 @@ export const startWorkSession: Handler = async (args, ctx) => {
153
171
  };
154
172
  }
155
173
 
156
- // Add next action at end
157
- if (data.next_task) {
174
+ // Add next action at end - pending requests take priority over tasks
175
+ if (hasUrgentQuestions) {
176
+ const firstQuestion = data.URGENT_QUESTIONS?.requests?.[0] || data.pending_requests?.[0];
177
+ result.next_action = firstQuestion
178
+ ? `answer_question(request_id: "${firstQuestion.id}", answer: "...")`
179
+ : 'Check pending_requests and respond using answer_question(request_id, answer)';
180
+ } else if (data.next_task) {
158
181
  result.next_action = `update_task(task_id: "${data.next_task.id}", status: "in_progress")`;
159
182
  } else if (data.project) {
160
183
  result.next_action = `start_fallback_activity(project_id: "${data.project.id}", activity: "code_review")`;
@@ -99,30 +99,36 @@ describe('createSprint', () => {
99
99
  ).rejects.toThrow(ValidationError);
100
100
  });
101
101
 
102
- it('should throw error for invalid start_date format', async () => {
102
+ it('should return error for invalid start_date format', async () => {
103
103
  const ctx = createMockContext();
104
104
 
105
- await expect(
106
- createSprint({
107
- project_id: VALID_UUID,
108
- title: 'Sprint 1',
109
- start_date: 'invalid',
110
- end_date: '2025-01-14'
111
- }, ctx)
112
- ).rejects.toThrow('Invalid start_date format');
105
+ const result = await createSprint({
106
+ project_id: VALID_UUID,
107
+ title: 'Sprint 1',
108
+ start_date: 'invalid',
109
+ end_date: '2025-01-14'
110
+ }, ctx);
111
+
112
+ expect(result.isError).toBe(true);
113
+ expect(result.result).toMatchObject({
114
+ error: 'Invalid start_date format. Use YYYY-MM-DD',
115
+ });
113
116
  });
114
117
 
115
- it('should throw error when end_date is before start_date', async () => {
118
+ it('should return error when end_date is before start_date', async () => {
116
119
  const ctx = createMockContext();
117
120
 
118
- await expect(
119
- createSprint({
120
- project_id: VALID_UUID,
121
- title: 'Sprint 1',
122
- start_date: '2025-01-14',
123
- end_date: '2025-01-01'
124
- }, ctx)
125
- ).rejects.toThrow('end_date must be on or after start_date');
121
+ const result = await createSprint({
122
+ project_id: VALID_UUID,
123
+ title: 'Sprint 1',
124
+ start_date: '2025-01-14',
125
+ end_date: '2025-01-01'
126
+ }, ctx);
127
+
128
+ expect(result.isError).toBe(true);
129
+ expect(result.result).toMatchObject({
130
+ error: 'end_date must be on or after start_date',
131
+ });
126
132
  });
127
133
 
128
134
  it('should create sprint successfully', async () => {
@@ -180,21 +186,24 @@ describe('createSprint', () => {
180
186
  );
181
187
  });
182
188
 
183
- it('should throw error when API call fails', async () => {
189
+ it('should return error when API call fails', async () => {
184
190
  mockApiClient.proxy.mockResolvedValue({
185
191
  ok: false,
186
192
  error: 'Database error',
187
193
  });
188
194
  const ctx = createMockContext();
189
195
 
190
- await expect(
191
- createSprint({
192
- project_id: VALID_UUID,
193
- title: 'Sprint 1',
194
- start_date: '2025-01-01',
195
- end_date: '2025-01-14',
196
- }, ctx)
197
- ).rejects.toThrow('Failed to create sprint: Database error');
196
+ const result = await createSprint({
197
+ project_id: VALID_UUID,
198
+ title: 'Sprint 1',
199
+ start_date: '2025-01-01',
200
+ end_date: '2025-01-14',
201
+ }, ctx);
202
+
203
+ expect(result.isError).toBe(true);
204
+ expect(result.result).toMatchObject({
205
+ error: 'Database error',
206
+ });
198
207
  });
199
208
  });
200
209
 
@@ -238,16 +247,19 @@ describe('updateSprint', () => {
238
247
  });
239
248
  });
240
249
 
241
- it('should throw error when API call fails', async () => {
250
+ it('should return error when API call fails', async () => {
242
251
  mockApiClient.proxy.mockResolvedValue({
243
252
  ok: false,
244
253
  error: 'Sprint not found',
245
254
  });
246
255
  const ctx = createMockContext();
247
256
 
248
- await expect(
249
- updateSprint({ sprint_id: VALID_UUID, title: 'Test' }, ctx)
250
- ).rejects.toThrow('Failed to update sprint: Sprint not found');
257
+ const result = await updateSprint({ sprint_id: VALID_UUID, title: 'Test' }, ctx);
258
+
259
+ expect(result.isError).toBe(true);
260
+ expect(result.result).toMatchObject({
261
+ error: 'Sprint not found',
262
+ });
251
263
  });
252
264
  });
253
265
 
@@ -417,16 +429,19 @@ describe('startSprint', () => {
417
429
  });
418
430
  });
419
431
 
420
- it('should throw error when sprint not in planning', async () => {
432
+ it('should return error when sprint not in planning', async () => {
421
433
  mockApiClient.proxy.mockResolvedValue({
422
434
  ok: false,
423
435
  error: "Cannot start sprint in 'active' status",
424
436
  });
425
437
  const ctx = createMockContext();
426
438
 
427
- await expect(
428
- startSprint({ sprint_id: VALID_UUID }, ctx)
429
- ).rejects.toThrow("Cannot start sprint in 'active' status");
439
+ const result = await startSprint({ sprint_id: VALID_UUID }, ctx);
440
+
441
+ expect(result.isError).toBe(true);
442
+ expect(result.result).toMatchObject({
443
+ error: "Cannot start sprint in 'active' status",
444
+ });
430
445
  });
431
446
  });
432
447
 
@@ -514,16 +529,19 @@ describe('addTaskToSprint', () => {
514
529
  ).rejects.toThrow(ValidationError);
515
530
  });
516
531
 
517
- it('should throw error for negative story_points', async () => {
532
+ it('should return error for negative story_points', async () => {
518
533
  const ctx = createMockContext();
519
534
 
520
- await expect(
521
- addTaskToSprint({
522
- sprint_id: VALID_UUID,
523
- task_id: VALID_UUID_2,
524
- story_points: -1,
525
- }, ctx)
526
- ).rejects.toThrow('story_points must be a non-negative integer');
535
+ const result = await addTaskToSprint({
536
+ sprint_id: VALID_UUID,
537
+ task_id: VALID_UUID_2,
538
+ story_points: -1,
539
+ }, ctx);
540
+
541
+ expect(result.isError).toBe(true);
542
+ expect(result.result).toMatchObject({
543
+ error: 'story_points must be a non-negative integer',
544
+ });
527
545
  });
528
546
 
529
547
  it('should add task to sprint with story points', async () => {
@@ -554,19 +572,22 @@ describe('addTaskToSprint', () => {
554
572
  });
555
573
  });
556
574
 
557
- it('should throw error when sprint not in planning', async () => {
575
+ it('should return error when sprint not in planning', async () => {
558
576
  mockApiClient.proxy.mockResolvedValue({
559
577
  ok: false,
560
578
  error: "Cannot add tasks to sprint in 'active' status",
561
579
  });
562
580
  const ctx = createMockContext();
563
581
 
564
- await expect(
565
- addTaskToSprint({
566
- sprint_id: VALID_UUID,
567
- task_id: VALID_UUID_2,
568
- }, ctx)
569
- ).rejects.toThrow("Cannot add tasks to sprint in 'active' status");
582
+ const result = await addTaskToSprint({
583
+ sprint_id: VALID_UUID,
584
+ task_id: VALID_UUID_2,
585
+ }, ctx);
586
+
587
+ expect(result.isError).toBe(true);
588
+ expect(result.result).toMatchObject({
589
+ error: "Cannot add tasks to sprint in 'active' status",
590
+ });
570
591
  });
571
592
  });
572
593