@task-mcp/shared 1.0.3 → 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.
Files changed (88) 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 +24 -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/response-format.d.ts +79 -0
  32. package/dist/schemas/response-format.d.ts.map +1 -0
  33. package/dist/schemas/response-format.js +17 -0
  34. package/dist/schemas/response-format.js.map +1 -0
  35. package/dist/schemas/task.d.ts +57 -0
  36. package/dist/schemas/task.d.ts.map +1 -1
  37. package/dist/schemas/task.js +34 -0
  38. package/dist/schemas/task.js.map +1 -1
  39. package/dist/utils/date.d.ts.map +1 -1
  40. package/dist/utils/date.js +17 -2
  41. package/dist/utils/date.js.map +1 -1
  42. package/dist/utils/hierarchy.d.ts +75 -0
  43. package/dist/utils/hierarchy.d.ts.map +1 -0
  44. package/dist/utils/hierarchy.js +179 -0
  45. package/dist/utils/hierarchy.js.map +1 -0
  46. package/dist/utils/id.d.ts +51 -1
  47. package/dist/utils/id.d.ts.map +1 -1
  48. package/dist/utils/id.js +124 -4
  49. package/dist/utils/id.js.map +1 -1
  50. package/dist/utils/id.test.d.ts +2 -0
  51. package/dist/utils/id.test.d.ts.map +1 -0
  52. package/dist/utils/id.test.js +228 -0
  53. package/dist/utils/id.test.js.map +1 -0
  54. package/dist/utils/index.d.ts +4 -2
  55. package/dist/utils/index.d.ts.map +1 -1
  56. package/dist/utils/index.js +7 -2
  57. package/dist/utils/index.js.map +1 -1
  58. package/dist/utils/natural-language.d.ts +45 -0
  59. package/dist/utils/natural-language.d.ts.map +1 -1
  60. package/dist/utils/natural-language.js +86 -0
  61. package/dist/utils/natural-language.js.map +1 -1
  62. package/dist/utils/projection.d.ts +65 -0
  63. package/dist/utils/projection.d.ts.map +1 -0
  64. package/dist/utils/projection.js +181 -0
  65. package/dist/utils/projection.js.map +1 -0
  66. package/dist/utils/projection.test.d.ts +2 -0
  67. package/dist/utils/projection.test.d.ts.map +1 -0
  68. package/dist/utils/projection.test.js +400 -0
  69. package/dist/utils/projection.test.js.map +1 -0
  70. package/package.json +1 -1
  71. package/src/algorithms/critical-path.ts +56 -24
  72. package/src/algorithms/dependency-integrity.ts +270 -0
  73. package/src/algorithms/index.ts +28 -0
  74. package/src/algorithms/tech-analysis.test.ts +413 -0
  75. package/src/algorithms/tech-analysis.ts +412 -0
  76. package/src/algorithms/topological-sort.ts +66 -9
  77. package/src/schemas/inbox.ts +32 -0
  78. package/src/schemas/index.ts +31 -0
  79. package/src/schemas/response-format.ts +108 -0
  80. package/src/schemas/task.ts +50 -0
  81. package/src/utils/date.ts +18 -2
  82. package/src/utils/hierarchy.ts +224 -0
  83. package/src/utils/id.test.ts +281 -0
  84. package/src/utils/id.ts +139 -4
  85. package/src/utils/index.ts +46 -2
  86. package/src/utils/natural-language.ts +113 -0
  87. package/src/utils/projection.test.ts +505 -0
  88. package/src/utils/projection.ts +251 -0
@@ -102,35 +102,48 @@ function calculateProjectDuration(taskMap: Map<string, CPMTask>): number {
102
102
  }
103
103
 
104
104
  /**
105
- * Find tasks that have successors (are dependencies of other tasks)
105
+ * Build successor index: maps taskId -> list of tasks that depend on it
106
+ * This is O(n * d) where d is average dependencies, done once upfront
107
+ * Allows O(1) successor lookup instead of O(n) per task
106
108
  */
