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/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. Returns summaries by default; set verbose=true for full project data.",
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: input.verbose ? projects : projects.map((p) => toProjectSummary(p.project, p.taskCount))
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: data.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: data.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 && data.tags.length > 0) {
1072
- const existingTags = existingTask.tags || [];
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: `Please open the roadmap-skill web interface by calling the open_web_interface tool with port ${portNum}. Do it immediately without asking for confirmation.`
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
  ]