@task-mcp/cli 1.0.29 → 1.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1168 -7
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -31,7 +31,17 @@ import {
31
31
  priorityColors,
32
32
  formatPriority,
33
33
  formatDependencies,
34
- banner
34
+ banner,
35
+ STATUS_SYMBOLS,
36
+ formatStatusSymbol,
37
+ PRIORITY_LEVELS,
38
+ formatPriorityBadge,
39
+ renderSection,
40
+ renderSections,
41
+ renderAlert,
42
+ renderStats,
43
+ formatDueDate,
44
+ formatTaskLine
35
45
  } from "@task-mcp/shared";
36
46
  import { truncateStr as truncateStr2 } from "@task-mcp/shared";
37
47
 
@@ -42,10 +52,17 @@ import {
42
52
  } from "@task-mcp/shared";
43
53
 
44
54
  // src/storage.ts
45
- import { TaskStore, InboxStore, StateStore } from "@task-mcp/mcp-server/storage";
55
+ import {
56
+ TaskStore,
57
+ InboxStore,
58
+ StateStore,
59
+ SessionStore,
60
+ getDefaultTasksDir
61
+ } from "@task-mcp/mcp-server/storage";
46
62
  var _taskStore = null;
47
63
  var _inboxStore = null;
48
64
  var _stateStore = null;
