@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
@@ -6,6 +6,7 @@
6
6
  * - checkin_file: Check in a file after editing
7
7
  * - get_file_checkouts: Get active checkouts for a project
8
8
  * - abandon_checkout: Force release a checkout
9
+ * - is_file_available: Check if a file is available for checkout
9
10
  */
10
11
 
11
12
  import type { Handler, HandlerRegistry } from './types.js';
@@ -41,6 +42,11 @@ const abandonCheckoutSchema = {
41
42
  file_path: { type: 'string' as const },
42
43
  };
43
44
 
45
+ const isFileAvailableSchema = {
46
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
47
+ file_path: { type: 'string' as const, required: true as const },
48
+ };
49
+
44
50
  export const checkoutFile: Handler = async (args, ctx) => {
45
51
  const { project_id, file_path, reason } = parseArgs(args, checkoutFileSchema);
46
52
 
@@ -48,7 +54,7 @@ export const checkoutFile: Handler = async (args, ctx) => {
48
54
  const response = await apiClient.checkoutFile(project_id, file_path, reason, ctx.session.currentSessionId || undefined);
49
55
 
50
56
  if (!response.ok) {
51
- throw new Error(response.error || 'Failed to checkout file');
57
+ return { result: { error: response.error || 'Failed to checkout file' }, isError: true };
52
58
  }
53
59
 
54
60
  return { result: response.data };
@@ -59,7 +65,7 @@ export const checkinFile: Handler = async (args, ctx) => {
59
65
 
60
66
  // Validate that either checkout_id or both project_id and file_path are provided
61
67
  if (!checkout_id && (!project_id || !file_path)) {
62
- throw new Error('Either checkout_id or both project_id and file_path are required');
68
+ return { result: { error: 'Either checkout_id or both project_id and file_path are required' }, isError: true };
63
69
  }
64
70
 
65
71
  const apiClient = getApiClient();
@@ -71,7 +77,7 @@ export const checkinFile: Handler = async (args, ctx) => {
71
77
  }, ctx.session.currentSessionId || undefined);
72
78
 
73
79
  if (!response.ok) {
74
- throw new Error(response.error || 'Failed to checkin file');
80
+ return { result: { error: response.error || 'Failed to checkin file' }, isError: true };
75
81
  }
76
82
 
77
83
  return { result: response.data };
@@ -88,7 +94,7 @@ export const getFileCheckouts: Handler = async (args, _ctx) => {
88
94
  });
89
95
 
90
96
  if (!response.ok) {
91
- throw new Error(response.error || 'Failed to get file checkouts');
97
+ return { result: { error: response.error || 'Failed to get file checkouts' }, isError: true };
92
98
  }
93
99
 
94
100
  return { result: response.data };
@@ -99,7 +105,7 @@ export const abandonCheckout: Handler = async (args, _ctx) => {
99
105
 
100
106
  // Validate that either checkout_id or both project_id and file_path are provided
101
107
  if (!checkout_id && (!project_id || !file_path)) {
102
- throw new Error('Either checkout_id or both project_id and file_path are required');
108
+ return { result: { error: 'Either checkout_id or both project_id and file_path are required' }, isError: true };
103
109
  }
104
110
 
105
111
  const apiClient = getApiClient();
@@ -110,12 +116,43 @@ export const abandonCheckout: Handler = async (args, _ctx) => {
110
116
  });
111
117
 
112
118
  if (!response.ok) {
113
- throw new Error(response.error || 'Failed to abandon checkout');
119
+ return { result: { error: response.error || 'Failed to abandon checkout' }, isError: true };
114
120
  }
115
121
 
116
122
  return { result: response.data };
117
123
  };
118
124
 
