@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.
Files changed (31) hide show
  1. package/dist/src/analysis/blockers.d.ts +35 -0
  2. package/dist/src/analysis/blockers.js +355 -0
  3. package/dist/src/{graph-model → analysis}/dependencyGraph.d.ts +1 -2
  4. package/dist/src/{graph.js → analysis/dependencyGraph.js} +256 -128
  5. package/dist/src/{graph.d.ts → analysis/graph.d.ts} +3 -4
  6. package/dist/src/analysis/graph.js +944 -0
  7. package/dist/src/analysis/rigSelectors.d.ts +26 -0
  8. package/dist/src/analysis/rigSelectors.js +172 -0
  9. package/dist/src/analysis/rollupModel.js +401 -0
  10. package/dist/src/{rollups.d.ts → analysis/rollups.d.ts} +2 -4
  11. package/dist/src/analysis/rollups.js +589 -0
  12. package/dist/src/{graph-model → analysis}/taskGraphCodes.js +1 -1
  13. package/dist/src/{graph-model → analysis}/taskGraphLayout.d.ts +2 -2
  14. package/dist/src/{graph-model → analysis}/taskGraphLayout.js +75 -29
  15. package/dist/src/analysis/taskGraphPrimitives.d.ts +58 -0
  16. package/dist/src/analysis/taskGraphPrimitives.js +528 -0
  17. package/dist/src/index.js +567 -396
  18. package/dist/src/plugin.d.ts +1 -11
  19. package/dist/src/plugin.js +567 -396
  20. package/package.json +3 -6
  21. package/dist/src/graph-model/dependencyGraph.js +0 -485
  22. package/dist/src/graph-model/rollups.js +0 -166
  23. package/dist/src/rollups.js +0 -199
  24. package/dist/src/taskRanking.d.ts +0 -24
  25. package/dist/src/taskRanking.js +0 -160
  26. package/dist/src/taskScore.d.ts +0 -17
  27. package/dist/src/taskScore.js +0 -49
  28. package/dist/src/taskSelection.d.ts +0 -33
  29. package/dist/src/taskSelection.js +0 -181
  30. /package/dist/src/{graph-model/rollups.d.ts → analysis/rollupModel.d.ts} +0 -0
  31. /package/dist/src/{graph-model → analysis}/taskGraphCodes.d.ts +0 -0
