@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.
- package/README.md +15 -0
- package/package.json +51 -0
- package/src/db/client.ts +34 -0
- package/src/db/index.ts +1 -0
- package/src/db/migrate.ts +51 -0
- package/src/db/migrations/001_initial_schema.sql +160 -0
- package/src/domain/index.ts +1 -0
- package/src/domain/types.ts +225 -0
- package/src/index.ts +7 -0
- package/src/repos/base.ts +151 -0
- package/src/repos/dependencies.ts +356 -0
- package/src/repos/features.ts +507 -0
- package/src/repos/index.ts +4 -0
- package/src/repos/projects.ts +350 -0
- package/src/repos/sections.ts +505 -0
- package/src/repos/tags.example.ts +125 -0
- package/src/repos/tags.ts +175 -0
- package/src/repos/tasks.ts +581 -0
- package/src/repos/templates.ts +649 -0
- package/src/server.ts +121 -0
- package/src/services/index.ts +2 -0
- package/src/services/status-validator.ts +100 -0
- package/src/services/workflow.ts +104 -0
- package/src/tools/apply-template.ts +129 -0
- package/src/tools/get-blocked-tasks.ts +63 -0
- package/src/tools/get-next-status.ts +183 -0
- package/src/tools/get-next-task.ts +75 -0
- package/src/tools/get-tag-usage.ts +54 -0
- package/src/tools/index.ts +30 -0
- package/src/tools/list-tags.ts +56 -0
- package/src/tools/manage-container.ts +333 -0
- package/src/tools/manage-dependency.ts +198 -0
- package/src/tools/manage-sections.ts +388 -0
- package/src/tools/manage-template.ts +313 -0
- package/src/tools/query-container.ts +296 -0
- package/src/tools/query-dependencies.ts +68 -0
- package/src/tools/query-sections.ts +70 -0
- package/src/tools/query-templates.ts +137 -0
- package/src/tools/query-workflow-state.ts +198 -0
- package/src/tools/registry.ts +180 -0
- package/src/tools/rename-tag.ts +64 -0
- package/src/tools/setup-project.ts +189 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { createSuccessResponse, createErrorResponse } from './registry';
|
|
4
|
+
import { ProjectStatus, FeatureStatus, TaskStatus } from '../domain/types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Status transition maps for each container type
|
|
8
|
+
*
|
|
9
|
+
* Note: CANCELLED and DEFERRED are intentionally non-terminal statuses.
|
|
10
|
+
* They allow transitions back to earlier workflow stages (BACKLOG/PENDING for tasks,
|
|
11
|
+
* PLANNING for projects) to support reinstating cancelled or deferred work.
|
|
12
|
+
*/
|
|
13
|
+
const PROJECT_TRANSITIONS: Record<ProjectStatus, ProjectStatus[]> = {
|
|
14
|
+
[ProjectStatus.PLANNING]: [ProjectStatus.IN_DEVELOPMENT, ProjectStatus.ON_HOLD, ProjectStatus.CANCELLED],
|
|
15
|
+
[ProjectStatus.IN_DEVELOPMENT]: [ProjectStatus.COMPLETED, ProjectStatus.ON_HOLD, ProjectStatus.CANCELLED],
|
|
16
|
+
[ProjectStatus.ON_HOLD]: [ProjectStatus.PLANNING, ProjectStatus.IN_DEVELOPMENT, ProjectStatus.CANCELLED],
|
|
17
|
+
[ProjectStatus.COMPLETED]: [ProjectStatus.ARCHIVED],
|
|
18
|
+
[ProjectStatus.CANCELLED]: [ProjectStatus.PLANNING], // Non-terminal: allows reinstating cancelled projects
|
|
19
|
+
[ProjectStatus.ARCHIVED]: []
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const FEATURE_TRANSITIONS: Record<FeatureStatus, FeatureStatus[]> = {
|
|
23
|
+
[FeatureStatus.DRAFT]: [FeatureStatus.PLANNING],
|
|
24
|
+
[FeatureStatus.PLANNING]: [FeatureStatus.IN_DEVELOPMENT, FeatureStatus.ON_HOLD],
|
|
25
|
+
[FeatureStatus.IN_DEVELOPMENT]: [FeatureStatus.TESTING, FeatureStatus.BLOCKED, FeatureStatus.ON_HOLD],
|
|
26
|
+
[FeatureStatus.TESTING]: [FeatureStatus.VALIDATING, FeatureStatus.IN_DEVELOPMENT],
|
|
27
|
+
[FeatureStatus.VALIDATING]: [FeatureStatus.PENDING_REVIEW, FeatureStatus.IN_DEVELOPMENT],
|
|
28
|
+
[FeatureStatus.PENDING_REVIEW]: [FeatureStatus.DEPLOYED, FeatureStatus.IN_DEVELOPMENT],
|
|
29
|
+
[FeatureStatus.BLOCKED]: [FeatureStatus.IN_DEVELOPMENT, FeatureStatus.ON_HOLD],
|
|
30
|
+
[FeatureStatus.ON_HOLD]: [FeatureStatus.PLANNING, FeatureStatus.IN_DEVELOPMENT],
|
|
31
|
+
[FeatureStatus.DEPLOYED]: [FeatureStatus.COMPLETED],
|
|
32
|
+
[FeatureStatus.COMPLETED]: [FeatureStatus.ARCHIVED],
|
|
33
|
+
[FeatureStatus.ARCHIVED]: []
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const TASK_TRANSITIONS: Record<TaskStatus, TaskStatus[]> = {
|
|
37
|
+
[TaskStatus.BACKLOG]: [TaskStatus.PENDING],
|
|
38
|
+
[TaskStatus.PENDING]: [TaskStatus.IN_PROGRESS, TaskStatus.BLOCKED, TaskStatus.ON_HOLD, TaskStatus.CANCELLED, TaskStatus.DEFERRED],
|
|
39
|
+
[TaskStatus.IN_PROGRESS]: [TaskStatus.IN_REVIEW, TaskStatus.TESTING, TaskStatus.BLOCKED, TaskStatus.ON_HOLD, TaskStatus.COMPLETED],
|
|
40
|
+
[TaskStatus.IN_REVIEW]: [TaskStatus.CHANGES_REQUESTED, TaskStatus.COMPLETED],
|
|
41
|
+
[TaskStatus.CHANGES_REQUESTED]: [TaskStatus.IN_PROGRESS],
|
|
42
|
+
[TaskStatus.TESTING]: [TaskStatus.READY_FOR_QA, TaskStatus.IN_PROGRESS],
|
|
43
|
+
[TaskStatus.READY_FOR_QA]: [TaskStatus.INVESTIGATING, TaskStatus.DEPLOYED, TaskStatus.COMPLETED],
|
|
44
|
+
[TaskStatus.INVESTIGATING]: [TaskStatus.IN_PROGRESS, TaskStatus.BLOCKED],
|
|
45
|
+
[TaskStatus.BLOCKED]: [TaskStatus.PENDING, TaskStatus.IN_PROGRESS],
|
|
46
|
+
[TaskStatus.ON_HOLD]: [TaskStatus.PENDING, TaskStatus.IN_PROGRESS],
|
|
47
|
+
[TaskStatus.DEPLOYED]: [TaskStatus.COMPLETED],
|
|
48
|
+
[TaskStatus.COMPLETED]: [], // Terminal: no transitions allowed
|
|
49
|
+
[TaskStatus.CANCELLED]: [TaskStatus.BACKLOG, TaskStatus.PENDING], // Non-terminal: allows reinstating cancelled tasks
|
|
50
|
+
[TaskStatus.DEFERRED]: [TaskStatus.BACKLOG, TaskStatus.PENDING] // Non-terminal: allows resuming deferred tasks
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Register the get_next_status MCP tool.
|
|
55
|
+
*
|
|
56
|
+
* Returns valid next statuses for a container based on its current status.
|
|
57
|
+
* Note: CANCELLED and DEFERRED statuses can transition back to earlier stages
|
|
58
|
+
* (BACKLOG/PENDING for tasks, PLANNING for projects) to support work reinstatement.
|
|
59
|
+
*/
|
|
60
|
+
export function registerGetNextStatusTool(server: McpServer): void {
|
|
61
|
+
server.tool(
|
|
62
|
+
'get_next_status',
|
|
63
|
+
'Get valid next statuses for a container. Returns an array of allowed status transitions based on the container type and current status.',
|
|
64
|
+
{
|
|
65
|
+
containerType: z.enum(['project', 'feature', 'task']).describe('Type of container (project, feature, or task)'),
|
|
66
|
+
currentStatus: z.string().describe('Current status of the container')
|
|
67
|
+
},
|
|
68
|
+
async (params: any) => {
|
|
69
|
+
try {
|
|
70
|
+
const { containerType, currentStatus } = params;
|
|
71
|
+
const upperStatus = currentStatus.toUpperCase();
|
|
72
|
+
|
|
73
|
+
let allowedTransitions: string[] = [];
|
|
74
|
+
|
|
75
|
+
switch (containerType) {
|
|
76
|
+
case 'project':
|
|
77
|
+
if (upperStatus in PROJECT_TRANSITIONS) {
|
|
78
|
+
allowedTransitions = PROJECT_TRANSITIONS[upperStatus as ProjectStatus];
|
|
79
|
+
} else {
|
|
80
|
+
return {
|
|
81
|
+
content: [{
|
|
82
|
+
type: 'text' as const,
|
|
83
|
+
text: JSON.stringify(
|
|
84
|
+
createErrorResponse(
|
|
85
|
+
`Invalid project status: ${currentStatus}`,
|
|
86
|
+
'VALIDATION_ERROR'
|
|
87
|
+
),
|
|
88
|
+
null,
|
|
89
|
+
2
|
|
90
|
+
)
|
|
91
|
+
}]
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
|
|
96
|
+
case 'feature':
|
|
97
|
+
if (upperStatus in FEATURE_TRANSITIONS) {
|
|
98
|
+
allowedTransitions = FEATURE_TRANSITIONS[upperStatus as FeatureStatus];
|
|
99
|
+
} else {
|
|
100
|
+
return {
|
|
101
|
+
content: [{
|
|
102
|
+
type: 'text' as const,
|
|
103
|
+
text: JSON.stringify(
|
|
104
|
+
createErrorResponse(
|
|
105
|
+
`Invalid feature status: ${currentStatus}`,
|
|
106
|
+
'VALIDATION_ERROR'
|
|
107
|
+
),
|
|
108
|
+
null,
|
|
109
|
+
2
|
|
110
|
+
)
|
|
111
|
+
}]
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
|
|
116
|
+
case 'task':
|
|
117
|
+
if (upperStatus in TASK_TRANSITIONS) {
|
|
118
|
+
allowedTransitions = TASK_TRANSITIONS[upperStatus as TaskStatus];
|
|
119
|
+
} else {
|
|
120
|
+
return {
|
|
121
|
+
content: [{
|
|
122
|
+
type: 'text' as const,
|
|
123
|
+
text: JSON.stringify(
|
|
124
|
+
createErrorResponse(
|
|
125
|
+
`Invalid task status: ${currentStatus}`,
|
|
126
|
+
'VALIDATION_ERROR'
|
|
127
|
+
),
|
|
128
|
+
null,
|
|
129
|
+
2
|
|
130
|
+
)
|
|
131
|
+
}]
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
default:
|
|
137
|
+
return {
|
|
138
|
+
content: [{
|
|
139
|
+
type: 'text' as const,
|
|
140
|
+
text: JSON.stringify(
|
|
141
|
+
createErrorResponse(
|
|
142
|
+
`Invalid container type: ${containerType}`,
|
|
143
|
+
'VALIDATION_ERROR'
|
|
144
|
+
),
|
|
145
|
+
null,
|
|
146
|
+
2
|
|
147
|
+
)
|
|
148
|
+
}]
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
content: [{
|
|
154
|
+
type: 'text' as const,
|
|
155
|
+
text: JSON.stringify(
|
|
156
|
+
createSuccessResponse(
|
|
157
|
+
`Found ${allowedTransitions.length} allowed transition(s) from ${upperStatus}`,
|
|
158
|
+
{
|
|
159
|
+
currentStatus: upperStatus,
|
|
160
|
+
allowedTransitions,
|
|
161
|
+
isTerminal: allowedTransitions.length === 0
|
|
162
|
+
}
|
|
163
|
+
),
|
|
164
|
+
null,
|
|
165
|
+
2
|
|
166
|
+
)
|
|
167
|
+
}]
|
|
168
|
+
};
|
|
169
|
+
} catch (error: any) {
|
|
170
|
+
return {
|
|
171
|
+
content: [{
|
|
172
|
+
type: 'text' as const,
|
|
173
|
+
text: JSON.stringify(
|
|
174
|
+
createErrorResponse('Failed to get next status', error.message),
|
|
175
|
+
null,
|
|
176
|
+
2
|
|
177
|
+
)
|
|
178
|
+
}]
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { createSuccessResponse, createErrorResponse, optionalUuidSchema } from './registry';
|
|
4
|
+
import { getNextTask } from '../repos/dependencies';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Register the get_next_task MCP tool.
|
|
8
|
+
*
|
|
9
|
+
* Recommends the next task to work on by priority and complexity,
|
|
10
|
+
* excluding blocked tasks.
|
|
11
|
+
*/
|
|
12
|
+
export function registerGetNextTaskTool(server: McpServer): void {
|
|
13
|
+
server.tool(
|
|
14
|
+
'get_next_task',
|
|
15
|
+
'Get the next recommended task to work on. Returns the highest priority PENDING task that has no incomplete blocking dependencies. Tasks are prioritized by priority (HIGH > MEDIUM > LOW), then by complexity (simpler first), then by creation time.',
|
|
16
|
+
{
|
|
17
|
+
projectId: optionalUuidSchema.describe('Filter by project ID'),
|
|
18
|
+
featureId: optionalUuidSchema.describe('Filter by feature ID'),
|
|
19
|
+
priority: z.string().optional().describe('Filter by priority (HIGH, MEDIUM, LOW)')
|
|
20
|
+
},
|
|
21
|
+
async (params: any) => {
|
|
22
|
+
try {
|
|
23
|
+
const result = getNextTask({
|
|
24
|
+
projectId: params.projectId,
|
|
25
|
+
featureId: params.featureId,
|
|
26
|
+
priority: params.priority
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (!result.success) {
|
|
30
|
+
return {
|
|
31
|
+
content: [{
|
|
32
|
+
type: 'text' as const,
|
|
33
|
+
text: JSON.stringify(createErrorResponse(result.error, result.code), null, 2)
|
|
34
|
+
}]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (result.data === null) {
|
|
39
|
+
return {
|
|
40
|
+
content: [{
|
|
41
|
+
type: 'text' as const,
|
|
42
|
+
text: JSON.stringify(
|
|
43
|
+
createSuccessResponse('No available tasks found matching the criteria', null),
|
|
44
|
+
null,
|
|
45
|
+
2
|
|
46
|
+
)
|
|
47
|
+
}]
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
content: [{
|
|
53
|
+
type: 'text' as const,
|
|
54
|
+
text: JSON.stringify(
|
|
55
|
+
createSuccessResponse('Next task retrieved successfully', result.data),
|
|
56
|
+
null,
|
|
57
|
+
2
|
|
58
|
+
)
|
|
59
|
+
}]
|
|
60
|
+
};
|
|
61
|
+
} catch (error: any) {
|
|
62
|
+
return {
|
|
63
|
+
content: [{
|
|
64
|
+
type: 'text' as const,
|
|
65
|
+
text: JSON.stringify(
|
|
66
|
+
createErrorResponse('Failed to get next task', error.message),
|
|
67
|
+
null,
|
|
68
|
+
2
|
|
69
|
+
)
|
|
70
|
+
}]
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { createSuccessResponse, createErrorResponse } from './registry';
|
|
4
|
+
import { getTagUsage } from '../repos/tags';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Register the get_tag_usage MCP tool
|
|
8
|
+
* Gets all entities that use a specific tag
|
|
9
|
+
*/
|
|
10
|
+
export function registerGetTagUsageTool(server: McpServer): void {
|
|
11
|
+
server.tool(
|
|
12
|
+
'get_tag_usage',
|
|
13
|
+
'Get all entities using a specific tag',
|
|
14
|
+
{
|
|
15
|
+
tag: z.string().describe('Tag to search for')
|
|
16
|
+
},
|
|
17
|
+
async (params) => {
|
|
18
|
+
try {
|
|
19
|
+
const result = getTagUsage(params.tag);
|
|
20
|
+
|
|
21
|
+
if (!result.success) {
|
|
22
|
+
return {
|
|
23
|
+
content: [{
|
|
24
|
+
type: 'text' as const,
|
|
25
|
+
text: JSON.stringify(createErrorResponse(result.error), null, 2)
|
|
26
|
+
}]
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
content: [{
|
|
32
|
+
type: 'text' as const,
|
|
33
|
+
text: JSON.stringify(
|
|
34
|
+
createSuccessResponse('Tag usage retrieved successfully', result.data),
|
|
35
|
+
null,
|
|
36
|
+
2
|
|
37
|
+
)
|
|
38
|
+
}]
|
|
39
|
+
};
|
|
40
|
+
} catch (error: any) {
|
|
41
|
+
return {
|
|
42
|
+
content: [{
|
|
43
|
+
type: 'text' as const,
|
|
44
|
+
text: JSON.stringify(
|
|
45
|
+
createErrorResponse(error.message || 'Failed to get tag usage'),
|
|
46
|
+
null,
|
|
47
|
+
2
|
|
48
|
+
)
|
|
49
|
+
}]
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Container tools
|
|
2
|
+
export { registerQueryContainerTool } from './query-container';
|
|
3
|
+
export { registerManageContainerTool } from './manage-container';
|
|
4
|
+
|
|
5
|
+
// Section tools
|
|
6
|
+
export { registerQuerySectionsTool } from './query-sections';
|
|
7
|
+
export { registerManageSectionsTool } from './manage-sections';
|
|
8
|
+
|
|
9
|
+
// Template tools
|
|
10
|
+
export { registerQueryTemplatesTool } from './query-templates';
|
|
11
|
+
export { registerManageTemplateTool } from './manage-template';
|
|
12
|
+
export { registerApplyTemplateTool } from './apply-template';
|
|
13
|
+
|
|
14
|
+
// Dependency tools
|
|
15
|
+
export { registerQueryDependenciesTool } from './query-dependencies';
|
|
16
|
+
export { registerManageDependencyTool } from './manage-dependency';
|
|
17
|
+
|
|
18
|
+
// Tag tools
|
|
19
|
+
export { registerListTagsTool } from './list-tags';
|
|
20
|
+
export { registerGetTagUsageTool } from './get-tag-usage';
|
|
21
|
+
export { registerRenameTagTool } from './rename-tag';
|
|
22
|
+
|
|
23
|
+
// Workflow tools
|
|
24
|
+
export { registerGetNextTaskTool } from './get-next-task';
|
|
25
|
+
export { registerGetBlockedTasksTool } from './get-blocked-tasks';
|
|
26
|
+
export { registerGetNextStatusTool } from './get-next-status';
|
|
27
|
+
export { registerQueryWorkflowStateTool } from './query-workflow-state';
|
|
28
|
+
|
|
29
|
+
// Setup tool
|
|
30
|
+
export { registerSetupProjectTool } from './setup-project';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { createSuccessResponse, createErrorResponse } from './registry';
|
|
4
|
+
import { listTags } from '../repos/tags';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Register the list_tags MCP tool
|
|
8
|
+
* Lists all tags with their usage counts, optionally filtered by entity type
|
|
9
|
+
*/
|
|
10
|
+
export function registerListTagsTool(server: McpServer): void {
|
|
11
|
+
server.tool(
|
|
12
|
+
'list_tags',
|
|
13
|
+
'List all tags with usage counts',
|
|
14
|
+
{
|
|
15
|
+
entityType: z.string().optional().describe('Filter by entity type (PROJECT, FEATURE, TASK)')
|
|
16
|
+
},
|
|
17
|
+
async (params) => {
|
|
18
|
+
try {
|
|
19
|
+
const result = listTags(
|
|
20
|
+
params.entityType ? { entityType: params.entityType } : undefined
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
if (!result.success) {
|
|
24
|
+
return {
|
|
25
|
+
content: [{
|
|
26
|
+
type: 'text' as const,
|
|
27
|
+
text: JSON.stringify(createErrorResponse(result.error), null, 2)
|
|
28
|
+
}]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
content: [{
|
|
34
|
+
type: 'text' as const,
|
|
35
|
+
text: JSON.stringify(
|
|
36
|
+
createSuccessResponse('Tags listed successfully', result.data),
|
|
37
|
+
null,
|
|
38
|
+
2
|
|
39
|
+
)
|
|
40
|
+
}]
|
|
41
|
+
};
|
|
42
|
+
} catch (error: any) {
|
|
43
|
+
return {
|
|
44
|
+
content: [{
|
|
45
|
+
type: 'text' as const,
|
|
46
|
+
text: JSON.stringify(
|
|
47
|
+
createErrorResponse(error.message || 'Failed to list tags'),
|
|
48
|
+
null,
|
|
49
|
+
2
|
|
50
|
+
)
|
|
51
|
+
}]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
}
|