@vibescope/mcp-server 0.1.0 → 0.2.1

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 (76) hide show
  1. package/README.md +1 -1
  2. package/dist/api-client.d.ts +120 -2
  3. package/dist/api-client.js +51 -5
  4. package/dist/handlers/bodies-of-work.js +84 -50
  5. package/dist/handlers/cost.js +62 -54
  6. package/dist/handlers/decisions.js +29 -16
  7. package/dist/handlers/deployment.js +114 -107
  8. package/dist/handlers/discovery.d.ts +3 -0
  9. package/dist/handlers/discovery.js +55 -657
  10. package/dist/handlers/fallback.js +42 -28
  11. package/dist/handlers/file-checkouts.d.ts +18 -0
  12. package/dist/handlers/file-checkouts.js +101 -0
  13. package/dist/handlers/findings.d.ts +14 -1
  14. package/dist/handlers/findings.js +104 -28
  15. package/dist/handlers/git-issues.js +36 -32
  16. package/dist/handlers/ideas.js +44 -26
  17. package/dist/handlers/index.d.ts +2 -0
  18. package/dist/handlers/index.js +6 -0
  19. package/dist/handlers/milestones.js +34 -27
  20. package/dist/handlers/organizations.js +86 -78
  21. package/dist/handlers/progress.js +22 -11
  22. package/dist/handlers/project.js +62 -22
  23. package/dist/handlers/requests.js +15 -11
  24. package/dist/handlers/roles.d.ts +18 -0
  25. package/dist/handlers/roles.js +130 -0
  26. package/dist/handlers/session.js +52 -15
  27. package/dist/handlers/sprints.js +78 -65
  28. package/dist/handlers/tasks.js +135 -74
  29. package/dist/handlers/tool-docs.d.ts +4 -3
  30. package/dist/handlers/tool-docs.js +252 -5
  31. package/dist/handlers/validation.js +30 -14
  32. package/dist/index.js +25 -7
  33. package/dist/tools.js +417 -4
  34. package/package.json +1 -1
  35. package/src/api-client.ts +161 -8
  36. package/src/handlers/__test-setup__.ts +12 -0
  37. package/src/handlers/bodies-of-work.ts +127 -111
  38. package/src/handlers/cost.test.ts +34 -44
  39. package/src/handlers/cost.ts +77 -92
  40. package/src/handlers/decisions.test.ts +3 -2
  41. package/src/handlers/decisions.ts +32 -27
  42. package/src/handlers/deployment.ts +144 -190
  43. package/src/handlers/discovery.test.ts +4 -5
  44. package/src/handlers/discovery.ts +60 -746
  45. package/src/handlers/fallback.test.ts +78 -0
  46. package/src/handlers/fallback.ts +51 -38
  47. package/src/handlers/file-checkouts.test.ts +477 -0
  48. package/src/handlers/file-checkouts.ts +127 -0
  49. package/src/handlers/findings.test.ts +274 -2
  50. package/src/handlers/findings.ts +123 -57
  51. package/src/handlers/git-issues.ts +40 -80
  52. package/src/handlers/ideas.ts +56 -54
  53. package/src/handlers/index.ts +6 -0
  54. package/src/handlers/milestones.test.ts +1 -1
  55. package/src/handlers/milestones.ts +47 -45
  56. package/src/handlers/organizations.ts +104 -129
  57. package/src/handlers/progress.ts +24 -22
  58. package/src/handlers/project.ts +89 -57
  59. package/src/handlers/requests.ts +18 -14
  60. package/src/handlers/roles.test.ts +303 -0
  61. package/src/handlers/roles.ts +208 -0
  62. package/src/handlers/session.test.ts +37 -2
  63. package/src/handlers/session.ts +64 -21
  64. package/src/handlers/sprints.ts +114 -134
  65. package/src/handlers/tasks.test.ts +61 -0
  66. package/src/handlers/tasks.ts +170 -139
  67. package/src/handlers/tool-docs.ts +1024 -0
  68. package/src/handlers/validation.test.ts +53 -1
  69. package/src/handlers/validation.ts +32 -21
  70. package/src/index.ts +25 -7
  71. package/src/tools.ts +417 -4
  72. package/dist/config/tool-categories.d.ts +0 -31
  73. package/dist/config/tool-categories.js +0 -253
  74. package/dist/knowledge.d.ts +0 -6
  75. package/dist/knowledge.js +0 -218
  76. package/src/knowledge.ts +0 -230
