@h-rig/supervisor-plugin 0.0.6-alpha.156 → 0.0.6-alpha.158

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/cli.js CHANGED
@@ -1,45 +1,455 @@
1
1
  // @bun
2
- var __require = import.meta.require;
3
-
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
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
+ });
20
15
  };
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
- };
16
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
+
18
+ // packages/supervisor-plugin/src/run-status.ts
19
+ function coerceRunStatus(value, fallback) {
20
+ return KNOWN_RUN_STATUS.has(value) ? value : fallback;
21
+ }
22
+ var KNOWN_RUN_STATUS;
23
+ var init_run_status = __esm(() => {
24
+ KNOWN_RUN_STATUS = new Set([
25
+ "created",
26
+ "queued",
27
+ "preparing",
28
+ "running",
29
+ "validating",
30
+ "reviewing",
31
+ "closing-out",
32
+ "completed",
33
+ "failed",
34
+ "stopped"
35
+ ]);
36
+ });
37
+
38
+ // packages/supervisor-plugin/src/analysis/taskGraphPrimitives.ts
39
+ import {
40
+ OPERATOR_INACTIVE_RUN_STATUSES
41
+ } from "@rig/contracts";
42
+ function isObjectRecord(value) {
43
+ return typeof value === "object" && value !== null && !Array.isArray(value);
44
+ }
45
+ function readStringList(value) {
46
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
47
+ }
48
+ function unique(values) {
49
+ return Array.from(new Set(values));
50
+ }
51
+ function readTaskMetadataStringList(task, key) {
52
+ const taskRecord = task;
53
+ const topLevel = readStringList(taskRecord[key]);
54
+ if (topLevel.length > 0)
55
+ return topLevel;
56
+ const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
57
+ const metadataList = readStringList(metadata?.[key]);
58
+ if (metadataList.length > 0)
59
+ return metadataList;
60
+ if (key === "dependencies") {
61
+ return readStringList(metadata?.deps);
62
+ }
63
+ return [];
64
+ }
65
+ function readTaskBlockingDependencyRefs(task) {
66
+ return readTaskMetadataStringList(task, "dependencies");
67
+ }
68
+ function readTaskSourceIssueId(task) {
69
+ if (typeof task.sourceIssueId === "string" && task.sourceIssueId.length > 0) {
70
+ return task.sourceIssueId;
71
+ }
72
+ const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
73
+ if (typeof metadata?.sourceIssueId === "string" && metadata.sourceIssueId.length > 0) {
74
+ return metadata.sourceIssueId;
75
+ }
76
+ const rigMetadata = isObjectRecord(metadata?._rig) ? metadata._rig : null;
77
+ return typeof rigMetadata?.sourceIssueId === "string" && rigMetadata.sourceIssueId.length > 0 ? rigMetadata.sourceIssueId : null;
78
+ }
79
+ function readTaskScope(task) {
80
+ const taskRecord = task;
81
+ const topLevel = readStringList(taskRecord.scope);
82
+ if (topLevel.length > 0)
83
+ return unique(topLevel.map((entry) => entry.trim()).filter((entry) => entry.length > 0));
84
+ const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
85
+ const metadataScope = readStringList(metadata?.scope);
86
+ if (metadataScope.length > 0)
87
+ return unique(metadataScope.map((entry) => entry.trim()).filter((entry) => entry.length > 0));
88
+ return unique([
89
+ ...readStringList(metadata?.files),
90
+ ...readStringList(metadata?.paths)
91
+ ].map((entry) => entry.trim()).filter((entry) => entry.length > 0));
92
+ }
93
+ function resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId) {
94
+ if (tasksById.has(ref))
95
+ return ref;
96
+ return taskIdBySourceIssueId.get(ref) ?? taskIdByExternalRef.get(ref) ?? null;
97
+ }
98
+ function buildTaskReferenceIndex(tasks) {
99
+ return {
100
+ tasksById: new Map(tasks.map((task) => [task.id, task])),
101
+ taskIdByExternalRef: new Map(tasks.flatMap((task) => task.externalId ? [[task.externalId, task.id]] : [])),
102
+ taskIdBySourceIssueId: new Map(tasks.flatMap((task) => {
103
+ const sourceIssueId = readTaskSourceIssueId(task);
104
+ return sourceIssueId ? [[sourceIssueId, task.id]] : [];
105
+ }))
106
+ };
107
+ }
108
+ function computeTaskBlockingDepths(tasks) {
109
+ const { tasksById, taskIdByExternalRef, taskIdBySourceIssueId } = buildTaskReferenceIndex(tasks);
110
+ const memo = new Map;
111
+ const visit = (taskId, stack) => {
112
+ const cached = memo.get(taskId);
113
+ if (cached !== undefined)
114
+ return cached;
115
+ if (stack.has(taskId))
116
+ return 0;
117
+ const task = tasksById.get(taskId);
118
+ if (!task)
119
+ return 0;
120
+ stack.add(taskId);
121
+ const blockers = readTaskBlockingDependencyRefs(task).map((ref) => resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId)).filter((ref) => ref !== null && ref !== taskId);
122
+ const depth = blockers.length === 0 ? 0 : Math.max(...blockers.map((blockerId) => visit(blockerId, stack) + 1));
123
+ stack.delete(taskId);
124
+ memo.set(taskId, depth);
125
+ return depth;
126
+ };
127
+ for (const task of tasks) {
128
+ visit(task.id, new Set);
129
+ }
130
+ return memo;
131
+ }
132
+ function isTaskTerminalStatus(status) {
133
+ switch (status) {
134
+ case "closed":
135
+ case "completed":
136
+ case "done":
137
+ case "cancelled":
138
+ case "canceled":
139
+ return true;
140
+ default:
141
+ return false;
142
+ }
143
+ }
144
+ function isTaskBlockedStatus(status) {
145
+ return status === "blocked";
146
+ }
147
+ function isTaskRunnableStatus(status) {
148
+ if (status === null || status === undefined || status === "")
149
+ return true;
150
+ if (isTaskTerminalStatus(status) || isTaskBlockedStatus(status))
151
+ return false;
152
+ switch (status) {
153
+ case "ready":
154
+ case "open":
155
+ case "failed":
156
+ return true;
157
+ default:
158
+ return false;
159
+ }
160
+ }
161
+ function computeTaskDependencyBadges(tasks) {
162
+ const index = buildTaskReferenceIndex(tasks);
163
+ const blockingDepths = computeTaskBlockingDepths(tasks);
164
+ const dependencyIdsByTask = new Map;
165
+ const unresolvedRefsByTask = new Map;
166
+ const blocksByTask = new Map;
167
+ for (const task of tasks) {
168
+ const dependencyIds = [];
169
+ const unresolvedRefs = [];
170
+ for (const ref of readTaskBlockingDependencyRefs(task)) {
171
+ const dependencyId = resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
172
+ if (dependencyId && dependencyId !== task.id) {
173
+ dependencyIds.push(dependencyId);
174
+ const blocks = blocksByTask.get(dependencyId);
175
+ if (blocks) {
176
+ blocks.push(task.id);
177
+ } else {
178
+ blocksByTask.set(dependencyId, [task.id]);
179
+ }
180
+ } else {
181
+ unresolvedRefs.push(ref);
182
+ }
32
183
  }
33
- if (options.timeoutMs !== undefined && Date.now() - startedAt >= options.timeoutMs) {
34
- return { runId: options.runId, status: "needs-attention", failed: true };
184
+ dependencyIdsByTask.set(task.id, unique(dependencyIds));
185
+ unresolvedRefsByTask.set(task.id, unique(unresolvedRefs));
186
+ }
187
+ const summaries = new Map;
188
+ for (const task of tasks) {
189
+ const dependencyIds = dependencyIdsByTask.get(task.id) ?? [];
190
+ const unresolvedDependencyRefs = unresolvedRefsByTask.get(task.id) ?? [];
191
+ const blockedBy = dependencyIds.filter((dependencyId) => {
192
+ const dependency = index.tasksById.get(dependencyId);
193
+ return dependency ? !isTaskTerminalStatus(dependency.status) : false;
194
+ });
195
+ const blocks = unique(blocksByTask.get(task.id) ?? []);
196
+ const blocked = isTaskBlockedStatus(task.status) || blockedBy.length > 0;
197
+ const ready = isTaskRunnableStatus(task.status) && !blocked;
198
+ const badges = [];
199
+ if (blocked) {
200
+ badges.push({
201
+ kind: "blocked",
202
+ label: blockedBy.length > 0 ? `blocked \xD7${blockedBy.length}` : "blocked",
203
+ description: blockedBy.length > 0 ? `Waiting on ${blockedBy.join(", ")}.` : "Task source marks this task blocked.",
204
+ ...blockedBy.length > 0 ? { count: blockedBy.length } : {},
205
+ taskIds: blockedBy
206
+ });
207
+ } else if (ready) {
208
+ badges.push({
209
+ kind: "ready",
210
+ label: "ready",
211
+ description: "No open dependencies block this task."
212
+ });
35
213
  }
36
- await options.waitForChange(pollMs);
214
+ if (dependencyIds.length > 0 || blocks.length > 0 || unresolvedDependencyRefs.length > 0) {
215
+ badges.push({
216
+ kind: "dependency",
217
+ label: `deps ${dependencyIds.length}/${blocks.length}`,
218
+ description: [
219
+ dependencyIds.length > 0 ? `Depends on ${dependencyIds.join(", ")}.` : null,
220
+ blocks.length > 0 ? `Blocks ${blocks.join(", ")}.` : null,
221
+ unresolvedDependencyRefs.length > 0 ? `Unresolved refs: ${unresolvedDependencyRefs.join(", ")}.` : null
222
+ ].filter((part) => part !== null).join(" "),
223
+ count: dependencyIds.length + blocks.length,
224
+ taskIds: unique([...dependencyIds, ...blocks])
225
+ });
226
+ }
227
+ summaries.set(task.id, {
228
+ taskId: task.id,
229
+ blockingDepth: blockingDepths.get(task.id) ?? 0,
230
+ dependencyIds,
231
+ unresolvedDependencyRefs,
232
+ blockedBy,
233
+ blocks,
234
+ blocked,
235
+ ready,
236
+ dependencyCount: dependencyIds.length,
237
+ dependentCount: blocks.length,
238
+ badges
239
+ });
37
240
  }
241
+ return summaries;
242
+ }
243
+ function stringValue(value) {
244
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
245
+ }
246
+ function stringArray(value) {
247
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim().length > 0).map((entry) => entry.trim()) : [];
248
+ }
249
+ function numberOrNull(value) {
250
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
251
+ }
252
+ function metadataOf(task) {
253
+ return isObjectRecord(task.metadata) ? task.metadata : {};
38
254
  }
