@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.
- package/README.md +60 -7
- package/dist/api-client.d.ts +187 -0
- package/dist/api-client.js +48 -0
- package/dist/handlers/blockers.js +9 -8
- package/dist/handlers/bodies-of-work.js +14 -14
- package/dist/handlers/connectors.d.ts +45 -0
- package/dist/handlers/connectors.js +183 -0
- package/dist/handlers/cost.d.ts +10 -0
- package/dist/handlers/cost.js +54 -0
- package/dist/handlers/decisions.js +3 -3
- package/dist/handlers/deployment.js +35 -19
- package/dist/handlers/discovery.d.ts +7 -0
- package/dist/handlers/discovery.js +61 -2
- package/dist/handlers/fallback.js +5 -4
- package/dist/handlers/file-checkouts.d.ts +2 -0
- package/dist/handlers/file-checkouts.js +38 -6
- package/dist/handlers/findings.js +13 -12
- package/dist/handlers/git-issues.js +4 -4
- package/dist/handlers/ideas.js +5 -5
- package/dist/handlers/index.d.ts +1 -0
- package/dist/handlers/index.js +3 -0
- package/dist/handlers/milestones.js +5 -5
- package/dist/handlers/organizations.js +13 -13
- package/dist/handlers/progress.js +2 -2
- package/dist/handlers/project.js +6 -6
- package/dist/handlers/requests.js +3 -3
- package/dist/handlers/session.js +28 -9
- package/dist/handlers/sprints.js +17 -17
- package/dist/handlers/tasks.d.ts +2 -0
- package/dist/handlers/tasks.js +78 -20
- package/dist/handlers/types.d.ts +64 -2
- package/dist/handlers/types.js +48 -1
- package/dist/handlers/validation.js +3 -3
- package/dist/index.js +7 -2716
- package/dist/token-tracking.d.ts +74 -0
- package/dist/token-tracking.js +122 -0
- package/dist/tools.js +298 -9
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +17 -0
- package/docs/TOOLS.md +2053 -0
- package/package.json +4 -1
- package/scripts/generate-docs.ts +212 -0
- package/src/api-client.test.ts +718 -0
- package/src/api-client.ts +231 -0
- package/src/handlers/__test-setup__.ts +9 -0
- package/src/handlers/blockers.test.ts +31 -19
- package/src/handlers/blockers.ts +9 -8
- package/src/handlers/bodies-of-work.test.ts +55 -32
- package/src/handlers/bodies-of-work.ts +14 -14
- package/src/handlers/connectors.test.ts +834 -0
- package/src/handlers/connectors.ts +229 -0
- package/src/handlers/cost.ts +66 -0
- package/src/handlers/decisions.test.ts +34 -25
- package/src/handlers/decisions.ts +3 -3
- package/src/handlers/deployment.ts +39 -19
- package/src/handlers/discovery.ts +61 -2
- package/src/handlers/fallback.test.ts +26 -22
- package/src/handlers/fallback.ts +5 -4
- package/src/handlers/file-checkouts.test.ts +242 -49
- package/src/handlers/file-checkouts.ts +44 -6
- package/src/handlers/findings.test.ts +38 -24
- package/src/handlers/findings.ts +13 -12
- package/src/handlers/git-issues.test.ts +51 -43
- package/src/handlers/git-issues.ts +4 -4
- package/src/handlers/ideas.test.ts +28 -23
- package/src/handlers/ideas.ts +5 -5
- package/src/handlers/index.ts +3 -0
- package/src/handlers/milestones.test.ts +33 -28
- package/src/handlers/milestones.ts +5 -5
- package/src/handlers/organizations.test.ts +104 -83
- package/src/handlers/organizations.ts +13 -13
- package/src/handlers/progress.test.ts +20 -14
- package/src/handlers/progress.ts +2 -2
- package/src/handlers/project.test.ts +34 -27
- package/src/handlers/project.ts +6 -6
- package/src/handlers/requests.test.ts +27 -18
- package/src/handlers/requests.ts +3 -3
- package/src/handlers/session.test.ts +47 -0
- package/src/handlers/session.ts +32 -9
- package/src/handlers/sprints.test.ts +71 -50
- package/src/handlers/sprints.ts +17 -17
- package/src/handlers/tasks.test.ts +77 -15
- package/src/handlers/tasks.ts +90 -21
- package/src/handlers/tool-categories.test.ts +66 -0
- package/src/handlers/types.ts +81 -2
- package/src/handlers/validation.test.ts +78 -45
- package/src/handlers/validation.ts +3 -3
- package/src/index.ts +12 -2732
- package/src/token-tracking.test.ts +453 -0
- package/src/token-tracking.ts +164 -0
- package/src/tools.ts +298 -9
- package/src/utils.test.ts +2 -2
- 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
|
|
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
|
|
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
|
|
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
|
|
248
|
-
|
|
249
|
-
).
|
|
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
|
|
334
|
+
it('should return error when insert fails', async () => {
|
|
331
335
|
mockApiClient.createProject.mockResolvedValue({
|
|
332
336
|
ok: false,
|
|
333
|
-
error: 'Failed to create project
|
|
337
|
+
error: 'Failed to create project',
|
|
334
338
|
});
|
|
335
339
|
const ctx = createMockContext();
|
|
336
340
|
|
|
337
|
-
await
|
|
338
|
-
|
|
339
|
-
).
|
|
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
|
|
453
|
+
it('should return error when update fails', async () => {
|
|
449
454
|
mockApiClient.updateProject.mockResolvedValue({
|
|
450
455
|
ok: false,
|
|
451
|
-
error: 'Failed to update project
|
|
456
|
+
error: 'Failed to update project',
|
|
452
457
|
});
|
|
453
458
|
const ctx = createMockContext();
|
|
454
459
|
|
|
455
|
-
await
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
).
|
|
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
|
|
531
|
+
it('should return error when update fails', async () => {
|
|
526
532
|
mockApiClient.updateProjectReadme.mockResolvedValue({
|
|
527
533
|
ok: false,
|
|
528
|
-
error: 'Failed to update README
|
|
534
|
+
error: 'Failed to update README',
|
|
529
535
|
});
|
|
530
536
|
const ctx = createMockContext();
|
|
531
537
|
|
|
532
|
-
await
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
).
|
|
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
|
});
|
package/src/handlers/project.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
).
|
|
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
|
|
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
|
|
170
|
-
|
|
171
|
-
).
|
|
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
|
|
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
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
).
|
|
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
|
});
|
package/src/handlers/requests.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
});
|
package/src/handlers/session.ts
CHANGED
|
@@ -105,22 +105,40 @@ export const startWorkSession: Handler = async (args, ctx) => {
|
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
//
|
|
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 (
|
|
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
|
|
102
|
+
it('should return error for invalid start_date format', async () => {
|
|
103
103
|
const ctx = createMockContext();
|
|
104
104
|
|
|
105
|
-
await
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
).
|
|
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
|
|
118
|
+
it('should return error when end_date is before start_date', async () => {
|
|
116
119
|
const ctx = createMockContext();
|
|
117
120
|
|
|
118
|
-
await
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
).
|
|
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
|
|
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
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
).
|
|
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
|
|
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
|
|
249
|
-
|
|
250
|
-
).
|
|
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
|
|
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
|
|
428
|
-
|
|
429
|
-
).
|
|
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
|
|
532
|
+
it('should return error for negative story_points', async () => {
|
|
518
533
|
const ctx = createMockContext();
|
|
519
534
|
|
|
520
|
-
await
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
).
|
|
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
|
|
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
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
).
|
|
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
|
|