@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,470 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Database Service
|
|
3
|
+
*
|
|
4
|
+
* Service layer for all Firestore operations related to tasks.
|
|
5
|
+
* Handles CRUD operations, task messages, and progress tracking.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getFirestore, Firestore, FieldValue } from 'firebase-admin/firestore'
|
|
9
|
+
import { getUserTasks, getUserTaskMessages, getUserTask, getUserTaskMessage } from '../constant/collections.js'
|
|
10
|
+
import { TaskSchema, type Task, type Milestone, type TaskItem, type TaskMessage } from '../schemas/task.js'
|
|
11
|
+
|
|
12
|
+
export class TaskDatabaseService {
|
|
13
|
+
private static db: Firestore | null = null
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Initialize the database connection
|
|
17
|
+
*/
|
|
18
|
+
static initialize(db?: Firestore): void {
|
|
19
|
+
this.db = db || getFirestore()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get the Firestore instance
|
|
24
|
+
*/
|
|
25
|
+
private static getDb(): Firestore {
|
|
26
|
+
if (!this.db) {
|
|
27
|
+
this.db = getFirestore()
|
|
28
|
+
}
|
|
29
|
+
return this.db
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ==================== CRUD Operations ====================
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create a new task
|
|
36
|
+
*/
|
|
37
|
+
static async createTask(
|
|
38
|
+
userId: string,
|
|
39
|
+
title: string,
|
|
40
|
+
description: string,
|
|
41
|
+
config?: Partial<Task['config']>,
|
|
42
|
+
metadata?: Task['metadata']
|
|
43
|
+
): Promise<Task> {
|
|
44
|
+
const db = this.getDb()
|
|
45
|
+
const now = new Date().toISOString()
|
|
46
|
+
|
|
47
|
+
const taskData: Omit<Task, 'id'> = {
|
|
48
|
+
user_id: userId,
|
|
49
|
+
title,
|
|
50
|
+
description,
|
|
51
|
+
status: 'not_started',
|
|
52
|
+
created_at: now,
|
|
53
|
+
updated_at: now,
|
|
54
|
+
progress: {
|
|
55
|
+
current_milestone: '',
|
|
56
|
+
current_task: '',
|
|
57
|
+
overall_percentage: 0,
|
|
58
|
+
milestones: [],
|
|
59
|
+
tasks: {}
|
|
60
|
+
},
|
|
61
|
+
execution: {
|
|
62
|
+
api_messages: [],
|
|
63
|
+
task_messages: [],
|
|
64
|
+
tool_results: []
|
|
65
|
+
},
|
|
66
|
+
config: {
|
|
67
|
+
system_prompt: config?.system_prompt || '',
|
|
68
|
+
auto_approve: config?.auto_approve ?? true,
|
|
69
|
+
max_iterations: config?.max_iterations || 500
|
|
70
|
+
},
|
|
71
|
+
metadata: metadata
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const tasksPath = getUserTasks(userId)
|
|
75
|
+
const docRef = await db.collection(tasksPath).add(taskData)
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
id: docRef.id,
|
|
79
|
+
...taskData
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get a task by ID
|
|
85
|
+
*/
|
|
86
|
+
static async getTask(userId: string, taskId: string): Promise<Task | null> {
|
|
87
|
+
const db = this.getDb()
|
|
88
|
+
const taskPath = getUserTask(userId, taskId)
|
|
89
|
+
const doc = await db.doc(taskPath).get()
|
|
90
|
+
|
|
91
|
+
if (!doc.exists) {
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const data = doc.data()
|
|
96
|
+
const result = TaskSchema.safeParse({ id: doc.id, ...data })
|
|
97
|
+
|
|
98
|
+
if (!result.success) {
|
|
99
|
+
console.error('Task validation failed:', result.error)
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return result.data
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Update a task
|
|
108
|
+
*/
|
|
109
|
+
static async updateTask(
|
|
110
|
+
userId: string,
|
|
111
|
+
taskId: string,
|
|
112
|
+
updates: Partial<Omit<Task, 'id' | 'user_id' | 'created_at'>>
|
|
113
|
+
): Promise<void> {
|
|
114
|
+
const db = this.getDb()
|
|
115
|
+
const taskPath = getUserTask(userId, taskId)
|
|
116
|
+
|
|
117
|
+
await db.doc(taskPath).update({
|
|
118
|
+
...updates,
|
|
119
|
+
updated_at: new Date().toISOString()
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Update task status
|
|
125
|
+
*/
|
|
126
|
+
static async updateTaskStatus(
|
|
127
|
+
userId: string,
|
|
128
|
+
taskId: string,
|
|
129
|
+
status: Task['status']
|
|
130
|
+
): Promise<void> {
|
|
131
|
+
const db = this.getDb()
|
|
132
|
+
const taskPath = getUserTask(userId, taskId)
|
|
133
|
+
const updates: any = {
|
|
134
|
+
status,
|
|
135
|
+
updated_at: new Date().toISOString()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Set timestamps based on status
|
|
139
|
+
if (status === 'in_progress') {
|
|
140
|
+
updates.started_at = new Date().toISOString()
|
|
141
|
+
} else if (status === 'completed' || status === 'failed') {
|
|
142
|
+
updates.completed_at = new Date().toISOString()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
await db.doc(taskPath).update(updates)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Delete a task
|
|
150
|
+
*/
|
|
151
|
+
static async deleteTask(userId: string, taskId: string): Promise<void> {
|
|
152
|
+
const db = this.getDb()
|
|
153
|
+
const taskPath = getUserTask(userId, taskId)
|
|
154
|
+
|
|
155
|
+
// Delete all messages first
|
|
156
|
+
const messagesPath = getUserTaskMessages(userId, taskId)
|
|
157
|
+
const messagesSnapshot = await db.collection(messagesPath).get()
|
|
158
|
+
|
|
159
|
+
const batch = db.batch()
|
|
160
|
+
messagesSnapshot.docs.forEach(doc => {
|
|
161
|
+
batch.delete(doc.ref)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
// Delete the task
|
|
165
|
+
batch.delete(db.doc(taskPath))
|
|
166
|
+
|
|
167
|
+
await batch.commit()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* List all tasks for a user
|
|
172
|
+
*/
|
|
173
|
+
static async listTasks(userId: string, limit = 50): Promise<Task[]> {
|
|
174
|
+
const db = this.getDb()
|
|
175
|
+
const tasksPath = getUserTasks(userId)
|
|
176
|
+
|
|
177
|
+
const snapshot = await db.collection(tasksPath)
|
|
178
|
+
.orderBy('created_at', 'desc')
|
|
179
|
+
.limit(limit)
|
|
180
|
+
.get()
|
|
181
|
+
|
|
182
|
+
const tasks: Task[] = []
|
|
183
|
+
snapshot.docs.forEach(doc => {
|
|
184
|
+
const result = TaskSchema.safeParse({ id: doc.id, ...doc.data() })
|
|
185
|
+
if (result.success) {
|
|
186
|
+
tasks.push(result.data)
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
return tasks
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ==================== Task Message Operations ====================
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Add a message to a task
|
|
197
|
+
*/
|
|
198
|
+
static async addMessage(
|
|
199
|
+
userId: string,
|
|
200
|
+
taskId: string,
|
|
201
|
+
role: 'user' | 'assistant' | 'system',
|
|
202
|
+
content: string,
|
|
203
|
+
metadata?: any
|
|
204
|
+
): Promise<string> {
|
|
205
|
+
const db = this.getDb()
|
|
206
|
+
const messagesPath = getUserTaskMessages(userId, taskId)
|
|
207
|
+
|
|
208
|
+
const message = {
|
|
209
|
+
task_id: taskId,
|
|
210
|
+
role,
|
|
211
|
+
content,
|
|
212
|
+
timestamp: new Date().toISOString(),
|
|
213
|
+
metadata: metadata || null
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const docRef = await db.collection(messagesPath).add(message)
|
|
217
|
+
return docRef.id
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get messages for a task
|
|
222
|
+
*/
|
|
223
|
+
static async getMessages(
|
|
224
|
+
userId: string,
|
|
225
|
+
taskId: string,
|
|
226
|
+
limit = 100
|
|
227
|
+
): Promise<TaskMessage[]> {
|
|
228
|
+
const db = this.getDb()
|
|
229
|
+
const messagesPath = getUserTaskMessages(userId, taskId)
|
|
230
|
+
|
|
231
|
+
const snapshot = await db.collection(messagesPath)
|
|
232
|
+
.orderBy('timestamp', 'asc')
|
|
233
|
+
.limit(limit)
|
|
234
|
+
.get()
|
|
235
|
+
|
|
236
|
+
return snapshot.docs.map(doc => ({
|
|
237
|
+
id: doc.id,
|
|
238
|
+
...doc.data()
|
|
239
|
+
} as TaskMessage))
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Delete a message
|
|
244
|
+
*/
|
|
245
|
+
static async deleteMessage(
|
|
246
|
+
userId: string,
|
|
247
|
+
taskId: string,
|
|
248
|
+
messageId: string
|
|
249
|
+
): Promise<void> {
|
|
250
|
+
const db = this.getDb()
|
|
251
|
+
const messagePath = getUserTaskMessage(userId, taskId, messageId)
|
|
252
|
+
await db.doc(messagePath).delete()
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ==================== Progress Operations ====================
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Update overall progress percentage
|
|
259
|
+
*/
|
|
260
|
+
static async updateOverallProgress(
|
|
261
|
+
userId: string,
|
|
262
|
+
taskId: string,
|
|
263
|
+
percentage: number
|
|
264
|
+
): Promise<void> {
|
|
265
|
+
const db = this.getDb()
|
|
266
|
+
const taskPath = getUserTask(userId, taskId)
|
|
267
|
+
|
|
268
|
+
await db.doc(taskPath).update({
|
|
269
|
+
'progress.overall_percentage': Math.min(100, Math.max(0, percentage)),
|
|
270
|
+
updated_at: new Date().toISOString()
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Create a milestone
|
|
276
|
+
*/
|
|
277
|
+
static async createMilestone(
|
|
278
|
+
userId: string,
|
|
279
|
+
taskId: string,
|
|
280
|
+
milestone: Milestone
|
|
281
|
+
): Promise<void> {
|
|
282
|
+
const db = this.getDb()
|
|
283
|
+
const taskPath = getUserTask(userId, taskId)
|
|
284
|
+
|
|
285
|
+
await db.doc(taskPath).update({
|
|
286
|
+
'progress.milestones': FieldValue.arrayUnion(milestone),
|
|
287
|
+
updated_at: new Date().toISOString()
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Update a milestone
|
|
293
|
+
*/
|
|
294
|
+
static async updateMilestone(
|
|
295
|
+
userId: string,
|
|
296
|
+
taskId: string,
|
|
297
|
+
milestoneId: string,
|
|
298
|
+
updates: Partial<Milestone>
|
|
299
|
+
): Promise<void> {
|
|
300
|
+
const task = await this.getTask(userId, taskId)
|
|
301
|
+
if (!task) throw new Error('Task not found')
|
|
302
|
+
|
|
303
|
+
const milestoneIndex = task.progress.milestones.findIndex(m => m.id === milestoneId)
|
|
304
|
+
if (milestoneIndex === -1) throw new Error('Milestone not found')
|
|
305
|
+
|
|
306
|
+
task.progress.milestones[milestoneIndex] = {
|
|
307
|
+
...task.progress.milestones[milestoneIndex],
|
|
308
|
+
...updates
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const db = this.getDb()
|
|
312
|
+
const taskPath = getUserTask(userId, taskId)
|
|
313
|
+
|
|
314
|
+
await db.doc(taskPath).update({
|
|
315
|
+
'progress.milestones': task.progress.milestones,
|
|
316
|
+
updated_at: new Date().toISOString()
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Complete a milestone
|
|
322
|
+
*/
|
|
323
|
+
static async completeMilestone(
|
|
324
|
+
userId: string,
|
|
325
|
+
taskId: string,
|
|
326
|
+
milestoneId: string
|
|
327
|
+
): Promise<void> {
|
|
328
|
+
await this.updateMilestone(userId, taskId, milestoneId, {
|
|
329
|
+
status: 'completed',
|
|
330
|
+
progress: 100,
|
|
331
|
+
completed_at: new Date().toISOString()
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Create a task item
|
|
337
|
+
*/
|
|
338
|
+
static async createTaskItem(
|
|
339
|
+
userId: string,
|
|
340
|
+
taskId: string,
|
|
341
|
+
milestoneId: string,
|
|
342
|
+
taskItem: TaskItem
|
|
343
|
+
): Promise<void> {
|
|
344
|
+
const task = await this.getTask(userId, taskId)
|
|
345
|
+
if (!task) throw new Error('Task not found')
|
|
346
|
+
|
|
347
|
+
if (!task.progress.tasks[milestoneId]) {
|
|
348
|
+
task.progress.tasks[milestoneId] = []
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
task.progress.tasks[milestoneId].push(taskItem)
|
|
352
|
+
|
|
353
|
+
const db = this.getDb()
|
|
354
|
+
const taskPath = getUserTask(userId, taskId)
|
|
355
|
+
|
|
356
|
+
await db.doc(taskPath).update({
|
|
357
|
+
'progress.tasks': task.progress.tasks,
|
|
358
|
+
updated_at: new Date().toISOString()
|
|
359
|
+
})
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Update a task item
|
|
364
|
+
*/
|
|
365
|
+
static async updateTaskItem(
|
|
366
|
+
userId: string,
|
|
367
|
+
taskId: string,
|
|
368
|
+
milestoneId: string,
|
|
369
|
+
taskItemId: string,
|
|
370
|
+
updates: Partial<TaskItem>
|
|
371
|
+
): Promise<void> {
|
|
372
|
+
const task = await this.getTask(userId, taskId)
|
|
373
|
+
if (!task) throw new Error('Task not found')
|
|
374
|
+
|
|
375
|
+
const items = task.progress.tasks[milestoneId]
|
|
376
|
+
if (!items) throw new Error('Milestone not found')
|
|
377
|
+
|
|
378
|
+
const itemIndex = items.findIndex(item => item.id === taskItemId)
|
|
379
|
+
if (itemIndex === -1) throw new Error('Task item not found')
|
|
380
|
+
|
|
381
|
+
items[itemIndex] = {
|
|
382
|
+
...items[itemIndex],
|
|
383
|
+
...updates
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const db = this.getDb()
|
|
387
|
+
const taskPath = getUserTask(userId, taskId)
|
|
388
|
+
|
|
389
|
+
await db.doc(taskPath).update({
|
|
390
|
+
[`progress.tasks.${milestoneId}`]: items,
|
|
391
|
+
updated_at: new Date().toISOString()
|
|
392
|
+
})
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Complete a task item
|
|
397
|
+
*/
|
|
398
|
+
static async completeTaskItem(
|
|
399
|
+
userId: string,
|
|
400
|
+
taskId: string,
|
|
401
|
+
milestoneId: string,
|
|
402
|
+
taskItemId: string
|
|
403
|
+
): Promise<void> {
|
|
404
|
+
await this.updateTaskItem(userId, taskId, milestoneId, taskItemId, {
|
|
405
|
+
status: 'completed',
|
|
406
|
+
completed_at: new Date().toISOString()
|
|
407
|
+
})
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ==================== Query Methods ====================
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get tasks by status
|
|
414
|
+
*/
|
|
415
|
+
static async getTasksByStatus(
|
|
416
|
+
userId: string,
|
|
417
|
+
status: Task['status'],
|
|
418
|
+
limit = 50
|
|
419
|
+
): Promise<Task[]> {
|
|
420
|
+
const db = this.getDb()
|
|
421
|
+
const tasksPath = getUserTasks(userId)
|
|
422
|
+
|
|
423
|
+
const snapshot = await db.collection(tasksPath)
|
|
424
|
+
.where('status', '==', status)
|
|
425
|
+
.orderBy('updated_at', 'desc')
|
|
426
|
+
.limit(limit)
|
|
427
|
+
.get()
|
|
428
|
+
|
|
429
|
+
const tasks: Task[] = []
|
|
430
|
+
snapshot.docs.forEach(doc => {
|
|
431
|
+
const result = TaskSchema.safeParse({ id: doc.id, ...doc.data() })
|
|
432
|
+
if (result.success) {
|
|
433
|
+
tasks.push(result.data)
|
|
434
|
+
}
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
return tasks
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Get active tasks (in_progress status)
|
|
442
|
+
*/
|
|
443
|
+
static async getActiveTasks(userId: string, limit = 50): Promise<Task[]> {
|
|
444
|
+
return this.getTasksByStatus(userId, 'in_progress', limit)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Get completed tasks
|
|
449
|
+
*/
|
|
450
|
+
static async getCompletedTasks(userId: string, limit = 50): Promise<Task[]> {
|
|
451
|
+
return this.getTasksByStatus(userId, 'completed', limit)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Search tasks by title
|
|
456
|
+
*/
|
|
457
|
+
static async searchTasksByTitle(
|
|
458
|
+
userId: string,
|
|
459
|
+
searchTerm: string,
|
|
460
|
+
limit = 50
|
|
461
|
+
): Promise<Task[]> {
|
|
462
|
+
const allTasks = await this.listTasks(userId, limit)
|
|
463
|
+
|
|
464
|
+
const searchLower = searchTerm.toLowerCase()
|
|
465
|
+
return allTasks.filter(task =>
|
|
466
|
+
task.title.toLowerCase().includes(searchLower) ||
|
|
467
|
+
task.description.toLowerCase().includes(searchLower)
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Validation Tests
|
|
3
|
+
*
|
|
4
|
+
* Test file to verify Zod schemas work correctly with valid and invalid data
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
TaskSchema,
|
|
9
|
+
MilestoneSchema,
|
|
10
|
+
TaskItemSchema,
|
|
11
|
+
TaskMessageSchema,
|
|
12
|
+
type Task,
|
|
13
|
+
type Milestone,
|
|
14
|
+
type TaskItem,
|
|
15
|
+
type TaskMessage
|
|
16
|
+
} from './schemas/task.js'
|
|
17
|
+
|
|
18
|
+
// Test valid milestone
|
|
19
|
+
const validMilestone: Milestone = {
|
|
20
|
+
id: 'M1',
|
|
21
|
+
name: 'Foundation',
|
|
22
|
+
description: 'Build core infrastructure',
|
|
23
|
+
status: 'in_progress',
|
|
24
|
+
progress: 50,
|
|
25
|
+
tasks_completed: 2,
|
|
26
|
+
tasks_total: 4,
|
|
27
|
+
started_at: '2026-02-16',
|
|
28
|
+
completed_at: undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log('Testing valid milestone...')
|
|
32
|
+
try {
|
|
33
|
+
const result = MilestoneSchema.parse(validMilestone)
|
|
34
|
+
console.log('✅ Valid milestone passed:', result.name)
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('❌ Valid milestone failed:', error)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Test valid task item
|
|
40
|
+
const validTaskItem: TaskItem = {
|
|
41
|
+
id: 'task-1',
|
|
42
|
+
name: 'Setup project',
|
|
43
|
+
description: 'Initialize Node.js project',
|
|
44
|
+
status: 'completed',
|
|
45
|
+
estimated_hours: 2,
|
|
46
|
+
completed_at: '2026-02-16',
|
|
47
|
+
notes: 'Completed successfully'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log('\nTesting valid task item...')
|
|
51
|
+
try {
|
|
52
|
+
const result = TaskItemSchema.parse(validTaskItem)
|
|
53
|
+
console.log('✅ Valid task item passed:', result.name)
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('❌ Valid task item failed:', error)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Test valid task
|
|
59
|
+
const validTask: Task = {
|
|
60
|
+
id: 'task-123',
|
|
61
|
+
user_id: 'user-456',
|
|
62
|
+
title: 'Build MCP Server',
|
|
63
|
+
description: 'Create task-mcp server with core tools',
|
|
64
|
+
status: 'in_progress',
|
|
65
|
+
created_at: '2026-02-16T00:00:00Z',
|
|
66
|
+
updated_at: '2026-02-16T12:00:00Z',
|
|
67
|
+
started_at: '2026-02-16T01:00:00Z',
|
|
68
|
+
|
|
69
|
+
progress: {
|
|
70
|
+
current_milestone: 'M1',
|
|
71
|
+
current_task: 'task-1',
|
|
72
|
+
overall_percentage: 25,
|
|
73
|
+
milestones: [validMilestone],
|
|
74
|
+
tasks: {
|
|
75
|
+
'M1': [validTaskItem]
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
execution: {
|
|
80
|
+
api_messages: [],
|
|
81
|
+
task_messages: [],
|
|
82
|
+
tool_results: []
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
config: {
|
|
86
|
+
system_prompt: 'You are a helpful assistant',
|
|
87
|
+
auto_approve: true,
|
|
88
|
+
max_iterations: 100
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
metadata: {
|
|
92
|
+
conversation_id: 'conv-789',
|
|
93
|
+
tags: ['mcp', 'server']
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log('\nTesting valid task...')
|
|
98
|
+
try {
|
|
99
|
+
const result = TaskSchema.parse(validTask)
|
|
100
|
+
console.log('✅ Valid task passed:', result.title)
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error('❌ Valid task failed:', error)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Test valid task message
|
|
106
|
+
const validMessage: TaskMessage = {
|
|
107
|
+
id: 'msg-001',
|
|
108
|
+
task_id: 'task-123',
|
|
109
|
+
role: 'assistant',
|
|
110
|
+
content: 'Task started successfully',
|
|
111
|
+
timestamp: '2026-02-16T12:00:00Z'
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log('\nTesting valid task message...')
|
|
115
|
+
try {
|
|
116
|
+
const result = TaskMessageSchema.parse(validMessage)
|
|
117
|
+
console.log('✅ Valid message passed:', result.content)
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error('❌ Valid message failed:', error)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Test invalid data (should fail)
|
|
123
|
+
console.log('\n--- Testing Invalid Data (should fail) ---')
|
|
124
|
+
|
|
125
|
+
// Invalid milestone (progress > 100)
|
|
126
|
+
console.log('\nTesting invalid milestone (progress > 100)...')
|
|
127
|
+
try {
|
|
128
|
+
MilestoneSchema.parse({
|
|
129
|
+
...validMilestone,
|
|
130
|
+
progress: 150
|
|
131
|
+
})
|
|
132
|
+
console.error('❌ Should have failed but passed')
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.log('✅ Correctly rejected invalid milestone')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Invalid task (wrong status)
|
|
138
|
+
console.log('\nTesting invalid task (wrong status)...')
|
|
139
|
+
try {
|
|
140
|
+
TaskSchema.parse({
|
|
141
|
+
...validTask,
|
|
142
|
+
status: 'invalid_status'
|
|
143
|
+
})
|
|
144
|
+
console.error('❌ Should have failed but passed')
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.log('✅ Correctly rejected invalid task status')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Invalid message (wrong role)
|
|
150
|
+
console.log('\nTesting invalid message (wrong role)...')
|
|
151
|
+
try {
|
|
152
|
+
TaskMessageSchema.parse({
|
|
153
|
+
...validMessage,
|
|
154
|
+
role: 'invalid_role'
|
|
155
|
+
})
|
|
156
|
+
console.error('❌ Should have failed but passed')
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.log('✅ Correctly rejected invalid message role')
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log('\n✅ All schema validation tests completed!')
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tools Index
|
|
3
|
+
*
|
|
4
|
+
* Exports all task management tools for the MCP server.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { taskGetStatusTool, handleTaskGetStatus } from './task-get-status.js'
|
|
8
|
+
import { taskGetNextStepTool, handleTaskGetNextStep } from './task-get-next-step.js'
|
|
9
|
+
import { taskUpdateProgressTool, handleTaskUpdateProgress } from './task-update-progress.js'
|
|
10
|
+
import { taskCompleteTaskItemTool, handleTaskCompleteTaskItem } from './task-complete-task-item.js'
|
|
11
|
+
import { taskCreateMilestoneTool, handleTaskCreateMilestone } from './task-create-milestone.js'
|
|
12
|
+
import { taskCreateTaskItemTool, handleTaskCreateTaskItem } from './task-create-task-item.js'
|
|
13
|
+
import { taskReportCompletionTool, handleTaskReportCompletion } from './task-report-completion.js'
|
|
14
|
+
import { taskAddMessageTool, handleTaskAddMessage } from './task-add-message.js'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* All tool definitions
|
|
18
|
+
*/
|
|
19
|
+
export const allTools = [
|
|
20
|
+
taskGetStatusTool,
|
|
21
|
+
taskGetNextStepTool,
|
|
22
|
+
taskUpdateProgressTool,
|
|
23
|
+
taskCompleteTaskItemTool,
|
|
24
|
+
taskCreateMilestoneTool,
|
|
25
|
+
taskCreateTaskItemTool,
|
|
26
|
+
taskReportCompletionTool,
|
|
27
|
+
taskAddMessageTool
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Tool handlers mapped by tool name
|
|
32
|
+
*/
|
|
33
|
+
export const toolHandlers = {
|
|
34
|
+
'task_get_status': handleTaskGetStatus,
|
|
35
|
+
'task_get_next_step': handleTaskGetNextStep,
|
|
36
|
+
'task_update_progress': handleTaskUpdateProgress,
|
|
37
|
+
'task_complete_task_item': handleTaskCompleteTaskItem,
|
|
38
|
+
'task_create_milestone': handleTaskCreateMilestone,
|
|
39
|
+
'task_create_task_item': handleTaskCreateTaskItem,
|
|
40
|
+
'task_report_completion': handleTaskReportCompletion,
|
|
41
|
+
'task_add_message': handleTaskAddMessage
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get tool handler by name
|
|
46
|
+
*/
|
|
47
|
+
export function getToolHandler(toolName: string) {
|
|
48
|
+
return toolHandlers[toolName as keyof typeof toolHandlers]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Re-export individual tools for direct imports
|
|
52
|
+
export {
|
|
53
|
+
taskGetStatusTool,
|
|
54
|
+
handleTaskGetStatus,
|
|
55
|
+
taskGetNextStepTool,
|
|
56
|
+
handleTaskGetNextStep,
|
|
57
|
+
taskUpdateProgressTool,
|
|
58
|
+
handleTaskUpdateProgress,
|
|
59
|
+
taskCompleteTaskItemTool,
|
|
60
|
+
handleTaskCompleteTaskItem,
|
|
61
|
+
taskCreateMilestoneTool,
|
|
62
|
+
handleTaskCreateMilestone,
|
|
63
|
+
taskCreateTaskItemTool,
|
|
64
|
+
handleTaskCreateTaskItem,
|
|
65
|
+
taskReportCompletionTool,
|
|
66
|
+
handleTaskReportCompletion,
|
|
67
|
+
taskAddMessageTool,
|
|
68
|
+
handleTaskAddMessage
|
|
69
|
+
}
|