@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,413 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
suggestSafeOrder,
|
|
4
|
-
findBreakingChanges,
|
|
5
|
-
findHighRiskTasks,
|
|
6
|
-
groupByTechArea,
|
|
7
|
-
getComplexitySummary,
|
|
8
|
-
getTechStackSummary,
|
|
9
|
-
suggestSubtaskCount,
|
|
10
|
-
} from "./tech-analysis.js";
|
|
11
|
-
import type { Task, TechArea, RiskLevel } from "../schemas/task.js";
|
|
12
|
-
|
|
13
|
-
// Helper to create mock tasks with tech stack analysis
|
|
14
|
-
function createTask(
|
|
15
|
-
id: string,
|
|
16
|
-
options: {
|
|
17
|
-
priority?: "critical" | "high" | "medium" | "low";
|
|
18
|
-
status?: "pending" | "in_progress" | "completed" | "blocked" | "cancelled";
|
|
19
|
-
areas?: TechArea[];
|
|
20
|
-
riskLevel?: RiskLevel;
|
|
21
|
-
hasBreakingChange?: boolean;
|
|
22
|
-
complexityScore?: number;
|
|
23
|
-
} = {}
|
|
24
|
-
): Task {
|
|
25
|
-
const task: Task = {
|
|
26
|
-
id,
|
|
27
|
-
title: `Task ${id}`,
|
|
28
|
-
status: options.status ?? "pending",
|
|
29
|
-
priority: options.priority ?? "medium",
|
|
30
|
-
workspace: "test-workspace",
|
|
31
|
-
createdAt: new Date().toISOString(),
|
|
32
|
-
updatedAt: new Date().toISOString(),
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
if (options.areas || options.riskLevel !== undefined || options.hasBreakingChange !== undefined) {
|
|
36
|
-
task.techStack = {};
|
|
37
|
-
if (options.areas) {
|
|
38
|
-
task.techStack.areas = options.areas;
|
|
39
|
-
}
|
|
40
|
-
if (options.riskLevel !== undefined) {
|
|
41
|
-
task.techStack.riskLevel = options.riskLevel;
|
|
42
|
-
}
|
|
43
|
-
if (options.hasBreakingChange !== undefined) {
|
|
44
|
-
task.techStack.hasBreakingChange = options.hasBreakingChange;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (options.complexityScore !== undefined) {
|
|
49
|
-
task.complexity = {
|
|
50
|
-
score: options.complexityScore,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return task;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
describe("suggestSafeOrder", () => {
|
|
58
|
-
test("returns empty result for empty input", () => {
|
|
59
|
-
const result = suggestSafeOrder([]);
|
|
60
|
-
expect(result.orderedTasks).toEqual([]);
|
|
61
|
-
expect(result.phases).toEqual([]);
|
|
62
|
-
expect(result.summary.totalTasks).toBe(0);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test("returns empty for all completed tasks", () => {
|
|
66
|
-
const tasks = [createTask("A", { status: "completed" })];
|
|
67
|
-
const result = suggestSafeOrder(tasks);
|
|
68
|
-
expect(result.orderedTasks).toEqual([]);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test("orders by tech level (schema before backend)", () => {
|
|
72
|
-
const tasks = [
|
|
73
|
-
createTask("backend-task", { areas: ["backend"] }),
|
|
74
|
-
createTask("schema-task", { areas: ["schema"] }),
|
|
75
|
-
];
|
|
76
|
-
const result = suggestSafeOrder(tasks);
|
|
77
|
-
|
|
78
|
-
expect(result.orderedTasks[0]!.id).toBe("schema-task");
|
|
79
|
-
expect(result.orderedTasks[1]!.id).toBe("backend-task");
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test("orders by tech level (full chain)", () => {
|
|
83
|
-
const tasks = [
|
|
84
|
-
createTask("frontend", { areas: ["frontend"] }),
|
|
85
|
-
createTask("backend", { areas: ["backend"] }),
|
|
86
|
-
createTask("schema", { areas: ["schema"] }),
|
|
87
|
-
createTask("test", { areas: ["test"] }),
|
|
88
|
-
createTask("devops", { areas: ["devops"] }),
|
|
89
|
-
];
|
|
90
|
-
const result = suggestSafeOrder(tasks);
|
|
91
|
-
|
|
92
|
-
const order = result.orderedTasks.map((t) => t.id);
|
|
93
|
-
expect(order).toEqual(["schema", "devops", "backend", "frontend", "test"]);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test("orders by risk level within same tech level", () => {
|
|
97
|
-
const tasks = [
|
|
98
|
-
createTask("high-risk", { areas: ["backend"], riskLevel: "high" }),
|
|
99
|
-
createTask("low-risk", { areas: ["backend"], riskLevel: "low" }),
|
|
100
|
-
createTask("medium-risk", { areas: ["backend"], riskLevel: "medium" }),
|
|
101
|
-
];
|
|
102
|
-
const result = suggestSafeOrder(tasks);
|
|
103
|
-
|
|
104
|
-
const order = result.orderedTasks.map((t) => t.id);
|
|
105
|
-
expect(order).toEqual(["low-risk", "medium-risk", "high-risk"]);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("puts breaking changes last within same tech/risk level", () => {
|
|
109
|
-
const tasks = [
|
|
110
|
-
createTask("breaking", { areas: ["backend"], riskLevel: "low", hasBreakingChange: true }),
|
|
111
|
-
createTask("safe", { areas: ["backend"], riskLevel: "low", hasBreakingChange: false }),
|
|
112
|
-
];
|
|
113
|
-
const result = suggestSafeOrder(tasks);
|
|
114
|
-
|
|
115
|
-
expect(result.orderedTasks[0]!.id).toBe("safe");
|
|
116
|
-
expect(result.orderedTasks[1]!.id).toBe("breaking");
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test("uses priority as tiebreaker", () => {
|
|
120
|
-
const tasks = [
|
|
121
|
-
createTask("low-priority", { areas: ["backend"], priority: "low" }),
|
|
122
|
-
createTask("high-priority", { areas: ["backend"], priority: "high" }),
|
|
123
|
-
];
|
|
124
|
-
const result = suggestSafeOrder(tasks);
|
|
125
|
-
|
|
126
|
-
expect(result.orderedTasks[0]!.id).toBe("high-priority");
|
|
127
|
-
expect(result.orderedTasks[1]!.id).toBe("low-priority");
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test("groups tasks into phases by tech level", () => {
|
|
131
|
-
const tasks = [
|
|
132
|
-
createTask("schema1", { areas: ["schema"] }),
|
|
133
|
-
createTask("schema2", { areas: ["schema"] }),
|
|
134
|
-
createTask("backend1", { areas: ["backend"] }),
|
|
135
|
-
];
|
|
136
|
-
const result = suggestSafeOrder(tasks);
|
|
137
|
-
|
|
138
|
-
expect(result.phases.length).toBe(2);
|
|
139
|
-
expect(result.phases[0]!.primaryArea).toBe("schema");
|
|
140
|
-
expect(result.phases[0]!.tasks.length).toBe(2);
|
|
141
|
-
expect(result.phases[1]!.primaryArea).toBe("backend");
|
|
142
|
-
expect(result.phases[1]!.tasks.length).toBe(1);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test("calculates summary statistics correctly", () => {
|
|
146
|
-
const tasks = [
|
|
147
|
-
createTask("t1", { areas: ["backend"], hasBreakingChange: true }),
|
|
148
|
-
createTask("t2", { areas: ["backend"], riskLevel: "high" }),
|
|
149
|
-
createTask("t3", { areas: ["backend"], riskLevel: "critical" }),
|
|
150
|
-
createTask("t4", { areas: ["backend"] }),
|
|
151
|
-
];
|
|
152
|
-
const result = suggestSafeOrder(tasks);
|
|
153
|
-
|
|
154
|
-
expect(result.summary.totalTasks).toBe(4);
|
|
155
|
-
expect(result.summary.breakingChanges).toBe(1);
|
|
156
|
-
expect(result.summary.highRiskCount).toBe(2);
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
describe("findBreakingChanges", () => {
|
|
161
|
-
test("returns empty for no breaking changes", () => {
|
|
162
|
-
const tasks = [
|
|
163
|
-
createTask("t1", { areas: ["backend"], hasBreakingChange: false }),
|
|
164
|
-
createTask("t2", { areas: ["backend"] }),
|
|
165
|
-
];
|
|
166
|
-
const result = findBreakingChanges(tasks);
|
|
167
|
-
expect(result).toEqual([]);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test("finds tasks with breaking changes", () => {
|
|
171
|
-
const tasks = [
|
|
172
|
-
createTask("t1", { areas: ["backend"], hasBreakingChange: true }),
|
|
173
|
-
createTask("t2", { areas: ["backend"], hasBreakingChange: false }),
|
|
174
|
-
createTask("t3", { areas: ["schema"], hasBreakingChange: true }),
|
|
175
|
-
];
|
|
176
|
-
const result = findBreakingChanges(tasks);
|
|
177
|
-
expect(result.length).toBe(2);
|
|
178
|
-
expect(result.map((t) => t.id).sort()).toEqual(["t1", "t3"]);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
test("excludes completed tasks", () => {
|
|
182
|
-
const tasks = [
|
|
183
|
-
createTask("t1", { status: "completed", areas: ["backend"], hasBreakingChange: true }),
|
|
184
|
-
createTask("t2", { status: "pending", areas: ["backend"], hasBreakingChange: true }),
|
|
185
|
-
];
|
|
186
|
-
const result = findBreakingChanges(tasks);
|
|
187
|
-
expect(result.length).toBe(1);
|
|
188
|
-
expect(result[0]!.id).toBe("t2");
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
describe("findHighRiskTasks", () => {
|
|
193
|
-
test("returns empty for no high-risk tasks", () => {
|
|
194
|
-
const tasks = [
|
|
195
|
-
createTask("t1", { areas: ["backend"], riskLevel: "low" }),
|
|
196
|
-
createTask("t2", { areas: ["backend"], riskLevel: "medium" }),
|
|
197
|
-
];
|
|
198
|
-
const result = findHighRiskTasks(tasks);
|
|
199
|
-
expect(result).toEqual([]);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
test("finds high and critical risk tasks", () => {
|
|
203
|
-
const tasks = [
|
|
204
|
-
createTask("t1", { areas: ["backend"], riskLevel: "low" }),
|
|
205
|
-
createTask("t2", { areas: ["backend"], riskLevel: "high" }),
|
|
206
|
-
createTask("t3", { areas: ["backend"], riskLevel: "critical" }),
|
|
207
|
-
];
|
|
208
|
-
const result = findHighRiskTasks(tasks);
|
|
209
|
-
expect(result.length).toBe(2);
|
|
210
|
-
expect(result.map((t) => t.id).sort()).toEqual(["t2", "t3"]);
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
describe("groupByTechArea", () => {
|
|
215
|
-
test("returns all areas with empty arrays for empty input", () => {
|
|
216
|
-
const result = groupByTechArea([]);
|
|
217
|
-
expect(result.get("schema")).toEqual([]);
|
|
218
|
-
expect(result.get("backend")).toEqual([]);
|
|
219
|
-
expect(result.get("frontend")).toEqual([]);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
test("groups tasks by their tech areas", () => {
|
|
223
|
-
const tasks = [
|
|
224
|
-
createTask("t1", { areas: ["backend"] }),
|
|
225
|
-
createTask("t2", { areas: ["backend"] }),
|
|
226
|
-
createTask("t3", { areas: ["frontend"] }),
|
|
227
|
-
];
|
|
228
|
-
const result = groupByTechArea(tasks);
|
|
229
|
-
|
|
230
|
-
expect(result.get("backend")!.length).toBe(2);
|
|
231
|
-
expect(result.get("frontend")!.length).toBe(1);
|
|
232
|
-
expect(result.get("schema")!.length).toBe(0);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
test("task appears in multiple groups if it spans multiple areas", () => {
|
|
236
|
-
const tasks = [
|
|
237
|
-
createTask("fullstack", { areas: ["backend", "frontend"] }),
|
|
238
|
-
];
|
|
239
|
-
const result = groupByTechArea(tasks);
|
|
240
|
-
|
|
241
|
-
expect(result.get("backend")!.length).toBe(1);
|
|
242
|
-
expect(result.get("frontend")!.length).toBe(1);
|
|
243
|
-
expect(result.get("backend")![0]!.id).toBe("fullstack");
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
test("defaults to backend if no area specified", () => {
|
|
247
|
-
const tasks = [createTask("t1", {})];
|
|
248
|
-
const result = groupByTechArea(tasks);
|
|
249
|
-
expect(result.get("backend")!.length).toBe(1);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
test("excludes completed tasks", () => {
|
|
253
|
-
const tasks = [
|
|
254
|
-
createTask("t1", { status: "completed", areas: ["backend"] }),
|
|
255
|
-
createTask("t2", { status: "pending", areas: ["backend"] }),
|
|
256
|
-
];
|
|
257
|
-
const result = groupByTechArea(tasks);
|
|
258
|
-
expect(result.get("backend")!.length).toBe(1);
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
describe("getComplexitySummary", () => {
|
|
263
|
-
test("returns empty summary for no tasks", () => {
|
|
264
|
-
const result = getComplexitySummary([]);
|
|
265
|
-
expect(result.distribution).toEqual({ low: 0, medium: 0, high: 0 });
|
|
266
|
-
expect(result.needsBreakdown).toEqual([]);
|
|
267
|
-
expect(result.averageScore).toBe(0);
|
|
268
|
-
expect(result.unanalyzed).toEqual([]);
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
test("calculates distribution correctly", () => {
|
|
272
|
-
const tasks = [
|
|
273
|
-
createTask("t1", { complexityScore: 2 }), // low
|
|
274
|
-
createTask("t2", { complexityScore: 3 }), // low
|
|
275
|
-
createTask("t3", { complexityScore: 5 }), // medium
|
|
276
|
-
createTask("t4", { complexityScore: 8 }), // high
|
|
277
|
-
];
|
|
278
|
-
const result = getComplexitySummary(tasks);
|
|
279
|
-
|
|
280
|
-
expect(result.distribution.low).toBe(2);
|
|
281
|
-
expect(result.distribution.medium).toBe(1);
|
|
282
|
-
expect(result.distribution.high).toBe(1);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
test("identifies tasks needing breakdown (score >= 7)", () => {
|
|
286
|
-
const tasks = [
|
|
287
|
-
createTask("t1", { complexityScore: 6 }),
|
|
288
|
-
createTask("t2", { complexityScore: 7 }),
|
|
289
|
-
createTask("t3", { complexityScore: 9 }),
|
|
290
|
-
];
|
|
291
|
-
const result = getComplexitySummary(tasks);
|
|
292
|
-
|
|
293
|
-
expect(result.needsBreakdown.length).toBe(2);
|
|
294
|
-
expect(result.needsBreakdown.map((t) => t.id).sort()).toEqual(["t2", "t3"]);
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
test("calculates average score correctly", () => {
|
|
298
|
-
const tasks = [
|
|
299
|
-
createTask("t1", { complexityScore: 2 }),
|
|
300
|
-
createTask("t2", { complexityScore: 6 }),
|
|
301
|
-
createTask("t3", { complexityScore: 10 }),
|
|
302
|
-
];
|
|
303
|
-
const result = getComplexitySummary(tasks);
|
|
304
|
-
|
|
305
|
-
expect(result.averageScore).toBe(6); // (2+6+10)/3
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
test("identifies unanalyzed tasks", () => {
|
|
309
|
-
const tasks = [
|
|
310
|
-
createTask("analyzed", { complexityScore: 5 }),
|
|
311
|
-
createTask("not-analyzed", {}),
|
|
312
|
-
];
|
|
313
|
-
const result = getComplexitySummary(tasks);
|
|
314
|
-
|
|
315
|
-
expect(result.unanalyzed.length).toBe(1);
|
|
316
|
-
expect(result.unanalyzed[0]!.id).toBe("not-analyzed");
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
test("excludes completed tasks", () => {
|
|
320
|
-
const tasks = [
|
|
321
|
-
createTask("t1", { status: "completed", complexityScore: 10 }),
|
|
322
|
-
createTask("t2", { status: "pending", complexityScore: 5 }),
|
|
323
|
-
];
|
|
324
|
-
const result = getComplexitySummary(tasks);
|
|
325
|
-
|
|
326
|
-
expect(result.distribution.high).toBe(0);
|
|
327
|
-
expect(result.distribution.medium).toBe(1);
|
|
328
|
-
});
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
describe("getTechStackSummary", () => {
|
|
332
|
-
test("returns empty summary for no tasks", () => {
|
|
333
|
-
const result = getTechStackSummary([]);
|
|
334
|
-
expect(result.areaCounts.schema).toBe(0);
|
|
335
|
-
expect(result.breakingChanges).toEqual([]);
|
|
336
|
-
expect(result.unanalyzed).toEqual([]);
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
test("counts tasks by area correctly", () => {
|
|
340
|
-
const tasks = [
|
|
341
|
-
createTask("t1", { areas: ["backend"] }),
|
|
342
|
-
createTask("t2", { areas: ["backend", "frontend"] }),
|
|
343
|
-
createTask("t3", { areas: ["schema"] }),
|
|
344
|
-
];
|
|
345
|
-
const result = getTechStackSummary(tasks);
|
|
346
|
-
|
|
347
|
-
expect(result.areaCounts.backend).toBe(2);
|
|
348
|
-
expect(result.areaCounts.frontend).toBe(1);
|
|
349
|
-
expect(result.areaCounts.schema).toBe(1);
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
test("tracks risk distribution", () => {
|
|
353
|
-
const tasks = [
|
|
354
|
-
createTask("t1", { areas: ["backend"], riskLevel: "low" }),
|
|
355
|
-
createTask("t2", { areas: ["backend"], riskLevel: "medium" }),
|
|
356
|
-
createTask("t3", { areas: ["backend"], riskLevel: "high" }),
|
|
357
|
-
];
|
|
358
|
-
const result = getTechStackSummary(tasks);
|
|
359
|
-
|
|
360
|
-
expect(result.riskDistribution.low).toBe(1);
|
|
361
|
-
expect(result.riskDistribution.medium).toBe(1);
|
|
362
|
-
expect(result.riskDistribution.high).toBe(1);
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
test("identifies breaking changes", () => {
|
|
366
|
-
const tasks = [
|
|
367
|
-
createTask("t1", { areas: ["backend"], hasBreakingChange: true }),
|
|
368
|
-
createTask("t2", { areas: ["backend"], hasBreakingChange: false }),
|
|
369
|
-
];
|
|
370
|
-
const result = getTechStackSummary(tasks);
|
|
371
|
-
|
|
372
|
-
expect(result.breakingChanges.length).toBe(1);
|
|
373
|
-
expect(result.breakingChanges[0]!.id).toBe("t1");
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
test("identifies unanalyzed tasks", () => {
|
|
377
|
-
const tasks = [
|
|
378
|
-
createTask("analyzed", { areas: ["backend"] }),
|
|
379
|
-
createTask("not-analyzed", {}),
|
|
380
|
-
];
|
|
381
|
-
const result = getTechStackSummary(tasks);
|
|
382
|
-
|
|
383
|
-
expect(result.unanalyzed.length).toBe(1);
|
|
384
|
-
expect(result.unanalyzed[0]!.id).toBe("not-analyzed");
|
|
385
|
-
});
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
describe("suggestSubtaskCount", () => {
|
|
389
|
-
test("returns 0 for very low complexity (1-2)", () => {
|
|
390
|
-
expect(suggestSubtaskCount(1)).toBe(0);
|
|
391
|
-
expect(suggestSubtaskCount(2)).toBe(0);
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
test("returns 2 for low complexity (3-4)", () => {
|
|
395
|
-
expect(suggestSubtaskCount(3)).toBe(2);
|
|
396
|
-
expect(suggestSubtaskCount(4)).toBe(2);
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
test("returns 3-4 for medium complexity (5-6)", () => {
|
|
400
|
-
expect(suggestSubtaskCount(5)).toBe(4);
|
|
401
|
-
expect(suggestSubtaskCount(6)).toBe(4);
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
test("returns 5-6 for high complexity (7-8)", () => {
|
|
405
|
-
expect(suggestSubtaskCount(7)).toBe(6);
|
|
406
|
-
expect(suggestSubtaskCount(8)).toBe(6);
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
test("returns 7-10 for very high complexity (9-10)", () => {
|
|
410
|
-
expect(suggestSubtaskCount(9)).toBe(9);
|
|
411
|
-
expect(suggestSubtaskCount(10)).toBe(10);
|
|
412
|
-
});
|
|
413
|
-
});
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
priorityToNumber,
|
|
4
|
-
topologicalSort,
|
|
5
|
-
wouldCreateCycle,
|
|
6
|
-
findDependents,
|
|
7
|
-
findDependencies,
|
|
8
|
-
} from "./topological-sort.js";
|
|
9
|
-
import type { Task } from "../schemas/task.js";
|
|
10
|
-
|
|
11
|
-
// Helper to create mock tasks
|
|
12
|
-
function createTask(id: string, priority: string = "medium", deps: string[] = []): Task {
|
|
13
|
-
return {
|
|
14
|
-
id,
|
|
15
|
-
title: `Task ${id}`,
|
|
16
|
-
status: "pending",
|
|
17
|
-
priority: priority as Task["priority"],
|
|
18
|
-
workspace: "test-workspace",
|
|
19
|
-
createdAt: new Date().toISOString(),
|
|
20
|
-
updatedAt: new Date().toISOString(),
|
|
21
|
-
dependencies: deps.map((depId) => ({ taskId: depId, type: "blocked_by" as const })),
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
describe("priorityToNumber", () => {
|
|
26
|
-
test("converts critical to 4", () => {
|
|
27
|
-
expect(priorityToNumber("critical")).toBe(4);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("converts high to 3", () => {
|
|
31
|
-
expect(priorityToNumber("high")).toBe(3);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("converts medium to 2", () => {
|
|
35
|
-
expect(priorityToNumber("medium")).toBe(2);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("converts low to 1", () => {
|
|
39
|
-
expect(priorityToNumber("low")).toBe(1);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("defaults to 2 for unknown priority", () => {
|
|
43
|
-
expect(priorityToNumber("unknown")).toBe(2);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe("topologicalSort", () => {
|
|
48
|
-
test("returns empty array for empty input", () => {
|
|
49
|
-
const result = topologicalSort([]);
|
|
50
|
-
expect(result).toEqual([]);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test("returns single task unchanged", () => {
|
|
54
|
-
const tasks = [createTask("A")];
|
|
55
|
-
const result = topologicalSort(tasks);
|
|
56
|
-
expect(result.length).toBe(1);
|
|
57
|
-
expect(result[0]!.id).toBe("A");
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("sorts by dependency order", () => {
|
|
61
|
-
// B depends on A, so A should come first
|
|
62
|
-
const tasks = [createTask("B", "high", ["A"]), createTask("A", "low")];
|
|
63
|
-
const result = topologicalSort(tasks);
|
|
64
|
-
expect(result[0]!.id).toBe("A");
|
|
65
|
-
expect(result[1]!.id).toBe("B");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("respects priority when no dependencies", () => {
|
|
69
|
-
const tasks = [
|
|
70
|
-
createTask("A", "low"),
|
|
71
|
-
createTask("B", "critical"),
|
|
72
|
-
createTask("C", "high"),
|
|
73
|
-
];
|
|
74
|
-
const result = topologicalSort(tasks);
|
|
75
|
-
expect(result[0]!.id).toBe("B"); // critical first
|
|
76
|
-
expect(result[1]!.id).toBe("C"); // then high
|
|
77
|
-
expect(result[2]!.id).toBe("A"); // then low
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("handles chain of dependencies", () => {
|
|
81
|
-
// C -> B -> A (C depends on B, B depends on A)
|
|
82
|
-
const tasks = [
|
|
83
|
-
createTask("C", "critical", ["B"]),
|
|
84
|
-
createTask("B", "high", ["A"]),
|
|
85
|
-
createTask("A", "low"),
|
|
86
|
-
];
|
|
87
|
-
const result = topologicalSort(tasks);
|
|
88
|
-
expect(result.map((t) => t.id)).toEqual(["A", "B", "C"]);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test("throws on circular dependency", () => {
|
|
92
|
-
// A -> B -> A
|
|
93
|
-
const tasks = [createTask("A", "medium", ["B"]), createTask("B", "medium", ["A"])];
|
|
94
|
-
expect(() => topologicalSort(tasks)).toThrow(/Circular dependency/);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test("handles diamond dependency", () => {
|
|
98
|
-
// D depends on B and C, both depend on A
|
|
99
|
-
const tasks = [
|
|
100
|
-
createTask("D", "medium", ["B", "C"]),
|
|
101
|
-
createTask("B", "high", ["A"]),
|
|
102
|
-
createTask("C", "low", ["A"]),
|
|
103
|
-
createTask("A", "medium"),
|
|
104
|
-
];
|
|
105
|
-
const result = topologicalSort(tasks);
|
|
106
|
-
// A must come first, D must come last
|
|
107
|
-
expect(result[0]!.id).toBe("A");
|
|
108
|
-
expect(result[3]!.id).toBe("D");
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe("wouldCreateCycle", () => {
|
|
113
|
-
test("returns false for valid dependency", () => {
|
|
114
|
-
const tasks = [createTask("A"), createTask("B")];
|
|
115
|
-
// Adding B blocked_by A should not create cycle
|
|
116
|
-
expect(wouldCreateCycle(tasks, "B", "A")).toBe(false);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test("returns true for direct cycle", () => {
|
|
120
|
-
const tasks = [createTask("A", "medium", ["B"]), createTask("B")];
|
|
121
|
-
// B is already blocking A, so A blocking B would create cycle
|
|
122
|
-
expect(wouldCreateCycle(tasks, "B", "A")).toBe(true);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test("returns true for indirect cycle", () => {
|
|
126
|
-
const tasks = [
|
|
127
|
-
createTask("A", "medium", ["B"]),
|
|
128
|
-
createTask("B", "medium", ["C"]),
|
|
129
|
-
createTask("C"),
|
|
130
|
-
];
|
|
131
|
-
// A <- B <- C, adding C <- A would create cycle
|
|
132
|
-
expect(wouldCreateCycle(tasks, "C", "A")).toBe(true);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
describe("findDependents", () => {
|
|
137
|
-
test("returns empty for task with no dependents", () => {
|
|
138
|
-
const tasks = [createTask("A"), createTask("B")];
|
|
139
|
-
const result = findDependents(tasks, "A");
|
|
140
|
-
expect(result).toEqual([]);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test("finds direct dependents", () => {
|
|
144
|
-
const tasks = [
|
|
145
|
-
createTask("A"),
|
|
146
|
-
createTask("B", "medium", ["A"]),
|
|
147
|
-
createTask("C", "medium", ["A"]),
|
|
148
|
-
];
|
|
149
|
-
const result = findDependents(tasks, "A");
|
|
150
|
-
expect(result.map((t) => t.id).sort()).toEqual(["B", "C"]);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test("finds transitive dependents", () => {
|
|
154
|
-
const tasks = [
|
|
155
|
-
createTask("A"),
|
|
156
|
-
createTask("B", "medium", ["A"]),
|
|
157
|
-
createTask("C", "medium", ["B"]),
|
|
158
|
-
];
|
|
159
|
-
const result = findDependents(tasks, "A");
|
|
160
|
-
expect(result.map((t) => t.id).sort()).toEqual(["B", "C"]);
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
describe("findDependencies", () => {
|
|
165
|
-
test("returns empty for task with no dependencies", () => {
|
|
166
|
-
const tasks = [createTask("A"), createTask("B")];
|
|
167
|
-
const result = findDependencies(tasks, "A");
|
|
168
|
-
expect(result).toEqual([]);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test("finds direct dependencies", () => {
|
|
172
|
-
const tasks = [
|
|
173
|
-
createTask("A"),
|
|
174
|
-
createTask("B"),
|
|
175
|
-
createTask("C", "medium", ["A", "B"]),
|
|
176
|
-
];
|
|
177
|
-
const result = findDependencies(tasks, "C");
|
|
178
|
-
expect(result.map((t) => t.id).sort()).toEqual(["A", "B"]);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
test("finds transitive dependencies", () => {
|
|
182
|
-
const tasks = [
|
|
183
|
-
createTask("A"),
|
|
184
|
-
createTask("B", "medium", ["A"]),
|
|
185
|
-
createTask("C", "medium", ["B"]),
|
|
186
|
-
];
|
|
187
|
-
const result = findDependencies(tasks, "C");
|
|
188
|
-
expect(result.map((t) => t.id).sort()).toEqual(["A", "B"]);
|
|
189
|
-
});
|
|
190
|
-
});
|