@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.
Files changed (2) hide show
  1. package/dist/index.js +655 -6
  2. 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 TaskFlow. Can optionally be a subtask by providing parent_task_id.",
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
- notes: z.string().optional().describe("Task notes in markdown"),
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
- parent_task_id: args.parent_task_id
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
- parent_task_id: task.parent_task_id,
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.2.2",
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.2.0",
48
+ "@vibetasks/core": "^0.4.0",
49
49
  "zod": "^3.22.0"
50
50
  },
51
51
  "devDependencies": {