@task-mcp/shared 1.0.4 → 1.0.6
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/dist/algorithms/critical-path.d.ts.map +1 -1
- package/dist/algorithms/critical-path.js +50 -26
- package/dist/algorithms/critical-path.js.map +1 -1
- package/dist/algorithms/dependency-integrity.d.ts +73 -0
- package/dist/algorithms/dependency-integrity.d.ts.map +1 -0
- package/dist/algorithms/dependency-integrity.js +189 -0
- package/dist/algorithms/dependency-integrity.js.map +1 -0
- package/dist/algorithms/index.d.ts +2 -0
- package/dist/algorithms/index.d.ts.map +1 -1
- package/dist/algorithms/index.js +2 -0
- package/dist/algorithms/index.js.map +1 -1
- package/dist/algorithms/tech-analysis.d.ts +106 -0
- package/dist/algorithms/tech-analysis.d.ts.map +1 -0
- package/dist/algorithms/tech-analysis.js +296 -0
- package/dist/algorithms/tech-analysis.js.map +1 -0
- package/dist/algorithms/tech-analysis.test.d.ts +2 -0
- package/dist/algorithms/tech-analysis.test.d.ts.map +1 -0
- package/dist/algorithms/tech-analysis.test.js +338 -0
- package/dist/algorithms/tech-analysis.test.js.map +1 -0
- package/dist/algorithms/topological-sort.d.ts.map +1 -1
- package/dist/algorithms/topological-sort.js +60 -8
- package/dist/algorithms/topological-sort.js.map +1 -1
- package/dist/schemas/inbox.d.ts +24 -0
- package/dist/schemas/inbox.d.ts.map +1 -0
- package/dist/schemas/inbox.js +25 -0
- package/dist/schemas/inbox.js.map +1 -0
- package/dist/schemas/index.d.ts +3 -1
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +9 -1
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/response-format.d.ts +79 -0
- package/dist/schemas/response-format.d.ts.map +1 -0
- package/dist/schemas/response-format.js +17 -0
- package/dist/schemas/response-format.js.map +1 -0
- package/dist/schemas/task.d.ts +57 -0
- package/dist/schemas/task.d.ts.map +1 -1
- package/dist/schemas/task.js +34 -0
- package/dist/schemas/task.js.map +1 -1
- package/dist/utils/date.d.ts.map +1 -1
- package/dist/utils/date.js +17 -2
- package/dist/utils/date.js.map +1 -1
- package/dist/utils/hierarchy.d.ts +75 -0
- package/dist/utils/hierarchy.d.ts.map +1 -0
- package/dist/utils/hierarchy.js +179 -0
- package/dist/utils/hierarchy.js.map +1 -0
- package/dist/utils/id.d.ts +51 -1
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +124 -4
- package/dist/utils/id.js.map +1 -1
- package/dist/utils/id.test.d.ts +2 -0
- package/dist/utils/id.test.d.ts.map +1 -0
- package/dist/utils/id.test.js +228 -0
- package/dist/utils/id.test.js.map +1 -0
- package/dist/utils/index.d.ts +4 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +7 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/natural-language.d.ts +45 -0
- package/dist/utils/natural-language.d.ts.map +1 -1
- package/dist/utils/natural-language.js +86 -0
- package/dist/utils/natural-language.js.map +1 -1
- package/dist/utils/projection.d.ts +65 -0
- package/dist/utils/projection.d.ts.map +1 -0
- package/dist/utils/projection.js +181 -0
- package/dist/utils/projection.js.map +1 -0
- package/dist/utils/projection.test.d.ts +2 -0
- package/dist/utils/projection.test.d.ts.map +1 -0
- package/dist/utils/projection.test.js +400 -0
- package/dist/utils/projection.test.js.map +1 -0
- package/package.json +1 -1
- package/src/algorithms/critical-path.ts +56 -24
- package/src/algorithms/dependency-integrity.ts +270 -0
- package/src/algorithms/index.ts +28 -0
- package/src/algorithms/tech-analysis.test.ts +413 -0
- package/src/algorithms/tech-analysis.ts +412 -0
- package/src/algorithms/topological-sort.ts +66 -9
- package/src/schemas/inbox.ts +32 -0
- package/src/schemas/index.ts +31 -0
- package/src/schemas/response-format.ts +108 -0
- package/src/schemas/task.ts +50 -0
- package/src/utils/date.ts +18 -2
- package/src/utils/hierarchy.ts +224 -0
- package/src/utils/id.test.ts +281 -0
- package/src/utils/id.ts +139 -4
- package/src/utils/index.ts +46 -2
- package/src/utils/natural-language.ts +113 -0
- package/src/utils/projection.test.ts +505 -0
- package/src/utils/projection.ts +251 -0
|
@@ -0,0 +1,413 @@
|
|
|
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
|
+
projectId: "test-project",
|
|
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
|
+
});
|