roadmap-skill 0.2.4 → 0.2.5

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
@@ -495,122 +495,361 @@ var deleteProjectTool = {
495
495
  // src/tools/task-tools.ts
496
496
  init_esm_shims();
497
497
  import { z as z2 } from "zod";
498
- var TaskStatusEnum = z2.enum(["todo", "in-progress", "review", "done"]);
499
- var TaskPriorityEnum = z2.enum(["low", "medium", "high", "critical"]);
500
- function generateTaskId() {
501
- return `task_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
498
+
499
+ // src/services/index.ts
500
+ init_esm_shims();
501
+
502
+ // src/services/tag-service.ts
503
+ init_esm_shims();
504
+ function generateTagId() {
505
+ return `tag_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
502
506
  }
503
- var createTaskTool = {
504
- name: "create_task",
505
- description: "Create a new task in a project",
506
- inputSchema: z2.object({
507
- projectId: z2.string().min(1, "Project ID is required"),
508
- title: z2.string().min(1, "Task title is required"),
509
- description: z2.string(),
510
- priority: TaskPriorityEnum.default("medium"),
511
- tags: z2.array(z2.string()).default([]),
512
- dueDate: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
513
- assignee: z2.string().optional()
514
- }),
515
- async execute(input) {
507
+ var TagService = class {
508
+ storage;
509
+ constructor(storage2) {
510
+ this.storage = storage2;
511
+ }
512
+ /**
513
+ * Create a new tag in a project
514
+ * @param projectId - The project ID
515
+ * @param data - Tag creation data
516
+ * @returns The created tag or error
517
+ */
518
+ async create(projectId, data) {
516
519
  try {
517
- const projectData = await storage.readProject(input.projectId);
520
+ const projectData = await this.storage.readProject(projectId);
518
521
  if (!projectData) {
519
522
  return {
520
523
  success: false,
521
- error: `Project with ID '${input.projectId}' not found`
524
+ error: `Project with ID '${projectId}' not found`,
525
+ code: "NOT_FOUND"
526
+ };
527
+ }
528
+ const existingTag = projectData.tags.find(
529
+ (t) => t.name.toLowerCase() === data.name.toLowerCase()
530
+ );
531
+ if (existingTag) {
532
+ return {
533
+ success: false,
534
+ error: `Tag with name '${data.name}' already exists in this project`,
535
+ code: "DUPLICATE_ERROR"
522
536
  };
523
537
  }
524
538
  const now = (/* @__PURE__ */ new Date()).toISOString();
525
- const task = {
526
- id: generateTaskId(),
527
- projectId: input.projectId,
528
- title: input.title,
529
- description: input.description,
530
- status: "todo",
531
- priority: input.priority,
532
- tags: input.tags,
533
- dueDate: input.dueDate ?? null,
534
- assignee: input.assignee ?? null,
535
- createdAt: now,
536
- updatedAt: now,
537
- completedAt: null
539
+ const tag = {
540
+ id: generateTagId(),
541
+ name: data.name,
542
+ color: data.color,
543
+ description: data.description || "",
544
+ createdAt: now
538
545
  };
539
- projectData.tasks.push(task);
546
+ projectData.tags.push(tag);
540
547
  projectData.project.updatedAt = now;
541
- const filePath = storage.getFilePath(input.projectId);
542
- const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
543
- await writeJsonFile2(filePath, projectData);
548
+ await this.saveProjectData(projectId, projectData);
544
549
  return {
545
550
  success: true,
546
- data: task
551
+ data: tag
547
552
  };
548
553
  } catch (error) {
549
554
  return {
550
555
  success: false,
551
- error: error instanceof Error ? error.message : "Failed to create task"
556
+ error: error instanceof Error ? error.message : "Failed to create tag",
557
+ code: "INTERNAL_ERROR"
552
558
  };
553
559
  }
554
560
  }
555
- };
556
- var listTasksTool = {
557
- name: "list_tasks",
558
- description: "List tasks with optional filters",
559
- inputSchema: z2.object({
560
- projectId: z2.string().optional(),
561
- status: TaskStatusEnum.optional(),
562
- priority: TaskPriorityEnum.optional(),
563
- tags: z2.array(z2.string()).optional(),
564
- assignee: z2.string().optional(),
565
- dueBefore: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
566
- dueAfter: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
567
- includeCompleted: z2.boolean().optional()
568
- }),
569
- async execute(input) {
561
+ /**
562
+ * List all tags in a project
563
+ * @param projectId - The project ID
564
+ * @returns Array of tags or error
565
+ */
566
+ async list(projectId) {
570
567
  try {
571
- const results = await storage.searchTasks({
572
- projectId: input.projectId,
573
- status: input.status,
574
- priority: input.priority,
575
- tags: input.tags,
576
- assignee: input.assignee,
577
- dueBefore: input.dueBefore,
578
- dueAfter: input.dueAfter,
579
- includeCompleted: input.includeCompleted
580
- });
568
+ const projectData = await this.storage.readProject(projectId);
569
+ if (!projectData) {
570
+ return {
571
+ success: false,
572
+ error: `Project with ID '${projectId}' not found`,
573
+ code: "NOT_FOUND"
574
+ };
575
+ }
581
576
  return {
582
577
  success: true,
583
- data: results
578
+ data: projectData.tags
584
579
  };
585
580
  } catch (error) {
586
581
  return {
587
582
  success: false,
588
- error: error instanceof Error ? error.message : "Failed to list tasks"
583
+ error: error instanceof Error ? error.message : "Failed to list tags",
584
+ code: "INTERNAL_ERROR"
589
585
  };
590
586
  }
591
587
  }
588
+ /**
589
+ * Update an existing tag
590
+ * @param projectId - The project ID
591
+ * @param tagId - The tag ID
592
+ * @param data - Tag update data
593
+ * @returns The updated tag or error
594
+ */
595
+ async update(projectId, tagId, data) {
596
+ try {
597
+ const projectData = await this.storage.readProject(projectId);
598
+ if (!projectData) {
599
+ return {
600
+ success: false,
601
+ error: `Project with ID '${projectId}' not found`,
602
+ code: "NOT_FOUND"
603
+ };
604
+ }
605
+ const tagIndex = projectData.tags.findIndex((t) => t.id === tagId);
606
+ if (tagIndex === -1) {
607
+ return {
608
+ success: false,
609
+ error: `Tag with ID '${tagId}' not found in project '${projectId}'`,
610
+ code: "NOT_FOUND"
611
+ };
612
+ }
613
+ if (Object.keys(data).length === 0) {
614
+ return {
615
+ success: false,
616
+ error: "At least one field to update is required",
617
+ code: "VALIDATION_ERROR"
618
+ };
619
+ }
620
+ if (data.name) {
621
+ const existingTag2 = projectData.tags.find(
622
+ (t) => t.name.toLowerCase() === data.name.toLowerCase() && t.id !== tagId
623
+ );
624
+ if (existingTag2) {
625
+ return {
626
+ success: false,
627
+ error: `Tag with name '${data.name}' already exists in this project`,
628
+ code: "DUPLICATE_ERROR"
629
+ };
630
+ }
631
+ }
632
+ const now = (/* @__PURE__ */ new Date()).toISOString();
633
+ const existingTag = projectData.tags[tagIndex];
634
+ const updatedTag = {
635
+ ...existingTag,
636
+ ...data,
637
+ id: existingTag.id,
638
+ createdAt: existingTag.createdAt
639
+ };
640
+ projectData.tags[tagIndex] = updatedTag;
641
+ projectData.project.updatedAt = now;
642
+ await this.saveProjectData(projectId, projectData);
643
+ return {
644
+ success: true,
645
+ data: updatedTag
646
+ };
647
+ } catch (error) {
648
+ return {
649
+ success: false,
650
+ error: error instanceof Error ? error.message : "Failed to update tag",
651
+ code: "INTERNAL_ERROR"
652
+ };
653
+ }
654
+ }
655
+ /**
656
+ * Delete a tag by ID
657
+ * Also removes the tag from all tasks that use it
658
+ * @param projectId - The project ID
659
+ * @param tagId - The tag ID
660
+ * @returns Delete result with tag info and count of updated tasks
661
+ */
662
+ async delete(projectId, tagId) {
663
+ try {
664
+ const projectData = await this.storage.readProject(projectId);
665
+ if (!projectData) {
666
+ return {
667
+ success: false,
668
+ error: `Project with ID '${projectId}' not found`,
669
+ code: "NOT_FOUND"
670
+ };
671
+ }
672
+ const tagIndex = projectData.tags.findIndex((t) => t.id === tagId);
673
+ if (tagIndex === -1) {
674
+ return {
675
+ success: false,
676
+ error: `Tag with ID '${tagId}' not found in project '${projectId}'`,
677
+ code: "NOT_FOUND"
678
+ };
679
+ }
680
+ const tag = projectData.tags[tagIndex];
681
+ const now = (/* @__PURE__ */ new Date()).toISOString();
682
+ let tasksUpdated = 0;
683
+ for (const task of projectData.tasks) {
684
+ const tagIndexInTask = task.tags.indexOf(tagId);
685
+ if (tagIndexInTask !== -1) {
686
+ task.tags.splice(tagIndexInTask, 1);
687
+ task.updatedAt = now;
688
+ tasksUpdated++;
689
+ }
690
+ }
691
+ projectData.tags.splice(tagIndex, 1);
692
+ projectData.project.updatedAt = now;
693
+ await this.saveProjectData(projectId, projectData);
694
+ return {
695
+ success: true,
696
+ data: {
697
+ deleted: true,
698
+ tag,
699
+ tasksUpdated
700
+ }
701
+ };
702
+ } catch (error) {
703
+ return {
704
+ success: false,
705
+ error: error instanceof Error ? error.message : "Failed to delete tag",
706
+ code: "INTERNAL_ERROR"
707
+ };
708
+ }
709
+ }
710
+ /**
711
+ * Get all tasks that have a specific tag by tag name
712
+ * @param projectId - The project ID
713
+ * @param tagName - The tag name
714
+ * @returns Tag info and matching tasks
715
+ */
716
+ async getTasksByTag(projectId, tagName) {
717
+ try {
718
+ const projectData = await this.storage.readProject(projectId);
719
+ if (!projectData) {
720
+ return {
721
+ success: false,
722
+ error: `Project with ID '${projectId}' not found`,
723
+ code: "NOT_FOUND"
724
+ };
725
+ }
726
+ const tag = projectData.tags.find(
727
+ (t) => t.name.toLowerCase() === tagName.toLowerCase()
728
+ );
729
+ if (!tag) {
730
+ return {
731
+ success: false,
732
+ error: `Tag with name '${tagName}' not found in project '${projectId}'`,
733
+ code: "NOT_FOUND"
734
+ };
735
+ }
736
+ const tasks = projectData.tasks.filter((t) => t.tags.includes(tag.id));
737
+ return {
738
+ success: true,
739
+ data: {
740
+ tag,
741
+ tasks,
742
+ count: tasks.length
743
+ }
744
+ };
745
+ } catch (error) {
746
+ return {
747
+ success: false,
748
+ error: error instanceof Error ? error.message : "Failed to get tasks by tag",
749
+ code: "INTERNAL_ERROR"
750
+ };
751
+ }
752
+ }
753
+ /**
754
+ * Helper method to save project data
755
+ * @param projectId - The project ID
756
+ * @param projectData - The project data to save
757
+ */
758
+ async saveProjectData(projectId, projectData) {
759
+ const filePath = this.storage.getFilePath(projectId);
760
+ const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
761
+ await writeJsonFile2(filePath, projectData);
762
+ }
592
763
  };
593
- var getTaskTool = {
594
- name: "get_task",
595
- description: "Get a specific task by project ID and task ID",
596
- inputSchema: z2.object({
597
- projectId: z2.string().min(1, "Project ID is required"),
598
- taskId: z2.string().min(1, "Task ID is required")
599
- }),
600
- async execute(input) {
764
+
765
+ // src/services/task-service.ts
766
+ init_esm_shims();
767
+ init_file_helpers();
768
+ function generateTaskId() {
769
+ return `task_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
770
+ }
771
+ function calculateCompletedAt(currentStatus, newStatus, existingCompletedAt, now) {
772
+ if (!newStatus) {
773
+ return existingCompletedAt;
774
+ }
775
+ if (newStatus === "done" && currentStatus !== "done") {
776
+ return now;
777
+ }
778
+ if (currentStatus === "done" && newStatus !== "done") {
779
+ return null;
780
+ }
781
+ return existingCompletedAt;
782
+ }
783
+ var TaskService = {
784
+ /**
785
+ * Create a new task in a project
786
+ * @param projectId - The project ID
787
+ * @param data - Task creation data
788
+ * @returns The created task or error
789
+ */
790
+ async create(projectId, data) {
601
791
  try {
602
- const projectData = await storage.readProject(input.projectId);
792
+ const projectData = await storage.readProject(projectId);
603
793
  if (!projectData) {
604
794
  return {
605
795
  success: false,
606
- error: `Project with ID '${input.projectId}' not found`
796
+ error: `Project with ID '${projectId}' not found`,
797
+ code: "NOT_FOUND"
798
+ };
799
+ }
800
+ const now = (/* @__PURE__ */ new Date()).toISOString();
801
+ const task = {
802
+ id: generateTaskId(),
803
+ projectId,
804
+ title: data.title,
805
+ description: data.description,
806
+ status: "todo",
807
+ priority: data.priority ?? "medium",
808
+ tags: data.tags ?? [],
809
+ dueDate: data.dueDate ?? null,
810
+ assignee: data.assignee ?? null,
811
+ createdAt: now,
812
+ updatedAt: now,
813
+ completedAt: null
814
+ };
815
+ projectData.tasks.push(task);
816
+ projectData.project.updatedAt = now;
817
+ const filePath = storage.getFilePath(projectId);
818
+ await writeJsonFile(filePath, projectData);
819
+ return {
820
+ success: true,
821
+ data: task
822
+ };
823
+ } catch (error) {
824
+ return {
825
+ success: false,
826
+ error: error instanceof Error ? error.message : "Failed to create task",
827
+ code: "INTERNAL_ERROR"
828
+ };
829
+ }
830
+ },
831
+ /**
832
+ * Get a specific task by project ID and task ID
833
+ * @param projectId - The project ID
834
+ * @param taskId - The task ID
835
+ * @returns The task or error
836
+ */
837
+ async get(projectId, taskId) {
838
+ try {
839
+ const projectData = await storage.readProject(projectId);
840
+ if (!projectData) {
841
+ return {
842
+ success: false,
843
+ error: `Project with ID '${projectId}' not found`,
844
+ code: "NOT_FOUND"
607
845
  };
608
846
  }
609
- const task = projectData.tasks.find((t) => t.id === input.taskId);
847
+ const task = projectData.tasks.find((t) => t.id === taskId);
610
848
  if (!task) {
611
849
  return {
612
850
  success: false,
613
- error: `Task with ID '${input.taskId}' not found in project '${input.projectId}'`
851
+ error: `Task with ID '${taskId}' not found in project '${projectId}'`,
852
+ code: "NOT_FOUND"
614
853
  };
615
854
  }
616
855
  return {
@@ -620,64 +859,66 @@ var getTaskTool = {
620
859
  } catch (error) {
621
860
  return {
622
861
  success: false,
623
- error: error instanceof Error ? error.message : "Failed to get task"
862
+ error: error instanceof Error ? error.message : "Failed to get task",
863
+ code: "INTERNAL_ERROR"
624
864
  };
625
865
  }
626
- }
627
- };
628
- var updateTaskTool = {
629
- name: "update_task",
630
- description: "Update an existing task",
631
- inputSchema: z2.object({
632
- projectId: z2.string().min(1, "Project ID is required"),
633
- taskId: z2.string().min(1, "Task ID is required"),
634
- title: z2.string().min(1).optional(),
635
- description: z2.string().optional(),
636
- status: TaskStatusEnum.optional(),
637
- priority: TaskPriorityEnum.optional(),
638
- tags: z2.array(z2.string()).optional(),
639
- dueDate: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().nullable(),
640
- assignee: z2.string().optional().nullable()
641
- }),
642
- async execute(input) {
866
+ },
867
+ /**
868
+ * Update an existing task
869
+ * Handles completedAt automatically based on status changes
870
+ * @param projectId - The project ID
871
+ * @param taskId - The task ID
872
+ * @param data - Task update data
873
+ * @returns The updated task or error
874
+ */
875
+ async update(projectId, taskId, data) {
643
876
  try {
644
- const projectData = await storage.readProject(input.projectId);
877
+ const projectData = await storage.readProject(projectId);
645
878
  if (!projectData) {
646
879
  return {
647
880
  success: false,
648
- error: `Project with ID '${input.projectId}' not found`
881
+ error: `Project with ID '${projectId}' not found`,
882
+ code: "NOT_FOUND"
649
883
  };
650
884
  }
651
- const taskIndex = projectData.tasks.findIndex((t) => t.id === input.taskId);
885
+ const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
652
886
  if (taskIndex === -1) {
653
887
  return {
654
888
  success: false,
655
- error: `Task with ID '${input.taskId}' not found in project '${input.projectId}'`
889
+ error: `Task with ID '${taskId}' not found in project '${projectId}'`,
890
+ code: "NOT_FOUND"
656
891
  };
657
892
  }
658
- const { projectId, taskId, ...updateData } = input;
659
- if (Object.keys(updateData).length === 0) {
893
+ const updateKeys = Object.keys(data);
894
+ if (updateKeys.length === 0) {
660
895
  return {
661
896
  success: false,
662
- error: "At least one field to update is required"
897
+ error: "At least one field to update is required",
898
+ code: "VALIDATION_ERROR"
663
899
  };
664
900
  }
665
901
  const now = (/* @__PURE__ */ new Date()).toISOString();
666
902
  const existingTask = projectData.tasks[taskIndex];
903
+ const completedAt = calculateCompletedAt(
904
+ existingTask.status,
905
+ data.status,
906
+ existingTask.completedAt,
907
+ now
908
+ );
667
909
  const updatedTask = {
668
910
  ...existingTask,
669
- ...updateData,
911
+ ...data,
670
912
  id: existingTask.id,
671
913
  projectId: existingTask.projectId,
672
914
  createdAt: existingTask.createdAt,
673
915
  updatedAt: now,
674
- completedAt: updateData.status === "done" && existingTask.status !== "done" ? now : updateData.status && updateData.status !== "done" ? null : existingTask.completedAt
916
+ completedAt
675
917
  };
676
918
  projectData.tasks[taskIndex] = updatedTask;
677
919
  projectData.project.updatedAt = now;
678
- const filePath = storage.getFilePath(input.projectId);
679
- const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
680
- await writeJsonFile2(filePath, projectData);
920
+ const filePath = storage.getFilePath(projectId);
921
+ await writeJsonFile(filePath, projectData);
681
922
  return {
682
923
  success: true,
683
924
  data: updatedTask
@@ -685,76 +926,103 @@ var updateTaskTool = {
685
926
  } catch (error) {
686
927
  return {
687
928
  success: false,
688
- error: error instanceof Error ? error.message : "Failed to update task"
929
+ error: error instanceof Error ? error.message : "Failed to update task",
930
+ code: "INTERNAL_ERROR"
689
931
  };
690
932
  }
691
- }
692
- };
693
- var deleteTaskTool = {
694
- name: "delete_task",
695
- description: "Delete a task by project ID and task ID",
696
- inputSchema: z2.object({
697
- projectId: z2.string().min(1, "Project ID is required"),
698
- taskId: z2.string().min(1, "Task ID is required")
699
- }),
700
- async execute(input) {
933
+ },
934
+ /**
935
+ * Delete a task by project ID and task ID
936
+ * @param projectId - The project ID
937
+ * @param taskId - The task ID
938
+ * @returns Void or error
939
+ */
940
+ async delete(projectId, taskId) {
701
941
  try {
702
- const projectData = await storage.readProject(input.projectId);
942
+ const projectData = await storage.readProject(projectId);
703
943
  if (!projectData) {
704
944
  return {
705
945
  success: false,
706
- error: `Project with ID '${input.projectId}' not found`
946
+ error: `Project with ID '${projectId}' not found`,
947
+ code: "NOT_FOUND"
707
948
  };
708
949
  }
709
- const taskIndex = projectData.tasks.findIndex((t) => t.id === input.taskId);
950
+ const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
710
951
  if (taskIndex === -1) {
711
952
  return {
712
953
  success: false,
713
- error: `Task with ID '${input.taskId}' not found in project '${input.projectId}'`
954
+ error: `Task with ID '${taskId}' not found in project '${projectId}'`,
955
+ code: "NOT_FOUND"
714
956
  };
715
957
  }
716
958
  const now = (/* @__PURE__ */ new Date()).toISOString();
717
959
  projectData.tasks.splice(taskIndex, 1);
718
960
  projectData.project.updatedAt = now;
719
- const filePath = storage.getFilePath(input.projectId);
720
- const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
721
- await writeJsonFile2(filePath, projectData);
961
+ const filePath = storage.getFilePath(projectId);
962
+ await writeJsonFile(filePath, projectData);
722
963
  return {
723
964
  success: true,
724
- data: { deleted: true }
965
+ data: void 0
725
966
  };
726
967
  } catch (error) {
727
968
  return {
728
969
  success: false,
729
- error: error instanceof Error ? error.message : "Failed to delete task"
970
+ error: error instanceof Error ? error.message : "Failed to delete task",
971
+ code: "INTERNAL_ERROR"
730
972
  };
731
973
  }
732
- }
733
- };
734
- var batchUpdateTasksTool = {
735
- name: "batch_update_tasks",
736
- description: "Update multiple tasks at once",
737
- inputSchema: z2.object({
738
- projectId: z2.string().min(1, "Project ID is required"),
739
- taskIds: z2.array(z2.string()).min(1, "At least one task ID is required"),
740
- status: TaskStatusEnum.optional(),
741
- priority: TaskPriorityEnum.optional(),
742
- tags: z2.array(z2.string()).optional(),
743
- tagOperation: z2.enum(["add", "remove", "replace"]).default("replace")
744
- }),
745
- async execute(input) {
974
+ },
975
+ /**
976
+ * List tasks with optional filters
977
+ * @param filters - Optional filters for the search
978
+ * @returns Array of tasks or error
979
+ */
980
+ async list(filters) {
746
981
  try {
747
- const projectData = await storage.readProject(input.projectId);
982
+ const results = await storage.searchTasks({
983
+ projectId: filters?.projectId,
984
+ status: filters?.status,
985
+ priority: filters?.priority,
986
+ tags: filters?.tags,
987
+ assignee: filters?.assignee,
988
+ dueBefore: filters?.dueBefore,
989
+ dueAfter: filters?.dueAfter,
990
+ includeCompleted: filters?.includeCompleted
991
+ });
992
+ const tasks = results.map((r) => r.task);
993
+ return {
994
+ success: true,
995
+ data: tasks
996
+ };
997
+ } catch (error) {
998
+ return {
999
+ success: false,
1000
+ error: error instanceof Error ? error.message : "Failed to list tasks",
1001
+ code: "INTERNAL_ERROR"
1002
+ };
1003
+ }
1004
+ },
1005
+ /**
1006
+ * Update multiple tasks at once
1007
+ * @param projectId - The project ID
1008
+ * @param taskIds - Array of task IDs to update
1009
+ * @param data - Batch update data
1010
+ * @returns Batch update result or error
1011
+ */
1012
+ async batchUpdate(projectId, taskIds, data) {
1013
+ try {
1014
+ const projectData = await storage.readProject(projectId);
748
1015
  if (!projectData) {
749
1016
  return {
750
1017
  success: false,
751
- error: `Project with ID '${input.projectId}' not found`
1018
+ error: `Project with ID '${projectId}' not found`,
1019
+ code: "NOT_FOUND"
752
1020
  };
753
1021
  }
754
1022
  const now = (/* @__PURE__ */ new Date()).toISOString();
755
1023
  const updatedTasks = [];
756
1024
  const notFoundIds = [];
757
- for (const taskId of input.taskIds) {
1025
+ for (const taskId of taskIds) {
758
1026
  const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
759
1027
  if (taskIndex === -1) {
760
1028
  notFoundIds.push(taskId);
@@ -762,29 +1030,34 @@ var batchUpdateTasksTool = {
762
1030
  }
763
1031
  const existingTask = projectData.tasks[taskIndex];
764
1032
  let updatedTags = existingTask.tags;
765
- if (input.tags && input.tags.length > 0) {
1033
+ if (data.tags && data.tags.length > 0) {
766
1034
  const existingTags = existingTask.tags || [];
767
- switch (input.tagOperation) {
1035
+ switch (data.tagOperation) {
768
1036
  case "add":
769
- updatedTags = [.../* @__PURE__ */ new Set([...existingTags, ...input.tags])];
1037
+ updatedTags = [.../* @__PURE__ */ new Set([...existingTags, ...data.tags])];
770
1038
  break;
771
1039
  case "remove":
772
- updatedTags = existingTags.filter((tag) => !input.tags.includes(tag));
1040
+ updatedTags = existingTags.filter((tag) => !data.tags.includes(tag));
773
1041
  break;
774
1042
  case "replace":
775
1043
  default:
776
- updatedTags = input.tags;
1044
+ updatedTags = data.tags;
777
1045
  break;
778
1046
  }
779
1047
  }
1048
+ const completedAt = calculateCompletedAt(
1049
+ existingTask.status,
1050
+ data.status,
1051
+ existingTask.completedAt,
1052
+ now
1053
+ );
780
1054
  const updatedTask = {
781
1055
  ...existingTask,
782
- ...input.status && { status: input.status },
783
- ...input.priority && { priority: input.priority },
1056
+ ...data.status && { status: data.status },
1057
+ ...data.priority && { priority: data.priority },
784
1058
  tags: updatedTags,
785
1059
  updatedAt: now,
786
- ...input.status === "done" && existingTask.status !== "done" && { completedAt: now },
787
- ...input.status && input.status !== "done" && { completedAt: null }
1060
+ completedAt
788
1061
  };
789
1062
  projectData.tasks[taskIndex] = updatedTask;
790
1063
  updatedTasks.push(updatedTask);
@@ -793,36 +1066,172 @@ var batchUpdateTasksTool = {
793
1066
  return {
794
1067
  success: false,
795
1068
  error: "No tasks were found to update",
796
- notFoundIds
1069
+ code: "NOT_FOUND"
797
1070
  };
798
1071
  }
799
1072
  projectData.project.updatedAt = now;
800
- const filePath = storage.getFilePath(input.projectId);
801
- const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
802
- await writeJsonFile2(filePath, projectData);
1073
+ const filePath = storage.getFilePath(projectId);
1074
+ await writeJsonFile(filePath, projectData);
1075
+ return {
1076
+ success: true,
1077
+ data: {
1078
+ updatedTasks,
1079
+ updatedCount: updatedTasks.length,
1080
+ notFoundIds: notFoundIds.length > 0 ? notFoundIds : void 0
1081
+ }
1082
+ };
1083
+ } catch (error) {
1084
+ return {
1085
+ success: false,
1086
+ error: error instanceof Error ? error.message : "Failed to batch update tasks",
1087
+ code: "INTERNAL_ERROR"
1088
+ };
1089
+ }
1090
+ }
1091
+ };
1092
+
1093
+ // src/services/types.ts
1094
+ init_esm_shims();
1095
+
1096
+ // src/tools/task-tools.ts
1097
+ var TaskStatusEnum = z2.enum(["todo", "in-progress", "review", "done"]);
1098
+ var TaskPriorityEnum = z2.enum(["low", "medium", "high", "critical"]);
1099
+ var createTaskTool = {
1100
+ name: "create_task",
1101
+ description: "Create a new task in a project",
1102
+ inputSchema: z2.object({
1103
+ projectId: z2.string().min(1, "Project ID is required"),
1104
+ title: z2.string().min(1, "Task title is required"),
1105
+ description: z2.string(),
1106
+ priority: TaskPriorityEnum.default("medium"),
1107
+ tags: z2.array(z2.string()).default([]),
1108
+ dueDate: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
1109
+ assignee: z2.string().optional()
1110
+ }),
1111
+ async execute(input) {
1112
+ const result = await TaskService.create(input.projectId, {
1113
+ title: input.title,
1114
+ description: input.description,
1115
+ priority: input.priority,
1116
+ tags: input.tags,
1117
+ dueDate: input.dueDate,
1118
+ assignee: input.assignee
1119
+ });
1120
+ return result;
1121
+ }
1122
+ };
1123
+ var listTasksTool = {
1124
+ name: "list_tasks",
1125
+ description: "List tasks with optional filters",
1126
+ inputSchema: z2.object({
1127
+ projectId: z2.string().optional(),
1128
+ status: TaskStatusEnum.optional(),
1129
+ priority: TaskPriorityEnum.optional(),
1130
+ tags: z2.array(z2.string()).optional(),
1131
+ assignee: z2.string().optional(),
1132
+ dueBefore: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
1133
+ dueAfter: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
1134
+ includeCompleted: z2.boolean().optional()
1135
+ }),
1136
+ async execute(input) {
1137
+ try {
1138
+ const results = await storage.searchTasks({
1139
+ projectId: input.projectId,
1140
+ status: input.status,
1141
+ priority: input.priority,
1142
+ tags: input.tags,
1143
+ assignee: input.assignee,
1144
+ dueBefore: input.dueBefore,
1145
+ dueAfter: input.dueAfter,
1146
+ includeCompleted: input.includeCompleted
1147
+ });
1148
+ return {
1149
+ success: true,
1150
+ data: results
1151
+ };
1152
+ } catch (error) {
1153
+ return {
1154
+ success: false,
1155
+ error: error instanceof Error ? error.message : "Failed to list tasks"
1156
+ };
1157
+ }
1158
+ }
1159
+ };
1160
+ var getTaskTool = {
1161
+ name: "get_task",
1162
+ description: "Get a specific task by project ID and task ID",
1163
+ inputSchema: z2.object({
1164
+ projectId: z2.string().min(1, "Project ID is required"),
1165
+ taskId: z2.string().min(1, "Task ID is required")
1166
+ }),
1167
+ async execute(input) {
1168
+ const result = await TaskService.get(input.projectId, input.taskId);
1169
+ return result;
1170
+ }
1171
+ };
1172
+ var updateTaskTool = {
1173
+ name: "update_task",
1174
+ description: "Update an existing task",
1175
+ inputSchema: z2.object({
1176
+ projectId: z2.string().min(1, "Project ID is required"),
1177
+ taskId: z2.string().min(1, "Task ID is required"),
1178
+ title: z2.string().min(1).optional(),
1179
+ description: z2.string().optional(),
1180
+ status: TaskStatusEnum.optional(),
1181
+ priority: TaskPriorityEnum.optional(),
1182
+ tags: z2.array(z2.string()).optional(),
1183
+ dueDate: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().nullable(),
1184
+ assignee: z2.string().optional().nullable()
1185
+ }),
1186
+ async execute(input) {
1187
+ const { projectId, taskId, ...updateData } = input;
1188
+ const result = await TaskService.update(projectId, taskId, updateData);
1189
+ return result;
1190
+ }
1191
+ };
1192
+ var deleteTaskTool = {
1193
+ name: "delete_task",
1194
+ description: "Delete a task by project ID and task ID",
1195
+ inputSchema: z2.object({
1196
+ projectId: z2.string().min(1, "Project ID is required"),
1197
+ taskId: z2.string().min(1, "Task ID is required")
1198
+ }),
1199
+ async execute(input) {
1200
+ const result = await TaskService.delete(input.projectId, input.taskId);
1201
+ if (result.success) {
803
1202
  return {
804
1203
  success: true,
805
- data: {
806
- updatedTasks,
807
- updatedCount: updatedTasks.length,
808
- notFoundIds: notFoundIds.length > 0 ? notFoundIds : void 0
809
- }
810
- };
811
- } catch (error) {
812
- return {
813
- success: false,
814
- error: error instanceof Error ? error.message : "Failed to batch update tasks"
1204
+ data: { deleted: true }
815
1205
  };
816
1206
  }
1207
+ return result;
1208
+ }
1209
+ };
1210
+ var batchUpdateTasksTool = {
1211
+ name: "batch_update_tasks",
1212
+ description: "Update multiple tasks at once",
1213
+ inputSchema: z2.object({
1214
+ projectId: z2.string().min(1, "Project ID is required"),
1215
+ taskIds: z2.array(z2.string()).min(1, "At least one task ID is required"),
1216
+ status: TaskStatusEnum.optional(),
1217
+ priority: TaskPriorityEnum.optional(),
1218
+ tags: z2.array(z2.string()).optional(),
1219
+ tagOperation: z2.enum(["add", "remove", "replace"]).default("replace")
1220
+ }),
1221
+ async execute(input) {
1222
+ const { projectId, taskIds, tagOperation, ...restData } = input;
1223
+ const result = await TaskService.batchUpdate(projectId, taskIds, {
1224
+ ...restData,
1225
+ tagOperation
1226
+ });
1227
+ return result;
817
1228
  }
818
1229
  };
819
1230
 
820
1231
  // src/tools/tag-tools.ts
821
1232
  init_esm_shims();
822
1233
  import { z as z3 } from "zod";
823
- function generateTagId() {
824
- return `tag_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
825
- }
1234
+ var tagService = new TagService(storage);
826
1235
  var createTagTool = {
827
1236
  name: "create_tag",
828
1237
  description: "Create a new tag in a project",
@@ -833,46 +1242,12 @@ var createTagTool = {
833
1242
  description: z3.string().default("")
834
1243
  }),
835
1244
  async execute(input) {
836
- try {
837
- const projectData = await storage.readProject(input.projectId);
838
- if (!projectData) {
839
- return {
840
- success: false,
841
- error: `Project with ID '${input.projectId}' not found`
842
- };
843
- }
844
- const existingTag = projectData.tags.find(
845
- (t) => t.name.toLowerCase() === input.name.toLowerCase()
846
- );
847
- if (existingTag) {
848
- return {
849
- success: false,
850
- error: `Tag with name '${input.name}' already exists in this project`
851
- };
852
- }
853
- const now = (/* @__PURE__ */ new Date()).toISOString();
854
- const tag = {
855
- id: generateTagId(),
856
- name: input.name,
857
- color: input.color,
858
- description: input.description,
859
- createdAt: now
860
- };
861
- projectData.tags.push(tag);
862
- projectData.project.updatedAt = now;
863
- const filePath = storage.getFilePath(input.projectId);
864
- const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
865
- await writeJsonFile2(filePath, projectData);
866
- return {
867
- success: true,
868
- data: tag
869
- };
870
- } catch (error) {
871
- return {
872
- success: false,
873
- error: error instanceof Error ? error.message : "Failed to create tag"
874
- };
1245
+ const { projectId, ...data } = input;
1246
+ const result = await tagService.create(projectId, data);
1247
+ if (!result.success) {
1248
+ return { success: false, error: result.error };
875
1249
  }
1250
+ return { success: true, data: result.data };
876
1251
  }
877
1252
  };
878
1253
  var listTagsTool = {
@@ -882,24 +1257,11 @@ var listTagsTool = {
882
1257
  projectId: z3.string().min(1, "Project ID is required")
883
1258
  }),
884
1259
  async execute(input) {
885
- try {
886
- const projectData = await storage.readProject(input.projectId);
887
- if (!projectData) {
888
- return {
889
- success: false,
890
- error: `Project with ID '${input.projectId}' not found`
891
- };
892
- }
893
- return {
894
- success: true,
895
- data: projectData.tags
896
- };
897
- } catch (error) {
898
- return {
899
- success: false,
900
- error: error instanceof Error ? error.message : "Failed to list tags"
901
- };
1260
+ const result = await tagService.list(input.projectId);
1261
+ if (!result.success) {
1262
+ return { success: false, error: result.error };
902
1263
  }
1264
+ return { success: true, data: result.data };
903
1265
  }
904
1266
  };
905
1267
  var updateTagTool = {
@@ -913,62 +1275,12 @@ var updateTagTool = {
913
1275
  description: z3.string().optional()
914
1276
  }),
915
1277
  async execute(input) {
916
- try {
917
- const projectData = await storage.readProject(input.projectId);
918
- if (!projectData) {
919
- return {
920
- success: false,
921
- error: `Project with ID '${input.projectId}' not found`
922
- };
923
- }
924
- const tagIndex = projectData.tags.findIndex((t) => t.id === input.tagId);
925
- if (tagIndex === -1) {
926
- return {
927
- success: false,
928
- error: `Tag with ID '${input.tagId}' not found in project '${input.projectId}'`
929
- };
930
- }
931
- const { projectId, tagId, ...updateData } = input;
932
- if (Object.keys(updateData).length === 0) {
933
- return {
934
- success: false,
935
- error: "At least one field to update is required"
936
- };
937
- }
938
- if (updateData.name) {
939
- const existingTag2 = projectData.tags.find(
940
- (t) => t.name.toLowerCase() === updateData.name.toLowerCase() && t.id !== input.tagId
941
- );
942
- if (existingTag2) {
943
- return {
944
- success: false,
945
- error: `Tag with name '${updateData.name}' already exists in this project`
946
- };
947
- }
948
- }
949
- const now = (/* @__PURE__ */ new Date()).toISOString();
950
- const existingTag = projectData.tags[tagIndex];
951
- const updatedTag = {
952
- ...existingTag,
953
- ...updateData,
954
- id: existingTag.id,
955
- createdAt: existingTag.createdAt
956
- };
957
- projectData.tags[tagIndex] = updatedTag;
958
- projectData.project.updatedAt = now;
959
- const filePath = storage.getFilePath(input.projectId);
960
- const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
961
- await writeJsonFile2(filePath, projectData);
962
- return {
963
- success: true,
964
- data: updatedTag
965
- };
966
- } catch (error) {
967
- return {
968
- success: false,
969
- error: error instanceof Error ? error.message : "Failed to update tag"
970
- };
1278
+ const { projectId, tagId, ...data } = input;
1279
+ const result = await tagService.update(projectId, tagId, data);
1280
+ if (!result.success) {
1281
+ return { success: false, error: result.error };
971
1282
  }
1283
+ return { success: true, data: result.data };
972
1284
  }
973
1285
  };
974
1286
  var deleteTagTool = {
@@ -979,51 +1291,11 @@ var deleteTagTool = {
979
1291
  tagId: z3.string().min(1, "Tag ID is required")
980
1292
  }),
981
1293
  async execute(input) {
982
- try {
983
- const projectData = await storage.readProject(input.projectId);
984
- if (!projectData) {
985
- return {
986
- success: false,
987
- error: `Project with ID '${input.projectId}' not found`
988
- };
989
- }
990
- const tagIndex = projectData.tags.findIndex((t) => t.id === input.tagId);
991
- if (tagIndex === -1) {
992
- return {
993
- success: false,
994
- error: `Tag with ID '${input.tagId}' not found in project '${input.projectId}'`
995
- };
996
- }
997
- const tag = projectData.tags[tagIndex];
998
- const now = (/* @__PURE__ */ new Date()).toISOString();
999
- let tasksUpdated = 0;
1000
- for (const task of projectData.tasks) {
1001
- const tagIndexInTask = task.tags.indexOf(input.tagId);
1002
- if (tagIndexInTask !== -1) {
1003
- task.tags.splice(tagIndexInTask, 1);
1004
- task.updatedAt = now;
1005
- tasksUpdated++;
1006
- }
1007
- }
1008
- projectData.tags.splice(tagIndex, 1);
1009
- projectData.project.updatedAt = now;
1010
- const filePath = storage.getFilePath(input.projectId);
1011
- const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
1012
- await writeJsonFile2(filePath, projectData);
1013
- return {
1014
- success: true,
1015
- data: {
1016
- deleted: true,
1017
- tag,
1018
- tasksUpdated
1019
- }
1020
- };
1021
- } catch (error) {
1022
- return {
1023
- success: false,
1024
- error: error instanceof Error ? error.message : "Failed to delete tag"
1025
- };
1294
+ const result = await tagService.delete(input.projectId, input.tagId);
1295
+ if (!result.success) {
1296
+ return { success: false, error: result.error };
1026
1297
  }
1298
+ return { success: true, data: result.data };
1027
1299
  }
1028
1300
  };
1029
1301
  var getTasksByTagTool = {
@@ -1034,38 +1306,11 @@ var getTasksByTagTool = {
1034
1306
  tagName: z3.string().min(1, "Tag name is required")
1035
1307
  }),
1036
1308
  async execute(input) {
1037
- try {
1038
- const projectData = await storage.readProject(input.projectId);
1039
- if (!projectData) {
1040
- return {
1041
- success: false,
1042
- error: `Project with ID '${input.projectId}' not found`
1043
- };
1044
- }
1045
- const tag = projectData.tags.find(
1046
- (t) => t.name.toLowerCase() === input.tagName.toLowerCase()
1047
- );
1048
- if (!tag) {
1049
- return {
1050
- success: false,
1051
- error: `Tag with name '${input.tagName}' not found in project '${input.projectId}'`
1052
- };
1053
- }
1054
- const tasks = projectData.tasks.filter((t) => t.tags.includes(tag.id));
1055
- return {
1056
- success: true,
1057
- data: {
1058
- tag,
1059
- tasks,
1060
- count: tasks.length
1061
- }
1062
- };
1063
- } catch (error) {
1064
- return {
1065
- success: false,
1066
- error: error instanceof Error ? error.message : "Failed to get tasks by tag"
1067
- };
1309
+ const result = await tagService.getTasksByTag(input.projectId, input.tagName);
1310
+ if (!result.success) {
1311
+ return { success: false, error: result.error };
1068
1312
  }
1313
+ return { success: true, data: result.data };
1069
1314
  }
1070
1315
  };
1071
1316
 
@@ -1076,6 +1321,10 @@ init_esm_shims();
1076
1321
  init_esm_shims();
1077
1322
  import express from "express";
1078
1323
  import * as path4 from "path";
1324
+ import { fileURLToPath as fileURLToPath2 } from "url";
1325
+ var __filename2 = fileURLToPath2(import.meta.url);
1326
+ var __dirname2 = path4.dirname(__filename2);
1327
+ var tagService2 = new TagService(storage);
1079
1328
  function createServer(port = 7860) {
1080
1329
  return new Promise((resolve, reject) => {
1081
1330
  const app = express();
@@ -1141,31 +1390,13 @@ function createServer(port = 7860) {
1141
1390
  app.post("/api/tasks", async (req, res) => {
1142
1391
  try {
1143
1392
  const { projectId, ...taskData } = req.body;
1144
- const projectData = await storage.readProject(projectId);
1145
- if (!projectData) {
1146
- res.status(404).json({ error: "Project not found" });
1393
+ const result = await TaskService.create(projectId, taskData);
1394
+ if (!result.success) {
1395
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
1396
+ res.status(statusCode).json({ error: result.error });
1147
1397
  return;
1148
1398
  }
1149
- const now = (/* @__PURE__ */ new Date()).toISOString();
1150
- const task = {
1151
- id: `task_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
1152
- projectId,
1153
- ...taskData,
1154
- status: taskData.status || "todo",
1155
- priority: taskData.priority || "medium",
1156
- tags: taskData.tags || [],
1157
- dueDate: taskData.dueDate || null,
1158
- assignee: taskData.assignee || null,
1159
- createdAt: now,
1160
- updatedAt: now,
1161
- completedAt: null
1162
- };
1163
- projectData.tasks.push(task);
1164
- projectData.project.updatedAt = now;
1165
- const filePath = storage.getFilePath(projectId);
1166
- const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
1167
- await writeJsonFile2(filePath, projectData);
1168
- res.json({ success: true, data: task });
1399
+ res.json({ success: true, data: result.data });
1169
1400
  } catch (error) {
1170
1401
  res.status(500).json({ error: error.message });
1171
1402
  }
@@ -1173,33 +1404,13 @@ function createServer(port = 7860) {
1173
1404
  app.put("/api/tasks", async (req, res) => {
1174
1405
  try {
1175
1406
  const { projectId, taskId, ...updateData } = req.body;
1176
- const projectData = await storage.readProject(projectId);
1177
- if (!projectData) {
1178
- res.status(404).json({ error: "Project not found" });
1179
- return;
1180
- }
1181
- const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
1182
- if (taskIndex === -1) {
1183
- res.status(404).json({ error: "Task not found" });
1407
+ const result = await TaskService.update(projectId, taskId, updateData);
1408
+ if (!result.success) {
1409
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
1410
+ res.status(statusCode).json({ error: result.error });
1184
1411
  return;
1185
1412
  }
1186
- const now = (/* @__PURE__ */ new Date()).toISOString();
1187
- const existingTask = projectData.tasks[taskIndex];
1188
- const updatedTask = {
1189
- ...existingTask,
1190
- ...updateData,
1191
- id: existingTask.id,
1192
- projectId: existingTask.projectId,
1193
- createdAt: existingTask.createdAt,
1194
- updatedAt: now,
1195
- completedAt: updateData.status === "done" && existingTask.status !== "done" ? now : updateData.status && updateData.status !== "done" ? null : existingTask.completedAt
1196
- };
1197
- projectData.tasks[taskIndex] = updatedTask;
1198
- projectData.project.updatedAt = now;
1199
- const filePath = storage.getFilePath(projectId);
1200
- const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
1201
- await writeJsonFile2(filePath, projectData);
1202
- res.json({ success: true, data: updatedTask });
1413
+ res.json({ success: true, data: result.data });
1203
1414
  } catch (error) {
1204
1415
  res.status(500).json({ error: error.message });
1205
1416
  }
@@ -1207,22 +1418,69 @@ function createServer(port = 7860) {
1207
1418
  app.delete("/api/tasks", async (req, res) => {
1208
1419
  try {
1209
1420
  const { projectId, taskId } = req.query;
1210
- const projectData = await storage.readProject(projectId);
1211
- if (!projectData) {
1212
- res.status(404).json({ error: "Project not found" });
1421
+ const result = await TaskService.delete(projectId, taskId);
1422
+ if (!result.success) {
1423
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
1424
+ res.status(statusCode).json({ error: result.error });
1213
1425
  return;
1214
1426
  }
1215
- const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
1216
- if (taskIndex === -1) {
1217
- res.status(404).json({ error: "Task not found" });
1427
+ res.json({ success: true });
1428
+ } catch (error) {
1429
+ res.status(500).json({ error: error.message });
1430
+ }
1431
+ });
1432
+ app.post("/api/projects/:projectId/tags", async (req, res) => {
1433
+ try {
1434
+ const { projectId } = req.params;
1435
+ const result = await tagService2.create(projectId, req.body);
1436
+ if (!result.success) {
1437
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
1438
+ res.status(statusCode).json({ error: result.error });
1218
1439
  return;
1219
1440
  }
1220
- projectData.tasks.splice(taskIndex, 1);
1221
- projectData.project.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1222
- const filePath = storage.getFilePath(projectId);
1223
- const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
1224
- await writeJsonFile2(filePath, projectData);
1225
- res.json({ success: true });
1441
+ res.json({ success: true, data: result.data });
1442
+ } catch (error) {
1443
+ res.status(500).json({ error: error.message });
1444
+ }
1445
+ });
1446
+ app.get("/api/projects/:projectId/tags", async (req, res) => {
1447
+ try {
1448
+ const { projectId } = req.params;
1449
+ const result = await tagService2.list(projectId);
1450
+ if (!result.success) {
1451
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
1452
+ res.status(statusCode).json({ error: result.error });
1453
+ return;
1454
+ }
1455
+ res.json({ success: true, data: result.data });
1456
+ } catch (error) {
1457
+ res.status(500).json({ error: error.message });
1458
+ }
1459
+ });
1460
+ app.put("/api/projects/:projectId/tags/:tagId", async (req, res) => {
1461
+ try {
1462
+ const { projectId, tagId } = req.params;
1463
+ const result = await tagService2.update(projectId, tagId, req.body);
1464
+ if (!result.success) {
1465
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
1466
+ res.status(statusCode).json({ error: result.error });
1467
+ return;
1468
+ }
1469
+ res.json({ success: true, data: result.data });
1470
+ } catch (error) {
1471
+ res.status(500).json({ error: error.message });
1472
+ }
1473
+ });
1474
+ app.delete("/api/projects/:projectId/tags/:tagId", async (req, res) => {
1475
+ try {
1476
+ const { projectId, tagId } = req.params;
1477
+ const result = await tagService2.delete(projectId, tagId);
1478
+ if (!result.success) {
1479
+ const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
1480
+ res.status(statusCode).json({ error: result.error });
1481
+ return;
1482
+ }
1483
+ res.json({ success: true, data: result.data });
1226
1484
  } catch (error) {
1227
1485
  res.status(500).json({ error: error.message });
1228
1486
  }
@@ -1249,7 +1507,7 @@ function createServer(port = 7860) {
1249
1507
  });
1250
1508
  }
1251
1509
  });
1252
- const distPath = path4.join(process.cwd(), "dist", "web", "app");
1510
+ const distPath = path4.join(__dirname2, "app");
1253
1511
  app.use(express.static(distPath));
1254
1512
  app.get("*", (req, res) => {
1255
1513
  if (req.path.startsWith("/api")) {
@@ -1272,6 +1530,13 @@ function createServer(port = 7860) {
1272
1530
  });
1273
1531
  });
1274
1532
  }
1533
+ if (process.argv[1]?.endsWith("server.js")) {
1534
+ const port = parseInt(process.argv[2] || "7860", 10);
1535
+ createServer(port).catch((err) => {
1536
+ console.error("Failed to start server:", err);
1537
+ process.exit(1);
1538
+ });
1539
+ }
1275
1540
 
1276
1541
  // src/tools/web-tools.ts
1277
1542
  import open from "open";