@h-rig/dependency-graph-plugin 0.0.6-alpha.135 → 0.0.6-alpha.138

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/index.js CHANGED
@@ -1,28 +1,280 @@
1
1
  // @bun
2
2
  // packages/dependency-graph-plugin/src/plugin.ts
3
3
  import { definePlugin } from "@rig/core";
4
- import { buildDependencyGraphModel } from "@rig/core/dependency-graph";
4
+ import {
5
+ classifyWorkspaceBlockers,
6
+ formatDependencyGraphDot,
7
+ getWorkspaceDependencyGraph,
8
+ getWorkspaceRollups,
9
+ listRuns,
10
+ listTasks
11
+ } from "@rig/client";
5
12
  var DEPENDENCY_GRAPH_PLUGIN_NAME = "@rig/dependency-graph-plugin";
6
- function projectDependencyGraph(tasks, options = {}) {
7
- return buildDependencyGraphModel(tasks, options);
13
+ var DEPENDENCY_GRAPH_CLI_ID = "dependency-graph.graph";
14
+ var WORKSPACE_STATUS_CLI_ID = "dependency-graph.status";
15
+ var WORKSPACE_SUMMARY_CLI_ID = "dependency-graph.summary";
16
+ var DEPENDENCY_GRAPH_PANEL_ID = "dependency-graph";
17
+ var EPICS_PANEL_ID = "epics";
18
+ var PEOPLE_PANEL_ID = "people";
19
+ function isRecord(value) {
20
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8
21
  }
22
+ function panelProjectRoot(context) {
23
+ return isRecord(context) && typeof context.projectRoot === "string" && context.projectRoot.length > 0 ? context.projectRoot : null;
24
+ }
25
+ function toDependencyGraphPanelPayload(model) {
26
+ const layoutByTaskId = new Map(model.layout.nodes.map((node) => [node.taskId, node]));
27
+ return {
28
+ nodes: model.nodes.map((node) => {
29
+ const layout = layoutByTaskId.get(node.taskId);
30
+ return {
31
+ id: node.taskId,
32
+ title: node.title,
33
+ status: node.status,
34
+ epicKey: node.epicKey,
35
+ assignee: node.assignee,
36
+ blockerClass: node.blockerClass,
37
+ blockingDepth: node.blockingDepth,
38
+ layout: layout ? { x: layout.x, y: layout.y, width: layout.width, height: layout.height } : undefined
39
+ };
40
+ }),
41
+ edges: model.edges.map((edge) => ({
42
+ id: `${edge.fromTaskId}:${edge.toTaskId}:${edge.type}`,
43
+ from: edge.fromTaskId,
44
+ to: edge.toTaskId,
45
+ type: edge.type
46
+ })),
47
+ cycles: model.cycles,
48
+ unresolvedRefs: model.unresolvedRefs,
49
+ degraded: model.degraded ? "dependency graph projection degraded" : null
50
+ };
51
+ }
52
+ function toWorkspaceRollupsPanelPayload(rollups) {
53
+ return {
54
+ epics: rollups.epics.map((epic) => ({
55
+ epicKey: epic.epicKey,
56
+ title: epic.epicKey,
57
+ total: epic.total,
58
+ percentComplete: epic.percentComplete,
59
+ blockedCount: epic.blockedCount,
60
+ inFlightCount: epic.inFlightCount,
61
+ humanBlockedCount: epic.humanBlockedCount
62
+ })),
63
+ people: rollups.assignees.map((assignee) => ({
64
+ assignee: assignee.assignee,
65
+ openCount: assignee.openTaskCount,
66
+ inFlightCount: assignee.inFlightRunCount,
67
+ prReviewCount: assignee.prsAwaitingReview,
68
+ humanBlockedCount: assignee.blockers.length,
69
+ activeRunIds: []
70
+ }))
71
+ };
72
+ }
73
+ async function produceDependencyGraphPanel(context) {
74
+ const projectRoot = panelProjectRoot(context);
75
+ if (!projectRoot)
76
+ return;
77
+ const model = await getWorkspaceDependencyGraph(projectRoot, {
78
+ listTasks,
79
+ listRuns,
80
+ classifyBlockers: (root) => classifyWorkspaceBlockers(root, { listTasks, listRuns })
81
+ });
82
+ return toDependencyGraphPanelPayload(model);
83
+ }
84
+ async function produceWorkspaceRollupsPanel(context) {
85
+ const projectRoot = panelProjectRoot(context);
86
+ if (!projectRoot)
87
+ return;
88
+ const rollups = await getWorkspaceRollups(projectRoot, {
89
+ listTasks,
90
+ listRuns,
91
+ classifyBlockers: (root) => classifyWorkspaceBlockers(root, { listTasks, listRuns })
92
+ });
93
+ return toWorkspaceRollupsPanelPayload(rollups);
94
+ }
95
+ function printJson(value) {
96
+ console.log(JSON.stringify(value, null, 2));
97
+ }
98
+ function takeFlag(args, flag) {
99
+ const rest = [...args];
100
+ const index = rest.indexOf(flag);
101
+ if (index < 0)
102
+ return { value: false, rest };
103
+ rest.splice(index, 1);
104
+ return { value: true, rest };
105
+ }
106
+ function takeOption(args, flag) {
107
+ const rest = [...args];
108
+ const index = rest.indexOf(flag);
109
+ if (index < 0)
110
+ return { rest };
111
+ const value = rest[index + 1];
112
+ if (!value || value.startsWith("-"))
113
+ throw new Error(`${flag} requires a value.`);
114
+ rest.splice(index, 2);
115
+ return { value, rest };
116
+ }
117
+ function requireNoExtraArgs(args, usage) {
118
+ if (args.length > 0)
119
+ throw new Error(`Unexpected argument: ${args[0]}
120
+ Usage: ${usage}`);
121
+ }
122
+ function graphSummary(model) {
123
+ const blocked = model.nodes.filter((node) => node.blockedBy.length > 0 || node.blockerClass === "task-blocked").length;
124
+ return [
125
+ `graph ${model.graphId}`,
126
+ `tasks: ${model.nodes.length}`,
127
+ `edges: ${model.edges.length}`,
128
+ `blocked tasks: ${blocked}`,
129
+ `human blockers: ${model.nodes.filter((node) => node.blockerClass && node.blockerClass !== "not-blocked" && node.blockerClass !== "task-blocked").length}`,
130
+ `cycles: ${model.cycles.length}`,
131
+ `unresolved refs: ${model.unresolvedRefs.length}`
132
+ ].join(`
133
+ `);
134
+ }
135
+ async function executeGraph(context, args) {
136
+ const dot = takeFlag(args, "--dot");
137
+ const json = takeFlag(dot.rest, "--json");
138
+ requireNoExtraArgs(json.rest, "rig graph [--json|--dot]");
139
+ if (dot.value && json.value)
140
+ throw new Error("Pass only one of --json or --dot.");
141
+ const model = await getWorkspaceDependencyGraph(context.projectRoot, {
142
+ listTasks,
143
+ listRuns,
144
+ classifyBlockers: (projectRoot) => classifyWorkspaceBlockers(projectRoot, { listTasks, listRuns })
145
+ });
146
+ const dotText = formatDependencyGraphDot(model);
147
+ if (context.outputMode === "text") {
148
+ if (dot.value)
149
+ console.log(dotText);
150
+ else if (json.value)
151
+ printJson(model);
152
+ else
153
+ console.log(graphSummary(model));
154
+ }
155
+ return { ok: true, group: "graph", command: dot.value ? "dot" : "show", details: { graph: model, dot: dotText } };
156
+ }
157
+ async function executeStatus(context, args) {
158
+ const json = takeFlag(args, "--json");
159
+ const epic = takeOption(json.rest, "--epic");
160
+ requireNoExtraArgs(epic.rest, "rig status [--epic <key>] [--json]");
161
+ const [rollups, blockers, runs] = await Promise.all([
162
+ getWorkspaceRollups(context.projectRoot, { listTasks, listRuns }),
163
+ classifyWorkspaceBlockers(context.projectRoot, { listTasks, listRuns }),
164
+ listRuns(context.projectRoot)
165
+ ]);
166
+ const filteredEpics = epic.value ? rollups.epics.filter((entry) => entry.epicKey === epic.value) : rollups.epics;
167
+ const details = {
168
+ epic: epic.value ?? null,
169
+ epicRollups: filteredEpics,
170
+ epics: filteredEpics.length,
171
+ assignees: rollups.assignees.length,
172
+ humanBlockers: blockers.human.length,
173
+ machineBlockers: blockers.machine.length,
174
+ runs: runs.length,
175
+ activeRuns: runs.filter((run) => run.live && !run.stale).length,
176
+ needsAttention: runs.filter((run) => run.status === "needs-attention" || run.pendingApprovals > 0 || run.pendingInputs > 0).length,
177
+ generatedAt: rollups.generatedAt
178
+ };
179
+ if (context.outputMode === "text") {
180
+ if (json.value)
181
+ printJson(details);
182
+ else
183
+ console.log([`epics: ${details.epics}`, `assignees: ${details.assignees}`, `blockers: ${details.humanBlockers} human, ${details.machineBlockers} machine`, `runs: ${details.runs} total, ${details.activeRuns} active, ${details.needsAttention} need attention`].join(`
184
+ `));
185
+ }
186
+ return { ok: true, group: "status", command: "show", details };
187
+ }
188
+ async function executeSummary(context, args) {
189
+ const json = takeFlag(args, "--json");
190
+ const epicOpt = takeOption(json.rest, "--epic");
191
+ requireNoExtraArgs(epicOpt.rest, "rig summary [--epic <key>] [--json]");
192
+ const rollups = await getWorkspaceRollups(context.projectRoot, { listTasks, listRuns });
193
+ const details = epicOpt.value ? { ...rollups, epics: rollups.epics.filter((epic) => epic.epicKey === epicOpt.value) } : rollups;
194
+ if (context.outputMode === "text") {
195
+ if (json.value)
196
+ printJson(details);
197
+ else
198
+ console.log([
199
+ ...details.epics.map((epic) => `epic ${epic.epicKey}: ${epic.percentComplete}% complete, ${epic.blockedCount} blocked`),
200
+ ...details.assignees.map((assignee) => `${assignee.assignee}: ${assignee.openTaskCount} open, ${assignee.inFlightRunCount} in flight`)
201
+ ].join(`
202
+ `) || "No rollups.");
203
+ }
204
+ return { ok: true, group: "summary", command: "show", details };
205
+ }
206
+ var dependencyGraphCliCommands = [
207
+ {
208
+ id: DEPENDENCY_GRAPH_CLI_ID,
209
+ family: "graph",
210
+ command: "rig graph [--json|--dot]",
211
+ description: "Render task dependency graph from the active task source.",
212
+ usage: "rig graph [--json|--dot]",
213
+ projectRequired: true,
214
+ run: executeGraph
215
+ },
216
+ {
217
+ id: WORKSPACE_STATUS_CLI_ID,
218
+ family: "status",
219
+ command: "rig status [--epic <key>] [--json]",
220
+ description: "Summarize workspace epics, blockers, and active runs.",
221
+ usage: "rig status [--epic <key>] [--json]",
222
+ projectRequired: true,
223
+ run: executeStatus
224
+ },
225
+ {
226
+ id: WORKSPACE_SUMMARY_CLI_ID,
227
+ family: "summary",
228
+ command: "rig summary [--epic <key>] [--json]",
229
+ description: "Print workspace rollups by epic and assignee.",
230
+ usage: "rig summary [--epic <key>] [--json]",
231
+ projectRequired: true,
232
+ run: executeSummary
233
+ }
234
+ ];
9
235
  var dependencyGraphPlugin = definePlugin({
10
236
  name: DEPENDENCY_GRAPH_PLUGIN_NAME,
11
237
  version: "0.0.0-alpha.1",
12
- provides: [],
13
- requires: [],
14
238
  contributes: {
15
- cliCommands: [
16
- {
17
- id: "dependency-graph.show",
18
- command: "rig graph",
19
- description: "Render or export the task dependency graph projection."
20
- }
21
- ]
239
+ capabilities: [
240
+ { id: "workspace.dependency-graph", title: "Workspace dependency graph", commandId: DEPENDENCY_GRAPH_CLI_ID, panelId: DEPENDENCY_GRAPH_PANEL_ID },
241
+ { id: "workspace.status", title: "Workspace status", commandId: WORKSPACE_STATUS_CLI_ID },
242
+ { id: "workspace.rollups", title: "Workspace rollups", commandId: WORKSPACE_SUMMARY_CLI_ID, panelId: EPICS_PANEL_ID }
243
+ ],
244
+ panels: [
245
+ { id: DEPENDENCY_GRAPH_PANEL_ID, slot: "capability", title: "Dependency graph", capabilityId: "workspace.dependency-graph" },
246
+ { id: EPICS_PANEL_ID, slot: "capability", title: "Epics", capabilityId: "workspace.rollups" },
247
+ { id: PEOPLE_PANEL_ID, slot: "capability", title: "People", capabilityId: "workspace.rollups" }
248
+ ],
249
+ cliCommands: dependencyGraphCliCommands.map(({ run: _run, ...metadata }) => metadata)
22
250
  }
251
+ }, {
252
+ featureCapabilities: [
253
+ { id: "workspace.dependency-graph", title: "Workspace dependency graph", commandId: DEPENDENCY_GRAPH_CLI_ID, panelId: DEPENDENCY_GRAPH_PANEL_ID },
254
+ { id: "workspace.status", title: "Workspace status", commandId: WORKSPACE_STATUS_CLI_ID },
255
+ { id: "workspace.rollups", title: "Workspace rollups", commandId: WORKSPACE_SUMMARY_CLI_ID, panelId: EPICS_PANEL_ID }
256
+ ],
257
+ panels: [
258
+ { id: DEPENDENCY_GRAPH_PANEL_ID, slot: "capability", title: "Dependency graph", capabilityId: "workspace.dependency-graph", produce: produceDependencyGraphPanel },
259
+ { id: EPICS_PANEL_ID, slot: "capability", title: "Epics", capabilityId: "workspace.rollups", produce: produceWorkspaceRollupsPanel }
260
+ ],
261
+ cliCommands: dependencyGraphCliCommands
23
262
  });
263
+ function createDependencyGraphPlugin() {
264
+ return dependencyGraphPlugin;
265
+ }
24
266
  export {
25
- projectDependencyGraph,
267
+ executeSummary,
268
+ executeStatus,
269
+ executeGraph,
26
270
  dependencyGraphPlugin,
27
- DEPENDENCY_GRAPH_PLUGIN_NAME
271
+ dependencyGraphCliCommands,
272
+ createDependencyGraphPlugin,
273
+ WORKSPACE_SUMMARY_CLI_ID,
274
+ WORKSPACE_STATUS_CLI_ID,
275
+ PEOPLE_PANEL_ID,
276
+ EPICS_PANEL_ID,
277
+ DEPENDENCY_GRAPH_PLUGIN_NAME,
278
+ DEPENDENCY_GRAPH_PANEL_ID,
279
+ DEPENDENCY_GRAPH_CLI_ID
28
280
  };
@@ -1,6 +1,45 @@
1
- import type { TaskSummary } from "@rig/contracts";
2
- import { type BuildDependencyGraphModelOptions, type DependencyGraphModel } from "@rig/core/dependency-graph";
1
+ import { type RuntimeCliContext } from "@rig/core";
3
2
  export declare const DEPENDENCY_GRAPH_PLUGIN_NAME = "@rig/dependency-graph-plugin";
4
- export declare function projectDependencyGraph(tasks: readonly TaskSummary[], options?: BuildDependencyGraphModelOptions): DependencyGraphModel;
3
+ export declare const DEPENDENCY_GRAPH_CLI_ID = "dependency-graph.graph";
4
+ export declare const WORKSPACE_STATUS_CLI_ID = "dependency-graph.status";
5
+ export declare const WORKSPACE_SUMMARY_CLI_ID = "dependency-graph.summary";
6
+ export declare const DEPENDENCY_GRAPH_PANEL_ID = "dependency-graph";
7
+ export declare const EPICS_PANEL_ID = "epics";
8
+ export declare const PEOPLE_PANEL_ID = "people";
9
+ type CommandOutcome = {
10
+ readonly ok: boolean;
11
+ readonly group: string;
12
+ readonly command: string;
13
+ readonly details?: Record<string, unknown>;
14
+ };
15
+ export declare function executeGraph(context: RuntimeCliContext, args: readonly string[]): Promise<CommandOutcome>;
16
+ export declare function executeStatus(context: RuntimeCliContext, args: readonly string[]): Promise<CommandOutcome>;
17
+ export declare function executeSummary(context: RuntimeCliContext, args: readonly string[]): Promise<CommandOutcome>;
18
+ export declare const dependencyGraphCliCommands: readonly [{
19
+ readonly id: "dependency-graph.graph";
20
+ readonly family: "graph";
21
+ readonly command: "rig graph [--json|--dot]";
22
+ readonly description: "Render task dependency graph from the active task source.";
23
+ readonly usage: "rig graph [--json|--dot]";
24
+ readonly projectRequired: true;
25
+ readonly run: typeof executeGraph;
26
+ }, {
27
+ readonly id: "dependency-graph.status";
28
+ readonly family: "status";
29
+ readonly command: "rig status [--epic <key>] [--json]";
30
+ readonly description: "Summarize workspace epics, blockers, and active runs.";
31
+ readonly usage: "rig status [--epic <key>] [--json]";
32
+ readonly projectRequired: true;
33
+ readonly run: typeof executeStatus;
34
+ }, {
35
+ readonly id: "dependency-graph.summary";
36
+ readonly family: "summary";
37
+ readonly command: "rig summary [--epic <key>] [--json]";
38
+ readonly description: "Print workspace rollups by epic and assignee.";
39
+ readonly usage: "rig summary [--epic <key>] [--json]";
40
+ readonly projectRequired: true;
41
+ readonly run: typeof executeSummary;
42
+ }];
5
43
  export declare const dependencyGraphPlugin: import("@rig/core").RigPluginWithRuntime;
44
+ export declare function createDependencyGraphPlugin(): import("@rig/core").RigPluginWithRuntime;
6
45
  export default dependencyGraphPlugin;
@@ -1,30 +1,282 @@
1
1
  // @bun
2
2
  // packages/dependency-graph-plugin/src/plugin.ts
3
3
  import { definePlugin } from "@rig/core";
4
- import { buildDependencyGraphModel } from "@rig/core/dependency-graph";
4
+ import {
5
+ classifyWorkspaceBlockers,
6
+ formatDependencyGraphDot,
7
+ getWorkspaceDependencyGraph,
8
+ getWorkspaceRollups,
9
+ listRuns,
10
+ listTasks
11
+ } from "@rig/client";
5
12
  var DEPENDENCY_GRAPH_PLUGIN_NAME = "@rig/dependency-graph-plugin";
6
- function projectDependencyGraph(tasks, options = {}) {
7
- return buildDependencyGraphModel(tasks, options);
13
+ var DEPENDENCY_GRAPH_CLI_ID = "dependency-graph.graph";
14
+ var WORKSPACE_STATUS_CLI_ID = "dependency-graph.status";
15
+ var WORKSPACE_SUMMARY_CLI_ID = "dependency-graph.summary";
16
+ var DEPENDENCY_GRAPH_PANEL_ID = "dependency-graph";
17
+ var EPICS_PANEL_ID = "epics";
18
+ var PEOPLE_PANEL_ID = "people";
19
+ function isRecord(value) {
20
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8
21
  }
22
+ function panelProjectRoot(context) {
23
+ return isRecord(context) && typeof context.projectRoot === "string" && context.projectRoot.length > 0 ? context.projectRoot : null;
24
+ }
25
+ function toDependencyGraphPanelPayload(model) {
26
+ const layoutByTaskId = new Map(model.layout.nodes.map((node) => [node.taskId, node]));
27
+ return {
28
+ nodes: model.nodes.map((node) => {
29
+ const layout = layoutByTaskId.get(node.taskId);
30
+ return {
31
+ id: node.taskId,
32
+ title: node.title,
33
+ status: node.status,
34
+ epicKey: node.epicKey,
35
+ assignee: node.assignee,
36
+ blockerClass: node.blockerClass,
37
+ blockingDepth: node.blockingDepth,
38
+ layout: layout ? { x: layout.x, y: layout.y, width: layout.width, height: layout.height } : undefined
39
+ };
40
+ }),
41
+ edges: model.edges.map((edge) => ({
42
+ id: `${edge.fromTaskId}:${edge.toTaskId}:${edge.type}`,
43
+ from: edge.fromTaskId,
44
+ to: edge.toTaskId,
45
+ type: edge.type
46
+ })),
47
+ cycles: model.cycles,
48
+ unresolvedRefs: model.unresolvedRefs,
49
+ degraded: model.degraded ? "dependency graph projection degraded" : null
50
+ };
51
+ }
52
+ function toWorkspaceRollupsPanelPayload(rollups) {
53
+ return {
54
+ epics: rollups.epics.map((epic) => ({
55
+ epicKey: epic.epicKey,
56
+ title: epic.epicKey,
57
+ total: epic.total,
58
+ percentComplete: epic.percentComplete,
59
+ blockedCount: epic.blockedCount,
60
+ inFlightCount: epic.inFlightCount,
61
+ humanBlockedCount: epic.humanBlockedCount
62
+ })),
63
+ people: rollups.assignees.map((assignee) => ({
64
+ assignee: assignee.assignee,
65
+ openCount: assignee.openTaskCount,
66
+ inFlightCount: assignee.inFlightRunCount,
67
+ prReviewCount: assignee.prsAwaitingReview,
68
+ humanBlockedCount: assignee.blockers.length,
69
+ activeRunIds: []
70
+ }))
71
+ };
72
+ }
73
+ async function produceDependencyGraphPanel(context) {
74
+ const projectRoot = panelProjectRoot(context);
75
+ if (!projectRoot)
76
+ return;
77
+ const model = await getWorkspaceDependencyGraph(projectRoot, {
78
+ listTasks,
79
+ listRuns,
80
+ classifyBlockers: (root) => classifyWorkspaceBlockers(root, { listTasks, listRuns })
81
+ });
82
+ return toDependencyGraphPanelPayload(model);
83
+ }
84
+ async function produceWorkspaceRollupsPanel(context) {
85
+ const projectRoot = panelProjectRoot(context);
86
+ if (!projectRoot)
87
+ return;
88
+ const rollups = await getWorkspaceRollups(projectRoot, {
89
+ listTasks,
90
+ listRuns,
91
+ classifyBlockers: (root) => classifyWorkspaceBlockers(root, { listTasks, listRuns })
92
+ });
93
+ return toWorkspaceRollupsPanelPayload(rollups);
94
+ }
95
+ function printJson(value) {
96
+ console.log(JSON.stringify(value, null, 2));
97
+ }
98
+ function takeFlag(args, flag) {
99
+ const rest = [...args];
100
+ const index = rest.indexOf(flag);
101
+ if (index < 0)
102
+ return { value: false, rest };
103
+ rest.splice(index, 1);
104
+ return { value: true, rest };
105
+ }
106
+ function takeOption(args, flag) {
107
+ const rest = [...args];
108
+ const index = rest.indexOf(flag);
109
+ if (index < 0)
110
+ return { rest };
111
+ const value = rest[index + 1];
112
+ if (!value || value.startsWith("-"))
113
+ throw new Error(`${flag} requires a value.`);
114
+ rest.splice(index, 2);
115
+ return { value, rest };
116
+ }
117
+ function requireNoExtraArgs(args, usage) {
118
+ if (args.length > 0)
119
+ throw new Error(`Unexpected argument: ${args[0]}
120
+ Usage: ${usage}`);
121
+ }
122
+ function graphSummary(model) {
123
+ const blocked = model.nodes.filter((node) => node.blockedBy.length > 0 || node.blockerClass === "task-blocked").length;
124
+ return [
125
+ `graph ${model.graphId}`,
126
+ `tasks: ${model.nodes.length}`,
127
+ `edges: ${model.edges.length}`,
128
+ `blocked tasks: ${blocked}`,
129
+ `human blockers: ${model.nodes.filter((node) => node.blockerClass && node.blockerClass !== "not-blocked" && node.blockerClass !== "task-blocked").length}`,
130
+ `cycles: ${model.cycles.length}`,
131
+ `unresolved refs: ${model.unresolvedRefs.length}`
132
+ ].join(`
133
+ `);
134
+ }
135
+ async function executeGraph(context, args) {
136
+ const dot = takeFlag(args, "--dot");
137
+ const json = takeFlag(dot.rest, "--json");
138
+ requireNoExtraArgs(json.rest, "rig graph [--json|--dot]");
139
+ if (dot.value && json.value)
140
+ throw new Error("Pass only one of --json or --dot.");
141
+ const model = await getWorkspaceDependencyGraph(context.projectRoot, {
142
+ listTasks,
143
+ listRuns,
144
+ classifyBlockers: (projectRoot) => classifyWorkspaceBlockers(projectRoot, { listTasks, listRuns })
145
+ });
146
+ const dotText = formatDependencyGraphDot(model);
147
+ if (context.outputMode === "text") {
148
+ if (dot.value)
149
+ console.log(dotText);
150
+ else if (json.value)
151
+ printJson(model);
152
+ else
153
+ console.log(graphSummary(model));
154
+ }
155
+ return { ok: true, group: "graph", command: dot.value ? "dot" : "show", details: { graph: model, dot: dotText } };
156
+ }
157
+ async function executeStatus(context, args) {
158
+ const json = takeFlag(args, "--json");
159
+ const epic = takeOption(json.rest, "--epic");
160
+ requireNoExtraArgs(epic.rest, "rig status [--epic <key>] [--json]");
161
+ const [rollups, blockers, runs] = await Promise.all([
162
+ getWorkspaceRollups(context.projectRoot, { listTasks, listRuns }),
163
+ classifyWorkspaceBlockers(context.projectRoot, { listTasks, listRuns }),
164
+ listRuns(context.projectRoot)
165
+ ]);
166
+ const filteredEpics = epic.value ? rollups.epics.filter((entry) => entry.epicKey === epic.value) : rollups.epics;
167
+ const details = {
168
+ epic: epic.value ?? null,
169
+ epicRollups: filteredEpics,
170
+ epics: filteredEpics.length,
171
+ assignees: rollups.assignees.length,
172
+ humanBlockers: blockers.human.length,
173
+ machineBlockers: blockers.machine.length,
174
+ runs: runs.length,
175
+ activeRuns: runs.filter((run) => run.live && !run.stale).length,
176
+ needsAttention: runs.filter((run) => run.status === "needs-attention" || run.pendingApprovals > 0 || run.pendingInputs > 0).length,
177
+ generatedAt: rollups.generatedAt
178
+ };
179
+ if (context.outputMode === "text") {
180
+ if (json.value)
181
+ printJson(details);
182
+ else
183
+ console.log([`epics: ${details.epics}`, `assignees: ${details.assignees}`, `blockers: ${details.humanBlockers} human, ${details.machineBlockers} machine`, `runs: ${details.runs} total, ${details.activeRuns} active, ${details.needsAttention} need attention`].join(`
184
+ `));
185
+ }
186
+ return { ok: true, group: "status", command: "show", details };
187
+ }
188
+ async function executeSummary(context, args) {
189
+ const json = takeFlag(args, "--json");
190
+ const epicOpt = takeOption(json.rest, "--epic");
191
+ requireNoExtraArgs(epicOpt.rest, "rig summary [--epic <key>] [--json]");
192
+ const rollups = await getWorkspaceRollups(context.projectRoot, { listTasks, listRuns });
193
+ const details = epicOpt.value ? { ...rollups, epics: rollups.epics.filter((epic) => epic.epicKey === epicOpt.value) } : rollups;
194
+ if (context.outputMode === "text") {
195
+ if (json.value)
196
+ printJson(details);
197
+ else
198
+ console.log([
199
+ ...details.epics.map((epic) => `epic ${epic.epicKey}: ${epic.percentComplete}% complete, ${epic.blockedCount} blocked`),
200
+ ...details.assignees.map((assignee) => `${assignee.assignee}: ${assignee.openTaskCount} open, ${assignee.inFlightRunCount} in flight`)
201
+ ].join(`
202
+ `) || "No rollups.");
203
+ }
204
+ return { ok: true, group: "summary", command: "show", details };
205
+ }
206
+ var dependencyGraphCliCommands = [
207
+ {
208
+ id: DEPENDENCY_GRAPH_CLI_ID,
209
+ family: "graph",
210
+ command: "rig graph [--json|--dot]",
211
+ description: "Render task dependency graph from the active task source.",
212
+ usage: "rig graph [--json|--dot]",
213
+ projectRequired: true,
214
+ run: executeGraph
215
+ },
216
+ {
217
+ id: WORKSPACE_STATUS_CLI_ID,
218
+ family: "status",
219
+ command: "rig status [--epic <key>] [--json]",
220
+ description: "Summarize workspace epics, blockers, and active runs.",
221
+ usage: "rig status [--epic <key>] [--json]",
222
+ projectRequired: true,
223
+ run: executeStatus
224
+ },
225
+ {
226
+ id: WORKSPACE_SUMMARY_CLI_ID,
227
+ family: "summary",
228
+ command: "rig summary [--epic <key>] [--json]",
229
+ description: "Print workspace rollups by epic and assignee.",
230
+ usage: "rig summary [--epic <key>] [--json]",
231
+ projectRequired: true,
232
+ run: executeSummary
233
+ }
234
+ ];
9
235
  var dependencyGraphPlugin = definePlugin({
10
236
  name: DEPENDENCY_GRAPH_PLUGIN_NAME,
11
237
  version: "0.0.0-alpha.1",
12
- provides: [],
13
- requires: [],
14
238
  contributes: {
15
- cliCommands: [
16
- {
17
- id: "dependency-graph.show",
18
- command: "rig graph",
19
- description: "Render or export the task dependency graph projection."
20
- }
21
- ]
239
+ capabilities: [
240
+ { id: "workspace.dependency-graph", title: "Workspace dependency graph", commandId: DEPENDENCY_GRAPH_CLI_ID, panelId: DEPENDENCY_GRAPH_PANEL_ID },
241
+ { id: "workspace.status", title: "Workspace status", commandId: WORKSPACE_STATUS_CLI_ID },
242
+ { id: "workspace.rollups", title: "Workspace rollups", commandId: WORKSPACE_SUMMARY_CLI_ID, panelId: EPICS_PANEL_ID }
243
+ ],
244
+ panels: [
245
+ { id: DEPENDENCY_GRAPH_PANEL_ID, slot: "capability", title: "Dependency graph", capabilityId: "workspace.dependency-graph" },
246
+ { id: EPICS_PANEL_ID, slot: "capability", title: "Epics", capabilityId: "workspace.rollups" },
247
+ { id: PEOPLE_PANEL_ID, slot: "capability", title: "People", capabilityId: "workspace.rollups" }
248
+ ],
249
+ cliCommands: dependencyGraphCliCommands.map(({ run: _run, ...metadata }) => metadata)
22
250
  }
251
+ }, {
252
+ featureCapabilities: [
253
+ { id: "workspace.dependency-graph", title: "Workspace dependency graph", commandId: DEPENDENCY_GRAPH_CLI_ID, panelId: DEPENDENCY_GRAPH_PANEL_ID },
254
+ { id: "workspace.status", title: "Workspace status", commandId: WORKSPACE_STATUS_CLI_ID },
255
+ { id: "workspace.rollups", title: "Workspace rollups", commandId: WORKSPACE_SUMMARY_CLI_ID, panelId: EPICS_PANEL_ID }
256
+ ],
257
+ panels: [
258
+ { id: DEPENDENCY_GRAPH_PANEL_ID, slot: "capability", title: "Dependency graph", capabilityId: "workspace.dependency-graph", produce: produceDependencyGraphPanel },
259
+ { id: EPICS_PANEL_ID, slot: "capability", title: "Epics", capabilityId: "workspace.rollups", produce: produceWorkspaceRollupsPanel }
260
+ ],
261
+ cliCommands: dependencyGraphCliCommands
23
262
  });
263
+ function createDependencyGraphPlugin() {
264
+ return dependencyGraphPlugin;
265
+ }
24
266
  var plugin_default = dependencyGraphPlugin;
25
267
  export {
26
- projectDependencyGraph,
268
+ executeSummary,
269
+ executeStatus,
270
+ executeGraph,
27
271
  dependencyGraphPlugin,
272
+ dependencyGraphCliCommands,
28
273
  plugin_default as default,
29
- DEPENDENCY_GRAPH_PLUGIN_NAME
274
+ createDependencyGraphPlugin,
275
+ WORKSPACE_SUMMARY_CLI_ID,
276
+ WORKSPACE_STATUS_CLI_ID,
277
+ PEOPLE_PANEL_ID,
278
+ EPICS_PANEL_ID,
279
+ DEPENDENCY_GRAPH_PLUGIN_NAME,
280
+ DEPENDENCY_GRAPH_PANEL_ID,
281
+ DEPENDENCY_GRAPH_CLI_ID
30
282
  };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@h-rig/dependency-graph-plugin",
3
- "version": "0.0.6-alpha.135",
3
+ "version": "0.0.6-alpha.138",
4
4
  "type": "module",
5
- "description": "First-party dependency graph projection plugin for Rig.",
5
+ "description": "First-party dependency graph, workspace status, and rollup capability plugin for Rig.",
6
6
  "license": "UNLICENSED",
7
7
  "files": [
8
8
  "dist",
@@ -25,7 +25,8 @@
25
25
  "module": "./dist/src/index.js",
26
26
  "types": "./dist/src/index.d.ts",
27
27
  "dependencies": {
28
- "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.135",
29
- "@rig/core": "npm:@h-rig/core@0.0.6-alpha.135"
28
+ "@rig/client": "npm:@h-rig/client@0.0.6-alpha.138",
29
+ "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.138",
30
+ "@rig/core": "npm:@h-rig/core@0.0.6-alpha.138"
30
31
  }
31
32
  }