rtaskmaster-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/LICENSE +21 -0
- package/README.md +244 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +7 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/storage.d.ts +38 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/storage.js +83 -0
- package/dist/core/storage.js.map +1 -0
- package/dist/core/task-manager.d.ts +67 -0
- package/dist/core/task-manager.d.ts.map +1 -0
- package/dist/core/task-manager.js +243 -0
- package/dist/core/task-manager.js.map +1 -0
- package/dist/core/types.d.ts +67 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +5 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +5 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +5 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/tools.d.ts +9 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +817 -0
- package/dist/mcp/tools.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tools - All tool definitions for RTaskmaster Task Master
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import { TaskManager } from "../core/task-manager.js";
|
|
8
|
+
// Schemas
|
|
9
|
+
const ProjectRootSchema = z.object({
|
|
10
|
+
projectRoot: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe("Absolute path to the project root directory"),
|
|
13
|
+
});
|
|
14
|
+
const TaskIdSchema = z.object({
|
|
15
|
+
projectRoot: z
|
|
16
|
+
.string()
|
|
17
|
+
.describe("Absolute path to the project root directory"),
|
|
18
|
+
taskId: z
|
|
19
|
+
.string()
|
|
20
|
+
.describe('Task ID (e.g., "1") or subtask ID (e.g., "1.2")'),
|
|
21
|
+
});
|
|
22
|
+
const StatusSchema = z.enum([
|
|
23
|
+
"pending",
|
|
24
|
+
"in-progress",
|
|
25
|
+
"done",
|
|
26
|
+
"blocked",
|
|
27
|
+
"deferred",
|
|
28
|
+
]);
|
|
29
|
+
const PrioritySchema = z.enum(["high", "medium", "low"]);
|
|
30
|
+
/**
|
|
31
|
+
* Register all RTaskmaster MCP tools
|
|
32
|
+
*/
|
|
33
|
+
export function registerTools(server) {
|
|
34
|
+
// ============ INIT ============
|
|
35
|
+
server.addTool({
|
|
36
|
+
name: "rtaskmaster_init",
|
|
37
|
+
description: "Initialize RTaskmaster Task Master in a project. Creates .rtaskmaster directory with tasks.json.",
|
|
38
|
+
parameters: z.object({
|
|
39
|
+
projectRoot: z
|
|
40
|
+
.string()
|
|
41
|
+
.describe("Absolute path to the project root directory"),
|
|
42
|
+
projectName: z.string().optional().describe("Optional project name"),
|
|
43
|
+
}),
|
|
44
|
+
execute: async (args) => {
|
|
45
|
+
const manager = new TaskManager(args.projectRoot);
|
|
46
|
+
if (manager.isInitialized()) {
|
|
47
|
+
return {
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: "text",
|
|
51
|
+
text: "✅ RTaskmaster is already initialized in this project.",
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const store = manager.initialize(args.projectName);
|
|
57
|
+
return {
|
|
58
|
+
content: [
|
|
59
|
+
{
|
|
60
|
+
type: "text",
|
|
61
|
+
text: `✅ RTaskmaster initialized successfully!\n\nProject: ${store.projectName}\nTasks file: ${args.projectRoot}/.rtaskmaster/tasks.json\n\nYou can now create tasks using rtaskmaster_create_task.`,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
// ============ GET TASKS ============
|
|
68
|
+
server.addTool({
|
|
69
|
+
name: "rtaskmaster_get_tasks",
|
|
70
|
+
description: "Get all tasks from the project, optionally filtered by status or priority.",
|
|
71
|
+
parameters: z.object({
|
|
72
|
+
projectRoot: z
|
|
73
|
+
.string()
|
|
74
|
+
.describe("Absolute path to the project root directory"),
|
|
75
|
+
status: StatusSchema.optional().describe("Filter by status"),
|
|
76
|
+
priority: PrioritySchema.optional().describe("Filter by priority"),
|
|
77
|
+
}),
|
|
78
|
+
execute: async (args) => {
|
|
79
|
+
const manager = new TaskManager(args.projectRoot);
|
|
80
|
+
if (!manager.isInitialized()) {
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: "❌ RTaskmaster is not initialized. Run rtaskmaster_init first.",
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const tasks = manager.getTasks({
|
|
91
|
+
status: args.status,
|
|
92
|
+
priority: args.priority,
|
|
93
|
+
});
|
|
94
|
+
const stats = manager.getStats();
|
|
95
|
+
if (tasks.length === 0) {
|
|
96
|
+
return {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: "text",
|
|
100
|
+
text: "📋 No tasks found.\n\nCreate your first task using rtaskmaster_create_task.",
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const taskList = tasks
|
|
106
|
+
.map((t) => {
|
|
107
|
+
const statusEmoji = {
|
|
108
|
+
pending: "⏳",
|
|
109
|
+
"in-progress": "🔄",
|
|
110
|
+
done: "✅",
|
|
111
|
+
blocked: "🚫",
|
|
112
|
+
deferred: "⏸️",
|
|
113
|
+
}[t.status] || "❓";
|
|
114
|
+
const priorityEmoji = {
|
|
115
|
+
high: "🔴",
|
|
116
|
+
medium: "🟡",
|
|
117
|
+
low: "🟢",
|
|
118
|
+
}[t.priority] || "";
|
|
119
|
+
let taskLine = `${statusEmoji} [${t.id}] ${t.title} ${priorityEmoji}`;
|
|
120
|
+
if (t.subtasks.length > 0) {
|
|
121
|
+
const doneSubtasks = t.subtasks.filter((s) => s.status === "done").length;
|
|
122
|
+
taskLine += ` (${doneSubtasks}/${t.subtasks.length} subtasks)`;
|
|
123
|
+
}
|
|
124
|
+
return taskLine;
|
|
125
|
+
})
|
|
126
|
+
.join("\n");
|
|
127
|
+
return {
|
|
128
|
+
content: [
|
|
129
|
+
{
|
|
130
|
+
type: "text",
|
|
131
|
+
text: `📋 Tasks (${stats.completionPercentage}% complete)\n\n${taskList}\n\n📊 Stats: ${stats.done}/${stats.total} done, ${stats.inProgress} in progress, ${stats.pending} pending`,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
// ============ GET TASK ============
|
|
138
|
+
server.addTool({
|
|
139
|
+
name: "rtaskmaster_get_task",
|
|
140
|
+
description: "Get detailed information about a specific task or subtask.",
|
|
141
|
+
parameters: TaskIdSchema,
|
|
142
|
+
execute: async (args) => {
|
|
143
|
+
const manager = new TaskManager(args.projectRoot);
|
|
144
|
+
if (!manager.isInitialized()) {
|
|
145
|
+
return {
|
|
146
|
+
content: [
|
|
147
|
+
{
|
|
148
|
+
type: "text",
|
|
149
|
+
text: "❌ RTaskmaster is not initialized. Run rtaskmaster_init first.",
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const task = manager.getTask(args.taskId);
|
|
155
|
+
if (!task) {
|
|
156
|
+
return {
|
|
157
|
+
content: [
|
|
158
|
+
{ type: "text", text: `❌ Task "${args.taskId}" not found.` },
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
// Check if it's a subtask (no subtasks property)
|
|
163
|
+
if (!("subtasks" in task)) {
|
|
164
|
+
return {
|
|
165
|
+
content: [
|
|
166
|
+
{
|
|
167
|
+
type: "text",
|
|
168
|
+
text: `📌 Subtask ${args.taskId}\n\nTitle: ${task.title}\nStatus: ${task.status}\n${task.description ? `Description: ${task.description}` : ""}`,
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
let output = `📌 Task ${task.id}: ${task.title}\n\n`;
|
|
174
|
+
output += `Status: ${task.status}\n`;
|
|
175
|
+
output += `Priority: ${task.priority}\n`;
|
|
176
|
+
output += `Description: ${task.description || "(none)"}\n`;
|
|
177
|
+
if (task.dependencies.length > 0) {
|
|
178
|
+
output += `Dependencies: ${task.dependencies.join(", ")}\n`;
|
|
179
|
+
}
|
|
180
|
+
if (task.details) {
|
|
181
|
+
output += `\n📝 Details:\n${task.details}\n`;
|
|
182
|
+
}
|
|
183
|
+
if (task.testStrategy) {
|
|
184
|
+
output += `\n🧪 Test Strategy:\n${task.testStrategy}\n`;
|
|
185
|
+
}
|
|
186
|
+
if (task.subtasks.length > 0) {
|
|
187
|
+
output += `\n📋 Subtasks:\n`;
|
|
188
|
+
task.subtasks.forEach((s) => {
|
|
189
|
+
const emoji = s.status === "done"
|
|
190
|
+
? "✅"
|
|
191
|
+
: s.status === "in-progress"
|
|
192
|
+
? "🔄"
|
|
193
|
+
: "⏳";
|
|
194
|
+
output += ` ${emoji} [${task.id}.${s.id}] ${s.title}\n`;
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
content: [{ type: "text", text: output }],
|
|
199
|
+
};
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
// ============ CREATE TASK ============
|
|
203
|
+
server.addTool({
|
|
204
|
+
name: "rtaskmaster_create_task",
|
|
205
|
+
description: "Create a new task.",
|
|
206
|
+
parameters: z.object({
|
|
207
|
+
projectRoot: z
|
|
208
|
+
.string()
|
|
209
|
+
.describe("Absolute path to the project root directory"),
|
|
210
|
+
title: z.string().describe("Task title"),
|
|
211
|
+
description: z.string().optional().describe("Task description"),
|
|
212
|
+
priority: PrioritySchema.optional().describe("Task priority (default: medium)"),
|
|
213
|
+
dependencies: z
|
|
214
|
+
.array(z.string())
|
|
215
|
+
.optional()
|
|
216
|
+
.describe("Array of task IDs this task depends on"),
|
|
217
|
+
details: z.string().optional().describe("Detailed implementation notes"),
|
|
218
|
+
testStrategy: z
|
|
219
|
+
.string()
|
|
220
|
+
.optional()
|
|
221
|
+
.describe("How to test/verify this task"),
|
|
222
|
+
}),
|
|
223
|
+
execute: async (args) => {
|
|
224
|
+
const manager = new TaskManager(args.projectRoot);
|
|
225
|
+
if (!manager.isInitialized()) {
|
|
226
|
+
return {
|
|
227
|
+
content: [
|
|
228
|
+
{
|
|
229
|
+
type: "text",
|
|
230
|
+
text: "❌ RTaskmaster is not initialized. Run rtaskmaster_init first.",
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const task = manager.createTask({
|
|
236
|
+
title: args.title,
|
|
237
|
+
description: args.description,
|
|
238
|
+
priority: args.priority,
|
|
239
|
+
dependencies: args.dependencies,
|
|
240
|
+
details: args.details,
|
|
241
|
+
testStrategy: args.testStrategy,
|
|
242
|
+
});
|
|
243
|
+
return {
|
|
244
|
+
content: [
|
|
245
|
+
{
|
|
246
|
+
type: "text",
|
|
247
|
+
text: `✅ Task created!\n\nID: ${task.id}\nTitle: ${task.title}\nPriority: ${task.priority}\nStatus: ${task.status}`,
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
};
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
// ============ UPDATE TASK ============
|
|
254
|
+
server.addTool({
|
|
255
|
+
name: "rtaskmaster_update_task",
|
|
256
|
+
description: "Update an existing task.",
|
|
257
|
+
parameters: z.object({
|
|
258
|
+
projectRoot: z
|
|
259
|
+
.string()
|
|
260
|
+
.describe("Absolute path to the project root directory"),
|
|
261
|
+
taskId: z.string().describe("Task ID to update"),
|
|
262
|
+
title: z.string().optional().describe("New title"),
|
|
263
|
+
description: z.string().optional().describe("New description"),
|
|
264
|
+
priority: PrioritySchema.optional().describe("New priority"),
|
|
265
|
+
dependencies: z.array(z.string()).optional().describe("New dependencies"),
|
|
266
|
+
details: z.string().optional().describe("New implementation details"),
|
|
267
|
+
testStrategy: z.string().optional().describe("New test strategy"),
|
|
268
|
+
}),
|
|
269
|
+
execute: async (args) => {
|
|
270
|
+
const manager = new TaskManager(args.projectRoot);
|
|
271
|
+
if (!manager.isInitialized()) {
|
|
272
|
+
return {
|
|
273
|
+
content: [
|
|
274
|
+
{
|
|
275
|
+
type: "text",
|
|
276
|
+
text: "❌ RTaskmaster is not initialized. Run rtaskmaster_init first.",
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
const { projectRoot, taskId, ...updates } = args;
|
|
282
|
+
const task = manager.updateTask(taskId, updates);
|
|
283
|
+
if (!task) {
|
|
284
|
+
return {
|
|
285
|
+
content: [{ type: "text", text: `❌ Task "${taskId}" not found.` }],
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
content: [
|
|
290
|
+
{
|
|
291
|
+
type: "text",
|
|
292
|
+
text: `✅ Task ${task.id} updated!\n\nTitle: ${task.title}\nStatus: ${task.status}\nPriority: ${task.priority}`,
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
};
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
// ============ DELETE TASK ============
|
|
299
|
+
server.addTool({
|
|
300
|
+
name: "rtaskmaster_delete_task",
|
|
301
|
+
description: "Delete a task.",
|
|
302
|
+
parameters: TaskIdSchema,
|
|
303
|
+
execute: async (args) => {
|
|
304
|
+
const manager = new TaskManager(args.projectRoot);
|
|
305
|
+
if (!manager.isInitialized()) {
|
|
306
|
+
return {
|
|
307
|
+
content: [
|
|
308
|
+
{
|
|
309
|
+
type: "text",
|
|
310
|
+
text: "❌ RTaskmaster is not initialized. Run rtaskmaster_init first.",
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
const deleted = manager.deleteTask(args.taskId);
|
|
316
|
+
if (!deleted) {
|
|
317
|
+
return {
|
|
318
|
+
content: [
|
|
319
|
+
{ type: "text", text: `❌ Task "${args.taskId}" not found.` },
|
|
320
|
+
],
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
content: [{ type: "text", text: `✅ Task ${args.taskId} deleted.` }],
|
|
325
|
+
};
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
// ============ SET STATUS ============
|
|
329
|
+
server.addTool({
|
|
330
|
+
name: "rtaskmaster_set_status",
|
|
331
|
+
description: "Update the status of a task or subtask.",
|
|
332
|
+
parameters: z.object({
|
|
333
|
+
projectRoot: z
|
|
334
|
+
.string()
|
|
335
|
+
.describe("Absolute path to the project root directory"),
|
|
336
|
+
taskId: z
|
|
337
|
+
.string()
|
|
338
|
+
.describe('Task ID (e.g., "1") or subtask ID (e.g., "1.2")'),
|
|
339
|
+
status: StatusSchema.describe("New status"),
|
|
340
|
+
}),
|
|
341
|
+
execute: async (args) => {
|
|
342
|
+
const manager = new TaskManager(args.projectRoot);
|
|
343
|
+
if (!manager.isInitialized()) {
|
|
344
|
+
return {
|
|
345
|
+
content: [
|
|
346
|
+
{
|
|
347
|
+
type: "text",
|
|
348
|
+
text: "❌ RTaskmaster is not initialized. Run rtaskmaster_init first.",
|
|
349
|
+
},
|
|
350
|
+
],
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const result = manager.setStatus(args.taskId, args.status);
|
|
354
|
+
if (!result) {
|
|
355
|
+
return {
|
|
356
|
+
content: [
|
|
357
|
+
{ type: "text", text: `❌ Task "${args.taskId}" not found.` },
|
|
358
|
+
],
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
content: [
|
|
363
|
+
{
|
|
364
|
+
type: "text",
|
|
365
|
+
text: `✅ Status updated!\n\n${args.taskId}: ${result.title} → ${args.status}`,
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
};
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
// ============ ADD SUBTASK ============
|
|
372
|
+
server.addTool({
|
|
373
|
+
name: "rtaskmaster_add_subtask",
|
|
374
|
+
description: "Add a subtask to an existing task.",
|
|
375
|
+
parameters: z.object({
|
|
376
|
+
projectRoot: z
|
|
377
|
+
.string()
|
|
378
|
+
.describe("Absolute path to the project root directory"),
|
|
379
|
+
taskId: z.string().describe("Parent task ID"),
|
|
380
|
+
title: z.string().describe("Subtask title"),
|
|
381
|
+
description: z.string().optional().describe("Subtask description"),
|
|
382
|
+
}),
|
|
383
|
+
execute: async (args) => {
|
|
384
|
+
const manager = new TaskManager(args.projectRoot);
|
|
385
|
+
if (!manager.isInitialized()) {
|
|
386
|
+
return {
|
|
387
|
+
content: [
|
|
388
|
+
{
|
|
389
|
+
type: "text",
|
|
390
|
+
text: "❌ RTaskmaster is not initialized. Run rtaskmaster_init first.",
|
|
391
|
+
},
|
|
392
|
+
],
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
const subtask = manager.addSubtask(args.taskId, {
|
|
396
|
+
title: args.title,
|
|
397
|
+
description: args.description,
|
|
398
|
+
});
|
|
399
|
+
if (!subtask) {
|
|
400
|
+
return {
|
|
401
|
+
content: [
|
|
402
|
+
{ type: "text", text: `❌ Task "${args.taskId}" not found.` },
|
|
403
|
+
],
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
content: [
|
|
408
|
+
{
|
|
409
|
+
type: "text",
|
|
410
|
+
text: `✅ Subtask added!\n\nID: ${args.taskId}.${subtask.id}\nTitle: ${subtask.title}`,
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
};
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
// ============ NEXT TASK ============
|
|
417
|
+
server.addTool({
|
|
418
|
+
name: "rtaskmaster_next_task",
|
|
419
|
+
description: "Get the next task to work on based on priority and dependencies.",
|
|
420
|
+
parameters: ProjectRootSchema,
|
|
421
|
+
execute: async (args) => {
|
|
422
|
+
const manager = new TaskManager(args.projectRoot);
|
|
423
|
+
if (!manager.isInitialized()) {
|
|
424
|
+
return {
|
|
425
|
+
content: [
|
|
426
|
+
{
|
|
427
|
+
type: "text",
|
|
428
|
+
text: "❌ RTaskmaster is not initialized. Run rtaskmaster_init first.",
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
const task = manager.getNextTask();
|
|
434
|
+
const stats = manager.getStats();
|
|
435
|
+
if (!task) {
|
|
436
|
+
if (stats.total === 0) {
|
|
437
|
+
return {
|
|
438
|
+
content: [
|
|
439
|
+
{
|
|
440
|
+
type: "text",
|
|
441
|
+
text: "📋 No tasks yet. Create your first task using rtaskmaster_create_task.",
|
|
442
|
+
},
|
|
443
|
+
],
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
if (stats.done === stats.total) {
|
|
447
|
+
return {
|
|
448
|
+
content: [
|
|
449
|
+
{
|
|
450
|
+
type: "text",
|
|
451
|
+
text: "🎉 All tasks are complete! Great job!",
|
|
452
|
+
},
|
|
453
|
+
],
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
content: [
|
|
458
|
+
{
|
|
459
|
+
type: "text",
|
|
460
|
+
text: "⏳ No tasks available. Remaining tasks may be blocked or deferred.",
|
|
461
|
+
},
|
|
462
|
+
],
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
let output = `🎯 Next Task: [${task.id}] ${task.title}\n\n`;
|
|
466
|
+
output += `Priority: ${task.priority}\n`;
|
|
467
|
+
output += `Status: ${task.status}\n`;
|
|
468
|
+
output += `Description: ${task.description || "(none)"}\n`;
|
|
469
|
+
if (task.details) {
|
|
470
|
+
output += `\n📝 Details:\n${task.details}\n`;
|
|
471
|
+
}
|
|
472
|
+
if (task.subtasks.length > 0) {
|
|
473
|
+
const pendingSubtasks = task.subtasks.filter((s) => s.status !== "done");
|
|
474
|
+
if (pendingSubtasks.length > 0) {
|
|
475
|
+
output += `\n📋 Pending Subtasks:\n`;
|
|
476
|
+
pendingSubtasks.forEach((s) => {
|
|
477
|
+
output += ` ⏳ [${task.id}.${s.id}] ${s.title}\n`;
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
output += `\n💡 To start working: rtaskmaster_set_status with taskId="${task.id}" and status="in-progress"`;
|
|
482
|
+
return {
|
|
483
|
+
content: [{ type: "text", text: output }],
|
|
484
|
+
};
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
// ============ GET STATS ============
|
|
488
|
+
server.addTool({
|
|
489
|
+
name: "rtaskmaster_stats",
|
|
490
|
+
description: "Get task statistics and project progress.",
|
|
491
|
+
parameters: ProjectRootSchema,
|
|
492
|
+
execute: async (args) => {
|
|
493
|
+
const manager = new TaskManager(args.projectRoot);
|
|
494
|
+
if (!manager.isInitialized()) {
|
|
495
|
+
return {
|
|
496
|
+
content: [
|
|
497
|
+
{
|
|
498
|
+
type: "text",
|
|
499
|
+
text: "❌ RTaskmaster is not initialized. Run rtaskmaster_init first.",
|
|
500
|
+
},
|
|
501
|
+
],
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
const stats = manager.getStats();
|
|
505
|
+
const progressBar = (percent) => {
|
|
506
|
+
const filled = Math.round(percent / 10);
|
|
507
|
+
const empty = 10 - filled;
|
|
508
|
+
return "█".repeat(filled) + "░".repeat(empty);
|
|
509
|
+
};
|
|
510
|
+
return {
|
|
511
|
+
content: [
|
|
512
|
+
{
|
|
513
|
+
type: "text",
|
|
514
|
+
text: `📊 Project Statistics\n\n` +
|
|
515
|
+
`Progress: ${progressBar(stats.completionPercentage)} ${stats.completionPercentage}%\n\n` +
|
|
516
|
+
`✅ Done: ${stats.done}\n` +
|
|
517
|
+
`🔄 In Progress: ${stats.inProgress}\n` +
|
|
518
|
+
`⏳ Pending: ${stats.pending}\n` +
|
|
519
|
+
`🚫 Blocked: ${stats.blocked}\n` +
|
|
520
|
+
`⏸️ Deferred: ${stats.deferred}\n` +
|
|
521
|
+
`━━━━━━━━━━━━━━━━━━━━\n` +
|
|
522
|
+
`📋 Total: ${stats.total}`,
|
|
523
|
+
},
|
|
524
|
+
],
|
|
525
|
+
};
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
// ============ GENERATE TASKS.MD ============
|
|
529
|
+
server.addTool({
|
|
530
|
+
name: "rtaskmaster_generate_tasks_md",
|
|
531
|
+
description: "Generate a human-readable TASKS.md checklist file from the current tasks. Creates or updates the file in the project root.",
|
|
532
|
+
parameters: z.object({
|
|
533
|
+
projectRoot: z
|
|
534
|
+
.string()
|
|
535
|
+
.describe("Absolute path to the project root directory"),
|
|
536
|
+
includeDetails: z
|
|
537
|
+
.boolean()
|
|
538
|
+
.optional()
|
|
539
|
+
.describe("Include task details and test strategies (default: false)"),
|
|
540
|
+
}),
|
|
541
|
+
execute: async (args) => {
|
|
542
|
+
const manager = new TaskManager(args.projectRoot);
|
|
543
|
+
if (!manager.isInitialized()) {
|
|
544
|
+
return {
|
|
545
|
+
content: [
|
|
546
|
+
{
|
|
547
|
+
type: "text",
|
|
548
|
+
text: "❌ RTaskmaster is not initialized. Run rtaskmaster_init first.",
|
|
549
|
+
},
|
|
550
|
+
],
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
const tasks = manager.getTasks();
|
|
554
|
+
const stats = manager.getStats();
|
|
555
|
+
const getStatusEmoji = (status) => {
|
|
556
|
+
const emojis = {
|
|
557
|
+
pending: "",
|
|
558
|
+
"in-progress": "🔄 ",
|
|
559
|
+
done: "",
|
|
560
|
+
blocked: "🚫 ",
|
|
561
|
+
deferred: "⏸️ ",
|
|
562
|
+
};
|
|
563
|
+
return emojis[status] || "";
|
|
564
|
+
};
|
|
565
|
+
const getCheckbox = (status) => status === "done" ? "[x]" : "[ ]";
|
|
566
|
+
const formatTask = (task, includeDetails) => {
|
|
567
|
+
let line = `- ${getCheckbox(task.status)} ${getStatusEmoji(task.status)}${task.title} (#${task.id})`;
|
|
568
|
+
if (task.dependencies.length > 0) {
|
|
569
|
+
line += ` [depends on: ${task.dependencies.join(", ")}]`;
|
|
570
|
+
}
|
|
571
|
+
let output = line + "\n";
|
|
572
|
+
if (task.description && includeDetails) {
|
|
573
|
+
output += ` > ${task.description}\n`;
|
|
574
|
+
}
|
|
575
|
+
// Add subtasks
|
|
576
|
+
if (task.subtasks.length > 0) {
|
|
577
|
+
task.subtasks.forEach((s) => {
|
|
578
|
+
output += ` - ${getCheckbox(s.status)} ${getStatusEmoji(s.status)}${s.title} (#${task.id}.${s.id})\n`;
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
return output;
|
|
582
|
+
};
|
|
583
|
+
const highPriority = tasks.filter((t) => t.priority === "high");
|
|
584
|
+
const mediumPriority = tasks.filter((t) => t.priority === "medium");
|
|
585
|
+
const lowPriority = tasks.filter((t) => t.priority === "low");
|
|
586
|
+
let content = `# Project Tasks
|
|
587
|
+
|
|
588
|
+
> Generated by RTaskmaster RBuildAI | Last updated: ${new Date().toISOString()}
|
|
589
|
+
>
|
|
590
|
+
> Both humans and AI agents can update this file.
|
|
591
|
+
> Source of truth: \`.rtaskmaster/tasks.json\`
|
|
592
|
+
|
|
593
|
+
## 📊 Progress
|
|
594
|
+
|
|
595
|
+
| Status | Count |
|
|
596
|
+
|--------|-------|
|
|
597
|
+
| ✅ Done | ${stats.done} |
|
|
598
|
+
| 🔄 In Progress | ${stats.inProgress} |
|
|
599
|
+
| ⏳ Pending | ${stats.pending} |
|
|
600
|
+
| 🚫 Blocked | ${stats.blocked} |
|
|
601
|
+
| ⏸️ Deferred | ${stats.deferred} |
|
|
602
|
+
| **Total** | **${stats.total}** |
|
|
603
|
+
|
|
604
|
+
**Completion: ${stats.completionPercentage}%**
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
## ✅ Tasks
|
|
609
|
+
|
|
610
|
+
`;
|
|
611
|
+
if (highPriority.length > 0) {
|
|
612
|
+
content += `### 🔴 High Priority\n\n`;
|
|
613
|
+
highPriority.forEach((t) => {
|
|
614
|
+
content += formatTask(t, args.includeDetails || false);
|
|
615
|
+
});
|
|
616
|
+
content += "\n";
|
|
617
|
+
}
|
|
618
|
+
if (mediumPriority.length > 0) {
|
|
619
|
+
content += `### 🟡 Medium Priority\n\n`;
|
|
620
|
+
mediumPriority.forEach((t) => {
|
|
621
|
+
content += formatTask(t, args.includeDetails || false);
|
|
622
|
+
});
|
|
623
|
+
content += "\n";
|
|
624
|
+
}
|
|
625
|
+
if (lowPriority.length > 0) {
|
|
626
|
+
content += `### 🟢 Low Priority\n\n`;
|
|
627
|
+
lowPriority.forEach((t) => {
|
|
628
|
+
content += formatTask(t, args.includeDetails || false);
|
|
629
|
+
});
|
|
630
|
+
content += "\n";
|
|
631
|
+
}
|
|
632
|
+
if (tasks.length === 0) {
|
|
633
|
+
content += `*No tasks yet. Create your first task using \`rtaskmaster_create_task\`.*\n\n`;
|
|
634
|
+
}
|
|
635
|
+
content += `---
|
|
636
|
+
|
|
637
|
+
## 📝 Checklist Legend
|
|
638
|
+
|
|
639
|
+
- \`[ ]\` - Pending/Not started
|
|
640
|
+
- \`[x]\` - Completed
|
|
641
|
+
- 🔄 - In Progress
|
|
642
|
+
- 🚫 - Blocked
|
|
643
|
+
- ⏸️ - Deferred
|
|
644
|
+
|
|
645
|
+
---
|
|
646
|
+
|
|
647
|
+
*Managed by [RTaskmaster RBuildAI](https://github.com/RagnarPitla/RTaskmaster-rbuildai-mcp)*
|
|
648
|
+
`;
|
|
649
|
+
const tasksFilePath = path.join(args.projectRoot, "TASKS.md");
|
|
650
|
+
fs.writeFileSync(tasksFilePath, content, "utf-8");
|
|
651
|
+
return {
|
|
652
|
+
content: [
|
|
653
|
+
{
|
|
654
|
+
type: "text",
|
|
655
|
+
text: `✅ Generated TASKS.md successfully!\n\nFile: ${tasksFilePath}\n\n📊 Contains ${stats.total} tasks (${stats.completionPercentage}% complete)`,
|
|
656
|
+
},
|
|
657
|
+
],
|
|
658
|
+
};
|
|
659
|
+
},
|
|
660
|
+
});
|
|
661
|
+
// ============ PARSE TASKS FROM REQUIREMENTS ============
|
|
662
|
+
server.addTool({
|
|
663
|
+
name: "rtaskmaster_parse_prd",
|
|
664
|
+
description: "Parse a PRD (Product Requirements Document) or README file and create tasks from it. Provide either content directly or a file path.",
|
|
665
|
+
parameters: z.object({
|
|
666
|
+
projectRoot: z
|
|
667
|
+
.string()
|
|
668
|
+
.describe("Absolute path to the project root directory"),
|
|
669
|
+
filePath: z
|
|
670
|
+
.string()
|
|
671
|
+
.optional()
|
|
672
|
+
.describe("Path to a PRD, README, or requirements file to parse"),
|
|
673
|
+
content: z
|
|
674
|
+
.string()
|
|
675
|
+
.optional()
|
|
676
|
+
.describe("Direct content/requirements text to parse into tasks"),
|
|
677
|
+
defaultPriority: PrioritySchema.optional().describe("Default priority for created tasks (default: medium)"),
|
|
678
|
+
}),
|
|
679
|
+
execute: async (args) => {
|
|
680
|
+
const manager = new TaskManager(args.projectRoot);
|
|
681
|
+
if (!manager.isInitialized()) {
|
|
682
|
+
return {
|
|
683
|
+
content: [
|
|
684
|
+
{
|
|
685
|
+
type: "text",
|
|
686
|
+
text: "❌ RTaskmaster is not initialized. Run rtaskmaster_init first.",
|
|
687
|
+
},
|
|
688
|
+
],
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
let textContent = args.content;
|
|
692
|
+
if (!textContent && args.filePath) {
|
|
693
|
+
const resolvedPath = path.isAbsolute(args.filePath)
|
|
694
|
+
? args.filePath
|
|
695
|
+
: path.join(args.projectRoot, args.filePath);
|
|
696
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
697
|
+
return {
|
|
698
|
+
content: [
|
|
699
|
+
{ type: "text", text: `❌ File not found: ${resolvedPath}` },
|
|
700
|
+
],
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
textContent = fs.readFileSync(resolvedPath, "utf-8");
|
|
704
|
+
}
|
|
705
|
+
if (!textContent) {
|
|
706
|
+
return {
|
|
707
|
+
content: [
|
|
708
|
+
{
|
|
709
|
+
type: "text",
|
|
710
|
+
text: "❌ Please provide either filePath or content to parse.",
|
|
711
|
+
},
|
|
712
|
+
],
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
// Simple parsing: look for bullet points, numbered lists, headers
|
|
716
|
+
const lines = textContent.split("\n");
|
|
717
|
+
const extractedTasks = [];
|
|
718
|
+
for (const line of lines) {
|
|
719
|
+
const trimmed = line.trim();
|
|
720
|
+
// Match bullet points: - [ ] task, * task, - task
|
|
721
|
+
const bulletMatch = trimmed.match(/^[-*•]\s*(?:\[.\])?\s*(.+)$/);
|
|
722
|
+
if (bulletMatch &&
|
|
723
|
+
bulletMatch[1].length > 5 &&
|
|
724
|
+
bulletMatch[1].length < 200) {
|
|
725
|
+
extractedTasks.push(bulletMatch[1]);
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
// Match numbered lists: 1. task, 1) task
|
|
729
|
+
const numberedMatch = trimmed.match(/^\d+[.)]\s*(.+)$/);
|
|
730
|
+
if (numberedMatch &&
|
|
731
|
+
numberedMatch[1].length > 5 &&
|
|
732
|
+
numberedMatch[1].length < 200) {
|
|
733
|
+
extractedTasks.push(numberedMatch[1]);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (extractedTasks.length === 0) {
|
|
737
|
+
return {
|
|
738
|
+
content: [
|
|
739
|
+
{
|
|
740
|
+
type: "text",
|
|
741
|
+
text: "⚠️ No tasks could be extracted from the content.\n\nTip: Format requirements as bullet points or numbered lists for best results.",
|
|
742
|
+
},
|
|
743
|
+
],
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
// Create tasks from extracted items
|
|
747
|
+
const createdTasks = [];
|
|
748
|
+
const defaultPriority = args.defaultPriority || "medium";
|
|
749
|
+
for (const title of extractedTasks) {
|
|
750
|
+
const task = manager.createTask({
|
|
751
|
+
title: title.substring(0, 100), // Limit title length
|
|
752
|
+
priority: defaultPriority,
|
|
753
|
+
});
|
|
754
|
+
createdTasks.push(task);
|
|
755
|
+
}
|
|
756
|
+
const taskList = createdTasks
|
|
757
|
+
.map((t) => ` [${t.id}] ${t.title}`)
|
|
758
|
+
.join("\n");
|
|
759
|
+
return {
|
|
760
|
+
content: [
|
|
761
|
+
{
|
|
762
|
+
type: "text",
|
|
763
|
+
text: `✅ Created ${createdTasks.length} tasks from the content!\n\n📋 Tasks created:\n${taskList}\n\n💡 Use rtaskmaster_get_tasks to see all tasks, or rtaskmaster_update_task to modify details.`,
|
|
764
|
+
},
|
|
765
|
+
],
|
|
766
|
+
};
|
|
767
|
+
},
|
|
768
|
+
});
|
|
769
|
+
// ============ BULK SET STATUS ============
|
|
770
|
+
server.addTool({
|
|
771
|
+
name: "rtaskmaster_bulk_status",
|
|
772
|
+
description: "Update the status of multiple tasks at once.",
|
|
773
|
+
parameters: z.object({
|
|
774
|
+
projectRoot: z
|
|
775
|
+
.string()
|
|
776
|
+
.describe("Absolute path to the project root directory"),
|
|
777
|
+
taskIds: z.array(z.string()).describe("Array of task IDs to update"),
|
|
778
|
+
status: StatusSchema.describe("New status for all specified tasks"),
|
|
779
|
+
}),
|
|
780
|
+
execute: async (args) => {
|
|
781
|
+
const manager = new TaskManager(args.projectRoot);
|
|
782
|
+
if (!manager.isInitialized()) {
|
|
783
|
+
return {
|
|
784
|
+
content: [
|
|
785
|
+
{
|
|
786
|
+
type: "text",
|
|
787
|
+
text: "❌ RTaskmaster is not initialized. Run rtaskmaster_init first.",
|
|
788
|
+
},
|
|
789
|
+
],
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
const results = [];
|
|
793
|
+
let successCount = 0;
|
|
794
|
+
let failCount = 0;
|
|
795
|
+
for (const taskId of args.taskIds) {
|
|
796
|
+
const result = manager.setStatus(taskId, args.status);
|
|
797
|
+
if (result) {
|
|
798
|
+
results.push(`✅ [${taskId}] ${result.title} → ${args.status}`);
|
|
799
|
+
successCount++;
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
results.push(`❌ [${taskId}] Not found`);
|
|
803
|
+
failCount++;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return {
|
|
807
|
+
content: [
|
|
808
|
+
{
|
|
809
|
+
type: "text",
|
|
810
|
+
text: `📋 Bulk Status Update Complete\n\n${results.join("\n")}\n\n✅ Success: ${successCount} | ❌ Failed: ${failCount}`,
|
|
811
|
+
},
|
|
812
|
+
],
|
|
813
|
+
};
|
|
814
|
+
},
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
//# sourceMappingURL=tools.js.map
|