@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
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Role Management Handlers
3
+ *
4
+ * Handles agent role configuration and assignment:
5
+ * - get_role_settings: Get role configuration for a project
6
+ * - update_role_settings: Update role settings for a project
7
+ * - set_session_role: Set the role for the current session
8
+ * - get_agents_by_role: Get active agents grouped by their assigned roles
9
+ */
10
+
11
+ import type { Handler, HandlerRegistry, AgentRole } from './types.js';
12
+ import { getApiClient } from '../api-client.js';
13
+
14
+ export const getRoleSettings: Handler = async (args, _ctx) => {
15
+ const { project_id } = args as { project_id: string };
16
+
17
+ if (!project_id) {
18
+ return {
19
+ result: { error: 'project_id is required' },
20
+ };
21
+ }
22
+
23
+ const apiClient = getApiClient();
24
+ const response = await apiClient.proxy<{
25
+ roles: Array<{
26
+ role: AgentRole;
27
+ enabled: boolean;
28
+ display_name: string | null;
29
+ description: string | null;
30
+ priority_filter: number[] | null;
31
+ fallback_activities: string[] | null;
32
+ auto_assign_validation: boolean;
33
+ auto_assign_deployment: boolean;
34
+ }>;
35
+ }>('get_role_settings', { project_id });
36
+
37
+ if (!response.ok) {
38
+ return {
39
+ result: { error: response.error || 'Failed to get role settings' },
40
+ };
41
+ }
42
+
43
+ return { result: response.data };
44
+ };
45
+
46
+ export const updateRoleSettings: Handler = async (args, _ctx) => {
47
+ const {
48
+ project_id,
49
+ role,
50
+ enabled,
51
+ display_name,
52
+ description,
53
+ priority_filter,
54
+ fallback_activities,
55
+ auto_assign_validation,
56
+ auto_assign_deployment,
57
+ } = args as {
58
+ project_id: string;
59
+ role: AgentRole;
60
+ enabled?: boolean;
61
+ display_name?: string;
62
+ description?: string;
63
+ priority_filter?: number[];
64
+ fallback_activities?: string[];
65
+ auto_assign_validation?: boolean;
66
+ auto_assign_deployment?: boolean;
67
+ };
68
+
69
+ if (!project_id) {
70
+ return {
71
+ result: { error: 'project_id is required' },
72
+ };
73
+ }
74
+
75
+ if (!role) {
76
+ return {
77
+ result: { error: 'role is required' },
78
+ };
79
+ }
80
+
81
+ const apiClient = getApiClient();
82
+ const response = await apiClient.proxy<{
83
+ success: boolean;
84
+ role: AgentRole;
85
+ }>('update_role_settings', {
86
+ project_id,
87
+ role,
88
+ enabled,
89
+ display_name,
90
+ description,
91
+ priority_filter,
92
+ fallback_activities,
93
+ auto_assign_validation,
94
+ auto_assign_deployment,
95
+ });
96
+
97
+ if (!response.ok) {
98
+ return {
99
+ result: { error: response.error || 'Failed to update role settings' },
100
+ };
101
+ }
102
+
103
+ return { result: response.data };
104
+ };
105
+
106
+ export const setSessionRole: Handler = async (args, ctx) => {
107
+ const { role, role_config } = args as {
108
+ role: AgentRole;
109
+ role_config?: Record<string, unknown>;
110
+ };
111
+ const { session, updateSession } = ctx;
112
+
113
+ if (!role) {
114
+ return {
115
+ result: { error: 'role is required' },
116
+ };
117
+ }
118
+
119
+ const validRoles: AgentRole[] = ['developer', 'validator', 'deployer', 'reviewer', 'maintainer'];
120
+ if (!validRoles.includes(role)) {
121
+ return {
122
+ result: {
123
+ error: `Invalid role: ${role}. Must be one of: ${validRoles.join(', ')}`,
124
+ },
125
+ };
126
+ }
127
+
128
+ // Update local session state
129
+ updateSession({ currentRole: role });
130
+
131
+ // If there's an active session, update it on the server too
132
+ if (session.currentSessionId) {
133
+ const apiClient = getApiClient();
134
+ const response = await apiClient.proxy<{
135
+ success: boolean;
136
+ session_id: string;
137
+ role: AgentRole;
138
+ }>('set_session_role', {
139
+ session_id: session.currentSessionId,
140
+ role,
141
+ role_config,
142
+ });
143
+
144
+ if (!response.ok) {
145
+ return {
146
+ result: { error: response.error || 'Failed to update session role' },
147
+ };
148
+ }
149
+
150
+ return {
151
+ result: {
152
+ success: true,
153
+ session_id: session.currentSessionId,
154
+ role,
155
+ message: `Session role updated to ${role}. Task filtering and fallback suggestions will now be optimized for this role.`,
156
+ },
157
+ };
158
+ }
159
+
160
+ return {
161
+ result: {
162
+ success: true,
163
+ role,
164
+ message: `Local role set to ${role}. Start a session to persist this role.`,
165
+ },
166
+ };
167
+ };
168
+
169
+ export const getAgentsByRole: Handler = async (args, _ctx) => {
170
+ const { project_id } = args as { project_id: string };
171
+
172
+ if (!project_id) {
173
+ return {
174
+ result: { error: 'project_id is required' },
175
+ };
176
+ }
177
+
178
+ const apiClient = getApiClient();
179
+ const response = await apiClient.proxy<{
180
+ agents_by_role: Record<AgentRole, Array<{
181
+ session_id: string;
182
+ agent_name: string;
183
+ status: string;
184
+ current_task_id: string | null;
185
+ current_task_title: string | null;
186
+ last_synced_at: string;
187
+ }>>;
188
+ total_active: number;
189
+ }>('get_agents_by_role', { project_id });
190
+
191
+ if (!response.ok) {
192
+ return {
193
+ result: { error: response.error || 'Failed to get agents by role' },
194
+ };
195
+ }
196
+
197
+ return { result: response.data };
198
+ };
199
+
200
+ /**
201
+ * Role handlers registry
202
+ */
203
+ export const roleHandlers: HandlerRegistry = {
204
+ get_role_settings: getRoleSettings,
205
+ update_role_settings: updateRoleSettings,
206
+ set_session_role: setSessionRole,
207
+ get_agents_by_role: getAgentsByRole,
208
+ };
@@ -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
  });