65
+ var _sessionStore = null;
49
66
  function getTaskStore() {
50
67
  if (!_taskStore) {
51
68
  _taskStore = new TaskStore;
@@ -64,6 +81,13 @@ function getStateStore() {
64
81
  }
65
82
  return _stateStore;
66
83
  }
84
+ function getSessionStore() {
85
+ if (!_sessionStore) {
86
+ const taskStore = getTaskStore();
87
+ _sessionStore = new SessionStore(getDefaultTasksDir(), taskStore.currentWorkspace);
88
+ }
89
+ return _sessionStore;
90
+ }
67
91
  function getCurrentWorkspace() {
68
92
  return getTaskStore().currentWorkspace;
69
93
  }
@@ -93,6 +117,149 @@ async function listInboxItems(status) {
93
117
  async function getActiveTag() {
94
118
  return getStateStore().getActiveTag();
95
119
  }
120
+ function calculateStats(tasks) {
121
+ const stats = {
122
+ total: tasks.length,
123
+ completed: 0,
124
+ inProgress: 0,
125
+ pending: 0,
126
+ blocked: 0,
127
+ cancelled: 0,
128
+ byPriority: { critical: 0, high: 0, medium: 0, low: 0 },
129
+ completionPercent: 0
130
+ };
131
+ for (const task of tasks) {
132
+ switch (task.status) {
133
+ case "completed":
134
+ stats.completed++;
135
+ break;
136
+ case "in_progress":
137
+ stats.inProgress++;
138
+ break;
139
+ case "pending":
140
+ stats.pending++;
141
+ break;
142
+ case "blocked":
143
+ stats.blocked++;
144
+ break;
145
+ case "cancelled":
146
+ stats.cancelled++;
147
+ break;
148
+ }
149
+ stats.byPriority[task.priority]++;
150
+ }
151
+ const nonCancelled = stats.total - stats.cancelled;
152
+ stats.completionPercent = nonCancelled > 0 ? Math.round(stats.completed / nonCancelled * 100) : 0;
153
+ return stats;
154
+ }
155
+ function calculateDependencyMetrics(tasks) {
156
+ const activeTasks = tasks.filter((t) => t.status !== "completed" && t.status !== "cancelled");
157
+ const completedIds = new Set(tasks.filter((t) => t.status === "completed").map((t) => t.id));
158
+ const dependentCounts = {};
159
+ for (const task of activeTasks) {
160
+ for (const dep of task.dependencies ?? []) {
161
+ dependentCounts[dep.taskId] = (dependentCounts[dep.taskId] ?? 0) + 1;
162
+ }
163
+ }
164
+ let mostDependedOn = null;
165
+ let maxCount = 0;
166
+ for (const [id, count] of Object.entries(dependentCounts)) {
167
+ if (count > maxCount) {
168
+ maxCount = count;
169
+ const task = tasks.find((t) => t.id === id);
170
+ if (task) {
171
+ mostDependedOn = { id, title: task.title, count };
172
+ }
173
+ }
174
+ }
175
+ const noDependencies = activeTasks.filter((t) => (t.dependencies?.length ?? 0) === 0).length;
176
+ const readyToWork = activeTasks.filter((t) => {
177
+ const deps = t.dependencies ?? [];
178
+ if (deps.length === 0)
179
+ return true;
180
+ return deps.every((d) => completedIds.has(d.taskId));
181
+ }).length;
182
+ const blockedByDependencies = activeTasks.length - readyToWork;
183
+ const totalDeps = activeTasks.reduce((sum, t) => sum + (t.dependencies?.length ?? 0), 0);
184
+ const avgDependencies = activeTasks.length > 0 ? totalDeps / activeTasks.length : 0;
185
+ return {
186
+ noDependencies,
187
+ readyToWork,
188
+ blockedByDependencies,
189
+ mostDependedOn,
190
+ avgDependencies: Math.round(avgDependencies * 10) / 10
191
+ };
192
+ }
193
+ function suggestNextTask(tasks) {
194
+ const completedIds = new Set(tasks.filter((t) => t.status === "completed").map((t) => t.id));
195
+ const actionable = tasks.filter((t) => {
196
+ if (t.status !== "pending" && t.status !== "in_progress")
197
+ return false;
198
+ const deps = t.dependencies ?? [];
199
+ return deps.every((d) => completedIds.has(d.taskId));
200
+ });
201
+ if (actionable.length === 0)
202
+ return null;
203
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
204
+ actionable.sort((a, b) => {
205
+ const pDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
206
+ if (pDiff !== 0)
207
+ return pDiff;
208
+ return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
209
+ });
210
+ return actionable[0] ?? null;
211
+ }
212
+ function formatTimeSince(isoTimestamp) {
213
+ const then = new Date(isoTimestamp).getTime();
214
+ const nowMs = Date.now();
215
+ const diffMs = nowMs - then;
216
+ const minutes = Math.floor(diffMs / (1000 * 60));
217
+ const hours = Math.floor(minutes / 60);
218
+ const days = Math.floor(hours / 24);
219
+ if (days > 0) {
220
+ return `${days}d ago`;
221
+ }
222
+ if (hours > 0) {
223
+ return `${hours}h ago`;
224
+ }
225
+ if (minutes > 0) {
226
+ return `${minutes}m ago`;
227
+ }
228
+ return "just now";
229
+ }
230
+ async function getLastSessionInfo() {
231
+ try {
232
+ const sessionStore = getSessionStore();
233
+ const lastSession = await sessionStore.getLastSession();
234
+ if (!lastSession || !lastSession.endedAt) {
235
+ return null;
236
+ }
237
+ const tasks = await listTasks();
238
+ const inProgressTasks = tasks.filter((t) => t.status === "in_progress");
239
+ const summary = lastSession.tasksSummary;
240
+ let currentInProgressTask;
241
+ if (inProgressTasks.length > 0) {
242
+ const sessionTaskIds = new Set(lastSession.tasksWorked);
243
+ const sessionInProgress = inProgressTasks.find((t) => sessionTaskIds.has(t.id));
244
+ const taskToShow = sessionInProgress ?? inProgressTasks[0];
245
+ if (taskToShow) {
246
+ currentInProgressTask = {
247
+ id: taskToShow.id,
248
+ title: taskToShow.title
249
+ };
250
+ }
251
+ }
252
+ return {
253
+ session: lastSession,
254
+ timeSince: formatTimeSince(lastSession.endedAt),
255
+ completed: summary?.completed ?? 0,
256
+ inProgress: inProgressTasks.length,
257
+ currentInProgressTask
258
+ };
259
+ } catch {
260
+ return null;
261
+ }
262
+ }
96
263
 
97
264
  // src/commands/dashboard.ts
98
265
  async function dashboard(workspaceName) {
@@ -141,6 +308,552 @@ async function dashboard(workspaceName) {
141
308
  console.log(output);
142
309
  }
143
310
 
311
+ // src/commands/smart-dashboard.ts
312
+ function getOverdueTasks(tasks) {
313
+ const today = new Date;
314
+ today.setHours(0, 0, 0, 0);
315
+ return tasks.filter((t) => {
316
+ if (t.status === "completed" || t.status === "cancelled")
317
+ return false;
318
+ if (!t.dueDate)
319
+ return false;
320
+ const due = new Date(t.dueDate);
321
+ due.setHours(0, 0, 0, 0);
322
+ return due < today;
323
+ });
324
+ }
325
+ function getStaleBlockedTasks(tasks, daysThreshold = 3) {
326
+ const now = Date.now();
327
+ const thresholdMs = daysThreshold * 24 * 60 * 60 * 1000;
328
+ return tasks.filter((t) => {
329
+ if (t.status !== "blocked")
330
+ return false;
331
+ const createdAt = new Date(t.createdAt).getTime();
332
+ return now - createdAt > thresholdMs;
333
+ });
334
+ }
335
+ function getDueSoonTasks(tasks) {
336
+ const today = new Date;
337
+ today.setHours(0, 0, 0, 0);
338
+ const tomorrow = new Date(today);
339
+ tomorrow.setDate(tomorrow.getDate() + 1);
340
+ const dayAfter = new Date(today);
341
+ dayAfter.setDate(dayAfter.getDate() + 2);
342
+ return tasks.filter((t) => {
343
+ if (t.status === "completed" || t.status === "cancelled")
344
+ return false;
345
+ if (!t.dueDate)
346
+ return false;
347
+ const due = new Date(t.dueDate);
348
+ due.setHours(0, 0, 0, 0);
349
+ return due >= today && due < dayAfter;
350
+ }).sort((a, b) => {
351
+ const dateA = new Date(a.dueDate).getTime();
352
+ const dateB = new Date(b.dueDate).getTime();
353
+ return dateA - dateB;
354
+ });
355
+ }
356
+ function getSuggestionReason(task, tasks) {
357
+ const reasons = [];
358
+ const blockingCount = tasks.filter((t) => t.dependencies?.some((d) => d.taskId === task.id)).length;
359
+ if (blockingCount > 0) {
360
+ reasons.push(`Unblocks ${blockingCount} task${blockingCount > 1 ? "s" : ""}`);
361
+ }
362
+ if (task.priority === "critical") {
363
+ reasons.push("Critical priority");
364
+ } else if (task.priority === "high") {
365
+ reasons.push("High priority");
366
+ }
367
+ if (task.dueDate) {
368
+ const today = new Date;
369
+ today.setHours(0, 0, 0, 0);
370
+ const due = new Date(task.dueDate);
371
+ due.setHours(0, 0, 0, 0);
372
+ const diffDays = Math.floor((due.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
373
+ if (diffDays < 0) {
374
+ reasons.push("Overdue");
375
+ } else if (diffDays === 0) {
376
+ reasons.push("Due today");
377
+ } else if (diffDays === 1) {
378
+ reasons.push("Due tomorrow");
379
+ }
380
+ }
381
+ if (task.status === "in_progress") {
382
+ reasons.push("Already started");
383
+ }
384
+ return reasons.length > 0 ? reasons.join(", ") : "Ready to start";
385
+ }
386
+ function taskToSectionItem(task, options = {}) {
387
+ const prefix = formatStatusSymbol(task.status);
388
+ const idStr = c.gray(task.id.slice(-4));
389
+ const title = truncateStr(task.title, 35);
390
+ const primary = `${idStr} ${title}`;
391
+ const suffixParts = [];
392
+ if (task.priority) {
393
+ suffixParts.push(formatPriorityBadge(task.priority));
394
+ }
395
+ if (task.dueDate) {
396
+ suffixParts.push(formatDueDate(task.dueDate));
397
+ }
398
+ const details = [];
399
+ if (options.showReason) {
400
+ details.push(options.showReason);
401
+ }
402
+ return {
403
+ primary,
404
+ prefix,
405
+ suffix: suffixParts.join(" "),
406
+ details
407
+ };
408
+ }
409
+ function renderHeader(workspace, stats) {
410
+ const lines = [];
411
+ const title = `task-mcp`;
412
+ const wsName = c.cyan(workspace);
413
+ const bar = progressBar(stats.completed, stats.total - stats.cancelled, {
414
+ width: 10,
415
+ showPercent: false
416
+ });
417
+ lines.push(c.gray(BOX.rTopLeft + hline(60, BOX.horizontal) + BOX.rTopRight));
418
+ lines.push(c.gray(BOX.vertical) + ` ${c.bold(title)} ${c.gray("@")} ${wsName}` + " ".repeat(20) + `Health: ${bar} ${stats.completionPercent}%` + " " + c.gray(BOX.vertical));
419
+ lines.push(c.gray(BOX.rBottomLeft + hline(60, BOX.horizontal) + BOX.rBottomRight));
420
+ return lines;
421
+ }
422
+ function renderNextAction(tasks) {
423
+ const nextTask = suggestNextTask(tasks);
424
+ if (!nextTask) {
425
+ return renderSection("Next Action", [], {
426
+ icon: "\uD83C\uDFAF",
427
+ emptyMessage: "No actionable tasks"
428
+ });
429
+ }
430
+ const reason = getSuggestionReason(nextTask, tasks);
431
+ const item = taskToSectionItem(nextTask, { showReason: reason });
432
+ return renderSection("Next Action", [item], { icon: "\uD83C\uDFAF" });
433
+ }
434
+ function renderAttention(tasks) {
435
+ const alerts = [];
436
+ const overdue = getOverdueTasks(tasks);
437
+ if (overdue.length > 0) {
438
+ alerts.push(c.red(`${overdue.length} task${overdue.length > 1 ? "s" : ""} overdue`));
439
+ }
440
+ const staleBlocked = getStaleBlockedTasks(tasks, 3);
441
+ if (staleBlocked.length > 0) {
442
+ alerts.push(c.yellow(`${staleBlocked.length} task${staleBlocked.length > 1 ? "s" : ""} blocked > 3 days`));
443
+ }
444
+ const blockedCount = tasks.filter((t) => t.status === "blocked").length;
445
+ if (blockedCount > 0 && staleBlocked.length === 0) {
446
+ alerts.push(c.yellow(`${blockedCount} task${blockedCount > 1 ? "s" : ""} blocked`));
447
+ }
448
+ const inProgressCount = tasks.filter((t) => t.status === "in_progress").length;
449
+ if (inProgressCount > 0) {
450
+ alerts.push(c.cyan(`${inProgressCount} task${inProgressCount > 1 ? "s" : ""} in progress`));
451
+ }
452
+ if (alerts.length === 0) {
453
+ return renderSection("Attention", [], {
454
+ icon: "\u26A0",
455
+ emptyMessage: c.green("All clear!"),
456
+ headerColor: "yellow"
457
+ });
458
+ }
459
+ const items = alerts.map((alert) => ({
460
+ primary: alert,
461
+ prefix: c.gray("\u2022")
462
+ }));
463
+ return renderSection("Attention", items, {
464
+ icon: "\u26A0",
465
+ headerColor: "yellow"
466
+ });
467
+ }
468
+ function renderDueSoon(tasks) {
469
+ const dueSoon = getDueSoonTasks(tasks);
470
+ if (dueSoon.length === 0) {
471
+ return renderSection("Due Soon", [], {
472
+ icon: "\uD83D\uDCC5",
473
+ emptyMessage: "No upcoming deadlines"
474
+ });
475
+ }
476
+ const items = dueSoon.slice(0, 5).map((task) => taskToSectionItem(task));
477
+ return renderSection("Due Soon", items, {
478
+ icon: "\uD83D\uDCC5",
479
+ showCount: dueSoon.length > 5
480
+ });
481
+ }
482
+ function renderQuickStats(stats) {
483
+ const statsRow = renderStats([
484
+ { label: "Total", value: stats.total },
485
+ { label: "Done", value: `${stats.completed} (${stats.completionPercent}%)`, color: "green" },
486
+ { label: "In Progress", value: stats.inProgress, color: "cyan" },
487
+ { label: "Blocked", value: stats.blocked, color: stats.blocked > 0 ? "red" : "gray" }
488
+ ]);
489
+ return ["", c.gray(statsRow)];
490
+ }
491
+ function renderLastSession(sessionInfo) {
492
+ if (!sessionInfo) {
493
+ return [];
494
+ }
495
+ const lines = [];
496
+ const header = `${c.gray("Last Session:")} ${c.cyan(sessionInfo.timeSince)}`;
497
+ lines.push(` ${header}`);
498
+ const summaryParts = [];
499
+ if (sessionInfo.completed > 0) {
500
+ summaryParts.push(`${sessionInfo.completed} completed`);
501
+ }
502
+ if (sessionInfo.inProgress > 0) {
503
+ summaryParts.push(`${sessionInfo.inProgress} in progress`);
504
+ }
505
+ if (summaryParts.length > 0) {
506
+ lines.push(` ${c.gray("->")} ${summaryParts.join(", ")}`);
507
+ }
508
+ if (sessionInfo.currentInProgressTask) {
509
+ const taskId = c.gray(sessionInfo.currentInProgressTask.id.slice(-8));
510
+ const title = truncateStr(sessionInfo.currentInProgressTask.title, 40);
511
+ lines.push(` ${c.gray("->")} ${taskId}: ${title} ${c.cyan("(in_progress)")}`);
512
+ }
513
+ return lines;
514
+ }
515
+ async function smartDashboard() {
516
+ const workspaces = await listWorkspaces();
517
+ if (workspaces.length === 0) {
518
+ console.log(c.yellow("No tasks found. Create a task first using the MCP server."));
519
+ return;
520
+ }
521
+ const workspace = getCurrentWorkspace();
522
+ const tasks = await listTasks();
523
+ const stats = calculateStats(tasks);
524
+ const lastSessionInfo = await getLastSessionInfo();
525
+ const lines = [];
526
+ lines.push(...renderHeader(workspace, stats));
527
+ lines.push("");
528
+ const lastSessionLines = renderLastSession(lastSessionInfo);
529
+ if (lastSessionLines.length > 0) {
530
+ lines.push(...lastSessionLines);
531
+ lines.push("");
532
+ }
533
+ lines.push(...renderNextAction(tasks));
534
+ lines.push("");
535
+ const attentionLines = renderAttention(tasks);
536
+ if (attentionLines.length > 2) {
537
+ lines.push(...attentionLines);
538
+ lines.push("");
539
+ }
540
+ lines.push(...renderDueSoon(tasks));
541
+ lines.push(...renderQuickStats(stats));
542
+ lines.push("");
543
+ lines.push(c.gray(" Run: task next | task health | task blocked | task tree"));
544
+ console.log(lines.join(`
545
+ `));
546
+ }
547
+
548
+ // src/commands/next.ts
549
+ var STRATEGY_DESCRIPTIONS = {
550
+ balanced: "Balance priority, dependencies, and due dates",
551
+ quick_wins: "Focus on quick, low-effort tasks",
552
+ unblock: "Prioritize tasks that unblock others",
553
+ critical_path: "Focus on critical path tasks"
554
+ };
555
+ function getDependentTasks(task, tasks) {
556
+ return tasks.filter((t) => t.dependencies?.some((d) => d.taskId === task.id));
557
+ }
558
+ function suggestByStrategy(tasks, strategy) {
559
+ const completedIds = new Set(tasks.filter((t) => t.status === "completed").map((t) => t.id));
560
+ const actionable = tasks.filter((t) => {
561
+ if (t.status !== "pending" && t.status !== "in_progress")
562
+ return false;
563
+ const deps = t.dependencies ?? [];
564
+ return deps.every((d) => completedIds.has(d.taskId));
565
+ });
566
+ if (actionable.length === 0)
567
+ return null;
568
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
569
+ switch (strategy) {
570
+ case "quick_wins":
571
+ actionable.sort((a, b) => {
572
+ const estA = a.estimate?.expected ?? 60;
573
+ const estB = b.estimate?.expected ?? 60;
574
+ if (estA !== estB)
575
+ return estA - estB;
576
+ return priorityOrder[a.priority] - priorityOrder[b.priority];
577
+ });
578
+ break;
579
+ case "unblock":
580
+ actionable.sort((a, b) => {
581
+ const depsA = getDependentTasks(a, tasks).length;
582
+ const depsB = getDependentTasks(b, tasks).length;
583
+ if (depsA !== depsB)
584
+ return depsB - depsA;
585
+ return priorityOrder[a.priority] - priorityOrder[b.priority];
586
+ });
587
+ break;
588
+ case "critical_path":
589
+ actionable.sort((a, b) => {
590
+ const pDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
591
+ if (pDiff !== 0)
592
+ return pDiff;
593
+ const depsA = getDependentTasks(a, tasks).length;
594
+ const depsB = getDependentTasks(b, tasks).length;
595
+ return depsB - depsA;
596
+ });
597
+ break;
598
+ case "balanced":
599
+ default:
600
+ actionable.sort((a, b) => {
601
+ const pDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
602
+ if (pDiff !== 0)
603
+ return pDiff;
604
+ const dueA = a.dueDate ? new Date(a.dueDate).getTime() : Infinity;
605
+ const dueB = b.dueDate ? new Date(b.dueDate).getTime() : Infinity;
606
+ if (dueA !== dueB)
607
+ return dueA - dueB;
608
+ return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
609
+ });
610
+ break;
611
+ }
612
+ return actionable[0] ?? null;
613
+ }
614
+ function buildReasons(task, tasks) {
615
+ const reasons = [];
616
+ if (task.priority === "critical") {
617
+ reasons.push("\uD83D\uDD34 Critical priority");
618
+ } else if (task.priority === "high") {
619
+ reasons.push("\uD83D\uDFE0 High priority");
620
+ }
621
+ if (task.dueDate) {
622
+ const today = new Date;
623
+ today.setHours(0, 0, 0, 0);
624
+ const due = new Date(task.dueDate);
625
+ due.setHours(0, 0, 0, 0);
626
+ const diffDays = Math.floor((due.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
627
+ if (diffDays < 0) {
628
+ reasons.push(`\u26A0\uFE0F Overdue by ${Math.abs(diffDays)} day${Math.abs(diffDays) > 1 ? "s" : ""}`);
629
+ } else if (diffDays === 0) {
630
+ reasons.push("\uD83D\uDCC5 Due today");
631
+ } else if (diffDays === 1) {
632
+ reasons.push("\uD83D\uDCC5 Due tomorrow");
633
+ } else if (diffDays <= 3) {
634
+ reasons.push(`\uD83D\uDCC5 Due in ${diffDays} days`);
635
+ }
636
+ }
637
+ const dependents = getDependentTasks(task, tasks);
638
+ if (dependents.length > 0) {
639
+ reasons.push(`\uD83D\uDD13 Unblocks ${dependents.length} task${dependents.length > 1 ? "s" : ""}`);
640
+ }
641
+ if (task.status === "in_progress") {
642
+ reasons.push("\u25B6\uFE0F Already in progress");
643
+ }
644
+ if (!task.dependencies || task.dependencies.length === 0) {
645
+ reasons.push("\u2705 No blockers");
646
+ }
647
+ if (reasons.length === 0) {
648
+ reasons.push("Ready to start");
649
+ }
650
+ return reasons;
651
+ }
652
+ function renderTaskDetail(task, _tasks) {
653
+ const lines = [];
654
+ const status = formatStatusSymbol(task.status);
655
+ const priority = formatPriorityBadge(task.priority);
656
+ lines.push("");
657
+ lines.push(` ${status} ${c.bold(task.title)} ${priority}`);
658
+ lines.push("");
659
+ const details = [
660
+ ["ID", c.gray(task.id)],
661
+ ["Status", task.status],
662
+ ["Priority", task.priority]
663
+ ];
664
+ if (task.dueDate) {
665
+ details.push(["Due", formatDueDate(task.dueDate)]);
666
+ }
667
+ if (task.estimate?.expected) {
668
+ const hours = Math.floor(task.estimate.expected / 60);
669
+ const mins = task.estimate.expected % 60;
670
+ const timeStr = hours > 0 ? `${hours}h ${mins}m` : `${mins}m`;
671
+ details.push(["Estimate", timeStr]);
672
+ }
673
+ if (task.tags && task.tags.length > 0) {
674
+ details.push(["Tags", task.tags.map((t) => c.cyan(`#${t}`)).join(" ")]);
675
+ }
676
+ for (const [label, value] of details) {
677
+ lines.push(` ${c.gray(label + ":")} ${value}`);
678
+ }
679
+ if (task.description) {
680
+ lines.push("");
681
+ lines.push(` ${c.gray("Description:")}`);
682
+ const descLines = task.description.split(`
683
+ `).slice(0, 3);
684
+ for (const line of descLines) {
685
+ lines.push(` ${c.dim(line)}`);
686
+ }
687
+ }
688
+ return lines;
689
+ }
690
+ function renderImpact(task, tasks) {
691
+ const dependents = getDependentTasks(task, tasks);
692
+ if (dependents.length === 0) {
693
+ return [];
694
+ }
695
+ const items = dependents.slice(0, 5).map((t) => ({
696
+ primary: `${c.gray(t.id.slice(-4))} ${t.title}`,
697
+ prefix: formatStatusSymbol(t.status)
698
+ }));
699
+ return [
700
+ "",
701
+ ...renderSection("Will Unblock", items, {
702
+ icon: "\uD83D\uDD13",
703
+ showCount: dependents.length > 5
704
+ })
705
+ ];
706
+ }
707
+ function renderAlternatives(currentStrategy) {
708
+ const others = Object.keys(STRATEGY_DESCRIPTIONS).filter((s) => s !== currentStrategy);
709
+ const lines = ["", c.gray(" Alternative strategies:")];
710
+ for (const strategy of others) {
711
+ lines.push(` ${c.cyan(`task next --strategy ${strategy}`)}`);
712
+ }
713
+ return lines;
714
+ }
715
+ async function nextCmd(options = {}) {
716
+ const strategy = options.strategy ?? "balanced";
717
+ const tasks = await listTasks();
718
+ const nextTask = suggestByStrategy(tasks, strategy);
719
+ if (!nextTask) {
720
+ console.log(c.yellow("No actionable tasks found."));
721
+ console.log(c.gray("All tasks may be completed, blocked, or have unmet dependencies."));
722
+ return;
723
+ }
724
+ const reasons = buildReasons(nextTask, tasks);
725
+ const lines = [];
726
+ lines.push(c.cyan(c.bold("\uD83C\uDFAF Suggested Next Task")));
727
+ lines.push(c.gray(hline(50)));
728
+ lines.push(...renderTaskDetail(nextTask, tasks));
729
+ lines.push("");
730
+ lines.push(c.gray(" Why this task:"));
731
+ for (const reason of reasons) {
732
+ lines.push(` ${reason}`);
733
+ }
734
+ lines.push(...renderImpact(nextTask, tasks));
735
+ lines.push("");
736
+ lines.push(c.gray(` Strategy: ${strategy} - ${STRATEGY_DESCRIPTIONS[strategy]}`));
737
+ lines.push(...renderAlternatives(strategy));
738
+ console.log(lines.join(`
739
+ `));
740
+ }
741
+
742
+ // src/commands/tree.ts
743
+ function buildTree(tasks) {
744
+ const taskMap = new Map;
745
+ for (const task of tasks) {
746
+ taskMap.set(task.id, task);
747
+ }
748
+ const nodeMap = new Map;
749
+ for (const task of tasks) {
750
+ nodeMap.set(task.id, { task, children: [] });
751
+ }
752
+ const roots = [];
753
+ for (const task of tasks) {
754
+ const node = nodeMap.get(task.id);
755
+ if (task.parentId && nodeMap.has(task.parentId)) {
756
+ const parent = nodeMap.get(task.parentId);
757
+ parent.children.push(node);
758
+ } else {
759
+ roots.push(node);
760
+ }
761
+ }
762
+ const sortNodes = (nodes) => {
763
+ nodes.sort((a, b) => {
764
+ const orderA = a.task.sortOrder ?? Infinity;
765
+ const orderB = b.task.sortOrder ?? Infinity;
766
+ if (orderA !== orderB)
767
+ return orderA - orderB;
768
+ return new Date(a.task.createdAt).getTime() - new Date(b.task.createdAt).getTime();
769
+ });
770
+ for (const node of nodes) {
771
+ sortNodes(node.children);
772
+ }
773
+ };
774
+ sortNodes(roots);
775
+ return roots;
776
+ }
777
+ function countProgress(node) {
778
+ let completed = node.task.status === "completed" ? 1 : 0;
779
+ let total = 1;
780
+ for (const child of node.children) {
781
+ const childProgress = countProgress(child);
782
+ completed += childProgress.completed;
783
+ total += childProgress.total;
784
+ }
785
+ return { completed, total };
786
+ }
787
+ function formatTaskLine2(task, progress) {
788
+ const status = formatStatusSymbol(task.status);
789
+ const shortId = c.gray(task.id.slice(-4));
790
+ const priority = task.priority !== "medium" ? formatPriorityBadge(task.priority) : "";
791
+ let progressStr = "";
792
+ if (progress && progress.total > 1) {
793
+ const childCompleted = progress.completed - (task.status === "completed" ? 1 : 0);
794
+ const childTotal = progress.total - 1;
795
+ if (childTotal > 0) {
796
+ const pct = Math.round(childCompleted / childTotal * 100);
797
+ progressStr = c.gray(` (${childCompleted}/${childTotal})`);
798
+ if (pct === 100) {
799
+ progressStr = c.green(` (${childCompleted}/${childTotal} \u2713)`);
800
+ }
801
+ }
802
+ }
803
+ if (task.status === "completed" || task.status === "cancelled") {
804
+ const title2 = c.gray(task.title);
805
+ return `${status} ${shortId} ${title2}${progressStr}`;
806
+ }
807
+ const title = task.title;
808
+ return `${status} ${shortId} ${title}${progressStr}${priority ? " " + priority : ""}`;
809
+ }
810
+ function renderNode(node, prefix, isLast, isRoot, lines) {
811
+ const branch = isRoot ? "" : isLast ? "\\-- " : "|-- ";
812
+ const continuation = isLast ? " " : "| ";
813
+ const progress = node.children.length > 0 ? countProgress(node) : undefined;
814
+ const taskLine = formatTaskLine2(node.task, progress);
815
+ lines.push(`${prefix}${branch}${taskLine}`);
816
+ const childPrefix = isRoot ? prefix : prefix + continuation;
817
+ const childCount = node.children.length;
818
+ for (let i = 0;i < childCount; i++) {
819
+ const child = node.children[i];
820
+ const childIsLast = i === childCount - 1;
821
+ renderNode(child, childPrefix, childIsLast, false, lines);
822
+ }
823
+ }
824
+ function renderTree(roots) {
825
+ const lines = [];
826
+ for (const root of roots) {
827
+ renderNode(root, "", true, true, lines);
828
+ if (roots.indexOf(root) < roots.length - 1) {
829
+ lines.push("");
830
+ }
831
+ }
832
+ return lines;
833
+ }
834
+ async function treeCmd(options = {}) {
835
+ const allTasks = await listTasks();
836
+ const tasks = options.hideCompleted ? allTasks.filter((t) => t.status !== "completed" && t.status !== "cancelled") : allTasks;
837
+ if (tasks.length === 0) {
838
+ console.log(c.yellow("No tasks found."));
839
+ if (options.hideCompleted) {
840
+ console.log(c.gray("Use without --hide-completed to include completed tasks."));
841
+ }
842
+ return;
843
+ }
844
+ const roots = buildTree(tasks);
845
+ const lines = [];
846
+ lines.push(c.cyan(c.bold("Task Hierarchy")));
847
+ lines.push(c.gray(hline(50)));
848
+ lines.push("");
849
+ lines.push(...renderTree(roots));
850
+ lines.push("");
851
+ lines.push(c.gray(hline(50)));
852
+ lines.push(c.gray("Legend: [ ] pending [~] progress [x] done [!] blocked"));
853
+ console.log(lines.join(`
854
+ `));
855
+ }
856
+
144
857
  // src/constants.ts
145
858
  var MENU_SEPARATOR_WIDTH = 40;
146
859
  var LIST_SEPARATOR_WIDTH = 50;
@@ -250,14 +963,26 @@ Workspaces (${workspaces.length})
250
963
 
251
964
  // src/interactive.ts
252
965
  import * as readline from "readline";
253
- async function prompt(question) {
966
+ async function prompt(question, options) {
254
967
  const rl = readline.createInterface({
255
968
  input: process.stdin,
256
969
  output: process.stdout
257
970
  });
258
971
  return new Promise((resolve) => {
259
- rl.question(question, (answer) => {
972
+ let timeoutId;
973
+ const cleanup = () => {
974
+ if (timeoutId)
975
+ clearTimeout(timeoutId);
260
976
  rl.close();
977
+ };
978
+ if (options?.timeoutMs !== undefined) {
979
+ timeoutId = setTimeout(() => {
980
+ cleanup();
981
+ resolve(options.defaultValue ?? "");
982
+ }, options.timeoutMs);
983
+ }
984
+ rl.question(question, (answer) => {
985
+ cleanup();
261
986
  resolve(answer.trim());
262
987
  });
263
988
  });
@@ -585,10 +1310,407 @@ async function inboxCountCmd() {
585
1310
  }
586
1311
  console.log();
587
1312
  }
1313
+
1314
+ // src/commands/blocked.ts
1315
+ function calculateDaysBlocked(task) {
1316
+ const blockedSince = task.updatedAt ?? task.createdAt;
1317
+ const blockedDate = new Date(blockedSince);
1318
+ const now = new Date;
1319
+ const diffMs = now.getTime() - blockedDate.getTime();
1320
+ return Math.floor(diffMs / (1000 * 60 * 60 * 24));
1321
+ }
1322
+ function getBlockerTasks(task, allTasks) {
1323
+ if (!task.dependencies || task.dependencies.length === 0)
1324
+ return [];
1325
+ const blockers = [];
1326
+ const completedStatuses = new Set(["completed", "cancelled"]);
1327
+ for (const dep of task.dependencies) {
1328
+ const blockerTask = allTasks.find((t) => t.id === dep.taskId);
1329
+ if (blockerTask && !completedStatuses.has(blockerTask.status)) {
1330
+ blockers.push(blockerTask);
1331
+ }
1332
+ }
1333
+ return blockers;
1334
+ }
1335
+ function getBlockReason(blockers) {
1336
+ if (blockers.length === 0) {
1337
+ return "Unknown (no active blockers found)";
1338
+ }
1339
+ const inProgress = blockers.filter((t) => t.status === "in_progress");
1340
+ const pending = blockers.filter((t) => t.status === "pending");
1341
+ const blocked = blockers.filter((t) => t.status === "blocked");
1342
+ const reasons = [];
1343
+ if (inProgress.length > 0) {
1344
+ reasons.push(`${inProgress.length} in progress`);
1345
+ }
1346
+ if (pending.length > 0) {
1347
+ reasons.push(`${pending.length} pending`);
1348
+ }
1349
+ if (blocked.length > 0) {
1350
+ reasons.push(`${blocked.length} blocked (chain)`);
1351
+ }
1352
+ return `Dependency tasks: ${reasons.join(", ")}`;
1353
+ }
1354
+ function analyzeBlockedTasks(tasks) {
1355
+ const blockedTasks = tasks.filter((t) => t.status === "blocked");
1356
+ const result = [];
1357
+ for (const task of blockedTasks) {
1358
+ const blockers = getBlockerTasks(task, tasks);
1359
+ const daysBlocked = calculateDaysBlocked(task);
1360
+ const isStale = daysBlocked >= 3;
1361
+ result.push({
1362
+ task,
1363
+ blockers,
1364
+ reason: getBlockReason(blockers),
1365
+ daysBlocked,
1366
+ isStale
1367
+ });
1368
+ }
1369
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
1370
+ result.sort((a, b) => {
1371
+ const pDiff = priorityOrder[a.task.priority] - priorityOrder[b.task.priority];
1372
+ if (pDiff !== 0)
1373
+ return pDiff;
1374
+ return b.daysBlocked - a.daysBlocked;
1375
+ });
1376
+ return result;
1377
+ }
1378
+ function renderBlockedTask(info) {
1379
+ const lines = [];
1380
+ const { task, blockers, reason, daysBlocked, isStale } = info;
1381
+ const staleIndicator = isStale ? c.red("[!]") : c.yellow("[B]");
1382
+ const taskId = c.gray(task.id.slice(-6));
1383
+ const priority = formatPriorityBadge(task.priority);
1384
+ const title = isStale ? c.red(task.title) : task.title;
1385
+ lines.push(` ${staleIndicator} ${taskId} ${title} ${priority}`);
1386
+ if (blockers.length > 0) {
1387
+ const blockerIds = blockers.map((b) => c.cyan(b.id.slice(-6))).join(", ");
1388
+ lines.push(` ${c.gray("blocked_by:")} ${blockerIds}`);
1389
+ }
1390
+ lines.push(` ${c.dim("->")} ${c.dim("Reason:")} ${c.dim(reason)}`);
1391
+ if (isStale) {
1392
+ lines.push(` ${c.dim("->")} ${c.red(`Blocked for ${daysBlocked} days`)}`);
1393
+ }
1394
+ return lines;
1395
+ }
1396
+ function renderStaleWarnings(staleItems) {
1397
+ if (staleItems.length === 0)
1398
+ return [];
1399
+ const message = `${staleItems.length} task${staleItems.length > 1 ? "s" : ""} blocked for 3+ days`;
1400
+ return ["", ...renderAlert(message, { severity: "warning" })];
1401
+ }
1402
+ function renderSummary(totalBlocked, staleCount, byPriority) {
1403
+ const stats = [
1404
+ { label: "Total Blocked", value: totalBlocked, color: "yellow" },
1405
+ { label: "Stale (3+ days)", value: staleCount, color: staleCount > 0 ? "red" : "gray" }
1406
+ ];
1407
+ if (totalBlocked > 0) {
1408
+ if (byPriority.critical > 0) {
1409
+ stats.push({ label: "Critical", value: byPriority.critical, color: "red" });
1410
+ }
1411
+ if (byPriority.high > 0) {
1412
+ stats.push({ label: "High", value: byPriority.high, color: "magenta" });
1413
+ }
1414
+ }
1415
+ return ["", ...renderStats(stats)];
1416
+ }
1417
+ async function blockedCmd(options = {}) {
1418
+ const tasks = await listTasks();
1419
+ const blockedInfo = analyzeBlockedTasks(tasks);
1420
+ const displayItems = options.stale ? blockedInfo.filter((info) => info.isStale) : blockedInfo;
1421
+ const staleItems = blockedInfo.filter((info) => info.isStale);
1422
+ const byPriority = { critical: 0, high: 0, medium: 0, low: 0 };
1423
+ for (const info of blockedInfo) {
1424
+ byPriority[info.task.priority]++;
1425
+ }
1426
+ const lines = [];
1427
+ const title = options.stale ? "Stale Blocked Tasks" : "Blocked Tasks";
1428
+ lines.push(c.yellow(c.bold(`[!] ${title}`)));
1429
+ lines.push(c.gray(hline(50)));
1430
+ if (displayItems.length === 0) {
1431
+ if (options.stale) {
1432
+ lines.push("");
1433
+ lines.push(c.green(" No stale blocked tasks found."));
1434
+ lines.push(c.gray(" All blocked tasks have been blocked for less than 3 days."));
1435
+ } else {
1436
+ lines.push("");
1437
+ lines.push(c.green(" No blocked tasks found."));
1438
+ lines.push(c.gray(" All tasks are ready to work or completed."));
1439
+ }
1440
+ console.log(lines.join(`
1441
+ `));
1442
+ return;
1443
+ }
1444
+ lines.push("");
1445
+ for (const info of displayItems) {
1446
+ lines.push(...renderBlockedTask(info));
1447
+ lines.push("");
1448
+ }
1449
+ if (!options.stale && staleItems.length > 0) {
1450
+ lines.push(...renderStaleWarnings(staleItems));
1451
+ }
1452
+ lines.push(...renderSummary(blockedInfo.length, staleItems.length, byPriority));
1453
+ if (!options.stale && staleItems.length > 0) {
1454
+ lines.push("");
1455
+ lines.push(c.gray(" Tip: Use --stale to show only stale blocked tasks"));
1456
+ }
1457
+ console.log(lines.join(`
1458
+ `));
1459
+ }
1460
+
1461
+ // src/commands/health.ts
1462
+ import { detectCircularDependencies, buildDependencyIndices } from "@task-mcp/shared";
1463
+ function getOverdueTasks2(tasks) {
1464
+ const today = new Date;
1465
+ today.setHours(0, 0, 0, 0);
1466
+ return tasks.filter((t) => {
1467
+ if (t.status === "completed" || t.status === "cancelled")
1468
+ return false;
1469
+ if (!t.dueDate)
1470
+ return false;
1471
+ const due = new Date(t.dueDate);
1472
+ due.setHours(0, 0, 0, 0);
1473
+ return due < today;
1474
+ });
1475
+ }
1476
+ function getStaleBlockedTasks2(tasks, staleDays = 3) {
1477
+ const cutoff = Date.now() - staleDays * 24 * 60 * 60 * 1000;
1478
+ return tasks.filter((t) => {
1479
+ if (t.status !== "blocked")
1480
+ return false;
1481
+ const updated = new Date(t.updatedAt).getTime();
1482
+ return updated < cutoff;
1483
+ });
1484
+ }
1485
+ function findBottlenecks(tasks, limit = 5) {
1486
+ const activeTasks = tasks.filter((t) => t.status !== "completed" && t.status !== "cancelled");
1487
+ const { dependentsIndex } = buildDependencyIndices(activeTasks);
1488
+ const bottlenecks = [];
1489
+ for (const task of activeTasks) {
1490
+ const dependents = dependentsIndex.get(task.id) ?? [];
1491
+ if (dependents.length > 0) {
1492
+ bottlenecks.push({
1493
+ task,
1494
+ blockingCount: dependents.length
1495
+ });
1496
+ }
1497
+ }
1498
+ bottlenecks.sort((a, b) => b.blockingCount - a.blockingCount);
1499
+ return bottlenecks.slice(0, limit);
1500
+ }
1501
+ function generateRecommendations(tasks, stats, metrics, bottlenecks) {
1502
+ const recommendations = [];
1503
+ const overdueMetric = metrics.find((m) => m.name === "Overdue tasks");
1504
+ if (overdueMetric?.status === "fail") {
1505
+ recommendations.push("Address overdue tasks immediately to prevent further delays");
1506
+ }
1507
+ const staleMetric = metrics.find((m) => m.name === "Stale blocked");
1508
+ if (staleMetric?.status !== "pass") {
1509
+ recommendations.push("Review stale blocked tasks - they may need dependency updates or reassignment");
1510
+ }
1511
+ if (bottlenecks.length > 0 && bottlenecks[0].blockingCount >= 3) {
1512
+ recommendations.push(`Focus on "${bottlenecks[0].task.title}" - it blocks ${bottlenecks[0].blockingCount} other tasks`);
1513
+ }
1514
+ if (stats.completionPercent < 30 && stats.total > 5) {
1515
+ recommendations.push("Completion rate is low. Consider breaking down large tasks or adjusting priorities");
1516
+ }
1517
+ if (stats.blocked > stats.total * 0.3 && stats.total > 3) {
1518
+ recommendations.push("Many tasks are blocked. Review dependency structure for optimization");
1519
+ }
1520
+ if (stats.inProgress === 0 && stats.pending > 0) {
1521
+ recommendations.push("No tasks in progress. Pick up the next available task to maintain momentum");
1522
+ }
1523
+ if (recommendations.length === 0) {
1524
+ recommendations.push("Workspace is in good health. Keep up the good work!");
1525
+ }
1526
+ return recommendations;
1527
+ }
1528
+ function calculateHealthScore(stats, cycles, overdue, staleBlocked) {
1529
+ let score = 100;
1530
+ if (cycles.length > 0) {
1531
+ score -= 30;
1532
+ }
1533
+ const overdueRatio = stats.total > 0 ? overdue.length / stats.total : 0;
1534
+ score -= Math.min(25, overdueRatio * 100);
1535
+ const staleRatio = stats.total > 0 ? staleBlocked.length / stats.total : 0;
1536
+ score -= Math.min(15, staleRatio * 50);
1537
+ const blockedRatio = stats.total > 0 ? stats.blocked / stats.total : 0;
1538
+ score -= Math.min(15, blockedRatio * 30);
1539
+ if (stats.completionPercent >= 50) {
1540
+ score += 5;
1541
+ }
1542
+ return Math.max(0, Math.min(100, Math.round(score)));
1543
+ }
1544
+ function analyzeHealth(tasks) {
1545
+ const stats = calculateStats(tasks);
1546
+ const depMetrics = calculateDependencyMetrics(tasks);
1547
+ const cycles = detectCircularDependencies(tasks);
1548
+ const overdue = getOverdueTasks2(tasks);
1549
+ const staleBlocked = getStaleBlockedTasks2(tasks);
1550
+ const bottlenecks = findBottlenecks(tasks);
1551
+ const metrics = [
1552
+ {
1553
+ name: "Completion rate",
1554
+ status: stats.completionPercent >= 50 ? "pass" : stats.completionPercent >= 20 ? "warn" : "fail",
1555
+ value: `${stats.completionPercent}%`,
1556
+ detail: `${stats.completed}/${stats.total - stats.cancelled} tasks`
1557
+ },
1558
+ {
1559
+ name: "No cycles",
1560
+ status: cycles.length === 0 ? "pass" : "fail",
1561
+ value: cycles.length === 0 ? "Clean" : `${cycles.length} found`,
1562
+ detail: cycles.length > 0 ? `Involves: ${cycles[0].join(" -> ")}` : undefined
1563
+ },
1564
+ {
1565
+ name: "Blocked tasks",
1566
+ status: stats.blocked === 0 ? "pass" : stats.blocked <= 2 ? "warn" : "fail",
1567
+ value: `${stats.blocked} tasks`,
1568
+ detail: depMetrics.blockedByDependencies > 0 ? `${depMetrics.blockedByDependencies} waiting on dependencies` : undefined
1569
+ },
1570
+ {
1571
+ name: "Overdue tasks",
1572
+ status: overdue.length === 0 ? "pass" : "fail",
1573
+ value: overdue.length === 0 ? "None" : `${overdue.length} tasks`,
1574
+ detail: overdue.length > 0 ? overdue.map((t) => t.title).slice(0, 2).join(", ") : undefined
1575
+ },
1576
+ {
1577
+ name: "Stale blocked",
1578
+ status: staleBlocked.length === 0 ? "pass" : "warn",
1579
+ value: staleBlocked.length === 0 ? "None" : `${staleBlocked.length} tasks (>3 days)`,
1580
+ detail: staleBlocked.length > 0 ? staleBlocked.map((t) => t.title).slice(0, 2).join(", ") : undefined
1581
+ }
1582
+ ];
1583
+ const score = calculateHealthScore(stats, cycles, overdue, staleBlocked);
1584
+ const recommendations = generateRecommendations(tasks, stats, metrics, bottlenecks);
1585
+ return {
1586
+ score,
1587
+ metrics,
1588
+ bottlenecks,
1589
+ recommendations
1590
+ };
1591
+ }
1592
+ function getMetricSymbol(status) {
1593
+ switch (status) {
1594
+ case "pass":
1595
+ return c.green("[/]");
1596
+ case "warn":
1597
+ return c.yellow("[!]");
1598
+ case "fail":
1599
+ return c.red("[x]");
1600
+ }
1601
+ }
1602
+ function renderHealthScore(score, workspace) {
1603
+ const lines = [];
1604
+ let scoreColor;
1605
+ let label;
1606
+ if (score >= 80) {
1607
+ scoreColor = c.green;
1608
+ label = "Excellent";
1609
+ } else if (score >= 60) {
1610
+ scoreColor = c.cyan;
1611
+ label = "Good";
1612
+ } else if (score >= 40) {
1613
+ scoreColor = c.yellow;
1614
+ label = "Fair";
1615
+ } else {
1616
+ scoreColor = c.red;
1617
+ label = "Needs Attention";
1618
+ }
1619
+ lines.push(c.cyan(c.bold("Workspace Health")));
1620
+ lines.push(c.gray(hline(50)));
1621
+ lines.push("");
1622
+ lines.push(` ${c.gray("Workspace:")} ${c.bold(workspace)}`);
1623
+ lines.push("");
1624
+ lines.push(` ${c.gray("Score:")} ${scoreColor(c.bold(`${score}`))}${c.gray("/100")} ${c.dim(`(${label})`)}`);
1625
+ lines.push("");
1626
+ lines.push(` ${progressBar(score, 100, { width: 40, showPercent: false })}`);
1627
+ return lines;
1628
+ }
1629
+ function renderMetrics(metrics) {
1630
+ const lines = [];
1631
+ lines.push("");
1632
+ lines.push(c.cyan(c.bold(" Health Metrics")));
1633
+ lines.push("");
1634
+ for (const metric of metrics) {
1635
+ const symbol = getMetricSymbol(metric.status);
1636
+ const value = metric.status === "pass" ? c.green(metric.value) : metric.status === "warn" ? c.yellow(metric.value) : c.red(metric.value);
1637
+ lines.push(` ${symbol} ${metric.name}: ${value}`);
1638
+ if (metric.detail) {
1639
+ lines.push(` ${c.dim(metric.detail)}`);
1640
+ }
1641
+ }
1642
+ return lines;
1643
+ }
1644
+ function renderBottlenecks(bottlenecks) {
1645
+ if (bottlenecks.length === 0) {
1646
+ return [];
1647
+ }
1648
+ const items = bottlenecks.map((b) => ({
1649
+ primary: `${c.gray(b.task.id.slice(-4))} ${b.task.title}`,
1650
+ secondary: c.yellow(`blocks ${b.blockingCount} task${b.blockingCount > 1 ? "s" : ""}`),
1651
+ prefix: formatStatusSymbol(b.task.status)
1652
+ }));
1653
+ return ["", ...renderSection("Bottlenecks", items, { icon: "" })];
1654
+ }
1655
+ function renderRecommendations(recommendations) {
1656
+ const lines = [];
1657
+ lines.push("");
1658
+ lines.push(c.cyan(c.bold(" Recommendations")));
1659
+ lines.push("");
1660
+ for (const rec of recommendations) {
1661
+ lines.push(` ${c.dim("-")} ${rec}`);
1662
+ }
1663
+ return lines;
1664
+ }
1665
+ function renderQuickSummary(score, workspace) {
1666
+ let scoreColor;
1667
+ let label;
1668
+ if (score >= 80) {
1669
+ scoreColor = c.green;
1670
+ label = "Excellent";
1671
+ } else if (score >= 60) {
1672
+ scoreColor = c.cyan;
1673
+ label = "Good";
1674
+ } else if (score >= 40) {
1675
+ scoreColor = c.yellow;
1676
+ label = "Fair";
1677
+ } else {
1678
+ scoreColor = c.red;
1679
+ label = "Needs Attention";
1680
+ }
1681
+ return [
1682
+ `${c.bold(workspace)} health: ${scoreColor(c.bold(`${score}`))}${c.gray("/100")} ${c.dim(`(${label})`)}`,
1683
+ `${progressBar(score, 100, { width: 30, showPercent: false })}`
1684
+ ];
1685
+ }
1686
+ async function healthCmd(options = {}) {
1687
+ const tasks = await listTasks();
1688
+ const workspace = getCurrentWorkspace();
1689
+ if (tasks.length === 0) {
1690
+ console.log(c.yellow("No tasks found in workspace."));
1691
+ console.log(c.gray(`Workspace: ${workspace}`));
1692
+ return;
1693
+ }
1694
+ const analysis = analyzeHealth(tasks);
1695
+ if (options.quick) {
1696
+ const lines2 = renderQuickSummary(analysis.score, workspace);
1697
+ console.log(lines2.join(`
1698
+ `));
1699
+ return;
1700
+ }
1701
+ const lines = [];
1702
+ lines.push(...renderHealthScore(analysis.score, workspace));
1703
+ lines.push(...renderMetrics(analysis.metrics));
1704
+ lines.push(...renderBottlenecks(analysis.bottlenecks));
1705
+ lines.push(...renderRecommendations(analysis.recommendations));
1706
+ lines.push("");
1707
+ console.log(lines.join(`
1708
+ `));
1709
+ }
588
1710
  // package.json
589
1711
  var package_default = {
590
1712
  name: "@task-mcp/cli",
591
- version: "1.0.28",
1713
+ version: "1.0.30",
592
1714
  description: "Zero-dependency CLI for task-mcp with Bun native visualization",
593
1715
  type: "module",
594
1716
  bin: {
@@ -658,7 +1780,7 @@ function parseArgs(argv) {
658
1780
  }
659
1781
  }
660
1782
  return {
661
- command: positional[0] ?? "dashboard",
1783
+ command: positional[0] ?? "",
662
1784
  args: positional.slice(1),
663
1785
  flags
664
1786
  };
@@ -672,7 +1794,16 @@ ${c.yellow("USAGE")}
672
1794
  ${c.bold("task")} [command] [options]
673
1795
 
674
1796
  ${c.yellow("COMMANDS")}
675
- ${c.cyan("dashboard")} [workspace] Show workspace dashboard (default)
1797
+ ${c.cyan("(no command)")} Show smart dashboard (default)
1798
+ ${c.cyan("next")} Show suggested next task with details
1799
+ ${c.cyan("n")} Alias for next
1800
+ ${c.cyan("blocked")} Show blocked tasks and their causes
1801
+ ${c.cyan("b")} Alias for blocked
1802
+ ${c.cyan("tree")} Show task hierarchy as ASCII tree
1803
+ ${c.cyan("t")} Alias for tree
1804
+ ${c.cyan("health")} Show workspace health analysis
1805
+ ${c.cyan("h")} Alias for health
1806
+ ${c.cyan("dashboard")} [workspace] Show detailed workspace dashboard
676
1807
  ${c.cyan("d")} [workspace] Alias for dashboard
677
1808
  ${c.cyan("list")} List all tasks
678
1809
  ${c.cyan("ls")} Alias for list
@@ -695,8 +1826,11 @@ ${c.yellow("INBOX SUBCOMMANDS")}
695
1826
 
696
1827
  ${c.yellow("OPTIONS")}
697
1828
  ${c.cyan("-i, --interactive")} Start interactive mode
1829
+ ${c.cyan("--strategy")} <strategy> Next task strategy (balanced,quick_wins,unblock,critical_path)
698
1830
  ${c.cyan("--status")} <status> Filter tasks by status (pending,in_progress,blocked,completed,cancelled)
699
1831
  ${c.cyan("--priority")} <priority> Filter tasks by priority (critical,high,medium,low)
1832
+ ${c.cyan("--stale")} Show only stale blocked tasks (3+ days)
1833
+ ${c.cyan("--quick")} Quick health score only
700
1834
  ${c.cyan("--all")} Include completed/cancelled tasks
701
1835
 
702
1836
  ${c.yellow("EXAMPLES")}
@@ -786,10 +1920,37 @@ async function main() {
786
1920
  }
787
1921
  try {
788
1922
  switch (command) {
1923
+ case "":
1924
+ await smartDashboard();
1925
+ break;
789
1926
  case "dashboard":
790
1927
  case "d":
791
1928
  await dashboard(args[0]);
792
1929
  break;
1930
+ case "next":
1931
+ case "n":
1932
+ await nextCmd({
1933
+ strategy: flags.strategy
1934
+ });
1935
+ break;
1936
+ case "blocked":
1937
+ case "b":
1938
+ await blockedCmd({
1939
+ stale: flags.stale === true
1940
+ });
1941
+ break;
1942
+ case "tree":
1943
+ case "t":
1944
+ await treeCmd({
1945
+ hideCompleted: flags["hide-completed"] === true
1946
+ });
1947
+ break;
1948
+ case "health":
1949
+ case "h":
1950
+ await healthCmd({
1951
+ quick: flags.quick === true
1952
+ });
1953
+ break;
793
1954
  case "list":
794
1955
  case "ls":
795
1956
  await listTasksCmd({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@task-mcp/cli",
3
- "version": "1.0.29",
3
+ "version": "1.0.31",
4
4
  "description": "Zero-dependency CLI for task-mcp with Bun native visualization",
5
5
  "type": "module",
6
6
  "bin": {