@vibetasks/mcp-server 0.6.0 → 0.6.2

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 +972 -73
  2. package/package.json +6 -5
package/dist/index.js CHANGED
@@ -19,13 +19,17 @@ import {
19
19
  containsError
20
20
  } from "@vibetasks/shared";
21
21
  import { z } from "zod";
22
+ import { zodToJsonSchema } from "zod-to-json-schema";
23
+ function toJsonSchema(zodSchema) {
24
+ return zodToJsonSchema(zodSchema, { $refStrategy: "none" });
25
+ }
22
26
  function setupTools(taskOps) {
23
- return [
27
+ const toolsWithZod = [
24
28
  // create_task
25
29
  {
26
30
  name: "create_task",
27
- description: "Create a new task in VibeTasks. Use this instead of TodoWrite - tasks persist across sessions and sync everywhere. If you plan to work on it immediately, set start_vibing=true.",
28
- inputSchema: z.object({
31
+ description: "Create a new task in VibeTasks. Use this instead of TodoWrite - tasks persist across sessions and sync everywhere.",
32
+ zodSchema: z.object({
29
33
  title: z.string().describe("Task title (required)"),
30
34
  description: z.string().optional().describe("Brief description of what this task involves"),
31
35
  notes: z.string().optional().describe("Detailed notes in markdown"),
@@ -37,7 +41,7 @@ function setupTools(taskOps) {
37
41
  due_date: z.string().optional().describe("Due date (ISO 8601 format)"),
38
42
  priority: z.enum(["none", "low", "medium", "high"]).default("none").describe("Task priority"),
39
43
  tags: z.array(z.string()).optional().describe("Tag names to attach"),
40
- start_vibing: z.boolean().default(false).describe(`If true, immediately set task to "vibing" status (you're starting work on it now)`)
44
+ session_id: z.string().optional().describe("AI session ID for tracking task ownership. Pass this to link task to your session.")
41
45
  }),
42
46
  handler: async (args, taskOps2) => {
43
47
  let tagIds = [];
@@ -60,15 +64,14 @@ function setupTools(taskOps) {
60
64
  notes_format: "markdown",
61
65
  due_date: args.due_date,
62
66
  priority: args.priority,
63
- subtasks_json: subtasksJson
67
+ subtasks_json: subtasksJson,
68
+ session_id: args.session_id,
69
+ created_by: "ai"
70
+ // Tasks created via MCP are always AI-created
64
71
  });
65
72
  if (tagIds.length > 0) {
66
73
  await taskOps2.linkTaskTags(task.id, tagIds);
67
74
  }
68
- let finalTask = task;
69
- if (args.start_vibing) {
70
- finalTask = await taskOps2.updateTask(task.id, { status: "vibing" });
71
- }
72
75
  return {
73
76
  content: [
74
77
  {
@@ -77,16 +80,14 @@ function setupTools(taskOps) {
77
80
  {
78
81
  success: true,
79
82
  task: {
80
- id: finalTask.id,
81
- title: finalTask.title,
82
- description: finalTask.description,
83
- priority: finalTask.priority,
84
- due_date: finalTask.due_date,
85
- status: finalTask.status,
83
+ id: task.id,
84
+ title: task.title,
85
+ description: task.description,
86
+ priority: task.priority,
87
+ due_date: task.due_date,
86
88
  subtasks: subtasksJson,
87
- created_at: finalTask.created_at
88
- },
89
- message: args.start_vibing ? "Task created and set to vibing status - you're now working on it!" : "Task created successfully"
89
+ created_at: task.created_at
90
+ }
90
91
  },
91
92
  null,
92
93
  2
@@ -100,7 +101,7 @@ function setupTools(taskOps) {
100
101
  {
101
102
  name: "get_tasks",
102
103
  description: "Get tasks by filter",
103
- inputSchema: z.object({
104
+ zodSchema: z.object({
104
105
  filter: z.enum(["all", "today", "upcoming", "completed"]).default("all").describe("Task filter")
105
106
  }),
106
107
  handler: async (args, taskOps2) => {
@@ -134,7 +135,7 @@ function setupTools(taskOps) {
134
135
  {
135
136
  name: "update_task",
136
137
  description: "Update an existing task",
137
- inputSchema: z.object({
138
+ zodSchema: z.object({
138
139
  task_id: z.string().describe("Task ID"),
139
140
  title: z.string().optional().describe("New title"),
140
141
  notes: z.string().optional().describe("New notes (user-facing description)"),
@@ -179,7 +180,7 @@ function setupTools(taskOps) {
179
180
  {
180
181
  name: "complete_task",
181
182
  description: "Mark a task as complete, optionally adding completion notes",
182
- inputSchema: z.object({
183
+ zodSchema: z.object({
183
184
  task_id: z.string().describe("Task ID"),
184
185
  context_notes: z.string().optional().describe("Completion notes - what was done, any final notes")
185
186
  }),
@@ -215,7 +216,7 @@ function setupTools(taskOps) {
215
216
  {
216
217
  name: "delete_task",
217
218
  description: "Delete a task",
218
- inputSchema: z.object({
219
+ zodSchema: z.object({
219
220
  task_id: z.string().describe("Task ID")
220
221
  }),
221
222
  handler: async (args, taskOps2) => {
@@ -241,7 +242,7 @@ function setupTools(taskOps) {
241
242
  {
242
243
  name: "search_tasks",
243
244
  description: "Search tasks by title",
244
- inputSchema: z.object({
245
+ zodSchema: z.object({
245
246
  query: z.string().describe("Search query"),
246
247
  limit: z.number().default(20).describe("Maximum results")
247
248
  }),
@@ -274,15 +275,14 @@ function setupTools(taskOps) {
274
275
  // create_task_with_subtasks
275
276
  {
276
277
  name: "create_task_with_subtasks",
277
- description: "Create a parent task with multiple subtasks in one call. Use this when using TodoWrite - it automatically mirrors your todo list to TaskFlow for persistent tracking. Set start_vibing=true if you're starting work immediately.",
278
- inputSchema: z.object({
278
+ description: "Create a parent task with multiple subtasks in one call. Use this when using TodoWrite - it automatically mirrors your todo list to TaskFlow for persistent tracking.",
279
+ zodSchema: z.object({
279
280
  title: z.string().describe("Parent task title (the overall goal)"),
280
281
  subtasks: z.array(z.string()).describe("Array of subtask titles (one for each todo item)"),
281
282
  notes: z.string().optional().describe("Notes for parent task"),
282
283
  due_date: z.string().optional().describe("Due date for parent task (ISO 8601 format)"),
283
284
  priority: z.enum(["none", "low", "medium", "high"]).default("none").describe("Priority for parent task"),
284
- tags: z.array(z.string()).optional().describe("Tag names to attach to parent task"),
285
- start_vibing: z.boolean().default(false).describe(`If true, immediately set parent task to "vibing" status (you're starting work on it now)`)
285
+ tags: z.array(z.string()).optional().describe("Tag names to attach to parent task")
286
286
  }),
287
287
  handler: async (args, taskOps2) => {
288
288
  let tagIds = [];
@@ -312,10 +312,6 @@ function setupTools(taskOps) {
312
312
  });
313
313
  subtasks.push(subtask);
314
314
  }
315
- let finalParentTask = parentTask;
316
- if (args.start_vibing) {
317
- finalParentTask = await taskOps2.updateTask(parentTask.id, { status: "vibing" });
318
- }
319
315
  return {
320
316
  content: [
321
317
  {
@@ -324,17 +320,15 @@ function setupTools(taskOps) {
324
320
  {
325
321
  success: true,
326
322
  parent_task: {
327
- id: finalParentTask.id,
328
- title: finalParentTask.title,
329
- priority: finalParentTask.priority,
330
- status: finalParentTask.status,
323
+ id: parentTask.id,
324
+ title: parentTask.title,
325
+ priority: parentTask.priority,
331
326
  subtask_count: subtasks.length
332
327
  },
333
328
  subtasks: subtasks.map((s) => ({
334
329
  id: s.id,
335
330
  title: s.title
336
- })),
337
- message: args.start_vibing ? "Parent task created and set to vibing status - you're now working on it!" : "Parent task and subtasks created successfully"
331
+ }))
338
332
  },
339
333
  null,
340
334
  2
@@ -348,7 +342,7 @@ function setupTools(taskOps) {
348
342
  {
349
343
  name: "start_vibing",
350
344
  description: 'Start vibing on a task (move to "vibing" status). Max 3 tasks vibing at once - research shows more causes 40% productivity loss.',
351
- inputSchema: z.object({
345
+ zodSchema: z.object({
352
346
  task_id: z.string().describe("Task ID to start vibing on")
353
347
  }),
354
348
  handler: async (args, taskOps2) => {
@@ -402,7 +396,7 @@ function setupTools(taskOps) {
402
396
  {
403
397
  name: "stop_vibing",
404
398
  description: 'Stop vibing on a task (move back to "todo" status). Use this when pausing work on a task to work on something else.',
405
- inputSchema: z.object({
399
+ zodSchema: z.object({
406
400
  task_id: z.string().describe("Task ID to stop vibing on"),
407
401
  context_notes: z.string().optional().describe("Optional: Save where you left off before stopping")
408
402
  }),
@@ -438,12 +432,17 @@ function setupTools(taskOps) {
438
432
  // get_vibing_tasks
439
433
  {
440
434
  name: "get_vibing_tasks",
441
- description: 'Get all tasks currently in "vibing" status. Use this to check what the user is actively working on.',
442
- inputSchema: z.object({}),
435
+ description: 'Get all tasks currently in "vibing" status. Use this to check what the user is actively working on. Shows session_id to identify which AI session owns each task.',
436
+ zodSchema: z.object({
437
+ session_id: z.string().optional().describe("Filter by session ID to see only your tasks")
438
+ }),
443
439
  handler: async (args, taskOps2) => {
444
440
  const allTasks = await taskOps2.getTasks("all");
445
- const vibingTasks = allTasks.filter((t) => t.status === "vibing");
441
+ let vibingTasks = allTasks.filter((t) => t.status === "vibing");
446
442
  const WIP_LIMIT = 3;
443
+ if (args.session_id) {
444
+ vibingTasks = vibingTasks.filter((t) => t.session_id === args.session_id);
445
+ }
447
446
  return {
448
447
  content: [
449
448
  {
@@ -456,11 +455,14 @@ function setupTools(taskOps) {
456
455
  title: t.title,
457
456
  context_notes: t.context_notes,
458
457
  priority: t.priority,
459
- due_date: t.due_date
458
+ due_date: t.due_date,
459
+ session_id: t.session_id,
460
+ created_by: t.created_by
460
461
  })),
461
462
  count: vibingTasks.length,
462
463
  wip_limit: WIP_LIMIT,
463
- at_limit: vibingTasks.length >= WIP_LIMIT
464
+ at_limit: vibingTasks.length >= WIP_LIMIT,
465
+ filtered_by_session: args.session_id || null
464
466
  },
465
467
  null,
466
468
  2
@@ -474,7 +476,7 @@ function setupTools(taskOps) {
474
476
  {
475
477
  name: "set_context_notes",
476
478
  description: 'Save "where I left off" notes for a task. Use this before pausing work or ending a session to capture context for later.',
477
- inputSchema: z.object({
479
+ zodSchema: z.object({
478
480
  task_id: z.string().describe("Task ID"),
479
481
  context_notes: z.string().describe("Where you left off - file, line, next step, blockers")
480
482
  }),
@@ -508,7 +510,7 @@ function setupTools(taskOps) {
508
510
  {
509
511
  name: "list_tasks",
510
512
  description: "List tasks filtered by status (todo, vibing, done) or all tasks",
511
- inputSchema: z.object({
513
+ zodSchema: z.object({
512
514
  status: z.enum(["todo", "vibing", "done", "all"]).default("all").describe("Filter by status"),
513
515
  limit: z.number().default(50).describe("Maximum results")
514
516
  }),
@@ -550,7 +552,7 @@ function setupTools(taskOps) {
550
552
  {
551
553
  name: "log_ai_session",
552
554
  description: "Manually log an AI session as a completed task",
553
- inputSchema: z.object({
555
+ zodSchema: z.object({
554
556
  summary: z.string().describe("What was accomplished"),
555
557
  files: z.array(z.string()).optional().describe("Files changed"),
556
558
  duration_minutes: z.number().optional().describe("Session duration")
@@ -598,7 +600,7 @@ Generated by TaskFlow MCP Server`;
598
600
  {
599
601
  name: "archive_task",
600
602
  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.",
601
- inputSchema: z.object({
603
+ zodSchema: z.object({
602
604
  task_id: z.string().describe("Task ID to archive")
603
605
  }),
604
606
  handler: async (args, taskOps2) => {
@@ -630,7 +632,7 @@ Generated by TaskFlow MCP Server`;
630
632
  {
631
633
  name: "get_archived_tasks",
632
634
  description: "Get all archived tasks. Returns tasks that have been archived, sorted by archive date (most recent first).",
633
- inputSchema: z.object({
635
+ zodSchema: z.object({
634
636
  limit: z.number().default(50).describe("Maximum number of archived tasks to return")
635
637
  }),
636
638
  handler: async (args, taskOps2) => {
@@ -664,7 +666,7 @@ Generated by TaskFlow MCP Server`;
664
666
  {
665
667
  name: "unarchive_task",
666
668
  description: "Restore an archived task. Moves the task from archived status back to done (or optionally another status).",
667
- inputSchema: z.object({
669
+ zodSchema: z.object({
668
670
  task_id: z.string().describe("Task ID to unarchive"),
669
671
  restore_status: z.enum(["todo", "vibing", "done"]).default("done").describe("Status to restore the task to (default: done)")
670
672
  }),
@@ -696,7 +698,7 @@ Generated by TaskFlow MCP Server`;
696
698
  {
697
699
  name: "capture_error",
698
700
  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.",
699
- inputSchema: z.object({
701
+ zodSchema: z.object({
700
702
  error_text: z.string().describe("The raw error text to parse"),
701
703
  source: z.enum(["terminal", "browser", "paste", "clipboard", "sentry", "ci"]).default("paste").describe("Source of the error (helps with parsing)")
702
704
  }),
@@ -743,7 +745,7 @@ Generated by TaskFlow MCP Server`;
743
745
  {
744
746
  name: "summarize_errors",
745
747
  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).",
746
- inputSchema: z.object({
748
+ zodSchema: z.object({
747
749
  log_text: z.string().describe("The raw log output containing errors"),
748
750
  max_lines: z.number().default(100).describe("Maximum lines in the summary output (default: 100)")
749
751
  }),
@@ -790,8 +792,8 @@ Generated by TaskFlow MCP Server`;
790
792
  // update_subtask
791
793
  {
792
794
  name: "update_subtask",
793
- description: "\u26A1 CRITICAL: Mark subtasks done IMMEDIATELY after completing each one. This tool updates a subtask - use it to mark done=true RIGHT AFTER you finish each step (not all at once!). Also for adding progress notes or updating titles.",
794
- inputSchema: z.object({
795
+ 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.",
796
+ zodSchema: z.object({
795
797
  task_id: z.string().describe("Parent task ID"),
796
798
  subtask_id: z.string().describe("Subtask ID to update"),
797
799
  done: z.boolean().optional().describe("Mark subtask as done/not done"),
@@ -816,12 +818,6 @@ Generated by TaskFlow MCP Server`;
816
818
  if (args.title !== void 0) subtasks[subtaskIndex].title = args.title;
817
819
  if (args.notes !== void 0) subtasks[subtaskIndex].notes = args.notes;
818
820
  const updated = await taskOps2.updateTask(args.task_id, { subtasks_json: subtasks });
819
- const doneCount = subtasks.filter((s) => s.done).length;
820
- const totalCount = subtasks.length;
821
- const percentage = Math.round(doneCount / totalCount * 100);
822
- const barLength = 20;
823
- const filled = Math.round(doneCount / totalCount * barLength);
824
- const progressBar = "\u2588".repeat(filled) + "\u2591".repeat(barLength - filled);
825
821
  return {
826
822
  content: [
827
823
  {
@@ -830,10 +826,9 @@ Generated by TaskFlow MCP Server`;
830
826
  {
831
827
  success: true,
832
828
  task_id: args.task_id,
833
- subtask_updated: subtasks[subtaskIndex],
834
- progress: `${doneCount}/${totalCount} subtasks completed (${percentage}%)`,
835
- progress_bar: `[${progressBar}] ${percentage}%`,
836
- message: args.done ? `\u2713 Subtask marked done! ${doneCount}/${totalCount} complete.${doneCount === totalCount ? " \u{1F389} All subtasks complete!" : ""}` : `Subtask updated. ${doneCount}/${totalCount} complete.`
829
+ subtask: subtasks[subtaskIndex],
830
+ all_subtasks: subtasks,
831
+ progress: `${subtasks.filter((s) => s.done).length}/${subtasks.length} done`
837
832
  },
838
833
  null,
839
834
  2
@@ -847,7 +842,7 @@ Generated by TaskFlow MCP Server`;
847
842
  {
848
843
  name: "add_subtask",
849
844
  description: "Add a new subtask to an existing task. Use this when you discover new steps while working.",
850
- inputSchema: z.object({
845
+ zodSchema: z.object({
851
846
  task_id: z.string().describe("Parent task ID"),
852
847
  title: z.string().describe("Subtask title"),
853
848
  notes: z.string().optional().describe("Optional notes")
@@ -886,7 +881,7 @@ Generated by TaskFlow MCP Server`;
886
881
  {
887
882
  name: "get_task_images",
888
883
  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.",
889
- inputSchema: z.object({
884
+ zodSchema: z.object({
890
885
  task_id: z.string().describe("Task ID to get images for")
891
886
  }),
892
887
  handler: async (args, taskOps2) => {
@@ -930,7 +925,7 @@ Generated by TaskFlow MCP Server`;
930
925
  {
931
926
  name: "get_current_task",
932
927
  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.',
933
- inputSchema: z.object({}),
928
+ zodSchema: z.object({}),
934
929
  handler: async (_args, taskOps2) => {
935
930
  const allTasks = await taskOps2.getTasks("all");
936
931
  const vibingTasks = allTasks.filter((t) => t.status === "vibing");
@@ -984,7 +979,7 @@ Generated by TaskFlow MCP Server`;
984
979
  {
985
980
  name: "report_error",
986
981
  description: "Report an error you encountered while working. Creates a high-priority task to track and fix it. Use this when you hit a build error, runtime error, or any issue that blocks progress.",
987
- inputSchema: z.object({
982
+ zodSchema: z.object({
988
983
  error_text: z.string().describe("The full error message including stack trace"),
989
984
  context: z.string().optional().describe("What you were trying to do when this error occurred"),
990
985
  file_path: z.string().optional().describe("File path where the error occurred (if known)"),
@@ -1068,7 +1063,7 @@ ${errorText}
1068
1063
  {
1069
1064
  name: "get_errors",
1070
1065
  description: '[DEPRECATED - use get_inbox with source="errors" instead] Get current errors/bugs. This is a legacy tool - prefer get_inbox for unified inbox access.',
1071
- inputSchema: z.object({
1066
+ zodSchema: z.object({
1072
1067
  priority: z.enum(["critical", "high", "all"]).default("all").describe("Filter by priority"),
1073
1068
  source: z.enum(["sentry", "ci", "manual", "all"]).default("all").describe("Filter by source"),
1074
1069
  unacknowledged_only: z.boolean().default(true).describe("Only show errors you haven't acknowledged yet"),
@@ -1115,7 +1110,7 @@ ${errorText}
1115
1110
  {
1116
1111
  name: "get_error_details",
1117
1112
  description: "Get full details for a specific error including parsed error context, notes, stack trace, and file context. Use after get_inbox to dive into a specific issue.",
1118
- inputSchema: z.object({
1113
+ zodSchema: z.object({
1119
1114
  error_id: z.string().describe("Error task ID")
1120
1115
  }),
1121
1116
  handler: async (args, taskOps2) => {
@@ -1175,7 +1170,7 @@ ${errorText}
1175
1170
  {
1176
1171
  name: "acknowledge_error",
1177
1172
  description: "Mark an error as acknowledged so it doesn't keep appearing. Use this after you've investigated or fixed an error.",
1178
- inputSchema: z.object({
1173
+ zodSchema: z.object({
1179
1174
  error_id: z.string().describe("Error task ID"),
1180
1175
  action: z.enum(["investigating", "fixed", "wont_fix", "need_info", "delegated"]).describe("What action was taken"),
1181
1176
  notes: z.string().optional().describe("Optional notes about what was done")
@@ -1225,7 +1220,7 @@ ${ackNote}`;
1225
1220
  {
1226
1221
  name: "get_inbox",
1227
1222
  description: "Get inbox items from all external sources (Sentry errors, GitHub issues, email, Slack, CI failures). Use this to see what needs attention.",
1228
- inputSchema: z.object({
1223
+ zodSchema: z.object({
1229
1224
  source: z.enum(["all", "sentry", "ci", "github", "email", "slack", "errors", "feedback"]).default("all").describe("Filter by source: all, sentry, ci, github, email, slack, errors (sentry+ci), feedback (github+email+slack)"),
1230
1225
  priority: z.enum(["all", "high", "medium", "low"]).default("all").describe("Filter by priority"),
1231
1226
  unacknowledged_only: z.boolean().default(true).describe("Only show items not yet acknowledged"),
@@ -1301,7 +1296,7 @@ ${ackNote}`;
1301
1296
  {
1302
1297
  name: "get_inbox_stats",
1303
1298
  description: "Get a quick summary of inbox status - counts by source and priority. Check this first to understand what needs attention.",
1304
- inputSchema: z.object({}),
1299
+ zodSchema: z.object({}),
1305
1300
  handler: async (_args, taskOps2) => {
1306
1301
  const stats = await taskOps2.getInboxStats();
1307
1302
  return {
@@ -1321,8 +1316,912 @@ ${ackNote}`;
1321
1316
  ]
1322
1317
  };
1323
1318
  }
1319
+ },
1320
+ // =============================================================================
1321
+ // SESSION MANAGEMENT TOOLS
1322
+ // =============================================================================
1323
+ // session_start - Start a new AI session
1324
+ {
1325
+ name: "session_start",
1326
+ description: "Start a new AI session for tracking work. Returns a memorable session ID. Call this at the beginning of a conversation to enable session tracking and crash recovery.",
1327
+ zodSchema: z.object({
1328
+ project: z.string().optional().describe("Project name (auto-detected if not provided)")
1329
+ }),
1330
+ handler: async (args, _taskOps) => {
1331
+ const adjectives = ["swift", "calm", "bold", "warm", "cool", "bright", "quick", "wise", "keen", "soft"];
1332
+ const nouns = ["fox", "owl", "bear", "wolf", "hawk", "moon", "star", "sun", "tree", "wave"];
1333
+ const adj = adjectives[Math.floor(Math.random() * adjectives.length)];
1334
+ const noun = nouns[Math.floor(Math.random() * nouns.length)];
1335
+ const num = Math.floor(Math.random() * 100);
1336
+ const sessionId = `${adj}-${noun}-${num}`;
1337
+ process.env.VIBETASKS_SESSION_ID = sessionId;
1338
+ process.env.VIBETASKS_SESSION_START = (/* @__PURE__ */ new Date()).toISOString();
1339
+ process.env.VIBETASKS_SESSION_PROJECT = args.project || "";
1340
+ return {
1341
+ content: [
1342
+ {
1343
+ type: "text",
1344
+ text: JSON.stringify(
1345
+ {
1346
+ success: true,
1347
+ session: {
1348
+ id: sessionId,
1349
+ started_at: process.env.VIBETASKS_SESSION_START,
1350
+ project: args.project || null
1351
+ },
1352
+ message: `Session "${sessionId}" started. Use this ID to continue work after a crash.`,
1353
+ tip: "If the AI crashes, tell the next one to continue from this session."
1354
+ },
1355
+ null,
1356
+ 2
1357
+ )
1358
+ }
1359
+ ]
1360
+ };
1361
+ }
1362
+ },
1363
+ // session_current - Get current session info
1364
+ {
1365
+ name: "session_current",
1366
+ description: "Get the current AI session information. Returns session ID, start time, and project. Use this to check if a session is active.",
1367
+ zodSchema: z.object({}),
1368
+ handler: async (_args, _taskOps) => {
1369
+ const sessionId = process.env.VIBETASKS_SESSION_ID;
1370
+ const startTime = process.env.VIBETASKS_SESSION_START;
1371
+ const project = process.env.VIBETASKS_SESSION_PROJECT;
1372
+ if (!sessionId) {
1373
+ return {
1374
+ content: [
1375
+ {
1376
+ type: "text",
1377
+ text: JSON.stringify(
1378
+ {
1379
+ success: true,
1380
+ session: null,
1381
+ message: "No active session. Use session_start to begin a new session."
1382
+ },
1383
+ null,
1384
+ 2
1385
+ )
1386
+ }
1387
+ ]
1388
+ };
1389
+ }
1390
+ const durationMs = startTime ? Date.now() - new Date(startTime).getTime() : 0;
1391
+ const durationMinutes = Math.floor(durationMs / 6e4);
1392
+ return {
1393
+ content: [
1394
+ {
1395
+ type: "text",
1396
+ text: JSON.stringify(
1397
+ {
1398
+ success: true,
1399
+ session: {
1400
+ id: sessionId,
1401
+ started_at: startTime,
1402
+ project: project || null,
1403
+ duration_minutes: durationMinutes,
1404
+ status: "active"
1405
+ }
1406
+ },
1407
+ null,
1408
+ 2
1409
+ )
1410
+ }
1411
+ ]
1412
+ };
1413
+ }
1414
+ },
1415
+ // session_end - End the current session
1416
+ {
1417
+ name: "session_end",
1418
+ description: "End the current AI session with an optional summary. Use this at the end of a conversation to mark the session as complete.",
1419
+ zodSchema: z.object({
1420
+ summary: z.string().optional().describe("Summary of what was accomplished in this session"),
1421
+ handoff_notes: z.string().optional().describe("Notes for the next AI to continue the work")
1422
+ }),
1423
+ handler: async (args, _taskOps) => {
1424
+ const sessionId = process.env.VIBETASKS_SESSION_ID;
1425
+ const startTime = process.env.VIBETASKS_SESSION_START;
1426
+ const project = process.env.VIBETASKS_SESSION_PROJECT;
1427
+ if (!sessionId) {
1428
+ return {
1429
+ content: [
1430
+ {
1431
+ type: "text",
1432
+ text: JSON.stringify(
1433
+ {
1434
+ success: false,
1435
+ error: "No active session to end"
1436
+ },
1437
+ null,
1438
+ 2
1439
+ )
1440
+ }
1441
+ ]
1442
+ };
1443
+ }
1444
+ const endTime = (/* @__PURE__ */ new Date()).toISOString();
1445
+ const durationMs = startTime ? new Date(endTime).getTime() - new Date(startTime).getTime() : 0;
1446
+ const durationMinutes = Math.floor(durationMs / 6e4);
1447
+ delete process.env.VIBETASKS_SESSION_ID;
1448
+ delete process.env.VIBETASKS_SESSION_START;
1449
+ delete process.env.VIBETASKS_SESSION_PROJECT;
1450
+ return {
1451
+ content: [
1452
+ {
1453
+ type: "text",
1454
+ text: JSON.stringify(
1455
+ {
1456
+ success: true,
1457
+ session: {
1458
+ id: sessionId,
1459
+ started_at: startTime,
1460
+ ended_at: endTime,
1461
+ project: project || null,
1462
+ duration_minutes: durationMinutes,
1463
+ summary: args.summary || null,
1464
+ handoff_notes: args.handoff_notes || null,
1465
+ status: "ended"
1466
+ },
1467
+ message: `Session "${sessionId}" ended after ${durationMinutes} minutes.`
1468
+ },
1469
+ null,
1470
+ 2
1471
+ )
1472
+ }
1473
+ ]
1474
+ };
1475
+ }
1476
+ },
1477
+ // session_log - Log an action or note to the current session
1478
+ {
1479
+ name: "session_log",
1480
+ description: "Log an action or note to the current session. Use this to record progress, decisions, or context that might be useful for crash recovery.",
1481
+ zodSchema: z.object({
1482
+ action_type: z.enum(["task_created", "task_completed", "task_updated", "file_modified", "note", "research_added", "error_captured"]).describe("Type of action being logged"),
1483
+ description: z.string().describe("Description of the action"),
1484
+ task_id: z.string().optional().describe("Related task ID (if applicable)"),
1485
+ file_path: z.string().optional().describe("Related file path (if applicable)")
1486
+ }),
1487
+ handler: async (args, _taskOps) => {
1488
+ const sessionId = process.env.VIBETASKS_SESSION_ID;
1489
+ if (!sessionId) {
1490
+ return {
1491
+ content: [
1492
+ {
1493
+ type: "text",
1494
+ text: JSON.stringify(
1495
+ {
1496
+ success: false,
1497
+ error: "No active session. Start one with session_start first."
1498
+ },
1499
+ null,
1500
+ 2
1501
+ )
1502
+ }
1503
+ ]
1504
+ };
1505
+ }
1506
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1507
+ return {
1508
+ content: [
1509
+ {
1510
+ type: "text",
1511
+ text: JSON.stringify(
1512
+ {
1513
+ success: true,
1514
+ logged: {
1515
+ session_id: sessionId,
1516
+ action_type: args.action_type,
1517
+ description: args.description,
1518
+ task_id: args.task_id || null,
1519
+ file_path: args.file_path || null,
1520
+ timestamp
1521
+ },
1522
+ message: "Action logged to session."
1523
+ },
1524
+ null,
1525
+ 2
1526
+ )
1527
+ }
1528
+ ]
1529
+ };
1530
+ }
1531
+ },
1532
+ // =============================================================================
1533
+ // RESEARCH CAPTURE TOOLS
1534
+ // =============================================================================
1535
+ // capture_research - Save research findings as a workspace doc
1536
+ {
1537
+ name: "capture_research",
1538
+ description: "Save research findings as a persistent workspace document. Use this when you explicitly research a topic and want to save the findings for future reference. The research doc will be available in the KnowledgeTab.",
1539
+ zodSchema: z.object({
1540
+ title: z.string().describe('Title for the research document (e.g., "React Server Components Best Practices")'),
1541
+ content: z.string().describe("Research findings in markdown format"),
1542
+ source_urls: z.array(z.string()).optional().describe("URLs that were researched"),
1543
+ search_queries: z.array(z.string()).optional().describe("Search queries used to find this information"),
1544
+ project: z.string().optional().describe("Project this research applies to (defaults to Sparktory)"),
1545
+ applies_to_children: z.boolean().default(true).describe("If true, this research will be available to child workspaces")
1546
+ }),
1547
+ handler: async (args, taskOps2) => {
1548
+ try {
1549
+ const doc = await taskOps2.createWorkspaceDoc({
1550
+ doc_type: "research",
1551
+ title: args.title,
1552
+ content: args.content,
1553
+ project_tag: args.project || "sparktory",
1554
+ created_by: "ai",
1555
+ source_urls: args.source_urls || [],
1556
+ search_queries: args.search_queries || [],
1557
+ applies_to_children: args.applies_to_children ?? true
1558
+ });
1559
+ const sessionId = process.env.VIBETASKS_SESSION_ID;
1560
+ return {
1561
+ content: [
1562
+ {
1563
+ type: "text",
1564
+ text: JSON.stringify(
1565
+ {
1566
+ success: true,
1567
+ research: {
1568
+ id: doc.id,
1569
+ title: args.title,
1570
+ project: doc.project_tag,
1571
+ source_count: args.source_urls?.length || 0,
1572
+ applies_to_children: args.applies_to_children ?? true
1573
+ },
1574
+ session_id: sessionId || null,
1575
+ message: `Research "${args.title}" saved to ${doc.project_tag} workspace.`,
1576
+ hint: "View in the Knowledge tab under Research docs."
1577
+ },
1578
+ null,
1579
+ 2
1580
+ )
1581
+ }
1582
+ ]
1583
+ };
1584
+ } catch (error) {
1585
+ return {
1586
+ content: [
1587
+ {
1588
+ type: "text",
1589
+ text: JSON.stringify(
1590
+ {
1591
+ success: false,
1592
+ error: error.message || "Failed to capture research"
1593
+ },
1594
+ null,
1595
+ 2
1596
+ )
1597
+ }
1598
+ ]
1599
+ };
1600
+ }
1601
+ }
1602
+ },
1603
+ // create_plan - Create a plan/roadmap with phases
1604
+ {
1605
+ name: "create_plan",
1606
+ description: "Create a plan or roadmap with phases/milestones as subtasks. Plans track progress based on subtask completion percentage. Use this for architecture decisions, feature roadmaps, or multi-phase projects.",
1607
+ zodSchema: z.object({
1608
+ title: z.string().describe('Plan title (e.g., "Q1 Feature Roadmap", "Auth System Architecture")'),
1609
+ description: z.string().optional().describe("Overview of what this plan covers"),
1610
+ phases: z.array(z.object({
1611
+ title: z.string().describe("Phase/milestone title"),
1612
+ notes: z.string().optional().describe("Details about this phase")
1613
+ })).describe("Phases or milestones of the plan"),
1614
+ project: z.string().optional().describe("Project this plan belongs to"),
1615
+ priority: z.enum(["none", "low", "medium", "high"]).default("medium").describe("Plan priority"),
1616
+ due_date: z.string().optional().describe("Target completion date (ISO 8601)")
1617
+ }),
1618
+ handler: async (args, taskOps2) => {
1619
+ try {
1620
+ const subtasksJson = args.phases.map((phase) => ({
1621
+ id: crypto.randomUUID(),
1622
+ title: phase.title,
1623
+ done: false,
1624
+ notes: phase.notes
1625
+ }));
1626
+ const plan = await taskOps2.createTask({
1627
+ title: args.title,
1628
+ description: args.description,
1629
+ notes: args.description ? `# ${args.title}
1630
+
1631
+ ${args.description}` : `# ${args.title}`,
1632
+ notes_format: "markdown",
1633
+ priority: args.priority,
1634
+ due_date: args.due_date,
1635
+ project_tag: args.project,
1636
+ subtasks_json: subtasksJson,
1637
+ is_plan: true,
1638
+ created_by: "ai"
1639
+ });
1640
+ const progress = `0/${args.phases.length} phases`;
1641
+ return {
1642
+ content: [
1643
+ {
1644
+ type: "text",
1645
+ text: JSON.stringify(
1646
+ {
1647
+ success: true,
1648
+ plan: {
1649
+ id: plan.id,
1650
+ title: plan.title,
1651
+ phases: subtasksJson.map((s) => ({ id: s.id, title: s.title })),
1652
+ phase_count: args.phases.length,
1653
+ progress,
1654
+ project: args.project || null,
1655
+ due_date: args.due_date || null
1656
+ },
1657
+ message: `Plan "${args.title}" created with ${args.phases.length} phases.`,
1658
+ hint: "Use update_subtask to mark phases complete as you progress."
1659
+ },
1660
+ null,
1661
+ 2
1662
+ )
1663
+ }
1664
+ ]
1665
+ };
1666
+ } catch (error) {
1667
+ return {
1668
+ content: [
1669
+ {
1670
+ type: "text",
1671
+ text: JSON.stringify(
1672
+ {
1673
+ success: false,
1674
+ error: error.message || "Failed to create plan"
1675
+ },
1676
+ null,
1677
+ 2
1678
+ )
1679
+ }
1680
+ ]
1681
+ };
1682
+ }
1683
+ }
1684
+ },
1685
+ // get_plans - List all plans
1686
+ {
1687
+ name: "get_plans",
1688
+ description: "Get all plans/roadmaps. Shows progress as percentage of completed phases.",
1689
+ zodSchema: z.object({
1690
+ project: z.string().optional().describe("Filter by project"),
1691
+ include_completed: z.boolean().default(false).describe("Include completed plans"),
1692
+ limit: z.number().default(20).describe("Maximum plans to return")
1693
+ }),
1694
+ handler: async (args, taskOps2) => {
1695
+ try {
1696
+ const allTasks = await taskOps2.getTasks("all");
1697
+ let plans = allTasks.filter((t) => t.is_plan === true);
1698
+ if (args.project) {
1699
+ plans = plans.filter(
1700
+ (t) => t.project_tag?.toLowerCase() === args.project?.toLowerCase()
1701
+ );
1702
+ }
1703
+ if (!args.include_completed) {
1704
+ plans = plans.filter((t) => !t.completed);
1705
+ }
1706
+ plans = plans.slice(0, args.limit);
1707
+ return {
1708
+ content: [
1709
+ {
1710
+ type: "text",
1711
+ text: JSON.stringify(
1712
+ {
1713
+ success: true,
1714
+ plans: plans.map((t) => {
1715
+ const subtasks = t.subtasks_json || [];
1716
+ const completed = subtasks.filter((s) => s.done).length;
1717
+ const total = subtasks.length;
1718
+ const percentage = total > 0 ? Math.round(completed / total * 100) : 0;
1719
+ return {
1720
+ id: t.id,
1721
+ title: t.title,
1722
+ project: t.project_tag,
1723
+ progress: `${completed}/${total} (${percentage}%)`,
1724
+ percentage,
1725
+ due_date: t.due_date,
1726
+ phases: subtasks.map((s) => ({
1727
+ id: s.id,
1728
+ title: s.title,
1729
+ done: s.done
1730
+ }))
1731
+ };
1732
+ }),
1733
+ count: plans.length,
1734
+ filter: { project: args.project, include_completed: args.include_completed }
1735
+ },
1736
+ null,
1737
+ 2
1738
+ )
1739
+ }
1740
+ ]
1741
+ };
1742
+ } catch (error) {
1743
+ return {
1744
+ content: [
1745
+ {
1746
+ type: "text",
1747
+ text: JSON.stringify(
1748
+ {
1749
+ success: false,
1750
+ error: error.message || "Failed to get plans"
1751
+ },
1752
+ null,
1753
+ 2
1754
+ )
1755
+ }
1756
+ ]
1757
+ };
1758
+ }
1759
+ }
1760
+ },
1761
+ // quick_checkpoint - Ultra-fast context save (Windsurf-inspired)
1762
+ {
1763
+ name: "quick_checkpoint",
1764
+ description: "Ultra-fast context checkpoint for the current vibing task. Auto-detects what changed and saves it. Use this frequently during work to create a crash-recovery trail. Zero-friction alternative to update_task.",
1765
+ zodSchema: z.object({
1766
+ completed: z.string().optional().describe("What you just completed (optional)"),
1767
+ next: z.string().optional().describe("What's next (optional)"),
1768
+ files_changed: z.array(z.string()).optional().describe("Files you modified (optional, will be auto-detected if git available)"),
1769
+ blocker: z.string().optional().describe("Any blocker encountered (optional)")
1770
+ }),
1771
+ handler: async (args, taskOps2) => {
1772
+ try {
1773
+ const allTasks = await taskOps2.getTasks("all");
1774
+ const vibingTasks = allTasks.filter((t) => t.status === "vibing");
1775
+ if (vibingTasks.length === 0) {
1776
+ return {
1777
+ content: [
1778
+ {
1779
+ type: "text",
1780
+ text: JSON.stringify(
1781
+ {
1782
+ success: false,
1783
+ error: "No task currently vibing. Use start_vibing first."
1784
+ },
1785
+ null,
1786
+ 2
1787
+ )
1788
+ }
1789
+ ]
1790
+ };
1791
+ }
1792
+ const task = vibingTasks[0];
1793
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1794
+ const parts = [`Checkpoint at ${timestamp}`];
1795
+ if (args.completed) {
1796
+ parts.push(`
1797
+ ## Completed
1798
+ ${args.completed}`);
1799
+ }
1800
+ if (args.files_changed && args.files_changed.length > 0) {
1801
+ parts.push(`
1802
+ ## Files Changed
1803
+ ${args.files_changed.map((f) => `- ${f}`).join("\n")}`);
1804
+ }
1805
+ if (args.next) {
1806
+ parts.push(`
1807
+ ## Next
1808
+ ${args.next}`);
1809
+ }
1810
+ if (args.blocker) {
1811
+ parts.push(`
1812
+ ## Blocker
1813
+ ${args.blocker}`);
1814
+ }
1815
+ const contextNotes = parts.join("\n");
1816
+ await taskOps2.updateTask(task.id, { context_notes: contextNotes });
1817
+ return {
1818
+ content: [
1819
+ {
1820
+ type: "text",
1821
+ text: JSON.stringify(
1822
+ {
1823
+ success: true,
1824
+ checkpointed: {
1825
+ task_id: task.id,
1826
+ task_title: task.title,
1827
+ timestamp,
1828
+ has_completed: !!args.completed,
1829
+ has_next: !!args.next,
1830
+ has_blocker: !!args.blocker,
1831
+ files_count: args.files_changed?.length || 0
1832
+ },
1833
+ message: "Checkpoint saved. Context preserved for crash recovery."
1834
+ },
1835
+ null,
1836
+ 2
1837
+ )
1838
+ }
1839
+ ]
1840
+ };
1841
+ } catch (error) {
1842
+ return {
1843
+ content: [
1844
+ {
1845
+ type: "text",
1846
+ text: JSON.stringify(
1847
+ {
1848
+ success: false,
1849
+ error: error.message || "Failed to checkpoint"
1850
+ },
1851
+ null,
1852
+ 2
1853
+ )
1854
+ }
1855
+ ]
1856
+ };
1857
+ }
1858
+ }
1859
+ },
1860
+ // get_research - Get saved research docs from workspace_docs table
1861
+ {
1862
+ name: "get_research",
1863
+ description: "Get saved research documents from the knowledge base. Use this to recall research findings from previous sessions.",
1864
+ zodSchema: z.object({
1865
+ project: z.string().optional().describe("Filter by project (defaults to all)"),
1866
+ search: z.string().optional().describe("Search term to filter research docs"),
1867
+ limit: z.number().default(10).describe("Maximum docs to return")
1868
+ }),
1869
+ handler: async (args, taskOps2) => {
1870
+ try {
1871
+ const docs = await taskOps2.getWorkspaceDocs({
1872
+ doc_type: "research",
1873
+ project_tag: args.project,
1874
+ search: args.search,
1875
+ limit: args.limit
1876
+ });
1877
+ return {
1878
+ content: [
1879
+ {
1880
+ type: "text",
1881
+ text: JSON.stringify(
1882
+ {
1883
+ success: true,
1884
+ research_docs: (docs || []).map((d) => ({
1885
+ id: d.id,
1886
+ title: d.title,
1887
+ project: d.project_tag,
1888
+ created_at: d.created_at,
1889
+ created_by: d.created_by,
1890
+ source_urls: d.source_urls || [],
1891
+ preview: d.content?.substring(0, 300) + (d.content && d.content.length > 300 ? "..." : "")
1892
+ })),
1893
+ count: docs?.length || 0,
1894
+ filter: { project: args.project, search: args.search }
1895
+ },
1896
+ null,
1897
+ 2
1898
+ )
1899
+ }
1900
+ ]
1901
+ };
1902
+ } catch (error) {
1903
+ return {
1904
+ content: [
1905
+ {
1906
+ type: "text",
1907
+ text: JSON.stringify(
1908
+ {
1909
+ success: false,
1910
+ error: error.message || "Failed to get research docs"
1911
+ },
1912
+ null,
1913
+ 2
1914
+ )
1915
+ }
1916
+ ]
1917
+ };
1918
+ }
1919
+ }
1920
+ },
1921
+ // =========================================================================
1922
+ // GIT-FOR-TASKS: Change Tracking Tools
1923
+ // =========================================================================
1924
+ // get_task_history - Like git log for a task
1925
+ {
1926
+ name: "get_task_history",
1927
+ description: "Get change history for a task (like git log). Shows who changed what and when. Use this to understand how a task evolved or to find a change to revert.",
1928
+ zodSchema: z.object({
1929
+ task_id: z.string().describe("Task ID to get history for"),
1930
+ limit: z.number().default(20).describe("Maximum changes to return"),
1931
+ change_types: z.array(z.enum([
1932
+ "created",
1933
+ "status_changed",
1934
+ "title_changed",
1935
+ "context_updated",
1936
+ "priority_changed",
1937
+ "subtask_done",
1938
+ "subtask_undone",
1939
+ "subtask_added",
1940
+ "archived",
1941
+ "restored",
1942
+ "revert"
1943
+ ])).optional().describe("Filter by specific change types")
1944
+ }),
1945
+ handler: async (args, taskOps2) => {
1946
+ try {
1947
+ const changes = await taskOps2.getTaskChanges(args.task_id, {
1948
+ limit: args.limit,
1949
+ changeTypes: args.change_types
1950
+ });
1951
+ const task = await taskOps2.getTask(args.task_id);
1952
+ return {
1953
+ content: [
1954
+ {
1955
+ type: "text",
1956
+ text: JSON.stringify(
1957
+ {
1958
+ success: true,
1959
+ task: {
1960
+ id: task.id,
1961
+ title: task.title,
1962
+ status: task.status
1963
+ },
1964
+ changes: changes.map((c) => ({
1965
+ id: c.id,
1966
+ change_type: c.change_type,
1967
+ message: c.message,
1968
+ actor_type: c.actor_type,
1969
+ field_changed: c.field_changed,
1970
+ old_value: c.old_value,
1971
+ new_value: c.new_value,
1972
+ session_id: c.session_id,
1973
+ created_at: c.created_at
1974
+ })),
1975
+ count: changes.length
1976
+ },
1977
+ null,
1978
+ 2
1979
+ )
1980
+ }
1981
+ ]
1982
+ };
1983
+ } catch (error) {
1984
+ return {
1985
+ content: [
1986
+ {
1987
+ type: "text",
1988
+ text: JSON.stringify({ success: false, error: error.message }, null, 2)
1989
+ }
1990
+ ]
1991
+ };
1992
+ }
1993
+ }
1994
+ },
1995
+ // revert_change - Like git revert
1996
+ {
1997
+ name: "revert_change",
1998
+ description: 'Revert a specific change (like git revert). Non-destructive: creates a new "revert" entry preserving history. Use get_task_history first to find the change ID.',
1999
+ zodSchema: z.object({
2000
+ change_id: z.string().describe("Change ID to revert (from get_task_history)"),
2001
+ message: z.string().optional().describe("Custom message for the revert")
2002
+ }),
2003
+ handler: async (args, taskOps2) => {
2004
+ try {
2005
+ const revertChange = await taskOps2.revertChange(args.change_id, args.message);
2006
+ const task = await taskOps2.getTask(revertChange.task_id);
2007
+ return {
2008
+ content: [
2009
+ {
2010
+ type: "text",
2011
+ text: JSON.stringify(
2012
+ {
2013
+ success: true,
2014
+ reverted: {
2015
+ change_id: args.change_id,
2016
+ revert_id: revertChange.id,
2017
+ task_id: task.id,
2018
+ task_title: task.title,
2019
+ message: revertChange.message
2020
+ },
2021
+ hint: `View history: get_task_history(task_id="${task.id}")`
2022
+ },
2023
+ null,
2024
+ 2
2025
+ )
2026
+ }
2027
+ ]
2028
+ };
2029
+ } catch (error) {
2030
+ return {
2031
+ content: [
2032
+ {
2033
+ type: "text",
2034
+ text: JSON.stringify({ success: false, error: error.message }, null, 2)
2035
+ }
2036
+ ]
2037
+ };
2038
+ }
2039
+ }
2040
+ },
2041
+ // pause_task - Like git stash
2042
+ {
2043
+ name: "pause_task",
2044
+ description: 'Pause a task (like git stash). Moves task to "paused" status with optional reason. Use this when you need to switch context but want to come back later.',
2045
+ zodSchema: z.object({
2046
+ task_id: z.string().describe("Task ID to pause"),
2047
+ reason: z.string().optional().describe("Why you are pausing this task")
2048
+ }),
2049
+ handler: async (args, taskOps2) => {
2050
+ try {
2051
+ const task = await taskOps2.pauseTask(args.task_id, args.reason);
2052
+ return {
2053
+ content: [
2054
+ {
2055
+ type: "text",
2056
+ text: JSON.stringify(
2057
+ {
2058
+ success: true,
2059
+ task: {
2060
+ id: task.id,
2061
+ title: task.title,
2062
+ status: "paused",
2063
+ reason: args.reason || null
2064
+ },
2065
+ hint: `Resume with: resume_task(task_id="${task.id}")`
2066
+ },
2067
+ null,
2068
+ 2
2069
+ )
2070
+ }
2071
+ ]
2072
+ };
2073
+ } catch (error) {
2074
+ return {
2075
+ content: [
2076
+ {
2077
+ type: "text",
2078
+ text: JSON.stringify({ success: false, error: error.message }, null, 2)
2079
+ }
2080
+ ]
2081
+ };
2082
+ }
2083
+ }
2084
+ },
2085
+ // resume_task - Like git stash pop
2086
+ {
2087
+ name: "resume_task",
2088
+ description: 'Resume a paused task (like git stash pop). Moves task back to "vibing" status.',
2089
+ zodSchema: z.object({
2090
+ task_id: z.string().describe("Task ID to resume")
2091
+ }),
2092
+ handler: async (args, taskOps2) => {
2093
+ try {
2094
+ const task = await taskOps2.resumeTask(args.task_id);
2095
+ return {
2096
+ content: [
2097
+ {
2098
+ type: "text",
2099
+ text: JSON.stringify(
2100
+ {
2101
+ success: true,
2102
+ task: {
2103
+ id: task.id,
2104
+ title: task.title,
2105
+ status: "vibing",
2106
+ context_notes: task.context_notes
2107
+ },
2108
+ message: "Task resumed! Continue where you left off."
2109
+ },
2110
+ null,
2111
+ 2
2112
+ )
2113
+ }
2114
+ ]
2115
+ };
2116
+ } catch (error) {
2117
+ return {
2118
+ content: [
2119
+ {
2120
+ type: "text",
2121
+ text: JSON.stringify({ success: false, error: error.message }, null, 2)
2122
+ }
2123
+ ]
2124
+ };
2125
+ }
2126
+ }
2127
+ },
2128
+ // get_paused_tasks - List all paused tasks
2129
+ {
2130
+ name: "get_paused_tasks",
2131
+ description: "Get all paused tasks. Shows tasks that have been paused (stashed) for later.",
2132
+ zodSchema: z.object({}),
2133
+ handler: async (_args, taskOps2) => {
2134
+ try {
2135
+ const allTasks = await taskOps2.getTasks("all");
2136
+ const pausedTasks = allTasks.filter((t) => t.status === "paused");
2137
+ return {
2138
+ content: [
2139
+ {
2140
+ type: "text",
2141
+ text: JSON.stringify(
2142
+ {
2143
+ success: true,
2144
+ paused_tasks: pausedTasks.map((t) => ({
2145
+ id: t.id,
2146
+ title: t.title,
2147
+ context_notes: t.context_notes,
2148
+ priority: t.priority
2149
+ })),
2150
+ count: pausedTasks.length,
2151
+ hint: pausedTasks.length > 0 ? `Resume with: resume_task(task_id="...")` : "No paused tasks."
2152
+ },
2153
+ null,
2154
+ 2
2155
+ )
2156
+ }
2157
+ ]
2158
+ };
2159
+ } catch (error) {
2160
+ return {
2161
+ content: [
2162
+ {
2163
+ type: "text",
2164
+ text: JSON.stringify({ success: false, error: error.message }, null, 2)
2165
+ }
2166
+ ]
2167
+ };
2168
+ }
2169
+ }
2170
+ },
2171
+ // search_changes - Search across all task changes
2172
+ {
2173
+ name: "search_changes",
2174
+ description: "Search task changes by message (like git log --grep). Find when something was changed across all tasks.",
2175
+ zodSchema: z.object({
2176
+ query: z.string().describe("Search query to match against change messages"),
2177
+ limit: z.number().default(20).describe("Maximum results")
2178
+ }),
2179
+ handler: async (args, taskOps2) => {
2180
+ try {
2181
+ const changes = await taskOps2.searchChanges(args.query, args.limit);
2182
+ return {
2183
+ content: [
2184
+ {
2185
+ type: "text",
2186
+ text: JSON.stringify(
2187
+ {
2188
+ success: true,
2189
+ query: args.query,
2190
+ changes: changes.map((c) => ({
2191
+ id: c.id,
2192
+ task_id: c.task_id,
2193
+ change_type: c.change_type,
2194
+ message: c.message,
2195
+ actor_type: c.actor_type,
2196
+ created_at: c.created_at
2197
+ })),
2198
+ count: changes.length
2199
+ },
2200
+ null,
2201
+ 2
2202
+ )
2203
+ }
2204
+ ]
2205
+ };
2206
+ } catch (error) {
2207
+ return {
2208
+ content: [
2209
+ {
2210
+ type: "text",
2211
+ text: JSON.stringify({ success: false, error: error.message }, null, 2)
2212
+ }
2213
+ ]
2214
+ };
2215
+ }
2216
+ }
1324
2217
  }
1325
2218
  ];
2219
+ return toolsWithZod.map((tool) => ({
2220
+ name: tool.name,
2221
+ description: tool.description,
2222
+ inputSchema: toJsonSchema(tool.zodSchema),
2223
+ handler: tool.handler
2224
+ }));
1326
2225
  }
1327
2226
  function summarizeErrorsLocal(logText, maxLines = 100) {
1328
2227
  const errorTypePatterns = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibetasks/mcp-server",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
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,14 +45,15 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@modelcontextprotocol/sdk": "^0.5.0",
48
- "@vibetasks/core": "^0.5.4",
49
- "@vibetasks/shared": "*",
50
- "zod": "^3.22.0"
48
+ "@vibetasks/core": "^0.5.8",
49
+ "@vibetasks/shared": "^1.4.5",
50
+ "zod": "^3.22.0",
51
+ "zod-to-json-schema": "^3.25.1"
51
52
  },
52
53
  "devDependencies": {
53
54
  "@types/node": "^20.0.0",
54
- "tsx": "^4.7.0",
55
55
  "tsup": "^8.0.0",
56
+ "tsx": "^4.7.0",
56
57
  "typescript": "^5.3.3"
57
58
  }
58
59
  }