@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/ideas.ts
CHANGED
|
@@ -12,22 +12,55 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { Handler, HandlerRegistry } from './types.js';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
parseArgs,
|
|
17
|
+
uuidValidator,
|
|
18
|
+
priorityValidator,
|
|
19
|
+
minutesValidator,
|
|
20
|
+
createEnumValidator,
|
|
21
|
+
} from '../validators.js';
|
|
16
22
|
import { getApiClient } from '../api-client.js';
|
|
17
23
|
|
|
18
|
-
|
|
24
|
+
const VALID_IDEA_STATUSES = ['raw', 'exploring', 'planned', 'in_development', 'shipped'] as const;
|
|
25
|
+
type IdeaStatus = typeof VALID_IDEA_STATUSES[number];
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
// Argument schemas for type-safe parsing
|
|
28
|
+
const addIdeaSchema = {
|
|
29
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
30
|
+
title: { type: 'string' as const, required: true as const },
|
|
31
|
+
description: { type: 'string' as const },
|
|
32
|
+
status: { type: 'string' as const, validate: createEnumValidator(VALID_IDEA_STATUSES) },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const updateIdeaSchema = {
|
|
36
|
+
idea_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
37
|
+
title: { type: 'string' as const },
|
|
38
|
+
description: { type: 'string' as const },
|
|
39
|
+
status: { type: 'string' as const, validate: createEnumValidator(VALID_IDEA_STATUSES) },
|
|
40
|
+
doc_url: { type: 'string' as const },
|
|
41
|
+
};
|
|
27
42
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
43
|
+
const getIdeasSchema = {
|
|
44
|
+
project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
45
|
+
status: { type: 'string' as const, validate: createEnumValidator(VALID_IDEA_STATUSES) },
|
|
46
|
+
limit: { type: 'number' as const, default: 50 },
|
|
47
|
+
offset: { type: 'number' as const, default: 0 },
|
|
48
|
+
search_query: { type: 'string' as const },
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const deleteIdeaSchema = {
|
|
52
|
+
idea_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const convertIdeaToTaskSchema = {
|
|
56
|
+
idea_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
57
|
+
priority: { type: 'number' as const, default: 3, validate: priorityValidator },
|
|
58
|
+
estimated_minutes: { type: 'number' as const, validate: minutesValidator },
|
|
59
|
+
update_status: { type: 'boolean' as const, default: true },
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const addIdea: Handler = async (args, ctx) => {
|
|
63
|
+
const { project_id, title, description, status } = parseArgs(args, addIdeaSchema);
|
|
31
64
|
|
|
32
65
|
const { session } = ctx;
|
|
33
66
|
const apiClient = getApiClient();
|
|
@@ -35,67 +68,49 @@ export const addIdea: Handler = async (args, ctx) => {
|
|
|
35
68
|
const response = await apiClient.addIdea(project_id, {
|
|
36
69
|
title,
|
|
37
70
|
description,
|
|
38
|
-
status
|
|
71
|
+
status: status as IdeaStatus | undefined
|
|
39
72
|
}, session.currentSessionId || undefined);
|
|
40
73
|
|
|
41
74
|
if (!response.ok) {
|
|
42
|
-
|
|
75
|
+
return { result: { error: response.error || 'Failed to add idea' }, isError: true };
|
|
43
76
|
}
|
|
44
77
|
|
|
45
78
|
return { result: { success: true, idea_id: response.data?.idea_id, title } };
|
|
46
79
|
};
|
|
47
80
|
|
|
48
|
-
export const updateIdea: Handler = async (args,
|
|
49
|
-
const { idea_id, title, description, status, doc_url } = args
|
|
50
|
-
idea_id: string;
|
|
51
|
-
title?: string;
|
|
52
|
-
description?: string;
|
|
53
|
-
status?: IdeaStatus;
|
|
54
|
-
doc_url?: string;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
validateRequired(idea_id, 'idea_id');
|
|
58
|
-
validateUUID(idea_id, 'idea_id');
|
|
81
|
+
export const updateIdea: Handler = async (args, _ctx) => {
|
|
82
|
+
const { idea_id, title, description, status, doc_url } = parseArgs(args, updateIdeaSchema);
|
|
59
83
|
|
|
60
84
|
const apiClient = getApiClient();
|
|
61
85
|
|
|
62
86
|
const response = await apiClient.updateIdea(idea_id, {
|
|
63
87
|
title,
|
|
64
88
|
description,
|
|
65
|
-
status,
|
|
89
|
+
status: status as IdeaStatus | undefined,
|
|
66
90
|
doc_url
|
|
67
91
|
});
|
|
68
92
|
|
|
69
93
|
if (!response.ok) {
|
|
70
|
-
|
|
94
|
+
return { result: { error: response.error || 'Failed to update idea' }, isError: true };
|
|
71
95
|
}
|
|
72
96
|
|
|
73
97
|
return { result: { success: true, idea_id } };
|
|
74
98
|
};
|
|
75
99
|
|
|
76
|
-
export const getIdeas: Handler = async (args,
|
|
77
|
-
const { project_id, status, limit
|
|
78
|
-
project_id: string;
|
|
79
|
-
status?: IdeaStatus;
|
|
80
|
-
limit?: number;
|
|
81
|
-
offset?: number;
|
|
82
|
-
search_query?: string;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
validateRequired(project_id, 'project_id');
|
|
86
|
-
validateUUID(project_id, 'project_id');
|
|
100
|
+
export const getIdeas: Handler = async (args, _ctx) => {
|
|
101
|
+
const { project_id, status, limit, offset, search_query } = parseArgs(args, getIdeasSchema);
|
|
87
102
|
|
|
88
103
|
const apiClient = getApiClient();
|
|
89
104
|
|
|
90
105
|
const response = await apiClient.getIdeas(project_id, {
|
|
91
|
-
status,
|
|
106
|
+
status: status as IdeaStatus | undefined,
|
|
92
107
|
limit,
|
|
93
108
|
offset,
|
|
94
109
|
search_query
|
|
95
110
|
});
|
|
96
111
|
|
|
97
112
|
if (!response.ok) {
|
|
98
|
-
|
|
113
|
+
return { result: { error: response.error || 'Failed to fetch ideas' }, isError: true };
|
|
99
114
|
}
|
|
100
115
|
|
|
101
116
|
return {
|
|
@@ -105,35 +120,22 @@ export const getIdeas: Handler = async (args, ctx) => {
|
|
|
105
120
|
};
|
|
106
121
|
};
|
|
107
122
|
|
|
108
|
-
export const deleteIdea: Handler = async (args,
|
|
109
|
-
const { idea_id } = args
|
|
110
|
-
|
|
111
|
-
validateRequired(idea_id, 'idea_id');
|
|
112
|
-
validateUUID(idea_id, 'idea_id');
|
|
123
|
+
export const deleteIdea: Handler = async (args, _ctx) => {
|
|
124
|
+
const { idea_id } = parseArgs(args, deleteIdeaSchema);
|
|
113
125
|
|
|
114
126
|
const apiClient = getApiClient();
|
|
115
127
|
|
|
116
128
|
const response = await apiClient.deleteIdea(idea_id);
|
|
117
129
|
|
|
118
130
|
if (!response.ok) {
|
|
119
|
-
|
|
131
|
+
return { result: { error: response.error || 'Failed to delete idea' }, isError: true };
|
|
120
132
|
}
|
|
121
133
|
|
|
122
134
|
return { result: { success: true } };
|
|
123
135
|
};
|
|
124
136
|
|
|
125
|
-
export const convertIdeaToTask: Handler = async (args,
|
|
126
|
-
const { idea_id, priority
|
|
127
|
-
idea_id: string;
|
|
128
|
-
priority?: number;
|
|
129
|
-
estimated_minutes?: number;
|
|
130
|
-
update_status?: boolean;
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
validateRequired(idea_id, 'idea_id');
|
|
134
|
-
validateUUID(idea_id, 'idea_id');
|
|
135
|
-
validatePriority(priority);
|
|
136
|
-
validateEstimatedMinutes(estimated_minutes);
|
|
137
|
+
export const convertIdeaToTask: Handler = async (args, _ctx) => {
|
|
138
|
+
const { idea_id, priority, estimated_minutes, update_status } = parseArgs(args, convertIdeaToTaskSchema);
|
|
137
139
|
|
|
138
140
|
const apiClient = getApiClient();
|
|
139
141
|
|
|
@@ -155,7 +157,7 @@ export const convertIdeaToTask: Handler = async (args, ctx) => {
|
|
|
155
157
|
});
|
|
156
158
|
|
|
157
159
|
if (!response.ok) {
|
|
158
|
-
|
|
160
|
+
return { result: { error: response.error || 'Failed to convert idea' }, isError: true };
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
return { result: response.data };
|
package/src/handlers/index.ts
CHANGED
|
@@ -25,6 +25,9 @@ export * from './organizations.js';
|
|
|
25
25
|
export * from './cost.js';
|
|
26
26
|
export * from './git-issues.js';
|
|
27
27
|
export * from './sprints.js';
|
|
28
|
+
export * from './file-checkouts.js';
|
|
29
|
+
export * from './roles.js';
|
|
30
|
+
export * from './connectors.js';
|
|
28
31
|
|
|
29
32
|
import type { HandlerRegistry } from './types.js';
|
|
30
33
|
import { milestoneHandlers } from './milestones.js';
|
|
@@ -46,6 +49,9 @@ import { organizationHandlers } from './organizations.js';
|
|
|
46
49
|
import { costHandlers } from './cost.js';
|
|
47
50
|
import { gitIssueHandlers } from './git-issues.js';
|
|
48
51
|
import { sprintHandlers } from './sprints.js';
|
|
52
|
+
import { fileCheckoutHandlers } from './file-checkouts.js';
|
|
53
|
+
import { roleHandlers } from './roles.js';
|
|
54
|
+
import { connectorHandlers } from './connectors.js';
|
|
49
55
|
|
|
50
56
|
/**
|
|
51
57
|
* Build the complete handler registry from all modules
|
|
@@ -71,5 +77,8 @@ export function buildHandlerRegistry(): HandlerRegistry {
|
|
|
71
77
|
...costHandlers,
|
|
72
78
|
...gitIssueHandlers,
|
|
73
79
|
...sprintHandlers,
|
|
80
|
+
...fileCheckoutHandlers,
|
|
81
|
+
...roleHandlers,
|
|
82
|
+
...connectorHandlers,
|
|
74
83
|
};
|
|
75
84
|
}
|
|
@@ -90,19 +90,20 @@ describe('addMilestone', () => {
|
|
|
90
90
|
);
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
it('should
|
|
93
|
+
it('should return error when API call fails', async () => {
|
|
94
94
|
mockApiClient.addMilestone.mockResolvedValue({
|
|
95
95
|
ok: false,
|
|
96
96
|
error: 'Task not found',
|
|
97
97
|
});
|
|
98
98
|
const ctx = createMockContext();
|
|
99
99
|
|
|
100
|
-
await
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
).
|
|
100
|
+
const result = await addMilestone({
|
|
101
|
+
task_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
102
|
+
title: 'Milestone 1',
|
|
103
|
+
}, ctx);
|
|
104
|
+
|
|
105
|
+
expect(result.isError).toBe(true);
|
|
106
|
+
expect(result.result).toMatchObject({ error: 'Task not found' });
|
|
106
107
|
});
|
|
107
108
|
});
|
|
108
109
|
|
|
@@ -132,7 +133,7 @@ describe('updateMilestone', () => {
|
|
|
132
133
|
|
|
133
134
|
await expect(
|
|
134
135
|
updateMilestone({ milestone_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx)
|
|
135
|
-
).rejects.toThrow(
|
|
136
|
+
).rejects.toThrow(ValidationError);
|
|
136
137
|
});
|
|
137
138
|
|
|
138
139
|
it('should throw error for invalid status', async () => {
|
|
@@ -143,7 +144,7 @@ describe('updateMilestone', () => {
|
|
|
143
144
|
milestone_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
144
145
|
status: 'invalid_status',
|
|
145
146
|
}, ctx)
|
|
146
|
-
).rejects.toThrow(
|
|
147
|
+
).rejects.toThrow(ValidationError);
|
|
147
148
|
});
|
|
148
149
|
|
|
149
150
|
it('should update title successfully', async () => {
|
|
@@ -192,19 +193,20 @@ describe('updateMilestone', () => {
|
|
|
192
193
|
);
|
|
193
194
|
});
|
|
194
195
|
|
|
195
|
-
it('should
|
|
196
|
+
it('should return error when API call fails', async () => {
|
|
196
197
|
mockApiClient.updateMilestone.mockResolvedValue({
|
|
197
198
|
ok: false,
|
|
198
199
|
error: 'Milestone not found',
|
|
199
200
|
});
|
|
200
201
|
const ctx = createMockContext();
|
|
201
202
|
|
|
202
|
-
await
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
).
|
|
203
|
+
const result = await updateMilestone({
|
|
204
|
+
milestone_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
205
|
+
title: 'New Title',
|
|
206
|
+
}, ctx);
|
|
207
|
+
|
|
208
|
+
expect(result.isError).toBe(true);
|
|
209
|
+
expect(result.result).toMatchObject({ error: 'Milestone not found' });
|
|
208
210
|
});
|
|
209
211
|
});
|
|
210
212
|
|
|
@@ -263,16 +265,17 @@ describe('completeMilestone', () => {
|
|
|
263
265
|
);
|
|
264
266
|
});
|
|
265
267
|
|
|
266
|
-
it('should
|
|
268
|
+
it('should return error when API call fails', async () => {
|
|
267
269
|
mockApiClient.completeMilestone.mockResolvedValue({
|
|
268
270
|
ok: false,
|
|
269
271
|
error: 'Milestone not found',
|
|
270
272
|
});
|
|
271
273
|
const ctx = createMockContext();
|
|
272
274
|
|
|
273
|
-
await
|
|
274
|
-
|
|
275
|
-
).
|
|
275
|
+
const result = await completeMilestone({ milestone_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
276
|
+
|
|
277
|
+
expect(result.isError).toBe(true);
|
|
278
|
+
expect(result.result).toMatchObject({ error: 'Milestone not found' });
|
|
276
279
|
});
|
|
277
280
|
});
|
|
278
281
|
|
|
@@ -332,16 +335,17 @@ describe('deleteMilestone', () => {
|
|
|
332
335
|
);
|
|
333
336
|
});
|
|
334
337
|
|
|
335
|
-
it('should
|
|
338
|
+
it('should return error when API call fails', async () => {
|
|
336
339
|
mockApiClient.deleteMilestone.mockResolvedValue({
|
|
337
340
|
ok: false,
|
|
338
341
|
error: 'Milestone not found',
|
|
339
342
|
});
|
|
340
343
|
const ctx = createMockContext();
|
|
341
344
|
|
|
342
|
-
await
|
|
343
|
-
|
|
344
|
-
).
|
|
345
|
+
const result = await deleteMilestone({ milestone_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
346
|
+
|
|
347
|
+
expect(result.isError).toBe(true);
|
|
348
|
+
expect(result.result).toMatchObject({ error: 'Milestone not found' });
|
|
345
349
|
});
|
|
346
350
|
});
|
|
347
351
|
|
|
@@ -456,15 +460,16 @@ describe('getMilestones', () => {
|
|
|
456
460
|
);
|
|
457
461
|
});
|
|
458
462
|
|
|
459
|
-
it('should
|
|
463
|
+
it('should return error when API call fails', async () => {
|
|
460
464
|
mockApiClient.getMilestones.mockResolvedValue({
|
|
461
465
|
ok: false,
|
|
462
466
|
error: 'Task not found',
|
|
463
467
|
});
|
|
464
468
|
const ctx = createMockContext();
|
|
465
469
|
|
|
466
|
-
await
|
|
467
|
-
|
|
468
|
-
).
|
|
470
|
+
const result = await getMilestones({ task_id: '123e4567-e89b-12d3-a456-426614174000' }, ctx);
|
|
471
|
+
|
|
472
|
+
expect(result.isError).toBe(true);
|
|
473
|
+
expect(result.result).toMatchObject({ error: 'Task not found' });
|
|
469
474
|
});
|
|
470
475
|
});
|
|
@@ -12,20 +12,47 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { Handler, HandlerRegistry } from './types.js';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
parseArgs,
|
|
17
|
+
uuidValidator,
|
|
18
|
+
createEnumValidator,
|
|
19
|
+
ValidationError,
|
|
20
|
+
} from '../validators.js';
|
|
16
21
|
import { getApiClient } from '../api-client.js';
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
23
|
+
const VALID_MILESTONE_STATUSES = ['pending', 'in_progress', 'completed'] as const;
|
|
24
|
+
type MilestoneStatus = typeof VALID_MILESTONE_STATUSES[number];
|
|
25
|
+
|
|
26
|
+
// Argument schemas for type-safe parsing
|
|
27
|
+
const addMilestoneSchema = {
|
|
28
|
+
task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
29
|
+
title: { type: 'string' as const, required: true as const },
|
|
30
|
+
description: { type: 'string' as const },
|
|
31
|
+
order_index: { type: 'number' as const },
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const updateMilestoneSchema = {
|
|
35
|
+
milestone_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
36
|
+
title: { type: 'string' as const },
|
|
37
|
+
description: { type: 'string' as const },
|
|
38
|
+
status: { type: 'string' as const, validate: createEnumValidator(VALID_MILESTONE_STATUSES) },
|
|
39
|
+
order_index: { type: 'number' as const },
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const completeMilestoneSchema = {
|
|
43
|
+
milestone_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const deleteMilestoneSchema = {
|
|
47
|
+
milestone_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
48
|
+
};
|
|
25
49
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
50
|
+
const getMilestonesSchema = {
|
|
51
|
+
task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const addMilestone: Handler = async (args, ctx) => {
|
|
55
|
+
const { task_id, title, description, order_index } = parseArgs(args, addMilestoneSchema);
|
|
29
56
|
|
|
30
57
|
const { session } = ctx;
|
|
31
58
|
const apiClient = getApiClient();
|
|
@@ -37,7 +64,7 @@ export const addMilestone: Handler = async (args, ctx) => {
|
|
|
37
64
|
}, session.currentSessionId || undefined);
|
|
38
65
|
|
|
39
66
|
if (!response.ok) {
|
|
40
|
-
|
|
67
|
+
return { result: { error: response.error || 'Failed to add milestone' }, isError: true };
|
|
41
68
|
}
|
|
42
69
|
|
|
43
70
|
return {
|
|
@@ -48,24 +75,8 @@ export const addMilestone: Handler = async (args, ctx) => {
|
|
|
48
75
|
};
|
|
49
76
|
};
|
|
50
77
|
|
|
51
|
-
export const updateMilestone: Handler = async (args,
|
|
52
|
-
const { milestone_id, title, description, status, order_index } = args
|
|
53
|
-
milestone_id: string;
|
|
54
|
-
title?: string;
|
|
55
|
-
description?: string;
|
|
56
|
-
status?: string;
|
|
57
|
-
order_index?: number;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
validateRequired(milestone_id, 'milestone_id');
|
|
61
|
-
validateUUID(milestone_id, 'milestone_id');
|
|
62
|
-
|
|
63
|
-
// Validate status if provided
|
|
64
|
-
if (status !== undefined) {
|
|
65
|
-
if (!['pending', 'in_progress', 'completed'].includes(status)) {
|
|
66
|
-
throw new ValidationError('status must be pending, in_progress, or completed');
|
|
67
|
-
}
|
|
68
|
-
}
|
|
78
|
+
export const updateMilestone: Handler = async (args, _ctx) => {
|
|
79
|
+
const { milestone_id, title, description, status, order_index } = parseArgs(args, updateMilestoneSchema);
|
|
69
80
|
|
|
70
81
|
// Check that at least one field is provided
|
|
71
82
|
if (title === undefined && description === undefined && status === undefined && order_index === undefined) {
|
|
@@ -77,12 +88,12 @@ export const updateMilestone: Handler = async (args, ctx) => {
|
|
|
77
88
|
const response = await apiClient.updateMilestone(milestone_id, {
|
|
78
89
|
title,
|
|
79
90
|
description,
|
|
80
|
-
status: status as
|
|
91
|
+
status: status as MilestoneStatus | undefined,
|
|
81
92
|
order_index
|
|
82
93
|
});
|
|
83
94
|
|
|
84
95
|
if (!response.ok) {
|
|
85
|
-
|
|
96
|
+
return { result: { error: response.error || 'Failed to update milestone' }, isError: true };
|
|
86
97
|
}
|
|
87
98
|
|
|
88
99
|
return {
|
|
@@ -93,18 +104,15 @@ export const updateMilestone: Handler = async (args, ctx) => {
|
|
|
93
104
|
};
|
|
94
105
|
};
|
|
95
106
|
|
|
96
|
-
export const completeMilestone: Handler = async (args,
|
|
97
|
-
const { milestone_id } = args
|
|
98
|
-
|
|
99
|
-
validateRequired(milestone_id, 'milestone_id');
|
|
100
|
-
validateUUID(milestone_id, 'milestone_id');
|
|
107
|
+
export const completeMilestone: Handler = async (args, _ctx) => {
|
|
108
|
+
const { milestone_id } = parseArgs(args, completeMilestoneSchema);
|
|
101
109
|
|
|
102
110
|
const apiClient = getApiClient();
|
|
103
111
|
|
|
104
112
|
const response = await apiClient.completeMilestone(milestone_id);
|
|
105
113
|
|
|
106
114
|
if (!response.ok) {
|
|
107
|
-
|
|
115
|
+
return { result: { error: response.error || 'Failed to complete milestone' }, isError: true };
|
|
108
116
|
}
|
|
109
117
|
|
|
110
118
|
return {
|
|
@@ -115,18 +123,15 @@ export const completeMilestone: Handler = async (args, ctx) => {
|
|
|
115
123
|
};
|
|
116
124
|
};
|
|
117
125
|
|
|
118
|
-
export const deleteMilestone: Handler = async (args,
|
|
119
|
-
const { milestone_id } = args
|
|
120
|
-
|
|
121
|
-
validateRequired(milestone_id, 'milestone_id');
|
|
122
|
-
validateUUID(milestone_id, 'milestone_id');
|
|
126
|
+
export const deleteMilestone: Handler = async (args, _ctx) => {
|
|
127
|
+
const { milestone_id } = parseArgs(args, deleteMilestoneSchema);
|
|
123
128
|
|
|
124
129
|
const apiClient = getApiClient();
|
|
125
130
|
|
|
126
131
|
const response = await apiClient.deleteMilestone(milestone_id);
|
|
127
132
|
|
|
128
133
|
if (!response.ok) {
|
|
129
|
-
|
|
134
|
+
return { result: { error: response.error || 'Failed to delete milestone' }, isError: true };
|
|
130
135
|
}
|
|
131
136
|
|
|
132
137
|
return {
|
|
@@ -137,18 +142,15 @@ export const deleteMilestone: Handler = async (args, ctx) => {
|
|
|
137
142
|
};
|
|
138
143
|
};
|
|
139
144
|
|
|
140
|
-
export const getMilestones: Handler = async (args,
|
|
141
|
-
const { task_id } = args
|
|
142
|
-
|
|
143
|
-
validateRequired(task_id, 'task_id');
|
|
144
|
-
validateUUID(task_id, 'task_id');
|
|
145
|
+
export const getMilestones: Handler = async (args, _ctx) => {
|
|
146
|
+
const { task_id } = parseArgs(args, getMilestonesSchema);
|
|
145
147
|
|
|
146
148
|
const apiClient = getApiClient();
|
|
147
149
|
|
|
148
150
|
const response = await apiClient.getMilestones(task_id);
|
|
149
151
|
|
|
150
152
|
if (!response.ok) {
|
|
151
|
-
|
|
153
|
+
return { result: { error: response.error || 'Failed to get milestones' }, isError: true };
|
|
152
154
|
}
|
|
153
155
|
|
|
154
156
|
// Stats are calculated server-side now
|