107
- function findTasksWithSuccessors(sortedTasks: Task[]): Set<string> {
108
- const tasksWithSuccessors = new Set<string>();
109
+ function buildSuccessorIndex(sortedTasks: Task[], taskMap: Map<string, CPMTask>): Map<string, CPMTask[]> {
110
+ const successorIndex = new Map<string, CPMTask[]>();
111
+
112
+ // Initialize empty arrays for all tasks
113
+ for (const task of sortedTasks) {
114
+ successorIndex.set(task.id, []);
115
+ }
109
116
 
117
+ // Build the index: if task A is blocked_by task B, then A is a successor of B
110
118
  for (const task of sortedTasks) {
111
119
  const deps = getBlockedByDeps(task);
120
+ const cpmTask = taskMap.get(task.id);
121
+ if (!cpmTask) continue;
122
+
112
123
  for (const depId of deps) {
113
- tasksWithSuccessors.add(depId);
124
+ const successors = successorIndex.get(depId);
125
+ if (successors) {
126
+ successors.push(cpmTask);
127
+ }
114
128
  }
115
129
  }
116
130
 
117
- return tasksWithSuccessors;
131
+ return successorIndex;
118
132
  }
119
133
 
120
134
  /**
121
- * Find successor tasks (tasks that depend on a given task)
135
+ * Find tasks that have successors (are dependencies of other tasks)
122
136
  */
123
- function findSuccessors(taskId: string, taskMap: Map<string, CPMTask>): CPMTask[] {
124
- const successors: CPMTask[] = [];
137
+ function findTasksWithSuccessors(successorIndex: Map<string, CPMTask[]>): Set<string> {
138
+ const tasksWithSuccessors = new Set<string>();
125
139
 
126
- for (const other of taskMap.values()) {
127
- const otherDeps = getBlockedByDeps(other);
128
- if (otherDeps.includes(taskId)) {
129
- successors.push(other);
140
+ for (const [taskId, successors] of successorIndex) {
141
+ if (successors.length > 0) {
142
+ tasksWithSuccessors.add(taskId);
130
143
  }
131
144
  }
132
145
 
133
- return successors;
146
+ return tasksWithSuccessors;
134
147
  }
135
148
 
136
149
  /**
@@ -141,9 +154,10 @@ function findSuccessors(taskId: string, taskMap: Map<string, CPMTask>): CPMTask[
141
154
  function backwardPass(
142
155
  sortedTasks: Task[],
143
156
  taskMap: Map<string, CPMTask>,
157
+ successorIndex: Map<string, CPMTask[]>,
144
158
  projectDuration: number
145
159
  ): void {
146
- const tasksWithSuccessors = findTasksWithSuccessors(sortedTasks);
160
+ const tasksWithSuccessors = findTasksWithSuccessors(successorIndex);
147
161
 
148
162
  // Initialize end tasks (tasks with no successors)
149
163
  for (const task of taskMap.values()) {
@@ -162,7 +176,7 @@ function backwardPass(
162
176
  if (!cpmTask) continue;
163
177
 
164
178
  const duration = getTaskDuration(task);
165
- const successors = findSuccessors(task.id, taskMap);
179
+ const successors = successorIndex.get(task.id) ?? [];
166
180
 
167
181
  if (successors.length > 0) {
168
182
  // LF = min(LS of all successors)
@@ -187,14 +201,29 @@ function calculateSlackAndCritical(taskMap: Map<string, CPMTask>): void {
187
201
  }
188
202
 
189
203
  /**
190
- * Count dependents for bottleneck detection
204
+ * Count dependents for bottleneck detection using successor index
205
+ * Uses dynamic programming: dependentCount = direct successors + their dependentCounts
206
+ * O(n) instead of O(n³) with findDependents
191
207
  */
192
- function countDependents(sortedTasks: Task[], activeTasks: Task[], taskMap: Map<string, CPMTask>): void {
193
- for (const task of sortedTasks) {
208
+ function countDependents(
209
+ sortedTasks: Task[],
210
+ taskMap: Map<string, CPMTask>,
211
+ successorIndex: Map<string, CPMTask[]>
212
+ ): void {
213
+ // Process in reverse topological order so we compute children before parents
214
+ const reverseSorted = [...sortedTasks].reverse();
215
+
216
+ for (const task of reverseSorted) {
194
217
  const cpmTask = taskMap.get(task.id);
195
- if (cpmTask) {
196
- cpmTask.dependentCount = findDependents(activeTasks, task.id).length;
218
+ if (!cpmTask) continue;
219
+
220
+ const successors = successorIndex.get(task.id) ?? [];
221
+ // dependentCount = number of direct successors + sum of their dependentCounts
222
+ let count = successors.length;
223
+ for (const successor of successors) {
224
+ count += successor.dependentCount;
197
225
  }
226
+ cpmTask.dependentCount = count;
198
227
  }
199
228
  }
200
229
 
@@ -248,20 +277,23 @@ export function criticalPathAnalysis(tasks: Task[]): CPMResult {
248
277
  // Initialize CPM tasks
249
278
  const taskMap = initializeCPMTasks(sortedTasks);
250
279
 
280
+ // Build successor index once (O(n*d)) for O(1) lookups
281
+ const successorIndex = buildSuccessorIndex(sortedTasks, taskMap);
282
+
251
283
  // Forward pass: Calculate earliest start/finish
252
284
  forwardPass(sortedTasks, taskMap);
253
285
 
254
286
  // Calculate project duration
255
287
  const projectDuration = calculateProjectDuration(taskMap);
256
288
 
257
- // Backward pass: Calculate latest start/finish
258
- backwardPass(sortedTasks, taskMap, projectDuration);
289
+ // Backward pass: Calculate latest start/finish (uses successorIndex)
290
+ backwardPass(sortedTasks, taskMap, successorIndex, projectDuration);
259
291
 
260
292
  // Calculate slack and mark critical tasks
261
293
  calculateSlackAndCritical(taskMap);
262
294
 
263
- // Count dependents for bottleneck detection
264
- countDependents(sortedTasks, activeTasks, taskMap);
295
+ // Count dependents for bottleneck detection (uses successorIndex, O(n))
296
+ countDependents(sortedTasks, taskMap, successorIndex);
265
297
 
266
298
  // Extract critical path
267
299
  const criticalPath = extractCriticalPath(sortedTasks, taskMap);
@@ -0,0 +1,270 @@
1
+ import type { Task } from "../schemas/task.js";
2
+ import { wouldCreateCycle } from "./topological-sort.js";
3
+
4
+ /**
5
+ * Error codes for dependency validation failures
6
+ */
7
+ export enum DependencyErrorCode {
8
+ SELF_DEPENDENCY = "SELF_DEPENDENCY",
9
+ TASK_NOT_FOUND = "TASK_NOT_FOUND",
10
+ BLOCKER_NOT_FOUND = "BLOCKER_NOT_FOUND",
11
+ CIRCULAR_DEPENDENCY = "CIRCULAR_DEPENDENCY",
12
+ DUPLICATE_DEPENDENCY = "DUPLICATE_DEPENDENCY",
13
+ }
14
+
15
+ /**
16
+ * Result of dependency validation
17
+ */
18
+ export interface DependencyValidationResult {
19
+ valid: boolean;
20
+ errorCode?: DependencyErrorCode;
21
+ errorMessage?: string;
22
+ suggestion?: string;
23
+ }
24
+
25
+ /**
26
+ * Options for dependency validation
27
+ */
28
+ export interface ValidateDependencyOptions {
29
+ taskId: string;
30
+ blockedBy: string;
31
+ tasks: Task[];
32
+ checkDuplicates?: boolean;
33
+ }
34
+
35
+ /**
36
+ * Validates if a dependency can be added between two tasks
37
+ */
38
+ export function validateDependency(
39
+ options: ValidateDependencyOptions
40
+ ): DependencyValidationResult {
41
+ const { taskId, blockedBy, tasks, checkDuplicates = true } = options;
42
+
43
+ // 1. Self-dependency check
44
+ if (taskId === blockedBy) {
45
+ return {
46
+ valid: false,
47
+ errorCode: DependencyErrorCode.SELF_DEPENDENCY,
48
+ errorMessage: `Task "${taskId}" cannot depend on itself`,
49
+ suggestion: "Specify a different blocker task",
50
+ };
51
+ }
52
+
53
+ // 2. Task existence check
54
+ const task = tasks.find((t) => t.id === taskId);
55
+ if (!task) {
56
+ return {
57
+ valid: false,
58
+ errorCode: DependencyErrorCode.TASK_NOT_FOUND,
59
+ errorMessage: `Task not found: "${taskId}"`,
60
+ suggestion: "Use task_list to find available tasks",
61
+ };
62
+ }
63
+
64
+ // 3. Blocker task existence check
65
+ const blocker = tasks.find((t) => t.id === blockedBy);
66
+ if (!blocker) {
67
+ return {
68
+ valid: false,
69
+ errorCode: DependencyErrorCode.BLOCKER_NOT_FOUND,
70
+ errorMessage: `Blocking task not found: "${blockedBy}"`,
71
+ suggestion: "Use task_list to find available tasks",
72
+ };
73
+ }
74
+
75
+ // 4. Duplicate dependency check
76
+ if (checkDuplicates) {
77
+ const existingDeps = task.dependencies ?? [];
78
+ const alreadyExists = existingDeps.some(
79
+ (d) => d.taskId === blockedBy && d.type === "blocked_by"
80
+ );
81
+ if (alreadyExists) {
82
+ return {
83
+ valid: false,
84
+ errorCode: DependencyErrorCode.DUPLICATE_DEPENDENCY,
85
+ errorMessage: `"${task.title}" is already blocked by "${blocker.title}"`,
86
+ suggestion: "This dependency already exists",
87
+ };
88
+ }
89
+ }
90
+
91
+ // 5. Circular dependency check
92
+ if (wouldCreateCycle(tasks, taskId, blockedBy)) {
93
+ return {
94
+ valid: false,
95
+ errorCode: DependencyErrorCode.CIRCULAR_DEPENDENCY,
96
+ errorMessage: "Adding this dependency would create a cycle",
97
+ suggestion: `"${blocker.title}" depends on "${task.title}". Use dependency_remove to break existing dependencies.`,
98
+ };
99
+ }
100
+
101
+ return { valid: true };
102
+ }
103
+
104
+ /**
105
+ * Invalid dependency reference
106
+ */
107
+ export interface InvalidDependencyReference {
108
+ taskId: string;
109
+ taskTitle: string;
110
+ invalidDependencyId: string;
111
+ type: "blocked_by" | "blocks" | "related";
112
+ }
113
+
114
+ /**
115
+ * Find all invalid dependency references (orphaned references)
116
+ */
117
+ export function findInvalidDependencies(
118
+ tasks: Task[]
119
+ ): InvalidDependencyReference[] {
120
+ const taskIds = new Set(tasks.map((t) => t.id));
121
+ const invalid: InvalidDependencyReference[] = [];
122
+
123
+ for (const task of tasks) {
124
+ const deps = task.dependencies ?? [];
125
+ for (const dep of deps) {
126
+ if (!taskIds.has(dep.taskId)) {
127
+ invalid.push({
128
+ taskId: task.id,
129
+ taskTitle: task.title,
130
+ invalidDependencyId: dep.taskId,
131
+ type: dep.type,
132
+ });
133
+ }
134
+ }
135
+ }
136
+
137
+ return invalid;
138
+ }
139
+
140
+ /**
141
+ * Find tasks with self-dependencies
142
+ */
143
+ export function findSelfDependencies(tasks: Task[]): string[] {
144
+ const selfDeps: string[] = [];
145
+
146
+ for (const task of tasks) {
147
+ const deps = task.dependencies ?? [];
148
+ if (deps.some((d) => d.taskId === task.id)) {
149
+ selfDeps.push(task.id);
150
+ }
151
+ }
152
+
153
+ return selfDeps;
154
+ }
155
+
156
+ /**
157
+ * Detect all circular dependency chains
158
+ */
159
+ export function detectCircularDependencies(tasks: Task[]): string[][] {
160
+ const taskMap = new Map(tasks.map((t) => [t.id, t]));
161
+ const visited = new Set<string>();
162
+ const recStack = new Set<string>();
163
+ const cycles: string[][] = [];
164
+
165
+ function dfs(taskId: string, path: string[]): void {
166
+ visited.add(taskId);
167
+ recStack.add(taskId);
168
+ path.push(taskId);
169
+
170
+ const task = taskMap.get(taskId);
171
+ if (!task) return;
172
+
173
+ const deps = (task.dependencies ?? [])
174
+ .filter((d) => d.type === "blocked_by")
175
+ .map((d) => d.taskId);
176
+
177
+ for (const depId of deps) {
178
+ if (!taskMap.has(depId)) continue;
179
+
180
+ if (!visited.has(depId)) {
181
+ dfs(depId, [...path]);
182
+ } else if (recStack.has(depId)) {
183
+ const cycleStart = path.indexOf(depId);
184
+ if (cycleStart !== -1) {
185
+ const cycle = path.slice(cycleStart);
186
+ const cycleKey = [...cycle].sort().join(",");
187
+ const exists = cycles.some(
188
+ (c) => [...c].sort().join(",") === cycleKey
189
+ );
190
+ if (!exists) {
191
+ cycles.push(cycle);
192
+ }
193
+ }
194
+ }
195
+ }
196
+
197
+ recStack.delete(taskId);
198
+ }
199
+
200
+ for (const task of tasks) {
201
+ if (!visited.has(task.id)) {
202
+ dfs(task.id, []);
203
+ }
204
+ }
205
+
206
+ return cycles;
207
+ }
208
+
209
+ /**
210
+ * Comprehensive dependency integrity report
211
+ */
212
+ export interface DependencyIntegrityReport {
213
+ valid: boolean;
214
+ totalTasks: number;
215
+ totalDependencies: number;
216
+ issues: {
217
+ selfDependencies: string[];
218
+ invalidReferences: InvalidDependencyReference[];
219
+ circularDependencies: string[][];
220
+ };
221
+ summary: string;
222
+ }
223
+
224
+ /**
225
+ * Check overall dependency integrity of a project
226
+ */
227
+ export function checkDependencyIntegrity(
228
+ tasks: Task[]
229
+ ): DependencyIntegrityReport {
230
+ const selfDeps = findSelfDependencies(tasks);
231
+ const invalidRefs = findInvalidDependencies(tasks);
232
+ const cycles = detectCircularDependencies(tasks);
233
+
234
+ const totalDependencies = tasks.reduce(
235
+ (sum, t) => sum + (t.dependencies?.length ?? 0),
236
+ 0
237
+ );
238
+
239
+ const issueCount = selfDeps.length + invalidRefs.length + cycles.length;
240
+ const valid = issueCount === 0;
241
+
242
+ let summary: string;
243
+ if (valid) {
244
+ summary = `All ${totalDependencies} dependencies across ${tasks.length} tasks are valid.`;
245
+ } else {
246
+ const parts: string[] = [];
247
+ if (selfDeps.length > 0) {
248
+ parts.push(`${selfDeps.length} self-dependencies`);
249
+ }
250
+ if (invalidRefs.length > 0) {
251
+ parts.push(`${invalidRefs.length} invalid references`);
252
+ }
253
+ if (cycles.length > 0) {
254
+ parts.push(`${cycles.length} circular dependency chains`);
255
+ }
256
+ summary = `Found ${issueCount} issue(s): ${parts.join(", ")}.`;
257
+ }
258
+
259
+ return {
260
+ valid,
261
+ totalTasks: tasks.length,
262
+ totalDependencies,
263
+ issues: {
264
+ selfDependencies: selfDeps,
265
+ invalidReferences: invalidRefs,
266
+ circularDependencies: cycles,
267
+ },
268
+ summary,
269
+ };
270
+ }
@@ -15,3 +15,31 @@ export {
15
15
  type CPMTask,
16
16
  type CPMResult,
17
17
  } from "./critical-path.js";
18
+
19
+ export {
20
+ suggestSafeOrder,
21
+ findBreakingChanges,
22
+ findHighRiskTasks,
23
+ groupByTechArea,
24
+ getComplexitySummary,
25
+ getTechStackSummary,
26
+ suggestSubtaskCount,
27
+ type SafeOrderResult,
28
+ type SafeOrderPhase,
29
+ type ComplexityDistribution,
30
+ type ComplexitySummary,
31
+ type TechStackSummary,
32
+ } from "./tech-analysis.js";
33
+
34
+ export {
35
+ validateDependency,
36
+ findInvalidDependencies,
37
+ findSelfDependencies,
38
+ detectCircularDependencies,
39
+ checkDependencyIntegrity,
40
+ DependencyErrorCode,
41
+ type DependencyValidationResult,
42
+ type ValidateDependencyOptions,
43
+ type InvalidDependencyReference,
44
+ type DependencyIntegrityReport,
45
+ } from "./dependency-integrity.js";