mcp-task-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +317 -0
- package/dist/agents.d.ts +25 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +93 -0
- package/dist/agents.js.map +1 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +28 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +38 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +70 -0
- package/dist/context.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +400 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts.d.ts +22 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +259 -0
- package/dist/prompts.js.map +1 -0
- package/dist/storage.d.ts +26 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +293 -0
- package/dist/storage.js.map +1 -0
- package/dist/templates.d.ts +18 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +441 -0
- package/dist/templates.js.map +1 -0
- package/dist/tools.d.ts +401 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +732 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +42 -0
package/dist/tools.js
ADDED
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
3
|
+
import { dirname } from "path";
|
|
4
|
+
import { existsSync, statSync, readdirSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { loadTasks, saveTasks, generateId, findTask, updateTask as updateTaskInStore, addSubtaskToTask, } from "./storage.js";
|
|
7
|
+
import { hasPermission, canActOnTask, getOrCreateAgent } from "./agents.js";
|
|
8
|
+
import { expandTaskPrompt, parsePrdPrompt, researchTaskPrompt, analyzeComplexityPrompt, checkCompliancePrompt, } from "./prompts.js";
|
|
9
|
+
import { resolvePath } from "./config.js";
|
|
10
|
+
import { getTemplateFiles, getInitialTasksJson } from "./templates.js";
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Schema Definitions
|
|
13
|
+
// ============================================================================
|
|
14
|
+
const AgentContextSchema = z.object({
|
|
15
|
+
agent_id: z.string().optional(),
|
|
16
|
+
role: z.enum(["planner", "worker", "judge"]).optional(),
|
|
17
|
+
});
|
|
18
|
+
export const ListTasksSchema = AgentContextSchema.extend({
|
|
19
|
+
status: z
|
|
20
|
+
.enum([
|
|
21
|
+
"pending",
|
|
22
|
+
"claimed",
|
|
23
|
+
"in_progress",
|
|
24
|
+
"review",
|
|
25
|
+
"approved",
|
|
26
|
+
"rejected",
|
|
27
|
+
"completed",
|
|
28
|
+
"cancelled",
|
|
29
|
+
"done",
|
|
30
|
+
"deprecated",
|
|
31
|
+
])
|
|
32
|
+
.optional(),
|
|
33
|
+
assigned_to: z.string().optional(),
|
|
34
|
+
});
|
|
35
|
+
export const GetTaskSchema = AgentContextSchema.extend({
|
|
36
|
+
task_id: z.string(),
|
|
37
|
+
});
|
|
38
|
+
export const AddTaskSchema = AgentContextSchema.extend({
|
|
39
|
+
title: z.string().optional(),
|
|
40
|
+
content: z.string().optional(), // Legacy alias for title
|
|
41
|
+
description: z.string().optional(),
|
|
42
|
+
details: z.string().optional(),
|
|
43
|
+
testStrategy: z.string().optional(),
|
|
44
|
+
priority: z.enum(["low", "medium", "high", "critical"]).default("medium"),
|
|
45
|
+
dependencies: z.array(z.string()).optional(),
|
|
46
|
+
});
|
|
47
|
+
export const UpdateTaskSchema = AgentContextSchema.extend({
|
|
48
|
+
task_id: z.string(),
|
|
49
|
+
title: z.string().optional(),
|
|
50
|
+
content: z.string().optional(), // Legacy alias for title
|
|
51
|
+
description: z.string().optional(),
|
|
52
|
+
details: z.string().optional(),
|
|
53
|
+
testStrategy: z.string().optional(),
|
|
54
|
+
status: z
|
|
55
|
+
.enum([
|
|
56
|
+
"pending",
|
|
57
|
+
"claimed",
|
|
58
|
+
"in_progress",
|
|
59
|
+
"review",
|
|
60
|
+
"approved",
|
|
61
|
+
"rejected",
|
|
62
|
+
"completed",
|
|
63
|
+
"cancelled",
|
|
64
|
+
"done",
|
|
65
|
+
"deprecated",
|
|
66
|
+
])
|
|
67
|
+
.optional(),
|
|
68
|
+
priority: z.enum(["low", "medium", "high", "critical"]).optional(),
|
|
69
|
+
metadata: z.record(z.unknown()).optional(),
|
|
70
|
+
});
|
|
71
|
+
export const CompleteTaskSchema = AgentContextSchema.extend({
|
|
72
|
+
task_id: z.string(),
|
|
73
|
+
});
|
|
74
|
+
export const NextTaskSchema = AgentContextSchema.extend({});
|
|
75
|
+
export const ClaimTaskSchema = AgentContextSchema.extend({
|
|
76
|
+
task_id: z.string(),
|
|
77
|
+
});
|
|
78
|
+
export const ReleaseTaskSchema = AgentContextSchema.extend({
|
|
79
|
+
task_id: z.string(),
|
|
80
|
+
});
|
|
81
|
+
export const HandoffTaskSchema = AgentContextSchema.extend({
|
|
82
|
+
task_id: z.string(),
|
|
83
|
+
to_role: z.enum(["planner", "worker", "judge"]),
|
|
84
|
+
notes: z.string().optional(),
|
|
85
|
+
});
|
|
86
|
+
export const ReviewTaskSchema = AgentContextSchema.extend({
|
|
87
|
+
task_id: z.string(),
|
|
88
|
+
});
|
|
89
|
+
export const ApproveTaskSchema = AgentContextSchema.extend({
|
|
90
|
+
task_id: z.string(),
|
|
91
|
+
notes: z.string().optional(),
|
|
92
|
+
});
|
|
93
|
+
export const RejectTaskSchema = AgentContextSchema.extend({
|
|
94
|
+
task_id: z.string(),
|
|
95
|
+
feedback: z.string(),
|
|
96
|
+
});
|
|
97
|
+
export const ExpandTaskSchema = AgentContextSchema.extend({
|
|
98
|
+
task_id: z.string(),
|
|
99
|
+
});
|
|
100
|
+
export const AddSubtaskSchema = AgentContextSchema.extend({
|
|
101
|
+
parent_id: z.string(),
|
|
102
|
+
title: z.string().optional(),
|
|
103
|
+
content: z.string().optional(), // Legacy alias for title
|
|
104
|
+
description: z.string().optional(),
|
|
105
|
+
details: z.string().optional(),
|
|
106
|
+
dependencies: z.array(z.number()).optional(),
|
|
107
|
+
});
|
|
108
|
+
export const SetDependenciesSchema = AgentContextSchema.extend({
|
|
109
|
+
task_id: z.string(),
|
|
110
|
+
dependencies: z.array(z.string()),
|
|
111
|
+
});
|
|
112
|
+
export const RemoveTaskSchema = AgentContextSchema.extend({
|
|
113
|
+
task_id: z.string(),
|
|
114
|
+
});
|
|
115
|
+
export const ParsePrdSchema = AgentContextSchema.extend({
|
|
116
|
+
prd_content: z.string(),
|
|
117
|
+
});
|
|
118
|
+
export const ResearchTaskSchema = AgentContextSchema.extend({
|
|
119
|
+
task_id: z.string(),
|
|
120
|
+
topic: z.string().optional(),
|
|
121
|
+
});
|
|
122
|
+
export const AnalyzeComplexitySchema = AgentContextSchema.extend({
|
|
123
|
+
task_id: z.string(),
|
|
124
|
+
});
|
|
125
|
+
export const CheckComplianceSchema = AgentContextSchema.extend({
|
|
126
|
+
path: z.string(),
|
|
127
|
+
fix: z.boolean().default(false),
|
|
128
|
+
});
|
|
129
|
+
export const InitProjectSchema = z.object({
|
|
130
|
+
project_name: z.string().optional(),
|
|
131
|
+
force: z.boolean().default(false),
|
|
132
|
+
});
|
|
133
|
+
function result(data) {
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function error(message) {
|
|
139
|
+
return {
|
|
140
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function checkPermission(role, tool) {
|
|
144
|
+
if (!hasPermission(role, tool)) {
|
|
145
|
+
return error(`Role '${role}' does not have permission to use '${tool}'`);
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
// ============================================================================
|
|
150
|
+
// Core Tools
|
|
151
|
+
// ============================================================================
|
|
152
|
+
export async function listTasks(config, params) {
|
|
153
|
+
const permError = checkPermission(params.role, "list_tasks");
|
|
154
|
+
if (permError)
|
|
155
|
+
return permError;
|
|
156
|
+
const store = await loadTasks(config);
|
|
157
|
+
let tasks = store.tasks;
|
|
158
|
+
if (params.status) {
|
|
159
|
+
tasks = tasks.filter((t) => t.status === params.status);
|
|
160
|
+
}
|
|
161
|
+
if (params.assigned_to) {
|
|
162
|
+
tasks = tasks.filter((t) => t.assigned_to === params.assigned_to);
|
|
163
|
+
}
|
|
164
|
+
return result({ tasks, total: tasks.length });
|
|
165
|
+
}
|
|
166
|
+
export async function getTask(config, params) {
|
|
167
|
+
const permError = checkPermission(params.role, "get_task");
|
|
168
|
+
if (permError)
|
|
169
|
+
return permError;
|
|
170
|
+
const store = await loadTasks(config);
|
|
171
|
+
const task = findTask(store, params.task_id);
|
|
172
|
+
if (!task) {
|
|
173
|
+
return error(`Task '${params.task_id}' not found`);
|
|
174
|
+
}
|
|
175
|
+
return result({ task });
|
|
176
|
+
}
|
|
177
|
+
export async function addTask(config, params) {
|
|
178
|
+
const permError = checkPermission(params.role, "add_task");
|
|
179
|
+
if (permError)
|
|
180
|
+
return permError;
|
|
181
|
+
// Use title or content (legacy)
|
|
182
|
+
const title = params.title || params.content;
|
|
183
|
+
if (!title) {
|
|
184
|
+
return error("Either 'title' or 'content' is required");
|
|
185
|
+
}
|
|
186
|
+
const store = await loadTasks(config);
|
|
187
|
+
const id = generateId(store);
|
|
188
|
+
const task = {
|
|
189
|
+
id,
|
|
190
|
+
title,
|
|
191
|
+
description: params.description,
|
|
192
|
+
details: params.details,
|
|
193
|
+
testStrategy: params.testStrategy,
|
|
194
|
+
status: "pending",
|
|
195
|
+
priority: params.priority,
|
|
196
|
+
dependencies: params.dependencies,
|
|
197
|
+
created_at: new Date().toISOString(),
|
|
198
|
+
updated_at: new Date().toISOString(),
|
|
199
|
+
};
|
|
200
|
+
store.tasks.push(task);
|
|
201
|
+
await saveTasks(config, store);
|
|
202
|
+
return result({ task, message: `Task ${id} created` });
|
|
203
|
+
}
|
|
204
|
+
export async function updateTask(config, params) {
|
|
205
|
+
const permError = checkPermission(params.role, "update_task");
|
|
206
|
+
if (permError)
|
|
207
|
+
return permError;
|
|
208
|
+
const store = await loadTasks(config);
|
|
209
|
+
const canAct = canActOnTask(store, params.task_id, params.agent_id, "update");
|
|
210
|
+
if (!canAct.allowed) {
|
|
211
|
+
return error(canAct.reason);
|
|
212
|
+
}
|
|
213
|
+
const updates = {};
|
|
214
|
+
// Use title or content (legacy)
|
|
215
|
+
if (params.title)
|
|
216
|
+
updates.title = params.title;
|
|
217
|
+
else if (params.content)
|
|
218
|
+
updates.title = params.content;
|
|
219
|
+
if (params.description)
|
|
220
|
+
updates.description = params.description;
|
|
221
|
+
if (params.details)
|
|
222
|
+
updates.details = params.details;
|
|
223
|
+
if (params.testStrategy)
|
|
224
|
+
updates.testStrategy = params.testStrategy;
|
|
225
|
+
if (params.status)
|
|
226
|
+
updates.status = params.status;
|
|
227
|
+
if (params.priority)
|
|
228
|
+
updates.priority = params.priority;
|
|
229
|
+
if (params.metadata)
|
|
230
|
+
updates.metadata = params.metadata;
|
|
231
|
+
const task = updateTaskInStore(store, params.task_id, updates);
|
|
232
|
+
if (!task) {
|
|
233
|
+
return error(`Task '${params.task_id}' not found`);
|
|
234
|
+
}
|
|
235
|
+
await saveTasks(config, store);
|
|
236
|
+
return result({ task, message: `Task ${params.task_id} updated` });
|
|
237
|
+
}
|
|
238
|
+
export async function completeTask(config, params) {
|
|
239
|
+
const permError = checkPermission(params.role, "complete_task");
|
|
240
|
+
if (permError)
|
|
241
|
+
return permError;
|
|
242
|
+
const store = await loadTasks(config);
|
|
243
|
+
const canAct = canActOnTask(store, params.task_id, params.agent_id, "complete");
|
|
244
|
+
if (!canAct.allowed) {
|
|
245
|
+
return error(canAct.reason);
|
|
246
|
+
}
|
|
247
|
+
const task = updateTaskInStore(store, params.task_id, {
|
|
248
|
+
status: "completed",
|
|
249
|
+
completed_at: new Date().toISOString(),
|
|
250
|
+
});
|
|
251
|
+
if (!task) {
|
|
252
|
+
return error(`Task '${params.task_id}' not found`);
|
|
253
|
+
}
|
|
254
|
+
await saveTasks(config, store);
|
|
255
|
+
return result({ task, message: `Task ${params.task_id} completed` });
|
|
256
|
+
}
|
|
257
|
+
export async function nextTask(config, params) {
|
|
258
|
+
const permError = checkPermission(params.role, "next_task");
|
|
259
|
+
if (permError)
|
|
260
|
+
return permError;
|
|
261
|
+
const store = await loadTasks(config);
|
|
262
|
+
// Find top-level tasks that are pending and have all dependencies met
|
|
263
|
+
const pendingTasks = store.tasks.filter((t) => {
|
|
264
|
+
if (t.parent_id)
|
|
265
|
+
return false; // Skip subtasks
|
|
266
|
+
if (t.status !== "pending")
|
|
267
|
+
return false;
|
|
268
|
+
// Check dependencies
|
|
269
|
+
if (t.dependencies && t.dependencies.length > 0) {
|
|
270
|
+
const unmetDeps = t.dependencies.filter((depId) => {
|
|
271
|
+
const dep = findTask(store, depId);
|
|
272
|
+
return !dep || (dep.status !== "completed" && dep.status !== "done");
|
|
273
|
+
});
|
|
274
|
+
if (unmetDeps.length > 0)
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
return true;
|
|
278
|
+
});
|
|
279
|
+
if (pendingTasks.length === 0) {
|
|
280
|
+
return result({
|
|
281
|
+
task: null,
|
|
282
|
+
message: "No tasks available (all completed or blocked by dependencies)",
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
// Sort by priority (critical > high > medium > low)
|
|
286
|
+
const priorityOrder = {
|
|
287
|
+
critical: 0,
|
|
288
|
+
high: 1,
|
|
289
|
+
medium: 2,
|
|
290
|
+
low: 3,
|
|
291
|
+
};
|
|
292
|
+
pendingTasks.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
|
|
293
|
+
const nextTask = pendingTasks[0];
|
|
294
|
+
return result({
|
|
295
|
+
task: nextTask,
|
|
296
|
+
message: `Recommended next task: ${nextTask.id}`,
|
|
297
|
+
available_count: pendingTasks.length,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
// ============================================================================
|
|
301
|
+
// Multi-Agent Coordination Tools
|
|
302
|
+
// ============================================================================
|
|
303
|
+
export async function claimTask(config, params) {
|
|
304
|
+
const permError = checkPermission(params.role, "claim_task");
|
|
305
|
+
if (permError)
|
|
306
|
+
return permError;
|
|
307
|
+
const store = await loadTasks(config);
|
|
308
|
+
const task = findTask(store, params.task_id);
|
|
309
|
+
if (!task) {
|
|
310
|
+
return error(`Task '${params.task_id}' not found`);
|
|
311
|
+
}
|
|
312
|
+
if (task.assigned_to) {
|
|
313
|
+
return error(`Task '${params.task_id}' is already assigned to ${task.assigned_to}`);
|
|
314
|
+
}
|
|
315
|
+
if (task.status !== "pending") {
|
|
316
|
+
return error(`Task '${params.task_id}' is not pending (status: ${task.status})`);
|
|
317
|
+
}
|
|
318
|
+
// Check dependencies
|
|
319
|
+
if (task.dependencies && task.dependencies.length > 0) {
|
|
320
|
+
const unmetDeps = task.dependencies.filter((depId) => {
|
|
321
|
+
const dep = findTask(store, depId);
|
|
322
|
+
return !dep || (dep.status !== "completed" && dep.status !== "done");
|
|
323
|
+
});
|
|
324
|
+
if (unmetDeps.length > 0) {
|
|
325
|
+
return error(`Task has unmet dependencies: ${unmetDeps.join(", ")}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const agentId = params.agent_id || "default-worker";
|
|
329
|
+
if (params.agent_id && params.role) {
|
|
330
|
+
getOrCreateAgent(store, params.agent_id, params.role);
|
|
331
|
+
}
|
|
332
|
+
updateTaskInStore(store, params.task_id, {
|
|
333
|
+
status: "claimed",
|
|
334
|
+
assigned_to: agentId,
|
|
335
|
+
claimed_at: new Date().toISOString(),
|
|
336
|
+
});
|
|
337
|
+
await saveTasks(config, store);
|
|
338
|
+
return result({ task, message: `Task ${params.task_id} claimed by ${agentId}` });
|
|
339
|
+
}
|
|
340
|
+
export async function releaseTask(config, params) {
|
|
341
|
+
const permError = checkPermission(params.role, "release_task");
|
|
342
|
+
if (permError)
|
|
343
|
+
return permError;
|
|
344
|
+
const store = await loadTasks(config);
|
|
345
|
+
const canAct = canActOnTask(store, params.task_id, params.agent_id, "release");
|
|
346
|
+
if (!canAct.allowed) {
|
|
347
|
+
return error(canAct.reason);
|
|
348
|
+
}
|
|
349
|
+
const task = updateTaskInStore(store, params.task_id, {
|
|
350
|
+
status: "pending",
|
|
351
|
+
assigned_to: undefined,
|
|
352
|
+
claimed_at: undefined,
|
|
353
|
+
});
|
|
354
|
+
if (!task) {
|
|
355
|
+
return error(`Task '${params.task_id}' not found`);
|
|
356
|
+
}
|
|
357
|
+
await saveTasks(config, store);
|
|
358
|
+
return result({ task, message: `Task ${params.task_id} released` });
|
|
359
|
+
}
|
|
360
|
+
export async function handoffTask(config, params) {
|
|
361
|
+
const permError = checkPermission(params.role, "handoff_task");
|
|
362
|
+
if (permError)
|
|
363
|
+
return permError;
|
|
364
|
+
const store = await loadTasks(config);
|
|
365
|
+
const canAct = canActOnTask(store, params.task_id, params.agent_id, "release");
|
|
366
|
+
if (!canAct.allowed) {
|
|
367
|
+
return error(canAct.reason);
|
|
368
|
+
}
|
|
369
|
+
const newStatus = params.to_role === "judge" ? "review" : "pending";
|
|
370
|
+
const task = updateTaskInStore(store, params.task_id, {
|
|
371
|
+
status: newStatus,
|
|
372
|
+
assigned_to: undefined,
|
|
373
|
+
claimed_at: undefined,
|
|
374
|
+
metadata: params.notes
|
|
375
|
+
? { ...findTask(store, params.task_id)?.metadata, handoff_notes: params.notes }
|
|
376
|
+
: undefined,
|
|
377
|
+
});
|
|
378
|
+
if (!task) {
|
|
379
|
+
return error(`Task '${params.task_id}' not found`);
|
|
380
|
+
}
|
|
381
|
+
await saveTasks(config, store);
|
|
382
|
+
return result({
|
|
383
|
+
task,
|
|
384
|
+
message: `Task ${params.task_id} handed off to ${params.to_role}`,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
export async function reviewTask(config, params) {
|
|
388
|
+
const permError = checkPermission(params.role, "review_task");
|
|
389
|
+
if (permError)
|
|
390
|
+
return permError;
|
|
391
|
+
const store = await loadTasks(config);
|
|
392
|
+
const task = findTask(store, params.task_id);
|
|
393
|
+
if (!task) {
|
|
394
|
+
return error(`Task '${params.task_id}' not found`);
|
|
395
|
+
}
|
|
396
|
+
return result({
|
|
397
|
+
task,
|
|
398
|
+
message: `Review task ${params.task_id}. Use approve_task or reject_task when done.`,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
export async function approveTask(config, params) {
|
|
402
|
+
const permError = checkPermission(params.role, "approve_task");
|
|
403
|
+
if (permError)
|
|
404
|
+
return permError;
|
|
405
|
+
const store = await loadTasks(config);
|
|
406
|
+
const task = findTask(store, params.task_id);
|
|
407
|
+
if (!task) {
|
|
408
|
+
return error(`Task '${params.task_id}' not found`);
|
|
409
|
+
}
|
|
410
|
+
if (task.status !== "review") {
|
|
411
|
+
return error(`Task '${params.task_id}' is not in review status`);
|
|
412
|
+
}
|
|
413
|
+
updateTaskInStore(store, params.task_id, {
|
|
414
|
+
status: "completed",
|
|
415
|
+
completed_at: new Date().toISOString(),
|
|
416
|
+
metadata: params.notes
|
|
417
|
+
? { ...task.metadata, approval_notes: params.notes }
|
|
418
|
+
: task.metadata,
|
|
419
|
+
});
|
|
420
|
+
await saveTasks(config, store);
|
|
421
|
+
return result({ task, message: `Task ${params.task_id} approved and completed` });
|
|
422
|
+
}
|
|
423
|
+
export async function rejectTask(config, params) {
|
|
424
|
+
const permError = checkPermission(params.role, "reject_task");
|
|
425
|
+
if (permError)
|
|
426
|
+
return permError;
|
|
427
|
+
const store = await loadTasks(config);
|
|
428
|
+
const task = findTask(store, params.task_id);
|
|
429
|
+
if (!task) {
|
|
430
|
+
return error(`Task '${params.task_id}' not found`);
|
|
431
|
+
}
|
|
432
|
+
if (task.status !== "review") {
|
|
433
|
+
return error(`Task '${params.task_id}' is not in review status`);
|
|
434
|
+
}
|
|
435
|
+
updateTaskInStore(store, params.task_id, {
|
|
436
|
+
status: "rejected",
|
|
437
|
+
metadata: { ...task.metadata, rejection_feedback: params.feedback },
|
|
438
|
+
});
|
|
439
|
+
await saveTasks(config, store);
|
|
440
|
+
return result({
|
|
441
|
+
task,
|
|
442
|
+
message: `Task ${params.task_id} rejected. Feedback: ${params.feedback}`,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
// ============================================================================
|
|
446
|
+
// Task Breakdown Tools
|
|
447
|
+
// ============================================================================
|
|
448
|
+
export async function expandTask(config, params) {
|
|
449
|
+
const permError = checkPermission(params.role, "expand_task");
|
|
450
|
+
if (permError)
|
|
451
|
+
return permError;
|
|
452
|
+
const store = await loadTasks(config);
|
|
453
|
+
const task = findTask(store, params.task_id);
|
|
454
|
+
if (!task) {
|
|
455
|
+
return error(`Task '${params.task_id}' not found`);
|
|
456
|
+
}
|
|
457
|
+
const promptResult = expandTaskPrompt(task);
|
|
458
|
+
return result(promptResult);
|
|
459
|
+
}
|
|
460
|
+
export async function addSubtask(config, params) {
|
|
461
|
+
const permError = checkPermission(params.role, "add_subtask");
|
|
462
|
+
if (permError)
|
|
463
|
+
return permError;
|
|
464
|
+
const store = await loadTasks(config);
|
|
465
|
+
const parent = findTask(store, params.parent_id);
|
|
466
|
+
if (!parent) {
|
|
467
|
+
return error(`Parent task '${params.parent_id}' not found`);
|
|
468
|
+
}
|
|
469
|
+
// Use title or content (legacy)
|
|
470
|
+
const title = params.title || params.content;
|
|
471
|
+
if (!title) {
|
|
472
|
+
return error("Either 'title' or 'content' is required");
|
|
473
|
+
}
|
|
474
|
+
const subtask = addSubtaskToTask(parent, {
|
|
475
|
+
title,
|
|
476
|
+
description: params.description,
|
|
477
|
+
details: params.details,
|
|
478
|
+
status: "pending",
|
|
479
|
+
dependencies: params.dependencies,
|
|
480
|
+
});
|
|
481
|
+
await saveTasks(config, store);
|
|
482
|
+
return result({ subtask, message: `Subtask ${subtask.id} added to task ${params.parent_id}` });
|
|
483
|
+
}
|
|
484
|
+
export async function setDependencies(config, params) {
|
|
485
|
+
const permError = checkPermission(params.role, "set_dependencies");
|
|
486
|
+
if (permError)
|
|
487
|
+
return permError;
|
|
488
|
+
const store = await loadTasks(config);
|
|
489
|
+
const task = findTask(store, params.task_id);
|
|
490
|
+
if (!task) {
|
|
491
|
+
return error(`Task '${params.task_id}' not found`);
|
|
492
|
+
}
|
|
493
|
+
// Validate all dependencies exist
|
|
494
|
+
for (const depId of params.dependencies) {
|
|
495
|
+
if (!findTask(store, depId)) {
|
|
496
|
+
return error(`Dependency task '${depId}' not found`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
// Check for circular dependencies (simple check)
|
|
500
|
+
if (params.dependencies.includes(params.task_id)) {
|
|
501
|
+
return error("Task cannot depend on itself");
|
|
502
|
+
}
|
|
503
|
+
updateTaskInStore(store, params.task_id, {
|
|
504
|
+
dependencies: params.dependencies,
|
|
505
|
+
});
|
|
506
|
+
await saveTasks(config, store);
|
|
507
|
+
return result({
|
|
508
|
+
task,
|
|
509
|
+
message: `Dependencies set for task ${params.task_id}: ${params.dependencies.join(", ")}`,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
export async function removeTask(config, params) {
|
|
513
|
+
const permError = checkPermission(params.role, "remove_task");
|
|
514
|
+
if (permError)
|
|
515
|
+
return permError;
|
|
516
|
+
const store = await loadTasks(config);
|
|
517
|
+
const taskIndex = store.tasks.findIndex((t) => t.id === params.task_id);
|
|
518
|
+
if (taskIndex === -1) {
|
|
519
|
+
return error(`Task '${params.task_id}' not found`);
|
|
520
|
+
}
|
|
521
|
+
const task = store.tasks[taskIndex];
|
|
522
|
+
// Remove task
|
|
523
|
+
store.tasks.splice(taskIndex, 1);
|
|
524
|
+
// Remove this task from any dependencies
|
|
525
|
+
for (const t of store.tasks) {
|
|
526
|
+
if (t.dependencies) {
|
|
527
|
+
t.dependencies = t.dependencies.filter((id) => id !== params.task_id);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
await saveTasks(config, store);
|
|
531
|
+
return result({ message: `Task ${params.task_id} removed` });
|
|
532
|
+
}
|
|
533
|
+
// ============================================================================
|
|
534
|
+
// Prompt-Based Tools
|
|
535
|
+
// ============================================================================
|
|
536
|
+
export async function parsePrd(config, params) {
|
|
537
|
+
const permError = checkPermission(params.role, "parse_prd");
|
|
538
|
+
if (permError)
|
|
539
|
+
return permError;
|
|
540
|
+
const promptResult = parsePrdPrompt(params.prd_content);
|
|
541
|
+
return result(promptResult);
|
|
542
|
+
}
|
|
543
|
+
export async function researchTask(config, params) {
|
|
544
|
+
const permError = checkPermission(params.role, "research_task");
|
|
545
|
+
if (permError)
|
|
546
|
+
return permError;
|
|
547
|
+
const store = await loadTasks(config);
|
|
548
|
+
const task = findTask(store, params.task_id);
|
|
549
|
+
if (!task) {
|
|
550
|
+
return error(`Task '${params.task_id}' not found`);
|
|
551
|
+
}
|
|
552
|
+
const promptResult = researchTaskPrompt(task, params.topic);
|
|
553
|
+
return result(promptResult);
|
|
554
|
+
}
|
|
555
|
+
export async function analyzeComplexity(config, params) {
|
|
556
|
+
const permError = checkPermission(params.role, "analyze_complexity");
|
|
557
|
+
if (permError)
|
|
558
|
+
return permError;
|
|
559
|
+
const store = await loadTasks(config);
|
|
560
|
+
const task = findTask(store, params.task_id);
|
|
561
|
+
if (!task) {
|
|
562
|
+
return error(`Task '${params.task_id}' not found`);
|
|
563
|
+
}
|
|
564
|
+
const promptResult = analyzeComplexityPrompt(task);
|
|
565
|
+
return result(promptResult);
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Read file contents, handling text files only
|
|
569
|
+
*/
|
|
570
|
+
async function readFileContent(filePath) {
|
|
571
|
+
const content = await readFile(filePath, "utf-8");
|
|
572
|
+
return content;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Get all text files in a directory (non-recursive for safety)
|
|
576
|
+
*/
|
|
577
|
+
function getTextFilesInDir(dirPath) {
|
|
578
|
+
const textExtensions = new Set([
|
|
579
|
+
".md", ".txt", ".json", ".yaml", ".yml", ".ts", ".tsx", ".js", ".jsx",
|
|
580
|
+
".html", ".css", ".scss", ".py", ".rb", ".go", ".rs", ".toml", ".ini",
|
|
581
|
+
".sh", ".bash", ".zsh", ".fish", ".mdx", ".astro", ".vue", ".svelte",
|
|
582
|
+
]);
|
|
583
|
+
const files = [];
|
|
584
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
585
|
+
for (const entry of entries) {
|
|
586
|
+
if (entry.isFile()) {
|
|
587
|
+
const ext = entry.name.substring(entry.name.lastIndexOf("."));
|
|
588
|
+
if (textExtensions.has(ext)) {
|
|
589
|
+
files.push(join(dirPath, entry.name));
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return files;
|
|
594
|
+
}
|
|
595
|
+
export async function checkCompliance(config, params) {
|
|
596
|
+
// No permission check - anyone can check compliance
|
|
597
|
+
const fullPath = resolvePath(config, params.path);
|
|
598
|
+
if (!existsSync(fullPath)) {
|
|
599
|
+
return error(`Path not found: ${params.path}`);
|
|
600
|
+
}
|
|
601
|
+
const stat = statSync(fullPath);
|
|
602
|
+
let content;
|
|
603
|
+
let pathDescription;
|
|
604
|
+
if (stat.isDirectory()) {
|
|
605
|
+
// Read all text files in directory
|
|
606
|
+
const files = getTextFilesInDir(fullPath);
|
|
607
|
+
if (files.length === 0) {
|
|
608
|
+
return error(`No text files found in directory: ${params.path}`);
|
|
609
|
+
}
|
|
610
|
+
const fileContents = [];
|
|
611
|
+
for (const file of files.slice(0, 10)) {
|
|
612
|
+
// Limit to 10 files
|
|
613
|
+
try {
|
|
614
|
+
const fileContent = await readFileContent(file);
|
|
615
|
+
const relativePath = file.replace(config.workspaceRoot + "/", "");
|
|
616
|
+
fileContents.push(`### ${relativePath}\n\`\`\`\n${fileContent.slice(0, 2000)}\n\`\`\``);
|
|
617
|
+
}
|
|
618
|
+
catch {
|
|
619
|
+
// Skip files that can't be read
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
content = fileContents.join("\n\n");
|
|
623
|
+
pathDescription = `${params.path} (${files.length} files${files.length > 10 ? ", showing first 10" : ""})`;
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
// Single file
|
|
627
|
+
try {
|
|
628
|
+
content = await readFileContent(fullPath);
|
|
629
|
+
pathDescription = params.path;
|
|
630
|
+
}
|
|
631
|
+
catch {
|
|
632
|
+
return error(`Could not read file: ${params.path}`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
// Truncate very large content
|
|
636
|
+
if (content.length > 20000) {
|
|
637
|
+
content = content.slice(0, 20000) + "\n\n... (truncated)";
|
|
638
|
+
}
|
|
639
|
+
const promptResult = checkCompliancePrompt(pathDescription, content, params.fix);
|
|
640
|
+
return result(promptResult);
|
|
641
|
+
}
|
|
642
|
+
// ============================================================================
|
|
643
|
+
// Project Initialization
|
|
644
|
+
// ============================================================================
|
|
645
|
+
/**
|
|
646
|
+
* Ensure directory exists
|
|
647
|
+
*/
|
|
648
|
+
async function ensureDir(filePath) {
|
|
649
|
+
const dir = dirname(filePath);
|
|
650
|
+
if (!existsSync(dir)) {
|
|
651
|
+
await mkdir(dir, { recursive: true });
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Initialize a project with agent-kit, memory_bank, and cursor rules
|
|
656
|
+
*/
|
|
657
|
+
export async function initProject(config, params) {
|
|
658
|
+
// Derive project name from workspace root if not provided
|
|
659
|
+
const projectName = params.project_name || config.workspaceRoot.split("/").pop() || "project";
|
|
660
|
+
const templateFiles = getTemplateFiles(projectName);
|
|
661
|
+
const created = [];
|
|
662
|
+
const skipped = [];
|
|
663
|
+
const errors = [];
|
|
664
|
+
// Process template files
|
|
665
|
+
for (const template of templateFiles) {
|
|
666
|
+
const fullPath = resolvePath(config, template.path);
|
|
667
|
+
// Check if file exists and has content
|
|
668
|
+
if (existsSync(fullPath) && !params.force) {
|
|
669
|
+
try {
|
|
670
|
+
const content = await readFile(fullPath, "utf-8");
|
|
671
|
+
if (content.trim().length > 0) {
|
|
672
|
+
skipped.push(template.path);
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
catch {
|
|
677
|
+
// File exists but can't be read, skip it
|
|
678
|
+
skipped.push(template.path);
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
// Create the file
|
|
683
|
+
try {
|
|
684
|
+
await ensureDir(fullPath);
|
|
685
|
+
await writeFile(fullPath, template.content, "utf-8");
|
|
686
|
+
created.push(template.path);
|
|
687
|
+
}
|
|
688
|
+
catch (e) {
|
|
689
|
+
errors.push(`${template.path}: ${e}`);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
// Create tasks directory with empty tasks.json
|
|
693
|
+
const tasksJsonPath = resolvePath(config, "memory_bank/tasks/tasks.json");
|
|
694
|
+
if (!existsSync(tasksJsonPath) || params.force) {
|
|
695
|
+
try {
|
|
696
|
+
await ensureDir(tasksJsonPath);
|
|
697
|
+
await writeFile(tasksJsonPath, getInitialTasksJson(), "utf-8");
|
|
698
|
+
created.push("memory_bank/tasks/tasks.json");
|
|
699
|
+
}
|
|
700
|
+
catch (e) {
|
|
701
|
+
errors.push(`memory_bank/tasks/tasks.json: ${e}`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
skipped.push("memory_bank/tasks/tasks.json");
|
|
706
|
+
}
|
|
707
|
+
// Build result message
|
|
708
|
+
const messages = [];
|
|
709
|
+
if (created.length > 0) {
|
|
710
|
+
messages.push(`Created ${created.length} files:`);
|
|
711
|
+
created.forEach((f) => messages.push(` ✓ ${f}`));
|
|
712
|
+
}
|
|
713
|
+
if (skipped.length > 0) {
|
|
714
|
+
messages.push(`\nSkipped ${skipped.length} existing files:`);
|
|
715
|
+
skipped.forEach((f) => messages.push(` - ${f}`));
|
|
716
|
+
}
|
|
717
|
+
if (errors.length > 0) {
|
|
718
|
+
messages.push(`\nErrors (${errors.length}):`);
|
|
719
|
+
errors.forEach((e) => messages.push(` ✗ ${e}`));
|
|
720
|
+
}
|
|
721
|
+
if (created.length === 0 && skipped.length > 0) {
|
|
722
|
+
messages.push("\nProject already initialised. Use force: true to overwrite.");
|
|
723
|
+
}
|
|
724
|
+
return result({
|
|
725
|
+
project_name: projectName,
|
|
726
|
+
created,
|
|
727
|
+
skipped,
|
|
728
|
+
errors,
|
|
729
|
+
message: messages.join("\n"),
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
//# sourceMappingURL=tools.js.map
|