255
+ function normalizeTaskStatus(status) {
256
+ const token = typeof status === "string" ? status.trim().toLowerCase() : "";
257
+ if (token === "done")
258
+ return "completed";
259
+ if (token === "canceled")
260
+ return "cancelled";
261
+ return TASK_STATUSES.has(token) ? token : "unknown";
262
+ }
263
+ function toTaskDependencyProjection(task) {
264
+ const metadata = metadataOf(task);
265
+ return {
266
+ id: String(task.id),
267
+ title: stringValue(task.title),
268
+ status: normalizeTaskStatus(task.status),
269
+ priority: numberOrNull(task.priority),
270
+ metadata,
271
+ externalId: stringValue(task.externalId),
272
+ sourceIssueId: stringValue(task.sourceIssueId),
273
+ dependencies: stringArray(task.dependencies),
274
+ parentChildDeps: stringArray(task.parentChildDeps),
275
+ createdAt: stringValue(task.createdAt) ?? "",
276
+ updatedAt: stringValue(task.updatedAt) ?? "",
277
+ role: stringValue(task.role),
278
+ scope: stringArray(task.scope),
279
+ validationKeys: stringArray(task.validationKeys),
280
+ labels: stringArray(task.labels),
281
+ assignees: task.assignees ?? null,
282
+ assignedTo: task.assignedTo ?? null
283
+ };
284
+ }
285
+ function latestRunByTaskId(runs) {
286
+ const byTask = new Map;
287
+ const stamp = (run) => Date.parse(run.updatedAt ?? run.startedAt ?? "") || 0;
288
+ for (const run of runs) {
289
+ if (!run.taskId)
290
+ continue;
291
+ const current = byTask.get(run.taskId);
292
+ if (!current || stamp(run) >= stamp(current))
293
+ byTask.set(run.taskId, run);
294
+ }
295
+ return byTask;
296
+ }
297
+ var TASK_STATUSES;
298
+ var init_taskGraphPrimitives = __esm(() => {
299
+ TASK_STATUSES = new Set([
300
+ "draft",
301
+ "open",
302
+ "ready",
303
+ "queued",
304
+ "running",
305
+ "in_progress",
306
+ "under_review",
307
+ "blocked",
308
+ "unknown",
309
+ "completed",
310
+ "failed",
311
+ "cancelled",
312
+ "closed"
313
+ ]);
314
+ });
39
315
 