@@ -0,0 +1,303 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import {
3
+ getRoleSettings,
4
+ updateRoleSettings,
5
+ setSessionRole,
6
+ getAgentsByRole,
7
+ } from './roles.js';
8
+ import { createMockContext } from './__test-utils__.js';
9
+ import { mockApiClient } from './__test-setup__.js';
10
+
11
+ // ============================================================================
12
+ // getRoleSettings Tests
13
+ // ============================================================================
14
+
15
+ describe('getRoleSettings', () => {
16
+ beforeEach(() => vi.clearAllMocks());
17
+
18
+ it('should return error for missing project_id', async () => {
19
+ const ctx = createMockContext();
20
+
21
+ const result = await getRoleSettings({}, ctx);
22
+
23
+ expect(result.result).toMatchObject({
24
+ error: 'project_id is required',
25
+ });
26
+ });
27
+
28
+ it('should get role settings successfully', async () => {
29
+ const mockRoles = [
30
+ { role: 'developer', enabled: true, display_name: 'Developer' },
31
+ { role: 'validator', enabled: true, display_name: 'Validator' },
32
+ ];
33
+ mockApiClient.proxy.mockResolvedValue({
34
+ ok: true,
35
+ data: { roles: mockRoles },
36
+ });
37
+ const ctx = createMockContext();
38
+
39
+ const result = await getRoleSettings(
40
+ { project_id: '123e4567-e89b-12d3-a456-426614174000' },
41
+ ctx
42
+ );
43
+
44
+ expect(result.result).toMatchObject({ roles: mockRoles });
45
+ expect(mockApiClient.proxy).toHaveBeenCalledWith(
46
+ 'get_role_settings',
47
+ { project_id: '123e4567-e89b-12d3-a456-426614174000' }
48
+ );
49
+ });
50
+
51
+ it('should return error from API', async () => {
52
+ mockApiClient.proxy.mockResolvedValue({
53
+ ok: false,
54
+ error: 'Project not found',
55
+ });
56
+ const ctx = createMockContext();
57
+
58
+ const result = await getRoleSettings(
59
+ { project_id: '123e4567-e89b-12d3-a456-426614174000' },
60
+ ctx
61
+ );
62
+
63
+ expect(result.result).toMatchObject({
64
+ error: 'Project not found',
65
+ });
66
+ });
67
+ });
68
+
69
+ // ============================================================================
70
+ // updateRoleSettings Tests
71
+ // ============================================================================
72
+
73
+ describe('updateRoleSettings', () => {
74
+ beforeEach(() => vi.clearAllMocks());
75
+
76
+ it('should return error for missing project_id', async () => {
77
+ const ctx = createMockContext();
78
+
79
+ const result = await updateRoleSettings({ role: 'developer' }, ctx);
80
+
81
+ expect(result.result).toMatchObject({
82
+ error: 'project_id is required',
83
+ });
84
+ });
85
+
86
+ it('should return error for missing role', async () => {
87
+ const ctx = createMockContext();
88
+
89
+ const result = await updateRoleSettings(
90
+ { project_id: '123e4567-e89b-12d3-a456-426614174000' },
91
+ ctx
92
+ );
93
+
94
+ expect(result.result).toMatchObject({
95
+ error: 'role is required',
96
+ });
97
+ });
98
+
99
+ it('should update role settings successfully', async () => {
100
+ mockApiClient.proxy.mockResolvedValue({
101
+ ok: true,
102
+ data: { success: true, role: 'validator' },
103
+ });
104
+ const ctx = createMockContext();
105
+
106
+ const result = await updateRoleSettings(
107
+ {
108
+ project_id: '123e4567-e89b-12d3-a456-426614174000',
109
+ role: 'validator',
110
+ enabled: true,
111
+ auto_assign_validation: true,
112
+ },
113
+ ctx
114
+ );
115
+
116
+ expect(result.result).toMatchObject({
117
+ success: true,
118
+ role: 'validator',
119
+ });
120
+ expect(mockApiClient.proxy).toHaveBeenCalledWith(
121
+ 'update_role_settings',
122
+ expect.objectContaining({
123
+ project_id: '123e4567-e89b-12d3-a456-426614174000',
124
+ role: 'validator',
125
+ enabled: true,
126
+ auto_assign_validation: true,
127
+ })
128
+ );
129
+ });
130
+
131
+ it('should return error from API', async () => {
132
+ mockApiClient.proxy.mockResolvedValue({
133
+ ok: false,
134
+ error: 'Access denied',
135
+ });
136
+ const ctx = createMockContext();
137
+
138
+ const result = await updateRoleSettings(
139
+ {
140
+ project_id: '123e4567-e89b-12d3-a456-426614174000',
141
+ role: 'validator',
142
+ },
143
+ ctx
144
+ );
145
+
146
+ expect(result.result).toMatchObject({
147
+ error: 'Access denied',
148
+ });
149
+ });
150
+ });
151
+
152
+ // ============================================================================
153
+ // setSessionRole Tests
154
+ // ============================================================================
155
+
156
+ describe('setSessionRole', () => {
157
+ beforeEach(() => vi.clearAllMocks());
158
+
159
+ it('should return error for missing role', async () => {
160
+ const ctx = createMockContext();
161
+
162
+ const result = await setSessionRole({}, ctx);
163
+
164
+ expect(result.result).toMatchObject({
165
+ error: 'role is required',
166
+ });
167
+ });
168
+
169
+ it('should return error for invalid role', async () => {
170
+ const ctx = createMockContext();
171
+
172
+ const result = await setSessionRole({ role: 'invalid_role' }, ctx);
173
+
174
+ expect(result.result).toMatchObject({
175
+ error: expect.stringContaining('Invalid role: invalid_role'),
176
+ });
177
+ });
178
+
179
+ it('should set local role when no active session', async () => {
180
+ const ctx = createMockContext({ sessionId: null });
181
+
182
+ const result = await setSessionRole({ role: 'validator' }, ctx);
183
+
184
+ expect(result.result).toMatchObject({
185
+ success: true,
186
+ role: 'validator',
187
+ message: expect.stringContaining('Local role set to validator'),
188
+ });
189
+ });
190
+
191
+ it('should update session role on server when session active', async () => {
192
+ mockApiClient.proxy.mockResolvedValue({
193
+ ok: true,
194
+ data: { success: true, session_id: 'session-123', role: 'deployer' },
195
+ });
196
+ const ctx = createMockContext({ sessionId: 'session-123' });
197
+
198
+ const result = await setSessionRole({ role: 'deployer' }, ctx);
199
+
200
+ expect(result.result).toMatchObject({
201
+ success: true,
202
+ session_id: 'session-123',
203
+ role: 'deployer',
204
+ });
205
+ expect(mockApiClient.proxy).toHaveBeenCalledWith(
206
+ 'set_session_role',
207
+ expect.objectContaining({
208
+ session_id: 'session-123',
209
+ role: 'deployer',
210
+ })
211
+ );
212
+ });
213
+
214
+ it('should update local state when setting role', async () => {
215
+ const updateSession = vi.fn();
216
+ const ctx = createMockContext({ sessionId: null });
217
+ ctx.updateSession = updateSession;
218
+
219
+ await setSessionRole({ role: 'reviewer' }, ctx);
220
+
221
+ expect(updateSession).toHaveBeenCalledWith({ currentRole: 'reviewer' });
222
+ });
223
+
224
+ it('should return error from API when updating session', async () => {
225
+ mockApiClient.proxy.mockResolvedValue({
226
+ ok: false,
227
+ error: 'Session not found',
228
+ });
229
+ const ctx = createMockContext({ sessionId: 'session-123' });
230
+
231
+ const result = await setSessionRole({ role: 'maintainer' }, ctx);
232
+
233
+ expect(result.result).toMatchObject({
234
+ error: 'Session not found',
235
+ });
236
+ });
237
+ });
238
+
239
+ // ============================================================================
240
+ // getAgentsByRole Tests
241
+ // ============================================================================
242
+
243
+ describe('getAgentsByRole', () => {
244
+ beforeEach(() => vi.clearAllMocks());
245
+
246
+ it('should return error for missing project_id', async () => {
247
+ const ctx = createMockContext();
248
+
249
+ const result = await getAgentsByRole({}, ctx);
250
+
251
+ expect(result.result).toMatchObject({
252
+ error: 'project_id is required',
253
+ });
254
+ });
255
+
256
+ it('should get agents by role successfully', async () => {
257
+ const mockAgentsByRole = {
258
+ developer: [
259
+ { session_id: 'session-1', agent_name: 'Edge', status: 'active' },
260
+ ],
261
+ validator: [],
262
+ deployer: [],
263
+ reviewer: [],
264
+ maintainer: [],
265
+ };
266
+ mockApiClient.proxy.mockResolvedValue({
267
+ ok: true,
268
+ data: { agents_by_role: mockAgentsByRole, total_active: 1 },
269
+ });
270
+ const ctx = createMockContext();
271
+
272
+ const result = await getAgentsByRole(
273
+ { project_id: '123e4567-e89b-12d3-a456-426614174000' },
274
+ ctx
275
+ );
276
+
277
+ expect(result.result).toMatchObject({
278
+ agents_by_role: mockAgentsByRole,
279
+ total_active: 1,
280
+ });
281
+ expect(mockApiClient.proxy).toHaveBeenCalledWith(
282
+ 'get_agents_by_role',
283
+ { project_id: '123e4567-e89b-12d3-a456-426614174000' }
284
+ );
285
+ });
286
+
287
+ it('should return error from API', async () => {
288
+ mockApiClient.proxy.mockResolvedValue({
289
+ ok: false,
290
+ error: 'Project not found',
291
+ });
292
+ const ctx = createMockContext();
293
+
294
+ const result = await getAgentsByRole(
295
+ { project_id: '123e4567-e89b-12d3-a456-426614174000' },
296
+ ctx
297
+ );
298
+
299
+ expect(result.result).toMatchObject({
300
+ error: 'Project not found',
301
+ });
302
+ });
303
+ });
@@ -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
+ };
@@ -31,7 +31,7 @@ describe('heartbeat', () => {
31
31
  session_id: 'session-123',
32
32
  });