125
+ export const isFileAvailable: Handler = async (args, _ctx) => {
126
+ const { project_id, file_path } = parseArgs(args, isFileAvailableSchema);
127
+
128
+ const apiClient = getApiClient();
129
+ const response = await apiClient.getFileCheckouts(project_id, {
130
+ status: 'checked_out',
131
+ file_path,
132
+ limit: 1
133
+ });
134
+
135
+ if (!response.ok) {
136
+ return { result: { error: response.error || 'Failed to check file availability' }, isError: true };
137
+ }
138
+
139
+ const checkouts = response.data?.checkouts || [];
140
+ const activeCheckout = checkouts.length > 0 ? checkouts[0] : null;
141
+
142
+ return {
143
+ result: {
144
+ available: !activeCheckout,
145
+ file_path,
146
+ checked_out_by: activeCheckout ? {
147
+ checkout_id: activeCheckout.id,
148
+ checked_out_by: activeCheckout.checked_out_by,
149
+ checked_out_at: activeCheckout.checked_out_at,
150
+ reason: activeCheckout.checkout_reason
151
+ } : null
152
+ }
153
+ };
154
+ };
155
+
119
156
  /**
120
157
  * File Checkouts handlers registry
121
158
  */
@@ -124,4 +161,5 @@ export const fileCheckoutHandlers: HandlerRegistry = {
124
161
  checkin_file: checkinFile,
125
162
  get_file_checkouts: getFileCheckouts,
126
163
  abandon_checkout: abandonCheckout,
164
+ is_file_available: isFileAvailable,
127
165
  };
@@ -106,16 +106,17 @@ describe('addFinding', () => {
106
106
  );
107
107
  });
108
108
 
109
- it('should throw error when API call fails', async () => {
109
+ it('should return error when API call fails', async () => {
110
110
  mockApiClient.addFinding.mockResolvedValue({
111
111
  ok: false,
112
112
  error: 'Insert failed',
113
113
  });
114
114
  const ctx = createMockContext();
115
115
 
116
- await expect(
117
- addFinding({ project_id: VALID_UUID, title: 'Test' }, ctx)
118
- ).rejects.toThrow('Insert failed');
116
+ const result = await addFinding({ project_id: VALID_UUID, title: 'Test' }, ctx);
117
+
118
+ expect(result.isError).toBe(true);
119
+ expect(result.result).toMatchObject({ error: 'Insert failed' });
119
120
  });
120
121
  });
121
122
 
@@ -259,16 +260,17 @@ describe('getFindings', () => {
259
260
  );
260
261
  });
261
262
 
262
- it('should throw error when API call fails', async () => {
263
+ it('should return error when API call fails', async () => {
263
264
  mockApiClient.getFindings.mockResolvedValue({
264
265
  ok: false,
265
266
  error: 'Query failed',
266
267
  });
267
268
  const ctx = createMockContext();
268
269
 
269
- await expect(
270
- getFindings({ project_id: VALID_UUID }, ctx)
271
- ).rejects.toThrow('Query failed');
270
+ const result = await getFindings({ project_id: VALID_UUID }, ctx);
271
+
272
+ expect(result.isError).toBe(true);
273
+ expect(result.result).toMatchObject({ error: 'Query failed' });
272
274
  });
273
275
  });
274
276
 
@@ -339,16 +341,17 @@ describe('updateFinding', () => {
339
341
  );
340
342
  });
341
343
 
342
- it('should throw error when API call fails', async () => {
344
+ it('should return error when API call fails', async () => {
343
345
  mockApiClient.updateFinding.mockResolvedValue({
344
346
  ok: false,
345
347
  error: 'Update failed',
346
348
  });
347
349
  const ctx = createMockContext();
348
350
 
349
- await expect(
350
- updateFinding({ finding_id: VALID_UUID, title: 'Test' }, ctx)
351
- ).rejects.toThrow('Update failed');
351
+ const result = await updateFinding({ finding_id: VALID_UUID, title: 'Test' }, ctx);
352
+
353
+ expect(result.isError).toBe(true);
354
+ expect(result.result).toMatchObject({ error: 'Update failed' });
352
355
  });
353
356
  });
354
357
 
@@ -397,16 +400,17 @@ describe('deleteFinding', () => {
397
400
  expect(mockApiClient.deleteFinding).toHaveBeenCalledWith(VALID_UUID);
398
401
  });
399
402
 
