@task-mcp/cli 1.0.21 → 1.0.23
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/README.md +1 -6
- package/dist/index.js +818 -0
- package/package.json +7 -5
- package/src/__tests__/ansi.test.ts +0 -221
- package/src/__tests__/dashboard.test.ts +0 -226
- package/src/__tests__/inbox.test.ts +0 -307
- package/src/__tests__/index.test.ts +0 -140
- package/src/__tests__/list.test.ts +0 -347
- package/src/__tests__/storage.test.ts +0 -271
- package/src/ansi.ts +0 -50
- package/src/commands/dashboard.ts +0 -92
- package/src/commands/inbox.ts +0 -229
- package/src/commands/list.ts +0 -106
- package/src/constants.ts +0 -59
- package/src/index.ts +0 -277
- package/src/interactive.ts +0 -254
- package/src/storage.ts +0 -221
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test, mock, beforeEach, afterEach, spyOn } from "bun:test";
|
|
2
|
-
import type { Task, WorkspaceInfo } from "../storage.js";
|
|
3
|
-
|
|
4
|
-
// Helper to create test tasks
|
|
5
|
-
function createTask(overrides: Partial<Task> = {}): Task {
|
|
6
|
-
return {
|
|
7
|
-
id: `task_${Math.random().toString(36).slice(2, 10)}`,
|
|
8
|
-
title: "Test Task",
|
|
9
|
-
status: "pending",
|
|
10
|
-
priority: "medium",
|
|
11
|
-
workspace: "test-workspace",
|
|
12
|
-
createdAt: new Date().toISOString(),
|
|
13
|
-
updatedAt: new Date().toISOString(),
|
|
14
|
-
...overrides,
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Helper to create test workspaces
|
|
19
|
-
function createWorkspace(overrides: Partial<WorkspaceInfo> = {}): WorkspaceInfo {
|
|
20
|
-
return {
|
|
21
|
-
name: "Test Workspace",
|
|
22
|
-
taskCount: 0,
|
|
23
|
-
completedCount: 0,
|
|
24
|
-
...overrides,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
describe("list commands - unit tests", () => {
|
|
29
|
-
describe("task filtering logic", () => {
|
|
30
|
-
test("filters tasks by single status", () => {
|
|
31
|
-
const tasks = [
|
|
32
|
-
createTask({ id: "task_1", status: "pending" }),
|
|
33
|
-
createTask({ id: "task_2", status: "completed" }),
|
|
34
|
-
createTask({ id: "task_3", status: "in_progress" }),
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
const status = "pending";
|
|
38
|
-
const statuses = status.split(",");
|
|
39
|
-
const filtered = tasks.filter((t) => statuses.includes(t.status));
|
|
40
|
-
|
|
41
|
-
expect(filtered.length).toBe(1);
|
|
42
|
-
expect(filtered[0]?.id).toBe("task_1");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test("filters tasks by multiple statuses", () => {
|
|
46
|
-
const tasks = [
|
|
47
|
-
createTask({ id: "task_1", status: "pending" }),
|
|
48
|
-
createTask({ id: "task_2", status: "completed" }),
|
|
49
|
-
createTask({ id: "task_3", status: "in_progress" }),
|
|
50
|
-
createTask({ id: "task_4", status: "blocked" }),
|
|
51
|
-
];
|
|
52
|
-
|
|
53
|
-
const status = "pending,in_progress";
|
|
54
|
-
const statuses = status.split(",");
|
|
55
|
-
const filtered = tasks.filter((t) => statuses.includes(t.status));
|
|
56
|
-
|
|
57
|
-
expect(filtered.length).toBe(2);
|
|
58
|
-
expect(filtered.map((t) => t.id).sort()).toEqual(["task_1", "task_3"]);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("filters tasks by priority", () => {
|
|
62
|
-
const tasks = [
|
|
63
|
-
createTask({ id: "task_1", priority: "high" }),
|
|
64
|
-
createTask({ id: "task_2", priority: "low" }),
|
|
65
|
-
createTask({ id: "task_3", priority: "high" }),
|
|
66
|
-
createTask({ id: "task_4", priority: "medium" }),
|
|
67
|
-
];
|
|
68
|
-
|
|
69
|
-
const priority = "high";
|
|
70
|
-
const priorities = priority.split(",");
|
|
71
|
-
const filtered = tasks.filter((t) => priorities.includes(t.priority));
|
|
72
|
-
|
|
73
|
-
expect(filtered.length).toBe(2);
|
|
74
|
-
expect(filtered.map((t) => t.id).sort()).toEqual(["task_1", "task_3"]);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("filters by multiple priorities", () => {
|
|
78
|
-
const tasks = [
|
|
79
|
-
createTask({ id: "task_1", priority: "high" }),
|
|
80
|
-
createTask({ id: "task_2", priority: "low" }),
|
|
81
|
-
createTask({ id: "task_3", priority: "critical" }),
|
|
82
|
-
createTask({ id: "task_4", priority: "medium" }),
|
|
83
|
-
];
|
|
84
|
-
|
|
85
|
-
const priority = "high,critical";
|
|
86
|
-
const priorities = priority.split(",");
|
|
87
|
-
const filtered = tasks.filter((t) => priorities.includes(t.priority));
|
|
88
|
-
|
|
89
|
-
expect(filtered.length).toBe(2);
|
|
90
|
-
expect(filtered.map((t) => t.id).sort()).toEqual(["task_1", "task_3"]);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("default filter hides completed and cancelled tasks", () => {
|
|
94
|
-
const tasks = [
|
|
95
|
-
createTask({ id: "task_1", status: "pending" }),
|
|
96
|
-
createTask({ id: "task_2", status: "completed" }),
|
|
97
|
-
createTask({ id: "task_3", status: "cancelled" }),
|
|
98
|
-
createTask({ id: "task_4", status: "in_progress" }),
|
|
99
|
-
];
|
|
100
|
-
|
|
101
|
-
// Default behavior: hide completed/cancelled
|
|
102
|
-
const filtered = tasks.filter(
|
|
103
|
-
(t) => t.status !== "completed" && t.status !== "cancelled"
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
expect(filtered.length).toBe(2);
|
|
107
|
-
expect(filtered.map((t) => t.id).sort()).toEqual(["task_1", "task_4"]);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test("all flag includes completed and cancelled tasks", () => {
|
|
111
|
-
const tasks = [
|
|
112
|
-
createTask({ id: "task_1", status: "pending" }),
|
|
113
|
-
createTask({ id: "task_2", status: "completed" }),
|
|
114
|
-
createTask({ id: "task_3", status: "cancelled" }),
|
|
115
|
-
];
|
|
116
|
-
|
|
117
|
-
const all = true;
|
|
118
|
-
// With --all, no filtering
|
|
119
|
-
const filtered = all ? tasks : tasks.filter(
|
|
120
|
-
(t) => t.status !== "completed" && t.status !== "cancelled"
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
expect(filtered.length).toBe(3);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("combined status and priority filtering", () => {
|
|
127
|
-
const tasks = [
|
|
128
|
-
createTask({ id: "task_1", status: "pending", priority: "high" }),
|
|
129
|
-
createTask({ id: "task_2", status: "pending", priority: "low" }),
|
|
130
|
-
createTask({ id: "task_3", status: "completed", priority: "high" }),
|
|
131
|
-
createTask({ id: "task_4", status: "in_progress", priority: "high" }),
|
|
132
|
-
];
|
|
133
|
-
|
|
134
|
-
const status = "pending,in_progress";
|
|
135
|
-
const priority = "high";
|
|
136
|
-
const statuses = status.split(",");
|
|
137
|
-
const priorities = priority.split(",");
|
|
138
|
-
|
|
139
|
-
let filtered = tasks.filter((t) => statuses.includes(t.status));
|
|
140
|
-
filtered = filtered.filter((t) => priorities.includes(t.priority));
|
|
141
|
-
|
|
142
|
-
expect(filtered.length).toBe(2);
|
|
143
|
-
expect(filtered.map((t) => t.id).sort()).toEqual(["task_1", "task_4"]);
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
describe("project progress calculation", () => {
|
|
148
|
-
test("calculates progress for empty project", () => {
|
|
149
|
-
const tasks: Task[] = [];
|
|
150
|
-
const completed = tasks.filter((t) => t.status === "completed").length;
|
|
151
|
-
const total = tasks.filter((t) => t.status !== "cancelled").length;
|
|
152
|
-
const progress = total > 0 ? `${completed}/${total}` : "0/0";
|
|
153
|
-
|
|
154
|
-
expect(progress).toBe("0/0");
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("calculates progress excluding cancelled tasks", () => {
|
|
158
|
-
const tasks = [
|
|
159
|
-
createTask({ status: "completed" }),
|
|
160
|
-
createTask({ status: "completed" }),
|
|
161
|
-
createTask({ status: "pending" }),
|
|
162
|
-
createTask({ status: "cancelled" }),
|
|
163
|
-
createTask({ status: "cancelled" }),
|
|
164
|
-
];
|
|
165
|
-
|
|
166
|
-
const completed = tasks.filter((t) => t.status === "completed").length;
|
|
167
|
-
const total = tasks.filter((t) => t.status !== "cancelled").length;
|
|
168
|
-
const progress = `${completed}/${total}`;
|
|
169
|
-
|
|
170
|
-
expect(progress).toBe("2/3"); // 2 completed out of 3 non-cancelled
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test("calculates 100% completion", () => {
|
|
174
|
-
const tasks = [
|
|
175
|
-
createTask({ status: "completed" }),
|
|
176
|
-
createTask({ status: "completed" }),
|
|
177
|
-
createTask({ status: "completed" }),
|
|
178
|
-
];
|
|
179
|
-
|
|
180
|
-
const completed = tasks.filter((t) => t.status === "completed").length;
|
|
181
|
-
const total = tasks.filter((t) => t.status !== "cancelled").length;
|
|
182
|
-
const progress = `${completed}/${total}`;
|
|
183
|
-
|
|
184
|
-
expect(progress).toBe("3/3");
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
test("handles all cancelled tasks", () => {
|
|
188
|
-
const tasks = [
|
|
189
|
-
createTask({ status: "cancelled" }),
|
|
190
|
-
createTask({ status: "cancelled" }),
|
|
191
|
-
];
|
|
192
|
-
|
|
193
|
-
const completed = tasks.filter((t) => t.status === "completed").length;
|
|
194
|
-
const total = tasks.filter((t) => t.status !== "cancelled").length;
|
|
195
|
-
const progress = total > 0 ? `${completed}/${total}` : "0/0";
|
|
196
|
-
|
|
197
|
-
expect(progress).toBe("0/0");
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
describe("formatProjectStatus", () => {
|
|
202
|
-
test("maps project statuses correctly", () => {
|
|
203
|
-
type ProjectStatus = "active" | "on_hold" | "completed" | "archived";
|
|
204
|
-
|
|
205
|
-
const formatProjectStatus = (status: ProjectStatus): string => {
|
|
206
|
-
const statusMap: Record<ProjectStatus, string> = {
|
|
207
|
-
active: "active",
|
|
208
|
-
on_hold: "on_hold",
|
|
209
|
-
completed: "completed",
|
|
210
|
-
archived: "archived",
|
|
211
|
-
};
|
|
212
|
-
return statusMap[status] ?? status;
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
expect(formatProjectStatus("active")).toBe("active");
|
|
216
|
-
expect(formatProjectStatus("on_hold")).toBe("on_hold");
|
|
217
|
-
expect(formatProjectStatus("completed")).toBe("completed");
|
|
218
|
-
expect(formatProjectStatus("archived")).toBe("archived");
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
describe("formatDate", () => {
|
|
223
|
-
test("formats valid date string", () => {
|
|
224
|
-
const formatDate = (dateStr?: string): string => {
|
|
225
|
-
if (!dateStr) return "-";
|
|
226
|
-
const date = new Date(dateStr);
|
|
227
|
-
return date.toLocaleDateString();
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
const result = formatDate("2024-12-25");
|
|
231
|
-
// Result depends on locale, but should be non-empty
|
|
232
|
-
expect(result).not.toBe("-");
|
|
233
|
-
expect(result.length).toBeGreaterThan(0);
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
test("returns dash for undefined date", () => {
|
|
237
|
-
const formatDate = (dateStr?: string): string => {
|
|
238
|
-
if (!dateStr) return "-";
|
|
239
|
-
const date = new Date(dateStr);
|
|
240
|
-
return date.toLocaleDateString();
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
expect(formatDate(undefined)).toBe("-");
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
test("returns dash for empty date string", () => {
|
|
247
|
-
const formatDate = (dateStr?: string): string => {
|
|
248
|
-
if (!dateStr) return "-";
|
|
249
|
-
const date = new Date(dateStr);
|
|
250
|
-
return date.toLocaleDateString();
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
expect(formatDate("")).toBe("-");
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
describe("table column definitions", () => {
|
|
258
|
-
test("task table has required columns", () => {
|
|
259
|
-
const columns = [
|
|
260
|
-
{ header: "ID", key: "id", width: 6 },
|
|
261
|
-
{ header: "Title", key: "title", width: 45 },
|
|
262
|
-
{ header: "Status", key: "status", width: 14 },
|
|
263
|
-
{ header: "Priority", key: "priority", width: 10 },
|
|
264
|
-
{ header: "Due", key: "dueDate", width: 12 },
|
|
265
|
-
];
|
|
266
|
-
|
|
267
|
-
expect(columns.find((c) => c.key === "id")).toBeDefined();
|
|
268
|
-
expect(columns.find((c) => c.key === "title")).toBeDefined();
|
|
269
|
-
expect(columns.find((c) => c.key === "status")).toBeDefined();
|
|
270
|
-
expect(columns.find((c) => c.key === "priority")).toBeDefined();
|
|
271
|
-
expect(columns.find((c) => c.key === "dueDate")).toBeDefined();
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
test("project table has required columns", () => {
|
|
275
|
-
const columns = [
|
|
276
|
-
{ header: "ID", key: "id", width: 6 },
|
|
277
|
-
{ header: "Name", key: "name", width: 30 },
|
|
278
|
-
{ header: "Status", key: "status", width: 14 },
|
|
279
|
-
{ header: "Tasks", key: "progress", width: 10 },
|
|
280
|
-
{ header: "Updated", key: "updatedAt", width: 12 },
|
|
281
|
-
];
|
|
282
|
-
|
|
283
|
-
expect(columns.find((c) => c.key === "id")).toBeDefined();
|
|
284
|
-
expect(columns.find((c) => c.key === "name")).toBeDefined();
|
|
285
|
-
expect(columns.find((c) => c.key === "status")).toBeDefined();
|
|
286
|
-
expect(columns.find((c) => c.key === "progress")).toBeDefined();
|
|
287
|
-
expect(columns.find((c) => c.key === "updatedAt")).toBeDefined();
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
describe("ID truncation", () => {
|
|
292
|
-
test("truncates task ID to 4 characters", () => {
|
|
293
|
-
const id = "task_abc12345";
|
|
294
|
-
const truncated = id.slice(0, 4);
|
|
295
|
-
expect(truncated).toBe("task");
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
test("truncates project ID to 4 characters", () => {
|
|
299
|
-
const id = "proj_xyz98765";
|
|
300
|
-
const truncated = id.slice(0, 4);
|
|
301
|
-
expect(truncated).toBe("proj");
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
test("handles short IDs", () => {
|
|
305
|
-
const id = "ab";
|
|
306
|
-
const truncated = id.slice(0, 4);
|
|
307
|
-
expect(truncated).toBe("ab");
|
|
308
|
-
});
|
|
309
|
-
});
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
describe("list commands - exports", () => {
|
|
313
|
-
test("listTasksCmd is exported", async () => {
|
|
314
|
-
const { listTasksCmd } = await import("../commands/list.js");
|
|
315
|
-
expect(typeof listTasksCmd).toBe("function");
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
test("listWorkspacesCmd is exported", async () => {
|
|
319
|
-
const { listWorkspacesCmd } = await import("../commands/list.js");
|
|
320
|
-
expect(typeof listWorkspacesCmd).toBe("function");
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
test("listTasksCmd accepts options object", async () => {
|
|
324
|
-
const { listTasksCmd } = await import("../commands/list.js");
|
|
325
|
-
|
|
326
|
-
// Verify the function signature accepts the expected options
|
|
327
|
-
const options = {
|
|
328
|
-
workspace: "test-workspace",
|
|
329
|
-
status: "pending",
|
|
330
|
-
priority: "high",
|
|
331
|
-
all: true,
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
// Type check passes if this compiles
|
|
335
|
-
expect(options.workspace).toBeDefined();
|
|
336
|
-
expect(options.status).toBeDefined();
|
|
337
|
-
expect(options.priority).toBeDefined();
|
|
338
|
-
expect(options.all).toBeDefined();
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
test("listWorkspacesCmd accepts no options", async () => {
|
|
342
|
-
const { listWorkspacesCmd } = await import("../commands/list.js");
|
|
343
|
-
|
|
344
|
-
// listWorkspacesCmd takes no options
|
|
345
|
-
expect(typeof listWorkspacesCmd).toBe("function");
|
|
346
|
-
});
|
|
347
|
-
});
|
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
calculateStats,
|
|
4
|
-
calculateDependencyMetrics,
|
|
5
|
-
suggestNextTask,
|
|
6
|
-
type Task,
|
|
7
|
-
} from "../storage.js";
|
|
8
|
-
|
|
9
|
-
// Helper to create test tasks
|
|
10
|
-
function createTask(overrides: Partial<Task> = {}): Task {
|
|
11
|
-
return {
|
|
12
|
-
id: `task_${Math.random().toString(36).slice(2, 10)}`,
|
|
13
|
-
title: "Test Task",
|
|
14
|
-
status: "pending",
|
|
15
|
-
priority: "medium",
|
|
16
|
-
workspace: "test-workspace",
|
|
17
|
-
createdAt: new Date().toISOString(),
|
|
18
|
-
updatedAt: new Date().toISOString(),
|
|
19
|
-
...overrides,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe("storage", () => {
|
|
24
|
-
describe("calculateStats", () => {
|
|
25
|
-
test("calculates stats for empty task list", () => {
|
|
26
|
-
const stats = calculateStats([]);
|
|
27
|
-
expect(stats.total).toBe(0);
|
|
28
|
-
expect(stats.completed).toBe(0);
|
|
29
|
-
expect(stats.pending).toBe(0);
|
|
30
|
-
expect(stats.completionPercent).toBe(0);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test("counts tasks by status", () => {
|
|
34
|
-
const tasks = [
|
|
35
|
-
createTask({ status: "pending" }),
|
|
36
|
-
createTask({ status: "pending" }),
|
|
37
|
-
createTask({ status: "in_progress" }),
|
|
38
|
-
createTask({ status: "completed" }),
|
|
39
|
-
createTask({ status: "blocked" }),
|
|
40
|
-
createTask({ status: "cancelled" }),
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
const stats = calculateStats(tasks);
|
|
44
|
-
expect(stats.total).toBe(6);
|
|
45
|
-
expect(stats.pending).toBe(2);
|
|
46
|
-
expect(stats.inProgress).toBe(1);
|
|
47
|
-
expect(stats.completed).toBe(1);
|
|
48
|
-
expect(stats.blocked).toBe(1);
|
|
49
|
-
expect(stats.cancelled).toBe(1);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("counts tasks by priority", () => {
|
|
53
|
-
const tasks = [
|
|
54
|
-
createTask({ priority: "critical" }),
|
|
55
|
-
createTask({ priority: "high" }),
|
|
56
|
-
createTask({ priority: "high" }),
|
|
57
|
-
createTask({ priority: "medium" }),
|
|
58
|
-
createTask({ priority: "low" }),
|
|
59
|
-
];
|
|
60
|
-
|
|
61
|
-
const stats = calculateStats(tasks);
|
|
62
|
-
expect(stats.byPriority.critical).toBe(1);
|
|
63
|
-
expect(stats.byPriority.high).toBe(2);
|
|
64
|
-
expect(stats.byPriority.medium).toBe(1);
|
|
65
|
-
expect(stats.byPriority.low).toBe(1);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("calculates completion percent excluding cancelled", () => {
|
|
69
|
-
const tasks = [
|
|
70
|
-
createTask({ status: "completed" }),
|
|
71
|
-
createTask({ status: "completed" }),
|
|
72
|
-
createTask({ status: "pending" }),
|
|
73
|
-
createTask({ status: "pending" }),
|
|
74
|
-
createTask({ status: "cancelled" }), // Should be excluded from calculation
|
|
75
|
-
];
|
|
76
|
-
|
|
77
|
-
const stats = calculateStats(tasks);
|
|
78
|
-
// 2 completed out of 4 non-cancelled = 50%
|
|
79
|
-
expect(stats.completionPercent).toBe(50);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test("handles all cancelled tasks", () => {
|
|
83
|
-
const tasks = [
|
|
84
|
-
createTask({ status: "cancelled" }),
|
|
85
|
-
createTask({ status: "cancelled" }),
|
|
86
|
-
];
|
|
87
|
-
|
|
88
|
-
const stats = calculateStats(tasks);
|
|
89
|
-
expect(stats.completionPercent).toBe(0);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe("calculateDependencyMetrics", () => {
|
|
94
|
-
test("calculates metrics for tasks without dependencies", () => {
|
|
95
|
-
const tasks = [
|
|
96
|
-
createTask({ status: "pending" }),
|
|
97
|
-
createTask({ status: "pending" }),
|
|
98
|
-
createTask({ status: "in_progress" }),
|
|
99
|
-
];
|
|
100
|
-
|
|
101
|
-
const metrics = calculateDependencyMetrics(tasks);
|
|
102
|
-
expect(metrics.noDependencies).toBe(3);
|
|
103
|
-
expect(metrics.readyToWork).toBe(3);
|
|
104
|
-
expect(metrics.blockedByDependencies).toBe(0);
|
|
105
|
-
expect(metrics.avgDependencies).toBe(0);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("identifies tasks blocked by dependencies", () => {
|
|
109
|
-
const task1 = createTask({ id: "task_1", status: "pending" });
|
|
110
|
-
const task2 = createTask({
|
|
111
|
-
id: "task_2",
|
|
112
|
-
status: "pending",
|
|
113
|
-
dependencies: [{ taskId: "task_1", type: "blocks" }],
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
const metrics = calculateDependencyMetrics([task1, task2]);
|
|
117
|
-
expect(metrics.noDependencies).toBe(1);
|
|
118
|
-
expect(metrics.readyToWork).toBe(1); // Only task1 is ready
|
|
119
|
-
expect(metrics.blockedByDependencies).toBe(1);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("ready to work includes tasks with completed dependencies", () => {
|
|
123
|
-
const task1 = createTask({ id: "task_1", status: "completed" });
|
|
124
|
-
const task2 = createTask({
|
|
125
|
-
id: "task_2",
|
|
126
|
-
status: "pending",
|
|
127
|
-
dependencies: [{ taskId: "task_1", type: "blocks" }],
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const metrics = calculateDependencyMetrics([task1, task2]);
|
|
131
|
-
expect(metrics.readyToWork).toBe(1); // task2 is ready because task1 is completed
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test("finds most depended-on task", () => {
|
|
135
|
-
const task1 = createTask({ id: "task_1", title: "Base Task", status: "pending" });
|
|
136
|
-
const task2 = createTask({
|
|
137
|
-
id: "task_2",
|
|
138
|
-
status: "pending",
|
|
139
|
-
dependencies: [{ taskId: "task_1", type: "blocks" }],
|
|
140
|
-
});
|
|
141
|
-
const task3 = createTask({
|
|
142
|
-
id: "task_3",
|
|
143
|
-
status: "pending",
|
|
144
|
-
dependencies: [{ taskId: "task_1", type: "blocks" }],
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
const metrics = calculateDependencyMetrics([task1, task2, task3]);
|
|
148
|
-
expect(metrics.mostDependedOn).not.toBeNull();
|
|
149
|
-
expect(metrics.mostDependedOn?.id).toBe("task_1");
|
|
150
|
-
expect(metrics.mostDependedOn?.count).toBe(2);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test("calculates average dependencies", () => {
|
|
154
|
-
const task1 = createTask({ id: "task_1", status: "pending" });
|
|
155
|
-
const task2 = createTask({
|
|
156
|
-
id: "task_2",
|
|
157
|
-
status: "pending",
|
|
158
|
-
dependencies: [
|
|
159
|
-
{ taskId: "task_1", type: "blocks" },
|
|
160
|
-
{ taskId: "task_x", type: "blocks" },
|
|
161
|
-
],
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
const metrics = calculateDependencyMetrics([task1, task2]);
|
|
165
|
-
expect(metrics.avgDependencies).toBe(1); // 2 deps / 2 tasks = 1
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test("excludes completed and cancelled tasks from active count", () => {
|
|
169
|
-
const tasks = [
|
|
170
|
-
createTask({ status: "completed" }),
|
|
171
|
-
createTask({ status: "cancelled" }),
|
|
172
|
-
createTask({ status: "pending" }),
|
|
173
|
-
];
|
|
174
|
-
|
|
175
|
-
const metrics = calculateDependencyMetrics(tasks);
|
|
176
|
-
expect(metrics.noDependencies).toBe(1); // Only the pending task
|
|
177
|
-
expect(metrics.readyToWork).toBe(1);
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
describe("suggestNextTask", () => {
|
|
182
|
-
test("returns null for empty task list", () => {
|
|
183
|
-
expect(suggestNextTask([])).toBeNull();
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test("returns null when no actionable tasks", () => {
|
|
187
|
-
const tasks = [
|
|
188
|
-
createTask({ status: "completed" }),
|
|
189
|
-
createTask({ status: "cancelled" }),
|
|
190
|
-
createTask({ status: "blocked" }),
|
|
191
|
-
];
|
|
192
|
-
expect(suggestNextTask(tasks)).toBeNull();
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
test("suggests task with highest priority", () => {
|
|
196
|
-
const lowTask = createTask({ id: "low", priority: "low", status: "pending" });
|
|
197
|
-
const highTask = createTask({ id: "high", priority: "high", status: "pending" });
|
|
198
|
-
const mediumTask = createTask({ id: "medium", priority: "medium", status: "pending" });
|
|
199
|
-
|
|
200
|
-
const suggestion = suggestNextTask([lowTask, highTask, mediumTask]);
|
|
201
|
-
expect(suggestion?.id).toBe("high");
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test("critical priority comes first", () => {
|
|
205
|
-
const tasks = [
|
|
206
|
-
createTask({ id: "high", priority: "high", status: "pending" }),
|
|
207
|
-
createTask({ id: "critical", priority: "critical", status: "pending" }),
|
|
208
|
-
];
|
|
209
|
-
|
|
210
|
-
const suggestion = suggestNextTask(tasks);
|
|
211
|
-
expect(suggestion?.id).toBe("critical");
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
test("suggests in_progress tasks", () => {
|
|
215
|
-
const tasks = [
|
|
216
|
-
createTask({ id: "pending", priority: "high", status: "pending" }),
|
|
217
|
-
createTask({ id: "in_progress", priority: "medium", status: "in_progress" }),
|
|
218
|
-
];
|
|
219
|
-
|
|
220
|
-
// Both should be considered, high priority wins
|
|
221
|
-
const suggestion = suggestNextTask(tasks);
|
|
222
|
-
expect(suggestion?.id).toBe("pending");
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
test("excludes tasks with uncompleted dependencies", () => {
|
|
226
|
-
const task1 = createTask({ id: "task_1", priority: "low", status: "pending" });
|
|
227
|
-
const task2 = createTask({
|
|
228
|
-
id: "task_2",
|
|
229
|
-
priority: "critical",
|
|
230
|
-
status: "pending",
|
|
231
|
-
dependencies: [{ taskId: "task_1", type: "blocks" }],
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
const suggestion = suggestNextTask([task1, task2]);
|
|
235
|
-
// task2 has higher priority but is blocked, so task1 should be suggested
|
|
236
|
-
expect(suggestion?.id).toBe("task_1");
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
test("includes tasks when dependencies are completed", () => {
|
|
240
|
-
const task1 = createTask({ id: "task_1", priority: "low", status: "completed" });
|
|
241
|
-
const task2 = createTask({
|
|
242
|
-
id: "task_2",
|
|
243
|
-
priority: "critical",
|
|
244
|
-
status: "pending",
|
|
245
|
-
dependencies: [{ taskId: "task_1", type: "blocks" }],
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
const suggestion = suggestNextTask([task1, task2]);
|
|
249
|
-
// task1 is completed, so task2 is now actionable
|
|
250
|
-
expect(suggestion?.id).toBe("task_2");
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
test("same priority uses creation date (older first)", () => {
|
|
254
|
-
const older = createTask({
|
|
255
|
-
id: "older",
|
|
256
|
-
priority: "high",
|
|
257
|
-
status: "pending",
|
|
258
|
-
createdAt: "2024-01-01T00:00:00Z",
|
|
259
|
-
});
|
|
260
|
-
const newer = createTask({
|
|
261
|
-
id: "newer",
|
|
262
|
-
priority: "high",
|
|
263
|
-
status: "pending",
|
|
264
|
-
createdAt: "2024-12-01T00:00:00Z",
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
const suggestion = suggestNextTask([newer, older]);
|
|
268
|
-
expect(suggestion?.id).toBe("older");
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
});
|
package/src/ansi.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Re-export terminal UI utilities from shared package
|
|
3
|
-
* This file exists for backward compatibility
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export {
|
|
7
|
-
// Colors and styles
|
|
8
|
-
color,
|
|
9
|
-
style,
|
|
10
|
-
styled,
|
|
11
|
-
c,
|
|
12
|
-
// Box drawing
|
|
13
|
-
BOX,
|
|
14
|
-
box,
|
|
15
|
-
drawBox,
|
|
16
|
-
hline,
|
|
17
|
-
// String utilities
|
|
18
|
-
stripAnsi,
|
|
19
|
-
displayWidth,
|
|
20
|
-
visibleLength,
|
|
21
|
-
pad,
|
|
22
|
-
padEnd,
|
|
23
|
-
padStart,
|
|
24
|
-
center,
|
|
25
|
-
truncateStr,
|
|
26
|
-
// Progress bar
|
|
27
|
-
progressBar,
|
|
28
|
-
type ProgressBarOptions,
|
|
29
|
-
// Layout
|
|
30
|
-
sideBySide,
|
|
31
|
-
sideBySideArrays,
|
|
32
|
-
type BoxOptions,
|
|
33
|
-
// Tables
|
|
34
|
-
table,
|
|
35
|
-
renderTable,
|
|
36
|
-
type TableColumn,
|
|
37
|
-
// Formatters
|
|
38
|
-
statusColors,
|
|
39
|
-
statusIcons,
|
|
40
|
-
icons,
|
|
41
|
-
formatStatus,
|
|
42
|
-
priorityColors,
|
|
43
|
-
formatPriority,
|
|
44
|
-
formatDependencies,
|
|
45
|
-
// Banner
|
|
46
|
-
banner,
|
|
47
|
-
} from "@task-mcp/shared";
|
|
48
|
-
|
|
49
|
-
// Alias for CLI compatibility
|
|
50
|
-
export { truncateStr as truncate } from "@task-mcp/shared";
|