@task-mcp/shared 1.0.4 → 1.0.7

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 (98) hide show
  1. package/dist/algorithms/critical-path.d.ts.map +1 -1
  2. package/dist/algorithms/critical-path.js +50 -26
  3. package/dist/algorithms/critical-path.js.map +1 -1
  4. package/dist/algorithms/dependency-integrity.d.ts +73 -0
  5. package/dist/algorithms/dependency-integrity.d.ts.map +1 -0
  6. package/dist/algorithms/dependency-integrity.js +189 -0
  7. package/dist/algorithms/dependency-integrity.js.map +1 -0
  8. package/dist/algorithms/index.d.ts +2 -0
  9. package/dist/algorithms/index.d.ts.map +1 -1
  10. package/dist/algorithms/index.js +2 -0
  11. package/dist/algorithms/index.js.map +1 -1
  12. package/dist/algorithms/tech-analysis.d.ts +106 -0
  13. package/dist/algorithms/tech-analysis.d.ts.map +1 -0
  14. package/dist/algorithms/tech-analysis.js +296 -0
  15. package/dist/algorithms/tech-analysis.js.map +1 -0
  16. package/dist/algorithms/tech-analysis.test.d.ts +2 -0
  17. package/dist/algorithms/tech-analysis.test.d.ts.map +1 -0
  18. package/dist/algorithms/tech-analysis.test.js +338 -0
  19. package/dist/algorithms/tech-analysis.test.js.map +1 -0
  20. package/dist/algorithms/topological-sort.d.ts.map +1 -1
  21. package/dist/algorithms/topological-sort.js +60 -8
  22. package/dist/algorithms/topological-sort.js.map +1 -1
  23. package/dist/schemas/inbox.d.ts +55 -0
  24. package/dist/schemas/inbox.d.ts.map +1 -0
  25. package/dist/schemas/inbox.js +25 -0
  26. package/dist/schemas/inbox.js.map +1 -0
  27. package/dist/schemas/index.d.ts +3 -1
  28. package/dist/schemas/index.d.ts.map +1 -1
  29. package/dist/schemas/index.js +9 -1
  30. package/dist/schemas/index.js.map +1 -1
  31. package/dist/schemas/project.d.ts +154 -41
  32. package/dist/schemas/project.d.ts.map +1 -1
  33. package/dist/schemas/project.js +38 -33
  34. package/dist/schemas/project.js.map +1 -1
  35. package/dist/schemas/response-format.d.ts +80 -0
  36. package/dist/schemas/response-format.d.ts.map +1 -0
  37. package/dist/schemas/response-format.js +17 -0
  38. package/dist/schemas/response-format.js.map +1 -0
  39. package/dist/schemas/task.d.ts +592 -94
  40. package/dist/schemas/task.d.ts.map +1 -1
  41. package/dist/schemas/task.js +124 -64
  42. package/dist/schemas/task.js.map +1 -1
  43. package/dist/schemas/view.d.ts +128 -37
  44. package/dist/schemas/view.d.ts.map +1 -1
  45. package/dist/schemas/view.js +38 -24
  46. package/dist/schemas/view.js.map +1 -1
  47. package/dist/utils/date.d.ts.map +1 -1
  48. package/dist/utils/date.js +17 -2
  49. package/dist/utils/date.js.map +1 -1
  50. package/dist/utils/hierarchy.d.ts +75 -0
  51. package/dist/utils/hierarchy.d.ts.map +1 -0
  52. package/dist/utils/hierarchy.js +179 -0
  53. package/dist/utils/hierarchy.js.map +1 -0
  54. package/dist/utils/id.d.ts +51 -1
  55. package/dist/utils/id.d.ts.map +1 -1
  56. package/dist/utils/id.js +124 -4
  57. package/dist/utils/id.js.map +1 -1
  58. package/dist/utils/id.test.d.ts +2 -0
  59. package/dist/utils/id.test.d.ts.map +1 -0
  60. package/dist/utils/id.test.js +228 -0
  61. package/dist/utils/id.test.js.map +1 -0
  62. package/dist/utils/index.d.ts +4 -2
  63. package/dist/utils/index.d.ts.map +1 -1
  64. package/dist/utils/index.js +7 -2
  65. package/dist/utils/index.js.map +1 -1
  66. package/dist/utils/natural-language.d.ts +45 -0
  67. package/dist/utils/natural-language.d.ts.map +1 -1
  68. package/dist/utils/natural-language.js +86 -0
  69. package/dist/utils/natural-language.js.map +1 -1
  70. package/dist/utils/projection.d.ts +65 -0
  71. package/dist/utils/projection.d.ts.map +1 -0
  72. package/dist/utils/projection.js +181 -0
  73. package/dist/utils/projection.js.map +1 -0
  74. package/dist/utils/projection.test.d.ts +2 -0
  75. package/dist/utils/projection.test.d.ts.map +1 -0
  76. package/dist/utils/projection.test.js +400 -0
  77. package/dist/utils/projection.test.js.map +1 -0
  78. package/package.json +2 -2
  79. package/src/algorithms/critical-path.ts +56 -24
  80. package/src/algorithms/dependency-integrity.ts +270 -0
  81. package/src/algorithms/index.ts +28 -0
  82. package/src/algorithms/tech-analysis.test.ts +413 -0
  83. package/src/algorithms/tech-analysis.ts +412 -0
  84. package/src/algorithms/topological-sort.ts +66 -9
  85. package/src/schemas/inbox.ts +32 -0
  86. package/src/schemas/index.ts +31 -0
  87. package/src/schemas/project.ts +43 -40
  88. package/src/schemas/response-format.ts +108 -0
  89. package/src/schemas/task.ts +145 -77
  90. package/src/schemas/view.ts +43 -33
  91. package/src/utils/date.ts +18 -2
  92. package/src/utils/hierarchy.ts +224 -0
  93. package/src/utils/id.test.ts +281 -0
  94. package/src/utils/id.ts +139 -4
  95. package/src/utils/index.ts +46 -2
  96. package/src/utils/natural-language.ts +113 -0
  97. package/src/utils/projection.test.ts +505 -0
  98. package/src/utils/projection.ts +251 -0
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Tech area ordering rules (lower = execute first)
3
+ * Based on dependency flow: DB changes → Infrastructure → Backend → Frontend → Tests
4
+ */
5
+ const TECH_ORDER = {
6
+ schema: 0, // DB/schema changes first
7
+ infra: 0, // Infrastructure setup
8
+ devops: 1, // CI/CD pipelines
9
+ backend: 2, // API/server
10
+ frontend: 3, // UI
11
+ test: 4, // Tests
12
+ docs: 4, // Documentation
13
+ refactor: 5, // Refactoring last
14
+ };
15
+ /**
16
+ * Risk level ordering (lower = safer, execute first)
17
+ */
18
+ const RISK_ORDER = {
19
+ low: 0,
20
+ medium: 1,
21
+ high: 2,
22
+ critical: 3,
23
+ };
24
+ /**
25
+ * Get the minimum tech order value for a task
26
+ * If multiple areas, use the lowest (most foundational)
27
+ */
28
+ function getMinTechOrder(task) {
29
+ const areas = task.techStack?.areas ?? [];
30
+ if (areas.length === 0)
31
+ return TECH_ORDER.backend; // Default to backend
32
+ return Math.min(...areas.map((area) => TECH_ORDER[area]));
33
+ }
34
+ /**
35
+ * Get the risk order value for a task
36
+ */
37
+ function getRiskOrder(task) {
38
+ const riskLevel = task.techStack?.riskLevel ?? "medium";
39
+ return RISK_ORDER[riskLevel];
40
+ }
41
+ /**
42
+ * Check if a task has breaking changes
43
+ */
44
+ function hasBreakingChange(task) {
45
+ return task.techStack?.hasBreakingChange === true;
46
+ }
47
+ /**
48
+ * Suggest safe execution order for tasks
49
+ *
50
+ * Ordering strategy:
51
+ * 1. Tech level (schema → infra → devops → backend → frontend → test → docs → refactor)
52
+ * 2. Risk level within same tech level (low → medium → high → critical)
53
+ * 3. Breaking changes last within same tech/risk level
54
+ * 4. Priority as tiebreaker (critical > high > medium > low)
55
+ */
56
+ export function suggestSafeOrder(tasks) {
57
+ // Filter to active tasks only
58
+ const activeTasks = tasks.filter((t) => t.status === "pending" || t.status === "in_progress");
59
+ if (activeTasks.length === 0) {
60
+ return {
61
+ orderedTasks: [],
62
+ phases: [],
63
+ summary: {
64
+ totalTasks: 0,
65
+ breakingChanges: 0,
66
+ highRiskCount: 0,
67
+ },
68
+ };
69
+ }
70
+ // Sort tasks
71
+ const sorted = [...activeTasks].sort((a, b) => {
72
+ // 1. Tech level (lower first)
73
+ const techDiff = getMinTechOrder(a) - getMinTechOrder(b);
74
+ if (techDiff !== 0)
75
+ return techDiff;
76
+ // 2. Risk level (lower first)
77
+ const riskDiff = getRiskOrder(a) - getRiskOrder(b);
78
+ if (riskDiff !== 0)
79
+ return riskDiff;
80
+ // 3. Breaking changes last
81
+ const aBreaking = hasBreakingChange(a) ? 1 : 0;
82
+ const bBreaking = hasBreakingChange(b) ? 1 : 0;
83
+ if (aBreaking !== bBreaking)
84
+ return aBreaking - bBreaking;
85
+ // 4. Priority as tiebreaker (higher priority first)
86
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
87
+ return (priorityOrder[a.priority] ?? 2) - (priorityOrder[b.priority] ?? 2);
88
+ });
89
+ // Group into phases by tech level
90
+ const phases = [];
91
+ let currentTechOrder = -1;
92
+ let currentPhase = null;
93
+ for (const task of sorted) {
94
+ const techOrder = getMinTechOrder(task);
95
+ if (techOrder !== currentTechOrder) {
96
+ // Start new phase
97
+ const primaryArea = getPrimaryArea(task);
98
+ currentPhase = {
99
+ phase: phases.length + 1,
100
+ primaryArea,
101
+ tasks: [task],
102
+ notes: [],
103
+ };
104
+ phases.push(currentPhase);
105
+ currentTechOrder = techOrder;
106
+ }
107
+ else {
108
+ // Add to current phase
109
+ currentPhase.tasks.push(task);
110
+ }
111
+ }
112
+ // Add notes to phases
113
+ for (const phase of phases) {
114
+ const breakingCount = phase.tasks.filter(hasBreakingChange).length;
115
+ const highRiskCount = phase.tasks.filter((t) => t.techStack?.riskLevel === "high" || t.techStack?.riskLevel === "critical").length;
116
+ if (breakingCount > 0) {
117
+ phase.notes.push(`${breakingCount} breaking change(s) - test thoroughly`);
118
+ }
119
+ if (highRiskCount > 0) {
120
+ phase.notes.push(`${highRiskCount} high-risk task(s) - review carefully`);
121
+ }
122
+ }
123
+ // Calculate summary
124
+ const breakingChanges = sorted.filter(hasBreakingChange).length;
125
+ const highRiskCount = sorted.filter((t) => t.techStack?.riskLevel === "high" || t.techStack?.riskLevel === "critical").length;
126
+ return {
127
+ orderedTasks: sorted,
128
+ phases,
129
+ summary: {
130
+ totalTasks: sorted.length,
131
+ breakingChanges,
132
+ highRiskCount,
133
+ },
134
+ };
135
+ }
136
+ /**
137
+ * Get the primary tech area for a task
138
+ */
139
+ function getPrimaryArea(task) {
140
+ const areas = task.techStack?.areas;
141
+ if (!areas || areas.length === 0)
142
+ return "mixed";
143
+ // Return the one with lowest order (most foundational)
144
+ let primary = areas[0];
145
+ for (let i = 1; i < areas.length; i++) {
146
+ const area = areas[i];
147
+ if (TECH_ORDER[area] < TECH_ORDER[primary]) {
148
+ primary = area;
149
+ }
150
+ }
151
+ return primary;
152
+ }
153
+ /**
154
+ * Find tasks with breaking changes
155
+ */
156
+ export function findBreakingChanges(tasks) {
157
+ return tasks.filter((t) => (t.status === "pending" || t.status === "in_progress") &&
158
+ hasBreakingChange(t));
159
+ }
160
+ /**
161
+ * Find high-risk tasks (high or critical risk level)
162
+ */
163
+ export function findHighRiskTasks(tasks) {
164
+ return tasks.filter((t) => (t.status === "pending" || t.status === "in_progress") &&
165
+ (t.techStack?.riskLevel === "high" || t.techStack?.riskLevel === "critical"));
166
+ }
167
+ /**
168
+ * Group tasks by tech area
169
+ * A task may appear in multiple groups if it spans multiple areas
170
+ */
171
+ export function groupByTechArea(tasks) {
172
+ const groups = new Map();
173
+ // Initialize all groups
174
+ const allAreas = [
175
+ "schema", "infra", "devops", "backend", "frontend", "test", "docs", "refactor"
176
+ ];
177
+ for (const area of allAreas) {
178
+ groups.set(area, []);
179
+ }
180
+ // Group active tasks
181
+ const activeTasks = tasks.filter((t) => t.status === "pending" || t.status === "in_progress");
182
+ for (const task of activeTasks) {
183
+ const areas = task.techStack?.areas ?? [];
184
+ if (areas.length === 0) {
185
+ // Default to backend if no area specified
186
+ groups.get("backend").push(task);
187
+ }
188
+ else {
189
+ for (const area of areas) {
190
+ groups.get(area).push(task);
191
+ }
192
+ }
193
+ }
194
+ return groups;
195
+ }
196
+ /**
197
+ * Analyze complexity distribution across tasks
198
+ */
199
+ export function getComplexitySummary(tasks) {
200
+ const activeTasks = tasks.filter((t) => t.status === "pending" || t.status === "in_progress");
201
+ const analyzed = activeTasks.filter((t) => t.complexity?.score !== undefined);
202
+ const unanalyzed = activeTasks.filter((t) => t.complexity?.score === undefined);
203
+ // Calculate distribution
204
+ const distribution = {
205
+ low: 0,
206
+ medium: 0,
207
+ high: 0,
208
+ };
209
+ let totalScore = 0;
210
+ const needsBreakdown = [];
211
+ for (const task of analyzed) {
212
+ const score = task.complexity.score;
213
+ totalScore += score;
214
+ if (score <= 3) {
215
+ distribution.low++;
216
+ }
217
+ else if (score <= 6) {
218
+ distribution.medium++;
219
+ }
220
+ else {
221
+ distribution.high++;
222
+ needsBreakdown.push(task);
223
+ }
224
+ }
225
+ return {
226
+ distribution,
227
+ needsBreakdown,
228
+ averageScore: analyzed.length > 0 ? totalScore / analyzed.length : 0,
229
+ unanalyzed,
230
+ };
231
+ }
232
+ /**
233
+ * Analyze tech stack distribution across tasks
234
+ */
235
+ export function getTechStackSummary(tasks) {
236
+ const activeTasks = tasks.filter((t) => t.status === "pending" || t.status === "in_progress");
237
+ const analyzed = activeTasks.filter((t) => t.techStack?.areas !== undefined);
238
+ const unanalyzed = activeTasks.filter((t) => t.techStack?.areas === undefined);
239
+ // Count by area
240
+ const areaCounts = {
241
+ schema: 0,
242
+ infra: 0,
243
+ devops: 0,
244
+ backend: 0,
245
+ frontend: 0,
246
+ test: 0,
247
+ docs: 0,
248
+ refactor: 0,
249
+ };
250
+ for (const task of analyzed) {
251
+ for (const area of task.techStack.areas) {
252
+ areaCounts[area]++;
253
+ }
254
+ }
255
+ // Risk distribution
256
+ const riskDistribution = {
257
+ low: 0,
258
+ medium: 0,
259
+ high: 0,
260
+ critical: 0,
261
+ };
262
+ for (const task of analyzed) {
263
+ const risk = task.techStack?.riskLevel ?? "medium";
264
+ riskDistribution[risk]++;
265
+ }
266
+ // Breaking changes
267
+ const breakingChanges = findBreakingChanges(activeTasks);
268
+ return {
269
+ areaCounts,
270
+ breakingChanges,
271
+ riskDistribution,
272
+ unanalyzed,
273
+ };
274
+ }
275
+ /**
276
+ * Suggest number of subtasks based on complexity score
277
+ *
278
+ * Mapping:
279
+ * 1-2: 0 (no breakdown needed)
280
+ * 3-4: 2
281
+ * 5-6: 3-4
282
+ * 7-8: 5-6
283
+ * 9-10: 7-10
284
+ */
285
+ export function suggestSubtaskCount(score) {
286
+ if (score <= 2)
287
+ return 0;
288
+ if (score <= 4)
289
+ return 2;
290
+ if (score <= 6)
291
+ return Math.ceil((score - 4) * 0.5 + 3); // 3-4
292
+ if (score <= 8)
293
+ return Math.ceil((score - 6) * 0.5 + 5); // 5-6
294
+ return Math.ceil((score - 8) * 1.5 + 7); // 7-10
295
+ }
296
+ //# sourceMappingURL=tech-analysis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tech-analysis.js","sourceRoot":"","sources":["../../src/algorithms/tech-analysis.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tech-analysis.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tech-analysis.test.d.ts","sourceRoot":"","sources":["../../src/algorithms/tech-analysis.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,338 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { suggestSafeOrder, findBreakingChanges, findHighRiskTasks, groupByTechArea, getComplexitySummary, getTechStackSummary, suggestSubtaskCount, } from "./tech-analysis.js";
3
+ // Helper to create mock tasks with tech stack analysis
4
+ function createTask(id, options = {}) {
5
+ const task = {
6
+ id,
7
+ title: `Task ${id}`,
8
+ status: options.status ?? "pending",
9
+ priority: options.priority ?? "medium",
10
+ projectId: "test-project",
11
+ createdAt: new Date().toISOString(),
12
+ updatedAt: new Date().toISOString(),
13
+ };
14
+ if (options.areas || options.riskLevel !== undefined || options.hasBreakingChange !== undefined) {
15
+ task.techStack = {};
16
+ if (options.areas) {
17
+ task.techStack.areas = options.areas;
18
+ }
19
+ if (options.riskLevel !== undefined) {
20
+ task.techStack.riskLevel = options.riskLevel;
21
+ }
22
+ if (options.hasBreakingChange !== undefined) {
23
+ task.techStack.hasBreakingChange = options.hasBreakingChange;
24
+ }
25
+ }
26
+ if (options.complexityScore !== undefined) {
27
+ task.complexity = {
28
+ score: options.complexityScore,
29
+ };
30
+ }
31
+ return task;
32
+ }
33
+ describe("suggestSafeOrder", () => {
34
+ test("returns empty result for empty input", () => {
35
+ const result = suggestSafeOrder([]);
36
+ expect(result.orderedTasks).toEqual([]);
37
+ expect(result.phases).toEqual([]);
38
+ expect(result.summary.totalTasks).toBe(0);
39
+ });
40
+ test("returns empty for all completed tasks", () => {
41
+ const tasks = [createTask("A", { status: "completed" })];
42
+ const result = suggestSafeOrder(tasks);
43
+ expect(result.orderedTasks).toEqual([]);
44
+ });
45
+ test("orders by tech level (schema before backend)", () => {
46
+ const tasks = [
47
+ createTask("backend-task", { areas: ["backend"] }),
48
+ createTask("schema-task", { areas: ["schema"] }),
49
+ ];
50
+ const result = suggestSafeOrder(tasks);
51
+ expect(result.orderedTasks[0].id).toBe("schema-task");
52
+ expect(result.orderedTasks[1].id).toBe("backend-task");
53
+ });
54
+ test("orders by tech level (full chain)", () => {
55
+ const tasks = [
56
+ createTask("frontend", { areas: ["frontend"] }),
57
+ createTask("backend", { areas: ["backend"] }),
58
+ createTask("schema", { areas: ["schema"] }),
59
+ createTask("test", { areas: ["test"] }),
60
+ createTask("devops", { areas: ["devops"] }),
61
+ ];
62
+ const result = suggestSafeOrder(tasks);
63
+ const order = result.orderedTasks.map((t) => t.id);
64
+ expect(order).toEqual(["schema", "devops", "backend", "frontend", "test"]);
65
+ });
66
+ test("orders by risk level within same tech level", () => {
67
+ const tasks = [
68
+ createTask("high-risk", { areas: ["backend"], riskLevel: "high" }),
69
+ createTask("low-risk", { areas: ["backend"], riskLevel: "low" }),
70
+ createTask("medium-risk", { areas: ["backend"], riskLevel: "medium" }),
71
+ ];
72
+ const result = suggestSafeOrder(tasks);
73
+ const order = result.orderedTasks.map((t) => t.id);
74
+ expect(order).toEqual(["low-risk", "medium-risk", "high-risk"]);
75
+ });
76
+ test("puts breaking changes last within same tech/risk level", () => {
77
+ const tasks = [
78
+ createTask("breaking", { areas: ["backend"], riskLevel: "low", hasBreakingChange: true }),
79
+ createTask("safe", { areas: ["backend"], riskLevel: "low", hasBreakingChange: false }),
80
+ ];
81
+ const result = suggestSafeOrder(tasks);
82
+ expect(result.orderedTasks[0].id).toBe("safe");
83
+ expect(result.orderedTasks[1].id).toBe("breaking");
84
+ });
85
+ test("uses priority as tiebreaker", () => {
86
+ const tasks = [
87
+ createTask("low-priority", { areas: ["backend"], priority: "low" }),
88
+ createTask("high-priority", { areas: ["backend"], priority: "high" }),
89
+ ];
90
+ const result = suggestSafeOrder(tasks);
91
+ expect(result.orderedTasks[0].id).toBe("high-priority");
92
+ expect(result.orderedTasks[1].id).toBe("low-priority");
93
+ });
94
+ test("groups tasks into phases by tech level", () => {
95
+ const tasks = [
96
+ createTask("schema1", { areas: ["schema"] }),
97
+ createTask("schema2", { areas: ["schema"] }),
98
+ createTask("backend1", { areas: ["backend"] }),
99
+ ];
100
+ const result = suggestSafeOrder(tasks);
101
+ expect(result.phases.length).toBe(2);
102
+ expect(result.phases[0].primaryArea).toBe("schema");
103
+ expect(result.phases[0].tasks.length).toBe(2);
104
+ expect(result.phases[1].primaryArea).toBe("backend");
105
+ expect(result.phases[1].tasks.length).toBe(1);
106
+ });
107
+ test("calculates summary statistics correctly", () => {
108
+ const tasks = [
109
+ createTask("t1", { areas: ["backend"], hasBreakingChange: true }),
110
+ createTask("t2", { areas: ["backend"], riskLevel: "high" }),
111
+ createTask("t3", { areas: ["backend"], riskLevel: "critical" }),
112
+ createTask("t4", { areas: ["backend"] }),
113
+ ];
114
+ const result = suggestSafeOrder(tasks);
115
+ expect(result.summary.totalTasks).toBe(4);
116
+ expect(result.summary.breakingChanges).toBe(1);
117
+ expect(result.summary.highRiskCount).toBe(2);
118
+ });
119
+ });
120
+ describe("findBreakingChanges", () => {
121
+ test("returns empty for no breaking changes", () => {
122
+ const tasks = [
123
+ createTask("t1", { areas: ["backend"], hasBreakingChange: false }),
124
+ createTask("t2", { areas: ["backend"] }),
125
+ ];
126
+ const result = findBreakingChanges(tasks);
127
+ expect(result).toEqual([]);
128
+ });
129
+ test("finds tasks with breaking changes", () => {
130
+ const tasks = [
131
+ createTask("t1", { areas: ["backend"], hasBreakingChange: true }),
132
+ createTask("t2", { areas: ["backend"], hasBreakingChange: false }),
133
+ createTask("t3", { areas: ["schema"], hasBreakingChange: true }),
134
+ ];
135
+ const result = findBreakingChanges(tasks);
136
+ expect(result.length).toBe(2);
137
+ expect(result.map((t) => t.id).sort()).toEqual(["t1", "t3"]);
138
+ });
139
+ test("excludes completed tasks", () => {
140
+ const tasks = [
141
+ createTask("t1", { status: "completed", areas: ["backend"], hasBreakingChange: true }),
142
+ createTask("t2", { status: "pending", areas: ["backend"], hasBreakingChange: true }),
143
+ ];
144
+ const result = findBreakingChanges(tasks);
145
+ expect(result.length).toBe(1);
146
+ expect(result[0].id).toBe("t2");
147
+ });
148
+ });
149
+ describe("findHighRiskTasks", () => {
150
+ test("returns empty for no high-risk tasks", () => {
151
+ const tasks = [
152
+ createTask("t1", { areas: ["backend"], riskLevel: "low" }),
153
+ createTask("t2", { areas: ["backend"], riskLevel: "medium" }),
154
+ ];
155
+ const result = findHighRiskTasks(tasks);
156
+ expect(result).toEqual([]);
157
+ });
158
+ test("finds high and critical risk tasks", () => {
159
+ const tasks = [
160
+ createTask("t1", { areas: ["backend"], riskLevel: "low" }),
161
+ createTask("t2", { areas: ["backend"], riskLevel: "high" }),
162
+ createTask("t3", { areas: ["backend"], riskLevel: "critical" }),
163
+ ];
164
+ const result = findHighRiskTasks(tasks);
165
+ expect(result.length).toBe(2);
166
+ expect(result.map((t) => t.id).sort()).toEqual(["t2", "t3"]);
167
+ });
168
+ });
169
+ describe("groupByTechArea", () => {
170
+ test("returns all areas with empty arrays for empty input", () => {
171
+ const result = groupByTechArea([]);
172
+ expect(result.get("schema")).toEqual([]);
173
+ expect(result.get("backend")).toEqual([]);
174
+ expect(result.get("frontend")).toEqual([]);
175
+ });
176
+ test("groups tasks by their tech areas", () => {
177
+ const tasks = [
178
+ createTask("t1", { areas: ["backend"] }),
179
+ createTask("t2", { areas: ["backend"] }),
180
+ createTask("t3", { areas: ["frontend"] }),
181
+ ];
182
+ const result = groupByTechArea(tasks);
183
+ expect(result.get("backend").length).toBe(2);
184
+ expect(result.get("frontend").length).toBe(1);
185
+ expect(result.get("schema").length).toBe(0);
186
+ });
187
+ test("task appears in multiple groups if it spans multiple areas", () => {
188
+ const tasks = [
189
+ createTask("fullstack", { areas: ["backend", "frontend"] }),
190
+ ];
191
+ const result = groupByTechArea(tasks);
192
+ expect(result.get("backend").length).toBe(1);
193
+ expect(result.get("frontend").length).toBe(1);
194
+ expect(result.get("backend")[0].id).toBe("fullstack");
195
+ });
196
+ test("defaults to backend if no area specified", () => {
197
+ const tasks = [createTask("t1", {})];
198
+ const result = groupByTechArea(tasks);
199
+ expect(result.get("backend").length).toBe(1);
200
+ });
201
+ test("excludes completed tasks", () => {
202
+ const tasks = [
203
+ createTask("t1", { status: "completed", areas: ["backend"] }),
204
+ createTask("t2", { status: "pending", areas: ["backend"] }),
205
+ ];
206
+ const result = groupByTechArea(tasks);
207
+ expect(result.get("backend").length).toBe(1);
208
+ });
209
+ });
210
+ describe("getComplexitySummary", () => {
211
+ test("returns empty summary for no tasks", () => {
212
+ const result = getComplexitySummary([]);
213
+ expect(result.distribution).toEqual({ low: 0, medium: 0, high: 0 });
214
+ expect(result.needsBreakdown).toEqual([]);
215
+ expect(result.averageScore).toBe(0);
216
+ expect(result.unanalyzed).toEqual([]);
217
+ });
218
+ test("calculates distribution correctly", () => {
219
+ const tasks = [
220
+ createTask("t1", { complexityScore: 2 }), // low
221
+ createTask("t2", { complexityScore: 3 }), // low
222
+ createTask("t3", { complexityScore: 5 }), // medium
223
+ createTask("t4", { complexityScore: 8 }), // high
224
+ ];
225
+ const result = getComplexitySummary(tasks);
226
+ expect(result.distribution.low).toBe(2);
227
+ expect(result.distribution.medium).toBe(1);
228
+ expect(result.distribution.high).toBe(1);
229
+ });
230
+ test("identifies tasks needing breakdown (score >= 7)", () => {
231
+ const tasks = [
232
+ createTask("t1", { complexityScore: 6 }),
233
+ createTask("t2", { complexityScore: 7 }),
234
+ createTask("t3", { complexityScore: 9 }),
235
+ ];
236
+ const result = getComplexitySummary(tasks);
237
+ expect(result.needsBreakdown.length).toBe(2);
238
+ expect(result.needsBreakdown.map((t) => t.id).sort()).toEqual(["t2", "t3"]);
239
+ });
240
+ test("calculates average score correctly", () => {
241
+ const tasks = [
242
+ createTask("t1", { complexityScore: 2 }),
243
+ createTask("t2", { complexityScore: 6 }),
244
+ createTask("t3", { complexityScore: 10 }),
245
+ ];
246
+ const result = getComplexitySummary(tasks);
247
+ expect(result.averageScore).toBe(6); // (2+6+10)/3
248
+ });
249
+ test("identifies unanalyzed tasks", () => {
250
+ const tasks = [
251
+ createTask("analyzed", { complexityScore: 5 }),
252
+ createTask("not-analyzed", {}),
253
+ ];
254
+ const result = getComplexitySummary(tasks);
255
+ expect(result.unanalyzed.length).toBe(1);
256
+ expect(result.unanalyzed[0].id).toBe("not-analyzed");
257
+ });
258
+ test("excludes completed tasks", () => {
259
+ const tasks = [
260
+ createTask("t1", { status: "completed", complexityScore: 10 }),
261
+ createTask("t2", { status: "pending", complexityScore: 5 }),
262
+ ];
263
+ const result = getComplexitySummary(tasks);
264
+ expect(result.distribution.high).toBe(0);
265
+ expect(result.distribution.medium).toBe(1);
266
+ });
267
+ });
268
+ describe("getTechStackSummary", () => {
269
+ test("returns empty summary for no tasks", () => {
270
+ const result = getTechStackSummary([]);
271
+ expect(result.areaCounts.schema).toBe(0);
272
+ expect(result.breakingChanges).toEqual([]);
273
+ expect(result.unanalyzed).toEqual([]);
274
+ });
275
+ test("counts tasks by area correctly", () => {
276
+ const tasks = [
277
+ createTask("t1", { areas: ["backend"] }),
278
+ createTask("t2", { areas: ["backend", "frontend"] }),
279
+ createTask("t3", { areas: ["schema"] }),
280
+ ];
281
+ const result = getTechStackSummary(tasks);
282
+ expect(result.areaCounts.backend).toBe(2);
283
+ expect(result.areaCounts.frontend).toBe(1);
284
+ expect(result.areaCounts.schema).toBe(1);
285
+ });
286
+ test("tracks risk distribution", () => {
287
+ const tasks = [
288
+ createTask("t1", { areas: ["backend"], riskLevel: "low" }),
289
+ createTask("t2", { areas: ["backend"], riskLevel: "medium" }),
290
+ createTask("t3", { areas: ["backend"], riskLevel: "high" }),
291
+ ];
292
+ const result = getTechStackSummary(tasks);
293
+ expect(result.riskDistribution.low).toBe(1);
294
+ expect(result.riskDistribution.medium).toBe(1);
295
+ expect(result.riskDistribution.high).toBe(1);
296
+ });
297
+ test("identifies breaking changes", () => {
298
+ const tasks = [
299
+ createTask("t1", { areas: ["backend"], hasBreakingChange: true }),
300
+ createTask("t2", { areas: ["backend"], hasBreakingChange: false }),
301
+ ];
302
+ const result = getTechStackSummary(tasks);
303
+ expect(result.breakingChanges.length).toBe(1);
304
+ expect(result.breakingChanges[0].id).toBe("t1");
305
+ });
306
+ test("identifies unanalyzed tasks", () => {
307
+ const tasks = [
308
+ createTask("analyzed", { areas: ["backend"] }),
309
+ createTask("not-analyzed", {}),
310
+ ];
311
+ const result = getTechStackSummary(tasks);
312
+ expect(result.unanalyzed.length).toBe(1);
313
+ expect(result.unanalyzed[0].id).toBe("not-analyzed");
314
+ });
315
+ });
316
+ describe("suggestSubtaskCount", () => {
317
+ test("returns 0 for very low complexity (1-2)", () => {
318
+ expect(suggestSubtaskCount(1)).toBe(0);
319
+ expect(suggestSubtaskCount(2)).toBe(0);
320
+ });
321
+ test("returns 2 for low complexity (3-4)", () => {
322
+ expect(suggestSubtaskCount(3)).toBe(2);
323
+ expect(suggestSubtaskCount(4)).toBe(2);
324
+ });
325
+ test("returns 3-4 for medium complexity (5-6)", () => {
326
+ expect(suggestSubtaskCount(5)).toBe(4);
327
+ expect(suggestSubtaskCount(6)).toBe(4);
328
+ });
329
+ test("returns 5-6 for high complexity (7-8)", () => {
330
+ expect(suggestSubtaskCount(7)).toBe(6);
331
+ expect(suggestSubtaskCount(8)).toBe(6);
332
+ });
333
+ test("returns 7-10 for very high complexity (9-10)", () => {
334
+ expect(suggestSubtaskCount(9)).toBe(9);
335
+ expect(suggestSubtaskCount(10)).toBe(10);
336
+ });
337
+ });
338
+ //# sourceMappingURL=tech-analysis.test.js.map