40
- // packages/supervisor-plugin/src/loop.ts
41
- import { computeTaskDependencyBadges, toTaskDependencyProjection } from "@rig/contracts";
42
- import { rankReadyTasks, selectRankedReadyTasks } from "@rig/dependency-graph-plugin";
316
+ // packages/supervisor-plugin/src/analysis/taskScore.ts
317
+ function finiteNumber(value, fallback) {
318
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
319
+ }
320
+ function scoreTask(input) {
321
+ const priority = finiteNumber(input.priority, 3);
322
+ const unblockCount = Math.max(0, finiteNumber(input.unblockCount, 0));
323
+ const roleWeight = input.role ? ROLE_WEIGHT[input.role] ?? 0 : 0;
324
+ const criticalityWeight = input.criticality ? CRITICALITY_WEIGHT[input.criticality] ?? 0 : 0;
325
+ const validationWeight = (input.validation ?? []).some((entry) => entry.includes("test-contract") || entry.includes("test-boundary")) ? 8 : 0;
326
+ const queueWeight = finiteNumber(input.queueWeight, 0);
327
+ return unblockCount * 100 + (10 - priority) + roleWeight + criticalityWeight + validationWeight + queueWeight;
328
+ }
329
+ function rankTasks(tasks, scoreInput, idOf) {
330
+ return tasks.map((task) => {
331
+ const input = scoreInput(task);
332
+ return {
333
+ task,
334
+ score: scoreTask(input),
335
+ priority: finiteNumber(input.priority, 3),
336
+ unblockCount: Math.max(0, finiteNumber(input.unblockCount, 0))
337
+ };
338
+ }).toSorted((left, right) => {
339
+ const scoreDelta = right.score - left.score;
340
+ if (scoreDelta !== 0)
341
+ return scoreDelta;
342
+ const unblockDelta = right.unblockCount - left.unblockCount;
343
+ if (unblockDelta !== 0)
344
+ return unblockDelta;
345
+ const priorityDelta = left.priority - right.priority;
346
+ if (priorityDelta !== 0)
347
+ return priorityDelta;
348
+ return idOf(left.task).localeCompare(idOf(right.task));
349
+ });
350
+ }
351
+ var ROLE_WEIGHT, CRITICALITY_WEIGHT;
352
+ var init_taskScore = __esm(() => {
353
+ ROLE_WEIGHT = {
354
+ architect: 25,
355
+ extractor: 10
356
+ };
357
+ CRITICALITY_WEIGHT = {
358
+ core: 20,
359
+ high: 10,
360
+ normal: 0
361
+ };
362
+ });
363
+
364
+ // packages/supervisor-plugin/src/analysis/taskRanking.ts
365
+ function isObjectRecord2(value) {
366
+ return typeof value === "object" && value !== null && !Array.isArray(value);
367
+ }
368
+ function readStringList2(value) {
369
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
370
+ }
371
+ function readTaskRole(task) {
372
+ if (typeof task.role === "string" && task.role.trim())
373
+ return task.role.trim();
374
+ const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
375
+ return typeof metadata?.role === "string" && metadata.role.trim() ? metadata.role.trim() : null;
376
+ }
377
+ function readTaskCriticality(task) {
378
+ const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
379
+ return typeof metadata?.criticality === "string" && metadata.criticality.trim() ? metadata.criticality.trim() : null;
380
+ }
381
+ function readTaskValidationKeys(task) {
382
+ const taskRecord = task;
383
+ const topLevel = readStringList2(taskRecord.validationKeys);
384
+ if (topLevel.length > 0)
385
+ return topLevel;
386
+ const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
387
+ return readStringList2(metadata?.validation);
388
+ }
389
+ function readTaskQueueWeight(task) {
390
+ const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
391
+ const queueWeight = metadata?.queueWeight ?? metadata?.queue_weight;
392
+ return typeof queueWeight === "number" && Number.isFinite(queueWeight) ? queueWeight : 0;
393
+ }
394
+ function scoreInputForTask(task, unblockCount) {
395
+ return {
396
+ priority: task.priority,
397
+ unblockCount,
398
+ role: readTaskRole(task),
399
+ criticality: readTaskCriticality(task),
400
+ validation: readTaskValidationKeys(task),
401
+ queueWeight: readTaskQueueWeight(task)
402
+ };
403
+ }
404
+ function rankReadyTasks(tasks, options = {}) {
405
+ const excluded = new Set(options.excludeTaskIds ?? []);
406
+ const activeTaskIds = new Set(options.activeTaskIds ?? []);
407
+ const badges = computeTaskDependencyBadges(tasks);
408
+ const tasksById = new Map(tasks.map((task) => [String(task.id), task]));
409
+ const openBlockCount = (task) => (badges.get(task.id)?.blocks ?? []).filter((blockedTaskId) => {
410
+ const blockedTask = tasksById.get(blockedTaskId);
411
+ return blockedTask ? !isTaskTerminalStatus(blockedTask.status) : false;
412
+ }).length;
413
+ 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);
414
+ const maxUnblockCount = Math.max(0, ...readyTasks.map(openBlockCount));
415
+ const selectedByMode = readyTasks.filter((task) => {
416
+ const unblockCount = openBlockCount(task);
417
+ if (options.selection === "blocking-only")
418
+ return unblockCount > 0;
419
+ if (options.selection === "max-unblock")
420
+ return maxUnblockCount > 0 && unblockCount === maxUnblockCount;
421
+ return true;
422
+ });
423
+ return rankTasks(selectedByMode, (task) => scoreInputForTask(task, openBlockCount(task)), (task) => task.id).map((entry) => ({
424
+ task: entry.task,
425
+ score: entry.score,
426
+ priority: entry.priority,
427
+ unblockCount: entry.unblockCount,
428
+ scope: readTaskScope(entry.task)
429
+ }));
430
+ }
431
+ function selectRankedReadyTasks(tasks, options = {}) {
432
+ const ranked = rankReadyTasks(tasks, options);
433
+ if (options.requireDisjointScopes !== true && !options.disjointWithScopes) {
434
+ return ranked.slice(0, options.limit).map((entry) => entry.task);
435
+ }
436
+ const occupiedScopes = new Set(options.disjointWithScopes ?? []);
437
+ const selected = [];
438
+ for (const entry of ranked) {
439
+ if (entry.scope.some((scope) => occupiedScopes.has(scope)))
440
+ continue;
441
+ selected.push(entry.task);
442
+ for (const scope of entry.scope)
443
+ occupiedScopes.add(scope);
444
+ if (options.limit !== undefined && selected.length >= options.limit)
445
+ break;
446
+ }
447
+ return selected;
448
+ }
449
+ var init_taskRanking = __esm(() => {
450
+ init_taskGraphPrimitives();
451
+ init_taskScore();
452
+ });
43
453
 
