@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.
- package/README.md +60 -7
- package/dist/api-client.d.ts +251 -1
- package/dist/api-client.js +82 -3
- package/dist/handlers/blockers.js +9 -8
- package/dist/handlers/bodies-of-work.js +96 -63
- 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 +112 -50
- package/dist/handlers/decisions.js +32 -19
- package/dist/handlers/deployment.js +144 -122
- package/dist/handlers/discovery.d.ts +7 -0
- package/dist/handlers/discovery.js +96 -7
- package/dist/handlers/fallback.js +29 -23
- package/dist/handlers/file-checkouts.d.ts +20 -0
- package/dist/handlers/file-checkouts.js +133 -0
- package/dist/handlers/findings.d.ts +6 -0
- package/dist/handlers/findings.js +96 -40
- package/dist/handlers/git-issues.js +40 -36
- package/dist/handlers/ideas.js +49 -31
- package/dist/handlers/index.d.ts +3 -0
- package/dist/handlers/index.js +9 -0
- package/dist/handlers/milestones.js +39 -32
- package/dist/handlers/organizations.js +99 -91
- package/dist/handlers/progress.js +24 -13
- package/dist/handlers/project.js +68 -28
- package/dist/handlers/requests.js +18 -14
- package/dist/handlers/roles.d.ts +18 -0
- package/dist/handlers/roles.js +130 -0
- package/dist/handlers/session.js +58 -17
- package/dist/handlers/sprints.js +93 -81
- package/dist/handlers/tasks.d.ts +2 -0
- package/dist/handlers/tasks.js +189 -91
- package/dist/handlers/types.d.ts +64 -2
- package/dist/handlers/types.js +48 -1
- package/dist/handlers/validation.js +21 -17
- 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 +685 -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 +320 -6
- package/src/handlers/__test-setup__.ts +16 -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 +115 -115
- package/src/handlers/connectors.test.ts +834 -0
- package/src/handlers/connectors.ts +229 -0
- package/src/handlers/cost.test.ts +34 -44
- package/src/handlers/cost.ts +136 -85
- package/src/handlers/decisions.test.ts +37 -27
- package/src/handlers/decisions.ts +35 -30
- package/src/handlers/deployment.ts +180 -208
- package/src/handlers/discovery.test.ts +4 -5
- package/src/handlers/discovery.ts +98 -8
- package/src/handlers/fallback.test.ts +26 -22
- package/src/handlers/fallback.ts +36 -33
- package/src/handlers/file-checkouts.test.ts +670 -0
- package/src/handlers/file-checkouts.ts +165 -0
- package/src/handlers/findings.test.ts +178 -19
- package/src/handlers/findings.ts +112 -74
- package/src/handlers/git-issues.test.ts +51 -43
- package/src/handlers/git-issues.ts +44 -84
- package/src/handlers/ideas.test.ts +28 -23
- package/src/handlers/ideas.ts +61 -59
- package/src/handlers/index.ts +9 -0
- package/src/handlers/milestones.test.ts +33 -28
- package/src/handlers/milestones.ts +52 -50
- package/src/handlers/organizations.test.ts +104 -83
- package/src/handlers/organizations.ts +117 -142
- package/src/handlers/progress.test.ts +20 -14
- package/src/handlers/progress.ts +26 -24
- package/src/handlers/project.test.ts +34 -27
- package/src/handlers/project.ts +95 -63
- package/src/handlers/requests.test.ts +27 -18
- package/src/handlers/requests.ts +21 -17
- package/src/handlers/roles.test.ts +303 -0
- package/src/handlers/roles.ts +208 -0
- package/src/handlers/session.test.ts +47 -0
- package/src/handlers/session.ts +71 -26
- package/src/handlers/sprints.test.ts +71 -50
- package/src/handlers/sprints.ts +113 -146
- package/src/handlers/tasks.test.ts +77 -15
- package/src/handlers/tasks.ts +231 -156
- 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 +23 -25
- 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 +685 -9
- package/src/utils.test.ts +2 -2
- package/src/utils.ts +17 -0
- package/dist/config/tool-categories.d.ts +0 -31
- package/dist/config/tool-categories.js +0 -253
- package/dist/knowledge.d.ts +0 -6
- 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
|
});
|
package/src/handlers/session.ts
CHANGED
|
@@ -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
|
|
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 &&
|
|
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
|
-
//
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|