@h-rig/supervisor-plugin 0.0.6-alpha.157 → 0.0.6-alpha.159

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,102 +1,468 @@
1
1
  // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
2
17
  var __require = import.meta.require;
3
18
 
4
- // packages/supervisor-plugin/src/awaiter.ts
5
- var TERMINAL_RUN_STATUSES = {
6
- created: false,
7
- queued: false,
8
- preparing: false,
9
- running: false,
10
- "waiting-approval": false,
11
- "waiting-user-input": false,
12
- paused: false,
13
- validating: false,
14
- reviewing: false,
15
- "closing-out": false,
16
- "needs-attention": true,
17
- completed: true,
18
- failed: true,
19
- stopped: true
20
- };
21
- async function awaitTerminalRun(options) {
22
- const startedAt = Date.now();
23
- const pollMs = Math.max(0, Math.floor(options.pollMs ?? 5000));
24
- while (true) {
25
- const snapshot = await options.readStatus(options.runId);
26
- if (snapshot && TERMINAL_RUN_STATUSES[snapshot.status]) {
27
- return {
28
- runId: options.runId,
29
- status: snapshot.status,
30
- failed: snapshot.status !== "completed"
31
- };
19
+ // packages/supervisor-plugin/src/run-status.ts
20
+ function coerceRunStatus(value, fallback) {
21
+ return KNOWN_RUN_STATUS.has(value) ? value : fallback;
22
+ }
23
+ var KNOWN_RUN_STATUS;
24
+ var init_run_status = __esm(() => {
25
+ KNOWN_RUN_STATUS = new Set([
26
+ "created",
27
+ "queued",
28
+ "preparing",
29
+ "running",
30
+ "validating",
31
+ "reviewing",
32
+ "closing-out",
33
+ "completed",
34
+ "failed",
35
+ "stopped"
36
+ ]);
37
+ });
38
+
39
+ // packages/supervisor-plugin/src/analysis/taskGraphPrimitives.ts
40
+ import {
41
+ OPERATOR_INACTIVE_RUN_STATUSES
42
+ } from "@rig/contracts";
43
+ function isObjectRecord(value) {
44
+ return typeof value === "object" && value !== null && !Array.isArray(value);
45
+ }
46
+ function readStringList(value) {
47
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
48
+ }
49
+ function unique(values) {
50
+ return Array.from(new Set(values));
51
+ }
52
+ function readTaskMetadataStringList(task, key) {
53
+ const taskRecord = task;
54
+ const topLevel = readStringList(taskRecord[key]);
55
+ if (topLevel.length > 0)
56
+ return topLevel;
57
+ const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
58
+ const metadataList = readStringList(metadata?.[key]);
59
+ if (metadataList.length > 0)
60
+ return metadataList;
61
+ if (key === "dependencies") {
62
+ return readStringList(metadata?.deps);
63
+ }
64
+ return [];
65
+ }
66
+ function readTaskBlockingDependencyRefs(task) {
67
+ return readTaskMetadataStringList(task, "dependencies");
68
+ }
69
+ function readTaskSourceIssueId(task) {
70
+ if (typeof task.sourceIssueId === "string" && task.sourceIssueId.length > 0) {
71
+ return task.sourceIssueId;
72
+ }
73
+ const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
74
+ if (typeof metadata?.sourceIssueId === "string" && metadata.sourceIssueId.length > 0) {
75
+ return metadata.sourceIssueId;
76
+ }
77
+ const rigMetadata = isObjectRecord(metadata?._rig) ? metadata._rig : null;
78
+ return typeof rigMetadata?.sourceIssueId === "string" && rigMetadata.sourceIssueId.length > 0 ? rigMetadata.sourceIssueId : null;
79
+ }
80
+ function readTaskScope(task) {
81
+ const taskRecord = task;
82
+ const topLevel = readStringList(taskRecord.scope);
83
+ if (topLevel.length > 0)
84
+ return unique(topLevel.map((entry) => entry.trim()).filter((entry) => entry.length > 0));
85
+ const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
86
+ const metadataScope = readStringList(metadata?.scope);
87
+ if (metadataScope.length > 0)
88
+ return unique(metadataScope.map((entry) => entry.trim()).filter((entry) => entry.length > 0));
89
+ return unique([
90
+ ...readStringList(metadata?.files),
91
+ ...readStringList(metadata?.paths)
92
+ ].map((entry) => entry.trim()).filter((entry) => entry.length > 0));
93
+ }
94
+ function isScopeList(input) {
95
+ return Array.isArray(input);
96
+ }
97
+ function normalizeScopeInput(input) {
98
+ return isScopeList(input) ? unique(input.map((entry) => entry.trim()).filter((entry) => entry.length > 0)) : readTaskScope(input);
99
+ }
100
+ function disjointScope(left, right) {
101
+ const leftScope = new Set(normalizeScopeInput(left));
102
+ if (leftScope.size === 0)
103
+ return true;
104
+ return normalizeScopeInput(right).every((entry) => !leftScope.has(entry));
105
+ }
106
+ function resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId) {
107
+ if (tasksById.has(ref))
108
+ return ref;
109
+ return taskIdBySourceIssueId.get(ref) ?? taskIdByExternalRef.get(ref) ?? null;
110
+ }
111
+ function buildTaskReferenceIndex(tasks) {
112
+ return {
113
+ tasksById: new Map(tasks.map((task) => [task.id, task])),
114
+ taskIdByExternalRef: new Map(tasks.flatMap((task) => task.externalId ? [[task.externalId, task.id]] : [])),
115
+ taskIdBySourceIssueId: new Map(tasks.flatMap((task) => {
116
+ const sourceIssueId = readTaskSourceIssueId(task);
117
+ return sourceIssueId ? [[sourceIssueId, task.id]] : [];
118
+ }))
119
+ };
120
+ }
121
+ function computeTaskBlockingDepths(tasks) {
122
+ const { tasksById, taskIdByExternalRef, taskIdBySourceIssueId } = buildTaskReferenceIndex(tasks);
123
+ const memo = new Map;
124
+ const visit = (taskId, stack) => {
125
+ const cached = memo.get(taskId);
126
+ if (cached !== undefined)
127
+ return cached;
128
+ if (stack.has(taskId))
129
+ return 0;
130
+ const task = tasksById.get(taskId);
131
+ if (!task)
132
+ return 0;
133
+ stack.add(taskId);
134
+ const blockers = readTaskBlockingDependencyRefs(task).map((ref) => resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId)).filter((ref) => ref !== null && ref !== taskId);
135
+ const depth = blockers.length === 0 ? 0 : Math.max(...blockers.map((blockerId) => visit(blockerId, stack) + 1));
136
+ stack.delete(taskId);
137
+ memo.set(taskId, depth);
138
+ return depth;
139
+ };
140
+ for (const task of tasks) {
141
+ visit(task.id, new Set);
142
+ }
143
+ return memo;
144
+ }
145
+ function isTaskTerminalStatus(status) {
146
+ switch (status) {
147
+ case "closed":
148
+ case "completed":
149
+ case "done":
150
+ case "cancelled":
151
+ case "canceled":
152
+ return true;
153
+ default:
154
+ return false;
155
+ }
156
+ }
157
+ function isTaskBlockedStatus(status) {
158
+ return status === "blocked";
159
+ }
160
+ function isTaskRunnableStatus(status) {
161
+ if (status === null || status === undefined || status === "")
162
+ return true;
163
+ if (isTaskTerminalStatus(status) || isTaskBlockedStatus(status))
164
+ return false;
165
+ switch (status) {
166
+ case "ready":
167
+ case "open":
168
+ case "failed":
169
+ return true;
170
+ default:
171
+ return false;
172
+ }
173
+ }
174
+ function computeTaskDependencyBadges(tasks) {
175
+ const index = buildTaskReferenceIndex(tasks);
176
+ const blockingDepths = computeTaskBlockingDepths(tasks);
177
+ const dependencyIdsByTask = new Map;
178
+ const unresolvedRefsByTask = new Map;
179
+ const blocksByTask = new Map;
180
+ for (const task of tasks) {
181
+ const dependencyIds = [];
182
+ const unresolvedRefs = [];
183
+ for (const ref of readTaskBlockingDependencyRefs(task)) {
184
+ const dependencyId = resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
185
+ if (dependencyId && dependencyId !== task.id) {
186
+ dependencyIds.push(dependencyId);
187
+ const blocks = blocksByTask.get(dependencyId);
188
+ if (blocks) {
189
+ blocks.push(task.id);
190
+ } else {
191
+ blocksByTask.set(dependencyId, [task.id]);
192
+ }
193
+ } else {
194
+ unresolvedRefs.push(ref);
195
+ }
32
196
  }
33
- if (options.timeoutMs !== undefined && Date.now() - startedAt >= options.timeoutMs) {
34
- return { runId: options.runId, status: "needs-attention", failed: true };
197
+ dependencyIdsByTask.set(task.id, unique(dependencyIds));
198
+ unresolvedRefsByTask.set(task.id, unique(unresolvedRefs));
199
+ }
200
+ const summaries = new Map;
201
+ for (const task of tasks) {
202
+ const dependencyIds = dependencyIdsByTask.get(task.id) ?? [];
203
+ const unresolvedDependencyRefs = unresolvedRefsByTask.get(task.id) ?? [];
204
+ const blockedBy = dependencyIds.filter((dependencyId) => {
205
+ const dependency = index.tasksById.get(dependencyId);
206
+ return dependency ? !isTaskTerminalStatus(dependency.status) : false;
207
+ });
208
+ const blocks = unique(blocksByTask.get(task.id) ?? []);
209
+ const blocked = isTaskBlockedStatus(task.status) || blockedBy.length > 0;
210
+ const ready = isTaskRunnableStatus(task.status) && !blocked;
211
+ const badges = [];
212
+ if (blocked) {
213
+ badges.push({
214
+ kind: "blocked",
215
+ label: blockedBy.length > 0 ? `blocked \xD7${blockedBy.length}` : "blocked",
216
+ description: blockedBy.length > 0 ? `Waiting on ${blockedBy.join(", ")}.` : "Task source marks this task blocked.",
217
+ ...blockedBy.length > 0 ? { count: blockedBy.length } : {},
218
+ taskIds: blockedBy
219
+ });
220
+ } else if (ready) {
221
+ badges.push({
222
+ kind: "ready",
223
+ label: "ready",
224
+ description: "No open dependencies block this task."
225
+ });
35
226
  }
36
- await options.waitForChange(pollMs);
227
+ if (dependencyIds.length > 0 || blocks.length > 0 || unresolvedDependencyRefs.length > 0) {
228
+ badges.push({
229
+ kind: "dependency",
230
+ label: `deps ${dependencyIds.length}/${blocks.length}`,
231
+ description: [
232
+ dependencyIds.length > 0 ? `Depends on ${dependencyIds.join(", ")}.` : null,
233
+ blocks.length > 0 ? `Blocks ${blocks.join(", ")}.` : null,
234
+ unresolvedDependencyRefs.length > 0 ? `Unresolved refs: ${unresolvedDependencyRefs.join(", ")}.` : null
235
+ ].filter((part) => part !== null).join(" "),
236
+ count: dependencyIds.length + blocks.length,
237
+ taskIds: unique([...dependencyIds, ...blocks])
238
+ });
239
+ }
240
+ summaries.set(task.id, {
241
+ taskId: task.id,
242
+ blockingDepth: blockingDepths.get(task.id) ?? 0,
243
+ dependencyIds,
244
+ unresolvedDependencyRefs,
245
+ blockedBy,
246
+ blocks,
247
+ blocked,
248
+ ready,
249
+ dependencyCount: dependencyIds.length,
250
+ dependentCount: blocks.length,
251
+ badges
252
+ });
37
253
  }
254
+ return summaries;
38
255
  }
39
- // packages/supervisor-plugin/src/closureStage.ts
40
- var SUPERVISOR_CLOSURE_STAGE_ID = "supervisor-closure-observer";
41
- function createClosureStage(port) {
42
- return async (ctx) => {
43
- const summary = await port.summarize(ctx);
44
- if (!summary)
45
- return { kind: "continue", ctx };
46
- await port.record?.(summary);
47
- const metadata = { ...ctx.metadata ?? {}, supervisorClosure: summary };
48
- return { kind: "continue", ctx: { ...ctx, metadata } };
49
- };
256
+ function stringValue(value) {
257
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
50
258
  }
51
- function summarizeClosure(ctx) {
52
- const state = (ctx.metadata ?? {}).closeoutState ?? null;
53
- const taskId = typeof ctx.taskId === "string" ? ctx.taskId : null;
54
- if (!state || !taskId)
55
- return null;
259
+ function stringArray(value) {
260
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim().length > 0).map((entry) => entry.trim()) : [];
261
+ }
262
+ function numberOrNull(value) {
263
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
264
+ }
265
+ function metadataOf(task) {
266
+ return isObjectRecord(task.metadata) ? task.metadata : {};
267
+ }
268
+ function normalizeTaskStatus(status) {
269
+ const token = typeof status === "string" ? status.trim().toLowerCase() : "";
270
+ if (token === "done")
271
+ return "completed";
272
+ if (token === "canceled")
273
+ return "cancelled";
274
+ return TASK_STATUSES.has(token) ? token : "unknown";
275
+ }
276
+ function toTaskDependencyProjection(task) {
277
+ const metadata = metadataOf(task);
56
278
  return {
57
- taskId,
58
- runId: ctx.runId,
59
- prUrl: state.prUrl ?? null,
60
- mergeGate: state.mergeGate ?? null,
61
- acceptanceChecked: [],
62
- unblockedTaskIds: state.unblockedTaskIds ?? []
279
+ id: String(task.id),
280
+ title: stringValue(task.title),
281
+ status: normalizeTaskStatus(task.status),
282
+ priority: numberOrNull(task.priority),
283
+ metadata,
284
+ externalId: stringValue(task.externalId),
285
+ sourceIssueId: stringValue(task.sourceIssueId),
286
+ dependencies: stringArray(task.dependencies),
287
+ parentChildDeps: stringArray(task.parentChildDeps),
288
+ createdAt: stringValue(task.createdAt) ?? "",
289
+ updatedAt: stringValue(task.updatedAt) ?? "",
290
+ role: stringValue(task.role),
291
+ scope: stringArray(task.scope),
292
+ validationKeys: stringArray(task.validationKeys),
293
+ labels: stringArray(task.labels),
294
+ assignees: task.assignees ?? null,
295
+ assignedTo: task.assignedTo ?? null
63
296
  };
64
297
  }
65
- function createSupervisorClosureStage() {
66
- return createClosureStage({ summarize: summarizeClosure });
298
+ function latestRunByTaskId(runs) {
299
+ const byTask = new Map;
300
+ const stamp = (run) => Date.parse(run.updatedAt ?? run.startedAt ?? "") || 0;
301
+ for (const run of runs) {
302
+ if (!run.taskId)
303
+ continue;
304
+ const current = byTask.get(run.taskId);
305
+ if (!current || stamp(run) >= stamp(current))
306
+ byTask.set(run.taskId, run);
307
+ }
308
+ return byTask;
67
309
  }
68
- function createDefaultSupervisorClosureStage() {
69
- return async (ctx) => {
70
- const summary = summarizeClosure(ctx);
71
- const projectRoot = (ctx.metadata ?? {}).projectRoot;
72
- if (summary && typeof projectRoot === "string") {
73
- const { appendFileSync, mkdirSync } = await import("fs");
74
- const { join } = await import("path");
75
- try {
76
- mkdirSync(join(projectRoot, ".rig"), { recursive: true });
77
- appendFileSync(join(projectRoot, ".rig", "supervisor.jsonl"), `${JSON.stringify({ kind: "supervisor.outcome", at: new Date().toISOString(), taskId: summary.taskId, runId: summary.runId, status: summary.mergeGate === "passed" ? "completed" : "needs-attention", failed: false, unblockedTaskIds: summary.unblockedTaskIds, closure: summary })}
78
- `);
79
- } catch {}
80
- }
81
- return { kind: "continue", ctx };
310
+ var TASK_STATUSES;
311
+ var init_taskGraphPrimitives = __esm(() => {
312
+ TASK_STATUSES = new Set([
313
+ "draft",
314
+ "open",
315
+ "ready",
316
+ "queued",
317
+ "running",
318
+ "in_progress",
319
+ "under_review",
320
+ "blocked",
321
+ "unknown",
322
+ "completed",
323
+ "failed",
324
+ "cancelled",
325
+ "closed"
326
+ ]);
327
+ });
328
+
329
+ // packages/supervisor-plugin/src/analysis/taskScore.ts
330
+ function finiteNumber(value, fallback) {
331
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
332
+ }
333
+ function scoreTask(input) {
334
+ const priority = finiteNumber(input.priority, 3);
335
+ const unblockCount = Math.max(0, finiteNumber(input.unblockCount, 0));
336
+ const roleWeight = input.role ? ROLE_WEIGHT[input.role] ?? 0 : 0;
337
+ const criticalityWeight = input.criticality ? CRITICALITY_WEIGHT[input.criticality] ?? 0 : 0;
338
+ const validationWeight = (input.validation ?? []).some((entry) => entry.includes("test-contract") || entry.includes("test-boundary")) ? 8 : 0;
339
+ const queueWeight = finiteNumber(input.queueWeight, 0);
340
+ return unblockCount * 100 + (10 - priority) + roleWeight + criticalityWeight + validationWeight + queueWeight;
341
+ }
342
+ function rankTasks(tasks, scoreInput, idOf) {
343
+ return tasks.map((task) => {
344
+ const input = scoreInput(task);
345
+ return {
346
+ task,
347
+ score: scoreTask(input),
348
+ priority: finiteNumber(input.priority, 3),
349
+ unblockCount: Math.max(0, finiteNumber(input.unblockCount, 0))
350
+ };
351
+ }).toSorted((left, right) => {
352
+ const scoreDelta = right.score - left.score;
353
+ if (scoreDelta !== 0)
354
+ return scoreDelta;
355
+ const unblockDelta = right.unblockCount - left.unblockCount;
356
+ if (unblockDelta !== 0)
357
+ return unblockDelta;
358
+ const priorityDelta = left.priority - right.priority;
359
+ if (priorityDelta !== 0)
360
+ return priorityDelta;
361
+ return idOf(left.task).localeCompare(idOf(right.task));
362
+ });
363
+ }
364
+ var ROLE_WEIGHT, CRITICALITY_WEIGHT;
365
+ var init_taskScore = __esm(() => {
366
+ ROLE_WEIGHT = {
367
+ architect: 25,
368
+ extractor: 10
82
369
  };
370
+ CRITICALITY_WEIGHT = {
371
+ core: 20,
372
+ high: 10,
373
+ normal: 0
374
+ };
375
+ });
376
+
377
+ // packages/supervisor-plugin/src/analysis/taskRanking.ts
378
+ function isObjectRecord2(value) {
379
+ return typeof value === "object" && value !== null && !Array.isArray(value);
83
380
  }
84
- var supervisorClosureStage = {
85
- id: SUPERVISOR_CLOSURE_STAGE_ID,
86
- kind: "observe",
87
- after: ["source-closeout"],
88
- before: ["journal-append"],
89
- priority: 0,
90
- protected: false
91
- };
92
- var supervisorClosureStageMutation = {
93
- op: "insert",
94
- contributedBy: "@rig/supervisor-plugin",
95
- stage: supervisorClosureStage
96
- };
97
- // packages/supervisor-plugin/src/loop.ts
98
- import { computeTaskDependencyBadges, toTaskDependencyProjection } from "@rig/contracts";
99
- import { rankReadyTasks, selectRankedReadyTasks } from "@rig/dependency-graph-plugin";
381
+ function readStringList2(value) {
382
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
383
+ }
384
+ function readTaskRole(task) {
385
+ if (typeof task.role === "string" && task.role.trim())
386
+ return task.role.trim();
387
+ const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
388
+ return typeof metadata?.role === "string" && metadata.role.trim() ? metadata.role.trim() : null;
389
+ }
390
+ function readTaskCriticality(task) {
391
+ const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
392
+ return typeof metadata?.criticality === "string" && metadata.criticality.trim() ? metadata.criticality.trim() : null;
393
+ }
394
+ function readTaskValidationKeys(task) {
395
+ const taskRecord = task;
396
+ const topLevel = readStringList2(taskRecord.validationKeys);
397
+ if (topLevel.length > 0)
398
+ return topLevel;
399
+ const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
400
+ return readStringList2(metadata?.validation);
401
+ }
402
+ function readTaskQueueWeight(task) {
403
+ const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
404
+ const queueWeight = metadata?.queueWeight ?? metadata?.queue_weight;
405
+ return typeof queueWeight === "number" && Number.isFinite(queueWeight) ? queueWeight : 0;
406
+ }
407
+ function scoreInputForTask(task, unblockCount) {
408
+ return {
409
+ priority: task.priority,
410
+ unblockCount,
411
+ role: readTaskRole(task),
412
+ criticality: readTaskCriticality(task),
413
+ validation: readTaskValidationKeys(task),
414
+ queueWeight: readTaskQueueWeight(task)
415
+ };
416
+ }
417
+ function rankReadyTasks(tasks, options = {}) {
418
+ const excluded = new Set(options.excludeTaskIds ?? []);
419
+ const activeTaskIds = new Set(options.activeTaskIds ?? []);
420
+ const badges = computeTaskDependencyBadges(tasks);
421
+ const tasksById = new Map(tasks.map((task) => [String(task.id), task]));
422
+ const openBlockCount = (task) => (badges.get(task.id)?.blocks ?? []).filter((blockedTaskId) => {
423
+ const blockedTask = tasksById.get(blockedTaskId);
424
+ return blockedTask ? !isTaskTerminalStatus(blockedTask.status) : false;
425
+ }).length;
426
+ 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);
427
+ const maxUnblockCount = Math.max(0, ...readyTasks.map(openBlockCount));
428
+ const selectedByMode = readyTasks.filter((task) => {
429
+ const unblockCount = openBlockCount(task);
430
+ if (options.selection === "blocking-only")
431
+ return unblockCount > 0;
432
+ if (options.selection === "max-unblock")
433
+ return maxUnblockCount > 0 && unblockCount === maxUnblockCount;
434
+ return true;
435
+ });
436
+ return rankTasks(selectedByMode, (task) => scoreInputForTask(task, openBlockCount(task)), (task) => task.id).map((entry) => ({
437
+ task: entry.task,
438
+ score: entry.score,
439
+ priority: entry.priority,
440
+ unblockCount: entry.unblockCount,
441
+ scope: readTaskScope(entry.task)
442
+ }));
443
+ }
444
+ function selectRankedReadyTasks(tasks, options = {}) {
445
+ const ranked = rankReadyTasks(tasks, options);
446
+ if (options.requireDisjointScopes !== true && !options.disjointWithScopes) {
447
+ return ranked.slice(0, options.limit).map((entry) => entry.task);
448
+ }
449
+ const occupiedScopes = new Set(options.disjointWithScopes ?? []);
450
+ const selected = [];
451
+ for (const entry of ranked) {
452
+ if (entry.scope.some((scope) => occupiedScopes.has(scope)))
453
+ continue;
454
+ selected.push(entry.task);
455
+ for (const scope of entry.scope)
456
+ occupiedScopes.add(scope);
457
+ if (options.limit !== undefined && selected.length >= options.limit)
458
+ break;
459
+ }
460
+ return selected;
461
+ }
462
+ var init_taskRanking = __esm(() => {
463
+ init_taskGraphPrimitives();
464
+ init_taskScore();
465
+ });
100
466
 
101
467
  // packages/supervisor-plugin/src/journal.ts
102
468
  import { mkdir, readFile, writeFile } from "fs/promises";
@@ -105,8 +471,6 @@ import { Schema } from "effect";
105
471
  import {
106
472
  SupervisorEvent
107
473
  } from "@rig/contracts";
108
- var NEWLINE = `
109
- `;
110
474
  function reduceSupervisorJournal(events) {
111
475
  let processed = 0;
112
476
  let succeeded = 0;
@@ -227,9 +591,102 @@ function createSupervisorJournal(store) {
227
591
  const readProjection = async () => reduceSupervisorJournal(await readEvents());
228
592
  return { append, readEvents, readProjection };
229
593
  }
594
+ var NEWLINE = `
595
+ `;
596
+ var init_journal = () => {};
597
+
598
+ // packages/supervisor-plugin/src/analysis/blockers.ts
599
+ function labelsFor(task) {
600
+ return readTaskMetadataStringList(task, "labels").map((label) => label.toLowerCase());
601
+ }
602
+ function configuredTier(task, labels) {
603
+ const metadata = task.metadata && typeof task.metadata === "object" && !Array.isArray(task.metadata) ? task.metadata : {};
604
+ const tier = metadata.riskTier ?? metadata.actionRiskTier;
605
+ if (tier === "t1-read" || tier === "t2-reversible" || tier === "t3-external" || tier === "t4-irreversible")
606
+ return tier;
607
+ if (labels.some((label) => /prod|deploy|migration|billing|delete|irreversible/.test(label)))
608
+ return "t4-irreversible";
609
+ if (labels.some((label) => /customer|vendor|external|secret|credential|approval|decision/.test(label)))
610
+ return "t3-external";
611
+ const scopes = task.scope ?? [];
612
+ if (scopes.some((scope) => /docs|readme|test|spec/.test(scope.toLowerCase())))
613
+ return "t1-read";
614
+ return "t2-reversible";
615
+ }
616
+ function tierOf(task, labels = []) {
617
+ const projection = "metadata" in task && "id" in task && typeof task.id === "string" ? toTaskDependencyProjection(task) : task;
618
+ return configuredTier(projection, labels.length > 0 ? labels : labelsFor(projection)) ?? "t2-reversible";
619
+ }
620
+ function baseClassification(task, blockerClass, source, rationale, labels) {
621
+ return {
622
+ taskId: task.id,
623
+ blockerClass,
624
+ actionRiskTier: tierOf(task, labels),
625
+ rationale,
626
+ source,
627
+ autoApplied: source === "llm"
628
+ };
629
+ }
630
+ function normalizeRunStatus(status) {
631
+ if (status === "waiting-approval" || status === "waiting-user-input" || status === "needs-attention" || status === "completed" || status === "failed" || status === "stopped" || status === "running")
632
+ return status;
633
+ return null;
634
+ }
635
+ function isHumanBlockerClass(blockerClass) {
636
+ return HUMAN_BLOCKERS.has(blockerClass);
637
+ }
638
+ function classifyBlocker(input) {
639
+ const labels = input.labels ?? labelsFor(input.task);
640
+ const runStatus = normalizeRunStatus(input.run?.status);
641
+ if (runStatus === "waiting-approval")
642
+ return baseClassification(input.task, "human-approval", "status", "run awaiting approval", labels);
643
+ if (runStatus === "waiting-user-input")
644
+ return baseClassification(input.task, "external-input", "status", "run awaiting user input", labels);
645
+ if (input.task.status !== "blocked")
646
+ return baseClassification(input.task, "not-blocked", "elimination", "task is not blocked", labels);
647
+ const incomplete = (input.badges.get(input.task.id)?.blockedBy ?? []).filter((dependencyId) => {
648
+ const dependency = input.tasksById.get(dependencyId);
649
+ return dependency ? !isTaskTerminalStatus(dependency.status) : false;
650
+ });
651
+ if (incomplete.length > 0)
652
+ return baseClassification(input.task, "task-blocked", "elimination", `blocked by ${incomplete.length} incomplete task(s)`, labels);
653
+ if (labels.includes("needs-decision"))
654
+ return baseClassification(input.task, "human-decision", "label", "needs-decision label", labels);
655
+ if (labels.some((label) => /^waiting-on-/.test(label)))
656
+ return baseClassification(input.task, "external-input", "label", "waiting-on-* label", labels);
657
+ if (input.config?.llm)
658
+ return baseClassification(input.task, "human-decision", "llm", "LLM residue classifier marked this as human-gated", labels);
659
+ return baseClassification(input.task, "human-decision", "elimination", "blocked, all task dependencies terminal; non-task gate remains", labels);
660
+ }
661
+ function classifyTasks(tasks, runs = [], options = {}) {
662
+ const projected = tasks.map(toTaskDependencyProjection);
663
+ const badges = computeTaskDependencyBadges(projected);
664
+ const tasksById = new Map(projected.map((task) => [task.id, task]));
665
+ const runByTask = latestRunByTaskId(runs);
666
+ const classifier = options.classifier ?? classifyBlocker;
667
+ 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));
668
+ return {
669
+ classifications,
670
+ byTaskId: new Map(classifications.map((classification) => [classification.taskId, classification])),
671
+ human: classifications.filter((classification) => isHumanBlockerClass(classification.blockerClass)),
672
+ machine: classifications.filter((classification) => !isHumanBlockerClass(classification.blockerClass)),
673
+ generatedAt: options.generatedAt ?? new Date().toISOString()
674
+ };
675
+ }
676
+ var HUMAN_BLOCKERS;
677
+ var init_blockers = __esm(() => {
678
+ init_taskGraphPrimitives();
679
+ HUMAN_BLOCKERS = new Set(["human-decision", "human-approval", "external-input"]);
680
+ });
230
681
 
231
682
  // packages/supervisor-plugin/src/loop.ts
232
- import { classifyTasks, isHumanBlockerClass } from "@rig/blocker-classifier-plugin";
683
+ var exports_loop = {};
684
+ __export(exports_loop, {
685
+ unblockTask: () => unblockTask,
686
+ runSupervisorLoop: () => runSupervisorLoop,
687
+ planSupervisorLoop: () => planSupervisorLoop,
688
+ controlSupervisorRun: () => controlSupervisorRun
689
+ });
233
690
  function selectionMode(policy) {
234
691
  return policy === "blocking-only" || policy === "max-unblock" ? policy : "all-ready";
235
692
  }
@@ -288,25 +745,7 @@ function failedOutcome(outcome) {
288
745
  return outcome.status === "failed" || outcome.status === "stopped" || outcome.status === "needs-attention" || outcome.status === "waiting-approval" || outcome.status === "waiting-user-input";
289
746
  }
290
747
  function runStatus(value) {
291
- switch (value) {
292
- case "created":
293
- case "queued":
294
- case "preparing":
295
- case "running":
296
- case "waiting-approval":
297
- case "waiting-user-input":
298
- case "paused":
299
- case "validating":
300
- case "reviewing":
301
- case "closing-out":
302
- case "needs-attention":
303
- case "completed":
304
- case "failed":
305
- case "stopped":
306
- return value;
307
- default:
308
- return "failed";
309
- }
748
+ return coerceRunStatus(value, "failed");
310
749
  }
311
750
  async function runSupervisorLoop(projectRoot, deps, options = {}) {
312
751
  const events = [{ kind: "supervisor.started", at: at(deps), options }];
@@ -403,10 +842,115 @@ async function unblockTask(projectRoot, taskId, deps, options = {}) {
403
842
  const blockers = collectBlockingClosure(taskId, badges);
404
843
  return runSupervisorLoop(projectRoot, deps, { ...options, candidateTaskIds: blockers, selectionPolicy: "rank", maxTasks: 1 });
405
844
  }
845
+ var init_loop = __esm(() => {
846
+ init_taskGraphPrimitives();
847
+ init_taskRanking();
848
+ init_journal();
849
+ init_run_status();
850
+ init_blockers();
851
+ });
406
852
 
853
+ // packages/supervisor-plugin/src/awaiter.ts
854
+ var TERMINAL_RUN_STATUSES = {
855
+ created: false,
856
+ queued: false,
857
+ preparing: false,
858
+ running: false,
859
+ "waiting-approval": false,
860
+ "waiting-user-input": false,
861
+ paused: false,
862
+ validating: false,
863
+ reviewing: false,
864
+ "closing-out": false,
865
+ "needs-attention": true,
866
+ completed: true,
867
+ failed: true,
868
+ stopped: true
869
+ };
870
+ async function awaitTerminalRun(options) {
871
+ const startedAt = Date.now();
872
+ const pollMs = Math.max(0, Math.floor(options.pollMs ?? 5000));
873
+ while (true) {
874
+ const snapshot = await options.readStatus(options.runId);
875
+ if (snapshot && TERMINAL_RUN_STATUSES[snapshot.status]) {
876
+ return {
877
+ runId: options.runId,
878
+ status: snapshot.status,
879
+ failed: snapshot.status !== "completed"
880
+ };
881
+ }
882
+ if (options.timeoutMs !== undefined && Date.now() - startedAt >= options.timeoutMs) {
883
+ return { runId: options.runId, status: "needs-attention", failed: true };
884
+ }
885
+ await options.waitForChange(pollMs);
886
+ }
887
+ }
888
+ // packages/supervisor-plugin/src/closureStage.ts
889
+ var SUPERVISOR_CLOSURE_STAGE_ID = "supervisor-closure-observer";
890
+ function createClosureStage(port) {
891
+ return async (ctx) => {
892
+ const summary = await port.summarize(ctx);
893
+ if (!summary)
894
+ return { kind: "continue", ctx };
895
+ await port.record?.(summary);
896
+ const metadata = { ...ctx.metadata ?? {}, supervisorClosure: summary };
897
+ return { kind: "continue", ctx: { ...ctx, metadata } };
898
+ };
899
+ }
900
+ function summarizeClosure(ctx) {
901
+ const state = (ctx.metadata ?? {}).closeoutState ?? null;
902
+ const taskId = typeof ctx.taskId === "string" ? ctx.taskId : null;
903
+ if (!state || !taskId)
904
+ return null;
905
+ return {
906
+ taskId,
907
+ runId: ctx.runId,
908
+ prUrl: state.prUrl ?? null,
909
+ mergeGate: state.mergeGate ?? null,
910
+ acceptanceChecked: [],
911
+ unblockedTaskIds: state.unblockedTaskIds ?? []
912
+ };
913
+ }
914
+ function createSupervisorClosureStage() {
915
+ return createClosureStage({ summarize: summarizeClosure });
916
+ }
917
+ function createDefaultSupervisorClosureStage() {
918
+ return async (ctx) => {
919
+ const summary = summarizeClosure(ctx);
920
+ const projectRoot = (ctx.metadata ?? {}).projectRoot;
921
+ if (summary && typeof projectRoot === "string") {
922
+ const { appendFileSync, mkdirSync } = await import("fs");
923
+ const { join } = await import("path");
924
+ try {
925
+ mkdirSync(join(projectRoot, ".rig"), { recursive: true });
926
+ appendFileSync(join(projectRoot, ".rig", "supervisor.jsonl"), `${JSON.stringify({ kind: "supervisor.outcome", at: new Date().toISOString(), taskId: summary.taskId, runId: summary.runId, status: summary.mergeGate === "passed" ? "completed" : "needs-attention", failed: false, unblockedTaskIds: summary.unblockedTaskIds, closure: summary })}
927
+ `);
928
+ } catch {}
929
+ }
930
+ return { kind: "continue", ctx };
931
+ };
932
+ }
933
+ var supervisorClosureStage = {
934
+ id: SUPERVISOR_CLOSURE_STAGE_ID,
935
+ kind: "observe",
936
+ after: ["source-closeout"],
937
+ before: ["journal-append"],
938
+ priority: 0,
939
+ protected: false
940
+ };
941
+ var supervisorClosureStageMutation = {
942
+ op: "insert",
943
+ contributedBy: "@rig/supervisor-plugin",
944
+ stage: supervisorClosureStage
945
+ };
407
946
  // packages/supervisor-plugin/src/cli.ts
947
+ import { RUN_DISPATCH, RUN_READ_MODEL, TASK_IO_SERVICE_CAPABILITY } from "@rig/contracts";
948
+ import { defineCapability } from "@rig/core/capability";
949
+ import { requireCapabilityForRoot } from "@rig/core/capability-loaders";
950
+ init_run_status();
408
951
  var SUPERVISOR_LOOP_CLI_ID = "supervisor.loop";
409
952
  var SUPERVISOR_UNBLOCK_CLI_ID = "supervisor.unblock";
953
+ var RunReadModelCap = defineCapability(RUN_READ_MODEL);
410
954
  function printJson(value) {
411
955
  console.log(JSON.stringify(value, null, 2));
412
956
  }
@@ -443,55 +987,36 @@ function parsePositiveInt(value, flag, fallback) {
443
987
  }
444
988
  return parsed;
445
989
  }
446
- function parseCsv(value) {
447
- return value?.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) ?? [];
448
- }
449
990
  function asRunStatus(value) {
450
- switch (value) {
451
- case "created":
452
- case "queued":
453
- case "preparing":
454
- case "running":
455
- case "waiting-approval":
456
- case "waiting-user-input":
457
- case "paused":
458
- case "validating":
459
- case "reviewing":
460
- case "closing-out":
461
- case "needs-attention":
462
- case "completed":
463
- case "failed":
464
- case "stopped":
465
- return value;
466
- default:
467
- return "needs-attention";
468
- }
991
+ return coerceRunStatus(value, "needs-attention");
469
992
  }
470
993
  function delay(ms) {
471
994
  const { promise, resolve } = Promise.withResolvers();
472
995
  setTimeout(resolve, ms);
473
996
  return promise;
474
997
  }
475
- async function loadSupervisorClient() {
476
- const [taskIo, runIo, dispatchIo] = await Promise.all([
477
- import("@rig/core/task-io"),
478
- import("@rig/run-worker/runs"),
479
- import("@rig/runtime/control-plane/dispatch")
998
+ async function loadSupervisorClient(projectRoot) {
999
+ const [taskIo, runReadModel] = await Promise.all([
1000
+ requireCapabilityForRoot(projectRoot, defineCapability(TASK_IO_SERVICE_CAPABILITY), "No task-sources plugin provides task IO for this project root."),
1001
+ requireCapabilityForRoot(projectRoot, RunReadModelCap, "No run-worker plugin provides run read-model for this project root.")
480
1002
  ]);
481
- return { listTasks: taskIo.listTasks, listRuns: runIo.listRuns, dispatchRun: dispatchIo.dispatchRun };
1003
+ return { listTasks: taskIo.listTasks, listRuns: (root) => runReadModel.listRuns({ projectRoot: root }) };
482
1004
  }
483
1005
  function supervisorDeps(timeoutMs) {
484
1006
  return {
485
- listTasks: async (projectRoot) => (await loadSupervisorClient()).listTasks(projectRoot),
486
- listRuns: async (projectRoot) => (await loadSupervisorClient()).listRuns(projectRoot),
487
- dispatchRun: async (...args) => (await loadSupervisorClient()).dispatchRun(...args),
1007
+ listTasks: async (projectRoot) => (await loadSupervisorClient(projectRoot)).listTasks(projectRoot),
1008
+ listRuns: async (projectRoot) => (await loadSupervisorClient(projectRoot)).listRuns(projectRoot),
1009
+ dispatchRun: async (input) => {
1010
+ const service = await requireCapabilityForRoot(input.projectRoot, defineCapability(RUN_DISPATCH), "No transport plugin provides run dispatch for this project root.");
1011
+ return service.dispatchRun(input);
1012
+ },
488
1013
  awaitRunTerminal: async (projectRoot, runId) => {
489
1014
  const awaitedRunId = runId;
490
1015
  return awaitTerminalRun({
491
1016
  runId: awaitedRunId,
492
1017
  timeoutMs,
493
1018
  readStatus: async (id) => {
494
- const { listRuns } = await loadSupervisorClient();
1019
+ const { listRuns } = await loadSupervisorClient(projectRoot);
495
1020
  const run = (await listRuns(projectRoot)).find((candidate) => candidate.runId === id);
496
1021
  return run ? { runId: id, status: asRunStatus(run.status), ...run.errorSummary ? { diagnostic: run.errorSummary } : {} } : null;
497
1022
  },
@@ -521,13 +1046,14 @@ async function executeLoop(context, args) {
521
1046
  const stopWhen = takeOption(task.rest, "--stop-when");
522
1047
  const timeout = takeOption(stopWhen.rest, "--timeout-ms");
523
1048
  requireNoExtraArgs(timeout.rest, "rig loop [--task <id>] [--max-tasks <n>] [--concurrency <n>] [--stop-when <csv>] [--dry-run] [--json]");
524
- const result = await runSupervisorLoop(context.projectRoot, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 30 * 60 * 1000)), {
1049
+ const { runSupervisorLoop: runSupervisorLoop2 } = await Promise.resolve().then(() => (init_loop(), exports_loop));
1050
+ const result = await runSupervisorLoop2(context.projectRoot, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 1800000)), {
525
1051
  maxTasks: parsePositiveInt(maxTasks.value, "--max-tasks", 1),
526
1052
  concurrency: parsePositiveInt(concurrency.value, "--concurrency", 1),
527
1053
  ...task.value ? { candidateTaskIds: [task.value] } : {},
528
- dryRun: dry.value || context.dryRun
1054
+ dryRun: Boolean(dry.value || context.dryRun)
529
1055
  });
530
- const details = { ...result, stopWhen: parseCsv(stopWhen.value) };
1056
+ const details = { ...result, stopWhen: stopWhen.value?.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) ?? [] };
531
1057
  if (context.outputMode === "text") {
532
1058
  if (json.value)
533
1059
  printJson(details);
@@ -542,7 +1068,8 @@ async function executeUnblock(context, args) {
542
1068
  const timeout = takeOption(json.rest, "--timeout-ms");
543
1069
  const taskId = timeout.rest[0]?.startsWith("-") ? undefined : timeout.rest[0];
544
1070
  requireNoExtraArgs(taskId ? timeout.rest.slice(1) : timeout.rest, "rig unblock [task-id] [--dry-run] [--json]");
545
- const result = await unblockTask(context.projectRoot, taskId ?? null, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 30 * 60 * 1000)), { dryRun: dry.value || context.dryRun });
1071
+ const { unblockTask: unblockTask2 } = await Promise.resolve().then(() => (init_loop(), exports_loop));
1072
+ const result = await unblockTask2(context.projectRoot, taskId ?? null, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 1800000)), { dryRun: Boolean(dry.value || context.dryRun) });
546
1073
  if (context.outputMode === "text") {
547
1074
  if (json.value)
548
1075
  printJson(result);
@@ -571,6 +1098,11 @@ var supervisorCliCommands = [
571
1098
  run: executeUnblock
572
1099
  }
573
1100
  ];
1101
+
1102
+ // packages/supervisor-plugin/src/index.ts
1103
+ init_loop();
1104
+ init_journal();
1105
+
574
1106
  // packages/supervisor-plugin/src/plugin.ts
575
1107
  import { RIG_CAPABILITY_PANEL_SLOT, RIG_SUPERVISOR_PANEL_ID } from "@rig/contracts";
576
1108
  import { definePlugin } from "@rig/core/config";
@@ -626,13 +1158,8 @@ function createSupervisorPlugin() {
626
1158
  return supervisorPlugin;
627
1159
  }
628
1160
  // packages/supervisor-plugin/src/supervisor.ts
629
- import {
630
- computeTaskDependencyBadges as computeTaskDependencyBadges2,
631
- disjointScope,
632
- isTaskTerminalStatus,
633
- readTaskScope
634
- } from "@rig/contracts";
635
- import { rankReadyTasks as rankReadyTasks2 } from "@rig/dependency-graph-plugin";
1161
+ init_taskGraphPrimitives();
1162
+ init_taskRanking();
636
1163
  var DEFAULT_STOP_WHEN = new Set(["all-done", "all-human-blocked", "source-error"]);
637
1164
  var HUMAN_BLOCKER_CLASS = {
638
1165
  "not-blocked": false,
@@ -679,14 +1206,14 @@ function filterTask(task, filter) {
679
1206
  }
680
1207
  return true;
681
1208
  }
682
- function classifyEmptyReadySet(snapshot, badges, classifyBlocker) {
1209
+ function classifyEmptyReadySet(snapshot, badges, classifyBlocker2) {
683
1210
  if ((snapshot.diagnostics ?? []).length > 0)
684
1211
  return "source-error";
685
1212
  const openTasks = snapshot.tasks.filter((task) => !isTaskTerminalStatus(task.status));
686
1213
  if (openTasks.length === 0)
687
1214
  return "all-done";
688
- if (classifyBlocker) {
689
- const allHumanBlocked = openTasks.every((task) => HUMAN_BLOCKER_CLASS[classifyBlocker(task, badges).blockerClass]);
1215
+ if (classifyBlocker2) {
1216
+ const allHumanBlocked = openTasks.every((task) => HUMAN_BLOCKER_CLASS[classifyBlocker2(task, badges).blockerClass]);
690
1217
  if (allHumanBlocked)
691
1218
  return "all-human-blocked";
692
1219
  }
@@ -715,11 +1242,11 @@ async function runSupervisor(ctx, options = {}) {
715
1242
  break;
716
1243
  }
717
1244
  const snapshot = await ctx.readTasks();
718
- const badges = computeTaskDependencyBadges2(snapshot.tasks);
1245
+ const badges = computeTaskDependencyBadges(snapshot.tasks);
719
1246
  const activeRuns = snapshot.activeRuns ?? [];
720
1247
  const activeTaskIds2 = activeRuns.map((run) => run.taskId);
721
1248
  const remainingSlots = normalized.maxTasks === null ? normalized.concurrency : Math.min(normalized.concurrency, normalized.maxTasks - processed);
722
- const ranked = rankReadyTasks2(snapshot.tasks, {
1249
+ const ranked = rankReadyTasks(snapshot.tasks, {
723
1250
  excludeTaskIds: completedTaskIds,
724
1251
  activeTaskIds: activeTaskIds2,
725
1252
  filter: (task) => filterTask(task, normalized.filter),