400
- it('should throw error when API call fails', async () => {
403
+ it('should return error when API call fails', async () => {
401
404
  mockApiClient.deleteFinding.mockResolvedValue({
402
405
  ok: false,
403
406
  error: 'Delete failed',
404
407
  });
405
408
  const ctx = createMockContext();
406
409
 
407
- await expect(
408
- deleteFinding({ finding_id: VALID_UUID }, ctx)
409
- ).rejects.toThrow('Delete failed');
410
+ const result = await deleteFinding({ finding_id: VALID_UUID }, ctx);
411
+
412
+ expect(result.isError).toBe(true);
413
+ expect(result.result).toMatchObject({ error: 'Delete failed' });
410
414
  });
411
415
  });
412
416
 
@@ -461,16 +465,17 @@ describe('getFindingsStats', () => {
461
465
  expect(mockApiClient.getFindingsStats).toHaveBeenCalledWith(VALID_UUID);
462
466
  });
463
467
 
464
- it('should throw error when API call fails', async () => {
468
+ it('should return error when API call fails', async () => {
465
469
  mockApiClient.getFindingsStats.mockResolvedValue({
466
470
  ok: false,
467
471
  error: 'Query failed',
468
472
  });
469
473
  const ctx = createMockContext();
470
474
 
471
- await expect(
472
- getFindingsStats({ project_id: VALID_UUID }, ctx)
473
- ).rejects.toThrow('Query failed');
475
+ const result = await getFindingsStats({ project_id: VALID_UUID }, ctx);
476
+
477
+ expect(result.isError).toBe(true);
478
+ expect(result.result).toMatchObject({ error: 'Query failed' });
474
479
  });
475
480
  });
476
481
 
@@ -495,6 +500,14 @@ describe('queryKnowledgeBase', () => {
495
500
  ).rejects.toThrow(ValidationError);
496
501
  });
497
502
 
503
+ it('should throw error for invalid scope value', async () => {
504
+ const ctx = createMockContext();
505
+
506
+ await expect(
507
+ queryKnowledgeBase({ project_id: VALID_UUID, scope: 'invalid_scope' }, ctx)
508
+ ).rejects.toThrow(ValidationError);
509
+ });
510
+
498
511
  it('should query with default parameters', async () => {
499
512
  mockApiClient.queryKnowledgeBase.mockResolvedValue({
500
513
  ok: true,
@@ -605,15 +618,16 @@ describe('queryKnowledgeBase', () => {
605
618
  );
606
619
  });
607
620
 
608
- it('should throw error when API call fails', async () => {
621
+ it('should return error when API call fails', async () => {
609
622
  mockApiClient.queryKnowledgeBase.mockResolvedValue({
610
623
  ok: false,
611
624
  error: 'Query failed',
612
625
  });
613
626
  const ctx = createMockContext();
614
627
 
615
- await expect(
616
- queryKnowledgeBase({ project_id: VALID_UUID }, ctx)
617
- ).rejects.toThrow('Query failed');
628
+ const result = await queryKnowledgeBase({ project_id: VALID_UUID }, ctx);
629
+
630
+ expect(result.isError).toBe(true);
631
+ expect(result.result).toMatchObject({ error: 'Query failed' });
618
632
  });
619
633
  });
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  import type { Handler, HandlerRegistry } from './types.js';
13
+ import { success, error } from './types.js';
13
14
  import { parseArgs, uuidValidator, createEnumValidator } from '../validators.js';
14
15
  import { getApiClient } from '../api-client.js';
15
16
 
@@ -86,10 +87,10 @@ export const addFinding: Handler = async (args, ctx) => {
86
87
  }, ctx.session.currentSessionId || undefined);
87
88
 
88
89
  if (!response.ok) {
89
- throw new Error(response.error || 'Failed to add finding');
90
+ return error(response.error || 'Failed to add finding');
90
91
  }
91
92
 
92
- return { result: response.data };
93
+ return success(response.data);
93
94
  };
94
95
 
95
96
  export const getFindings: Handler = async (args, _ctx) => {
@@ -107,10 +108,10 @@ export const getFindings: Handler = async (args, _ctx) => {
107
108
  });
108
109
 
109
110
  if (!response.ok) {
110
- throw new Error(response.error || 'Failed to get findings');
111
+ return error(response.error || 'Failed to get findings');
111
112
  }
112
113
 
113
- return { result: response.data };
114
+ return success(response.data);
114
115
  };
