@vibetasks/mcp-server 0.2.1 → 0.3.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 +615 -0
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -303,6 +303,171 @@ function setupTools(taskOps) {
303
303
  };
304
304
  }
305
305
  },
306
+ // start_vibing
307
+ {
308
+ name: "start_vibing",
309
+ description: 'Start vibing on a task (move to "vibing" status). Max 3 tasks vibing at once - research shows more causes 40% productivity loss.',
310
+ inputSchema: z.object({
311
+ task_id: z.string().describe("Task ID to start vibing on")
312
+ }),
313
+ handler: async (args, taskOps2) => {
314
+ const allTasks = await taskOps2.getTasks("all");
315
+ const vibingTasks = allTasks.filter((t) => t.status === "vibing");
316
+ const WIP_LIMIT = 3;
317
+ if (vibingTasks.length >= WIP_LIMIT) {
318
+ return {
319
+ content: [
320
+ {
321
+ type: "text",
322
+ text: JSON.stringify(
323
+ {
324
+ success: false,
325
+ error: `WIP limit reached! You have ${vibingTasks.length} tasks vibing. Complete one before starting another.`,
326
+ vibing_tasks: vibingTasks.map((t) => ({ id: t.id, title: t.title })),
327
+ wip_limit: WIP_LIMIT
328
+ },
329
+ null,
330
+ 2
331
+ )
332
+ }
333
+ ]
334
+ };
335
+ }
336
+ const task = await taskOps2.updateTask(args.task_id, { status: "vibing" });
337
+ return {
338
+ content: [
339
+ {
340
+ type: "text",
341
+ text: JSON.stringify(
342
+ {
343
+ success: true,
344
+ task: {
345
+ id: task.id,
346
+ title: task.title,
347
+ status: "vibing"
348
+ },
349
+ wip_count: vibingTasks.length + 1,
350
+ wip_limit: WIP_LIMIT
351
+ },
352
+ null,
353
+ 2
354
+ )
355
+ }
356
+ ]
357
+ };
358
+ }
359
+ },
360
+ // get_vibing_tasks
361
+ {
362
+ name: "get_vibing_tasks",
363
+ description: 'Get all tasks currently in "vibing" status. Use this to check what the user is actively working on.',
364
+ inputSchema: z.object({}),
365
+ handler: async (args, taskOps2) => {
366
+ const allTasks = await taskOps2.getTasks("all");
367
+ const vibingTasks = allTasks.filter((t) => t.status === "vibing");
368
+ const WIP_LIMIT = 3;
369
+ return {
370
+ content: [
371
+ {
372
+ type: "text",
373
+ text: JSON.stringify(
374
+ {
375
+ success: true,
376
+ vibing_tasks: vibingTasks.map((t) => ({
377
+ id: t.id,
378
+ title: t.title,
379
+ context_notes: t.context_notes,
380
+ priority: t.priority,
381
+ due_date: t.due_date
382
+ })),
383
+ count: vibingTasks.length,
384
+ wip_limit: WIP_LIMIT,
385
+ at_limit: vibingTasks.length >= WIP_LIMIT
386
+ },
387
+ null,
388
+ 2
389
+ )
390
+ }
391
+ ]
392
+ };
393
+ }
394
+ },
395
+ // set_context_notes
396
+ {
397
+ name: "set_context_notes",
398
+ description: 'Save "where I left off" notes for a task. Use this before pausing work or ending a session to capture context for later.',
399
+ inputSchema: z.object({
400
+ task_id: z.string().describe("Task ID"),
401
+ context_notes: z.string().describe("Where you left off - file, line, next step, blockers")
402
+ }),
403
+ handler: async (args, taskOps2) => {
404
+ const task = await taskOps2.updateTask(args.task_id, {
405
+ context_notes: args.context_notes
406
+ });
407
+ return {
408
+ content: [
409
+ {
410
+ type: "text",
411
+ text: JSON.stringify(
412
+ {
413
+ success: true,
414
+ task: {
415
+ id: task.id,
416
+ title: task.title,
417
+ context_notes: task.context_notes
418
+ },
419
+ message: "Context notes saved. You can resume seamlessly later."
420
+ },
421
+ null,
422
+ 2
423
+ )
424
+ }
425
+ ]
426
+ };
427
+ }
428
+ },
429
+ // list_tasks (with status filter)
430
+ {
431
+ name: "list_tasks",
432
+ description: "List tasks filtered by status (todo, vibing, done) or all tasks",
433
+ inputSchema: z.object({
434
+ status: z.enum(["todo", "vibing", "done", "all"]).default("all").describe("Filter by status"),
435
+ limit: z.number().default(50).describe("Maximum results")
436
+ }),
437
+ handler: async (args, taskOps2) => {
438
+ const allTasks = await taskOps2.getTasks("all");
439
+ let tasks = allTasks;
440
+ if (args.status !== "all") {
441
+ tasks = allTasks.filter((t) => t.status === args.status);
442
+ }
443
+ tasks = tasks.slice(0, args.limit);
444
+ return {
445
+ content: [
446
+ {
447
+ type: "text",
448
+ text: JSON.stringify(
449
+ {
450
+ success: true,
451
+ tasks: tasks.map((t) => ({
452
+ id: t.id,
453
+ title: t.title,
454
+ status: t.status,
455
+ priority: t.priority,
456
+ due_date: t.due_date,
457
+ context_notes: t.context_notes,
458
+ tags: t.tags?.map((tag) => tag.name) || []
459
+ })),
460
+ count: tasks.length,
461
+ filter: args.status
462
+ },
463
+ null,
464
+ 2
465
+ )
466
+ }
467
+ ]
468
+ };
469
+ }
470
+ },
306
471
  // log_ai_session