@@ -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 { classifyWorkspaceBlockers } from "@rig/blocker-classifier-plugin";
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/graph-model/dependencyGraph.ts
9
- import { computeTaskDependencyBadges, readTaskMetadataStringList as readTaskMetadataStringList2, resolveTaskReference as resolveTaskReference2, buildTaskReferenceIndex as buildTaskReferenceIndex2 } from "@rig/contracts";
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/graph-model/taskGraphCodes.ts
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/graph-model/taskGraphLayout.ts
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 isObjectRecord(value) {
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 = isObjectRecord(task.metadata) ? task.metadata : null;
480
+ const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
66
481
  if (typeof metadata?.issueType === "string")
67
482
  return metadata.issueType;
68
- const raw = isObjectRecord(metadata?.raw) ? metadata.raw : null;
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(snapshot, tasks, options) {
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: runs.length,
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/graph-model/dependencyGraph.ts
353
- import { readTaskAssigneeLogins } from "@rig/read-model-plugin";
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 = buildTaskReferenceIndex2(tasks);
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 readTaskMetadataStringList2(task, "dependencies")) {
440
- const dependencyId = resolveTaskReference2(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
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 readTaskMetadataStringList2(task, "parentChildDeps")) {
447
- const parentId = resolveTaskReference2(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
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 = readTaskAssigneeLogins(task);
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(options.snapshot ?? null, tasks, { showParentChild: options.showParentChild ?? true }),
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/graph-model/rollups.ts
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 isObjectRecord2(value) {
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 = isObjectRecord2(task.metadata) ? task.metadata : null;
598
- const raw = isObjectRecord2(metadata?.raw) ? metadata.raw : null;
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 = readTaskMetadataStringList3(task, "parentChildDeps")[0];
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 = buildTaskReferenceIndex3(tasks);
626
- const badges = computeTaskDependencyBadges2(tasks);
627
- const resolve = (ref) => resolveTaskReference3(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
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 = computeTaskDependencyBadges2(tasks);
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) => toTaskSummary2(task, input.workspaceId !== undefined ? { workspaceId: input.workspaceId } : {}));
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 ?? classifyTasks2(input.tasks, runs, input.generatedAt !== undefined ? { generatedAt: input.generatedAt } : {}).byTaskId;
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
- var getWorkspaceStatus = getWorkspaceRollups;
748
- // packages/dependency-graph-plugin/src/taskScore.ts
749
- var ROLE_WEIGHT = {
750
- architect: 25,
751
- extractor: 10
752
- };
753
- var CRITICALITY_WEIGHT = {
754
- core: 20,
755
- high: 10,
756
- normal: 0
757
- };
758
- function finiteNumber(value, fallback) {
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 readStringList(value) {
802
- return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
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 selectRankedReadyTasks(tasks, options = {}) {
882
- const ranked = rankReadyTasks(tasks, options);
883
- if (options.requireDisjointScopes !== true && !options.disjointWithScopes) {
884
- return ranked.slice(0, options.limit).map((entry) => entry.task);
885
- }
886
- const occupiedScopes = new Set(options.disjointWithScopes ?? []);
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
- // packages/dependency-graph-plugin/src/taskSelection.ts
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 NOT_READY_STATUSES = {
916
- ...CLOSED_STATUSES,
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 isRecord(value) {
924
- return typeof value === "object" && value !== null && !Array.isArray(value);
1212
+ function taskStatus(task) {
1213
+ return typeof task.status === "string" ? task.status.trim().toLowerCase() : "";
925
1214
  }
926
- function readTaskStatus(task) {
927
- const value = task.status;
928
- return typeof value === "string" ? value.trim().toLowerCase() : "";
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 currentLogin(env = process.env) {
944
- for (const key of ["RIG_GITHUB_LOGIN", "GITHUB_ACTOR", "GH_USER", "USER", "USERNAME"]) {
945
- const value = env[key]?.trim();
946
- if (value)
947
- return value;
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
- return;
950
- }
951
- function resolveAssignee(value, env = process.env) {
952
- const trimmed = value?.trim();
953
- if (!trimmed)
954
- return;
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(needle);
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 taskId = normalizeTaskId(input.taskId ?? undefined);
1031
- if (!taskId)
1032
- throw new Error("Missing task id.");
1033
- const task = await getTaskForCommand(input.projectRoot, taskId, { getTask: input.getTask }).catch(() => null);
1034
- return { taskId, task };
1035
- }
1036
-
1037
- // packages/dependency-graph-plugin/src/plugin.ts
1038
- var DEPENDENCY_GRAPH_PLUGIN_NAME = "@rig/dependency-graph-plugin";
1039
- var DEPENDENCY_GRAPH_CLI_ID = "dependency-graph.graph";
1040
- var WORKSPACE_STATUS_CLI_ID = "dependency-graph.status";
1041
- var WORKSPACE_SUMMARY_CLI_ID = "dependency-graph.summary";
1042
- var DEPENDENCY_GRAPH_PANEL_ID = "dependency-graph";
1043
- var EPICS_PANEL_ID = "epics";
1044
- var PEOPLE_PANEL_ID = "people";
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(rollups2) {
1279
+ function toWorkspaceRollupsPanelPayload(rollups) {
1084
1280
  return {
1085
- epics: rollups2.epics.map((epic) => ({
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: rollups2.assignees.map((assignee) => ({
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 rollups2 = await getWorkspaceRollups(projectRoot, {
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(rollups2);
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 [rollups2, blockers, runs] = await Promise.all([
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 ? rollups2.epics.filter((entry) => entry.epicKey === epic.value) : rollups2.epics;
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: rollups2.assignees.length,
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: rollups2.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 rollups2 = await getWorkspaceRollups(context.projectRoot, { listTasks, listRuns });
1229
- const details = epicOpt.value ? { ...rollups2, epics: rollups2.epics.filter((epic) => epic.epicKey === epicOpt.value) } : rollups2;
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 },
@@ -1290,39 +1487,13 @@ function createDependencyGraphPlugin() {
1290
1487
  }
1291
1488
  var plugin_default = dependencyGraphPlugin;
1292
1489
  export {
1293
- taskMatchesState,
1294
- taskMatchesSearch,
1295
- taskMatchesAssignee,
1296
- taskAssignees,
1297
- selectWorkspaceRollups,
1298
- selectRankedReadyTasks,
1299
- selectNextTask,
1300
- selectNextReadyTaskByPriority,
1301
- selectNextReadyTask,
1302
- scoreTask,
1303
- resolveStartTask,
1304
- resolveAssignee,
1305
- readTaskStatus,
1306
- rankTasks,
1307
- rankReadyTasks,
1308
- normalizeAssigneeFilter,
1309
- isReadyTask,
1310
- getWorkspaceStatus,
1311
- getWorkspaceRollups,
1312
- getWorkspaceDependencyGraph,
1313
- formatDependencyGraphDot,
1314
1490
  executeSummary,
1315
1491
  executeStatus,
1316
1492
  executeGraph,
1317
1493
  dependencyGraphPlugin,
1318
1494
  dependencyGraphCliCommands,
1319
1495
  plugin_default as default,
1320
- currentLogin,
1321
1496
  createDependencyGraphPlugin,
1322
- collectAssigneeLogins,
1323
- buildWorkspaceDependencyGraph,
1324
- asDependencyProjection,
1325
- applyFilters,
1326
1497
  WORKSPACE_SUMMARY_CLI_ID,
1327
1498
  WORKSPACE_STATUS_CLI_ID,
1328
1499
  PEOPLE_PANEL_ID,