@prmichaelsen/task-mcp 0.2.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/.env.example +19 -0
- package/AGENT.md +1165 -0
- package/CHANGELOG.md +72 -0
- package/agent/commands/acp.commit.md +511 -0
- package/agent/commands/acp.init.md +376 -0
- package/agent/commands/acp.package-install.md +347 -0
- package/agent/commands/acp.proceed.md +311 -0
- package/agent/commands/acp.report.md +392 -0
- package/agent/commands/acp.status.md +280 -0
- package/agent/commands/acp.sync.md +323 -0
- package/agent/commands/acp.update.md +301 -0
- package/agent/commands/acp.validate.md +385 -0
- package/agent/commands/acp.version-check-for-updates.md +275 -0
- package/agent/commands/acp.version-check.md +190 -0
- package/agent/commands/acp.version-update.md +288 -0
- package/agent/commands/command.template.md +273 -0
- package/agent/commands/git.commit.md +511 -0
- package/agent/commands/git.init.md +513 -0
- package/agent/design/.gitkeep +0 -0
- package/agent/design/acp-task-execution-requirements.md +555 -0
- package/agent/design/api-dto-design.md +394 -0
- package/agent/design/code-extraction-guide.md +827 -0
- package/agent/design/design.template.md +136 -0
- package/agent/design/requirements.template.md +387 -0
- package/agent/design/rest-api-integration.md +489 -0
- package/agent/design/sdk-export-requirements.md +549 -0
- package/agent/milestones/.gitkeep +0 -0
- package/agent/milestones/milestone-1-{title}.template.md +206 -0
- package/agent/milestones/milestone-2-task-infrastructure.md +232 -0
- package/agent/milestones/milestone-4-autonomous-execution.md +235 -0
- package/agent/patterns/.gitkeep +0 -0
- package/agent/patterns/bootstrap.md +1271 -0
- package/agent/patterns/bootstrap.template.md +1237 -0
- package/agent/patterns/pattern.template.md +364 -0
- package/agent/progress.template.yaml +158 -0
- package/agent/progress.yaml +375 -0
- package/agent/scripts/check-for-updates.sh +88 -0
- package/agent/scripts/install.sh +157 -0
- package/agent/scripts/uninstall.sh +75 -0
- package/agent/scripts/update.sh +139 -0
- package/agent/scripts/version.sh +35 -0
- package/agent/tasks/.gitkeep +0 -0
- package/agent/tasks/task-1-{title}.template.md +225 -0
- package/agent/tasks/task-86-task-data-model-schemas.md +143 -0
- package/agent/tasks/task-87-task-database-service.md +220 -0
- package/agent/tasks/task-88-firebase-client-wrapper.md +139 -0
- package/agent/tasks/task-88-task-execution-engine.md +277 -0
- package/agent/tasks/task-89-mcp-server-implementation.md +197 -0
- package/agent/tasks/task-90-build-configuration.md +146 -0
- package/agent/tasks/task-91-deployment-configuration.md +128 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +191 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +191 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/src/client.ts.html +1030 -0
- package/coverage/lcov-report/src/constant/collections.ts.html +469 -0
- package/coverage/lcov-report/src/constant/index.html +116 -0
- package/coverage/lcov-report/src/dto/index.html +116 -0
- package/coverage/lcov-report/src/dto/transformers.ts.html +568 -0
- package/coverage/lcov-report/src/index.html +146 -0
- package/coverage/lcov-report/src/schemas/index.html +116 -0
- package/coverage/lcov-report/src/schemas/task.ts.html +547 -0
- package/coverage/lcov-report/src/server-factory.ts.html +418 -0
- package/coverage/lcov-report/src/server.ts.html +289 -0
- package/coverage/lcov-report/src/services/index.html +116 -0
- package/coverage/lcov-report/src/services/task-database.service.ts.html +1495 -0
- package/coverage/lcov-report/src/tools/index.html +236 -0
- package/coverage/lcov-report/src/tools/index.ts.html +292 -0
- package/coverage/lcov-report/src/tools/task-add-message.ts.html +277 -0
- package/coverage/lcov-report/src/tools/task-complete-task-item.ts.html +343 -0
- package/coverage/lcov-report/src/tools/task-create-milestone.ts.html +286 -0
- package/coverage/lcov-report/src/tools/task-create-task-item.ts.html +358 -0
- package/coverage/lcov-report/src/tools/task-get-next-step.ts.html +460 -0
- package/coverage/lcov-report/src/tools/task-get-status.ts.html +316 -0
- package/coverage/lcov-report/src/tools/task-report-completion.ts.html +343 -0
- package/coverage/lcov-report/src/tools/task-update-progress.ts.html +232 -0
- package/coverage/lcov.info +974 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/src/client.ts.html +1030 -0
- package/coverage/src/constant/collections.ts.html +469 -0
- package/coverage/src/constant/index.html +116 -0
- package/coverage/src/dto/index.html +116 -0
- package/coverage/src/dto/transformers.ts.html +568 -0
- package/coverage/src/index.html +146 -0
- package/coverage/src/schemas/index.html +116 -0
- package/coverage/src/schemas/task.ts.html +547 -0
- package/coverage/src/server-factory.ts.html +418 -0
- package/coverage/src/server.ts.html +289 -0
- package/coverage/src/services/index.html +116 -0
- package/coverage/src/services/task-database.service.ts.html +1495 -0
- package/coverage/src/tools/index.html +236 -0
- package/coverage/src/tools/index.ts.html +292 -0
- package/coverage/src/tools/task-add-message.ts.html +277 -0
- package/coverage/src/tools/task-complete-task-item.ts.html +343 -0
- package/coverage/src/tools/task-create-milestone.ts.html +286 -0
- package/coverage/src/tools/task-create-task-item.ts.html +358 -0
- package/coverage/src/tools/task-get-next-step.ts.html +460 -0
- package/coverage/src/tools/task-get-status.ts.html +316 -0
- package/coverage/src/tools/task-report-completion.ts.html +343 -0
- package/coverage/src/tools/task-update-progress.ts.html +232 -0
- package/firestore.rules +95 -0
- package/jest.config.js +31 -0
- package/package.json +67 -0
- package/src/client.spec.ts +199 -0
- package/src/client.ts +315 -0
- package/src/constant/collections.ts +128 -0
- package/src/dto/index.ts +47 -0
- package/src/dto/task-api.dto.ts +219 -0
- package/src/dto/transformers.spec.ts +462 -0
- package/src/dto/transformers.ts +161 -0
- package/src/schemas/task.ts +154 -0
- package/src/server-factory.spec.ts +70 -0
- package/src/server-factory.ts +111 -0
- package/src/server.ts +68 -0
- package/src/services/task-database.service.e2e.ts +116 -0
- package/src/services/task-database.service.spec.ts +479 -0
- package/src/services/task-database.service.ts +470 -0
- package/src/test-schemas.ts +161 -0
- package/src/tools/index.ts +69 -0
- package/src/tools/task-add-message.ts +64 -0
- package/src/tools/task-complete-task-item.ts +86 -0
- package/src/tools/task-create-milestone.ts +67 -0
- package/src/tools/task-create-task-item.ts +91 -0
- package/src/tools/task-get-next-step.spec.ts +136 -0
- package/src/tools/task-get-next-step.ts +125 -0
- package/src/tools/task-get-status.spec.ts +213 -0
- package/src/tools/task-get-status.ts +77 -0
- package/src/tools/task-report-completion.ts +86 -0
- package/src/tools/task-update-progress.ts +49 -0
- package/src/tools/tools.spec.ts +194 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool: task_add_message
|
|
3
|
+
*
|
|
4
|
+
* Add a message to the task conversation thread.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FirebaseClient } from '@/client.js'
|
|
8
|
+
|
|
9
|
+
export const taskAddMessageTool = {
|
|
10
|
+
name: 'task_add_message',
|
|
11
|
+
description: 'Add a message to the task conversation thread',
|
|
12
|
+
inputSchema: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
task_id: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'Task ID'
|
|
18
|
+
},
|
|
19
|
+
role: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
enum: ['user', 'assistant', 'system'],
|
|
22
|
+
description: 'Message role'
|
|
23
|
+
},
|
|
24
|
+
content: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Message content'
|
|
27
|
+
},
|
|
28
|
+
metadata: {
|
|
29
|
+
type: 'object',
|
|
30
|
+
description: 'Optional metadata (JSON object)'
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
required: ['task_id', 'role', 'content']
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function handleTaskAddMessage(
|
|
38
|
+
client: FirebaseClient,
|
|
39
|
+
args: {
|
|
40
|
+
task_id: string
|
|
41
|
+
role: 'user' | 'assistant' | 'system'
|
|
42
|
+
content: string
|
|
43
|
+
metadata?: any
|
|
44
|
+
}
|
|
45
|
+
): Promise<string> {
|
|
46
|
+
try {
|
|
47
|
+
const messageId = await client.addMessage(
|
|
48
|
+
args.task_id,
|
|
49
|
+
args.role,
|
|
50
|
+
args.content,
|
|
51
|
+
args.metadata
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return JSON.stringify({
|
|
55
|
+
success: true,
|
|
56
|
+
task_id: args.task_id,
|
|
57
|
+
message_id: messageId,
|
|
58
|
+
role: args.role,
|
|
59
|
+
message: 'Message added to task thread'
|
|
60
|
+
}, null, 2)
|
|
61
|
+
} catch (error) {
|
|
62
|
+
throw new Error(`Failed to add message: ${error instanceof Error ? error.message : String(error)}`)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool: task_complete_task_item
|
|
3
|
+
*
|
|
4
|
+
* Mark a task item as complete and update milestone progress.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FirebaseClient } from '@/client.js'
|
|
8
|
+
|
|
9
|
+
export const taskCompleteTaskItemTool = {
|
|
10
|
+
name: 'task_complete_task_item',
|
|
11
|
+
description: 'Mark a task item as complete',
|
|
12
|
+
inputSchema: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
task_id: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: 'Task ID'
|
|
18
|
+
},
|
|
19
|
+
milestone_id: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'Milestone ID'
|
|
22
|
+
},
|
|
23
|
+
task_item_id: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Task item ID to complete'
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
required: ['task_id', 'milestone_id', 'task_item_id']
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function handleTaskCompleteTaskItem(
|
|
33
|
+
client: FirebaseClient,
|
|
34
|
+
args: { task_id: string; milestone_id: string; task_item_id: string }
|
|
35
|
+
): Promise<string> {
|
|
36
|
+
try {
|
|
37
|
+
// Complete the task item
|
|
38
|
+
await client.completeTaskItem(args.task_id, args.milestone_id, args.task_item_id)
|
|
39
|
+
|
|
40
|
+
// Get updated task to calculate new progress
|
|
41
|
+
const task = await client.getTask(args.task_id)
|
|
42
|
+
|
|
43
|
+
if (!task) {
|
|
44
|
+
throw new Error('Task not found after update')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Find the milestone
|
|
48
|
+
const milestone = task.progress.milestones.find(m => m.id === args.milestone_id)
|
|
49
|
+
const milestoneItems = task.progress.tasks[args.milestone_id] || []
|
|
50
|
+
const completedCount = milestoneItems.filter(item => item.status === 'completed').length
|
|
51
|
+
|
|
52
|
+
// Update milestone progress
|
|
53
|
+
const milestoneProgress = milestone ? Math.round((completedCount / milestoneItems.length) * 100) : 0
|
|
54
|
+
|
|
55
|
+
if (milestone && milestone.progress !== milestoneProgress) {
|
|
56
|
+
await client.updateMilestone(args.task_id, args.milestone_id, {
|
|
57
|
+
progress: milestoneProgress,
|
|
58
|
+
tasks_completed: completedCount,
|
|
59
|
+
tasks_total: milestoneItems.length
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Find next task
|
|
64
|
+
const nextTask = milestoneItems.find(
|
|
65
|
+
item => item.status === 'not_started' || item.status === 'in_progress'
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return JSON.stringify({
|
|
69
|
+
success: true,
|
|
70
|
+
task_id: args.task_id,
|
|
71
|
+
completed_task: args.task_item_id,
|
|
72
|
+
milestone_progress: milestoneProgress,
|
|
73
|
+
milestone_tasks_completed: completedCount,
|
|
74
|
+
milestone_tasks_total: milestoneItems.length,
|
|
75
|
+
next_task: nextTask ? {
|
|
76
|
+
id: nextTask.id,
|
|
77
|
+
name: nextTask.name
|
|
78
|
+
} : null,
|
|
79
|
+
message: nextTask
|
|
80
|
+
? `Task item completed. Next: ${nextTask.name}`
|
|
81
|
+
: 'Task item completed. Milestone complete!'
|
|
82
|
+
}, null, 2)
|
|
83
|
+
} catch (error) {
|
|
84
|
+
throw new Error(`Failed to complete task item: ${error instanceof Error ? error.message : String(error)}`)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool: task_create_milestone
|
|
3
|
+
*
|
|
4
|
+
* Create a new milestone in a task.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FirebaseClient } from '@/client.js'
|
|
8
|
+
import type { Milestone } from '@/schemas/task.js'
|
|
9
|
+
|
|
10
|
+
export const taskCreateMilestoneTool = {
|
|
11
|
+
name: 'task_create_milestone',
|
|
12
|
+
description: 'Create a new milestone in a task',
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
task_id: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
description: 'Task ID'
|
|
19
|
+
},
|
|
20
|
+
milestone_id: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'Unique milestone ID (e.g., "milestone-1")'
|
|
23
|
+
},
|
|
24
|
+
name: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Milestone name'
|
|
27
|
+
},
|
|
28
|
+
description: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'Milestone description'
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
required: ['task_id', 'milestone_id', 'name', 'description']
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function handleTaskCreateMilestone(
|
|
38
|
+
client: FirebaseClient,
|
|
39
|
+
args: { task_id: string; milestone_id: string; name: string; description: string }
|
|
40
|
+
): Promise<string> {
|
|
41
|
+
try {
|
|
42
|
+
const milestone: Milestone = {
|
|
43
|
+
id: args.milestone_id,
|
|
44
|
+
name: args.name,
|
|
45
|
+
description: args.description,
|
|
46
|
+
status: 'not_started',
|
|
47
|
+
progress: 0,
|
|
48
|
+
tasks_completed: 0,
|
|
49
|
+
tasks_total: 0
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await client.createMilestone(args.task_id, milestone)
|
|
53
|
+
|
|
54
|
+
return JSON.stringify({
|
|
55
|
+
success: true,
|
|
56
|
+
task_id: args.task_id,
|
|
57
|
+
milestone: {
|
|
58
|
+
id: milestone.id,
|
|
59
|
+
name: milestone.name,
|
|
60
|
+
description: milestone.description
|
|
61
|
+
},
|
|
62
|
+
message: `Milestone "${milestone.name}" created successfully`
|
|
63
|
+
}, null, 2)
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw new Error(`Failed to create milestone: ${error instanceof Error ? error.message : String(error)}`)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool: task_create_task_item
|
|
3
|
+
*
|
|
4
|
+
* Create a new task item within a milestone.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FirebaseClient } from '@/client.js'
|
|
8
|
+
import type { TaskItem } from '@/schemas/task.js'
|
|
9
|
+
|
|
10
|
+
export const taskCreateTaskItemTool = {
|
|
11
|
+
name: 'task_create_task_item',
|
|
12
|
+
description: 'Create a new task item within a milestone',
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
task_id: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
description: 'Task ID'
|
|
19
|
+
},
|
|
20
|
+
milestone_id: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'Milestone ID'
|
|
23
|
+
},
|
|
24
|
+
task_item_id: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Unique task item ID (e.g., "task-1")'
|
|
27
|
+
},
|
|
28
|
+
name: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'Task item name'
|
|
31
|
+
},
|
|
32
|
+
description: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: 'Task item description'
|
|
35
|
+
},
|
|
36
|
+
estimated_hours: {
|
|
37
|
+
type: 'number',
|
|
38
|
+
description: 'Estimated hours to complete (optional)'
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
required: ['task_id', 'milestone_id', 'task_item_id', 'name', 'description']
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function handleTaskCreateTaskItem(
|
|
46
|
+
client: FirebaseClient,
|
|
47
|
+
args: {
|
|
48
|
+
task_id: string
|
|
49
|
+
milestone_id: string
|
|
50
|
+
task_item_id: string
|
|
51
|
+
name: string
|
|
52
|
+
description: string
|
|
53
|
+
estimated_hours?: number
|
|
54
|
+
}
|
|
55
|
+
): Promise<string> {
|
|
56
|
+
try {
|
|
57
|
+
const taskItem: TaskItem = {
|
|
58
|
+
id: args.task_item_id,
|
|
59
|
+
name: args.name,
|
|
60
|
+
description: args.description,
|
|
61
|
+
status: 'not_started',
|
|
62
|
+
estimated_hours: args.estimated_hours
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await client.createTaskItem(args.task_id, args.milestone_id, taskItem)
|
|
66
|
+
|
|
67
|
+
// Update milestone task count
|
|
68
|
+
const task = await client.getTask(args.task_id)
|
|
69
|
+
if (task) {
|
|
70
|
+
const milestoneItems = task.progress.tasks[args.milestone_id] || []
|
|
71
|
+
await client.updateMilestone(args.task_id, args.milestone_id, {
|
|
72
|
+
tasks_total: milestoneItems.length
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return JSON.stringify({
|
|
77
|
+
success: true,
|
|
78
|
+
task_id: args.task_id,
|
|
79
|
+
milestone_id: args.milestone_id,
|
|
80
|
+
task_item: {
|
|
81
|
+
id: taskItem.id,
|
|
82
|
+
name: taskItem.name,
|
|
83
|
+
description: taskItem.description,
|
|
84
|
+
estimated_hours: taskItem.estimated_hours
|
|
85
|
+
},
|
|
86
|
+
message: `Task item "${taskItem.name}" created in milestone`
|
|
87
|
+
}, null, 2)
|
|
88
|
+
} catch (error) {
|
|
89
|
+
throw new Error(`Failed to create task item: ${error instanceof Error ? error.message : String(error)}`)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for task_get_next_step tool
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { handleTaskGetNextStep, taskGetNextStepTool } from './task-get-next-step.js'
|
|
6
|
+
import { FirebaseClient } from '@/client.js'
|
|
7
|
+
import type { Task } from '@/schemas/task.js'
|
|
8
|
+
|
|
9
|
+
jest.mock('@/client.js')
|
|
10
|
+
|
|
11
|
+
describe('task_get_next_step', () => {
|
|
12
|
+
let mockClient: jest.Mocked<FirebaseClient>
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
mockClient = {
|
|
16
|
+
getTask: jest.fn()
|
|
17
|
+
} as any
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('Tool Definition', () => {
|
|
21
|
+
it('should have correct name', () => {
|
|
22
|
+
expect(taskGetNextStepTool.name).toBe('task_get_next_step')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('should require task_id parameter', () => {
|
|
26
|
+
expect(taskGetNextStepTool.inputSchema.required).toContain('task_id')
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('Handler', () => {
|
|
31
|
+
it('should return paused status for paused task', async () => {
|
|
32
|
+
const mockTask: Task = {
|
|
33
|
+
id: 'task-123',
|
|
34
|
+
user_id: 'user-456',
|
|
35
|
+
title: 'Test Task',
|
|
36
|
+
description: 'Test',
|
|
37
|
+
status: 'paused',
|
|
38
|
+
created_at: '2026-02-16T00:00:00Z',
|
|
39
|
+
updated_at: '2026-02-16T00:00:00Z',
|
|
40
|
+
progress: {
|
|
41
|
+
current_milestone: '',
|
|
42
|
+
current_task: '',
|
|
43
|
+
overall_percentage: 0,
|
|
44
|
+
milestones: [],
|
|
45
|
+
tasks: {}
|
|
46
|
+
},
|
|
47
|
+
execution: { api_messages: [], task_messages: [], tool_results: [] },
|
|
48
|
+
config: { system_prompt: '', auto_approve: true },
|
|
49
|
+
metadata: undefined
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
mockClient.getTask.mockResolvedValue(mockTask)
|
|
53
|
+
|
|
54
|
+
const result = await handleTaskGetNextStep(mockClient, { task_id: 'task-123' })
|
|
55
|
+
const parsed = JSON.parse(result)
|
|
56
|
+
|
|
57
|
+
expect(parsed.status).toBe('paused')
|
|
58
|
+
expect(parsed.instructions).toBeNull()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('should return no_milestone status when no milestones exist', async () => {
|
|
62
|
+
const mockTask: Task = {
|
|
63
|
+
id: 'task-123',
|
|
64
|
+
user_id: 'user-456',
|
|
65
|
+
title: 'Test Task',
|
|
66
|
+
description: 'Test',
|
|
67
|
+
status: 'in_progress',
|
|
68
|
+
created_at: '2026-02-16T00:00:00Z',
|
|
69
|
+
updated_at: '2026-02-16T00:00:00Z',
|
|
70
|
+
progress: {
|
|
71
|
+
current_milestone: '',
|
|
72
|
+
current_task: '',
|
|
73
|
+
overall_percentage: 0,
|
|
74
|
+
milestones: [],
|
|
75
|
+
tasks: {}
|
|
76
|
+
},
|
|
77
|
+
execution: { api_messages: [], task_messages: [], tool_results: [] },
|
|
78
|
+
config: { system_prompt: '', auto_approve: true },
|
|
79
|
+
metadata: undefined
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
mockClient.getTask.mockResolvedValue(mockTask)
|
|
83
|
+
|
|
84
|
+
const result = await handleTaskGetNextStep(mockClient, { task_id: 'task-123' })
|
|
85
|
+
const parsed = JSON.parse(result)
|
|
86
|
+
|
|
87
|
+
expect(parsed.status).toBe('no_milestone')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('should return current task instructions', async () => {
|
|
91
|
+
const mockTask: Task = {
|
|
92
|
+
id: 'task-123',
|
|
93
|
+
user_id: 'user-456',
|
|
94
|
+
title: 'Test Task',
|
|
95
|
+
description: 'Test',
|
|
96
|
+
status: 'in_progress',
|
|
97
|
+
created_at: '2026-02-16T00:00:00Z',
|
|
98
|
+
updated_at: '2026-02-16T00:00:00Z',
|
|
99
|
+
progress: {
|
|
100
|
+
current_milestone: 'milestone-1',
|
|
101
|
+
current_task: 'task-1',
|
|
102
|
+
overall_percentage: 50,
|
|
103
|
+
milestones: [{
|
|
104
|
+
id: 'milestone-1',
|
|
105
|
+
name: 'Milestone 1',
|
|
106
|
+
description: 'First milestone',
|
|
107
|
+
status: 'in_progress',
|
|
108
|
+
progress: 50,
|
|
109
|
+
tasks_completed: 0,
|
|
110
|
+
tasks_total: 2
|
|
111
|
+
}],
|
|
112
|
+
tasks: {
|
|
113
|
+
'milestone-1': [{
|
|
114
|
+
id: 'task-1',
|
|
115
|
+
name: 'Task 1',
|
|
116
|
+
description: 'First task',
|
|
117
|
+
status: 'in_progress'
|
|
118
|
+
}]
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
execution: { api_messages: [], task_messages: [], tool_results: [] },
|
|
122
|
+
config: { system_prompt: '', auto_approve: true },
|
|
123
|
+
metadata: undefined
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
mockClient.getTask.mockResolvedValue(mockTask)
|
|
127
|
+
|
|
128
|
+
const result = await handleTaskGetNextStep(mockClient, { task_id: 'task-123' })
|
|
129
|
+
const parsed = JSON.parse(result)
|
|
130
|
+
|
|
131
|
+
expect(parsed.status).toBe('in_progress')
|
|
132
|
+
expect(parsed.current_task.name).toBe('Task 1')
|
|
133
|
+
expect(parsed.instructions).toContain('Task 1')
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
})
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool: task_get_next_step
|
|
3
|
+
*
|
|
4
|
+
* Get instructions for the next step in the current task.
|
|
5
|
+
* Returns the current task item with steps and verification criteria.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { FirebaseClient } from '@/client.js'
|
|
9
|
+
|
|
10
|
+
export const taskGetNextStepTool = {
|
|
11
|
+
name: 'task_get_next_step',
|
|
12
|
+
description: 'Get instructions for the next step in the current task',
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
task_id: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
description: 'Task ID'
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
required: ['task_id']
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function handleTaskGetNextStep(
|
|
26
|
+
client: FirebaseClient,
|
|
27
|
+
args: { task_id: string }
|
|
28
|
+
): Promise<string> {
|
|
29
|
+
try {
|
|
30
|
+
const task = await client.getTask(args.task_id)
|
|
31
|
+
|
|
32
|
+
if (!task) {
|
|
33
|
+
throw new Error(`Task not found: ${args.task_id}`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check if task is paused or completed
|
|
37
|
+
if (task.status === 'paused') {
|
|
38
|
+
return JSON.stringify({
|
|
39
|
+
status: 'paused',
|
|
40
|
+
message: 'Task is paused. Resume the task to continue.',
|
|
41
|
+
instructions: null
|
|
42
|
+
}, null, 2)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (task.status === 'completed') {
|
|
46
|
+
return JSON.stringify({
|
|
47
|
+
status: 'completed',
|
|
48
|
+
message: 'Task is already completed.',
|
|
49
|
+
instructions: null
|
|
50
|
+
}, null, 2)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Find current milestone
|
|
54
|
+
const currentMilestone = task.progress.milestones.find(
|
|
55
|
+
m => m.id === task.progress.current_milestone
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if (!currentMilestone) {
|
|
59
|
+
return JSON.stringify({
|
|
60
|
+
status: 'no_milestone',
|
|
61
|
+
message: 'No current milestone. Create milestones to begin work.',
|
|
62
|
+
instructions: 'Use task_create_milestone to add milestones to this task.'
|
|
63
|
+
}, null, 2)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Find current task item
|
|
67
|
+
const milestoneItems = task.progress.tasks[task.progress.current_milestone] || []
|
|
68
|
+
const currentTaskItem = milestoneItems.find(
|
|
69
|
+
item => item.id === task.progress.current_task
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if (!currentTaskItem) {
|
|
73
|
+
// Find next not_started or in_progress task
|
|
74
|
+
const nextTask = milestoneItems.find(
|
|
75
|
+
item => item.status === 'not_started' || item.status === 'in_progress'
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if (!nextTask) {
|
|
79
|
+
return JSON.stringify({
|
|
80
|
+
status: 'milestone_complete',
|
|
81
|
+
message: `Milestone "${currentMilestone.name}" is complete.`,
|
|
82
|
+
instructions: 'Move to the next milestone or complete the task.',
|
|
83
|
+
current_milestone: currentMilestone.name
|
|
84
|
+
}, null, 2)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return JSON.stringify({
|
|
88
|
+
status: 'ready',
|
|
89
|
+
current_milestone: {
|
|
90
|
+
id: currentMilestone.id,
|
|
91
|
+
name: currentMilestone.name
|
|
92
|
+
},
|
|
93
|
+
next_task: {
|
|
94
|
+
id: nextTask.id,
|
|
95
|
+
name: nextTask.name,
|
|
96
|
+
description: nextTask.description,
|
|
97
|
+
status: nextTask.status,
|
|
98
|
+
estimated_hours: nextTask.estimated_hours
|
|
99
|
+
},
|
|
100
|
+
instructions: `Begin work on: ${nextTask.name}\n\nDescription: ${nextTask.description}`
|
|
101
|
+
}, null, 2)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Return current task item details
|
|
105
|
+
return JSON.stringify({
|
|
106
|
+
status: 'in_progress',
|
|
107
|
+
current_milestone: {
|
|
108
|
+
id: currentMilestone.id,
|
|
109
|
+
name: currentMilestone.name,
|
|
110
|
+
progress: currentMilestone.progress
|
|
111
|
+
},
|
|
112
|
+
current_task: {
|
|
113
|
+
id: currentTaskItem.id,
|
|
114
|
+
name: currentTaskItem.name,
|
|
115
|
+
description: currentTaskItem.description,
|
|
116
|
+
status: currentTaskItem.status,
|
|
117
|
+
estimated_hours: currentTaskItem.estimated_hours,
|
|
118
|
+
notes: currentTaskItem.notes
|
|
119
|
+
},
|
|
120
|
+
instructions: `Continue work on: ${currentTaskItem.name}\n\nDescription: ${currentTaskItem.description}${currentTaskItem.notes ? `\n\nNotes: ${currentTaskItem.notes}` : ''}`
|
|
121
|
+
}, null, 2)
|
|
122
|
+
} catch (error) {
|
|
123
|
+
throw new Error(`Failed to get next step: ${error instanceof Error ? error.message : String(error)}`)
|
|
124
|
+
}
|
|
125
|
+
}
|