@h-rig/dependency-graph-plugin 0.0.6-alpha.156 → 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
package/dist/src/index.js
CHANGED
|
@@ -1,14 +1,439 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var __require = import.meta.require;
|
|
3
|
-
|
|
4
2
|
// packages/dependency-graph-plugin/src/plugin.ts
|
|
5
3
|
import { definePlugin } from "@rig/core/config";
|
|
6
|
-
import {
|
|
4
|
+
import { defineCapability } from "@rig/core/capability";
|
|
5
|
+
import { requireCapabilityForRoot } from "@rig/core/capability-loaders";
|
|
6
|
+
import {
|
|
7
|
+
RUN_READ_MODEL,
|
|
8
|
+
TASK_IO_SERVICE_CAPABILITY,
|
|
9
|
+
TASK_SELECTION
|
|
10
|
+
} from "@rig/contracts";
|
|
11
|
+
|
|
12
|
+
// packages/dependency-graph-plugin/src/analysis/taskGraphPrimitives.ts
|
|
13
|
+
import {
|
|
14
|
+
OPERATOR_INACTIVE_RUN_STATUSES
|
|
15
|
+
} from "@rig/contracts";
|
|
16
|
+
function isObjectRecord(value) {
|
|
17
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
function readStringList(value) {
|
|
20
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
21
|
+
}
|
|
22
|
+
function unique(values) {
|
|
23
|
+
return Array.from(new Set(values));
|
|
24
|
+
}
|
|
25
|
+
function readTaskMetadataStringList(task, key) {
|
|
26
|
+
const taskRecord = task;
|
|
27
|
+
const topLevel = readStringList(taskRecord[key]);
|
|
28
|
+
if (topLevel.length > 0)
|
|
29
|
+
return topLevel;
|
|
30
|
+
const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
|
|
31
|
+
const metadataList = readStringList(metadata?.[key]);
|
|
32
|
+
if (metadataList.length > 0)
|
|
33
|
+
return metadataList;
|
|
34
|
+
if (key === "dependencies") {
|
|
35
|
+
return readStringList(metadata?.deps);
|
|
36
|
+
}
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
function readTaskBlockingDependencyRefs(task) {
|
|
40
|
+
return readTaskMetadataStringList(task, "dependencies");
|
|
41
|
+
}
|
|
42
|
+
function readTaskSourceIssueId(task) {
|
|
43
|
+
if (typeof task.sourceIssueId === "string" && task.sourceIssueId.length > 0) {
|
|
44
|
+
return task.sourceIssueId;
|
|
45
|
+
}
|
|
46
|
+
const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
|
|
47
|
+
if (typeof metadata?.sourceIssueId === "string" && metadata.sourceIssueId.length > 0) {
|
|
48
|
+
return metadata.sourceIssueId;
|
|
49
|
+
}
|
|
50
|
+
const rigMetadata = isObjectRecord(metadata?._rig) ? metadata._rig : null;
|
|
51
|
+
return typeof rigMetadata?.sourceIssueId === "string" && rigMetadata.sourceIssueId.length > 0 ? rigMetadata.sourceIssueId : null;
|
|
52
|
+
}
|
|
53
|
+
function resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId) {
|
|
54
|
+
if (tasksById.has(ref))
|
|
55
|
+
return ref;
|
|
56
|
+
return taskIdBySourceIssueId.get(ref) ?? taskIdByExternalRef.get(ref) ?? null;
|
|
57
|
+
}
|
|
58
|
+
function buildTaskReferenceIndex(tasks) {
|
|
59
|
+
return {
|
|
60
|
+
tasksById: new Map(tasks.map((task) => [task.id, task])),
|
|
61
|
+
taskIdByExternalRef: new Map(tasks.flatMap((task) => task.externalId ? [[task.externalId, task.id]] : [])),
|
|
62
|
+
taskIdBySourceIssueId: new Map(tasks.flatMap((task) => {
|
|
63
|
+
const sourceIssueId = readTaskSourceIssueId(task);
|
|
64
|
+
return sourceIssueId ? [[sourceIssueId, task.id]] : [];
|
|
65
|
+
}))
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function computeTaskBlockingDepths(tasks) {
|
|
69
|
+
const { tasksById, taskIdByExternalRef, taskIdBySourceIssueId } = buildTaskReferenceIndex(tasks);
|
|
70
|
+
const memo = new Map;
|
|
71
|
+
const visit = (taskId, stack) => {
|
|
72
|
+
const cached = memo.get(taskId);
|
|
73
|
+
if (cached !== undefined)
|
|
74
|
+
return cached;
|
|
75
|
+
if (stack.has(taskId))
|
|
76
|
+
return 0;
|
|
77
|
+
const task = tasksById.get(taskId);
|
|
78
|
+
if (!task)
|
|
79
|
+
return 0;
|
|
80
|
+
stack.add(taskId);
|
|
81
|
+
const blockers = readTaskBlockingDependencyRefs(task).map((ref) => resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId)).filter((ref) => ref !== null && ref !== taskId);
|
|
82
|
+
const depth = blockers.length === 0 ? 0 : Math.max(...blockers.map((blockerId) => visit(blockerId, stack) + 1));
|
|
83
|
+
stack.delete(taskId);
|
|
84
|
+
memo.set(taskId, depth);
|
|
85
|
+
return depth;
|
|
86
|
+
};
|
|
87
|
+
for (const task of tasks) {
|
|
88
|
+
visit(task.id, new Set);
|
|
89
|
+
}
|
|
90
|
+
return memo;
|
|
91
|
+
}
|
|
92
|
+
function isTaskTerminalStatus(status) {
|
|
93
|
+
switch (status) {
|
|
94
|
+
case "closed":
|
|
95
|
+
case "completed":
|
|
96
|
+
case "done":
|
|
97
|
+
case "cancelled":
|
|
98
|
+
case "canceled":
|
|
99
|
+
return true;
|
|
100
|
+
default:
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function isTaskBlockedStatus(status) {
|
|
105
|
+
return status === "blocked";
|
|
106
|
+
}
|
|
107
|
+
function isTaskRunnableStatus(status) {
|
|
108
|
+
if (status === null || status === undefined || status === "")
|
|
109
|
+
return true;
|
|
110
|
+
if (isTaskTerminalStatus(status) || isTaskBlockedStatus(status))
|
|
111
|
+
return false;
|
|
112
|
+
switch (status) {
|
|
113
|
+
case "ready":
|
|
114
|
+
case "open":
|
|
115
|
+
case "failed":
|
|
116
|
+
return true;
|
|
117
|
+
default:
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function computeTaskDependencyBadges(tasks) {
|
|
122
|
+
const index = buildTaskReferenceIndex(tasks);
|
|
123
|
+
const blockingDepths = computeTaskBlockingDepths(tasks);
|
|
124
|
+
const dependencyIdsByTask = new Map;
|
|
125
|
+
const unresolvedRefsByTask = new Map;
|
|
126
|
+
const blocksByTask = new Map;
|
|
127
|
+
for (const task of tasks) {
|
|
128
|
+
const dependencyIds = [];
|
|
129
|
+
const unresolvedRefs = [];
|
|
130
|
+
for (const ref of readTaskBlockingDependencyRefs(task)) {
|
|
131
|
+
const dependencyId = resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
|
|
132
|
+
if (dependencyId && dependencyId !== task.id) {
|
|
133
|
+
dependencyIds.push(dependencyId);
|
|
134
|
+
const blocks = blocksByTask.get(dependencyId);
|
|
135
|
+
if (blocks) {
|
|
136
|
+
blocks.push(task.id);
|
|
137
|
+
} else {
|
|
138
|
+
blocksByTask.set(dependencyId, [task.id]);
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
unresolvedRefs.push(ref);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
dependencyIdsByTask.set(task.id, unique(dependencyIds));
|
|
145
|
+
unresolvedRefsByTask.set(task.id, unique(unresolvedRefs));
|
|
146
|
+
}
|
|
147
|
+
const summaries = new Map;
|
|
148
|
+
for (const task of tasks) {
|
|
149
|
+
const dependencyIds = dependencyIdsByTask.get(task.id) ?? [];
|
|
150
|
+
const unresolvedDependencyRefs = unresolvedRefsByTask.get(task.id) ?? [];
|
|
151
|
+
const blockedBy = dependencyIds.filter((dependencyId) => {
|
|
152
|
+
const dependency = index.tasksById.get(dependencyId);
|
|
153
|
+
return dependency ? !isTaskTerminalStatus(dependency.status) : false;
|
|
154
|
+
});
|
|
155
|
+
const blocks = unique(blocksByTask.get(task.id) ?? []);
|
|
156
|
+
const blocked = isTaskBlockedStatus(task.status) || blockedBy.length > 0;
|
|
157
|
+
const ready = isTaskRunnableStatus(task.status) && !blocked;
|
|
158
|
+
const badges = [];
|
|
159
|
+
if (blocked) {
|
|
160
|
+
badges.push({
|
|
161
|
+
kind: "blocked",
|
|
162
|
+
label: blockedBy.length > 0 ? `blocked \xD7${blockedBy.length}` : "blocked",
|
|
163
|
+
description: blockedBy.length > 0 ? `Waiting on ${blockedBy.join(", ")}.` : "Task source marks this task blocked.",
|
|
164
|
+
...blockedBy.length > 0 ? { count: blockedBy.length } : {},
|
|
165
|
+
taskIds: blockedBy
|
|
166
|
+
});
|
|
167
|
+
} else if (ready) {
|
|
168
|
+
badges.push({
|
|
169
|
+
kind: "ready",
|
|
170
|
+
label: "ready",
|
|
171
|
+
description: "No open dependencies block this task."
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (dependencyIds.length > 0 || blocks.length > 0 || unresolvedDependencyRefs.length > 0) {
|
|
175
|
+
badges.push({
|
|
176
|
+
kind: "dependency",
|
|
177
|
+
label: `deps ${dependencyIds.length}/${blocks.length}`,
|
|
178
|
+
description: [
|
|
179
|
+
dependencyIds.length > 0 ? `Depends on ${dependencyIds.join(", ")}.` : null,
|
|
180
|
+
blocks.length > 0 ? `Blocks ${blocks.join(", ")}.` : null,
|
|
181
|
+
unresolvedDependencyRefs.length > 0 ? `Unresolved refs: ${unresolvedDependencyRefs.join(", ")}.` : null
|
|
182
|
+
].filter((part) => part !== null).join(" "),
|
|
183
|
+
count: dependencyIds.length + blocks.length,
|
|
184
|
+
taskIds: unique([...dependencyIds, ...blocks])
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
summaries.set(task.id, {
|
|
188
|
+
taskId: task.id,
|
|
189
|
+
blockingDepth: blockingDepths.get(task.id) ?? 0,
|
|
190
|
+
dependencyIds,
|
|
191
|
+
unresolvedDependencyRefs,
|
|
192
|
+
blockedBy,
|
|
193
|
+
blocks,
|
|
194
|
+
blocked,
|
|
195
|
+
ready,
|
|
196
|
+
dependencyCount: dependencyIds.length,
|
|
197
|
+
dependentCount: blocks.length,
|
|
198
|
+
badges
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return summaries;
|
|
202
|
+
}
|
|
203
|
+
var TASK_STATUSES = new Set([
|
|
204
|
+
"draft",
|
|
205
|
+
"open",
|
|
206
|
+
"ready",
|
|
207
|
+
"queued",
|
|
208
|
+
"running",
|
|
209
|
+
"in_progress",
|
|
210
|
+
"under_review",
|
|
211
|
+
"blocked",
|
|
212
|
+
"unknown",
|
|
213
|
+
"completed",
|
|
214
|
+
"failed",
|
|
215
|
+
"cancelled",
|
|
216
|
+
"closed"
|
|
217
|
+
]);
|
|
218
|
+
function stringValue(value) {
|
|
219
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
220
|
+
}
|
|
221
|
+
function stringArray(value) {
|
|
222
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim().length > 0).map((entry) => entry.trim()) : [];
|
|
223
|
+
}
|
|
224
|
+
function numberOrNull(value) {
|
|
225
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
|
226
|
+
}
|
|
227
|
+
function metadataOf(task) {
|
|
228
|
+
return isObjectRecord(task.metadata) ? task.metadata : {};
|
|
229
|
+
}
|
|
230
|
+
function normalizeTaskStatus(status) {
|
|
231
|
+
const token = typeof status === "string" ? status.trim().toLowerCase() : "";
|
|
232
|
+
if (token === "done")
|
|
233
|
+
return "completed";
|
|
234
|
+
if (token === "canceled")
|
|
235
|
+
return "cancelled";
|
|
236
|
+
return TASK_STATUSES.has(token) ? token : "unknown";
|
|
237
|
+
}
|
|
238
|
+
function toTaskDependencyProjection(task) {
|
|
239
|
+
const metadata = metadataOf(task);
|
|
240
|
+
return {
|
|
241
|
+
id: String(task.id),
|
|
242
|
+
title: stringValue(task.title),
|
|
243
|
+
status: normalizeTaskStatus(task.status),
|
|
244
|
+
priority: numberOrNull(task.priority),
|
|
245
|
+
metadata,
|
|
246
|
+
externalId: stringValue(task.externalId),
|
|
247
|
+
sourceIssueId: stringValue(task.sourceIssueId),
|
|
248
|
+
dependencies: stringArray(task.dependencies),
|
|
249
|
+
parentChildDeps: stringArray(task.parentChildDeps),
|
|
250
|
+
createdAt: stringValue(task.createdAt) ?? "",
|
|
251
|
+
updatedAt: stringValue(task.updatedAt) ?? "",
|
|
252
|
+
role: stringValue(task.role),
|
|
253
|
+
scope: stringArray(task.scope),
|
|
254
|
+
validationKeys: stringArray(task.validationKeys),
|
|
255
|
+
labels: stringArray(task.labels),
|
|
256
|
+
assignees: task.assignees ?? null,
|
|
257
|
+
assignedTo: task.assignedTo ?? null
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function toTaskSummary(task, defaults = {}) {
|
|
261
|
+
const projection = toTaskDependencyProjection(task);
|
|
262
|
+
const metadata = metadataOf(task);
|
|
263
|
+
const createdAt = stringValue(task.createdAt) ?? "1970-01-01T00:00:00.000Z";
|
|
264
|
+
const updatedAt = stringValue(task.updatedAt) ?? createdAt;
|
|
265
|
+
return {
|
|
266
|
+
id: projection.id,
|
|
267
|
+
workspaceId: stringValue(task.workspaceId) ?? defaults.workspaceId ?? "workspace",
|
|
268
|
+
graphId: stringValue(task.graphId) ?? defaults.graphId ?? null,
|
|
269
|
+
externalId: projection.externalId,
|
|
270
|
+
title: projection.title ?? projection.id,
|
|
271
|
+
description: stringValue(task.description) ?? stringValue(task.body) ?? "",
|
|
272
|
+
status: projection.status,
|
|
273
|
+
priority: numberOrNull(task.priority),
|
|
274
|
+
role: projection.role,
|
|
275
|
+
scope: [...projection.scope ?? []],
|
|
276
|
+
validationKeys: [...projection.validationKeys ?? []],
|
|
277
|
+
...projection.sourceIssueId ? { sourceIssueId: projection.sourceIssueId } : {},
|
|
278
|
+
dependencies: [...projection.dependencies ?? []],
|
|
279
|
+
parentChildDeps: [...projection.parentChildDeps ?? []],
|
|
280
|
+
metadata,
|
|
281
|
+
createdAt,
|
|
282
|
+
updatedAt
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function taskPriorityValue(task) {
|
|
286
|
+
return typeof task.priority === "number" && Number.isFinite(task.priority) ? task.priority : Number.MAX_SAFE_INTEGER;
|
|
287
|
+
}
|
|
288
|
+
function selectNextReadyTaskByPriority(tasks, options = {}) {
|
|
289
|
+
const excluded = new Set(options.excludeTaskIds ?? []);
|
|
290
|
+
const badges = computeTaskDependencyBadges(tasks);
|
|
291
|
+
const candidates = tasks.filter((task) => !excluded.has(task.id)).filter((task) => options.filter?.(task) ?? true).filter((task) => badges.get(task.id)?.ready === true).toSorted((left, right) => {
|
|
292
|
+
const priorityDelta = taskPriorityValue(left) - taskPriorityValue(right);
|
|
293
|
+
if (priorityDelta !== 0)
|
|
294
|
+
return priorityDelta;
|
|
295
|
+
const createdDelta = (left.createdAt ?? "").localeCompare(right.createdAt ?? "");
|
|
296
|
+
if (createdDelta !== 0)
|
|
297
|
+
return createdDelta;
|
|
298
|
+
return left.id.localeCompare(right.id);
|
|
299
|
+
});
|
|
300
|
+
return candidates[0] ?? null;
|
|
301
|
+
}
|
|
302
|
+
function normalizeLogin(value) {
|
|
303
|
+
return value.trim().replace(/^@+/, "").toLowerCase();
|
|
304
|
+
}
|
|
305
|
+
function assigneeLoginsFromValue(value) {
|
|
306
|
+
if (!Array.isArray(value))
|
|
307
|
+
return [];
|
|
308
|
+
return value.flatMap((entry) => {
|
|
309
|
+
if (typeof entry === "string" && entry.trim())
|
|
310
|
+
return [normalizeLogin(entry)];
|
|
311
|
+
if (isObjectRecord(entry) && typeof entry.login === "string" && entry.login.trim()) {
|
|
312
|
+
return [normalizeLogin(entry.login)];
|
|
313
|
+
}
|
|
314
|
+
return [];
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
function readTaskAssigneeLogins(task) {
|
|
318
|
+
const taskRecord = task;
|
|
319
|
+
const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
|
|
320
|
+
const raw = isObjectRecord(metadata?.raw) ? metadata.raw : null;
|
|
321
|
+
return Array.from(new Set([
|
|
322
|
+
...assigneeLoginsFromValue(taskRecord.assignees),
|
|
323
|
+
...assigneeLoginsFromValue(metadata?.assignees),
|
|
324
|
+
...assigneeLoginsFromValue(raw?.assignees)
|
|
325
|
+
]));
|
|
326
|
+
}
|
|
327
|
+
function latestRunByTaskId(runs) {
|
|
328
|
+
const byTask = new Map;
|
|
329
|
+
const stamp = (run) => Date.parse(run.updatedAt ?? run.startedAt ?? "") || 0;
|
|
330
|
+
for (const run of runs) {
|
|
331
|
+
if (!run.taskId)
|
|
332
|
+
continue;
|
|
333
|
+
const current = byTask.get(run.taskId);
|
|
334
|
+
if (!current || stamp(run) >= stamp(current))
|
|
335
|
+
byTask.set(run.taskId, run);
|
|
336
|
+
}
|
|
337
|
+
return byTask;
|
|
338
|
+
}
|
|
339
|
+
function normalizeRunStatusToken(status) {
|
|
340
|
+
return String(status ?? "").trim().toLowerCase().replace(/[\s_]+/g, "-");
|
|
341
|
+
}
|
|
342
|
+
function isOperatorActiveRunStatus(status) {
|
|
343
|
+
const normalized = normalizeRunStatusToken(status);
|
|
344
|
+
if (!normalized)
|
|
345
|
+
return false;
|
|
346
|
+
return !OPERATOR_INACTIVE_RUN_STATUSES.has(normalized);
|
|
347
|
+
}
|
|
7
348
|
|
|
8
|
-
// packages/dependency-graph-plugin/src/
|
|
9
|
-
|
|
349
|
+
// packages/dependency-graph-plugin/src/analysis/blockers.ts
|
|
350
|
+
var HUMAN_BLOCKERS = new Set(["human-decision", "human-approval", "external-input"]);
|
|
351
|
+
function labelsFor(task) {
|
|
352
|
+
return readTaskMetadataStringList(task, "labels").map((label) => label.toLowerCase());
|
|
353
|
+
}
|
|
354
|
+
function configuredTier(task, labels) {
|
|
355
|
+
const metadata = task.metadata && typeof task.metadata === "object" && !Array.isArray(task.metadata) ? task.metadata : {};
|
|
356
|
+
const tier = metadata.riskTier ?? metadata.actionRiskTier;
|
|
357
|
+
if (tier === "t1-read" || tier === "t2-reversible" || tier === "t3-external" || tier === "t4-irreversible")
|
|
358
|
+
return tier;
|
|
359
|
+
if (labels.some((label) => /prod|deploy|migration|billing|delete|irreversible/.test(label)))
|
|
360
|
+
return "t4-irreversible";
|
|
361
|
+
if (labels.some((label) => /customer|vendor|external|secret|credential|approval|decision/.test(label)))
|
|
362
|
+
return "t3-external";
|
|
363
|
+
const scopes = task.scope ?? [];
|
|
364
|
+
if (scopes.some((scope) => /docs|readme|test|spec/.test(scope.toLowerCase())))
|
|
365
|
+
return "t1-read";
|
|
366
|
+
return "t2-reversible";
|
|
367
|
+
}
|
|
368
|
+
function tierOf(task, labels = []) {
|
|
369
|
+
const projection = "metadata" in task && "id" in task && typeof task.id === "string" ? toTaskDependencyProjection(task) : task;
|
|
370
|
+
return configuredTier(projection, labels.length > 0 ? labels : labelsFor(projection)) ?? "t2-reversible";
|
|
371
|
+
}
|
|
372
|
+
function baseClassification(task, blockerClass, source, rationale, labels) {
|
|
373
|
+
return {
|
|
374
|
+
taskId: task.id,
|
|
375
|
+
blockerClass,
|
|
376
|
+
actionRiskTier: tierOf(task, labels),
|
|
377
|
+
rationale,
|
|
378
|
+
source,
|
|
379
|
+
autoApplied: source === "llm"
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function normalizeRunStatus(status) {
|
|
383
|
+
if (status === "waiting-approval" || status === "waiting-user-input" || status === "needs-attention" || status === "completed" || status === "failed" || status === "stopped" || status === "running")
|
|
384
|
+
return status;
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
function isHumanBlockerClass(blockerClass) {
|
|
388
|
+
return HUMAN_BLOCKERS.has(blockerClass);
|
|
389
|
+
}
|
|
390
|
+
function classifyBlocker(input) {
|
|
391
|
+
const labels = input.labels ?? labelsFor(input.task);
|
|
392
|
+
const runStatus = normalizeRunStatus(input.run?.status);
|
|
393
|
+
if (runStatus === "waiting-approval")
|
|
394
|
+
return baseClassification(input.task, "human-approval", "status", "run awaiting approval", labels);
|
|
395
|
+
if (runStatus === "waiting-user-input")
|
|
396
|
+
return baseClassification(input.task, "external-input", "status", "run awaiting user input", labels);
|
|
397
|
+
if (input.task.status !== "blocked")
|
|
398
|
+
return baseClassification(input.task, "not-blocked", "elimination", "task is not blocked", labels);
|
|
399
|
+
const incomplete = (input.badges.get(input.task.id)?.blockedBy ?? []).filter((dependencyId) => {
|
|
400
|
+
const dependency = input.tasksById.get(dependencyId);
|
|
401
|
+
return dependency ? !isTaskTerminalStatus(dependency.status) : false;
|
|
402
|
+
});
|
|
403
|
+
if (incomplete.length > 0)
|
|
404
|
+
return baseClassification(input.task, "task-blocked", "elimination", `blocked by ${incomplete.length} incomplete task(s)`, labels);
|
|
405
|
+
if (labels.includes("needs-decision"))
|
|
406
|
+
return baseClassification(input.task, "human-decision", "label", "needs-decision label", labels);
|
|
407
|
+
if (labels.some((label) => /^waiting-on-/.test(label)))
|
|
408
|
+
return baseClassification(input.task, "external-input", "label", "waiting-on-* label", labels);
|
|
409
|
+
if (input.config?.llm)
|
|
410
|
+
return baseClassification(input.task, "human-decision", "llm", "LLM residue classifier marked this as human-gated", labels);
|
|
411
|
+
return baseClassification(input.task, "human-decision", "elimination", "blocked, all task dependencies terminal; non-task gate remains", labels);
|
|
412
|
+
}
|
|
413
|
+
function classifyTasks(tasks, runs = [], options = {}) {
|
|
414
|
+
const projected = tasks.map(toTaskDependencyProjection);
|
|
415
|
+
const badges = computeTaskDependencyBadges(projected);
|
|
416
|
+
const tasksById = new Map(projected.map((task) => [task.id, task]));
|
|
417
|
+
const runByTask = latestRunByTaskId(runs);
|
|
418
|
+
const classifier = options.classifier ?? classifyBlocker;
|
|
419
|
+
const classifications = projected.map((task) => classifier({ task, badges, tasksById, run: runByTask.get(task.id) ?? null, labels: labelsFor(task) })).filter((classification) => options.humanOnly !== true || isHumanBlockerClass(classification.blockerClass));
|
|
420
|
+
return {
|
|
421
|
+
classifications,
|
|
422
|
+
byTaskId: new Map(classifications.map((classification) => [classification.taskId, classification])),
|
|
423
|
+
human: classifications.filter((classification) => isHumanBlockerClass(classification.blockerClass)),
|
|
424
|
+
machine: classifications.filter((classification) => !isHumanBlockerClass(classification.blockerClass)),
|
|
425
|
+
generatedAt: options.generatedAt ?? new Date().toISOString()
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
async function classifyWorkspaceBlockers(projectRoot, deps) {
|
|
429
|
+
const [tasks, runs] = await Promise.all([
|
|
430
|
+
deps.listTasks(projectRoot),
|
|
431
|
+
deps.listRuns ? deps.listRuns(projectRoot) : Promise.resolve([])
|
|
432
|
+
]);
|
|
433
|
+
return classifyTasks(tasks, runs, deps);
|
|
434
|
+
}
|
|
10
435
|
|
|
11
|
-
// packages/dependency-graph-plugin/src/
|
|
436
|
+
// packages/dependency-graph-plugin/src/analysis/taskGraphCodes.ts
|
|
12
437
|
var TASK_CODE_RE = /^\[([A-Z0-9]+(?:-[A-Z0-9]+)*)\]\s*/;
|
|
13
438
|
function extractTaskCode(title) {
|
|
14
439
|
const match = title.match(TASK_CODE_RE);
|
|
@@ -29,17 +454,7 @@ function stripTaskCode(label) {
|
|
|
29
454
|
return label.replace(TASK_CODE_RE, "");
|
|
30
455
|
}
|
|
31
456
|
|
|
32
|
-
// packages/dependency-graph-plugin/src/
|
|
33
|
-
import {
|
|
34
|
-
buildTaskReferenceIndex,
|
|
35
|
-
readTaskMetadataStringList,
|
|
36
|
-
resolveTaskReference
|
|
37
|
-
} from "@rig/contracts";
|
|
38
|
-
import {
|
|
39
|
-
selectPendingApprovals,
|
|
40
|
-
selectRunsByTask,
|
|
41
|
-
selectUserInputsForRun
|
|
42
|
-
} from "@rig/read-model-plugin";
|
|
457
|
+
// packages/dependency-graph-plugin/src/analysis/taskGraphLayout.ts
|
|
43
458
|
var CARD_WIDTH = 200;
|
|
44
459
|
var CARD_HEIGHT = 110;
|
|
45
460
|
var CELL_V_PAD = 12;
|
|
@@ -58,14 +473,14 @@ var PALETTE = [
|
|
|
58
473
|
{ bg: "#132c35", border: "#1783a6", edge: "#53c4e5" },
|
|
59
474
|
{ bg: "#26310f", border: "#6d9a19", edge: "#a7da42" }
|
|
60
475
|
];
|
|
61
|
-
function
|
|
476
|
+
function isObjectRecord2(value) {
|
|
62
477
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
63
478
|
}
|
|
64
479
|
function readIssueType(task) {
|
|
65
|
-
const metadata =
|
|
480
|
+
const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
|
|
66
481
|
if (typeof metadata?.issueType === "string")
|
|
67
482
|
return metadata.issueType;
|
|
68
|
-
const raw =
|
|
483
|
+
const raw = isObjectRecord2(metadata?.raw) ? metadata.raw : null;
|
|
69
484
|
return typeof raw?.issueType === "string" ? raw.issueType : null;
|
|
70
485
|
}
|
|
71
486
|
function isGraphTask(task) {
|
|
@@ -164,7 +579,7 @@ function computeDepths(ids, edges) {
|
|
|
164
579
|
}
|
|
165
580
|
return memo;
|
|
166
581
|
}
|
|
167
|
-
function buildTaskGraphLayout(
|
|
582
|
+
function buildTaskGraphLayout(tasks, options) {
|
|
168
583
|
const showParentChild = options?.showParentChild ?? false;
|
|
169
584
|
const graphTasks = tasks.filter(isGraphTask);
|
|
170
585
|
if (graphTasks.length === 0) {
|
|
@@ -295,7 +710,6 @@ function buildTaskGraphLayout(snapshot, tasks, options) {
|
|
|
295
710
|
x: (colX[index] ?? 0) + CELL_H_PAD,
|
|
296
711
|
width: CARD_WIDTH
|
|
297
712
|
}));
|
|
298
|
-
const pendingApprovalRunIds = new Set(selectPendingApprovals(snapshot).map((approval) => approval.runId));
|
|
299
713
|
const nodes = [];
|
|
300
714
|
for (const [rowIndex, [rowKey]] of orderedRows.entries()) {
|
|
301
715
|
for (let stage = 0;stage <= maxStage; stage += 1) {
|
|
@@ -304,13 +718,6 @@ function buildTaskGraphLayout(snapshot, tasks, options) {
|
|
|
304
718
|
const baseY = (rowY[rowIndex] ?? 0) + CELL_V_PAD;
|
|
305
719
|
const palette = PALETTE[rowIndex % PALETTE.length];
|
|
306
720
|
for (const [stackIndex, task] of cellTasks.entries()) {
|
|
307
|
-
const runs = selectRunsByTask(snapshot, task.id);
|
|
308
|
-
const runIds = new Set(runs.map((run) => run.id));
|
|
309
|
-
const hasApprovals = runs.some((run) => pendingApprovalRunIds.has(run.id));
|
|
310
|
-
const hasPendingUserInput = runs.some((run) => selectUserInputsForRun(snapshot, run.id).some((request) => request.status === "pending"));
|
|
311
|
-
const hasRejectedReview = (snapshot?.reviews ?? []).some((review) => runIds.has(review.runId) && review.status === "rejected");
|
|
312
|
-
const hasFailedValidations = (snapshot?.validations ?? []).some((validation) => runIds.has(validation.runId) && validation.status === "failed");
|
|
313
|
-
const artifactCount = (snapshot?.artifacts ?? []).filter((artifact) => runIds.has(artifact.runId)).length;
|
|
314
721
|
nodes.push({
|
|
315
722
|
id: task.id,
|
|
316
723
|
taskId: task.id,
|
|
@@ -328,12 +735,12 @@ function buildTaskGraphLayout(snapshot, tasks, options) {
|
|
|
328
735
|
strippedTitle: stripTaskCode(task.title),
|
|
329
736
|
depsIn: depsIn.get(task.id) ?? 0,
|
|
330
737
|
depsOut: depsOut.get(task.id) ?? 0,
|
|
331
|
-
runCount:
|
|
332
|
-
hasApprovals,
|
|
333
|
-
hasPendingUserInput,
|
|
334
|
-
hasRejectedReview,
|
|
335
|
-
hasFailedValidations,
|
|
336
|
-
artifactCount
|
|
738
|
+
runCount: 0,
|
|
739
|
+
hasApprovals: false,
|
|
740
|
+
hasPendingUserInput: false,
|
|
741
|
+
hasRejectedReview: false,
|
|
742
|
+
hasFailedValidations: false,
|
|
743
|
+
artifactCount: 0
|
|
337
744
|
});
|
|
338
745
|
}
|
|
339
746
|
}
|
|
@@ -349,8 +756,37 @@ function buildTaskGraphLayout(snapshot, tasks, options) {
|
|
|
349
756
|
};
|
|
350
757
|
}
|
|
351
758
|
|
|
352
|
-
// packages/dependency-graph-plugin/src/
|
|
353
|
-
|
|
759
|
+
// packages/dependency-graph-plugin/src/analysis/rigSelectors.ts
|
|
760
|
+
function isObjectRecord3(value) {
|
|
761
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
762
|
+
}
|
|
763
|
+
function normalizeLogin2(value) {
|
|
764
|
+
return value.trim().replace(/^@+/, "").toLowerCase();
|
|
765
|
+
}
|
|
766
|
+
function assigneeLoginsFromValue2(value) {
|
|
767
|
+
if (!Array.isArray(value))
|
|
768
|
+
return [];
|
|
769
|
+
return value.flatMap((entry) => {
|
|
770
|
+
if (typeof entry === "string" && entry.trim())
|
|
771
|
+
return [normalizeLogin2(entry)];
|
|
772
|
+
if (isObjectRecord3(entry) && typeof entry.login === "string" && entry.login.trim()) {
|
|
773
|
+
return [normalizeLogin2(entry.login)];
|
|
774
|
+
}
|
|
775
|
+
return [];
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
function readTaskAssigneeLogins2(task) {
|
|
779
|
+
const taskRecord = task;
|
|
780
|
+
const metadata = isObjectRecord3(task.metadata) ? task.metadata : null;
|
|
781
|
+
const raw = isObjectRecord3(metadata?.raw) ? metadata.raw : null;
|
|
782
|
+
return Array.from(new Set([
|
|
783
|
+
...assigneeLoginsFromValue2(taskRecord.assignees),
|
|
784
|
+
...assigneeLoginsFromValue2(metadata?.assignees),
|
|
785
|
+
...assigneeLoginsFromValue2(raw?.assignees)
|
|
786
|
+
]));
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// packages/dependency-graph-plugin/src/analysis/dependencyGraph.ts
|
|
354
790
|
function uniqueSorted(values) {
|
|
355
791
|
return Array.from(new Set(values)).sort((left, right) => left.localeCompare(right));
|
|
356
792
|
}
|
|
@@ -432,19 +868,19 @@ function detectBlockingCycles(tasks, edges) {
|
|
|
432
868
|
}
|
|
433
869
|
function buildDependencyGraphModel(tasks, options = {}) {
|
|
434
870
|
const badges = computeTaskDependencyBadges(tasks);
|
|
435
|
-
const index =
|
|
871
|
+
const index = buildTaskReferenceIndex(tasks);
|
|
436
872
|
const edges = [];
|
|
437
873
|
const unresolvedRefs = [];
|
|
438
874
|
for (const task of tasks) {
|
|
439
|
-
for (const ref of
|
|
440
|
-
const dependencyId =
|
|
875
|
+
for (const ref of readTaskMetadataStringList(task, "dependencies")) {
|
|
876
|
+
const dependencyId = resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
|
|
441
877
|
if (dependencyId)
|
|
442
878
|
edges.push({ fromTaskId: dependencyId, toTaskId: String(task.id), type: "blocks" });
|
|
443
879
|
else
|
|
444
880
|
unresolvedRefs.push(ref);
|
|
445
881
|
}
|
|
446
|
-
for (const ref of
|
|
447
|
-
const parentId =
|
|
882
|
+
for (const ref of readTaskMetadataStringList(task, "parentChildDeps")) {
|
|
883
|
+
const parentId = resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
|
|
448
884
|
if (parentId)
|
|
449
885
|
edges.push({ fromTaskId: parentId, toTaskId: String(task.id), type: "parent-child" });
|
|
450
886
|
else
|
|
@@ -454,7 +890,7 @@ function buildDependencyGraphModel(tasks, options = {}) {
|
|
|
454
890
|
const dedupedEdges = dedupeEdges(edges);
|
|
455
891
|
const nodes = tasks.map((task) => {
|
|
456
892
|
const summary = badges.get(task.id);
|
|
457
|
-
const assignees =
|
|
893
|
+
const assignees = readTaskAssigneeLogins2(task);
|
|
458
894
|
const groupKey = extractTaskGroupKey(task.title);
|
|
459
895
|
return {
|
|
460
896
|
taskId: String(task.id),
|
|
@@ -479,7 +915,7 @@ function buildDependencyGraphModel(tasks, options = {}) {
|
|
|
479
915
|
graphId: options.graphId ?? deriveGraphId(tasks),
|
|
480
916
|
nodes,
|
|
481
917
|
edges: dedupedEdges,
|
|
482
|
-
layout: buildTaskGraphLayout(
|
|
918
|
+
layout: buildTaskGraphLayout(tasks, { showParentChild: options.showParentChild ?? true }),
|
|
483
919
|
cycles: detectBlockingCycles(tasks, dedupedEdges),
|
|
484
920
|
unresolvedRefs: uniqueSorted(unresolvedRefs),
|
|
485
921
|
degraded: false,
|
|
@@ -487,9 +923,7 @@ function buildDependencyGraphModel(tasks, options = {}) {
|
|
|
487
923
|
};
|
|
488
924
|
}
|
|
489
925
|
|
|
490
|
-
// packages/dependency-graph-plugin/src/graph.ts
|
|
491
|
-
import { toTaskSummary } from "@rig/contracts";
|
|
492
|
-
import { classifyTasks } from "@rig/blocker-classifier-plugin";
|
|
926
|
+
// packages/dependency-graph-plugin/src/analysis/graph.ts
|
|
493
927
|
function overlayLayoutRunState(model, runs) {
|
|
494
928
|
if (runs.length === 0)
|
|
495
929
|
return model.layout;
|
|
@@ -572,16 +1006,7 @@ function escapeDot(value) {
|
|
|
572
1006
|
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
573
1007
|
}
|
|
574
1008
|
|
|
575
|
-
// packages/dependency-graph-plugin/src/
|
|
576
|
-
import {
|
|
577
|
-
isOperatorActiveRunStatus,
|
|
578
|
-
buildTaskReferenceIndex as buildTaskReferenceIndex3,
|
|
579
|
-
computeTaskDependencyBadges as computeTaskDependencyBadges2,
|
|
580
|
-
isTaskTerminalStatus,
|
|
581
|
-
readTaskMetadataStringList as readTaskMetadataStringList3,
|
|
582
|
-
resolveTaskReference as resolveTaskReference3
|
|
583
|
-
} from "@rig/contracts";
|
|
584
|
-
import { readTaskAssigneeLogins as readTaskAssigneeLogins2 } from "@rig/read-model-plugin";
|
|
1009
|
+
// packages/dependency-graph-plugin/src/analysis/rollupModel.ts
|
|
585
1010
|
var UNASSIGNED_EPIC = "(unassigned-epic)";
|
|
586
1011
|
var UNASSIGNED_ASSIGNEE = "(unassigned)";
|
|
587
1012
|
var HUMAN_BLOCKER_CLASSES = {
|
|
@@ -590,12 +1015,12 @@ var HUMAN_BLOCKER_CLASSES = {
|
|
|
590
1015
|
"external-input": true,
|
|
591
1016
|
unknown: true
|
|
592
1017
|
};
|
|
593
|
-
function
|
|
1018
|
+
function isObjectRecord4(value) {
|
|
594
1019
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
595
1020
|
}
|
|
596
1021
|
function readIssueType2(task) {
|
|
597
|
-
const metadata =
|
|
598
|
-
const raw =
|
|
1022
|
+
const metadata = isObjectRecord4(task.metadata) ? task.metadata : null;
|
|
1023
|
+
const raw = isObjectRecord4(metadata?.raw) ? metadata.raw : null;
|
|
599
1024
|
const value = raw?.issueType ?? metadata?.issueType;
|
|
600
1025
|
return typeof value === "string" && value.trim() ? value.trim().toLowerCase() : null;
|
|
601
1026
|
}
|
|
@@ -614,7 +1039,7 @@ function isInFlightTaskStatus(status) {
|
|
|
614
1039
|
}
|
|
615
1040
|
}
|
|
616
1041
|
function epicKeyForTask(task, tasksById, resolve) {
|
|
617
|
-
const parentRef =
|
|
1042
|
+
const parentRef = readTaskMetadataStringList(task, "parentChildDeps")[0];
|
|
618
1043
|
const parentId = parentRef ? resolve(parentRef) : null;
|
|
619
1044
|
const parent = parentId ? tasksById.get(parentId) : null;
|
|
620
1045
|
if (parent)
|
|
@@ -622,9 +1047,9 @@ function epicKeyForTask(task, tasksById, resolve) {
|
|
|
622
1047
|
return extractTaskGroupKey(task.title) ?? UNASSIGNED_EPIC;
|
|
623
1048
|
}
|
|
624
1049
|
function rollupByEpic(tasks, classifications = new Map) {
|
|
625
|
-
const index =
|
|
626
|
-
const badges =
|
|
627
|
-
const resolve = (ref) =>
|
|
1050
|
+
const index = buildTaskReferenceIndex(tasks);
|
|
1051
|
+
const badges = computeTaskDependencyBadges(tasks);
|
|
1052
|
+
const resolve = (ref) => resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
|
|
628
1053
|
const buckets = new Map;
|
|
629
1054
|
for (const task of tasks) {
|
|
630
1055
|
if (isEpicTask(task))
|
|
@@ -667,7 +1092,7 @@ function assigneesForTask(task) {
|
|
|
667
1092
|
}
|
|
668
1093
|
function rollupByAssignee(tasks, runs) {
|
|
669
1094
|
const tasksById = new Map(tasks.map((task) => [String(task.id), task]));
|
|
670
|
-
const badges =
|
|
1095
|
+
const badges = computeTaskDependencyBadges(tasks);
|
|
671
1096
|
const buckets = new Map;
|
|
672
1097
|
const ensureBucket = (assignee) => {
|
|
673
1098
|
const existing = buckets.get(assignee);
|
|
@@ -714,16 +1139,14 @@ function rollupByAssignee(tasks, runs) {
|
|
|
714
1139
|
})).toSorted((left, right) => left.assignee.localeCompare(right.assignee));
|
|
715
1140
|
}
|
|
716
1141
|
|
|
717
|
-
// packages/dependency-graph-plugin/src/rollups.ts
|
|
718
|
-
import { toTaskSummary as toTaskSummary2 } from "@rig/contracts";
|
|
719
|
-
import { classifyTasks as classifyTasks2 } from "@rig/blocker-classifier-plugin";
|
|
1142
|
+
// packages/dependency-graph-plugin/src/analysis/rollups.ts
|
|
720
1143
|
function blockerClassMap(classifications) {
|
|
721
1144
|
return new Map([...classifications.entries()].map(([taskId, classification]) => [taskId, classification.blockerClass]));
|
|
722
1145
|
}
|
|
723
1146
|
function selectWorkspaceRollups(input) {
|
|
724
|
-
const tasks = input.tasks.map((task) =>
|
|
1147
|
+
const tasks = input.tasks.map((task) => toTaskSummary(task, input.workspaceId !== undefined ? { workspaceId: input.workspaceId } : {}));
|
|
725
1148
|
const runs = input.runs ?? [];
|
|
726
|
-
const classifications = input.classifications ??
|
|
1149
|
+
const classifications = input.classifications ?? classifyTasks(input.tasks, runs, input.generatedAt !== undefined ? { generatedAt: input.generatedAt } : {}).byTaskId;
|
|
727
1150
|
return {
|
|
728
1151
|
epics: [...rollupByEpic(tasks, blockerClassMap(classifications))],
|
|
729
1152
|
assignees: [...rollupByAssignee(tasks, runs.map((run) => run.projection))],
|
|
@@ -744,165 +1167,31 @@ async function getWorkspaceRollups(projectRoot, deps) {
|
|
|
744
1167
|
...deps.workspaceId !== undefined ? { workspaceId: deps.workspaceId } : {}
|
|
745
1168
|
});
|
|
746
1169
|
}
|
|
747
|
-
|
|
748
|
-
// packages/dependency-graph-plugin/src/
|
|
749
|
-
var
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
var
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
function
|
|
759
|
-
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
760
|
-
}
|
|
761
|
-
function scoreTask(input) {
|
|
762
|
-
const priority = finiteNumber(input.priority, 3);
|
|
763
|
-
const unblockCount = Math.max(0, finiteNumber(input.unblockCount, 0));
|
|
764
|
-
const roleWeight = input.role ? ROLE_WEIGHT[input.role] ?? 0 : 0;
|
|
765
|
-
const criticalityWeight = input.criticality ? CRITICALITY_WEIGHT[input.criticality] ?? 0 : 0;
|
|
766
|
-
const validationWeight = (input.validation ?? []).some((entry) => entry.includes("test-contract") || entry.includes("test-boundary")) ? 8 : 0;
|
|
767
|
-
const queueWeight = finiteNumber(input.queueWeight, 0);
|
|
768
|
-
return unblockCount * 100 + (10 - priority) + roleWeight + criticalityWeight + validationWeight + queueWeight;
|
|
769
|
-
}
|
|
770
|
-
function rankTasks(tasks, scoreInput, idOf) {
|
|
771
|
-
return tasks.map((task) => {
|
|
772
|
-
const input = scoreInput(task);
|
|
773
|
-
return {
|
|
774
|
-
task,
|
|
775
|
-
score: scoreTask(input),
|
|
776
|
-
priority: finiteNumber(input.priority, 3),
|
|
777
|
-
unblockCount: Math.max(0, finiteNumber(input.unblockCount, 0))
|
|
778
|
-
};
|
|
779
|
-
}).toSorted((left, right) => {
|
|
780
|
-
const scoreDelta = right.score - left.score;
|
|
781
|
-
if (scoreDelta !== 0)
|
|
782
|
-
return scoreDelta;
|
|
783
|
-
const unblockDelta = right.unblockCount - left.unblockCount;
|
|
784
|
-
if (unblockDelta !== 0)
|
|
785
|
-
return unblockDelta;
|
|
786
|
-
const priorityDelta = left.priority - right.priority;
|
|
787
|
-
if (priorityDelta !== 0)
|
|
788
|
-
return priorityDelta;
|
|
789
|
-
return idOf(left.task).localeCompare(idOf(right.task));
|
|
790
|
-
});
|
|
791
|
-
}
|
|
792
|
-
// packages/dependency-graph-plugin/src/taskRanking.ts
|
|
793
|
-
import {
|
|
794
|
-
computeTaskDependencyBadges as computeTaskDependencyBadges3,
|
|
795
|
-
isTaskTerminalStatus as isTaskTerminalStatus2,
|
|
796
|
-
readTaskScope
|
|
797
|
-
} from "@rig/contracts";
|
|
798
|
-
function isObjectRecord3(value) {
|
|
1170
|
+
|
|
1171
|
+
// packages/dependency-graph-plugin/src/plugin.ts
|
|
1172
|
+
var DEPENDENCY_GRAPH_PLUGIN_NAME = "@rig/dependency-graph-plugin";
|
|
1173
|
+
var DEPENDENCY_GRAPH_CLI_ID = "dependency-graph.graph";
|
|
1174
|
+
var WORKSPACE_STATUS_CLI_ID = "dependency-graph.status";
|
|
1175
|
+
var WORKSPACE_SUMMARY_CLI_ID = "dependency-graph.summary";
|
|
1176
|
+
var DEPENDENCY_GRAPH_PANEL_ID = "dependency-graph";
|
|
1177
|
+
var EPICS_PANEL_ID = "epics";
|
|
1178
|
+
var PEOPLE_PANEL_ID = "people";
|
|
1179
|
+
var TaskSelectionCap = defineCapability(TASK_SELECTION);
|
|
1180
|
+
var RunReadModelCap = defineCapability(RUN_READ_MODEL);
|
|
1181
|
+
function isRecord(value) {
|
|
799
1182
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
800
1183
|
}
|
|
801
|
-
function
|
|
802
|
-
return
|
|
803
|
-
}
|
|
804
|
-
function priorityValue(task) {
|
|
805
|
-
return typeof task.priority === "number" && Number.isFinite(task.priority) ? task.priority : Number.MAX_SAFE_INTEGER;
|
|
806
|
-
}
|
|
807
|
-
function readTaskRole(task) {
|
|
808
|
-
if (typeof task.role === "string" && task.role.trim())
|
|
809
|
-
return task.role.trim();
|
|
810
|
-
const metadata = isObjectRecord3(task.metadata) ? task.metadata : null;
|
|
811
|
-
return typeof metadata?.role === "string" && metadata.role.trim() ? metadata.role.trim() : null;
|
|
812
|
-
}
|
|
813
|
-
function readTaskCriticality(task) {
|
|
814
|
-
const metadata = isObjectRecord3(task.metadata) ? task.metadata : null;
|
|
815
|
-
return typeof metadata?.criticality === "string" && metadata.criticality.trim() ? metadata.criticality.trim() : null;
|
|
816
|
-
}
|
|
817
|
-
function readTaskValidationKeys(task) {
|
|
818
|
-
const taskRecord = task;
|
|
819
|
-
const topLevel = readStringList(taskRecord.validationKeys);
|
|
820
|
-
if (topLevel.length > 0)
|
|
821
|
-
return topLevel;
|
|
822
|
-
const metadata = isObjectRecord3(task.metadata) ? task.metadata : null;
|
|
823
|
-
return readStringList(metadata?.validation);
|
|
824
|
-
}
|
|
825
|
-
function readTaskQueueWeight(task) {
|
|
826
|
-
const metadata = isObjectRecord3(task.metadata) ? task.metadata : null;
|
|
827
|
-
const queueWeight = metadata?.queueWeight ?? metadata?.queue_weight;
|
|
828
|
-
return typeof queueWeight === "number" && Number.isFinite(queueWeight) ? queueWeight : 0;
|
|
829
|
-
}
|
|
830
|
-
function scoreInputForTask(task, unblockCount) {
|
|
831
|
-
return {
|
|
832
|
-
priority: task.priority,
|
|
833
|
-
unblockCount,
|
|
834
|
-
role: readTaskRole(task),
|
|
835
|
-
criticality: readTaskCriticality(task),
|
|
836
|
-
validation: readTaskValidationKeys(task),
|
|
837
|
-
queueWeight: readTaskQueueWeight(task)
|
|
838
|
-
};
|
|
839
|
-
}
|
|
840
|
-
function selectNextReadyTaskByPriority(tasks, options = {}) {
|
|
841
|
-
const excluded = new Set(options.excludeTaskIds ?? []);
|
|
842
|
-
const badges = computeTaskDependencyBadges3(tasks);
|
|
843
|
-
const candidates = tasks.filter((task) => !excluded.has(task.id)).filter((task) => options.filter?.(task) ?? true).filter((task) => badges.get(task.id)?.ready === true).toSorted((left, right) => {
|
|
844
|
-
const priorityDelta = priorityValue(left) - priorityValue(right);
|
|
845
|
-
if (priorityDelta !== 0)
|
|
846
|
-
return priorityDelta;
|
|
847
|
-
const createdDelta = (left.createdAt ?? "").localeCompare(right.createdAt ?? "");
|
|
848
|
-
if (createdDelta !== 0)
|
|
849
|
-
return createdDelta;
|
|
850
|
-
return left.id.localeCompare(right.id);
|
|
851
|
-
});
|
|
852
|
-
return candidates[0] ?? null;
|
|
853
|
-
}
|
|
854
|
-
function rankReadyTasks(tasks, options = {}) {
|
|
855
|
-
const excluded = new Set(options.excludeTaskIds ?? []);
|
|
856
|
-
const activeTaskIds = new Set(options.activeTaskIds ?? []);
|
|
857
|
-
const badges = computeTaskDependencyBadges3(tasks);
|
|
858
|
-
const tasksById = new Map(tasks.map((task) => [String(task.id), task]));
|
|
859
|
-
const openBlockCount = (task) => (badges.get(task.id)?.blocks ?? []).filter((blockedTaskId) => {
|
|
860
|
-
const blockedTask = tasksById.get(blockedTaskId);
|
|
861
|
-
return blockedTask ? !isTaskTerminalStatus2(blockedTask.status) : false;
|
|
862
|
-
}).length;
|
|
863
|
-
const readyTasks = tasks.filter((task) => !excluded.has(task.id)).filter((task) => !activeTaskIds.has(task.id)).filter((task) => options.filter?.(task) ?? true).filter((task) => badges.get(task.id)?.ready === true);
|
|
864
|
-
const maxUnblockCount = Math.max(0, ...readyTasks.map(openBlockCount));
|
|
865
|
-
const selectedByMode = readyTasks.filter((task) => {
|
|
866
|
-
const unblockCount = openBlockCount(task);
|
|
867
|
-
if (options.selection === "blocking-only")
|
|
868
|
-
return unblockCount > 0;
|
|
869
|
-
if (options.selection === "max-unblock")
|
|
870
|
-
return maxUnblockCount > 0 && unblockCount === maxUnblockCount;
|
|
871
|
-
return true;
|
|
872
|
-
});
|
|
873
|
-
return rankTasks(selectedByMode, (task) => scoreInputForTask(task, openBlockCount(task)), (task) => task.id).map((entry) => ({
|
|
874
|
-
task: entry.task,
|
|
875
|
-
score: entry.score,
|
|
876
|
-
priority: entry.priority,
|
|
877
|
-
unblockCount: entry.unblockCount,
|
|
878
|
-
scope: readTaskScope(entry.task)
|
|
879
|
-
}));
|
|
1184
|
+
function panelProjectRoot(context) {
|
|
1185
|
+
return isRecord(context) && typeof context.projectRoot === "string" && context.projectRoot.length > 0 ? context.projectRoot : null;
|
|
880
1186
|
}
|
|
881
|
-
function
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
const selected = [];
|
|
888
|
-
for (const entry of ranked) {
|
|
889
|
-
if (entry.scope.some((scope) => occupiedScopes.has(scope)))
|
|
890
|
-
continue;
|
|
891
|
-
selected.push(entry.task);
|
|
892
|
-
for (const scope of entry.scope)
|
|
893
|
-
occupiedScopes.add(scope);
|
|
894
|
-
if (options.limit !== undefined && selected.length >= options.limit)
|
|
895
|
-
break;
|
|
896
|
-
}
|
|
897
|
-
return selected;
|
|
1187
|
+
async function loadDependencyGraphClientIo(projectRoot) {
|
|
1188
|
+
const [taskIo, runReadModel] = await Promise.all([
|
|
1189
|
+
requireCapabilityForRoot(projectRoot, defineCapability(TASK_IO_SERVICE_CAPABILITY), "No task-sources plugin provides task IO for this project root."),
|
|
1190
|
+
requireCapabilityForRoot(projectRoot, RunReadModelCap, "No run-worker plugin provides run read-model for this project root.")
|
|
1191
|
+
]);
|
|
1192
|
+
return { listRuns: (root) => runReadModel.listRuns({ projectRoot: root }), listTasks: taskIo.listTasks };
|
|
898
1193
|
}
|
|
899
|
-
|
|
900
|
-
import {
|
|
901
|
-
getTaskForCommand,
|
|
902
|
-
listTasksForCommand,
|
|
903
|
-
normalizeTaskId
|
|
904
|
-
} from "@rig/core/task-io";
|
|
905
|
-
var CLOSED_STATUSES = {
|
|
1194
|
+
var CLOSED_TASK_STATUSES = {
|
|
906
1195
|
closed: true,
|
|
907
1196
|
completed: true,
|
|
908
1197
|
complete: true,
|
|
@@ -912,146 +1201,53 @@ var CLOSED_STATUSES = {
|
|
|
912
1201
|
merged: true,
|
|
913
1202
|
resolved: true
|
|
914
1203
|
};
|
|
915
|
-
var
|
|
916
|
-
...
|
|
1204
|
+
var NOT_READY_TASK_STATUSES = {
|
|
1205
|
+
...CLOSED_TASK_STATUSES,
|
|
917
1206
|
blocked: true,
|
|
918
1207
|
in_progress: true,
|
|
919
1208
|
"in-progress": true,
|
|
920
1209
|
running: true,
|
|
921
1210
|
active: true
|
|
922
1211
|
};
|
|
923
|
-
function
|
|
924
|
-
return typeof
|
|
1212
|
+
function taskStatus(task) {
|
|
1213
|
+
return typeof task.status === "string" ? task.status.trim().toLowerCase() : "";
|
|
925
1214
|
}
|
|
926
|
-
function
|
|
927
|
-
const
|
|
928
|
-
return
|
|
929
|
-
}
|
|
930
|
-
function isReadyTask(task) {
|
|
931
|
-
const status = readTaskStatus(task);
|
|
932
|
-
return status.length === 0 || NOT_READY_STATUSES[status] !== true;
|
|
933
|
-
}
|
|
934
|
-
function asDependencyProjection(task) {
|
|
935
|
-
const record = task;
|
|
936
|
-
return {
|
|
937
|
-
...record,
|
|
938
|
-
status: typeof record.status === "string" && record.status.trim() ? record.status : "open",
|
|
939
|
-
priority: record.priority ?? "P2",
|
|
940
|
-
metadata: isRecord(record.metadata) ? record.metadata : {}
|
|
941
|
-
};
|
|
1215
|
+
function taskIsReady(task) {
|
|
1216
|
+
const status = taskStatus(task);
|
|
1217
|
+
return status.length === 0 || NOT_READY_TASK_STATUSES[status] !== true;
|
|
942
1218
|
}
|
|
943
|
-
function
|
|
944
|
-
|
|
945
|
-
const
|
|
946
|
-
if (
|
|
947
|
-
return
|
|
1219
|
+
function taskMatchesSelectionFilters(task, filters = {}) {
|
|
1220
|
+
if (filters.state) {
|
|
1221
|
+
const closed = CLOSED_TASK_STATUSES[taskStatus(task)] === true;
|
|
1222
|
+
if (filters.state === "closed" ? !closed : closed)
|
|
1223
|
+
return false;
|
|
948
1224
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
const lowered = trimmed.toLowerCase();
|
|
956
|
-
if (lowered === "me" || lowered === "@me")
|
|
957
|
-
return currentLogin(env) ?? "@me";
|
|
958
|
-
return trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
|
|
959
|
-
}
|
|
960
|
-
var normalizeAssigneeFilter = resolveAssignee;
|
|
961
|
-
function taskMatchesState(task, state) {
|
|
962
|
-
if (!state)
|
|
963
|
-
return true;
|
|
964
|
-
const closed = CLOSED_STATUSES[readTaskStatus(task)] === true;
|
|
965
|
-
return state === "closed" ? closed : !closed;
|
|
966
|
-
}
|
|
967
|
-
function taskMatchesSearch(task, search) {
|
|
968
|
-
const needle = search?.trim().toLowerCase();
|
|
969
|
-
if (!needle)
|
|
970
|
-
return true;
|
|
971
|
-
const title = typeof task.title === "string" ? task.title : "";
|
|
972
|
-
const body = typeof task.body === "string" ? task.body : "";
|
|
973
|
-
const description = typeof task.description === "string" ? task.description : "";
|
|
974
|
-
return `${task.id}
|
|
1225
|
+
const search = filters.search?.trim().toLowerCase();
|
|
1226
|
+
if (search) {
|
|
1227
|
+
const title = typeof task.title === "string" ? task.title : "";
|
|
1228
|
+
const body = typeof task.body === "string" ? task.body : "";
|
|
1229
|
+
const description = typeof task.description === "string" ? task.description : "";
|
|
1230
|
+
if (!`${task.id}
|
|
975
1231
|
${title}
|
|
976
1232
|
${body}
|
|
977
|
-
${description}`.toLowerCase().includes(
|
|
978
|
-
|
|
979
|
-
function collectAssigneeLogins(value) {
|
|
980
|
-
if (typeof value === "string")
|
|
981
|
-
return [value];
|
|
982
|
-
if (Array.isArray(value))
|
|
983
|
-
return value.flatMap((entry) => collectAssigneeLogins(entry));
|
|
984
|
-
if (isRecord(value))
|
|
985
|
-
return [value.login, value.username, value.name, value.id].flatMap((entry) => collectAssigneeLogins(entry));
|
|
986
|
-
return [];
|
|
987
|
-
}
|
|
988
|
-
function taskAssignees(task) {
|
|
989
|
-
const raw = isRecord(task.raw) ? task.raw : null;
|
|
990
|
-
return [
|
|
991
|
-
task.assignee,
|
|
992
|
-
task.assignees,
|
|
993
|
-
task.assignedTo,
|
|
994
|
-
task.owner,
|
|
995
|
-
raw?.assignee,
|
|
996
|
-
raw?.assignees,
|
|
997
|
-
raw?.assignedTo,
|
|
998
|
-
raw?.owner
|
|
999
|
-
].flatMap((value) => collectAssigneeLogins(value));
|
|
1000
|
-
}
|
|
1001
|
-
function taskMatchesAssignee(task, assignee) {
|
|
1002
|
-
if (!assignee)
|
|
1003
|
-
return true;
|
|
1004
|
-
const needle = assignee.startsWith("@") ? assignee.slice(1) : assignee;
|
|
1005
|
-
return taskAssignees(task).some((login) => {
|
|
1006
|
-
const normalized = login.startsWith("@") ? login.slice(1) : login;
|
|
1007
|
-
return normalized.localeCompare(needle, undefined, { sensitivity: "accent" }) === 0;
|
|
1008
|
-
});
|
|
1009
|
-
}
|
|
1010
|
-
function applyFilters(tasks, filters = {}) {
|
|
1011
|
-
const filtered = tasks.filter((task) => taskMatchesAssignee(task, resolveAssignee(filters.assignee))).filter((task) => taskMatchesState(task, filters.state)).filter((task) => taskMatchesSearch(task, filters.search));
|
|
1012
|
-
return filters.limit === undefined ? filtered : filtered.slice(0, filters.limit);
|
|
1013
|
-
}
|
|
1014
|
-
function selectNextReadyTask(tasks, filters = {}) {
|
|
1015
|
-
const matching = applyFilters(tasks, { ...filters, limit: undefined }).filter(isReadyTask);
|
|
1016
|
-
const selected = selectNextReadyTaskByPriority(matching.map(asDependencyProjection), {
|
|
1017
|
-
filter: (task) => isReadyTask(task)
|
|
1018
|
-
});
|
|
1019
|
-
return selected ?? matching[0] ?? null;
|
|
1020
|
-
}
|
|
1021
|
-
var selectNextTask = selectNextReadyTask;
|
|
1022
|
-
async function resolveStartTask(input) {
|
|
1023
|
-
if (input.next) {
|
|
1024
|
-
const task2 = selectNextReadyTask(await listTasksForCommand(input.projectRoot, { listTasks: input.listTasks }), input.filters ?? {});
|
|
1025
|
-
const taskId2 = normalizeTaskId(task2?.id);
|
|
1026
|
-
if (!task2 || !taskId2)
|
|
1027
|
-
throw new Error("No ready task found.");
|
|
1028
|
-
return { taskId: taskId2, task: task2 };
|
|
1233
|
+
${description}`.toLowerCase().includes(search))
|
|
1234
|
+
return false;
|
|
1029
1235
|
}
|
|
1030
|
-
const
|
|
1031
|
-
if (!
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
function isRecord2(value) {
|
|
1046
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1047
|
-
}
|
|
1048
|
-
function panelProjectRoot(context) {
|
|
1049
|
-
return isRecord2(context) && typeof context.projectRoot === "string" && context.projectRoot.length > 0 ? context.projectRoot : null;
|
|
1050
|
-
}
|
|
1051
|
-
async function loadDependencyGraphClientIo() {
|
|
1052
|
-
const { listTasks } = await import("@rig/core/task-io");
|
|
1053
|
-
const { listRuns } = await import("@rig/run-worker/runs");
|
|
1054
|
-
return { listRuns, listTasks };
|
|
1236
|
+
const assignee = filters.assignee?.trim().replace(/^@+/, "").toLowerCase();
|
|
1237
|
+
if (assignee && !readTaskAssigneeLogins(toTaskDependencyProjection(task)).some((login) => login.replace(/^@+/, "").toLowerCase() === assignee))
|
|
1238
|
+
return false;
|
|
1239
|
+
return true;
|
|
1240
|
+
}
|
|
1241
|
+
function selectReadyTaskSummary(tasks, filters = {}) {
|
|
1242
|
+
const candidates = tasks.filter((task) => taskMatchesSelectionFilters(task, filters)).filter(taskIsReady).map((task) => toTaskSummary(task));
|
|
1243
|
+
const badges = computeTaskDependencyBadges(candidates);
|
|
1244
|
+
const ready = candidates.filter((task) => badges.get(task.id)?.ready === true);
|
|
1245
|
+
const selected = selectNextReadyTaskByPriority(candidates);
|
|
1246
|
+
return { selected: selected ?? candidates[0] ?? null, ready };
|
|
1247
|
+
}
|
|
1248
|
+
async function selectNextReadyTaskForProject(input) {
|
|
1249
|
+
const { listTasks } = await loadDependencyGraphClientIo(input.projectRoot);
|
|
1250
|
+
return selectReadyTaskSummary(await listTasks(input.projectRoot), input.filters);
|
|
1055
1251
|
}
|
|
1056
1252
|
function toDependencyGraphPanelPayload(model) {
|
|
1057
1253
|
const layoutByTaskId = new Map(model.layout.nodes.map((node) => [node.taskId, node]));
|
|
@@ -1080,9 +1276,9 @@ function toDependencyGraphPanelPayload(model) {
|
|
|
1080
1276
|
degraded: model.degraded ? "dependency graph projection degraded" : null
|
|
1081
1277
|
};
|
|
1082
1278
|
}
|
|
1083
|
-
function toWorkspaceRollupsPanelPayload(
|
|
1279
|
+
function toWorkspaceRollupsPanelPayload(rollups) {
|
|
1084
1280
|
return {
|
|
1085
|
-
epics:
|
|
1281
|
+
epics: rollups.epics.map((epic) => ({
|
|
1086
1282
|
epicKey: epic.epicKey,
|
|
1087
1283
|
title: epic.epicKey,
|
|
1088
1284
|
total: epic.total,
|
|
@@ -1091,7 +1287,7 @@ function toWorkspaceRollupsPanelPayload(rollups2) {
|
|
|
1091
1287
|
inFlightCount: epic.inFlightCount,
|
|
1092
1288
|
humanBlockedCount: epic.humanBlockedCount
|
|
1093
1289
|
})),
|
|
1094
|
-
people:
|
|
1290
|
+
people: rollups.assignees.map((assignee) => ({
|
|
1095
1291
|
assignee: assignee.assignee,
|
|
1096
1292
|
openCount: assignee.openTaskCount,
|
|
1097
1293
|
inFlightCount: assignee.inFlightRunCount,
|
|
@@ -1105,7 +1301,7 @@ async function produceDependencyGraphPanel(context) {
|
|
|
1105
1301
|
const projectRoot = panelProjectRoot(context);
|
|
1106
1302
|
if (!projectRoot)
|
|
1107
1303
|
return;
|
|
1108
|
-
const { listRuns, listTasks } = await loadDependencyGraphClientIo();
|
|
1304
|
+
const { listRuns, listTasks } = await loadDependencyGraphClientIo(projectRoot);
|
|
1109
1305
|
const model = await getWorkspaceDependencyGraph(projectRoot, {
|
|
1110
1306
|
listTasks,
|
|
1111
1307
|
listRuns,
|
|
@@ -1117,13 +1313,13 @@ async function produceWorkspaceRollupsPanel(context) {
|
|
|
1117
1313
|
const projectRoot = panelProjectRoot(context);
|
|
1118
1314
|
if (!projectRoot)
|
|
1119
1315
|
return;
|
|
1120
|
-
const { listRuns, listTasks } = await loadDependencyGraphClientIo();
|
|
1121
|
-
const
|
|
1316
|
+
const { listRuns, listTasks } = await loadDependencyGraphClientIo(projectRoot);
|
|
1317
|
+
const rollups = await getWorkspaceRollups(projectRoot, {
|
|
1122
1318
|
listTasks,
|
|
1123
1319
|
listRuns,
|
|
1124
1320
|
classifyBlockers: (root) => classifyWorkspaceBlockers(root, { listTasks, listRuns })
|
|
1125
1321
|
});
|
|
1126
|
-
return toWorkspaceRollupsPanelPayload(
|
|
1322
|
+
return toWorkspaceRollupsPanelPayload(rollups);
|
|
1127
1323
|
}
|
|
1128
1324
|
function printJson(value) {
|
|
1129
1325
|
console.log(JSON.stringify(value, null, 2));
|
|
@@ -1171,7 +1367,7 @@ async function executeGraph(context, args) {
|
|
|
1171
1367
|
requireNoExtraArgs(json.rest, "rig graph [--json|--dot]");
|
|
1172
1368
|
if (dot.value && json.value)
|
|
1173
1369
|
throw new Error("Pass only one of --json or --dot.");
|
|
1174
|
-
const { listRuns, listTasks } = await loadDependencyGraphClientIo();
|
|
1370
|
+
const { listRuns, listTasks } = await loadDependencyGraphClientIo(context.projectRoot);
|
|
1175
1371
|
const model = await getWorkspaceDependencyGraph(context.projectRoot, {
|
|
1176
1372
|
listTasks,
|
|
1177
1373
|
listRuns,
|
|
@@ -1192,24 +1388,24 @@ async function executeStatus(context, args) {
|
|
|
1192
1388
|
const json = takeFlag(args, "--json");
|
|
1193
1389
|
const epic = takeOption(json.rest, "--epic");
|
|
1194
1390
|
requireNoExtraArgs(epic.rest, "rig status [--epic <key>] [--json]");
|
|
1195
|
-
const { listRuns, listTasks } = await loadDependencyGraphClientIo();
|
|
1196
|
-
const [
|
|
1391
|
+
const { listRuns, listTasks } = await loadDependencyGraphClientIo(context.projectRoot);
|
|
1392
|
+
const [rollups, blockers, runs] = await Promise.all([
|
|
1197
1393
|
getWorkspaceRollups(context.projectRoot, { listTasks, listRuns }),
|
|
1198
1394
|
classifyWorkspaceBlockers(context.projectRoot, { listTasks, listRuns }),
|
|
1199
1395
|
listRuns(context.projectRoot)
|
|
1200
1396
|
]);
|
|
1201
|
-
const filteredEpics = epic.value ?
|
|
1397
|
+
const filteredEpics = epic.value ? rollups.epics.filter((entry) => entry.epicKey === epic.value) : rollups.epics;
|
|
1202
1398
|
const details = {
|
|
1203
1399
|
epic: epic.value ?? null,
|
|
1204
1400
|
epicRollups: filteredEpics,
|
|
1205
1401
|
epics: filteredEpics.length,
|
|
1206
|
-
assignees:
|
|
1402
|
+
assignees: rollups.assignees.length,
|
|
1207
1403
|
humanBlockers: blockers.human.length,
|
|
1208
1404
|
machineBlockers: blockers.machine.length,
|
|
1209
1405
|
runs: runs.length,
|
|
1210
1406
|
activeRuns: runs.filter((run) => run.live && !run.stale).length,
|
|
1211
1407
|
needsAttention: runs.filter((run) => run.status === "needs-attention" || run.pendingApprovals > 0 || run.pendingInputs > 0).length,
|
|
1212
|
-
generatedAt:
|
|
1408
|
+
generatedAt: rollups.generatedAt
|
|
1213
1409
|
};
|
|
1214
1410
|
if (context.outputMode === "text") {
|
|
1215
1411
|
if (json.value)
|
|
@@ -1224,9 +1420,9 @@ async function executeSummary(context, args) {
|
|
|
1224
1420
|
const json = takeFlag(args, "--json");
|
|
1225
1421
|
const epicOpt = takeOption(json.rest, "--epic");
|
|
1226
1422
|
requireNoExtraArgs(epicOpt.rest, "rig summary [--epic <key>] [--json]");
|
|
1227
|
-
const { listRuns, listTasks } = await loadDependencyGraphClientIo();
|
|
1228
|
-
const
|
|
1229
|
-
const details = epicOpt.value ? { ...
|
|
1423
|
+
const { listRuns, listTasks } = await loadDependencyGraphClientIo(context.projectRoot);
|
|
1424
|
+
const rollups = await getWorkspaceRollups(context.projectRoot, { listTasks, listRuns });
|
|
1425
|
+
const details = epicOpt.value ? { ...rollups, epics: rollups.epics.filter((epic) => epic.epicKey === epicOpt.value) } : rollups;
|
|
1230
1426
|
if (context.outputMode === "text") {
|
|
1231
1427
|
if (json.value)
|
|
1232
1428
|
printJson(details);
|
|
@@ -1275,7 +1471,8 @@ var dependencyGraphPlugin = definePlugin({
|
|
|
1275
1471
|
capabilities: [
|
|
1276
1472
|
{ id: "workspace.dependency-graph", title: "Workspace dependency graph", commandId: DEPENDENCY_GRAPH_CLI_ID, panelId: DEPENDENCY_GRAPH_PANEL_ID },
|
|
1277
1473
|
{ id: "workspace.status", title: "Workspace status", commandId: WORKSPACE_STATUS_CLI_ID },
|
|
1278
|
-
{ id: "workspace.rollups", title: "Workspace rollups", commandId: WORKSPACE_SUMMARY_CLI_ID, panelId: EPICS_PANEL_ID }
|
|
1474
|
+
{ id: "workspace.rollups", title: "Workspace rollups", commandId: WORKSPACE_SUMMARY_CLI_ID, panelId: EPICS_PANEL_ID },
|
|
1475
|
+
TaskSelectionCap.provide({ selectNextReadyTask: selectNextReadyTaskForProject }, { title: "Task selection", description: "Select the next ready task from the configured task source." })
|
|
1279
1476
|
],
|
|
1280
1477
|
panels: [
|
|
1281
1478
|
{ id: DEPENDENCY_GRAPH_PANEL_ID, slot: "capability", title: "Dependency graph", capabilityId: "workspace.dependency-graph", produce: produceDependencyGraphPanel },
|
|
@@ -1289,38 +1486,12 @@ function createDependencyGraphPlugin() {
|
|
|
1289
1486
|
return dependencyGraphPlugin;
|
|
1290
1487
|
}
|
|
1291
1488
|
export {
|
|
1292
|
-
taskMatchesState,
|
|
1293
|
-
taskMatchesSearch,
|
|
1294
|
-
taskMatchesAssignee,
|
|
1295
|
-
taskAssignees,
|
|
1296
|
-
selectWorkspaceRollups,
|
|
1297
|
-
selectRankedReadyTasks,
|
|
1298
|
-
selectNextTask,
|
|
1299
|
-
selectNextReadyTaskByPriority,
|
|
1300
|
-
selectNextReadyTask,
|
|
1301
|
-
scoreTask,
|
|
1302
|
-
resolveStartTask,
|
|
1303
|
-
resolveAssignee,
|
|
1304
|
-
readTaskStatus,
|
|
1305
|
-
rankTasks,
|
|
1306
|
-
rankReadyTasks,
|
|
1307
|
-
normalizeAssigneeFilter,
|
|
1308
|
-
isReadyTask,
|
|
1309
|
-
getWorkspaceStatus,
|
|
1310
|
-
getWorkspaceRollups,
|
|
1311
|
-
getWorkspaceDependencyGraph,
|
|
1312
|
-
formatDependencyGraphDot,
|
|
1313
1489
|
executeSummary,
|
|
1314
1490
|
executeStatus,
|
|
1315
1491
|
executeGraph,
|
|
1316
1492
|
dependencyGraphPlugin,
|
|
1317
1493
|
dependencyGraphCliCommands,
|
|
1318
|
-
currentLogin,
|
|
1319
1494
|
createDependencyGraphPlugin,
|
|
1320
|
-
collectAssigneeLogins,
|
|
1321
|
-
buildWorkspaceDependencyGraph,
|
|
1322
|
-
asDependencyProjection,
|
|
1323
|
-
applyFilters,
|
|
1324
1495
|
WORKSPACE_SUMMARY_CLI_ID,
|
|
1325
1496
|
WORKSPACE_STATUS_CLI_ID,
|
|
1326
1497
|
PEOPLE_PANEL_ID,
|