roadmap-skill 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/LICENSE +21 -0
- package/README.md +147 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2203 -0
- package/dist/index.js.map +1 -0
- package/dist/web/server.js +441 -0
- package/dist/web/server.js.map +1 -0
- package/package.json +72 -0
- package/templates/ai-project.json +142 -0
- package/templates/mobile-app.json +126 -0
- package/templates/web-app.json +108 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2203 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// node_modules/tsup/assets/esm_shims.js
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
var init_esm_shims = __esm({
|
|
15
|
+
"node_modules/tsup/assets/esm_shims.js"() {
|
|
16
|
+
"use strict";
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// src/utils/file-helpers.ts
|
|
21
|
+
var file_helpers_exports = {};
|
|
22
|
+
__export(file_helpers_exports, {
|
|
23
|
+
ensureDir: () => ensureDir,
|
|
24
|
+
readJsonFile: () => readJsonFile,
|
|
25
|
+
writeJsonFile: () => writeJsonFile
|
|
26
|
+
});
|
|
27
|
+
import * as fs from "fs/promises";
|
|
28
|
+
async function readJsonFile(filePath) {
|
|
29
|
+
try {
|
|
30
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
31
|
+
return JSON.parse(content);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (error instanceof Error) {
|
|
34
|
+
throw new Error(`Failed to read JSON file ${filePath}: ${error.message}`);
|
|
35
|
+
}
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function writeJsonFile(filePath, data) {
|
|
40
|
+
try {
|
|
41
|
+
const content = JSON.stringify(data, null, 2);
|
|
42
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error instanceof Error) {
|
|
45
|
+
throw new Error(`Failed to write JSON file ${filePath}: ${error.message}`);
|
|
46
|
+
}
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function ensureDir(dirPath) {
|
|
51
|
+
try {
|
|
52
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error instanceof Error) {
|
|
55
|
+
throw new Error(`Failed to create directory ${dirPath}: ${error.message}`);
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
var init_file_helpers = __esm({
|
|
61
|
+
"src/utils/file-helpers.ts"() {
|
|
62
|
+
"use strict";
|
|
63
|
+
init_esm_shims();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// src/index.ts
|
|
68
|
+
init_esm_shims();
|
|
69
|
+
|
|
70
|
+
// src/server.ts
|
|
71
|
+
init_esm_shims();
|
|
72
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
73
|
+
import {
|
|
74
|
+
CallToolRequestSchema,
|
|
75
|
+
ListToolsRequestSchema,
|
|
76
|
+
ListResourcesRequestSchema,
|
|
77
|
+
ReadResourceRequestSchema,
|
|
78
|
+
ListPromptsRequestSchema,
|
|
79
|
+
GetPromptRequestSchema
|
|
80
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
81
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
82
|
+
|
|
83
|
+
// src/tools/index.ts
|
|
84
|
+
init_esm_shims();
|
|
85
|
+
|
|
86
|
+
// src/tools/project-tools.ts
|
|
87
|
+
init_esm_shims();
|
|
88
|
+
import { z } from "zod";
|
|
89
|
+
|
|
90
|
+
// src/storage/index.ts
|
|
91
|
+
init_esm_shims();
|
|
92
|
+
import * as path3 from "path";
|
|
93
|
+
|
|
94
|
+
// src/utils/path-helpers.ts
|
|
95
|
+
init_esm_shims();
|
|
96
|
+
import * as os from "os";
|
|
97
|
+
import * as path2 from "path";
|
|
98
|
+
function getStorageDir() {
|
|
99
|
+
const homeDir = os.homedir();
|
|
100
|
+
return path2.join(homeDir, ".roadmap-skill", "projects");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/storage/index.ts
|
|
104
|
+
init_file_helpers();
|
|
105
|
+
var ProjectStorage = class {
|
|
106
|
+
storageDir;
|
|
107
|
+
constructor() {
|
|
108
|
+
this.storageDir = getStorageDir();
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Ensure the storage directory exists
|
|
112
|
+
*/
|
|
113
|
+
async ensureDirectory() {
|
|
114
|
+
await ensureDir(this.storageDir);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get the file path for a project
|
|
118
|
+
* @param projectId - The project ID
|
|
119
|
+
* @returns Full path to the project JSON file
|
|
120
|
+
*/
|
|
121
|
+
getFilePath(projectId) {
|
|
122
|
+
return path3.join(this.storageDir, `${projectId}.json`);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Create a new project
|
|
126
|
+
* @param input - Project creation data
|
|
127
|
+
* @returns The created project data
|
|
128
|
+
*/
|
|
129
|
+
async createProject(input) {
|
|
130
|
+
await this.ensureDirectory();
|
|
131
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
132
|
+
const projectId = `proj_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
133
|
+
const project = {
|
|
134
|
+
id: projectId,
|
|
135
|
+
name: input.name,
|
|
136
|
+
description: input.description,
|
|
137
|
+
projectType: input.projectType,
|
|
138
|
+
status: "active",
|
|
139
|
+
startDate: input.startDate,
|
|
140
|
+
targetDate: input.targetDate,
|
|
141
|
+
createdAt: now,
|
|
142
|
+
updatedAt: now
|
|
143
|
+
};
|
|
144
|
+
const projectData = {
|
|
145
|
+
version: 1,
|
|
146
|
+
project,
|
|
147
|
+
milestones: [],
|
|
148
|
+
tasks: [],
|
|
149
|
+
tags: []
|
|
150
|
+
};
|
|
151
|
+
const filePath = this.getFilePath(projectId);
|
|
152
|
+
await writeJsonFile(filePath, projectData);
|
|
153
|
+
return projectData;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Read a project by ID
|
|
157
|
+
* @param projectId - The project ID
|
|
158
|
+
* @returns The project data or null if not found
|
|
159
|
+
*/
|
|
160
|
+
async readProject(projectId) {
|
|
161
|
+
try {
|
|
162
|
+
const filePath = this.getFilePath(projectId);
|
|
163
|
+
return await readJsonFile(filePath);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (error instanceof Error && error.message.includes("ENOENT")) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Update an existing project
|
|
173
|
+
* @param projectId - The project ID
|
|
174
|
+
* @param input - Project update data
|
|
175
|
+
* @returns The updated project data or null if not found
|
|
176
|
+
*/
|
|
177
|
+
async updateProject(projectId, input) {
|
|
178
|
+
const projectData = await this.readProject(projectId);
|
|
179
|
+
if (!projectData) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
183
|
+
projectData.project = {
|
|
184
|
+
...projectData.project,
|
|
185
|
+
...input,
|
|
186
|
+
updatedAt: now
|
|
187
|
+
};
|
|
188
|
+
const filePath = this.getFilePath(projectId);
|
|
189
|
+
await writeJsonFile(filePath, projectData);
|
|
190
|
+
return projectData;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Delete a project by ID
|
|
194
|
+
* @param projectId - The project ID
|
|
195
|
+
* @returns True if deleted, false if not found
|
|
196
|
+
*/
|
|
197
|
+
async deleteProject(projectId) {
|
|
198
|
+
try {
|
|
199
|
+
const filePath = this.getFilePath(projectId);
|
|
200
|
+
const fs3 = await import("fs/promises");
|
|
201
|
+
await fs3.unlink(filePath);
|
|
202
|
+
return true;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (error instanceof Error && error.message.includes("ENOENT")) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* List all projects sorted by updatedAt (descending)
|
|
212
|
+
* @returns Array of project summaries (project + metadata)
|
|
213
|
+
*/
|
|
214
|
+
async listProjects() {
|
|
215
|
+
await this.ensureDirectory();
|
|
216
|
+
const fs3 = await import("fs/promises");
|
|
217
|
+
const files = await fs3.readdir(this.storageDir);
|
|
218
|
+
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
219
|
+
const projects = [];
|
|
220
|
+
for (const file of jsonFiles) {
|
|
221
|
+
try {
|
|
222
|
+
const filePath = path3.join(this.storageDir, file);
|
|
223
|
+
const data = await readJsonFile(filePath);
|
|
224
|
+
projects.push({
|
|
225
|
+
project: data.project,
|
|
226
|
+
taskCount: data.tasks.length,
|
|
227
|
+
milestoneCount: data.milestones.length
|
|
228
|
+
});
|
|
229
|
+
} catch {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return projects.sort(
|
|
234
|
+
(a, b) => new Date(b.project.updatedAt).getTime() - new Date(a.project.updatedAt).getTime()
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Search tasks across all projects with filters
|
|
239
|
+
* @param filters - Search filters
|
|
240
|
+
* @returns Array of matching tasks with project context
|
|
241
|
+
*/
|
|
242
|
+
async searchTasks(filters) {
|
|
243
|
+
await this.ensureDirectory();
|
|
244
|
+
const fs3 = await import("fs/promises");
|
|
245
|
+
const files = await fs3.readdir(this.storageDir);
|
|
246
|
+
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
247
|
+
const results = [];
|
|
248
|
+
for (const file of jsonFiles) {
|
|
249
|
+
try {
|
|
250
|
+
const filePath = path3.join(this.storageDir, file);
|
|
251
|
+
const data = await readJsonFile(filePath);
|
|
252
|
+
if (filters.projectId && data.project.id !== filters.projectId) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
for (const task of data.tasks) {
|
|
256
|
+
if (filters.status && task.status !== filters.status) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (filters.priority && task.priority !== filters.priority) {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (filters.assignee && task.assignee !== filters.assignee) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (filters.dueBefore && task.dueDate && task.dueDate > filters.dueBefore) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (filters.dueAfter && task.dueDate && task.dueDate < filters.dueAfter) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (filters.tags && filters.tags.length > 0 && !filters.tags.some((tag) => task.tags.includes(tag))) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (filters.searchText && !task.title.toLowerCase().includes(filters.searchText.toLowerCase()) && !task.description.toLowerCase().includes(filters.searchText.toLowerCase())) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
results.push({ task, project: data.project });
|
|
278
|
+
}
|
|
279
|
+
} catch {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return results;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
var storage = new ProjectStorage();
|
|
287
|
+
|
|
288
|
+
// src/tools/project-tools.ts
|
|
289
|
+
var ProjectTypeEnum = z.enum(["roadmap", "skill-tree", "kanban"]);
|
|
290
|
+
var ProjectStatusEnum = z.enum(["active", "completed", "archived"]);
|
|
291
|
+
var createProjectTool = {
|
|
292
|
+
name: "create_project",
|
|
293
|
+
description: "Create a new project roadmap",
|
|
294
|
+
inputSchema: z.object({
|
|
295
|
+
name: z.string().min(1, "Project name is required"),
|
|
296
|
+
description: z.string(),
|
|
297
|
+
projectType: ProjectTypeEnum,
|
|
298
|
+
startDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format"),
|
|
299
|
+
targetDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format")
|
|
300
|
+
}),
|
|
301
|
+
async execute(input) {
|
|
302
|
+
try {
|
|
303
|
+
const projectInput = {
|
|
304
|
+
name: input.name,
|
|
305
|
+
description: input.description,
|
|
306
|
+
projectType: input.projectType,
|
|
307
|
+
startDate: input.startDate,
|
|
308
|
+
targetDate: input.targetDate
|
|
309
|
+
};
|
|
310
|
+
const projectData = await storage.createProject(projectInput);
|
|
311
|
+
return {
|
|
312
|
+
success: true,
|
|
313
|
+
data: projectData
|
|
314
|
+
};
|
|
315
|
+
} catch (error) {
|
|
316
|
+
return {
|
|
317
|
+
success: false,
|
|
318
|
+
error: error instanceof Error ? error.message : "Failed to create project"
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
var listProjectsTool = {
|
|
324
|
+
name: "list_projects",
|
|
325
|
+
description: "List all projects with their task and milestone counts",
|
|
326
|
+
inputSchema: z.object({}),
|
|
327
|
+
async execute() {
|
|
328
|
+
try {
|
|
329
|
+
const projects = await storage.listProjects();
|
|
330
|
+
return {
|
|
331
|
+
success: true,
|
|
332
|
+
data: projects
|
|
333
|
+
};
|
|
334
|
+
} catch (error) {
|
|
335
|
+
return {
|
|
336
|
+
success: false,
|
|
337
|
+
error: error instanceof Error ? error.message : "Failed to list projects"
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
var getProjectTool = {
|
|
343
|
+
name: "get_project",
|
|
344
|
+
description: "Get a project by ID with all its data (tasks, tags, milestones)",
|
|
345
|
+
inputSchema: z.object({
|
|
346
|
+
projectId: z.string().min(1, "Project ID is required")
|
|
347
|
+
}),
|
|
348
|
+
async execute(input) {
|
|
349
|
+
try {
|
|
350
|
+
const projectData = await storage.readProject(input.projectId);
|
|
351
|
+
if (!projectData) {
|
|
352
|
+
return {
|
|
353
|
+
success: false,
|
|
354
|
+
error: `Project with ID '${input.projectId}' not found`
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
success: true,
|
|
359
|
+
data: projectData
|
|
360
|
+
};
|
|
361
|
+
} catch (error) {
|
|
362
|
+
return {
|
|
363
|
+
success: false,
|
|
364
|
+
error: error instanceof Error ? error.message : "Failed to get project"
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
var updateProjectTool = {
|
|
370
|
+
name: "update_project",
|
|
371
|
+
description: "Update an existing project",
|
|
372
|
+
inputSchema: z.object({
|
|
373
|
+
projectId: z.string().min(1, "Project ID is required"),
|
|
374
|
+
name: z.string().min(1).optional(),
|
|
375
|
+
description: z.string().optional(),
|
|
376
|
+
projectType: ProjectTypeEnum.optional(),
|
|
377
|
+
status: ProjectStatusEnum.optional(),
|
|
378
|
+
startDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
379
|
+
targetDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
|
|
380
|
+
}),
|
|
381
|
+
async execute(input) {
|
|
382
|
+
try {
|
|
383
|
+
const { projectId, ...updateData } = input;
|
|
384
|
+
if (Object.keys(updateData).length === 0) {
|
|
385
|
+
return {
|
|
386
|
+
success: false,
|
|
387
|
+
error: "At least one field to update is required"
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
const updateInput = updateData;
|
|
391
|
+
const projectData = await storage.updateProject(projectId, updateInput);
|
|
392
|
+
if (!projectData) {
|
|
393
|
+
return {
|
|
394
|
+
success: false,
|
|
395
|
+
error: `Project with ID '${projectId}' not found`
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
success: true,
|
|
400
|
+
data: projectData
|
|
401
|
+
};
|
|
402
|
+
} catch (error) {
|
|
403
|
+
return {
|
|
404
|
+
success: false,
|
|
405
|
+
error: error instanceof Error ? error.message : "Failed to update project"
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
var deleteProjectTool = {
|
|
411
|
+
name: "delete_project",
|
|
412
|
+
description: "Delete a project by ID",
|
|
413
|
+
inputSchema: z.object({
|
|
414
|
+
projectId: z.string().min(1, "Project ID is required")
|
|
415
|
+
}),
|
|
416
|
+
async execute(input) {
|
|
417
|
+
try {
|
|
418
|
+
const deleted = await storage.deleteProject(input.projectId);
|
|
419
|
+
if (!deleted) {
|
|
420
|
+
return {
|
|
421
|
+
success: false,
|
|
422
|
+
error: `Project with ID '${input.projectId}' not found`
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
success: true,
|
|
427
|
+
data: { deleted: true }
|
|
428
|
+
};
|
|
429
|
+
} catch (error) {
|
|
430
|
+
return {
|
|
431
|
+
success: false,
|
|
432
|
+
error: error instanceof Error ? error.message : "Failed to delete project"
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
// src/tools/task-tools.ts
|
|
439
|
+
init_esm_shims();
|
|
440
|
+
import { z as z2 } from "zod";
|
|
441
|
+
var TaskStatusEnum = z2.enum(["todo", "in-progress", "review", "done"]);
|
|
442
|
+
var TaskPriorityEnum = z2.enum(["low", "medium", "high", "critical"]);
|
|
443
|
+
function generateTaskId() {
|
|
444
|
+
return `task_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
445
|
+
}
|
|
446
|
+
var createTaskTool = {
|
|
447
|
+
name: "create_task",
|
|
448
|
+
description: "Create a new task in a project",
|
|
449
|
+
inputSchema: z2.object({
|
|
450
|
+
projectId: z2.string().min(1, "Project ID is required"),
|
|
451
|
+
title: z2.string().min(1, "Task title is required"),
|
|
452
|
+
description: z2.string(),
|
|
453
|
+
priority: TaskPriorityEnum.default("medium"),
|
|
454
|
+
tags: z2.array(z2.string()).default([]),
|
|
455
|
+
dueDate: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
456
|
+
assignee: z2.string().optional()
|
|
457
|
+
}),
|
|
458
|
+
async execute(input) {
|
|
459
|
+
try {
|
|
460
|
+
const projectData = await storage.readProject(input.projectId);
|
|
461
|
+
if (!projectData) {
|
|
462
|
+
return {
|
|
463
|
+
success: false,
|
|
464
|
+
error: `Project with ID '${input.projectId}' not found`
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
468
|
+
const task = {
|
|
469
|
+
id: generateTaskId(),
|
|
470
|
+
projectId: input.projectId,
|
|
471
|
+
title: input.title,
|
|
472
|
+
description: input.description,
|
|
473
|
+
status: "todo",
|
|
474
|
+
priority: input.priority,
|
|
475
|
+
tags: input.tags,
|
|
476
|
+
dueDate: input.dueDate ?? null,
|
|
477
|
+
assignee: input.assignee ?? null,
|
|
478
|
+
createdAt: now,
|
|
479
|
+
updatedAt: now,
|
|
480
|
+
completedAt: null
|
|
481
|
+
};
|
|
482
|
+
projectData.tasks.push(task);
|
|
483
|
+
projectData.project.updatedAt = now;
|
|
484
|
+
const filePath = storage.getFilePath(input.projectId);
|
|
485
|
+
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
486
|
+
await writeJsonFile2(filePath, projectData);
|
|
487
|
+
return {
|
|
488
|
+
success: true,
|
|
489
|
+
data: task
|
|
490
|
+
};
|
|
491
|
+
} catch (error) {
|
|
492
|
+
return {
|
|
493
|
+
success: false,
|
|
494
|
+
error: error instanceof Error ? error.message : "Failed to create task"
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
var listTasksTool = {
|
|
500
|
+
name: "list_tasks",
|
|
501
|
+
description: "List tasks with optional filters",
|
|
502
|
+
inputSchema: z2.object({
|
|
503
|
+
projectId: z2.string().optional(),
|
|
504
|
+
status: TaskStatusEnum.optional(),
|
|
505
|
+
priority: TaskPriorityEnum.optional(),
|
|
506
|
+
tags: z2.array(z2.string()).optional(),
|
|
507
|
+
assignee: z2.string().optional(),
|
|
508
|
+
dueBefore: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
509
|
+
dueAfter: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
|
|
510
|
+
}),
|
|
511
|
+
async execute(input) {
|
|
512
|
+
try {
|
|
513
|
+
const results = await storage.searchTasks({
|
|
514
|
+
projectId: input.projectId,
|
|
515
|
+
status: input.status,
|
|
516
|
+
priority: input.priority,
|
|
517
|
+
tags: input.tags,
|
|
518
|
+
assignee: input.assignee,
|
|
519
|
+
dueBefore: input.dueBefore,
|
|
520
|
+
dueAfter: input.dueAfter
|
|
521
|
+
});
|
|
522
|
+
return {
|
|
523
|
+
success: true,
|
|
524
|
+
data: results
|
|
525
|
+
};
|
|
526
|
+
} catch (error) {
|
|
527
|
+
return {
|
|
528
|
+
success: false,
|
|
529
|
+
error: error instanceof Error ? error.message : "Failed to list tasks"
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
var getTaskTool = {
|
|
535
|
+
name: "get_task",
|
|
536
|
+
description: "Get a specific task by project ID and task ID",
|
|
537
|
+
inputSchema: z2.object({
|
|
538
|
+
projectId: z2.string().min(1, "Project ID is required"),
|
|
539
|
+
taskId: z2.string().min(1, "Task ID is required")
|
|
540
|
+
}),
|
|
541
|
+
async execute(input) {
|
|
542
|
+
try {
|
|
543
|
+
const projectData = await storage.readProject(input.projectId);
|
|
544
|
+
if (!projectData) {
|
|
545
|
+
return {
|
|
546
|
+
success: false,
|
|
547
|
+
error: `Project with ID '${input.projectId}' not found`
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
const task = projectData.tasks.find((t) => t.id === input.taskId);
|
|
551
|
+
if (!task) {
|
|
552
|
+
return {
|
|
553
|
+
success: false,
|
|
554
|
+
error: `Task with ID '${input.taskId}' not found in project '${input.projectId}'`
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
return {
|
|
558
|
+
success: true,
|
|
559
|
+
data: task
|
|
560
|
+
};
|
|
561
|
+
} catch (error) {
|
|
562
|
+
return {
|
|
563
|
+
success: false,
|
|
564
|
+
error: error instanceof Error ? error.message : "Failed to get task"
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
var updateTaskTool = {
|
|
570
|
+
name: "update_task",
|
|
571
|
+
description: "Update an existing task",
|
|
572
|
+
inputSchema: z2.object({
|
|
573
|
+
projectId: z2.string().min(1, "Project ID is required"),
|
|
574
|
+
taskId: z2.string().min(1, "Task ID is required"),
|
|
575
|
+
title: z2.string().min(1).optional(),
|
|
576
|
+
description: z2.string().optional(),
|
|
577
|
+
status: TaskStatusEnum.optional(),
|
|
578
|
+
priority: TaskPriorityEnum.optional(),
|
|
579
|
+
tags: z2.array(z2.string()).optional(),
|
|
580
|
+
dueDate: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().nullable(),
|
|
581
|
+
assignee: z2.string().optional().nullable()
|
|
582
|
+
}),
|
|
583
|
+
async execute(input) {
|
|
584
|
+
try {
|
|
585
|
+
const projectData = await storage.readProject(input.projectId);
|
|
586
|
+
if (!projectData) {
|
|
587
|
+
return {
|
|
588
|
+
success: false,
|
|
589
|
+
error: `Project with ID '${input.projectId}' not found`
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
const taskIndex = projectData.tasks.findIndex((t) => t.id === input.taskId);
|
|
593
|
+
if (taskIndex === -1) {
|
|
594
|
+
return {
|
|
595
|
+
success: false,
|
|
596
|
+
error: `Task with ID '${input.taskId}' not found in project '${input.projectId}'`
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
const { projectId, taskId, ...updateData } = input;
|
|
600
|
+
if (Object.keys(updateData).length === 0) {
|
|
601
|
+
return {
|
|
602
|
+
success: false,
|
|
603
|
+
error: "At least one field to update is required"
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
607
|
+
const existingTask = projectData.tasks[taskIndex];
|
|
608
|
+
const updatedTask = {
|
|
609
|
+
...existingTask,
|
|
610
|
+
...updateData,
|
|
611
|
+
id: existingTask.id,
|
|
612
|
+
projectId: existingTask.projectId,
|
|
613
|
+
createdAt: existingTask.createdAt,
|
|
614
|
+
updatedAt: now,
|
|
615
|
+
completedAt: updateData.status === "done" && existingTask.status !== "done" ? now : updateData.status && updateData.status !== "done" ? null : existingTask.completedAt
|
|
616
|
+
};
|
|
617
|
+
projectData.tasks[taskIndex] = updatedTask;
|
|
618
|
+
projectData.project.updatedAt = now;
|
|
619
|
+
const filePath = storage.getFilePath(input.projectId);
|
|
620
|
+
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
621
|
+
await writeJsonFile2(filePath, projectData);
|
|
622
|
+
return {
|
|
623
|
+
success: true,
|
|
624
|
+
data: updatedTask
|
|
625
|
+
};
|
|
626
|
+
} catch (error) {
|
|
627
|
+
return {
|
|
628
|
+
success: false,
|
|
629
|
+
error: error instanceof Error ? error.message : "Failed to update task"
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
var deleteTaskTool = {
|
|
635
|
+
name: "delete_task",
|
|
636
|
+
description: "Delete a task by project ID and task ID",
|
|
637
|
+
inputSchema: z2.object({
|
|
638
|
+
projectId: z2.string().min(1, "Project ID is required"),
|
|
639
|
+
taskId: z2.string().min(1, "Task ID is required")
|
|
640
|
+
}),
|
|
641
|
+
async execute(input) {
|
|
642
|
+
try {
|
|
643
|
+
const projectData = await storage.readProject(input.projectId);
|
|
644
|
+
if (!projectData) {
|
|
645
|
+
return {
|
|
646
|
+
success: false,
|
|
647
|
+
error: `Project with ID '${input.projectId}' not found`
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
const taskIndex = projectData.tasks.findIndex((t) => t.id === input.taskId);
|
|
651
|
+
if (taskIndex === -1) {
|
|
652
|
+
return {
|
|
653
|
+
success: false,
|
|
654
|
+
error: `Task with ID '${input.taskId}' not found in project '${input.projectId}'`
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
658
|
+
projectData.tasks.splice(taskIndex, 1);
|
|
659
|
+
projectData.project.updatedAt = now;
|
|
660
|
+
const filePath = storage.getFilePath(input.projectId);
|
|
661
|
+
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
662
|
+
await writeJsonFile2(filePath, projectData);
|
|
663
|
+
return {
|
|
664
|
+
success: true,
|
|
665
|
+
data: { deleted: true }
|
|
666
|
+
};
|
|
667
|
+
} catch (error) {
|
|
668
|
+
return {
|
|
669
|
+
success: false,
|
|
670
|
+
error: error instanceof Error ? error.message : "Failed to delete task"
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
var batchUpdateTasksTool = {
|
|
676
|
+
name: "batch_update_tasks",
|
|
677
|
+
description: "Update multiple tasks at once",
|
|
678
|
+
inputSchema: z2.object({
|
|
679
|
+
projectId: z2.string().min(1, "Project ID is required"),
|
|
680
|
+
taskIds: z2.array(z2.string()).min(1, "At least one task ID is required"),
|
|
681
|
+
status: TaskStatusEnum.optional(),
|
|
682
|
+
priority: TaskPriorityEnum.optional(),
|
|
683
|
+
tags: z2.array(z2.string()).optional(),
|
|
684
|
+
tagOperation: z2.enum(["add", "remove", "replace"]).default("replace")
|
|
685
|
+
}),
|
|
686
|
+
async execute(input) {
|
|
687
|
+
try {
|
|
688
|
+
const projectData = await storage.readProject(input.projectId);
|
|
689
|
+
if (!projectData) {
|
|
690
|
+
return {
|
|
691
|
+
success: false,
|
|
692
|
+
error: `Project with ID '${input.projectId}' not found`
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
696
|
+
const updatedTasks = [];
|
|
697
|
+
const notFoundIds = [];
|
|
698
|
+
for (const taskId of input.taskIds) {
|
|
699
|
+
const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
|
|
700
|
+
if (taskIndex === -1) {
|
|
701
|
+
notFoundIds.push(taskId);
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
const existingTask = projectData.tasks[taskIndex];
|
|
705
|
+
let updatedTags = existingTask.tags;
|
|
706
|
+
if (input.tags && input.tags.length > 0) {
|
|
707
|
+
switch (input.tagOperation) {
|
|
708
|
+
case "add":
|
|
709
|
+
updatedTags = [.../* @__PURE__ */ new Set([...existingTask.tags, ...input.tags])];
|
|
710
|
+
break;
|
|
711
|
+
case "remove":
|
|
712
|
+
updatedTags = existingTask.tags.filter((tag) => !input.tags.includes(tag));
|
|
713
|
+
break;
|
|
714
|
+
case "replace":
|
|
715
|
+
default:
|
|
716
|
+
updatedTags = input.tags;
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
const updatedTask = {
|
|
721
|
+
...existingTask,
|
|
722
|
+
...input.status && { status: input.status },
|
|
723
|
+
...input.priority && { priority: input.priority },
|
|
724
|
+
tags: updatedTags,
|
|
725
|
+
updatedAt: now,
|
|
726
|
+
...input.status === "done" && existingTask.status !== "done" && { completedAt: now },
|
|
727
|
+
...input.status && input.status !== "done" && { completedAt: null }
|
|
728
|
+
};
|
|
729
|
+
projectData.tasks[taskIndex] = updatedTask;
|
|
730
|
+
updatedTasks.push(updatedTask);
|
|
731
|
+
}
|
|
732
|
+
if (updatedTasks.length === 0) {
|
|
733
|
+
return {
|
|
734
|
+
success: false,
|
|
735
|
+
error: "No tasks were found to update",
|
|
736
|
+
notFoundIds
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
projectData.project.updatedAt = now;
|
|
740
|
+
const filePath = storage.getFilePath(input.projectId);
|
|
741
|
+
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
742
|
+
await writeJsonFile2(filePath, projectData);
|
|
743
|
+
return {
|
|
744
|
+
success: true,
|
|
745
|
+
data: {
|
|
746
|
+
updatedTasks,
|
|
747
|
+
updatedCount: updatedTasks.length,
|
|
748
|
+
notFoundIds: notFoundIds.length > 0 ? notFoundIds : void 0
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
} catch (error) {
|
|
752
|
+
return {
|
|
753
|
+
success: false,
|
|
754
|
+
error: error instanceof Error ? error.message : "Failed to batch update tasks"
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
// src/tools/tag-tools.ts
|
|
761
|
+
init_esm_shims();
|
|
762
|
+
import { z as z3 } from "zod";
|
|
763
|
+
function generateTagId() {
|
|
764
|
+
return `tag_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
765
|
+
}
|
|
766
|
+
var createTagTool = {
|
|
767
|
+
name: "create_tag",
|
|
768
|
+
description: "Create a new tag in a project",
|
|
769
|
+
inputSchema: z3.object({
|
|
770
|
+
projectId: z3.string().min(1, "Project ID is required"),
|
|
771
|
+
name: z3.string().min(1, "Tag name is required"),
|
|
772
|
+
color: z3.string().regex(/^#[0-9A-Fa-f]{6}$/, "Color must be a valid hex code (e.g., #FF5733)"),
|
|
773
|
+
description: z3.string().default("")
|
|
774
|
+
}),
|
|
775
|
+
async execute(input) {
|
|
776
|
+
try {
|
|
777
|
+
const projectData = await storage.readProject(input.projectId);
|
|
778
|
+
if (!projectData) {
|
|
779
|
+
return {
|
|
780
|
+
success: false,
|
|
781
|
+
error: `Project with ID '${input.projectId}' not found`
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
const existingTag = projectData.tags.find(
|
|
785
|
+
(t) => t.name.toLowerCase() === input.name.toLowerCase()
|
|
786
|
+
);
|
|
787
|
+
if (existingTag) {
|
|
788
|
+
return {
|
|
789
|
+
success: false,
|
|
790
|
+
error: `Tag with name '${input.name}' already exists in this project`
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
794
|
+
const tag = {
|
|
795
|
+
id: generateTagId(),
|
|
796
|
+
name: input.name,
|
|
797
|
+
color: input.color,
|
|
798
|
+
description: input.description,
|
|
799
|
+
createdAt: now
|
|
800
|
+
};
|
|
801
|
+
projectData.tags.push(tag);
|
|
802
|
+
projectData.project.updatedAt = now;
|
|
803
|
+
const filePath = storage.getFilePath(input.projectId);
|
|
804
|
+
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
805
|
+
await writeJsonFile2(filePath, projectData);
|
|
806
|
+
return {
|
|
807
|
+
success: true,
|
|
808
|
+
data: tag
|
|
809
|
+
};
|
|
810
|
+
} catch (error) {
|
|
811
|
+
return {
|
|
812
|
+
success: false,
|
|
813
|
+
error: error instanceof Error ? error.message : "Failed to create tag"
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
var listTagsTool = {
|
|
819
|
+
name: "list_tags",
|
|
820
|
+
description: "List all tags in a project",
|
|
821
|
+
inputSchema: z3.object({
|
|
822
|
+
projectId: z3.string().min(1, "Project ID is required")
|
|
823
|
+
}),
|
|
824
|
+
async execute(input) {
|
|
825
|
+
try {
|
|
826
|
+
const projectData = await storage.readProject(input.projectId);
|
|
827
|
+
if (!projectData) {
|
|
828
|
+
return {
|
|
829
|
+
success: false,
|
|
830
|
+
error: `Project with ID '${input.projectId}' not found`
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
return {
|
|
834
|
+
success: true,
|
|
835
|
+
data: projectData.tags
|
|
836
|
+
};
|
|
837
|
+
} catch (error) {
|
|
838
|
+
return {
|
|
839
|
+
success: false,
|
|
840
|
+
error: error instanceof Error ? error.message : "Failed to list tags"
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
var updateTagTool = {
|
|
846
|
+
name: "update_tag",
|
|
847
|
+
description: "Update an existing tag",
|
|
848
|
+
inputSchema: z3.object({
|
|
849
|
+
projectId: z3.string().min(1, "Project ID is required"),
|
|
850
|
+
tagId: z3.string().min(1, "Tag ID is required"),
|
|
851
|
+
name: z3.string().min(1).optional(),
|
|
852
|
+
color: z3.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
|
|
853
|
+
description: z3.string().optional()
|
|
854
|
+
}),
|
|
855
|
+
async execute(input) {
|
|
856
|
+
try {
|
|
857
|
+
const projectData = await storage.readProject(input.projectId);
|
|
858
|
+
if (!projectData) {
|
|
859
|
+
return {
|
|
860
|
+
success: false,
|
|
861
|
+
error: `Project with ID '${input.projectId}' not found`
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
const tagIndex = projectData.tags.findIndex((t) => t.id === input.tagId);
|
|
865
|
+
if (tagIndex === -1) {
|
|
866
|
+
return {
|
|
867
|
+
success: false,
|
|
868
|
+
error: `Tag with ID '${input.tagId}' not found in project '${input.projectId}'`
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
const { projectId, tagId, ...updateData } = input;
|
|
872
|
+
if (Object.keys(updateData).length === 0) {
|
|
873
|
+
return {
|
|
874
|
+
success: false,
|
|
875
|
+
error: "At least one field to update is required"
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
if (updateData.name) {
|
|
879
|
+
const existingTag2 = projectData.tags.find(
|
|
880
|
+
(t) => t.name.toLowerCase() === updateData.name.toLowerCase() && t.id !== input.tagId
|
|
881
|
+
);
|
|
882
|
+
if (existingTag2) {
|
|
883
|
+
return {
|
|
884
|
+
success: false,
|
|
885
|
+
error: `Tag with name '${updateData.name}' already exists in this project`
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
890
|
+
const existingTag = projectData.tags[tagIndex];
|
|
891
|
+
const updatedTag = {
|
|
892
|
+
...existingTag,
|
|
893
|
+
...updateData,
|
|
894
|
+
id: existingTag.id,
|
|
895
|
+
createdAt: existingTag.createdAt
|
|
896
|
+
};
|
|
897
|
+
projectData.tags[tagIndex] = updatedTag;
|
|
898
|
+
projectData.project.updatedAt = now;
|
|
899
|
+
const filePath = storage.getFilePath(input.projectId);
|
|
900
|
+
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
901
|
+
await writeJsonFile2(filePath, projectData);
|
|
902
|
+
return {
|
|
903
|
+
success: true,
|
|
904
|
+
data: updatedTag
|
|
905
|
+
};
|
|
906
|
+
} catch (error) {
|
|
907
|
+
return {
|
|
908
|
+
success: false,
|
|
909
|
+
error: error instanceof Error ? error.message : "Failed to update tag"
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
var deleteTagTool = {
|
|
915
|
+
name: "delete_tag",
|
|
916
|
+
description: "Delete a tag by project ID and tag ID",
|
|
917
|
+
inputSchema: z3.object({
|
|
918
|
+
projectId: z3.string().min(1, "Project ID is required"),
|
|
919
|
+
tagId: z3.string().min(1, "Tag ID is required")
|
|
920
|
+
}),
|
|
921
|
+
async execute(input) {
|
|
922
|
+
try {
|
|
923
|
+
const projectData = await storage.readProject(input.projectId);
|
|
924
|
+
if (!projectData) {
|
|
925
|
+
return {
|
|
926
|
+
success: false,
|
|
927
|
+
error: `Project with ID '${input.projectId}' not found`
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
const tagIndex = projectData.tags.findIndex((t) => t.id === input.tagId);
|
|
931
|
+
if (tagIndex === -1) {
|
|
932
|
+
return {
|
|
933
|
+
success: false,
|
|
934
|
+
error: `Tag with ID '${input.tagId}' not found in project '${input.projectId}'`
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
const tag = projectData.tags[tagIndex];
|
|
938
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
939
|
+
let tasksUpdated = 0;
|
|
940
|
+
for (const task of projectData.tasks) {
|
|
941
|
+
const tagIndexInTask = task.tags.indexOf(input.tagId);
|
|
942
|
+
if (tagIndexInTask !== -1) {
|
|
943
|
+
task.tags.splice(tagIndexInTask, 1);
|
|
944
|
+
task.updatedAt = now;
|
|
945
|
+
tasksUpdated++;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
projectData.tags.splice(tagIndex, 1);
|
|
949
|
+
projectData.project.updatedAt = now;
|
|
950
|
+
const filePath = storage.getFilePath(input.projectId);
|
|
951
|
+
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
952
|
+
await writeJsonFile2(filePath, projectData);
|
|
953
|
+
return {
|
|
954
|
+
success: true,
|
|
955
|
+
data: {
|
|
956
|
+
deleted: true,
|
|
957
|
+
tag,
|
|
958
|
+
tasksUpdated
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
} catch (error) {
|
|
962
|
+
return {
|
|
963
|
+
success: false,
|
|
964
|
+
error: error instanceof Error ? error.message : "Failed to delete tag"
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
var getTasksByTagTool = {
|
|
970
|
+
name: "get_tasks_by_tag",
|
|
971
|
+
description: "Get all tasks that have a specific tag",
|
|
972
|
+
inputSchema: z3.object({
|
|
973
|
+
projectId: z3.string().min(1, "Project ID is required"),
|
|
974
|
+
tagName: z3.string().min(1, "Tag name is required")
|
|
975
|
+
}),
|
|
976
|
+
async execute(input) {
|
|
977
|
+
try {
|
|
978
|
+
const projectData = await storage.readProject(input.projectId);
|
|
979
|
+
if (!projectData) {
|
|
980
|
+
return {
|
|
981
|
+
success: false,
|
|
982
|
+
error: `Project with ID '${input.projectId}' not found`
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
const tag = projectData.tags.find(
|
|
986
|
+
(t) => t.name.toLowerCase() === input.tagName.toLowerCase()
|
|
987
|
+
);
|
|
988
|
+
if (!tag) {
|
|
989
|
+
return {
|
|
990
|
+
success: false,
|
|
991
|
+
error: `Tag with name '${input.tagName}' not found in project '${input.projectId}'`
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
const tasks = projectData.tasks.filter((t) => t.tags.includes(tag.id));
|
|
995
|
+
return {
|
|
996
|
+
success: true,
|
|
997
|
+
data: {
|
|
998
|
+
tag,
|
|
999
|
+
tasks,
|
|
1000
|
+
count: tasks.length
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
} catch (error) {
|
|
1004
|
+
return {
|
|
1005
|
+
success: false,
|
|
1006
|
+
error: error instanceof Error ? error.message : "Failed to get tasks by tag"
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1012
|
+
// src/tools/web-tools.ts
|
|
1013
|
+
init_esm_shims();
|
|
1014
|
+
|
|
1015
|
+
// src/web/server.ts
|
|
1016
|
+
init_esm_shims();
|
|
1017
|
+
import express from "express";
|
|
1018
|
+
import * as path4 from "path";
|
|
1019
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1020
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
1021
|
+
var __dirname2 = path4.dirname(__filename2);
|
|
1022
|
+
function createServer(port = 7860) {
|
|
1023
|
+
const app = express();
|
|
1024
|
+
app.use(express.json());
|
|
1025
|
+
app.get("/api/projects", async (_req, res) => {
|
|
1026
|
+
try {
|
|
1027
|
+
const projects = await storage.listProjects();
|
|
1028
|
+
res.json(projects);
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
res.status(500).json({ error: error.message });
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
app.get("/api/projects/:id", async (req, res) => {
|
|
1034
|
+
try {
|
|
1035
|
+
const project = await storage.readProject(req.params.id);
|
|
1036
|
+
if (!project) {
|
|
1037
|
+
res.status(404).json({ error: "Project not found" });
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
res.json(project);
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
res.status(500).json({ error: error.message });
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
app.get("/api/tasks", async (req, res) => {
|
|
1046
|
+
try {
|
|
1047
|
+
const filters = req.query;
|
|
1048
|
+
const tasks = await storage.searchTasks(filters);
|
|
1049
|
+
res.json(tasks);
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
res.status(500).json({ error: error.message });
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
app.post("/api/projects", async (req, res) => {
|
|
1055
|
+
try {
|
|
1056
|
+
const project = await storage.createProject(req.body);
|
|
1057
|
+
res.json({ success: true, data: project });
|
|
1058
|
+
} catch (error) {
|
|
1059
|
+
res.status(500).json({ error: error.message });
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
app.put("/api/projects", async (req, res) => {
|
|
1063
|
+
try {
|
|
1064
|
+
const { projectId, ...updateData } = req.body;
|
|
1065
|
+
const project = await storage.updateProject(projectId, updateData);
|
|
1066
|
+
res.json({ success: true, data: project });
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
res.status(500).json({ error: error.message });
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
app.delete("/api/projects", async (req, res) => {
|
|
1072
|
+
try {
|
|
1073
|
+
const { projectId } = req.query;
|
|
1074
|
+
await storage.deleteProject(projectId);
|
|
1075
|
+
res.json({ success: true });
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
res.status(500).json({ error: error.message });
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
app.post("/api/tasks", async (req, res) => {
|
|
1081
|
+
try {
|
|
1082
|
+
const { projectId, ...taskData } = req.body;
|
|
1083
|
+
const projectData = await storage.readProject(projectId);
|
|
1084
|
+
if (!projectData) {
|
|
1085
|
+
res.status(404).json({ error: "Project not found" });
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1089
|
+
const task = {
|
|
1090
|
+
id: `task_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
1091
|
+
projectId,
|
|
1092
|
+
...taskData,
|
|
1093
|
+
status: taskData.status || "todo",
|
|
1094
|
+
priority: taskData.priority || "medium",
|
|
1095
|
+
tags: taskData.tags || [],
|
|
1096
|
+
dueDate: taskData.dueDate || null,
|
|
1097
|
+
assignee: taskData.assignee || null,
|
|
1098
|
+
createdAt: now,
|
|
1099
|
+
updatedAt: now,
|
|
1100
|
+
completedAt: null
|
|
1101
|
+
};
|
|
1102
|
+
projectData.tasks.push(task);
|
|
1103
|
+
projectData.project.updatedAt = now;
|
|
1104
|
+
const filePath = storage.getFilePath(projectId);
|
|
1105
|
+
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
1106
|
+
await writeJsonFile2(filePath, projectData);
|
|
1107
|
+
res.json({ success: true, data: task });
|
|
1108
|
+
} catch (error) {
|
|
1109
|
+
res.status(500).json({ error: error.message });
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
app.put("/api/tasks", async (req, res) => {
|
|
1113
|
+
try {
|
|
1114
|
+
const { projectId, taskId, ...updateData } = req.body;
|
|
1115
|
+
const projectData = await storage.readProject(projectId);
|
|
1116
|
+
if (!projectData) {
|
|
1117
|
+
res.status(404).json({ error: "Project not found" });
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
|
|
1121
|
+
if (taskIndex === -1) {
|
|
1122
|
+
res.status(404).json({ error: "Task not found" });
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1126
|
+
const existingTask = projectData.tasks[taskIndex];
|
|
1127
|
+
const updatedTask = {
|
|
1128
|
+
...existingTask,
|
|
1129
|
+
...updateData,
|
|
1130
|
+
id: existingTask.id,
|
|
1131
|
+
projectId: existingTask.projectId,
|
|
1132
|
+
createdAt: existingTask.createdAt,
|
|
1133
|
+
updatedAt: now,
|
|
1134
|
+
completedAt: updateData.status === "done" && existingTask.status !== "done" ? now : updateData.status && updateData.status !== "done" ? null : existingTask.completedAt
|
|
1135
|
+
};
|
|
1136
|
+
projectData.tasks[taskIndex] = updatedTask;
|
|
1137
|
+
projectData.project.updatedAt = now;
|
|
1138
|
+
const filePath = storage.getFilePath(projectId);
|
|
1139
|
+
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
1140
|
+
await writeJsonFile2(filePath, projectData);
|
|
1141
|
+
res.json({ success: true, data: updatedTask });
|
|
1142
|
+
} catch (error) {
|
|
1143
|
+
res.status(500).json({ error: error.message });
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
app.delete("/api/tasks", async (req, res) => {
|
|
1147
|
+
try {
|
|
1148
|
+
const { projectId, taskId } = req.query;
|
|
1149
|
+
const projectData = await storage.readProject(projectId);
|
|
1150
|
+
if (!projectData) {
|
|
1151
|
+
res.status(404).json({ error: "Project not found" });
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
|
|
1155
|
+
if (taskIndex === -1) {
|
|
1156
|
+
res.status(404).json({ error: "Task not found" });
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
projectData.tasks.splice(taskIndex, 1);
|
|
1160
|
+
projectData.project.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1161
|
+
const filePath = storage.getFilePath(projectId);
|
|
1162
|
+
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
1163
|
+
await writeJsonFile2(filePath, projectData);
|
|
1164
|
+
res.json({ success: true });
|
|
1165
|
+
} catch (error) {
|
|
1166
|
+
res.status(500).json({ error: error.message });
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
const distPath = path4.join(__dirname2, "app");
|
|
1170
|
+
app.use(express.static(distPath));
|
|
1171
|
+
app.get("*", (req, res) => {
|
|
1172
|
+
if (req.path.startsWith("/api")) {
|
|
1173
|
+
res.status(404).json({ error: "API not found" });
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
res.sendFile(path4.join(distPath, "index.html"));
|
|
1177
|
+
});
|
|
1178
|
+
const server = app.listen(port, "0.0.0.0", () => {
|
|
1179
|
+
console.log(`Web interface server running at http://0.0.0.0:${port}`);
|
|
1180
|
+
});
|
|
1181
|
+
return server;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// src/tools/web-tools.ts
|
|
1185
|
+
var activeServer = null;
|
|
1186
|
+
var openWebInterfaceTool = {
|
|
1187
|
+
name: "open_web_interface",
|
|
1188
|
+
description: "Open web visualization interface",
|
|
1189
|
+
parameters: {
|
|
1190
|
+
type: "object",
|
|
1191
|
+
properties: {
|
|
1192
|
+
port: {
|
|
1193
|
+
type: "number",
|
|
1194
|
+
description: "Port to run the web interface on (default: 7860)"
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
},
|
|
1198
|
+
async execute(args) {
|
|
1199
|
+
if (activeServer) {
|
|
1200
|
+
return {
|
|
1201
|
+
message: "Web interface is already running",
|
|
1202
|
+
url: `http://localhost:${activeServer.address().port}`
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
const port = args.port || 7860;
|
|
1206
|
+
activeServer = createServer(port);
|
|
1207
|
+
return {
|
|
1208
|
+
message: "Web interface started successfully",
|
|
1209
|
+
url: `http://localhost:${port}`
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
var closeWebInterfaceTool = {
|
|
1214
|
+
name: "close_web_interface",
|
|
1215
|
+
description: "Close web visualization interface",
|
|
1216
|
+
async execute() {
|
|
1217
|
+
if (!activeServer) {
|
|
1218
|
+
return { message: "Web interface is not running" };
|
|
1219
|
+
}
|
|
1220
|
+
return new Promise((resolve) => {
|
|
1221
|
+
activeServer.close(() => {
|
|
1222
|
+
activeServer = null;
|
|
1223
|
+
resolve({ message: "Web interface stopped" });
|
|
1224
|
+
});
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
|
|
1229
|
+
// src/tools/template-tools.ts
|
|
1230
|
+
init_esm_shims();
|
|
1231
|
+
import { z as z4 } from "zod";
|
|
1232
|
+
import * as path5 from "path";
|
|
1233
|
+
import * as fs2 from "fs/promises";
|
|
1234
|
+
var TEMPLATES_DIR = path5.join(process.cwd(), "templates");
|
|
1235
|
+
async function getTemplateFiles() {
|
|
1236
|
+
try {
|
|
1237
|
+
const files = await fs2.readdir(TEMPLATES_DIR);
|
|
1238
|
+
return files.filter((f) => f.endsWith(".json"));
|
|
1239
|
+
} catch {
|
|
1240
|
+
return [];
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
async function loadTemplate(templateName) {
|
|
1244
|
+
try {
|
|
1245
|
+
const fileName = templateName.endsWith(".json") ? templateName : `${templateName}.json`;
|
|
1246
|
+
const filePath = path5.join(TEMPLATES_DIR, fileName);
|
|
1247
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
1248
|
+
return JSON.parse(content);
|
|
1249
|
+
} catch {
|
|
1250
|
+
return null;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
function generateId(prefix) {
|
|
1254
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
1255
|
+
}
|
|
1256
|
+
var listTemplatesTool = {
|
|
1257
|
+
name: "list_templates",
|
|
1258
|
+
description: "List all available project templates",
|
|
1259
|
+
inputSchema: z4.object({}),
|
|
1260
|
+
async execute() {
|
|
1261
|
+
try {
|
|
1262
|
+
const templateFiles = await getTemplateFiles();
|
|
1263
|
+
const templates = [];
|
|
1264
|
+
for (const file of templateFiles) {
|
|
1265
|
+
const template = await loadTemplate(file);
|
|
1266
|
+
if (template) {
|
|
1267
|
+
templates.push({
|
|
1268
|
+
name: file.replace(".json", ""),
|
|
1269
|
+
displayName: template.name,
|
|
1270
|
+
description: template.description,
|
|
1271
|
+
projectType: template.projectType,
|
|
1272
|
+
taskCount: template.tasks.length,
|
|
1273
|
+
tagCount: template.tags.length
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
return {
|
|
1278
|
+
success: true,
|
|
1279
|
+
data: templates
|
|
1280
|
+
};
|
|
1281
|
+
} catch (error) {
|
|
1282
|
+
return {
|
|
1283
|
+
success: false,
|
|
1284
|
+
error: error instanceof Error ? error.message : "Failed to list templates"
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
};
|
|
1289
|
+
var getTemplateTool = {
|
|
1290
|
+
name: "get_template",
|
|
1291
|
+
description: "Get detailed information about a specific template",
|
|
1292
|
+
inputSchema: z4.object({
|
|
1293
|
+
templateName: z4.string().min(1, "Template name is required")
|
|
1294
|
+
}),
|
|
1295
|
+
async execute(input) {
|
|
1296
|
+
try {
|
|
1297
|
+
const template = await loadTemplate(input.templateName);
|
|
1298
|
+
if (!template) {
|
|
1299
|
+
return {
|
|
1300
|
+
success: false,
|
|
1301
|
+
error: `Template '${input.templateName}' not found`
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
return {
|
|
1305
|
+
success: true,
|
|
1306
|
+
data: {
|
|
1307
|
+
name: template.name,
|
|
1308
|
+
description: template.description,
|
|
1309
|
+
projectType: template.projectType,
|
|
1310
|
+
tasks: template.tasks.map((t) => ({
|
|
1311
|
+
title: t.title,
|
|
1312
|
+
description: t.description,
|
|
1313
|
+
priority: t.priority,
|
|
1314
|
+
tags: t.tags,
|
|
1315
|
+
estimatedHours: t.estimatedHours
|
|
1316
|
+
})),
|
|
1317
|
+
tags: template.tags
|
|
1318
|
+
}
|
|
1319
|
+
};
|
|
1320
|
+
} catch (error) {
|
|
1321
|
+
return {
|
|
1322
|
+
success: false,
|
|
1323
|
+
error: error instanceof Error ? error.message : "Failed to get template"
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
};
|
|
1328
|
+
var applyTemplateTool = {
|
|
1329
|
+
name: "apply_template",
|
|
1330
|
+
description: "Create a new project from a template",
|
|
1331
|
+
inputSchema: z4.object({
|
|
1332
|
+
templateName: z4.string().min(1, "Template name is required"),
|
|
1333
|
+
projectName: z4.string().min(1, "Project name is required"),
|
|
1334
|
+
description: z4.string().default(""),
|
|
1335
|
+
startDate: z4.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").optional(),
|
|
1336
|
+
targetDate: z4.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in YYYY-MM-DD format").optional()
|
|
1337
|
+
}),
|
|
1338
|
+
async execute(input) {
|
|
1339
|
+
try {
|
|
1340
|
+
const template = await loadTemplate(input.templateName);
|
|
1341
|
+
if (!template) {
|
|
1342
|
+
return {
|
|
1343
|
+
success: false,
|
|
1344
|
+
error: `Template '${input.templateName}' not found`
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
const projectInput = {
|
|
1348
|
+
name: input.projectName,
|
|
1349
|
+
description: input.description || template.description,
|
|
1350
|
+
projectType: template.projectType,
|
|
1351
|
+
startDate: input.startDate || (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
1352
|
+
targetDate: input.targetDate || (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
1353
|
+
};
|
|
1354
|
+
const projectData = await storage.createProject(projectInput);
|
|
1355
|
+
const projectId = projectData.project.id;
|
|
1356
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1357
|
+
const tagNameToId = /* @__PURE__ */ new Map();
|
|
1358
|
+
const tags = [];
|
|
1359
|
+
for (const templateTag of template.tags) {
|
|
1360
|
+
const tag = {
|
|
1361
|
+
id: generateId("tag"),
|
|
1362
|
+
name: templateTag.name,
|
|
1363
|
+
color: templateTag.color,
|
|
1364
|
+
description: "",
|
|
1365
|
+
createdAt: now
|
|
1366
|
+
};
|
|
1367
|
+
tags.push(tag);
|
|
1368
|
+
tagNameToId.set(templateTag.name, tag.id);
|
|
1369
|
+
}
|
|
1370
|
+
const tasks = [];
|
|
1371
|
+
for (const templateTask of template.tasks) {
|
|
1372
|
+
const taskTagIds = templateTask.tags.map((tagName) => tagNameToId.get(tagName)).filter((id) => id !== void 0);
|
|
1373
|
+
const task = {
|
|
1374
|
+
id: generateId("task"),
|
|
1375
|
+
projectId,
|
|
1376
|
+
title: templateTask.title,
|
|
1377
|
+
description: templateTask.description,
|
|
1378
|
+
status: "todo",
|
|
1379
|
+
priority: templateTask.priority,
|
|
1380
|
+
tags: taskTagIds,
|
|
1381
|
+
dueDate: null,
|
|
1382
|
+
assignee: null,
|
|
1383
|
+
createdAt: now,
|
|
1384
|
+
updatedAt: now,
|
|
1385
|
+
completedAt: null
|
|
1386
|
+
};
|
|
1387
|
+
tasks.push(task);
|
|
1388
|
+
}
|
|
1389
|
+
projectData.tags = tags;
|
|
1390
|
+
projectData.tasks = tasks;
|
|
1391
|
+
projectData.project.updatedAt = now;
|
|
1392
|
+
const filePath = storage.getFilePath(projectId);
|
|
1393
|
+
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
1394
|
+
await writeJsonFile2(filePath, projectData);
|
|
1395
|
+
return {
|
|
1396
|
+
success: true,
|
|
1397
|
+
data: {
|
|
1398
|
+
project: projectData.project,
|
|
1399
|
+
taskCount: tasks.length,
|
|
1400
|
+
tagCount: tags.length,
|
|
1401
|
+
tasksCreated: tasks.map((t) => ({
|
|
1402
|
+
id: t.id,
|
|
1403
|
+
title: t.title,
|
|
1404
|
+
priority: t.priority
|
|
1405
|
+
})),
|
|
1406
|
+
tagsCreated: tags.map((t) => ({
|
|
1407
|
+
id: t.id,
|
|
1408
|
+
name: t.name,
|
|
1409
|
+
color: t.color
|
|
1410
|
+
}))
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1413
|
+
} catch (error) {
|
|
1414
|
+
return {
|
|
1415
|
+
success: false,
|
|
1416
|
+
error: error instanceof Error ? error.message : "Failed to apply template"
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
|
|
1422
|
+
// src/resources/index.ts
|
|
1423
|
+
init_esm_shims();
|
|
1424
|
+
|
|
1425
|
+
// src/resources/project-resources.ts
|
|
1426
|
+
init_esm_shims();
|
|
1427
|
+
var projectResources = [
|
|
1428
|
+
{
|
|
1429
|
+
uri: "roadmap://projects",
|
|
1430
|
+
name: "Project List",
|
|
1431
|
+
mimeType: "application/json",
|
|
1432
|
+
description: "Returns a list of all projects with basic metadata"
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
uri: "roadmap://project/{projectId}",
|
|
1436
|
+
name: "Project Details",
|
|
1437
|
+
mimeType: "application/json",
|
|
1438
|
+
description: "Returns detailed information about a specific project including tasks, milestones, and tags"
|
|
1439
|
+
},
|
|
1440
|
+
{
|
|
1441
|
+
uri: "roadmap://project/{projectId}/tasks",
|
|
1442
|
+
name: "Project Tasks",
|
|
1443
|
+
mimeType: "application/json",
|
|
1444
|
+
description: "Returns all tasks for a specific project"
|
|
1445
|
+
},
|
|
1446
|
+
{
|
|
1447
|
+
uri: "roadmap://project/{projectId}/progress",
|
|
1448
|
+
name: "Project Progress",
|
|
1449
|
+
mimeType: "application/json",
|
|
1450
|
+
description: "Returns progress statistics for a specific project"
|
|
1451
|
+
}
|
|
1452
|
+
];
|
|
1453
|
+
var RESOURCE_PATTERNS = {
|
|
1454
|
+
projectList: /^roadmap:\/\/projects$/,
|
|
1455
|
+
projectDetails: /^roadmap:\/\/project\/([^/]+)$/,
|
|
1456
|
+
projectTasks: /^roadmap:\/\/project\/([^/]+)\/tasks$/,
|
|
1457
|
+
projectProgress: /^roadmap:\/\/project\/([^/]+)\/progress$/
|
|
1458
|
+
};
|
|
1459
|
+
async function handleResourceRequest(uri) {
|
|
1460
|
+
if (RESOURCE_PATTERNS.projectList.test(uri)) {
|
|
1461
|
+
return await getProjectListResource(uri);
|
|
1462
|
+
}
|
|
1463
|
+
const projectDetailsMatch = uri.match(RESOURCE_PATTERNS.projectDetails);
|
|
1464
|
+
if (projectDetailsMatch && !uri.includes("/tasks") && !uri.includes("/progress")) {
|
|
1465
|
+
const projectId = projectDetailsMatch[1];
|
|
1466
|
+
return await getProjectDetailsResource(uri, projectId);
|
|
1467
|
+
}
|
|
1468
|
+
const projectTasksMatch = uri.match(RESOURCE_PATTERNS.projectTasks);
|
|
1469
|
+
if (projectTasksMatch) {
|
|
1470
|
+
const projectId = projectTasksMatch[1];
|
|
1471
|
+
return await getProjectTasksResource(uri, projectId);
|
|
1472
|
+
}
|
|
1473
|
+
const projectProgressMatch = uri.match(RESOURCE_PATTERNS.projectProgress);
|
|
1474
|
+
if (projectProgressMatch) {
|
|
1475
|
+
const projectId = projectProgressMatch[1];
|
|
1476
|
+
return await getProjectProgressResource(uri, projectId);
|
|
1477
|
+
}
|
|
1478
|
+
return null;
|
|
1479
|
+
}
|
|
1480
|
+
async function getProjectListResource(uri) {
|
|
1481
|
+
const projects = await storage.listProjects();
|
|
1482
|
+
const data = {
|
|
1483
|
+
projects: projects.map((p) => ({
|
|
1484
|
+
id: p.project.id,
|
|
1485
|
+
name: p.project.name,
|
|
1486
|
+
description: p.project.description,
|
|
1487
|
+
status: p.project.status,
|
|
1488
|
+
projectType: p.project.projectType,
|
|
1489
|
+
taskCount: p.taskCount,
|
|
1490
|
+
milestoneCount: p.milestoneCount,
|
|
1491
|
+
updatedAt: p.project.updatedAt
|
|
1492
|
+
})),
|
|
1493
|
+
totalCount: projects.length
|
|
1494
|
+
};
|
|
1495
|
+
return {
|
|
1496
|
+
uri,
|
|
1497
|
+
mimeType: "application/json",
|
|
1498
|
+
text: JSON.stringify(data, null, 2)
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
async function getProjectDetailsResource(uri, projectId) {
|
|
1502
|
+
const projectData = await storage.readProject(projectId);
|
|
1503
|
+
if (!projectData) {
|
|
1504
|
+
return null;
|
|
1505
|
+
}
|
|
1506
|
+
const data = {
|
|
1507
|
+
project: projectData.project,
|
|
1508
|
+
milestones: projectData.milestones,
|
|
1509
|
+
tasks: projectData.tasks,
|
|
1510
|
+
tags: projectData.tags,
|
|
1511
|
+
stats: {
|
|
1512
|
+
taskCount: projectData.tasks.length,
|
|
1513
|
+
milestoneCount: projectData.milestones.length,
|
|
1514
|
+
tagCount: projectData.tags.length,
|
|
1515
|
+
completedTasks: projectData.tasks.filter((t) => t.status === "done").length,
|
|
1516
|
+
inProgressTasks: projectData.tasks.filter((t) => t.status === "in-progress").length
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
return {
|
|
1520
|
+
uri,
|
|
1521
|
+
mimeType: "application/json",
|
|
1522
|
+
text: JSON.stringify(data, null, 2)
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
async function getProjectTasksResource(uri, projectId) {
|
|
1526
|
+
const projectData = await storage.readProject(projectId);
|
|
1527
|
+
if (!projectData) {
|
|
1528
|
+
return null;
|
|
1529
|
+
}
|
|
1530
|
+
const tasksByStatus = {
|
|
1531
|
+
todo: projectData.tasks.filter((t) => t.status === "todo"),
|
|
1532
|
+
inProgress: projectData.tasks.filter((t) => t.status === "in-progress"),
|
|
1533
|
+
review: projectData.tasks.filter((t) => t.status === "review"),
|
|
1534
|
+
done: projectData.tasks.filter((t) => t.status === "done")
|
|
1535
|
+
};
|
|
1536
|
+
const data = {
|
|
1537
|
+
projectId,
|
|
1538
|
+
projectName: projectData.project.name,
|
|
1539
|
+
tasks: projectData.tasks,
|
|
1540
|
+
tasksByStatus,
|
|
1541
|
+
summary: {
|
|
1542
|
+
total: projectData.tasks.length,
|
|
1543
|
+
todo: tasksByStatus.todo.length,
|
|
1544
|
+
inProgress: tasksByStatus.inProgress.length,
|
|
1545
|
+
review: tasksByStatus.review.length,
|
|
1546
|
+
done: tasksByStatus.done.length
|
|
1547
|
+
}
|
|
1548
|
+
};
|
|
1549
|
+
return {
|
|
1550
|
+
uri,
|
|
1551
|
+
mimeType: "application/json",
|
|
1552
|
+
text: JSON.stringify(data, null, 2)
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
async function getProjectProgressResource(uri, projectId) {
|
|
1556
|
+
const projectData = await storage.readProject(projectId);
|
|
1557
|
+
if (!projectData) {
|
|
1558
|
+
return null;
|
|
1559
|
+
}
|
|
1560
|
+
const tasks = projectData.tasks;
|
|
1561
|
+
const totalTasks = tasks.length;
|
|
1562
|
+
const completedTasks = tasks.filter((t) => t.status === "done").length;
|
|
1563
|
+
const inProgressTasks = tasks.filter((t) => t.status === "in-progress").length;
|
|
1564
|
+
const reviewTasks = tasks.filter((t) => t.status === "review").length;
|
|
1565
|
+
const todoTasks = tasks.filter((t) => t.status === "todo").length;
|
|
1566
|
+
const completionPercentage = totalTasks > 0 ? Math.round(completedTasks / totalTasks * 100) : 0;
|
|
1567
|
+
const milestones = projectData.milestones;
|
|
1568
|
+
const completedMilestones = milestones.filter((m) => m.completedAt !== null).length;
|
|
1569
|
+
const milestoneProgress = milestones.length > 0 ? Math.round(completedMilestones / milestones.length * 100) : 0;
|
|
1570
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1571
|
+
const overdueTasks = tasks.filter(
|
|
1572
|
+
(t) => t.dueDate && t.dueDate < now && t.status !== "done"
|
|
1573
|
+
);
|
|
1574
|
+
const priorityBreakdown = {
|
|
1575
|
+
critical: tasks.filter((t) => t.priority === "critical").length,
|
|
1576
|
+
high: tasks.filter((t) => t.priority === "high").length,
|
|
1577
|
+
medium: tasks.filter((t) => t.priority === "medium").length,
|
|
1578
|
+
low: tasks.filter((t) => t.priority === "low").length
|
|
1579
|
+
};
|
|
1580
|
+
const data = {
|
|
1581
|
+
projectId,
|
|
1582
|
+
projectName: projectData.project.name,
|
|
1583
|
+
projectStatus: projectData.project.status,
|
|
1584
|
+
dates: {
|
|
1585
|
+
startDate: projectData.project.startDate,
|
|
1586
|
+
targetDate: projectData.project.targetDate,
|
|
1587
|
+
daysRemaining: calculateDaysRemaining(projectData.project.targetDate)
|
|
1588
|
+
},
|
|
1589
|
+
taskProgress: {
|
|
1590
|
+
total: totalTasks,
|
|
1591
|
+
completed: completedTasks,
|
|
1592
|
+
inProgress: inProgressTasks,
|
|
1593
|
+
review: reviewTasks,
|
|
1594
|
+
todo: todoTasks,
|
|
1595
|
+
completionPercentage
|
|
1596
|
+
},
|
|
1597
|
+
milestoneProgress: {
|
|
1598
|
+
total: milestones.length,
|
|
1599
|
+
completed: completedMilestones,
|
|
1600
|
+
percentage: milestoneProgress
|
|
1601
|
+
},
|
|
1602
|
+
overdueTasks: {
|
|
1603
|
+
count: overdueTasks.length,
|
|
1604
|
+
tasks: overdueTasks.map((t) => ({
|
|
1605
|
+
id: t.id,
|
|
1606
|
+
title: t.title,
|
|
1607
|
+
dueDate: t.dueDate,
|
|
1608
|
+
status: t.status
|
|
1609
|
+
}))
|
|
1610
|
+
},
|
|
1611
|
+
priorityBreakdown,
|
|
1612
|
+
lastUpdated: projectData.project.updatedAt
|
|
1613
|
+
};
|
|
1614
|
+
return {
|
|
1615
|
+
uri,
|
|
1616
|
+
mimeType: "application/json",
|
|
1617
|
+
text: JSON.stringify(data, null, 2)
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
function calculateDaysRemaining(targetDate) {
|
|
1621
|
+
const target = new Date(targetDate);
|
|
1622
|
+
const now = /* @__PURE__ */ new Date();
|
|
1623
|
+
const diffTime = target.getTime() - now.getTime();
|
|
1624
|
+
const diffDays = Math.ceil(diffTime / (1e3 * 60 * 60 * 24));
|
|
1625
|
+
return diffDays > 0 ? diffDays : null;
|
|
1626
|
+
}
|
|
1627
|
+
function getAllResources() {
|
|
1628
|
+
return projectResources;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
// src/prompts/index.ts
|
|
1632
|
+
init_esm_shims();
|
|
1633
|
+
|
|
1634
|
+
// src/prompts/project-prompts.ts
|
|
1635
|
+
init_esm_shims();
|
|
1636
|
+
var projectPrompts = [
|
|
1637
|
+
{
|
|
1638
|
+
name: "projectPlanningPrompt",
|
|
1639
|
+
description: "Helps users plan a new project with structured guidance",
|
|
1640
|
+
arguments: [
|
|
1641
|
+
{
|
|
1642
|
+
name: "projectType",
|
|
1643
|
+
description: "Type of project (roadmap, skill-tree, kanban)",
|
|
1644
|
+
required: false
|
|
1645
|
+
}
|
|
1646
|
+
]
|
|
1647
|
+
},
|
|
1648
|
+
{
|
|
1649
|
+
name: "taskManagementPrompt",
|
|
1650
|
+
description: "Helps users manage and organize tasks effectively",
|
|
1651
|
+
arguments: [
|
|
1652
|
+
{
|
|
1653
|
+
name: "projectId",
|
|
1654
|
+
description: "ID of the project to manage tasks for",
|
|
1655
|
+
required: false
|
|
1656
|
+
}
|
|
1657
|
+
]
|
|
1658
|
+
},
|
|
1659
|
+
{
|
|
1660
|
+
name: "roadmapOverviewPrompt",
|
|
1661
|
+
description: "Shows a comprehensive overview of the roadmap",
|
|
1662
|
+
arguments: []
|
|
1663
|
+
},
|
|
1664
|
+
{
|
|
1665
|
+
name: "milestoneReviewPrompt",
|
|
1666
|
+
description: "Helps review and evaluate project milestones",
|
|
1667
|
+
arguments: [
|
|
1668
|
+
{
|
|
1669
|
+
name: "projectId",
|
|
1670
|
+
description: "ID of the project to review milestones for",
|
|
1671
|
+
required: false
|
|
1672
|
+
}
|
|
1673
|
+
]
|
|
1674
|
+
}
|
|
1675
|
+
];
|
|
1676
|
+
function getProjectPlanningPrompt(projectType) {
|
|
1677
|
+
const typeContext = projectType ? `You are planning a ${projectType} project.` : "You are planning a new project.";
|
|
1678
|
+
return {
|
|
1679
|
+
description: "Project Planning Assistant",
|
|
1680
|
+
messages: [
|
|
1681
|
+
{
|
|
1682
|
+
role: "user",
|
|
1683
|
+
content: {
|
|
1684
|
+
type: "text",
|
|
1685
|
+
text: `${typeContext}
|
|
1686
|
+
|
|
1687
|
+
I will help you plan your project step by step. Let's start by defining the key aspects:
|
|
1688
|
+
|
|
1689
|
+
## Step 1: Project Definition
|
|
1690
|
+
- What is the name of your project?
|
|
1691
|
+
- What is the main goal or objective?
|
|
1692
|
+
- Who is the target audience or beneficiary?
|
|
1693
|
+
|
|
1694
|
+
## Step 2: Scope and Timeline
|
|
1695
|
+
- What are the start and target completion dates?
|
|
1696
|
+
- What are the major deliverables?
|
|
1697
|
+
- What is explicitly out of scope?
|
|
1698
|
+
|
|
1699
|
+
## Step 3: Key Milestones
|
|
1700
|
+
Identify 3-5 major milestones that mark significant progress:
|
|
1701
|
+
- Milestone 1: [Name] - Target Date
|
|
1702
|
+
- Milestone 2: [Name] - Target Date
|
|
1703
|
+
- Milestone 3: [Name] - Target Date
|
|
1704
|
+
|
|
1705
|
+
## Step 4: Initial Tasks
|
|
1706
|
+
List the first 5-10 tasks to get started:
|
|
1707
|
+
- Task name and brief description
|
|
1708
|
+
- Priority level (low, medium, high, critical)
|
|
1709
|
+
- Estimated completion time
|
|
1710
|
+
|
|
1711
|
+
## Step 5: Resources and Tags
|
|
1712
|
+
- What categories or tags will help organize this project?
|
|
1713
|
+
- Are there any specific resources, tools, or people needed?
|
|
1714
|
+
|
|
1715
|
+
## Example
|
|
1716
|
+
Here's a simple example for a "Learn TypeScript" skill-tree project:
|
|
1717
|
+
|
|
1718
|
+
**Project**: TypeScript Mastery
|
|
1719
|
+
**Goal**: Become proficient in TypeScript for web development
|
|
1720
|
+
**Timeline**: 3 months (Jan 1 - Mar 31)
|
|
1721
|
+
|
|
1722
|
+
**Milestones**:
|
|
1723
|
+
1. Complete basic syntax (Week 2)
|
|
1724
|
+
2. Build first TypeScript app (Week 6)
|
|
1725
|
+
3. Master advanced types (Week 10)
|
|
1726
|
+
4. Complete final project (Week 12)
|
|
1727
|
+
|
|
1728
|
+
**Initial Tasks**:
|
|
1729
|
+
1. Set up TypeScript environment (high priority)
|
|
1730
|
+
2. Complete "Hello World" tutorial (medium priority)
|
|
1731
|
+
3. Study type annotations (high priority)
|
|
1732
|
+
4. Practice with interfaces (medium priority)
|
|
1733
|
+
|
|
1734
|
+
Please share your project details, and I'll help you refine the plan!`
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
]
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
function getTaskManagementPrompt(projectId) {
|
|
1741
|
+
const projectContext = projectId ? `Managing tasks for project: ${projectId}` : "Managing tasks across all projects";
|
|
1742
|
+
return {
|
|
1743
|
+
description: "Task Management Assistant",
|
|
1744
|
+
messages: [
|
|
1745
|
+
{
|
|
1746
|
+
role: "user",
|
|
1747
|
+
content: {
|
|
1748
|
+
type: "text",
|
|
1749
|
+
text: `${projectContext}
|
|
1750
|
+
|
|
1751
|
+
I'll help you manage your tasks effectively. Here are the key areas we can work on:
|
|
1752
|
+
|
|
1753
|
+
## Task Organization
|
|
1754
|
+
|
|
1755
|
+
### Current Status Review
|
|
1756
|
+
Let's review tasks by status:
|
|
1757
|
+
- **Todo**: Tasks waiting to be started
|
|
1758
|
+
- **In Progress**: Tasks currently being worked on
|
|
1759
|
+
- **Review**: Tasks completed and awaiting review
|
|
1760
|
+
- **Done**: Completed tasks
|
|
1761
|
+
|
|
1762
|
+
### Priority Management
|
|
1763
|
+
Tasks should be prioritized as:
|
|
1764
|
+
- **Critical**: Blockers, urgent deadlines, essential functionality
|
|
1765
|
+
- **High**: Important features, near-term deadlines
|
|
1766
|
+
- **Medium**: Standard work, nice-to-have features
|
|
1767
|
+
- **Low**: Backlog items, future improvements
|
|
1768
|
+
|
|
1769
|
+
## Task Actions
|
|
1770
|
+
|
|
1771
|
+
### Creating New Tasks
|
|
1772
|
+
When creating a task, include:
|
|
1773
|
+
- Clear, actionable title
|
|
1774
|
+
- Detailed description with acceptance criteria
|
|
1775
|
+
- Priority level
|
|
1776
|
+
- Due date (if applicable)
|
|
1777
|
+
- Assignee (if applicable)
|
|
1778
|
+
- Relevant tags
|
|
1779
|
+
|
|
1780
|
+
### Updating Tasks
|
|
1781
|
+
Common updates include:
|
|
1782
|
+
- Status changes (todo \u2192 in progress \u2192 review \u2192 done)
|
|
1783
|
+
- Priority adjustments
|
|
1784
|
+
- Due date modifications
|
|
1785
|
+
- Adding comments or notes
|
|
1786
|
+
|
|
1787
|
+
### Batch Operations
|
|
1788
|
+
You can perform actions on multiple tasks:
|
|
1789
|
+
- Update status for multiple tasks
|
|
1790
|
+
- Reassign tasks
|
|
1791
|
+
- Apply tags to multiple tasks
|
|
1792
|
+
|
|
1793
|
+
## Best Practices
|
|
1794
|
+
|
|
1795
|
+
1. **Keep tasks small**: Break large work into manageable pieces
|
|
1796
|
+
2. **Write clear titles**: Use action verbs (e.g., "Implement", "Fix", "Update")
|
|
1797
|
+
3. **Set realistic due dates**: Account for dependencies and blockers
|
|
1798
|
+
4. **Review regularly**: Check overdue tasks and adjust priorities
|
|
1799
|
+
5. **Use tags consistently**: Create a tagging convention and stick to it
|
|
1800
|
+
|
|
1801
|
+
## Example Workflow
|
|
1802
|
+
|
|
1803
|
+
**Morning Routine**:
|
|
1804
|
+
1. Review overdue tasks
|
|
1805
|
+
2. Check what's in progress
|
|
1806
|
+
3. Prioritize today's work
|
|
1807
|
+
4. Update task statuses
|
|
1808
|
+
|
|
1809
|
+
**Weekly Review**:
|
|
1810
|
+
1. Review completed tasks
|
|
1811
|
+
2. Assess progress toward milestones
|
|
1812
|
+
3. Reprioritize upcoming work
|
|
1813
|
+
4. Identify blockers
|
|
1814
|
+
|
|
1815
|
+
What would you like to work on? You can:
|
|
1816
|
+
- View tasks by status
|
|
1817
|
+
- Create new tasks
|
|
1818
|
+
- Update existing tasks
|
|
1819
|
+
- Search for specific tasks
|
|
1820
|
+
- Get an overview of task distribution`
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
]
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
function getRoadmapOverviewPrompt() {
|
|
1827
|
+
return {
|
|
1828
|
+
description: "Roadmap Overview Assistant",
|
|
1829
|
+
messages: [
|
|
1830
|
+
{
|
|
1831
|
+
role: "user",
|
|
1832
|
+
content: {
|
|
1833
|
+
type: "text",
|
|
1834
|
+
text: `Welcome to your Roadmap Overview!
|
|
1835
|
+
|
|
1836
|
+
I'll help you get a comprehensive view of all your projects and their progress. Here's what we can explore:
|
|
1837
|
+
|
|
1838
|
+
## Dashboard Overview
|
|
1839
|
+
|
|
1840
|
+
### Project Summary
|
|
1841
|
+
- Total number of projects
|
|
1842
|
+
- Projects by status (active, completed, archived)
|
|
1843
|
+
- Projects by type (roadmap, skill-tree, kanban)
|
|
1844
|
+
- Recently updated projects
|
|
1845
|
+
|
|
1846
|
+
### Task Overview
|
|
1847
|
+
- Total tasks across all projects
|
|
1848
|
+
- Tasks by status (todo, in progress, review, done)
|
|
1849
|
+
- Overdue tasks requiring attention
|
|
1850
|
+
- Tasks by priority level
|
|
1851
|
+
|
|
1852
|
+
### Progress Metrics
|
|
1853
|
+
- Overall completion percentage
|
|
1854
|
+
- Milestones completed vs. total
|
|
1855
|
+
- Average tasks per project
|
|
1856
|
+
- Upcoming deadlines
|
|
1857
|
+
|
|
1858
|
+
## Project Health Indicators
|
|
1859
|
+
|
|
1860
|
+
### Green (Healthy)
|
|
1861
|
+
- On track with timeline
|
|
1862
|
+
- No overdue critical tasks
|
|
1863
|
+
- Regular progress being made
|
|
1864
|
+
|
|
1865
|
+
### Yellow (Attention Needed)
|
|
1866
|
+
- Some tasks overdue but not critical
|
|
1867
|
+
- Approaching deadline
|
|
1868
|
+
- Tasks stuck in review
|
|
1869
|
+
|
|
1870
|
+
### Red (Requires Action)
|
|
1871
|
+
- Critical tasks overdue
|
|
1872
|
+
- Missed milestones
|
|
1873
|
+
- No recent progress
|
|
1874
|
+
|
|
1875
|
+
## Navigation Guide
|
|
1876
|
+
|
|
1877
|
+
### Viewing Projects
|
|
1878
|
+
- List all projects with key metrics
|
|
1879
|
+
- Filter by status or type
|
|
1880
|
+
- Sort by various criteria (updated, created, name)
|
|
1881
|
+
|
|
1882
|
+
### Drilling Down
|
|
1883
|
+
- View specific project details
|
|
1884
|
+
- See all tasks for a project
|
|
1885
|
+
- Check milestone progress
|
|
1886
|
+
- Review tag usage
|
|
1887
|
+
|
|
1888
|
+
### Taking Action
|
|
1889
|
+
- Identify projects needing attention
|
|
1890
|
+
- Find overdue tasks
|
|
1891
|
+
- Review completed work
|
|
1892
|
+
- Plan upcoming work
|
|
1893
|
+
|
|
1894
|
+
## Example Queries
|
|
1895
|
+
|
|
1896
|
+
**"Show me all active projects"**
|
|
1897
|
+
Lists projects with status = active, sorted by most recently updated
|
|
1898
|
+
|
|
1899
|
+
**"What tasks are overdue?"**
|
|
1900
|
+
Shows all tasks where due date < today and status != done
|
|
1901
|
+
|
|
1902
|
+
**"Which projects are at risk?"**
|
|
1903
|
+
Identifies projects with overdue critical/high priority tasks
|
|
1904
|
+
|
|
1905
|
+
**"Show my progress this week"**
|
|
1906
|
+
Displays tasks completed in the last 7 days
|
|
1907
|
+
|
|
1908
|
+
## Quick Actions
|
|
1909
|
+
|
|
1910
|
+
1. **Review Overdue Items**: Check what needs immediate attention
|
|
1911
|
+
2. **Celebrate Wins**: Review recently completed tasks
|
|
1912
|
+
3. **Plan Ahead**: Look at upcoming milestones and deadlines
|
|
1913
|
+
4. **Clean Up**: Archive completed projects
|
|
1914
|
+
|
|
1915
|
+
What would you like to see first? I can show you:
|
|
1916
|
+
- A summary of all projects
|
|
1917
|
+
- Overdue tasks across all projects
|
|
1918
|
+
- Progress statistics
|
|
1919
|
+
- Specific project details`
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
]
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
function getMilestoneReviewPrompt(projectId) {
|
|
1926
|
+
const projectContext = projectId ? `Reviewing milestones for project: ${projectId}` : "Reviewing milestones across all projects";
|
|
1927
|
+
return {
|
|
1928
|
+
description: "Milestone Review Assistant",
|
|
1929
|
+
messages: [
|
|
1930
|
+
{
|
|
1931
|
+
role: "user",
|
|
1932
|
+
content: {
|
|
1933
|
+
type: "text",
|
|
1934
|
+
text: `${projectContext}
|
|
1935
|
+
|
|
1936
|
+
I'll help you review and evaluate your project milestones. Milestones are key checkpoints that help track significant progress.
|
|
1937
|
+
|
|
1938
|
+
## Milestone Review Framework
|
|
1939
|
+
|
|
1940
|
+
### What is a Milestone?
|
|
1941
|
+
A milestone represents a significant achievement or checkpoint in your project:
|
|
1942
|
+
- Major deliverable completion
|
|
1943
|
+
- Phase transition
|
|
1944
|
+
- Key decision point
|
|
1945
|
+
- External deadline
|
|
1946
|
+
|
|
1947
|
+
### Milestone Status Types
|
|
1948
|
+
- **Not Started**: Target date in future, no work completed
|
|
1949
|
+
- **In Progress**: Work ongoing toward milestone
|
|
1950
|
+
- **At Risk**: May not meet target date
|
|
1951
|
+
- **Completed**: Achieved and marked complete
|
|
1952
|
+
- **Missed**: Target date passed without completion
|
|
1953
|
+
|
|
1954
|
+
## Review Checklist
|
|
1955
|
+
|
|
1956
|
+
### For Each Milestone, Ask:
|
|
1957
|
+
|
|
1958
|
+
1. **Relevance**
|
|
1959
|
+
- Is this milestone still relevant to project goals?
|
|
1960
|
+
- Does it represent meaningful progress?
|
|
1961
|
+
- Are the success criteria clear?
|
|
1962
|
+
|
|
1963
|
+
2. **Timeline**
|
|
1964
|
+
- Is the target date realistic?
|
|
1965
|
+
- Are there dependencies blocking progress?
|
|
1966
|
+
- Do we need to adjust the date?
|
|
1967
|
+
|
|
1968
|
+
3. **Progress**
|
|
1969
|
+
- What percentage complete is this milestone?
|
|
1970
|
+
- Which tasks contribute to this milestone?
|
|
1971
|
+
- Are there blockers or risks?
|
|
1972
|
+
|
|
1973
|
+
4. **Completion Criteria**
|
|
1974
|
+
- What defines "complete" for this milestone?
|
|
1975
|
+
- Are there acceptance criteria?
|
|
1976
|
+
- Who needs to sign off?
|
|
1977
|
+
|
|
1978
|
+
## Milestone Management Actions
|
|
1979
|
+
|
|
1980
|
+
### Creating Milestones
|
|
1981
|
+
Best practices for new milestones:
|
|
1982
|
+
- Use clear, descriptive names
|
|
1983
|
+
- Set realistic target dates
|
|
1984
|
+
- Define completion criteria
|
|
1985
|
+
- Link related tasks
|
|
1986
|
+
|
|
1987
|
+
### Updating Milestones
|
|
1988
|
+
Common updates during review:
|
|
1989
|
+
- Adjust target dates based on progress
|
|
1990
|
+
- Update descriptions to reflect scope changes
|
|
1991
|
+
- Mark completed when criteria met
|
|
1992
|
+
- Archive obsolete milestones
|
|
1993
|
+
|
|
1994
|
+
### Tracking Progress
|
|
1995
|
+
Ways to monitor milestone health:
|
|
1996
|
+
- Percentage of linked tasks complete
|
|
1997
|
+
- Time remaining vs. work remaining
|
|
1998
|
+
- Risk assessment (low/medium/high)
|
|
1999
|
+
- Dependency status
|
|
2000
|
+
|
|
2001
|
+
## Example Milestone Review
|
|
2002
|
+
|
|
2003
|
+
**Project**: Website Redesign
|
|
2004
|
+
**Milestone**: Design Phase Complete
|
|
2005
|
+
**Target Date**: March 15, 2024
|
|
2006
|
+
|
|
2007
|
+
**Review Questions**:
|
|
2008
|
+
- \u2713 Relevance: Still critical path item
|
|
2009
|
+
- \u26A0 Timeline: 5 days remaining, 60% complete
|
|
2010
|
+
- Progress: 3 of 5 design tasks done
|
|
2011
|
+
- Risks: User research taking longer than expected
|
|
2012
|
+
|
|
2013
|
+
**Decision**: Extend target date by 3 days, add resources to user research
|
|
2014
|
+
|
|
2015
|
+
## Review Schedule Recommendations
|
|
2016
|
+
|
|
2017
|
+
### Weekly
|
|
2018
|
+
- Quick check of upcoming milestones (next 2 weeks)
|
|
2019
|
+
- Identify any at-risk items
|
|
2020
|
+
|
|
2021
|
+
### Monthly
|
|
2022
|
+
- Full review of all active milestones
|
|
2023
|
+
- Adjust timelines as needed
|
|
2024
|
+
- Celebrate completed milestones
|
|
2025
|
+
|
|
2026
|
+
### Quarterly
|
|
2027
|
+
- Strategic review of milestone alignment
|
|
2028
|
+
- Archive completed project milestones
|
|
2029
|
+
- Plan next quarter's milestones
|
|
2030
|
+
|
|
2031
|
+
## Metrics to Track
|
|
2032
|
+
|
|
2033
|
+
- **Milestone Completion Rate**: % of milestones completed on time
|
|
2034
|
+
- **Average Delay**: How often and by how much dates slip
|
|
2035
|
+
- **Scope Changes**: Number of milestones added/removed
|
|
2036
|
+
- **Predictability**: Variance between planned and actual dates
|
|
2037
|
+
|
|
2038
|
+
What would you like to do?
|
|
2039
|
+
- Review milestones for a specific project
|
|
2040
|
+
- Identify at-risk milestones
|
|
2041
|
+
- Plan new milestones
|
|
2042
|
+
- Analyze milestone performance
|
|
2043
|
+
- Update existing milestones`
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
]
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
function getPromptByName(name, args) {
|
|
2050
|
+
switch (name) {
|
|
2051
|
+
case "projectPlanningPrompt":
|
|
2052
|
+
return getProjectPlanningPrompt(args?.projectType);
|
|
2053
|
+
case "taskManagementPrompt":
|
|
2054
|
+
return getTaskManagementPrompt(args?.projectId);
|
|
2055
|
+
case "roadmapOverviewPrompt":
|
|
2056
|
+
return getRoadmapOverviewPrompt();
|
|
2057
|
+
case "milestoneReviewPrompt":
|
|
2058
|
+
return getMilestoneReviewPrompt(args?.projectId);
|
|
2059
|
+
default:
|
|
2060
|
+
return null;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
function getAllPrompts() {
|
|
2064
|
+
return projectPrompts;
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
// src/server.ts
|
|
2068
|
+
var allTools = [
|
|
2069
|
+
createProjectTool,
|
|
2070
|
+
listProjectsTool,
|
|
2071
|
+
getProjectTool,
|
|
2072
|
+
updateProjectTool,
|
|
2073
|
+
deleteProjectTool,
|
|
2074
|
+
createTaskTool,
|
|
2075
|
+
listTasksTool,
|
|
2076
|
+
getTaskTool,
|
|
2077
|
+
updateTaskTool,
|
|
2078
|
+
deleteTaskTool,
|
|
2079
|
+
batchUpdateTasksTool,
|
|
2080
|
+
createTagTool,
|
|
2081
|
+
listTagsTool,
|
|
2082
|
+
updateTagTool,
|
|
2083
|
+
deleteTagTool,
|
|
2084
|
+
getTasksByTagTool,
|
|
2085
|
+
openWebInterfaceTool,
|
|
2086
|
+
closeWebInterfaceTool
|
|
2087
|
+
];
|
|
2088
|
+
var toolMap = new Map(allTools.map((tool) => [tool.name, tool]));
|
|
2089
|
+
function createServer2() {
|
|
2090
|
+
const server = new Server(
|
|
2091
|
+
{
|
|
2092
|
+
name: "roadmap-skill",
|
|
2093
|
+
version: "0.1.0"
|
|
2094
|
+
},
|
|
2095
|
+
{
|
|
2096
|
+
capabilities: {
|
|
2097
|
+
tools: {},
|
|
2098
|
+
resources: {},
|
|
2099
|
+
prompts: {}
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
);
|
|
2103
|
+
server.setRequestHandler(ListToolsRequestSchema, async (_request) => {
|
|
2104
|
+
return {
|
|
2105
|
+
tools: allTools.map((tool) => {
|
|
2106
|
+
const inputSchema = tool.parameters || tool.inputSchema;
|
|
2107
|
+
return {
|
|
2108
|
+
name: tool.name,
|
|
2109
|
+
description: tool.description,
|
|
2110
|
+
inputSchema: inputSchema || { type: "object" }
|
|
2111
|
+
};
|
|
2112
|
+
})
|
|
2113
|
+
};
|
|
2114
|
+
});
|
|
2115
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2116
|
+
const { name, arguments: args } = request.params;
|
|
2117
|
+
const tool = toolMap.get(name);
|
|
2118
|
+
if (!tool) {
|
|
2119
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
2120
|
+
}
|
|
2121
|
+
try {
|
|
2122
|
+
const result = await tool.execute(args);
|
|
2123
|
+
return {
|
|
2124
|
+
content: [
|
|
2125
|
+
{
|
|
2126
|
+
type: "text",
|
|
2127
|
+
text: JSON.stringify(result, null, 2)
|
|
2128
|
+
}
|
|
2129
|
+
]
|
|
2130
|
+
};
|
|
2131
|
+
} catch (error) {
|
|
2132
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2133
|
+
throw new Error(`Tool execution failed: ${errorMessage}`);
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
server.setRequestHandler(ListResourcesRequestSchema, async (_request) => {
|
|
2137
|
+
const resources = getAllResources();
|
|
2138
|
+
return { resources };
|
|
2139
|
+
});
|
|
2140
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
2141
|
+
const uri = request.params.uri;
|
|
2142
|
+
const resourceContent = await handleResourceRequest(uri);
|
|
2143
|
+
if (!resourceContent) {
|
|
2144
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
2145
|
+
}
|
|
2146
|
+
return {
|
|
2147
|
+
contents: [resourceContent]
|
|
2148
|
+
};
|
|
2149
|
+
});
|
|
2150
|
+
server.setRequestHandler(ListPromptsRequestSchema, async (_request) => {
|
|
2151
|
+
const prompts = getAllPrompts();
|
|
2152
|
+
return { prompts };
|
|
2153
|
+
});
|
|
2154
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
2155
|
+
const { name, arguments: args } = request.params;
|
|
2156
|
+
const promptResult = getPromptByName(name, args);
|
|
2157
|
+
if (!promptResult) {
|
|
2158
|
+
throw new Error(`Prompt not found: ${name}`);
|
|
2159
|
+
}
|
|
2160
|
+
return promptResult;
|
|
2161
|
+
});
|
|
2162
|
+
return server;
|
|
2163
|
+
}
|
|
2164
|
+
async function startServer() {
|
|
2165
|
+
const server = createServer2();
|
|
2166
|
+
const transport = new StdioServerTransport();
|
|
2167
|
+
console.error("Roadmap Skill MCP Server starting...");
|
|
2168
|
+
try {
|
|
2169
|
+
await server.connect(transport);
|
|
2170
|
+
console.error("Roadmap Skill MCP Server connected and ready");
|
|
2171
|
+
} catch (error) {
|
|
2172
|
+
console.error("Failed to start server:", error);
|
|
2173
|
+
throw error;
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
// src/index.ts
|
|
2178
|
+
async function main() {
|
|
2179
|
+
process.on("SIGINT", () => {
|
|
2180
|
+
console.error("Received SIGINT, shutting down gracefully...");
|
|
2181
|
+
process.exit(0);
|
|
2182
|
+
});
|
|
2183
|
+
process.on("SIGTERM", () => {
|
|
2184
|
+
console.error("Received SIGTERM, shutting down gracefully...");
|
|
2185
|
+
process.exit(0);
|
|
2186
|
+
});
|
|
2187
|
+
process.on("uncaughtException", (error) => {
|
|
2188
|
+
console.error("Uncaught exception:", error);
|
|
2189
|
+
process.exit(1);
|
|
2190
|
+
});
|
|
2191
|
+
process.on("unhandledRejection", (reason) => {
|
|
2192
|
+
console.error("Unhandled rejection:", reason);
|
|
2193
|
+
process.exit(1);
|
|
2194
|
+
});
|
|
2195
|
+
try {
|
|
2196
|
+
await startServer();
|
|
2197
|
+
} catch (error) {
|
|
2198
|
+
console.error("Failed to start server:", error);
|
|
2199
|
+
process.exit(1);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
main();
|
|
2203
|
+
//# sourceMappingURL=index.js.map
|