@allpepper/task-orchestrator 0.1.0

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 (42) hide show
  1. package/README.md +15 -0
  2. package/package.json +51 -0
  3. package/src/db/client.ts +34 -0
  4. package/src/db/index.ts +1 -0
  5. package/src/db/migrate.ts +51 -0
  6. package/src/db/migrations/001_initial_schema.sql +160 -0
  7. package/src/domain/index.ts +1 -0
  8. package/src/domain/types.ts +225 -0
  9. package/src/index.ts +7 -0
  10. package/src/repos/base.ts +151 -0
  11. package/src/repos/dependencies.ts +356 -0
  12. package/src/repos/features.ts +507 -0
  13. package/src/repos/index.ts +4 -0
  14. package/src/repos/projects.ts +350 -0
  15. package/src/repos/sections.ts +505 -0
  16. package/src/repos/tags.example.ts +125 -0
  17. package/src/repos/tags.ts +175 -0
  18. package/src/repos/tasks.ts +581 -0
  19. package/src/repos/templates.ts +649 -0
  20. package/src/server.ts +121 -0
  21. package/src/services/index.ts +2 -0
  22. package/src/services/status-validator.ts +100 -0
  23. package/src/services/workflow.ts +104 -0
  24. package/src/tools/apply-template.ts +129 -0
  25. package/src/tools/get-blocked-tasks.ts +63 -0
  26. package/src/tools/get-next-status.ts +183 -0
  27. package/src/tools/get-next-task.ts +75 -0
  28. package/src/tools/get-tag-usage.ts +54 -0
  29. package/src/tools/index.ts +30 -0
  30. package/src/tools/list-tags.ts +56 -0
  31. package/src/tools/manage-container.ts +333 -0
  32. package/src/tools/manage-dependency.ts +198 -0
  33. package/src/tools/manage-sections.ts +388 -0
  34. package/src/tools/manage-template.ts +313 -0
  35. package/src/tools/query-container.ts +296 -0
  36. package/src/tools/query-dependencies.ts +68 -0
  37. package/src/tools/query-sections.ts +70 -0
  38. package/src/tools/query-templates.ts +137 -0
  39. package/src/tools/query-workflow-state.ts +198 -0
  40. package/src/tools/registry.ts +180 -0
  41. package/src/tools/rename-tag.ts +64 -0
  42. package/src/tools/setup-project.ts +189 -0