115
116
 
116
117
  /**
@@ -125,10 +126,10 @@ export const getFindingsStats: Handler = async (args, _ctx) => {
125
126
  const response = await apiClient.getFindingsStats(project_id);
126
127
 
127
128
  if (!response.ok) {
128
- throw new Error(response.error || 'Failed to get findings stats');
129
+ return error(response.error || 'Failed to get findings stats');
129
130
  }
130
131
 
131
- return { result: response.data };
132
+ return success(response.data);
132
133
  };
133
134
 
134
135
  export const updateFinding: Handler = async (args, _ctx) => {
@@ -144,10 +145,10 @@ export const updateFinding: Handler = async (args, _ctx) => {
144
145
  });
145
146
 
146
147
  if (!response.ok) {
147
- throw new Error(response.error || 'Failed to update finding');
148
+ return error(response.error || 'Failed to update finding');
148
149
  }
149
150
 
150
- return { result: response.data };
151
+ return success(response.data);
151
152
  };
152
153
 
153
154
  export const deleteFinding: Handler = async (args, _ctx) => {
@@ -157,10 +158,10 @@ export const deleteFinding: Handler = async (args, _ctx) => {
157
158
  const response = await apiClient.deleteFinding(finding_id);
158
159
 
159
160
  if (!response.ok) {
160
- throw new Error(response.error || 'Failed to delete finding');
161
+ return error(response.error || 'Failed to delete finding');
161
162
  }
162
163
 
163
- return { result: response.data };
164
+ return success(response.data);
164
165
  };
165
166
 
166
167
  /**
@@ -183,10 +184,10 @@ export const queryKnowledgeBase: Handler = async (args, _ctx) => {
183
184
  });
184
185
 
185
186
  if (!response.ok) {
186
- throw new Error(response.error || 'Failed to query knowledge base');
187
+ return error(response.error || 'Failed to query knowledge base');
187
188
  }
188
189
 
189
- return { result: response.data };
190
+ return success(response.data);
190
191
  };
191
192
 
192
193
  /**
@@ -60,7 +60,7 @@ describe('addGitIssue', () => {
60
60
  issue_type: 'invalid_type',
61
61
  branch: 'feature/test',
62
62
  }, ctx)
63
- ).rejects.toThrow('Invalid issue_type');
63
+ ).rejects.toThrow(ValidationError);
64
64
  });
65
65
 
66
66
  it('should throw error for invalid task_id UUID', async () => {
@@ -180,35 +180,37 @@ describe('addGitIssue', () => {
180
180
  }
181
181
  });
182
182
 
183
- it('should throw error when API call fails', async () => {
183
+ it('should return error when API call fails', async () => {
184
184
  mockApiClient.addGitIssue.mockResolvedValue({
185
185
  ok: false,
186
186
  error: 'Insert failed',
187
187
  });
188
188
  const ctx = createMockContext();
189
189
 
190
- await expect(
191
- addGitIssue({
192
- project_id: VALID_PROJECT_ID,
193
- issue_type: 'merge_conflict',
194
- branch: 'feature/test',
195
- }, ctx)
196
- ).rejects.toThrow('Insert failed');
190
+ const result = await addGitIssue({
191
+ project_id: VALID_PROJECT_ID,
192
+ issue_type: 'merge_conflict',
193
+ branch: 'feature/test',
194
+ }, ctx);
195
+
196
+ expect(result.isError).toBe(true);
197
+ expect(result.result).toMatchObject({ error: 'Insert failed' });
197
198
  });
198
199
 
199
- it('should throw default error message when API fails without error', async () => {
200
+ it('should return default error message when API fails without error', async () => {
200
201
  mockApiClient.addGitIssue.mockResolvedValue({
201
202
  ok: false,
202
203
  });
203
204
  const ctx = createMockContext();
204
205
 
205
- await expect(
206
- addGitIssue({
207
- project_id: VALID_PROJECT_ID,
208
- issue_type: 'merge_conflict',
209
- branch: 'feature/test',
210
- }, ctx)
211
- ).rejects.toThrow('Failed to add git issue');
206
+ const result = await addGitIssue({
207
+ project_id: VALID_PROJECT_ID,
208
+ issue_type: 'merge_conflict',
209
+ branch: 'feature/test',
210
+ }, ctx);
211
+
212
+ expect(result.isError).toBe(true);
213
+ expect(result.result).toMatchObject({ error: 'Failed to add git issue' });
212
214
  });
213
215
  });
214
216
 
@@ -314,27 +316,29 @@ describe('resolveGitIssue', () => {
314
316
  );
315
317
  });
316
318
 
317
- it('should throw error when API call fails', async () => {
319
+ it('should return error when API call fails', async () => {
318
320
  mockApiClient.resolveGitIssue.mockResolvedValue({
319
321
  ok: false,
320
322
  error: 'Update failed',
321
323
  });
322
324
  const ctx = createMockContext();
323
325
 
324
- await expect(
325
- resolveGitIssue({ git_issue_id: VALID_GIT_ISSUE_ID }, ctx)
326
- ).rejects.toThrow('Update failed');
326
+ const result = await resolveGitIssue({ git_issue_id: VALID_GIT_ISSUE_ID }, ctx);
327
+
328
+ expect(result.isError).toBe(true);
329
+ expect(result.result).toMatchObject({ error: 'Update failed' });
327
330
  });
328
331
 
329
- it('should throw default error message when API fails without error', async () => {
332
+ it('should return default error message when API fails without error', async () => {
330
333
  mockApiClient.resolveGitIssue.mockResolvedValue({
331
334
  ok: false,
332
335
  });
333
336
  const ctx = createMockContext();
334
337
 
335
- await expect(
336
- resolveGitIssue({ git_issue_id: VALID_GIT_ISSUE_ID }, ctx)
337
- ).rejects.toThrow('Failed to resolve git issue');
338
+ const result = await resolveGitIssue({ git_issue_id: VALID_GIT_ISSUE_ID }, ctx);
339
+
340
+ expect(result.isError).toBe(true);
341
+ expect(result.result).toMatchObject({ error: 'Failed to resolve git issue' });
338
342
  });
339
343
  });
340
344
 
@@ -364,7 +368,7 @@ describe('getGitIssues', () => {
364
368
 
365
369
  await expect(
366
370
  getGitIssues({ project_id: VALID_PROJECT_ID, status: 'invalid_status' }, ctx)
367
- ).rejects.toThrow('Invalid status');
371
+ ).rejects.toThrow(ValidationError);
368
372
  });
369
373
 
370
374
  it('should throw error for invalid issue_type filter', async () => {
@@ -372,7 +376,7 @@ describe('getGitIssues', () => {
372
376
 
373
377
  await expect(
374
378
  getGitIssues({ project_id: VALID_PROJECT_ID, issue_type: 'invalid_type' }, ctx)
375
- ).rejects.toThrow('Invalid issue_type');
379
+ ).rejects.toThrow(ValidationError);
376
380
  });
377
381
 
378
382
  it('should return empty list when no git issues', async () => {
@@ -519,27 +523,29 @@ describe('getGitIssues', () => {
519
523
  );
520
524
  });
521
525
 
522
- it('should throw error when API call fails', async () => {
526
+ it('should return error when API call fails', async () => {
523
527
  mockApiClient.getGitIssues.mockResolvedValue({
524
528
  ok: false,
525
529
  error: 'Query failed',
526
530
  });
527
531
  const ctx = createMockContext();
528
532
 
529
- await expect(
530
- getGitIssues({ project_id: VALID_PROJECT_ID }, ctx)
531
- ).rejects.toThrow('Query failed');
533
+ const result = await getGitIssues({ project_id: VALID_PROJECT_ID }, ctx);
534
+
535
+ expect(result.isError).toBe(true);
536
+ expect(result.result).toMatchObject({ error: 'Query failed' });
532
537
  });
533
538
 
534
- it('should throw default error message when API fails without error', async () => {
539
+ it('should return default error message when API fails without error', async () => {
535
540
  mockApiClient.getGitIssues.mockResolvedValue({
536
541
  ok: false,
537
542
  });
538
543
  const ctx = createMockContext();
539
544
 
540
- await expect(
541
- getGitIssues({ project_id: VALID_PROJECT_ID }, ctx)
542
- ).rejects.toThrow('Failed to fetch git issues');
545
+ const result = await getGitIssues({ project_id: VALID_PROJECT_ID }, ctx);
546
+
547
+ expect(result.isError).toBe(true);
548
+ expect(result.result).toMatchObject({ error: 'Failed to fetch git issues' });
543
549
  });
544
550
  });
545
551
 
@@ -598,26 +604,28 @@ describe('deleteGitIssue', () => {
598
604
  );
599
605
  });
600
606
 
601
- it('should throw error when API call fails', async () => {
607
+ it('should return error when API call fails', async () => {
602
608
  mockApiClient.deleteGitIssue.mockResolvedValue({
603
609
  ok: false,
604
610
  error: 'Delete failed',
605
611
  });
606
612
  const ctx = createMockContext();
607
613
 
608
- await expect(
609
- deleteGitIssue({ git_issue_id: VALID_GIT_ISSUE_ID }, ctx)
610
- ).rejects.toThrow('Delete failed');
614
+ const result = await deleteGitIssue({ git_issue_id: VALID_GIT_ISSUE_ID }, ctx);
615
+
616
+ expect(result.isError).toBe(true);
617
+ expect(result.result).toMatchObject({ error: 'Delete failed' });
611
618
  });
612
619
 
613
- it('should throw default error message when API fails without error', async () => {
620
+ it('should return default error message when API fails without error', async () => {
614
621
  mockApiClient.deleteGitIssue.mockResolvedValue({
615
622
  ok: false,
616
623
  });
617
624
  const ctx = createMockContext();
618
625
 
619
- await expect(
620
- deleteGitIssue({ git_issue_id: VALID_GIT_ISSUE_ID }, ctx)
621
- ).rejects.toThrow('Failed to delete git issue');
626
+ const result = await deleteGitIssue({ git_issue_id: VALID_GIT_ISSUE_ID }, ctx);
627
+
628
+ expect(result.isError).toBe(true);
629
+ expect(result.result).toMatchObject({ error: 'Failed to delete git issue' });
622
630
  });
623
631
  });
@@ -70,7 +70,7 @@ export const addGitIssue: Handler = async (args, ctx) => {
70
70
  }, ctx.session.currentSessionId || undefined);
71
71
 
72
72
  if (!response.ok) {
73
- throw new Error(response.error || 'Failed to add git issue');
73
+ return { result: { error: response.error || 'Failed to add git issue' }, isError: true };
74
74
  }
75
75
 
76
76
  return { result: response.data };
@@ -86,7 +86,7 @@ export const resolveGitIssue: Handler = async (args, ctx) => {
86
86
  }, ctx.session.currentSessionId || undefined);
87
87
 
88
88
  if (!response.ok) {
89
- throw new Error(response.error || 'Failed to resolve git issue');
89
+ return { result: { error: response.error || 'Failed to resolve git issue' }, isError: true };
90
90
  }
91
91
 
92
92
  return { result: response.data };
@@ -104,7 +104,7 @@ export const getGitIssues: Handler = async (args, _ctx) => {
104
104
  });
105
105
 
106
106
  if (!response.ok) {
107
- throw new Error(response.error || 'Failed to fetch git issues');
107
+ return { result: { error: response.error || 'Failed to fetch git issues' }, isError: true };
108
108
  }
109
109
 
110
110
  return { result: response.data };
@@ -117,7 +117,7 @@ export const deleteGitIssue: Handler = async (args, _ctx) => {
117
117
  const response = await apiClient.deleteGitIssue(git_issue_id);
118
118
 
119
119
  if (!response.ok) {
120
- throw new Error(response.error || 'Failed to delete git issue');
120
+ return { result: { error: response.error || 'Failed to delete git issue' }, isError: true };
121
121
  }
122
122
 
123
123
  return { result: response.data };