@@ -10,22 +10,47 @@
10
10
  */
11
11
 
12
12
  import type { Handler, HandlerRegistry, TokenUsage } from './types.js';
13
+ import { parseArgs, createEnumValidator } from '../validators.js';
13
14
  import { getApiClient } from '../api-client.js';
14
15
 
16
+ const VALID_MODES = ['lite', 'full'] as const;
17
+ const VALID_MODELS = ['opus', 'sonnet', 'haiku'] as const;
18
+ const VALID_ROLES = ['developer', 'validator', 'deployer', 'reviewer', 'maintainer'] as const;
19
+
20
+ type SessionMode = typeof VALID_MODES[number];
21
+ type SessionModel = typeof VALID_MODELS[number];
22
+ type SessionRole = typeof VALID_ROLES[number];
23
+
24
+ // Argument schemas for type-safe parsing
25
+ const startWorkSessionSchema = {
26
+ project_id: { type: 'string' as const },
27
+ git_url: { type: 'string' as const },
28
+ mode: { type: 'string' as const, default: 'lite', validate: createEnumValidator(VALID_MODES) },
29
+ model: { type: 'string' as const, validate: createEnumValidator(VALID_MODELS) },
30
+ role: { type: 'string' as const, default: 'developer', validate: createEnumValidator(VALID_ROLES) },
31
+ };
32
+
33
+ const heartbeatSchema = {
34
+ session_id: { type: 'string' as const },
35
+ current_worktree_path: { type: 'string' as const },
36
+ };
37
+
38
+ const endWorkSessionSchema = {
39
+ session_id: { type: 'string' as const },
40
+ };
41
+
42
+ const getHelpSchema = {
43
+ topic: { type: 'string' as const, required: true as const },
44
+ };
45
+
15
46
  export const startWorkSession: Handler = async (args, ctx) => {
16
- const { project_id, git_url, mode = 'lite', model, role = 'developer' } = args as {
17
- project_id?: string;
18
- git_url?: string;
19
- mode?: 'lite' | 'full';
20
- model?: 'opus' | 'sonnet' | 'haiku';
21
- role?: 'developer' | 'validator' | 'deployer' | 'reviewer' | 'maintainer';
22
- };
47
+ const { project_id, git_url, mode, model, role } = parseArgs(args, startWorkSessionSchema);
23
48
 
24
49
  const { session, updateSession } = ctx;
25
50
 
26
51
  // Reset token tracking for new session with model info
27
52
  const normalizedModel = model ? model.toLowerCase().replace(/^claude[- ]*/i, '') : null;
28
- const validModel = normalizedModel && ['opus', 'sonnet', 'haiku'].includes(normalizedModel)
53
+ const validModel = normalizedModel && VALID_MODELS.includes(normalizedModel as SessionModel)
29
54
  ? normalizedModel
30
55
  : null;
31
56
 
@@ -52,9 +77,9 @@ export const startWorkSession: Handler = async (args, ctx) => {
52
77
  const response = await apiClient.startSession({
53
78
  project_id,
54
79
  git_url,
55
- mode,
56
- model,
57
- role
80
+ mode: mode as SessionMode,
81
+ model: model as SessionModel | undefined,
82
+ role: role as SessionRole
58
83
  });
59
84
 
60
85
  if (!response.ok) {
@@ -80,22 +105,40 @@ export const startWorkSession: Handler = async (args, ctx) => {
80
105
  });
81
106
  }
82
107
 
83
- // 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
84
112
  const result: Record<string, unknown> = {
85
113
  session_started: true,
86
- directive: data.directive || 'ACTION_REQUIRED: Start working immediately.',
87
- auto_continue: true,
88
- session_id: data.session_id,
89
- persona: data.persona,
90
- role: data.role,
91
- project: data.project,
92
114
  };
93
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
+
94
131
  // Add task data
95
132
  if (data.next_task) {
96
133
  result.next_task = data.next_task;
97
134
  }
98
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
+
99
142
  // Add active tasks for full mode
100
143
  if (data.active_tasks) {
101
144
  result.active_tasks = data.active_tasks;
@@ -128,8 +171,13 @@ export const startWorkSession: Handler = async (args, ctx) => {
128
171
  };
129
172
  }
130
173
 
131
- // Add next action at end
132
- 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) {
133
181
  result.next_action = `update_task(task_id: "${data.next_task.id}", status: "in_progress")`;
134
182
  } else if (data.project) {
135
183
  result.next_action = `start_fallback_activity(project_id: "${data.project.id}", activity: "code_review")`;
@@ -139,10 +187,7 @@ export const startWorkSession: Handler = async (args, ctx) => {
139
187
  };
140
188
 
141
189
  export const heartbeat: Handler = async (args, ctx) => {
142
- const { session_id, current_worktree_path } = args as {
143
- session_id?: string;
144
- current_worktree_path?: string | null;
145
- };
190
+ const { session_id, current_worktree_path } = parseArgs(args, heartbeatSchema);
146
191
  const { session } = ctx;
147
192
  const targetSession = session_id || session.currentSessionId;
148
193
 
@@ -186,7 +231,7 @@ export const heartbeat: Handler = async (args, ctx) => {
186
231
  };
187
232
 
188
233
  export const endWorkSession: Handler = async (args, ctx) => {
189
- const { session_id } = args as { session_id?: string };
234
+ const { session_id } = parseArgs(args, endWorkSessionSchema);
190
235
  const { session, updateSession } = ctx;
191
236
  const targetSession = session_id || session.currentSessionId;
192
237
 
@@ -251,7 +296,7 @@ export const endWorkSession: Handler = async (args, ctx) => {
251
296
  };
252
297
 
253
298
  export const getHelp: Handler = async (args, _ctx) => {
254
- const { topic } = args as { topic: string };
299
+ const { topic } = parseArgs(args, getHelpSchema);
255
300
 
256
301
  const apiClient = getApiClient();
257
302
  const response = await apiClient.getHelpTopic(topic);
@@ -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