44
454
  // packages/supervisor-plugin/src/journal.ts
45
455
  import { Schema } from "effect";
@@ -119,9 +529,100 @@ function reduceSupervisorJournal(events) {
119
529
  }
120
530
  return { status, processed, succeeded, failed, skipped, current, plannedOrder, selectionPolicy, concurrency, idleReason, stopReason, closures, anomalies };
121
531
  }
532
+ var init_journal = () => {};
533
+
534
+ // packages/supervisor-plugin/src/analysis/blockers.ts
535
+ function labelsFor(task) {
536
+ return readTaskMetadataStringList(task, "labels").map((label) => label.toLowerCase());
537
+ }
538
+ function configuredTier(task, labels) {
539
+ const metadata = task.metadata && typeof task.metadata === "object" && !Array.isArray(task.metadata) ? task.metadata : {};
540
+ const tier = metadata.riskTier ?? metadata.actionRiskTier;
541
+ if (tier === "t1-read" || tier === "t2-reversible" || tier === "t3-external" || tier === "t4-irreversible")
542
+ return tier;
543
+ if (labels.some((label) => /prod|deploy|migration|billing|delete|irreversible/.test(label)))
544
+ return "t4-irreversible";
545
+ if (labels.some((label) => /customer|vendor|external|secret|credential|approval|decision/.test(label)))
546
+ return "t3-external";
547
+ const scopes = task.scope ?? [];
548
+ if (scopes.some((scope) => /docs|readme|test|spec/.test(scope.toLowerCase())))
549
+ return "t1-read";
550
+ return "t2-reversible";
551
+ }
552
+ function tierOf(task, labels = []) {
553
+ const projection = "metadata" in task && "id" in task && typeof task.id === "string" ? toTaskDependencyProjection(task) : task;
554
+ return configuredTier(projection, labels.length > 0 ? labels : labelsFor(projection)) ?? "t2-reversible";
555
+ }
556
+ function baseClassification(task, blockerClass, source, rationale, labels) {
557
+ return {
558
+ taskId: task.id,
559
+ blockerClass,
560
+ actionRiskTier: tierOf(task, labels),
561
+ rationale,
562
+ source,
563
+ autoApplied: source === "llm"
564
+ };
565
+ }
566
+ function normalizeRunStatus(status) {
567
+ if (status === "waiting-approval" || status === "waiting-user-input" || status === "needs-attention" || status === "completed" || status === "failed" || status === "stopped" || status === "running")
568
+ return status;
569
+ return null;
570
+ }
571
+ function isHumanBlockerClass(blockerClass) {
572
+ return HUMAN_BLOCKERS.has(blockerClass);
573
+ }
574
+ function classifyBlocker(input) {
575
+ const labels = input.labels ?? labelsFor(input.task);
576
+ const runStatus = normalizeRunStatus(input.run?.status);
577
+ if (runStatus === "waiting-approval")
578
+ return baseClassification(input.task, "human-approval", "status", "run awaiting approval", labels);
579
+ if (runStatus === "waiting-user-input")
580
+ return baseClassification(input.task, "external-input", "status", "run awaiting user input", labels);
581
+ if (input.task.status !== "blocked")
582
+ return baseClassification(input.task, "not-blocked", "elimination", "task is not blocked", labels);
583
+ const incomplete = (input.badges.get(input.task.id)?.blockedBy ?? []).filter((dependencyId) => {
584
+ const dependency = input.tasksById.get(dependencyId);
585
+ return dependency ? !isTaskTerminalStatus(dependency.status) : false;
586
+ });
587
+ if (incomplete.length > 0)
588
+ return baseClassification(input.task, "task-blocked", "elimination", `blocked by ${incomplete.length} incomplete task(s)`, labels);
589
+ if (labels.includes("needs-decision"))
590
+ return baseClassification(input.task, "human-decision", "label", "needs-decision label", labels);
591
+ if (labels.some((label) => /^waiting-on-/.test(label)))
592
+ return baseClassification(input.task, "external-input", "label", "waiting-on-* label", labels);
593
+ if (input.config?.llm)
594
+ return baseClassification(input.task, "human-decision", "llm", "LLM residue classifier marked this as human-gated", labels);
595
+ return baseClassification(input.task, "human-decision", "elimination", "blocked, all task dependencies terminal; non-task gate remains", labels);
596
+ }
597
+ function classifyTasks(tasks, runs = [], options = {}) {
598
+ const projected = tasks.map(toTaskDependencyProjection);
599
+ const badges = computeTaskDependencyBadges(projected);
600
+ const tasksById = new Map(projected.map((task) => [task.id, task]));
601
+ const runByTask = latestRunByTaskId(runs);
602
+ const classifier = options.classifier ?? classifyBlocker;
603
+ 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));
604
+ return {
605
+ classifications,
606
+ byTaskId: new Map(classifications.map((classification) => [classification.taskId, classification])),
607
+ human: classifications.filter((classification) => isHumanBlockerClass(classification.blockerClass)),
608
+ machine: classifications.filter((classification) => !isHumanBlockerClass(classification.blockerClass)),
609
+ generatedAt: options.generatedAt ?? new Date().toISOString()
610
+ };
611
+ }
612
+ var HUMAN_BLOCKERS;
613
+ var init_blockers = __esm(() => {
614
+ init_taskGraphPrimitives();
615
+ HUMAN_BLOCKERS = new Set(["human-decision", "human-approval", "external-input"]);
616
+ });
122
617
 
