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