@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.
Files changed (142) hide show
  1. package/.env.example +19 -0
  2. package/AGENT.md +1165 -0
  3. package/CHANGELOG.md +72 -0
  4. package/agent/commands/acp.commit.md +511 -0
  5. package/agent/commands/acp.init.md +376 -0
  6. package/agent/commands/acp.package-install.md +347 -0
  7. package/agent/commands/acp.proceed.md +311 -0
  8. package/agent/commands/acp.report.md +392 -0
  9. package/agent/commands/acp.status.md +280 -0
  10. package/agent/commands/acp.sync.md +323 -0
  11. package/agent/commands/acp.update.md +301 -0
  12. package/agent/commands/acp.validate.md +385 -0
  13. package/agent/commands/acp.version-check-for-updates.md +275 -0
  14. package/agent/commands/acp.version-check.md +190 -0
  15. package/agent/commands/acp.version-update.md +288 -0
  16. package/agent/commands/command.template.md +273 -0
  17. package/agent/commands/git.commit.md +511 -0
  18. package/agent/commands/git.init.md +513 -0
  19. package/agent/design/.gitkeep +0 -0
  20. package/agent/design/acp-task-execution-requirements.md +555 -0
  21. package/agent/design/api-dto-design.md +394 -0
  22. package/agent/design/code-extraction-guide.md +827 -0
  23. package/agent/design/design.template.md +136 -0
  24. package/agent/design/requirements.template.md +387 -0
  25. package/agent/design/rest-api-integration.md +489 -0
  26. package/agent/design/sdk-export-requirements.md +549 -0
  27. package/agent/milestones/.gitkeep +0 -0
  28. package/agent/milestones/milestone-1-{title}.template.md +206 -0
  29. package/agent/milestones/milestone-2-task-infrastructure.md +232 -0
  30. package/agent/milestones/milestone-4-autonomous-execution.md +235 -0
  31. package/agent/patterns/.gitkeep +0 -0
  32. package/agent/patterns/bootstrap.md +1271 -0
  33. package/agent/patterns/bootstrap.template.md +1237 -0
  34. package/agent/patterns/pattern.template.md +364 -0
  35. package/agent/progress.template.yaml +158 -0
  36. package/agent/progress.yaml +375 -0
  37. package/agent/scripts/check-for-updates.sh +88 -0
  38. package/agent/scripts/install.sh +157 -0
  39. package/agent/scripts/uninstall.sh +75 -0
  40. package/agent/scripts/update.sh +139 -0
  41. package/agent/scripts/version.sh +35 -0
  42. package/agent/tasks/.gitkeep +0 -0
  43. package/agent/tasks/task-1-{title}.template.md +225 -0
  44. package/agent/tasks/task-86-task-data-model-schemas.md +143 -0
  45. package/agent/tasks/task-87-task-database-service.md +220 -0
  46. package/agent/tasks/task-88-firebase-client-wrapper.md +139 -0
  47. package/agent/tasks/task-88-task-execution-engine.md +277 -0
  48. package/agent/tasks/task-89-mcp-server-implementation.md +197 -0
  49. package/agent/tasks/task-90-build-configuration.md +146 -0
  50. package/agent/tasks/task-91-deployment-configuration.md +128 -0
  51. package/coverage/base.css +224 -0
  52. package/coverage/block-navigation.js +87 -0
  53. package/coverage/favicon.png +0 -0
  54. package/coverage/index.html +191 -0
  55. package/coverage/lcov-report/base.css +224 -0
  56. package/coverage/lcov-report/block-navigation.js +87 -0
  57. package/coverage/lcov-report/favicon.png +0 -0
  58. package/coverage/lcov-report/index.html +191 -0
  59. package/coverage/lcov-report/prettify.css +1 -0
  60. package/coverage/lcov-report/prettify.js +2 -0
  61. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  62. package/coverage/lcov-report/sorter.js +210 -0
  63. package/coverage/lcov-report/src/client.ts.html +1030 -0
  64. package/coverage/lcov-report/src/constant/collections.ts.html +469 -0
  65. package/coverage/lcov-report/src/constant/index.html +116 -0
  66. package/coverage/lcov-report/src/dto/index.html +116 -0
  67. package/coverage/lcov-report/src/dto/transformers.ts.html +568 -0
  68. package/coverage/lcov-report/src/index.html +146 -0
  69. package/coverage/lcov-report/src/schemas/index.html +116 -0
  70. package/coverage/lcov-report/src/schemas/task.ts.html +547 -0
  71. package/coverage/lcov-report/src/server-factory.ts.html +418 -0
  72. package/coverage/lcov-report/src/server.ts.html +289 -0
  73. package/coverage/lcov-report/src/services/index.html +116 -0
  74. package/coverage/lcov-report/src/services/task-database.service.ts.html +1495 -0
  75. package/coverage/lcov-report/src/tools/index.html +236 -0
  76. package/coverage/lcov-report/src/tools/index.ts.html +292 -0
  77. package/coverage/lcov-report/src/tools/task-add-message.ts.html +277 -0
  78. package/coverage/lcov-report/src/tools/task-complete-task-item.ts.html +343 -0
  79. package/coverage/lcov-report/src/tools/task-create-milestone.ts.html +286 -0
  80. package/coverage/lcov-report/src/tools/task-create-task-item.ts.html +358 -0
  81. package/coverage/lcov-report/src/tools/task-get-next-step.ts.html +460 -0
  82. package/coverage/lcov-report/src/tools/task-get-status.ts.html +316 -0
  83. package/coverage/lcov-report/src/tools/task-report-completion.ts.html +343 -0
  84. package/coverage/lcov-report/src/tools/task-update-progress.ts.html +232 -0
  85. package/coverage/lcov.info +974 -0
  86. package/coverage/prettify.css +1 -0
  87. package/coverage/prettify.js +2 -0
  88. package/coverage/sort-arrow-sprite.png +0 -0
  89. package/coverage/sorter.js +210 -0
  90. package/coverage/src/client.ts.html +1030 -0
  91. package/coverage/src/constant/collections.ts.html +469 -0
  92. package/coverage/src/constant/index.html +116 -0
  93. package/coverage/src/dto/index.html +116 -0
  94. package/coverage/src/dto/transformers.ts.html +568 -0
  95. package/coverage/src/index.html +146 -0
  96. package/coverage/src/schemas/index.html +116 -0
  97. package/coverage/src/schemas/task.ts.html +547 -0
  98. package/coverage/src/server-factory.ts.html +418 -0
  99. package/coverage/src/server.ts.html +289 -0
  100. package/coverage/src/services/index.html +116 -0
  101. package/coverage/src/services/task-database.service.ts.html +1495 -0
  102. package/coverage/src/tools/index.html +236 -0
  103. package/coverage/src/tools/index.ts.html +292 -0
  104. package/coverage/src/tools/task-add-message.ts.html +277 -0
  105. package/coverage/src/tools/task-complete-task-item.ts.html +343 -0
  106. package/coverage/src/tools/task-create-milestone.ts.html +286 -0
  107. package/coverage/src/tools/task-create-task-item.ts.html +358 -0
  108. package/coverage/src/tools/task-get-next-step.ts.html +460 -0
  109. package/coverage/src/tools/task-get-status.ts.html +316 -0
  110. package/coverage/src/tools/task-report-completion.ts.html +343 -0
  111. package/coverage/src/tools/task-update-progress.ts.html +232 -0
  112. package/firestore.rules +95 -0
  113. package/jest.config.js +31 -0
  114. package/package.json +67 -0
  115. package/src/client.spec.ts +199 -0
  116. package/src/client.ts +315 -0
  117. package/src/constant/collections.ts +128 -0
  118. package/src/dto/index.ts +47 -0
  119. package/src/dto/task-api.dto.ts +219 -0
  120. package/src/dto/transformers.spec.ts +462 -0
  121. package/src/dto/transformers.ts +161 -0
  122. package/src/schemas/task.ts +154 -0
  123. package/src/server-factory.spec.ts +70 -0
  124. package/src/server-factory.ts +111 -0
  125. package/src/server.ts +68 -0
  126. package/src/services/task-database.service.e2e.ts +116 -0
  127. package/src/services/task-database.service.spec.ts +479 -0
  128. package/src/services/task-database.service.ts +470 -0
  129. package/src/test-schemas.ts +161 -0
  130. package/src/tools/index.ts +69 -0
  131. package/src/tools/task-add-message.ts +64 -0
  132. package/src/tools/task-complete-task-item.ts +86 -0
  133. package/src/tools/task-create-milestone.ts +67 -0
  134. package/src/tools/task-create-task-item.ts +91 -0
  135. package/src/tools/task-get-next-step.spec.ts +136 -0
  136. package/src/tools/task-get-next-step.ts +125 -0
  137. package/src/tools/task-get-status.spec.ts +213 -0
  138. package/src/tools/task-get-status.ts +77 -0
  139. package/src/tools/task-report-completion.ts +86 -0
  140. package/src/tools/task-update-progress.ts +49 -0
  141. package/src/tools/tools.spec.ts +194 -0
  142. package/tsconfig.json +31 -0
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Tests for task_get_status tool
3
+ */
4
+
5
+ import { handleTaskGetStatus, taskGetStatusTool } from './task-get-status.js'
6
+ import { FirebaseClient } from '@/client.js'
7
+ import type { Task } from '@/schemas/task.js'
8
+
9
+ // Mock FirebaseClient
10
+ jest.mock('@/client.js')
11
+
12
+ describe('task_get_status', () => {
13
+ let mockClient: jest.Mocked<FirebaseClient>
14
+
15
+ beforeEach(() => {
16
+ mockClient = {
17
+ getTask: jest.fn()
18
+ } as any
19
+ })
20
+
21
+ describe('Tool Definition', () => {
22
+ it('should have correct name', () => {
23
+ expect(taskGetStatusTool.name).toBe('task_get_status')
24
+ })
25
+
26
+ it('should have description', () => {
27
+ expect(taskGetStatusTool.description).toBeTruthy()
28
+ })
29
+
30
+ it('should require task_id parameter', () => {
31
+ expect(taskGetStatusTool.inputSchema.required).toContain('task_id')
32
+ })
33
+ })
34
+
35
+ describe('Handler', () => {
36
+ it('should return task status for valid task', async () => {
37
+ const mockTask: Task = {
38
+ id: 'task-123',
39
+ user_id: 'user-456',
40
+ title: 'Test Task',
41
+ description: 'Test description',
42
+ status: 'in_progress',
43
+ created_at: '2026-02-16T00:00:00Z',
44
+ updated_at: '2026-02-16T00:00:00Z',
45
+ progress: {
46
+ current_milestone: 'milestone-1',
47
+ current_task: 'task-1',
48
+ overall_percentage: 50,
49
+ milestones: [
50
+ {
51
+ id: 'milestone-1',
52
+ name: 'Milestone 1',
53
+ description: 'First milestone',
54
+ status: 'in_progress',
55
+ progress: 50,
56
+ tasks_completed: 1,
57
+ tasks_total: 2
58
+ }
59
+ ],
60
+ tasks: {
61
+ 'milestone-1': [
62
+ {
63
+ id: 'task-1',
64
+ name: 'Task 1',
65
+ description: 'First task',
66
+ status: 'in_progress'
67
+ }
68
+ ]
69
+ }
70
+ },
71
+ execution: {
72
+ api_messages: [],
73
+ task_messages: [],
74
+ tool_results: []
75
+ },
76
+ config: {
77
+
78
+ system_prompt: '',
79
+ auto_approve: true
80
+ },
81
+ metadata: undefined
82
+ }
83
+
84
+ mockClient.getTask.mockResolvedValue(mockTask)
85
+
86
+ const result = await handleTaskGetStatus(mockClient, { task_id: 'task-123' })
87
+ const parsed = JSON.parse(result)
88
+
89
+ expect(parsed.task_id).toBe('task-123')
90
+ expect(parsed.task_title).toBe('Test Task')
91
+ expect(parsed.status).toBe('in_progress')
92
+ expect(parsed.overall_progress).toBe(50)
93
+ expect(parsed.current_milestone).toBeTruthy()
94
+ expect(parsed.current_milestone.name).toBe('Milestone 1')
95
+ expect(parsed.current_task).toBeTruthy()
96
+ expect(parsed.current_task.name).toBe('Task 1')
97
+ })
98
+
99
+ it('should throw error if task not found', async () => {
100
+ mockClient.getTask.mockResolvedValue(null)
101
+
102
+ await expect(
103
+ handleTaskGetStatus(mockClient, { task_id: 'nonexistent' })
104
+ ).rejects.toThrow('Task not found: nonexistent')
105
+ })
106
+
107
+ it('should handle task with no current milestone', async () => {
108
+ const mockTask: Task = {
109
+ id: 'task-123',
110
+ user_id: 'user-456',
111
+ title: 'Test Task',
112
+ description: 'Test description',
113
+ status: 'not_started',
114
+ created_at: '2026-02-16T00:00:00Z',
115
+ updated_at: '2026-02-16T00:00:00Z',
116
+ progress: {
117
+ current_milestone: '',
118
+ current_task: '',
119
+ overall_percentage: 0,
120
+ milestones: [],
121
+ tasks: {}
122
+ },
123
+ execution: {
124
+ api_messages: [],
125
+ task_messages: [],
126
+ tool_results: []
127
+ },
128
+ config: {
129
+
130
+ system_prompt: '',
131
+ auto_approve: true
132
+ },
133
+ metadata: undefined
134
+ }
135
+
136
+ mockClient.getTask.mockResolvedValue(mockTask)
137
+
138
+ const result = await handleTaskGetStatus(mockClient, { task_id: 'task-123' })
139
+ const parsed = JSON.parse(result)
140
+
141
+ expect(parsed.current_milestone).toBeNull()
142
+ expect(parsed.current_task).toBeNull()
143
+ expect(parsed.milestones_summary.total).toBe(0)
144
+ })
145
+
146
+ it('should include milestones summary', async () => {
147
+ const mockTask: Task = {
148
+ id: 'task-123',
149
+ user_id: 'user-456',
150
+ title: 'Test Task',
151
+ description: 'Test description',
152
+ status: 'in_progress',
153
+ created_at: '2026-02-16T00:00:00Z',
154
+ updated_at: '2026-02-16T00:00:00Z',
155
+ progress: {
156
+ current_milestone: 'milestone-2',
157
+ current_task: '',
158
+ overall_percentage: 50,
159
+ milestones: [
160
+ {
161
+ id: 'milestone-1',
162
+ name: 'Milestone 1',
163
+ description: 'First',
164
+ status: 'completed',
165
+ progress: 100,
166
+ tasks_completed: 2,
167
+ tasks_total: 2
168
+ },
169
+ {
170
+ id: 'milestone-2',
171
+ name: 'Milestone 2',
172
+ description: 'Second',
173
+ status: 'in_progress',
174
+ progress: 50,
175
+ tasks_completed: 1,
176
+ tasks_total: 2
177
+ },
178
+ {
179
+ id: 'milestone-3',
180
+ name: 'Milestone 3',
181
+ description: 'Third',
182
+ status: 'not_started',
183
+ progress: 0,
184
+ tasks_completed: 0,
185
+ tasks_total: 2
186
+ }
187
+ ],
188
+ tasks: {}
189
+ },
190
+ execution: {
191
+ api_messages: [],
192
+ task_messages: [],
193
+ tool_results: []
194
+ },
195
+ config: {
196
+
197
+ system_prompt: '',
198
+ auto_approve: true
199
+ },
200
+ metadata: undefined
201
+ }
202
+
203
+ mockClient.getTask.mockResolvedValue(mockTask)
204
+
205
+ const result = await handleTaskGetStatus(mockClient, { task_id: 'task-123' })
206
+ const parsed = JSON.parse(result)
207
+
208
+ expect(parsed.milestones_summary.total).toBe(3)
209
+ expect(parsed.milestones_summary.completed).toBe(1)
210
+ expect(parsed.milestones_summary.in_progress).toBe(1)
211
+ })
212
+ })
213
+ })
@@ -0,0 +1,77 @@
1
+ /**
2
+ * MCP Tool: task_get_status
3
+ *
4
+ * Get current task status and progress information.
5
+ * Returns task title, status, current milestone, and overall progress.
6
+ */
7
+
8
+ import { FirebaseClient } from '@/client.js'
9
+
10
+ export const taskGetStatusTool = {
11
+ name: 'task_get_status',
12
+ description: 'Get current task status and progress',
13
+ inputSchema: {
14
+ type: 'object',
15
+ properties: {
16
+ task_id: {
17
+ type: 'string',
18
+ description: 'Task ID to get status for'
19
+ }
20
+ },
21
+ required: ['task_id']
22
+ }
23
+ }
24
+
25
+ export async function handleTaskGetStatus(
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
+ // Find current milestone details
37
+ const currentMilestone = task.progress.milestones.find(
38
+ m => m.id === task.progress.current_milestone
39
+ )
40
+
41
+ // Find current task item details
42
+ let currentTaskItem = null
43
+ if (task.progress.current_task && task.progress.current_milestone) {
44
+ const milestoneItems = task.progress.tasks[task.progress.current_milestone] || []
45
+ currentTaskItem = milestoneItems.find(
46
+ item => item.id === task.progress.current_task
47
+ )
48
+ }
49
+
50
+ return JSON.stringify({
51
+ task_id: task.id,
52
+ task_title: task.title,
53
+ status: task.status,
54
+ overall_progress: task.progress.overall_percentage,
55
+ current_milestone: currentMilestone ? {
56
+ id: currentMilestone.id,
57
+ name: currentMilestone.name,
58
+ status: currentMilestone.status,
59
+ progress: currentMilestone.progress,
60
+ tasks_completed: currentMilestone.tasks_completed,
61
+ tasks_total: currentMilestone.tasks_total
62
+ } : null,
63
+ current_task: currentTaskItem ? {
64
+ id: currentTaskItem.id,
65
+ name: currentTaskItem.name,
66
+ status: currentTaskItem.status
67
+ } : null,
68
+ milestones_summary: {
69
+ total: task.progress.milestones.length,
70
+ completed: task.progress.milestones.filter(m => m.status === 'completed').length,
71
+ in_progress: task.progress.milestones.filter(m => m.status === 'in_progress').length
72
+ }
73
+ }, null, 2)
74
+ } catch (error) {
75
+ throw new Error(`Failed to get task status: ${error instanceof Error ? error.message : String(error)}`)
76
+ }
77
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * MCP Tool: task_report_completion
3
+ *
4
+ * Agent reports completion of a task item and gets next instructions.
5
+ * This is a convenience tool that combines completing a task item and getting the next step.
6
+ */
7
+
8
+ import { FirebaseClient } from '@/client.js'
9
+ import { handleTaskCompleteTaskItem } from './task-complete-task-item.js'
10
+ import { handleTaskGetNextStep } from './task-get-next-step.js'
11
+
12
+ export const taskReportCompletionTool = {
13
+ name: 'task_report_completion',
14
+ description: 'Report completion of a task item and get next instructions',
15
+ inputSchema: {
16
+ type: 'object',
17
+ properties: {
18
+ task_id: {
19
+ type: 'string',
20
+ description: 'Task ID'
21
+ },
22
+ milestone_id: {
23
+ type: 'string',
24
+ description: 'Milestone ID'
25
+ },
26
+ task_item_id: {
27
+ type: 'string',
28
+ description: 'Task item ID that was completed'
29
+ },
30
+ notes: {
31
+ type: 'string',
32
+ description: 'Optional notes about the completion'
33
+ }
34
+ },
35
+ required: ['task_id', 'milestone_id', 'task_item_id']
36
+ }
37
+ }
38
+
39
+ export async function handleTaskReportCompletion(
40
+ client: FirebaseClient,
41
+ args: {
42
+ task_id: string
43
+ milestone_id: string
44
+ task_item_id: string
45
+ notes?: string
46
+ }
47
+ ): Promise<string> {
48
+ try {
49
+ // Add notes if provided
50
+ if (args.notes) {
51
+ await client.updateTaskItem(
52
+ args.task_id,
53
+ args.milestone_id,
54
+ args.task_item_id,
55
+ { notes: args.notes }
56
+ )
57
+ }
58
+
59
+ // Complete the task item
60
+ const completionResult = await handleTaskCompleteTaskItem(client, {
61
+ task_id: args.task_id,
62
+ milestone_id: args.milestone_id,
63
+ task_item_id: args.task_item_id
64
+ })
65
+
66
+ // Get next step
67
+ const nextStepResult = await handleTaskGetNextStep(client, {
68
+ task_id: args.task_id
69
+ })
70
+
71
+ const completion = JSON.parse(completionResult)
72
+ const nextStep = JSON.parse(nextStepResult)
73
+
74
+ return JSON.stringify({
75
+ completion: {
76
+ success: completion.success,
77
+ completed_task: completion.completed_task,
78
+ milestone_progress: completion.milestone_progress
79
+ },
80
+ next_step: nextStep,
81
+ message: completion.message
82
+ }, null, 2)
83
+ } catch (error) {
84
+ throw new Error(`Failed to report completion: ${error instanceof Error ? error.message : String(error)}`)
85
+ }
86
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * MCP Tool: task_update_progress
3
+ *
4
+ * Update the overall progress percentage for a task.
5
+ */
6
+
7
+ import { FirebaseClient } from '@/client.js'
8
+
9
+ export const taskUpdateProgressTool = {
10
+ name: 'task_update_progress',
11
+ description: 'Update the overall progress percentage for a task',
12
+ inputSchema: {
13
+ type: 'object',
14
+ properties: {
15
+ task_id: {
16
+ type: 'string',
17
+ description: 'Task ID'
18
+ },
19
+ percentage: {
20
+ type: 'number',
21
+ description: 'Progress percentage (0-100)',
22
+ minimum: 0,
23
+ maximum: 100
24
+ }
25
+ },
26
+ required: ['task_id', 'percentage']
27
+ }
28
+ }
29
+
30
+ export async function handleTaskUpdateProgress(
31
+ client: FirebaseClient,
32
+ args: { task_id: string; percentage: number }
33
+ ): Promise<string> {
34
+ try {
35
+ // Validate percentage
36
+ const percentage = Math.min(100, Math.max(0, args.percentage))
37
+
38
+ await client.updateOverallProgress(args.task_id, percentage)
39
+
40
+ return JSON.stringify({
41
+ success: true,
42
+ task_id: args.task_id,
43
+ progress: percentage,
44
+ message: `Progress updated to ${percentage}%`
45
+ }, null, 2)
46
+ } catch (error) {
47
+ throw new Error(`Failed to update progress: ${error instanceof Error ? error.message : String(error)}`)
48
+ }
49
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Tests for remaining MCP tools
3
+ */
4
+
5
+ import { handleTaskUpdateProgress } from './task-update-progress.js'
6
+ import { handleTaskCompleteTaskItem } from './task-complete-task-item.js'
7
+ import { handleTaskCreateMilestone } from './task-create-milestone.js'
8
+ import { handleTaskCreateTaskItem } from './task-create-task-item.js'
9
+ import { handleTaskAddMessage } from './task-add-message.js'
10
+ import { FirebaseClient } from '@/client.js'
11
+ import type { Task } from '@/schemas/task.js'
12
+
13
+ jest.mock('@/client.js')
14
+
15
+ describe('MCP Tools', () => {
16
+ let mockClient: jest.Mocked<FirebaseClient>
17
+
18
+ beforeEach(() => {
19
+ mockClient = {
20
+ updateOverallProgress: jest.fn(),
21
+ completeTaskItem: jest.fn(),
22
+ getTask: jest.fn(),
23
+ updateMilestone: jest.fn(),
24
+ createMilestone: jest.fn(),
25
+ createTaskItem: jest.fn(),
26
+ addMessage: jest.fn()
27
+ } as any
28
+ })
29
+
30
+ describe('task_update_progress', () => {
31
+ it('should update progress successfully', async () => {
32
+ mockClient.updateOverallProgress.mockResolvedValue(undefined)
33
+
34
+ const result = await handleTaskUpdateProgress(mockClient, {
35
+ task_id: 'task-123',
36
+ percentage: 75
37
+ })
38
+
39
+ const parsed = JSON.parse(result)
40
+ expect(parsed.success).toBe(true)
41
+ expect(parsed.progress).toBe(75)
42
+ expect(mockClient.updateOverallProgress).toHaveBeenCalledWith('task-123', 75)
43
+ })
44
+
45
+ it('should clamp percentage to 0-100 range', async () => {
46
+ mockClient.updateOverallProgress.mockResolvedValue(undefined)
47
+
48
+ await handleTaskUpdateProgress(mockClient, {
49
+ task_id: 'task-123',
50
+ percentage: 150
51
+ })
52
+
53
+ expect(mockClient.updateOverallProgress).toHaveBeenCalledWith('task-123', 100)
54
+ })
55
+ })
56
+
57
+ describe('task_complete_task_item', () => {
58
+ it('should complete task item and return next task', async () => {
59
+ const mockTask: Task = {
60
+ id: 'task-123',
61
+ user_id: 'user-456',
62
+ title: 'Test',
63
+ description: 'Test',
64
+ status: 'in_progress',
65
+ created_at: '2026-02-16T00:00:00Z',
66
+ updated_at: '2026-02-16T00:00:00Z',
67
+ progress: {
68
+ current_milestone: 'milestone-1',
69
+ current_task: 'task-1',
70
+ overall_percentage: 0,
71
+ milestones: [{
72
+ id: 'milestone-1',
73
+ name: 'Milestone 1',
74
+ description: 'Test',
75
+ status: 'in_progress',
76
+ progress: 0,
77
+ tasks_completed: 0,
78
+ tasks_total: 2
79
+ }],
80
+ tasks: {
81
+ 'milestone-1': [
82
+ { id: 'task-1', name: 'Task 1', description: 'Test', status: 'completed' },
83
+ { id: 'task-2', name: 'Task 2', description: 'Test', status: 'not_started' }
84
+ ]
85
+ }
86
+ },
87
+ execution: { api_messages: [], task_messages: [], tool_results: [] },
88
+ config: { system_prompt: '', auto_approve: true },
89
+ metadata: undefined
90
+ }
91
+
92
+ mockClient.completeTaskItem.mockResolvedValue(undefined)
93
+ mockClient.getTask.mockResolvedValue(mockTask)
94
+ mockClient.updateMilestone.mockResolvedValue(undefined)
95
+
96
+ const result = await handleTaskCompleteTaskItem(mockClient, {
97
+ task_id: 'task-123',
98
+ milestone_id: 'milestone-1',
99
+ task_item_id: 'task-1'
100
+ })
101
+
102
+ const parsed = JSON.parse(result)
103
+ expect(parsed.success).toBe(true)
104
+ expect(parsed.next_task).toBeTruthy()
105
+ expect(parsed.next_task.name).toBe('Task 2')
106
+ })
107
+ })
108
+
109
+ describe('task_create_milestone', () => {
110
+ it('should create milestone successfully', async () => {
111
+ mockClient.createMilestone.mockResolvedValue(undefined)
112
+
113
+ const result = await handleTaskCreateMilestone(mockClient, {
114
+ task_id: 'task-123',
115
+ milestone_id: 'milestone-1',
116
+ name: 'Milestone 1',
117
+ description: 'Test milestone'
118
+ })
119
+
120
+ const parsed = JSON.parse(result)
121
+ expect(parsed.success).toBe(true)
122
+ expect(parsed.milestone.name).toBe('Milestone 1')
123
+ })
124
+ })
125
+
126
+ describe('task_create_task_item', () => {
127
+ it('should create task item successfully', async () => {
128
+ const mockTask: Task = {
129
+ id: 'task-123',
130
+ user_id: 'user-456',
131
+ title: 'Test',
132
+ description: 'Test',
133
+ status: 'in_progress',
134
+ created_at: '2026-02-16T00:00:00Z',
135
+ updated_at: '2026-02-16T00:00:00Z',
136
+ progress: {
137
+ current_milestone: 'milestone-1',
138
+ current_task: '',
139
+ overall_percentage: 0,
140
+ milestones: [{
141
+ id: 'milestone-1',
142
+ name: 'Milestone 1',
143
+ description: 'Test',
144
+ status: 'in_progress',
145
+ progress: 0,
146
+ tasks_completed: 0,
147
+ tasks_total: 0
148
+ }],
149
+ tasks: {
150
+ 'milestone-1': [
151
+ { id: 'task-1', name: 'Task 1', description: 'Test', status: 'not_started' }
152
+ ]
153
+ }
154
+ },
155
+ execution: { api_messages: [], task_messages: [], tool_results: [] },
156
+ config: { system_prompt: '', auto_approve: true },
157
+ metadata: undefined
158
+ }
159
+
160
+ mockClient.createTaskItem.mockResolvedValue(undefined)
161
+ mockClient.getTask.mockResolvedValue(mockTask)
162
+ mockClient.updateMilestone.mockResolvedValue(undefined)
163
+
164
+ const result = await handleTaskCreateTaskItem(mockClient, {
165
+ task_id: 'task-123',
166
+ milestone_id: 'milestone-1',
167
+ task_item_id: 'task-1',
168
+ name: 'Task 1',
169
+ description: 'Test task'
170
+ })
171
+
172
+ const parsed = JSON.parse(result)
173
+ expect(parsed.success).toBe(true)
174
+ expect(parsed.task_item.name).toBe('Task 1')
175
+ })
176
+ })
177
+
178
+ describe('task_add_message', () => {
179
+ it('should add message successfully', async () => {
180
+ mockClient.addMessage.mockResolvedValue('message-123')
181
+
182
+ const result = await handleTaskAddMessage(mockClient, {
183
+ task_id: 'task-123',
184
+ role: 'assistant',
185
+ content: 'Test message'
186
+ })
187
+
188
+ const parsed = JSON.parse(result)
189
+ expect(parsed.success).toBe(true)
190
+ expect(parsed.message_id).toBe('message-123')
191
+ expect(parsed.role).toBe('assistant')
192
+ })
193
+ })
194
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "node",
6
+ "lib": ["ES2022"],
7
+ "types": ["node", "jest"],
8
+
9
+ "outDir": "./dist",
10
+ "rootDir": "./src",
11
+
12
+ "esModuleInterop": true,
13
+ "allowSyntheticDefaultImports": true,
14
+ "strict": true,
15
+ "skipLibCheck": true,
16
+ "forceConsistentCasingInFileNames": true,
17
+ "resolveJsonModule": true,
18
+
19
+ "declaration": true,
20
+ "declarationMap": true,
21
+ "sourceMap": true,
22
+
23
+ "baseUrl": ".",
24
+ "paths": {
25
+ "@/*": ["src/*"]
26
+ }
27
+ },
28
+
29
+ "include": ["src/**/*"],
30
+ "exclude": ["node_modules", "dist"]
31
+ }