@h-rig/dependency-graph-plugin 0.0.6-alpha.157 → 0.0.6-alpha.158
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/src/analysis/blockers.d.ts +35 -0
- package/dist/src/analysis/blockers.js +355 -0
- package/dist/src/{graph-model → analysis}/dependencyGraph.d.ts +1 -2
- package/dist/src/{graph.js → analysis/dependencyGraph.js} +256 -128
- package/dist/src/{graph.d.ts → analysis/graph.d.ts} +3 -4
- package/dist/src/analysis/graph.js +944 -0
- package/dist/src/analysis/rigSelectors.d.ts +26 -0
- package/dist/src/analysis/rigSelectors.js +172 -0
- package/dist/src/analysis/rollupModel.js +401 -0
- package/dist/src/{rollups.d.ts → analysis/rollups.d.ts} +2 -4
- package/dist/src/analysis/rollups.js +589 -0
- package/dist/src/{graph-model → analysis}/taskGraphCodes.js +1 -1
- package/dist/src/{graph-model → analysis}/taskGraphLayout.d.ts +2 -2
- package/dist/src/{graph-model → analysis}/taskGraphLayout.js +75 -29
- package/dist/src/analysis/taskGraphPrimitives.d.ts +58 -0
- package/dist/src/analysis/taskGraphPrimitives.js +528 -0
- package/dist/src/index.js +567 -396
- package/dist/src/plugin.d.ts +1 -11
- package/dist/src/plugin.js +567 -396
- package/package.json +3 -6
- package/dist/src/graph-model/dependencyGraph.js +0 -485
- package/dist/src/graph-model/rollups.js +0 -166
- package/dist/src/rollups.js +0 -199
- package/dist/src/taskRanking.d.ts +0 -24
- package/dist/src/taskRanking.js +0 -160
- package/dist/src/taskScore.d.ts +0 -17
- package/dist/src/taskScore.js +0 -49
- package/dist/src/taskSelection.d.ts +0 -33
- package/dist/src/taskSelection.js +0 -181
- /package/dist/src/{graph-model/rollups.d.ts → analysis/rollupModel.d.ts} +0 -0
- /package/dist/src/{graph-model → analysis}/taskGraphCodes.d.ts +0 -0
|
@@ -1,8 +1,212 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// packages/dependency-graph-plugin/src/
|
|
3
|
-
import {
|
|
2
|
+
// packages/dependency-graph-plugin/src/analysis/taskGraphPrimitives.ts
|
|
3
|
+
import {
|
|
4
|
+
OPERATOR_INACTIVE_RUN_STATUSES
|
|
5
|
+
} from "@rig/contracts";
|
|
6
|
+
function isObjectRecord(value) {
|
|
7
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
function readStringList(value) {
|
|
10
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
11
|
+
}
|
|
12
|
+
function unique(values) {
|
|
13
|
+
return Array.from(new Set(values));
|
|
14
|
+
}
|
|
15
|
+
function readTaskMetadataStringList(task, key) {
|
|
16
|
+
const taskRecord = task;
|
|
17
|
+
const topLevel = readStringList(taskRecord[key]);
|
|
18
|
+
if (topLevel.length > 0)
|
|
19
|
+
return topLevel;
|
|
20
|
+
const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
|
|
21
|
+
const metadataList = readStringList(metadata?.[key]);
|
|
22
|
+
if (metadataList.length > 0)
|
|
23
|
+
return metadataList;
|
|
24
|
+
if (key === "dependencies") {
|
|
25
|
+
return readStringList(metadata?.deps);
|
|
26
|
+
}
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
function readTaskBlockingDependencyRefs(task) {
|
|
30
|
+
return readTaskMetadataStringList(task, "dependencies");
|
|
31
|
+
}
|
|
32
|
+
function readTaskSourceIssueId(task) {
|
|
33
|
+
if (typeof task.sourceIssueId === "string" && task.sourceIssueId.length > 0) {
|
|
34
|
+
return task.sourceIssueId;
|
|
35
|
+
}
|
|
36
|
+
const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
|
|
37
|
+
if (typeof metadata?.sourceIssueId === "string" && metadata.sourceIssueId.length > 0) {
|
|
38
|
+
return metadata.sourceIssueId;
|
|
39
|
+
}
|
|
40
|
+
const rigMetadata = isObjectRecord(metadata?._rig) ? metadata._rig : null;
|
|
41
|
+
return typeof rigMetadata?.sourceIssueId === "string" && rigMetadata.sourceIssueId.length > 0 ? rigMetadata.sourceIssueId : null;
|
|
42
|
+
}
|
|
43
|
+
function resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId) {
|
|
44
|
+
if (tasksById.has(ref))
|
|
45
|
+
return ref;
|
|
46
|
+
return taskIdBySourceIssueId.get(ref) ?? taskIdByExternalRef.get(ref) ?? null;
|
|
47
|
+
}
|
|
48
|
+
function buildTaskReferenceIndex(tasks) {
|
|
49
|
+
return {
|
|
50
|
+
tasksById: new Map(tasks.map((task) => [task.id, task])),
|
|
51
|
+
taskIdByExternalRef: new Map(tasks.flatMap((task) => task.externalId ? [[task.externalId, task.id]] : [])),
|
|
52
|
+
taskIdBySourceIssueId: new Map(tasks.flatMap((task) => {
|
|
53
|
+
const sourceIssueId = readTaskSourceIssueId(task);
|
|
54
|
+
return sourceIssueId ? [[sourceIssueId, task.id]] : [];
|
|
55
|
+
}))
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function computeTaskBlockingDepths(tasks) {
|
|
59
|
+
const { tasksById, taskIdByExternalRef, taskIdBySourceIssueId } = buildTaskReferenceIndex(tasks);
|
|
60
|
+
const memo = new Map;
|
|
61
|
+
const visit = (taskId, stack) => {
|
|
62
|
+
const cached = memo.get(taskId);
|
|
63
|
+
if (cached !== undefined)
|
|
64
|
+
return cached;
|
|
65
|
+
if (stack.has(taskId))
|
|
66
|
+
return 0;
|
|
67
|
+
const task = tasksById.get(taskId);
|
|
68
|
+
if (!task)
|
|
69
|
+
return 0;
|
|
70
|
+
stack.add(taskId);
|
|
71
|
+
const blockers = readTaskBlockingDependencyRefs(task).map((ref) => resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId)).filter((ref) => ref !== null && ref !== taskId);
|
|
72
|
+
const depth = blockers.length === 0 ? 0 : Math.max(...blockers.map((blockerId) => visit(blockerId, stack) + 1));
|
|
73
|
+
stack.delete(taskId);
|
|
74
|
+
memo.set(taskId, depth);
|
|
75
|
+
return depth;
|
|
76
|
+
};
|
|
77
|
+
for (const task of tasks) {
|
|
78
|
+
visit(task.id, new Set);
|
|
79
|
+
}
|
|
80
|
+
return memo;
|
|
81
|
+
}
|
|
82
|
+
function isTaskTerminalStatus(status) {
|
|
83
|
+
switch (status) {
|
|
84
|
+
case "closed":
|
|
85
|
+
case "completed":
|
|
86
|
+
case "done":
|
|
87
|
+
case "cancelled":
|
|
88
|
+
case "canceled":
|
|
89
|
+
return true;
|
|
90
|
+
default:
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function isTaskBlockedStatus(status) {
|
|
95
|
+
return status === "blocked";
|
|
96
|
+
}
|
|
97
|
+
function isTaskRunnableStatus(status) {
|
|
98
|
+
if (status === null || status === undefined || status === "")
|
|
99
|
+
return true;
|
|
100
|
+
if (isTaskTerminalStatus(status) || isTaskBlockedStatus(status))
|
|
101
|
+
return false;
|
|
102
|
+
switch (status) {
|
|
103
|
+
case "ready":
|
|
104
|
+
case "open":
|
|
105
|
+
case "failed":
|
|
106
|
+
return true;
|
|
107
|
+
default:
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function computeTaskDependencyBadges(tasks) {
|
|
112
|
+
const index = buildTaskReferenceIndex(tasks);
|
|
113
|
+
const blockingDepths = computeTaskBlockingDepths(tasks);
|
|
114
|
+
const dependencyIdsByTask = new Map;
|
|
115
|
+
const unresolvedRefsByTask = new Map;
|
|
116
|
+
const blocksByTask = new Map;
|
|
117
|
+
for (const task of tasks) {
|
|
118
|
+
const dependencyIds = [];
|
|
119
|
+
const unresolvedRefs = [];
|
|
120
|
+
for (const ref of readTaskBlockingDependencyRefs(task)) {
|
|
121
|
+
const dependencyId = resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
|
|
122
|
+
if (dependencyId && dependencyId !== task.id) {
|
|
123
|
+
dependencyIds.push(dependencyId);
|
|
124
|
+
const blocks = blocksByTask.get(dependencyId);
|
|
125
|
+
if (blocks) {
|
|
126
|
+
blocks.push(task.id);
|
|
127
|
+
} else {
|
|
128
|
+
blocksByTask.set(dependencyId, [task.id]);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
unresolvedRefs.push(ref);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
dependencyIdsByTask.set(task.id, unique(dependencyIds));
|
|
135
|
+
unresolvedRefsByTask.set(task.id, unique(unresolvedRefs));
|
|
136
|
+
}
|
|
137
|
+
const summaries = new Map;
|
|
138
|
+
for (const task of tasks) {
|
|
139
|
+
const dependencyIds = dependencyIdsByTask.get(task.id) ?? [];
|
|
140
|
+
const unresolvedDependencyRefs = unresolvedRefsByTask.get(task.id) ?? [];
|
|
141
|
+
const blockedBy = dependencyIds.filter((dependencyId) => {
|
|
142
|
+
const dependency = index.tasksById.get(dependencyId);
|
|
143
|
+
return dependency ? !isTaskTerminalStatus(dependency.status) : false;
|
|
144
|
+
});
|
|
145
|
+
const blocks = unique(blocksByTask.get(task.id) ?? []);
|
|
146
|
+
const blocked = isTaskBlockedStatus(task.status) || blockedBy.length > 0;
|
|
147
|
+
const ready = isTaskRunnableStatus(task.status) && !blocked;
|
|
148
|
+
const badges = [];
|
|
149
|
+
if (blocked) {
|
|
150
|
+
badges.push({
|
|
151
|
+
kind: "blocked",
|
|
152
|
+
label: blockedBy.length > 0 ? `blocked \xD7${blockedBy.length}` : "blocked",
|
|
153
|
+
description: blockedBy.length > 0 ? `Waiting on ${blockedBy.join(", ")}.` : "Task source marks this task blocked.",
|
|
154
|
+
...blockedBy.length > 0 ? { count: blockedBy.length } : {},
|
|
155
|
+
taskIds: blockedBy
|
|
156
|
+
});
|
|
157
|
+
} else if (ready) {
|
|
158
|
+
badges.push({
|
|
159
|
+
kind: "ready",
|
|
160
|
+
label: "ready",
|
|
161
|
+
description: "No open dependencies block this task."
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
if (dependencyIds.length > 0 || blocks.length > 0 || unresolvedDependencyRefs.length > 0) {
|
|
165
|
+
badges.push({
|
|
166
|
+
kind: "dependency",
|
|
167
|
+
label: `deps ${dependencyIds.length}/${blocks.length}`,
|
|
168
|
+
description: [
|
|
169
|
+
dependencyIds.length > 0 ? `Depends on ${dependencyIds.join(", ")}.` : null,
|
|
170
|
+
blocks.length > 0 ? `Blocks ${blocks.join(", ")}.` : null,
|
|
171
|
+
unresolvedDependencyRefs.length > 0 ? `Unresolved refs: ${unresolvedDependencyRefs.join(", ")}.` : null
|
|
172
|
+
].filter((part) => part !== null).join(" "),
|
|
173
|
+
count: dependencyIds.length + blocks.length,
|
|
174
|
+
taskIds: unique([...dependencyIds, ...blocks])
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
summaries.set(task.id, {
|
|
178
|
+
taskId: task.id,
|
|
179
|
+
blockingDepth: blockingDepths.get(task.id) ?? 0,
|
|
180
|
+
dependencyIds,
|
|
181
|
+
unresolvedDependencyRefs,
|
|
182
|
+
blockedBy,
|
|
183
|
+
blocks,
|
|
184
|
+
blocked,
|
|
185
|
+
ready,
|
|
186
|
+
dependencyCount: dependencyIds.length,
|
|
187
|
+
dependentCount: blocks.length,
|
|
188
|
+
badges
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return summaries;
|
|
192
|
+
}
|
|
193
|
+
var TASK_STATUSES = new Set([
|
|
194
|
+
"draft",
|
|
195
|
+
"open",
|
|
196
|
+
"ready",
|
|
197
|
+
"queued",
|
|
198
|
+
"running",
|
|
199
|
+
"in_progress",
|
|
200
|
+
"under_review",
|
|
201
|
+
"blocked",
|
|
202
|
+
"unknown",
|
|
203
|
+
"completed",
|
|
204
|
+
"failed",
|
|
205
|
+
"cancelled",
|
|
206
|
+
"closed"
|
|
207
|
+
]);
|
|
4
208
|
|
|
5
|
-
// packages/dependency-graph-plugin/src/
|
|
209
|
+
// packages/dependency-graph-plugin/src/analysis/taskGraphCodes.ts
|
|
6
210
|
var TASK_CODE_RE = /^\[([A-Z0-9]+(?:-[A-Z0-9]+)*)\]\s*/;
|
|
7
211
|
function extractTaskCode(title) {
|
|
8
212
|
const match = title.match(TASK_CODE_RE);
|
|
@@ -23,17 +227,7 @@ function stripTaskCode(label) {
|
|
|
23
227
|
return label.replace(TASK_CODE_RE, "");
|
|
24
228
|
}
|
|
25
229
|
|
|
26
|
-
// packages/dependency-graph-plugin/src/
|
|
27
|
-
import {
|
|
28
|
-
buildTaskReferenceIndex,
|
|
29
|
-
readTaskMetadataStringList,
|
|
30
|
-
resolveTaskReference
|
|
31
|
-
} from "@rig/contracts";
|
|
32
|
-
import {
|
|
33
|
-
selectPendingApprovals,
|
|
34
|
-
selectRunsByTask,
|
|
35
|
-
selectUserInputsForRun
|
|
36
|
-
} from "@rig/read-model-plugin";
|
|
230
|
+
// packages/dependency-graph-plugin/src/analysis/taskGraphLayout.ts
|
|
37
231
|
var CARD_WIDTH = 200;
|
|
38
232
|
var CARD_HEIGHT = 110;
|
|
39
233
|
var CELL_V_PAD = 12;
|
|
@@ -52,14 +246,14 @@ var PALETTE = [
|
|
|
52
246
|
{ bg: "#132c35", border: "#1783a6", edge: "#53c4e5" },
|
|
53
247
|
{ bg: "#26310f", border: "#6d9a19", edge: "#a7da42" }
|
|
54
248
|
];
|
|
55
|
-
function
|
|
249
|
+
function isObjectRecord2(value) {
|
|
56
250
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
57
251
|
}
|
|
58
252
|
function readIssueType(task) {
|
|
59
|
-
const metadata =
|
|
253
|
+
const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
|
|
60
254
|
if (typeof metadata?.issueType === "string")
|
|
61
255
|
return metadata.issueType;
|
|
62
|
-
const raw =
|
|
256
|
+
const raw = isObjectRecord2(metadata?.raw) ? metadata.raw : null;
|
|
63
257
|
return typeof raw?.issueType === "string" ? raw.issueType : null;
|
|
64
258
|
}
|
|
65
259
|
function isGraphTask(task) {
|
|
@@ -158,7 +352,7 @@ function computeDepths(ids, edges) {
|
|
|
158
352
|
}
|
|
159
353
|
return memo;
|
|
160
354
|
}
|
|
161
|
-
function buildTaskGraphLayout(
|
|
355
|
+
function buildTaskGraphLayout(tasks, options) {
|
|
162
356
|
const showParentChild = options?.showParentChild ?? false;
|
|
163
357
|
const graphTasks = tasks.filter(isGraphTask);
|
|
164
358
|
if (graphTasks.length === 0) {
|
|
@@ -289,7 +483,6 @@ function buildTaskGraphLayout(snapshot, tasks, options) {
|
|
|
289
483
|
x: (colX[index] ?? 0) + CELL_H_PAD,
|
|
290
484
|
width: CARD_WIDTH
|
|
291
485
|
}));
|
|
292
|
-
const pendingApprovalRunIds = new Set(selectPendingApprovals(snapshot).map((approval) => approval.runId));
|
|
293
486
|
const nodes = [];
|
|
294
487
|
for (const [rowIndex, [rowKey]] of orderedRows.entries()) {
|
|
295
488
|
for (let stage = 0;stage <= maxStage; stage += 1) {
|
|
@@ -298,13 +491,6 @@ function buildTaskGraphLayout(snapshot, tasks, options) {
|
|
|
298
491
|
const baseY = (rowY[rowIndex] ?? 0) + CELL_V_PAD;
|
|
299
492
|
const palette = PALETTE[rowIndex % PALETTE.length];
|
|
300
493
|
for (const [stackIndex, task] of cellTasks.entries()) {
|
|
301
|
-
const runs = selectRunsByTask(snapshot, task.id);
|
|
302
|
-
const runIds = new Set(runs.map((run) => run.id));
|
|
303
|
-
const hasApprovals = runs.some((run) => pendingApprovalRunIds.has(run.id));
|
|
304
|
-
const hasPendingUserInput = runs.some((run) => selectUserInputsForRun(snapshot, run.id).some((request) => request.status === "pending"));
|
|
305
|
-
const hasRejectedReview = (snapshot?.reviews ?? []).some((review) => runIds.has(review.runId) && review.status === "rejected");
|
|
306
|
-
const hasFailedValidations = (snapshot?.validations ?? []).some((validation) => runIds.has(validation.runId) && validation.status === "failed");
|
|
307
|
-
const artifactCount = (snapshot?.artifacts ?? []).filter((artifact) => runIds.has(artifact.runId)).length;
|
|
308
494
|
nodes.push({
|
|
309
495
|
id: task.id,
|
|
310
496
|
taskId: task.id,
|
|
@@ -322,12 +508,12 @@ function buildTaskGraphLayout(snapshot, tasks, options) {
|
|
|
322
508
|
strippedTitle: stripTaskCode(task.title),
|
|
323
509
|
depsIn: depsIn.get(task.id) ?? 0,
|
|
324
510
|
depsOut: depsOut.get(task.id) ?? 0,
|
|
325
|
-
runCount:
|
|
326
|
-
hasApprovals,
|
|
327
|
-
hasPendingUserInput,
|
|
328
|
-
hasRejectedReview,
|
|
329
|
-
hasFailedValidations,
|
|
330
|
-
artifactCount
|
|
511
|
+
runCount: 0,
|
|
512
|
+
hasApprovals: false,
|
|
513
|
+
hasPendingUserInput: false,
|
|
514
|
+
hasRejectedReview: false,
|
|
515
|
+
hasFailedValidations: false,
|
|
516
|
+
artifactCount: 0
|
|
331
517
|
});
|
|
332
518
|
}
|
|
333
519
|
}
|
|
@@ -343,8 +529,37 @@ function buildTaskGraphLayout(snapshot, tasks, options) {
|
|
|
343
529
|
};
|
|
344
530
|
}
|
|
345
531
|
|
|
346
|
-
// packages/dependency-graph-plugin/src/
|
|
347
|
-
|
|
532
|
+
// packages/dependency-graph-plugin/src/analysis/rigSelectors.ts
|
|
533
|
+
function isObjectRecord3(value) {
|
|
534
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
535
|
+
}
|
|
536
|
+
function normalizeLogin(value) {
|
|
537
|
+
return value.trim().replace(/^@+/, "").toLowerCase();
|
|
538
|
+
}
|
|
539
|
+
function assigneeLoginsFromValue(value) {
|
|
540
|
+
if (!Array.isArray(value))
|
|
541
|
+
return [];
|
|
542
|
+
return value.flatMap((entry) => {
|
|
543
|
+
if (typeof entry === "string" && entry.trim())
|
|
544
|
+
return [normalizeLogin(entry)];
|
|
545
|
+
if (isObjectRecord3(entry) && typeof entry.login === "string" && entry.login.trim()) {
|
|
546
|
+
return [normalizeLogin(entry.login)];
|
|
547
|
+
}
|
|
548
|
+
return [];
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
function readTaskAssigneeLogins(task) {
|
|
552
|
+
const taskRecord = task;
|
|
553
|
+
const metadata = isObjectRecord3(task.metadata) ? task.metadata : null;
|
|
554
|
+
const raw = isObjectRecord3(metadata?.raw) ? metadata.raw : null;
|
|
555
|
+
return Array.from(new Set([
|
|
556
|
+
...assigneeLoginsFromValue(taskRecord.assignees),
|
|
557
|
+
...assigneeLoginsFromValue(metadata?.assignees),
|
|
558
|
+
...assigneeLoginsFromValue(raw?.assignees)
|
|
559
|
+
]));
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// packages/dependency-graph-plugin/src/analysis/dependencyGraph.ts
|
|
348
563
|
function uniqueSorted(values) {
|
|
349
564
|
return Array.from(new Set(values)).sort((left, right) => left.localeCompare(right));
|
|
350
565
|
}
|
|
@@ -426,19 +641,19 @@ function detectBlockingCycles(tasks, edges) {
|
|
|
426
641
|
}
|
|
427
642
|
function buildDependencyGraphModel(tasks, options = {}) {
|
|
428
643
|
const badges = computeTaskDependencyBadges(tasks);
|
|
429
|
-
const index =
|
|
644
|
+
const index = buildTaskReferenceIndex(tasks);
|
|
430
645
|
const edges = [];
|
|
431
646
|
const unresolvedRefs = [];
|
|
432
647
|
for (const task of tasks) {
|
|
433
|
-
for (const ref of
|
|
434
|
-
const dependencyId =
|
|
648
|
+
for (const ref of readTaskMetadataStringList(task, "dependencies")) {
|
|
649
|
+
const dependencyId = resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
|
|
435
650
|
if (dependencyId)
|
|
436
651
|
edges.push({ fromTaskId: dependencyId, toTaskId: String(task.id), type: "blocks" });
|
|
437
652
|
else
|
|
438
653
|
unresolvedRefs.push(ref);
|
|
439
654
|
}
|
|
440
|
-
for (const ref of
|
|
441
|
-
const parentId =
|
|
655
|
+
for (const ref of readTaskMetadataStringList(task, "parentChildDeps")) {
|
|
656
|
+
const parentId = resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
|
|
442
657
|
if (parentId)
|
|
443
658
|
edges.push({ fromTaskId: parentId, toTaskId: String(task.id), type: "parent-child" });
|
|
444
659
|
else
|
|
@@ -473,100 +688,13 @@ function buildDependencyGraphModel(tasks, options = {}) {
|
|
|
473
688
|
graphId: options.graphId ?? deriveGraphId(tasks),
|
|
474
689
|
nodes,
|
|
475
690
|
edges: dedupedEdges,
|
|
476
|
-
layout: buildTaskGraphLayout(
|
|
691
|
+
layout: buildTaskGraphLayout(tasks, { showParentChild: options.showParentChild ?? true }),
|
|
477
692
|
cycles: detectBlockingCycles(tasks, dedupedEdges),
|
|
478
693
|
unresolvedRefs: uniqueSorted(unresolvedRefs),
|
|
479
694
|
degraded: false,
|
|
480
695
|
generatedAt: options.generatedAt ?? new Date().toISOString()
|
|
481
696
|
};
|
|
482
697
|
}
|
|
483
|
-
|
|
484
|
-
// packages/dependency-graph-plugin/src/graph.ts
|
|
485
|
-
import { toTaskSummary } from "@rig/contracts";
|
|
486
|
-
import { classifyTasks } from "@rig/blocker-classifier-plugin";
|
|
487
|
-
function overlayLayoutRunState(model, runs) {
|
|
488
|
-
if (runs.length === 0)
|
|
489
|
-
return model.layout;
|
|
490
|
-
const runsByTask = new Map;
|
|
491
|
-
for (const run of runs) {
|
|
492
|
-
if (!run.taskId)
|
|
493
|
-
continue;
|
|
494
|
-
runsByTask.set(run.taskId, [...runsByTask.get(run.taskId) ?? [], run]);
|
|
495
|
-
}
|
|
496
|
-
return {
|
|
497
|
-
...model.layout,
|
|
498
|
-
nodes: model.layout.nodes.map((node) => {
|
|
499
|
-
const taskRuns = runsByTask.get(node.taskId) ?? [];
|
|
500
|
-
return {
|
|
501
|
-
...node,
|
|
502
|
-
runCount: taskRuns.length,
|
|
503
|
-
hasApprovals: taskRuns.some((run) => run.pendingApprovals > 0),
|
|
504
|
-
hasPendingUserInput: taskRuns.some((run) => run.pendingInputs > 0),
|
|
505
|
-
hasRejectedReview: taskRuns.some((run) => run.status === "reviewing" && Boolean(run.errorSummary)),
|
|
506
|
-
hasFailedValidations: taskRuns.some((run) => run.status === "failed")
|
|
507
|
-
};
|
|
508
|
-
})
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
function buildWorkspaceDependencyGraph(input) {
|
|
512
|
-
const summaries = input.tasks.map((task) => toTaskSummary(task, input.workspaceId !== undefined ? { workspaceId: input.workspaceId } : {}));
|
|
513
|
-
const model = (input.projectDependencyGraph ?? buildDependencyGraphModel)(summaries, {
|
|
514
|
-
...input.generatedAt !== undefined ? { generatedAt: input.generatedAt } : {},
|
|
515
|
-
...input.graphId !== undefined ? { graphId: input.graphId } : {},
|
|
516
|
-
...input.showParentChild !== undefined ? { showParentChild: input.showParentChild } : {}
|
|
517
|
-
});
|
|
518
|
-
const classifications = input.classifications ?? classifyTasks(input.tasks, input.runs ?? [], input.generatedAt !== undefined ? { generatedAt: input.generatedAt } : {}).byTaskId;
|
|
519
|
-
return {
|
|
520
|
-
...model,
|
|
521
|
-
layout: overlayLayoutRunState(model, input.runs ?? []),
|
|
522
|
-
nodes: model.nodes.map((node) => {
|
|
523
|
-
const classification = classifications.get(node.taskId);
|
|
524
|
-
return {
|
|
525
|
-
...node,
|
|
526
|
-
blockerClass: classification?.blockerClass ?? node.blockerClass,
|
|
527
|
-
actionRiskTier: classification?.actionRiskTier ?? null
|
|
528
|
-
};
|
|
529
|
-
})
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
async function getWorkspaceDependencyGraph(projectRoot, deps) {
|
|
533
|
-
const [tasks, runs, blockers] = await Promise.all([
|
|
534
|
-
deps.listTasks(projectRoot),
|
|
535
|
-
deps.listRuns ? deps.listRuns(projectRoot) : Promise.resolve([]),
|
|
536
|
-
deps.classifyBlockers ? deps.classifyBlockers(projectRoot) : Promise.resolve(null)
|
|
537
|
-
]);
|
|
538
|
-
return buildWorkspaceDependencyGraph({
|
|
539
|
-
tasks,
|
|
540
|
-
runs,
|
|
541
|
-
...blockers?.byTaskId !== undefined ? { classifications: blockers.byTaskId } : {},
|
|
542
|
-
...deps.generatedAt !== undefined ? { generatedAt: deps.generatedAt } : {},
|
|
543
|
-
...deps.graphId !== undefined ? { graphId: deps.graphId } : {},
|
|
544
|
-
...deps.workspaceId !== undefined ? { workspaceId: deps.workspaceId } : {},
|
|
545
|
-
...deps.projectDependencyGraph !== undefined ? { projectDependencyGraph: deps.projectDependencyGraph } : {}
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
function formatDependencyGraphDot(model) {
|
|
549
|
-
const lines = ["digraph RigDependencies {", " rankdir=LR;"];
|
|
550
|
-
for (const node of model.nodes) {
|
|
551
|
-
const color = node.blockerClass === "task-blocked" ? "orange" : node.blockerClass && node.blockerClass !== "not-blocked" ? "red" : "gray";
|
|
552
|
-
lines.push(` "${escapeDot(node.taskId)}" [label="${escapeDot(node.title)}", color="${color}"];`);
|
|
553
|
-
}
|
|
554
|
-
for (const edge of model.edges) {
|
|
555
|
-
lines.push(` "${escapeDot(edge.fromTaskId)}" -> "${escapeDot(edge.toTaskId)}" [label="${edge.type}", color="${edgeColor(edge)}"];`);
|
|
556
|
-
}
|
|
557
|
-
lines.push("}");
|
|
558
|
-
return `${lines.join(`
|
|
559
|
-
`)}
|
|
560
|
-
`;
|
|
561
|
-
}
|
|
562
|
-
function edgeColor(edge) {
|
|
563
|
-
return edge.type === "blocks" ? "black" : edge.type === "parent-child" ? "blue" : "gray";
|
|
564
|
-
}
|
|
565
|
-
function escapeDot(value) {
|
|
566
|
-
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
567
|
-
}
|
|
568
698
|
export {
|
|
569
|
-
|
|
570
|
-
formatDependencyGraphDot,
|
|
571
|
-
buildWorkspaceDependencyGraph
|
|
699
|
+
buildDependencyGraphModel
|
|
572
700
|
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { BuildDependencyGraphModelOptions, DependencyGraphModel, DependencyNode } from "./
|
|
2
|
-
import type { TaskLike } from "@rig/
|
|
3
|
-
import
|
|
4
|
-
import { type WorkspaceBlockers } from "@rig/blocker-classifier-plugin";
|
|
1
|
+
import type { BuildDependencyGraphModelOptions, DependencyGraphModel, DependencyNode } from "./dependencyGraph";
|
|
2
|
+
import type { ActionRiskTier, BlockerClass, BlockerClassification, RunRecord, TaskLike, TaskSummary } from "@rig/contracts";
|
|
3
|
+
import { type WorkspaceBlockers } from "./blockers";
|
|
5
4
|
export interface ClientDependencyNode extends Omit<DependencyNode, "blockerClass" | "actionRiskTier"> {
|
|
6
5
|
readonly blockerClass: BlockerClass | null;
|
|
7
6
|
readonly actionRiskTier: ActionRiskTier | null;
|