@vibescope/mcp-server 0.2.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 (65) hide show
  1. package/dist/api-client.d.ts +64 -1
  2. package/dist/api-client.js +34 -3
  3. package/dist/handlers/bodies-of-work.js +82 -49
  4. package/dist/handlers/cost.js +62 -54
  5. package/dist/handlers/decisions.js +29 -16
  6. package/dist/handlers/deployment.js +112 -106
  7. package/dist/handlers/discovery.js +35 -5
  8. package/dist/handlers/fallback.js +24 -19
  9. package/dist/handlers/file-checkouts.d.ts +18 -0
  10. package/dist/handlers/file-checkouts.js +101 -0
  11. package/dist/handlers/findings.d.ts +6 -0
  12. package/dist/handlers/findings.js +85 -30
  13. package/dist/handlers/git-issues.js +36 -32
  14. package/dist/handlers/ideas.js +44 -26
  15. package/dist/handlers/index.d.ts +2 -0
  16. package/dist/handlers/index.js +6 -0
  17. package/dist/handlers/milestones.js +34 -27
  18. package/dist/handlers/organizations.js +86 -78
  19. package/dist/handlers/progress.js +22 -11
  20. package/dist/handlers/project.js +62 -22
  21. package/dist/handlers/requests.js +15 -11
  22. package/dist/handlers/roles.d.ts +18 -0
  23. package/dist/handlers/roles.js +130 -0
  24. package/dist/handlers/session.js +30 -8
  25. package/dist/handlers/sprints.js +76 -64
  26. package/dist/handlers/tasks.js +113 -73
  27. package/dist/handlers/validation.js +18 -14
  28. package/dist/tools.js +387 -0
  29. package/package.json +1 -1
  30. package/src/api-client.ts +89 -6
  31. package/src/handlers/__test-setup__.ts +7 -0
  32. package/src/handlers/bodies-of-work.ts +101 -101
  33. package/src/handlers/cost.test.ts +34 -44
  34. package/src/handlers/cost.ts +77 -92
  35. package/src/handlers/decisions.test.ts +3 -2
  36. package/src/handlers/decisions.ts +32 -27
  37. package/src/handlers/deployment.ts +142 -190
  38. package/src/handlers/discovery.test.ts +4 -5
  39. package/src/handlers/discovery.ts +37 -6
  40. package/src/handlers/fallback.ts +31 -29
  41. package/src/handlers/file-checkouts.test.ts +477 -0
  42. package/src/handlers/file-checkouts.ts +127 -0
  43. package/src/handlers/findings.test.ts +145 -0
  44. package/src/handlers/findings.ts +101 -64
  45. package/src/handlers/git-issues.ts +40 -80
  46. package/src/handlers/ideas.ts +56 -54
  47. package/src/handlers/index.ts +6 -0
  48. package/src/handlers/milestones.test.ts +1 -1
  49. package/src/handlers/milestones.ts +47 -45
  50. package/src/handlers/organizations.ts +104 -129
  51. package/src/handlers/progress.ts +24 -22
  52. package/src/handlers/project.ts +89 -57
  53. package/src/handlers/requests.ts +18 -14
  54. package/src/handlers/roles.test.ts +303 -0
  55. package/src/handlers/roles.ts +208 -0
  56. package/src/handlers/session.ts +39 -17
  57. package/src/handlers/sprints.ts +96 -129
  58. package/src/handlers/tasks.ts +144 -138
  59. package/src/handlers/validation.test.ts +1 -1
  60. package/src/handlers/validation.ts +20 -22
  61. package/src/tools.ts +387 -0
  62. package/dist/config/tool-categories.d.ts +0 -31
  63. package/dist/config/tool-categories.js +0 -253
  64. package/dist/knowledge.d.ts +0 -6
  65. package/dist/knowledge.js +0 -218
@@ -198,7 +198,7 @@ describe('getDecisions', () => {
198
198
  });
199
199
  });
200
200
 
