roadmap-skill 0.2.9 → 0.2.11
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/CHANGELOG.md +67 -0
- package/README.md +28 -80
- package/README.zh.md +33 -80
- package/dist/index.js +129 -15
- package/dist/index.js.map +1 -1
- package/dist/web/server.js +86 -5
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -345,14 +345,15 @@ var storage = new ProjectStorage();
|
|
|
345
345
|
// src/tools/project-tools.ts
|
|
346
346
|
var ProjectTypeEnum = z.enum(["roadmap", "skill-tree", "kanban"]);
|
|
347
347
|
var ProjectStatusEnum = z.enum(["active", "completed", "archived"]);
|
|
348
|
-
function toProjectSummary(project, taskCount) {
|
|
348
|
+
function toProjectSummary(project, taskCount, tags) {
|
|
349
349
|
return {
|
|
350
350
|
id: project.id,
|
|
351
351
|
name: project.name,
|
|
352
352
|
projectType: project.projectType,
|
|
353
353
|
status: project.status,
|
|
354
354
|
targetDate: project.targetDate,
|
|
355
|
-
taskCount
|
|
355
|
+
taskCount,
|
|
356
|
+
...tags ? { tags } : {}
|
|
356
357
|
};
|
|
357
358
|
}
|
|
358
359
|
function toTaskSummary(task) {
|
|
@@ -401,16 +402,33 @@ var createProjectTool = {
|
|
|
401
402
|
};
|
|
402
403
|
var listProjectsTool = {
|
|
403
404
|
name: "list_projects",
|
|
404
|
-
description: "List all projects.
|
|
405
|
+
description: "List all projects. Summary mode includes current project tags for Agent reuse. Set verbose=true for full project data.",
|
|
405
406
|
inputSchema: z.object({
|
|
406
407
|
verbose: z.boolean().optional()
|
|
407
408
|
}),
|
|
408
409
|
async execute(input) {
|
|
409
410
|
try {
|
|
410
411
|
const projects = await storage.listProjects();
|
|
412
|
+
if (input.verbose) {
|
|
413
|
+
return {
|
|
414
|
+
success: true,
|
|
415
|
+
data: projects
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
const summaries = await Promise.all(
|
|
419
|
+
projects.map(async (projectSummary) => {
|
|
420
|
+
const projectData = await storage.readProject(projectSummary.project.id);
|
|
421
|
+
const tags = (projectData?.tags ?? []).map((tag) => ({
|
|
422
|
+
id: tag.id,
|
|
423
|
+
name: tag.name,
|
|
424
|
+
color: tag.color
|
|
425
|
+
}));
|
|
426
|
+
return toProjectSummary(projectSummary.project, projectSummary.taskCount, tags);
|
|
427
|
+
})
|
|
428
|
+
);
|
|
411
429
|
return {
|
|
412
430
|
success: true,
|
|
413
|
-
data:
|
|
431
|
+
data: summaries
|
|
414
432
|
};
|
|
415
433
|
} catch (error) {
|
|
416
434
|
return {
|
|
@@ -533,6 +551,39 @@ init_esm_shims();
|
|
|
533
551
|
function generateTagId() {
|
|
534
552
|
return `tag_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
535
553
|
}
|
|
554
|
+
var HEX_COLOR_PATTERN = /^#[0-9A-Fa-f]{6}$/;
|
|
555
|
+
var TAG_COLOR_PALETTE = [
|
|
556
|
+
"#FF6B6B",
|
|
557
|
+
"#FF9F43",
|
|
558
|
+
"#FDCB6E",
|
|
559
|
+
"#6C5CE7",
|
|
560
|
+
"#74B9FF",
|
|
561
|
+
"#00B894",
|
|
562
|
+
"#00CEC9",
|
|
563
|
+
"#E17055",
|
|
564
|
+
"#FAB1A0",
|
|
565
|
+
"#55A3FF",
|
|
566
|
+
"#A29BFE",
|
|
567
|
+
"#FD79A8"
|
|
568
|
+
];
|
|
569
|
+
function hashTagName(value) {
|
|
570
|
+
let hash = 5381;
|
|
571
|
+
for (const char of value.toLowerCase()) {
|
|
572
|
+
hash = hash * 33 ^ char.charCodeAt(0);
|
|
573
|
+
}
|
|
574
|
+
return Math.abs(hash >>> 0);
|
|
575
|
+
}
|
|
576
|
+
function resolveTagColor(tagName, inputColor) {
|
|
577
|
+
if (inputColor) {
|
|
578
|
+
return inputColor;
|
|
579
|
+
}
|
|
580
|
+
const normalizedTagName = tagName.trim();
|
|
581
|
+
if (normalizedTagName.length === 0) {
|
|
582
|
+
return TAG_COLOR_PALETTE[0];
|
|
583
|
+
}
|
|
584
|
+
const colorIndex = hashTagName(normalizedTagName) % TAG_COLOR_PALETTE.length;
|
|
585
|
+
return TAG_COLOR_PALETTE[colorIndex];
|
|
586
|
+
}
|
|
536
587
|
var TagService = class {
|
|
537
588
|
storage;
|
|
538
589
|
constructor(storage2) {
|
|
@@ -564,11 +615,19 @@ var TagService = class {
|
|
|
564
615
|
code: "DUPLICATE_ERROR"
|
|
565
616
|
};
|
|
566
617
|
}
|
|
618
|
+
const resolvedColor = resolveTagColor(data.name, data.color);
|
|
619
|
+
if (!HEX_COLOR_PATTERN.test(resolvedColor)) {
|
|
620
|
+
return {
|
|
621
|
+
success: false,
|
|
622
|
+
error: `Color must be a valid hex code (e.g., #FF5733), received '${resolvedColor}'`,
|
|
623
|
+
code: "VALIDATION_ERROR"
|
|
624
|
+
};
|
|
625
|
+
}
|
|
567
626
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
568
627
|
const tag = {
|
|
569
628
|
id: generateTagId(),
|
|
570
629
|
name: data.name,
|
|
571
|
-
color:
|
|
630
|
+
color: resolvedColor,
|
|
572
631
|
description: data.description || "",
|
|
573
632
|
createdAt: now
|
|
574
633
|
};
|
|
@@ -667,6 +726,13 @@ var TagService = class {
|
|
|
667
726
|
};
|
|
668
727
|
}
|
|
669
728
|
}
|
|
729
|
+
if (data.color && !HEX_COLOR_PATTERN.test(data.color)) {
|
|
730
|
+
return {
|
|
731
|
+
success: false,
|
|
732
|
+
error: `Color must be a valid hex code (e.g., #FF5733), received '${data.color}'`,
|
|
733
|
+
code: "VALIDATION_ERROR"
|
|
734
|
+
};
|
|
735
|
+
}
|
|
670
736
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
671
737
|
const existingTag = projectData.tags[tagIndex];
|
|
672
738
|
const updatedTag = {
|
|
@@ -818,6 +884,10 @@ function calculateCompletedAt(currentStatus, newStatus, existingCompletedAt, now
|
|
|
818
884
|
}
|
|
819
885
|
return existingCompletedAt;
|
|
820
886
|
}
|
|
887
|
+
function findInvalidTagIds(projectData, tagIds) {
|
|
888
|
+
const validTagIds = new Set(projectData.tags.map((tag) => tag.id));
|
|
889
|
+
return tagIds.filter((tagId) => !validTagIds.has(tagId));
|
|
890
|
+
}
|
|
821
891
|
var TaskService = {
|
|
822
892
|
/**
|
|
823
893
|
* Create a new task in a project
|
|
@@ -835,6 +905,15 @@ var TaskService = {
|
|
|
835
905
|
code: "NOT_FOUND"
|
|
836
906
|
};
|
|
837
907
|
}
|
|
908
|
+
const incomingTagIds = data.tags ?? [];
|
|
909
|
+
const invalidTagIds = findInvalidTagIds(projectData, incomingTagIds);
|
|
910
|
+
if (invalidTagIds.length > 0) {
|
|
911
|
+
return {
|
|
912
|
+
success: false,
|
|
913
|
+
error: `Invalid tag IDs for project '${projectId}': ${invalidTagIds.join(", ")}`,
|
|
914
|
+
code: "VALIDATION_ERROR"
|
|
915
|
+
};
|
|
916
|
+
}
|
|
838
917
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
839
918
|
const task = {
|
|
840
919
|
id: generateTaskId(),
|
|
@@ -843,7 +922,7 @@ var TaskService = {
|
|
|
843
922
|
description: data.description,
|
|
844
923
|
status: "todo",
|
|
845
924
|
priority: data.priority ?? "medium",
|
|
846
|
-
tags:
|
|
925
|
+
tags: incomingTagIds,
|
|
847
926
|
dueDate: data.dueDate ?? null,
|
|
848
927
|
assignee: data.assignee ?? null,
|
|
849
928
|
createdAt: now,
|
|
@@ -936,6 +1015,16 @@ var TaskService = {
|
|
|
936
1015
|
code: "VALIDATION_ERROR"
|
|
937
1016
|
};
|
|
938
1017
|
}
|
|
1018
|
+
if (data.tags) {
|
|
1019
|
+
const invalidTagIds = findInvalidTagIds(projectData, data.tags);
|
|
1020
|
+
if (invalidTagIds.length > 0) {
|
|
1021
|
+
return {
|
|
1022
|
+
success: false,
|
|
1023
|
+
error: `Invalid tag IDs for project '${projectId}': ${invalidTagIds.join(", ")}`,
|
|
1024
|
+
code: "VALIDATION_ERROR"
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
939
1028
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
940
1029
|
const existingTask = projectData.tasks[taskIndex];
|
|
941
1030
|
const completedAt = calculateCompletedAt(
|
|
@@ -1067,9 +1156,19 @@ var TaskService = {
|
|
|
1067
1156
|
continue;
|
|
1068
1157
|
}
|
|
1069
1158
|
const existingTask = projectData.tasks[taskIndex];
|
|
1070
|
-
let updatedTags = existingTask.tags;
|
|
1071
|
-
if (data.tags
|
|
1072
|
-
const
|
|
1159
|
+
let updatedTags = existingTask.tags ?? [];
|
|
1160
|
+
if (data.tags) {
|
|
1161
|
+
const invalidTagIds = findInvalidTagIds(projectData, data.tags);
|
|
1162
|
+
if (invalidTagIds.length > 0) {
|
|
1163
|
+
return {
|
|
1164
|
+
success: false,
|
|
1165
|
+
error: `Invalid tag IDs for project '${projectId}': ${invalidTagIds.join(", ")}`,
|
|
1166
|
+
code: "VALIDATION_ERROR"
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
if (data.tags) {
|
|
1171
|
+
const existingTags = existingTask.tags ?? [];
|
|
1073
1172
|
switch (data.tagOperation) {
|
|
1074
1173
|
case "add":
|
|
1075
1174
|
updatedTags = [.../* @__PURE__ */ new Set([...existingTags, ...data.tags])];
|
|
@@ -1147,7 +1246,7 @@ function toTaskSummary2(task) {
|
|
|
1147
1246
|
}
|
|
1148
1247
|
var createTaskTool = {
|
|
1149
1248
|
name: "create_task",
|
|
1150
|
-
description: "Create a new task in a project. Returns summary by default; set verbose=true for full data.",
|
|
1249
|
+
description: "Create a new task in a project. tags must be existing tag IDs from the same project. Returns summary by default; set verbose=true for full data.",
|
|
1151
1250
|
inputSchema: z2.object({
|
|
1152
1251
|
projectId: z2.string().min(1, "Project ID is required"),
|
|
1153
1252
|
title: z2.string().min(1, "Task title is required"),
|
|
@@ -1227,7 +1326,7 @@ var getTaskTool = {
|
|
|
1227
1326
|
};
|
|
1228
1327
|
var updateTaskTool = {
|
|
1229
1328
|
name: "update_task",
|
|
1230
|
-
description: "Update an existing task. Returns summary by default; set verbose=true for full data.",
|
|
1329
|
+
description: "Update an existing task. tags must be existing tag IDs from the same project. Returns summary by default; set verbose=true for full data.",
|
|
1231
1330
|
inputSchema: z2.object({
|
|
1232
1331
|
projectId: z2.string().min(1, "Project ID is required"),
|
|
1233
1332
|
taskId: z2.string().min(1, "Task ID is required"),
|
|
@@ -1270,7 +1369,7 @@ var deleteTaskTool = {
|
|
|
1270
1369
|
};
|
|
1271
1370
|
var batchUpdateTasksTool = {
|
|
1272
1371
|
name: "batch_update_tasks",
|
|
1273
|
-
description: "Update multiple tasks at once. Returns summaries by default; set verbose=true for full task data.",
|
|
1372
|
+
description: "Update multiple tasks at once. tags must be existing tag IDs from the same project. Returns summaries by default; set verbose=true for full task data.",
|
|
1274
1373
|
inputSchema: z2.object({
|
|
1275
1374
|
projectId: z2.string().min(1, "Project ID is required"),
|
|
1276
1375
|
taskIds: z2.array(z2.string()).min(1, "At least one task ID is required"),
|
|
@@ -1304,11 +1403,11 @@ import { z as z3 } from "zod";
|
|
|
1304
1403
|
var tagService = new TagService(storage);
|
|
1305
1404
|
var createTagTool = {
|
|
1306
1405
|
name: "create_tag",
|
|
1307
|
-
description: "Create a new tag in a project",
|
|
1406
|
+
description: "Create a new tag in a project. If color is omitted, it is generated deterministically from tag name.",
|
|
1308
1407
|
inputSchema: z3.object({
|
|
1309
1408
|
projectId: z3.string().min(1, "Project ID is required"),
|
|
1310
1409
|
name: z3.string().min(1, "Tag name is required"),
|
|
1311
|
-
color: z3.string().regex(/^#[0-9A-Fa-f]{6}$/, "Color must be a valid hex code (e.g., #FF5733)"),
|
|
1410
|
+
color: z3.string().regex(/^#[0-9A-Fa-f]{6}$/, "Color must be a valid hex code (e.g., #FF5733)").optional(),
|
|
1312
1411
|
description: z3.string().default("")
|
|
1313
1412
|
}),
|
|
1314
1413
|
async execute(input) {
|
|
@@ -2442,6 +2541,8 @@ function getQuickCapturePrompt(idea, projectId) {
|
|
|
2442
2541
|
|
|
2443
2542
|
${projectContext}
|
|
2444
2543
|
|
|
2544
|
+
## [Non-intrusive task] Pause your current work briefly to complete this quick capture, then let the user know it's done and resume what you were doing.
|
|
2545
|
+
|
|
2445
2546
|
## Please complete the following steps immediately:
|
|
2446
2547
|
|
|
2447
2548
|
### 1. Task Information Extraction
|
|
@@ -2462,8 +2563,18 @@ Suggest relevant tags:
|
|
|
2462
2563
|
- Priority tags: urgent, important
|
|
2463
2564
|
- Domain tags: frontend, backend, design, testing (if applicable)
|
|
2464
2565
|
|
|
2566
|
+
Tag consistency rules:
|
|
2567
|
+
- list_tags first, then prefer reusing existing tags in that project
|
|
2568
|
+
- If no suitable tag exists, create_tag first (color optional, system can auto-generate) and then use the returned tag ID
|
|
2569
|
+
- create_task.tags must contain tag IDs only (not tag names)
|
|
2570
|
+
|
|
2465
2571
|
### 4. Create Task
|
|
2466
2572
|
Use create_task to create the task immediately \u2014 no confirmation needed.
|
|
2573
|
+
Before create_task:
|
|
2574
|
+
1. Call list_tags(projectId)
|
|
2575
|
+
2. Match suggested tags to existing tags by name (case-insensitive)
|
|
2576
|
+
3. For missing tags, call create_tag(projectId, name, color?)
|
|
2577
|
+
4. Build create_task.tags from tag IDs only
|
|
2467
2578
|
|
|
2468
2579
|
### 5. Status
|
|
2469
2580
|
After creating, if work starts immediately, use update_task to set status to in-progress.
|
|
@@ -2478,6 +2589,7 @@ After creating, if work starts immediately, use update_task to set status to in-
|
|
|
2478
2589
|
- Type: Bug
|
|
2479
2590
|
- Priority: High (affects user experience)
|
|
2480
2591
|
- Suggested Tags: bug, frontend, mobile
|
|
2592
|
+
- create_task.tags: [existing_or_new_tag_ids]
|
|
2481
2593
|
- Recommended Project: [Recommend if there's a web project]
|
|
2482
2594
|
|
|
2483
2595
|
## Current Idea:
|
|
@@ -2499,7 +2611,9 @@ function getOpenWebUIPrompt(port) {
|
|
|
2499
2611
|
role: "user",
|
|
2500
2612
|
content: {
|
|
2501
2613
|
type: "text",
|
|
2502
|
-
text:
|
|
2614
|
+
text: `**[Non-intrusive task]** Pause your current work briefly to complete this quick action, then let the user know it's done and resume what you were doing.
|
|
2615
|
+
|
|
2616
|
+
Call the open_web_interface tool${port ? ` with port ${portNum}` : ""} to launch the roadmap-skill web visualization interface.`
|
|
2503
2617
|
}
|
|
2504
2618
|
}
|
|
2505
2619
|
]
|