@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
package/src/handlers/project.ts
CHANGED
|
@@ -10,14 +10,60 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { Handler, HandlerRegistry } from './types.js';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
parseArgs,
|
|
15
|
+
uuidValidator,
|
|
16
|
+
projectStatusValidator,
|
|
17
|
+
createEnumValidator,
|
|
18
|
+
VALID_PROJECT_STATUSES,
|
|
19
|
+
} from '../validators.js';
|
|
14
20
|
import { getApiClient } from '../api-client.js';
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
const VALID_GIT_WORKFLOWS = ['none', 'trunk-based', 'github-flow', 'git-flow'] as const;
|
|
23
|
+
type GitWorkflow = typeof VALID_GIT_WORKFLOWS[number];
|
|
24
|
+
|
|
25
|
+
// Argument schemas for type-safe parsing
|
|
26
|
+
const getProjectContextSchema = {
|
|
27
|
+
project_id: { type: 'string' as const, validate: uuidValidator },
|
|
28
|
+
git_url: { type: 'string' as const },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const getGitWorkflowSchema = {
|
|
32
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
33
|
+
task_id: { type: 'string' as const, validate: uuidValidator },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const createProjectSchema = {
|
|
37
|
+
name: { type: 'string' as const, required: true as const },
|
|
38
|
+
description: { type: 'string' as const },
|
|
39
|
+
goal: { type: 'string' as const },
|
|
40
|
+
git_url: { type: 'string' as const },
|
|
41
|
+
tech_stack: { type: 'array' as const },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const updateProjectSchema = {
|
|
45
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
46
|
+
name: { type: 'string' as const },
|
|
47
|
+
description: { type: 'string' as const },
|
|
48
|
+
goal: { type: 'string' as const },
|
|
49
|
+
git_url: { type: 'string' as const },
|
|
50
|
+
tech_stack: { type: 'array' as const },
|
|
51
|
+
status: { type: 'string' as const, validate: projectStatusValidator },
|
|
52
|
+
git_workflow: { type: 'string' as const, validate: createEnumValidator(VALID_GIT_WORKFLOWS) },
|
|
53
|
+
git_main_branch: { type: 'string' as const },
|
|
54
|
+
git_develop_branch: { type: 'string' as const },
|
|
55
|
+
git_auto_branch: { type: 'boolean' as const },
|
|
56
|
+
git_auto_tag: { type: 'boolean' as const },
|
|
57
|
+
deployment_instructions: { type: 'string' as const },
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const updateProjectReadmeSchema = {
|
|
61
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
62
|
+
readme_content: { type: 'string' as const, required: true as const },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const getProjectContext: Handler = async (args, _ctx) => {
|
|
66
|
+
const { project_id, git_url } = parseArgs(args, getProjectContextSchema);
|
|
21
67
|
|
|
22
68
|
const apiClient = getApiClient();
|
|
23
69
|
|
|
@@ -26,7 +72,7 @@ export const getProjectContext: Handler = async (args, ctx) => {
|
|
|
26
72
|
const response = await apiClient.listProjects();
|
|
27
73
|
|
|
28
74
|
if (!response.ok) {
|
|
29
|
-
|
|
75
|
+
return { result: { error: response.error || 'Failed to fetch projects' }, isError: true };
|
|
30
76
|
}
|
|
31
77
|
|
|
32
78
|
return { result: { projects: response.data?.projects || [] } };
|
|
@@ -36,7 +82,7 @@ export const getProjectContext: Handler = async (args, ctx) => {
|
|
|
36
82
|
const response = await apiClient.getProject(project_id || 'by-git-url', git_url);
|
|
37
83
|
|
|
38
84
|
if (!response.ok) {
|
|
39
|
-
|
|
85
|
+
return { result: { error: response.error || 'Failed to fetch project' }, isError: true };
|
|
40
86
|
}
|
|
41
87
|
|
|
42
88
|
if (!response.data?.found) {
|
|
@@ -51,35 +97,21 @@ export const getProjectContext: Handler = async (args, ctx) => {
|
|
|
51
97
|
return { result: response.data };
|
|
52
98
|
};
|
|
53
99
|
|
|
54
|
-
export const getGitWorkflow: Handler = async (args,
|
|
55
|
-
const { project_id, task_id } = args
|
|
56
|
-
project_id: string;
|
|
57
|
-
task_id?: string;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
validateRequired(project_id, 'project_id');
|
|
61
|
-
validateUUID(project_id, 'project_id');
|
|
100
|
+
export const getGitWorkflow: Handler = async (args, _ctx) => {
|
|
101
|
+
const { project_id, task_id } = parseArgs(args, getGitWorkflowSchema);
|
|
62
102
|
|
|
63
103
|
const apiClient = getApiClient();
|
|
64
104
|
const response = await apiClient.getGitWorkflow(project_id, task_id);
|
|
65
105
|
|
|
66
106
|
if (!response.ok) {
|
|
67
|
-
|
|
107
|
+
return { result: { error: response.error || 'Failed to get git workflow' }, isError: true };
|
|
68
108
|
}
|
|
69
109
|
|
|
70
110
|
return { result: response.data };
|
|
71
111
|
};
|
|
72
112
|
|
|
73
|
-
export const createProject: Handler = async (args,
|
|
74
|
-
const { name, description, goal, git_url, tech_stack } = args
|
|
75
|
-
name: string;
|
|
76
|
-
description?: string;
|
|
77
|
-
goal?: string;
|
|
78
|
-
git_url?: string;
|
|
79
|
-
tech_stack?: string[];
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
validateRequired(name, 'name');
|
|
113
|
+
export const createProject: Handler = async (args, _ctx) => {
|
|
114
|
+
const { name, description, goal, git_url, tech_stack } = parseArgs(args, createProjectSchema);
|
|
83
115
|
|
|
84
116
|
const apiClient = getApiClient();
|
|
85
117
|
const response = await apiClient.createProject({
|
|
@@ -87,64 +119,64 @@ export const createProject: Handler = async (args, ctx) => {
|
|
|
87
119
|
description,
|
|
88
120
|
goal,
|
|
89
121
|
git_url,
|
|
90
|
-
tech_stack
|
|
122
|
+
tech_stack: tech_stack as string[] | undefined
|
|
91
123
|
});
|
|
92
124
|
|
|
93
125
|
if (!response.ok) {
|
|
94
|
-
|
|
126
|
+
return { result: { error: response.error || 'Failed to create project' }, isError: true };
|
|
95
127
|
}
|
|
96
128
|
|
|
97
129
|
return { result: response.data };
|
|
98
130
|
};
|
|
99
131
|
|
|
100
|
-
export const updateProject: Handler = async (args,
|
|
101
|
-
const {
|
|
102
|
-
project_id
|
|
103
|
-
name
|
|
104
|
-
description
|
|
105
|
-
goal
|
|
106
|
-
git_url
|
|
107
|
-
tech_stack
|
|
108
|
-
status
|
|
109
|
-
git_workflow
|
|
110
|
-
git_main_branch
|
|
111
|
-
git_develop_branch
|
|
112
|
-
git_auto_branch
|
|
113
|
-
git_auto_tag
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
deployment_instructions?: string;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
validateRequired(project_id, 'project_id');
|
|
120
|
-
validateUUID(project_id, 'project_id');
|
|
121
|
-
validateProjectStatus(updates.status);
|
|
132
|
+
export const updateProject: Handler = async (args, _ctx) => {
|
|
133
|
+
const {
|
|
134
|
+
project_id,
|
|
135
|
+
name,
|
|
136
|
+
description,
|
|
137
|
+
goal,
|
|
138
|
+
git_url,
|
|
139
|
+
tech_stack,
|
|
140
|
+
status,
|
|
141
|
+
git_workflow,
|
|
142
|
+
git_main_branch,
|
|
143
|
+
git_develop_branch,
|
|
144
|
+
git_auto_branch,
|
|
145
|
+
git_auto_tag,
|
|
146
|
+
deployment_instructions
|
|
147
|
+
} = parseArgs(args, updateProjectSchema);
|
|
122
148
|
|
|
123
149
|
const apiClient = getApiClient();
|
|
124
|
-
const response = await apiClient.updateProject(project_id,
|
|
150
|
+
const response = await apiClient.updateProject(project_id, {
|
|
151
|
+
name,
|
|
152
|
+
description,
|
|
153
|
+
goal,
|
|
154
|
+
git_url,
|
|
155
|
+
tech_stack: tech_stack as string[] | undefined,
|
|
156
|
+
status: status as typeof VALID_PROJECT_STATUSES[number] | undefined,
|
|
157
|
+
git_workflow: git_workflow as GitWorkflow | undefined,
|
|
158
|
+
git_main_branch,
|
|
159
|
+
git_develop_branch,
|
|
160
|
+
git_auto_branch,
|
|
161
|
+
git_auto_tag,
|
|
162
|
+
deployment_instructions
|
|
163
|
+
});
|
|
125
164
|
|
|
126
165
|
if (!response.ok) {
|
|
127
|
-
|
|
166
|
+
return { result: { error: response.error || 'Failed to update project' }, isError: true };
|
|
128
167
|
}
|
|
129
168
|
|
|
130
169
|
return { result: response.data };
|
|
131
170
|
};
|
|
132
171
|
|
|
133
|
-
export const updateProjectReadme: Handler = async (args,
|
|
134
|
-
const { project_id, readme_content } = args
|
|
135
|
-
project_id: string;
|
|
136
|
-
readme_content: string;
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
validateRequired(project_id, 'project_id');
|
|
140
|
-
validateUUID(project_id, 'project_id');
|
|
141
|
-
validateRequired(readme_content, 'readme_content');
|
|
172
|
+
export const updateProjectReadme: Handler = async (args, _ctx) => {
|
|
173
|
+
const { project_id, readme_content } = parseArgs(args, updateProjectReadmeSchema);
|
|
142
174
|
|
|
143
175
|
const apiClient = getApiClient();
|
|
144
176
|
const response = await apiClient.updateProjectReadme(project_id, readme_content);
|
|
145
177
|
|
|
146
178
|
if (!response.ok) {
|
|
147
|
-
|
|
179
|
+
return { result: { error: response.error || 'Failed to update README' }, isError: true };
|
|
148
180
|
}
|
|
149
181
|
|
|
150
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
|
@@ -10,14 +10,25 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { Handler, HandlerRegistry } from './types.js';
|
|
13
|
-
import {
|
|
13
|
+
import { parseArgs, uuidValidator } from '../validators.js';
|
|
14
14
|
import { getApiClient } from '../api-client.js';
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// Argument schemas for type-safe parsing
|
|
17
|
+
const getPendingRequestsSchema = {
|
|
18
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
19
|
+
};
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
const acknowledgeRequestSchema = {
|
|
22
|
+
request_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const answerQuestionSchema = {
|
|
26
|
+
request_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
27
|
+
answer: { type: 'string' as const, required: true as const },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const getPendingRequests: Handler = async (args, ctx) => {
|
|
31
|
+
const { project_id } = parseArgs(args, getPendingRequestsSchema);
|
|
21
32
|
|
|
22
33
|
const { session } = ctx;
|
|
23
34
|
const apiClient = getApiClient();
|
|
@@ -25,7 +36,7 @@ export const getPendingRequests: Handler = async (args, ctx) => {
|
|
|
25
36
|
const response = await apiClient.getPendingRequests(project_id, session.currentSessionId || undefined);
|
|
26
37
|
|
|
27
38
|
if (!response.ok) {
|
|
28
|
-
|
|
39
|
+
return { result: { error: response.error || 'Failed to get pending requests' }, isError: true };
|
|
29
40
|
}
|
|
30
41
|
|
|
31
42
|
return {
|
|
@@ -37,10 +48,7 @@ export const getPendingRequests: Handler = async (args, ctx) => {
|
|
|
37
48
|
};
|
|
38
49
|
|
|
39
50
|
export const acknowledgeRequest: Handler = async (args, ctx) => {
|
|
40
|
-
const { request_id } = args
|
|
41
|
-
|
|
42
|
-
validateRequired(request_id, 'request_id');
|
|
43
|
-
validateUUID(request_id, 'request_id');
|
|
51
|
+
const { request_id } = parseArgs(args, acknowledgeRequestSchema);
|
|
44
52
|
|
|
45
53
|
const { session } = ctx;
|
|
46
54
|
const apiClient = getApiClient();
|
|
@@ -48,7 +56,7 @@ export const acknowledgeRequest: Handler = async (args, ctx) => {
|
|
|
48
56
|
const response = await apiClient.acknowledgeRequest(request_id, session.currentSessionId || undefined);
|
|
49
57
|
|
|
50
58
|
if (!response.ok) {
|
|
51
|
-
|
|
59
|
+
return { result: { error: response.error || 'Failed to acknowledge request' }, isError: true };
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
return {
|
|
@@ -59,11 +67,7 @@ export const acknowledgeRequest: Handler = async (args, ctx) => {
|
|
|
59
67
|
};
|
|
60
68
|
|
|
61
69
|
export const answerQuestion: Handler = async (args, ctx) => {
|
|
62
|
-
const { request_id, answer } = args
|
|
63
|
-
|
|
64
|
-
validateRequired(request_id, 'request_id');
|
|
65
|
-
validateRequired(answer, 'answer');
|
|
66
|
-
validateUUID(request_id, 'request_id');
|
|
70
|
+
const { request_id, answer } = parseArgs(args, answerQuestionSchema);
|
|
67
71
|
|
|
68
72
|
const { session } = ctx;
|
|
69
73
|
const apiClient = getApiClient();
|
|
@@ -71,7 +75,7 @@ export const answerQuestion: Handler = async (args, ctx) => {
|
|
|
71
75
|
const response = await apiClient.answerQuestion(request_id, answer, session.currentSessionId || undefined);
|
|
72
76
|
|
|
73
77
|
if (!response.ok) {
|
|
74
|
-
|
|
78
|
+
return { result: { error: response.error || 'Failed to answer question' }, isError: true };
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
return {
|
|
@@ -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
|
+
});
|