roadmap-skill 0.2.4 → 0.2.6
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 +712 -417
- package/dist/index.js.map +1 -1
- package/dist/web/app/assets/main-BJPGJG4y.js +9 -0
- package/dist/web/app/index.html +1 -1
- package/dist/web/server.js +708 -61
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
- package/dist/web/app/assets/main-DE1dRfHs.js +0 -9
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
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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(
|
|
520
|
+
const projectData = await this.storage.readProject(projectId);
|
|
518
521
|
if (!projectData) {
|
|
519
522
|
return {
|
|
520
523
|
success: false,
|
|
521
|
-
error: `Project with ID '${
|
|
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
|
|
526
|
-
id:
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
description:
|
|
530
|
-
|
|
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.
|
|
546
|
+
projectData.tags.push(tag);
|
|
540
547
|
projectData.project.updatedAt = now;
|
|
541
|
-
|
|
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:
|
|
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
|
|
556
|
+
error: error instanceof Error ? error.message : "Failed to create tag",
|
|
557
|
+
code: "INTERNAL_ERROR"
|
|
552
558
|
};
|
|
553
559
|
}
|
|
554
560
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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(
|
|
792
|
+
const projectData = await storage.readProject(projectId);
|
|
603
793
|
if (!projectData) {
|
|
604
794
|
return {
|
|
605
795
|
success: false,
|
|
606
|
-
error: `Project with ID '${
|
|
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 ===
|
|
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 '${
|
|
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
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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(
|
|
877
|
+
const projectData = await storage.readProject(projectId);
|
|
645
878
|
if (!projectData) {
|
|
646
879
|
return {
|
|
647
880
|
success: false,
|
|
648
|
-
error: `Project with ID '${
|
|
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 ===
|
|
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 '${
|
|
889
|
+
error: `Task with ID '${taskId}' not found in project '${projectId}'`,
|
|
890
|
+
code: "NOT_FOUND"
|
|
656
891
|
};
|
|
657
892
|
}
|
|
658
|
-
const
|
|
659
|
-
if (
|
|
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
|
-
...
|
|
911
|
+
...data,
|
|
670
912
|
id: existingTask.id,
|
|
671
913
|
projectId: existingTask.projectId,
|
|
672
914
|
createdAt: existingTask.createdAt,
|
|
673
915
|
updatedAt: now,
|
|
674
|
-
completedAt
|
|
916
|
+
completedAt
|
|
675
917
|
};
|
|
676
918
|
projectData.tasks[taskIndex] = updatedTask;
|
|
677
919
|
projectData.project.updatedAt = now;
|
|
678
|
-
const filePath = storage.getFilePath(
|
|
679
|
-
|
|
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
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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(
|
|
942
|
+
const projectData = await storage.readProject(projectId);
|
|
703
943
|
if (!projectData) {
|
|
704
944
|
return {
|
|
705
945
|
success: false,
|
|
706
|
-
error: `Project with ID '${
|
|
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 ===
|
|
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 '${
|
|
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(
|
|
720
|
-
|
|
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:
|
|
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
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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
|
|
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 '${
|
|
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
|
|
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 (
|
|
1033
|
+
if (data.tags && data.tags.length > 0) {
|
|
766
1034
|
const existingTags = existingTask.tags || [];
|
|
767
|
-
switch (
|
|
1035
|
+
switch (data.tagOperation) {
|
|
768
1036
|
case "add":
|
|
769
|
-
updatedTags = [.../* @__PURE__ */ new Set([...existingTags, ...
|
|
1037
|
+
updatedTags = [.../* @__PURE__ */ new Set([...existingTags, ...data.tags])];
|
|
770
1038
|
break;
|
|
771
1039
|
case "remove":
|
|
772
|
-
updatedTags = existingTags.filter((tag) => !
|
|
1040
|
+
updatedTags = existingTags.filter((tag) => !data.tags.includes(tag));
|
|
773
1041
|
break;
|
|
774
1042
|
case "replace":
|
|
775
1043
|
default:
|
|
776
|
-
updatedTags =
|
|
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
|
-
...
|
|
783
|
-
...
|
|
1056
|
+
...data.status && { status: data.status },
|
|
1057
|
+
...data.priority && { priority: data.priority },
|
|
784
1058
|
tags: updatedTags,
|
|
785
1059
|
updatedAt: now,
|
|
786
|
-
|
|
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
|
-
|
|
1069
|
+
code: "NOT_FOUND"
|
|
797
1070
|
};
|
|
798
1071
|
}
|
|
799
1072
|
projectData.project.updatedAt = now;
|
|
800
|
-
const filePath = storage.getFilePath(
|
|
801
|
-
|
|
802
|
-
|
|
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
|
-
|
|
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
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
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
|
-
|
|
983
|
-
|
|
984
|
-
|
|
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
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
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,40 @@ 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
|
+
import { createRequire } from "module";
|
|
1326
|
+
import { existsSync } from "fs";
|
|
1327
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
1328
|
+
var __dirname2 = path4.dirname(__filename2);
|
|
1329
|
+
function resolveAppPath() {
|
|
1330
|
+
const candidates = [];
|
|
1331
|
+
try {
|
|
1332
|
+
const require2 = createRequire(import.meta.url);
|
|
1333
|
+
const pkgRoot = path4.dirname(require2.resolve("../../package.json"));
|
|
1334
|
+
candidates.push({ path: path4.join(pkgRoot, "dist/web/app"), source: "package.json resolve" });
|
|
1335
|
+
} catch {
|
|
1336
|
+
}
|
|
1337
|
+
candidates.push({ path: path4.join(__dirname2, "app"), source: "__dirname relative" });
|
|
1338
|
+
candidates.push({ path: path4.join(process.cwd(), "dist/web/app"), source: "process.cwd()" });
|
|
1339
|
+
candidates.push({ path: path4.join(__dirname2, "../web/app"), source: "__dirname/../web/app" });
|
|
1340
|
+
for (const { path: candidatePath, source } of candidates) {
|
|
1341
|
+
const indexPath = path4.join(candidatePath, "index.html");
|
|
1342
|
+
if (existsSync(indexPath)) {
|
|
1343
|
+
console.log(`[roadmap-skill] Static files found at: ${candidatePath} (via ${source})`);
|
|
1344
|
+
return candidatePath;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
const triedPaths = candidates.map((c) => ` - ${c.path} (${c.source})`).join("\n");
|
|
1348
|
+
throw new Error(
|
|
1349
|
+
`Cannot find web app static files.
|
|
1350
|
+
|
|
1351
|
+
Tried:
|
|
1352
|
+
${triedPaths}
|
|
1353
|
+
|
|
1354
|
+
Ensure the package is installed correctly and web assets exist.`
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
var tagService2 = new TagService(storage);
|
|
1079
1358
|
function createServer(port = 7860) {
|
|
1080
1359
|
return new Promise((resolve, reject) => {
|
|
1081
1360
|
const app = express();
|
|
@@ -1141,31 +1420,13 @@ function createServer(port = 7860) {
|
|
|
1141
1420
|
app.post("/api/tasks", async (req, res) => {
|
|
1142
1421
|
try {
|
|
1143
1422
|
const { projectId, ...taskData } = req.body;
|
|
1144
|
-
const
|
|
1145
|
-
if (!
|
|
1146
|
-
|
|
1423
|
+
const result = await TaskService.create(projectId, taskData);
|
|
1424
|
+
if (!result.success) {
|
|
1425
|
+
const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
|
|
1426
|
+
res.status(statusCode).json({ error: result.error });
|
|
1147
1427
|
return;
|
|
1148
1428
|
}
|
|
1149
|
-
|
|
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 });
|
|
1429
|
+
res.json({ success: true, data: result.data });
|
|
1169
1430
|
} catch (error) {
|
|
1170
1431
|
res.status(500).json({ error: error.message });
|
|
1171
1432
|
}
|
|
@@ -1173,33 +1434,13 @@ function createServer(port = 7860) {
|
|
|
1173
1434
|
app.put("/api/tasks", async (req, res) => {
|
|
1174
1435
|
try {
|
|
1175
1436
|
const { projectId, taskId, ...updateData } = req.body;
|
|
1176
|
-
const
|
|
1177
|
-
if (!
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
}
|
|
1181
|
-
const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
|
|
1182
|
-
if (taskIndex === -1) {
|
|
1183
|
-
res.status(404).json({ error: "Task not found" });
|
|
1437
|
+
const result = await TaskService.update(projectId, taskId, updateData);
|
|
1438
|
+
if (!result.success) {
|
|
1439
|
+
const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
|
|
1440
|
+
res.status(statusCode).json({ error: result.error });
|
|
1184
1441
|
return;
|
|
1185
1442
|
}
|
|
1186
|
-
|
|
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 });
|
|
1443
|
+
res.json({ success: true, data: result.data });
|
|
1203
1444
|
} catch (error) {
|
|
1204
1445
|
res.status(500).json({ error: error.message });
|
|
1205
1446
|
}
|
|
@@ -1207,22 +1448,69 @@ function createServer(port = 7860) {
|
|
|
1207
1448
|
app.delete("/api/tasks", async (req, res) => {
|
|
1208
1449
|
try {
|
|
1209
1450
|
const { projectId, taskId } = req.query;
|
|
1210
|
-
const
|
|
1211
|
-
if (!
|
|
1212
|
-
|
|
1451
|
+
const result = await TaskService.delete(projectId, taskId);
|
|
1452
|
+
if (!result.success) {
|
|
1453
|
+
const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
|
|
1454
|
+
res.status(statusCode).json({ error: result.error });
|
|
1213
1455
|
return;
|
|
1214
1456
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1457
|
+
res.json({ success: true });
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
res.status(500).json({ error: error.message });
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
app.post("/api/projects/:projectId/tags", async (req, res) => {
|
|
1463
|
+
try {
|
|
1464
|
+
const { projectId } = req.params;
|
|
1465
|
+
const result = await tagService2.create(projectId, req.body);
|
|
1466
|
+
if (!result.success) {
|
|
1467
|
+
const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
|
|
1468
|
+
res.status(statusCode).json({ error: result.error });
|
|
1218
1469
|
return;
|
|
1219
1470
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1471
|
+
res.json({ success: true, data: result.data });
|
|
1472
|
+
} catch (error) {
|
|
1473
|
+
res.status(500).json({ error: error.message });
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
app.get("/api/projects/:projectId/tags", async (req, res) => {
|
|
1477
|
+
try {
|
|
1478
|
+
const { projectId } = req.params;
|
|
1479
|
+
const result = await tagService2.list(projectId);
|
|
1480
|
+
if (!result.success) {
|
|
1481
|
+
const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
|
|
1482
|
+
res.status(statusCode).json({ error: result.error });
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
res.json({ success: true, data: result.data });
|
|
1486
|
+
} catch (error) {
|
|
1487
|
+
res.status(500).json({ error: error.message });
|
|
1488
|
+
}
|
|
1489
|
+
});
|
|
1490
|
+
app.put("/api/projects/:projectId/tags/:tagId", async (req, res) => {
|
|
1491
|
+
try {
|
|
1492
|
+
const { projectId, tagId } = req.params;
|
|
1493
|
+
const result = await tagService2.update(projectId, tagId, req.body);
|
|
1494
|
+
if (!result.success) {
|
|
1495
|
+
const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
|
|
1496
|
+
res.status(statusCode).json({ error: result.error });
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
res.json({ success: true, data: result.data });
|
|
1500
|
+
} catch (error) {
|
|
1501
|
+
res.status(500).json({ error: error.message });
|
|
1502
|
+
}
|
|
1503
|
+
});
|
|
1504
|
+
app.delete("/api/projects/:projectId/tags/:tagId", async (req, res) => {
|
|
1505
|
+
try {
|
|
1506
|
+
const { projectId, tagId } = req.params;
|
|
1507
|
+
const result = await tagService2.delete(projectId, tagId);
|
|
1508
|
+
if (!result.success) {
|
|
1509
|
+
const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
|
|
1510
|
+
res.status(statusCode).json({ error: result.error });
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
res.json({ success: true, data: result.data });
|
|
1226
1514
|
} catch (error) {
|
|
1227
1515
|
res.status(500).json({ error: error.message });
|
|
1228
1516
|
}
|
|
@@ -1249,7 +1537,7 @@ function createServer(port = 7860) {
|
|
|
1249
1537
|
});
|
|
1250
1538
|
}
|
|
1251
1539
|
});
|
|
1252
|
-
const distPath =
|
|
1540
|
+
const distPath = resolveAppPath();
|
|
1253
1541
|
app.use(express.static(distPath));
|
|
1254
1542
|
app.get("*", (req, res) => {
|
|
1255
1543
|
if (req.path.startsWith("/api")) {
|
|
@@ -1272,6 +1560,13 @@ function createServer(port = 7860) {
|
|
|
1272
1560
|
});
|
|
1273
1561
|
});
|
|
1274
1562
|
}
|
|
1563
|
+
if (process.argv[1]?.endsWith("server.js")) {
|
|
1564
|
+
const port = parseInt(process.argv[2] || "7860", 10);
|
|
1565
|
+
createServer(port).catch((err) => {
|
|
1566
|
+
console.error("Failed to start server:", err);
|
|
1567
|
+
process.exit(1);
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1275
1570
|
|
|
1276
1571
|
// src/tools/web-tools.ts
|
|
1277
1572
|
import open from "open";
|