@hasna/todos 0.9.34 → 0.9.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -2258,6 +2258,8 @@ function ensureSchema(db) {
2258
2258
  ensureColumn("tasks", "requires_approval", "INTEGER NOT NULL DEFAULT 0");
2259
2259
  ensureColumn("tasks", "approved_by", "TEXT");
2260
2260
  ensureColumn("tasks", "approved_at", "TEXT");
2261
+ ensureColumn("tasks", "recurrence_rule", "TEXT");
2262
+ ensureColumn("tasks", "recurrence_parent_id", "TEXT REFERENCES tasks(id) ON DELETE SET NULL");
2261
2263
  ensureColumn("agents", "role", "TEXT DEFAULT 'agent'");
2262
2264
  ensureColumn("agents", "permissions", `TEXT DEFAULT '["*"]'`);
2263
2265
  ensureColumn("agents", "reports_to", "TEXT");
@@ -2282,6 +2284,8 @@ function ensureSchema(db) {
2282
2284
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_agent ON plans(agent_id)");
2283
2285
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_task ON task_history(task_id)");
2284
2286
  ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_agent ON task_history(agent_id)");
2287
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_parent ON tasks(recurrence_parent_id)");
2288
+ ensureIndex("CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_rule ON tasks(recurrence_rule) WHERE recurrence_rule IS NOT NULL");
2285
2289
  }
2286
2290
  function backfillTaskTags(db) {
2287
2291
  try {
@@ -2581,17 +2585,26 @@ var init_database = __esm(() => {
2581
2585
  ALTER TABLE agents ADD COLUMN org_id TEXT REFERENCES orgs(id) ON DELETE SET NULL;
2582
2586
  ALTER TABLE projects ADD COLUMN org_id TEXT REFERENCES orgs(id) ON DELETE SET NULL;
2583
2587
  INSERT OR IGNORE INTO _migrations (id) VALUES (12);
2588
+ `,
2589
+ `
2590
+ ALTER TABLE tasks ADD COLUMN recurrence_rule TEXT;
2591
+ ALTER TABLE tasks ADD COLUMN recurrence_parent_id TEXT REFERENCES tasks(id) ON DELETE SET NULL;
2592
+ CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_parent ON tasks(recurrence_parent_id);
2593
+ CREATE INDEX IF NOT EXISTS idx_tasks_recurrence_rule ON tasks(recurrence_rule) WHERE recurrence_rule IS NOT NULL;
2594
+ INSERT OR IGNORE INTO _migrations (id) VALUES (13);
2584
2595
  `
2585
2596
  ];
2586
2597
  });
2587
2598
 
2588
2599
  // src/types/index.ts
2589
- var VersionConflictError, TaskNotFoundError, ProjectNotFoundError, PlanNotFoundError, LockError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError;
2600
+ var VersionConflictError, TaskNotFoundError, ProjectNotFoundError, PlanNotFoundError, LockError, AgentNotFoundError, TaskListNotFoundError, DependencyCycleError, CompletionGuardError;
2590
2601
  var init_types = __esm(() => {
2591
2602
  VersionConflictError = class VersionConflictError extends Error {
2592
2603
  taskId;
2593
2604
  expectedVersion;
2594
2605
  actualVersion;
2606
+ static code = "VERSION_CONFLICT";
2607
+ static suggestion = "Fetch the task with get_task to get the current version before updating.";
2595
2608
  constructor(taskId, expectedVersion, actualVersion) {
2596
2609
  super(`Version conflict for task ${taskId}: expected ${expectedVersion}, got ${actualVersion}`);
2597
2610
  this.taskId = taskId;
@@ -2602,6 +2615,8 @@ var init_types = __esm(() => {
2602
2615
  };
2603
2616
  TaskNotFoundError = class TaskNotFoundError extends Error {
2604
2617
  taskId;
2618
+ static code = "TASK_NOT_FOUND";
2619
+ static suggestion = "Verify the task ID. Use list_tasks or search_tasks to find the correct ID.";
2605
2620
  constructor(taskId) {
2606
2621
  super(`Task not found: ${taskId}`);
2607
2622
  this.taskId = taskId;
@@ -2610,6 +2625,8 @@ var init_types = __esm(() => {
2610
2625
  };
2611
2626
  ProjectNotFoundError = class ProjectNotFoundError extends Error {
2612
2627
  projectId;
2628
+ static code = "PROJECT_NOT_FOUND";
2629
+ static suggestion = "Use list_projects to see available projects.";
2613
2630
  constructor(projectId) {
2614
2631
  super(`Project not found: ${projectId}`);
2615
2632
  this.projectId = projectId;
@@ -2618,6 +2635,8 @@ var init_types = __esm(() => {
2618
2635
  };
2619
2636
  PlanNotFoundError = class PlanNotFoundError extends Error {
2620
2637
  planId;
2638
+ static code = "PLAN_NOT_FOUND";
2639
+ static suggestion = "Use list_plans to see available plans.";
2621
2640
  constructor(planId) {
2622
2641
  super(`Plan not found: ${planId}`);
2623
2642
  this.planId = planId;
@@ -2627,6 +2646,8 @@ var init_types = __esm(() => {
2627
2646
  LockError = class LockError extends Error {
2628
2647
  taskId;
2629
2648
  lockedBy;
2649
+ static code = "LOCK_ERROR";
2650
+ static suggestion = "Wait for the lock to expire (30 min) or contact the lock holder.";
2630
2651
  constructor(taskId, lockedBy) {
2631
2652
  super(`Task ${taskId} is locked by ${lockedBy}`);
2632
2653
  this.taskId = taskId;
@@ -2634,8 +2655,20 @@ var init_types = __esm(() => {
2634
2655
  this.name = "LockError";
2635
2656
  }
2636
2657
  };
2658
+ AgentNotFoundError = class AgentNotFoundError extends Error {
2659
+ agentId;
2660
+ static code = "AGENT_NOT_FOUND";
2661
+ static suggestion = "Use register_agent to create the agent first, or list_agents to find existing ones.";
2662
+ constructor(agentId) {
2663
+ super(`Agent not found: ${agentId}`);
2664
+ this.agentId = agentId;
2665
+ this.name = "AgentNotFoundError";
2666
+ }
2667
+ };
2637
2668
  TaskListNotFoundError = class TaskListNotFoundError extends Error {
2638
2669
  taskListId;
2670
+ static code = "TASK_LIST_NOT_FOUND";
2671
+ static suggestion = "Use list_task_lists to see available lists.";
2639
2672
  constructor(taskListId) {
2640
2673
  super(`Task list not found: ${taskListId}`);
2641
2674
  this.taskListId = taskListId;
@@ -2645,6 +2678,8 @@ var init_types = __esm(() => {
2645
2678
  DependencyCycleError = class DependencyCycleError extends Error {
2646
2679
  taskId;
2647
2680
  dependsOn;
2681
+ static code = "DEPENDENCY_CYCLE";
2682
+ static suggestion = "Check the dependency chain with get_task to avoid circular references.";
2648
2683
  constructor(taskId, dependsOn) {
2649
2684
  super(`Adding dependency ${taskId} -> ${dependsOn} would create a cycle`);
2650
2685
  this.taskId = taskId;
@@ -2655,6 +2690,8 @@ var init_types = __esm(() => {
2655
2690
  CompletionGuardError = class CompletionGuardError extends Error {
2656
2691
  reason;
2657
2692
  retryAfterSeconds;
2693
+ static code = "COMPLETION_BLOCKED";
2694
+ static suggestion = "Wait for the cooldown period, then retry.";
2658
2695
  constructor(reason, retryAfterSeconds) {
2659
2696
  super(reason);
2660
2697
  this.reason = reason;
@@ -2974,6 +3011,96 @@ var init_audit = __esm(() => {
2974
3011
  init_database();
2975
3012
  });
2976
3013
 
3014
+ // src/lib/recurrence.ts
3015
+ function parseRecurrenceRule(rule) {
3016
+ const normalized = rule.trim().toLowerCase();
3017
+ if (normalized === "every weekday" || normalized === "every weekdays") {
3018
+ return { type: "specific_days", days: [1, 2, 3, 4, 5] };
3019
+ }
3020
+ if (normalized === "every day" || normalized === "daily") {
3021
+ return { type: "interval", interval: 1, unit: "day" };
3022
+ }
3023
+ if (normalized === "every week" || normalized === "weekly") {
3024
+ return { type: "interval", interval: 1, unit: "week" };
3025
+ }
3026
+ if (normalized === "every month" || normalized === "monthly") {
3027
+ return { type: "interval", interval: 1, unit: "month" };
3028
+ }
3029
+ const intervalMatch = normalized.match(/^every\s+(\d+)\s+(day|week|month)s?$/);
3030
+ if (intervalMatch) {
3031
+ return {
3032
+ type: "interval",
3033
+ interval: parseInt(intervalMatch[1], 10),
3034
+ unit: intervalMatch[2]
3035
+ };
3036
+ }
3037
+ const daysMatch = normalized.match(/^every\s+(.+)$/);
3038
+ if (daysMatch) {
3039
+ const dayParts = daysMatch[1].split(/[,\s]+/).map((d) => d.trim()).filter(Boolean);
3040
+ const days = [];
3041
+ for (const part of dayParts) {
3042
+ const dayNum = DAY_NAMES[part];
3043
+ if (dayNum !== undefined) {
3044
+ days.push(dayNum);
3045
+ }
3046
+ }
3047
+ if (days.length > 0) {
3048
+ return { type: "specific_days", days: days.sort((a, b) => a - b) };
3049
+ }
3050
+ }
3051
+ throw new Error(`Invalid recurrence rule: "${rule}". Supported formats: "every day", "every weekday", "every week", "every 2 weeks", "every month", "every N days/weeks/months", "every monday", "every mon,wed,fri"`);
3052
+ }
3053
+ function nextOccurrence(rule, from) {
3054
+ const parsed = parseRecurrenceRule(rule);
3055
+ const base = from || new Date;
3056
+ if (parsed.type === "interval") {
3057
+ const next = new Date(base);
3058
+ if (parsed.unit === "day") {
3059
+ next.setDate(next.getDate() + parsed.interval);
3060
+ } else if (parsed.unit === "week") {
3061
+ next.setDate(next.getDate() + parsed.interval * 7);
3062
+ } else if (parsed.unit === "month") {
3063
+ next.setMonth(next.getMonth() + parsed.interval);
3064
+ }
3065
+ return next.toISOString();
3066
+ }
3067
+ if (parsed.type === "specific_days") {
3068
+ const currentDay = base.getDay();
3069
+ const days = parsed.days;
3070
+ let daysToAdd = Infinity;
3071
+ for (const day of days) {
3072
+ let diff = day - currentDay;
3073
+ if (diff <= 0)
3074
+ diff += 7;
3075
+ if (diff < daysToAdd)
3076
+ daysToAdd = diff;
3077
+ }
3078
+ const next = new Date(base);
3079
+ next.setDate(next.getDate() + daysToAdd);
3080
+ return next.toISOString();
3081
+ }
3082
+ throw new Error(`Cannot calculate next occurrence for rule: "${rule}"`);
3083
+ }
3084
+ var DAY_NAMES;
3085
+ var init_recurrence = __esm(() => {
3086
+ DAY_NAMES = {
3087
+ sunday: 0,
3088
+ sun: 0,
3089
+ monday: 1,
3090
+ mon: 1,
3091
+ tuesday: 2,
3092
+ tue: 2,
3093
+ wednesday: 3,
3094
+ wed: 3,
3095
+ thursday: 4,
3096
+ thu: 4,
3097
+ friday: 5,
3098
+ fri: 5,
3099
+ saturday: 6,
3100
+ sat: 6
3101
+ };
3102
+ });
3103
+
2977
3104
  // src/db/tasks.ts
2978
3105
  function rowToTask(row) {
2979
3106
  return {
@@ -3005,8 +3132,8 @@ function createTask(input, db) {
3005
3132
  const tags = input.tags || [];
3006
3133
  const shortId = input.project_id ? nextTaskShortId(input.project_id, d) : null;
3007
3134
  const title = shortId ? `${shortId}: ${input.title}` : input.title;
3008
- d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, requires_approval, approved_by, approved_at)
3009
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?)`, [
3135
+ d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id)
3136
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
3010
3137
  id,
3011
3138
  shortId,
3012
3139
  input.project_id || null,
@@ -3029,7 +3156,9 @@ function createTask(input, db) {
3029
3156
  input.estimated_minutes || null,
3030
3157
  input.requires_approval ? 1 : 0,
3031
3158
  null,
3032
- null
3159
+ null,
3160
+ input.recurrence_rule || null,
3161
+ input.recurrence_parent_id || null
3033
3162
  ]);
3034
3163
  if (tags.length > 0) {
3035
3164
  insertTaskTags(id, tags, d);
@@ -3129,6 +3258,11 @@ function listTasks(filter = {}, db) {
3129
3258
  conditions.push("task_list_id = ?");
3130
3259
  params.push(filter.task_list_id);
3131
3260
  }
3261
+ if (filter.has_recurrence === true) {
3262
+ conditions.push("recurrence_rule IS NOT NULL");
3263
+ } else if (filter.has_recurrence === false) {
3264
+ conditions.push("recurrence_rule IS NULL");
3265
+ }
3132
3266
  const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
3133
3267
  let limitClause = "";
3134
3268
  if (filter.limit) {
@@ -3144,6 +3278,69 @@ function listTasks(filter = {}, db) {
3144
3278
  created_at DESC${limitClause}`).all(...params);
3145
3279
  return rows.map(rowToTask);
3146
3280
  }
3281
+ function countTasks(filter = {}, db) {
3282
+ const d = db || getDatabase();
3283
+ const conditions = [];
3284
+ const params = [];
3285
+ if (filter.project_id) {
3286
+ conditions.push("project_id = ?");
3287
+ params.push(filter.project_id);
3288
+ }
3289
+ if (filter.parent_id !== undefined) {
3290
+ if (filter.parent_id === null) {
3291
+ conditions.push("parent_id IS NULL");
3292
+ } else {
3293
+ conditions.push("parent_id = ?");
3294
+ params.push(filter.parent_id);
3295
+ }
3296
+ }
3297
+ if (filter.status) {
3298
+ if (Array.isArray(filter.status)) {
3299
+ conditions.push(`status IN (${filter.status.map(() => "?").join(",")})`);
3300
+ params.push(...filter.status);
3301
+ } else {
3302
+ conditions.push("status = ?");
3303
+ params.push(filter.status);
3304
+ }
3305
+ }
3306
+ if (filter.priority) {
3307
+ if (Array.isArray(filter.priority)) {
3308
+ conditions.push(`priority IN (${filter.priority.map(() => "?").join(",")})`);
3309
+ params.push(...filter.priority);
3310
+ } else {
3311
+ conditions.push("priority = ?");
3312
+ params.push(filter.priority);
3313
+ }
3314
+ }
3315
+ if (filter.assigned_to) {
3316
+ conditions.push("assigned_to = ?");
3317
+ params.push(filter.assigned_to);
3318
+ }
3319
+ if (filter.agent_id) {
3320
+ conditions.push("agent_id = ?");
3321
+ params.push(filter.agent_id);
3322
+ }
3323
+ if (filter.session_id) {
3324
+ conditions.push("session_id = ?");
3325
+ params.push(filter.session_id);
3326
+ }
3327
+ if (filter.tags && filter.tags.length > 0) {
3328
+ const placeholders = filter.tags.map(() => "?").join(",");
3329
+ conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
3330
+ params.push(...filter.tags);
3331
+ }
3332
+ if (filter.plan_id) {
3333
+ conditions.push("plan_id = ?");
3334
+ params.push(filter.plan_id);
3335
+ }
3336
+ if (filter.task_list_id) {
3337
+ conditions.push("task_list_id = ?");
3338
+ params.push(filter.task_list_id);
3339
+ }
3340
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
3341
+ const row = d.query(`SELECT COUNT(*) as count FROM tasks ${where}`).get(...params);
3342
+ return row.count;
3343
+ }
3147
3344
  function updateTask(id, input, db) {
3148
3345
  const d = db || getDatabase();
3149
3346
  const task = getTask(id, d);
@@ -3215,6 +3412,10 @@ function updateTask(id, input, db) {
3215
3412
  sets.push("approved_at = ?");
3216
3413
  params.push(now());
3217
3414
  }
3415
+ if (input.recurrence_rule !== undefined) {
3416
+ sets.push("recurrence_rule = ?");
3417
+ params.push(input.recurrence_rule);
3418
+ }
3218
3419
  params.push(id, input.version);
3219
3420
  const result = d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ? AND version = ?`, params);
3220
3421
  if (result.changes === 0) {
@@ -3288,7 +3489,7 @@ function startTask(id, agentId, db) {
3288
3489
  logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
3289
3490
  return { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, version: task.version + 1, updated_at: timestamp };
3290
3491
  }
3291
- function completeTask(id, agentId, db, evidence) {
3492
+ function completeTask(id, agentId, db, options) {
3292
3493
  const d = db || getDatabase();
3293
3494
  const task = getTask(id, d);
3294
3495
  if (!task)
@@ -3297,7 +3498,9 @@ function completeTask(id, agentId, db, evidence) {
3297
3498
  throw new LockError(id, task.locked_by);
3298
3499
  }
3299
3500
  checkCompletionGuard(task, agentId || null, d);
3300
- if (evidence) {
3501
+ const evidence = options ? { files_changed: options.files_changed, test_results: options.test_results, commit_hash: options.commit_hash, notes: options.notes } : undefined;
3502
+ const hasEvidence = evidence && (evidence.files_changed || evidence.test_results || evidence.commit_hash || evidence.notes);
3503
+ if (hasEvidence) {
3301
3504
  const meta2 = { ...task.metadata, _evidence: evidence };
3302
3505
  d.run("UPDATE tasks SET metadata = ? WHERE id = ?", [JSON.stringify(meta2), id]);
3303
3506
  }
@@ -3305,7 +3508,14 @@ function completeTask(id, agentId, db, evidence) {
3305
3508
  d.run(`UPDATE tasks SET status = 'completed', locked_by = NULL, locked_at = NULL, completed_at = ?, version = version + 1, updated_at = ?
3306
3509
  WHERE id = ?`, [timestamp, timestamp, id]);
3307
3510
  logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
3308
- const meta = evidence ? { ...task.metadata, _evidence: evidence } : task.metadata;
3511
+ let spawnedTask = null;
3512
+ if (task.recurrence_rule && !options?.skip_recurrence) {
3513
+ spawnedTask = spawnNextRecurrence(task, d);
3514
+ }
3515
+ const meta = hasEvidence ? { ...task.metadata, _evidence: evidence } : task.metadata;
3516
+ if (spawnedTask) {
3517
+ meta._next_recurrence = { id: spawnedTask.id, short_id: spawnedTask.short_id, due_at: spawnedTask.due_at };
3518
+ }
3309
3519
  return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, version: task.version + 1, updated_at: timestamp, metadata: meta };
3310
3520
  }
3311
3521
  function lockTask(id, agentId, db) {
@@ -3368,6 +3578,266 @@ function getTaskDependencies(taskId, db) {
3368
3578
  const d = db || getDatabase();
3369
3579
  return d.query("SELECT * FROM task_dependencies WHERE task_id = ?").all(taskId);
3370
3580
  }
3581
+ function cloneTask(taskId, overrides, db) {
3582
+ const d = db || getDatabase();
3583
+ const source = getTask(taskId, d);
3584
+ if (!source)
3585
+ throw new TaskNotFoundError(taskId);
3586
+ const input = {
3587
+ title: overrides?.title ?? source.title,
3588
+ description: overrides?.description ?? source.description ?? undefined,
3589
+ priority: overrides?.priority ?? source.priority,
3590
+ project_id: overrides?.project_id ?? source.project_id ?? undefined,
3591
+ parent_id: overrides?.parent_id ?? source.parent_id ?? undefined,
3592
+ plan_id: overrides?.plan_id ?? source.plan_id ?? undefined,
3593
+ task_list_id: overrides?.task_list_id ?? source.task_list_id ?? undefined,
3594
+ status: overrides?.status ?? "pending",
3595
+ agent_id: overrides?.agent_id ?? source.agent_id ?? undefined,
3596
+ assigned_to: overrides?.assigned_to ?? source.assigned_to ?? undefined,
3597
+ tags: overrides?.tags ?? source.tags,
3598
+ metadata: overrides?.metadata ?? source.metadata,
3599
+ estimated_minutes: overrides?.estimated_minutes ?? source.estimated_minutes ?? undefined,
3600
+ recurrence_rule: overrides?.recurrence_rule ?? source.recurrence_rule ?? undefined
3601
+ };
3602
+ return createTask(input, d);
3603
+ }
3604
+ function getTaskGraph(taskId, direction = "both", db) {
3605
+ const d = db || getDatabase();
3606
+ const task = getTask(taskId, d);
3607
+ if (!task)
3608
+ throw new TaskNotFoundError(taskId);
3609
+ function toNode(t) {
3610
+ const deps = getTaskDependencies(t.id, d);
3611
+ const hasUnfinishedDeps = deps.some((dep) => {
3612
+ const depTask = getTask(dep.depends_on, d);
3613
+ return depTask && depTask.status !== "completed";
3614
+ });
3615
+ return { id: t.id, short_id: t.short_id, title: t.title, status: t.status, priority: t.priority, is_blocked: hasUnfinishedDeps };
3616
+ }
3617
+ function buildUp(id, visited) {
3618
+ if (visited.has(id))
3619
+ return [];
3620
+ visited.add(id);
3621
+ const deps = d.query("SELECT depends_on FROM task_dependencies WHERE task_id = ?").all(id);
3622
+ return deps.map((dep) => {
3623
+ const depTask = getTask(dep.depends_on, d);
3624
+ if (!depTask)
3625
+ return null;
3626
+ return { task: toNode(depTask), depends_on: buildUp(dep.depends_on, visited), blocks: [] };
3627
+ }).filter(Boolean);
3628
+ }
3629
+ function buildDown(id, visited) {
3630
+ if (visited.has(id))
3631
+ return [];
3632
+ visited.add(id);
3633
+ const dependents = d.query("SELECT task_id FROM task_dependencies WHERE depends_on = ?").all(id);
3634
+ return dependents.map((dep) => {
3635
+ const depTask = getTask(dep.task_id, d);
3636
+ if (!depTask)
3637
+ return null;
3638
+ return { task: toNode(depTask), depends_on: [], blocks: buildDown(dep.task_id, visited) };
3639
+ }).filter(Boolean);
3640
+ }
3641
+ const rootNode = toNode(task);
3642
+ const depends_on = direction === "up" || direction === "both" ? buildUp(taskId, new Set) : [];
3643
+ const blocks = direction === "down" || direction === "both" ? buildDown(taskId, new Set) : [];
3644
+ return { task: rootNode, depends_on, blocks };
3645
+ }
3646
+ function moveTask(taskId, target, db) {
3647
+ const d = db || getDatabase();
3648
+ const task = getTask(taskId, d);
3649
+ if (!task)
3650
+ throw new TaskNotFoundError(taskId);
3651
+ const sets = ["updated_at = ?", "version = version + 1"];
3652
+ const params = [now()];
3653
+ if (target.task_list_id !== undefined) {
3654
+ sets.push("task_list_id = ?");
3655
+ params.push(target.task_list_id);
3656
+ }
3657
+ if (target.project_id !== undefined) {
3658
+ sets.push("project_id = ?");
3659
+ params.push(target.project_id);
3660
+ }
3661
+ if (target.plan_id !== undefined) {
3662
+ sets.push("plan_id = ?");
3663
+ params.push(target.plan_id);
3664
+ }
3665
+ params.push(taskId);
3666
+ d.run(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ?`, params);
3667
+ return getTask(taskId, d);
3668
+ }
3669
+ function spawnNextRecurrence(completedTask, db) {
3670
+ const dueAt = nextOccurrence(completedTask.recurrence_rule, new Date);
3671
+ let title = completedTask.title;
3672
+ if (completedTask.short_id && title.startsWith(completedTask.short_id + ": ")) {
3673
+ title = title.slice(completedTask.short_id.length + 2);
3674
+ }
3675
+ const recurrenceParentId = completedTask.recurrence_parent_id || completedTask.id;
3676
+ return createTask({
3677
+ title,
3678
+ description: completedTask.description ?? undefined,
3679
+ priority: completedTask.priority,
3680
+ project_id: completedTask.project_id ?? undefined,
3681
+ task_list_id: completedTask.task_list_id ?? undefined,
3682
+ plan_id: completedTask.plan_id ?? undefined,
3683
+ assigned_to: completedTask.assigned_to ?? undefined,
3684
+ tags: completedTask.tags,
3685
+ metadata: completedTask.metadata,
3686
+ estimated_minutes: completedTask.estimated_minutes ?? undefined,
3687
+ recurrence_rule: completedTask.recurrence_rule,
3688
+ recurrence_parent_id: recurrenceParentId,
3689
+ due_at: dueAt
3690
+ }, db);
3691
+ }
3692
+ function claimNextTask(agentId, filters, db) {
3693
+ const d = db || getDatabase();
3694
+ const tx = d.transaction(() => {
3695
+ const task = getNextTask(agentId, filters, d);
3696
+ if (!task)
3697
+ return null;
3698
+ return startTask(task.id, agentId, d);
3699
+ });
3700
+ return tx();
3701
+ }
3702
+ function getNextTask(agentId, filters, db) {
3703
+ const d = db || getDatabase();
3704
+ clearExpiredLocks(d);
3705
+ const conditions = ["status = 'pending'", "(locked_by IS NULL OR locked_at < ?)"];
3706
+ const params = [lockExpiryCutoff()];
3707
+ if (filters?.project_id) {
3708
+ conditions.push("project_id = ?");
3709
+ params.push(filters.project_id);
3710
+ }
3711
+ if (filters?.task_list_id) {
3712
+ conditions.push("task_list_id = ?");
3713
+ params.push(filters.task_list_id);
3714
+ }
3715
+ if (filters?.plan_id) {
3716
+ conditions.push("plan_id = ?");
3717
+ params.push(filters.plan_id);
3718
+ }
3719
+ if (filters?.tags && filters.tags.length > 0) {
3720
+ const placeholders = filters.tags.map(() => "?").join(",");
3721
+ conditions.push(`id IN (SELECT task_id FROM task_tags WHERE tag IN (${placeholders}))`);
3722
+ params.push(...filters.tags);
3723
+ }
3724
+ conditions.push("id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')");
3725
+ const where = conditions.join(" AND ");
3726
+ let sql = `SELECT * FROM tasks WHERE ${where} ORDER BY `;
3727
+ if (agentId) {
3728
+ sql += `CASE WHEN assigned_to = ? THEN 0 WHEN assigned_to IS NULL THEN 1 ELSE 2 END, `;
3729
+ params.push(agentId);
3730
+ }
3731
+ sql += `CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END, created_at ASC LIMIT 1`;
3732
+ const row = d.query(sql).get(...params);
3733
+ return row ? rowToTask(row) : null;
3734
+ }
3735
+ function getActiveWork(filters, db) {
3736
+ const d = db || getDatabase();
3737
+ clearExpiredLocks(d);
3738
+ const conditions = ["status = 'in_progress'"];
3739
+ const params = [];
3740
+ if (filters?.project_id) {
3741
+ conditions.push("project_id = ?");
3742
+ params.push(filters.project_id);
3743
+ }
3744
+ if (filters?.task_list_id) {
3745
+ conditions.push("task_list_id = ?");
3746
+ params.push(filters.task_list_id);
3747
+ }
3748
+ const where = conditions.join(" AND ");
3749
+ const rows = d.query(`SELECT id, short_id, title, priority, assigned_to, locked_by, locked_at, updated_at FROM tasks WHERE ${where} ORDER BY
3750
+ CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
3751
+ updated_at DESC`).all(...params);
3752
+ return rows;
3753
+ }
3754
+ function getTasksChangedSince(since, filters, db) {
3755
+ const d = db || getDatabase();
3756
+ const conditions = ["updated_at > ?"];
3757
+ const params = [since];
3758
+ if (filters?.project_id) {
3759
+ conditions.push("project_id = ?");
3760
+ params.push(filters.project_id);
3761
+ }
3762
+ if (filters?.task_list_id) {
3763
+ conditions.push("task_list_id = ?");
3764
+ params.push(filters.task_list_id);
3765
+ }
3766
+ const where = conditions.join(" AND ");
3767
+ const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at DESC`).all(...params);
3768
+ return rows.map(rowToTask);
3769
+ }
3770
+ function failTask(id, agentId, reason, options, db) {
3771
+ const d = db || getDatabase();
3772
+ const task = getTask(id, d);
3773
+ if (!task)
3774
+ throw new TaskNotFoundError(id);
3775
+ const meta = {
3776
+ ...task.metadata,
3777
+ _failure: {
3778
+ reason: reason || "Unknown failure",
3779
+ error_code: options?.error_code || null,
3780
+ failed_by: agentId || null,
3781
+ failed_at: now(),
3782
+ retry_requested: options?.retry || false
3783
+ }
3784
+ };
3785
+ const timestamp = now();
3786
+ d.run(`UPDATE tasks SET status = 'failed', locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
3787
+ WHERE id = ?`, [JSON.stringify(meta), timestamp, id]);
3788
+ logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
3789
+ const failedTask = {
3790
+ ...task,
3791
+ status: "failed",
3792
+ locked_by: null,
3793
+ locked_at: null,
3794
+ metadata: meta,
3795
+ version: task.version + 1,
3796
+ updated_at: timestamp
3797
+ };
3798
+ let retryTask;
3799
+ if (options?.retry) {
3800
+ let title = task.title;
3801
+ if (task.short_id && title.startsWith(task.short_id + ": ")) {
3802
+ title = title.slice(task.short_id.length + 2);
3803
+ }
3804
+ retryTask = createTask({
3805
+ title,
3806
+ description: task.description ?? undefined,
3807
+ priority: task.priority,
3808
+ project_id: task.project_id ?? undefined,
3809
+ task_list_id: task.task_list_id ?? undefined,
3810
+ plan_id: task.plan_id ?? undefined,
3811
+ assigned_to: task.assigned_to ?? undefined,
3812
+ tags: task.tags,
3813
+ metadata: { ...task.metadata, _retry: { original_id: task.id, retry_after: options.retry_after || null, failure_reason: reason } },
3814
+ estimated_minutes: task.estimated_minutes ?? undefined,
3815
+ recurrence_rule: task.recurrence_rule ?? undefined,
3816
+ due_at: options.retry_after || task.due_at || undefined
3817
+ }, d);
3818
+ }
3819
+ return { task: failedTask, retryTask };
3820
+ }
3821
+ function getStaleTasks(staleMinutes = 30, filters, db) {
3822
+ const d = db || getDatabase();
3823
+ const cutoff = new Date(Date.now() - staleMinutes * 60 * 1000).toISOString();
3824
+ const conditions = [
3825
+ "status = 'in_progress'",
3826
+ "(updated_at < ? OR (locked_at IS NOT NULL AND locked_at < ?))"
3827
+ ];
3828
+ const params = [cutoff, cutoff];
3829
+ if (filters?.project_id) {
3830
+ conditions.push("project_id = ?");
3831
+ params.push(filters.project_id);
3832
+ }
3833
+ if (filters?.task_list_id) {
3834
+ conditions.push("task_list_id = ?");
3835
+ params.push(filters.task_list_id);
3836
+ }
3837
+ const where = conditions.join(" AND ");
3838
+ const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at ASC`).all(...params);
3839
+ return rows.map(rowToTask);
3840
+ }
3371
3841
  function wouldCreateCycle(taskId, dependsOn, db) {
3372
3842
  const visited = new Set;
3373
3843
  const queue = [dependsOn];
@@ -3385,12 +3855,98 @@ function wouldCreateCycle(taskId, dependsOn, db) {
3385
3855
  }
3386
3856
  return false;
3387
3857
  }
3858
+ function getTaskStats(filters, db) {
3859
+ const d = db || getDatabase();
3860
+ const conditions = [];
3861
+ const params = [];
3862
+ if (filters?.project_id) {
3863
+ conditions.push("project_id = ?");
3864
+ params.push(filters.project_id);
3865
+ }
3866
+ if (filters?.task_list_id) {
3867
+ conditions.push("task_list_id = ?");
3868
+ params.push(filters.task_list_id);
3869
+ }
3870
+ if (filters?.agent_id) {
3871
+ conditions.push("(agent_id = ? OR assigned_to = ?)");
3872
+ params.push(filters.agent_id, filters.agent_id);
3873
+ }
3874
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
3875
+ const totalRow = d.query(`SELECT COUNT(*) as count FROM tasks ${where}`).get(...params);
3876
+ const statusRows = d.query(`SELECT status, COUNT(*) as count FROM tasks ${where} GROUP BY status`).all(...params);
3877
+ const by_status = {};
3878
+ for (const r of statusRows)
3879
+ by_status[r.status] = r.count;
3880
+ const priorityRows = d.query(`SELECT priority, COUNT(*) as count FROM tasks ${where} GROUP BY priority`).all(...params);
3881
+ const by_priority = {};
3882
+ for (const r of priorityRows)
3883
+ by_priority[r.priority] = r.count;
3884
+ const agentRows = d.query(`SELECT COALESCE(assigned_to, agent_id, 'unassigned') as agent, COUNT(*) as count FROM tasks ${where} GROUP BY agent`).all(...params);
3885
+ const by_agent = {};
3886
+ for (const r of agentRows)
3887
+ by_agent[r.agent] = r.count;
3888
+ const completed = by_status["completed"] || 0;
3889
+ const completion_rate = totalRow.count > 0 ? Math.round(completed / totalRow.count * 100) : 0;
3890
+ return { total: totalRow.count, by_status, by_priority, completion_rate, by_agent };
3891
+ }
3892
+ function bulkCreateTasks(inputs, db) {
3893
+ const d = db || getDatabase();
3894
+ const tempIdToRealId = new Map;
3895
+ const created = [];
3896
+ const tx = d.transaction(() => {
3897
+ for (const input of inputs) {
3898
+ const { temp_id, depends_on_temp_ids: _deps, ...createInput } = input;
3899
+ const task = createTask(createInput, d);
3900
+ if (temp_id)
3901
+ tempIdToRealId.set(temp_id, task.id);
3902
+ created.push({ temp_id: temp_id || null, id: task.id, short_id: task.short_id, title: task.title });
3903
+ }
3904
+ for (const input of inputs) {
3905
+ if (input.depends_on_temp_ids && input.depends_on_temp_ids.length > 0) {
3906
+ const taskId = input.temp_id ? tempIdToRealId.get(input.temp_id) : null;
3907
+ if (!taskId)
3908
+ continue;
3909
+ for (const depTempId of input.depends_on_temp_ids) {
3910
+ const depRealId = tempIdToRealId.get(depTempId);
3911
+ if (depRealId) {
3912
+ addDependency(taskId, depRealId, d);
3913
+ }
3914
+ }
3915
+ }
3916
+ }
3917
+ });
3918
+ tx();
3919
+ return { created };
3920
+ }
3921
+ function bulkUpdateTasks(taskIds, updates, db) {
3922
+ const d = db || getDatabase();
3923
+ let updated = 0;
3924
+ const failed = [];
3925
+ const tx = d.transaction(() => {
3926
+ for (const id of taskIds) {
3927
+ try {
3928
+ const task = getTask(id, d);
3929
+ if (!task) {
3930
+ failed.push({ id, error: "Task not found" });
3931
+ continue;
3932
+ }
3933
+ updateTask(id, { ...updates, version: task.version }, d);
3934
+ updated++;
3935
+ } catch (e) {
3936
+ failed.push({ id, error: e instanceof Error ? e.message : String(e) });
3937
+ }
3938
+ }
3939
+ });
3940
+ tx();
3941
+ return { updated, failed };
3942
+ }
3388
3943
  var init_tasks = __esm(() => {
3389
3944
  init_types();
3390
3945
  init_database();
3391
3946
  init_projects();
3392
3947
  init_completion_guard();
3393
3948
  init_audit();
3949
+ init_recurrence();
3394
3950
  });
3395
3951
 
3396
3952
  // src/db/agents.ts
@@ -3715,19 +4271,64 @@ function rowToTask2(row) {
3715
4271
  requires_approval: Boolean(row.requires_approval)
3716
4272
  };
3717
4273
  }
3718
- function searchTasks(query, projectId, taskListId, db) {
4274
+ function searchTasks(options, projectId, taskListId, db) {
4275
+ const opts = typeof options === "string" ? { query: options, project_id: projectId, task_list_id: taskListId } : options;
3719
4276
  const d = db || getDatabase();
3720
4277
  clearExpiredLocks(d);
3721
- const pattern = `%${query}%`;
4278
+ const pattern = `%${opts.query}%`;
3722
4279
  let sql = `SELECT * FROM tasks WHERE (title LIKE ? OR description LIKE ? OR EXISTS (SELECT 1 FROM task_tags WHERE task_tags.task_id = tasks.id AND tag LIKE ?))`;
3723
4280
  const params = [pattern, pattern, pattern];
3724
- if (projectId) {
4281
+ if (opts.project_id) {
3725
4282
  sql += " AND project_id = ?";
3726
- params.push(projectId);
4283
+ params.push(opts.project_id);
3727
4284
  }
3728
- if (taskListId) {
4285
+ if (opts.task_list_id) {
3729
4286
  sql += " AND task_list_id = ?";
3730
- params.push(taskListId);
4287
+ params.push(opts.task_list_id);
4288
+ }
4289
+ if (opts.status) {
4290
+ if (Array.isArray(opts.status)) {
4291
+ sql += ` AND status IN (${opts.status.map(() => "?").join(",")})`;
4292
+ params.push(...opts.status);
4293
+ } else {
4294
+ sql += " AND status = ?";
4295
+ params.push(opts.status);
4296
+ }
4297
+ }
4298
+ if (opts.priority) {
4299
+ if (Array.isArray(opts.priority)) {
4300
+ sql += ` AND priority IN (${opts.priority.map(() => "?").join(",")})`;
4301
+ params.push(...opts.priority);
4302
+ } else {
4303
+ sql += " AND priority = ?";
4304
+ params.push(opts.priority);
4305
+ }
4306
+ }
4307
+ if (opts.assigned_to) {
4308
+ sql += " AND assigned_to = ?";
4309
+ params.push(opts.assigned_to);
4310
+ }
4311
+ if (opts.agent_id) {
4312
+ sql += " AND agent_id = ?";
4313
+ params.push(opts.agent_id);
4314
+ }
4315
+ if (opts.created_after) {
4316
+ sql += " AND created_at > ?";
4317
+ params.push(opts.created_after);
4318
+ }
4319
+ if (opts.updated_after) {
4320
+ sql += " AND updated_at > ?";
4321
+ params.push(opts.updated_after);
4322
+ }
4323
+ if (opts.has_dependencies === true) {
4324
+ sql += " AND id IN (SELECT task_id FROM task_dependencies)";
4325
+ } else if (opts.has_dependencies === false) {
4326
+ sql += " AND id NOT IN (SELECT task_id FROM task_dependencies)";
4327
+ }
4328
+ if (opts.is_blocked === true) {
4329
+ sql += " AND id IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')";
4330
+ } else if (opts.is_blocked === false) {
4331
+ sql += " AND id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')";
3731
4332
  }
3732
4333
  sql += ` ORDER BY
3733
4334
  CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
@@ -8360,25 +8961,38 @@ var exports_mcp = {};
8360
8961
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8361
8962
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8362
8963
  function formatError(error) {
8363
- if (error instanceof VersionConflictError)
8364
- return `Version conflict: ${error.message}`;
8365
- if (error instanceof TaskNotFoundError)
8366
- return `Not found: ${error.message}`;
8367
- if (error instanceof PlanNotFoundError)
8368
- return `Not found: ${error.message}`;
8369
- if (error instanceof TaskListNotFoundError)
8370
- return `Not found: ${error.message}`;
8371
- if (error instanceof LockError)
8372
- return `Lock error: ${error.message}`;
8373
- if (error instanceof DependencyCycleError)
8374
- return `Dependency cycle: ${error.message}`;
8964
+ if (error instanceof VersionConflictError) {
8965
+ return JSON.stringify({ code: VersionConflictError.code, message: error.message, suggestion: VersionConflictError.suggestion });
8966
+ }
8967
+ if (error instanceof TaskNotFoundError) {
8968
+ return JSON.stringify({ code: TaskNotFoundError.code, message: error.message, suggestion: TaskNotFoundError.suggestion });
8969
+ }
8970
+ if (error instanceof ProjectNotFoundError) {
8971
+ return JSON.stringify({ code: ProjectNotFoundError.code, message: error.message, suggestion: ProjectNotFoundError.suggestion });
8972
+ }
8973
+ if (error instanceof PlanNotFoundError) {
8974
+ return JSON.stringify({ code: PlanNotFoundError.code, message: error.message, suggestion: PlanNotFoundError.suggestion });
8975
+ }
8976
+ if (error instanceof TaskListNotFoundError) {
8977
+ return JSON.stringify({ code: TaskListNotFoundError.code, message: error.message, suggestion: TaskListNotFoundError.suggestion });
8978
+ }
8979
+ if (error instanceof LockError) {
8980
+ return JSON.stringify({ code: LockError.code, message: error.message, suggestion: LockError.suggestion });
8981
+ }
8982
+ if (error instanceof AgentNotFoundError) {
8983
+ return JSON.stringify({ code: AgentNotFoundError.code, message: error.message, suggestion: AgentNotFoundError.suggestion });
8984
+ }
8985
+ if (error instanceof DependencyCycleError) {
8986
+ return JSON.stringify({ code: DependencyCycleError.code, message: error.message, suggestion: DependencyCycleError.suggestion });
8987
+ }
8375
8988
  if (error instanceof CompletionGuardError) {
8376
- const retry = error.retryAfterSeconds ? ` (retry after ${error.retryAfterSeconds}s)` : "";
8377
- return `Completion blocked: ${error.reason}${retry}`;
8989
+ const retry = error.retryAfterSeconds ? { retryAfterSeconds: error.retryAfterSeconds } : {};
8990
+ return JSON.stringify({ code: CompletionGuardError.code, message: error.reason, suggestion: CompletionGuardError.suggestion, ...retry });
8991
+ }
8992
+ if (error instanceof Error) {
8993
+ return JSON.stringify({ code: "UNKNOWN_ERROR", message: error.message });
8378
8994
  }
8379
- if (error instanceof Error)
8380
- return error.message;
8381
- return String(error);
8995
+ return JSON.stringify({ code: "UNKNOWN_ERROR", message: String(error) });
8382
8996
  }
8383
8997
  function resolveId(partialId, table = "tasks") {
8384
8998
  const db = getDatabase();
@@ -8401,7 +9015,8 @@ function formatTask(task) {
8401
9015
  const id = task.short_id || task.id.slice(0, 8);
8402
9016
  const assigned = task.assigned_to ? ` -> ${task.assigned_to}` : "";
8403
9017
  const lock = task.locked_by ? ` [locked:${task.locked_by}]` : "";
8404
- return `${id} ${task.status.padEnd(11)} ${task.priority.padEnd(8)} ${task.title}${assigned}${lock}`;
9018
+ const recur = task.recurrence_rule ? ` [\u21BB]` : "";
9019
+ return `${id} ${task.status.padEnd(11)} ${task.priority.padEnd(8)} ${task.title}${assigned}${lock}${recur}`;
8405
9020
  }
8406
9021
  function formatTaskDetail(task) {
8407
9022
  const parts = [
@@ -8426,6 +9041,10 @@ function formatTaskDetail(task) {
8426
9041
  parts.push(`Plan: ${task.plan_id}`);
8427
9042
  if (task.tags.length > 0)
8428
9043
  parts.push(`Tags: ${task.tags.join(", ")}`);
9044
+ if (task.recurrence_rule)
9045
+ parts.push(`Recurrence: ${task.recurrence_rule}`);
9046
+ if (task.recurrence_parent_id)
9047
+ parts.push(`Recurrence parent: ${task.recurrence_parent_id}`);
8429
9048
  parts.push(`Version: ${task.version}`);
8430
9049
  parts.push(`Created: ${task.created_at}`);
8431
9050
  if (task.completed_at)
@@ -8453,7 +9072,7 @@ var init_mcp = __esm(() => {
8453
9072
  init_types();
8454
9073
  server = new McpServer({
8455
9074
  name: "todos",
8456
- version: "0.9.33"
9075
+ version: "0.9.35"
8457
9076
  });
8458
9077
  server.tool("create_task", "Create a new task", {
8459
9078
  title: exports_external.string(),
@@ -8471,7 +9090,8 @@ var init_mcp = __esm(() => {
8471
9090
  tags: exports_external.array(exports_external.string()).optional(),
8472
9091
  metadata: exports_external.record(exports_external.unknown()).optional(),
8473
9092
  estimated_minutes: exports_external.number().optional(),
8474
- requires_approval: exports_external.boolean().optional()
9093
+ requires_approval: exports_external.boolean().optional(),
9094
+ recurrence_rule: exports_external.string().optional()
8475
9095
  }, async (params) => {
8476
9096
  try {
8477
9097
  const resolved = { ...params };
@@ -8489,7 +9109,7 @@ var init_mcp = __esm(() => {
8489
9109
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8490
9110
  }
8491
9111
  });
8492
- server.tool("list_tasks", "List tasks with optional filters", {
9112
+ server.tool("list_tasks", "List tasks with optional filters and pagination.", {
8493
9113
  project_id: exports_external.string().optional(),
8494
9114
  status: exports_external.union([
8495
9115
  exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]),
@@ -8502,7 +9122,10 @@ var init_mcp = __esm(() => {
8502
9122
  assigned_to: exports_external.string().optional(),
8503
9123
  tags: exports_external.array(exports_external.string()).optional(),
8504
9124
  plan_id: exports_external.string().optional(),
8505
- task_list_id: exports_external.string().optional()
9125
+ task_list_id: exports_external.string().optional(),
9126
+ has_recurrence: exports_external.boolean().optional(),
9127
+ limit: exports_external.number().optional(),
9128
+ offset: exports_external.number().optional()
8506
9129
  }, async (params) => {
8507
9130
  try {
8508
9131
  const resolved = { ...params };
@@ -8513,8 +9136,10 @@ var init_mcp = __esm(() => {
8513
9136
  if (resolved.task_list_id)
8514
9137
  resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
8515
9138
  const tasks = listTasks(resolved);
9139
+ const { limit: _limit, offset: _offset, ...countFilter } = resolved;
9140
+ const total = countTasks(countFilter);
8516
9141
  if (tasks.length === 0) {
8517
- return { content: [{ type: "text", text: "No tasks found." }] };
9142
+ return { content: [{ type: "text", text: total > 0 ? `No tasks in this page (total: ${total}).` : "No tasks found." }] };
8518
9143
  }
8519
9144
  const text = tasks.map((t) => {
8520
9145
  const lock = t.locked_by ? ` [locked by ${t.locked_by}]` : "";
@@ -8522,13 +9147,15 @@ var init_mcp = __esm(() => {
8522
9147
  return `[${t.status}] ${t.id.slice(0, 8)} | ${t.priority} | ${t.title}${assigned}${lock}`;
8523
9148
  }).join(`
8524
9149
  `);
9150
+ const pagination = resolved.limit ? `
9151
+ (showing ${tasks.length} of ${total}, offset: ${resolved.offset || 0})` : "";
8525
9152
  return { content: [{ type: "text", text: `${tasks.length} task(s):
8526
- ${text}` }] };
9153
+ ${text}${pagination}` }] };
8527
9154
  } catch (e) {
8528
9155
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8529
9156
  }
8530
9157
  });
8531
- server.tool("get_task", "Get full task details with relations", {
9158
+ server.tool("get_task", "Get full task details with subtasks, deps, and comments.", {
8532
9159
  id: exports_external.string()
8533
9160
  }, async ({ id }) => {
8534
9161
  try {
@@ -8597,7 +9224,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
8597
9224
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8598
9225
  }
8599
9226
  });
8600
- server.tool("delete_task", "Delete a task permanently", {
9227
+ server.tool("delete_task", "Delete a task permanently. Subtasks cascade-deleted.", {
8601
9228
  id: exports_external.string()
8602
9229
  }, async ({ id }) => {
8603
9230
  try {
@@ -8613,7 +9240,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
8613
9240
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8614
9241
  }
8615
9242
  });
8616
- server.tool("start_task", "Claim, lock, and set task status to in_progress.", {
9243
+ server.tool("start_task", "Claim, lock, and set task to in_progress.", {
8617
9244
  id: exports_external.string(),
8618
9245
  agent_id: exports_external.string()
8619
9246
  }, async ({ id, agent_id }) => {
@@ -8625,19 +9252,26 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
8625
9252
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8626
9253
  }
8627
9254
  });
8628
- server.tool("complete_task", "Mark task completed and release lock.", {
9255
+ server.tool("complete_task", "Complete a task. For recurring tasks, auto-spawns next instance.", {
8629
9256
  id: exports_external.string(),
8630
- agent_id: exports_external.string().optional()
8631
- }, async ({ id, agent_id }) => {
9257
+ agent_id: exports_external.string().optional(),
9258
+ skip_recurrence: exports_external.boolean().optional()
9259
+ }, async ({ id, agent_id, skip_recurrence }) => {
8632
9260
  try {
8633
9261
  const resolvedId = resolveId(id);
8634
- const task = completeTask(resolvedId, agent_id);
8635
- return { content: [{ type: "text", text: `completed: ${formatTask(task)}` }] };
9262
+ const task = completeTask(resolvedId, agent_id, undefined, { skip_recurrence });
9263
+ let text = `completed: ${formatTask(task)}`;
9264
+ if (task.metadata._next_recurrence) {
9265
+ const next = task.metadata._next_recurrence;
9266
+ text += `
9267
+ next: ${next.short_id || next.id.slice(0, 8)} due ${next.due_at}`;
9268
+ }
9269
+ return { content: [{ type: "text", text }] };
8636
9270
  } catch (e) {
8637
9271
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8638
9272
  }
8639
9273
  });
8640
- server.tool("lock_task", "Acquire exclusive lock on a task", {
9274
+ server.tool("lock_task", "Acquire exclusive lock. Expires after 30 min. Idempotent per agent.", {
8641
9275
  id: exports_external.string(),
8642
9276
  agent_id: exports_external.string()
8643
9277
  }, async ({ id, agent_id }) => {
@@ -8652,7 +9286,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
8652
9286
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8653
9287
  }
8654
9288
  });
8655
- server.tool("unlock_task", "Release exclusive lock on a task", {
9289
+ server.tool("unlock_task", "Release exclusive lock on a task.", {
8656
9290
  id: exports_external.string(),
8657
9291
  agent_id: exports_external.string().optional()
8658
9292
  }, async ({ id, agent_id }) => {
@@ -8664,7 +9298,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
8664
9298
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8665
9299
  }
8666
9300
  });
8667
- server.tool("add_dependency", "Add a dependency: task_id depends on depends_on.", {
9301
+ server.tool("add_dependency", "Add a dependency. Prevents cycles via BFS detection.", {
8668
9302
  task_id: exports_external.string(),
8669
9303
  depends_on: exports_external.string()
8670
9304
  }, async ({ task_id, depends_on }) => {
@@ -8677,7 +9311,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
8677
9311
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8678
9312
  }
8679
9313
  });
8680
- server.tool("remove_dependency", "Remove a dependency between tasks", {
9314
+ server.tool("remove_dependency", "Remove a dependency link between two tasks.", {
8681
9315
  task_id: exports_external.string(),
8682
9316
  depends_on: exports_external.string()
8683
9317
  }, async ({ task_id, depends_on }) => {
@@ -8695,7 +9329,7 @@ Parent: ${task.parent.id.slice(0, 8)} | ${task.parent.title}`);
8695
9329
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8696
9330
  }
8697
9331
  });
8698
- server.tool("add_comment", "Add a comment/note to a task", {
9332
+ server.tool("add_comment", "Add a comment or note to a task. Comments are append-only.", {
8699
9333
  task_id: exports_external.string(),
8700
9334
  content: exports_external.string(),
8701
9335
  agent_id: exports_external.string().optional(),
@@ -8726,7 +9360,7 @@ ${text}` }] };
8726
9360
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8727
9361
  }
8728
9362
  });
8729
- server.tool("create_project", "Register a new project", {
9363
+ server.tool("create_project", "Register a new project with auto-generated task prefix.", {
8730
9364
  name: exports_external.string(),
8731
9365
  path: exports_external.string(),
8732
9366
  description: exports_external.string().optional(),
@@ -8745,7 +9379,7 @@ ${text}` }] };
8745
9379
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8746
9380
  }
8747
9381
  });
8748
- server.tool("create_plan", "Create a new plan", {
9382
+ server.tool("create_plan", "Create a plan to group related tasks.", {
8749
9383
  name: exports_external.string(),
8750
9384
  project_id: exports_external.string().optional(),
8751
9385
  description: exports_external.string().optional(),
@@ -8770,7 +9404,7 @@ ${text}` }] };
8770
9404
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8771
9405
  }
8772
9406
  });
8773
- server.tool("list_plans", "List plans with optional project filter", {
9407
+ server.tool("list_plans", "List all plans, optionally filtered by project.", {
8774
9408
  project_id: exports_external.string().optional()
8775
9409
  }, async ({ project_id }) => {
8776
9410
  try {
@@ -8790,7 +9424,7 @@ ${text}` }] };
8790
9424
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8791
9425
  }
8792
9426
  });
8793
- server.tool("get_plan", "Get plan details", {
9427
+ server.tool("get_plan", "Get plan details including status and timestamps.", {
8794
9428
  id: exports_external.string()
8795
9429
  }, async ({ id }) => {
8796
9430
  try {
@@ -8815,7 +9449,7 @@ ${text}` }] };
8815
9449
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8816
9450
  }
8817
9451
  });
8818
- server.tool("update_plan", "Update a plan", {
9452
+ server.tool("update_plan", "Update plan fields (name, description, status).", {
8819
9453
  id: exports_external.string(),
8820
9454
  name: exports_external.string().optional(),
8821
9455
  description: exports_external.string().optional(),
@@ -8839,7 +9473,7 @@ ${text}` }] };
8839
9473
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8840
9474
  }
8841
9475
  });
8842
- server.tool("delete_plan", "Delete a plan", {
9476
+ server.tool("delete_plan", "Delete a plan. Tasks in the plan are orphaned (not deleted).", {
8843
9477
  id: exports_external.string()
8844
9478
  }, async ({ id }) => {
8845
9479
  try {
@@ -8855,15 +9489,34 @@ ${text}` }] };
8855
9489
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8856
9490
  }
8857
9491
  });
8858
- server.tool("search_tasks", "Full-text search across task titles, descriptions, tags.", {
9492
+ server.tool("search_tasks", "Full-text search across tasks with filters.", {
8859
9493
  query: exports_external.string(),
8860
9494
  project_id: exports_external.string().optional(),
8861
- task_list_id: exports_external.string().optional()
8862
- }, async ({ query, project_id, task_list_id }) => {
9495
+ task_list_id: exports_external.string().optional(),
9496
+ status: exports_external.union([
9497
+ exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]),
9498
+ exports_external.array(exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]))
9499
+ ]).optional(),
9500
+ priority: exports_external.union([
9501
+ exports_external.enum(["low", "medium", "high", "critical"]),
9502
+ exports_external.array(exports_external.enum(["low", "medium", "high", "critical"]))
9503
+ ]).optional(),
9504
+ assigned_to: exports_external.string().optional(),
9505
+ agent_id: exports_external.string().optional(),
9506
+ created_after: exports_external.string().optional(),
9507
+ updated_after: exports_external.string().optional(),
9508
+ has_dependencies: exports_external.boolean().optional(),
9509
+ is_blocked: exports_external.boolean().optional()
9510
+ }, async ({ query, project_id, task_list_id, ...filters }) => {
8863
9511
  try {
8864
9512
  const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
8865
9513
  const resolvedTaskListId = task_list_id ? resolveId(task_list_id, "task_lists") : undefined;
8866
- const tasks = searchTasks(query, resolvedProjectId, resolvedTaskListId);
9514
+ const tasks = searchTasks({
9515
+ query,
9516
+ project_id: resolvedProjectId,
9517
+ task_list_id: resolvedTaskListId,
9518
+ ...filters
9519
+ });
8867
9520
  if (tasks.length === 0) {
8868
9521
  return { content: [{ type: "text", text: `No tasks matching "${query}".` }] };
8869
9522
  }
@@ -8875,7 +9528,7 @@ ${text}` }] };
8875
9528
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8876
9529
  }
8877
9530
  });
8878
- server.tool("sync", "Sync tasks with an agent task list.", {
9531
+ server.tool("sync", "Sync tasks between local DB and agent task list.", {
8879
9532
  task_list_id: exports_external.string().optional(),
8880
9533
  agent: exports_external.string().optional(),
8881
9534
  all_agents: exports_external.boolean().optional(),
@@ -8923,7 +9576,7 @@ ${text}` }] };
8923
9576
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8924
9577
  }
8925
9578
  });
8926
- server.tool("register_agent", "Register an agent (idempotent by name).", {
9579
+ server.tool("register_agent", "Register an agent (idempotent by name). Updates last_seen_at.", {
8927
9580
  name: exports_external.string(),
8928
9581
  description: exports_external.string().optional()
8929
9582
  }, async ({ name, description }) => {
@@ -8960,7 +9613,7 @@ ${text}` }] };
8960
9613
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
8961
9614
  }
8962
9615
  });
8963
- server.tool("get_agent", "Get agent details by ID or name", {
9616
+ server.tool("get_agent", "Get agent details by ID or name. Provide one of id or name.", {
8964
9617
  id: exports_external.string().optional(),
8965
9618
  name: exports_external.string().optional()
8966
9619
  }, async ({ id, name }) => {
@@ -8989,9 +9642,9 @@ ${text}` }] };
8989
9642
  }
8990
9643
  });
8991
9644
  server.tool("rename_agent", "Rename an agent. Resolve by id or current name.", {
8992
- id: exports_external.string().optional().describe("Agent ID"),
8993
- name: exports_external.string().optional().describe("Current agent name"),
8994
- new_name: exports_external.string().describe("New name for the agent")
9645
+ id: exports_external.string().optional(),
9646
+ name: exports_external.string().optional(),
9647
+ new_name: exports_external.string()
8995
9648
  }, async ({ id, name, new_name }) => {
8996
9649
  try {
8997
9650
  if (!id && !name) {
@@ -9014,8 +9667,8 @@ ID: ${updated.id}`
9014
9667
  }
9015
9668
  });
9016
9669
  server.tool("delete_agent", "Delete an agent permanently. Resolve by id or name.", {
9017
- id: exports_external.string().optional().describe("Agent ID"),
9018
- name: exports_external.string().optional().describe("Agent name")
9670
+ id: exports_external.string().optional(),
9671
+ name: exports_external.string().optional()
9019
9672
  }, async ({ id, name }) => {
9020
9673
  try {
9021
9674
  if (!id && !name) {
@@ -9037,7 +9690,7 @@ ID: ${updated.id}`
9037
9690
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
9038
9691
  }
9039
9692
  });
9040
- server.tool("create_task_list", "Create a new task list", {
9693
+ server.tool("create_task_list", "Create a task list container for organizing tasks.", {
9041
9694
  name: exports_external.string(),
9042
9695
  slug: exports_external.string().optional(),
9043
9696
  project_id: exports_external.string().optional(),
@@ -9063,7 +9716,7 @@ Description: ${list.description}` : ""}`
9063
9716
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
9064
9717
  }
9065
9718
  });
9066
- server.tool("list_task_lists", "List task lists, optionally filtered by project", {
9719
+ server.tool("list_task_lists", "List all task lists, optionally filtered by project.", {
9067
9720
  project_id: exports_external.string().optional()
9068
9721
  }, async ({ project_id }) => {
9069
9722
  try {
@@ -9083,7 +9736,7 @@ ${text}` }] };
9083
9736
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
9084
9737
  }
9085
9738
  });
9086
- server.tool("get_task_list", "Get task list details", {
9739
+ server.tool("get_task_list", "Get task list details including slug and metadata.", {
9087
9740
  id: exports_external.string()
9088
9741
  }, async ({ id }) => {
9089
9742
  try {
@@ -9111,7 +9764,7 @@ ${text}` }] };
9111
9764
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
9112
9765
  }
9113
9766
  });
9114
- server.tool("update_task_list", "Update a task list", {
9767
+ server.tool("update_task_list", "Update a task list's name or description.", {
9115
9768
  id: exports_external.string(),
9116
9769
  name: exports_external.string().optional(),
9117
9770
  description: exports_external.string().optional()
@@ -9132,7 +9785,7 @@ Slug: ${list.slug}`
9132
9785
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
9133
9786
  }
9134
9787
  });
9135
- server.tool("delete_task_list", "Delete a task list. Tasks lose association but keep data.", {
9788
+ server.tool("delete_task_list", "Delete a task list. Tasks are orphaned, not deleted.", {
9136
9789
  id: exports_external.string()
9137
9790
  }, async ({ id }) => {
9138
9791
  try {
@@ -9148,7 +9801,7 @@ Slug: ${list.slug}`
9148
9801
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
9149
9802
  }
9150
9803
  });
9151
- server.tool("get_task_history", "Get audit log for a task.", {
9804
+ server.tool("get_task_history", "Get audit log \u2014 field changes with timestamps and actors.", {
9152
9805
  task_id: exports_external.string()
9153
9806
  }, async ({ task_id }) => {
9154
9807
  try {
@@ -9165,7 +9818,7 @@ ${text}` }] };
9165
9818
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
9166
9819
  }
9167
9820
  });
9168
- server.tool("get_recent_activity", "Get recent task changes across all tasks.", {
9821
+ server.tool("get_recent_activity", "Get recent task changes \u2014 global activity feed.", {
9169
9822
  limit: exports_external.number().optional()
9170
9823
  }, async ({ limit }) => {
9171
9824
  try {
@@ -9181,7 +9834,7 @@ ${text}` }] };
9181
9834
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
9182
9835
  }
9183
9836
  });
9184
- server.tool("create_webhook", "Register a webhook to receive task change events.", {
9837
+ server.tool("create_webhook", "Register a webhook for task change events.", {
9185
9838
  url: exports_external.string(),
9186
9839
  events: exports_external.array(exports_external.string()).optional(),
9187
9840
  secret: exports_external.string().optional()
@@ -9208,7 +9861,7 @@ ${text}` }] };
9208
9861
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
9209
9862
  }
9210
9863
  });
9211
- server.tool("delete_webhook", "Delete a webhook", {
9864
+ server.tool("delete_webhook", "Delete a webhook by ID.", {
9212
9865
  id: exports_external.string()
9213
9866
  }, async ({ id }) => {
9214
9867
  try {
@@ -9274,7 +9927,7 @@ ${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
9274
9927
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
9275
9928
  }
9276
9929
  });
9277
- server.tool("delete_template", "Delete a task template", { id: exports_external.string() }, async ({ id }) => {
9930
+ server.tool("delete_template", "Delete a task template by ID.", { id: exports_external.string() }, async ({ id }) => {
9278
9931
  try {
9279
9932
  const { deleteTemplate: deleteTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
9280
9933
  const deleted = deleteTemplate2(id);
@@ -9283,7 +9936,7 @@ ${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
9283
9936
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
9284
9937
  }
9285
9938
  });
9286
- server.tool("approve_task", "Approve a task that requires approval.", {
9939
+ server.tool("approve_task", "Approve a task with requires_approval=true.", {
9287
9940
  id: exports_external.string(),
9288
9941
  agent_id: exports_external.string().optional()
9289
9942
  }, async ({ id, agent_id }) => {
@@ -9302,7 +9955,31 @@ ${task.id.slice(0, 8)} | ${task.priority} | ${task.title}` }] };
9302
9955
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
9303
9956
  }
9304
9957
  });
9305
- server.tool("get_my_tasks", "Get assigned tasks and stats for an agent.", {
9958
+ server.tool("fail_task", "Mark a task as failed with structured reason and optional auto-retry.", {
9959
+ id: exports_external.string(),
9960
+ agent_id: exports_external.string().optional(),
9961
+ reason: exports_external.string().optional(),
9962
+ error_code: exports_external.string().optional(),
9963
+ retry: exports_external.boolean().optional(),
9964
+ retry_after: exports_external.string().optional()
9965
+ }, async ({ id, agent_id, reason, error_code, retry, retry_after }) => {
9966
+ try {
9967
+ const resolvedId = resolveId(id);
9968
+ const result = failTask(resolvedId, agent_id, reason, { retry, retry_after, error_code });
9969
+ let text = `failed: ${formatTask(result.task)}`;
9970
+ if (reason)
9971
+ text += `
9972
+ Reason: ${reason}`;
9973
+ if (result.retryTask) {
9974
+ text += `
9975
+ Retry task created: ${formatTask(result.retryTask)}`;
9976
+ }
9977
+ return { content: [{ type: "text", text }] };
9978
+ } catch (e) {
9979
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
9980
+ }
9981
+ });
9982
+ server.tool("get_my_tasks", "Get tasks assigned to/created by an agent with stats.", {
9306
9983
  agent_name: exports_external.string()
9307
9984
  }, async ({ agent_name }) => {
9308
9985
  try {
@@ -9335,7 +10012,7 @@ In Progress:`);
9335
10012
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
9336
10013
  }
9337
10014
  });
9338
- server.tool("get_org_chart", "Get agent org chart \u2014 who reports to who.", {}, async () => {
10015
+ server.tool("get_org_chart", "Get agent org chart showing reporting hierarchy.", {}, async () => {
9339
10016
  try {
9340
10017
  let render = function(nodes, indent = 0) {
9341
10018
  return nodes.map((n) => {
@@ -9357,7 +10034,7 @@ In Progress:`);
9357
10034
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
9358
10035
  }
9359
10036
  });
9360
- server.tool("set_reports_to", "Set who an agent reports to in the org chart.", {
10037
+ server.tool("set_reports_to", "Set agent reporting relationship in org chart.", {
9361
10038
  agent_name: exports_external.string(),
9362
10039
  manager_name: exports_external.string().optional()
9363
10040
  }, async ({ agent_name, manager_name }) => {
@@ -9380,7 +10057,326 @@ In Progress:`);
9380
10057
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
9381
10058
  }
9382
10059
  });
9383
- server.tool("search_tools", "List tool names matching a query.", { query: exports_external.string().optional() }, async ({ query }) => {
10060
+ server.tool("bulk_update_tasks", "Update multiple tasks at once with the same changes.", {
10061
+ task_ids: exports_external.array(exports_external.string()),
10062
+ status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
10063
+ priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
10064
+ assigned_to: exports_external.string().optional(),
10065
+ tags: exports_external.array(exports_external.string()).optional()
10066
+ }, async ({ task_ids, ...updates }) => {
10067
+ try {
10068
+ const resolvedIds = task_ids.map((id) => resolveId(id));
10069
+ const result = bulkUpdateTasks(resolvedIds, updates);
10070
+ const parts = [`Updated ${result.updated} task(s).`];
10071
+ if (result.failed.length > 0) {
10072
+ parts.push(`Failed ${result.failed.length}:`);
10073
+ for (const f of result.failed)
10074
+ parts.push(` ${f.id.slice(0, 8)}: ${f.error}`);
10075
+ }
10076
+ return { content: [{ type: "text", text: parts.join(`
10077
+ `) }] };
10078
+ } catch (e) {
10079
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
10080
+ }
10081
+ });
10082
+ server.tool("clone_task", "Duplicate a task with optional field overrides.", {
10083
+ task_id: exports_external.string(),
10084
+ title: exports_external.string().optional(),
10085
+ description: exports_external.string().optional(),
10086
+ priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
10087
+ status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
10088
+ project_id: exports_external.string().optional(),
10089
+ plan_id: exports_external.string().optional(),
10090
+ task_list_id: exports_external.string().optional(),
10091
+ assigned_to: exports_external.string().optional(),
10092
+ tags: exports_external.array(exports_external.string()).optional(),
10093
+ estimated_minutes: exports_external.number().optional()
10094
+ }, async ({ task_id, ...overrides }) => {
10095
+ try {
10096
+ const resolvedId = resolveId(task_id);
10097
+ const resolved = { ...overrides };
10098
+ if (resolved.project_id)
10099
+ resolved.project_id = resolveId(resolved.project_id, "projects");
10100
+ if (resolved.plan_id)
10101
+ resolved.plan_id = resolveId(resolved.plan_id, "plans");
10102
+ if (resolved.task_list_id)
10103
+ resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
10104
+ const task = cloneTask(resolvedId, resolved);
10105
+ return { content: [{ type: "text", text: `cloned: ${formatTask(task)}` }] };
10106
+ } catch (e) {
10107
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
10108
+ }
10109
+ });
10110
+ server.tool("get_task_stats", "Get task analytics: counts by status, priority, agent.", {
10111
+ project_id: exports_external.string().optional(),
10112
+ task_list_id: exports_external.string().optional(),
10113
+ agent_id: exports_external.string().optional()
10114
+ }, async ({ project_id, task_list_id, agent_id }) => {
10115
+ try {
10116
+ const filters = {};
10117
+ if (project_id)
10118
+ filters.project_id = resolveId(project_id, "projects");
10119
+ if (task_list_id)
10120
+ filters.task_list_id = resolveId(task_list_id, "task_lists");
10121
+ if (agent_id)
10122
+ filters.agent_id = agent_id;
10123
+ const stats = getTaskStats(Object.keys(filters).length > 0 ? filters : undefined);
10124
+ return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
10125
+ } catch (e) {
10126
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
10127
+ }
10128
+ });
10129
+ server.tool("get_task_graph", "Get full dependency tree for a task.", {
10130
+ id: exports_external.string(),
10131
+ direction: exports_external.enum(["up", "down", "both"]).optional()
10132
+ }, async ({ id, direction }) => {
10133
+ try {
10134
+ let formatNode = function(node, indent) {
10135
+ const prefix = " ".repeat(indent);
10136
+ const idLabel = node.task.short_id || node.task.id.slice(0, 8);
10137
+ const blocked = node.task.is_blocked ? " (blocked: yes)" : "";
10138
+ let out = `${prefix}[${node.task.status}] ${idLabel} | ${node.task.title}${blocked}
10139
+ `;
10140
+ if (node.depends_on.length > 0) {
10141
+ out += `${prefix} Depends on:
10142
+ `;
10143
+ for (const dep of node.depends_on) {
10144
+ out += formatNode(dep, indent + 2);
10145
+ }
10146
+ }
10147
+ if (node.blocks.length > 0) {
10148
+ out += `${prefix} Blocks:
10149
+ `;
10150
+ for (const dep of node.blocks) {
10151
+ out += formatNode(dep, indent + 2);
10152
+ }
10153
+ }
10154
+ return out;
10155
+ };
10156
+ const taskId = resolveId(id, "tasks");
10157
+ const graph = getTaskGraph(taskId, direction || "both");
10158
+ let text = `Task: ${formatNode(graph, 0)}`;
10159
+ if (graph.depends_on.length > 0) {
10160
+ text += `
10161
+ Depends on:
10162
+ `;
10163
+ for (const dep of graph.depends_on) {
10164
+ text += formatNode(dep, 1);
10165
+ }
10166
+ }
10167
+ if (graph.blocks.length > 0) {
10168
+ text += `
10169
+ Blocks:
10170
+ `;
10171
+ for (const dep of graph.blocks) {
10172
+ text += formatNode(dep, 1);
10173
+ }
10174
+ }
10175
+ return { content: [{ type: "text", text }] };
10176
+ } catch (e) {
10177
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
10178
+ }
10179
+ });
10180
+ server.tool("bulk_create_tasks", "Create multiple tasks atomically with dependency support.", {
10181
+ tasks: exports_external.array(exports_external.object({
10182
+ temp_id: exports_external.string().optional(),
10183
+ title: exports_external.string(),
10184
+ description: exports_external.string().optional(),
10185
+ priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
10186
+ status: exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"]).optional(),
10187
+ project_id: exports_external.string().optional(),
10188
+ plan_id: exports_external.string().optional(),
10189
+ task_list_id: exports_external.string().optional(),
10190
+ agent_id: exports_external.string().optional(),
10191
+ assigned_to: exports_external.string().optional(),
10192
+ tags: exports_external.array(exports_external.string()).optional(),
10193
+ estimated_minutes: exports_external.number().optional(),
10194
+ depends_on_temp_ids: exports_external.array(exports_external.string()).optional()
10195
+ })),
10196
+ project_id: exports_external.string().optional(),
10197
+ plan_id: exports_external.string().optional(),
10198
+ task_list_id: exports_external.string().optional()
10199
+ }, async ({ tasks, project_id, plan_id, task_list_id }) => {
10200
+ try {
10201
+ const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
10202
+ const resolvedPlanId = plan_id ? resolveId(plan_id, "plans") : undefined;
10203
+ const resolvedTaskListId = task_list_id ? resolveId(task_list_id, "task_lists") : undefined;
10204
+ const enrichedTasks = tasks.map((t) => ({
10205
+ ...t,
10206
+ project_id: t.project_id || resolvedProjectId,
10207
+ plan_id: t.plan_id || resolvedPlanId,
10208
+ task_list_id: t.task_list_id || resolvedTaskListId
10209
+ }));
10210
+ const result = bulkCreateTasks(enrichedTasks);
10211
+ const lines = result.created.map((t) => {
10212
+ const tid = t.temp_id ? `[${t.temp_id}] ` : "";
10213
+ const sid = t.short_id || t.id.slice(0, 8);
10214
+ return ` ${tid}${sid} | ${t.title}`;
10215
+ });
10216
+ return { content: [{ type: "text", text: `Created ${result.created.length} task(s):
10217
+ ${lines.join(`
10218
+ `)}` }] };
10219
+ } catch (e) {
10220
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
10221
+ }
10222
+ });
10223
+ server.tool("move_task", "Move a task to a different list, project, or plan.", {
10224
+ task_id: exports_external.string(),
10225
+ task_list_id: exports_external.string().nullable().optional(),
10226
+ project_id: exports_external.string().nullable().optional(),
10227
+ plan_id: exports_external.string().nullable().optional()
10228
+ }, async ({ task_id, ...target }) => {
10229
+ try {
10230
+ const resolvedId = resolveId(task_id);
10231
+ const resolvedTarget = {};
10232
+ if (target.task_list_id !== undefined)
10233
+ resolvedTarget.task_list_id = target.task_list_id ? resolveId(target.task_list_id, "task_lists") : null;
10234
+ if (target.project_id !== undefined)
10235
+ resolvedTarget.project_id = target.project_id ? resolveId(target.project_id, "projects") : null;
10236
+ if (target.plan_id !== undefined)
10237
+ resolvedTarget.plan_id = target.plan_id ? resolveId(target.plan_id, "plans") : null;
10238
+ const task = moveTask(resolvedId, resolvedTarget);
10239
+ return { content: [{ type: "text", text: `moved: ${formatTask(task)}` }] };
10240
+ } catch (e) {
10241
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
10242
+ }
10243
+ });
10244
+ server.tool("get_next_task", "Get the best pending task to work on next.", {
10245
+ agent_id: exports_external.string().optional(),
10246
+ project_id: exports_external.string().optional(),
10247
+ task_list_id: exports_external.string().optional(),
10248
+ plan_id: exports_external.string().optional(),
10249
+ tags: exports_external.array(exports_external.string()).optional()
10250
+ }, async ({ agent_id, project_id, task_list_id, plan_id, tags }) => {
10251
+ try {
10252
+ const filters = {};
10253
+ if (project_id)
10254
+ filters.project_id = resolveId(project_id, "projects");
10255
+ if (task_list_id)
10256
+ filters.task_list_id = resolveId(task_list_id, "task_lists");
10257
+ if (plan_id)
10258
+ filters.plan_id = resolveId(plan_id, "plans");
10259
+ if (tags)
10260
+ filters.tags = tags;
10261
+ const task = getNextTask(agent_id, Object.keys(filters).length > 0 ? filters : undefined);
10262
+ if (!task) {
10263
+ return { content: [{ type: "text", text: "No tasks available \u2014 all pending tasks are blocked, locked, or none exist." }] };
10264
+ }
10265
+ return { content: [{ type: "text", text: `next: ${formatTask(task)}
10266
+ ${formatTaskDetail(task)}` }] };
10267
+ } catch (e) {
10268
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
10269
+ }
10270
+ });
10271
+ server.tool("get_active_work", "See all in-progress tasks and who is working on them.", {
10272
+ project_id: exports_external.string().optional(),
10273
+ task_list_id: exports_external.string().optional()
10274
+ }, async ({ project_id, task_list_id }) => {
10275
+ try {
10276
+ const filters = {};
10277
+ if (project_id)
10278
+ filters.project_id = resolveId(project_id, "projects");
10279
+ if (task_list_id)
10280
+ filters.task_list_id = resolveId(task_list_id, "task_lists");
10281
+ const work = getActiveWork(Object.keys(filters).length > 0 ? filters : undefined);
10282
+ if (work.length === 0) {
10283
+ return { content: [{ type: "text", text: "No active work \u2014 no tasks are currently in progress." }] };
10284
+ }
10285
+ const text = work.map((w) => {
10286
+ const id = w.short_id || w.id.slice(0, 8);
10287
+ const agent = w.assigned_to || w.locked_by || "unassigned";
10288
+ const since = w.updated_at;
10289
+ return `${agent.padEnd(12)} | ${w.priority.padEnd(8)} | ${id} | ${w.title} (since ${since})`;
10290
+ }).join(`
10291
+ `);
10292
+ return { content: [{ type: "text", text: `${work.length} active task(s):
10293
+ ${text}` }] };
10294
+ } catch (e) {
10295
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
10296
+ }
10297
+ });
10298
+ server.tool("get_tasks_changed_since", "Get tasks modified after a timestamp for incremental sync.", {
10299
+ since: exports_external.string(),
10300
+ project_id: exports_external.string().optional(),
10301
+ task_list_id: exports_external.string().optional()
10302
+ }, async ({ since, project_id, task_list_id }) => {
10303
+ try {
10304
+ const filters = {};
10305
+ if (project_id)
10306
+ filters.project_id = resolveId(project_id, "projects");
10307
+ if (task_list_id)
10308
+ filters.task_list_id = resolveId(task_list_id, "task_lists");
10309
+ const tasks = getTasksChangedSince(since, Object.keys(filters).length > 0 ? filters : undefined);
10310
+ if (tasks.length === 0) {
10311
+ return { content: [{ type: "text", text: `No tasks changed since ${since}.` }] };
10312
+ }
10313
+ const text = tasks.map((t) => {
10314
+ const assigned = t.assigned_to ? ` -> ${t.assigned_to}` : "";
10315
+ return `[${t.status}] ${t.id.slice(0, 8)} | ${t.priority} | ${t.title}${assigned} (updated: ${t.updated_at})`;
10316
+ }).join(`
10317
+ `);
10318
+ return { content: [{ type: "text", text: `${tasks.length} task(s) changed since ${since}:
10319
+ ${text}` }] };
10320
+ } catch (e) {
10321
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
10322
+ }
10323
+ });
10324
+ server.tool("claim_next_task", "Atomically claim, lock, and start the best pending task.", {
10325
+ agent_id: exports_external.string(),
10326
+ project_id: exports_external.string().optional(),
10327
+ task_list_id: exports_external.string().optional(),
10328
+ plan_id: exports_external.string().optional(),
10329
+ tags: exports_external.array(exports_external.string()).optional()
10330
+ }, async ({ agent_id, project_id, task_list_id, plan_id, tags }) => {
10331
+ try {
10332
+ const filters = {};
10333
+ if (project_id)
10334
+ filters.project_id = resolveId(project_id, "projects");
10335
+ if (task_list_id)
10336
+ filters.task_list_id = resolveId(task_list_id, "task_lists");
10337
+ if (plan_id)
10338
+ filters.plan_id = resolveId(plan_id, "plans");
10339
+ if (tags)
10340
+ filters.tags = tags;
10341
+ const task = claimNextTask(agent_id, Object.keys(filters).length > 0 ? filters : undefined);
10342
+ if (!task) {
10343
+ return { content: [{ type: "text", text: "No tasks available to claim." }] };
10344
+ }
10345
+ return { content: [{ type: "text", text: `claimed: ${formatTask(task)}
10346
+ ${formatTaskDetail(task)}` }] };
10347
+ } catch (e) {
10348
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
10349
+ }
10350
+ });
10351
+ server.tool("get_stale_tasks", "Find stale in_progress tasks with no recent activity.", {
10352
+ stale_minutes: exports_external.number().optional(),
10353
+ project_id: exports_external.string().optional(),
10354
+ task_list_id: exports_external.string().optional()
10355
+ }, async ({ stale_minutes, project_id, task_list_id }) => {
10356
+ try {
10357
+ const filters = {};
10358
+ if (project_id)
10359
+ filters.project_id = resolveId(project_id, "projects");
10360
+ if (task_list_id)
10361
+ filters.task_list_id = resolveId(task_list_id, "task_lists");
10362
+ const tasks = getStaleTasks(stale_minutes || 30, Object.keys(filters).length > 0 ? filters : undefined);
10363
+ if (tasks.length === 0) {
10364
+ return { content: [{ type: "text", text: "No stale tasks found." }] };
10365
+ }
10366
+ const text = tasks.map((t) => {
10367
+ const id = t.short_id || t.id.slice(0, 8);
10368
+ const agent = t.locked_by || t.assigned_to || "unknown";
10369
+ const staleFor = Math.round((Date.now() - new Date(t.updated_at).getTime()) / 60000);
10370
+ return `${id} | ${agent} | ${t.title} (stale ${staleFor}min)`;
10371
+ }).join(`
10372
+ `);
10373
+ return { content: [{ type: "text", text: `${tasks.length} stale task(s):
10374
+ ${text}` }] };
10375
+ } catch (e) {
10376
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
10377
+ }
10378
+ });
10379
+ server.tool("search_tools", "List all tool names, optionally filtered by substring.", { query: exports_external.string().optional() }, async ({ query }) => {
9384
10380
  const all = [
9385
10381
  "create_task",
9386
10382
  "list_tasks",
@@ -9389,6 +10385,7 @@ In Progress:`);
9389
10385
  "delete_task",
9390
10386
  "start_task",
9391
10387
  "complete_task",
10388
+ "fail_task",
9392
10389
  "lock_task",
9393
10390
  "unlock_task",
9394
10391
  "approve_task",
@@ -9405,7 +10402,11 @@ In Progress:`);
9405
10402
  "register_agent",
9406
10403
  "list_agents",
9407
10404
  "get_agent",
10405
+ "rename_agent",
10406
+ "delete_agent",
9408
10407
  "get_my_tasks",
10408
+ "get_org_chart",
10409
+ "set_reports_to",
9409
10410
  "create_task_list",
9410
10411
  "list_task_lists",
9411
10412
  "get_task_list",
@@ -9413,6 +10414,10 @@ In Progress:`);
9413
10414
  "delete_task_list",
9414
10415
  "search_tasks",
9415
10416
  "sync",
10417
+ "clone_task",
10418
+ "move_task",
10419
+ "get_next_task",
10420
+ "claim_next_task",
9416
10421
  "get_task_history",
9417
10422
  "get_recent_activity",
9418
10423
  "create_webhook",
@@ -9422,6 +10427,13 @@ In Progress:`);
9422
10427
  "list_templates",
9423
10428
  "create_task_from_template",
9424
10429
  "delete_template",
10430
+ "bulk_update_tasks",
10431
+ "bulk_create_tasks",
10432
+ "get_task_stats",
10433
+ "get_task_graph",
10434
+ "get_active_work",
10435
+ "get_tasks_changed_since",
10436
+ "get_stale_tasks",
9425
10437
  "search_tools",
9426
10438
  "describe_tools"
9427
10439
  ];
@@ -9429,27 +10441,175 @@ In Progress:`);
9429
10441
  const matches = q ? all.filter((n) => n.includes(q)) : all;
9430
10442
  return { content: [{ type: "text", text: matches.join(", ") }] };
9431
10443
  });
9432
- server.tool("describe_tools", "Get descriptions for specific tools by name.", { names: exports_external.array(exports_external.string()) }, async ({ names }) => {
10444
+ server.tool("describe_tools", "Get detailed parameter info for specific tools by name.", { names: exports_external.array(exports_external.string()) }, async ({ names }) => {
9433
10445
  const descriptions = {
9434
- create_task: "Create a task. Params: title(req), description, priority, project_id, plan_id, tags, assigned_to, estimated_minutes, requires_approval",
9435
- list_tasks: "List tasks. Params: status, priority, project_id, plan_id, assigned_to, tags, limit",
9436
- get_task: "Get full task details. Params: id",
9437
- update_task: "Update task fields. Params: id, version(req), title, description, status, priority, tags, assigned_to, due_at",
9438
- delete_task: "Delete a task. Params: id",
9439
- start_task: "Claim, lock, and start a task. Params: id",
9440
- complete_task: "Mark task completed. Params: id, agent_id",
9441
- approve_task: "Approve task requiring approval. Params: id, agent_id",
9442
- create_plan: "Create a plan. Params: name, description, project_id, task_list_id, agent_id, status",
9443
- list_plans: "List plans. Params: project_id",
9444
- get_plan: "Get plan with tasks. Params: id",
9445
- search_tasks: "Full-text search tasks. Params: query, project_id, task_list_id",
9446
- get_my_tasks: "Get your tasks and stats. Params: agent_name",
9447
- get_task_history: "Get task audit log. Params: task_id",
9448
- get_recent_activity: "Recent changes across all tasks. Params: limit",
9449
- create_template: "Create task template. Params: name, title_pattern, description, priority, tags",
9450
- create_task_from_template: "Create task from template. Params: template_id, title, priority, assigned_to"
10446
+ create_task: `Create a new task.
10447
+ Params: title(string, req), description(string), priority(low|medium|high|critical, default:medium), status(pending|in_progress|completed|failed|cancelled, default:pending), project_id(string), parent_id(string \u2014 creates subtask), plan_id(string), task_list_id(string), agent_id(string), assigned_to(string), tags(string[]), metadata(object), estimated_minutes(number), requires_approval(boolean), recurrence_rule(string \u2014 e.g. 'every day', 'every weekday', 'every 2 weeks', 'every monday'), session_id(string), working_dir(string)
10448
+ Example: {title: 'Daily standup', recurrence_rule: 'every weekday', priority: 'medium'}`,
10449
+ list_tasks: `List tasks with optional filters. Supports pagination.
10450
+ Params: status(string|string[]), priority(string|string[]), project_id(string), plan_id(string), task_list_id(string), assigned_to(string), tags(string[]), has_recurrence(boolean \u2014 true=only recurring, false=only non-recurring), limit(number), offset(number)
10451
+ Example: {status: ['pending', 'in_progress'], has_recurrence: true, limit: 20}`,
10452
+ get_task: `Get full task details with subtasks, deps, and comments.
10453
+ Params: id(string, req \u2014 task ID, short_id like 'APP-00001', or partial ID)
10454
+ Example: {id: 'a1b2c3d4'}`,
10455
+ update_task: `Update task fields. Requires version for optimistic locking (get it from get_task first).
10456
+ Params: id(string, req), version(number, req), title(string), description(string), status(pending|in_progress|completed|failed|cancelled), priority(low|medium|high|critical), assigned_to(string), tags(string[]), metadata(object), plan_id(string), task_list_id(string)
10457
+ Example: {id: 'a1b2c3d4', version: 3, status: 'completed'}`,
10458
+ delete_task: `Delete a task permanently. Subtasks cascade-delete. Dependencies removed.
10459
+ Params: id(string, req)
10460
+ Example: {id: 'a1b2c3d4'}`,
10461
+ start_task: `Claim, lock, and set task status to in_progress in one call.
10462
+ Params: id(string, req), agent_id(string, req \u2014 your 8-char agent ID)
10463
+ Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
10464
+ complete_task: `Mark task completed, release lock, set completed_at timestamp. For recurring tasks, auto-spawns next instance unless skip_recurrence is true.
10465
+ Params: id(string, req), agent_id(string, optional \u2014 required if locked by different agent), skip_recurrence(boolean \u2014 set true to prevent auto-creating next recurring instance)
10466
+ Example: {id: 'a1b2c3d4', skip_recurrence: false}`,
10467
+ lock_task: `Acquire exclusive lock on a task. Locks auto-expire after 30 min. Re-locking by same agent is idempotent.
10468
+ Params: id(string, req), agent_id(string, req)
10469
+ Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
10470
+ unlock_task: `Release exclusive lock on a task.
10471
+ Params: id(string, req), agent_id(string, optional \u2014 omit to force-unlock)
10472
+ Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
10473
+ approve_task: `Approve a task with requires_approval=true. Must be approved before completion.
10474
+ Params: id(string, req), agent_id(string, optional \u2014 defaults to 'system')
10475
+ Example: {id: 'a1b2c3d4', agent_id: 'e5f6g7h8'}`,
10476
+ fail_task: `Mark a task as failed with structured reason and optional auto-retry. Stores failure info in metadata._failure, releases lock.
10477
+ Params: id(string, req), agent_id(string, optional), reason(string), error_code(string \u2014 e.g. 'TIMEOUT'), retry(boolean \u2014 create new pending copy), retry_after(ISO date)
10478
+ Example: {id: 'a1b2c3d4', reason: 'Build timeout', error_code: 'TIMEOUT', retry: true}`,
10479
+ add_dependency: `Add a dependency: task_id depends on depends_on. Prevents cycles via BFS.
10480
+ Params: task_id(string, req), depends_on(string, req)
10481
+ Example: {task_id: 'abc12345', depends_on: 'def67890'}`,
10482
+ remove_dependency: `Remove a dependency link between two tasks.
10483
+ Params: task_id(string, req), depends_on(string, req)
10484
+ Example: {task_id: 'abc12345', depends_on: 'def67890'}`,
10485
+ add_comment: `Add a comment/note to a task. Comments are append-only.
10486
+ Params: task_id(string, req), content(string, req), agent_id(string), session_id(string)
10487
+ Example: {task_id: 'a1b2c3d4', content: 'Blocked by API rate limit'}`,
10488
+ create_project: `Register a new project. Auto-generates task prefix for short IDs (e.g. APP-00001).
10489
+ Params: name(string, req), path(string, req \u2014 unique absolute path), description(string), task_list_id(string)
10490
+ Example: {name: 'my-app', path: '/Users/dev/my-app'}`,
10491
+ list_projects: "List all registered projects. No params.",
10492
+ create_plan: `Create a plan to group related tasks.
10493
+ Params: name(string, req), project_id(string), description(string), status(active|completed|archived, default:active), task_list_id(string), agent_id(string)
10494
+ Example: {name: 'Sprint 1', project_id: 'a1b2c3d4'}`,
10495
+ list_plans: `List all plans, optionally filtered by project.
10496
+ Params: project_id(string)
10497
+ Example: {project_id: 'a1b2c3d4'}`,
10498
+ get_plan: `Get plan details (name, status, description, timestamps).
10499
+ Params: id(string, req)
10500
+ Example: {id: 'a1b2c3d4'}`,
10501
+ update_plan: `Update plan fields.
10502
+ Params: id(string, req), name(string), description(string), status(active|completed|archived), task_list_id(string), agent_id(string)
10503
+ Example: {id: 'a1b2c3d4', status: 'completed'}`,
10504
+ delete_plan: `Delete a plan. Tasks in the plan are orphaned, not deleted.
10505
+ Params: id(string, req)
10506
+ Example: {id: 'a1b2c3d4'}`,
10507
+ register_agent: `Register an agent (idempotent by name). Returns existing agent if name matches.
10508
+ Params: name(string, req \u2014 e.g. 'maximus'), description(string)
10509
+ Example: {name: 'maximus', description: 'Backend developer'}`,
10510
+ list_agents: "List all registered agents with IDs, names, and last seen timestamps. No params.",
10511
+ get_agent: `Get agent details by ID or name. Provide one of id or name.
10512
+ Params: id(string), name(string)
10513
+ Example: {name: 'maximus'}`,
10514
+ rename_agent: `Rename an agent. Resolve by id or current name.
10515
+ Params: id(string), name(string \u2014 current name), new_name(string, req)
10516
+ Example: {name: 'old-name', new_name: 'new-name'}`,
10517
+ delete_agent: `Delete an agent permanently. Resolve by id or name.
10518
+ Params: id(string), name(string)
10519
+ Example: {name: 'maximus'}`,
10520
+ get_my_tasks: `Get all tasks assigned to/created by an agent, with stats (pending/active/done/rate).
10521
+ Params: agent_name(string, req)
10522
+ Example: {agent_name: 'maximus'}`,
10523
+ get_org_chart: "Get agent org chart showing reporting hierarchy. No params.",
10524
+ set_reports_to: `Set who an agent reports to in the org chart. Omit manager_name for top-level.
10525
+ Params: agent_name(string, req), manager_name(string, optional)
10526
+ Example: {agent_name: 'brutus', manager_name: 'maximus'}`,
10527
+ create_task_list: `Create a task list \u2014 a container/folder for organizing tasks.
10528
+ Params: name(string, req), slug(string \u2014 auto-generated if omitted), project_id(string), description(string)
10529
+ Example: {name: 'Sprint 1', project_id: 'a1b2c3d4'}`,
10530
+ list_task_lists: `List all task lists, optionally filtered by project.
10531
+ Params: project_id(string)
10532
+ Example: {project_id: 'a1b2c3d4'}`,
10533
+ get_task_list: `Get task list details (name, slug, project, metadata).
10534
+ Params: id(string, req)
10535
+ Example: {id: 'a1b2c3d4'}`,
10536
+ update_task_list: `Update a task list's name or description.
10537
+ Params: id(string, req), name(string), description(string)
10538
+ Example: {id: 'a1b2c3d4', name: 'Sprint 2'}`,
10539
+ delete_task_list: `Delete a task list. Tasks are orphaned (not deleted).
10540
+ Params: id(string, req)
10541
+ Example: {id: 'a1b2c3d4'}`,
10542
+ search_tasks: `Full-text search across task titles, descriptions, and tags. Supports filters.
10543
+ Params: query(string, req), project_id(string), task_list_id(string), status(string|string[]), priority(string|string[]), assigned_to(string), agent_id(string), created_after(ISO date), updated_after(ISO date), has_dependencies(boolean), is_blocked(boolean)
10544
+ Example: {query: 'auth bug', status: 'pending'}`,
10545
+ get_next_task: `Get the optimal next task to work on \u2014 finds highest-priority pending task that is not blocked or locked. Prefers tasks assigned to the given agent.
10546
+ Params: agent_id(string \u2014 prefers your tasks), project_id(string), task_list_id(string), plan_id(string), tags(string[])
10547
+ Example: {agent_id: 'a1b2c3d4', project_id: 'e5f6g7h8'}`,
10548
+ claim_next_task: `Atomically find the best pending task, lock it, and start it \u2014 one call instead of get_next_task + start_task. Eliminates race conditions between agents.
10549
+ Params: agent_id(string, req \u2014 used for lock and assignment), project_id(string), task_list_id(string), plan_id(string), tags(string[])
10550
+ Example: {agent_id: 'a1b2c3d4', project_id: 'e5f6g7h8'}`,
10551
+ sync: `Sync tasks between local DB and agent task list (e.g. Claude Code).
10552
+ Params: agent(string, default:'claude'), task_list_id(string), all_agents(boolean), project_id(string), direction(push|pull|both, default:both), prefer(local|remote, default:remote)
10553
+ Example: {agent: 'claude', direction: 'push'}`,
10554
+ clone_task: `Duplicate a task with optional field overrides. Creates new independent copy.
10555
+ Params: task_id(string, req), title(string), description(string), priority(low|medium|high|critical), status(pending|in_progress|completed|failed|cancelled), project_id(string), plan_id(string), task_list_id(string), assigned_to(string), tags(string[]), estimated_minutes(number)
10556
+ Example: {task_id: 'a1b2c3d4', title: 'Cloned task', assigned_to: 'brutus'}`,
10557
+ move_task: `Move a task to a different list, project, or plan.
10558
+ Params: task_id(string, req), task_list_id(string|null), project_id(string|null), plan_id(string|null)
10559
+ Example: {task_id: 'a1b2c3d4', task_list_id: 'e5f6g7h8'}`,
10560
+ bulk_update_tasks: `Update multiple tasks at once with the same changes.
10561
+ Params: task_ids(string[], req), status(pending|in_progress|completed|failed|cancelled), priority(low|medium|high|critical), assigned_to(string), tags(string[])
10562
+ Example: {task_ids: ['abc12345', 'def67890'], status: 'completed'}`,
10563
+ bulk_create_tasks: `Create multiple tasks atomically. Supports inter-task dependencies via temp_id references.
10564
+ Params: tasks(array, req \u2014 [{temp_id, title, description, priority, status, project_id, plan_id, task_list_id, agent_id, assigned_to, tags, estimated_minutes, depends_on_temp_ids}]), project_id(string \u2014 default for all), plan_id(string \u2014 default for all), task_list_id(string \u2014 default for all)
10565
+ Example: {tasks: [{temp_id: 'a', title: 'First'}, {temp_id: 'b', title: 'Second', depends_on_temp_ids: ['a']}]}`,
10566
+ get_task_stats: `Get task analytics: counts by status, priority, agent, and completion rate. All via SQL.
10567
+ Params: project_id(string), task_list_id(string), agent_id(string)
10568
+ Example: {project_id: 'a1b2c3d4'}`,
10569
+ get_task_graph: `Get full dependency tree for a task \u2014 upstream blockers and downstream dependents.
10570
+ Params: id(string, req), direction(up|down|both, default:both)
10571
+ Example: {id: 'a1b2c3d4', direction: 'up'}`,
10572
+ get_task_history: `Get audit log for a task \u2014 all field changes with timestamps and actors.
10573
+ Params: task_id(string, req)
10574
+ Example: {task_id: 'a1b2c3d4'}`,
10575
+ get_recent_activity: `Get recent task changes across all tasks \u2014 global activity feed.
10576
+ Params: limit(number, default:50)
10577
+ Example: {limit: 20}`,
10578
+ create_webhook: `Register a webhook for task change events.
10579
+ Params: url(string, req), events(string[] \u2014 empty=all), secret(string \u2014 HMAC signing)
10580
+ Example: {url: 'https://example.com/hook', events: ['task.created', 'task.completed']}`,
10581
+ list_webhooks: "List all registered webhooks. No params.",
10582
+ delete_webhook: `Delete a webhook by ID.
10583
+ Params: id(string, req)
10584
+ Example: {id: 'a1b2c3d4'}`,
10585
+ create_template: `Create a reusable task template.
10586
+ Params: name(string, req), title_pattern(string, req \u2014 e.g. 'Fix: {description}'), description(string), priority(low|medium|high|critical), tags(string[]), project_id(string), plan_id(string)
10587
+ Example: {name: 'Bug Report', title_pattern: 'Bug: {description}', priority: 'high', tags: ['bug']}`,
10588
+ list_templates: "List all task templates. No params.",
10589
+ create_task_from_template: `Create a task from a template with optional overrides.
10590
+ Params: template_id(string, req), title(string), description(string), priority(low|medium|high|critical), assigned_to(string), project_id(string)
10591
+ Example: {template_id: 'a1b2c3d4', assigned_to: 'maximus'}`,
10592
+ delete_template: `Delete a task template.
10593
+ Params: id(string, req)
10594
+ Example: {id: 'a1b2c3d4'}`,
10595
+ get_active_work: `See all in-progress tasks and who is working on them.
10596
+ Params: project_id(string, optional), task_list_id(string, optional)
10597
+ Example: {project_id: 'a1b2c3d4'}`,
10598
+ get_tasks_changed_since: `Get tasks modified after a timestamp \u2014 incremental delta sync.
10599
+ Params: since(string, req \u2014 ISO date), project_id(string, optional), task_list_id(string, optional)
10600
+ Example: {since: '2026-03-14T10:00:00Z'}`,
10601
+ get_stale_tasks: `Find stale in_progress tasks with no recent activity.
10602
+ Params: stale_minutes(number, default:30), project_id(string, optional), task_list_id(string, optional)
10603
+ Example: {stale_minutes: 60, project_id: 'a1b2c3d4'}`,
10604
+ search_tools: `List all tool names or filter by substring.
10605
+ Params: query(string, optional)
10606
+ Example: {query: 'task'}`,
10607
+ describe_tools: `Get detailed descriptions and parameter info for tools by name.
10608
+ Params: names(string[], req)
10609
+ Example: {names: ['create_task', 'update_task']}`
9451
10610
  };
9452
- const result = names.map((n) => `${n}: ${descriptions[n] || "See tool schema"}`).join(`
10611
+ const result = names.map((n) => `${n}: ${descriptions[n] || "Unknown tool. Use search_tools to list available tools."}`).join(`
10612
+
9453
10613
  `);
9454
10614
  return { content: [{ type: "text", text: result }] };
9455
10615
  });