201
- it('should call API client getDecisions', async () => {
201
+ it('should call API client getDecisions with options', async () => {
202
202
  mockApiClient.getDecisions.mockResolvedValue({
203
203
  ok: true,
204
204
  data: { decisions: [] },
@@ -211,7 +211,8 @@ describe('getDecisions', () => {
211
211
  );
212
212
 
213
213
  expect(mockApiClient.getDecisions).toHaveBeenCalledWith(
214
- '123e4567-e89b-12d3-a456-426614174000'
214
+ '123e4567-e89b-12d3-a456-426614174000',
215
+ { limit: 50, offset: 0, search_query: undefined }
215
216
  );
216
217
  });
217
218
 
@@ -10,22 +10,31 @@
10
10
  */
11
11
 
12
12
  import type { Handler, HandlerRegistry } from './types.js';
13
- import { validateRequired, validateUUID } from '../validators.js';
13
+ import { parseArgs, uuidValidator } from '../validators.js';
14
14
  import { getApiClient } from '../api-client.js';
15
15
 
16
- export const logDecision: Handler = async (args, ctx) => {
17
- const { project_id, title, description, rationale, alternatives_considered } = args as {
18
- project_id: string;
19
- title: string;
20
- description: string;
21
- rationale?: string;
22
- alternatives_considered?: string[];
23
- };
16
+ // Argument schemas for type-safe parsing
17
+ const logDecisionSchema = {
18
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
19
+ title: { type: 'string' as const, required: true as const },
20
+ description: { type: 'string' as const, required: true as const },
21
+ rationale: { type: 'string' as const },
22
+ alternatives_considered: { type: 'array' as const },
23
+ };
24
24
 
25
- validateRequired(project_id, 'project_id');
26
- validateUUID(project_id, 'project_id');
27
- validateRequired(title, 'title');
28
- validateRequired(description, 'description');
25
+ const getDecisionsSchema = {
26
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
27
+ limit: { type: 'number' as const, default: 50 },
28
+ offset: { type: 'number' as const, default: 0 },
29
+ search_query: { type: 'string' as const },
30
+ };
31
+
32
+ const deleteDecisionSchema = {
33
+ decision_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
34
+ };
35
+
36
+ export const logDecision: Handler = async (args, ctx) => {
37
+ const { project_id, title, description, rationale, alternatives_considered } = parseArgs(args, logDecisionSchema);
29
38
 
30
39
  const { session } = ctx;
31
40
  const apiClient = getApiClient();
@@ -34,7 +43,7 @@ export const logDecision: Handler = async (args, ctx) => {
34
43
  title,
35
44
  description,
36
45
  rationale,
37
- alternatives_considered
46
+ alternatives_considered: alternatives_considered as string[] | undefined
38
47
  }, session.currentSessionId || undefined);
39
48
 
40
49
  if (!response.ok) {
@@ -44,17 +53,16 @@ export const logDecision: Handler = async (args, ctx) => {
44
53
  return { result: { success: true, title, decision_id: response.data?.decision_id } };
45
54
  };
46
55
 
47
- export const getDecisions: Handler = async (args, ctx) => {
48
- const { project_id } = args as {
49
- project_id: string;
50
- };
51
-
52
- validateRequired(project_id, 'project_id');
53
- validateUUID(project_id, 'project_id');
56
+ export const getDecisions: Handler = async (args, _ctx) => {
57
+ const { project_id, limit, offset, search_query } = parseArgs(args, getDecisionsSchema);
54
58
 
55
59
  const apiClient = getApiClient();
56
60
 
57
- const response = await apiClient.getDecisions(project_id);
61
+ const response = await apiClient.getDecisions(project_id, {
62
+ limit,
63
+ offset,
64
+ search_query
65
+ });
58
66
 
59
67
  if (!response.ok) {
60
68
  throw new Error(`Failed to fetch decisions: ${response.error}`);
@@ -67,11 +75,8 @@ export const getDecisions: Handler = async (args, ctx) => {
67
75
  };
68
76
  };
69
77
 
70
- export const deleteDecision: Handler = async (args, ctx) => {
71
- const { decision_id } = args as { decision_id: string };
72
-
73
- validateRequired(decision_id, 'decision_id');
74
- validateUUID(decision_id, 'decision_id');
78
+ export const deleteDecision: Handler = async (args, _ctx) => {
79
+ const { decision_id } = parseArgs(args, deleteDecisionSchema);
75
80
 
76
81
  const apiClient = getApiClient();
77
82
 
@@ -17,34 +17,132 @@
17
17
  import type { Handler, HandlerRegistry } from './types.js';
18
18
  import {
19
19
  ValidationError,
20
- validateRequired,
21
- validateUUID,
22
- validateEnvironment,
20
+ parseArgs,
21
+ uuidValidator,
22
+ createEnumValidator,
23
23
  } from '../validators.js';
24
24
  import { getApiClient } from '../api-client.js';
25
25
 
26
- export const requestDeployment: Handler = async (args, ctx) => {
27
- const { project_id, environment = 'production', version_bump = 'patch', notes, git_ref } = args as {
28
- project_id: string;
29
- environment?: string;
30
- version_bump?: 'patch' | 'minor' | 'major';
31
- notes?: string;
32
- git_ref?: string;
33
- };
26
+ const ENVIRONMENTS = ['development', 'staging', 'production'] as const;
27
+ const VERSION_BUMPS = ['patch', 'minor', 'major'] as const;
28
+ const REQUIREMENT_TYPES = ['migration', 'env_var', 'config', 'manual', 'breaking_change', 'agent_task'] as const;
29
+ const REQUIREMENT_STAGES = ['preparation', 'deployment', 'verification'] as const;
30
+ const REQUIREMENT_STATUSES = ['pending', 'completed', 'converted_to_task', 'all'] as const;
31
+ const SCHEDULE_TYPES = ['once', 'daily', 'weekly', 'monthly'] as const;
34
32
 
35
- const { session } = ctx;
33
+ type Environment = typeof ENVIRONMENTS[number];
34
+ type VersionBump = typeof VERSION_BUMPS[number];
35
+ type RequirementType = typeof REQUIREMENT_TYPES[number];
36
+ type RequirementStage = typeof REQUIREMENT_STAGES[number];
37
+ type RequirementStatus = typeof REQUIREMENT_STATUSES[number];
38
+ type ScheduleType = typeof SCHEDULE_TYPES[number];
39
+
40
+ // ============================================================================
41
+ // Argument Schemas
42
+ // ============================================================================
36
43
 
37
- validateRequired(project_id, 'project_id');
38
- validateUUID(project_id, 'project_id');
39
- validateEnvironment(environment);
44
+ const requestDeploymentSchema = {
45
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
46
+ environment: { type: 'string' as const, default: 'production', validate: createEnumValidator(ENVIRONMENTS) },
47
+ version_bump: { type: 'string' as const, default: 'patch', validate: createEnumValidator(VERSION_BUMPS) },
48
+ notes: { type: 'string' as const },
49
+ git_ref: { type: 'string' as const },
50
+ };
40
51
 
41
- if (version_bump && !['patch', 'minor', 'major'].includes(version_bump)) {
42
- throw new ValidationError('Invalid version_bump value', {
43
- field: 'version_bump',
44
- validValues: ['patch', 'minor', 'major'],
45
- hint: 'Must be one of: patch, minor, major',
46
- });
47
- }
52
+ const claimDeploymentValidationSchema = {
53
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
54
+ };
55
+
56
+ const reportValidationSchema = {
57
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
58
+ build_passed: { type: 'boolean' as const, required: true as const },
59
+ tests_passed: { type: 'boolean' as const },
60
+ error_message: { type: 'string' as const },
61
+ };
62
+
63
+ const checkDeploymentStatusSchema = {
64
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
65
+ };
66
+
67
+ const startDeploymentSchema = {
68
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
69
+ };
70
+
71
+ const completeDeploymentSchema = {
72
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
73
+ success: { type: 'boolean' as const, required: true as const },
74
+ summary: { type: 'string' as const },
75
+ };
76
+
77
+ const cancelDeploymentSchema = {
78
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
79
+ reason: { type: 'string' as const },
80
+ };
81
+
82
+ const addDeploymentRequirementSchema = {
83
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
84
+ type: { type: 'string' as const, required: true as const, validate: createEnumValidator(REQUIREMENT_TYPES) },
85
+ title: { type: 'string' as const, required: true as const },
86
+ description: { type: 'string' as const },
87
+ file_path: { type: 'string' as const },
88
+ stage: { type: 'string' as const, default: 'preparation', validate: createEnumValidator(REQUIREMENT_STAGES) },
89
+ blocking: { type: 'boolean' as const, default: false },
90
+ recurring: { type: 'boolean' as const, default: false },
91
+ };
92
+
93
+ const completeDeploymentRequirementSchema = {
94
+ requirement_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
95
+ };
96
+
97
+ const getDeploymentRequirementsSchema = {
98
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
99
+ status: { type: 'string' as const, default: 'pending', validate: createEnumValidator(REQUIREMENT_STATUSES) },
100
+ stage: { type: 'string' as const, validate: createEnumValidator([...REQUIREMENT_STAGES, 'all'] as const) },
101
+ };
102
+
103
+ const scheduleDeploymentSchema = {
104
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
105
+ environment: { type: 'string' as const, default: 'production', validate: createEnumValidator(ENVIRONMENTS) },
106
+ version_bump: { type: 'string' as const, default: 'patch', validate: createEnumValidator(VERSION_BUMPS) },
107
+ schedule_type: { type: 'string' as const, default: 'once', validate: createEnumValidator(SCHEDULE_TYPES) },
108
+ scheduled_at: { type: 'string' as const, required: true as const },
109
+ auto_trigger: { type: 'boolean' as const, default: true },
110
+ notes: { type: 'string' as const },
111
+ git_ref: { type: 'string' as const },
112
+ };
113
+
114
+ const getScheduledDeploymentsSchema = {
115
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
116
+ include_disabled: { type: 'boolean' as const, default: false },
117
+ };
118
+
119
+ const updateScheduledDeploymentSchema = {
120
+ schedule_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
121
+ environment: { type: 'string' as const, validate: createEnumValidator(ENVIRONMENTS) },
122
+ version_bump: { type: 'string' as const, validate: createEnumValidator(VERSION_BUMPS) },
123
+ schedule_type: { type: 'string' as const, validate: createEnumValidator(SCHEDULE_TYPES) },
124
+ scheduled_at: { type: 'string' as const },
125
+ auto_trigger: { type: 'boolean' as const },
126
+ enabled: { type: 'boolean' as const },
127
+ notes: { type: 'string' as const },
128
+ git_ref: { type: 'string' as const },
129
+ };
130
+
131
+ const deleteScheduledDeploymentSchema = {
132
+ schedule_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
133
+ };
134
+
135
+ const triggerScheduledDeploymentSchema = {
136
+ schedule_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
137
+ };
138
+
139
+ const checkDueDeploymentsSchema = {
140
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
141
+ };
142
+
143
+ export const requestDeployment: Handler = async (args, ctx) => {
144
+ const { project_id, environment, version_bump, notes, git_ref } = parseArgs(args, requestDeploymentSchema);
145
+ const { session } = ctx;
48
146
 
49
147
  const apiClient = getApiClient();
50
148
  const response = await apiClient.requestDeployment(project_id, {
@@ -62,12 +160,9 @@ export const requestDeployment: Handler = async (args, ctx) => {
62
160
  };
63
161
 
64
162
  export const claimDeploymentValidation: Handler = async (args, ctx) => {
65
- const { project_id } = args as { project_id: string };
163
+ const { project_id } = parseArgs(args, claimDeploymentValidationSchema);
66
164
  const { session } = ctx;
67
165
 
68
- validateRequired(project_id, 'project_id');
69
- validateUUID(project_id, 'project_id');
70
-
71
166
  const apiClient = getApiClient();
72
167
  const response = await apiClient.claimDeploymentValidation(
73
168
  project_id,
@@ -82,24 +177,9 @@ export const claimDeploymentValidation: Handler = async (args, ctx) => {
82
177
  };
83
178
 
84
179
  export const reportValidation: Handler = async (args, ctx) => {
85
- const { project_id, build_passed, tests_passed, error_message } = args as {
86
- project_id: string;
87
- build_passed: boolean;
88
- tests_passed?: boolean;
89
- error_message?: string;
90
- };
91
-
180
+ const { project_id, build_passed, tests_passed, error_message } = parseArgs(args, reportValidationSchema);
92
181
  const { session } = ctx;
93
182
 
94
- validateRequired(project_id, 'project_id');
95
- validateUUID(project_id, 'project_id');
96
- if (build_passed === undefined) {
97
- throw new ValidationError('build_passed is required', {
98
- field: 'build_passed',
99
- hint: 'Set to true if the build succeeded, false otherwise',
100
- });
101
- }
102
-
103
183
  const apiClient = getApiClient();
104
184
  const response = await apiClient.reportValidation(project_id, {
105
185
  build_passed,
@@ -115,10 +195,7 @@ export const reportValidation: Handler = async (args, ctx) => {
115
195
  };
116
196
 
117
197
  export const checkDeploymentStatus: Handler = async (args, ctx) => {
118
- const { project_id } = args as { project_id: string };
119
-
120
- validateRequired(project_id, 'project_id');
121
- validateUUID(project_id, 'project_id');
198
+ const { project_id } = parseArgs(args, checkDeploymentStatusSchema);
122
199
 
123
200
  const apiClient = getApiClient();
124
201
  const response = await apiClient.checkDeploymentStatus(project_id);
@@ -131,12 +208,9 @@ export const checkDeploymentStatus: Handler = async (args, ctx) => {
131
208
  };
132
209
 
133
210
  export const startDeployment: Handler = async (args, ctx) => {
134
- const { project_id } = args as { project_id: string };
211
+ const { project_id } = parseArgs(args, startDeploymentSchema);
135
212
  const { session } = ctx;
136
213
 
137
- validateRequired(project_id, 'project_id');
138
- validateUUID(project_id, 'project_id');
139
-
140
214
  const apiClient = getApiClient();
141
215
  const response = await apiClient.startDeployment(
142
216
  project_id,
@@ -151,23 +225,9 @@ export const startDeployment: Handler = async (args, ctx) => {
151
225
  };
152
226
 
153
227
  export const completeDeployment: Handler = async (args, ctx) => {
154
- const { project_id, success, summary } = args as {
155
- project_id: string;
156
- success: boolean;
157
- summary?: string;
158
- };
159
-
228
+ const { project_id, success, summary } = parseArgs(args, completeDeploymentSchema);
160
229
  const { session } = ctx;
161
230
 
162
- validateRequired(project_id, 'project_id');
163
- validateUUID(project_id, 'project_id');
164
- if (success === undefined) {
165
- throw new ValidationError('success is required', {
166
- field: 'success',
167
- hint: 'Set to true if deployment succeeded, false otherwise',
168
- });
169
- }
170
-
171
231
  const apiClient = getApiClient();
172
232
  const response = await apiClient.completeDeployment(project_id, {
173
233
  success,
@@ -182,10 +242,7 @@ export const completeDeployment: Handler = async (args, ctx) => {
182
242
  };
183
243
 
184
244
  export const cancelDeployment: Handler = async (args, ctx) => {
185
- const { project_id, reason } = args as { project_id: string; reason?: string };
186
-
187
- validateRequired(project_id, 'project_id');
188
- validateUUID(project_id, 'project_id');
245
+ const { project_id, reason } = parseArgs(args, cancelDeploymentSchema);
189
246
 
190
247
  const apiClient = getApiClient();
191
248
  const response = await apiClient.cancelDeployment(project_id, reason);
@@ -198,31 +255,7 @@ export const cancelDeployment: Handler = async (args, ctx) => {
198
255
  };
199
256
 
200
257
  export const addDeploymentRequirement: Handler = async (args, ctx) => {
201
- const { project_id, type, title, description, file_path, stage = 'preparation', blocking = false, recurring = false } = args as {
202
- project_id: string;
203
- type: string;
204
- title: string;
205
- description?: string;
206
- file_path?: string;
207
- stage?: string;
208
- blocking?: boolean;
209
- recurring?: boolean;
210
- };
211
-
212
- validateRequired(project_id, 'project_id');
213
- validateUUID(project_id, 'project_id');
214
- validateRequired(type, 'type');
215
- validateRequired(title, 'title');
216
-
217
- const validTypes = ['migration', 'env_var', 'config', 'manual', 'breaking_change', 'agent_task'];
218
- if (!validTypes.includes(type)) {
219
- throw new ValidationError(`type must be one of: ${validTypes.join(', ')}`);
220
- }
221
-
222
- const validStages = ['preparation', 'deployment', 'verification'];
223
- if (!validStages.includes(stage)) {
224
- throw new ValidationError(`stage must be one of: ${validStages.join(', ')}`);
225
- }
258
+ const { project_id, type, title, description, file_path, stage, blocking, recurring } = parseArgs(args, addDeploymentRequirementSchema);
226
259
 
227
260
  const apiClient = getApiClient();
228
261
  const response = await apiClient.addDeploymentRequirement(project_id, {
@@ -243,10 +276,7 @@ export const addDeploymentRequirement: Handler = async (args, ctx) => {
243
276
  };
244
277
 
245
278
  export const completeDeploymentRequirement: Handler = async (args, ctx) => {
246
- const { requirement_id } = args as { requirement_id: string };
247
-
248
- validateRequired(requirement_id, 'requirement_id');
249
- validateUUID(requirement_id, 'requirement_id');
279
+ const { requirement_id } = parseArgs(args, completeDeploymentRequirementSchema);
250
280
 
251
281
  const apiClient = getApiClient();
252
282
  const response = await apiClient.completeDeploymentRequirement(requirement_id);
@@ -259,14 +289,7 @@ export const completeDeploymentRequirement: Handler = async (args, ctx) => {
259
289
  };
260
290
 
261
291
  export const getDeploymentRequirements: Handler = async (args, ctx) => {
262
- const { project_id, status = 'pending', stage } = args as {
263
- project_id: string;
264
- status?: string;
265
- stage?: string;
266
- };
267
-
268
- validateRequired(project_id, 'project_id');
269
- validateUUID(project_id, 'project_id');
292
+ const { project_id, status, stage } = parseArgs(args, getDeploymentRequirementsSchema);
270
293
 
271
294
  const apiClient = getApiClient();
272
295
  const response = await apiClient.getDeploymentRequirements(project_id, {
@@ -288,42 +311,14 @@ export const getDeploymentRequirements: Handler = async (args, ctx) => {
288
311
  export const scheduleDeployment: Handler = async (args, ctx) => {
289
312
  const {
290
313
  project_id,
291
- environment = 'production',
292
- version_bump = 'patch',
293
- schedule_type = 'once',
314
+ environment,
315
+ version_bump,
316
+ schedule_type,
294
317
  scheduled_at,
295
- auto_trigger = true,
318
+ auto_trigger,
296
319
  notes,
297
320
  git_ref,
298
- } = args as {
299
- project_id: string;
300
- environment?: string;
301
- version_bump?: string;
302
- schedule_type?: string;
303
- scheduled_at: string;
304
- auto_trigger?: boolean;
305
- notes?: string;
306
- git_ref?: string;
307
- };
308
-
309
- validateRequired(project_id, 'project_id');
310
- validateUUID(project_id, 'project_id');
311
- validateRequired(scheduled_at, 'scheduled_at');
312
- validateEnvironment(environment);
313
-
314
- if (!['patch', 'minor', 'major'].includes(version_bump)) {
315
- throw new ValidationError('Invalid version_bump value', {
316
- field: 'version_bump',
317
- validValues: ['patch', 'minor', 'major'],
318
- });
319
- }
320
-
321
- if (!['once', 'daily', 'weekly', 'monthly'].includes(schedule_type)) {
322
- throw new ValidationError('Invalid schedule_type value', {
323
- field: 'schedule_type',
324
- validValues: ['once', 'daily', 'weekly', 'monthly'],
325
- });
326
- }
321
+ } = parseArgs(args, scheduleDeploymentSchema);
327
322
 
328
323
  // Parse and validate scheduled_at
329
324
  const scheduledDate = new Date(scheduled_at);
@@ -359,13 +354,7 @@ export const scheduleDeployment: Handler = async (args, ctx) => {
359
354
  };
360
355
 
361
356
  export const getScheduledDeployments: Handler = async (args, ctx) => {
362
- const { project_id, include_disabled = false } = args as {
363
- project_id: string;
364
- include_disabled?: boolean;
365
- };
366
-
367
- validateRequired(project_id, 'project_id');
368
- validateUUID(project_id, 'project_id');
357
+ const { project_id, include_disabled } = parseArgs(args, getScheduledDeploymentsSchema);
369
358
 
370
359
  const apiClient = getApiClient();
371
360
  const response = await apiClient.getScheduledDeployments(project_id, include_disabled);
@@ -388,41 +377,13 @@ export const updateScheduledDeployment: Handler = async (args, ctx) => {
388
377
  enabled,
389
378
  notes,
390
379
  git_ref,
391
- } = args as {
392
- schedule_id: string;
393
- environment?: string;
394
- version_bump?: string;
395
- schedule_type?: string;
396
- scheduled_at?: string;
397
- auto_trigger?: boolean;
398
- enabled?: boolean;
399
- notes?: string;
400
- git_ref?: string;
401
- };
402
-
403
- validateRequired(schedule_id, 'schedule_id');
404
- validateUUID(schedule_id, 'schedule_id');
380
+ } = parseArgs(args, updateScheduledDeploymentSchema);
405
381
 
406
382
  const updates: Record<string, unknown> = {};
407
383
 
408
- if (environment !== undefined) {
409
- validateEnvironment(environment);
410
- updates.environment = environment;
411
- }
412
-
413
- if (version_bump !== undefined) {
414
- if (!['patch', 'minor', 'major'].includes(version_bump)) {
415
- throw new ValidationError('Invalid version_bump value');
416
- }
417
- updates.version_bump = version_bump;
418
- }
419
-
420
- if (schedule_type !== undefined) {
421
- if (!['once', 'daily', 'weekly', 'monthly'].includes(schedule_type)) {
422
- throw new ValidationError('Invalid schedule_type value');
423
- }
424
- updates.schedule_type = schedule_type;
425
- }
384
+ if (environment !== undefined) updates.environment = environment;
385
+ if (version_bump !== undefined) updates.version_bump = version_bump;
386
+ if (schedule_type !== undefined) updates.schedule_type = schedule_type;
426
387
 
427
388
  if (scheduled_at !== undefined) {
428
389
  const scheduledDate = new Date(scheduled_at);
@@ -461,10 +422,7 @@ export const updateScheduledDeployment: Handler = async (args, ctx) => {
461
422
  };
462
423
 
463
424
  export const deleteScheduledDeployment: Handler = async (args, ctx) => {
464
- const { schedule_id } = args as { schedule_id: string };
465
-
466
- validateRequired(schedule_id, 'schedule_id');
467
- validateUUID(schedule_id, 'schedule_id');
425
+ const { schedule_id } = parseArgs(args, deleteScheduledDeploymentSchema);
468
426
 
469
427
  const apiClient = getApiClient();
470
428
  const response = await apiClient.deleteScheduledDeployment(schedule_id);
@@ -477,12 +435,9 @@ export const deleteScheduledDeployment: Handler = async (args, ctx) => {
477
435
  };
478
436
 
479
437
  export const triggerScheduledDeployment: Handler = async (args, ctx) => {
480
- const { schedule_id } = args as { schedule_id: string };
438
+ const { schedule_id } = parseArgs(args, triggerScheduledDeploymentSchema);
481
439
  const { session } = ctx;
482
440
 
483
- validateRequired(schedule_id, 'schedule_id');
484
- validateUUID(schedule_id, 'schedule_id');
485
-
486
441
  const apiClient = getApiClient();
487
442
  const response = await apiClient.triggerScheduledDeployment(
488
443
  schedule_id,
@@ -497,10 +452,7 @@ export const triggerScheduledDeployment: Handler = async (args, ctx) => {
497
452
  };
498
453
 
499
454
  export const checkDueDeployments: Handler = async (args, ctx) => {
500
- const { project_id } = args as { project_id: string };
501
-
502
- validateRequired(project_id, 'project_id');
503
- validateUUID(project_id, 'project_id');
455
+ const { project_id } = parseArgs(args, checkDueDeploymentsSchema);
504
456
 
505
457
  const apiClient = getApiClient();
506
458
  const response = await apiClient.checkDueDeployments(project_id);
@@ -1,6 +1,7 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { discoverTools, getToolInfo } from './discovery.js';
3
3
  import { createMockContext } from './__test-utils__.js';
4
+ import { ValidationError } from '../validators.js';
4
5
 
5
6
  // ============================================================================
6
7
  // discoverTools Tests
@@ -113,13 +114,11 @@ describe('discoverTools', () => {
113
114
  // ============================================================================
114
115
 
115
116
  describe('getToolInfo', () => {
116
- it('should return error for missing tool_name', async () => {
117
+ it('should throw ValidationError for missing tool_name', async () => {
117
118
  const ctx = createMockContext();
118
119
 
119
- const result = await getToolInfo({}, ctx);
120
- const res = result.result as { error: string };
121
-
122
- expect(res.error).toBe('tool_name is required');
120
+ await expect(getToolInfo({}, ctx)).rejects.toThrow(ValidationError);
121
+ await expect(getToolInfo({}, ctx)).rejects.toThrow('Missing required field: tool_name');
123
122
  });
124
123
 
125
124
  it('should return error for unknown tool', async () => {
@@ -10,6 +10,16 @@
10
10
  */
11
11
 
12
12
  import type { Handler, HandlerRegistry } from './types.js';
13
+ import { parseArgs } from '../validators.js';
14
+
15
+ // Argument schemas for type-safe parsing
16
+ const discoverToolsSchema = {
17
+ category: { type: 'string' as const },
18
+ };
19
+
20
+ const getToolInfoSchema = {
21
+ tool_name: { type: 'string' as const, required: true as const },
22
+ };
13
23
 
14
24
  // Lazy-loaded tool documentation cache
15
25
  let toolInfoCache: Record<string, string> | null = null;
@@ -169,6 +179,22 @@ const TOOL_CATEGORIES: Record<string, { description: string; tools: Array<{ name
169
179
  { name: 'activate_body_of_work', brief: 'Activate for work' },
170
180
  ],
171
181
  },
182
+ sprints: {
183
+ description: 'Time-bounded sprints with velocity tracking',
184
+ tools: [
185
+ { name: 'create_sprint', brief: 'Create new sprint' },
186
+ { name: 'update_sprint', brief: 'Update sprint details' },
187
+ { name: 'get_sprint', brief: 'Get sprint with tasks' },
188
+ { name: 'get_sprints', brief: 'List project sprints' },
189
+ { name: 'delete_sprint', brief: 'Delete sprint' },
190
+ { name: 'start_sprint', brief: 'Start sprint' },
191
+ { name: 'complete_sprint', brief: 'Complete sprint' },
192
+ { name: 'add_task_to_sprint', brief: 'Add task to sprint' },
193
+ { name: 'remove_task_from_sprint', brief: 'Remove from sprint' },
194
+ { name: 'get_sprint_backlog', brief: 'Get available tasks' },
195
+ { name: 'get_sprint_velocity', brief: 'Velocity metrics' },
196
+ ],
197
+ },
172
198
  requests: {
173
199
  description: 'User request handling',
174
200
  tools: [
@@ -206,6 +232,15 @@ const TOOL_CATEGORIES: Record<string, { description: string; tools: Array<{ name
206
232
  { name: 'get_task_costs', brief: 'Cost per task' },
207
233
  ],
208
234
  },
235
+ git_issues: {
236
+ description: 'Git conflict and issue tracking',
237
+ tools: [
238
+ { name: 'add_git_issue', brief: 'Record git issue' },
239
+ { name: 'resolve_git_issue', brief: 'Mark resolved' },
240
+ { name: 'get_git_issues', brief: 'List git issues' },
241
+ { name: 'delete_git_issue', brief: 'Delete git issue' },
242
+ ],
243
+ },
209
244
  knowledge: {
210
245
  description: 'Queryable knowledge base from project data',
211
246
  tools: [
@@ -215,7 +250,7 @@ const TOOL_CATEGORIES: Record<string, { description: string; tools: Array<{ name
215
250
  };
216
251
 
217
252
  export const discoverTools: Handler = async (args) => {
218
- const { category } = args as { category?: string };
253
+ const { category } = parseArgs(args, discoverToolsSchema);
219
254
 
220
255
  if (category) {
221
256
  // Return tools in specific category
@@ -254,11 +289,7 @@ export const discoverTools: Handler = async (args) => {
254
289
  };
255
290
 
256
291
  export const getToolInfo: Handler = async (args) => {
257
- const { tool_name } = args as { tool_name: string };
258
-
259
- if (!tool_name) {
260
- return { result: { error: 'tool_name is required' } };
261
- }
292
+ const { tool_name } = parseArgs(args, getToolInfoSchema);
262
293
 
263
294
  // Lazy-load tool documentation
264
295
  const toolDocs = await getToolDocs();