@task-mcp/shared 1.0.20 → 1.0.21
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/package.json +1 -6
- package/dist/algorithms/critical-path.d.ts +0 -46
- package/dist/algorithms/critical-path.d.ts.map +0 -1
- package/dist/algorithms/critical-path.js +0 -320
- package/dist/algorithms/critical-path.js.map +0 -1
- package/dist/algorithms/critical-path.test.d.ts +0 -2
- package/dist/algorithms/critical-path.test.d.ts.map +0 -1
- package/dist/algorithms/critical-path.test.js +0 -194
- package/dist/algorithms/critical-path.test.js.map +0 -1
- package/dist/algorithms/dependency-integrity.d.ts +0 -81
- package/dist/algorithms/dependency-integrity.d.ts.map +0 -1
- package/dist/algorithms/dependency-integrity.js +0 -207
- package/dist/algorithms/dependency-integrity.js.map +0 -1
- package/dist/algorithms/dependency-integrity.test.d.ts +0 -2
- package/dist/algorithms/dependency-integrity.test.d.ts.map +0 -1
- package/dist/algorithms/dependency-integrity.test.js +0 -309
- package/dist/algorithms/dependency-integrity.test.js.map +0 -1
- package/dist/algorithms/index.d.ts +0 -5
- package/dist/algorithms/index.d.ts.map +0 -1
- package/dist/algorithms/index.js +0 -5
- package/dist/algorithms/index.js.map +0 -1
- package/dist/algorithms/tech-analysis.d.ts +0 -106
- package/dist/algorithms/tech-analysis.d.ts.map +0 -1
- package/dist/algorithms/tech-analysis.js +0 -344
- package/dist/algorithms/tech-analysis.js.map +0 -1
- package/dist/algorithms/tech-analysis.test.d.ts +0 -2
- package/dist/algorithms/tech-analysis.test.d.ts.map +0 -1
- package/dist/algorithms/tech-analysis.test.js +0 -338
- package/dist/algorithms/tech-analysis.test.js.map +0 -1
- package/dist/algorithms/topological-sort.d.ts +0 -41
- package/dist/algorithms/topological-sort.d.ts.map +0 -1
- package/dist/algorithms/topological-sort.js +0 -165
- package/dist/algorithms/topological-sort.js.map +0 -1
- package/dist/algorithms/topological-sort.test.d.ts +0 -2
- package/dist/algorithms/topological-sort.test.d.ts.map +0 -1
- package/dist/algorithms/topological-sort.test.js +0 -162
- package/dist/algorithms/topological-sort.test.js.map +0 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -7
- package/dist/index.js.map +0 -1
- package/dist/schemas/inbox.d.ts +0 -55
- package/dist/schemas/inbox.d.ts.map +0 -1
- package/dist/schemas/inbox.js +0 -25
- package/dist/schemas/inbox.js.map +0 -1
- package/dist/schemas/index.d.ts +0 -7
- package/dist/schemas/index.d.ts.map +0 -1
- package/dist/schemas/index.js +0 -17
- package/dist/schemas/index.js.map +0 -1
- package/dist/schemas/project.d.ts +0 -177
- package/dist/schemas/project.d.ts.map +0 -1
- package/dist/schemas/project.js +0 -56
- package/dist/schemas/project.js.map +0 -1
- package/dist/schemas/response-format.d.ts +0 -148
- package/dist/schemas/response-format.d.ts.map +0 -1
- package/dist/schemas/response-format.js +0 -18
- package/dist/schemas/response-format.js.map +0 -1
- package/dist/schemas/response-schema.d.ts +0 -307
- package/dist/schemas/response-schema.d.ts.map +0 -1
- package/dist/schemas/response-schema.js +0 -75
- package/dist/schemas/response-schema.js.map +0 -1
- package/dist/schemas/response-schema.test.d.ts +0 -2
- package/dist/schemas/response-schema.test.d.ts.map +0 -1
- package/dist/schemas/response-schema.test.js +0 -256
- package/dist/schemas/response-schema.test.js.map +0 -1
- package/dist/schemas/state.d.ts +0 -17
- package/dist/schemas/state.d.ts.map +0 -1
- package/dist/schemas/state.js +0 -17
- package/dist/schemas/state.js.map +0 -1
- package/dist/schemas/task.d.ts +0 -881
- package/dist/schemas/task.d.ts.map +0 -1
- package/dist/schemas/task.js +0 -189
- package/dist/schemas/task.js.map +0 -1
- package/dist/schemas/view.d.ts +0 -143
- package/dist/schemas/view.d.ts.map +0 -1
- package/dist/schemas/view.js +0 -48
- package/dist/schemas/view.js.map +0 -1
- package/dist/utils/dashboard-renderer.d.ts +0 -93
- package/dist/utils/dashboard-renderer.d.ts.map +0 -1
- package/dist/utils/dashboard-renderer.js +0 -424
- package/dist/utils/dashboard-renderer.js.map +0 -1
- package/dist/utils/dashboard-renderer.test.d.ts +0 -2
- package/dist/utils/dashboard-renderer.test.d.ts.map +0 -1
- package/dist/utils/dashboard-renderer.test.js +0 -774
- package/dist/utils/dashboard-renderer.test.js.map +0 -1
- package/dist/utils/date.d.ts +0 -94
- package/dist/utils/date.d.ts.map +0 -1
- package/dist/utils/date.js +0 -323
- package/dist/utils/date.js.map +0 -1
- package/dist/utils/date.test.d.ts +0 -2
- package/dist/utils/date.test.d.ts.map +0 -1
- package/dist/utils/date.test.js +0 -276
- package/dist/utils/date.test.js.map +0 -1
- package/dist/utils/hierarchy.d.ts +0 -102
- package/dist/utils/hierarchy.d.ts.map +0 -1
- package/dist/utils/hierarchy.js +0 -236
- package/dist/utils/hierarchy.js.map +0 -1
- package/dist/utils/hierarchy.test.d.ts +0 -2
- package/dist/utils/hierarchy.test.d.ts.map +0 -1
- package/dist/utils/hierarchy.test.js +0 -436
- package/dist/utils/hierarchy.test.js.map +0 -1
- package/dist/utils/id.d.ts +0 -60
- package/dist/utils/id.d.ts.map +0 -1
- package/dist/utils/id.js +0 -118
- package/dist/utils/id.js.map +0 -1
- package/dist/utils/id.test.d.ts +0 -2
- package/dist/utils/id.test.d.ts.map +0 -1
- package/dist/utils/id.test.js +0 -193
- package/dist/utils/id.test.js.map +0 -1
- package/dist/utils/index.d.ts +0 -12
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -34
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/natural-language.d.ts +0 -57
- package/dist/utils/natural-language.d.ts.map +0 -1
- package/dist/utils/natural-language.js +0 -211
- package/dist/utils/natural-language.js.map +0 -1
- package/dist/utils/natural-language.test.d.ts +0 -2
- package/dist/utils/natural-language.test.d.ts.map +0 -1
- package/dist/utils/natural-language.test.js +0 -197
- package/dist/utils/natural-language.test.js.map +0 -1
- package/dist/utils/priority-queue.d.ts +0 -17
- package/dist/utils/priority-queue.d.ts.map +0 -1
- package/dist/utils/priority-queue.js +0 -62
- package/dist/utils/priority-queue.js.map +0 -1
- package/dist/utils/priority-queue.test.d.ts +0 -2
- package/dist/utils/priority-queue.test.d.ts.map +0 -1
- package/dist/utils/priority-queue.test.js +0 -82
- package/dist/utils/priority-queue.test.js.map +0 -1
- package/dist/utils/projection.d.ts +0 -65
- package/dist/utils/projection.d.ts.map +0 -1
- package/dist/utils/projection.js +0 -180
- package/dist/utils/projection.js.map +0 -1
- package/dist/utils/projection.test.d.ts +0 -2
- package/dist/utils/projection.test.d.ts.map +0 -1
- package/dist/utils/projection.test.js +0 -336
- package/dist/utils/projection.test.js.map +0 -1
- package/dist/utils/terminal-ui.d.ts +0 -208
- package/dist/utils/terminal-ui.d.ts.map +0 -1
- package/dist/utils/terminal-ui.js +0 -611
- package/dist/utils/terminal-ui.js.map +0 -1
- package/dist/utils/terminal-ui.test.d.ts +0 -2
- package/dist/utils/terminal-ui.test.d.ts.map +0 -1
- package/dist/utils/terminal-ui.test.js +0 -683
- package/dist/utils/terminal-ui.test.js.map +0 -1
- package/dist/utils/workspace.d.ts +0 -100
- package/dist/utils/workspace.d.ts.map +0 -1
- package/dist/utils/workspace.js +0 -173
- package/dist/utils/workspace.js.map +0 -1
- package/dist/utils/workspace.test.d.ts +0 -2
- package/dist/utils/workspace.test.d.ts.map +0 -1
- package/dist/utils/workspace.test.js +0 -97
- package/dist/utils/workspace.test.js.map +0 -1
- package/src/algorithms/critical-path.test.ts +0 -241
- package/src/algorithms/dependency-integrity.test.ts +0 -348
- package/src/algorithms/tech-analysis.test.ts +0 -413
- package/src/algorithms/topological-sort.test.ts +0 -190
- package/src/schemas/response-schema.test.ts +0 -314
- package/src/utils/dashboard-renderer.test.ts +0 -983
- package/src/utils/date.test.ts +0 -329
- package/src/utils/hierarchy.test.ts +0 -505
- package/src/utils/id.test.ts +0 -235
- package/src/utils/natural-language.test.ts +0 -242
- package/src/utils/priority-queue.test.ts +0 -103
- package/src/utils/projection.test.ts +0 -425
- package/src/utils/terminal-ui.test.ts +0 -831
- package/src/utils/workspace.test.ts +0 -125
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
criticalPathAnalysis,
|
|
4
|
-
findParallelTasks,
|
|
5
|
-
suggestNextTask,
|
|
6
|
-
} from "./critical-path.js";
|
|
7
|
-
import type { Task } from "../schemas/task.js";
|
|
8
|
-
|
|
9
|
-
// Helper to create mock tasks
|
|
10
|
-
function createTask(
|
|
11
|
-
id: string,
|
|
12
|
-
options: {
|
|
13
|
-
priority?: string;
|
|
14
|
-
deps?: string[];
|
|
15
|
-
estimate?: number;
|
|
16
|
-
status?: string;
|
|
17
|
-
contexts?: string[];
|
|
18
|
-
} = {}
|
|
19
|
-
): Task {
|
|
20
|
-
const task: Task = {
|
|
21
|
-
id,
|
|
22
|
-
title: `Task ${id}`,
|
|
23
|
-
status: (options.status ?? "pending") as Task["status"],
|
|
24
|
-
priority: (options.priority ?? "medium") as Task["priority"],
|
|
25
|
-
workspace: "test-workspace",
|
|
26
|
-
createdAt: new Date().toISOString(),
|
|
27
|
-
updatedAt: new Date().toISOString(),
|
|
28
|
-
dependencies: (options.deps ?? []).map((depId) => ({
|
|
29
|
-
taskId: depId,
|
|
30
|
-
type: "blocked_by" as const,
|
|
31
|
-
})),
|
|
32
|
-
};
|
|
33
|
-
if (options.estimate) {
|
|
34
|
-
task.estimate = { expected: options.estimate, confidence: "medium" as const };
|
|
35
|
-
}
|
|
36
|
-
if (options.contexts) {
|
|
37
|
-
task.contexts = options.contexts;
|
|
38
|
-
}
|
|
39
|
-
return task;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
describe("criticalPathAnalysis", () => {
|
|
43
|
-
test("returns empty result for empty input", () => {
|
|
44
|
-
const result = criticalPathAnalysis([]);
|
|
45
|
-
expect(result.tasks).toEqual([]);
|
|
46
|
-
expect(result.criticalPath).toEqual([]);
|
|
47
|
-
expect(result.projectDuration).toBe(0);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("returns empty for all completed tasks", () => {
|
|
51
|
-
const tasks = [createTask("A", { status: "completed" })];
|
|
52
|
-
const result = criticalPathAnalysis(tasks);
|
|
53
|
-
expect(result.tasks).toEqual([]);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("calculates single task correctly", () => {
|
|
57
|
-
const tasks = [createTask("A", { estimate: 60 })];
|
|
58
|
-
const result = criticalPathAnalysis(tasks);
|
|
59
|
-
|
|
60
|
-
expect(result.projectDuration).toBe(60);
|
|
61
|
-
expect(result.criticalPath.length).toBe(1);
|
|
62
|
-
expect(result.criticalPath[0]!.id).toBe("A");
|
|
63
|
-
expect(result.criticalPath[0]!.isCritical).toBe(true);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test("identifies critical path in linear chain", () => {
|
|
67
|
-
// A -> B -> C (each 30 min)
|
|
68
|
-
const tasks = [
|
|
69
|
-
createTask("A", { estimate: 30 }),
|
|
70
|
-
createTask("B", { estimate: 30, deps: ["A"] }),
|
|
71
|
-
createTask("C", { estimate: 30, deps: ["B"] }),
|
|
72
|
-
];
|
|
73
|
-
const result = criticalPathAnalysis(tasks);
|
|
74
|
-
|
|
75
|
-
expect(result.projectDuration).toBe(90);
|
|
76
|
-
expect(result.criticalPath.length).toBe(3);
|
|
77
|
-
expect(result.criticalPath.map((t) => t.id)).toEqual(["A", "B", "C"]);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("calculates slack for parallel tasks", () => {
|
|
81
|
-
// A (60) and B (30) both lead to C
|
|
82
|
-
// A is on critical path, B has 30 min slack
|
|
83
|
-
const tasks = [
|
|
84
|
-
createTask("A", { estimate: 60 }),
|
|
85
|
-
createTask("B", { estimate: 30 }),
|
|
86
|
-
createTask("C", { estimate: 30, deps: ["A", "B"] }),
|
|
87
|
-
];
|
|
88
|
-
const result = criticalPathAnalysis(tasks);
|
|
89
|
-
|
|
90
|
-
expect(result.projectDuration).toBe(90); // A + C
|
|
91
|
-
|
|
92
|
-
const taskA = result.tasks.find((t) => t.id === "A")!;
|
|
93
|
-
const taskB = result.tasks.find((t) => t.id === "B")!;
|
|
94
|
-
const taskC = result.tasks.find((t) => t.id === "C")!;
|
|
95
|
-
|
|
96
|
-
expect(taskA.isCritical).toBe(true);
|
|
97
|
-
expect(taskB.slack).toBe(30); // B can start 30 min late
|
|
98
|
-
expect(taskC.isCritical).toBe(true);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test("identifies bottlenecks by dependent count", () => {
|
|
102
|
-
// A blocks B, C, D
|
|
103
|
-
const tasks = [
|
|
104
|
-
createTask("A", { estimate: 30 }),
|
|
105
|
-
createTask("B", { estimate: 30, deps: ["A"] }),
|
|
106
|
-
createTask("C", { estimate: 30, deps: ["A"] }),
|
|
107
|
-
createTask("D", { estimate: 30, deps: ["A"] }),
|
|
108
|
-
];
|
|
109
|
-
const result = criticalPathAnalysis(tasks);
|
|
110
|
-
|
|
111
|
-
expect(result.bottlenecks.length).toBeGreaterThan(0);
|
|
112
|
-
expect(result.bottlenecks[0]!.id).toBe("A"); // A blocks the most tasks
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
describe("findParallelTasks", () => {
|
|
117
|
-
test("returns empty for empty input", () => {
|
|
118
|
-
const result = findParallelTasks([]);
|
|
119
|
-
expect(result).toEqual([]);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("returns single group for independent tasks", () => {
|
|
123
|
-
const tasks = [
|
|
124
|
-
createTask("A"),
|
|
125
|
-
createTask("B"),
|
|
126
|
-
createTask("C"),
|
|
127
|
-
];
|
|
128
|
-
const result = findParallelTasks(tasks);
|
|
129
|
-
|
|
130
|
-
expect(result.length).toBe(1);
|
|
131
|
-
expect(result[0]!.length).toBe(3);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test("excludes tasks with uncompleted dependencies", () => {
|
|
135
|
-
const tasks = [
|
|
136
|
-
createTask("A"),
|
|
137
|
-
createTask("B", { deps: ["A"] }),
|
|
138
|
-
];
|
|
139
|
-
const result = findParallelTasks(tasks);
|
|
140
|
-
|
|
141
|
-
// Only A is available (B is blocked)
|
|
142
|
-
expect(result.length).toBe(1);
|
|
143
|
-
expect(result[0]!.length).toBe(1);
|
|
144
|
-
expect(result[0]![0]!.id).toBe("A");
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test("includes task when dependency is completed", () => {
|
|
148
|
-
const tasks = [
|
|
149
|
-
createTask("A", { status: "completed" }),
|
|
150
|
-
createTask("B", { deps: ["A"] }),
|
|
151
|
-
createTask("C"),
|
|
152
|
-
];
|
|
153
|
-
const result = findParallelTasks(tasks);
|
|
154
|
-
|
|
155
|
-
// B and C can run in parallel
|
|
156
|
-
expect(result.length).toBe(1);
|
|
157
|
-
expect(result[0]!.map((t) => t.id).sort()).toEqual(["B", "C"]);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
test("excludes completed tasks from result", () => {
|
|
161
|
-
const tasks = [
|
|
162
|
-
createTask("A", { status: "completed" }),
|
|
163
|
-
createTask("B"),
|
|
164
|
-
];
|
|
165
|
-
const result = findParallelTasks(tasks);
|
|
166
|
-
|
|
167
|
-
expect(result.length).toBe(1);
|
|
168
|
-
expect(result[0]!.length).toBe(1);
|
|
169
|
-
expect(result[0]![0]!.id).toBe("B");
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
describe("suggestNextTask", () => {
|
|
174
|
-
test("returns null for empty input", () => {
|
|
175
|
-
const result = suggestNextTask([]);
|
|
176
|
-
expect(result).toBeNull();
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test("returns null when all tasks completed", () => {
|
|
180
|
-
const tasks = [createTask("A", { status: "completed" })];
|
|
181
|
-
const result = suggestNextTask(tasks);
|
|
182
|
-
expect(result).toBeNull();
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test("prefers critical path tasks", () => {
|
|
186
|
-
// A is critical (longer), B has slack
|
|
187
|
-
const tasks = [
|
|
188
|
-
createTask("A", { estimate: 60, priority: "low" }),
|
|
189
|
-
createTask("B", { estimate: 30, priority: "high" }),
|
|
190
|
-
createTask("C", { estimate: 30, deps: ["A", "B"] }),
|
|
191
|
-
];
|
|
192
|
-
const result = suggestNextTask(tasks);
|
|
193
|
-
|
|
194
|
-
// A is on critical path, should be suggested first
|
|
195
|
-
expect(result!.id).toBe("A");
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test("filters by context when specified", () => {
|
|
199
|
-
const tasks = [
|
|
200
|
-
createTask("A", { priority: "critical", contexts: ["office"] }),
|
|
201
|
-
createTask("B", { priority: "high", contexts: ["focus"] }),
|
|
202
|
-
];
|
|
203
|
-
const result = suggestNextTask(tasks, { contexts: ["focus"] });
|
|
204
|
-
|
|
205
|
-
expect(result!.id).toBe("B");
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test("filters by max time when specified", () => {
|
|
209
|
-
const tasks = [
|
|
210
|
-
createTask("A", { estimate: 120, priority: "critical" }),
|
|
211
|
-
createTask("B", { estimate: 30, priority: "high" }),
|
|
212
|
-
];
|
|
213
|
-
const result = suggestNextTask(tasks, { maxMinutes: 60 });
|
|
214
|
-
|
|
215
|
-
expect(result!.id).toBe("B");
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
test("skips blocked tasks", () => {
|
|
219
|
-
const tasks = [
|
|
220
|
-
createTask("A", { priority: "low" }),
|
|
221
|
-
createTask("B", { priority: "critical", deps: ["A"] }),
|
|
222
|
-
];
|
|
223
|
-
const result = suggestNextTask(tasks);
|
|
224
|
-
|
|
225
|
-
// B is blocked, so A should be suggested
|
|
226
|
-
expect(result!.id).toBe("A");
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
test("considers tasks with more dependents", () => {
|
|
230
|
-
const tasks = [
|
|
231
|
-
createTask("A", { priority: "medium" }),
|
|
232
|
-
createTask("B", { priority: "medium" }),
|
|
233
|
-
createTask("C", { priority: "medium", deps: ["A"] }),
|
|
234
|
-
createTask("D", { priority: "medium", deps: ["A"] }),
|
|
235
|
-
];
|
|
236
|
-
const result = suggestNextTask(tasks);
|
|
237
|
-
|
|
238
|
-
// A blocks more tasks (C and D), should be preferred over B
|
|
239
|
-
expect(result!.id).toBe("A");
|
|
240
|
-
});
|
|
241
|
-
});
|
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
DependencyErrorCode,
|
|
4
|
-
validateDependency,
|
|
5
|
-
findInvalidDependencies,
|
|
6
|
-
findSelfDependencies,
|
|
7
|
-
detectCircularDependencies,
|
|
8
|
-
checkDependencyIntegrity,
|
|
9
|
-
} from "./dependency-integrity.js";
|
|
10
|
-
import type { Task } from "../schemas/task.js";
|
|
11
|
-
|
|
12
|
-
// Helper to create mock tasks
|
|
13
|
-
function createTask(
|
|
14
|
-
id: string,
|
|
15
|
-
title: string = `Task ${id}`,
|
|
16
|
-
deps: Array<{ taskId: string; type: "blocked_by" | "blocks" | "related" }> = []
|
|
17
|
-
): Task {
|
|
18
|
-
return {
|
|
19
|
-
id,
|
|
20
|
-
title,
|
|
21
|
-
status: "pending",
|
|
22
|
-
priority: "medium",
|
|
23
|
-
workspace: "test-workspace",
|
|
24
|
-
createdAt: new Date().toISOString(),
|
|
25
|
-
updatedAt: new Date().toISOString(),
|
|
26
|
-
dependencies: deps,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe("validateDependency", () => {
|
|
31
|
-
test("returns valid for a valid dependency", () => {
|
|
32
|
-
const tasks = [createTask("A"), createTask("B")];
|
|
33
|
-
const result = validateDependency({
|
|
34
|
-
taskId: "B",
|
|
35
|
-
blockedBy: "A",
|
|
36
|
-
tasks,
|
|
37
|
-
});
|
|
38
|
-
expect(result.valid).toBe(true);
|
|
39
|
-
expect(result.errorCode).toBeUndefined();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("rejects self-dependency", () => {
|
|
43
|
-
const tasks = [createTask("A")];
|
|
44
|
-
const result = validateDependency({
|
|
45
|
-
taskId: "A",
|
|
46
|
-
blockedBy: "A",
|
|
47
|
-
tasks,
|
|
48
|
-
});
|
|
49
|
-
expect(result.valid).toBe(false);
|
|
50
|
-
expect(result.errorCode).toBe(DependencyErrorCode.SELF_DEPENDENCY);
|
|
51
|
-
expect(result.errorMessage).toContain("cannot depend on itself");
|
|
52
|
-
expect(result.suggestion).toBeDefined();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("rejects when task not found", () => {
|
|
56
|
-
const tasks = [createTask("A")];
|
|
57
|
-
const result = validateDependency({
|
|
58
|
-
taskId: "B",
|
|
59
|
-
blockedBy: "A",
|
|
60
|
-
tasks,
|
|
61
|
-
});
|
|
62
|
-
expect(result.valid).toBe(false);
|
|
63
|
-
expect(result.errorCode).toBe(DependencyErrorCode.TASK_NOT_FOUND);
|
|
64
|
-
expect(result.errorMessage).toContain("Task not found");
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test("rejects when blocker not found", () => {
|
|
68
|
-
const tasks = [createTask("A")];
|
|
69
|
-
const result = validateDependency({
|
|
70
|
-
taskId: "A",
|
|
71
|
-
blockedBy: "B",
|
|
72
|
-
tasks,
|
|
73
|
-
});
|
|
74
|
-
expect(result.valid).toBe(false);
|
|
75
|
-
expect(result.errorCode).toBe(DependencyErrorCode.BLOCKER_NOT_FOUND);
|
|
76
|
-
expect(result.errorMessage).toContain("Blocking task not found");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("rejects duplicate dependency", () => {
|
|
80
|
-
const tasks = [
|
|
81
|
-
createTask("A"),
|
|
82
|
-
createTask("B", "Task B", [{ taskId: "A", type: "blocked_by" }]),
|
|
83
|
-
];
|
|
84
|
-
const result = validateDependency({
|
|
85
|
-
taskId: "B",
|
|
86
|
-
blockedBy: "A",
|
|
87
|
-
tasks,
|
|
88
|
-
});
|
|
89
|
-
expect(result.valid).toBe(false);
|
|
90
|
-
expect(result.errorCode).toBe(DependencyErrorCode.DUPLICATE_DEPENDENCY);
|
|
91
|
-
expect(result.errorMessage).toContain("already blocked by");
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("allows duplicate when checkDuplicates is false", () => {
|
|
95
|
-
const tasks = [
|
|
96
|
-
createTask("A"),
|
|
97
|
-
createTask("B", "Task B", [{ taskId: "A", type: "blocked_by" }]),
|
|
98
|
-
];
|
|
99
|
-
const result = validateDependency({
|
|
100
|
-
taskId: "B",
|
|
101
|
-
blockedBy: "A",
|
|
102
|
-
tasks,
|
|
103
|
-
checkDuplicates: false,
|
|
104
|
-
});
|
|
105
|
-
expect(result.valid).toBe(true);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("rejects circular dependency", () => {
|
|
109
|
-
const tasks = [
|
|
110
|
-
createTask("A", "Task A", [{ taskId: "B", type: "blocked_by" }]),
|
|
111
|
-
createTask("B"),
|
|
112
|
-
];
|
|
113
|
-
// B is blocking A, so A blocking B would create a cycle
|
|
114
|
-
const result = validateDependency({
|
|
115
|
-
taskId: "B",
|
|
116
|
-
blockedBy: "A",
|
|
117
|
-
tasks,
|
|
118
|
-
});
|
|
119
|
-
expect(result.valid).toBe(false);
|
|
120
|
-
expect(result.errorCode).toBe(DependencyErrorCode.CIRCULAR_DEPENDENCY);
|
|
121
|
-
expect(result.errorMessage).toContain("cycle");
|
|
122
|
-
expect(result.suggestion).toContain("depends on");
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test("rejects indirect circular dependency", () => {
|
|
126
|
-
const tasks = [
|
|
127
|
-
createTask("A", "Task A", [{ taskId: "B", type: "blocked_by" }]),
|
|
128
|
-
createTask("B", "Task B", [{ taskId: "C", type: "blocked_by" }]),
|
|
129
|
-
createTask("C"),
|
|
130
|
-
];
|
|
131
|
-
// A <- B <- C, adding C <- A would create cycle
|
|
132
|
-
const result = validateDependency({
|
|
133
|
-
taskId: "C",
|
|
134
|
-
blockedBy: "A",
|
|
135
|
-
tasks,
|
|
136
|
-
});
|
|
137
|
-
expect(result.valid).toBe(false);
|
|
138
|
-
expect(result.errorCode).toBe(DependencyErrorCode.CIRCULAR_DEPENDENCY);
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
describe("findInvalidDependencies", () => {
|
|
143
|
-
test("returns empty array when all dependencies are valid", () => {
|
|
144
|
-
const tasks = [
|
|
145
|
-
createTask("A"),
|
|
146
|
-
createTask("B", "Task B", [{ taskId: "A", type: "blocked_by" }]),
|
|
147
|
-
];
|
|
148
|
-
const result = findInvalidDependencies(tasks);
|
|
149
|
-
expect(result).toEqual([]);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test("finds orphaned dependency references", () => {
|
|
153
|
-
const tasks = [
|
|
154
|
-
createTask("A", "Task A", [{ taskId: "non-existent", type: "blocked_by" }]),
|
|
155
|
-
];
|
|
156
|
-
const result = findInvalidDependencies(tasks);
|
|
157
|
-
expect(result.length).toBe(1);
|
|
158
|
-
expect(result[0]!.taskId).toBe("A");
|
|
159
|
-
expect(result[0]!.taskTitle).toBe("Task A");
|
|
160
|
-
expect(result[0]!.invalidDependencyId).toBe("non-existent");
|
|
161
|
-
expect(result[0]!.type).toBe("blocked_by");
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test("finds multiple invalid references across tasks", () => {
|
|
165
|
-
const tasks = [
|
|
166
|
-
createTask("A", "Task A", [{ taskId: "deleted-1", type: "blocked_by" }]),
|
|
167
|
-
createTask("B", "Task B", [
|
|
168
|
-
{ taskId: "A", type: "blocked_by" },
|
|
169
|
-
{ taskId: "deleted-2", type: "blocks" },
|
|
170
|
-
]),
|
|
171
|
-
];
|
|
172
|
-
const result = findInvalidDependencies(tasks);
|
|
173
|
-
expect(result.length).toBe(2);
|
|
174
|
-
expect(result.map((r) => r.invalidDependencyId).sort()).toEqual([
|
|
175
|
-
"deleted-1",
|
|
176
|
-
"deleted-2",
|
|
177
|
-
]);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test("returns empty array for tasks without dependencies", () => {
|
|
181
|
-
const tasks = [createTask("A"), createTask("B")];
|
|
182
|
-
const result = findInvalidDependencies(tasks);
|
|
183
|
-
expect(result).toEqual([]);
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
describe("findSelfDependencies", () => {
|
|
188
|
-
test("returns empty array when no self-dependencies exist", () => {
|
|
189
|
-
const tasks = [
|
|
190
|
-
createTask("A"),
|
|
191
|
-
createTask("B", "Task B", [{ taskId: "A", type: "blocked_by" }]),
|
|
192
|
-
];
|
|
193
|
-
const result = findSelfDependencies(tasks);
|
|
194
|
-
expect(result).toEqual([]);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test("finds task with self-dependency", () => {
|
|
198
|
-
const tasks = [
|
|
199
|
-
createTask("A", "Task A", [{ taskId: "A", type: "blocked_by" }]),
|
|
200
|
-
];
|
|
201
|
-
const result = findSelfDependencies(tasks);
|
|
202
|
-
expect(result).toEqual(["A"]);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
test("finds multiple self-dependencies", () => {
|
|
206
|
-
const tasks = [
|
|
207
|
-
createTask("A", "Task A", [{ taskId: "A", type: "blocked_by" }]),
|
|
208
|
-
createTask("B"),
|
|
209
|
-
createTask("C", "Task C", [{ taskId: "C", type: "blocks" }]),
|
|
210
|
-
];
|
|
211
|
-
const result = findSelfDependencies(tasks);
|
|
212
|
-
expect(result.sort()).toEqual(["A", "C"]);
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
describe("detectCircularDependencies", () => {
|
|
217
|
-
test("returns empty array when no cycles exist", () => {
|
|
218
|
-
const tasks = [
|
|
219
|
-
createTask("A"),
|
|
220
|
-
createTask("B", "Task B", [{ taskId: "A", type: "blocked_by" }]),
|
|
221
|
-
createTask("C", "Task C", [{ taskId: "B", type: "blocked_by" }]),
|
|
222
|
-
];
|
|
223
|
-
const result = detectCircularDependencies(tasks);
|
|
224
|
-
expect(result).toEqual([]);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
test("detects direct circular dependency (A -> B -> A)", () => {
|
|
228
|
-
const tasks = [
|
|
229
|
-
createTask("A", "Task A", [{ taskId: "B", type: "blocked_by" }]),
|
|
230
|
-
createTask("B", "Task B", [{ taskId: "A", type: "blocked_by" }]),
|
|
231
|
-
];
|
|
232
|
-
const result = detectCircularDependencies(tasks);
|
|
233
|
-
expect(result.length).toBe(1);
|
|
234
|
-
expect(result[0]!.sort()).toEqual(["A", "B"]);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
test("detects longer cycle (A -> B -> C -> A)", () => {
|
|
238
|
-
const tasks = [
|
|
239
|
-
createTask("A", "Task A", [{ taskId: "B", type: "blocked_by" }]),
|
|
240
|
-
createTask("B", "Task B", [{ taskId: "C", type: "blocked_by" }]),
|
|
241
|
-
createTask("C", "Task C", [{ taskId: "A", type: "blocked_by" }]),
|
|
242
|
-
];
|
|
243
|
-
const result = detectCircularDependencies(tasks);
|
|
244
|
-
expect(result.length).toBe(1);
|
|
245
|
-
expect(result[0]!.sort()).toEqual(["A", "B", "C"]);
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
test("ignores non-blocking dependency types for cycle detection", () => {
|
|
249
|
-
const tasks = [
|
|
250
|
-
createTask("A", "Task A", [{ taskId: "B", type: "related" }]),
|
|
251
|
-
createTask("B", "Task B", [{ taskId: "A", type: "related" }]),
|
|
252
|
-
];
|
|
253
|
-
const result = detectCircularDependencies(tasks);
|
|
254
|
-
expect(result).toEqual([]);
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
test("handles tasks with missing dependency targets", () => {
|
|
258
|
-
const tasks = [
|
|
259
|
-
createTask("A", "Task A", [{ taskId: "non-existent", type: "blocked_by" }]),
|
|
260
|
-
];
|
|
261
|
-
const result = detectCircularDependencies(tasks);
|
|
262
|
-
expect(result).toEqual([]);
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
describe("checkDependencyIntegrity", () => {
|
|
267
|
-
test("returns valid for healthy task set", () => {
|
|
268
|
-
const tasks = [
|
|
269
|
-
createTask("A"),
|
|
270
|
-
createTask("B", "Task B", [{ taskId: "A", type: "blocked_by" }]),
|
|
271
|
-
createTask("C", "Task C", [{ taskId: "B", type: "blocked_by" }]),
|
|
272
|
-
];
|
|
273
|
-
const result = checkDependencyIntegrity(tasks);
|
|
274
|
-
expect(result.valid).toBe(true);
|
|
275
|
-
expect(result.totalTasks).toBe(3);
|
|
276
|
-
expect(result.totalDependencies).toBe(2);
|
|
277
|
-
expect(result.issues.selfDependencies).toEqual([]);
|
|
278
|
-
expect(result.issues.invalidReferences).toEqual([]);
|
|
279
|
-
expect(result.issues.circularDependencies).toEqual([]);
|
|
280
|
-
expect(result.summary).toContain("All 2 dependencies");
|
|
281
|
-
expect(result.summary).toContain("valid");
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
test("detects self-dependency issues", () => {
|
|
285
|
-
const tasks = [
|
|
286
|
-
createTask("A", "Task A", [{ taskId: "A", type: "blocked_by" }]),
|
|
287
|
-
];
|
|
288
|
-
const result = checkDependencyIntegrity(tasks);
|
|
289
|
-
expect(result.valid).toBe(false);
|
|
290
|
-
expect(result.issues.selfDependencies).toEqual(["A"]);
|
|
291
|
-
expect(result.summary).toContain("self-dependencies");
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
test("detects invalid reference issues", () => {
|
|
295
|
-
const tasks = [
|
|
296
|
-
createTask("A", "Task A", [{ taskId: "deleted", type: "blocked_by" }]),
|
|
297
|
-
];
|
|
298
|
-
const result = checkDependencyIntegrity(tasks);
|
|
299
|
-
expect(result.valid).toBe(false);
|
|
300
|
-
expect(result.issues.invalidReferences.length).toBe(1);
|
|
301
|
-
expect(result.summary).toContain("invalid references");
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
test("detects circular dependency issues", () => {
|
|
305
|
-
const tasks = [
|
|
306
|
-
createTask("A", "Task A", [{ taskId: "B", type: "blocked_by" }]),
|
|
307
|
-
createTask("B", "Task B", [{ taskId: "A", type: "blocked_by" }]),
|
|
308
|
-
];
|
|
309
|
-
const result = checkDependencyIntegrity(tasks);
|
|
310
|
-
expect(result.valid).toBe(false);
|
|
311
|
-
expect(result.issues.circularDependencies.length).toBe(1);
|
|
312
|
-
expect(result.summary).toContain("circular dependency");
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
test("reports multiple issue types", () => {
|
|
316
|
-
const tasks = [
|
|
317
|
-
createTask("A", "Task A", [
|
|
318
|
-
{ taskId: "A", type: "blocked_by" }, // self-dependency
|
|
319
|
-
{ taskId: "deleted", type: "blocked_by" }, // invalid reference
|
|
320
|
-
]),
|
|
321
|
-
createTask("B", "Task B", [{ taskId: "C", type: "blocked_by" }]),
|
|
322
|
-
createTask("C", "Task C", [{ taskId: "B", type: "blocked_by" }]), // circular
|
|
323
|
-
];
|
|
324
|
-
const result = checkDependencyIntegrity(tasks);
|
|
325
|
-
expect(result.valid).toBe(false);
|
|
326
|
-
expect(result.issues.selfDependencies.length).toBeGreaterThan(0);
|
|
327
|
-
expect(result.issues.invalidReferences.length).toBeGreaterThan(0);
|
|
328
|
-
expect(result.issues.circularDependencies.length).toBeGreaterThan(0);
|
|
329
|
-
expect(result.summary).toContain("self-dependencies");
|
|
330
|
-
expect(result.summary).toContain("invalid references");
|
|
331
|
-
expect(result.summary).toContain("circular dependency");
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
test("handles empty task array", () => {
|
|
335
|
-
const result = checkDependencyIntegrity([]);
|
|
336
|
-
expect(result.valid).toBe(true);
|
|
337
|
-
expect(result.totalTasks).toBe(0);
|
|
338
|
-
expect(result.totalDependencies).toBe(0);
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
test("handles tasks with no dependencies", () => {
|
|
342
|
-
const tasks = [createTask("A"), createTask("B"), createTask("C")];
|
|
343
|
-
const result = checkDependencyIntegrity(tasks);
|
|
344
|
-
expect(result.valid).toBe(true);
|
|
345
|
-
expect(result.totalTasks).toBe(3);
|
|
346
|
-
expect(result.totalDependencies).toBe(0);
|
|
347
|
-
});
|
|
348
|
-
});
|