@vibetasks/mcp-server 0.2.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +655 -6
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -18,14 +18,19 @@ function setupTools(taskOps) {
|
|
|
18
18
|
// create_task
|
|
19
19
|
{
|
|
20
20
|
name: "create_task",
|
|
21
|
-
description: "Create a new task in
|
|
21
|
+
description: "Create a new task in VibeTasks. Use this instead of TodoWrite - tasks persist across sessions and sync everywhere.",
|
|
22
22
|
inputSchema: z.object({
|
|
23
23
|
title: z.string().describe("Task title (required)"),
|
|
24
|
-
|
|
24
|
+
description: z.string().optional().describe("Brief description of what this task involves"),
|
|
25
|
+
notes: z.string().optional().describe("Detailed notes in markdown"),
|
|
26
|
+
subtasks: z.array(z.object({
|
|
27
|
+
title: z.string().describe("Subtask title"),
|
|
28
|
+
done: z.boolean().default(false).describe("Is this subtask completed?"),
|
|
29
|
+
notes: z.string().optional().describe("Progress notes for this subtask")
|
|
30
|
+
})).optional().describe("Inline subtasks - each with title, done status, and optional notes"),
|
|
25
31
|
due_date: z.string().optional().describe("Due date (ISO 8601 format)"),
|
|
26
32
|
priority: z.enum(["none", "low", "medium", "high"]).default("none").describe("Task priority"),
|
|
27
|
-
tags: z.array(z.string()).optional().describe("Tag names to attach")
|
|
28
|
-
parent_task_id: z.string().optional().describe("Parent task ID to create this as a subtask")
|
|
33
|
+
tags: z.array(z.string()).optional().describe("Tag names to attach")
|
|
29
34
|
}),
|
|
30
35
|
handler: async (args, taskOps2) => {
|
|
31
36
|
let tagIds = [];
|
|
@@ -35,13 +40,20 @@ function setupTools(taskOps) {
|
|
|
35
40
|
tagIds.push(tag.id);
|
|
36
41
|
}
|
|
37
42
|
}
|
|
43
|
+
const subtasksJson = args.subtasks?.map((st) => ({
|
|
44
|
+
id: crypto.randomUUID(),
|
|
45
|
+
title: st.title,
|
|
46
|
+
done: st.done || false,
|
|
47
|
+
notes: st.notes
|
|
48
|
+
})) || [];
|
|
38
49
|
const task = await taskOps2.createTask({
|
|
39
50
|
title: args.title,
|
|
51
|
+
description: args.description,
|
|
40
52
|
notes: args.notes,
|
|
41
53
|
notes_format: "markdown",
|
|
42
54
|
due_date: args.due_date,
|
|
43
55
|
priority: args.priority,
|
|
44
|
-
|
|
56
|
+
subtasks_json: subtasksJson
|
|
45
57
|
});
|
|
46
58
|
if (tagIds.length > 0) {
|
|
47
59
|
await taskOps2.linkTaskTags(task.id, tagIds);
|
|
@@ -56,9 +68,10 @@ function setupTools(taskOps) {
|
|
|
56
68
|
task: {
|
|
57
69
|
id: task.id,
|
|
58
70
|
title: task.title,
|
|
71
|
+
description: task.description,
|
|
59
72
|
priority: task.priority,
|
|
60
73
|
due_date: task.due_date,
|
|
61
|
-
|
|
74
|
+
subtasks: subtasksJson,
|
|
62
75
|
created_at: task.created_at
|
|
63
76
|
}
|
|
64
77
|
},
|
|
@@ -515,8 +528,644 @@ Generated by TaskFlow MCP Server`;
|
|
|
515
528
|
]
|
|
516
529
|
};
|
|
517
530
|
}
|
|
531
|
+
},
|
|
532
|
+
// archive_task
|
|
533
|
+
{
|
|
534
|
+
name: "archive_task",
|
|
535
|
+
description: "Archive a completed task. Moves the task to archived status, hiding it from default views. Use this to clean up completed tasks without deleting them.",
|
|
536
|
+
inputSchema: z.object({
|
|
537
|
+
task_id: z.string().describe("Task ID to archive")
|
|
538
|
+
}),
|
|
539
|
+
handler: async (args, taskOps2) => {
|
|
540
|
+
const task = await taskOps2.archiveTask(args.task_id);
|
|
541
|
+
return {
|
|
542
|
+
content: [
|
|
543
|
+
{
|
|
544
|
+
type: "text",
|
|
545
|
+
text: JSON.stringify(
|
|
546
|
+
{
|
|
547
|
+
success: true,
|
|
548
|
+
task: {
|
|
549
|
+
id: task.id,
|
|
550
|
+
title: task.title,
|
|
551
|
+
status: "archived",
|
|
552
|
+
archived_at: task.archived_at
|
|
553
|
+
},
|
|
554
|
+
message: "Task archived successfully. Use get_archived_tasks to view archived tasks."
|
|
555
|
+
},
|
|
556
|
+
null,
|
|
557
|
+
2
|
|
558
|
+
)
|
|
559
|
+
}
|
|
560
|
+
]
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
// get_archived_tasks
|
|
565
|
+
{
|
|
566
|
+
name: "get_archived_tasks",
|
|
567
|
+
description: "Get all archived tasks. Returns tasks that have been archived, sorted by archive date (most recent first).",
|
|
568
|
+
inputSchema: z.object({
|
|
569
|
+
limit: z.number().default(50).describe("Maximum number of archived tasks to return")
|
|
570
|
+
}),
|
|
571
|
+
handler: async (args, taskOps2) => {
|
|
572
|
+
const tasks = await taskOps2.getArchivedTasks(args.limit);
|
|
573
|
+
return {
|
|
574
|
+
content: [
|
|
575
|
+
{
|
|
576
|
+
type: "text",
|
|
577
|
+
text: JSON.stringify(
|
|
578
|
+
{
|
|
579
|
+
success: true,
|
|
580
|
+
archived_tasks: tasks.map((t) => ({
|
|
581
|
+
id: t.id,
|
|
582
|
+
title: t.title,
|
|
583
|
+
completed_at: t.completed_at,
|
|
584
|
+
archived_at: t.archived_at,
|
|
585
|
+
priority: t.priority,
|
|
586
|
+
tags: t.tags?.map((tag) => tag.name) || []
|
|
587
|
+
})),
|
|
588
|
+
count: tasks.length
|
|
589
|
+
},
|
|
590
|
+
null,
|
|
591
|
+
2
|
|
592
|
+
)
|
|
593
|
+
}
|
|
594
|
+
]
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
// unarchive_task
|
|
599
|
+
{
|
|
600
|
+
name: "unarchive_task",
|
|
601
|
+
description: "Restore an archived task. Moves the task from archived status back to done (or optionally another status).",
|
|
602
|
+
inputSchema: z.object({
|
|
603
|
+
task_id: z.string().describe("Task ID to unarchive"),
|
|
604
|
+
restore_status: z.enum(["todo", "vibing", "done"]).default("done").describe("Status to restore the task to (default: done)")
|
|
605
|
+
}),
|
|
606
|
+
handler: async (args, taskOps2) => {
|
|
607
|
+
const task = await taskOps2.unarchiveTask(args.task_id, args.restore_status);
|
|
608
|
+
return {
|
|
609
|
+
content: [
|
|
610
|
+
{
|
|
611
|
+
type: "text",
|
|
612
|
+
text: JSON.stringify(
|
|
613
|
+
{
|
|
614
|
+
success: true,
|
|
615
|
+
task: {
|
|
616
|
+
id: task.id,
|
|
617
|
+
title: task.title,
|
|
618
|
+
status: task.status
|
|
619
|
+
},
|
|
620
|
+
message: `Task restored to '${args.restore_status}' status.`
|
|
621
|
+
},
|
|
622
|
+
null,
|
|
623
|
+
2
|
|
624
|
+
)
|
|
625
|
+
}
|
|
626
|
+
]
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
// capture_error
|
|
631
|
+
{
|
|
632
|
+
name: "capture_error",
|
|
633
|
+
description: "Parse error text from clipboard, terminal, or direct paste. Extracts structured information from common error formats (Node.js, Python, Expo, webpack, TypeScript, etc.) for AI consumption.",
|
|
634
|
+
inputSchema: z.object({
|
|
635
|
+
error_text: z.string().describe("The raw error text to parse"),
|
|
636
|
+
source: z.enum(["terminal", "browser", "expo", "other"]).default("other").describe("Source of the error (helps with parsing)")
|
|
637
|
+
}),
|
|
638
|
+
handler: async (args, _taskOps) => {
|
|
639
|
+
const { error_text, source } = args;
|
|
640
|
+
const parsed = parseError(error_text, source);
|
|
641
|
+
return {
|
|
642
|
+
content: [
|
|
643
|
+
{
|
|
644
|
+
type: "text",
|
|
645
|
+
text: JSON.stringify(
|
|
646
|
+
{
|
|
647
|
+
success: true,
|
|
648
|
+
...parsed
|
|
649
|
+
},
|
|
650
|
+
null,
|
|
651
|
+
2
|
|
652
|
+
)
|
|
653
|
+
}
|
|
654
|
+
]
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
// summarize_errors
|
|
659
|
+
{
|
|
660
|
+
name: "summarize_errors",
|
|
661
|
+
description: "Summarize and deduplicate large error logs (up to thousands of lines). Groups errors by type/file, deduplicates similar errors using pattern matching, filters out node_modules frames, and returns a condensed summary (50-100 lines max).",
|
|
662
|
+
inputSchema: z.object({
|
|
663
|
+
log_text: z.string().describe("The raw log output containing errors"),
|
|
664
|
+
max_lines: z.number().default(100).describe("Maximum lines in the summary output (default: 100)")
|
|
665
|
+
}),
|
|
666
|
+
handler: async (args, _taskOps) => {
|
|
667
|
+
const { log_text, max_lines = 100 } = args;
|
|
668
|
+
const result = summarizeErrors(log_text, max_lines);
|
|
669
|
+
return {
|
|
670
|
+
content: [
|
|
671
|
+
{
|
|
672
|
+
type: "text",
|
|
673
|
+
text: JSON.stringify(
|
|
674
|
+
{
|
|
675
|
+
success: true,
|
|
676
|
+
...result
|
|
677
|
+
},
|
|
678
|
+
null,
|
|
679
|
+
2
|
|
680
|
+
)
|
|
681
|
+
}
|
|
682
|
+
]
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
// update_subtask
|
|
687
|
+
{
|
|
688
|
+
name: "update_subtask",
|
|
689
|
+
description: "Update a subtask within a task. Mark it done, add progress notes, or update the title. Use this to track progress on individual steps.",
|
|
690
|
+
inputSchema: z.object({
|
|
691
|
+
task_id: z.string().describe("Parent task ID"),
|
|
692
|
+
subtask_id: z.string().describe("Subtask ID to update"),
|
|
693
|
+
done: z.boolean().optional().describe("Mark subtask as done/not done"),
|
|
694
|
+
title: z.string().optional().describe("Update subtask title"),
|
|
695
|
+
notes: z.string().optional().describe('Add progress notes (e.g., "Completed auth module, need to test edge cases")')
|
|
696
|
+
}),
|
|
697
|
+
handler: async (args, taskOps2) => {
|
|
698
|
+
const task = await taskOps2.getTask(args.task_id);
|
|
699
|
+
const subtasks = task.subtasks_json || task.subtasks || [];
|
|
700
|
+
const subtaskIndex = subtasks.findIndex((st) => st.id === args.subtask_id);
|
|
701
|
+
if (subtaskIndex === -1) {
|
|
702
|
+
return {
|
|
703
|
+
content: [
|
|
704
|
+
{
|
|
705
|
+
type: "text",
|
|
706
|
+
text: JSON.stringify({ success: false, error: `Subtask ${args.subtask_id} not found` }, null, 2)
|
|
707
|
+
}
|
|
708
|
+
]
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
if (args.done !== void 0) subtasks[subtaskIndex].done = args.done;
|
|
712
|
+
if (args.title !== void 0) subtasks[subtaskIndex].title = args.title;
|
|
713
|
+
if (args.notes !== void 0) subtasks[subtaskIndex].notes = args.notes;
|
|
714
|
+
const updated = await taskOps2.updateTask(args.task_id, { subtasks_json: subtasks });
|
|
715
|
+
return {
|
|
716
|
+
content: [
|
|
717
|
+
{
|
|
718
|
+
type: "text",
|
|
719
|
+
text: JSON.stringify(
|
|
720
|
+
{
|
|
721
|
+
success: true,
|
|
722
|
+
task_id: args.task_id,
|
|
723
|
+
subtask: subtasks[subtaskIndex],
|
|
724
|
+
all_subtasks: subtasks,
|
|
725
|
+
progress: `${subtasks.filter((s) => s.done).length}/${subtasks.length} done`
|
|
726
|
+
},
|
|
727
|
+
null,
|
|
728
|
+
2
|
|
729
|
+
)
|
|
730
|
+
}
|
|
731
|
+
]
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
},
|
|
735
|
+
// add_subtask
|
|
736
|
+
{
|
|
737
|
+
name: "add_subtask",
|
|
738
|
+
description: "Add a new subtask to an existing task. Use this when you discover new steps while working.",
|
|
739
|
+
inputSchema: z.object({
|
|
740
|
+
task_id: z.string().describe("Parent task ID"),
|
|
741
|
+
title: z.string().describe("Subtask title"),
|
|
742
|
+
notes: z.string().optional().describe("Optional notes")
|
|
743
|
+
}),
|
|
744
|
+
handler: async (args, taskOps2) => {
|
|
745
|
+
const task = await taskOps2.getTask(args.task_id);
|
|
746
|
+
const subtasks = task.subtasks_json || task.subtasks || [];
|
|
747
|
+
const newSubtask = {
|
|
748
|
+
id: crypto.randomUUID(),
|
|
749
|
+
title: args.title,
|
|
750
|
+
done: false,
|
|
751
|
+
notes: args.notes
|
|
752
|
+
};
|
|
753
|
+
subtasks.push(newSubtask);
|
|
754
|
+
await taskOps2.updateTask(args.task_id, { subtasks_json: subtasks });
|
|
755
|
+
return {
|
|
756
|
+
content: [
|
|
757
|
+
{
|
|
758
|
+
type: "text",
|
|
759
|
+
text: JSON.stringify(
|
|
760
|
+
{
|
|
761
|
+
success: true,
|
|
762
|
+
task_id: args.task_id,
|
|
763
|
+
new_subtask: newSubtask,
|
|
764
|
+
total_subtasks: subtasks.length
|
|
765
|
+
},
|
|
766
|
+
null,
|
|
767
|
+
2
|
|
768
|
+
)
|
|
769
|
+
}
|
|
770
|
+
]
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
},
|
|
774
|
+
// get_task_images
|
|
775
|
+
{
|
|
776
|
+
name: "get_task_images",
|
|
777
|
+
description: "Get image attachments for a task. Returns signed URLs that can be viewed. Use this to see mockups, screenshots, or error images attached to a task.",
|
|
778
|
+
inputSchema: z.object({
|
|
779
|
+
task_id: z.string().describe("Task ID to get images for")
|
|
780
|
+
}),
|
|
781
|
+
handler: async (args, taskOps2) => {
|
|
782
|
+
const task = await taskOps2.getTask(args.task_id);
|
|
783
|
+
const attachments = task.attachments || [];
|
|
784
|
+
const images = attachments.filter((a) => a.is_image || a.file_type?.startsWith("image/"));
|
|
785
|
+
const imageData = await Promise.all(
|
|
786
|
+
images.map(async (img) => {
|
|
787
|
+
const url = await taskOps2.getAttachmentUrl(img.storage_path);
|
|
788
|
+
return {
|
|
789
|
+
id: img.id,
|
|
790
|
+
file_name: img.file_name,
|
|
791
|
+
file_type: img.file_type,
|
|
792
|
+
url,
|
|
793
|
+
alt_text: img.alt_text,
|
|
794
|
+
ai_description: img.ai_description
|
|
795
|
+
};
|
|
796
|
+
})
|
|
797
|
+
);
|
|
798
|
+
return {
|
|
799
|
+
content: [
|
|
800
|
+
{
|
|
801
|
+
type: "text",
|
|
802
|
+
text: JSON.stringify(
|
|
803
|
+
{
|
|
804
|
+
success: true,
|
|
805
|
+
task_id: args.task_id,
|
|
806
|
+
task_title: task.title,
|
|
807
|
+
images: imageData,
|
|
808
|
+
count: imageData.length
|
|
809
|
+
},
|
|
810
|
+
null,
|
|
811
|
+
2
|
|
812
|
+
)
|
|
813
|
+
}
|
|
814
|
+
]
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
// get_current_task
|
|
819
|
+
{
|
|
820
|
+
name: "get_current_task",
|
|
821
|
+
description: 'Get the task you should be working on right now. Returns the first "vibing" task with full details including subtasks, description, context notes, and images. Call this at the start of a session to know where you left off.',
|
|
822
|
+
inputSchema: z.object({}),
|
|
823
|
+
handler: async (_args, taskOps2) => {
|
|
824
|
+
const allTasks = await taskOps2.getTasks("all");
|
|
825
|
+
const vibingTasks = allTasks.filter((t) => t.status === "vibing");
|
|
826
|
+
if (vibingTasks.length === 0) {
|
|
827
|
+
return {
|
|
828
|
+
content: [
|
|
829
|
+
{
|
|
830
|
+
type: "text",
|
|
831
|
+
text: JSON.stringify(
|
|
832
|
+
{
|
|
833
|
+
success: true,
|
|
834
|
+
current_task: null,
|
|
835
|
+
message: "No tasks currently vibing. Use list_tasks to see available tasks, or create_task to add one."
|
|
836
|
+
},
|
|
837
|
+
null,
|
|
838
|
+
2
|
|
839
|
+
)
|
|
840
|
+
}
|
|
841
|
+
]
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
const task = await taskOps2.getTask(vibingTasks[0].id);
|
|
845
|
+
return {
|
|
846
|
+
content: [
|
|
847
|
+
{
|
|
848
|
+
type: "text",
|
|
849
|
+
text: JSON.stringify(
|
|
850
|
+
{
|
|
851
|
+
success: true,
|
|
852
|
+
current_task: {
|
|
853
|
+
id: task.id,
|
|
854
|
+
title: task.title,
|
|
855
|
+
description: task.description,
|
|
856
|
+
context_notes: task.context_notes,
|
|
857
|
+
subtasks: task.subtasks_json || task.subtasks || [],
|
|
858
|
+
progress: task.subtasks_json ? `${task.subtasks_json.filter((s) => s.done).length}/${task.subtasks_json.length} subtasks done` : null,
|
|
859
|
+
priority: task.priority,
|
|
860
|
+
has_images: (task.attachments || []).some((a) => a.is_image || a.file_type?.startsWith("image/"))
|
|
861
|
+
},
|
|
862
|
+
other_vibing_count: vibingTasks.length - 1
|
|
863
|
+
},
|
|
864
|
+
null,
|
|
865
|
+
2
|
|
866
|
+
)
|
|
867
|
+
}
|
|
868
|
+
]
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
];
|
|
873
|
+
}
|
|
874
|
+
function parseError(errorText, source) {
|
|
875
|
+
const lines = errorText.split("\n");
|
|
876
|
+
let errorType = "Unknown";
|
|
877
|
+
let file = null;
|
|
878
|
+
let line = null;
|
|
879
|
+
let column = null;
|
|
880
|
+
let message = "";
|
|
881
|
+
const stackFrames = [];
|
|
882
|
+
const patterns = {
|
|
883
|
+
// Node.js/JavaScript: "TypeError: Cannot read property 'x' of undefined"
|
|
884
|
+
jsError: /^(\w+Error):\s*(.+)$/,
|
|
885
|
+
// Python: "TypeError: unsupported operand type(s)"
|
|
886
|
+
pythonError: /^(\w+Error|Exception):\s*(.+)$/,
|
|
887
|
+
// Stack frame patterns
|
|
888
|
+
// Node.js: " at functionName (file.js:10:5)"
|
|
889
|
+
nodeStackFrame: /^\s*at\s+(?:(.+?)\s+)?\(?(.+?):(\d+):(\d+)\)?$/,
|
|
890
|
+
// Python: ' File "script.py", line 10, in function_name'
|
|
891
|
+
pythonStackFrame: /^\s*File\s+"([^"]+)",\s*line\s*(\d+)(?:,\s*in\s+(.+))?$/,
|
|
892
|
+
// Webpack/bundler: "ERROR in ./src/file.tsx:10:5"
|
|
893
|
+
webpackError: /^ERROR\s+in\s+\.?(.+?):(\d+):(\d+)$/,
|
|
894
|
+
// TypeScript: "src/file.ts(10,5): error TS2345:"
|
|
895
|
+
tsError: /^(.+?)\((\d+),(\d+)\):\s*(error\s+TS\d+):\s*(.+)$/,
|
|
896
|
+
// Expo/React Native: "Error: ..." with component stack
|
|
897
|
+
expoError: /^Error:\s*(.+)$/,
|
|
898
|
+
// Generic file:line:col pattern
|
|
899
|
+
genericFileLine: /([^\s:]+\.[a-zA-Z]+):(\d+)(?::(\d+))?/,
|
|
900
|
+
// ESLint/Prettier style: " 10:5 error Message"
|
|
901
|
+
lintError: /^\s*(\d+):(\d+)\s+(error|warning)\s+(.+)$/
|
|
902
|
+
};
|
|
903
|
+
for (let i = 0; i < lines.length; i++) {
|
|
904
|
+
const trimmedLine = lines[i].trim();
|
|
905
|
+
if (!trimmedLine) continue;
|
|
906
|
+
const tsMatch = trimmedLine.match(patterns.tsError);
|
|
907
|
+
if (tsMatch) {
|
|
908
|
+
file = tsMatch[1];
|
|
909
|
+
line = parseInt(tsMatch[2], 10);
|
|
910
|
+
column = parseInt(tsMatch[3], 10);
|
|
911
|
+
errorType = tsMatch[4];
|
|
912
|
+
message = tsMatch[5];
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
const webpackMatch = trimmedLine.match(patterns.webpackError);
|
|
916
|
+
if (webpackMatch) {
|
|
917
|
+
file = webpackMatch[1];
|
|
918
|
+
line = parseInt(webpackMatch[2], 10);
|
|
919
|
+
column = parseInt(webpackMatch[3], 10);
|
|
920
|
+
errorType = "WebpackError";
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
const jsMatch = trimmedLine.match(patterns.jsError);
|
|
924
|
+
if (jsMatch && !message) {
|
|
925
|
+
errorType = jsMatch[1];
|
|
926
|
+
message = jsMatch[2];
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
const pythonMatch = trimmedLine.match(patterns.pythonError);
|
|
930
|
+
if (pythonMatch && !message) {
|
|
931
|
+
errorType = pythonMatch[1];
|
|
932
|
+
message = pythonMatch[2];
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
if (source === "expo") {
|
|
936
|
+
const expoMatch = trimmedLine.match(patterns.expoError);
|
|
937
|
+
if (expoMatch && !message) {
|
|
938
|
+
errorType = "ExpoError";
|
|
939
|
+
message = expoMatch[1];
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
const nodeFrameMatch = trimmedLine.match(patterns.nodeStackFrame);
|
|
944
|
+
if (nodeFrameMatch) {
|
|
945
|
+
const framePath = nodeFrameMatch[2];
|
|
946
|
+
const frameLine = nodeFrameMatch[3];
|
|
947
|
+
const frameCol = nodeFrameMatch[4];
|
|
948
|
+
const funcName = nodeFrameMatch[1] || "<anonymous>";
|
|
949
|
+
if (!isLibraryFrame(framePath)) {
|
|
950
|
+
stackFrames.push(`${funcName} at ${framePath}:${frameLine}:${frameCol}`);
|
|
951
|
+
if (!file) {
|
|
952
|
+
file = framePath;
|
|
953
|
+
line = parseInt(frameLine, 10);
|
|
954
|
+
column = parseInt(frameCol, 10);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
const pythonFrameMatch = trimmedLine.match(patterns.pythonStackFrame);
|
|
960
|
+
if (pythonFrameMatch) {
|
|
961
|
+
const framePath = pythonFrameMatch[1];
|
|
962
|
+
const frameLine = pythonFrameMatch[2];
|
|
963
|
+
const funcName = pythonFrameMatch[3] || "<module>";
|
|
964
|
+
if (!isLibraryFrame(framePath)) {
|
|
965
|
+
stackFrames.push(`${funcName} at ${framePath}:${frameLine}`);
|
|
966
|
+
file = framePath;
|
|
967
|
+
line = parseInt(frameLine, 10);
|
|
968
|
+
}
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
const lintMatch = trimmedLine.match(patterns.lintError);
|
|
972
|
+
if (lintMatch) {
|
|
973
|
+
line = parseInt(lintMatch[1], 10);
|
|
974
|
+
column = parseInt(lintMatch[2], 10);
|
|
975
|
+
errorType = lintMatch[3] === "error" ? "LintError" : "LintWarning";
|
|
976
|
+
message = lintMatch[4];
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
if (!file) {
|
|
980
|
+
const genericMatch = trimmedLine.match(patterns.genericFileLine);
|
|
981
|
+
if (genericMatch && !isLibraryFrame(genericMatch[1])) {
|
|
982
|
+
file = genericMatch[1];
|
|
983
|
+
line = parseInt(genericMatch[2], 10);
|
|
984
|
+
column = genericMatch[3] ? parseInt(genericMatch[3], 10) : null;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if (errorType !== "Unknown" && !message && trimmedLine && !trimmedLine.startsWith("at ")) {
|
|
988
|
+
message = trimmedLine;
|
|
518
989
|
}
|
|
990
|
+
}
|
|
991
|
+
if (!message) {
|
|
992
|
+
message = lines.find((l) => l.trim())?.trim() || "No error message found";
|
|
993
|
+
}
|
|
994
|
+
const rawExcerpt = lines.slice(0, 10).join("\n");
|
|
995
|
+
return {
|
|
996
|
+
error_type: errorType,
|
|
997
|
+
file,
|
|
998
|
+
line,
|
|
999
|
+
column,
|
|
1000
|
+
message,
|
|
1001
|
+
stack_summary: stackFrames.slice(0, 5),
|
|
1002
|
+
// Limit to 5 frames
|
|
1003
|
+
raw_excerpt: rawExcerpt
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
function isLibraryFrame(filePath) {
|
|
1007
|
+
const libraryPatterns = [
|
|
1008
|
+
/node_modules/,
|
|
1009
|
+
/site-packages/,
|
|
1010
|
+
/dist-packages/,
|
|
1011
|
+
/\.pnpm/,
|
|
1012
|
+
/\.yarn/,
|
|
1013
|
+
/internal\//,
|
|
1014
|
+
/^node:/,
|
|
1015
|
+
/<anonymous>/,
|
|
1016
|
+
/webpack-internal/,
|
|
1017
|
+
/react-dom/,
|
|
1018
|
+
/react-native/,
|
|
1019
|
+
/expo/,
|
|
1020
|
+
/metro/,
|
|
1021
|
+
/hermes/
|
|
1022
|
+
];
|
|
1023
|
+
return libraryPatterns.some((pattern) => pattern.test(filePath));
|
|
1024
|
+
}
|
|
1025
|
+
function summarizeErrors(logText, maxLines = 100) {
|
|
1026
|
+
const errorTypePatterns = [
|
|
1027
|
+
{ type: "TypeError", pattern: /TypeError:\s*(.+)/i },
|
|
1028
|
+
{ type: "SyntaxError", pattern: /SyntaxError:\s*(.+)/i },
|
|
1029
|
+
{ type: "ReferenceError", pattern: /ReferenceError:\s*(.+)/i },
|
|
1030
|
+
{ type: "RangeError", pattern: /RangeError:\s*(.+)/i },
|
|
1031
|
+
{ type: "EvalError", pattern: /EvalError:\s*(.+)/i },
|
|
1032
|
+
{ type: "URIError", pattern: /URIError:\s*(.+)/i },
|
|
1033
|
+
{ type: "AggregateError", pattern: /AggregateError:\s*(.+)/i },
|
|
1034
|
+
{ type: "InternalError", pattern: /InternalError:\s*(.+)/i },
|
|
1035
|
+
{ type: "AssertionError", pattern: /AssertionError:\s*(.+)/i },
|
|
1036
|
+
{ type: "ValidationError", pattern: /ValidationError:\s*(.+)/i },
|
|
1037
|
+
{ type: "NetworkError", pattern: /NetworkError:\s*(.+)/i },
|
|
1038
|
+
{ type: "TimeoutError", pattern: /TimeoutError:\s*(.+)/i },
|
|
1039
|
+
{ type: "ENOENT", pattern: /ENOENT:\s*(.+)/i },
|
|
1040
|
+
{ type: "EACCES", pattern: /EACCES:\s*(.+)/i },
|
|
1041
|
+
{ type: "ECONNREFUSED", pattern: /ECONNREFUSED:\s*(.+)/i },
|
|
1042
|
+
{ type: "ETIMEDOUT", pattern: /ETIMEDOUT:\s*(.+)/i },
|
|
1043
|
+
{ type: "ModuleNotFoundError", pattern: /ModuleNotFoundError:\s*(.+)/i },
|
|
1044
|
+
{ type: "ImportError", pattern: /ImportError:\s*(.+)/i },
|
|
1045
|
+
{ type: "AttributeError", pattern: /AttributeError:\s*(.+)/i },
|
|
1046
|
+
{ type: "KeyError", pattern: /KeyError:\s*(.+)/i },
|
|
1047
|
+
{ type: "ValueError", pattern: /ValueError:\s*(.+)/i },
|
|
1048
|
+
{ type: "IndexError", pattern: /IndexError:\s*(.+)/i },
|
|
1049
|
+
{ type: "Error", pattern: /(?:^|\s)Error:\s*(.+)/i },
|
|
1050
|
+
{ type: "Warning", pattern: /(?:^|\s)(?:Warning|WARN):\s*(.+)/i },
|
|
1051
|
+
{ type: "Failed", pattern: /(?:FAILED|Failed|failed):\s*(.+)/i },
|
|
1052
|
+
{ type: "Exception", pattern: /Exception:\s*(.+)/i }
|
|
519
1053
|
];
|
|
1054
|
+
const lines = logText.split("\n");
|
|
1055
|
+
const totalLines = lines.length;
|
|
1056
|
+
const errorMap = /* @__PURE__ */ new Map();
|
|
1057
|
+
let totalErrorCount = 0;
|
|
1058
|
+
const filePathPattern = /(?:at\s+(?:.*?\s+\()?)?([A-Za-z]:)?([/\\](?!node_modules)[^\s:()]+\.[a-z]+)(?::(\d+))?(?::(\d+))?/gi;
|
|
1059
|
+
const nodeModulesPattern = /node_modules/i;
|
|
1060
|
+
function normalizeMessage(msg) {
|
|
1061
|
+
return msg.replace(/['"`].*?['"`]/g, "<string>").replace(/\b\d+\b/g, "<num>").replace(/0x[0-9a-fA-F]+/g, "<hex>").replace(/\{[^}]*\}/g, "<obj>").replace(/\[[^\]]*\]/g, "<arr>").replace(
|
|
1062
|
+
/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/gi,
|
|
1063
|
+
"<uuid>"
|
|
1064
|
+
).replace(/\s+/g, " ").trim().substring(0, 200);
|
|
1065
|
+
}
|
|
1066
|
+
function extractAppFiles(line) {
|
|
1067
|
+
const files = [];
|
|
1068
|
+
const matches = line.matchAll(filePathPattern);
|
|
1069
|
+
for (const match of matches) {
|
|
1070
|
+
const fullPath = (match[1] || "") + match[2];
|
|
1071
|
+
if (!nodeModulesPattern.test(fullPath)) {
|
|
1072
|
+
const lineNum = match[3] ? `:${match[3]}` : "";
|
|
1073
|
+
files.push(fullPath + lineNum);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
return files;
|
|
1077
|
+
}
|
|
1078
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1079
|
+
const line = lines[i];
|
|
1080
|
+
if (!line.trim()) continue;
|
|
1081
|
+
for (const { type, pattern } of errorTypePatterns) {
|
|
1082
|
+
const match = line.match(pattern);
|
|
1083
|
+
if (match) {
|
|
1084
|
+
totalErrorCount++;
|
|
1085
|
+
const message = match[1] || line;
|
|
1086
|
+
const normalized = normalizeMessage(message);
|
|
1087
|
+
const key = `${type}:${normalized}`;
|
|
1088
|
+
const contextLines = lines.slice(i, Math.min(i + 6, lines.length));
|
|
1089
|
+
const files = /* @__PURE__ */ new Set();
|
|
1090
|
+
for (const contextLine of contextLines) {
|
|
1091
|
+
extractAppFiles(contextLine).forEach((f) => files.add(f));
|
|
1092
|
+
}
|
|
1093
|
+
if (errorMap.has(key)) {
|
|
1094
|
+
const existing = errorMap.get(key);
|
|
1095
|
+
existing.count++;
|
|
1096
|
+
files.forEach((f) => existing.files.add(f));
|
|
1097
|
+
} else {
|
|
1098
|
+
let example = line;
|
|
1099
|
+
for (let j = i + 1; j < Math.min(i + 6, lines.length); j++) {
|
|
1100
|
+
const stackLine = lines[j];
|
|
1101
|
+
if (stackLine.trim().startsWith("at ") && !nodeModulesPattern.test(stackLine)) {
|
|
1102
|
+
example += "\n" + stackLine;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
errorMap.set(key, {
|
|
1106
|
+
type,
|
|
1107
|
+
message: message.substring(0, 150),
|
|
1108
|
+
normalizedMessage: normalized,
|
|
1109
|
+
files,
|
|
1110
|
+
count: 1,
|
|
1111
|
+
firstOccurrence: i + 1,
|
|
1112
|
+
example: example.substring(0, 500)
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
break;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
const groupedErrors = Array.from(errorMap.values()).sort((a, b) => b.count - a.count).map((e) => ({
|
|
1120
|
+
type: e.type,
|
|
1121
|
+
count: e.count,
|
|
1122
|
+
example: e.example,
|
|
1123
|
+
files: Array.from(e.files).slice(0, 5)
|
|
1124
|
+
// Limit files per error
|
|
1125
|
+
}));
|
|
1126
|
+
const summaryLines = [];
|
|
1127
|
+
summaryLines.push(`=== ERROR SUMMARY ===`);
|
|
1128
|
+
summaryLines.push(`Total lines processed: ${totalLines}`);
|
|
1129
|
+
summaryLines.push(`Total errors found: ${totalErrorCount}`);
|
|
1130
|
+
summaryLines.push(`Unique error patterns: ${groupedErrors.length}`);
|
|
1131
|
+
summaryLines.push("");
|
|
1132
|
+
const typeCount = /* @__PURE__ */ new Map();
|
|
1133
|
+
for (const err of groupedErrors) {
|
|
1134
|
+
typeCount.set(err.type, (typeCount.get(err.type) || 0) + err.count);
|
|
1135
|
+
}
|
|
1136
|
+
summaryLines.push("--- Errors by Type ---");
|
|
1137
|
+
for (const [type, count] of Array.from(typeCount.entries()).sort(
|
|
1138
|
+
(a, b) => b[1] - a[1]
|
|
1139
|
+
)) {
|
|
1140
|
+
summaryLines.push(` ${type}: ${count}`);
|
|
1141
|
+
}
|
|
1142
|
+
summaryLines.push("");
|
|
1143
|
+
summaryLines.push("--- Top Errors (by frequency) ---");
|
|
1144
|
+
let currentLines = summaryLines.length;
|
|
1145
|
+
const maxDetailedErrors = Math.min(groupedErrors.length, 20);
|
|
1146
|
+
for (let i = 0; i < maxDetailedErrors && currentLines < maxLines - 5; i++) {
|
|
1147
|
+
const err = groupedErrors[i];
|
|
1148
|
+
summaryLines.push("");
|
|
1149
|
+
summaryLines.push(`[${i + 1}] ${err.type} (x${err.count})`);
|
|
1150
|
+
const exampleLines = err.example.split("\n");
|
|
1151
|
+
for (const exLine of exampleLines) {
|
|
1152
|
+
if (currentLines >= maxLines - 3) break;
|
|
1153
|
+
summaryLines.push(` ${exLine}`);
|
|
1154
|
+
currentLines++;
|
|
1155
|
+
}
|
|
1156
|
+
if (err.files.length > 0) {
|
|
1157
|
+
summaryLines.push(` Files: ${err.files.join(", ")}`);
|
|
1158
|
+
}
|
|
1159
|
+
currentLines = summaryLines.length;
|
|
1160
|
+
}
|
|
1161
|
+
const summary = summaryLines.slice(0, maxLines).join("\n");
|
|
1162
|
+
return {
|
|
1163
|
+
summary,
|
|
1164
|
+
error_count: totalErrorCount,
|
|
1165
|
+
unique_errors: groupedErrors.length,
|
|
1166
|
+
grouped_errors: groupedErrors.slice(0, 50)
|
|
1167
|
+
// Limit to top 50
|
|
1168
|
+
};
|
|
520
1169
|
}
|
|
521
1170
|
|
|
522
1171
|
// src/resources/index.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibetasks/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "VibeTasks MCP Server for Claude Code, Cursor, and AI coding tools. Status-based task management: todo → vibing → done.",
|
|
5
5
|
"author": "Vyas",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@modelcontextprotocol/sdk": "^0.5.0",
|
|
48
|
-
"@vibetasks/core": "^0.
|
|
48
|
+
"@vibetasks/core": "^0.4.0",
|
|
49
49
|
"zod": "^3.22.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|