33
33
  expect(result.result).toHaveProperty('timestamp');
34
- expect(mockApiClient.heartbeat).toHaveBeenCalledWith('session-123');
34
+ expect(mockApiClient.heartbeat).toHaveBeenCalledWith('session-123', { current_worktree_path: undefined });
35
35
  });
36
36
 
37
37
  it('should use provided session_id over current session', async () => {
@@ -48,7 +48,20 @@ describe('heartbeat', () => {
48
48
  success: true,
49
49
  session_id: 'other-session-456',
50
50
  });
51
- expect(mockApiClient.heartbeat).toHaveBeenCalledWith('other-session-456');
51
+ expect(mockApiClient.heartbeat).toHaveBeenCalledWith('other-session-456', { current_worktree_path: undefined });
52
+ });
53
+
54
+ it('should pass worktree_path to API', async () => {
55
+ const ctx = createMockContext();
56
+ mockApiClient.heartbeat.mockResolvedValue({
57
+ ok: true,
58
+ data: { timestamp: '2026-01-14T10:00:00Z' },
59
+ });
60
+ mockApiClient.syncSession.mockResolvedValue({ ok: true });
61
+
62
+ await heartbeat({ current_worktree_path: '../project-task-abc123' }, ctx);
63
+
64
+ expect(mockApiClient.heartbeat).toHaveBeenCalledWith('session-123', { current_worktree_path: '../project-task-abc123' });
52
65
  });