123
618
  // packages/supervisor-plugin/src/loop.ts
124
- import { classifyTasks, isHumanBlockerClass } from "@rig/blocker-classifier-plugin";
619
+ var exports_loop = {};
620
+ __export(exports_loop, {
621
+ unblockTask: () => unblockTask,
622
+ runSupervisorLoop: () => runSupervisorLoop,
623
+ planSupervisorLoop: () => planSupervisorLoop,
624
+ controlSupervisorRun: () => controlSupervisorRun
625
+ });
125
626
  function selectionMode(policy) {
126
627
  return policy === "blocking-only" || policy === "max-unblock" ? policy : "all-ready";
127
628
  }
@@ -180,25 +681,7 @@ function failedOutcome(outcome) {
180
681
  return outcome.status === "failed" || outcome.status === "stopped" || outcome.status === "needs-attention" || outcome.status === "waiting-approval" || outcome.status === "waiting-user-input";
181
682
  }
182
683
  function runStatus(value) {
183
- switch (value) {
184
- case "created":
185
- case "queued":
186
- case "preparing":
187
- case "running":
188
- case "waiting-approval":
189
- case "waiting-user-input":
190
- case "paused":
191
- case "validating":
192
- case "reviewing":
193
- case "closing-out":
194
- case "needs-attention":
195
- case "completed":
196
- case "failed":
197
- case "stopped":
198
- return value;
199
- default:
200
- return "failed";
201
- }
684
+ return coerceRunStatus(value, "failed");
202
685
  }
