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