@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
|
@@ -102,35 +102,48 @@ function calculateProjectDuration(taskMap: Map<string, CPMTask>): number {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
|
-
*
|
|
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
|
|
108
|
-
const
|
|
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
|
-
|
|
124
|
+
const successors = successorIndex.get(depId);
|
|
125
|
+
if (successors) {
|
|
126
|
+
successors.push(cpmTask);
|
|
127
|
+
}
|
|
114
128
|
}
|
|
115
129
|
}
|
|
116
130
|
|
|
117
|
-
return
|
|
131
|
+
return successorIndex;
|
|
118
132
|
}
|
|
119
133
|
|
|
120
134
|
/**
|
|
121
|
-
* Find
|
|
135
|
+
* Find tasks that have successors (are dependencies of other tasks)
|
|
122
136
|
*/
|
|
123
|
-
function
|
|
124
|
-
const
|
|
137
|
+
function findTasksWithSuccessors(successorIndex: Map<string, CPMTask[]>): Set<string> {
|
|
138
|
+
const tasksWithSuccessors = new Set<string>();
|
|
125
139
|
|
|
126
|
-
for (const
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
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(
|
|
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 =
|
|
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(
|
|
193
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
+
}
|
package/src/algorithms/index.ts
CHANGED
|
@@ -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";
|