203
686
  async function runSupervisorLoop(projectRoot, deps, options = {}) {
204
687
  const events = [{ kind: "supervisor.started", at: at(deps), options }];
@@ -269,6 +752,10 @@ async function runSupervisorLoop(projectRoot, deps, options = {}) {
269
752
  const projection = reduceSupervisorJournal(events);
270
753
  return { ok: failed === 0, dryRun: options.dryRun === true, plannedOrder: plannedAll, events, projection };
271
754
  }
755
+ async function controlSupervisorRun(projectRoot, runId, control, deps) {
756
+ await deps.deliverRunControl(projectRoot, runId, control);
757
+ return { ok: true, runId, control: control.kind };
758
+ }
272
759
  function collectBlockingClosure(taskId, badges) {
273
760
  const closure = new Set;
274
761
  const visit = (currentTaskId) => {
@@ -291,10 +778,60 @@ async function unblockTask(projectRoot, taskId, deps, options = {}) {
291
778
  const blockers = collectBlockingClosure(taskId, badges);
292
779
  return runSupervisorLoop(projectRoot, deps, { ...options, candidateTaskIds: blockers, selectionPolicy: "rank", maxTasks: 1 });
293
780
  }
781
+ var init_loop = __esm(() => {
782
+ init_taskGraphPrimitives();
783
+ init_taskRanking();
784
+ init_journal();
785
+ init_run_status();
786
+ init_blockers();
787
+ });
294
788
 
295
789
  // packages/supervisor-plugin/src/cli.ts
790
+ import { RUN_DISPATCH, RUN_READ_MODEL, TASK_IO_SERVICE_CAPABILITY } from "@rig/contracts";
791
+ import { defineCapability } from "@rig/core/capability";
792
+ import { requireCapabilityForRoot } from "@rig/core/capability-loaders";
793
+
794
+ // packages/supervisor-plugin/src/awaiter.ts
795
+ var TERMINAL_RUN_STATUSES = {
796
+ created: false,
797
+ queued: false,
798
+ preparing: false,
799
+ running: false,
800
+ "waiting-approval": false,
801
+ "waiting-user-input": false,
802
+ paused: false,
803
+ validating: false,
804
+ reviewing: false,
805
+ "closing-out": false,
806
+ "needs-attention": true,
807
+ completed: true,
808
+ failed: true,
809
+ stopped: true
810
+ };
811
+ async function awaitTerminalRun(options) {
812
+ const startedAt = Date.now();
813
+ const pollMs = Math.max(0, Math.floor(options.pollMs ?? 5000));
814
+ while (true) {
815
+ const snapshot = await options.readStatus(options.runId);
816
+ if (snapshot && TERMINAL_RUN_STATUSES[snapshot.status]) {
817
+ return {
818
+ runId: options.runId,
819
+ status: snapshot.status,
820
+ failed: snapshot.status !== "completed"
821
+ };
822
+ }
823
+ if (options.timeoutMs !== undefined && Date.now() - startedAt >= options.timeoutMs) {
824
+ return { runId: options.runId, status: "needs-attention", failed: true };
825
+ }
826
+ await options.waitForChange(pollMs);
827
+ }
828
+ }
829
+
830
+ // packages/supervisor-plugin/src/cli.ts
831
+ init_run_status();
296
832
  var SUPERVISOR_LOOP_CLI_ID = "supervisor.loop";
297
833
  var SUPERVISOR_UNBLOCK_CLI_ID = "supervisor.unblock";
834
+ var RunReadModelCap = defineCapability(RUN_READ_MODEL);
298
835
  function printJson(value) {
299
836
  console.log(JSON.stringify(value, null, 2));
300
837
  }
@@ -331,55 +868,36 @@ function parsePositiveInt(value, flag, fallback) {
331
868
  }
332
869
  return parsed;
333
870
  }
334
- function parseCsv(value) {
335
- return value?.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) ?? [];
336
- }
337
871
  function asRunStatus(value) {
338
- switch (value) {
339
- case "created":
340
- case "queued":
341
- case "preparing":
342
- case "running":
343
- case "waiting-approval":
344
- case "waiting-user-input":
345
- case "paused":
346
- case "validating":
347
- case "reviewing":
348
- case "closing-out":
349
- case "needs-attention":
350
- case "completed":
351
- case "failed":
352
- case "stopped":
353
- return value;
354
- default:
355
- return "needs-attention";
356
- }
872
+ return coerceRunStatus(value, "needs-attention");
357
873
  }
358
874
  function delay(ms) {
359
875
  const { promise, resolve } = Promise.withResolvers();
360
876
  setTimeout(resolve, ms);
361
877
  return promise;
362
878
  }
363
- async function loadSupervisorClient() {
364
- const [taskIo, runIo, dispatchIo] = await Promise.all([
365
- import("@rig/core/task-io"),
366
- import("@rig/run-worker/runs"),
367
- import("@rig/runtime/control-plane/dispatch")
879
+ async function loadSupervisorClient(projectRoot) {
880
+ const [taskIo, runReadModel] = await Promise.all([
881
+ requireCapabilityForRoot(projectRoot, defineCapability(TASK_IO_SERVICE_CAPABILITY), "No task-sources plugin provides task IO for this project root."),
882
+ requireCapabilityForRoot(projectRoot, RunReadModelCap, "No run-worker plugin provides run read-model for this project root.")
368
883
  ]);
369
- return { listTasks: taskIo.listTasks, listRuns: runIo.listRuns, dispatchRun: dispatchIo.dispatchRun };
884
+ return { listTasks: taskIo.listTasks, listRuns: (root) => runReadModel.listRuns({ projectRoot: root }) };
370
885
  }
371
886
  function supervisorDeps(timeoutMs) {
372
887
  return {
373
- listTasks: async (projectRoot) => (await loadSupervisorClient()).listTasks(projectRoot),
374
- listRuns: async (projectRoot) => (await loadSupervisorClient()).listRuns(projectRoot),
375
- dispatchRun: async (...args) => (await loadSupervisorClient()).dispatchRun(...args),
888
+ listTasks: async (projectRoot) => (await loadSupervisorClient(projectRoot)).listTasks(projectRoot),
889
+ listRuns: async (projectRoot) => (await loadSupervisorClient(projectRoot)).listRuns(projectRoot),
890
+ dispatchRun: async (input) => {
891
+ const service = await requireCapabilityForRoot(input.projectRoot, defineCapability(RUN_DISPATCH), "No transport plugin provides run dispatch for this project root.");
892
+ return service.dispatchRun(input);
893
+ },
376
894
  awaitRunTerminal: async (projectRoot, runId) => {
377
895
  const awaitedRunId = runId;
378
896
  return awaitTerminalRun({
379
897
  runId: awaitedRunId,
380
898
  timeoutMs,
381
899
  readStatus: async (id) => {
382
- const { listRuns } = await loadSupervisorClient();
900
+ const { listRuns } = await loadSupervisorClient(projectRoot);
383
901
  const run = (await listRuns(projectRoot)).find((candidate) => candidate.runId === id);
384
902
  return run ? { runId: id, status: asRunStatus(run.status), ...run.errorSummary ? { diagnostic: run.errorSummary } : {} } : null;
385
903
  },
@@ -409,13 +927,14 @@ async function executeLoop(context, args) {
409
927
  const stopWhen = takeOption(task.rest, "--stop-when");
410
928
  const timeout = takeOption(stopWhen.rest, "--timeout-ms");
411
929
  requireNoExtraArgs(timeout.rest, "rig loop [--task <id>] [--max-tasks <n>] [--concurrency <n>] [--stop-when <csv>] [--dry-run] [--json]");
412
- const result = await runSupervisorLoop(context.projectRoot, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 30 * 60 * 1000)), {
930
+ const { runSupervisorLoop: runSupervisorLoop2 } = await Promise.resolve().then(() => (init_loop(), exports_loop));
931
+ const result = await runSupervisorLoop2(context.projectRoot, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 1800000)), {
413
932
  maxTasks: parsePositiveInt(maxTasks.value, "--max-tasks", 1),
414
933
  concurrency: parsePositiveInt(concurrency.value, "--concurrency", 1),
415
934
  ...task.value ? { candidateTaskIds: [task.value] } : {},
416
- dryRun: dry.value || context.dryRun
935
+ dryRun: Boolean(dry.value || context.dryRun)
417
936
  });
418
- const details = { ...result, stopWhen: parseCsv(stopWhen.value) };
937
+ const details = { ...result, stopWhen: stopWhen.value?.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) ?? [] };
419
938
  if (context.outputMode === "text") {
420
939
  if (json.value)
421
940
  printJson(details);
@@ -430,7 +949,8 @@ async function executeUnblock(context, args) {
430
949
  const timeout = takeOption(json.rest, "--timeout-ms");
431
950
  const taskId = timeout.rest[0]?.startsWith("-") ? undefined : timeout.rest[0];
432
951
  requireNoExtraArgs(taskId ? timeout.rest.slice(1) : timeout.rest, "rig unblock [task-id] [--dry-run] [--json]");
433
- const result = await unblockTask(context.projectRoot, taskId ?? null, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 30 * 60 * 1000)), { dryRun: dry.value || context.dryRun });
952
+ const { unblockTask: unblockTask2 } = await Promise.resolve().then(() => (init_loop(), exports_loop));
953
+ const result = await unblockTask2(context.projectRoot, taskId ?? null, supervisorDeps(parsePositiveInt(timeout.value, "--timeout-ms", 1800000)), { dryRun: Boolean(dry.value || context.dryRun) });
434
954
  if (context.outputMode === "text") {
435
955
  if (json.value)
436
956
  printJson(result);