@@ -0,0 +1,388 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { createSuccessResponse, createErrorResponse, uuidSchema, optionalUuidSchema } from './registry';
4
+ import {
5
+ addSection,
6
+ updateSection,
7
+ updateSectionText,
8
+ deleteSection,
9
+ reorderSections,
10
+ bulkCreateSections,
11
+ bulkDeleteSections
12
+ } from '../repos/sections';
13
+
14
+ /**
15
+ * Register the manage_sections MCP tool
16
+ *
17
+ * Provides comprehensive section management with operations: add, update, updateText, delete, reorder, bulkCreate, bulkDelete.
18
+ */
19
+ export function registerManageSectionsTool(server: McpServer): void {
20
+ server.tool(
21
+ 'manage_sections',
22
+ 'Manage sections with operations: add (create new), update (modify fields), updateText (update content only), delete (remove), reorder (change ordinals), bulkCreate (create multiple), bulkDelete (remove multiple).',
23
+ {
24
+ operation: z.enum(['add', 'update', 'updateText', 'delete', 'reorder', 'bulkCreate', 'bulkDelete']).describe('The operation to perform'),
25
+ entityType: z.enum(['PROJECT', 'FEATURE', 'TASK']).optional().describe('Required for: add, bulkCreate'),
26
+ entityId: optionalUuidSchema.describe('Required for: add, reorder, bulkCreate'),
27
+ sectionId: optionalUuidSchema.describe('Required for: update, updateText, delete'),
28
+ title: z.string().optional().describe('Section title (for add/update)'),
29
+ usageDescription: z.string().optional().describe('Description of how to use this section (for add/update)'),
30
+ content: z.string().optional().describe('Section content (for add/update/updateText)'),
31
+ contentFormat: z.enum(['PLAIN_TEXT', 'MARKDOWN', 'JSON', 'CODE']).optional().describe('Content format (for add/update)'),
32
+ tags: z.string().optional().describe('Comma-separated tags (for add/update)'),
33
+ ordinal: z.number().int().optional().describe('Display order (for add)'),
34
+ version: z.number().int().optional().describe('Required for: update, updateText (for optimistic locking)'),
35
+ orderedIds: z.string().optional().describe('Comma-separated section IDs in new order (for reorder)'),
36
+ sections: z.string().optional().describe('JSON array of sections to create (for bulkCreate)'),
37
+ sectionIds: z.string().optional().describe('Comma-separated section IDs to delete (for bulkDelete)')
38
+ },
39
+ async (params) => {
40
+ try {
41
+ switch (params.operation) {
42
+ case 'add': {
43
+ // Validate required parameters
44
+ if (!params.entityType || !params.entityId || !params.title || !params.usageDescription || !params.content) {
45
+ return {
46
+ content: [{
47
+ type: 'text' as const,
48
+ text: JSON.stringify(
49
+ createErrorResponse('Missing required parameters for add: entityType, entityId, title, usageDescription, content'),
50
+ null,
51
+ 2
52
+ )
53
+ }]
54
+ };
55
+ }
56
+
57
+ const result = addSection({
58
+ entityType: params.entityType,
59
+ entityId: params.entityId,
60
+ title: params.title,
61
+ usageDescription: params.usageDescription,
62
+ content: params.content,
63
+ contentFormat: params.contentFormat,
64
+ ordinal: params.ordinal,
65
+ tags: params.tags
66
+ });
67
+
68
+ if (!result.success) {
69
+ return {
70
+ content: [{
71
+ type: 'text' as const,
72
+ text: JSON.stringify(createErrorResponse(result.error), null, 2)
73
+ }]
74
+ };
75
+ }
76
+
77
+ return {
78
+ content: [{
79
+ type: 'text' as const,
80
+ text: JSON.stringify(
81
+ createSuccessResponse('Section added successfully', result.data),
82
+ null,
83
+ 2
84
+ )
85
+ }]
86
+ };
87
+ }
88
+
89
+ case 'update': {
90
+ // Validate required parameters
91
+ if (!params.sectionId || params.version === undefined) {
92
+ return {
93
+ content: [{
94
+ type: 'text' as const,
95
+ text: JSON.stringify(
96
+ createErrorResponse('Missing required parameters for update: sectionId, version'),
97
+ null,
98
+ 2
99
+ )
100
+ }]
101
+ };
102
+ }
103
+
104
+ const result = updateSection(params.sectionId, {
105
+ title: params.title,
106
+ usageDescription: params.usageDescription,
107
+ content: params.content,
108
+ contentFormat: params.contentFormat,
109
+ tags: params.tags,
110
+ version: params.version
111
+ });
112
+
113
+ if (!result.success) {
114
+ return {
115
+ content: [{
116
+ type: 'text' as const,
117
+ text: JSON.stringify(createErrorResponse(result.error), null, 2)
118
+ }]
119
+ };
120
+ }
121
+
122
+ return {
123
+ content: [{
124
+ type: 'text' as const,
125
+ text: JSON.stringify(
126
+ createSuccessResponse('Section updated successfully', result.data),
127
+ null,
128
+ 2
129
+ )
130
+ }]
131
+ };
132
+ }
133
+
134
+ case 'updateText': {
135
+ // Validate required parameters
136
+ if (!params.sectionId || !params.content || params.version === undefined) {
137
+ return {
138
+ content: [{
139
+ type: 'text' as const,
140
+ text: JSON.stringify(
141
+ createErrorResponse('Missing required parameters for updateText: sectionId, content, version'),
142
+ null,
143
+ 2
144
+ )
145
+ }]
146
+ };
147
+ }
148
+
149
+ const result = updateSectionText(params.sectionId, params.content, params.version);
150
+
151
+ if (!result.success) {
152
+ return {
153
+ content: [{
154
+ type: 'text' as const,
155
+ text: JSON.stringify(createErrorResponse(result.error), null, 2)
156
+ }]
157
+ };
158
+ }
159
+
160
+ return {
161
+ content: [{
162
+ type: 'text' as const,
163
+ text: JSON.stringify(
164
+ createSuccessResponse('Section text updated successfully', result.data),
165
+ null,
166
+ 2
167
+ )
168
+ }]
169
+ };
170
+ }
171
+
172
+ case 'delete': {
173
+ // Validate required parameters
174
+ if (!params.sectionId) {
175
+ return {
176
+ content: [{
177
+ type: 'text' as const,
178
+ text: JSON.stringify(
179
+ createErrorResponse('Missing required parameter for delete: sectionId'),
180
+ null,
181
+ 2
182
+ )
183
+ }]
184
+ };
185
+ }
186
+
187
+ const result = deleteSection(params.sectionId);
188
+
189
+ if (!result.success) {
190
+ return {
191
+ content: [{
192
+ type: 'text' as const,
193
+ text: JSON.stringify(createErrorResponse(result.error), null, 2)
194
+ }]
195
+ };
196
+ }
197
+
198
+ return {
199
+ content: [{
200
+ type: 'text' as const,
201
+ text: JSON.stringify(
202
+ createSuccessResponse('Section deleted successfully', { deleted: result.data }),
203
+ null,
204
+ 2
205
+ )
206
+ }]
207
+ };
208
+ }
209
+
210
+ case 'reorder': {
211
+ // Validate required parameters
212
+ if (!params.entityId || !params.entityType || !params.orderedIds) {
213
+ return {
214
+ content: [{
215
+ type: 'text' as const,
216
+ text: JSON.stringify(
217
+ createErrorResponse('Missing required parameters for reorder: entityId, entityType, orderedIds'),
218
+ null,
219
+ 2
220
+ )
221
+ }]
222
+ };
223
+ }
224
+
225
+ const orderedIds = params.orderedIds.split(',').map(id => id.trim().replace(/-/g, ''));
226
+ const result = reorderSections(params.entityId, params.entityType, orderedIds);
227
+
228
+ if (!result.success) {
229
+ return {
230
+ content: [{
231
+ type: 'text' as const,
232
+ text: JSON.stringify(createErrorResponse(result.error), null, 2)
233
+ }]
234
+ };
235
+ }
236
+
237
+ return {
238
+ content: [{
239
+ type: 'text' as const,
240
+ text: JSON.stringify(
241
+ createSuccessResponse('Sections reordered successfully', { reordered: result.data }),
242
+ null,
243
+ 2
244
+ )
245
+ }]
246
+ };
247
+ }
248
+
249
+ case 'bulkCreate': {
250
+ // Validate required parameters
251
+ if (!params.sections) {
252
+ return {
253
+ content: [{
254
+ type: 'text' as const,
255
+ text: JSON.stringify(
256
+ createErrorResponse('Missing required parameter for bulkCreate: sections (JSON array string)'),
257
+ null,
258
+ 2
259
+ )
260
+ }]
261
+ };
262
+ }
263
+
264
+ if (!params.entityType || !params.entityId) {
265
+ return {
266
+ content: [{
267
+ type: 'text' as const,
268
+ text: JSON.stringify(
269
+ createErrorResponse('Missing required parameters for bulkCreate: entityType, entityId'),
270
+ null,
271
+ 2
272
+ )
273
+ }]
274
+ };
275
+ }
276
+
277
+ let sectionsArray;
278
+ try {
279
+ sectionsArray = JSON.parse(params.sections);
280
+ } catch (error) {
281
+ return {
282
+ content: [{
283
+ type: 'text' as const,
284
+ text: JSON.stringify(
285
+ createErrorResponse('Invalid JSON format for sections parameter'),
286
+ null,
287
+ 2
288
+ )
289
+ }]
290
+ };
291
+ }
292
+
293
+ // Inject entityType and entityId from top-level params into each section
294
+ const enrichedSections = sectionsArray.map((section: any) => ({
295
+ ...section,
296
+ entityType: params.entityType,
297
+ entityId: params.entityId,
298
+ }));
299
+
300
+ const result = bulkCreateSections(enrichedSections);
301
+
302
+ if (!result.success) {
303
+ return {
304
+ content: [{
305
+ type: 'text' as const,
306
+ text: JSON.stringify(createErrorResponse(result.error), null, 2)
307
+ }]
308
+ };
309
+ }
310
+
311
+ return {
312
+ content: [{
313
+ type: 'text' as const,
314
+ text: JSON.stringify(
315
+ createSuccessResponse('Sections bulk created successfully', result.data),
316
+ null,
317
+ 2
318
+ )
319
+ }]
320
+ };
321
+ }
322
+
323
+ case 'bulkDelete': {
324
+ // Validate required parameters
325
+ if (!params.sectionIds) {
326
+ return {
327
+ content: [{
328
+ type: 'text' as const,
329
+ text: JSON.stringify(
330
+ createErrorResponse('Missing required parameter for bulkDelete: sectionIds'),
331
+ null,
332
+ 2
333
+ )
334
+ }]
335
+ };
336
+ }
337
+
338
+ const sectionIds = params.sectionIds.split(',').map(id => id.trim().replace(/-/g, ''));
339
+ const result = bulkDeleteSections(sectionIds);
340
+
341
+ if (!result.success) {
342
+ return {
343
+ content: [{
344
+ type: 'text' as const,
345
+ text: JSON.stringify(createErrorResponse(result.error), null, 2)
346
+ }]
347
+ };
348
+ }
349
+
350
+ return {
351
+ content: [{
352
+ type: 'text' as const,
353
+ text: JSON.stringify(
354
+ createSuccessResponse('Sections bulk deleted successfully', { deletedCount: result.data }),
355
+ null,
356
+ 2
357
+ )
358
+ }]
359
+ };
360
+ }
361
+
362
+ default:
363
+ return {
364
+ content: [{
365
+ type: 'text' as const,
366
+ text: JSON.stringify(
367
+ createErrorResponse(`Invalid operation: ${params.operation}`),
368
+ null,
369
+ 2
370
+ )
371
+ }]
372
+ };
373
+ }
374
+ } catch (error: any) {
375
+ return {
376
+ content: [{
377
+ type: 'text' as const,
378
+ text: JSON.stringify(
379
+ createErrorResponse(error.message || 'Failed to manage section'),
380
+ null,
381
+ 2
382
+ )
383
+ }]
384
+ };
385
+ }
386
+ }
387
+ );
388
+ }
@@ -0,0 +1,313 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { createSuccessResponse, createErrorResponse, uuidSchema, optionalUuidSchema } from './registry';
4
+ import {
5
+ createTemplate,
6
+ updateTemplate,
7
+ deleteTemplate,
8
+ enableTemplate,
9
+ disableTemplate,
10
+ addTemplateSection
11
+ } from '../repos/templates';
12
+
13
+ /**
14
+ * Registers the `manage_template` MCP tool.
15
+ *
16
+ * Operations:
17
+ * - create: Create a new template
18
+ * - update: Update an existing template
19
+ * - delete: Delete a template
20
+ * - enable: Enable a template
21
+ * - disable: Disable a template
22
+ * - addSection: Add a section to a template
23
+ */
24
+ export function registerManageTemplateTool(server: McpServer): void {
25
+ server.tool(
26
+ 'manage_template',
27
+ 'Manage templates with operations: create, update, delete, enable, disable, or addSection',
28
+ {
29
+ operation: z.enum(['create', 'update', 'delete', 'enable', 'disable', 'addSection']),
30
+ id: optionalUuidSchema,
31
+ name: z.string().optional(),
32
+ description: z.string().optional(),
33
+ targetEntityType: z.string().optional(),
34
+ isBuiltIn: z.boolean().optional(),
35
+ isProtected: z.boolean().optional(),
36
+ createdBy: z.string().optional(),
37
+ tags: z.string().optional(),
38
+ // Section-specific parameters (for addSection operation)
39
+ title: z.string().optional(),
40
+ usageDescription: z.string().optional(),
41
+ contentSample: z.string().optional(),
42
+ contentFormat: z.string().optional(),
43
+ isRequired: z.boolean().optional(),
44
+ ordinal: z.number().int().optional()
45
+ },
46
+ async (params: any) => {
47
+ try {
48
+ const operation = params.operation;
49
+
50
+ // Validate ID for operations that require it
51
+ const requiresId = ['update', 'delete', 'enable', 'disable', 'addSection'];
52
+ if (requiresId.includes(operation) && !params.id) {
53
+ return {
54
+ content: [{
55
+ type: 'text' as const,
56
+ text: JSON.stringify(
57
+ createErrorResponse(`Template ID is required for ${operation} operation`),
58
+ null,
59
+ 2
60
+ )
61
+ }]
62
+ };
63
+ }
64
+
65
+ switch (operation) {
66
+ case 'create': {
67
+ // Validate required parameters for create
68
+ if (!params.name || !params.description || !params.targetEntityType) {
69
+ return {
70
+ content: [{
71
+ type: 'text' as const,
72
+ text: JSON.stringify(
73
+ createErrorResponse('name, description, and targetEntityType are required for create operation'),
74
+ null,
75
+ 2
76
+ )
77
+ }]
78
+ };
79
+ }
80
+
81
+ const result = createTemplate({
82
+ name: params.name,
83
+ description: params.description,
84
+ targetEntityType: params.targetEntityType,
85
+ isBuiltIn: params.isBuiltIn,
86
+ isProtected: params.isProtected,
87
+ createdBy: params.createdBy,
88
+ tags: params.tags
89
+ });
90
+
91
+ if (!result.success) {
92
+ return {
93
+ content: [{
94
+ type: 'text' as const,
95
+ text: JSON.stringify(
96
+ createErrorResponse(result.error || 'Failed to create template'),
97
+ null,
98
+ 2
99
+ )
100
+ }]
101
+ };
102
+ }
103
+
104
+ return {
105
+ content: [{
106
+ type: 'text' as const,
107
+ text: JSON.stringify(
108
+ createSuccessResponse('Template created successfully', result.data),
109
+ null,
110
+ 2
111
+ )
112
+ }]
113
+ };
114
+ }
115
+
116
+ case 'update': {
117
+ const updateParams: any = {};
118
+ if (params.name !== undefined) updateParams.name = params.name;
119
+ if (params.description !== undefined) updateParams.description = params.description;
120
+ if (params.tags !== undefined) updateParams.tags = params.tags;
121
+
122
+ const result = updateTemplate(params.id!, updateParams);
123
+
124
+ if (!result.success) {
125
+ return {
126
+ content: [{
127
+ type: 'text' as const,
128
+ text: JSON.stringify(
129
+ createErrorResponse(result.error || 'Failed to update template'),
130
+ null,
131
+ 2
132
+ )
133
+ }]
134
+ };
135
+ }
136
+
137
+ return {
138
+ content: [{
139
+ type: 'text' as const,
140
+ text: JSON.stringify(
141
+ createSuccessResponse('Template updated successfully', result.data),
142
+ null,
143
+ 2
144
+ )
145
+ }]
146
+ };
147
+ }
148
+
149
+ case 'delete': {
150
+ const result = deleteTemplate(params.id!);
151
+
152
+ if (!result.success) {
153
+ return {
154
+ content: [{
155
+ type: 'text' as const,
156
+ text: JSON.stringify(
157
+ createErrorResponse(result.error || 'Failed to delete template'),
158
+ null,
159
+ 2
160
+ )
161
+ }]
162
+ };
163
+ }
164
+
165
+ return {
166
+ content: [{
167
+ type: 'text' as const,
168
+ text: JSON.stringify(
169
+ createSuccessResponse('Template deleted successfully', { deleted: true }),
170
+ null,
171
+ 2
172
+ )
173
+ }]
174
+ };
175
+ }
176
+
177
+ case 'enable': {
178
+ const result = enableTemplate(params.id!);
179
+
180
+ if (!result.success) {
181
+ return {
182
+ content: [{
183
+ type: 'text' as const,
184
+ text: JSON.stringify(
185
+ createErrorResponse(result.error || 'Failed to enable template'),
186
+ null,
187
+ 2
188
+ )
189
+ }]
190
+ };
191
+ }
192
+
193
+ return {
194
+ content: [{
195
+ type: 'text' as const,
196
+ text: JSON.stringify(
197
+ createSuccessResponse('Template enabled successfully', result.data),
198
+ null,
199
+ 2
200
+ )
201
+ }]
202
+ };
203
+ }
204
+
205
+ case 'disable': {
206
+ const result = disableTemplate(params.id!);
207
+
208
+ if (!result.success) {
209
+ return {
210
+ content: [{
211
+ type: 'text' as const,
212
+ text: JSON.stringify(
213
+ createErrorResponse(result.error || 'Failed to disable template'),
214
+ null,
215
+ 2
216
+ )
217
+ }]
218
+ };
219
+ }
220
+
221
+ return {
222
+ content: [{
223
+ type: 'text' as const,
224
+ text: JSON.stringify(
225
+ createSuccessResponse('Template disabled successfully', result.data),
226
+ null,
227
+ 2
228
+ )
229
+ }]
230
+ };
231
+ }
232
+
233
+ case 'addSection': {
234
+ // Validate required parameters for addSection
235
+ if (!params.title || !params.usageDescription || !params.contentSample) {
236
+ return {
237
+ content: [{
238
+ type: 'text' as const,
239
+ text: JSON.stringify(
240
+ createErrorResponse('title, usageDescription, and contentSample are required for addSection operation'),
241
+ null,
242
+ 2
243
+ )
244
+ }]
245
+ };
246
+ }
247
+
248
+ const result = addTemplateSection({
249
+ templateId: params.id!,
250
+ title: params.title,
251
+ usageDescription: params.usageDescription,
252
+ contentSample: params.contentSample,
253
+ contentFormat: params.contentFormat,
254
+ isRequired: params.isRequired,
255
+ tags: params.tags,
256
+ ordinal: params.ordinal
257
+ });
258
+
259
+ if (!result.success) {
260
+ return {
261
+ content: [{
262
+ type: 'text' as const,
263
+ text: JSON.stringify(
264
+ createErrorResponse(result.error || 'Failed to add template section'),
265
+ null,
266
+ 2
267
+ )
268
+ }]
269
+ };
270
+ }
271
+
272
+ return {
273
+ content: [{
274
+ type: 'text' as const,
275
+ text: JSON.stringify(
276
+ createSuccessResponse('Template section added successfully', result.data),
277
+ null,
278
+ 2
279
+ )
280
+ }]
281
+ };
282
+ }
283
+
284
+ default:
285
+ return {
286
+ content: [{
287
+ type: 'text' as const,
288
+ text: JSON.stringify(
289
+ createErrorResponse(`Invalid operation: ${operation}`),
290
+ null,
291
+ 2
292
+ )
293
+ }]
294
+ };
295
+ }
296
+ } catch (error: any) {
297
+ return {
298
+ content: [{
299
+ type: 'text' as const,
300
+ text: JSON.stringify(
301
+ createErrorResponse(
302
+ 'Internal error',
303
+ error.message || 'Unknown error occurred'
304
+ ),
305
+ null,
306
+ 2
307
+ )
308
+ }]
309
+ };
310
+ }
311
+ }
312
+ );
313
+ }