@task-mcp/shared 1.0.20 → 1.0.22

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 (175) hide show
  1. package/README.md +122 -0
  2. package/package.json +1 -6
  3. package/src/algorithms/critical-path.ts +31 -6
  4. package/src/algorithms/dependency-integrity.ts +5 -2
  5. package/src/algorithms/topological-sort.ts +66 -17
  6. package/src/utils/index.ts +5 -5
  7. package/src/utils/natural-language.ts +210 -83
  8. package/src/utils/projection.ts +8 -8
  9. package/src/utils/workspace.ts +16 -4
  10. package/dist/algorithms/critical-path.d.ts +0 -46
  11. package/dist/algorithms/critical-path.d.ts.map +0 -1
  12. package/dist/algorithms/critical-path.js +0 -320
  13. package/dist/algorithms/critical-path.js.map +0 -1
  14. package/dist/algorithms/critical-path.test.d.ts +0 -2
  15. package/dist/algorithms/critical-path.test.d.ts.map +0 -1
  16. package/dist/algorithms/critical-path.test.js +0 -194
  17. package/dist/algorithms/critical-path.test.js.map +0 -1
  18. package/dist/algorithms/dependency-integrity.d.ts +0 -81
  19. package/dist/algorithms/dependency-integrity.d.ts.map +0 -1
  20. package/dist/algorithms/dependency-integrity.js +0 -207
  21. package/dist/algorithms/dependency-integrity.js.map +0 -1
  22. package/dist/algorithms/dependency-integrity.test.d.ts +0 -2
  23. package/dist/algorithms/dependency-integrity.test.d.ts.map +0 -1
  24. package/dist/algorithms/dependency-integrity.test.js +0 -309
  25. package/dist/algorithms/dependency-integrity.test.js.map +0 -1
  26. package/dist/algorithms/index.d.ts +0 -5
  27. package/dist/algorithms/index.d.ts.map +0 -1
  28. package/dist/algorithms/index.js +0 -5
  29. package/dist/algorithms/index.js.map +0 -1
  30. package/dist/algorithms/tech-analysis.d.ts +0 -106
  31. package/dist/algorithms/tech-analysis.d.ts.map +0 -1
  32. package/dist/algorithms/tech-analysis.js +0 -344
  33. package/dist/algorithms/tech-analysis.js.map +0 -1
  34. package/dist/algorithms/tech-analysis.test.d.ts +0 -2
  35. package/dist/algorithms/tech-analysis.test.d.ts.map +0 -1
  36. package/dist/algorithms/tech-analysis.test.js +0 -338
  37. package/dist/algorithms/tech-analysis.test.js.map +0 -1
  38. package/dist/algorithms/topological-sort.d.ts +0 -41
  39. package/dist/algorithms/topological-sort.d.ts.map +0 -1
  40. package/dist/algorithms/topological-sort.js +0 -165
  41. package/dist/algorithms/topological-sort.js.map +0 -1
  42. package/dist/algorithms/topological-sort.test.d.ts +0 -2
  43. package/dist/algorithms/topological-sort.test.d.ts.map +0 -1
  44. package/dist/algorithms/topological-sort.test.js +0 -162
  45. package/dist/algorithms/topological-sort.test.js.map +0 -1
  46. package/dist/index.d.ts +0 -4
  47. package/dist/index.d.ts.map +0 -1
  48. package/dist/index.js +0 -7
  49. package/dist/index.js.map +0 -1
  50. package/dist/schemas/inbox.d.ts +0 -55
  51. package/dist/schemas/inbox.d.ts.map +0 -1
  52. package/dist/schemas/inbox.js +0 -25
  53. package/dist/schemas/inbox.js.map +0 -1
  54. package/dist/schemas/index.d.ts +0 -7
  55. package/dist/schemas/index.d.ts.map +0 -1
  56. package/dist/schemas/index.js +0 -17
  57. package/dist/schemas/index.js.map +0 -1
  58. package/dist/schemas/project.d.ts +0 -177
  59. package/dist/schemas/project.d.ts.map +0 -1
  60. package/dist/schemas/project.js +0 -56
  61. package/dist/schemas/project.js.map +0 -1
  62. package/dist/schemas/response-format.d.ts +0 -148
  63. package/dist/schemas/response-format.d.ts.map +0 -1
  64. package/dist/schemas/response-format.js +0 -18
  65. package/dist/schemas/response-format.js.map +0 -1
  66. package/dist/schemas/response-schema.d.ts +0 -307
  67. package/dist/schemas/response-schema.d.ts.map +0 -1
  68. package/dist/schemas/response-schema.js +0 -75
  69. package/dist/schemas/response-schema.js.map +0 -1
  70. package/dist/schemas/response-schema.test.d.ts +0 -2
  71. package/dist/schemas/response-schema.test.d.ts.map +0 -1
  72. package/dist/schemas/response-schema.test.js +0 -256
  73. package/dist/schemas/response-schema.test.js.map +0 -1
  74. package/dist/schemas/state.d.ts +0 -17
  75. package/dist/schemas/state.d.ts.map +0 -1
  76. package/dist/schemas/state.js +0 -17
  77. package/dist/schemas/state.js.map +0 -1
  78. package/dist/schemas/task.d.ts +0 -881
  79. package/dist/schemas/task.d.ts.map +0 -1
  80. package/dist/schemas/task.js +0 -189
  81. package/dist/schemas/task.js.map +0 -1
  82. package/dist/schemas/view.d.ts +0 -143
  83. package/dist/schemas/view.d.ts.map +0 -1
  84. package/dist/schemas/view.js +0 -48
  85. package/dist/schemas/view.js.map +0 -1
  86. package/dist/utils/dashboard-renderer.d.ts +0 -93
  87. package/dist/utils/dashboard-renderer.d.ts.map +0 -1
  88. package/dist/utils/dashboard-renderer.js +0 -424
  89. package/dist/utils/dashboard-renderer.js.map +0 -1
  90. package/dist/utils/dashboard-renderer.test.d.ts +0 -2
  91. package/dist/utils/dashboard-renderer.test.d.ts.map +0 -1
  92. package/dist/utils/dashboard-renderer.test.js +0 -774
  93. package/dist/utils/dashboard-renderer.test.js.map +0 -1
  94. package/dist/utils/date.d.ts +0 -94
  95. package/dist/utils/date.d.ts.map +0 -1
  96. package/dist/utils/date.js +0 -323
  97. package/dist/utils/date.js.map +0 -1
  98. package/dist/utils/date.test.d.ts +0 -2
  99. package/dist/utils/date.test.d.ts.map +0 -1
  100. package/dist/utils/date.test.js +0 -276
  101. package/dist/utils/date.test.js.map +0 -1
  102. package/dist/utils/hierarchy.d.ts +0 -102
  103. package/dist/utils/hierarchy.d.ts.map +0 -1
  104. package/dist/utils/hierarchy.js +0 -236
  105. package/dist/utils/hierarchy.js.map +0 -1
  106. package/dist/utils/hierarchy.test.d.ts +0 -2
  107. package/dist/utils/hierarchy.test.d.ts.map +0 -1
  108. package/dist/utils/hierarchy.test.js +0 -436
  109. package/dist/utils/hierarchy.test.js.map +0 -1
  110. package/dist/utils/id.d.ts +0 -60
  111. package/dist/utils/id.d.ts.map +0 -1
  112. package/dist/utils/id.js +0 -118
  113. package/dist/utils/id.js.map +0 -1
  114. package/dist/utils/id.test.d.ts +0 -2
  115. package/dist/utils/id.test.d.ts.map +0 -1
  116. package/dist/utils/id.test.js +0 -193
  117. package/dist/utils/id.test.js.map +0 -1
  118. package/dist/utils/index.d.ts +0 -12
  119. package/dist/utils/index.d.ts.map +0 -1
  120. package/dist/utils/index.js +0 -34
  121. package/dist/utils/index.js.map +0 -1
  122. package/dist/utils/natural-language.d.ts +0 -57
  123. package/dist/utils/natural-language.d.ts.map +0 -1
  124. package/dist/utils/natural-language.js +0 -211
  125. package/dist/utils/natural-language.js.map +0 -1
  126. package/dist/utils/natural-language.test.d.ts +0 -2
  127. package/dist/utils/natural-language.test.d.ts.map +0 -1
  128. package/dist/utils/natural-language.test.js +0 -197
  129. package/dist/utils/natural-language.test.js.map +0 -1
  130. package/dist/utils/priority-queue.d.ts +0 -17
  131. package/dist/utils/priority-queue.d.ts.map +0 -1
  132. package/dist/utils/priority-queue.js +0 -62
  133. package/dist/utils/priority-queue.js.map +0 -1
  134. package/dist/utils/priority-queue.test.d.ts +0 -2
  135. package/dist/utils/priority-queue.test.d.ts.map +0 -1
  136. package/dist/utils/priority-queue.test.js +0 -82
  137. package/dist/utils/priority-queue.test.js.map +0 -1
  138. package/dist/utils/projection.d.ts +0 -65
  139. package/dist/utils/projection.d.ts.map +0 -1
  140. package/dist/utils/projection.js +0 -180
  141. package/dist/utils/projection.js.map +0 -1
  142. package/dist/utils/projection.test.d.ts +0 -2
  143. package/dist/utils/projection.test.d.ts.map +0 -1
  144. package/dist/utils/projection.test.js +0 -336
  145. package/dist/utils/projection.test.js.map +0 -1
  146. package/dist/utils/terminal-ui.d.ts +0 -208
  147. package/dist/utils/terminal-ui.d.ts.map +0 -1
  148. package/dist/utils/terminal-ui.js +0 -611
  149. package/dist/utils/terminal-ui.js.map +0 -1
  150. package/dist/utils/terminal-ui.test.d.ts +0 -2
  151. package/dist/utils/terminal-ui.test.d.ts.map +0 -1
  152. package/dist/utils/terminal-ui.test.js +0 -683
  153. package/dist/utils/terminal-ui.test.js.map +0 -1
  154. package/dist/utils/workspace.d.ts +0 -100
  155. package/dist/utils/workspace.d.ts.map +0 -1
  156. package/dist/utils/workspace.js +0 -173
  157. package/dist/utils/workspace.js.map +0 -1
  158. package/dist/utils/workspace.test.d.ts +0 -2
  159. package/dist/utils/workspace.test.d.ts.map +0 -1
  160. package/dist/utils/workspace.test.js +0 -97
  161. package/dist/utils/workspace.test.js.map +0 -1
  162. package/src/algorithms/critical-path.test.ts +0 -241
  163. package/src/algorithms/dependency-integrity.test.ts +0 -348
  164. package/src/algorithms/tech-analysis.test.ts +0 -413
  165. package/src/algorithms/topological-sort.test.ts +0 -190
  166. package/src/schemas/response-schema.test.ts +0 -314
  167. package/src/utils/dashboard-renderer.test.ts +0 -983
  168. package/src/utils/date.test.ts +0 -329
  169. package/src/utils/hierarchy.test.ts +0 -505
  170. package/src/utils/id.test.ts +0 -235
  171. package/src/utils/natural-language.test.ts +0 -242
  172. package/src/utils/priority-queue.test.ts +0 -103
  173. package/src/utils/projection.test.ts +0 -425
  174. package/src/utils/terminal-ui.test.ts +0 -831
  175. 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
- });