307
472
  {
308
473
  name: "log_ai_session",
@@ -350,9 +515,459 @@ Generated by TaskFlow MCP Server`;
350
515
  ]
351
516
  };
352
517
  }
518
+ },
519
+ // archive_task
520
+ {
521
+ name: "archive_task",
522
+ 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.",
523
+ inputSchema: z.object({
524
+ task_id: z.string().describe("Task ID to archive")
525
+ }),
526
+ handler: async (args, taskOps2) => {
527
+ const task = await taskOps2.archiveTask(args.task_id);
528
+ return {
529
+ content: [
530
+ {
531
+ type: "text",
532
+ text: JSON.stringify(
533
+ {
534
+ success: true,
535
+ task: {
536
+ id: task.id,
537
+ title: task.title,
538
+ status: "archived",
539
+ archived_at: task.archived_at
540
+ },
541
+ message: "Task archived successfully. Use get_archived_tasks to view archived tasks."
542
+ },
543
+ null,
544
+ 2
545
+ )
546
+ }
547
+ ]
548
+ };
549
+ }
550
+ },
551
+ // get_archived_tasks
552
+ {
553
+ name: "get_archived_tasks",
554
+ description: "Get all archived tasks. Returns tasks that have been archived, sorted by archive date (most recent first).",
555
+ inputSchema: z.object({
556
+ limit: z.number().default(50).describe("Maximum number of archived tasks to return")
557
+ }),
558
+ handler: async (args, taskOps2) => {
559
+ const tasks = await taskOps2.getArchivedTasks(args.limit);
560
+ return {
561
+ content: [
562
+ {
563
+ type: "text",
564
+ text: JSON.stringify(
565
+ {
566
+ success: true,
567
+ archived_tasks: tasks.map((t) => ({
568
+ id: t.id,
569
+ title: t.title,
570
+ completed_at: t.completed_at,
571
+ archived_at: t.archived_at,
572
+ priority: t.priority,
573
+ tags: t.tags?.map((tag) => tag.name) || []
574
+ })),
575
+ count: tasks.length
576
+ },
577
+ null,
578
+ 2
579
+ )
580
+ }
581
+ ]
582
+ };
583
+ }
584
+ },
585
+ // unarchive_task
586
+ {
587
+ name: "unarchive_task",
588
+ description: "Restore an archived task. Moves the task from archived status back to done (or optionally another status).",
589
+ inputSchema: z.object({
590
+ task_id: z.string().describe("Task ID to unarchive"),
591
+ restore_status: z.enum(["todo", "vibing", "done"]).default("done").describe("Status to restore the task to (default: done)")
592
+ }),
593
+ handler: async (args, taskOps2) => {
594
+ const task = await taskOps2.unarchiveTask(args.task_id, args.restore_status);
595
+ return {
596
+ content: [
597
+ {
598
+ type: "text",
599
+ text: JSON.stringify(
600
+ {
601
+ success: true,
602
+ task: {
603
+ id: task.id,
604
+ title: task.title,
605
+ status: task.status
606
+ },
607
+ message: `Task restored to '${args.restore_status}' status.`
608
+ },
609
+ null,
610
+ 2
611
+ )
612
+ }
613
+ ]
614
+ };
615
+ }
616
+ },
617
+ // capture_error
618
+ {
619
+ name: "capture_error",
620
+ 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.",
621
+ inputSchema: z.object({
622
+ error_text: z.string().describe("The raw error text to parse"),
623
+ source: z.enum(["terminal", "browser", "expo", "other"]).default("other").describe("Source of the error (helps with parsing)")
624
+ }),
625
+ handler: async (args, _taskOps) => {
626
+ const { error_text, source } = args;
627
+ const parsed = parseError(error_text, source);
628
+ return {
629
+ content: [
630
+ {
631
+ type: "text",
632
+ text: JSON.stringify(
633
+ {
634
+ success: true,
635
+ ...parsed
636
+ },
637
+ null,
638
+ 2
639
+ )
640
+ }
641
+ ]
642
+ };
643
+ }
644
+ },
645
+ // summarize_errors
646
+ {
647
+ name: "summarize_errors",
648
+ 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).",
649
+ inputSchema: z.object({
650
+ log_text: z.string().describe("The raw log output containing errors"),
651
+ max_lines: z.number().default(100).describe("Maximum lines in the summary output (default: 100)")
652
+ }),
653
+ handler: async (args, _taskOps) => {
654
+ const { log_text, max_lines = 100 } = args;
655
+ const result = summarizeErrors(log_text, max_lines);
656
+ return {
657
+ content: [
658
+ {
659
+ type: "text",
660
+ text: JSON.stringify(
661
+ {
662
+ success: true,
663
+ ...result
664
+ },
665
+ null,
666
+ 2
667
+ )
668
+ }
669
+ ]
670
+ };
671
+ }
353
672
  }
354
673
  ];
355
674
  }
675
+ function parseError(errorText, source) {
676
+ const lines = errorText.split("\n");
677
+ let errorType = "Unknown";
678
+ let file = null;
679
+ let line = null;
680
+ let column = null;
681
+ let message = "";
682
+ const stackFrames = [];
683
+ const patterns = {
684
+ // Node.js/JavaScript: "TypeError: Cannot read property 'x' of undefined"
685
+ jsError: /^(\w+Error):\s*(.+)$/,
686
+ // Python: "TypeError: unsupported operand type(s)"
687
+ pythonError: /^(\w+Error|Exception):\s*(.+)$/,
688
+ // Stack frame patterns
689
+ // Node.js: " at functionName (file.js:10:5)"
690
+ nodeStackFrame: /^\s*at\s+(?:(.+?)\s+)?\(?(.+?):(\d+):(\d+)\)?$/,
691
+ // Python: ' File "script.py", line 10, in function_name'
692
+ pythonStackFrame: /^\s*File\s+"([^"]+)",\s*line\s*(\d+)(?:,\s*in\s+(.+))?$/,
693
+ // Webpack/bundler: "ERROR in ./src/file.tsx:10:5"
694
+ webpackError: /^ERROR\s+in\s+\.?(.+?):(\d+):(\d+)$/,
695
+ // TypeScript: "src/file.ts(10,5): error TS2345:"
696
+ tsError: /^(.+?)\((\d+),(\d+)\):\s*(error\s+TS\d+):\s*(.+)$/,
697
+ // Expo/React Native: "Error: ..." with component stack
698
+ expoError: /^Error:\s*(.+)$/,
699
+ // Generic file:line:col pattern
700
+ genericFileLine: /([^\s:]+\.[a-zA-Z]+):(\d+)(?::(\d+))?/,
701
+ // ESLint/Prettier style: " 10:5 error Message"
702
+ lintError: /^\s*(\d+):(\d+)\s+(error|warning)\s+(.+)$/
703
+ };
704
+ for (let i = 0; i < lines.length; i++) {
705
+ const trimmedLine = lines[i].trim();
706
+ if (!trimmedLine) continue;
707
+ const tsMatch = trimmedLine.match(patterns.tsError);
708
+ if (tsMatch) {
709
+ file = tsMatch[1];
710
+ line = parseInt(tsMatch[2], 10);
711
+ column = parseInt(tsMatch[3], 10);
712
+ errorType = tsMatch[4];
713
+ message = tsMatch[5];
714
+ continue;
715
+ }
716
+ const webpackMatch = trimmedLine.match(patterns.webpackError);
717
+ if (webpackMatch) {
718
+ file = webpackMatch[1];
719
+ line = parseInt(webpackMatch[2], 10);
720
+ column = parseInt(webpackMatch[3], 10);
721
+ errorType = "WebpackError";
722
+ continue;
723
+ }
724
+ const jsMatch = trimmedLine.match(patterns.jsError);
725
+ if (jsMatch && !message) {
726
+ errorType = jsMatch[1];
727
+ message = jsMatch[2];
728
+ continue;
729
+ }
730
+ const pythonMatch = trimmedLine.match(patterns.pythonError);
731
+ if (pythonMatch && !message) {
732
+ errorType = pythonMatch[1];
733
+ message = pythonMatch[2];
734
+ continue;
735
+ }
736
+ if (source === "expo") {
737
+ const expoMatch = trimmedLine.match(patterns.expoError);
738
+ if (expoMatch && !message) {
739
+ errorType = "ExpoError";
740
+ message = expoMatch[1];
741
+ continue;
742
+ }
743
+ }
744
+ const nodeFrameMatch = trimmedLine.match(patterns.nodeStackFrame);
745
+ if (nodeFrameMatch) {
746
+ const framePath = nodeFrameMatch[2];
747
+ const frameLine = nodeFrameMatch[3];
748
+ const frameCol = nodeFrameMatch[4];
749
+ const funcName = nodeFrameMatch[1] || "<anonymous>";
750
+ if (!isLibraryFrame(framePath)) {
751
+ stackFrames.push(`${funcName} at ${framePath}:${frameLine}:${frameCol}`);
752
+ if (!file) {
753
+ file = framePath;
754
+ line = parseInt(frameLine, 10);
755
+ column = parseInt(frameCol, 10);
756
+ }
757
+ }
758
+ continue;
759
+ }
760
+ const pythonFrameMatch = trimmedLine.match(patterns.pythonStackFrame);
761
+ if (pythonFrameMatch) {
762
+ const framePath = pythonFrameMatch[1];
763
+ const frameLine = pythonFrameMatch[2];
764
+ const funcName = pythonFrameMatch[3] || "<module>";
765
+ if (!isLibraryFrame(framePath)) {
766
+ stackFrames.push(`${funcName} at ${framePath}:${frameLine}`);
767
+ file = framePath;
768
+ line = parseInt(frameLine, 10);
769
+ }
770
+ continue;
771
+ }
772
+ const lintMatch = trimmedLine.match(patterns.lintError);
773
+ if (lintMatch) {
774
+ line = parseInt(lintMatch[1], 10);
775
+ column = parseInt(lintMatch[2], 10);
776
+ errorType = lintMatch[3] === "error" ? "LintError" : "LintWarning";
777
+ message = lintMatch[4];
778
+ continue;
779
+ }
780
+ if (!file) {
781
+ const genericMatch = trimmedLine.match(patterns.genericFileLine);
782
+ if (genericMatch && !isLibraryFrame(genericMatch[1])) {
783
+ file = genericMatch[1];
784
+ line = parseInt(genericMatch[2], 10);
785
+ column = genericMatch[3] ? parseInt(genericMatch[3], 10) : null;
786
+ }
787
+ }
788
+ if (errorType !== "Unknown" && !message && trimmedLine && !trimmedLine.startsWith("at ")) {
789
+ message = trimmedLine;
790
+ }
791
+ }
792
+ if (!message) {
793
+ message = lines.find((l) => l.trim())?.trim() || "No error message found";
794
+ }
795
+ const rawExcerpt = lines.slice(0, 10).join("\n");
796
+ return {
797
+ error_type: errorType,
798
+ file,
799
+ line,
800
+ column,
801
+ message,
802
+ stack_summary: stackFrames.slice(0, 5),
803
+ // Limit to 5 frames
804
+ raw_excerpt: rawExcerpt
805
+ };
806
+ }
807
+ function isLibraryFrame(filePath) {
808
+ const libraryPatterns = [
809
+ /node_modules/,
810
+ /site-packages/,
811
+ /dist-packages/,
812
+ /\.pnpm/,
813
+ /\.yarn/,
814
+ /internal\//,
815
+ /^node:/,
816
+ /<anonymous>/,
817
+ /webpack-internal/,
818
+ /react-dom/,
819
+ /react-native/,
820
+ /expo/,
821
+ /metro/,
822
+ /hermes/
823
+ ];
824
+ return libraryPatterns.some((pattern) => pattern.test(filePath));
825
+ }
826
+ function summarizeErrors(logText, maxLines = 100) {
827
+ const errorTypePatterns = [
828
+ { type: "TypeError", pattern: /TypeError:\s*(.+)/i },
829
+ { type: "SyntaxError", pattern: /SyntaxError:\s*(.+)/i },
830
+ { type: "ReferenceError", pattern: /ReferenceError:\s*(.+)/i },
831
+ { type: "RangeError", pattern: /RangeError:\s*(.+)/i },
832
+ { type: "EvalError", pattern: /EvalError:\s*(.+)/i },
833
+ { type: "URIError", pattern: /URIError:\s*(.+)/i },
834
+ { type: "AggregateError", pattern: /AggregateError:\s*(.+)/i },
835
+ { type: "InternalError", pattern: /InternalError:\s*(.+)/i },
836
+ { type: "AssertionError", pattern: /AssertionError:\s*(.+)/i },
837
+ { type: "ValidationError", pattern: /ValidationError:\s*(.+)/i },
838
+ { type: "NetworkError", pattern: /NetworkError:\s*(.+)/i },
839
+ { type: "TimeoutError", pattern: /TimeoutError:\s*(.+)/i },
840
+ { type: "ENOENT", pattern: /ENOENT:\s*(.+)/i },
841
+ { type: "EACCES", pattern: /EACCES:\s*(.+)/i },
842
+ { type: "ECONNREFUSED", pattern: /ECONNREFUSED:\s*(.+)/i },
843
+ { type: "ETIMEDOUT", pattern: /ETIMEDOUT:\s*(.+)/i },
844
+ { type: "ModuleNotFoundError", pattern: /ModuleNotFoundError:\s*(.+)/i },
845
+ { type: "ImportError", pattern: /ImportError:\s*(.+)/i },
846
+ { type: "AttributeError", pattern: /AttributeError:\s*(.+)/i },
847
+ { type: "KeyError", pattern: /KeyError:\s*(.+)/i },
848
+ { type: "ValueError", pattern: /ValueError:\s*(.+)/i },
849
+ { type: "IndexError", pattern: /IndexError:\s*(.+)/i },
850
+ { type: "Error", pattern: /(?:^|\s)Error:\s*(.+)/i },
851
+ { type: "Warning", pattern: /(?:^|\s)(?:Warning|WARN):\s*(.+)/i },
852
+ { type: "Failed", pattern: /(?:FAILED|Failed|failed):\s*(.+)/i },
853
+ { type: "Exception", pattern: /Exception:\s*(.+)/i }
854
+ ];
855
+ const lines = logText.split("\n");
856
+ const totalLines = lines.length;
857
+ const errorMap = /* @__PURE__ */ new Map();
858
+ let totalErrorCount = 0;
859
+ const filePathPattern = /(?:at\s+(?:.*?\s+\()?)?([A-Za-z]:)?([/\\](?!node_modules)[^\s:()]+\.[a-z]+)(?::(\d+))?(?::(\d+))?/gi;
860
+ const nodeModulesPattern = /node_modules/i;
861
+ function normalizeMessage(msg) {
862
+ 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(
863
+ /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/gi,
864
+ "<uuid>"
865
+ ).replace(/\s+/g, " ").trim().substring(0, 200);
866
+ }
867
+ function extractAppFiles(line) {
868
+ const files = [];
869
+ const matches = line.matchAll(filePathPattern);
870
+ for (const match of matches) {
871
+ const fullPath = (match[1] || "") + match[2];
872
+ if (!nodeModulesPattern.test(fullPath)) {
873
+ const lineNum = match[3] ? `:${match[3]}` : "";
874
+ files.push(fullPath + lineNum);
875
+ }
876
+ }
877
+ return files;
878
+ }
879
+ for (let i = 0; i < lines.length; i++) {
880
+ const line = lines[i];
881
+ if (!line.trim()) continue;
882
+ for (const { type, pattern } of errorTypePatterns) {
883
+ const match = line.match(pattern);
884
+ if (match) {
885
+ totalErrorCount++;
886
+ const message = match[1] || line;
887
+ const normalized = normalizeMessage(message);
888
+ const key = `${type}:${normalized}`;
889
+ const contextLines = lines.slice(i, Math.min(i + 6, lines.length));
890
+ const files = /* @__PURE__ */ new Set();
891
+ for (const contextLine of contextLines) {
892
+ extractAppFiles(contextLine).forEach((f) => files.add(f));
893
+ }
894
+ if (errorMap.has(key)) {
895
+ const existing = errorMap.get(key);
896
+ existing.count++;
897
+ files.forEach((f) => existing.files.add(f));
898
+ } else {
899
+ let example = line;
900
+ for (let j = i + 1; j < Math.min(i + 6, lines.length); j++) {
901
+ const stackLine = lines[j];
902
+ if (stackLine.trim().startsWith("at ") && !nodeModulesPattern.test(stackLine)) {
903
+ example += "\n" + stackLine;
904
+ }
905
+ }
906
+ errorMap.set(key, {
907
+ type,
908
+ message: message.substring(0, 150),
909
+ normalizedMessage: normalized,
910
+ files,
911
+ count: 1,
912
+ firstOccurrence: i + 1,
913
+ example: example.substring(0, 500)
914
+ });
915
+ }
916
+ break;
917
+ }
918
+ }
919
+ }
920
+ const groupedErrors = Array.from(errorMap.values()).sort((a, b) => b.count - a.count).map((e) => ({
921
+ type: e.type,
922
+ count: e.count,
923
+ example: e.example,
924
+ files: Array.from(e.files).slice(0, 5)
925
+ // Limit files per error
926
+ }));
927
+ const summaryLines = [];
928
+ summaryLines.push(`=== ERROR SUMMARY ===`);
929
+ summaryLines.push(`Total lines processed: ${totalLines}`);
930
+ summaryLines.push(`Total errors found: ${totalErrorCount}`);
931
+ summaryLines.push(`Unique error patterns: ${groupedErrors.length}`);
932
+ summaryLines.push("");
933
+ const typeCount = /* @__PURE__ */ new Map();
934
+ for (const err of groupedErrors) {
935
+ typeCount.set(err.type, (typeCount.get(err.type) || 0) + err.count);
936
+ }
937
+ summaryLines.push("--- Errors by Type ---");
938
+ for (const [type, count] of Array.from(typeCount.entries()).sort(
939
+ (a, b) => b[1] - a[1]
940
+ )) {
941
+ summaryLines.push(` ${type}: ${count}`);
942
+ }
943
+ summaryLines.push("");
944
+ summaryLines.push("--- Top Errors (by frequency) ---");
945
+ let currentLines = summaryLines.length;
946
+ const maxDetailedErrors = Math.min(groupedErrors.length, 20);
947
+ for (let i = 0; i < maxDetailedErrors && currentLines < maxLines - 5; i++) {
948
+ const err = groupedErrors[i];
949
+ summaryLines.push("");
950
+ summaryLines.push(`[${i + 1}] ${err.type} (x${err.count})`);
951
+ const exampleLines = err.example.split("\n");
952
+ for (const exLine of exampleLines) {
953
+ if (currentLines >= maxLines - 3) break;
954
+ summaryLines.push(` ${exLine}`);
955
+ currentLines++;
956
+ }
957
+ if (err.files.length > 0) {
958
+ summaryLines.push(` Files: ${err.files.join(", ")}`);
959
+ }
960
+ currentLines = summaryLines.length;
961
+ }
962
+ const summary = summaryLines.slice(0, maxLines).join("\n");
963
+ return {
964
+ summary,
965
+ error_count: totalErrorCount,
966
+ unique_errors: groupedErrors.length,
967
+ grouped_errors: groupedErrors.slice(0, 50)
968
+ // Limit to top 50
969
+ };
970
+ }
356
971
 
357
972
  // src/resources/index.ts
358
973
  function setupResources(taskOps) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibetasks/mcp-server",
3
- "version": "0.2.1",
3
+ "version": "0.3.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.3.0",
49
49
  "zod": "^3.22.0"
50
50
  },
51
51
  "devDependencies": {