53
66
 
54
67
  it('should return error when no active session', async () => {
@@ -101,6 +114,10 @@ describe('getHelp', () => {
101
114
 
102
115
  it('should return help content for valid topic', async () => {
103
116
  const ctx = createMockContext();
117
+ mockApiClient.getHelpTopic.mockResolvedValue({
118
+ ok: true,
119
+ data: { slug: 'tasks', title: 'Task Workflow', content: '# Task Workflow\nTest content' },
120
+ });
104
121
 
105
122
  const result = await getHelp({ topic: 'tasks' }, ctx);
106
123
 
@@ -110,6 +127,10 @@ describe('getHelp', () => {
110
127
 
111
128
  it('should return getting_started help', async () => {
112
129
  const ctx = createMockContext();
130
+ mockApiClient.getHelpTopic.mockResolvedValue({
131
+ ok: true,
132
+ data: { slug: 'getting_started', title: 'Getting Started', content: '# Getting Started\nTest content' },
133
+ });
113
134
 
114
135
  const result = await getHelp({ topic: 'getting_started' }, ctx);
115
136
 
@@ -119,6 +140,11 @@ describe('getHelp', () => {
119
140
 
120
141
  it('should return error for unknown topic', async () => {
121
142
  const ctx = createMockContext();
143
+ mockApiClient.getHelpTopic.mockResolvedValue({ ok: true, data: null });
144
+ mockApiClient.getHelpTopics.mockResolvedValue({
145
+ ok: true,
146
+ data: [{ slug: 'tasks', title: 'Tasks' }, { slug: 'getting_started', title: 'Getting Started' }],
147
+ });
122
148
 
123
149
  const result = await getHelp({ topic: 'unknown_topic' }, ctx);
124
150
 
@@ -130,6 +156,15 @@ describe('getHelp', () => {
130
156
 
131
157
  it('should list available topics for unknown topic', async () => {
132
158
  const ctx = createMockContext();
159
+ mockApiClient.getHelpTopic.mockResolvedValue({ ok: true, data: null });
160
+ mockApiClient.getHelpTopics.mockResolvedValue({
161
+ ok: true,
162
+ data: [
163
+ { slug: 'tasks', title: 'Tasks' },
164
+ { slug: 'getting_started', title: 'Getting Started' },
165
+ { slug: 'validation', title: 'Validation' },
166
+ ],
167
+ });
133
168
 
134
169
  const result = await getHelp({ topic: 'nonexistent' }, ctx);
135
170